From 18c83f58e335a9ed0b3f24b0c25a1be2da3c7405 Mon Sep 17 00:00:00 2001 From: krychlicki Date: Sun, 28 May 2017 15:49:34 +0200 Subject: [PATCH] Qt: bug fixes and enhancement to traffic graph widget (#1429) * clear trafficgraph on clear button click * set default sample height set default sample height so after clearing traffic graph have some scale * reduce available traffic graph ranges, add optimized graph data storage reduce available traffic graph ranges to 10 (5m,10m,15m,30m,1h,2h,3h,6h,12h,24h), store graph data so range change is possible, data storage contains only necessary data to create graphs for all supported ranges eg. for 10m range storage only half of 10m samples - the second half is calculated from 5m range samples, encapsulate all traffic graph related data into one class * code formatting corrections --- src/Makefile.qt.include | 2 + src/Makefile.qttest.include | 7 +- src/qt/forms/debugwindow.ui | 8 +- src/qt/rpcconsole.cpp | 14 +- src/qt/rpcconsole.h | 3 +- src/qt/test/test_main.cpp | 5 + src/qt/test/trafficgraphdatatests.cpp | 174 ++++++++++++++++++ src/qt/test/trafficgraphdatatests.h | 20 +++ src/qt/trafficgraphdata.cpp | 248 ++++++++++++++++++++++++++ src/qt/trafficgraphdata.h | 90 ++++++++++ src/qt/trafficgraphwidget.cpp | 119 ++++++------ src/qt/trafficgraphwidget.h | 14 +- 12 files changed, 619 insertions(+), 85 deletions(-) create mode 100644 src/qt/test/trafficgraphdatatests.cpp create mode 100644 src/qt/test/trafficgraphdatatests.h create mode 100644 src/qt/trafficgraphdata.cpp create mode 100644 src/qt/trafficgraphdata.h diff --git a/src/Makefile.qt.include b/src/Makefile.qt.include index f375bbf82..7a98e4b74 100644 --- a/src/Makefile.qt.include +++ b/src/Makefile.qt.include @@ -149,6 +149,7 @@ BITCOIN_QT_H = \ qt/sendcoinsentry.h \ qt/signverifymessagedialog.h \ qt/splashscreen.h \ + qt/trafficgraphdata.h \ qt/trafficgraphwidget.h \ qt/transactiondesc.h \ qt/transactiondescdialog.h \ @@ -379,6 +380,7 @@ BITCOIN_QT_CPP = \ qt/qvaluecombobox.cpp \ qt/rpcconsole.cpp \ qt/splashscreen.cpp \ + qt/trafficgraphdata.cpp \ qt/trafficgraphwidget.cpp \ qt/utilitydialog.cpp diff --git a/src/Makefile.qttest.include b/src/Makefile.qttest.include index 0714032de..a5216d6fa 100644 --- a/src/Makefile.qttest.include +++ b/src/Makefile.qttest.include @@ -3,7 +3,8 @@ TESTS += qt/test/test_dash-qt TEST_QT_MOC_CPP = \ qt/test/moc_compattests.cpp \ - qt/test/moc_uritests.cpp + qt/test/moc_trafficgraphdatatests.cpp \ + qt/test/moc_uritests.cpp if ENABLE_WALLET TEST_QT_MOC_CPP += qt/test/moc_paymentservertests.cpp @@ -13,7 +14,8 @@ TEST_QT_H = \ qt/test/compattests.h \ qt/test/uritests.h \ qt/test/paymentrequestdata.h \ - qt/test/paymentservertests.h + qt/test/paymentservertests.h \ + qt/test/trafficgraphdatatests.h qt_test_test_dash_qt_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) $(BITCOIN_QT_INCLUDES) \ $(QT_INCLUDES) $(QT_TEST_INCLUDES) $(PROTOBUF_CFLAGS) @@ -22,6 +24,7 @@ qt_test_test_dash_qt_SOURCES = \ qt/test/compattests.cpp \ qt/test/test_main.cpp \ qt/test/uritests.cpp \ + qt/test/trafficgraphdatatests.cpp \ $(TEST_QT_H) if ENABLE_WALLET qt_test_test_dash_qt_SOURCES += \ diff --git a/src/qt/forms/debugwindow.ui b/src/qt/forms/debugwindow.ui index cb43adc77..ea9ac14ee 100644 --- a/src/qt/forms/debugwindow.ui +++ b/src/qt/forms/debugwindow.ui @@ -524,16 +524,16 @@ - 1 + 0 - 288 + 9 - 12 + 1 - 6 + 3 Qt::Horizontal diff --git a/src/qt/rpcconsole.cpp b/src/qt/rpcconsole.cpp index fb4134637..a250dff5d 100644 --- a/src/qt/rpcconsole.cpp +++ b/src/qt/rpcconsole.cpp @@ -45,7 +45,7 @@ const int CONSOLE_HISTORY = 50; const QSize ICON_SIZE(24, 24); -const int INITIAL_TRAFFIC_GRAPH_MINS = 30; +const TrafficGraphData::GraphRange INITIAL_TRAFFIC_GRAPH_SETTING = TrafficGraphData::Range_30m; // Repair parameters const QString SALVAGEWALLET("-salvagewallet"); @@ -298,7 +298,7 @@ RPCConsole::RPCConsole(const PlatformStyle *platformStyle, QWidget *parent) : RPCRegisterTimerInterface(rpcTimerInterface); startExecutor(); - setTrafficGraphRange(INITIAL_TRAFFIC_GRAPH_MINS); + setTrafficGraphRange(INITIAL_TRAFFIC_GRAPH_SETTING); ui->peerHeading->setText(tr("Select a peer to view detailed information.")); @@ -732,9 +732,7 @@ void RPCConsole::scrollToEnd() void RPCConsole::on_sldGraphRange_valueChanged(int value) { - const int multiplier = 5; // each position on the slider represents 5 min - int mins = value * multiplier; - setTrafficGraphRange(mins); + setTrafficGraphRange(static_cast(value)); } QString RPCConsole::FormatBytes(quint64 bytes) @@ -749,10 +747,10 @@ QString RPCConsole::FormatBytes(quint64 bytes) return QString(tr("%1 GB")).arg(bytes / 1024 / 1024 / 1024); } -void RPCConsole::setTrafficGraphRange(int mins) +void RPCConsole::setTrafficGraphRange(TrafficGraphData::GraphRange range) { - ui->trafficGraph->setGraphRangeMins(mins); - ui->lblGraphRange->setText(GUIUtil::formatDurationStr(mins * 60)); + ui->trafficGraph->setGraphRangeMins(range); + ui->lblGraphRange->setText(GUIUtil::formatDurationStr(TrafficGraphData::RangeMinutes[range] * 60)); } void RPCConsole::updateTrafficStats(quint64 totalBytesIn, quint64 totalBytesOut) diff --git a/src/qt/rpcconsole.h b/src/qt/rpcconsole.h index 63735c21e..63a2a86cd 100644 --- a/src/qt/rpcconsole.h +++ b/src/qt/rpcconsole.h @@ -7,6 +7,7 @@ #include "guiutil.h" #include "peertablemodel.h" +#include "trafficgraphdata.h" #include "net.h" @@ -126,7 +127,7 @@ Q_SIGNALS: private: static QString FormatBytes(quint64 bytes); void startExecutor(); - void setTrafficGraphRange(int mins); + void setTrafficGraphRange(TrafficGraphData::GraphRange range); /** Build parameter list for restart */ void buildParameterlist(QString arg); /** show detailed information on ui about selected node */ diff --git a/src/qt/test/test_main.cpp b/src/qt/test/test_main.cpp index 1183b2b20..00adddc04 100644 --- a/src/qt/test/test_main.cpp +++ b/src/qt/test/test_main.cpp @@ -10,6 +10,7 @@ #include "util.h" #include "uritests.h" #include "compattests.h" +#include "trafficgraphdatatests.h" #ifdef ENABLE_WALLET #include "paymentservertests.h" @@ -54,5 +55,9 @@ int main(int argc, char *argv[]) if (QTest::qExec(&test4) != 0) fInvalid = true; + TrafficGraphDataTests test5; + if (QTest::qExec(&test5) != 0) + fInvalid = true; + return fInvalid; } diff --git a/src/qt/test/trafficgraphdatatests.cpp b/src/qt/test/trafficgraphdatatests.cpp new file mode 100644 index 000000000..af4d4f99d --- /dev/null +++ b/src/qt/test/trafficgraphdatatests.cpp @@ -0,0 +1,174 @@ +#include "trafficgraphdatatests.h" +#include "../trafficgraphdata.h" +#include +#include +#include + +void TrafficGraphDataTests::simpleCurrentSampleQueueTests() +{ + TrafficGraphData trafficGraphData(TrafficGraphData::Range_5m); + for (int i = 0; i < TrafficGraphData::DESIRED_DATA_SAMPLES; i++) + QVERIFY(trafficGraphData.update(TrafficSample(i, i))); + + TrafficGraphData::SampleQueue queue = trafficGraphData.getCurrentRangeQueue(); + QCOMPARE(queue.size(), TrafficGraphData::DESIRED_DATA_SAMPLES); + for (int i = 0; i < TrafficGraphData::DESIRED_DATA_SAMPLES; i++){ + QCOMPARE((int)queue.at(i).in, TrafficGraphData::DESIRED_DATA_SAMPLES - i - 1); + QCOMPARE((int)queue.at(i).out, TrafficGraphData::DESIRED_DATA_SAMPLES - i - 1); + } + + QVERIFY(trafficGraphData.update(TrafficSample(0, 0))); + queue = trafficGraphData.getCurrentRangeQueue(); + QCOMPARE(queue.size(), TrafficGraphData::DESIRED_DATA_SAMPLES); + QCOMPARE((int)queue.at(0).in, 0); + QCOMPARE((int)queue.at(0).out, 0); +} + +namespace +{ +void checkQueue(const TrafficGraphData::SampleQueue& queue, int value) +{ + for (int i = 0; i < queue.size(); i++){ + std::ostringstream oss; + oss<< "i:" << i << " value:" << value << " actual:" << queue.at(i).in; + QVERIFY2(value == (int)queue.at(i).in, oss.str().c_str()); + QVERIFY2(value == (int)queue.at(i).out, oss.str().c_str()); + } +} + +void testQueueFill(TrafficGraphData::GraphRange range, int multiplier) +{ + int size = 10000; + TrafficGraphData trafficGraphData(range); + for (int i = 1; i <= size; i++){ + bool result = trafficGraphData.update(TrafficSample(1, 1)); + std::ostringstream oss; + oss<< "result:" << result << " multiplier:" << multiplier << " i:" << i << " range:" << range; + if (i == 1){ + if (range == TrafficGraphData::Range_5m) + QVERIFY2(result, oss.str().c_str()); + else + QVERIFY2(!result, oss.str().c_str()); + } + else if (i % multiplier == 0){ + QVERIFY2(result, oss.str().c_str()); + } + else { + QVERIFY2(!result, oss.str().c_str()); + } + } + TrafficGraphData::SampleQueue queue = trafficGraphData.getCurrentRangeQueue(); + QCOMPARE(queue.size(), std::min(size / multiplier, TrafficGraphData::DESIRED_DATA_SAMPLES)); + checkQueue(queue, multiplier); +} +} + +void TrafficGraphDataTests::accumulationCurrentSampleQueueTests() +{ + testQueueFill(TrafficGraphData::Range_10m, 2); + testQueueFill(TrafficGraphData::Range_15m, 3); + testQueueFill(TrafficGraphData::Range_30m, 6); + testQueueFill(TrafficGraphData::Range_1h, 12); + testQueueFill(TrafficGraphData::Range_2h, 24); + testQueueFill(TrafficGraphData::Range_3h, 36); + testQueueFill(TrafficGraphData::Range_6h, 72); + testQueueFill(TrafficGraphData::Range_12h, 144); + testQueueFill(TrafficGraphData::Range_24h, 288); +} + +namespace +{ +void checkRange(TrafficGraphData& trafficGraphData, int size, TrafficGraphData::GraphRange toRange, int multiplier) +{ + TrafficGraphData::SampleQueue queue = trafficGraphData.getRangeQueue(toRange); + QCOMPARE(queue.size(), std::min(size / multiplier, TrafficGraphData::DESIRED_DATA_SAMPLES)); + checkQueue(queue,multiplier); +} + +void testQueueFillAndCheckRangesForSize(int size) +{ + TrafficGraphData trafficGraphData(TrafficGraphData::Range_5m); + for (int i = 1; i <= size; i++){ + trafficGraphData.update(TrafficSample(1, 1)); + } + + checkRange(trafficGraphData, size, TrafficGraphData::Range_10m, 2); + checkRange(trafficGraphData, size, TrafficGraphData::Range_15m, 3); + checkRange(trafficGraphData, size, TrafficGraphData::Range_30m, 6); + checkRange(trafficGraphData, size, TrafficGraphData::Range_1h, 12); + checkRange(trafficGraphData, size, TrafficGraphData::Range_2h, 24); + checkRange(trafficGraphData, size, TrafficGraphData::Range_3h, 36); + checkRange(trafficGraphData, size, TrafficGraphData::Range_6h, 72); + checkRange(trafficGraphData, size, TrafficGraphData::Range_12h, 144); + checkRange(trafficGraphData, size, TrafficGraphData::Range_24h, 288); +} +} + +void TrafficGraphDataTests::getRangeTests() +{ + testQueueFillAndCheckRangesForSize(TrafficGraphData::DESIRED_DATA_SAMPLES); + testQueueFillAndCheckRangesForSize(TrafficGraphData::DESIRED_DATA_SAMPLES * 2); + testQueueFillAndCheckRangesForSize(TrafficGraphData::DESIRED_DATA_SAMPLES * 3); + testQueueFillAndCheckRangesForSize(TrafficGraphData::DESIRED_DATA_SAMPLES * 6); + testQueueFillAndCheckRangesForSize(TrafficGraphData::DESIRED_DATA_SAMPLES * 12); + testQueueFillAndCheckRangesForSize(TrafficGraphData::DESIRED_DATA_SAMPLES * 24); + testQueueFillAndCheckRangesForSize(TrafficGraphData::DESIRED_DATA_SAMPLES * 36); + testQueueFillAndCheckRangesForSize(TrafficGraphData::DESIRED_DATA_SAMPLES * 72); + testQueueFillAndCheckRangesForSize(TrafficGraphData::DESIRED_DATA_SAMPLES * 144); + testQueueFillAndCheckRangesForSize(TrafficGraphData::DESIRED_DATA_SAMPLES * 288); +} + +namespace +{ +void compareQueues(const TrafficGraphData::SampleQueue& expected, const TrafficGraphData::SampleQueue& actual) +{ + QCOMPARE(expected.size(), actual.size()); + for (int i = 0; i < expected.size(); i++){ + std::ostringstream oss; + oss<< "i:" << i << " expected:" << expected.at(i).in << " actual:" << actual.at(i).in; + QVERIFY2((int)expected.at(i).in == (int)actual.at(i).in, oss.str().c_str()); + QVERIFY2((int)expected.at(i).out == (int)actual.at(i).out, oss.str().c_str()); + } +} + +void testRangeSwitch(TrafficGraphData::GraphRange baseRange, TrafficGraphData::GraphRange toRange,int size) +{ + QTime time = QTime::currentTime(); + qsrand((uint)time.msec()); + TrafficGraphData trafficGraphDataBase(baseRange); + TrafficGraphData trafficGraphData(toRange); + for (int i = 1; i <= size; i++){ + int in = qrand() % 1000; + int out = qrand() % 1000; + trafficGraphData.update(TrafficSample(in, out)); + trafficGraphDataBase.update(TrafficSample(in, out)); + } + trafficGraphDataBase.switchRange(toRange); + compareQueues(trafficGraphData.getCurrentRangeQueue(),trafficGraphDataBase.getCurrentRangeQueue()); +} +} + +void TrafficGraphDataTests::switchRangeTests() +{ + testRangeSwitch(TrafficGraphData::Range_5m, TrafficGraphData::Range_10m, 10000); + testRangeSwitch(TrafficGraphData::Range_5m, TrafficGraphData::Range_30m, 20000); + testRangeSwitch(TrafficGraphData::Range_5m, TrafficGraphData::Range_15m, 8000 * 2 - 1); + testRangeSwitch(TrafficGraphData::Range_5m, TrafficGraphData::Range_24h, 8000 * 288 - 1); +} + + + +void TrafficGraphDataTests::clearTests() +{ + TrafficGraphData trafficGraphData(TrafficGraphData::Range_5m); + for (int i = 1; i <= TrafficGraphData::DESIRED_DATA_SAMPLES; i++){ + trafficGraphData.update(TrafficSample(1, 1)); + } + QCOMPARE(trafficGraphData.getCurrentRangeQueue().size(),TrafficGraphData::DESIRED_DATA_SAMPLES); + trafficGraphData.clear(); + QCOMPARE(trafficGraphData.getCurrentRangeQueue().size(), 0); + for (int i = 1; i <= TrafficGraphData::DESIRED_DATA_SAMPLES; i++){ + trafficGraphData.update(TrafficSample(1, 1)); + } + QCOMPARE(trafficGraphData.getCurrentRangeQueue().size(), TrafficGraphData::DESIRED_DATA_SAMPLES); +} diff --git a/src/qt/test/trafficgraphdatatests.h b/src/qt/test/trafficgraphdatatests.h new file mode 100644 index 000000000..06a27d0c0 --- /dev/null +++ b/src/qt/test/trafficgraphdatatests.h @@ -0,0 +1,20 @@ +#ifndef TRAFFICGRAPHDATATESTS_H +#define TRAFFICGRAPHDATATESTS_H + +#include +#include + +class TrafficGraphDataTests : public QObject +{ + Q_OBJECT + +private Q_SLOTS: + void simpleCurrentSampleQueueTests(); + void accumulationCurrentSampleQueueTests(); + void getRangeTests(); + void switchRangeTests(); + void clearTests(); +}; + + +#endif // TRAFFICGRAPHDATATESTS_H diff --git a/src/qt/trafficgraphdata.cpp b/src/qt/trafficgraphdata.cpp new file mode 100644 index 000000000..21129a257 --- /dev/null +++ b/src/qt/trafficgraphdata.cpp @@ -0,0 +1,248 @@ +#include + +const int TrafficGraphData::RangeMinutes[] = {5,10,15,30,60,120,180,360,720,1440}; +const int TrafficGraphData::DESIRED_DATA_SAMPLES = 800; +const int TrafficGraphData::DesiredQueueSizes[] = { + TrafficGraphData::DESIRED_DATA_SAMPLES, //Range_5m + TrafficGraphData::DESIRED_DATA_SAMPLES/2, //Range_10m + TrafficGraphData::DESIRED_DATA_SAMPLES*2/3, //Range_15m + TrafficGraphData::DESIRED_DATA_SAMPLES/2, //Range_30m + TrafficGraphData::DESIRED_DATA_SAMPLES/2, //Range_1h + TrafficGraphData::DESIRED_DATA_SAMPLES/2, //Range_2h + TrafficGraphData::DESIRED_DATA_SAMPLES*2/3, //Range_3h + TrafficGraphData::DESIRED_DATA_SAMPLES/2, //Range_6h + TrafficGraphData::DESIRED_DATA_SAMPLES/2, //Range_12h + TrafficGraphData::DESIRED_DATA_SAMPLES/2, //Range_24h + } ; + +const int TrafficGraphData::SMALLEST_SAMPLE_PERIOD = + TrafficGraphData::RangeMinutes[TrafficGraphData::Range_5m] * 60 * 1000 / TrafficGraphData::DESIRED_DATA_SAMPLES; + +TrafficGraphData::TrafficGraphData(GraphRange range) + :currentGraphRange(range), + currentSampleCounter(0), + nLastBytesIn(0), + nLastBytesOut(0) +{ +} + + void TrafficGraphData::tryAddingSampleToStash(GraphRange range) +{ + SampleQueue& queue = sampleMap[range]; + if (queue.size() > DesiredQueueSizes[range]){ + sampleStash[range].push_front(queue.at(DesiredQueueSizes[range])); + } +} + +void TrafficGraphData::tryUpdateNextWithLast2Samples(GraphRange range, GraphRange nextRange) +{ + SampleQueue& queue = sampleMap[range]; + if (queue.size() == DesiredQueueSizes[range] + 2){ + update(nextRange, queue.takeLast() + queue.takeLast()); + } +} + +void TrafficGraphData::tryUpdateNextWithLast3Samples(GraphRange range, GraphRange nextRange) +{ + SampleQueue& stashQueue = sampleStash[range]; + if (stashQueue.size() == 3){ + update(nextRange, stashQueue.takeLast() + stashQueue.takeLast() + stashQueue.takeLast()); + } +} + +void TrafficGraphData::setLastBytes(quint64 nLastBytesIn, quint64 nLastBytesOut) +{ + this->nLastBytesIn = nLastBytesIn; + this->nLastBytesOut = nLastBytesOut; +} + +bool TrafficGraphData::update(quint64 totalBytesRecv, quint64 totalBytesSent) +{ + float inRate = (totalBytesRecv - nLastBytesIn) / 1024.0f * 1000 / SMALLEST_SAMPLE_PERIOD; + float outRate = (totalBytesSent - nLastBytesOut) / 1024.0f * 1000 / SMALLEST_SAMPLE_PERIOD; + nLastBytesIn = totalBytesRecv; + nLastBytesOut = totalBytesSent; + return update(TrafficSample(inRate,outRate)); +} + +bool TrafficGraphData::update(const TrafficSample& trafficSample) +{ + update(Range_5m, trafficSample); + + currentSampleCounter++; + + if (RangeMinutes[currentGraphRange]/RangeMinutes[Range_5m] == currentSampleCounter){ + currentSampleCounter = 0; + return true; + } + return false; +} + +void TrafficGraphData::update(GraphRange range, const TrafficSample& trafficSample) +{ + SampleQueue& queue = sampleMap[range]; + queue.push_front(trafficSample); + + switch(range){ + case Range_5m: + tryAddingSampleToStash(Range_5m); + tryUpdateNextWithLast2Samples(Range_5m, Range_10m); + tryUpdateNextWithLast3Samples(Range_5m, Range_15m); + return; + case Range_15m: + tryUpdateNextWithLast2Samples(Range_15m, Range_30m); + return; + case Range_30m: + tryUpdateNextWithLast2Samples(Range_30m, Range_1h); + return; + case Range_1h: + tryAddingSampleToStash(Range_1h); + tryUpdateNextWithLast2Samples(Range_1h, Range_2h); + tryUpdateNextWithLast3Samples(Range_1h, Range_3h); + return; + case Range_3h: + tryUpdateNextWithLast2Samples(Range_3h, Range_6h); + return; + case Range_6h: + tryUpdateNextWithLast2Samples( Range_6h, Range_12h); + return; + case Range_12h: + tryUpdateNextWithLast2Samples(Range_12h, Range_24h); + return; + default: + if (queue.size() > DesiredQueueSizes[range]) + queue.removeLast(); + return; + } +} + + +void TrafficGraphData::switchRange(GraphRange newRange) +{ + currentGraphRange = newRange; + currentSampleCounter = 0; +} + +TrafficGraphData::SampleQueue TrafficGraphData::sumEach2Samples(const SampleQueue& rangeQueue) +{ + SampleQueue result; + int i = rangeQueue.size() - 1; + + while (i - 1 >= 0){ + result.push_front(rangeQueue.at(i)+ rangeQueue.at(i-1)); + i -= 2; + } + return result; +} + +TrafficGraphData::SampleQueue TrafficGraphData::sumEach3Samples(const SampleQueue& rangeQueue, GraphRange range) +{ + SampleQueue result; + int lastUnusedSample = std::min(rangeQueue.size()-1 ,DESIRED_DATA_SAMPLES-1); + + // use stash first + SampleQueue& stashQueue = sampleStash[range]; + TrafficSample sum(0,0); + for (int i = 0; i < stashQueue.size(); i++){ + sum+=stashQueue.at(i); + } + int toFullSample = 3 - stashQueue.size(); + if (toFullSample > rangeQueue.size()) + return result; + + // append to stash data to create whole sample + for (int i = 0; i < toFullSample; i++){ + sum+=rangeQueue.at(lastUnusedSample); + lastUnusedSample--; + } + result.push_front(sum); + + while (lastUnusedSample - 2 >= 0){ + result.push_front(rangeQueue.at(lastUnusedSample) + rangeQueue.at(lastUnusedSample-1) + rangeQueue.at(lastUnusedSample-2)); + lastUnusedSample -= 3; + } + return result; +} + +TrafficGraphData::SampleQueue TrafficGraphData::getRangeQueue(GraphRange range) +{ + switch(range){ + case Range_5m: + return sampleMap[Range_5m]; + case Range_10m: + { + SampleQueue queue = sumEach2Samples(getRangeQueue(Range_5m)); + queue.append(sampleMap[Range_10m]); + return queue; + } + case Range_15m: + { + SampleQueue queue = sumEach3Samples(getRangeQueue(Range_5m), Range_5m); + queue.append(sampleMap[Range_15m]); + return queue; + } + case Range_30m: + { + SampleQueue queue = sumEach2Samples(getRangeQueue(Range_15m)); + queue.append(sampleMap[Range_30m]); + return queue; + } + case Range_1h: + { + SampleQueue queue = sumEach2Samples(getRangeQueue(Range_30m)); + queue.append(sampleMap[Range_1h]); + return queue; + } + case Range_2h: + { + SampleQueue queue = sumEach2Samples(getRangeQueue(Range_1h)); + queue.append(sampleMap[Range_2h]); + return queue; + } + case Range_3h: + { + SampleQueue queue = sumEach3Samples(getRangeQueue(Range_1h),Range_1h); + queue.append(sampleMap[Range_3h]); + return queue; + } + case Range_6h: + { + SampleQueue queue = sumEach2Samples(getRangeQueue(Range_3h)); + queue.append(sampleMap[Range_6h]); + return queue; + } + case Range_12h: + { + SampleQueue queue = sumEach2Samples(getRangeQueue(Range_6h)); + queue.append(sampleMap[Range_12h]); + return queue; + } + case Range_24h: + { + SampleQueue queue = sumEach2Samples(getRangeQueue(Range_12h)); + queue.append(sampleMap[Range_24h]); + return queue; + }; + default: + return SampleQueue(); + + } +} + +TrafficGraphData::SampleQueue TrafficGraphData::getCurrentRangeQueue() +{ + SampleQueue newQueue; + getRangeQueue(currentGraphRange).mid(0,DESIRED_DATA_SAMPLES).swap(newQueue); + return newQueue; +} + +void TrafficGraphData::clear() +{ + sampleMap.clear(); + sampleMap.clear(); + currentSampleCounter = 0; + nLastBytesIn = 0; + nLastBytesOut = 0; +} + + diff --git a/src/qt/trafficgraphdata.h b/src/qt/trafficgraphdata.h new file mode 100644 index 000000000..6c9fbd078 --- /dev/null +++ b/src/qt/trafficgraphdata.h @@ -0,0 +1,90 @@ +#ifndef TRAFFICGRAPHDATA_H +#define TRAFFICGRAPHDATA_H + +#include +#include + +struct TrafficSample +{ + float in; + float out; + + TrafficSample(float in, float out) + { + this->in = in; + this->out = out; + } + + TrafficSample operator+(const TrafficSample& other) const + { + return TrafficSample(this->in + other.in, this->out + other.out); + } + + TrafficSample& operator+=(const TrafficSample& rhs) + { + this->in += rhs.in; + this->out += rhs.out; + return *this; + } +}; + +class TrafficGraphData +{ +public: + enum GraphRange + { + Range_5m, + Range_10m, + Range_15m, + Range_30m, + Range_1h, + Range_2h, + Range_3h, + Range_6h, + Range_12h, + Range_24h, + }; + + static const int RangeMinutes[]; + static const int DESIRED_DATA_SAMPLES; + static const int SMALLEST_SAMPLE_PERIOD; + + typedef QQueue SampleQueue; + typedef QHash SampleQueueMap; + + TrafficGraphData(GraphRange range); + bool update(const TrafficSample& trafficSample); + bool update(quint64 totalBytesRecv, quint64 totalBytesSent); + void switchRange(GraphRange newRange); + SampleQueue getRangeQueue(GraphRange range); + SampleQueue getCurrentRangeQueue(); + void clear(); + void setLastBytes(quint64 nLastBytesIn, quint64 nLastBytesOut); + +private: + static const int DesiredQueueSizes[]; + + SampleQueueMap sampleMap; + SampleQueueMap sampleStash; + + GraphRange currentGraphRange; + int currentSampleCounter; + + quint64 nLastBytesIn; + quint64 nLastBytesOut; + + void update(GraphRange range, const TrafficSample &trafficSample); + + void tryAddingSampleToStash(GraphRange range); + void tryUpdateNextWithLast2Samples(GraphRange range, GraphRange nextRange); + void tryUpdateNextWithLast3Samples(GraphRange range, GraphRange nextRange); + + SampleQueue sumEach2Samples(const SampleQueue &rangeQueue); + SampleQueue sumEach3Samples(const SampleQueue &rangeQueue, GraphRange range); + + + TrafficGraphData(const TrafficGraphData& that); + TrafficGraphData& operator=(TrafficGraphData const&); +}; + +#endif // TRAFFICGRAPHDATA_H diff --git a/src/qt/trafficgraphwidget.cpp b/src/qt/trafficgraphwidget.cpp index 601d554c0..89b98356a 100644 --- a/src/qt/trafficgraphwidget.cpp +++ b/src/qt/trafficgraphwidget.cpp @@ -5,38 +5,38 @@ #include "trafficgraphwidget.h" #include "clientmodel.h" +#include + #include #include #include #include -#define DESIRED_SAMPLES 800 - #define XMARGIN 10 #define YMARGIN 10 +#define DEFAULT_SAMPLE_HEIGHT 1.1f + TrafficGraphWidget::TrafficGraphWidget(QWidget *parent) : QWidget(parent), timer(0), - fMax(0.0f), + fMax(DEFAULT_SAMPLE_HEIGHT), nMins(0), - vSamplesIn(), - vSamplesOut(), - nLastBytesIn(0), - nLastBytesOut(0), - clientModel(0) + clientModel(0), + trafficGraphData(TrafficGraphData::Range_30m) { timer = new QTimer(this); connect(timer, SIGNAL(timeout()), SLOT(updateRates())); + timer->setInterval(TrafficGraphData::SMALLEST_SAMPLE_PERIOD); + timer->start(); } void TrafficGraphWidget::setClientModel(ClientModel *model) { clientModel = model; if(model) { - nLastBytesIn = model->getTotalBytesRecv(); - nLastBytesOut = model->getTotalBytesSent(); + trafficGraphData.setLastBytes(model->getTotalBytesRecv(), model->getTotalBytesSent()); } } @@ -45,21 +45,34 @@ int TrafficGraphWidget::getGraphRangeMins() const return nMins; } -void TrafficGraphWidget::paintPath(QPainterPath &path, QQueue &samples) + +void TrafficGraphWidget::paintPath(QPainterPath &path, const TrafficGraphData::SampleQueue &queue, SampleChooser chooser) { int h = height() - YMARGIN * 2, w = width() - XMARGIN * 2; - int sampleCount = samples.size(), x = XMARGIN + w, y; + int sampleCount = queue.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); + x = XMARGIN + w - w * i / TrafficGraphData::DESIRED_DATA_SAMPLES; + y = YMARGIN + h - (int)(h * chooser(queue.at(i)) / fMax); path.lineTo(x, y); } path.lineTo(x, YMARGIN + h); } } +namespace +{ + float chooseIn(const TrafficSample& sample) + { + return sample.in; + } + float chooseOut(const TrafficSample& sample) + { + return sample.out; + } +} + void TrafficGraphWidget::paintEvent(QPaintEvent *) { QPainter painter(this); @@ -102,19 +115,20 @@ void TrafficGraphWidget::paintEvent(QPaintEvent *) } } - if(!vSamplesIn.empty()) { - QPainterPath p; - paintPath(p, vSamplesIn); - painter.fillPath(p, QColor(0, 255, 0, 128)); + const TrafficGraphData::SampleQueue& queue = trafficGraphData.getCurrentRangeQueue(); + + if(!queue.empty()) { + QPainterPath pIn; + paintPath(pIn, queue, boost::bind(chooseIn,_1)); + painter.fillPath(pIn, 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.drawPath(pIn); + + QPainterPath pOut; + paintPath(pOut, queue, boost::bind(chooseOut,_1)); + painter.fillPath(pOut, QColor(255, 0, 0, 128)); painter.setPen(Qt::red); - painter.drawPath(p); + painter.drawPath(pOut); } } @@ -122,54 +136,31 @@ void TrafficGraphWidget::updateRates() { 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; + bool updated = trafficGraphData.update(clientModel->getTotalBytesRecv(),clientModel->getTotalBytesSent()); - while(vSamplesIn.size() > DESIRED_SAMPLES) { - vSamplesIn.pop_back(); + if (updated){ + float tmax = DEFAULT_SAMPLE_HEIGHT; + Q_FOREACH(const TrafficSample& sample, trafficGraphData.getCurrentRangeQueue()) { + if(sample.in > tmax) tmax = sample.in; + if(sample.out > tmax) tmax = sample.out; + } + fMax = tmax; + update(); } - while(vSamplesOut.size() > DESIRED_SAMPLES) { - vSamplesOut.pop_back(); - } - - float tmax = 0.0f; - Q_FOREACH(float f, vSamplesIn) { - if(f > tmax) tmax = f; - } - Q_FOREACH(float f, vSamplesOut) { - if(f > tmax) tmax = f; - } - fMax = tmax; - update(); } -void TrafficGraphWidget::setGraphRangeMins(int mins) +void TrafficGraphWidget::setGraphRangeMins(int value) { - nMins = mins; - int msecsPerSample = nMins * 60 * 1000 / DESIRED_SAMPLES; - timer->stop(); - timer->setInterval(msecsPerSample); - - clear(); + trafficGraphData.switchRange(static_cast(value)); + update(); } void TrafficGraphWidget::clear() { - timer->stop(); - - vSamplesOut.clear(); - vSamplesIn.clear(); - fMax = 0.0f; - + trafficGraphData.clear(); + fMax = DEFAULT_SAMPLE_HEIGHT; if(clientModel) { - nLastBytesIn = clientModel->getTotalBytesRecv(); - nLastBytesOut = clientModel->getTotalBytesSent(); + trafficGraphData.setLastBytes(clientModel->getTotalBytesRecv(), clientModel->getTotalBytesSent()); } - timer->start(); + update(); } diff --git a/src/qt/trafficgraphwidget.h b/src/qt/trafficgraphwidget.h index 00660574a..87315dbcf 100644 --- a/src/qt/trafficgraphwidget.h +++ b/src/qt/trafficgraphwidget.h @@ -5,6 +5,10 @@ #ifndef BITCOIN_QT_TRAFFICGRAPHWIDGET_H #define BITCOIN_QT_TRAFFICGRAPHWIDGET_H +#include "trafficgraphdata.h" + +#include + #include #include @@ -29,20 +33,18 @@ protected: public Q_SLOTS: void updateRates(); - void setGraphRangeMins(int mins); + void setGraphRangeMins(int value); void clear(); private: - void paintPath(QPainterPath &path, QQueue &samples); + typedef boost::function SampleChooser; + void paintPath(QPainterPath &path, const TrafficGraphData::SampleQueue &queue, SampleChooser chooser); QTimer *timer; float fMax; int nMins; - QQueue vSamplesIn; - QQueue vSamplesOut; - quint64 nLastBytesIn; - quint64 nLastBytesOut; ClientModel *clientModel; + TrafficGraphData trafficGraphData; }; #endif // BITCOIN_QT_TRAFFICGRAPHWIDGET_H