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