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 : FD_PACK_MAX_TXNCACHE_TXN_PER_SLOT,
85 0 : config->development.bench.larger_max_cost_per_block );
86 0 : FD_TEST( fd_pod_insertf_ulong( topo->props, txncache_obj->id, "txncache" ) );
87 :
88 0 : fd_topob_wksp( topo, "accdb" );
89 0 : fd_topo_obj_t * accdb_obj = setup_topo_accdb( topo, "accdb",
90 0 : config->firedancer.accounts.max_accounts,
91 0 : config->firedancer.runtime.max_live_slots,
92 0 : FD_RUNTIME_MAX_WRITABLE_ACCOUNTS_PER_SLOT,
93 0 : 8192UL,
94 0 : 1UL<<35UL,
95 0 : config->firedancer.accounts.cache_size_gib*(1UL<<30UL),
96 0 : config->tiles.bundle.enabled,
97 0 : 2UL,
98 0 : 0UL );
99 0 : FD_TEST( fd_pod_insertf_ulong( topo->props, accdb_obj->id, "accdb" ) );
100 :
101 0 : fd_topob_wksp( topo, "banks" );
102 0 : fd_topo_obj_t * banks_obj = setup_topo_banks( topo, "banks",
103 0 : config->firedancer.runtime.max_live_slots,
104 0 : config->firedancer.runtime.max_fork_width,
105 0 : config->development.bench.larger_max_cost_per_block );
106 0 : FD_TEST( fd_pod_insertf_ulong( topo->props, banks_obj->id, "banks" ) );
107 :
108 0 : #define FOR(cnt) for( ulong i=0UL; i<cnt; i++ )
109 :
110 : /* metrics tile *****************************************************/
111 0 : fd_topob_wksp( topo, "metric_in" );
112 0 : fd_topob_wksp( topo, "metric" );
113 0 : fd_topob_tile( topo, "metric", "metric", "metric_in", ULONG_MAX, 0, 0, 0 );
114 :
115 : /* read() tile */
116 0 : fd_topob_wksp( topo, "snapct" );
117 0 : fd_topo_tile_t * snapct_tile = fd_topob_tile( topo, "snapct", "snapct", "metric_in", ULONG_MAX, 0, 0, 0 );
118 0 : snapct_tile->allow_shutdown = 1;
119 :
120 : /* load tile */
121 0 : fd_topob_wksp( topo, "snapld" );
122 0 : fd_topo_tile_t * snapld_tile = fd_topob_tile( topo, "snapld", "snapld", "metric_in", ULONG_MAX, 0, 0, 0 );
123 0 : snapld_tile->allow_shutdown = 1;
124 :
125 : /* "snapdc": Zstandard decompress tile */
126 0 : fd_topob_wksp( topo, "snapdc" );
127 0 : fd_topo_tile_t * snapdc_tile = fd_topob_tile( topo, "snapdc", "snapdc", "metric_in", ULONG_MAX, 0, 0, 0 );
128 0 : snapdc_tile->allow_shutdown = 1;
129 :
130 : /* "snapin": Snapshot parser tile */
131 0 : fd_topob_wksp( topo, "snapin" );
132 0 : fd_topo_tile_t * snapin_tile = fd_topob_tile( topo, "snapin", "snapin", "metric_in", ULONG_MAX, 0, 0, 0 );
133 0 : snapin_tile->allow_shutdown = 1;
134 :
135 0 : fd_topob_wksp( topo, "snapwr" );
136 0 : fd_topo_tile_t * snapwr_tile = fd_topob_tile( topo, "snapwr", "snapwr", "metric_in", ULONG_MAX, 0, 0, 0 );
137 0 : snapwr_tile->allow_shutdown = 1;
138 :
139 0 : fd_topob_wksp( topo, "diag" );
140 0 : fd_topob_tile( topo, "diag", "diag", "metric_in", ULONG_MAX, 0, 0, 0 );
141 0 : fd_topo_tile_t * accdb_tile = fd_topob_tile( topo, "accdb", "accdb", "metric_in", ULONG_MAX, 0, 0, 0 );
142 :
143 0 : fd_topob_wksp( topo, "snapct_ld" );
144 0 : fd_topob_wksp( topo, "snapld_dc" );
145 0 : fd_topob_wksp( topo, "snapdc_in" );
146 :
147 0 : fd_topob_wksp( topo, "snapin_manif" );
148 0 : fd_topob_wksp( topo, "snapct_repr" );
149 :
150 0 : fd_topob_wksp( topo, "snapin_ct" );
151 0 : fd_topob_wksp( topo, "snapwr_ct" );
152 :
153 0 : fd_topob_link( topo, "snapct_ld", "snapct_ld", 128UL, sizeof(fd_ssctrl_init_t), 1UL );
154 0 : fd_topob_link( topo, "snapld_dc", "snapld_dc", 16384UL, FD_SNAPSHOT_DATA_MTU, 1UL );
155 0 : fd_topob_link( topo, "snapdc_in", "snapdc_in", 16384UL, FD_SNAPSHOT_DATA_MTU, 1UL );
156 0 : fd_topob_link( topo, "snapin_manif", "snapin_manif", 4UL, sizeof(fd_snapshot_manifest_t), 1UL )->permit_no_consumers = 1;
157 0 : fd_topob_link( topo, "snapct_repr", "snapct_repr", 128UL, 0UL, 1UL )->permit_no_consumers = 1;
158 :
159 0 : fd_topob_link( topo, "snapin_ct", "snapin_ct", 128UL, 0UL, 1UL );
160 0 : fd_topob_link( topo, "snapwr_ct", "snapwr_ct", 128UL, 0UL, 1UL );
161 0 : fd_topob_tile_in( topo, "snapct", 0UL, "metric_in", "snapin_ct", 0UL, FD_TOPOB_RELIABLE, FD_TOPOB_POLLED );
162 0 : fd_topob_tile_in( topo, "snapct", 0UL, "metric_in", "snapwr_ct", 0UL, FD_TOPOB_RELIABLE, FD_TOPOB_POLLED );
163 :
164 0 : fd_topob_tile_in ( topo, "snapct", 0UL, "metric_in", "snapld_dc", 0UL, FD_TOPOB_RELIABLE, FD_TOPOB_POLLED );
165 0 : fd_topob_tile_out( topo, "snapct", 0UL, "snapct_ld", 0UL );
166 0 : fd_topob_tile_out( topo, "snapct", 0UL, "snapct_repr", 0UL );
167 0 : fd_topob_tile_in ( topo, "snapld", 0UL, "metric_in", "snapct_ld", 0UL, FD_TOPOB_RELIABLE, FD_TOPOB_POLLED );
168 0 : fd_topob_tile_out( topo, "snapld", 0UL, "snapld_dc", 0UL );
169 0 : fd_topob_tile_in ( topo, "snapdc", 0UL, "metric_in", "snapld_dc", 0UL, FD_TOPOB_RELIABLE, FD_TOPOB_POLLED );
170 0 : fd_topob_tile_out( topo, "snapdc", 0UL, "snapdc_in", 0UL );
171 0 : fd_topob_tile_in ( topo, "snapin", 0UL, "metric_in", "snapdc_in", 0UL, FD_TOPOB_RELIABLE, FD_TOPOB_POLLED );
172 0 : fd_topob_tile_out( topo, "snapin", 0UL, "snapin_manif", 0UL );
173 0 : fd_topob_tile_out( topo, "snapin", 0UL, "snapin_ct", 0UL );
174 0 : fd_topob_tile_in ( topo, "snapwr", 0UL, "metric_in", "snapdc_in", 0UL, FD_TOPOB_RELIABLE, FD_TOPOB_POLLED );
175 0 : fd_topob_tile_out( topo, "snapwr", 0UL, "snapwr_ct", 0UL );
176 :
177 0 : fd_topob_tile_uses( topo, snapin_tile, txncache_obj, FD_SHMEM_JOIN_MODE_READ_WRITE );
178 0 : fd_topob_tile_uses( topo, snapin_tile, accdb_obj, FD_SHMEM_JOIN_MODE_READ_WRITE );
179 0 : fd_topob_tile_uses( topo, snapin_tile, banks_obj, FD_SHMEM_JOIN_MODE_READ_WRITE );
180 0 : fd_topob_tile_uses( topo, accdb_tile, accdb_obj, FD_SHMEM_JOIN_MODE_READ_WRITE );
181 0 : snapin_tile->snapin.accdb_obj_id = accdb_obj->id;
182 0 : snapin_tile->snapin.txncache_obj_id = txncache_obj->id;
183 0 : snapin_tile->snapin.banks_obj_id = banks_obj->id;
184 0 : snapin_tile->snapin.max_live_slots = config->firedancer.runtime.max_live_slots;
185 :
186 0 : for( ulong i=0UL; i<topo->tile_cnt; i++ ) {
187 0 : fd_topo_tile_t * tile = &topo->tiles[ i ];
188 0 : fd_topo_configure_tile( tile, config );
189 0 : }
190 :
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 :
232 0 : fd_cstr_ncpy( args->snapshot_load.snapshot_dir, snapshot_dir, sizeof(args->snapshot_load.snapshot_dir) );
233 0 : args->snapshot_load.accounts_hist = accounts_hist;
234 0 : args->snapshot_load.offline = offline;
235 0 : args->snapshot_load.no_incremental = no_incremental;
236 0 : args->snapshot_load.no_watch = no_watch;
237 0 : }
238 :
239 : /* ACCOUNTS_HIST_N (32) is chosen to make the histogram lightweight.
240 : And because accounts can have a data size in the range [0, 10MiB],
241 : the width of the bins increments in powers of 2. In the future, it
242 : should be possible to pass this as a configuration parameter. */
243 0 : #define ACCOUNTS_HIST_N (32)
244 :
245 : struct accounts_hist {
246 : ulong total_cnt;
247 : ulong total_acc;
248 : ulong bin_thi[ ACCOUNTS_HIST_N ];
249 : ulong bin_cnt[ ACCOUNTS_HIST_N ];
250 : ulong bin_acc[ ACCOUNTS_HIST_N ];
251 : ulong bin_min[ ACCOUNTS_HIST_N ];
252 : ulong bin_max[ ACCOUNTS_HIST_N ];
253 : ulong token_cnt;
254 : };
255 : typedef struct accounts_hist accounts_hist_t;
256 :
257 : static inline void
258 0 : accounts_hist_reset( accounts_hist_t * hist ) {
259 0 : hist->total_cnt = 0UL;
260 0 : hist->total_acc = 0UL;
261 0 : for( int i=0; i < ACCOUNTS_HIST_N; i++ ) {
262 0 : hist->bin_thi[ i ] = fd_ulong_if( i > 0, fd_pow2( ulong, i-1 ), 0UL );
263 0 : hist->bin_cnt[ i ] = 0UL;
264 0 : hist->bin_acc[ i ] = 0UL;
265 0 : hist->bin_min[ i ] = ULONG_MAX;
266 0 : hist->bin_max[ i ] = 0UL;
267 0 : }
268 0 : hist->token_cnt = 0UL;
269 0 : }
270 :
271 : static inline void FD_FN_UNUSED
272 : accounts_hist_update( accounts_hist_t * hist,
273 0 : ulong account_sz ) {
274 0 : hist->total_cnt += 1UL;
275 0 : hist->total_acc += account_sz;
276 0 : int i=0;
277 : /* This allows for arbitrary thresholds - not optimized for pow2
278 : bins. */
279 0 : for( ; i < ACCOUNTS_HIST_N; i++ ) {
280 0 : if( FD_UNLIKELY( account_sz <= hist->bin_thi[ i ] )) {
281 0 : hist->bin_cnt[ i ] += 1;
282 0 : hist->bin_acc[ i ] += account_sz;
283 0 : hist->bin_min[ i ] = fd_ulong_min( hist->bin_min[ i ], account_sz );
284 0 : hist->bin_max[ i ] = fd_ulong_max( hist->bin_max[ i ], account_sz );
285 0 : break;
286 0 : }
287 0 : }
288 0 : FD_TEST( i < ACCOUNTS_HIST_N );
289 0 : }
290 :
291 : static inline int
292 0 : accounts_hist_check( accounts_hist_t const * hist ) {
293 0 : ulong cnt = 0UL;
294 0 : ulong acc = 0UL;
295 0 : for( int i=0; i < ACCOUNTS_HIST_N; i++ ) {
296 0 : cnt += hist->bin_cnt[ i ];
297 0 : acc += hist->bin_acc[ i ];
298 0 : }
299 0 : if( cnt != hist->total_cnt ) return -1;
300 0 : if( acc != hist->total_acc ) return -2;
301 0 : return 0;
302 0 : }
303 :
304 : static void
305 0 : accounts_hist_print( accounts_hist_t const * hist ) {
306 0 : double hist_total_cnt_M = (double)hist->total_cnt / (double)1.0e6;
307 0 : double hist_total_cnt_GiB = (double)hist->total_acc / (double)1073741824;
308 0 : printf( "\n" );
309 0 : printf( "hist_total_cnt %16lu ( %6.1f M )\n", hist->total_cnt, hist_total_cnt_M );
310 0 : printf( "hist_total_acc %16lu ( %6.1f GiB )\n", hist->total_acc, hist_total_cnt_GiB );
311 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" );
312 0 : ulong sum_cnt = 0UL;
313 0 : ulong sum_acc = 0UL;
314 0 : for( int i=0; i < ACCOUNTS_HIST_N; i++ ) {
315 : /* bin thresholds */
316 0 : ulong hist_bin_tlo = hist->bin_thi[ fd_int_if( i>0, i-1, i ) ];
317 0 : ulong hist_bin_thi = hist->bin_thi[ i ];
318 : /* bin cnt */
319 0 : ulong hist_bin_cnt = hist->bin_cnt[ i ];
320 0 : sum_cnt += hist->bin_cnt[ i ];
321 0 : double sum_cnt_p = (double)(sum_cnt * 100) / (double)hist->total_cnt;
322 0 : double hist_bin_cnt_K = (double)(hist_bin_cnt) / (double)1.0e3;
323 : /* bin acc */
324 0 : ulong hist_bin_acc = hist->bin_acc[ i ];
325 0 : sum_acc += hist->bin_acc[ i ];
326 0 : double sum_acc_p = (double)(sum_acc * 100) / (double)hist->total_acc;
327 0 : double hist_bin_acc_MiB = (double)(hist_bin_acc) / (double)1048576.0f;
328 : /* bin min, max, avg */
329 0 : ulong hist_bin_min = fd_ulong_if( hist->bin_cnt[ i ] > 0, hist->bin_min[ i ], 0UL );
330 0 : ulong hist_bin_max = hist->bin_max[ i ];
331 0 : ulong hist_bin_avg = hist->bin_cnt[ i ] > 0 ? hist->bin_acc[ i ] / hist->bin_cnt[ i ] : 0UL;
332 : /* log */
333 0 : char buf[256];
334 0 : FD_TEST( fd_cstr_printf_check( buf, sizeof(buf), NULL,
335 0 : "%12lu %s sz <= %12lu | %8.1f K (%6.1f %%) | %8.1f MiB (%6.1f %%) | %12lu | %12lu | %12lu |\n",
336 0 : hist_bin_tlo, i==0? "<=" : "< ", hist_bin_thi,
337 0 : hist_bin_cnt_K, sum_cnt_p,
338 0 : hist_bin_acc_MiB, sum_acc_p,
339 0 : hist_bin_min, hist_bin_max, hist_bin_avg ) );
340 0 : printf( "%s", buf );
341 0 : }
342 0 : printf( "\n" );
343 0 : }
344 :
345 : static void
346 : accounts_hist( accounts_hist_t * hist,
347 0 : config_t * config ) {
348 0 : fd_topo_t * topo = &config->topo;
349 0 : ulong accdb_obj_id = fd_pod_query_ulong( topo->props, "accdb", ULONG_MAX );
350 0 : FD_TEST( accdb_obj_id!=ULONG_MAX );
351 0 : void * _accdb_shmem = fd_topo_obj_laddr( topo, accdb_obj_id );
352 0 : fd_accdb_shmem_t * shmem = fd_accdb_shmem_join( _accdb_shmem );
353 0 : FD_TEST( shmem );
354 :
355 : /* Recompute the shmem layout to locate acc_map and acc_pool element
356 : storage without taking a writer joiner slot. This mirrors the
357 : layout in fd_accdb_shmem_new and fd_accdb_join_readonly. */
358 :
359 0 : ulong max_live_slots = shmem->max_live_slots;
360 0 : ulong max_accounts = shmem->max_accounts;
361 0 : ulong max_account_writes_per_slot = shmem->max_account_writes_per_slot;
362 0 : ulong partition_cnt = shmem->partition_cnt;
363 0 : ulong chain_cnt = shmem->chain_cnt;
364 0 : ulong txn_max = max_live_slots * max_account_writes_per_slot;
365 :
366 0 : FD_SCRATCH_ALLOC_INIT( l, shmem );
367 0 : FD_SCRATCH_ALLOC_APPEND( l, FD_ACCDB_SHMEM_ALIGN, sizeof(fd_accdb_shmem_t) );
368 0 : FD_SCRATCH_ALLOC_APPEND( l, fork_pool_align(), fork_pool_footprint() );
369 0 : FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_accdb_fork_shmem_t), max_live_slots*sizeof(fd_accdb_fork_shmem_t) );
370 0 : FD_SCRATCH_ALLOC_APPEND( l, descends_set_align(), max_live_slots*descends_set_footprint( max_live_slots ) );
371 0 : uint * acc_map = FD_SCRATCH_ALLOC_APPEND( l, alignof(uint), chain_cnt*sizeof(uint) );
372 0 : FD_SCRATCH_ALLOC_APPEND( l, acc_pool_align(), acc_pool_footprint() );
373 0 : fd_accdb_accmeta_t * acc_pool = FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_accdb_accmeta_t), max_accounts*sizeof(fd_accdb_accmeta_t) );
374 0 : (void)txn_max; (void)partition_cnt;
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_snapshot_fds( config );
455 :
456 0 : fd_topo_join_workspaces( topo, FD_SHMEM_JOIN_MODE_READ_WRITE, FD_TOPO_CORE_DUMP_LEVEL_DISABLED );
457 0 : fd_topo_fill( topo );
458 :
459 0 : fd_topo_tile_t * snapct_tile = &topo->tiles[ fd_topo_find_tile( topo, "snapct", 0UL ) ];
460 0 : fd_topo_tile_t * snapld_tile = &topo->tiles[ fd_topo_find_tile( topo, "snapld", 0UL ) ];
461 0 : fd_topo_tile_t * snapdc_tile = &topo->tiles[ fd_topo_find_tile( topo, "snapdc", 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 :
465 0 : double tick_per_ns = fd_tempo_tick_per_ns( NULL );
466 0 : double ns_per_tick = 1.0/tick_per_ns;
467 :
468 0 : long start = fd_log_wallclock();
469 0 : fd_topo_run_single_process( topo, 2, config->uid, config->gid, fdctl_tile_run );
470 :
471 0 : ulong volatile * const snapct_metrics = fd_metrics_tile( snapct_tile->metrics );
472 0 : ulong volatile * const snapld_metrics = fd_metrics_tile( snapld_tile->metrics );
473 0 : ulong volatile * const snapdc_metrics = fd_metrics_tile( snapdc_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 :
477 0 : ulong total_off_old = 0UL;
478 0 : ulong decomp_off_old = 0UL;
479 0 : ulong snapld_wait_old = 0UL;
480 0 : ulong snapdc_wait_old = 0UL;
481 0 : ulong snapin_wait_old = 0UL;
482 0 : ulong snapwr_wait_old = 0UL;
483 0 : ulong acc_cnt_old = 0UL;
484 :
485 0 : int color = fd_log_colorize() && isatty( STDOUT_FILENO );
486 0 : char const * c_bold = color ? SL_BOLD : "";
487 0 : char const * c_dim = color ? SL_DIM : "";
488 0 : char const * c_norm = color ? SL_RESET : "";
489 :
490 0 : sleep( 1 );
491 0 : if( watch ) {
492 0 : printf( "%scomp%s compressed %s·%s %sraw%s uncompressed %s·%s %sacc%s accounts %s·%s "
493 0 : "%sbusy%s %%%s of load·decompress·insert·write, yellow when the bottleneck%s\n",
494 0 : c_dim, c_norm, c_dim, c_norm,
495 0 : c_dim, c_norm, c_dim, c_norm,
496 0 : c_dim, c_norm, c_dim, c_norm,
497 0 : c_dim, c_norm, c_dim, c_norm );
498 0 : fflush( stdout );
499 0 : }
500 :
501 0 : long next = start+1000L*1000L*1000L;
502 0 : for(;;) {
503 0 : ulong snapct_status = FD_VOLATILE_CONST( snapct_metrics[ MIDX( GAUGE, TILE, STATUS ) ] );
504 0 : ulong snapld_status = FD_VOLATILE_CONST( snapld_metrics[ MIDX( GAUGE, TILE, STATUS ) ] );
505 0 : ulong snapdc_status = FD_VOLATILE_CONST( snapdc_metrics[ MIDX( GAUGE, TILE, STATUS ) ] );
506 0 : ulong snapin_status = FD_VOLATILE_CONST( snapin_metrics[ MIDX( GAUGE, TILE, STATUS ) ] );
507 0 : ulong snapwr_status = FD_VOLATILE_CONST( snapwr_metrics[ MIDX( GAUGE, TILE, STATUS ) ] );
508 :
509 0 : if( FD_UNLIKELY( snapct_status==2UL && snapld_status==2UL && snapdc_status==2UL && snapin_status==2UL && snapwr_status==2UL ) ) break;
510 :
511 0 : long cur = fd_log_wallclock();
512 0 : if( FD_UNLIKELY( cur<next ) ) {
513 0 : long sleep_nanos = fd_long_min( 1000L*1000L, next-cur );
514 0 : FD_TEST( !fd_sys_util_nanosleep( (uint)(sleep_nanos/(1000L*1000L*1000L)), (uint)(sleep_nanos%(1000L*1000L*1000L)) ) );
515 0 : continue;
516 0 : }
517 :
518 0 : ulong total_off = snapct_metrics[ MIDX( GAUGE, SNAPCT, FULL_BYTES_READ ) ] +
519 0 : snapct_metrics[ MIDX( GAUGE, SNAPCT, INCREMENTAL_BYTES_READ ) ];
520 0 : ulong decomp_off = snapdc_metrics[ MIDX( GAUGE, SNAPDC, FULL_DECOMPRESSED_BYTES_WRITTEN ) ] +
521 0 : snapdc_metrics[ MIDX( GAUGE, SNAPDC, INCREMENTAL_DECOMPRESSED_BYTES_WRITTEN ) ];
522 : /* Waiting on either neighbor counts as not busy */
523 0 : ulong snapld_wait = snapld_metrics[ MIDX( COUNTER, TILE, REGIME_DURATION_NANOS_CAUGHT_UP_POSTFRAG ) ]
524 0 : + snapld_metrics[ MIDX( COUNTER, TILE, REGIME_DURATION_NANOS_BACKPRESSURE_PREFRAG ) ];
525 0 : ulong snapdc_wait = snapdc_metrics[ MIDX( COUNTER, TILE, REGIME_DURATION_NANOS_CAUGHT_UP_POSTFRAG ) ]
526 0 : + snapdc_metrics[ MIDX( COUNTER, TILE, REGIME_DURATION_NANOS_BACKPRESSURE_PREFRAG ) ];
527 0 : ulong snapin_wait = snapin_metrics[ MIDX( COUNTER, TILE, REGIME_DURATION_NANOS_CAUGHT_UP_POSTFRAG ) ]
528 0 : + snapin_metrics[ MIDX( COUNTER, TILE, REGIME_DURATION_NANOS_BACKPRESSURE_PREFRAG ) ];
529 0 : ulong snapwr_wait = snapwr_metrics[ MIDX( COUNTER, TILE, REGIME_DURATION_NANOS_CAUGHT_UP_POSTFRAG ) ]
530 0 : + snapwr_metrics[ MIDX( COUNTER, TILE, REGIME_DURATION_NANOS_BACKPRESSURE_PREFRAG ) ];
531 :
532 0 : char const * phase = phase_cstr( snapct_metrics[ MIDX( GAUGE, SNAPCT, STATE ) ] );
533 0 : ulong consumed, dc_in, dc_out, size_bytes;
534 0 : if( FD_UNLIKELY( !strcmp( phase, "incr" ) ) ) {
535 0 : consumed = fd_ulong_min( snapin_metrics[ MIDX( GAUGE, SNAPIN, INCREMENTAL_BYTES_READ ) ],
536 0 : snapwr_metrics[ MIDX( GAUGE, SNAPWR, INCREMENTAL_BYTES_READ ) ] );
537 0 : dc_in = snapdc_metrics[ MIDX( GAUGE, SNAPDC, INCREMENTAL_COMPRESSED_BYTES_READ ) ];
538 0 : dc_out = snapdc_metrics[ MIDX( GAUGE, SNAPDC, INCREMENTAL_DECOMPRESSED_BYTES_WRITTEN ) ];
539 0 : size_bytes = snapct_metrics[ MIDX( GAUGE, SNAPCT, INCREMENTAL_SIZE_BYTES ) ];
540 0 : } else {
541 0 : consumed = fd_ulong_min( snapin_metrics[ MIDX( GAUGE, SNAPIN, FULL_BYTES_READ ) ],
542 0 : snapwr_metrics[ MIDX( GAUGE, SNAPWR, FULL_BYTES_READ ) ] );
543 0 : dc_in = snapdc_metrics[ MIDX( GAUGE, SNAPDC, FULL_COMPRESSED_BYTES_READ ) ];
544 0 : dc_out = snapdc_metrics[ MIDX( GAUGE, SNAPDC, FULL_DECOMPRESSED_BYTES_WRITTEN ) ];
545 0 : size_bytes = snapct_metrics[ MIDX( GAUGE, SNAPCT, FULL_SIZE_BYTES ) ];
546 0 : }
547 0 : double done_comp = dc_out ? (double)dc_in*( (double)consumed/(double)dc_out ) : 0.0;
548 0 : double progress = size_bytes ? clamp_pct( 100.0*done_comp/(double)size_bytes ) : 0.0;
549 :
550 0 : ulong acc_cnt = snapin_metrics[ MIDX( GAUGE, SNAPIN, ACCOUNT_LOADED ) ];
551 :
552 0 : if( watch ) {
553 0 : double busy[ 4 ] = {
554 0 : clamp_pct( 100.0-( ( (double)( snapld_wait-snapld_wait_old )*ns_per_tick )/1e7 ) ),
555 0 : clamp_pct( 100.0-( ( (double)( snapdc_wait-snapdc_wait_old )*ns_per_tick )/1e7 ) ),
556 0 : clamp_pct( 100.0-( ( (double)( snapin_wait-snapin_wait_old )*ns_per_tick )/1e7 ) ),
557 0 : clamp_pct( 100.0-( ( (double)( snapwr_wait-snapwr_wait_old )*ns_per_tick )/1e7 ) ),
558 0 : };
559 :
560 0 : char bar[ 256 ];
561 0 : printf( " %s%4s%s %s %s%5.1f%%%s"
562 0 : " %scomp%s %5.2f %sGB/s%s"
563 0 : " %sraw%s %5.2f %sGB/s%s"
564 0 : " %sacc%s %4.1f %sM/s%s",
565 0 : c_dim, phase, c_norm,
566 0 : fmt_bar( bar, sizeof(bar), color, progress, 20UL ),
567 0 : c_bold, progress, c_norm,
568 0 : c_dim, c_norm, (double)( total_off -total_off_old )/1e9, c_dim, c_norm,
569 0 : c_dim, c_norm, (double)( decomp_off-decomp_off_old )/1e9, c_dim, c_norm,
570 0 : c_dim, c_norm, (double)( acc_cnt -acc_cnt_old )/1e6, c_dim, c_norm );
571 :
572 0 : static char const * tile_key[ 4 ] = { "ld", "dc", "in", "wr" };
573 0 : printf( " %sbusy%s", c_dim, c_norm );
574 0 : for( ulong i=0UL; i<4UL; i++ )
575 0 : printf( " %s%s%s %s%3.0f%s%%%s",
576 0 : c_dim, tile_key[ i ], c_norm,
577 0 : sev_color( color, busy[ i ] ), busy[ i ], c_dim, c_norm );
578 0 : printf( "\n" );
579 0 : fflush( stdout );
580 0 : }
581 0 : total_off_old = total_off;
582 0 : decomp_off_old = decomp_off;
583 0 : snapld_wait_old = snapld_wait;
584 0 : snapdc_wait_old = snapdc_wait;
585 0 : snapin_wait_old = snapin_wait;
586 0 : snapwr_wait_old = snapwr_wait;
587 0 : acc_cnt_old = acc_cnt;
588 :
589 0 : next+=1000L*1000L*1000L;
590 0 : }
591 :
592 0 : if( args->snapshot_load.accounts_hist ) {
593 0 : accounts_hist_t hist[1];
594 0 : accounts_hist_reset( hist );
595 0 : FD_LOG_NOTICE(( "Accounts histogram: starting" ));
596 0 : accounts_hist( hist, config );
597 0 : FD_TEST( !accounts_hist_check( hist ) );
598 0 : accounts_hist_print( hist );
599 0 : }
600 0 : }
601 :
602 : static void
603 0 : snapshot_load_args_help( fd_action_help_t * help ) {
604 0 : fd_action_help_arg( help, "--snapshot-dir", "<path>", "Load/save snapshots from this directory" );
605 0 : fd_action_help_arg( help, "--offline", NULL, "Do not attempt to download snapshots" );
606 0 : fd_action_help_arg( help, "--no-incremental", NULL, "Disable incremental snapshot loading" );
607 0 : fd_action_help_arg( help, "--no-watch", NULL, "Do not print periodic progress updates" );
608 0 : fd_action_help_arg( help, "--db-sz", "<bytes>", "Database size in bytes (e.g. 10e9 -> 10 GB)" );
609 0 : fd_action_help_arg( help, "--db-rec-max", "<num>", "Database max record/account count (e.g. 10e6 -> 10M accounts)" );
610 0 : fd_action_help_arg( help, "--fsck", NULL, "After loading, run database integrity checks" );
611 : fd_action_help_arg( help, "--accounts-hist", NULL, "After loading, analyze account size distribution" );
612 0 : }
613 :
614 : action_t fd_action_snapshot_load = {
615 : .name = NAME,
616 : .topo = snapshot_load_topo1,
617 : .perm = dev_cmd_perm,
618 : .args = snapshot_load_args,
619 : .fn = snapshot_load_cmd_fn,
620 : .description = "Load a snapshot into a database and optionally inspect it",
621 : .detail = "Boots a reduced topology that downloads (or reads from disk) a full and\n"
622 : "optional incremental snapshot, loads the accounts into a database, and\n"
623 : "can then run integrity checks or analyze the account size distribution.",
624 : .usage = NAME " [OPTIONS]",
625 : .args_help = snapshot_load_args_help,
626 : };
|