mirror of
https://github.com/dogecoin/dogecoin.git
synced 2026-01-31 10:30:52 +00:00
Retain network traffic data on slider change
See #2523, a request not to drop data when changing the time scale of the graph. This change includes several components performed by @michilumin: * allows resampling and data retention * separates data from visual sample stores on the graph * uses averages instead of maximums for samples * improves timing accuracy Co-authored-by: Michi Lumin <michi@luskwood.org>
This commit is contained in:
parent
a31fa4ff01
commit
530e7c1008
@ -1,9 +1,11 @@
|
||||
// Copyright (c) 2011-2015 The Bitcoin Core developers
|
||||
// Copyright (c) 2022 The Dogecoin Core developers
|
||||
// Distributed under the MIT software license, see the accompanying
|
||||
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||
|
||||
#include "trafficgraphwidget.h"
|
||||
#include "clientmodel.h"
|
||||
#include "utiltime.h"
|
||||
|
||||
#include <QPainter>
|
||||
#include <QPainterPath>
|
||||
@ -12,32 +14,52 @@
|
||||
|
||||
#include <cmath>
|
||||
|
||||
#define DESIRED_SAMPLES 800
|
||||
#define NUM_DISPLAYED_SAMPLES 800
|
||||
#define BASE_SAMPLES_BUF_SIZE 24 * 60 * 60
|
||||
#define BASE_SAMPLE_PERIOD_MS 1000
|
||||
|
||||
#define XMARGIN 10
|
||||
#define YMARGIN 10
|
||||
|
||||
TrafficGraphWidget::TrafficGraphWidget(QWidget *parent) :
|
||||
QWidget(parent),
|
||||
timer(0),
|
||||
fMax(0.0f),
|
||||
nMins(0),
|
||||
vSamplesIn(),
|
||||
vSamplesOut(),
|
||||
nLastBytesIn(0),
|
||||
nLastBytesOut(0),
|
||||
|
||||
vTimespanSamplesIn(NUM_DISPLAYED_SAMPLES),
|
||||
vTimespanSamplesOut(NUM_DISPLAYED_SAMPLES),
|
||||
nTimespanLastBytesIn(0),
|
||||
nTimespanLastBytesOut(0),
|
||||
nTimespanLastSampleTimeMs(0),
|
||||
nTimespanHeadIndex(0),
|
||||
timespanTimer(0),
|
||||
vBaseSamplesIn(BASE_SAMPLES_BUF_SIZE),
|
||||
vBaseSamplesOut(BASE_SAMPLES_BUF_SIZE),
|
||||
nBaseLastBytesIn(0),
|
||||
nBaseLastBytesOut(0),
|
||||
nBaseLastSampleTimeMs(0),
|
||||
nBaseSamplesHeadIndex(0),
|
||||
baseTimer(0),
|
||||
|
||||
clientModel(0)
|
||||
{
|
||||
timer = new QTimer(this);
|
||||
connect(timer, SIGNAL(timeout()), SLOT(updateRates()));
|
||||
timespanTimer = new QTimer(this);
|
||||
connect(timespanTimer, SIGNAL(timeout()), SLOT(updateTimespanRates()));
|
||||
|
||||
baseTimer = new QTimer(this);
|
||||
connect(baseTimer, SIGNAL(timeout()), SLOT(updateBaseRates()));
|
||||
}
|
||||
|
||||
void TrafficGraphWidget::setClientModel(ClientModel *model)
|
||||
{
|
||||
clientModel = model;
|
||||
if(model) {
|
||||
nLastBytesIn = model->getTotalBytesRecv();
|
||||
nLastBytesOut = model->getTotalBytesSent();
|
||||
nTimespanLastBytesIn = model->getTotalBytesRecv();
|
||||
nTimespanLastBytesOut = model->getTotalBytesSent();
|
||||
nBaseLastBytesIn = nTimespanLastBytesIn;
|
||||
nBaseLastBytesOut = nTimespanLastBytesOut;
|
||||
|
||||
baseTimer->start(BASE_SAMPLE_PERIOD_MS);
|
||||
}
|
||||
}
|
||||
|
||||
@ -46,19 +68,80 @@ int TrafficGraphWidget::getGraphRangeMins() const
|
||||
return nMins;
|
||||
}
|
||||
|
||||
void TrafficGraphWidget::paintPath(QPainterPath &path, QQueue<float> &samples)
|
||||
float TrafficGraphWidget::resampleSamples(const QVector<float> &source, QVector<float> &dest) const
|
||||
{
|
||||
int h = height() - YMARGIN * 2, w = width() - XMARGIN * 2;
|
||||
int sampleCount = samples.size(), x = XMARGIN + w, y;
|
||||
if(sampleCount > 0) {
|
||||
path.moveTo(x, YMARGIN + h);
|
||||
for(int i = 0; i < sampleCount; ++i) {
|
||||
x = XMARGIN + w - w * i / DESIRED_SAMPLES;
|
||||
y = YMARGIN + h - (int)(h * samples.at(i) / fMax);
|
||||
path.lineTo(x, y);
|
||||
}
|
||||
path.lineTo(x, YMARGIN + h);
|
||||
int numSourceSamples = nMins * 60;
|
||||
float stride = (float)numSourceSamples / dest.size();
|
||||
float currentStride = stride;
|
||||
float maxSample = 0.0f;
|
||||
float lastAverageRate = 0.0f;
|
||||
|
||||
int sourceTailIndex = nBaseSamplesHeadIndex - numSourceSamples;
|
||||
if (sourceTailIndex < 0) {
|
||||
sourceTailIndex += BASE_SAMPLES_BUF_SIZE;
|
||||
}
|
||||
|
||||
int currentSampleIndex = sourceTailIndex;
|
||||
for (int destIndex = 0; destIndex < dest.size(); destIndex++) {
|
||||
int numSamplesToRead = (int)currentStride;
|
||||
|
||||
float aggregate = 0.0f;
|
||||
for (int i = 0; i < numSamplesToRead; ++i) {
|
||||
float currentSample = source[currentSampleIndex];
|
||||
|
||||
aggregate += currentSample;
|
||||
|
||||
currentSampleIndex++;
|
||||
if (currentSampleIndex >= source.size()) {
|
||||
currentSampleIndex = 0;
|
||||
}
|
||||
}
|
||||
|
||||
float averageRate = 0.0f;
|
||||
if (numSamplesToRead == 0) {
|
||||
// We seem to be trying to upsample. just use the previous sample.
|
||||
averageRate = lastAverageRate;
|
||||
}
|
||||
else {
|
||||
averageRate = aggregate / numSamplesToRead;
|
||||
}
|
||||
|
||||
maxSample = std::max(maxSample, averageRate);
|
||||
|
||||
dest[destIndex] = averageRate;
|
||||
lastAverageRate = averageRate;
|
||||
|
||||
currentStride -= numSamplesToRead;
|
||||
currentStride += stride;
|
||||
}
|
||||
|
||||
return maxSample;
|
||||
}
|
||||
|
||||
void TrafficGraphWidget::paintPath(QPainterPath &path, const QVector<float> &samples, int samplesHead)
|
||||
{
|
||||
int h = height() - YMARGIN * 2;
|
||||
int w = width() - XMARGIN * 2;
|
||||
int lastY = 0;
|
||||
|
||||
path.moveTo(XMARGIN, YMARGIN + h);
|
||||
|
||||
int samplesIndex = samplesHead;
|
||||
for (int i = 0; i < samples.size(); ++i) {
|
||||
int x = w * i / NUM_DISPLAYED_SAMPLES;
|
||||
int y = h - (int)(h * samples[samplesIndex] / fMax);
|
||||
|
||||
path.lineTo(XMARGIN + x, YMARGIN + y);
|
||||
|
||||
samplesIndex++;
|
||||
if (samplesIndex >= samples.size()) {
|
||||
samplesIndex = 0;
|
||||
}
|
||||
|
||||
lastY = y;
|
||||
}
|
||||
path.lineTo(XMARGIN + w, YMARGIN + lastY);
|
||||
path.lineTo(XMARGIN + w, YMARGIN + h);
|
||||
}
|
||||
|
||||
void TrafficGraphWidget::paintEvent(QPaintEvent *)
|
||||
@ -66,7 +149,9 @@ void TrafficGraphWidget::paintEvent(QPaintEvent *)
|
||||
QPainter painter(this);
|
||||
painter.fillRect(rect(), Qt::black);
|
||||
|
||||
if(fMax <= 0.0f) return;
|
||||
if (fMax <= 0.0f) {
|
||||
return;
|
||||
}
|
||||
|
||||
QColor axisCol(Qt::gray);
|
||||
int h = height() - YMARGIN * 2;
|
||||
@ -79,7 +164,7 @@ void TrafficGraphWidget::paintEvent(QPaintEvent *)
|
||||
|
||||
const QString units = tr("KB/s");
|
||||
const float yMarginText = 2.0;
|
||||
|
||||
|
||||
// draw lines
|
||||
painter.setPen(axisCol);
|
||||
painter.drawText(XMARGIN, YMARGIN + h - h * val / fMax-yMarginText, QString("%1 %2").arg(val).arg(units));
|
||||
@ -87,6 +172,7 @@ void TrafficGraphWidget::paintEvent(QPaintEvent *)
|
||||
int yy = YMARGIN + h - h * y / fMax;
|
||||
painter.drawLine(XMARGIN, yy, width() - XMARGIN, yy);
|
||||
}
|
||||
|
||||
// if we drew 3 or fewer lines, break them up at the next lower order of magnitude
|
||||
if(fMax / val <= 3.0f) {
|
||||
axisCol = axisCol.darker();
|
||||
@ -103,74 +189,114 @@ void TrafficGraphWidget::paintEvent(QPaintEvent *)
|
||||
}
|
||||
}
|
||||
|
||||
if(!vSamplesIn.empty()) {
|
||||
QPainterPath p;
|
||||
paintPath(p, vSamplesIn);
|
||||
painter.fillPath(p, QColor(0, 255, 0, 128));
|
||||
painter.setPen(Qt::green);
|
||||
painter.drawPath(p);
|
||||
}
|
||||
if(!vSamplesOut.empty()) {
|
||||
QPainterPath p;
|
||||
paintPath(p, vSamplesOut);
|
||||
painter.fillPath(p, QColor(255, 0, 0, 128));
|
||||
painter.setPen(Qt::red);
|
||||
painter.drawPath(p);
|
||||
}
|
||||
QPainterPath samplesPathIn;
|
||||
paintPath(samplesPathIn, vTimespanSamplesIn, nTimespanHeadIndex);
|
||||
painter.fillPath(samplesPathIn, QColor(0, 255, 0, 128));
|
||||
painter.setPen(Qt::green);
|
||||
painter.drawPath(samplesPathIn);
|
||||
|
||||
QPainterPath samplesPathOut;
|
||||
paintPath(samplesPathOut, vTimespanSamplesOut, nTimespanHeadIndex);
|
||||
painter.fillPath(samplesPathOut, QColor(255, 0, 0, 128));
|
||||
painter.setPen(Qt::red);
|
||||
painter.drawPath(samplesPathOut);
|
||||
}
|
||||
|
||||
void TrafficGraphWidget::updateRates()
|
||||
void TrafficGraphWidget::updateTimespanRates()
|
||||
{
|
||||
if(!clientModel) return;
|
||||
|
||||
quint64 bytesIn = clientModel->getTotalBytesRecv(),
|
||||
bytesOut = clientModel->getTotalBytesSent();
|
||||
float inRate = (bytesIn - nLastBytesIn) / 1024.0f * 1000 / timer->interval();
|
||||
float outRate = (bytesOut - nLastBytesOut) / 1024.0f * 1000 / timer->interval();
|
||||
vSamplesIn.push_front(inRate);
|
||||
vSamplesOut.push_front(outRate);
|
||||
nLastBytesIn = bytesIn;
|
||||
nLastBytesOut = bytesOut;
|
||||
|
||||
while(vSamplesIn.size() > DESIRED_SAMPLES) {
|
||||
vSamplesIn.pop_back();
|
||||
}
|
||||
while(vSamplesOut.size() > DESIRED_SAMPLES) {
|
||||
vSamplesOut.pop_back();
|
||||
if (!clientModel) {
|
||||
return;
|
||||
}
|
||||
|
||||
float tmax = 0.0f;
|
||||
Q_FOREACH(float f, vSamplesIn) {
|
||||
if(f > tmax) tmax = f;
|
||||
int64_t currentTimeMs = GetTimeMillis();
|
||||
int64_t elapsedMs = currentTimeMs - nTimespanLastSampleTimeMs;
|
||||
if (elapsedMs <= 0) {
|
||||
return;
|
||||
}
|
||||
Q_FOREACH(float f, vSamplesOut) {
|
||||
if(f > tmax) tmax = f;
|
||||
|
||||
quint64 bytesIn = clientModel->getTotalBytesRecv();
|
||||
quint64 bytesOut = clientModel->getTotalBytesSent();
|
||||
|
||||
float inRate = (bytesIn - nTimespanLastBytesIn) / 1000.0f * (1000.0f / elapsedMs);
|
||||
float outRate = (bytesOut - nTimespanLastBytesOut) / 1000.0f * (1000.0f / elapsedMs);
|
||||
|
||||
nTimespanLastBytesIn = bytesIn;
|
||||
nTimespanLastBytesOut = bytesOut;
|
||||
nTimespanLastSampleTimeMs = currentTimeMs;
|
||||
|
||||
vTimespanSamplesIn[nTimespanHeadIndex] = inRate;
|
||||
vTimespanSamplesOut[nTimespanHeadIndex] = outRate;
|
||||
|
||||
nTimespanHeadIndex++;
|
||||
if (nTimespanHeadIndex >= NUM_DISPLAYED_SAMPLES) {
|
||||
nTimespanHeadIndex = 0;
|
||||
}
|
||||
fMax = tmax;
|
||||
|
||||
fMax = 0.0f;
|
||||
const int nInSampleSize = std::min(vTimespanSamplesIn.size(), vTimespanSamplesOut.size());
|
||||
for (int i = 0; i < nInSampleSize; i++)
|
||||
{
|
||||
fMax = std::max(std::max(fMax, vTimespanSamplesIn[i]), vTimespanSamplesOut[i]);
|
||||
}
|
||||
|
||||
update();
|
||||
}
|
||||
|
||||
void TrafficGraphWidget::updateBaseRates()
|
||||
{
|
||||
if (!clientModel) {
|
||||
return;
|
||||
}
|
||||
|
||||
int64_t currentTimeMs = GetTimeMillis();
|
||||
int64_t elapsedMs = currentTimeMs - nBaseLastSampleTimeMs;
|
||||
if (elapsedMs <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
quint64 bytesIn = clientModel->getTotalBytesRecv();
|
||||
quint64 bytesOut = clientModel->getTotalBytesSent();
|
||||
|
||||
float inRate = (bytesIn - nBaseLastBytesIn) / 1000.0f * 1000.0f / elapsedMs;
|
||||
float outRate = (bytesOut - nBaseLastBytesOut) / 1000.0f * 1000.0f / elapsedMs;
|
||||
|
||||
nBaseLastBytesIn = bytesIn;
|
||||
nBaseLastBytesOut = bytesOut;
|
||||
nBaseLastSampleTimeMs = currentTimeMs;
|
||||
|
||||
vBaseSamplesIn[nBaseSamplesHeadIndex] = inRate;
|
||||
vBaseSamplesOut[nBaseSamplesHeadIndex] = outRate;
|
||||
|
||||
nBaseSamplesHeadIndex++;
|
||||
if (nBaseSamplesHeadIndex >= BASE_SAMPLES_BUF_SIZE) {
|
||||
nBaseSamplesHeadIndex = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void TrafficGraphWidget::setGraphRangeMins(int mins)
|
||||
{
|
||||
nMins = mins;
|
||||
int msecsPerSample = nMins * 60 * 1000 / DESIRED_SAMPLES;
|
||||
timer->stop();
|
||||
timer->setInterval(msecsPerSample);
|
||||
nTimespanHeadIndex = 0;
|
||||
|
||||
clear();
|
||||
float maxIn = resampleSamples(vBaseSamplesIn, vTimespanSamplesIn);
|
||||
float maxOut = resampleSamples(vBaseSamplesOut, vTimespanSamplesOut);
|
||||
fMax = std::max(maxIn, maxOut);
|
||||
|
||||
timespanTimer->start((mins * 60 * 1000) / NUM_DISPLAYED_SAMPLES);
|
||||
}
|
||||
|
||||
void TrafficGraphWidget::clear()
|
||||
{
|
||||
timer->stop();
|
||||
|
||||
vSamplesOut.clear();
|
||||
vSamplesIn.clear();
|
||||
vTimespanSamplesIn.fill(0.0f);
|
||||
vTimespanSamplesOut.fill(0.0f);
|
||||
vBaseSamplesIn.fill(0.0f);
|
||||
vBaseSamplesOut.fill(0.0f);
|
||||
fMax = 0.0f;
|
||||
|
||||
if(clientModel) {
|
||||
nLastBytesIn = clientModel->getTotalBytesRecv();
|
||||
nLastBytesOut = clientModel->getTotalBytesSent();
|
||||
if (clientModel) {
|
||||
nTimespanLastBytesIn = clientModel->getTotalBytesRecv();
|
||||
nTimespanLastBytesOut = clientModel->getTotalBytesSent();
|
||||
nBaseLastBytesIn = nTimespanLastBytesIn;
|
||||
nBaseLastBytesOut = nTimespanLastBytesOut;
|
||||
}
|
||||
timer->start();
|
||||
}
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
// Copyright (c) 2011-2015 The Bitcoin Core developers
|
||||
// Copyright (c) 2022 The Dogecoin Core developers
|
||||
// Distributed under the MIT software license, see the accompanying
|
||||
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||
|
||||
@ -6,7 +7,7 @@
|
||||
#define BITCOIN_QT_TRAFFICGRAPHWIDGET_H
|
||||
|
||||
#include <QWidget>
|
||||
#include <QQueue>
|
||||
#include <QVector>
|
||||
|
||||
class ClientModel;
|
||||
|
||||
@ -28,20 +29,46 @@ protected:
|
||||
void paintEvent(QPaintEvent *);
|
||||
|
||||
public Q_SLOTS:
|
||||
void updateRates();
|
||||
void updateTimespanRates();
|
||||
void updateBaseRates();
|
||||
void setGraphRangeMins(int mins);
|
||||
void clear();
|
||||
|
||||
private:
|
||||
void paintPath(QPainterPath &path, QQueue<float> &samples);
|
||||
void paintPath(QPainterPath &path, const QVector<float> &samples, int samplesHead);
|
||||
float resampleSamples(const QVector<float> &source, QVector<float> &dest) const;
|
||||
|
||||
QTimer *timer;
|
||||
float fMax;
|
||||
int nMins;
|
||||
QQueue<float> vSamplesIn;
|
||||
QQueue<float> vSamplesOut;
|
||||
quint64 nLastBytesIn;
|
||||
quint64 nLastBytesOut;
|
||||
|
||||
/*
|
||||
*These QVector<float>s are implemented as circular buffers so that we always have a maximum of
|
||||
*BASE_SAMPLES_BUF_SIZE (defined in trafficgraphwidget.cpp, default of 24 hours * 60 minutes * 60 seconds,
|
||||
*or 86400 samples per day at a samplerate of 1sps) to work with. A second circular "display buffer" is
|
||||
*implemented with a maximum of NUM_DISPLAYED_SAMPLES (defined in trafficgraphwidget.cpp, default of 800)
|
||||
*which are the 'visual samples' which can represent different amounts of time according to the slider.
|
||||
*
|
||||
*The "base samples" for a given timespan are resampled into the "displayed samples" (in this case 800) to
|
||||
*redraw and show a history of the selected timespan. When these variables are incremented, we check limits
|
||||
*to see if we need to wrap around and begin overwriting the oldest values in the circular buffers.
|
||||
*/
|
||||
|
||||
QVector<float> vTimespanSamplesIn;
|
||||
QVector<float> vTimespanSamplesOut;
|
||||
quint64 nTimespanLastBytesIn;
|
||||
quint64 nTimespanLastBytesOut;
|
||||
int64_t nTimespanLastSampleTimeMs;
|
||||
int nTimespanHeadIndex;
|
||||
QTimer *timespanTimer;
|
||||
|
||||
QVector<float> vBaseSamplesIn;
|
||||
QVector<float> vBaseSamplesOut;
|
||||
quint64 nBaseLastBytesIn;
|
||||
quint64 nBaseLastBytesOut;
|
||||
int64_t nBaseLastSampleTimeMs;
|
||||
int nBaseSamplesHeadIndex;
|
||||
QTimer *baseTimer;
|
||||
|
||||
ClientModel *clientModel;
|
||||
};
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user