Skip to content

Commit

Permalink
Merge pull request #134 from cactusdynamics/examples-update
Browse files Browse the repository at this point in the history
Batch 1 of example updates
  • Loading branch information
shuhaowu authored Sep 1, 2024
2 parents 4450f9c + f88257c commit 239944a
Show file tree
Hide file tree
Showing 20 changed files with 207 additions and 167 deletions.
3 changes: 1 addition & 2 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -183,11 +183,10 @@ if(${CMAKE_PROJECT_NAME} STREQUAL ${PROJECT_NAME})

if (ENABLE_EXAMPLES)
message(STATUS "Building example programs. Turn it off via ENABLE_EXAMPLES=OFF")
add_subdirectory(examples/lockless_example)
add_subdirectory(examples/lockless_examples)
add_subdirectory(examples/logging_example)
add_subdirectory(examples/message_passing_example)
add_subdirectory(examples/mutex_example)
add_subdirectory(examples/signal_handling_example)
add_subdirectory(examples/simple_deadline_example)
add_subdirectory(examples/simple_example)
add_subdirectory(examples/random_example)
Expand Down
20 changes: 10 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,22 +29,22 @@ writing a real-time Linux application. Some key features are:
Examples
--------

See each example's README for more details on what they do.

* [`simple_example`](examples/simple_example/): The most basic example showing
a single real-time looping thread.
* [`signal_handling_example`](examples/signal_handling_example/): Same as
`simple_example`, except the program respond to SIGTERM and SIGINT and quit
upon receiving the signal.
* [`logging_example`](examples/logging_example/): Demonstrates setting up custom
logging configuration via `cactus_rt::App`.
a single real-time looping thread running at 1000 Hz.
* [`tracing_example`](examples/tracing_example/): This demonstrates how to use
the real-time-safe tracing system built into cactus-rt. This is probably a
good thing to undrestand immediately after the above example.
* [`mutex_example`](examples/mutex_example/): Demonstrates the usage of
priority-inheritence mutex (`cactus_rt::mutex`) to pass data between real-time
and non-real-time threads.
and non-real-time threads via the implementation of a mutex-based double
buffer.
* [`logging_example`](examples/logging_example/): Demonstrates setting up custom
logging configuration via `cactus_rt::App`.

* [`simple_deadline_example`](examples/simple_deadline_example/): Same as
`simple_example`, except it uses `SCHED_DEADLINE` as opposed to `SCHED_FIFO`.
This is for a more advanced use case.
* [`tracing_example`](examples/tracing_example/): Shows how to dynamically start and stop tracing, as well as trace custom application functions.

* [`tracing_example_no_rt`](examples/tracing_example_no_rt/): Shows how to using the tracing library in `cactus_rt` without using `cactus_rt::App`.


Expand Down
10 changes: 0 additions & 10 deletions examples/lockless_example/CMakeLists.txt

This file was deleted.

10 changes: 10 additions & 0 deletions examples/lockless_examples/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
add_executable(rt_lockless_realtime_read_example
realtime_read.cc
)

target_link_libraries(rt_lockless_realtime_read_example
PRIVATE
cactus_rt
)

setup_cactus_rt_target_options(rt_lockless_realtime_read_example)
3 changes: 3 additions & 0 deletions examples/lockless_examples/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Lockless examples
=================

