]>
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" | |
d793f94b | 8 | #include "main.h" |
4ace963c | 9 | #include "ui_interface.h" |
a6df7ab5 JG |
10 | #include "util.h" |
11 | #include "utiltime.h" | |
d793f94b | 12 | #include "utilmoneystr.h" |
a6df7ab5 JG |
13 | |
14 | #include <boost/thread.hpp> | |
4ace963c JG |
15 | #include <boost/thread/synchronized_value.hpp> |
16 | #include <string> | |
db853f8a | 17 | #include <sys/ioctl.h> |
1da44b34 | 18 | #include <unistd.h> |
a6df7ab5 | 19 | |
cb709831 JG |
20 | CCriticalSection cs_metrics; |
21 | ||
000499ae | 22 | boost::synchronized_value<int64_t> nNodeStartTime; |
a6df7ab5 JG |
23 | AtomicCounter transactionsValidated; |
24 | AtomicCounter ehSolverRuns; | |
e7d59bbc | 25 | AtomicCounter solutionTargetChecks; |
a6df7ab5 JG |
26 | AtomicCounter minedBlocks; |
27 | ||
d793f94b JG |
28 | boost::synchronized_value<std::list<uint256>> trackedBlocks; |
29 | ||
4ace963c JG |
30 | boost::synchronized_value<std::list<std::string>> messageBox; |
31 | boost::synchronized_value<std::string> initMessage; | |
32 | bool loaded = false; | |
33 | ||
eb5b582e JG |
34 | extern int64_t GetNetworkHashPS(int lookup, int height); |
35 | ||
d793f94b JG |
36 | void TrackMinedBlock(uint256 hash) |
37 | { | |
cb709831 | 38 | LOCK(cs_metrics); |
d793f94b JG |
39 | minedBlocks.increment(); |
40 | trackedBlocks->push_back(hash); | |
41 | } | |
42 | ||
000499ae JG |
43 | void MarkStartTime() |
44 | { | |
45 | *nNodeStartTime = GetTime(); | |
46 | } | |
47 | ||
48 | int64_t GetUptime() | |
49 | { | |
50 | return GetTime() - *nNodeStartTime; | |
51 | } | |
52 | ||
53 | double GetLocalSolPS_INTERNAL(int64_t uptime) | |
54 | { | |
55 | return uptime > 0 ? (double)solutionTargetChecks.get() / uptime : 0; | |
56 | } | |
57 | ||
58 | double GetLocalSolPS() | |
59 | { | |
60 | return GetLocalSolPS_INTERNAL(GetUptime()); | |
61 | } | |
62 | ||
4ace963c JG |
63 | static bool metrics_ThreadSafeMessageBox(const std::string& message, |
64 | const std::string& caption, | |
65 | unsigned int style) | |
66 | { | |
67 | std::string strCaption; | |
68 | // Check for usage of predefined caption | |
69 | switch (style) { | |
70 | case CClientUIInterface::MSG_ERROR: | |
71 | strCaption += _("Error"); | |
72 | break; | |
73 | case CClientUIInterface::MSG_WARNING: | |
74 | strCaption += _("Warning"); | |
75 | break; | |
76 | case CClientUIInterface::MSG_INFORMATION: | |
77 | strCaption += _("Information"); | |
78 | break; | |
79 | default: | |
80 | strCaption += caption; // Use supplied caption (can be empty) | |
81 | } | |
82 | ||
83 | boost::strict_lock_ptr<std::list<std::string>> u = messageBox.synchronize(); | |
84 | u->push_back(strCaption + ": " + message); | |
85 | if (u->size() > 5) { | |
86 | u->pop_back(); | |
87 | } | |
88 | } | |
89 | ||
90 | static void metrics_InitMessage(const std::string& message) | |
91 | { | |
92 | *initMessage = message; | |
93 | } | |
94 | ||
95 | void ConnectMetricsScreen() | |
96 | { | |
97 | uiInterface.ThreadSafeMessageBox.disconnect_all_slots(); | |
98 | uiInterface.ThreadSafeMessageBox.connect(metrics_ThreadSafeMessageBox); | |
99 | uiInterface.InitMessage.disconnect_all_slots(); | |
100 | uiInterface.InitMessage.connect(metrics_InitMessage); | |
101 | } | |
102 | ||
eb5b582e JG |
103 | int printNetworkStats() |
104 | { | |
105 | LOCK2(cs_main, cs_vNodes); | |
106 | ||
107 | std::cout << " " << _("Block height") << " | " << chainActive.Height() << std::endl; | |
108 | std::cout << " " << _("Network solution rate") << " | " << GetNetworkHashPS(120, -1) << " Sol/s" << std::endl; | |
109 | std::cout << " " << _("Connections") << " | " << vNodes.size() << std::endl; | |
110 | std::cout << std::endl; | |
111 | ||
112 | return 4; | |
113 | } | |
114 | ||
848c89cd | 115 | int printMiningStatus(bool mining) |
d30273f9 | 116 | { |
848c89cd JG |
117 | // Number of lines that are always displayed |
118 | int lines = 1; | |
119 | ||
d30273f9 JG |
120 | if (mining) { |
121 | int nThreads = GetArg("-genproclimit", 1); | |
122 | if (nThreads < 0) { | |
123 | // In regtest threads defaults to 1 | |
124 | if (Params().DefaultMinerThreads()) | |
125 | nThreads = Params().DefaultMinerThreads(); | |
126 | else | |
127 | nThreads = boost::thread::hardware_concurrency(); | |
128 | } | |
eb5b582e JG |
129 | std::cout << strprintf(_("You are mining with the %s solver on %d threads."), |
130 | GetArg("-equihashsolver", "default"), nThreads) << std::endl; | |
848c89cd | 131 | lines++; |
d30273f9 | 132 | } else { |
0ddd6d1c JG |
133 | std::cout << _("You are currently not mining.") << std::endl; |
134 | std::cout << _("To enable mining, add 'gen=1' to your zcash.conf and restart.") << std::endl; | |
848c89cd | 135 | lines += 2; |
d30273f9 JG |
136 | } |
137 | std::cout << std::endl; | |
848c89cd JG |
138 | |
139 | return lines; | |
d30273f9 JG |
140 | } |
141 | ||
000499ae | 142 | int printMetrics(size_t cols, bool mining) |
d30273f9 JG |
143 | { |
144 | // Number of lines that are always displayed | |
145 | int lines = 3; | |
146 | ||
147 | // Calculate uptime | |
000499ae | 148 | int64_t uptime = GetUptime(); |
d30273f9 JG |
149 | int days = uptime / (24 * 60 * 60); |
150 | int hours = (uptime - (days * 24 * 60 * 60)) / (60 * 60); | |
151 | int minutes = (uptime - (((days * 24) + hours) * 60 * 60)) / 60; | |
152 | int seconds = uptime - (((((days * 24) + hours) * 60) + minutes) * 60); | |
153 | ||
154 | // Display uptime | |
0ddd6d1c | 155 | std::string duration; |
d30273f9 | 156 | if (days > 0) { |
0ddd6d1c JG |
157 | duration = strprintf(_("%d days, %d hours, %d minutes, %d seconds"), days, hours, minutes, seconds); |
158 | } else if (hours > 0) { | |
159 | duration = strprintf(_("%d hours, %d minutes, %d seconds"), hours, minutes, seconds); | |
160 | } else if (minutes > 0) { | |
161 | duration = strprintf(_("%d minutes, %d seconds"), minutes, seconds); | |
162 | } else { | |
163 | duration = strprintf(_("%d seconds"), seconds); | |
d30273f9 | 164 | } |
db853f8a JG |
165 | std::string strDuration = strprintf(_("Since starting this node %s ago:"), duration); |
166 | std::cout << strDuration << std::endl; | |
167 | lines += (strDuration.size() / cols); | |
d30273f9 | 168 | |
1c8d5c40 | 169 | int validatedCount = transactionsValidated.get(); |
ba6fc72b S |
170 | if (validatedCount > 1) { |
171 | std::cout << "- " << strprintf(_("You have validated %d transactions!"), validatedCount) << std::endl; | |
172 | } else if (validatedCount == 1) { | |
1c8d5c40 | 173 | std::cout << "- " << _("You have validated a transaction!") << std::endl; |
ba6fc72b | 174 | } else { |
1c8d5c40 | 175 | std::cout << "- " << _("You have validated no transactions.") << std::endl; |
ba6fc72b | 176 | } |
d30273f9 | 177 | |
24f10266 | 178 | if (mining && loaded) { |
000499ae | 179 | double solps = GetLocalSolPS_INTERNAL(uptime); |
199b3aaf JG |
180 | std::string strSolps = strprintf("%.4f Sol/s", solps); |
181 | std::cout << "- " << strprintf(_("You have contributed %s on average to the network solution rate."), strSolps) << std::endl; | |
0ddd6d1c | 182 | std::cout << "- " << strprintf(_("You have completed %d Equihash solver runs."), ehSolverRuns.get()) << std::endl; |
e7d59bbc | 183 | lines += 2; |
d30273f9 | 184 | |
cb709831 JG |
185 | int mined = 0; |
186 | int orphaned = 0; | |
22ee0efe JG |
187 | CAmount immature {0}; |
188 | CAmount mature {0}; | |
cb709831 JG |
189 | { |
190 | LOCK2(cs_main, cs_metrics); | |
d793f94b JG |
191 | boost::strict_lock_ptr<std::list<uint256>> u = trackedBlocks.synchronize(); |
192 | auto consensusParams = Params().GetConsensus(); | |
193 | auto tipHeight = chainActive.Height(); | |
d793f94b JG |
194 | |
195 | // Update orphans and calculate subsidies | |
73a43918 JG |
196 | std::list<uint256>::iterator it = u->begin(); |
197 | while (it != u->end()) { | |
d793f94b JG |
198 | auto hash = *it; |
199 | if (mapBlockIndex.count(hash) > 0 && | |
200 | chainActive.Contains(mapBlockIndex[hash])) { | |
201 | int height = mapBlockIndex[hash]->nHeight; | |
202 | CAmount subsidy = GetBlockSubsidy(height, consensusParams); | |
203 | if ((height > 0) && (height <= consensusParams.GetLastFoundersRewardBlockHeight())) { | |
204 | subsidy -= subsidy/5; | |
205 | } | |
206 | if (std::max(0, COINBASE_MATURITY - (tipHeight - height)) > 0) { | |
207 | immature += subsidy; | |
208 | } else { | |
209 | mature += subsidy; | |
210 | } | |
73a43918 | 211 | it++; |
d793f94b JG |
212 | } else { |
213 | it = u->erase(it); | |
214 | } | |
215 | } | |
d793f94b | 216 | |
cb709831 JG |
217 | mined = minedBlocks.get(); |
218 | orphaned = mined - u->size(); | |
219 | } | |
220 | ||
221 | if (mined > 0) { | |
22ee0efe | 222 | std::string units = Params().CurrencyUnits(); |
0ddd6d1c | 223 | std::cout << "- " << strprintf(_("You have mined %d blocks!"), mined) << std::endl; |
d793f94b JG |
224 | std::cout << " " |
225 | << strprintf(_("Orphaned: %d blocks, Immature: %u %s, Mature: %u %s"), | |
226 | orphaned, | |
227 | FormatMoney(immature), units, | |
228 | FormatMoney(mature), units) | |
229 | << std::endl; | |
230 | lines += 2; | |
d30273f9 JG |
231 | } |
232 | } | |
233 | std::cout << std::endl; | |
234 | ||
235 | return lines; | |
236 | } | |
237 | ||
db853f8a | 238 | int printMessageBox(size_t cols) |
4ace963c JG |
239 | { |
240 | boost::strict_lock_ptr<std::list<std::string>> u = messageBox.synchronize(); | |
241 | ||
242 | if (u->size() == 0) { | |
243 | return 0; | |
244 | } | |
245 | ||
db853f8a | 246 | int lines = 2 + u->size(); |
0ddd6d1c | 247 | std::cout << _("Messages:") << std::endl; |
4ace963c JG |
248 | for (auto it = u->cbegin(); it != u->cend(); ++it) { |
249 | std::cout << *it << std::endl; | |
db853f8a JG |
250 | // Handle wrapped lines |
251 | lines += (it->size() / cols); | |
4ace963c | 252 | } |
d30273f9 | 253 | std::cout << std::endl; |
db853f8a | 254 | return lines; |
4ace963c JG |
255 | } |
256 | ||
257 | int printInitMessage() | |
258 | { | |
259 | if (loaded) { | |
260 | return 0; | |
261 | } | |
262 | ||
263 | std::string msg = *initMessage; | |
0ddd6d1c | 264 | std::cout << _("Init message:") << " " << msg << std::endl; |
d30273f9 | 265 | std::cout << std::endl; |
4ace963c | 266 | |
0ddd6d1c | 267 | if (msg == _("Done loading")) { |
4ace963c JG |
268 | loaded = true; |
269 | } | |
270 | ||
271 | return 2; | |
272 | } | |
273 | ||
a6df7ab5 JG |
274 | void ThreadShowMetricsScreen() |
275 | { | |
276 | // Make this thread recognisable as the metrics screen thread | |
277 | RenameThread("zcash-metrics-screen"); | |
278 | ||
3c024773 | 279 | // Determine whether we should render a persistent UI or rolling metrics |
83ccbf6b JG |
280 | bool isTTY = isatty(STDOUT_FILENO); |
281 | bool isScreen = GetBoolArg("-metricsui", isTTY); | |
282 | int64_t nRefresh = GetArg("-metricsrefreshtime", isTTY ? 1 : 600); | |
a6df7ab5 | 283 | |
3c024773 JG |
284 | if (isScreen) { |
285 | // Clear screen | |
286 | std::cout << "\e[2J"; | |
a6df7ab5 | 287 | |
3c024773 JG |
288 | // Print art |
289 | std::cout << METRICS_ART << std::endl; | |
290 | std::cout << std::endl; | |
291 | ||
292 | // Thank you text | |
293 | std::cout << _("Thank you for running a Zcash node!") << std::endl; | |
294 | std::cout << _("You're helping to strengthen the network and contributing to a social good :)") << std::endl; | |
295 | std::cout << std::endl; | |
296 | } | |
a6df7ab5 | 297 | |
a6df7ab5 | 298 | while (true) { |
4ace963c | 299 | // Number of lines that are always displayed |
d30273f9 | 300 | int lines = 1; |
c0876672 | 301 | int cols = 80; |
a6df7ab5 | 302 | |
db853f8a | 303 | // Get current window size |
83ccbf6b | 304 | if (isTTY) { |
1da44b34 JT |
305 | struct winsize w; |
306 | w.ws_col = 0; | |
307 | if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &w) != -1 && w.ws_col != 0) { | |
308 | cols = w.ws_col; | |
309 | } | |
c0876672 | 310 | } |
db853f8a | 311 | |
3c024773 JG |
312 | if (isScreen) { |
313 | // Erase below current position | |
314 | std::cout << "\e[J"; | |
315 | } | |
a6df7ab5 | 316 | |
848c89cd JG |
317 | // Miner status |
318 | bool mining = GetBoolArg("-gen", false); | |
319 | ||
eb5b582e JG |
320 | if (loaded) { |
321 | lines += printNetworkStats(); | |
322 | } | |
848c89cd | 323 | lines += printMiningStatus(mining); |
000499ae | 324 | lines += printMetrics(cols, mining); |
c0876672 | 325 | lines += printMessageBox(cols); |
4ace963c JG |
326 | lines += printInitMessage(); |
327 | ||
3c024773 JG |
328 | if (isScreen) { |
329 | // Explain how to exit | |
330 | std::cout << "[" << _("Press Ctrl+C to exit") << "] [" << _("Set 'showmetrics=0' to hide") << "]" << std::endl; | |
331 | } else { | |
332 | // Print delineator | |
e2752e1e | 333 | std::cout << "----------------------------------------" << std::endl; |
3c024773 | 334 | } |
a6df7ab5 | 335 | |
83ccbf6b JG |
336 | int64_t nWaitEnd = GetTime() + nRefresh; |
337 | while (GetTime() < nWaitEnd) { | |
338 | boost::this_thread::interruption_point(); | |
339 | MilliSleep(200); | |
340 | } | |
a6df7ab5 | 341 | |
3c024773 JG |
342 | if (isScreen) { |
343 | // Return to the top of the updating section | |
344 | std::cout << "\e[" << lines << "A"; | |
345 | } | |
a6df7ab5 JG |
346 | } |
347 | } |