Line data Source code
1 : #ifndef HEADER_fd_src_disco_gui_fd_gui_h
2 : #define HEADER_fd_src_disco_gui_fd_gui_h
3 :
4 : #include "fd_gui_peers.h"
5 :
6 : #include "../topo/fd_topo.h"
7 :
8 : #include "../../ballet/txn/fd_txn.h"
9 : #include "../../disco/tiles.h"
10 : #include "../../disco/fd_txn_p.h"
11 : #include "../../discof/restore/fd_snapct_tile.h"
12 : #include "../../discof/tower/fd_tower_tile.h"
13 : #include "../../flamenco/leaders/fd_leaders.h"
14 : #include "../../flamenco/types/fd_types_custom.h"
15 : #include "../../util/fd_util_base.h"
16 : #include "../../util/hist/fd_histf.h"
17 : #include "../../waltz/http/fd_http_server.h"
18 :
19 : /* frankendancer only */
20 0 : #define FD_GUI_MAX_PEER_CNT ( 40200UL)
21 :
22 : /* frankendancer only */
23 0 : #define FD_GUI_START_PROGRESS_TYPE_INITIALIZING ( 0)
24 0 : #define FD_GUI_START_PROGRESS_TYPE_SEARCHING_FOR_FULL_SNAPSHOT ( 1)
25 0 : #define FD_GUI_START_PROGRESS_TYPE_DOWNLOADING_FULL_SNAPSHOT ( 2)
26 0 : #define FD_GUI_START_PROGRESS_TYPE_SEARCHING_FOR_INCREMENTAL_SNAPSHOT ( 3)
27 0 : #define FD_GUI_START_PROGRESS_TYPE_DOWNLOADING_INCREMENTAL_SNAPSHOT ( 4)
28 0 : #define FD_GUI_START_PROGRESS_TYPE_CLEANING_BLOCK_STORE ( 5)
29 0 : #define FD_GUI_START_PROGRESS_TYPE_CLEANING_ACCOUNTS ( 6)
30 0 : #define FD_GUI_START_PROGRESS_TYPE_LOADING_LEDGER ( 7)
31 0 : #define FD_GUI_START_PROGRESS_TYPE_PROCESSING_LEDGER ( 8)
32 0 : #define FD_GUI_START_PROGRESS_TYPE_STARTING_SERVICES ( 9)
33 0 : #define FD_GUI_START_PROGRESS_TYPE_HALTED (10)
34 0 : #define FD_GUI_START_PROGRESS_TYPE_WAITING_FOR_SUPERMAJORITY (11)
35 0 : #define FD_GUI_START_PROGRESS_TYPE_RUNNING (12)
36 :
37 : /* frankendancer only */
38 : struct fd_gui_gossip_peer {
39 : fd_pubkey_t pubkey[ 1 ];
40 : ulong wallclock;
41 : ushort shred_version;
42 :
43 : int has_version;
44 : struct {
45 : ushort major;
46 : ushort minor;
47 : ushort patch;
48 :
49 : int has_commit;
50 : uint commit;
51 :
52 : uint feature_set;
53 : } version;
54 :
55 : struct {
56 : uint ipv4;
57 : ushort port;
58 : } sockets[ 12 ];
59 : };
60 :
61 : /* frankendancer only */
62 : struct fd_gui_vote_account {
63 : fd_pubkey_t pubkey[ 1 ];
64 : fd_pubkey_t vote_account[ 1 ];
65 :
66 : ulong activated_stake;
67 : ulong last_vote;
68 : ulong root_slot;
69 : ulong epoch_credits;
70 : uchar commission;
71 : int delinquent;
72 : };
73 :
74 : /* frankendancer only */
75 : struct fd_gui_validator_info {
76 : fd_pubkey_t pubkey[ 1 ];
77 :
78 : char name[ 64 ];
79 : char website[ 128 ];
80 : char details[ 256 ];
81 : char icon_uri[ 128 ];
82 : };
83 :
84 : /* frankendancer only */
85 : #define FD_GUI_SLOT_LEADER_UNSTARTED (0UL)
86 : #define FD_GUI_SLOT_LEADER_STARTED (1UL)
87 : #define FD_GUI_SLOT_LEADER_ENDED (2UL)
88 :
89 0 : #define FD_GUI_SLOTS_CNT (864000UL) /* 2x 432000 */
90 0 : #define FD_GUI_LEADER_CNT (4096UL)
91 :
92 0 : #define FD_GUI_TPS_HISTORY_WINDOW_DURATION_SECONDS (10L)
93 0 : #define FD_GUI_TPS_HISTORY_SAMPLE_CNT (150UL)
94 :
95 0 : #define FD_GUI_TILE_TIMER_SNAP_CNT (512UL)
96 : #define FD_GUI_TILE_TIMER_LEADER_DOWNSAMPLE_CNT (50UL) /* 500ms / 10ms */
97 0 : #define FD_GUI_SCHEDULER_COUNT_SNAP_CNT (512UL)
98 : #define FD_GUI_SCHEDULER_COUNT_LEADER_DOWNSAMPLE_CNT (50UL) /* 500ms / 10ms */
99 : #define FD_GUI_TILE_TIMER_TILE_CNT (256UL)
100 :
101 0 : #define FD_GUI_VOTE_STATE_NON_VOTING (0)
102 0 : #define FD_GUI_VOTE_STATE_VOTING (1)
103 0 : #define FD_GUI_VOTE_STATE_DELINQUENT (2)
104 :
105 0 : #define FD_GUI_BOOT_PROGRESS_TYPE_JOINING_GOSSIP (1)
106 0 : #define FD_GUI_BOOT_PROGRESS_TYPE_LOADING_FULL_SNAPSHOT (2)
107 0 : #define FD_GUI_BOOT_PROGRESS_TYPE_LOADING_INCREMENTAL_SNAPSHOT (3)
108 0 : #define FD_GUI_BOOT_PROGRESS_TYPE_CATCHING_UP (4)
109 0 : #define FD_GUI_BOOT_PROGRESS_TYPE_RUNNING (5)
110 :
111 0 : #define FD_GUI_BOOT_PROGRESS_FULL_SNAPSHOT_IDX (0UL)
112 0 : #define FD_GUI_BOOT_PROGRESS_INCREMENTAL_SNAPSHOT_IDX (1UL)
113 0 : #define FD_GUI_BOOT_PROGRESS_SNAPSHOT_CNT (2UL)
114 :
115 0 : #define FD_GUI_SLOT_LEVEL_INCOMPLETE (0)
116 0 : #define FD_GUI_SLOT_LEVEL_COMPLETED (1)
117 0 : #define FD_GUI_SLOT_LEVEL_OPTIMISTICALLY_CONFIRMED (2)
118 0 : #define FD_GUI_SLOT_LEVEL_ROOTED (3)
119 0 : #define FD_GUI_SLOT_LEVEL_FINALIZED (4)
120 :
121 : /* Ideally, we would store an entire epoch's worth of transactions. If
122 : we assume any given validator will have at most 5% stake, and average
123 : transactions per slot is around 10_000, then an epoch will have about
124 : 432_000*10_000*0.05 transactions (~2^28).
125 :
126 : Unfortunately, the transaction struct is 100+ bytes. If we sized the
127 : array to 2^28 entries then the memory required would be ~26GB. In
128 : order to keep memory usage to a more reasonable level, we'll
129 : arbitrarily use a fourth of that size. */
130 0 : #define FD_GUI_TXN_HISTORY_SZ (1UL<<26UL)
131 :
132 0 : #define FD_GUI_TXN_FLAGS_STARTED ( 1U)
133 0 : #define FD_GUI_TXN_FLAGS_ENDED ( 2U)
134 0 : #define FD_GUI_TXN_FLAGS_IS_SIMPLE_VOTE ( 4U)
135 0 : #define FD_GUI_TXN_FLAGS_FROM_BUNDLE ( 8U)
136 0 : #define FD_GUI_TXN_FLAGS_LANDED_IN_BLOCK (16U)
137 :
138 : /* One use case for tracking ingress shred slot is to estimate when we
139 : have caught up to the tip of the blockchain. A naive approach would
140 : be to track the maximum seen slot.
141 :
142 : maximum_seen_slot = fd_ulong_max( maximum_seen_slot, new_slot_from_shred_tile );
143 :
144 : Unfortunately, this doesn't always work because a validator can send
145 : a slot number that is arbitrarily large on a false fork. Also, these
146 : shreds can be for a repair response, which can be arbitrarily small.
147 :
148 : The prospects here seem bleak, but not all hope is lost! We know
149 : that for a sufficiently large historical time window there is a high
150 : probability that at least some of the slots we observe will be valid
151 : recent turbine slots. For a sufficiently small window there is a high
152 : probability that all the observed shred slots are non-malicious (i.e.
153 : not arbitrarily large).
154 :
155 : In practice shred slots are almost always non-malicious. We can keep
156 : a history of the 12 largest slots we've seen in the past 4.8 seconds.
157 : We'll consider the "tip" of the blockchain to be the maximum slot in
158 : our history. This way, if we receive maliciously large slot number,
159 : it will be evicted after 4.8 seconds. If we receive a small slot from
160 : a repair response it will be ignored because we've seen other larger
161 : slots, meaning that our estimate is eventually consistent. For
162 : monitoring purposes this is sufficient.
163 :
164 : The worst case scenario is that this validator receives an incorrect
165 : shred slot slot more than once every 3 leader rotations. Before the
166 : previous incorrect slot is evicted from the history, a new one takes
167 : it's place and we wouldn't never get a correct estimate of the tip of
168 : the chain. We also would indefinitely think that that we haven't
169 : caught up. This would require the chain having perpetually malicious
170 : leaders with adjacent rotations. If this happens, Solana has bigger
171 : problems. */
172 0 : #define FD_GUI_TURBINE_SLOT_HISTORY_SZ ( 12UL )
173 :
174 : /* Like the turbine slot, the latest repair slot can also swing to
175 : arbitrarily large values due to a malicious fork switch. The gui
176 : provides the same guarantees for freshness and accuracy. This
177 : history is somewhat larger to handle the increased repair bandwidth
178 : during catch up. */
179 0 : #define FD_GUI_REPAIR_SLOT_HISTORY_SZ ( 512UL )
180 :
181 : /* FD_GUI_*_CATCH_UP_HISTORY_SZ is the capacity of the record of slots
182 : seen from repair or turbine during the catch up stage at startup.
183 : These buffers are run-length encoded, so they will typically be very
184 : small. The worst-case scenario is unbounded, so bounds here are
185 : determined heuristically. */
186 : #define FD_GUI_REPAIR_CATCH_UP_HISTORY_SZ (4096UL)
187 : #define FD_GUI_TURBINE_CATCH_UP_HISTORY_SZ (4096UL)
188 :
189 : /* FD_GUI_SHREDS_STAGING_SZ is number of shred events we'll retain in
190 : in a small staging area. The lifecycle of a shred looks something
191 : like the following
192 :
193 : states] turbine -> repairing (optional) -> processing -> waiting_for_siblings -> slot_complete
194 : events] ^-repair_requested ^-shred_received/shred_repaired ^-shred_replayed ^-max(shred_replayed)
195 :
196 : We're interested in recording timestamps for state transitions (which
197 : these docs call "shred events"). Unfortunately, due to forking,
198 : duplicate packets, etc we can't make any guarantees about ordering or
199 : uniqueness for these event timestamps. Instead the GUI just records
200 : timestamps for all events as they occur and put them into an array.
201 : Newly recorded event timestamps are also broadcast live to WebSocket
202 : consumers.
203 :
204 : The amount of shred events for non-finalized blocks can't really be
205 : bounded, so we use generous estimates here to set a memory bound. */
206 0 : #define FD_GUI_MAX_SHREDS_PER_BLOCK (32UL*1024UL)
207 0 : #define FD_GUI_MAX_EVENTS_PER_SHRED ( 32UL)
208 0 : #define FD_GUI_SHREDS_STAGING_SZ (32UL * FD_GUI_MAX_SHREDS_PER_BLOCK * FD_GUI_MAX_EVENTS_PER_SHRED)
209 :
210 : /* FD_GUI_SHREDS_HISTORY_SZ the number of shred events in our historical
211 : shred store. Shred events here belong to finalized slots which means
212 : we won't record any additional shred updates for these slots.
213 :
214 : All shred events for a given slot will be places in a contiguous
215 : chunk in the array, and the bounding indicies are stored in the
216 : fd_gui_slot_t slot history. Within a slot chunk, shred events are
217 : ordered in the ordered they were recorded by the gui tile.
218 :
219 : Ideally, we have enough space to store an epoch's worth of events,
220 : but we are limited by realistic memory consumption. Instead, we pick
221 : bound heuristically. */
222 0 : #define FD_GUI_SHREDS_HISTORY_SZ (432000UL*2000UL*4UL / 6UL)
223 :
224 : #define FD_GUI_SLOT_SHRED_REPAIR_REQUEST (0UL)
225 0 : #define FD_GUI_SLOT_SHRED_SHRED_RECEIVED_TURBINE (1UL)
226 0 : #define FD_GUI_SLOT_SHRED_SHRED_RECEIVED_REPAIR (2UL)
227 : /* #define FD_GUI_SLOT_SHRED_SHRED_REPLAY_EXEC_START (3UL) // UNUSED */
228 0 : #define FD_GUI_SLOT_SHRED_SHRED_REPLAY_EXEC_DONE (3UL)
229 0 : #define FD_GUI_SLOT_SHRED_SHRED_SLOT_COMPLETE (4UL)
230 :
231 0 : #define FD_GUI_SLOT_RANKINGS_SZ (100UL)
232 0 : #define FD_GUI_SLOT_RANKING_TYPE_ASC (0)
233 0 : #define FD_GUI_SLOT_RANKING_TYPE_DESC (1)
234 :
235 : struct fd_gui_tile_timers {
236 : ulong caughtup_housekeeping_ticks;
237 : ulong processing_housekeeping_ticks;
238 : ulong backpressure_housekeeping_ticks;
239 :
240 : ulong caughtup_prefrag_ticks;
241 : ulong processing_prefrag_ticks;
242 : ulong backpressure_prefrag_ticks;
243 :
244 : ulong caughtup_postfrag_ticks;
245 : ulong processing_postfrag_ticks;
246 : };
247 :
248 : typedef struct fd_gui_tile_timers fd_gui_tile_timers_t;
249 :
250 : struct fd_gui_scheduler_counts {
251 : long sample_time_ns;
252 : ulong regular;
253 : ulong votes;
254 : ulong conflicting;
255 : ulong bundles;
256 : };
257 :
258 : typedef struct fd_gui_scheduler_counts fd_gui_scheduler_counts_t;
259 :
260 : struct fd_gui_leader_slot {
261 : ulong slot;
262 : long leader_start_time; /* UNIX timestamp of when we first became leader in this slot */
263 : long leader_end_time; /* UNIX timestamp of when we stopped being leader in this slot */
264 :
265 : /* Stem tiles can exist in one of 8 distinct activity regimes at any
266 : given moment. One of these regimes, caughtup_postfrag, is the
267 : only regime where a tile is in a spin loop without doing any
268 : useful work. This info is useful from a monitoring perspective
269 : because it lets us estimate CPU utilization on a pinned core.
270 :
271 : Every 10ms, the gui tile samples the amount of time tiles spent
272 : in each regime in the past 10ms. This sample is used to infer
273 : the CPU utilization in the past 10ms. This utilization is
274 : streamed live to WebSocket clients.
275 :
276 : In additional to live utilization, we are interested in recording
277 : utilization during one of this validator's leader slots. The gui
278 : tile is continuously recording samples to storage with capacity
279 : FD_GUI_TILE_TIMER_SNAP_CNT. The sample index is recorded at the
280 : start and end of a leader slot, and the number of samples is
281 : downsampled to be at most FD_GUI_TILE_TIMER_LEADER_DOWNSAMPLE_CNT
282 : samples (e.g. if there was an unusually long leader slot) and
283 : inserted into historical storage with capacity FD_GUI_LEADER_CNT.
284 : FD_GUI_TILE_TIMER_TILE_CNT is the maximum number of tiles supported. */
285 : fd_gui_tile_timers_t tile_timers[ FD_GUI_TILE_TIMER_LEADER_DOWNSAMPLE_CNT ][ FD_GUI_TILE_TIMER_TILE_CNT ];
286 : ulong tile_timers_sample_cnt;
287 :
288 : fd_gui_scheduler_counts_t scheduler_counts[ FD_GUI_SCHEDULER_COUNT_LEADER_DOWNSAMPLE_CNT ][ 1 ];
289 : ulong scheduler_counts_sample_cnt;
290 :
291 : struct {
292 : uint microblocks_upper_bound; /* An upper bound on the number of microblocks in the slot. If the number of
293 : microblocks observed is equal to this, the slot can be considered over.
294 : Generally, the bound is set to a "final" state by a done packing message,
295 : which sets it to the exact number of microblocks, but sometimes this message
296 : is not sent, if the max upper bound published by poh was already correct. */
297 : uint begin_microblocks; /* The number of microblocks we have seen be started (sent) from pack to banks. */
298 : uint end_microblocks; /* The number of microblocks we have seen be ended (sent) from banks to poh. The
299 : slot is only considered over if the begin and end microblocks seen are both equal
300 : to the microblock upper bound. */
301 :
302 : ulong start_offset; /* The smallest pack transaction index for this slot. The first transaction for this slot will
303 : be written to gui->txs[ start_offset%FD_GUI_TXN_HISTORY_SZ ]. */
304 : ulong end_offset; /* The largest pack transaction index for this slot, plus 1. The last transaction for this
305 : slot will be written to gui->txs[ (start_offset-1)%FD_GUI_TXN_HISTORY_SZ ]. */
306 : } txs;
307 :
308 : fd_done_packing_t scheduler_stats[ 1 ];
309 : };
310 :
311 : typedef struct fd_gui_leader_slot fd_gui_leader_slot_t;
312 :
313 : struct fd_gui_slot_completed {
314 : ulong slot;
315 : long completed_time;
316 : ulong parent_slot;
317 : uint max_compute_units;
318 : uint total_txn_cnt;
319 : uint vote_txn_cnt;
320 : uint failed_txn_cnt;
321 : uint nonvote_failed_txn_cnt;
322 : ulong transaction_fee;
323 : ulong priority_fee;
324 : ulong tips;
325 : uint compute_units;
326 : uint shred_cnt;
327 : };
328 :
329 : typedef struct fd_gui_slot_completed fd_gui_slot_completed_t;
330 :
331 : struct fd_gui_slot_staged_shred_event {
332 : long timestamp;
333 : ulong slot;
334 : ushort shred_idx;
335 : uchar event;
336 : };
337 :
338 : typedef struct fd_gui_slot_staged_shred_event fd_gui_slot_staged_shred_event_t;
339 :
340 : struct __attribute__((packed)) fd_gui_slot_history_shred_event {
341 : long timestamp;
342 : ushort shred_idx;
343 : uchar event;
344 : };
345 :
346 : typedef struct fd_gui_slot_history_shred_event fd_gui_slot_history_shred_event_t;
347 :
348 : struct fd_gui_slot_ranking {
349 : ulong slot;
350 : ulong value;
351 : int type;
352 : };
353 : typedef struct fd_gui_slot_ranking fd_gui_slot_ranking_t;
354 :
355 : struct fd_gui_slot_rankings {
356 : fd_gui_slot_ranking_t largest_tips [ FD_GUI_SLOT_RANKINGS_SZ+1UL ];
357 : fd_gui_slot_ranking_t largest_fees [ FD_GUI_SLOT_RANKINGS_SZ+1UL ];
358 : fd_gui_slot_ranking_t largest_rewards [ FD_GUI_SLOT_RANKINGS_SZ+1UL ];
359 : fd_gui_slot_ranking_t largest_duration [ FD_GUI_SLOT_RANKINGS_SZ+1UL ];
360 : fd_gui_slot_ranking_t largest_compute_units [ FD_GUI_SLOT_RANKINGS_SZ+1UL ];
361 : fd_gui_slot_ranking_t largest_skipped [ FD_GUI_SLOT_RANKINGS_SZ+1UL ];
362 : fd_gui_slot_ranking_t largest_rewards_per_cu [ FD_GUI_SLOT_RANKINGS_SZ+1UL ];
363 : fd_gui_slot_ranking_t smallest_tips [ FD_GUI_SLOT_RANKINGS_SZ+1UL ];
364 : fd_gui_slot_ranking_t smallest_fees [ FD_GUI_SLOT_RANKINGS_SZ+1UL ];
365 : fd_gui_slot_ranking_t smallest_rewards [ FD_GUI_SLOT_RANKINGS_SZ+1UL ];
366 : fd_gui_slot_ranking_t smallest_rewards_per_cu[ FD_GUI_SLOT_RANKINGS_SZ+1UL ];
367 : fd_gui_slot_ranking_t smallest_duration [ FD_GUI_SLOT_RANKINGS_SZ+1UL ];
368 : fd_gui_slot_ranking_t smallest_compute_units [ FD_GUI_SLOT_RANKINGS_SZ+1UL ];
369 : fd_gui_slot_ranking_t smallest_skipped [ FD_GUI_SLOT_RANKINGS_SZ+1UL ];
370 : };
371 :
372 : typedef struct fd_gui_slot_rankings fd_gui_slot_rankings_t;
373 :
374 : struct fd_gui_ephemeral_slot {
375 : ulong slot; /* ULONG_MAX indicates invalid/evicted */
376 : long timestamp_arrival_nanos;
377 : };
378 : typedef struct fd_gui_ephemeral_slot fd_gui_ephemeral_slot_t;
379 :
380 : struct __attribute__((packed)) fd_gui_txn {
381 : uchar signature[ FD_TXN_SIGNATURE_SZ ];
382 : ulong transaction_fee;
383 : ulong priority_fee;
384 : ulong tips;
385 : long timestamp_arrival_nanos;
386 :
387 : /* compute_units_requested has both execution and non-execution cus */
388 : uint compute_units_requested : 21; /* <= 1.4M */
389 : uint compute_units_consumed : 21; /* <= 1.4M */
390 : uint bank_idx : 6; /* in [0, 64) */
391 : uint error_code : 6; /* in [0, 64) */
392 : int timestamp_delta_start_nanos;
393 : int timestamp_delta_end_nanos;
394 :
395 : /* txn_{}_pct is used as a fraction of the total microblock
396 : duration. For example, txn_load_end_pct can be used to find the
397 : time when this transaction started executing:
398 :
399 : timestamp_delta_start_exec_nanos = (
400 : (timestamp_delta_end_nanos-timestamp_delta_start_nanos) *
401 : ((double)txn_{}_pct/USHORT_MAX)
402 : ) */
403 : uchar txn_start_pct;
404 : uchar txn_load_end_pct;
405 : uchar txn_end_pct;
406 : uchar txn_preload_end_pct;
407 : uchar flags; /* assigned with the FD_GUI_TXN_FLAGS_* macros */
408 : uchar source_tpu; /* FD_TXN_M_TPU_SOURCE_* */
409 : uint source_ipv4;
410 : uint microblock_idx;
411 : };
412 :
413 : typedef struct fd_gui_txn fd_gui_txn_t;
414 :
415 : struct fd_gui_txn_waterfall {
416 : struct {
417 : ulong quic;
418 : ulong udp;
419 : ulong gossip;
420 : ulong block_engine;
421 : ulong pack_cranked;
422 : } in;
423 :
424 : struct {
425 : ulong net_overrun;
426 : ulong quic_overrun;
427 : ulong quic_frag_drop;
428 : ulong quic_abandoned;
429 : ulong tpu_quic_invalid;
430 : ulong tpu_udp_invalid;
431 : ulong verify_overrun;
432 : ulong verify_parse;
433 : ulong verify_failed;
434 : ulong verify_duplicate;
435 : ulong dedup_duplicate;
436 : ulong resolv_lut_failed;
437 : ulong resolv_expired;
438 : ulong resolv_ancient;
439 : ulong resolv_no_ledger;
440 : ulong resolv_retained;
441 : ulong pack_invalid;
442 : ulong pack_invalid_bundle;
443 : ulong pack_expired;
444 : ulong pack_already_executed;
445 : ulong pack_retained;
446 : ulong pack_wait_full;
447 : ulong pack_leader_slow;
448 : ulong bank_invalid;
449 : ulong bank_nonce_already_advanced;
450 : ulong bank_nonce_advance_failed;
451 : ulong bank_nonce_wrong_blockhash;
452 : ulong block_success;
453 : ulong block_fail;
454 : } out;
455 : };
456 :
457 : typedef struct fd_gui_txn_waterfall fd_gui_txn_waterfall_t;
458 :
459 : struct fd_gui_tile_stats {
460 : long sample_time_nanos;
461 :
462 : ulong net_in_rx_bytes; /* Number of bytes received by the net or sock tile*/
463 : ulong quic_conn_cnt; /* Number of active QUIC connections */
464 : fd_histf_t bundle_rx_delay_hist; /* Histogram of bundle rx delay */
465 : ulong bundle_rtt_smoothed_nanos; /* RTT (nanoseconds) moving average */
466 : ulong verify_drop_cnt; /* Number of transactions dropped by verify tiles */
467 : ulong verify_total_cnt; /* Number of transactions received by verify tiles */
468 : ulong dedup_drop_cnt; /* Number of transactions dropped by dedup tile */
469 : ulong dedup_total_cnt; /* Number of transactions received by dedup tile */
470 : ulong pack_buffer_cnt; /* Number of buffered transactions in the pack tile */
471 : ulong pack_buffer_capacity; /* Total size of the pack transaction buffer */
472 : ulong bank_txn_exec_cnt; /* Number of transactions processed by the bank tile */
473 : ulong net_out_tx_bytes; /* Number of bytes sent by the net or sock tile */
474 : };
475 :
476 : typedef struct fd_gui_tile_stats fd_gui_tile_stats_t;
477 :
478 : struct fd_gui_slot {
479 : ulong slot;
480 : ulong parent_slot;
481 : uint max_compute_units;
482 : long completed_time;
483 : int mine;
484 : int skipped;
485 : int must_republish;
486 : int level;
487 : uint total_txn_cnt;
488 : uint vote_txn_cnt;
489 : uint failed_txn_cnt;
490 : uint nonvote_failed_txn_cnt;
491 : uint compute_units;
492 : ulong transaction_fee;
493 : ulong priority_fee;
494 : ulong tips;
495 : uint shred_cnt;
496 :
497 : /* Some slot info is only tracked for our own leader slots. These
498 : slots are kept in a separate buffer. */
499 : ulong leader_history_idx;
500 :
501 : fd_gui_txn_waterfall_t waterfall_begin[ 1 ];
502 : fd_gui_txn_waterfall_t waterfall_end[ 1 ];
503 :
504 : fd_gui_tile_stats_t tile_stats_begin[ 1 ];
505 : fd_gui_tile_stats_t tile_stats_end[ 1 ];
506 :
507 : struct {
508 : ulong start_offset; /* gui->shreds.history[ start_offset % FD_GUI_SHREDS_HISTORY_SZ ] is the first shred event in
509 : contiguous chunk of events in the shred history corresponding to this slot. */
510 : ulong end_offset; /* gui->shreds.history[ end_offset % FD_GUI_SHREDS_HISTORY_SZ ] is the last shred event in
511 : contiguous chunk of events in the shred history corresponding to this slot. */
512 : } shreds;
513 : };
514 :
515 : typedef struct fd_gui_slot fd_gui_slot_t;
516 :
517 : struct fd_gui {
518 : fd_http_server_t * http;
519 : fd_topo_t * topo;
520 :
521 : long next_sample_400millis;
522 : long next_sample_100millis;
523 : long next_sample_10millis;
524 :
525 : ulong leader_slot;
526 :
527 : struct {
528 : fd_pubkey_t identity_key[ 1 ];
529 : int has_vote_key;
530 : fd_pubkey_t vote_key[ 1 ];
531 : char vote_key_base58[ FD_BASE58_ENCODED_32_SZ ];
532 : char identity_key_base58[ FD_BASE58_ENCODED_32_SZ ];
533 :
534 : int is_full_client;
535 : char const * version;
536 : char const * cluster;
537 :
538 : ulong vote_distance;
539 : int vote_state;
540 :
541 : long startup_time_nanos;
542 :
543 : union {
544 : struct { /* frankendancer only */
545 : uchar phase;
546 : int startup_got_full_snapshot;
547 :
548 : ulong startup_incremental_snapshot_slot;
549 : uint startup_incremental_snapshot_peer_ip_addr;
550 : ushort startup_incremental_snapshot_peer_port;
551 : double startup_incremental_snapshot_elapsed_secs;
552 : double startup_incremental_snapshot_remaining_secs;
553 : double startup_incremental_snapshot_throughput;
554 : ulong startup_incremental_snapshot_total_bytes;
555 : ulong startup_incremental_snapshot_current_bytes;
556 :
557 : ulong startup_full_snapshot_slot;
558 : uint startup_full_snapshot_peer_ip_addr;
559 : ushort startup_full_snapshot_peer_port;
560 : double startup_full_snapshot_elapsed_secs;
561 : double startup_full_snapshot_remaining_secs;
562 : double startup_full_snapshot_throughput;
563 : ulong startup_full_snapshot_total_bytes;
564 : ulong startup_full_snapshot_current_bytes;
565 :
566 : ulong startup_ledger_slot;
567 : ulong startup_ledger_max_slot;
568 :
569 : ulong startup_waiting_for_supermajority_slot;
570 : ulong startup_waiting_for_supermajority_stake_pct;
571 : } startup_progress;
572 : struct { /* used in the full client */
573 : uchar phase;
574 : long joining_gossip_time_nanos;
575 : struct {
576 : ulong slot;
577 : uint peer_addr;
578 : ushort peer_port;
579 : ulong total_bytes_compressed;
580 : long reset_time_nanos; /* UNIX nanosecond timestamp */
581 : long sample_time_nanos;
582 : ulong reset_cnt;
583 :
584 : ulong read_bytes_compressed;
585 : char read_path[ PATH_MAX+30UL ]; /* URL or filesystem path. 30 is fd_cstr_nlen( "https://255.255.255.255:12345/", ULONG_MAX ) */
586 :
587 : ulong decompress_bytes_decompressed;
588 : ulong decompress_bytes_compressed;
589 :
590 : ulong insert_bytes_decompressed;
591 : char insert_path[ PATH_MAX ];
592 : ulong insert_accounts_current;
593 : } loading_snapshot[ FD_GUI_BOOT_PROGRESS_SNAPSHOT_CNT ];
594 :
595 : long catching_up_time_nanos;
596 : ulong catching_up_first_replay_slot;
597 : } boot_progress;
598 : };
599 :
600 : int schedule_strategy;
601 :
602 : ulong identity_account_balance;
603 : ulong vote_account_balance;
604 : ulong estimated_slot_duration_nanos;
605 :
606 : ulong sock_tile_cnt;
607 : ulong net_tile_cnt;
608 : ulong quic_tile_cnt;
609 : ulong verify_tile_cnt;
610 : ulong resolv_tile_cnt;
611 : ulong bank_tile_cnt;
612 : ulong shred_tile_cnt;
613 :
614 : ulong slot_rooted;
615 : ulong slot_optimistically_confirmed;
616 : ulong slot_completed;
617 : ulong slot_estimated;
618 : ulong slot_caught_up;
619 : ulong slot_repair;
620 : ulong slot_turbine;
621 :
622 : fd_gui_ephemeral_slot_t slots_max_turbine[ FD_GUI_TURBINE_SLOT_HISTORY_SZ+1UL ];
623 : fd_gui_ephemeral_slot_t slots_max_repair [ FD_GUI_REPAIR_SLOT_HISTORY_SZ +1UL ];
624 :
625 : /* catchup_* is run-length encoded. i.e. adjacent pairs represent
626 : contiguous runs */
627 : ulong catch_up_turbine[ FD_GUI_TURBINE_CATCH_UP_HISTORY_SZ ];
628 : ulong catch_up_turbine_sz;
629 :
630 : ulong catch_up_repair[ FD_GUI_REPAIR_CATCH_UP_HISTORY_SZ ];
631 : ulong catch_up_repair_sz;
632 :
633 : ulong estimated_tps_history_idx;
634 : ulong estimated_tps_history[ FD_GUI_TPS_HISTORY_SAMPLE_CNT ][ 3UL ];
635 :
636 : fd_gui_txn_waterfall_t txn_waterfall_reference[ 1 ];
637 : fd_gui_txn_waterfall_t txn_waterfall_current[ 1 ];
638 :
639 : fd_gui_tile_stats_t tile_stats_reference[ 1 ];
640 : fd_gui_tile_stats_t tile_stats_current[ 1 ];
641 :
642 : ulong tile_timers_snap_idx;
643 : ulong tile_timers_snap_idx_slot_start;
644 : /* Temporary storage for samples. Will be downsampled into leader history on slot end. */
645 : fd_gui_tile_timers_t tile_timers_snap[ FD_GUI_TILE_TIMER_SNAP_CNT ][ FD_GUI_TILE_TIMER_TILE_CNT ];
646 :
647 : ulong scheduler_counts_snap_idx;
648 : ulong scheduler_counts_snap_idx_slot_start;
649 : /* Temporary storage for samples. Will be downsampled into leader history on slot end. */
650 : fd_gui_scheduler_counts_t scheduler_counts_snap[ FD_GUI_SCHEDULER_COUNT_SNAP_CNT ][ 1 ];
651 : } summary;
652 :
653 : struct {
654 : fd_gui_ipinfo_node_t * nodes;
655 : char country_code[ 512 ][ 3 ]; /* ISO 3166-1 alpha-2 country codes */
656 : } ipinfo;
657 :
658 : fd_gui_slot_t slots[ FD_GUI_SLOTS_CNT ][ 1 ];
659 :
660 : fd_gui_leader_slot_t leader_slots[ FD_GUI_LEADER_CNT ][ 1 ];
661 : ulong leader_slots_cnt;
662 :
663 : fd_gui_txn_t txs[ FD_GUI_TXN_HISTORY_SZ ][ 1 ];
664 : ulong pack_txn_idx; /* The pack index of the most recently received transaction */
665 :
666 : struct {
667 : int has_block_engine;
668 : char name[ 16 ];
669 : char url[ 256 ];
670 : char ip_cstr[ 40 ]; /* IPv4 or IPv6 cstr */
671 : int status;
672 : } block_engine;
673 :
674 : struct {
675 : int has_epoch[ 2 ];
676 :
677 : struct {
678 : ulong epoch;
679 : long start_time;
680 : long end_time;
681 :
682 : ulong my_total_slots;
683 : ulong my_skipped_slots;
684 :
685 : ulong start_slot;
686 : ulong end_slot;
687 : ulong excluded_stake;
688 : fd_epoch_leaders_t * lsched;
689 : uchar __attribute__((aligned(FD_EPOCH_LEADERS_ALIGN))) _lsched[ FD_EPOCH_LEADERS_FOOTPRINT(MAX_STAKED_LEADERS, MAX_SLOTS_PER_EPOCH) ];
690 : fd_vote_stake_weight_t stakes[ MAX_STAKED_LEADERS ];
691 :
692 : ulong rankings_slot; /* One more than the largest slot we've processed into our rankings */
693 : fd_gui_slot_rankings_t rankings[ 1 ]; /* global slot rankings */
694 : fd_gui_slot_rankings_t my_rankings[ 1 ]; /* my slots only */
695 : } epochs[ 2 ];
696 : } epoch;
697 :
698 : struct { /* frankendancer only */
699 : ulong peer_cnt;
700 : struct fd_gui_gossip_peer peers[ FD_GUI_MAX_PEER_CNT ];
701 : } gossip;
702 :
703 : struct { /* frankendancer only */
704 : ulong vote_account_cnt;
705 : struct fd_gui_vote_account vote_accounts[ FD_GUI_MAX_PEER_CNT ];
706 : } vote_account;
707 :
708 : struct { /* frankendancer only */
709 : ulong info_cnt;
710 : struct fd_gui_validator_info info[ FD_GUI_MAX_PEER_CNT ];
711 : } validator_info;
712 :
713 : fd_gui_peers_ctx_t * peers; /* full-client */
714 :
715 : struct {
716 : ulong staged_next_broadcast; /* staged[ staged_next_broadcast % FD_GUI_SHREDS_STAGING_SZ ] is the first shred event
717 : that hasn't yet been broadcast to WebSocket clients */
718 : ulong staged_head; /* staged_head % FD_GUI_SHREDS_STAGING_SZ is the first valid event in staged */
719 : ulong staged_tail; /* staged_tail % FD_GUI_SHREDS_STAGING_SZ is one past the last valid event in staged */
720 : fd_gui_slot_staged_shred_event_t staged [ FD_GUI_SHREDS_STAGING_SZ ];
721 :
722 : ulong history_slot; /* the largest slot store in history */
723 : ulong history_tail; /* history_tail % FD_GUI_SHREDS_HISTORY_SZ is one past the last valid event in history */
724 : fd_gui_slot_history_shred_event_t history[ FD_GUI_SHREDS_HISTORY_SZ ];
725 :
726 : /* scratch space for stable sorts */
727 : fd_gui_slot_staged_shred_event_t _staged_scratch [ FD_GUI_SHREDS_STAGING_SZ ];
728 : fd_gui_slot_staged_shred_event_t _staged_scratch2[ FD_GUI_SHREDS_STAGING_SZ ];
729 : } shreds; /* full client */
730 : };
731 :
732 : typedef struct fd_gui fd_gui_t;
733 :
734 : FD_PROTOTYPES_BEGIN
735 :
736 : FD_FN_CONST ulong
737 : fd_gui_align( void );
738 :
739 : FD_FN_CONST ulong
740 : fd_gui_footprint( void );
741 :
742 : void *
743 : fd_gui_new( void * shmem,
744 : fd_http_server_t * http,
745 : char const * version,
746 : char const * cluster,
747 : uchar const * identity_key,
748 : int has_vote_key,
749 : uchar const * vote_key,
750 : int is_full_client,
751 : int snapshots_enabled,
752 : int is_voting,
753 : int schedule_strategy,
754 : fd_topo_t * topo,
755 : long now );
756 :
757 : fd_gui_t *
758 : fd_gui_join( void * shmem );
759 :
760 : void
761 : fd_gui_set_identity( fd_gui_t * gui,
762 : uchar const * identity_pubkey );
763 :
764 : void
765 : fd_gui_ws_open( fd_gui_t * gui,
766 : ulong conn_id );
767 :
768 : int
769 : fd_gui_ws_message( fd_gui_t * gui,
770 : ulong ws_conn_id,
771 : uchar const * data,
772 : ulong data_len );
773 :
774 : void
775 : fd_gui_plugin_message( fd_gui_t * gui,
776 : ulong plugin_msg,
777 : uchar const * msg,
778 : long now );
779 :
780 : void
781 : fd_gui_became_leader( fd_gui_t * gui,
782 : ulong slot,
783 : long start_time_nanos,
784 : long end_time_nanos,
785 : ulong max_compute_units,
786 : ulong max_microblocks );
787 :
788 : void
789 : fd_gui_unbecame_leader( fd_gui_t * gui,
790 : ulong _slot,
791 : fd_done_packing_t const * done_packing,
792 : long now );
793 :
794 : void
795 : fd_gui_microblock_execution_begin( fd_gui_t * gui,
796 : long now,
797 : ulong _slot,
798 : fd_txn_p_t * txns,
799 : ulong txn_cnt,
800 : uint microblock_idx,
801 : ulong pack_txn_idx );
802 :
803 : void
804 : fd_gui_microblock_execution_end( fd_gui_t * gui,
805 : long now,
806 : ulong bank_idx,
807 : ulong _slot,
808 : ulong txn_cnt,
809 : fd_txn_p_t * txns,
810 : ulong pack_txn_idx,
811 : uchar txn_start_pct,
812 : uchar txn_load_end_pct,
813 : uchar txn_end_pct,
814 : uchar txn_preload_end_pct,
815 : ulong tips );
816 :
817 : int
818 : fd_gui_poll( fd_gui_t * gui, long now );
819 :
820 : void
821 : fd_gui_handle_shred( fd_gui_t * gui,
822 : ulong slot,
823 : ulong shred_idx,
824 : int is_turbine,
825 : long tsorig );
826 :
827 : void
828 : fd_gui_handle_exec_txn_done( fd_gui_t * gui,
829 : ulong slot,
830 : ulong start_shred_idx,
831 : ulong end_shred_idx,
832 : long tsorig_ns,
833 : long tspub_ns );
834 :
835 : void
836 : fd_gui_handle_repair_slot( fd_gui_t * gui, ulong slot, long now );
837 :
838 : void
839 : fd_gui_handle_snapshot_update( fd_gui_t * gui,
840 : fd_snapct_update_t const * msg );
841 :
842 : void
843 : fd_gui_handle_leader_schedule( fd_gui_t * gui,
844 : fd_stake_weight_msg_t const * leader_schedule,
845 : long now );
846 :
847 : void
848 : fd_gui_handle_tower_update( fd_gui_t * gui,
849 : fd_tower_slot_done_t const * msg,
850 : long now );
851 :
852 : void
853 : fd_gui_handle_replay_update( fd_gui_t * gui,
854 : fd_gui_slot_completed_t * slot_completed,
855 : long now );
856 :
857 : void
858 : fd_gui_handle_genesis_hash( fd_gui_t * gui,
859 : uchar const * msg );
860 :
861 : static inline fd_gui_slot_t *
862 0 : fd_gui_get_slot( fd_gui_t const * gui, ulong _slot ) {
863 0 : fd_gui_slot_t const * slot = gui->slots[ _slot % FD_GUI_SLOTS_CNT ];
864 0 : if( FD_UNLIKELY( slot->slot==ULONG_MAX || _slot==ULONG_MAX || slot->slot!=_slot ) ) return NULL;
865 0 : return (fd_gui_slot_t *)slot;
866 0 : }
867 :
868 : static inline fd_gui_slot_t const *
869 0 : fd_gui_get_slot_const( fd_gui_t const * gui, ulong _slot ) {
870 0 : return fd_gui_get_slot( gui, _slot );
871 0 : }
872 :
873 : static inline fd_gui_leader_slot_t *
874 0 : fd_gui_get_leader_slot( fd_gui_t const * gui, ulong _slot ) {
875 0 : fd_gui_slot_t const * slot = fd_gui_get_slot( gui, _slot );
876 0 : if( FD_UNLIKELY( !slot
877 0 : || !slot->mine
878 0 : || slot->leader_history_idx==ULONG_MAX
879 0 : || slot->leader_history_idx + FD_GUI_LEADER_CNT < gui->leader_slots_cnt
880 0 : || gui->leader_slots[ slot->leader_history_idx % FD_GUI_LEADER_CNT ]->slot!=_slot ) ) return NULL;
881 0 : return (fd_gui_leader_slot_t *)gui->leader_slots[ slot->leader_history_idx % FD_GUI_LEADER_CNT ];
882 0 : }
883 :
884 : static inline fd_gui_leader_slot_t const *
885 0 : fd_gui_get_leader_slot_const( fd_gui_t const * gui, ulong _slot ) {
886 0 : return fd_gui_get_leader_slot( gui, _slot );
887 0 : }
888 :
889 : /* fd_gui_get_root_slot returns a handle to the closest ancestor of slot
890 : that is a root, if available, otherwise NULL. */
891 : static inline fd_gui_slot_t *
892 : fd_gui_get_root_slot( fd_gui_t const * gui,
893 0 : ulong slot ) {
894 0 : fd_gui_slot_t * c = fd_gui_get_slot( gui, slot );
895 0 : while( c ) {
896 0 : if( FD_UNLIKELY( c->level>=FD_GUI_SLOT_LEVEL_ROOTED ) ) return c;
897 0 : c = fd_gui_get_slot( gui, c->parent_slot );
898 0 : }
899 0 : return NULL;
900 0 : }
901 :
902 : /* fd_gui_slot_is_ancestor returns 1 if anc is known to be an ancestor
903 : of slot (on the same fork), 0 otherwise. */
904 : static inline int
905 : fd_gui_slot_is_ancestor( fd_gui_t const * gui,
906 : ulong anc,
907 0 : ulong slot ) {
908 0 : fd_gui_slot_t * c = fd_gui_get_slot( gui, slot );
909 0 : while( c ) {
910 0 : if( FD_UNLIKELY( c->slot==anc ) ) return 1;
911 0 : c = fd_gui_get_slot( gui, c->parent_slot );
912 0 : }
913 0 : return 0;
914 0 : }
915 :
916 : /* fd_gui_get_parent_slot_on_fork returns a handle to the parent of slot
917 : on the fork ending on frontier_slot. If slot is unknown or skipped,
918 : the closest (by slot number) valid parent on the fork is returned.
919 :
920 : NULL if slot is not an ancestor of frontier slot or if the parent is
921 : unknown. */
922 : static inline fd_gui_slot_t *
923 : fd_gui_get_parent_slot_on_fork( fd_gui_t const * gui,
924 : ulong frontier_slot,
925 0 : ulong slot ) {
926 0 : fd_gui_slot_t * c = fd_gui_get_slot( gui, frontier_slot );
927 0 : while( c ) {
928 0 : if( FD_UNLIKELY( c->slot<=slot ) ) return NULL;
929 0 : fd_gui_slot_t * p = fd_gui_get_slot( gui, c->parent_slot );
930 0 : if( FD_UNLIKELY( p && p->slot<=slot-1UL ) ) return p;
931 0 : c = p;
932 0 : }
933 0 : return NULL;
934 0 : }
935 :
936 : /* fd_gui_is_skipped_on_fork returns 1 if slot is skipped on the fork
937 : starting at anc and ending at des, 0 otherwise. */
938 : static inline int
939 : fd_gui_is_skipped_on_fork( fd_gui_t const * gui,
940 : ulong anc,
941 : ulong des,
942 0 : ulong slot ) {
943 0 : fd_gui_slot_t const * c = fd_gui_get_slot( gui, des );
944 0 : while( c ) {
945 0 : if( FD_UNLIKELY( anc==c->slot ) ) return 0; /* on the fork, not skipped */
946 0 : fd_gui_slot_t const * p = fd_gui_get_slot( gui, c->parent_slot );
947 0 : if( FD_UNLIKELY( p && p->slot<slot && c->slot>slot ) ) return 1; /* in-between two nodes, skipped */
948 0 : c = p;
949 0 : }
950 :
951 0 : return 0; /* slot not between anc and des, or is unknown */
952 0 : }
953 :
954 : FD_PROTOTYPES_END
955 :
956 : #endif /* HEADER_fd_src_disco_gui_fd_gui_h */
|