├── .clang-format ├── .gitattributes ├── .github └── workflows │ └── build.yaml ├── .gitignore ├── CMakeLists.txt ├── CODE_OF_CONDUCT.md ├── LICENSE ├── README.md ├── scripts ├── helpers.py ├── imgs │ ├── callback_duration.png │ └── perf_data.png ├── process_callback_duration.ipynb └── process_memory_usage.ipynb └── src ├── callback_duration.cpp ├── callback_duration.hpp ├── memory_usage.cpp ├── memory_usage.hpp └── plugin.cpp /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | Language: Cpp 3 | # BasedOnStyle: Google 4 | AccessModifierOffset: -1 5 | AlignAfterOpenBracket: Align 6 | AlignArrayOfStructures: None 7 | AlignConsecutiveMacros: None 8 | AlignConsecutiveAssignments: None 9 | AlignConsecutiveBitFields: None 10 | AlignConsecutiveDeclarations: None 11 | AlignEscapedNewlines: Left 12 | AlignOperands: Align 13 | AlignTrailingComments: true 14 | AllowAllArgumentsOnNextLine: true 15 | AllowAllParametersOfDeclarationOnNextLine: true 16 | AllowShortEnumsOnASingleLine: true 17 | AllowShortBlocksOnASingleLine: Never 18 | AllowShortCaseLabelsOnASingleLine: false 19 | AllowShortFunctionsOnASingleLine: All 20 | AllowShortLambdasOnASingleLine: All 21 | AllowShortIfStatementsOnASingleLine: WithoutElse 22 | AllowShortLoopsOnASingleLine: true 23 | AlwaysBreakAfterDefinitionReturnType: None 24 | AlwaysBreakAfterReturnType: None 25 | AlwaysBreakBeforeMultilineStrings: true 26 | AlwaysBreakTemplateDeclarations: Yes 27 | AttributeMacros: 28 | - __capability 29 | BinPackArguments: true 30 | BinPackParameters: true 31 | BraceWrapping: 32 | AfterCaseLabel: false 33 | AfterClass: false 34 | AfterControlStatement: Never 35 | AfterEnum: false 36 | AfterFunction: false 37 | AfterNamespace: false 38 | AfterObjCDeclaration: false 39 | AfterStruct: false 40 | AfterUnion: false 41 | AfterExternBlock: false 42 | BeforeCatch: false 43 | BeforeElse: false 44 | BeforeLambdaBody: false 45 | BeforeWhile: false 46 | IndentBraces: false 47 | SplitEmptyFunction: true 48 | SplitEmptyRecord: true 49 | SplitEmptyNamespace: true 50 | BreakBeforeBinaryOperators: None 51 | BreakBeforeConceptDeclarations: true 52 | BreakBeforeBraces: Attach 53 | BreakBeforeInheritanceComma: false 54 | BreakInheritanceList: BeforeColon 55 | BreakBeforeTernaryOperators: true 56 | BreakConstructorInitializersBeforeComma: false 57 | BreakConstructorInitializers: BeforeColon 58 | BreakAfterJavaFieldAnnotations: false 59 | BreakStringLiterals: true 60 | ColumnLimit: 120 61 | CommentPragmas: '^ IWYU pragma:' 62 | QualifierAlignment: Leave 63 | CompactNamespaces: false 64 | ConstructorInitializerIndentWidth: 4 65 | ContinuationIndentWidth: 4 66 | Cpp11BracedListStyle: true 67 | DeriveLineEnding: true 68 | DerivePointerAlignment: true 69 | DisableFormat: false 70 | EmptyLineAfterAccessModifier: Never 71 | EmptyLineBeforeAccessModifier: LogicalBlock 72 | ExperimentalAutoDetectBinPacking: false 73 | PackConstructorInitializers: NextLine 74 | BasedOnStyle: '' 75 | ConstructorInitializerAllOnOneLineOrOnePerLine: false 76 | AllowAllConstructorInitializersOnNextLine: true 77 | FixNamespaceComments: true 78 | ForEachMacros: 79 | - foreach 80 | - Q_FOREACH 81 | - BOOST_FOREACH 82 | IfMacros: 83 | - KJ_IF_MAYBE 84 | IncludeBlocks: Regroup 85 | IncludeCategories: 86 | - Regex: '^' 87 | Priority: 2 88 | SortPriority: 0 89 | CaseSensitive: false 90 | - Regex: '^<.*\.h>' 91 | Priority: 1 92 | SortPriority: 0 93 | CaseSensitive: false 94 | - Regex: '^<.*' 95 | Priority: 2 96 | SortPriority: 0 97 | CaseSensitive: false 98 | - Regex: '.*' 99 | Priority: 3 100 | SortPriority: 0 101 | CaseSensitive: false 102 | IncludeIsMainRegex: '([-_](test|unittest))?$' 103 | IncludeIsMainSourceRegex: '' 104 | IndentAccessModifiers: false 105 | IndentCaseLabels: true 106 | IndentCaseBlocks: false 107 | IndentGotoLabels: true 108 | IndentPPDirectives: None 109 | IndentExternBlock: AfterExternBlock 110 | IndentRequires: false 111 | IndentWidth: 2 112 | IndentWrappedFunctionNames: false 113 | InsertTrailingCommas: None 114 | JavaScriptQuotes: Leave 115 | JavaScriptWrapImports: true 116 | KeepEmptyLinesAtTheStartOfBlocks: false 117 | LambdaBodyIndentation: Signature 118 | MacroBlockBegin: '' 119 | MacroBlockEnd: '' 120 | MaxEmptyLinesToKeep: 1 121 | NamespaceIndentation: None 122 | ObjCBinPackProtocolList: Never 123 | ObjCBlockIndentWidth: 2 124 | ObjCBreakBeforeNestedBlockParam: true 125 | ObjCSpaceAfterProperty: false 126 | ObjCSpaceBeforeProtocolList: true 127 | PenaltyBreakAssignment: 2 128 | PenaltyBreakBeforeFirstCallParameter: 1 129 | PenaltyBreakComment: 300 130 | PenaltyBreakFirstLessLess: 120 131 | PenaltyBreakOpenParenthesis: 0 132 | PenaltyBreakString: 1000 133 | PenaltyBreakTemplateDeclaration: 10 134 | PenaltyExcessCharacter: 1000000 135 | PenaltyReturnTypeOnItsOwnLine: 200 136 | PenaltyIndentedWhitespace: 0 137 | PointerAlignment: Left 138 | PPIndentWidth: -1 139 | RawStringFormats: 140 | - Language: Cpp 141 | Delimiters: 142 | - cc 143 | - CC 144 | - cpp 145 | - Cpp 146 | - CPP 147 | - 'c++' 148 | - 'C++' 149 | CanonicalDelimiter: '' 150 | BasedOnStyle: google 151 | - Language: TextProto 152 | Delimiters: 153 | - pb 154 | - PB 155 | - proto 156 | - PROTO 157 | EnclosingFunctions: 158 | - EqualsProto 159 | - EquivToProto 160 | - PARSE_PARTIAL_TEXT_PROTO 161 | - PARSE_TEST_PROTO 162 | - PARSE_TEXT_PROTO 163 | - ParseTextOrDie 164 | - ParseTextProtoOrDie 165 | - ParseTestProto 166 | - ParsePartialTestProto 167 | CanonicalDelimiter: pb 168 | BasedOnStyle: google 169 | ReferenceAlignment: Pointer 170 | ReflowComments: true 171 | RemoveBracesLLVM: false 172 | SeparateDefinitionBlocks: Leave 173 | ShortNamespaceLines: 1 174 | SortIncludes: CaseSensitive 175 | SortJavaStaticImport: Before 176 | SortUsingDeclarations: true 177 | SpaceAfterCStyleCast: false 178 | SpaceAfterLogicalNot: false 179 | SpaceAfterTemplateKeyword: true 180 | SpaceBeforeAssignmentOperators: true 181 | SpaceBeforeCaseColon: false 182 | SpaceBeforeCpp11BracedList: false 183 | SpaceBeforeCtorInitializerColon: true 184 | SpaceBeforeInheritanceColon: true 185 | SpaceBeforeParens: ControlStatements 186 | SpaceBeforeParensOptions: 187 | AfterControlStatements: true 188 | AfterForeachMacros: true 189 | AfterFunctionDefinitionName: false 190 | AfterFunctionDeclarationName: false 191 | AfterIfMacros: true 192 | AfterOverloadedOperator: false 193 | BeforeNonEmptyParentheses: false 194 | SpaceAroundPointerQualifiers: Default 195 | SpaceBeforeRangeBasedForLoopColon: true 196 | SpaceInEmptyBlock: false 197 | SpaceInEmptyParentheses: false 198 | SpacesBeforeTrailingComments: 2 199 | SpacesInAngles: Never 200 | SpacesInConditionalStatement: false 201 | SpacesInContainerLiterals: true 202 | SpacesInCStyleCastParentheses: false 203 | SpacesInLineCommentPrefix: 204 | Minimum: 1 205 | Maximum: -1 206 | SpacesInParentheses: false 207 | SpacesInSquareBrackets: false 208 | SpaceBeforeSquareBrackets: false 209 | BitFieldColonSpacing: Both 210 | Standard: Auto 211 | StatementAttributeLikeMacros: 212 | - Q_EMIT 213 | StatementMacros: 214 | - Q_UNUSED 215 | - QT_REQUIRE_VERSION 216 | TabWidth: 8 217 | UseCRLF: false 218 | UseTab: Never 219 | WhitespaceSensitiveMacros: 220 | - STRINGIZE 221 | - PP_STRINGIZE 222 | - BOOST_PP_STRINGIZE 223 | - NS_SWIFT_NAME 224 | - CF_SWIFT_NAME 225 | ... 226 | 227 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # prevents GitHub from thinking this is a JuPyter notebok repository due to the embedded graphs 2 | *.ipynb linguist-generated=true -------------------------------------------------------------------------------- /.github/workflows/build.yaml: -------------------------------------------------------------------------------- 1 | name: ROS2 Tracing CPP Build 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | workflow_dispatch: 9 | release: 10 | types: [edited] 11 | 12 | jobs: 13 | build-plugin: 14 | runs-on: ubuntu-22.04 15 | steps: 16 | - name: Checkout Code 17 | uses: actions/checkout@v2 18 | 19 | - name: Set up LTTNG 20 | run: | 21 | sudo apt-add-repository ppa:lttng/stable-2.13 -y 22 | sudo apt-get update 23 | sudo apt-get install -y lttng-tools lttng-modules-dkms liblttng-ust-dev 24 | 25 | - name: Build Babeltrace2 from Source 26 | run: | 27 | mkdir -p external && cd external 28 | curl -L https://www.efficios.com/files/babeltrace/babeltrace2-2.0.6.tar.bz2 -o babeltrace.tar.bz2 29 | tar -xvf babeltrace.tar.bz2 && cd babeltrace2-* 30 | BABELTRACE_DEV_MODE=1 BABELTRACE_MINIMAL_LOG_LEVEL=TRACE ./configure --disable-debug-info 31 | make -j$(nproc) 32 | sudo make install 33 | 34 | - name: Build Plugin 35 | run: | 36 | mkdir -p build && cd build 37 | cmake .. 38 | make -j$(nproc) 39 | 40 | - name: Verify Plugins Built Successfully 41 | run: | 42 | if [ ! -f build/plugins/libros2_tracing_cpp.so ]; then 43 | echo "Plugin build failed!" && exit 1 44 | fi 45 | 46 | - name: Upload Plugin as Artifact 47 | uses: actions/upload-artifact@v4 48 | with: 49 | name: plugin 50 | path: build 51 | 52 | create-release: 53 | if: github.ref == 'refs/heads/main' 54 | needs: build-plugin 55 | permissions: 56 | contents: write 57 | runs-on: ubuntu-latest 58 | steps: 59 | - name: Download Plugin Artifact 60 | uses: actions/download-artifact@v4 61 | with: 62 | name: plugin 63 | path: build 64 | 65 | - name: Upload Plugin to Release 66 | uses: ncipollo/release-action@v1 67 | with: 68 | token: ${{ secrets.GITHUB_TOKEN }} 69 | artifacts: "build/plugins/libros2_tracing_cpp.so" 70 | tag: v1 71 | allowUpdates: true 72 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # vscode settings 2 | .vscode 3 | 4 | # clangd cache 5 | .cache 6 | 7 | # cmake gitignore 8 | build 9 | CMakeLists.txt.user 10 | CMakeCache.txt 11 | CMakeFiles 12 | CMakeScripts 13 | Testing 14 | Makefile 15 | cmake_install.cmake 16 | install_manifest.txt 17 | compile_commands.json 18 | CTestTestfile.cmake 19 | _deps 20 | CMakeUserPresets.json 21 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.10) 2 | project(ros2_tracing_cpp VERSION 1.0.0 LANGUAGES CXX) 3 | 4 | set(CMAKE_CXX_STANDARD 11) 5 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 6 | set(CMAKE_CXX_EXTENSIONS OFF) 7 | 8 | set(CMAKE_EXPORT_COMPILE_COMMANDS ON) 9 | set(POSITION_INDEPENDENT_CODE ON) 10 | 11 | if(NOT CMAKE_BUILD_TYPE) 12 | set(CMAKE_BUILD_TYPE Release CACHE STRING 13 | "Choose the type of build (Debug, Release, RelWithDebInfo, MinSizeRel)" 14 | FORCE) 15 | endif() 16 | 17 | find_package(PkgConfig REQUIRED) 18 | pkg_check_modules(BABELTRACE2 REQUIRED babeltrace2) 19 | 20 | set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/plugins) 21 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${BABELTRACE2_CFLAGS}") 22 | 23 | add_library(ros2_tracing_cpp SHARED src/plugin.cpp src/callback_duration.cpp src/memory_usage.cpp) 24 | 25 | target_link_libraries(ros2_tracing_cpp ${BABELTRACE2_LIBRARIES}) 26 | 27 | target_include_directories(ros2_tracing_cpp PRIVATE ${BABELTRACE2_INCLUDE_DIRS}) 28 | 29 | set_target_properties(ros2_tracing_cpp PROPERTIES OUTPUT_NAME "ros2_tracing_cpp") 30 | 31 | set(CMAKE_INSTALL_PREFIX /usr) 32 | install(TARGETS ros2_tracing_cpp LIBRARY DESTINATION local/lib/babeltrace2/plugins) 33 | 34 | set(CPACK_GENERATOR "DEB") 35 | set(CPACK_PACKAGE_NAME "ros2_tracing_cpp") 36 | set(CPACK_PACKAGE_VERSION "1.0.0") 37 | set(CPACK_DEBIAN_COMPRESSION_TYPE "gzip") 38 | set(CPACK_PACKAGE_FILE_NAME "ros2-tracing-cpp") 39 | set(CPACK_PACKAGE_DESCRIPTION "ROS2 tracing C++ plugin") 40 | set(CPACK_PACKAGE_CONTACT "William Kaiser ") 41 | set(CPACK_RESOURCE_FILE_LICENSE "../LICENSE") 42 | set(CPACK_DEBIAN_PACKAGE_DEPENDS "babeltrace2") 43 | set(CPACK_DEBIAN_PACKAGE_SECTION "utils") 44 | set(CPACK_DEBIAN_PACKAGE_PRIORITY "optional") 45 | set(CPACK_DEBIAN_PACKAGE_ARCHITECTURE "${CMAKE_SYSTEM_PROCESSOR}") 46 | 47 | include(CPack) 48 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | We as members, contributors, and leaders pledge to make participation in our 6 | community a harassment-free experience for everyone, regardless of age, body 7 | size, visible or invisible disability, ethnicity, sex characteristics, gender 8 | identity and expression, level of experience, education, socio-economic status, 9 | nationality, personal appearance, race, religion, or sexual identity 10 | and orientation. 11 | 12 | We pledge to act and interact in ways that contribute to an open, welcoming, 13 | diverse, inclusive, and healthy community. 14 | 15 | ## Our Standards 16 | 17 | Examples of behavior that contributes to a positive environment for our 18 | community include: 19 | 20 | * Demonstrating empathy and kindness toward other people 21 | * Being respectful of differing opinions, viewpoints, and experiences 22 | * Giving and gracefully accepting constructive feedback 23 | * Accepting responsibility and apologizing to those affected by our mistakes, 24 | and learning from the experience 25 | * Focusing on what is best not just for us as individuals, but for the 26 | overall community 27 | 28 | Examples of unacceptable behavior include: 29 | 30 | * The use of sexualized language or imagery, and sexual attention or 31 | advances of any kind 32 | * Trolling, insulting or derogatory comments, and personal or political attacks 33 | * Public or private harassment 34 | * Publishing others' private information, such as a physical or email 35 | address, without their explicit permission 36 | * Other conduct which could reasonably be considered inappropriate in a 37 | professional setting 38 | 39 | ## Enforcement Responsibilities 40 | 41 | Community leaders are responsible for clarifying and enforcing our standards of 42 | acceptable behavior and will take appropriate and fair corrective action in 43 | response to any behavior that they deem inappropriate, threatening, offensive, 44 | or harmful. 45 | 46 | Community leaders have the right and responsibility to remove, edit, or reject 47 | comments, commits, code, wiki edits, issues, and other contributions that are 48 | not aligned to this Code of Conduct, and will communicate reasons for moderation 49 | decisions when appropriate. 50 | 51 | ## Scope 52 | 53 | This Code of Conduct applies within all community spaces, and also applies when 54 | an individual is officially representing the community in public spaces. 55 | Examples of representing our community include using an official e-mail address, 56 | posting via an official social media account, or acting as an appointed 57 | representative at an online or offline event. 58 | 59 | ## Enforcement 60 | 61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 62 | reported to the community leaders responsible for enforcement at 63 | wkaisertexas@gmail.com. 64 | All complaints will be reviewed and investigated promptly and fairly. 65 | 66 | All community leaders are obligated to respect the privacy and security of the 67 | reporter of any incident. 68 | 69 | ## Enforcement Guidelines 70 | 71 | Community leaders will follow these Community Impact Guidelines in determining 72 | the consequences for any action they deem in violation of this Code of Conduct: 73 | 74 | ### 1. Correction 75 | 76 | **Community Impact**: Use of inappropriate language or other behavior deemed 77 | unprofessional or unwelcome in the community. 78 | 79 | **Consequence**: A private, written warning from community leaders, providing 80 | clarity around the nature of the violation and an explanation of why the 81 | behavior was inappropriate. A public apology may be requested. 82 | 83 | ### 2. Warning 84 | 85 | **Community Impact**: A violation through a single incident or series 86 | of actions. 87 | 88 | **Consequence**: A warning with consequences for continued behavior. No 89 | interaction with the people involved, including unsolicited interaction with 90 | those enforcing the Code of Conduct, for a specified period of time. This 91 | includes avoiding interactions in community spaces as well as external channels 92 | like social media. Violating these terms may lead to a temporary or 93 | permanent ban. 94 | 95 | ### 3. Temporary Ban 96 | 97 | **Community Impact**: A serious violation of community standards, including 98 | sustained inappropriate behavior. 99 | 100 | **Consequence**: A temporary ban from any sort of interaction or public 101 | communication with the community for a specified period of time. No public or 102 | private interaction with the people involved, including unsolicited interaction 103 | with those enforcing the Code of Conduct, is allowed during this period. 104 | Violating these terms may lead to a permanent ban. 105 | 106 | ### 4. Permanent Ban 107 | 108 | **Community Impact**: Demonstrating a pattern of violation of community 109 | standards, including sustained inappropriate behavior, harassment of an 110 | individual, or aggression toward or disparagement of classes of individuals. 111 | 112 | **Consequence**: A permanent ban from any sort of public interaction within 113 | the community. 114 | 115 | ## Attribution 116 | 117 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 118 | version 2.0, available at 119 | https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. 120 | 121 | Community Impact Guidelines were inspired by [Mozilla's code of conduct 122 | enforcement ladder](https://github.com/mozilla/diversity). 123 | 124 | [homepage]: https://www.contributor-covenant.org 125 | 126 | For answers to common questions about this code of conduct, see the FAQ at 127 | https://www.contributor-covenant.org/faq. Translations are available at 128 | https://www.contributor-covenant.org/translations. 129 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Attribution-NonCommercial 4.0 International 2 | 3 | ======================================================================= 4 | 5 | Creative Commons Corporation ("Creative Commons") is not a law firm and 6 | does not provide legal services or legal advice. Distribution of 7 | Creative Commons public licenses does not create a lawyer-client or 8 | other relationship. Creative Commons makes its licenses and related 9 | information available on an "as-is" basis. Creative Commons gives no 10 | warranties regarding its licenses, any material licensed under their 11 | terms and conditions, or any related information. Creative Commons 12 | disclaims all liability for damages resulting from their use to the 13 | fullest extent possible. 14 | 15 | Using Creative Commons Public Licenses 16 | 17 | Creative Commons public licenses provide a standard set of terms and 18 | conditions that creators and other rights holders may use to share 19 | original works of authorship and other material subject to copyright 20 | and certain other rights specified in the public license below. The 21 | following considerations are for informational purposes only, are not 22 | exhaustive, and do not form part of our licenses. 23 | 24 | Considerations for licensors: Our public licenses are 25 | intended for use by those authorized to give the public 26 | permission to use material in ways otherwise restricted by 27 | copyright and certain other rights. Our licenses are 28 | irrevocable. Licensors should read and understand the terms 29 | and conditions of the license they choose before applying it. 30 | Licensors should also secure all rights necessary before 31 | applying our licenses so that the public can reuse the 32 | material as expected. Licensors should clearly mark any 33 | material not subject to the license. This includes other CC- 34 | licensed material, or material used under an exception or 35 | limitation to copyright. More considerations for licensors: 36 | wiki.creativecommons.org/Considerations_for_licensors 37 | 38 | Considerations for the public: By using one of our public 39 | licenses, a licensor grants the public permission to use the 40 | licensed material under specified terms and conditions. If 41 | the licensor's permission is not necessary for any reason--for 42 | example, because of any applicable exception or limitation to 43 | copyright--then that use is not regulated by the license. Our 44 | licenses grant only permissions under copyright and certain 45 | other rights that a licensor has authority to grant. Use of 46 | the licensed material may still be restricted for other 47 | reasons, including because others have copyright or other 48 | rights in the material. A licensor may make special requests, 49 | such as asking that all changes be marked or described. 50 | Although not required by our licenses, you are encouraged to 51 | respect those requests where reasonable. More considerations 52 | for the public: 53 | wiki.creativecommons.org/Considerations_for_licensees 54 | 55 | ======================================================================= 56 | 57 | Creative Commons Attribution-NonCommercial 4.0 International Public 58 | License 59 | 60 | By exercising the Licensed Rights (defined below), You accept and agree 61 | to be bound by the terms and conditions of this Creative Commons 62 | Attribution-NonCommercial 4.0 International Public License ("Public 63 | License"). To the extent this Public License may be interpreted as a 64 | contract, You are granted the Licensed Rights in consideration of Your 65 | acceptance of these terms and conditions, and the Licensor grants You 66 | such rights in consideration of benefits the Licensor receives from 67 | making the Licensed Material available under these terms and 68 | conditions. 69 | 70 | 71 | Section 1 -- Definitions. 72 | 73 | a. Adapted Material means material subject to Copyright and Similar 74 | Rights that is derived from or based upon the Licensed Material 75 | and in which the Licensed Material is translated, altered, 76 | arranged, transformed, or otherwise modified in a manner requiring 77 | permission under the Copyright and Similar Rights held by the 78 | Licensor. For purposes of this Public License, where the Licensed 79 | Material is a musical work, performance, or sound recording, 80 | Adapted Material is always produced where the Licensed Material is 81 | synched in timed relation with a moving image. 82 | 83 | b. Adapter's License means the license You apply to Your Copyright 84 | and Similar Rights in Your contributions to Adapted Material in 85 | accordance with the terms and conditions of this Public License. 86 | 87 | c. Copyright and Similar Rights means copyright and/or similar rights 88 | closely related to copyright including, without limitation, 89 | performance, broadcast, sound recording, and Sui Generis Database 90 | Rights, without regard to how the rights are labeled or 91 | categorized. For purposes of this Public License, the rights 92 | specified in Section 2(b)(1)-(2) are not Copyright and Similar 93 | Rights. 94 | d. Effective Technological Measures means those measures that, in the 95 | absence of proper authority, may not be circumvented under laws 96 | fulfilling obligations under Article 11 of the WIPO Copyright 97 | Treaty adopted on December 20, 1996, and/or similar international 98 | agreements. 99 | 100 | e. Exceptions and Limitations means fair use, fair dealing, and/or 101 | any other exception or limitation to Copyright and Similar Rights 102 | that applies to Your use of the Licensed Material. 103 | 104 | f. Licensed Material means the artistic or literary work, database, 105 | or other material to which the Licensor applied this Public 106 | License. 107 | 108 | g. Licensed Rights means the rights granted to You subject to the 109 | terms and conditions of this Public License, which are limited to 110 | all Copyright and Similar Rights that apply to Your use of the 111 | Licensed Material and that the Licensor has authority to license. 112 | 113 | h. Licensor means the individual(s) or entity(ies) granting rights 114 | under this Public License. 115 | 116 | i. NonCommercial means not primarily intended for or directed towards 117 | commercial advantage or monetary compensation. For purposes of 118 | this Public License, the exchange of the Licensed Material for 119 | other material subject to Copyright and Similar Rights by digital 120 | file-sharing or similar means is NonCommercial provided there is 121 | no payment of monetary compensation in connection with the 122 | exchange. 123 | 124 | j. Share means to provide material to the public by any means or 125 | process that requires permission under the Licensed Rights, such 126 | as reproduction, public display, public performance, distribution, 127 | dissemination, communication, or importation, and to make material 128 | available to the public including in ways that members of the 129 | public may access the material from a place and at a time 130 | individually chosen by them. 131 | 132 | k. Sui Generis Database Rights means rights other than copyright 133 | resulting from Directive 96/9/EC of the European Parliament and of 134 | the Council of 11 March 1996 on the legal protection of databases, 135 | as amended and/or succeeded, as well as other essentially 136 | equivalent rights anywhere in the world. 137 | 138 | l. You means the individual or entity exercising the Licensed Rights 139 | under this Public License. Your has a corresponding meaning. 140 | 141 | 142 | Section 2 -- Scope. 143 | 144 | a. License grant. 145 | 146 | 1. Subject to the terms and conditions of this Public License, 147 | the Licensor hereby grants You a worldwide, royalty-free, 148 | non-sublicensable, non-exclusive, irrevocable license to 149 | exercise the Licensed Rights in the Licensed Material to: 150 | 151 | a. reproduce and Share the Licensed Material, in whole or 152 | in part, for NonCommercial purposes only; and 153 | 154 | b. produce, reproduce, and Share Adapted Material for 155 | NonCommercial purposes only. 156 | 157 | 2. Exceptions and Limitations. For the avoidance of doubt, where 158 | Exceptions and Limitations apply to Your use, this Public 159 | License does not apply, and You do not need to comply with 160 | its terms and conditions. 161 | 162 | 3. Term. The term of this Public License is specified in Section 163 | 6(a). 164 | 165 | 4. Media and formats; technical modifications allowed. The 166 | Licensor authorizes You to exercise the Licensed Rights in 167 | all media and formats whether now known or hereafter created, 168 | and to make technical modifications necessary to do so. The 169 | Licensor waives and/or agrees not to assert any right or 170 | authority to forbid You from making technical modifications 171 | necessary to exercise the Licensed Rights, including 172 | technical modifications necessary to circumvent Effective 173 | Technological Measures. For purposes of this Public License, 174 | simply making modifications authorized by this Section 2(a) 175 | (4) never produces Adapted Material. 176 | 177 | 5. Downstream recipients. 178 | 179 | a. Offer from the Licensor -- Licensed Material. Every 180 | recipient of the Licensed Material automatically 181 | receives an offer from the Licensor to exercise the 182 | Licensed Rights under the terms and conditions of this 183 | Public License. 184 | 185 | b. No downstream restrictions. You may not offer or impose 186 | any additional or different terms or conditions on, or 187 | apply any Effective Technological Measures to, the 188 | Licensed Material if doing so restricts exercise of the 189 | Licensed Rights by any recipient of the Licensed 190 | Material. 191 | 192 | 6. No endorsement. Nothing in this Public License constitutes or 193 | may be construed as permission to assert or imply that You 194 | are, or that Your use of the Licensed Material is, connected 195 | with, or sponsored, endorsed, or granted official status by, 196 | the Licensor or others designated to receive attribution as 197 | provided in Section 3(a)(1)(A)(i). 198 | 199 | b. Other rights. 200 | 201 | 1. Moral rights, such as the right of integrity, are not 202 | licensed under this Public License, nor are publicity, 203 | privacy, and/or other similar personality rights; however, to 204 | the extent possible, the Licensor waives and/or agrees not to 205 | assert any such rights held by the Licensor to the limited 206 | extent necessary to allow You to exercise the Licensed 207 | Rights, but not otherwise. 208 | 209 | 2. Patent and trademark rights are not licensed under this 210 | Public License. 211 | 212 | 3. To the extent possible, the Licensor waives any right to 213 | collect royalties from You for the exercise of the Licensed 214 | Rights, whether directly or through a collecting society 215 | under any voluntary or waivable statutory or compulsory 216 | licensing scheme. In all other cases the Licensor expressly 217 | reserves any right to collect such royalties, including when 218 | the Licensed Material is used other than for NonCommercial 219 | purposes. 220 | 221 | 222 | Section 3 -- License Conditions. 223 | 224 | Your exercise of the Licensed Rights is expressly made subject to the 225 | following conditions. 226 | 227 | a. Attribution. 228 | 229 | 1. If You Share the Licensed Material (including in modified 230 | form), You must: 231 | 232 | a. retain the following if it is supplied by the Licensor 233 | with the Licensed Material: 234 | 235 | i. identification of the creator(s) of the Licensed 236 | Material and any others designated to receive 237 | attribution, in any reasonable manner requested by 238 | the Licensor (including by pseudonym if 239 | designated); 240 | 241 | ii. a copyright notice; 242 | 243 | iii. a notice that refers to this Public License; 244 | 245 | iv. a notice that refers to the disclaimer of 246 | warranties; 247 | 248 | v. a URI or hyperlink to the Licensed Material to the 249 | extent reasonably practicable; 250 | 251 | b. indicate if You modified the Licensed Material and 252 | retain an indication of any previous modifications; and 253 | 254 | c. indicate the Licensed Material is licensed under this 255 | Public License, and include the text of, or the URI or 256 | hyperlink to, this Public License. 257 | 258 | 2. You may satisfy the conditions in Section 3(a)(1) in any 259 | reasonable manner based on the medium, means, and context in 260 | which You Share the Licensed Material. For example, it may be 261 | reasonable to satisfy the conditions by providing a URI or 262 | hyperlink to a resource that includes the required 263 | information. 264 | 265 | 3. If requested by the Licensor, You must remove any of the 266 | information required by Section 3(a)(1)(A) to the extent 267 | reasonably practicable. 268 | 269 | 4. If You Share Adapted Material You produce, the Adapter's 270 | License You apply must not prevent recipients of the Adapted 271 | Material from complying with this Public License. 272 | 273 | 274 | Section 4 -- Sui Generis Database Rights. 275 | 276 | Where the Licensed Rights include Sui Generis Database Rights that 277 | apply to Your use of the Licensed Material: 278 | 279 | a. for the avoidance of doubt, Section 2(a)(1) grants You the right 280 | to extract, reuse, reproduce, and Share all or a substantial 281 | portion of the contents of the database for NonCommercial purposes 282 | only; 283 | 284 | b. if You include all or a substantial portion of the database 285 | contents in a database in which You have Sui Generis Database 286 | Rights, then the database in which You have Sui Generis Database 287 | Rights (but not its individual contents) is Adapted Material; and 288 | 289 | c. You must comply with the conditions in Section 3(a) if You Share 290 | all or a substantial portion of the contents of the database. 291 | 292 | For the avoidance of doubt, this Section 4 supplements and does not 293 | replace Your obligations under this Public License where the Licensed 294 | Rights include other Copyright and Similar Rights. 295 | 296 | 297 | Section 5 -- Disclaimer of Warranties and Limitation of Liability. 298 | 299 | a. UNLESS OTHERWISE SEPARATELY UNDERTAKEN BY THE LICENSOR, TO THE 300 | EXTENT POSSIBLE, THE LICENSOR OFFERS THE LICENSED MATERIAL AS-IS 301 | AND AS-AVAILABLE, AND MAKES NO REPRESENTATIONS OR WARRANTIES OF 302 | ANY KIND CONCERNING THE LICENSED MATERIAL, WHETHER EXPRESS, 303 | IMPLIED, STATUTORY, OR OTHER. THIS INCLUDES, WITHOUT LIMITATION, 304 | WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR 305 | PURPOSE, NON-INFRINGEMENT, ABSENCE OF LATENT OR OTHER DEFECTS, 306 | ACCURACY, OR THE PRESENCE OR ABSENCE OF ERRORS, WHETHER OR NOT 307 | KNOWN OR DISCOVERABLE. WHERE DISCLAIMERS OF WARRANTIES ARE NOT 308 | ALLOWED IN FULL OR IN PART, THIS DISCLAIMER MAY NOT APPLY TO YOU. 309 | 310 | b. TO THE EXTENT POSSIBLE, IN NO EVENT WILL THE LICENSOR BE LIABLE 311 | TO YOU ON ANY LEGAL THEORY (INCLUDING, WITHOUT LIMITATION, 312 | NEGLIGENCE) OR OTHERWISE FOR ANY DIRECT, SPECIAL, INDIRECT, 313 | INCIDENTAL, CONSEQUENTIAL, PUNITIVE, EXEMPLARY, OR OTHER LOSSES, 314 | COSTS, EXPENSES, OR DAMAGES ARISING OUT OF THIS PUBLIC LICENSE OR 315 | USE OF THE LICENSED MATERIAL, EVEN IF THE LICENSOR HAS BEEN 316 | ADVISED OF THE POSSIBILITY OF SUCH LOSSES, COSTS, EXPENSES, OR 317 | DAMAGES. WHERE A LIMITATION OF LIABILITY IS NOT ALLOWED IN FULL OR 318 | IN PART, THIS LIMITATION MAY NOT APPLY TO YOU. 319 | 320 | c. The disclaimer of warranties and limitation of liability provided 321 | above shall be interpreted in a manner that, to the extent 322 | possible, most closely approximates an absolute disclaimer and 323 | waiver of all liability. 324 | 325 | 326 | Section 6 -- Term and Termination. 327 | 328 | a. This Public License applies for the term of the Copyright and 329 | Similar Rights licensed here. However, if You fail to comply with 330 | this Public License, then Your rights under this Public License 331 | terminate automatically. 332 | 333 | b. Where Your right to use the Licensed Material has terminated under 334 | Section 6(a), it reinstates: 335 | 336 | 1. automatically as of the date the violation is cured, provided 337 | it is cured within 30 days of Your discovery of the 338 | violation; or 339 | 340 | 2. upon express reinstatement by the Licensor. 341 | 342 | For the avoidance of doubt, this Section 6(b) does not affect any 343 | right the Licensor may have to seek remedies for Your violations 344 | of this Public License. 345 | 346 | c. For the avoidance of doubt, the Licensor may also offer the 347 | Licensed Material under separate terms or conditions or stop 348 | distributing the Licensed Material at any time; however, doing so 349 | will not terminate this Public License. 350 | 351 | d. Sections 1, 5, 6, 7, and 8 survive termination of this Public 352 | License. 353 | 354 | 355 | Section 7 -- Other Terms and Conditions. 356 | 357 | a. The Licensor shall not be bound by any additional or different 358 | terms or conditions communicated by You unless expressly agreed. 359 | 360 | b. Any arrangements, understandings, or agreements regarding the 361 | Licensed Material not stated herein are separate from and 362 | independent of the terms and conditions of this Public License. 363 | 364 | 365 | Section 8 -- Interpretation. 366 | 367 | a. For the avoidance of doubt, this Public License does not, and 368 | shall not be interpreted to, reduce, limit, restrict, or impose 369 | conditions on any use of the Licensed Material that could lawfully 370 | be made without permission under this Public License. 371 | 372 | b. To the extent possible, if any provision of this Public License is 373 | deemed unenforceable, it shall be automatically reformed to the 374 | minimum extent necessary to make it enforceable. If the provision 375 | cannot be reformed, it shall be severed from this Public License 376 | without affecting the enforceability of the remaining terms and 377 | conditions. 378 | 379 | c. No term or condition of this Public License will be waived and no 380 | failure to comply consented to unless expressly agreed to by the 381 | Licensor. 382 | 383 | d. Nothing in this Public License constitutes or may be interpreted 384 | as a limitation upon, or waiver of, any privileges and immunities 385 | that apply to the Licensor or You, including from the legal 386 | processes of any jurisdiction or authority. 387 | 388 | ======================================================================= 389 | 390 | Creative Commons is not a party to its public 391 | licenses. Notwithstanding, Creative Commons may elect to apply one of 392 | its public licenses to material it publishes and in those instances 393 | will be considered the “Licensor.” The text of the Creative Commons 394 | public licenses is dedicated to the public domain under the CC0 Public 395 | Domain Dedication. Except for the limited purpose of indicating that 396 | material is shared under a Creative Commons public license or as 397 | otherwise permitted by the Creative Commons policies published at 398 | creativecommons.org/policies, Creative Commons does not authorize the 399 | use of the trademark "Creative Commons" or any other trademark or logo 400 | of Creative Commons without its prior written consent including, 401 | without limitation, in connection with any unauthorized modifications 402 | to any of its public licenses or any other arrangements, 403 | understandings, or agreements concerning use of licensed material. For 404 | the avoidance of doubt, this paragraph does not form part of the 405 | public licenses. 406 | 407 | Creative Commons may be contacted at creativecommons.org. 408 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ROS 2 Tracing C++ 2 | 3 | A custom plugin for babeltrace2 which replaces [ROS 2 Tracing](https://github.com/ros2/ros2_tracing) for analyzing C++ ROS 2 nodes. 4 | 5 |

