1use crate::buffer::MessageBuffer;
8use std::collections::VecDeque;
9use std::sync::atomic::{AtomicUsize, Ordering};
10use std::sync::{Arc, Mutex};
11use thiserror::Error;
12
13const DEFAULT_TOTAL_BUDGET: usize = 256 * 1024 * 1024;
14const DEFAULT_MESSAGE_BUFFER_LIMIT: usize = 64 * 1024 * 1024;
15const DEFAULT_RAFT_LOG_CACHE_LIMIT: usize = 32 * 1024 * 1024;
16const DEFAULT_CONNECTION_POOL_LIMIT: usize = 16 * 1024 * 1024;
17
18#[derive(Debug, Clone, Copy, PartialEq, Eq)]
19pub enum MemoryComponent {
20 MessageBuffer,
21 RaftLogCache,
22 ConnectionPool,
23 BlockCache,
24}
25
26#[derive(Debug, Clone, Copy, PartialEq)]
27pub struct MemoryConfig {
28 pub total_budget: usize,
29 pub message_buffer_limit: usize,
30 pub raft_log_cache_limit: usize,
31 pub connection_pool_limit: usize,
32 pub backpressure_threshold: f32,
33 pub emergency_threshold: f32,
34}
35
36impl Default for MemoryConfig {
37 fn default() -> Self {
38 Self {
39 total_budget: DEFAULT_TOTAL_BUDGET,
40 message_buffer_limit: DEFAULT_MESSAGE_BUFFER_LIMIT,
41 raft_log_cache_limit: DEFAULT_RAFT_LOG_CACHE_LIMIT,
42 connection_pool_limit: DEFAULT_CONNECTION_POOL_LIMIT,
43 backpressure_threshold: 0.80,
44 emergency_threshold: 0.95,
45 }
46 }
47}
48
49#[derive(Debug, Error, PartialEq)]
50pub enum MemoryError {
51 #[error("memory total_budget must be greater than zero")]
52 ZeroBudget,
53 #[error("memory threshold must be finite and between zero and one")]
54 InvalidThreshold,
55 #[error("memory component limit exceeds total budget")]
56 ComponentLimitExceedsBudget,
57 #[error("at least one memory sample is required")]
58 EmptySamples,
59 #[error("allocation ratios must be finite, non-negative, and sum to one")]
60 InvalidAllocationRatio,
61}
62
63#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
64pub struct MemoryStats {
65 pub total_budget: usize,
66 pub current_usage: usize,
67 pub message_buffer_bytes: usize,
68 pub raft_log_cache_bytes: usize,
69 pub connection_pool_bytes: usize,
70 pub block_cache_bytes: usize,
71 pub budget_exceeded: bool,
72}
73
74#[derive(Debug, Clone, Copy, PartialEq)]
76pub struct AllocationRatio {
77 pub message_buffer: f32,
78 pub raft_cache: f32,
79 pub connection_pool: f32,
80 pub block_cache: f32,
81}
82
83impl Default for AllocationRatio {
84 fn default() -> Self {
85 Self {
86 message_buffer: 0.30,
87 raft_cache: 0.20,
88 connection_pool: 0.10,
89 block_cache: 0.40,
90 }
91 }
92}
93
94impl AllocationRatio {
95 fn validate(self) -> Result<(), MemoryError> {
96 let values = [
97 self.message_buffer,
98 self.raft_cache,
99 self.connection_pool,
100 self.block_cache,
101 ];
102 if values
103 .iter()
104 .any(|value| !value.is_finite() || *value < 0.0)
105 || (values.iter().sum::<f32>() - 1.0).abs() > 0.0001
106 {
107 return Err(MemoryError::InvalidAllocationRatio);
108 }
109 Ok(())
110 }
111}
112
113pub type WorkloadProfile = AllocationRatio;
115
116#[derive(Debug)]
118pub struct RaftLogCache {
119 max_bytes: usize,
120 used_bytes: usize,
121 entries: VecDeque<(u64, Vec<u8>)>,
122}
123
124impl RaftLogCache {
125 pub fn new(max_bytes: usize) -> Self {
126 Self {
127 max_bytes,
128 used_bytes: 0,
129 entries: VecDeque::new(),
130 }
131 }
132
133 pub fn insert(&mut self, index: u64, payload: Vec<u8>) {
134 if let Some(position) = self.entries.iter().position(|(key, _)| *key == index)
135 && let Some((_, old)) = self.entries.remove(position)
136 {
137 self.used_bytes = self.used_bytes.saturating_sub(old.len());
138 }
139 if payload.len() > self.max_bytes {
140 return;
141 }
142 while self.used_bytes.saturating_add(payload.len()) > self.max_bytes {
143 let Some((_, evicted)) = self.entries.pop_front() else {
144 break;
145 };
146 self.used_bytes = self.used_bytes.saturating_sub(evicted.len());
147 }
148 self.used_bytes = self.used_bytes.saturating_add(payload.len());
149 self.entries.push_back((index, payload));
150 }
151
152 pub fn get(&mut self, index: u64) -> Option<Vec<u8>> {
153 let position = self.entries.iter().position(|(key, _)| *key == index)?;
154 let entry = self.entries.remove(position)?;
155 let payload = entry.1.clone();
156 self.entries.push_back(entry);
157 Some(payload)
158 }
159
160 pub fn used_bytes(&self) -> usize {
161 self.used_bytes
162 }
163
164 fn evict_bytes(&mut self, target_bytes: usize) -> usize {
165 let mut evicted = 0;
166 while evicted < target_bytes {
167 let Some((_, payload)) = self.entries.pop_front() else {
168 break;
169 };
170 evicted = evicted.saturating_add(payload.len());
171 self.used_bytes = self.used_bytes.saturating_sub(payload.len());
172 }
173 evicted
174 }
175}
176
177#[derive(Debug)]
184pub struct BlockCacheHandle {
185 capacity: AtomicUsize,
186 used_bytes: AtomicUsize,
187}
188
189impl BlockCacheHandle {
190 pub fn new(capacity: usize) -> Self {
191 Self {
192 capacity: AtomicUsize::new(capacity),
193 used_bytes: AtomicUsize::new(0),
194 }
195 }
196
197 pub fn set_capacity(&self, capacity: usize) {
198 self.capacity.store(capacity, Ordering::Relaxed);
199 }
200
201 pub fn capacity(&self) -> usize {
202 self.capacity.load(Ordering::Relaxed)
203 }
204
205 pub fn set_used_bytes(&self, bytes: usize) {
206 self.used_bytes.store(bytes, Ordering::Relaxed);
207 }
208
209 pub fn used_bytes(&self) -> usize {
210 self.used_bytes.load(Ordering::Relaxed)
211 }
212
213 fn evict_bytes(&self, target_bytes: usize) -> usize {
214 loop {
215 let current = self.used_bytes();
216 let evicted = current.min(target_bytes);
217 if self
218 .used_bytes
219 .compare_exchange(
220 current,
221 current - evicted,
222 Ordering::Relaxed,
223 Ordering::Relaxed,
224 )
225 .is_ok()
226 {
227 return evicted;
228 }
229 }
230 }
231}
232
233#[derive(Debug, Clone, Copy, PartialEq, Eq)]
234pub struct UnifiedMemoryMetrics {
235 pub total_budget: usize,
236 pub current_usage: usize,
237 pub message_buffer_bytes: usize,
238 pub raft_cache_bytes: usize,
239 pub connection_pool_bytes: usize,
240 pub block_cache_bytes: usize,
241 pub evicted_bytes: usize,
242}
243
244#[derive(Debug, Clone, Copy, PartialEq, Eq)]
246pub struct MemoryMeasurement {
247 pub rss_bytes: Option<u64>,
248 pub cgroup_current_bytes: Option<u64>,
249 pub cgroup_limit_bytes: Option<u64>,
250}
251
252impl MemoryMeasurement {
253 pub const fn from_values(
254 rss_bytes: Option<u64>,
255 cgroup_current_bytes: Option<u64>,
256 cgroup_limit_bytes: Option<u64>,
257 ) -> Self {
258 Self {
259 rss_bytes,
260 cgroup_current_bytes,
261 cgroup_limit_bytes,
262 }
263 }
264
265 pub fn observed_bytes(self) -> Option<u64> {
267 self.rss_bytes.or(self.cgroup_current_bytes)
268 }
269
270 pub fn capture() -> Self {
272 let rss_bytes = std::fs::read_to_string("/proc/self/status")
273 .ok()
274 .and_then(|status| {
275 status.lines().find_map(|line| {
276 let value = line.strip_prefix("VmRSS:")?.split_whitespace().next()?;
277 value
278 .parse::<u64>()
279 .ok()
280 .map(|kib| kib.saturating_mul(1024))
281 })
282 });
283 let cgroup_current_bytes = read_first_number(&[
284 "/sys/fs/cgroup/memory.current",
285 "/sys/fs/cgroup/memory/memory.usage_in_bytes",
286 ]);
287 let cgroup_limit_bytes = read_first_number(&[
288 "/sys/fs/cgroup/memory.max",
289 "/sys/fs/cgroup/memory/memory.limit_in_bytes",
290 ]);
291 Self::from_values(rss_bytes, cgroup_current_bytes, cgroup_limit_bytes)
292 }
293}
294
295#[derive(Debug, Clone, Copy, PartialEq, Eq)]
297pub struct MemoryStabilityReport {
298 pub peak_bytes: u64,
299 pub final_bytes: u64,
300 pub growth_bytes: u64,
301 pub within_budget: bool,
302 pub stable: bool,
303}
304
305impl MemoryStabilityReport {
306 pub fn from_samples(
307 samples: &[MemoryMeasurement],
308 budget_bytes: u64,
309 allowed_growth_bytes: u64,
310 ) -> Result<Self, MemoryError> {
311 let values: Vec<u64> = samples
312 .iter()
313 .copied()
314 .filter_map(MemoryMeasurement::observed_bytes)
315 .collect();
316 let Some((&first, rest)) = values.split_first() else {
317 return Err(MemoryError::EmptySamples);
318 };
319 let peak_bytes = values.iter().copied().max().unwrap_or(first);
320 let final_bytes = values.last().copied().unwrap_or(first);
321 let growth_bytes = final_bytes.saturating_sub(first);
322 Ok(Self {
323 peak_bytes,
324 final_bytes,
325 growth_bytes,
326 within_budget: peak_bytes <= budget_bytes,
327 stable: growth_bytes <= allowed_growth_bytes
328 && rest
329 .iter()
330 .all(|value| value.saturating_sub(first) <= allowed_growth_bytes),
331 })
332 }
333}
334
335fn read_first_number(paths: &[&str]) -> Option<u64> {
336 paths.iter().find_map(|path| {
337 let value = std::fs::read_to_string(path).ok()?.trim().to_owned();
338 if value == "max" {
339 None
340 } else {
341 value.parse().ok()
342 }
343 })
344}
345
346#[derive(Debug)]
347pub struct MemoryManager {
348 state: Mutex<MemoryState>,
349}
350
351#[derive(Debug)]
352struct MemoryState {
353 config: MemoryConfig,
354 stats: MemoryStats,
355}
356
357impl MemoryManager {
358 pub fn new(config: MemoryConfig) -> Result<Self, MemoryError> {
359 validate_config(&config)?;
360 Ok(Self {
361 state: Mutex::new(MemoryState {
362 stats: MemoryStats {
363 total_budget: config.total_budget,
364 ..MemoryStats::default()
365 },
366 config,
367 }),
368 })
369 }
370
371 pub fn resize_memory_budget(&self, new_budget: usize) -> Result<(), MemoryError> {
372 if new_budget == 0 {
373 return Err(MemoryError::ZeroBudget);
374 }
375 let mut state = self.state.lock().expect("memory manager lock poisoned");
376 state.config.total_budget = new_budget;
377 state.stats.total_budget = new_budget;
378 refresh_budget_flag(&mut state.stats);
379 Ok(())
380 }
381
382 pub fn set_component_usage(&self, component: MemoryComponent, bytes: usize) {
383 let mut state = self.state.lock().expect("memory manager lock poisoned");
384 let slot = match component {
385 MemoryComponent::MessageBuffer => &mut state.stats.message_buffer_bytes,
386 MemoryComponent::RaftLogCache => &mut state.stats.raft_log_cache_bytes,
387 MemoryComponent::ConnectionPool => &mut state.stats.connection_pool_bytes,
388 MemoryComponent::BlockCache => &mut state.stats.block_cache_bytes,
389 };
390 *slot = bytes;
391 refresh_budget_flag(&mut state.stats);
392 }
393
394 fn set_component_limit(&self, component: MemoryComponent, bytes: usize) {
395 let mut state = self.state.lock().expect("memory manager lock poisoned");
396 let limit = bytes.min(state.config.total_budget);
397 match component {
398 MemoryComponent::MessageBuffer => state.config.message_buffer_limit = limit,
399 MemoryComponent::RaftLogCache => state.config.raft_log_cache_limit = limit,
400 MemoryComponent::ConnectionPool => state.config.connection_pool_limit = limit,
401 MemoryComponent::BlockCache => {}
402 }
403 }
404
405 pub fn trigger_gc(&self) -> Result<(), MemoryError> {
409 let mut state = self.state.lock().expect("memory manager lock poisoned");
410 state.stats.message_buffer_bytes = state
411 .stats
412 .message_buffer_bytes
413 .min(state.config.message_buffer_limit);
414 state.stats.raft_log_cache_bytes = state
415 .stats
416 .raft_log_cache_bytes
417 .min(state.config.raft_log_cache_limit);
418 state.stats.connection_pool_bytes = state
419 .stats
420 .connection_pool_bytes
421 .min(state.config.connection_pool_limit);
422 let fixed = state
423 .stats
424 .message_buffer_bytes
425 .saturating_add(state.stats.raft_log_cache_bytes)
426 .saturating_add(state.stats.connection_pool_bytes);
427 let remaining = state.config.total_budget.saturating_sub(fixed);
428 state.stats.block_cache_bytes = state.stats.block_cache_bytes.min(remaining);
429 refresh_budget_flag(&mut state.stats);
430 Ok(())
431 }
432
433 pub fn get_memory_stats(&self) -> MemoryStats {
434 self.state
435 .lock()
436 .expect("memory manager lock poisoned")
437 .stats
438 }
439}
440
441pub struct IntegratedCacheManager {
444 pub message_buffer: MessageBuffer,
445 pub raft_cache: RaftLogCache,
446 pub block_cache: Arc<BlockCacheHandle>,
447 pub total_budget: usize,
448 pub allocation_ratio: AllocationRatio,
449 memory: Arc<MemoryManager>,
450 connection_pool_bytes: usize,
451 evicted_bytes: usize,
452}
453
454impl IntegratedCacheManager {
455 pub fn new(config: MemoryConfig) -> Result<Self, MemoryError> {
456 Self::with_block_cache(
457 config,
458 Arc::new(BlockCacheHandle::new(
459 (config.total_budget as f32 * AllocationRatio::default().block_cache) as usize,
460 )),
461 )
462 }
463
464 pub fn with_block_cache(
465 config: MemoryConfig,
466 block_cache: Arc<BlockCacheHandle>,
467 ) -> Result<Self, MemoryError> {
468 let memory = Arc::new(MemoryManager::new(config)?);
469 let allocation_ratio = AllocationRatio::default();
470 allocation_ratio.validate()?;
471 let manager = Self {
472 message_buffer: MessageBuffer::new(
473 config.message_buffer_limit,
474 config.backpressure_threshold,
475 config.emergency_threshold,
476 ),
477 raft_cache: RaftLogCache::new(config.raft_log_cache_limit),
478 block_cache,
479 total_budget: config.total_budget,
480 allocation_ratio,
481 memory,
482 connection_pool_bytes: 0,
483 evicted_bytes: 0,
484 };
485 manager.sync_usage();
486 Ok(manager)
487 }
488
489 pub fn rebalance(&mut self, workload: WorkloadProfile) {
493 if workload.validate().is_err() {
494 return;
495 }
496 self.allocation_ratio = workload;
497 let budget = self.total_budget;
498 let message_limit = (budget as f32 * workload.message_buffer) as usize;
499 let raft_limit = (budget as f32 * workload.raft_cache) as usize;
500 let connection_limit = (budget as f32 * workload.connection_pool) as usize;
501 let block_limit = (budget as f32 * workload.block_cache) as usize;
502 self.memory
503 .set_component_limit(MemoryComponent::MessageBuffer, message_limit);
504 self.memory
505 .set_component_limit(MemoryComponent::RaftLogCache, raft_limit);
506 self.memory
507 .set_component_limit(MemoryComponent::ConnectionPool, connection_limit);
508 self.block_cache.set_capacity(block_limit);
509 self.evicted_bytes = self.evicted_bytes.saturating_add(
510 self.evict_message_bytes(
511 self.message_buffer
512 .used_bytes()
513 .saturating_sub(message_limit),
514 ),
515 );
516 self.evicted_bytes = self.evicted_bytes.saturating_add(
517 self.raft_cache
518 .evict_bytes(self.raft_cache.used_bytes().saturating_sub(raft_limit)),
519 );
520 self.evicted_bytes = self.evicted_bytes.saturating_add(
521 self.block_cache
522 .evict_bytes(self.block_cache.used_bytes().saturating_sub(block_limit)),
523 );
524 self.sync_usage();
525 }
526
527 pub fn emergency_evict(&mut self, target_bytes: usize) -> usize {
530 let mut remaining = target_bytes;
531 let mut evicted = 0;
532 let released = self.block_cache.evict_bytes(remaining);
533 remaining = remaining.saturating_sub(released);
534 evicted += released;
535 if remaining > 0 {
536 let released = self.raft_cache.evict_bytes(remaining);
537 remaining = remaining.saturating_sub(released);
538 evicted += released;
539 }
540 if remaining > 0 {
541 evicted += self.evict_message_bytes(remaining);
542 }
543 self.evicted_bytes = self.evicted_bytes.saturating_add(evicted);
544 self.sync_usage();
545 evicted
546 }
547
548 pub fn get_unified_metrics(&self) -> UnifiedMemoryMetrics {
549 self.sync_usage();
550 let stats = self.memory.get_memory_stats();
551 UnifiedMemoryMetrics {
552 total_budget: stats.total_budget,
553 current_usage: stats.current_usage,
554 message_buffer_bytes: stats.message_buffer_bytes,
555 raft_cache_bytes: stats.raft_log_cache_bytes,
556 connection_pool_bytes: stats.connection_pool_bytes,
557 block_cache_bytes: stats.block_cache_bytes,
558 evicted_bytes: self.evicted_bytes,
559 }
560 }
561
562 pub fn set_connection_pool_usage(&mut self, bytes: usize) {
565 self.connection_pool_bytes = bytes;
566 self.sync_usage();
567 }
568
569 pub fn memory_manager(&self) -> Arc<MemoryManager> {
570 Arc::clone(&self.memory)
571 }
572
573 fn evict_message_bytes(&mut self, target_bytes: usize) -> usize {
574 let mut evicted = 0;
575 while evicted < target_bytes {
576 let Some(message) = self.message_buffer.pop() else {
577 break;
578 };
579 evicted = evicted.saturating_add(message.payload.len());
580 }
581 evicted
582 }
583
584 fn sync_usage(&self) {
585 self.memory.set_component_usage(
586 MemoryComponent::MessageBuffer,
587 self.message_buffer.used_bytes(),
588 );
589 self.memory
590 .set_component_usage(MemoryComponent::RaftLogCache, self.raft_cache.used_bytes());
591 self.memory
592 .set_component_usage(MemoryComponent::ConnectionPool, self.connection_pool_bytes);
593 self.memory
594 .set_component_usage(MemoryComponent::BlockCache, self.block_cache.used_bytes());
595 }
596}
597
598fn validate_config(config: &MemoryConfig) -> Result<(), MemoryError> {
599 if config.total_budget == 0 {
600 return Err(MemoryError::ZeroBudget);
601 }
602 if config.message_buffer_limit > config.total_budget
603 || config.raft_log_cache_limit > config.total_budget
604 || config.connection_pool_limit > config.total_budget
605 {
606 return Err(MemoryError::ComponentLimitExceedsBudget);
607 }
608 if !config.backpressure_threshold.is_finite()
609 || !config.emergency_threshold.is_finite()
610 || !(0.0..=1.0).contains(&config.backpressure_threshold)
611 || !(0.0..=1.0).contains(&config.emergency_threshold)
612 || config.backpressure_threshold > config.emergency_threshold
613 {
614 return Err(MemoryError::InvalidThreshold);
615 }
616 Ok(())
617}
618
619fn refresh_budget_flag(stats: &mut MemoryStats) {
620 stats.current_usage = stats
621 .message_buffer_bytes
622 .saturating_add(stats.raft_log_cache_bytes)
623 .saturating_add(stats.connection_pool_bytes)
624 .saturating_add(stats.block_cache_bytes);
625 stats.budget_exceeded = stats.current_usage > stats.total_budget;
626}