From 492cac2ee22ea4b8f62774362bf3003d13f45319 Mon Sep 17 00:00:00 2001 From: jolavillette Date: Sat, 17 Jan 2026 13:39:27 +0100 Subject: [PATCH] BW statistics: polish, remove useless values, display outqueue size in bytes, display drain = outqueue / outspeed --- .../src/gui/statistics/BwCtrlWindow.cpp | 263 +++++++----------- .../src/gui/statistics/BwCtrlWindow.h | 53 +++- .../src/gui/statistics/BwCtrlWindow.ui | 20 +- 3 files changed, 152 insertions(+), 184 deletions(-) diff --git a/retroshare-gui/src/gui/statistics/BwCtrlWindow.cpp b/retroshare-gui/src/gui/statistics/BwCtrlWindow.cpp index adc65ab82..49bf855b0 100644 --- a/retroshare-gui/src/gui/statistics/BwCtrlWindow.cpp +++ b/retroshare-gui/src/gui/statistics/BwCtrlWindow.cpp @@ -24,6 +24,7 @@ #include "util/RsQtVersion.h" #include #include +#include #include #include @@ -96,69 +97,44 @@ void BWListDelegate::paint(QPainter * painter, const QStyleOptionViewItem & opti switch(index.column()) { case COLUMN_IN_RATE: - temp = QString::asprintf("%.3f ", index.data().toFloat()); - //temp=QString::number(index.data().toFloat()); - painter->drawText(option.rect, Qt::AlignRight, temp); - break; - case COLUMN_IN_MAX: - temp = QString::asprintf("%.3f ", index.data().toFloat()); - //temp=QString::number(index.data().toFloat()); - painter->drawText(option.rect, Qt::AlignRight, temp); - break; - case COLUMN_IN_QUEUE: - temp=QString::number(index.data().toInt()); - painter->drawText(option.rect, Qt::AlignRight, temp); - break; - case COLUMN_IN_ALLOC: - flValue = index.data().toFloat(); - if (flValue < std::numeric_limits::max()){ - temp = QString::asprintf("%.3f ", flValue); + case COLUMN_IN_MAX: + case COLUMN_IN_ALLOWED: + case COLUMN_OUT_RATE: + case COLUMN_OUT_MAX: + case COLUMN_OUT_ALLOWED: + { + QVariant d = index.data(); + if (!d.isValid() || d.isNull()) { + temp = ""; // Display empty instead of 0.0 } else { - temp=strNA; + temp = QString::asprintf("%.1f ", d.toFloat()); } - painter->drawText(option.rect, Qt::AlignRight, temp); - break; - case COLUMN_IN_ALLOC_SENT: - qi64Value = index.data().value(); - if (qi64Value < std::numeric_limits::max()){ - temp= QString::number(qi64Value); - } else { - temp = strNA; + painter->drawText(option.rect, Qt::AlignRight | Qt::AlignVCenter, temp); } - painter->drawText(option.rect, Qt::AlignRight, temp); break; - case COLUMN_OUT_RATE: - temp = QString::asprintf("%.3f ", index.data().toFloat()); - //temp=QString::number(index.data().toFloat()); - painter->drawText(option.rect, Qt::AlignRight, temp); - break; - case COLUMN_OUT_MAX: - temp = QString::asprintf("%.3f ", index.data().toFloat()); - //temp=QString::number(index.data().toFloat()); - painter->drawText(option.rect, Qt::AlignRight, temp); - break; - case COLUMN_OUT_QUEUE: - temp=QString::number(index.data().toInt()); - painter->drawText(option.rect, Qt::AlignRight, temp); - break; - case COLUMN_OUT_ALLOC: - flValue = index.data().toFloat(); - if (flValue < std::numeric_limits::max()){ - temp=QString::number(flValue); - } else { - temp = strNA; - } - painter->drawText(option.rect, Qt::AlignRight, temp); - break; - case COLUMN_OUT_ALLOC_SENT: - qi64Value = index.data().value(); - if (qi64Value < std::numeric_limits::max()){ - temp= QString::number(qi64Value); - } else { - temp = strNA; - } - painter->drawText(option.rect, Qt::AlignRight, temp); + + case COLUMN_IN_QUEUE_ITEMS: + case COLUMN_OUT_QUEUE_ITEMS: + temp = QString::number(index.data().toInt()); + painter->drawText(option.rect, Qt::AlignRight, temp); + break; + case COLUMN_OUT_QUEUE_BYTES: + qi64Value = index.data().value(); + if (qi64Value >= 1024 * 1024) + temp = QString::asprintf("%.2f MB ", qi64Value / (1024.0 * 1024.0)); + else if (qi64Value >= 1024) + temp = QString::asprintf("%.1f KB ", qi64Value / 1024.0); + else + temp = QString::number(qi64Value) + " B "; + painter->drawText(option.rect, Qt::AlignRight, temp); + break; + + case COLUMN_DRAIN: + // Drain time: Integer only, no decimals, no "s" + temp = QString::number((int)index.data().toFloat()) + " "; + painter->drawText(option.rect, Qt::AlignRight | Qt::AlignVCenter, temp); break; + default: painter->drawText(option.rect, Qt::AlignLeft, index.data().toString()); } @@ -182,17 +158,22 @@ BwCtrlWindow::BwCtrlWindow(QWidget *parent) : RsAutoUpdatePage(1000,parent) { setupUi(this); - - BWDelegate = new BWListDelegate(); + BWDelegate = new BWListDelegate(this); bwTreeWidget->setItemDelegate(BWDelegate); - //float FS = QFontMetricsF(font()).height(); - //float fact = FS/14.0 ; + QHeaderView *header = bwTreeWidget->header(); - /* Set header resize modes and initial section sizes Peer TreeView*/ - QHeaderView * _header = bwTreeWidget->header () ; -// _header->resizeSection ( COLUMN_RSNAME, 170*fact ); - QHeaderView_setSectionResizeMode(_header, QHeaderView::Interactive); + // Set Header alignments + for (int i = 0; i < COLUMN_COUNT; ++i) { + if (i == COLUMN_RSNAME || i == COLUMN_PEERID) + bwTreeWidget->headerItem()->setTextAlignment(i, Qt::AlignLeft | Qt::AlignVCenter); + else + bwTreeWidget->headerItem()->setTextAlignment(i, Qt::AlignRight | Qt::AlignVCenter); + } + + // Ensure the Totals row doesn't get messed up by initial sorting + bwTreeWidget->setSortingEnabled(true); + header->setSortIndicator(COLUMN_RSNAME, Qt::AscendingOrder); } BwCtrlWindow::~BwCtrlWindow() @@ -225,41 +206,51 @@ void BwCtrlWindow::updateBandwidth() { QTreeWidget *peerTreeWidget = bwTreeWidget; + // Disable sorting while clearing and refilling to avoid UI glitches + peerTreeWidget->setSortingEnabled(false); peerTreeWidget->clear(); RsConfigDataRates totalRates; std::map rateMap; std::map::iterator it; - rsConfig->getTotalBandwidthRates(totalRates); + rsConfig->getTotalBandwidthRates(totalRates); rsConfig->getAllBandwidthRates(rateMap); - /* insert */ - QTreeWidgetItem *item = new QTreeWidgetItem(); + // some calculation + float totalEffectiveSpeed = std::max(totalRates.mRateOut, 1.0f); + float totalDrain = (float)totalRates.mQueueOutBytes / (totalEffectiveSpeed * 1024.0f); + + /* insert */ + BwCtrlWidgetItem *item = new BwCtrlWidgetItem(true); // true = isTotal peerTreeWidget->addTopLevelItem(item); peerTreeWidget->setSelectionMode(QAbstractItemView::SingleSelection); /* do Totals */ - item -> setData(COLUMN_PEERID, Qt::DisplayRole, tr("TOTALS")); + item -> setData(COLUMN_PEERID, Qt::DisplayRole, tr("")); item -> setData(COLUMN_RSNAME, Qt::DisplayRole, tr("Totals")); item -> setData(COLUMN_IN_RATE, Qt::DisplayRole, totalRates.mRateIn); item -> setData(COLUMN_IN_MAX, Qt::DisplayRole,totalRates.mRateMaxIn); - item -> setData(COLUMN_IN_QUEUE, Qt::DisplayRole, totalRates.mQueueIn); - item -> setData(COLUMN_IN_ALLOC, Qt::DisplayRole, std::numeric_limits::max()); - item -> setData(COLUMN_IN_ALLOC_SENT, Qt::DisplayRole, std::numeric_limits::max()); + item -> setData(COLUMN_IN_ALLOWED, Qt::DisplayRole, QVariant()); + + item -> setData(COLUMN_IN_QUEUE_ITEMS, Qt::DisplayRole, totalRates.mQueueIn); item -> setData(COLUMN_OUT_RATE, Qt::DisplayRole, totalRates.mRateOut); item -> setData(COLUMN_OUT_MAX, Qt::DisplayRole, totalRates.mRateMaxOut); - item -> setData(COLUMN_OUT_QUEUE, Qt::DisplayRole, totalRates.mQueueOut); - item -> setData(COLUMN_OUT_ALLOC, Qt::DisplayRole, std::numeric_limits::max()); - item -> setData(COLUMN_OUT_ALLOC_SENT, Qt::DisplayRole, std::numeric_limits::max()); + item -> setData(COLUMN_OUT_ALLOWED, Qt::DisplayRole, QVariant()); + + item -> setData(COLUMN_OUT_QUEUE_ITEMS, Qt::DisplayRole, totalRates.mQueueOut); + item -> setData(COLUMN_OUT_QUEUE_BYTES, Qt::DisplayRole, qint64(totalRates.mQueueOutBytes)); + item -> setData(COLUMN_DRAIN, Qt::DisplayRole, totalDrain); + time_t now = time(NULL); for(it = rateMap.begin(); it != rateMap.end(); ++it) { /* find the entry */ - QTreeWidgetItem *peer_item = NULL; + BwCtrlWidgetItem *peer_item = new BwCtrlWidgetItem(false); // false = not total + peerTreeWidget->addTopLevelItem(peer_item); #if 0 QString qpeerid = QString::fromStdString(*it); int itemCount = peerTreeWidget->topLevelItemCount(); @@ -274,13 +265,6 @@ void BwCtrlWindow::updateBandwidth() } #endif - if (!peer_item) - { - /* insert */ - peer_item = new QTreeWidgetItem(); - peerTreeWidget->addTopLevelItem(peer_item); - } - std::string name = rsPeers->getPeerName(it->first); peer_item -> setData(COLUMN_PEERID, Qt::DisplayRole, QString::fromStdString(it->first.toStdString())); @@ -288,91 +272,44 @@ void BwCtrlWindow::updateBandwidth() peer_item -> setData(COLUMN_IN_RATE, Qt::DisplayRole, it->second.mRateIn); peer_item -> setData(COLUMN_IN_MAX, Qt::DisplayRole, it->second.mRateMaxIn); - peer_item -> setData(COLUMN_IN_QUEUE, Qt::DisplayRole, it->second.mQueueIn); - peer_item -> setData(COLUMN_IN_ALLOC, Qt::DisplayRole, it->second.mAllocIn); - peer_item -> setData(COLUMN_IN_ALLOC_SENT, Qt::DisplayRole, qint64(now - it->second.mAllocTs)); + peer_item -> setData(COLUMN_IN_ALLOWED, Qt::DisplayRole, it->second.mAllocIn); + + peer_item -> setData(COLUMN_IN_QUEUE_ITEMS, Qt::DisplayRole, it->second.mQueueIn); peer_item -> setData(COLUMN_OUT_RATE, Qt::DisplayRole, it->second.mRateOut); peer_item -> setData(COLUMN_OUT_MAX, Qt::DisplayRole, it->second.mRateMaxOut); - peer_item -> setData(COLUMN_OUT_QUEUE, Qt::DisplayRole, it->second.mQueueOut); - if (it->second.mAllowedTs != 0) - { - peer_item -> setData(COLUMN_OUT_ALLOC, Qt::DisplayRole, it->second.mAllowedOut); - peer_item -> setData(COLUMN_OUT_ALLOC_SENT, Qt::DisplayRole,qint64(now - it->second.mAllowedTs)); - } - else - { - peer_item -> setData(COLUMN_OUT_ALLOC, Qt::DisplayRole, std::numeric_limits::max()); - peer_item -> setData(COLUMN_OUT_ALLOC_SENT, Qt::DisplayRole, std::numeric_limits::max()); - } + peer_item -> setData(COLUMN_OUT_ALLOWED, Qt::DisplayRole, it->second.mAllowedOut); + peer_item -> setData(COLUMN_OUT_QUEUE_ITEMS, Qt::DisplayRole, it->second.mQueueOut); + peer_item -> setData(COLUMN_OUT_QUEUE_BYTES, Qt::DisplayRole, qint64(it->second.mQueueOutBytes)); - /* colour the columns */ - if (it->second.mAllowedTs != 0) - { - if (it->second.mAllowedOut < it->second.mRateOut) - { - /* RED */ - QColor bc("#ff4444"); // red - peer_item -> setBackground(COLUMN_OUT_RATE,QBrush(bc)); - - } - else if (it->second.mAllowedOut < it->second.mRateMaxOut) - { - /* YELLOW */ - QColor bc("#ffff66"); // yellow - peer_item -> setBackground(COLUMN_OUT_MAX,QBrush(bc)); - - } - else - { - /* GREEN */ - QColor bc("#44ff44");//bright green - peer_item -> setBackground(COLUMN_OUT_ALLOC,QBrush(bc)); - } - } - else - { - /* GRAY */ - QColor bc("#444444");// gray - peer_item -> setBackground(COLUMN_OUT_ALLOC,QBrush(bc)); - peer_item -> setBackground(COLUMN_OUT_ALLOC_SENT,QBrush(bc)); - - } - - /* queueOut */ -#define QUEUE_RED 10000 -#define QUEUE_ORANGE 2000 -#define QUEUE_YELLOW 500 - - if (it->second.mQueueOut > QUEUE_RED) - { - /* RED */ - QColor bc("#ff4444"); // red - peer_item -> setBackground(COLUMN_OUT_QUEUE,QBrush(bc)); - - } - else if (it->second.mQueueOut > QUEUE_ORANGE) - { - /* ORANGE */ - QColor bc("#ff9900"); //orange - peer_item -> setBackground(COLUMN_OUT_QUEUE,QBrush(bc)); - - } - else if (it->second.mQueueOut > QUEUE_YELLOW) - { - /* YELLOW */ - QColor bc("#ffff66"); // yellow - peer_item -> setBackground(COLUMN_OUT_QUEUE,QBrush(bc)); - - } - else - { - /* GREEN */ - QColor bc("#44ff44");//bright green - peer_item -> setBackground(COLUMN_OUT_QUEUE,QBrush(bc)); - } + float effectiveSpeed = std::max(it->second.mRateOut, 1.0f); + float drainTime = (float)it->second.mQueueOutBytes / (effectiveSpeed * 1024.0f); + peer_item -> setData(COLUMN_DRAIN, Qt::DisplayRole, drainTime); + // Orange if >= 30, Red if >= 60 + if (drainTime >= 60.0f) { + // Red background with white text for critical congestion + peer_item->setBackground(COLUMN_DRAIN, QBrush(QColor("#FF4444"))); + peer_item->setForeground(COLUMN_DRAIN, QBrush(Qt::white)); + } + else if (drainTime >= 30.0f) { + // Orange background for warning levels + peer_item->setBackground(COLUMN_DRAIN, QBrush(QColor("#FFA500"))); + peer_item->setForeground(COLUMN_DRAIN, QBrush(Qt::black)); + } } + // Re-enable sorting - the custom operator< will now keep Totals at index 0 + peerTreeWidget->setSortingEnabled(true); } +// Persist column sizes +void BwCtrlWindow::showEvent(QShowEvent *) { + QSettings settings; + bwTreeWidget->header()->restoreState(settings.value("BwCtrlColumns").toByteArray()); +} + +void BwCtrlWindow::hideEvent(QHideEvent *) { + QSettings settings; + settings.setValue("BwCtrlColumns", bwTreeWidget->header()->saveState()); +} diff --git a/retroshare-gui/src/gui/statistics/BwCtrlWindow.h b/retroshare-gui/src/gui/statistics/BwCtrlWindow.h index 4126eb6bc..d0799be44 100644 --- a/retroshare-gui/src/gui/statistics/BwCtrlWindow.h +++ b/retroshare-gui/src/gui/statistics/BwCtrlWindow.h @@ -33,22 +33,51 @@ #define COLUMN_PEERID 1 #define COLUMN_IN_RATE 2 #define COLUMN_IN_MAX 3 -#define COLUMN_IN_QUEUE 4 -#define COLUMN_IN_ALLOC 5 -#define COLUMN_IN_ALLOC_SENT 6 -#define COLUMN_OUT_RATE 7 -#define COLUMN_OUT_MAX 8 -#define COLUMN_OUT_QUEUE 9 -#define COLUMN_OUT_ALLOC 10 -#define COLUMN_OUT_ALLOC_SENT 11 -#define COLUMN_ALLOWED RECVD 12 -#define COLUMN_COUNT 13 - +#define COLUMN_IN_ALLOWED 4 +#define COLUMN_IN_QUEUE_ITEMS 5 +#define COLUMN_OUT_RATE 6 +#define COLUMN_OUT_MAX 7 +#define COLUMN_OUT_ALLOWED 8 +#define COLUMN_OUT_QUEUE_ITEMS 9 +#define COLUMN_OUT_QUEUE_BYTES 10 +#define COLUMN_DRAIN 11 +#define COLUMN_COUNT 12 class QModelIndex; class QPainter; class BWListDelegate ; +/** + * Custom Item to force Totals row to stay at the top and handle sorting + */ +class BwCtrlWidgetItem : public QTreeWidgetItem { +public: + BwCtrlWidgetItem(bool total = false) : QTreeWidgetItem(), isTotal(total) {} + bool isTotal; + + // Fixed sorting logic to pin Totals row to the top + bool operator<(const QTreeWidgetItem &other) const override { + const BwCtrlWidgetItem *otherBw = static_cast(&other); + int column = treeWidget()->sortColumn(); + + // Keep Totals row at the very top regardless of Ascending/Descending order + bool isAscending = (treeWidget()->header()->sortIndicatorOrder() == Qt::AscendingOrder); + if (this->isTotal) return isAscending; + if (otherBw->isTotal) return !isAscending; + + // Standard data comparison for other rows + QVariant v1 = data(column, Qt::DisplayRole); + QVariant v2 = other.data(column, Qt::DisplayRole); + + if (v1.type() == QVariant::Double || v1.type() == (QVariant::Type)QMetaType::Float) + return v1.toFloat() < v2.toFloat(); + if (v1.type() == QVariant::Int || v1.type() == QVariant::LongLong) + return v1.toLongLong() < v2.toLongLong(); + + return v1.toString().localeAwareCompare(v2.toString()) < 0; + } +}; + class BwCtrlWindow : public RsAutoUpdatePage, public Ui::BwCtrlWindow { Q_OBJECT @@ -64,5 +93,7 @@ public slots: protected: BWListDelegate *BWDelegate; + void showEvent(QShowEvent *event) override; + void hideEvent(QHideEvent *event) override; }; diff --git a/retroshare-gui/src/gui/statistics/BwCtrlWindow.ui b/retroshare-gui/src/gui/statistics/BwCtrlWindow.ui index cf9e0ab8d..ece8a3011 100644 --- a/retroshare-gui/src/gui/statistics/BwCtrlWindow.ui +++ b/retroshare-gui/src/gui/statistics/BwCtrlWindow.ui @@ -35,52 +35,52 @@ - In (KB/s) + In (kB/s) - InMax (KB/s) + InMax (kB/s) - InQueue + InAllowed (kB/s) - InAllocated (KB/s) + InQueue items - Allocated Sent + Out (kB/s) - Out (KB/s) + OutMax (kB/s) - OutMax (KB/s) + OutAllowed (kB/s) - OutQueue + OutQueue items - OutAllowed (KB/s) + OutQueue bytes - Allowed Recvd + Drain