Line data Source code
1 : #include "fd_gui_shred.h"
2 : #include "fd_gui.h"
3 :
4 0 : #define FD_GUI_SHRED_LITERAL (0U)
5 0 : #define FD_GUI_SHRED_DELTA (1U)
6 0 : #define FD_GUI_SHRED_TS_NEG (2U)
7 0 : #define FD_GUI_SHRED_IDX_NEG (4U)
8 0 : #define FD_GUI_SHRED_EVENT_SHIFT (3U)
9 :
10 : struct __attribute__((packed)) fd_gui_shred_delta {
11 : uint insert_delta;
12 : uint timestamp_delta : 24;
13 : uchar idx_delta;
14 : };
15 : typedef struct fd_gui_shred_delta fd_gui_shred_delta_t;
16 :
17 : FD_STATIC_ASSERT( sizeof(fd_gui_shred_delta_t)==8UL, shred_delta_sz );
18 :
19 : static ulong
20 0 : shred_window( long ns ) {
21 0 : return ns<=0L ? 0UL : (ulong)(ns/FD_GUI_HIST_RES_1S_NS);
22 0 : }
23 :
24 : static inline fd_gui_shred_event_t
25 0 : fec_shred_event( fd_gui_fec_event_t const * src ) {
26 0 : return (fd_gui_shred_event_t){ .insert_time_ns=src->insert_time_ns, .event_time_ns=src->event_time_ns,
27 0 : .slot=src->key.slot, .idx=src->key.idx, .event=src->key.event };
28 0 : }
29 :
30 : static int
31 : batch_append( fd_gui_shred_builder_t * builder,
32 0 : fd_gui_shred_event_t const * event ) {
33 0 : fd_gui_shred_batch_t * batch = &builder->batch;
34 0 : fd_gui_shred_event_t const * prev = &builder->prev;
35 0 : ulong ts_delta = event->event_time_ns>=prev->event_time_ns
36 0 : ? (ulong)event->event_time_ns-(ulong)prev->event_time_ns
37 0 : : (ulong)prev->event_time_ns-(ulong)event->event_time_ns;
38 0 : ulong idx_delta = event->idx>=prev->idx ? (ulong)(event->idx-prev->idx) : (ulong)(prev->idx-event->idx);
39 0 : int delta = batch->event_cnt && event->slot==prev->slot && ts_delta<=FD_GUI_SHRED_EVENT_TS_MAX && idx_delta<=UCHAR_MAX &&
40 0 : event->event<=(UCHAR_MAX>>FD_GUI_SHRED_EVENT_SHIFT);
41 0 : ulong sz = 1UL + (delta ? sizeof(fd_gui_shred_delta_t) : sizeof(fd_gui_shred_event_t));
42 0 : if( FD_UNLIKELY( sz>sizeof(batch->data)-batch->data_sz ) ) return 0;
43 :
44 0 : uchar * dst = batch->data+batch->data_sz;
45 0 : if( FD_LIKELY( delta ) ) {
46 0 : *dst++ = (uchar)(FD_GUI_SHRED_DELTA |
47 0 : (event->event_time_ns<prev->event_time_ns ? FD_GUI_SHRED_TS_NEG : 0U) |
48 0 : (event->idx<prev->idx ? FD_GUI_SHRED_IDX_NEG : 0U) |
49 0 : ((uint)event->event<<FD_GUI_SHRED_EVENT_SHIFT));
50 0 : fd_gui_shred_delta_t d = {
51 0 : .insert_delta = (uint)(event->insert_time_ns-prev->insert_time_ns),
52 0 : .timestamp_delta = (uint)(ts_delta & FD_GUI_SHRED_EVENT_TS_MAX),
53 0 : .idx_delta = (uchar)idx_delta
54 0 : };
55 0 : fd_memcpy( dst, &d, sizeof(d) );
56 0 : } else {
57 0 : *dst++ = FD_GUI_SHRED_LITERAL;
58 0 : fd_memcpy( dst, event, sizeof(*event) );
59 0 : }
60 0 : batch->data_sz += (uint)sz;
61 0 : batch->event_cnt++;
62 0 : builder->prev = *event;
63 0 : return 1;
64 0 : }
65 :
66 : ulong
67 : fd_gui_fec_archive( fd_gui_t * gui,
68 0 : ulong budget ) {
69 0 : if( !gui->db || !gui->hist ) return 0UL;
70 0 : ulong total = 0UL;
71 0 : while( budget ) {
72 0 : fd_gui_store_kv_scan_t scan;
73 0 : fd_gui_store_kv_scan_begin( gui->db, &scan, FD_GUI_HIST_FEC_EVENTS );
74 0 : fd_gui_shred_builder_t builder = {0};
75 0 : fd_gui_fec_event_batch_t events = { .min_ns=LONG_MAX, .max_ns=LONG_MIN };
76 0 : ulong count = 0UL;
77 0 : while( count<budget && fd_gui_store_kv_scan_next( &scan ) ) {
78 0 : fd_gui_fec_event_t const * src = scan.rec;
79 0 : if( src->key.slot>=fd_gui_event_closed_slot_wmark( gui ) ) break;
80 : /* Bound the batch's admission span so event-time queries can find
81 : it (see FD_GUI_FEC_BATCH_SPAN_NS). This also keeps the codec's
82 : 32-bit admission deltas in range. */
83 0 : if( count && src->insert_time_ns-events.insert_time_ns>FD_GUI_FEC_BATCH_SPAN_NS ) break;
84 0 : fd_gui_shred_event_t event = fec_shred_event( src );
85 0 : if( !count ) events.insert_time_ns = builder.batch.insert_time_ns = src->insert_time_ns;
86 0 : if( !batch_append( &builder, &event ) ) break;
87 0 : events.min_ns = fd_long_min( events.min_ns, src->event_time_ns );
88 0 : events.max_ns = fd_long_max( events.max_ns, src->event_time_ns );
89 0 : count++;
90 0 : }
91 0 : if( !count ) break; /* An open prefix pins all later admissions. */
92 0 : events.batch = builder.batch;
93 : /* Keep staging intact on archive admission failure. */
94 0 : if( fd_gui_hist_ts_append( gui, FD_GUI_HIST_FEC_EVENT_ARCHIVE, &events ) ) break;
95 0 : FD_TEST( fd_gui_store_kv_reclaim_prefix( gui->db, FD_GUI_HIST_FEC_EVENTS, count, NULL, NULL )==count );
96 0 : budget -= count;
97 0 : total += count;
98 0 : }
99 0 : return total;
100 0 : }
101 :
102 : static void
103 0 : batch_next( fd_gui_shred_event_iter_t * iter ) {
104 0 : uchar const * src = iter->_batch->data+iter->_data_off;
105 0 : uchar tag = *src++;
106 0 : if( FD_LIKELY( tag & FD_GUI_SHRED_DELTA ) ) {
107 0 : fd_gui_shred_delta_t d;
108 0 : fd_memcpy( &d, src, sizeof(d) );
109 0 : iter->event.insert_time_ns += (long)d.insert_delta;
110 0 : iter->event.event_time_ns += (tag & FD_GUI_SHRED_TS_NEG) ? -(long)d.timestamp_delta : (long)d.timestamp_delta;
111 0 : iter->event.idx = (ushort)((tag & FD_GUI_SHRED_IDX_NEG) ? iter->event.idx-d.idx_delta : iter->event.idx+d.idx_delta);
112 0 : iter->event.event = (uchar)(tag>>FD_GUI_SHRED_EVENT_SHIFT);
113 0 : iter->_data_off += 1UL+sizeof(d);
114 0 : } else {
115 0 : fd_memcpy( &iter->event, src, sizeof(iter->event) );
116 0 : iter->_data_off += 1UL+sizeof(iter->event);
117 0 : }
118 0 : iter->_batch_idx++;
119 0 : }
120 :
121 : static int
122 : fec_admitted_before( void const * rec,
123 0 : void * ctx ) {
124 0 : return ((fd_gui_fec_event_t const *)rec)->insert_time_ns<*(long const *)ctx;
125 0 : }
126 :
127 : static fd_gui_shred_event_iter_t *
128 : shred_iter_begin( fd_gui_t * gui,
129 : fd_gui_shred_event_iter_t * iter,
130 : int event_time,
131 : int fec,
132 : long after_ns,
133 0 : long before_ns ) {
134 0 : fd_memset( iter, 0, sizeof(*iter) );
135 0 : iter->_gui = gui;
136 0 : iter->_after_ns = after_ns;
137 0 : iter->_before_ns = before_ns;
138 0 : iter->_event_time = event_time;
139 0 : iter->_fec_archive = fec;
140 :
141 : /* Event-time shred queries widen the scan by +-1 second. The
142 : assumption is that event-ordering approximately equals
143 : insert-ordering, and in rare cases where there is more than a second
144 : of skew in the system we can tolerate missing data. Stored FEC event
145 : times are within FD_GUI_FEC_EVENT_LAG_MAX_NS of admission and
146 : batches span at most FD_GUI_FEC_BATCH_SPAN_NS of admission. */
147 0 : if( FD_LIKELY( gui->db && gui->hist && after_ns<=before_ns ) ) {
148 0 : long lo_margin = fec ? FD_GUI_FEC_EVENT_LAG_MAX_NS+FD_GUI_FEC_BATCH_SPAN_NS : event_time ? FD_GUI_HIST_RES_1S_NS : 0L;
149 0 : long hi_margin = fec ? FD_GUI_FEC_EVENT_LAG_MAX_NS : event_time ? FD_GUI_HIST_RES_1S_NS : 0L;
150 0 : long lo_ns = fd_long_max( 0L, fd_long_sat_sub( after_ns, lo_margin ) );
151 0 : long hi_ns = fd_long_min( LONG_MAX-1L, fd_long_sat_add( before_ns, hi_margin ) );
152 0 : int dbi = fec ? FD_GUI_HIST_FEC_EVENT_ARCHIVE : FD_GUI_HIST_SHRED_EVENTS;
153 0 : iter->_hist_active = !fd_gui_hist_range_begin( gui, &iter->_hist_iter, dbi, lo_ns, hi_ns, NULL, NULL );
154 0 : if( fec ) {
155 : /* Staged admission times are nondecreasing, so only admissions in
156 : [after_ns-LAG_MAX,before_ns+LAG_MAX) can match. */
157 0 : long insert_lo = fd_long_sat_sub( after_ns, FD_GUI_FEC_EVENT_LAG_MAX_NS );
158 0 : iter->_fec_insert_hi = fd_long_sat_add( before_ns, FD_GUI_FEC_EVENT_LAG_MAX_NS );
159 0 : fd_gui_store_kv_scan_begin( gui->db, &iter->_fec_iter, FD_GUI_HIST_FEC_EVENTS );
160 0 : fd_gui_store_kv_scan_seek( &iter->_fec_iter, fec_admitted_before, &insert_lo );
161 0 : } else {
162 0 : iter->_builder_pending = 1;
163 0 : }
164 0 : }
165 0 : return iter;
166 0 : }
167 :
168 : fd_gui_shred_event_iter_t *
169 : fd_gui_shred_event_hist_iter_begin( fd_gui_t * gui,
170 : fd_gui_shred_event_iter_t * iter,
171 : long after_ns,
172 0 : long before_ns ) {
173 0 : return shred_iter_begin( gui, iter, 1, 0, after_ns, before_ns );
174 0 : }
175 :
176 : fd_gui_shred_event_iter_t *
177 : fd_gui_shred_event_iter_begin( fd_gui_t * gui,
178 : fd_gui_shred_event_iter_t * iter,
179 : long after_ns,
180 0 : long before_ns ) {
181 0 : return shred_iter_begin( gui, iter, 0, 0, fd_long_max( after_ns, 0L ), fd_long_min( before_ns, LONG_MAX-1L ) );
182 0 : }
183 :
184 : int
185 0 : fd_gui_shred_event_iter_next( fd_gui_shred_event_iter_t * iter ) {
186 0 : while( iter->_batch || iter->_hist_active || iter->_builder_pending ) {
187 0 : if( iter->_batch && iter->_batch_idx<iter->_batch->event_cnt ) {
188 0 : batch_next( iter );
189 0 : long ts = iter->_event_time ? iter->event.event_time_ns : iter->event.insert_time_ns;
190 0 : if( ts>=iter->_after_ns && (iter->_event_time ? ts<iter->_before_ns : ts<=iter->_before_ns) ) return 1;
191 0 : continue;
192 0 : }
193 0 : iter->_batch = NULL;
194 0 : iter->_batch_idx = 0UL;
195 0 : iter->_data_off = 0UL;
196 0 : if( iter->_hist_active && fd_gui_hist_range_next( &iter->_hist_iter ) ) {
197 0 : if( iter->_fec_archive ) {
198 0 : fd_gui_fec_event_batch_t const * b = iter->_hist_iter.rec;
199 0 : if( b->max_ns<iter->_after_ns || b->min_ns>=iter->_before_ns ) continue;
200 0 : iter->_batch = &b->batch;
201 0 : } else iter->_batch = iter->_hist_iter.rec;
202 0 : continue;
203 0 : }
204 0 : if( iter->_hist_active ) fd_gui_hist_range_end( &iter->_hist_iter );
205 0 : iter->_hist_active = 0;
206 0 : if( iter->_builder_pending ) {
207 0 : iter->_builder_pending = 0;
208 0 : iter->_batch = &iter->_gui->shreds.builder.batch;
209 0 : }
210 0 : }
211 :
212 : /* Staged FEC events, only set up by fd_gui_fec_event_iter_begin. */
213 0 : while( fd_gui_store_kv_scan_next( &iter->_fec_iter ) ) {
214 0 : fd_gui_fec_event_t const * src = iter->_fec_iter.rec;
215 0 : if( src->insert_time_ns>=iter->_fec_insert_hi ) {
216 0 : iter->_fec_iter.db = NULL;
217 0 : break;
218 0 : }
219 0 : if( src->event_time_ns<iter->_after_ns || src->event_time_ns>=iter->_before_ns ) continue;
220 0 : iter->event = fec_shred_event( src );
221 0 : return 1;
222 0 : }
223 0 : return 0;
224 0 : }
225 :
226 : void
227 0 : fd_gui_shred_event_iter_end( fd_gui_shred_event_iter_t * iter ) {
228 0 : if( iter->_hist_active ) fd_gui_hist_range_end( &iter->_hist_iter );
229 0 : iter->_hist_active = 0;
230 0 : iter->_builder_pending = 0;
231 0 : iter->_batch = NULL;
232 0 : iter->_fec_iter.db = NULL;
233 0 : }
234 :
235 : static void
236 0 : batch_flush( fd_gui_t * gui ) {
237 0 : fd_gui_shred_batch_t * batch = &gui->shreds.builder.batch;
238 0 : if( !batch->event_cnt ) return;
239 0 : if( FD_UNLIKELY( fd_gui_hist_ts_append( gui, FD_GUI_HIST_SHRED_EVENTS, batch ) ) ) {
240 0 : gui->shreds.dropped_event_cnt += batch->event_cnt;
241 0 : }
242 0 : batch->event_cnt = 0U;
243 0 : batch->data_sz = 0U;
244 0 : }
245 :
246 : void
247 : fd_gui_shred_event_append( fd_gui_t * gui,
248 : ulong slot,
249 : ulong idx,
250 : uchar event,
251 : long timestamp,
252 0 : long now ) {
253 0 : fd_gui_shred_builder_t * builder = &gui->shreds.builder;
254 0 : int marker = event==FD_GUI_SLOT_SHRED_SHRED_SLOT_COMPLETE;
255 0 : if( FD_UNLIKELY( !gui->db || !gui->hist || slot>UINT_MAX || idx>USHORT_MAX ||
256 0 : (marker ? idx!=USHORT_MAX : idx==USHORT_MAX) ||
257 0 : (event>FD_GUI_SLOT_SHRED_SHRED_SLOT_COMPLETE && event!=FD_GUI_SLOT_SHRED_SHRED_PUBLISHED) ||
258 0 : (!marker && (timestamp<0L || timestamp==LONG_MAX || slot<fd_gui_event_closed_slot_wmark( gui ))) ||
259 0 : now<0L || now==LONG_MAX || now<builder->prev.insert_time_ns ) ) {
260 0 : gui->shreds.dropped_event_cnt++;
261 0 : return;
262 0 : }
263 0 : if( event==FD_GUI_SLOT_SHRED_SHRED_RECEIVED_TURBINE || event==FD_GUI_SLOT_SHRED_SHRED_RECEIVED_REPAIR ||
264 0 : event==FD_GUI_SLOT_SHRED_SHRED_PUBLISHED ) {
265 : /* Index the first arrival per slot for the timeline; the raw events
266 : cannot be looked up by slot. Producer timestamps need not arrive
267 : in order. */
268 0 : fd_gui_epoch_t * epoch = fd_gui_get_epoch_by_slot( gui, slot );
269 0 : if( epoch && slot>=epoch->start_slot && slot-epoch->start_slot<fd_ulong_min( epoch->slot_cnt, MAX_SLOTS_PER_EPOCH ) ) {
270 0 : ulong i = slot-epoch->start_slot;
271 0 : uchar state = epoch->timeline_slot_state[ i ];
272 0 : epoch->timeline_slot_first_shred_ns[ i ] = (state & FD_GUI_TIMELINE_SLOT_STATE_STARTED)
273 0 : ? fd_long_min( epoch->timeline_slot_first_shred_ns[ i ], timestamp ) : timestamp;
274 0 : epoch->timeline_slot_state[ i ] = (uchar)(state | FD_GUI_TIMELINE_SLOT_STATE_STARTED);
275 0 : }
276 0 : }
277 0 : fd_gui_shred_batch_t * batch = &builder->batch;
278 0 : long window_ns = (long)shred_window( now )*FD_GUI_HIST_RES_1S_NS;
279 0 : if( batch->event_cnt && batch->insert_time_ns!=window_ns ) batch_flush( gui );
280 0 : batch->insert_time_ns = window_ns;
281 0 : fd_gui_shred_event_t rec = { .insert_time_ns=now, .event_time_ns=timestamp, .slot=(uint)slot, .idx=(ushort)idx, .event=event };
282 0 : if( FD_UNLIKELY( !batch_append( builder, &rec ) ) ) {
283 0 : batch_flush( gui );
284 0 : FD_TEST( batch_append( builder, &rec ) );
285 0 : }
286 0 : }
287 :
288 : int
289 : fd_gui_shred_flush( fd_gui_t * gui,
290 0 : long now ) {
291 0 : if( FD_UNLIKELY( !gui->db || !gui->hist ) ) return 0;
292 0 : fd_gui_shred_batch_t const * batch = &gui->shreds.builder.batch;
293 0 : if( !batch->event_cnt || shred_window( batch->insert_time_ns )>=shred_window( now ) ) return 0;
294 0 : batch_flush( gui );
295 0 : return 1;
296 0 : }
297 :
298 : ulong
299 0 : fd_gui_event_closed_slot_wmark( fd_gui_t const * gui ) {
300 0 : return gui->shreds.closed_slot_wmark;
301 0 : }
302 :
303 : /* fec_slot_hi_wmark returns the FEC staging high watermark: slots at or
304 : above it are not staged. */
305 :
306 : static inline ulong
307 0 : fec_slot_hi_wmark( fd_gui_t const * gui ) {
308 0 : return fd_ulong_sat_add( gui->shreds.closed_slot_wmark, FD_GUI_FEC_SLOT_WMARK_CNT );
309 0 : }
310 :
311 : void
312 : fd_gui_fec_event_iter_begin( fd_gui_t * gui,
313 : fd_gui_shred_event_iter_t * iter,
314 : long after_ns,
315 0 : long before_ns ) {
316 0 : shred_iter_begin( gui, iter, 1, 1, after_ns, before_ns );
317 0 : }
318 :
319 : /* fec_event_skewed tests whether timestamp is more than
320 : FD_GUI_FEC_EVENT_LAG_MAX_NS from the admission time insert_ns.
321 : Skewed events are not stored, so event-time queries can find every
322 : stored event from its admission time. */
323 :
324 : static int
325 : fec_event_skewed( long insert_ns,
326 0 : long timestamp ) {
327 0 : return timestamp<fd_long_sat_sub( insert_ns, FD_GUI_FEC_EVENT_LAG_MAX_NS ) ||
328 0 : timestamp>fd_long_sat_add( insert_ns, FD_GUI_FEC_EVENT_LAG_MAX_NS );
329 0 : }
330 :
331 : void
332 : fd_gui_fec_event_append( fd_gui_t * gui,
333 : ulong slot,
334 : ulong idx,
335 : uchar event,
336 : long timestamp,
337 0 : long now ) {
338 0 : int marker = event==FD_GUI_SLOT_SHRED_SHRED_SLOT_COMPLETE;
339 0 : if( FD_UNLIKELY( !gui->db || !gui->hist || slot>UINT_MAX || idx>USHORT_MAX ||
340 0 : (marker ? idx!=USHORT_MAX : idx==USHORT_MAX) || timestamp<0L || timestamp==LONG_MAX ||
341 0 : now<0L || now==LONG_MAX ||
342 0 : (event>FD_GUI_SLOT_SHRED_SHRED_SLOT_COMPLETE && event!=FD_GUI_SLOT_SHRED_SHRED_PUBLISHED) ||
343 0 : (!marker && slot<fd_gui_event_closed_slot_wmark( gui )) || slot>=fec_slot_hi_wmark( gui ) ||
344 0 : (marker && gui->shreds.fec_marker_sequence==ULONG_MAX) ) ) {
345 0 : gui->shreds.dropped_fec_event_cnt++;
346 0 : return;
347 0 : }
348 : /* Staging admission order must be monotone in admission time, since
349 : it becomes the archive order. */
350 0 : now = fd_long_max( now, gui->shreds.fec_insert_time_ns );
351 0 : fd_gui_fec_event_key_t key = { .slot=(uint)slot, .idx=(ushort)idx, .event=event };
352 0 : fd_gui_fec_event_t * dst = marker ? NULL : fd_gui_hist_kv_get( gui, FD_GUI_HIST_FEC_EVENTS, &key );
353 0 : if( dst && timestamp>=dst->event_time_ns ) return;
354 0 : if( FD_UNLIKELY( fec_event_skewed( dst ? dst->insert_time_ns : now, timestamp ) ) ) {
355 0 : gui->shreds.clamped_fec_event_cnt++;
356 0 : return;
357 0 : }
358 0 : if( dst ) {
359 0 : dst->event_time_ns = timestamp;
360 0 : return;
361 0 : }
362 0 : if( marker ) key.marker_sequence = ++gui->shreds.fec_marker_sequence;
363 0 : dst = fd_gui_hist_kv_get_or_create( gui, FD_GUI_HIST_FEC_EVENTS, &key );
364 0 : if( FD_UNLIKELY( !dst ) ) {
365 0 : gui->shreds.dropped_fec_event_cnt++;
366 0 : return;
367 0 : }
368 0 : *dst = (fd_gui_fec_event_t){ .key=key, .insert_time_ns=now, .event_time_ns=timestamp };
369 0 : gui->shreds.fec_insert_time_ns = now;
370 0 : }
371 :
372 : void
373 : fd_gui_shred_event_slot_complete( fd_gui_t * gui,
374 : ulong slot,
375 : long timestamp,
376 0 : long now ) {
377 0 : if( FD_UNLIKELY( slot>UINT_MAX ) ) return;
378 0 : fd_gui_shred_event_append( gui, slot, USHORT_MAX, FD_GUI_SLOT_SHRED_SHRED_SLOT_COMPLETE, timestamp, now );
379 0 : fd_gui_fec_event_append( gui, slot, USHORT_MAX, FD_GUI_SLOT_SHRED_SHRED_SLOT_COMPLETE, timestamp, now );
380 0 : }
381 :
382 : void
383 : fd_gui_event_closed_slot_wmark_advance( fd_gui_t * gui,
384 0 : ulong wmark ) {
385 0 : if( FD_UNLIKELY( wmark==ULONG_MAX || wmark<=gui->shreds.closed_slot_wmark ) ) return;
386 0 : gui->shreds.closed_slot_wmark = wmark;
387 0 : }
388 :
389 : int
390 : fd_gui_event_bounds( fd_gui_t * gui,
391 : int dbi,
392 : long * lo,
393 0 : long * hi ) {
394 0 : if( dbi==FD_GUI_HIST_FEC_EVENTS ) {
395 : /* Bound by admission time, widened by the lag bound and batch span
396 : rather than scanning event times. */
397 0 : long first = LONG_MAX;
398 0 : long last = LONG_MIN;
399 0 : long a_first, a_last;
400 0 : if( gui->db && fd_gui_store_ts_live_timestamp_bounds( gui->db, FD_GUI_HIST_FEC_EVENT_ARCHIVE, &a_first, &a_last ) ) {
401 0 : first = a_first;
402 0 : last = fd_long_sat_add( a_last, FD_GUI_FEC_BATCH_SPAN_NS );
403 0 : }
404 0 : if( gui->db ) {
405 0 : fd_gui_store_kv_scan_t it;
406 0 : fd_gui_store_kv_scan_begin( gui->db, &it, FD_GUI_HIST_FEC_EVENTS );
407 0 : if( fd_gui_store_kv_scan_next( &it ) ) {
408 0 : first = fd_long_min( first, ((fd_gui_fec_event_t const *)it.rec)->insert_time_ns );
409 0 : last = fd_long_max( last, gui->shreds.fec_insert_time_ns );
410 0 : }
411 0 : }
412 0 : int have = first!=LONG_MAX;
413 0 : *lo = have ? fd_long_max( 0L, fd_long_sat_sub( first, FD_GUI_FEC_EVENT_LAG_MAX_NS ) ) : LONG_MAX;
414 0 : *hi = have ? fd_long_min( LONG_MAX-1L, fd_long_sat_add( last, FD_GUI_FEC_EVENT_LAG_MAX_NS+1L ) ) : LONG_MIN;
415 0 : return have;
416 0 : }
417 0 : long first;
418 0 : long last;
419 0 : int have = gui->db && fd_gui_store_ts_live_timestamp_bounds( gui->db, (ulong)dbi, &first, &last );
420 0 : if( dbi==FD_GUI_HIST_SHRED_EVENTS && gui->shreds.builder.batch.event_cnt ) {
421 0 : long active = gui->shreds.builder.batch.insert_time_ns;
422 0 : first = have ? fd_long_min( first, active ) : active;
423 0 : last = have ? fd_long_max( last, active ) : active;
424 0 : have = 1;
425 0 : }
426 :
427 0 : if( have && dbi==FD_GUI_HIST_SHRED_EVENTS ) last = fd_long_sat_add( last, FD_GUI_HIST_RES_1S_NS-1L );
428 0 : *lo = have ? fd_long_max( 0L, fd_long_sat_sub( first, FD_GUI_HIST_RES_1S_NS ) ) : LONG_MAX;
429 0 : *hi = have ? fd_long_min( LONG_MAX-1L, fd_long_sat_add( last, FD_GUI_HIST_RES_1S_NS+1L ) ) : LONG_MIN;
430 0 : return have;
431 0 : }
432 :
433 : int
434 : fd_gui_shred_window_is_empty( fd_gui_t * gui,
435 : long after_ns,
436 0 : long before_ns ) {
437 0 : fd_gui_shred_event_iter_t it[ 1 ];
438 0 : fd_gui_shred_event_iter_begin( gui, it, after_ns, before_ns );
439 0 : int empty = !fd_gui_shred_event_iter_next( it );
440 0 : fd_gui_shred_event_iter_end( it );
441 0 : return empty;
442 0 : }
|