File renamed without changes.
9 changes: 6 additions & 3 deletions examples/mutex_example/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,17 @@
This program shows the usage of sharing data between an RT and a non-RT thread
via the `cactus_rt::mutex`, which is a priority-inheritence mutex compatible
with the [`Lockable`](https://en.cppreference.com/w/cpp/named_req/Lockable)
interface.
interface. This means you can use this `mutex` just like you would a normal
mutex from STL and expect that priority inheritance is enabled on it.

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

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

To run:

```bash
Expand Down
7 changes: 5 additions & 2 deletions examples/mutex_example/double_buffer.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@

#include <cactus_rt/mutex.h>

#include <cstdint>
#include <mutex>

/**
Expand All @@ -12,7 +11,11 @@
* Also only uses a single mutex so reads, writes, and swaps contend on a
* single lock, which is no good for performance.
*
* Realistically, you would implement this in a lock-free manner, like this:
* cactus-rt already has something for this use case (real-time thread writes
* and non-real-time thread reads) implemented via the
* cactus_rt::experimental::lockless::spsc::RealtimeWritableValue.
*
* There could also be alternative implementations like:
* https://stackoverflow.com/questions/23666069/single-producer-single-consumer-data-structure-with-double-buffer-in-c
*/
template <typename T>
Expand Down
42 changes: 28 additions & 14 deletions examples/mutex_example/main.cc
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ using cactus_rt::App;
using cactus_rt::CyclicThread;
using cactus_rt::Thread;

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

public:
explicit RTThread(const char* name, cactus_rt::CyclicThreadConfig config, NaiveDoubleBuffer<Data>& buf)
: CyclicThread(name, config),
explicit RTThread(NaiveDoubleBuffer<Data>& buf)
: CyclicThread("RTThread", CreateConfig()),
buf_(buf) {}

protected:
Expand All @@ -40,14 +43,24 @@ class RTThread : public CyclicThread {

return LoopControl::Continue;
}

private:
static cactus_rt::CyclicThreadConfig CreateConfig() {
cactus_rt::CyclicThreadConfig thread_config;
thread_config.period_ns = 1'000'000;
thread_config.SetFifoScheduler(80);

return thread_config;
}
};

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

public:
explicit NonRTThread(const char* name, cactus_rt::CyclicThreadConfig config, NaiveDoubleBuffer<Data>& buf)
: Thread(name, config), buf_(buf) {}
explicit NonRTThread(NaiveDoubleBuffer<Data>& buf)
: Thread("NonRTThread", CreateConfig()),
buf_(buf) {}

protected:
void Run() final {
Expand All @@ -58,32 +71,33 @@ class NonRTThread : public Thread {
std::this_thread::sleep_for(std::chrono::milliseconds(500));
}
}

private:
static cactus_rt::ThreadConfig CreateConfig() {
cactus_rt::CyclicThreadConfig rt_thread_config;
rt_thread_config.SetOtherScheduler(0 /* niceness */);
return rt_thread_config;
}
};

// Trivial demonstration of the double buffer.
void TrivialDemo() {
// Trivial demonstration that the double buffer does work..
NaiveDoubleBuffer<int> buf;
buf.Write(2);
auto a = buf.SwapAndRead();
std::cout << "a is " << a << std::endl;
}

// The actual application running.
void ThreadedDemo() {
cactus_rt::CyclicThreadConfig rt_thread_config;
rt_thread_config.period_ns = 1'000'000;
rt_thread_config.SetFifoScheduler(80 /* priority */);

cactus_rt::CyclicThreadConfig non_rt_thread_config;
non_rt_thread_config.SetOtherScheduler(0 /* niceness */);

// The double buffer is shared between the two threads, so we pass a reference
// into the thread and maintain the object lifetime to this function.
NaiveDoubleBuffer<Data> buf;

App app;

auto rt_thread = app.CreateThread<RTThread>("RTThread", rt_thread_config, buf);
auto non_rt_thread = app.CreateThread<NonRTThread>("NonRTThread", non_rt_thread_config, buf);
auto rt_thread = app.CreateThread<RTThread>(buf);
auto non_rt_thread = app.CreateThread<NonRTThread>(buf);

constexpr unsigned int time = 10;
app.Start();
Expand Down
10 changes: 0 additions & 10 deletions examples/signal_handling_example/CMakeLists.txt

This file was deleted.

4 changes: 0 additions & 4 deletions examples/signal_handling_example/README.md

This file was deleted.

57 changes: 0 additions & 57 deletions examples/signal_handling_example/main.cc

This file was deleted.

10 changes: 9 additions & 1 deletion examples/simple_example/README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,12 @@
`simple_example`
================

The simplest real-time example to get started with.
This gives you the most basic scaffolding to get started with in a 1000-Hz
real-time application without any bells and whistles.

To run this:

```bash
$ make debug
$ sudo build/debug/examples/simple_example/rt_simple_example
```
28 changes: 18 additions & 10 deletions examples/simple_example/main.cc
Original file line number Diff line number Diff line change
Expand Up @@ -29,14 +29,16 @@ class ExampleRTThread : public CyclicThread {
* @brief This methods runs every loop, which for this particular example is every 1ms.
*
* @param elapsed_ns The number of nanoseconds elapsed since the App::Start was called.
* @return true if you want the thread to stop
* @return false if you want to thread to continue
* @return LoopControl::Continue if you want the thread to continue
* @return LoopControl::Stop if you want to thread to stop
*/
LoopControl Loop(int64_t elapsed_ns) noexcept final {
// Code written in this function executes every 1 ms.

// This demonstrates the usage of the quill logger. This emits a log message every 1s.
LOG_INFO_LIMIT(std::chrono::seconds(1), Logger(), "Looping for {}", std::chrono::nanoseconds(elapsed_ns));

// Return LoopControl::Stop if you want the thread to stop.
return LoopControl::Continue;
}

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

int main() {
// Sets up the signal handlers for SIGINT and SIGTERM (by default).
cactus_rt::SetUpTerminationSignalHandler();

// We first create cactus_rt App object.
App app;

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

constexpr unsigned int time = 5;
std::cout << "Testing RT loop for " << time << " seconds.\n";

// Start the application, which starts all the registered threads (any thread
// passed to App::RegisterThread) in the order they are registered.
app.Start();

// We let the application run for 5 seconds.
std::this_thread::sleep_for(std::chrono::seconds(time));
std::cout << "App started\n";

// This function blocks until SIGINT or SIGTERM are received.
cactus_rt::WaitForAndHandleTerminationSignal();

std::cout << "Caught signal, requesting stop...\n";

// We ask the application to stop, which stops all registered threads in the
// order they are registered.
// We ask the application to stop, which stops all threads in the order they
// are created. If you want the application to run indefinitely, remove this
// line.
app.RequestStop();

// We wait until all threads registered are done here.
Expand Down
37 changes: 32 additions & 5 deletions examples/tracing_example/README.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,37 @@
Tracing example
===============
`tracing_example`
=================

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

Features demonstrated:

1. Enable tracing.
2. Trace custom spans in the application.
1. Enable and disable tracing dynamically.
2. Trace custom code in your application with spans.
3. Setting the output file location.

To run this:

```bash
$ make debug
$ sudo build/debug/examples/tracing_example/rt_tracing_example
```

To visualize the trace, go to https://cactusdynamics.github.io/perfetto (or
https://ui.perfetto.dev which doesn't have as much bells and whistles) and load
the trace in `build/data1.perfetto` and `build/data2.perfetto`.

You should be able to see something like this:

![image](./perfetto-timeline1.png)

By clicking on the `Loop` slice, filtering and sorting with the table that pops
up, then clicking on the ID of the longest `Loop` in the table will bring you to
the following view:

![image](./perfetto-timeline2.png)

Clicking on the _Latency_ tab on the side bar and selecting the right span will
get you something like the following:

![image](./perfetto-hist.png)
Loading

0 comments on commit 239944a

Please sign in to comment.