Line data Source code
1 : #include "fd_gui.h"
2 : #include "fd_gui_printf.h"
3 :
4 : #include "../metrics/fd_metrics.h"
5 : #include "../plugin/fd_plugin.h"
6 :
7 : #include "../../ballet/base58/fd_base58.h"
8 : #include "../../ballet/json/cJSON.h"
9 : #include "../../flamenco/genesis/fd_genesis_cluster.h"
10 :
11 : FD_FN_CONST ulong
12 3 : fd_gui_align( void ) {
13 3 : return 128UL;
14 3 : }
15 :
16 : FD_FN_CONST ulong
17 3 : fd_gui_footprint( void ) {
18 3 : return sizeof(fd_gui_t);
19 3 : }
20 :
21 : void *
22 : fd_gui_new( void * shmem,
23 : fd_http_server_t * http,
24 : char const * version,
25 : char const * cluster,
26 : uchar const * identity_key,
27 : int is_voting,
28 0 : fd_topo_t * topo ) {
29 :
30 0 : if( FD_UNLIKELY( !shmem ) ) {
31 0 : FD_LOG_WARNING(( "NULL shmem" ));
32 0 : return NULL;
33 0 : }
34 :
35 0 : if( FD_UNLIKELY( !fd_ulong_is_aligned( (ulong)shmem, fd_gui_align() ) ) ) {
36 0 : FD_LOG_WARNING(( "misaligned shmem" ));
37 0 : return NULL;
38 0 : }
39 :
40 0 : if( FD_UNLIKELY( topo->tile_cnt>FD_GUI_TILE_TIMER_TILE_CNT ) ) {
41 0 : FD_LOG_WARNING(( "too many tiles" ));
42 0 : return NULL;
43 0 : }
44 :
45 0 : fd_gui_t * gui = (fd_gui_t *)shmem;
46 :
47 0 : gui->http = http;
48 0 : gui->topo = topo;
49 :
50 0 : gui->debug_in_leader_slot = ULONG_MAX;
51 :
52 :
53 0 : gui->next_sample_400millis = fd_log_wallclock();
54 0 : gui->next_sample_100millis = gui->next_sample_400millis;
55 0 : gui->next_sample_10millis = gui->next_sample_400millis;
56 :
57 0 : memcpy( gui->summary.identity_key->uc, identity_key, 32UL );
58 0 : fd_base58_encode_32( identity_key, NULL, gui->summary.identity_key_base58 );
59 0 : gui->summary.identity_key_base58[ FD_BASE58_ENCODED_32_SZ-1UL ] = '\0';
60 :
61 0 : gui->summary.version = version;
62 0 : gui->summary.cluster = cluster;
63 0 : gui->summary.startup_time_nanos = gui->next_sample_400millis;
64 :
65 0 : gui->summary.startup_progress = FD_GUI_START_PROGRESS_TYPE_INITIALIZING;
66 0 : gui->summary.startup_got_full_snapshot = 0;
67 0 : gui->summary.startup_full_snapshot_slot = 0;
68 0 : gui->summary.startup_incremental_snapshot_slot = 0;
69 0 : gui->summary.startup_waiting_for_supermajority_slot = ULONG_MAX;
70 :
71 0 : gui->summary.identity_account_balance = 0UL;
72 0 : gui->summary.vote_account_balance = 0UL;
73 0 : gui->summary.estimated_slot_duration_nanos = 0UL;
74 :
75 0 : gui->summary.vote_distance = 0UL;
76 0 : gui->summary.vote_state = is_voting ? FD_GUI_VOTE_STATE_VOTING : FD_GUI_VOTE_STATE_NON_VOTING;
77 :
78 0 : gui->summary.net_tile_cnt = fd_topo_tile_name_cnt( gui->topo, "net" );
79 0 : gui->summary.quic_tile_cnt = fd_topo_tile_name_cnt( gui->topo, "quic" );
80 0 : gui->summary.verify_tile_cnt = fd_topo_tile_name_cnt( gui->topo, "verify" );
81 0 : gui->summary.resolv_tile_cnt = fd_topo_tile_name_cnt( gui->topo, "resolv" );
82 0 : gui->summary.bank_tile_cnt = fd_topo_tile_name_cnt( gui->topo, "bank" );
83 0 : gui->summary.shred_tile_cnt = fd_topo_tile_name_cnt( gui->topo, "shred" );
84 :
85 0 : gui->summary.slot_rooted = 0UL;
86 0 : gui->summary.slot_optimistically_confirmed = 0UL;
87 0 : gui->summary.slot_completed = 0UL;
88 0 : gui->summary.slot_estimated = 0UL;
89 :
90 0 : gui->summary.estimated_tps_history_idx = 0UL;
91 0 : memset( gui->summary.estimated_tps_history, 0, sizeof(gui->summary.estimated_tps_history) );
92 :
93 0 : memset( gui->summary.txn_waterfall_reference, 0, sizeof(gui->summary.txn_waterfall_reference) );
94 0 : memset( gui->summary.txn_waterfall_current, 0, sizeof(gui->summary.txn_waterfall_current) );
95 :
96 0 : memset( gui->summary.tile_stats_reference, 0, sizeof(gui->summary.tile_stats_reference) );
97 0 : memset( gui->summary.tile_stats_current, 0, sizeof(gui->summary.tile_stats_current) );
98 :
99 0 : memset( gui->summary.tile_timers_snap[ 0 ], 0, sizeof(gui->summary.tile_timers_snap[ 0 ]) );
100 0 : memset( gui->summary.tile_timers_snap[ 1 ], 0, sizeof(gui->summary.tile_timers_snap[ 1 ]) );
101 0 : gui->summary.tile_timers_snap_idx = 2UL;
102 0 : gui->summary.tile_timers_history_idx = 0UL;
103 0 : for( ulong i=0UL; i<FD_GUI_TILE_TIMER_LEADER_CNT; i++ ) gui->summary.tile_timers_leader_history_slot[ i ] = ULONG_MAX;
104 :
105 0 : gui->cus.offset = 0UL;
106 :
107 0 : gui->block_engine.has_block_engine = 0;
108 :
109 0 : gui->epoch.has_epoch[ 0 ] = 0;
110 0 : gui->epoch.has_epoch[ 1 ] = 0;
111 :
112 0 : gui->gossip.peer_cnt = 0UL;
113 0 : gui->vote_account.vote_account_cnt = 0UL;
114 0 : gui->validator_info.info_cnt = 0UL;
115 :
116 0 : for( ulong i=0UL; i<FD_GUI_SLOTS_CNT; i++ ) gui->slots[ i ]->slot = ULONG_MAX;
117 :
118 0 : return gui;
119 0 : }
120 :
121 : fd_gui_t *
122 0 : fd_gui_join( void * shmem ) {
123 0 : return (fd_gui_t *)shmem;
124 0 : }
125 :
126 : void
127 : fd_gui_set_identity( fd_gui_t * gui,
128 0 : uchar const * identity_pubkey ) {
129 0 : memcpy( gui->summary.identity_key->uc, identity_pubkey, 32UL );
130 0 : fd_base58_encode_32( identity_pubkey, NULL, gui->summary.identity_key_base58 );
131 0 : gui->summary.identity_key_base58[ FD_BASE58_ENCODED_32_SZ-1UL ] = '\0';
132 :
133 0 : fd_gui_printf_identity_key( gui );
134 0 : fd_http_server_ws_broadcast( gui->http );
135 0 : }
136 :
137 : void
138 : fd_gui_ws_open( fd_gui_t * gui,
139 0 : ulong ws_conn_id ) {
140 0 : void (* printers[] )( fd_gui_t * gui ) = {
141 0 : fd_gui_printf_startup_progress,
142 0 : fd_gui_printf_version,
143 0 : fd_gui_printf_cluster,
144 0 : fd_gui_printf_commit_hash,
145 0 : fd_gui_printf_identity_key,
146 0 : fd_gui_printf_uptime_nanos,
147 0 : fd_gui_printf_vote_state,
148 0 : fd_gui_printf_vote_distance,
149 0 : fd_gui_printf_skipped_history,
150 0 : fd_gui_printf_tps_history,
151 0 : fd_gui_printf_tiles,
152 0 : fd_gui_printf_identity_balance,
153 0 : fd_gui_printf_vote_balance,
154 0 : fd_gui_printf_estimated_slot_duration_nanos,
155 0 : fd_gui_printf_root_slot,
156 0 : fd_gui_printf_optimistically_confirmed_slot,
157 0 : fd_gui_printf_completed_slot,
158 0 : fd_gui_printf_estimated_slot,
159 0 : fd_gui_printf_live_tile_timers,
160 0 : };
161 :
162 0 : ulong printers_len = sizeof(printers) / sizeof(printers[0]);
163 0 : for( ulong i=0UL; i<printers_len; i++ ) {
164 0 : printers[ i ]( gui );
165 0 : FD_TEST( !fd_http_server_ws_send( gui->http, ws_conn_id ) );
166 0 : }
167 :
168 0 : if( FD_LIKELY( gui->block_engine.has_block_engine ) ) {
169 0 : fd_gui_printf_block_engine( gui );
170 0 : FD_TEST( !fd_http_server_ws_send( gui->http, ws_conn_id ) );
171 0 : }
172 :
173 0 : for( ulong i=0UL; i<2UL; i++ ) {
174 0 : if( FD_LIKELY( gui->epoch.has_epoch[ i ] ) ) {
175 0 : fd_gui_printf_skip_rate( gui, i );
176 0 : FD_TEST( !fd_http_server_ws_send( gui->http, ws_conn_id ) );
177 0 : fd_gui_printf_epoch( gui, i );
178 0 : FD_TEST( !fd_http_server_ws_send( gui->http, ws_conn_id ) );
179 0 : }
180 0 : }
181 :
182 : /* Print peers last because it's the largest message and would
183 : block other information. */
184 0 : fd_gui_printf_peers_all( gui );
185 0 : FD_TEST( !fd_http_server_ws_send( gui->http, ws_conn_id ) );
186 0 : }
187 :
188 : static void
189 0 : fd_gui_tile_timers_snap( fd_gui_t * gui ) {
190 0 : fd_gui_tile_timers_t * cur = gui->summary.tile_timers_snap[ gui->summary.tile_timers_snap_idx ];
191 0 : gui->summary.tile_timers_snap_idx = (gui->summary.tile_timers_snap_idx+1UL)%FD_GUI_TILE_TIMER_SNAP_CNT;
192 0 : for( ulong i=0UL; i<gui->topo->tile_cnt; i++ ) {
193 0 : fd_topo_tile_t * tile = &gui->topo->tiles[ i ];
194 0 : if ( FD_UNLIKELY( !tile->metrics ) ) {
195 : /* bench tiles might not have been booted initially.
196 : This check shouldn't be necessary if all tiles barrier after boot. */
197 : // TODO(FIXME) this probably isn't the right fix but it makes fddev bench work for now
198 0 : return;
199 0 : }
200 0 : volatile ulong const * tile_metrics = fd_metrics_tile( tile->metrics );
201 :
202 0 : cur[ i ].caughtup_housekeeping_ticks = tile_metrics[ MIDX( COUNTER, TILE, REGIME_DURATION_NANOS_CAUGHT_UP_HOUSEKEEPING ) ];
203 0 : cur[ i ].processing_housekeeping_ticks = tile_metrics[ MIDX( COUNTER, TILE, REGIME_DURATION_NANOS_PROCESSING_HOUSEKEEPING ) ];
204 0 : cur[ i ].backpressure_housekeeping_ticks = tile_metrics[ MIDX( COUNTER, TILE, REGIME_DURATION_NANOS_BACKPRESSURE_HOUSEKEEPING ) ];
205 0 : cur[ i ].caughtup_prefrag_ticks = tile_metrics[ MIDX( COUNTER, TILE, REGIME_DURATION_NANOS_CAUGHT_UP_PREFRAG ) ];
206 0 : cur[ i ].processing_prefrag_ticks = tile_metrics[ MIDX( COUNTER, TILE, REGIME_DURATION_NANOS_PROCESSING_PREFRAG ) ];
207 0 : cur[ i ].backpressure_prefrag_ticks = tile_metrics[ MIDX( COUNTER, TILE, REGIME_DURATION_NANOS_BACKPRESSURE_PREFRAG ) ];
208 0 : cur[ i ].caughtup_postfrag_ticks = tile_metrics[ MIDX( COUNTER, TILE, REGIME_DURATION_NANOS_CAUGHT_UP_POSTFRAG ) ];
209 0 : cur[ i ].processing_postfrag_ticks = tile_metrics[ MIDX( COUNTER, TILE, REGIME_DURATION_NANOS_PROCESSING_POSTFRAG ) ];
210 0 : }
211 0 : }
212 :
213 : static void
214 0 : fd_gui_estimated_tps_snap( fd_gui_t * gui ) {
215 0 : ulong total_txn_cnt = 0UL;
216 0 : ulong vote_txn_cnt = 0UL;
217 0 : ulong nonvote_failed_txn_cnt = 0UL;
218 :
219 0 : for( ulong i=0UL; i<fd_ulong_min( gui->summary.slot_completed+1UL, FD_GUI_SLOTS_CNT ); i++ ) {
220 0 : ulong _slot = gui->summary.slot_completed-i;
221 0 : fd_gui_slot_t * slot = gui->slots[ _slot % FD_GUI_SLOTS_CNT ];
222 0 : if( FD_UNLIKELY( slot->slot==ULONG_MAX || slot->slot!=_slot ) ) break; /* Slot no longer exists, no TPS. */
223 0 : if( FD_UNLIKELY( slot->completed_time==LONG_MAX ) ) continue; /* Slot is on this fork but was never completed, must have been in root path on boot. */
224 0 : if( FD_UNLIKELY( slot->completed_time+FD_GUI_TPS_HISTORY_WINDOW_DURATION_SECONDS*1000L*1000L*1000L<gui->next_sample_400millis ) ) break; /* Slot too old. */
225 0 : if( FD_UNLIKELY( slot->skipped ) ) continue; /* Skipped slots don't count to TPS. */
226 :
227 0 : total_txn_cnt += slot->total_txn_cnt;
228 0 : vote_txn_cnt += slot->vote_txn_cnt;
229 0 : nonvote_failed_txn_cnt += slot->nonvote_failed_txn_cnt;
230 0 : }
231 :
232 0 : gui->summary.estimated_tps_history[ gui->summary.estimated_tps_history_idx ][ 0 ] = total_txn_cnt;
233 0 : gui->summary.estimated_tps_history[ gui->summary.estimated_tps_history_idx ][ 1 ] = vote_txn_cnt;
234 0 : gui->summary.estimated_tps_history[ gui->summary.estimated_tps_history_idx ][ 2 ] = nonvote_failed_txn_cnt;
235 0 : gui->summary.estimated_tps_history_idx = (gui->summary.estimated_tps_history_idx+1UL) % FD_GUI_TPS_HISTORY_SAMPLE_CNT;
236 0 : }
237 :
238 : /* Snapshot all of the data from metrics to construct a view of the
239 : transaction waterfall.
240 :
241 : Tiles are sampled in reverse pipeline order: this helps prevent data
242 : discrepancies where a later tile has "seen" more transactions than an
243 : earlier tile, which shouldn't typically happen. */
244 :
245 : static void
246 : fd_gui_txn_waterfall_snap( fd_gui_t * gui,
247 0 : fd_gui_txn_waterfall_t * cur ) {
248 0 : fd_topo_t * topo = gui->topo;
249 :
250 0 : cur->out.block_success = 0UL;
251 0 : cur->out.block_fail = 0UL;
252 :
253 0 : cur->out.bank_invalid = 0UL;
254 0 : for( ulong i=0UL; i<gui->summary.bank_tile_cnt; i++ ) {
255 0 : fd_topo_tile_t const * bank = &topo->tiles[ fd_topo_find_tile( topo, "bank", i ) ];
256 :
257 0 : volatile ulong const * bank_metrics = fd_metrics_tile( bank->metrics );
258 :
259 0 : cur->out.block_success += bank_metrics[ MIDX( COUNTER, BANK, SUCCESSFUL_TRANSACTIONS ) ];
260 :
261 0 : cur->out.block_fail +=
262 0 : bank_metrics[ MIDX( COUNTER, BANK, EXECUTED_FAILED_TRANSACTIONS ) ]
263 0 : + bank_metrics[ MIDX( COUNTER, BANK, FEE_ONLY_TRANSACTIONS ) ];
264 :
265 0 : cur->out.bank_invalid +=
266 0 : bank_metrics[ MIDX( COUNTER, BANK, TRANSACTION_LOAD_ADDRESS_TABLES_SLOT_HASHES_SYSVAR_NOT_FOUND ) ]
267 0 : + bank_metrics[ MIDX( COUNTER, BANK, TRANSACTION_LOAD_ADDRESS_TABLES_ACCOUNT_NOT_FOUND ) ]
268 0 : + bank_metrics[ MIDX( COUNTER, BANK, TRANSACTION_LOAD_ADDRESS_TABLES_INVALID_ACCOUNT_OWNER ) ]
269 0 : + bank_metrics[ MIDX( COUNTER, BANK, TRANSACTION_LOAD_ADDRESS_TABLES_INVALID_ACCOUNT_DATA ) ]
270 0 : + bank_metrics[ MIDX( COUNTER, BANK, TRANSACTION_LOAD_ADDRESS_TABLES_INVALID_INDEX ) ];
271 :
272 0 : cur->out.bank_invalid +=
273 0 : bank_metrics[ MIDX( COUNTER, BANK, PROCESSING_FAILED ) ]
274 0 : + bank_metrics[ MIDX( COUNTER, BANK, PRECOMPILE_VERIFY_FAILURE ) ];
275 0 : }
276 :
277 :
278 0 : fd_topo_tile_t const * pack = &topo->tiles[ fd_topo_find_tile( topo, "pack", 0UL ) ];
279 0 : volatile ulong const * pack_metrics = fd_metrics_tile( pack->metrics );
280 :
281 0 : cur->out.pack_invalid =
282 0 : pack_metrics[ MIDX( COUNTER, PACK, TRANSACTION_DROPPED_PARTIAL_BUNDLE ) ]
283 0 : + pack_metrics[ MIDX( COUNTER, PACK, TRANSACTION_INSERTED_BUNDLE_BLACKLIST ) ]
284 0 : + pack_metrics[ MIDX( COUNTER, PACK, TRANSACTION_INSERTED_WRITE_SYSVAR ) ]
285 0 : + pack_metrics[ MIDX( COUNTER, PACK, TRANSACTION_INSERTED_ESTIMATION_FAIL ) ]
286 0 : + pack_metrics[ MIDX( COUNTER, PACK, TRANSACTION_INSERTED_DUPLICATE_ACCOUNT ) ]
287 0 : + pack_metrics[ MIDX( COUNTER, PACK, TRANSACTION_INSERTED_TOO_MANY_ACCOUNTS ) ]
288 0 : + pack_metrics[ MIDX( COUNTER, PACK, TRANSACTION_INSERTED_TOO_LARGE ) ]
289 0 : + pack_metrics[ MIDX( COUNTER, PACK, TRANSACTION_INSERTED_ADDR_LUT ) ]
290 0 : + pack_metrics[ MIDX( COUNTER, PACK, TRANSACTION_INSERTED_UNAFFORDABLE ) ]
291 0 : + pack_metrics[ MIDX( COUNTER, PACK, TRANSACTION_INSERTED_DUPLICATE ) ];
292 :
293 0 : cur->out.pack_expired = pack_metrics[ MIDX( COUNTER, PACK, TRANSACTION_INSERTED_EXPIRED ) ] +
294 0 : pack_metrics[ MIDX( COUNTER, PACK, TRANSACTION_EXPIRED ) ];
295 :
296 0 : cur->out.pack_leader_slow =
297 0 : pack_metrics[ MIDX( COUNTER, PACK, TRANSACTION_INSERTED_PRIORITY ) ]
298 0 : + pack_metrics[ MIDX( COUNTER, PACK, TRANSACTION_INSERTED_NONVOTE_REPLACE ) ]
299 0 : + pack_metrics[ MIDX( COUNTER, PACK, TRANSACTION_INSERTED_VOTE_REPLACE ) ];
300 :
301 0 : cur->out.pack_wait_full =
302 0 : pack_metrics[ MIDX( COUNTER, PACK, TRANSACTION_DROPPED_FROM_EXTRA ) ];
303 :
304 0 : cur->out.pack_retained = pack_metrics[ MIDX( GAUGE, PACK, AVAILABLE_TRANSACTIONS ) ];
305 :
306 0 : ulong inserted_to_extra = pack_metrics[ MIDX( COUNTER, PACK, TRANSACTION_INSERTED_TO_EXTRA ) ];
307 0 : ulong inserted_from_extra = pack_metrics[ MIDX( COUNTER, PACK, TRANSACTION_INSERTED_FROM_EXTRA ) ]
308 0 : + pack_metrics[ MIDX( COUNTER, PACK, TRANSACTION_DROPPED_FROM_EXTRA ) ];
309 0 : cur->out.pack_retained += fd_ulong_if( inserted_to_extra>=inserted_from_extra, inserted_to_extra-inserted_from_extra, 0UL );
310 :
311 0 : cur->out.resolv_lut_failed = 0UL;
312 0 : cur->out.resolv_expired = 0UL;
313 0 : cur->out.resolv_ancient = 0UL;
314 0 : cur->out.resolv_no_ledger = 0UL;
315 0 : cur->out.resolv_retained = 0UL;
316 0 : for( ulong i=0UL; i<gui->summary.resolv_tile_cnt; i++ ) {
317 0 : fd_topo_tile_t const * resolv = &topo->tiles[ fd_topo_find_tile( topo, "resolv", i ) ];
318 0 : volatile ulong const * resolv_metrics = fd_metrics_tile( resolv->metrics );
319 :
320 0 : cur->out.resolv_no_ledger += resolv_metrics[ MIDX( COUNTER, RESOLV, NO_BANK_DROP ) ];
321 0 : cur->out.resolv_expired += resolv_metrics[ MIDX( COUNTER, RESOLV, BLOCKHASH_EXPIRED ) ]
322 0 : + resolv_metrics[ MIDX( COUNTER, RESOLV, TRANSACTION_BUNDLE_PEER_FAILURE ) ];
323 0 : cur->out.resolv_lut_failed += resolv_metrics[ MIDX( COUNTER, RESOLV, LUT_RESOLVED_ACCOUNT_NOT_FOUND ) ]
324 0 : + resolv_metrics[ MIDX( COUNTER, RESOLV, LUT_RESOLVED_INVALID_ACCOUNT_OWNER ) ]
325 0 : + resolv_metrics[ MIDX( COUNTER, RESOLV, LUT_RESOLVED_INVALID_ACCOUNT_DATA ) ]
326 0 : + resolv_metrics[ MIDX( COUNTER, RESOLV, LUT_RESOLVED_ACCOUNT_UNINITIALIZED ) ]
327 0 : + resolv_metrics[ MIDX( COUNTER, RESOLV, LUT_RESOLVED_INVALID_LOOKUP_INDEX ) ];
328 0 : cur->out.resolv_ancient += resolv_metrics[ MIDX( COUNTER, RESOLV, STASH_OPERATION_OVERRUN ) ];
329 :
330 0 : ulong inserted_to_resolv = resolv_metrics[ MIDX( COUNTER, RESOLV, STASH_OPERATION_INSERTED ) ];
331 0 : ulong removed_from_resolv = resolv_metrics[ MIDX( COUNTER, RESOLV, STASH_OPERATION_OVERRUN ) ]
332 0 : + resolv_metrics[ MIDX( COUNTER, RESOLV, STASH_OPERATION_PUBLISHED ) ]
333 0 : + resolv_metrics[ MIDX( COUNTER, RESOLV, STASH_OPERATION_REMOVED ) ];
334 0 : cur->out.resolv_retained += fd_ulong_if( inserted_to_resolv>=removed_from_resolv, inserted_to_resolv-removed_from_resolv, 0UL );
335 0 : }
336 :
337 :
338 0 : fd_topo_tile_t const * dedup = &topo->tiles[ fd_topo_find_tile( topo, "dedup", 0UL ) ];
339 0 : volatile ulong const * dedup_metrics = fd_metrics_tile( dedup->metrics );
340 :
341 0 : cur->out.dedup_duplicate = dedup_metrics[ MIDX( COUNTER, DEDUP, TRANSACTION_DEDUP_FAILURE ) ]
342 0 : + dedup_metrics[ MIDX( COUNTER, DEDUP, TRANSACTION_BUNDLE_PEER_FAILURE ) ];
343 :
344 :
345 0 : cur->out.verify_overrun = 0UL;
346 0 : cur->out.verify_duplicate = 0UL;
347 0 : cur->out.verify_parse = 0UL;
348 0 : cur->out.verify_failed = 0UL;
349 :
350 0 : for( ulong i=0UL; i<gui->summary.verify_tile_cnt; i++ ) {
351 0 : fd_topo_tile_t const * verify = &topo->tiles[ fd_topo_find_tile( topo, "verify", i ) ];
352 0 : volatile ulong const * verify_metrics = fd_metrics_tile( verify->metrics );
353 :
354 0 : for( ulong j=0UL; j<gui->summary.quic_tile_cnt; j++ ) {
355 : /* TODO: Not precise... even if 1 frag gets skipped, it could have been for this verify tile. */
356 0 : cur->out.verify_overrun += fd_metrics_link_in( verify->metrics, j )[ FD_METRICS_COUNTER_LINK_OVERRUN_POLLING_FRAG_COUNT_OFF ] / gui->summary.verify_tile_cnt;
357 0 : cur->out.verify_overrun += fd_metrics_link_in( verify->metrics, j )[ FD_METRICS_COUNTER_LINK_OVERRUN_READING_FRAG_COUNT_OFF ];
358 0 : }
359 :
360 0 : cur->out.verify_failed += verify_metrics[ MIDX( COUNTER, VERIFY, TRANSACTION_VERIFY_FAILURE ) ] +
361 0 : verify_metrics[ MIDX( COUNTER, VERIFY, TRANSACTION_BUNDLE_PEER_FAILURE ) ];
362 0 : cur->out.verify_parse += verify_metrics[ MIDX( COUNTER, VERIFY, TRANSACTION_PARSE_FAILURE ) ];
363 0 : cur->out.verify_duplicate += verify_metrics[ MIDX( COUNTER, VERIFY, TRANSACTION_DEDUP_FAILURE ) ];
364 0 : }
365 :
366 :
367 0 : cur->out.quic_overrun = 0UL;
368 0 : cur->out.quic_frag_drop = 0UL;
369 0 : cur->out.quic_abandoned = 0UL;
370 0 : cur->out.tpu_quic_invalid = 0UL;
371 0 : cur->out.tpu_udp_invalid = 0UL;
372 0 : for( ulong i=0UL; i<gui->summary.quic_tile_cnt; i++ ) {
373 0 : fd_topo_tile_t const * quic = &topo->tiles[ fd_topo_find_tile( topo, "quic", i ) ];
374 0 : volatile ulong * quic_metrics = fd_metrics_tile( quic->metrics );
375 :
376 0 : cur->out.tpu_udp_invalid += quic_metrics[ MIDX( COUNTER, QUIC, LEGACY_TXN_UNDERSZ ) ];
377 0 : cur->out.tpu_udp_invalid += quic_metrics[ MIDX( COUNTER, QUIC, LEGACY_TXN_OVERSZ ) ];
378 0 : cur->out.tpu_quic_invalid += quic_metrics[ MIDX( COUNTER, QUIC, PKT_UNDERSZ ) ];
379 0 : cur->out.tpu_quic_invalid += quic_metrics[ MIDX( COUNTER, QUIC, PKT_OVERSZ ) ];
380 0 : cur->out.tpu_quic_invalid += quic_metrics[ MIDX( COUNTER, QUIC, TXN_OVERSZ ) ];
381 0 : cur->out.tpu_quic_invalid += quic_metrics[ MIDX( COUNTER, QUIC, PKT_CRYPTO_FAILED ) ];
382 0 : cur->out.tpu_quic_invalid += quic_metrics[ MIDX( COUNTER, QUIC, PKT_NO_CONN ) ];
383 0 : cur->out.tpu_quic_invalid += quic_metrics[ MIDX( COUNTER, QUIC, PKT_NET_HEADER_INVALID ) ];
384 0 : cur->out.tpu_quic_invalid += quic_metrics[ MIDX( COUNTER, QUIC, PKT_QUIC_HEADER_INVALID ) ];
385 0 : cur->out.quic_abandoned += quic_metrics[ MIDX( COUNTER, QUIC, TXNS_ABANDONED ) ];
386 0 : cur->out.quic_frag_drop += quic_metrics[ MIDX( COUNTER, QUIC, TXNS_OVERRUN ) ];
387 :
388 0 : for( ulong j=0UL; j<gui->summary.net_tile_cnt; j++ ) {
389 : /* TODO: Not precise... net frags that were skipped might not have been destined for QUIC tile */
390 : /* TODO: Not precise... even if 1 frag gets skipped, it could have been for this QUIC tile */
391 0 : cur->out.quic_overrun += fd_metrics_link_in( quic->metrics, j )[ FD_METRICS_COUNTER_LINK_OVERRUN_POLLING_FRAG_COUNT_OFF ] / gui->summary.quic_tile_cnt;
392 0 : cur->out.quic_overrun += fd_metrics_link_in( quic->metrics, j )[ FD_METRICS_COUNTER_LINK_OVERRUN_READING_FRAG_COUNT_OFF ];
393 0 : }
394 0 : }
395 :
396 0 : cur->out.net_overrun = 0UL;
397 0 : for( ulong i=0UL; i<gui->summary.net_tile_cnt; i++ ) {
398 0 : fd_topo_tile_t const * net = &topo->tiles[ fd_topo_find_tile( topo, "net", i ) ];
399 0 : volatile ulong * net_metrics = fd_metrics_tile( net->metrics );
400 :
401 0 : cur->out.net_overrun += net_metrics[ MIDX( COUNTER, NET, XDP_RX_RING_FULL ) ];
402 0 : cur->out.net_overrun += net_metrics[ MIDX( COUNTER, NET, XDP_RX_DROPPED_OTHER ) ];
403 0 : cur->out.net_overrun += net_metrics[ MIDX( COUNTER, NET, XDP_RX_FILL_RING_EMPTY_DESCS ) ];
404 0 : }
405 :
406 0 : ulong bundle_txns_received = 0UL;
407 0 : ulong bundle_tile_idx = fd_topo_find_tile( topo, "bundle", 0UL );
408 0 : if( FD_LIKELY( bundle_tile_idx!=ULONG_MAX ) ) {
409 0 : fd_topo_tile_t const * bundle = &topo->tiles[ bundle_tile_idx ];
410 0 : volatile ulong const * bundle_metrics = fd_metrics_tile( bundle->metrics );
411 :
412 0 : bundle_txns_received = bundle_metrics[ MIDX( COUNTER, BUNDLE, TRANSACTION_RECEIVED ) ];
413 0 : }
414 :
415 0 : cur->in.pack_cranked = pack_metrics[ MIDX( COUNTER, PACK, BUNDLE_CRANK_STATUS_INSERTED ) ];
416 0 : cur->in.gossip = dedup_metrics[ MIDX( COUNTER, DEDUP, GOSSIPED_VOTES_RECEIVED ) ];
417 0 : cur->in.quic = cur->out.tpu_quic_invalid +
418 0 : cur->out.quic_overrun +
419 0 : cur->out.quic_frag_drop +
420 0 : cur->out.quic_abandoned +
421 0 : cur->out.net_overrun;
422 0 : cur->in.udp = cur->out.tpu_udp_invalid;
423 0 : cur->in.block_engine = bundle_txns_received;
424 0 : for( ulong i=0UL; i<gui->summary.quic_tile_cnt; i++ ) {
425 0 : fd_topo_tile_t const * quic = &topo->tiles[ fd_topo_find_tile( topo, "quic", i ) ];
426 0 : volatile ulong * quic_metrics = fd_metrics_tile( quic->metrics );
427 :
428 0 : cur->in.quic += quic_metrics[ MIDX( COUNTER, QUIC, TXNS_RECEIVED_QUIC_FAST ) ];
429 0 : cur->in.quic += quic_metrics[ MIDX( COUNTER, QUIC, TXNS_RECEIVED_QUIC_FRAG ) ];
430 0 : cur->in.udp += quic_metrics[ MIDX( COUNTER, QUIC, TXNS_RECEIVED_UDP ) ];
431 0 : }
432 0 : }
433 :
434 : static void
435 : fd_gui_tile_stats_snap( fd_gui_t * gui,
436 : fd_gui_txn_waterfall_t const * waterfall,
437 0 : fd_gui_tile_stats_t * stats ) {
438 0 : fd_topo_t const * topo = gui->topo;
439 :
440 0 : stats->sample_time_nanos = fd_log_wallclock();
441 :
442 0 : stats->net_in_rx_bytes = 0UL;
443 0 : stats->net_out_tx_bytes = 0UL;
444 0 : for( ulong i=0UL; i<gui->summary.net_tile_cnt; i++ ) {
445 0 : fd_topo_tile_t const * net = &topo->tiles[ fd_topo_find_tile( topo, "net", i ) ];
446 0 : volatile ulong * net_metrics = fd_metrics_tile( net->metrics );
447 :
448 0 : stats->net_in_rx_bytes += net_metrics[ MIDX( COUNTER, NET, RX_BYTES_TOTAL ) ];
449 0 : stats->net_out_tx_bytes += net_metrics[ MIDX( COUNTER, NET, TX_BYTES_TOTAL ) ];
450 0 : }
451 :
452 0 : stats->quic_conn_cnt = 0UL;
453 0 : for( ulong i=0UL; i<gui->summary.quic_tile_cnt; i++ ) {
454 0 : fd_topo_tile_t const * quic = &topo->tiles[ fd_topo_find_tile( topo, "quic", i ) ];
455 0 : volatile ulong * quic_metrics = fd_metrics_tile( quic->metrics );
456 :
457 0 : stats->quic_conn_cnt += quic_metrics[ MIDX( GAUGE, QUIC, CONNECTIONS_ACTIVE ) ];
458 0 : }
459 :
460 0 : stats->verify_drop_cnt = waterfall->out.verify_duplicate +
461 0 : waterfall->out.verify_parse +
462 0 : waterfall->out.verify_failed;
463 0 : stats->verify_total_cnt = waterfall->in.gossip +
464 0 : waterfall->in.quic +
465 0 : waterfall->in.udp -
466 0 : waterfall->out.net_overrun -
467 0 : waterfall->out.tpu_quic_invalid -
468 0 : waterfall->out.tpu_udp_invalid -
469 0 : waterfall->out.quic_abandoned -
470 0 : waterfall->out.quic_frag_drop -
471 0 : waterfall->out.quic_overrun -
472 0 : waterfall->out.verify_overrun;
473 0 : stats->dedup_drop_cnt = waterfall->out.dedup_duplicate;
474 0 : stats->dedup_total_cnt = stats->verify_total_cnt -
475 0 : waterfall->out.verify_duplicate -
476 0 : waterfall->out.verify_parse -
477 0 : waterfall->out.verify_failed;
478 :
479 0 : fd_topo_tile_t const * pack = &topo->tiles[ fd_topo_find_tile( topo, "pack", 0UL ) ];
480 0 : volatile ulong const * pack_metrics = fd_metrics_tile( pack->metrics );
481 0 : stats->pack_buffer_cnt = pack_metrics[ MIDX( GAUGE, PACK, AVAILABLE_TRANSACTIONS ) ];
482 0 : stats->pack_buffer_capacity = pack->pack.max_pending_transactions;
483 :
484 0 : stats->bank_txn_exec_cnt = waterfall->out.block_fail + waterfall->out.block_success;
485 0 : }
486 :
487 : int
488 0 : fd_gui_poll( fd_gui_t * gui ) {
489 0 : long now = fd_log_wallclock();
490 :
491 0 : int did_work = 0;
492 :
493 0 : if( FD_LIKELY( now>gui->next_sample_400millis ) ) {
494 0 : fd_gui_estimated_tps_snap( gui );
495 0 : fd_gui_printf_estimated_tps( gui );
496 0 : fd_http_server_ws_broadcast( gui->http );
497 :
498 0 : gui->next_sample_400millis += 400L*1000L*1000L;
499 0 : did_work = 1;
500 0 : }
501 :
502 0 : if( FD_LIKELY( now>gui->next_sample_100millis ) ) {
503 0 : fd_gui_txn_waterfall_snap( gui, gui->summary.txn_waterfall_current );
504 0 : fd_gui_printf_live_txn_waterfall( gui, gui->summary.txn_waterfall_reference, gui->summary.txn_waterfall_current, 0UL /* TODO: REAL NEXT LEADER SLOT */ );
505 0 : fd_http_server_ws_broadcast( gui->http );
506 :
507 0 : memcpy( gui->summary.tile_stats_reference, gui->summary.tile_stats_current, sizeof(struct fd_gui_tile_stats) );
508 0 : fd_gui_tile_stats_snap( gui, gui->summary.txn_waterfall_current, gui->summary.tile_stats_current );
509 0 : fd_gui_printf_live_tile_stats( gui, gui->summary.tile_stats_reference, gui->summary.tile_stats_current );
510 0 : fd_http_server_ws_broadcast( gui->http );
511 :
512 0 : gui->next_sample_100millis += 100L*1000L*1000L;
513 0 : did_work = 1;
514 0 : }
515 :
516 0 : if( FD_LIKELY( now>gui->next_sample_10millis ) ) {
517 0 : fd_gui_tile_timers_snap( gui );
518 :
519 0 : fd_gui_printf_live_tile_timers( gui );
520 0 : fd_http_server_ws_broadcast( gui->http );
521 :
522 0 : gui->next_sample_10millis += 10L*1000L*1000L;
523 0 : did_work = 1;
524 0 : }
525 :
526 0 : return did_work;
527 0 : }
528 :
529 : static void
530 : fd_gui_handle_gossip_update( fd_gui_t * gui,
531 0 : uchar const * msg ) {
532 0 : ulong const * header = (ulong const *)fd_type_pun_const( msg );
533 0 : ulong peer_cnt = header[ 0 ];
534 :
535 0 : FD_TEST( peer_cnt<=40200UL );
536 :
537 0 : ulong added_cnt = 0UL;
538 0 : ulong added[ 40200 ] = {0};
539 :
540 0 : ulong update_cnt = 0UL;
541 0 : ulong updated[ 40200 ] = {0};
542 :
543 0 : ulong removed_cnt = 0UL;
544 0 : fd_pubkey_t removed[ 40200 ] = {0};
545 :
546 0 : uchar const * data = (uchar const *)(header+1UL);
547 0 : for( ulong i=0UL; i<gui->gossip.peer_cnt; i++ ) {
548 0 : int found = 0;
549 0 : for( ulong j=0UL; j<peer_cnt; j++ ) {
550 0 : if( FD_UNLIKELY( !memcmp( gui->gossip.peers[ i ].pubkey, data+j*(58UL+12UL*6UL), 32UL ) ) ) {
551 0 : found = 1;
552 0 : break;
553 0 : }
554 0 : }
555 :
556 0 : if( FD_UNLIKELY( !found ) ) {
557 0 : fd_memcpy( removed[ removed_cnt++ ].uc, gui->gossip.peers[ i ].pubkey->uc, 32UL );
558 0 : if( FD_LIKELY( i+1UL!=gui->gossip.peer_cnt ) ) {
559 0 : fd_memcpy( &gui->gossip.peers[ i ], &gui->gossip.peers[ gui->gossip.peer_cnt-1UL ], sizeof(struct fd_gui_gossip_peer) );
560 0 : gui->gossip.peer_cnt--;
561 0 : i--;
562 0 : }
563 0 : }
564 0 : }
565 :
566 0 : ulong before_peer_cnt = gui->gossip.peer_cnt;
567 0 : for( ulong i=0UL; i<peer_cnt; i++ ) {
568 0 : int found = 0;
569 0 : ulong found_idx = 0;
570 0 : for( ulong j=0UL; j<gui->gossip.peer_cnt; j++ ) {
571 0 : if( FD_UNLIKELY( !memcmp( gui->gossip.peers[ j ].pubkey, data+i*(58UL+12UL*6UL), 32UL ) ) ) {
572 0 : found_idx = j;
573 0 : found = 1;
574 0 : break;
575 0 : }
576 0 : }
577 :
578 0 : if( FD_UNLIKELY( !found ) ) {
579 0 : fd_memcpy( gui->gossip.peers[ gui->gossip.peer_cnt ].pubkey->uc, data+i*(58UL+12UL*6UL), 32UL );
580 0 : gui->gossip.peers[ gui->gossip.peer_cnt ].wallclock = *(ulong const *)(data+i*(58UL+12UL*6UL)+32UL);
581 0 : gui->gossip.peers[ gui->gossip.peer_cnt ].shred_version = *(ushort const *)(data+i*(58UL+12UL*6UL)+40UL);
582 0 : gui->gossip.peers[ gui->gossip.peer_cnt ].has_version = *(data+i*(58UL+12UL*6UL)+42UL);
583 0 : if( FD_LIKELY( gui->gossip.peers[ gui->gossip.peer_cnt ].has_version ) ) {
584 0 : gui->gossip.peers[ gui->gossip.peer_cnt ].version.major = *(ushort const *)(data+i*(58UL+12UL*6UL)+43UL);
585 0 : gui->gossip.peers[ gui->gossip.peer_cnt ].version.minor = *(ushort const *)(data+i*(58UL+12UL*6UL)+45UL);
586 0 : gui->gossip.peers[ gui->gossip.peer_cnt ].version.patch = *(ushort const *)(data+i*(58UL+12UL*6UL)+47UL);
587 0 : gui->gossip.peers[ gui->gossip.peer_cnt ].version.has_commit = *(data+i*(58UL+12UL*6UL)+49UL);
588 0 : if( FD_LIKELY( gui->gossip.peers[ gui->gossip.peer_cnt ].version.has_commit ) ) {
589 0 : gui->gossip.peers[ gui->gossip.peer_cnt ].version.commit = *(uint const *)(data+i*(58UL+12UL*6UL)+50UL);
590 0 : }
591 0 : gui->gossip.peers[ gui->gossip.peer_cnt ].version.feature_set = *(uint const *)(data+i*(58UL+12UL*6UL)+54UL);
592 0 : }
593 :
594 0 : for( ulong j=0UL; j<12UL; j++ ) {
595 0 : gui->gossip.peers[ gui->gossip.peer_cnt ].sockets[ j ].ipv4 = *(uint const *)(data+i*(58UL+12UL*6UL)+58UL+j*6UL);
596 0 : gui->gossip.peers[ gui->gossip.peer_cnt ].sockets[ j ].port = *(ushort const *)(data+i*(58UL+12UL*6UL)+58UL+j*6UL+4UL);
597 0 : }
598 :
599 0 : gui->gossip.peer_cnt++;
600 0 : } else {
601 0 : int peer_updated = gui->gossip.peers[ found_idx ].shred_version!=*(ushort const *)(data+i*(58UL+12UL*6UL)+40UL) ||
602 : // gui->gossip.peers[ found_idx ].wallclock!=*(ulong const *)(data+i*(58UL+12UL*6UL)+32UL) ||
603 0 : gui->gossip.peers[ found_idx ].has_version!=*(data+i*(58UL+12UL*6UL)+42UL);
604 :
605 0 : if( FD_LIKELY( !peer_updated && gui->gossip.peers[ found_idx ].has_version ) ) {
606 0 : peer_updated = gui->gossip.peers[ found_idx ].version.major!=*(ushort const *)(data+i*(58UL+12UL*6UL)+43UL) ||
607 0 : gui->gossip.peers[ found_idx ].version.minor!=*(ushort const *)(data+i*(58UL+12UL*6UL)+45UL) ||
608 0 : gui->gossip.peers[ found_idx ].version.patch!=*(ushort const *)(data+i*(58UL+12UL*6UL)+47UL) ||
609 0 : gui->gossip.peers[ found_idx ].version.has_commit!=*(data+i*(58UL+12UL*6UL)+49UL) ||
610 0 : (gui->gossip.peers[ found_idx ].version.has_commit && gui->gossip.peers[ found_idx ].version.commit!=*(uint const *)(data+i*(58UL+12UL*6UL)+50UL)) ||
611 0 : gui->gossip.peers[ found_idx ].version.feature_set!=*(uint const *)(data+i*(58UL+12UL*6UL)+54UL);
612 0 : }
613 :
614 0 : if( FD_LIKELY( !peer_updated ) ) {
615 0 : for( ulong j=0UL; j<12UL; j++ ) {
616 0 : peer_updated = gui->gossip.peers[ found_idx ].sockets[ j ].ipv4!=*(uint const *)(data+i*(58UL+12UL*6UL)+58UL+j*6UL) ||
617 0 : gui->gossip.peers[ found_idx ].sockets[ j ].port!=*(ushort const *)(data+i*(58UL+12UL*6UL)+58UL+j*6UL+4UL);
618 0 : if( FD_LIKELY( peer_updated ) ) break;
619 0 : }
620 0 : }
621 :
622 0 : if( FD_UNLIKELY( peer_updated ) ) {
623 0 : updated[ update_cnt++ ] = found_idx;
624 0 : gui->gossip.peers[ found_idx ].shred_version = *(ushort const *)(data+i*(58UL+12UL*6UL)+40UL);
625 0 : gui->gossip.peers[ found_idx ].wallclock = *(ulong const *)(data+i*(58UL+12UL*6UL)+32UL);
626 0 : gui->gossip.peers[ found_idx ].has_version = *(data+i*(58UL+12UL*6UL)+42UL);
627 0 : if( FD_LIKELY( gui->gossip.peers[ found_idx ].has_version ) ) {
628 0 : gui->gossip.peers[ found_idx ].version.major = *(ushort const *)(data+i*(58UL+12UL*6UL)+43UL);
629 0 : gui->gossip.peers[ found_idx ].version.minor = *(ushort const *)(data+i*(58UL+12UL*6UL)+45UL);
630 0 : gui->gossip.peers[ found_idx ].version.patch = *(ushort const *)(data+i*(58UL+12UL*6UL)+47UL);
631 0 : gui->gossip.peers[ found_idx ].version.has_commit = *(data+i*(58UL+12UL*6UL)+49UL);
632 0 : if( FD_LIKELY( gui->gossip.peers[ found_idx ].version.has_commit ) ) {
633 0 : gui->gossip.peers[ found_idx ].version.commit = *(uint const *)(data+i*(58UL+12UL*6UL)+50UL);
634 0 : }
635 0 : gui->gossip.peers[ found_idx ].version.feature_set = *(uint const *)(data+i*(58UL+12UL*6UL)+54UL);
636 0 : }
637 :
638 0 : for( ulong j=0UL; j<12UL; j++ ) {
639 0 : gui->gossip.peers[ found_idx ].sockets[ j ].ipv4 = *(uint const *)(data+i*(58UL+12UL*6UL)+58UL+j*6UL);
640 0 : gui->gossip.peers[ found_idx ].sockets[ j ].port = *(ushort const *)(data+i*(58UL+12UL*6UL)+58UL+j*6UL+4UL);
641 0 : }
642 0 : }
643 0 : }
644 0 : }
645 :
646 0 : added_cnt = gui->gossip.peer_cnt - before_peer_cnt;
647 0 : for( ulong i=before_peer_cnt; i<gui->gossip.peer_cnt; i++ ) added[ i-before_peer_cnt ] = i;
648 :
649 0 : fd_gui_printf_peers_gossip_update( gui, updated, update_cnt, removed, removed_cnt, added, added_cnt );
650 0 : fd_http_server_ws_broadcast( gui->http );
651 0 : }
652 :
653 : static void
654 : fd_gui_handle_vote_account_update( fd_gui_t * gui,
655 0 : uchar const * msg ) {
656 0 : ulong const * header = (ulong const *)fd_type_pun_const( msg );
657 0 : ulong peer_cnt = header[ 0 ];
658 :
659 0 : FD_TEST( peer_cnt<=40200UL );
660 :
661 0 : ulong added_cnt = 0UL;
662 0 : ulong added[ 40200 ] = {0};
663 :
664 0 : ulong update_cnt = 0UL;
665 0 : ulong updated[ 40200 ] = {0};
666 :
667 0 : ulong removed_cnt = 0UL;
668 0 : fd_pubkey_t removed[ 40200 ] = {0};
669 :
670 0 : uchar const * data = (uchar const *)(header+1UL);
671 0 : for( ulong i=0UL; i<gui->vote_account.vote_account_cnt; i++ ) {
672 0 : int found = 0;
673 0 : for( ulong j=0UL; j<peer_cnt; j++ ) {
674 0 : if( FD_UNLIKELY( !memcmp( gui->vote_account.vote_accounts[ i ].vote_account, data+j*112UL, 32UL ) ) ) {
675 0 : found = 1;
676 0 : break;
677 0 : }
678 0 : }
679 :
680 0 : if( FD_UNLIKELY( !found ) ) {
681 0 : fd_memcpy( removed[ removed_cnt++ ].uc, gui->vote_account.vote_accounts[ i ].vote_account->uc, 32UL );
682 0 : if( FD_LIKELY( i+1UL!=gui->vote_account.vote_account_cnt ) ) {
683 0 : fd_memcpy( &gui->vote_account.vote_accounts[ i ], &gui->vote_account.vote_accounts[ gui->vote_account.vote_account_cnt-1UL ], sizeof(struct fd_gui_vote_account) );
684 0 : gui->vote_account.vote_account_cnt--;
685 0 : i--;
686 0 : }
687 0 : }
688 0 : }
689 :
690 0 : ulong before_peer_cnt = gui->vote_account.vote_account_cnt;
691 0 : for( ulong i=0UL; i<peer_cnt; i++ ) {
692 0 : int found = 0;
693 0 : ulong found_idx;
694 0 : for( ulong j=0UL; j<gui->vote_account.vote_account_cnt; j++ ) {
695 0 : if( FD_UNLIKELY( !memcmp( gui->vote_account.vote_accounts[ j ].vote_account, data+i*112UL, 32UL ) ) ) {
696 0 : found_idx = j;
697 0 : found = 1;
698 0 : break;
699 0 : }
700 0 : }
701 :
702 0 : if( FD_UNLIKELY( !found ) ) {
703 0 : fd_memcpy( gui->vote_account.vote_accounts[ gui->vote_account.vote_account_cnt ].vote_account->uc, data+i*112UL, 32UL );
704 0 : fd_memcpy( gui->vote_account.vote_accounts[ gui->vote_account.vote_account_cnt ].pubkey->uc, data+i*112UL+32UL, 32UL );
705 :
706 0 : gui->vote_account.vote_accounts[ gui->vote_account.vote_account_cnt ].activated_stake = *(ulong const *)(data+i*112UL+64UL);
707 0 : gui->vote_account.vote_accounts[ gui->vote_account.vote_account_cnt ].last_vote = *(ulong const *)(data+i*112UL+72UL);
708 0 : gui->vote_account.vote_accounts[ gui->vote_account.vote_account_cnt ].root_slot = *(ulong const *)(data+i*112UL+80UL);
709 0 : gui->vote_account.vote_accounts[ gui->vote_account.vote_account_cnt ].epoch_credits = *(ulong const *)(data+i*112UL+88UL);
710 0 : gui->vote_account.vote_accounts[ gui->vote_account.vote_account_cnt ].commission = *(data+i*112UL+96UL);
711 0 : gui->vote_account.vote_accounts[ gui->vote_account.vote_account_cnt ].delinquent = *(data+i*112UL+97UL);
712 :
713 0 : gui->vote_account.vote_account_cnt++;
714 0 : } else {
715 0 : int peer_updated =
716 0 : memcmp( gui->vote_account.vote_accounts[ found_idx ].pubkey->uc, data+i*112UL+32UL, 32UL ) ||
717 0 : gui->vote_account.vote_accounts[ found_idx ].activated_stake != *(ulong const *)(data+i*112UL+64UL) ||
718 : // gui->vote_account.vote_accounts[ found_idx ].last_vote != *(ulong const *)(data+i*112UL+72UL) ||
719 : // gui->vote_account.vote_accounts[ found_idx ].root_slot != *(ulong const *)(data+i*112UL+80UL) ||
720 : // gui->vote_account.vote_accounts[ found_idx ].epoch_credits != *(ulong const *)(data+i*112UL+88UL) ||
721 0 : gui->vote_account.vote_accounts[ found_idx ].commission != *(data+i*112UL+96UL) ||
722 0 : gui->vote_account.vote_accounts[ found_idx ].delinquent != *(data+i*112UL+97UL);
723 :
724 0 : if( FD_UNLIKELY( peer_updated ) ) {
725 0 : updated[ update_cnt++ ] = found_idx;
726 :
727 0 : fd_memcpy( gui->vote_account.vote_accounts[ found_idx ].pubkey->uc, data+i*112UL+32UL, 32UL );
728 0 : gui->vote_account.vote_accounts[ found_idx ].activated_stake = *(ulong const *)(data+i*112UL+64UL);
729 0 : gui->vote_account.vote_accounts[ found_idx ].last_vote = *(ulong const *)(data+i*112UL+72UL);
730 0 : gui->vote_account.vote_accounts[ found_idx ].root_slot = *(ulong const *)(data+i*112UL+80UL);
731 0 : gui->vote_account.vote_accounts[ found_idx ].epoch_credits = *(ulong const *)(data+i*112UL+88UL);
732 0 : gui->vote_account.vote_accounts[ found_idx ].commission = *(data+i*112UL+96UL);
733 0 : gui->vote_account.vote_accounts[ found_idx ].delinquent = *(data+i*112UL+97UL);
734 0 : }
735 0 : }
736 0 : }
737 :
738 0 : added_cnt = gui->vote_account.vote_account_cnt - before_peer_cnt;
739 0 : for( ulong i=before_peer_cnt; i<gui->vote_account.vote_account_cnt; i++ ) added[ i-before_peer_cnt ] = i;
740 :
741 0 : fd_gui_printf_peers_vote_account_update( gui, updated, update_cnt, removed, removed_cnt, added, added_cnt );
742 0 : fd_http_server_ws_broadcast( gui->http );
743 0 : }
744 :
745 : static void
746 : fd_gui_handle_validator_info_update( fd_gui_t * gui,
747 0 : uchar const * msg ) {
748 0 : uchar const * data = (uchar const *)fd_type_pun_const( msg );
749 :
750 0 : ulong added_cnt = 0UL;
751 0 : ulong added[ 1 ] = {0};
752 :
753 0 : ulong update_cnt = 0UL;
754 0 : ulong updated[ 1 ] = {0};
755 :
756 0 : ulong removed_cnt = 0UL;
757 : /* Unlike gossip or vote account updates, validator info messages come
758 : in as info is disovered, and may contain as little as 1 validator
759 : per message. Therefore it doesn't make sense to use the remove
760 : mechanism. */
761 :
762 0 : ulong before_peer_cnt = gui->validator_info.info_cnt;
763 0 : int found = 0;
764 0 : ulong found_idx;
765 0 : for( ulong j=0UL; j<gui->validator_info.info_cnt; j++ ) {
766 0 : if( FD_UNLIKELY( !memcmp( gui->validator_info.info[ j ].pubkey, data, 32UL ) ) ) {
767 0 : found_idx = j;
768 0 : found = 1;
769 0 : break;
770 0 : }
771 0 : }
772 :
773 0 : if( FD_UNLIKELY( !found ) ) {
774 0 : fd_memcpy( gui->validator_info.info[ gui->validator_info.info_cnt ].pubkey->uc, data, 32UL );
775 :
776 0 : strncpy( gui->validator_info.info[ gui->validator_info.info_cnt ].name, (char const *)(data+32UL), 64 );
777 0 : gui->validator_info.info[ gui->validator_info.info_cnt ].name[ 63 ] = '\0';
778 :
779 0 : strncpy( gui->validator_info.info[ gui->validator_info.info_cnt ].website, (char const *)(data+96UL), 128 );
780 0 : gui->validator_info.info[ gui->validator_info.info_cnt ].website[ 127 ] = '\0';
781 :
782 0 : strncpy( gui->validator_info.info[ gui->validator_info.info_cnt ].details, (char const *)(data+224UL), 256 );
783 0 : gui->validator_info.info[ gui->validator_info.info_cnt ].details[ 255 ] = '\0';
784 :
785 0 : strncpy( gui->validator_info.info[ gui->validator_info.info_cnt ].icon_uri, (char const *)(data+480UL), 128 );
786 0 : gui->validator_info.info[ gui->validator_info.info_cnt ].icon_uri[ 127 ] = '\0';
787 :
788 0 : gui->validator_info.info_cnt++;
789 0 : } else {
790 0 : int peer_updated =
791 0 : memcmp( gui->validator_info.info[ found_idx ].pubkey->uc, data, 32UL ) ||
792 0 : strncmp( gui->validator_info.info[ found_idx ].name, (char const *)(data+32UL), 64 ) ||
793 0 : strncmp( gui->validator_info.info[ found_idx ].website, (char const *)(data+96UL), 128 ) ||
794 0 : strncmp( gui->validator_info.info[ found_idx ].details, (char const *)(data+224UL), 256 ) ||
795 0 : strncmp( gui->validator_info.info[ found_idx ].icon_uri, (char const *)(data+480UL), 128 );
796 :
797 0 : if( FD_UNLIKELY( peer_updated ) ) {
798 0 : updated[ update_cnt++ ] = found_idx;
799 :
800 0 : fd_memcpy( gui->validator_info.info[ found_idx ].pubkey->uc, data, 32UL );
801 :
802 0 : strncpy( gui->validator_info.info[ found_idx ].name, (char const *)(data+32UL), 64 );
803 0 : gui->validator_info.info[ found_idx ].name[ 63 ] = '\0';
804 :
805 0 : strncpy( gui->validator_info.info[ found_idx ].website, (char const *)(data+96UL), 128 );
806 0 : gui->validator_info.info[ found_idx ].website[ 127 ] = '\0';
807 :
808 0 : strncpy( gui->validator_info.info[ found_idx ].details, (char const *)(data+224UL), 256 );
809 0 : gui->validator_info.info[ found_idx ].details[ 255 ] = '\0';
810 :
811 0 : strncpy( gui->validator_info.info[ found_idx ].icon_uri, (char const *)(data+480UL), 128 );
812 0 : gui->validator_info.info[ found_idx ].icon_uri[ 127 ] = '\0';
813 0 : }
814 0 : }
815 :
816 0 : added_cnt = gui->validator_info.info_cnt - before_peer_cnt;
817 0 : for( ulong i=before_peer_cnt; i<gui->validator_info.info_cnt; i++ ) added[ i-before_peer_cnt ] = i;
818 :
819 0 : fd_gui_printf_peers_validator_info_update( gui, updated, update_cnt, NULL, removed_cnt, added, added_cnt );
820 0 : fd_http_server_ws_broadcast( gui->http );
821 0 : }
822 :
823 : int
824 : fd_gui_request_slot( fd_gui_t * gui,
825 : ulong ws_conn_id,
826 : ulong request_id,
827 0 : cJSON const * params ) {
828 0 : const cJSON * slot_param = cJSON_GetObjectItemCaseSensitive( params, "slot" );
829 0 : if( FD_UNLIKELY( !cJSON_IsNumber( slot_param ) ) ) return FD_HTTP_SERVER_CONNECTION_CLOSE_BAD_REQUEST;
830 :
831 0 : ulong _slot = slot_param->valueulong;
832 0 : fd_gui_slot_t const * slot = gui->slots[ _slot % FD_GUI_SLOTS_CNT ];
833 0 : if( FD_UNLIKELY( slot->slot!=_slot || slot->slot==ULONG_MAX ) ) {
834 0 : fd_gui_printf_null_query_response( gui, "slot", "query", request_id );
835 0 : FD_TEST( !fd_http_server_ws_send( gui->http, ws_conn_id ) );
836 0 : return 0;
837 0 : }
838 :
839 0 : fd_gui_printf_slot_request( gui, _slot, request_id );
840 0 : FD_TEST( !fd_http_server_ws_send( gui->http, ws_conn_id ) );
841 0 : return 0;
842 0 : }
843 :
844 : int
845 : fd_gui_request_slot_detailed( fd_gui_t * gui,
846 : ulong ws_conn_id,
847 : ulong request_id,
848 0 : cJSON const * params ) {
849 0 : const cJSON * slot_param = cJSON_GetObjectItemCaseSensitive( params, "slot" );
850 0 : if( FD_UNLIKELY( !cJSON_IsNumber( slot_param ) ) ) return FD_HTTP_SERVER_CONNECTION_CLOSE_BAD_REQUEST;
851 :
852 0 : ulong _slot = slot_param->valueulong;
853 0 : fd_gui_slot_t const * slot = gui->slots[ _slot % FD_GUI_SLOTS_CNT ];
854 0 : if( FD_UNLIKELY( slot->slot!=_slot || slot->slot==ULONG_MAX ) ) {
855 0 : fd_gui_printf_null_query_response( gui, "slot", "query", request_id );
856 0 : FD_TEST( !fd_http_server_ws_send( gui->http, ws_conn_id ) );
857 0 : return 0;
858 0 : }
859 :
860 0 : fd_gui_printf_slot_request_detailed( gui, _slot, request_id );
861 0 : FD_TEST( !fd_http_server_ws_send( gui->http, ws_conn_id ) );
862 0 : return 0;
863 0 : }
864 :
865 : int
866 : fd_gui_ws_message( fd_gui_t * gui,
867 : ulong ws_conn_id,
868 : uchar const * data,
869 0 : ulong data_len ) {
870 : /* TODO: cJSON allocates, might fail SIGSYS due to brk(2)...
871 : switch off this (or use wksp allocator) */
872 0 : const char * parse_end;
873 0 : cJSON * json = cJSON_ParseWithLengthOpts( (char *)data, data_len, &parse_end, 0 );
874 0 : if( FD_UNLIKELY( !json ) ) {
875 0 : return FD_HTTP_SERVER_CONNECTION_CLOSE_BAD_REQUEST;
876 0 : }
877 :
878 0 : const cJSON * node = cJSON_GetObjectItemCaseSensitive( json, "id" );
879 0 : if( FD_UNLIKELY( !cJSON_IsNumber( node ) ) ) {
880 0 : cJSON_Delete( json );
881 0 : return FD_HTTP_SERVER_CONNECTION_CLOSE_BAD_REQUEST;
882 0 : }
883 0 : ulong id = node->valueulong;
884 :
885 0 : const cJSON * topic = cJSON_GetObjectItemCaseSensitive( json, "topic" );
886 0 : if( FD_UNLIKELY( !cJSON_IsString( topic ) || topic->valuestring==NULL ) ) {
887 0 : cJSON_Delete( json );
888 0 : return FD_HTTP_SERVER_CONNECTION_CLOSE_BAD_REQUEST;
889 0 : }
890 :
891 0 : const cJSON * key = cJSON_GetObjectItemCaseSensitive( json, "key" );
892 0 : if( FD_UNLIKELY( !cJSON_IsString( key ) || key->valuestring==NULL ) ) {
893 0 : cJSON_Delete( json );
894 0 : return FD_HTTP_SERVER_CONNECTION_CLOSE_BAD_REQUEST;
895 0 : }
896 :
897 0 : if( FD_LIKELY( !strcmp( topic->valuestring, "slot" ) && !strcmp( key->valuestring, "query" ) ) ) {
898 0 : const cJSON * params = cJSON_GetObjectItemCaseSensitive( json, "params" );
899 0 : if( FD_UNLIKELY( !cJSON_IsObject( params ) ) ) {
900 0 : cJSON_Delete( json );
901 0 : return FD_HTTP_SERVER_CONNECTION_CLOSE_BAD_REQUEST;
902 0 : }
903 :
904 0 : int result = fd_gui_request_slot( gui, ws_conn_id, id, params );
905 0 : cJSON_Delete( json );
906 0 : return result;
907 0 : } else if( FD_LIKELY( !strcmp( topic->valuestring, "slot" ) && !strcmp( key->valuestring, "query_detailed" ) ) ) {
908 0 : const cJSON * params = cJSON_GetObjectItemCaseSensitive( json, "params" );
909 0 : if( FD_UNLIKELY( !cJSON_IsObject( params ) ) ) {
910 0 : cJSON_Delete( json );
911 0 : return FD_HTTP_SERVER_CONNECTION_CLOSE_BAD_REQUEST;
912 0 : }
913 :
914 0 : int result = fd_gui_request_slot_detailed( gui, ws_conn_id, id, params );
915 0 : cJSON_Delete( json );
916 0 : return result;
917 0 : } else if( FD_LIKELY( !strcmp( topic->valuestring, "summary" ) && !strcmp( key->valuestring, "ping" ) ) ) {
918 0 : fd_gui_printf_summary_ping( gui, id );
919 0 : FD_TEST( !fd_http_server_ws_send( gui->http, ws_conn_id ) );
920 :
921 0 : cJSON_Delete( json );
922 0 : return 0;
923 0 : }
924 :
925 0 : cJSON_Delete( json );
926 0 : return FD_HTTP_SERVER_CONNECTION_CLOSE_UNKNOWN_METHOD;
927 0 : }
928 :
929 : static void
930 : fd_gui_clear_slot( fd_gui_t * gui,
931 : ulong _slot,
932 0 : ulong _parent_slot ) {
933 0 : fd_gui_slot_t * slot = gui->slots[ _slot % FD_GUI_SLOTS_CNT ];
934 :
935 0 : int mine = 0;
936 0 : ulong epoch_idx = 0UL;
937 0 : for( ulong i=0UL; i<2UL; i++) {
938 0 : if( FD_UNLIKELY( !gui->epoch.has_epoch[ i ] ) ) continue;
939 0 : if( FD_LIKELY( _slot>=gui->epoch.epochs[ i ].start_slot && _slot<=gui->epoch.epochs[ i ].end_slot ) ) {
940 0 : fd_pubkey_t const * slot_leader = fd_epoch_leaders_get( gui->epoch.epochs[ i ].lsched, _slot );
941 0 : mine = !memcmp( slot_leader->uc, gui->summary.identity_key->uc, 32UL );
942 0 : epoch_idx = i;
943 0 : break;
944 0 : }
945 0 : }
946 :
947 0 : slot->slot = _slot;
948 0 : slot->parent_slot = _parent_slot;
949 0 : slot->mine = mine;
950 0 : slot->skipped = 0;
951 0 : slot->must_republish = 1;
952 0 : slot->level = FD_GUI_SLOT_LEVEL_INCOMPLETE;
953 0 : slot->total_txn_cnt = UINT_MAX;
954 0 : slot->vote_txn_cnt = UINT_MAX;
955 0 : slot->failed_txn_cnt = UINT_MAX;
956 0 : slot->nonvote_failed_txn_cnt = UINT_MAX;
957 0 : slot->compute_units = UINT_MAX;
958 0 : slot->transaction_fee = ULONG_MAX;
959 0 : slot->priority_fee = ULONG_MAX;
960 0 : slot->tips = ULONG_MAX;
961 0 : slot->leader_state = FD_GUI_SLOT_LEADER_UNSTARTED;
962 0 : slot->completed_time = LONG_MAX;
963 :
964 0 : slot->cus.leader_start_time = LONG_MAX;
965 0 : slot->cus.leader_end_time = LONG_MAX;
966 0 : slot->cus.max_compute_units = UINT_MAX;
967 0 : slot->cus.microblocks_upper_bound = USHORT_MAX;
968 0 : slot->cus.begin_microblocks = 0U;
969 0 : slot->cus.end_microblocks = 0U;
970 0 : slot->cus.reference_ticks = LONG_MAX;
971 0 : slot->cus.reference_nanos = LONG_MAX;
972 0 : for( ulong i=0UL; i<65UL; i++ ) slot->cus.has_offset[ i ] = 0;
973 :
974 0 : if( FD_LIKELY( slot->mine ) ) {
975 : /* All slots start off not skipped, until we see it get off the reset
976 : chain. */
977 0 : gui->epoch.epochs[ epoch_idx ].my_total_slots++;
978 0 : }
979 :
980 0 : if( FD_UNLIKELY( !_slot ) ) {
981 : /* Slot 0 is always rooted */
982 0 : slot->level = FD_GUI_SLOT_LEVEL_ROOTED;
983 0 : }
984 0 : }
985 :
986 : static void
987 : fd_gui_handle_leader_schedule( fd_gui_t * gui,
988 0 : ulong const * msg ) {
989 0 : ulong epoch = msg[ 0 ];
990 0 : ulong staked_cnt = msg[ 1 ];
991 0 : ulong start_slot = msg[ 2 ];
992 0 : ulong slot_cnt = msg[ 3 ];
993 0 : ulong excluded_stake = msg[ 4 ];
994 :
995 0 : FD_TEST( staked_cnt<=50000UL );
996 0 : FD_TEST( slot_cnt<=432000UL );
997 :
998 0 : ulong idx = epoch % 2UL;
999 0 : gui->epoch.has_epoch[ idx ] = 1;
1000 :
1001 :
1002 0 : gui->epoch.epochs[ idx ].epoch = epoch;
1003 0 : gui->epoch.epochs[ idx ].start_slot = start_slot;
1004 0 : gui->epoch.epochs[ idx ].end_slot = start_slot + slot_cnt - 1; // end_slot is inclusive.
1005 0 : gui->epoch.epochs[ idx ].excluded_stake = excluded_stake;
1006 0 : gui->epoch.epochs[ idx ].my_total_slots = 0UL;
1007 0 : gui->epoch.epochs[ idx ].my_skipped_slots = 0UL;
1008 0 : fd_epoch_leaders_delete( fd_epoch_leaders_leave( gui->epoch.epochs[ idx ].lsched ) );
1009 0 : gui->epoch.epochs[idx].lsched = fd_epoch_leaders_join( fd_epoch_leaders_new( gui->epoch.epochs[ idx ]._lsched,
1010 0 : epoch,
1011 0 : gui->epoch.epochs[ idx ].start_slot,
1012 0 : slot_cnt,
1013 0 : staked_cnt,
1014 0 : fd_type_pun_const( msg+5UL ),
1015 0 : excluded_stake ) );
1016 0 : fd_memcpy( gui->epoch.epochs[ idx ].stakes, fd_type_pun_const( msg+5UL ), staked_cnt*sizeof(gui->epoch.epochs[ idx ].stakes[ 0 ]) );
1017 :
1018 0 : if( FD_UNLIKELY( start_slot==0UL ) ) {
1019 0 : gui->epoch.epochs[ 0 ].start_time = fd_log_wallclock();
1020 0 : } else {
1021 0 : gui->epoch.epochs[ idx ].start_time = LONG_MAX;
1022 :
1023 0 : for( ulong i=0UL; i<fd_ulong_min( start_slot-1UL, FD_GUI_SLOTS_CNT ); i++ ) {
1024 0 : fd_gui_slot_t * slot = gui->slots[ (start_slot-i) % FD_GUI_SLOTS_CNT ];
1025 0 : if( FD_UNLIKELY( slot->slot!=(start_slot-i) ) ) break;
1026 0 : else if( FD_UNLIKELY( slot->skipped ) ) continue;
1027 :
1028 0 : gui->epoch.epochs[ idx ].start_time = slot->completed_time;
1029 0 : break;
1030 0 : }
1031 0 : }
1032 :
1033 0 : fd_gui_printf_epoch( gui, idx );
1034 0 : fd_http_server_ws_broadcast( gui->http );
1035 0 : }
1036 :
1037 : static void
1038 : fd_gui_handle_slot_start( fd_gui_t * gui,
1039 0 : ulong * msg ) {
1040 0 : ulong _slot = msg[ 0 ];
1041 0 : ulong _parent_slot = msg[ 1 ];
1042 : // FD_LOG_WARNING(( "Got start slot %lu parent_slot %lu", _slot, _parent_slot ));
1043 0 : FD_TEST( gui->debug_in_leader_slot==ULONG_MAX );
1044 0 : gui->debug_in_leader_slot = _slot;
1045 :
1046 0 : fd_gui_slot_t * slot = gui->slots[ _slot % FD_GUI_SLOTS_CNT ];
1047 :
1048 0 : if( FD_UNLIKELY( slot->slot!=_slot ) ) fd_gui_clear_slot( gui, _slot, _parent_slot );
1049 0 : slot->leader_state = FD_GUI_SLOT_LEADER_STARTED;
1050 :
1051 0 : fd_gui_tile_timers_snap( gui );
1052 0 : gui->summary.tile_timers_snap_idx_slot_start = (gui->summary.tile_timers_snap_idx+(FD_GUI_TILE_TIMER_SNAP_CNT-1UL))%FD_GUI_TILE_TIMER_SNAP_CNT;
1053 :
1054 0 : fd_gui_txn_waterfall_t waterfall[ 1 ];
1055 0 : fd_gui_txn_waterfall_snap( gui, waterfall );
1056 0 : fd_gui_tile_stats_snap( gui, waterfall, slot->tile_stats_begin );
1057 0 : }
1058 :
1059 : static void
1060 : fd_gui_handle_slot_end( fd_gui_t * gui,
1061 0 : ulong * msg ) {
1062 0 : ulong _slot = msg[ 0 ];
1063 0 : ulong _cus_used = msg[ 1 ];
1064 0 : if( FD_UNLIKELY( gui->debug_in_leader_slot!=_slot ) ) {
1065 0 : FD_LOG_ERR(( "gui->debug_in_leader_slot %lu _slot %lu", gui->debug_in_leader_slot, _slot ));
1066 0 : }
1067 0 : gui->debug_in_leader_slot = ULONG_MAX;
1068 :
1069 0 : fd_gui_slot_t * slot = gui->slots[ _slot % FD_GUI_SLOTS_CNT ];
1070 0 : FD_TEST( slot->slot==_slot );
1071 :
1072 0 : slot->leader_state = FD_GUI_SLOT_LEADER_ENDED;
1073 0 : slot->compute_units = (uint)_cus_used;
1074 :
1075 0 : fd_gui_tile_timers_snap( gui );
1076 : /* Record slot number so we can detect overwrite. */
1077 0 : gui->summary.tile_timers_leader_history_slot[ gui->summary.tile_timers_history_idx ] = _slot;
1078 : /* Point into per-leader-slot storage. */
1079 0 : slot->tile_timers_history_idx = gui->summary.tile_timers_history_idx;
1080 : /* Downsample tile timers into per-leader-slot storage. */
1081 0 : ulong end = gui->summary.tile_timers_snap_idx;
1082 0 : end = fd_ulong_if( end<gui->summary.tile_timers_snap_idx_slot_start, end+FD_GUI_TILE_TIMER_SNAP_CNT, end );
1083 0 : gui->summary.tile_timers_leader_history_slot_sample_cnt[ gui->summary.tile_timers_history_idx ] = end-gui->summary.tile_timers_snap_idx_slot_start;
1084 0 : ulong stride = fd_ulong_max( 1UL, (end-gui->summary.tile_timers_snap_idx_slot_start) / FD_GUI_TILE_TIMER_LEADER_DOWNSAMPLE_CNT );
1085 0 : for( ulong sample_snap_idx=gui->summary.tile_timers_snap_idx_slot_start, i=0UL; sample_snap_idx<end; sample_snap_idx+=stride, i++ ) {
1086 0 : memcpy( gui->summary.tile_timers_leader_history[ gui->summary.tile_timers_history_idx ][ i ], gui->summary.tile_timers_snap[ sample_snap_idx%FD_GUI_TILE_TIMER_SNAP_CNT ], sizeof(gui->summary.tile_timers_leader_history[ gui->summary.tile_timers_history_idx ][ i ]) );
1087 0 : }
1088 0 : gui->summary.tile_timers_history_idx = (gui->summary.tile_timers_history_idx+1UL)%FD_GUI_TILE_TIMER_LEADER_CNT;
1089 :
1090 : /* When a slot ends, snap the state of the waterfall and save it into
1091 : that slot, and also reset the reference counters to the end of the
1092 : slot. */
1093 :
1094 0 : fd_gui_txn_waterfall_snap( gui, slot->waterfall_end );
1095 0 : memcpy( slot->waterfall_begin, gui->summary.txn_waterfall_reference, sizeof(slot->waterfall_begin) );
1096 0 : memcpy( gui->summary.txn_waterfall_reference, slot->waterfall_end, sizeof(gui->summary.txn_waterfall_reference) );
1097 :
1098 0 : fd_gui_tile_stats_snap( gui, slot->waterfall_end, slot->tile_stats_end );
1099 0 : }
1100 :
1101 : static void
1102 : fd_gui_handle_reset_slot( fd_gui_t * gui,
1103 0 : ulong * msg ) {
1104 0 : ulong last_landed_vote = msg[ 0 ];
1105 :
1106 0 : ulong parent_cnt = msg[ 1 ];
1107 0 : FD_TEST( parent_cnt<4096UL );
1108 :
1109 0 : ulong _slot = msg[ 2 ];
1110 :
1111 0 : for( ulong i=0UL; i<parent_cnt; i++ ) {
1112 0 : ulong parent_slot = msg[2UL+i];
1113 0 : fd_gui_slot_t * slot = gui->slots[ parent_slot % FD_GUI_SLOTS_CNT ];
1114 0 : if( FD_UNLIKELY( slot->slot!=parent_slot ) ) {
1115 0 : ulong parent_parent_slot = ULONG_MAX;
1116 0 : if( FD_UNLIKELY( i!=parent_cnt-1UL) ) parent_parent_slot = msg[ 3UL+i ];
1117 0 : fd_gui_clear_slot( gui, parent_slot, parent_parent_slot );
1118 0 : }
1119 0 : }
1120 :
1121 0 : if( FD_UNLIKELY( gui->summary.vote_distance!=_slot-last_landed_vote ) ) {
1122 0 : gui->summary.vote_distance = _slot-last_landed_vote;
1123 0 : fd_gui_printf_vote_distance( gui );
1124 0 : fd_http_server_ws_broadcast( gui->http );
1125 0 : }
1126 :
1127 0 : if( FD_LIKELY( gui->summary.vote_state!=FD_GUI_VOTE_STATE_NON_VOTING ) ) {
1128 0 : if( FD_UNLIKELY( last_landed_vote==ULONG_MAX || (last_landed_vote+150UL)<_slot ) ) {
1129 0 : if( FD_UNLIKELY( gui->summary.vote_state!=FD_GUI_VOTE_STATE_DELINQUENT ) ) {
1130 0 : gui->summary.vote_state = FD_GUI_VOTE_STATE_DELINQUENT;
1131 0 : fd_gui_printf_vote_state( gui );
1132 0 : fd_http_server_ws_broadcast( gui->http );
1133 0 : }
1134 0 : } else {
1135 0 : if( FD_UNLIKELY( gui->summary.vote_state!=FD_GUI_VOTE_STATE_VOTING ) ) {
1136 0 : gui->summary.vote_state = FD_GUI_VOTE_STATE_VOTING;
1137 0 : fd_gui_printf_vote_state( gui );
1138 0 : fd_http_server_ws_broadcast( gui->http );
1139 0 : }
1140 0 : }
1141 0 : }
1142 :
1143 0 : ulong parent_slot_idx = 0UL;
1144 :
1145 0 : int republish_skip_rate[ 2 ] = {0};
1146 :
1147 0 : for( ulong i=0UL; i<fd_ulong_min( _slot+1, FD_GUI_SLOTS_CNT ); i++ ) {
1148 0 : ulong parent_slot = _slot - i;
1149 0 : ulong parent_idx = parent_slot % FD_GUI_SLOTS_CNT;
1150 :
1151 0 : fd_gui_slot_t * slot = gui->slots[ parent_idx ];
1152 0 : if( FD_UNLIKELY( slot->slot==ULONG_MAX || slot->slot!=parent_slot ) ) fd_gui_clear_slot( gui, parent_slot, ULONG_MAX );
1153 :
1154 : /* The chain of parents may stretch into already rooted slots if
1155 : they haven't been squashed yet, if we reach one of them we can
1156 : just exit, all the information prior to the root is already
1157 : correct. */
1158 :
1159 0 : if( FD_LIKELY( slot->level>=FD_GUI_SLOT_LEVEL_ROOTED ) ) break;
1160 :
1161 0 : int should_republish = slot->must_republish;
1162 0 : slot->must_republish = 0;
1163 :
1164 0 : if( FD_UNLIKELY( parent_slot!=msg[2UL+parent_slot_idx] ) ) {
1165 : /* We are between two parents in the rooted chain, which means
1166 : we were skipped. */
1167 0 : if( FD_UNLIKELY( !slot->skipped ) ) {
1168 0 : slot->skipped = 1;
1169 0 : should_republish = 1;
1170 0 : if( FD_LIKELY( slot->mine ) ) {
1171 0 : for( ulong i=0UL; i<2UL; i++ ) {
1172 0 : if( FD_LIKELY( parent_slot>=gui->epoch.epochs[ i ].start_slot && parent_slot<=gui->epoch.epochs[ i ].end_slot ) ) {
1173 0 : gui->epoch.epochs[ i ].my_skipped_slots++;
1174 0 : republish_skip_rate[ i ] = 1;
1175 0 : break;
1176 0 : }
1177 0 : }
1178 0 : }
1179 0 : }
1180 0 : } else {
1181 : /* Reached the next parent... */
1182 0 : if( FD_UNLIKELY( slot->skipped ) ) {
1183 0 : slot->skipped = 0;
1184 0 : should_republish = 1;
1185 0 : if( FD_LIKELY( slot->mine ) ) {
1186 0 : for( ulong i=0UL; i<2UL; i++ ) {
1187 0 : if( FD_LIKELY( parent_slot>=gui->epoch.epochs[ i ].start_slot && parent_slot<=gui->epoch.epochs[ i ].end_slot ) ) {
1188 0 : gui->epoch.epochs[ i ].my_skipped_slots--;
1189 0 : republish_skip_rate[ i ] = 1;
1190 0 : break;
1191 0 : }
1192 0 : }
1193 0 : }
1194 0 : }
1195 0 : parent_slot_idx++;
1196 0 : }
1197 :
1198 0 : if( FD_LIKELY( should_republish ) ) {
1199 0 : fd_gui_printf_slot( gui, parent_slot );
1200 0 : fd_http_server_ws_broadcast( gui->http );
1201 0 : }
1202 :
1203 : /* We reached the last parent in the chain, everything above this
1204 : must have already been rooted, so we can exit. */
1205 :
1206 0 : if( FD_UNLIKELY( parent_slot_idx>=parent_cnt ) ) break;
1207 0 : }
1208 :
1209 0 : ulong last_slot = _slot;
1210 0 : long last_published = gui->slots[ _slot % FD_GUI_SLOTS_CNT ]->completed_time;
1211 :
1212 0 : for( ulong i=0UL; i<fd_ulong_min( _slot+1, 750UL ); i++ ) {
1213 0 : ulong parent_slot = _slot - i;
1214 0 : ulong parent_idx = parent_slot % FD_GUI_SLOTS_CNT;
1215 :
1216 0 : fd_gui_slot_t * slot = gui->slots[ parent_idx ];
1217 0 : if( FD_UNLIKELY( slot->slot==ULONG_MAX) ) break;
1218 0 : if( FD_UNLIKELY( slot->slot!=parent_slot ) ) {
1219 0 : FD_LOG_ERR(( "_slot %lu i %lu we expect _slot-i %lu got slot->slot %lu", _slot, i, _slot-i, slot->slot ));
1220 0 : }
1221 :
1222 0 : if( FD_LIKELY( !slot->skipped ) ) {
1223 0 : last_slot = parent_slot;
1224 0 : last_published = slot->completed_time;
1225 0 : }
1226 0 : }
1227 :
1228 0 : if( FD_LIKELY( _slot!=last_slot )) {
1229 0 : gui->summary.estimated_slot_duration_nanos = (ulong)(fd_log_wallclock()-last_published)/(_slot-last_slot);
1230 0 : fd_gui_printf_estimated_slot_duration_nanos( gui );
1231 0 : fd_http_server_ws_broadcast( gui->http );
1232 0 : }
1233 :
1234 0 : if( FD_LIKELY( _slot!=gui->summary.slot_completed ) ) {
1235 0 : gui->summary.slot_completed = _slot;
1236 0 : fd_gui_printf_completed_slot( gui );
1237 0 : fd_http_server_ws_broadcast( gui->http );
1238 0 : }
1239 :
1240 0 : for( ulong i=0UL; i<2UL; i++ ) {
1241 0 : if( FD_LIKELY( republish_skip_rate[ i ] ) ) {
1242 0 : fd_gui_printf_skip_rate( gui, i );
1243 0 : fd_http_server_ws_broadcast( gui->http );
1244 0 : }
1245 0 : }
1246 0 : }
1247 :
1248 : static void
1249 : fd_gui_handle_completed_slot( fd_gui_t * gui,
1250 0 : ulong * msg ) {
1251 0 : ulong _slot = msg[ 0 ];
1252 0 : uint total_txn_count = (uint)msg[ 1 ];
1253 0 : uint nonvote_txn_count = (uint)msg[ 2 ];
1254 0 : uint failed_txn_count = (uint)msg[ 3 ];
1255 0 : uint nonvote_failed_txn_count = (uint)msg[ 4 ];
1256 0 : uint compute_units = (uint)msg[ 5 ];
1257 0 : ulong transaction_fee = msg[ 6 ];
1258 0 : ulong priority_fee = msg[ 7 ];
1259 0 : ulong tips = msg[ 8 ];
1260 0 : ulong _parent_slot = msg[ 9 ];
1261 :
1262 0 : fd_gui_slot_t * slot = gui->slots[ _slot % FD_GUI_SLOTS_CNT ];
1263 0 : if( FD_UNLIKELY( slot->slot!=_slot ) ) fd_gui_clear_slot( gui, _slot, _parent_slot );
1264 :
1265 0 : slot->completed_time = fd_log_wallclock();
1266 0 : slot->parent_slot = _parent_slot;
1267 0 : if( FD_LIKELY( slot->level<FD_GUI_SLOT_LEVEL_COMPLETED ) ) {
1268 : /* Typically a slot goes from INCOMPLETE to COMPLETED but it can
1269 : happen that it starts higher. One such case is when we
1270 : optimistically confirm a higher slot that skips this one, but
1271 : then later we replay this one anyway to track the bank fork. */
1272 :
1273 0 : if( FD_LIKELY( _slot<gui->summary.slot_optimistically_confirmed ) ) {
1274 : /* Cluster might have already optimistically confirmed by the time
1275 : we finish replaying it. */
1276 0 : slot->level = FD_GUI_SLOT_LEVEL_OPTIMISTICALLY_CONFIRMED;
1277 0 : } else {
1278 0 : slot->level = FD_GUI_SLOT_LEVEL_COMPLETED;
1279 0 : }
1280 0 : }
1281 0 : slot->total_txn_cnt = total_txn_count;
1282 0 : slot->vote_txn_cnt = total_txn_count - nonvote_txn_count;
1283 0 : slot->failed_txn_cnt = failed_txn_count;
1284 0 : slot->nonvote_failed_txn_cnt = nonvote_failed_txn_count;
1285 0 : slot->transaction_fee = transaction_fee;
1286 0 : slot->priority_fee = priority_fee;
1287 0 : slot->tips = tips;
1288 0 : if( FD_LIKELY( slot->leader_state==FD_GUI_SLOT_LEADER_UNSTARTED ) ) {
1289 : /* If we were already leader for this slot, then the poh component
1290 : calculated the CUs used and sent them there, rather than the
1291 : replay component which is sending this completed slot. */
1292 0 : slot->compute_units = compute_units;
1293 0 : }
1294 :
1295 0 : if( FD_UNLIKELY( gui->epoch.has_epoch[ 0 ] && _slot==gui->epoch.epochs[ 0 ].end_slot ) ) {
1296 0 : gui->epoch.epochs[ 0 ].end_time = slot->completed_time;
1297 0 : } else if( FD_UNLIKELY( gui->epoch.has_epoch[ 1 ] && _slot==gui->epoch.epochs[ 1 ].end_slot ) ) {
1298 0 : gui->epoch.epochs[ 1 ].end_time = slot->completed_time;
1299 0 : }
1300 :
1301 : /* Broadcast new skip rate if one of our slots got completed. */
1302 0 : if( FD_LIKELY( slot->mine ) ) {
1303 0 : for( ulong i=0UL; i<2UL; i++ ) {
1304 0 : if( FD_LIKELY( _slot>=gui->epoch.epochs[ i ].start_slot && _slot<=gui->epoch.epochs[ i ].end_slot ) ) {
1305 0 : fd_gui_printf_skip_rate( gui, i );
1306 0 : fd_http_server_ws_broadcast( gui->http );
1307 0 : break;
1308 0 : }
1309 0 : }
1310 0 : }
1311 0 : }
1312 :
1313 : static void
1314 : fd_gui_handle_rooted_slot( fd_gui_t * gui,
1315 0 : ulong * msg ) {
1316 0 : ulong _slot = msg[ 0 ];
1317 :
1318 : // FD_LOG_WARNING(( "Got rooted slot %lu", _slot ));
1319 :
1320 : /* Slot 0 is always rooted. No need to iterate all the way back to
1321 : i==_slot */
1322 0 : for( ulong i=0UL; i<fd_ulong_min( _slot, FD_GUI_SLOTS_CNT ); i++ ) {
1323 0 : ulong parent_slot = _slot - i;
1324 0 : ulong parent_idx = parent_slot % FD_GUI_SLOTS_CNT;
1325 :
1326 0 : fd_gui_slot_t * slot = gui->slots[ parent_idx ];
1327 0 : if( FD_UNLIKELY( slot->slot==ULONG_MAX) ) break;
1328 :
1329 0 : if( FD_UNLIKELY( slot->slot!=parent_slot ) ) {
1330 0 : FD_LOG_ERR(( "_slot %lu i %lu we expect parent_slot %lu got slot->slot %lu", _slot, i, parent_slot, slot->slot ));
1331 0 : }
1332 0 : if( FD_UNLIKELY( slot->level>=FD_GUI_SLOT_LEVEL_ROOTED ) ) break;
1333 :
1334 0 : slot->level = FD_GUI_SLOT_LEVEL_ROOTED;
1335 0 : fd_gui_printf_slot( gui, parent_slot );
1336 0 : fd_http_server_ws_broadcast( gui->http );
1337 0 : }
1338 :
1339 0 : gui->summary.slot_rooted = _slot;
1340 0 : fd_gui_printf_root_slot( gui );
1341 0 : fd_http_server_ws_broadcast( gui->http );
1342 0 : }
1343 :
1344 : static void
1345 : fd_gui_handle_optimistically_confirmed_slot( fd_gui_t * gui,
1346 0 : ulong * msg ) {
1347 0 : ulong _slot = msg[ 0 ];
1348 :
1349 : /* Slot 0 is always rooted. No need to iterate all the way back to
1350 : i==_slot */
1351 0 : for( ulong i=0UL; i<fd_ulong_min( _slot, FD_GUI_SLOTS_CNT ); i++ ) {
1352 0 : ulong parent_slot = _slot - i;
1353 0 : ulong parent_idx = parent_slot % FD_GUI_SLOTS_CNT;
1354 :
1355 0 : fd_gui_slot_t * slot = gui->slots[ parent_idx ];
1356 0 : if( FD_UNLIKELY( slot->slot==ULONG_MAX) ) break;
1357 :
1358 0 : if( FD_UNLIKELY( slot->slot>parent_slot ) ) {
1359 0 : FD_LOG_ERR(( "_slot %lu i %lu we expect parent_slot %lu got slot->slot %lu", _slot, i, parent_slot, slot->slot ));
1360 0 : } else if( FD_UNLIKELY( slot->slot<parent_slot ) ) {
1361 : /* Slot not even replayed yet ... will come out as optmistically confirmed */
1362 0 : continue;
1363 0 : }
1364 0 : if( FD_UNLIKELY( slot->level>=FD_GUI_SLOT_LEVEL_ROOTED ) ) break;
1365 :
1366 0 : if( FD_LIKELY( slot->level<FD_GUI_SLOT_LEVEL_OPTIMISTICALLY_CONFIRMED ) ) {
1367 0 : slot->level = FD_GUI_SLOT_LEVEL_OPTIMISTICALLY_CONFIRMED;
1368 0 : fd_gui_printf_slot( gui, parent_slot );
1369 0 : fd_http_server_ws_broadcast( gui->http );
1370 0 : }
1371 0 : }
1372 :
1373 0 : if( FD_UNLIKELY( _slot<gui->summary.slot_optimistically_confirmed ) ) {
1374 : /* Optimistically confirmed slot went backwards ... mark some slots as no
1375 : longer optimistically confirmed. */
1376 0 : for( ulong i=gui->summary.slot_optimistically_confirmed; i>=_slot; i-- ) {
1377 0 : fd_gui_slot_t * slot = gui->slots[ i % FD_GUI_SLOTS_CNT ];
1378 0 : if( FD_UNLIKELY( slot->slot==ULONG_MAX ) ) break;
1379 0 : if( FD_LIKELY( slot->slot==i ) ) {
1380 : /* It's possible for the optimistically confirmed slot to skip
1381 : backwards between two slots that we haven't yet replayed. In
1382 : that case we don't need to change anything, since they will
1383 : get marked properly when they get completed. */
1384 0 : slot->level = FD_GUI_SLOT_LEVEL_COMPLETED;
1385 0 : fd_gui_printf_slot( gui, i );
1386 0 : fd_http_server_ws_broadcast( gui->http );
1387 0 : }
1388 0 : }
1389 0 : }
1390 :
1391 0 : gui->summary.slot_optimistically_confirmed = _slot;
1392 0 : fd_gui_printf_optimistically_confirmed_slot( gui );
1393 0 : fd_http_server_ws_broadcast( gui->http );
1394 0 : }
1395 :
1396 : static void
1397 : fd_gui_handle_balance_update( fd_gui_t * gui,
1398 0 : ulong const * msg ) {
1399 0 : switch( msg[ 0 ] ) {
1400 0 : case 0UL:
1401 0 : gui->summary.identity_account_balance = msg[ 1 ];
1402 0 : fd_gui_printf_identity_balance( gui );
1403 0 : fd_http_server_ws_broadcast( gui->http );
1404 0 : break;
1405 0 : case 1UL:
1406 0 : gui->summary.vote_account_balance = msg[ 1 ];
1407 0 : fd_gui_printf_vote_balance( gui );
1408 0 : fd_http_server_ws_broadcast( gui->http );
1409 0 : break;
1410 0 : default:
1411 0 : FD_LOG_ERR(( "balance: unknown account type: %lu", msg[ 0 ] ));
1412 0 : }
1413 0 : }
1414 :
1415 : static void
1416 : fd_gui_handle_start_progress( fd_gui_t * gui,
1417 0 : uchar const * msg ) {
1418 0 : uchar type = msg[ 0 ];
1419 :
1420 0 : switch (type) {
1421 0 : case 0:
1422 0 : gui->summary.startup_progress = FD_GUI_START_PROGRESS_TYPE_INITIALIZING;
1423 0 : FD_LOG_INFO(( "progress: initializing" ));
1424 0 : break;
1425 0 : case 1: {
1426 0 : char const * snapshot_type;
1427 0 : if( FD_UNLIKELY( gui->summary.startup_got_full_snapshot ) ) {
1428 0 : gui->summary.startup_progress = FD_GUI_START_PROGRESS_TYPE_SEARCHING_FOR_INCREMENTAL_SNAPSHOT;
1429 0 : snapshot_type = "incremental";
1430 0 : } else {
1431 0 : gui->summary.startup_progress = FD_GUI_START_PROGRESS_TYPE_SEARCHING_FOR_FULL_SNAPSHOT;
1432 0 : snapshot_type = "full";
1433 0 : }
1434 0 : FD_LOG_INFO(( "progress: searching for %s snapshot", snapshot_type ));
1435 0 : break;
1436 0 : }
1437 0 : case 2: {
1438 0 : uchar is_full_snapshot = msg[ 1 ];
1439 0 : if( FD_LIKELY( is_full_snapshot ) ) {
1440 0 : gui->summary.startup_progress = FD_GUI_START_PROGRESS_TYPE_DOWNLOADING_FULL_SNAPSHOT;
1441 0 : gui->summary.startup_full_snapshot_slot = *((ulong *)(msg + 2));
1442 0 : gui->summary.startup_full_snapshot_peer_ip_addr = *((uint *)(msg + 10));
1443 0 : gui->summary.startup_full_snapshot_peer_port = *((ushort *)(msg + 14));
1444 0 : gui->summary.startup_full_snapshot_total_bytes = *((ulong *)(msg + 16));
1445 0 : gui->summary.startup_full_snapshot_current_bytes = *((ulong *)(msg + 24));
1446 0 : gui->summary.startup_full_snapshot_elapsed_secs = *((double *)(msg + 32));
1447 0 : gui->summary.startup_full_snapshot_remaining_secs = *((double *)(msg + 40));
1448 0 : gui->summary.startup_full_snapshot_throughput = *((double *)(msg + 48));
1449 0 : FD_LOG_INFO(( "progress: downloading full snapshot: slot=%lu", gui->summary.startup_full_snapshot_slot ));
1450 0 : } else {
1451 0 : gui->summary.startup_progress = FD_GUI_START_PROGRESS_TYPE_DOWNLOADING_INCREMENTAL_SNAPSHOT;
1452 0 : gui->summary.startup_incremental_snapshot_slot = *((ulong *)(msg + 2));
1453 0 : gui->summary.startup_incremental_snapshot_peer_ip_addr = *((uint *)(msg + 10));
1454 0 : gui->summary.startup_incremental_snapshot_peer_port = *((ushort *)(msg + 14));
1455 0 : gui->summary.startup_incremental_snapshot_total_bytes = *((ulong *)(msg + 16));
1456 0 : gui->summary.startup_incremental_snapshot_current_bytes = *((ulong *)(msg + 24));
1457 0 : gui->summary.startup_incremental_snapshot_elapsed_secs = *((double *)(msg + 32));
1458 0 : gui->summary.startup_incremental_snapshot_remaining_secs = *((double *)(msg + 40));
1459 0 : gui->summary.startup_incremental_snapshot_throughput = *((double *)(msg + 48));
1460 0 : FD_LOG_INFO(( "progress: downloading incremental snapshot: slot=%lu", gui->summary.startup_incremental_snapshot_slot ));
1461 0 : }
1462 0 : break;
1463 0 : }
1464 0 : case 3: {
1465 0 : gui->summary.startup_got_full_snapshot = 1;
1466 0 : break;
1467 0 : }
1468 0 : case 4:
1469 0 : gui->summary.startup_progress = FD_GUI_START_PROGRESS_TYPE_CLEANING_BLOCK_STORE;
1470 0 : FD_LOG_INFO(( "progress: cleaning block store" ));
1471 0 : break;
1472 0 : case 5:
1473 0 : gui->summary.startup_progress = FD_GUI_START_PROGRESS_TYPE_CLEANING_ACCOUNTS;
1474 0 : FD_LOG_INFO(( "progress: cleaning accounts" ));
1475 0 : break;
1476 0 : case 6:
1477 0 : gui->summary.startup_progress = FD_GUI_START_PROGRESS_TYPE_LOADING_LEDGER;
1478 0 : FD_LOG_INFO(( "progress: loading ledger" ));
1479 0 : break;
1480 0 : case 7: {
1481 0 : gui->summary.startup_progress = FD_GUI_START_PROGRESS_TYPE_PROCESSING_LEDGER;
1482 0 : gui->summary.startup_ledger_slot = fd_ulong_load_8( msg + 1 );
1483 0 : gui->summary.startup_ledger_max_slot = fd_ulong_load_8( msg + 9 );
1484 0 : FD_LOG_INFO(( "progress: processing ledger: slot=%lu, max_slot=%lu", gui->summary.startup_ledger_slot, gui->summary.startup_ledger_max_slot ));
1485 0 : break;
1486 0 : }
1487 0 : case 8:
1488 0 : gui->summary.startup_progress = FD_GUI_START_PROGRESS_TYPE_STARTING_SERVICES;
1489 0 : FD_LOG_INFO(( "progress: starting services" ));
1490 0 : break;
1491 0 : case 9:
1492 0 : gui->summary.startup_progress = FD_GUI_START_PROGRESS_TYPE_HALTED;
1493 0 : FD_LOG_INFO(( "progress: halted" ));
1494 0 : break;
1495 0 : case 10: {
1496 0 : gui->summary.startup_progress = FD_GUI_START_PROGRESS_TYPE_WAITING_FOR_SUPERMAJORITY;
1497 0 : gui->summary.startup_waiting_for_supermajority_slot = fd_ulong_load_8( msg + 1 );
1498 0 : gui->summary.startup_waiting_for_supermajority_stake_pct = fd_ulong_load_8( msg + 9 );
1499 0 : FD_LOG_INFO(( "progress: waiting for supermajority: slot=%lu, gossip_stake_percent=%lu", gui->summary.startup_waiting_for_supermajority_slot, gui->summary.startup_waiting_for_supermajority_stake_pct ));
1500 0 : break;
1501 0 : }
1502 0 : case 11:
1503 0 : gui->summary.startup_progress = FD_GUI_START_PROGRESS_TYPE_RUNNING;
1504 0 : FD_LOG_INFO(( "progress: running" ));
1505 0 : break;
1506 0 : default:
1507 0 : FD_LOG_ERR(( "progress: unknown type: %u", type ));
1508 0 : }
1509 :
1510 0 : fd_gui_printf_startup_progress( gui );
1511 0 : fd_http_server_ws_broadcast( gui->http );
1512 0 : }
1513 :
1514 : static void
1515 : fd_gui_handle_genesis_hash( fd_gui_t * gui,
1516 0 : uchar const * msg ) {
1517 0 : FD_BASE58_ENCODE_32_BYTES(msg, hash_cstr);
1518 0 : ulong cluster = fd_genesis_cluster_identify(hash_cstr);
1519 0 : char const * cluster_name = fd_genesis_cluster_name(cluster);
1520 :
1521 0 : if( FD_LIKELY( strcmp( gui->summary.cluster, cluster_name ) ) ) {
1522 0 : gui->summary.cluster = fd_genesis_cluster_name(cluster);
1523 0 : fd_gui_printf_cluster( gui );
1524 0 : fd_http_server_ws_broadcast( gui->http );
1525 0 : }
1526 0 : }
1527 :
1528 : static void
1529 : fd_gui_handle_block_engine_update( fd_gui_t * gui,
1530 0 : uchar const * msg ) {
1531 0 : fd_plugin_msg_block_engine_update_t const * update = (fd_plugin_msg_block_engine_update_t const *)msg;
1532 :
1533 0 : gui->block_engine.has_block_engine = 1;
1534 0 : strncpy( gui->block_engine.name, update->name, sizeof(gui->block_engine.name) );
1535 0 : strncpy( gui->block_engine.url, update->url, sizeof(gui->block_engine.url) );
1536 0 : gui->block_engine.status = update->status;
1537 :
1538 0 : fd_gui_printf_block_engine( gui );
1539 0 : fd_http_server_ws_broadcast( gui->http );
1540 0 : }
1541 :
1542 : void
1543 : fd_gui_plugin_message( fd_gui_t * gui,
1544 : ulong plugin_msg,
1545 0 : uchar const * msg ) {
1546 :
1547 0 : switch( plugin_msg ) {
1548 0 : case FD_PLUGIN_MSG_SLOT_ROOTED:
1549 0 : fd_gui_handle_rooted_slot( gui, (ulong *)msg );
1550 0 : break;
1551 0 : case FD_PLUGIN_MSG_SLOT_OPTIMISTICALLY_CONFIRMED:
1552 0 : fd_gui_handle_optimistically_confirmed_slot( gui, (ulong *)msg );
1553 0 : break;
1554 0 : case FD_PLUGIN_MSG_SLOT_COMPLETED:
1555 0 : fd_gui_handle_completed_slot( gui, (ulong *)msg );
1556 0 : break;
1557 0 : case FD_PLUGIN_MSG_SLOT_ESTIMATED:
1558 0 : gui->summary.slot_estimated = *(ulong const *)msg;
1559 0 : fd_gui_printf_estimated_slot( gui );
1560 0 : fd_http_server_ws_broadcast( gui->http );
1561 0 : break;
1562 0 : case FD_PLUGIN_MSG_LEADER_SCHEDULE: {
1563 0 : fd_gui_handle_leader_schedule( gui, (ulong const *)msg );
1564 0 : break;
1565 0 : }
1566 0 : case FD_PLUGIN_MSG_SLOT_START: {
1567 0 : fd_gui_handle_slot_start( gui, (ulong *)msg );
1568 0 : break;
1569 0 : }
1570 0 : case FD_PLUGIN_MSG_SLOT_END: {
1571 0 : fd_gui_handle_slot_end( gui, (ulong *)msg );
1572 0 : break;
1573 0 : }
1574 0 : case FD_PLUGIN_MSG_GOSSIP_UPDATE: {
1575 0 : fd_gui_handle_gossip_update( gui, msg );
1576 0 : break;
1577 0 : }
1578 0 : case FD_PLUGIN_MSG_VOTE_ACCOUNT_UPDATE: {
1579 0 : fd_gui_handle_vote_account_update( gui, msg );
1580 0 : break;
1581 0 : }
1582 0 : case FD_PLUGIN_MSG_VALIDATOR_INFO: {
1583 0 : fd_gui_handle_validator_info_update( gui, msg );
1584 0 : break;
1585 0 : }
1586 0 : case FD_PLUGIN_MSG_SLOT_RESET: {
1587 0 : fd_gui_handle_reset_slot( gui, (ulong *)msg );
1588 0 : break;
1589 0 : }
1590 0 : case FD_PLUGIN_MSG_BALANCE: {
1591 0 : fd_gui_handle_balance_update( gui, (ulong *)msg );
1592 0 : break;
1593 0 : }
1594 0 : case FD_PLUGIN_MSG_START_PROGRESS: {
1595 0 : fd_gui_handle_start_progress( gui, msg );
1596 0 : break;
1597 0 : }
1598 0 : case FD_PLUGIN_MSG_GENESIS_HASH_KNOWN: {
1599 0 : fd_gui_handle_genesis_hash( gui, msg );
1600 0 : break;
1601 0 : }
1602 0 : case FD_PLUGIN_MSG_BLOCK_ENGINE_UPDATE: {
1603 0 : fd_gui_handle_block_engine_update( gui, msg );
1604 0 : break;
1605 0 : }
1606 0 : default:
1607 0 : FD_LOG_ERR(( "Unhandled plugin msg: 0x%lx", plugin_msg ));
1608 0 : break;
1609 0 : }
1610 0 : }
1611 :
1612 : void
1613 : fd_gui_became_leader( fd_gui_t * gui,
1614 : ulong _slot,
1615 : long start_time_nanos,
1616 : long end_time_nanos,
1617 : ulong max_compute_units,
1618 0 : ulong max_microblocks ) {
1619 0 : fd_gui_slot_t * slot = gui->slots[ _slot % FD_GUI_SLOTS_CNT ];
1620 0 : if( FD_UNLIKELY( slot->slot!=_slot ) ) fd_gui_clear_slot( gui, _slot, ULONG_MAX );
1621 :
1622 0 : slot->cus.leader_start_time = start_time_nanos;
1623 :
1624 0 : long tickcount = fd_tickcount();
1625 0 : if( FD_LIKELY( slot->cus.reference_ticks==LONG_MAX ) ) slot->cus.reference_ticks = tickcount;
1626 0 : slot->cus.reference_nanos = fd_log_wallclock() - (long)((double)(tickcount - slot->cus.reference_ticks) / fd_tempo_tick_per_ns( NULL ));
1627 :
1628 0 : slot->cus.leader_end_time = end_time_nanos;
1629 0 : slot->cus.max_compute_units = (uint)max_compute_units;
1630 0 : if( FD_LIKELY( slot->cus.microblocks_upper_bound==USHORT_MAX ) ) slot->cus.microblocks_upper_bound = (ushort)max_microblocks;
1631 0 : }
1632 :
1633 : void
1634 : fd_gui_unbecame_leader( fd_gui_t * gui,
1635 : ulong _slot,
1636 0 : ulong microblocks_in_slot ) {
1637 0 : fd_gui_slot_t * slot = gui->slots[ _slot % FD_GUI_SLOTS_CNT ];
1638 0 : if( FD_UNLIKELY( slot->slot!=_slot ) ) fd_gui_clear_slot( gui, _slot, ULONG_MAX );
1639 :
1640 0 : slot->cus.microblocks_upper_bound = (ushort)microblocks_in_slot;
1641 0 : }
1642 :
1643 : void
1644 : fd_gui_execution_begin( fd_gui_t * gui,
1645 : long tickcount,
1646 : ulong _slot,
1647 : ulong bank_idx,
1648 : ulong txn_cnt,
1649 0 : fd_txn_p_t * txns ) {
1650 0 : fd_gui_slot_t * slot = gui->slots[ _slot % FD_GUI_SLOTS_CNT ];
1651 0 : if( FD_UNLIKELY( slot->slot!=_slot ) ) fd_gui_clear_slot( gui, _slot, ULONG_MAX );
1652 :
1653 0 : if( FD_UNLIKELY( !slot->cus.has_offset[ 64UL ] ) ) slot->cus.start_offset[ 64UL ] = gui->cus.offset;
1654 0 : slot->cus.has_offset[ 64UL ] = 1;
1655 0 : if( FD_UNLIKELY( slot->cus.reference_ticks==LONG_MAX ) ) slot->cus.reference_ticks = tickcount;
1656 :
1657 0 : for( ulong i=0UL; i<txn_cnt; i++ ) {
1658 0 : fd_txn_p_t * txn = &txns[ i ];
1659 :
1660 0 : ulong comp = fd_gui_cu_history_compress( FD_GUI_EXECUTION_TYPE_BEGIN,
1661 0 : bank_idx,
1662 0 : txn->pack_cu.non_execution_cus + txn->pack_cu.requested_exec_plus_acct_data_cus,
1663 0 : i==0UL,
1664 0 : slot->cus.reference_ticks,
1665 0 : tickcount );
1666 0 : gui->cus.history[ gui->cus.offset%FD_GUI_COMPUTE_UNITS_HISTORY_SZ ] = comp;
1667 0 : gui->cus.offset++;
1668 0 : }
1669 :
1670 0 : slot->cus.end_offset[ 64UL ] = gui->cus.offset;
1671 0 : slot->cus.begin_microblocks = (ushort)(slot->cus.begin_microblocks + txn_cnt);
1672 0 : }
1673 :
1674 : void
1675 : fd_gui_execution_end( fd_gui_t * gui,
1676 : long tickcount,
1677 : ulong bank_idx,
1678 : int is_last_in_bundle,
1679 : ulong _slot,
1680 : ulong txn_cnt,
1681 0 : fd_txn_p_t * txns ) {
1682 0 : fd_gui_slot_t * slot = gui->slots[ _slot % FD_GUI_SLOTS_CNT ];
1683 0 : if( FD_UNLIKELY( slot->slot!=_slot ) ) fd_gui_clear_slot( gui, _slot, ULONG_MAX );
1684 :
1685 0 : if( FD_UNLIKELY( !slot->cus.has_offset[ bank_idx ] ) ) slot->cus.start_offset[ bank_idx ] = gui->cus.offset;
1686 0 : slot->cus.has_offset[ bank_idx ] = 1;
1687 0 : if( FD_UNLIKELY( slot->cus.reference_ticks==LONG_MAX ) ) slot->cus.reference_ticks = tickcount;
1688 :
1689 0 : for( ulong i=0UL; i<txn_cnt; i++ ) {
1690 0 : fd_txn_p_t * txn = &txns[ i ];
1691 :
1692 0 : ulong comp = fd_gui_cu_history_compress( FD_GUI_EXECUTION_TYPE_END,
1693 0 : bank_idx,
1694 0 : txn->bank_cu.rebated_cus,
1695 0 : is_last_in_bundle,
1696 0 : slot->cus.reference_ticks,
1697 0 : tickcount );
1698 0 : gui->cus.history[ gui->cus.offset%FD_GUI_COMPUTE_UNITS_HISTORY_SZ ] = comp;
1699 0 : gui->cus.offset++;
1700 0 : }
1701 :
1702 0 : slot->cus.end_offset[ bank_idx ] = gui->cus.offset;
1703 0 : slot->cus.end_microblocks = (ushort)(slot->cus.end_microblocks + txn_cnt);
1704 0 : }
|