Add traffic monitor
authorDmitriy Korniychuk <dmitiry@korniychuk.org.ua>
Wed, 12 Nov 2014 13:11:02 +0000 (15:11 +0200)
committerDmitriy Korniychuk <dmitiry@korniychuk.org.ua>
Wed, 12 Nov 2014 13:11:02 +0000 (15:11 +0200)
14 files changed:
novacoin-qt.pro
src/net.cpp
src/net.h
src/qt/clientmodel.cpp
src/qt/clientmodel.h
src/qt/forms/rpcconsole.ui
src/qt/guiutil.cpp
src/qt/guiutil.h
src/qt/locale/novacoin_en.ts
src/qt/locale/novacoin_ru.ts
src/qt/rpcconsole.cpp
src/qt/rpcconsole.h
src/qt/trafficgraphwidget.cpp [new file with mode: 0644]
src/qt/trafficgraphwidget.h [new file with mode: 0644]

index aca0e19..a044cd6 100644 (file)
@@ -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 \
index c2ec139..3b1ef2a 100644 (file)
@@ -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;
+}
index cf80be2..ddcaa15 100644 (file)
--- 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)
index 064882b..c100188 100644 (file)
@@ -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)
index 43c863d..c8d30f6 100644 (file)
@@ -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);
index a36c553..098b3cd 100644 (file)
        </item>
       </layout>
      </widget>
+     <widget class="QWidget" name="tab_nettraffic">
+      <attribute name="title">
+       <string>&amp;Network Traffic</string>
+      </attribute>
+      <layout class="QHBoxLayout" name="horizontalLayout_3">
+       <item>
+        <layout class="QVBoxLayout" name="verticalLayout_4">
+         <item>
+          <widget class="TrafficGraphWidget" name="trafficGraph" native="true">
+           <property name="sizePolicy">
+            <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
+             <horstretch>0</horstretch>
+             <verstretch>0</verstretch>
+            </sizepolicy>
+           </property>
+          </widget>
+         </item>
+         <item>
+          <layout class="QHBoxLayout" name="horizontalLayout_2">
+           <item>
+            <widget class="QSlider" name="sldGraphRange">
+             <property name="minimum">
+              <number>1</number>
+             </property>
+             <property name="maximum">
+              <number>288</number>
+             </property>
+             <property name="pageStep">
+              <number>12</number>
+             </property>
+             <property name="value">
+              <number>6</number>
+             </property>
+             <property name="orientation">
+              <enum>Qt::Horizontal</enum>
+             </property>
+            </widget>
+           </item>
+           <item>
+            <widget class="QLabel" name="lblGraphRange">
+             <property name="minimumSize">
+              <size>
+               <width>100</width>
+               <height>0</height>
+              </size>
+             </property>
+             <property name="alignment">
+              <set>Qt::AlignCenter</set>
+             </property>
+            </widget>
+           </item>
+           <item>
+            <widget class="QPushButton" name="btnClearTrafficGraph">
+             <property name="text">
+              <string>&amp;Clear</string>
+             </property>
+             <property name="autoDefault">
+              <bool>false</bool>
+             </property>
+            </widget>
+           </item>
+          </layout>
+         </item>
+        </layout>
+       </item>
+       <item>
+        <layout class="QVBoxLayout" name="verticalLayout">
+         <item>
+          <widget class="QGroupBox" name="groupBox">
+           <property name="title">
+            <string>Totals</string>
+           </property>
+           <layout class="QVBoxLayout" name="verticalLayout_5">
+            <item>
+             <layout class="QHBoxLayout" name="horizontalLayout_4">
+              <item>
+               <widget class="Line" name="line">
+                <property name="sizePolicy">
+                 <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+                  <horstretch>0</horstretch>
+                  <verstretch>0</verstretch>
+                 </sizepolicy>
+                </property>
+                <property name="minimumSize">
+                 <size>
+                  <width>10</width>
+                  <height>0</height>
+                 </size>
+                </property>
+                <property name="palette">
+                 <palette>
+                  <active>
+                   <colorrole role="Light">
+                    <brush brushstyle="SolidPattern">
+                     <color alpha="255">
+                      <red>0</red>
+                      <green>255</green>
+                      <blue>0</blue>
+                     </color>
+                    </brush>
+                   </colorrole>
+                  </active>
+                  <inactive>
+                   <colorrole role="Light">
+                    <brush brushstyle="SolidPattern">
+                     <color alpha="255">
+                      <red>0</red>
+                      <green>255</green>
+                      <blue>0</blue>
+                     </color>
+                    </brush>
+                   </colorrole>
+                  </inactive>
+                  <disabled>
+                   <colorrole role="Light">
+                    <brush brushstyle="SolidPattern">
+                     <color alpha="255">
+                      <red>0</red>
+                      <green>255</green>
+                      <blue>0</blue>
+                     </color>
+                    </brush>
+                   </colorrole>
+                  </disabled>
+                 </palette>
+                </property>
+                <property name="orientation">
+                 <enum>Qt::Horizontal</enum>
+                </property>
+               </widget>
+              </item>
+              <item>
+               <widget class="QLabel" name="label_16">
+                <property name="text">
+                 <string>Received</string>
+                </property>
+               </widget>
+              </item>
+              <item>
+               <widget class="QLabel" name="lblBytesIn">
+                <property name="minimumSize">
+                 <size>
+                  <width>50</width>
+                  <height>0</height>
+                 </size>
+                </property>
+                <property name="alignment">
+                 <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+                </property>
+               </widget>
+              </item>
+             </layout>
+            </item>
+            <item>
+             <layout class="QHBoxLayout" name="horizontalLayout_5">
+              <item>
+               <widget class="Line" name="line_2">
+                <property name="sizePolicy">
+                 <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+                  <horstretch>0</horstretch>
+                  <verstretch>0</verstretch>
+                 </sizepolicy>
+                </property>
+                <property name="minimumSize">
+                 <size>
+                  <width>10</width>
+                  <height>0</height>
+                 </size>
+                </property>
+                <property name="palette">
+                 <palette>
+                  <active>
+                   <colorrole role="Light">
+                    <brush brushstyle="SolidPattern">
+                     <color alpha="255">
+                      <red>255</red>
+                      <green>0</green>
+                      <blue>0</blue>
+                     </color>
+                    </brush>
+                   </colorrole>
+                  </active>
+                  <inactive>
+                   <colorrole role="Light">
+                    <brush brushstyle="SolidPattern">
+                     <color alpha="255">
+                      <red>255</red>
+                      <green>0</green>
+                      <blue>0</blue>
+                     </color>
+                    </brush>
+                   </colorrole>
+                  </inactive>
+                  <disabled>
+                   <colorrole role="Light">
+                    <brush brushstyle="SolidPattern">
+                     <color alpha="255">
+                      <red>255</red>
+                      <green>0</green>
+                      <blue>0</blue>
+                     </color>
+                    </brush>
+                   </colorrole>
+                  </disabled>
+                 </palette>
+                </property>
+                <property name="orientation">
+                 <enum>Qt::Horizontal</enum>
+                </property>
+               </widget>
+              </item>
+              <item>
+               <widget class="QLabel" name="label_17">
+                <property name="text">
+                 <string>Sent</string>
+                </property>
+               </widget>
+              </item>
+              <item>
+               <widget class="QLabel" name="lblBytesOut">
+                <property name="minimumSize">
+                 <size>
+                  <width>50</width>
+                  <height>0</height>
+                 </size>
+                </property>
+                <property name="alignment">
+                 <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+                </property>
+               </widget>
+              </item>
+             </layout>
+            </item>
+            <item>
+             <spacer name="verticalSpacer_4">
+              <property name="orientation">
+               <enum>Qt::Vertical</enum>
+              </property>
+              <property name="sizeHint" stdset="0">
+               <size>
+                <width>20</width>
+                <height>407</height>
+               </size>
+              </property>
+             </spacer>
+            </item>
+           </layout>
+          </widget>
+         </item>
+        </layout>
+       </item>
+      </layout>
+     </widget>
     </widget>
    </item>
   </layout>
  </widget>
