Merge pull request #2924 from sje397/TrafficGraph

[QT] Add network traffic graph to debug window
This commit is contained in:
Wladimir J. van der Laan
2013-10-15 03:48:22 -07:00
13 changed files with 618 additions and 9 deletions

View File

@@ -48,7 +48,7 @@ QT_MOC_CPP = moc_aboutdialog.cpp moc_addressbookpage.cpp \
moc_optionsmodel.cpp moc_overviewpage.cpp moc_paymentserver.cpp \
moc_qrcodedialog.cpp moc_qvalidatedlineedit.cpp moc_qvaluecombobox.cpp \
moc_rpcconsole.cpp moc_sendcoinsdialog.cpp moc_sendcoinsentry.cpp \
moc_signverifymessagedialog.cpp moc_splashscreen.cpp moc_transactiondesc.cpp \
moc_signverifymessagedialog.cpp moc_splashscreen.cpp moc_trafficgraphwidget.cpp moc_transactiondesc.cpp \
moc_transactiondescdialog.cpp moc_transactionfilterproxy.cpp \
moc_transactiontablemodel.cpp moc_transactionview.cpp moc_walletframe.cpp \
moc_walletmodel.cpp moc_walletstack.cpp moc_walletview.cpp
@@ -73,7 +73,7 @@ BITCOIN_QT_H = aboutdialog.h addressbookpage.h addresstablemodel.h \
optionsmodel.h overviewpage.h paymentrequestplus.h paymentserver.h \
qrcodedialog.h qvalidatedlineedit.h qvaluecombobox.h rpcconsole.h \
sendcoinsdialog.h sendcoinsentry.h signverifymessagedialog.h splashscreen.h \
transactiondescdialog.h transactiondesc.h transactionfilterproxy.h \
trafficgraphwidget.h transactiondescdialog.h transactiondesc.h transactionfilterproxy.h \
transactionrecord.h transactiontablemodel.h transactionview.h walletframe.h \
walletmodel.h walletmodeltransaction.h walletstack.h walletview.h
@@ -102,7 +102,7 @@ BITCOIN_QT_CPP = aboutdialog.cpp addressbookpage.cpp \
optionsdialog.cpp optionsmodel.cpp overviewpage.cpp paymentrequestplus.cpp \
paymentserver.cpp qvalidatedlineedit.cpp qvaluecombobox.cpp \
rpcconsole.cpp sendcoinsdialog.cpp sendcoinsentry.cpp \
signverifymessagedialog.cpp splashscreen.cpp transactiondesc.cpp \
signverifymessagedialog.cpp splashscreen.cpp trafficgraphwidget.cpp transactiondesc.cpp \
transactiondescdialog.cpp transactionfilterproxy.cpp transactionrecord.cpp \
transactiontablemodel.cpp transactionview.cpp walletframe.cpp \
walletmodel.cpp walletmodeltransaction.cpp walletstack.cpp walletview.cpp

View File

@@ -51,6 +51,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 (chainActive.Tip())
@@ -85,6 +95,8 @@ void ClientModel::updateTimer()
// ensure we return the maximum of newNumBlocksOfPeers and newNumBlocks to not create weird displays in the GUI
emit numBlocksChanged(newNumBlocks, std::max(newNumBlocksOfPeers, newNumBlocks));
}
emit bytesChanged(getTotalBytesRecv(), getTotalBytesSent());
}
void ClientModel::updateNumConnections(int numConnections)

View File

@@ -35,6 +35,9 @@ public:
int getNumBlocks() const;
int getNumBlocksAtStartup();
quint64 getTotalBytesRecv() const;
quint64 getTotalBytesSent() const;
double getVerificationProgress() const;
QDateTime getLastBlockDate() const;
@@ -74,6 +77,7 @@ signals:
void numConnectionsChanged(int count);
void numBlocksChanged(int count, int countOfPeers);
void alertsChanged(const QString &warnings);
void bytesChanged(quint64 totalBytesIn, quint64 totalBytesOut);
//! Asynchronous message notification
void message(const QString &title, const QString &message, unsigned int style);

View File

@@ -445,10 +445,271 @@
</item>
</layout>
</widget>
<widget class="QWidget" name="tab">
<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>
</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>In:</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>Out:</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>

View File

@@ -22,6 +22,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;
@@ -204,6 +206,7 @@ RPCConsole::RPCConsole(QWidget *parent) :
ui->openSSLVersion->setText(SSLeay_version(SSLEAY_VERSION));
startExecutor();
setTrafficGraphRange(INITIAL_TRAFFIC_GRAPH_MINS);
clear();
}
@@ -253,7 +256,8 @@ bool RPCConsole::eventFilter(QObject* obj, QEvent *event)
void RPCConsole::setClientModel(ClientModel *model)
{
this->clientModel = model;
clientModel = model;
ui->trafficGraph->setClientModel(model);
if(model)
{
// Keep up to date with client
@@ -263,6 +267,9 @@ void RPCConsole::setClientModel(ClientModel *model)
setNumBlocks(model->getNumBlocks(), model->getNumBlocksOfPeers());
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());
@@ -431,3 +438,49 @@ 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);
if(mins < 60) {
ui->lblGraphRange->setText(QString(tr("%1 m")).arg(mins));
} else {
int hours = mins / 60;
int minsLeft = mins % 60;
if(minsLeft == 0) {
ui->lblGraphRange->setText(QString(tr("%1 h")).arg(hours));
} else {
ui->lblGraphRange->setText(QString(tr("%1 h %2 m")).arg(hours).arg(minsLeft));
}
}
}
void RPCConsole::updateTrafficStats(quint64 totalBytesIn, quint64 totalBytesOut)
{
ui->lblBytesIn->setText(FormatBytes(totalBytesIn));
ui->lblBytesOut->setText(FormatBytes(totalBytesOut));
}
void RPCConsole::on_btnClearTrafficGraph_clicked()
{
ui->trafficGraph->clear();
}

View File

@@ -37,6 +37,12 @@ private slots:
void on_openDebugLogfileButton_clicked();
/** display messagebox with program parameters (same as bitcoin-qt --help) */
void on_showCLOptionsButton_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);
/** clear traffic graph */
void on_btnClearTrafficGraph_clicked();
public slots:
void clear();
@@ -55,6 +61,9 @@ signals:
void cmdRequest(const QString &command);
private:
static QString FormatBytes(quint64 bytes);
void setTrafficGraphRange(int mins);
Ui::RPCConsole *ui;
ClientModel *clientModel;
QStringList history;

View File

@@ -0,0 +1,169 @@
#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");
// draw lines
painter.setPen(axisCol);
painter.drawText(XMARGIN, YMARGIN + h - h * val / fMax, 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, 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();
}

View File

@@ -0,0 +1,44 @@
#ifndef TRAFFICGRAPHWIDGET_H
#define 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 // TRAFFICGRAPHWIDGET_H