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