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