Skip to main content

cu_profiler_core/diagnostics/
mod.rs

1//! Diagnostic engine: turns analysed data into actionable, Solana-specific
2//! findings.
3
4pub mod rules;
5
6pub use rules::Context;
7
8use serde::{Deserialize, Serialize};
9
10use crate::budget::Severity;
11
12/// A single finding about a scenario.
13#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
14pub struct Diagnostic {
15    /// Stable identifier, e.g. `"cpi_explosion"`.
16    pub id: String,
17    /// Short human title.
18    pub title: String,
19    /// Severity.
20    pub severity: Severity,
21    /// Scenario the finding applies to.
22    pub scenario: String,
23    /// The evidence that triggered the finding.
24    pub evidence: String,
25    /// What to do about it.
26    pub recommendation: String,
27}
28
29/// Run every rule against the context and collect the findings.
30#[must_use]
31pub fn evaluate(ctx: &Context) -> Vec<Diagnostic> {
32    rules::RULES.iter().filter_map(|rule| rule(ctx)).collect()
33}
34
35#[cfg(test)]
36mod tests {
37    use super::*;
38    use crate::budget::{self, BudgetPolicy};
39    use crate::confidence::Confidence;
40    use crate::model::Measurement;
41    use crate::scenario::ExpectedResult;
42
43    #[test]
44    fn flags_absolute_budget_and_cpi_explosion() {
45        let measurement = Measurement {
46            total_cu: 120_000,
47            cpi_count: 10,
48            ..Measurement::empty()
49        };
50        let policy = BudgetPolicy {
51            absolute_max_cu: Some(100_000),
52            ..Default::default()
53        };
54        let policy_results = budget::evaluate(&measurement, &policy, None);
55        let confidence = Confidence::high();
56        let ctx = Context {
57            scenario: "swap",
58            measurement: &measurement,
59            policy_results: &policy_results,
60            baseline: None,
61            confidence: &confidence,
62            expected: ExpectedResult::Success,
63            scope_count: 0,
64            log_line_count: 0,
65            late_validation: false,
66        };
67        let diags = evaluate(&ctx);
68        let ids: Vec<&str> = diags.iter().map(|d| d.id.as_str()).collect();
69        assert!(ids.contains(&"absolute_budget_exceeded"));
70        assert!(ids.contains(&"cpi_explosion"));
71    }
72
73    #[test]
74    fn clean_run_has_no_diagnostics() {
75        let measurement = Measurement {
76            total_cu: 10_000,
77            ..Measurement::empty()
78        };
79        let confidence = Confidence::high();
80        let ctx = Context {
81            scenario: "ok",
82            measurement: &measurement,
83            policy_results: &[],
84            baseline: None,
85            confidence: &confidence,
86            expected: ExpectedResult::Success,
87            scope_count: 0,
88            log_line_count: 0,
89            late_validation: false,
90        };
91        assert!(evaluate(&ctx).is_empty());
92    }
93
94    #[test]
95    fn flags_cpi_share_log_bloat_and_late_validation() {
96        let measurement = Measurement {
97            total_cu: 50_000,
98            cpi_count: 2,
99            unattributed_pct: 10.0, // ⇒ 90% CPI share
100            ..Measurement::empty()
101        };
102        let confidence = Confidence::high();
103        let ctx = Context {
104            scenario: "swap",
105            measurement: &measurement,
106            policy_results: &[],
107            baseline: None,
108            confidence: &confidence,
109            expected: ExpectedResult::Success,
110            scope_count: 4,
111            log_line_count: 40,
112            late_validation: true,
113        };
114        let ids: Vec<String> = evaluate(&ctx).into_iter().map(|d| d.id).collect();
115        assert!(ids.contains(&"high_cpi_share".to_string()));
116        assert!(ids.contains(&"event_log_bloat".to_string()));
117        assert!(ids.contains(&"late_validation".to_string()));
118    }
119}