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 );
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 0 : snapshot_load_topo1( config_t * config ) {
242 0 : snapshot_load_topo( config );
243 0 : }
244 :
245 : static void
246 : snapshot_load_args( int * pargc,
247 : char *** pargv,
248 0 : args_t * args ) {
249 0 : if( FD_UNLIKELY( fd_env_strip_cmdline_contains( pargc, pargv, "--help" ) ) ) {
250 0 : fputs(
251 0 : "\nUsage: firedancer-dev snapshot-load [GLOBAL FLAGS] [FLAGS]\n"
252 0 : "\n"
253 0 : "Global Flags:\n"
254 0 : " --mainnet Use Solana mainnet-beta defaults\n"
255 0 : " --testnet Use Solana testnet defaults\n"
256 0 : " --devnet Use Solana devnet defaults\n"
257 0 : "\n"
258 0 : "Flags:\n"
259 0 : " --snapshot-dir PATH Load/save snapshots from this directory\n"
260 0 : " --offline Do not attempt to download snapshots\n"
261 0 : " --no-incremental Disable incremental snapshot loading\n"
262 0 : " --no-watch Do not print periodic progress updates\n"
263 0 : " --db-rec-max <num> Database max record/account count (e.g. 10e6 -> 10M accounts)\n"
264 0 : " --accounts-hist After loading, analyze account size distribution\n"
265 0 : "\n",
266 0 : stderr );
267 0 : exit( 0 );
268 0 : }
269 0 : memset( &args->snapshot_load, 0, sizeof(args->snapshot_load) );
270 :
271 0 : char const * snapshot_dir = fd_env_strip_cmdline_cstr ( pargc, pargv, "--snapshot-dir", NULL, NULL );
272 0 : int offline = fd_env_strip_cmdline_contains( pargc, pargv, "--offline" )!=0;
273 0 : int no_incremental= fd_env_strip_cmdline_contains( pargc, pargv, "--no-incremental" )!=0;
274 0 : int no_watch = fd_env_strip_cmdline_contains( pargc, pargv, "--no-watch" )!=0;
275 0 : int accounts_hist = fd_env_strip_cmdline_contains( pargc, pargv, "--accounts-hist" )!=0;
276 0 : double db_sz = fd_env_strip_cmdline_double ( pargc, pargv, "--db-sz", NULL, 0.0 );
277 0 : double db_rec_max = fd_env_strip_cmdline_double ( pargc, pargv, "--db-rec-max", NULL, 0.0 );
278 0 : if( FD_UNLIKELY( !(db_sz>=0.0 && db_sz<1.8e19) ) ) FD_LOG_ERR(( "--db-sz out of range" )); /* also rejects NaN */
279 0 : if( FD_UNLIKELY( !(db_rec_max>=0.0 && db_rec_max<1.8e19) ) ) FD_LOG_ERR(( "--db-rec-max out of range" ));
280 :
281 0 : fd_cstr_ncpy( args->snapshot_load.snapshot_dir, snapshot_dir, sizeof(args->snapshot_load.snapshot_dir) );
282 0 : args->snapshot_load.accounts_hist = accounts_hist;
283 0 : args->snapshot_load.offline = offline;
284 0 : args->snapshot_load.no_incremental = no_incremental;
285 0 : args->snapshot_load.no_watch = no_watch;
286 0 : args->snapshot_load.db_rec_max = (ulong)db_rec_max;
287 0 : args->snapshot_load.cache_sz = (ulong)db_sz;
288 0 : }
289 :
290 : /* ACCOUNTS_HIST_N (32) is chosen to make the histogram lightweight.
291 : And because accounts can have a data size in the range [0, 10MiB],
292 : the width of the bins increments in powers of 2. In the future, it
293 : should be possible to pass this as a configuration parameter. */
294 0 : #define ACCOUNTS_HIST_N (32)
295 :
296 : struct accounts_hist {
297 : ulong total_cnt;
298 : ulong total_acc;
299 : ulong bin_thi[ ACCOUNTS_HIST_N ];
300 : ulong bin_cnt[ ACCOUNTS_HIST_N ];
301 : ulong bin_acc[ ACCOUNTS_HIST_N ];
302 : ulong bin_min[ ACCOUNTS_HIST_N ];
303 : ulong bin_max[ ACCOUNTS_HIST_N ];
304 : ulong token_cnt;
305 : };
306 : typedef struct accounts_hist accounts_hist_t;
307 :
308 : static inline void
309 0 : accounts_hist_reset( accounts_hist_t * hist ) {
310 0 : hist->total_cnt = 0UL;
311 0 : hist->total_acc = 0UL;
312 0 : for( int i=0; i < ACCOUNTS_HIST_N; i++ ) {
313 0 : hist->bin_thi[ i ] = fd_ulong_if( i > 0, fd_pow2( ulong, i-1 ), 0UL );
314 0 : hist->bin_cnt[ i ] = 0UL;
315 0 : hist->bin_acc[ i ] = 0UL;
316 0 : hist->bin_min[ i ] = ULONG_MAX;
317 0 : hist->bin_max[ i ] = 0UL;
318 0 : }
319 0 : hist->token_cnt = 0UL;
320 0 : }
321 :
322 : static inline void FD_FN_UNUSED
323 : accounts_hist_update( accounts_hist_t * hist,
324 0 : ulong account_sz ) {
325 0 : hist->total_cnt += 1UL;
326 0 : hist->total_acc += account_sz;
327 0 : int i=0;
328 : /* This allows for arbitrary thresholds - not optimized for pow2
329 : bins. */
330 0 : for( ; i < ACCOUNTS_HIST_N; i++ ) {
331 0 : if( FD_UNLIKELY( account_sz <= hist->bin_thi[ i ] )) {
332 0 : hist->bin_cnt[ i ] += 1;
333 0 : hist->bin_acc[ i ] += account_sz;
334 0 : hist->bin_min[ i ] = fd_ulong_min( hist->bin_min[ i ], account_sz );
335 0 : hist->bin_max[ i ] = fd_ulong_max( hist->bin_max[ i ], account_sz );
336 0 : break;
337 0 : }
338 0 : }
339 0 : FD_TEST( i < ACCOUNTS_HIST_N );
340 0 : }
341 :
342 : static inline int
343 0 : accounts_hist_check( accounts_hist_t const * hist ) {
344 0 : ulong cnt = 0UL;
345 0 : ulong acc = 0UL;
346 0 : for( int i=0; i < ACCOUNTS_HIST_N; i++ ) {
347 0 : cnt += hist->bin_cnt[ i ];
348 0 : acc += hist->bin_acc[ i ];
349 0 : }
350 0 : if( cnt != hist->total_cnt ) return -1;
351 0 : if( acc != hist->total_acc ) return -2;
352 0 : return 0;
353 0 : }
354 :
355 : static void
356 0 : accounts_hist_print( accounts_hist_t const * hist ) {
357 0 : double hist_total_cnt_M = (double)hist->total_cnt / (double)1.0e6;
358 0 : double hist_total_cnt_GiB = (double)hist->total_acc / (double)1073741824;
359 0 : printf( "\n" );
360 0 : printf( "hist_total_cnt %16lu ( %6.1f M )\n", hist->total_cnt, hist_total_cnt_M );
361 0 : printf( "hist_total_acc %16lu ( %6.1f GiB )\n", hist->total_acc, hist_total_cnt_GiB );
362 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" );
363 0 : ulong sum_cnt = 0UL;
364 0 : ulong sum_acc = 0UL;
365 0 : for( int i=0; i < ACCOUNTS_HIST_N; i++ ) {
366 : /* bin thresholds */
367 0 : ulong hist_bin_tlo = hist->bin_thi[ fd_int_if( i>0, i-1, i ) ];
368 0 : ulong hist_bin_thi = hist->bin_thi[ i ];
369 : /* bin cnt */
370 0 : ulong hist_bin_cnt = hist->bin_cnt[ i ];
371 0 : sum_cnt += hist->bin_cnt[ i ];
372 0 : double sum_cnt_p = (double)(sum_cnt * 100) / (double)hist->total_cnt;
373 0 : double hist_bin_cnt_K = (double)(hist_bin_cnt) / (double)1.0e3;
374 : /* bin acc */
375 0 : ulong hist_bin_acc = hist->bin_acc[ i ];
376 0 : sum_acc += hist->bin_acc[ i ];
377 0 : double sum_acc_p = (double)(sum_acc * 100) / (double)hist->total_acc;
378 0 : double hist_bin_acc_MiB = (double)(hist_bin_acc) / (double)1048576.0f;
379 : /* bin min, max, avg */
380 0 : ulong hist_bin_min = fd_ulong_if( hist->bin_cnt[ i ] > 0, hist->bin_min[ i ], 0UL );
381 0 : ulong hist_bin_max = hist->bin_max[ i ];
382 0 : ulong hist_bin_avg = hist->bin_cnt[ i ] > 0 ? hist->bin_acc[ i ] / hist->bin_cnt[ i ] : 0UL;
383 : /* log */
384 0 : char buf[256];
385 0 : FD_TEST( fd_cstr_printf_check( buf, sizeof(buf), NULL,
386 0 : "%12lu %s sz <= %12lu | %8.1f K (%6.1f %%) | %8.1f MiB (%6.1f %%) | %12lu | %12lu | %12lu |\n",
387 0 : hist_bin_tlo, i==0? "<=" : "< ", hist_bin_thi,
388 0 : hist_bin_cnt_K, sum_cnt_p,
389 0 : hist_bin_acc_MiB, sum_acc_p,
390 0 : hist_bin_min, hist_bin_max, hist_bin_avg ) );
391 0 : printf( "%s", buf );
392 0 : }
393 0 : printf( "\n" );
394 0 : }
395 :
396 : static void
397 : accounts_hist( accounts_hist_t * hist,
398 0 : config_t * config ) {
399 0 : fd_topo_t * topo = &config->topo;
400 0 : ulong accdb_obj_id = fd_pod_query_ulong( topo->props, "accdb", ULONG_MAX );
401 0 : FD_TEST( accdb_obj_id!=ULONG_MAX );
402 0 : void * _accdb_shmem = fd_topo_obj_laddr( topo, accdb_obj_id );
403 0 : fd_accdb_shmem_t * shmem = fd_accdb_shmem_join( _accdb_shmem );
404 0 : FD_TEST( shmem );
405 :
406 : /* Recompute the shmem layout to locate acc_map and acc_pool element
407 : storage without taking a writer joiner slot. This mirrors the
408 : layout in fd_accdb_shmem_new and fd_accdb_join_readonly. */
409 :
410 0 : ulong max_live_slots = shmem->max_live_slots;
411 0 : ulong max_accounts = shmem->max_accounts;
412 0 : ulong chain_cnt = shmem->chain_cnt;
413 :
414 0 : FD_SCRATCH_ALLOC_INIT( l, shmem );
415 0 : FD_SCRATCH_ALLOC_APPEND( l, FD_ACCDB_SHMEM_ALIGN, sizeof(fd_accdb_shmem_t) );
416 0 : FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_accdb_fork_shmem_t), max_live_slots*sizeof(fd_accdb_fork_shmem_t) );
417 0 : FD_SCRATCH_ALLOC_APPEND( l, descends_set_align(), max_live_slots*descends_set_footprint( max_live_slots ) );
418 0 : uint * acc_map = FD_SCRATCH_ALLOC_APPEND( l, alignof(uint), chain_cnt*sizeof(uint) );
419 0 : fd_accdb_accmeta_t * acc_pool = FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_accdb_accmeta_t), max_accounts*sizeof(fd_accdb_accmeta_t) );
420 :
421 : /* Walk every hash chain. Each non-UINT_MAX head index yields a
422 : linked list of live acc_pool elements via map.next. */
423 :
424 0 : for( ulong chain_i=0UL; chain_i<chain_cnt; chain_i++ ) {
425 0 : uint acc_idx = acc_map[ chain_i ];
426 0 : while( acc_idx!=UINT_MAX ) {
427 0 : fd_accdb_accmeta_t const * accmeta = &acc_pool[ acc_idx ];
428 0 : ulong data_sz = (ulong)FD_ACCDB_SIZE_DATA( accmeta->executable_size );
429 0 : accounts_hist_update( hist, sizeof(fd_accdb_disk_meta_t) + data_sz );
430 0 : acc_idx = accmeta->map.next;
431 0 : }
432 0 : }
433 0 : }
434 :
435 : /* fixup_config applies command-line arguments to config, overriding
436 : defaults / config file */
437 :
438 : static void
439 : fixup_config( config_t * config,
440 0 : args_t const * args ) {
441 0 : if( args->snapshot_load.snapshot_dir[0] ) {
442 0 : fd_cstr_ncpy( config->paths.snapshots, args->snapshot_load.snapshot_dir, sizeof(config->paths.snapshots) );
443 0 : }
444 :
445 0 : if( args->snapshot_load.db_rec_max ) {
446 0 : config->firedancer.accounts.max_accounts = args->snapshot_load.db_rec_max;
447 0 : }
448 :
449 0 : if( args->snapshot_load.cache_sz ) {
450 0 : config->firedancer.accounts.cache_size_gib = fd_ulong_align_up( args->snapshot_load.cache_sz, (1UL<<30) )>>30;
451 0 : }
452 :
453 0 : if( args->snapshot_load.offline ) {
454 0 : config->firedancer.snapshots.sources.gossip.allow_any = 0;
455 0 : config->firedancer.snapshots.sources.gossip.allow_list_cnt = 0;
456 0 : config->firedancer.snapshots.sources.servers_cnt = 0;
457 0 : }
458 :
459 0 : if( args->snapshot_load.no_incremental ) {
460 0 : config->firedancer.snapshots.incremental_snapshots = 0;
461 0 : }
462 :
463 0 : if( FD_UNLIKELY( config->firedancer.snapshots.sources.gossip.allow_any || config->firedancer.snapshots.sources.gossip.allow_list_cnt ) ) {
464 0 : FD_LOG_WARNING(( "snapshot-load command is incompatible with gossip snapshot sources; disabling gossip snapshot sources" ));
465 0 : config->firedancer.snapshots.sources.gossip.allow_any = 0;
466 0 : config->firedancer.snapshots.sources.gossip.allow_list_cnt = 0;
467 0 : }
468 :
469 : /* FIXME Unfortunately, the fdctl boot procedure constructs the
470 : topology before parsing command-line arguments. So, here,
471 : we construct the topology again (a third time ... sigh). */
472 0 : snapshot_load_topo( config );
473 0 : }
474 :
475 : static void
476 : snapshot_load_cmd_fn( args_t * args,
477 0 : config_t * config ) {
478 0 : fixup_config( config, args );
479 :
480 0 : int watch = !args->snapshot_load.no_watch;
481 :
482 0 : fd_topo_t * topo = &config->topo;
483 :
484 0 : args_t configure_args = {
485 0 : .configure.command = CONFIGURE_CMD_INIT,
486 0 : };
487 :
488 0 : for( ulong i=0UL; STAGES[ i ]; i++ )
489 0 : configure_args.configure.stages[ i ] = STAGES[ i ];
490 0 : configure_cmd_fn( &configure_args, config );
491 :
492 0 : run_firedancer_init( config, 1, 0 );
493 :
494 0 : initialize_accdb_fd( config );
495 0 : initialize_epoch_credits_fd( config );
496 0 : initialize_cost_tracker_fd( config );
497 0 : initialize_vote_stakes_fd( config );
498 0 : initialize_stake_delegations_fd( config );
499 0 : initialize_store_fds( config );
500 0 : initialize_snapshot_fds( config );
501 :
502 0 : fd_topo_join_workspaces( topo, FD_SHMEM_JOIN_MODE_READ_WRITE, FD_TOPO_CORE_DUMP_LEVEL_DISABLED );
503 0 : fd_topo_fill( topo );
504 :
505 0 : fd_topo_tile_t * snapct_tile = &topo->tiles[ fd_topo_find_tile( topo, "snapct", 0UL ) ];
506 0 : fd_topo_tile_t * snapld_tile = &topo->tiles[ fd_topo_find_tile( topo, "snapld", 0UL ) ];
507 0 : ulong snapdc_tile_cnt = config->firedancer.layout.snapdc_tile_count;
508 0 : ulong snapin_tile_cnt = config->firedancer.layout.snapin_tile_count;
509 0 : FD_TEST( snapin_tile_cnt<=FD_TOPO_MAX_TILE_IN_LINKS );
510 :
511 0 : double tick_per_ns = fd_tempo_tick_per_ns( NULL );
512 0 : double ns_per_tick = 1.0/tick_per_ns;
513 :
514 0 : long start = fd_log_wallclock();
515 0 : fd_topo_run_single_process( topo, config->uid, config->gid, fdctl_tile_run );
516 :
517 0 : ulong volatile * const snapct_metrics = fd_metrics_tile( snapct_tile->metrics );
518 0 : ulong volatile * const snapld_metrics = fd_metrics_tile( snapld_tile->metrics );
519 0 : ulong volatile * snapin_all_metrics[ FD_TOPO_MAX_TILE_IN_LINKS ];
520 0 : for( ulong i=0UL; i<snapin_tile_cnt; i++ ) {
521 0 : fd_topo_tile_t * tile = &topo->tiles[ fd_topo_find_tile( topo, "snapin", i ) ];
522 0 : snapin_all_metrics[ i ] = fd_metrics_tile( tile->metrics );
523 0 : }
524 0 : ulong volatile * snapdc_metrics[ FD_TOPO_MAX_TILE_IN_LINKS ];
525 0 : for( ulong i=0UL; i<snapdc_tile_cnt; i++ ) {
526 0 : fd_topo_tile_t * snapdc_tile = &topo->tiles[ fd_topo_find_tile( topo, "snapdc", i ) ];
527 0 : snapdc_metrics[ i ] = fd_metrics_tile( snapdc_tile->metrics );
528 0 : }
529 :
530 0 : ulong total_off_old = 0UL;
531 0 : ulong decomp_off_old = 0UL;
532 0 : ulong snapld_wait_old = 0UL;
533 0 : ulong snapdc_wait_old[ FD_TOPO_MAX_TILE_IN_LINKS ] = {0};
534 0 : ulong snapin_wait_old[ FD_TOPO_MAX_TILE_IN_LINKS ] = {0};
535 0 : ulong acc_cnt_old = 0UL;
536 :
537 0 : int color = fd_log_colorize() && isatty( STDOUT_FILENO );
538 0 : char const * c_bold = color ? SL_BOLD : "";
539 0 : char const * c_dim = color ? SL_DIM : "";
540 0 : char const * c_norm = color ? SL_RESET : "";
541 :
542 0 : sleep( 1 );
543 0 : if( watch ) {
544 0 : printf( "%scomp%s compressed %s·%s %sraw%s uncompressed %s·%s %sacc%s accounts %s·%s "
545 0 : "%sbusy%s %%%s of load·decompress·insert·write, yellow when the bottleneck%s\n",
546 0 : c_dim, c_norm, c_dim, c_norm,
547 0 : c_dim, c_norm, c_dim, c_norm,
548 0 : c_dim, c_norm, c_dim, c_norm,
549 0 : c_dim, c_norm, c_dim, c_norm );
550 0 : fflush( stdout );
551 0 : }
552 :
553 0 : long next = start+1000L*1000L*1000L;
554 0 : for(;;) {
555 0 : ulong snapct_status = FD_VOLATILE_CONST( snapct_metrics[ MIDX( GAUGE, TILE, STATE ) ] );
556 0 : ulong snapld_status = FD_VOLATILE_CONST( snapld_metrics[ MIDX( GAUGE, TILE, STATE ) ] );
557 0 : int snapin_shutdown = 1;
558 0 : for( ulong i=0UL; i<snapin_tile_cnt; i++ ) {
559 0 : snapin_shutdown &= FD_VOLATILE_CONST( snapin_all_metrics[ i ][ MIDX( GAUGE, TILE, STATE ) ] )==2UL;
560 0 : }
561 0 : int snapdc_shutdown = 1;
562 0 : for( ulong i=0UL; i<snapdc_tile_cnt; i++ ) {
563 0 : snapdc_shutdown &= FD_VOLATILE_CONST( snapdc_metrics[ i ][ MIDX( GAUGE, TILE, STATE ) ] )==2UL;
564 0 : }
565 :
566 0 : if( FD_UNLIKELY( snapct_status==2UL && snapld_status==2UL && snapdc_shutdown && snapin_shutdown ) ) break;
567 :
568 0 : long cur = fd_log_wallclock();
569 0 : if( FD_UNLIKELY( cur<next ) ) {
570 0 : long sleep_nanos = fd_long_min( 1000L*1000L, next-cur );
571 0 : FD_TEST( !fd_sys_util_nanosleep( (uint)(sleep_nanos/(1000L*1000L*1000L)), (uint)(sleep_nanos%(1000L*1000L*1000L)) ) );
572 0 : continue;
573 0 : }
574 :
575 0 : ulong total_off = snapct_metrics[ MIDX( GAUGE, SNAPCT, READ_PROGRESS_BYTES_FULL ) ] +
576 0 : snapct_metrics[ MIDX( GAUGE, SNAPCT, READ_PROGRESS_BYTES_INCREMENTAL ) ];
577 0 : ulong decomp_off = 0UL;
578 0 : ulong snapdc_wait[ FD_TOPO_MAX_TILE_IN_LINKS ];
579 0 : for( ulong i=0UL; i<snapdc_tile_cnt; i++ ) {
580 0 : decomp_off += snapdc_metrics[ i ][ MIDX( GAUGE, SNAPDC, DECOMPRESSED_PROGRESS_BYTES_FULL ) ] +
581 0 : snapdc_metrics[ i ][ MIDX( GAUGE, SNAPDC, DECOMPRESSED_PROGRESS_BYTES_INCREMENTAL ) ];
582 0 : snapdc_wait[ i ] = snapdc_metrics[ i ][ MIDX( COUNTER, TILE, REGIME_DURATION_NANOS_CAUGHT_UP_POSTFRAG ) ]
583 0 : + snapdc_metrics[ i ][ MIDX( COUNTER, TILE, REGIME_DURATION_NANOS_CAUGHT_UP_SLEEPING ) ]
584 0 : + snapdc_metrics[ i ][ MIDX( COUNTER, TILE, REGIME_DURATION_NANOS_BACKPRESSURE_PREFRAG ) ]
585 0 : + snapdc_metrics[ i ][ MIDX( COUNTER, TILE, REGIME_DURATION_NANOS_BACKPRESSURE_SLEEPING ) ];
586 0 : }
587 : /* Waiting on either neighbor counts as not busy */
588 0 : ulong snapld_wait = snapld_metrics[ MIDX( COUNTER, TILE, REGIME_DURATION_NANOS_CAUGHT_UP_POSTFRAG ) ]
589 0 : + snapld_metrics[ MIDX( COUNTER, TILE, REGIME_DURATION_NANOS_CAUGHT_UP_SLEEPING ) ]
590 0 : + snapld_metrics[ MIDX( COUNTER, TILE, REGIME_DURATION_NANOS_BACKPRESSURE_PREFRAG ) ]
591 0 : + snapld_metrics[ MIDX( COUNTER, TILE, REGIME_DURATION_NANOS_BACKPRESSURE_SLEEPING ) ];
592 0 : ulong snapin_wait[ FD_TOPO_MAX_TILE_IN_LINKS ];
593 0 : ulong acc_cnt = 0UL;
594 0 : for( ulong i=0UL; i<snapin_tile_cnt; i++ ) {
595 0 : ulong volatile * metrics = snapin_all_metrics[ i ];
596 0 : snapin_wait[ i ] = metrics[ MIDX( COUNTER, TILE, REGIME_DURATION_NANOS_CAUGHT_UP_POSTFRAG ) ]
597 0 : + metrics[ MIDX( COUNTER, TILE, REGIME_DURATION_NANOS_CAUGHT_UP_SLEEPING ) ]
598 0 : + metrics[ MIDX( COUNTER, TILE, REGIME_DURATION_NANOS_BACKPRESSURE_PREFRAG ) ]
599 0 : + metrics[ MIDX( COUNTER, TILE, REGIME_DURATION_NANOS_BACKPRESSURE_SLEEPING ) ];
600 0 : acc_cnt += metrics[ MIDX( GAUGE, SNAPIN, ACCOUNT_PROGRESS_NEW ) ];
601 0 : }
602 :
603 0 : char const * phase = phase_cstr( snapct_metrics[ MIDX( GAUGE, SNAPCT, LOAD_STATE ) ] );
604 0 : ulong consumed, dc_in, dc_out, size_bytes;
605 0 : if( FD_UNLIKELY( !strcmp( phase, "incr" ) ) ) {
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_INCREMENTAL ) ] );
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_INCREMENTAL ) ];
612 0 : dc_out += snapdc_metrics[ i ][ MIDX( GAUGE, SNAPDC, DECOMPRESSED_PROGRESS_BYTES_INCREMENTAL ) ];
613 0 : }
614 0 : size_bytes = snapct_metrics[ MIDX( GAUGE, SNAPCT, READ_EXPECTED_BYTES_INCREMENTAL ) ];
615 0 : } else {
616 0 : consumed = ULONG_MAX;
617 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 ) ] );
618 0 : dc_in = 0UL;
619 0 : dc_out = 0UL;
620 0 : for( ulong i=0UL; i<snapdc_tile_cnt; i++ ) {
621 0 : dc_in += snapdc_metrics[ i ][ MIDX( GAUGE, SNAPDC, COMPRESSED_PROGRESS_BYTES_FULL ) ];
622 0 : dc_out += snapdc_metrics[ i ][ MIDX( GAUGE, SNAPDC, DECOMPRESSED_PROGRESS_BYTES_FULL ) ];
623 0 : }
624 0 : size_bytes = snapct_metrics[ MIDX( GAUGE, SNAPCT, READ_EXPECTED_BYTES_FULL ) ];
625 0 : }
626 0 : double done_comp = dc_out ? (double)dc_in*( (double)consumed/(double)dc_out ) : 0.0;
627 0 : double progress = size_bytes ? clamp_pct( 100.0*done_comp/(double)size_bytes ) : 0.0;
628 :
629 0 : if( watch ) {
630 0 : double snapdc_busy[ FD_TOPO_MAX_TILE_IN_LINKS ];
631 0 : double snapdc_busy_avg = 0.0;
632 0 : for( ulong i=0UL; i<snapdc_tile_cnt; i++ ) {
633 0 : snapdc_busy[ i ] = clamp_pct( 100.0-( ( (double)( snapdc_wait[ i ]-snapdc_wait_old[ i ] )*ns_per_tick )/1e7 ) );
634 0 : snapdc_busy_avg += snapdc_busy[ i ];
635 0 : }
636 0 : snapdc_busy_avg /= (double)snapdc_tile_cnt;
637 :
638 0 : double snapin_busy[ FD_TOPO_MAX_TILE_IN_LINKS ];
639 0 : double snapin_busy_avg = 0.0;
640 0 : for( ulong i=0UL; i<snapin_tile_cnt; i++ ) {
641 0 : snapin_busy[ i ] = clamp_pct( 100.0-( ( (double)( snapin_wait[ i ]-snapin_wait_old[ i ] )*ns_per_tick )/1e7 ) );
642 0 : snapin_busy_avg += snapin_busy[ i ];
643 0 : }
644 0 : snapin_busy_avg /= (double)snapin_tile_cnt;
645 :
646 0 : double busy[ 3 ] = {
647 0 : clamp_pct( 100.0-( ( (double)( snapld_wait-snapld_wait_old )*ns_per_tick )/1e7 ) ),
648 0 : snapdc_busy_avg,
649 0 : snapin_busy_avg,
650 0 : };
651 :
652 0 : char bar[ 256 ];
653 0 : printf( " %s%4s%s %s %s%5.1f%%%s"
654 0 : " %scomp%s %5.2f %sGB/s%s"
655 0 : " %sraw%s %5.2f %sGB/s%s"
656 0 : " %sacc%s %4.1f %sM/s%s",
657 0 : c_dim, phase, c_norm,
658 0 : fmt_bar( bar, sizeof(bar), color, progress, 20UL ),
659 0 : c_bold, progress, c_norm,
660 0 : c_dim, c_norm, (double)( total_off -total_off_old )/1e9, c_dim, c_norm,
661 0 : c_dim, c_norm, (double)( decomp_off-decomp_off_old )/1e9, c_dim, c_norm,
662 0 : c_dim, c_norm, (double)fd_ulong_sat_sub( acc_cnt, acc_cnt_old )/1e6, c_dim, c_norm );
663 :
664 0 : static char const * tile_key[ 3 ] = { "ld", "dc(avg)", "in(avg)" };
665 0 : printf( " %sbusy%s", c_dim, c_norm );
666 0 : for( ulong i=0UL; i<3UL; i++ ) {
667 0 : printf( " %s%s%s %s%3.0f%s%%%s",
668 0 : c_dim, tile_key[ i ], c_norm,
669 0 : sev_color( color, busy[ i ] ), busy[ i ], c_dim, c_norm );
670 0 : }
671 0 : for( ulong i=0UL; i<snapdc_tile_cnt; i++ ) {
672 0 : printf( " %sdc%lu%s %s%3.0f%s%%%s",
673 0 : c_dim, i, c_norm,
674 0 : sev_color( color, snapdc_busy[ i ] ), snapdc_busy[ i ], c_dim, c_norm );
675 0 : }
676 0 : for( ulong i=0UL; i<snapin_tile_cnt; i++ ) {
677 0 : printf( " %sin%lu%s %s%3.0f%s%%%s",
678 0 : c_dim, i, c_norm,
679 0 : sev_color( color, snapin_busy[ i ] ), snapin_busy[ i ], c_dim, c_norm );
680 0 : }
681 0 : printf( "\n" );
682 0 : fflush( stdout );
683 0 : }
684 0 : total_off_old = total_off;
685 0 : decomp_off_old = decomp_off;
686 0 : snapld_wait_old = snapld_wait;
687 0 : for( ulong i=0UL; i<snapdc_tile_cnt; i++ ) {
688 0 : snapdc_wait_old[ i ] = snapdc_wait[ i ];
689 0 : }
690 0 : for( ulong i=0UL; i<snapin_tile_cnt; i++ ) {
691 0 : snapin_wait_old[ i ] = snapin_wait[ i ];
692 0 : }
693 0 : acc_cnt_old = acc_cnt;
694 :
695 0 : next+=1000L*1000L*1000L;
696 0 : }
697 :
698 0 : if( args->snapshot_load.accounts_hist ) {
699 0 : accounts_hist_t hist[1];
700 0 : accounts_hist_reset( hist );
701 0 : FD_LOG_NOTICE(( "Accounts histogram: starting" ));
702 0 : accounts_hist( hist, config );
703 0 : FD_TEST( !accounts_hist_check( hist ) );
704 0 : accounts_hist_print( hist );
705 0 : }
706 0 : }
707 :
708 : static void
709 0 : snapshot_load_args_help( fd_action_help_t * help ) {
710 0 : fd_action_help_arg( help, "--snapshot-dir", "<path>", "Load/save snapshots from this directory" );
711 0 : fd_action_help_arg( help, "--offline", NULL, "Do not attempt to download snapshots" );
712 0 : fd_action_help_arg( help, "--no-incremental", NULL, "Disable incremental snapshot loading" );
713 0 : fd_action_help_arg( help, "--no-watch", NULL, "Do not print periodic progress updates" );
714 0 : fd_action_help_arg( help, "--db-sz", "<bytes>", "Accounts cache size in bytes (e.g. 10e9 -> 10 GB)" );
715 0 : fd_action_help_arg( help, "--db-rec-max", "<num>", "Database max record/account count (e.g. 10e6 -> 10M accounts)" );
716 0 : fd_action_help_arg( help, "--fsck", NULL, "After loading, run database integrity checks" );
717 0 : fd_action_help_arg( help, "--accounts-hist", NULL, "After loading, analyze account size distribution" );
718 0 : }
719 :
720 : action_t fd_action_snapshot_load = {
721 : .name = NAME,
722 : .topo = snapshot_load_topo1,
723 : .perm = dev_cmd_perm,
724 : .args = snapshot_load_args,
725 : .fn = snapshot_load_cmd_fn,
726 : .description = "Load a snapshot into a database and optionally inspect it",
727 : .detail = "Boots a reduced topology that downloads (or reads from disk) a full and\n"
728 : "optional incremental snapshot, loads the accounts into a database, and\n"
729 : "can then run integrity checks or analyze the account size distribution.",
730 : .usage = NAME " [OPTIONS]",
731 : .args_help = snapshot_load_args_help,
732 : };
|