12#ifndef RTCTK_DATATASK_COMPUTATIONBASE_HPP
13#define RTCTK_DATATASK_COMPUTATIONBASE_HPP
20#include <ipcq/reader.hpp>
21#include <numapp/numapolicies.hpp>
22#include <numapp/thread.hpp>
28#include <shared_mutex>
35class ComputationMonitor {
40 uint32_t last_sample_id;
42 float buffer_occupancy;
43 std::chrono::duration<double, std::micro> dur_read;
44 std::chrono::duration<double, std::micro> dur_compute;
45 std::chrono::duration<double, std::micro> dur_publish;
50 explicit ComputationMonitor(ComponentMetricsIf& metrics,
const std::string&
id)
51 : m_start_time(std::chrono::steady_clock::now()) {
56 m_pc_cycles_reg = metrics.AddCounter(
58 CounterMetricInfo(
id +
"/num_cycles",
"cycles since running", base_tags,
"num_cycles"));
60 m_pc_samples_reg = metrics.AddCounter(
63 id +
"/num_samples",
"samples read since running", base_tags,
"num_samples"));
65 m_pc_sample_id_reg = metrics.AddCounter(
68 id +
"/last_sample_id",
"last observed sample id", base_tags,
"last_sample_id"));
70 m_pc_freq_estimate_reg = metrics.AddCounter(&m_pc_freq_estimate,
72 "frequency estimate [Hz]",
77 m_pc_occupancy_reg = metrics.AddCounter(&m_pc_occupancy,
79 "buffer occupancy [%]",
84 m_pc_dur_read_reg = metrics.AddCounter(&m_pc_dur_read,
91 m_pc_dur_compute_reg = metrics.AddCounter(&m_pc_dur_compute,
93 "compute duration [us]",
98 m_pc_dur_publish_reg = metrics.AddCounter(&m_pc_dur_publish,
100 "publish duration [us]",
106 void Tick(
const Stats& stats)
noexcept {
107 using namespace std::chrono_literals;
109 auto now_time = std::chrono::steady_clock::now();
110 auto elapsed_time = now_time - m_start_time;
111 if (elapsed_time > 2s) {
112 m_start_time = now_time;
114 m_pc_cycles.Store(stats.num_cycles);
115 m_pc_samples.Store(stats.num_samples);
116 m_pc_sample_id.Store(stats.last_sample_id);
117 m_pc_freq_estimate.Store(stats.freq_estimate);
118 m_pc_occupancy.Store(stats.buffer_occupancy);
119 m_pc_dur_read.Store(stats.dur_read.count());
120 m_pc_dur_compute.Store(stats.dur_compute.count());
121 m_pc_dur_publish.Store(stats.dur_publish.count());
125 void Reset()
noexcept {
126 m_start_time = std::chrono::steady_clock::now();
127 m_pc_cycles.Store(0);
128 m_pc_samples.Store(0);
129 m_pc_sample_id.Store(0);
130 m_pc_freq_estimate.Store(0);
131 m_pc_occupancy.Store(0.0);
132 m_pc_dur_read.Store(0);
133 m_pc_dur_compute.Store(0);
134 m_pc_dur_publish.Store(0);
138 std::chrono::steady_clock::time_point m_start_time;
140 perfc::CounterI64 m_pc_cycles;
141 perfc::ScopedRegistration m_pc_cycles_reg;
143 perfc::CounterI64 m_pc_samples;
144 perfc::ScopedRegistration m_pc_samples_reg;
146 perfc::CounterI64 m_pc_sample_id;
147 perfc::ScopedRegistration m_pc_sample_id_reg;
149 perfc::CounterDouble m_pc_freq_estimate;
150 perfc::ScopedRegistration m_pc_freq_estimate_reg;
152 perfc::CounterDouble m_pc_occupancy;
153 perfc::ScopedRegistration m_pc_occupancy_reg;
155 perfc::CounterI64 m_pc_dur_read;
156 perfc::ScopedRegistration m_pc_dur_read_reg;
158 perfc::CounterI64 m_pc_dur_compute;
159 perfc::ScopedRegistration m_pc_dur_compute_reg;
161 perfc::CounterI64 m_pc_dur_publish;
162 perfc::ScopedRegistration m_pc_dur_publish_reg;
181template <
typename TopicTypeX,
typename ReaderType = ipcq::Reader<TopicTypeX>>
210 const std::string& shm_name,
213 std::chrono::milliseconds sample_timeout,
214 std::optional<numapp::NumaPolicies> thread_policies)
238 const std::string&
id,
239 const std::string& shm_name,
242 std::chrono::milliseconds sample_timeout,
243 std::optional<bool> publish_metrics,
244 std::optional<numapp::NumaPolicies> thread_policies)
246 , m_services(services)
248 , m_shm_name(shm_name)
251 , m_chunk_size(
std::max(1l, (
std::chrono::milliseconds(500) / sample_timeout)))
252 , m_sample_timeout(sample_timeout)
253 , m_publish_metrics(publish_metrics.value_or(true))
254 , m_thread_policies(
std::move(thread_policies))
255 , m_command(Command::
IDLE)
277 m_to_read.store(value, std::memory_order_relaxed);
284 return m_to_read.load(std::memory_order_relaxed);
294 m_to_skip.store(value, std::memory_order_relaxed);
301 return m_to_skip.load(std::memory_order_relaxed);
311 m_exception =
nullptr;
312 m_command = Command::IDLE;
313 m_thread = numapp::MakeThread(
"Computation",
314 m_thread_policies.value_or(numapp::NumaPolicies()),
315 &ComputationBase::Work,
318 using namespace std::chrono_literals;
319 std::this_thread::sleep_for(10ms);
335 m_command = Command::EXIT;
336 if (m_thread.joinable()) {
350 void Run(std::optional<size_t> cycles = std::nullopt) {
351 m_cycles_to_run = cycles.value_or(0);
352 m_command = Command::RUN;
372 void AwaitIdle(std::optional<std::chrono::milliseconds> poll_interval = std::nullopt) {
374 using namespace std::chrono_literals;
375 std::this_thread::sleep_for(poll_interval.value_or(10ms));
394 void RunOnceSync(std::optional<std::chrono::milliseconds> poll_interval = std::nullopt) {
405 m_command = Command::IDLE;
416 auto state = m_state.load(std::memory_order_relaxed);
417 auto command = m_command.load(std::memory_order_relaxed);
419 if (state ==
State::IDLE and command == Command::RUN) {
434 return m_cycles.load(std::memory_order_relaxed);
445 auto lock = std::shared_lock{m_exception_mutex};
447 std::rethrow_exception(m_exception);
492 using namespace std::chrono_literals;
494 const std::error_code ok{};
495 std::error_code ret = ok;
500 m_cycles.store(cycles, std::memory_order_relaxed);
502 auto t0 = std::chrono::steady_clock::now();
509 auto reader = ReaderType::MakeReader(m_shm_name.c_str(), 30s);
511 std::unique_ptr<ComputationMonitor> monitor;
512 if (m_publish_metrics) {
520 Command command = m_command.load(std::memory_order_relaxed);
521 if (command == Command::EXIT) {
525 m_state.store(
State::OFF, std::memory_order_relaxed);
527 }
else if (command == Command::IDLE) {
530 m_cycles.store(cycles, std::memory_order_relaxed);
534 m_state.store(
State::IDLE, std::memory_order_relaxed);
536 std::this_thread::sleep_for(10ms);
538 auto prev_state = m_state.exchange(
State::RUNNING, std::memory_order_relaxed);
545 std::format(
"[{}] SHM Reset failed: {}", m_id, ret.message()));
549 auto occupancy = Occupancy(reader);
551 size_t to_read = m_to_read.load(std::memory_order_relaxed);
552 size_t to_skip = m_to_skip.load(std::memory_order_relaxed);
556 size_t sample_idx = 0;
557 uint32_t last_sample_id = 0;
558 t0 = std::chrono::steady_clock::now();
559 ret = Read(reader, to_read, [&](
const TopicType& sample) {
561 last_sample_id = sample.sample_id;
568 std::format(
"[{}] Work() Read: cycle {}, buffer occupancy {}",
573 std::format(
"[{}] SHM Read failed: {}", m_id, ret.message()));
576 if (m_command.load(std::memory_order_relaxed) != Command::RUN) {
582 t1 = std::chrono::steady_clock::now();
584 t2 = std::chrono::steady_clock::now();
586 t3 = std::chrono::steady_clock::now();
588 ret = Skip(reader, to_skip);
592 std::format(
"[{}] Work() Skip: cycle {}, buffer occupancy {}",
597 std::format(
"[{}] SHM Skip failed: {}", m_id, ret.message()));
600 t4 = std::chrono::steady_clock::now();
603 m_cycles.store(cycles, std::memory_order_relaxed);
610 (to_read + to_skip) / std::chrono::duration<float>(t4 - t0).count(),
618 if (
size_t c2r = m_cycles_to_run.load(); c2r != 0 and c2r == cycles) {
625 std::scoped_lock lock(m_exception_mutex);
626 m_exception = std::current_exception();
630 template <
typename Operation>
631 std::error_code Read(ReaderType& reader,
size_t to_read,
const Operation& op) {
632 using namespace std::chrono;
635 const std::error_code ok;
636 std::pair<std::error_code, size_t> ret;
637 milliseconds time_elapsed{0};
638 auto time_start = steady_clock::now();
641 if (m_command.load(std::memory_order_relaxed) != Command::RUN) {
646 size_t to_read_now = std::min(m_chunk_size, to_read - read);
648 ret = reader.Read(op, to_read_now, m_sample_timeout);
649 if (ret.first != ok) {
653 if (read == to_read) {
657 time_elapsed = duration_cast<milliseconds>(steady_clock::now() - time_start);
658 if (time_elapsed > m_sample_timeout * to_read) {
659 return std::make_error_code(std::errc::timed_out);
664 std::error_code Skip(ReaderType& reader,
size_t to_skip) {
665 using namespace std::chrono;
668 const std::error_code ok;
669 std::pair<std::error_code, size_t> ret;
670 milliseconds time_elapsed{0};
671 auto time_start = steady_clock::now();
674 if ((to_skip == 0) or (m_command.load(std::memory_order_relaxed) != Command::RUN)) {
679 size_t to_skip_now = std::min(m_chunk_size, to_skip - skipped);
681 ret = reader.Skip(to_skip_now, m_sample_timeout);
682 if (ret.first != ok) {
685 skipped += ret.second;
686 if (skipped == to_skip) {
690 time_elapsed = duration_cast<milliseconds>(steady_clock::now() - time_start);
691 if (time_elapsed > m_sample_timeout * to_skip) {
692 return std::make_error_code(std::errc::timed_out);
697 std::error_code Reset(ReaderType& reader) {
698 auto ret = reader.Reset();
699 if (ret == ipcq::Error::WouldBlock) {
705 float Occupancy(ReaderType& reader) {
706 return 100.0 * (
static_cast<float>(reader.NumAvailable()) / reader.Size());
710 enum class Command : uint8_t { RUN,
IDLE, EXIT };
712 log4cplus::Logger& m_logger;
715 std::string m_shm_name;
716 std::atomic<size_t> m_to_read;
717 std::atomic<size_t> m_to_skip;
719 std::chrono::milliseconds m_sample_timeout;
720 bool m_publish_metrics;
721 std::optional<numapp::NumaPolicies> m_thread_policies;
722 std::atomic<Command> m_command;
723 std::atomic<State> m_state;
724 std::atomic<size_t> m_cycles_to_run;
725 std::atomic<size_t> m_cycles;
726 std::exception_ptr m_exception =
nullptr;
727 std::shared_mutex m_exception_mutex;
728 std::thread m_thread;
The RtctkException class is the base class for all Rtctk exceptions.
Definition exceptions.hpp:220
Component metrics interface.
Definition componentMetricsIf.hpp:163
Defines auxiliary information associated with each counter registered with ComponentMetricsIf.
Definition componentMetricsIf.hpp:48
Helper class for passing tags in Telegraf.
Definition influxTagMap.hpp:26
Container class that holds services of any type.
Definition serviceContainer.hpp:38
void Run(std::optional< size_t > cycles=std::nullopt)
Commands the worker thread to perform number of computation cycles (async method).
Definition computationBase.hpp:350
void Spawn()
Spawns the worker thread (sync method).
Definition computationBase.hpp:309
virtual void OnThreadStart()
Optional user-hook called after worker thread started and reader was created.
Definition computationBase.hpp:457
rtctk::componentFramework::ServiceContainer ServiceContainer
Definition computationBase.hpp:196
virtual void OnCycleStart(size_t to_read)
Optional user-hook called immediately before a read-cycle from SHM starts.
Definition computationBase.hpp:465
void Idle()
Commands the worker thread to stop performing computation cycles (async method).
Definition computationBase.hpp:404
rtctk::componentFramework::ComponentMetricsIf ComponentMetricsIf
Definition computationBase.hpp:197
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:372
void SetSamplesToSkip(size_t value)
Sets the number of samples to skip.
Definition computationBase.hpp:293
size_t GetSamplesToSkip() const
Gets the number of samples to skip.
Definition computationBase.hpp:300
void CheckErrors()
Checks for errors in the worker thread and rethrows them to the caller.
Definition computationBase.hpp:444
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:394
virtual ~ComputationBase()=default
Destructor.
size_t GetCycles() const
Retrieves number of computation cycles performed since running.
Definition computationBase.hpp:433
void SetSamplesToRead(size_t value)
Sets the number of samples to read.
Definition computationBase.hpp:276
void Join()
Terminates the worker thread (sync method).
Definition computationBase.hpp:334
State
Current state of the worker thread.
Definition computationBase.hpp:189
@ RUNNING
Definition computationBase.hpp:190
@ OFF
Definition computationBase.hpp:193
@ IDLE
Definition computationBase.hpp:191
@ ERROR
Definition computationBase.hpp:192
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:184
size_t GetSamplesToRead() const
Gets the number of samples to read.
Definition computationBase.hpp:283
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:358
State GetState() const
Returns the current state of the worker thread.
Definition computationBase.hpp:415
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:237
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:209
Header file for ComponentMetricsIf.
log4cplus::Logger & GetLogger(const std::string &name="app")
Get handle to a specific logger.
Definition logger.cpp:191
Provides macros and utilities for exception handling.
Logging Support Library based on log4cplus.
Definition commandReplier.cpp:21
Definition computationBase.hpp:31
Definition commandReplier.cpp:21
Definition ddsSub.hpp:155
A container that can hold any type of service.
static constexpr std::string PERCENT
Definition componentMetricsIf.hpp:256
static constexpr std::string MICRO_SECONDS
Definition componentMetricsIf.hpp:257
static constexpr std::string HERTZ
Definition componentMetricsIf.hpp:258