use crate::observation::source;
use crate::observation::{
BindContext, Perturbation, Sensor, SensorDescriptor, SensorId, SensorOutcome, StateWriter,
Uncertainty,
};
use corescout_core::cpuset::CpuSet;
use corescout_core::error::{Error, Result};
use corescout_mirror::entity::keys;
use corescout_mirror::relation::{Relation, RelationKind};
use corescout_mirror::state::{ChannelId, Semantics, Unit};
use std::path::PathBuf;
struct Target {
row: u32,
current: PathBuf,
min: PathBuf,
max: PathBuf,
base: PathBuf,
}
pub struct FrequencySensor {
targets: Vec<Target>,
channel_current: Option<ChannelId>,
channel_min: Option<ChannelId>,
channel_max: Option<ChannelId>,
channel_base: Option<ChannelId>,
}
impl FrequencySensor {
pub fn new() -> FrequencySensor {
FrequencySensor {
targets: Vec::new(),
channel_current: None,
channel_min: None,
channel_max: None,
channel_base: None,
}
}
}
impl Default for FrequencySensor {
fn default() -> Self {
Self::new()
}
}
impl Sensor for FrequencySensor {
fn descriptor(&self) -> SensorDescriptor {
SensorDescriptor {
id: SensorId(1),
key: "frequency",
physical_fact: "the clock rate each core is running at, as the cpufreq driver \
reports it",
source: "/sys/devices/system/cpu/cpuN/cpufreq/",
max_rate_hz: 100.0,
perturbation: Perturbation::Low,
uncertainty: Uncertainty::relative(
0.05,
"scaling_cur_freq is the governor's request, not a measurement; the core \
may be at a different point in its ramp, or held lower by a power limit",
),
requires_privilege: false,
}
}
fn bind(&mut self, ctx: &mut BindContext<'_>) -> Result<()> {
let cpu_dir = ctx.substrate().roots.sys.join("devices/system/cpu");
let online: Vec<u32> = ctx
.substrate()
.topology
.logical_cpus
.iter()
.filter(|c| c.online)
.map(|c| c.id)
.collect();
let mut targets = Vec::new();
let mut domains: Vec<(String, Vec<u32>)> = Vec::new();
for cpu in online {
let dir = cpu_dir.join(format!("cpu{cpu}/cpufreq"));
if !dir.exists() {
continue;
}
let Some(row) = ctx.row_of(&keys::logical_cpu(cpu)) else {
continue;
};
targets.push(Target {
row,
current: dir.join("scaling_cur_freq"),
min: dir.join("cpuinfo_min_freq"),
max: dir.join("cpuinfo_max_freq"),
base: dir.join("base_frequency"),
});
if let Some(related) = source::string(dir.join("related_cpus")) {
if let Ok(set) = CpuSet::parse_list(&related) {
let members = set.to_vec();
if members.len() > 1 {
let key = corescout_core::cpuset::format_list(&members);
if !domains.iter().any(|(k, _)| *k == key) {
domains.push((key, members));
}
}
}
}
}
if targets.is_empty() {
return Err(Error::unsupported(
"cpufreq is not present on this machine (no scaling driver, or a container \
without /sys/devices/system/cpu/*/cpufreq)",
));
}
for (_, members) in domains {
for a in &members {
for b in &members {
if a == b {
continue;
}
if let (Some(from), Some(to)) = (
ctx.row_of(&keys::logical_cpu(*a)),
ctx.row_of(&keys::logical_cpu(*b)),
) {
ctx.declare_relation(Relation::new(
from,
to,
RelationKind::FrequencyDomain,
));
}
}
}
}
self.targets = targets;
self.channel_current =
Some(ctx.declare_channel("cpu.frequency.current", Unit::Kilohertz, Semantics::Instant));
self.channel_min =
Some(ctx.declare_channel("cpu.frequency.min", Unit::Kilohertz, Semantics::Configured));
self.channel_max =
Some(ctx.declare_channel("cpu.frequency.max", Unit::Kilohertz, Semantics::Configured));
self.channel_base =
Some(ctx.declare_channel("cpu.frequency.base", Unit::Kilohertz, Semantics::Configured));
Ok(())
}
fn observe(&mut self, out: &mut StateWriter<'_>) -> SensorOutcome {
let mut outcome = SensorOutcome::default();
for target in &self.targets {
source::sample(
out,
&mut outcome,
target.row,
self.channel_current,
&target.current,
true,
);
source::sample(
out,
&mut outcome,
target.row,
self.channel_min,
&target.min,
true,
);
source::sample(
out,
&mut outcome,
target.row,
self.channel_max,
&target.max,
true,
);
source::sample(
out,
&mut outcome,
target.row,
self.channel_base,
&target.base,
false,
);
}
outcome
}
}