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::*;