Skip to content

Commit ad0b811

Browse files
authored
feat(planning_debug_tools): add cpu_usage_checker.py (autowarefoundation#66)
* feat(planning_debug_tools): add cpu_usage_checker.py Signed-off-by: Takayuki Murooka <takayuki5168@gmail.com> * update README Signed-off-by: Takayuki Murooka <takayuki5168@gmail.com> --------- Signed-off-by: Takayuki Murooka <takayuki5168@gmail.com>
1 parent 2656150 commit ad0b811

File tree

4 files changed

+107
-0
lines changed

4 files changed

+107
-0
lines changed

planning/planning_debug_tools/CMakeLists.txt

+1
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ install(PROGRAMS
5151
scripts/processing_time_checker.py
5252
scripts/trajectory_visualizer.py
5353
scripts/closest_velocity_checker.py
54+
scripts/cpu_usage_checker.py
5455
scripts/perception_replayer/perception_reproducer.py
5556
scripts/perception_replayer/perception_replayer.py
5657
scripts/update_logger_level.sh

planning/planning_debug_tools/README.md

+13
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ This package contains several planning-related debug tools.
77
- **Perception reproducer**: generates detected objects from rosbag data in planning simulator environment
88
- **processing time checker**: displays processing_time of modules on the terminal
99
- **logging level updater**: updates the logging level of the planning modules.
10+
- **CPU Usage Checker**: displays CPU usage of ROS processes on the terminal
1011

1112
## Trajectory analyzer
1213

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

294295
![logging_level_updater_typo](image/logging_level_updater_typo.png)
296+
297+
## CPU Usage Checker
298+
299+
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.
300+
301+
![cpu_usage_checker](image/cpu_usage_checker.png)
302+
303+
You can run the program by the following command.
304+
305+
```bash
306+
ros2 run planning_debug_tools cpu_usage_checker.py
307+
```
Loading
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
#!/usr/bin/env python3
2+
3+
# Copyright 2024 TIER IV, Inc.
4+
#
5+
# Licensed under the Apache License, Version 2.0 (the "License");
6+
# you may not use this file except in compliance with the License.
7+
# You may obtain a copy of the License at
8+
#
9+
# http://www.apache.org/licenses/LICENSE-2.0
10+
#
11+
# Unless required by applicable law or agreed to in writing, software
12+
# distributed under the License is distributed on an "AS IS" BASIS,
13+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
# See the License for the specific language governing permissions and
15+
# limitations under the License.
16+
17+
import os
18+
import threading
19+
20+
import psutil
21+
22+
23+
def get_cpu_usage(pid, cpu_usages, interval):
24+
try:
25+
proc = psutil.Process(pid)
26+
cpu_usage = proc.cpu_percent(interval=interval)
27+
cmdline = proc.cmdline()
28+
process_name = " ".join(cmdline)
29+
component = process_name.split("__ns:=/")[1].split("/")[0].split(" ")[0]
30+
if component == "":
31+
component = "others"
32+
container = process_name.split("__node:=", 1)[1].split(" ")[0]
33+
cpu_usages[pid] = {"component": component, "container": container, "usage": cpu_usage}
34+
except (psutil.NoSuchProcess, psutil.AccessDenied, psutil.ZombieProcess, IndexError):
35+
pass
36+
37+
38+
def print_cpu_usage(cpu_usages):
39+
# clear terminal
40+
os.system("clear")
41+
if not cpu_usages:
42+
print("No processes found with the specified name.")
43+
return
44+
45+
# sort in the order of component name
46+
sorted_cpu_usages = sorted(cpu_usages.items(), key=lambda x: x[1]["component"])
47+
48+
print(" CPU Usage")
49+
print("-" * 185)
50+
51+
last_component = None
52+
for pid, data in sorted_cpu_usages:
53+
component = data["component"]
54+
container = data["container"]
55+
usage = data["usage"]
56+
bar = "#" * int(usage)
57+
58+
if last_component and last_component != component:
59+
print("|" + "-" * 17 + "|" + "-" * 57 + "|" + "-" * 7 + "|" + "-" * 100)
60+
61+
last_component = component
62+
process_info = f"| {component.split('/')[-1].ljust(15)} | {container.ljust(55)} | {str(usage).ljust(4)}% | {bar}"
63+
print(process_info)
64+
65+
print("-" * 185)
66+
67+
68+
def main():
69+
cpu_usages = {}
70+
while True:
71+
# create thread to calculate cpu usage of each process since it takes time
72+
process_name_keyword = "__node:="
73+
threads = []
74+
for proc in psutil.process_iter(["pid", "name", "cpu_percent", "cmdline"]):
75+
try:
76+
if process_name_keyword in " ".join(proc.info["cmdline"]):
77+
pid = proc.info["pid"]
78+
thread = threading.Thread(target=get_cpu_usage, args=(pid, cpu_usages, 1.0))
79+
threads.append(thread)
80+
thread.start()
81+
except (psutil.NoSuchProcess, psutil.AccessDenied, psutil.ZombieProcess):
82+
pass
83+
84+
# wait for all the thread to finish
85+
for thread in threads:
86+
thread.join()
87+
88+
# print cpu usage
89+
print_cpu_usage(cpu_usages)
90+
91+
92+
if __name__ == "__main__":
93+
main()

0 commit comments

Comments
 (0)