Bug description
The qemu exporter writes ~5× less energy to the virtiofs energy_uj file than it should, causing scaph_host_power_microwatts inside the VM to report ~5× lower than the real power consumption of the VM.
The root cause is in src/exporters/qemu.rs. The main loop runs every 5 seconds and computes the energy to add to the virtiofs counter as:
let uj_to_add = ratio.value.parse::<f64>().unwrap()
* topo_energy.value.parse::<f64>().unwrap()
/ 100.0;
topo_energy is the return value of topology.get_records_diff_power_microwatts(), which is correctly documented and implemented — it returns instantaneous power in µW (µJ/s), not accumulated energy:
/// Returns a Record instance containing the power consumed between
/// last and previous measurement, in microwatts.
pub fn get_records_diff_power_microwatts(&self) -> Option<Record> {
...
let microwatts = microjoules as f64 / time_diff;
return Some(Record::new(..., (microwatts as u64).to_string(), units::Unit::MicroWatt));
}
The bug is in the caller — the qemu exporter uses this µW value as if it were µJ and adds it directly to energy_uj without converting. To convert power to energy over one loop iteration, the result must be multiplied by the step duration in seconds:
energy (µJ) = power (µW) × time (s)
Without this factor, each 5-second cycle adds only ~1 second worth of energy to the counter instead of 5 seconds. The virtiofs counter grows 5× slower than it should, and Scaphandre inside the VM reads that counter and reports ~5× lower than the real power consumption.
The proposed fix is a single-line change in iterate():
// current (incorrect):
let uj_to_add = ratio.value.parse::<f64>().unwrap()
* topo_energy.value.parse::<f64>().unwrap()
/ 100.0;
// corrected:
let uj_to_add = ratio.value.parse::<f64>().unwrap()
* topo_energy.value.parse::<f64>().unwrap()
/ 100.0
* step_secs; // step_secs = 5.0 with the current default, passed from run()
To Reproduce
- Run
scaphandre qemu on a hypervisor with at least one running VM and virtiofs configured.
- Run
scaphandre --vm prometheus inside the VM.
- Record
scaph_host_power_microwatts inside the VM.
- On the hypervisor, record
scaph_process_power_consumption_microwatts for the qemu process of the same VM.
- Compare: the value inside the VM will be ~5× lower than the value on the hypervisor.
To verify the root cause directly, measure the virtiofs counter delta over the same time window:
T1_RAPL=$(cat /sys/class/powercap/intel-rapl:0/energy_uj)
T1_VFS=$(cat /var/lib/libvirt/scaphandre/<vm-name>/intel-rapl:0/energy_uj)
sleep 30
T2_RAPL=$(cat /sys/class/powercap/intel-rapl:0/energy_uj)
T2_VFS=$(cat /var/lib/libvirt/scaphandre/<vm-name>/intel-rapl:0/energy_uj)
echo "RAPL delta: $((T2_RAPL - T1_RAPL)) µJ"
echo "VFS delta: $((T2_VFS - T1_VFS)) µJ"
echo "VFS × 5: $(( (T2_VFS-T1_VFS)*5 )) µJ"
VFS × 5 will closely match the energy proportional to the VM's CPU share, while the raw VFS delta will be ~5× too low.
Additionally, writing a known value manually to the virtiofs counter (bypassing the qemu exporter) confirms the VM-side agent reads and computes correctly. Incrementing the counter by 100,000 µW × 5s = 500,000 µJ every 5 seconds produces ~99,360 µW inside the VM (0.64% error, consistent with bash sleep jitter), confirming the bug is entirely in the write side.
Expected behavior
scaph_host_power_microwatts inside the VM should match scaph_process_power_consumption_microwatts of the corresponding qemu process on the hypervisor (within measurement noise). The energy written to the virtiofs counter per loop cycle should equal ratio% × topo_power_µW / 100 × step_seconds.
Screenshots
Single-script measurement run (30 seconds, all values captured simultaneously):
Delta RAPL total (S0+S1+DRAM): 1,793,372,378 µJ in 30s → 59,779,079 µW
scaph_host_power_microwatts (hypervisor): 59,992,820 µW ← correct (0.3% error vs RAPL)
scaph_process_cpu_usage_percentage (qemu): 0.21535%
scaph_process_power_consumption_microwatts (qemu): 129,196 µW ← correct (59,992,820 × 0.21535 / 100 = 129,200 µW)
Delta virtiofs: 754,170 µJ
Expected in virtiofs (30s): 3,875,880 µJ (129,196 µW × 30s)
Actual virtiofs × 5: 3,770,850 µJ ← matches expected within 2.7% ✓
Factor: 5.14×
scaph_host_power_microwatts (inside VM): ~22,000 µW ← reported (~5× too low)
Expected (from hypervisor): ~129,000 µW ← real
The 2.7% residual error after applying ×5 is consistent with scheduler jitter in the 5-second sleep and the non-perfect alignment between our measurement window and the exporter's internal cycle.
Environment
- Linux distribution: hypervisor Ubuntu 22.04 LTS, VM Ubuntu 24.04 LTS
- Kernel version: (standard kernel for each distro)
- Scaphandre version: v1.0.2 on both hypervisor and VM
- Hypervisor CPU: 2 physical sockets, 20 cores/socket, 2 threads/core = 80 logical CPUs
- VM: 8 vCPUs, 16 GB RAM
Additional context
This bug affects all deployments using scaphandre qemu regardless of the number of sockets, since the missing factor is the loop step (5s by default), not a socket count. The step value is hardcoded — if a --step CLI flag is added to the qemu subcommand in the future, passing step.as_secs_f64() into iterate() would make the fix work correctly for any step value without further changes.
The same underreporting affects scaph_process_power_consumption_microwatts for all processes and containers running inside the VM, since container-level power is derived proportionally from scaph_host_power_microwatts.
Bug description
The
qemuexporter writes ~5× less energy to the virtiofsenergy_ujfile than it should, causingscaph_host_power_microwattsinside the VM to report ~5× lower than the real power consumption of the VM.The root cause is in
src/exporters/qemu.rs. The main loop runs every 5 seconds and computes the energy to add to the virtiofs counter as:topo_energyis the return value oftopology.get_records_diff_power_microwatts(), which is correctly documented and implemented — it returns instantaneous power in µW (µJ/s), not accumulated energy:The bug is in the caller — the qemu exporter uses this µW value as if it were µJ and adds it directly to
energy_ujwithout converting. To convert power to energy over one loop iteration, the result must be multiplied by the step duration in seconds:Without this factor, each 5-second cycle adds only ~1 second worth of energy to the counter instead of 5 seconds. The virtiofs counter grows 5× slower than it should, and Scaphandre inside the VM reads that counter and reports ~5× lower than the real power consumption.
The proposed fix is a single-line change in
iterate():To Reproduce
scaphandre qemuon a hypervisor with at least one running VM and virtiofs configured.scaphandre --vm prometheusinside the VM.scaph_host_power_microwattsinside the VM.scaph_process_power_consumption_microwattsfor the qemu process of the same VM.To verify the root cause directly, measure the virtiofs counter delta over the same time window:
VFS × 5will closely match the energy proportional to the VM's CPU share, while the rawVFS deltawill be ~5× too low.Additionally, writing a known value manually to the virtiofs counter (bypassing the qemu exporter) confirms the VM-side agent reads and computes correctly. Incrementing the counter by
100,000 µW × 5s = 500,000 µJevery 5 seconds produces~99,360 µWinside the VM (0.64% error, consistent with bashsleepjitter), confirming the bug is entirely in the write side.Expected behavior
scaph_host_power_microwattsinside the VM should matchscaph_process_power_consumption_microwattsof the corresponding qemu process on the hypervisor (within measurement noise). The energy written to the virtiofs counter per loop cycle should equalratio% × topo_power_µW / 100 × step_seconds.Screenshots
Single-script measurement run (30 seconds, all values captured simultaneously):
The 2.7% residual error after applying ×5 is consistent with scheduler jitter in the 5-second sleep and the non-perfect alignment between our measurement window and the exporter's internal cycle.
Environment
Additional context
This bug affects all deployments using
scaphandre qemuregardless of the number of sockets, since the missing factor is the loop step (5s by default), not a socket count. The step value is hardcoded — if a--stepCLI flag is added to theqemusubcommand in the future, passingstep.as_secs_f64()intoiterate()would make the fix work correctly for any step value without further changes.The same underreporting affects
scaph_process_power_consumption_microwattsfor all processes and containers running inside the VM, since container-level power is derived proportionally fromscaph_host_power_microwatts.