Merge branch 'tracetools' into 'master'

See merge request micro-ROS/ros2_tracing!1

Signed-off-by: Ingo Luetkebohle <ingo.luetkebohle@de.bosch.com>
This commit is contained in:
Ingo Lütkebohle 2019-06-18 13:11:17 +00:00
commit cc3c142f9f
37 changed files with 2981 additions and 0 deletions

32
.gitlab-ci.yml Normal file
View file

@ -0,0 +1,32 @@
image: ros:dashing-ros-base
variables:
DOCKER_DRIVER: overlay2
before_script:
- apt update
- rosdep update
- rosdep install -y --from-paths . -i .
- ln -s src
build:
script:
- colcon build
- colcon test
artifacts:
paths:
- install
- build
reports:
junit: build/**/Test.xml
build_enabled:
script:
- colcon build --cmake-args -DWITH_LTTNG=ON
- colcon test
artifacts:
paths:
- install
- build
reports:
junit: build/**/Test.xml

202
LICENSE Normal file
View 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.

518
doc/design_ros_2.md Normal file
View file

@ -0,0 +1,518 @@
# ROS 2 tracing
Design document for ROS 2 tracing, instrumentation, and analysis effort.
## Introduction
Tracing allows to record run-time data from a system, both for system data (e.g., when a process
is being scheduled, or when I/O occurs) and for user-defined data. This package helps with
user-defined trace data within the ROS2 framework, e.g. to trace when messages arrive,
when timers fire, when callbacks are being run, etc.
## Goals and requirements
### Goals
1. Provide low-overhead tools and resources for robotics software development based on ROS 2.
2. Make tracing easier to use with ROS.
### Requirements: instrumentation
Instrumentation should be built around the main uses of ROS 2, and should include relevant information:
1. Overall
1. When creating a publisher/subscriber/service/client/etc., appropriate references should be kept in order to correlate with other tracepoints related to the same instance.
1. Publishers & subscriptions
1. When creating a publisher/subscription, the effective topic name should be included (i.e. including namespace and after remapping).
2. When publishing a message, some sort of message identifier should be included in the tracepoint so it can be tracked through DDS up to the subscriber's side.
3. Callbacks (subscription, service, client, timer)
1. Callback function symbol should be included, whenever possible.
2. Information about callback execution (e.g. start & end) should be available.
4. Timers
1. Information about the period should be available.
5. Executors
1. Information about spin cycles & periods should be available.
6. Others
1. Provide generic tracepoints for user code.
### Requirements: analysis & visualization
Analyses process trace data. They should be general enough to be useful for different use-cases, e.g.:
* Callback duration
* Time between callbacks (between two callback starts and/or a callback end and a start)
* Message age (as the difference between processing time and message timestamp)
* Message size
* Memory usage
* Execution time/proportion accross a process' nodes/components
* Interruptions (noting that these may be more useful as time-based metrics instead of overall statistics):
* scheduling events during a callback
* delay between the moment a thread becomes ready and when it's actually scheduled
* CPU cycles
with mean, stdev, etc. when applicable.
Generic tracepoints for ROS 2 user code could be applied to a user-provided model for higher-level behaviour statistics and visualization.
### Tools/accessibility
To make tracing ROS 2 more accessible and easier to adopt, we can put effort into integrating LTTng session setup & recording into the ROS 2 launch system.
This might include converting existing `tracetools` scripts to more flexible Python scripts, and then plugging that into the launch system.
## Instrumentation design
This section includes information about ROS 2's design & architecture through descriptions of the main execution flows. The instrumentation can then be built around that.
### Flow description
#### Process creation
In the call to `rclcpp::init()`, a process-specific `rclcpp::Context` object is fetched and CLI arguments are parsed. Much of the work is actually done by `rcl` through a call to `rcl_init()`. This call processes the `rcl_context_t` handle, which is wrapped by the `Context` object. Also, inside this call, `rcl` calls `rmw_init()` to process the `rmw` context (`rmw_context_t`) as well. This `rmw` handle is itself part of the `rcl_context_t` handle.
This has to be done once per process, and usually at the very beginning. The components that are then instanciated share this context.
```mermaid
sequenceDiagram
participant process
participant rclcpp
participant Context
participant rcl
participant rmw
participant tracetools
Note over rmw: (implementation)
process->>rclcpp: rclcpp::init(argc, argv)
Note over rclcpp: fetches process-specific Context object
rclcpp->>Context: init(argc, argv)
Note over Context: allocates rcl_context_t handle
Context->>rcl: rcl_init(out rcl_context_t)
Note over rcl: validates & processes rcl_context_t handle
rcl->>rmw: rmw_init(out rmw_context_t)
Note over rmw: validates & processes rmw_context_t handle
rcl-->>tracetools: TP(rcl_init, rcl_context_t *)
```
#### Node/component creation
In ROS 2, a process can contain multiple nodes. These are sometimes referred to as "components."
These components are instanciated by the containing process. They are usually classes that extend `rclcpp::Node`, so that the node initialization work is done by the parent constructor.
This parent constructor will allocate its own `rcl_node_t` handle and call `rcl_node_init()`, which will validate the node name/namespace. `rcl` will also call `rmw_create_node()` to get the node's `rmw` handle (`rmw_node_t`). This will be used later by publishers and subscriptions.
```mermaid
sequenceDiagram
participant process
participant Component
participant rclcpp
participant rcl
participant rmw
participant tracetools
Note over rmw: (implementation)
process->>Component: Component()
Component->>rclcpp: : Node(node_name, namespace)
Note over rclcpp: allocates rcl_node_t handle
rclcpp->>rcl: rcl_node_init(out rcl_node_t, node_name, namespace)
Note over rcl: validates node name/namespace
Note over rcl: populates rcl_note_t
rcl->>rmw: rmw_create_node(node_name, local_namespace) : rmw_node_t
Note over rmw: creates rmw_node_t handle
rcl-->>tracetools: TP(rcl_node_init, rcl_node_t *, rmw_node_t *, node_name, namespace)
```
#### Publisher creation
The component calls `create_publisher()`, a `rclcpp::Node` method for convenience. That ends up creating an `rclcpp::Publisher` object which extends `rclcpp::PublisherBase`. The latter allocates an `rcl_publisher_t` handle, fetches the corresponding `rcl_node_t` handle, and calls `rcl_publisher_init()` in its constructor. `rcl` does topic name expansion/remapping/validation. It creates an `rmw_publisher_t` handle by calling `rmw_create_publisher()` of the given `rmw` implementation and associates with the node's `rmw_node_t` handle and the publisher's `rcl_publisher_t` handle.
If intra-process publishing/subscription is enabled, it will be set up after creating the publisher object, through a call to `PublisherBase::setup_intra_process()`, which calls `rcl_publisher_init()`.
```mermaid
sequenceDiagram
participant Component
participant rclcpp
participant Publisher
participant rcl
participant rmw
participant tracetools
Note over rmw: (implementation)
Component->>rclcpp: create_publisher(topic_name, options, use_intra_process)
Note over rclcpp: (...)
rclcpp->>Publisher: Publisher(topic_name, options)
Note over Publisher: allocates rcl_publisher_t handle
Publisher->>rcl: rcl_publisher_init(out rcl_publisher_t, rcl_node_t, topic_name, options)
Note over rcl: populates rcl_publisher_t
rcl->>rmw: rmw_create_publisher(rmw_node_t, topic_name, qos_options) : rmw_publisher_t
Note over rmw: creates rmw_publisher_t handle
rcl-->>tracetools: TP(rcl_publisher_init, rcl_node_t *, rcl_publisher_t *, rmw_publisher_t *, topic_name, depth)
opt use_intra_process
rclcpp->>Publisher: setup_intra_process()
Publisher->>rcl: rcl_publisher_init(...)
end
```
#### Subscription creation
Subscription creation is done in a very similar manner.
The componenent calls `create_publisher()`, which ends up creating an `rclcpp::Subscription` object which extends `rclcpp::SubscriptionBase`. The latter allocates an `rcl_subscription_t` handle, fetches its `rcl_node_t` handle, and calls `rcl_subscription_init()` in its constructor. `rcl` does topic name expansion/remapping/validation. It creates an `rmw_subscription_t` handle by calling `rmw_create_subscription()` of the given `rmw` implementation and associates it with the node's `rmw_node_t` handle and the subscription's `rcl_subscription_t` handle.
If intra-process publishing/subscription is enabled, it will be set up after creating the subscription object, through a call to `Subscription::setup_intra_process()`, which calls `rcl_subscription_init()`. This is very similar to a normal (inter-process) subscription, but it sets some flags for later.
```mermaid
sequenceDiagram
participant Component
participant rclcpp
participant Subscription
participant rcl
participant rmw
participant tracetools
Note over rmw: (implementation)
Component->>rclcpp: create_subscription(topic_name, callback, options, use_intra_process)
Note over rclcpp: (...)
rclcpp->>Subscription: Subscription(topic_name, callback, options)
Note over Subscription: allocates rcl_subscription_t handle
Subscription->>rcl: rcl_subscription_init(out rcl_subscription_t, rcl_node_t, topic_name, options)
Note over rcl: populates rcl_subscription_t
rcl->>rmw: rmw_create_subscription(rmw_node_t, topic_name, qos_options) : rmw_subscription_t
Note over rmw: creates rmw_subscription_t handle
rcl-->>tracetools: TP(rcl_subscription_init, rcl_node_t *, rcl_subscription_t *, rmw_subscription_t *, topic_name, depth)
opt use_intra_process
rclcpp->>Subscription: setup_intra_process()
Subscription->>rcl: rcl_subscription_init(...)
end
rclcpp-->>tracetools: TP(rclcpp_subscription_callback_added, rcl_subscription_t *, &any_callback)
```
#### Executors
An `rclcpp::executor::Executor` object is created for a given process. It can be a `SingleThreadedExecutor` or a `MultiThreadedExecutor`.
Components are instanciated, usually as a `shared_ptr` through `std::make_shared<Component>()`, then added to the executor with `Executor::add_node()`.
After all the components have been added, `Executor::spin()` is called. `SingleThreadedExecutor::spin()` simply loops forever until the process' context isn't valid anymore. It fetches the next `rclcpp::AnyExecutable` (e.g. subscription, timer, service, client), and calls `Executor::execute_any_executable()` with it. This then calls the relevant `execute*()` method (e.g. `execute_timer()`, `execute_subscription()`, `execute_intra_process_subscription()`, `execute_service()`, `execute_client()`).
```mermaid
sequenceDiagram
participant process
participant Executor
participant tracetools
process->>Executor: Executor()
Note over process: instanciates components
process->>Executor: add_node(component)
process->>Executor: spin()
loop until shutdown
Executor-->>tracetools: TP(?)
Note over Executor: get_next_executable()
Note over Executor: execute_any_executable()
Note over Executor: execute_*()
end
```
#### Subscription callbacks
Subscriptions are handled in the `rclcpp` layer. Callbacks are wrapped by an `rclcpp::AnySubscriptionCallback` object, which is registered when creating the `rclcpp::Subscription` object.
In `execute_*subscription()`, the `Executor` asks the `Subscription` to allocate a message though `Subscription::create_message()`. It then calls `rcl_take*()`, which calls `rmw_take_with_info()`. If that is successful, the `Executor` then passes that on to the subscription through `rclcpp::SubscriptionBase::handle_message()`. This checks if it's the right type of subscription (i.e. inter vs. intra process), then it calls `dispatch()` on the `rclcpp::AnySubscriptionCallback` object with the message (cast to the actual type). This calls the actual `std::function` with the right signature.
Finally, it returns the message object through `Subscription::return_message()`.
```mermaid
sequenceDiagram
participant Executor
participant Subscription
participant AnySubscriptionCallback
participant rcl
participant rmw
participant tracetools
Note over rmw: (implementation)
Note over Executor: execute_subscription()
Executor->>Subscription: create_message(): std::shared_ptr<void>
Executor->>rcl: rcl_take*(rcl_subscription_t, out msg) : ret
rcl->>rmw: rmw_take_with_info(rmw_subscription_t, out msg, out taken)
Note over rmw: copies available message to msg if there is one
opt RCL_RET_OK == ret
Executor->>Subscription: handle_message(msg)
Note over Subscription: casts msg to its actual type
Subscription->>AnySubscriptionCallback: dispatch(typed_msg)
AnySubscriptionCallback-->>tracetools: TP(callback_start, this, is_intra_process)
Note over AnySubscriptionCallback: std::function(...)
AnySubscriptionCallback-->>tracetools: TP(callback_end, this)
end
Executor->>Subscription: return_message(msg)
```
#### Message publishing
To publish a message, an object is first allocated and then populated by the `Component` (or equivalent). Then, the message is sent to the `Publisher` through `publish()`. This then passes that on to `rcl`, which itself passes it to `rmw`.
TODO add inter- vs. intra-process execution flow
TODO talk about IntraProcessManager stuff?
```mermaid
sequenceDiagram
participant Component
participant Publisher
participant rcl
participant rmw
participant tracetools
Note over rmw: (implementation)
Note over Component: creates a msg
Component->>Publisher: publish(msg)
Note over Publisher: ...
Publisher->>rcl: rcl_publish(rcl_publisher_t, msg)
rcl->>rmw: rmw_publish(rmw_publisher_t, msg)
rmw-->>tracetools: TP(?)
```
#### Service creation
Service server creation is similar to subscription creation. The `Component` calls `create_service()` which ends up creating a `rclcpp::Service`. In its constructor, it allocates a `rcl_service_t` handle, then calls `rcl_service_init()`. This processes the handle and validates the service name. It calls `rmw_create_service()` to get the corresponding `rmw_service_t` handle.
```mermaid
sequenceDiagram
participant Component
participant rclcpp
participant Service
participant rcl
participant rmw
participant tracetools
Note over rmw: (implementation)
Component->>rclcpp: create_service(service_name, callback)
Note over rclcpp: (...)
rclcpp->>Service: Service(rcl_node_t, service_name, callback, options)
Note over Service: allocates a rcl_service_t handle
Service->>rcl: rcl_service_init(out rcl_service_t, rcl_node_t, service_name, options)
Note over rcl: validates & processes service handle
rcl->>rmw: rmw_create_service(rmw_node_t, service_name, qos_options) : rmw_service_t
Note over rmw: creates rmw_service_t handle
rcl-->>tracetools: TP(rcl_service_init, rcl_node_t *, rcl_service_t *, rmw_service_t *, service_name)
Service-->>tracetools: TP(rclcpp_service_callback_added, rcl_service_t *, &any_callback)
```
#### Service callbacks
Service callbacks are similar to subscription callbacks. In `execute_service()`, the `Executor` allocates request header and request objects. It then calls `rcl_take_request()` and passes them along with the service handle.
`rcl` calls `rmw_take_request()`. If those are successful, then the `Executor` calls `handle_request()` on the `Service`. This casts the request to its actual type, allocates a response object, and calls `dispatch()` on its `AnyServiceCallback` object, which calls the actual `std::function` with the right signature.
For the service response, `Service` calls `rcl_send_response()` which calls `rmw_send_response()`.
```mermaid
sequenceDiagram
participant Executor
participant Service
participant AnyServiceCallback
participant rcl
participant rmw
participant tracetools
Note over rmw: (implementation)
Note over Executor: execute_service()
Note over Executor: allocates request header and request
Executor->>rcl: rcl_take_request(rcl_service, out request_header, out request) : ret
rcl->>rmw: rmw_take_request(rmw_service_t, out request_header, out request, out taken)
opt RCL_RET_OK == ret
Executor->>Service: handle_request(request_header, request)
Note over Service: casts request to its actual type
Note over Service: allocates a response object
Service->>AnyServiceCallback: dispatch(request_header, typed_request, response)
AnyServiceCallback-->>tracetools: TP(callback_start, this)
Note over AnyServiceCallback: std::function(...)
AnyServiceCallback-->>tracetools: TP(callback_end, this)
Service->>rcl: rcl_send_response(rcl_service_t, request_header, response)
rcl->>rmw: rmw_send_response(rmw_service_t, request_header, response)
end
```
#### Client creation
Client creation is similar to publisher creation. The `Component` calls `create_client()` which ends up creating a `rclcpp::Client`. In its constructor, it allocates a `rcl_client_t` handle, then calls `rcl_client_init()`. This validates and processes the handle. It also calls `rmw_create_client()` which creates the `rmw_client_t` handle.
```mermaid
sequenceDiagram
participant Component
participant Node
participant Client
participant rcl
participant rmw
participant tracetools
Note over rmw: (implementation)
Component->>Node: create_client(service_name, options)
Node->>Client: Client(service_name, options)
Note over Client: allocates a rcl_client_t handle
Client->>rcl: rcl_client_init(out rcl_client_t, rcl_node_t, service_name, options)
Note over rcl: validates and processes rcl_client_t handle
rcl->>rmw: rmw_create_client(rmw_node_t, service_name, qos_options) : rmw_client_t
Note over rmw: creates rmw_client_t handle
rcl-->>tracetools: TP(rcl_client_init, rcl_node_t *, rcl_client_t *, rmw_client_t *, service_name)
```
#### Client request
A client request has multiple steps. The `Component` (or the owner of the `Client`) first creates a request object. It then calls `Client::async_send_request()` with the request. It can also provide a callback, but it's optional. The `Client` passes that on to `rcl` by calling `rcl_send_request()`. `rcl` generates a sequence number and assigns it to the request, then calls `rmw_send_request()`. Once this is done, the `Client` puts this sequence number in an internal map along with the created promise and future objects, and the callback (which might simply be empty).
At this point, the `Client` could simply let its callback be called. It can also use the future object returned by `async_send_request()`, and call `rclcpp::spin_until_future_complete()`. This waits until the future object is ready, or until timeout, and returns.
If this last call was successful, then the `Component` can get the result and do something with it.
```mermaid
sequenceDiagram
participant Component
participant Executor
participant Client
participant rclcpp
participant rcl
participant rmw
participant tracetools
Note over rmw: (implementation)
Note over Component: creates request
Component->>Client: async_send_request(request[, callback]) : result_future
Client->>rcl: rcl_send_request(rcl_client_t, request, out sequence_number)
Note over rcl: assigns sequence_number
rcl-->>tracetools: TP?(rcl_send_request, rcl_client_t *, sequence_number)
rcl->>rmw: rmw_send_request(rmw_client_t, request, sequence_number)
Note over Client: puts sequence_number in a map with promise+callback+future
Component->>rclcpp: spin_until_future_complete(result_future) : result_status
Note over Executor: execute_client()
Note over Executor: creates request_header and response objects
Executor->>rcl: rcl_take_response(rcl_client_t, out request_header, out response) : ret
rcl-->>tracetools: TP?()
rcl->>rmw: rmw_take_response(rmw_client_t, out request_header, out response, out taken)
opt RCL_RET_OK == ret
Executor->>Client: handle_response(request_header, response)
Note over Client: gets sequence_number from request_header
Client-->>tracetools: TP?()
Note over Client: gets promise+callback+future from its map
Note over Client: callback(future)
end
rclcpp->>Component: ready or timeout
opt SUCCESS == result_status
Note over Component: result_future.get() : result
Note over Component: do something with result
end
```
#### Timer creation
Timer creation is similar to subscription creation. The `Component` calls `create_service()` which ends up creating a `rclcpp::WallTimer`. In its constructor, it creates a `rclcpp::Clock` object, which (for a `WallTimer`) is simply a nanosecond clock. It then allocates a `rcl_timer_t` handle, then calls `rcl_timer_init()`. This processes the handle and validates the period.
Note that `rcl_timer_init()` can take a callback as a parameter, but right now that feature is not used anywhere (`nullptr` is given), and callbacks are instead handled in the `rclcpp` layer.
```mermaid
sequenceDiagram
participant Component
participant Node
participant WallTimer
participant rcl
participant tracetools
Component->>Node: create_wall_timer(period, callback)
Node->>WallTimer: WallTimer(period, callback, Context)
Note over WallTimer: creates a Clock object
Note over WallTimer: allocates a rcl_timer_t handle
WallTimer->>rcl: rcl_timer_init(out rcl_timer_t, Clock, rcl_context_t, period)
Note over rcl: validates and processes rcl_timer_t handle
rcl-->>tracetools: TP(rcl_timer_init, rcl_timer_t *, period)
WallTimer-->>tracetools: TP(rclcpp_timer_callback_added, rcl_timer_t *, &callback)
```
#### Timer callbacks
Timer callbacks are similar to susbcription callbacks. In `execute_timer()`, the `Executor` calls `execute_callback()` on the `WallTimer`. The timer then calls `rcl_timer_call()` with its `rcl_timer_t` handle and checks if the callback should be called.
If it that is the case, then the timer will call the actual `std::function`. Depending on the `std::function` that was given when creating the timer, it will either call the callback without any parameters or it will pass a reference of itself.
```mermaid
sequenceDiagram
participant Executor
participant WallTimer
participant rcl
participant tracetools
Note over Executor: execute_timer()
Executor->>WallTimer: execute_callback()
WallTimer->>rcl: rcl_timer_call(rcl_timer_t) : ret
Note over rcl: validates and updates timer
opt RCL_RET_TIMER_CANCELED != ret && RCL_RET_OK == ret
WallTimer-->>tracetools: TP(callback_start, &callback)
Note over WallTimer: std::function(...)
WallTimer-->>tracetools: TP(callback_end, &callback)
end
```
## Design & implementation notes
### Targeted tools/dependencies
The targeted tools or dependencies are:
* LTTng for tracing
* pandas and Jupyter for analysis & visualization
### Design
The plan is to use LTTng with a ROS wrapper package like `tracetools` for ROS 1. The suggested setup is:
* a tracing package (e.g. `tracetools`) wraps calls to LTTng
* ROS 2 is instrumented with calls to the tracing package, therefore it becomes a dependency and ships with the core stack
* by default, the tracing package's functions are empty -- they do not do anything
* if users wants to enable tracing, they need to
* install LTTng
* compile the tracing package from source, setting the right compile flag(s)
* overlay it on top of their ROS 2 installation
* use other package(s) for analysis and visualization
## Architecture
![](img/tracing_architecture.png)
### Timeline
The first goal is to statically instrument ROS 2, aiming for it to be in the ROS 2 E-turtle release (Nov 2019).
This includes transposing the existing ROS 1 instrumentation to ROS 2, wherever applicable. This step may not include instrumenting DDS implementations, and thus may be limited to the layer(s) right before `rmw`.
### Notes on client libraries
ROS offer a client library (`rcl`) written in C as the base for any language-specific implementation, such as `rclcpp` and `rclpy`.
However, `rcl` is obviously fairly basic, and still does leave a fair amount of implementation work up to the client libraries. For example, callbacks are not handled in `rcl`, and are left to the client library implementations.
This means that some instrumentation work will have to be re-done for every client library that we want to trace. We cannot simply instrument `rcl`, nor can we only instrument the base `rmw` interface if we want to dig into that.
This effort should first focus on `rcl` and `rclcpp` , but `rclpy` should eventually be added and supported.
### ROS 1/2 compatibility
We could look into making analyses work on both ROS 1 and ROS 2, through a common instrumentation interface (or other abstraction).

