Skip to content

Commit 86a60c8

Browse files
Az3gitAlexey Zharkov
andauthored
REFACTORING: write_to_csv optimization (#489)
close #491 --------- Co-authored-by: Alexey Zharkov <alexezharkov@yandex.ru>
1 parent 8ce50c5 commit 86a60c8

10 files changed

Lines changed: 123 additions & 65 deletions

File tree

source/connection/flow/tcp/tcp_flow.cpp

Lines changed: 19 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,8 @@ TcpFlow::TcpFlow(Id a_id, std::shared_ptr<IConnection> a_conn,
4646
}
4747

4848
void TcpFlow::update(Packet packet) {
49-
PacketType type =
50-
static_cast<PacketType>(packet.flags.get_flag(m_packet_type_label).value_or_throw());
49+
PacketType type = static_cast<PacketType>(
50+
packet.flags.get_flag(m_packet_type_label).value_or_throw());
5151
if (m_src.expired()) {
5252
LOG_ERROR(fmt::format("Sender exprired for flow {}; ignore packet {}",
5353
to_string(), packet.to_string()));
@@ -317,7 +317,8 @@ void TcpFlow::process_ack(Packet ack, std::size_t confirm_count) {
317317
Packet TcpFlow::generate_data_packet(PacketNum packet_num) {
318318
Packet packet;
319319
packet.flags = get_flag_manager();
320-
packet.flags.set_flag(m_packet_type_label, PacketType::DATA).log_err_if_not_present("Failed to set packet type (DATA)");
320+
packet.flags.set_flag(m_packet_type_label, PacketType::DATA)
321+
.log_err_if_not_present("Failed to set packet type (DATA)");
321322

322323
set_avg_rtt_if_present(packet);
323324
packet.size = m_packet_size;
@@ -335,7 +336,8 @@ Packet TcpFlow::generate_data_packet(PacketNum packet_num) {
335336
void TcpFlow::set_avg_rtt_if_present(Packet& packet) {
336337
std::optional<TimeNs> avg_rtt = m_rtt_statistics.get_mean();
337338
if (avg_rtt.has_value()) {
338-
set_avg_rtt_flag(packet.flags, avg_rtt.value()).log_err_if_not_present("Failed to set average RTT");
339+
set_avg_rtt_flag(packet.flags, avg_rtt.value())
340+
.log_err_if_not_present("Failed to set average RTT");
339341
}
340342
}
341343

@@ -410,21 +412,26 @@ Packet TcpFlow::create_ack(Packet data) {
410412
ack.congestion_experienced = data.congestion_experienced;
411413

412414
ack.flags = get_flag_manager();
413-
ack.flags.set_flag(
414-
m_packet_type_label,
415-
(M_COLLECTIVE_ACK_SUPPORT ? PacketType::COLLECTIVE_ACK
416-
: PacketType::ACK)).log_err_if_not_present("Failed to set ACK flag");
417-
ack.flags.set_flag(m_ack_ttl_label, data.ttl).log_err_if_not_present("Failed to set TTL flag");
415+
ack.flags
416+
.set_flag(m_packet_type_label,
417+
(M_COLLECTIVE_ACK_SUPPORT ? PacketType::COLLECTIVE_ACK
418+
: PacketType::ACK))
419+
.log_err_if_not_present("Failed to set ACK flag");
420+
ack.flags.set_flag(m_ack_ttl_label, data.ttl)
421+
.log_err_if_not_present("Failed to set TTL flag");
418422

419423
utils::StrExpected<TimeNs> exp_avg_rtt = get_avg_rtt_label(data.flags);
420424
// TODO: use LOG_INFO for this part
421-
bool is_not_present = exp_avg_rtt.log_err_if_not_present(fmt::format("avg rtt flag does not set in data packet {} so it "
422-
"will not be set in ack {}", data.to_string(), ack.to_string()));
425+
bool is_not_present = exp_avg_rtt.log_err_if_not_present(
426+
fmt::format("avg rtt flag does not set in data packet {} so it "
427+
"will not be set in ack {}",
428+
data.to_string(), ack.to_string()));
423429
if (is_not_present) {
424430
return ack;
425431
}
426432

427-
set_avg_rtt_flag(ack.flags, exp_avg_rtt.value()).log_err_if_not_present("Failed to set average RTT");
433+
set_avg_rtt_flag(ack.flags, exp_avg_rtt.value())
434+
.log_err_if_not_present("Failed to set average RTT");
428435
return ack;
429436
}
430437

source/device/hashers/adaptive_flowlet_hasher.cpp

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,9 @@ std::uint32_t AdaptiveFlowletHasher::get_hash(const Packet& packet) {
3434
TimeNs elapsed_from_last_seen = curr_time - last_seen;
3535

3636
utils::StrExpected<TimeNs> exp_avg_rtt = get_avg_rtt_label(packet.flags);
37-
bool is_not_present = exp_avg_rtt.log_err_if_not_present("Adaptive flowlet hasher could not find avg rtt;"
38-
"returned previous hash");
37+
bool is_not_present = exp_avg_rtt.log_err_if_not_present(
38+
"Adaptive flowlet hasher could not find avg rtt;"
39+
"returned previous hash");
3940
if (is_not_present) {
4041
return ecmp_hash + shift;
4142
}

source/metrics/metrics_storage.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@ void MetricsStorage::add_record(TimeNs time, double value) {
1010
m_records.emplace_back(time, value);
1111
}
1212

13-
std::vector<std::pair<TimeNs, double> > MetricsStorage::get_records() const {
13+
const std::vector<std::pair<TimeNs, double>>& MetricsStorage::get_records()
14+
const {
1415
return m_records;
1516
}
1617

source/metrics/metrics_storage.hpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,15 +12,15 @@ class MetricsStorage {
1212
public:
1313
void add_record(TimeNs time, double value);
1414

15-
std::vector<std::pair<TimeNs, double> > get_records() const;
15+
const std::vector<std::pair<TimeNs, double>>& get_records() const;
1616
void export_to_file(std::filesystem::path path) const;
1717
matplot::figure_handle get_picture(PlotMetadata metadata) const;
1818
void draw_plot(std::filesystem::path path, PlotMetadata metadata) const;
1919
void draw_on_plot(matplot::figure_handle& fig,
2020
std::string_view name = "") const;
2121

2222
private:
23-
std::vector<std::pair<TimeNs, double> > m_records;
23+
std::vector<std::pair<TimeNs, double>> m_records;
2424
};
2525

2626
} // namespace sim

source/metrics/write_to_csv.cpp

Lines changed: 85 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -2,54 +2,102 @@
22

33
#include <cmath>
44
#include <limits>
5+
#include <span>
56

67
namespace sim {
78

89
void 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

source/packet.hpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@
33
#include <string>
44

55
#include "connection/flow/i_flow.hpp"
6-
#include "utils/flag_manager.hpp"
76
#include "data.hpp"
7+
#include "utils/flag_manager.hpp"
88

99
namespace sim {
1010

source/parser/topology/ecn/ecn_parser.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,4 @@ ECN EcnParser::parse_ecn(const ConfigNode& node) {
1010
return ECN(min, max, probability);
1111
}
1212

13-
}
13+
} // namespace sim

source/utils/flag_manager.hpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ class FlagManager {
4848
}
4949

5050
[[nodiscard]] utils::StrExpected<void> set_flag(FlagId id,
51-
BitStorage value) {
51+
BitStorage value) {
5252
auto it = m_flag_manager.find(id);
5353
if (it == m_flag_manager.end()) {
5454
return std::unexpected(

source/utils/str_expected.hpp

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ class StrExpected : public std::expected<T, std::string> {
1717
StrExpected(std::unexpected<U> a_unexpected)
1818
: std::expected<T, std::string>(a_unexpected) {
1919
static_assert(std::is_constructible_v<std::expected<T, std::string>,
20-
std::unexpected<U> >);
20+
std::unexpected<U>>);
2121
}
2222

2323
StrExpected(T a_value)
@@ -72,7 +72,8 @@ class StrExpected : public std::expected<T, std::string> {
7272
return result;
7373
}
7474

75-
bool apply_if_not_present(std::function<void(const std::string&)> apply_func) {
75+
bool apply_if_not_present(
76+
std::function<void(const std::string&)> apply_func) {
7677
bool result = !this->has_value();
7778
if (result) {
7879
apply_func(this->error());
@@ -102,8 +103,7 @@ class StrExpected<void> : public std::expected<void, std::string> {
102103
std::unexpected<U>>);
103104
}
104105

105-
StrExpected()
106-
: std::expected<void, std::string>(std::in_place) {}
106+
StrExpected() : std::expected<void, std::string>(std::in_place) {}
107107

108108
template <typename TErr = std::runtime_error>
109109
void value_or_throw() const {
@@ -154,7 +154,8 @@ class StrExpected<void> : public std::expected<void, std::string> {
154154
return result;
155155
}
156156

157-
bool apply_if_not_present(std::function<void(const std::string&)> apply_func) {
157+
bool apply_if_not_present(
158+
std::function<void(const std::string&)> apply_func) {
158159
bool result = !this->has_value();
159160
if (result) {
160161
apply_func(this->error());

test/_mocks/new_conn_mock.cpp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,10 @@
33
namespace test {
44

55
NewConnectionMock::NewConnectionMock(
6-
Id a_id,
7-
[[maybe_unused]] std::shared_ptr<sim::IHost> a_src,
6+
Id a_id, [[maybe_unused]] std::shared_ptr<sim::IHost> a_src,
87
[[maybe_unused]] std::shared_ptr<sim::IHost> a_dest,
9-
std::shared_ptr<sim::INewMPLB> a_mplb) : m_id(std::move(a_id)) {
8+
std::shared_ptr<sim::INewMPLB> a_mplb)
9+
: m_id(std::move(a_id)) {
1010
m_context.mplb = std::move(a_mplb);
1111
}
1212

0 commit comments

Comments
 (0)