62 const std::string& unit_id,
75 void Prepare(
const std::filesystem::path& file_path)
override;
80 void Start()
override;
87 std::vector<std::filesystem::path>
Stop()
override;
95 std::vector<std::string>
filter = {};
104 enum class DataSource : uint8_t {
116 DataSource source = DataSource::RTR;
119 MetaDataCfg metadata;
127 class MetaDataQueue {
136 std::optional<RepositoryIf::MetaData> TryPop();
144 std::queue<RepositoryIf::MetaData> m_queue;
155 class DataPointRecorderIf {
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;
170 template <
typename DpType>
171 class DataPointRecorder :
public DataPointRecorderIf {
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>;
178 DataPointRecorder(RepositorySubscriberIf& repo,
const DataPointCfg& cfg)
182 , m_fits_writer(COLUMNS)
183 , m_subscription({}) {
186 void Prepare(std::filesystem::path file_path)
override {
188 m_fits_writer.SetColumnLength(2, m_repo.GetDataPointSize(m_cfg.name));
191 if (m_cfg.source == DataPointCfg::DataSource::RTR) {
192 m_fits_writer.SetDisabledFields({
false,
false,
false});
195 m_fits_writer.SetDisabledFields({
false,
true,
false});
199 std::string filename = m_cfg.name.ToString();
200 if (filename.starts_with(
'/')) {
201 filename.erase(0, 1);
203 std::ranges::replace(filename,
'/',
'_');
205 m_pathname_fits = file_path / (filename + m_fits_writer.DefaultFileExtension());
206 m_pathname_jsonl = file_path / (filename +
".jsonl");
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);
215 void Start()
override {
217 if (m_cfg.capture.on_change) {
218 m_subscription = std::move(m_repo.Subscribe(
220 [
this](
const auto& path,
const auto& metadata) { m_queue.Push(metadata); },
225 if (m_cfg.capture.on_start) {
226 RetrieveAndRecord(std::nullopt);
230 std::vector<std::filesystem::path> Stop()
override {
232 if (m_cfg.capture.on_change) {
233 m_subscription.Unsubscribe();
236 if (m_cfg.capture.on_stop) {
237 RetrieveAndRecord(std::nullopt);
240 m_fits_writer.Close();
241 if (m_cfg.metadata.enabled) {
246 m_fits_writer.Close();
247 if (m_cfg.metadata.enabled) {
250 throw_with_nested(CII_MAKE_EXCEPTION_WITH_NESTED(
252 std::current_exception(),
253 std::format(
"Problem to stop DataPointRecorder for data path: {}",
254 m_cfg.name.ToString())));
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);
265 size_t HasWork()
override {
266 return m_queue.Size();
269 size_t Work()
override {
270 size_t work_counter = 0;
272 if (
auto md = m_queue.TryPop(); md.has_value()) {
273 RetrieveAndRecord(md);
283 void RetrieveAndRecord(
const std::optional<RepositoryIf::MetaData>& md_cb) {
286 RepositoryIf::MetaData md_read;
287 RepositoryIf::BatchRequest req;
288 req.ReadDataPoint(m_cfg.name, value, md_read);
289 m_repo.SendRequest(req).Wait();
292 SequenceIdType sequence_id = 0;
294 bool write_sample =
true;
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;
305 "DataPointRecorder: pathname: '{}' missed sequence_id: '{}'.",
306 m_cfg.name.ToString(),
313 m_fits_writer.Write(AsTuple(ts, sequence_id, value));
315 if (m_cfg.metadata.enabled) {
317 if (m_cfg.metadata.filter.empty()) {
318 m_md_writer << md_in <<
"\n";
321 for (
const auto& filter : m_cfg.metadata.filter) {
322 if (md_in.contains(filter)) {
323 md_out[filter] = md_in.at(filter);
326 m_md_writer << md_out <<
"\n";
333 uint64_t sequence_id,
334 const DpType& data) {
335 auto t = ts.time_since_epoch().count();
337 return std::make_tuple(t, sequence_id,
ToSpan(data));
339 return std::make_tuple(t, sequence_id, data);
345 {{
"timestamp"sv,
""sv}, {
"sequence_id"sv,
""sv}, {
"payload"sv,
""sv}}};
347 log4cplus::Logger& m_logger;
348 RepositorySubscriberIf& m_repo;
350 MetaDataQueue m_queue;
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;
361 void ReadConfigAndCreateDataPointRecorders();
363 std::unique_ptr<DataPointRecorderIf>
364 MakeDataPointRecorder(RepositorySubscriberIf& repo,
const DataPointCfg& dp_cfg);
366 void Process(
const std::stop_token& st,
const std::filesystem::path& base_path);
368 using TypeMapType = std::map<std::type_index,
369 std::function<std::unique_ptr<DataPointRecorderIf>(
370 RepositorySubscriberIf&,
const DataPointCfg&)>>;
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);
380 static const TypeMapType TYPE_MAP;
382 inline static constexpr std::string_view RTR_PATH_NUM_WORKERS =
383 "/{}/dynamic/rec_units/{}/num_workers";
385 inline static constexpr std::string_view RTR_PATH_DATA_POINT_LIST =
386 "/{}/dynamic/rec_units/{}/datapoint_list";
388 inline static constexpr size_t DEFAULT_NUM_WORKERS = 3;
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;