RTC Toolkit 6.0.0-pre2
Loading...
Searching...
No Matches
repositoryRecordingUnit.hpp
Go to the documentation of this file.
1
12#ifndef RTCTK_COMPONENTFRAMEWORK_REPOSITORYRECORDINGUNIT_HPP
13#define RTCTK_COMPONENTFRAMEWORK_REPOSITORYRECORDINGUNIT_HPP
14
15#include <algorithm>
26
27#include <numapp/numapolicies.hpp>
28#include <numapp/thread.hpp>
29#include <taiclock/taiClock.hpp>
30
31#include <boost/asio.hpp>
32#include <fmt/format.h>
33
34#include <chrono>
35#include <cstdint>
36#include <fstream>
37#include <queue>
38#include <semaphore>
39#include <stop_token>
40#include <string>
41#include <thread>
42
44
45using namespace std::string_view_literals;
46using namespace std::chrono_literals;
47
54public:
62 RepositoryRecordingUnit(const std::string& comp_id,
63 const std::string& unit_id,
64 ServiceContainer& services);
65
69 ~RepositoryRecordingUnit() override;
70
76 void Prepare(const std::filesystem::path& file_path) override;
77
81 void Start() override;
82
88 std::vector<std::filesystem::path> Stop() override;
89
90private:
91 struct DataPointCfg {
92 struct MetaDataCfg {
94 bool enabled = false;
96 std::vector<std::string> filter = {};
97 };
98
99 struct CaptureCfg {
100 bool on_start = true;
101 bool on_stop = true;
102 bool on_change = true;
103 };
104
105 enum class DataSource : uint8_t {
106 RTR,
107 OLDB,
108 };
109
111 bool enabled = true;
112
114 DataPointPath name;
115
117 DataSource source = DataSource::RTR;
118
119 // settings related to metadata
120 MetaDataCfg metadata;
121
122 // settings related to when to record the data
123 CaptureCfg capture;
124 };
125
127
128 class MetaDataQueue {
129 public:
130 /*
131 * Pushes an item to the queue
132 */
133 void Push(const RepositoryIf::MetaData& data);
134 /*
135 * Retrieves an item from the queue, returns std::nullopt if empty
136 */
137 std::optional<RepositoryIf::MetaData> TryPop();
138 /*
139 * Gets number of items currently in queue
140 */
141 size_t Size();
142
143 private:
144 std::mutex m_mutex;
145 std::queue<RepositoryIf::MetaData> m_queue;
146 };
147
149
156 class DataPointRecorderIf {
157 public:
158 virtual ~DataPointRecorderIf() = default;
159 virtual void Prepare(std::filesystem::path file_path) = 0;
160 virtual void Start() = 0;
161 virtual std::vector<std::filesystem::path> Stop() = 0;
162 virtual size_t HasWork() = 0;
163 virtual size_t Work() = 0;
164 };
165
167
171 template <typename DpType>
172 class DataPointRecorder : public DataPointRecorderIf {
173 public:
174 using TimePointType = taiclock::TaiClock::time_point::rep;
175 using SequenceIdType = uint64_t;
176 using ValueType = std::conditional_t<IS_SPAN_CONVERTIBLE<DpType>, AsSpan<DpType>, DpType>;
177 using FitsRecorderType = FitsRecorder<TimePointType, SequenceIdType, ValueType>;
178
179 DataPointRecorder(RepositorySubscriberIf& repo, const DataPointCfg& cfg)
180 : m_logger(GetLogger("rtctk"))
181 , m_repo(repo)
182 , m_cfg(cfg)
183 , m_fits_writer(COLUMNS)
184 , m_subscription({}) {
185 }
186
187 void Prepare(std::filesystem::path file_path) override {
188 // set the column length to avoid dynamic-sized fits binary table cells
189 m_fits_writer.SetColumnLength(2, m_repo.GetDataPointSize(m_cfg.name));
190
191 // set the disabled field depending on the data source
192 if (m_cfg.source == DataPointCfg::DataSource::RTR) {
193 m_fits_writer.SetDisabledFields({false, false, false});
194 } else {
195 // no sequence_id field for oldb recording
196 m_fits_writer.SetDisabledFields({false, true, false});
197 }
198
199 // compute the file names
200 std::string filename = m_cfg.name.ToString();
201 if (filename.starts_with('/')) {
202 filename.erase(0, 1);
203 }
204 std::ranges::replace(filename, '/', '_');
205
206 m_pathname_fits = file_path / (filename + m_fits_writer.DefaultFileExtension());
207 m_pathname_jsonl = file_path / (filename + ".jsonl");
208
209 // open the files
210 m_fits_writer.Open(m_pathname_fits);
211 if (m_cfg.metadata.enabled) {
212 m_md_writer.open(m_pathname_jsonl, std::ios::out);
213 }
214 }
215
216 void Start() override {
217 // subscribe if necessary
218 if (m_cfg.capture.on_change) {
219 m_subscription = std::move(m_repo.Subscribe(
220 m_cfg.name,
221 [this](const auto& path, const auto& metadata) { m_queue.Push(metadata); },
222 nullptr));
223 }
224
225 // capture data on_start
226 if (m_cfg.capture.on_start) {
227 RetrieveAndRecord(std::nullopt);
228 }
229 }
230
231 std::vector<std::filesystem::path> Stop() override {
232 if (m_cfg.capture.on_change) {
233 m_subscription.Unsubscribe();
234 }
235
236 if (m_cfg.capture.on_stop) {
237 RetrieveAndRecord(std::nullopt);
238 }
239
240 m_fits_writer.Close();
241 if (m_cfg.metadata.enabled) {
242 m_md_writer.close();
243 }
244
245 std::vector<std::filesystem::path> files;
246 files.push_back(m_pathname_fits);
247 if (m_cfg.metadata.enabled) {
248 files.push_back(m_pathname_jsonl);
249 }
250 return files;
251 }
252
253 size_t HasWork() override {
254 return m_queue.Size();
255 }
256
257 size_t Work() override {
258 size_t work_counter = 0;
259 for (;;) {
260 if (auto md = m_queue.TryPop(); md.has_value()) {
261 RetrieveAndRecord(md);
262 work_counter++;
263 } else {
264 break;
265 }
266 }
267 return work_counter;
268 }
269
270 private:
271 void RetrieveAndRecord(const std::optional<RepositoryIf::MetaData>& md_cb) {
272 DpType value;
273
274 RepositoryIf::MetaData md_read;
275 RepositoryIf::BatchRequest req;
276 req.ReadDataPoint(m_cfg.name, value, md_read);
277 m_repo.SendRequest(req).Wait();
278
279 auto ts = md_read["timestamp"].Cast<RepositoryIf::Timestamp>();
280 SequenceIdType sequence_id = 0;
281
282 bool write_sample = true;
283
284 if (m_cfg.source == DataPointCfg::DataSource::RTR) {
285 sequence_id = md_read["sequence_id"].Cast<RtcUInt64>();
286 if (md_cb.has_value()) {
287 auto sequence_id_cb = md_cb.value()["sequence_id"].Cast<RtcUInt64>();
288 if (sequence_id_cb != sequence_id) {
289 write_sample = false;
290 LOG4CPLUS_WARN(
291 m_logger,
292 fmt::format(
293 "DataPointRecorder: pathname: '{}' missed sequence_id: '{}'.",
294 m_cfg.name,
295 sequence_id_cb));
296 }
297 }
298 }
299
300 if (write_sample) {
301 m_fits_writer.Write(AsTuple(ts, sequence_id, value));
302
303 if (m_cfg.metadata.enabled) {
304 JsonPayload md_in = md_read;
305 if (m_cfg.metadata.filter.empty()) {
306 m_md_writer << md_in << "\n";
307 } else {
308 JsonPayload md_out;
309 for (const auto& filter : m_cfg.metadata.filter) {
310 if (md_in.contains(filter)) {
311 md_out[filter] = md_in.at(filter);
312 }
313 }
314 m_md_writer << md_out << "\n";
315 }
316 }
317 }
318 }
319
320 static FitsRecorderType::TupleType AsTuple(const taiclock::TaiClock::time_point& ts,
321 uint64_t sequence_id,
322 const DpType& data) {
323 auto t = ts.time_since_epoch().count();
324 if constexpr (IS_SPAN_CONVERTIBLE<DpType>) {
325 return std::make_tuple(t, sequence_id, ToSpan(data));
326 } else {
327 return std::make_tuple(t, sequence_id, data);
328 }
329 }
330
331 static constexpr typename FitsRecorderType::ColumnDescription COLUMNS =
333 {{"timestamp"sv, ""sv}, {"sequence_id"sv, ""sv}, {"payload"sv, ""sv}}};
334
335 log4cplus::Logger& m_logger;
336 RepositorySubscriberIf& m_repo;
337 DataPointCfg m_cfg;
338 MetaDataQueue m_queue;
339 // TODO: consider adding a mutex to protect the file writers
340 FitsRecorderType m_fits_writer;
341 std::ofstream m_md_writer;
342 RepositorySubscriberIf::Subscription m_subscription;
343 std::filesystem::path m_pathname_fits;
344 std::filesystem::path m_pathname_jsonl;
345 };
346
348
349 void ReadConfigAndCreateDataPointRecorders();
350
351 std::unique_ptr<DataPointRecorderIf>
352 MakeDataPointRecorder(RepositorySubscriberIf& repo, const DataPointCfg& dp_cfg);
353
354 void Process(const std::stop_token& st, const std::filesystem::path& base_path);
355
356 using TypeMapType = std::map<std::type_index,
357 std::function<std::unique_ptr<DataPointRecorderIf>(
358 RepositorySubscriberIf&, const DataPointCfg&)>>;
359
360 template <typename T>
361 static TypeMapType::value_type MakeRecordingUnitTypeMapEntry() {
362 return std::make_pair(std::type_index{typeid(T)},
363 [](RepositorySubscriberIf& repo, const DataPointCfg& cfg) {
364 return std::make_unique<DataPointRecorder<T>>(repo, cfg);
365 });
366 }
367
368 static const TypeMapType TYPE_MAP;
369
370 inline static constexpr std::string_view RTR_PATH_NUM_WORKERS =
371 "/{}/dynamic/rec_units/{}/num_workers";
372
373 inline static constexpr std::string_view RTR_PATH_DATA_POINT_LIST =
374 "/{}/dynamic/rec_units/{}/datapoint_list";
375
376 inline static constexpr size_t DEFAULT_NUM_WORKERS = 3;
377
378 log4cplus::Logger& m_logger;
379 size_t m_num_workers;
380 std::vector<std::unique_ptr<DataPointRecorderIf>> m_recorders;
381 perfc::CounterI64 m_samples_queued;
382 perfc::ScopedRegistration m_samples_queued_reg;
383 perfc::CounterI64 m_samples_written;
384 perfc::ScopedRegistration m_samples_written_reg;
385 std::mutex m_mutex_files;
386 std::vector<std::filesystem::path> m_files;
387 std::binary_semaphore m_sem_start;
388 std::jthread m_process_thread;
389};
390
391} // namespace rtctk::componentFramework
392
393#endif // RTCTK_COMPONENTFRAMEWORK_DATAPOINTRECORDINGUNIT_HPP
This class provides a wrapper for a data point path.
Definition dataPointPath.hpp:77
std::tuple< T... > TupleType
Definition dataRecorder.hpp:43
const std::array< ColumnMetaData, sizeof...(T)> ColumnDescription
Definition dataRecorder.hpp:49
RecordingUnit(const std::string &comp_id, const std::string &unit_id, const std::string &unit_type, ServiceContainer &services)
Create a new RecordingIngestion.
Definition recordingUnit.cpp:19
Class for passing/receiving metadata to/from the repository.
Definition repositoryIf.hpp:146
Clock::time_point Timestamp
Definition repositoryIf.hpp:58
void Start() override
Start the recording.
Definition repositoryRecordingUnit.cpp:247
void Prepare(const std::filesystem::path &file_path) override
Prepare the recording.
Definition repositoryRecordingUnit.cpp:223
std::vector< std::filesystem::path > Stop() override
Stop the recording and wait for it's termination.
Definition repositoryRecordingUnit.cpp:255
~RepositoryRecordingUnit() override
Destructor.
Definition repositoryRecordingUnit.cpp:67
RepositoryRecordingUnit(const std::string &comp_id, const std::string &unit_id, ServiceContainer &services)
Create a new recording unit.
Definition repositoryRecordingUnit.cpp:43
Container class that holds services of any type.
Definition serviceContainer.hpp:39
Header file for ComponentMetricsIf.
log4cplus::Logger & GetLogger(const std::string &name="app")
Get handle to a specific logger.
Definition logger.cpp:192
Header file for DataPointPath.
Provides an abstract DataRecorder class as the output stage for a recording unit.
Provides macros and utilities for exception handling.
FitsRecorder allows to write ColumnData to into fits files in a specified directory.
Logging Support Library based on log4cplus.
Definition commandReplier.cpp:22
nlohmann::json JsonPayload
Type requirements:
Definition jsonPayload.hpp:25
AsSpanT< T > ToSpan(T &data)
Simple function that converts types that are convertible to spans to a span.
Definition recordingUtils.hpp:25
constexpr bool IS_SPAN_CONVERTIBLE
Small helper alias for IsSpanConvertible.
Definition recordingTypeTraits.hpp:66
std::uint64_t RtcUInt64
Definition basicTypes.hpp:45
FitsRecorder allows to write ColumnData to into fits files in a specified directory.
Abstract base class defining functionality common to all recording units.
A container that can hold any type of service.
std::vector< std::string > filter
filter for specific metadata keys (empty filter means record all)
Definition repositoryRecordingUnit.hpp:96
bool enabled
whether to produce an additional .jsonl file with metadata
Definition repositoryRecordingUnit.hpp:94
A timestring in ISO-8601 format.