RTC Toolkit 6.0.0
Loading...
Searching...
No Matches
computationBase.hpp
Go to the documentation of this file.
1
11
12#ifndef RTCTK_DATATASK_COMPUTATIONBASE_HPP
13#define RTCTK_DATATASK_COMPUTATIONBASE_HPP
14
19
20#include <ipcq/reader.hpp>
21#include <numapp/numapolicies.hpp>
22#include <numapp/thread.hpp>
23
24#include <atomic>
25#include <chrono>
26#include <format>
27#include <optional>
28#include <shared_mutex>
29#include <thread>
30
31namespace rtctk::dataTask {
32
33namespace {
34
35class ComputationMonitor {
36public:
37 struct Stats {
38 size_t num_cycles;
39 size_t num_samples;
40 uint32_t last_sample_id;
41 float freq_estimate;
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;
46 };
47
48 using ComponentMetricsIf = rtctk::componentFramework::ComponentMetricsIf;
49
50 explicit ComputationMonitor(ComponentMetricsIf& metrics, const std::string& id)
51 : m_start_time(std::chrono::steady_clock::now()) {
52 using namespace rtctk::componentFramework;
53
54 const InfluxTagMap base_tags = {{"id", id}};
55
56 m_pc_cycles_reg = metrics.AddCounter(
57 &m_pc_cycles,
58 CounterMetricInfo(id + "/num_cycles", "cycles since running", base_tags, "num_cycles"));
59
60 m_pc_samples_reg = metrics.AddCounter(
61 &m_pc_samples,
63 id + "/num_samples", "samples read since running", base_tags, "num_samples"));
64
65 m_pc_sample_id_reg = metrics.AddCounter(
66 &m_pc_sample_id,
68 id + "/last_sample_id", "last observed sample id", base_tags, "last_sample_id"));
69
70 m_pc_freq_estimate_reg = metrics.AddCounter(&m_pc_freq_estimate,
71 CounterMetricInfo(id + "/frequency_estimate",
72 "frequency estimate [Hz]",
73 base_tags,
74 "frequency_estimate",
76
77 m_pc_occupancy_reg = metrics.AddCounter(&m_pc_occupancy,
78 CounterMetricInfo(id + "/buffer_occupancy",
79 "buffer occupancy [%]",
80 base_tags,
81 "buffer_occupancy",
83
84 m_pc_dur_read_reg = metrics.AddCounter(&m_pc_dur_read,
85 CounterMetricInfo(id + "/duration_read",
86 "read duration [us]",
87 base_tags,
88 "duration_read",
90
91 m_pc_dur_compute_reg = metrics.AddCounter(&m_pc_dur_compute,
92 CounterMetricInfo(id + "/duration_compute",
93 "compute duration [us]",
94 base_tags,
95 "duration_compute",
97
98 m_pc_dur_publish_reg = metrics.AddCounter(&m_pc_dur_publish,
99 CounterMetricInfo(id + "/duration_publish",
100 "publish duration [us]",
101 base_tags,
102 "duration_publish",
104 }
105
106 void Tick(const Stats& stats) noexcept {
107 using namespace std::chrono_literals;
108
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;
113
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());
122 }
123 }
124
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);
135 }
136
137private:
138 std::chrono::steady_clock::time_point m_start_time;
139
140 perfc::CounterI64 m_pc_cycles;
141 perfc::ScopedRegistration m_pc_cycles_reg;
142
143 perfc::CounterI64 m_pc_samples;
144 perfc::ScopedRegistration m_pc_samples_reg;
145
146 perfc::CounterI64 m_pc_sample_id;
147 perfc::ScopedRegistration m_pc_sample_id_reg;
148
149 perfc::CounterDouble m_pc_freq_estimate;
150 perfc::ScopedRegistration m_pc_freq_estimate_reg;
151
152 perfc::CounterDouble m_pc_occupancy;
153 perfc::ScopedRegistration m_pc_occupancy_reg;
154
155 perfc::CounterI64 m_pc_dur_read;
156 perfc::ScopedRegistration m_pc_dur_read_reg;
157
158 perfc::CounterI64 m_pc_dur_compute;
159 perfc::ScopedRegistration m_pc_dur_compute_reg;
160
161 perfc::CounterI64 m_pc_dur_publish;
162 perfc::ScopedRegistration m_pc_dur_publish_reg;
163};
164
165} // namespace
166
181template <typename TopicTypeX, typename ReaderType = ipcq::Reader<TopicTypeX>>
183public:
184 using TopicType = TopicTypeX;
185
189 enum class State : uint8_t {
190 RUNNING, // thread is reading, skipping and performing computations
191 IDLE, // thread is continuously resetting the queue, no computations
192 ERROR, // thread terminated automatically due to an error
193 OFF // thread was not yet started or terminated manually
194 };
195
198
210 const std::string& shm_name,
211 size_t to_read,
212 size_t to_skip,
213 std::chrono::milliseconds sample_timeout,
214 std::optional<numapp::NumaPolicies> thread_policies)
215 : ComputationBase(services,
216 "computation",
217 shm_name,
218 to_read,
219 to_skip,
220 sample_timeout,
221 true,
222 thread_policies) {
223 }
224
238 const std::string& id,
239 const std::string& shm_name,
240 size_t to_read,
241 size_t to_skip,
242 std::chrono::milliseconds sample_timeout,
243 std::optional<bool> publish_metrics,
244 std::optional<numapp::NumaPolicies> thread_policies)
245 : m_logger(rtctk::componentFramework::GetLogger("rtctk"))
246 , m_services(services)
247 , m_id(id)
248 , m_shm_name(shm_name)
249 , m_to_read(to_read)
250 , m_to_skip(to_skip)
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)
256 , m_state(State::OFF)
257 , m_cycles_to_run(0)
258 , m_cycles(0) {
259 }
260
268 virtual ~ComputationBase() = default;
269
276 void SetSamplesToRead(size_t value) {
277 m_to_read.store(value, std::memory_order_relaxed);
278 }
279
283 size_t GetSamplesToRead() const {
284 return m_to_read.load(std::memory_order_relaxed);
285 }
286
293 void SetSamplesToSkip(size_t value) {
294 m_to_skip.store(value, std::memory_order_relaxed);
295 }
296
300 size_t GetSamplesToSkip() const {
301 return m_to_skip.load(std::memory_order_relaxed);
302 }
303
309 void Spawn() {
310 // clear the previous exception to be able to recover
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,
316 this);
317 for (;;) {
318 using namespace std::chrono_literals;
319 std::this_thread::sleep_for(10ms);
320
321 CheckErrors();
322
323 if (GetState() != State::OFF) {
324 break;
325 }
326 }
327 }
328
334 void Join() {
335 m_command = Command::EXIT;
336 if (m_thread.joinable()) {
337 m_thread.join();
338 }
339 }
340
350 void Run(std::optional<size_t> cycles = std::nullopt) {
351 m_cycles_to_run = cycles.value_or(0);
352 m_command = Command::RUN;
353 }
354
358 void RunOnce() {
359 Run(1);
360 }
361
372 void AwaitIdle(std::optional<std::chrono::milliseconds> poll_interval = std::nullopt) {
373 for (;;) {
374 using namespace std::chrono_literals;
375 std::this_thread::sleep_for(poll_interval.value_or(10ms));
376
377 CheckErrors();
378
379 if (GetState() == State::IDLE) {
380 break;
381 }
382 }
383 }
384
394 void RunOnceSync(std::optional<std::chrono::milliseconds> poll_interval = std::nullopt) {
395 RunOnce();
396 AwaitIdle(poll_interval);
397 }
398
404 void Idle() {
405 m_command = Command::IDLE;
406 }
407
415 State GetState() const {
416 auto state = m_state.load(std::memory_order_relaxed);
417 auto command = m_command.load(std::memory_order_relaxed);
418
419 if (state == State::IDLE and command == Command::RUN) {
420 return State::RUNNING;
421 } else {
422 return state;
423 }
424 }
425
433 size_t GetCycles() const {
434 return m_cycles.load(std::memory_order_relaxed);
435 }
436
444 void CheckErrors() {
445 auto lock = std::shared_lock{m_exception_mutex};
446 if (m_exception) {
447 std::rethrow_exception(m_exception);
448 }
449 }
450
451protected:
457 virtual void OnThreadStart() {
458 }
459
465 virtual void OnCycleStart(size_t to_read) {
466 }
467
473 virtual void CopyData(size_t sample_idx, const TopicType& sample) noexcept = 0;
474
480 virtual void Compute() = 0;
481
487 virtual void Publish() = 0;
488
489private:
490 void Work() {
491 using namespace rtctk::componentFramework;
492 using namespace std::chrono_literals;
493
494 const std::error_code ok{};
495 std::error_code ret = ok;
496
497 size_t cycles = 0; // since State::RUNNING
498 // cppcheck-suppress variableScope
499 size_t samples = 0; // since State::RUNNING
500 m_cycles.store(cycles, std::memory_order_relaxed);
501
502 auto t0 = std::chrono::steady_clock::now();
503 auto t1 = t0;
504 auto t2 = t0;
505 auto t3 = t0;
506 auto t4 = t0;
507
508 try {
509 auto reader = ReaderType::MakeReader(m_shm_name.c_str(), 30s);
510
511 std::unique_ptr<ComputationMonitor> monitor;
512 if (m_publish_metrics) {
513 monitor = std::make_unique<ComputationMonitor>(m_services.Get<ComponentMetricsIf>(),
514 m_id);
515 }
516
518
519 for (;;) {
520 Command command = m_command.load(std::memory_order_relaxed);
521 if (command == Command::EXIT) {
522 if (monitor) {
523 monitor->Reset();
524 }
525 m_state.store(State::OFF, std::memory_order_relaxed);
526 return;
527 } else if (command == Command::IDLE) {
528 cycles = 0;
529 samples = 0;
530 m_cycles.store(cycles, std::memory_order_relaxed);
531
532 // as long as we publish metrics to CII OLDB, we wont reset the monitor here
533 // monitor->Reset();
534 m_state.store(State::IDLE, std::memory_order_relaxed);
535
536 std::this_thread::sleep_for(10ms);
537 } else { // RUN
538 auto prev_state = m_state.exchange(State::RUNNING, std::memory_order_relaxed);
539 if (prev_state != State::RUNNING) {
540 // we are entering State::RUNNING for first time
541 ret = Reset(reader);
542 if (ret != ok) {
543 CII_THROW(
545 std::format("[{}] SHM Reset failed: {}", m_id, ret.message()));
546 }
547 }
548
549 auto occupancy = Occupancy(reader);
550
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);
553
554 OnCycleStart(to_read);
555
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) {
560 CopyData(sample_idx, sample);
561 last_sample_id = sample.sample_id;
562 sample_idx++;
563 });
564
565 if (ret != ok) {
566 LOG4CPLUS_ERROR(
567 m_logger,
568 std::format("[{}] Work() Read: cycle {}, buffer occupancy {}",
569 m_id,
570 cycles,
571 occupancy));
572 CII_THROW(RtctkException,
573 std::format("[{}] SHM Read failed: {}", m_id, ret.message()));
574 }
575
576 if (m_command.load(std::memory_order_relaxed) != Command::RUN) {
577 // premature exit if command changed while reading
578 continue;
579 }
580
581 // all data has arrived for this cycle
582 t1 = std::chrono::steady_clock::now();
583 Compute();
584 t2 = std::chrono::steady_clock::now();
585 Publish();
586 t3 = std::chrono::steady_clock::now();
587
588 ret = Skip(reader, to_skip);
589 if (ret != ok) {
590 LOG4CPLUS_ERROR(
591 m_logger,
592 std::format("[{}] Work() Skip: cycle {}, buffer occupancy {}",
593 m_id,
594 cycles,
595 occupancy));
596 CII_THROW(RtctkException,
597 std::format("[{}] SHM Skip failed: {}", m_id, ret.message()));
598 }
599
600 t4 = std::chrono::steady_clock::now();
601 cycles++;
602 samples += to_read;
603 m_cycles.store(cycles, std::memory_order_relaxed);
604
605 if (monitor) {
606 monitor->Tick(
607 {cycles,
608 samples,
609 last_sample_id,
610 (to_read + to_skip) / std::chrono::duration<float>(t4 - t0).count(),
611 occupancy,
612 t1 - t0,
613 t2 - t1,
614 t3 - t2});
615 }
616
617 // after having computed requested number of cycles we Idle
618 if (size_t c2r = m_cycles_to_run.load(); c2r != 0 and c2r == cycles) {
619 Idle();
620 }
621 }
622 }
623 } catch (...) {
624 m_state.store(State::ERROR, std::memory_order_relaxed);
625 std::scoped_lock lock(m_exception_mutex);
626 m_exception = std::current_exception();
627 }
628 }
629
630 template <typename Operation>
631 std::error_code Read(ReaderType& reader, size_t to_read, const Operation& op) {
632 using namespace std::chrono;
633
634 size_t read = 0;
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();
639
640 for (;;) {
641 if (m_command.load(std::memory_order_relaxed) != Command::RUN) {
642 // premature exit, no error
643 return {};
644 }
645
646 size_t to_read_now = std::min(m_chunk_size, to_read - read);
647
648 ret = reader.Read(op, to_read_now, m_sample_timeout);
649 if (ret.first != ok) {
650 return ret.first;
651 }
652 read += ret.second;
653 if (read == to_read) {
654 return {};
655 }
656
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);
660 }
661 }
662 }
663
664 std::error_code Skip(ReaderType& reader, size_t to_skip) {
665 using namespace std::chrono;
666
667 size_t skipped = 0;
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();
672
673 for (;;) {
674 if ((to_skip == 0) or (m_command.load(std::memory_order_relaxed) != Command::RUN)) {
675 // premature exit, no error
676 return {};
677 }
678
679 size_t to_skip_now = std::min(m_chunk_size, to_skip - skipped);
680
681 ret = reader.Skip(to_skip_now, m_sample_timeout);
682 if (ret.first != ok) {
683 return ret.first;
684 }
685 skipped += ret.second;
686 if (skipped == to_skip) {
687 return {};
688 }
689
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);
693 }
694 }
695 }
696
697 std::error_code Reset(ReaderType& reader) {
698 auto ret = reader.Reset();
699 if (ret == ipcq::Error::WouldBlock) {
700 return {};
701 }
702 return ret;
703 }
704
705 float Occupancy(ReaderType& reader) {
706 return 100.0 * (static_cast<float>(reader.NumAvailable()) / reader.Size());
707 }
708
709private:
710 enum class Command : uint8_t { RUN, IDLE, EXIT };
711
712 log4cplus::Logger& m_logger;
713 ServiceContainer& m_services;
714 std::string m_id;
715 std::string m_shm_name;
716 std::atomic<size_t> m_to_read;
717 std::atomic<size_t> m_to_skip;
718 size_t m_chunk_size;
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;
729};
730
731} // namespace rtctk::dataTask
732
733#endif // RTCTK_DATATASK_COMPUTATIONBASE_HPP
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