63 const std::string& unit_id,
76 void Prepare(
const std::filesystem::path& file_path)
override;
81 void Start()
override;
88 std::vector<std::filesystem::path>
Stop()
override;
96 std::vector<std::string>
filter = {};
105 enum class DataSource : uint8_t {
117 DataSource source = DataSource::RTR;
120 MetaDataCfg metadata;
128 class MetaDataQueue {
137 std::optional<RepositoryIf::MetaData> TryPop();
145 std::queue<RepositoryIf::MetaData> m_queue;
156 class DataPointRecorderIf {
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;
171 template <
typename DpType>
172 class DataPointRecorder :
public DataPointRecorderIf {
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>;
179 DataPointRecorder(RepositorySubscriberIf& repo,
const DataPointCfg& cfg)
183 , m_fits_writer(COLUMNS)
184 , m_subscription({}) {
187 void Prepare(std::filesystem::path file_path)
override {
189 m_fits_writer.SetColumnLength(2, m_repo.GetDataPointSize(m_cfg.name));
192 if (m_cfg.source == DataPointCfg::DataSource::RTR) {
193 m_fits_writer.SetDisabledFields({
false,
false,
false});
196 m_fits_writer.SetDisabledFields({
false,
true,
false});
200 std::string filename = m_cfg.name.ToString();
201 if (filename.starts_with(
'/')) {
202 filename.erase(0, 1);
204 std::ranges::replace(filename,
'/',
'_');
206 m_pathname_fits = file_path / (filename + m_fits_writer.DefaultFileExtension());
207 m_pathname_jsonl = file_path / (filename +
".jsonl");
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);
216 void Start()
override {
218 if (m_cfg.capture.on_change) {
219 m_subscription = std::move(m_repo.Subscribe(
221 [
this](
const auto& path,
const auto& metadata) { m_queue.Push(metadata); },
226 if (m_cfg.capture.on_start) {
227 RetrieveAndRecord(std::nullopt);
231 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) {
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);
253 size_t HasWork()
override {
254 return m_queue.Size();
257 size_t Work()
override {
258 size_t work_counter = 0;
260 if (
auto md = m_queue.TryPop(); md.has_value()) {
261 RetrieveAndRecord(md);
271 void RetrieveAndRecord(
const std::optional<RepositoryIf::MetaData>& md_cb) {
274 RepositoryIf::MetaData md_read;
275 RepositoryIf::BatchRequest req;
276 req.ReadDataPoint(m_cfg.name, value, md_read);
277 m_repo.SendRequest(req).Wait();
280 SequenceIdType sequence_id = 0;
282 bool write_sample =
true;
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;
293 "DataPointRecorder: pathname: '{}' missed sequence_id: '{}'.",
301 m_fits_writer.Write(AsTuple(ts, sequence_id, value));
303 if (m_cfg.metadata.enabled) {
305 if (m_cfg.metadata.filter.empty()) {
306 m_md_writer << md_in <<
"\n";
309 for (
const auto& filter : m_cfg.metadata.filter) {
310 if (md_in.contains(filter)) {
311 md_out[filter] = md_in.at(filter);
314 m_md_writer << md_out <<
"\n";
321 uint64_t sequence_id,
322 const DpType& data) {
323 auto t = ts.time_since_epoch().count();
325 return std::make_tuple(t, sequence_id,
ToSpan(data));
327 return std::make_tuple(t, sequence_id, data);
333 {{
"timestamp"sv,
""sv}, {
"sequence_id"sv,
""sv}, {
"payload"sv,
""sv}}};
335 log4cplus::Logger& m_logger;
336 RepositorySubscriberIf& m_repo;
338 MetaDataQueue m_queue;
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;
349 void ReadConfigAndCreateDataPointRecorders();
351 std::unique_ptr<DataPointRecorderIf>
352 MakeDataPointRecorder(RepositorySubscriberIf& repo,
const DataPointCfg& dp_cfg);
354 void Process(
const std::stop_token& st,
const std::filesystem::path& base_path);
356 using TypeMapType = std::map<std::type_index,
357 std::function<std::unique_ptr<DataPointRecorderIf>(
358 RepositorySubscriberIf&,
const DataPointCfg&)>>;
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);
368 static const TypeMapType TYPE_MAP;
370 inline static constexpr std::string_view RTR_PATH_NUM_WORKERS =
371 "/{}/dynamic/rec_units/{}/num_workers";
373 inline static constexpr std::string_view RTR_PATH_DATA_POINT_LIST =
374 "/{}/dynamic/rec_units/{}/datapoint_list";
376 inline static constexpr size_t DEFAULT_NUM_WORKERS = 3;
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;