Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,12 @@ through `virDomainQemuMonitorCommand`. Its configuration is loaded from
`backends.d/qemu.json`. Named IOThreads and virtqueue mappings can be inspected
or changed through the backend CLI and D-Bus operations.

## Experimental PSI monitoring

With `experimental_psi_monitoring` enabled, each tick samples the CPU, I/O, and
memory `some.avg10` values from `/proc/pressure` and exposes them to the active
scaling engine. Sampling is disabled by default.

## Status line

Every tick the daemon emits one INFO line per tracked VM and one aggregate line
Expand Down
3 changes: 2 additions & 1 deletion io-thread-controller.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,6 @@
"backend_config_dir": "/etc/io-thread-controller.d/backends",
"vm_state_path": "/run/io-thread-controller/vm-ownership.json",
"dry_run": false,
"max_instances_adjusted_per_poll": 0
"max_instances_adjusted_per_poll": 0,
"experimental_psi_monitoring": false
}
4 changes: 4 additions & 0 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,9 @@ pub struct Config {
/// the cap.
#[serde(default = "default_max_instances_adjusted_per_poll")]
pub max_instances_adjusted_per_poll: u32,
/// every tick and exposing it to the active engine.
#[serde(default)]
pub experimental_psi_monitoring: bool,
/// When true, log the scaling verdict but skip the actuation
/// call to `set_thread_count`.
#[serde(default)]
Expand Down Expand Up @@ -140,6 +143,7 @@ impl Default for Config {
enable_aggregate_status_line: true,
print_status_header: false,
max_instances_adjusted_per_poll: default_max_instances_adjusted_per_poll(),
experimental_psi_monitoring: false,
dry_run: false,
}
}
Expand Down
28 changes: 27 additions & 1 deletion src/controller.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,9 @@ use crate::{
backends::{BackendClientError, IoThreadProperties, VqMapping},
config::Config,
dbus::DbusRequest,
engines::{AppliedOutcome, BlockedReason, EngineTickContext, ScaleAction, ScalingEngine},
engines::{
AppliedOutcome, BlockedReason, EngineTickContext, PsiSample, ScaleAction, ScalingEngine,
},
instance::{Instance, InstanceStatus},
rolling::format_1_5_15,
state::{StateError, VmOwnership, VmStateStore},
Expand Down Expand Up @@ -199,6 +201,8 @@ pub struct Controller {
last_host_cpu: Option<HostCpuSample>,
/// Fraction of host CPU consumed since the previous tick.
host_cpu_util: f64,
/// Latest host pressure-stall snapshot.
psi: PsiSample,
}

impl Controller {
Expand All @@ -215,6 +219,7 @@ impl Controller {
tick_index: 0,
last_host_cpu: None,
host_cpu_util: 0.0,
psi: PsiSample::default(),
})
}

Expand Down Expand Up @@ -475,11 +480,15 @@ impl Controller {
}
self.last_host_cpu = Some(current);
}
if self.cfg.experimental_psi_monitoring {
self.psi = read_psi_sample();
}
let context = EngineTickContext {
now: Instant::now(),
min_thread_count: self.cfg.min_thread_count,
max_thread_count: self.cfg.max_thread_count,
host_cpu_util: self.host_cpu_util,
psi: self.psi,
tick_index: self.tick_index,
};
let fleet: Vec<_> = self.instances.values().cloned().collect();
Expand Down Expand Up @@ -822,6 +831,23 @@ impl Controller {
}
}

/// Read CPU, I/O, and memory pressure; unavailable values become zero.
fn read_psi_sample() -> PsiSample {
use procfs::{CpuPressure, Current, IoPressure, MemoryPressure};

PsiSample {
cpu_some_avg10: CpuPressure::current()
.map(|pressure| pressure.some.avg10 as f64)
.unwrap_or(0.0),
io_some_avg10: IoPressure::current()
.map(|pressure| pressure.some.avg10 as f64)
.unwrap_or(0.0),
memory_some_avg10: MemoryPressure::current()
.map(|pressure| pressure.some.avg10 as f64)
.unwrap_or(0.0),
}
}

/// Read aggregate host CPU counters.
fn read_host_cpu_sample() -> Result<HostCpuSample, ControllerError> {
let total = procfs::KernelStats::current()?.total;
Expand Down
13 changes: 13 additions & 0 deletions src/engines/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,17 @@ pub enum EngineError {
NoSuchEngine(String),
}

/// Host pressure-stall information sampled from `/proc/pressure`.
#[derive(Debug, Default, Clone, Copy)]
pub struct PsiSample {
/// CPU `some.avg10` percentage.
pub cpu_some_avg10: f64,
/// I/O `some.avg10` percentage.
pub io_some_avg10: f64,
/// Memory `some.avg10` percentage.
pub memory_some_avg10: f64,
}

/// Scaling operation selected by an engine.
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
pub enum ScaleAction {
Expand Down Expand Up @@ -88,6 +99,8 @@ pub struct EngineTickContext {
/// Host-wide CPU utilisation (0.0-1.0) from successive `/proc/stat`
/// samples; zero until two samples exist.
pub host_cpu_util: f64,
/// Latest host pressure-stall snapshot.
pub psi: PsiSample,
/// Monotonically increasing tick sequence number.
pub tick_index: u64,
}
Expand Down
3 changes: 2 additions & 1 deletion src/engines/threshold.rs
Original file line number Diff line number Diff line change
Expand Up @@ -498,7 +498,7 @@ mod tests {
use super::{ThresholdConfig, ThresholdEngine, log_performance_revert};
use crate::{
backends::BackendClientError,
engines::{AppliedOutcome, EngineTickContext, ScaleAction, ScalingEngine},
engines::{AppliedOutcome, EngineTickContext, PsiSample, ScaleAction, ScalingEngine},
instance::{
Instance, InstanceClient, InstancePerfSample, InstanceStatus, ThreadPoolSnapshot,
},
Expand Down Expand Up @@ -561,6 +561,7 @@ mod tests {
min_thread_count: 2,
max_thread_count: 8,
host_cpu_util: 0.0,
psi: PsiSample::default(),
tick_index: 0,
}
}
Expand Down
Loading