corescout_substrate/observation/
frequency.rs1use crate::observation::source;
39use crate::observation::{
40 BindContext, Perturbation, Sensor, SensorDescriptor, SensorId, SensorOutcome, StateWriter,
41 Uncertainty,
42};
43use corescout_core::cpuset::CpuSet;
44use corescout_core::error::{Error, Result};
45use corescout_mirror::entity::keys;
46use corescout_mirror::relation::{Relation, RelationKind};
47use corescout_mirror::state::{ChannelId, Semantics, Unit};
48use std::path::PathBuf;
49
50struct Target {
52 row: u32,
53 current: PathBuf,
54 min: PathBuf,
55 max: PathBuf,
56 base: PathBuf,
57}
58
59pub struct FrequencySensor {
60 targets: Vec<Target>,
61 channel_current: Option<ChannelId>,
62 channel_min: Option<ChannelId>,
63 channel_max: Option<ChannelId>,
64 channel_base: Option<ChannelId>,
65}
66
67impl FrequencySensor {
68 pub fn new() -> FrequencySensor {
69 FrequencySensor {
70 targets: Vec::new(),
71 channel_current: None,
72 channel_min: None,
73 channel_max: None,
74 channel_base: None,
75 }
76 }
77}
78
79impl Default for FrequencySensor {
80 fn default() -> Self {
81 Self::new()
82 }
83}
84
85impl Sensor for FrequencySensor {
86 fn descriptor(&self) -> SensorDescriptor {
87 SensorDescriptor {
88 id: SensorId(1),
89 key: "frequency",
90 physical_fact: "the clock rate each core is running at, as the cpufreq driver \
91 reports it",
92 source: "/sys/devices/system/cpu/cpuN/cpufreq/",
93 max_rate_hz: 100.0,
94 perturbation: Perturbation::Low,
97 uncertainty: Uncertainty::relative(
98 0.05,
99 "scaling_cur_freq is the governor's request, not a measurement; the core \
100 may be at a different point in its ramp, or held lower by a power limit",
101 ),
102 requires_privilege: false,
103 }
104 }
105
106 fn bind(&mut self, ctx: &mut BindContext<'_>) -> Result<()> {
107 let cpu_dir = ctx.substrate().roots.sys.join("devices/system/cpu");
108 let online: Vec<u32> = ctx
109 .substrate()
110 .topology
111 .logical_cpus
112 .iter()
113 .filter(|c| c.online)
114 .map(|c| c.id)
115 .collect();
116
117 let mut targets = Vec::new();
118 let mut domains: Vec<(String, Vec<u32>)> = Vec::new();
119 for cpu in online {
120 let dir = cpu_dir.join(format!("cpu{cpu}/cpufreq"));
121 if !dir.exists() {
122 continue;
123 }
124 let Some(row) = ctx.row_of(&keys::logical_cpu(cpu)) else {
125 continue;
126 };
127 targets.push(Target {
128 row,
129 current: dir.join("scaling_cur_freq"),
130 min: dir.join("cpuinfo_min_freq"),
131 max: dir.join("cpuinfo_max_freq"),
132 base: dir.join("base_frequency"),
133 });
134
135 if let Some(related) = source::string(dir.join("related_cpus")) {
138 if let Ok(set) = CpuSet::parse_list(&related) {
139 let members = set.to_vec();
140 if members.len() > 1 {
141 let key = corescout_core::cpuset::format_list(&members);
142 if !domains.iter().any(|(k, _)| *k == key) {
143 domains.push((key, members));
144 }
145 }
146 }
147 }
148 }
149
150 if targets.is_empty() {
151 return Err(Error::unsupported(
152 "cpufreq is not present on this machine (no scaling driver, or a container \
153 without /sys/devices/system/cpu/*/cpufreq)",
154 ));
155 }
156
157 for (_, members) in domains {
159 for a in &members {
160 for b in &members {
161 if a == b {
162 continue;
163 }
164 if let (Some(from), Some(to)) = (
165 ctx.row_of(&keys::logical_cpu(*a)),
166 ctx.row_of(&keys::logical_cpu(*b)),
167 ) {
168 ctx.declare_relation(Relation::new(
169 from,
170 to,
171 RelationKind::FrequencyDomain,
172 ));
173 }
174 }
175 }
176 }
177
178 self.targets = targets;
179 self.channel_current =
180 Some(ctx.declare_channel("cpu.frequency.current", Unit::Kilohertz, Semantics::Instant));
181 self.channel_min =
182 Some(ctx.declare_channel("cpu.frequency.min", Unit::Kilohertz, Semantics::Configured));
183 self.channel_max =
184 Some(ctx.declare_channel("cpu.frequency.max", Unit::Kilohertz, Semantics::Configured));
185 self.channel_base =
186 Some(ctx.declare_channel("cpu.frequency.base", Unit::Kilohertz, Semantics::Configured));
187 Ok(())
188 }
189
190 fn observe(&mut self, out: &mut StateWriter<'_>) -> SensorOutcome {
191 let mut outcome = SensorOutcome::default();
192 for target in &self.targets {
193 source::sample(
197 out,
198 &mut outcome,
199 target.row,
200 self.channel_current,
201 &target.current,
202 true,
203 );
204 source::sample(
205 out,
206 &mut outcome,
207 target.row,
208 self.channel_min,
209 &target.min,
210 true,
211 );
212 source::sample(
213 out,
214 &mut outcome,
215 target.row,
216 self.channel_max,
217 &target.max,
218 true,
219 );
220 source::sample(
223 out,
224 &mut outcome,
225 target.row,
226 self.channel_base,
227 &target.base,
228 false,
229 );
230 }
231 outcome
232 }
233}