Skip to content

Commit

Permalink
feat(autoware_debug_tools): add system_usage_monitor.py (#85)
Browse files Browse the repository at this point in the history
* feat(autoware_debug_tools): add system_usage_monitor.py

Signed-off-by: Takayuki Murooka <takayuki5168@gmail.com>

* publish system usage

Signed-off-by: Takayuki Murooka <takayuki5168@gmail.com>

---------

Signed-off-by: Takayuki Murooka <takayuki5168@gmail.com>
  • Loading branch information
takayuki5168 authored Jul 25, 2024
1 parent 0cd9722 commit 3baf48f
Show file tree
Hide file tree
Showing 8 changed files with 184 additions and 107 deletions.
12 changes: 12 additions & 0 deletions common/autoware_debug_tools/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,15 @@ This tool visualizes `tier4_debug_msgs/msg/ProcessingTimeTree` messages.
3. Then, the visualizer will show the processing time tree.

![visualize-tree](images/visualize-tree.png)

## System Usage Monitor

The purpose of the System Usage Monitor is to monitor, visualize and publish the CPU usage and memory usage of the ROS processes. By providing a real-time terminal-based visualization, users can easily confirm the cpu and memory usage as in the picture below.

![system_usage_monitor](image/system_usage_monitor.png)

You can run the program by the following command.

```bash
ros2 run autoware_debug_tools system_usage_monitor
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
#!/usr/bin/env python3

# Copyright 2024 TIER IV, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import os
import signal
import sys
import threading

import psutil
import rclpy
from rclpy.node import Node
from tier4_debug_msgs.msg import SystemUsage
from tier4_debug_msgs.msg import SystemUsageArray


def signal_handler(sig, frame):
sys.exit(0)


def get_system_usage(pid, system_usages, interval):
try:
proc = psutil.Process(pid)
cpu_usage = proc.cpu_percent(interval=interval)
memory_usage = proc.memory_info().rss
cmdline = proc.cmdline()
process_name = " ".join(cmdline)
component = (
process_name.split("__ns:=/")[1].split("/")[0].split(" ")[0]
if "__ns:=/" in process_name
else ""
)
if component == "":
component = "others"
container = process_name.split("__node:=", 1)[1].split(" ")[0]
system_usages[pid] = {
"component": component,
"container": container,
"cpu_usage": cpu_usage,
"memory_usage": memory_usage,
}
except (psutil.NoSuchProcess, psutil.AccessDenied, psutil.ZombieProcess, IndexError):
pass


def print_system_usage(sorted_system_usages):
# clear terminal
os.system("clear")
if not sorted_system_usages:
print("No processes found with the specified name.")
return

print("|" + "-" * 202 + "|")
print(
"|"
+ "\033[1m"
+ "Process Information".center(72, " ")
+ "\033[0m"
+ "|"
+ "\033[1m"
+ "CPU Usage".center(62, " ")
+ "\033[0m"
+ "|"
+ "\033[1m"
+ "Memory Usage".center(66, " ")
+ "\033[0m"
+ "|"
)
print("|" + "-" * 202 + "|")

last_component = None
for pid, data in sorted_system_usages:
component = data["component"]
container = data["container"]
cpu_usage = data["cpu_usage"]
memory_usage = data["memory_usage"] / 1024**2
cpu_bar = "#" * int(cpu_usage * 0.5)
memory_bar = "#" * int(memory_usage * 0.06)

if last_component and last_component != component:
print(
"|"
+ "-" * 16
+ "|"
+ "-" * 55
+ "|"
+ "-" * 9
+ "|"
+ "-" * 52
+ "|"
+ "-" * 13
+ "|"
+ "-" * 52
+ "|"
)

last_component = component
process_info = f"| {component.split('/')[-1].ljust(14)} | {container.ljust(53)} | {cpu_usage:4.1f}[%] | {cpu_bar:<50} | {memory_usage:6.1f}[MiB] | {memory_bar:<50} |"
print(process_info)

print("|" + "-" * 202 + "|")


def main(args=None):
signal.signal(signal.SIGINT, signal_handler)

rclpy.init(args=args)
node = Node("system_usage_monitor")

pub_system_usage = node.create_publisher(
SystemUsageArray,
"~/system_usage",
1,
)

system_usages = {}
while True:
# create thread to calculate cpu usage of each process since it takes time
process_name_keyword = "__node:="
threads = []
for proc in psutil.process_iter(["pid", "name", "cpu_percent", "cmdline"]):
try:
if process_name_keyword in " ".join(proc.info["cmdline"]):
pid = proc.info["pid"]
thread = threading.Thread(
target=get_system_usage, args=(pid, system_usages, 1.0)
)
threads.append(thread)
thread.start()
except (psutil.NoSuchProcess, psutil.AccessDenied, psutil.ZombieProcess):
pass

# wait for all the thread to finish
for thread in threads:
thread.join()

# sort in the order of component name
sorted_system_usages = sorted(
system_usages.items(), key=lambda x: x[1]["component"] + x[1]["container"]
)

# print system usage
print_system_usage(sorted_system_usages)

# publish system usage
system_usage_array = SystemUsageArray()
system_usage_array.stamp = node.get_clock().now().to_msg()
for pid, data in sorted_system_usages:
system_usage = SystemUsage()
system_usage.pid = pid
system_usage.name = data["component"] + "/" + data["container"]
system_usage.cpu_usage = data["cpu_usage"]
system_usage.memory_usage = float(data["memory_usage"])
system_usage_array.system_usage.append(system_usage)
pub_system_usage.publish(system_usage_array)


if __name__ == "__main__":
main()
1 change: 1 addition & 0 deletions common/autoware_debug_tools/setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
entry_points={
"console_scripts": [
"processing_time_visualizer = autoware_debug_tools.processing_time_visualizer.node:main",
"system_usage_monitor = autoware_debug_tools.system_usage_monitor:main",
],
},
)
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 0 additions & 1 deletion planning/planning_debug_tools/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,6 @@ install(PROGRAMS
scripts/processing_time_checker.py
scripts/trajectory_visualizer.py
scripts/closest_velocity_checker.py
scripts/cpu_usage_checker.py
scripts/perception_replayer/perception_reproducer.py
scripts/perception_replayer/perception_replayer.py
scripts/update_logger_level.sh
Expand Down
13 changes: 0 additions & 13 deletions planning/planning_debug_tools/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ This package contains several planning-related debug tools.
- **Perception reproducer**: generates detected objects from rosbag data in planning simulator environment
- **processing time checker**: displays processing_time of modules on the terminal
- **logging level updater**: updates the logging level of the planning modules.
- **CPU Usage Checker**: displays CPU usage of ROS processes on the terminal

## Trajectory analyzer

Expand Down Expand Up @@ -293,15 +292,3 @@ ros2 run planning_debug_tools update_logger_level.sh <module-name> <logger-level
When you have a typo of the planning module, the script will show the available modules.

![logging_level_updater_typo](image/logging_level_updater_typo.png)

## CPU Usage Checker

The purpose of the CPU Usage Checker is to monitor and visualize the CPU usage of the ROS processes. By providing a real-time terminal-based visualization, users can easily confirm the cpu usage as in the picture below.

![cpu_usage_checker](image/cpu_usage_checker.png)

You can run the program by the following command.

```bash
ros2 run planning_debug_tools cpu_usage_checker.py
```
Binary file not shown.
93 changes: 0 additions & 93 deletions planning/planning_debug_tools/scripts/cpu_usage_checker.py

This file was deleted.

0 comments on commit 3baf48f

Please sign in to comment.