LCOV - code coverage report
Current view: top level - app/shared/commands/watch - watch.c (source / functions) Hit Total Coverage
Test: cov.lcov Lines: 0 1442 0.0 %
Date: 2026-09-17 04:28:31 Functions: 0 43 0.0 %

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

Generated by: LCOV version 1.14