diff --git a/src/backends/qemu/client.rs b/src/backends/qemu/client.rs index 5dfb862..b185ed4 100644 --- a/src/backends/qemu/client.rs +++ b/src/backends/qemu/client.rs @@ -36,6 +36,7 @@ use crate::{ }, }, instance::{InstanceClient, ThreadPoolSnapshot}, + util::ProcessExt, }; /// Wire client for an QEMU-managed VM. @@ -261,7 +262,7 @@ impl InstanceClient for QemuInstanceClient { "qemu topology reported no iothread TIDs".into(), )); } - let pid = Process::new(first_tid)? + let pid = Process::from_pid(first_tid)? .status() .map_err(|e| BackendClientError::InvalidState(format!("pid resolution: {e}")))? .tgid; diff --git a/src/backends/qemu/helpers.rs b/src/backends/qemu/helpers.rs index cc66ab5..776e32c 100644 --- a/src/backends/qemu/helpers.rs +++ b/src/backends/qemu/helpers.rs @@ -29,7 +29,10 @@ use regex::Regex; use rustix::param; use thiserror::Error; -use crate::backends::{BackendClientError, VqMapping}; +use crate::{ + backends::{BackendClientError, VqMapping}, + util::ProcessExt, +}; /// Errors produced while querying or reconfiguring a QEMU vm. #[derive(Debug, Error)] @@ -229,7 +232,7 @@ impl CpuSample { wall: Instant::now(), }; - let p = Process::new(pid)?; + let p = Process::from_pid(pid)?; for &tid in iothread_tids.values() { let stat = p.task_from_tid(tid)?.stat()?; out.jiffies.insert(tid, stat.utime + stat.stime); diff --git a/src/controller.rs b/src/controller.rs index 8c9de7a..668fb40 100644 --- a/src/controller.rs +++ b/src/controller.rs @@ -12,7 +12,9 @@ use std::{ }; use futures_util::future::join_all; -use procfs::{CpuPressure, Current, CurrentSI, IoPressure, MemoryPressure, ProcError}; +use procfs::{ + CpuPressure, Current, FromReadSI, IoPressure, KernelStats, MemoryPressure, ProcError, +}; use serde::{Deserialize, Serialize}; use statistical::median; use thiserror::Error; @@ -27,6 +29,7 @@ use crate::{ instance::{CgroupError, Instance, InstanceStatus, SnapshotLatency}, per_vm_log, state::{StateError, VmOwnership, VmStateStore}, + util::Path, }; /// Wire-facing container for the D-Bus `GetSnapshot` reply. #[derive(Debug, Clone, Default, Serialize, Deserialize)] @@ -1008,7 +1011,9 @@ fn read_psi_sample() -> PsiSample { /// Read aggregate host CPU counters. fn read_host_cpu_sample() -> Result { - let total = procfs::KernelStats::current()?.total; + let current_si = procfs::current_system_info(); + let total = KernelStats::from_file(Path::new("/proc/stat"), current_si)?.total; + let idle_ticks = total.idle.saturating_add(total.iowait.unwrap_or(0)); let total_ticks = total .user diff --git a/src/instance.rs b/src/instance.rs index 9eed3dc..4a44322 100644 --- a/src/instance.rs +++ b/src/instance.rs @@ -27,7 +27,7 @@ use tokio::sync::RwLock; use crate::{ backends::{BackendClientError, IoThreadProperties, VqMapping}, rolling::RollingMetrics, - util::Path, + util::{Path, ProcessExt}, }; #[derive(Debug, Error)] @@ -647,7 +647,7 @@ fn read_cpu_sample( pid: i32, filter: &crate::instance::ThreadNameFilter, ) -> Result { - let process = Process::new(pid)?; + let process = Process::from_pid(pid)?; let tasks = process.tasks()?; let mut cpu_ticks = 0u64; let mut thread_count = 0u32; @@ -702,7 +702,7 @@ fn read_cpu_sample( /// Read cgroup v2 CPU quota and throttle counters for `pid`. fn read_cgroup_sample(pid: i32) -> Result { - let process = Process::new(pid)?; + let process = Process::from_pid(pid)?; let cgroups = process.cgroups()?; let path = cgroups .0 diff --git a/src/util.rs b/src/util.rs index 49667f2..9ff211b 100644 --- a/src/util.rs +++ b/src/util.rs @@ -142,6 +142,22 @@ macro_rules! path { }; } +pub(crate) trait ProcessExt { + fn from_pid(pid: i32) -> procfs::ProcResult; +} + +impl ProcessExt for procfs::process::Process { + /// `/proc/` after applying [`IO_THREAD_CONTROLLER_ROOT_PATH`]. + /// + /// `procfs::process::Process::new` always opens the real `/proc`. Callers + /// that should honor the mock root pass this to create the process. + fn from_pid(pid: i32) -> procfs::ProcResult { + let path = Path::new(&format!("/proc/{pid}")); + let path = std::path::PathBuf::from(std::path::Path::as_os_str(&path)); + Self::new_with_root(path) + } +} + #[cfg(test)] mod tests { use super::*;