Files
MARTe-Integrated-Components/Client/streamhub-qt/StatsDialog.cpp
2026-06-26 09:11:10 +02:00

163 lines
5.5 KiB
C++

/**
* @file StatsDialog.cpp
*/
#include "StatsDialog.h"
#include "Hub.h"
#include "Theme.h"
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QTableWidget>
#include <QHeaderView>
#include <QLabel>
#include <QPushButton>
#include <QScrollArea>
#include <QPainter>
#include <cstdio>
namespace shq {
using StreamHubClient::SourceStats;
/* ── HistogramWidget ─────────────────────────────────────────────────────── */
HistogramWidget::HistogramWidget(QWidget* parent) : QWidget(parent) {
setMinimumHeight(120);
}
void HistogramWidget::setData(const SourceStats& st) {
min_ = st.cycleHistMin;
max_ = st.cycleHistMax;
valid_ = (max_ > min_);
for (int i = 0; i < 20; i++) { bins_[i] = st.cycleHist[i]; }
update();
}
void HistogramWidget::paintEvent(QPaintEvent*) {
QPainter p(this);
p.fillRect(rect(), col::mantle());
if (!valid_) { return; }
double maxCount = 0.0;
for (int i = 0; i < 20; i++) { if (bins_[i] > maxCount) { maxCount = bins_[i]; } }
if (maxCount <= 0.0) { return; }
const int L = 4, R = 4, T = 4, B = 16;
QRectF area(L, T, width() - L - R, height() - T - B);
double bw = area.width() / 20.0;
p.setPen(Qt::NoPen);
p.setBrush(col::blue());
for (int i = 0; i < 20; i++) {
double h = (bins_[i] / maxCount) * area.height();
QRectF bar(area.left() + i * bw + 1, area.bottom() - h,
bw - 2, h);
p.drawRect(bar);
}
p.setPen(col::subtext());
QFont f = p.font(); f.setPointSize(7); p.setFont(f);
p.drawText(QRectF(L, height() - B, area.width() / 2, B),
Qt::AlignLeft | Qt::AlignVCenter,
QString::number(min_, 'g', 3) + " ms");
p.drawText(QRectF(L + area.width() / 2, height() - B, area.width() / 2, B),
Qt::AlignRight | Qt::AlignVCenter,
QString::number(max_, 'g', 3) + " ms");
}
/* ── StatsDialog ─────────────────────────────────────────────────────────── */
static const char* kCols[] = {
"Source", "State", "Cycles Rx", "Lost", "Rate Hz",
"Frags/cyc", "Bytes/cyc", "Cycle ms (avg±std)", "Cycle ms (min/max)"
};
static const int kNumCols = 9;
StatsDialog::StatsDialog(Hub* hub, QWidget* parent)
: QDialog(parent), hub_(hub) {
setWindowTitle("Statistics");
resize(760, 460);
auto* lay = new QVBoxLayout(this);
table_ = new QTableWidget(this);
table_->setColumnCount(kNumCols);
QStringList headers;
for (int i = 0; i < kNumCols; i++) { headers << kCols[i]; }
table_->setHorizontalHeaderLabels(headers);
table_->verticalHeader()->setVisible(false);
table_->setEditTriggers(QAbstractItemView::NoEditTriggers);
table_->horizontalHeader()->setSectionResizeMode(QHeaderView::ResizeToContents);
lay->addWidget(table_);
auto* scroll = new QScrollArea(this);
scroll->setWidgetResizable(true);
auto* histHost = new QWidget(scroll);
histLay_ = new QVBoxLayout(histHost);
scroll->setWidget(histHost);
lay->addWidget(scroll, 1);
auto* btnRow = new QHBoxLayout();
btnRow->addStretch(1);
auto* refreshBtn = new QPushButton("Refresh", this);
connect(refreshBtn, &QPushButton::clicked, this, [this]() {
hub_->sendGetStats();
refresh();
});
btnRow->addWidget(refreshBtn);
lay->addLayout(btnRow);
refresh();
}
void StatsDialog::refresh() {
const auto& sources = hub_->sources();
table_->setRowCount(static_cast<int>(sources.size()));
char buf[64];
for (int r = 0; r < static_cast<int>(sources.size()); r++) {
const Source& src = sources[r];
const SourceStats& st = src.stats;
auto set = [&](int c, const QString& s, const QColor* fg = nullptr) {
auto* it = new QTableWidgetItem(s);
if (fg) { it->setForeground(*fg); }
table_->setItem(r, c, it);
};
set(0, QString::fromStdString(src.id));
QColor stc = (st.state == "connected") ? col::green() : col::red();
set(1, QString::fromStdString(st.state), &stc);
set(2, QString::number(static_cast<qulonglong>(st.totalReceived)));
set(3, QString::number(static_cast<qulonglong>(st.totalLost)));
snprintf(buf, sizeof(buf), "%.2f \u00b1 %.2f", st.rateHz, st.rateStdHz);
set(4, buf);
snprintf(buf, sizeof(buf), "%.1f", st.fragsPerCycle); set(5, buf);
snprintf(buf, sizeof(buf), "%.0f", st.bytesPerCycle); set(6, buf);
snprintf(buf, sizeof(buf), "%.3f \u00b1 %.3f", st.cycleAvgMs, st.cycleStdMs);
set(7, buf);
snprintf(buf, sizeof(buf), "%.3f / %.3f", st.cycleMinMs, st.cycleMaxMs);
set(8, buf);
}
/* Rebuild histogram blocks. */
for (auto* w : histBlocks_) { w->deleteLater(); }
histBlocks_.clear();
hists_.clear();
for (const auto& src : sources) {
if (src.stats.cycleHistMax <= src.stats.cycleHistMin) { continue; }
auto* block = new QWidget();
auto* bl = new QVBoxLayout(block);
bl->setContentsMargins(0, 0, 0, 0);
bl->addWidget(new QLabel("Cycle time — " +
QString::fromStdString(src.id), block));
auto* h = new HistogramWidget(block);
h->setData(src.stats);
bl->addWidget(h);
histLay_->addWidget(block);
histBlocks_.push_back(block);
hists_.push_back(h);
}
}
} /* namespace shq */