Skip to main content

nmbrs_runtime/
control_catalog.rs

1// Copyright 2024-2026 Jonathan Shook
2// SPDX-License-Identifier: Apache-2.0
3
4//! SRD-23 — the **dynamic-control capability catalog**.
5//!
6//! Controls are declared *imperatively* into the live component tree at run
7//! time (`Component::controls().declare(...)`), which makes them enumerable
8//! through that tree once it exists — `dryrun=controls`, SRD-23
9//! §"Enumeration". But that only surfaces the controls a given run *happened*
10//! to declare: anything conditional (`rate`, only when a phase sets `rate:`)
11//! or adapter-owned (`cql_trace_rate`, only when the CQL adapter is active) is
12//! invisible until its conditions are met. That asymmetry is why
13//! `concurrency` (always declared) feels discoverable and `rate` does not.
14//!
15//! This module adds the complementary **capability tier**: a static,
16//! pre-instantiation description of every control the binary *can* declare,
17//! readable by `nmbrs describe controls` without running anything. Each
18//! [`ControlDesc`] is the **single source of truth** — the imperative
19//! declaration *derives* its name / value-type / default / range / gauge from
20//! the descriptor (via [`ControlDesc::build_u32`] / [`ControlDesc::build_f64`]
21//! / [`ControlDesc::build_rate`]), so the discovery surface and the live
22//! control can never drift.
23//!
24//! Owners: core controls (`concurrency`, `rate`) live here; adapter controls
25//! are contributed by each adapter's
26//! [`supported_controls`](crate::adapter::AdapterRegistration::supported_controls)
27//! and unioned in by [`all_controls`].
28
29use nmbrs_metrics::controls::{BranchScope, Control, ControlBuilder};
30
31/// The value shape of a control, as projected onto the f64-writable surface
32/// every writer (TUI `e`, `POST /controls`, polydat `control_set`,
33/// `optimize.servo`) shares.
34#[derive(Debug, Clone, Copy, PartialEq, Eq)]
35pub enum ControlValueType {
36    /// Unsigned integer count (e.g. `concurrency` — fibers). Written as an
37    /// f64, range-checked and truncated to `u32` on apply.
38    Count,
39    /// Throughput in operations per second (e.g. `rate`). Backed by a
40    /// `RateSpec`; the f64 surface is ops/sec.
41    Rate,
42    /// A probability / fraction in `[min, max]` (e.g. `cql_trace_rate`).
43    Fraction,
44    /// An event-frequency ceiling in events/sec (e.g.
45    /// `retry_exemplar_max_hz`). `0` = uncapped.
46    Frequency,
47}
48
49impl ControlValueType {
50    /// Short human label for `describe` tables.
51    pub fn label(self) -> &'static str {
52        match self {
53            ControlValueType::Count => "count(u32)",
54            ControlValueType::Rate => "rate(ops/s)",
55            ControlValueType::Fraction => "fraction",
56            ControlValueType::Frequency => "freq(hz)",
57        }
58    }
59}
60
61/// The condition under which a control is actually declared on the component
62/// tree — the *why isn't this knob here?* a user needs when a control is
63/// absent from a given run.
64#[derive(Debug, Clone, Copy, PartialEq, Eq)]
65pub enum DeclaredWhen {
66    /// Declared on every activity, unconditionally.
67    Always,
68    /// Declared when a phase/op sets the named field (e.g. `rate` ⇒ the
69    /// `rate:` phase field both seeds and declares the `rate` control).
70    PhaseField(&'static str),
71    /// Declared when the named adapter is active in the run.
72    AdapterActive(&'static str),
73    /// Declared only when the named *driver* implementation backs its adapter
74    /// (e.g. `cassandra-cpp` for the `cql` adapter — the pure-Rust `scylla`
75    /// driver does not declare it). Adapter-owned but driver-specific.
76    Driver(&'static str),
77}
78
79impl std::fmt::Display for DeclaredWhen {
80    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
81        match self {
82            DeclaredWhen::Always => write!(f, "always"),
83            DeclaredWhen::PhaseField(field) => write!(f, "when a phase sets `{field}:`"),
84            DeclaredWhen::AdapterActive(name) => write!(f, "when the `{name}` adapter is active"),
85            DeclaredWhen::Driver(driver) => write!(f, "when the `{driver}` driver is active"),
86        }
87    }
88}
89
90/// Static, declarative description of one dynamic control's *capability*. See
91/// the module docs: this is the single source the imperative declaration
92/// derives from and that `describe controls` reads, so they cannot drift.
93#[derive(Debug, Clone, Copy)]
94pub struct ControlDesc {
95    /// Canonical control name — the key written via `control_set`, the TUI
96    /// `e` prompt, `GET`/`POST /controls`, and `optimize.servo`.
97    pub name: &'static str,
98    /// Value shape (drives the derived gauge + f64 conversion).
99    pub value_type: ControlValueType,
100    /// Documented default, projected to f64 (the baseline a run starts from
101    /// when the field is unset; informational for discovery).
102    pub default: f64,
103    /// Inclusive lower bound the derived `from_f64` validator enforces. For
104    /// [`ControlValueType::Rate`] this is treated as an *exclusive* floor (a
105    /// rate of `0` disables the limiter rather than servoing it).
106    pub min: f64,
107    /// Inclusive upper bound the derived `from_f64` validator enforces.
108    pub max: f64,
109    /// Unit label for `describe` (e.g. `fibers`, `ops/sec`, `probability`).
110    pub unit: &'static str,
111    /// One-line description of what the knob does.
112    pub doc: &'static str,
113    /// When the control is actually declared on the component tree.
114    pub declared_when: DeclaredWhen,
115}
116
117impl ControlDesc {
118    /// Derive a `u32` count control from this descriptor — name, `[min, max]`
119    /// range validator, and gauge projection all come from the descriptor —
120    /// seeded at `initial`. The caller attaches the instance-specific applier
121    /// (e.g. the fiber-pool resize) separately.
122    pub fn build_u32(&self, initial: u32) -> Control<u32> {
123        debug_assert_eq!(
124            self.value_type,
125            ControlValueType::Count,
126            "{} is not a Count control",
127            self.name
128        );
129        let (name, min, max) = (self.name, self.min, self.max);
130        ControlBuilder::new(name, initial)
131            .reify_as_gauge(|v: &u32| Some(*v as f64))
132            .from_f64(move |v| {
133                if !v.is_finite() || !(min..=max).contains(&v) {
134                    Err(format!("{name} out of range [{min}, {max}]: got {v}"))
135                } else {
136                    Ok(v as u32)
137                }
138            })
139            .branch_scope(BranchScope::Local)
140            .build()
141    }
142
143    /// Derive an `f64` fraction/frequency control from this descriptor.
144    pub fn build_f64(&self, initial: f64) -> Control<f64> {
145        debug_assert!(
146            matches!(
147                self.value_type,
148                ControlValueType::Fraction | ControlValueType::Frequency
149            ),
150            "{} is not an f64-shaped control",
151            self.name
152        );
153        let (name, min, max) = (self.name, self.min, self.max);
154        ControlBuilder::new(name, initial)
155            .reify_as_gauge(|v: &f64| Some(*v))
156            .from_f64(move |v| {
157                if !v.is_finite() || !(min..=max).contains(&v) {
158                    Err(format!("{name} must be in [{min}, {max}]: got {v}"))
159                } else {
160                    Ok(v)
161                }
162            })
163            .branch_scope(BranchScope::Local)
164            .build()
165    }
166
167    /// Derive a `RateSpec` throughput control from this descriptor. `min` is an
168    /// *exclusive* floor: a non-positive value is rejected (a zero rate
169    /// disables the limiter, which is a phase-config decision, not a servo).
170    pub fn build_rate(&self, initial_ops_per_sec: f64) -> Control<nmbrs_rate::RateSpec> {
171        debug_assert_eq!(
172            self.value_type,
173            ControlValueType::Rate,
174            "{} is not a Rate control",
175            self.name
176        );
177        let name = self.name;
178        ControlBuilder::new(name, nmbrs_rate::RateSpec::new(initial_ops_per_sec))
179            .reify_as_gauge(|spec: &nmbrs_rate::RateSpec| Some(spec.ops_per_sec))
180            .from_f64(move |v| {
181                if v <= 0.0 {
182                    Err(format!("{name} must be > 0, got {v}"))
183                } else {
184                    Ok(nmbrs_rate::RateSpec::new(v))
185                }
186            })
187            .branch_scope(BranchScope::Local)
188            .build()
189    }
190}
191
192/// `concurrency` — the fiber count the executor maintains for a phase. Always
193/// declared, so it is the one control present in every run.
194pub const CONCURRENCY: ControlDesc = ControlDesc {
195    name: "concurrency",
196    value_type: ControlValueType::Count,
197    default: 1.0,
198    min: 1.0,
199    max: 100_000.0,
200    unit: "fibers",
201    doc: "Concurrent fibers the executor maintains; re-balanced live on every change.",
202    declared_when: DeclaredWhen::Always,
203};
204
205/// `rate` — the cycle-rate limiter (ops/sec). Declared only when a phase sets
206/// `rate:`; that field value seeds the control (and is the warmup an
207/// `optimize.servo: rate` retargets from).
208pub const RATE: ControlDesc = ControlDesc {
209    name: "rate",
210    value_type: ControlValueType::Rate,
211    default: 0.0,
212    min: 0.0,
213    max: f64::INFINITY,
214    unit: "ops/sec",
215    doc: "Target cycle rate (ops/sec) enforced by the rate limiter.",
216    declared_when: DeclaredWhen::PhaseField("rate"),
217};
218
219/// `retry_exemplar_rate` — retry-error counter-exemplar sampling
220/// (SRD-82 Part 3b / `exec_events`). Push-on-set: the applier is one
221/// atomic store into the activity's shared `ExemplarConfig`; samplers
222/// read it only on their retry path. Ops that pin `retry_exemplar_*`
223/// params hold private cells this control does not move.
224pub const RETRY_EXEMPLAR_RATE: ControlDesc = ControlDesc {
225    name: "retry_exemplar_rate",
226    value_type: ControlValueType::Fraction,
227    default: 0.0,
228    min: 0.0,
229    max: 1.0,
230    unit: "probability",
231    doc: "Fraction of retried op errors sampled to the session log as counter-exemplars (0 = off, 1 = all); moves every op without a pinned retry_exemplar_* param.",
232    declared_when: DeclaredWhen::Always,
233};
234
235/// `retry_exemplar_max_hz` — emission-frequency ceiling for retry
236/// exemplars; admissions over it are squelched and counted (reported
237/// on the next emitted line). `0` = uncapped.
238pub const RETRY_EXEMPLAR_MAX_HZ: ControlDesc = ControlDesc {
239    name: "retry_exemplar_max_hz",
240    value_type: ControlValueType::Frequency,
241    default: 5.0,
242    min: 0.0,
243    max: 1_000_000.0,
244    unit: "events/sec",
245    doc: "Ceiling on retry-exemplar emissions per second; excess is squelched and counted, never silent. 0 = uncapped.",
246    declared_when: DeclaredWhen::Always,
247};
248
249/// The core controls every build supports (subject to each one's
250/// [`DeclaredWhen`] condition). Adapter controls are added by [`all_controls`].
251pub fn core_controls() -> &'static [ControlDesc] {
252    const CORE: &[ControlDesc] = &[
253        CONCURRENCY,
254        RATE,
255        RETRY_EXEMPLAR_RATE,
256        RETRY_EXEMPLAR_MAX_HZ,
257    ];
258    CORE
259}
260
261/// Where a control's descriptor is contributed from — shown by
262/// `describe controls` so a user knows which subsystem owns the knob.
263#[derive(Debug, Clone, PartialEq, Eq)]
264pub enum ControlOwner {
265    /// Core activity/runtime control (always compiled in).
266    Core,
267    /// Contributed by an adapter registration (its primary driver name).
268    Adapter(String),
269}
270
271impl std::fmt::Display for ControlOwner {
272    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
273        match self {
274            ControlOwner::Core => write!(f, "core"),
275            ControlOwner::Adapter(name) => write!(f, "adapter:{name}"),
276        }
277    }
278}
279
280/// One row of the capability catalog: a descriptor plus its owner.
281pub struct ControlEntry {
282    /// The static capability description.
283    pub desc: &'static ControlDesc,
284    /// Which subsystem contributes it.
285    pub owner: ControlOwner,
286}
287
288/// Enumerate **every** control the binary can declare — core plus each
289/// registered adapter's [`supported_controls`](crate::adapter::AdapterRegistration::supported_controls)
290/// — for `nmbrs describe controls`. This is the static *capability* view
291/// (SRD-23 §"Enumeration"), distinct from the *instance* view `dryrun=controls`
292/// walks over the live component tree.
293pub fn all_controls() -> Vec<ControlEntry> {
294    let mut out: Vec<ControlEntry> = core_controls()
295        .iter()
296        .map(|d| ControlEntry {
297            desc: d,
298            owner: ControlOwner::Core,
299        })
300        .collect();
301    for reg in inventory::iter::<crate::adapter::AdapterRegistration> {
302        let owner_name = (reg.names)().first().copied().unwrap_or("?");
303        for d in (reg.supported_controls)() {
304            out.push(ControlEntry {
305                desc: d,
306                owner: ControlOwner::Adapter(owner_name.to_string()),
307            });
308        }
309    }
310    out
311}
312
313#[cfg(test)]
314mod tests {
315    use super::*;
316    use nmbrs_metrics::controls::ControlOrigin;
317
318    #[test]
319    fn core_controls_cover_concurrency_and_rate_with_conditions() {
320        let core = core_controls();
321        let conc = core
322            .iter()
323            .find(|d| d.name == "concurrency")
324            .expect("concurrency present");
325        let rate = core
326            .iter()
327            .find(|d| d.name == "rate")
328            .expect("rate present");
329        // The asymmetry the catalog fixes: concurrency is always present, rate
330        // only when a phase sets `rate:` — both now *discoverable* statically.
331        assert_eq!(conc.declared_when, DeclaredWhen::Always);
332        assert_eq!(rate.declared_when, DeclaredWhen::PhaseField("rate"));
333        assert_eq!(conc.value_type, ControlValueType::Count);
334        assert_eq!(rate.value_type, ControlValueType::Rate);
335    }
336
337    #[tokio::test]
338    async fn build_u32_derives_name_and_range_from_descriptor() {
339        use nmbrs_metrics::controls::ErasedControl;
340        // The live control is *derived* from the descriptor — its name and the
341        // range its `from_f64` enforces come from `CONCURRENCY`, not a separate
342        // literal, so the discovery surface and the knob can't drift.
343        let ctl = CONCURRENCY.build_u32(8);
344        assert_eq!(ctl.name(), "concurrency");
345        assert_eq!(ctl.value(), 8);
346        // In-range write applies; out-of-range (below min=1) is rejected by the
347        // derived validator.
348        assert!(ctl.set_f64(16.0, ControlOrigin::Launch).await.is_ok());
349        assert_eq!(ctl.value(), 16);
350        assert!(ctl.set_f64(0.0, ControlOrigin::Launch).await.is_err());
351    }
352
353    #[tokio::test]
354    async fn build_rate_rejects_non_positive() {
355        use nmbrs_metrics::controls::ErasedControl;
356        let ctl = RATE.build_rate(1000.0);
357        assert_eq!(ctl.name(), "rate");
358        assert!(ctl.set_f64(500.0, ControlOrigin::Launch).await.is_ok());
359        assert!(ctl.set_f64(0.0, ControlOrigin::Launch).await.is_err());
360    }
361
362    #[test]
363    fn all_controls_includes_core_and_is_owner_tagged() {
364        let all = all_controls();
365        let conc = all
366            .iter()
367            .find(|e| e.desc.name == "concurrency")
368            .expect("concurrency enumerated");
369        assert_eq!(conc.owner, ControlOwner::Core);
370    }
371}