Binary file not shown.

After

Width:  |  Height:  |  Size: 107 KiB

23
doc/public_post_plan.md Normal file
View file

@ -0,0 +1,23 @@
Plan for a first public post/announcement.
This week.
Simple example:
* Jupyter notebook to plot:
* subscription callback duration
* CPU cycles/callback
* and resolve/demangle symbols
* Include all files/scripts needed to get the result, but keep it simple
* To be hosted somewhere
* With some documentation/guide on how to try it out
Post:
* Mention that we're working on this
* Talk about goals in general
* Target = next release
* Show off example
* Invite people to ask questions/discuss
Architecture considerations (right now but also later):
* Try to use a dataframe as a ROS model abstraction, instead of first going through a layer with python data structures
*

96
tracetools/CMakeLists.txt Normal file
View file

@ -0,0 +1,96 @@
cmake_minimum_required(VERSION 3.5)
project(tracetools)
# Default to C++14
if(NOT CMAKE_CXX_STANDARD)
set(CMAKE_CXX_STANDARD 14)
endif()
if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")
add_compile_options(-Wall -Wextra -Wpedantic -fPIC)
endif()
find_package(ament_cmake REQUIRED)
option(WITH_LTTNG "Include support for tracing with LTTng" OFF)
if(WITH_LTTNG)
# Try to find LTTng
find_package(PkgConfig REQUIRED)
pkg_check_modules(LTTNG REQUIRED lttng-ust)
endif()
if(LTTNG_FOUND)
set(LTTNG_TP_FILES
include/tracetools/tp_call.h
src/tp_call.c
)
set(TRACING_ENABLED TRUE)
message("LTTng found: tracing enabled")
elseif(WITH_LTTNG)
message("LTTng NOT found: tracing disabled")
endif()
include_directories(include)
# Status checking tool
add_executable(status
src/status.c
src/tracetools.c
src/utils.cpp
)
target_link_libraries(status
${PROJECT_NAME}
)
ament_target_dependencies(status
${PROJECT_NAME}
)
install(TARGETS
status
DESTINATION lib/${PROJECT_NAME}
)
# Tracetools lib
set(SOURCES
src/tracetools.c
src/utils.cpp
)
if(TRACING_ENABLED)
list(APPEND SOURCES ${LTTNG_TP_FILES})
endif()
add_library(${PROJECT_NAME} ${SOURCES})
if(TRACING_ENABLED)
target_compile_definitions(${PROJECT_NAME} PUBLIC TRACETOOLS_LTTNG_ENABLED)
target_link_libraries(${PROJECT_NAME} ${LTTNG_LIBRARIES} -ldl)
else()
target_link_libraries(${PROJECT_NAME})
endif()
ament_export_interfaces(${PROJECT_NAME}_export HAS_LIBRARY_TARGET)
install(
DIRECTORY include/
DESTINATION include
)
install(
TARGETS ${PROJECT_NAME}
EXPORT ${PROJECT_NAME}_export
LIBRARY DESTINATION lib
ARCHIVE DESTINATION lib
RUNTIME DESTINATION bin
INCLUDES DESTINATION include
)
ament_export_include_directories(include)
if(TRACING_ENABLED)
ament_export_libraries(${PROJECT_NAME} ${LTTNG_LIBRARIES})
else()
ament_export_libraries(${PROJECT_NAME})
endif()
if(BUILD_TESTING)
find_package(ament_lint_auto REQUIRED)
ament_lint_auto_find_test_dependencies()
endif()
ament_package()
add_definitions("-D${PROJECT_NAME}_VERSION=\"${${PROJECT_NAME}_VERSION}\"")

