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

          Line data    Source code
       1             : #ifndef HEADER_fd_src_disco_gui_fd_gui_store_h
       2             : #define HEADER_fd_src_disco_gui_fd_gui_store_h
       3             : 
       4             : /* fd_gui_store is the GUI's historical event *backend*: a single mmap'd
       5             :    file carved into fixed-size regions that the rings claim and release
       6             :    on demand (so each ring grows and shrinks dynamically), plus
       7             :    in-memory indices.
       8             : 
       9             :    The store hosts a fixed set of named rings.  Each ring is one of two
      10             :    *kinds*:
      11             : 
      12             :      - KEY-VALUE (KV) rings are a generic point store: a record is
      13             :        uniquely identified by a caller-supplied key and there is exactly
      14             :        one record per key.
      15             : 
      16             :      - TIME-SERIES (TS) rings are an append-only stream of fixed-size
      17             :        records.  Each record carries its own timestamp.  The store
      18             :        derives the record's `window` (an integer time bucket) from it.
      19             :        Readers query records by a time range scan. Appends must be in
      20             :        nondecreasing timestamp order.
      21             : 
      22             :    ---- ring-buffer backend --------------------------------------------
      23             : 
      24             :    fd_gui_store is one mmap'd file plus in-memory indices.  The file is
      25             :    a superblock page followed by a pool of uniform REGION_SZ regions;
      26             :    regions are claimed/released on demand from a shared LIFO free list
      27             :    (so a given ring has composite regions that are generally
      28             :    discontiguous) and the file is allocated up to the highest claimed
      29             :    region with fallocate as needed, never exceeding size_bytes.  Region
      30             :    ownership and the free list are in-memory; only the superblock
      31             :    persists.
      32             : 
      33             :      file offset 0                data_off
      34             :      +----------------------------+--------+--------+--------+-  -+--------+
      35             :      |        superblock          | region | region | region |... | region |
      36             :      | magic,size,ring[MAX_RINGS] |   0    |   1    |   2    |    | cnt-1  |
      37             :      +----------------------------+--------+--------+--------+-  -+--------+
      38             :      |<-- data_off (page 0..N) -->|<---------- REGION_SZ each ------------>|
      39             :      (allocated to highest claimed region)
      40             : 
      41             :    ---- rings ----------------------------------------------------------
      42             : 
      43             :    Both storage shapes are ring buffers addressed by generation cursors
      44             :    (monotonically increasing).  Cursors stay contiguous per ring even
      45             :    though the regions backing them are physically scattered: cursor
      46             :    `cur` lives in the (cur/region_capacity)-th region the ring owns, at
      47             :    slot (cur%region_capacity).
      48             : 
      49             :    Each ring keeps an ordered region_ids ring mapping region_idx to
      50             :    region_id.
      51             : 
      52             :      cursor:           0   1   2 | 3   4   5 | 6   7   8
      53             :      +---+---+---+---+---+---+---+---+---+
      54             :      slot in region: | 0 | 1 | 2 | 0 | 1 | 2 | 0 | 1 | 2 |  (cur % region_capacity)
      55             :      +---+---+---+---+---+---+---+---+---+
      56             :      logical ord:     \___ 0 __/ \___ 1 __/ \___ 2 __/      (cur / region_capacity)
      57             :      |           |           |
      58             :      region_ids[ord]:  phys 5      phys 2      phys 9       (physically scattered)
      59             : 
      60             :    ---- cursor watermarks ----------------------------------------------
      61             : 
      62             :    Three monotonic watermarks (tail_cur <= evict_cur <= head_cur) divide
      63             :    a ring's cursor line.  tail_cur is a physical watermark used by the
      64             :    eviction policy to free space in the ring. evict_cur is a logical
      65             :    watermark used by readers to skip entries marked for eviction.
      66             : 
      67             :      cursor --->  (older, smaller)                       (newer, larger)
      68             :      .................|===============|########################|- - - - - -
      69             :      reclaimed slots | evicted slots |      live records      |  unwritten
      70             :      (regions freed) | (below wmark) |  (>= evict_cur, alive) |  (future)
      71             :      ^               ^                        ^
      72             :      tail_cur        evict_cur                head_cur
      73             : 
      74             :    KV records do not store the key separately: the key is a
      75             :    caller-declared sub-field of the value (val + key_off, key_sz bytes),
      76             :    so a lookup reads and compares it in place via the ring's
      77             :    key_cmp/key_hash callbacks. */
      78             : 
      79             : #include "../../util/fd_util_base.h"
      80             : 
      81             : struct fd_gui_store_private;
      82             : typedef struct fd_gui_store_private fd_gui_store_t;
      83             : 
      84           0 : #define FD_GUI_STORE_SUCCESS   ( 0)
      85           0 : #define FD_GUI_STORE_ERR       (-1)
      86           0 : #define FD_GUI_STORE_MAP_FULL  ( 1)
      87           0 : #define FD_GUI_STORE_RING_FULL ( 2) /* this KV ring's index budget is full */
      88             : 
      89           0 : #define FD_GUI_STORE_KIND_KV (0)
      90           0 : #define FD_GUI_STORE_KIND_TS (1)
      91             : 
      92             : /* FD_GUI_STORE_REGION_SZ is the fixed size of every region in the pool.
      93             :    No ring's record stride may exceed it (fd_gui_store_new fails fast
      94             :    otherwise).  The store prepends no per-record header (both kinds embed
      95             :    their metadata in the value), so FD_GUI_STORE_MAX_REC_SZ is the whole
      96             :    region size.  Callers that know their record types at compile time
      97             :    should static-assert sizeof(their largest record) <=
      98             :    FD_GUI_STORE_MAX_REC_SZ. */
      99           0 : #define FD_GUI_STORE_REGION_SZ  (40UL<<20)
     100             : #define FD_GUI_STORE_MAX_REC_SZ (FD_GUI_STORE_REGION_SZ)
     101             : 
     102             : /* Each ring has a protected rolling base.  Unclaimed base regions are
     103             :    reserved lazily; other rings can neither allocate them nor pressure-
     104             :    evict records in the newest BASE_REGIONS regions.  A writer may recycle
     105             :    its own oldest eligible records when shared capacity is exhausted. */
     106           0 : #define FD_GUI_STORE_BASE_REGIONS (3UL)
     107             : 
     108             : /* FD_GUI_STORE_MAX_RINGS is the maximum number of named rings a store
     109             :    can host.  It bounds the per-ring metrics arrays below. */
     110           0 : #define FD_GUI_STORE_MAX_RINGS (64UL)
     111             : 
     112             : struct fd_gui_store_desc {
     113             :   char const * name;    /* the name of the ring.  Must be non-NULL and unique within the store */
     114             :   int          kind;    /* FD_GUI_STORE_KIND_KV or FD_GUI_STORE_KIND_TS */
     115             :   ulong        key_off; /* byte offset, within the value, of the record's key (pass 0 for TS) */
     116             :   ulong        key_sz;  /* key size in bytes */
     117             :   ulong      ( * key_hash )( void const * key ); /* hashes a key to a ulong (pass NULL for TS) */
     118             :   int        ( * key_cmp  )( void const * a, void const * b ); /* full-key total order; may treat sentinels as wildcards for get_any/iter (pass NULL for TS) */
     119             :   ulong        val_sz;      /* record size in bytes */
     120             :   ulong        val_align;   /* record alignment (power of two, >=1; pass 1 for none) */
     121             :   ulong        ts_off;      /* byte offset, within the value, of the record's `long` timestamp (TS only; pass 0 for KV) */
     122             :   ulong        granularity; /* TS window divisor: window = (ulong)(*(long*)(val+ts_off)) / granularity (pass 0 for KV) */
     123             :   ulong        max_records; /* enforced KV index budget (0 means 1; ignored for TS) */
     124             : };
     125             : 
     126             : typedef struct fd_gui_store_desc fd_gui_store_desc_t;
     127             : 
     128             : FD_PROTOTYPES_BEGIN
     129             : 
     130             : FD_FN_CONST ulong
     131             : fd_gui_store_align( void );
     132             : 
     133             : FD_FN_CONST ulong
     134             : fd_gui_store_footprint( ulong                       size_bytes,
     135             :                         ulong                       ring_cnt,
     136             :                         fd_gui_store_desc_t const * descs );
     137             : 
     138             : /* fd_gui_store_new formats the workspace region `mem` and opens the
     139             :    backing store whose data file is `path`, with a size ceiling of
     140             :    `size_bytes`.  The parent directory of `path` must already exist.
     141             :    `ring_cnt` named rings are created, one per entry of `descs`.  The
     142             :    `seed+i` initializes KV ring `i`'s index.  The store is wiped on open.
     143             : 
     144             :    Returns `mem` on success, or NULL on failure (logged at WARNING). */
     145             : 
     146             : void *
     147             : fd_gui_store_new( void *                      mem,
     148             :                   char const *                path,
     149             :                   ulong                       size_bytes,
     150             :                   ulong                       ring_cnt,
     151             :                   ulong                       seed,
     152             :                   fd_gui_store_desc_t const * descs );
     153             : 
     154             : fd_gui_store_t *
     155             : fd_gui_store_join( void * mem );
     156             : 
     157             : void *
     158             : fd_gui_store_leave( fd_gui_store_t * db );
     159             : 
     160             : void *
     161             : fd_gui_store_delete( void * mem );
     162             : 
     163             : /* fd_gui_store_cnt returns the number of named rings in the store. */
     164             : 
     165             : ulong
     166             : fd_gui_store_cnt( fd_gui_store_t const * db );
     167             : 
     168             : /* ---- space accounting ------------------------------------------------- */
     169             : 
     170             : ulong
     171             : fd_gui_store_used_bytes( fd_gui_store_t * db );
     172             : 
     173             : ulong
     174             : fd_gui_store_live_bytes( fd_gui_store_t * db );
     175             : 
     176             : ulong
     177             : fd_gui_store_size( fd_gui_store_t const * db );
     178             : 
     179             : /* fd_gui_store_file_sz returns the logical allocated length of the
     180             :    backing file. */
     181             : 
     182             : ulong
     183             : fd_gui_store_file_sz( fd_gui_store_t const * db );
     184             : 
     185             : /* fd_gui_store_free_region_cnt returns the number of regions currently
     186             :    unclaimed on disk. */
     187             : 
     188             : ulong
     189             : fd_gui_store_free_region_cnt( fd_gui_store_t const * db );
     190             : 
     191             : /* fd_gui_store_shared_free_region_cnt returns the unclaimed regions
     192             :    available excluding the base region allocation. */
     193             : ulong
     194             : fd_gui_store_shared_free_region_cnt( fd_gui_store_t const * db );
     195             : 
     196             : /* fd_gui_store_min_size returns the minimum file size ceiling for
     197             :    ring_cnt rings; zero for an invalid ring count.  new/footprint
     198             :    reject smaller stores before opening a file. */
     199             : FD_FN_CONST ulong
     200             : fd_gui_store_min_size( ulong ring_cnt );
     201             : 
     202             : /* fd_gui_store_reclaim performs pressure reclamation, removing at most
     203             :    budget records from one oldest region.  ring_idx==ULONG_MAX selects
     204             :    the oldest eligible excess region across all rings and protects their
     205             :    newest BASE_REGIONS regions.
     206             :    Otherwise only the named writer's own oldest region is considered,
     207             :    including its base.  eligible may be NULL (all records eligible).
     208             :    A rejected head is never skipped within a ring.  Returns records
     209             :    removed (not regions freed); zero means no eligible candidate.
     210             :    Explicit kv_evict/ts_evict below are logical deletion operations and
     211             :    intentionally do not implement the cross-ring pressure policy. */
     212             : ulong
     213             : fd_gui_store_reclaim( fd_gui_store_t * db,
     214             :                       ulong            ring_idx,
     215             :                       ulong            budget,
     216             :                       int (*eligible)( ulong, void const *, void * ),
     217             :                       void *           ctx );
     218             : 
     219             : int
     220             : fd_gui_store_fd( fd_gui_store_t const * db );
     221             : 
     222             : struct fd_gui_store_metrics {
     223             :   ulong kv_lookups   [ FD_GUI_STORE_MAX_RINGS ]; /* KV lookup calls (per call)         */
     224             :   ulong ts_appends   [ FD_GUI_STORE_MAX_RINGS ]; /* TS records appended (per rec)      */
     225             :   ulong ts_reads     [ FD_GUI_STORE_MAX_RINGS ]; /* TS scan calls (per call)           */
     226             :   ulong ts_read_records[ FD_GUI_STORE_MAX_RINGS ]; /* TS records returned (per rec)    */
     227             :   ulong evict_records[ FD_GUI_STORE_MAX_RINGS ]; /* records physically evicted         */
     228             :   ulong evicts       [ FD_GUI_STORE_MAX_RINGS ]; /* kv_evict + ts_evict calls          */
     229             :   ulong region_grows [ FD_GUI_STORE_MAX_RINGS ]; /* regions claimed from the free list */
     230             :   ulong region_reclaims[ FD_GUI_STORE_MAX_RINGS ]; /* regions returned to the free list */
     231             :   ulong map_full     [ FD_GUI_STORE_MAX_RINGS ]; /* low-level MAP_FULL returns before higher-layer recovery */
     232             : };
     233             : 
     234             : typedef struct fd_gui_store_metrics fd_gui_store_metrics_t;
     235             : 
     236             : fd_gui_store_metrics_t const *
     237             : fd_gui_store_metrics( fd_gui_store_t const * db );
     238             : 
     239             : /* fd_gui_store_ring_stats computes on-demand space accounting for ring
     240             :    `ring_idx`. */
     241             : void
     242             : fd_gui_store_ring_stats( fd_gui_store_t * db,
     243             :                          ulong            ring_idx,
     244             :                          ulong *          used_bytes,
     245             :                          ulong *          cap_bytes,
     246             :                          ulong *          free_bytes,
     247             :                          ulong *          used_slots,
     248             :                          ulong *          cap_slots,
     249             :                          ulong *          free_slots );
     250             : 
     251             : /* ---- KV ring -------------------------------------------------------- */
     252             : 
     253             : /* fd_gui_store_kv_get_or_create reserves (creating if absent) the
     254             :    record for `key` and, on success, hands back a mutable pointer to the
     255             :    record's value region.  Returns RING_FULL if this ring's max_records
     256             :    budget is exhausted, MAP_FULL if no region is available under the
     257             :    base reservation policy, or ERR on invalid input / file growth error.
     258             :    An existing record can be updated even when either resource is full. */
     259             : 
     260             : int
     261             : fd_gui_store_kv_get_or_create( fd_gui_store_t * db,
     262             :                                ulong            ring_idx,
     263             :                                void const *     key,
     264             :                                void **          val_out );
     265             : 
     266             : /* fd_gui_store_kv_get looks up the record for `key` in KV ring
     267             :    `ring_idx` and returns a mutable pointer to the record's value
     268             :    region, or NULL if the record is not found. */
     269             : 
     270             : void *
     271             : fd_gui_store_kv_get( fd_gui_store_t * db,
     272             :                      ulong            ring_idx,
     273             :                      void const *     key );
     274             : 
     275             : /* fd_gui_store_kv_get_any looks up the lowest record whose key matches
     276             :    `key` (per the ring's key_cmp, which may treat sentinel fields in `key`
     277             :    as wildcards) and returns a mutable pointer to its value region, or
     278             :    NULL if none.  Pass key==NULL to match the lowest record in the ring. */
     279             : 
     280             : void *
     281             : fd_gui_store_kv_get_any( fd_gui_store_t * db,
     282             :                          ulong            ring_idx,
     283             :                          void const *     key );
     284             : 
     285             : struct fd_gui_store_kv_iter {
     286             :   void const *  rec;        /* current record value (into the store map); NULL when done */
     287             :   void const *  key;        /* current record full key (into rec, at key_off); NULL when done */
     288             :   ulong         val_sz;     /* current record value size                                  */
     289             :   ulong         key_sz;     /* current record full key size                               */
     290             :   /* opaque state */
     291             :   void *        _db;        /* backend handle (fd_gui_store_t *) */
     292             :   ulong         _ring_idx;
     293             :   int           _valid;
     294             :   int           _have_prev; /* 1 once at least one record has been emitted */
     295             :   uchar         _key[ 16 ]; /* query key (may hold wildcards); FD_GUI_STORE_KV_KEY_MAX */
     296             :   uchar         _prev_key[ 16 ]; /* last emitted full key (selection cursor) */
     297             : };
     298             : 
     299             : typedef struct fd_gui_store_kv_iter fd_gui_store_kv_iter_t;
     300             : 
     301             : fd_gui_store_kv_iter_t *
     302             : fd_gui_store_kv_iter_begin( fd_gui_store_t *         db,
     303             :                             fd_gui_store_kv_iter_t * iter,
     304             :                             ulong                    ring_idx,
     305             :                             void const *             key );
     306             : 
     307             : FD_FN_PURE static inline int
     308           0 : fd_gui_store_kv_iter_done( fd_gui_store_kv_iter_t const * iter ) {
     309           0 :   return !iter->_valid;
     310           0 : }
     311             : 
     312             : int
     313             : fd_gui_store_kv_iter_next( fd_gui_store_kv_iter_t * iter );
     314             : 
     315             : 
     316             : struct fd_gui_store_kv_scan {
     317             :   void const *     rec;
     318             :   fd_gui_store_t * db;
     319             :   ulong            ring_idx;
     320             :   ulong            cur;
     321             :   ulong            end;
     322             :   ulong            append_capacity;
     323             : };
     324             : typedef struct fd_gui_store_kv_scan fd_gui_store_kv_scan_t;
     325             : 
     326             : /* fd_gui_store_kv_scan_begin snapshots a KV ring's admission-order end.
     327             :    scan_next skips evicted records and returns 1 with iter->rec set, or 0
     328             :    at the snapshot end.  No iterator cleanup is required. */
     329             : 
     330             : void
     331             : fd_gui_store_kv_scan_begin( fd_gui_store_t *         db,
     332             :                             fd_gui_store_kv_scan_t * iter,
     333             :                             ulong                    ring_idx );
     334             : 
     335             : int
     336             : fd_gui_store_kv_scan_next( fd_gui_store_kv_scan_t * iter );
     337             : 
     338             : /* fd_gui_store_kv_reclaim_prefix removes up to budget records in admission
     339             :    order, stopping at the first record rejected by eligible(rec,ctx).
     340             :    Returns the number removed. */
     341             : 
     342             : ulong
     343             : fd_gui_store_kv_reclaim_prefix( fd_gui_store_t * db,
     344             :                                 ulong            ring_idx,
     345             :                                 ulong            budget,
     346             :                                 int (*eligible)( void const *, void * ),
     347             :                                 void *           ctx );
     348             : 
     349             : /* ---- KV ring: eviction ---------------------------------------------- */
     350             : 
     351             : /* fd_gui_store_kv_evict reclaims drained records (up to budget records)
     352             :    from the `ring_idx` ring, stopping at the hi_key watermark. */
     353             : 
     354             : int
     355             : fd_gui_store_kv_evict( fd_gui_store_t * db,
     356             :                        ulong            ring_idx,
     357             :                        void const *     hi_key,
     358             :                        ulong *          budget,
     359             :                        int *            drained );
     360             : 
     361             : /* ---- TS ring: append-only stream ------------------------------------ */
     362             : 
     363             : /* fd_gui_store_ts_append appends one fixed-size record to ring
     364             :    `ring_idx`. Returns FD_GUI_STORE_SUCCESS, FD_GUI_STORE_MAP_FULL,
     365             :    or FD_GUI_STORE_ERR.  A record whose timestamp is lower than the
     366             :    newest live record's is rejected with FD_GUI_STORE_ERR. */
     367             : 
     368             : int
     369             : fd_gui_store_ts_append( fd_gui_store_t * db,
     370             :                         ulong            ring_idx,
     371             :                         void const *     val );
     372             : 
     373             : /* fd_gui_store_ts_emplace appends one record with timestamp ts to ring
     374             :    `ring_idx` in place.  On success, *val_out points to the stored
     375             :    record.  Same ordering requirement as fd_gui_store_ts_append. */
     376             : 
     377             : int
     378             : fd_gui_store_ts_emplace( fd_gui_store_t * db,
     379             :                          ulong            ring_idx,
     380             :                          long             ts,
     381             :                          void **          val_out );
     382             : 
     383             : /* fd_gui_store_ts_filter_fn is an optional per-record predicate
     384             :    evaluated during a scan: it returns non-zero to emit the record or
     385             :    zero to skip it. A NULL filter accepts every record. */
     386             : 
     387             : typedef int (*fd_gui_store_ts_filter_fn)( void const * rec, void * ctx );
     388             : 
     389             : struct fd_gui_store_ts_iter {
     390             :   void const *           rec;       /* current record (into the store map); NULL when done */
     391             :   ulong                  window;    /* current record's window                   */
     392             :   /* opaque state */
     393             :   void *                 _db;       /* backend handle (fd_gui_store_t *) */
     394             :   void *                 _cur;      /* next ring cursor to examine (value, not ptr) */
     395             :   int                    _valid;
     396             :   ulong                  _rec_sz;   /* ring_idx+1 (0 means uninitialised) */
     397             :   ulong                  _window_hi;
     398             :   ulong                  _cur_hi;   /* ring cursor to stop at (exclusive; head_cur at scan begin) */
     399             :   fd_gui_store_ts_filter_fn _filter;
     400             :   void *                 _filter_ctx;
     401             : };
     402             : 
     403             : typedef struct fd_gui_store_ts_iter fd_gui_store_ts_iter_t;
     404             : 
     405             : fd_gui_store_ts_iter_t *
     406             : fd_gui_store_ts_scan_begin( fd_gui_store_t *          db,
     407             :                             fd_gui_store_ts_iter_t *  iter,
     408             :                             ulong                     ring_idx,
     409             :                             ulong                     window_lo,
     410             :                             ulong                     window_hi,
     411             :                             fd_gui_store_ts_filter_fn filter,
     412             :                             void *                    filter_ctx );
     413             : 
     414             : FD_FN_PURE static inline int
     415           0 : fd_gui_store_ts_scan_done( fd_gui_store_ts_iter_t const * iter ) {
     416           0 :   return !iter->_valid;
     417           0 : }
     418             : 
     419             : int
     420             : fd_gui_store_ts_scan_next( fd_gui_store_ts_iter_t * iter );
     421             : 
     422             : void
     423             : fd_gui_store_ts_scan_end( fd_gui_store_ts_iter_t * iter );
     424             : 
     425             : /* fd_gui_store_ts_live_timestamp_bounds returns the timestamps of the
     426             :    first and last live records in physical append order in TS ring
     427             :    `ring_idx`.  These are the minimum and maximum live timestamps when
     428             :    appends have nondecreasing timestamps.
     429             : 
     430             :    Returns 1 if the ring holds
     431             :    any record, 0 if it is empty or not a TS ring. */
     432             : 
     433             : int
     434             : fd_gui_store_ts_live_timestamp_bounds( fd_gui_store_t * db,
     435             :                                        ulong            ring_idx,
     436             :                                        long *           out_first_timestamp,
     437             :                                        long *           out_last_timestamp );
     438             : 
     439             : /* ---- TS ring: eviction ---------------------------------------------- */
     440             : 
     441             : /* fd_gui_store_ts_evict reclaims drained records (up to budget records)
     442             :    from the `ring_idx` ring, stopping at the hi_key watermark. */
     443             : 
     444             : int
     445             : fd_gui_store_ts_evict( fd_gui_store_t * db,
     446             :                        ulong            ring_idx,
     447             :                        ulong            hi_window,
     448             :                        ulong *          budget,
     449             :                        int *            drained );
     450             : 
     451             : FD_PROTOTYPES_END
     452             : 
     453             : #endif /* HEADER_fd_src_disco_gui_fd_gui_store_h */

Generated by: LCOV version 1.14