Skip to main content

dynoxide/
bench_counters.rs

1//! Counters for work the engine repeats, compiled out unless asked for.
2//!
3//! Two of the open performance questions are about how many times something
4//! happens rather than how long it takes: whether an index entry is rebuilt when
5//! one was already to hand, and whether a batch resolves the same table's
6//! metadata once or twice per statement. A count answers those exactly and the
7//! same on every machine, where a timing does not.
8//!
9//! That matters more than it sounds. The wall-clock suite runs Criterion, which
10//! `AGENTS.md` calls advisory because a shared runner moves it by up to roughly
11//! 3x, and the deterministic instrument, iai-callgrind, needs Valgrind and so
12//! runs on Linux only. A count needs neither.
13//!
14//! It also answers a question that cannot be measured where it happens. The
15//! wasm backend crosses a bridge to a JS worker for every metadata read and
16//! caches nothing, so the cost there is the number of calls; and the number of
17//! calls is decided by the action code, not by the backend under it. Counting
18//! natively gives the wasm figure without a browser.
19//!
20//! Enabled by the `bench-counters` feature. Without it every counter is a unit
21//! struct and every `record` an empty inline function, so nothing reaches a
22//! release build.
23
24#[cfg(feature = "bench-counters")]
25mod enabled {
26    use std::sync::atomic::{AtomicUsize, Ordering};
27
28    /// Projected index entries built, by `actions::gsi::build_index_item`.
29    pub static INDEX_ENTRIES_BUILT: AtomicUsize = AtomicUsize::new(0);
30    /// Table metadata reads reaching the storage backend.
31    pub static METADATA_READS: AtomicUsize = AtomicUsize::new(0);
32    /// Key schemas parsed out of a table's metadata JSON.
33    pub static KEY_SCHEMA_PARSES: AtomicUsize = AtomicUsize::new(0);
34    /// Index definition sets deserialised out of a table's metadata JSON, by
35    /// the `parse_*_defs` helpers. One per family per call.
36    pub static INDEX_DEFS_PARSES: AtomicUsize = AtomicUsize::new(0);
37
38    pub fn record(counter: &AtomicUsize) {
39        counter.fetch_add(1, Ordering::Relaxed);
40    }
41
42    /// Zero every counter. Call before the operation under measurement.
43    pub fn reset() {
44        for counter in [
45            &INDEX_ENTRIES_BUILT,
46            &METADATA_READS,
47            &KEY_SCHEMA_PARSES,
48            &INDEX_DEFS_PARSES,
49        ] {
50            counter.store(0, Ordering::Relaxed);
51        }
52    }
53
54    /// What the counters read now.
55    pub fn snapshot() -> Counts {
56        Counts {
57            index_entries_built: INDEX_ENTRIES_BUILT.load(Ordering::Relaxed),
58            metadata_reads: METADATA_READS.load(Ordering::Relaxed),
59            key_schema_parses: KEY_SCHEMA_PARSES.load(Ordering::Relaxed),
60            index_defs_parses: INDEX_DEFS_PARSES.load(Ordering::Relaxed),
61        }
62    }
63
64    #[derive(Debug, Clone, Copy, PartialEq, Eq)]
65    pub struct Counts {
66        pub index_entries_built: usize,
67        pub metadata_reads: usize,
68        pub key_schema_parses: usize,
69        pub index_defs_parses: usize,
70    }
71}
72
73#[cfg(not(feature = "bench-counters"))]
74mod enabled {
75    /// Stands in for a counter when the feature is off, so call sites read the
76    /// same either way and cost nothing.
77    pub struct Counter;
78    pub static INDEX_ENTRIES_BUILT: Counter = Counter;
79    pub static METADATA_READS: Counter = Counter;
80    pub static KEY_SCHEMA_PARSES: Counter = Counter;
81    pub static INDEX_DEFS_PARSES: Counter = Counter;
82
83    #[inline(always)]
84    pub fn record(_counter: &Counter) {}
85}
86
87pub use enabled::*;