@@ -242,6 +242,9 @@ impl ProcessCollector { |
| 242 | 242 | let net_listen = net_stats.listen_count; |
| 243 | 243 | let net_established = net_stats.established_count; |
| 244 | 244 | |
| 245 | + // Detect container |
| 246 | + let container = Self::detect_container(proc); |
| 247 | + |
| 245 | 248 | Ok(ProcessInfo { |
| 246 | 249 | pid, |
| 247 | 250 | ppid: stat.ppid, |
@@ -264,9 +267,63 @@ impl ProcessCollector { |
| 264 | 267 | net_tx_rate, |
| 265 | 268 | state, |
| 266 | 269 | user, |
| 270 | + container, |
| 267 | 271 | }) |
| 268 | 272 | } |
| 269 | 273 | |
| 274 | + /// Detect if a process is running in a container. |
| 275 | + /// Returns a short container ID (first 12 chars) if detected. |
| 276 | + fn detect_container(proc: &Process) -> Option<String> { |
| 277 | + // Read cgroup info |
| 278 | + let cgroups = proc.cgroups().ok()?; |
| 279 | + |
| 280 | + for cg in cgroups { |
| 281 | + let path = cg.pathname; |
| 282 | + |
| 283 | + // Docker containers: /docker/<container_id> or /docker/<container_id>/... |
| 284 | + if let Some(rest) = path.strip_prefix("/docker/") { |
| 285 | + let id = rest.split('/').next().unwrap_or(""); |
| 286 | + if id.len() >= 12 { |
| 287 | + return Some(format!("docker:{}", &id[..12])); |
| 288 | + } |
| 289 | + } |
| 290 | + |
| 291 | + // Podman containers: /libpod-<container_id>.scope or similar |
| 292 | + if path.contains("/libpod-") { |
| 293 | + if let Some(start) = path.find("/libpod-") { |
| 294 | + let rest = &path[start + 8..]; |
| 295 | + if let Some(end) = rest.find('.') { |
| 296 | + let id = &rest[..end]; |
| 297 | + if id.len() >= 12 { |
| 298 | + return Some(format!("podman:{}", &id[..12])); |
| 299 | + } |
| 300 | + } |
| 301 | + } |
| 302 | + } |
| 303 | + |
| 304 | + // containerd/k8s: /kubepods/... or cri-containerd-<id> |
| 305 | + if path.contains("/kubepods") { |
| 306 | + // Extract container ID from kubernetes cgroup path |
| 307 | + if let Some(start) = path.rfind('/') { |
| 308 | + let id = &path[start + 1..]; |
| 309 | + if id.len() >= 12 { |
| 310 | + return Some(format!("k8s:{}", &id[..12.min(id.len())])); |
| 311 | + } |
| 312 | + } |
| 313 | + } |
| 314 | + |
| 315 | + // LXC containers: /lxc/<name> |
| 316 | + if let Some(rest) = path.strip_prefix("/lxc/") { |
| 317 | + let name = rest.split('/').next().unwrap_or(""); |
| 318 | + if !name.is_empty() { |
| 319 | + return Some(format!("lxc:{}", name)); |
| 320 | + } |
| 321 | + } |
| 322 | + } |
| 323 | + |
| 324 | + None |
| 325 | + } |
| 326 | + |
| 270 | 327 | /// Kill a process by PID. |
| 271 | 328 | pub fn kill(&self, pid: i32, signal: i32) -> Result<()> { |
| 272 | 329 | use nix::sys::signal::{kill, Signal}; |