diff --git a/qa/rpc-tests/test_framework/util.py b/qa/rpc-tests/test_framework/util.py index a8e260f95..bfb9bddf1 100644 --- a/qa/rpc-tests/test_framework/util.py +++ b/qa/rpc-tests/test_framework/util.py @@ -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"); diff --git a/src/Makefile.am b/src/Makefile.am index 7467919d4..8ded268cf 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -120,6 +120,7 @@ BITCOIN_CORE_H = \ main.h \ memusage.h \ merkleblock.h \ + metrics.h \ miner.h \ mruset.h \ net.h \ @@ -205,6 +206,7 @@ libbitcoin_server_a_SOURCES = \ leveldbwrapper.cpp \ main.cpp \ merkleblock.cpp \ + metrics.cpp \ miner.cpp \ net.cpp \ noui.cpp \ diff --git a/src/init.cpp b/src/init.cpp index 37564d951..6a9eb6f16 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -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" @@ -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, "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 diff --git a/src/main.cpp b/src/main.cpp index 699b28b5a..ca8f0f8d7 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -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" @@ -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 { diff --git a/src/metrics.cpp b/src/metrics.cpp new file mode 100644 index 000000000..32fc4ba05 --- /dev/null +++ b/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 +#include +#include +#include + +AtomicCounter transactionsValidated; +AtomicCounter ehSolverRuns; +AtomicCounter solutionTargetChecks; +AtomicCounter minedBlocks; + +boost::synchronized_value> messageBox; +boost::synchronized_value 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> 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> 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"; + } +} diff --git a/src/metrics.h b/src/metrics.h new file mode 100644 index 000000000..4a2ca2264 --- /dev/null +++ b/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 +#include + +struct AtomicCounter { + std::atomic 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" +"   "; diff --git a/src/miner.cpp b/src/miner.cpp index 7693394e7..bde9babd5 100644 --- a/src/miner.cpp +++ b/src/miner.cpp @@ -12,6 +12,7 @@ #include "consensus/validation.h" #include "hash.h" #include "main.h" +#include "metrics.h" #include "net.h" #include "pow.h" #include "primitives/transaction.h" @@ -437,6 +438,8 @@ static bool ProcessBlockFound(CBlock* pblock, CWallet& wallet, CReserveKey& rese if (!ProcessNewBlock(state, NULL, pblock, true, NULL)) return error("ZcashMiner: ProcessNewBlock, block not accepted"); + minedBlocks.increment(); + return true; } @@ -538,6 +541,7 @@ void static BitcoinMiner(CWallet *pwallet) // Write the solution to the hash and compute the result. LogPrint("pow", "- Checking solution against target\n"); pblock->nSolution = soln; + solutionTargetChecks.increment(); if (UintToArith256(pblock->GetHash()) > hashTarget) { return false; @@ -555,8 +559,11 @@ void static BitcoinMiner(CWallet *pwallet) SetThreadPriority(THREAD_PRIORITY_LOWEST); // In regression test mode, stop mining after a block is found. - if (chainparams.MineBlocksOnDemand()) + if (chainparams.MineBlocksOnDemand()) { + // Increment here because throwing skips the call below + ehSolverRuns.increment(); throw boost::thread_interrupted(); + } return true; }; @@ -581,6 +588,7 @@ void static BitcoinMiner(CWallet *pwallet) eq.showbsizes(r); } eq.digitK(0); + ehSolverRuns.increment(); // Convert solution indices to byte array (decompress) and pass it to validBlock method. for (size_t s = 0; s < eq.nsols; s++) { @@ -600,8 +608,11 @@ void static BitcoinMiner(CWallet *pwallet) } else { try { // If we find a valid block, we rebuild - if (EhOptimisedSolve(n, k, curr_state, validBlock, cancelled)) + bool found = EhOptimisedSolve(n, k, curr_state, validBlock, cancelled); + ehSolverRuns.increment(); + if (found) { break; + } } catch (EhSolverCancelledException&) { LogPrint("pow", "Equihash solver cancelled\n"); std::lock_guard lock{m_cs}; diff --git a/src/pow/tromp/equi_miner.h b/src/pow/tromp/equi_miner.h index 2f6531fe8..9559c178e 100644 --- a/src/pow/tromp/equi_miner.h +++ b/src/pow/tromp/equi_miner.h @@ -300,7 +300,7 @@ struct equi { } for (u32 i=0; i < 65; i++) { #ifdef HIST - printf(" %d:%d", i, binsizes[i]); +// printf(" %d:%d", i, binsizes[i]); #else #ifdef SPARK u32 sparks = binsizes[i] / SPARKSCALE; @@ -309,10 +309,10 @@ struct equi { for (u32 bs = binsizes[i]; bs; bs >>= 1) sparks++; sparks = sparks * 7 / SPARKSCALE; #endif - printf("\342\226%c", '\201' + sparks); +// printf("\342\226%c", '\201' + sparks); #endif } - printf("\n"); +// printf("\n"); #endif } @@ -590,7 +590,7 @@ nc++, candidate(tree(bucketid, s0, s1)); } } } -printf(" %d candidates ", nc); +//printf(" %d candidates ", nc); } }; @@ -603,7 +603,7 @@ typedef struct { void barrier(pthread_barrier_t *barry) { const int rc = pthread_barrier_wait(barry); if (rc != 0 && rc != PTHREAD_BARRIER_SERIAL_THREAD) { - printf("Could not wait on barrier\n"); +// printf("Could not wait on barrier\n"); pthread_exit(NULL); } } @@ -613,7 +613,7 @@ void *worker(void *vp) { equi *eq = tp->eq; if (tp->id == 0) - printf("Digit 0\n"); +// printf("Digit 0\n"); barrier(&eq->barry); eq->digit0(tp->id); barrier(&eq->barry); @@ -624,19 +624,19 @@ void *worker(void *vp) { barrier(&eq->barry); for (u32 r = 1; r < WK; r++) { if (tp->id == 0) - printf("Digit %d", r); +// printf("Digit %d", r); barrier(&eq->barry); r&1 ? eq->digitodd(r, tp->id) : eq->digiteven(r, tp->id); barrier(&eq->barry); if (tp->id == 0) { - printf(" x%d b%d h%d\n", eq->xfull, eq->bfull, eq->hfull); +// printf(" x%d b%d h%d\n", eq->xfull, eq->bfull, eq->hfull); eq->xfull = eq->bfull = eq->hfull = 0; eq->showbsizes(r); } barrier(&eq->barry); } if (tp->id == 0) - printf("Digit %d\n", WK); +// printf("Digit %d\n", WK); eq->digitK(tp->id); barrier(&eq->barry); pthread_exit(NULL); diff --git a/src/rpcmining.cpp b/src/rpcmining.cpp index acdf366a1..3759bd03a 100644 --- a/src/rpcmining.cpp +++ b/src/rpcmining.cpp @@ -11,6 +11,7 @@ #include "crypto/equihash.h" #include "init.h" #include "main.h" +#include "metrics.h" #include "miner.h" #include "net.h" #include "pow.h" @@ -191,15 +192,20 @@ Value generate(const Array& params, bool fHelp) std::function)> validBlock = [&pblock](std::vector soln) { pblock->nSolution = soln; + solutionTargetChecks.increment(); return CheckProofOfWork(pblock->GetHash(), pblock->nBits, Params().GetConsensus()); }; - if (EhBasicSolveUncancellable(n, k, curr_state, validBlock)) + bool found = EhBasicSolveUncancellable(n, k, curr_state, validBlock); + ehSolverRuns.increment(); + if (found) { goto endloop; + } } endloop: CValidationState state; if (!ProcessNewBlock(state, NULL, pblock, true, NULL)) throw JSONRPCError(RPC_INTERNAL_ERROR, "ProcessNewBlock, block not accepted"); + minedBlocks.increment(); ++nHeight; blockHashes.push_back(pblock->GetHash().GetHex()); }