diff --git a/source/link/link.cpp b/source/link/link.cpp index fe62b07c61..5a40a6217b 100644 --- a/source/link/link.cpp +++ b/source/link/link.cpp @@ -1,7 +1,6 @@ #include "link/link.hpp" #include "logger/logger.hpp" -#include "metrics/metrics_collector.hpp" #include "scheduler.hpp" namespace sim { @@ -15,8 +14,10 @@ Link::Link(Id a_id, std::weak_ptr a_from, std::weak_ptr a_to, m_to(a_to), m_speed(a_speed), m_propagation_delay(a_delay), - m_from_egress(a_max_from_egress_buffer_size), - m_to_ingress(a_max_to_ingress_buffer_size) { + m_from_egress(a_max_from_egress_buffer_size, a_id, + LinkQueueType::FromEgress), + m_to_ingress(a_max_to_ingress_buffer_size, a_id, + LinkQueueType::ToIngress) { if (a_from.expired() || a_to.expired()) { LOG_WARN("Passed link to device is expired"); } else if (a_speed == SpeedGbps(0)) { @@ -142,10 +143,6 @@ void Link::arrive(Packet packet) { return; } - MetricsCollector::get_instance().add_queue_size( - get_id(), Scheduler::get_instance().get_current_time(), - m_from_egress.get_size()); - m_to.lock()->notify_about_arrival( Scheduler::get_instance().get_current_time()); LOG_INFO("Packet arrived to the next device. Packet: " + diff --git a/source/link/link.hpp b/source/link/link.hpp index 6ef0b559f7..a440a81a36 100644 --- a/source/link/link.hpp +++ b/source/link/link.hpp @@ -4,7 +4,7 @@ #include "event/event.hpp" #include "link/i_link.hpp" -#include "packet_queue/simple_packet_queue.hpp" +#include "packet_queue/link_queue.hpp" namespace sim { @@ -70,10 +70,10 @@ class Link : public ILink, public std::enable_shared_from_this { TimeNs m_propagation_delay; // Queue at the ingress port of the m_to device - SimplePacketQueue m_from_egress; + LinkQueue m_from_egress; // Queue at the egress port of the m_to device - SimplePacketQueue m_to_ingress; + LinkQueue m_to_ingress; }; } // namespace sim diff --git a/source/link/packet_queue/i_packet_queue.hpp b/source/link/packet_queue/i_packet_queue.hpp index ea15171a58..84dcfa980f 100644 --- a/source/link/packet_queue/i_packet_queue.hpp +++ b/source/link/packet_queue/i_packet_queue.hpp @@ -8,7 +8,7 @@ class IPacketQueue { virtual ~IPacketQueue() = default; virtual bool push(Packet packet) = 0; - virtual Packet front() = 0; + virtual Packet front() const = 0; virtual void pop() = 0; virtual bool empty() const = 0; diff --git a/source/link/packet_queue/link_queue.cpp b/source/link/packet_queue/link_queue.cpp new file mode 100644 index 0000000000..b10024705a --- /dev/null +++ b/source/link/packet_queue/link_queue.cpp @@ -0,0 +1,49 @@ +#include "link_queue.hpp" + +#include "metrics/metrics_collector.hpp" +#include "scheduler.hpp" +#include "simple_packet_queue.hpp" + +namespace sim { + +std::string to_string(LinkQueueType type) { + switch (type) { + case LinkQueueType::FromEgress: + return "from_ingress_queue_size"; + case LinkQueueType::ToIngress: + return "to_ingress_queue_size"; + default: + LOG_ERROR(fmt::format("Undefined link queue type: {}", + static_cast(type))); + return "queue_size"; + } +} + +LinkQueue::LinkQueue(SizeByte a_queue_size, Id a_link_id, + LinkQueueType a_type) + : m_queue(a_queue_size), m_link_id(a_link_id), m_type(a_type) {} + +bool LinkQueue::push(Packet packet) { + bool result = m_queue.push(std::move(packet)); + MetricsCollector::get_instance().add_queue_size( + m_link_id, Scheduler::get_instance().get_current_time(), + m_queue.get_size(), m_type); + return result; +} + +Packet LinkQueue::front() const { return m_queue.front(); } + +void LinkQueue::pop() { + m_queue.pop(); + MetricsCollector::get_instance().add_queue_size( + m_link_id, Scheduler::get_instance().get_current_time(), + m_queue.get_size(), m_type); +} + +SizeByte LinkQueue::get_size() const { return m_queue.get_size(); } + +bool LinkQueue::empty() const { return m_queue.empty(); } + +SizeByte LinkQueue::get_max_size() const { return m_queue.get_max_size(); } + +} // namespace sim \ No newline at end of file diff --git a/source/link/packet_queue/link_queue.hpp b/source/link/packet_queue/link_queue.hpp new file mode 100644 index 0000000000..fed5ccaa40 --- /dev/null +++ b/source/link/packet_queue/link_queue.hpp @@ -0,0 +1,32 @@ +#pragma once + +#include "simple_packet_queue.hpp" + +namespace sim { + +enum class LinkQueueType { FromEgress, ToIngress }; + +std::string to_string(LinkQueueType type); + +// Class for two types of links: +// eggress queue of sourse link device or +// ingress queue of desination link device +class LinkQueue : public IPacketQueue { +public: + LinkQueue(SizeByte a_max_size, Id a_link_id, LinkQueueType a_type); + ~LinkQueue() = default; + + bool push(Packet packet) final; + Packet front() const final; + void pop() final; + + SizeByte get_size() const final; + bool empty() const final; + SizeByte get_max_size() const final; + +private: + SimplePacketQueue m_queue; + Id m_link_id; + LinkQueueType m_type; +}; +} // namespace sim \ No newline at end of file diff --git a/source/link/packet_queue/simple_packet_queue.cpp b/source/link/packet_queue/simple_packet_queue.cpp index 9a49b812a6..f49b0d370d 100644 --- a/source/link/packet_queue/simple_packet_queue.cpp +++ b/source/link/packet_queue/simple_packet_queue.cpp @@ -13,7 +13,7 @@ bool SimplePacketQueue::push(Packet packet) { return true; } -Packet SimplePacketQueue::front() { +Packet SimplePacketQueue::front() const { if (m_queue.empty()) { throw std::runtime_error("Can not get front packet from empty queue"); } diff --git a/source/link/packet_queue/simple_packet_queue.hpp b/source/link/packet_queue/simple_packet_queue.hpp index 317f58c81a..6784712941 100644 --- a/source/link/packet_queue/simple_packet_queue.hpp +++ b/source/link/packet_queue/simple_packet_queue.hpp @@ -1,5 +1,4 @@ #pragma once -#include #include #include "i_packet_queue.hpp" @@ -14,7 +13,7 @@ class SimplePacketQueue : public IPacketQueue { // returns true on succseed (remaining space is enought), false // otherwice bool push(Packet packet) final; - Packet front() final; + Packet front() const final; void pop() final; SizeByte get_size() const final; diff --git a/source/main.cpp b/source/main.cpp index bb34e37337..361e26c7dc 100644 --- a/source/main.cpp +++ b/source/main.cpp @@ -31,7 +31,7 @@ int main(const int argc, char **argv) { Logger::get_instance().disable_logs(); } - sim::MetricsCollector::get_instance().set_metrics_filter( + sim::MetricsCollector::set_metrics_filter( flags["metrics-filter"].as()); sim::YamlParser parser; diff --git a/source/metrics/draw_plots.cpp b/source/metrics/draw_plots.cpp new file mode 100644 index 0000000000..8770459eac --- /dev/null +++ b/source/metrics/draw_plots.cpp @@ -0,0 +1,33 @@ +#include "draw_plots.hpp" + +#include "utils/safe_matplot.hpp" + +namespace sim { + +void draw_on_same_plot(std::filesystem::path path, PlotMetricsData data, + PlotMetadata metadata) { + if (data.empty()) { + return; + } + auto fig = put_on_same_plot(data, metadata); + + matplot::safe_save(fig, path.string()); +} + +matplot::figure_handle put_on_same_plot(PlotMetricsData data, + PlotMetadata metadata) { + auto fig = matplot::figure(true); + auto ax = fig->current_axes(); + ax->hold(matplot::on); + + for (auto& [values, name] : data) { + values.draw_on_plot(fig, name); + } + ax->xlabel(metadata.x_label); + ax->ylabel(metadata.y_label); + ax->title(metadata.title); + ax->legend(std::vector()); + return fig; +} + +} // namespace sim \ No newline at end of file diff --git a/source/metrics/draw_plots.hpp b/source/metrics/draw_plots.hpp new file mode 100644 index 0000000000..12a07a0bc4 --- /dev/null +++ b/source/metrics/draw_plots.hpp @@ -0,0 +1,17 @@ +#pragma once +#include + +#include "metrics_storage.hpp" + +namespace sim { + +using PlotMetricsData = std::vector >; + +// Puts data from different DataStorage on one plot +matplot::figure_handle put_on_same_plot(PlotMetricsData data, + PlotMetadata metadata); + +// Draws data from different DataStorage on one plot +void draw_on_same_plot(std::filesystem::path path, PlotMetricsData data, + PlotMetadata metadata); +} // namespace sim diff --git a/source/metrics/links_queue_size_storage.cpp b/source/metrics/links_queue_size_storage.cpp new file mode 100644 index 0000000000..3158802ef0 --- /dev/null +++ b/source/metrics/links_queue_size_storage.cpp @@ -0,0 +1,106 @@ +#include "links_queue_size_storage.hpp" + +#include "draw_plots.hpp" +#include "link/i_link.hpp" +#include "multi_id_metrics_storage.hpp" +#include "utils/safe_matplot.hpp" +#include "write_to_csv.hpp" + +namespace sim { + +LinksQueueSizeStorage::LinksQueueSizeStorage(std::string a_filter) + : m_filter(a_filter) {} + +void LinksQueueSizeStorage::add_record(Id id, LinkQueueType type, TimeNs time, + double value) { + std::pair key = std::make_pair(id, type); + auto it = m_storage.find(key); + if (it == m_storage.end()) { + std::string filename = get_metrics_filename(id); + if (!std::regex_match(filename, m_filter)) { + m_storage[std::move(key)] = std::nullopt; + } else { + MetricsStorage new_storage; + new_storage.add_record(time, value); + m_storage.emplace(std::move(key), std::move(new_storage)); + } + } else if (it->second.has_value()) { + it->second->add_record(time, value); + } +} + +void LinksQueueSizeStorage::export_to_files( + std::filesystem::path output_dir_path) const { + std::map > > + multi_id_storage; + for (const auto& [key, values] : data()) { + auto [id, type] = key; + multi_id_storage[id].emplace_back(values, to_string(type)); + } + for (auto [id, storages] : multi_id_storage) { + write_to_csv(storages, output_dir_path / get_metrics_filename(id)); + } +} + +void LinksQueueSizeStorage::draw_plots( + std::filesystem::path output_dir_path) const { + // for data from both from_ingress and to_ingress queue sizes + std::map queue_size_data; + for (auto& [key, values] : data()) { + auto [link_id, type] = key; + std::string curve_name = to_string(type); + std::replace(curve_name.begin(), curve_name.end(), '_', ' '); + queue_size_data[link_id].emplace_back(values, curve_name); + } + for (auto [link_id, data] : queue_size_data) { + auto link = + IdentifierFactory::get_instance().get_object(link_id); + PlotMetadata metadata = { + "Time, ns", "Values, bytes", + fmt::format("Queue size from {} to {}", link->get_from()->get_id(), + link->get_to()->get_id())}; + + auto fig = put_on_same_plot(data, metadata); + auto ax = fig->current_axes(); + + auto limits = ax->xlim(); + + auto draw_gorizontal_line = [&limits]( + double line_y, std::string_view name, + std::initializer_list color) { + matplot::line(0, line_y, limits[1], line_y) + ->line_width(1.5) + .color(color) + .display_name(name); + }; + + draw_gorizontal_line(link->get_max_from_egress_buffer_size().value(), + "max from egress", {1.f, 0.f, 0.f}); + draw_gorizontal_line(link->get_max_to_ingress_queue_size().value(), + "max to ingress", {0.f, 0.f, 1.f}); + + ax->xlim({0, limits[1]}); + ax->color("white"); + + std::filesystem::path plot_path = + output_dir_path / fmt::format("{}.svg", link_id); + + matplot::safe_save(fig, plot_path.string()); + } +} + +std::map, MetricsStorage> +LinksQueueSizeStorage::data() const { + std::map, MetricsStorage> result; + for (auto [id, maybe_storage] : m_storage) { + if (maybe_storage) { + result[id] = maybe_storage.value(); + } + } + return result; +} + +std::string LinksQueueSizeStorage::get_metrics_filename(Id id) const { + return fmt::format("queue_size/{}.csv", id); +} +} // namespace sim \ No newline at end of file diff --git a/source/metrics/links_queue_size_storage.hpp b/source/metrics/links_queue_size_storage.hpp new file mode 100644 index 0000000000..045c0432ec --- /dev/null +++ b/source/metrics/links_queue_size_storage.hpp @@ -0,0 +1,35 @@ +#pragma once +#include +#include +#include +#include +#include + +#include "link/packet_queue/link_queue.hpp" +#include "metrics_storage.hpp" +#include "types.hpp" + +namespace sim { +class LinksQueueSizeStorage { +public: + LinksQueueSizeStorage(std::string filter); + + void add_record(Id id, LinkQueueType type, TimeNs time, double value); + void export_to_files(std::filesystem::path output_dir_path) const; + void draw_plots(std::filesystem::path output_dir_path) const; + + std::map, MetricsStorage> data() const; + +private: + std::string get_metrics_filename(Id id) const; + + // If m_storage does not contain some id, there was no check is metrics file + // name for id correspond to m_filter + // If m_storage[id] = std::nullopt, this check was failed + // Otherwice, check was succseed + std::map, std::optional> + m_storage; + + std::regex m_filter; +}; +} // namespace sim \ No newline at end of file diff --git a/source/metrics/metrics_collector.cpp b/source/metrics/metrics_collector.cpp index df67ca1062..d78328cc8e 100644 --- a/source/metrics/metrics_collector.cpp +++ b/source/metrics/metrics_collector.cpp @@ -2,6 +2,7 @@ #include +#include "draw_plots.hpp" #include "flow/i_flow.hpp" #include "link/i_link.hpp" #include "utils/identifier_factory.hpp" @@ -9,6 +10,17 @@ namespace sim { +std::string MetricsCollector::m_metrics_filter = ".*"; +bool MetricsCollector::m_is_initialised = false; + +MetricsCollector::MetricsCollector() + : m_RTT_storage("rtt", m_metrics_filter), + m_cwnd_storage("cwnd", m_metrics_filter), + m_rate_storage("rate", m_metrics_filter), + m_links_queue_size_storage(m_metrics_filter) { + m_is_initialised = true; +} + MetricsCollector& MetricsCollector::get_instance() { static MetricsCollector instance; return instance; @@ -27,42 +39,19 @@ void MetricsCollector::add_RTT(Id flow_id, TimeNs time, TimeNs value) { m_RTT_storage.add_record(std::move(flow_id), time, value.value()); } -void MetricsCollector::add_queue_size(Id link_id, TimeNs time, SizeByte value) { - m_queue_size_storage.add_record(std::move(link_id), time, value.value()); +void MetricsCollector::add_queue_size(Id link_id, TimeNs time, SizeByte value, + LinkQueueType type) { + m_links_queue_size_storage.add_record(link_id, type, time, value.value()); } void MetricsCollector::export_metrics_to_files( std::filesystem::path metrics_dir) const { m_RTT_storage.export_to_files(metrics_dir); - m_queue_size_storage.export_to_files(metrics_dir); + m_links_queue_size_storage.export_to_files(metrics_dir); m_cwnd_storage.export_to_files(metrics_dir); m_rate_storage.export_to_files(metrics_dir); } -// verctor of pairs -using PlotMetricsData = std::vector >; - -// Draws data from different DataStorage on one plot -static void draw_on_same_plot(std::filesystem::path path, PlotMetricsData data, - PlotMetadata metadata) { - if (data.empty()) { - return; - } - auto fig = matplot::figure(true); - auto ax = fig->current_axes(); - ax->hold(matplot::on); - - for (auto& [values, name] : data) { - values.draw_on_plot(fig, name); - } - ax->xlabel(metadata.x_label); - ax->ylabel(metadata.y_label); - ax->title(metadata.title); - ax->legend(std::vector()); - - matplot::safe_save(fig, path.string()); -} - void MetricsCollector::draw_cwnd_plot(std::filesystem::path path) const { PlotMetricsData data; std::transform( @@ -79,10 +68,11 @@ void MetricsCollector::draw_cwnd_plot(std::filesystem::path path) const { {"Time, ns", "CWND, packets", "CWND"}); } -void MetricsCollector::draw_RTT_plot(std::filesystem::path path) const { +void MetricsCollector::draw_delivery_rate_plot( + std::filesystem::path path) const { PlotMetricsData data; std::transform( - begin(m_RTT_storage.data()), end(m_RTT_storage.data()), + begin(m_rate_storage.data()), end(m_rate_storage.data()), std::back_inserter(data), [](auto const& pair) { auto flow = IdentifierFactory::get_instance().get_object(pair.first); @@ -92,14 +82,13 @@ void MetricsCollector::draw_RTT_plot(std::filesystem::path path) const { return std::make_pair(pair.second, name); }); draw_on_same_plot(path, std::move(data), - {"Time, ns", "RTT, ns", "Round Trip Time"}); + {"Time, ns", "Values, Gbps", "Delivery rate"}); } -void MetricsCollector::draw_delivery_rate_plot( - std::filesystem::path path) const { +void MetricsCollector::draw_RTT_plot(std::filesystem::path path) const { PlotMetricsData data; std::transform( - begin(m_rate_storage.data()), end(m_rate_storage.data()), + begin(m_RTT_storage.data()), end(m_RTT_storage.data()), std::back_inserter(data), [](auto const& pair) { auto flow = IdentifierFactory::get_instance().get_object(pair.first); @@ -109,36 +98,12 @@ void MetricsCollector::draw_delivery_rate_plot( return std::make_pair(pair.second, name); }); draw_on_same_plot(path, std::move(data), - {"Time, ns", "Values, Gbps", "Delivery rate"}); + {"Time, ns", "RTT, ns", "Round Trip Time"}); } void MetricsCollector::draw_queue_size_plots( std::filesystem::path dir_path) const { - for (auto& [link_id, values] : m_queue_size_storage.data()) { - auto link = - IdentifierFactory::get_instance().get_object(link_id); - auto fig = values.get_picture( - {"Time, ns", "Values, bytes", - fmt::format("Queue size from {} to {}", link->get_from()->get_id(), - link->get_to()->get_id())}); - auto ax = fig->current_axes(); - - auto limits = ax->xlim(); - matplot::line(0, link->get_max_from_egress_buffer_size().value(), - limits[1], - link->get_max_from_egress_buffer_size().value()) - ->line_width(1.5) - .color({1.f, 0.0f, 0.0f}); - - ax->xlim({0, limits[1]}); - - ax->color("white"); - - std::filesystem::path plot_path = - dir_path / fmt::format("{}.svg", link_id); - - matplot::safe_save(fig, plot_path.string()); - } + m_links_queue_size_storage.draw_plots(dir_path); } void MetricsCollector::draw_metric_plots( @@ -150,10 +115,13 @@ void MetricsCollector::draw_metric_plots( } void MetricsCollector::set_metrics_filter(const std::string& filter) { - m_RTT_storage.set_filter(filter); - m_cwnd_storage.set_filter(filter); - m_rate_storage.set_filter(filter); - m_queue_size_storage.set_filter(filter); + if (m_is_initialised) { + LOG_ERROR(fmt::format( + "Set metrics filter {} when MetricsCollector already initialized " + "with filter {}; no effect", + filter, m_metrics_filter)); + } + m_metrics_filter = filter; } } // namespace sim diff --git a/source/metrics/metrics_collector.hpp b/source/metrics/metrics_collector.hpp index ef6925f83c..37da6c7519 100644 --- a/source/metrics/metrics_collector.hpp +++ b/source/metrics/metrics_collector.hpp @@ -3,8 +3,9 @@ #include #include +#include "link/packet_queue/link_queue.hpp" +#include "links_queue_size_storage.hpp" #include "multi_id_metrics_storage.hpp" - namespace sim { class MetricsCollector { @@ -14,15 +15,16 @@ class MetricsCollector { void add_cwnd(Id flow_id, TimeNs time, double cwnd); void add_delivery_rate(Id flow_id, TimeNs time, SpeedGbps value); void add_RTT(Id flow_id, TimeNs time, TimeNs value); - void add_queue_size(Id link_id, TimeNs time, SizeByte value); + void add_queue_size(Id link_id, TimeNs time, SizeByte value, + LinkQueueType type = LinkQueueType::FromEgress); void export_metrics_to_files(std::filesystem::path metrics_dir) const; void draw_metric_plots(std::filesystem::path metrics_dir) const; - void set_metrics_filter(const std::string& filter); + static void set_metrics_filter(const std::string& filter); private: - MetricsCollector() {} + MetricsCollector(); MetricsCollector(const MetricsCollector&) = delete; MetricsCollector& operator=(const MetricsCollector&) = delete; @@ -31,14 +33,16 @@ class MetricsCollector { void draw_RTT_plot(std::filesystem::path path) const; void draw_queue_size_plots(std::filesystem::path dir_path) const; + static std::string m_metrics_filter; + static bool m_is_initialised; + // flow_ID --> vector of values - MultiIdMetricsStorage m_RTT_storage = MultiIdMetricsStorage("rtt"); - MultiIdMetricsStorage m_cwnd_storage = MultiIdMetricsStorage("cwnd"); - MultiIdMetricsStorage m_rate_storage = MultiIdMetricsStorage("rate"); + MultiIdMetricsStorage m_RTT_storage; + MultiIdMetricsStorage m_cwnd_storage; + MultiIdMetricsStorage m_rate_storage; // link_ID --> vector of values - MultiIdMetricsStorage m_queue_size_storage = - MultiIdMetricsStorage("queue_size"); + LinksQueueSizeStorage m_links_queue_size_storage; }; } // namespace sim diff --git a/source/metrics/metrics_storage.cpp b/source/metrics/metrics_storage.cpp index 23b268562d..cadc76d2cc 100644 --- a/source/metrics/metrics_storage.cpp +++ b/source/metrics/metrics_storage.cpp @@ -9,6 +9,11 @@ namespace sim { void MetricsStorage::add_record(TimeNs time, double value) { m_records.emplace_back(time, value); } + +std::vector > MetricsStorage::get_records() const { + return m_records; +} + void MetricsStorage::export_to_file(std::filesystem::path path) const { utils::create_all_directories(path); std::ofstream output_file(path); diff --git a/source/metrics/metrics_storage.hpp b/source/metrics/metrics_storage.hpp index 8794c572da..4e0cc376d2 100644 --- a/source/metrics/metrics_storage.hpp +++ b/source/metrics/metrics_storage.hpp @@ -2,22 +2,17 @@ #include #include -#include +#include "plot_metadata.hpp" #include "types.hpp" namespace sim { -struct PlotMetadata { - std::string x_label; - std::string y_label; - std::string title; -}; - class MetricsStorage { public: void add_record(TimeNs time, double value); + std::vector > get_records() const; void export_to_file(std::filesystem::path path) const; matplot::figure_handle get_picture(PlotMetadata metadata) const; void draw_plot(std::filesystem::path path, PlotMetadata metadata) const; diff --git a/source/metrics/multi_id_metrics_collector.cpp b/source/metrics/multi_id_metrics_collector.cpp index 5d0435b496..4c4f33e6ad 100644 --- a/source/metrics/multi_id_metrics_collector.cpp +++ b/source/metrics/multi_id_metrics_collector.cpp @@ -3,13 +3,15 @@ #include "multi_id_metrics_storage.hpp" namespace sim { -MultiIdMetricsStorage::MultiIdMetricsStorage(std::string a_metric_name) - : metric_name(std::move(a_metric_name)) {} +MultiIdMetricsStorage::MultiIdMetricsStorage(std::string a_metric_name, + std::string a_filter) + : metric_name(std::move(a_metric_name)), m_filter(a_filter) {} void MultiIdMetricsStorage::add_record(Id id, TimeNs time, double value) { auto it = m_storage.find(id); if (it == m_storage.end()) { - if (!std::regex_match(get_metrics_filename(id), m_filter)) { + std::string filename = get_metrics_filename(id); + if (!std::regex_match(filename, m_filter)) { m_storage[id] = std::nullopt; } else { MetricsStorage new_storage; @@ -39,11 +41,6 @@ std::unordered_map MultiIdMetricsStorage::data() const { } return result; } - -void MultiIdMetricsStorage::set_filter(std::string filter) { - m_filter = std::regex(filter); -} - std::string MultiIdMetricsStorage::get_metrics_filename(Id id) const { return fmt::format("{}/{}.txt", metric_name, id); } diff --git a/source/metrics/multi_id_metrics_storage.hpp b/source/metrics/multi_id_metrics_storage.hpp index aa44d8a138..626e7eef3b 100644 --- a/source/metrics/multi_id_metrics_storage.hpp +++ b/source/metrics/multi_id_metrics_storage.hpp @@ -5,19 +5,16 @@ #include #include "metrics_storage.hpp" - namespace sim { class MultiIdMetricsStorage { public: - MultiIdMetricsStorage(std::string a_metric_name); + MultiIdMetricsStorage(std::string a_metric_name, std::string a_filter); void add_record(Id id, TimeNs time, double value); void export_to_files(std::filesystem::path output_dir_path) const; std::unordered_map data() const; - void set_filter(std::string filter); - private: std::string get_metrics_filename(Id id) const; diff --git a/source/metrics/plot_metadata.hpp b/source/metrics/plot_metadata.hpp new file mode 100644 index 0000000000..4f62b3d35e --- /dev/null +++ b/source/metrics/plot_metadata.hpp @@ -0,0 +1,11 @@ +#pragma once +#include + +namespace sim { + +struct PlotMetadata { + std::string x_label; + std::string y_label; + std::string title; +}; +} // namespace sim \ No newline at end of file diff --git a/source/metrics/write_to_csv.cpp b/source/metrics/write_to_csv.cpp new file mode 100644 index 0000000000..37cacfbdf0 --- /dev/null +++ b/source/metrics/write_to_csv.cpp @@ -0,0 +1,55 @@ +#include "write_to_csv.hpp" + +#include +#include + +namespace sim { + +void write_to_csv( + const std::vector >& storages, + std::filesystem::path output_path) { + size_t count_storages = storages.size(); + // values[time][i] is a value of metric for i-th storage at time time; + // If there were no measurement at time, values[time][i] = + // std::numeric_limits::quiet_NaN() + std::map > values; + double nan = std::numeric_limits::quiet_NaN(); + std::vector default_values(count_storages, nan); + for (size_t i = 0; i < count_storages; i++) { + for (const auto& [time, value] : storages[i].first.get_records()) { + if (values.find(time) == values.end()) { + values[time] = default_values; + } + values[time][i] = value; + } + } + + std::vector previous_time_row = default_values; + // push values by time using increasing order of keys (time) in std::map + for (auto& [time, time_values] : values) { + for (size_t i = 0; i < count_storages; i++) { + if (std::isnan(time_values[i])) { + time_values[i] = previous_time_row[i]; + } else { + previous_time_row[i] = time_values[i]; + } + } + } + + utils::create_all_directories(output_path); + std::ofstream out(output_path); + out << "Time"; + for (size_t i = 0; i < count_storages; i++) { + out << ',' << storages[i].second; + } + out << '\n'; + for (const auto& [time, time_values] : values) { + out << time; + for (auto value : time_values) { + out << ',' << value; + } + out << '\n'; + } +} + +} // namespace sim \ No newline at end of file diff --git a/source/metrics/write_to_csv.hpp b/source/metrics/write_to_csv.hpp new file mode 100644 index 0000000000..b1710162be --- /dev/null +++ b/source/metrics/write_to_csv.hpp @@ -0,0 +1,18 @@ +#pragma once +#include +#include +#include +#include +#include + +#include "metrics_storage.hpp" +#include "utils/filesystem.hpp" +namespace sim { + +// by list of pairs (metrics storage, metric name) generates csv table and +// writes it to output_path +void write_to_csv( + const std::vector >& storages, + std::filesystem::path output_path); + +} // namespace sim \ No newline at end of file diff --git a/source/utils/filesystem.hpp b/source/utils/filesystem.hpp index 2af9832e02..b1e6edcf56 100644 --- a/source/utils/filesystem.hpp +++ b/source/utils/filesystem.hpp @@ -1,5 +1,6 @@ #pragma once #include +#include #include