Line data Source code
1 : #include "../../firedancer/topology.h"
2 : #include "../../platform/fd_sys_util.h"
3 : #include "../../shared/commands/configure/configure.h"
4 : #include "../../shared/commands/run/run.h"
5 : #include "../../shared_dev/commands/dev.h"
6 : #include "../../../disco/metrics/fd_metrics.h"
7 : #include "../../../disco/topo/fd_topob.h"
8 : #include "../../../disco/pack/fd_pack_cost.h"
9 : #include "../../../util/pod/fd_pod_format.h"
10 : #include "../../../discof/restore/utils/fd_ssctrl.h"
11 : #include "../../../discof/restore/utils/fd_ssmsg.h"
12 : #include "../../../flamenco/runtime/fd_cost_tracker.h"
13 : #include "../../../flamenco/accdb/fd_accdb_private.h"
14 :
15 : #include <fcntl.h> /* open */
16 : #include <sys/resource.h>
17 : #include <linux/capability.h>
18 : #include <unistd.h> /* close, sleep */
19 : #include <stdlib.h>
20 : #include <stdio.h>
21 :
22 : #define NAME "snapshot-load"
23 :
24 0 : #define SL_RESET "\033[0m"
25 0 : #define SL_BOLD "\033[1m"
26 0 : #define SL_DIM "\033[2m"
27 0 : #define SL_RED "\033[31m"
28 : #define SL_GREEN "\033[32m"
29 0 : #define SL_YELLOW "\033[33m"
30 :
31 : static char const *
32 : sev_color( int color,
33 0 : double pct ) {
34 0 : if( FD_UNLIKELY( !color ) ) return "";
35 0 : if( FD_UNLIKELY( pct>=85.0 ) ) return SL_RED;
36 0 : if( FD_UNLIKELY( pct>=50.0 ) ) return SL_YELLOW;
37 0 : return "";
38 0 : }
39 :
40 : static double
41 0 : clamp_pct( double pct ) {
42 0 : return fd_double_if( pct<0.0, 0.0, fd_double_if( pct>100.0, 100.0, pct ) );
43 0 : }
44 :
45 : static char *
46 : fmt_bar( char * buf,
47 : ulong buf_sz,
48 : int color,
49 : double pct,
50 0 : ulong width ) {
51 0 : ulong filled = (ulong)( clamp_pct( pct )*(double)width/100.0+0.5 );
52 0 : ulong len = 0UL, l;
53 0 : FD_TEST( fd_cstr_printf_check( buf, buf_sz, &len, "%s", color ? SL_GREEN : "" ) );
54 0 : for( ulong i=0UL; i<filled; i++ ) { FD_TEST( fd_cstr_printf_check( buf+len, buf_sz-len, &l, "█" ) ); len += l; }
55 0 : FD_TEST( fd_cstr_printf_check( buf+len, buf_sz-len, &l, "%s", color ? SL_DIM : "" ) ); len += l;
56 0 : for( ulong i=filled; i<width; i++ ) { FD_TEST( fd_cstr_printf_check( buf+len, buf_sz-len, &l, "░" ) ); len += l; }
57 0 : FD_TEST( fd_cstr_printf_check( buf+len, buf_sz-len, &l, "%s", color ? SL_RESET : "" ) );
58 0 : return buf;
59 0 : }
60 :
61 : static char const *
62 0 : phase_cstr( ulong state ) {
63 0 : if( FD_LIKELY( ( state>= 5UL && state<= 8UL ) || ( state>=13UL && state<=16UL ) ) ) return "full";
64 0 : if( FD_LIKELY( ( state>= 9UL && state<=12UL ) || ( state>=17UL && state<=20UL ) ) ) return "incr";
65 0 : if( FD_UNLIKELY( state==21UL ) ) return "done";
66 0 : return "wait";
67 0 : }
68 :
69 : extern fd_topo_obj_callbacks_t * CALLBACKS[];
70 :
71 : fd_topo_run_tile_t
72 : fdctl_tile_run( fd_topo_tile_t const * tile );
73 :
74 : static void
75 0 : snapshot_load_topo( config_t * config ) {
76 0 : config->firedancer.layout.resolv_tile_count = 0;
77 0 : fd_topo_t * topo = &config->topo;
78 0 : fd_topob_new( &config->topo, config->name );
79 0 : topo->max_page_size = fd_cstr_to_shmem_page_sz( config->hugetlbfs.max_page_size );
80 :
81 0 : fd_topob_wksp( topo, "txncache" );
82 0 : fd_topo_obj_t * txncache_obj = setup_topo_txncache( topo, "txncache",
83 0 : config->firedancer.runtime.max_live_slots,
84 0 : 2UL*config->limits.max_txn_per_slot );
85 0 : FD_TEST( fd_pod_insertf_ulong( topo->props, txncache_obj->id, "txncache" ) );
86 :
87 0 : fd_topob_wksp( topo, "accdb" );
88 0 : fd_topo_obj_t * accdb_obj = setup_topo_accdb( topo, "accdb",
89 0 : config->firedancer.accounts.max_accounts,
90 0 : config->firedancer.runtime.max_live_slots,
91 0 : FD_RUNTIME_MAX_ACC_WRITES_PER_SLOT,
92 0 : 8192UL,
93 0 : 1UL<<35UL,
94 0 : config->firedancer.accounts.cache_size_gib*(1UL<<30UL),
95 0 : config->tiles.bundle.enabled,
96 0 : 2UL,
97 0 : 0UL );
98 0 : FD_TEST( fd_pod_insertf_ulong( topo->props, accdb_obj->id, "accdb" ) );
99 :
100 0 : fd_topob_wksp( topo, "banks" );
101 0 : fd_topo_obj_t * banks_obj = setup_topo_banks( topo, "banks",
102 0 : config->firedancer.runtime.max_live_slots,
103 0 : config->firedancer.runtime.max_fork_width,
104 0 : config->development.bench.max_cost_per_block );
105 0 : FD_TEST( fd_pod_insertf_ulong( topo->props, banks_obj->id, "banks" ) );
106 :
107 0 : #define FOR(cnt) for( ulong i=0UL; i<cnt; i++ )
108 :
109 : /* metrics tile *****************************************************/
110 0 : fd_topob_wksp( topo, "metric_in" );
111 0 : fd_topob_wksp( topo, "metric" );
112 0 : fd_topob_tile( topo, "metric", "metric", "metric_in", ULONG_MAX, 0, 0, 0, 1 );
113 :
114 : /* read() tile */
115 0 : fd_topob_wksp( topo, "snapct" );
116 0 : fd_topo_tile_t * snapct_tile = fd_topob_tile( topo, "snapct", "snapct", "metric_in", ULONG_MAX, 0, 0, 0, 0 );
117 0 : snapct_tile->allow_shutdown = 1;
118 :
119 : /* load tile */
120 0 : fd_topob_wksp( topo, "snapld" );
121 0 : fd_topo_tile_t * snapld_tile = fd_topob_tile( topo, "snapld", "snapld", "metric_in", ULONG_MAX, 0, 0, 0, 0 );
122 0 : snapld_tile->allow_shutdown = 1;
123 :
124 : /* "snapdc": Zstandard decompress tile */
125 0 : fd_topob_wksp( topo, "snapdc" );
126 0 : ulong snapdc_tile_cnt = config->firedancer.layout.snapdc_tile_count;
127 0 : FOR(snapdc_tile_cnt) fd_topob_tile( topo, "snapdc", "snapdc", "metric_in", ULONG_MAX, 0, 0, 0, 0 )->allow_shutdown = 1;
128 :
129 : /* "snapin": Snapshot parser tile */
130 0 : fd_topob_wksp( topo, "snapin" );
131 0 : fd_topo_tile_t * snapin_tile = fd_topob_tile( topo, "snapin", "snapin", "metric_in", ULONG_MAX, 0, 0, 0, 0 );
132 0 : snapin_tile->allow_shutdown = 1;
133 :
134 0 : fd_topob_wksp( topo, "snapwr" );
135 0 : fd_topo_tile_t * snapwr_tile = fd_topob_tile( topo, "snapwr", "snapwr", "metric_in", ULONG_MAX, 0, 0, 0, 0 );
136 0 : snapwr_tile->allow_shutdown = 1;
137 :
138 0 : fd_topob_wksp( topo, "diag" );
139 0 : fd_topob_tile( topo, "diag", "diag", "metric_in", ULONG_MAX, 0, 0, 0, 0 );
140 0 : fd_topo_tile_t * accdb_tile = fd_topob_tile( topo, "accdb", "accdb", "metric_in", ULONG_MAX, 0, 0, 0, 0 );
141 :
142 0 : fd_topob_wksp( topo, "snapct_ld" );
143 0 : fd_topob_wksp( topo, "snapld_dc" );
144 0 : fd_topob_wksp( topo, "snapdc_in" );
145 :
146 0 : fd_topob_wksp( topo, "snapin_manif" );
147 0 : fd_topob_wksp( topo, "snapct_repr" );
148 :
149 0 : fd_topob_wksp( topo, "snapin_ct" );
150 0 : fd_topob_wksp( topo, "snapwr_ct" );
151 :
152 0 : fd_topob_link( topo, "snapct_ld", "snapct_ld", 128UL, sizeof(fd_ssctrl_init_t), 1UL );
153 0 : fd_topob_link( topo, "snapld_dc", "snapld_dc", FD_SNAPSHOT_DATA_DEPTH, FD_SNAPSHOT_DATA_MTU, 1UL );
154 0 : FOR(snapdc_tile_cnt) fd_topob_link( topo, "snapdc_in", "snapdc_in", FD_SNAPSHOT_DATA_DEPTH, FD_SNAPSHOT_DATA_MTU, 1UL );
155 0 : fd_topob_link( topo, "snapin_manif", "snapin_manif", 4UL, sizeof(fd_snapshot_manifest_t), 1UL )->permit_no_consumers = 1;
156 0 : fd_topob_link( topo, "snapct_repr", "snapct_repr", 128UL, 0UL, 1UL )->permit_no_consumers = 1;
157 :
158 0 : fd_topob_link( topo, "snapin_ct", "snapin_ct", 128UL, 0UL, 1UL );
159 0 : fd_topob_link( topo, "snapwr_ct", "snapwr_ct", 128UL, 0UL, 1UL );
160 0 : fd_topob_tile_in( topo, "snapct", 0UL, "metric_in", "snapin_ct", 0UL, FD_TOPOB_RELIABLE, FD_TOPOB_POLLED );
161 0 : fd_topob_tile_in( topo, "snapct", 0UL, "metric_in", "snapwr_ct", 0UL, FD_TOPOB_RELIABLE, FD_TOPOB_POLLED );
162 :
163 0 : fd_topob_tile_in ( topo, "snapct", 0UL, "metric_in", "snapld_dc", 0UL, FD_TOPOB_RELIABLE, FD_TOPOB_POLLED );
164 0 : fd_topob_tile_out( topo, "snapct", 0UL, "snapct_ld", 0UL );
165 0 : fd_topob_tile_out( topo, "snapct", 0UL, "snapct_repr", 0UL );
166 0 : fd_topob_tile_in ( topo, "snapld", 0UL, "metric_in", "snapct_ld", 0UL, FD_TOPOB_RELIABLE, FD_TOPOB_POLLED );
167 0 : fd_topob_tile_out( topo, "snapld", 0UL, "snapld_dc", 0UL );
168 0 : FOR(snapdc_tile_cnt) fd_topob_tile_in ( topo, "snapdc", i, "metric_in", "snapld_dc", 0UL, FD_TOPOB_RELIABLE, FD_TOPOB_POLLED );
169 0 : FOR(snapdc_tile_cnt) fd_topob_tile_out( topo, "snapdc", i, "snapdc_in", i );
170 0 : FOR(snapdc_tile_cnt) fd_topob_tile_in ( topo, "snapin", 0UL, "metric_in", "snapdc_in", i, FD_TOPOB_RELIABLE, FD_TOPOB_POLLED );
171 0 : fd_topob_tile_out( topo, "snapin", 0UL, "snapin_manif", 0UL );
172 0 : fd_topob_tile_out( topo, "snapin", 0UL, "snapin_ct", 0UL );
173 0 : FOR(snapdc_tile_cnt) fd_topob_tile_in ( topo, "snapwr", 0UL, "metric_in", "snapdc_in", i, FD_TOPOB_RELIABLE, FD_TOPOB_POLLED );
174 0 : fd_topob_tile_out( topo, "snapwr", 0UL, "snapwr_ct", 0UL );
175 :
176 0 : fd_topob_tile_uses( topo, snapin_tile, txncache_obj, FD_SHMEM_JOIN_MODE_READ_WRITE );
177 0 : fd_topob_tile_uses( topo, snapin_tile, accdb_obj, FD_SHMEM_JOIN_MODE_READ_WRITE );
178 0 : fd_topob_tile_uses( topo, snapin_tile, banks_obj, FD_SHMEM_JOIN_MODE_READ_WRITE );
179 0 : fd_topob_tile_uses( topo, accdb_tile, accdb_obj, FD_SHMEM_JOIN_MODE_READ_WRITE );
180 0 : snapin_tile->snapin.accdb_obj_id = accdb_obj->id;
181 0 : snapin_tile->snapin.txncache_obj_id = txncache_obj->id;
182 0 : snapin_tile->snapin.banks_obj_id = banks_obj->id;
183 0 : snapin_tile->snapin.max_live_slots = config->firedancer.runtime.max_live_slots;
184 :
185 0 : for( ulong i=0UL; i<topo->tile_cnt; i++ ) {
186 0 : fd_topo_tile_t * tile = &topo->tiles[ i ];
187 0 : fd_topo_configure_tile( tile, config );
188 0 : }
189 :
190 0 : fd_topob_waker( topo );
191 0 : fd_topob_auto_layout( topo, 0 );
192 0 : fd_topob_finish( topo, CALLBACKS );
193 0 : }
194 :
195 : static void
196 0 : snapshot_load_topo1( config_t * config ) {
197 0 : snapshot_load_topo( config );
198 0 : }
199 :
200 : static void
201 : snapshot_load_args( int * pargc,
202 : char *** pargv,
203 0 : args_t * args ) {
204 0 : if( FD_UNLIKELY( fd_env_strip_cmdline_contains( pargc, pargv, "--help" ) ) ) {
205 0 : fputs(
206 0 : "\nUsage: firedancer-dev snapshot-load [GLOBAL FLAGS] [FLAGS]\n"
207 0 : "\n"
208 0 : "Global Flags:\n"
209 0 : " --mainnet Use Solana mainnet-beta defaults\n"
210 0 : " --testnet Use Solana testnet defaults\n"
211 0 : " --devnet Use Solana devnet defaults\n"
212 0 : "\n"
213 0 : "Flags:\n"
214 0 : " --snapshot-dir PATH Load/save snapshots from this directory\n"
215 0 : " --offline Do not attempt to download snapshots\n"
216 0 : " --no-incremental Disable incremental snapshot loading\n"
217 0 : " --no-watch Do not print periodic progress updates\n"
218 0 : " --db-rec-max <num> Database max record/account count (e.g. 10e6 -> 10M accounts)\n"
219 0 : " --accounts-hist After loading, analyze account size distribution\n"
220 0 : "\n",
221 0 : stderr );
222 0 : exit( 0 );
223 0 : }
224 0 : memset( &args->snapshot_load, 0, sizeof(args->snapshot_load) );
225 :
226 0 : char const * snapshot_dir = fd_env_strip_cmdline_cstr ( pargc, pargv, "--snapshot-dir", NULL, NULL );
227 0 : int offline = fd_env_strip_cmdline_contains( pargc, pargv, "--offline" )!=0;
228 0 : int no_incremental= fd_env_strip_cmdline_contains( pargc, pargv, "--no-incremental" )!=0;
229 0 : int no_watch = fd_env_strip_cmdline_contains( pargc, pargv, "--no-watch" )!=0;
230 0 : int accounts_hist = fd_env_strip_cmdline_contains( pargc, pargv, "--accounts-hist" )!=0;
231 0 : double db_sz = fd_env_strip_cmdline_double ( pargc, pargv, "--db-sz", NULL, 0.0 );
232 0 : double db_rec_max = fd_env_strip_cmdline_double ( pargc, pargv, "--db-rec-max", NULL, 0.0 );
233 0 : if( FD_UNLIKELY( !(db_sz>=0.0 && db_sz<1.8e19) ) ) FD_LOG_ERR(( "--db-sz out of range" )); /* also rejects NaN */
234 0 : if( FD_UNLIKELY( !(db_rec_max>=0.0 && db_rec_max<1.8e19) ) ) FD_LOG_ERR(( "--db-rec-max out of range" ));
235 :
236 0 : fd_cstr_ncpy( args->snapshot_load.snapshot_dir, snapshot_dir, sizeof(args->snapshot_load.snapshot_dir) );
237 0 : args->snapshot_load.accounts_hist = accounts_hist;
238 0 : args->snapshot_load.offline = offline;
239 0 : args->snapshot_load.no_incremental = no_incremental;
240 0 : args->snapshot_load.no_watch = no_watch;
241 0 : args->snapshot_load.db_rec_max = (ulong)db_rec_max;
242 0 : args->snapshot_load.cache_sz = (ulong)db_sz;
243 0 : }
244 :
245 : /* ACCOUNTS_HIST_N (32) is chosen to make the histogram lightweight.
246 : And because accounts can have a data size in the range [0, 10MiB],
247 : the width of the bins increments in powers of 2. In the future, it
248 : should be possible to pass this as a configuration parameter. */
249 0 : #define ACCOUNTS_HIST_N (32)
250 :
251 : struct accounts_hist {
252 : ulong total_cnt;
253 : ulong total_acc;
254 : ulong bin_thi[ ACCOUNTS_HIST_N ];
255 : ulong bin_cnt[ ACCOUNTS_HIST_N ];
256 : ulong bin_acc[ ACCOUNTS_HIST_N ];
257 : ulong bin_min[ ACCOUNTS_HIST_N ];
258 : ulong bin_max[ ACCOUNTS_HIST_N ];
259 : ulong token_cnt;
260 : };
261 : typedef struct accounts_hist accounts_hist_t;
262 :
263 : static inline void
264 0 : accounts_hist_reset( accounts_hist_t * hist ) {
265 0 : hist->total_cnt = 0UL;
266 0 : hist->total_acc = 0UL;
267 0 : for( int i=0; i < ACCOUNTS_HIST_N; i++ ) {
268 0 : hist->bin_thi[ i ] = fd_ulong_if( i > 0, fd_pow2( ulong, i-1 ), 0UL );
269 0 : hist->bin_cnt[ i ] = 0UL;
270 0 : hist->bin_acc[ i ] = 0UL;
271 0 : hist->bin_min[ i ] = ULONG_MAX;
272 0 : hist->bin_max[ i ] = 0UL;
273 0 : }
274 0 : hist->token_cnt = 0UL;
275 0 : }
276 :
277 : static inline void FD_FN_UNUSED
278 : accounts_hist_update( accounts_hist_t * hist,
279 0 : ulong account_sz ) {
280 0 : hist->total_cnt += 1UL;
281 0 : hist->total_acc += account_sz;
282 0 : int i=0;
283 : /* This allows for arbitrary thresholds - not optimized for pow2
284 : bins. */
285 0 : for( ; i < ACCOUNTS_HIST_N; i++ ) {
286 0 : if( FD_UNLIKELY( account_sz <= hist->bin_thi[ i ] )) {
287 0 : hist->bin_cnt[ i ] += 1;
288 0 : hist->bin_acc[ i ] += account_sz;
289 0 : hist->bin_min[ i ] = fd_ulong_min( hist->bin_min[ i ], account_sz );
290 0 : hist->bin_max[ i ] = fd_ulong_max( hist->bin_max[ i ], account_sz );
291 0 : break;
292 0 : }
293 0 : }
294 0 : FD_TEST( i < ACCOUNTS_HIST_N );
295 0 : }
296 :
297 : static inline int
298 0 : accounts_hist_check( accounts_hist_t const * hist ) {
299 0 : ulong cnt = 0UL;
300 0 : ulong acc = 0UL;
301 0 : for( int i=0; i < ACCOUNTS_HIST_N; i++ ) {
302 0 : cnt += hist->bin_cnt[ i ];
303 0 : acc += hist->bin_acc[ i ];
304 0 : }
305 0 : if( cnt != hist->total_cnt ) return -1;
306 0 : if( acc != hist->total_acc ) return -2;
307 0 : return 0;
308 0 : }
309 :
310 : static void
311 0 : accounts_hist_print( accounts_hist_t const * hist ) {
312 0 : double hist_total_cnt_M = (double)hist->total_cnt / (double)1.0e6;
313 0 : double hist_total_cnt_GiB = (double)hist->total_acc / (double)1073741824;
314 0 : printf( "\n" );
315 0 : printf( "hist_total_cnt %16lu ( %6.1f M )\n", hist->total_cnt, hist_total_cnt_M );
316 0 : printf( "hist_total_acc %16lu ( %6.1f GiB )\n", hist->total_acc, hist_total_cnt_GiB );
317 0 : printf( " bin_th_lo < sz <= bin_th_hi | bin_cnt (run_sum%%) | bin_acc (run_sum%%) | bin_min B | bin_max B | bin_avg B |\n" );
318 0 : ulong sum_cnt = 0UL;
319 0 : ulong sum_acc = 0UL;
320 0 : for( int i=0; i < ACCOUNTS_HIST_N; i++ ) {
321 : /* bin thresholds */
322 0 : ulong hist_bin_tlo = hist->bin_thi[ fd_int_if( i>0, i-1, i ) ];
323 0 : ulong hist_bin_thi = hist->bin_thi[ i ];
324 : /* bin cnt */
325 0 : ulong hist_bin_cnt = hist->bin_cnt[ i ];
326 0 : sum_cnt += hist->bin_cnt[ i ];
327 0 : double sum_cnt_p = (double)(sum_cnt * 100) / (double)hist->total_cnt;
328 0 : double hist_bin_cnt_K = (double)(hist_bin_cnt) / (double)1.0e3;
329 : /* bin acc */
330 0 : ulong hist_bin_acc = hist->bin_acc[ i ];
331 0 : sum_acc += hist->bin_acc[ i ];
332 0 : double sum_acc_p = (double)(sum_acc * 100) / (double)hist->total_acc;
333 0 : double hist_bin_acc_MiB = (double)(hist_bin_acc) / (double)1048576.0f;
334 : /* bin min, max, avg */
335 0 : ulong hist_bin_min = fd_ulong_if( hist->bin_cnt[ i ] > 0, hist->bin_min[ i ], 0UL );
336 0 : ulong hist_bin_max = hist->bin_max[ i ];
337 0 : ulong hist_bin_avg = hist->bin_cnt[ i ] > 0 ? hist->bin_acc[ i ] / hist->bin_cnt[ i ] : 0UL;
338 : /* log */
339 0 : char buf[256];
340 0 : FD_TEST( fd_cstr_printf_check( buf, sizeof(buf), NULL,
341 0 : "%12lu %s sz <= %12lu | %8.1f K (%6.1f %%) | %8.1f MiB (%6.1f %%) | %12lu | %12lu | %12lu |\n",
342 0 : hist_bin_tlo, i==0? "<=" : "< ", hist_bin_thi,
343 0 : hist_bin_cnt_K, sum_cnt_p,
344 0 : hist_bin_acc_MiB, sum_acc_p,
345 0 : hist_bin_min, hist_bin_max, hist_bin_avg ) );
346 0 : printf( "%s", buf );
347 0 : }
348 0 : printf( "\n" );
349 0 : }
350 :
351 : static void
352 : accounts_hist( accounts_hist_t * hist,
353 0 : config_t * config ) {
354 0 : fd_topo_t * topo = &config->topo;
355 0 : ulong accdb_obj_id = fd_pod_query_ulong( topo->props, "accdb", ULONG_MAX );
356 0 : FD_TEST( accdb_obj_id!=ULONG_MAX );
357 0 : void * _accdb_shmem = fd_topo_obj_laddr( topo, accdb_obj_id );
358 0 : fd_accdb_shmem_t * shmem = fd_accdb_shmem_join( _accdb_shmem );
359 0 : FD_TEST( shmem );
360 :
361 : /* Recompute the shmem layout to locate acc_map and acc_pool element
362 : storage without taking a writer joiner slot. This mirrors the
363 : layout in fd_accdb_shmem_new and fd_accdb_join_readonly. */
364 :
365 0 : ulong max_live_slots = shmem->max_live_slots;
366 0 : ulong max_accounts = shmem->max_accounts;
367 0 : ulong chain_cnt = shmem->chain_cnt;
368 :
369 0 : FD_SCRATCH_ALLOC_INIT( l, shmem );
370 0 : FD_SCRATCH_ALLOC_APPEND( l, FD_ACCDB_SHMEM_ALIGN, sizeof(fd_accdb_shmem_t) );
371 0 : FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_accdb_fork_shmem_t), max_live_slots*sizeof(fd_accdb_fork_shmem_t) );
372 0 : FD_SCRATCH_ALLOC_APPEND( l, descends_set_align(), max_live_slots*descends_set_footprint( max_live_slots ) );
373 0 : uint * acc_map = FD_SCRATCH_ALLOC_APPEND( l, alignof(uint), chain_cnt*sizeof(uint) );
374 0 : fd_accdb_accmeta_t * acc_pool = FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_accdb_accmeta_t), max_accounts*sizeof(fd_accdb_accmeta_t) );
375 :
376 : /* Walk every hash chain. Each non-UINT_MAX head index yields a
377 : linked list of live acc_pool elements via map.next. */
378 :
379 0 : for( ulong chain_i=0UL; chain_i<chain_cnt; chain_i++ ) {
380 0 : uint acc_idx = acc_map[ chain_i ];
381 0 : while( acc_idx!=UINT_MAX ) {
382 0 : fd_accdb_accmeta_t const * accmeta = &acc_pool[ acc_idx ];
383 0 : ulong data_sz = (ulong)FD_ACCDB_SIZE_DATA( accmeta->executable_size );
384 0 : accounts_hist_update( hist, sizeof(fd_accdb_disk_meta_t) + data_sz );
385 0 : acc_idx = accmeta->map.next;
386 0 : }
387 0 : }
388 0 : }
389 :
390 : /* fixup_config applies command-line arguments to config, overriding
391 : defaults / config file */
392 :
393 : static void
394 : fixup_config( config_t * config,
395 0 : args_t const * args ) {
396 0 : fd_topo_t * topo = &config->topo;
397 0 : if( args->snapshot_load.snapshot_dir[0] ) {
398 0 : fd_cstr_ncpy( config->paths.snapshots, args->snapshot_load.snapshot_dir, sizeof(config->paths.snapshots) );
399 0 : }
400 :
401 0 : if( args->snapshot_load.db_rec_max ) {
402 0 : config->firedancer.accounts.max_accounts = args->snapshot_load.db_rec_max;
403 0 : }
404 :
405 0 : if( args->snapshot_load.cache_sz ) {
406 0 : config->firedancer.accounts.cache_size_gib = fd_ulong_align_up( args->snapshot_load.cache_sz, (1UL<<30) )>>30;
407 0 : }
408 :
409 0 : if( args->snapshot_load.offline ) {
410 0 : config->firedancer.snapshots.sources.gossip.allow_any = 0;
411 0 : config->firedancer.snapshots.sources.gossip.allow_list_cnt = 0;
412 0 : config->firedancer.snapshots.sources.servers_cnt = 0;
413 0 : }
414 :
415 0 : if( args->snapshot_load.no_incremental ) {
416 0 : config->firedancer.snapshots.incremental_snapshots = 0;
417 0 : }
418 :
419 0 : if( FD_UNLIKELY( config->firedancer.snapshots.sources.gossip.allow_any || config->firedancer.snapshots.sources.gossip.allow_list_cnt ) ) {
420 0 : FD_LOG_WARNING(( "snapshot-load command is incompatible with gossip snapshot sources; disabling gossip snapshot sources" ));
421 0 : config->firedancer.snapshots.sources.gossip.allow_any = 0;
422 0 : config->firedancer.snapshots.sources.gossip.allow_list_cnt = 0;
423 0 : }
424 :
425 : /* FIXME Unfortunately, the fdctl boot procedure constructs the
426 : topology before parsing command-line arguments. So, here,
427 : we construct the topology again (a third time ... sigh). */
428 0 : snapshot_load_topo( config );
429 :
430 0 : fd_topob_auto_layout( topo, 0 );
431 0 : fd_topob_finish( topo, CALLBACKS );
432 0 : }
433 :
434 : static void
435 : snapshot_load_cmd_fn( args_t * args,
436 0 : config_t * config ) {
437 0 : fixup_config( config, args );
438 :
439 0 : int watch = !args->snapshot_load.no_watch;
440 :
441 0 : fd_topo_t * topo = &config->topo;
442 :
443 0 : args_t configure_args = {
444 0 : .configure.command = CONFIGURE_CMD_INIT,
445 0 : };
446 :
447 0 : for( ulong i=0UL; STAGES[ i ]; i++ )
448 0 : configure_args.configure.stages[ i ] = STAGES[ i ];
449 0 : configure_cmd_fn( &configure_args, config );
450 :
451 0 : run_firedancer_init( config, 1, 0 );
452 :
453 0 : initialize_accdb_fd( config );
454 0 : initialize_store_fds( config );
455 0 : initialize_snapshot_fds( config );
456 :
457 0 : fd_topo_join_workspaces( topo, FD_SHMEM_JOIN_MODE_READ_WRITE, FD_TOPO_CORE_DUMP_LEVEL_DISABLED );
458 0 : fd_topo_fill( topo );
459 :
460 0 : fd_topo_tile_t * snapct_tile = &topo->tiles[ fd_topo_find_tile( topo, "snapct", 0UL ) ];
461 0 : fd_topo_tile_t * snapld_tile = &topo->tiles[ fd_topo_find_tile( topo, "snapld", 0UL ) ];
462 0 : fd_topo_tile_t * snapin_tile = &topo->tiles[ fd_topo_find_tile( topo, "snapin", 0UL ) ];
463 0 : fd_topo_tile_t * snapwr_tile = &topo->tiles[ fd_topo_find_tile( topo, "snapwr", 0UL ) ];
464 0 : ulong snapdc_tile_cnt = config->firedancer.layout.snapdc_tile_count;
465 :
466 0 : double tick_per_ns = fd_tempo_tick_per_ns( NULL );
467 0 : double ns_per_tick = 1.0/tick_per_ns;
468 :
469 0 : long start = fd_log_wallclock();
470 0 : fd_topo_run_single_process( topo, 2, config->uid, config->gid, fdctl_tile_run );
471 :
472 0 : ulong volatile * const snapct_metrics = fd_metrics_tile( snapct_tile->metrics );
473 0 : ulong volatile * const snapld_metrics = fd_metrics_tile( snapld_tile->metrics );
474 0 : ulong volatile * const snapin_metrics = fd_metrics_tile( snapin_tile->metrics );
475 0 : ulong volatile * const snapwr_metrics = fd_metrics_tile( snapwr_tile->metrics );
476 0 : ulong volatile * snapdc_metrics[ FD_TOPO_MAX_TILE_IN_LINKS ];
477 0 : for( ulong i=0UL; i<snapdc_tile_cnt; i++ ) {
478 0 : fd_topo_tile_t * snapdc_tile = &topo->tiles[ fd_topo_find_tile( topo, "snapdc", i ) ];
479 0 : snapdc_metrics[ i ] = fd_metrics_tile( snapdc_tile->metrics );
480 0 : }
481 :
482 0 : ulong total_off_old = 0UL;
483 0 : ulong decomp_off_old = 0UL;
484 0 : ulong snapld_wait_old = 0UL;
485 0 : ulong snapdc_wait_old[ FD_TOPO_MAX_TILE_IN_LINKS ] = {0};
486 0 : ulong snapin_wait_old = 0UL;
487 0 : ulong snapwr_wait_old = 0UL;
488 0 : ulong acc_cnt_old = 0UL;
489 :
490 0 : int color = fd_log_colorize() && isatty( STDOUT_FILENO );
491 0 : char const * c_bold = color ? SL_BOLD : "";
492 0 : char const * c_dim = color ? SL_DIM : "";
493 0 : char const * c_norm = color ? SL_RESET : "";
494 :
495 0 : sleep( 1 );
496 0 : if( watch ) {
497 0 : printf( "%scomp%s compressed %s·%s %sraw%s uncompressed %s·%s %sacc%s accounts %s·%s "
498 0 : "%sbusy%s %%%s of load·decompress·insert·write, yellow when the bottleneck%s\n",
499 0 : c_dim, c_norm, c_dim, c_norm,
500 0 : c_dim, c_norm, c_dim, c_norm,
501 0 : c_dim, c_norm, c_dim, c_norm,
502 0 : c_dim, c_norm, c_dim, c_norm );
503 0 : fflush( stdout );
504 0 : }
505 :
506 0 : long next = start+1000L*1000L*1000L;
507 0 : for(;;) {
508 0 : ulong snapct_status = FD_VOLATILE_CONST( snapct_metrics[ MIDX( GAUGE, TILE, STATUS ) ] );
509 0 : ulong snapld_status = FD_VOLATILE_CONST( snapld_metrics[ MIDX( GAUGE, TILE, STATUS ) ] );
510 0 : ulong snapin_status = FD_VOLATILE_CONST( snapin_metrics[ MIDX( GAUGE, TILE, STATUS ) ] );
511 0 : ulong snapwr_status = FD_VOLATILE_CONST( snapwr_metrics[ MIDX( GAUGE, TILE, STATUS ) ] );
512 0 : int snapdc_shutdown = 1;
513 0 : for( ulong i=0UL; i<snapdc_tile_cnt; i++ ) {
514 0 : snapdc_shutdown &= FD_VOLATILE_CONST( snapdc_metrics[ i ][ MIDX( GAUGE, TILE, STATUS ) ] )==2UL;
515 0 : }
516 :
517 0 : if( FD_UNLIKELY( snapct_status==2UL && snapld_status==2UL && snapdc_shutdown && snapin_status==2UL && snapwr_status==2UL ) ) break;
518 :
519 0 : long cur = fd_log_wallclock();
520 0 : if( FD_UNLIKELY( cur<next ) ) {
521 0 : long sleep_nanos = fd_long_min( 1000L*1000L, next-cur );
522 0 : FD_TEST( !fd_sys_util_nanosleep( (uint)(sleep_nanos/(1000L*1000L*1000L)), (uint)(sleep_nanos%(1000L*1000L*1000L)) ) );
523 0 : continue;
524 0 : }
525 :
526 0 : ulong total_off = snapct_metrics[ MIDX( GAUGE, SNAPCT, FULL_BYTES_READ ) ] +
527 0 : snapct_metrics[ MIDX( GAUGE, SNAPCT, INCREMENTAL_BYTES_READ ) ];
528 0 : ulong decomp_off = 0UL;
529 0 : ulong snapdc_wait[ FD_TOPO_MAX_TILE_IN_LINKS ];
530 0 : for( ulong i=0UL; i<snapdc_tile_cnt; i++ ) {
531 0 : decomp_off += snapdc_metrics[ i ][ MIDX( GAUGE, SNAPDC, FULL_DECOMPRESSED_BYTES_WRITTEN ) ] +
532 0 : snapdc_metrics[ i ][ MIDX( GAUGE, SNAPDC, INCREMENTAL_DECOMPRESSED_BYTES_WRITTEN ) ];
533 0 : snapdc_wait[ i ] = snapdc_metrics[ i ][ MIDX( COUNTER, TILE, REGIME_DURATION_NANOS_CAUGHT_UP_POSTFRAG ) ]
534 0 : + snapdc_metrics[ i ][ MIDX( COUNTER, TILE, REGIME_DURATION_NANOS_BACKPRESSURE_PREFRAG ) ];
535 0 : }
536 : /* Waiting on either neighbor counts as not busy */
537 0 : ulong snapld_wait = snapld_metrics[ MIDX( COUNTER, TILE, REGIME_DURATION_NANOS_CAUGHT_UP_POSTFRAG ) ]
538 0 : + snapld_metrics[ MIDX( COUNTER, TILE, REGIME_DURATION_NANOS_BACKPRESSURE_PREFRAG ) ];
539 0 : ulong snapin_wait = snapin_metrics[ MIDX( COUNTER, TILE, REGIME_DURATION_NANOS_CAUGHT_UP_POSTFRAG ) ]
540 0 : + snapin_metrics[ MIDX( COUNTER, TILE, REGIME_DURATION_NANOS_BACKPRESSURE_PREFRAG ) ];
541 0 : ulong snapwr_wait = snapwr_metrics[ MIDX( COUNTER, TILE, REGIME_DURATION_NANOS_CAUGHT_UP_POSTFRAG ) ]
542 0 : + snapwr_metrics[ MIDX( COUNTER, TILE, REGIME_DURATION_NANOS_BACKPRESSURE_PREFRAG ) ];
543 :
544 0 : char const * phase = phase_cstr( snapct_metrics[ MIDX( GAUGE, SNAPCT, STATE ) ] );
545 0 : ulong consumed, dc_in, dc_out, size_bytes;
546 0 : if( FD_UNLIKELY( !strcmp( phase, "incr" ) ) ) {
547 0 : consumed = fd_ulong_min( snapin_metrics[ MIDX( GAUGE, SNAPIN, INCREMENTAL_BYTES_READ ) ],
548 0 : snapwr_metrics[ MIDX( GAUGE, SNAPWR, INCREMENTAL_BYTES_READ ) ] );
549 0 : dc_in = 0UL;
550 0 : dc_out = 0UL;
551 0 : for( ulong i=0UL; i<snapdc_tile_cnt; i++ ) {
552 0 : dc_in += snapdc_metrics[ i ][ MIDX( GAUGE, SNAPDC, INCREMENTAL_COMPRESSED_BYTES_READ ) ];
553 0 : dc_out += snapdc_metrics[ i ][ MIDX( GAUGE, SNAPDC, INCREMENTAL_DECOMPRESSED_BYTES_WRITTEN ) ];
554 0 : }
555 0 : size_bytes = snapct_metrics[ MIDX( GAUGE, SNAPCT, INCREMENTAL_SIZE_BYTES ) ];
556 0 : } else {
557 0 : consumed = fd_ulong_min( snapin_metrics[ MIDX( GAUGE, SNAPIN, FULL_BYTES_READ ) ],
558 0 : snapwr_metrics[ MIDX( GAUGE, SNAPWR, FULL_BYTES_READ ) ] );
559 0 : dc_in = 0UL;
560 0 : dc_out = 0UL;
561 0 : for( ulong i=0UL; i<snapdc_tile_cnt; i++ ) {
562 0 : dc_in += snapdc_metrics[ i ][ MIDX( GAUGE, SNAPDC, FULL_COMPRESSED_BYTES_READ ) ];
563 0 : dc_out += snapdc_metrics[ i ][ MIDX( GAUGE, SNAPDC, FULL_DECOMPRESSED_BYTES_WRITTEN ) ];
564 0 : }
565 0 : size_bytes = snapct_metrics[ MIDX( GAUGE, SNAPCT, FULL_SIZE_BYTES ) ];
566 0 : }
567 0 : double done_comp = dc_out ? (double)dc_in*( (double)consumed/(double)dc_out ) : 0.0;
568 0 : double progress = size_bytes ? clamp_pct( 100.0*done_comp/(double)size_bytes ) : 0.0;
569 :
570 0 : ulong acc_cnt = snapin_metrics[ MIDX( GAUGE, SNAPIN, ACCOUNT_LOADED ) ];
571 :
572 0 : if( watch ) {
573 0 : double snapdc_busy[ FD_TOPO_MAX_TILE_IN_LINKS ];
574 0 : double snapdc_busy_avg = 0.0;
575 0 : for( ulong i=0UL; i<snapdc_tile_cnt; i++ ) {
576 0 : snapdc_busy[ i ] = clamp_pct( 100.0-( ( (double)( snapdc_wait[ i ]-snapdc_wait_old[ i ] )*ns_per_tick )/1e7 ) );
577 0 : snapdc_busy_avg += snapdc_busy[ i ];
578 0 : }
579 0 : snapdc_busy_avg /= (double)snapdc_tile_cnt;
580 :
581 0 : double busy[ 4 ] = {
582 0 : clamp_pct( 100.0-( ( (double)( snapld_wait-snapld_wait_old )*ns_per_tick )/1e7 ) ),
583 0 : snapdc_busy_avg,
584 0 : clamp_pct( 100.0-( ( (double)( snapin_wait-snapin_wait_old )*ns_per_tick )/1e7 ) ),
585 0 : clamp_pct( 100.0-( ( (double)( snapwr_wait-snapwr_wait_old )*ns_per_tick )/1e7 ) ),
586 0 : };
587 :
588 0 : char bar[ 256 ];
589 0 : printf( " %s%4s%s %s %s%5.1f%%%s"
590 0 : " %scomp%s %5.2f %sGB/s%s"
591 0 : " %sraw%s %5.2f %sGB/s%s"
592 0 : " %sacc%s %4.1f %sM/s%s",
593 0 : c_dim, phase, c_norm,
594 0 : fmt_bar( bar, sizeof(bar), color, progress, 20UL ),
595 0 : c_bold, progress, c_norm,
596 0 : c_dim, c_norm, (double)( total_off -total_off_old )/1e9, c_dim, c_norm,
597 0 : c_dim, c_norm, (double)( decomp_off-decomp_off_old )/1e9, c_dim, c_norm,
598 0 : c_dim, c_norm, (double)( acc_cnt -acc_cnt_old )/1e6, c_dim, c_norm );
599 :
600 0 : static char const * tile_key[ 4 ] = { "ld", "dc(avg)", "in", "wr" };
601 0 : printf( " %sbusy%s", c_dim, c_norm );
602 0 : for( ulong i=0UL; i<4UL; i++ ) {
603 0 : printf( " %s%s%s %s%3.0f%s%%%s",
604 0 : c_dim, tile_key[ i ], c_norm,
605 0 : sev_color( color, busy[ i ] ), busy[ i ], c_dim, c_norm );
606 0 : }
607 0 : for( ulong i=0UL; i<snapdc_tile_cnt; i++ ) {
608 0 : printf( " %sdc%lu%s %s%3.0f%s%%%s",
609 0 : c_dim, i, c_norm,
610 0 : sev_color( color, snapdc_busy[ i ] ), snapdc_busy[ i ], c_dim, c_norm );
611 0 : }
612 0 : printf( "\n" );
613 0 : fflush( stdout );
614 0 : }
615 0 : total_off_old = total_off;
616 0 : decomp_off_old = decomp_off;
617 0 : snapld_wait_old = snapld_wait;
618 0 : for( ulong i=0UL; i<snapdc_tile_cnt; i++ ) {
619 0 : snapdc_wait_old[ i ] = snapdc_wait[ i ];
620 0 : }
621 0 : snapin_wait_old = snapin_wait;
622 0 : snapwr_wait_old = snapwr_wait;
623 0 : acc_cnt_old = acc_cnt;
624 :
625 0 : next+=1000L*1000L*1000L;
626 0 : }
627 :
628 0 : if( args->snapshot_load.accounts_hist ) {
629 0 : accounts_hist_t hist[1];
630 0 : accounts_hist_reset( hist );
631 0 : FD_LOG_NOTICE(( "Accounts histogram: starting" ));
632 0 : accounts_hist( hist, config );
633 0 : FD_TEST( !accounts_hist_check( hist ) );
634 0 : accounts_hist_print( hist );
635 0 : }
636 0 : }
637 :
638 : static void
639 0 : snapshot_load_args_help( fd_action_help_t * help ) {
640 0 : fd_action_help_arg( help, "--snapshot-dir", "<path>", "Load/save snapshots from this directory" );
641 0 : fd_action_help_arg( help, "--offline", NULL, "Do not attempt to download snapshots" );
642 0 : fd_action_help_arg( help, "--no-incremental", NULL, "Disable incremental snapshot loading" );
643 0 : fd_action_help_arg( help, "--no-watch", NULL, "Do not print periodic progress updates" );
644 0 : fd_action_help_arg( help, "--db-sz", "<bytes>", "Accounts cache size in bytes (e.g. 10e9 -> 10 GB)" );
645 0 : fd_action_help_arg( help, "--db-rec-max", "<num>", "Database max record/account count (e.g. 10e6 -> 10M accounts)" );
646 0 : fd_action_help_arg( help, "--fsck", NULL, "After loading, run database integrity checks" );
647 : fd_action_help_arg( help, "--accounts-hist", NULL, "After loading, analyze account size distribution" );
648 0 : }
649 :
650 : action_t fd_action_snapshot_load = {
651 : .name = NAME,
652 : .topo = snapshot_load_topo1,
653 : .perm = dev_cmd_perm,
654 : .args = snapshot_load_args,
655 : .fn = snapshot_load_cmd_fn,
656 : .description = "Load a snapshot into a database and optionally inspect it",
657 : .detail = "Boots a reduced topology that downloads (or reads from disk) a full and\n"
658 : "optional incremental snapshot, loads the accounts into a database, and\n"
659 : "can then run integrity checks or analyze the account size distribution.",
660 : .usage = NAME " [OPTIONS]",
661 : .args_help = snapshot_load_args_help,
662 : };
|