diff --git a/src/qt/forms/debugwindow.ui b/src/qt/forms/debugwindow.ui index 21968010230..97c9ebe3f9d 100644 --- a/src/qt/forms/debugwindow.ui +++ b/src/qt/forms/debugwindow.ui @@ -630,20 +630,26 @@ - 1 + 0 - 288 + 2400 - 12 + 200 - 6 + 0 Qt::Horizontal + + QSlider::TicksBelow + + + 200 + @@ -659,16 +665,6 @@ - - - - &Reset - - - false - - - diff --git a/src/qt/guiutil.cpp b/src/qt/guiutil.cpp index 9565fa508f1..783c45f3577 100644 --- a/src/qt/guiutil.cpp +++ b/src/qt/guiutil.cpp @@ -799,14 +799,56 @@ QString formatBytes(uint64_t bytes) { if (bytes < 1'000) return QObject::tr("%1 B").arg(bytes); + if (bytes < 10'000) + return QObject::tr("%1 kB").arg(0.01 * (bytes / 10)); + if (bytes < 100'000) + return QObject::tr("%1 kB").arg(0.1 * (bytes / 100)); if (bytes < 1'000'000) return QObject::tr("%1 kB").arg(bytes / 1'000); + if (bytes < 10'000'000) + return QObject::tr("%1 MB").arg(0.01 * (bytes / 10'000)); + if (bytes < 100'000'000) + return QObject::tr("%1 MB").arg(0.1 * (bytes / 100'000)); if (bytes < 1'000'000'000) return QObject::tr("%1 MB").arg(bytes / 1'000'000); return QObject::tr("%1 GB").arg(bytes / 1'000'000'000); } +QString formatBytesps(float val) +{ + if (val < 10) + //: "Bytes per second" + return QObject::tr("%1 B/s").arg(0.01 * int(val * 100 + 0.5)); + if (val < 100) + //: "Bytes per second" + return QObject::tr("%1 B/s").arg(0.1 * int(val * 10 + 0.5)); + if (val < 1'000) + //: "Bytes per second" + return QObject::tr("%1 B/s").arg(int(val + 0.5)); + if (val < 10'000) + //: "Kilobytes per second" + return QObject::tr("%1 kB/s").arg(0.01 * int(val / 10 + 0.5)); + if (val < 100'000) + //: "Kilobytes per second" + return QObject::tr("%1 kB/s").arg(0.1 * int(val / 100 + 0.5)); + if (val < 1'000'000) + //: "Kilobytes per second" + return QObject::tr("%1 kB/s").arg(int(val / 1'000 + 0.5)); + if (val < 10'000'000) + //: "Megabytes per second" + return QObject::tr("%1 MB/s").arg(0.01 * int(val / 10'000 + 0.5)); + if (val < 100'000'000) + //: "Megabytes per second" + return QObject::tr("%1 MB/s").arg(0.1 * int(val / 100'000 + 0.5)); + if (val < 10'000'000'000) + //: "Megabytes per second" + return QObject::tr("%1 MB/s").arg(long(val / 1'000'000 + 0.5)); + + //: "Gigabytes per second" + return QObject::tr("%1 GB/s").arg(long(val / 1'000'000'000 + 0.5)); +} + qreal calculateIdealFontSize(int width, const QString& text, QFont font, qreal minPointSize, qreal font_size) { while(font_size >= minPointSize) { font.setPointSizeF(font_size); diff --git a/src/qt/guiutil.h b/src/qt/guiutil.h index 0224b18b4ee..3e015f8085c 100644 --- a/src/qt/guiutil.h +++ b/src/qt/guiutil.h @@ -235,6 +235,7 @@ namespace GUIUtil QString formatNiceTimeOffset(qint64 secs); QString formatBytes(uint64_t bytes); + QString formatBytesps(float bytes); qreal calculateIdealFontSize(int width, const QString& text, QFont font, qreal minPointSize = 4, qreal startPointSize = 14); diff --git a/src/qt/rpcconsole.cpp b/src/qt/rpcconsole.cpp index c5e5e69df6a..b8b0c688128 100644 --- a/src/qt/rpcconsole.cpp +++ b/src/qt/rpcconsole.cpp @@ -57,7 +57,6 @@ #include const int CONSOLE_HISTORY = 50; -const int INITIAL_TRAFFIC_GRAPH_MINS = 30; const QSize FONT_RANGE(4, 40); const char fontSizeSettingsKey[] = "consoleFontSize"; @@ -560,7 +559,6 @@ RPCConsole::RPCConsole(interfaces::Node& node, const PlatformStyle *_platformSty connect(ui->clearButton, &QAbstractButton::clicked, [this] { clear(); }); connect(ui->fontBiggerButton, &QAbstractButton::clicked, this, &RPCConsole::fontBigger); connect(ui->fontSmallerButton, &QAbstractButton::clicked, this, &RPCConsole::fontSmaller); - connect(ui->btnClearTrafficGraph, &QPushButton::clicked, ui->trafficGraph, &TrafficGraphWidget::clear); // disable the wallet selector by default ui->WalletSelector->setVisible(false); @@ -572,7 +570,7 @@ RPCConsole::RPCConsole(interfaces::Node& node, const PlatformStyle *_platformSty // based timer interface m_node.rpcSetTimerInterfaceIfUnset(rpcTimerInterface); - setTrafficGraphRange(INITIAL_TRAFFIC_GRAPH_MINS); + setTrafficGraphRange(1); // 1 is the lowest setting (0 bumps up) updateDetailWidget(); consoleFontSize = settings.value(fontSizeSettingsKey, QFont().pointSize()).toInt(); @@ -1133,21 +1131,63 @@ void RPCConsole::scrollToEnd() scrollbar->setValue(scrollbar->maximum()); } -void RPCConsole::on_sldGraphRange_valueChanged(int value) +void RPCConsole::on_sldGraphRange_valueChanged(int slider_value) { - const int multiplier = 5; // each position on the slider represents 5 min - int mins = value * multiplier; - setTrafficGraphRange(mins); + static int64_t last_click_time = 0; + static bool last_click_was_up = false; + unsigned int value = (slider_value + 100) / 200 + 1; // minimum of 1, 0 reserve for scale bump + if (!slider_in_use) { + // Avoid accidental boucing of direction + int64_t now = GetTimeMillis(); + bool this_click_is_up = false; + bool bouncing = false; + if (slider_value > set_slider_value) + this_click_is_up = true; + if (now - last_click_time < 250 && this_click_is_up != last_click_was_up) { + bouncing = true; + ui->sldGraphRange->blockSignals(true); + ui->sldGraphRange->setValue(set_slider_value); + ui->sldGraphRange->blockSignals(false); + } + last_click_time = now; + last_click_was_up = this_click_is_up; + set_slider_value = slider_value; + if (bouncing) return; + } + set_slider_value = slider_value; + setTrafficGraphRange(value); +} + +void RPCConsole::setTrafficGraphRange(unsigned int value) +{ + std::chrono::minutes mins = ui->trafficGraph->setGraphRange(value); + if (value) + set_slider_value = (value - 1) * 200; + else { + set_slider_value += 200; + ui->sldGraphRange->blockSignals(true); + ui->sldGraphRange->setValue(set_slider_value); + ui->sldGraphRange->blockSignals(false); + } + ui->lblGraphRange->setText(GUIUtil::formatDurationStr(mins)); } -void RPCConsole::setTrafficGraphRange(int mins) +void RPCConsole::on_sldGraphRange_sliderReleased() { - ui->trafficGraph->setGraphRange(std::chrono::minutes{mins}); - ui->lblGraphRange->setText(GUIUtil::formatDurationStr(std::chrono::minutes{mins})); + ui->sldGraphRange->setValue(set_slider_value); // Snap the slider to where this value is + slider_in_use = false; +} + +void RPCConsole::on_sldGraphRange_sliderPressed() +{ + slider_in_use = true; } void RPCConsole::updateTrafficStats(quint64 totalBytesIn, quint64 totalBytesOut) { + if (!slider_in_use && ui->trafficGraph->GraphRangeBump()) { + setTrafficGraphRange(0); // bump it up + } ui->lblBytesIn->setText(GUIUtil::formatBytes(totalBytesIn)); ui->lblBytesOut->setText(GUIUtil::formatBytes(totalBytesOut)); } diff --git a/src/qt/rpcconsole.h b/src/qt/rpcconsole.h index 528e2bef7d5..ffba9490165 100644 --- a/src/qt/rpcconsole.h +++ b/src/qt/rpcconsole.h @@ -84,6 +84,8 @@ private Q_SLOTS: void on_openDebugLogfileButton_clicked(); /** change the time range of the network traffic graph */ void on_sldGraphRange_valueChanged(int value); + void on_sldGraphRange_sliderReleased(); + void on_sldGraphRange_sliderPressed(); /** update traffic statistics */ void updateTrafficStats(quint64 totalBytesIn, quint64 totalBytesOut); void resizeEvent(QResizeEvent *event) override; @@ -140,7 +142,7 @@ public Q_SLOTS: } const ts; void startExecutor(); - void setTrafficGraphRange(int mins); + void setTrafficGraphRange(unsigned int value); enum ColumnWidths { @@ -170,6 +172,8 @@ public Q_SLOTS: bool m_is_executing{false}; QByteArray m_peer_widget_header_state; QByteArray m_banlist_widget_header_state; + bool slider_in_use{false}; + int set_slider_value{0}; /** Update UI with latest network info from model. */ void updateNetworkState(); diff --git a/src/qt/trafficgraphwidget.cpp b/src/qt/trafficgraphwidget.cpp index aebd44d5f7c..0af4a2a92d4 100644 --- a/src/qt/trafficgraphwidget.cpp +++ b/src/qt/trafficgraphwidget.cpp @@ -5,11 +5,14 @@ #include #include #include +#include #include #include #include #include +#include +#include #include #include @@ -22,44 +25,107 @@ TrafficGraphWidget::TrafficGraphWidget(QWidget *parent) : QWidget(parent), timer(nullptr), - fMax(0.0f), vSamplesIn(), vSamplesOut(), - nLastBytesIn(0), - nLastBytesOut(0), + vTimeStamp(), + nLastBytesIn(), + nLastBytesOut(), + nLastTime(), clientModel(nullptr) { timer = new QTimer(this); - connect(timer, &QTimer::timeout, this, &TrafficGraphWidget::updateRates); + connect(timer, &QTimer::timeout, this, &TrafficGraphWidget::updateStuff); + timer->setInterval(75); + timer->start(); + setMouseTracking(true); } void TrafficGraphWidget::setClientModel(ClientModel *model) { clientModel = model; + int64_t nTime = GetTimeMillis(); if(model) { - nLastBytesIn = model->node().getTotalBytesRecv(); - nLastBytesOut = model->node().getTotalBytesSent(); + for (int i = 0; i < VALUES_SIZE; i++) { + nLastBytesIn[i] = model->node().getTotalBytesRecv(); + nLastBytesOut[i] = model->node().getTotalBytesSent(); + nLastTime[i] = std::chrono::milliseconds{nTime}; + vSamplesIn[i].push_front(nLastBytesIn[i]); + vSamplesOut[i].push_front(nLastBytesOut[i]); + vTimeStamp[i].push_front(nLastTime[i]); + } } } -std::chrono::minutes TrafficGraphWidget::getGraphRange() const { return m_range; } +bool TrafficGraphWidget::GraphRangeBump() const { return m_bump_value; } + +int TrafficGraphWidget::y_value(float value) +{ + int h = height() - YMARGIN * 2; + return YMARGIN + h - (h * 1.0 * (fToggle ? (pow(value, 0.30102) / pow(fMax, 0.30102)) : (value / fMax))); +} void TrafficGraphWidget::paintPath(QPainterPath &path, QQueue &samples) { - int sampleCount = samples.size(); + int sampleCount = std::min(int(DESIRED_SAMPLES * m_range / values[m_value]), int(samples.size())); if(sampleCount > 0) { int h = height() - YMARGIN * 2, w = width() - XMARGIN * 2; int x = XMARGIN + w; path.moveTo(x, YMARGIN + h); for(int i = 0; i < sampleCount; ++i) { - x = XMARGIN + w - w * i / DESIRED_SAMPLES; - int y = YMARGIN + h - (int)(h * samples.at(i) / fMax); + x = XMARGIN + w - w * i * values[m_value] / m_range / DESIRED_SAMPLES; + int y = y_value(samples.at(i)); path.lineTo(x, y); } path.lineTo(x, YMARGIN + h); } } +float floatmax(float a, float b) +{ + if (a > b) return a; + else return b; +} + +void TrafficGraphWidget::mouseMoveEvent(QMouseEvent *event) +{ + QWidget::mouseMoveEvent(event); + if (fMax <= 0.0f) return; + static int last_x = -1; + static int last_y = -1; + int x = event->x(); + int y = event->y(); + x_offset = event->globalX() - x; + y_offset = event->globalY() - y; + if (last_x == x && last_y == y) return; // Do nothing if mouse hasn't moved + int h = height() - YMARGIN * 2, w = width() - XMARGIN * 2; + int i = (w + XMARGIN - x) * DESIRED_SAMPLES / w; + int sampleSize = vTimeStamp[m_value].size(); + unsigned int smallest_distance = 50; int closest_i = (i >= 0 && i < sampleSize) ? i : -1; + if (sampleSize && i >= -10 && i < sampleSize + 2 && y <= h + YMARGIN + 3) { + for (int test_i = std::max(i - 2, 0); test_i < std::min(i + 10, sampleSize); test_i++) { + float val = floatmax(vSamplesIn[m_value].at(test_i), vSamplesOut[m_value].at(test_i)); + int y_data = y_value(val); + unsigned int distance = abs(y - y_data); + if (distance < smallest_distance) { + smallest_distance = distance; + closest_i = test_i; + } + } + } + if (ttpoint != closest_i) { + ttpoint = closest_i; + update(); // Calls paintEvent() to draw or delete the highlighted point + } + last_x = x; last_y = y; +} + +void TrafficGraphWidget::mousePressEvent(QMouseEvent *event) +{ + QWidget::mousePressEvent(event); + fToggle = !fToggle; + update(); +} + void TrafficGraphWidget::paintEvent(QPaintEvent *) { QPainter painter(this); @@ -76,101 +142,227 @@ void TrafficGraphWidget::paintEvent(QPaintEvent *) 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(); + // if we drew 10 or 3 fewer lines, break them up at the next lower order of magnitude + if(fMax / val <= (fToggle ? 10.0f : 3.0f)) { + float oldval = val; 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)); + painter.setPen(axisCol.darker()); + painter.drawText(XMARGIN, y_value(val)-yMarginText, GUIUtil::formatBytesps(val*1000)); int count = 1; - for(float y = val; y < fMax; y += val, count++) { - // don't overwrite lines drawn above + for(float y = val; y < (!fToggle || fMax / val < 20 ? fMax : oldval); y += val, count++) { if(count % 10 == 0) continue; - int yy = YMARGIN + h - h * y / fMax; + int yy = y_value(y); painter.drawLine(XMARGIN, yy, width() - XMARGIN, yy); } + if (fToggle) { + int yy = y_value(val*0.1); + painter.setPen(axisCol.darker().darker()); + painter.drawText(XMARGIN, yy-yMarginText, GUIUtil::formatBytesps(val*100)); + painter.drawLine(XMARGIN, yy, width() - XMARGIN, yy); + } + val = oldval; + } + // draw lines + painter.setPen(axisCol); + for(float y = val; y < fMax; y += val) { + int yy = y_value(y); + painter.drawLine(XMARGIN, yy, width() - XMARGIN, yy); } + painter.drawText(XMARGIN, y_value(val)-yMarginText, GUIUtil::formatBytesps(val*1000)); painter.setRenderHint(QPainter::Antialiasing); - if(!vSamplesIn.empty()) { + if(!vSamplesIn[m_value].empty()) { QPainterPath p; - paintPath(p, vSamplesIn); + paintPath(p, vSamplesIn[m_value]); painter.fillPath(p, QColor(0, 255, 0, 128)); painter.setPen(Qt::green); painter.drawPath(p); } - if(!vSamplesOut.empty()) { + if(!vSamplesOut[m_value].empty()) { QPainterPath p; - paintPath(p, vSamplesOut); + paintPath(p, vSamplesOut[m_value]); painter.fillPath(p, QColor(255, 0, 0, 128)); painter.setPen(Qt::red); painter.drawPath(p); } + int sampleCount = vTimeStamp[m_value].size(); + if (ttpoint >= 0 && ttpoint < sampleCount) { + painter.setPen(Qt::yellow); + int w = width() - XMARGIN * 2; + int x = XMARGIN + w - w * ttpoint / DESIRED_SAMPLES; + int y = y_value(floatmax(vSamplesIn[m_value].at(ttpoint), vSamplesOut[m_value].at(ttpoint))); + painter.drawEllipse(QPointF(x, y), 3, 3); + QString strTime; + std::chrono::milliseconds sampleTime{0}; + if (ttpoint + 1 < sampleCount) + sampleTime = vTimeStamp[m_value].at(ttpoint+1); + else + sampleTime = vTimeStamp[m_value].at(ttpoint); + int age = GetTime() - sampleTime.count() / 1000; + if (age < 60*60*23) + strTime += QString::fromStdString(FormatISO8601Time(sampleTime.count() / 1000)); + else + strTime += QString::fromStdString(FormatISO8601DateTime(sampleTime.count() / 1000)); + int nDuration = (vTimeStamp[m_value].at(ttpoint) - sampleTime).count(); + if (nDuration > 0) { + if (nDuration > 9999) + strTime += " +" + GUIUtil::formatDurationStr(std::chrono::seconds{nDuration/1000}); + else + strTime += " +" + GUIUtil::formatPingTime(std::chrono::microseconds{nDuration*1000}); + } + QString strData = tr("In") + " " + GUIUtil::formatBytesps(vSamplesIn[m_value].at(ttpoint)*1000) + "\n" + tr("Out") + " " + GUIUtil::formatBytesps(vSamplesOut[m_value].at(ttpoint)*1000); + // Line below allows ToolTip to move faster than the default ToolTip timeout (10 seconds). + QToolTip::showText(QPoint(x + x_offset, y + y_offset), strTime + "\n. " + strData); + QToolTip::showText(QPoint(x + x_offset, y + y_offset), strTime + "\n " + strData); + tt_time = GetTime(); + } else + QToolTip::hideText(); } -void TrafficGraphWidget::updateRates() +void TrafficGraphWidget::update_fMax() { - if(!clientModel) return; - - quint64 bytesIn = clientModel->node().getTotalBytesRecv(), - bytesOut = clientModel->node().getTotalBytesSent(); - float in_rate_kilobytes_per_sec = static_cast(bytesIn - nLastBytesIn) / timer->interval(); - float out_rate_kilobytes_per_sec = static_cast(bytesOut - nLastBytesOut) / timer->interval(); - vSamplesIn.push_front(in_rate_kilobytes_per_sec); - vSamplesOut.push_front(out_rate_kilobytes_per_sec); - nLastBytesIn = bytesIn; - nLastBytesOut = bytesOut; - - while(vSamplesIn.size() > DESIRED_SAMPLES) { - vSamplesIn.pop_back(); - } - while(vSamplesOut.size() > DESIRED_SAMPLES) { - vSamplesOut.pop_back(); - } - float tmax = 0.0f; - for (const float f : vSamplesIn) { + for (const float f : vSamplesIn[m_new_value]) { if(f > tmax) tmax = f; } - for (const float f : vSamplesOut) { + for (const float f : vSamplesOut[m_new_value]) { if(f > tmax) tmax = f; } - fMax = tmax; - update(); + new_fMax = tmax; } -void TrafficGraphWidget::setGraphRange(std::chrono::minutes new_range) +bool update_num(float new_val, float ¤t, float &increment, int length) { - m_range = new_range; - const auto msecs_per_sample{std::chrono::duration_cast(m_range) / DESIRED_SAMPLES}; - timer->stop(); - timer->setInterval(msecs_per_sample); + if (new_val == 0 || current == new_val) + return false; - clear(); + if (abs(increment) <= abs(0.8 * current) / length) { // allow equal to as current and increment could be zero + if (new_val > current) + increment = 1.0 * (current+1) / length; // +1s are to get it started even if current is zero + else + increment = -1.0 * (current+1) / length; + if (abs(increment) > abs(new_val - current)) // Only check this when creating an increment + increment = 0; // Nothing to do! + } else { + if (((increment > 0) && (current + increment * 2 > new_val)) || + ((increment < 0) && (current + increment * 2 < new_val))) { + increment = increment / 2; // Keep the momentum going even if new_val is elsewhere. + } else { + if (((increment > 0) && (current + increment * 4 < new_val)) || + ((increment < 0) && (current + increment * 4 > new_val))) { + increment = increment * 2; + } + } + } + if (abs(increment) < 0.8 * current / length) { + if ((increment >= 0 && new_val > current) || (increment <= 0 && new_val < current)) + current = new_val; + increment = 0; + } else + current += increment; + + return true; } -void TrafficGraphWidget::clear() +void TrafficGraphWidget::updateStuff() { - timer->stop(); + if(!clientModel) return; - vSamplesOut.clear(); - vSamplesIn.clear(); - fMax = 0.0f; + static int nInterval{timer->interval()}; + int64_t nTime{GetTimeMillis()}; - if(clientModel) { - nLastBytesIn = clientModel->node().getTotalBytesRecv(); - nLastBytesOut = clientModel->node().getTotalBytesSent(); + bool fUpdate = false; + for (int i = 0; i < VALUES_SIZE; i++) { + int64_t msecs_per_sample = int64_t(values[i]) * int64_t(60000) / DESIRED_SAMPLES; + if (nTime > (nLastTime[i].count() + msecs_per_sample - nInterval/2)) { // REBTODO - fix bad timing + updateRates(i); + if (i == m_value) { + if (ttpoint >= 0 && ttpoint < DESIRED_SAMPLES) { + ttpoint++; // Move the selected point to the left + if (ttpoint >= DESIRED_SAMPLES) ttpoint = -1; + } + fUpdate = true; + } + if (i == m_new_value) + update_fMax(); + } } - timer->start(); + + static float y_increment = 0; + static float x_increment = 0; + if (update_num(new_fMax, fMax, y_increment, height() - YMARGIN * 2)) + fUpdate = true; + if (update_num(values[m_new_value], m_range, x_increment, width() - XMARGIN * 2)) { + if (values[m_new_value] > m_range && values[m_value] < m_range * 0.99) { + m_value++; + } else if (m_value > 0 && values[m_new_value] <= m_range && values[m_value-1] > m_range * 0.99) + m_value--; + fUpdate = true; + } else if (m_value != m_new_value) { + fUpdate = true; + m_value = m_new_value; + } + + static bool last_fToggle = fToggle; + if (!QToolTip::isVisible()) { + if (ttpoint >= 0) { // Remove the yellow circle if the ToolTip has gone due to mouse moving elsewhere. + if (last_fToggle == fToggle) { // Not lost due to a toggle + ttpoint = -1; + } else + last_fToggle = fToggle; + fUpdate = true; + } + } else if (ttpoint >= 0 && GetTime() >= tt_time + 9) // ToolTip is about to expire so refresh it. + fUpdate = true; // TODO - technically it's only the ToolTip that needs to be refreshed + + if (fUpdate) + update(); +} + +void TrafficGraphWidget::updateRates(int i) +{ + std::chrono::milliseconds nTime{GetTimeMillis()}; + quint64 bytesIn = clientModel->node().getTotalBytesRecv(), + bytesOut = clientModel->node().getTotalBytesSent(); + int nRealInterval = (nTime - nLastTime[i]).count(); + float in_rate_kilobytes_per_sec = static_cast(bytesIn - nLastBytesIn[i]) / nRealInterval; + float out_rate_kilobytes_per_sec = static_cast(bytesOut - nLastBytesOut[i]) / nRealInterval; + vSamplesIn[i].push_front(in_rate_kilobytes_per_sec); + vSamplesOut[i].push_front(out_rate_kilobytes_per_sec); + vTimeStamp[i].push_front(nTime); + nLastTime[i] = nTime; + nLastBytesIn[i] = bytesIn; + nLastBytesOut[i] = bytesOut; + static bool fFull[VALUES_SIZE]; + while(vTimeStamp[i].size() > DESIRED_SAMPLES) { + if (ttpoint < 0 && m_value == i && i < VALUES_SIZE - 1 && !fFull[i]) + m_bump_value = true; + + fFull[i] = true; + + vSamplesIn[i].pop_back(); + vSamplesOut[i].pop_back(); + vTimeStamp[i].pop_back(); + } +} + +std::chrono::minutes TrafficGraphWidget::setGraphRange(unsigned int value) +{ + // value is the array marker plus 1 (as zero is reserved for bumping up) + if (!value) { // bump + m_bump_value = false; + value = m_value + 1; + } else + value--; // get the array marker + int old_value = m_new_value; + m_new_value = std::min((int)value, VALUES_SIZE - 1); + if (m_new_value != old_value) { + update_fMax(); + update(); + } + + return std::chrono::minutes{values[m_new_value]}; } diff --git a/src/qt/trafficgraphwidget.h b/src/qt/trafficgraphwidget.h index a40b7345403..b3ea9bc4d48 100644 --- a/src/qt/trafficgraphwidget.h +++ b/src/qt/trafficgraphwidget.h @@ -17,6 +17,8 @@ class QPaintEvent; class QTimer; QT_END_NAMESPACE +#define VALUES_SIZE 12 + class TrafficGraphWidget : public QWidget { Q_OBJECT @@ -24,26 +26,43 @@ class TrafficGraphWidget : public QWidget public: explicit TrafficGraphWidget(QWidget *parent = nullptr); void setClientModel(ClientModel *model); - std::chrono::minutes getGraphRange() const; + bool GraphRangeBump() const; protected: void paintEvent(QPaintEvent *) override; + int y_value(float value); + void mouseMoveEvent(QMouseEvent *event) override; + int ttpoint = -1; + int x_offset = 0; + int y_offset = 0; + int64_t tt_time = 0; + void mousePressEvent(QMouseEvent *event) override; + bool fToggle = true; public Q_SLOTS: - void updateRates(); - void setGraphRange(std::chrono::minutes new_range); - void clear(); + void updateStuff(); + std::chrono::minutes setGraphRange(unsigned int value); private: + void update_fMax(); void paintPath(QPainterPath &path, QQueue &samples); + void updateRates(int value); QTimer *timer; - float fMax; - std::chrono::minutes m_range{0}; - QQueue vSamplesIn; - QQueue vSamplesOut; - quint64 nLastBytesIn; - quint64 nLastBytesOut; + float fMax{0}; + float new_fMax{0}; + float m_range{0}; + int m_value{0}; + int m_new_value{0}; + bool m_bump_value{false}; + QQueue vSamplesIn[VALUES_SIZE]; + QQueue vSamplesOut[VALUES_SIZE]; + QQueue vTimeStamp[VALUES_SIZE]; + quint64 nLastBytesIn[VALUES_SIZE]; + quint64 nLastBytesOut[VALUES_SIZE]; + std::chrono::milliseconds nLastTime[VALUES_SIZE]; + unsigned int values[VALUES_SIZE] = {5, 10, 20, 45, 90, 3*60, 6*60, 12*60, 24*60, 3*24*60, 7*24*60, 28*24*60}; + //unsigned int values[VALUES_SIZE] = {5, 15, 60, 3*60, 12*60, 2*24*60, 7*24*60, 28*24*60}; ClientModel *clientModel; }; diff --git a/src/util/time.cpp b/src/util/time.cpp index f7712f0dc82..5b7aea848e4 100644 --- a/src/util/time.cpp +++ b/src/util/time.cpp @@ -155,6 +155,17 @@ std::string FormatISO8601Date(int64_t nTime) { return strprintf("%04i-%02i-%02i", ts.tm_year + 1900, ts.tm_mon + 1, ts.tm_mday); } +std::string FormatISO8601Time(int64_t nTime) { + struct tm ts; + time_t time_val = nTime; +#ifdef HAVE_GMTIME_R + gmtime_r(&time_val, &ts); +#else + gmtime_s(&ts, &time_val); +#endif + return strprintf("%02i:%02i:%02iZ", ts.tm_hour, ts.tm_min, ts.tm_sec); +} + int64_t ParseISO8601DateTime(const std::string& str) { static const boost::posix_time::ptime epoch = boost::posix_time::from_time_t(0); diff --git a/src/util/time.h b/src/util/time.h index 9d92b237250..be244138bb4 100644 --- a/src/util/time.h +++ b/src/util/time.h @@ -74,6 +74,7 @@ T GetTime(); */ std::string FormatISO8601DateTime(int64_t nTime); std::string FormatISO8601Date(int64_t nTime); +std::string FormatISO8601Time(int64_t nTime); int64_t ParseISO8601DateTime(const std::string& str); /**