26
tracetools/README.md Normal file
View file

@ -0,0 +1,26 @@
# tracetools
## Building
If tracing is not enabled when building, or if LTTng is not found, then this package will not do anything.
To enable tracing:
1. Install [LTTng](https://lttng.org/docs/v2.10/#doc-ubuntu):
```
$ sudo apt-get install lttng-tools lttng-modules-dkms liblttng-ust-dev
```
2. Build with the `WITH_LTTNG` flag:
```
$ colcon build --cmake-args " -DWITH_LTTNG=ON"
```
3. Check if tracing is enabled (after sourcing):
```
$ ros2 run tracetools tracetools_status
```
## Tracing
By default, the steps above will not lead to trace data being generated, and thus they will have no impact on execution.
LTTng has to be enabled: TODO mention scripts

View file

@ -0,0 +1,228 @@
// 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.
// Provide fake header guard for cpplint
#undef TRACETOOLS__TP_CALL_H_
#ifndef TRACETOOLS__TP_CALL_H_
#define TRACETOOLS__TP_CALL_H_
#undef TRACEPOINT_PROVIDER
#define TRACEPOINT_PROVIDER ros2
#undef TRACEPOINT_INCLUDE
#define TRACEPOINT_INCLUDE "tracetools/tp_call.h"
#if !defined(_TRACETOOLS__TP_CALL_H_) || defined(TRACEPOINT_HEADER_MULTI_READ)
#define _TRACETOOLS__TP_CALL_H_
#include <lttng/tracepoint.h>
#include <stdint.h>
TRACEPOINT_EVENT(
TRACEPOINT_PROVIDER,
rcl_init,
TP_ARGS(
const void *, context_handle_arg,
const char *, version_arg
),
TP_FIELDS(
ctf_integer_hex(const void *, context_handle, context_handle_arg)
ctf_string(version, version_arg)
)
)
TRACEPOINT_EVENT(
TRACEPOINT_PROVIDER,
rcl_node_init,
TP_ARGS(
const void *, node_handle_arg,
const void *, rmw_handle_arg,
const char *, node_name_arg,
const char *, namespace_arg
),
TP_FIELDS(
ctf_integer_hex(const void *, node_handle, node_handle_arg)
ctf_integer_hex(const void *, rmw_handle, rmw_handle_arg)
ctf_string(node_name, node_name_arg)
ctf_string(namespace, namespace_arg)
)
)
TRACEPOINT_EVENT(
TRACEPOINT_PROVIDER,
rcl_publisher_init,
TP_ARGS(
const void *, publisher_handle_arg,
const void *, node_handle_arg,
const void *, rmw_publisher_handle_arg,
const char *, topic_name_arg,
const size_t, queue_depth_arg
),
TP_FIELDS(
ctf_integer_hex(const void *, publisher_handle, publisher_handle_arg)
ctf_integer_hex(const void *, node_handle, node_handle_arg)
ctf_integer_hex(const void *, rmw_publisher_handle, rmw_publisher_handle_arg)
ctf_string(topic_name, topic_name_arg)
ctf_integer(const size_t, queue_depth, queue_depth_arg)
)
)
TRACEPOINT_EVENT(
TRACEPOINT_PROVIDER,
rcl_subscription_init,
TP_ARGS(
const void *, subscription_handle_arg,
const void *, node_handle_arg,
const void *, rmw_subscription_handle_arg,
const char *, topic_name_arg,
const size_t, queue_depth_arg
),
TP_FIELDS(
ctf_integer_hex(const void *, subscription_handle, subscription_handle_arg)
ctf_integer_hex(const void *, node_handle, node_handle_arg)
ctf_integer_hex(const void *, rmw_subscription_handle, rmw_subscription_handle_arg)
ctf_string(topic_name, topic_name_arg)
ctf_integer(const size_t, queue_depth, queue_depth_arg)
)
)
TRACEPOINT_EVENT(
TRACEPOINT_PROVIDER,
rclcpp_subscription_callback_added,
TP_ARGS(
const void *, subscription_handle_arg,
const void *, callback_arg
),
TP_FIELDS(
ctf_integer_hex(const void *, subscription_handle, subscription_handle_arg)
ctf_integer_hex(const void *, callback, callback_arg)
)
)
TRACEPOINT_EVENT(
TRACEPOINT_PROVIDER,
rcl_service_init,
TP_ARGS(
const void *, service_handle_arg,
const void *, node_handle_arg,
const void *, rmw_service_handle_arg,
const char *, service_name_arg
),
TP_FIELDS(
ctf_integer_hex(const void *, service_handle, service_handle_arg)
ctf_integer_hex(const void *, node_handle, node_handle_arg)
ctf_integer_hex(const void *, rmw_service_handle, rmw_service_handle_arg)
ctf_string(service_name, service_name_arg)
)
)
TRACEPOINT_EVENT(
TRACEPOINT_PROVIDER,
rclcpp_service_callback_added,
TP_ARGS(
const void *, service_handle_arg,
const void *, callback_arg
),
TP_FIELDS(
ctf_integer_hex(const void *, service_handle, service_handle_arg)
ctf_integer_hex(const void *, callback, callback_arg)
)
)
TRACEPOINT_EVENT(
TRACEPOINT_PROVIDER,
rcl_client_init,
TP_ARGS(
const void *, client_handle_arg,
const void *, node_handle_arg,
const void *, rmw_client_handle_arg,
const char *, service_name_arg
),
TP_FIELDS(
ctf_integer_hex(const void *, client_handle, client_handle_arg)
ctf_integer_hex(const void *, node_handle, node_handle_arg)
ctf_integer_hex(const void *, rmw_client_handle, rmw_client_handle_arg)
ctf_string(service_name, service_name_arg)
)
)
TRACEPOINT_EVENT(
TRACEPOINT_PROVIDER,
rcl_timer_init,
TP_ARGS(
const void *, timer_handle_arg,
int64_t, period_arg
),
TP_FIELDS(
ctf_integer_hex(const void *, timer_handle, timer_handle_arg)
ctf_integer(int64_t, period, period_arg)
)
)
TRACEPOINT_EVENT(
TRACEPOINT_PROVIDER,
rclcpp_timer_callback_added,
TP_ARGS(
const void *, timer_handle_arg,
const void *, callback_arg
),
TP_FIELDS(
ctf_integer_hex(const void *, timer_handle, timer_handle_arg)
ctf_integer_hex(const void *, callback, callback_arg)
)
)
TRACEPOINT_EVENT(
TRACEPOINT_PROVIDER,
rclcpp_callback_register,
TP_ARGS(
const void *, callback_arg,
const char *, symbol_arg
),
TP_FIELDS(
ctf_integer_hex(const void *, callback, callback_arg)
ctf_string(symbol, symbol_arg)
)
)
TRACEPOINT_EVENT(
TRACEPOINT_PROVIDER,
callback_start,
TP_ARGS(
const void *, callback_arg,
int, is_intra_process_arg
),
TP_FIELDS(
ctf_integer_hex(const void *, callback, callback_arg)
ctf_integer(int, is_intra_process, is_intra_process_arg)
)
)
TRACEPOINT_EVENT(
TRACEPOINT_PROVIDER,
callback_end,
TP_ARGS(
const void *, callback_arg
),
TP_FIELDS(
ctf_integer_hex(const void *, callback, callback_arg)
)
)
#endif // _TRACETOOLS__TP_CALL_H_
#include <lttng/tracepoint-event.h>
#endif // TRACETOOLS__TP_CALL_H_

View file

@ -0,0 +1,153 @@
// 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.
#ifndef TRACETOOLS__TRACETOOLS_H_
#define TRACETOOLS__TRACETOOLS_H_
#include <stdint.h>
#include <string.h>
#include <stdbool.h>
#define TRACEPOINT(event_name, ...) \
(ros_trace_ ## event_name)(__VA_ARGS__)
#ifdef __cplusplus
extern "C"
{
#endif
/**
* Report whether tracing is compiled in
*/
bool ros_trace_compile_status();
/**
* tp: rcl_init
*/
void TRACEPOINT(
rcl_init,
const void * context_handle);
/**
* tp: rcl_node_init
*/
void TRACEPOINT(
rcl_node_init,
const void * node_handle,
const void * rmw_handle,
const char * node_name,
const char * node_namespace);
/**
* tp: rcl_publisher_init
*/
void TRACEPOINT(
rcl_publisher_init,
const void * publisher_handle,
const void * node_handle,
const void * rmw_publisher_handle,
const char * topic_name,
const size_t queue_depth);
/**
* tp: rcl_subscription_init
*/
void TRACEPOINT(
rcl_subscription_init,
const void * subscription_handle,
const void * node_handle,
const void * rmw_subscription_handle,
const char * topic_name,
const size_t queue_depth);
/**
* tp: rclcpp_subscription_callback_added
*/
void TRACEPOINT(
rclcpp_subscription_callback_added,
const void * subscription_handle,
const void * callback);
/**
* tp: rcl_service_init
*/
void TRACEPOINT(
rcl_service_init,
const void * service_handle,
const void * node_handle,
const void * rmw_service_handle,
const char * service_name);
/**
* tp: rclcpp_service_callback_added
*/
void TRACEPOINT(
rclcpp_service_callback_added,
const void * service_handle,
const void * callback);
/**
* tp: rcl_client_init
*/
void TRACEPOINT(
rcl_client_init,
const void * client_handle,
const void * node_handle,
const void * rmw_client_handle,
const char * service_name);
/**
* tp: rcl_timer_init
*/
void TRACEPOINT(
rcl_timer_init,
const void * timer_handle,
int64_t period);
/**
* tp: rclcpp_timer_callback_added
*/
void TRACEPOINT(
rclcpp_timer_callback_added,
const void * timer_handle,
const void * callback);
/**
* tp: rclcpp_callback_register
*/
void TRACEPOINT(
rclcpp_callback_register,
const void * callback,
const char * function_symbol);
/**
* tp: callback_start
*/
void TRACEPOINT(
callback_start,
const void * callback,
const bool is_intra_process);
/**
* tp: callback_end
*/
void TRACEPOINT(
callback_end,
const void * callback);
#ifdef __cplusplus
}
#endif
#endif // TRACETOOLS__TRACETOOLS_H_

View file

@ -0,0 +1,35 @@
// 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.
#ifndef TRACETOOLS__UTILS_HPP_
#define TRACETOOLS__UTILS_HPP_
#include <stddef.h>
#include <functional>
template<typename T, typename ... U>
void * get_address(std::function<T(U...)> f)
{
typedef T (fnType)(U...);
fnType ** fnPointer = f.template target<fnType *>();
// Might be a lambda
if (fnPointer == nullptr) {
return 0;
}
return reinterpret_cast<void *>(*fnPointer);
}
const char * get_symbol(void * funptr);
#endif // TRACETOOLS__UTILS_HPP_

24
tracetools/package.xml Normal file
View file

@ -0,0 +1,24 @@
<?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</name>
<version>0.0.1</version>
<description>ROS 2 wrapper for instrumentation</description>
<maintainer email="fixed-term.christophe.bourquebedard@de.bosch.com">Christophe Bedard</maintainer>
<maintainer email="ingo.luetkebohle@de.bosch.com">Ingo Luetkebohle</maintainer>
<license>Apache Software License 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>
<buildtool_depend>ament_cmake</buildtool_depend>
<buildtool_depend>pkg-config</buildtool_depend>
<depend>liblttng-ust-dev</depend>
<test_depend>ament_lint_auto</test_depend>
<test_depend>ament_lint_common</test_depend>
<export>
<build_type>ament_cmake</build_type>
</export>
</package>

View file

@ -0,0 +1,63 @@
#!/bin/bash
# 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.
# set up ust ros2 events
for event in \
ros2:rcl_init \
ros2:rcl_node_init \
ros2:rcl_publisher_init \
ros2:rcl_subscription_init \
ros2:rclcpp_subscription_callback_added \
ros2:rcl_service_init \
ros2:rclcpp_service_callback_added \
ros2:rcl_client_init \
ros2:rcl_timer_init \
ros2:rclcpp_timer_callback_added \
ros2:rclcpp_register_callback \
ros2:callback_start \
ros2:callback_end
do
lttng enable-event -c ros2 -u $event
done
# process context
lttng add-context -c ros2 -u \
-t vpid -t procname \
-t vtid -t perf:thread:instructions \
-t perf:thread:cycles -t perf:thread:cpu-cycles
#kernel
lttng enable-channel --kernel kchan --subbuf-size=8M
# # network
# for event in net_dev_queue netif_receive_skb net_if_receive_skb
# do
# lttng enable-event --kernel --channel=kchan $event
# done
# other kernel stuff
for event in sched_switch sched_waking sched_pi_setprio sched_process_fork sched_process_exit sched_process_free sched_wakeup\
irq_softirq_entry irq_softirq_raise irq_softirq_exit irq_handler_entry irq_handler_exit\
lttng_statedump_process_state lttng_statedump_start lttng_statedump_end lttng_statedump_network_interface lttng_statedump_block_device\
block_rq_complete block_rq_insert block_rq_issue\
block_bio_frontmerge sched_migrate sched_migrate_task power_cpu_frequency\
net_dev_queue netif_receive_skb net_if_receive_skb\
timer_hrtimer_start timer_hrtimer_cancel timer_hrtimer_expire_entry timer_hrtimer_expire_exit
do
lttng enable-event --kernel --channel=kchan $event
done
# lttng enable-event -k --syscall --all

103
tracetools/scripts/trace.sh Executable file
View file

@ -0,0 +1,103 @@
#!/bin/bash
# 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.
## Helper script for ROS tracing
##
## Call this by providing these arguments:
## 1. a session name [optional; 'ros2' will be used]
## 2. a wait time before killing and stopping (in seconds)
## 3. a roslaunch/rosrun command
## ex: ./trace.sh 3 roslaunch tracecompass_ros_testcases pub_sub.launch
source ./${BASH_SOURCE%/*}/../../../../install/local_setup.bash
## Parameters
# if no parameters were given, exit with error
if [ -z "$1" ] ; then
echo "Error: no parameters were given!"
exit 1
elif [ "$1" == "-h" ] ; then
echo -e "ROS tracing helper script.\n" \
"Provide 3 arguments:\n" \
"1. the lttng session name [optional; 'ros2' will be used]\n" \
"2. the wait time before killing and stopping (in seconds)\n" \
"3. the ros2 run command\n" \
"Example: ./trace.sh ros-trace 3 ros2 run tracetools tracetools_status"
exit 0
fi
# session name
session_name="$1"
case "$session_name" in
''|*[!0-9]*) # not a number: good
shift
;;
*) # number: so use a default session name
session_name="ros2"
;;
esac
# wait time (seconds) before killing and stopping
sleep_time="$1"
case "$sleep_time" in
''|*[!0-9]*) # not a number: error!
echo "Error: not a valid sleep time!"
exit 1
;;
*) # number: good
shift
;;
esac
# command from remaining arguments
if [ -z "$1" ] ; then
echo "Error: no command was given!"
exit 1
fi
launch_cmd="$@"
launch_cmd+=" &"
## Trace
# create lttng session (and set output to traces/ directory)
lttng create $session_name --output=./${BASH_SOURCE%/*}/../traces/$session_name/
# enable events
./${BASH_SOURCE%/*}/setup-lttng.sh
# start
lttng start
# preload UST library
export LD_PRELOAD=/usr/lib/x86_64-linux-gnu/liblttng-ust-cyg-profile.so
# launch
eval "$launch_cmd"
# wait a bit and kill
echo "waiting $sleep_time..."
sleep $sleep_time
echo "killing"
kill $!
# wait again for everything to shutdown
echo "waiting for shutdown..."
sleep 2
echo "stopping"
# stop & destroy
lttng stop
lttng destroy

28
tracetools/src/status.c Normal file
View file

@ -0,0 +1,28 @@
// 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.
#include <stdio.h>
#include "tracetools/tracetools.h"
int main()
{
printf("Tracing ");
if (ros_trace_compile_status()) {
printf("enabled\n");
return 0;
} else {
printf("disabled\n");
return 1;
}
}

18
tracetools/src/tp_call.c Normal file
View file

@ -0,0 +1,18 @@
// 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.
#define TRACEPOINT_CREATE_PROBES
#define TRACEPOINT_DEFINE
#include "tracetools/tp_call.h"

218
tracetools/src/tracetools.c Normal file
View file

@ -0,0 +1,218 @@
// 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.
#include "tracetools/tracetools.h"
#if defined(TRACETOOLS_LTTNG_ENABLED) && !defined(_WIN32)
# include "tracetools/tp_call.h"
# define CONDITIONAL_TP(...) \
tracepoint(__VA_ARGS__)
#else
# define CONDITIONAL_TP(...)
#endif
bool ros_trace_compile_status()
{
#if defined(TRACETOOLS_LTTNG_ENABLED) && !defined(_WIN32)
return true;
#else
return false;
#endif
}
#ifndef _WIN32
# pragma GCC diagnostic push
# pragma GCC diagnostic ignored "-Wunused-parameter"
#endif
void TRACEPOINT(
rcl_init,
const void * context_handle)
{
CONDITIONAL_TP(
ros2,
rcl_init,
context_handle,
tracetools_VERSION);
}
void TRACEPOINT(
rcl_node_init,
const void * node_handle,
const void * rmw_handle,
const char * node_name,
const char * node_namespace)
{
CONDITIONAL_TP(
ros2,
rcl_node_init,
node_handle,
rmw_handle,
node_name,
node_namespace);
}
void TRACEPOINT(
rcl_publisher_init,
const void * publisher_handle,
const void * node_handle,
const void * rmw_publisher_handle,
const char * topic_name,
const size_t queue_depth)
{
CONDITIONAL_TP(
ros2,
rcl_publisher_init,
publisher_handle,
node_handle,
rmw_publisher_handle,
topic_name,
queue_depth);
}
void TRACEPOINT(
rcl_subscription_init,
const void * subscription_handle,
const void * node_handle,
const void * rmw_subscription_handle,
const char * topic_name,
const size_t queue_depth)
{
CONDITIONAL_TP(
ros2,
rcl_subscription_init,
subscription_handle,
node_handle,
rmw_subscription_handle,
topic_name,
queue_depth);
}
void TRACEPOINT(
rclcpp_subscription_callback_added,
const void * subscription_handle,
const void * callback)
{
CONDITIONAL_TP(
ros2,
rclcpp_subscription_callback_added,
subscription_handle,
callback);
}
void TRACEPOINT(
rcl_service_init,
const void * service_handle,
const void * node_handle,
const void * rmw_service_handle,
const char * service_name)
{
CONDITIONAL_TP(
ros2,
rcl_service_init,
service_handle,
node_handle,
rmw_service_handle,
service_name);
}
void TRACEPOINT(
rclcpp_service_callback_added,
const void * service_handle,
const void * callback)
{
CONDITIONAL_TP(
ros2,
rclcpp_service_callback_added,
service_handle,
callback);
}
void TRACEPOINT(
rcl_client_init,
const void * client_handle,
const void * node_handle,
const void * rmw_client_handle,
const char * service_name)
{
CONDITIONAL_TP(
ros2,
rcl_client_init,
client_handle,
node_handle,
rmw_client_handle,
service_name);
}
void TRACEPOINT(
rcl_timer_init,
const void * timer_handle,
int64_t period)
{
CONDITIONAL_TP(
ros2,
rcl_timer_init,
timer_handle,
period);
}
void TRACEPOINT(
rclcpp_timer_callback_added,
const void * timer_handle,
const void * callback)
{
CONDITIONAL_TP(
ros2,
rclcpp_timer_callback_added,
timer_handle,
callback);
}
void TRACEPOINT(
rclcpp_callback_register,
const void * callback,
const char * function_symbol)
{
CONDITIONAL_TP(
ros2,
rclcpp_callback_register,
callback,
function_symbol);
}
void TRACEPOINT(
callback_start,
const void * callback,
const bool is_intra_process)
{
CONDITIONAL_TP(
ros2,
callback_start,
callback,
(is_intra_process ? 1 : 0));
}
void TRACEPOINT(
callback_end,
const void * callback)
{
CONDITIONAL_TP(
ros2,
callback_end,
callback);
}
#ifndef _WIN32
# pragma GCC diagnostic pop
#endif

45
tracetools/src/utils.cpp Normal file
View file

@ -0,0 +1,45 @@
// 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.
#if defined(TRACETOOLS_LTTNG_ENABLED) && !defined(_WIN32)
#include <dlfcn.h>
#include <cxxabi.h>
#endif
#include "tracetools/utils.hpp"
const char * get_symbol(void * funptr)
{
#define SYMBOL_UNKNOWN "UNKNOWN"
#if defined(TRACETOOLS_LTTNG_ENABLED) && !defined(_WIN32)
#define SYMBOL_LAMBDA "[lambda]"
if (funptr == 0) {
return SYMBOL_LAMBDA;
}
Dl_info info;
if (dladdr(funptr, &info) == 0) {
return SYMBOL_UNKNOWN;
}
char * demangled = nullptr;
int status;
demangled = abi::__cxa_demangle(info.dli_sname, NULL, 0, &status);
// Use demangled symbol if possible
const char * demangled_val = (status == 0 ? demangled : info.dli_sname);
return demangled_val != 0 ? demangled_val : SYMBOL_UNKNOWN;
#else
(void)funptr;
return SYMBOL_UNKNOWN;
#endif
}

2
tracetools_test/.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
*~
*.pyc

View file

@ -0,0 +1,117 @@
cmake_minimum_required(VERSION 3.5)
project(tracetools_test)
# Default to C++14
if(NOT CMAKE_CXX_STANDARD)
set(CMAKE_CXX_STANDARD 14)
endif()
if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")
add_compile_options(-Wall -Wextra -Wpedantic -fPIC)
endif()
find_package(ament_cmake REQUIRED)
# Tests
if(BUILD_TESTING)
find_package(rclcpp REQUIRED)
find_package(std_msgs REQUIRED)
find_package(std_srvs REQUIRED)
find_package(tracetools REQUIRED)
add_executable(test_publisher
src/test_publisher.cpp
)
ament_target_dependencies(test_publisher
rclcpp
std_msgs
)
add_executable(test_subscription
src/test_subscription.cpp
)
ament_target_dependencies(test_subscription
rclcpp
std_msgs
)
add_executable(test_ping
src/test_ping.cpp
)
ament_target_dependencies(test_ping
rclcpp
std_msgs
)
add_executable(test_pong
src/test_pong.cpp
)
ament_target_dependencies(test_pong
rclcpp
std_msgs
)
add_executable(test_timer
src/test_timer.cpp
)
ament_target_dependencies(test_timer
rclcpp
)
add_executable(test_service
src/test_service.cpp
)
ament_target_dependencies(test_service
rclcpp
std_srvs
)
add_executable(test_service_ping
src/test_service_ping.cpp
)
ament_target_dependencies(test_service_ping
rclcpp
std_srvs
)
add_executable(test_service_pong
src/test_service_pong.cpp
)
ament_target_dependencies(test_service_pong
rclcpp
std_srvs
)
install(TARGETS
test_publisher
test_subscription
test_ping
test_pong
test_timer
test_service
test_service_ping
test_service_pong
DESTINATION lib/${PROJECT_NAME}
)
find_package(ament_lint_auto REQUIRED)
ament_lint_auto_find_test_dependencies()
find_package(ament_cmake_pytest REQUIRED)
# Run each test in its own pytest invocation
set(_tracetools_test_pytest_tests
test/test_node.py
test/test_publisher.py
test/test_subscription.py
test/test_subscription_callback.py
test/test_timer.py
test/test_service.py
test/test_service_callback.py
)
foreach(_test_path ${_tracetools_test_pytest_tests})
get_filename_component(_test_name ${_test_path} NAME_WE)
ament_add_pytest_test(${_test_name} ${_test_path}
PYTHON_EXECUTABLE "${PYTHON_EXECUTABLE}"
APPEND_ENV AMENT_PREFIX_PATH=${ament_index_build_path}
PYTHONPATH=${CMAKE_CURRENT_BINARY_DIR}
TIMEOUT 60
)
endforeach()
endif()
ament_package()

View file

@ -0,0 +1,33 @@
<?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_test</name>
<version>0.0.1</version>
<description>Separate test package for tracetools</description>
<maintainer email="fixed-term.christophe.bourquebedard@de.bosch.com">Christophe Bedard</maintainer>
<maintainer email="ingo.luetkebohle@de.bosch.com">Ingo Luetkebohle</maintainer>
<license>Apache Software License 2.0</license>
<author email="fixed-term.christophe.bourquebedard@de.bosch.com">Christophe Bedard</author>
<buildtool_depend>ament_cmake</buildtool_depend>
<buildtool_depend>pkg-config</buildtool_depend>
<build_depend>rclcpp</build_depend>
<build_depend>std_msgs</build_depend>
<build_depend>std_srvs</build_depend>
<exec_depend>rclcpp</exec_depend>
<exec_depend>std_msgs</exec_depend>
<exec_depend>std_srvs</exec_depend>
<test_depend>ament_cmake_pytest</test_depend>
<test_depend>ament_lint_auto</test_depend>
<test_depend>ament_lint_common</test_depend>
<test_depend>launch_ros</test_depend>
<test_depend>python3-pytest</test_depend>
<test_depend>tracetools</test_depend>
<export>
<build_type>ament_cmake</build_type>
</export>
</package>

View file

@ -0,0 +1,74 @@
// 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.
#include <memory>
#include <chrono>
#include "rclcpp/rclcpp.hpp"
#include "std_msgs/msg/string.hpp"
using namespace std::chrono_literals;
class PingNode : public rclcpp::Node
{
public:
explicit PingNode(rclcpp::NodeOptions options)
: Node("ping_node", options)
{
sub_ = this->create_subscription<std_msgs::msg::String>(
"pong",
rclcpp::QoS(10),
std::bind(&PingNode::callback, this, std::placeholders::_1));
pub_ = this->create_publisher<std_msgs::msg::String>(
"ping",
rclcpp::QoS(10));
timer_ = this->create_wall_timer(
500ms,
std::bind(&PingNode::timer_callback, this));
}
private:
void callback(const std_msgs::msg::String::SharedPtr msg)
{
RCLCPP_INFO(this->get_logger(), "[output] %s", msg->data.c_str());
rclcpp::shutdown();
}
void timer_callback()
{
auto msg = std::make_shared<std_msgs::msg::String>();
msg->data = "some random ping string";
pub_->publish(*msg);
}
rclcpp::Subscription<std_msgs::msg::String>::SharedPtr sub_;
rclcpp::Publisher<std_msgs::msg::String>::SharedPtr pub_;
rclcpp::TimerBase::SharedPtr timer_;
};
int main(int argc, char * argv[])
{
rclcpp::init(argc, argv);
rclcpp::executors::SingleThreadedExecutor exec;
auto ping_node = std::make_shared<PingNode>(rclcpp::NodeOptions());
exec.add_node(ping_node);
printf("spinning\n");
exec.spin();
// Will actually be called inside the node's callback
rclcpp::shutdown();
return 0;
}

View file

@ -0,0 +1,63 @@
// 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.
#include <memory>
#include "rclcpp/rclcpp.hpp"
#include "std_msgs/msg/string.hpp"
class PongNode : public rclcpp::Node
{
public:
explicit PongNode(rclcpp::NodeOptions options)
: Node("pong_node", options)
{
sub_ = this->create_subscription<std_msgs::msg::String>(
"ping",
rclcpp::QoS(10),
std::bind(&PongNode::callback, this, std::placeholders::_1));
pub_ = this->create_publisher<std_msgs::msg::String>(
"pong",
rclcpp::QoS(10));
}
private:
void callback(const std_msgs::msg::String::SharedPtr msg)
{
RCLCPP_INFO(this->get_logger(), "[output] %s", msg->data.c_str());
auto next_msg = std::make_shared<std_msgs::msg::String>();
next_msg->data = "some random pong string";
pub_->publish(*next_msg);
rclcpp::shutdown();
}
rclcpp::Subscription<std_msgs::msg::String>::SharedPtr sub_;
rclcpp::Publisher<std_msgs::msg::String>::SharedPtr pub_;
};
int main(int argc, char * argv[])
{
rclcpp::init(argc, argv);
rclcpp::executors::SingleThreadedExecutor exec;
auto pong_node = std::make_shared<PongNode>(rclcpp::NodeOptions());
exec.add_node(pong_node);
printf("spinning\n");
exec.spin();
// Will actually be called inside the node's callback
rclcpp::shutdown();
return 0;
}

View file

@ -0,0 +1,48 @@
// Copyright 2019 Robert Bosch GmbH
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <memory>
#include "rclcpp/rclcpp.hpp"
#include "std_msgs/msg/string.hpp"
class PubNode : public rclcpp::Node
{
public:
explicit PubNode(rclcpp::NodeOptions options)
: Node("pub_node", options)
{
pub_ = this->create_publisher<std_msgs::msg::String>(
"the_topic",
rclcpp::QoS(10));
}
private:
rclcpp::Publisher<std_msgs::msg::String>::SharedPtr pub_;
};
int main(int argc, char * argv[])
{
rclcpp::init(argc, argv);
rclcpp::executors::SingleThreadedExecutor exec;
auto pub_node = std::make_shared<PubNode>(rclcpp::NodeOptions());
exec.add_node(pub_node);
printf("spinning once\n");
exec.spin_once();
rclcpp::shutdown();
return 0;
}

View file

@ -0,0 +1,64 @@
// 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.
#include <memory>
#include "rclcpp/rclcpp.hpp"
#include "std_srvs/srv/empty.hpp"
class ServiceNode : public rclcpp::Node
{
public:
explicit ServiceNode(rclcpp::NodeOptions options)
: Node("service_node", options)
{
srv_ = this->create_service<std_srvs::srv::Empty>(
"service",
std::bind(
&ServiceNode::service_callback,
this,
std::placeholders::_1,
std::placeholders::_2,
std::placeholders::_3));
}
private:
void service_callback(
const std::shared_ptr<rmw_request_id_t> request_header,
const std::shared_ptr<std_srvs::srv::Empty::Request> request,
const std::shared_ptr<std_srvs::srv::Empty::Response> response)
{
// Nothing
(void)request_header;
(void)request;
(void)response;
}
rclcpp::Service<std_srvs::srv::Empty>::SharedPtr srv_;
};
int main(int argc, char * argv[])
{
rclcpp::init(argc, argv);
rclcpp::executors::SingleThreadedExecutor exec;
auto service_node = std::make_shared<ServiceNode>(rclcpp::NodeOptions());
exec.add_node(service_node);
printf("spinning once\n");
exec.spin_once();
rclcpp::shutdown();
return 0;
}

View file

@ -0,0 +1,82 @@
// 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.
#include <memory>
#include <chrono>
#include "rclcpp/rclcpp.hpp"
#include "std_srvs/srv/empty.hpp"
using namespace std::chrono_literals;
class PingNode : public rclcpp::Node
{
public:
explicit PingNode(rclcpp::NodeOptions options)
: Node("ping_node", options)
{
srv_ = this->create_service<std_srvs::srv::Empty>(
"pong",
std::bind(
&PingNode::service_callback,
this,
std::placeholders::_1,
std::placeholders::_2,
std::placeholders::_3));
client_ = this->create_client<std_srvs::srv::Empty>(
"ping");
timer_ = this->create_wall_timer(
500ms,
std::bind(&PingNode::timer_callback, this));
}
private:
void service_callback(
const std::shared_ptr<rmw_request_id_t> request_header,
const std::shared_ptr<std_srvs::srv::Empty::Request> request,
const std::shared_ptr<std_srvs::srv::Empty::Response> response)
{
(void)request_header;
(void)request;
(void)response;
RCLCPP_INFO(this->get_logger(), "got request");
rclcpp::shutdown();
}
void timer_callback()
{
auto req = std::make_shared<std_srvs::srv::Empty::Request>();
client_->async_send_request(req);
}
rclcpp::Service<std_srvs::srv::Empty>::SharedPtr srv_;
rclcpp::Client<std_srvs::srv::Empty>::SharedPtr client_;
rclcpp::TimerBase::SharedPtr timer_;
};
int main(int argc, char * argv[])
{
rclcpp::init(argc, argv);
rclcpp::executors::SingleThreadedExecutor exec;
auto ping_node = std::make_shared<PingNode>(rclcpp::NodeOptions());
exec.add_node(ping_node);
printf("spinning\n");
exec.spin();
// Will actually be called inside the node's service callback
rclcpp::shutdown();
return 0;
}

View file

@ -0,0 +1,70 @@
// 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.
#include <memory>
#include "rclcpp/rclcpp.hpp"
#include "std_srvs/srv/empty.hpp"
class PongNode : public rclcpp::Node
{
public:
explicit PongNode(rclcpp::NodeOptions options)
: Node("pong_node", options)
{
srv_ = this->create_service<std_srvs::srv::Empty>(
"ping",
std::bind(
&PongNode::service_callback,
this,
std::placeholders::_1,
std::placeholders::_2,
std::placeholders::_3));
client_ = this->create_client<std_srvs::srv::Empty>("pong");
}
private:
void service_callback(
const std::shared_ptr<rmw_request_id_t> request_header,
const std::shared_ptr<std_srvs::srv::Empty::Request> request,
const std::shared_ptr<std_srvs::srv::Empty::Response> response)
{
(void)request_header;
(void)request;
(void)response;
RCLCPP_INFO(this->get_logger(), "got request");
auto req = std::make_shared<std_srvs::srv::Empty::Request>();
client_->async_send_request(req);
rclcpp::shutdown();
}
rclcpp::Service<std_srvs::srv::Empty>::SharedPtr srv_;
rclcpp::Client<std_srvs::srv::Empty>::SharedPtr client_;
};
int main(int argc, char * argv[])
{
rclcpp::init(argc, argv);
rclcpp::executors::SingleThreadedExecutor exec;
auto pong_node = std::make_shared<PongNode>(rclcpp::NodeOptions());
exec.add_node(pong_node);
printf("spinning\n");
exec.spin();
// Will actually be called inside the node's service callback
rclcpp::shutdown();
return 0;
}

View file

@ -0,0 +1,55 @@
// 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.
#include <memory>
#include "rclcpp/rclcpp.hpp"
#include "std_msgs/msg/string.hpp"
class SubNode : public rclcpp::Node
{
public:
explicit SubNode(rclcpp::NodeOptions options)
: Node("sub_node", options)
{
sub_ = this->create_subscription<std_msgs::msg::String>(
"the_topic",
rclcpp::QoS(10),
std::bind(&SubNode::callback, this, std::placeholders::_1));
}
private:
void callback(const std_msgs::msg::String::SharedPtr msg)
{
// Nothing
(void)msg;
}
rclcpp::Subscription<std_msgs::msg::String>::SharedPtr sub_;
};
int main(int argc, char * argv[])
{
rclcpp::init(argc, argv);
rclcpp::executors::SingleThreadedExecutor exec;
auto sub_node = std::make_shared<SubNode>(rclcpp::NodeOptions());
exec.add_node(sub_node);
printf("spinning once\n");
exec.spin_once();
rclcpp::shutdown();
return 0;
}

View file

@ -0,0 +1,62 @@
// 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.
#include <memory>
#include <chrono>
#include "rclcpp/rclcpp.hpp"
using namespace std::chrono_literals;
class TimerNode : public rclcpp::Node
{
public:
explicit TimerNode(rclcpp::NodeOptions options)
: Node("timer_node", options)
{
is_done_ = false;
timer_ = this->create_wall_timer(
1ms,
std::bind(&TimerNode::timer_callback, this));
}
private:
void timer_callback()
{
if (is_done_) {
rclcpp::shutdown();
} else {
is_done_ = true;
}
}
rclcpp::TimerBase::SharedPtr timer_;
bool is_done_;
};
int main(int argc, char * argv[])
{
rclcpp::init(argc, argv);
rclcpp::executors::SingleThreadedExecutor exec;
auto timer_node = std::make_shared<TimerNode>(rclcpp::NodeOptions());
exec.add_node(timer_node);
printf("spinning\n");
exec.spin();
// Will actually be called inside the timer's callback
rclcpp::shutdown();
return 0;
}

View file

@ -0,0 +1,54 @@
# 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.
import unittest
from tracetools_test.utils import (
cleanup_trace,
get_trace_event_names,
run_and_trace,
)
BASE_PATH = '/tmp'
PKG = 'tracetools_test'
node_creation_events = [
'ros2:rcl_init',
'ros2:rcl_node_init',
]
class TestNode(unittest.TestCase):
def test_creation(self):
session_name_prefix = 'session-test-node-creation'
test_node = ['test_publisher']
exit_code, full_path = run_and_trace(
BASE_PATH,
session_name_prefix,
node_creation_events,
None,
PKG,
test_node)
self.assertEqual(exit_code, 0)
trace_events = get_trace_event_names(full_path)
print(f'trace_events: {trace_events}')
self.assertSetEqual(set(node_creation_events), trace_events)
cleanup_trace(full_path)
if __name__ == '__main__':
unittest.main()

View file

@ -0,0 +1,53 @@
# 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.
import unittest
from tracetools_test.utils import (
cleanup_trace,
get_trace_event_names,
run_and_trace,
)
BASE_PATH = '/tmp'
PKG = 'tracetools_test'
publisher_creation_events = [
'ros2:rcl_publisher_init',
]
class TestPublisher(unittest.TestCase):
def test_creation(self):
session_name_prefix = 'session-test-publisher-creation'
test_node = ['test_publisher']
exit_code, full_path = run_and_trace(
BASE_PATH,
session_name_prefix,
publisher_creation_events,
None,
PKG,
test_node)
self.assertEqual(exit_code, 0)
trace_events = get_trace_event_names(full_path)
print(f'trace_events: {trace_events}')
self.assertSetEqual(set(publisher_creation_events), trace_events)
cleanup_trace(full_path)
if __name__ == '__main__':
unittest.main()

View file

@ -0,0 +1,54 @@
# 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.
import unittest
from tracetools_test.utils import (
cleanup_trace,
get_trace_event_names,
run_and_trace,
)
BASE_PATH = '/tmp'
PKG = 'tracetools_test'
service_creation_events = [
'ros2:rcl_service_init',
'ros2:rclcpp_service_callback_added',
]
class TestService(unittest.TestCase):
def test_creation(self):
session_name_prefix = 'session-test-service-creation'
test_nodes = ['test_service']
exit_code, full_path = run_and_trace(
BASE_PATH,
session_name_prefix,
service_creation_events,
None,
PKG,
test_nodes)
self.assertEqual(exit_code, 0)
trace_events = get_trace_event_names(full_path)
print(f'trace_events: {trace_events}')
self.assertSetEqual(set(service_creation_events), trace_events)
cleanup_trace(full_path)
if __name__ == '__main__':
unittest.main()

View file

@ -0,0 +1,54 @@
# 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.
import unittest
from tracetools_test.utils import (
cleanup_trace,
get_trace_event_names,
run_and_trace,
)
BASE_PATH = '/tmp'
PKG = 'tracetools_test'
service_callback_events = [
'ros2:callback_start',
'ros2:callback_end',
]
class TestServiceCallback(unittest.TestCase):
def test_callback(self):
session_name_prefix = 'session-test-service-callback'
test_nodes = ['test_service_ping', 'test_service_pong']
exit_code, full_path = run_and_trace(
BASE_PATH,
session_name_prefix,
service_callback_events,
None,
PKG,
test_nodes)
self.assertEqual(exit_code, 0)
trace_events = get_trace_event_names(full_path)
print(f'trace_events: {trace_events}')
self.assertSetEqual(set(service_callback_events), trace_events)
cleanup_trace(full_path)
if __name__ == '__main__':
unittest.main()

View file

@ -0,0 +1,54 @@
# 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.
import unittest
from tracetools_test.utils import (
cleanup_trace,
get_trace_event_names,
run_and_trace,
)
BASE_PATH = '/tmp'
PKG = 'tracetools_test'
subscription_creation_events = [
'ros2:rcl_subscription_init',
'ros2:rclcpp_subscription_callback_added',
]
class TestSubscription(unittest.TestCase):
def test_creation(self):
session_name_prefix = 'session-test-subscription-creation'
test_node = ['test_subscription']
exit_code, full_path = run_and_trace(
BASE_PATH,
session_name_prefix,
subscription_creation_events,
None,
PKG,
test_node)
self.assertEqual(exit_code, 0)
trace_events = get_trace_event_names(full_path)
print(f'trace_events: {trace_events}')
self.assertSetEqual(set(subscription_creation_events), trace_events)
cleanup_trace(full_path)
if __name__ == '__main__':
unittest.main()

View file

@ -0,0 +1,54 @@
# 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.
import unittest
from tracetools_test.utils import (
cleanup_trace,
get_trace_event_names,
run_and_trace,
)
BASE_PATH = '/tmp'
PKG = 'tracetools_test'
subscription_callback_events = [
'ros2:callback_start',
'ros2:callback_end',
]
class TestSubscriptionCallback(unittest.TestCase):
def test_callback(self):
session_name_prefix = 'session-test-subscription-callback'
test_nodes = ['test_ping', 'test_pong']
exit_code, full_path = run_and_trace(
BASE_PATH,
session_name_prefix,
subscription_callback_events,
None,
PKG,
test_nodes)
self.assertEqual(exit_code, 0)
trace_events = get_trace_event_names(full_path)
print(f'trace_events: {trace_events}')
self.assertSetEqual(set(subscription_callback_events), trace_events)
cleanup_trace(full_path)
if __name__ == '__main__':
unittest.main()

View file

@ -0,0 +1,56 @@
# 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.
import unittest
from tracetools_test.utils import (
cleanup_trace,
get_trace_event_names,
run_and_trace,
)
BASE_PATH = '/tmp'
PKG = 'tracetools_test'
timer_events = [
'ros2:rcl_timer_init',
'ros2:rclcpp_timer_callback_added',
'ros2:callback_start',
'ros2:callback_end',
]
class TestTimer(unittest.TestCase):
def test_all(self):
session_name_prefix = 'session-test-timer-all'
test_nodes = ['test_timer']
exit_code, full_path = run_and_trace(
BASE_PATH,
session_name_prefix,
timer_events,
None,
PKG,
test_nodes)
self.assertEqual(exit_code, 0)
trace_events = get_trace_event_names(full_path)
print(f'trace_events: {trace_events}')
self.assertSetEqual(set(timer_events), trace_events)
cleanup_trace(full_path)
if __name__ == '__main__':
unittest.main()

View 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.

View file

@ -0,0 +1,107 @@
# 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.
"""Utils for tracetools_test."""
import os
import shutil
import time
from typing import List
from typing import Set
from typing import Tuple
import babeltrace
from launch import LaunchDescription
from launch import LaunchService
from launch_ros import get_default_launch_description
import launch_ros.actions
from tracetools_trace.tools.lttng import (
lttng_destroy,
lttng_setup,
lttng_start,
lttng_stop,
)
def run_and_trace(
base_path: str,
session_name_prefix: str,
ros_events: List[str],
kernel_events: List[str],
package_name: str,
node_names: List[str]
) -> Tuple[int, str]:
"""
Run a node while tracing.
:param base_path: the base path where to put the trace directory
:param session_name_prefix: the session name prefix for the trace directory
:param ros_events: the list of ROS UST events to enable
:param kernel_events: the list of kernel events to enable
:param package_name: the name of the package to use
:param node_names: the names of the nodes to execute
:return: exit code, full generated path
"""
session_name = f'{session_name_prefix}-{time.strftime("%Y%m%d%H%M%S")}'
full_path = os.path.join(base_path, session_name)
print(f'trace directory: {full_path}')
lttng_setup(session_name, full_path, ros_events=ros_events, kernel_events=kernel_events)
lttng_start(session_name)
nodes = []
for node_name in node_names:
n = launch_ros.actions.Node(
package=package_name,
node_executable=node_name,
output='screen')
nodes.append(n)
ld = LaunchDescription(nodes)
ls = LaunchService()
ls.include_launch_description(get_default_launch_description())
ls.include_launch_description(ld)
exit_code = ls.run()
lttng_stop(session_name)
lttng_destroy(session_name)
return exit_code, full_path
def cleanup_trace(full_path: str) -> None:
"""
Cleanup trace data.
:param full_path: the full path to the main trace directory
"""
shutil.rmtree(full_path)
def get_trace_event_names(trace_directory: str) -> Set[str]:
"""
Get a set of event names in a trace.
:param trace_directory: the path to the main/top trace directory
:return: event names
"""
tc = babeltrace.TraceCollection()
tc.add_traces_recursive(trace_directory, 'ctf')
event_names = set()
for event in tc.events:
event_names.add(event.name)
return event_names