cu_profiler_core/diagnostics/
mod.rs1pub mod rules;
5
6pub use rules::Context;
7
8use serde::{Deserialize, Serialize};
9
10use crate::budget::Severity;
11
12#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
14pub struct Diagnostic {
15 pub id: String,
17 pub title: String,
19 pub severity: Severity,
21 pub scenario: String,
23 pub evidence: String,
25 pub recommendation: String,
27}
28
29#[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, ..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}