6 | Callback duration created by ROS 2 Tracing C++ 7 |

8 |

9 | Callback execution time plot generated using ROS 2 Tracing C++ 10 |

11 | 12 | ## Table of Contents 13 | 14 | - [Introduction](#introduction) 15 | - [Alternatives to ROS 2 Tracing C++](#alternatives-to-ros-2-tracing-c) 16 | - [Supported Analysis Sinks](#supported-analysis-sinks) 17 | - [Running ROS 2 Tracing C++](#running-ros-2-tracing-c) 18 | - [Analyzing Processed Traces in a Jupyter Notebook](#analyzing-processed-traces-in-a-jupyter-notebook) 19 | - [Installation Guide](#installation-guide) 20 | - [LTTNG Installation](#lttng-and-babeltrace-2-installation) 21 | - [ROS 2 TraceTools Launch Installation](#ros-2-tracetools-launch-installation) 22 | - [ROS 2 Tracing C++ Installation](#ros-2-tracing-c-installation) 23 | - [Building from Source](#building-from-source) 24 | - [Babeltrace 2 from Source](#babeltrace-2-from-source) 25 | - [ROS 2 Tracing C++ from Source](#ros-2-tracing-c-from-source) 26 | - [Collecting Traces with ROS 2 TraceTools Launch](#collecting-traces-with-ros-2-tracetools-launch) 27 | - [Sink Output Data Schema](#sink-output-data-schema) 28 | - [Callback Duration Products](#callback-duration-products) 29 | - [Memory Usage Products](#memory-usage-products) 30 | - [Improving Trace Processing Time](#improving-trace-processing-time) 31 | - [Collecting Traces in Docker](#collecting-traces-in-docker) 32 | - [Resources](#resources) 33 | 34 | ## Introduction 35 | 36 | Trace analysis is incredibly powerful. However, processing traces with ROS 2 tracing took quite a bit longer than program execution times themselves. As a result, I wrote a custom C++ babeltrace2 plugin which uses the same mechanism of analyzing tracing but with much greater performance. As a rule of thumb, speedups of between **25x and 50x** can be expected. This enables real-time trace processing. 37 | 38 |

