22
33#include < cmath>
44#include < limits>
5+ #include < span>
56
67namespace sim {
78
89void write_to_csv (
9- const std::vector<std::pair<MetricsStorage, std::string> >& storages,
10- std::filesystem::path output_path) {
11- size_t count_storages = storages.size ();
12- // values[time][i] is a value of metric for i-th storage at time time;
13- // If there were no measurement at time, values[time][i] =
14- // std::numeric_limits<double>::quiet_NaN()
15- std::map<TimeNs, std::vector<double > > values;
16- double nan = std::numeric_limits<double >::quiet_NaN ();
17- std::vector<double > default_values (count_storages, nan);
18- for (size_t i = 0 ; i < count_storages; i++) {
19- for (const auto & [time, value] : storages[i].first .get_records ()) {
20- if (values.find (time) == values.end ()) {
21- values[time] = default_values;
10+ const std::vector<std::pair<MetricsStorage, std::string>>& storages,
11+ const std::filesystem::path output_path) {
12+ static constexpr std::size_t FLUSH_THRESHOLD = (1 << 20 ); // 1 MB
13+ const size_t count_storages = storages.size ();
14+
15+ using Sample = std::pair<TimeNs, double >;
16+ using SeriesSpan = std::span<const Sample>;
17+
18+ // References to the underlying data vectors
19+ std::vector<SeriesSpan> series;
20+ series.reserve (count_storages);
21+
22+ for (const auto & [storage, name] : storages) {
23+ const std::vector<Sample>& recs = storage.get_records ();
24+ series.emplace_back (recs.data (), recs.size ());
25+ }
26+
27+ // Indices of the current element for each storage
28+ std::vector<size_t > idx (count_storages, 0 );
29+
30+ const double nan = std::numeric_limits<double >::quiet_NaN ();
31+ // Most recent known values (forward-fill state)
32+ std::vector<double > values (count_storages, nan);
33+
34+ utils::create_all_directories (output_path);
35+ std::ofstream out (output_path, std::ios::binary);
36+ if (!out) {
37+ throw std::runtime_error (" Failed to open output file: " +
38+ output_path.string ());
39+ }
40+
41+ fmt::memory_buffer buffer;
42+ auto it = std::back_inserter (buffer);
43+
44+ // Header row
45+ fmt::format_to (it, " Time" );
46+ for (const auto & p : storages) {
47+ fmt::format_to (it, " ,{}" , p.second );
48+ }
49+ fmt::format_to (it, " \n " );
50+
51+ auto get_next_time = [&]() -> std::optional<TimeNs> {
52+ std::optional<TimeNs> result;
53+ for (size_t i = 0 ; i < count_storages; ++i) {
54+ const auto & recs = series[i];
55+ if (idx[i] < recs.size ()) {
56+ TimeNs t = recs[idx[i]].first ;
57+ if (!result || t < result.value ()) {
58+ result = t;
59+ }
2260 }
23- values[time][i] = value;
2461 }
25- }
62+ return result;
63+ };
2664
27- std::vector<double > previous_time_row = default_values;
28- // push values by time using increasing order of keys (time) in std::map
29- for (auto & [time, time_values] : values) {
30- for (size_t i = 0 ; i < count_storages; i++) {
31- if (std::isnan (time_values[i])) {
32- time_values[i] = previous_time_row[i];
33- } else {
34- previous_time_row[i] = time_values[i];
65+ auto flush_buffer = [&]() {
66+ out.write (buffer.data (), static_cast <std::streamsize>(buffer.size ()));
67+ buffer.clear ();
68+ };
69+
70+ // Main time-merge loop (k-way merge)
71+ for (auto next_time_opt = get_next_time (); next_time_opt.has_value ();
72+ next_time_opt = get_next_time ()) {
73+ TimeNs next_time = next_time_opt.value ();
74+ // 1) Update forward-filled values for all storages that have a record
75+ // at next_time
76+ for (size_t i = 0 ; i < count_storages; ++i) {
77+ const auto & recs = series[i];
78+ if (idx[i] < recs.size () && recs[idx[i]].first == next_time) {
79+ values[i] = recs[idx[i]].second ;
80+ ++idx[i];
3581 }
3682 }
37- }
83+ // 2) Append the row to the buffer
84+ fmt::format_to (it, " {}" , next_time.value ());
85+ for (double v : values) {
86+ fmt::format_to (it, " ,{}" , v);
87+ }
88+ fmt::format_to (it, " \n " );
3889
39- utils::create_all_directories (output_path);
40- std::ofstream out (output_path);
41- out << " Time" ;
42- for (size_t i = 0 ; i < count_storages; i++) {
43- out << ' ,' << storages[i].second ;
44- }
45- out << ' \n ' ;
46- for (const auto & [time, time_values] : values) {
47- out << time;
48- for (auto value : time_values) {
49- out << ' ,' << value;
90+ // 3) Optionally flush the buffer periodically to avoid memory growth
91+ // (e.g., every 1–8 MB)
92+ if (buffer.size () > FLUSH_THRESHOLD ) {
93+ flush_buffer ();
5094 }
51- out << ' \n ' ;
95+ }
96+
97+ // Final flush
98+ if (buffer.size ()) {
99+ flush_buffer ();
52100 }
53101}
54102
55- } // namespace sim
103+ } // namespace sim
0 commit comments