Line data Source code
1 : #include "fd_gui_hist.h"
2 : #include "fd_gui_store.h"
3 : #include "fd_gui_shred.h"
4 : #include "fd_gui.h" /* fd_gui_t, record types */
5 :
6 : #include <stddef.h> /* offsetof */
7 :
8 : /* Every record type must fit in one store region (header + record). */
9 : FD_STATIC_ASSERT( sizeof(fd_gui_shred_batch_t )<=FD_GUI_STORE_MAX_REC_SZ, rec_fits );
10 : FD_STATIC_ASSERT( sizeof(fd_gui_tile_timers_hist_t)<=FD_GUI_STORE_MAX_REC_SZ, rec_fits );
11 : FD_STATIC_ASSERT( sizeof(fd_gui_scheduler_counts_t)<=FD_GUI_STORE_MAX_REC_SZ, rec_fits );
12 : FD_STATIC_ASSERT( sizeof(fd_gui_tile_stats_t )<=FD_GUI_STORE_MAX_REC_SZ, rec_fits );
13 : FD_STATIC_ASSERT( sizeof(fd_gui_txn_waterfall_t )<=FD_GUI_STORE_MAX_REC_SZ, rec_fits );
14 : FD_STATIC_ASSERT( sizeof(fd_gui_store_txn_start_t )<=FD_GUI_STORE_MAX_REC_SZ, rec_fits );
15 : FD_STATIC_ASSERT( sizeof(fd_gui_store_txn_end_t )<=FD_GUI_STORE_MAX_REC_SZ, rec_fits );
16 : FD_STATIC_ASSERT( sizeof(fd_gui_store_replay_txn_t)<=FD_GUI_STORE_MAX_REC_SZ, rec_fits );
17 : FD_STATIC_ASSERT( sizeof(fd_gui_slot_t )<=FD_GUI_STORE_MAX_REC_SZ, rec_fits );
18 : FD_STATIC_ASSERT( sizeof(fd_gui_leader_slot_t )<=FD_GUI_STORE_MAX_REC_SZ, rec_fits );
19 : FD_STATIC_ASSERT( sizeof(fd_gui_epoch_t )<=FD_GUI_STORE_MAX_REC_SZ, rec_fits );
20 : FD_STATIC_ASSERT( sizeof(fd_gui_timeline_day_t )<=FD_GUI_STORE_MAX_REC_SZ, rec_fits );
21 :
22 : /* Each key type must alias the matching record field exactly. */
23 : FD_STATIC_ASSERT( offsetof( fd_gui_hist_slot_key_t, slot )==offsetof( fd_gui_slot_t, slot ), key_layout );
24 : FD_STATIC_ASSERT( offsetof( fd_gui_hist_slot_key_t, bank_seq )==offsetof( fd_gui_slot_t, bank_seq ), key_layout );
25 : FD_STATIC_ASSERT( offsetof( fd_gui_hist_leader_slot_key_t, slot )==offsetof( fd_gui_leader_slot_t, slot ), key_layout );
26 : FD_STATIC_ASSERT( offsetof( fd_gui_hist_leader_slot_key_t, bank_seq )==offsetof( fd_gui_leader_slot_t, bank_seq ), key_layout );
27 : FD_STATIC_ASSERT( offsetof( fd_gui_hist_epoch_key_t, epoch )==offsetof( fd_gui_epoch_t, epoch ), key_layout );
28 :
29 0 : #define FD_GUI_HIST_KEYSHAPE_TIMESERIES (0) /* (ts, ...) */
30 0 : #define FD_GUI_HIST_KEYSHAPE_SLOT_BANK (1) /* (slot, bank_seq) */
31 0 : #define FD_GUI_HIST_KEYSHAPE_EPOCH (2) /* (epoch) */
32 :
33 : static inline long
34 0 : fd_gui_hist_dbi_res_ns( int dbi FD_PARAM_UNUSED ) {
35 0 : return FD_GUI_HIST_RES_1S_NS; /* all TS DBs use the 1s resolution */
36 0 : }
37 :
38 : static inline ulong
39 0 : fd_gui_hist_dbi_ts_off( int dbi ) {
40 0 : switch( dbi ) {
41 0 : case FD_GUI_HIST_SHRED_EVENTS: return offsetof( fd_gui_shred_batch_t, insert_time_ns );
42 0 : case FD_GUI_HIST_TILE_TIMERS: return offsetof( fd_gui_tile_timers_hist_t, sample_time_nanos );
43 0 : case FD_GUI_HIST_SCHEDULER_COUNTS: return offsetof( fd_gui_scheduler_counts_t, sample_time_ns );
44 0 : case FD_GUI_HIST_TILE_STATS: return offsetof( fd_gui_tile_stats_t, sample_time_nanos );
45 0 : case FD_GUI_HIST_TXN_WATERFALL: return offsetof( fd_gui_txn_waterfall_t, sample_time_nanos );
46 0 : case FD_GUI_HIST_TXN_START: return offsetof( fd_gui_store_txn_start_t, insert_time_ns );
47 0 : case FD_GUI_HIST_TXN_END: return offsetof( fd_gui_store_txn_end_t, insert_time_ns );
48 0 : case FD_GUI_HIST_REPLAY_TXN: return offsetof( fd_gui_store_replay_txn_t, insert_time_ns );
49 0 : case FD_GUI_HIST_TIMELINE_DAY: return offsetof( fd_gui_timeline_day_t, end_time_ns );
50 0 : default: return 0UL;
51 0 : }
52 0 : }
53 :
54 : static inline ulong
55 0 : fd_gui_hist_window( long ts_ns, long res_ns ) {
56 0 : return (ulong)( ts_ns / res_ns );
57 0 : }
58 :
59 : static int
60 0 : fd_gui_hist_keyshape( int dbi ) {
61 0 : switch( dbi ) {
62 0 : case FD_GUI_HIST_SCHEDULER_COUNTS:
63 0 : case FD_GUI_HIST_TILE_TIMERS:
64 0 : case FD_GUI_HIST_TILE_STATS:
65 0 : case FD_GUI_HIST_TXN_WATERFALL:
66 0 : case FD_GUI_HIST_SHRED_EVENTS:
67 0 : case FD_GUI_HIST_TXN_START:
68 0 : case FD_GUI_HIST_TXN_END: return FD_GUI_HIST_KEYSHAPE_TIMESERIES;
69 0 : case FD_GUI_HIST_REPLAY_TXN:
70 0 : case FD_GUI_HIST_TIMELINE_DAY: return FD_GUI_HIST_KEYSHAPE_TIMESERIES;
71 0 : case FD_GUI_HIST_SLOT:
72 0 : case FD_GUI_HIST_LEADER_SLOT: return FD_GUI_HIST_KEYSHAPE_SLOT_BANK;
73 0 : case FD_GUI_HIST_EPOCH: return FD_GUI_HIST_KEYSHAPE_EPOCH;
74 0 : default: FD_LOG_ERR(( "invalid dbi %d", dbi )); return -1;
75 0 : }
76 0 : }
77 :
78 : static inline int
79 0 : fd_gui_hist_is_timeseries( int dbi ) {
80 0 : return fd_gui_hist_keyshape( dbi )==FD_GUI_HIST_KEYSHAPE_TIMESERIES;
81 0 : }
82 :
83 : static ulong
84 0 : fd_gui_hist_slot_key_hash( void const * key ) {
85 0 : fd_gui_hist_slot_key_t const * k = key;
86 0 : return fd_ulong_hash( k->slot );
87 0 : }
88 :
89 : static int
90 0 : fd_gui_hist_slot_key_cmp( void const * a, void const * b ) {
91 0 : fd_gui_hist_slot_key_t const * ka = a;
92 0 : fd_gui_hist_slot_key_t const * kb = b;
93 0 : if( ka->slot<kb->slot ) return -1;
94 0 : if( ka->slot>kb->slot ) return 1;
95 0 : if( ka->bank_seq==ULONG_MAX || kb->bank_seq==ULONG_MAX ) return 0;
96 0 : if( ka->bank_seq<kb->bank_seq ) return -1;
97 0 : if( ka->bank_seq>kb->bank_seq ) return 1;
98 0 : return 0;
99 0 : }
100 :
101 : static ulong
102 0 : fd_gui_hist_leader_slot_key_hash( void const * key ) {
103 0 : fd_gui_hist_leader_slot_key_t const * k = key;
104 0 : return fd_ulong_hash( k->slot );
105 0 : }
106 :
107 : static int
108 0 : fd_gui_hist_leader_slot_key_cmp( void const * a, void const * b ) {
109 0 : fd_gui_hist_leader_slot_key_t const * ka = a;
110 0 : fd_gui_hist_leader_slot_key_t const * kb = b;
111 0 : if( ka->slot<kb->slot ) return -1;
112 0 : if( ka->slot>kb->slot ) return 1;
113 0 : if( ka->bank_seq==ULONG_MAX || kb->bank_seq==ULONG_MAX ) return 0;
114 0 : if( ka->bank_seq<kb->bank_seq ) return -1;
115 0 : if( ka->bank_seq>kb->bank_seq ) return 1;
116 0 : return 0;
117 0 : }
118 :
119 : static ulong
120 0 : fd_gui_hist_epoch_key_hash( void const * key ) {
121 0 : fd_gui_hist_epoch_key_t const * k = key;
122 0 : return fd_ulong_hash( k->epoch );
123 0 : }
124 :
125 : static int
126 0 : fd_gui_hist_epoch_key_cmp( void const * a, void const * b ) {
127 0 : fd_gui_hist_epoch_key_t const * ka = a;
128 0 : fd_gui_hist_epoch_key_t const * kb = b;
129 0 : if( ka->epoch<kb->epoch ) return -1;
130 0 : if( ka->epoch>kb->epoch ) return 1;
131 0 : return 0;
132 0 : }
133 :
134 : static inline ulong
135 0 : fd_gui_hist_rec_sz( int dbi ) {
136 0 : switch( dbi ) {
137 0 : case FD_GUI_HIST_SHRED_EVENTS: return sizeof(fd_gui_shred_batch_t);
138 0 : case FD_GUI_HIST_TILE_TIMERS: return sizeof(fd_gui_tile_timers_hist_t);
139 0 : case FD_GUI_HIST_SCHEDULER_COUNTS: return sizeof(fd_gui_scheduler_counts_t);
140 0 : case FD_GUI_HIST_TILE_STATS: return sizeof(fd_gui_tile_stats_t);
141 0 : case FD_GUI_HIST_TXN_WATERFALL: return sizeof(fd_gui_txn_waterfall_t);
142 0 : case FD_GUI_HIST_TXN_START: return sizeof(fd_gui_store_txn_start_t);
143 0 : case FD_GUI_HIST_TXN_END: return sizeof(fd_gui_store_txn_end_t);
144 0 : case FD_GUI_HIST_REPLAY_TXN: return sizeof(fd_gui_store_replay_txn_t);
145 0 : case FD_GUI_HIST_SLOT: return sizeof(fd_gui_slot_t);
146 0 : case FD_GUI_HIST_LEADER_SLOT: return sizeof(fd_gui_leader_slot_t);
147 0 : case FD_GUI_HIST_EPOCH: return sizeof(fd_gui_epoch_t);
148 0 : case FD_GUI_HIST_TIMELINE_DAY: return sizeof(fd_gui_timeline_day_t);
149 0 : default: return 0UL;
150 0 : }
151 0 : }
152 :
153 : /* fd_gui_hist_key_sz returns the KV key width for `dbi`: 16 bytes
154 : (slot, bank_seq) for slot-keyed DBs, 8 bytes for EPOCH, 0 for TS. */
155 : static inline ulong
156 0 : fd_gui_hist_key_sz( int dbi ) {
157 0 : switch( dbi ) {
158 0 : case FD_GUI_HIST_SLOT: return sizeof(fd_gui_hist_slot_key_t);
159 0 : case FD_GUI_HIST_LEADER_SLOT: return sizeof(fd_gui_hist_leader_slot_key_t);
160 0 : case FD_GUI_HIST_EPOCH: return sizeof(fd_gui_hist_epoch_key_t);
161 0 : default: return 0UL; /* TS DBs have no key */
162 0 : }
163 0 : }
164 :
165 : /* fd_gui_hist_key_hash / fd_gui_hist_key_cmp return the per-DB KV key
166 : callbacks for `dbi` (NULL for TS DBs). */
167 :
168 : static inline ulong
169 0 : ( * fd_gui_hist_key_hash( int dbi ) )( void const * key ) {
170 0 : switch( dbi ) {
171 0 : case FD_GUI_HIST_SLOT: return fd_gui_hist_slot_key_hash;
172 0 : case FD_GUI_HIST_LEADER_SLOT: return fd_gui_hist_leader_slot_key_hash;
173 0 : case FD_GUI_HIST_EPOCH: return fd_gui_hist_epoch_key_hash;
174 0 : default: return NULL;
175 0 : }
176 0 : }
177 :
178 : static inline int
179 0 : ( * fd_gui_hist_key_cmp( int dbi ) )( void const * a, void const * b ) {
180 0 : switch( dbi ) {
181 0 : case FD_GUI_HIST_SLOT: return fd_gui_hist_slot_key_cmp;
182 0 : case FD_GUI_HIST_LEADER_SLOT: return fd_gui_hist_leader_slot_key_cmp;
183 0 : case FD_GUI_HIST_EPOCH: return fd_gui_hist_epoch_key_cmp;
184 0 : default: return NULL;
185 0 : }
186 0 : }
187 :
188 : /* fd_gui_hist_kv_stride approximates fd_gui_store's per-record ring stride
189 : for KV DB `dbi`. The store adds no header (the key and link live inside
190 : the value), so the stride is just the align-padded record. */
191 : static inline ulong
192 0 : fd_gui_hist_kv_stride( int dbi ) {
193 0 : return fd_ulong_align_up( fd_gui_hist_rec_sz( dbi ), 8UL );
194 0 : }
195 :
196 : fd_gui_store_desc_t const *
197 0 : fd_gui_hist_db_descs( ulong store_bytes ) {
198 0 : static char const * const names[ FD_GUI_HIST_CNT ] = {
199 0 : "scheduler_counts", "tile_timers", "shred_events", "txn_start",
200 0 : "txn_end", "slot", "leader_slot", "epoch", "tile_stats",
201 0 : "txn_waterfall", "timeline_day", "replay_txn"
202 0 : };
203 0 : static fd_gui_store_desc_t descs[ FD_GUI_HIST_CNT ];
204 0 : static ulong built_for = 0UL; /* store_bytes the table was built for (0 = unbuilt) */
205 :
206 0 : if( FD_UNLIKELY( built_for && built_for!=store_bytes ) )
207 0 : FD_LOG_ERR(( "fd_gui_hist_db_descs: called with %lu after %lu", store_bytes, built_for ));
208 :
209 0 : if( FD_UNLIKELY( !built_for ) ) {
210 0 : ulong shared = fd_ulong_sat_sub( store_bytes, fd_gui_store_min_size( FD_GUI_HIST_CNT ) ) / FD_GUI_STORE_REGION_SZ;
211 :
212 0 : for( int i=0; i<FD_GUI_HIST_CNT; i++ ) {
213 0 : int ts = fd_gui_hist_is_timeseries( i );
214 0 : ulong rec_sz = fd_gui_hist_rec_sz( i );
215 0 : ulong key_sz = ts ? 0UL : fd_gui_hist_key_sz( i ); /* TS DBs have no key (0) */
216 0 : ulong val_sz = ts ? fd_ulong_max( rec_sz, 1UL ) : rec_sz;
217 0 : ulong max_records = 0UL;
218 0 : if( !ts ) {
219 0 : int shape = fd_gui_hist_keyshape( i );
220 0 : ulong cap = FD_GUI_STORE_REGION_SZ / fd_gui_hist_kv_stride( i );
221 0 : ulong base = FD_GUI_STORE_BASE_REGIONS*cap;
222 0 : ulong limit = shape==FD_GUI_HIST_KEYSHAPE_EPOCH ? FD_GUI_HIST_MAX_EPOCHS
223 0 : : FD_GUI_HIST_MAX_EPOCHS*(i==FD_GUI_HIST_LEADER_SLOT ? FD_GUI_HIST_MAX_LEADER_SLOTS_PER_EPOCH : MAX_SLOTS_PER_EPOCH);
224 0 : max_records = fd_ulong_max( base, fd_ulong_min( (FD_GUI_STORE_BASE_REGIONS+shared)*cap, limit ) );
225 0 : }
226 :
227 0 : descs[ i ].name = names[ i ];
228 0 : descs[ i ].kind = ts ? FD_GUI_STORE_KIND_TS : FD_GUI_STORE_KIND_KV;
229 0 : descs[ i ].key_off = 0UL; /* the key is the leading field(s) of the value */
230 0 : descs[ i ].key_sz = key_sz;
231 0 : descs[ i ].key_hash = ts ? NULL : fd_gui_hist_key_hash( i );
232 0 : descs[ i ].key_cmp = ts ? NULL : fd_gui_hist_key_cmp( i );
233 0 : descs[ i ].val_sz = val_sz;
234 0 : descs[ i ].val_align = 8UL;
235 0 : descs[ i ].ts_off = ts ? fd_gui_hist_dbi_ts_off( i ) : 0UL;
236 0 : descs[ i ].granularity = ts ? (ulong)fd_gui_hist_dbi_res_ns( i ) : 0UL;
237 0 : descs[ i ].max_records = max_records;
238 0 : }
239 0 : FD_COMPILER_MFENCE();
240 0 : built_for = store_bytes;
241 0 : }
242 0 : return descs;
243 0 : }
244 :
245 : /* Maximum number of records deleted per eviction iteration. */
246 0 : #define FD_GUI_HIST_EVICT_BATCH (512UL)
247 :
248 : struct fd_gui_hist_private {
249 : ulong magic; /* ==FD_GUI_HIST_MAGIC after fd_gui_hist_new */
250 : long last_ts[ FD_GUI_HIST_CNT ];
251 : int has_last_ts[ FD_GUI_HIST_CNT ];
252 :
253 : fd_gui_hist_metrics_t metrics;
254 : };
255 :
256 : FD_FN_CONST ulong
257 0 : fd_gui_hist_align( void ) {
258 0 : return 128UL;
259 0 : }
260 :
261 : FD_FN_CONST ulong
262 0 : fd_gui_hist_footprint( void ) {
263 0 : ulong l = FD_LAYOUT_INIT;
264 0 : l = FD_LAYOUT_APPEND( l, fd_gui_hist_align(), sizeof(fd_gui_hist_t) );
265 0 : return FD_LAYOUT_FINI( l, fd_gui_hist_align() );
266 0 : }
267 :
268 : void *
269 : fd_gui_hist_new( void * mem,
270 0 : fd_gui_store_t const * db ) {
271 0 : if( FD_UNLIKELY( !mem ) ) { FD_LOG_WARNING(( "fd_gui_hist_new: null mem" )); return NULL; }
272 0 : if( FD_UNLIKELY( !fd_ulong_is_aligned( (ulong)mem, fd_gui_hist_align() ) ) ) { FD_LOG_WARNING(( "fd_gui_hist_new: misaligned mem" )); return NULL; }
273 0 : if( FD_UNLIKELY( !db ) ) { FD_LOG_WARNING(( "fd_gui_hist_new: null db" )); return NULL; }
274 :
275 0 : ulong store_bytes = fd_gui_store_size( db );
276 0 : ulong min_bytes = fd_gui_store_min_size( FD_GUI_HIST_CNT );
277 0 : if( FD_UNLIKELY( store_bytes<min_bytes ) ) {
278 0 : FD_LOG_WARNING(( "fd_gui_hist_new: store size %lu bytes too small; must be >= %lu bytes", store_bytes, min_bytes ));
279 0 : return NULL;
280 0 : }
281 :
282 0 : fd_memset( mem, 0, sizeof(fd_gui_hist_t) );
283 0 : FD_SCRATCH_ALLOC_INIT( l, mem );
284 0 : fd_gui_hist_t * hist = FD_SCRATCH_ALLOC_APPEND( l, fd_gui_hist_align(), sizeof(fd_gui_hist_t) );
285 0 : FD_SCRATCH_ALLOC_FINI( l, fd_gui_hist_align() );
286 :
287 0 : FD_COMPILER_MFENCE();
288 0 : hist->magic = FD_GUI_HIST_MAGIC;
289 0 : FD_COMPILER_MFENCE();
290 0 : return mem;
291 0 : }
292 :
293 : fd_gui_hist_t *
294 0 : fd_gui_hist_join( void * mem ) {
295 0 : if( FD_UNLIKELY( !mem ) ) return NULL;
296 0 : fd_gui_hist_t * hist = (fd_gui_hist_t *)mem;
297 0 : if( FD_UNLIKELY( hist->magic!=FD_GUI_HIST_MAGIC ) ) { FD_LOG_WARNING(( "fd_gui_hist_join: bad magic" )); return NULL; }
298 0 : return hist;
299 0 : }
300 :
301 : void *
302 0 : fd_gui_hist_leave( fd_gui_hist_t * hist ) {
303 0 : return (void *)hist;
304 0 : }
305 :
306 : void *
307 0 : fd_gui_hist_delete( void * mem ) {
308 0 : if( FD_UNLIKELY( !mem ) ) return NULL;
309 0 : ((fd_gui_hist_t *)mem)->magic = 0UL;
310 0 : return mem;
311 0 : }
312 :
313 : static inline fd_gui_hist_t *
314 0 : fd_gui_hist( fd_gui_t * gui ) {
315 0 : return (fd_gui_hist_t *)gui->hist;
316 0 : }
317 :
318 : static inline fd_gui_store_t *
319 0 : fd_gui_hist_db( fd_gui_t * gui ) {
320 0 : return (fd_gui_store_t *)gui->db;
321 0 : }
322 :
323 : fd_gui_hist_metrics_t const *
324 0 : fd_gui_hist_metrics( fd_gui_t const * gui ) {
325 0 : if( FD_UNLIKELY( !gui->hist ) ) return NULL;
326 0 : return &((fd_gui_hist_t const *)gui->hist)->metrics;
327 0 : }
328 :
329 : static int
330 : fd_gui_hist_make_room( fd_gui_t * gui,
331 : int dbi,
332 0 : int rc ) {
333 0 : if( FD_UNLIKELY( rc!=FD_GUI_STORE_MAP_FULL && rc!=FD_GUI_STORE_RING_FULL ) ) return 0;
334 0 : if( rc==FD_GUI_STORE_MAP_FULL && fd_gui_store_reclaim( gui->db, ULONG_MAX, FD_GUI_HIST_EVICT_BATCH, NULL, NULL ) ) return 1;
335 0 : return !!fd_gui_store_reclaim( gui->db, (ulong)dbi, FD_GUI_HIST_EVICT_BATCH, NULL, NULL );
336 0 : }
337 :
338 : void *
339 : fd_gui_hist_kv_get_or_create( fd_gui_t * gui,
340 : int dbi,
341 0 : void const * key ) {
342 0 : 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; }
343 :
344 0 : int forced_eviction = 0;
345 0 : void * val = NULL;
346 0 : int rc;
347 0 : for(;;) {
348 0 : rc = fd_gui_store_kv_get_or_create( fd_gui_hist_db( gui ), (ulong)dbi, key, &val );
349 0 : if( FD_LIKELY( rc==FD_GUI_STORE_SUCCESS ) ) {
350 0 : if( FD_UNLIKELY( forced_eviction ) ) fd_gui_hist( gui )->metrics.reserves[ dbi ]++;
351 0 : return val;
352 0 : }
353 0 : if( FD_UNLIKELY( !fd_gui_hist_make_room( gui, dbi, rc ) ) ) break;
354 0 : forced_eviction = 1;
355 0 : }
356 0 : if( FD_UNLIKELY( rc==FD_GUI_STORE_MAP_FULL || rc==FD_GUI_STORE_RING_FULL ) ) {
357 0 : fd_gui_hist( gui )->metrics.map_full[ dbi ]++;
358 0 : }
359 0 : return NULL;
360 0 : }
361 :
362 : void *
363 : fd_gui_hist_ts_emplace( fd_gui_t * gui,
364 : int dbi,
365 0 : long stored_ts ) {
366 0 : if( FD_UNLIKELY( dbi<0 || dbi>=FD_GUI_HIST_CNT ) ) { FD_LOG_WARNING(( "fd_gui_hist_ts_emplace: bad dbi %d", dbi )); return NULL; }
367 0 : if( FD_UNLIKELY( !fd_gui_hist_is_timeseries( dbi ) ) ) { FD_LOG_WARNING(( "fd_gui_hist_ts_emplace: dbi %d is not time-series", dbi )); return NULL; }
368 0 : fd_gui_store_t * db = fd_gui_hist_db( gui );
369 :
370 0 : ulong rec_sz = fd_gui_hist_rec_sz( dbi );
371 0 : if( FD_UNLIKELY( !rec_sz ) ) { FD_LOG_WARNING(( "fd_gui_hist_ts_emplace: dbi %d has no record type", dbi )); return NULL; }
372 :
373 0 : fd_gui_hist_t * hist = fd_gui_hist( gui );
374 0 : if( FD_UNLIKELY( hist->has_last_ts[ dbi ] && stored_ts<hist->last_ts[ dbi ] ) ) {
375 0 : FD_LOG_WARNING(( "fd_gui_hist_ts_emplace: dbi %d insertion timestamp decreased from %ld to %ld", dbi, hist->last_ts[ dbi ], stored_ts ));
376 0 : return NULL;
377 0 : }
378 :
379 0 : int forced_eviction = 0;
380 0 : void * val = NULL;
381 0 : int rc;
382 0 : for(;;) {
383 0 : rc = fd_gui_store_ts_emplace( db, (ulong)dbi, stored_ts, &val );
384 0 : if( FD_LIKELY( rc==FD_GUI_STORE_SUCCESS ) ) {
385 0 : hist->last_ts[ dbi ] = stored_ts;
386 0 : hist->has_last_ts[ dbi ] = 1;
387 0 : if( FD_UNLIKELY( forced_eviction ) ) hist->metrics.reserves[ dbi ]++;
388 0 : return val;
389 0 : }
390 0 : if( FD_UNLIKELY( !fd_gui_hist_make_room( gui, dbi, rc ) ) ) break;
391 0 : forced_eviction = 1;
392 0 : }
393 0 : if( FD_UNLIKELY( rc==FD_GUI_STORE_MAP_FULL ) ) {
394 0 : fd_gui_hist( gui )->metrics.map_full[ dbi ]++;
395 0 : FD_LOG_WARNING(( "fd_gui_hist_ts_emplace: dropping a record for dbi %d; store full and nothing left to evict", dbi ));
396 0 : }
397 0 : return NULL;
398 0 : }
399 :
400 : int
401 : fd_gui_hist_ts_append( fd_gui_t * gui,
402 : int dbi,
403 0 : void const * val ) {
404 0 : if( FD_UNLIKELY( dbi<0 || dbi>=FD_GUI_HIST_CNT ) ) { FD_LOG_WARNING(( "fd_gui_hist_ts_append: bad dbi %d", dbi )); return -1; }
405 0 : 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; }
406 0 : ulong rec_sz = fd_gui_hist_rec_sz( dbi );
407 0 : if( FD_UNLIKELY( !rec_sz ) ) { FD_LOG_WARNING(( "fd_gui_hist_ts_append: dbi %d has no record type", dbi )); return -1; }
408 0 : long stored_ts;
409 0 : fd_memcpy( &stored_ts, (uchar const *)val + fd_gui_hist_dbi_ts_off( dbi ), sizeof(stored_ts) );
410 0 : void * dst = fd_gui_hist_ts_emplace( gui, dbi, stored_ts );
411 0 : if( FD_UNLIKELY( !dst ) ) return -1;
412 0 : fd_memcpy( dst, val, rec_sz );
413 0 : return 0;
414 0 : }
415 :
416 : static void
417 0 : fd_gui_hist_iter_load( fd_gui_hist_iter_t * iter ) {
418 0 : for(;;) {
419 0 : if( fd_gui_store_ts_scan_done( &iter->_it ) ) { iter->rec = NULL; return; }
420 0 : void const * rec = iter->_it.rec; /* the gui record verbatim; self-describing */
421 0 : if( !iter->_filter || iter->_filter( rec, iter->_filter_ctx ) ) {
422 0 : iter->rec = rec;
423 0 : return;
424 0 : }
425 0 : fd_gui_store_ts_scan_next( &iter->_it );
426 0 : }
427 0 : }
428 :
429 : int
430 : fd_gui_hist_range_begin( fd_gui_t * gui,
431 : fd_gui_hist_iter_t * iter,
432 : int dbi,
433 : long lo_ns,
434 : long hi_ns,
435 : fd_gui_hist_range_filter_fn filter,
436 0 : void * filter_ctx ) {
437 0 : fd_gui_store_t * db = fd_gui_hist_db( gui );
438 0 : memset( iter, 0, sizeof(fd_gui_hist_iter_t) );
439 :
440 0 : 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; }
441 :
442 0 : FD_TEST( lo_ns!=LONG_MIN && hi_ns!=LONG_MAX ); /* open-ended queries not supported */
443 :
444 0 : long res_ns = fd_gui_hist_dbi_res_ns( dbi );
445 0 : ulong window_lo = (lo_ns<=0L) ? 0UL : fd_gui_hist_window( lo_ns, res_ns );
446 0 : ulong window_hi = fd_gui_hist_window( hi_ns, res_ns );
447 :
448 0 : iter->_dbi = dbi;
449 0 : iter->rec_sz = fd_gui_hist_rec_sz( dbi );
450 0 : iter->_filter = filter;
451 0 : iter->_filter_ctx = filter_ctx;
452 :
453 : /* Clamp the request time range to the index bounds. */
454 0 : long first_ts;
455 0 : long last_ts;
456 0 : if( FD_UNLIKELY( !fd_gui_store_ts_live_timestamp_bounds( db, (ulong)dbi, &first_ts, &last_ts ) ) ) return 0; /* empty ring */
457 :
458 0 : ulong first_window = fd_gui_hist_window( fd_long_max( first_ts, 0L ), res_ns );
459 0 : ulong last_window = fd_gui_hist_window( fd_long_max( last_ts, 0L ), res_ns );
460 0 : window_lo = fd_ulong_max( window_lo, first_window );
461 0 : window_hi = fd_ulong_min( window_hi, last_window );
462 0 : if( FD_UNLIKELY( window_lo>window_hi ) ) return 0; /* request does not overlap the ring */
463 :
464 0 : fd_gui_store_ts_scan_begin( db, &iter->_it, (ulong)dbi, window_lo, window_hi, NULL, NULL );
465 0 : fd_gui_hist_iter_load( iter );
466 0 : return 0;
467 0 : }
468 :
469 : int
470 0 : fd_gui_hist_range_next( fd_gui_hist_iter_t * iter ) {
471 0 : if( FD_UNLIKELY( !iter->rec ) ) return 0;
472 0 : if( iter->_emitted ) {
473 0 : fd_gui_store_ts_scan_next( &iter->_it );
474 0 : fd_gui_hist_iter_load( iter );
475 0 : if( !iter->rec ) return 0;
476 0 : }
477 0 : iter->_emitted = 1;
478 0 : return 1;
479 0 : }
480 :
481 : void
482 0 : fd_gui_hist_range_end( fd_gui_hist_iter_t * iter ) {
483 0 : fd_gui_store_ts_scan_end( &iter->_it );
484 0 : }
485 :
486 : void *
487 : fd_gui_hist_kv_get( fd_gui_t * gui,
488 : int dbi,
489 0 : void const * key ) {
490 0 : 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; }
491 0 : return fd_gui_store_kv_get( fd_gui_hist_db( gui ), (ulong)dbi, key );
492 0 : }
493 :
494 : void *
495 : fd_gui_hist_kv_get_slot_any( fd_gui_t * gui,
496 : int dbi,
497 0 : ulong slot ) {
498 0 : if( FD_UNLIKELY( fd_gui_hist_keyshape( dbi )!=FD_GUI_HIST_KEYSHAPE_SLOT_BANK ) ) {
499 0 : FD_LOG_WARNING(( "fd_gui_hist_kv_get_slot_any: dbi %d is not slot/bank-keyed", dbi ));
500 0 : return NULL;
501 0 : }
502 :
503 0 : fd_gui_hist_slot_key_t key = { .slot=slot, .bank_seq=ULONG_MAX };
504 0 : return fd_gui_store_kv_get_any( fd_gui_hist_db( gui ), (ulong)dbi, &key );
505 0 : }
506 :
507 : /* fd_gui_hist_ts_iter_load reads the iterator's current backend record into
508 : the typed iterator fields (or marks it done). */
509 : static void
510 0 : fd_gui_hist_ts_iter_load( fd_gui_hist_kv_slot_iter_t * iter ) {
511 0 : if( FD_UNLIKELY( fd_gui_store_kv_iter_done( &iter->_it ) ) ) {
512 0 : iter->rec = NULL;
513 0 : iter->bank_seq = ULONG_MAX;
514 0 : return;
515 0 : }
516 0 : iter->rec = iter->_it.rec;
517 0 : iter->bank_seq = ( iter->_it.key_sz>=2UL*sizeof(ulong) )
518 0 : ? ((ulong const *)iter->_it.key)[ 1 ]
519 0 : : ULONG_MAX;
520 0 : }
521 :
522 : fd_gui_hist_kv_slot_iter_t *
523 : fd_gui_hist_kv_iter_begin( fd_gui_t * gui,
524 : fd_gui_hist_kv_slot_iter_t * iter,
525 : int dbi,
526 0 : ulong slot ) {
527 0 : iter->rec = NULL;
528 0 : iter->bank_seq = ULONG_MAX;
529 0 : if( FD_UNLIKELY( fd_gui_hist_keyshape( dbi )!=FD_GUI_HIST_KEYSHAPE_SLOT_BANK ) ) {
530 0 : FD_LOG_WARNING(( "fd_gui_hist_kv_iter_begin: dbi %d is not slot/bank-keyed", dbi ));
531 0 : fd_gui_hist_slot_key_t none = { .slot=ULONG_MAX, .bank_seq=ULONG_MAX };
532 0 : fd_gui_store_kv_iter_begin( fd_gui_hist_db( gui ), &iter->_it, (ulong)dbi, &none );
533 0 : return iter;
534 0 : }
535 0 : fd_gui_hist_slot_key_t key = { .slot=slot, .bank_seq=ULONG_MAX };
536 0 : fd_gui_store_kv_iter_begin( fd_gui_hist_db( gui ), &iter->_it, (ulong)dbi, &key );
537 0 : fd_gui_hist_ts_iter_load( iter );
538 0 : return iter;
539 0 : }
540 :
541 : int
542 0 : fd_gui_hist_kv_iter_next( fd_gui_hist_kv_slot_iter_t * iter ) {
543 0 : if( FD_UNLIKELY( !iter->rec ) ) return 0;
544 0 : fd_gui_store_kv_iter_next( &iter->_it );
545 0 : fd_gui_hist_ts_iter_load( iter );
546 0 : return iter->rec!=NULL;
547 0 : }
548 :
549 : int
550 0 : fd_gui_hist_evict_step( fd_gui_t * gui ) {
551 0 : if( FD_UNLIKELY( !gui->db || !gui->hist ) ) return 0;
552 : /* Keep one shared region in reserve. */
553 0 : if( fd_gui_store_shared_free_region_cnt( gui->db )>1UL ) return 0;
554 0 : return !!fd_gui_store_reclaim( gui->db, ULONG_MAX, FD_GUI_HIST_EVICT_BATCH, NULL, NULL );
555 0 : }
|