Skip to content

Commit 29bb2c9

Browse files
authored
Merge pull request #8 from microROS/feature/cbg-executor_ping-pong
cbg-executor ping-pong demo
2 parents b004096 + 47683ce commit 29bb2c9

13 files changed

Lines changed: 636 additions & 0 deletions
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
cmake_minimum_required(VERSION 3.5)
2+
project(cbg-executor_ping-pong_cpp)
3+
4+
# Default to C++14
5+
if(NOT CMAKE_CXX_STANDARD)
6+
set(CMAKE_CXX_STANDARD 14)
7+
endif()
8+
9+
if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")
10+
add_compile_options(-Wall -Wextra -Wpedantic)
11+
endif()
12+
13+
find_package(ament_cmake REQUIRED)
14+
find_package(rclcpp REQUIRED)
15+
find_package(std_msgs REQUIRED)
16+
17+
include_directories(include)
18+
19+
add_executable(ping-pong main.cpp src/PingSubNode.cpp src/PongSubNode.cpp)
20+
ament_target_dependencies(ping-pong rclcpp std_msgs)
21+
22+
install(TARGETS ping-pong
23+
DESTINATION bin/${PROJECT_NAME}
24+
)
25+
26+
ament_package()
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
# cbg-executor_ping-pong_cpp
2+
3+
This package provides a small example for the use of the Callback-group-level Executor concept.
4+
5+
The Callback-group-level Executor leverages the callback-group concept in rclcpp by introducing real-time profiles such as RT-CRITICAL and BEST-EFFORT in the callback-group API (i.e. [rclcpp/callback_group.hpp](https://github.com/microROS/rclcpp/blob/master/rclcpp/include/rclcpp/callback_group.hpp)). Each callback requiring specific real-time guarantees, when created, may therefore be associated with a dedicated callback group. With this in place, the Executor class and depending classes (e.g., for memory allocation) were enhanced to operate at a finer, callback-group-level granularity.
6+
7+
This allows a single node to have callbacks with different real-time profiles assigned to different Executor instances – within one process. Thus, an Executor instance can be dedicated to one or few specific callback groups and the Executor’s thread (or threads) can be prioritized according to the real-time requirements of these groups. For example, all time-critical callbacks may be handled by an "RT-CRITICAL" Executor instance running at the highest scheduler priority.
8+
9+
As a proof of concept, we implemented a small test bench in the present package *cbg-executor_ping-pong_cpp*. The test bench comprises a Ping node and a Pong node which exchange real-time and best-effort messages simultaneously with each other. Each class of messages is handled with a dedicated Executor, as illustrated in the following figure.
10+
11+
![](doc/ping_pong_diagram.png)
12+
13+
The Ping node can be configured to send messages at a configured rate. The Pong node takes these ping messages and replies each of them. Before sending the reply, it can be configured to burn cycles (thereby varying the processor load) to simulate some message processing. We provide bash scripts to test intra-process and inter-process communication scenarios, wherein the nodes are co-located either in one process or two processes, respectively. These scripts also vary the message rates and processor loads. After each run, the Ping node outputs the measured throughput of real-time and best effort messages.
14+
15+
16+
## Running the test bench
17+
18+
After building the test bench with [colcon](https://github.com/ros2/ros2/wiki/Colcon-Tutorial), the Ping and Pong nodes may be either started in one process or in two processes. Please note that the test bench requires sudo privileges to be able to change the thread priorities using `pthread_setschedparam(..)`. Start the executable by
19+
20+
```
21+
ros2 run cbg-executor_ping-pong_cpp ping-pong args...
22+
```
23+
24+
where `args...` are five or six arguments as follows:
25+
26+
```
27+
[type] [rt_ping_period_us] [be_ping_period_us] [rt_busyloop_us] [be_busyloop_us] [cpu_id]
28+
29+
type: determines the nodes included in this process:
30+
i: ping node only
31+
o: pong node only
32+
io: ping node and pong node
33+
34+
rt_ping_period_us: microseconds between publishing of ping messages by real-time thread
35+
in ping node
36+
be_ping_period_us: microseconds between publishing of ping messages by best-effort
37+
thread in ping node
38+
rt_busyloop_us: microseconds of computation by real-time thread in pong node before
39+
answering with pong
40+
be_busyloop_us: microseconds of computation by best-effort thread in pong node before
41+
answering with pong
42+
cpu_id (optional): pins both, real-time thread and best-effort thread, to the given cpu
43+
```
44+
45+
When using type `i`, a second process with type `o` has to be started simultaneously.
46+
47+
The shell run\* scripts in this folder run various experiments in sequence. For pertinent results, we propose to use the [PREEMPT_RT patch](https://wiki.linuxfoundation.org/realtime/start) for the Linux kernel.
48+
49+
50+
## Implementation Details
51+
52+
The algorithms of the Ping node and of the Pong node are factored out into classes [_PingSubNode_](include/PingSubNode.hpp) and [_PongSubNode_](include/PongSubNode.hpp) - configurable with regard to the real-time profile and the topic prefix. Thus, the Ping node contains two instances of the _PingSubNode_ and the Pong node contains two instances of _PongSubNode_. (And the test bench could be easily extended to more than two ping-pong paths.)
53+
54+
The PingSubNode contains a timer for sending the ping messages and a subscription for the corresponding pong messages. Also, it records the number of messages being sent and received and measures the roundtrip time.
55+
56+
The PongSubNode contains a subscription for the ping messages and a publisher for the corresponding pong messages. On receiving a ping message, it calls the `PongSubNode::burn_cpu_cycles()` functions to simulate a given processing time before replying with a pong.
57+
58+
The Ping and Pong nodes, the two executors, etc. are composed and configured in the `main(..)` function of [main.cpp](main.cpp). This function also starts and ends the experiment for a predefined duration and prints out the throughput and latency statistics.
82.1 KB
Loading
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
// Copyright
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
#ifndef CBG_EXECUTOR_PING_PONG__PING_SUB_NODE_HPP_
16+
#define CBG_EXECUTOR_PING_PONG__PING_SUB_NODE_HPP_
17+
18+
#include <memory>
19+
#include <string>
20+
#include <vector>
21+
22+
#include "rclcpp/node.hpp"
23+
#include "rclcpp/rclcpp.hpp"
24+
#include "std_msgs/msg/int32.hpp"
25+
26+
27+
/// This class implements the Ping side of one ping-pong path of the test-bench. See README.md
28+
/// for a simple architecture diagram and a description of the whole test bench.
29+
class PingSubNode
30+
{
31+
public:
32+
typedef std::shared_ptr<PingSubNode> SharedPtr;
33+
34+
PingSubNode(
35+
rclcpp::Node::SharedPtr node, rclcpp::callback_group::RealTimeClass cbg_class,
36+
const std::string & topics_prefix, const std::chrono::microseconds send_period);
37+
38+
virtual ~PingSubNode() = default;
39+
40+
rclcpp::callback_group::CallbackGroup::SharedPtr get_callback_group() {return callback_group_;}
41+
42+
/// Prints out the measured message throughput and latency.
43+
void print_statistics();
44+
45+
private:
46+
/// The callback group for the timer and the pong subscription.
47+
rclcpp::callback_group::CallbackGroup::SharedPtr callback_group_{};
48+
49+
/// Prefix for the ping and pong topics - here RT or BE.
50+
const std::string topics_prefix_;
51+
52+
/// Timer for sending the pings in the given send period (cf. ctor).
53+
rclcpp::TimerBase::SharedPtr ping_timer_{};
54+
55+
rclcpp::Publisher<std_msgs::msg::Int32>::SharedPtr ping_publisher_{};
56+
57+
rclcpp::Subscription<std_msgs::msg::Int32>::SharedPtr pong_subscription_{};
58+
59+
size_t ping_sent_count_ = 0;
60+
size_t pong_received_count_ = 0;
61+
std::vector<std::chrono::system_clock::time_point> ping_sent_timestamps_{};
62+
std::vector<std::chrono::system_clock::time_point> pong_received_timestamps_{};
63+
64+
void ping_timer_callback();
65+
66+
void pong_subscription_callback(const std_msgs::msg::Int32::SharedPtr msg);
67+
};
68+
69+
#endif // CBG_EXECUTOR_PING_PONG__PING_SUB_NODE_HPP_
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
// Copyright
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
#ifndef CBG_EXECUTOR_PING_PONG__PONG_SUB_NODE_HPP_
16+
#define CBG_EXECUTOR_PING_PONG__PONG_SUB_NODE_HPP_
17+
18+
#include <chrono>
19+
#include <memory>
20+
#include <string>
21+
22+
#include "rclcpp/rclcpp.hpp"
23+
#include "std_msgs/msg/int32.hpp"
24+
25+
/// This class implements the Pong side of one ping-pong path of the test-bench. See README.md
26+
/// for a simple architecture diagram and a description of the whole test bench.
27+
class PongSubNode
28+
{
29+
public:
30+
typedef std::shared_ptr<PongSubNode> SharedPtr;
31+
32+
PongSubNode(
33+
rclcpp::Node::SharedPtr node, rclcpp::callback_group::RealTimeClass cbg_class,
34+
const std::string & topics_prefix, std::chrono::microseconds cpu_load);
35+
36+
virtual ~PongSubNode() = default;
37+
38+
rclcpp::callback_group::CallbackGroup::SharedPtr get_callback_group() {return callback_group_;}
39+
40+
private:
41+
/// The callback group for the ping subscription.
42+
rclcpp::callback_group::CallbackGroup::SharedPtr callback_group_;
43+
44+
/// Prefix for the ping and pong topics - here RT or BE.
45+
const std::string topics_prefix_;
46+
47+
rclcpp::Subscription<std_msgs::msg::Int32>::SharedPtr ping_subscription_{};
48+
49+
rclcpp::Publisher<std_msgs::msg::Int32>::SharedPtr pong_publisher_{};
50+
51+
/// The given duration (cf. ctor) to simulate some processing on receiving a ping message.
52+
const std::chrono::microseconds cpu_load_;
53+
54+
void ping_subscription_callback(const std_msgs::msg::Int32::SharedPtr msg);
55+
56+
/// Burns CPU cycles for the cpu_load_ duration.
57+
void burn_cpu_cycles();
58+
};
59+
60+
#endif // CBG_EXECUTOR_PING_PONG__PONG_SUB_NODE_HPP_

0 commit comments

Comments
 (0)