cbtop/bricks/collectors/
memory.rs1use crate::brick::{Brick, BrickAssertion, BrickBudget, BrickVerification};
6use crate::ring_buffer::RingBuffer;
7use std::any::Any;
8use std::fs::read_to_string;
9use std::time::Instant;
10
11#[derive(Debug, Clone)]
12pub struct MemoryMetrics {
13 pub timestamp: Instant,
14 pub total_kb: u64,
15 pub available_kb: u64,
16 pub free_kb: u64,
17 pub swap_total_kb: u64,
18 pub swap_free_kb: u64,
19}
20
21impl Default for MemoryMetrics {
22 fn default() -> Self {
23 Self {
24 timestamp: Instant::now(),
25 total_kb: 0,
26 available_kb: 0,
27 free_kb: 0,
28 swap_total_kb: 0,
29 swap_free_kb: 0,
30 }
31 }
32}
33
34pub struct MemoryCollectorBrick {
35 history: RingBuffer<MemoryMetrics>,
36}
37
38impl MemoryCollectorBrick {
39 pub fn new() -> Self {
40 Self {
41 history: RingBuffer::new(120),
42 }
43 }
44
45 pub fn collect(&mut self) -> MemoryMetrics {
46 let metrics = self.read_meminfo().unwrap_or_default();
47 self.history.push(metrics.clone());
48 metrics
49 }
50
51 fn read_meminfo(&self) -> Result<MemoryMetrics, std::io::Error> {
52 let content = read_to_string("/proc/meminfo")?;
53 let mut metrics = MemoryMetrics {
54 timestamp: Instant::now(),
55 ..Default::default()
56 };
57
58 for line in content.lines() {
59 let parts: Vec<&str> = line.split_whitespace().collect();
60 if parts.len() < 2 {
61 continue;
62 }
63
64 let value = parts[1].parse::<u64>().unwrap_or(0);
65 match parts[0] {
66 "MemTotal:" => metrics.total_kb = value,
67 "MemAvailable:" => metrics.available_kb = value,
68 "MemFree:" => metrics.free_kb = value,
69 "SwapTotal:" => metrics.swap_total_kb = value,
70 "SwapFree:" => metrics.swap_free_kb = value,
71 _ => {}
72 }
73 }
74 Ok(metrics)
75 }
76
77 pub fn history(&self) -> &RingBuffer<MemoryMetrics> {
78 &self.history
79 }
80}
81
82impl Brick for MemoryCollectorBrick {
83 fn brick_name(&self) -> &'static str {
84 "memory_collector"
85 }
86
87 fn assertions(&self) -> Vec<BrickAssertion> {
88 vec![
89 BrickAssertion::custom("mem_total_positive", |b| {
90 let s = b
91 .downcast_ref::<MemoryCollectorBrick>()
92 .expect("brick MUST be MemoryCollectorBrick");
93 s.history.back().map_or(true, |m| m.total_kb > 0)
94 }),
95 BrickAssertion::max_latency_ms(2),
96 ]
97 }
98
99 fn budget(&self) -> BrickBudget {
100 BrickBudget {
101 collect_ms: 2,
102 layout_ms: 0,
103 render_ms: 0,
104 }
105 }
106
107 fn verify(&self) -> BrickVerification {
108 let mut v = BrickVerification::new();
109 for a in self.assertions() {
110 v.check(&a);
111 }
112 v
113 }
114
115 fn as_any(&self) -> &dyn Any {
116 self
117 }
118}
119
120#[cfg(test)]
121mod tests {
122 use super::*;
123
124 #[test]
125 fn test_memory_collector() {
126 let mut collector = MemoryCollectorBrick::new();
127 let metrics = collector.collect();
128 if std::path::Path::new("/proc/meminfo").exists() {
131 assert!(metrics.total_kb > 0);
132 } else {
133 assert!(metrics.total_kb == 0);
135 }
136 }
137}