Merge branch 'analysis' into 'master'
Add basic analysis tools See merge request ros_tracing/tracetools_analysis!1
This commit is contained in:
commit
514ca46da6
25 changed files with 1966 additions and 0 deletions
23
.gitlab-ci.yml
Normal file
23
.gitlab-ci.yml
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
image: registry.gitlab.com/ros_tracing/ros2_tracing/ci-base:latest
|
||||||
|
|
||||||
|
variables:
|
||||||
|
DOCKER_DRIVER: overlay2
|
||||||
|
PACKAGES_LIST: tracetools_analysis
|
||||||
|
|
||||||
|
before_script:
|
||||||
|
- git clone https://gitlab.com/ros_tracing/ros2_tracing.git
|
||||||
|
|
||||||
|
build:
|
||||||
|
script:
|
||||||
|
- colcon build --symlink-install --packages-up-to $PACKAGES_LIST
|
||||||
|
- colcon test --packages-select $PACKAGES_LIST
|
||||||
|
- colcon test-result
|
||||||
|
artifacts:
|
||||||
|
paths:
|
||||||
|
- install
|
||||||
|
- build/*/test_results/*/*.xunit.xml
|
||||||
|
- build/*/pytest.xml
|
||||||
|
reports:
|
||||||
|
junit:
|
||||||
|
- build/*/test_results/*/*.xunit.xml
|
||||||
|
- build/*/pytest.xml
|
202
LICENSE
Normal file
202
LICENSE
Normal file
|
@ -0,0 +1,202 @@
|
||||||
|
|
||||||
|
Apache License
|
||||||
|
Version 2.0, January 2004
|
||||||
|
http://www.apache.org/licenses/
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||||
|
|
||||||
|
1. Definitions.
|
||||||
|
|
||||||
|
"License" shall mean the terms and conditions for use, reproduction,
|
||||||
|
and distribution as defined by Sections 1 through 9 of this document.
|
||||||
|
|
||||||
|
"Licensor" shall mean the copyright owner or entity authorized by
|
||||||
|
the copyright owner that is granting the License.
|
||||||
|
|
||||||
|
"Legal Entity" shall mean the union of the acting entity and all
|
||||||
|
other entities that control, are controlled by, or are under common
|
||||||
|
control with that entity. For the purposes of this definition,
|
||||||
|
"control" means (i) the power, direct or indirect, to cause the
|
||||||
|
direction or management of such entity, whether by contract or
|
||||||
|
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||||
|
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||||
|
|
||||||
|
"You" (or "Your") shall mean an individual or Legal Entity
|
||||||
|
exercising permissions granted by this License.
|
||||||
|
|
||||||
|
"Source" form shall mean the preferred form for making modifications,
|
||||||
|
including but not limited to software source code, documentation
|
||||||
|
source, and configuration files.
|
||||||
|
|
||||||
|
"Object" form shall mean any form resulting from mechanical
|
||||||
|
transformation or translation of a Source form, including but
|
||||||
|
not limited to compiled object code, generated documentation,
|
||||||
|
and conversions to other media types.
|
||||||
|
|
||||||
|
"Work" shall mean the work of authorship, whether in Source or
|
||||||
|
Object form, made available under the License, as indicated by a
|
||||||
|
copyright notice that is included in or attached to the work
|
||||||
|
(an example is provided in the Appendix below).
|
||||||
|
|
||||||
|
"Derivative Works" shall mean any work, whether in Source or Object
|
||||||
|
form, that is based on (or derived from) the Work and for which the
|
||||||
|
editorial revisions, annotations, elaborations, or other modifications
|
||||||
|
represent, as a whole, an original work of authorship. For the purposes
|
||||||
|
of this License, Derivative Works shall not include works that remain
|
||||||
|
separable from, or merely link (or bind by name) to the interfaces of,
|
||||||
|
the Work and Derivative Works thereof.
|
||||||
|
|
||||||
|
"Contribution" shall mean any work of authorship, including
|
||||||
|
the original version of the Work and any modifications or additions
|
||||||
|
to that Work or Derivative Works thereof, that is intentionally
|
||||||
|
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||||
|
or by an individual or Legal Entity authorized to submit on behalf of
|
||||||
|
the copyright owner. For the purposes of this definition, "submitted"
|
||||||
|
means any form of electronic, verbal, or written communication sent
|
||||||
|
to the Licensor or its representatives, including but not limited to
|
||||||
|
communication on electronic mailing lists, source code control systems,
|
||||||
|
and issue tracking systems that are managed by, or on behalf of, the
|
||||||
|
Licensor for the purpose of discussing and improving the Work, but
|
||||||
|
excluding communication that is conspicuously marked or otherwise
|
||||||
|
designated in writing by the copyright owner as "Not a Contribution."
|
||||||
|
|
||||||
|
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||||
|
on behalf of whom a Contribution has been received by Licensor and
|
||||||
|
subsequently incorporated within the Work.
|
||||||
|
|
||||||
|
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
copyright license to reproduce, prepare Derivative Works of,
|
||||||
|
publicly display, publicly perform, sublicense, and distribute the
|
||||||
|
Work and such Derivative Works in Source or Object form.
|
||||||
|
|
||||||
|
3. Grant of Patent License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
(except as stated in this section) patent license to make, have made,
|
||||||
|
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||||
|
where such license applies only to those patent claims licensable
|
||||||
|
by such Contributor that are necessarily infringed by their
|
||||||
|
Contribution(s) alone or by combination of their Contribution(s)
|
||||||
|
with the Work to which such Contribution(s) was submitted. If You
|
||||||
|
institute patent litigation against any entity (including a
|
||||||
|
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||||
|
or a Contribution incorporated within the Work constitutes direct
|
||||||
|
or contributory patent infringement, then any patent licenses
|
||||||
|
granted to You under this License for that Work shall terminate
|
||||||
|
as of the date such litigation is filed.
|
||||||
|
|
||||||
|
4. Redistribution. You may reproduce and distribute copies of the
|
||||||
|
Work or Derivative Works thereof in any medium, with or without
|
||||||
|
modifications, and in Source or Object form, provided that You
|
||||||
|
meet the following conditions:
|
||||||
|
|
||||||
|
(a) You must give any other recipients of the Work or
|
||||||
|
Derivative Works a copy of this License; and
|
||||||
|
|
||||||
|
(b) You must cause any modified files to carry prominent notices
|
||||||
|
stating that You changed the files; and
|
||||||
|
|
||||||
|
(c) You must retain, in the Source form of any Derivative Works
|
||||||
|
that You distribute, all copyright, patent, trademark, and
|
||||||
|
attribution notices from the Source form of the Work,
|
||||||
|
excluding those notices that do not pertain to any part of
|
||||||
|
the Derivative Works; and
|
||||||
|
|
||||||
|
(d) If the Work includes a "NOTICE" text file as part of its
|
||||||
|
distribution, then any Derivative Works that You distribute must
|
||||||
|
include a readable copy of the attribution notices contained
|
||||||
|
within such NOTICE file, excluding those notices that do not
|
||||||
|
pertain to any part of the Derivative Works, in at least one
|
||||||
|
of the following places: within a NOTICE text file distributed
|
||||||
|
as part of the Derivative Works; within the Source form or
|
||||||
|
documentation, if provided along with the Derivative Works; or,
|
||||||
|
within a display generated by the Derivative Works, if and
|
||||||
|
wherever such third-party notices normally appear. The contents
|
||||||
|
of the NOTICE file are for informational purposes only and
|
||||||
|
do not modify the License. You may add Your own attribution
|
||||||
|
notices within Derivative Works that You distribute, alongside
|
||||||
|
or as an addendum to the NOTICE text from the Work, provided
|
||||||
|
that such additional attribution notices cannot be construed
|
||||||
|
as modifying the License.
|
||||||
|
|
||||||
|
You may add Your own copyright statement to Your modifications and
|
||||||
|
may provide additional or different license terms and conditions
|
||||||
|
for use, reproduction, or distribution of Your modifications, or
|
||||||
|
for any such Derivative Works as a whole, provided Your use,
|
||||||
|
reproduction, and distribution of the Work otherwise complies with
|
||||||
|
the conditions stated in this License.
|
||||||
|
|
||||||
|
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||||
|
any Contribution intentionally submitted for inclusion in the Work
|
||||||
|
by You to the Licensor shall be under the terms and conditions of
|
||||||
|
this License, without any additional terms or conditions.
|
||||||
|
Notwithstanding the above, nothing herein shall supersede or modify
|
||||||
|
the terms of any separate license agreement you may have executed
|
||||||
|
with Licensor regarding such Contributions.
|
||||||
|
|
||||||
|
6. Trademarks. This License does not grant permission to use the trade
|
||||||
|
names, trademarks, service marks, or product names of the Licensor,
|
||||||
|
except as required for reasonable and customary use in describing the
|
||||||
|
origin of the Work and reproducing the content of the NOTICE file.
|
||||||
|
|
||||||
|
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||||
|
agreed to in writing, Licensor provides the Work (and each
|
||||||
|
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
implied, including, without limitation, any warranties or conditions
|
||||||
|
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||||
|
appropriateness of using or redistributing the Work and assume any
|
||||||
|
risks associated with Your exercise of permissions under this License.
|
||||||
|
|
||||||
|
8. Limitation of Liability. In no event and under no legal theory,
|
||||||
|
whether in tort (including negligence), contract, or otherwise,
|
||||||
|
unless required by applicable law (such as deliberate and grossly
|
||||||
|
negligent acts) or agreed to in writing, shall any Contributor be
|
||||||
|
liable to You for damages, including any direct, indirect, special,
|
||||||
|
incidental, or consequential damages of any character arising as a
|
||||||
|
result of this License or out of the use or inability to use the
|
||||||
|
Work (including but not limited to damages for loss of goodwill,
|
||||||
|
work stoppage, computer failure or malfunction, or any and all
|
||||||
|
other commercial damages or losses), even if such Contributor
|
||||||
|
has been advised of the possibility of such damages.
|
||||||
|
|
||||||
|
9. Accepting Warranty or Additional Liability. While redistributing
|
||||||
|
the Work or Derivative Works thereof, You may choose to offer,
|
||||||
|
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||||
|
or other liability obligations and/or rights consistent with this
|
||||||
|
License. However, in accepting such obligations, You may act only
|
||||||
|
on Your own behalf and on Your sole responsibility, not on behalf
|
||||||
|
of any other Contributor, and only if You agree to indemnify,
|
||||||
|
defend, and hold each Contributor harmless for any liability
|
||||||
|
incurred by, or claims asserted against, such Contributor by reason
|
||||||
|
of your accepting any such warranty or additional liability.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
APPENDIX: How to apply the Apache License to your work.
|
||||||
|
|
||||||
|
To apply the Apache License to your work, attach the following
|
||||||
|
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||||
|
replaced with your own identifying information. (Don't include
|
||||||
|
the brackets!) The text should be enclosed in the appropriate
|
||||||
|
comment syntax for the file format. We also recommend that a
|
||||||
|
file or class name and description of purpose be included on the
|
||||||
|
same "printed page" as the copyright notice for easier
|
||||||
|
identification within third-party archives.
|
||||||
|
|
||||||
|
Copyright [yyyy] [name of copyright owner]
|
||||||
|
|
||||||
|
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.
|
20
README.md
20
README.md
|
@ -0,0 +1,20 @@
|
||||||
|
# tracetools_analysis
|
||||||
|
|
||||||
|
Analysis tools for [ROS 2 tracing](https://gitlab.com/ros_tracing/ros2_tracing).
|
||||||
|
|
||||||
|
# Setup
|
||||||
|
|
||||||
|
To display results, install:
|
||||||
|
|
||||||
|
* [Jupyter](https://jupyter.org/install)
|
||||||
|
* [Bokeh](https://bokeh.pydata.org/en/latest/docs/user_guide/quickstart.html#userguide-quickstart-install)
|
||||||
|
|
||||||
|
# Use
|
||||||
|
|
||||||
|
Start Jupyter Notebook:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ jupyter notebook
|
||||||
|
```
|
||||||
|
|
||||||
|
Then navigate to the [`analysis/`](./tracetools_analysis/analysis/) directory, and select one of the provided notebooks, or create your own!
|
3
tracetools_analysis/.gitignore
vendored
Normal file
3
tracetools_analysis/.gitignore
vendored
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
*~
|
||||||
|
*.pyc
|
||||||
|
|
0
tracetools_analysis/README.md
Normal file
0
tracetools_analysis/README.md
Normal file
5
tracetools_analysis/analysis/.gitignore
vendored
Normal file
5
tracetools_analysis/analysis/.gitignore
vendored
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
*.svg
|
||||||
|
*.png
|
||||||
|
*.pdf
|
||||||
|
.ipynb_checkpoints
|
||||||
|
|
618
tracetools_analysis/analysis/callback_duration.ipynb
Normal file
618
tracetools_analysis/analysis/callback_duration.ipynb
Normal file
File diff suppressed because one or more lines are too long
23
tracetools_analysis/package.xml
Normal file
23
tracetools_analysis/package.xml
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
<?xml version="1.0"?>
|
||||||
|
<?xml-model href="http://download.ros.org/schema/package_format2.xsd" schematypens="http://www.w3.org/2001/XMLSchema"?>
|
||||||
|
<package format="2">
|
||||||
|
<name>tracetools_analysis</name>
|
||||||
|
<version>0.0.1</version>
|
||||||
|
<description>Tools for analysing trace data</description>
|
||||||
|
<maintainer email="fixed-term.christophe.bourquebedard@de.bosch.com">Christophe Bedard</maintainer>
|
||||||
|
<maintainer email="ingo.luetkebohle@de.bosch.com">Ingo Lütkebohle</maintainer>
|
||||||
|
<license>Apache 2.0</license>
|
||||||
|
<author email="ingo.luetkebohle@de.bosch.com">Ingo Luetkebohle</author>
|
||||||
|
<author email="fixed-term.christophe.bourquebedard@de.bosch.com">Christophe Bedard</author>
|
||||||
|
|
||||||
|
<exec_depend>tracetools_read</exec_depend>
|
||||||
|
|
||||||
|
<test_depend>ament_copyright</test_depend>
|
||||||
|
<test_depend>ament_flake8</test_depend>
|
||||||
|
<test_depend>ament_pep257</test_depend>
|
||||||
|
<test_depend>python3-pytest</test_depend>
|
||||||
|
|
||||||
|
<export>
|
||||||
|
<build_type>ament_python</build_type>
|
||||||
|
</export>
|
||||||
|
</package>
|
4
tracetools_analysis/setup.cfg
Normal file
4
tracetools_analysis/setup.cfg
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
[develop]
|
||||||
|
script-dir=$base/lib/tracetools_analysis
|
||||||
|
[install]
|
||||||
|
install-scripts=$base/lib/tracetools_analysis
|
41
tracetools_analysis/setup.py
Normal file
41
tracetools_analysis/setup.py
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
from setuptools import find_packages
|
||||||
|
from setuptools import setup
|
||||||
|
|
||||||
|
package_name = 'tracetools_analysis'
|
||||||
|
|
||||||
|
setup(
|
||||||
|
name=package_name,
|
||||||
|
version='0.0.1',
|
||||||
|
packages=find_packages(exclude=['test']),
|
||||||
|
data_files=[
|
||||||
|
('share/' + package_name, ['package.xml']),
|
||||||
|
],
|
||||||
|
install_requires=['setuptools'],
|
||||||
|
maintainer=(
|
||||||
|
'Christophe Bedard, '
|
||||||
|
'Ingo Lütkebohle'
|
||||||
|
),
|
||||||
|
maintainer_email=(
|
||||||
|
'fixed-term.christophe.bourquebedard@de.bosch.com, '
|
||||||
|
'ingo.luetkebohle@de.bosch.com'
|
||||||
|
),
|
||||||
|
author=(
|
||||||
|
'Christophe Bedard, '
|
||||||
|
'Ingo Lütkebohle'
|
||||||
|
),
|
||||||
|
author_email=(
|
||||||
|
'fixed-term.christophe.bourquebedard@de.bosch.com, '
|
||||||
|
'ingo.luetkebohle@de.bosch.com'
|
||||||
|
),
|
||||||
|
# url='',
|
||||||
|
keywords=['ROS'],
|
||||||
|
description='Tools for analysing trace data',
|
||||||
|
entry_points={
|
||||||
|
'console_scripts': [
|
||||||
|
f'convert = {package_name}.convert:main',
|
||||||
|
f'process = {package_name}.process:main',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
license='Apache 2.0',
|
||||||
|
tests_require=['pytest'],
|
||||||
|
)
|
23
tracetools_analysis/test/test_copyright.py
Normal file
23
tracetools_analysis/test/test_copyright.py
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
# Copyright 2017 Open Source Robotics Foundation, 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.
|
||||||
|
|
||||||
|
from ament_copyright.main import main
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.copyright
|
||||||
|
@pytest.mark.linter
|
||||||
|
def test_copyright():
|
||||||
|
rc = main(argv=['.', 'test'])
|
||||||
|
assert rc == 0, 'Found errors'
|
23
tracetools_analysis/test/test_flake8.py
Normal file
23
tracetools_analysis/test/test_flake8.py
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
# Copyright 2017 Open Source Robotics Foundation, 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.
|
||||||
|
|
||||||
|
from ament_flake8.main import main
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.flake8
|
||||||
|
@pytest.mark.linter
|
||||||
|
def test_flake8():
|
||||||
|
rc = main(argv=[])
|
||||||
|
assert rc == 0, 'Found errors'
|
23
tracetools_analysis/test/test_pep257.py
Normal file
23
tracetools_analysis/test/test_pep257.py
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
# Copyright 2015 Open Source Robotics Foundation, 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.
|
||||||
|
|
||||||
|
from ament_pep257.main import main
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.linter
|
||||||
|
@pytest.mark.pep257
|
||||||
|
def test_pep257():
|
||||||
|
rc = main(argv=[])
|
||||||
|
assert rc == 0, 'Found code style errors / warnings'
|
16
tracetools_analysis/tracetools_analysis/__init__.py
Normal file
16
tracetools_analysis/tracetools_analysis/__init__.py
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
"""Reading and interpreting of LTTng trace data."""
|
||||||
|
__author__ = 'Luetkebohle Ingo (CR/AEX3)'
|
13
tracetools_analysis/tracetools_analysis/analysis/__init__.py
Normal file
13
tracetools_analysis/tracetools_analysis/analysis/__init__.py
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
# 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.
|
168
tracetools_analysis/tracetools_analysis/analysis/data_model.py
Normal file
168
tracetools_analysis/tracetools_analysis/analysis/data_model.py
Normal file
|
@ -0,0 +1,168 @@
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
"""Module for data model."""
|
||||||
|
|
||||||
|
import pandas as pd
|
||||||
|
|
||||||
|
|
||||||
|
class DataModel():
|
||||||
|
"""
|
||||||
|
Container to model pre-processed data for analysis.
|
||||||
|
|
||||||
|
Contains data for an analysis to use. This is a middleground between trace events data and the
|
||||||
|
output data of an analysis. This aims to represent the data in a ROS-aware way.
|
||||||
|
It uses pandas DataFrames directly.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self) -> None:
|
||||||
|
# Objects (one-time events, usually when something is created)
|
||||||
|
self.contexts = pd.DataFrame(columns=['context_handle',
|
||||||
|
'timestamp',
|
||||||
|
'pid',
|
||||||
|
'version'])
|
||||||
|
self.contexts.set_index(['context_handle'], inplace=True, drop=True)
|
||||||
|
self.nodes = pd.DataFrame(columns=['node_handle',
|
||||||
|
'timestamp',
|
||||||
|
'tid',
|
||||||
|
'rmw_handle',
|
||||||
|
'name',
|
||||||
|
'namespace'])
|
||||||
|
self.nodes.set_index(['node_handle'], inplace=True, drop=True)
|
||||||
|
self.publishers = pd.DataFrame(columns=['publisher_handle',
|
||||||
|
'timestamp',
|
||||||
|
'node_handle',
|
||||||
|
'rmw_handle',
|
||||||
|
'topic_name',
|
||||||
|
'depth'])
|
||||||
|
self.publishers.set_index(['publisher_handle'], inplace=True, drop=True)
|
||||||
|
self.subscriptions = pd.DataFrame(columns=['subscription_handle',
|
||||||
|
'timestamp',
|
||||||
|
'node_handle',
|
||||||
|
'rmw_handle',
|
||||||
|
'topic_name',
|
||||||
|
'depth'])
|
||||||
|
self.subscriptions.set_index(['subscription_handle'], inplace=True, drop=True)
|
||||||
|
self.services = pd.DataFrame(columns=['service_handle',
|
||||||
|
'timestamp',
|
||||||
|
'node_handle',
|
||||||
|
'rmw_handle',
|
||||||
|
'service_name'])
|
||||||
|
self.services.set_index(['service_handle'], inplace=True, drop=True)
|
||||||
|
self.clients = pd.DataFrame(columns=['client_handle',
|
||||||
|
'timestamp',
|
||||||
|
'node_handle',
|
||||||
|
'rmw_handle',
|
||||||
|
'service_name'])
|
||||||
|
self.clients.set_index(['client_handle'], inplace=True, drop=True)
|
||||||
|
self.timers = pd.DataFrame(columns=['timer_handle',
|
||||||
|
'timestamp',
|
||||||
|
'period',
|
||||||
|
'tid'])
|
||||||
|
self.timers.set_index(['timer_handle'], inplace=True, drop=True)
|
||||||
|
|
||||||
|
self.callback_objects = pd.DataFrame(columns=['handle',
|
||||||
|
'timestamp',
|
||||||
|
'callback_object'])
|
||||||
|
self.callback_objects.set_index(['handle'], inplace=True, drop=True)
|
||||||
|
self.callback_symbols = pd.DataFrame(columns=['callback_object',
|
||||||
|
'timestamp',
|
||||||
|
'symbol'])
|
||||||
|
self.callback_symbols.set_index(['callback_object'], inplace=True, drop=True)
|
||||||
|
|
||||||
|
# Events (multiple instances, may not have a meaningful index)
|
||||||
|
self.callback_instances = pd.DataFrame(columns=['callback_object',
|
||||||
|
'timestamp',
|
||||||
|
'duration',
|
||||||
|
'intra_process'])
|
||||||
|
|
||||||
|
def add_context(
|
||||||
|
self, context_handle, timestamp, pid, version
|
||||||
|
) -> None:
|
||||||
|
self.contexts.loc[context_handle] = [timestamp, pid, version]
|
||||||
|
|
||||||
|
def add_node(
|
||||||
|
self, node_handle, timestamp, tid, rmw_handle, name, namespace
|
||||||
|
) -> None:
|
||||||
|
self.nodes.loc[node_handle] = [timestamp, tid, rmw_handle, name, namespace]
|
||||||
|
|
||||||
|
def add_publisher(
|
||||||
|
self, handle, timestamp, node_handle, rmw_handle, topic_name, depth
|
||||||
|
) -> None:
|
||||||
|
self.publishers.loc[handle] = [timestamp, node_handle, rmw_handle, topic_name, depth]
|
||||||
|
|
||||||
|
def add_subscription(
|
||||||
|
self, handle, timestamp, node_handle, rmw_handle, topic_name, depth
|
||||||
|
) -> None:
|
||||||
|
self.subscriptions.loc[handle] = [timestamp, node_handle, rmw_handle, topic_name, depth]
|
||||||
|
|
||||||
|
def add_service(
|
||||||
|
self, handle, timestamp, node_handle, rmw_handle, service_name
|
||||||
|
) -> None:
|
||||||
|
self.services.loc[handle] = [timestamp, node_handle, rmw_handle, service_name]
|
||||||
|
|
||||||
|
def add_client(
|
||||||
|
self, handle, timestamp, node_handle, rmw_handle, service_name
|
||||||
|
) -> None:
|
||||||
|
self.clients.loc[handle] = [timestamp, node_handle, rmw_handle, service_name]
|
||||||
|
|
||||||
|
def add_timer(
|
||||||
|
self, handle, timestamp, period, tid
|
||||||
|
) -> None:
|
||||||
|
self.timers.loc[handle] = [timestamp, period, tid]
|
||||||
|
|
||||||
|
def add_callback_object(
|
||||||
|
self, handle, timestamp, callback_object
|
||||||
|
) -> None:
|
||||||
|
self.callback_objects.loc[handle] = [timestamp, callback_object]
|
||||||
|
|
||||||
|
def add_callback_symbol(
|
||||||
|
self, callback_object, timestamp, symbol
|
||||||
|
) -> None:
|
||||||
|
self.callback_symbols.loc[callback_object] = [timestamp, symbol]
|
||||||
|
|
||||||
|
def add_callback_instance(
|
||||||
|
self, callback_object, timestamp, duration, intra_process
|
||||||
|
) -> None:
|
||||||
|
data = {
|
||||||
|
'callback_object': callback_object,
|
||||||
|
'timestamp': timestamp,
|
||||||
|
'duration': duration,
|
||||||
|
'intra_process': intra_process,
|
||||||
|
}
|
||||||
|
self.callback_instances = self.callback_instances.append(data, ignore_index=True)
|
||||||
|
|
||||||
|
def print_model(self) -> None:
|
||||||
|
"""Debug method to print every contained df."""
|
||||||
|
print('====================DATA MODEL====================')
|
||||||
|
print(f'Contexts:\n{self.contexts.to_string()}')
|
||||||
|
print()
|
||||||
|
print(f'Nodes:\n{self.nodes.to_string()}')
|
||||||
|
print()
|
||||||
|
print(f'Publishers:\n{self.publishers.to_string()}')
|
||||||
|
print()
|
||||||
|
print(f'Subscriptions:\n{self.subscriptions.to_string()}')
|
||||||
|
print()
|
||||||
|
print(f'Services:\n{self.services.to_string()}')
|
||||||
|
print()
|
||||||
|
print(f'Clients:\n{self.clients.to_string()}')
|
||||||
|
print()
|
||||||
|
print(f'Timers:\n{self.timers.to_string()}')
|
||||||
|
print()
|
||||||
|
print(f'Callback objects:\n{self.callback_objects.to_string()}')
|
||||||
|
print()
|
||||||
|
print(f'Callback symbols:\n{self.callback_symbols.to_string()}')
|
||||||
|
print()
|
||||||
|
print(f'Callback instances:\n{self.callback_instances.to_string()}')
|
||||||
|
print('==================================================')
|
71
tracetools_analysis/tracetools_analysis/analysis/handler.py
Normal file
71
tracetools_analysis/tracetools_analysis/analysis/handler.py
Normal file
|
@ -0,0 +1,71 @@
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
"""Module for event handler."""
|
||||||
|
|
||||||
|
import sys
|
||||||
|
from typing import Callable
|
||||||
|
from typing import Dict
|
||||||
|
from typing import List
|
||||||
|
|
||||||
|
from tracetools_read.utils import get_event_name
|
||||||
|
from tracetools_read.utils import get_field
|
||||||
|
|
||||||
|
from .lttng_models import EventMetadata
|
||||||
|
|
||||||
|
|
||||||
|
class EventHandler():
|
||||||
|
"""Base event handling class."""
|
||||||
|
|
||||||
|
def __init__(self, handler_map: Dict[str, Callable[[Dict, EventMetadata], None]]) -> None:
|
||||||
|
"""
|
||||||
|
Constructor.
|
||||||
|
|
||||||
|
:param handler_map: the mapping from event name to handling method
|
||||||
|
"""
|
||||||
|
self._handler_map = handler_map
|
||||||
|
|
||||||
|
def handle_events(self, events: List[Dict[str, str]]) -> None:
|
||||||
|
"""
|
||||||
|
Handle events by calling their handlers.
|
||||||
|
|
||||||
|
:param events: the events to process
|
||||||
|
"""
|
||||||
|
for event in events:
|
||||||
|
self._handle(event)
|
||||||
|
|
||||||
|
def _handle(self, event: Dict[str, str]) -> None:
|
||||||
|
event_name = get_event_name(event)
|
||||||
|
handler_function = self._handler_map.get(event_name, None)
|
||||||
|
if handler_function is not None:
|
||||||
|
pid = get_field(
|
||||||
|
event,
|
||||||
|
'vpid',
|
||||||
|
default=get_field(
|
||||||
|
event,
|
||||||
|
'pid',
|
||||||
|
raise_if_not_found=False))
|
||||||
|
tid = get_field(
|
||||||
|
event,
|
||||||
|
'vtid',
|
||||||
|
default=get_field(
|
||||||
|
event,
|
||||||
|
'tid',
|
||||||
|
raise_if_not_found=False))
|
||||||
|
timestamp = get_field(event, '_timestamp')
|
||||||
|
procname = get_field(event, 'procname')
|
||||||
|
metadata = EventMetadata(event_name, pid, tid, timestamp, procname)
|
||||||
|
handler_function(event, metadata)
|
||||||
|
else:
|
||||||
|
print(f'unhandled event name: {event_name}', file=sys.stderr)
|
38
tracetools_analysis/tracetools_analysis/analysis/load.py
Normal file
38
tracetools_analysis/tracetools_analysis/analysis/load.py
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
"""Module for pickle loading."""
|
||||||
|
|
||||||
|
import pickle
|
||||||
|
from typing import Dict
|
||||||
|
from typing import List
|
||||||
|
|
||||||
|
|
||||||
|
def load_pickle(pickle_file_path: str) -> List[Dict]:
|
||||||
|
"""
|
||||||
|
Load pickle file containing converted trace events.
|
||||||
|
|
||||||
|
:param pickle_file_path: the path to the pickle file to load
|
||||||
|
:return: the list of events read from the file
|
||||||
|
"""
|
||||||
|
events = []
|
||||||
|
with open(pickle_file_path, 'rb') as f:
|
||||||
|
p = pickle.Unpickler(f)
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
events.append(p.load())
|
||||||
|
except EOFError:
|
||||||
|
break # we're done
|
||||||
|
|
||||||
|
return events
|
|
@ -0,0 +1,46 @@
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
"""Module LTTng traces/events models."""
|
||||||
|
|
||||||
|
|
||||||
|
class EventMetadata():
|
||||||
|
"""Container for event metadata."""
|
||||||
|
|
||||||
|
def __init__(self, event_name, pid, tid, timestamp, procname) -> None:
|
||||||
|
self._event_name = event_name
|
||||||
|
self._pid = pid
|
||||||
|
self._tid = tid
|
||||||
|
self._timestamp = timestamp
|
||||||
|
self._procname = procname
|
||||||
|
|
||||||
|
@property
|
||||||
|
def event_name(self):
|
||||||
|
return self._event_name
|
||||||
|
|
||||||
|
@property
|
||||||
|
def pid(self):
|
||||||
|
return self._pid
|
||||||
|
|
||||||
|
@property
|
||||||
|
def tid(self):
|
||||||
|
return self._tid
|
||||||
|
|
||||||
|
@property
|
||||||
|
def timestamp(self):
|
||||||
|
return self._timestamp
|
||||||
|
|
||||||
|
@property
|
||||||
|
def procname(self):
|
||||||
|
return self._procname
|
|
@ -0,0 +1,212 @@
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
"""Module for trace events processor and ROS model creation."""
|
||||||
|
|
||||||
|
from typing import Dict
|
||||||
|
from typing import List
|
||||||
|
|
||||||
|
from tracetools_read.utils import get_field
|
||||||
|
|
||||||
|
from .data_model import DataModel
|
||||||
|
from .handler import EventHandler
|
||||||
|
from .lttng_models import EventMetadata
|
||||||
|
|
||||||
|
|
||||||
|
class Ros2Processor(EventHandler):
|
||||||
|
"""
|
||||||
|
ROS 2-aware event processing/handling class implementation.
|
||||||
|
|
||||||
|
Handles a trace's events and builds a model with the data.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self) -> None:
|
||||||
|
# Link a ROS trace event to its corresponding handling method
|
||||||
|
handler_map = {
|
||||||
|
'ros2:rcl_init':
|
||||||
|
self._handle_rcl_init,
|
||||||
|
'ros2:rcl_node_init':
|
||||||
|
self._handle_rcl_node_init,
|
||||||
|
'ros2:rcl_publisher_init':
|
||||||
|
self._handle_rcl_publisher_init,
|
||||||
|
'ros2:rcl_subscription_init':
|
||||||
|
self._handle_subscription_init,
|
||||||
|
'ros2:rclcpp_subscription_callback_added':
|
||||||
|
self._handle_rclcpp_subscription_callback_added,
|
||||||
|
'ros2:rcl_service_init':
|
||||||
|
self._handle_rcl_service_init,
|
||||||
|
'ros2:rclcpp_service_callback_added':
|
||||||
|
self._handle_rclcpp_service_callback_added,
|
||||||
|
'ros2:rcl_client_init':
|
||||||
|
self._handle_rcl_client_init,
|
||||||
|
'ros2:rcl_timer_init':
|
||||||
|
self._handle_rcl_timer_init,
|
||||||
|
'ros2:rclcpp_timer_callback_added':
|
||||||
|
self._handle_rclcpp_timer_callback_added,
|
||||||
|
'ros2:rclcpp_callback_register':
|
||||||
|
self._handle_rclcpp_callback_register,
|
||||||
|
'ros2:callback_start':
|
||||||
|
self._handle_callback_start,
|
||||||
|
'ros2:callback_end':
|
||||||
|
self._handle_callback_end,
|
||||||
|
}
|
||||||
|
super().__init__(handler_map)
|
||||||
|
|
||||||
|
self._data = DataModel()
|
||||||
|
|
||||||
|
# Temporary buffers
|
||||||
|
self._callback_instances = {}
|
||||||
|
|
||||||
|
def get_data_model(self) -> DataModel:
|
||||||
|
return self._data
|
||||||
|
|
||||||
|
def _handle_rcl_init(
|
||||||
|
self, event: Dict, metadata: EventMetadata
|
||||||
|
) -> None:
|
||||||
|
context_handle = get_field(event, 'context_handle')
|
||||||
|
timestamp = metadata.timestamp
|
||||||
|
pid = metadata.pid
|
||||||
|
version = get_field(event, 'version')
|
||||||
|
self._data.add_context(context_handle, timestamp, pid, version)
|
||||||
|
|
||||||
|
def _handle_rcl_node_init(
|
||||||
|
self, event: Dict, metadata: EventMetadata
|
||||||
|
) -> None:
|
||||||
|
handle = get_field(event, 'node_handle')
|
||||||
|
timestamp = metadata.timestamp
|
||||||
|
tid = metadata.tid
|
||||||
|
rmw_handle = get_field(event, 'rmw_handle')
|
||||||
|
name = get_field(event, 'node_name')
|
||||||
|
namespace = get_field(event, 'namespace')
|
||||||
|
self._data.add_node(handle, timestamp, tid, rmw_handle, name, namespace)
|
||||||
|
|
||||||
|
def _handle_rcl_publisher_init(
|
||||||
|
self, event: Dict, metadata: EventMetadata
|
||||||
|
) -> None:
|
||||||
|
handle = get_field(event, 'publisher_handle')
|
||||||
|
timestamp = metadata.timestamp
|
||||||
|
node_handle = get_field(event, 'node_handle')
|
||||||
|
rmw_handle = get_field(event, 'rmw_publisher_handle')
|
||||||
|
topic_name = get_field(event, 'topic_name')
|
||||||
|
depth = get_field(event, 'queue_depth')
|
||||||
|
self._data.add_publisher(handle, timestamp, node_handle, rmw_handle, topic_name, depth)
|
||||||
|
|
||||||
|
def _handle_subscription_init(
|
||||||
|
self, event: Dict, metadata: EventMetadata
|
||||||
|
) -> None:
|
||||||
|
handle = get_field(event, 'subscription_handle')
|
||||||
|
timestamp = metadata.timestamp
|
||||||
|
node_handle = get_field(event, 'node_handle')
|
||||||
|
rmw_handle = get_field(event, 'rmw_subscription_handle')
|
||||||
|
topic_name = get_field(event, 'topic_name')
|
||||||
|
depth = get_field(event, 'queue_depth')
|
||||||
|
self._data.add_subscription(handle, timestamp, node_handle, rmw_handle, topic_name, depth)
|
||||||
|
|
||||||
|
def _handle_rclcpp_subscription_callback_added(
|
||||||
|
self, event: Dict, metadata: EventMetadata
|
||||||
|
) -> None:
|
||||||
|
handle = get_field(event, 'subscription_handle')
|
||||||
|
timestamp = metadata.timestamp
|
||||||
|
callback_object = get_field(event, 'callback')
|
||||||
|
self._data.add_callback_object(handle, timestamp, callback_object)
|
||||||
|
|
||||||
|
def _handle_rcl_service_init(
|
||||||
|
self, event: Dict, metadata: EventMetadata
|
||||||
|
) -> None:
|
||||||
|
handle = get_field(event, 'service_handle')
|
||||||
|
timestamp = metadata.timestamp
|
||||||
|
node_handle = get_field(event, 'node_handle')
|
||||||
|
rmw_handle = get_field(event, 'rmw_service_handle')
|
||||||
|
service_name = get_field(event, 'service_name')
|
||||||
|
self._data.add_service(handle, timestamp, node_handle, rmw_handle, service_name)
|
||||||
|
|
||||||
|
def _handle_rclcpp_service_callback_added(
|
||||||
|
self, event: Dict, metadata: EventMetadata
|
||||||
|
) -> None:
|
||||||
|
handle = get_field(event, 'service_handle')
|
||||||
|
timestamp = metadata.timestamp
|
||||||
|
callback_object = get_field(event, 'callback')
|
||||||
|
self._data.add_callback_object(handle, timestamp, callback_object)
|
||||||
|
|
||||||
|
def _handle_rcl_client_init(
|
||||||
|
self, event: Dict, metadata: EventMetadata
|
||||||
|
) -> None:
|
||||||
|
handle = get_field(event, 'client_handle')
|
||||||
|
timestamp = metadata.timestamp
|
||||||
|
node_handle = get_field(event, 'node_handle')
|
||||||
|
rmw_handle = get_field(event, 'rmw_client_handle')
|
||||||
|
service_name = get_field(event, 'service_name')
|
||||||
|
self._data.add_client(handle, timestamp, node_handle, rmw_handle, service_name)
|
||||||
|
|
||||||
|
def _handle_rcl_timer_init(
|
||||||
|
self, event: Dict, metadata: EventMetadata
|
||||||
|
) -> None:
|
||||||
|
handle = get_field(event, 'timer_handle')
|
||||||
|
timestamp = metadata.timestamp
|
||||||
|
period = get_field(event, 'period')
|
||||||
|
tid = metadata.tid
|
||||||
|
self._data.add_timer(handle, timestamp, period, tid)
|
||||||
|
|
||||||
|
def _handle_rclcpp_timer_callback_added(
|
||||||
|
self, event: Dict, metadata: EventMetadata
|
||||||
|
) -> None:
|
||||||
|
handle = get_field(event, 'timer_handle')
|
||||||
|
timestamp = metadata.timestamp
|
||||||
|
callback_object = get_field(event, 'callback')
|
||||||
|
self._data.add_callback_object(handle, timestamp, callback_object)
|
||||||
|
|
||||||
|
def _handle_rclcpp_callback_register(
|
||||||
|
self, event: Dict, metadata: EventMetadata
|
||||||
|
) -> None:
|
||||||
|
callback_object = get_field(event, 'callback')
|
||||||
|
timestamp = metadata.timestamp
|
||||||
|
symbol = get_field(event, 'symbol')
|
||||||
|
self._data.add_callback_symbol(callback_object, timestamp, symbol)
|
||||||
|
|
||||||
|
def _handle_callback_start(
|
||||||
|
self, event: Dict, metadata: EventMetadata
|
||||||
|
) -> None:
|
||||||
|
# Add to dict
|
||||||
|
callback_addr = get_field(event, 'callback')
|
||||||
|
self._callback_instances[callback_addr] = (event, metadata)
|
||||||
|
|
||||||
|
def _handle_callback_end(
|
||||||
|
self, event: Dict, metadata: EventMetadata
|
||||||
|
) -> None:
|
||||||
|
# Fetch from dict
|
||||||
|
callback_object = get_field(event, 'callback')
|
||||||
|
(event_start, metadata_start) = self._callback_instances.get(callback_object)
|
||||||
|
if event_start is not None and metadata_start is not None:
|
||||||
|
del self._callback_instances[callback_object]
|
||||||
|
duration = metadata.timestamp - metadata_start.timestamp
|
||||||
|
is_intra_process = get_field(event_start, 'is_intra_process', raise_if_not_found=False)
|
||||||
|
self._data.add_callback_instance(
|
||||||
|
callback_object,
|
||||||
|
metadata_start.timestamp,
|
||||||
|
duration,
|
||||||
|
bool(is_intra_process))
|
||||||
|
else:
|
||||||
|
print(f'No matching callback start for callback object "{callback_object}"')
|
||||||
|
|
||||||
|
|
||||||
|
def ros2_process(events: List[Dict[str, str]]) -> Ros2Processor:
|
||||||
|
"""
|
||||||
|
Process unpickled events and create ROS 2 model.
|
||||||
|
|
||||||
|
:param events: the list of events
|
||||||
|
:return: the processor object
|
||||||
|
"""
|
||||||
|
processor = Ros2Processor()
|
||||||
|
processor.handle_events(events)
|
||||||
|
return processor
|
231
tracetools_analysis/tracetools_analysis/analysis/utils.py
Normal file
231
tracetools_analysis/tracetools_analysis/analysis/utils.py
Normal file
|
@ -0,0 +1,231 @@
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
"""Module for data model utility class."""
|
||||||
|
|
||||||
|
from datetime import datetime as dt
|
||||||
|
from typing import Any
|
||||||
|
from typing import Mapping
|
||||||
|
from typing import Union
|
||||||
|
|
||||||
|
from pandas import DataFrame
|
||||||
|
|
||||||
|
from .data_model import DataModel
|
||||||
|
|
||||||
|
|
||||||
|
class DataModelUtil():
|
||||||
|
"""
|
||||||
|
Data model utility class.
|
||||||
|
|
||||||
|
Provides functions to get info on a data model.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, data_model: DataModel) -> None:
|
||||||
|
"""
|
||||||
|
Constructor.
|
||||||
|
|
||||||
|
:param data_model: the data model object to use
|
||||||
|
"""
|
||||||
|
self._data = data_model
|
||||||
|
|
||||||
|
def get_callback_symbols(self) -> Mapping[int, str]:
|
||||||
|
"""
|
||||||
|
Get mappings between a callback object and its resolved symbol.
|
||||||
|
|
||||||
|
:return: the map
|
||||||
|
"""
|
||||||
|
callback_instances = self._data.callback_instances
|
||||||
|
callback_symbols = self._data.callback_symbols
|
||||||
|
|
||||||
|
# Get a list of callback objects
|
||||||
|
callback_objects = set(callback_instances['callback_object'])
|
||||||
|
# Get their symbol
|
||||||
|
return {obj: callback_symbols.loc[obj, 'symbol'] for obj in callback_objects}
|
||||||
|
|
||||||
|
def get_callback_durations(
|
||||||
|
self, callback_obj: int
|
||||||
|
) -> DataFrame:
|
||||||
|
"""
|
||||||
|
Get durations of callback instances for a given callback object.
|
||||||
|
|
||||||
|
:param callback_obj: the callback object value
|
||||||
|
:return: a dataframe containing the start timestamp (datetime)
|
||||||
|
and duration (ms) of all callback instances for that object
|
||||||
|
"""
|
||||||
|
data = self._data.callback_instances.loc[
|
||||||
|
self._data.callback_instances.loc[:, 'callback_object'] == callback_obj,
|
||||||
|
['timestamp', 'duration']
|
||||||
|
]
|
||||||
|
# Transform both columns to ms
|
||||||
|
data[['timestamp', 'duration']] = data[
|
||||||
|
['timestamp', 'duration']
|
||||||
|
].apply(lambda d: d / 1000000.0)
|
||||||
|
# Transform start timestamp column to datetime objects
|
||||||
|
data['timestamp'] = data['timestamp'].apply(lambda t: dt.fromtimestamp(t / 1000.0))
|
||||||
|
return data
|
||||||
|
|
||||||
|
def get_callback_owner_info(
|
||||||
|
self, callback_obj: int
|
||||||
|
) -> Union[str, None]:
|
||||||
|
"""
|
||||||
|
Get information about the owner of a callback.
|
||||||
|
|
||||||
|
Depending on the type of callback, it will give different kinds of info:
|
||||||
|
* subscription: node name, topic name
|
||||||
|
* timer: tid, period of timer
|
||||||
|
* service/client: node name, service name
|
||||||
|
|
||||||
|
:param callback_obj: the callback object value
|
||||||
|
:return: information about the owner of the callback, or `None` if it fails
|
||||||
|
"""
|
||||||
|
# Get handle corresponding to callback object
|
||||||
|
handle = self._data.callback_objects.loc[
|
||||||
|
self._data.callback_objects['callback_object'] == callback_obj
|
||||||
|
].index.values.astype(int)[0]
|
||||||
|
|
||||||
|
type_name = None
|
||||||
|
info = None
|
||||||
|
# Check if it's a timer first (since it's slightly different than the others)
|
||||||
|
if handle in self._data.timers.index:
|
||||||
|
type_name = 'Timer'
|
||||||
|
info = self.get_timer_handle_info(handle)
|
||||||
|
elif handle in self._data.publishers.index:
|
||||||
|
type_name = 'Publisher'
|
||||||
|
info = self.get_publisher_handle_info(handle)
|
||||||
|
elif handle in self._data.subscriptions.index:
|
||||||
|
type_name = 'Subscription'
|
||||||
|
info = self.get_subscription_handle_info(handle)
|
||||||
|
elif handle in self._data.services.index:
|
||||||
|
type_name = 'Service'
|
||||||
|
info = self.get_subscription_handle_info(handle)
|
||||||
|
elif handle in self._data.clients.index:
|
||||||
|
type_name = 'Client'
|
||||||
|
info = self.get_client_handle_info(handle)
|
||||||
|
|
||||||
|
if info is not None:
|
||||||
|
info = f'{type_name} -- {self.format_info_dict(info)}'
|
||||||
|
return info
|
||||||
|
|
||||||
|
def get_timer_handle_info(
|
||||||
|
self, timer_handle: int
|
||||||
|
) -> Union[Mapping[str, Any], None]:
|
||||||
|
"""
|
||||||
|
Get information about the owner of a timer.
|
||||||
|
|
||||||
|
:param timer_handle: the timer handle value
|
||||||
|
:return: a dictionary with name:value info, or `None` if it fails
|
||||||
|
"""
|
||||||
|
# TODO find a way to link a timer to a specific node
|
||||||
|
if timer_handle not in self._data.timers.index:
|
||||||
|
return None
|
||||||
|
|
||||||
|
tid = self._data.timers.loc[timer_handle, 'tid']
|
||||||
|
period_ns = self._data.timers.loc[timer_handle, 'period']
|
||||||
|
period_ms = period_ns / 1000000.0
|
||||||
|
return {'tid': tid, 'period': f'{period_ms:.0f} ms'}
|
||||||
|
|
||||||
|
def get_publisher_handle_info(
|
||||||
|
self, publisher_handle: int
|
||||||
|
) -> Union[Mapping[str, Any], None]:
|
||||||
|
"""
|
||||||
|
Get information about a publisher handle.
|
||||||
|
|
||||||
|
:param publisher_handle: the publisher handle value
|
||||||
|
:return: a dictionary with name:value info, or `None` if it fails
|
||||||
|
"""
|
||||||
|
if publisher_handle not in self._data.publishers.index:
|
||||||
|
return None
|
||||||
|
|
||||||
|
node_handle = self._data.publishers.loc[publisher_handle, 'node_handle']
|
||||||
|
node_handle_info = self.get_node_handle_info(node_handle)
|
||||||
|
topic_name = self._data.publishers.loc[publisher_handle, 'topic_name']
|
||||||
|
publisher_info = {'topic': topic_name}
|
||||||
|
return {**node_handle_info, **publisher_info}
|
||||||
|
|
||||||
|
def get_subscription_handle_info(
|
||||||
|
self, subscription_handle: int
|
||||||
|
) -> Union[Mapping[str, Any], None]:
|
||||||
|
"""
|
||||||
|
Get information about a subscription handle.
|
||||||
|
|
||||||
|
:param subscription_handle: the subscription handle value
|
||||||
|
:return: a dictionary with name:value info, or `None` if it fails
|
||||||
|
"""
|
||||||
|
subscriptions_info = self._data.subscriptions.merge(
|
||||||
|
self._data.nodes,
|
||||||
|
left_on='node_handle',
|
||||||
|
right_index=True)
|
||||||
|
if subscription_handle not in self._data.subscriptions.index:
|
||||||
|
return None
|
||||||
|
|
||||||
|
node_handle = subscriptions_info.loc[subscription_handle, 'node_handle']
|
||||||
|
node_handle_info = self.get_node_handle_info(node_handle)
|
||||||
|
topic_name = subscriptions_info.loc[subscription_handle, 'topic_name']
|
||||||
|
subscription_info = {'topic': topic_name}
|
||||||
|
return {**node_handle_info, **subscription_info}
|
||||||
|
|
||||||
|
def get_service_handle_info(
|
||||||
|
self, service_handle: int
|
||||||
|
) -> Union[Mapping[str, Any], None]:
|
||||||
|
"""
|
||||||
|
Get information about a service handle.
|
||||||
|
|
||||||
|
:param service_handle: the service handle value
|
||||||
|
:return: a dictionary with name:value info, or `None` if it fails
|
||||||
|
"""
|
||||||
|
if service_handle not in self._data.services:
|
||||||
|
return None
|
||||||
|
|
||||||
|
node_handle = self._data.services.loc[service_handle, 'node_handle']
|
||||||
|
node_handle_info = self.get_node_handle_info(node_handle)
|
||||||
|
service_name = self._data.services.loc[service_handle, 'service_name']
|
||||||
|
service_info = {'service': service_name}
|
||||||
|
return {**node_handle_info, **service_info}
|
||||||
|
|
||||||
|
def get_client_handle_info(
|
||||||
|
self, client_handle: int
|
||||||
|
) -> Union[Mapping[str, Any], None]:
|
||||||
|
"""
|
||||||
|
Get information about a client handle.
|
||||||
|
|
||||||
|
:param client_handle: the client handle value
|
||||||
|
:return: a dictionary with name:value info, or `None` if it fails
|
||||||
|
"""
|
||||||
|
if client_handle not in self._data.clients:
|
||||||
|
return None
|
||||||
|
|
||||||
|
node_handle = self._data.clients.loc[client_handle, 'node_handle']
|
||||||
|
node_handle_info = self.get_node_handle_info(node_handle)
|
||||||
|
service_name = self._data.clients.loc[client_handle, 'service_name']
|
||||||
|
service_info = {'service': service_name}
|
||||||
|
return {**node_handle_info, **service_info}
|
||||||
|
|
||||||
|
def get_node_handle_info(
|
||||||
|
self, node_handle: int
|
||||||
|
) -> Union[Mapping[str, Any], None]:
|
||||||
|
"""
|
||||||
|
Get information about a node handle.
|
||||||
|
|
||||||
|
:param node_handle: the node handle value
|
||||||
|
:return: a dictionary with name:value info, or `None` if it fails
|
||||||
|
"""
|
||||||
|
if node_handle not in self._data.nodes.index:
|
||||||
|
return None
|
||||||
|
|
||||||
|
node_name = self._data.nodes.loc[node_handle, 'name']
|
||||||
|
tid = self._data.nodes.loc[node_handle, 'tid']
|
||||||
|
return {'node': node_name, 'tid': tid}
|
||||||
|
|
||||||
|
def format_info_dict(self, info_dict: Mapping[str, Any]) -> str:
|
||||||
|
return ', '.join([f'{key}: {val}' for key, val in info_dict.items()])
|
|
@ -0,0 +1,13 @@
|
||||||
|
# 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.
|
57
tracetools_analysis/tracetools_analysis/conversion/ctf.py
Normal file
57
tracetools_analysis/tracetools_analysis/conversion/ctf.py
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
"""Module with CTF to pickle conversion functions."""
|
||||||
|
|
||||||
|
from pickle import Pickler
|
||||||
|
|
||||||
|
from tracetools_read import utils
|
||||||
|
|
||||||
|
|
||||||
|
def ctf_to_pickle(trace_directory: str, target: Pickler) -> int:
|
||||||
|
"""
|
||||||
|
Load CTF trace, convert events, and dump to a pickle file.
|
||||||
|
|
||||||
|
:param trace_directory: the trace directory
|
||||||
|
:param target: the target pickle file to write to
|
||||||
|
:return: the number of events written
|
||||||
|
"""
|
||||||
|
ctf_events = utils._get_trace_ctf_events(trace_directory)
|
||||||
|
|
||||||
|
count = 0
|
||||||
|
count_written = 0
|
||||||
|
|
||||||
|
for event in ctf_events:
|
||||||
|
count += 1
|
||||||
|
|
||||||
|
pod = utils.event_to_dict(event)
|
||||||
|
target.dump(pod)
|
||||||
|
count_written += 1
|
||||||
|
|
||||||
|
return count_written
|
||||||
|
|
||||||
|
|
||||||
|
def convert(trace_directory: str, pickle_target_path: str) -> int:
|
||||||
|
"""
|
||||||
|
Convert CTF trace to pickle file.
|
||||||
|
|
||||||
|
:param trace_directory: the trace directory
|
||||||
|
:param pickle_target_path: the path to the pickle file that will be created
|
||||||
|
:return: the number of events written to the pickle file
|
||||||
|
"""
|
||||||
|
with open(pickle_target_path, 'wb') as f:
|
||||||
|
p = Pickler(f, protocol=4)
|
||||||
|
count = ctf_to_pickle(trace_directory, p)
|
||||||
|
|
||||||
|
return count
|
50
tracetools_analysis/tracetools_analysis/convert.py
Normal file
50
tracetools_analysis/tracetools_analysis/convert.py
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
"""Entrypoint/script to convert CTF trace data to a pickle file."""
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import os
|
||||||
|
import time
|
||||||
|
|
||||||
|
from tracetools_analysis.conversion import ctf
|
||||||
|
|
||||||
|
|
||||||
|
def parse_args():
|
||||||
|
parser = argparse.ArgumentParser(
|
||||||
|
description='Convert CTF trace data to a pickle file.')
|
||||||
|
parser.add_argument(
|
||||||
|
'trace_directory', help='the path to the main CTF trace directory')
|
||||||
|
parser.add_argument(
|
||||||
|
'--pickle-path', '-p',
|
||||||
|
help='the path to the target pickle file to generate (default: $trace_directory/pickle)')
|
||||||
|
args = parser.parse_args()
|
||||||
|
if args.pickle_path is None:
|
||||||
|
args.pickle_path = os.path.join(args.trace_directory, 'pickle')
|
||||||
|
return args
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
args = parse_args()
|
||||||
|
|
||||||
|
trace_directory = args.trace_directory
|
||||||
|
pickle_target_path = args.pickle_path
|
||||||
|
|
||||||
|
print(f'importing trace directory: {trace_directory}')
|
||||||
|
start_time = time.time()
|
||||||
|
count = ctf.convert(trace_directory, pickle_target_path)
|
||||||
|
time_diff = time.time() - start_time
|
||||||
|
print(f'converted {count} events in {time_diff * 1000:.2f} ms')
|
||||||
|
print(f'pickle written to: {pickle_target_path}')
|
43
tracetools_analysis/tracetools_analysis/process.py
Normal file
43
tracetools_analysis/tracetools_analysis/process.py
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
"""Entrypoint/script to process events from a pickle file to build a ROS model."""
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import time
|
||||||
|
|
||||||
|
from tracetools_analysis.analysis import load
|
||||||
|
from tracetools_analysis.analysis import ros2_processor
|
||||||
|
|
||||||
|
|
||||||
|
def parse_args():
|
||||||
|
parser = argparse.ArgumentParser(description='Process a pickle file generated '
|
||||||
|
'from tracing and analyze the data.')
|
||||||
|
parser.add_argument('pickle_file', help='the pickle file to import')
|
||||||
|
return parser.parse_args()
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
args = parse_args()
|
||||||
|
|
||||||
|
pickle_filename = args.pickle_file
|
||||||
|
|
||||||
|
start_time = time.time()
|
||||||
|
events = load.load_pickle(pickle_filename)
|
||||||
|
processor = ros2_processor.ros2_process(events)
|
||||||
|
time_diff = time.time() - start_time
|
||||||
|
print(f'processed {len(events)} events in {time_diff * 1000:.2f} ms')
|
||||||
|
|
||||||
|
processor.get_data_model().print_model()
|
Loading…
Add table
Add a link
Reference in a new issue