Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update examples with readme and latest style #141

Merged
merged 7 commits into from
Oct 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 16 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ writing a real-time Linux application. Some key features are:
Examples
--------

Core examples that show you how to use all the facilities of cactus-rt:

* [`simple_example`](examples/simple_example/): The most basic example showing
a single real-time looping thread running at 1000 Hz.
* [`tracing_example`](examples/tracing_example/): This demonstrates how to use
Expand All @@ -38,14 +40,26 @@ Examples
priority-inheritence mutex (`cactus_rt::mutex`) to pass data between real-time
and non-real-time threads via the implementation of a mutex-based double
buffer.
* [`lockless_examples`](examples/lockless_examples/): Demonstrates the usage of
lockless data structures built into cactus-rt to safely pass data to and from
the real-time thread.
* [`logging_example`](examples/logging_example/): Demonstrates setting up custom
logging configuration via `cactus_rt::App`.
* [`message_passing_example`](examples/message_passing_example/): A simplified
example of a real robotics application consists of two threads: a real-time
thread that is generating metrics and a non-real-time data logger that logs
the metrics to disk.
* [`ros2`](examples/ros2/): Shows how to use cactus-rt with ROS2.

Some examples showing optional/advanced features/usage patterns of cactus-rt.

* [`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_no_rt`](examples/tracing_example_no_rt/): Shows how to using the tracing library in `cactus_rt` without using `cactus_rt::App`.
* [`random_example`](examples/random_example/): Shows you how to use cactus-rt's
real-time-safe random number generator.
* [`tracing_example_no_rt`](examples/tracing_example_no_rt/): Shows how to using
the tracing library in `cactus_rt` without using `cactus_rt::App`.


Installation instructions
Expand Down
7 changes: 7 additions & 0 deletions examples/lockless_examples/README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
Lockless examples
=================

`realtime_read`
---------------

