Skip to main content

spatialrust_platform/
budget.rs

1//! Named performance budgets and measured samples.
2
3use crate::{PlatformError, PlatformResult};
4
5/// Dimension of a performance budget.
6#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
7pub enum BudgetKind {
8    /// Wall-clock latency upper bound in microseconds.
9    LatencyMicros,
10    /// Wall-clock latency upper bound.
11    LatencyMillis,
12    /// Explicit transfer / copy volume upper bound.
13    BytesCopied,
14    /// Host memory residency upper bound.
15    MemoryBytes,
16    /// Number of dynamic allocations in one measured operation.
17    AllocationCount,
18    /// Number of worker threads permitted by the measured policy.
19    ThreadCount,
20}
21
22/// One declared budget ceiling.
23#[derive(Clone, Debug, PartialEq, Eq)]
24pub struct PerformanceBudget {
25    /// Budget id, e.g. `north-star-e2e-latency`.
26    pub id: String,
27    /// Dimension being constrained.
28    pub kind: BudgetKind,
29    /// Inclusive maximum allowed measurement.
30    pub ceiling: u64,
31}
32
33/// One measured sample against a budget id.
34#[derive(Clone, Debug, PartialEq, Eq)]
35pub struct PerformanceSample {
36    /// Matching budget id.
37    pub budget_id: String,
38    /// Observed value in the budget's units.
39    pub observed: u64,
40}
41
42/// Collection of budgets plus measured samples.
43#[derive(Clone, Debug, Default, PartialEq, Eq)]
44pub struct PerformanceBudgetReport {
45    budgets: Vec<PerformanceBudget>,
46    samples: Vec<PerformanceSample>,
47}
48
49impl PerformanceBudgetReport {
50    /// Creates an empty report.
51    #[must_use]
52    pub fn new() -> Self {
53        Self::default()
54    }
55
56    /// Declares a budget ceiling.
57    pub fn declare(&mut self, budget: PerformanceBudget) {
58        self.budgets.push(budget);
59    }
60
61    /// Records one measurement.
62    pub fn sample(&mut self, budget_id: impl Into<String>, observed: u64) {
63        self.samples.push(PerformanceSample { budget_id: budget_id.into(), observed });
64    }
65
66    /// Returns budgets.
67    #[must_use]
68    pub fn budgets(&self) -> &[PerformanceBudget] {
69        &self.budgets
70    }
71
72    /// Returns samples.
73    #[must_use]
74    pub fn samples(&self) -> &[PerformanceSample] {
75        &self.samples
76    }
77
78    /// Fails when any sample exceeds its ceiling, or samples a missing budget.
79    pub fn assert_within_budgets(&self) -> PlatformResult<()> {
80        for sample in &self.samples {
81            let Some(budget) = self.budgets.iter().find(|b| b.id == sample.budget_id) else {
82                return Err(PlatformError::InvalidConfiguration(format!(
83                    "sample references unknown budget `{}`",
84                    sample.budget_id
85                )));
86            };
87            if sample.observed > budget.ceiling {
88                return Err(PlatformError::BudgetExceeded {
89                    budget_id: budget.id.clone(),
90                    observed: sample.observed,
91                    ceiling: budget.ceiling,
92                });
93            }
94        }
95        Ok(())
96    }
97}
98
99#[cfg(test)]
100mod tests {
101    use super::{BudgetKind, PerformanceBudget, PerformanceBudgetReport};
102
103    #[test]
104    fn rejects_over_ceiling() {
105        let mut report = PerformanceBudgetReport::new();
106        report.declare(PerformanceBudget {
107            id: "latency".into(),
108            kind: BudgetKind::LatencyMillis,
109            ceiling: 100,
110        });
111        report.sample("latency", 50);
112        assert!(report.assert_within_budgets().is_ok());
113        report.sample("latency", 101);
114        assert!(report.assert_within_budgets().is_err());
115    }
116}