RTC Toolkit 6.0.0-pre2
Loading...
Searching...
No Matches
computationBase.hpp
Go to the documentation of this file.
1
12
13#ifndef RTCTK_DATATASK_COMPUTATIONBASE_HPP
14#define RTCTK_DATATASK_COMPUTATIONBASE_HPP
15
20
21#include <ipcq/reader.hpp>
22#include <numapp/numapolicies.hpp>
23#include <numapp/thread.hpp>
24
25#include <atomic>
26#include <chrono>
27#include <format>
28#include <optional>
29#include <shared_mutex>
30#include <thread>
31
32namespace rtctk::dataTask {
33
34namespace {
35
36class ComputationMonitor {
37public:
38 struct Stats {
39 size_t num_cycles;
40 size_t num_samples;
41 uint32_t last_sample_id;
42 float freq_estimate;
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;
47 };
48
49 using ComponentMetricsIf = rtctk::componentFramework::ComponentMetricsIf;
50
51 explicit ComputationMonitor(ComponentMetricsIf& metrics, const std::string& id)
52 : m_start_time(std::chrono::steady_clock::now()) {
53 using namespace rtctk::componentFramework;
54
55 const InfluxTagMap base_tags = {{"id", id}};
56
57 m_pc_cycles_reg = metrics.AddCounter(
58 &m_pc_cycles, CounterMetricInfo(id + "/num_cycles", "cycles since running", base_tags, "num_cycles"));
59
60 m_pc_samples_reg = metrics.AddCounter(
61 &m_pc_samples,
62 CounterMetricInfo(id + "/num_samples", "samples read since running", base_tags, "num_samples"));
63
64 m_pc_sample_id_reg = metrics.AddCounter(
65 &m_pc_sample_id,
66 CounterMetricInfo(id + "/last_sample_id", "last observed sample id", base_tags, "last_sample_id"));
67
68 m_pc_freq_estimate_reg =
69 metrics.AddCounter(&m_pc_freq_estimate,
70 CounterMetricInfo(id + "/frequency_estimate",
71 "frequency estimate [Hz]",
72 base_tags,
73 "frequency_estimate",
75
76 m_pc_occupancy_reg =
77 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 =
85 metrics.AddCounter(&m_pc_dur_read,
86 CounterMetricInfo(id + "/duration_read",
87 "read duration [us]",
88 base_tags,
89 "duration_read",
91
92 m_pc_dur_compute_reg =
93 metrics.AddCounter(&m_pc_dur_compute,
94 CounterMetricInfo(id + "/duration_compute",
95 "compute duration [us]",
96 base_tags,
97 "duration_compute",
99
100 m_pc_dur_publish_reg =
101 metrics.AddCounter(&m_pc_dur_publish,
102 CounterMetricInfo(id + "/duration_publish",
103 "publish duration [us]",
104 base_tags,
105 "duration_publish",
107 }
108
109 void Tick(const Stats& stats) noexcept {
110 using namespace std::chrono_literals;
111
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;
116
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());
125 }
126 }
127
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);
138 }
139
140private:
141 std::chrono::steady_clock::time_point m_start_time;
142
143 perfc::CounterI64 m_pc_cycles;
144 perfc::ScopedRegistration m_pc_cycles_reg;
145
146 perfc::CounterI64 m_pc_samples;
147 perfc::ScopedRegistration m_pc_samples_reg;
148
149 perfc::CounterI64 m_pc_sample_id;
150 perfc::ScopedRegistration m_pc_sample_id_reg;
151
152 perfc::CounterDouble m_pc_freq_estimate;
153 perfc::ScopedRegistration m_pc_freq_estimate_reg;
154
155 perfc::CounterDouble m_pc_occupancy;
156 perfc::ScopedRegistration m_pc_occupancy_reg;
157
158 perfc::CounterI64 m_pc_dur_read;
159 perfc::ScopedRegistration m_pc_dur_read_reg;
160
161 perfc::CounterI64 m_pc_dur_compute;
162 perfc::ScopedRegistration m_pc_dur_compute_reg;
163
164 perfc::CounterI64 m_pc_dur_publish;
165 perfc::ScopedRegistration m_pc_dur_publish_reg;
166};
167
168} // namespace
169
184template <typename TopicTypeX, typename ReaderType = ipcq::Reader<TopicTypeX>>
186public:
187 using TopicType = TopicTypeX;
188
192 enum class State : uint8_t {
193 RUNNING, // thread is reading, skipping and performing computations
194 IDLE, // thread is continuously resetting the queue, no computations
195 ERROR, // thread terminated automatically due to an error
196 OFF // thread was not yet started or terminated manually
197 };
198
201
213 const std::string& shm_name,
214 size_t to_read,
215 size_t to_skip,
216 std::chrono::milliseconds sample_timeout,
217 std::optional<numapp::NumaPolicies> thread_policies)
218 : ComputationBase(services,
219 "computation",
220 shm_name,
221 to_read,
222 to_skip,
223 sample_timeout,
224 true,
225 thread_policies) {
226 }
227
241 const std::string& id,
242 const std::string& shm_name,
243 size_t to_read,
244 size_t to_skip,
245 std::chrono::milliseconds sample_timeout,
246 std::optional<bool> publish_metrics,
247 std::optional<numapp::NumaPolicies> thread_policies)
248 : m_logger(rtctk::componentFramework::GetLogger("rtctk"))
249 , m_services(services)
250 , m_id(id)
251 , m_shm_name(shm_name)
252 , m_to_read(to_read)
253 , m_to_skip(to_skip)
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)
259 , m_state(State::OFF)
260 , m_cycles_to_run(0)
261 , m_cycles(0) {
262 }
263
271 virtual ~ComputationBase() = default;
272
279 void SetSamplesToRead(size_t value) {
280 m_to_read.store(value, std::memory_order_relaxed);
281 }
282
286 size_t GetSamplesToRead() const {
287 return m_to_read.load(std::memory_order_relaxed);
288 }
289
296 void SetSamplesToSkip(size_t value) {
297 m_to_skip.store(value, std::memory_order_relaxed);
298 }
299
303 size_t GetSamplesToSkip() const {
304 return m_to_skip.load(std::memory_order_relaxed);
305 }
306
312 void Spawn() {
313 // clear the previous exception to be able to recover
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,
319 this);
320 for (;;) {
321 using namespace std::chrono_literals;
322 std::this_thread::sleep_for(10ms);
323
324 CheckErrors();
325
326 if (GetState() != State::OFF) {
327 break;
328 }
329 }
330 }
331
337 void Join() {
338 m_command = Command::EXIT;
339 if (m_thread.joinable()) {
340 m_thread.join();
341 }
342 }
343
353 void Run(std::optional<size_t> cycles = std::nullopt) {
354 m_cycles_to_run = cycles.value_or(0);
355 m_command = Command::RUN;
356 }
357
361 void RunOnce() {
362 Run(1);
363 }
364
375 void AwaitIdle(std::optional<std::chrono::milliseconds> poll_interval = std::nullopt) {
376 for (;;) {
377 using namespace std::chrono_literals;
378 std::this_thread::sleep_for(poll_interval.value_or(10ms));
379
380 CheckErrors();
381
382 if (GetState() == State::IDLE) {
383 break;
384 }
385 }
386 }
387
397 void RunOnceSync(std::optional<std::chrono::milliseconds> poll_interval = std::nullopt) {
398 RunOnce();
399 AwaitIdle(poll_interval);
400 }
401
407 void Idle() {
408 m_command = Command::IDLE;
409 }
410
418 State GetState() const {
419 auto state = m_state.load(std::memory_order_relaxed);
420 auto command = m_command.load(std::memory_order_relaxed);
421
422 if (state == State::IDLE and command == Command::RUN) {
423 return State::RUNNING;
424 } else {
425 return state;
426 }
427 }
428
436 size_t GetCycles() const {
437 return m_cycles.load(std::memory_order_relaxed);
438 }
439
447 void CheckErrors() {
448 auto lock = std::shared_lock{m_exception_mutex};
449 if (m_exception) {
450 std::rethrow_exception(m_exception);
451 }
452 }
453
454protected:
460 virtual void OnThreadStart() {
461 }
462
468 virtual void OnCycleStart(size_t to_read) {
469 }
470
476 virtual void CopyData(size_t sample_idx, const TopicType& sample) noexcept = 0;
477
483 virtual void Compute() = 0;
484
490 virtual void Publish() = 0;
491
492private:
493 void Work() {
494 using namespace rtctk::componentFramework;
495 using namespace std::chrono_literals;
496
497 const std::error_code ok{};
498 std::error_code ret = ok;
499
500 size_t cycles = 0; // since State::RUNNING
501 size_t samples = 0; // since State::RUNNING
502 m_cycles.store(cycles, std::memory_order_relaxed);
503
504 auto t0 = std::chrono::steady_clock::now();
505 auto t1 = t0;
506 auto t2 = t0;
507 auto t3 = t0;
508 auto t4 = t0;
509
510 try {
511 auto reader = ReaderType::MakeReader(m_shm_name.c_str(), 30s);
512
513 std::unique_ptr<ComputationMonitor> monitor;
514 if (m_publish_metrics) {
515 monitor = std::make_unique<ComputationMonitor>(m_services.Get<ComponentMetricsIf>(),
516 m_id);
517 }
518
520
521 for (;;) {
522 Command command = m_command.load(std::memory_order_relaxed);
523 if (command == Command::EXIT) {
524 if (monitor) {
525 monitor->Reset();
526 }
527 m_state.store(State::OFF, std::memory_order_relaxed);
528 return;
529 } else if (command == Command::IDLE) {
530 cycles = 0;
531 samples = 0;
532 m_cycles.store(cycles, std::memory_order_relaxed);
533
534 // as long as we publish metrics to CII OLDB, we wont reset the monitor here
535 // monitor->Reset();
536 m_state.store(State::IDLE, std::memory_order_relaxed);
537
538 std::this_thread::sleep_for(10ms);
539 } else { // RUN
540 auto prev_state = m_state.exchange(State::RUNNING, std::memory_order_relaxed);
541 if (prev_state != State::RUNNING) {
542 // we are entering State::RUNNING for first time
543 ret = Reset(reader);
544 if (ret != ok) {
545 CII_THROW(
547 std::format("[{}] SHM Reset failed: {}", m_id, ret.message()));
548 }
549 }
550
551 auto occupancy = Occupancy(reader);
552
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);
555
556 OnCycleStart(to_read);
557
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) {
562 CopyData(sample_idx, sample);
563 last_sample_id = sample.sample_id;
564 sample_idx++;
565 });
566
567 if (ret != ok) {
568 LOG4CPLUS_ERROR(
569 m_logger,
570 std::format("[{}] Work() Read: cycle {}, buffer occupancy {}",
571 m_id,
572 cycles,
573 occupancy));
574 CII_THROW(RtctkException,
575 std::format("[{}] SHM Read failed: {}", m_id, ret.message()));
576 }
577
578 if (m_command.load(std::memory_order_relaxed) != Command::RUN) {
579 // premature exit if command changed while reading
580 continue;
581 }
582
583 // all data has arrived for this cycle
584 t1 = std::chrono::steady_clock::now();
585 Compute();
586 t2 = std::chrono::steady_clock::now();
587 Publish();
588 t3 = std::chrono::steady_clock::now();
589
590 ret = Skip(reader, to_skip);
591 if (ret != ok) {
592 LOG4CPLUS_ERROR(
593 m_logger,
594 std::format("[{}] Work() Skip: cycle {}, buffer occupancy {}",
595 m_id,
596 cycles,
597 occupancy));
598 CII_THROW(RtctkException,
599 std::format("[{}] SHM Skip failed: {}", m_id, ret.message()));
600 }
601
602 t4 = std::chrono::steady_clock::now();
603 cycles++;
604 samples += to_read;
605 m_cycles.store(cycles, std::memory_order_relaxed);
606
607 if (monitor) {
608 monitor->Tick(
609 {cycles,
610 samples,
611 last_sample_id,
612 (to_read + to_skip) / std::chrono::duration<float>(t4 - t0).count(),
613 occupancy,
614 t1 - t0,
615 t2 - t1,
616 t3 - t2});
617 }
618
619 // after having computed requested number of cycles we Idle
620 if (size_t c2r = m_cycles_to_run.load(); c2r != 0 and c2r == cycles) {
621 Idle();
622 }
623 }
624 }
625 } catch (...) {
626 m_state.store(State::ERROR, std::memory_order_relaxed);
627 std::scoped_lock lock(m_exception_mutex);
628 m_exception = std::current_exception();
629 }
630 }
631
632 template <typename Operation>
633 std::error_code Read(ReaderType& reader, size_t to_read, const Operation& op) {
634 using namespace std::chrono;
635
636 size_t read = 0;
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();
641
642 for (;;) {
643 if (m_command.load(std::memory_order_relaxed) != Command::RUN) {
644 // premature exit, no error
645 return {};
646 }
647
648 size_t to_read_now = std::min(m_chunk_size, to_read - read);
649
650 ret = reader.Read(op, to_read_now, m_sample_timeout);
651 if (ret.first != ok) {
652 return ret.first;
653 }
654 read += ret.second;
655 if (read == to_read) {
656 return {};
657 }
658
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);
662 }
663 }
664 }
665
666 std::error_code Skip(ReaderType& reader, size_t to_skip) {
667 using namespace std::chrono;
668
669 size_t skipped = 0;
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();
674
675 for (;;) {
676 if ((to_skip == 0) or (m_command.load(std::memory_order_relaxed) != Command::RUN)) {
677 // premature exit, no error
678 return {};
679 }
680
681 size_t to_skip_now = std::min(m_chunk_size, to_skip - skipped);
682
683 ret = reader.Skip(to_skip_now, m_sample_timeout);
684 if (ret.first != ok) {
685 return ret.first;
686 }
687 skipped += ret.second;
688 if (skipped == to_skip) {
689 return {};
690 }
691
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);
695 }
696 }
697 }
698
699 std::error_code Reset(ReaderType& reader) {
700 auto ret = reader.Reset();
701 if (ret == ipcq::Error::WouldBlock) {
702 return {};
703 }
704 return ret;
705 }
706
707 float Occupancy(ReaderType& reader) {
708 return 100.0 * (static_cast<float>(reader.NumAvailable()) / reader.Size());
709 }
710
711private:
712 enum class Command : uint8_t { RUN, IDLE, EXIT };
713
714 log4cplus::Logger& m_logger;
715 ServiceContainer& m_services;
716 std::string m_id;
717 std::string m_shm_name;
718 std::atomic<size_t> m_to_read;
719 std::atomic<size_t> m_to_skip;
720 size_t m_chunk_size;
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;
731};
732
733} // namespace rtctk::dataTask
734
735#endif // RTCTK_DATATASK_COMPUTATIONBASE_HPP
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