nmbrs_metrics/cells.rs
1// Copyright 2024-2026 Jonathan Shook
2// SPDX-License-Identifier: Apache-2.0
3
4//! Dimensional cells materialised from data.
5//!
6//! A metric whose series are one-per-instance of some dimension — a compaction
7//! tier, a keyspace, a node — does NOT get there by attaching labels to an
8//! instrument. The label set of a [`Component`] *is* the dimensional cell:
9//!
10//! > The component's `effective_labels` define the dimensional cell; the same
11//! > family on a different component is a different cell and produces no
12//! > collision.
13//!
14//! So one series per instance is one CHILD COMPONENT per instance, with the
15//! family registered exactly once on each. The duplicate-family rejection in
16//! [`Component::register_instrument`] is untouched and keeps catching what it
17//! exists to catch: two different instruments claiming one name inside one
18//! dimensional context.
19//!
20//! This registry is the resolve-or-create for those children. It is
21//! deliberately generic — it knows about coordinates and components, not about
22//! metrics — and it hangs off the component tree, so a cell's lifetime is its
23//! parent's: cells created under a phase component die when that phase's
24//! subtree is dropped.
25//!
26//! Coordinates are whole. A two-dimension coordinate resolves to ONE child
27//! carrying both labels, never to nested children — nesting would impose an
28//! arbitrary order on co-equal dimensions, and `a` inside `b` is a different
29//! tree shape from `b` inside `a` for no reason the data supports.
30
31use std::collections::HashMap;
32use std::sync::{Arc, Mutex, RwLock};
33
34use crate::component::Component;
35use crate::labels::Labels;
36
37/// Resolve-or-create map from a coordinate to the child component that
38/// represents it. Interior-mutable so it is reachable through a read guard on
39/// the parent, mirroring [`Component::controls`].
40#[derive(Default)]
41pub struct CellMap {
42 /// Keyed by the coordinate's canonical rendering. `Labels::to_prometheus`
43 /// is order-stable, so two spellings of one coordinate map to one cell.
44 inner: Mutex<HashMap<String, Arc<RwLock<Component>>>>,
45}
46
47impl CellMap {
48 pub fn new() -> Self {
49 Self {
50 inner: Mutex::new(HashMap::new()),
51 }
52 }
53
54 /// The cell for `coord` under `parent`, creating and attaching it on first
55 /// sight. Idempotent: the same coordinate yields the same component for as
56 /// long as the parent lives, so a caller may resolve per cycle and get a
57 /// registry write only once.
58 ///
59 /// `coord` carries only the dimension labels this cell adds. Inherited
60 /// labels come from the parent via [`crate::component::attach`], which also
61 /// enforces that no ancestor already owns these names.
62 pub fn resolve(
63 &self,
64 parent: &Arc<RwLock<Component>>,
65 coord: &Labels,
66 ) -> Arc<RwLock<Component>> {
67 let key = coord.to_prometheus();
68 let mut map = self.inner.lock().unwrap_or_else(|e| e.into_inner());
69 if let Some(existing) = map.get(&key) {
70 return existing.clone();
71 }
72 let mut component = Component::new(coord.clone(), HashMap::new());
73 // Running from birth. `Component::new` starts in `Starting`, and the
74 // cadence walk captures only `Running` components — a cell left
75 // `Starting` would accept samples and emit none of them. A cell exists
76 // because data is already flowing into it, so there is no window in
77 // which `Starting` would be the honest state.
78 component.set_state(crate::component::ComponentState::Running);
79 let child = Arc::new(RwLock::new(component));
80 crate::component::attach(parent, &child);
81 map.insert(key, child.clone());
82 child
83 }
84
85 /// Number of distinct coordinates materialised so far. For diagnostics and
86 /// tests — there is deliberately no cap: how many series a dimension has is
87 /// a modelling decision, and silently dropping data would be worse than
88 /// having many.
89 pub fn len(&self) -> usize {
90 self.inner.lock().unwrap_or_else(|e| e.into_inner()).len()
91 }
92
93 pub fn is_empty(&self) -> bool {
94 self.len() == 0
95 }
96}
97
98/// Resolve the cell for `coord` under `parent`, creating it on first sight.
99///
100/// Free function rather than a method so the lock discipline lives in ONE
101/// place: resolving attaches a child, which takes `parent`'s write lock, so a
102/// caller holding a read guard across the call self-deadlocks on the same
103/// `RwLock`. Binding the `Arc` first is what releases it.
104///
105/// `parent` must be **the component the metric registers on**, not an ambient
106/// one. A cell REFINES an identity: the coordinate adds dimensions to the label
107/// set that component already contributes. Sourcing the parent from ambient
108/// context instead composes identity from wherever the code happens to be
109/// running, which silently drops the parts the registration site owns (`op=`,
110/// most obviously) — a different metric identity wearing the same family name.
111pub fn resolve_under(parent: &Arc<RwLock<Component>>, coord: &Labels) -> Arc<RwLock<Component>> {
112 let cells = parent.read().unwrap_or_else(|e| e.into_inner()).cells();
113 cells.resolve(parent, coord)
114}
115
116#[cfg(test)]
117mod tests {
118 use super::*;
119
120 fn parent() -> Arc<RwLock<Component>> {
121 Arc::new(RwLock::new(Component::new(
122 Labels::of("phase", "finalize"),
123 HashMap::new(),
124 )))
125 }
126
127 /// The same coordinate must resolve to the SAME component, or a per-cycle
128 /// resolve would attach a new child every cycle and re-register the family
129 /// on each — the registry write this design exists to avoid.
130 #[test]
131 fn a_coordinate_resolves_to_one_stable_cell() {
132 let p = parent();
133 let cells = CellMap::new();
134 let a = cells.resolve(&p, &Labels::of("tier", "24"));
135 let b = cells.resolve(&p, &Labels::of("tier", "24"));
136 assert!(Arc::ptr_eq(&a, &b), "one coordinate must be one cell");
137 assert_eq!(cells.len(), 1);
138 }
139
140 /// Distinct values are distinct cells — which is what makes one family
141 /// registrable on each without colliding.
142 #[test]
143 fn distinct_values_are_distinct_cells() {
144 let p = parent();
145 let cells = CellMap::new();
146 let a = cells.resolve(&p, &Labels::of("tier", "24"));
147 let b = cells.resolve(&p, &Labels::of("tier", "25"));
148 assert!(!Arc::ptr_eq(&a, &b));
149 assert_eq!(cells.len(), 2);
150
151 // The registry invariant this whole design protects: the same family
152 // registers cleanly on both, because they are different cells.
153 for c in [&a, &b] {
154 let g = c.write().unwrap();
155 let mut g = g;
156 g.register_instrument(
157 "compaction_bytes_out",
158 crate::component::InstrumentRef::Gauge(Arc::new(
159 crate::instruments::gauge::ValueGauge::new(Labels::default()),
160 )),
161 )
162 .expect("same family on a different cell must not collide");
163 }
164 }
165
166 /// A cell inherits its parent's labels and adds its own, so the emitted
167 /// series carries the full coordinate.
168 #[test]
169 fn a_cell_inherits_the_parent_dimensions() {
170 let p = parent();
171 let cells = CellMap::new();
172 let c = cells.resolve(&p, &Labels::of("tier", "24"));
173 let eff = c.read().unwrap().effective_labels().to_prometheus();
174 assert!(
175 eff.contains("phase=") && eff.contains("tier="),
176 "cell must carry inherited + own dimensions, got {eff}"
177 );
178 }
179
180 /// Two dimensions are ONE cell carrying both, not a nesting.
181 #[test]
182 fn a_multi_dimension_coordinate_is_a_single_child() {
183 let p = parent();
184 let cells = CellMap::new();
185 let coord = Labels::of("tier", "24").with("keyspace", "baselines");
186 let c = cells.resolve(&p, &coord);
187 assert_eq!(
188 c.read().unwrap().child_count(),
189 0,
190 "a coordinate must not nest one dimension inside another"
191 );
192 let eff = c.read().unwrap().effective_labels().to_prometheus();
193 assert!(
194 eff.contains("tier=") && eff.contains("keyspace="),
195 "both dimensions belong to one cell, got {eff}"
196 );
197 assert_eq!(p.read().unwrap().child_count(), 1);
198 }
199
200 /// Identity is the LABEL SET (with the family name promoted into it), so a
201 /// cell must add to the parent's dimensions, never stand in for them. If a
202 /// cell were parented somewhere else, the emitted identity would silently
203 /// lose whatever the registration site owned.
204 #[test]
205 fn a_cell_refines_the_parents_identity_rather_than_replacing_it() {
206 let phase = parent();
207 let op = Arc::new(RwLock::new(Component::new(
208 Labels::of("op", "read_history"),
209 HashMap::new(),
210 )));
211 crate::component::attach(&phase, &op);
212
213 let cell = resolve_under(&op, &Labels::of("tier", "24"));
214 let eff = cell.read().unwrap().effective_labels().to_prometheus();
215
216 for owned in ["phase=", "op=", "tier="] {
217 assert!(
218 eff.contains(owned),
219 "a cell must carry every dimension its ancestors own; {owned} \
220 missing from {eff}"
221 );
222 }
223 }
224
225 /// Two siblings sharing a label set AT THE SAME TIME is the case that
226 /// breaks identity: each can register the same family, and the two
227 /// instruments then wear one identity with the per-component duplicate
228 /// check unable to see it.
229 #[test]
230 #[should_panic(expected = "sibling-identity violation")]
231 fn concurrent_siblings_cannot_share_a_label_set() {
232 let p = parent();
233 let _first = {
234 let c = Arc::new(RwLock::new(Component::new(
235 Labels::of("tier", "24"),
236 HashMap::new(),
237 )));
238 crate::component::attach(&p, &c);
239 c // kept alive and never stopped
240 };
241 let second = Arc::new(RwLock::new(Component::new(
242 Labels::of("tier", "24"),
243 HashMap::new(),
244 )));
245 crate::component::attach(&p, &second);
246 }
247
248 /// Sequential reuse must stay legal — an iteration whose values repeat
249 /// (fib yields `n=1` twice) re-materialises the SAME identity, which is one
250 /// identity sampled again over time. An unconditional check panicked here.
251 #[test]
252 fn a_label_set_may_be_reused_after_the_previous_component_stops() {
253 use crate::component::ComponentState;
254 let p = parent();
255 let first = Arc::new(RwLock::new(Component::new(
256 Labels::of("tier", "24"),
257 HashMap::new(),
258 )));
259 crate::component::attach(&p, &first);
260 first.write().unwrap().set_state(ComponentState::Stopped);
261
262 let second = Arc::new(RwLock::new(Component::new(
263 Labels::of("tier", "24"),
264 HashMap::new(),
265 )));
266 crate::component::attach(&p, &second); // must not panic
267 assert_eq!(p.read().unwrap().child_count(), 2);
268 }
269
270 /// The index must not keep a dead component alive, and must not leak a
271 /// phantom claim when a component is dropped without being stopped.
272 #[test]
273 fn a_dropped_component_releases_its_claim() {
274 let p = parent();
275 {
276 let c = Arc::new(RwLock::new(Component::new(
277 Labels::of("tier", "24"),
278 HashMap::new(),
279 )));
280 crate::component::attach(&p, &c);
281 // `children` holds a strong ref, so drop that too: this models a
282 // component detached and released rather than stopped.
283 crate::component::detach(&p, &c);
284 }
285 let again = Arc::new(RwLock::new(Component::new(
286 Labels::of("tier", "24"),
287 HashMap::new(),
288 )));
289 crate::component::attach(&p, &again); // must not panic
290 }
291
292 /// Distinct values attach as distinct siblings — the thing cells exist to
293 /// create.
294 #[test]
295 fn distinct_siblings_still_attach() {
296 let p = parent();
297 for v in ["24", "25", "26"] {
298 let c = Arc::new(RwLock::new(Component::new(
299 Labels::of("tier", v),
300 HashMap::new(),
301 )));
302 crate::component::attach(&p, &c);
303 }
304 assert_eq!(p.read().unwrap().child_count(), 3);
305 }
306
307 /// So the guarantee has to come from HERE: the same coordinate resolves to
308 /// the existing cell instead of attaching a twin, which is what keeps one
309 /// coordinate mapped to one identity.
310 #[test]
311 fn the_resolver_is_what_prevents_a_duplicated_cell_identity() {
312 let p = parent();
313 let coord = Labels::of("tier", "24");
314 for _ in 0..5 {
315 resolve_under(&p, &coord);
316 }
317 assert_eq!(
318 p.read().unwrap().child_count(),
319 1,
320 "repeated resolution must not multiply cells for one coordinate"
321 );
322 }
323
324 /// Cells route through the same check, so a resolver cannot be the only
325 /// thing standing between the tree and a duplicated identity.
326 #[test]
327 fn repeated_cell_resolution_does_not_trip_the_sibling_check() {
328 let p = parent();
329 let coord = Labels::of("tier", "24");
330 let first = resolve_under(&p, &coord);
331 let again = resolve_under(&p, &coord);
332 assert!(
333 Arc::ptr_eq(&first, &again),
334 "memoisation must return the existing cell rather than attach a twin"
335 );
336 assert_eq!(p.read().unwrap().child_count(), 1);
337 }
338}