LCOV - code coverage report
Current view: top level - disco/gui - fd_gui_hist.c (source / functions) Hit Total Coverage
Test: cov.lcov Lines: 0 384 0.0 %
Date: 2026-10-22 18:10:43 Functions: 0 40 0.0 %

          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 : }

Generated by: LCOV version 1.14