+ <customwidgets>
+  <customwidget>
+   <class>TrafficGraphWidget</class>
+   <extends>QWidget</extends>
+   <header>trafficgraphwidget.h</header>
+   <container>1</container>
+   <slots>
+    <slot>clear()</slot>
+   </slots>
+  </customwidget>
+ </customwidgets>
  <resources>
   <include location="../bitcoin.qrc"/>
  </resources>
index 646e27c..53873e5 100644 (file)
@@ -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
 
index 5b7ee21..1ff4aba 100644 (file)
@@ -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
 
index cf48a2f..eca0faf 100644 (file)
@@ -2223,6 +2223,14 @@ This label turns red, if the priority is smaller than &quot;medium&quot;.
     </message>
 </context>
 <context>
+    <name>TrafficGraphWidget</name>
+    <message>
+        <location filename="../trafficgraphwidget.cpp" line="+79"/>
+        <source>KB/s</source>
+        <translation type="unfinished"></translation>
+    </message>
+</context>
+<context>
     <name>TransactionDesc</name>
     <message>
         <location filename="../transactiondesc.cpp" line="+19"/>
index 41d3856..0394730 100644 (file)
@@ -2246,6 +2246,13 @@ This label turns red, if the priority is smaller than &quot;medium&quot;.
     </message>
 </context>
 <context>
+    <name>TrafficGraphWidget</name>
+    <message>
+        <source>KB/s</source>
+        <translation>КБ/сек</translation>
+    </message>
+</context>
+<context>
     <name>TransactionDesc</name>
     <message>
         <location filename="../transactiondesc.cpp" line="+19"/>
index 3d498f0..1f2fa9d 100644 (file)
@@ -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;
+}
index 3c38b4b..a4d1ebb 100644 (file)
@@ -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 (file)
index 0000000..5f14b80
--- /dev/null
@@ -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 <QPainter>
+#include <QColor>
+#include <QTimer>
+
+#include <cmath>
+
+#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<float> &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 (file)
index 0000000..50571e0
--- /dev/null
@@ -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 <QWidget>
+#include <QQueue>
+
+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<float> &samples);
+
+    QTimer *timer;
+    float fMax;
+    int nMins;
+    QQueue<float> vSamplesIn;
+    QQueue<float> vSamplesOut;
+    quint64 nLastBytesIn;
+    quint64 nLastBytesOut;
+    ClientModel *clientModel;
+};
+
+#endif // BITCOIN_QT_TRAFFICGRAPHWIDGET_H