Skip to content

Commit 239944a

Browse files
authored
Merge pull request #134 from cactusdynamics/examples-update
Batch 1 of example updates
2 parents 4450f9c + f88257c commit 239944a

File tree

20 files changed

+207
-167
lines changed

20 files changed

+207
-167
lines changed

Diff for: CMakeLists.txt

+1-2
Original file line numberDiff line numberDiff line change
@@ -183,11 +183,10 @@ if(${CMAKE_PROJECT_NAME} STREQUAL ${PROJECT_NAME})
183183

184184
if (ENABLE_EXAMPLES)
185185
message(STATUS "Building example programs. Turn it off via ENABLE_EXAMPLES=OFF")
186-
add_subdirectory(examples/lockless_example)
186+
add_subdirectory(examples/lockless_examples)
187187
add_subdirectory(examples/logging_example)
188188
add_subdirectory(examples/message_passing_example)
189189
add_subdirectory(examples/mutex_example)
190-
add_subdirectory(examples/signal_handling_example)
191190
add_subdirectory(examples/simple_deadline_example)
192191
add_subdirectory(examples/simple_example)
193192
add_subdirectory(examples/random_example)

Diff for: README.md

+10-10
Original file line numberDiff line numberDiff line change
@@ -29,22 +29,22 @@ writing a real-time Linux application. Some key features are:
2929
Examples
3030
--------
3131

32-
See each example's README for more details on what they do.
33-
3432
* [`simple_example`](examples/simple_example/): The most basic example showing
35-
a single real-time looping thread.
36-
* [`signal_handling_example`](examples/signal_handling_example/): Same as
37-
`simple_example`, except the program respond to SIGTERM and SIGINT and quit
38-
upon receiving the signal.
39-
* [`logging_example`](examples/logging_example/): Demonstrates setting up custom
40-
logging configuration via `cactus_rt::App`.
33+
a single real-time looping thread running at 1000 Hz.
34+
* [`tracing_example`](examples/tracing_example/): This demonstrates how to use
35+
the real-time-safe tracing system built into cactus-rt. This is probably a
36+
good thing to undrestand immediately after the above example.
4137
* [`mutex_example`](examples/mutex_example/): Demonstrates the usage of
4238
priority-inheritence mutex (`cactus_rt::mutex`) to pass data between real-time
43-
and non-real-time threads.
39+
and non-real-time threads via the implementation of a mutex-based double
40+
buffer.
41+
* [`logging_example`](examples/logging_example/): Demonstrates setting up custom
42+
logging configuration via `cactus_rt::App`.
43+
4444
* [`simple_deadline_example`](examples/simple_deadline_example/): Same as
4545
`simple_example`, except it uses `SCHED_DEADLINE` as opposed to `SCHED_FIFO`.
4646
This is for a more advanced use case.
47-
* [`tracing_example`](examples/tracing_example/): Shows how to dynamically start and stop tracing, as well as trace custom application functions.
47+
4848
* [`tracing_example_no_rt`](examples/tracing_example_no_rt/): Shows how to using the tracing library in `cactus_rt` without using `cactus_rt::App`.
4949

5050

Diff for: examples/lockless_example/CMakeLists.txt

-10
This file was deleted.

Diff for: examples/lockless_examples/CMakeLists.txt

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
add_executable(rt_lockless_realtime_read_example
2+
realtime_read.cc
3+
)
4+
5+
target_link_libraries(rt_lockless_realtime_read_example
6+
PRIVATE
7+
cactus_rt
8+
)
9+
10+
setup_cactus_rt_target_options(rt_lockless_realtime_read_example)

Diff for: examples/lockless_examples/README.md

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Lockless examples
2+
=================
3+
File renamed without changes.

Diff for: examples/mutex_example/README.md

