13#ifndef RTCTK_DATATASK_COMPUTATIONBASE_HPP
14#define RTCTK_DATATASK_COMPUTATIONBASE_HPP
21#include <ipcq/reader.hpp>
22#include <numapp/numapolicies.hpp>
23#include <numapp/thread.hpp>
29#include <shared_mutex>
36class ComputationMonitor {
41 uint32_t last_sample_id;
43 float buffer_occupancy;
44 std::chrono::duration<double, std::micro> dur_read;
45 std::chrono::duration<double, std::micro> dur_compute;
46 std::chrono::duration<double, std::micro> dur_publish;
51 explicit ComputationMonitor(ComponentMetricsIf& metrics,
const std::string&
id)
52 : m_start_time(std::chrono::steady_clock::now()) {
57 m_pc_cycles_reg = metrics.AddCounter(
58 &m_pc_cycles,
CounterMetricInfo(
id +
"/num_cycles",
"cycles since running", base_tags,
"num_cycles"));
60 m_pc_samples_reg = metrics.AddCounter(
62 CounterMetricInfo(
id +
"/num_samples",
"samples read since running", base_tags,
"num_samples"));
64 m_pc_sample_id_reg = metrics.AddCounter(
66 CounterMetricInfo(
id +
"/last_sample_id",
"last observed sample id", base_tags,
"last_sample_id"));
68 m_pc_freq_estimate_reg =
69 metrics.AddCounter(&m_pc_freq_estimate,
71 "frequency estimate [Hz]",
77 metrics.AddCounter(&m_pc_occupancy,
79 "buffer occupancy [%]",
85 metrics.AddCounter(&m_pc_dur_read,
92 m_pc_dur_compute_reg =
93 metrics.AddCounter(&m_pc_dur_compute,
95 "compute duration [us]",
100 m_pc_dur_publish_reg =
101 metrics.AddCounter(&m_pc_dur_publish,
103 "publish duration [us]",
109 void Tick(
const Stats& stats)
noexcept {
110 using namespace std::chrono_literals;
112 auto now_time = std::chrono::steady_clock::now();
113 auto elapsed_time = now_time - m_start_time;
114 if (elapsed_time > 2s) {
115 m_start_time = now_time;
117 m_pc_cycles.Store(stats.num_cycles);
118 m_pc_samples.Store(stats.num_samples);
119 m_pc_sample_id.Store(stats.last_sample_id);
120 m_pc_freq_estimate.Store(stats.freq_estimate);
121 m_pc_occupancy.Store(stats.buffer_occupancy);
122 m_pc_dur_read.Store(stats.dur_read.count());
123 m_pc_dur_compute.Store(stats.dur_compute.count());
124 m_pc_dur_publish.Store(stats.dur_publish.count());
128 void Reset()
noexcept {
129 m_start_time = std::chrono::steady_clock::now();
130 m_pc_cycles.Store(0);
131 m_pc_samples.Store(0);
132 m_pc_sample_id.Store(0);
133 m_pc_freq_estimate.Store(0);
134 m_pc_occupancy.Store(0.0);
135 m_pc_dur_read.Store(0);
136 m_pc_dur_compute.Store(0);
137 m_pc_dur_publish.Store(0);
141 std::chrono::steady_clock::time_point m_start_time;
143 perfc::CounterI64 m_pc_cycles;
144 perfc::ScopedRegistration m_pc_cycles_reg;
146 perfc::CounterI64 m_pc_samples;
147 perfc::ScopedRegistration m_pc_samples_reg;
149 perfc::CounterI64 m_pc_sample_id;
150 perfc::ScopedRegistration m_pc_sample_id_reg;
152 perfc::CounterDouble m_pc_freq_estimate;
153 perfc::ScopedRegistration m_pc_freq_estimate_reg;
155 perfc::CounterDouble m_pc_occupancy;
156 perfc::ScopedRegistration m_pc_occupancy_reg;
158 perfc::CounterI64 m_pc_dur_read;
159 perfc::ScopedRegistration m_pc_dur_read_reg;
161 perfc::CounterI64 m_pc_dur_compute;
162 perfc::ScopedRegistration m_pc_dur_compute_reg;
164 perfc::CounterI64 m_pc_dur_publish;
165 perfc::ScopedRegistration m_pc_dur_publish_reg;
184template <
typename TopicTypeX,
typename ReaderType = ipcq::Reader<TopicTypeX>>
213 const std::string& shm_name,
216 std::chrono::milliseconds sample_timeout,
217 std::optional<numapp::NumaPolicies> thread_policies)
241 const std::string&
id,
242 const std::string& shm_name,
245 std::chrono::milliseconds sample_timeout,
246 std::optional<bool> publish_metrics,
247 std::optional<numapp::NumaPolicies> thread_policies)
249 , m_services(services)
251 , m_shm_name(shm_name)
254 , m_chunk_size(
std::max(1l, (
std::chrono::milliseconds(500) / sample_timeout)))
255 , m_sample_timeout(sample_timeout)
256 , m_publish_metrics(publish_metrics.value_or(true))
257 , m_thread_policies(
std::move(thread_policies))
258 , m_command(Command::
IDLE)
280 m_to_read.store(value, std::memory_order_relaxed);
287 return m_to_read.load(std::memory_order_relaxed);
297 m_to_skip.store(value, std::memory_order_relaxed);
304 return m_to_skip.load(std::memory_order_relaxed);
314 m_exception =
nullptr;
315 m_command = Command::IDLE;
316 m_thread = numapp::MakeThread(
"Computation",
317 m_thread_policies.value_or(numapp::NumaPolicies()),
318 &ComputationBase::Work,
321 using namespace std::chrono_literals;
322 std::this_thread::sleep_for(10ms);
338 m_command = Command::EXIT;
339 if (m_thread.joinable()) {
353 void Run(std::optional<size_t> cycles = std::nullopt) {
354 m_cycles_to_run = cycles.value_or(0);
355 m_command = Command::RUN;
375 void AwaitIdle(std::optional<std::chrono::milliseconds> poll_interval = std::nullopt) {
377 using namespace std::chrono_literals;
378 std::this_thread::sleep_for(poll_interval.value_or(10ms));
397 void RunOnceSync(std::optional<std::chrono::milliseconds> poll_interval = std::nullopt) {
408 m_command = Command::IDLE;
419 auto state = m_state.load(std::memory_order_relaxed);
420 auto command = m_command.load(std::memory_order_relaxed);
422 if (state ==
State::IDLE and command == Command::RUN) {
437 return m_cycles.load(std::memory_order_relaxed);
448 auto lock = std::shared_lock{m_exception_mutex};
450 std::rethrow_exception(m_exception);
495 using namespace std::chrono_literals;
497 const std::error_code ok{};
498 std::error_code ret = ok;
502 m_cycles.store(cycles, std::memory_order_relaxed);
504 auto t0 = std::chrono::steady_clock::now();
511 auto reader = ReaderType::MakeReader(m_shm_name.c_str(), 30s);
513 std::unique_ptr<ComputationMonitor> monitor;
514 if (m_publish_metrics) {
522 Command command = m_command.load(std::memory_order_relaxed);
523 if (command == Command::EXIT) {
527 m_state.store(
State::OFF, std::memory_order_relaxed);
529 }
else if (command == Command::IDLE) {
532 m_cycles.store(cycles, std::memory_order_relaxed);
536 m_state.store(
State::IDLE, std::memory_order_relaxed);
538 std::this_thread::sleep_for(10ms);
540 auto prev_state = m_state.exchange(
State::RUNNING, std::memory_order_relaxed);
547 std::format(
"[{}] SHM Reset failed: {}", m_id, ret.message()));
551 auto occupancy = Occupancy(reader);
553 size_t to_read = m_to_read.load(std::memory_order_relaxed);
554 size_t to_skip = m_to_skip.load(std::memory_order_relaxed);
558 size_t sample_idx = 0;
559 uint32_t last_sample_id = 0;
560 t0 = std::chrono::steady_clock::now();
561 ret = Read(reader, to_read, [&](
const TopicType& sample) {
563 last_sample_id = sample.sample_id;
570 std::format(
"[{}] Work() Read: cycle {}, buffer occupancy {}",
575 std::format(
"[{}] SHM Read failed: {}", m_id, ret.message()));
578 if (m_command.load(std::memory_order_relaxed) != Command::RUN) {
584 t1 = std::chrono::steady_clock::now();
586 t2 = std::chrono::steady_clock::now();
588 t3 = std::chrono::steady_clock::now();
590 ret = Skip(reader, to_skip);
594 std::format(
"[{}] Work() Skip: cycle {}, buffer occupancy {}",
599 std::format(
"[{}] SHM Skip failed: {}", m_id, ret.message()));
602 t4 = std::chrono::steady_clock::now();
605 m_cycles.store(cycles, std::memory_order_relaxed);
612 (to_read + to_skip) / std::chrono::duration<float>(t4 - t0).count(),
620 if (
size_t c2r = m_cycles_to_run.load(); c2r != 0 and c2r == cycles) {
627 std::scoped_lock lock(m_exception_mutex);
628 m_exception = std::current_exception();
632 template <
typename Operation>
633 std::error_code Read(ReaderType& reader,
size_t to_read,
const Operation& op) {
634 using namespace std::chrono;
637 const std::error_code ok;
638 std::pair<std::error_code, size_t> ret;
639 milliseconds time_elapsed{0};
640 auto time_start = steady_clock::now();
643 if (m_command.load(std::memory_order_relaxed) != Command::RUN) {
648 size_t to_read_now = std::min(m_chunk_size, to_read - read);
650 ret = reader.Read(op, to_read_now, m_sample_timeout);
651 if (ret.first != ok) {
655 if (read == to_read) {
659 time_elapsed = duration_cast<milliseconds>(steady_clock::now() - time_start);
660 if (time_elapsed > m_sample_timeout * to_read) {
661 return std::make_error_code(std::errc::timed_out);
666 std::error_code Skip(ReaderType& reader,
size_t to_skip) {
667 using namespace std::chrono;
670 const std::error_code ok;
671 std::pair<std::error_code, size_t> ret;
672 milliseconds time_elapsed{0};
673 auto time_start = steady_clock::now();
676 if ((to_skip == 0) or (m_command.load(std::memory_order_relaxed) != Command::RUN)) {
681 size_t to_skip_now = std::min(m_chunk_size, to_skip - skipped);
683 ret = reader.Skip(to_skip_now, m_sample_timeout);
684 if (ret.first != ok) {
687 skipped += ret.second;
688 if (skipped == to_skip) {
692 time_elapsed = duration_cast<milliseconds>(steady_clock::now() - time_start);
693 if (time_elapsed > m_sample_timeout * to_skip) {
694 return std::make_error_code(std::errc::timed_out);
699 std::error_code Reset(ReaderType& reader) {
700 auto ret = reader.Reset();
701 if (ret == ipcq::Error::WouldBlock) {
707 float Occupancy(ReaderType& reader) {
708 return 100.0 * (
static_cast<float>(reader.NumAvailable()) / reader.Size());
712 enum class Command : uint8_t { RUN,
IDLE, EXIT };
714 log4cplus::Logger& m_logger;
717 std::string m_shm_name;
718 std::atomic<size_t> m_to_read;
719 std::atomic<size_t> m_to_skip;
721 std::chrono::milliseconds m_sample_timeout;
722 bool m_publish_metrics;
723 std::optional<numapp::NumaPolicies> m_thread_policies;
724 std::atomic<Command> m_command;
725 std::atomic<State> m_state;
726 std::atomic<size_t> m_cycles_to_run;
727 std::atomic<size_t> m_cycles;
728 std::exception_ptr m_exception =
nullptr;
729 std::shared_mutex m_exception_mutex;
730 std::thread m_thread;
The RtctkException class is the base class for all Rtctk exceptions.
Definition exceptions.hpp:213
Component metrics interface.
Definition componentMetricsIf.hpp:164
Defines auxiliary information associated with each counter registered with ComponentMetricsIf.
Definition componentMetricsIf.hpp:49
Helper class for passing tags in Telegraf.
Definition influxTagMap.hpp:27
Container class that holds services of any type.
Definition serviceContainer.hpp:39
void Run(std::optional< size_t > cycles=std::nullopt)
Commands the worker thread to perform number of computation cycles (async method).
Definition computationBase.hpp:353
void Spawn()
Spawns the worker thread (sync method).
Definition computationBase.hpp:312
virtual void OnThreadStart()
Optional user-hook called after worker thread started and reader was created.
Definition computationBase.hpp:460
rtctk::componentFramework::ServiceContainer ServiceContainer
Definition computationBase.hpp:199
virtual void OnCycleStart(size_t to_read)
Optional user-hook called immediately before a read-cycle from SHM starts.
Definition computationBase.hpp:468
void Idle()
Commands the worker thread to stop performing computation cycles (async method).
Definition computationBase.hpp:407
rtctk::componentFramework::ComponentMetricsIf ComponentMetricsIf
Definition computationBase.hpp:200
void AwaitIdle(std::optional< std::chrono::milliseconds > poll_interval=std::nullopt)
Blocks until the requested of cycles are completed or an error occurs.
Definition computationBase.hpp:375
void SetSamplesToSkip(size_t value)
Sets the number of samples to skip.
Definition computationBase.hpp:296
size_t GetSamplesToSkip() const
Gets the number of samples to skip.
Definition computationBase.hpp:303
void CheckErrors()
Checks for errors in the worker thread and rethrows them to the caller.
Definition computationBase.hpp:447
void RunOnceSync(std::optional< std::chrono::milliseconds > poll_interval=std::nullopt)
Commands the worker thread to perform a single computation cycle (sync method).
Definition computationBase.hpp:397
virtual ~ComputationBase()=default
Destructor.
size_t GetCycles() const
Retrieves number of computation cycles performed since running.
Definition computationBase.hpp:436
void SetSamplesToRead(size_t value)
Sets the number of samples to read.
Definition computationBase.hpp:279
void Join()
Terminates the worker thread (sync method).
Definition computationBase.hpp:337
State
Current state of the worker thread.
Definition computationBase.hpp:192
@ RUNNING
Definition computationBase.hpp:193
@ OFF
Definition computationBase.hpp:196
@ IDLE
Definition computationBase.hpp:194
@ ERROR
Definition computationBase.hpp:195
virtual void Compute()=0
User-hook to perform a computation cycle.
virtual void CopyData(size_t sample_idx, const TopicType &sample) noexcept=0
User-hook used to copy data of a single computation cycle into user-owned sample buffer.
TopicTypeX TopicType
Definition computationBase.hpp:187
size_t GetSamplesToRead() const
Gets the number of samples to read.
Definition computationBase.hpp:286
virtual void Publish()=0
User-hook to publish a computation result.
void RunOnce()
Commands the worker thread to perform a single computation cycle (async method).
Definition computationBase.hpp:361
State GetState() const
Returns the current state of the worker thread.
Definition computationBase.hpp:418
ComputationBase(ServiceContainer &services, const std::string &id, const std::string &shm_name, size_t to_read, size_t to_skip, std::chrono::milliseconds sample_timeout, std::optional< bool > publish_metrics, std::optional< numapp::NumaPolicies > thread_policies)
Constructor.
Definition computationBase.hpp:240
ComputationBase(ServiceContainer &services, const std::string &shm_name, size_t to_read, size_t to_skip, std::chrono::milliseconds sample_timeout, std::optional< numapp::NumaPolicies > thread_policies)
Constructor.
Definition computationBase.hpp:212
Header file for ComponentMetricsIf.
log4cplus::Logger & GetLogger(const std::string &name="app")
Get handle to a specific logger.
Definition logger.cpp:192
Provides macros and utilities for exception handling.
Logging Support Library based on log4cplus.
Definition commandReplier.cpp:22
Definition computationBase.hpp:32
Definition commandReplier.cpp:22
Definition ddsSub.hpp:156
A container that can hold any type of service.
static constexpr std::string PERCENT
Definition componentMetricsIf.hpp:257
static constexpr std::string MICRO_SECONDS
Definition componentMetricsIf.hpp:258
static constexpr std::string HERTZ
Definition componentMetricsIf.hpp:259