Line data Source code
1 : #include "watch.h"
2 : #include "generated/watch_seccomp.h"
3 :
4 : #include "../../fd_bootinfo.h"
5 :
6 : #include "../../../../discof/backup/fd_snapmk_tile.h"
7 : #include "../../../../discof/restore/fd_snapct_tile.h"
8 : #include "../../../../discof/gossip/fd_gossip_tile.h"
9 : #include "../../../../disco/events/fd_event_client.h"
10 : #include "../../../../disco/metrics/fd_metrics.h"
11 : #include "../../../../disco/node_info/fd_node_info.h"
12 : #include "../../../../disco/genesis/fd_genesis_cluster.h"
13 : #include "../../../../util/pod/fd_pod.h"
14 : #include "../../../../util/tile/fd_tile.h"
15 :
16 : #include <errno.h>
17 : #include <sys/select.h>
18 : #include <time.h>
19 : #include <unistd.h>
20 : #include <sys/resource.h>
21 : #include <linux/capability.h>
22 :
23 : #include "../../../platform/fd_sys_util.h"
24 :
25 : void
26 : watch_cmd_perm( args_t * args FD_PARAM_UNUSED,
27 : fd_cap_chk_t * chk,
28 0 : config_t const * config ) {
29 0 : ulong mlock_limit = fd_topo_mlock( &config->topo );
30 :
31 0 : fd_cap_chk_raise_rlimit( chk, "watch", RLIMIT_MEMLOCK, mlock_limit, "call `rlimit(2)` to increase `RLIMIT_MEMLOCK` so all memory can be locked with `mlock(2)`" );
32 :
33 0 : if( fd_sandbox_requires_cap_sys_admin( config->uid, config->gid ) )
34 0 : fd_cap_chk_cap( chk, "watch", CAP_SYS_ADMIN, "call `unshare(2)` with `CLONE_NEWUSER` to sandbox the process in a user namespace" );
35 0 : if( FD_LIKELY( getuid() != config->uid ) )
36 0 : fd_cap_chk_cap( chk, "watch", CAP_SETUID, "call `setresuid(2)` to switch uid to the sanbox user" );
37 0 : if( FD_LIKELY( getgid() != config->gid ) )
38 0 : fd_cap_chk_cap( chk, "watch", CAP_SETGID, "call `setresgid(2)` to switch gid to the sandbox user" );
39 0 : }
40 :
41 :
42 : static ulong lines_printed;
43 : static int ended_on_newline = 1;
44 :
45 : static char frame_buf[ 65536UL ];
46 : static ulong frame_len;
47 :
48 : /* Show all category detail rows, not just the primary row of each.
49 : Set from --full. */
50 : static int watch_full = 0;
51 :
52 : static void
53 0 : flush_frame( void ) {
54 0 : ulong written = 0UL;
55 0 : while( written<frame_len ) {
56 0 : long w = write( STDOUT_FILENO, frame_buf+written, frame_len-written );
57 0 : if( FD_UNLIKELY( -1==w && errno==EAGAIN ) ) continue;
58 0 : else if( FD_UNLIKELY( -1==w ) ) FD_LOG_ERR(( "write() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
59 0 : else if( FD_UNLIKELY( 0==w ) ) break;
60 0 : written += (ulong)w;
61 0 : }
62 0 : frame_len = 0UL;
63 0 : }
64 :
65 : static int
66 0 : drain( int fd ) {
67 0 : int needs_reprint = 0;
68 :
69 0 : while( 1 ) {
70 0 : uchar buf[ 16384UL ];
71 0 : long result = read( fd, buf, sizeof(buf) );
72 0 : if( FD_UNLIKELY( -1==result && errno==EAGAIN ) ) break;
73 0 : else if( FD_UNLIKELY( -1==result ) ) FD_LOG_ERR(( "read() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
74 0 : else if( FD_UNLIKELY( !result ) ) fd_sys_util_exit_group( 0 ); /* writer gone: validator exited */
75 :
76 0 : if( FD_LIKELY( !needs_reprint ) ) {
77 : /* Buffer the erase sequence and first log chunk together so the
78 : terminal never renders a blank frame between erase and content. */
79 0 : frame_len = 0UL;
80 0 : if( FD_UNLIKELY( !ended_on_newline ) ) {
81 0 : FD_TEST( fd_cstr_printf_check( frame_buf, sizeof(frame_buf), &frame_len, "\033[%luA\033[%luM\033[1A\033[0J", lines_printed, lines_printed ) );
82 0 : } else {
83 0 : FD_TEST( fd_cstr_printf_check( frame_buf, sizeof(frame_buf), &frame_len, "\033[%luA\033[%luM\033[0J", lines_printed, lines_printed ) );
84 0 : }
85 0 : }
86 0 : FD_TEST( frame_len+(ulong)result<=sizeof(frame_buf) );
87 0 : fd_memcpy( frame_buf+frame_len, buf, (ulong)result );
88 0 : frame_len += (ulong)result;
89 0 : flush_frame();
90 0 : needs_reprint = 1;
91 :
92 0 : ended_on_newline = buf[ (ulong)result-1UL ]=='\n';
93 0 : }
94 :
95 0 : return needs_reprint;
96 0 : }
97 :
98 : static char *
99 : fmt_bytes( char * buf,
100 : ulong buf_sz,
101 0 : long bytes ) {
102 0 : char * tmp = fd_alloca_check( 1UL, buf_sz );
103 0 : if( FD_LIKELY( 8L*bytes<1000L ) ) FD_TEST( fd_cstr_printf_check( tmp, buf_sz, NULL, "%ld bit/s", 8L*bytes ) );
104 0 : else if( FD_LIKELY( 8L*bytes<1000000L ) ) FD_TEST( fd_cstr_printf_check( tmp, buf_sz, NULL, "%.1f Kbit/s", (double)(8L*bytes)/1000.0 ) );
105 0 : else if( FD_LIKELY( 8L*bytes<1000000000L ) ) FD_TEST( fd_cstr_printf_check( tmp, buf_sz, NULL, "%.1f Mbit/s", (double)(8L*bytes)/1000000.0 ) );
106 0 : else FD_TEST( fd_cstr_printf_check( tmp, buf_sz, NULL, "%.1f Gbit/s", (double)(8L*bytes)/1000000000.0 ) );
107 :
108 0 : FD_TEST( fd_cstr_printf_check( buf, buf_sz, NULL, "%12s", tmp ) );
109 0 : return buf;
110 0 : }
111 :
112 : static char *
113 : fmt_count( char * buf,
114 : ulong buf_sz,
115 0 : ulong count ) {
116 0 : char * tmp = fd_alloca_check( 1UL, buf_sz );
117 0 : if( FD_LIKELY( count<1000UL ) ) FD_TEST( fd_cstr_printf_check( tmp, buf_sz, NULL, "%lu", count ) );
118 0 : else if( FD_LIKELY( count<1000000UL ) ) FD_TEST( fd_cstr_printf_check( tmp, buf_sz, NULL, "%.1f K", (double)count/1000.0 ) );
119 0 : else if( FD_LIKELY( count<1000000000UL ) ) FD_TEST( fd_cstr_printf_check( tmp, buf_sz, NULL, "%.1f M", (double)count/1000000.0 ) );
120 0 : else FD_TEST( fd_cstr_printf_check( tmp, buf_sz, NULL, "%.1f G", (double)count/1000000000.0 ) );
121 :
122 0 : FD_TEST( fd_cstr_printf_check( buf, buf_sz, NULL, "%7s", tmp ) );
123 0 : return buf;
124 0 : }
125 :
126 : static char *
127 : fmt_countf( char * buf,
128 : ulong buf_sz,
129 0 : double count ) {
130 0 : char * tmp = fd_alloca_check( 1UL, buf_sz );
131 0 : if( FD_LIKELY( count<1000UL ) ) FD_TEST( fd_cstr_printf_check( tmp, buf_sz, NULL, "%.1f", count ) );
132 0 : else if( FD_LIKELY( count<1000000UL ) ) FD_TEST( fd_cstr_printf_check( tmp, buf_sz, NULL, "%.1f K", (double)count/1000.0 ) );
133 0 : else if( FD_LIKELY( count<1000000000UL ) ) FD_TEST( fd_cstr_printf_check( tmp, buf_sz, NULL, "%.1f M", (double)count/1000000.0 ) );
134 0 : else memcpy( tmp, "-", 2UL );
135 :
136 0 : FD_TEST( fd_cstr_printf_check( buf, buf_sz, NULL, "%7s", tmp ) );
137 0 : return buf;
138 0 : }
139 :
140 : static char *
141 : fmt_count_tight( char * buf,
142 : ulong buf_sz,
143 0 : ulong count ) {
144 0 : char * tmp = fd_alloca_check( 1UL, buf_sz );
145 0 : if( count<1000UL ) FD_TEST( fd_cstr_printf_check( tmp, buf_sz, NULL, "%lu", count ) );
146 0 : else if( count<1000000UL ) FD_TEST( fd_cstr_printf_check( tmp, buf_sz, NULL, "%.1fK", (double)count/1e3 ) );
147 0 : else if( count<1000000000UL ) FD_TEST( fd_cstr_printf_check( tmp, buf_sz, NULL, "%.1fM", (double)count/1e6 ) );
148 0 : else FD_TEST( fd_cstr_printf_check( tmp, buf_sz, NULL, "%.1fG", (double)count/1e9 ) );
149 0 : FD_TEST( fd_cstr_printf_check( buf, buf_sz, NULL, "%6s", tmp ) );
150 0 : return buf;
151 0 : }
152 :
153 : static char *
154 : fmt_countf_tight( char * buf,
155 : ulong buf_sz,
156 0 : double count ) {
157 0 : char * tmp = fd_alloca_check( 1UL, buf_sz );
158 0 : if( count<1000.0 ) FD_TEST( fd_cstr_printf_check( tmp, buf_sz, NULL, "%.1f", count ) );
159 0 : else if( count<1000000.0 ) FD_TEST( fd_cstr_printf_check( tmp, buf_sz, NULL, "%.1fK", count/1e3 ) );
160 0 : else if( count<1000000000.0 ) FD_TEST( fd_cstr_printf_check( tmp, buf_sz, NULL, "%.1fM", count/1e6 ) );
161 0 : else FD_TEST( fd_cstr_printf_check( tmp, buf_sz, NULL, "%.1fG", count/1e9 ) );
162 0 : FD_TEST( fd_cstr_printf_check( buf, buf_sz, NULL, "%6s", tmp ) );
163 0 : return buf;
164 0 : }
165 :
166 : static long
167 : diff_link( config_t const * config,
168 : char const * link_name,
169 : ulong const * prev_link,
170 : ulong const * cur_link,
171 0 : ulong idx ) {
172 0 : long result = 0L;
173 :
174 0 : ulong seen[ (FD_TOPO_MAX_LINKS+63UL)/64UL ] = {0};
175 0 : ulong overall_polled_idx = 0UL;
176 0 : for( ulong i=0UL; i<config->topo.tile_cnt; i++ ) {
177 0 : fd_topo_tile_t const * tile = &config->topo.tiles[ i ];
178 0 : for( ulong j=0UL; j<config->topo.tiles[ i ].in_cnt; j++ ) {
179 0 : fd_topo_link_t const * link = &config->topo.links[ tile->in_link_id[ j ] ];
180 0 : if( FD_UNLIKELY( !tile->in_link_poll[ j ] ) ) continue;
181 :
182 0 : if( FD_LIKELY( !strcmp( link->name, link_name ) ) ) {
183 0 : ulong id = tile->in_link_id[ j ];
184 0 : if( FD_LIKELY( !( seen[ id>>6 ] & (1UL<<(id&63UL)) ) ) ) {
185 0 : seen[ id>>6 ] |= 1UL<<(id&63UL);
186 0 : result += (long)cur_link[ overall_polled_idx*8UL+idx ]-(long)prev_link[ overall_polled_idx*8UL+idx ];
187 0 : }
188 0 : }
189 :
190 0 : overall_polled_idx++;
191 0 : }
192 0 : }
193 0 : return result;
194 0 : }
195 :
196 : /* diff_tile returns the positive increase between the last and current
197 : value of an increasing counter. Clamps wraparound to zero (this
198 : happens to gauge-based counters, e.g. "number of events in an epoch") */
199 :
200 : static ulong
201 : diff_tile( config_t const * config,
202 : char const * tile_name,
203 : ulong const * prev_tile,
204 : ulong const * cur_tile,
205 0 : ulong idx ) {
206 0 : ulong result = 0UL;
207 0 : for( ulong i=0UL; i<config->topo.tile_cnt; i++ ) {
208 0 : fd_topo_tile_t const * tile = &config->topo.tiles[ i ];
209 0 : if( FD_UNLIKELY( strcmp( tile->name, tile_name ) ) ) continue;
210 0 : ulong prev = prev_tile[ i*FD_METRICS_TOTAL_SZ+idx ];
211 0 : ulong cur = cur_tile[ i*FD_METRICS_TOTAL_SZ+idx ];
212 0 : result += cur>=prev ? cur-prev : 0UL;
213 0 : }
214 0 : return result;
215 0 : }
216 :
217 : static ulong
218 0 : total_crds( ulong const * metrics ) {
219 0 : ulong sum = 0UL;
220 0 : for( ulong i=0UL; i<FD_METRICS_ENUM_CRDS_VALUE_TYPE_CNT; i++ ) {
221 0 : sum += metrics[ MIDX( GAUGE, GOSSIP, CRDS_USED_CONTACT_INFO_V1 )+i ];
222 0 : }
223 0 : return sum;
224 0 : }
225 :
226 : /* diff_tile_regime is diff_tile summed over a TileRegime group mask */
227 :
228 : static ulong
229 : diff_tile_regime( config_t const * config,
230 : char const * tile_name,
231 : ulong const * prev_tile,
232 : ulong const * cur_tile,
233 0 : ulong mask ) {
234 0 : ulong result = 0UL;
235 0 : for( ; mask; mask=fd_ulong_pop_lsb( mask ) ) {
236 0 : result += diff_tile( config, tile_name, prev_tile, cur_tile, MIDX( COUNTER, TILE, REGIME_DURATION_NANOS )+(ulong)fd_ulong_find_lsb( mask ) );
237 0 : }
238 0 : return result;
239 0 : }
240 :
241 : static ulong
242 0 : total_regime( ulong const * metrics ) {
243 0 : ulong sum = 0UL;
244 0 : for( ulong i=0UL; i<FD_METRICS_ENUM_TILE_REGIME_CNT; i++ ) {
245 0 : sum += metrics[ MIDX( COUNTER, TILE, REGIME_DURATION_NANOS )+i ];
246 0 : }
247 0 : return sum;
248 0 : }
249 :
250 : /* Bench */
251 : static ulong tps_sent_samples_idx = 0UL;
252 : static ulong tps_sent_samples[ 200UL ];
253 : /* Replay */
254 : static ulong cups_samples_idx = 0UL;
255 : static ulong cups_samples[ 100UL ];
256 : static ulong sps_samples_idx = 0UL;
257 : static ulong sps_samples[ 200UL ];
258 : static ulong tps_samples_idx = 0UL;
259 : static ulong tps_samples[ 200UL ];
260 : /* Snapshot */
261 : static ulong snapshot_rx_idx = 0UL;
262 : static ulong snapshot_rx_samples[ 100UL ];
263 : static long snap_cur_nanos = 0L;
264 : static long snap_prev_nanos = 0L;
265 : static ulong snap_dt_idx = 0UL;
266 : static long snap_dt_samples[ 200UL ];
267 :
268 : static long
269 0 : wallclock_monotonic( void ) {
270 0 : struct timespec ts;
271 0 : clock_gettime( CLOCK_MONOTONIC, &ts );
272 0 : return (long)ts.tv_sec*(long)1e9+ts.tv_nsec;
273 0 : }
274 :
275 : static double
276 0 : window_seconds( ulong num ) {
277 0 : ulong len = sizeof(snap_dt_samples)/sizeof(snap_dt_samples[0]);
278 0 : ulong cnt = fd_ulong_min( snap_dt_idx, fd_ulong_min( num, len ) );
279 0 : long sum = 0L;
280 0 : for( ulong i=1UL; i<=cnt; i++ ) sum += snap_dt_samples[ (snap_dt_idx-i)%len ];
281 0 : return (double)fd_long_max( sum, 1L )/1e9;
282 0 : }
283 :
284 : static ulong snapshot_acc_idx = 0UL;
285 : static ulong snapshot_acc_samples[ 100UL ];
286 : static ulong snapshot_wr_idx = 0UL;
287 : static ulong snapshot_wr_samples[ 100UL ];
288 : /* Backup */
289 : static ulong backup_acc_idx = 0UL;
290 : static ulong backup_acc_samples[ 100UL ];
291 : static ulong backup_read_idx = 0UL;
292 : static ulong backup_read_samples[ 100UL ];
293 : static ulong backup_comp_idx = 0UL;
294 : static ulong backup_comp_samples[ 100UL ];
295 : static ulong backup_upload_idx = 0UL;
296 : static ulong backup_upload_samples[ 100UL ];
297 : /* Event */
298 : static ulong events_sent_samples_idx = 0UL;
299 : static ulong events_sent_samples[ 100UL ];
300 : static ulong events_acked_samples_idx = 0UL;
301 : static ulong events_acked_samples[ 100UL ];
302 : static ulong event_bytes_written_samples_idx = 0UL;
303 : static ulong event_bytes_written_samples[ 100UL ];
304 : static ulong event_bytes_read_samples_idx = 0UL;
305 : static ulong event_bytes_read_samples[ 100UL ];
306 :
307 : /* Peer discovery follows logistic growth so the per-capita rate
308 : (dn/dt)/n is linear in n and hits zero at n=N_total. A streaming
309 : least-squares of per-capita rate vs count over 150ms buckets projects
310 : N_total.
311 :
312 : The displayed bar is n/N_total slew-limited to 60%/s (the raw
313 : projection can jump when the fit locks on) and capped at 90%. The
314 : final 10% is walked by the table-quiet gap against the tile's
315 : 500ms saturation deadline, so full coincides exactly with the
316 : tile's PEER_SATURATED condition, which is mirrored here. */
317 :
318 : static long gossip_bucket_nanos = 0L;
319 : static ulong gossip_bucket_peers = 0UL;
320 : static double gossip_sx = 0.0, gossip_sy = 0.0, gossip_sxx = 0.0, gossip_sxy = 0.0, gossip_sw = 0.0;
321 : static ulong gossip_peer_hwm = 0UL;
322 : static long gossip_peer_hwm_nanos = 0L;
323 : static long gossip_disp_nanos = 0L;
324 : static double gossip_disp_pct = 0.0;
325 : static double gossip_conv_pct = 0.0;
326 : static int gossip_saturated = 0;
327 :
328 0 : #define GOSSIP_SAT_QUIET_SECS (0.5) /* FD_GOSSIP_PEER_SAT_QUIET_NS */
329 0 : #define GOSSIP_BAR_SLEW (60.0) /* %/s */
330 0 : #define GOSSIP_BAR_FIT_CAP (90.0) /* % from the fit, rest from the quiet gap */
331 :
332 : static void
333 : sample_gossip( config_t const * config,
334 : ulong const * cur_tile,
335 0 : long now ) {
336 0 : ulong tile_idx = fd_topo_find_tile( &config->topo, "gossip", 0UL );
337 0 : if( FD_UNLIKELY( tile_idx==ULONG_MAX || gossip_saturated ) ) return;
338 :
339 0 : ulong const * t = &cur_tile[ tile_idx*FD_METRICS_TOTAL_SZ ];
340 0 : ulong peers = t[ MIDX( GAUGE, GOSSIP, CRDS_PEER_COUNT_STAKED ) ]+t[ MIDX( GAUGE, GOSSIP, CRDS_PEER_COUNT_UNSTAKED ) ];
341 :
342 0 : if( FD_UNLIKELY( peers>gossip_peer_hwm ) ) {
343 0 : gossip_peer_hwm = peers;
344 0 : gossip_peer_hwm_nanos = now;
345 0 : } else if( FD_UNLIKELY( peers>1UL && gossip_peer_hwm_nanos &&
346 0 : (double)(now-gossip_peer_hwm_nanos)/1e9>GOSSIP_SAT_QUIET_SECS ) ) {
347 : /* Mirrors the tile's PEER_SATURATED condition. */
348 0 : gossip_saturated = 1;
349 0 : gossip_conv_pct = 100.0;
350 0 : return;
351 0 : }
352 :
353 : /* Feed the regression once discovery is underway (the entrypoint
354 : alone doesn't count). */
355 0 : if( FD_LIKELY( peers>=10UL ) ) {
356 0 : if( FD_UNLIKELY( !gossip_bucket_nanos ) ) {
357 0 : gossip_bucket_nanos = now;
358 0 : gossip_bucket_peers = peers;
359 0 : } else if( FD_UNLIKELY( (double)(now-gossip_bucket_nanos)/1e9>=0.15 ) ) {
360 0 : double dt = (double)(now-gossip_bucket_nanos)/1e9;
361 0 : double x = 0.5*(double)(peers+gossip_bucket_peers);
362 0 : double y = ((double)peers-(double)gossip_bucket_peers)/dt/x; /* per-capita rate */
363 0 : gossip_sx += x; gossip_sy += y; gossip_sxx += x*x; gossip_sxy += x*y; gossip_sw += 1.0;
364 0 : gossip_bucket_nanos = now;
365 0 : gossip_bucket_peers = peers;
366 0 : }
367 0 : }
368 :
369 0 : double target = 0.0;
370 0 : if( FD_LIKELY( gossip_sw>=4.0 ) ) {
371 0 : double den = gossip_sw*gossip_sxx-gossip_sx*gossip_sx;
372 0 : if( FD_LIKELY( den>1e-9 ) ) {
373 0 : double slope = (gossip_sw*gossip_sxy-gossip_sx*gossip_sy)/den;
374 0 : double intercept = (gossip_sy-slope*gossip_sx)/gossip_sw;
375 0 : if( FD_LIKELY( slope<-1e-12 && intercept>0.0 ) ) {
376 0 : double total = -intercept/slope; /* n where per-capita rate hits zero */
377 0 : target = fd_double_if( total>0.0, 100.0*(double)peers/total, 0.0 );
378 0 : target = fd_double_if( target>GOSSIP_BAR_FIT_CAP, GOSSIP_BAR_FIT_CAP, target );
379 0 : }
380 0 : }
381 0 : }
382 :
383 0 : if( FD_UNLIKELY( !gossip_disp_nanos ) ) gossip_disp_nanos = now;
384 0 : double step = GOSSIP_BAR_SLEW*(double)(now-gossip_disp_nanos)/1e9;
385 0 : gossip_disp_nanos = now;
386 0 : if( FD_LIKELY( target>gossip_disp_pct ) )
387 0 : gossip_disp_pct = fd_double_if( gossip_disp_pct+step>target, target, gossip_disp_pct+step );
388 :
389 0 : double gap = 0.0;
390 0 : if( FD_LIKELY( peers>1UL && gossip_peer_hwm_nanos ) )
391 0 : gap = (double)(now-gossip_peer_hwm_nanos)/1e9/GOSSIP_SAT_QUIET_SECS;
392 :
393 0 : double pct = gossip_disp_pct+(100.0-GOSSIP_BAR_FIT_CAP)*gap;
394 0 : pct = fd_double_if( pct>99.0, 99.0, pct );
395 0 : gossip_conv_pct = fd_double_if( pct>gossip_conv_pct, pct, gossip_conv_pct );
396 0 : }
397 :
398 : /* Accounts */
399 : static ulong accdb_samples_idx = 0UL;
400 : static ulong accdb_acquired_samples[ 200UL ];
401 : static ulong accdb_writable_samples[ 200UL ];
402 : static ulong accdb_missed_samples [ 200UL ];
403 : static ulong accdb_evicted_samples [ 200UL ];
404 : static ulong accdb_waited_samples [ 200UL ];
405 : static ulong accdb_bytes_rd_samples[ 200UL ];
406 : static ulong accdb_bytes_wr_samples[ 200UL ];
407 : static ulong accdb_bytes_cp_samples[ 200UL ];
408 : static ulong accdb_bytes_pe_samples[ 200UL ];
409 : static ulong accdb_evicted_class_samples[ 8UL ][ 200UL ];
410 : static ulong accdb_preevicted_samples[ 200UL ];
411 : static ulong accdb_preevicted_class_samples[ 8UL ][ 200UL ];
412 : static ulong accdb_committed_new_class_samples[ 8UL ][ 200UL ];
413 : static ulong accdb_committed_overwrite_class_samples[ 8UL ][ 200UL ];
414 : /* Repair server */
415 : static ulong shreds_stored_samples_idx = 0UL;
416 : static ulong shreds_stored_sample[ 200UL ] ;
417 : static ulong rserve_rps_valid_samples_idx = 0UL;
418 : static ulong rserve_rps_valid_samples[ 100UL ];
419 : static ulong rserve_rps_invalid_samples_idx = 0UL;
420 : static ulong rserve_rps_invalid_samples[ 100UL ];
421 : static ulong rserve_rps_miss_samples[ 100UL ];
422 : static ulong rserve_rps_sigvfy_samples[ 100UL ];
423 : static ulong rserve_rps_stale_samples[ 100UL ];
424 : static ulong rserve_rps_other_rej_samples[ 100UL ];
425 :
426 0 : #define RESET "\033[0m"
427 : #define BOLD "\033[1m"
428 : #define DIM "\033[2m"
429 :
430 0 : #define RED "\033[31m"
431 0 : #define GREEN "\033[32m"
432 : #define ORANGE "\033[38;5;208m"
433 0 : #define YELLOW "\033[33m"
434 : #define BLUE "\033[34m"
435 : #define MAGENTA "\033[35m"
436 : #define CYAN "\033[36m"
437 :
438 : #define BGREEN "\033[92m"
439 : #define BYELLOW "\033[93m"
440 :
441 : /* Cluster accents matching the GUI palette. */
442 0 : #define CLUS_MAINNET "\033[38;5;43m" /* #1CE7C2 */
443 0 : #define CLUS_TESTNET "\033[38;5;178m" /* #E7B81C */
444 0 : #define CLUS_DEVNET "\033[38;5;166m" /* #E7601C */
445 0 : #define CLUS_PYTHNET "\033[38;5;129m" /* #9D1CE7 */
446 0 : #define CLUS_PYTHTEST "\033[38;5;162m" /* #E71C88 */
447 0 : #define CLUS_UNKNOWN "\033[38;5;245m" /* #898989 */
448 :
449 : #define CLEARLN "\033[K"
450 :
451 : /* Row grammar: an accent tinted glyph + bold colored section label
452 : in a fixed 12 char column, a dim divider, then dim lowercase keys
453 : with plain values so the data carries the visual weight. ROWH()
454 : opens a section row (pass the label pre-padded to 12 chars), ROWC
455 : opens a continuation row aligned under the divider, K() emits one
456 : key, U() a dim unit suffix. Icons are single cell glyphs from the
457 : geometric shapes block so every row aligns in every font. */
458 : #define ROWH( icon, color, label ) color icon " " BOLD label RESET DIM "│" RESET
459 : #define ROWC " " DIM "│" RESET
460 : #define K( key ) " " DIM key " " RESET
461 : #define U( unit ) DIM unit RESET
462 :
463 : /* Severity color for utilization style percentages: quiet when calm,
464 : loud only when it matters. */
465 : static char const *
466 0 : sev_color( double pct ) {
467 0 : if( FD_UNLIKELY( pct>=85.0 ) ) return RED;
468 0 : if( FD_UNLIKELY( pct>=50.0 ) ) return YELLOW;
469 0 : return "";
470 0 : }
471 :
472 : /* Unicode block progress bar, filled green with a dim tail. */
473 : static char *
474 : fmt_bar( char * buf,
475 : ulong buf_sz,
476 : double pct,
477 0 : ulong width ) {
478 0 : pct = fd_double_if( pct<0.0, 0.0, fd_double_if( pct>100.0, 100.0, pct ) );
479 0 : ulong filled = (ulong)( pct*(double)width/100.0+0.5 );
480 0 : ulong len = 0UL, l;
481 0 : FD_TEST( fd_cstr_printf_check( buf, buf_sz, &len, GREEN ) );
482 0 : for( ulong i=0UL; i<filled; i++ ) { FD_TEST( fd_cstr_printf_check( buf+len, buf_sz-len, &l, "█" ) ); len += l; }
483 0 : FD_TEST( fd_cstr_printf_check( buf+len, buf_sz-len, &l, DIM ) ); len += l;
484 0 : for( ulong i=filled; i<width; i++ ) { FD_TEST( fd_cstr_printf_check( buf+len, buf_sz-len, &l, "░" ) ); len += l; }
485 0 : FD_TEST( fd_cstr_printf_check( buf+len, buf_sz-len, &l, RESET ) );
486 0 : return buf;
487 0 : }
488 :
489 : #define BAR( pct, width ) (__extension__({ \
490 : fmt_bar( fd_alloca_check( 1UL, 256UL ), 256UL, pct, width ); \
491 : }))
492 :
493 0 : #define PRINT(...) do { \
494 0 : ulong _len; \
495 0 : FD_TEST( fd_cstr_printf_check( frame_buf+frame_len, sizeof(frame_buf)-frame_len, &_len, __VA_ARGS__ ) ); \
496 0 : frame_len += _len; \
497 0 : } while(0)
498 :
499 0 : #define SNAP_DT_NS() (fd_long_max( snap_cur_nanos-snap_prev_nanos, 1L ))
500 :
501 : #define DIFF_LINK_BYTES( link_name, metric_type, metric_subtype, metric ) (__extension__({ \
502 : long bytes = diff_link( config, link_name, prev_link, cur_link, MIDX( metric_type, metric_subtype, metric ) ); \
503 : fmt_bytes( fd_alloca_check( 1UL, 64UL ), 64UL, (long)((double)bytes*1e9/(double)SNAP_DT_NS()) ); \
504 : }))
505 :
506 0 : #define DIFF_BYTES( tile_name, metric_type, metric_subtype, metric ) (__extension__({ \
507 0 : ulong bytes = diff_tile( config, tile_name, prev_tile, cur_tile, MIDX( metric_type, metric_subtype, metric ) ); \
508 0 : fmt_bytes( fd_alloca_check( 1UL, 64UL ), 64UL, (long)((double)bytes*1e9/(double)SNAP_DT_NS()) ); \
509 0 : }))
510 :
511 0 : #define COUNT( count ) (__extension__({ \
512 0 : fmt_count( fd_alloca_check( 1UL, 64UL ), 64UL, count ); \
513 0 : }))
514 :
515 0 : #define COUNTF( count ) (__extension__({ \
516 0 : fmt_countf( fd_alloca_check( 1UL, 64UL ), 64UL, count ); \
517 0 : }))
518 :
519 0 : #define COUNT_T( count ) (__extension__({ \
520 0 : fmt_count_tight( fd_alloca_check( 1UL, 32UL ), 32UL, count ); \
521 0 : }))
522 :
523 0 : #define COUNTF_T( count ) (__extension__({ \
524 0 : fmt_countf_tight( fd_alloca_check( 1UL, 32UL ), 32UL, count ); \
525 0 : }))
526 :
527 : static int
528 : write_bench( config_t const * config,
529 : ulong const * cur_tile,
530 0 : ulong const * prev_tile ) {
531 0 : if( FD_UNLIKELY( fd_topo_find_tile( &config->topo, "benchs", 0UL )==ULONG_MAX ) ) return 0;
532 :
533 0 : ulong tps_sum = 0UL;
534 0 : ulong num_tps_samples = fd_ulong_min( tps_sent_samples_idx, sizeof(tps_sent_samples)/sizeof(tps_sent_samples[0]));
535 0 : for( ulong i=0UL; i<num_tps_samples; i++ ) tps_sum += tps_sent_samples[ i ];
536 0 : char * tps_str = COUNTF( (double)tps_sum/window_seconds( num_tps_samples ) );
537 :
538 0 : PRINT( ROWH( "◎", BGREEN, "bench " )
539 0 : K( "tps" ) "%s"
540 0 : K( "benchg" ), tps_str );
541 0 : for( ulong i=0UL; i<config->topo.tile_cnt; i++ ) {
542 0 : if( FD_LIKELY( strcmp( config->topo.tiles[ i ].name, "benchg" ) ) ) continue;
543 :
544 0 : ulong total_ticks = total_regime( &cur_tile[ i*FD_METRICS_TOTAL_SZ ] )-total_regime( &prev_tile[ i*FD_METRICS_TOTAL_SZ ] );
545 0 : double backp_pct = 100.0*(double)diff_tile_regime( config, "benchg", prev_tile, cur_tile, FD_METRICS_ENUM_TILE_REGIME_G_BACKPRESSURE_MASK )/(double)total_ticks;
546 0 : double idle_pct = 100.0*(double)diff_tile_regime( config, "benchg", prev_tile, cur_tile, FD_METRICS_ENUM_TILE_REGIME_G_IDLE_MASK )/(double)total_ticks;
547 0 : double busy_pct = 100.0 - idle_pct - backp_pct;
548 :
549 0 : PRINT( " %s%.1f" U( "%%" ) RESET, sev_color( busy_pct ), busy_pct );
550 0 : }
551 :
552 0 : PRINT( K( "benchs" ) );
553 0 : for( ulong i=0UL; i<config->topo.tile_cnt; i++ ) {
554 0 : if( FD_LIKELY( strcmp( config->topo.tiles[ i ].name, "benchs" ) ) ) continue;
555 :
556 0 : ulong total_ticks = total_regime( &cur_tile[ i*FD_METRICS_TOTAL_SZ ] )-total_regime( &prev_tile[ i*FD_METRICS_TOTAL_SZ ] );
557 0 : double backp_pct = 100.0*(double)diff_tile_regime( config, "benchs", prev_tile, cur_tile, FD_METRICS_ENUM_TILE_REGIME_G_BACKPRESSURE_MASK )/(double)total_ticks;
558 0 : double idle_pct = 100.0*(double)diff_tile_regime( config, "benchs", prev_tile, cur_tile, FD_METRICS_ENUM_TILE_REGIME_G_IDLE_MASK )/(double)total_ticks;
559 0 : double busy_pct = 100.0 - idle_pct - backp_pct;
560 :
561 0 : PRINT( " %s%.1f" U( "%%" ) RESET, sev_color( busy_pct ), busy_pct );
562 0 : }
563 :
564 0 : PRINT( CLEARLN "\n" );
565 0 : return 1;
566 0 : }
567 :
568 : static void
569 : write_backtest( config_t const * config,
570 0 : ulong const * cur_tile ) {
571 0 : ulong backt_idx = fd_topo_find_tile( &config->topo, "backt", 0UL );
572 0 : ulong start_slot = cur_tile[ backt_idx*FD_METRICS_TOTAL_SZ+MIDX( GAUGE, BACKT, START_SLOT ) ];
573 0 : ulong final_slot = cur_tile[ backt_idx*FD_METRICS_TOTAL_SZ+MIDX( GAUGE, BACKT, END_SLOT ) ];
574 :
575 0 : ulong replay_idx = fd_topo_find_tile( &config->topo, "replay", 0UL );
576 0 : ulong current_slot = cur_tile[ replay_idx*FD_METRICS_TOTAL_SZ+MIDX( GAUGE, REPLAY, ROOT_SLOT ) ];
577 0 : current_slot = current_slot ? current_slot : start_slot;
578 :
579 0 : ulong completed_slots = current_slot-start_slot;
580 :
581 0 : if( FD_UNLIKELY( !final_slot ) ) { /* unbounded */
582 0 : PRINT( ROWH( "◇", BGREEN, "backtest " )
583 0 : K( "slots" ) "%lu" U( " / ?" ) CLEARLN "\n", completed_slots );
584 0 : return;
585 0 : }
586 :
587 0 : ulong total_slots = final_slot-start_slot;
588 0 : double progress = total_slots ? 100.0 * (double)completed_slots / (double)total_slots : 100.0;
589 0 : PRINT( ROWH( "◇", BGREEN, "backtest " )
590 0 : " %s " BOLD "%5.1f" RESET U( "%%" )
591 0 : K( "slots" ) "%lu" U( " /" ) " %lu" CLEARLN "\n",
592 0 : BAR( progress, 20UL ), progress, completed_slots, total_slots );
593 0 : }
594 :
595 : static void
596 : write_snapshots( config_t const * config,
597 : ulong const * cur_tile,
598 0 : ulong const * prev_tile ) {
599 0 : ulong snapct_idx = fd_topo_find_tile( &config->topo, "snapct", 0UL );
600 0 : ulong snapdc_tile_cnt = fd_topo_tile_name_cnt( &config->topo, "snapdc" );
601 0 : ulong snapin_tile_cnt = fd_topo_tile_name_cnt( &config->topo, "snapin" );
602 0 : ulong state = cur_tile[ snapct_idx*FD_METRICS_TOTAL_SZ+MIDX( GAUGE, SNAPCT, LOAD_STATE ) ];
603 :
604 0 : double progress = 0.0;
605 0 : int incremental = 0;
606 0 : switch( state ) {
607 0 : case FD_SNAPCT_STATE_WAITING_FOR_PEERS:
608 0 : case FD_SNAPCT_STATE_WAITING_FOR_PEERS_INCREMENTAL:
609 0 : case FD_SNAPCT_STATE_COLLECTING_PEERS:
610 0 : case FD_SNAPCT_STATE_COLLECTING_PEERS_INCREMENTAL:
611 0 : break;
612 0 : case FD_SNAPCT_STATE_READING_INCREMENTAL_FILE:
613 0 : case FD_SNAPCT_STATE_FLUSHING_INCREMENTAL_FILE_FINI:
614 0 : case FD_SNAPCT_STATE_FLUSHING_INCREMENTAL_FILE_DONE:
615 0 : case FD_SNAPCT_STATE_READING_INCREMENTAL_HTTP:
616 0 : case FD_SNAPCT_STATE_FLUSHING_INCREMENTAL_HTTP_FINI:
617 0 : case FD_SNAPCT_STATE_FLUSHING_INCREMENTAL_HTTP_DONE:
618 0 : incremental = 1;
619 0 : __attribute__((fallthrough));
620 0 : case FD_SNAPCT_STATE_READING_FULL_FILE:
621 0 : case FD_SNAPCT_STATE_FLUSHING_FULL_FILE_FINI:
622 0 : case FD_SNAPCT_STATE_FLUSHING_FULL_FILE_DONE:
623 0 : case FD_SNAPCT_STATE_READING_FULL_HTTP:
624 0 : case FD_SNAPCT_STATE_FLUSHING_FULL_HTTP_FINI:
625 0 : case FD_SNAPCT_STATE_FLUSHING_FULL_HTTP_DONE: {
626 : /* Progress is the compressed-equivalent of bytes fully applied
627 : (parse+write consumption, converted back through the decompress
628 : ratio), same as snapshot-load, rather than bytes merely
629 : downloaded by snapct. */
630 0 : ulong consumed, dc_in, dc_out, size_bytes;
631 0 : if( FD_UNLIKELY( incremental ) ) {
632 0 : consumed = ULONG_MAX;
633 0 : for( ulong i=0UL; i<snapin_tile_cnt; i++ ) {
634 0 : ulong snapin_idx = fd_topo_find_tile( &config->topo, "snapin", i );
635 0 : consumed = fd_ulong_min( consumed, cur_tile[ snapin_idx*FD_METRICS_TOTAL_SZ+MIDX( GAUGE, SNAPIN, PARSE_PROGRESS_BYTES_INCREMENTAL ) ] );
636 0 : }
637 0 : dc_in = 0UL;
638 0 : dc_out = 0UL;
639 0 : for( ulong i=0UL; i<snapdc_tile_cnt; i++ ) {
640 0 : ulong snapdc_idx = fd_topo_find_tile( &config->topo, "snapdc", i );
641 0 : dc_in += cur_tile[ snapdc_idx*FD_METRICS_TOTAL_SZ+MIDX( GAUGE, SNAPDC, COMPRESSED_PROGRESS_BYTES_INCREMENTAL ) ];
642 0 : dc_out += cur_tile[ snapdc_idx*FD_METRICS_TOTAL_SZ+MIDX( GAUGE, SNAPDC, DECOMPRESSED_PROGRESS_BYTES_INCREMENTAL ) ];
643 0 : }
644 0 : size_bytes = cur_tile[ snapct_idx*FD_METRICS_TOTAL_SZ+MIDX( GAUGE, SNAPCT, READ_EXPECTED_BYTES_INCREMENTAL ) ];
645 0 : } else {
646 0 : consumed = ULONG_MAX;
647 0 : for( ulong i=0UL; i<snapin_tile_cnt; i++ ) {
648 0 : ulong snapin_idx = fd_topo_find_tile( &config->topo, "snapin", i );
649 0 : consumed = fd_ulong_min( consumed, cur_tile[ snapin_idx*FD_METRICS_TOTAL_SZ+MIDX( GAUGE, SNAPIN, PARSE_PROGRESS_BYTES_FULL ) ] );
650 0 : }
651 0 : dc_in = 0UL;
652 0 : dc_out = 0UL;
653 0 : for( ulong i=0UL; i<snapdc_tile_cnt; i++ ) {
654 0 : ulong snapdc_idx = fd_topo_find_tile( &config->topo, "snapdc", i );
655 0 : dc_in += cur_tile[ snapdc_idx*FD_METRICS_TOTAL_SZ+MIDX( GAUGE, SNAPDC, COMPRESSED_PROGRESS_BYTES_FULL ) ];
656 0 : dc_out += cur_tile[ snapdc_idx*FD_METRICS_TOTAL_SZ+MIDX( GAUGE, SNAPDC, DECOMPRESSED_PROGRESS_BYTES_FULL ) ];
657 0 : }
658 0 : size_bytes = cur_tile[ snapct_idx*FD_METRICS_TOTAL_SZ+MIDX( GAUGE, SNAPCT, READ_EXPECTED_BYTES_FULL ) ];
659 0 : }
660 0 : double done_comp = dc_out ? (double)dc_in*( (double)consumed/(double)dc_out ) : 0.0;
661 0 : if( FD_LIKELY( size_bytes ) ) progress = fd_double_if( done_comp>(double)size_bytes, 100.0, 100.0*done_comp/(double)size_bytes );
662 0 : break;
663 0 : }
664 0 : case FD_SNAPCT_STATE_SHUTDOWN:
665 0 : progress = 100.0;
666 0 : break;
667 0 : }
668 :
669 0 : ulong snap_rx_sum = 0UL;
670 0 : ulong num_snap_rx_samples = fd_ulong_min( snapshot_rx_idx, sizeof(snapshot_rx_samples)/sizeof(snapshot_rx_samples[0]) );
671 0 : for( ulong i=0UL; i<num_snap_rx_samples; i++ ) snap_rx_sum += snapshot_rx_samples[ i ];
672 0 : double megabytes_per_second = 0.0;
673 0 : if( FD_LIKELY( num_snap_rx_samples ) ) megabytes_per_second = (double)snap_rx_sum/window_seconds( num_snap_rx_samples )/1e6;
674 :
675 0 : ulong accounts_sum = 0UL;
676 0 : ulong num_accounts_samples = fd_ulong_min( snapshot_acc_idx, sizeof(snapshot_acc_samples)/sizeof(snapshot_acc_samples[0]) );
677 0 : for( ulong i=0UL; i<num_accounts_samples; i++ ) accounts_sum += snapshot_acc_samples[ i ];
678 0 : double million_accounts_per_second = 0.0;
679 0 : if( FD_LIKELY( num_accounts_samples ) ) million_accounts_per_second = (double)accounts_sum/window_seconds( num_accounts_samples )/1e6;
680 :
681 0 : ulong snap_wr_sum = 0UL;
682 0 : ulong num_snap_wr_samples = fd_ulong_min( snapshot_wr_idx, sizeof(snapshot_wr_samples)/sizeof(snapshot_wr_samples[0]) );
683 0 : for( ulong i=0UL; i<num_snap_wr_samples; i++ ) snap_wr_sum += snapshot_wr_samples[ i ];
684 0 : double wr_megabytes_per_second = 0.0;
685 0 : if( FD_LIKELY( num_snap_wr_samples ) ) wr_megabytes_per_second = (double)snap_wr_sum/window_seconds( num_snap_wr_samples )/1e6;
686 :
687 0 : ulong snapct_total_ticks = total_regime( &cur_tile[ snapct_idx*FD_METRICS_TOTAL_SZ ] )-total_regime( &prev_tile[ snapct_idx*FD_METRICS_TOTAL_SZ ] );
688 0 : ulong snapld_total_ticks = total_regime( &cur_tile[ fd_topo_find_tile( &config->topo, "snapld", 0UL )*FD_METRICS_TOTAL_SZ ] )-total_regime( &prev_tile[ fd_topo_find_tile( &config->topo, "snapld", 0UL )*FD_METRICS_TOTAL_SZ ] );
689 0 : ulong snapdc_total_ticks = 0UL;
690 0 : for( ulong i=0UL; i<snapdc_tile_cnt; i++ ) {
691 0 : ulong snapdc_idx = fd_topo_find_tile( &config->topo, "snapdc", i );
692 0 : snapdc_total_ticks += total_regime( &cur_tile[ snapdc_idx*FD_METRICS_TOTAL_SZ ] )-total_regime( &prev_tile[ snapdc_idx*FD_METRICS_TOTAL_SZ ] );
693 0 : }
694 0 : ulong snapin_total_ticks = 0UL;
695 0 : for( ulong i=0UL; i<snapin_tile_cnt; i++ ) {
696 0 : ulong idx = fd_topo_find_tile( &config->topo, "snapin", i );
697 0 : snapin_total_ticks += total_regime( &cur_tile[ idx*FD_METRICS_TOTAL_SZ ] )-total_regime( &prev_tile[ idx*FD_METRICS_TOTAL_SZ ] );
698 0 : }
699 0 : snapct_total_ticks = fd_ulong_max( snapct_total_ticks, 1UL );
700 0 : snapld_total_ticks = fd_ulong_max( snapld_total_ticks, 1UL );
701 0 : snapdc_total_ticks = fd_ulong_max( snapdc_total_ticks, 1UL );
702 0 : snapin_total_ticks = fd_ulong_max( snapin_total_ticks, 1UL );
703 :
704 0 : double snapct_backp_pct = 100.0*(double)diff_tile_regime( config, "snapct", prev_tile, cur_tile, FD_METRICS_ENUM_TILE_REGIME_G_BACKPRESSURE_MASK )/(double)snapct_total_ticks;
705 0 : double snapld_backp_pct = 100.0*(double)diff_tile_regime( config, "snapld", prev_tile, cur_tile, FD_METRICS_ENUM_TILE_REGIME_G_BACKPRESSURE_MASK )/(double)snapld_total_ticks;
706 0 : double snapdc_backp_pct = 100.0*(double)diff_tile_regime( config, "snapdc", prev_tile, cur_tile, FD_METRICS_ENUM_TILE_REGIME_G_BACKPRESSURE_MASK )/(double)snapdc_total_ticks;
707 0 : double snapin_backp_pct = 100.0*(double)diff_tile_regime( config, "snapin", prev_tile, cur_tile, FD_METRICS_ENUM_TILE_REGIME_G_BACKPRESSURE_MASK )/(double)snapin_total_ticks;
708 :
709 0 : double snapct_idle_pct = 100.0*(double)diff_tile_regime( config, "snapct", prev_tile, cur_tile, FD_METRICS_ENUM_TILE_REGIME_G_IDLE_MASK )/(double)snapct_total_ticks;
710 0 : double snapld_idle_pct = 100.0*(double)diff_tile_regime( config, "snapld", prev_tile, cur_tile, FD_METRICS_ENUM_TILE_REGIME_G_IDLE_MASK )/(double)snapld_total_ticks;
711 0 : double snapdc_idle_pct = 100.0*(double)diff_tile_regime( config, "snapdc", prev_tile, cur_tile, FD_METRICS_ENUM_TILE_REGIME_G_IDLE_MASK )/(double)snapdc_total_ticks;
712 0 : double snapin_idle_pct = 100.0*(double)diff_tile_regime( config, "snapin", prev_tile, cur_tile, FD_METRICS_ENUM_TILE_REGIME_G_IDLE_MASK )/(double)snapin_total_ticks;
713 :
714 0 : double busy [ 4 ] = { 100.0-snapct_idle_pct-snapct_backp_pct,
715 0 : 100.0-snapld_idle_pct-snapld_backp_pct,
716 0 : 100.0-snapdc_idle_pct-snapdc_backp_pct,
717 0 : 100.0-snapin_idle_pct-snapin_backp_pct };
718 0 : double backp[ 4 ] = { snapct_backp_pct, snapld_backp_pct, snapdc_backp_pct, snapin_backp_pct };
719 0 : char const * stage[ 4 ] = { "ct", "ld", "dc", "in" };
720 :
721 0 : PRINT( ROWH( "◐", BYELLOW, "snapshot " )
722 0 : " %s " BOLD "%5.1f" RESET U( "%%" )
723 0 : K( "state" ) "%s"
724 0 : K( "rx" ) "%3.0f" U( " MB/s" )
725 0 : K( "wr" ) "%3.0f" U( " MB/s" )
726 0 : K( "acc" ) "%3.1f" U( " M/s" )
727 0 : K( "busy" ),
728 0 : BAR( progress, 20UL ),
729 0 : progress,
730 0 : fd_snapct_state_str( (int)state ),
731 0 : megabytes_per_second,
732 0 : wr_megabytes_per_second,
733 0 : million_accounts_per_second );
734 0 : for( ulong i=0UL; i<4UL; i++ )
735 0 : PRINT( " " U( "%s" ) " %s%3.0f" U( "%%" ) RESET, stage[ i ], sev_color( busy[ i ] ), busy[ i ] );
736 0 : PRINT( K( "backp" ) );
737 0 : for( ulong i=0UL; i<4UL; i++ )
738 0 : PRINT( " " U( "%s" ) " %s%3.0f" U( "%%" ) RESET, stage[ i ], sev_color( backp[ i ] ), backp[ i ] );
739 0 : PRINT( CLEARLN "\n" );
740 0 : }
741 :
742 : static long
743 : diff_tile_idx( ulong const * prev_tile,
744 : ulong const * cur_tile,
745 : ulong tile_idx,
746 0 : ulong metric_off ) {
747 0 : return (long)cur_tile [ tile_idx*FD_METRICS_TOTAL_SZ+metric_off ] -
748 0 : (long)prev_tile[ tile_idx*FD_METRICS_TOTAL_SZ+metric_off ];
749 0 : }
750 :
751 : static void
752 : accdb_per_tile_offsets( char const * name,
753 0 : ulong * offs /* 11 entries: acquired, writable, missed, waited, rd, wr, cp, evicted_class_base, preevicted_class_base, committed_new_class_base, committed_overwrite_class_base */ ) {
754 0 : if( !strcmp( name, "execle" ) ) {
755 0 : offs[0] =MIDX(COUNTER,EXECLE,ACCDB_ACCOUNT_READONLY_ACQUIRED ); offs[1]=MIDX(COUNTER,EXECLE,ACCDB_ACCOUNT_WRITABLE_ACQUIRED);
756 0 : offs[2] =MIDX(COUNTER,EXECLE,ACCDB_ACCOUNT_CACHE_MISSED ); offs[3]=MIDX(COUNTER,EXECLE,ACCDB_ACCOUNT_WAITED );
757 0 : offs[4] =MIDX(COUNTER,EXECLE,ACCDB_DISK_READ_BYTES ); offs[5]=MIDX(COUNTER,EXECLE,ACCDB_DISK_WRITTEN_BYTES );
758 0 : offs[6] =MIDX(COUNTER,EXECLE,ACCDB_COPIED_BYTES ); offs[7]=MIDX(COUNTER,EXECLE,ACCDB_ACCOUNT_EVICTED );
759 0 : offs[8] =ULONG_MAX ; offs[9]=MIDX(COUNTER,EXECLE,ACCDB_ACCOUNT_NEW_COMMITTED );
760 0 : offs[10]=MIDX(COUNTER,EXECLE,ACCDB_ACCOUNT_OVERWRITE_COMMITTED);
761 0 : } else if( !strcmp( name, "execrp" ) ) {
762 0 : offs[0] =MIDX(COUNTER,EXECRP,ACCDB_ACCOUNT_READONLY_ACQUIRED ); offs[1]=MIDX(COUNTER,EXECRP,ACCDB_ACCOUNT_WRITABLE_ACQUIRED);
763 0 : offs[2] =MIDX(COUNTER,EXECRP,ACCDB_ACCOUNT_CACHE_MISSED ); offs[3]=MIDX(COUNTER,EXECRP,ACCDB_ACCOUNT_WAITED );
764 0 : offs[4] =MIDX(COUNTER,EXECRP,ACCDB_DISK_READ_BYTES ); offs[5]=MIDX(COUNTER,EXECRP,ACCDB_DISK_WRITTEN_BYTES );
765 0 : offs[6] =MIDX(COUNTER,EXECRP,ACCDB_COPIED_BYTES ); offs[7]=MIDX(COUNTER,EXECRP,ACCDB_ACCOUNT_EVICTED );
766 0 : offs[8] =ULONG_MAX ; offs[9]=MIDX(COUNTER,EXECRP,ACCDB_ACCOUNT_NEW_COMMITTED );
767 0 : offs[10]=MIDX(COUNTER,EXECRP,ACCDB_ACCOUNT_OVERWRITE_COMMITTED);
768 0 : } else if( !strcmp( name, "replay" ) ) {
769 0 : offs[0] =MIDX(COUNTER,REPLAY,ACCDB_ACCOUNT_READONLY_ACQUIRED ); offs[1]=MIDX(COUNTER,REPLAY,ACCDB_ACCOUNT_WRITABLE_ACQUIRED);
770 0 : offs[2] =MIDX(COUNTER,REPLAY,ACCDB_ACCOUNT_CACHE_MISSED ); offs[3]=MIDX(COUNTER,REPLAY,ACCDB_ACCOUNT_WAITED );
771 0 : offs[4] =MIDX(COUNTER,REPLAY,ACCDB_DISK_READ_BYTES ); offs[5]=MIDX(COUNTER,REPLAY,ACCDB_DISK_WRITTEN_BYTES );
772 0 : offs[6] =MIDX(COUNTER,REPLAY,ACCDB_COPIED_BYTES ); offs[7]=MIDX(COUNTER,REPLAY,ACCDB_ACCOUNT_EVICTED );
773 0 : offs[8] =ULONG_MAX ; offs[9]=MIDX(COUNTER,REPLAY,ACCDB_ACCOUNT_NEW_COMMITTED );
774 0 : offs[10]=MIDX(COUNTER,REPLAY,ACCDB_ACCOUNT_OVERWRITE_COMMITTED);
775 0 : } else if( !strcmp( name, "accdb" ) ) {
776 : /* The accdb tile only runs background work (compact, preevict,
777 : advance_root, purge); it never acquires/releases. Sentinel
778 : everything that comes from the acquire/release path. */
779 0 : offs[0] =ULONG_MAX ; offs[1]=ULONG_MAX ;
780 0 : offs[2] =ULONG_MAX ; offs[3]=ULONG_MAX ;
781 0 : offs[4] =MIDX(COUNTER,ACCDB,DISK_READ_BYTES ); offs[5]=MIDX(COUNTER,ACCDB,DISK_WRITTEN_BYTES );
782 0 : offs[6] =ULONG_MAX ; offs[7]=ULONG_MAX ;
783 0 : offs[8] =MIDX(COUNTER,ACCDB,ACCOUNT_PREEVICTED ); offs[9]=ULONG_MAX ;
784 0 : offs[10]=ULONG_MAX;
785 0 : } else if( !strcmp( name, "rpc" ) ) {
786 : /* RPC is a read-only accdb consumer. It only emits the subset
787 : of counters that fd_accdb_read_one_nocache touches; everything
788 : else is sentinel and skipped by sample_accdb. */
789 0 : offs[0] =MIDX(COUNTER,RPC,ACCDB_ACCOUNT_READONLY_ACQUIRED); offs[1]=ULONG_MAX;
790 0 : offs[2] =MIDX(COUNTER,RPC,ACCDB_ACCOUNT_CACHE_MISSED); offs[3]=MIDX(COUNTER,RPC,ACCDB_ACCOUNT_WAITED);
791 0 : offs[4] =MIDX(COUNTER,RPC,ACCDB_DISK_READ_BYTES ); offs[5]=ULONG_MAX;
792 0 : offs[6] =MIDX(COUNTER,RPC,ACCDB_COPIED_BYTES ); offs[7]=ULONG_MAX;
793 0 : offs[8] =ULONG_MAX; offs[9]=ULONG_MAX;
794 0 : offs[10]=ULONG_MAX;
795 0 : } else if( !strcmp( name, "resolv" ) ) {
796 : /* Resolv is a read-only accdb consumer (address lookup table
797 : reads on the receive path). Same RO subset as RPC. */
798 0 : offs[0] =MIDX(COUNTER,RESOLV,ACCDB_ACCOUNT_READONLY_ACQUIRED ); offs[1]=ULONG_MAX;
799 0 : offs[2] =MIDX(COUNTER,RESOLV,ACCDB_ACCOUNT_CACHE_MISSED); offs[3]=MIDX(COUNTER,RESOLV,ACCDB_ACCOUNT_WAITED);
800 0 : offs[4] =MIDX(COUNTER,RESOLV,ACCDB_DISK_READ_BYTES ); offs[5]=ULONG_MAX;
801 0 : offs[6] =MIDX(COUNTER,RESOLV,ACCDB_COPIED_BYTES ); offs[7]=ULONG_MAX;
802 0 : offs[8] =ULONG_MAX; offs[9]=ULONG_MAX;
803 0 : offs[10]=ULONG_MAX;
804 0 : } else {
805 0 : for( ulong i=0UL; i<11UL; i++ ) offs[i] = ULONG_MAX;
806 0 : }
807 0 : }
808 :
809 : static void
810 : sample_accdb( config_t const * config,
811 : ulong const * prev_tile,
812 0 : ulong const * cur_tile ) {
813 0 : long acquired = 0L, writable = 0L, missed = 0L, evicted = 0L, waited = 0L;
814 0 : long bytes_rd = 0L, bytes_wr = 0L, bytes_cp = 0L, bytes_pe = 0L;
815 0 : long preevicted = 0L;
816 0 : long evicted_class[ 8 ] = {0};
817 0 : long preevicted_class[ 8 ] = {0};
818 0 : long committed_new_class[ 8 ] = {0};
819 0 : long committed_overwrite_class[ 8 ] = {0};
820 :
821 0 : for( ulong i=0UL; i<config->topo.tile_cnt; i++ ) {
822 0 : ulong offs[11];
823 0 : accdb_per_tile_offsets( config->topo.tiles[ i ].name, offs );
824 0 : if( offs[0]!=ULONG_MAX ) {
825 0 : for( ulong c=0UL; c<8UL; c++ ) acquired += diff_tile_idx( prev_tile, cur_tile, i, offs[0] + c );
826 0 : }
827 0 : if( offs[1]!=ULONG_MAX ) {
828 0 : for( ulong c=0UL; c<8UL; c++ ) writable += diff_tile_idx( prev_tile, cur_tile, i, offs[1] + c );
829 0 : }
830 0 : if( offs[3]!=ULONG_MAX ) waited += diff_tile_idx( prev_tile, cur_tile, i, offs[3] );
831 0 : if( offs[4]!=ULONG_MAX ) bytes_rd += diff_tile_idx( prev_tile, cur_tile, i, offs[4] );
832 0 : if( offs[6]!=ULONG_MAX ) bytes_cp += diff_tile_idx( prev_tile, cur_tile, i, offs[6] );
833 0 : if( offs[5]!=ULONG_MAX ) {
834 0 : long this_wr = diff_tile_idx( prev_tile, cur_tile, i, offs[5] );
835 0 : if( !strcmp( config->topo.tiles[ i ].name, "accdb" ) ) bytes_pe += this_wr;
836 0 : else bytes_wr += this_wr;
837 0 : }
838 0 : for( ulong c=0UL; c<8UL; c++ ) {
839 0 : if( offs[2]!=ULONG_MAX ) missed += diff_tile_idx( prev_tile, cur_tile, i, offs[2] + c );
840 0 : if( offs[7]!=ULONG_MAX ) {
841 0 : long d = diff_tile_idx( prev_tile, cur_tile, i, offs[7] + c );
842 0 : evicted_class[ c ] += d;
843 0 : evicted += d;
844 0 : }
845 0 : if( offs[8]!=ULONG_MAX ) {
846 0 : long d = diff_tile_idx( prev_tile, cur_tile, i, offs[8] + c );
847 0 : preevicted_class[ c ] += d;
848 0 : preevicted += d;
849 0 : }
850 0 : if( offs[9] !=ULONG_MAX ) committed_new_class [ c ] += diff_tile_idx( prev_tile, cur_tile, i, offs[9] + c );
851 0 : if( offs[10]!=ULONG_MAX ) committed_overwrite_class[ c ] += diff_tile_idx( prev_tile, cur_tile, i, offs[10] + c );
852 0 : }
853 0 : }
854 :
855 0 : ulong slot = accdb_samples_idx % (sizeof(accdb_acquired_samples)/sizeof(accdb_acquired_samples[0]));
856 0 : accdb_acquired_samples[ slot ] = (ulong)acquired;
857 0 : accdb_writable_samples[ slot ] = (ulong)writable;
858 0 : accdb_missed_samples [ slot ] = (ulong)missed;
859 0 : accdb_evicted_samples [ slot ] = (ulong)evicted;
860 0 : accdb_waited_samples [ slot ] = (ulong)waited;
861 0 : accdb_bytes_rd_samples[ slot ] = (ulong)bytes_rd;
862 0 : accdb_bytes_wr_samples[ slot ] = (ulong)bytes_wr;
863 0 : accdb_bytes_cp_samples[ slot ] = (ulong)bytes_cp;
864 0 : accdb_bytes_pe_samples[ slot ] = (ulong)bytes_pe;
865 0 : accdb_preevicted_samples[ slot ] = (ulong)preevicted;
866 0 : for( ulong c=0UL; c<8UL; c++ ) {
867 0 : accdb_evicted_class_samples [ c ][ slot ] = (ulong)evicted_class [ c ];
868 0 : accdb_preevicted_class_samples [ c ][ slot ] = (ulong)preevicted_class [ c ];
869 0 : accdb_committed_new_class_samples [ c ][ slot ] = (ulong)committed_new_class [ c ];
870 0 : accdb_committed_overwrite_class_samples[ c ][ slot ] = (ulong)committed_overwrite_class[ c ];
871 0 : }
872 0 : accdb_samples_idx++;
873 0 : }
874 :
875 : static uint
876 : write_accdb( config_t const * config,
877 : ulong const * cur_tile,
878 0 : ulong const * prev_tile ) {
879 0 : ulong accdb_tile_idx = fd_topo_find_tile( &config->topo, "accdb", 0UL );
880 0 : if( accdb_tile_idx==ULONG_MAX ) return 0U;
881 :
882 0 : ulong const * t = cur_tile + accdb_tile_idx*FD_METRICS_TOTAL_SZ;
883 :
884 0 : ulong accdb_total_ticks = total_regime( &cur_tile[ accdb_tile_idx*FD_METRICS_TOTAL_SZ ] )-total_regime( &prev_tile[ accdb_tile_idx*FD_METRICS_TOTAL_SZ ] );
885 0 : accdb_total_ticks = fd_ulong_max( accdb_total_ticks, 1UL );
886 0 : double accdb_backp_pct = 100.0*(double)diff_tile_regime( config, "accdb", prev_tile, cur_tile, FD_METRICS_ENUM_TILE_REGIME_G_BACKPRESSURE_MASK )/(double)accdb_total_ticks;
887 0 : double accdb_idle_pct = 100.0*(double)diff_tile_regime( config, "accdb", prev_tile, cur_tile, FD_METRICS_ENUM_TILE_REGIME_G_IDLE_MASK )/(double)accdb_total_ticks;
888 0 : double accdb_busy_pct = 100.0 - accdb_backp_pct - accdb_idle_pct;
889 :
890 0 : ulong acct_cnt = t[ MIDX( GAUGE, ACCDB, ACCOUNT_USED ) ];
891 0 : ulong acct_cap = t[ MIDX( GAUGE, ACCDB, ACCOUNT_CAPACITY ) ];
892 0 : ulong used_bytes = t[ MIDX( GAUGE, ACCDB, DISK_USED_BYTES ) ];
893 0 : ulong current_bytes = t[ MIDX( GAUGE, ACCDB, DISK_CONSUMED_BYTES ) ];
894 0 : ulong alloc_bytes = t[ MIDX( GAUGE, ACCDB, DISK_ALLOCATED_BYTES ) ];
895 0 : ulong in_compaction = t[ MIDX( GAUGE, ACCDB, IS_COMPACTING ) ];
896 0 : ulong compact_req = t[ MIDX( COUNTER, ACCDB, COMPACTION_REQUESTED ) ];
897 0 : ulong compact_done = t[ MIDX( COUNTER, ACCDB, COMPACTION_COMPLETED ) ];
898 :
899 0 : ulong frag_bytes = current_bytes>used_bytes ? current_bytes-used_bytes : 0UL;
900 0 : double data_gb = (double)alloc_bytes/1e9;
901 0 : double live_gb = (double)used_bytes/1e9;
902 0 : double frag_gb = (double)frag_bytes/1e9;
903 0 : double frag_pct = current_bytes ? 100.0*(double)frag_bytes/(double)current_bytes : 0.0;
904 0 : double index_pct = acct_cap ? 100.0*(double)acct_cnt/(double)acct_cap : 0.0;
905 :
906 0 : PRINT( ROWH( "■", GREEN, "accounts " )
907 0 : K( "cache" ) "%lu" U( " GiB" )
908 0 : K( "disk" ) "%.1f" U( " GB" )
909 0 : K( "live" ) "%.1f" U( " GB" )
910 0 : K( "frag" ) "%.1f" U( " GB" ) " %s%4.1f" U( "%%" ) RESET
911 0 : K( "index" ) "%s%4.1f" U( "%%" ) RESET " " U( "%.1fM/%.1fM" )
912 0 : K( "compact" ) "%s " U( "%lu/%lu" )
913 0 : K( "busy" ) "%s%3.0f" U( "%%" ) RESET CLEARLN "\n",
914 0 : config->firedancer.accounts.cache_size_gib,
915 0 : data_gb, live_gb,
916 0 : frag_gb, sev_color( frag_pct ), frag_pct,
917 0 : sev_color( index_pct ), index_pct, (double)acct_cnt/1e6, (double)acct_cap/1e6,
918 0 : in_compaction ? YELLOW "running" RESET : DIM "idle" RESET, compact_done, compact_req,
919 0 : sev_color( accdb_busy_pct ), accdb_busy_pct );
920 :
921 0 : ulong const cap = sizeof(accdb_acquired_samples)/sizeof(accdb_acquired_samples[0]);
922 0 : ulong n = fd_ulong_min( accdb_samples_idx, cap );
923 0 : if( !n ) n = 1UL;
924 :
925 0 : ulong sum_acq = 0UL, sum_wr = 0UL, sum_miss = 0UL, sum_evict = 0UL, sum_wait = 0UL;
926 0 : ulong sum_brd = 0UL, sum_bwr = 0UL, sum_bcp = 0UL, sum_bpe = 0UL;
927 0 : ulong sum_pre = 0UL;
928 0 : for( ulong i=0UL; i<n; i++ ) {
929 0 : sum_acq += accdb_acquired_samples[ i ];
930 0 : sum_wr += accdb_writable_samples[ i ];
931 0 : sum_miss += accdb_missed_samples [ i ];
932 0 : sum_evict += accdb_evicted_samples [ i ];
933 0 : sum_wait += accdb_waited_samples [ i ];
934 0 : sum_brd += accdb_bytes_rd_samples[ i ];
935 0 : sum_bwr += accdb_bytes_wr_samples[ i ];
936 0 : sum_bcp += accdb_bytes_cp_samples[ i ];
937 0 : sum_bpe += accdb_bytes_pe_samples[ i ];
938 0 : sum_pre += accdb_preevicted_samples[ i ];
939 0 : }
940 :
941 0 : double acquired = (double)sum_acq/window_seconds( n );
942 0 : double writable = (double)sum_wr/window_seconds( n );
943 0 : double missed = (double)sum_miss/window_seconds( n );
944 0 : double evicted = (double)sum_evict/window_seconds( n );
945 0 : double waited = (double)sum_wait/window_seconds( n );
946 0 : double bytes_rd = (double)sum_brd/window_seconds( n );
947 0 : double bytes_wr = (double)sum_bwr/window_seconds( n );
948 0 : double bytes_cp = (double)sum_bcp/window_seconds( n );
949 0 : double bytes_pe = (double)sum_bpe/window_seconds( n );
950 0 : double preevicted = (double)sum_pre/window_seconds( n );
951 :
952 0 : double hit_pct = acquired>0.0 ? 100.0*(acquired-missed)/acquired : 0.0;
953 :
954 0 : char * read_str = fmt_bytes( fd_alloca_check( 1UL, 64UL ), 64UL, (long)bytes_rd );
955 0 : char * write_str = fmt_bytes( fd_alloca_check( 1UL, 64UL ), 64UL, (long)bytes_wr );
956 0 : char * copy_str = fmt_bytes( fd_alloca_check( 1UL, 64UL ), 64UL, (long)bytes_cp );
957 0 : char * preevict_str= fmt_bytes( fd_alloca_check( 1UL, 64UL ), 64UL, (long)bytes_pe );
958 0 : char * acq_str = COUNTF( acquired );
959 0 : char * wr_str = COUNTF( writable );
960 0 : char * miss_str = COUNTF( missed );
961 0 : char * evict_str = COUNTF( evicted );
962 0 : char * pre_str = COUNTF( preevicted );
963 0 : char * wait_str = COUNTF( waited );
964 :
965 0 : if( FD_LIKELY( !watch_full ) ) return 1;
966 :
967 0 : PRINT( ROWC
968 0 : K( "acquire" ) "%s" U( "/s" ) " " U( "(%s wr/s)" )
969 0 : K( "hit" ) "%5.1f" U( "%%" )
970 0 : K( "miss" ) "%s" U( "/s" )
971 0 : K( "evict" ) "%s" U( "/s (+%s/s)" )
972 0 : K( "wait" ) "%s" U( "/s" )
973 0 : K( "io" ) "%s" U( " rd" ) " %s" U( " wr-acq" ) " %s" U( " wr-pe" ) " %s" U( " cp" ) CLEARLN "\n",
974 0 : acq_str, wr_str, hit_pct, miss_str, evict_str, pre_str, wait_str,
975 0 : read_str, write_str, preevict_str, copy_str );
976 :
977 0 : char * evict_class_str[ 8 ];
978 0 : char * preevict_class_str[ 8 ];
979 0 : char * commit_new_class_str[ 8 ];
980 0 : char * commit_overwrite_class_str[ 8 ];
981 0 : for( ulong c=0UL; c<8UL; c++ ) {
982 0 : ulong sum_c = 0UL, sum_pc = 0UL, sum_cn = 0UL, sum_co = 0UL;
983 0 : for( ulong i=0UL; i<n; i++ ) {
984 0 : sum_c += accdb_evicted_class_samples [ c ][ i ];
985 0 : sum_pc += accdb_preevicted_class_samples [ c ][ i ];
986 0 : sum_cn += accdb_committed_new_class_samples [ c ][ i ];
987 0 : sum_co += accdb_committed_overwrite_class_samples[ c ][ i ];
988 0 : }
989 0 : evict_class_str [ c ] = COUNTF_T( (double)sum_c/window_seconds( n ) );
990 0 : preevict_class_str [ c ] = COUNTF_T( (double)sum_pc/window_seconds( n ) );
991 0 : commit_new_class_str [ c ] = COUNTF_T( (double)sum_cn/window_seconds( n ) );
992 0 : commit_overwrite_class_str[ c ] = COUNTF_T( (double)sum_co/window_seconds( n ) );
993 0 : }
994 :
995 : /* Per size class grid: dim header row, then one row per family
996 : with every cell right aligned in a fixed 19 char column. */
997 0 : static char const * class_lbl[ 8 ] = { "128b", "512b", "2k", "8k", "32k", "128k", "1m", "10m" };
998 :
999 0 : PRINT( ROWC K( "class " ) );
1000 0 : for( ulong c=0UL; c<8UL; c++ ) PRINT( DIM "%21s" RESET, class_lbl[ c ] );
1001 0 : PRINT( CLEARLN "\n" );
1002 :
1003 0 : PRINT( ROWC K( "evict/s " ) );
1004 0 : for( ulong c=0UL; c<8UL; c++ ) PRINT( " %6s" DIM "+" RESET "%6s", evict_class_str[ c ], preevict_class_str[ c ] );
1005 0 : PRINT( CLEARLN "\n" );
1006 :
1007 0 : PRINT( ROWC K( "commit/s" ) );
1008 0 : for( ulong c=0UL; c<8UL; c++ ) PRINT( " %6s" DIM "=" RESET "%6s", commit_new_class_str[ c ], commit_overwrite_class_str[ c ] );
1009 0 : PRINT( CLEARLN "\n" );
1010 :
1011 0 : ulong cache_used_off = MIDX( GAUGE, ACCDB, CACHE_CLASS_USED );
1012 0 : ulong cache_max_off = MIDX( GAUGE, ACCDB, CACHE_CLASS_CAPACITY );
1013 0 : char * cache_used_str[ 8 ];
1014 0 : char * cache_max_str [ 8 ];
1015 0 : double cache_pct [ 8 ];
1016 0 : for( ulong c=0UL; c<8UL; c++ ) {
1017 0 : ulong used = t[ cache_used_off + c ];
1018 0 : ulong max = t[ cache_max_off + c ];
1019 0 : cache_used_str[ c ] = COUNT_T( used );
1020 0 : cache_max_str [ c ] = COUNT_T( max );
1021 0 : cache_pct [ c ] = max ? 100.0*(double)used/(double)max : 0.0;
1022 0 : }
1023 :
1024 0 : PRINT( ROWC K( "cache " ) );
1025 0 : for( ulong c=0UL; c<8UL; c++ ) {
1026 : /* COUNT_T strings are already 6 wide: cell = 1+5+1+1+6+1+6 = 21. */
1027 0 : PRINT( " %s%5.1f" U( "%%" ) RESET " %s" DIM "/" RESET "%s",
1028 0 : sev_color( cache_pct[ c ] ), cache_pct[ c ],
1029 0 : cache_used_str[ c ], cache_max_str[ c ] );
1030 0 : }
1031 0 : PRINT( CLEARLN "\n" );
1032 :
1033 0 : ulong cache_resv_off = MIDX( GAUGE, ACCDB, CACHE_CLASS_RESERVED );
1034 0 : ulong cache_tracked_off = MIDX( GAUGE, ACCDB, IS_CACHE_CLASS_RESERVATION_TRACKED );
1035 0 : char * cache_resv_str[ 8 ];
1036 0 : for( ulong c=0UL; c<8UL; c++ ) {
1037 0 : if( !t[ cache_tracked_off + c ] ) cache_resv_str[ c ] = " off ";
1038 0 : else cache_resv_str[ c ] = COUNT_T( t[ cache_resv_off + c ] );
1039 0 : }
1040 :
1041 0 : PRINT( ROWC K( "reserved" ) );
1042 0 : for( ulong c=0UL; c<8UL; c++ ) PRINT( " %6s", cache_resv_str[ c ] );
1043 0 : PRINT( CLEARLN "\n" );
1044 0 : return 7;
1045 0 : }
1046 :
1047 : static uint
1048 : write_wfs( config_t const * config,
1049 0 : ulong const * cur_tile ) {
1050 0 : ulong gossip_tile_idx = fd_topo_find_tile( &config->topo, "gossip", 0UL );
1051 0 : if( FD_UNLIKELY( gossip_tile_idx==ULONG_MAX ) ) return 0U;
1052 :
1053 0 : int wfs_state = (int)cur_tile[ gossip_tile_idx*FD_METRICS_TOTAL_SZ+MIDX( GAUGE, GOSSIP, WAIT_FOR_SUPERMAJORITY_STATE ) ];
1054 0 : if( FD_LIKELY( wfs_state==FD_GOSSIP_WFS_STATE_DONE ) ) return 0U;
1055 :
1056 0 : char const * state_str;
1057 0 : switch( wfs_state ) {
1058 0 : case FD_GOSSIP_WFS_STATE_INIT: state_str = "loading snapshot"; break;
1059 0 : case FD_GOSSIP_WFS_STATE_WAIT: state_str = "waiting"; break;
1060 0 : case FD_GOSSIP_WFS_STATE_PUBLISH: state_str = "starting"; break;
1061 0 : default: return 0U;
1062 0 : }
1063 :
1064 0 : ulong _stake_online = cur_tile[ gossip_tile_idx*FD_METRICS_TOTAL_SZ+MIDX( GAUGE, GOSSIP, WAIT_FOR_SUPERMAJORITY_STAKE_ONLINE_LAMPORTS ) ];
1065 0 : ulong _stake_total = cur_tile[ gossip_tile_idx*FD_METRICS_TOTAL_SZ+MIDX( GAUGE, GOSSIP, WAIT_FOR_SUPERMAJORITY_STAKE_TRACKED_LAMPORTS ) ];
1066 0 : ulong peers_online = cur_tile[ gossip_tile_idx*FD_METRICS_TOTAL_SZ+MIDX( GAUGE, GOSSIP, WAIT_FOR_SUPERMAJORITY_STAKED_PEER_ONLINE ) ];
1067 0 : ulong peers_total = cur_tile[ gossip_tile_idx*FD_METRICS_TOTAL_SZ+MIDX( GAUGE, GOSSIP, WAIT_FOR_SUPERMAJORITY_STAKED_PEER_COUNT ) ];
1068 :
1069 0 : ulong ipecho_tile_idx = fd_topo_find_tile( &config->topo, "ipecho", 0UL );
1070 0 : ulong shred_ver = 0UL;
1071 0 : if( FD_LIKELY( ipecho_tile_idx!=ULONG_MAX ) ) shred_ver = cur_tile[ ipecho_tile_idx*FD_METRICS_TOTAL_SZ+MIDX( GAUGE, IPECHO, SHRED_VERSION ) ];
1072 :
1073 0 : double stake_pct = _stake_total>0UL ? 100.0*(double)_stake_online/(double)_stake_total : 0.0;
1074 0 : double stake_div = (_stake_total<(ulong)1e14) ? 1e9 : 1e15;
1075 0 : char const * stake_unit = (_stake_total<(ulong)1e14) ? " SOL" : "M";
1076 0 : double stake_online = (double)_stake_online / stake_div;
1077 0 : double stake_total = (double)_stake_total / stake_div;
1078 :
1079 0 : PRINT( ROWH( "○", YELLOW, "cluster boot" )
1080 0 : K( "state" ) "%s"
1081 0 : K( "stake" ) "%s " BOLD "%3.0f" RESET U( "%%" ) " " U( "(%.1f%s / %.1f%s)" )
1082 0 : K( "shred ver" ) "%lu"
1083 0 : K( "peers" ) "%lu" U( " online " ) "%lu" U( " offline" )
1084 0 : K( "bank hash" ) "%s" CLEARLN "\n",
1085 0 : state_str,
1086 0 : BAR( stake_pct, 10UL ),
1087 0 : stake_pct,
1088 0 : stake_online,
1089 0 : stake_unit,
1090 0 : stake_total,
1091 0 : stake_unit,
1092 0 : shred_ver,
1093 0 : peers_online,
1094 0 : peers_total>peers_online ? peers_total-peers_online : 0UL,
1095 0 : config->firedancer.consensus.wait_for_supermajority_with_bank_hash );
1096 0 : return 1U;
1097 0 : }
1098 :
1099 : static uint
1100 : write_gossip( config_t const * config,
1101 : ulong const * cur_tile,
1102 : ulong const * prev_tile,
1103 : ulong const * cur_link,
1104 0 : ulong const * prev_link ) {
1105 0 : ulong gossip_tile_idx = fd_topo_find_tile( &config->topo, "gossip", 0UL );
1106 0 : if( gossip_tile_idx==ULONG_MAX ) return 0U;
1107 0 : ulong const * t = &cur_tile[ gossip_tile_idx*FD_METRICS_TOTAL_SZ ];
1108 0 : char * contact_info = COUNT( t[ MIDX( GAUGE, GOSSIP, CRDS_USED_CONTACT_INFO_V2 ) ] );
1109 :
1110 0 : ulong gossip_total_ticks = total_regime( &cur_tile[ gossip_tile_idx*FD_METRICS_TOTAL_SZ ] )-total_regime( &prev_tile[ gossip_tile_idx*FD_METRICS_TOTAL_SZ ] );
1111 0 : gossip_total_ticks = fd_ulong_max( gossip_total_ticks, 1UL );
1112 0 : double gossip_backp_pct = 100.0*(double)diff_tile_regime( config, "gossip", prev_tile, cur_tile, FD_METRICS_ENUM_TILE_REGIME_G_BACKPRESSURE_MASK )/(double)gossip_total_ticks;
1113 0 : double gossip_idle_pct = 100.0*(double)diff_tile_regime( config, "gossip", prev_tile, cur_tile, FD_METRICS_ENUM_TILE_REGIME_G_IDLE_MASK )/(double)gossip_total_ticks;
1114 0 : double gossip_busy_pct = 100.0 - gossip_backp_pct - gossip_idle_pct;
1115 :
1116 0 : PRINT( ROWH( "●", BLUE, "gossip " )
1117 0 : K( "rx" ) "%s"
1118 0 : K( "tx" ) "%s"
1119 0 : K( "crds" ) "%s"
1120 0 : K( "peers" ) "%s ",
1121 0 : DIFF_LINK_BYTES( "net_gossvf", COUNTER, LINK, FRAG_CONSUMED_BYTES ),
1122 0 : DIFF_LINK_BYTES( "gossip_net", COUNTER, LINK, FRAG_CONSUMED_BYTES ),
1123 0 : COUNT( total_crds( t ) ),
1124 0 : contact_info );
1125 0 : if( FD_UNLIKELY( gossip_saturated ) ) PRINT( GREEN "✓" RESET DIM " saturated" RESET );
1126 0 : else PRINT( "%s " DIM "%3.0f%%" RESET, BAR( gossip_conv_pct, 8UL ), gossip_conv_pct );
1127 0 : PRINT( K( "busy" ) "%s%3.0f" U( "%%" ) RESET
1128 0 : K( "backp" ) "%s%3.0f" U( "%%" ) RESET CLEARLN "\n",
1129 0 : sev_color( gossip_busy_pct ), gossip_busy_pct,
1130 0 : sev_color( gossip_backp_pct ), gossip_backp_pct );
1131 0 : return 1U;
1132 0 : }
1133 :
1134 : static uint
1135 : write_repair( config_t const * config,
1136 : ulong const * cur_tile,
1137 : ulong const * cur_link,
1138 0 : ulong const * prev_link ) {
1139 0 : ulong repair_tile_idx = fd_topo_find_tile( &config->topo, "rotor", 0UL );
1140 0 : char const * repair_label = "rotor ";
1141 0 : if( repair_tile_idx==ULONG_MAX ) return 0U;
1142 0 : ulong highest_off = MIDX( GAUGE, ROTOR, HIGHEST_DELIVERED_SLOT );
1143 0 : ulong current_off = MIDX( GAUGE, ROTOR, HIGHEST_RECEIVED_SLOT );
1144 0 : ulong repair_slot = cur_tile[ repair_tile_idx*FD_METRICS_TOTAL_SZ+highest_off ];
1145 0 : ulong turbine_slot = cur_tile[ repair_tile_idx*FD_METRICS_TOTAL_SZ+current_off ];
1146 0 : turbine_slot = fd_ulong_max( turbine_slot, cur_tile[ repair_tile_idx*FD_METRICS_TOTAL_SZ+MIDX( GAUGE, ROTOR, CATCHUP_SLOT ) ] );
1147 0 : long repair_lag = (long)repair_slot-(long)turbine_slot;
1148 0 : PRINT( ROWH( "▲", RED, "%s" )
1149 0 : K( "rx" ) "%s"
1150 0 : K( "tx" ) "%s"
1151 0 : K( "repair slot" ) "%lu %s%+03ld" RESET
1152 0 : K( "turbine slot" ) "%lu" CLEARLN "\n",
1153 0 : repair_label,
1154 0 : DIFF_LINK_BYTES( "net_repair", COUNTER, LINK, FRAG_CONSUMED_BYTES ),
1155 0 : DIFF_LINK_BYTES( "repair_net", COUNTER, LINK, FRAG_CONSUMED_BYTES ),
1156 0 : repair_slot,
1157 0 : repair_lag<-4L ? RED : DIM,
1158 0 : repair_lag,
1159 0 : turbine_slot );
1160 0 : return 1U;
1161 0 : }
1162 :
1163 : static uint
1164 : write_rserve( config_t const * config,
1165 : ulong const * cur_tile,
1166 : ulong const * cur_link,
1167 0 : ulong const * prev_link ) {
1168 0 : ulong rserve_tile_idx = fd_topo_find_tile( &config->topo, "rserve", 0UL );
1169 0 : if( rserve_tile_idx==ULONG_MAX ) return 0UL;
1170 :
1171 0 : ulong const * t = cur_tile + rserve_tile_idx*FD_METRICS_TOTAL_SZ;
1172 :
1173 0 : ulong shreds_stored_sum = 0UL;
1174 0 : ulong num_stored_shreds = fd_ulong_min( shreds_stored_samples_idx, sizeof(shreds_stored_sample)/sizeof(shreds_stored_sample[0]));
1175 0 : for( ulong i=0UL; i<num_stored_shreds; i++ ) shreds_stored_sum += shreds_stored_sample[ i ];
1176 0 : char * shreds_stored = COUNTF( (double)shreds_stored_sum/window_seconds( num_stored_shreds ) );
1177 :
1178 0 : ulong shreds_cur = t[ MIDX( GAUGE, RSERVE, SHRED_USED ) ];
1179 0 : ulong shreds_max = t[ MIDX( GAUGE, RSERVE, SHRED_CAPACITY ) ];
1180 :
1181 0 : ulong valid_sum = 0UL;
1182 0 : ulong num_valid_samples = fd_ulong_min( rserve_rps_valid_samples_idx, sizeof(rserve_rps_valid_samples)/sizeof(rserve_rps_valid_samples[0]) );
1183 0 : for( ulong i=0UL; i<num_valid_samples; i++ ) valid_sum += rserve_rps_valid_samples[ i ];
1184 0 : char * valid_str = COUNTF( (double)valid_sum/window_seconds( num_valid_samples ) );
1185 :
1186 0 : ulong invalid_sum = 0UL;
1187 0 : ulong num_invalid_samples = fd_ulong_min( rserve_rps_invalid_samples_idx, sizeof(rserve_rps_invalid_samples)/sizeof(rserve_rps_invalid_samples[0]) );
1188 0 : for( ulong i=0UL; i<num_invalid_samples; i++ ) invalid_sum += rserve_rps_invalid_samples[ i ];
1189 0 : char * invalid_str = COUNTF( (double)invalid_sum/window_seconds( num_invalid_samples ) );
1190 :
1191 0 : ulong num_total_samples = fd_ulong_max( num_valid_samples, 1UL );
1192 0 : char * total_str = COUNTF( (double)(valid_sum+invalid_sum)/window_seconds( num_total_samples ) );
1193 :
1194 0 : ulong n_inv = fd_ulong_max( num_invalid_samples, 1UL );
1195 :
1196 0 : ulong miss_sum = 0UL;
1197 0 : for( ulong i=0UL; i<num_invalid_samples; i++ ) miss_sum += rserve_rps_miss_samples[ i ];
1198 0 : char * miss_str = COUNTF( (double)miss_sum/window_seconds( n_inv ) );
1199 :
1200 0 : ulong sigvfy_sum = 0UL;
1201 0 : for( ulong i=0UL; i<num_invalid_samples; i++ ) sigvfy_sum += rserve_rps_sigvfy_samples[ i ];
1202 0 : char * sigvfy_str = COUNTF( (double)sigvfy_sum/window_seconds( n_inv ) );
1203 :
1204 0 : ulong stale_sum = 0UL;
1205 0 : for( ulong i=0UL; i<num_invalid_samples; i++ ) stale_sum += rserve_rps_stale_samples[ i ];
1206 0 : char * stale_str = COUNTF( (double)stale_sum/window_seconds( n_inv ) );
1207 :
1208 0 : ulong other_sum = 0UL;
1209 0 : for( ulong i=0UL; i<num_invalid_samples; i++ ) other_sum += rserve_rps_other_rej_samples[ i ];
1210 0 : char * other_str = COUNTF( (double)other_sum/window_seconds( n_inv ) );
1211 :
1212 0 : ulong disk_used = t[ MIDX( GAUGE, RSERVE, DISK_USED_BYTES ) ];
1213 0 : ulong disk_alloc = t[ MIDX( GAUGE, RSERVE, DISK_PEAK_BYTES ) ];
1214 0 : double disk_gb = (double)disk_alloc/1e9;
1215 0 : double disk_pct = disk_alloc ? 100.0*(double)disk_used/(double)disk_alloc : 0.0;
1216 :
1217 0 : char * shreds_cur_str = COUNT( shreds_cur );
1218 0 : char * shreds_max_str = COUNT( shreds_max );
1219 :
1220 0 : PRINT( ROWH( "□", GREEN, "rserve " )
1221 0 : K( "rx" ) "%s"
1222 0 : K( "tx" ) "%s"
1223 0 : K( "shreds" ) "%s" U( "/s" ) " " U( "(%s / %s)" )
1224 0 : K( "disk" ) "%.1f" U( " GB" ) " %s%4.1f" U( "%%" ) RESET,
1225 0 : DIFF_LINK_BYTES( "net_rserve", COUNTER, LINK, FRAG_CONSUMED_BYTES ),
1226 0 : DIFF_LINK_BYTES( "rserve_net", COUNTER, LINK, FRAG_CONSUMED_BYTES ),
1227 0 : shreds_stored, shreds_cur_str, shreds_max_str,
1228 0 : disk_gb, sev_color( disk_pct ), disk_pct );
1229 0 : PRINT( K( "rps" ) "%s" U( "/s" ) " " U( "(%s valid, %s invalid)" ) CLEARLN "\n",
1230 0 : total_str, valid_str, invalid_str );
1231 :
1232 0 : if( FD_LIKELY( !watch_full ) ) return 1U;
1233 :
1234 0 : PRINT( ROWC
1235 0 : K( "miss" ) "%s" U( "/s" )
1236 0 : K( "sigvfy" ) "%s" U( "/s" )
1237 0 : K( "stale" ) "%s" U( "/s" )
1238 0 : K( "other" ) "%s" U( "/s" ) CLEARLN "\n",
1239 0 : miss_str, sigvfy_str, stale_str, other_str );
1240 0 : return 2U;
1241 0 : }
1242 :
1243 : static uint
1244 : write_replay( config_t const * config,
1245 0 : ulong const * cur_tile ) {
1246 0 : ulong repair_tile_idx = fd_topo_find_tile( &config->topo, "rotor", 0UL );
1247 0 : ulong replay_tile_idx = fd_topo_find_tile( &config->topo, "replay", 0UL );
1248 0 : if( replay_tile_idx==ULONG_MAX ) return 0U;
1249 :
1250 0 : ulong reset_slot = cur_tile[ replay_tile_idx*FD_METRICS_TOTAL_SZ+MIDX( GAUGE, REPLAY, RESET_SLOT ) ];
1251 0 : ulong next_leader_slot = cur_tile[ replay_tile_idx*FD_METRICS_TOTAL_SZ+MIDX( GAUGE, REPLAY, NEXT_LEADER_SLOT ) ];
1252 0 : ulong leader_slot = cur_tile[ replay_tile_idx*FD_METRICS_TOTAL_SZ+MIDX( GAUGE, REPLAY, LEADER_SLOT ) ];
1253 0 : char * next_leader_slot_str = fd_alloca_check( 1UL, 64UL );
1254 :
1255 0 : ulong turbine_slot;
1256 0 : if( repair_tile_idx!=ULONG_MAX ) {
1257 0 : turbine_slot = cur_tile[ repair_tile_idx*FD_METRICS_TOTAL_SZ+MIDX( GAUGE, ROTOR, HIGHEST_RECEIVED_SLOT ) ];
1258 0 : turbine_slot = fd_ulong_max( turbine_slot, cur_tile[ repair_tile_idx*FD_METRICS_TOTAL_SZ+MIDX( GAUGE, ROTOR, CATCHUP_SLOT ) ] );
1259 0 : } else {
1260 0 : turbine_slot = reset_slot;
1261 0 : }
1262 :
1263 0 : ulong slot_in_seconds = (ulong)((double)(next_leader_slot-reset_slot)*0.4);
1264 0 : if( FD_UNLIKELY( leader_slot ) ) FD_TEST( fd_cstr_printf_check( next_leader_slot_str, 64UL, NULL, "now" ) );
1265 0 : else if( FD_LIKELY( next_leader_slot>0UL ) ) FD_TEST( fd_cstr_printf_check( next_leader_slot_str, 64UL, NULL, "%lum %lus", slot_in_seconds/60UL, slot_in_seconds%60UL ) );
1266 0 : else FD_TEST( fd_cstr_printf_check( next_leader_slot_str, 64UL, NULL, "never" ) );
1267 :
1268 0 : ulong root_distance = cur_tile[ replay_tile_idx*FD_METRICS_TOTAL_SZ+MIDX( GAUGE, REPLAY, ROOT_SLOT_DISTANCE ) ];
1269 0 : ulong live_banks = cur_tile[ replay_tile_idx*FD_METRICS_TOTAL_SZ+MIDX( GAUGE, REPLAY, BANK_USED ) ];
1270 :
1271 0 : ulong sps_sum = 0UL;
1272 0 : ulong num_sps_samples = fd_ulong_min( sps_samples_idx, sizeof(sps_samples)/sizeof(sps_samples[0]));
1273 0 : for( ulong i=0UL; i<num_sps_samples; i++ ) sps_sum += sps_samples[ i ];
1274 0 : char * sps_str = COUNTF( (double)sps_sum/window_seconds( num_sps_samples ) );
1275 :
1276 0 : ulong tps_sum = 0UL;
1277 0 : ulong num_tps_samples = fd_ulong_min( tps_samples_idx, sizeof(tps_samples)/sizeof(tps_samples[0]));
1278 0 : for( ulong i=0UL; i<num_tps_samples; i++ ) tps_sum += tps_samples[ i ];
1279 0 : char * tps_str = COUNTF( (double)tps_sum/window_seconds( num_tps_samples ) );
1280 :
1281 0 : ulong cups_sum = 0UL;
1282 0 : ulong num_cups_samples = fd_ulong_min( cups_samples_idx, sizeof(cups_samples)/sizeof(cups_samples[0]));
1283 0 : for( ulong i=0UL; i<num_cups_samples; i++ ) cups_sum += cups_samples[ i ];
1284 0 : char * mcups_str = COUNTF( (double)cups_sum/window_seconds( num_cups_samples ) );
1285 :
1286 0 : long replay_lag = (long)reset_slot-(long)turbine_slot;
1287 0 : PRINT( ROWH( "▶", MAGENTA, "replay " )
1288 0 : K( "slot" ) BOLD "%lu" RESET " %s%+03ld" RESET
1289 0 : K( "cu/s" ) "%s"
1290 0 : K( "tps" ) "%s"
1291 0 : K( "sps" ) "%s"
1292 0 : K( "leader in" ) "%s"
1293 0 : K( "root dist" ) "%lu"
1294 0 : K( "banks" ) "%2lu" CLEARLN "\n",
1295 0 : reset_slot,
1296 0 : replay_lag<-4L ? RED : DIM,
1297 0 : replay_lag,
1298 0 : mcups_str,
1299 0 : tps_str,
1300 0 : sps_str,
1301 0 : next_leader_slot_str,
1302 0 : root_distance,
1303 0 : live_banks );
1304 0 : return 1U;
1305 0 : }
1306 :
1307 : #define VOTOR_RATE( metric ) (__extension__({ \
1308 : ulong cnt = diff_tile( config, "votor", prev_tile, cur_tile, MIDX( COUNTER, VOTOR, metric ) ); \
1309 : COUNTF( (double)cnt*1e9/(double)SNAP_DT_NS() ); \
1310 : }))
1311 :
1312 : static uint
1313 : write_votor( config_t const * config,
1314 : ulong const * cur_tile,
1315 0 : ulong const * prev_tile ) {
1316 0 : ulong votor_tile_idx = fd_topo_find_tile( &config->topo, "votor", 0UL );
1317 0 : if( votor_tile_idx==ULONG_MAX ) return 0U;
1318 0 : ulong const * t = &cur_tile[ votor_tile_idx*FD_METRICS_TOTAL_SZ ];
1319 :
1320 0 : ulong replay_tile_idx = fd_topo_find_tile( &config->topo, "replay", 0UL );
1321 0 : ulong reset_slot = replay_tile_idx!=ULONG_MAX ? cur_tile[ replay_tile_idx*FD_METRICS_TOTAL_SZ+MIDX( GAUGE, REPLAY, RESET_SLOT ) ] : 0UL;
1322 :
1323 0 : ulong finalized_slot = t[ MIDX( GAUGE, VOTOR, HIGHEST_FINAL_CERT_SLOT ) ];
1324 0 : ulong slots_used = t[ MIDX( GAUGE, VOTOR, VOTING_SLOT_USED ) ];
1325 0 : ulong slots_max = slots_used+t[ MIDX( GAUGE, VOTOR, VOTING_SLOT_FREE ) ];
1326 0 : ulong peers_out = t[ MIDX( GAUGE, VOTOR, CLIENT_CONN_USED )+FD_METRICS_ENUM_QUIC_CONN_STATE_V_ACTIVE_IDX ];
1327 0 : ulong peers_in = t[ MIDX( GAUGE, VOTOR, SERVER_CONN_USED )+FD_METRICS_ENUM_QUIC_CONN_STATE_V_ACTIVE_IDX ];
1328 0 : long rank = (long)t[ MIDX( GAUGE, VOTOR, RANK ) ];
1329 0 : double slots_pct = slots_max ? 100.0*(double)slots_used/(double)slots_max : 0.0;
1330 0 : long final_lag = (long)finalized_slot-(long)reset_slot;
1331 :
1332 0 : PRINT( ROWH( "◇", CYAN, "votor " )
1333 0 : K( "final" ) BOLD "%lu" RESET " %s%+03ld" RESET
1334 0 : K( "slots" ) "%s%lu" RESET U( "/%lu" ),
1335 0 : finalized_slot,
1336 0 : final_lag<-32L ? RED : DIM,
1337 0 : final_lag,
1338 0 : sev_color( slots_pct ), slots_used, slots_max );
1339 0 : if( FD_UNLIKELY( rank<0L ) ) {
1340 0 : PRINT( " " YELLOW "unstaked" RESET );
1341 0 : } else {
1342 0 : PRINT( K( "votes" ) "%s" U( "/s" )
1343 0 : K( "certs" ) "%s" U( "/s" )
1344 0 : K( "peers" ) "%lu" U( "out" ) " %lu" U( "in" )
1345 0 : K( "rank" ) "%ld",
1346 0 : VOTOR_RATE( VOTE_RX_SUCCESS ),
1347 0 : VOTOR_RATE( CERT_RX_SUCCESS ),
1348 0 : peers_out, peers_in,
1349 0 : rank );
1350 0 : }
1351 0 : PRINT( CLEARLN "\n" );
1352 0 : return 1U;
1353 0 : }
1354 :
1355 : #undef VOTOR_RATE
1356 :
1357 : static uint
1358 : write_gui( config_t const * config,
1359 : ulong const * cur_tile,
1360 0 : ulong const * prev_tile ) {
1361 0 : char const * gui_name = "gui";
1362 0 : ulong gui_tile_idx = fd_topo_find_tile( &config->topo, gui_name, 0UL );
1363 0 : if( FD_UNLIKELY( gui_tile_idx==ULONG_MAX ) ) return 0U;
1364 :
1365 0 : ulong off_conn_active, off_websocket_conn_active, off_websocket_frame_tx, off_websocket_frame_rx;
1366 0 : char * bytes_read_s;
1367 0 : char * bytes_written_s;
1368 0 : off_conn_active = MIDX( GAUGE, GUI, CONN_OPEN );
1369 0 : off_websocket_conn_active = MIDX( GAUGE, GUI, WEBSOCKET_CONN_OPEN );
1370 0 : off_websocket_frame_tx = MIDX( COUNTER, GUI, WEBSOCKET_FRAME_TX );
1371 0 : off_websocket_frame_rx = MIDX( COUNTER, GUI, WEBSOCKET_FRAME_RX );
1372 :
1373 0 : bytes_read_s = DIFF_BYTES( gui_name, COUNTER, GUI, CONN_RX_BYTES );
1374 0 : bytes_written_s = DIFF_BYTES( gui_name, COUNTER, GUI, CONN_TX_BYTES );
1375 :
1376 0 : ulong connection_count = cur_tile[ gui_tile_idx*FD_METRICS_TOTAL_SZ+off_conn_active ]+cur_tile[ gui_tile_idx*FD_METRICS_TOTAL_SZ+off_websocket_conn_active ];
1377 0 : ulong gui_total_ticks = total_regime( &cur_tile[ gui_tile_idx*FD_METRICS_TOTAL_SZ ] )-total_regime( &prev_tile[ gui_tile_idx*FD_METRICS_TOTAL_SZ ] );
1378 0 : gui_total_ticks = fd_ulong_max( gui_total_ticks, 1UL );
1379 0 : double gui_backp_pct = 100.0*(double)diff_tile_regime( config, gui_name, prev_tile, cur_tile, FD_METRICS_ENUM_TILE_REGIME_G_BACKPRESSURE_MASK )/(double)gui_total_ticks;
1380 0 : double gui_idle_pct = 100.0*(double)diff_tile_regime( config, gui_name, prev_tile, cur_tile, FD_METRICS_ENUM_TILE_REGIME_G_IDLE_MASK )/(double)gui_total_ticks;
1381 0 : double gui_busy_pct = 100.0 - gui_backp_pct - gui_idle_pct;
1382 :
1383 0 : ulong sent_frame_count = diff_tile( config, gui_name, prev_tile, cur_tile, off_websocket_frame_tx );
1384 0 : char * sent_frame_count_s = COUNT( sent_frame_count );
1385 0 : ulong received_frame_count = diff_tile( config, gui_name, prev_tile, cur_tile, off_websocket_frame_rx );
1386 :
1387 0 : PRINT( ROWH( "◉", CYAN, "gui " )
1388 0 : K( "conns" ) "%lu"
1389 0 : K( "frames" ) "%s" U( " in" ) " %s" U( " out" )
1390 0 : K( "bw" ) "%s" U( " in" ) " %s" U( " out" )
1391 0 : K( "busy" ) "%s%3.0f" U( "%%" ) RESET CLEARLN "\n",
1392 0 : connection_count,
1393 0 : COUNT( received_frame_count ),
1394 0 : sent_frame_count_s,
1395 0 : bytes_read_s,
1396 0 : bytes_written_s,
1397 0 : sev_color( gui_busy_pct ), gui_busy_pct );
1398 0 : return 1U;
1399 0 : }
1400 :
1401 : static uint
1402 : write_event( config_t const * config,
1403 0 : ulong const * cur_tile ) {
1404 0 : ulong event_tile_idx = fd_topo_find_tile( &config->topo, "event", 0UL );
1405 0 : if( event_tile_idx==ULONG_MAX ) return 0U;
1406 :
1407 0 : ulong connection_state = cur_tile[ event_tile_idx*FD_METRICS_TOTAL_SZ+MIDX( GAUGE, EVENT, CONN_STATE ) ];
1408 0 : char const * connection_state_str;
1409 0 : switch( connection_state ) {
1410 0 : case FD_EVENT_CLIENT_STATE_DISCONNECTED: connection_state_str = RED "disconnected" RESET; break;
1411 0 : case FD_EVENT_CLIENT_STATE_CONNECTING: connection_state_str = YELLOW "connecting" RESET; break;
1412 0 : case FD_EVENT_CLIENT_STATE_AUTHENTICATING: connection_state_str = YELLOW "authenticating" RESET; break;
1413 0 : case FD_EVENT_CLIENT_STATE_CONNECTED: connection_state_str = GREEN "connected" RESET; break;
1414 0 : default: connection_state_str = RED "unknown" RESET; break;
1415 0 : }
1416 :
1417 0 : ulong event_queue_count = cur_tile[ event_tile_idx*FD_METRICS_TOTAL_SZ+MIDX( GAUGE, EVENT, QUEUE_USED ) ];
1418 0 : ulong event_queue_unsent = cur_tile[ event_tile_idx*FD_METRICS_TOTAL_SZ+MIDX( GAUGE, EVENT, QUEUE_UNSENT_COUNT ) ];
1419 0 : ulong event_queue_unacked = event_queue_count>event_queue_unsent ? event_queue_count-event_queue_unsent : 0UL;
1420 0 : ulong event_last_acked_id = cur_tile[ event_tile_idx*FD_METRICS_TOTAL_SZ+MIDX( GAUGE, EVENT, LAST_ACKED_NONCE ) ];
1421 0 : ulong event_queue_drops = cur_tile[ event_tile_idx*FD_METRICS_TOTAL_SZ+MIDX( COUNTER, EVENT, QUEUE_EVICTED ) ];
1422 0 : ulong event_queue_bytes_used = cur_tile[ event_tile_idx*FD_METRICS_TOTAL_SZ+MIDX( GAUGE, EVENT, QUEUE_USED_BYTES ) ];
1423 0 : ulong event_queue_bytes_capacity = cur_tile[ event_tile_idx*FD_METRICS_TOTAL_SZ+MIDX( GAUGE, EVENT, QUEUE_CAPACITY_BYTES ) ];
1424 :
1425 0 : double event_queue_pct_full = event_queue_bytes_capacity>0UL ? 100.0*(double)event_queue_bytes_used/(double)event_queue_bytes_capacity : 0.0;
1426 :
1427 0 : ulong events_sent_sum = 0UL;
1428 0 : ulong num_events_sent_samples = fd_ulong_min( events_sent_samples_idx, sizeof(events_sent_samples)/sizeof(events_sent_samples[0]));
1429 0 : for( ulong i=0UL; i<num_events_sent_samples; i++ ) events_sent_sum += events_sent_samples[ i ];
1430 0 : char * events_sent_str = COUNTF( (double)events_sent_sum/window_seconds( num_events_sent_samples ) );
1431 :
1432 0 : ulong events_acked_sum = 0UL;
1433 0 : ulong num_events_acked_samples = fd_ulong_min( events_acked_samples_idx, sizeof(events_acked_samples)/sizeof(events_acked_samples[0]));
1434 0 : for( ulong i=0UL; i<num_events_acked_samples; i++ ) events_acked_sum += events_acked_samples[ i ];
1435 0 : char * events_acked_str = COUNTF( (double)events_acked_sum/window_seconds( num_events_acked_samples ) );
1436 :
1437 0 : ulong bytes_written_sum = 0UL;
1438 0 : ulong num_bytes_written_samples = fd_ulong_min( event_bytes_written_samples_idx, sizeof(event_bytes_written_samples)/sizeof(event_bytes_written_samples[0]));
1439 0 : for( ulong i=0UL; i<num_bytes_written_samples; i++ ) bytes_written_sum += event_bytes_written_samples[ i ];
1440 0 : long bytes_written_per_sec = (long)((double)bytes_written_sum/window_seconds( num_bytes_written_samples ));
1441 0 : char * bytes_written_str = fmt_bytes( fd_alloca_check( 1UL, 64UL ), 64UL, bytes_written_per_sec );
1442 :
1443 0 : ulong bytes_read_sum = 0UL;
1444 0 : ulong num_bytes_read_samples = fd_ulong_min( event_bytes_read_samples_idx, sizeof(event_bytes_read_samples)/sizeof(event_bytes_read_samples[0]));
1445 0 : for( ulong i=0UL; i<num_bytes_read_samples; i++ ) bytes_read_sum += event_bytes_read_samples[ i ];
1446 0 : long bytes_read_per_sec = (long)((double)bytes_read_sum/window_seconds( num_bytes_read_samples ));
1447 0 : char * bytes_read_str = fmt_bytes( fd_alloca_check( 1UL, 64UL ), 64UL, bytes_read_per_sec );
1448 :
1449 0 : char * event_queue_unacked_s = COUNT( event_queue_unacked );
1450 0 : char * event_queue_unsent_s = COUNT( event_queue_unsent );
1451 0 : char * event_last_acked_id_s = COUNT( event_last_acked_id );
1452 :
1453 0 : PRINT( ROWH( "◆", YELLOW, "event " )
1454 0 : K( "state" ) "%s"
1455 0 : K( "unacked" ) "%s"
1456 0 : K( "queue" ) "%s"
1457 0 : K( "sent" ) "%s" U( "/s" )
1458 0 : K( "acked" ) "%s" U( "/s" )
1459 0 : K( "last ack" ) "%s"
1460 0 : K( "bw" ) "%s" U( " in" ) " %s" U( " out" )
1461 0 : K( "drops" ) "%s"
1462 0 : K( "full" ) "%s%3.0f" U( "%%" ) RESET CLEARLN "\n",
1463 0 : connection_state_str,
1464 0 : event_queue_unacked_s,
1465 0 : event_queue_unsent_s,
1466 0 : events_sent_str,
1467 0 : events_acked_str,
1468 0 : event_last_acked_id_s,
1469 0 : bytes_read_str,
1470 0 : bytes_written_str,
1471 0 : COUNT( event_queue_drops ),
1472 0 : sev_color( event_queue_pct_full ), event_queue_pct_full );
1473 0 : return 1U;
1474 0 : }
1475 :
1476 : /* Pad a value to a fixed display width so fields that fill in after
1477 : boot (identity, vote, shred version, ...) never shift the columns
1478 : to their right on redraw. Unknown values render as a dim "-". */
1479 : static char *
1480 : fmt_field( char * buf,
1481 : ulong buf_sz,
1482 : char const * val,
1483 : int known,
1484 0 : int width ) {
1485 0 : if( FD_LIKELY( known ) ) FD_TEST( fd_cstr_printf_check( buf, buf_sz, NULL, "%-*s", width, val ) );
1486 0 : else FD_TEST( fd_cstr_printf_check( buf, buf_sz, NULL, DIM "%-*s" RESET, width, "-" ) );
1487 0 : return buf;
1488 0 : }
1489 :
1490 : static char *
1491 : base58_short( char out[ static 16 ],
1492 0 : uchar const key[ static 32 ] ) {
1493 0 : char enc[ FD_BASE58_ENCODED_32_SZ ];
1494 0 : ulong len;
1495 0 : fd_base58_encode_32( key, &len, enc );
1496 0 : char * p = fd_cstr_init( out );
1497 0 : p = fd_cstr_append_text( p, enc, 6 ),
1498 0 : p = fd_cstr_append_text( p, "…", sizeof("…")-1 ),
1499 0 : p = fd_cstr_append_text( p, enc+len-6, 6 );
1500 0 : fd_cstr_fini( p );
1501 0 : return out;
1502 0 : }
1503 :
1504 : static void
1505 : fmt_balance( char out[ static 64 ],
1506 0 : ulong lamports ) {
1507 0 : ulong maj = lamports / 1000000000UL;
1508 0 : ulong min = lamports % 1000000000UL;
1509 0 : fd_cstr_printf_check( out, 64UL, NULL, "%lu.%09lu", maj, min );
1510 0 : }
1511 :
1512 : static uint
1513 : write_node_info( config_t const * config,
1514 : ulong const * cur_tile,
1515 0 : fd_node_info_t const * node_info ) {
1516 :
1517 0 : char identity_b58[ FD_BASE58_ENCODED_32_SZ ];
1518 0 : int has_identity = node_info && !fd_pubkey_check_zero( &node_info->identity );
1519 0 : if( FD_LIKELY( has_identity ) ) fd_base58_encode_32( node_info->identity.key, NULL, identity_b58 );
1520 :
1521 0 : char vote_acc_b58[ FD_BASE58_ENCODED_32_SZ ];
1522 0 : int has_vote_acc = node_info && !fd_pubkey_check_zero( &node_info->vote_account );
1523 0 : if( FD_LIKELY( has_vote_acc ) ) fd_base58_encode_32( node_info->vote_account.key, NULL, vote_acc_b58 );
1524 :
1525 0 : char shred_ver[ 16 ];
1526 0 : int has_shred_ver = 1;
1527 0 : ulong ipecho_idx = fd_topo_find_tile( &config->topo, "ipecho", 0UL );
1528 0 : ushort shred_version = 0;
1529 0 : if( FD_LIKELY( ipecho_idx!=ULONG_MAX ) ) shred_version = (ushort)cur_tile[ ipecho_idx*FD_METRICS_TOTAL_SZ+MIDX( GAUGE, IPECHO, SHRED_VERSION ) ];
1530 0 : if( shred_version ) {
1531 0 : fd_cstr_printf_check( shred_ver, sizeof(shred_ver), NULL, "%hu", shred_version );
1532 0 : } else if( config->consensus.expected_shred_version ) {
1533 0 : fd_cstr_printf_check( shred_ver, sizeof(shred_ver), NULL, "(%hu)", config->consensus.expected_shred_version );
1534 0 : } else {
1535 0 : has_shred_ver = 0;
1536 0 : }
1537 :
1538 0 : char genesis_hash_b58[ FD_BASE58_ENCODED_32_SZ ] = {0};
1539 0 : char genesis_short[ 16 ] = {0};
1540 0 : int has_genesis_b58 = 0;
1541 0 : if( FD_LIKELY( node_info && !fd_pubkey_check_zero( &node_info->genesis_hash ) ) ) {
1542 0 : fd_base58_encode_32( node_info->genesis_hash.key, NULL, genesis_hash_b58 );
1543 0 : base58_short( genesis_short, node_info->genesis_hash.key );
1544 0 : has_genesis_b58 = 1;
1545 0 : }
1546 :
1547 0 : char const * cluster_str = "";
1548 0 : char const * cluster_color = "";
1549 0 : if( has_genesis_b58 ) {
1550 0 : ulong cluster_id = fd_genesis_cluster_identify( genesis_hash_b58 );
1551 0 : cluster_str = cluster_id==FD_CLUSTER_MAINNET_BETA ? "mainnet" : fd_genesis_cluster_name( cluster_id );
1552 0 : switch( cluster_id ) {
1553 0 : case FD_CLUSTER_MAINNET_BETA: cluster_color = CLUS_MAINNET; break;
1554 0 : case FD_CLUSTER_TESTNET: cluster_color = CLUS_TESTNET; break;
1555 0 : case FD_CLUSTER_DEVNET: cluster_color = CLUS_DEVNET; break;
1556 0 : case FD_CLUSTER_PYTHNET: cluster_color = CLUS_PYTHNET; break;
1557 0 : case FD_CLUSTER_PYTHTEST: cluster_color = CLUS_PYTHTEST; break;
1558 0 : default: cluster_color = CLUS_UNKNOWN; break;
1559 0 : }
1560 0 : }
1561 :
1562 0 : char uptime_str[ 32UL ];
1563 0 : int has_uptime = 1;
1564 0 : long now = fd_log_wallclock();
1565 0 : if( FD_LIKELY( config->boot_timestamp_nanos>0L && now>config->boot_timestamp_nanos ) ) {
1566 0 : ulong elapsed_s = (ulong)( (now - config->boot_timestamp_nanos) / (long)1e9 );
1567 0 : ulong days = elapsed_s / 86400UL;
1568 0 : ulong hours = (elapsed_s % 86400UL) / 3600UL;
1569 0 : ulong mins = (elapsed_s % 3600UL) / 60UL;
1570 0 : ulong secs = elapsed_s % 60UL;
1571 0 : if( days ) fd_cstr_printf_check( uptime_str, sizeof(uptime_str), NULL, "%lud %luh %lum", days, hours, mins );
1572 0 : else if( hours ) fd_cstr_printf_check( uptime_str, sizeof(uptime_str), NULL, "%luh %lum %lus", hours, mins, secs );
1573 0 : else fd_cstr_printf_check( uptime_str, sizeof(uptime_str), NULL, "%lum %lus", mins, secs );
1574 0 : } else {
1575 0 : has_uptime = 0;
1576 0 : }
1577 :
1578 0 : ulong replay_idx = fd_topo_find_tile( &config->topo, "replay", 0UL );
1579 0 : if( FD_UNLIKELY( replay_idx==ULONG_MAX ) ) return 0U;
1580 0 : ulong const * replay_metrics = &cur_tile[ replay_idx*FD_METRICS_TOTAL_SZ ];
1581 0 : ulong identity_balance = replay_metrics[ MIDX( GAUGE, REPLAY, IDENTITY_BALANCE_LAMPORTS ) ];
1582 0 : ulong stake_amount = replay_metrics[ MIDX( GAUGE, REPLAY, ACTIVE_STAKE_LAMPORTS ) ];
1583 0 : ulong epoch_credits = replay_metrics[ MIDX( GAUGE, REPLAY, EPOCH_CREDITS ) ];
1584 0 : ulong tot_stake = replay_metrics[ MIDX( GAUGE, REPLAY, CLUSTER_ACTIVE_STAKE_LAMPORTS ) ];
1585 0 : char identity_balance_str[ 64 ]; fmt_balance( identity_balance_str, identity_balance );
1586 0 : char stake_amount_str [ 64 ]; fmt_balance( stake_amount_str, stake_amount );
1587 0 : double stake_percent = 0.0;
1588 0 : if( tot_stake>0UL ) stake_percent = 100.0*(double)stake_amount/(double)tot_stake;
1589 :
1590 : /* Fixed width slots (base58 keys are at most 44 chars) so fields
1591 : that appear over time never shift their neighbors. */
1592 0 : char id_field [ 64 ]; fmt_field( id_field, sizeof(id_field), identity_b58, has_identity, 44 );
1593 0 : char vote_field[ 64 ]; fmt_field( vote_field, sizeof(vote_field), vote_acc_b58, has_vote_acc, 44 );
1594 0 : char clus_field[ 32 ]; fmt_field( clus_field, sizeof(clus_field), cluster_str, has_genesis_b58, 8 );
1595 0 : char up_field [ 64 ]; fmt_field( up_field, sizeof(up_field), uptime_str, has_uptime, 12 );
1596 0 : char sv_field [ 32 ]; fmt_field( sv_field, sizeof(sv_field), shred_ver, has_shred_ver, 7 );
1597 :
1598 0 : PRINT( ROWH( "◈", ORANGE, "node " )
1599 0 : K( "cluster" ) "%s" BOLD "%s" RESET
1600 0 : K( "id" ) BOLD "%s" RESET
1601 0 : K( "vote" ) "%s"
1602 0 : K( "uptime" ) "%s"
1603 0 : K( "shred" ) "%s"
1604 0 : K( "genesis" ) "%s",
1605 0 : cluster_color,
1606 0 : clus_field,
1607 0 : id_field,
1608 0 : vote_field,
1609 0 : up_field,
1610 0 : sv_field,
1611 0 : has_genesis_b58 ? genesis_short : DIM "-" RESET );
1612 0 : PRINT( CLEARLN "\n" );
1613 :
1614 0 : if( FD_LIKELY( !watch_full ) ) return 1U;
1615 :
1616 0 : PRINT( ROWC
1617 0 : K( "balance" ) "%s" U( " SOL" )
1618 0 : K( "stake" ) "%s" U( " SOL" ) " " U( "(%.2f%%)" )
1619 0 : K( "credits" ) "%lu" CLEARLN "\n",
1620 0 : identity_balance_str, stake_amount_str, stake_percent, epoch_credits );
1621 :
1622 0 : return 2U;
1623 0 : }
1624 :
1625 : static uint
1626 : write_backup( config_t const * config,
1627 0 : ulong const * cur_tile ) {
1628 0 : ulong snapmk_idx = fd_topo_find_tile( &config->topo, "snapmk", 0UL );
1629 0 : ulong snapsv_idx = fd_topo_find_tile( &config->topo, "snapsv", 0UL );
1630 0 : ulong conns = snapsv_idx==ULONG_MAX ? 0UL : cur_tile[ snapsv_idx*FD_METRICS_TOTAL_SZ+MIDX( GAUGE, SNAPSV, CONN_OPEN ) ];
1631 0 : ulong state = snapmk_idx==ULONG_MAX ? SNAPMK_STATE_IDLE : cur_tile[ snapmk_idx*FD_METRICS_TOTAL_SZ+MIDX( GAUGE, SNAPMK, JOB_STATE ) ];
1632 0 : int active = state>=SNAPMK_STATE_START && state<=SNAPMK_STATE_FAIL;
1633 0 : int server_on = conns>0UL;
1634 0 : if( FD_UNLIKELY( !active && !server_on ) ) return 0U;
1635 :
1636 0 : ulong upload_sum = 0UL;
1637 0 : ulong upload_cnt = fd_ulong_min( backup_upload_idx, sizeof(backup_upload_samples)/sizeof(backup_upload_samples[0]) );
1638 0 : for( ulong i=0UL; i<upload_cnt; i++ ) upload_sum += backup_upload_samples[ i ];
1639 0 : double upload_mbps = upload_cnt ? 8.0*(double)upload_sum/window_seconds( upload_cnt )/1e6 : 0.0;
1640 :
1641 0 : PRINT( ROWH( "□", BLUE, "backup " ) );
1642 0 : if( FD_LIKELY( server_on ) ) {
1643 0 : PRINT( K( "conns" ) "%3lu" K( "upload" ) "%4.0f" U( " Mbit/s" ), conns, upload_mbps );
1644 0 : }
1645 0 : if( FD_UNLIKELY( !active ) ) {
1646 0 : PRINT( CLEARLN "\n" );
1647 0 : return 1U;
1648 0 : }
1649 :
1650 0 : ulong snapmk_off = snapmk_idx*FD_METRICS_TOTAL_SZ;
1651 0 : ulong full_slot = cur_tile[ snapmk_off+MIDX( GAUGE, SNAPMK, LAST_STARTED_SLOT_FULL ) ];
1652 0 : ulong incr_slot = cur_tile[ snapmk_off+MIDX( GAUGE, SNAPMK, LAST_STARTED_SLOT_INCREMENTAL ) ];
1653 0 : int incremental = incr_slot>full_slot;
1654 :
1655 0 : double progress = 0.0;
1656 0 : if( FD_LIKELY( !incremental && state==SNAPMK_STATE_ACCDB_DISK ) ) {
1657 0 : ulong snaprd_idx = fd_topo_find_tile( &config->topo, "snaprd", 0UL );
1658 0 : if( FD_LIKELY( snaprd_idx!=ULONG_MAX ) ) {
1659 0 : ulong snaprd_off = snaprd_idx*FD_METRICS_TOTAL_SZ;
1660 0 : ulong done = cur_tile[ snaprd_off+MIDX( GAUGE, SNAPRD, EXPORT_PROGRESS_BYTES ) ];
1661 0 : ulong total = cur_tile[ snaprd_off+MIDX( GAUGE, SNAPRD, EXPORT_EXPECTED_BYTES ) ];
1662 0 : if( FD_LIKELY( total ) ) progress = 100.0*(double)fd_ulong_min( done, total )/(double)total;
1663 0 : }
1664 0 : } else if( FD_LIKELY( !incremental && state>SNAPMK_STATE_ACCDB_DISK && state<=SNAPMK_STATE_DONE ) ) {
1665 0 : progress = 100.0;
1666 0 : } else if( FD_UNLIKELY( incremental && state==SNAPMK_STATE_ACCDB_DELTA ) ) {
1667 0 : ulong done = cur_tile[ snapmk_off+MIDX( GAUGE, SNAPMK, INCREMENTAL_ACCOUNT_PROGRESS ) ];
1668 0 : ulong total = cur_tile[ snapmk_off+MIDX( GAUGE, SNAPMK, INCREMENTAL_ACCOUNT_EXPECTED ) ];
1669 0 : progress = total ? 100.0*(double)fd_ulong_min( done, total )/(double)total : 100.0;
1670 0 : } else if( FD_UNLIKELY( incremental && state>SNAPMK_STATE_ACCDB_DELTA && state<=SNAPMK_STATE_DONE ) ) {
1671 0 : progress = 100.0;
1672 0 : }
1673 :
1674 0 : ulong acc_sum = 0UL;
1675 0 : ulong acc_cnt = fd_ulong_min( backup_acc_idx, sizeof(backup_acc_samples)/sizeof(backup_acc_samples[0]) );
1676 0 : for( ulong i=0UL; i<acc_cnt; i++ ) acc_sum += backup_acc_samples[ i ];
1677 :
1678 0 : ulong read_sum = 0UL;
1679 0 : ulong read_cnt = fd_ulong_min( backup_read_idx, sizeof(backup_read_samples)/sizeof(backup_read_samples[0]) );
1680 0 : for( ulong i=0UL; i<read_cnt; i++ ) read_sum += backup_read_samples[ i ];
1681 :
1682 0 : ulong comp_sum = 0UL;
1683 0 : ulong comp_cnt = fd_ulong_min( backup_comp_idx, sizeof(backup_comp_samples)/sizeof(backup_comp_samples[0]) );
1684 0 : for( ulong i=0UL; i<comp_cnt; i++ ) comp_sum += backup_comp_samples[ i ];
1685 :
1686 0 : double accounts_per_second = acc_cnt ? (double)acc_sum/window_seconds( acc_cnt ) : 0.0;
1687 0 : double read_mbps = read_cnt ? (double)read_sum/window_seconds( read_cnt )/1e6 : 0.0;
1688 0 : double comp_mbps = comp_cnt ? (double)comp_sum/window_seconds( comp_cnt )/1e6 : 0.0;
1689 0 : char * accounts_rate = COUNTF_T( accounts_per_second );
1690 :
1691 0 : PRINT( " %s " BOLD "%5.1f" RESET U( "%%" )
1692 0 : K( "type" ) "%-4s"
1693 0 : K( "slot" ) "%10lu"
1694 0 : K( "acc" ) "%s" U( "/s" )
1695 0 : K( "read" ) "%4.0f" U( " MB/s" )
1696 0 : K( "comp" ) "%4.0f" U( " MB/s" ) CLEARLN "\n",
1697 0 : BAR( progress, 20UL ), progress,
1698 0 : incremental ? "incr" : "full",
1699 0 : incremental ? incr_slot : full_slot,
1700 0 : accounts_rate,
1701 0 : read_mbps,
1702 0 : comp_mbps );
1703 0 : return 1U;
1704 0 : }
1705 :
1706 : static void
1707 : write_summary( config_t const * config,
1708 : fd_node_info_box_t const * shinfo,
1709 : ulong const * cur_tile,
1710 : ulong const * prev_tile,
1711 : ulong const * cur_link,
1712 0 : ulong const * prev_link ) {
1713 0 : (void)config;
1714 0 : (void)prev_tile;
1715 0 : (void)cur_tile;
1716 :
1717 0 : if( FD_UNLIKELY( !ended_on_newline ) ) PRINT( "\n" );
1718 0 : PRINT( "\033[?7l" ); /* disable autowrap mode */
1719 0 : lines_printed = 0UL;
1720 :
1721 0 : ulong snapct_idx = fd_topo_find_tile( &config->topo, "snapct", 0UL );
1722 0 : int shutdown = 1;
1723 0 : if( FD_LIKELY( snapct_idx!=ULONG_MAX ) ) shutdown = cur_tile[ snapct_idx*FD_METRICS_TOTAL_SZ+MIDX( GAUGE, SNAPCT, LOAD_STATE ) ]==FD_SNAPCT_STATE_SHUTDOWN;
1724 :
1725 0 : static long snap_shutdown_time = 0L;
1726 0 : if( FD_UNLIKELY( !snap_shutdown_time && !shutdown ) ) snap_shutdown_time = 1L; /* Was not shutdown on boot */
1727 0 : if( FD_UNLIKELY( !snap_shutdown_time && shutdown ) ) snap_shutdown_time = 2L; /* Was shutdown on boot */
1728 0 : if( FD_UNLIKELY( snap_shutdown_time==1L && shutdown ) ) snap_shutdown_time = fd_log_wallclock();
1729 :
1730 0 : fd_node_info_t node_info[1] = {0};
1731 0 : if( FD_LIKELY( shinfo ) ) fd_node_info_read( node_info, shinfo );
1732 0 : lines_printed += write_node_info( config, cur_tile, node_info );
1733 :
1734 0 : if( FD_UNLIKELY( write_bench( config, cur_tile, prev_tile ) ) ) lines_printed++;
1735 :
1736 0 : ulong backt_idx = fd_topo_find_tile( &config->topo, "backt", 0UL );
1737 0 : if( FD_UNLIKELY( backt_idx!=ULONG_MAX ) ) {
1738 0 : lines_printed++;
1739 0 : write_backtest( config, cur_tile );
1740 0 : }
1741 :
1742 0 : long now = fd_log_wallclock();
1743 0 : if( FD_UNLIKELY( snap_shutdown_time==1L || now<snap_shutdown_time+(long)2e9 ) ) {
1744 0 : lines_printed++;
1745 0 : write_snapshots( config, cur_tile, prev_tile );
1746 0 : }
1747 :
1748 0 : lines_printed += write_accdb( config, cur_tile, prev_tile );
1749 0 : lines_printed += write_wfs( config, cur_tile );
1750 0 : lines_printed += write_gossip( config, cur_tile, prev_tile, cur_link, prev_link );
1751 0 : lines_printed += write_repair( config, cur_tile, cur_link, prev_link );
1752 0 : lines_printed += write_rserve( config, cur_tile, cur_link, prev_link );
1753 0 : lines_printed += write_replay( config, cur_tile );
1754 0 : lines_printed += write_votor( config, cur_tile, prev_tile );
1755 0 : lines_printed += write_gui( config, cur_tile, prev_tile );
1756 0 : lines_printed += write_event( config, cur_tile );
1757 0 : lines_printed += write_backup( config, cur_tile );
1758 :
1759 0 : PRINT( "\033[?7h" ); /* enable autowrap mode */
1760 0 : }
1761 :
1762 : static void
1763 : snap_tiles( fd_topo_t const * topo,
1764 0 : ulong * tiles ) {
1765 0 : for( ulong i=0UL; i<topo->tile_cnt; i++ ) {
1766 0 : fd_topo_tile_t const * tile = &topo->tiles[ i ];
1767 0 : volatile ulong const * metrics = fd_metrics_tile( tile->metrics );
1768 0 : FD_TEST( metrics );
1769 0 : for( ulong j=0UL; j<FD_METRICS_TOTAL_SZ/8UL; j++ ) tiles[ i*FD_METRICS_TOTAL_SZ+j ] = metrics[ j ];
1770 0 : }
1771 0 : }
1772 :
1773 : static void
1774 : snap_links( fd_topo_t const * topo,
1775 0 : ulong * links ) {
1776 0 : ulong overall_polled_idx = 0UL;
1777 :
1778 0 : for( ulong i=0UL; i<topo->tile_cnt; i++ ) {
1779 0 : fd_topo_tile_t const * tile = &topo->tiles[ i ];
1780 :
1781 0 : ulong polled_in_idx = 0UL;
1782 0 : for( ulong j=0UL; j<topo->tiles[ i ].in_cnt; j++ ) {
1783 0 : if( FD_UNLIKELY( !tile->in_link_poll[ j ] ) ) continue;
1784 :
1785 0 : volatile ulong const * metrics = fd_metrics_link_in( tile->metrics, polled_in_idx );
1786 0 : FD_TEST( metrics );
1787 0 : for( ulong k=0UL; k<FD_METRICS_ALL_LINK_IN_TOTAL; k++ ) links[ overall_polled_idx*8UL+k ] = metrics[ k ];
1788 0 : polled_in_idx++;
1789 0 : overall_polled_idx++;
1790 0 : }
1791 0 : }
1792 0 : }
1793 :
1794 : static ulong tiles[ 2UL*FD_TILE_MAX*FD_METRICS_TOTAL_SZ ];
1795 : static ulong links[ 2UL*4096UL*8UL*FD_METRICS_ALL_LINK_IN_TOTAL ];
1796 :
1797 : static void
1798 : run( config_t const * config,
1799 0 : int drain_output_fd ) {
1800 0 : (void)config;
1801 0 : (void)drain_output_fd;
1802 :
1803 0 : ulong node_info_obj_id = fd_pod_query_ulong( config->topo.props, "node_info", ULONG_MAX );
1804 0 : fd_node_info_box_t * node_info = node_info_obj_id==ULONG_MAX ? NULL :
1805 0 : fd_node_info_box_join( fd_topo_obj_laddr( &config->topo, node_info_obj_id ) );
1806 0 : FD_TEST( node_info_obj_id==ULONG_MAX || node_info );
1807 :
1808 0 : ulong tile_cnt = config->topo.tile_cnt;
1809 :
1810 0 : ulong cons_cnt = 0UL;
1811 0 : for( ulong i=0UL; i<config->topo.tile_cnt; i++ ) {
1812 0 : for( ulong j=0UL; j<config->topo.tiles[ i ].in_cnt; j++ ) {
1813 0 : if( FD_UNLIKELY( config->topo.tiles[ i ].in_link_poll[ j ] ) ) cons_cnt++;
1814 0 : }
1815 0 : }
1816 :
1817 0 : FD_TEST( tile_cnt<=FD_TILE_MAX );
1818 0 : FD_TEST( cons_cnt<=4096UL );
1819 :
1820 0 : snap_tiles( &config->topo, tiles );
1821 0 : fd_memcpy( tiles+tile_cnt*FD_METRICS_TOTAL_SZ, tiles, tile_cnt*FD_METRICS_TOTAL_SZ*sizeof(ulong) );
1822 :
1823 0 : snap_links( &config->topo, links );
1824 0 : fd_memcpy( links+(cons_cnt*8UL*FD_METRICS_ALL_LINK_IN_TOTAL), links, cons_cnt*8UL*FD_METRICS_ALL_LINK_IN_TOTAL*sizeof(ulong) );
1825 :
1826 0 : snap_cur_nanos = wallclock_monotonic();
1827 0 : snap_prev_nanos = snap_cur_nanos-(long)1e7;
1828 :
1829 0 : ulong last_snap = 1UL;
1830 :
1831 :
1832 0 : frame_len = 0UL;
1833 0 : write_summary( config, node_info, tiles+last_snap*tile_cnt*FD_METRICS_TOTAL_SZ, tiles+(1UL-last_snap)*tile_cnt*FD_METRICS_TOTAL_SZ, links+last_snap*(cons_cnt*8UL*FD_METRICS_ALL_LINK_IN_TOTAL), links+(1UL-last_snap)*(cons_cnt*8UL*FD_METRICS_ALL_LINK_IN_TOTAL) );
1834 0 : flush_frame();
1835 :
1836 0 : long next = fd_log_wallclock()+(long)1e7;
1837 0 : for(;;) {
1838 0 : long timeout_ns = next-fd_log_wallclock();
1839 0 : if( FD_LIKELY( timeout_ns>0L ) ) {
1840 0 : fd_set rfds;
1841 0 : FD_ZERO( &rfds );
1842 0 : if( FD_LIKELY( drain_output_fd>=0 ) ) FD_SET( drain_output_fd, &rfds );
1843 0 : struct timeval tv = { .tv_sec = timeout_ns/(long)1e9, .tv_usec = (timeout_ns%(long)1e9)/1000L+1L };
1844 0 : if( FD_UNLIKELY( -1==select( drain_output_fd>=0 ? drain_output_fd+1 : 0, drain_output_fd>=0 ? &rfds : NULL, NULL, NULL, &tv ) && errno!=EINTR ) )
1845 0 : FD_LOG_ERR(( "select() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
1846 0 : }
1847 0 : if( FD_UNLIKELY( drain_output_fd>=0 ) ) {
1848 0 : if( FD_UNLIKELY( drain( drain_output_fd ) ) ) {
1849 0 : frame_len = 0UL;
1850 0 : write_summary( config, node_info, tiles+last_snap*tile_cnt*FD_METRICS_TOTAL_SZ, tiles+(1UL-last_snap)*tile_cnt*FD_METRICS_TOTAL_SZ, links+last_snap*(cons_cnt*8UL*FD_METRICS_ALL_LINK_IN_TOTAL), links+(1UL-last_snap)*(cons_cnt*8UL*FD_METRICS_ALL_LINK_IN_TOTAL) );
1851 0 : flush_frame();
1852 0 : }
1853 0 : }
1854 :
1855 0 : long now = fd_log_wallclock();
1856 0 : if( FD_UNLIKELY( now>=next ) ) {
1857 0 : last_snap = 1UL-last_snap;
1858 0 : snap_tiles( &config->topo, tiles+last_snap*tile_cnt*FD_METRICS_TOTAL_SZ );
1859 0 : snap_links( &config->topo, links+last_snap*(cons_cnt*8UL*FD_METRICS_ALL_LINK_IN_TOTAL) );
1860 0 : snap_prev_nanos = snap_cur_nanos;
1861 0 : snap_cur_nanos = wallclock_monotonic();
1862 0 : snap_dt_samples[ (snap_dt_idx++)%(sizeof(snap_dt_samples)/sizeof(snap_dt_samples[0])) ] = snap_cur_nanos-snap_prev_nanos;
1863 :
1864 : /* Bench */
1865 0 : tps_sent_samples[ tps_sent_samples_idx%(sizeof(tps_sent_samples)/sizeof(tps_sent_samples[0])) ] = diff_tile( config, "benchs", tiles+(1UL-last_snap)*tile_cnt*FD_METRICS_TOTAL_SZ, tiles+last_snap*tile_cnt*FD_METRICS_TOTAL_SZ, MIDX( COUNTER, BENCHS, TXN_TX ) );
1866 0 : tps_sent_samples_idx++;
1867 :
1868 : /* Replay */
1869 0 : sps_samples[ sps_samples_idx%(sizeof(sps_samples)/sizeof(sps_samples[0])) ] = diff_tile( config, "replay", tiles+(1UL-last_snap)*tile_cnt*FD_METRICS_TOTAL_SZ, tiles+last_snap*tile_cnt*FD_METRICS_TOTAL_SZ, MIDX( COUNTER, REPLAY, BANK_FROZEN ) );
1870 0 : sps_samples_idx++;
1871 0 : tps_samples[ tps_samples_idx%(sizeof(tps_samples)/sizeof(tps_samples[0])) ] = diff_tile( config, "replay", tiles+(1UL-last_snap)*tile_cnt*FD_METRICS_TOTAL_SZ, tiles+last_snap*tile_cnt*FD_METRICS_TOTAL_SZ, MIDX( GAUGE, REPLAY, CHAIN_TXN_HEIGHT ) );
1872 0 : tps_samples_idx++;
1873 0 : cups_samples[ cups_samples_idx%(sizeof(cups_samples)/sizeof(cups_samples[0])) ] =
1874 0 : diff_tile( config, "execrp", tiles+(1UL-last_snap)*tile_cnt*FD_METRICS_TOTAL_SZ, tiles+last_snap*tile_cnt*FD_METRICS_TOTAL_SZ, MIDX( COUNTER, EXECRP, CU_CONSUMED ) ) +
1875 0 : diff_tile( config, "execle", tiles+(1UL-last_snap)*tile_cnt*FD_METRICS_TOTAL_SZ, tiles+last_snap*tile_cnt*FD_METRICS_TOTAL_SZ, MIDX( COUNTER, EXECLE, CU_CONSUMED ) );
1876 0 : cups_samples_idx++;
1877 :
1878 : /* Snapshot */
1879 0 : snapshot_rx_samples[ snapshot_rx_idx%(sizeof(snapshot_rx_samples)/sizeof(snapshot_rx_samples[0])) ] = diff_tile( config, "snapct", tiles+(1UL-last_snap)*tile_cnt*FD_METRICS_TOTAL_SZ, tiles+last_snap*tile_cnt*FD_METRICS_TOTAL_SZ, MIDX( GAUGE, SNAPCT, READ_PROGRESS_BYTES_FULL ) ) +
1880 0 : diff_tile( config, "snapct", tiles+(1UL-last_snap)*tile_cnt*FD_METRICS_TOTAL_SZ, tiles+last_snap*tile_cnt*FD_METRICS_TOTAL_SZ, MIDX( GAUGE, SNAPCT, READ_PROGRESS_BYTES_INCREMENTAL ) );
1881 0 : snapshot_rx_idx++;
1882 0 : snapshot_acc_samples[ snapshot_acc_idx%(sizeof(snapshot_acc_samples)/sizeof(snapshot_acc_samples[0])) ] = diff_tile( config, "snapin", tiles+(1UL-last_snap)*tile_cnt*FD_METRICS_TOTAL_SZ, tiles+last_snap*tile_cnt*FD_METRICS_TOTAL_SZ, MIDX( GAUGE, SNAPIN, ACCOUNT_PROGRESS_NEW ) );
1883 0 : snapshot_acc_idx++;
1884 0 : snapshot_wr_samples[ snapshot_wr_idx%(sizeof(snapshot_wr_samples)/sizeof(snapshot_wr_samples[0])) ] = diff_tile( config, "snapin", tiles+(1UL-last_snap)*tile_cnt*FD_METRICS_TOTAL_SZ, tiles+last_snap*tile_cnt*FD_METRICS_TOTAL_SZ, MIDX( COUNTER, SNAPIN, DISK_WRITTEN_BYTES ) );
1885 0 : snapshot_wr_idx++;
1886 :
1887 : /* Backup */
1888 0 : backup_acc_samples[ backup_acc_idx%(sizeof(backup_acc_samples)/sizeof(backup_acc_samples[0])) ] = diff_tile( config, "snapzp", tiles+(1UL-last_snap)*tile_cnt*FD_METRICS_TOTAL_SZ, tiles+last_snap*tile_cnt*FD_METRICS_TOTAL_SZ, MIDX( COUNTER, SNAPZP, ACCOUNT_COMPRESSED ) );
1889 0 : backup_acc_idx++;
1890 0 : backup_read_samples[ backup_read_idx%(sizeof(backup_read_samples)/sizeof(backup_read_samples[0])) ] = diff_tile( config, "snaprd", tiles+(1UL-last_snap)*tile_cnt*FD_METRICS_TOTAL_SZ, tiles+last_snap*tile_cnt*FD_METRICS_TOTAL_SZ, MIDX( COUNTER, SNAPRD, DISK_READ_BYTES ) );
1891 0 : backup_read_idx++;
1892 0 : backup_comp_samples[ backup_comp_idx%(sizeof(backup_comp_samples)/sizeof(backup_comp_samples[0])) ] = diff_tile( config, "snapmk", tiles+(1UL-last_snap)*tile_cnt*FD_METRICS_TOTAL_SZ, tiles+last_snap*tile_cnt*FD_METRICS_TOTAL_SZ, MIDX( COUNTER, SNAPMK, COMPRESS_CONSUMED_BYTES ) ) +
1893 0 : diff_tile( config, "snapzp", tiles+(1UL-last_snap)*tile_cnt*FD_METRICS_TOTAL_SZ, tiles+last_snap*tile_cnt*FD_METRICS_TOTAL_SZ, MIDX( COUNTER, SNAPZP, COMPRESS_CONSUMED_BYTES ) );
1894 0 : backup_comp_idx++;
1895 0 : backup_upload_samples[ backup_upload_idx%(sizeof(backup_upload_samples)/sizeof(backup_upload_samples[0])) ] = diff_tile( config, "snapsv", tiles+(1UL-last_snap)*tile_cnt*FD_METRICS_TOTAL_SZ, tiles+last_snap*tile_cnt*FD_METRICS_TOTAL_SZ, MIDX( COUNTER, SNAPSV, HTTP_TX_BYTES ) );
1896 0 : backup_upload_idx++;
1897 :
1898 : /* Events */
1899 0 : events_sent_samples[ events_sent_samples_idx%(sizeof(events_sent_samples)/sizeof(events_sent_samples[0])) ] = diff_tile( config, "event", tiles+(1UL-last_snap)*tile_cnt*FD_METRICS_TOTAL_SZ, tiles+last_snap*tile_cnt*FD_METRICS_TOTAL_SZ, MIDX( COUNTER, EVENT, EVENT_TX ) );
1900 0 : events_sent_samples_idx++;
1901 0 : events_acked_samples[ events_acked_samples_idx%(sizeof(events_acked_samples)/sizeof(events_acked_samples[0])) ] = diff_tile( config, "event", tiles+(1UL-last_snap)*tile_cnt*FD_METRICS_TOTAL_SZ, tiles+last_snap*tile_cnt*FD_METRICS_TOTAL_SZ, MIDX( COUNTER, EVENT, ACK_RX ) );
1902 0 : events_acked_samples_idx++;
1903 0 : event_bytes_written_samples[ event_bytes_written_samples_idx%(sizeof(event_bytes_written_samples)/sizeof(event_bytes_written_samples[0])) ] = diff_tile( config, "event", tiles+(1UL-last_snap)*tile_cnt*FD_METRICS_TOTAL_SZ, tiles+last_snap*tile_cnt*FD_METRICS_TOTAL_SZ, MIDX( COUNTER, EVENT, STREAM_TX_BYTES ) );
1904 0 : event_bytes_written_samples_idx++;
1905 0 : event_bytes_read_samples[ event_bytes_read_samples_idx%(sizeof(event_bytes_read_samples)/sizeof(event_bytes_read_samples[0])) ] = diff_tile( config, "event", tiles+(1UL-last_snap)*tile_cnt*FD_METRICS_TOTAL_SZ, tiles+last_snap*tile_cnt*FD_METRICS_TOTAL_SZ, MIDX( COUNTER, EVENT, STREAM_RX_BYTES ) );
1906 0 : event_bytes_read_samples_idx++;
1907 :
1908 : /* Accounts */
1909 0 : sample_accdb( config, tiles+(1UL-last_snap)*tile_cnt*FD_METRICS_TOTAL_SZ, tiles+last_snap*tile_cnt*FD_METRICS_TOTAL_SZ );
1910 :
1911 : /* Gossip */
1912 0 : sample_gossip( config, tiles+last_snap*tile_cnt*FD_METRICS_TOTAL_SZ, now );
1913 :
1914 : /* Repair server */
1915 0 : shreds_stored_sample[ shreds_stored_samples_idx%(sizeof(shreds_stored_sample)/sizeof(shreds_stored_sample[0])) ] = diff_tile( config, "rserve", tiles+(1UL-last_snap)*tile_cnt*FD_METRICS_TOTAL_SZ, tiles+last_snap*tile_cnt*FD_METRICS_TOTAL_SZ, MIDX( GAUGE, RSERVE, SHRED_USED ) );
1916 0 : shreds_stored_samples_idx++;
1917 :
1918 0 : rserve_rps_valid_samples[ rserve_rps_valid_samples_idx%(sizeof(rserve_rps_valid_samples)/sizeof(rserve_rps_valid_samples[0])) ] =
1919 0 : diff_tile( config, "rserve", tiles+(1UL-last_snap)*tile_cnt*FD_METRICS_TOTAL_SZ, tiles+last_snap*tile_cnt*FD_METRICS_TOTAL_SZ, MIDX( COUNTER, RSERVE, RESPONSE_TX_PING ) ) +
1920 0 : diff_tile( config, "rserve", tiles+(1UL-last_snap)*tile_cnt*FD_METRICS_TOTAL_SZ, tiles+last_snap*tile_cnt*FD_METRICS_TOTAL_SZ, MIDX( COUNTER, RSERVE, RESPONSE_TX_WINDOW_INDEX ) ) +
1921 0 : diff_tile( config, "rserve", tiles+(1UL-last_snap)*tile_cnt*FD_METRICS_TOTAL_SZ, tiles+last_snap*tile_cnt*FD_METRICS_TOTAL_SZ, MIDX( COUNTER, RSERVE, RESPONSE_TX_HIGHEST_WINDOW_INDEX ) ) +
1922 0 : diff_tile( config, "rserve", tiles+(1UL-last_snap)*tile_cnt*FD_METRICS_TOTAL_SZ, tiles+last_snap*tile_cnt*FD_METRICS_TOTAL_SZ, MIDX( COUNTER, RSERVE, RESPONSE_TX_ORPHAN ) );
1923 0 : rserve_rps_valid_samples_idx++;
1924 :
1925 0 : rserve_rps_invalid_samples[ rserve_rps_invalid_samples_idx%(sizeof(rserve_rps_invalid_samples)/sizeof(rserve_rps_invalid_samples[0])) ] = 0UL;
1926 0 : for( ulong r=0UL; r<FD_METRICS_ENUM_REPAIR_REQUEST_TYPE_CNT; r++ ) {
1927 0 : rserve_rps_invalid_samples[ rserve_rps_invalid_samples_idx%(sizeof(rserve_rps_invalid_samples)/sizeof(rserve_rps_invalid_samples[0])) ] +=
1928 0 : diff_tile( config, "rserve", tiles+(1UL-last_snap)*tile_cnt*FD_METRICS_TOTAL_SZ, tiles+last_snap*tile_cnt*FD_METRICS_TOTAL_SZ, MIDX( COUNTER, RSERVE, REQUEST_UNANSWERED )+r );
1929 0 : }
1930 0 : for( ulong r=0UL; r<FD_METRICS_ENUM_REQUEST_DROP_REASON_CNT; r++ ) {
1931 0 : rserve_rps_invalid_samples[ rserve_rps_invalid_samples_idx%(sizeof(rserve_rps_invalid_samples)/sizeof(rserve_rps_invalid_samples[0])) ] +=
1932 0 : diff_tile( config, "rserve", tiles+(1UL-last_snap)*tile_cnt*FD_METRICS_TOTAL_SZ, tiles+last_snap*tile_cnt*FD_METRICS_TOTAL_SZ, MIDX( COUNTER, RSERVE, REQUEST_DROPPED )+r );
1933 0 : }
1934 0 : {
1935 0 : ulong slot = rserve_rps_invalid_samples_idx % (sizeof(rserve_rps_miss_samples)/sizeof(rserve_rps_miss_samples[0]));
1936 0 : ulong const * prev = tiles+(1UL-last_snap)*tile_cnt*FD_METRICS_TOTAL_SZ;
1937 0 : ulong const * cur = tiles+last_snap*tile_cnt*FD_METRICS_TOTAL_SZ;
1938 :
1939 0 : rserve_rps_miss_samples[ slot ] = 0UL;
1940 0 : for( ulong r=0UL; r<FD_METRICS_ENUM_REPAIR_REQUEST_TYPE_CNT; r++ ) rserve_rps_miss_samples[ slot ] += diff_tile( config, "rserve", prev, cur, MIDX( COUNTER, RSERVE, REQUEST_UNANSWERED )+r );
1941 :
1942 0 : rserve_rps_sigvfy_samples[ slot ] = diff_tile( config, "rserve", prev, cur, MIDX( COUNTER, RSERVE, REQUEST_DROPPED )+FD_METRICS_ENUM_REQUEST_DROP_REASON_V_SIGNATURE_FAILED_IDX );
1943 :
1944 0 : rserve_rps_stale_samples[ slot ] = diff_tile( config, "rserve", prev, cur, MIDX( COUNTER, RSERVE, REQUEST_DROPPED )+FD_METRICS_ENUM_REQUEST_DROP_REASON_V_TIMESTAMP_SKEW_IDX );
1945 :
1946 0 : rserve_rps_other_rej_samples[ slot ] =
1947 0 : diff_tile( config, "rserve", prev, cur, MIDX( COUNTER, RSERVE, REQUEST_DROPPED )+FD_METRICS_ENUM_REQUEST_DROP_REASON_V_FROM_SELF_IDX ) +
1948 0 : diff_tile( config, "rserve", prev, cur, MIDX( COUNTER, RSERVE, REQUEST_DROPPED )+FD_METRICS_ENUM_REQUEST_DROP_REASON_V_PONG_TOKEN_INVALID_IDX ) +
1949 0 : diff_tile( config, "rserve", prev, cur, MIDX( COUNTER, RSERVE, REQUEST_DROPPED )+FD_METRICS_ENUM_REQUEST_DROP_REASON_V_WRONG_RECIPIENT_IDX ) +
1950 0 : diff_tile( config, "rserve", prev, cur, MIDX( COUNTER, RSERVE, REQUEST_DROPPED )+FD_METRICS_ENUM_REQUEST_DROP_REASON_V_SHRED_INDEX_INVALID_IDX ) +
1951 0 : diff_tile( config, "rserve", prev, cur, MIDX( COUNTER, RSERVE, REQUEST_DROPPED )+FD_METRICS_ENUM_REQUEST_DROP_REASON_V_FEC_SET_INDEX_INVALID_IDX );
1952 0 : }
1953 0 : rserve_rps_invalid_samples_idx++;
1954 :
1955 : /* Move cursor to top of dashboard and overwrite in place.
1956 : All output is buffered and flushed in a single write() so
1957 : the terminal never renders a partially drawn frame. */
1958 0 : frame_len = 0UL;
1959 0 : PRINT( "\033[?25l" ); /* hide cursor during redraw */
1960 0 : if( FD_UNLIKELY( !ended_on_newline ) ) {
1961 0 : PRINT( "\033[%luA\r", lines_printed+1UL );
1962 0 : } else {
1963 0 : PRINT( "\033[%luA\r", lines_printed );
1964 0 : }
1965 0 : write_summary( config, node_info, tiles+last_snap*tile_cnt*FD_METRICS_TOTAL_SZ, tiles+(1UL-last_snap)*tile_cnt*FD_METRICS_TOTAL_SZ, links+last_snap*(cons_cnt*8UL*FD_METRICS_ALL_LINK_IN_TOTAL), links+(1UL-last_snap)*(cons_cnt*8UL*FD_METRICS_ALL_LINK_IN_TOTAL) );
1966 0 : PRINT( "\033[0J" ); /* clear any leftover lines below */
1967 0 : PRINT( "\033[?25h" ); /* show cursor */
1968 0 : flush_frame();
1969 0 : next += (long)1e7;
1970 0 : }
1971 0 : }
1972 0 : }
1973 :
1974 : void
1975 : watch_cmd_args( int * pargc,
1976 : char *** pargv,
1977 0 : args_t * args ) {
1978 0 : args->watch.drain_output_fd = -1;
1979 0 : args->watch.full = fd_env_strip_cmdline_contains( pargc, pargv, "--full" );
1980 0 : }
1981 :
1982 : void
1983 : watch_cmd_fn( args_t * args,
1984 0 : config_t * config ) {
1985 : /* Development commands spawn watch internally with the validator's
1986 : own config, only discover when invoked standalone. */
1987 0 : if( FD_LIKELY( args->watch.drain_output_fd==-1 ) ) fd_bootinfo_adopt( config );
1988 :
1989 0 : int allow_fds[ 5 ];
1990 0 : ulong allow_fds_cnt = 0;
1991 0 : allow_fds[ allow_fds_cnt++ ] = 0; /* stdin */
1992 0 : allow_fds[ allow_fds_cnt++ ] = 1; /* stdout */
1993 0 : allow_fds[ allow_fds_cnt++ ] = 2; /* stderr */
1994 0 : if( FD_LIKELY( fd_log_private_logfile_fd()!=-1 ) )
1995 0 : allow_fds[ allow_fds_cnt++ ] = fd_log_private_logfile_fd(); /* logfile */
1996 0 : if( FD_UNLIKELY( args->watch.drain_output_fd!=-1 ) )
1997 0 : allow_fds[ allow_fds_cnt++ ] = args->watch.drain_output_fd; /* maybe we are interposing firedancer log output with the monitor */
1998 :
1999 0 : if( FD_LIKELY( args->watch.drain_output_fd==-1 ) ) fd_bootinfo_check_layout( config );
2000 0 : fd_topo_join_workspaces( &config->topo, FD_SHMEM_JOIN_MODE_READ_ONLY, FD_TOPO_CORE_DUMP_LEVEL_DISABLED );
2001 :
2002 0 : struct sock_filter seccomp_filter[ 128UL ];
2003 0 : uint drain_output_fd = args->watch.drain_output_fd >= 0 ? (uint)args->watch.drain_output_fd : (uint)-1;
2004 0 : populate_sock_filter_policy_watch( 128UL, seccomp_filter, (uint)fd_log_private_logfile_fd(), drain_output_fd );
2005 :
2006 0 : if( FD_LIKELY( config->development.sandbox ) ) {
2007 0 : fd_sandbox_enter( config->uid,
2008 0 : config->gid,
2009 0 : 0,
2010 0 : 0,
2011 0 : 0,
2012 0 : 1, /* Keep controlling terminal for main so it can receive Ctrl+C */
2013 0 : 0,
2014 0 : 0UL,
2015 0 : 0UL,
2016 0 : 0UL,
2017 0 : 0UL,
2018 0 : allow_fds_cnt,
2019 0 : allow_fds,
2020 0 : sock_filter_policy_watch_instr_cnt,
2021 0 : seccomp_filter );
2022 0 : } else {
2023 0 : fd_sandbox_switch_uid_gid( config->uid, config->gid );
2024 0 : }
2025 :
2026 0 : fd_topo_fill( &config->topo );
2027 :
2028 0 : watch_full = args->watch.full;
2029 0 : run( config, args->watch.drain_output_fd );
2030 0 : }
2031 :
2032 : static void
2033 0 : watch_args_help( fd_action_help_t * help ) {
2034 0 : fd_action_help_arg( help, "--full", NULL, "Show all detail rows for each category, not just the primary row" );
2035 0 : }
2036 :
2037 : action_t fd_action_watch = {
2038 : .name = "watch",
2039 : .args = watch_cmd_args,
2040 : .fn = watch_cmd_fn,
2041 : .require_config = 0,
2042 : .perm = watch_cmd_perm,
2043 : .description = "Watch a locally running Firedancer instance with a terminal GUI",
2044 : .detail = "Connects to a running validator and renders a terminal dashboard of the\n"
2045 : "most important monitoring and operational metrics.",
2046 : .usage = "watch [--full]",
2047 : .args_help = watch_args_help,
2048 : };
|