Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add node metrics screen #1375

Merged
merged 12 commits into from Oct 23, 2016
1 change: 1 addition & 0 deletions qa/rpc-tests/test_framework/util.py
Expand Up @@ -65,6 +65,7 @@ def initialize_datadir(dirname, n):
os.makedirs(datadir)
with open(os.path.join(datadir, "zcash.conf"), 'w') as f:
f.write("regtest=1\n");
f.write("showmetrics=0\n");
f.write("rpcuser=rt\n");
f.write("rpcpassword=rt\n");
f.write("port="+str(p2p_port(n))+"\n");
Expand Down
2 changes: 2 additions & 0 deletions src/Makefile.am
Expand Up @@ -120,6 +120,7 @@ BITCOIN_CORE_H = \
main.h \
memusage.h \
merkleblock.h \
metrics.h \
miner.h \
mruset.h \
net.h \
Expand Down Expand Up @@ -205,6 +206,7 @@ libbitcoin_server_a_SOURCES = \
leveldbwrapper.cpp \
main.cpp \
merkleblock.cpp \
metrics.cpp \
miner.cpp \
net.cpp \
noui.cpp \
Expand Down
7 changes: 7 additions & 0 deletions src/init.cpp
Expand Up @@ -16,6 +16,7 @@
#include "consensus/validation.h"
#include "key.h"
#include "main.h"
#include "metrics.h"
#include "miner.h"
#include "net.h"
#include "rpcserver.h"
Expand Down Expand Up @@ -975,6 +976,12 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler)
CScheduler::Function serviceLoop = boost::bind(&CScheduler::serviceQueue, &scheduler);
threadGroup.create_thread(boost::bind(&TraceThread<CScheduler::Function>, "scheduler", serviceLoop));

if (GetBoolArg("-showmetrics", true) && !fPrintToConsole && !GetBoolArg("-daemon", false)) {
// Start the persistent metrics interface
ConnectMetricsScreen();
threadGroup.create_thread(&ThreadShowMetricsScreen);
}

