memory_budget/budget/memory/
tick.rs1use std::sync::Arc;
4use std::sync::atomic::Ordering;
5
6use super::super::snapshot::{BudgetSnapshot, CacheSnapshot, ReporterSnapshot};
7use super::super::state::LiveViews;
8use super::handle::MemoryBudget;
9use crate::policy::{CacheView, PolicyInput};
10use crate::resizable::Resizable;
11
12impl MemoryBudget {
13 pub fn tick_now(&self) -> BudgetSnapshot {
17 let rss_sysinfo_bytes = self.state.rss.read_rss_bytes();
18 let snapshot = self.run_tick(rss_sysinfo_bytes);
19 self.emit(&snapshot);
20 #[cfg(feature = "heap-profiling")]
25 if let Some(profiler) = self.state.heap_profiler.as_ref() {
26 profiler.maybe_dump(snapshot.rss_bytes);
27 }
28 if let Some(ceiling) = self.state.config.hard_ceiling_bytes()
33 && snapshot.rss_bytes > ceiling
34 {
35 tracing::error!(
36 target: "memory_budget::budget",
37 rss_bytes = snapshot.rss_bytes,
38 rss_sysinfo_bytes = snapshot.rss_sysinfo_bytes,
39 ceiling_bytes = ceiling,
40 "hard ceiling exceeded — aborting process"
41 );
42 std::process::abort();
43 }
44 snapshot
45 }
46
47 pub fn spawn(&self) -> tokio::task::JoinHandle<()> {
51 let state = Arc::clone(&self.state);
52 let interval = state.config.tick();
53 tokio::spawn(async move {
54 let handle = MemoryBudget { state };
55 let mut ticker = tokio::time::interval(interval);
56 ticker.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Delay);
57 loop {
58 ticker.tick().await;
59 handle.tick_now();
60 }
61 })
62 }
63
64 fn run_tick(&self, rss_sysinfo_bytes: u64) -> BudgetSnapshot {
69 let effective_target = self
70 .state
71 .config
72 .target_bytes()
73 .saturating_sub(self.state.active_lease_bytes.load(Ordering::Relaxed));
74 let jemalloc = self.state.jemalloc.read();
75 let rss_bytes = if jemalloc.resident_bytes > 0 {
82 jemalloc.resident_bytes
83 } else {
84 rss_sysinfo_bytes
85 };
86 let live = self.live_views();
87 let previous_cache_bytes = self.state.previous_cache_bytes.load(Ordering::Relaxed);
88 let previous_cache_count = self.state.previous_cache_count.load(Ordering::Relaxed) as u32;
89 let previous_rss_bytes = self.state.previous_rss_bytes.load(Ordering::Relaxed);
90 let input = PolicyInput {
91 effective_target_bytes: effective_target,
92 rss_bytes,
93 caches: live.views,
94 previous_cache_bytes,
95 previous_cache_count,
96 previous_rss_bytes,
97 };
98 let output = self.state.policy.reapportion(&input);
99 for (cache, new_max) in live.live_caches.iter().zip(output.new_max_bytes.iter()) {
100 cache.set_max_bytes(*new_max);
101 }
102 let current_cache_bytes: u64 = input.caches.iter().map(|c| c.current_bytes).sum();
108 self.state
109 .previous_cache_bytes
110 .store(current_cache_bytes, Ordering::Relaxed);
111 self.state
112 .previous_cache_count
113 .store(input.caches.len() as u64, Ordering::Relaxed);
114 self.state
115 .previous_rss_bytes
116 .store(rss_bytes, Ordering::Relaxed);
117 let verdict_label = output.verdict.label();
118 let (verdict_streak, previous_verdict_label, previous_verdict_streak) =
119 self.advance_verdict_streak(verdict_label);
120 let reporters = self.live_reporters();
121 BudgetSnapshot {
122 target_bytes: self.state.config.target_bytes(),
123 effective_target_bytes: effective_target,
124 rss_bytes,
125 rss_sysinfo_bytes,
126 lease_bytes_active: self.state.active_lease_bytes.load(Ordering::Relaxed),
127 jemalloc,
128 verdict: output.verdict,
129 verdict_streak,
130 previous_verdict_label,
131 previous_verdict_streak,
132 reporters,
133 caches: input
134 .caches
135 .into_iter()
136 .zip(output.new_max_bytes)
137 .map(|(view, new_max)| CacheSnapshot {
138 name: view.name,
139 current_bytes: view.current_bytes,
140 previous_max_bytes: view.max_bytes,
141 new_max_bytes: new_max,
142 hit_rate: view.stats.hit_rate(),
143 })
144 .collect(),
145 }
146 }
147
148 fn advance_verdict_streak(
153 &self,
154 label: &'static str,
155 ) -> (u32, Option<&'static str>, Option<u32>) {
156 let mut guard = self
157 .state
158 .verdict_streak
159 .lock()
160 .expect("verdict streak lock");
161 if guard.label == label && guard.length > 0 {
162 guard.length = guard.length.saturating_add(1);
163 (guard.length, None, None)
164 } else {
165 let prev_label = if guard.length > 0 {
166 Some(guard.label)
167 } else {
168 None
169 };
170 let prev_streak = if guard.length > 0 {
171 Some(guard.length)
172 } else {
173 None
174 };
175 guard.label = label;
176 guard.length = 1;
177 (1, prev_label, prev_streak)
178 }
179 }
180
181 fn live_views(&self) -> LiveViews {
184 let mut guard = self.state.registrations.lock().expect("registrations lock");
185 let mut views = Vec::with_capacity(guard.len());
186 let mut live_caches: Vec<Arc<dyn Resizable>> = Vec::with_capacity(guard.len());
187 guard.retain(|reg| {
188 let Some(cache) = reg.cache.upgrade() else {
189 return false;
190 };
191 views.push(CacheView {
192 name: cache.name().to_owned(),
193 max_bytes: cache.max_bytes(),
194 preferred_max_bytes: reg.preferred_max_bytes,
195 current_bytes: cache.current_bytes(),
196 stats: cache.stats(),
197 });
198 live_caches.push(cache);
199 true
200 });
201 LiveViews { views, live_caches }
202 }
203
204 fn live_reporters(&self) -> Vec<ReporterSnapshot> {
207 let mut guard = self.state.reporters.lock().expect("reporters lock");
208 let mut out = Vec::with_capacity(guard.len());
209 guard.retain(|weak| {
210 let Some(reporter) = weak.upgrade() else {
211 return false;
212 };
213 out.push(ReporterSnapshot {
214 name: reporter.name().to_owned(),
215 current_bytes: reporter.current_bytes(),
216 });
217 true
218 });
219 out
220 }
221}