This example shows you how to use the `RealtimeReadableValue` lockless data
structure. This data structure allows you to read a struct from a single
real-time thread in a wait-free manner O(1). The writer has to be a single
thread is non-real-time as the write algorithm is lock-free but not wait-free.
8 changes: 4 additions & 4 deletions examples/lockless_examples/realtime_read.cc
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ class RTThread : public CyclicThread {
Context& ctx_;
Pose current_target_pose_;

static cactus_rt::CyclicThreadConfig CreateThreadConfig() {
static cactus_rt::CyclicThreadConfig MakeConfig() {
cactus_rt::CyclicThreadConfig thread_config;
thread_config.period_ns = 1'000'000;
thread_config.cpu_affinity = std::vector<size_t>{2};
Expand All @@ -70,7 +70,7 @@ class RTThread : public CyclicThread {
}

public:
RTThread(Context& ctx) : CyclicThread("RTThread", CreateThreadConfig()), ctx_(ctx) {}
RTThread(Context& ctx) : CyclicThread("RTThread", MakeConfig()), ctx_(ctx) {}

protected:
LoopControl Loop(int64_t /*now*/) noexcept final {
Expand Down Expand Up @@ -104,14 +104,14 @@ class RTThread : public CyclicThread {
class NonRTThread : public Thread {
Context& ctx_;

static cactus_rt::ThreadConfig CreateThreadConfig() {
static cactus_rt::ThreadConfig MakeConfig() {
cactus_rt::ThreadConfig thread_config;
thread_config.SetOtherScheduler();
return thread_config;
}

public:
NonRTThread(Context& ctx) : Thread("NonRTThread", CreateThreadConfig()), ctx_(ctx) {}
NonRTThread(Context& ctx) : Thread("NonRTThread", MakeConfig()), ctx_(ctx) {}

void Run() final {
ctx_.target_pose.Write(Pose(1.5, 2.5, 3.5, 4.5, 5.5, 6.5));
Expand Down
27 changes: 14 additions & 13 deletions examples/logging_example/main.cc
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,16 @@ using cactus_rt::CyclicThread;
class ExampleRTThread : public CyclicThread {
int64_t loop_counter_ = 0;

static cactus_rt::CyclicThreadConfig MakeConfig() {
cactus_rt::CyclicThreadConfig thread_config;
thread_config.period_ns = 1'000'000;
thread_config.cpu_affinity = std::vector<size_t>{2};
thread_config.SetFifoScheduler(80);
return thread_config;
}

public:
ExampleRTThread(const char* name, cactus_rt::CyclicThreadConfig config) : CyclicThread(name, config) {}
ExampleRTThread() : CyclicThread("ExampleRTThread", MakeConfig()) {}

int64_t GetLoopCounter() const {
return loop_counter_;
Expand All @@ -26,33 +34,26 @@ class ExampleRTThread : public CyclicThread {
if (loop_counter_ % 1000 == 0) {
LOG_INFO(Logger(), "Loop {}", loop_counter_);
}

LOG_INFO_LIMIT(std::chrono::milliseconds{1500}, Logger(), "Log limit: Loop {}", loop_counter_);
return LoopControl::Continue;
}
};

int main() {
cactus_rt::CyclicThreadConfig thread_config;
thread_config.period_ns = 1'000'000;
thread_config.cpu_affinity = std::vector<size_t>{2};
thread_config.SetFifoScheduler(80);

// Create a cactus_rt app configuration
cactus_rt::AppConfig app_config;

// Create a Quill logging config to configure logging
quill::Config logging_config;

// Disable strict timestamp order - this will be faster, but logs may appear out of order
logging_config.backend_thread_strict_log_timestamp_order = false;
app_config.logger_config.backend_thread_strict_log_timestamp_order = false;

// Set the background logging thread CPU affinity
logging_config.backend_thread_cpu_affinity = 1; // Different CPU than the CyclicThread CPU!
app_config.logger_config.backend_thread_cpu_affinity = 1; // Different CPU than the CyclicThread CPU!

app_config.logger_config = logging_config;
App app("LoggingExampleApp", app_config);

auto thread = app.CreateThread<ExampleRTThread>("ExampleRTThread", thread_config);
auto thread = app.CreateThread<ExampleRTThread>();

constexpr unsigned int time = 5;

std::cout << "Testing RT loop for " << time << " seconds.\n";
Expand Down
25 changes: 8 additions & 17 deletions examples/message_passing_example/data_logger_thread.cc
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ DataLogger::DataLogger(
period_ns_(period_ns),
write_data_interval_ns_(write_data_interval_ns),
queue_(kQueueCapacity),
message_count_({0, 0}) {
message_count_(CountData{0, 0}) {
file_.open(data_file_path);
if (!file_.is_open()) {
throw std::runtime_error{"failed to open data file"};
Expand Down Expand Up @@ -61,10 +61,6 @@ void DataLogger::Run() {
break;
}

// TODO: looking at the code for this, it seems safe as it just calls
// nanosleep. That said, to make it explicit that we're not using the c++
// thread library, should just refactor that code out in to Thread::sleep
// here.
std::this_thread::sleep_for(std::chrono::nanoseconds(period_ns_));
}
}
Expand All @@ -87,22 +83,17 @@ void DataLogger::WriteAndEmptyDataFromBuffer() noexcept {
}

void DataLogger::ReadAndLogMessageCount() noexcept {
const auto current_count = message_count_.load();
const auto current_count = message_count_.Read();

LOG_INFO(Logger(), "received {} messages and dropped {}", current_count.total_messages, current_count.total_messages - current_count.successful_messages);
}

// A demonstration of how to pass a small amount of data via std::atomic if it can be done in a lock free manner.
// This is called from the real-time thread. The loop is probably unnecessary
// because it must be the only thread that reads and writes to this variable..
// successful_message_count should be either 0 or 1...
// This is called from the real-time thread.
void DataLogger::IncrementMessageCount(uint32_t successful_message_count) noexcept {
auto old_count = message_count_.load();
decltype(old_count) new_count;

do {
new_count = old_count;
new_count.successful_messages += successful_message_count;
new_count.total_messages += 1;
} while (!message_count_.compare_exchange_weak(old_count, new_count));
message_count_.Modify([successful_message_count](CountData old_value) noexcept {
old_value.successful_messages += successful_message_count;
old_value.total_messages += 1;
return old_value;
});
}
5 changes: 2 additions & 3 deletions examples/message_passing_example/data_logger_thread.h
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
#ifndef CACTUS_RT_EXAMPLES_MESSAGE_PASSING_EXAMPLE_DATA_LOGGER_H_
#define CACTUS_RT_EXAMPLES_MESSAGE_PASSING_EXAMPLE_DATA_LOGGER_H_

#include <cactus_rt/experimental/lockless.h>
#include <cactus_rt/rt.h>
#include <readerwriterqueue.h>

#include <atomic>
#include <cstdint>
#include <fstream>
#include <vector>
Expand Down Expand Up @@ -38,9 +38,8 @@ class DataLogger : public Thread {
uint32_t total_messages;
};

using AtomicMessageCount = std::atomic<CountData>;
using AtomicMessageCount = cactus_rt::experimental::lockless::AtomicMessage<CountData>;
AtomicMessageCount message_count_;
static_assert(AtomicMessageCount::is_always_lock_free);

std::vector<OutputData> data_buffer_; // A simple buffer to hold the data in non-realtime thread so batch writing can occur

Expand Down
4 changes: 2 additions & 2 deletions examples/message_passing_example/rt_thread.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ class RtThread : public CyclicThread {
size_t max_iterations_;
size_t iterations_ = 0;

static cactus_rt::CyclicThreadConfig MakeRealTimeThreadConfig() {
static cactus_rt::CyclicThreadConfig MakeConfig() {
cactus_rt::CyclicThreadConfig config;
config.period_ns = 1'000'000;
config.cpu_affinity = std::vector<size_t>{2};
Expand All @@ -25,7 +25,7 @@ class RtThread : public CyclicThread {

public:
RtThread(std::shared_ptr<DataLogger> data_logger, size_t max_iterations = 30000)
: CyclicThread("RtThread", MakeRealTimeThreadConfig()),
: CyclicThread("RtThread", MakeConfig()),
data_logger_(data_logger),
max_iterations_(max_iterations) {
}
Expand Down
5 changes: 4 additions & 1 deletion examples/mutex_example/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,10 @@ 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._
_Note: a lockless version of this double buffer is implemented by the cactus-rt
framework under `cactus_rt::experimental::lockless::spsc::RealtimeWritableValue`
which doesn't require a lock. That serves as an alternative to this code without
the usage of a mutex._

To run:

Expand Down
10 changes: 6 additions & 4 deletions examples/mutex_example/main.cc
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ 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.
//
// Alternatively, you can also use RealtimeWritableValue instead of a mutex.
struct Data {
double v1 = 0.0;
double v2 = 0.0;
Expand All @@ -25,7 +27,7 @@ class RTThread : public CyclicThread {

public:
explicit RTThread(NaiveDoubleBuffer<Data>& buf)
: CyclicThread("RTThread", CreateConfig()),
: CyclicThread("RTThread", MakeConfig()),
buf_(buf) {}

protected:
Expand All @@ -45,7 +47,7 @@ class RTThread : public CyclicThread {
}

private:
static cactus_rt::CyclicThreadConfig CreateConfig() {
static cactus_rt::CyclicThreadConfig MakeConfig() {
cactus_rt::CyclicThreadConfig thread_config;
thread_config.period_ns = 1'000'000;
thread_config.SetFifoScheduler(80);
Expand All @@ -59,7 +61,7 @@ class NonRTThread : public Thread {

public:
explicit NonRTThread(NaiveDoubleBuffer<Data>& buf)
: Thread("NonRTThread", CreateConfig()),
: Thread("NonRTThread", MakeConfig()),
buf_(buf) {}

protected:
Expand All @@ -73,7 +75,7 @@ class NonRTThread : public Thread {
}

private:
static cactus_rt::ThreadConfig CreateConfig() {
static cactus_rt::ThreadConfig MakeConfig() {
cactus_rt::CyclicThreadConfig rt_thread_config;
rt_thread_config.SetOtherScheduler(0 /* niceness */);
return rt_thread_config;
Expand Down
13 changes: 13 additions & 0 deletions examples/random_example/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
`random_example`
================

Technically, [none of the random number generators in C++ are O(1)][1]. This
means there is a small (infinitesimal?) probability that it may cause real-time
problems (for some of the implementation it may be near impossible). To ensure
absolute safety at the cost of slightly non-uniform random number generation,
cactus-rt implements a O(1) random number generator with the Xorshift algorithm.
This example shows how to use it.

**WARNING: DO NOT USE THIS RNG IN SECURE CRYPTOGRAPHIC CONTEXT AS IT IS NOT PERFECTLY RANDOM**.

[1]: https://youtu.be/Tof5pRedskI?t=2514
7 changes: 5 additions & 2 deletions examples/random_example/main.cc
Original file line number Diff line number Diff line change
Expand Up @@ -28,15 +28,18 @@ class Histogram {
};

int main() {
// Generate the seed once in non-real-time code.
const uint64_t seed = std::random_device{}();
std::cout << "Seed: " << seed << "\n";

Histogram<20> hist;

cactus_rt::experimental::Xorshift64Rand rng(seed);
// Initialize the RNG state in non-real-time code.
cactus_rt::experimental::random::Xorshift64Rand rng(seed);

for (int i = 0; i < 1'000'000; i++) {
const float num = cactus_rt::experimental::RandomRealNumber(rng);
// RealNumber(rng) is always O(1).
const float num = cactus_rt::experimental::random::RealNumber(rng);
if (num >= 1.0F || num < 0.0F) {
std::cerr << "ERROR: seed = " << seed << " i = " << i << " num = " << num << " is out of range \n";
return 1;
Expand Down
Loading
Loading