hyperi_rustlib/metrics/
container.rs1use std::fs;
12use std::path::Path;
13
14#[derive(Debug, Clone)]
16pub struct ContainerMetrics {
17 namespace: String,
18 cgroup_version: CgroupVersion,
19}
20
21#[derive(Debug, Clone, Copy)]
22enum CgroupVersion {
23 V1,
24 V2,
25 Unknown,
26}
27
28impl ContainerMetrics {
29 #[must_use]
31 pub fn new(namespace: &str) -> Self {
32 let cgroup_version = detect_cgroup_version();
33
34 let this = Self {
35 namespace: namespace.to_string(),
36 cgroup_version,
37 };
38
39 this.register_metrics();
40 this
41 }
42
43 fn register_metrics(&self) {
45 let ns = &self.namespace;
46
47 metrics::describe_gauge!(
48 format!("{ns}_container_memory_limit_bytes"),
49 "Container memory limit in bytes".to_string()
50 );
51 metrics::describe_gauge!(
52 format!("{ns}_container_memory_usage_bytes"),
53 "Container memory usage in bytes".to_string()
54 );
55 metrics::describe_gauge!(
56 format!("{ns}_container_cpu_limit_cores"),
57 "Container CPU limit in cores".to_string()
58 );
59 }
60
61 pub fn update(&self) {
63 let ns = &self.namespace;
64
65 if let Some(limit) = self.read_memory_limit() {
67 metrics::gauge!(format!("{ns}_container_memory_limit_bytes")).set(limit as f64);
68 }
69
70 if let Some(usage) = self.read_memory_usage() {
72 metrics::gauge!(format!("{ns}_container_memory_usage_bytes")).set(usage as f64);
73 }
74
75 if let Some(cores) = self.read_cpu_limit() {
77 metrics::gauge!(format!("{ns}_container_cpu_limit_cores")).set(cores);
78 }
79 }
80
81 fn read_memory_limit(&self) -> Option<u64> {
83 match self.cgroup_version {
84 CgroupVersion::V2 => {
85 read_cgroup_value("/sys/fs/cgroup/memory.max")
87 }
88 CgroupVersion::V1 => {
89 read_cgroup_value("/sys/fs/cgroup/memory/memory.limit_in_bytes")
91 }
92 CgroupVersion::Unknown => None,
93 }
94 }
95
96 fn read_memory_usage(&self) -> Option<u64> {
98 match self.cgroup_version {
99 CgroupVersion::V2 => {
100 read_cgroup_value("/sys/fs/cgroup/memory.current")
102 }
103 CgroupVersion::V1 => {
104 read_cgroup_value("/sys/fs/cgroup/memory/memory.usage_in_bytes")
106 }
107 CgroupVersion::Unknown => None,
108 }
109 }
110
111 fn read_cpu_limit(&self) -> Option<f64> {
113 match self.cgroup_version {
114 CgroupVersion::V2 => {
115 let content = fs::read_to_string("/sys/fs/cgroup/cpu.max").ok()?;
117 parse_cpu_max_v2(&content)
118 }
119 CgroupVersion::V1 => {
120 let quota = read_cgroup_value("/sys/fs/cgroup/cpu/cpu.cfs_quota_us")?;
122 let period = read_cgroup_value("/sys/fs/cgroup/cpu/cpu.cfs_period_us")?;
123
124 if quota == u64::MAX || period == 0 {
125 None
126 } else {
127 Some(quota as f64 / period as f64)
128 }
129 }
130 CgroupVersion::Unknown => None,
131 }
132 }
133}
134
135fn detect_cgroup_version() -> CgroupVersion {
137 if Path::new("/sys/fs/cgroup/cgroup.controllers").exists() {
139 return CgroupVersion::V2;
140 }
141
142 if Path::new("/sys/fs/cgroup/memory/memory.limit_in_bytes").exists() {
144 return CgroupVersion::V1;
145 }
146
147 CgroupVersion::Unknown
148}
149
150fn read_cgroup_value(path: &str) -> Option<u64> {
152 let content = fs::read_to_string(path).ok()?;
153 let trimmed = content.trim();
154
155 if trimmed == "max" {
157 return Some(u64::MAX);
158 }
159
160 trimmed.parse().ok()
161}
162
163fn parse_cpu_max_v2(content: &str) -> Option<f64> {
165 let parts: Vec<&str> = content.split_whitespace().collect();
166 if parts.len() != 2 {
167 return None;
168 }
169
170 let quota = parts[0];
171 let period: u64 = parts[1].parse().ok()?;
172
173 if quota == "max" || period == 0 {
174 return None;
175 }
176
177 let quota_us: u64 = quota.parse().ok()?;
178 Some(quota_us as f64 / period as f64)
179}
180
181#[cfg(test)]
182mod tests {
183 use super::*;
184
185 #[test]
186 fn test_container_metrics_new() {
187 let cm = ContainerMetrics::new("test");
188 assert_eq!(cm.namespace, "test");
189 }
190
191 #[test]
192 fn test_parse_cpu_max_v2() {
193 assert_eq!(parse_cpu_max_v2("100000 100000"), Some(1.0));
194 assert_eq!(parse_cpu_max_v2("50000 100000"), Some(0.5));
195 assert_eq!(parse_cpu_max_v2("max 100000"), None);
196 assert_eq!(parse_cpu_max_v2("invalid"), None);
197 }
198
199 #[test]
200 fn test_container_metrics_update() {
201 let cm = ContainerMetrics::new("test");
202 cm.update();
204 }
205}