LCOV - code coverage report
Current view: top level - disco/gui - fd_gui_store.h (source / functions) Hit Total Coverage
Test: cov.lcov Lines: 13 14 92.9 %
Date: 2026-08-14 04:54:57 Functions: 3 16 18.8 %

          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. The scan order is
      20             :        insertion 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 ftruncate'd up to the highest claimed
      29             :    region as needed, never exceeding size_bytes.  Region ownership, the
      30             :    free list and the TS window index 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             :      (ftruncate'd 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     1805901 : #define FD_GUI_STORE_SUCCESS  ( 0)
      85           0 : #define FD_GUI_STORE_ERR      (-1)
      86      442374 : #define FD_GUI_STORE_MAP_FULL ( 1)
      87             : 
      88        1668 : #define FD_GUI_STORE_KIND_KV (0)
      89          24 : #define FD_GUI_STORE_KIND_TS (1)
      90             : 
      91             : /* FD_GUI_STORE_REGION_SZ is the fixed size of every region in the pool.
      92             :    No ring's record stride may exceed it (fd_gui_store_new fails fast
      93             :    otherwise).  The store prepends no per-record header (both kinds embed
      94             :    their metadata in the value), so FD_GUI_STORE_MAX_REC_SZ is the whole
      95             :    region size.  Callers that know their record types at compile time
      96             :    should static-assert sizeof(their largest record) <=
      97             :    FD_GUI_STORE_MAX_REC_SZ. */
      98         123 : #define FD_GUI_STORE_REGION_SZ  (36UL<<20)
      99             : #define FD_GUI_STORE_MAX_REC_SZ (FD_GUI_STORE_REGION_SZ)
     100             : 
     101             : /* FD_GUI_STORE_MAX_RINGS is the maximum number of named rings a store
     102             :    can host.  It bounds the per-ring metrics arrays below. */
     103        3315 : #define FD_GUI_STORE_MAX_RINGS (64UL)
     104             : 
     105             : /* FD_GUI_STORE_TS_IDX_DEPTH is the depth of the time-series index
     106             :    array, measured in one second window buckets. */
     107   855366642 : #define FD_GUI_STORE_TS_IDX_DEPTH (30UL*24UL*60UL*60UL) /* 30 days of 1s windows */
     108             : 
     109             : struct fd_gui_store_desc {
     110             :   char const * name;    /* the name of the ring.  Must be non-NULL and unique within the store */
     111             :   int          kind;    /* FD_GUI_STORE_KIND_KV or FD_GUI_STORE_KIND_TS */
     112             :   ulong        key_off; /* byte offset, within the value, of the record's key (pass 0 for TS) */
     113             :   ulong        key_sz;  /* key size in bytes */
     114             :   ulong      ( * key_hash )( void const * key ); /* hashes a key to a ulong (pass NULL for TS) */
     115             :   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) */
     116             :   ulong        val_sz;      /* record size in bytes */
     117             :   ulong        val_align;   /* record alignment (power of two, >=1; pass 1 for none) */
     118             :   ulong        ts_off;      /* byte offset, within the value, of the record's `long` timestamp (TS only; pass 0 for KV) */
     119             :   ulong        granularity; /* TS window divisor: window = (ulong)(*(long*)(val+ts_off)) / granularity (pass 0 for KV) */
     120             :   ulong        max_records; /* The maximum number of distinct live records this ring can ever hold (pass 0 for TS) */
     121             : };
     122             : 
     123             : typedef struct fd_gui_store_desc fd_gui_store_desc_t;
     124             : 
     125             : FD_PROTOTYPES_BEGIN
     126             : 
     127             : FD_FN_CONST ulong
     128             : fd_gui_store_align( void );
     129             : 
     130             : FD_FN_CONST ulong
     131             : fd_gui_store_footprint( ulong                       size_bytes,
     132             :                         ulong                       ring_cnt,
     133             :                         fd_gui_store_desc_t const * descs );
     134             : 
     135             : /* fd_gui_store_new formats the workspace region `mem` and opens the
     136             :    backing store whose data file is `path`, with a size ceiling of
     137             :    `size_bytes`.  The parent directory of `path` must already exist.
     138             :    `ring_cnt` named rings are created, one per entry of `descs`.  The
     139             :    store is wiped on open.
     140             : 
     141             :    Returns `mem` on success, or NULL on failure (logged at WARNING). */
     142             : 
     143             : void *
     144             : fd_gui_store_new( void *                      mem,
     145             :                   char const *                path,
     146             :                   ulong                       size_bytes,
     147             :                   ulong                       ring_cnt,
     148             :                   fd_gui_store_desc_t const * descs );
     149             : 
     150             : fd_gui_store_t *
     151             : fd_gui_store_join( void * mem );
     152             : 
     153             : void *
     154             : fd_gui_store_leave( fd_gui_store_t * db );
     155             : 
     156             : void *
     157             : fd_gui_store_delete( void * mem );
     158             : 
     159             : /* fd_gui_store_cnt returns the number of named rings in the store. */
     160             : 
     161             : ulong
     162             : fd_gui_store_cnt( fd_gui_store_t const * db );
     163             : 
     164             : /* ---- space accounting ------------------------------------------------- */
     165             : 
     166             : ulong
     167             : fd_gui_store_used_bytes( fd_gui_store_t * db );
     168             : 
     169             : ulong
     170             : fd_gui_store_live_bytes( fd_gui_store_t * db );
     171             : 
     172             : ulong
     173             : fd_gui_store_size( fd_gui_store_t const * db );
     174             : 
     175             : /* fd_gui_store_free_region_cnt returns the number of regions currently
     176             :    unclaimed on disk. */
     177             : 
     178             : ulong
     179             : fd_gui_store_free_region_cnt( fd_gui_store_t const * db );
     180             : 
     181             : int
     182             : fd_gui_store_fd( fd_gui_store_t const * db );
     183             : 
     184             : /* fd_gui_store_min_overhead_bytes returns the fixed byte overhead a
     185             :    store adds on top of usable record space: the superblock page plus
     186             :    one region.  A store whose size_bytes ceiling is at least this large
     187             :    plus the caller's desired usable capacity is guaranteed to lay out
     188             :    with at least one region. */
     189             : 
     190             : FD_FN_CONST ulong
     191             : fd_gui_store_min_overhead_bytes( void );
     192             : 
     193             : struct fd_gui_store_metrics {
     194             :   ulong kv_lookups   [ FD_GUI_STORE_MAX_RINGS ]; /* KV lookup calls (per call)         */
     195             :   ulong ts_appends   [ FD_GUI_STORE_MAX_RINGS ]; /* TS records appended (per rec)      */
     196             :   ulong ts_reads     [ FD_GUI_STORE_MAX_RINGS ]; /* TS scan calls (per call)           */
     197             :   ulong ts_read_records[ FD_GUI_STORE_MAX_RINGS ]; /* TS records returned (per rec)    */
     198             :   ulong evict_records[ FD_GUI_STORE_MAX_RINGS ]; /* records physically evicted         */
     199             :   ulong evicts       [ FD_GUI_STORE_MAX_RINGS ]; /* kv_evict + ts_evict calls          */
     200             :   ulong region_grows [ FD_GUI_STORE_MAX_RINGS ]; /* regions claimed from the free list */
     201             :   ulong region_reclaims[ FD_GUI_STORE_MAX_RINGS ]; /* regions returned to the free list */
     202             :   ulong map_full     [ FD_GUI_STORE_MAX_RINGS ]; /* low-level MAP_FULL returns before higher-layer recovery */
     203             : };
     204             : 
     205             : typedef struct fd_gui_store_metrics fd_gui_store_metrics_t;
     206             : 
     207             : fd_gui_store_metrics_t const *
     208             : fd_gui_store_metrics( fd_gui_store_t const * db );
     209             : 
     210             : /* fd_gui_store_ring_stats computes on-demand space accounting for ring
     211             :    `ring_idx`. */
     212             : void
     213             : fd_gui_store_ring_stats( fd_gui_store_t * db,
     214             :                          ulong            ring_idx,
     215             :                          ulong *          used_bytes,
     216             :                          ulong *          cap_bytes,
     217             :                          ulong *          free_bytes,
     218             :                          ulong *          used_slots,
     219             :                          ulong *          cap_slots,
     220             :                          ulong *          free_slots );
     221             : 
     222             : /* ---- KV ring -------------------------------------------------------- */
     223             : 
     224             : /* fd_gui_store_kv_get_or_create reserves (creating if absent) the
     225             :    record for `key` and, on success, hands back a mutable pointer to the
     226             :    record's value region. */
     227             : 
     228             : int
     229             : fd_gui_store_kv_get_or_create( fd_gui_store_t * db,
     230             :                                ulong            ring_idx,
     231             :                                void const *     key,
     232             :                                void **          val_out );
     233             : 
     234             : /* fd_gui_store_kv_get looks up the record for `key` in KV ring
     235             :    `ring_idx` and returns a mutable pointer to the record's value
     236             :    region, or NULL if the record is not found. */
     237             : 
     238             : void *
     239             : fd_gui_store_kv_get( fd_gui_store_t * db,
     240             :                      ulong            ring_idx,
     241             :                      void const *     key );
     242             : 
     243             : /* fd_gui_store_kv_get_any looks up the lowest record whose key matches
     244             :    `key` (per the ring's key_cmp, which may treat sentinel fields in `key`
     245             :    as wildcards) and returns a mutable pointer to its value region, or
     246             :    NULL if none.  Pass key==NULL to match the lowest record in the ring. */
     247             : 
     248             : void *
     249             : fd_gui_store_kv_get_any( fd_gui_store_t * db,
     250             :                          ulong            ring_idx,
     251             :                          void const *     key );
     252             : 
     253             : struct fd_gui_store_kv_iter {
     254             :   void const *  rec;        /* current record value (into the store map); NULL when done */
     255             :   void const *  key;        /* current record full key (into rec, at key_off); NULL when done */
     256             :   ulong         val_sz;     /* current record value size                                  */
     257             :   ulong         key_sz;     /* current record full key size                               */
     258             :   /* opaque state */
     259             :   void *        _db;        /* backend handle (fd_gui_store_t *) */
     260             :   ulong         _ring_idx;
     261             :   int           _valid;
     262             :   int           _have_prev; /* 1 once at least one record has been emitted */
     263             :   uchar         _key[ 16 ]; /* query key (may hold wildcards); FD_GUI_STORE_KV_KEY_MAX */
     264             :   uchar         _prev_key[ 16 ]; /* last emitted full key (selection cursor) */
     265             : };
     266             : 
     267             : typedef struct fd_gui_store_kv_iter fd_gui_store_kv_iter_t;
     268             : 
     269             : fd_gui_store_kv_iter_t *
     270             : fd_gui_store_kv_iter_begin( fd_gui_store_t *         db,
     271             :                             fd_gui_store_kv_iter_t * iter,
     272             :                             ulong                    ring_idx,
     273             :                             void const *             key );
     274             : 
     275             : FD_FN_PURE static inline int
     276          18 : fd_gui_store_kv_iter_done( fd_gui_store_kv_iter_t const * iter ) {
     277          18 :   return !iter->_valid;
     278          18 : }
     279             : 
     280             : int
     281             : fd_gui_store_kv_iter_next( fd_gui_store_kv_iter_t * iter );
     282             : 
     283             : 
     284             : /* ---- KV ring: eviction ---------------------------------------------- */
     285             : 
     286             : /* fd_gui_store_kv_evict reclaims drained records (up to budget records)
     287             :    from the `ring_idx` ring, stopping at the hi_key watermark. */
     288             : 
     289             : int
     290             : fd_gui_store_kv_evict( fd_gui_store_t * db,
     291             :                        ulong            ring_idx,
     292             :                        void const *     hi_key,
     293             :                        ulong *          budget,
     294             :                        int *            drained );
     295             : 
     296             : /* ---- TS ring: append-only stream ------------------------------------ */
     297             : 
     298             : /* fd_gui_store_ts_append appends one fixed-size record to ring
     299             :    `ring_idx`. Returns FD_GUI_STORE_SUCCESS, FD_GUI_STORE_MAP_FULL,
     300             :    or FD_GUI_STORE_ERR. */
     301             : 
     302             : int
     303             : fd_gui_store_ts_append( fd_gui_store_t * db,
     304             :                         ulong            ring_idx,
     305             :                         void const *     val );
     306             : 
     307             : /* fd_gui_store_ts_filter_fn is an optional per-record predicate
     308             :    evaluated during a scan: it returns non-zero to emit the record or
     309             :    zero to skip it. A NULL filter accepts every record. */
     310             : 
     311             : typedef int (*fd_gui_store_ts_filter_fn)( void const * rec, void * ctx );
     312             : 
     313             : struct fd_gui_store_ts_iter {
     314             :   void const *           rec;       /* current record (into the store map); NULL when done */
     315             :   ulong                  window;    /* current record's window                   */
     316             :   /* opaque state */
     317             :   void *                 _db;       /* backend handle (fd_gui_store_t *) */
     318             :   void *                 _cur;      /* next ring cursor to examine (value, not ptr) */
     319             :   int                    _valid;
     320             :   ulong                  _rec_sz;   /* ring_idx+1 (0 means uninitialised) */
     321             :   ulong                  _window_lo;
     322             :   ulong                  _window_hi;
     323             :   ulong                  _cur_hi;   /* ring cursor to stop at (exclusive; from the window index) */
     324             :   fd_gui_store_ts_filter_fn _filter;
     325             :   void *                 _filter_ctx;
     326             : };
     327             : 
     328             : typedef struct fd_gui_store_ts_iter fd_gui_store_ts_iter_t;
     329             : 
     330             : fd_gui_store_ts_iter_t *
     331             : fd_gui_store_ts_scan_begin( fd_gui_store_t *          db,
     332             :                             fd_gui_store_ts_iter_t *  iter,
     333             :                             ulong                     ring_idx,
     334             :                             ulong                     window_lo,
     335             :                             ulong                     window_hi,
     336             :                             fd_gui_store_ts_filter_fn filter,
     337             :                             void *                    filter_ctx );
     338             : 
     339             : FD_FN_PURE static inline int
     340        3819 : fd_gui_store_ts_scan_done( fd_gui_store_ts_iter_t const * iter ) {
     341        3819 :   return !iter->_valid;
     342        3819 : }
     343             : 
     344             : int
     345             : fd_gui_store_ts_scan_next( fd_gui_store_ts_iter_t * iter );
     346             : 
     347             : void
     348             : fd_gui_store_ts_scan_end( fd_gui_store_ts_iter_t * iter );
     349             : 
     350             : /* fd_gui_store_ts_oldest_window returns, in *out_window, the time
     351             :    window for the oldest record in TS ring `ring_idx`.  Returns 1 if the
     352             :    ring holds any record, 0 if it is empty or not a TS ring. */
     353             : 
     354             : int
     355             : fd_gui_store_ts_oldest_window( fd_gui_store_t * db,
     356             :                                ulong            ring_idx,
     357             :                                ulong *          out_window );
     358             : 
     359             : /* ---- TS ring: eviction ---------------------------------------------- */
     360             : 
     361             : /* fd_gui_store_ts_evict reclaims drained records (up to budget records)
     362             :    from the `ring_idx` ring, stopping at the hi_key watermark. */
     363             : 
     364             : int
     365             : fd_gui_store_ts_evict( fd_gui_store_t * db,
     366             :                        ulong            ring_idx,
     367             :                        ulong            hi_window,
     368             :                        ulong *          budget,
     369             :                        int *            drained );
     370             : 
     371             : FD_PROTOTYPES_END
     372             : 
     373             : #endif /* HEADER_fd_src_disco_gui_fd_gui_store_h */

Generated by: LCOV version 1.14