// Initialize Zcash circuit parameters
ZC_LoadParams();
// These must be disabled for now, they are buggy and we probably don't
Expand Down
6 changes: 6 additions & 0 deletions src/main.cpp
Expand Up @@ -16,6 +16,7 @@
#include "consensus/validation.h"
#include "init.h"
#include "merkleblock.h"
#include "metrics.h"
#include "net.h"
#include "pow.h"
#include "txdb.h"
Expand Down Expand Up @@ -833,6 +834,11 @@ unsigned int GetP2SHSigOpCount(const CTransaction& tx, const CCoinsViewCache& in

bool CheckTransaction(const CTransaction& tx, CValidationState &state)
{
// Don't count coinbase transactions because mining skews the count
if (!tx.IsCoinBase()) {
transactionsValidated.increment();
}

if (!CheckTransactionWithoutProofVerification(tx, state)) {
return false;
} else {
Expand Down
216 changes: 216 additions & 0 deletions src/metrics.cpp
@@ -0,0 +1,216 @@
// Copyright (c) 2016 The Zcash developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.

#include "metrics.h"

#include "chainparams.h"
#include "ui_interface.h"
#include "util.h"
#include "utiltime.h"

#include <boost/thread.hpp>
#include <boost/thread/synchronized_value.hpp>
#include <string>
#include <sys/ioctl.h>

AtomicCounter transactionsValidated;
AtomicCounter ehSolverRuns;
AtomicCounter solutionTargetChecks;
AtomicCounter minedBlocks;

boost::synchronized_value<std::list<std::string>> messageBox;
boost::synchronized_value<std::string> initMessage;
bool loaded = false;

static bool metrics_ThreadSafeMessageBox(const std::string& message,
const std::string& caption,
unsigned int style)
{
std::string strCaption;
// Check for usage of predefined caption
switch (style) {
case CClientUIInterface::MSG_ERROR:
strCaption += _("Error");
break;
case CClientUIInterface::MSG_WARNING:
strCaption += _("Warning");
break;
case CClientUIInterface::MSG_INFORMATION:
strCaption += _("Information");
break;
default:
strCaption += caption; // Use supplied caption (can be empty)
}

boost::strict_lock_ptr<std::list<std::string>> u = messageBox.synchronize();
u->push_back(strCaption + ": " + message);
if (u->size() > 5) {
u->pop_back();
}
}

static void metrics_InitMessage(const std::string& message)
{
*initMessage = message;
}

void ConnectMetricsScreen()
{
uiInterface.ThreadSafeMessageBox.disconnect_all_slots();
uiInterface.ThreadSafeMessageBox.connect(metrics_ThreadSafeMessageBox);
uiInterface.InitMessage.disconnect_all_slots();
uiInterface.InitMessage.connect(metrics_InitMessage);
}

void printMiningStatus(bool mining)
{
if (mining) {
int nThreads = GetArg("-genproclimit", 1);
if (nThreads < 0) {
// In regtest threads defaults to 1
if (Params().DefaultMinerThreads())
nThreads = Params().DefaultMinerThreads();
else
nThreads = boost::thread::hardware_concurrency();
}
std::cout << strprintf(_("You are running %d mining threads."), nThreads) << std::endl;
} else {
std::cout << _("You are currently not mining.") << std::endl;
std::cout << _("To enable mining, add 'gen=1' to your zcash.conf and restart.") << std::endl;
}
std::cout << std::endl;
}

int printMetrics(size_t cols, int64_t nStart, bool mining)
{
// Number of lines that are always displayed
int lines = 3;

// Calculate uptime
int64_t uptime = GetTime() - nStart;
int days = uptime / (24 * 60 * 60);
int hours = (uptime - (days * 24 * 60 * 60)) / (60 * 60);
int minutes = (uptime - (((days * 24) + hours) * 60 * 60)) / 60;
int seconds = uptime - (((((days * 24) + hours) * 60) + minutes) * 60);

// Display uptime
std::string duration;
if (days > 0) {
duration = strprintf(_("%d days, %d hours, %d minutes, %d seconds"), days, hours, minutes, seconds);
} else if (hours > 0) {
duration = strprintf(_("%d hours, %d minutes, %d seconds"), hours, minutes, seconds);
} else if (minutes > 0) {
duration = strprintf(_("%d minutes, %d seconds"), minutes, seconds);
} else {
duration = strprintf(_("%d seconds"), seconds);
}
std::string strDuration = strprintf(_("Since starting this node %s ago:"), duration);
std::cout << strDuration << std::endl;
lines += (strDuration.size() / cols);

std::cout << "- " << strprintf(_("You have validated %d transactions!"), transactionsValidated.get()) << std::endl;

if (mining) {
double solps = uptime > 0 ? (double)solutionTargetChecks.get() / uptime : 0;
std::string strSolps = strprintf("%.4f Sol/s", solps);
std::cout << "- " << strprintf(_("You have contributed %s on average to the network solution rate."), strSolps) << std::endl;
std::cout << "- " << strprintf(_("You have completed %d Equihash solver runs."), ehSolverRuns.get()) << std::endl;
lines += 2;

int mined = minedBlocks.get();
if (mined > 0) {
std::cout << "- " << strprintf(_("You have mined %d blocks!"), mined) << std::endl;
lines++;
}
}
std::cout << std::endl;

return lines;
}

int printMessageBox(size_t cols)
{
boost::strict_lock_ptr<std::list<std::string>> u = messageBox.synchronize();

if (u->size() == 0) {
return 0;
}

int lines = 2 + u->size();
std::cout << _("Messages:") << std::endl;
for (auto it = u->cbegin(); it != u->cend(); ++it) {
std::cout << *it << std::endl;
// Handle wrapped lines
lines += (it->size() / cols);
}
std::cout << std::endl;
return lines;
}

int printInitMessage()
{
if (loaded) {
return 0;
}

std::string msg = *initMessage;
std::cout << _("Init message:") << " " << msg << std::endl;
std::cout << std::endl;

if (msg == _("Done loading")) {
loaded = true;
}

return 2;
}

void ThreadShowMetricsScreen()
{
// Make this thread recognisable as the metrics screen thread
RenameThread("zcash-metrics-screen");

// Clear screen
std::cout << "\e[2J";

// Print art
std::cout << METRICS_ART << std::endl;
std::cout << std::endl;

// Thank you text
std::cout << _("Thank you for running a Zcash node!") << std::endl;
std::cout << _("You're helping to strengthen the network and contributing to a social good :)") << std::endl;
std::cout << std::endl;

// Miner status
bool mining = GetBoolArg("-gen", false);
printMiningStatus(mining);

// Count uptime
int64_t nStart = GetTime();

while (true) {
// Number of lines that are always displayed
int lines = 1;

// Get current window size
struct winsize w;
ioctl(STDOUT_FILENO, TIOCGWINSZ, &w);

// Erase below current position
std::cout << "\e[J";

lines += printMetrics(w.ws_col, nStart, mining);
lines += printMessageBox(w.ws_col);
lines += printInitMessage();

// Explain how to exit
std::cout << "[" << _("Press Ctrl+C to exit") << "] [" << _("Set 'showmetrics=0' to hide") << "]" << std::endl;;

boost::this_thread::interruption_point();
MilliSleep(1000);

// Return to the top of the updating section
std::cout << "\e[" << lines << "A";
}
}
62 changes: 62 additions & 0 deletions src/metrics.h
@@ -0,0 +1,62 @@
// Copyright (c) 2016 The Zcash developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.

#include <atomic>
#include <string>

struct AtomicCounter {
std::atomic<int> value;

AtomicCounter() : value {0} { }

void increment(){
++value;
}

void decrement(){
--value;
}

int get(){
return value.load();
}
};

extern AtomicCounter transactionsValidated;
extern AtomicCounter ehSolverRuns;
extern AtomicCounter solutionTargetChecks;
extern AtomicCounter minedBlocks;

void ConnectMetricsScreen();
void ThreadShowMetricsScreen();

/**
* Heart image: https://commons.wikimedia.org/wiki/File:Heart_coraz%C3%B3n.svg
* License: CC BY-SA 3.0
*
* Rendering options:
* Zcash: img2txt -W 40 -H 20 -f utf8 -d none -g 0.7 Z-yellow.orange-logo.png
* Heart: img2txt -W 40 -H 20 -f utf8 -d none 2000px-Heart_corazón.svg.png
*/
const std::string METRICS_ART =
"   \n"
"   \n"
"  :88SX@888@@X8:  8; %X X% ;8 \n"
"  %%Xt%tt%SSSSS:XXXt@@  X :: :: X \n"
"  @S;;tt%%%t ;;::XXXXSX  % SS % \n"
"  .t:::;;%8888 88888tXXXX8;  S S \n"
"  .%...:::8 8::XXX%;  X X \n"
"  8888...:t888888X 8t;;::XX8   8 8 \n"
" %888888...:::;:8  :Xttt;;;::X@    \n"
" 888888888...:St 8:%%tttt;;;:X  X X \n"
" 88888888888S8  :%;ttt%%tttt;;X  8 8 \n"
" %888888888%t 8S:;;;tt%%%ttt;8  : : \n"
"  8t8888888  S8888888Stt%%%t@   :: :: \n"
"  .@tt888@ 8;;ttt@;  t t \n"
"  .8ttt8@SSSSS SXXXX%:;;;X;  8 8 \n"
"  X8ttt8888% %88...::X8   X. .X \n"
"  %8@tt88;8888%8888%8X   :; ;: \n"
"  :@888@XXX@888:  tt \n"
"   \n"
"   ";