use crate::error::DoDResult;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
pub struct TimingMeasurement {
start_time: Option<std::time::SystemTime>,
end_time: Option<std::time::SystemTime>,
elapsed_ms: u64,
}
impl TimingMeasurement {
pub fn new() -> Self {
Self {
start_time: Some(std::time::SystemTime::now()),
end_time: None,
elapsed_ms: 0,
}
}
pub fn finished(mut self, elapsed_ms: u64) -> Self {
self.elapsed_ms = elapsed_ms;
self.end_time = Some(std::time::SystemTime::now());
self
}
pub fn elapsed_ms(&self) -> u64 {
self.elapsed_ms
}
pub fn within_constraint(&self, max_ms: u64) -> bool {
self.elapsed_ms <= max_ms
}
}
impl Default for TimingMeasurement {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub struct TimingGuarantee {
max_ms: u64,
strict: bool,
}
impl TimingGuarantee {
pub fn new(max_ms: u64) -> Self {
Self {
max_ms,
strict: true,
}
}
pub fn soft(max_ms: u64) -> Self {
Self {
max_ms,
strict: false,
}
}
pub fn max_ms(&self) -> u64 {
self.max_ms
}
pub fn is_strict(&self) -> bool {
self.strict
}
pub fn check(&self, measurement: &TimingMeasurement) -> DoDResult<()> {
if measurement.elapsed_ms > self.max_ms {
if self.strict {
return Err(crate::error::DoDError::TimingViolation {
expected: self.max_ms,
actual: measurement.elapsed_ms,
});
}
}
Ok(())
}
}
pub const CHATMAN_CONSTANT_MS: u64 = 8;
pub fn kernel_timing_constraint() -> TimingGuarantee {
TimingGuarantee::new(CHATMAN_CONSTANT_MS)
}
pub fn decision_closure_timing() -> TimingGuarantee {
TimingGuarantee::new(2)
}
pub fn schema_validation_timing() -> TimingGuarantee {
TimingGuarantee::soft(5)
}
#[derive(Clone)]
pub struct TimingEnforcer {
constraints: Vec<(String, TimingGuarantee)>,
measurements: Vec<(String, TimingMeasurement)>,
}
impl TimingEnforcer {
pub fn new() -> Self {
Self {
constraints: Vec::new(),
measurements: Vec::new(),
}
}
pub fn with_constraint(mut self, name: impl Into<String>, constraint: TimingGuarantee) -> Self {
self.constraints.push((name.into(), constraint));
self
}
pub fn record_measurement(
mut self, name: impl Into<String>, measurement: TimingMeasurement,
) -> Self {
self.measurements.push((name.into(), measurement));
self
}
pub fn verify(&self) -> DoDResult<()> {
for (_name, measurement) in &self.measurements {
for (_constraint_name, constraint) in &self.constraints {
constraint.check(measurement)?;
}
}
Ok(())
}
pub fn measurements(&self) -> &[(String, TimingMeasurement)] {
&self.measurements
}
pub fn stats(&self) -> TimingStats {
if self.measurements.is_empty() {
return TimingStats::default();
}
let times: Vec<u64> = self
.measurements
.iter()
.map(|(_, m)| m.elapsed_ms)
.collect();
let sum: u64 = times.iter().sum();
let count = times.len() as u64;
let mean = sum / count;
let mut sorted = times.clone();
sorted.sort_unstable();
let median = sorted[sorted.len() / 2];
let min = *sorted.first().unwrap_or(&0);
let max = *sorted.last().unwrap_or(&0);
let variance: u64 = times
.iter()
.map(|t| {
let diff = if *t > mean { *t - mean } else { mean - *t };
diff * diff
})
.sum::<u64>()
/ count;
let stddev = (variance as f64).sqrt() as u64;
TimingStats {
count: count as usize,
min,
max,
mean,
median,
stddev,
p99: sorted[(sorted.len() * 99) / 100],
}
}
}
impl Default for TimingEnforcer {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct TimingStats {
pub count: usize,
pub min: u64,
pub max: u64,
pub mean: u64,
pub median: u64,
pub stddev: u64,
pub p99: u64,
}
impl TimingStats {
pub fn within_chatman_constant(&self) -> bool {
self.p99 <= CHATMAN_CONSTANT_MS
}
pub fn confidence_interval_95(&self) -> (u64, u64) {
let margin = (1.96 * self.stddev as f64) as u64;
let lower = self.mean.saturating_sub(margin);
let upper = self.mean + margin;
(lower, upper)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_timing_measurement() {
let measurement = TimingMeasurement::new().finished(5);
assert_eq!(measurement.elapsed_ms(), 5);
}
#[test]
fn test_timing_guarantee_check() -> DoDResult<()> {
let guarantee = TimingGuarantee::new(10);
let measurement = TimingMeasurement::new().finished(8);
guarantee.check(&measurement)?;
Ok(())
}
#[test]
fn test_timing_violation() {
let guarantee = TimingGuarantee::new(5);
let measurement = TimingMeasurement::new().finished(10);
assert!(guarantee.check(&measurement).is_err());
}
#[test]
fn test_chatman_constant() {
assert_eq!(CHATMAN_CONSTANT_MS, 8);
}
#[test]
fn test_timing_enforcer() -> DoDResult<()> {
let enforcer = TimingEnforcer::new()
.with_constraint("kernel", kernel_timing_constraint())
.record_measurement("test", TimingMeasurement::new().finished(5));
enforcer.verify()?;
Ok(())
}
#[test]
fn test_timing_stats() {
let enforcer = TimingEnforcer::new()
.record_measurement("m1", TimingMeasurement::new().finished(5))
.record_measurement("m2", TimingMeasurement::new().finished(10))
.record_measurement("m3", TimingMeasurement::new().finished(7));
let stats = enforcer.stats();
assert_eq!(stats.count, 3);
assert_eq!(stats.min, 5);
assert_eq!(stats.max, 10);
}
}