nodedb_mem/governor/
metrics.rs1use std::sync::atomic::Ordering;
7
8use super::core::MemoryGovernor;
9use crate::engine::EngineId;
10use crate::pressure::{PressureLevel, PressureThresholds};
11
12impl MemoryGovernor {
13 pub fn total_allocated(&self) -> usize {
17 self.budgets.iter().map(|b| b.allocated()).sum()
18 }
19
20 pub fn total_over_release_count(&self) -> usize {
27 self.global_counter.over_release.total()
28 }
29
30 pub fn global_over_release_count(&self) -> usize {
32 self.global_counter.over_release.global()
33 }
34
35 pub fn database_over_release_count(&self) -> usize {
37 self.global_counter.over_release.database()
38 }
39
40 pub fn tenant_over_release_count(&self) -> usize {
42 self.global_counter.over_release.tenant()
43 }
44
45 pub fn engine_over_release_count(&self) -> usize {
47 self.global_counter.over_release.engine()
48 }
49
50 pub fn global_utilization_percent(&self) -> u8 {
55 let allocated = self.global_counter.allocated.load(Ordering::Relaxed);
56 if self.global_ceiling == 0 {
57 return if allocated == 0 { 0 } else { 100 };
58 }
59 ((allocated as u128 * 100) / self.global_ceiling as u128).min(100) as u8
60 }
61
62 pub fn engine_pressure(&self, engine: EngineId) -> PressureLevel {
64 self.thresholds
65 .level_for(self.budgets[engine.index()].utilization_percent())
66 }
67
68 pub fn global_pressure(&self) -> PressureLevel {
70 self.thresholds.level_for(self.global_utilization_percent())
71 }
72
73 pub fn worst_engine_pressure(&self) -> PressureLevel {
78 self.budgets
79 .iter()
80 .map(|b| self.thresholds.level_for(b.utilization_percent()))
81 .max()
82 .unwrap_or(PressureLevel::Normal)
83 }
84
85 pub fn set_thresholds(&mut self, thresholds: PressureThresholds) {
87 self.thresholds = thresholds;
88 }
89
90 pub fn snapshot(&self) -> Vec<EngineSnapshot> {
92 EngineId::ALL
93 .iter()
94 .zip(self.budgets.iter())
95 .map(|(&engine, budget)| EngineSnapshot {
96 engine,
97 allocated: budget.allocated(),
98 limit: budget.limit(),
99 peak: budget.peak(),
100 rejections: budget.rejections(),
101 utilization_percent: budget.utilization_percent(),
102 })
103 .collect()
104 }
105}
106
107#[derive(Debug, Clone)]
109pub struct EngineSnapshot {
110 pub engine: EngineId,
111 pub allocated: usize,
112 pub limit: usize,
113 pub peak: usize,
114 pub rejections: usize,
115 pub utilization_percent: u8,
116}
117
118#[cfg(test)]
119mod tests {
120 use super::*;
121 use crate::governor::test_support::{db, tenant, test_config};
122
123 #[test]
124 fn snapshot_reports_all_engines() {
125 let gov = MemoryGovernor::new(test_config()).unwrap();
126 let _tok = gov
127 .try_reserve(db(), tenant(), EngineId::Vector, 2048)
128 .unwrap();
129
130 let snap = gov.snapshot();
131 assert_eq!(snap.len(), EngineId::COUNT);
132
133 let vector_snap = snap.iter().find(|s| s.engine == EngineId::Vector).unwrap();
134 assert_eq!(vector_snap.allocated, 2048);
135 assert_eq!(vector_snap.limit, 4096);
136 assert_eq!(vector_snap.utilization_percent, 50);
137 }
138
139 #[test]
140 fn engine_pressure_levels() {
141 let gov = MemoryGovernor::new(test_config()).unwrap();
142
143 assert_eq!(gov.engine_pressure(EngineId::Vector), PressureLevel::Normal);
144
145 let _tok1 = gov
146 .try_reserve(db(), tenant(), EngineId::Vector, 2868)
147 .unwrap();
148 assert_eq!(
149 gov.engine_pressure(EngineId::Vector),
150 PressureLevel::Warning
151 );
152 }
153
154 #[test]
155 fn worst_engine_pressure_picks_highest() {
156 let gov = MemoryGovernor::new(test_config()).unwrap();
157 assert_eq!(gov.worst_engine_pressure(), PressureLevel::Normal);
158
159 let _tok = gov
162 .try_reserve(db(), tenant(), EngineId::Query, 1800)
163 .unwrap();
164 assert_eq!(gov.engine_pressure(EngineId::Vector), PressureLevel::Normal);
165 assert_eq!(gov.worst_engine_pressure(), PressureLevel::Critical);
166 }
167}