Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(autoware_debug_tools): add system_usage_monitor.py #85

Merged
merged 2 commits into from
Jul 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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.

Loading