From dfb7a23711b260ff9232f9d878b82804eac19c4c Mon Sep 17 00:00:00 2001 From: gades Date: Wed, 12 Nov 2014 15:11:02 +0200 Subject: [PATCH] Add traffic monitor --- novacoin-qt.pro | 2 + src/net.cpp | 52 ++++++-- src/net.h | 12 ++ src/qt/clientmodel.cpp | 12 ++ src/qt/clientmodel.h | 4 + src/qt/forms/rpcconsole.ui | 264 +++++++++++++++++++++++++++++++++++++++++ src/qt/guiutil.cpp | 20 +++ src/qt/guiutil.h | 2 + src/qt/locale/novacoin_en.ts | 8 ++ src/qt/locale/novacoin_ru.ts | 7 + src/qt/rpcconsole.cpp | 58 +++++++++ src/qt/rpcconsole.h | 18 +++ src/qt/trafficgraphwidget.cpp | 175 +++++++++++++++++++++++++++ src/qt/trafficgraphwidget.h | 48 ++++++++ 14 files changed, 668 insertions(+), 14 deletions(-) create mode 100644 src/qt/trafficgraphwidget.cpp create mode 100644 src/qt/trafficgraphwidget.h diff --git a/novacoin-qt.pro b/novacoin-qt.pro index aca0e19..a044cd6 100644 --- a/novacoin-qt.pro +++ b/novacoin-qt.pro @@ -245,6 +245,7 @@ HEADERS += src/qt/bitcoingui.h \ src/qt/bitcoinunits.h \ src/qt/qvaluecombobox.h \ src/qt/askpassphrasedialog.h \ + src/qt/trafficgraphwidget.h \ src/protocol.h \ src/qt/notificator.h \ src/qt/qtipcserver.h \ @@ -267,6 +268,7 @@ SOURCES += src/qt/bitcoin.cpp src/qt/bitcoingui.cpp \ src/qt/aboutdialog.cpp \ src/qt/editaddressdialog.cpp \ src/qt/bitcoinaddressvalidator.cpp \ + src/qt/trafficgraphwidget.cpp \ src/alert.cpp \ src/version.cpp \ src/sync.cpp \ diff --git a/src/net.cpp b/src/net.cpp index c2ec139..3b1ef2a 100644 --- a/src/net.cpp +++ b/src/net.cpp @@ -432,17 +432,17 @@ void AddressCurrentlyConnected(const CService& addr) - - +uint64_t CNode::nTotalBytesRecv = 0; +uint64_t CNode::nTotalBytesSent = 0; +CCriticalSection CNode::cs_totalBytesRecv; +CCriticalSection CNode::cs_totalBytesSent; CNode* FindNode(const CNetAddr& ip) { - { - LOCK(cs_vNodes); - BOOST_FOREACH(CNode* pnode, vNodes) - if ((CNetAddr)pnode->addr == ip) - return (pnode); - } + LOCK(cs_vNodes); + BOOST_FOREACH(CNode* pnode, vNodes) + if ((CNetAddr)pnode->addr == ip) + return (pnode); return NULL; } @@ -457,12 +457,10 @@ CNode* FindNode(std::string addrName) CNode* FindNode(const CService& addr) { - { - LOCK(cs_vNodes); - BOOST_FOREACH(CNode* pnode, vNodes) - if ((CService)pnode->addr == addr) - return (pnode); - } + LOCK(cs_vNodes); + BOOST_FOREACH(CNode* pnode, vNodes) + if ((CService)pnode->addr == addr) + return (pnode); return NULL; } @@ -912,6 +910,7 @@ void ThreadSocketHandler2(void* parg) memcpy(&vRecv[nPos], pchBuf, nBytes); pnode->nLastRecv = GetTime(); pnode->nRecvBytes += nBytes; + pnode->RecordBytesRecv(nBytes); } else if (nBytes == 0) { @@ -954,6 +953,7 @@ void ThreadSocketHandler2(void* parg) vSend.erase(vSend.begin(), vSend.begin() + nBytes); pnode->nLastSend = GetTime(); pnode->nSendBytes += nBytes; + pnode->RecordBytesSent(nBytes); } else if (nBytes < 0) { @@ -2033,3 +2033,27 @@ void RelayTransaction(const CTransaction& tx, const uint256& hash, const CDataSt RelayInventory(inv); } + +void CNode::RecordBytesRecv(uint64_t bytes) +{ + LOCK(cs_totalBytesRecv); + nTotalBytesRecv += bytes; +} + +void CNode::RecordBytesSent(uint64_t bytes) +{ + LOCK(cs_totalBytesSent); + nTotalBytesSent += bytes; +} + +uint64_t CNode::GetTotalBytesRecv() +{ + LOCK(cs_totalBytesRecv); + return nTotalBytesRecv; +} + +uint64_t CNode::GetTotalBytesSent() +{ + LOCK(cs_totalBytesSent); + return nTotalBytesSent; +} diff --git a/src/net.h b/src/net.h index cf80be2..ddcaa15 100644 --- a/src/net.h +++ b/src/net.h @@ -265,7 +265,13 @@ public: } } + private: + // Network usage totals + static CCriticalSection cs_totalBytesRecv; + static CCriticalSection cs_totalBytesSent; + static uint64_t nTotalBytesRecv; + static uint64_t nTotalBytesSent; CNode(const CNode&); void operator=(const CNode&); public: @@ -650,6 +656,12 @@ public: static bool IsBanned(CNetAddr ip); bool Misbehaving(int howmuch); // 1 == a little, 100 == a lot void copyStats(CNodeStats &stats); + // Network stats + static void RecordBytesRecv(uint64_t bytes); + static void RecordBytesSent(uint64_t bytes); + + static uint64_t GetTotalBytesRecv(); + static uint64_t GetTotalBytesSent(); }; inline void RelayInventory(const CInv& inv) diff --git a/src/qt/clientmodel.cpp b/src/qt/clientmodel.cpp index 064882b..c100188 100644 --- a/src/qt/clientmodel.cpp +++ b/src/qt/clientmodel.cpp @@ -64,6 +64,16 @@ int ClientModel::getNumBlocksAtStartup() return numBlocksAtStartup; } +quint64 ClientModel::getTotalBytesRecv() const +{ + return CNode::GetTotalBytesRecv(); +} + +quint64 ClientModel::getTotalBytesSent() const +{ + return CNode::GetTotalBytesSent(); +} + QDateTime ClientModel::getLastBlockDate() const { if (pindexBest) @@ -86,6 +96,8 @@ void ClientModel::updateTimer() emit numBlocksChanged(newNumBlocks, newNumBlocksOfPeers); } + + emit bytesChanged(getTotalBytesRecv(), getTotalBytesSent()); } void ClientModel::updateNumConnections(int numConnections) diff --git a/src/qt/clientmodel.h b/src/qt/clientmodel.h index 43c863d..c8d30f6 100644 --- a/src/qt/clientmodel.h +++ b/src/qt/clientmodel.h @@ -30,6 +30,9 @@ public: int getNumBlocks() const; int getNumBlocksAtStartup(); + quint64 getTotalBytesRecv() const; + quint64 getTotalBytesSent() const; + QDateTime getLastBlockDate() const; //! Return true if client connected to testnet @@ -61,6 +64,7 @@ private: signals: void numConnectionsChanged(int count); void numBlocksChanged(int count, int countOfPeers); + void bytesChanged(quint64 totalBytesIn, quint64 totalBytesOut); //! Asynchronous error notification void error(const QString &title, const QString &message, bool modal); diff --git a/src/qt/forms/rpcconsole.ui b/src/qt/forms/rpcconsole.ui index a36c553..098b3cd 100644 --- a/src/qt/forms/rpcconsole.ui +++ b/src/qt/forms/rpcconsole.ui @@ -445,10 +445,274 @@ + + + &Network Traffic + + + + + + + + + 0 + 0 + + + + + + + + + + 1 + + + 288 + + + 12 + + + 6 + + + Qt::Horizontal + + + + + + + + 100 + 0 + + + + Qt::AlignCenter + + + + + + + &Clear + + + false + + + + + + + + + + + + + Totals + + + + + + + + + 0 + 0 + + + + + 10 + 0 + + + + + + + + + 0 + 255 + 0 + + + + + + + + + 0 + 255 + 0 + + + + + + + + + 0 + 255 + 0 + + + + + + + + Qt::Horizontal + + + + + + + Received + + + + + + + + 50 + 0 + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + + + + + 0 + 0 + + + + + 10 + 0 + + + + + + + + + 255 + 0 + 0 + + + + + + + + + 255 + 0 + 0 + + + + + + + + + 255 + 0 + 0 + + + + + + + + Qt::Horizontal + + + + + + + Sent + + + + + + + + 50 + 0 + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + + Qt::Vertical + + + + 20 + 407 + + + + + + + + + + + + + + TrafficGraphWidget + QWidget +
trafficgraphwidget.h
+ 1 + + clear() + +
+
diff --git a/src/qt/guiutil.cpp b/src/qt/guiutil.cpp index 646e27c..53873e5 100644 --- a/src/qt/guiutil.cpp +++ b/src/qt/guiutil.cpp @@ -471,5 +471,25 @@ void HelpMessageBox::showOrPrint() #endif } +QString formatDurationStr(int secs) +{ + QStringList strList; + int days = secs / 86400; + int hours = (secs % 86400) / 3600; + int mins = (secs % 3600) / 60; + int seconds = secs % 60; + + if (days) + strList.append(QString(QObject::tr("%1 d")).arg(days)); + if (hours) + strList.append(QString(QObject::tr("%1 h")).arg(hours)); + if (mins) + strList.append(QString(QObject::tr("%1 m")).arg(mins)); + if (seconds || (!days && !hours && !mins)) + strList.append(QString(QObject::tr("%1 s")).arg(seconds)); + + return strList.join(" "); +} + } // namespace GUIUtil diff --git a/src/qt/guiutil.h b/src/qt/guiutil.h index 5b7ee21..1ff4aba 100644 --- a/src/qt/guiutil.h +++ b/src/qt/guiutil.h @@ -114,6 +114,8 @@ namespace GUIUtil QString coreOptions; QString uiOptions; }; + /* Convert seconds into a QString with days, hours, mins, secs */ + QString formatDurationStr(int secs); } // namespace GUIUtil diff --git a/src/qt/locale/novacoin_en.ts b/src/qt/locale/novacoin_en.ts index cf48a2f..eca0faf 100644 --- a/src/qt/locale/novacoin_en.ts +++ b/src/qt/locale/novacoin_en.ts @@ -2223,6 +2223,14 @@ This label turns red, if the priority is smaller than "medium". + TrafficGraphWidget + + + KB/s + + + + TransactionDesc diff --git a/src/qt/locale/novacoin_ru.ts b/src/qt/locale/novacoin_ru.ts index 41d3856..0394730 100644 --- a/src/qt/locale/novacoin_ru.ts +++ b/src/qt/locale/novacoin_ru.ts @@ -2246,6 +2246,13 @@ This label turns red, if the priority is smaller than "medium". + TrafficGraphWidget + + KB/s + КБ/сек + + + TransactionDesc diff --git a/src/qt/rpcconsole.cpp b/src/qt/rpcconsole.cpp index 3d498f0..1f2fa9d 100644 --- a/src/qt/rpcconsole.cpp +++ b/src/qt/rpcconsole.cpp @@ -24,6 +24,8 @@ const int CONSOLE_HISTORY = 50; const QSize ICON_SIZE(24, 24); +const int INITIAL_TRAFFIC_GRAPH_MINS = 30; + const struct { const char *url; const char *source; @@ -203,11 +205,13 @@ RPCConsole::RPCConsole(QWidget *parent) : ui->messagesWidget->installEventFilter(this); connect(ui->clearButton, SIGNAL(clicked()), this, SLOT(clear())); + connect(ui->btnClearTrafficGraph, SIGNAL(clicked()), ui->trafficGraph, SLOT(clear())); // set OpenSSL version label ui->openSSLVersion->setText(SSLeay_version(SSLEAY_VERSION)); startExecutor(); + setTrafficGraphRange(INITIAL_TRAFFIC_GRAPH_MINS); clear(); } @@ -257,12 +261,15 @@ bool RPCConsole::eventFilter(QObject* obj, QEvent *event) void RPCConsole::setClientModel(ClientModel *model) { this->clientModel = model; + ui->trafficGraph->setClientModel(model); if(model) { // Subscribe to information, replies, messages, errors connect(model, SIGNAL(numConnectionsChanged(int)), this, SLOT(setNumConnections(int))); connect(model, SIGNAL(numBlocksChanged(int,int)), this, SLOT(setNumBlocks(int,int))); + updateTrafficStats(model->getTotalBytesRecv(), model->getTotalBytesSent()); + connect(model, SIGNAL(bytesChanged(quint64,quint64)), this, SLOT(updateTrafficStats(quint64, quint64))); // Provide initial values ui->clientVersion->setText(model->formatFullVersion()); ui->clientName->setText(model->clientName()); @@ -436,3 +443,54 @@ void RPCConsole::on_showCLOptionsButton_clicked() GUIUtil::HelpMessageBox help; help.exec(); } +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); +} + +QString RPCConsole::FormatBytes(quint64 bytes) +{ + if(bytes < 1024) + return QString(tr("%1 B")).arg(bytes); + if(bytes < 1024 * 1024) + return QString(tr("%1 KB")).arg(bytes / 1024); + if(bytes < 1024 * 1024 * 1024) + return QString(tr("%1 MB")).arg(bytes / 1024 / 1024); + + return QString(tr("%1 GB")).arg(bytes / 1024 / 1024 / 1024); +} + +void RPCConsole::setTrafficGraphRange(int mins) +{ + ui->trafficGraph->setGraphRangeMins(mins); + ui->lblGraphRange->setText(GUIUtil::formatDurationStr(mins * 60)); +} + +void RPCConsole::updateTrafficStats(quint64 totalBytesIn, quint64 totalBytesOut) +{ + ui->lblBytesIn->setText(FormatBytes(totalBytesIn)); + ui->lblBytesOut->setText(FormatBytes(totalBytesOut)); +} + +void RPCConsole::resizeEvent(QResizeEvent *event) +{ + QWidget::resizeEvent(event); +} + +void RPCConsole::showEvent(QShowEvent *event) +{ + QWidget::showEvent(event); + + if (!clientModel) + return; +} + +void RPCConsole::hideEvent(QHideEvent *event) +{ + QWidget::hideEvent(event); + + if (!clientModel) + return; +} diff --git a/src/qt/rpcconsole.h b/src/qt/rpcconsole.h index 3c38b4b..a4d1ebb 100644 --- a/src/qt/rpcconsole.h +++ b/src/qt/rpcconsole.h @@ -35,6 +35,13 @@ private slots: void on_tabWidget_currentChanged(int index); /** open the debug.log from the current datadir */ void on_openDebugLogfileButton_clicked(); + /** change the time range of the network traffic graph */ + void on_sldGraphRange_valueChanged(int value); + /** update traffic statistics */ + void updateTrafficStats(quint64 totalBytesIn, quint64 totalBytesOut); + void resizeEvent(QResizeEvent *event); + void showEvent(QShowEvent *event); + void hideEvent(QHideEvent *event); /** display messagebox with program parameters (same as bitcoin-qt --help) */ void on_showCLOptionsButton_clicked(); @@ -55,6 +62,17 @@ signals: void cmdRequest(const QString &command); private: + static QString FormatBytes(quint64 bytes); + void setTrafficGraphRange(int mins); + /** show detailed information on ui about selected node */ + + enum ColumnWidths + { + ADDRESS_COLUMN_WIDTH = 200, + SUBVERSION_COLUMN_WIDTH = 100, + PING_COLUMN_WIDTH = 80 + }; + Ui::RPCConsole *ui; ClientModel *clientModel; QStringList history; diff --git a/src/qt/trafficgraphwidget.cpp b/src/qt/trafficgraphwidget.cpp new file mode 100644 index 0000000..5f14b80 --- /dev/null +++ b/src/qt/trafficgraphwidget.cpp @@ -0,0 +1,175 @@ +// Copyright (c) 2011-2013 The Bitcoin developers +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include "trafficgraphwidget.h" +#include "clientmodel.h" + +#include +#include +#include + +#include + +#define DESIRED_SAMPLES 800 + +#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), + clientModel(0) +{ + timer = new QTimer(this); + connect(timer, SIGNAL(timeout()), SLOT(updateRates())); +} + +void TrafficGraphWidget::setClientModel(ClientModel *model) +{ + clientModel = model; + if(model) { + nLastBytesIn = model->getTotalBytesRecv(); + nLastBytesOut = model->getTotalBytesSent(); + } +} + +int TrafficGraphWidget::getGraphRangeMins() const +{ + return nMins; +} + +void TrafficGraphWidget::paintPath(QPainterPath &path, QQueue &samples) +{ + 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); + } +} + +void TrafficGraphWidget::paintEvent(QPaintEvent *) +{ + QPainter painter(this); + painter.fillRect(rect(), Qt::black); + + if(fMax <= 0.0f) return; + + QColor axisCol(Qt::gray); + int h = height() - YMARGIN * 2; + painter.setPen(axisCol); + painter.drawLine(XMARGIN, YMARGIN + h, width() - XMARGIN, YMARGIN + h); + + // decide what order of magnitude we are + int base = floor(log10(fMax)); + float val = pow(10.0f, base); + + 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)); + for(float y = val; y < fMax; y += val) { + 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(); + val = pow(10.0f, base - 1); + painter.setPen(axisCol); + painter.drawText(XMARGIN, YMARGIN + h - h * val / fMax-yMarginText, QString("%1 %2").arg(val).arg(units)); + int count = 1; + for(float y = val; y < fMax; y += val, count++) { + // don't overwrite lines drawn above + if(count % 10 == 0) + continue; + int yy = YMARGIN + h - h * y / fMax; + painter.drawLine(XMARGIN, yy, width() - XMARGIN, yy); + } + } + + 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); + } +} + +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; + + while(vSamplesIn.size() > DESIRED_SAMPLES) { + vSamplesIn.pop_back(); + } + while(vSamplesOut.size() > DESIRED_SAMPLES) { + vSamplesOut.pop_back(); + } + + float tmax = 0.0f; + foreach(float f, vSamplesIn) { + if(f > tmax) tmax = f; + } + foreach(float f, vSamplesOut) { + if(f > tmax) tmax = f; + } + fMax = tmax; + update(); +} + +void TrafficGraphWidget::setGraphRangeMins(int mins) +{ + nMins = mins; + int msecsPerSample = nMins * 60 * 1000 / DESIRED_SAMPLES; + timer->stop(); + timer->setInterval(msecsPerSample); + + clear(); +} + +void TrafficGraphWidget::clear() +{ + timer->stop(); + + vSamplesOut.clear(); + vSamplesIn.clear(); + fMax = 0.0f; + + if(clientModel) { + nLastBytesIn = clientModel->getTotalBytesRecv(); + nLastBytesOut = clientModel->getTotalBytesSent(); + } + timer->start(); +} diff --git a/src/qt/trafficgraphwidget.h b/src/qt/trafficgraphwidget.h new file mode 100644 index 0000000..50571e0 --- /dev/null +++ b/src/qt/trafficgraphwidget.h @@ -0,0 +1,48 @@ +// Copyright (c) 2011-2013 The Bitcoin developers +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef BITCOIN_QT_TRAFFICGRAPHWIDGET_H +#define BITCOIN_QT_TRAFFICGRAPHWIDGET_H + +#include +#include + +class ClientModel; + +QT_BEGIN_NAMESPACE +class QPaintEvent; +class QTimer; +QT_END_NAMESPACE + +class TrafficGraphWidget : public QWidget +{ + Q_OBJECT + +public: + explicit TrafficGraphWidget(QWidget *parent = 0); + void setClientModel(ClientModel *model); + int getGraphRangeMins() const; + +protected: + void paintEvent(QPaintEvent *); + +public slots: + void updateRates(); + void setGraphRangeMins(int mins); + void clear(); + +private: + void paintPath(QPainterPath &path, QQueue &samples); + + QTimer *timer; + float fMax; + int nMins; + QQueue vSamplesIn; + QQueue vSamplesOut; + quint64 nLastBytesIn; + quint64 nLastBytesOut; + ClientModel *clientModel; +}; + +#endif // BITCOIN_QT_TRAFFICGRAPHWIDGET_H -- 1.7.1