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 : };
|