Line data Source code
1 : #include "fd_gui_hist.h"
2 : #include "fd_gui_store.h"
3 : #include "fd_gui.h" /* fd_gui_t, record types */
4 :
5 : #include <stddef.h> /* offsetof */
6 :
7 : /* Every record type must fit in one store region (header + record). */
8 : FD_STATIC_ASSERT( sizeof(fd_gui_slot_history_shred_event_t)<=FD_GUI_STORE_MAX_REC_SZ, rec_fits );
9 : FD_STATIC_ASSERT( sizeof(fd_gui_tile_timers_hist_t )<=FD_GUI_STORE_MAX_REC_SZ, rec_fits );
10 : FD_STATIC_ASSERT( sizeof(fd_gui_scheduler_counts_t )<=FD_GUI_STORE_MAX_REC_SZ, rec_fits );
11 : FD_STATIC_ASSERT( sizeof(fd_gui_tile_stats_t )<=FD_GUI_STORE_MAX_REC_SZ, rec_fits );
12 : FD_STATIC_ASSERT( sizeof(fd_gui_txn_waterfall_t )<=FD_GUI_STORE_MAX_REC_SZ, rec_fits );
13 : FD_STATIC_ASSERT( sizeof(fd_gui_store_txn_start_t )<=FD_GUI_STORE_MAX_REC_SZ, rec_fits );
14 : FD_STATIC_ASSERT( sizeof(fd_gui_store_txn_end_t )<=FD_GUI_STORE_MAX_REC_SZ, rec_fits );
15 : FD_STATIC_ASSERT( sizeof(fd_gui_slot_t )<=FD_GUI_STORE_MAX_REC_SZ, rec_fits );
16 : FD_STATIC_ASSERT( sizeof(fd_gui_leader_slot_t )<=FD_GUI_STORE_MAX_REC_SZ, rec_fits );
17 : FD_STATIC_ASSERT( sizeof(fd_gui_epoch_t )<=FD_GUI_STORE_MAX_REC_SZ, rec_fits );
18 :
19 : /* fd_gui_hist_ts_append copies each TS record through a fixed stack buffer
20 : so it can rewrite the clamped timestamp; every TS record type must fit. */
21 : #define FD_GUI_HIST_TS_SZ_MAX (512UL)
22 : FD_STATIC_ASSERT( sizeof(fd_gui_slot_history_shred_event_t)<=FD_GUI_HIST_TS_SZ_MAX, ts_rec_fits );
23 : FD_STATIC_ASSERT( sizeof(fd_gui_tile_timers_hist_t )<=FD_GUI_HIST_TS_SZ_MAX, ts_rec_fits );
24 : FD_STATIC_ASSERT( sizeof(fd_gui_scheduler_counts_t )<=FD_GUI_HIST_TS_SZ_MAX, ts_rec_fits );
25 : FD_STATIC_ASSERT( sizeof(fd_gui_tile_stats_t )<=FD_GUI_HIST_TS_SZ_MAX, ts_rec_fits );
26 : FD_STATIC_ASSERT( sizeof(fd_gui_txn_waterfall_t )<=FD_GUI_HIST_TS_SZ_MAX, ts_rec_fits );
27 : FD_STATIC_ASSERT( sizeof(fd_gui_store_txn_start_t )<=FD_GUI_HIST_TS_SZ_MAX, ts_rec_fits );
28 : FD_STATIC_ASSERT( sizeof(fd_gui_store_txn_end_t )<=FD_GUI_HIST_TS_SZ_MAX, ts_rec_fits );
29 :
30 : /* Each key type must alias the matching record field exactly. */
31 : FD_STATIC_ASSERT( offsetof( fd_gui_hist_slot_key_t, slot )==offsetof( fd_gui_slot_t, slot ), key_layout );
32 : FD_STATIC_ASSERT( offsetof( fd_gui_hist_slot_key_t, bank_seq )==offsetof( fd_gui_slot_t, bank_seq ), key_layout );
33 : FD_STATIC_ASSERT( offsetof( fd_gui_hist_leader_slot_key_t, slot )==offsetof( fd_gui_leader_slot_t, slot ), key_layout );
34 : FD_STATIC_ASSERT( offsetof( fd_gui_hist_leader_slot_key_t, bank_seq )==offsetof( fd_gui_leader_slot_t, bank_seq ), key_layout );
35 : FD_STATIC_ASSERT( offsetof( fd_gui_hist_epoch_key_t, epoch )==offsetof( fd_gui_epoch_t, epoch ), key_layout );
36 :
37 11811 : #define FD_GUI_HIST_KEYSHAPE_TIMESERIES (0) /* (ts, ...) */
38 6720 : #define FD_GUI_HIST_KEYSHAPE_SLOT_BANK (1) /* (slot, bank_seq) */
39 291 : #define FD_GUI_HIST_KEYSHAPE_EPOCH (2) /* (epoch) */
40 :
41 : static inline long
42 75 : fd_gui_hist_dbi_res_ns( int dbi FD_PARAM_UNUSED ) {
43 75 : return FD_GUI_HIST_RES_1S_NS; /* all TS DBs use the 1s resolution */
44 75 : }
45 :
46 : static inline ulong
47 3237 : fd_gui_hist_dbi_ts_off( int dbi ) {
48 3237 : switch( dbi ) {
49 3111 : case FD_GUI_HIST_SHRED_EVENTS: return offsetof( fd_gui_slot_history_shred_event_t, timestamp );
50 3 : case FD_GUI_HIST_TILE_TIMERS: return offsetof( fd_gui_tile_timers_hist_t, sample_time_nanos );
51 108 : case FD_GUI_HIST_SCHEDULER_COUNTS: return offsetof( fd_gui_scheduler_counts_t, sample_time_ns );
52 3 : case FD_GUI_HIST_TILE_STATS: return offsetof( fd_gui_tile_stats_t, sample_time_nanos );
53 3 : case FD_GUI_HIST_TXN_WATERFALL: return offsetof( fd_gui_txn_waterfall_t, sample_time_nanos );
54 3 : case FD_GUI_HIST_TXN_START: return offsetof( fd_gui_store_txn_start_t, microblock_start_ns );
55 3 : case FD_GUI_HIST_TXN_END: return offsetof( fd_gui_store_txn_end_t, microblock_end_ns );
56 3 : default: return 0UL;
57 3237 : }
58 3237 : }
59 :
60 : static inline ulong
61 111 : fd_gui_hist_window( long ts_ns, long res_ns ) {
62 111 : return (ulong)( ts_ns / res_ns );
63 111 : }
64 :
65 : static int
66 11040 : fd_gui_hist_keyshape( int dbi ) {
67 11040 : switch( dbi ) {
68 222 : case FD_GUI_HIST_SCHEDULER_COUNTS:
69 318 : case FD_GUI_HIST_TILE_TIMERS:
70 414 : case FD_GUI_HIST_TILE_STATS:
71 510 : case FD_GUI_HIST_TXN_WATERFALL:
72 606 : case FD_GUI_HIST_TOWER:
73 3846 : case FD_GUI_HIST_SHRED_EVENTS:
74 3942 : case FD_GUI_HIST_TXN_START:
75 4038 : case FD_GUI_HIST_TXN_END: return FD_GUI_HIST_KEYSHAPE_TIMESERIES;
76 6435 : case FD_GUI_HIST_SLOT:
77 6720 : case FD_GUI_HIST_LEADER_SLOT: return FD_GUI_HIST_KEYSHAPE_SLOT_BANK;
78 282 : case FD_GUI_HIST_EPOCH: return FD_GUI_HIST_KEYSHAPE_EPOCH;
79 0 : default: FD_LOG_ERR(( "invalid dbi %d", dbi )); return -1;
80 11040 : }
81 11040 : }
82 :
83 : static inline int
84 7773 : fd_gui_hist_is_timeseries( int dbi ) {
85 7773 : return fd_gui_hist_keyshape( dbi )==FD_GUI_HIST_KEYSHAPE_TIMESERIES;
86 7773 : }
87 :
88 : static ulong
89 12597 : fd_gui_hist_slot_key_hash( void const * key ) {
90 12597 : fd_gui_hist_slot_key_t const * k = key;
91 12597 : return fd_ulong_hash( k->slot );
92 12597 : }
93 :
94 : static int
95 6369 : fd_gui_hist_slot_key_cmp( void const * a, void const * b ) {
96 6369 : fd_gui_hist_slot_key_t const * ka = a;
97 6369 : fd_gui_hist_slot_key_t const * kb = b;
98 6369 : if( ka->slot<kb->slot ) return -1;
99 3279 : if( ka->slot>kb->slot ) return 1;
100 3276 : if( ka->bank_seq==ULONG_MAX || kb->bank_seq==ULONG_MAX ) return 0;
101 3147 : if( ka->bank_seq<kb->bank_seq ) return -1;
102 3147 : if( ka->bank_seq>kb->bank_seq ) return 1;
103 3147 : return 0;
104 3147 : }
105 :
106 : static ulong
107 306 : fd_gui_hist_leader_slot_key_hash( void const * key ) {
108 306 : fd_gui_hist_leader_slot_key_t const * k = key;
109 306 : return fd_ulong_hash( k->slot );
110 306 : }
111 :
112 : static int
113 129 : fd_gui_hist_leader_slot_key_cmp( void const * a, void const * b ) {
114 129 : fd_gui_hist_leader_slot_key_t const * ka = a;
115 129 : fd_gui_hist_leader_slot_key_t const * kb = b;
116 129 : if( ka->slot<kb->slot ) return -1;
117 99 : if( ka->slot>kb->slot ) return 1;
118 99 : if( ka->bank_seq==ULONG_MAX || kb->bank_seq==ULONG_MAX ) return 0;
119 33 : if( ka->bank_seq<kb->bank_seq ) return -1;
120 33 : if( ka->bank_seq>kb->bank_seq ) return 1;
121 33 : return 0;
122 33 : }
123 :
124 : static ulong
125 342 : fd_gui_hist_epoch_key_hash( void const * key ) {
126 342 : fd_gui_hist_epoch_key_t const * k = key;
127 342 : return fd_ulong_hash( k->epoch );
128 342 : }
129 :
130 : static int
131 390 : fd_gui_hist_epoch_key_cmp( void const * a, void const * b ) {
132 390 : fd_gui_hist_epoch_key_t const * ka = a;
133 390 : fd_gui_hist_epoch_key_t const * kb = b;
134 390 : if( ka->epoch<kb->epoch ) return -1;
135 327 : if( ka->epoch>kb->epoch ) return 1;
136 201 : return 0;
137 327 : }
138 :
139 : static inline ulong
140 3360 : fd_gui_hist_rec_sz( int dbi ) {
141 3360 : switch( dbi ) {
142 3144 : case FD_GUI_HIST_SHRED_EVENTS: return sizeof(fd_gui_slot_history_shred_event_t);
143 3 : case FD_GUI_HIST_TILE_TIMERS: return sizeof(fd_gui_tile_timers_hist_t);
144 126 : case FD_GUI_HIST_SCHEDULER_COUNTS: return sizeof(fd_gui_scheduler_counts_t);
145 3 : case FD_GUI_HIST_TILE_STATS: return sizeof(fd_gui_tile_stats_t);
146 3 : case FD_GUI_HIST_TXN_WATERFALL: return sizeof(fd_gui_txn_waterfall_t);
147 3 : case FD_GUI_HIST_TXN_START: return sizeof(fd_gui_store_txn_start_t);
148 3 : case FD_GUI_HIST_TXN_END: return sizeof(fd_gui_store_txn_end_t);
149 24 : case FD_GUI_HIST_SLOT: return sizeof(fd_gui_slot_t);
150 24 : case FD_GUI_HIST_LEADER_SLOT: return sizeof(fd_gui_leader_slot_t);
151 24 : case FD_GUI_HIST_EPOCH: return sizeof(fd_gui_epoch_t);
152 : /* FD_GUI_HIST_TOWER is declared but not yet written (no record type). */
153 3 : default: return 0UL;
154 3360 : }
155 3360 : }
156 :
157 : /* fd_gui_hist_key_sz returns the KV key width for `dbi`: 16 bytes
158 : (slot, bank_seq) for slot-keyed DBs, 8 bytes (epoch) for EPOCH, 0 for TS. */
159 : static inline ulong
160 9 : fd_gui_hist_key_sz( int dbi ) {
161 9 : switch( dbi ) {
162 3 : case FD_GUI_HIST_SLOT: return sizeof(fd_gui_hist_slot_key_t);
163 3 : case FD_GUI_HIST_LEADER_SLOT: return sizeof(fd_gui_hist_leader_slot_key_t);
164 3 : case FD_GUI_HIST_EPOCH: return sizeof(fd_gui_hist_epoch_key_t);
165 0 : default: return 0UL; /* TS DBs have no key */
166 9 : }
167 9 : }
168 :
169 : /* fd_gui_hist_key_hash / fd_gui_hist_key_cmp return the per-DB KV key
170 : callbacks for `dbi` (NULL for TS DBs). */
171 :
172 : static inline ulong
173 9 : ( * fd_gui_hist_key_hash( int dbi ) )( void const * key ) {
174 9 : switch( dbi ) {
175 3 : case FD_GUI_HIST_SLOT: return fd_gui_hist_slot_key_hash;
176 3 : case FD_GUI_HIST_LEADER_SLOT: return fd_gui_hist_leader_slot_key_hash;
177 3 : case FD_GUI_HIST_EPOCH: return fd_gui_hist_epoch_key_hash;
178 0 : default: return NULL;
179 9 : }
180 9 : }
181 :
182 : static inline int
183 9 : ( * fd_gui_hist_key_cmp( int dbi ) )( void const * a, void const * b ) {
184 9 : switch( dbi ) {
185 3 : case FD_GUI_HIST_SLOT: return fd_gui_hist_slot_key_cmp;
186 3 : case FD_GUI_HIST_LEADER_SLOT: return fd_gui_hist_leader_slot_key_cmp;
187 3 : case FD_GUI_HIST_EPOCH: return fd_gui_hist_epoch_key_cmp;
188 0 : default: return NULL;
189 9 : }
190 9 : }
191 :
192 : /* fd_gui_hist_kv_stride approximates fd_gui_store's per-record ring stride
193 : for KV DB `dbi`. The store adds no header (the key and link live inside
194 : the value), so the stride is just the align-padded record. */
195 : static inline ulong
196 63 : fd_gui_hist_kv_stride( int dbi ) {
197 63 : return fd_ulong_align_up( fd_gui_hist_rec_sz( dbi ), 8UL );
198 63 : }
199 :
200 : static inline ulong
201 21 : fd_gui_hist_bytes_per_epoch( void ) {
202 21 : return fd_gui_hist_kv_stride( FD_GUI_HIST_EPOCH )
203 21 : + MAX_SLOTS_PER_EPOCH * ( fd_gui_hist_kv_stride( FD_GUI_HIST_SLOT ) + fd_gui_hist_kv_stride( FD_GUI_HIST_LEADER_SLOT ) );
204 21 : }
205 :
206 : fd_gui_store_desc_t const *
207 36 : fd_gui_hist_db_descs( ulong store_bytes ) {
208 36 : static char const * const names[ FD_GUI_HIST_CNT ] = {
209 36 : "scheduler_counts", "tile_timers", "shred_events", "txn_start",
210 36 : "txn_end", "tower", "slot", "leader_slot",
211 36 : "epoch", "tile_stats", "txn_waterfall"
212 36 : };
213 36 : static fd_gui_store_desc_t descs[ FD_GUI_HIST_CNT ];
214 36 : static ulong built_for = 0UL; /* store_bytes the table was built for (0 = unbuilt) */
215 :
216 36 : if( FD_UNLIKELY( built_for && built_for!=store_bytes ) )
217 0 : FD_LOG_ERR(( "fd_gui_hist_db_descs: called with %lu after %lu", store_bytes, built_for ));
218 :
219 36 : if( FD_UNLIKELY( !built_for ) ) {
220 3 : ulong per_epoch_bytes = fd_gui_hist_bytes_per_epoch();
221 3 : ulong epoch_n = fd_ulong_min( fd_ulong_max( FD_GUI_HIST_MIN_EPOCHS, store_bytes / fd_ulong_max( per_epoch_bytes, 1UL ) ), FD_GUI_HIST_MAX_EPOCHS );
222 3 : FD_TEST( FD_GUI_HIST_MAX_EPOCHS>=FD_GUI_HIST_MIN_EPOCHS );
223 :
224 36 : for( int i=0; i<FD_GUI_HIST_CNT; i++ ) {
225 33 : int ts = fd_gui_hist_is_timeseries( i );
226 33 : ulong rec_sz = fd_gui_hist_rec_sz( i );
227 33 : ulong key_sz = ts ? 0UL : fd_gui_hist_key_sz( i ); /* TS DBs have no key (0) */
228 33 : ulong val_sz = ts ? fd_ulong_max( rec_sz, 1UL ) : rec_sz;
229 33 : ulong max_records = 0UL;
230 33 : if( !ts ) {
231 9 : max_records = ( fd_gui_hist_keyshape( i )==FD_GUI_HIST_KEYSHAPE_EPOCH ) ? epoch_n : epoch_n * MAX_SLOTS_PER_EPOCH;
232 9 : }
233 :
234 33 : descs[ i ].name = names[ i ];
235 33 : descs[ i ].kind = ts ? FD_GUI_STORE_KIND_TS : FD_GUI_STORE_KIND_KV;
236 33 : descs[ i ].key_off = 0UL; /* the key is the leading field(s) of the value */
237 33 : descs[ i ].key_sz = key_sz;
238 33 : descs[ i ].key_hash = ts ? NULL : fd_gui_hist_key_hash( i );
239 33 : descs[ i ].key_cmp = ts ? NULL : fd_gui_hist_key_cmp( i );
240 33 : descs[ i ].val_sz = val_sz;
241 33 : descs[ i ].val_align = 8UL;
242 33 : descs[ i ].ts_off = ts ? fd_gui_hist_dbi_ts_off( i ) : 0UL;
243 33 : descs[ i ].granularity = ts ? (ulong)fd_gui_hist_dbi_res_ns( i ) : 0UL;
244 33 : descs[ i ].max_records = max_records;
245 33 : }
246 3 : FD_COMPILER_MFENCE();
247 3 : built_for = store_bytes;
248 3 : }
249 36 : return descs;
250 36 : }
251 :
252 : /* ---- space-pressure eviction -----------------------------------------
253 :
254 : Eviction is a small resumable state machine, advanced in small batches
255 : at an infrequent cadence. It evicts the oldest epoch as a whole, in
256 : phases:
257 :
258 : IDLE -> nothing in progress (the common case)
259 : SLOT -> deleting the epoch's (slot,bank_seq) KV rows
260 : TIMESERIES -> deleting the epoch's TS rows (by wallclock window)
261 : EPOCH -> deleting the EPOCH record itself, then -> IDLE
262 :
263 : The trigger is space, not age: eviction happens eagely as utilization
264 : crosses hysteresis thresholds. */
265 :
266 1026 : #define FD_GUI_HIST_EVICT_IDLE (0)
267 192 : #define FD_GUI_HIST_EVICT_SLOT (1)
268 609 : #define FD_GUI_HIST_EVICT_TIMESERIES (2)
269 126 : #define FD_GUI_HIST_EVICT_EPOCH (3)
270 :
271 : /* High/low water marks as a fraction (in 1/100ths) of the configured map
272 : size. */
273 0 : #define FD_GUI_HIST_EVICT_HIGH_PCT (99UL)
274 0 : #define FD_GUI_HIST_EVICT_LOW_PCT (95UL)
275 :
276 : /* Maximum number of records deleted per evition iteration. */
277 738 : #define FD_GUI_HIST_EVICT_BATCH (512UL)
278 :
279 : struct fd_gui_hist_private {
280 : ulong magic; /* ==FD_GUI_HIST_MAGIC after fd_gui_hist_new */
281 :
282 : struct {
283 : int armed; /* 1 once over the high-water mark, until under low */
284 : int phase; /* FD_GUI_HIST_EVICT_* */
285 : ulong epoch; /* epoch being evicted */
286 : ulong start_slot; /* first slot of the epoch (inclusive) */
287 : ulong end_slot; /* last slot of the epoch (inclusive) */
288 : ulong window_hi; /* last TS window of the epoch (inclusive) */
289 : int have_ts; /* 1 if the epoch has any TS to evict */
290 : int cur_dbi; /* DB the current phase is mid-scan on */
291 : } evict;
292 :
293 : fd_gui_hist_metrics_t metrics;
294 : };
295 :
296 : FD_FN_CONST ulong
297 396 : fd_gui_hist_align( void ) {
298 396 : return 128UL;
299 396 : }
300 :
301 : FD_FN_CONST ulong
302 54 : fd_gui_hist_footprint( void ) {
303 54 : ulong l = FD_LAYOUT_INIT;
304 54 : l = FD_LAYOUT_APPEND( l, fd_gui_hist_align(), sizeof(fd_gui_hist_t) );
305 54 : return FD_LAYOUT_FINI( l, fd_gui_hist_align() );
306 54 : }
307 :
308 : void *
309 : fd_gui_hist_new( void * mem,
310 18 : fd_gui_store_t const * db ) {
311 18 : if( FD_UNLIKELY( !mem ) ) { FD_LOG_WARNING(( "fd_gui_hist_new: null mem" )); return NULL; }
312 18 : if( FD_UNLIKELY( !fd_ulong_is_aligned( (ulong)mem, fd_gui_hist_align() ) ) ) { FD_LOG_WARNING(( "fd_gui_hist_new: misaligned mem" )); return NULL; }
313 18 : if( FD_UNLIKELY( !db ) ) { FD_LOG_WARNING(( "fd_gui_hist_new: null db" )); return NULL; }
314 :
315 18 : ulong store_bytes = fd_gui_store_size( db );
316 18 : ulong min_bytes = FD_GUI_HIST_MIN_EPOCHS * fd_gui_hist_bytes_per_epoch() + fd_gui_store_min_overhead_bytes();
317 18 : if( FD_UNLIKELY( store_bytes<min_bytes ) ) {
318 0 : FD_LOG_WARNING(( "fd_gui_hist_new: store size %lu bytes too small; must be >= %lu bytes", store_bytes, min_bytes ));
319 0 : return NULL;
320 0 : }
321 :
322 18 : fd_memset( mem, 0, sizeof(fd_gui_hist_t) );
323 18 : FD_SCRATCH_ALLOC_INIT( l, mem );
324 18 : fd_gui_hist_t * hist = FD_SCRATCH_ALLOC_APPEND( l, fd_gui_hist_align(), sizeof(fd_gui_hist_t) );
325 18 : hist->evict.phase = FD_GUI_HIST_EVICT_IDLE;
326 18 : FD_SCRATCH_ALLOC_FINI( l, fd_gui_hist_align() );
327 :
328 18 : FD_COMPILER_MFENCE();
329 18 : hist->magic = FD_GUI_HIST_MAGIC;
330 18 : FD_COMPILER_MFENCE();
331 18 : return mem;
332 18 : }
333 :
334 : fd_gui_hist_t *
335 18 : fd_gui_hist_join( void * mem ) {
336 18 : if( FD_UNLIKELY( !mem ) ) return NULL;
337 18 : fd_gui_hist_t * hist = (fd_gui_hist_t *)mem;
338 18 : if( FD_UNLIKELY( hist->magic!=FD_GUI_HIST_MAGIC ) ) { FD_LOG_WARNING(( "fd_gui_hist_join: bad magic" )); return NULL; }
339 18 : return hist;
340 18 : }
341 :
342 : void *
343 0 : fd_gui_hist_leave( fd_gui_hist_t * hist ) {
344 0 : return (void *)hist;
345 0 : }
346 :
347 : void *
348 0 : fd_gui_hist_delete( void * mem ) {
349 0 : if( FD_UNLIKELY( !mem ) ) return NULL;
350 0 : ((fd_gui_hist_t *)mem)->magic = 0UL;
351 0 : return mem;
352 0 : }
353 :
354 : static inline fd_gui_hist_t *
355 882 : fd_gui_hist( fd_gui_t * gui ) {
356 882 : return (fd_gui_hist_t *)gui->hist;
357 882 : }
358 :
359 : static inline fd_gui_store_t *
360 17418 : fd_gui_hist_db( fd_gui_t * gui ) {
361 17418 : return (fd_gui_store_t *)gui->db;
362 17418 : }
363 :
364 : static int
365 0 : fd_gui_hist_reserve_evict_step( fd_gui_t * gui ) {
366 0 : if( FD_LIKELY( fd_gui_hist_evict_oldest( gui ) ) ) return 1;
367 0 : return fd_gui_hist_evict_ts_oldest( gui );
368 0 : }
369 :
370 : static int
371 6573 : fd_gui_hist_reserve( fd_gui_t * gui, int dbi ) {
372 6573 : fd_gui_store_t * db = fd_gui_hist_db( gui );
373 6573 : (void)dbi;
374 6573 : if( FD_LIKELY( fd_gui_store_free_region_cnt( db )>0UL ) ) return 0; /* fast path: room already */
375 :
376 0 : int evicted = 0;
377 0 : while( fd_gui_store_free_region_cnt( db )==0UL ) {
378 0 : if( FD_UNLIKELY( !fd_gui_hist_reserve_evict_step( gui ) ) ) break; /* genuinely full */
379 0 : evicted = 1;
380 0 : }
381 0 : return evicted;
382 6573 : }
383 :
384 : static int
385 0 : fd_gui_hist_map_full_evict_step( fd_gui_t * gui ) {
386 0 : return fd_gui_hist_reserve_evict_step( gui );
387 0 : }
388 :
389 : fd_gui_hist_metrics_t const *
390 0 : fd_gui_hist_metrics( fd_gui_t const * gui ) {
391 0 : if( FD_UNLIKELY( !gui->hist ) ) return NULL;
392 0 : return &((fd_gui_hist_t const *)gui->hist)->metrics;
393 0 : }
394 :
395 : void *
396 : fd_gui_hist_kv_get_or_create( fd_gui_t * gui,
397 : int dbi,
398 3360 : void const * key ) {
399 3360 : if( FD_UNLIKELY( fd_gui_hist_is_timeseries( dbi ) ) ) { FD_LOG_WARNING(( "fd_gui_hist_kv_get_or_create: dbi %d is time-series", dbi )); return NULL; }
400 :
401 3360 : int forced_eviction = fd_gui_hist_reserve( gui, dbi );
402 3360 : void * val = NULL;
403 3360 : int rc;
404 3360 : for(;;) {
405 3360 : rc = fd_gui_store_kv_get_or_create( fd_gui_hist_db( gui ), (ulong)dbi, key, &val );
406 3360 : if( FD_LIKELY( rc==FD_GUI_STORE_SUCCESS ) ) {
407 3360 : if( FD_UNLIKELY( forced_eviction ) ) fd_gui_hist( gui )->metrics.reserves[ dbi ]++;
408 3360 : return val;
409 3360 : }
410 0 : if( FD_LIKELY( rc!=FD_GUI_STORE_MAP_FULL ) ) break;
411 0 : if( FD_UNLIKELY( !fd_gui_hist_map_full_evict_step( gui ) ) ) break;
412 0 : forced_eviction = 1;
413 0 : }
414 0 : if( FD_UNLIKELY( rc==FD_GUI_STORE_MAP_FULL ) ) {
415 0 : fd_gui_hist( gui )->metrics.map_full[ dbi ]++;
416 0 : FD_LOG_WARNING(( "fd_gui_hist_kv_get_or_create: dropping a record for dbi %d; store full and nothing left to evict", dbi ));
417 0 : }
418 0 : return NULL;
419 3360 : }
420 :
421 : int
422 : fd_gui_hist_ts_append( fd_gui_t * gui,
423 : int dbi,
424 : long now,
425 : long ts_ns,
426 3213 : void const * val ) {
427 3213 : if( FD_UNLIKELY( dbi<0 || dbi>=FD_GUI_HIST_CNT ) ) { FD_LOG_WARNING(( "fd_gui_hist_ts_append: bad dbi %d", dbi )); return -1; }
428 3213 : if( FD_UNLIKELY( !fd_gui_hist_is_timeseries( dbi ) ) ) { FD_LOG_WARNING(( "fd_gui_hist_ts_append: dbi %d is not time-series", dbi )); return -1; }
429 3213 : fd_gui_store_t * db = fd_gui_hist_db( gui );
430 :
431 3213 : ulong rec_sz = fd_gui_hist_rec_sz( dbi );
432 3213 : if( FD_UNLIKELY( !rec_sz ) ) { FD_LOG_WARNING(( "fd_gui_hist_ts_append: dbi %d has no record type", dbi )); return -1; }
433 :
434 : /* Clamp the record's timestamp to a bounded skew around `now` and
435 : write the clamped value back into the record. */
436 3213 : long clamped_ts = fd_long_max( now-FD_GUI_HIST_TS_SKEW_NS, fd_long_min( ts_ns, now+FD_GUI_HIST_TS_SKEW_NS ) );
437 :
438 3213 : uchar buf[ FD_GUI_HIST_TS_SZ_MAX ];
439 3213 : if( FD_UNLIKELY( rec_sz>sizeof(buf) ) ) { FD_LOG_WARNING(( "fd_gui_hist_ts_append: dbi %d record too large (%lu)", dbi, rec_sz )); return -1; }
440 3213 : fd_memcpy( buf, val, rec_sz );
441 3213 : *(long *)( buf + fd_gui_hist_dbi_ts_off( dbi ) ) = clamped_ts;
442 :
443 : /* Reserve space ahead of the append. */
444 3213 : int forced_eviction = fd_gui_hist_reserve( gui, dbi );
445 3213 : int rc;
446 3213 : for(;;) {
447 3213 : rc = fd_gui_store_ts_append( db, (ulong)dbi, buf );
448 3213 : if( FD_LIKELY( rc==FD_GUI_STORE_SUCCESS ) ) {
449 3213 : if( FD_UNLIKELY( forced_eviction ) ) fd_gui_hist( gui )->metrics.reserves[ dbi ]++;
450 3213 : return 0;
451 3213 : }
452 0 : if( FD_LIKELY( rc!=FD_GUI_STORE_MAP_FULL ) ) break;
453 0 : if( FD_UNLIKELY( !fd_gui_hist_map_full_evict_step( gui ) ) ) break;
454 0 : forced_eviction = 1;
455 0 : }
456 0 : if( FD_UNLIKELY( rc==FD_GUI_STORE_MAP_FULL ) ) {
457 0 : fd_gui_hist( gui )->metrics.map_full[ dbi ]++;
458 0 : FD_LOG_WARNING(( "fd_gui_hist_ts_append: dropping a record for dbi %d; store full and nothing left to evict", dbi ));
459 0 : }
460 0 : return -1;
461 3213 : }
462 :
463 :
464 : static void
465 3753 : fd_gui_hist_iter_load( fd_gui_hist_iter_t * iter ) {
466 3753 : for(;;) {
467 3753 : if( fd_gui_store_ts_scan_done( &iter->_it ) ) { iter->rec = NULL; return; }
468 3702 : void const * rec = iter->_it.rec; /* the gui record verbatim; self-describing */
469 3702 : if( !iter->_filter || iter->_filter( rec, iter->_filter_ctx ) ) {
470 3702 : iter->rec = rec;
471 3702 : return;
472 3702 : }
473 0 : fd_gui_store_ts_scan_next( &iter->_it );
474 0 : }
475 3753 : }
476 :
477 : int
478 : fd_gui_hist_range_begin( fd_gui_t * gui,
479 : fd_gui_hist_iter_t * iter,
480 : int dbi,
481 : long lo_ns,
482 : long hi_ns,
483 : fd_gui_hist_range_filter_fn filter,
484 51 : void * filter_ctx ) {
485 51 : fd_gui_store_t * db = fd_gui_hist_db( gui );
486 51 : memset( iter, 0, sizeof(fd_gui_hist_iter_t) );
487 :
488 51 : if( FD_UNLIKELY( !fd_gui_hist_is_timeseries( dbi ) ) ) { FD_LOG_WARNING(( "fd_gui_hist_range_begin: dbi %d is not time-series", dbi )); return -1; }
489 :
490 51 : FD_TEST( lo_ns!=LONG_MIN && hi_ns!=LONG_MAX ); /* open-ended queries not supported */
491 :
492 51 : long res_ns = fd_gui_hist_dbi_res_ns( dbi );
493 51 : ulong window_lo = (lo_ns<=0L) ? 0UL : fd_gui_hist_window( lo_ns, res_ns );
494 51 : ulong window_hi = fd_gui_hist_window( hi_ns, res_ns );
495 :
496 51 : iter->_dbi = dbi;
497 51 : iter->rec_sz = fd_gui_hist_rec_sz( dbi );
498 51 : iter->_filter = filter;
499 51 : iter->_filter_ctx = filter_ctx;
500 :
501 51 : fd_gui_store_ts_scan_begin( db, &iter->_it, (ulong)dbi, window_lo, window_hi, NULL, NULL );
502 51 : fd_gui_hist_iter_load( iter );
503 51 : return 0;
504 51 : }
505 :
506 : int
507 3753 : fd_gui_hist_range_next( fd_gui_hist_iter_t * iter ) {
508 3753 : if( FD_UNLIKELY( !iter->rec ) ) return 0;
509 3744 : if( iter->_emitted ) {
510 3702 : fd_gui_store_ts_scan_next( &iter->_it );
511 3702 : fd_gui_hist_iter_load( iter );
512 3702 : if( !iter->rec ) return 0;
513 3702 : }
514 3702 : iter->_emitted = 1;
515 3702 : return 1;
516 3744 : }
517 :
518 : void
519 51 : fd_gui_hist_range_end( fd_gui_hist_iter_t * iter ) {
520 51 : fd_gui_store_ts_scan_end( &iter->_it );
521 51 : }
522 :
523 : void *
524 : fd_gui_hist_kv_get( fd_gui_t * gui,
525 : int dbi,
526 87 : void const * key ) {
527 87 : if( FD_UNLIKELY( fd_gui_hist_is_timeseries( dbi ) ) ) { FD_LOG_WARNING(( "fd_gui_hist_meta_get: dbi %d is time-series", dbi )); return NULL; }
528 87 : return fd_gui_store_kv_get( fd_gui_hist_db( gui ), (ulong)dbi, key );
529 87 : }
530 :
531 : void *
532 : fd_gui_hist_kv_get_slot_any( fd_gui_t * gui,
533 : int dbi,
534 3258 : ulong slot ) {
535 3258 : if( FD_UNLIKELY( fd_gui_hist_keyshape( dbi )!=FD_GUI_HIST_KEYSHAPE_SLOT_BANK ) ) {
536 0 : FD_LOG_WARNING(( "fd_gui_hist_kv_get_slot_any: dbi %d is not slot/bank-keyed", dbi ));
537 0 : return NULL;
538 0 : }
539 :
540 3258 : fd_gui_hist_slot_key_t key = { .slot=slot, .bank_seq=ULONG_MAX };
541 3258 : return fd_gui_store_kv_get_any( fd_gui_hist_db( gui ), (ulong)dbi, &key );
542 3258 : }
543 :
544 : /* fd_gui_hist_ts_iter_load reads the iterator's current backend record into
545 : the typed iterator fields (or marks it done). */
546 : static void
547 0 : fd_gui_hist_ts_iter_load( fd_gui_hist_kv_slot_iter_t * iter ) {
548 0 : if( FD_UNLIKELY( fd_gui_store_kv_iter_done( &iter->_it ) ) ) {
549 0 : iter->rec = NULL;
550 0 : iter->bank_seq = ULONG_MAX;
551 0 : return;
552 0 : }
553 0 : iter->rec = iter->_it.rec;
554 0 : iter->bank_seq = ( iter->_it.key_sz>=2UL*sizeof(ulong) )
555 0 : ? ((ulong const *)iter->_it.key)[ 1 ]
556 0 : : ULONG_MAX;
557 0 : }
558 :
559 : fd_gui_hist_kv_slot_iter_t *
560 : fd_gui_hist_kv_iter_begin( fd_gui_t * gui,
561 : fd_gui_hist_kv_slot_iter_t * iter,
562 : int dbi,
563 0 : ulong slot ) {
564 0 : iter->rec = NULL;
565 0 : iter->bank_seq = ULONG_MAX;
566 0 : if( FD_UNLIKELY( fd_gui_hist_keyshape( dbi )!=FD_GUI_HIST_KEYSHAPE_SLOT_BANK ) ) {
567 0 : FD_LOG_WARNING(( "fd_gui_hist_kv_iter_begin: dbi %d is not slot/bank-keyed", dbi ));
568 0 : fd_gui_hist_slot_key_t none = { .slot=ULONG_MAX, .bank_seq=ULONG_MAX };
569 0 : fd_gui_store_kv_iter_begin( fd_gui_hist_db( gui ), &iter->_it, (ulong)dbi, &none );
570 0 : return iter;
571 0 : }
572 0 : fd_gui_hist_slot_key_t key = { .slot=slot, .bank_seq=ULONG_MAX };
573 0 : fd_gui_store_kv_iter_begin( fd_gui_hist_db( gui ), &iter->_it, (ulong)dbi, &key );
574 0 : fd_gui_hist_ts_iter_load( iter );
575 0 : return iter;
576 0 : }
577 :
578 : int
579 0 : fd_gui_hist_kv_iter_next( fd_gui_hist_kv_slot_iter_t * iter ) {
580 0 : if( FD_UNLIKELY( !iter->rec ) ) return 0;
581 0 : fd_gui_store_kv_iter_next( &iter->_it );
582 0 : fd_gui_hist_ts_iter_load( iter );
583 0 : return iter->rec!=NULL;
584 0 : }
585 :
586 : /* fd_gui_hist_evict_used_pct returns the store's high-water-mark fill level
587 : as a percentage (0..100) of its configured map size. */
588 :
589 : static ulong
590 0 : fd_gui_hist_evict_used_pct( fd_gui_t * gui ) {
591 0 : fd_gui_store_t * db = fd_gui_hist_db( gui );
592 0 : ulong size = fd_gui_store_size( db );
593 0 : if( FD_UNLIKELY( !size ) ) return 0UL;
594 0 : return ( fd_gui_store_used_bytes( db ) * 100UL ) / size;
595 0 : }
596 :
597 : /* fd_gui_hist_evict_slot_completed_window returns, in *out_window, the
598 : wallclock window (floored completion time) of the lowest-bank_seq
599 : SLOT record for `slot`, or 0 if there is no such record (or it
600 : has no completion time). Used to bound the TS eviction window.
601 : Returns 1 on success. */
602 :
603 : static int
604 : fd_gui_hist_evict_slot_completed_window( fd_gui_t * gui,
605 : ulong slot,
606 63 : ulong * out_window ) {
607 63 : fd_gui_slot_t const * rmeta = fd_gui_hist_kv_get_slot_any( gui, FD_GUI_HIST_SLOT, slot );
608 63 : if( FD_UNLIKELY( !rmeta ) ) return 0;
609 60 : if( FD_UNLIKELY( rmeta->completed_time==LONG_MAX ) ) return 0;
610 60 : *out_window = fd_gui_hist_window( rmeta->completed_time, FD_GUI_HIST_RES_1S_NS );
611 60 : return 1;
612 60 : }
613 :
614 : /* fd_gui_hist_evict_begin sets up an eviction cascade for the oldest epoch.
615 : Returns 1 if a cascade was armed (state populated, phase advanced past
616 : IDLE), 0 if there is nothing to evict. */
617 :
618 : static int
619 72 : fd_gui_hist_evict_begin( fd_gui_t * gui ) {
620 72 : fd_gui_hist_t * hist = fd_gui_hist( gui );
621 :
622 : /* Make sure we always keep the current/next epoch. */
623 72 : if( FD_UNLIKELY( gui->epoch.stored_epoch_cnt<FD_GUI_HIST_MIN_EPOCHS ) ) return 0;
624 :
625 63 : fd_gui_epoch_t const * meta = fd_gui_store_kv_get_any( fd_gui_hist_db( gui ), (ulong)FD_GUI_HIST_EPOCH, NULL );
626 63 : if( FD_UNLIKELY( !meta ) ) return 0;
627 63 : ulong epoch = meta->epoch;
628 63 : ulong meta_start = meta->start_slot;
629 63 : ulong meta_cnt = meta->slot_cnt;
630 63 : ulong start_slot = meta_start;
631 63 : ulong end_slot = meta_start + meta_cnt - 1UL;
632 63 : ulong next_start = meta_start + meta_cnt; /* next epoch's first slot; always valid (>= FD_GUI_HIST_MIN_EPOCHS resident) */
633 :
634 63 : ulong window_hi = 0UL;
635 63 : int have_ts = 0;
636 63 : ulong next_window;
637 63 : if( FD_LIKELY( fd_gui_hist_evict_slot_completed_window( gui, next_start, &next_window ) ) ) {
638 60 : window_hi = ( next_window>0UL ) ? ( next_window-1UL ) : 0UL;
639 60 : have_ts = next_window>0UL;
640 60 : }
641 :
642 63 : hist->evict.epoch = epoch;
643 63 : hist->evict.start_slot = start_slot;
644 63 : hist->evict.end_slot = end_slot;
645 63 : hist->evict.window_hi = window_hi;
646 63 : hist->evict.have_ts = have_ts;
647 63 : hist->evict.phase = FD_GUI_HIST_EVICT_SLOT;
648 63 : hist->evict.cur_dbi = FD_GUI_HIST_SLOT;
649 63 : return 1;
650 63 : }
651 :
652 : /* fd_gui_hist_evict_slot_batch advances KV DB `dbi`'s watermark to
653 : evict the (slot,bank_seq) rows with slot <= end_slot, decrementing
654 : *budget per reclaimed row. Returns 1 if the DB's range is fully
655 : drained, 0 if it stopped because the budget ran out. */
656 :
657 : static int
658 : fd_gui_hist_evict_slot_batch( fd_gui_t * gui,
659 : int dbi,
660 : ulong end_slot,
661 129 : ulong * budget ) {
662 129 : fd_gui_hist_slot_key_t hi = { .slot=end_slot+1UL, .bank_seq=0UL };
663 129 : int drained = 1;
664 129 : fd_gui_store_kv_evict( fd_gui_hist_db( gui ), (ulong)dbi, &hi, budget, &drained );
665 129 : return drained;
666 129 : }
667 :
668 : /* fd_gui_hist_evict_ts_batch advances TS DB `dbi`'s watermark to evict
669 : rows with window <= window_hi, decrementing *budget per reclaimed row.
670 : Returns 1 if drained, 0 if it stopped on the budget. */
671 :
672 : static int
673 : fd_gui_hist_evict_ts_batch( fd_gui_t * gui,
674 : int dbi,
675 : ulong window_hi,
676 483 : ulong * budget ) {
677 483 : int drained = 1;
678 483 : fd_gui_store_ts_evict( fd_gui_hist_db( gui ), (ulong)dbi, window_hi+1UL, budget, &drained );
679 483 : return drained;
680 483 : }
681 :
682 : /* fd_gui_hist_evict_one advances an in-progress cascade by one bounded batch.
683 : Returns 1 (it always does work, or
684 : resolves the cascade, when not IDLE). */
685 :
686 : static int
687 738 : fd_gui_hist_evict_one( fd_gui_t * gui ) {
688 738 : fd_gui_hist_t * hist = fd_gui_hist( gui );
689 738 : ulong budget = FD_GUI_HIST_EVICT_BATCH;
690 :
691 738 : switch( hist->evict.phase ) {
692 :
693 129 : case FD_GUI_HIST_EVICT_SLOT: {
694 129 : int drained = fd_gui_hist_evict_slot_batch( gui, hist->evict.cur_dbi, hist->evict.end_slot, &budget );
695 129 : if( !drained ) return 1; /* budget spent on this DB; resume next step */
696 : /* advance to the next slot-keyed KV DB, or to the TS phase */
697 126 : if( hist->evict.cur_dbi==FD_GUI_HIST_SLOT ) {
698 63 : hist->evict.cur_dbi = FD_GUI_HIST_LEADER_SLOT;
699 63 : } else {
700 63 : hist->evict.phase = FD_GUI_HIST_EVICT_TIMESERIES;
701 63 : hist->evict.cur_dbi = 0; /* fd_gui_hist_evict_one finds the first TS DB below */
702 63 : }
703 126 : return 1;
704 129 : }
705 :
706 546 : case FD_GUI_HIST_EVICT_TIMESERIES: {
707 : /* skip non-TS DBs (the KV DBs interleave by index) */
708 726 : while( hist->evict.cur_dbi<FD_GUI_HIST_CNT && !fd_gui_hist_is_timeseries( hist->evict.cur_dbi ) ) hist->evict.cur_dbi++;
709 546 : if( hist->evict.cur_dbi>=FD_GUI_HIST_CNT || !hist->evict.have_ts ) {
710 63 : hist->evict.phase = FD_GUI_HIST_EVICT_EPOCH;
711 63 : return 1;
712 63 : }
713 483 : int drained = fd_gui_hist_evict_ts_batch( gui, hist->evict.cur_dbi, hist->evict.window_hi, &budget );
714 483 : if( !drained ) return 1;
715 480 : hist->evict.cur_dbi++; /* next step picks up the next TS DB (or the EPOCH phase) */
716 480 : return 1;
717 483 : }
718 :
719 63 : case FD_GUI_HIST_EVICT_EPOCH: {
720 63 : fd_gui_hist_epoch_key_t hi = { .epoch=hist->evict.epoch+1UL };
721 :
722 63 : fd_gui_store_t * db = fd_gui_hist_db( gui );
723 63 : int drained = 1;
724 63 : fd_gui_store_kv_evict( db, (ulong)FD_GUI_HIST_EPOCH, &hi, &budget, &drained );
725 63 : if( !drained ) return 1; /* budget spent; resume this phase next step */
726 63 : if( FD_LIKELY( gui->epoch.stored_epoch_cnt ) ) gui->epoch.stored_epoch_cnt--;
727 :
728 63 : hist->evict.phase = FD_GUI_HIST_EVICT_IDLE; /* cascade complete */
729 63 : return 1;
730 63 : }
731 :
732 0 : default:
733 0 : hist->evict.phase = FD_GUI_HIST_EVICT_IDLE;
734 0 : return 0;
735 738 : }
736 738 : }
737 :
738 : int
739 0 : fd_gui_hist_evict_step( fd_gui_t * gui ) {
740 0 : if( FD_UNLIKELY( !gui->db || !gui->hist ) ) return 0;
741 0 : fd_gui_hist_t * hist = fd_gui_hist( gui );
742 :
743 0 : if( hist->evict.phase==FD_GUI_HIST_EVICT_IDLE ) {
744 0 : ulong used_pct = fd_gui_hist_evict_used_pct( gui );
745 0 : if( used_pct>=FD_GUI_HIST_EVICT_HIGH_PCT ) hist->evict.armed = 1;
746 0 : else if( used_pct<FD_GUI_HIST_EVICT_LOW_PCT ) hist->evict.armed = 0;
747 0 : if( !hist->evict.armed ) return 0;
748 0 : if( FD_UNLIKELY( !fd_gui_hist_evict_begin( gui ) ) ) { hist->evict.armed = 0; return 0; } /* nothing to evict */
749 0 : return 1;
750 0 : }
751 :
752 0 : return fd_gui_hist_evict_one( gui );
753 0 : }
754 :
755 : int
756 72 : fd_gui_hist_evict_oldest( fd_gui_t * gui ) {
757 72 : fd_gui_hist_t * hist = fd_gui_hist( gui );
758 72 : if( FD_UNLIKELY( !gui->db || !gui->hist ) ) return 0;
759 72 : if( hist->evict.phase==FD_GUI_HIST_EVICT_IDLE && FD_UNLIKELY( !fd_gui_hist_evict_begin( gui ) ) ) return 0;
760 801 : while( hist->evict.phase!=FD_GUI_HIST_EVICT_IDLE ) fd_gui_hist_evict_one( gui );
761 63 : return 1;
762 72 : }
763 :
764 : /* fd_gui_hist_ts_oldest_window finds the oldest live window across all
765 : TS DBs (the global minimum of each DB's oldest live record's window).
766 : Stores it in *out_window and returns 1 if any TS record exists, 0 if
767 : every TS DB is empty. O(1) per DB: no scan. */
768 :
769 : static int
770 : fd_gui_hist_ts_oldest_window( fd_gui_t * gui,
771 18 : ulong * out_window ) {
772 18 : fd_gui_store_t * db = fd_gui_hist_db( gui );
773 18 : ulong oldest = ULONG_MAX;
774 18 : int found = 0;
775 216 : for( int dbi=0; dbi<FD_GUI_HIST_CNT; dbi++ ) {
776 198 : if( !fd_gui_hist_is_timeseries( dbi ) ) continue;
777 144 : ulong window;
778 144 : if( fd_gui_store_ts_oldest_window( db, (ulong)dbi, &window ) ) {
779 30 : found = 1;
780 30 : oldest = fd_ulong_min( oldest, window );
781 30 : }
782 144 : }
783 18 : if( FD_UNLIKELY( !found ) ) return 0;
784 15 : *out_window = oldest;
785 15 : return 1;
786 18 : }
787 :
788 : int
789 18 : fd_gui_hist_evict_ts_oldest( fd_gui_t * gui ) {
790 18 : if( FD_UNLIKELY( !gui->db || !gui->hist ) ) return 0;
791 :
792 18 : ulong oldest;
793 18 : if( FD_UNLIKELY( !fd_gui_hist_ts_oldest_window( gui, &oldest ) ) ) return 0; /* no TS data left */
794 :
795 180 : for( int dbi=0; dbi<FD_GUI_HIST_CNT; dbi++ ) {
796 165 : if( !fd_gui_hist_is_timeseries( dbi ) ) continue;
797 120 : ulong budget = ULONG_MAX;
798 120 : int drained = 1;
799 120 : fd_gui_store_ts_evict( fd_gui_hist_db( gui ), (ulong)dbi, oldest+1UL, &budget, &drained );
800 120 : }
801 15 : return 1;
802 18 : }
|