nucleus/resources/
stats.rs1use crate::error::{NucleusError, Result};
2use std::fs;
3use std::path::Path;
4
5#[derive(Debug, Clone)]
7pub struct ResourceStats {
8 pub memory_usage: u64,
10
11 pub memory_limit: u64,
13
14 pub memory_swap_usage: u64,
16
17 pub cpu_usage_ns: u64,
19
20 pub pid_count: u64,
22
23 pub memory_percent: f64,
25}
26
27impl ResourceStats {
28 pub fn from_cgroup(cgroup_path: &str) -> Result<Self> {
30 let cgroup_path = Path::new(cgroup_path);
31
32 let memory_usage = Self::read_memory_current(cgroup_path)?;
34 let memory_limit = Self::read_memory_max(cgroup_path)?;
35
36 let memory_percent = if memory_limit > 0 {
38 (memory_usage as f64 / memory_limit as f64) * 100.0
39 } else {
40 0.0
41 };
42
43 let memory_swap_usage = Self::read_memory_swap(cgroup_path).unwrap_or(0);
45
46 let cpu_usage_ns = Self::read_cpu_usage(cgroup_path)?;
48
49 let pid_count = Self::read_pid_current(cgroup_path)?;
51
52 Ok(Self {
53 memory_usage,
54 memory_limit,
55 memory_swap_usage,
56 cpu_usage_ns,
57 pid_count,
58 memory_percent,
59 })
60 }
61
62 fn read_memory_current(cgroup_path: &Path) -> Result<u64> {
64 let path = cgroup_path.join("memory.current");
65 Self::read_u64_file(&path)
66 }
67
68 fn read_memory_max(cgroup_path: &Path) -> Result<u64> {
70 let path = cgroup_path.join("memory.max");
71 let content = fs::read_to_string(&path).map_err(|e| {
72 NucleusError::ResourceError(format!("Failed to read {:?}: {}", path, e))
73 })?;
74
75 if content.trim() == "max" {
77 Ok(0)
78 } else {
79 content.trim().parse().map_err(|e| {
80 NucleusError::ResourceError(format!("Failed to parse memory.max: {}", e))
81 })
82 }
83 }
84
85 fn read_memory_swap(cgroup_path: &Path) -> Result<u64> {
87 let path = cgroup_path.join("memory.swap.current");
88 Self::read_u64_file(&path)
89 }
90
91 fn read_cpu_usage(cgroup_path: &Path) -> Result<u64> {
93 let path = cgroup_path.join("cpu.stat");
94 let content = fs::read_to_string(&path).map_err(|e| {
95 NucleusError::ResourceError(format!("Failed to read {:?}: {}", path, e))
96 })?;
97
98 for line in content.lines() {
103 if let Some(value_str) = line.strip_prefix("usage_usec ") {
104 let usec: u64 = value_str.parse().map_err(|e| {
105 NucleusError::ResourceError(format!("Failed to parse CPU usage: {}", e))
106 })?;
107 return Ok(usec * 1000);
109 }
110 }
111
112 Err(NucleusError::ResourceError(format!(
113 "cpu.stat at {:?} does not contain 'usage_usec' key",
114 path
115 )))
116 }
117
118 fn read_pid_current(cgroup_path: &Path) -> Result<u64> {
120 let path = cgroup_path.join("pids.current");
121 Self::read_u64_file(&path)
122 }
123
124 fn read_u64_file(path: &Path) -> Result<u64> {
126 let content = fs::read_to_string(path).map_err(|e| {
127 NucleusError::ResourceError(format!("Failed to read {:?}: {}", path, e))
128 })?;
129
130 content
131 .trim()
132 .parse()
133 .map_err(|e| NucleusError::ResourceError(format!("Failed to parse {:?}: {}", path, e)))
134 }
135
136 pub fn format_memory(bytes: u64) -> String {
138 const KB: u64 = 1024;
139 const MB: u64 = KB * 1024;
140 const GB: u64 = MB * 1024;
141
142 if bytes >= GB {
143 format!("{:.2}G", bytes as f64 / GB as f64)
144 } else if bytes >= MB {
145 format!("{:.2}M", bytes as f64 / MB as f64)
146 } else if bytes >= KB {
147 format!("{:.2}K", bytes as f64 / KB as f64)
148 } else {
149 format!("{}B", bytes)
150 }
151 }
152
153 pub fn format_cpu_time(ns: u64) -> String {
155 let seconds = ns as f64 / 1_000_000_000.0;
156 format!("{:.2}s", seconds)
157 }
158}
159
160#[cfg(test)]
161mod tests {
162 use super::*;
163
164 #[test]
165 fn test_format_memory() {
166 assert_eq!(ResourceStats::format_memory(512), "512B");
167 assert_eq!(ResourceStats::format_memory(1024), "1.00K");
168 assert_eq!(ResourceStats::format_memory(1024 * 1024), "1.00M");
169 assert_eq!(ResourceStats::format_memory(1024 * 1024 * 1024), "1.00G");
170 assert_eq!(ResourceStats::format_memory(512 * 1024 * 1024), "512.00M");
171 }
172
173 #[test]
174 fn test_format_cpu_time() {
175 assert_eq!(ResourceStats::format_cpu_time(0), "0.00s");
176 assert_eq!(ResourceStats::format_cpu_time(1_000_000_000), "1.00s");
177 assert_eq!(ResourceStats::format_cpu_time(5_500_000_000), "5.50s");
178 }
179}