1use crate::error::DoDResult;
7use serde::{Deserialize, Serialize};
8#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
12pub struct TimingMeasurement {
13 start_time: Option<std::time::SystemTime>,
15 end_time: Option<std::time::SystemTime>,
17 elapsed_ms: u64,
19}
20
21impl TimingMeasurement {
22 pub fn new() -> Self {
24 Self {
25 start_time: Some(std::time::SystemTime::now()),
26 end_time: None,
27 elapsed_ms: 0,
28 }
29 }
30
31 pub fn finished(mut self, elapsed_ms: u64) -> Self {
33 self.elapsed_ms = elapsed_ms;
34 self.end_time = Some(std::time::SystemTime::now());
35 self
36 }
37
38 pub fn elapsed_ms(&self) -> u64 {
40 self.elapsed_ms
41 }
42
43 pub fn within_constraint(&self, max_ms: u64) -> bool {
45 self.elapsed_ms <= max_ms
46 }
47}
48
49impl Default for TimingMeasurement {
50 fn default() -> Self {
51 Self::new()
52 }
53}
54
55#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
57pub struct TimingGuarantee {
58 max_ms: u64,
60 strict: bool,
62}
63
64impl TimingGuarantee {
65 pub fn new(max_ms: u64) -> Self {
67 Self {
68 max_ms,
69 strict: true,
70 }
71 }
72
73 pub fn soft(max_ms: u64) -> Self {
75 Self {
76 max_ms,
77 strict: false,
78 }
79 }
80
81 pub fn max_ms(&self) -> u64 {
83 self.max_ms
84 }
85
86 pub fn is_strict(&self) -> bool {
88 self.strict
89 }
90
91 pub fn check(&self, measurement: &TimingMeasurement) -> DoDResult<()> {
93 if measurement.elapsed_ms > self.max_ms {
94 if self.strict {
95 return Err(crate::error::DoDError::TimingViolation {
96 expected: self.max_ms,
97 actual: measurement.elapsed_ms,
98 });
99 }
100 }
101 Ok(())
102 }
103}
104
105pub const CHATMAN_CONSTANT_MS: u64 = 8;
107
108pub fn kernel_timing_constraint() -> TimingGuarantee {
110 TimingGuarantee::new(CHATMAN_CONSTANT_MS)
111}
112
113pub fn decision_closure_timing() -> TimingGuarantee {
115 TimingGuarantee::new(2)
116}
117
118pub fn schema_validation_timing() -> TimingGuarantee {
120 TimingGuarantee::soft(5)
121}
122
123#[derive(Clone)]
125pub struct TimingEnforcer {
126 constraints: Vec<(String, TimingGuarantee)>,
128 measurements: Vec<(String, TimingMeasurement)>,
130}
131
132impl TimingEnforcer {
133 pub fn new() -> Self {
135 Self {
136 constraints: Vec::new(),
137 measurements: Vec::new(),
138 }
139 }
140
141 pub fn with_constraint(mut self, name: impl Into<String>, constraint: TimingGuarantee) -> Self {
143 self.constraints.push((name.into(), constraint));
144 self
145 }
146
147 pub fn record_measurement(
149 mut self, name: impl Into<String>, measurement: TimingMeasurement,
150 ) -> Self {
151 self.measurements.push((name.into(), measurement));
152 self
153 }
154
155 pub fn verify(&self) -> DoDResult<()> {
157 for (_name, measurement) in &self.measurements {
158 for (_constraint_name, constraint) in &self.constraints {
159 constraint.check(measurement)?;
160 }
161 }
162 Ok(())
163 }
164
165 pub fn measurements(&self) -> &[(String, TimingMeasurement)] {
167 &self.measurements
168 }
169
170 pub fn stats(&self) -> TimingStats {
172 if self.measurements.is_empty() {
173 return TimingStats::default();
174 }
175
176 let times: Vec<u64> = self
177 .measurements
178 .iter()
179 .map(|(_, m)| m.elapsed_ms)
180 .collect();
181 let sum: u64 = times.iter().sum();
182 let count = times.len() as u64;
183 let mean = sum / count;
184
185 let mut sorted = times.clone();
186 sorted.sort_unstable();
187 let median = sorted[sorted.len() / 2];
188 let min = *sorted.first().unwrap_or(&0);
189 let max = *sorted.last().unwrap_or(&0);
190
191 let variance: u64 = times
193 .iter()
194 .map(|t| {
195 let diff = if *t > mean { *t - mean } else { mean - *t };
196 diff * diff
197 })
198 .sum::<u64>()
199 / count;
200 let stddev = (variance as f64).sqrt() as u64;
201
202 TimingStats {
203 count: count as usize,
204 min,
205 max,
206 mean,
207 median,
208 stddev,
209 p99: sorted[(sorted.len() * 99) / 100],
210 }
211 }
212}
213
214impl Default for TimingEnforcer {
215 fn default() -> Self {
216 Self::new()
217 }
218}
219
220#[derive(Debug, Clone, Default, Serialize, Deserialize)]
222pub struct TimingStats {
223 pub count: usize,
225 pub min: u64,
227 pub max: u64,
229 pub mean: u64,
231 pub median: u64,
233 pub stddev: u64,
235 pub p99: u64,
237}
238
239impl TimingStats {
240 pub fn within_chatman_constant(&self) -> bool {
242 self.p99 <= CHATMAN_CONSTANT_MS
243 }
244
245 pub fn confidence_interval_95(&self) -> (u64, u64) {
247 let margin = (1.96 * self.stddev as f64) as u64;
248 let lower = self.mean.saturating_sub(margin);
249 let upper = self.mean + margin;
250 (lower, upper)
251 }
252}
253
254#[cfg(test)]
255mod tests {
256 use super::*;
257
258 #[test]
259 fn test_timing_measurement() {
260 let measurement = TimingMeasurement::new().finished(5);
261 assert_eq!(measurement.elapsed_ms(), 5);
262 }
263
264 #[test]
265 fn test_timing_guarantee_check() -> DoDResult<()> {
266 let guarantee = TimingGuarantee::new(10);
267 let measurement = TimingMeasurement::new().finished(8);
268 guarantee.check(&measurement)?;
269 Ok(())
270 }
271
272 #[test]
273 fn test_timing_violation() {
274 let guarantee = TimingGuarantee::new(5);
275 let measurement = TimingMeasurement::new().finished(10);
276 assert!(guarantee.check(&measurement).is_err());
277 }
278
279 #[test]
280 fn test_chatman_constant() {
281 assert_eq!(CHATMAN_CONSTANT_MS, 8);
282 }
283
284 #[test]
285 fn test_timing_enforcer() -> DoDResult<()> {
286 let enforcer = TimingEnforcer::new()
287 .with_constraint("kernel", kernel_timing_constraint())
288 .record_measurement("test", TimingMeasurement::new().finished(5));
289
290 enforcer.verify()?;
291 Ok(())
292 }
293
294 #[test]
295 fn test_timing_stats() {
296 let enforcer = TimingEnforcer::new()
297 .record_measurement("m1", TimingMeasurement::new().finished(5))
298 .record_measurement("m2", TimingMeasurement::new().finished(10))
299 .record_measurement("m3", TimingMeasurement::new().finished(7));
300
301 let stats = enforcer.stats();
302 assert_eq!(stats.count, 3);
303 assert_eq!(stats.min, 5);
304 assert_eq!(stats.max, 10);
305 }
306}