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