Skip to main content

tacet_core/preflight/
mod.rs

1//! Preflight checks to validate measurement setup before analysis.
2//!
3//! This module provides diagnostic checks that help identify common issues
4//! with timing measurement setups that could lead to false positives or
5//! unreliable results.
6//!
7//! # no_std Support
8//!
9//! All checks in this module are no_std compatible. Platform-specific checks
10//! (like filesystem-based system configuration checks) remain in the main
11//! tacet crate.
12//!
13//! # Checks Provided
14//!
15//! - **Sanity Check**: Fixed-vs-Fixed comparison to detect broken harness
16//! - **Autocorrelation**: Detects periodic interference patterns
17//! - **Resolution**: Detects timer resolution issues
18//!
19//! # Aggregation
20//!
21//! The `PreflightResult` and `PreflightWarnings` types aggregate results from
22//! all core checks. Use `run_core_checks()` to run all no_std-compatible checks.
23
24extern crate alloc;
25
26use alloc::vec::Vec;
27
28mod autocorr;
29mod resolution;
30mod sanity;
31
32pub use autocorr::{autocorrelation_check, compute_acf, AutocorrWarning};
33pub use resolution::{resolution_check, ResolutionWarning};
34pub use sanity::{sanity_check, SanityWarning};
35
36/// Result of running core preflight checks (no_std compatible).
37///
38/// This struct aggregates warnings from all core checks. Platform-specific
39/// checks (like system configuration) are handled by the `tacet` crate.
40#[derive(Debug, Clone, Default)]
41#[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))]
42pub struct PreflightResult {
43    /// All warnings collected from preflight checks.
44    pub warnings: PreflightWarnings,
45
46    /// Whether any critical warnings were found.
47    pub has_critical: bool,
48
49    /// Whether the measurement setup is considered valid.
50    pub is_valid: bool,
51}
52
53impl PreflightResult {
54    /// Create a new empty preflight result.
55    pub fn new() -> Self {
56        Self {
57            warnings: PreflightWarnings::default(),
58            has_critical: false,
59            is_valid: true,
60        }
61    }
62
63    /// Add a sanity warning.
64    pub fn add_sanity_warning(&mut self, warning: SanityWarning) {
65        if warning.is_result_undermining() {
66            self.has_critical = true;
67            self.is_valid = false;
68        }
69        self.warnings.sanity.push(warning);
70    }
71
72    /// Add an autocorrelation warning.
73    pub fn add_autocorr_warning(&mut self, warning: AutocorrWarning) {
74        self.warnings.autocorr.push(warning);
75    }
76
77    /// Add a resolution warning.
78    pub fn add_resolution_warning(&mut self, warning: ResolutionWarning) {
79        if warning.is_result_undermining() {
80            self.has_critical = true;
81            self.is_valid = false;
82        }
83        self.warnings.resolution.push(warning);
84    }
85
86    /// Check if there are any warnings.
87    pub fn has_warnings(&self) -> bool {
88        !self.warnings.sanity.is_empty()
89            || !self.warnings.autocorr.is_empty()
90            || !self.warnings.resolution.is_empty()
91    }
92}
93
94/// Collection of warnings from core preflight checks (no_std compatible).
95///
96/// Platform-specific warnings (like system configuration) are not included
97/// here; they are added by the `tacet` crate's preflight module.
98#[derive(Debug, Clone, Default)]
99#[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))]
100pub struct PreflightWarnings {
101    /// Warnings from sanity check (Fixed-vs-Fixed).
102    pub sanity: Vec<SanityWarning>,
103
104    /// Warnings from autocorrelation check.
105    pub autocorr: Vec<AutocorrWarning>,
106
107    /// Warnings from timer resolution check.
108    pub resolution: Vec<ResolutionWarning>,
109}
110
111impl PreflightWarnings {
112    /// Create an empty warnings collection.
113    pub fn new() -> Self {
114        Self::default()
115    }
116
117    /// Get total number of warnings.
118    pub fn count(&self) -> usize {
119        self.sanity.len() + self.autocorr.len() + self.resolution.len()
120    }
121
122    /// Check if empty.
123    pub fn is_empty(&self) -> bool {
124        self.count() == 0
125    }
126}
127
128/// Run core preflight checks (no_std compatible).
129///
130/// This runs the three core checks that don't require std:
131/// - Resolution check (timer quantization)
132/// - Sanity check (Fixed-vs-Fixed)
133/// - Autocorrelation check
134///
135/// Platform-specific checks (like CPU governor) are handled by `tacet::preflight::run_all_checks()`.
136///
137/// # Arguments
138///
139/// * `fixed_samples` - Timing samples from fixed input class (baseline), in nanoseconds
140/// * `random_samples` - Timing samples from random input class (sample), in nanoseconds
141/// * `timer_resolution_ns` - Timer resolution in nanoseconds
142/// * `seed` - Seed for reproducible randomization in sanity check
143///
144/// # Returns
145///
146/// A `PreflightResult` containing all warnings and validity assessment.
147pub fn run_core_checks(
148    fixed_samples: &[f64],
149    random_samples: &[f64],
150    timer_resolution_ns: f64,
151    seed: u64,
152) -> PreflightResult {
153    let mut result = PreflightResult::new();
154
155    // Run resolution check (quantization)
156    if let Some(warning) = resolution_check(fixed_samples, timer_resolution_ns) {
157        result.add_resolution_warning(warning);
158    }
159
160    // Run sanity check (Fixed-vs-Fixed) with randomization
161    if let Some(warning) = sanity_check(fixed_samples, timer_resolution_ns, seed) {
162        result.add_sanity_warning(warning);
163    }
164
165    // Run autocorrelation check
166    if let Some(warning) = autocorrelation_check(fixed_samples, random_samples) {
167        result.add_autocorr_warning(warning);
168    }
169
170    result
171}
172
173#[cfg(test)]
174mod tests {
175    use super::*;
176
177    #[test]
178    fn test_preflight_result_default() {
179        let result = PreflightResult::new();
180        assert!(result.is_valid);
181        assert!(!result.has_critical);
182        assert!(!result.has_warnings());
183    }
184
185    #[test]
186    fn test_warnings_count() {
187        let mut warnings = PreflightWarnings::new();
188        assert_eq!(warnings.count(), 0);
189        assert!(warnings.is_empty());
190
191        warnings.sanity.push(SanityWarning::BrokenHarness {
192            variance_ratio: 7.5,
193        });
194        assert_eq!(warnings.count(), 1);
195        assert!(!warnings.is_empty());
196    }
197
198    #[test]
199    fn test_run_core_checks_empty() {
200        // Empty samples should not crash
201        let result = run_core_checks(&[], &[], 1.0, 42);
202        // With empty data, preflight should be valid (no data to fail on)
203        assert!(result.is_valid);
204    }
205
206    #[test]
207    fn test_run_core_checks_normal_data() {
208        // Generate some reasonable timing data
209        let fixed: Vec<f64> = (0..1000).map(|i| 100.0 + (i % 10) as f64).collect();
210        let random: Vec<f64> = (0..1000).map(|i| 105.0 + (i % 10) as f64).collect();
211
212        let result = run_core_checks(&fixed, &random, 1.0, 42);
213        // Should pass with reasonable data
214        assert!(result.is_valid);
215    }
216}