LCOV - code coverage report
Current view: top level - disco/gui - fd_gui_hist.c (source / functions) Hit Total Coverage
Test: cov.lcov Lines: 0 450 0.0 %
Date: 2026-11-11 12:40:43 Functions: 0 52 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             : FD_STATIC_ASSERT( sizeof(fd_gui_timeline_tile_day_t)<=FD_GUI_STORE_MAX_REC_SZ, rec_fits );
      22             : FD_STATIC_ASSERT( sizeof(fd_gui_fec_event_t        )<=FD_GUI_STORE_MAX_REC_SZ, rec_fits );
      23             : FD_STATIC_ASSERT( sizeof(fd_gui_fec_event_batch_t  )<=FD_GUI_STORE_MAX_REC_SZ, rec_fits );
      24             : 
      25             : /* Each key type must alias the matching record field exactly. */
      26             : FD_STATIC_ASSERT( offsetof( fd_gui_hist_slot_key_t,        slot     )==offsetof( fd_gui_slot_t,        slot     ), key_layout );
      27             : FD_STATIC_ASSERT( offsetof( fd_gui_hist_slot_key_t,        bank_seq )==offsetof( fd_gui_slot_t,        bank_seq ), key_layout );
      28             : FD_STATIC_ASSERT( offsetof( fd_gui_hist_leader_slot_key_t, slot     )==offsetof( fd_gui_leader_slot_t, slot     ), key_layout );
      29             : FD_STATIC_ASSERT( offsetof( fd_gui_hist_leader_slot_key_t, bank_seq )==offsetof( fd_gui_leader_slot_t, bank_seq ), key_layout );
      30             : FD_STATIC_ASSERT( offsetof( fd_gui_hist_epoch_key_t,       epoch    )==offsetof( fd_gui_epoch_t,       epoch    ), key_layout );
      31             : FD_STATIC_ASSERT( offsetof( fd_gui_hist_tile_day_key_t,    day_idx  )==offsetof( fd_gui_timeline_tile_day_t, day_idx  ), key_layout );
      32             : FD_STATIC_ASSERT( offsetof( fd_gui_hist_tile_day_key_t,    tile_idx )==offsetof( fd_gui_timeline_tile_day_t, tile_idx ), key_layout );
      33             : 
      34           0 : #define FD_GUI_HIST_KEYSHAPE_TIMESERIES (0) /* (ts, ...)        */
      35           0 : #define FD_GUI_HIST_KEYSHAPE_SLOT_BANK  (1) /* (slot, bank_seq) */
      36           0 : #define FD_GUI_HIST_KEYSHAPE_EPOCH      (2) /* (epoch)          */
      37           0 : #define FD_GUI_HIST_KEYSHAPE_DAY_TILE   (3) /* (day_idx, tile_idx) */
      38           0 : #define FD_GUI_HIST_KEYSHAPE_FEC        (4) /* (16-byte FEC event key) */
      39             : 
      40             : static inline long
      41           0 : fd_gui_hist_dbi_res_ns( int dbi FD_PARAM_UNUSED ) {
      42           0 :   return FD_GUI_HIST_RES_1S_NS; /* all TS DBs use the 1s resolution */
      43           0 : }
      44             : 
      45             : static inline ulong
      46           0 : fd_gui_hist_dbi_ts_off( int dbi ) {
      47           0 :   switch( dbi ) {
      48           0 :     case FD_GUI_HIST_SHRED_EVENTS:     return offsetof( fd_gui_shred_batch_t,      insert_time_ns    );
      49           0 :     case FD_GUI_HIST_TILE_TIMERS:      return offsetof( fd_gui_tile_timers_hist_t, sample_time_nanos );
      50           0 :     case FD_GUI_HIST_SCHEDULER_COUNTS: return offsetof( fd_gui_scheduler_counts_t, sample_time_ns    );
      51           0 :     case FD_GUI_HIST_TILE_STATS:       return offsetof( fd_gui_tile_stats_t,       sample_time_nanos );
      52           0 :     case FD_GUI_HIST_TXN_WATERFALL:    return offsetof( fd_gui_txn_waterfall_t,    sample_time_nanos );
      53           0 :     case FD_GUI_HIST_TXN_START:        return offsetof( fd_gui_store_txn_start_t,  insert_time_ns    );
      54           0 :     case FD_GUI_HIST_TXN_END:          return offsetof( fd_gui_store_txn_end_t,    insert_time_ns    );
      55           0 :     case FD_GUI_HIST_REPLAY_TXN:       return offsetof( fd_gui_store_replay_txn_t, insert_time_ns    );
      56           0 :     case FD_GUI_HIST_TIMELINE_DAY:     return offsetof( fd_gui_timeline_day_t,     end_time_ns       );
      57           0 :     case FD_GUI_HIST_FEC_EVENT_ARCHIVE: return offsetof( fd_gui_fec_event_batch_t, insert_time_ns    );
      58           0 :     default:                           return 0UL;
      59           0 :   }
      60           0 : }
      61             : 
      62             : static inline ulong
      63           0 : fd_gui_hist_window( long ts_ns, long res_ns ) {
      64           0 :   return (ulong)( ts_ns / res_ns );
      65           0 : }
      66             : 
      67             : static int
      68           0 : fd_gui_hist_keyshape( int dbi ) {
      69           0 :   switch( dbi ) {
      70           0 :     case FD_GUI_HIST_SCHEDULER_COUNTS:
      71           0 :     case FD_GUI_HIST_TILE_TIMERS:
      72           0 :     case FD_GUI_HIST_TILE_STATS:
      73           0 :     case FD_GUI_HIST_TXN_WATERFALL:
      74           0 :     case FD_GUI_HIST_SHRED_EVENTS:
      75           0 :     case FD_GUI_HIST_TXN_START:
      76           0 :     case FD_GUI_HIST_TXN_END:          return FD_GUI_HIST_KEYSHAPE_TIMESERIES;
      77           0 :     case FD_GUI_HIST_REPLAY_TXN:
      78           0 :     case FD_GUI_HIST_TIMELINE_DAY:
      79           0 :     case FD_GUI_HIST_FEC_EVENT_ARCHIVE: return FD_GUI_HIST_KEYSHAPE_TIMESERIES;
      80           0 :     case FD_GUI_HIST_SLOT:
      81           0 :     case FD_GUI_HIST_LEADER_SLOT:      return FD_GUI_HIST_KEYSHAPE_SLOT_BANK;
      82           0 :     case FD_GUI_HIST_EPOCH:            return FD_GUI_HIST_KEYSHAPE_EPOCH;
      83           0 :     case FD_GUI_HIST_TIMELINE_TILE_DAY: return FD_GUI_HIST_KEYSHAPE_DAY_TILE;
      84           0 :     case FD_GUI_HIST_FEC_EVENTS:       return FD_GUI_HIST_KEYSHAPE_FEC;
      85           0 :     default: FD_LOG_ERR(( "invalid dbi %d", dbi )); return -1;
      86           0 :   }
      87           0 : }
      88             : 
      89             : static inline int
      90           0 : fd_gui_hist_is_timeseries( int dbi ) {
      91           0 :   return fd_gui_hist_keyshape( dbi )==FD_GUI_HIST_KEYSHAPE_TIMESERIES;
      92           0 : }
      93             : 
      94             : static ulong
      95           0 : fd_gui_hist_slot_key_hash( void const * key ) {
      96           0 :   fd_gui_hist_slot_key_t const * k = key;
      97           0 :   return fd_ulong_hash( k->slot );
      98           0 : }
      99             : 
     100             : static int
     101           0 : fd_gui_hist_slot_key_cmp( void const * a, void const * b ) {
     102           0 :   fd_gui_hist_slot_key_t const * ka = a;
     103           0 :   fd_gui_hist_slot_key_t const * kb = b;
     104           0 :   if( ka->slot<kb->slot ) return -1;
     105           0 :   if( ka->slot>kb->slot ) return  1;
     106           0 :   if( ka->bank_seq==ULONG_MAX || kb->bank_seq==ULONG_MAX ) return 0;
     107           0 :   if( ka->bank_seq<kb->bank_seq ) return -1;
     108           0 :   if( ka->bank_seq>kb->bank_seq ) return  1;
     109           0 :   return 0;
     110           0 : }
     111             : 
     112             : static ulong
     113           0 : fd_gui_hist_leader_slot_key_hash( void const * key ) {
     114           0 :   fd_gui_hist_leader_slot_key_t const * k = key;
     115           0 :   return fd_ulong_hash( k->slot );
     116           0 : }
     117             : 
     118             : static int
     119           0 : fd_gui_hist_leader_slot_key_cmp( void const * a, void const * b ) {
     120           0 :   fd_gui_hist_leader_slot_key_t const * ka = a;
     121           0 :   fd_gui_hist_leader_slot_key_t const * kb = b;
     122           0 :   if( ka->slot<kb->slot ) return -1;
     123           0 :   if( ka->slot>kb->slot ) return  1;
     124           0 :   if( ka->bank_seq==ULONG_MAX || kb->bank_seq==ULONG_MAX ) return 0;
     125           0 :   if( ka->bank_seq<kb->bank_seq ) return -1;
     126           0 :   if( ka->bank_seq>kb->bank_seq ) return  1;
     127           0 :   return 0;
     128           0 : }
     129             : 
     130             : static ulong
     131           0 : fd_gui_hist_epoch_key_hash( void const * key ) {
     132           0 :   fd_gui_hist_epoch_key_t const * k = key;
     133           0 :   return fd_ulong_hash( k->epoch );
     134           0 : }
     135             : 
     136             : static int
     137           0 : fd_gui_hist_epoch_key_cmp( void const * a, void const * b ) {
     138           0 :   fd_gui_hist_epoch_key_t const * ka = a;
     139           0 :   fd_gui_hist_epoch_key_t const * kb = b;
     140           0 :   if( ka->epoch<kb->epoch ) return -1;
     141           0 :   if( ka->epoch>kb->epoch ) return  1;
     142           0 :   return 0;
     143           0 : }
     144             : 
     145             : static ulong
     146           0 : fd_gui_hist_tile_day_key_hash( void const * key ) {
     147           0 :   fd_gui_hist_tile_day_key_t const * k = key;
     148           0 :   return fd_ulong_hash( k->day_idx ) ^ fd_ulong_hash( k->tile_idx+0x9e3779b97f4a7c15UL );
     149           0 : }
     150             : 
     151             : static int
     152           0 : fd_gui_hist_tile_day_key_cmp( void const * a, void const * b ) {
     153           0 :   fd_gui_hist_tile_day_key_t const * ka = a;
     154           0 :   fd_gui_hist_tile_day_key_t const * kb = b;
     155           0 :   if( ka->day_idx<kb->day_idx )   return -1;
     156           0 :   if( ka->day_idx>kb->day_idx )   return  1;
     157           0 :   if( ka->tile_idx<kb->tile_idx ) return -1;
     158           0 :   if( ka->tile_idx>kb->tile_idx ) return  1;
     159           0 :   return 0;
     160           0 : }
     161             : 
     162             : static ulong
     163           0 : fd_gui_hist_fec_key_hash( void const * key ) {
     164           0 :   ulong words[ 2 ];
     165           0 :   fd_memcpy( words, key, sizeof(words) );
     166           0 :   return fd_ulong_hash( words[ 0 ] ) ^ fd_ulong_hash( words[ 1 ] );
     167           0 : }
     168             : 
     169             : static int
     170             : fd_gui_hist_fec_key_cmp( void const * a,
     171           0 :                          void const * b ) {
     172           0 :   return memcmp( a, b, sizeof(fd_gui_fec_event_key_t) );
     173           0 : }
     174             : 
     175             : static inline ulong
     176           0 : fd_gui_hist_rec_sz( int dbi ) {
     177           0 :   switch( dbi ) {
     178           0 :     case FD_GUI_HIST_SHRED_EVENTS:     return sizeof(fd_gui_shred_batch_t);
     179           0 :     case FD_GUI_HIST_TILE_TIMERS:      return sizeof(fd_gui_tile_timers_hist_t);
     180           0 :     case FD_GUI_HIST_SCHEDULER_COUNTS: return sizeof(fd_gui_scheduler_counts_t);
     181           0 :     case FD_GUI_HIST_TILE_STATS:       return sizeof(fd_gui_tile_stats_t);
     182           0 :     case FD_GUI_HIST_TXN_WATERFALL:    return sizeof(fd_gui_txn_waterfall_t);
     183           0 :     case FD_GUI_HIST_TXN_START:        return sizeof(fd_gui_store_txn_start_t);
     184           0 :     case FD_GUI_HIST_TXN_END:          return sizeof(fd_gui_store_txn_end_t);
     185           0 :     case FD_GUI_HIST_REPLAY_TXN:       return sizeof(fd_gui_store_replay_txn_t);
     186           0 :     case FD_GUI_HIST_SLOT:             return sizeof(fd_gui_slot_t);
     187           0 :     case FD_GUI_HIST_LEADER_SLOT:      return sizeof(fd_gui_leader_slot_t);
     188           0 :     case FD_GUI_HIST_EPOCH:            return sizeof(fd_gui_epoch_t);
     189           0 :     case FD_GUI_HIST_TIMELINE_DAY:     return sizeof(fd_gui_timeline_day_t);
     190           0 :     case FD_GUI_HIST_TIMELINE_TILE_DAY: return sizeof(fd_gui_timeline_tile_day_t);
     191           0 :     case FD_GUI_HIST_FEC_EVENTS:       return sizeof(fd_gui_fec_event_t);
     192           0 :     case FD_GUI_HIST_FEC_EVENT_ARCHIVE: return sizeof(fd_gui_fec_event_batch_t);
     193           0 :     default:                           return 0UL;
     194           0 :   }
     195           0 : }
     196             : 
     197             : /* fd_gui_hist_key_sz returns the KV key width for `dbi`: 16 bytes
     198             :    (slot, bank_seq) for slot-keyed DBs, 8 bytes for EPOCH, 16 bytes
     199             :    (day_idx, tile_idx) for TIMELINE_TILE_DAY, 16 bytes for FEC_EVENTS,
     200             :    0 for TS. */
     201             : static inline ulong
     202           0 : fd_gui_hist_key_sz( int dbi ) {
     203           0 :   switch( dbi ) {
     204           0 :     case FD_GUI_HIST_SLOT:        return sizeof(fd_gui_hist_slot_key_t);
     205           0 :     case FD_GUI_HIST_LEADER_SLOT: return sizeof(fd_gui_hist_leader_slot_key_t);
     206           0 :     case FD_GUI_HIST_EPOCH:       return sizeof(fd_gui_hist_epoch_key_t);
     207           0 :     case FD_GUI_HIST_TIMELINE_TILE_DAY: return sizeof(fd_gui_hist_tile_day_key_t);
     208           0 :     case FD_GUI_HIST_FEC_EVENTS:  return sizeof(fd_gui_fec_event_key_t);
     209           0 :     default:                      return 0UL; /* TS DBs have no key */
     210           0 :   }
     211           0 : }
     212             : 
     213             : /* fd_gui_hist_key_hash / fd_gui_hist_key_cmp return the per-DB KV key
     214             :    callbacks for `dbi` (NULL for TS DBs). */
     215             : 
     216             : static inline ulong
     217           0 : ( * fd_gui_hist_key_hash( int dbi ) )( void const * key ) {
     218           0 :   switch( dbi ) {
     219           0 :     case FD_GUI_HIST_SLOT:        return fd_gui_hist_slot_key_hash;
     220           0 :     case FD_GUI_HIST_LEADER_SLOT: return fd_gui_hist_leader_slot_key_hash;
     221           0 :     case FD_GUI_HIST_EPOCH:       return fd_gui_hist_epoch_key_hash;
     222           0 :     case FD_GUI_HIST_TIMELINE_TILE_DAY: return fd_gui_hist_tile_day_key_hash;
     223           0 :     case FD_GUI_HIST_FEC_EVENTS:  return fd_gui_hist_fec_key_hash;
     224           0 :     default:                      return NULL;
     225           0 :   }
     226           0 : }
     227             : 
     228             : static inline int
     229           0 : ( * fd_gui_hist_key_cmp( int dbi ) )( void const * a, void const * b ) {
     230           0 :   switch( dbi ) {
     231           0 :     case FD_GUI_HIST_SLOT:        return fd_gui_hist_slot_key_cmp;
     232           0 :     case FD_GUI_HIST_LEADER_SLOT: return fd_gui_hist_leader_slot_key_cmp;
     233           0 :     case FD_GUI_HIST_EPOCH:       return fd_gui_hist_epoch_key_cmp;
     234           0 :     case FD_GUI_HIST_TIMELINE_TILE_DAY: return fd_gui_hist_tile_day_key_cmp;
     235           0 :     case FD_GUI_HIST_FEC_EVENTS:  return fd_gui_hist_fec_key_cmp;
     236           0 :     default:                      return NULL;
     237           0 :   }
     238           0 : }
     239             : 
     240             : /* fd_gui_hist_kv_stride approximates fd_gui_store's per-record ring stride
     241             :    for KV DB `dbi`.  The store adds no header (the key and link live inside
     242             :    the value), so the stride is just the align-padded record. */
     243             : static inline ulong
     244           0 : fd_gui_hist_kv_stride( int dbi ) {
     245           0 :   return fd_ulong_align_up( fd_gui_hist_rec_sz( dbi ), 8UL );
     246           0 : }
     247             : 
     248             : /* fd_gui_hist_kv_cap returns the number of records of KV DB `dbi` that
     249             :    fit in one store region. */
     250             : static inline ulong
     251           0 : fd_gui_hist_kv_cap( int dbi ) {
     252           0 :   return FD_GUI_STORE_REGION_SZ / fd_gui_hist_kv_stride( dbi );
     253           0 : }
     254             : 
     255             : /* fd_gui_hist_base_regions returns the number of regions DB `dbi` is
     256             :    guaranteed to keep (see fd_gui_store_desc_t::base_regions).  The
     257             :    retention floors are:
     258             :      - TS DBs: the newest full region plus the head being filled.
     259             :      - slot/leader_slot/epoch: at least FD_GUI_HIST_MIN_EPOCHS epochs of
     260             :        records plus one partially filled head region.
     261             :      - timeline_day: today and yesterday (one record per region).
     262             :      - timeline_tile_day: today's and yesterday's record for every tile
     263             :        plus one partially filled head region.
     264             :    Everything above these floors comes from the shared pool, which is
     265             :    reclaimed oldest region first across DBs. */
     266             : static inline ulong
     267           0 : fd_gui_hist_ceil_div( ulong a, ulong b ) {
     268           0 :   return (a+b-1UL)/b;
     269           0 : }
     270             : 
     271             : static ulong
     272           0 : fd_gui_hist_base_regions( int dbi ) {
     273           0 :   ulong cap = fd_gui_hist_kv_cap( dbi );
     274           0 :   switch( dbi ) {
     275           0 :     case FD_GUI_HIST_SLOT:              return fd_gui_hist_ceil_div( FD_GUI_HIST_MIN_EPOCHS*MAX_SLOTS_PER_EPOCH, cap ) + 1UL;
     276           0 :     case FD_GUI_HIST_LEADER_SLOT:       return fd_gui_hist_ceil_div( FD_GUI_HIST_MIN_EPOCHS*FD_GUI_HIST_MAX_LEADER_SLOTS_PER_EPOCH, cap ) + 1UL;
     277           0 :     case FD_GUI_HIST_EPOCH:             return fd_gui_hist_ceil_div( FD_GUI_HIST_MIN_EPOCHS, cap ) + 1UL;
     278           0 :     case FD_GUI_HIST_TIMELINE_DAY:      return 2UL;
     279           0 :     case FD_GUI_HIST_TIMELINE_TILE_DAY: return fd_gui_hist_ceil_div( 2UL*FD_TOPO_MAX_TILES, cap ) + 1UL;
     280           0 :     default:                            return 2UL;
     281           0 :   }
     282           0 : }
     283             : 
     284             : /* fd_gui_hist_max_keys returns the most distinct keys KV DB `dbi` can
     285             :    ever hold.  Shared regions beyond this are never needed. */
     286             : static ulong
     287           0 : fd_gui_hist_max_keys( int dbi ) {
     288           0 :   switch( dbi ) {
     289           0 :     case FD_GUI_HIST_SLOT:              return FD_GUI_HIST_MAX_EPOCHS*MAX_SLOTS_PER_EPOCH;
     290           0 :     case FD_GUI_HIST_LEADER_SLOT:       return FD_GUI_HIST_MAX_EPOCHS*FD_GUI_HIST_MAX_LEADER_SLOTS_PER_EPOCH;
     291           0 :     case FD_GUI_HIST_EPOCH:             return FD_GUI_HIST_MAX_EPOCHS;
     292           0 :     case FD_GUI_HIST_TIMELINE_TILE_DAY: return FD_GUI_HIST_MAX_TIMELINE_DAYS*FD_TOPO_MAX_TILES;
     293           0 :     default: FD_LOG_ERR(( "invalid kv dbi %d", dbi )); return 0UL;
     294           0 :   }
     295           0 : }
     296             : 
     297             : /* fd_gui_hist_kv_max_records returns max_records for KV DB `dbi`, given
     298             :    its base_regions and the number of shared regions in the store. */
     299             : static ulong
     300             : fd_gui_hist_kv_max_records( int   dbi,
     301             :                             ulong base_regions,
     302           0 :                             ulong shared ) {
     303           0 :   if( dbi==FD_GUI_HIST_FEC_EVENTS ) return FD_GUI_FEC_RECORD_MAX;
     304           0 :   ulong cap = fd_gui_hist_kv_cap( dbi );
     305           0 :   ulong lo  = base_regions*cap;
     306           0 :   ulong hi  = (base_regions+shared)*cap;
     307           0 :   return fd_ulong_max( lo, fd_ulong_min( hi, fd_gui_hist_max_keys( dbi ) ) );
     308           0 : }
     309             : 
     310             : fd_gui_store_desc_t *
     311             : fd_gui_hist_db_descs( fd_gui_store_desc_t descs[ static FD_GUI_HIST_CNT ],
     312           0 :                       ulong               store_bytes ) {
     313           0 :   static char const * const names[ FD_GUI_HIST_CNT ] = {
     314           0 :     "scheduler_counts", "tile_timers", "shred_events", "txn_start",
     315           0 :     "txn_end", "slot", "leader_slot", "epoch", "tile_stats",
     316           0 :     "txn_waterfall", "timeline_day", "replay_txn", "timeline_tile_day",
     317           0 :     "fec_events", "fec_event_archive"
     318           0 :   };
     319             : 
     320           0 :   for( int i=0; i<FD_GUI_HIST_CNT; i++ ) descs[ i ].base_regions = fd_gui_hist_base_regions( i );
     321           0 :   ulong shared = fd_ulong_sat_sub( store_bytes, fd_gui_store_min_size( FD_GUI_HIST_CNT, descs ) ) / FD_GUI_STORE_REGION_SZ;
     322             : 
     323           0 :   for( int i=0; i<FD_GUI_HIST_CNT; i++ ) {
     324           0 :     int   ts     = fd_gui_hist_is_timeseries( i );
     325           0 :     ulong rec_sz = fd_gui_hist_rec_sz( i );
     326           0 :     ulong key_sz = ts ? 0UL : fd_gui_hist_key_sz( i ); /* TS DBs have no key (0) */
     327           0 :     ulong val_sz = ts ? fd_ulong_max( rec_sz, 1UL ) : rec_sz;
     328           0 :     ulong max_records = ts ? 0UL : fd_gui_hist_kv_max_records( i, descs[ i ].base_regions, shared );
     329             : 
     330           0 :     descs[ i ].name        = names[ i ];
     331           0 :     descs[ i ].kind        = ts ? FD_GUI_STORE_KIND_TS : FD_GUI_STORE_KIND_KV;
     332           0 :     descs[ i ].key_off     = 0UL; /* the key is the leading field(s) of the value */
     333           0 :     descs[ i ].key_sz      = key_sz;
     334           0 :     descs[ i ].key_hash    = ts ? NULL : fd_gui_hist_key_hash( i );
     335           0 :     descs[ i ].key_cmp     = ts ? NULL : fd_gui_hist_key_cmp( i );
     336           0 :     descs[ i ].val_sz      = val_sz;
     337           0 :     descs[ i ].val_align   = 8UL;
     338           0 :     descs[ i ].ts_off      = ts ? fd_gui_hist_dbi_ts_off( i ) : 0UL;
     339           0 :     descs[ i ].granularity = ts ? (ulong)fd_gui_hist_dbi_res_ns( i ) : 0UL;
     340           0 :     descs[ i ].max_records = max_records;
     341           0 :   }
     342           0 :   return descs;
     343           0 : }
     344             : 
     345             : ulong
     346           0 : fd_gui_hist_store_footprint( ulong store_bytes ) {
     347           0 :   fd_gui_store_desc_t descs[ FD_GUI_HIST_CNT ];
     348           0 :   return fd_gui_store_footprint( store_bytes, FD_GUI_HIST_CNT, fd_gui_hist_db_descs( descs, store_bytes ) );
     349           0 : }
     350             : 
     351             : void *
     352             : fd_gui_hist_store_new( void *       mem,
     353             :                        char const * path,
     354             :                        ulong        store_bytes,
     355           0 :                        ulong        seed ) {
     356           0 :   fd_gui_store_desc_t descs[ FD_GUI_HIST_CNT ];
     357           0 :   return fd_gui_store_new( mem, path, store_bytes, FD_GUI_HIST_CNT, seed, fd_gui_hist_db_descs( descs, store_bytes ) );
     358           0 : }
     359             : 
     360             : /* Maximum number of records deleted per eviction iteration. */
     361           0 : #define FD_GUI_HIST_EVICT_BATCH (512UL)
     362             : 
     363             : struct fd_gui_hist_private {
     364             :   ulong magic;          /* ==FD_GUI_HIST_MAGIC after fd_gui_hist_new */
     365             :   long  last_ts[ FD_GUI_HIST_CNT ];
     366             :   int   has_last_ts[ FD_GUI_HIST_CNT ];
     367             : 
     368             :   fd_gui_hist_metrics_t metrics;
     369             : };
     370             : 
     371             : FD_FN_CONST ulong
     372           0 : fd_gui_hist_align( void ) {
     373           0 :   return 128UL;
     374           0 : }
     375             : 
     376             : FD_FN_CONST ulong
     377           0 : fd_gui_hist_footprint( void ) {
     378           0 :   ulong l = FD_LAYOUT_INIT;
     379           0 :   l = FD_LAYOUT_APPEND( l, fd_gui_hist_align(), sizeof(fd_gui_hist_t) );
     380           0 :   return FD_LAYOUT_FINI( l, fd_gui_hist_align() );
     381           0 : }
     382             : 
     383             : void *
     384             : fd_gui_hist_new( void *                 mem,
     385           0 :                  fd_gui_store_t const * db ) {
     386           0 :   if( FD_UNLIKELY( !mem ) ) { FD_LOG_WARNING(( "fd_gui_hist_new: null mem" )); return NULL; }
     387           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; }
     388           0 :   if( FD_UNLIKELY( !db ) ) { FD_LOG_WARNING(( "fd_gui_hist_new: null db" )); return NULL; }
     389             : 
     390           0 :   if( FD_UNLIKELY( fd_gui_store_cnt( db )!=FD_GUI_HIST_CNT ) ) {
     391           0 :     FD_LOG_WARNING(( "fd_gui_hist_new: store has %lu DBs, expected %d", fd_gui_store_cnt( db ), FD_GUI_HIST_CNT ));
     392           0 :     return NULL;
     393           0 :   }
     394             : 
     395           0 :   fd_memset( mem, 0, sizeof(fd_gui_hist_t) );
     396           0 :   FD_SCRATCH_ALLOC_INIT( l, mem );
     397           0 :   fd_gui_hist_t * hist = FD_SCRATCH_ALLOC_APPEND( l, fd_gui_hist_align(), sizeof(fd_gui_hist_t) );
     398           0 :   FD_SCRATCH_ALLOC_FINI( l, fd_gui_hist_align() );
     399             : 
     400           0 :   FD_COMPILER_MFENCE();
     401           0 :   hist->magic = FD_GUI_HIST_MAGIC;
     402           0 :   FD_COMPILER_MFENCE();
     403           0 :   return mem;
     404           0 : }
     405             : 
     406             : fd_gui_hist_t *
     407           0 : fd_gui_hist_join( void * mem ) {
     408           0 :   if( FD_UNLIKELY( !mem ) ) return NULL;
     409           0 :   fd_gui_hist_t * hist = (fd_gui_hist_t *)mem;
     410           0 :   if( FD_UNLIKELY( hist->magic!=FD_GUI_HIST_MAGIC ) ) { FD_LOG_WARNING(( "fd_gui_hist_join: bad magic" )); return NULL; }
     411           0 :   return hist;
     412           0 : }
     413             : 
     414             : void *
     415           0 : fd_gui_hist_leave( fd_gui_hist_t * hist ) {
     416           0 :   return (void *)hist;
     417           0 : }
     418             : 
     419             : void *
     420           0 : fd_gui_hist_delete( void * mem ) {
     421           0 :   if( FD_UNLIKELY( !mem ) ) return NULL;
     422           0 :   ((fd_gui_hist_t *)mem)->magic = 0UL;
     423           0 :   return mem;
     424           0 : }
     425             : 
     426             : static inline fd_gui_hist_t *
     427           0 : fd_gui_hist( fd_gui_t * gui ) {
     428           0 :   return (fd_gui_hist_t *)gui->hist;
     429           0 : }
     430             : 
     431             : static inline fd_gui_store_t *
     432           0 : fd_gui_hist_db( fd_gui_t * gui ) {
     433           0 :   return (fd_gui_store_t *)gui->db;
     434           0 : }
     435             : 
     436             : fd_gui_hist_metrics_t const *
     437           0 : fd_gui_hist_metrics( fd_gui_t const * gui ) {
     438           0 :   if( FD_UNLIKELY( !gui->hist ) ) return NULL;
     439           0 :   return &((fd_gui_hist_t const *)gui->hist)->metrics;
     440           0 : }
     441             : 
     442             : static int
     443             : fd_gui_hist_reclaim_eligible_cb( ulong        dbi,
     444             :                                  void const * rec FD_PARAM_UNUSED,
     445           0 :                                  void *       ctx FD_PARAM_UNUSED ) {
     446             :   /* FEC staging is not disposable history, including closed records
     447             :      awaiting archival.  Only fd_gui_fec_archive drains it. */
     448           0 :   return dbi!=FD_GUI_HIST_FEC_EVENTS;
     449           0 : }
     450             : 
     451             : static int
     452             : fd_gui_hist_make_room( fd_gui_t * gui,
     453             :                        int        dbi,
     454           0 :                        int        rc ) {
     455           0 :   if( FD_UNLIKELY( rc!=FD_GUI_STORE_MAP_FULL && rc!=FD_GUI_STORE_RING_FULL ) ) return 0;
     456           0 :   if( dbi==FD_GUI_HIST_FEC_EVENTS ) return !!fd_gui_fec_archive( gui, FD_GUI_HIST_EVICT_BATCH );
     457           0 :   if( rc==FD_GUI_STORE_MAP_FULL && fd_gui_store_reclaim( gui->db, ULONG_MAX, FD_GUI_HIST_EVICT_BATCH, fd_gui_hist_reclaim_eligible_cb, NULL ) ) return 1;
     458           0 :   return !!fd_gui_store_reclaim( gui->db, (ulong)dbi, FD_GUI_HIST_EVICT_BATCH, fd_gui_hist_reclaim_eligible_cb, NULL );
     459           0 : }
     460             : 
     461             : void *
     462             : fd_gui_hist_kv_get_or_create( fd_gui_t *   gui,
     463             :                               int          dbi,
     464           0 :                               void const * key ) {
     465           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; }
     466             : 
     467           0 :   int forced_eviction = 0;
     468           0 :   void * val = NULL;
     469           0 :   int rc;
     470           0 :   for(;;) {
     471           0 :     rc = fd_gui_store_kv_get_or_create( fd_gui_hist_db( gui ), (ulong)dbi, key, &val );
     472           0 :     if( FD_LIKELY( rc==FD_GUI_STORE_SUCCESS ) ) {
     473           0 :       if( FD_UNLIKELY( forced_eviction ) ) fd_gui_hist( gui )->metrics.reserves[ dbi ]++;
     474           0 :       return val;
     475           0 :     }
     476             :     /* FEC staging pressure gets one bounded archive attempt. */
     477           0 :     if( forced_eviction && dbi==FD_GUI_HIST_FEC_EVENTS ) break;
     478           0 :     if( FD_UNLIKELY( !fd_gui_hist_make_room( gui, dbi, rc ) ) ) break;
     479           0 :     forced_eviction = 1;
     480           0 :   }
     481           0 :   if( FD_UNLIKELY( rc==FD_GUI_STORE_MAP_FULL || rc==FD_GUI_STORE_RING_FULL ) ) {
     482           0 :     fd_gui_hist( gui )->metrics.map_full[ dbi ]++;
     483           0 :   }
     484           0 :   return NULL;
     485           0 : }
     486             : 
     487             : void *
     488             : fd_gui_hist_ts_emplace( fd_gui_t * gui,
     489             :                         int        dbi,
     490           0 :                         long       stored_ts ) {
     491           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; }
     492           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; }
     493           0 :   fd_gui_store_t * db = fd_gui_hist_db( gui );
     494             : 
     495           0 :   ulong rec_sz = fd_gui_hist_rec_sz( dbi );
     496           0 :   if( FD_UNLIKELY( !rec_sz ) ) { FD_LOG_WARNING(( "fd_gui_hist_ts_emplace: dbi %d has no record type", dbi )); return NULL; }
     497             : 
     498           0 :   fd_gui_hist_t * hist = fd_gui_hist( gui );
     499           0 :   if( FD_UNLIKELY( hist->has_last_ts[ dbi ] && stored_ts<hist->last_ts[ dbi ] ) ) {
     500           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 ));
     501           0 :     return NULL;
     502           0 :   }
     503             : 
     504           0 :   int forced_eviction = 0;
     505           0 :   void * val = NULL;
     506           0 :   int rc;
     507           0 :   for(;;) {
     508           0 :     rc = fd_gui_store_ts_emplace( db, (ulong)dbi, stored_ts, &val );
     509           0 :     if( FD_LIKELY( rc==FD_GUI_STORE_SUCCESS ) ) {
     510           0 :       hist->last_ts[ dbi ]     = stored_ts;
     511           0 :       hist->has_last_ts[ dbi ] = 1;
     512           0 :       if( FD_UNLIKELY( forced_eviction ) ) hist->metrics.reserves[ dbi ]++;
     513           0 :       return val;
     514           0 :     }
     515           0 :     if( FD_UNLIKELY( !fd_gui_hist_make_room( gui, dbi, rc ) ) ) break;
     516           0 :     forced_eviction = 1;
     517           0 :   }
     518           0 :   if( FD_UNLIKELY( rc==FD_GUI_STORE_MAP_FULL ) ) {
     519           0 :     fd_gui_hist( gui )->metrics.map_full[ dbi ]++;
     520           0 :     FD_LOG_WARNING(( "fd_gui_hist_ts_emplace: dropping a record for dbi %d; store full and nothing left to evict", dbi ));
     521           0 :   }
     522           0 :   return NULL;
     523           0 : }
     524             : 
     525             : int
     526             : fd_gui_hist_ts_append( fd_gui_t *   gui,
     527             :                        int          dbi,
     528           0 :                        void const * val ) {
     529           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; }
     530           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; }
     531           0 :   ulong rec_sz = fd_gui_hist_rec_sz( dbi );
     532           0 :   if( FD_UNLIKELY( !rec_sz ) ) { FD_LOG_WARNING(( "fd_gui_hist_ts_append: dbi %d has no record type", dbi )); return -1; }
     533           0 :   long stored_ts;
     534           0 :   fd_memcpy( &stored_ts, (uchar const *)val + fd_gui_hist_dbi_ts_off( dbi ), sizeof(stored_ts) );
     535           0 :   void * dst = fd_gui_hist_ts_emplace( gui, dbi, stored_ts );
     536           0 :   if( FD_UNLIKELY( !dst ) ) return -1;
     537           0 :   fd_memcpy( dst, val, rec_sz );
     538           0 :   return 0;
     539           0 : }
     540             : 
     541             : static void
     542           0 : fd_gui_hist_iter_load( fd_gui_hist_iter_t * iter ) {
     543           0 :   for(;;) {
     544           0 :     if( fd_gui_store_ts_scan_done( &iter->_it ) ) { iter->rec = NULL; return; }
     545           0 :     void const * rec = iter->_it.rec; /* the gui record verbatim; self-describing */
     546           0 :     if( !iter->_filter || iter->_filter( rec, iter->_filter_ctx ) ) {
     547           0 :       iter->rec = rec;
     548           0 :       return;
     549           0 :     }
     550           0 :     fd_gui_store_ts_scan_next( &iter->_it );
     551           0 :   }
     552           0 : }
     553             : 
     554             : int
     555             : fd_gui_hist_range_begin( fd_gui_t *                   gui,
     556             :                          fd_gui_hist_iter_t *         iter,
     557             :                          int                          dbi,
     558             :                          long                         lo_ns,
     559             :                          long                         hi_ns,
     560             :                          fd_gui_hist_range_filter_fn  filter,
     561           0 :                          void *                       filter_ctx ) {
     562           0 :   fd_gui_store_t * db = fd_gui_hist_db( gui );
     563           0 :   memset( iter, 0, sizeof(fd_gui_hist_iter_t) );
     564             : 
     565           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; }
     566             : 
     567           0 :   FD_TEST( lo_ns!=LONG_MIN && hi_ns!=LONG_MAX ); /* open-ended queries not supported */
     568             : 
     569           0 :   long res_ns = fd_gui_hist_dbi_res_ns( dbi );
     570           0 :   ulong window_lo = (lo_ns<=0L) ? 0UL : fd_gui_hist_window( lo_ns, res_ns );
     571           0 :   ulong window_hi = fd_gui_hist_window( hi_ns, res_ns );
     572             : 
     573           0 :   iter->_dbi        = dbi;
     574           0 :   iter->rec_sz      = fd_gui_hist_rec_sz( dbi );
     575           0 :   iter->_filter     = filter;
     576           0 :   iter->_filter_ctx = filter_ctx;
     577             : 
     578             :   /* Clamp the request time range to the index bounds. */
     579           0 :   long first_ts;
     580           0 :   long last_ts;
     581           0 :   if( FD_UNLIKELY( !fd_gui_store_ts_live_timestamp_bounds( db, (ulong)dbi, &first_ts, &last_ts ) ) ) return 0; /* empty ring */
     582             : 
     583           0 :   ulong first_window = fd_gui_hist_window( fd_long_max( first_ts, 0L ), res_ns );
     584           0 :   ulong last_window  = fd_gui_hist_window( fd_long_max( last_ts,  0L ), res_ns );
     585           0 :   window_lo = fd_ulong_max( window_lo, first_window );
     586           0 :   window_hi = fd_ulong_min( window_hi, last_window );
     587           0 :   if( FD_UNLIKELY( window_lo>window_hi ) ) return 0; /* request does not overlap the ring */
     588             : 
     589           0 :   fd_gui_store_ts_scan_begin( db, &iter->_it, (ulong)dbi, window_lo, window_hi, NULL, NULL );
     590           0 :   fd_gui_hist_iter_load( iter );
     591           0 :   return 0;
     592           0 : }
     593             : 
     594             : int
     595           0 : fd_gui_hist_range_next( fd_gui_hist_iter_t * iter ) {
     596           0 :   if( FD_UNLIKELY( !iter->rec ) ) return 0;
     597           0 :   if( iter->_emitted ) {
     598           0 :     fd_gui_store_ts_scan_next( &iter->_it );
     599           0 :     fd_gui_hist_iter_load( iter );
     600           0 :     if( !iter->rec ) return 0;
     601           0 :   }
     602           0 :   iter->_emitted = 1;
     603           0 :   return 1;
     604           0 : }
     605             : 
     606             : void
     607           0 : fd_gui_hist_range_end( fd_gui_hist_iter_t * iter ) {
     608           0 :   fd_gui_store_ts_scan_end( &iter->_it );
     609           0 : }
     610             : 
     611             : void *
     612             : fd_gui_hist_kv_get( fd_gui_t *   gui,
     613             :                     int          dbi,
     614           0 :                     void const * key ) {
     615           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; }
     616           0 :   return fd_gui_store_kv_get( fd_gui_hist_db( gui ), (ulong)dbi, key );
     617           0 : }
     618             : 
     619             : void *
     620             : fd_gui_hist_kv_get_slot_any( fd_gui_t * gui,
     621             :                              int        dbi,
     622           0 :                              ulong      slot ) {
     623           0 :   if( FD_UNLIKELY( fd_gui_hist_keyshape( dbi )!=FD_GUI_HIST_KEYSHAPE_SLOT_BANK ) ) {
     624           0 :     FD_LOG_WARNING(( "fd_gui_hist_kv_get_slot_any: dbi %d is not slot/bank-keyed", dbi ));
     625           0 :     return NULL;
     626           0 :   }
     627             : 
     628           0 :   fd_gui_hist_slot_key_t key = { .slot=slot, .bank_seq=ULONG_MAX };
     629           0 :   return fd_gui_store_kv_get_any( fd_gui_hist_db( gui ), (ulong)dbi, &key );
     630           0 : }
     631             : 
     632             : /* fd_gui_hist_ts_iter_load reads the iterator's current backend record into
     633             :    the typed iterator fields (or marks it done). */
     634             : static void
     635           0 : fd_gui_hist_ts_iter_load( fd_gui_hist_kv_slot_iter_t * iter ) {
     636           0 :   if( FD_UNLIKELY( fd_gui_store_kv_iter_done( &iter->_it ) ) ) {
     637           0 :     iter->rec      = NULL;
     638           0 :     iter->bank_seq = ULONG_MAX;
     639           0 :     return;
     640           0 :   }
     641           0 :   iter->rec      = iter->_it.rec;
     642           0 :   iter->bank_seq = ( iter->_it.key_sz>=2UL*sizeof(ulong) )
     643           0 :                  ? ((ulong const *)iter->_it.key)[ 1 ]
     644           0 :                  : ULONG_MAX;
     645           0 : }
     646             : 
     647             : fd_gui_hist_kv_slot_iter_t *
     648             : fd_gui_hist_kv_iter_begin( fd_gui_t *                   gui,
     649             :                            fd_gui_hist_kv_slot_iter_t * iter,
     650             :                            int                          dbi,
     651           0 :                            ulong                        slot ) {
     652           0 :   iter->rec      = NULL;
     653           0 :   iter->bank_seq = ULONG_MAX;
     654           0 :   if( FD_UNLIKELY( fd_gui_hist_keyshape( dbi )!=FD_GUI_HIST_KEYSHAPE_SLOT_BANK ) ) {
     655           0 :     FD_LOG_WARNING(( "fd_gui_hist_kv_iter_begin: dbi %d is not slot/bank-keyed", dbi ));
     656           0 :     fd_gui_hist_slot_key_t none = { .slot=ULONG_MAX, .bank_seq=ULONG_MAX };
     657           0 :     fd_gui_store_kv_iter_begin( fd_gui_hist_db( gui ), &iter->_it, (ulong)dbi, &none );
     658           0 :     return iter;
     659           0 :   }
     660           0 :   fd_gui_hist_slot_key_t key = { .slot=slot, .bank_seq=ULONG_MAX };
     661           0 :   fd_gui_store_kv_iter_begin( fd_gui_hist_db( gui ), &iter->_it, (ulong)dbi, &key );
     662           0 :   fd_gui_hist_ts_iter_load( iter );
     663           0 :   return iter;
     664           0 : }
     665             : 
     666             : int
     667           0 : fd_gui_hist_kv_iter_next( fd_gui_hist_kv_slot_iter_t * iter ) {
     668           0 :   if( FD_UNLIKELY( !iter->rec ) ) return 0;
     669           0 :   fd_gui_store_kv_iter_next( &iter->_it );
     670           0 :   fd_gui_hist_ts_iter_load( iter );
     671           0 :   return iter->rec!=NULL;
     672           0 : }
     673             : 
     674             : int
     675           0 : fd_gui_hist_evict_step( fd_gui_t * gui ) {
     676           0 :   if( FD_UNLIKELY( !gui->db || !gui->hist ) ) return 0;
     677             :   /* Keep one shared region in reserve. */
     678           0 :   if( fd_gui_store_shared_free_region_cnt( gui->db )>1UL ) return 0;
     679           0 :   return !!fd_gui_store_reclaim( gui->db, ULONG_MAX, FD_GUI_HIST_EVICT_BATCH, fd_gui_hist_reclaim_eligible_cb, NULL );
     680           0 : }

Generated by: LCOV version 1.14