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