Implemented qt port + e2e
This commit is contained in:
@@ -0,0 +1,162 @@
|
||||
/**
|
||||
* @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 */
|
||||
Reference in New Issue
Block a user