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 : #include "fd_gui_shred.h"
6 : #include "fd_gui_ema.h"
7 :
8 : #include "../topo/fd_topo.h"
9 : #include "../diag/fd_diag_tile.h"
10 :
11 : #include "../../ballet/txn/fd_txn.h"
12 : #include "../../disco/tiles.h"
13 : #include "../../disco/fd_txn_p.h"
14 : #include "../../disco/bundle/fd_bundle_tile.h"
15 : #include "../../discof/backup/fd_snapsv_tile.h"
16 : #include "../../discof/restore/fd_snapct_tile.h"
17 : #include "../../discof/restore/utils/fd_ssmsg.h"
18 : #include "../../discof/restore/utils/fd_wfs.h"
19 : #include "../../discof/replay/fd_replay_tile.h"
20 : #include "../../flamenco/leaders/fd_leaders.h"
21 : #include "../../flamenco/runtime/sysvar/fd_sysvar_epoch_schedule.h" /* fd_slot_to_epoch */
22 : #include "../../util/fd_util_base.h"
23 : #include "../../util/hist/fd_histf.h"
24 : #include "../../waltz/http/fd_http_server.h"
25 : #include "../../waltz/http/fd_url.h"
26 : #include "../../flamenco/accdb/fd_accdb_cache.h"
27 : #include "../../flamenco/accdb/fd_accdb_shmem.h"
28 : #include "../../flamenco/alpenglow/fd_block_marker.h" /* FD_NUM_SLOTS_FOR_REWARD */
29 :
30 :
31 : /* ---- Network Bandwidth Monitoring ----------------------------------- */
32 :
33 0 : #define FD_GUI_NETWORK_EMA_HALF_LIFE_NS (1000000000L) /* 1 second in nanoseconds */
34 0 : #define FD_GUI_NET_PROTO_CNT (7UL) /* turbine, gossip, tpu, repair, rserve, metric, votor */
35 0 : #define FD_GUI_NET_RATE_MAX_WINDOW_NS (300L*1000L*1000L*1000L) /* 5 minutes in nanoseconds */
36 :
37 : /* Monotonic deque element for sliding-window max tracking of
38 : EMA-smoothed network throughput. */
39 : struct fd_gui_rate_entry {
40 : long ts_nanos;
41 : double value;
42 : };
43 : typedef struct fd_gui_rate_entry fd_gui_rate_entry_t;
44 :
45 : /* At 100 ms sampling, 5 minutes = 3000 samples. */
46 : #define DEQUE_NAME fd_gui_rate_deque
47 0 : #define DEQUE_T fd_gui_rate_entry_t
48 0 : #define DEQUE_MAX 4096UL
49 : #include "../../util/tmpl/fd_deque.c"
50 :
51 : /* ---- History Buffers ------------------------------------------------ */
52 :
53 0 : #define FD_GUI_TPS_HISTORY_WINDOW_DURATION_SECONDS (10L)
54 0 : #define FD_GUI_TPS_HISTORY_SAMPLE_CNT (300UL)
55 :
56 0 : #define FD_GUI_PROGCACHE_HISTORY_CNT (600UL) /* 60s / 100ms */
57 :
58 0 : #define FD_GUI_TURBINE_RECV_TIMESTAMPS (750UL)
59 :
60 : /* One use case for tracking ingress shred slot is to estimate when we
61 : have caught up to the tip of the blockchain. A naive approach would
62 : be to track the maximum seen slot.
63 :
64 : maximum_seen_slot = fd_ulong_max( maximum_seen_slot, new_slot_from_shred_tile );
65 :
66 : Unfortunately, this doesn't always work because a validator can send
67 : a slot number that is arbitrarily large on a false fork. Also, these
68 : shreds can be for a repair response, which can be arbitrarily small.
69 :
70 : The prospects here seem bleak, but not all hope is lost! We know
71 : that for a sufficiently large historical time window there is a high
72 : probability that at least some of the slots we observe will be valid
73 : recent turbine slots. For a sufficiently small window there is a high
74 : probability that all the observed shred slots are non-malicious (i.e.
75 : not arbitrarily large).
76 :
77 : In practice shred slots are almost always non-malicious. We can keep
78 : a history of the 12 largest slots we've seen in the past 4.8 seconds.
79 : We'll consider the "tip" of the blockchain to be the maximum slot in
80 : our history. This way, if we receive maliciously large slot number,
81 : it will be evicted after 4.8 seconds. If we receive a small slot from
82 : a repair response it will be ignored because we've seen other larger
83 : slots, meaning that our estimate is eventually consistent. For
84 : monitoring purposes this is sufficient.
85 :
86 : The worst case scenario is that this validator receives an incorrect
87 : shred slot slot more than once every 3 leader rotations. Before the
88 : previous incorrect slot is evicted from the history, a new one takes
89 : it's place and we wouldn't never get a correct estimate of the tip of
90 : the chain. We also would indefinitely think that that we haven't
91 : caught up. This would require the chain having perpetually malicious
92 : leaders with adjacent rotations. If this happens, Solana has bigger
93 : problems. */
94 0 : #define FD_GUI_TURBINE_SLOT_HISTORY_SZ ( 12UL )
95 :
96 : /* Like the turbine slot, the latest repair slot can also swing to
97 : arbitrarily large values due to a malicious fork switch. The gui
98 : provides the same guarantees for freshness and accuracy. This
99 : history is somewhat larger to handle the increased repair bandwidth
100 : during catch up. */
101 0 : #define FD_GUI_REPAIR_SLOT_HISTORY_SZ ( 512UL )
102 :
103 : /* FD_GUI_*_CATCH_UP_HISTORY_SZ is the capacity of the record of slots
104 : seen from repair or turbine during the catch up stage at startup.
105 : These buffers are run-length encoded, so they will typically be very
106 : small. The worst-case scenario is unbounded, so bounds here are
107 : determined heuristically. */
108 0 : #define FD_GUI_REPAIR_CATCH_UP_HISTORY_SZ (4096UL)
109 0 : #define FD_GUI_TURBINE_CATCH_UP_HISTORY_SZ (4096UL)
110 :
111 0 : #define FD_GUI_SLOT_RANKINGS_SZ (100UL)
112 0 : #define FD_GUI_SLOT_RANKING_TYPE_ASC (0)
113 0 : #define FD_GUI_SLOT_RANKING_TYPE_DESC (1)
114 :
115 : /* ---- Boot ----------------------------------------------------------- */
116 :
117 0 : #define FD_GUI_BOOT_PROGRESS_TYPE_JOINING_GOSSIP (1)
118 0 : #define FD_GUI_BOOT_PROGRESS_TYPE_LOADING_FULL_SNAPSHOT (2)
119 0 : #define FD_GUI_BOOT_PROGRESS_TYPE_LOADING_INCREMENTAL_SNAPSHOT (3)
120 0 : #define FD_GUI_BOOT_PROGRESS_TYPE_WAITING_FOR_SUPERMAJORITY (4)
121 0 : #define FD_GUI_BOOT_PROGRESS_TYPE_CATCHING_UP (5)
122 0 : #define FD_GUI_BOOT_PROGRESS_TYPE_RUNNING (6)
123 :
124 0 : #define FD_GUI_BOOT_PROGRESS_FULL_SNAPSHOT_IDX (0UL)
125 0 : #define FD_GUI_BOOT_PROGRESS_INCREMENTAL_SNAPSHOT_IDX (1UL)
126 0 : #define FD_GUI_BOOT_PROGRESS_SNAPSHOT_CNT (2UL)
127 :
128 0 : #define FD_GUI_SLOT_LEVEL_INCOMPLETE (0)
129 0 : #define FD_GUI_SLOT_LEVEL_COMPLETED (1)
130 0 : #define FD_GUI_SLOT_LEVEL_OPTIMISTICALLY_CONFIRMED (2) /* "notarized" under Alpenglow */
131 0 : #define FD_GUI_SLOT_LEVEL_ROOTED (3)
132 0 : #define FD_GUI_SLOT_LEVEL_FINALIZED (4)
133 :
134 : /* Strength of the notarization and finalization proofs known for a
135 : block.*/
136 :
137 0 : #define FD_GUI_AG_NOTAR_NONE ((uchar)0)
138 0 : #define FD_GUI_AG_NOTAR_FALLBACK ((uchar)1)
139 0 : #define FD_GUI_AG_NOTAR_REGULAR ((uchar)2) /* supersedes fallback */
140 :
141 0 : #define FD_GUI_AG_FINAL_NONE ((uchar)0)
142 0 : #define FD_GUI_AG_FINAL_IMPLICIT ((uchar)1) /* an ancestor of a finalized block */
143 0 : #define FD_GUI_AG_FINAL_SLOW ((uchar)2) /* supersedes implicit */
144 0 : #define FD_GUI_AG_FINAL_FAST ((uchar)3) /* supersedes slow */
145 :
146 : /* Whether our ordinary notarize or skip vote for a slot made it into
147 : the reward certificate. */
148 :
149 0 : #define FD_GUI_VOTE_REWARDED_UNKNOWN ((uchar)0)
150 0 : #define FD_GUI_VOTE_REWARDED_YES ((uchar)1)
151 0 : #define FD_GUI_VOTE_REWARDED_NO ((uchar)2)
152 :
153 0 : #define FD_GUI_IS_VOTER_UNKNOWN ((uchar)0)
154 0 : #define FD_GUI_IS_VOTER_YES ((uchar)1)
155 0 : #define FD_GUI_IS_VOTER_NO ((uchar)2)
156 :
157 : /* Under Tower, NOTARIZED isn't used and FINALIZED is a catch-all for
158 : skipped. UNKNOWN is used while fork choice has not resolved a
159 : replayed slot. */
160 0 : #define FD_GUI_SKIP_STATUS_UNKNOWN ((uchar)0)
161 0 : #define FD_GUI_SKIP_STATUS_NOT_SKIPPED ((uchar)1)
162 0 : #define FD_GUI_SKIP_STATUS_NOTARIZED ((uchar)2)
163 0 : #define FD_GUI_SKIP_STATUS_FINALIZED ((uchar)3)
164 :
165 0 : #define FD_GUI_TXN_FLAGS_STARTED ( 1U)
166 0 : #define FD_GUI_TXN_FLAGS_ENDED ( 2U)
167 0 : #define FD_GUI_TXN_FLAGS_IS_SIMPLE_VOTE ( 4U)
168 0 : #define FD_GUI_TXN_FLAGS_FROM_BUNDLE ( 8U)
169 0 : #define FD_GUI_TXN_FLAGS_LANDED_IN_BLOCK (16U)
170 :
171 0 : #define FD_GUI_VOTE_STATE_NON_VOTING (0)
172 0 : #define FD_GUI_VOTE_STATE_VOTING (1)
173 0 : #define FD_GUI_VOTE_STATE_DELINQUENT (2)
174 :
175 : #define FD_GUI_MAX_VOTE_DISTANCE (512UL)
176 :
177 : #define FD_GUI_LANDED_VOTE_MAX (4096UL)
178 :
179 0 : #define FD_GUI_HTTP_MIN_SEND_BUFFER_SZ (256UL<<20)
180 :
181 0 : #define FD_GUI_TIMELINE_STORED_GRANULARITY_CNT (7UL)
182 0 : #define FD_GUI_TIMELINE_GRANULARITY_CNT (20UL)
183 : #define FD_GUI_TIMELINE_QUERY_SHRED_MAX (262144UL)
184 : /* At least one bucket per pixel on a 4K display, and enough to cover
185 : the widest raw txn_batch view (1.5h) at 2s in a single request. */
186 : #define FD_GUI_TIMELINE_QUERY_MAX_BUCKETS (4096UL)
187 :
188 : struct fd_gui_timeline_granularity {
189 : char const * name;
190 : ulong stored_idx; /* stored granularity the buckets are built from */
191 : ulong merge_cnt; /* stored buckets merged into one bucket */
192 : };
193 : typedef struct fd_gui_timeline_granularity fd_gui_timeline_granularity_t;
194 : extern fd_gui_timeline_granularity_t const fd_gui_timeline_granularities[ FD_GUI_TIMELINE_GRANULARITY_CNT ];
195 : extern ulong const fd_gui_timeline_stored_granularity_ns[ FD_GUI_TIMELINE_STORED_GRANULARITY_CNT ];
196 :
197 : /* fd_gui_timeline_granularity_ns returns the bucket duration of
198 : granularity g in nanoseconds. */
199 :
200 : static inline ulong
201 0 : fd_gui_timeline_granularity_ns( ulong g ) {
202 0 : fd_gui_timeline_granularity_t const * desc = &fd_gui_timeline_granularities[ g ];
203 0 : return fd_gui_timeline_stored_granularity_ns[ desc->stored_idx ]*desc->merge_cnt;
204 0 : }
205 :
206 : /* Stored timeline-day bucket layout. */
207 0 : #define FD_GUI_TIMELINE_DAY_NS (86400000000000L)
208 :
209 0 : #define FD_GUI_TIMELINE_GRANULARITY_250MS (0)
210 0 : #define FD_GUI_TIMELINE_GRANULARITY_2S (1)
211 0 : #define FD_GUI_TIMELINE_GRANULARITY_15S (2)
212 0 : #define FD_GUI_TIMELINE_GRANULARITY_2M (3)
213 0 : #define FD_GUI_TIMELINE_GRANULARITY_15M (4)
214 0 : #define FD_GUI_TIMELINE_GRANULARITY_2H (5)
215 0 : #define FD_GUI_TIMELINE_GRANULARITY_12H (6)
216 :
217 0 : #define FD_GUI_TIMELINE_250MS_CNT (FD_GUI_TIMELINE_DAY_NS / ( 250L*1000L*1000L))
218 0 : #define FD_GUI_TIMELINE_2S_CNT (FD_GUI_TIMELINE_DAY_NS / ( 2L*1000L*1000L*1000L))
219 0 : #define FD_GUI_TIMELINE_15S_CNT (FD_GUI_TIMELINE_DAY_NS / ( 15L*1000L*1000L*1000L))
220 0 : #define FD_GUI_TIMELINE_2M_CNT (FD_GUI_TIMELINE_DAY_NS / ( 2L*60L*1000L*1000L*1000L))
221 0 : #define FD_GUI_TIMELINE_15M_CNT (FD_GUI_TIMELINE_DAY_NS / ( 15L*60L*1000L*1000L*1000L))
222 0 : #define FD_GUI_TIMELINE_2H_CNT (FD_GUI_TIMELINE_DAY_NS / ( 2L*60L*60L*1000L*1000L*1000L))
223 0 : #define FD_GUI_TIMELINE_12H_CNT (FD_GUI_TIMELINE_DAY_NS / (12L*60L*60L*1000L*1000L*1000L))
224 :
225 : #define FD_GUI_TIMELINE_FIELD_MAP( X ) \
226 : /* field 250ms 2s 15s 2m 15m 2h 12h */ \
227 0 : X( START_SLOT, start_slot, ulong, ulong, ulong, ulong, ulong, ulong, ulong ) \
228 0 : X( END_SLOT, end_slot, ulong, ulong, ulong, ulong, ulong, ulong, ulong ) \
229 0 : X( TURBINE, turbine, uint, uint, uint, uint, ulong, ulong, ulong ) \
230 0 : X( REPAIR, repair, uint, uint, uint, uint, ulong, ulong, ulong ) \
231 0 : X( RECONSTRUCTED, reconstructed, uint, uint, uint, uint, ulong, ulong, ulong ) \
232 0 : X( PUBLISHED, published, uint, uint, uint, uint, uint, ulong, ulong ) \
233 0 : X( COMPUTE_UNITS, compute_units, uint, uint, ulong, ulong, ulong, ulong, ulong ) \
234 0 : X( MAX_COMPUTE, max_compute, uint, uint, uint, uint, uint, uint, uint ) \
235 0 : X( TXN_FEES, txn_fees, ulong, ulong, ulong, ulong, ulong, ulong, ulong ) \
236 0 : X( PRIO_FEES, prio_fees, ulong, ulong, ulong, ulong, ulong, ulong, ulong ) \
237 0 : X( TIPS, tips, ulong, ulong, ulong, ulong, ulong, ulong, ulong ) \
238 0 : X( NONVOTE_SUCCESS, nonvote_success, uint, uint, uint, uint, uint, ulong, ulong ) \
239 0 : X( NONVOTE_FAILED, nonvote_failed, uint, uint, uint, uint, uint, ulong, ulong ) \
240 0 : X( VOTE_SUCCESS, vote_success, uint, uint, uint, uint, uint, ulong, ulong ) \
241 0 : X( VOTE_FAILED, vote_failed, uint, uint, uint, uint, uint, ulong, ulong ) \
242 0 : X( SKIPPED, skipped, ushort, ushort, ushort, ushort, ushort, ushort, uint )
243 :
244 : #define FD_GUI_TIMELINE_FIELD_ENUM( id, name, t0, t1, t2, t3, t4, t5, t6 ) FD_GUI_TIMELINE_FIELD_##id,
245 : enum fd_gui_timeline_field {
246 : FD_GUI_TIMELINE_FIELD_MAP( FD_GUI_TIMELINE_FIELD_ENUM )
247 : FD_GUI_TIMELINE_FIELD_CNT
248 : };
249 : #undef FD_GUI_TIMELINE_FIELD_ENUM
250 :
251 : #define FD_GUI_TIMELINE_DECL_250MS(id, name, t0, t1, t2, t3, t4, t5, t6) t0 name[ FD_GUI_TIMELINE_250MS_CNT ];
252 : #define FD_GUI_TIMELINE_DECL_2S(id, name, t0, t1, t2, t3, t4, t5, t6) t1 name[ FD_GUI_TIMELINE_2S_CNT ];
253 : #define FD_GUI_TIMELINE_DECL_15S(id, name, t0, t1, t2, t3, t4, t5, t6) t2 name[ FD_GUI_TIMELINE_15S_CNT ];
254 : #define FD_GUI_TIMELINE_DECL_2M(id, name, t0, t1, t2, t3, t4, t5, t6) t3 name[ FD_GUI_TIMELINE_2M_CNT ];
255 : #define FD_GUI_TIMELINE_DECL_15M(id, name, t0, t1, t2, t3, t4, t5, t6) t4 name[ FD_GUI_TIMELINE_15M_CNT ];
256 : #define FD_GUI_TIMELINE_DECL_2H(id, name, t0, t1, t2, t3, t4, t5, t6) t5 name[ FD_GUI_TIMELINE_2H_CNT ];
257 : #define FD_GUI_TIMELINE_DECL_12H(id, name, t0, t1, t2, t3, t4, t5, t6) t6 name[ FD_GUI_TIMELINE_12H_CNT ];
258 :
259 : typedef struct fd_gui_timeline_250ms { FD_GUI_TIMELINE_FIELD_MAP( FD_GUI_TIMELINE_DECL_250MS ) } fd_gui_timeline_250ms_t;
260 : typedef struct fd_gui_timeline_2s { FD_GUI_TIMELINE_FIELD_MAP( FD_GUI_TIMELINE_DECL_2S ) } fd_gui_timeline_2s_t;
261 : typedef struct fd_gui_timeline_15s { FD_GUI_TIMELINE_FIELD_MAP( FD_GUI_TIMELINE_DECL_15S ) } fd_gui_timeline_15s_t;
262 : typedef struct fd_gui_timeline_2m { FD_GUI_TIMELINE_FIELD_MAP( FD_GUI_TIMELINE_DECL_2M ) } fd_gui_timeline_2m_t;
263 : typedef struct fd_gui_timeline_15m { FD_GUI_TIMELINE_FIELD_MAP( FD_GUI_TIMELINE_DECL_15M ) } fd_gui_timeline_15m_t;
264 : typedef struct fd_gui_timeline_2h { FD_GUI_TIMELINE_FIELD_MAP( FD_GUI_TIMELINE_DECL_2H ) } fd_gui_timeline_2h_t;
265 : typedef struct fd_gui_timeline_12h { FD_GUI_TIMELINE_FIELD_MAP( FD_GUI_TIMELINE_DECL_12H ) } fd_gui_timeline_12h_t;
266 :
267 : #undef FD_GUI_TIMELINE_DECL_250MS
268 : #undef FD_GUI_TIMELINE_DECL_2S
269 : #undef FD_GUI_TIMELINE_DECL_15S
270 : #undef FD_GUI_TIMELINE_DECL_2M
271 : #undef FD_GUI_TIMELINE_DECL_15M
272 : #undef FD_GUI_TIMELINE_DECL_2H
273 : #undef FD_GUI_TIMELINE_DECL_12H
274 :
275 : struct fd_gui_timeline_day {
276 : long insert_time_ns;
277 : long end_time_ns; /* exclusive UTC day end (the following midnight) */
278 : fd_gui_timeline_250ms_t bucket_250ms;
279 : fd_gui_timeline_2s_t bucket_2s;
280 : fd_gui_timeline_15s_t bucket_15s;
281 : fd_gui_timeline_2m_t bucket_2m;
282 : fd_gui_timeline_15m_t bucket_15m;
283 : fd_gui_timeline_2h_t bucket_2h;
284 : fd_gui_timeline_12h_t bucket_12h;
285 : };
286 : typedef struct fd_gui_timeline_day fd_gui_timeline_day_t;
287 :
288 : static inline ulong
289 0 : fd_gui_timeline_bucket_cnt( int granularity ) {
290 0 : switch( granularity ) {
291 0 : case FD_GUI_TIMELINE_GRANULARITY_250MS: return FD_GUI_TIMELINE_250MS_CNT;
292 0 : case FD_GUI_TIMELINE_GRANULARITY_2S: return FD_GUI_TIMELINE_2S_CNT;
293 0 : case FD_GUI_TIMELINE_GRANULARITY_15S: return FD_GUI_TIMELINE_15S_CNT;
294 0 : case FD_GUI_TIMELINE_GRANULARITY_2M: return FD_GUI_TIMELINE_2M_CNT;
295 0 : case FD_GUI_TIMELINE_GRANULARITY_15M: return FD_GUI_TIMELINE_15M_CNT;
296 0 : case FD_GUI_TIMELINE_GRANULARITY_2H: return FD_GUI_TIMELINE_2H_CNT;
297 0 : case FD_GUI_TIMELINE_GRANULARITY_12H: return FD_GUI_TIMELINE_12H_CNT;
298 0 : default: FD_LOG_ERR(( "invalid timeline granularity %d", granularity ));
299 0 : }
300 0 : }
301 :
302 : static inline void *
303 : fd_gui_timeline_field_ptr( fd_gui_timeline_day_t * day,
304 : int granularity,
305 : int field,
306 : ulong idx,
307 0 : ulong * sz ) {
308 0 : if( FD_UNLIKELY( !day ) ) FD_LOG_ERR(( "NULL timeline day" ));
309 0 : ulong bucket_cnt = fd_gui_timeline_bucket_cnt( granularity );
310 0 : if( FD_UNLIKELY( idx>=bucket_cnt ) ) FD_LOG_ERR(( "timeline bucket index %lu out of range [0,%lu)", idx, bucket_cnt ));
311 0 : #define FD_GUI_TIMELINE_FIELD_CASE( id, name, t0, t1, t2, t3, t4, t5, t6 ) \
312 0 : case FD_GUI_TIMELINE_FIELD_##id: \
313 0 : switch( granularity ) { \
314 0 : case FD_GUI_TIMELINE_GRANULARITY_250MS: *sz=sizeof(t0); return &day->bucket_250ms.name[ idx ]; \
315 0 : case FD_GUI_TIMELINE_GRANULARITY_2S: *sz=sizeof(t1); return &day->bucket_2s.name [ idx ]; \
316 0 : case FD_GUI_TIMELINE_GRANULARITY_15S: *sz=sizeof(t2); return &day->bucket_15s.name [ idx ]; \
317 0 : case FD_GUI_TIMELINE_GRANULARITY_2M: *sz=sizeof(t3); return &day->bucket_2m.name [ idx ]; \
318 0 : case FD_GUI_TIMELINE_GRANULARITY_15M: *sz=sizeof(t4); return &day->bucket_15m.name [ idx ]; \
319 0 : case FD_GUI_TIMELINE_GRANULARITY_2H: *sz=sizeof(t5); return &day->bucket_2h.name [ idx ]; \
320 0 : case FD_GUI_TIMELINE_GRANULARITY_12H: *sz=sizeof(t6); return &day->bucket_12h.name [ idx ]; \
321 0 : default: FD_LOG_ERR(( "invalid timeline granularity %d", granularity )); \
322 0 : }
323 0 : switch( field ) {
324 0 : FD_GUI_TIMELINE_FIELD_MAP( FD_GUI_TIMELINE_FIELD_CASE )
325 0 : default: FD_LOG_ERR(( "invalid timeline field %d", field ));
326 0 : }
327 0 : #undef FD_GUI_TIMELINE_FIELD_CASE
328 0 : }
329 :
330 : static inline ulong
331 : fd_gui_timeline_field_get( fd_gui_timeline_day_t const * day,
332 : int granularity,
333 : int field,
334 0 : ulong idx ) {
335 0 : ulong sz = 0UL;
336 0 : void const * ptr = fd_gui_timeline_field_ptr( (fd_gui_timeline_day_t *)day, granularity, field, idx, &sz );
337 0 : if( sz==sizeof(ushort) ) { ushort v = *(ushort const *)ptr; return v==USHORT_MAX ? ULONG_MAX : (ulong)v; }
338 0 : if( sz==sizeof(uint ) ) { uint v = *(uint const *)ptr; return v==UINT_MAX ? ULONG_MAX : (ulong)v; }
339 0 : ulong v = *(ulong const *)ptr;
340 0 : return v;
341 0 : }
342 :
343 : static inline void
344 : fd_gui_timeline_field_set( fd_gui_timeline_day_t * day,
345 : int granularity,
346 : int field,
347 : ulong idx,
348 0 : ulong value ) {
349 0 : ulong sz = 0UL;
350 0 : void * ptr = fd_gui_timeline_field_ptr( day, granularity, field, idx, &sz );
351 0 : if( sz==sizeof(ushort) ) { *(ushort *)ptr = value==ULONG_MAX ? USHORT_MAX : (ushort)fd_ulong_min( value, (ulong)USHORT_MAX-1UL ); return; }
352 0 : if( sz==sizeof(uint ) ) { *(uint *)ptr = value==ULONG_MAX ? UINT_MAX : (uint )fd_ulong_min( value, (ulong)UINT_MAX-1UL ); return; }
353 0 : *(ulong *)ptr = value;
354 0 : }
355 :
356 : /* fd_gui_timeline_combine returns the result of folding value into the
357 : accumulator acc for field. */
358 :
359 : static inline ulong
360 : fd_gui_timeline_combine( ulong acc,
361 : ulong value,
362 0 : int field ) {
363 0 : if( value==ULONG_MAX ) return acc;
364 0 : if( acc==ULONG_MAX ) return value;
365 0 : switch( field ) {
366 0 : case FD_GUI_TIMELINE_FIELD_START_SLOT: return fd_ulong_min( acc, value );
367 0 : case FD_GUI_TIMELINE_FIELD_END_SLOT:
368 0 : case FD_GUI_TIMELINE_FIELD_MAX_COMPUTE: return fd_ulong_max( acc, value );
369 0 : default: return fd_ulong_min( ULONG_MAX-1UL, fd_ulong_sat_add( acc, value ) );
370 0 : }
371 0 : }
372 :
373 : /* fd_gui_timeline_field_accum folds value into a stored bucket. */
374 :
375 : static inline void
376 : fd_gui_timeline_field_accum( fd_gui_timeline_day_t * day,
377 : int granularity,
378 : int field,
379 : ulong idx,
380 0 : ulong value ) {
381 0 : ulong acc = fd_gui_timeline_field_get( day, granularity, field, idx );
382 0 : fd_gui_timeline_field_set( day, granularity, field, idx, fd_gui_timeline_combine( acc, value, field ) );
383 0 : }
384 :
385 : struct fd_gui_tile_timers {
386 : long sample_time_nanos; /* wallclock ns this sample was taken; identical across the per-tile records. */
387 : ulong tile_idx; /* global tile index into topo->tiles. */
388 : ulong timers[ FD_METRICS_ENUM_TILE_REGIME_CNT ];
389 : ulong sched_timers[ FD_METRICS_ENUM_THREAD_REGIME_CNT ];
390 :
391 : int in_backp;
392 : ushort last_cpu;
393 : uchar status;
394 : ulong heartbeat;
395 : ulong backp_cnt;
396 : ulong nvcsw;
397 : ulong nivcsw;
398 : ulong minflt;
399 : ulong majflt;
400 : ulong interrupts;
401 : ulong tlb_shootdowns;
402 : ulong timer_ticks;
403 : };
404 :
405 : typedef struct fd_gui_tile_timers fd_gui_tile_timers_t;
406 :
407 0 : #define FD_GUI_EXEC_DONE_SLOT_CNT (16UL)
408 :
409 : struct fd_gui_tile_timers_hist {
410 : long sample_time_nanos;
411 : ushort tile_idx;
412 : uchar alive;
413 : uchar in_backp;
414 : ushort timers[ FD_METRICS_ENUM_TILE_REGIME_CNT ];
415 : ushort sched_timers[ FD_METRICS_ENUM_THREAD_REGIME_CNT ];
416 : ushort idle_ratio;
417 : ushort last_cpu;
418 : ulong backp_msgs;
419 : ulong nvcsw;
420 : ulong nivcsw;
421 : ulong minflt;
422 : ulong majflt;
423 : ulong interrupts;
424 : ulong tlb_shootdowns;
425 : ulong timer_ticks;
426 : };
427 :
428 : typedef struct fd_gui_tile_timers_hist fd_gui_tile_timers_hist_t;
429 :
430 : struct fd_gui_scheduler_counts {
431 : long sample_time_ns;
432 : ulong regular;
433 : ulong votes;
434 : ulong conflicting;
435 : ulong bundles;
436 : };
437 :
438 : typedef struct fd_gui_scheduler_counts fd_gui_scheduler_counts_t;
439 :
440 : struct fd_gui_network_stats {
441 : /* total bytes accumulated */
442 : struct {
443 : ulong turbine;
444 : ulong gossip;
445 : ulong tpu;
446 : ulong repair;
447 : ulong rserve;
448 : ulong metric;
449 : ulong votor;
450 : } in, out;
451 : };
452 :
453 : typedef struct fd_gui_network_stats fd_gui_network_stats_t;
454 :
455 : struct fd_gui_accounts_stats {
456 : /* Raw counters/gauges read from tile metric pages. Stored so we can
457 : compute deltas (and per-second rates) against a previous snapshot. */
458 : long sample_time_nanos;
459 :
460 : /* Disk (gauges from accdb tile). */
461 : ulong accounts_total;
462 : ulong accounts_capacity;
463 : ulong disk_allocated_bytes;
464 : ulong disk_current_bytes;
465 : ulong disk_used_bytes;
466 :
467 : /* Compaction (gauges + counters from accdb tile). */
468 : ulong in_compaction;
469 : ulong compactions_requested;
470 : ulong compactions_completed;
471 : ulong accounts_relocated_bytes;
472 :
473 : /* Cache occupancy (gauges from accdb tile, per class). */
474 : ulong cache_class_used [ FD_ACCDB_CACHE_CLASS_CNT ];
475 : ulong cache_class_max [ FD_ACCDB_CACHE_CLASS_CNT ];
476 : ulong cache_class_reserved [ FD_ACCDB_CACHE_CLASS_CNT ];
477 : /* Preeviction thresholds, expressed as used-slot counts directly
478 : comparable to cache_class_used / cache_class_max. */
479 : ulong cache_class_target_used [ FD_ACCDB_CACHE_CLASS_CNT ];
480 : ulong cache_class_low_water_used [ FD_ACCDB_CACHE_CLASS_CNT ];
481 :
482 : /* Aggregate counters summed across all accdb consumer tiles. */
483 : ulong acquired; /* total acquires */
484 : ulong acquired_writable; /* writable subset of acquires (RW tiles only) */
485 : ulong acquired_per_class [ FD_ACCDB_CACHE_CLASS_CNT ]; /* acquires attributed to a class (RW tiles only) */
486 : ulong acquired_writable_per_class [ FD_ACCDB_CACHE_CLASS_CNT ]; /* writable acquires attributed to a class (RW tiles only) */
487 : ulong not_found_per_class [ FD_ACCDB_CACHE_CLASS_CNT ]; /* misses, per class */
488 : ulong evicted_per_class [ FD_ACCDB_CACHE_CLASS_CNT ];
489 : ulong preevicted_per_class [ FD_ACCDB_CACHE_CLASS_CNT ];
490 : ulong committed_new_per_class [ FD_ACCDB_CACHE_CLASS_CNT ];
491 : ulong committed_overwrite_per_class[ FD_ACCDB_CACHE_CLASS_CNT ];
492 :
493 : /* IO counters. bytes_written/read_ops/write_ops are summed across
494 : all consumer tiles; bytes_written_accdb is the bytes written by
495 : the accdb tile itself (background preevict + compaction), used to
496 : compute the prewrite ratio. */
497 : ulong bytes_read;
498 : ulong bytes_copied;
499 : ulong bytes_written;
500 : ulong bytes_written_accdb;
501 : ulong read_ops;
502 : ulong write_ops;
503 : };
504 :
505 : typedef struct fd_gui_accounts_stats fd_gui_accounts_stats_t;
506 :
507 : struct fd_gui_turbine_slot {
508 : ulong slot;
509 : long timestamp;
510 : };
511 :
512 : typedef struct fd_gui_turbine_slot fd_gui_turbine_slot_t;
513 :
514 : struct __attribute__((packed)) fd_gui_slot {
515 : ulong slot; /* this record's slot number. */
516 : ulong bank_seq; /* this block's fork discriminator (the record's own key bank_seq).*/
517 : ulong parent_bank_seq; /* parent block's fork discriminator (ULONG_MAX if unknown) */
518 : long completed_time; /* slot completion wallclock ns (LONG_MAX if unknown) */
519 : uint shred_cnt; /* slot->shred_cnt at completion */
520 : fd_hash_t block_hash; /* block hash of the slot */
521 : uchar mine:1; /* 1 if this was our leader slot */
522 : uchar is_voter:2; /* one of FD_GUI_IS_VOTER_* */
523 : uint vote_success; /* successful vote txn count (UINT_MAX if unknown) */
524 : uint vote_failed; /* failed vote txn count (UINT_MAX if unknown) */
525 : uint nonvote_success; /* successful nonvote txn count (UINT_MAX if unknown) */
526 : uint nonvote_failed; /* failed nonvote txn count (UINT_MAX if unknown) */
527 : uchar vote_latency_exact; /* our vote latency minus skipped slots on the landed fork, or FD_GUI_VOTE_LATENCY_NOT_VOTED if no vote landed. */
528 : uint max_compute_units;/* block compute unit limit (UINT_MAX if unknown) */
529 : uint compute_units; /* block compute units consumed (UINT_MAX if unknown) */
530 : ulong transaction_fee; /* total transaction fee (ULONG_MAX if unknown) */
531 : ulong priority_fee; /* total priority fee (ULONG_MAX if unknown) */
532 : ulong tips; /* total tips (ULONG_MAX if unknown) */
533 : ulong parent_slot; /* parent (on-fork) slot number (ULONG_MAX if unknown) */
534 : long parent_completed_time; /* parent block completion wallclock ns (LONG_MAX if unknown) */
535 : ulong vote_slot; /* most recent slot we had landed a vote for as of this slot's replay (ULONG_MAX if unknown) */
536 : uchar skip; /* one of FD_GUI_SKIP_STATUS_* */
537 : uchar level; /* one of FD_GUI_SLOT_LEVEL_* */
538 : uchar notarization_kind; /* Alpenglow: one of FD_GUI_AG_NOTAR_* */
539 : uchar finalization_kind; /* Alpenglow: one of FD_GUI_AG_FINAL_* */
540 : uchar vote_rewarded; /* Alpenglow: one of FD_GUI_VOTE_REWARDED_* */
541 : ushort vote_count; /* Alpenglow: distinct signers of the reward certs carried by this (cert) slot, USHORT_MAX if unknown */
542 : };
543 :
544 : typedef struct fd_gui_slot fd_gui_slot_t;
545 :
546 : static inline void
547 : fd_gui_slot_set_voter_state( fd_gui_slot_t * slot,
548 0 : uchar state ) {
549 0 : slot->is_voter = FD_GUI_IS_VOTER_UNKNOWN;
550 0 : if( state==FD_GUI_IS_VOTER_YES ) slot->is_voter = FD_GUI_IS_VOTER_YES;
551 0 : else if( state==FD_GUI_IS_VOTER_NO ) slot->is_voter = FD_GUI_IS_VOTER_NO;
552 0 : }
553 :
554 : /* Alpenglow certificates name a block by (slot, block_id), while slot
555 : records are keyed by (slot, bank_seq). Certificates are buffered as
556 : they arrive before replay completes their block. They are then
557 : eagerly joined by their block_id. The buffer covers max_live_slots
558 : so replay can consume any block retained by the execution pipeline. */
559 :
560 : struct fd_gui_ag_slot {
561 : ulong slot; /* ULONG_MAX when this entry is empty */
562 : fd_hash_t block_id; /* Double merkle root of the block data. { 0 } by default. */
563 : ulong bank_seq; /* ULONG_MAX until this block completes replay */
564 : uchar notarization_kind; /* one of FD_GUI_AG_NOTAR_* */
565 : uchar finalization_kind; /* one of FD_GUI_AG_FINAL_* */
566 : uchar skip; /* one of FD_GUI_SKIP_STATUS_* */
567 : };
568 :
569 : typedef struct fd_gui_ag_slot fd_gui_ag_slot_t;
570 :
571 : struct fd_gui_slot_ranking {
572 : ulong slot;
573 : ulong value;
574 : int type;
575 : };
576 : typedef struct fd_gui_slot_ranking fd_gui_slot_ranking_t;
577 :
578 : struct fd_gui_slot_rankings {
579 : fd_gui_slot_ranking_t largest_tips [ FD_GUI_SLOT_RANKINGS_SZ+1UL ];
580 : fd_gui_slot_ranking_t largest_fees [ FD_GUI_SLOT_RANKINGS_SZ+1UL ];
581 : fd_gui_slot_ranking_t largest_rewards [ FD_GUI_SLOT_RANKINGS_SZ+1UL ];
582 : fd_gui_slot_ranking_t largest_duration [ FD_GUI_SLOT_RANKINGS_SZ+1UL ];
583 : fd_gui_slot_ranking_t largest_compute_units [ FD_GUI_SLOT_RANKINGS_SZ+1UL ];
584 : fd_gui_slot_ranking_t largest_skipped [ FD_GUI_SLOT_RANKINGS_SZ+1UL ];
585 : fd_gui_slot_ranking_t largest_rewards_per_cu [ FD_GUI_SLOT_RANKINGS_SZ+1UL ];
586 : fd_gui_slot_ranking_t smallest_tips [ FD_GUI_SLOT_RANKINGS_SZ+1UL ];
587 : fd_gui_slot_ranking_t smallest_fees [ FD_GUI_SLOT_RANKINGS_SZ+1UL ];
588 : fd_gui_slot_ranking_t smallest_rewards [ FD_GUI_SLOT_RANKINGS_SZ+1UL ];
589 : fd_gui_slot_ranking_t smallest_rewards_per_cu[ FD_GUI_SLOT_RANKINGS_SZ+1UL ];
590 : fd_gui_slot_ranking_t smallest_duration [ FD_GUI_SLOT_RANKINGS_SZ+1UL ];
591 : fd_gui_slot_ranking_t smallest_compute_units [ FD_GUI_SLOT_RANKINGS_SZ+1UL ];
592 : fd_gui_slot_ranking_t smallest_skipped [ FD_GUI_SLOT_RANKINGS_SZ+1UL ];
593 : };
594 :
595 : typedef struct fd_gui_slot_rankings fd_gui_slot_rankings_t;
596 :
597 0 : #define FD_GUI_EPOCH_SCHED_CNT ((MAX_SLOTS_PER_EPOCH+FD_EPOCH_SLOTS_PER_ROTATION-1UL)/FD_EPOCH_SLOTS_PER_ROTATION)
598 : #define FD_GUI_EPOCH_PUB_CNT (MAX_STAKE_WEIGHTS)
599 :
600 0 : #define FD_GUI_VOTE_LATENCY_NOT_VOTED ((uchar)(UCHAR_MAX)) /* vote missing */
601 0 : #define FD_GUI_VOTE_LATENCY_MAX ((uchar)(UCHAR_MAX-1UL)) /* largest observable vote latency */
602 :
603 : struct fd_gui_epoch {
604 : ulong epoch;
605 : ulong start_slot;
606 : ulong slot_cnt; /* end_slot = start_slot + slot_cnt - 1 */
607 : long start_time; /* epoch start wallclock ns (LONG_MAX if unknown) */
608 : long end_time; /* epoch end wallclock ns (LONG_MAX if unknown) */
609 : long target_slot_duration_ns; /* protocol target slot duration */
610 : ulong my_total_slots; /* our leader slots seen this epoch */
611 : ulong my_skipped_slots; /* our skipped leader slots this epoch */
612 : ulong rankings_slot; /* one more than the largest slot processed into rankings */
613 : fd_gui_slot_rankings_t rankings [ 1 ]; /* global slot rankings */
614 : fd_gui_slot_rankings_t my_rankings[ 1 ]; /* my slots only */
615 :
616 : uchar latency_exact[ MAX_SLOTS_PER_EPOCH ]; /* skip-discounted latency or FD_GUI_VOTE_LATENCY_* */
617 : uchar is_voter [ MAX_SLOTS_PER_EPOCH ]; /* Alpenglow: one of FD_GUI_IS_VOTER_* */
618 : uchar skipped [ MAX_SLOTS_PER_EPOCH ]; /* 1 if the slot was skipped on the rooted fork */
619 : uchar vote_rewarded[ MAX_SLOTS_PER_EPOCH ]; /* Alpenglow: one of FD_GUI_VOTE_REWARDED_* */
620 : ushort vote_count [ MAX_SLOTS_PER_EPOCH ]; /* Alpenglow: distinct signers of the reward certs carried by this (cert) slot, USHORT_MAX if unknown */
621 :
622 : fd_epoch_schedule_t epoch_schedule; /* slot<->epoch conversion (fd_slot_to_epoch) */
623 : ulong pub_cnt; /* number of deduped leader pubkeys in pub[] */
624 : ulong stakes_cnt; /* number of entries in stakes[] */
625 : uint sched[ FD_GUI_EPOCH_SCHED_CNT ]; /* rotation -> index into pub[] */
626 : fd_pubkey_t pub[ FD_GUI_EPOCH_PUB_CNT ]; /* deduped leaders */
627 : fd_vote_stake_weight_t stakes[ MAX_STAKE_WEIGHTS ];
628 : };
629 :
630 : typedef struct fd_gui_epoch fd_gui_epoch_t;
631 :
632 : struct fd_gui_ephemeral_slot {
633 : ulong slot; /* ULONG_MAX indicates invalid/evicted */
634 : long timestamp_arrival_nanos;
635 : };
636 : typedef struct fd_gui_ephemeral_slot fd_gui_ephemeral_slot_t;
637 :
638 : struct __attribute__((packed)) fd_gui_store_txn_start {
639 : ulong slot;
640 : ulong bank_seq; /* per-fork bank sequence */
641 : long insert_time_ns;
642 : ulong txn_idx; /* per-(bank_seq) transaction index */
643 : uchar signature[ FD_TXN_SIGNATURE_SZ ];
644 : ulong transaction_fee;
645 : ulong priority_fee;
646 : long timestamp_arrival_nanos;
647 : long microblock_start_ns; /* absolute ns (begin tspub) */
648 : uint compute_units_requested;
649 : uint microblock_idx;
650 : uint source_ipv4;
651 : uchar source_tpu;
652 : uchar flags; /* STARTED | IS_SIMPLE_VOTE | FROM_BUNDLE */
653 : };
654 : typedef struct fd_gui_store_txn_start fd_gui_store_txn_start_t;
655 :
656 : struct __attribute__((packed)) fd_gui_store_txn_end {
657 : ulong slot;
658 : ulong bank_seq; /* per-fork bank sequence */
659 : long insert_time_ns;
660 : ulong txn_idx;
661 : long timestamp_arrival_nanos;
662 : long microblock_end_ns; /* absolute ns (end tspub) */
663 : fd_txn_ns_dt_t txn_ns_dt; /* relative to microblock_start */
664 : ulong tips;
665 : uint compute_units_consumed;
666 : uint bank_idx;
667 : uint error_code;
668 : uchar flags; /* ENDED | LANDED_IN_BLOCK */
669 : };
670 : typedef struct fd_gui_store_txn_end fd_gui_store_txn_end_t;
671 :
672 : struct fd_gui_store_replay_txn {
673 : long insert_time_ns;
674 : long completion_time_ns;
675 : ulong block_compute_unit_limit;
676 : ulong slot;
677 : ulong txn_idx;
678 : ulong txn_exec_idx;
679 : ulong txn_sigverify_exec_idx;
680 : uint txn_start_shred_idx; /* UINT_MAX if unknown; USHORT_MAX is a valid saturated index */
681 : uint txn_end_shred_idx; /* Exclusive end, capped at the block's last shred; UINT_MAX if unknown */
682 : uchar signature[ FD_TXN_SIGNATURE_SZ ];
683 :
684 : long sigverify_start_ns;
685 : long sigverify_end_ns;
686 : long load_start_ns;
687 : long check_start_ns;
688 : long exec_start_ns;
689 : long commit_start_ns;
690 : long commit_end_ns;
691 :
692 : ulong transaction_fee;
693 : ulong priority_fee;
694 : ulong tips;
695 : uint compute_units_requested; /* 0 means unavailable */
696 : uint compute_units_consumed;
697 : uint error_code;
698 : uchar is_committable;
699 : uchar is_fees_only;
700 : uchar is_simple_vote;
701 : };
702 : typedef struct fd_gui_store_replay_txn fd_gui_store_replay_txn_t;
703 :
704 : struct fd_gui_slot_txn_join {
705 : fd_gui_store_txn_start_t const * start;
706 : fd_gui_store_txn_end_t const * end;
707 : };
708 :
709 : typedef struct fd_gui_slot_txn_join fd_gui_slot_txn_join_t;
710 :
711 : struct fd_gui_txn_waterfall {
712 : long sample_time_nanos; /* wallclock ns this sample was taken. */
713 : struct {
714 : ulong quic;
715 : ulong udp;
716 : ulong gossip;
717 : ulong block_engine;
718 : ulong pack_cranked;
719 : } in;
720 :
721 : struct {
722 : ulong net_overrun;
723 : ulong quic_overrun;
724 : ulong quic_frag_drop;
725 : ulong quic_abandoned;
726 : ulong tpu_quic_invalid;
727 : ulong tpu_udp_invalid;
728 : ulong verify_overrun;
729 : ulong verify_parse;
730 : ulong verify_failed;
731 : ulong verify_duplicate;
732 : ulong dedup_duplicate;
733 : ulong resolv_lut_failed;
734 : ulong resolv_expired;
735 : ulong resolv_ancient;
736 : ulong resolv_no_ledger;
737 : ulong resolv_retained;
738 : ulong pack_invalid;
739 : ulong pack_invalid_bundle;
740 : ulong pack_expired;
741 : ulong pack_already_executed;
742 : ulong pack_retained;
743 : ulong pack_leader_slow;
744 : ulong bank_invalid;
745 : ulong bank_nonce_already_advanced;
746 : ulong bank_nonce_advance_failed;
747 : ulong bank_nonce_wrong_blockhash;
748 : ulong block_success;
749 : ulong block_fail;
750 : } out;
751 : };
752 :
753 : typedef struct fd_gui_txn_waterfall fd_gui_txn_waterfall_t;
754 :
755 : struct __attribute__((packed)) fd_gui_leader_slot {
756 : ulong slot; /* this record's slot number (the record's own key). */
757 : ulong bank_seq; /* this block's fork discriminator (the record's own key bank_seq). */
758 : long leader_start_time; /* wallclock ns we became leader */
759 : long leader_end_time; /* wallclock ns we stopped being leader */
760 : long txn_insert_time_min_ns;
761 : long txn_insert_time_max_ns;
762 : fd_hash_t block_hash; /* block hash of the produced block */
763 : ulong max_microblocks; /* initial max microblocks packable into the slot */
764 : ulong max_compute_units; /* block CU limit announced on becoming leader */
765 : uint microblocks_upper_bound; /* final/exact microblock upper bound */
766 : uint begin_microblocks; /* microblocks started (pack -> bank) */
767 : uint end_microblocks; /* microblocks ended (bank -> poh) */
768 : fd_done_packing_t scheduler_stats[ 1 ]; /* pack "done packing" record (limits, usage, results) */
769 : fd_gui_txn_waterfall_t waterfall_reference[ 1 ];
770 : fd_gui_txn_waterfall_t waterfall[ 1 ];
771 : uchar has_waterfall; /* 1 once waterfall has been finalized */
772 : uchar unbecame_leader; /* 1 if we relinquished leadership for this slot */
773 : };
774 :
775 : typedef struct fd_gui_leader_slot fd_gui_leader_slot_t;
776 :
777 : struct fd_gui_tile_stats {
778 : long sample_time_nanos;
779 :
780 : ulong net_in_rx_bytes; /* Number of bytes received by the net or sock tile*/
781 : ulong quic_conn_cnt; /* Number of active QUIC connections */
782 : fd_histf_t bundle_rx_delay_hist; /* Histogram of bundle rx delay */
783 : ulong bundle_rtt_smoothed_nanos; /* RTT (nanoseconds) moving average */
784 : ulong verify_drop_cnt; /* Number of transactions dropped by verify tiles */
785 : ulong verify_total_cnt; /* Number of transactions received by verify tiles */
786 : ulong dedup_drop_cnt; /* Number of transactions dropped by dedup tile */
787 : ulong dedup_total_cnt; /* Number of transactions received by dedup tile */
788 : ulong pack_buffer_cnt; /* Number of buffered transactions in the pack tile */
789 : ulong pack_buffer_capacity; /* Total size of the pack transaction buffer */
790 : ulong bank_txn_exec_cnt; /* Number of transactions processed by the bank tile */
791 : ulong net_out_tx_bytes; /* Number of bytes sent by the net or sock tile */
792 : };
793 :
794 : typedef struct fd_gui_tile_stats fd_gui_tile_stats_t;
795 :
796 :
797 : struct fd_gui_boot_progress {
798 : uchar phase;
799 : ulong boot_target_slot_duration_nanos;
800 : long joining_gossip_time_nanos;
801 : struct {
802 : ulong slot;
803 : uint peer_addr;
804 : ushort peer_port;
805 : ulong total_bytes_compressed;
806 : long reset_time_nanos; /* UNIX nanosecond timestamp */
807 : long sample_time_nanos;
808 : ulong reset_cnt;
809 :
810 : ulong read_bytes_compressed;
811 : char read_path[ PATH_MAX+30UL ]; /* URL or filesystem path. 30 is fd_cstr_nlen( "https://255.255.255.255:12345/", ULONG_MAX ) */
812 :
813 : ulong decompress_bytes_decompressed;
814 : ulong decompress_bytes_compressed;
815 :
816 : ulong insert_bytes_decompressed;
817 : char insert_path[ PATH_MAX ];
818 : ulong insert_accounts_current;
819 : } loading_snapshot[ FD_GUI_BOOT_PROGRESS_SNAPSHOT_CNT ];
820 :
821 : ulong wfs_total_stake;
822 : ulong wfs_connected_stake;
823 : ulong wfs_total_peers;
824 : ulong wfs_connected_peers;
825 : ulong wfs_attempt;
826 :
827 : long catching_up_time_nanos;
828 : ulong catching_up_first_replay_slot;
829 : };
830 :
831 : typedef struct fd_gui_boot_progress fd_gui_boot_progress_t;
832 :
833 : #define FD_GUI_SNAPSV_PENDING_MAX (16384UL)
834 : #define FD_GUI_SNAPSV_PUBLISH_INTERVAL_NANOS ( 50L*1000L*1000L)
835 :
836 : struct fd_gui_snapsv_pending {
837 : fd_snapsv_msg_snap_t msg;
838 : long sample_time_nanos;
839 : int closed;
840 : };
841 :
842 : typedef struct fd_gui_snapsv_pending fd_gui_snapsv_pending_t;
843 :
844 : /* Triangular-weighted moving window over per-snap deltas. Snap
845 : cadence is ~100ms, window is FD_GUI_ACCDB_WIN_SAMPLES samples
846 : (~5s). The output rate for any metric is:
847 :
848 : rate = sum( w[i] * delta[i] ) / sum( w[i] * dt[i] )
849 :
850 : where w[i] is the triangular weight (newest sample heaviest,
851 : oldest weight=1). This is fully caught up to a new rate after
852 : the window length but smoother than a boxcar (no cliff edge as
853 : samples age out). */
854 0 : #define FD_GUI_ACCDB_WIN_SAMPLES 50UL
855 :
856 : /* Per-partition triangular-weighted windows for read/write rates.
857 : Sized to match the accdb partition pool ceiling (8192). Rates
858 : are derived in fd_gui_printf via fd_gui_accdb_weighted_rate. */
859 0 : #define FD_GUI_MAX_PARTITIONS 8192UL
860 :
861 0 : #define FD_GUI_ACCDB_EMA_HALF_LIFE_NS (600L*1000L*1000L*1000L) /* 10 minutes */
862 :
863 : /* Tile kinds. Determines which subset of metrics to read. */
864 0 : #define FD_GUI_ACCDB_TILE_KIND_RW 0 /* execle, execrp, replay, tower */
865 0 : #define FD_GUI_ACCDB_TILE_KIND_RO 1 /* rpc, resolv */
866 0 : #define FD_GUI_ACCDB_TILE_KIND_SNAPIN 2 /* snapin (direct snapshot disk writer) */
867 0 : #define FD_GUI_ACCDB_TILE_KIND_ACCDB 3 /* accdb tile itself (prewrite + compaction writes) */
868 :
869 : /* 60s-history rings for the per-tile sparkline. Each bucket is the
870 : sum of per-snap deltas that fell into that bucket window. When a
871 : bucket interval elapses the per-second rate is formatted once, as
872 : the "%.2f," JSON array element it is emitted as, and appended to a
873 : packed text ring (oldest first, the oldest dropping off the front
874 : once full) so each emit is one copy rather than a printf per sample.
875 : 240 buckets x 250ms = 60 second window. The longest element is the
876 : rate of a wrapped counter, 2^64/0.25 s: 20 digits, ".00" and the
877 : comma. */
878 0 : #define FD_GUI_ACCDB_SPARKLINE_SAMPLES 240UL
879 0 : #define FD_GUI_ACCDB_SPARKLINE_BUCKET_NS 250000000L
880 : #define FD_GUI_ACCDB_SPARKLINE_TEXT_MAX 24UL
881 :
882 : struct fd_gui_accdb_stats {
883 : ulong accdb_win_idx; /* next write index */
884 : ulong accdb_win_count; /* samples filled, capped at FD_GUI_ACCDB_WIN_SAMPLES */
885 : long accdb_win_dt_nanos [ FD_GUI_ACCDB_WIN_SAMPLES ];
886 :
887 : /* Aggregate delta rings (units per snap). */
888 : ulong agg_acquired_win [ FD_GUI_ACCDB_WIN_SAMPLES ];
889 : ulong agg_acquired_writable_win [ FD_GUI_ACCDB_WIN_SAMPLES ];
890 : ulong agg_bytes_read_win [ FD_GUI_ACCDB_WIN_SAMPLES ];
891 : ulong agg_bytes_copied_win [ FD_GUI_ACCDB_WIN_SAMPLES ];
892 : ulong agg_bytes_written_win [ FD_GUI_ACCDB_WIN_SAMPLES ];
893 : ulong agg_bytes_written_accdb_win[FD_GUI_ACCDB_WIN_SAMPLES ];
894 : ulong agg_read_ops_win [ FD_GUI_ACCDB_WIN_SAMPLES ];
895 : ulong agg_write_ops_win [ FD_GUI_ACCDB_WIN_SAMPLES ];
896 : ulong agg_relocated_bytes_win [ FD_GUI_ACCDB_WIN_SAMPLES ];
897 : ulong agg_misses_win [ FD_GUI_ACCDB_WIN_SAMPLES ];
898 :
899 : /* Per-class delta rings (units per snap). */
900 : ulong class_acq_win [ FD_ACCDB_CACHE_CLASS_CNT ][ FD_GUI_ACCDB_WIN_SAMPLES ];
901 : ulong class_acq_wr_win [ FD_ACCDB_CACHE_CLASS_CNT ][ FD_GUI_ACCDB_WIN_SAMPLES ];
902 : ulong class_not_found_win [ FD_ACCDB_CACHE_CLASS_CNT ][ FD_GUI_ACCDB_WIN_SAMPLES ];
903 : ulong class_evicted_win [ FD_ACCDB_CACHE_CLASS_CNT ][ FD_GUI_ACCDB_WIN_SAMPLES ];
904 : ulong class_preevicted_win [ FD_ACCDB_CACHE_CLASS_CNT ][ FD_GUI_ACCDB_WIN_SAMPLES ];
905 : ulong class_commit_new_win [ FD_ACCDB_CACHE_CLASS_CNT ][ FD_GUI_ACCDB_WIN_SAMPLES ];
906 : ulong class_commit_over_win [ FD_ACCDB_CACHE_CLASS_CNT ][ FD_GUI_ACCDB_WIN_SAMPLES ];
907 :
908 : ulong partition_cnt; /* live count from accdb_shmem; <= FD_GUI_MAX_PARTITIONS */
909 : ulong partition_read_ops_win [ FD_GUI_MAX_PARTITIONS ][ FD_GUI_ACCDB_WIN_SAMPLES ];
910 : ulong partition_bytes_read_win [ FD_GUI_MAX_PARTITIONS ][ FD_GUI_ACCDB_WIN_SAMPLES ];
911 : ulong partition_write_ops_win [ FD_GUI_MAX_PARTITIONS ][ FD_GUI_ACCDB_WIN_SAMPLES ];
912 : ulong partition_bytes_written_win[FD_GUI_MAX_PARTITIONS ][ FD_GUI_ACCDB_WIN_SAMPLES ];
913 :
914 : /* Per-partition snapshots (most recent values, for non-rate fields:
915 : offset, layer, write_offset, bytes_freed, ticks, compaction state). */
916 : fd_accdb_shmem_partition_info_t partitions[ FD_GUI_MAX_PARTITIONS ];
917 :
918 : /* Cumulative counters from the previous snap for delta computation. */
919 : ulong partition_prev_read_ops [ FD_GUI_MAX_PARTITIONS ];
920 : ulong partition_prev_bytes_read [ FD_GUI_MAX_PARTITIONS ];
921 : ulong partition_prev_write_ops [ FD_GUI_MAX_PARTITIONS ];
922 : ulong partition_prev_bytes_written[FD_GUI_MAX_PARTITIONS ];
923 :
924 : /* Per-tier (i.e. hot, warm, cold) EMAs (10 min half-life) used to project when the next
925 : compaction will trigger. */
926 : long tier_sample_nanos;
927 : fd_gui_ema_t tier_fill_bps_ema[ FD_ACCDB_COMPACTION_LAYER_CNT ];
928 : fd_gui_ema_t tier_free_bps_ema[ FD_ACCDB_COMPACTION_LAYER_CNT ];
929 : double next_compaction_remaining_secs;
930 : ulong next_compaction_partition_idx;
931 :
932 : /* Per-tile accdb stats. At init we walk the topology and assign a
933 : slot to each tile that uses the account database (execle, execrp,
934 : replay, tower, rpc, resolv, accdb). Each slot keeps cumulative
935 : previous values for delta computation and a triangular-weighted
936 : delta ring (same cadence / weighting as the aggregate rings). */
937 : ulong accdb_tile_cnt;
938 : ushort accdb_tile_topo_idx [ FD_TOPO_MAX_TILES ]; /* index into topo->tiles */
939 : uchar accdb_tile_kind [ FD_TOPO_MAX_TILES ];
940 :
941 : /* Most-recent cumulative values per tile, plus the snapshot from
942 : the previous snap for delta computation. */
943 : ulong tile_cur_acquired [ FD_TOPO_MAX_TILES ];
944 : ulong tile_cur_acquired_writable [ FD_TOPO_MAX_TILES ];
945 : ulong tile_cur_bytes_read [ FD_TOPO_MAX_TILES ];
946 : ulong tile_cur_bytes_copied [ FD_TOPO_MAX_TILES ];
947 : ulong tile_cur_bytes_written [ FD_TOPO_MAX_TILES ];
948 : ulong tile_cur_read_ops [ FD_TOPO_MAX_TILES ];
949 : ulong tile_cur_write_ops [ FD_TOPO_MAX_TILES ];
950 : ulong tile_cur_misses [ FD_TOPO_MAX_TILES ];
951 : ulong tile_cur_evicted [ FD_TOPO_MAX_TILES ];
952 : ulong tile_cur_committed [ FD_TOPO_MAX_TILES ];
953 : ulong tile_cur_acquire_calls [ FD_TOPO_MAX_TILES ];
954 : uchar tile_cur_status [ FD_TOPO_MAX_TILES ]; /* 1=running, 2=shutdown */
955 :
956 : ulong tile_prev_acquired [ FD_TOPO_MAX_TILES ];
957 : ulong tile_prev_acquired_writable[ FD_TOPO_MAX_TILES ];
958 : ulong tile_prev_bytes_read [ FD_TOPO_MAX_TILES ];
959 : ulong tile_prev_bytes_copied [ FD_TOPO_MAX_TILES ];
960 : ulong tile_prev_bytes_written [ FD_TOPO_MAX_TILES ];
961 : ulong tile_prev_read_ops [ FD_TOPO_MAX_TILES ];
962 : ulong tile_prev_write_ops [ FD_TOPO_MAX_TILES ];
963 : ulong tile_prev_misses [ FD_TOPO_MAX_TILES ];
964 : ulong tile_prev_evicted [ FD_TOPO_MAX_TILES ];
965 : ulong tile_prev_committed [ FD_TOPO_MAX_TILES ];
966 : ulong tile_prev_acquire_calls [ FD_TOPO_MAX_TILES ];
967 :
968 : /* Per-tile delta rings. */
969 : ulong tile_acquired_win [ FD_TOPO_MAX_TILES ][ FD_GUI_ACCDB_WIN_SAMPLES ];
970 : ulong tile_acquired_writable_win[ FD_TOPO_MAX_TILES ][ FD_GUI_ACCDB_WIN_SAMPLES ];
971 : ulong tile_bytes_read_win [ FD_TOPO_MAX_TILES ][ FD_GUI_ACCDB_WIN_SAMPLES ];
972 : ulong tile_bytes_copied_win [ FD_TOPO_MAX_TILES ][ FD_GUI_ACCDB_WIN_SAMPLES ];
973 : ulong tile_bytes_written_win [ FD_TOPO_MAX_TILES ][ FD_GUI_ACCDB_WIN_SAMPLES ];
974 : ulong tile_read_ops_win [ FD_TOPO_MAX_TILES ][ FD_GUI_ACCDB_WIN_SAMPLES ];
975 : ulong tile_write_ops_win [ FD_TOPO_MAX_TILES ][ FD_GUI_ACCDB_WIN_SAMPLES ];
976 : ulong tile_misses_win [ FD_TOPO_MAX_TILES ][ FD_GUI_ACCDB_WIN_SAMPLES ];
977 : ulong tile_evicted_win [ FD_TOPO_MAX_TILES ][ FD_GUI_ACCDB_WIN_SAMPLES ];
978 : ulong tile_committed_win [ FD_TOPO_MAX_TILES ][ FD_GUI_ACCDB_WIN_SAMPLES ];
979 : ulong tile_acquire_calls_win [ FD_TOPO_MAX_TILES ][ FD_GUI_ACCDB_WIN_SAMPLES ];
980 :
981 : long tile_sparkline_bucket_start_nanos [ FD_TOPO_MAX_TILES ];
982 : ulong tile_sparkline_acq_bucket [ FD_TOPO_MAX_TILES ];
983 : ulong tile_sparkline_acq_wr_bucket [ FD_TOPO_MAX_TILES ];
984 : /* Per-second rates of the last N completed buckets as the JSON array
985 : body they are emitted as: the "%.2f," elements concatenated oldest
986 : first in text[ 0, text_len ). Elements contain no comma but the
987 : one that ends them, so the boundaries are the commas. */
988 : char tile_sparkline_acq_text [ FD_TOPO_MAX_TILES ][ FD_GUI_ACCDB_SPARKLINE_SAMPLES*FD_GUI_ACCDB_SPARKLINE_TEXT_MAX ];
989 : char tile_sparkline_acq_wr_text [ FD_TOPO_MAX_TILES ][ FD_GUI_ACCDB_SPARKLINE_SAMPLES*FD_GUI_ACCDB_SPARKLINE_TEXT_MAX ];
990 : ulong tile_sparkline_acq_text_len [ FD_TOPO_MAX_TILES ];
991 : ulong tile_sparkline_acq_wr_text_len [ FD_TOPO_MAX_TILES ];
992 : ulong tile_sparkline_count [ FD_TOPO_MAX_TILES ]; /* completed buckets, capped at FD_GUI_ACCDB_SPARKLINE_SAMPLES */
993 : };
994 :
995 : typedef struct fd_gui_accdb_stats fd_gui_accdb_stats_t;
996 :
997 : /* fd_gui_summary_t holds the aggregate node-level state rendered by the
998 : "summary" websocket topic and the terminal dashboard. */
999 :
1000 : struct fd_gui_summary {
1001 : fd_pubkey_t identity_key[ 1 ];
1002 : int has_vote_key;
1003 : fd_pubkey_t vote_key[ 1 ];
1004 : char vote_key_base58[ FD_BASE58_ENCODED_32_SZ ];
1005 : char identity_key_base58[ FD_BASE58_ENCODED_32_SZ ];
1006 :
1007 : int is_alpenglow;
1008 : int is_voting; /* configured to vote (vote account set) */
1009 : char const * version;
1010 : char const * cluster;
1011 : char accounts_database_path[ PATH_MAX ];
1012 : char gui_database_path [ PATH_MAX ];
1013 :
1014 : char wfs_bank_hash[ FD_BASE58_ENCODED_32_SZ ];
1015 : ulong wfs_slot;
1016 : ushort expected_shred_version;
1017 : int wfs_enabled;
1018 :
1019 : ulong vote_distance;
1020 : int vote_state;
1021 :
1022 : long startup_time_nanos;
1023 :
1024 : fd_gui_boot_progress_t boot_progress;
1025 : fd_gui_boot_progress_t prev_boot_progress;
1026 :
1027 : int schedule_strategy;
1028 :
1029 : ulong identity_account_balance;
1030 : ulong vote_account_balance;
1031 : ushort vote_commission;
1032 : ulong estimated_slot_duration_nanos;
1033 :
1034 : ulong sock_tile_cnt;
1035 : ulong mlx5_tile_cnt;
1036 : ulong net_tile_cnt;
1037 : ulong quic_tile_cnt;
1038 : ulong verify_tile_cnt;
1039 : ulong resolv_tile_cnt;
1040 : ulong execle_tile_cnt;
1041 : ulong execrp_tile_cnt;
1042 : ulong shred_tile_cnt;
1043 :
1044 : ulong slot_rooted;
1045 : ulong slot_finalized;
1046 : ulong slot_notarized;
1047 : ulong slot_optimistically_confirmed;
1048 : ulong slot_estimated;
1049 : ulong slot_caught_up;
1050 : ulong slot_repair;
1051 : ulong slot_turbine;
1052 : ulong slot_reset;
1053 : ulong slot_storage;
1054 : ulong slot_tower;
1055 : ulong slot_tower_bank_seq; /* tracks canonical fork frontier */
1056 : ulong slot_voted;
1057 : ulong active_fork_cnt;
1058 :
1059 : struct {
1060 : ulong epoch;
1061 : ulong skipped;
1062 : ulong total;
1063 : } skip_rate[ 2 ];
1064 :
1065 : fd_gui_ephemeral_slot_t slots_max_turbine[ FD_GUI_TURBINE_SLOT_HISTORY_SZ+1UL ];
1066 : fd_gui_ephemeral_slot_t slots_max_repair [ FD_GUI_REPAIR_SLOT_HISTORY_SZ +1UL ];
1067 :
1068 : /* catchup_* is run-length encoded. i.e. adjacent pairs represent
1069 : contiguous runs */
1070 : ulong catch_up_turbine[ FD_GUI_TURBINE_CATCH_UP_HISTORY_SZ ];
1071 : ulong catch_up_turbine_sz;
1072 :
1073 : ulong catch_up_repair[ FD_GUI_REPAIR_CATCH_UP_HISTORY_SZ ];
1074 : ulong catch_up_repair_sz;
1075 :
1076 : ulong estimated_tps_history_idx;
1077 : struct {
1078 : ulong vote_failed;
1079 : ulong vote_success;
1080 : ulong nonvote_success;
1081 : ulong nonvote_failed;
1082 : } estimated_tps_history[ FD_GUI_TPS_HISTORY_SAMPLE_CNT ];
1083 :
1084 : fd_gui_network_stats_t network_stats_current[ 1 ];
1085 : fd_gui_network_stats_t network_stats_prev [ 1 ];
1086 : int network_stats_has_prev;
1087 :
1088 : /* EMA-smoothed network throughput (bytes/sec) with a 1-second
1089 : half-life. */
1090 : fd_gui_ema_t ingress_ema[ FD_GUI_NET_PROTO_CNT ];
1091 : fd_gui_ema_t egress_ema[ FD_GUI_NET_PROTO_CNT ];
1092 : long net_rate_prev_ts;
1093 : fd_gui_rate_entry_t * ingress_maxq;
1094 : fd_gui_rate_entry_t * egress_maxq;
1095 :
1096 : fd_gui_accdb_stats_t accdb[ 1 ];
1097 :
1098 : fd_gui_accounts_stats_t accounts_stats_reference[ 1 ];
1099 : fd_gui_accounts_stats_t accounts_stats_current [ 1 ];
1100 : int accounts_stats_have_reference;
1101 :
1102 : fd_gui_txn_waterfall_t txn_waterfall_reference[ 1 ];
1103 : fd_gui_txn_waterfall_t txn_waterfall_current [ 1 ];
1104 :
1105 : fd_gui_tile_stats_t tile_stats_reference[ 1 ];
1106 : fd_gui_tile_stats_t tile_stats_current [ 1 ];
1107 :
1108 : ulong progcache_history_idx;
1109 : ulong progcache_hits_history [ FD_GUI_PROGCACHE_HISTORY_CNT ];
1110 : ulong progcache_lookups_history[ FD_GUI_PROGCACHE_HISTORY_CNT ];
1111 : ulong progcache_hits_1min;
1112 : ulong progcache_lookups_1min;
1113 :
1114 : fd_gui_tile_timers_t tile_timers_reference[ FD_TOPO_MAX_TILES ];
1115 : fd_gui_tile_timers_t tile_timers_current [ FD_TOPO_MAX_TILES ];
1116 : fd_gui_tile_timers_hist_t tile_timers_packed [ FD_TOPO_MAX_TILES ];
1117 :
1118 : /* Topo tile indices in display order, built once on init. */
1119 : ulong tile[ FD_TOPO_MAX_TILES ];
1120 : ulong tile_cnt;
1121 : };
1122 :
1123 : typedef struct fd_gui_summary fd_gui_summary_t;
1124 :
1125 : struct fd_gui_timeline_query_bucket {
1126 : ulong fields[ FD_GUI_TIMELINE_FIELD_CNT ];
1127 : };
1128 : typedef struct fd_gui_timeline_query_bucket fd_gui_timeline_query_bucket_t;
1129 :
1130 : /* Workspace for timeline queries. The GUI tile is single threaded and
1131 : each user runs to completion, so the members alias. */
1132 :
1133 : union fd_gui_timeline_scratch {
1134 : fd_gui_shred_event_t events[ FD_GUI_TIMELINE_QUERY_SHRED_MAX ];
1135 : fd_gui_timeline_query_bucket_t buckets[ FD_GUI_TIMELINE_QUERY_MAX_BUCKETS ];
1136 : };
1137 : typedef union fd_gui_timeline_scratch fd_gui_timeline_scratch_t;
1138 :
1139 : struct fd_gui {
1140 : fd_gui_timeline_scratch_t timeline_scratch;
1141 :
1142 : fd_http_server_t * http;
1143 : fd_topo_t const * topo;
1144 : fd_accdb_shmem_t const * accdb_shmem;
1145 :
1146 : void * db; /* GUI database */
1147 : void * hist;
1148 :
1149 : double tick_per_ns;
1150 : ulong tile_cnt;
1151 :
1152 : long next_sample_1sec;
1153 : long next_sample_200millis;
1154 : long next_sample_100millis;
1155 : long next_sample_50millis;
1156 : long next_sample_40millis;
1157 : long next_sample_25millis;
1158 : long next_sample_10millis;
1159 :
1160 : ulong leader_begin_slot;
1161 : ulong leader_end_slot;
1162 : ulong leader_slot_pending;
1163 : ulong leader_bank_seq_pending;
1164 :
1165 : fd_gui_summary_t summary;
1166 :
1167 : struct {
1168 : int valid;
1169 : fd_diag_system_resources_t resources;
1170 : } system;
1171 :
1172 : /* Scratch record for the synthesized skipped slots returned by
1173 : fd_gui_slot_get_canon_safe. */
1174 : fd_gui_slot_t skipped_scratch[ 1 ];
1175 :
1176 : struct {
1177 : ulong slot_cnt;
1178 : fd_gui_ag_slot_t * slot; /* [slot_cnt] */
1179 : } ag;
1180 :
1181 : /* used for estimating slot duration */
1182 : fd_gui_turbine_slot_t turbine_slots[ FD_GUI_TURBINE_RECV_TIMESTAMPS ];
1183 :
1184 : /* Reusable scratch for reassembling a single slot's transactions at
1185 : query time (fd_gui_printf_slot_transactions_request). */
1186 : struct {
1187 : ulong max;
1188 : fd_gui_store_txn_start_t * starts; /* [max] */
1189 : fd_gui_store_txn_end_t * ends; /* [max] */
1190 : fd_gui_slot_txn_join_t * joined; /* [max] */
1191 : } slot_txn_scratch;
1192 :
1193 : struct {
1194 : ulong landed_slot;
1195 : ulong landed_bank_seq;
1196 : ulong voted_slot;
1197 : } landed_votes[ FD_GUI_LANDED_VOTE_MAX ];
1198 : ulong landed_vote_cnt;
1199 :
1200 : struct {
1201 : int has_block_engine;
1202 : char name[ 16 ];
1203 : char url[ FD_URL_MAX ];
1204 : char ip_cstr[ 40 ]; /* IPv4 or IPv6 cstr */
1205 : int status;
1206 : } block_engine;
1207 :
1208 : struct {
1209 : /* The epoch we are currently in, advanced at epoch_info ingest. */
1210 : ulong current_epoch;
1211 :
1212 : int has_epoch_schedule;
1213 : fd_epoch_schedule_t epoch_schedule;
1214 :
1215 : ulong missed_vote_history_dirty; /* epoch whose missed vote history changed since the last broadcast, ULONG_MAX if none */
1216 :
1217 : uchar __attribute__((aligned(FD_EPOCH_LEADERS_ALIGN))) lsched_scratch[ FD_EPOCH_LEADERS_FOOTPRINT(MAX_STAKE_WEIGHTS, MAX_SLOTS_PER_EPOCH) ];
1218 : fd_vote_stake_weight_t stakes_scratch[ MAX_STAKE_WEIGHTS ];
1219 : } epoch;
1220 :
1221 : fd_gui_peers_ctx_t * peers; /* full-client */
1222 :
1223 : /* One decoded shred event for fd_gui_printf_shreds_window, which
1224 : prints a window's events column by column and would otherwise
1225 : decode the batches once per column. */
1226 : struct {
1227 : fd_gui_shred_scratch_t * ev; /* [max] */
1228 : ulong max;
1229 : } shred_scratch;
1230 :
1231 : /* Earliest REPLAY_EXEC_DONE timestamp per shred of the
1232 : FD_GUI_EXEC_DONE_SLOT_CNT most recent slots, LONG_MAX if none */
1233 : struct {
1234 : ulong slot[ FD_GUI_EXEC_DONE_SLOT_CNT ];
1235 : long * ts; /* [FD_GUI_EXEC_DONE_SLOT_CNT][FD_SHRED_BLK_MAX] */
1236 : } exec_done;
1237 :
1238 : struct {
1239 : ulong leader_shred_cnt; /* A gauge counting the number of leader shreds seen on the SHRED_OUT link. Resets at
1240 : the end of a leader slot. This works because leader fecs are published in order. */
1241 : ulong leader_shred_slot; /* The slot leader_shred_cnt is currently counting, ULONG_MAX if none. Used to reset
1242 : counter after an abandoned/ended slot. This works because leader fecs are published
1243 : in order. */
1244 :
1245 : fd_gui_shred_builder_t builder;
1246 : ulong dropped_event_cnt;
1247 :
1248 : /* Events with insert_time_ns below this watermark have already
1249 : been pushed to clients. */
1250 : long broadcast_watermark_ns;
1251 : } shreds;
1252 :
1253 : struct {
1254 : ulong pending_cnt;
1255 : fd_gui_snapsv_pending_t pending[ FD_GUI_SNAPSV_PENDING_MAX ];
1256 : } snapsv;
1257 : };
1258 :
1259 : typedef struct fd_gui fd_gui_t;
1260 :
1261 : FD_PROTOTYPES_BEGIN
1262 :
1263 : /* fd_gui_timeline_day_get returns the retained calendar day for day_idx, or NULL if not found. */
1264 :
1265 : fd_gui_timeline_day_t *
1266 : fd_gui_timeline_day_get( fd_gui_t * gui,
1267 : ulong day_idx );
1268 :
1269 : /* fd_gui_handle_replay_txn records replay transaction metadata. */
1270 :
1271 : void
1272 : fd_gui_handle_replay_txn( fd_gui_t * gui,
1273 : fd_replay_txn_executed_t const * txn,
1274 : long now );
1275 :
1276 : /* fd_gui_tile_timers_diff computes the compact, display-ready diff of a
1277 : single tile's timers between two raw cumulative samples `prev` and
1278 : `cur`, writing it into `out`. */
1279 :
1280 : void
1281 : fd_gui_tile_timers_diff( fd_gui_tile_timers_hist_t * out,
1282 : fd_gui_tile_timers_t const * prev,
1283 : fd_gui_tile_timers_t const * cur,
1284 : ulong tile_idx,
1285 : long sample_time_nanos );
1286 :
1287 : /* fd_gui_boot_snapshot_slot returns the boot slot: the incremental's
1288 : if one loaded, else the full's, ULONG_MAX before any load. Parsed
1289 : from archive filenames, so best-effort. */
1290 :
1291 : FD_FN_PURE static inline ulong
1292 0 : fd_gui_boot_snapshot_slot( fd_gui_t const * gui ) {
1293 0 : for( ulong i=FD_GUI_BOOT_PROGRESS_SNAPSHOT_CNT; i>0UL; i-- ) {
1294 0 : ulong slot = gui->summary.boot_progress.loading_snapshot[ i-1UL ].slot;
1295 0 : if( slot!=ULONG_MAX ) return slot;
1296 0 : }
1297 0 : return ULONG_MAX;
1298 0 : }
1299 :
1300 : /* fd_gui_wfs_mode recomputes the WFS verdict (see fd_wfs.h); gossip's
1301 : WAIT_FOR_SUPERMAJORITY_STATE gauge cannot tell MATCH from NOOP, as
1302 : both end in STATE_DONE. */
1303 :
1304 : FD_FN_PURE static inline int
1305 0 : fd_gui_wfs_mode( fd_gui_t const * gui ) {
1306 0 : return fd_wfs_mode( gui->summary.wfs_slot,
1307 0 : gui->summary.wfs_bank_hash[ 0 ]=='\0',
1308 0 : (ulong)gui->summary.expected_shred_version,
1309 0 : fd_gui_boot_snapshot_slot( gui ) );
1310 0 : }
1311 :
1312 : FD_FN_CONST ulong
1313 : fd_gui_align( void );
1314 :
1315 : ulong
1316 : fd_gui_footprint( ulong tile_cnt,
1317 : ulong max_live_slots,
1318 : ulong max_txn_per_slot );
1319 :
1320 : void *
1321 : fd_gui_new( void * shmem,
1322 : fd_http_server_t * http,
1323 : char const * version,
1324 : char const * cluster,
1325 : uchar const * identity_key,
1326 : int has_vote_key,
1327 : uchar const * vote_key,
1328 : int is_alpenglow,
1329 : ulong max_live_slots,
1330 : ulong max_txn_per_slot,
1331 : int snapshots_enabled,
1332 : int is_voting,
1333 : int schedule_strategy,
1334 : char const * wfs_expected_bank_hash_cstr,
1335 : ushort expected_shred_version,
1336 : ulong wfs_slot,
1337 : char const * accounts_database_path,
1338 : char const * gui_database_path,
1339 : void * db,
1340 : fd_topo_t const * topo,
1341 : fd_accdb_shmem_t const * accdb_shmem,
1342 : long now );
1343 :
1344 : fd_gui_t *
1345 : fd_gui_join( void * shmem );
1346 :
1347 : void
1348 : fd_gui_set_identity( fd_gui_t * gui,
1349 : uchar const * identity_pubkey );
1350 :
1351 : void
1352 : fd_gui_handle_diag_snapshot( fd_gui_t * gui,
1353 : void const * data,
1354 : ulong data_sz );
1355 :
1356 : void
1357 : fd_gui_ws_open( fd_gui_t * gui,
1358 : ulong conn_id,
1359 : long now );
1360 :
1361 : int
1362 : fd_gui_ws_message( fd_gui_t * gui,
1363 : ulong ws_conn_id,
1364 : uchar const * data,
1365 : ulong data_len );
1366 :
1367 : void
1368 : fd_gui_became_leader( fd_gui_t * gui,
1369 : ulong slot,
1370 : long start_time_nanos,
1371 : long end_time_nanos,
1372 : ulong max_compute_units,
1373 : ulong max_microblocks,
1374 : ulong bank_seq );
1375 :
1376 : void
1377 : fd_gui_unbecame_leader( fd_gui_t * gui,
1378 : ulong _slot,
1379 : fd_done_packing_t const * done_packing );
1380 :
1381 : void
1382 : fd_gui_done_draining( fd_gui_t * gui,
1383 : long now );
1384 :
1385 : void
1386 : fd_gui_microblock_execution_begin( fd_gui_t * gui,
1387 : long tspub_ns,
1388 : ulong _slot,
1389 : fd_txn_e_t * txns,
1390 : ulong txn_cnt,
1391 : uint microblock_idx,
1392 : ulong pack_txn_idx,
1393 : ulong bank_seq,
1394 : long now );
1395 :
1396 : void
1397 : fd_gui_microblock_execution_end( fd_gui_t * gui,
1398 : long tspub_ns,
1399 : ulong bank_idx,
1400 : ulong _slot,
1401 : ulong txn_cnt,
1402 : fd_txn_p_t * txns,
1403 : ulong pack_txn_idx,
1404 : fd_txn_ns_dt_t txn_ns_dt,
1405 : long exec_end_ticks,
1406 : ulong tips,
1407 : ulong bank_seq,
1408 : long now );
1409 :
1410 : int
1411 : fd_gui_poll( fd_gui_t * gui, long now );
1412 :
1413 : long
1414 : fd_gui_next_deadline( fd_gui_t const * gui );
1415 :
1416 : void
1417 : fd_gui_handle_block_engine_update( fd_gui_t * gui,
1418 : fd_bundle_block_engine_update_t const * update );
1419 :
1420 : void
1421 : fd_gui_handle_shred( fd_gui_t * gui,
1422 : ulong slot,
1423 : ulong shred_idx,
1424 : int is_turbine,
1425 : long tsorig,
1426 : long now );
1427 :
1428 : void
1429 : fd_gui_handle_leader_fec( fd_gui_t * gui,
1430 : ulong slot,
1431 : ulong fec_shred_cnt,
1432 : int is_end_of_slot,
1433 : long tsorig,
1434 : long now );
1435 :
1436 : void
1437 : fd_gui_handle_exec_txn_done( fd_gui_t * gui,
1438 : ulong slot,
1439 : ulong start_shred_idx,
1440 : ulong end_shred_idx,
1441 : long tsorig_ns,
1442 : long tspub_ns,
1443 : long now );
1444 :
1445 : void
1446 : fd_gui_handle_repair_slot( fd_gui_t * gui, ulong slot, long now );
1447 :
1448 : void
1449 : fd_gui_handle_repair_request( fd_gui_t * gui, ulong slot, ulong shred_idx, long tsorig, long now );
1450 :
1451 : void
1452 : fd_gui_handle_snapshot_update( fd_gui_t * gui,
1453 : fd_snapct_update_t const * msg );
1454 :
1455 : void
1456 : fd_gui_handle_snapsv_update( fd_gui_t * gui,
1457 : ulong sig,
1458 : fd_snapsv_msg_t const * msg,
1459 : ulong sz,
1460 : int eom,
1461 : long now );
1462 :
1463 : void
1464 : fd_gui_stage_snapshot_manifest( fd_gui_t * gui,
1465 : fd_snapshot_manifest_t const * manifest );
1466 :
1467 : void
1468 : fd_gui_handle_epoch_info( fd_gui_t * gui,
1469 : fd_epoch_info_msg_t const * epoch_info,
1470 : long now );
1471 :
1472 : void
1473 : fd_gui_handle_replay_update( fd_gui_t * gui,
1474 : fd_replay_slot_completed_t const * slot_completed,
1475 : ulong vote_slot,
1476 : long now );
1477 :
1478 : void
1479 : fd_gui_stage_landed_vote( fd_gui_t * gui,
1480 : ulong landed_slot,
1481 : ulong landed_bank_seq,
1482 : ulong voted_slot );
1483 :
1484 : void
1485 : fd_gui_handle_genesis_hash( fd_gui_t * gui,
1486 : fd_hash_t const * msg );
1487 :
1488 : /* fd_gui_handle_root_advanced is invoked on REPLAY_SIG_ROOT_ADVANCED.
1489 : It roots the (slot, bank_seq) fork and updates the dependent state. */
1490 : void
1491 : fd_gui_handle_root_advanced( fd_gui_t * gui,
1492 : ulong slot,
1493 : ulong bank_seq,
1494 : long now );
1495 :
1496 : /* fd_gui_handle_oc_advanced is invoked on REPLAY_SIG_OC_ADVANCED. It
1497 : marks the (slot, bank_seq) fork optimistically confirmed. */
1498 : void
1499 : fd_gui_handle_oc_advanced( fd_gui_t * gui,
1500 : ulong slot,
1501 : ulong bank_seq,
1502 : long now );
1503 :
1504 : void
1505 : fd_gui_handle_ag_notarized( fd_gui_t * gui,
1506 : ulong slot,
1507 : fd_hash_t const * block_id,
1508 : uchar notarization_kind );
1509 :
1510 : void
1511 : fd_gui_handle_ag_finalized( fd_gui_t * gui,
1512 : ulong slot,
1513 : fd_hash_t const * block_id,
1514 : uchar finalization_kind );
1515 :
1516 : void
1517 : fd_gui_handle_ag_skip_cert( fd_gui_t * gui,
1518 : ulong slot );
1519 :
1520 : void
1521 : fd_gui_handle_ag_leader( fd_gui_t * gui,
1522 : ulong parent_slot );
1523 :
1524 : void
1525 : fd_gui_ag_register_block( fd_gui_t * gui,
1526 : ulong slot,
1527 : fd_hash_t const * block_id,
1528 : ulong bank_seq );
1529 :
1530 : int
1531 : fd_gui_ag_slot_is_skip_notarized( fd_gui_t const * gui,
1532 : ulong slot );
1533 :
1534 : /* fd_gui_slot_get_canon_safe resolves slot number `_slot` on the
1535 : canonical fork and returns a renderable record. */
1536 : fd_gui_slot_t *
1537 : fd_gui_slot_get_canon_safe( fd_gui_t * gui, ulong _slot );
1538 :
1539 :
1540 : /* fd_gui_epoch returns the DB EPOCH record for `epoch`, or NULL if no
1541 : record for that epoch is durable in the store. The returned pointer
1542 : may be written in place. */
1543 : static inline fd_gui_epoch_t *
1544 0 : fd_gui_epoch( fd_gui_t * gui, ulong epoch ) {
1545 0 : fd_gui_hist_epoch_key_t key[ 1 ]; key->epoch = epoch;
1546 0 : return (fd_gui_epoch_t *)fd_gui_hist_kv_get( gui, FD_GUI_HIST_EPOCH, key );
1547 0 : }
1548 :
1549 : /* fd_gui_current_epoch returns the record for the current epoch,
1550 : or NULL if no epoch has been ingested yet. */
1551 : static inline fd_gui_epoch_t *
1552 0 : fd_gui_current_epoch( fd_gui_t * gui ) {
1553 0 : if( FD_UNLIKELY( gui->epoch.current_epoch==ULONG_MAX ) ) return NULL;
1554 0 : return fd_gui_epoch( gui, gui->epoch.current_epoch );
1555 0 : }
1556 :
1557 : /* fd_gui_epoch_get_or_create returns the mutable DB EPOCH record
1558 : for `epoch`, creating it if none exists yet. */
1559 : static inline fd_gui_epoch_t *
1560 : fd_gui_epoch_get_or_create( fd_gui_t * gui,
1561 : ulong epoch,
1562 0 : int * created_out ) {
1563 0 : fd_gui_epoch_t * rec = fd_gui_epoch( gui, epoch );
1564 0 : if( FD_LIKELY( rec ) ) { if( created_out ) *created_out = 0; return rec; }
1565 :
1566 0 : fd_gui_hist_epoch_key_t key[ 1 ]; key->epoch = epoch;
1567 0 : rec = fd_gui_hist_kv_get_or_create( gui, FD_GUI_HIST_EPOCH, key );
1568 0 : if( FD_UNLIKELY( !rec ) ) return NULL;
1569 0 : if( created_out ) *created_out = 1;
1570 0 : return rec;
1571 0 : }
1572 :
1573 : /* fd_gui_first_replay_slot returns the lowest slot number the validator
1574 : has authoritative knowledge of during this run. */
1575 : static inline ulong
1576 0 : fd_gui_first_replay_slot( fd_gui_t const * gui ) {
1577 0 : ulong slot_incremental = gui->summary.boot_progress.loading_snapshot[ FD_GUI_BOOT_PROGRESS_INCREMENTAL_SNAPSHOT_IDX ].slot;
1578 0 : ulong slot_full = gui->summary.boot_progress.loading_snapshot[ FD_GUI_BOOT_PROGRESS_FULL_SNAPSHOT_IDX ].slot;
1579 0 : return fd_ulong_if( slot_incremental!=ULONG_MAX, slot_incremental+1UL,
1580 0 : fd_ulong_if( slot_full!=ULONG_MAX, slot_full+1UL, ULONG_MAX ) );
1581 0 : }
1582 :
1583 : static inline fd_gui_leader_slot_t *
1584 : fd_gui_slot_leader_get( fd_gui_t * gui,
1585 : ulong slot,
1586 0 : ulong bank_seq ) {
1587 0 : if( FD_UNLIKELY( !gui->db || slot==ULONG_MAX || bank_seq==ULONG_MAX ) ) return NULL;
1588 0 : fd_gui_hist_leader_slot_key_t key = { .slot = slot, .bank_seq = bank_seq };
1589 0 : return fd_gui_hist_kv_get( gui, FD_GUI_HIST_LEADER_SLOT, &key );
1590 0 : }
1591 :
1592 : /* fd_gui_slot_leader_get_or_create returns the mutable returns the
1593 : DB LEADER_SLOT record for (slot, bank_seq), creating it if none
1594 : exists yet. */
1595 :
1596 : static inline fd_gui_leader_slot_t *
1597 : fd_gui_slot_leader_get_or_create( fd_gui_t * gui,
1598 : ulong slot,
1599 0 : ulong bank_seq ) {
1600 0 : if( FD_UNLIKELY( !gui->db || slot==ULONG_MAX ) ) return NULL;
1601 :
1602 0 : fd_gui_leader_slot_t * rec = fd_gui_slot_leader_get( gui, slot, bank_seq );
1603 0 : if( FD_LIKELY( rec ) ) return rec;
1604 :
1605 0 : fd_gui_hist_leader_slot_key_t key = { .slot = slot, .bank_seq = bank_seq };
1606 0 : fd_gui_leader_slot_t * seed = fd_gui_hist_kv_get_or_create( gui, FD_GUI_HIST_LEADER_SLOT, &key );
1607 0 : if( FD_UNLIKELY( !seed ) ) return NULL;
1608 :
1609 0 : *seed = (fd_gui_leader_slot_t){
1610 0 : .slot = slot,
1611 0 : .bank_seq = bank_seq,
1612 0 : .leader_start_time = LONG_MAX,
1613 0 : .leader_end_time = LONG_MAX,
1614 0 : .txn_insert_time_min_ns = LONG_MAX,
1615 0 : .txn_insert_time_max_ns = LONG_MIN,
1616 0 : .max_microblocks = ULONG_MAX,
1617 0 : .max_compute_units = ULONG_MAX,
1618 0 : .microblocks_upper_bound = UINT_MAX,
1619 0 : .begin_microblocks = 0U,
1620 0 : .end_microblocks = 0U,
1621 0 : .has_waterfall = 0,
1622 0 : .unbecame_leader = 0
1623 0 : };
1624 0 : return seed;
1625 0 : }
1626 :
1627 : /* fd_gui_slot_leader_get_any returns the DB record for key matching
1628 : (lslot, *), regardless of fork, or NULL if no record exists for lslot. */
1629 :
1630 : static inline fd_gui_leader_slot_t *
1631 0 : fd_gui_slot_leader_get_any( fd_gui_t * gui, ulong _lslot ) {
1632 0 : if( FD_UNLIKELY( !gui->db || _lslot==ULONG_MAX ) ) return NULL;
1633 0 : return fd_gui_hist_kv_get_slot_any( gui, FD_GUI_HIST_LEADER_SLOT, _lslot );
1634 0 : }
1635 :
1636 : /* fd_gui_slot_get returns the DB record for the exact key
1637 : (slot, bank_seq), or NULL if none exists. */
1638 :
1639 : static inline fd_gui_slot_t *
1640 0 : fd_gui_slot_get( fd_gui_t * gui, ulong slot, ulong bank_seq ) {
1641 0 : if( FD_UNLIKELY( slot==ULONG_MAX || bank_seq==ULONG_MAX ) ) return NULL;
1642 0 : fd_gui_hist_slot_key_t key;
1643 0 : key.slot = slot; key.bank_seq = bank_seq;
1644 0 : return (fd_gui_slot_t *)fd_gui_hist_kv_get( gui, FD_GUI_HIST_SLOT, &key );
1645 0 : }
1646 :
1647 : /* fd_gui_slot_parent_get returns the DB record for the parent of `c` on
1648 : c's own fork or NULL if the parent is unknown or has no record. */
1649 :
1650 : static inline fd_gui_slot_t *
1651 0 : fd_gui_slot_parent_get( fd_gui_t * gui, fd_gui_slot_t const * c ) {
1652 0 : if( FD_UNLIKELY( !c ) ) return NULL;
1653 0 : return fd_gui_slot_get( gui, c->parent_slot, c->parent_bank_seq );
1654 0 : }
1655 :
1656 : /* fd_gui_slot_get_canon returns the DB record for slot number `_slot`
1657 : on the canonical fork, or NULL if `_slot` is skipped, above the
1658 : canonical tip, or the tip is not yet resolved. The canonical fork is
1659 : the consensus fork chosen by tower fork choice. */
1660 :
1661 : static inline fd_gui_slot_t *
1662 0 : fd_gui_slot_get_canon( fd_gui_t * gui, ulong _slot ) {
1663 0 : if( FD_UNLIKELY( _slot==ULONG_MAX ) ) return NULL;
1664 :
1665 : /* At or below the root the canonical fork has settled, meaning there
1666 : should be exactly one NOT_SKIPPED entry per slot. */
1667 0 : if( FD_LIKELY( gui->summary.slot_rooted!=ULONG_MAX && _slot<=gui->summary.slot_rooted ) ) {
1668 0 : fd_gui_hist_kv_slot_iter_t it[ 1 ];
1669 0 : for( fd_gui_hist_kv_iter_begin( gui, it, FD_GUI_HIST_SLOT, _slot ); it->rec; fd_gui_hist_kv_iter_next( it ) ) {
1670 0 : fd_gui_slot_t const * rec = (fd_gui_slot_t const *)it->rec;
1671 0 : if( FD_LIKELY( rec->skip==FD_GUI_SKIP_STATUS_NOT_SKIPPED ) ) return fd_gui_slot_get( gui, _slot, it->bank_seq );
1672 0 : }
1673 0 : return NULL; /* skipped on the canonical fork, or no record */
1674 0 : }
1675 :
1676 : /* Otherwise do a fork-walk from the canonical frontier. */
1677 0 : ulong slot = gui->summary.slot_tower;
1678 0 : ulong bank_seq = gui->summary.slot_tower_bank_seq;
1679 0 : for(;;) {
1680 0 : if( FD_UNLIKELY( slot==ULONG_MAX || bank_seq==ULONG_MAX ) ) return NULL;
1681 0 : if( FD_UNLIKELY( slot<_slot ) ) return NULL; /* walked past _slot: skipped or above-tip */
1682 0 : fd_gui_slot_t * n = fd_gui_slot_get( gui, slot, bank_seq );
1683 0 : if( FD_UNLIKELY( !n ) ) return NULL; /* evicted / no record */
1684 0 : if( FD_LIKELY( slot==_slot ) ) return n;
1685 0 : slot = n->parent_slot;
1686 0 : bank_seq = n->parent_bank_seq;
1687 0 : }
1688 0 : }
1689 :
1690 : /* fd_gui_slot_get_any returns the DB record for slot number `_slot`
1691 : without any fork awareness: it is a simple store lookup that returns
1692 : the first record that exists for `_slot`. */
1693 :
1694 : static inline fd_gui_slot_t *
1695 0 : fd_gui_slot_get_any( fd_gui_t * gui, ulong _slot ) {
1696 0 : if( FD_UNLIKELY( _slot==ULONG_MAX ) ) return NULL;
1697 :
1698 0 : fd_gui_hist_kv_slot_iter_t it[ 1 ];
1699 0 : fd_gui_hist_kv_iter_begin( gui, it, FD_GUI_HIST_SLOT, _slot );
1700 0 : if( FD_UNLIKELY( !it->rec ) ) return NULL;
1701 0 : return fd_gui_slot_get( gui, _slot, it->bank_seq );
1702 0 : }
1703 :
1704 : /* fd_gui_slot_is_ancestor returns 1 if (ancestor_slot,
1705 : ancestor_bank_seq) is known to be an ancestor of (slot, bank_seq), 0
1706 : otherwise. */
1707 :
1708 : static inline int
1709 : fd_gui_slot_is_ancestor( fd_gui_t * gui,
1710 : ulong ancestor_slot,
1711 : ulong ancestor_bank_seq,
1712 : ulong slot,
1713 0 : ulong bank_seq ) {
1714 0 : for(;;) {
1715 0 : if( FD_UNLIKELY( slot==ancestor_slot ) ) return bank_seq==ancestor_bank_seq;
1716 0 : if( FD_UNLIKELY( slot<ancestor_slot ) ) return 0;
1717 :
1718 0 : fd_gui_slot_t const * rec = fd_gui_slot_get( gui, slot, bank_seq );
1719 0 : if( FD_UNLIKELY( !rec ) ) return 0;
1720 0 : slot = rec->parent_slot;
1721 0 : bank_seq = rec->parent_bank_seq;
1722 0 : }
1723 0 : }
1724 :
1725 : /* fd_gui_slot_is_skipped returns 1 if `slot` is skipped on the fork
1726 : whose tip is (des, des_bank_seq) and whose root is `root`, 0
1727 : otherwise. */
1728 :
1729 : static inline int
1730 0 : fd_gui_slot_is_skipped( fd_gui_t * gui, ulong root, ulong des, ulong des_bank_seq, ulong slot ) {
1731 0 : fd_gui_slot_t * c = fd_gui_slot_get( gui, des, des_bank_seq );
1732 0 : ulong cslot = des;
1733 0 : while( c ) {
1734 0 : if( FD_UNLIKELY( root==cslot ) ) return 0; /* on the fork, not skipped */
1735 0 : ulong pslot = c->parent_slot;
1736 0 : fd_gui_slot_t * p = fd_gui_slot_parent_get( gui, c );
1737 0 : if( FD_UNLIKELY( p && pslot<slot && cslot>slot ) ) return 1; /* in-between two on-fork records, skipped */
1738 0 : c = p;
1739 0 : cslot = pslot;
1740 0 : }
1741 0 : return 0; /* slot not between root and des, or is unknown */
1742 0 : }
1743 :
1744 : /* fd_gui_get_epoch_by_slot returns the mutable DB EPOCH record covering
1745 : `_slot` (resolved via the epoch schedule), or NULL if no epoch
1746 : schedule is known yet or no record for that epoch is present. */
1747 :
1748 : static inline fd_gui_epoch_t *
1749 0 : fd_gui_get_epoch_by_slot( fd_gui_t * gui, ulong _slot ) {
1750 0 : if( FD_UNLIKELY( !gui->epoch.has_epoch_schedule ) ) return NULL;
1751 0 : ulong epoch = fd_slot_to_epoch( &gui->epoch.epoch_schedule, _slot, NULL );
1752 0 : return fd_gui_epoch( gui, epoch );
1753 0 : }
1754 :
1755 : /* fd_gui_slot_voter_state returns one of FD_GUI_IS_VOTER_* for
1756 : `_slot`. */
1757 :
1758 : static inline uchar
1759 0 : fd_gui_slot_voter_state( fd_gui_t * gui, ulong _slot ) {
1760 0 : fd_gui_epoch_t const * epoch = fd_gui_get_epoch_by_slot( gui, _slot );
1761 0 : if( FD_UNLIKELY( !epoch || _slot<epoch->start_slot ) ) return FD_GUI_IS_VOTER_UNKNOWN;
1762 0 : ulong idx = _slot - epoch->start_slot;
1763 0 : return idx<epoch->slot_cnt ? epoch->is_voter[ idx ] : FD_GUI_IS_VOTER_UNKNOWN;
1764 0 : }
1765 :
1766 : static inline uchar
1767 0 : fd_gui_slot_vote_rewarded_state( fd_gui_t * gui, ulong _slot ) {
1768 0 : fd_gui_epoch_t const * epoch = fd_gui_get_epoch_by_slot( gui, _slot );
1769 0 : if( FD_UNLIKELY( !epoch || _slot<epoch->start_slot ) ) return FD_GUI_VOTE_REWARDED_UNKNOWN;
1770 0 : ulong idx = _slot - epoch->start_slot;
1771 0 : return idx<epoch->slot_cnt ? epoch->vote_rewarded[ idx ] : FD_GUI_VOTE_REWARDED_UNKNOWN;
1772 0 : }
1773 :
1774 : static inline ushort
1775 0 : fd_gui_slot_vote_count( fd_gui_t * gui, ulong _slot ) {
1776 0 : fd_gui_epoch_t const * epoch = fd_gui_get_epoch_by_slot( gui, _slot );
1777 0 : if( FD_UNLIKELY( !epoch || _slot<epoch->start_slot ) ) return USHORT_MAX;
1778 0 : ulong idx = _slot-epoch->start_slot;
1779 0 : return idx<epoch->slot_cnt ? epoch->vote_count[ idx ] : USHORT_MAX;
1780 0 : }
1781 :
1782 : /* fd_gui_get_slot_leader returns the leader pubkey scheduled for
1783 : `_slot`, or NULL if `epoch` is NULL, `_slot` is outside the epoch,
1784 : or the schedule index is indeterminate. */
1785 :
1786 : static inline fd_pubkey_t const *
1787 0 : fd_gui_get_slot_leader( fd_gui_epoch_t const * epoch, ulong _slot ) {
1788 0 : if( FD_UNLIKELY( !epoch ) ) return NULL;
1789 0 : if( FD_UNLIKELY( _slot<epoch->start_slot || _slot>=epoch->start_slot+epoch->slot_cnt ) ) return NULL;
1790 0 : ulong rot = (_slot - epoch->start_slot)/FD_EPOCH_SLOTS_PER_ROTATION;
1791 0 : uint idx = epoch->sched[ rot ];
1792 0 : if( FD_UNLIKELY( idx>=epoch->pub_cnt ) ) return NULL; /* indeterminate */
1793 0 : return &epoch->pub[ idx ];
1794 0 : }
1795 :
1796 : static inline int
1797 0 : fd_gui_slot_is_mine( fd_gui_t * gui, ulong _slot ) {
1798 0 : fd_gui_epoch_t const * epoch = fd_gui_get_epoch_by_slot( gui, _slot );
1799 0 : fd_pubkey_t const * slot_leader = fd_gui_get_slot_leader( epoch, _slot );
1800 0 : if( FD_UNLIKELY( !slot_leader ) ) return 0;
1801 0 : return !memcmp( slot_leader->uc, gui->summary.identity_key->uc, 32UL );
1802 0 : }
1803 :
1804 : /* fd_gui_slot_get_or_create returns the mutable returns the mutable DB
1805 : SLOT record for (_slot, bank_seq), creating it if none exists yet. */
1806 :
1807 : static inline fd_gui_slot_t *
1808 : fd_gui_slot_get_or_create( fd_gui_t * gui,
1809 : ulong _slot,
1810 : ulong _parent_slot,
1811 : ulong bank_seq,
1812 0 : ulong parent_bank_seq ) {
1813 0 : fd_gui_slot_t * rec = fd_gui_slot_get( gui, _slot, bank_seq );
1814 0 : if( FD_LIKELY( rec ) ) return rec;
1815 :
1816 0 : fd_gui_epoch_t * epoch = fd_gui_get_epoch_by_slot( gui, _slot );
1817 0 : fd_pubkey_t const * slot_leader = fd_gui_get_slot_leader( epoch, _slot );
1818 0 : int mine = fd_int_if( !!slot_leader, !memcmp( slot_leader->uc, gui->summary.identity_key->uc, 32UL ), 0 );
1819 :
1820 0 : fd_gui_hist_slot_key_t key;
1821 0 : key.slot = _slot; key.bank_seq = bank_seq;
1822 0 : fd_gui_slot_t * meta = fd_gui_hist_kv_get_or_create( gui, FD_GUI_HIST_SLOT, &key );
1823 0 : if( FD_UNLIKELY( !meta ) ) return NULL;
1824 :
1825 0 : meta->slot = _slot;
1826 0 : meta->bank_seq = bank_seq;
1827 0 : meta->parent_bank_seq = parent_bank_seq;
1828 0 : meta->parent_slot = _parent_slot;
1829 0 : meta->vote_slot = ULONG_MAX;
1830 0 : meta->vote_latency_exact = FD_GUI_VOTE_LATENCY_NOT_VOTED;
1831 0 : meta->max_compute_units = UINT_MAX;
1832 0 : meta->completed_time = LONG_MAX;
1833 0 : meta->parent_completed_time = LONG_MAX;
1834 0 : meta->mine = (uchar)(mine & 1);
1835 0 : meta->is_voter = FD_GUI_IS_VOTER_NO;
1836 0 : if( FD_UNLIKELY( gui->summary.is_alpenglow ) ) meta->is_voter = FD_GUI_IS_VOTER_UNKNOWN;
1837 0 : meta->skip = FD_GUI_SKIP_STATUS_UNKNOWN;
1838 0 : meta->level = (uchar)( _slot ? FD_GUI_SLOT_LEVEL_INCOMPLETE : FD_GUI_SLOT_LEVEL_ROOTED ); /* slot 0 always rooted */
1839 0 : meta->notarization_kind = FD_GUI_AG_NOTAR_NONE;
1840 0 : meta->finalization_kind = FD_GUI_AG_FINAL_NONE;
1841 0 : meta->vote_rewarded = FD_GUI_VOTE_REWARDED_UNKNOWN;
1842 0 : meta->vote_count = USHORT_MAX;
1843 0 : meta->vote_failed = UINT_MAX;
1844 0 : meta->vote_success = UINT_MAX;
1845 0 : meta->nonvote_success = UINT_MAX;
1846 0 : meta->nonvote_failed = UINT_MAX;
1847 0 : meta->compute_units = UINT_MAX;
1848 0 : meta->transaction_fee = ULONG_MAX;
1849 0 : meta->priority_fee = ULONG_MAX;
1850 0 : meta->tips = ULONG_MAX;
1851 0 : meta->shred_cnt = UINT_MAX;
1852 0 : memset( meta->block_hash.uc, 0, sizeof(fd_hash_t) );
1853 :
1854 0 : if( FD_UNLIKELY( mine && epoch ) ) epoch->my_total_slots++;
1855 :
1856 0 : return meta;
1857 0 : }
1858 :
1859 : FD_PROTOTYPES_END
1860 :
1861 : #endif /* HEADER_fd_src_disco_gui_fd_gui_h */
|