mirror of
https://github.com/Retropex/bitcoin.git
synced 2025-06-04 00:12:33 +02:00
Merge 8550 via old_stats_qt-25+knots
This commit is contained in:
commit
f6b1eae17b
@ -20,6 +20,7 @@ QT_FORMS_UI = \
|
||||
qt/forms/createwalletdialog.ui \
|
||||
qt/forms/editaddressdialog.ui \
|
||||
qt/forms/helpmessagedialog.ui \
|
||||
qt/forms/mempoolstats.ui \
|
||||
qt/forms/intro.ui \
|
||||
qt/forms/modaloverlay.ui \
|
||||
qt/forms/openuridialog.ui \
|
||||
@ -56,6 +57,7 @@ QT_MOC_CPP = \
|
||||
qt/moc_macdockiconhandler.cpp \
|
||||
qt/moc_macnotificationhandler.cpp \
|
||||
qt/moc_modaloverlay.cpp \
|
||||
qt/moc_mempoolstats.cpp \
|
||||
qt/moc_notificator.cpp \
|
||||
qt/moc_openuridialog.cpp \
|
||||
qt/moc_optionsdialog.cpp \
|
||||
@ -129,6 +131,7 @@ BITCOIN_QT_H = \
|
||||
qt/macnotificationhandler.h \
|
||||
qt/macos_appnap.h \
|
||||
qt/modaloverlay.h \
|
||||
qt/mempoolstats.h \
|
||||
qt/networkstyle.h \
|
||||
qt/notificator.h \
|
||||
qt/openuridialog.h \
|
||||
@ -232,6 +235,7 @@ BITCOIN_QT_BASE_CPP = \
|
||||
qt/initexecutor.cpp \
|
||||
qt/intro.cpp \
|
||||
qt/modaloverlay.cpp \
|
||||
qt/mempoolstats.cpp \
|
||||
qt/networkstyle.cpp \
|
||||
qt/notificator.cpp \
|
||||
qt/optionsdialog.cpp \
|
||||
|
@ -30,6 +30,7 @@
|
||||
#include <qt/splashscreen.h>
|
||||
#include <qt/utilitydialog.h>
|
||||
#include <qt/winshutdownmonitor.h>
|
||||
#include <stats/stats.h>
|
||||
#include <uint256.h>
|
||||
#include <util/exception.h>
|
||||
#include <util/string.h>
|
||||
@ -639,6 +640,9 @@ int GuiMain(int argc, char* argv[])
|
||||
app.parameterSetup();
|
||||
GUIUtil::LogQtInfo();
|
||||
|
||||
// Enable mempool stats by default
|
||||
gArgs.SoftSetBoolArg("-statsenable", true);
|
||||
|
||||
if (gArgs.GetBoolArg("-splash", DEFAULT_SPLASHSCREEN) && !gArgs.GetBoolArg("-min", false))
|
||||
app.createSplashScreen(networkStyle.data());
|
||||
|
||||
|
@ -9,6 +9,7 @@
|
||||
#include <qt/createwalletdialog.h>
|
||||
#include <qt/guiconstants.h>
|
||||
#include <qt/guiutil.h>
|
||||
#include <qt/mempoolstats.h>
|
||||
#include <qt/modaloverlay.h>
|
||||
#include <qt/networkstyle.h>
|
||||
#include <qt/notificator.h>
|
||||
@ -328,6 +329,11 @@ void BitcoinGUI::createActions()
|
||||
openRPCConsoleAction->setEnabled(false);
|
||||
openRPCConsoleAction->setObjectName("openRPCConsoleAction");
|
||||
|
||||
showMempoolStatsAction = new QAction(tr("&Mempool Statistics"), this);
|
||||
showMempoolStatsAction->setStatusTip(tr("Mempool Statistics"));
|
||||
// initially disable the mempool stats menu item
|
||||
showMempoolStatsAction->setEnabled(false);
|
||||
|
||||
usedSendingAddressesAction = new QAction(tr("&Sending addresses"), this);
|
||||
usedSendingAddressesAction->setStatusTip(tr("Show the list of used sending addresses and labels"));
|
||||
usedReceivingAddressesAction = new QAction(tr("&Receiving addresses"), this);
|
||||
@ -372,6 +378,8 @@ void BitcoinGUI::createActions()
|
||||
connect(optionsAction, &QAction::triggered, this, &BitcoinGUI::optionsClicked);
|
||||
connect(showHelpMessageAction, &QAction::triggered, this, &BitcoinGUI::showHelpMessageClicked);
|
||||
connect(openRPCConsoleAction, &QAction::triggered, this, &BitcoinGUI::showDebugWindow);
|
||||
connect(showMempoolStatsAction, &QAction::triggered, this, &BitcoinGUI::showMempoolStatsWindow);
|
||||
|
||||
// prevents an open debug window from becoming stuck/unusable on client shutdown
|
||||
connect(quitAction, &QAction::triggered, rpcConsole, &QWidget::hide);
|
||||
|
||||
@ -542,6 +550,9 @@ void BitcoinGUI::createMenuBar()
|
||||
window_menu->addAction(usedReceivingAddressesAction);
|
||||
}
|
||||
|
||||
window_menu->addSeparator();
|
||||
window_menu->addAction(showMempoolStatsAction);
|
||||
|
||||
window_menu->addSeparator();
|
||||
for (RPCConsole::TabTypes tab_type : rpcConsole->tabs()) {
|
||||
QAction* tab_action = window_menu->addAction(rpcConsole->tabTitle(tab_type));
|
||||
@ -839,6 +850,7 @@ void BitcoinGUI::createTrayIconMenu()
|
||||
QAction* options_action = trayIconMenu->addAction(optionsAction->text(), optionsAction, &QAction::trigger);
|
||||
options_action->setMenuRole(QAction::PreferencesRole);
|
||||
QAction* node_window_action = trayIconMenu->addAction(openRPCConsoleAction->text(), openRPCConsoleAction, &QAction::trigger);
|
||||
QAction* mempoolstats_action = trayIconMenu->addAction(showMempoolStatsAction->text(), showMempoolStatsAction, &QAction::trigger);
|
||||
QAction* quit_action{nullptr};
|
||||
#ifndef Q_OS_MACOS
|
||||
// Note: On macOS, the Dock icon's menu already has Quit action.
|
||||
@ -867,7 +879,7 @@ void BitcoinGUI::createTrayIconMenu()
|
||||
// Using QSystemTrayIcon::Context is not reliable.
|
||||
// See https://bugreports.qt.io/browse/QTBUG-91697
|
||||
trayIconMenu.get(), &QMenu::aboutToShow,
|
||||
[this, show_hide_action, send_action, receive_action, sign_action, verify_action, options_action, node_window_action, quit_action] {
|
||||
[this, show_hide_action, send_action, receive_action, sign_action, verify_action, options_action, node_window_action, mempoolstats_action, quit_action] {
|
||||
if (m_node.shutdownRequested()) return; // nothing to do, node is shutting down.
|
||||
|
||||
if (show_hide_action) show_hide_action->setText(
|
||||
@ -888,6 +900,7 @@ void BitcoinGUI::createTrayIconMenu()
|
||||
}
|
||||
options_action->setEnabled(optionsAction->isEnabled());
|
||||
node_window_action->setEnabled(openRPCConsoleAction->isEnabled());
|
||||
mempoolstats_action->setEnabled(showMempoolStatsAction->isEnabled());
|
||||
if (quit_action) quit_action->setEnabled(true);
|
||||
}
|
||||
});
|
||||
@ -924,6 +937,19 @@ void BitcoinGUI::showHelpMessageClicked()
|
||||
GUIUtil::bringToFront(helpMessageDialog);
|
||||
}
|
||||
|
||||
void BitcoinGUI::showMempoolStatsWindow()
|
||||
{
|
||||
// only build the mempool stats window if its requested
|
||||
if (!mempoolStats)
|
||||
mempoolStats = new MempoolStats(this);
|
||||
if (clientModel)
|
||||
mempoolStats->setClientModel(clientModel);
|
||||
mempoolStats->showNormal();
|
||||
mempoolStats->show();
|
||||
mempoolStats->raise();
|
||||
mempoolStats->activateWindow();
|
||||
}
|
||||
|
||||
#ifdef ENABLE_WALLET
|
||||
void BitcoinGUI::openClicked()
|
||||
{
|
||||
@ -1297,6 +1323,7 @@ void BitcoinGUI::showEvent(QShowEvent *event)
|
||||
{
|
||||
// enable the debug window when the main window shows up
|
||||
openRPCConsoleAction->setEnabled(true);
|
||||
showMempoolStatsAction->setEnabled(true);
|
||||
aboutAction->setEnabled(true);
|
||||
optionsAction->setEnabled(true);
|
||||
}
|
||||
|
@ -41,6 +41,7 @@ class WalletFrame;
|
||||
class WalletModel;
|
||||
class HelpMessageDialog;
|
||||
class ModalOverlay;
|
||||
class MempoolStats;
|
||||
enum class SynchronizationState;
|
||||
|
||||
namespace interfaces {
|
||||
@ -154,6 +155,7 @@ private:
|
||||
QAction* openRPCConsoleAction = nullptr;
|
||||
QAction* openAction = nullptr;
|
||||
QAction* showHelpMessageAction = nullptr;
|
||||
QAction* showMempoolStatsAction = nullptr;
|
||||
QAction* m_create_wallet_action{nullptr};
|
||||
QAction* m_open_wallet_action{nullptr};
|
||||
QMenu* m_open_wallet_menu{nullptr};
|
||||
@ -173,6 +175,7 @@ private:
|
||||
RPCConsole* rpcConsole = nullptr;
|
||||
HelpMessageDialog* helpMessageDialog = nullptr;
|
||||
ModalOverlay* modalOverlay = nullptr;
|
||||
MempoolStats* mempoolStats = nullptr;
|
||||
|
||||
QMenu* m_network_context_menu = new QMenu(this);
|
||||
|
||||
@ -304,6 +307,8 @@ public Q_SLOTS:
|
||||
void showDebugWindowActivateConsole();
|
||||
/** Show help message dialog */
|
||||
void showHelpMessageClicked();
|
||||
/** Show mempool stats window */
|
||||
void showMempoolStatsWindow();
|
||||
|
||||
/** Show window if hidden, unminimize when minimized, rise when obscured or show if hidden and fToggleHidden is true */
|
||||
void showNormalIfMinimized() { showNormalIfMinimized(false); }
|
||||
|
@ -231,6 +231,11 @@ void ClientModel::TipChanged(SynchronizationState sync_state, interfaces::BlockT
|
||||
nLastUpdateNotification = now;
|
||||
}
|
||||
|
||||
static void MempoolStatsDidChange(ClientModel *clientmodel)
|
||||
{
|
||||
QMetaObject::invokeMethod(clientmodel, "updateMempoolStats", Qt::QueuedConnection);
|
||||
}
|
||||
|
||||
void ClientModel::subscribeToCoreSignals()
|
||||
{
|
||||
m_handler_show_progress = m_node.handleShowProgress(
|
||||
@ -263,6 +268,8 @@ void ClientModel::subscribeToCoreSignals()
|
||||
[this](SynchronizationState sync_state, interfaces::BlockTip tip, bool presync) {
|
||||
TipChanged(sync_state, tip, /*verification_progress=*/0.0, presync ? SyncType::HEADER_PRESYNC : SyncType::HEADER_SYNC);
|
||||
});
|
||||
|
||||
m_connection_mempool_stats_did_change = CStats::DefaultStats()->MempoolStatsDidChange.connect(std::bind(MempoolStatsDidChange, this));
|
||||
}
|
||||
|
||||
void ClientModel::unsubscribeFromCoreSignals()
|
||||
@ -274,6 +281,8 @@ void ClientModel::unsubscribeFromCoreSignals()
|
||||
m_handler_banned_list_changed->disconnect();
|
||||
m_handler_notify_block_tip->disconnect();
|
||||
m_handler_notify_header_tip->disconnect();
|
||||
|
||||
m_connection_mempool_stats_did_change.disconnect();
|
||||
}
|
||||
|
||||
bool ClientModel::getProxyInfo(std::string& ip_port) const
|
||||
@ -285,3 +294,20 @@ bool ClientModel::getProxyInfo(std::string& ip_port) const
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
mempoolSamples_t ClientModel::getMempoolStatsInRange(QDateTime &from, QDateTime &to)
|
||||
{
|
||||
// get stats from the core stats model
|
||||
uint64_t timeFrom = from.toTime_t();
|
||||
uint64_t timeTo = to.toTime_t();
|
||||
|
||||
mempoolSamples_t samples = CStats::DefaultStats()->mempoolGetValuesInRange(timeFrom,timeTo);
|
||||
from.setTime_t(timeFrom);
|
||||
to.setTime_t(timeTo);
|
||||
return samples;
|
||||
}
|
||||
|
||||
void ClientModel::updateMempoolStats()
|
||||
{
|
||||
Q_EMIT mempoolStatsDidUpdate();
|
||||
}
|
||||
|
@ -10,6 +10,7 @@
|
||||
|
||||
#include <atomic>
|
||||
#include <memory>
|
||||
#include <stats/stats.h>
|
||||
#include <sync.h>
|
||||
#include <uint256.h>
|
||||
|
||||
@ -94,6 +95,8 @@ public:
|
||||
Mutex m_cached_tip_mutex;
|
||||
uint256 m_cached_tip_blocks GUARDED_BY(m_cached_tip_mutex){};
|
||||
|
||||
mempoolSamples_t getMempoolStatsInRange(QDateTime &from, QDateTime &to);
|
||||
|
||||
private:
|
||||
interfaces::Node& m_node;
|
||||
std::unique_ptr<interfaces::Handler> m_handler_show_progress;
|
||||
@ -103,6 +106,7 @@ private:
|
||||
std::unique_ptr<interfaces::Handler> m_handler_banned_list_changed;
|
||||
std::unique_ptr<interfaces::Handler> m_handler_notify_block_tip;
|
||||
std::unique_ptr<interfaces::Handler> m_handler_notify_header_tip;
|
||||
boost::signals2::scoped_connection m_connection_mempool_stats_did_change;
|
||||
OptionsModel *optionsModel;
|
||||
PeerTableModel* peerTableModel{nullptr};
|
||||
PeerTableSortProxy* m_peer_table_sort_proxy{nullptr};
|
||||
@ -128,6 +132,12 @@ Q_SIGNALS:
|
||||
|
||||
// Show progress dialog e.g. for verifychain
|
||||
void showProgress(const QString &title, int nProgress);
|
||||
|
||||
void mempoolStatsDidUpdate();
|
||||
|
||||
public Q_SLOTS:
|
||||
/* stats stack */
|
||||
void updateMempoolStats();
|
||||
};
|
||||
|
||||
#endif // BITCOIN_QT_CLIENTMODEL_H
|
||||
|
44
src/qt/forms/mempoolstats.ui
Normal file
44
src/qt/forms/mempoolstats.ui
Normal file
@ -0,0 +1,44 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>MempoolStats</class>
|
||||
<widget class="QWidget" name="MempoolStats">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>480</width>
|
||||
<height>380</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>480</width>
|
||||
<height>380</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Mempool Stats</string>
|
||||
</property>
|
||||
<widget class="QGraphicsView" name="graphicsView">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>256</width>
|
||||
<height>200</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="styleSheet">
|
||||
<string notr="true"/>
|
||||
</property>
|
||||
<property name="verticalScrollBarPolicy">
|
||||
<enum>Qt::ScrollBarAlwaysOff</enum>
|
||||
</property>
|
||||
<property name="horizontalScrollBarPolicy">
|
||||
<enum>Qt::ScrollBarAlwaysOff</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
416
src/qt/mempoolstats.cpp
Normal file
416
src/qt/mempoolstats.cpp
Normal file
@ -0,0 +1,416 @@
|
||||
// Copyright (c) 2016 The Bitcoin Core developers
|
||||
// Distributed under the MIT software license, see the accompanying
|
||||
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||
|
||||
#include <qt/mempoolstats.h>
|
||||
#include <qt/forms/ui_mempoolstats.h>
|
||||
|
||||
#include <qt/clientmodel.h>
|
||||
#include <qt/guiutil.h>
|
||||
#include <stats/stats.h>
|
||||
|
||||
#include <math.h>
|
||||
|
||||
static const char *LABEL_FONT = "Arial";
|
||||
static int LABEL_TITLE_SIZE = 22;
|
||||
static int LABEL_KV_SIZE = 12;
|
||||
|
||||
static const int TEN_MINS = 600;
|
||||
static const int ONE_HOUR = 3600;
|
||||
static const int ONE_DAY = ONE_HOUR*24;
|
||||
|
||||
static const int LABEL_LEFT_SIZE = 30;
|
||||
static const int LABEL_RIGHT_SIZE = 30;
|
||||
static const int GRAPH_PADDING_LEFT = 30+LABEL_LEFT_SIZE;
|
||||
static const int GRAPH_PADDING_RIGHT = 30+LABEL_RIGHT_SIZE;
|
||||
static const int GRAPH_PADDING_TOP = 10;
|
||||
static const int GRAPH_PADDING_TOP_LABEL = 150;
|
||||
static const int GRAPH_PADDING_BOTTOM = 50;
|
||||
static const int LABEL_HEIGHT = 15;
|
||||
|
||||
void ClickableTextItem::mousePressEvent(QGraphicsSceneMouseEvent *event)
|
||||
{
|
||||
Q_EMIT objectClicked(this);
|
||||
}
|
||||
|
||||
void ClickableTextItem::setEnabled(bool state)
|
||||
{
|
||||
if (state)
|
||||
setDefaultTextColor(QColor(15,68,113, 250));
|
||||
else
|
||||
setDefaultTextColor(QColor(100,100,100, 200));
|
||||
}
|
||||
|
||||
MempoolStats::MempoolStats(QWidget *parent) :
|
||||
QWidget(parent, Qt::Window),
|
||||
timeFilter(TEN_MINS),
|
||||
ui(new Ui::MempoolStats)
|
||||
{
|
||||
ui->setupUi(this);
|
||||
if (parent) {
|
||||
parent->installEventFilter(this);
|
||||
raise();
|
||||
}
|
||||
|
||||
// autoadjust font size
|
||||
QGraphicsTextItem testText("jY"); //screendesign expected 27.5 pixel in width for this string
|
||||
testText.setFont(QFont(LABEL_FONT, LABEL_TITLE_SIZE, QFont::Light));
|
||||
LABEL_TITLE_SIZE *= 27.5/testText.boundingRect().width();
|
||||
LABEL_KV_SIZE *= 27.5/testText.boundingRect().width();
|
||||
|
||||
scene = new QGraphicsScene();
|
||||
ui->graphicsView->setScene(scene);
|
||||
ui->graphicsView->setRenderHints(QPainter::Antialiasing | QPainter::SmoothPixmapTransform);
|
||||
|
||||
if (clientModel)
|
||||
drawChart();
|
||||
}
|
||||
|
||||
void MempoolStats::setClientModel(ClientModel *model)
|
||||
{
|
||||
clientModel = model;
|
||||
|
||||
if (model)
|
||||
connect(model, SIGNAL(mempoolStatsDidUpdate()), this, SLOT(drawChart()));
|
||||
}
|
||||
|
||||
void MempoolStats::drawChart()
|
||||
{
|
||||
if (!(isVisible() && clientModel))
|
||||
return;
|
||||
|
||||
if (!titleItem)
|
||||
{
|
||||
// create labels (only once)
|
||||
titleItem = scene->addText(tr("Mempool Statistics"));
|
||||
titleItem->setFont(QFont(LABEL_FONT, LABEL_TITLE_SIZE, QFont::Light));
|
||||
titleLine = scene->addLine(0,0,100,100);
|
||||
titleLine->setPen(QPen(QColor(100,100,100, 200), 2, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin));
|
||||
|
||||
cbShowMemUsage = new QCheckBox("Dynamic Memory Usage");
|
||||
cbShowMemUsage->setChecked(true);
|
||||
cbShowMemUsage->setStyleSheet("background-color: rgb(255,255,255);");
|
||||
dynMemUsageSwitch = scene->addWidget(cbShowMemUsage);
|
||||
connect(cbShowMemUsage, SIGNAL(stateChanged(int)), this, SLOT(drawChart()));
|
||||
cbShowMemUsage->setFont(QFont(LABEL_FONT, LABEL_KV_SIZE, QFont::Light));
|
||||
dynMemUsageValueItem = scene->addText("N/A");
|
||||
dynMemUsageValueItem->setFont(QFont(LABEL_FONT, LABEL_KV_SIZE, QFont::Bold));
|
||||
|
||||
cbShowNumTxns = new QCheckBox("Amount of Transactions");
|
||||
cbShowNumTxns->setChecked(true);
|
||||
cbShowNumTxns->setStyleSheet("background-color: rgb(255,255,255);");
|
||||
txCountSwitch = scene->addWidget(cbShowNumTxns);
|
||||
scene->addItem(txCountSwitch);
|
||||
connect(cbShowNumTxns, SIGNAL(stateChanged(int)), this, SLOT(drawChart()));
|
||||
cbShowNumTxns->setFont(QFont(LABEL_FONT, LABEL_KV_SIZE, QFont::Light));
|
||||
txCountValueItem = scene->addText("N/A");
|
||||
txCountValueItem->setFont(QFont(LABEL_FONT, LABEL_KV_SIZE, QFont::Bold));
|
||||
|
||||
cbShowMinFeerate = new QCheckBox("MinRelayFee per KB");
|
||||
cbShowMinFeerate->setChecked(false);
|
||||
cbShowMinFeerate->setStyleSheet("background-color: rgb(255,255,255);");
|
||||
minFeeSwitch = scene->addWidget(cbShowMinFeerate);
|
||||
scene->addItem(minFeeSwitch);
|
||||
connect(cbShowMinFeerate, SIGNAL(stateChanged(int)), this, SLOT(drawChart()));
|
||||
cbShowMinFeerate->setFont(QFont(LABEL_FONT, LABEL_KV_SIZE, QFont::Light));
|
||||
minFeeValueItem = scene->addText(tr("N/A"));
|
||||
minFeeValueItem->setFont(QFont(LABEL_FONT, LABEL_KV_SIZE, QFont::Bold));
|
||||
|
||||
noDataItem = scene->addText(tr("No Data available"));
|
||||
noDataItem->setFont(QFont(LABEL_FONT, LABEL_TITLE_SIZE, QFont::Light));
|
||||
noDataItem->setDefaultTextColor(QColor(100,100,100, 200));
|
||||
|
||||
last10MinLabel = new ClickableTextItem(); last10MinLabel->setPlainText(tr("Last 10 min"));
|
||||
scene->addItem(last10MinLabel);
|
||||
connect(last10MinLabel, SIGNAL(objectClicked(QGraphicsItem*)), this, SLOT(objectClicked(QGraphicsItem*)));
|
||||
last10MinLabel->setFont(QFont(LABEL_FONT, LABEL_KV_SIZE, QFont::Light));
|
||||
lastHourLabel = new ClickableTextItem(); lastHourLabel->setPlainText(tr("Last Hour"));
|
||||
scene->addItem(lastHourLabel);
|
||||
connect(lastHourLabel, SIGNAL(objectClicked(QGraphicsItem*)), this, SLOT(objectClicked(QGraphicsItem*)));
|
||||
lastHourLabel->setFont(QFont(LABEL_FONT, LABEL_KV_SIZE, QFont::Light));
|
||||
lastDayLabel = new ClickableTextItem(); lastDayLabel->setPlainText(tr("Last Day"));
|
||||
scene->addItem(lastDayLabel);
|
||||
connect(lastDayLabel, SIGNAL(objectClicked(QGraphicsItem*)), this, SLOT(objectClicked(QGraphicsItem*)));
|
||||
lastDayLabel->setFont(QFont(LABEL_FONT, LABEL_KV_SIZE, QFont::Light));
|
||||
allDataLabel = new ClickableTextItem(); allDataLabel->setPlainText(tr("All Data"));
|
||||
scene->addItem(allDataLabel);
|
||||
connect(allDataLabel, SIGNAL(objectClicked(QGraphicsItem*)), this, SLOT(objectClicked(QGraphicsItem*)));
|
||||
allDataLabel->setFont(QFont(LABEL_FONT, LABEL_KV_SIZE, QFont::Light));
|
||||
}
|
||||
|
||||
last10MinLabel->setEnabled((timeFilter == TEN_MINS));
|
||||
lastHourLabel->setEnabled((timeFilter == ONE_HOUR));
|
||||
lastDayLabel->setEnabled((timeFilter == ONE_DAY));
|
||||
allDataLabel->setEnabled((timeFilter == 0));
|
||||
|
||||
// remove the items which needs to be redrawn
|
||||
for (QGraphicsItem * item : redrawItems)
|
||||
{
|
||||
scene->removeItem(item);
|
||||
delete item;
|
||||
}
|
||||
redrawItems.clear();
|
||||
|
||||
// get the samples
|
||||
QDateTime toDateTime = QDateTime::currentDateTime();
|
||||
QDateTime fromDateTime = toDateTime.addSecs(-timeFilter); //-1h
|
||||
if (timeFilter == 0)
|
||||
{
|
||||
// disable filter if timeFilter == 0
|
||||
toDateTime.setTime_t(0);
|
||||
fromDateTime.setTime_t(0);
|
||||
}
|
||||
|
||||
mempoolSamples_t vSamples = clientModel->getMempoolStatsInRange(fromDateTime, toDateTime);
|
||||
|
||||
// set the values into the overview labels
|
||||
if (vSamples.size())
|
||||
{
|
||||
dynMemUsageValueItem->setPlainText(GUIUtil::formatBytes((uint64_t)vSamples.back().m_dyn_mem_usage));
|
||||
txCountValueItem->setPlainText(QString::number(vSamples.back().m_tx_count));
|
||||
minFeeValueItem->setPlainText(QString::number(vSamples.back().m_min_fee_per_k));
|
||||
}
|
||||
|
||||
// set dynamic label positions
|
||||
int maxValueSize = std::max(std::max(txCountValueItem->boundingRect().width(), dynMemUsageValueItem->boundingRect().width()), minFeeValueItem->boundingRect().width());
|
||||
maxValueSize = ceil(maxValueSize*0.11)*10; //use size steps of 10dip
|
||||
|
||||
int rightPaddingLabels = std::max(std::max(dynMemUsageSwitch->boundingRect().width(), txCountSwitch->boundingRect().width()), minFeeSwitch->boundingRect().width())+maxValueSize;
|
||||
int rightPadding = 10;
|
||||
dynMemUsageSwitch->setPos(width()-rightPaddingLabels-rightPadding, 5);
|
||||
|
||||
txCountSwitch->setPos(width()-rightPaddingLabels-rightPadding, dynMemUsageSwitch->pos().y()+dynMemUsageSwitch->boundingRect().height());
|
||||
|
||||
minFeeSwitch->setPos(width()-rightPaddingLabels-rightPadding, txCountSwitch->pos().y()+txCountSwitch->boundingRect().height());
|
||||
|
||||
dynMemUsageValueItem->setPos(width()-dynMemUsageValueItem->boundingRect().width()-rightPadding, dynMemUsageSwitch->pos().y());
|
||||
txCountValueItem->setPos(width()-txCountValueItem->boundingRect().width()-rightPadding, txCountSwitch->pos().y());
|
||||
minFeeValueItem->setPos(width()-minFeeValueItem->boundingRect().width()-rightPadding, minFeeSwitch->pos().y());
|
||||
|
||||
titleItem->setPos(5,minFeeSwitch->pos().y()+minFeeSwitch->boundingRect().height()-titleItem->boundingRect().height()+10);
|
||||
titleLine->setLine(10, titleItem->pos().y()+titleItem->boundingRect().height(), width()-10, titleItem->pos().y()+titleItem->boundingRect().height());
|
||||
|
||||
// center the optional "no data" label
|
||||
noDataItem->setPos(width()/2.0-noDataItem->boundingRect().width()/2.0, height()/2.0);
|
||||
|
||||
// set the position of the filter icons
|
||||
static const int filterBottomPadding = 30;
|
||||
int totalWidth = last10MinLabel->boundingRect().width()+lastHourLabel->boundingRect().width()+lastDayLabel->boundingRect().width()+allDataLabel->boundingRect().width()+30;
|
||||
last10MinLabel->setPos((width()-totalWidth)/2.0,height()-filterBottomPadding);
|
||||
lastHourLabel->setPos((width()-totalWidth)/2.0+last10MinLabel->boundingRect().width()+10,height()-filterBottomPadding);
|
||||
lastDayLabel->setPos((width()-totalWidth)/2.0+last10MinLabel->boundingRect().width()+lastHourLabel->boundingRect().width()+20,height()-filterBottomPadding);
|
||||
allDataLabel->setPos((width()-totalWidth)/2.0+last10MinLabel->boundingRect().width()+lastHourLabel->boundingRect().width()+lastDayLabel->boundingRect().width()+30,height()-filterBottomPadding);
|
||||
|
||||
// don't paint the grind/graph if there are no or only a single sample
|
||||
if (vSamples.size() < 2)
|
||||
{
|
||||
noDataItem->setVisible(true);
|
||||
return;
|
||||
}
|
||||
noDataItem->setVisible(false);
|
||||
|
||||
int bottom = ui->graphicsView->size().height()-GRAPH_PADDING_BOTTOM;
|
||||
qreal maxwidth = ui->graphicsView->size().width()-GRAPH_PADDING_LEFT-GRAPH_PADDING_RIGHT;
|
||||
qreal maxheightG = ui->graphicsView->size().height()-GRAPH_PADDING_TOP-GRAPH_PADDING_TOP_LABEL-LABEL_HEIGHT;
|
||||
float paddingTopSizeFactor = 1.2;
|
||||
qreal step = maxwidth/(double)vSamples.size();
|
||||
|
||||
// make sure we skip samples that would be drawn narrower then 1px
|
||||
// larger window can result in drawing more samples
|
||||
int samplesStep = 1;
|
||||
if (step < 1)
|
||||
samplesStep = ceil(1/samplesStep);
|
||||
|
||||
// find maximum values
|
||||
int64_t maxDynMemUsage = 0;
|
||||
int64_t minDynMemUsage = std::numeric_limits<int64_t>::max();
|
||||
int64_t maxTxCount = 0;
|
||||
int64_t minTxCount = std::numeric_limits<int64_t>::max();
|
||||
int64_t maxMinFee = 0;
|
||||
uint32_t maxTimeDetla = vSamples.back().m_time_delta-vSamples.front().m_time_delta;
|
||||
for(const struct CStatsMempoolSample &sample : vSamples)
|
||||
{
|
||||
if (sample.m_dyn_mem_usage > maxDynMemUsage)
|
||||
maxDynMemUsage = sample.m_dyn_mem_usage;
|
||||
|
||||
if (sample.m_dyn_mem_usage < minDynMemUsage)
|
||||
minDynMemUsage = sample.m_dyn_mem_usage;
|
||||
|
||||
if (sample.m_tx_count > maxTxCount)
|
||||
maxTxCount = sample.m_tx_count;
|
||||
|
||||
if (sample.m_tx_count < minTxCount)
|
||||
minTxCount = sample.m_tx_count;
|
||||
|
||||
if (sample.m_min_fee_per_k > maxMinFee)
|
||||
maxMinFee = sample.m_min_fee_per_k;
|
||||
}
|
||||
|
||||
int64_t dynMemUsagelog10Val = pow(10.0, floor(log10(maxDynMemUsage*paddingTopSizeFactor-minDynMemUsage)));
|
||||
int64_t topDynMemUsage = ceil((double)maxDynMemUsage*paddingTopSizeFactor/dynMemUsagelog10Val)*dynMemUsagelog10Val;
|
||||
int64_t bottomDynMemUsage = floor((double)minDynMemUsage/dynMemUsagelog10Val)*dynMemUsagelog10Val;
|
||||
|
||||
int64_t txCountLog10Val = pow(10.0, floor(log10(maxTxCount*paddingTopSizeFactor-minTxCount)));
|
||||
int64_t topTxCount = ceil((double)maxTxCount*paddingTopSizeFactor/txCountLog10Val)*txCountLog10Val;
|
||||
int64_t bottomTxCount = floor((double)minTxCount/txCountLog10Val)*txCountLog10Val;
|
||||
|
||||
qreal currentX = GRAPH_PADDING_LEFT;
|
||||
QPainterPath dynMemUsagePath(QPointF(currentX, bottom));
|
||||
QPainterPath txCountPath(QPointF(currentX, bottom));
|
||||
QPainterPath minFeePath(QPointF(currentX, bottom));
|
||||
|
||||
// draw the three possible paths
|
||||
for (mempoolSamples_t::iterator it = vSamples.begin(); it != vSamples.end(); it+=samplesStep)
|
||||
{
|
||||
const struct CStatsMempoolSample &sample = (*it);
|
||||
qreal xPos = maxTimeDetla > 0 ? maxwidth/maxTimeDetla*(sample.m_time_delta-vSamples.front().m_time_delta) : maxwidth/(double)vSamples.size();
|
||||
if (sample.m_time_delta == vSamples.front().m_time_delta)
|
||||
{
|
||||
dynMemUsagePath.moveTo(GRAPH_PADDING_LEFT+xPos, bottom-maxheightG/(topDynMemUsage-bottomDynMemUsage)*(sample.m_dyn_mem_usage-bottomDynMemUsage));
|
||||
txCountPath.moveTo(GRAPH_PADDING_LEFT+xPos, bottom-maxheightG/(topTxCount-bottomTxCount)*(sample.m_tx_count-bottomTxCount));
|
||||
minFeePath.moveTo(GRAPH_PADDING_LEFT+xPos, bottom-maxheightG/maxMinFee*sample.m_min_fee_per_k);
|
||||
}
|
||||
else
|
||||
{
|
||||
dynMemUsagePath.lineTo(GRAPH_PADDING_LEFT+xPos, bottom-maxheightG/(topDynMemUsage-bottomDynMemUsage)*(sample.m_dyn_mem_usage-bottomDynMemUsage));
|
||||
txCountPath.lineTo(GRAPH_PADDING_LEFT+xPos, bottom-maxheightG/(topTxCount-bottomTxCount)*(sample.m_tx_count-bottomTxCount));
|
||||
minFeePath.lineTo(GRAPH_PADDING_LEFT+xPos, bottom-maxheightG/maxMinFee*sample.m_min_fee_per_k);
|
||||
}
|
||||
}
|
||||
|
||||
// copy the path for the fill
|
||||
QPainterPath dynMemUsagePathFill(dynMemUsagePath);
|
||||
|
||||
// close the path for the fill
|
||||
dynMemUsagePathFill.lineTo(GRAPH_PADDING_LEFT+maxwidth, bottom);
|
||||
dynMemUsagePathFill.lineTo(GRAPH_PADDING_LEFT, bottom);
|
||||
|
||||
QPainterPath dynMemUsageGridPath(QPointF(currentX, bottom));
|
||||
|
||||
// draw horizontal grid
|
||||
int amountOfLinesH = 5;
|
||||
QFont gridFont;
|
||||
gridFont.setPointSize(8);
|
||||
for (int i=0; i < amountOfLinesH; i++)
|
||||
{
|
||||
qreal lY = bottom-i*(maxheightG/(amountOfLinesH-1));
|
||||
dynMemUsageGridPath.moveTo(GRAPH_PADDING_LEFT, lY);
|
||||
dynMemUsageGridPath.lineTo(GRAPH_PADDING_LEFT+maxwidth, lY);
|
||||
|
||||
size_t gridDynSize = (float)i*(topDynMemUsage-bottomDynMemUsage)/(amountOfLinesH-1) + bottomDynMemUsage;
|
||||
size_t gridTxCount = (float)i*(topTxCount-bottomTxCount)/(amountOfLinesH-1) + bottomTxCount;
|
||||
|
||||
QGraphicsTextItem *itemDynSize = scene->addText(GUIUtil::formatBytes(gridDynSize), gridFont);
|
||||
QGraphicsTextItem *itemTxCount = scene->addText(QString::number(gridTxCount), gridFont);
|
||||
|
||||
itemDynSize->setPos(GRAPH_PADDING_LEFT-itemDynSize->boundingRect().width(), lY-(itemDynSize->boundingRect().height()/2));
|
||||
itemTxCount->setPos(GRAPH_PADDING_LEFT+maxwidth, lY-(itemDynSize->boundingRect().height()/2));
|
||||
redrawItems.append(itemDynSize);
|
||||
redrawItems.append(itemTxCount);
|
||||
}
|
||||
|
||||
// draw vertical grid
|
||||
int amountOfLinesV = 4;
|
||||
QDateTime drawTime(fromDateTime);
|
||||
std::string fromS = fromDateTime.toString().toStdString();
|
||||
std::string toS = toDateTime.toString().toStdString();
|
||||
qint64 secsTotal = fromDateTime.secsTo(toDateTime);
|
||||
for (int i=0; i <= amountOfLinesV; i++)
|
||||
{
|
||||
qreal lX = i*(maxwidth/(amountOfLinesV));
|
||||
dynMemUsageGridPath.moveTo(GRAPH_PADDING_LEFT+lX, bottom);
|
||||
dynMemUsageGridPath.lineTo(GRAPH_PADDING_LEFT+lX, bottom-maxheightG);
|
||||
|
||||
QGraphicsTextItem *item = scene->addText(drawTime.toString("HH:mm"), gridFont);
|
||||
item->setPos(GRAPH_PADDING_LEFT+lX-(item->boundingRect().width()/2), bottom);
|
||||
redrawItems.append(item);
|
||||
qint64 step = secsTotal/amountOfLinesV;
|
||||
drawTime = drawTime.addSecs(step);
|
||||
}
|
||||
|
||||
// materialize path
|
||||
QPen gridPen(QColor(100,100,100, 200), 1, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin);
|
||||
redrawItems.append(scene->addPath(dynMemUsageGridPath, gridPen));
|
||||
|
||||
// draw semi-transparent gradient for the dynamic memory size fill
|
||||
QLinearGradient gradient(currentX, bottom, currentX, 0);
|
||||
gradient.setColorAt(1.0, QColor(15,68,113, 250));
|
||||
gradient.setColorAt(0, QColor(255,255,255,0));
|
||||
QBrush graBru(gradient);
|
||||
|
||||
QPen linePenBlue(QColor(15,68,113, 250), 2, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin);
|
||||
QPen linePenRed(QColor(188,49,62, 250), 2, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin);
|
||||
QPen linePenGreen(QColor(49,188,62, 250), 2, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin);
|
||||
|
||||
if (cbShowNumTxns->isChecked())
|
||||
redrawItems.append(scene->addPath(txCountPath, linePenRed));
|
||||
if (cbShowMinFeerate->isChecked())
|
||||
redrawItems.append(scene->addPath(minFeePath, linePenGreen));
|
||||
if (cbShowMemUsage->isChecked())
|
||||
{
|
||||
redrawItems.append(scene->addPath(dynMemUsagePath, linePenBlue));
|
||||
redrawItems.append(scene->addPath(dynMemUsagePathFill, QPen(Qt::NoPen), graBru));
|
||||
}
|
||||
}
|
||||
|
||||
// We override the virtual resizeEvent of the QWidget to adjust tables column
|
||||
// sizes as the tables width is proportional to the dialogs width.
|
||||
void MempoolStats::resizeEvent(QResizeEvent *event)
|
||||
{
|
||||
QWidget::resizeEvent(event);
|
||||
ui->graphicsView->resize(size());
|
||||
ui->graphicsView->scene()->setSceneRect(rect());
|
||||
drawChart();
|
||||
}
|
||||
|
||||
void MempoolStats::showEvent(QShowEvent *event)
|
||||
{
|
||||
QWidget::showEvent(event);
|
||||
if (clientModel)
|
||||
drawChart();
|
||||
}
|
||||
|
||||
void MempoolStats::objectClicked(QGraphicsItem *item)
|
||||
{
|
||||
if (item == last10MinLabel)
|
||||
timeFilter = 600;
|
||||
|
||||
if (item == lastHourLabel)
|
||||
timeFilter = 3600;
|
||||
|
||||
if (item == lastDayLabel)
|
||||
timeFilter = 24*3600;
|
||||
|
||||
if (item == allDataLabel)
|
||||
timeFilter = 0;
|
||||
|
||||
drawChart();
|
||||
}
|
||||
|
||||
MempoolStats::~MempoolStats()
|
||||
{
|
||||
if (titleItem)
|
||||
{
|
||||
for (QGraphicsItem * item : redrawItems)
|
||||
{
|
||||
scene->removeItem(item);
|
||||
delete item;
|
||||
}
|
||||
redrawItems.clear();
|
||||
|
||||
delete titleItem;
|
||||
delete titleLine;
|
||||
delete noDataItem;
|
||||
delete dynMemUsageValueItem;
|
||||
delete txCountValueItem;
|
||||
delete minFeeValueItem;
|
||||
delete last10MinLabel;
|
||||
delete lastHourLabel;
|
||||
delete lastDayLabel;
|
||||
delete allDataLabel;
|
||||
delete txCountSwitch;
|
||||
delete minFeeSwitch;
|
||||
delete dynMemUsageSwitch;
|
||||
delete scene;
|
||||
}
|
||||
}
|
84
src/qt/mempoolstats.h
Normal file
84
src/qt/mempoolstats.h
Normal file
@ -0,0 +1,84 @@
|
||||
// Copyright (c) 2016 The Bitcoin Core developers
|
||||
// Distributed under the MIT software license, see the accompanying
|
||||
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||
|
||||
#ifndef BITCOIN_QT_MEMPOOLSTATS_H
|
||||
#define BITCOIN_QT_MEMPOOLSTATS_H
|
||||
|
||||
#include <QWidget>
|
||||
#include <QGraphicsLineItem>
|
||||
#include <QGraphicsPixmapItem>
|
||||
|
||||
#include <QCheckBox>
|
||||
#include <QGraphicsProxyWidget>
|
||||
|
||||
#include <QEvent>
|
||||
|
||||
class ClientModel;
|
||||
|
||||
class ClickableTextItem : public QGraphicsTextItem
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
void setEnabled(bool state);
|
||||
protected:
|
||||
void mousePressEvent(QGraphicsSceneMouseEvent *event) override;
|
||||
Q_SIGNALS:
|
||||
void objectClicked(QGraphicsItem*);
|
||||
};
|
||||
|
||||
|
||||
namespace Ui {
|
||||
class MempoolStats;
|
||||
}
|
||||
|
||||
class MempoolStats : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
MempoolStats(QWidget *parent = nullptr);
|
||||
~MempoolStats();
|
||||
|
||||
void setClientModel(ClientModel *model);
|
||||
|
||||
public Q_SLOTS:
|
||||
void drawChart();
|
||||
void objectClicked(QGraphicsItem *);
|
||||
|
||||
private:
|
||||
ClientModel *clientModel{nullptr};
|
||||
|
||||
virtual void resizeEvent(QResizeEvent *event) override;
|
||||
virtual void showEvent(QShowEvent *event) override;
|
||||
|
||||
QGraphicsTextItem *titleItem{nullptr};
|
||||
QGraphicsLineItem *titleLine;
|
||||
QGraphicsTextItem *noDataItem;
|
||||
|
||||
QGraphicsTextItem *dynMemUsageValueItem;
|
||||
QGraphicsTextItem *txCountValueItem;
|
||||
QGraphicsTextItem *minFeeValueItem;
|
||||
|
||||
ClickableTextItem *last10MinLabel;
|
||||
ClickableTextItem *lastHourLabel;
|
||||
ClickableTextItem *lastDayLabel;
|
||||
ClickableTextItem *allDataLabel;
|
||||
|
||||
QGraphicsProxyWidget *txCountSwitch;
|
||||
QGraphicsProxyWidget *minFeeSwitch;
|
||||
QGraphicsProxyWidget *dynMemUsageSwitch;
|
||||
|
||||
QGraphicsScene *scene{nullptr};
|
||||
QVector<QGraphicsItem*> redrawItems;
|
||||
|
||||
QCheckBox *cbShowMemUsage;
|
||||
QCheckBox *cbShowNumTxns;
|
||||
QCheckBox *cbShowMinFeerate;
|
||||
|
||||
int64_t timeFilter;
|
||||
|
||||
Ui::MempoolStats *ui;
|
||||
};
|
||||
|
||||
#endif // BITCOIN_QT_MEMPOOLSTATS_H
|
Loading…
Reference in New Issue
Block a user