Merge branch '18-implement-memory-usage-analysis' into 'master'
Implement userspace memory usage analysis Closes #18 See merge request micro-ROS/ros_tracing/tracetools_analysis!27
This commit is contained in:
commit
db1e0ffa65
9 changed files with 563 additions and 1 deletions
158
tracetools_analysis/analysis/memory_usage.ipynb
Normal file
158
tracetools_analysis/analysis/memory_usage.ipynb
Normal file
|
@ -0,0 +1,158 @@
|
||||||
|
{
|
||||||
|
"cells": [
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": null,
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [],
|
||||||
|
"source": [
|
||||||
|
"# Memory usage\n",
|
||||||
|
"#\n",
|
||||||
|
"# Get trace data using the provided launch file:\n",
|
||||||
|
"# $ ros2 launch tracetools_analysis memory_usage.launch.py\n",
|
||||||
|
"# (wait a few seconds, then kill with Ctrl+C)\n",
|
||||||
|
"# AND\n",
|
||||||
|
"# Convert trace data:\n",
|
||||||
|
"# $ ros2 trace-analysis convert ~/.ros/tracing/memory-usage/ust"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": null,
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [],
|
||||||
|
"source": [
|
||||||
|
"converted_file_path = '~/.ros/tracing/memory-usage/ust/converted'"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": null,
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [],
|
||||||
|
"source": [
|
||||||
|
"import sys\n",
|
||||||
|
"# Assuming a workspace with:\n",
|
||||||
|
"# src/tracetools_analysis/\n",
|
||||||
|
"# src/micro-ROS/ros_tracing/ros2_tracing/tracetools_read/\n",
|
||||||
|
"sys.path.insert(0, '../')\n",
|
||||||
|
"sys.path.insert(0, '../../../micro-ROS/ros_tracing/ros2_tracing/tracetools_read/')\n",
|
||||||
|
"import datetime as dt\n",
|
||||||
|
"\n",
|
||||||
|
"from bokeh.palettes import viridis\n",
|
||||||
|
"from bokeh.plotting import figure\n",
|
||||||
|
"from bokeh.plotting import output_notebook\n",
|
||||||
|
"from bokeh.io import show\n",
|
||||||
|
"from bokeh.layouts import row\n",
|
||||||
|
"from bokeh.models import ColumnDataSource\n",
|
||||||
|
"from bokeh.models import DatetimeTickFormatter\n",
|
||||||
|
"from bokeh.models import NumeralTickFormatter\n",
|
||||||
|
"import numpy as np\n",
|
||||||
|
"import pandas as pd\n",
|
||||||
|
"\n",
|
||||||
|
"from tracetools_analysis.loading import load_file\n",
|
||||||
|
"from tracetools_analysis.processor import Processor\n",
|
||||||
|
"from tracetools_analysis.processor.memory_usage import MemoryUsageHandler\n",
|
||||||
|
"from tracetools_analysis.processor.ros2 import Ros2Handler\n",
|
||||||
|
"from tracetools_analysis.utils.memory_usage import MemoryUsageDataModelUtil\n",
|
||||||
|
"from tracetools_analysis.utils.ros2 import Ros2DataModelUtil"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": null,
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [],
|
||||||
|
"source": [
|
||||||
|
"# Process\n",
|
||||||
|
"events = load_file(converted_file_path)\n",
|
||||||
|
"memory_handler = MemoryUsageHandler()\n",
|
||||||
|
"ros2_handler = Ros2Handler()\n",
|
||||||
|
"Processor(memory_handler, ros2_handler).process(events)"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": null,
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [],
|
||||||
|
"source": [
|
||||||
|
"memory_data_util = MemoryUsageDataModelUtil(memory_handler.data)\n",
|
||||||
|
"ros2_data_util = Ros2DataModelUtil(ros2_handler.data)\n",
|
||||||
|
"\n",
|
||||||
|
"output_notebook()\n",
|
||||||
|
"psize = 650"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": null,
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [],
|
||||||
|
"source": [
|
||||||
|
"# Plot memory usage\n",
|
||||||
|
"memory_usage_dfs = memory_data_util.get_absolute_memory_usage_by_tid()\n",
|
||||||
|
"tids = ros2_data_util.get_tids()\n",
|
||||||
|
"\n",
|
||||||
|
"colours = viridis(len(tids) + 1)\n",
|
||||||
|
"first_tid = min(tids)\n",
|
||||||
|
"starttime = memory_usage_dfs[first_tid].loc[:, 'timestamp'].iloc[0].strftime('%Y-%m-%d %H:%M')\n",
|
||||||
|
"memory = figure(\n",
|
||||||
|
" title='Userspace memory usage per thread/node',\n",
|
||||||
|
" x_axis_label=f'time ({starttime})',\n",
|
||||||
|
" y_axis_label='memory usage',\n",
|
||||||
|
" plot_width=psize, plot_height=psize,\n",
|
||||||
|
")\n",
|
||||||
|
"\n",
|
||||||
|
"i_colour = 0\n",
|
||||||
|
"for tid, memory_usage_df in memory_usage_dfs.items():\n",
|
||||||
|
" if tid not in tids:\n",
|
||||||
|
" continue\n",
|
||||||
|
" memory.line(\n",
|
||||||
|
" x='timestamp',\n",
|
||||||
|
" y='memory_usage',\n",
|
||||||
|
" legend=str(tid) + ' ' + str(ros2_data_util.get_node_names_from_tid(tid)),\n",
|
||||||
|
" line_width=2,\n",
|
||||||
|
" source=ColumnDataSource(memory_usage_df),\n",
|
||||||
|
" line_color=colours[i_colour],\n",
|
||||||
|
" )\n",
|
||||||
|
" i_colour += 1\n",
|
||||||
|
"\n",
|
||||||
|
"memory.title.align = 'center'\n",
|
||||||
|
"memory.legend.label_text_font_size = '11px'\n",
|
||||||
|
"memory.xaxis[0].formatter = DatetimeTickFormatter(seconds=['%Ss'])\n",
|
||||||
|
"memory.yaxis[0].formatter = NumeralTickFormatter(format='0.0b')\n",
|
||||||
|
"\n",
|
||||||
|
"show(memory)"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": null,
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [],
|
||||||
|
"source": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"metadata": {
|
||||||
|
"kernelspec": {
|
||||||
|
"display_name": "Python 3",
|
||||||
|
"language": "python",
|
||||||
|
"name": "python3"
|
||||||
|
},
|
||||||
|
"language_info": {
|
||||||
|
"codemirror_mode": {
|
||||||
|
"name": "ipython",
|
||||||
|
"version": 3
|
||||||
|
},
|
||||||
|
"file_extension": ".py",
|
||||||
|
"mimetype": "text/x-python",
|
||||||
|
"name": "python",
|
||||||
|
"nbconvert_exporter": "python",
|
||||||
|
"pygments_lexer": "ipython3",
|
||||||
|
"version": "3.6.9"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nbformat": 4,
|
||||||
|
"nbformat_minor": 2
|
||||||
|
}
|
48
tracetools_analysis/launch/memory_usage.launch.py
Normal file
48
tracetools_analysis/launch/memory_usage.launch.py
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
# Copyright 2019 Robert Bosch GmbH
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
"""Example launch file for a memory_usage analysis."""
|
||||||
|
|
||||||
|
from launch import LaunchDescription
|
||||||
|
from launch_ros.actions import Node
|
||||||
|
from tracetools_launch.action import Trace
|
||||||
|
from tracetools_trace.tools.names import DEFAULT_EVENTS_ROS
|
||||||
|
|
||||||
|
|
||||||
|
def generate_launch_description():
|
||||||
|
return LaunchDescription([
|
||||||
|
Trace(
|
||||||
|
session_name='memory-usage',
|
||||||
|
events_ust=[
|
||||||
|
'lttng_ust_libc:malloc',
|
||||||
|
'lttng_ust_libc:calloc',
|
||||||
|
'lttng_ust_libc:realloc',
|
||||||
|
'lttng_ust_libc:free',
|
||||||
|
'lttng_ust_libc:memalign',
|
||||||
|
'lttng_ust_libc:posix_memalign',
|
||||||
|
] + DEFAULT_EVENTS_ROS,
|
||||||
|
),
|
||||||
|
Node(
|
||||||
|
package='tracetools_test',
|
||||||
|
node_executable='test_ping',
|
||||||
|
arguments=['do_more'],
|
||||||
|
output='screen',
|
||||||
|
),
|
||||||
|
Node(
|
||||||
|
package='tracetools_test',
|
||||||
|
node_executable='test_pong',
|
||||||
|
arguments=['do_more'],
|
||||||
|
output='screen',
|
||||||
|
),
|
||||||
|
])
|
|
@ -43,7 +43,8 @@ setup(
|
||||||
'console_scripts': [
|
'console_scripts': [
|
||||||
f'convert = {package_name}.convert:main',
|
f'convert = {package_name}.convert:main',
|
||||||
f'process = {package_name}.process:main',
|
f'process = {package_name}.process:main',
|
||||||
f'cb_durations = {package_name}.scripts.cb_durations:main'
|
f'cb_durations = {package_name}.scripts.cb_durations:main',
|
||||||
|
f'memory_usage = {package_name}.scripts.memory_usage:main',
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
license='Apache 2.0',
|
license='Apache 2.0',
|
||||||
|
|
|
@ -0,0 +1,56 @@
|
||||||
|
# Copyright 2019 Apex.AI, 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.
|
||||||
|
|
||||||
|
"""Module for memory usage data model."""
|
||||||
|
|
||||||
|
import pandas as pd
|
||||||
|
|
||||||
|
from . import DataModel
|
||||||
|
|
||||||
|
|
||||||
|
class MemoryUsageDataModel(DataModel):
|
||||||
|
"""
|
||||||
|
Container to model pre-processed memory usage data for analysis.
|
||||||
|
|
||||||
|
Contains changes in memory allocation (e.g. + for malloc, - for free) with the corresponding
|
||||||
|
timestamp.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self) -> None:
|
||||||
|
"""Create a MemoryUsageDataModel."""
|
||||||
|
super().__init__()
|
||||||
|
self.memory_diff = pd.DataFrame(columns=[
|
||||||
|
'timestamp',
|
||||||
|
'tid',
|
||||||
|
'memory_diff',
|
||||||
|
])
|
||||||
|
|
||||||
|
def add_memory_difference(
|
||||||
|
self,
|
||||||
|
timestamp: int,
|
||||||
|
tid: int,
|
||||||
|
memory_diff: int,
|
||||||
|
) -> None:
|
||||||
|
data = {
|
||||||
|
'timestamp': timestamp,
|
||||||
|
'tid': tid,
|
||||||
|
'memory_diff': memory_diff,
|
||||||
|
}
|
||||||
|
self.memory_diff = self.memory_diff.append(data, ignore_index=True)
|
||||||
|
|
||||||
|
def print_data(self) -> None:
|
||||||
|
print('==================MEMORY USAGE DATA MODEL==================')
|
||||||
|
tail = 20
|
||||||
|
print(f'Memory difference (tail={tail}):\n{self.times.tail(tail).to_string()}')
|
||||||
|
print('===========================================================')
|
|
@ -0,0 +1,152 @@
|
||||||
|
# Copyright 2019 Apex.AI, 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.
|
||||||
|
|
||||||
|
"""Module for memory usage events processing."""
|
||||||
|
|
||||||
|
from typing import Dict
|
||||||
|
|
||||||
|
from tracetools_read import get_field
|
||||||
|
|
||||||
|
from . import EventHandler
|
||||||
|
from . import EventMetadata
|
||||||
|
from ..data_model.memory_usage import MemoryUsageDataModel
|
||||||
|
|
||||||
|
|
||||||
|
class MemoryUsageHandler(EventHandler):
|
||||||
|
"""
|
||||||
|
Handler that extracts data for memory usage.
|
||||||
|
|
||||||
|
It uses the following events:
|
||||||
|
* lttng_ust_libc:malloc
|
||||||
|
* lttng_ust_libc:calloc
|
||||||
|
* lttng_ust_libc:realloc
|
||||||
|
* lttng_ust_libc:free
|
||||||
|
* lttng_ust_libc:memalign
|
||||||
|
* lttng_ust_libc:posix_memalign
|
||||||
|
|
||||||
|
The above events are generated when LD_PRELOAD-ing liblttng-ust-libc-wrapper.so, see:
|
||||||
|
https://lttng.org/docs/v2.10/#doc-liblttng-ust-libc-pthread-wrapper
|
||||||
|
|
||||||
|
Implementation inspired by Trace Compass' implementation:
|
||||||
|
https://git.eclipse.org/c/tracecompass/org.eclipse.tracecompass.git/tree/lttng/org.eclipse.tracecompass.lttng2.ust.core/src/org/eclipse/tracecompass/internal/lttng2/ust/core/analysis/memory/UstMemoryStateProvider.java#n161
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
**kwargs,
|
||||||
|
) -> None:
|
||||||
|
# Link event to handling method
|
||||||
|
handler_map = {
|
||||||
|
'lttng_ust_libc:malloc':
|
||||||
|
self._handle_malloc,
|
||||||
|
'lttng_ust_libc:calloc':
|
||||||
|
self._handle_calloc,
|
||||||
|
'lttng_ust_libc:realloc':
|
||||||
|
self._handle_realloc,
|
||||||
|
'lttng_ust_libc:free':
|
||||||
|
self._handle_free,
|
||||||
|
'lttng_ust_libc:memalign':
|
||||||
|
self._handle_memalign,
|
||||||
|
'lttng_ust_libc:posix_memalign':
|
||||||
|
self._handle_posix_memalign,
|
||||||
|
}
|
||||||
|
super().__init__(
|
||||||
|
handler_map=handler_map,
|
||||||
|
**kwargs,
|
||||||
|
)
|
||||||
|
|
||||||
|
self._data_model = MemoryUsageDataModel()
|
||||||
|
|
||||||
|
# Temporary buffers
|
||||||
|
# pointer -> current memory size
|
||||||
|
# (used to know keep track of the memory size allocated at a given pointer)
|
||||||
|
self._memory: Dict[int, int] = {}
|
||||||
|
|
||||||
|
@property
|
||||||
|
def data(self) -> MemoryUsageDataModel:
|
||||||
|
return self._data_model
|
||||||
|
|
||||||
|
def _handle_malloc(
|
||||||
|
self, event: Dict, metadata: EventMetadata
|
||||||
|
) -> None:
|
||||||
|
ptr = get_field(event, 'ptr')
|
||||||
|
if ptr != 0:
|
||||||
|
size = get_field(event, 'size')
|
||||||
|
self._handle(event, metadata, ptr, size)
|
||||||
|
|
||||||
|
def _handle_calloc(
|
||||||
|
self, event: Dict, metadata: EventMetadata
|
||||||
|
) -> None:
|
||||||
|
ptr = get_field(event, 'ptr')
|
||||||
|
if ptr != 0:
|
||||||
|
nmemb = get_field(event, 'nmemb')
|
||||||
|
size = get_field(event, 'size')
|
||||||
|
self._handle(event, metadata, ptr, size * nmemb)
|
||||||
|
|
||||||
|
def _handle_realloc(
|
||||||
|
self, event: Dict, metadata: EventMetadata
|
||||||
|
) -> None:
|
||||||
|
ptr = get_field(event, 'ptr')
|
||||||
|
if ptr != 0:
|
||||||
|
new_ptr = get_field(event, 'in_ptr')
|
||||||
|
size = get_field(event, 'size')
|
||||||
|
self._handle(event, metadata, ptr, 0)
|
||||||
|
self._handle(event, metadata, new_ptr, size)
|
||||||
|
|
||||||
|
def _handle_free(
|
||||||
|
self, event: Dict, metadata: EventMetadata
|
||||||
|
) -> None:
|
||||||
|
ptr = get_field(event, 'ptr')
|
||||||
|
if ptr != 0:
|
||||||
|
self._handle(event, metadata, ptr, 0)
|
||||||
|
|
||||||
|
def _handle_memalign(
|
||||||
|
self, event: Dict, metadata: EventMetadata
|
||||||
|
) -> None:
|
||||||
|
ptr = get_field(event, 'ptr')
|
||||||
|
if ptr != 0:
|
||||||
|
size = get_field(event, 'size')
|
||||||
|
self._handle(event, metadata, ptr, size)
|
||||||
|
|
||||||
|
def _handle_posix_memalign(
|
||||||
|
self, event: Dict, metadata: EventMetadata
|
||||||
|
) -> None:
|
||||||
|
ptr = get_field(event, 'out_ptr')
|
||||||
|
if ptr != 0:
|
||||||
|
size = get_field(event, 'size')
|
||||||
|
self._handle(event, metadata, ptr, size)
|
||||||
|
|
||||||
|
def _handle(
|
||||||
|
self,
|
||||||
|
event: Dict,
|
||||||
|
metadata: EventMetadata,
|
||||||
|
ptr: int,
|
||||||
|
size: int,
|
||||||
|
) -> None:
|
||||||
|
timestamp = metadata.timestamp
|
||||||
|
tid = metadata.tid
|
||||||
|
|
||||||
|
memory_difference = size
|
||||||
|
# Store the size allocated for the given pointer
|
||||||
|
if memory_difference != 0:
|
||||||
|
self._memory[ptr] = memory_difference
|
||||||
|
else:
|
||||||
|
# Othersize, if size is 0, it means it was deleted
|
||||||
|
# Try to fetch the size stored previously
|
||||||
|
allocated_memory = self._memory.get(ptr, None)
|
||||||
|
if allocated_memory is not None:
|
||||||
|
memory_difference = -allocated_memory
|
||||||
|
|
||||||
|
# Add to data model
|
||||||
|
self.data.add_memory_difference(timestamp, tid, memory_difference)
|
|
@ -36,6 +36,10 @@ class ProfileHandler(EventHandler):
|
||||||
* lttng_ust_cyg_profile_fast:func_exit
|
* lttng_ust_cyg_profile_fast:func_exit
|
||||||
* sched_switch
|
* sched_switch
|
||||||
|
|
||||||
|
The above events are generated when using -finstrument-functions with gcc and LD_PRELOAD-ing
|
||||||
|
liblttng-ust-cyg-profile-fast.so, see:
|
||||||
|
https://lttng.org/docs/v2.10/#doc-liblttng-ust-cyg-profile
|
||||||
|
|
||||||
TODO get debug_info from babeltrace for
|
TODO get debug_info from babeltrace for
|
||||||
lttng_ust_cyg_profile_fast:func_entry events
|
lttng_ust_cyg_profile_fast:func_entry events
|
||||||
(or resolve { address -> function } name another way)
|
(or resolve { address -> function } name another way)
|
||||||
|
|
|
@ -0,0 +1,67 @@
|
||||||
|
# Copyright 2019 Apex.AI, 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 sys
|
||||||
|
|
||||||
|
import pandas as pd
|
||||||
|
|
||||||
|
from tracetools_analysis.loading import load_file
|
||||||
|
from tracetools_analysis.processor import Processor
|
||||||
|
from tracetools_analysis.processor.memory_usage import MemoryUsageHandler
|
||||||
|
from tracetools_analysis.processor.ros2 import Ros2Handler
|
||||||
|
from tracetools_analysis.utils.memory_usage import MemoryUsageDataModelUtil
|
||||||
|
from tracetools_analysis.utils.ros2 import Ros2DataModelUtil
|
||||||
|
|
||||||
|
|
||||||
|
# From: https://stackoverflow.com/a/32009595/6476709
|
||||||
|
def format_memory_size(size: int, precision: int = 2):
|
||||||
|
suffixes = ['B', 'KB', 'MB', 'GB', 'TB']
|
||||||
|
suffixIndex = 0
|
||||||
|
while size > 1024 and suffixIndex < 4:
|
||||||
|
# Increment the index of the suffix
|
||||||
|
suffixIndex += 1
|
||||||
|
# Apply the division
|
||||||
|
size = size / 1024.0
|
||||||
|
return f'{size:.{precision}f} {suffixes[suffixIndex]}'
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
if len(sys.argv) < 2:
|
||||||
|
print('Syntax: <converted tracefile>')
|
||||||
|
sys.exit(-1)
|
||||||
|
file_path = sys.argv[1]
|
||||||
|
|
||||||
|
events = load_file(file_path)
|
||||||
|
memory_handler = MemoryUsageHandler()
|
||||||
|
ros2_handler = Ros2Handler()
|
||||||
|
Processor(memory_handler, ros2_handler).process(events)
|
||||||
|
|
||||||
|
memory_data_util = MemoryUsageDataModelUtil(memory_handler.data)
|
||||||
|
ros2_data_util = Ros2DataModelUtil(ros2_handler.data)
|
||||||
|
|
||||||
|
memory_usage_dfs = memory_data_util.get_absolute_memory_usage_by_tid()
|
||||||
|
tids = ros2_data_util.get_tids()
|
||||||
|
|
||||||
|
data = [
|
||||||
|
[
|
||||||
|
tid,
|
||||||
|
ros2_data_util.get_node_names_from_tid(tid),
|
||||||
|
format_memory_size(memory_usage['memory_usage'].max(), precision=1),
|
||||||
|
]
|
||||||
|
for tid, memory_usage in memory_usage_dfs.items()
|
||||||
|
if tid in tids
|
||||||
|
]
|
||||||
|
|
||||||
|
summary_df = pd.DataFrame(data, columns=['tid', 'node_names', 'max_memory_usage'])
|
||||||
|
print('\n\n' + summary_df.to_string(index=False))
|
|
@ -0,0 +1,72 @@
|
||||||
|
# Copyright 2019 Apex.AI, 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.
|
||||||
|
|
||||||
|
"""Module for memory usage data model utils."""
|
||||||
|
|
||||||
|
from collections import defaultdict
|
||||||
|
from typing import Dict
|
||||||
|
|
||||||
|
from pandas import DataFrame
|
||||||
|
|
||||||
|
from . import DataModelUtil
|
||||||
|
from ..data_model.memory_usage import MemoryUsageDataModel
|
||||||
|
|
||||||
|
|
||||||
|
class MemoryUsageDataModelUtil(DataModelUtil):
|
||||||
|
"""Memory usage data model utility class."""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
data_model: MemoryUsageDataModel,
|
||||||
|
) -> None:
|
||||||
|
"""
|
||||||
|
Create a MemoryUsageDataModelUtil.
|
||||||
|
|
||||||
|
:param data_model: the data model object to use
|
||||||
|
"""
|
||||||
|
super().__init__(data_model)
|
||||||
|
|
||||||
|
def get_absolute_memory_usage_by_tid(self) -> Dict[int, DataFrame]:
|
||||||
|
"""
|
||||||
|
Get absolute memory usage over time per tid.
|
||||||
|
|
||||||
|
:return (tid -> DataFrame of absolute memory usage over time)
|
||||||
|
"""
|
||||||
|
previous = defaultdict(int)
|
||||||
|
data = defaultdict(list)
|
||||||
|
for index, row in self.data.memory_diff.iterrows():
|
||||||
|
timestamp = row['timestamp']
|
||||||
|
tid = int(row['tid'])
|
||||||
|
diff = row['memory_diff']
|
||||||
|
previous_value = previous[tid]
|
||||||
|
next_value = previous_value + diff
|
||||||
|
data[tid].append({
|
||||||
|
'timestamp': timestamp,
|
||||||
|
'tid': tid,
|
||||||
|
'memory_usage': previous_value,
|
||||||
|
})
|
||||||
|
data[tid].append({
|
||||||
|
'timestamp': timestamp,
|
||||||
|
'tid': tid,
|
||||||
|
'memory_usage': next_value,
|
||||||
|
})
|
||||||
|
previous[tid] = next_value
|
||||||
|
return {
|
||||||
|
tid: self.convert_time_columns(
|
||||||
|
DataFrame(data[tid], columns=['timestamp', 'tid', 'memory_usage']),
|
||||||
|
columns_ns_to_datetime=['timestamp'],
|
||||||
|
inplace=True,
|
||||||
|
)
|
||||||
|
for tid in data
|
||||||
|
}
|
|
@ -114,6 +114,10 @@ class Ros2DataModelUtil(DataModelUtil):
|
||||||
obj: self._prettify(callback_symbols.loc[obj, 'symbol']) for obj in callback_objects
|
obj: self._prettify(callback_symbols.loc[obj, 'symbol']) for obj in callback_objects
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def get_tids(self) -> List[str]:
|
||||||
|
"""Get a list of thread ids corresponding to the nodes."""
|
||||||
|
return self.data.nodes['tid'].unique().tolist()
|
||||||
|
|
||||||
def get_callback_durations(
|
def get_callback_durations(
|
||||||
self,
|
self,
|
||||||
callback_obj: int,
|
callback_obj: int,
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue