LCOV - code coverage report
Current view: top level - app/shared/commands - mem.c (source / functions) Hit Total Coverage
Test: cov.lcov Lines: 0 167 0.0 %
Date: 2026-08-13 04:56:22 Functions: 0 7 0.0 %

          Line data    Source code
       1             : #include "../fd_config.h"
       2             : #include "../fd_action.h"
       3             : 
       4             : #include <unistd.h>
       5             : 
       6             : struct mem_obj_entry {
       7             :   ulong footprint;
       8             :   char  wksp[ 14 ]; /* fd_topo_wksp_t.name */
       9             :   char  obj[ 13 ];  /* fd_topo_obj_t.name */
      10             : };
      11             : typedef struct mem_obj_entry mem_obj_entry_t;
      12             : 
      13             : #define SORT_NAME        sort_obj_by_footprint
      14           0 : #define SORT_KEY_T       mem_obj_entry_t
      15           0 : #define SORT_BEFORE(a,b) ((a).footprint>(b).footprint)
      16             : #include "../../../util/tmpl/fd_sort.c"
      17             : 
      18             : extern action_t * ACTIONS[];
      19             : 
      20             : static void
      21           0 : fmt_size( char * buf, ulong sz ) {
      22           0 :   if( FD_LIKELY( sz >= (1UL<<30) ) )      FD_TEST( fd_cstr_printf_check( buf, 24, NULL, "%.1f GiB", (double)sz/(double)(1UL<<30) ) );
      23           0 :   else if( FD_LIKELY( sz >= (1UL<<20) ) ) FD_TEST( fd_cstr_printf_check( buf, 24, NULL, "%.1f MiB", (double)sz/(double)(1UL<<20) ) );
      24           0 :   else if( FD_LIKELY( sz >= (1UL<<10) ) ) FD_TEST( fd_cstr_printf_check( buf, 24, NULL, "%.1f KiB", (double)sz/(double)(1UL<<10) ) );
      25           0 :   else                                    FD_TEST( fd_cstr_printf_check( buf, 24, NULL, "%lu B",    sz                           ) );
      26           0 : }
      27             : 
      28             : /* json_escaped copies src into out as JSON string contents, escaping
      29             :    quotes, backslashes and control characters.  out must hold at
      30             :    least 6*strlen(src)+1 (worst case: every byte becomes \uXXXX). */
      31             : 
      32             : static void
      33           0 : json_escaped( char const * src, char * out, ulong out_sz ) {
      34           0 :   ulong j = 0UL;
      35           0 :   for( ulong i=0UL; src[ i ]; i++ ) {
      36           0 :     uchar c = (uchar)src[ i ];
      37           0 :     if( FD_UNLIKELY( c=='"' || c=='\\' ) ) { FD_TEST( j+2UL<out_sz ); out[ j++ ] = '\\'; out[ j++ ] = (char)c; }
      38           0 :     else if( FD_UNLIKELY( c<0x20 ) ) {
      39           0 :       FD_TEST( j+7UL<=out_sz && fd_cstr_printf_check( out+j, out_sz-j, NULL, "\\u%04x", (uint)c ) );
      40           0 :       j += 6UL;
      41           0 :     }
      42           0 :     else { FD_TEST( j+1UL<out_sz ); out[ j++ ] = (char)c; }
      43           0 :   }
      44           0 :   out[ j ] = '\0';
      45           0 : }
      46             : 
      47           0 : #define BAR_W (24UL)
      48             : 
      49             : /* fmt_bar renders val/max as a BAR_W cell wide bar with 1/8th cell
      50             :    resolution, space padded to exactly BAR_W display cells.  buf must
      51             :    hold at least 3*BAR_W+1 bytes. */
      52             : 
      53             : static void
      54           0 : fmt_bar( char * buf, ulong val, ulong max ) {
      55           0 :   static char const * const eighth[ 8 ] = { "", "▏", "▎", "▍", "▌", "▋", "▊", "▉" };
      56             : 
      57           0 :   ulong eighths = max ? (val*BAR_W*8UL)/max : 0UL;
      58           0 :   if( val && !eighths ) eighths = 1UL;
      59           0 :   ulong full = eighths/8UL;
      60           0 :   ulong rem  = eighths%8UL;
      61             : 
      62           0 :   char * p = fd_cstr_init( buf );
      63           0 :   ulong cells = 0UL;
      64           0 :   for( ulong i=0UL; i<full; i++, cells++ ) p = fd_cstr_append_cstr( p, "█" );
      65           0 :   if( rem ) { p = fd_cstr_append_cstr( p, eighth[ rem ] ); cells++; }
      66           0 :   for( ulong i=cells; i<BAR_W; i++ ) p = fd_cstr_append_char( p, ' ' );
      67           0 :   fd_cstr_fini( p );
      68           0 : }
      69             : 
      70             : static void
      71             : mem_cmd_args( int *    pargc,
      72             :               char *** pargv,
      73           0 :               args_t * args ) {
      74           0 :   char const * topo_name = fd_env_strip_cmdline_cstr( pargc, pargv, "--topo", NULL, "" );
      75           0 :   args->mem.sort = fd_env_strip_cmdline_contains( pargc, pargv, "--sort" );
      76           0 :   args->mem.json = fd_env_strip_cmdline_contains( pargc, pargv, "--json" );
      77             : 
      78           0 :   ulong topo_name_len = strlen( topo_name );
      79           0 :   if( FD_UNLIKELY( topo_name_len > sizeof(args->mem.topo)-1 ) ) FD_LOG_ERR(( "Unknown --topo %s", topo_name ));
      80           0 :   fd_cstr_fini( fd_cstr_append_text( fd_cstr_init( args->mem.topo ), topo_name, topo_name_len ) );
      81           0 : }
      82             : 
      83             : static void
      84             : reconstruct_topo( config_t *   config,
      85           0 :                   char const * topo_name ) {
      86           0 :   if( !topo_name[0] ) return; /* keep default action topo */
      87             : 
      88           0 :   action_t const * selected = NULL;
      89           0 :   for( action_t ** a=ACTIONS; *a; a++ ) {
      90           0 :     action_t const * action = *a;
      91           0 :     if( 0==strcmp( action->name, topo_name ) ) {
      92           0 :       selected = action;
      93           0 :       break;
      94           0 :     }
      95           0 :   }
      96             : 
      97           0 :   if( !selected       ) FD_LOG_ERR(( "Unknown --topo %s", topo_name ));
      98           0 :   if( !selected->topo ) FD_LOG_ERR(( "Cannot recover topology for --topo %s", topo_name ));
      99             : 
     100           0 :   selected->topo( config );
     101           0 : }
     102             : 
     103             : void
     104             : mem_cmd_fn( args_t *   args,
     105           0 :             config_t * config ) {
     106           0 :   reconstruct_topo( config, args->mem.topo );
     107             : 
     108           0 :   if( FD_UNLIKELY( args->mem.sort ) ) {
     109           0 :     fd_topo_t * topo = &config->topo;
     110             : 
     111             :     /* Max entries: objects + per-wksp loose + per-wksp overhead + extra pages */
     112           0 :     mem_obj_entry_t entries[ FD_TOPO_MAX_OBJS + 2UL*FD_TOPO_MAX_WKSPS + 2UL ];
     113           0 :     ulong cnt = 0UL;
     114             : 
     115             :     /* Real topology objects */
     116           0 :     for( ulong i=0UL; i<topo->obj_cnt; i++ ) {
     117           0 :       fd_topo_obj_t * obj = &topo->objs[ i ];
     118           0 :       entries[ cnt ].footprint = obj->footprint;
     119           0 :       fd_cstr_fini( fd_cstr_append_text( fd_cstr_init( entries[ cnt ].wksp ), topo->workspaces[ obj->wksp_id ].name, sizeof(entries[ cnt ].wksp)-1 ) );
     120           0 :       fd_cstr_fini( fd_cstr_append_text( fd_cstr_init( entries[ cnt ].obj ), obj->name, sizeof(entries[ cnt ].obj)-1 ) );
     121           0 :       cnt++;
     122           0 :     }
     123             : 
     124             :     /* Per-workspace loose memory and page rounding overhead */
     125           0 :     for( ulong i=0UL; i<topo->wksp_cnt; i++ ) {
     126           0 :       fd_topo_wksp_t * wksp = &topo->workspaces[ i ];
     127           0 :       ulong loose = wksp->total_footprint - wksp->known_footprint;
     128           0 :       if( loose ) {
     129           0 :         entries[ cnt ].footprint = loose;
     130           0 :         fd_cstr_fini( fd_cstr_append_text( fd_cstr_init( entries[ cnt ].wksp ), wksp->name, sizeof(entries[ cnt ].wksp)-1 ) );
     131           0 :         fd_cstr_fini( fd_cstr_append_text( fd_cstr_init( entries[ cnt ].obj ), "loose", 5 ) );
     132           0 :         cnt++;
     133           0 :       }
     134           0 :       ulong pages_sz = wksp->page_cnt * wksp->page_sz;
     135           0 :       ulong overhead = pages_sz - wksp->total_footprint;
     136           0 :       if( overhead ) {
     137           0 :         entries[ cnt ].footprint = overhead;
     138           0 :         fd_cstr_fini( fd_cstr_append_text( fd_cstr_init( entries[ cnt ].wksp ), wksp->name, sizeof(entries[ cnt ].wksp)-1 ) );
     139           0 :         fd_cstr_fini( fd_cstr_append_text( fd_cstr_init( entries[ cnt ].obj ), "padding", 7 ) );
     140           0 :         cnt++;
     141           0 :       }
     142           0 :     }
     143             : 
     144             :     /* Tile stacks: each tile maps (FD_TILE_PRIVATE_STACK_SZ/FD_SHMEM_HUGE_PAGE_SZ)+2
     145             :        huge pages for its stack (see fd_topo_tile_extra_huge_pages). */
     146           0 :     ulong stack_huge_pages = topo->tile_cnt * ((FD_TILE_PRIVATE_STACK_SZ/FD_SHMEM_HUGE_PAGE_SZ)+2UL);
     147           0 :     if( stack_huge_pages ) {
     148           0 :       entries[ cnt ].footprint = stack_huge_pages * FD_SHMEM_HUGE_PAGE_SZ;
     149           0 :       fd_cstr_fini( fd_cstr_append_text( fd_cstr_init( entries[ cnt ].wksp ), "", 0 ) );
     150           0 :       fd_cstr_fini( fd_cstr_append_text( fd_cstr_init( entries[ cnt ].obj ), "tile_stacks", 11 ) );
     151           0 :       cnt++;
     152           0 :     }
     153             : 
     154             :     /* Extra normal pages (private keys, XSK rings, log locks, etc.) */
     155           0 :     ulong extra_normal = fd_topo_normal_page_cnt( topo );
     156           0 :     if( extra_normal ) {
     157           0 :       entries[ cnt ].footprint = extra_normal * FD_SHMEM_NORMAL_PAGE_SZ;
     158           0 :       fd_cstr_fini( fd_cstr_append_text( fd_cstr_init( entries[ cnt ].wksp ), "", 0 ) );
     159           0 :       fd_cstr_fini( fd_cstr_append_text( fd_cstr_init( entries[ cnt ].obj ), "normal_pages", 12 ) );
     160           0 :       cnt++;
     161           0 :     }
     162             : 
     163           0 :     sort_obj_by_footprint_inplace( entries, cnt );
     164             : 
     165           0 :     ulong total = 0UL;
     166           0 :     for( ulong i=0UL; i<cnt; i++ ) total += entries[ i ].footprint;
     167           0 :     ulong max = cnt ? entries[ 0 ].footprint : 0UL;
     168             : 
     169           0 :     if( FD_UNLIKELY( args->mem.json ) ) {
     170           0 :       FD_LOG_STDOUT(( "{\n  \"total_bytes\": %lu,\n  \"entries\": [\n", total ));
     171           0 :       ulong cum = 0UL;
     172           0 :       for( ulong i=0UL; i<cnt; i++ ) {
     173           0 :         cum += entries[ i ].footprint;
     174             :         /* 6x worst case expansion (\uXXXX) of the 12 char names */
     175           0 :         char wksp[ 6UL*sizeof(entries[ i ].wksp)+1UL ]; json_escaped( entries[ i ].wksp, wksp, sizeof(wksp) );
     176           0 :         char obj [ 6UL*sizeof(entries[ i ].obj )+1UL ]; json_escaped( entries[ i ].obj,  obj,  sizeof(obj)  );
     177           0 :         FD_LOG_STDOUT(( "    { \"workspace\": \"%s\", \"object\": \"%s\", \"bytes\": %lu, \"cum_bytes\": %lu }%s\n",
     178           0 :                         wksp, obj, entries[ i ].footprint, cum, i==cnt-1UL ? "" : "," ));
     179           0 :       }
     180           0 :       FD_LOG_STDOUT(( "  ]\n}\n" ));
     181           0 :       return;
     182           0 :     }
     183             : 
     184           0 :     int color = fd_log_colorize() && isatty( STDOUT_FILENO );
     185           0 :     char const * c_bold   = color ? "\033[1m"  : "";
     186           0 :     char const * c_dim    = color ? "\033[2m"  : "";
     187           0 :     char const * c_normal = color ? "\033[0m"  : "";
     188           0 :     char const * c_blue   = color ? "\033[34m" : "";
     189             : 
     190           0 :     char rule[ 3UL*110UL+1UL ];
     191           0 :     char * p = fd_cstr_init( rule );
     192           0 :     for( ulong i=0UL; i<110UL; i++ ) p = fd_cstr_append_cstr( p, "─" );
     193           0 :     fd_cstr_fini( p );
     194             : 
     195           0 :     FD_LOG_STDOUT(( "%s%10s  %6s  %-*s  %15s  %10s  %7s  %-12s  %-12s%s\n", c_dim, "SIZE", "PCT", (int)BAR_W, "", "BYTES", "CUM SIZE", "CUM PCT", "WORKSPACE", "OBJECT", c_normal ));
     196           0 :     ulong cum = 0UL;
     197           0 :     for( ulong i=0UL; i<cnt; i++ ) {
     198           0 :       ulong sz = entries[ i ].footprint;
     199           0 :       cum += sz;
     200           0 :       char sz_str[ 24 ], cum_str[ 24 ], bar[ 3UL*BAR_W+1UL ];
     201           0 :       fmt_size( sz_str,  sz  );
     202           0 :       fmt_size( cum_str, cum );
     203           0 :       fmt_bar( bar, sz, max );
     204           0 :       double pct     = total ? 100.0 * (double)sz  / (double)total : 0.0;
     205           0 :       double cum_pct = total ? 100.0 * (double)cum / (double)total : 0.0;
     206           0 :       char const * sz_style  = sz>=(1UL<<30) ? c_bold : (sz<(1UL<<20) ? c_dim : "");
     207           0 :       char const * pct_style = pct>=10.0 ? c_bold : "";
     208           0 :       FD_LOG_STDOUT(( "%s%10s%s  %s%5.1f%%%s  %s%s%s  %s%15lu%s  %s%10s%s  %s%6.1f%%%s  %-12s  %s\n",
     209           0 :                       sz_style, sz_str, sz_style[0] ? c_normal : "",
     210           0 :                       pct_style, pct, pct_style[0] ? c_normal : "",
     211           0 :                       c_blue, bar, c_normal,
     212           0 :                       c_dim, sz, c_normal,
     213           0 :                       c_dim, cum_str, c_normal,
     214           0 :                       c_dim, cum_pct, c_normal,
     215           0 :                       entries[ i ].wksp, entries[ i ].obj ));
     216           0 :     }
     217             : 
     218           0 :     char total_str[ 24 ];
     219           0 :     fmt_size( total_str, total );
     220           0 :     FD_LOG_STDOUT(( "%s%s%s\n", c_dim, rule, c_normal ));
     221           0 :     FD_LOG_STDOUT(( "%s%10s  %5.1f%%  %-*s  %15lu  %10s  %6.1f%%  %-12s%s\n",
     222           0 :                     c_bold, total_str, 100.0, (int)BAR_W, "", total, total_str, 100.0, "TOTAL", c_normal ));
     223           0 :     return;
     224           0 :   }
     225             : 
     226           0 :   if( FD_UNLIKELY( args->mem.json ) ) fd_topo_print_json( &config->topo );
     227           0 :   else                                fd_topo_print_log( 1, &config->topo );
     228           0 : }
     229             : 
     230             : static void
     231           0 : mem_args_help( fd_action_help_t * help ) {
     232           0 :   fd_action_help_arg( help, "--topo", "<command>", "Build the topology from another subcommand (e.g. `gossip`) instead of\n"
     233           0 :                                                    "the default validator topology.  <command> is the name of a subcommand\n"
     234           0 :                                                    "that builds its own topology" );
     235           0 :   fd_action_help_arg( help, "--sort", NULL,        "List objects largest first, with per object and cumulative memory\n"
     236           0 :                                                    "footprint, instead of the default per memory region layout" );
     237             :   fd_action_help_arg( help, "--json", NULL,        "Print the output as JSON" );
     238           0 : }
     239             : 
     240             : action_t fd_action_mem = {
     241             :   .name           = "mem",
     242             :   .args           = mem_cmd_args,
     243             :   .fn             = mem_cmd_fn,
     244             :   .require_config = 1,
     245             :   .perm           = NULL,
     246             :   .description    = "Print the validator's memory regions and tile layout",
     247             :   .detail         = "Prints the shared memory regions and tiles that the validator topology\n"
     248             :                     "allocates, including how much memory each object reserves.  A tile is a\n"
     249             :                     "single thread pinned to a CPU core that performs one part of the validator's\n"
     250             :                     "work.  This is computed from the configuration without attaching to a\n"
     251             :                     "running validator.",
     252             :   .usage          = "mem [OPTIONS]",
     253             :   .args_help      = mem_args_help,
     254             : };

Generated by: LCOV version 1.14