Merge 8550 via old_stats_qt-25+knots

This commit is contained in:
Luke Dashjr 2023-11-15 23:49:11 +00:00
commit f6b1eae17b
9 changed files with 621 additions and 1 deletions

View File

@ -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 \

View File

@ -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());

View File

@ -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);
}

View File

@ -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); }

View File

@ -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();
}

View File

@ -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

View 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
View 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
View 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