]> Git Repo - VerusCoin.git/blob - src/metrics.cpp
Wrap messages nicely on metrics screen
[VerusCoin.git] / src / metrics.cpp
1 // Copyright (c) 2016 The Zcash developers
2 // Distributed under the MIT software license, see the accompanying
3 // file COPYING or http://www.opensource.org/licenses/mit-license.php.
4
5 #include "metrics.h"
6
7 #include "chainparams.h"
8 #include "main.h"
9 #include "ui_interface.h"
10 #include "util.h"
11 #include "utiltime.h"
12 #include "utilmoneystr.h"
13 #include "utilstrencodings.h"
14
15 #include <boost/thread.hpp>
16 #include <boost/thread/synchronized_value.hpp>
17 #include <string>
18 #include <sys/ioctl.h>
19 #include <unistd.h>
20
21 void AtomicTimer::start()
22 {
23     std::unique_lock<std::mutex> lock(mtx);
24     if (threads < 1) {
25         start_time = GetTime();
26     }
27     ++threads;
28 }
29
30 void AtomicTimer::stop()
31 {
32     std::unique_lock<std::mutex> lock(mtx);
33     // Ignore excess calls to stop()
34     if (threads > 0) {
35         --threads;
36         if (threads < 1) {
37             int64_t time_span = GetTime() - start_time;
38             total_time += time_span;
39         }
40     }
41 }
42
43 bool AtomicTimer::running()
44 {
45     std::unique_lock<std::mutex> lock(mtx);
46     return threads > 0;
47 }
48
49 double AtomicTimer::rate(const AtomicCounter& count)
50 {
51     std::unique_lock<std::mutex> lock(mtx);
52     int64_t duration = total_time;
53     if (threads > 0) {
54         // Timer is running, so get the latest count
55         duration += GetTime() - start_time;
56     }
57     return duration > 0 ? (double)count.get() / duration : 0;
58 }
59
60 CCriticalSection cs_metrics;
61
62 boost::synchronized_value<int64_t> nNodeStartTime;
63 boost::synchronized_value<int64_t> nNextRefresh;
64 AtomicCounter transactionsValidated;
65 AtomicCounter ehSolverRuns;
66 AtomicCounter solutionTargetChecks;
67 AtomicCounter minedBlocks;
68 AtomicTimer miningTimer;
69
70 boost::synchronized_value<std::list<uint256>> trackedBlocks;
71
72 boost::synchronized_value<std::list<std::string>> messageBox;
73 boost::synchronized_value<std::string> initMessage;
74 bool loaded = false;
75
76 extern int64_t GetNetworkHashPS(int lookup, int height);
77
78 void TrackMinedBlock(uint256 hash)
79 {
80     LOCK(cs_metrics);
81     minedBlocks.increment();
82     trackedBlocks->push_back(hash);
83 }
84
85 void MarkStartTime()
86 {
87     *nNodeStartTime = GetTime();
88 }
89
90 int64_t GetUptime()
91 {
92     return GetTime() - *nNodeStartTime;
93 }
94
95 double GetLocalSolPS()
96 {
97     return miningTimer.rate(solutionTargetChecks);
98 }
99
100 void TriggerRefresh()
101 {
102     *nNextRefresh = GetTime();
103     // Ensure that the refresh has started before we return
104     MilliSleep(200);
105 }
106
107 static bool metrics_ThreadSafeMessageBox(const std::string& message,
108                                       const std::string& caption,
109                                       unsigned int style)
110 {
111     // The SECURE flag has no effect in the metrics UI.
112     style &= ~CClientUIInterface::SECURE;
113
114     std::string strCaption;
115     // Check for usage of predefined caption
116     switch (style) {
117     case CClientUIInterface::MSG_ERROR:
118         strCaption += _("Error");
119         break;
120     case CClientUIInterface::MSG_WARNING:
121         strCaption += _("Warning");
122         break;
123     case CClientUIInterface::MSG_INFORMATION:
124         strCaption += _("Information");
125         break;
126     default:
127         strCaption += caption; // Use supplied caption (can be empty)
128     }
129
130     boost::strict_lock_ptr<std::list<std::string>> u = messageBox.synchronize();
131     u->push_back(strCaption + ": " + message);
132     if (u->size() > 5) {
133         u->pop_back();
134     }
135
136     TriggerRefresh();
137     return false;
138 }
139
140 static bool metrics_ThreadSafeQuestion(const std::string& /* ignored interactive message */, const std::string& message, const std::string& caption, unsigned int style)
141 {
142     return metrics_ThreadSafeMessageBox(message, caption, style);
143 }
144
145 static void metrics_InitMessage(const std::string& message)
146 {
147     *initMessage = message;
148 }
149
150 void ConnectMetricsScreen()
151 {
152     uiInterface.ThreadSafeMessageBox.disconnect_all_slots();
153     uiInterface.ThreadSafeMessageBox.connect(metrics_ThreadSafeMessageBox);
154     uiInterface.ThreadSafeQuestion.disconnect_all_slots();
155     uiInterface.ThreadSafeQuestion.connect(metrics_ThreadSafeQuestion);
156     uiInterface.InitMessage.disconnect_all_slots();
157     uiInterface.InitMessage.connect(metrics_InitMessage);
158 }
159
160 int printStats(bool mining)
161 {
162     // Number of lines that are always displayed
163     int lines = 4;
164
165     int height;
166     size_t connections;
167     int64_t netsolps;
168     {
169         LOCK2(cs_main, cs_vNodes);
170         height = chainActive.Height();
171         connections = vNodes.size();
172         netsolps = GetNetworkHashPS(120, -1);
173     }
174     auto localsolps = GetLocalSolPS();
175
176     std::cout << "           " << _("Block height") << " | " << height << std::endl;
177     std::cout << "            " << _("Connections") << " | " << connections << std::endl;
178     std::cout << "  " << _("Network solution rate") << " | " << netsolps << " Sol/s" << std::endl;
179     if (mining && miningTimer.running()) {
180         std::cout << "    " << _("Local solution rate") << " | " << strprintf("%.4f Sol/s", localsolps) << std::endl;
181         lines++;
182     }
183     std::cout << std::endl;
184
185     return lines;
186 }
187
188 int printMiningStatus(bool mining)
189 {
190 #ifdef ENABLE_MINING
191     // Number of lines that are always displayed
192     int lines = 1;
193
194     if (mining) {
195         int nThreads = GetArg("-genproclimit", 1);
196         if (nThreads < 0) {
197             // In regtest threads defaults to 1
198             if (Params().DefaultMinerThreads())
199                 nThreads = Params().DefaultMinerThreads();
200             else
201                 nThreads = boost::thread::hardware_concurrency();
202         }
203         if (miningTimer.running()) {
204             std::cout << strprintf(_("You are mining with the %s solver on %d threads."),
205                                    GetArg("-equihashsolver", "default"), nThreads) << std::endl;
206         } else {
207             bool fvNodesEmpty;
208             {
209                 LOCK(cs_vNodes);
210                 fvNodesEmpty = vNodes.empty();
211             }
212             if (fvNodesEmpty) {
213                 std::cout << _("Mining is paused while waiting for connections.") << std::endl;
214             } else if (IsInitialBlockDownload()) {
215                 std::cout << _("Mining is paused while downloading blocks.") << std::endl;
216             } else {
217                 std::cout << _("Mining is paused (a JoinSplit may be in progress).") << std::endl;
218             }
219         }
220         lines++;
221     } else {
222         std::cout << _("You are currently not mining.") << std::endl;
223         std::cout << _("To enable mining, add 'gen=1' to your zcash.conf and restart.") << std::endl;
224         lines += 2;
225     }
226     std::cout << std::endl;
227
228     return lines;
229 #else // ENABLE_MINING
230     return 0;
231 #endif // !ENABLE_MINING
232 }
233
234 int printMetrics(size_t cols, bool mining)
235 {
236     // Number of lines that are always displayed
237     int lines = 3;
238
239     // Calculate uptime
240     int64_t uptime = GetUptime();
241     int days = uptime / (24 * 60 * 60);
242     int hours = (uptime - (days * 24 * 60 * 60)) / (60 * 60);
243     int minutes = (uptime - (((days * 24) + hours) * 60 * 60)) / 60;
244     int seconds = uptime - (((((days * 24) + hours) * 60) + minutes) * 60);
245
246     // Display uptime
247     std::string duration;
248     if (days > 0) {
249         duration = strprintf(_("%d days, %d hours, %d minutes, %d seconds"), days, hours, minutes, seconds);
250     } else if (hours > 0) {
251         duration = strprintf(_("%d hours, %d minutes, %d seconds"), hours, minutes, seconds);
252     } else if (minutes > 0) {
253         duration = strprintf(_("%d minutes, %d seconds"), minutes, seconds);
254     } else {
255         duration = strprintf(_("%d seconds"), seconds);
256     }
257     std::string strDuration = strprintf(_("Since starting this node %s ago:"), duration);
258     std::cout << strDuration << std::endl;
259     lines += (strDuration.size() / cols);
260
261     int validatedCount = transactionsValidated.get();
262     if (validatedCount > 1) {
263       std::cout << "- " << strprintf(_("You have validated %d transactions!"), validatedCount) << std::endl;
264     } else if (validatedCount == 1) {
265       std::cout << "- " << _("You have validated a transaction!") << std::endl;
266     } else {
267       std::cout << "- " << _("You have validated no transactions.") << std::endl;
268     }
269
270     if (mining && loaded) {
271         std::cout << "- " << strprintf(_("You have completed %d Equihash solver runs."), ehSolverRuns.get()) << std::endl;
272         lines++;
273
274         int mined = 0;
275         int orphaned = 0;
276         CAmount immature {0};
277         CAmount mature {0};
278         {
279             LOCK2(cs_main, cs_metrics);
280             boost::strict_lock_ptr<std::list<uint256>> u = trackedBlocks.synchronize();
281             auto consensusParams = Params().GetConsensus();
282             auto tipHeight = chainActive.Height();
283
284             // Update orphans and calculate subsidies
285             std::list<uint256>::iterator it = u->begin();
286             while (it != u->end()) {
287                 auto hash = *it;
288                 if (mapBlockIndex.count(hash) > 0 &&
289                         chainActive.Contains(mapBlockIndex[hash])) {
290                     int height = mapBlockIndex[hash]->nHeight;
291                     CAmount subsidy = GetBlockSubsidy(height, consensusParams);
292                     if ((height > 0) && (height <= consensusParams.GetLastFoundersRewardBlockHeight())) {
293                         subsidy -= subsidy/5;
294                     }
295                     if (std::max(0, COINBASE_MATURITY - (tipHeight - height)) > 0) {
296                         immature += subsidy;
297                     } else {
298                         mature += subsidy;
299                     }
300                     it++;
301                 } else {
302                     it = u->erase(it);
303                 }
304             }
305
306             mined = minedBlocks.get();
307             orphaned = mined - u->size();
308         }
309
310         if (mined > 0) {
311             std::string units = Params().CurrencyUnits();
312             std::cout << "- " << strprintf(_("You have mined %d blocks!"), mined) << std::endl;
313             std::cout << "  "
314                       << strprintf(_("Orphaned: %d blocks, Immature: %u %s, Mature: %u %s"),
315                                      orphaned,
316                                      FormatMoney(immature), units,
317                                      FormatMoney(mature), units)
318                       << std::endl;
319             lines += 2;
320         }
321     }
322     std::cout << std::endl;
323
324     return lines;
325 }
326
327 int printMessageBox(size_t cols)
328 {
329     boost::strict_lock_ptr<std::list<std::string>> u = messageBox.synchronize();
330
331     if (u->size() == 0) {
332         return 0;
333     }
334
335     int lines = 2 + u->size();
336     std::cout << _("Messages:") << std::endl;
337     for (auto it = u->cbegin(); it != u->cend(); ++it) {
338         auto msg = FormatParagraph(*it, cols, 2);
339         std::cout << "- " << msg << std::endl;
340         // Handle newlines and wrapped lines
341         size_t i = 0;
342         size_t j = 0;
343         while (j < msg.size()) {
344             i = msg.find('\n', j);
345             if (i == std::string::npos) {
346                 i = msg.size();
347             } else {
348                 // Newline
349                 lines++;
350             }
351             j = i + 1;
352         }
353     }
354     std::cout << std::endl;
355     return lines;
356 }
357
358 int printInitMessage()
359 {
360     if (loaded) {
361         return 0;
362     }
363
364     std::string msg = *initMessage;
365     std::cout << _("Init message:") << " " << msg << std::endl;
366     std::cout << std::endl;
367
368     if (msg == _("Done loading")) {
369         loaded = true;
370     }
371
372     return 2;
373 }
374
375 void ThreadShowMetricsScreen()
376 {
377     // Make this thread recognisable as the metrics screen thread
378     RenameThread("zcash-metrics-screen");
379
380     // Determine whether we should render a persistent UI or rolling metrics
381     bool isTTY = isatty(STDOUT_FILENO);
382     bool isScreen = GetBoolArg("-metricsui", isTTY);
383     int64_t nRefresh = GetArg("-metricsrefreshtime", isTTY ? 1 : 600);
384
385     if (isScreen) {
386         // Clear screen
387         std::cout << "\e[2J";
388
389         // Print art
390         std::cout << METRICS_ART << std::endl;
391         std::cout << std::endl;
392
393         // Thank you text
394         std::cout << _("Thank you for running a Zcash node!") << std::endl;
395         std::cout << _("You're helping to strengthen the network and contributing to a social good :)") << std::endl;
396
397         // Privacy notice text
398         std::cout << PrivacyInfo();
399         std::cout << std::endl;
400     }
401
402     while (true) {
403         // Number of lines that are always displayed
404         int lines = 1;
405         int cols = 80;
406
407         // Get current window size
408         if (isTTY) {
409             struct winsize w;
410             w.ws_col = 0;
411             if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &w) != -1 && w.ws_col != 0) {
412                 cols = w.ws_col;
413             }
414         }
415
416         if (isScreen) {
417             // Erase below current position
418             std::cout << "\e[J";
419         }
420
421         // Miner status
422 #ifdef ENABLE_MINING
423         bool mining = GetBoolArg("-gen", false);
424 #else
425         bool mining = false;
426 #endif
427
428         if (loaded) {
429             lines += printStats(mining);
430             lines += printMiningStatus(mining);
431         }
432         lines += printMetrics(cols, mining);
433         lines += printMessageBox(cols);
434         lines += printInitMessage();
435
436         if (isScreen) {
437             // Explain how to exit
438             std::cout << "[" << _("Press Ctrl+C to exit") << "] [" << _("Set 'showmetrics=0' to hide") << "]" << std::endl;
439         } else {
440             // Print delineator
441             std::cout << "----------------------------------------" << std::endl;
442         }
443
444         *nNextRefresh = GetTime() + nRefresh;
445         while (GetTime() < *nNextRefresh) {
446             boost::this_thread::interruption_point();
447             MilliSleep(200);
448         }
449
450         if (isScreen) {
451             // Return to the top of the updating section
452             std::cout << "\e[" << lines << "A";
453         }
454     }
455 }
This page took 0.049067 seconds and 4 git commands to generate.