metrale_telemetry/
kernel_audit.rs1mod report;
19
20pub use report::{render_kernel_table, unresolved_report};
21
22use std::collections::BTreeMap;
23use std::panic::Location;
24use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
25
26static SEALED: AtomicBool = AtomicBool::new(false);
28static ALLOW_UNRESOLVED: AtomicBool = AtomicBool::new(false);
30static UNRESOLVED: AtomicU64 = AtomicU64::new(0);
33static LATE_WARNED: AtomicBool = AtomicBool::new(false);
35
36#[derive(Clone, Debug)]
38pub struct AuditRow {
39 pub module: String,
40 pub func: String,
41 pub loaded: bool,
43 pub site: &'static Location<'static>,
46}
47
48impl AuditRow {
49 pub fn name(&self) -> String {
51 format!("{}::{}", self.module, self.func)
52 }
53}
54
55pub fn record(module: &str, func: &str, loaded: bool, site: &'static Location<'static>) {
61 if !loaded && SEALED.load(Ordering::Acquire) {
62 late_miss(module, func, site);
63 }
64 if let Ok(mut v) = crate::run_metrics::metrics().kernel_audit.lock() {
65 v.push((module.to_string(), func.to_string(), loaded, site));
66 }
67}
68
69fn late_miss(module: &str, func: &str, site: &'static Location<'static>) {
73 UNRESOLVED.fetch_add(1, Ordering::Relaxed);
74 if ALLOW_UNRESOLVED.load(Ordering::Relaxed) {
75 if !LATE_WARNED.swap(true, Ordering::Relaxed) {
76 tracing::warn!(
77 "kernel lookup {module}::{func} at {}:{} failed AFTER the boot audit sealed. \
78 This lookup is not eager, so the boot gate could not see it. Continuing \
79 because --dangerously-allow-unresolved-kernel-lookups was passed. \
80 Performance may be seriously degraded. We recommend you open a GitHub issue \
81 and/or open a PR to solve this issue. \
82 (metrale_kernel_lookups_unresolved counts every occurrence.)",
83 site.file(),
84 site.line(),
85 );
86 }
87 return;
88 }
89 tracing::error!(
92 "kernel lookup {module}::{func} at {}:{} failed AFTER the boot audit sealed — a \
93 non-eager lookup that the boot gate could not see. The dispatch that asked for it is \
94 now on a silent fallback path. Aborting. Pass \
95 --dangerously-allow-unresolved-kernel-lookups to downgrade this to a warning.",
96 site.file(),
97 site.line(),
98 );
99 std::process::abort();
100}
101
102pub fn seal(unresolved: u64, allow: bool) {
106 UNRESOLVED.store(unresolved, Ordering::Relaxed);
107 ALLOW_UNRESOLVED.store(allow, Ordering::Relaxed);
108 LATE_WARNED.store(false, Ordering::Relaxed);
109 SEALED.store(true, Ordering::Release);
110}
111
112pub fn unseal() {
115 SEALED.store(false, Ordering::Release);
116 UNRESOLVED.store(0, Ordering::Relaxed);
117 LATE_WARNED.store(false, Ordering::Relaxed);
118}
119
120pub fn unresolved_lookups() -> u64 {
123 UNRESOLVED.load(Ordering::Relaxed)
124}
125
126pub fn audit_rows() -> Vec<AuditRow> {
129 let mut resolved: BTreeMap<(String, String), (bool, &'static Location<'static>)> =
130 BTreeMap::new();
131 if let Ok(v) = crate::run_metrics::metrics().kernel_audit.lock() {
132 for (m, f, ok, site) in v.iter() {
133 let e = resolved
134 .entry((m.clone(), f.clone()))
135 .or_insert((false, *site));
136 e.0 = e.0 || *ok;
137 }
138 }
139 resolved
140 .into_iter()
141 .map(|((module, func), (loaded, site))| AuditRow {
142 module,
143 func,
144 loaded,
145 site,
146 })
147 .collect()
148}
149
150#[derive(Clone, Debug, Default)]
154pub struct FailureSplit {
155 pub required: Vec<AuditRow>,
158 pub expected: Vec<AuditRow>,
161}
162
163pub fn classify_failures(expected_absent: &[(&str, &str)]) -> FailureSplit {
166 split_failures(&audit_rows(), expected_absent)
167}
168
169pub fn split_failures(rows: &[AuditRow], expected_absent: &[(&str, &str)]) -> FailureSplit {
171 let (expected, required): (Vec<AuditRow>, Vec<AuditRow>) =
172 rows.iter().filter(|r| !r.loaded).cloned().partition(|r| {
173 expected_absent
174 .iter()
175 .any(|(em, ef)| *em == r.module.as_str() && *ef == r.func.as_str())
176 });
177 FailureSplit { required, expected }
178}