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 : snapshot_load_layout( config_t * config,
76 0 : fd_topo_t * topo ) {
77 0 : if( FD_LIKELY( !strcmp( config->layout.affinity, "auto" ) ) ) {
78 0 : fd_topob_auto_layout( topo );
79 0 : return;
80 0 : }
81 :
82 0 : ushort tile_to_cpu[ FD_TILE_MAX ];
83 0 : ulong affinity_tile_cnt = fd_topob_parse_affinity_cstr( config->layout.affinity, tile_to_cpu, 1, 1 );
84 0 : if( FD_UNLIKELY( affinity_tile_cnt<topo->tile_cnt ) ) {
85 0 : FD_LOG_ERR(( "snapshot-load topology has %lu tiles, but [layout.affinity] only provides %lu entries",
86 0 : topo->tile_cnt, affinity_tile_cnt ));
87 0 : }
88 0 : if( FD_UNLIKELY( affinity_tile_cnt>topo->tile_cnt ) ) {
89 0 : FD_LOG_WARNING(( "snapshot-load topology has %lu tiles, but [layout.affinity] provides %lu entries; ignoring extras",
90 0 : topo->tile_cnt, affinity_tile_cnt ));
91 0 : }
92 :
93 0 : fd_topo_cpus_t cpus[1];
94 0 : fd_topo_cpus_init( cpus );
95 0 : for( ulong i=0UL; i<topo->tile_cnt; i++ ) {
96 0 : ushort encoded = tile_to_cpu[ i ];
97 0 : ushort cpu_idx = (ushort)(encoded & ~FD_TOPOB_CPU_SHARED);
98 0 : if( FD_UNLIKELY( encoded!=USHORT_MAX && cpu_idx>=cpus->cpu_cnt ) ) {
99 0 : FD_LOG_ERR(( "[layout.affinity] assigns snapshot-load tile %lu to CPU %hu, but the system has %lu CPUs",
100 0 : i, cpu_idx, cpus->cpu_cnt ));
101 0 : }
102 0 : topo->tiles[ i ].cpu_idx = fd_ulong_if( encoded==USHORT_MAX,
103 0 : ULONG_MAX,
104 0 : (ulong)cpu_idx );
105 0 : topo->tiles[ i ].floats = encoded!=USHORT_MAX && !!(encoded & FD_TOPOB_CPU_SHARED);
106 0 : }
107 0 : }
108 :
109 : static void
110 0 : snapshot_load_topo( config_t * config ) {
111 0 : config->firedancer.layout.resolv_tile_count = 0;
112 0 : fd_topo_t * topo = &config->topo;
113 0 : fd_topob_new( &config->topo, config->name );
114 0 : topo->max_page_size = fd_cstr_to_shmem_page_sz( config->hugetlbfs.max_page_size );
115 :
116 0 : fd_topob_wksp( topo, "txncache" );
117 0 : fd_topo_obj_t * txncache_obj = setup_topo_txncache( topo, "txncache",
118 0 : config->firedancer.runtime.max_live_slots,
119 0 : 2UL*config->limits.max_txn_per_slot, config->firedancer.runtime.transaction_cache_size_mib<<20 );
120 0 : FD_TEST( fd_pod_insertf_ulong( topo->props, txncache_obj->id, "txncache" ) );
121 :
122 0 : fd_topob_wksp( topo, "accdb" );
123 0 : fd_topo_obj_t * accdb_obj = setup_topo_accdb( topo, "accdb",
124 0 : config->firedancer.accounts.max_accounts,
125 0 : config->firedancer.runtime.max_live_slots,
126 0 : FD_RUNTIME_MAX_ACC_WRITES_PER_SLOT,
127 0 : 8192UL,
128 0 : 1UL<<35UL,
129 0 : config->firedancer.accounts.cache_size_gib*(1UL<<30UL),
130 0 : config->tiles.bundle.enabled,
131 0 : 1UL+config->firedancer.layout.snapin_tile_count,
132 0 : 0UL );
133 0 : FD_TEST( fd_pod_insertf_ulong( topo->props, accdb_obj->id, "accdb" ) );
134 :
135 0 : fd_topob_wksp( topo, "banks" );
136 0 : fd_topo_obj_t * banks_obj = setup_topo_banks( topo, "banks",
137 0 : config->firedancer.runtime.max_live_slots,
138 0 : config->firedancer.runtime.bank_cache_cnt,
139 0 : config->firedancer.runtime.stake_delegation_cache_cnt,
140 0 : config->development.bench.max_cost_per_block );
141 0 : FD_TEST( fd_pod_insertf_ulong( topo->props, banks_obj->id, "banks" ) );
142 :
143 0 : #define FOR(cnt) for( ulong i=0UL; i<cnt; i++ )
144 :
145 : /* metrics tile *****************************************************/
146 0 : fd_topob_wksp( topo, "metric_in" );
147 0 : fd_topob_wksp( topo, "metric" );
148 0 : fd_topob_tile( topo, "metric", "metric", "metric_in", ULONG_MAX, 0, 0, 1 );
149 :
150 : /* read() tile */
151 0 : fd_topob_wksp( topo, "snapct" );
152 0 : fd_topo_tile_t * snapct_tile = fd_topob_tile( topo, "snapct", "snapct", "metric_in", ULONG_MAX, 0, 0, 1 );
153 0 : snapct_tile->allow_shutdown = 1;
154 :
155 : /* load tile */
156 0 : fd_topob_wksp( topo, "snapld" );
157 0 : fd_topo_tile_t * snapld_tile = fd_topob_tile( topo, "snapld", "snapld", "metric_in", ULONG_MAX, 0, 0, 1 );
158 0 : snapld_tile->allow_shutdown = 1;
159 :
160 : /* "snapdc": Zstandard decompress tile */
161 0 : fd_topob_wksp( topo, "snapdc" );
162 0 : ulong snapdc_tile_cnt = config->firedancer.layout.snapdc_tile_count;
163 0 : FOR(snapdc_tile_cnt) fd_topob_tile( topo, "snapdc", "snapdc", "metric_in", ULONG_MAX, 0, 0, 0 )->allow_shutdown = 1;
164 :
165 : /* Parallel snapshot loader tiles. */
166 0 : fd_topob_wksp( topo, "snapin" );
167 0 : ulong snapin_tile_cnt = config->firedancer.layout.snapin_tile_count;
168 0 : FOR(snapin_tile_cnt) {
169 0 : fd_topo_tile_t * tile = fd_topob_tile( topo, "snapin", "snapin", "metric_in", ULONG_MAX, 0, 0, 0 );
170 0 : tile->allow_shutdown = 1;
171 0 : }
172 :
173 0 : fd_topob_wksp( topo, "snapin_shmem" );
174 0 : fd_topo_obj_t * shmem_obj = fd_topob_obj( topo, "snapin_shmem", "snapin_shmem" );
175 0 : FD_TEST( fd_pod_insertf_ulong( topo->props, shmem_obj->id, "snapin_shmem" ) );
176 :
177 0 : fd_topo_obj_t * dc_ticket_obj = fd_topob_obj_named( topo, "fseq", "snapdc", "frame_ticket" );
178 0 : FOR(snapdc_tile_cnt) fd_topob_tile_uses( topo, &topo->tiles[ fd_topo_find_tile( topo, "snapdc", i ) ], dc_ticket_obj, FD_SHMEM_JOIN_MODE_READ_WRITE );
179 :
180 0 : fd_topob_wksp( topo, "diag" );
181 0 : fd_topob_tile( topo, "diag", "diag", "metric_in", ULONG_MAX, 0, 0, 0 );
182 0 : fd_topo_tile_t * accdb_tile = fd_topob_tile( topo, "accdb", "accdb", "metric_in", ULONG_MAX, 0, 0, 0 );
183 :
184 0 : fd_topob_wksp( topo, "snapct_ld" );
185 0 : fd_topob_wksp( topo, "snapld_dc" );
186 0 : fd_topob_wksp( topo, "snapdc_in" );
187 :
188 0 : fd_topob_wksp( topo, "snapin_manif" );
189 0 : fd_topob_wksp( topo, "snapct_repr" );
190 :
191 0 : fd_topob_wksp( topo, "snapin_ct" );
192 :
193 0 : fd_topob_link( topo, "snapct_ld", "snapct_ld", 128UL, sizeof(fd_ssctrl_msg_t), 1UL );
194 0 : fd_topob_link( topo, "snapld_dc", "snapld_dc", FD_SNAPSHOT_DATA_DEPTH, FD_SNAPSHOT_DATA_MTU, 1UL );
195 0 : FOR(snapdc_tile_cnt) fd_topob_link( topo, "snapdc_in", "snapdc_in", FD_SNAPSHOT_DC_IN_DEPTH, FD_SNAPSHOT_DATA_MTU, 1UL );
196 0 : fd_topob_link( topo, "snapin_manif", "snapin_manif", 4UL, sizeof(fd_snapshot_manifest_t), 1UL )->permit_no_consumers = 1;
197 0 : fd_topob_link( topo, "snapct_repr", "snapct_repr", 128UL, 0UL, 1UL )->permit_no_consumers = 1;
198 :
199 0 : FOR(snapin_tile_cnt) fd_topob_link( topo, "snapin_ct", "snapin_ct", 128UL, 0UL, 1UL );
200 0 : FOR(snapin_tile_cnt) fd_topob_tile_in( topo, "snapct", 0UL, "metric_in", "snapin_ct", i, FD_TOPOB_RELIABLE, FD_TOPOB_POLLED );
201 :
202 0 : fd_topob_tile_in ( topo, "snapct", 0UL, "metric_in", "snapld_dc", 0UL, FD_TOPOB_RELIABLE, FD_TOPOB_POLLED );
203 0 : fd_topob_tile_out( topo, "snapct", 0UL, "snapct_ld", 0UL );
204 0 : fd_topob_tile_out( topo, "snapct", 0UL, "snapct_repr", 0UL );
205 0 : fd_topob_tile_in ( topo, "snapld", 0UL, "metric_in", "snapct_ld", 0UL, FD_TOPOB_RELIABLE, FD_TOPOB_POLLED );
206 0 : fd_topob_tile_out( topo, "snapld", 0UL, "snapld_dc", 0UL );
207 0 : FOR(snapdc_tile_cnt) fd_topob_tile_in ( topo, "snapdc", i, "metric_in", "snapld_dc", 0UL, FD_TOPOB_RELIABLE, FD_TOPOB_POLLED );
208 0 : FOR(snapdc_tile_cnt) fd_topob_tile_out( topo, "snapdc", i, "snapdc_in", i );
209 0 : for( ulong t=0UL; t<snapin_tile_cnt; t++ ) {
210 0 : FOR(snapdc_tile_cnt) fd_topob_tile_in( topo, "snapin", t, "metric_in", "snapdc_in", i, FD_TOPOB_RELIABLE, FD_TOPOB_POLLED );
211 0 : fd_topob_tile_out( topo, "snapin", t, "snapin_ct", t );
212 0 : }
213 0 : fd_topob_tile_out( topo, "snapin", 0UL, "snapin_manif", 0UL );
214 :
215 0 : fd_topo_tile_t * snapin_tile = &topo->tiles[ fd_topo_find_tile( topo, "snapin", 0UL ) ];
216 0 : fd_topob_tile_uses( topo, snapin_tile, txncache_obj, FD_SHMEM_JOIN_MODE_READ_WRITE );
217 0 : fd_topob_tile_uses( topo, accdb_tile, accdb_obj, FD_SHMEM_JOIN_MODE_READ_WRITE );
218 0 : FOR(snapin_tile_cnt) {
219 0 : fd_topo_tile_t * tile = &topo->tiles[ fd_topo_find_tile( topo, "snapin", i ) ];
220 0 : fd_topob_tile_uses( topo, tile, accdb_obj, FD_SHMEM_JOIN_MODE_READ_WRITE );
221 0 : fd_topob_tile_uses( topo, tile, shmem_obj, FD_SHMEM_JOIN_MODE_READ_WRITE );
222 0 : fd_topob_tile_uses( topo, tile, banks_obj, FD_SHMEM_JOIN_MODE_READ_WRITE );
223 0 : tile->snapin.accdb_obj_id = accdb_obj->id;
224 0 : tile->snapin.txncache_obj_id = txncache_obj->id;
225 0 : tile->snapin.banks_obj_id = banks_obj->id;
226 0 : tile->snapin.shmem_obj_id = shmem_obj->id;
227 0 : tile->snapin.max_live_slots = config->firedancer.runtime.max_live_slots;
228 0 : }
229 :
230 0 : for( ulong i=0UL; i<topo->tile_cnt; i++ ) {
231 0 : fd_topo_tile_t * tile = &topo->tiles[ i ];
232 0 : fd_topo_configure_tile( tile, config );
233 0 : }
234 :
235 0 : snapshot_load_layout( config, topo );
236 0 : fd_topob_waker( topo );
237 0 : fd_topob_finish( topo, CALLBACKS );
238 0 : }
239 :
240 : static void
241 : snapshot_load_args( int * pargc,
242 : char *** pargv,
243 0 : args_t * args ) {
244 0 : if( FD_UNLIKELY( fd_env_strip_cmdline_contains( pargc, pargv, "--help" ) ) ) {
245 0 : fputs(
246 0 : "\nUsage: firedancer-dev snapshot-load [GLOBAL FLAGS] [FLAGS]\n"
247 0 : "\n"
248 0 : "Global Flags:\n"
249 0 : " --mainnet Use Solana mainnet-beta defaults\n"
250 0 : " --testnet Use Solana testnet defaults\n"
251 0 : " --devnet Use Solana devnet defaults\n"
252 0 : "\n"
253 0 : "Flags:\n"
254 0 : " --snapshot-dir PATH Load/save snapshots from this directory\n"
255 0 : " --offline Do not attempt to download snapshots\n"
256 0 : " --no-incremental Disable incremental snapshot loading\n"
257 0 : " --no-watch Do not print periodic progress updates\n"
258 0 : " --db-rec-max <num> Database max record/account count (e.g. 10e6 -> 10M accounts)\n"
259 0 : " --accounts-hist After loading, analyze account size distribution\n"
260 0 : "\n",
261 0 : stderr );
262 0 : exit( 0 );
263 0 : }
264 0 : memset( &args->snapshot_load, 0, sizeof(args->snapshot_load) );
265 :
266 0 : char const * snapshot_dir = fd_env_strip_cmdline_cstr ( pargc, pargv, "--snapshot-dir", NULL, NULL );
267 0 : int offline = fd_env_strip_cmdline_contains( pargc, pargv, "--offline" )!=0;
268 0 : int no_incremental= fd_env_strip_cmdline_contains( pargc, pargv, "--no-incremental" )!=0;
269 0 : int no_watch = fd_env_strip_cmdline_contains( pargc, pargv, "--no-watch" )!=0;
270 0 : int accounts_hist = fd_env_strip_cmdline_contains( pargc, pargv, "--accounts-hist" )!=0;
271 0 : double db_sz = fd_env_strip_cmdline_double ( pargc, pargv, "--db-sz", NULL, 0.0 );
272 0 : double db_rec_max = fd_env_strip_cmdline_double ( pargc, pargv, "--db-rec-max", NULL, 0.0 );
273 0 : if( FD_UNLIKELY( !(db_sz>=0.0 && db_sz<1.8e19) ) ) FD_LOG_ERR(( "--db-sz out of range" )); /* also rejects NaN */
274 0 : if( FD_UNLIKELY( !(db_rec_max>=0.0 && db_rec_max<1.8e19) ) ) FD_LOG_ERR(( "--db-rec-max out of range" ));
275 :
276 0 : fd_cstr_ncpy( args->snapshot_load.snapshot_dir, snapshot_dir, sizeof(args->snapshot_load.snapshot_dir) );
277 0 : args->snapshot_load.accounts_hist = accounts_hist;
278 0 : args->snapshot_load.offline = offline;
279 0 : args->snapshot_load.no_incremental = no_incremental;
280 0 : args->snapshot_load.no_watch = no_watch;
281 0 : args->snapshot_load.db_rec_max = (ulong)db_rec_max;
282 0 : args->snapshot_load.cache_sz = (ulong)db_sz;
283 0 : }
284 :
285 : /* ACCOUNTS_HIST_N (32) is chosen to make the histogram lightweight.
286 : And because accounts can have a data size in the range [0, 10MiB],
287 : the width of the bins increments in powers of 2. In the future, it
288 : should be possible to pass this as a configuration parameter. */
289 0 : #define ACCOUNTS_HIST_N (32)
290 :
291 : struct accounts_hist {
292 : ulong total_cnt;
293 : ulong total_acc;
294 : ulong bin_thi[ ACCOUNTS_HIST_N ];
295 : ulong bin_cnt[ ACCOUNTS_HIST_N ];
296 : ulong bin_acc[ ACCOUNTS_HIST_N ];
297 : ulong bin_min[ ACCOUNTS_HIST_N ];
298 : ulong bin_max[ ACCOUNTS_HIST_N ];
299 : ulong token_cnt;
300 : };
301 : typedef struct accounts_hist accounts_hist_t;
302 :
303 : static inline void
304 0 : accounts_hist_reset( accounts_hist_t * hist ) {
305 0 : hist->total_cnt = 0UL;
306 0 : hist->total_acc = 0UL;
307 0 : for( int i=0; i < ACCOUNTS_HIST_N; i++ ) {
308 0 : hist->bin_thi[ i ] = fd_ulong_if( i > 0, fd_pow2( ulong, i-1 ), 0UL );
309 0 : hist->bin_cnt[ i ] = 0UL;
310 0 : hist->bin_acc[ i ] = 0UL;
311 0 : hist->bin_min[ i ] = ULONG_MAX;
312 0 : hist->bin_max[ i ] = 0UL;
313 0 : }
314 0 : hist->token_cnt = 0UL;
315 0 : }
316 :
317 : static inline void FD_FN_UNUSED
318 : accounts_hist_update( accounts_hist_t * hist,
319 0 : ulong account_sz ) {
320 0 : hist->total_cnt += 1UL;
321 0 : hist->total_acc += account_sz;
322 0 : int i=0;
323 : /* This allows for arbitrary thresholds - not optimized for pow2
324 : bins. */
325 0 : for( ; i < ACCOUNTS_HIST_N; i++ ) {
326 0 : if( FD_UNLIKELY( account_sz <= hist->bin_thi[ i ] )) {
327 0 : hist->bin_cnt[ i ] += 1;
328 0 : hist->bin_acc[ i ] += account_sz;
329 0 : hist->bin_min[ i ] = fd_ulong_min( hist->bin_min[ i ], account_sz );
330 0 : hist->bin_max[ i ] = fd_ulong_max( hist->bin_max[ i ], account_sz );
331 0 : break;
332 0 : }
333 0 : }
334 0 : FD_TEST( i < ACCOUNTS_HIST_N );
335 0 : }
336 :
337 : static inline int
338 0 : accounts_hist_check( accounts_hist_t const * hist ) {
339 0 : ulong cnt = 0UL;
340 0 : ulong acc = 0UL;
341 0 : for( int i=0; i < ACCOUNTS_HIST_N; i++ ) {
342 0 : cnt += hist->bin_cnt[ i ];
343 0 : acc += hist->bin_acc[ i ];
344 0 : }
345 0 : if( cnt != hist->total_cnt ) return -1;
346 0 : if( acc != hist->total_acc ) return -2;
347 0 : return 0;
348 0 : }
349 :
350 : static void
351 0 : accounts_hist_print( accounts_hist_t const * hist ) {
352 0 : double hist_total_cnt_M = (double)hist->total_cnt / (double)1.0e6;
353 0 : double hist_total_cnt_GiB = (double)hist->total_acc / (double)1073741824;
354 0 : printf( "\n" );
355 0 : printf( "hist_total_cnt %16lu ( %6.1f M )\n", hist->total_cnt, hist_total_cnt_M );
356 0 : printf( "hist_total_acc %16lu ( %6.1f GiB )\n", hist->total_acc, hist_total_cnt_GiB );
357 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" );
358 0 : ulong sum_cnt = 0UL;
359 0 : ulong sum_acc = 0UL;
360 0 : for( int i=0; i < ACCOUNTS_HIST_N; i++ ) {
361 : /* bin thresholds */
362 0 : ulong hist_bin_tlo = hist->bin_thi[ fd_int_if( i>0, i-1, i ) ];
363 0 : ulong hist_bin_thi = hist->bin_thi[ i ];
364 : /* bin cnt */
365 0 : ulong hist_bin_cnt = hist->bin_cnt[ i ];
366 0 : sum_cnt += hist->bin_cnt[ i ];
367 0 : double sum_cnt_p = (double)(sum_cnt * 100) / (double)hist->total_cnt;
368 0 : double hist_bin_cnt_K = (double)(hist_bin_cnt) / (double)1.0e3;
369 : /* bin acc */
370 0 : ulong hist_bin_acc = hist->bin_acc[ i ];
371 0 : sum_acc += hist->bin_acc[ i ];
372 0 : double sum_acc_p = (double)(sum_acc * 100) / (double)hist->total_acc;
373 0 : double hist_bin_acc_MiB = (double)(hist_bin_acc) / (double)1048576.0f;
374 : /* bin min, max, avg */
375 0 : ulong hist_bin_min = fd_ulong_if( hist->bin_cnt[ i ] > 0, hist->bin_min[ i ], 0UL );
376 0 : ulong hist_bin_max = hist->bin_max[ i ];
377 0 : ulong hist_bin_avg = hist->bin_cnt[ i ] > 0 ? hist->bin_acc[ i ] / hist->bin_cnt[ i ] : 0UL;
378 : /* log */
379 0 : char buf[256];
380 0 : FD_TEST( fd_cstr_printf_check( buf, sizeof(buf), NULL,
381 0 : "%12lu %s sz <= %12lu | %8.1f K (%6.1f %%) | %8.1f MiB (%6.1f %%) | %12lu | %12lu | %12lu |\n",
382 0 : hist_bin_tlo, i==0? "<=" : "< ", hist_bin_thi,
383 0 : hist_bin_cnt_K, sum_cnt_p,
384 0 : hist_bin_acc_MiB, sum_acc_p,
385 0 : hist_bin_min, hist_bin_max, hist_bin_avg ) );
386 0 : printf( "%s", buf );
387 0 : }
388 0 : printf( "\n" );
389 0 : }
390 :
391 : static void
392 : accounts_hist( accounts_hist_t * hist,
393 0 : config_t * config ) {
394 0 : fd_topo_t * topo = &config->topo;
395 0 : ulong accdb_obj_id = fd_pod_query_ulong( topo->props, "accdb", ULONG_MAX );
396 0 : FD_TEST( accdb_obj_id!=ULONG_MAX );
397 0 : void * _accdb_shmem = fd_topo_obj_laddr( topo, accdb_obj_id );
398 0 : fd_accdb_shmem_t * shmem = fd_accdb_shmem_join( _accdb_shmem );
399 0 : FD_TEST( shmem );
400 :
401 : /* Recompute the shmem layout to locate acc_map and acc_pool element
402 : storage without taking a writer joiner slot. This mirrors the
403 : layout in fd_accdb_shmem_new and fd_accdb_join_readonly. */
404 :
405 0 : ulong max_live_slots = shmem->max_live_slots;
406 0 : ulong max_accounts = shmem->max_accounts;
407 0 : ulong chain_cnt = shmem->chain_cnt;
408 :
409 0 : FD_SCRATCH_ALLOC_INIT( l, shmem );
410 0 : FD_SCRATCH_ALLOC_APPEND( l, FD_ACCDB_SHMEM_ALIGN, sizeof(fd_accdb_shmem_t) );
411 0 : FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_accdb_fork_shmem_t), max_live_slots*sizeof(fd_accdb_fork_shmem_t) );
412 0 : FD_SCRATCH_ALLOC_APPEND( l, descends_set_align(), max_live_slots*descends_set_footprint( max_live_slots ) );
413 0 : uint * acc_map = FD_SCRATCH_ALLOC_APPEND( l, alignof(uint), chain_cnt*sizeof(uint) );
414 0 : fd_accdb_accmeta_t * acc_pool = FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_accdb_accmeta_t), max_accounts*sizeof(fd_accdb_accmeta_t) );
415 :
416 : /* Walk every hash chain. Each non-UINT_MAX head index yields a
417 : linked list of live acc_pool elements via map.next. */
418 :
419 0 : for( ulong chain_i=0UL; chain_i<chain_cnt; chain_i++ ) {
420 0 : uint acc_idx = acc_map[ chain_i ];
421 0 : while( acc_idx!=UINT_MAX ) {
422 0 : fd_accdb_accmeta_t const * accmeta = &acc_pool[ acc_idx ];
423 0 : ulong data_sz = (ulong)FD_ACCDB_SIZE_DATA( accmeta->executable_size );
424 0 : accounts_hist_update( hist, sizeof(fd_accdb_disk_meta_t) + data_sz );
425 0 : acc_idx = accmeta->map.next;
426 0 : }
427 0 : }
428 0 : }
429 :
430 : /* snapshot_load_apply_args applies command-line arguments to config,
431 : overriding defaults / config file. Called by boot before the
432 : topology is built, so the topology picks these values up directly. */
433 :
434 : static void
435 : snapshot_load_apply_args( args_t const * args,
436 0 : config_t * config ) {
437 0 : if( args->snapshot_load.snapshot_dir[0] ) {
438 0 : fd_cstr_ncpy( config->paths.snapshots, args->snapshot_load.snapshot_dir, sizeof(config->paths.snapshots) );
439 0 : }
440 :
441 0 : if( args->snapshot_load.db_rec_max ) {
442 0 : config->firedancer.accounts.max_accounts = args->snapshot_load.db_rec_max;
443 0 : }
444 :
445 0 : if( args->snapshot_load.cache_sz ) {
446 0 : config->firedancer.accounts.cache_size_gib = fd_ulong_align_up( args->snapshot_load.cache_sz, (1UL<<30) )>>30;
447 0 : }
448 :
449 0 : if( args->snapshot_load.offline ) {
450 0 : config->firedancer.snapshots.sources.gossip.allow_any = 0;
451 0 : config->firedancer.snapshots.sources.gossip.allow_list_cnt = 0;
452 0 : config->firedancer.snapshots.sources.servers_cnt = 0;
453 0 : }
454 :
455 0 : if( args->snapshot_load.no_incremental ) {
456 0 : config->firedancer.snapshots.incremental_snapshots = 0;
457 0 : }
458 :
459 0 : if( FD_UNLIKELY( config->firedancer.snapshots.sources.gossip.allow_any || config->firedancer.snapshots.sources.gossip.allow_list_cnt ) ) {
460 0 : FD_LOG_WARNING(( "snapshot-load command is incompatible with gossip snapshot sources; disabling gossip snapshot sources" ));
461 0 : config->firedancer.snapshots.sources.gossip.allow_any = 0;
462 0 : config->firedancer.snapshots.sources.gossip.allow_list_cnt = 0;
463 0 : }
464 0 : }
465 :
466 : static void
467 : snapshot_load_cmd_fn( args_t * args,
468 0 : config_t * config ) {
469 0 : int watch = !args->snapshot_load.no_watch;
470 :
471 0 : fd_topo_t * topo = &config->topo;
472 :
473 0 : args_t configure_args = {
474 0 : .configure.command = CONFIGURE_CMD_INIT,
475 0 : };
476 :
477 0 : for( ulong i=0UL; STAGES[ i ]; i++ )
478 0 : configure_args.configure.stages[ i ] = STAGES[ i ];
479 0 : configure_cmd_fn( &configure_args, config );
480 :
481 0 : run_firedancer_init( config, 1, 0 );
482 :
483 0 : initialize_accdb_fd( config );
484 0 : initialize_epoch_credits_fd( config );
485 0 : initialize_cost_tracker_fd( config );
486 0 : initialize_vote_stakes_fd( config );
487 0 : initialize_stake_delegations_fd( config );
488 0 : initialize_txncache_fd( config );
489 0 : initialize_store_fds( config );
490 0 : initialize_snapshot_fds( config );
491 :
492 0 : fd_topo_join_workspaces( topo, FD_SHMEM_JOIN_MODE_READ_WRITE, FD_TOPO_CORE_DUMP_LEVEL_DISABLED );
493 0 : fd_topo_fill( topo );
494 :
495 0 : fd_topo_tile_t * snapct_tile = &topo->tiles[ fd_topo_find_tile( topo, "snapct", 0UL ) ];
496 0 : fd_topo_tile_t * snapld_tile = &topo->tiles[ fd_topo_find_tile( topo, "snapld", 0UL ) ];
497 0 : ulong snapdc_tile_cnt = config->firedancer.layout.snapdc_tile_count;
498 0 : ulong snapin_tile_cnt = config->firedancer.layout.snapin_tile_count;
499 0 : FD_TEST( snapin_tile_cnt<=FD_TOPO_MAX_TILE_IN_LINKS );
500 :
501 0 : double tick_per_ns = fd_tempo_tick_per_ns( NULL );
502 0 : double ns_per_tick = 1.0/tick_per_ns;
503 :
504 0 : long start = fd_log_wallclock();
505 0 : fd_topo_run_single_process( topo, config->uid, config->gid, fdctl_tile_run );
506 :
507 0 : ulong volatile * const snapct_metrics = fd_metrics_tile( snapct_tile->metrics );
508 0 : ulong volatile * const snapld_metrics = fd_metrics_tile( snapld_tile->metrics );
509 0 : ulong volatile * snapin_all_metrics[ FD_TOPO_MAX_TILE_IN_LINKS ];
510 0 : for( ulong i=0UL; i<snapin_tile_cnt; i++ ) {
511 0 : fd_topo_tile_t * tile = &topo->tiles[ fd_topo_find_tile( topo, "snapin", i ) ];
512 0 : snapin_all_metrics[ i ] = fd_metrics_tile( tile->metrics );
513 0 : }
514 0 : ulong volatile * snapdc_metrics[ FD_TOPO_MAX_TILE_IN_LINKS ];
515 0 : for( ulong i=0UL; i<snapdc_tile_cnt; i++ ) {
516 0 : fd_topo_tile_t * snapdc_tile = &topo->tiles[ fd_topo_find_tile( topo, "snapdc", i ) ];
517 0 : snapdc_metrics[ i ] = fd_metrics_tile( snapdc_tile->metrics );
518 0 : }
519 :
520 0 : ulong total_off_old = 0UL;
521 0 : ulong decomp_off_old = 0UL;
522 0 : ulong snapld_wait_old = 0UL;
523 0 : ulong snapdc_wait_old[ FD_TOPO_MAX_TILE_IN_LINKS ] = {0};
524 0 : ulong snapin_wait_old[ FD_TOPO_MAX_TILE_IN_LINKS ] = {0};
525 0 : ulong acc_cnt_old = 0UL;
526 :
527 0 : int color = fd_log_colorize() && isatty( STDOUT_FILENO );
528 0 : char const * c_bold = color ? SL_BOLD : "";
529 0 : char const * c_dim = color ? SL_DIM : "";
530 0 : char const * c_norm = color ? SL_RESET : "";
531 :
532 0 : sleep( 1 );
533 0 : if( watch ) {
534 0 : printf( "%scomp%s compressed %s·%s %sraw%s uncompressed %s·%s %sacc%s accounts %s·%s "
535 0 : "%sbusy%s %%%s of load·decompress·insert·write, yellow when the bottleneck%s\n",
536 0 : c_dim, c_norm, c_dim, c_norm,
537 0 : c_dim, c_norm, c_dim, c_norm,
538 0 : c_dim, c_norm, c_dim, c_norm,
539 0 : c_dim, c_norm, c_dim, c_norm );
540 0 : fflush( stdout );
541 0 : }
542 :
543 0 : long next = start+1000L*1000L*1000L;
544 0 : for(;;) {
545 0 : ulong snapct_status = FD_VOLATILE_CONST( snapct_metrics[ MIDX( GAUGE, TILE, STATE ) ] );
546 0 : ulong snapld_status = FD_VOLATILE_CONST( snapld_metrics[ MIDX( GAUGE, TILE, STATE ) ] );
547 0 : int snapin_shutdown = 1;
548 0 : for( ulong i=0UL; i<snapin_tile_cnt; i++ ) {
549 0 : snapin_shutdown &= FD_VOLATILE_CONST( snapin_all_metrics[ i ][ MIDX( GAUGE, TILE, STATE ) ] )==2UL;
550 0 : }
551 0 : int snapdc_shutdown = 1;
552 0 : for( ulong i=0UL; i<snapdc_tile_cnt; i++ ) {
553 0 : snapdc_shutdown &= FD_VOLATILE_CONST( snapdc_metrics[ i ][ MIDX( GAUGE, TILE, STATE ) ] )==2UL;
554 0 : }
555 :
556 0 : if( FD_UNLIKELY( snapct_status==2UL && snapld_status==2UL && snapdc_shutdown && snapin_shutdown ) ) break;
557 :
558 0 : long cur = fd_log_wallclock();
559 0 : if( FD_UNLIKELY( cur<next ) ) {
560 0 : long sleep_nanos = fd_long_min( 1000L*1000L, next-cur );
561 0 : FD_TEST( !fd_sys_util_nanosleep( (uint)(sleep_nanos/(1000L*1000L*1000L)), (uint)(sleep_nanos%(1000L*1000L*1000L)) ) );
562 0 : continue;
563 0 : }
564 :
565 0 : ulong total_off = snapct_metrics[ MIDX( GAUGE, SNAPCT, READ_PROGRESS_BYTES_FULL ) ] +
566 0 : snapct_metrics[ MIDX( GAUGE, SNAPCT, READ_PROGRESS_BYTES_INCREMENTAL ) ];
567 0 : ulong decomp_off = 0UL;
568 0 : ulong snapdc_wait[ FD_TOPO_MAX_TILE_IN_LINKS ];
569 0 : for( ulong i=0UL; i<snapdc_tile_cnt; i++ ) {
570 0 : decomp_off += snapdc_metrics[ i ][ MIDX( GAUGE, SNAPDC, DECOMPRESSED_PROGRESS_BYTES_FULL ) ] +
571 0 : snapdc_metrics[ i ][ MIDX( GAUGE, SNAPDC, DECOMPRESSED_PROGRESS_BYTES_INCREMENTAL ) ];
572 0 : snapdc_wait[ i ] = snapdc_metrics[ i ][ MIDX( COUNTER, TILE, REGIME_DURATION_NANOS_CAUGHT_UP_POSTFRAG ) ]
573 0 : + snapdc_metrics[ i ][ MIDX( COUNTER, TILE, REGIME_DURATION_NANOS_CAUGHT_UP_SLEEPING ) ]
574 0 : + snapdc_metrics[ i ][ MIDX( COUNTER, TILE, REGIME_DURATION_NANOS_BACKPRESSURE_PREFRAG ) ]
575 0 : + snapdc_metrics[ i ][ MIDX( COUNTER, TILE, REGIME_DURATION_NANOS_BACKPRESSURE_SLEEPING ) ];
576 0 : }
577 : /* Waiting on either neighbor counts as not busy */
578 0 : ulong snapld_wait = snapld_metrics[ MIDX( COUNTER, TILE, REGIME_DURATION_NANOS_CAUGHT_UP_POSTFRAG ) ]
579 0 : + snapld_metrics[ MIDX( COUNTER, TILE, REGIME_DURATION_NANOS_CAUGHT_UP_SLEEPING ) ]
580 0 : + snapld_metrics[ MIDX( COUNTER, TILE, REGIME_DURATION_NANOS_BACKPRESSURE_PREFRAG ) ]
581 0 : + snapld_metrics[ MIDX( COUNTER, TILE, REGIME_DURATION_NANOS_BACKPRESSURE_SLEEPING ) ];
582 0 : ulong snapin_wait[ FD_TOPO_MAX_TILE_IN_LINKS ];
583 0 : ulong acc_cnt = 0UL;
584 0 : for( ulong i=0UL; i<snapin_tile_cnt; i++ ) {
585 0 : ulong volatile * metrics = snapin_all_metrics[ i ];
586 0 : snapin_wait[ i ] = metrics[ MIDX( COUNTER, TILE, REGIME_DURATION_NANOS_CAUGHT_UP_POSTFRAG ) ]
587 0 : + metrics[ MIDX( COUNTER, TILE, REGIME_DURATION_NANOS_CAUGHT_UP_SLEEPING ) ]
588 0 : + metrics[ MIDX( COUNTER, TILE, REGIME_DURATION_NANOS_BACKPRESSURE_PREFRAG ) ]
589 0 : + metrics[ MIDX( COUNTER, TILE, REGIME_DURATION_NANOS_BACKPRESSURE_SLEEPING ) ];
590 0 : acc_cnt += metrics[ MIDX( GAUGE, SNAPIN, ACCOUNT_PROGRESS_NEW ) ];
591 0 : }
592 :
593 0 : char const * phase = phase_cstr( snapct_metrics[ MIDX( GAUGE, SNAPCT, LOAD_STATE ) ] );
594 0 : ulong consumed, dc_in, dc_out, size_bytes;
595 0 : if( FD_UNLIKELY( !strcmp( phase, "incr" ) ) ) {
596 0 : consumed = ULONG_MAX;
597 0 : for( ulong i=0UL; i<snapin_tile_cnt; i++ ) consumed = fd_ulong_min( consumed, snapin_all_metrics[ i ][ MIDX( GAUGE, SNAPIN, PARSE_PROGRESS_BYTES_INCREMENTAL ) ] );
598 0 : dc_in = 0UL;
599 0 : dc_out = 0UL;
600 0 : for( ulong i=0UL; i<snapdc_tile_cnt; i++ ) {
601 0 : dc_in += snapdc_metrics[ i ][ MIDX( GAUGE, SNAPDC, COMPRESSED_PROGRESS_BYTES_INCREMENTAL ) ];
602 0 : dc_out += snapdc_metrics[ i ][ MIDX( GAUGE, SNAPDC, DECOMPRESSED_PROGRESS_BYTES_INCREMENTAL ) ];
603 0 : }
604 0 : size_bytes = snapct_metrics[ MIDX( GAUGE, SNAPCT, READ_EXPECTED_BYTES_INCREMENTAL ) ];
605 0 : } else {
606 0 : consumed = ULONG_MAX;
607 0 : for( ulong i=0UL; i<snapin_tile_cnt; i++ ) consumed = fd_ulong_min( consumed, snapin_all_metrics[ i ][ MIDX( GAUGE, SNAPIN, PARSE_PROGRESS_BYTES_FULL ) ] );
608 0 : dc_in = 0UL;
609 0 : dc_out = 0UL;
610 0 : for( ulong i=0UL; i<snapdc_tile_cnt; i++ ) {
611 0 : dc_in += snapdc_metrics[ i ][ MIDX( GAUGE, SNAPDC, COMPRESSED_PROGRESS_BYTES_FULL ) ];
612 0 : dc_out += snapdc_metrics[ i ][ MIDX( GAUGE, SNAPDC, DECOMPRESSED_PROGRESS_BYTES_FULL ) ];
613 0 : }
614 0 : size_bytes = snapct_metrics[ MIDX( GAUGE, SNAPCT, READ_EXPECTED_BYTES_FULL ) ];
615 0 : }
616 0 : double done_comp = dc_out ? (double)dc_in*( (double)consumed/(double)dc_out ) : 0.0;
617 0 : double progress = size_bytes ? clamp_pct( 100.0*done_comp/(double)size_bytes ) : 0.0;
618 :
619 0 : if( watch ) {
620 0 : double snapdc_busy[ FD_TOPO_MAX_TILE_IN_LINKS ];
621 0 : double snapdc_busy_avg = 0.0;
622 0 : for( ulong i=0UL; i<snapdc_tile_cnt; i++ ) {
623 0 : snapdc_busy[ i ] = clamp_pct( 100.0-( ( (double)( snapdc_wait[ i ]-snapdc_wait_old[ i ] )*ns_per_tick )/1e7 ) );
624 0 : snapdc_busy_avg += snapdc_busy[ i ];
625 0 : }
626 0 : snapdc_busy_avg /= (double)snapdc_tile_cnt;
627 :
628 0 : double snapin_busy[ FD_TOPO_MAX_TILE_IN_LINKS ];
629 0 : double snapin_busy_avg = 0.0;
630 0 : for( ulong i=0UL; i<snapin_tile_cnt; i++ ) {
631 0 : snapin_busy[ i ] = clamp_pct( 100.0-( ( (double)( snapin_wait[ i ]-snapin_wait_old[ i ] )*ns_per_tick )/1e7 ) );
632 0 : snapin_busy_avg += snapin_busy[ i ];
633 0 : }
634 0 : snapin_busy_avg /= (double)snapin_tile_cnt;
635 :
636 0 : double busy[ 3 ] = {
637 0 : clamp_pct( 100.0-( ( (double)( snapld_wait-snapld_wait_old )*ns_per_tick )/1e7 ) ),
638 0 : snapdc_busy_avg,
639 0 : snapin_busy_avg,
640 0 : };
641 :
642 0 : char bar[ 256 ];
643 0 : printf( " %s%4s%s %s %s%5.1f%%%s"
644 0 : " %scomp%s %5.2f %sGB/s%s"
645 0 : " %sraw%s %5.2f %sGB/s%s"
646 0 : " %sacc%s %4.1f %sM/s%s",
647 0 : c_dim, phase, c_norm,
648 0 : fmt_bar( bar, sizeof(bar), color, progress, 20UL ),
649 0 : c_bold, progress, c_norm,
650 0 : c_dim, c_norm, (double)( total_off -total_off_old )/1e9, c_dim, c_norm,
651 0 : c_dim, c_norm, (double)( decomp_off-decomp_off_old )/1e9, c_dim, c_norm,
652 0 : c_dim, c_norm, (double)fd_ulong_sat_sub( acc_cnt, acc_cnt_old )/1e6, c_dim, c_norm );
653 :
654 0 : static char const * tile_key[ 3 ] = { "ld", "dc(avg)", "in(avg)" };
655 0 : printf( " %sbusy%s", c_dim, c_norm );
656 0 : for( ulong i=0UL; i<3UL; i++ ) {
657 0 : printf( " %s%s%s %s%3.0f%s%%%s",
658 0 : c_dim, tile_key[ i ], c_norm,
659 0 : sev_color( color, busy[ i ] ), busy[ i ], c_dim, c_norm );
660 0 : }
661 0 : for( ulong i=0UL; i<snapdc_tile_cnt; i++ ) {
662 0 : printf( " %sdc%lu%s %s%3.0f%s%%%s",
663 0 : c_dim, i, c_norm,
664 0 : sev_color( color, snapdc_busy[ i ] ), snapdc_busy[ i ], c_dim, c_norm );
665 0 : }
666 0 : for( ulong i=0UL; i<snapin_tile_cnt; i++ ) {
667 0 : printf( " %sin%lu%s %s%3.0f%s%%%s",
668 0 : c_dim, i, c_norm,
669 0 : sev_color( color, snapin_busy[ i ] ), snapin_busy[ i ], c_dim, c_norm );
670 0 : }
671 0 : printf( "\n" );
672 0 : fflush( stdout );
673 0 : }
674 0 : total_off_old = total_off;
675 0 : decomp_off_old = decomp_off;
676 0 : snapld_wait_old = snapld_wait;
677 0 : for( ulong i=0UL; i<snapdc_tile_cnt; i++ ) {
678 0 : snapdc_wait_old[ i ] = snapdc_wait[ i ];
679 0 : }
680 0 : for( ulong i=0UL; i<snapin_tile_cnt; i++ ) {
681 0 : snapin_wait_old[ i ] = snapin_wait[ i ];
682 0 : }
683 0 : acc_cnt_old = acc_cnt;
684 :
685 0 : next+=1000L*1000L*1000L;
686 0 : }
687 :
688 0 : if( args->snapshot_load.accounts_hist ) {
689 0 : accounts_hist_t hist[1];
690 0 : accounts_hist_reset( hist );
691 0 : FD_LOG_NOTICE(( "Accounts histogram: starting" ));
692 0 : accounts_hist( hist, config );
693 0 : FD_TEST( !accounts_hist_check( hist ) );
694 0 : accounts_hist_print( hist );
695 0 : }
696 0 : }
697 :
698 : static void
699 0 : snapshot_load_args_help( fd_action_help_t * help ) {
700 0 : fd_action_help_arg( help, "--snapshot-dir", "<path>", "Load/save snapshots from this directory" );
701 0 : fd_action_help_arg( help, "--offline", NULL, "Do not attempt to download snapshots" );
702 0 : fd_action_help_arg( help, "--no-incremental", NULL, "Disable incremental snapshot loading" );
703 0 : fd_action_help_arg( help, "--no-watch", NULL, "Do not print periodic progress updates" );
704 0 : fd_action_help_arg( help, "--db-sz", "<bytes>", "Accounts cache size in bytes (e.g. 10e9 -> 10 GB)" );
705 0 : fd_action_help_arg( help, "--db-rec-max", "<num>", "Database max record/account count (e.g. 10e6 -> 10M accounts)" );
706 0 : fd_action_help_arg( help, "--fsck", NULL, "After loading, run database integrity checks" );
707 0 : fd_action_help_arg( help, "--accounts-hist", NULL, "After loading, analyze account size distribution" );
708 0 : }
709 :
710 : action_t fd_action_snapshot_load = {
711 : .name = NAME,
712 : .topo = snapshot_load_topo,
713 : .perm = dev_cmd_perm,
714 : .args = snapshot_load_args,
715 : .apply_args = snapshot_load_apply_args,
716 : .fn = snapshot_load_cmd_fn,
717 : .description = "Load a snapshot into a database and optionally inspect it",
718 : .detail = "Boots a reduced topology that downloads (or reads from disk) a full and\n"
719 : "optional incremental snapshot, loads the accounts into a database, and\n"
720 : "can then run integrity checks or analyze the account size distribution.",
721 : .usage = NAME " [OPTIONS]",
722 : .args_help = snapshot_load_args_help,
723 : };
|