+6-3
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,17 @@
44
This program shows the usage of sharing data between an RT and a non-RT thread
55
via the `cactus_rt::mutex`, which is a priority-inheritence mutex compatible
66
with the [`Lockable`](https://en.cppreference.com/w/cpp/named_req/Lockable)
7-
interface.
7+
interface. This means you can use this `mutex` just like you would a normal
8+
mutex from STL and expect that priority inheritance is enabled on it.
89

9-
Specifically, this example implements a very naive double buffer. This data
10-
structure has 2 data slots guarded by the priority-inheriting mutex. The RT
10+
In this example, we use the `cactus_rt::mutex` to implement a very naive double
11+
buffer. It has 2 data slots guarded by the priority-inheriting mutex. The RT
1112
thread writes to the double buffer at 1 kHz and the non-RT thread reads it every
1213
half second. Once it is read, it is logged via the `cactus_rt` logging
1314
capability.
1415

16+
_Note: a lockless version of this double buffer is implemented by the cactus-rt framework under `cactus_rt::experimental::lockless::spsc::AtomicWritableValue` which doesn't require a lock. That serves as an alternative to this code without the usage of a mutex._
17+
1518
To run:
1619

1720
```bash

Diff for: examples/mutex_example/double_buffer.h

+5-2
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33

44
#include <cactus_rt/mutex.h>
55

6-
#include <cstdint>
76
#include <mutex>
87

98
/**
@@ -12,7 +11,11 @@
1211
* Also only uses a single mutex so reads, writes, and swaps contend on a
1312
* single lock, which is no good for performance.
1413
*
15-
* Realistically, you would implement this in a lock-free manner, like this:
14+
* cactus-rt already has something for this use case (real-time thread writes
15+
* and non-real-time thread reads) implemented via the
16+
* cactus_rt::experimental::lockless::spsc::RealtimeWritableValue.
17+
*
18+
* There could also be alternative implementations like:
1619
* https://stackoverflow.com/questions/23666069/single-producer-single-consumer-data-structure-with-double-buffer-in-c
1720
*/
1821
template <typename T>

Diff for: examples/mutex_example/main.cc

+28-14
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@ using cactus_rt::App;
1010
using cactus_rt::CyclicThread;
1111
using cactus_rt::Thread;
1212

13+
// This is the data structure we are passing between the RT and non-RT thread.
14+
// It is big enough such that it cannot be atomically changed with a single
15+
// instruction to necessitate a mutex.
1316
struct Data {
1417
double v1 = 0.0;
1518
double v2 = 0.0;
@@ -21,8 +24,8 @@ class RTThread : public CyclicThread {
2124
NaiveDoubleBuffer<Data>& buf_;
2225

2326
public:
24-
explicit RTThread(const char* name, cactus_rt::CyclicThreadConfig config, NaiveDoubleBuffer<Data>& buf)
25-
: CyclicThread(name, config),
27+
explicit RTThread(NaiveDoubleBuffer<Data>& buf)
28+
: CyclicThread("RTThread", CreateConfig()),
2629
buf_(buf) {}
2730

2831
protected:
@@ -40,14 +43,24 @@ class RTThread : public CyclicThread {
4043

4144
return LoopControl::Continue;
4245
}
46+
47+
private:
48+
static cactus_rt::CyclicThreadConfig CreateConfig() {
49+
cactus_rt::CyclicThreadConfig thread_config;
50+
thread_config.period_ns = 1'000'000;
51+
thread_config.SetFifoScheduler(80);
52+
53+
return thread_config;
54+
}
4355
};
4456

4557
class NonRTThread : public Thread {
4658
NaiveDoubleBuffer<Data>& buf_;
4759

4860
public:
49-
explicit NonRTThread(const char* name, cactus_rt::CyclicThreadConfig config, NaiveDoubleBuffer<Data>& buf)
50-
: Thread(name, config), buf_(buf) {}
61+
explicit NonRTThread(NaiveDoubleBuffer<Data>& buf)
62+
: Thread("NonRTThread", CreateConfig()),
63+
buf_(buf) {}
5164

5265
protected:
5366
void Run() final {
@@ -58,32 +71,33 @@ class NonRTThread : public Thread {
5871
std::this_thread::sleep_for(std::chrono::milliseconds(500));
5972
}
6073
}
74+
75+
private:
76+
static cactus_rt::ThreadConfig CreateConfig() {
77+
cactus_rt::CyclicThreadConfig rt_thread_config;
78+
rt_thread_config.SetOtherScheduler(0 /* niceness */);
79+
return rt_thread_config;
80+
}
6181
};
6282

83+
// Trivial demonstration of the double buffer.
6384
void TrivialDemo() {
64-
// Trivial demonstration that the double buffer does work..
6585
NaiveDoubleBuffer<int> buf;
6686
buf.Write(2);
6787
auto a = buf.SwapAndRead();
6888
std::cout << "a is " << a << std::endl;
6989
}
7090

91+
// The actual application running.
7192
void ThreadedDemo() {
72-
cactus_rt::CyclicThreadConfig rt_thread_config;
73-
rt_thread_config.period_ns = 1'000'000;
74-
rt_thread_config.SetFifoScheduler(80 /* priority */);
75-
76-
cactus_rt::CyclicThreadConfig non_rt_thread_config;
77-
non_rt_thread_config.SetOtherScheduler(0 /* niceness */);
78-
7993
// The double buffer is shared between the two threads, so we pass a reference
8094
// into the thread and maintain the object lifetime to this function.
8195
NaiveDoubleBuffer<Data> buf;
8296

8397
App app;
8498

85-
auto rt_thread = app.CreateThread<RTThread>("RTThread", rt_thread_config, buf);
86-
auto non_rt_thread = app.CreateThread<NonRTThread>("NonRTThread", non_rt_thread_config, buf);
99+
auto rt_thread = app.CreateThread<RTThread>(buf);
100+
auto non_rt_thread = app.CreateThread<NonRTThread>(buf);
87101

88102
constexpr unsigned int time = 10;
89103
app.Start();

Diff for: examples/signal_handling_example/CMakeLists.txt

-10
This file was deleted.

Diff for: examples/signal_handling_example/README.md

-4
This file was deleted.

Diff for: examples/signal_handling_example/main.cc

-57
This file was deleted.

Diff for: examples/simple_example/README.md

+9-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,12 @@
11
`simple_example`
22
================
33

4-
The simplest real-time example to get started with.
4+
This gives you the most basic scaffolding to get started with in a 1000-Hz
5+
real-time application without any bells and whistles.
6+
7+
To run this:
8+
9+
```bash
10+
$ make debug
11+
$ sudo build/debug/examples/simple_example/rt_simple_example
12+
```

Diff for: examples/simple_example/main.cc

+18-10
Original file line numberDiff line numberDiff line change
@@ -29,14 +29,16 @@ class ExampleRTThread : public CyclicThread {
2929
* @brief This methods runs every loop, which for this particular example is every 1ms.
3030
*
3131
* @param elapsed_ns The number of nanoseconds elapsed since the App::Start was called.
32-
* @return true if you want the thread to stop
33-
* @return false if you want to thread to continue
32+
* @return LoopControl::Continue if you want the thread to continue
33+
* @return LoopControl::Stop if you want to thread to stop
3434
*/
3535
LoopControl Loop(int64_t elapsed_ns) noexcept final {
3636
// Code written in this function executes every 1 ms.
3737

3838
// This demonstrates the usage of the quill logger. This emits a log message every 1s.
3939
LOG_INFO_LIMIT(std::chrono::seconds(1), Logger(), "Looping for {}", std::chrono::nanoseconds(elapsed_ns));
40+
41+
// Return LoopControl::Stop if you want the thread to stop.
4042
return LoopControl::Continue;
4143
}
4244

@@ -62,24 +64,30 @@ class ExampleRTThread : public CyclicThread {
6264
};
6365

6466
int main() {
67+
// Sets up the signal handlers for SIGINT and SIGTERM (by default).
68+
cactus_rt::SetUpTerminationSignalHandler();
69+
6570
// We first create cactus_rt App object.
6671
App app;
6772

68-
// We then create a thread object.
73+
// We then create a thread object. Threads should always be created via the
74+
// App::CreateThread factory method.
6975
auto thread = app.CreateThread<ExampleRTThread>();
7076

71-
constexpr unsigned int time = 5;
72-
std::cout << "Testing RT loop for " << time << " seconds.\n";
73-
7477
// Start the application, which starts all the registered threads (any thread
7578
// passed to App::RegisterThread) in the order they are registered.
7679
app.Start();
7780

78-
// We let the application run for 5 seconds.
79-
std::this_thread::sleep_for(std::chrono::seconds(time));
81+
std::cout << "App started\n";
82+
83+
// This function blocks until SIGINT or SIGTERM are received.
84+
cactus_rt::WaitForAndHandleTerminationSignal();
85+
86+
std::cout << "Caught signal, requesting stop...\n";
8087

81-
// We ask the application to stop, which stops all registered threads in the
82-
// order they are registered.
88+
// We ask the application to stop, which stops all threads in the order they
89+
// are created. If you want the application to run indefinitely, remove this
90+
// line.
8391
app.RequestStop();
8492

8593
// We wait until all threads registered are done here.

Diff for: examples/tracing_example/README.md

+32-5
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,37 @@
1-
Tracing example
2-
===============
1+
`tracing_example`
2+
=================
33

4-
Designed to show off how to trace a real-time application.
4+
Designed to show off how to trace a real-time application with the
5+
real-time-safe tracing system.
56

67
Features demonstrated:
78

8-
1. Enable tracing.
9-
2. Trace custom spans in the application.
9+
1. Enable and disable tracing dynamically.
10+
2. Trace custom code in your application with spans.
1011
3. Setting the output file location.
12+
13+
To run this:
14+
15+
```bash
16+
$ make debug
17+
$ sudo build/debug/examples/tracing_example/rt_tracing_example
18+
```
19+
20+
To visualize the trace, go to https://cactusdynamics.github.io/perfetto (or
21+
https://ui.perfetto.dev which doesn't have as much bells and whistles) and load
22+
the trace in `build/data1.perfetto` and `build/data2.perfetto`.
23+
24+
You should be able to see something like this:
25+
26+
![image](./perfetto-timeline1.png)
27+
28+
By clicking on the `Loop` slice, filtering and sorting with the table that pops
29+
up, then clicking on the ID of the longest `Loop` in the table will bring you to
30+
the following view:
31+
32+
![image](./perfetto-timeline2.png)
33+
34+
Clicking on the _Latency_ tab on the side bar and selecting the right span will
35+
get you something like the following:
36+
37+
![image](./perfetto-hist.png)

0 commit comments

Comments
 (0)