#pragma once #include #include #include "metrics/MetricSeries.hpp" #include #include #include #include namespace tw::dbg::tools { /** * Plots one series against the seconds behind now, ending at the last second * that has fully elapsed. * * The line follows whichever statistic the series is read with. Reading an * average also shades the quietest and busiest value of each second behind it; * a total has no such range to show, since its buckets already hold every value * of that second added together. */ class MetricWidget { public: using Series = metrics::MetricSeries; private: std::string m_name; std::string m_unit; const Series* m_series; metrics::MetricField m_field; /** * The second in progress is left out: it only holds the part of itself * that has elapsed, so drawing it makes the newest point drop and climb * back once a second. */ static constexpr size_t SKIP_IN_PROGRESS = 1; std::vector m_ages; std::vector m_values; std::vector m_lows; std::vector m_highs; bool has_range() const { return m_field == metrics::MetricField::Avg; } /** Describes what is drawn, so the numbers always match the line. */ void draw_summary() const { if(m_values.empty()) { ImGui::TextUnformatted("no samples yet"); return; } auto [low, high] = std::minmax_element(m_values.begin(), m_values.end()); double total = 0.0; for(double value : m_values) { total += value; } ImGui::Text("min %.1f %s avg %.1f %s max %.1f %s", *low, m_unit.c_str(), total / (double)m_values.size(), m_unit.c_str(), *high, m_unit.c_str()); } public: MetricWidget(std::string name, std::string unit, const Series& series, metrics::MetricField field) : m_name(std::move(name)), m_unit(std::move(unit)), m_series(&series), m_field(field) { } /** Draws the last `history_in_seconds` seconds of the series. */ void draw(size_t history_in_seconds) { ImGui::PushID(m_name.c_str()); m_series->linearize(m_ages, m_values, m_field, history_in_seconds, SKIP_IN_PROGRESS); if(has_range()) { m_series->linearize(m_ages, m_lows, metrics::MetricField::Min, history_in_seconds, SKIP_IN_PROGRESS); m_series->linearize(m_ages, m_highs, metrics::MetricField::Max, history_in_seconds, SKIP_IN_PROGRESS); } draw_summary(); if(ImPlot::BeginPlot(m_name.c_str(), ImVec2(-1.0f, 150.0f))) { ImPlot::SetupAxes("seconds ago", m_unit.c_str(), ImPlotAxisFlags_None, ImPlotAxisFlags_AutoFit); ImPlot::SetupAxisLimits(ImAxis_X1, -(double)history_in_seconds, 0.0, ImGuiCond_Always); const int count = (int)m_values.size(); if(has_range() && count > 0) { ImPlot::PlotShaded("range", m_ages.data(), m_lows.data(), m_highs.data(), count); } if(count > 0) { ImPlot::PlotLine(m_name.c_str(), m_ages.data(), m_values.data(), count); } ImPlot::EndPlot(); } ImGui::PopID(); } }; }