Files
Towards/modules/client/src/debug/tools/MetricWidget.hpp
T

116 lines
3.4 KiB
C++
Raw Normal View History

2026-07-18 14:31:15 +02:00
#pragma once
#include <imgui.h>
#include <implot.h>
2026-07-22 17:34:44 +02:00
#include "metrics/MetricSeries.hpp"
#include <algorithm>
#include <chrono>
#include <string>
#include <vector>
2026-07-18 14:31:15 +02:00
namespace tw::dbg::tools {
2026-07-22 17:34:44 +02:00
/**
* 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.
*/
2026-07-18 14:31:15 +02:00
class MetricWidget {
public:
2026-07-22 17:34:44 +02:00
using Series = metrics::MetricSeries<std::chrono::seconds>;
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<double> m_ages;
std::vector<double> m_values;
std::vector<double> m_lows;
std::vector<double> m_highs;
bool has_range() const {
return m_field == metrics::MetricField::Avg;
2026-07-18 14:31:15 +02:00
}
2026-07-22 17:34:44 +02:00
/** 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;
}
2026-07-18 14:31:15 +02:00
2026-07-22 17:34:44 +02:00
auto [low, high] = std::minmax_element(m_values.begin(), m_values.end());
2026-07-18 14:31:15 +02:00
2026-07-22 17:34:44 +02:00
double total = 0.0;
for(double value : m_values) {
total += value;
}
2026-07-18 14:31:15 +02:00
2026-07-22 17:34:44 +02:00
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());
2026-07-18 14:31:15 +02:00
}
2026-07-22 17:34:44 +02:00
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) {
2026-07-18 14:31:15 +02:00
ImGui::PushID(m_name.c_str());
2026-07-22 17:34:44 +02:00
m_series->linearize(m_ages, m_values, m_field, history_in_seconds, SKIP_IN_PROGRESS);
2026-07-18 14:31:15 +02:00
2026-07-22 17:34:44 +02:00
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);
2026-07-18 14:31:15 +02:00
}
2026-07-22 17:34:44 +02:00
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);
2026-07-18 14:31:15 +02:00
2026-07-22 17:34:44 +02:00
const int count = (int)m_values.size();
2026-07-18 14:31:15 +02:00
2026-07-22 17:34:44 +02:00
if(has_range() && count > 0) {
ImPlot::PlotShaded("range", m_ages.data(), m_lows.data(), m_highs.data(), count);
2026-07-18 14:31:15 +02:00
}
2026-07-22 17:34:44 +02:00
if(count > 0) {
ImPlot::PlotLine(m_name.c_str(), m_ages.data(), m_values.data(), count);
}
2026-07-18 14:31:15 +02:00
ImPlot::EndPlot();
}
ImGui::PopID();
}
};
}