39 | Speed comparison between ros 2 tracing and ros 2 tracing c++ 40 |

41 |

42 | Processing times of 2 minutes of real-time trace collection on the Cavalier Autonomous Racing stack. A speedup of 52.1x and 20.9x is achieved for the memory usage and callback duration calculator, respectively. 43 |

44 | 45 | ## Alternatives to ROS 2 Tracing C++ 46 | 47 | There exist other trace analysis solutions for ROS 2 48 | 49 | - [ROS 2 tracing analysis](https://github.com/ros-tracing/tracetools_analysis/tree/humble) is a good choice if you do not need high-performance trace analysis. 50 | - [LTTNG Analyses](https://github.com/lttng/lttng-analyses) have good examples of trace-analysis scripts written in Python if you are looking to learn more about what you can get from trace-analysis. 51 | 52 | > [!IMPORTANT] 53 | > ROS 2 Tracing and the associated tooling is quite good. Consider analyzing traces with that first as it is quick to get started. If your traces process in a reasonable amount of time, you likely do not need to use this package. 54 | 55 | ## Supported Analysis Sinks 56 | 57 | The supported features are inspired by the [ROS 2 tracing analysis](https://github.com/ros-tracing/tracetools_analysis/tree/humble) package. 58 | 59 | | Plugin | Required Tracepoints | Description | 60 | | :----- | :------------------- | :---------- | 61 | | `sink.ros2_tracing_cpp.callback_duration` | `ros2:rclcpp_callback_register`, `ros2:callback_start`, `ros2:callback_end` | Collects the duration of each callback triggered | 62 | | `sink.ros2_tracing_cpp.memory_usage` | `lttng_ust_libc:malloc`, `lttng_ust_libc:calloc`, `lttng_ust_libc:realloc`, `lttng_ust_libc:memalign`, `lttng_ust_libc:posix_memalign`, `lttng_ust_libc:free` | Gets information about the lifecycle of objects allocated by nodes | 63 | 64 | ## Running ROS 2 Tracing C++ 65 | 66 | To run the plugin, use `babeltrace2` with the specified plugin path and your tracing session output path. 67 | 68 | ```bash 69 | babeltrace2 --plugin-path . ~/.ros/tracing/ros2_tracing_session/ --component=sink.ros2_tracing_cpp.memory_usage 70 | ``` 71 | 72 | After the trace has been fully processed, a metadata file will be produced for the run and a set of CSV files containing the processed traces. For more information about the format of produced files, see the [Sink Output Data Schema](#sink-output-data-schema) section. 73 | 74 | ## Analyzing Processed Traces in a Jupyter Notebook 75 | 76 | For each sink, there is a pre-built Jupyter notebook which makes a standard set of plots per node. These analyses are designed to be extensible and a good starting point for any future work. 77 | 78 | | Plugin | Jupyter Notebook | 79 | | :----- | :--------------- | 80 | | `sink.ros2_tracing_cpp.callback_duration` | [process_callback_duration.ipynb](./scripts/process_callback_duration.ipynb) | 81 | | `sink.ros2_tracing_cpp.memory_usage` | [process_callback_duration.ipynb](./scripts/process_memory_usage.ipynb) | 82 | 83 | 84 | ## Installation Guide 85 | 86 | To use ROS 2 Tracing C++, you must first: 87 | 88 | 1. install [Linux Trace Toolkit: next generation (LTTNG)](https://lttng.org/docs/) and [Babeltrace 2](https://babeltrace.org/#bt2-get) 89 | 2. install ROS 2 Tracetools Launch and rebuild your code 90 | 3. download and move the plugin from the releases page 91 | 92 | ### LTTNG and Babeltrace 2 Installation 93 | 94 | Install LTTNG and Babeltrace 2 from [the stable Linux PPA package](lttng.org/docs/v2.13/#doc-ubuntu-ppa): 95 | 96 | ```bash 97 | sudo apt-add-repository ppa:lttng/stable-2.13 98 | sudo apt-get update 99 | sudo apt install -y lttng-tools lttng-modules-dkms liblttng-ust-dev 100 | sudo apt install -y babeltrace2 101 | ``` 102 | 103 | ### ROS 2 TraceTools Launch Installation 104 | 105 | Installing tracetools requires `apt` installing the `tracetools-launch` package and then sourcing the tracetools package **before** building any of the other packages which should be instrumented. 106 | 107 | ```bash 108 | sudo apt install ros-humble-tracetools-launch 109 | (cd external && git clone https://github.com/ros2/ros2_tracing && cd ros2_tracing && git checkout humble) 110 | colcon build --packages-up-to tracetools 111 | source install/setup.sh 112 | ``` 113 | 114 | After this, you can build your packages with `colcon` like normal. 115 | 116 | > [!WARNING] 117 | > However, you must `source install/setup.sh` before building any of the other packages you want tracepoints enabled in. This is **atypical** for a ROS 2 project but is a valid workaround to building with tracepoints enabled while not requiring a from-source ROS build. 118 | 119 | ### ROS 2 Tracing C++ Installation 120 | 121 | If you are running on a x86 system, you can [download the latest release binary](https://github.com/wkaisertexas/ros2_tracing_cpp/releases/latest) from the GitHub release. 122 | 123 | ```bash 124 | wget https://github.com/wkaisertexas/ros2_tracing_cpp/releases/download/v1/libros2_tracing_cpp.so 125 | mkdir -p /usr/local/lib/babeltrace2/plugins 126 | sudo cp libros2_tracing_cpp.so /usr/local/lib/babeltrace2/plugins 127 | ``` 128 | 129 | > [!NOTE] 130 | > Moving `libros2_tracing_cpp.so` to `/usr/local/lib/babeltrace2/plugins` is optional but allows the trace analysis sinks to be used without specifying `--plugin-path`. 131 | 132 | ## Building from Source 133 | 134 | Building ROS 2 Tracing C++ from source requires: 135 | 136 | 1. build `babeltrace2` from source 137 | 2. build the plugin `libros2_tracing_cpp.so` 138 | 139 | ### Babeltrace 2 from Source 140 | 141 | To build a plugin, building from source is required ([see guide](https://babeltrace.org/docs/v2.0/libbabeltrace2/guide-build-bt2-dev.html)). This assumes you have a `.gitignore`-d directory named `external` for non-source packages. 142 | 143 | ```bash 144 | # downloading and extracting babeltrace2 145 | mkdir -p external 146 | cd external 147 | curl https://www.efficios.com/files/babeltrace/babeltrace2-2.0.6.tar.bz2 -o babeltrace.tar.bz2 148 | tar -xvf babeltrace.tar.bz2 149 | cd babeltrace2-*/ 150 | 151 | # building an installing from source 152 | BABELTRACE_DEV_MODE=1 BABELTRACE_MINIMAL_LOG_LEVEL=TRACE ./configure --disable-debug-info 153 | make -j$(nproc) 154 | sudo make install 155 | cd ../.. 156 | ``` 157 | 158 | After that, `babeltrace2` will be built from source and installed. 159 | 160 | ### ROS 2 Tracing C++ from Source 161 | 162 | To build the ROS 2 Tracing C++ plugin from source, run: 163 | 164 | ```bash 165 | git clone https://github.com/wkaisertexas/ros2_tracing_cpp 166 | cd ros2_tracing_cpp 167 | 168 | mkdir build 169 | cd build 170 | cmake .. 171 | make -j$(nproc) 172 | sudo make install 173 | ``` 174 | 175 | At this point, the plugin will be built in `build/plugins/libros2_tracing_cpp.so` where you can reference the different sinks to process your traces. 176 | 177 | ## Collecting Traces with ROS 2 TraceTools Launch 178 | 179 | With `tracetools-launch` installed, you can add the following to your launch file to collect traces. 180 | 181 | ```python 182 | from launch import LaunchDescription 183 | 184 | from tracetools_launch.action import Trace 185 | 186 | CALLBACK_TRACEPOINTS = set([ 187 | "ros2:rclcpp_callback_register", 188 | "ros2:callback_start", 189 | "ros2:callback_end", 190 | ]) 191 | """Tracepoints required for callback duration analysis""" 192 | 193 | MEMORY_TRACEPOINTS = set([ 194 | "lttng_ust_libc:malloc", 195 | "lttng_ust_libc:calloc", 196 | "lttng_ust_libc:realloc", 197 | "lttng_ust_libc:memalign", 198 | "lttng_ust_libc:posix_memalign", 199 | "lttng_ust_libc:free" 200 | ]) 201 | """Tracepoints required for memory usage analysis""" 202 | 203 | def generate_launch_description() -> LaunchDescription: 204 | tracepoints_to_collect = CALLBACK_TRACEPOINTS | MEMORY_TRACEPOINTS 205 | trace_session = Trace( 206 | session_name="callback_duration_and_memory_usage", 207 | events_ust=list(tracepoints_to_collect), 208 | base_path="~/.ros/tracing", # default trace location 209 | ) 210 | 211 | # the rest of your launch file 212 | 213 | return LaunchDescription([ 214 | trace_session, 215 | # your nodes and parameters 216 | ]) 217 | ``` 218 | 219 | This launch command will create `~/.ros/tracing/callback_duration_and_memory_usage` which contains several nested folders. Running `babeltrace2 ~/.ros/tracing/callback_duration_and_memory_usage | less` will print out traces. 220 | 221 | ## Sink Output Data Schema 222 | 223 | In this section, notes and helpful tips to process outputs generated by each plugin is included. 224 | 225 | ### Callback Duration Products 226 | 227 | For files produced by `callback_duration`, a `callback_duration_metadata.csv` file which contains links and metadata to `callback_*.csv` files is produced. A script called [`scripts/process_callback_duration.ipynb`](./scripts/process_callback_duration.ipynb) makes callback duration plots using the collected traces. 228 | 229 | | Column | Description | 230 | | :----- | :---------- | 231 | | `symbol` | The raw callback symbol monitored | 232 | | `procname` | Process name of collected callback | 233 | | `address` | The address of the selected callback, used as a pseudo-identifier | 234 | | `count` | the number of times the callback was called | 235 | | `path` | the relative path of the file containing the callback | 236 | | `avg_duration` | the average duration of the callback (useful for crude filtering) | 237 | 238 | The file contained in the path variable is a CSV file. The first two rows are the `symbol` and the `procname`. Then the file has the following columns: 239 | 240 | | Column | Description | 241 | | :----- | :---------- | 242 | | `time` | time in nanoseconds since the unix epoch that the callback occurred | 243 | | `duration` | the duration of the callback in nanoseconds | 244 | 245 | > [!TIP] 246 | > If you are processing this in Pandas, you can use `pd.read_csv(path, skiprows=2)` to ignore the header 247 | 248 | ### Memory Usage Products 249 | 250 | Files produced by `memory_usage`, you get a `memory_usage_metadata.csv` file which contains links and metadata to `mem_*.csv` files. A script called [`scripts/process_memory_usage.ipynb`](./scripts/process_memory_usage.ipynb) makes a 2x2 grid of plots examining allocations over time and the relative frequency of allocations of differing sizes. 251 | 252 | | Columns | Description | 253 | | :------ | :---------- | 254 | | `path` | the relative path to the `.csv` file containing the allocation information | 255 | | `vpid` | the virtual pid of the ros2 node | 256 | | `procname` | process name making the allocation | 257 | | `avg_alloc_lifecycle` | the average lifecycle of allocated objects | 258 | | `avg_alloc_size` | the average allocation size | 259 | | `allocation_count` | the total number of allocations | 260 | | `max_process_memory` | the maximum amount of process memory | 261 | 262 | The file contained in the `path` variable is a CSV file containing the following information: 263 | 264 | | Columns | Description | 265 | | :------ | :---------- | 266 | | `type` | type of the allocation either `malloc`, `calloc`, `realloc`, `memalign` or `posix_memalign` | 267 | | `time` | time in nanoseconds since the unix epoch the object was allocated | 268 | | `duration` | how long the memory stuck around. A nan value means that the object was not freed | 269 | | `prev_size` | the previous allocation size in bytes (only for realloc) | 270 | | `size` | the size of the allocation in bytes | 271 | | `vtid` | the virtual thread id which called the allocator | 272 | 273 | ## Improving Trace Processing Time 274 | 275 | Looking at a profile of this plugin, iterating through the traces with babeltrace2 takes **over 85% of total time**. Collecting the minimal set of events required for each plugin is the single-greatest tactic for reducing program execution time. 276 | 277 | ![Perf Trace of `memory_usage`](./scripts/imgs/perf_data.png) 278 | 279 | > [!IMPORTANT] 280 | > To speed up processing time, consider using a live session created with `llttng create my-session --live` and `babeltrace2 --plugin-path . --input-format=lttng-live net://localhost/host/localhost/my-session --component=sink.ros2_tracing_cpp.memory_usage` 281 | 282 | ## Collecting Traces in Docker 283 | 284 | Collecting traces inside a Docker container requires a non-standard setup. Namely, you **do not** install `lttng-modules-dkms` 285 | 286 | ```dockerfile 287 | RUN apt update && apt install -y ros-humble-tracetools-* babeltrace2 lttng-tools liblttng-ust-dev 288 | ``` 289 | 290 | After this, running your docker image with the `--privileged` flag will allow the collection of both userspace and kernel traces inside the container. 291 | 292 | ## Resources 293 | 294 | 1. [Babeltrace 2 Sink Example](https://babeltrace.org/docs/v2.0/libbabeltrace2/example-simple-sink-cmp-cls.html) 295 | 2. [ROS 2 Trace Analysis](https://github.com/ros-tracing/tracetools_analysis/tree/humble) 296 | 3. [LTTNG Documentation](https://lttng.org/docs/v2.13/) 297 | 4. [ROS 2 Tracing White Paper](https://arxiv.org/abs/2201.00393) 298 | 5. [ROS 2 Tracing Overhead Evaluation](https://github.com/christophebedard/ros2_tracing-overhead-evaluation) 299 | 6. [ROS 2 Tracing GitHub](https://github.com/ros2/ros2_tracing/tree/humble) 300 | 301 | > [!NOTE] 302 | > If you liked this repository, please consider giving it a star! 303 | -------------------------------------------------------------------------------- /scripts/helpers.py: -------------------------------------------------------------------------------- 1 | """ 2 | Helpers when working with traces 3 | """ 4 | 5 | def prettify( 6 | original: str, 7 | ) -> str: 8 | """ 9 | Process symbol to make it more readable. 10 | 11 | * remove std::allocator 12 | * remove std::default_delete 13 | * bind object: remove placeholder 14 | 15 | :param original: the original symbol 16 | :return: the prettified symbol 17 | """ 18 | pretty = original 19 | # remove spaces 20 | pretty = pretty.replace(" ", "") 21 | # allocator 22 | std_allocator = "_>" 23 | pretty = pretty.replace(std_allocator, "") 24 | # default_delete 25 | std_defaultdelete = "std::default_delete" 26 | if std_defaultdelete in pretty: 27 | dd_start = pretty.find(std_defaultdelete) 28 | template_param_open = dd_start + len(std_defaultdelete) 29 | # find index of matching/closing GT sign 30 | template_param_close = template_param_open 31 | level = 0 32 | done = False 33 | while not done: 34 | template_param_close += 1 35 | if pretty[template_param_close] == "<": 36 | level += 1 37 | elif pretty[template_param_close] == ">": 38 | if level == 0: 39 | done = True 40 | else: 41 | level -= 1 42 | pretty = pretty[:dd_start] + pretty[(template_param_close + 1) :] 43 | # bind 44 | std_bind = "std::_Bind<" 45 | if pretty.startswith(std_bind): 46 | # remove bind<> 47 | pretty = pretty.replace(std_bind, "") 48 | pretty = pretty[:-1] 49 | # remove placeholder stuff 50 | placeholder_from = pretty.find("*") 51 | placeholder_to = pretty.find(")", placeholder_from) 52 | pretty = pretty[:placeholder_from] + "?" + pretty[(placeholder_to + 1) :] 53 | # remove dangling comma 54 | pretty = pretty.replace(",>", ">") 55 | # restore meaningful spaces 56 | if pretty.startswith("void"): 57 | pretty = "void" + " " + pretty[len("void") :] 58 | if pretty.endswith("const"): 59 | pretty = pretty[: (len(pretty) - len("const"))] + " " + "const" 60 | 61 | # remove std:: 62 | pretty = pretty.replace("std::", "") 63 | 64 | # remove void 65 | pretty = pretty.replace("void ", "") 66 | 67 | # adding a new line 68 | pretty = pretty.replace("::?)(", "::?)\n(") 69 | 70 | # getting rid of the parenthesis 71 | pretty = pretty.replace("::?)", "::?")[1:] 72 | 73 | return pretty 74 | -------------------------------------------------------------------------------- /scripts/imgs/callback_duration.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wkaisertexas/ros2_tracing_cpp/39ad1b03c18a512663bfe39f4ed370417905573d/scripts/imgs/callback_duration.png -------------------------------------------------------------------------------- /scripts/imgs/perf_data.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wkaisertexas/ros2_tracing_cpp/39ad1b03c18a512663bfe39f4ed370417905573d/scripts/imgs/perf_data.png -------------------------------------------------------------------------------- /src/callback_duration.cpp: -------------------------------------------------------------------------------- 1 | #include "callback_duration.hpp" 2 | 3 | bt_component_class_initialize_method_status callback_duration_initialize( 4 | bt_self_component_sink *self_component_sink, bt_self_component_sink_configuration * /*configuration*/, 5 | const bt_value * /*params*/, void * /*initialize_method_data*/) { 6 | auto *callback_duration = new CallbackDuration(); 7 | 8 | /* Initialize the first event message's index */ 9 | callback_duration->events_index = 1; 10 | 11 | const size_t DEFAULT_SIZE = 10000; 12 | callback_duration->callback_last_called.reserve(DEFAULT_SIZE); 13 | callback_duration->callback_last_called.max_load_factor(0.3); 14 | 15 | /* Set the component's user data to our private data structure */ 16 | bt_self_component_set_data(bt_self_component_sink_as_self_component(self_component_sink), callback_duration); 17 | 18 | /* Add an input port named `in` to the sink component */ 19 | bt_self_component_sink_add_input_port(self_component_sink, "in", NULL, NULL); 20 | 21 | return BT_COMPONENT_CLASS_INITIALIZE_METHOD_STATUS_OK; 22 | } 23 | 24 | void callback_duration_finalize(bt_self_component_sink *self_component_sink) { 25 | /* Retrieve our private data from the component's user data */ 26 | auto *callback_duration = (struct CallbackDuration *)bt_self_component_get_data( 27 | bt_self_component_sink_as_self_component(self_component_sink)); 28 | 29 | std::ofstream metadataFile("callback_duration_metadata.csv"); 30 | metadataFile << "symbol,procname,address,count,path,avg_duration" << std::endl; 31 | 32 | /* Iterate over the callback durations to create CSV files */ 33 | for (const auto &entry : callback_duration->callback_durations) { 34 | uint64_t callback_address = entry.first; 35 | const auto &durations = entry.second; 36 | 37 | // Get the CallbackInfo for this callback address 38 | auto &callback_info = callback_duration->callback_map[callback_address]; 39 | auto &callback_name = callback_info.symbol; 40 | auto &procname = callback_info.procname; 41 | 42 | std::string s = callback_name; 43 | 44 | /* Sanitize the string for CSV seralization */ 45 | size_t pos = 0; 46 | while ((pos = s.find('"', pos)) != std::string::npos) { 47 | s.replace(pos, 1, "\"\""); 48 | pos += 2; // Move past the escaped double quotes 49 | } 50 | s = "\"" + s + "\""; 51 | 52 | metadataFile << s << ","; 53 | metadataFile << procname << ","; 54 | metadataFile << callback_address << ","; 55 | metadataFile << durations.size() << ","; 56 | 57 | // Sanitize callback_name for filename 58 | std::string sanitized_callback_name = callback_name; 59 | // Replace any invalid filename characters 60 | for (auto &ch : sanitized_callback_name) { 61 | if (!isalnum(ch) && ch != '-' && ch != '_') { 62 | ch = '_'; 63 | } 64 | } 65 | 66 | /* creating the filename */ 67 | char filename[4096]; 68 | snprintf(filename, sizeof(filename), "callback_%" PRIu64 "_%s.csv", callback_address, procname.c_str()); 69 | metadataFile << filename << ","; 70 | 71 | /* opens the file for writing */ 72 | FILE *file = fopen(filename, "w"); 73 | if (file == nullptr) { 74 | fprintf(stderr, "Failed to open file %s for writing\n", filename); 75 | continue; 76 | } 77 | 78 | // Write metadata: callback name on top 79 | fprintf(file, "%s\n", callback_name.c_str()); 80 | fprintf(file, "%s\n", procname.c_str()); 81 | 82 | /* writes the column data */ 83 | fprintf(file, "time,duration\n"); 84 | 85 | /* writes the file data */ 86 | double total = 0.0; 87 | for (const auto &pair : durations) { 88 | uint64_t time = pair.first; 89 | uint64_t duration = pair.second; 90 | total += duration / 1000000000.0 / durations.size(); 91 | 92 | fprintf(file, "%" PRIu64 ",%" PRIu64 "\n", time, duration); 93 | } 94 | 95 | metadataFile << total << std::endl; 96 | 97 | fclose(file); 98 | } 99 | 100 | std::cout << "Wrote output to metadata.csv..." << std::endl; 101 | 102 | /* Free the allocated structure */ 103 | delete callback_duration; 104 | } 105 | 106 | bt_component_class_sink_graph_is_configured_method_status callback_duration_graph_is_configured( 107 | bt_self_component_sink *self_component_sink) { 108 | /* Retrieve our private data from the component's user data */ 109 | auto *callback_duration = (struct CallbackDuration *)bt_self_component_get_data( 110 | bt_self_component_sink_as_self_component(self_component_sink)); 111 | 112 | /* Borrow our unique port */ 113 | bt_self_component_port_input *in_port = bt_self_component_sink_borrow_input_port_by_index(self_component_sink, 0); 114 | 115 | /* Create the upstream message iterator */ 116 | bt_message_iterator_create_from_sink_component(self_component_sink, in_port, &callback_duration->message_iterator); 117 | 118 | return BT_COMPONENT_CLASS_SINK_GRAPH_IS_CONFIGURED_METHOD_STATUS_OK; 119 | } 120 | 121 | void callback_duration_calculator(struct CallbackDuration *callback_duration, const bt_message *message) { 122 | /* Discard if it's not an event message */ 123 | if (bt_message_get_type(message) != BT_MESSAGE_TYPE_EVENT) { 124 | return; 125 | } 126 | 127 | /* Increment the current event message's index */ 128 | callback_duration->events_index++; 129 | 130 | /* Borrow the event message's event and its class */ 131 | const bt_event *event = bt_message_event_borrow_event_const(message); 132 | const bt_event_class *event_class = bt_event_borrow_class_const(event); 133 | const char *event_name = bt_event_class_get_name(event_class); 134 | 135 | /* Check for `ros2:callbackRegistered` event */ 136 | if (strcmp(event_name, "ros2:rclcpp_callback_register") == 0) { 137 | /* Simulate retrieving the callback address (for demonstration) */ 138 | 139 | // we borrow the payload and use the bt_field_structure_borrow_member_field_by_name_const 140 | const bt_field *payload = bt_event_borrow_payload_field_const(event); 141 | const bt_field *event_common_context = bt_event_borrow_common_context_field_const(event); 142 | const bt_field *callback_address_field = bt_field_structure_borrow_member_field_by_name_const(payload, "callback"); 143 | uint64_t callback_address_value = bt_field_integer_unsigned_get_value(callback_address_field); 144 | const bt_field *callback_symbol_field = bt_field_structure_borrow_member_field_by_name_const(payload, "symbol"); 145 | const char *symbol_value = callback_symbol_field != nullptr ? bt_field_string_get_value(callback_symbol_field) : ""; 146 | const bt_field *procname_field = 147 | bt_field_structure_borrow_member_field_by_name_const(event_common_context, "procname"); 148 | const char *procname_value = procname_field != nullptr ? bt_field_string_get_value(procname_field) : ""; 149 | 150 | /* Create CallbackInfo and add it to the callback map */ 151 | CallbackInfo callback_info; 152 | callback_info.symbol = symbol_value; 153 | callback_info.procname = procname_value; 154 | 155 | callback_duration->callback_map[callback_address_value] = callback_info; 156 | 157 | /* Log the callback registration */ 158 | printf("callback registered(#%" PRIu64 "): %s \n", callback_duration->events_index, procname_value); 159 | } else if (strcmp(event_name, "ros2:callback_start") == 0) { 160 | /* log that a callback has been started and the time in nanoseconds that that happens */ 161 | const bt_field *payload = bt_event_borrow_payload_field_const(event); 162 | const bt_field *callback_address_field = bt_field_structure_borrow_member_field_by_name_const(payload, "callback"); 163 | if (callback_address_field == nullptr) { 164 | return; 165 | } 166 | uint64_t callback_address_value = bt_field_integer_unsigned_get_value(callback_address_field); 167 | 168 | const bt_clock_snapshot *clock_snapshot = bt_message_event_borrow_default_clock_snapshot_const(message); 169 | 170 | int64_t ns_from_origin; 171 | bt_clock_snapshot_get_ns_from_origin(clock_snapshot, &ns_from_origin); 172 | 173 | callback_duration->callback_last_called[callback_address_value] = ns_from_origin; 174 | } else if (strcmp(event_name, "ros2:callback_end") == 0) { 175 | /* log that a callback has been ended */ 176 | const bt_field *payload = bt_event_borrow_payload_field_const(event); 177 | const bt_field *callback_address_field = bt_field_structure_borrow_member_field_by_name_const(payload, "callback"); 178 | uint64_t callback_address_value = bt_field_integer_unsigned_get_value(callback_address_field); 179 | 180 | const bt_clock_snapshot *clock_snapshot = bt_message_event_borrow_default_clock_snapshot_const(message); 181 | 182 | int64_t ns_from_origin; 183 | bt_clock_snapshot_get_ns_from_origin(clock_snapshot, &ns_from_origin); 184 | 185 | // logging the duration 186 | int64_t last_called = callback_duration->callback_last_called[callback_address_value]; 187 | callback_duration->callback_durations[callback_address_value].emplace_back(last_called, 188 | ns_from_origin - last_called); 189 | 190 | if (last_called > ns_from_origin) { 191 | std::cerr << "Message ordering error" << std::endl; 192 | } 193 | } 194 | } 195 | 196 | bt_component_class_sink_consume_method_status callback_durations_consume(bt_self_component_sink *self_component_sink) { 197 | bt_component_class_sink_consume_method_status status = BT_COMPONENT_CLASS_SINK_CONSUME_METHOD_STATUS_OK; 198 | 199 | /* Retrieve our private data from the component's user data */ 200 | auto *callback_duration = (struct CallbackDuration *)bt_self_component_get_data( 201 | bt_self_component_sink_as_self_component(self_component_sink)); 202 | 203 | /* Consume a batch of messages from the upstream message iterator */ 204 | bt_message_array_const messages; 205 | uint64_t message_count; 206 | bt_message_iterator_next_status next_status = 207 | bt_message_iterator_next(callback_duration->message_iterator, &messages, &message_count); 208 | 209 | if (next_status == BT_MESSAGE_ITERATOR_NEXT_STATUS_END) { 210 | /* End of iteration: put the message iterator's reference */ 211 | bt_message_iterator_put_ref(callback_duration->message_iterator); 212 | return BT_COMPONENT_CLASS_SINK_CONSUME_METHOD_STATUS_END; 213 | } else if (next_status == BT_MESSAGE_ITERATOR_NEXT_STATUS_AGAIN) { 214 | return BT_COMPONENT_CLASS_SINK_CONSUME_METHOD_STATUS_AGAIN; 215 | } else if (next_status == BT_MESSAGE_ITERATOR_NEXT_STATUS_MEMORY_ERROR) { 216 | return BT_COMPONENT_CLASS_SINK_CONSUME_METHOD_STATUS_MEMORY_ERROR; 217 | } else if (next_status == BT_MESSAGE_ITERATOR_NEXT_STATUS_ERROR) { 218 | return BT_COMPONENT_CLASS_SINK_CONSUME_METHOD_STATUS_ERROR; 219 | } 220 | 221 | /* For each consumed message */ 222 | for (uint64_t i = 0; i < message_count; i++) { 223 | /* Current message */ 224 | const bt_message *message = messages[i]; 225 | 226 | /* Print line for current message if it's an event message */ 227 | callback_duration_calculator(callback_duration, message); 228 | 229 | /* Put this message's reference */ 230 | bt_message_put_ref(message); 231 | } 232 | 233 | return status; 234 | } 235 | -------------------------------------------------------------------------------- /src/callback_duration.hpp: -------------------------------------------------------------------------------- 1 | /// Callback duration calculator 2 | /// 3 | /// Uses three lttng tracepoints 4 | /// - ros2:rclcpp_callback_register to associate a symbol with a callback address 5 | /// - ros2:callback_start to write to an unordered map that the callback at that memory address was called 6 | /// - ros2:callback_end to log the ending (and therefore the duration of) the callback 7 | 8 | #pragma once 9 | 10 | #include 11 | 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | 24 | extern "C" { 25 | #include 26 | } 27 | 28 | /// Structure to hold callback metadata 29 | struct CallbackInfo { 30 | /// The full path to the symbol registered 31 | std::string symbol; 32 | /// Name of the registering node 33 | std::string procname; 34 | }; 35 | 36 | /// Sink component's private data 37 | struct CallbackDuration { 38 | /// Message iterator used by every babeltrace program 39 | bt_message_iterator *message_iterator; 40 | 41 | /// Index of the events trigger 42 | uint64_t events_index; 43 | 44 | /// Maps a callback address to some metadata about the callback 45 | std::unordered_map callback_map; 46 | 47 | /// Unordered map which maps a callback address to the nearest starting callback 48 | /// 49 | /// The value of the unordered map is the time in nanoseconds since the UNIX epoch 50 | std::unordered_map callback_last_called; 51 | 52 | /// Unordered map which maps a callback address to a list of callbacks and their duration 53 | std::unordered_map>> callback_durations; 54 | }; 55 | 56 | /// Creates a final consumer of trace information which collects the callback durations 57 | bt_component_class_initialize_method_status callback_duration_initialize( 58 | bt_self_component_sink *self_component_sink, bt_self_component_sink_configuration * /*configuration*/, 59 | const bt_value * /*params*/, void * /*initialize_method_data*/); 60 | 61 | /// Called when the sink has processed all the traces 62 | void callback_duration_finalize(bt_self_component_sink *self_component_sink); 63 | 64 | /// Called when the trace processing graph containing the sink component is configured. 65 | bt_component_class_sink_graph_is_configured_method_status callback_duration_graph_is_configured( 66 | bt_self_component_sink *self_component_sink); 67 | 68 | /// Uses the registration of callbacks to capture the symbol 69 | /// 70 | /// Then uses a standard map to get the time each callback was called and log the callback being created 71 | void callback_duration_calculator(struct CallbackDuration *callback_duration, const bt_message *message); 72 | 73 | /// Consumes a batch of messages to calculate the ros2 callback durations 74 | bt_component_class_sink_consume_method_status callback_durations_consume(bt_self_component_sink *self_component_sink); -------------------------------------------------------------------------------- /src/memory_usage.cpp: -------------------------------------------------------------------------------- 1 | #include "memory_usage.hpp" 2 | 3 | bt_component_class_initialize_method_status memory_usage_initialize( 4 | bt_self_component_sink *self_component_sink, bt_self_component_sink_configuration * /*configuration*/, 5 | const bt_value * /*params*/, void * /*initialize_method_data*/) { 6 | auto *memory_usage = new MemoryUsage(); 7 | 8 | /* Initialize the first event message's index */ 9 | memory_usage->events_index = 1; 10 | memory_usage->addr_to_alllocations_idx.reserve(DEFAULT_SIZE); 11 | memory_usage->addr_to_alllocations_idx.max_load_factor(0.3); 12 | memory_usage->allocations.reserve(DEFAULT_SIZE); 13 | 14 | // TODO: think about passing data to my configuration with params 15 | // see: https://chatgpt.com/c/6726ae8a-5e00-8008-b4d7-5fbd480f8964 16 | 17 | /* Set the component's user data to our private data structure */ 18 | bt_self_component_set_data(bt_self_component_sink_as_self_component(self_component_sink), memory_usage); 19 | 20 | /* Add an input port named `in` to the sink component */ 21 | bt_self_component_sink_add_input_port(self_component_sink, "in", nullptr, nullptr); 22 | 23 | return BT_COMPONENT_CLASS_INITIALIZE_METHOD_STATUS_OK; 24 | } 25 | 26 | void memory_usage_finalize(bt_self_component_sink *self_component_sink) { 27 | /* Retrieve our private data from the component's user data */ 28 | auto *memory_usage = 29 | (struct MemoryUsage *)bt_self_component_get_data(bt_self_component_sink_as_self_component(self_component_sink)); 30 | 31 | /* Iterate through writing allocations */ 32 | for (size_t idx = 0; idx < memory_usage->allocations.size(); idx++) { 33 | auto &alloc_info = memory_usage->allocations[idx]; 34 | auto &node_info = memory_usage->vpid_to_node[alloc_info.vpid]; 35 | auto &output_file = node_info.output_file; 36 | 37 | if (!output_file.is_open()) { 38 | output_file.open(node_info_make_filename(node_info)); 39 | 40 | output_file << "type,time,duration,prev_size,size,vtid" << std::endl; 41 | } 42 | 43 | /* writing allocation type */ 44 | switch (alloc_info.type) { 45 | case AllocationType::MALLOC: 46 | output_file << "malloc,"; 47 | break; 48 | case AllocationType::CALLOC: 49 | output_file << "calloc,"; 50 | break; 51 | case AllocationType::REALLOC: 52 | output_file << "realloc,"; 53 | break; 54 | case AllocationType::MEMALIGN: 55 | output_file << "memalign,"; 56 | break; 57 | case AllocationType::POSIX_MEMALIGN: 58 | output_file << "posix_memalign,"; 59 | break; 60 | default: 61 | std::cerr << "Allocation type not known" << std::endl; 62 | } 63 | 64 | /* writing the time */ 65 | output_file << alloc_info.time << ","; 66 | 67 | /* writing the duration */ 68 | output_file << alloc_info.duration << ","; 69 | 70 | /* writing the previous size */ 71 | if (alloc_info.type == AllocationType::REALLOC) { 72 | output_file << alloc_info.prev_size; 73 | } 74 | 75 | output_file << ","; 76 | 77 | /* writing the allocation size */ 78 | output_file << alloc_info.size << ","; 79 | 80 | /* writing the thread id */ 81 | output_file << alloc_info.vtid; 82 | 83 | /* writing a new line (noteable not std::endl) so the buffer is not flushed each time for performance reasons */ 84 | output_file << "\n"; 85 | } 86 | 87 | /* Going through to close each file */ 88 | for (auto &vpid_and_node_info : memory_usage->vpid_to_node) { 89 | auto &output_file = vpid_and_node_info.second.output_file; 90 | if (output_file.is_open()) { 91 | output_file.close(); 92 | } 93 | } 94 | 95 | /* Creating a metadata page */ 96 | std::ofstream metadata("memory_usage_metadata.csv"); 97 | metadata << "path,vpid,procname,avg_alloc_lifecycle,avg_alloc_size,allocation_count,max_memory" << std::endl; 98 | 99 | for (auto &vpid_and_node_info : memory_usage->vpid_to_node) { 100 | auto &node_info = vpid_and_node_info.second; 101 | 102 | /* filename */ 103 | metadata << node_info_make_filename(node_info) << ","; 104 | 105 | /* virtual pid */ 106 | metadata << node_info.vpid << ","; 107 | 108 | /* procname */ 109 | metadata << node_info.procname << ","; 110 | 111 | /* summary statistics */ 112 | uint64_t count = node_info.count; 113 | 114 | uint64_t avg_alloc_size = node_info.total_size / count; 115 | uint64_t avg_alloc_lifecycle = node_info.total_duration / count; 116 | 117 | metadata << avg_alloc_lifecycle << ","; 118 | metadata << avg_alloc_size << ","; 119 | metadata << count << ","; 120 | 121 | /* maximum process memory */ 122 | metadata << node_info.max_memory; 123 | 124 | metadata << std::endl; 125 | } 126 | 127 | delete memory_usage; 128 | } 129 | 130 | bt_component_class_sink_graph_is_configured_method_status memory_usage_graph_is_configured( 131 | bt_self_component_sink *self_component_sink) { 132 | /* Retrieve our private data from the component's user data */ 133 | auto *memory_usage = 134 | (struct MemoryUsage *)bt_self_component_get_data(bt_self_component_sink_as_self_component(self_component_sink)); 135 | 136 | /* Borrow our unique port */ 137 | bt_self_component_port_input *in_port = bt_self_component_sink_borrow_input_port_by_index(self_component_sink, 0); 138 | 139 | /* Create the upstream message iterator */ 140 | bt_message_iterator_create_from_sink_component(self_component_sink, in_port, &memory_usage->message_iterator); 141 | 142 | return BT_COMPONENT_CLASS_SINK_GRAPH_IS_CONFIGURED_METHOD_STATUS_OK; 143 | } 144 | 145 | void memory_usage_calculator(struct MemoryUsage *memory_usage, const bt_message *message) { 146 | /* Discard if it's not an event message */ 147 | if (bt_message_get_type(message) != BT_MESSAGE_TYPE_EVENT) { 148 | return; 149 | } 150 | 151 | /* Increment the current event message's index */ 152 | memory_usage->events_index++; 153 | if (memory_usage->events_index % PRINT_FREQ == 0) { 154 | std::cerr << "Processed " << memory_usage->events_index / 1000000 << " million events" << std::endl; 155 | } 156 | 157 | /* Borrow the event message's event and its class */ 158 | const bt_event *event = bt_message_event_borrow_event_const(message); 159 | const bt_event_class *event_class = bt_event_borrow_class_const(event); 160 | const char *event_name = bt_event_class_get_name(event_class); 161 | 162 | /* Getting the timestamp of the event */ 163 | const bt_clock_snapshot *clock_snapshot = bt_message_event_borrow_default_clock_snapshot_const(message); 164 | int64_t timestamp_ns; 165 | if (clock_snapshot != nullptr) { 166 | bt_clock_snapshot_get_ns_from_origin(clock_snapshot, ×tamp_ns); 167 | } else { 168 | std::cerr << "Failed to get the clock for a message" << std::endl; 169 | return; 170 | } 171 | 172 | memory_usage->max_timestamp = std::max(memory_usage->max_timestamp, timestamp_ns); 173 | 174 | /* Looking at memory allocations */ 175 | AllocationType alloc_type; 176 | bool is_allocation = true; 177 | 178 | if (strcmp(event_name, "lttng_ust_libc:malloc") == 0) { 179 | alloc_type = AllocationType::MALLOC; 180 | } else if (strcmp(event_name, "lttng_ust_libc:calloc") == 0) { 181 | alloc_type = AllocationType::CALLOC; 182 | } else if (strcmp(event_name, "lttng_ust_libc:realloc") == 0) { 183 | alloc_type = AllocationType::REALLOC; 184 | } else if (strcmp(event_name, "lttng_ust_libc:memalign") == 0) { 185 | alloc_type = AllocationType::MEMALIGN; 186 | } else if (strcmp(event_name, "lttng_ust_libc:posix_memalign") == 0) { 187 | alloc_type = AllocationType::POSIX_MEMALIGN; 188 | } else { 189 | is_allocation = false; 190 | } 191 | 192 | if (is_allocation) { 193 | const bt_field *payload = bt_event_borrow_payload_field_const(event); 194 | const bt_field *event_common_context = bt_event_borrow_common_context_field_const(event); 195 | 196 | /* Fields extraction */ 197 | const bt_field *pointer_address_field = bt_field_structure_borrow_member_field_by_name_const(payload, "ptr"); 198 | const Address pointer_address_value = bt_field_integer_unsigned_get_value(pointer_address_field); 199 | const bt_field *vtid_field = bt_field_structure_borrow_member_field_by_name_const(event_common_context, "vtid"); 200 | const VID vtid = bt_field_integer_signed_get_value(vtid_field); 201 | const bt_field *vpid_field = bt_field_structure_borrow_member_field_by_name_const(event_common_context, "vpid"); 202 | const VID vpid = bt_field_integer_signed_get_value(vpid_field); 203 | const bt_field *size_field = bt_field_structure_borrow_member_field_by_name_const(payload, "size"); 204 | const Address allocation_size = bt_field_integer_unsigned_get_value(size_field); 205 | 206 | if (memory_usage->vpid_to_node.find(vpid) == memory_usage->vpid_to_node.end()) { 207 | auto &node_info = memory_usage->vpid_to_node[vpid]; 208 | 209 | const bt_field *procname_field = 210 | bt_field_structure_borrow_member_field_by_name_const(event_common_context, "procname"); 211 | const char *procname_value = procname_field != nullptr ? bt_field_string_get_value(procname_field) : ""; 212 | 213 | node_info.procname = std::string(procname_value); 214 | node_info.vpid = vpid; 215 | } 216 | 217 | /* Checks whether a previous value was contained in the array in cases where a realloc is present */ 218 | bool should_log_reallocation = 219 | alloc_type == AllocationType::REALLOC && memory_usage->addr_to_alllocations_idx.find(pointer_address_value) != 220 | memory_usage->addr_to_alllocations_idx.end(); 221 | 222 | /* Special reallocation case */ 223 | if (should_log_reallocation) { 224 | const auto realloc_idx = memory_usage->addr_to_alllocations_idx[pointer_address_value]; 225 | auto &alloc_info = memory_usage->allocations[realloc_idx]; 226 | 227 | alloc_info.duration = timestamp_ns - alloc_info.time; 228 | } 229 | 230 | /* Creating the allocation information object */ 231 | AllocationInfo alloc_info; 232 | 233 | alloc_info.vtid = vtid; 234 | alloc_info.vpid = vpid; 235 | alloc_info.time = timestamp_ns; 236 | alloc_info.duration = 0; // 0 means it was never freed 237 | alloc_info.size = allocation_size; 238 | alloc_info.prev_size = 0; 239 | alloc_info.type = alloc_type; 240 | 241 | /* Updating summary statistics */ 242 | auto &node_info = memory_usage->vpid_to_node[vpid]; 243 | node_info.count++; 244 | 245 | const uint64_t diff_size = allocation_info_diff_size(alloc_info); 246 | node_info.total_size += diff_size; 247 | node_info.curr_memory += diff_size; 248 | node_info.max_memory = std::max(node_info.max_memory, node_info.curr_memory); 249 | 250 | memory_usage->addr_to_alllocations_idx[pointer_address_value] = memory_usage->allocations.size(); 251 | memory_usage->allocations.push_back(alloc_info); 252 | 253 | return; 254 | } 255 | 256 | /* Handling memory free events */ 257 | if (strcmp(event_name, "lttng_ust_libc:free") == 0) { 258 | const bt_field *payload = bt_event_borrow_payload_field_const(event); 259 | const bt_field *event_common_context = bt_event_borrow_common_context_field_const(event); 260 | 261 | /* Fields extraction */ 262 | const bt_field *vpid_field = bt_field_structure_borrow_member_field_by_name_const(event_common_context, "vpid"); 263 | const VID vpid = bt_field_integer_signed_get_value(vpid_field); 264 | const bt_field *pointer_address_field = bt_field_structure_borrow_member_field_by_name_const(payload, "ptr"); 265 | const Address pointer_address_value = bt_field_integer_unsigned_get_value(pointer_address_field); 266 | 267 | /* Get the past allocation information and set the duration */ 268 | auto allocation_idx = memory_usage->addr_to_alllocations_idx[pointer_address_value]; 269 | if (allocation_idx >= memory_usage->allocations.size()) { 270 | return; 271 | } 272 | 273 | auto &allocation_info = memory_usage->allocations[allocation_idx]; 274 | auto &node_info = memory_usage->vpid_to_node[vpid]; 275 | node_info.curr_memory -= allocation_info.size; 276 | 277 | allocation_info.duration = timestamp_ns - allocation_info.time; 278 | node_info.total_duration += allocation_info.duration; 279 | memory_usage->addr_to_alllocations_idx.erase(pointer_address_value); 280 | } 281 | } 282 | 283 | bt_component_class_sink_consume_method_status memory_usage_consume(bt_self_component_sink *self_component_sink) { 284 | bt_component_class_sink_consume_method_status status = BT_COMPONENT_CLASS_SINK_CONSUME_METHOD_STATUS_OK; 285 | 286 | /* Retrieve our private data from the component's user data */ 287 | auto *memory_usage = 288 | (struct MemoryUsage *)bt_self_component_get_data(bt_self_component_sink_as_self_component(self_component_sink)); 289 | 290 | /* Consume a batch of messages from the upstream message iterator */ 291 | bt_message_array_const messages; 292 | Address message_count; 293 | bt_message_iterator_next_status next_status = 294 | bt_message_iterator_next(memory_usage->message_iterator, &messages, &message_count); 295 | 296 | if (next_status == BT_MESSAGE_ITERATOR_NEXT_STATUS_END) { 297 | /* Decrements the reference counter of the message (effectively a free) */ 298 | bt_message_iterator_put_ref(memory_usage->message_iterator); 299 | return BT_COMPONENT_CLASS_SINK_CONSUME_METHOD_STATUS_END; 300 | } 301 | 302 | if (next_status == BT_MESSAGE_ITERATOR_NEXT_STATUS_AGAIN) { 303 | return BT_COMPONENT_CLASS_SINK_CONSUME_METHOD_STATUS_AGAIN; 304 | } 305 | 306 | if (next_status == BT_MESSAGE_ITERATOR_NEXT_STATUS_MEMORY_ERROR) { 307 | return BT_COMPONENT_CLASS_SINK_CONSUME_METHOD_STATUS_MEMORY_ERROR; 308 | } 309 | 310 | if (next_status == BT_MESSAGE_ITERATOR_NEXT_STATUS_ERROR) { 311 | return BT_COMPONENT_CLASS_SINK_CONSUME_METHOD_STATUS_ERROR; 312 | } 313 | 314 | /* For each consumed message */ 315 | for (Address i = 0; i < message_count; i++) { 316 | /* Current message */ 317 | const bt_message *message = messages[i]; 318 | 319 | /* Print line for current message if it's an event message */ 320 | memory_usage_calculator(memory_usage, message); 321 | 322 | /* Decrements the reference counter of the message (effectively a free) */ 323 | bt_message_put_ref(message); 324 | } 325 | 326 | return status; 327 | } -------------------------------------------------------------------------------- /src/memory_usage.hpp: -------------------------------------------------------------------------------- 1 | /// Memory usage calculator 2 | /// 3 | /// Logs allocation and deallocations and the life of each. Does not really care about ros2 nodes because I wanted to 4 | /// avoid building ros2 from scratch 5 | /// 6 | /// Uses six lttng tracepoints 7 | /// - lttng_ust_libc:malloc 8 | /// - lttng_ust_libc:calloc 9 | /// - lttng_ust_libc:realloc 10 | /// - lttng_ust_libc:memalign 11 | /// - lttng_ust_libc:posix_memalign 12 | /// - lttng_ust_libc:free 13 | 14 | #pragma once 15 | 16 | #include 17 | 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | 31 | extern "C" { 32 | #include 33 | } 34 | 35 | /// Change the address size if you are running on a non-64 bit architecture 36 | using Address = uint64_t; 37 | 38 | /// Types of vpid and vtid 39 | using VID = int64_t; 40 | 41 | /// Print frequency to tell how many events are processed 42 | constexpr const size_t PRINT_FREQ = 1000000; 43 | constexpr const size_t DEFAULT_SIZE = 10000; 44 | 45 | /// The types of memory allocation (each have different overhead) 46 | enum AllocationType { 47 | MALLOC, 48 | CALLOC, 49 | REALLOC, 50 | MEMALIGN, 51 | POSIX_MEMALIGN, 52 | }; 53 | 54 | /// Keeps track of metadata about allocations 55 | /// 56 | /// Required because different allocation types have differing overhead 57 | struct AllocationInfo { 58 | /// The time the memory was allocated 59 | int64_t time; 60 | 61 | /// The virtual pid of the process allocating memory 62 | VID vpid; 63 | 64 | /// The virtual thread id of process allocating memory 65 | VID vtid; 66 | 67 | /// The length the allocation lasted (start time minus end_time) 68 | int64_t duration; 69 | 70 | // The size of the memory allocation 71 | size_t size; 72 | 73 | // The size of the previous allocation (if realloc was used) 74 | size_t prev_size; 75 | 76 | /// The type of allocation 77 | AllocationType type; 78 | }; 79 | 80 | /// Aggregator of summary statistics per ros2 node 81 | struct NodeInfo { 82 | /// vpid of the node 83 | VID vpid; 84 | 85 | /// Current heap-allocated memory usage 86 | int64_t curr_memory; 87 | 88 | /// Maximum heap-allocated memory usage 89 | int64_t max_memory; 90 | 91 | /// Name of the process 92 | std::string procname; 93 | 94 | /// Total number of allocations 95 | size_t count; 96 | 97 | /// Total size of allocations 98 | size_t total_size; 99 | 100 | /// Total duration of allocations 101 | size_t total_duration; 102 | 103 | /// Allocation summary output file 104 | std::ofstream output_file; 105 | }; 106 | 107 | /// Makes a filename from a NodeInfo 108 | inline std::string node_info_make_filename(const NodeInfo &node) { return "mem_" + node.procname + ".csv"; } 109 | 110 | /// Computes the size difference from an allocation 111 | inline uint64_t allocation_info_diff_size(const AllocationInfo &alloc_info) { 112 | if (alloc_info.type == AllocationType::REALLOC) { 113 | return alloc_info.size - alloc_info.prev_size; 114 | } 115 | 116 | return alloc_info.size; 117 | } 118 | 119 | /// Memory usage sink's private data 120 | struct MemoryUsage { 121 | /// Message iterator used by every babeltrace program 122 | bt_message_iterator *message_iterator; 123 | 124 | /// The maximum timestamp an allocation occurred, used to calculate the lifecycle of objects which were not freed 125 | int64_t max_timestamp; 126 | 127 | /// Events count 128 | uint64_t events_index; 129 | 130 | /// Previous message time 131 | int64_t prev_msg; 132 | 133 | /// Current memory usage measured in megabytes 134 | uint64_t memory_usage; 135 | 136 | /// VPID to node_info which is called once 137 | std::unordered_map vpid_to_node; 138 | 139 | /// Memory address to vpid used to keep track of allocation 140 | std::unordered_map addr_to_alllocations_idx; 141 | 142 | /// Final allocation information logged by pid 143 | /// NOTE: now that I am thinking about it having such a huge vector may not be the best idea, will look at later 144 | std::vector allocations; 145 | }; 146 | 147 | /// Initializes the memory usage sink 148 | bt_component_class_initialize_method_status memory_usage_initialize( 149 | bt_self_component_sink *self_component_sink, bt_self_component_sink_configuration * /*configuration*/, 150 | const bt_value * /*params*/, void * /*initialize_method_data*/); 151 | 152 | /// Finalize is called when the message stream ends and this leads to the data being written to a file 153 | void memory_usage_finalize(bt_self_component_sink *self_component_sink); 154 | 155 | /// Called when the trace processing graph containing the sink component 156 | /// is configured. 157 | bt_component_class_sink_graph_is_configured_method_status memory_usage_graph_is_configured( 158 | bt_self_component_sink *self_component_sink); 159 | 160 | /// Computes memory usage statistics given an event message 161 | void memory_usage_calculator(struct MemoryUsage *memory_usage, const bt_message *message); 162 | 163 | /// Consumes a "batch" of messages all at once and calculates their memory usage 164 | bt_component_class_sink_consume_method_status memory_usage_consume(bt_self_component_sink *self_component_sink); -------------------------------------------------------------------------------- /src/plugin.cpp: -------------------------------------------------------------------------------- 1 | #include "callback_duration.hpp" 2 | #include "memory_usage.hpp" 3 | 4 | /* Mandatory */ 5 | BT_PLUGIN_MODULE(); 6 | 7 | /* Define the `trace_analysis` plugin */ 8 | BT_PLUGIN(ros2_tracing_cpp); 9 | 10 | /* Author Information */ 11 | BT_PLUGIN_DESCRIPTION("ROS2 trace processing sinks"); 12 | BT_PLUGIN_AUTHOR("William Kaiser"); 13 | BT_PLUGIN_LICENSE("CC BY-NC 4.0."); 14 | 15 | /* Define the `memory_usage` sink component class */ 16 | BT_PLUGIN_SINK_COMPONENT_CLASS(memory_usage, memory_usage_consume); 17 | BT_PLUGIN_SINK_COMPONENT_CLASS_DESCRIPTION(memory_usage, "Computes the heap memory usage of ROS 2 nodes"); 18 | 19 | /* Set some of the `memory_usage` sink component class's optional methods */ 20 | BT_PLUGIN_SINK_COMPONENT_CLASS_INITIALIZE_METHOD(memory_usage, memory_usage_initialize); 21 | BT_PLUGIN_SINK_COMPONENT_CLASS_FINALIZE_METHOD(memory_usage, memory_usage_finalize); 22 | BT_PLUGIN_SINK_COMPONENT_CLASS_GRAPH_IS_CONFIGURED_METHOD(memory_usage, memory_usage_graph_is_configured); 23 | 24 | /* Define the `callback_duration` sink component class */ 25 | BT_PLUGIN_SINK_COMPONENT_CLASS(callback_duration, callback_durations_consume); 26 | BT_PLUGIN_SINK_COMPONENT_CLASS_DESCRIPTION(callback_duration, "Computes the callback durations of ROS 2 nodes"); 27 | 28 | /* Set some of the `output` sink component class's optional methods */ 29 | BT_PLUGIN_SINK_COMPONENT_CLASS_INITIALIZE_METHOD(callback_duration, callback_duration_initialize); 30 | BT_PLUGIN_SINK_COMPONENT_CLASS_FINALIZE_METHOD(callback_duration, callback_duration_finalize); 31 | BT_PLUGIN_SINK_COMPONENT_CLASS_GRAPH_IS_CONFIGURED_METHOD(callback_duration, callback_duration_graph_is_configured); --------------------------------------------------------------------------------