Skip to content

Commit

Permalink
feat(autoware_debug_tools): add system performance plotter (#91)
Browse files Browse the repository at this point in the history
* feat(autoware_debug_tools): add system performance plotter

Signed-off-by: Takayuki Murooka <[email protected]>

* update README

Signed-off-by: Takayuki Murooka <[email protected]>

* fix README.md

Signed-off-by: Takayuki Murooka <[email protected]>

---------

Signed-off-by: Takayuki Murooka <[email protected]>
  • Loading branch information
takayuki5168 authored Aug 7, 2024
1 parent d5eae1a commit 8c281ae
Show file tree
Hide file tree
Showing 9 changed files with 446 additions and 0 deletions.
48 changes: 48 additions & 0 deletions common/autoware_debug_tools/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,3 +59,51 @@ You can run the program by the following command.
```bash
ros2 run autoware_debug_tools system_usage_monitor
```

## System Performance Plotter

This script plots the following metrics by each Autoware's module.

- processing time
- CPU usage
- memory usage

### Usage

Run the following commands according to your purpose.

```bash
# plot processing time
ros2 run autoware_debug_tools processing_time_plotter <bag-path>

# plot CPU usage
ros2 run autoware_debug_tools cpu_usage_plotter <bag-path>

# plot memory usage
ros2 run autoware_debug_tools memory_usage_plotter <bag-path>
```

There are several options.

- `-c`:
- can filter modules in the specific component (e.g. `all`, `planning`, `system`, etc).
- `-n <number>`:
- can pick up top `<number>` critical modules.
- `-g <text>`
- can filter the modules which include `<text>`.
- `-y <val>`
- can set the height of the plot to `<val>`.

### Examples

```bash
ros2 run autoware_debug_tools processing_time_plotter <bag-path> -c planning -g behavior_path -y 300
```

![processing_time_plot_example](images/processing_time_plot_example.png)

```bash
ros2 run autoware_debug_tools cpu_usage_plotter <bag-path> -n 20
```

![cpu_usage_plot_example](images/cpu_usage_plot_example.png)
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
#!/usr/bin/env python3

from tier4_debug_msgs.msg import SystemUsageArray

from .system_performance_plotter_base import PREDEFINED_COMPONENT_NAMES
from .system_performance_plotter_base import SystemPerformancePlotterBase
from .system_performance_plotter_base import create_common_argment


class CpuUsagePlotter(SystemPerformancePlotterBase):
def check_topic(self, topic_name):
if self.grep_topic_name is not None and self.grep_topic_name not in topic_name:
return False
if "/system_usage" not in topic_name:
return False
return True

def update_metrics_func(self, topic_name, data, date_time):
if not isinstance(data, SystemUsageArray):
return

for system_usage in data.system_usage:
if self.component_name == "all":
# all
pass
elif self.component_name not in PREDEFINED_COMPONENT_NAMES:
# others (other than PREDEFINED_COMPONENT_NAMES)
if system_usage.name.split("/")[0] in PREDEFINED_COMPONENT_NAMES:
continue
elif system_usage.name.split("/")[0] != self.component_name:
# specific component
continue

if system_usage.name not in self.stamp_and_metrics:
self.stamp_and_metrics[system_usage.name] = []
self.max_metrics[system_usage.name] = 0.0

cpu_usage = system_usage.cpu_usage
self.stamp_and_metrics[system_usage.name].append([date_time, cpu_usage])
self.max_metrics[system_usage.name] = max(
self.max_metrics[system_usage.name], cpu_usage
)


def main():
args = create_common_argment(100)
plotter = CpuUsagePlotter(args, "CPU Usage [%]", "_cpu_usage")
plotter.run()


if __name__ == "__main__":
main()
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
#!/usr/bin/env python3

from tier4_debug_msgs.msg import SystemUsageArray

from .system_performance_plotter_base import PREDEFINED_COMPONENT_NAMES
from .system_performance_plotter_base import SystemPerformancePlotterBase
from .system_performance_plotter_base import create_common_argment


class MemoryUsagePlotter(SystemPerformancePlotterBase):
def check_topic(self, topic_name):
if self.grep_topic_name is not None and self.grep_topic_name not in topic_name:
return False
if "/system_usage" not in topic_name:
return False
return True

def update_metrics_func(self, topic_name, data, date_time):
if not isinstance(data, SystemUsageArray):
return

for system_usage in data.system_usage:
if self.component_name == "all":
# all
pass
elif self.component_name not in PREDEFINED_COMPONENT_NAMES:
# others (other than PREDEFINED_COMPONENT_NAMES)
if system_usage.name.split("/")[0] in PREDEFINED_COMPONENT_NAMES:
continue
elif system_usage.name.split("/")[0] != self.component_name:
# specific component
continue

if system_usage.name not in self.stamp_and_metrics:
self.stamp_and_metrics[system_usage.name] = []
self.max_metrics[system_usage.name] = 0.0

memory_usage = system_usage.memory_usage / 1024**2
self.stamp_and_metrics[system_usage.name].append([date_time, memory_usage])
self.max_metrics[system_usage.name] = max(
self.max_metrics[system_usage.name], memory_usage
)


def main():
args = create_common_argment()
plotter = MemoryUsagePlotter(args, "Memory Usage [MiB]", "_memory_usage")
plotter.run()


if __name__ == "__main__":
main()
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
#!/usr/bin/env python3

from tier4_debug_msgs.msg import Float64Stamped

from .system_performance_plotter_base import PREDEFINED_COMPONENT_NAMES
from .system_performance_plotter_base import SystemPerformancePlotterBase
from .system_performance_plotter_base import create_common_argment


class ProcessingTimePlotter(SystemPerformancePlotterBase):
def check_topic(self, topic_name):
if self.grep_topic_name is not None and self.grep_topic_name not in topic_name:
return False
if "/processing_time_ms" not in topic_name:
return False

if self.component_name == "all":
# all
pass
elif self.component_name in PREDEFINED_COMPONENT_NAMES:
# specific component
if f"/{self.component_name}/" not in topic_name:
return False
else:
# others (other than PREDEFINED_COMPONENT_NAMES)
for predefined_component_name in PREDEFINED_COMPONENT_NAMES:
if f"/{predefined_component_name}/" in topic_name:
return False
return True

def update_metrics_func(self, topic_name, data, date_time):
if not isinstance(data, Float64Stamped):
return

if topic_name not in self.stamp_and_metrics:
self.stamp_and_metrics[topic_name] = []
self.max_metrics[topic_name] = 0.0

processing_time_ms = data.data
self.stamp_and_metrics[topic_name].append([date_time, processing_time_ms])
self.max_metrics[topic_name] = max(self.max_metrics[topic_name], processing_time_ms)


def main():
args = create_common_argment(100)
plotter = ProcessingTimePlotter(args, "Processing Time [ms]", "_processing_time")
plotter.run()


if __name__ == "__main__":
main()
Loading

0 comments on commit 8c281ae

Please sign in to comment.