#[cfg(target_os = "windows")]
pub mod windows;
pub mod counters;
pub mod frequency;
pub mod idle;
pub mod interrupts;
pub mod power;
pub mod scheduler;
pub mod source;
pub mod thermal;
use corescout_core::Result;
use corescout_mirror::entity::Entity;
use corescout_mirror::relation::Relation;
use corescout_mirror::schema::{Availability, AvailabilityMatrix};
use corescout_mirror::state::{ChannelId, ChannelSpec, Semantics, StateMatrix, Unit};
use crate::discovery::Substrate;
pub use corescout_mirror::schema::{Perturbation, SensorId, SensorReport, Uncertainty};
#[derive(Debug, Clone, PartialEq)]
pub struct SensorDescriptor {
pub id: SensorId,
pub key: &'static str,
pub physical_fact: &'static str,
pub source: &'static str,
pub max_rate_hz: f64,
pub perturbation: Perturbation,
pub uncertainty: Uncertainty,
pub requires_privilege: bool,
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct SensorOutcome {
pub samples: u32,
pub errors: u32,
pub sample_age_ns: u64,
}
impl SensorOutcome {
pub fn sample(&mut self) {
self.samples += 1;
}
pub fn error(&mut self) {
self.errors += 1;
}
pub fn aged(&mut self, age_ns: u64) {
self.sample_age_ns = self.sample_age_ns.max(age_ns);
}
}
pub struct BindContext<'a> {
substrate: &'a Substrate,
sensor: SensorId,
entities: &'a mut Vec<Entity>,
channels: &'a mut Vec<ChannelSpec>,
relations: &'a mut Vec<Relation>,
}
impl<'a> BindContext<'a> {
pub fn new(
substrate: &'a Substrate,
sensor: SensorId,
entities: &'a mut Vec<Entity>,
channels: &'a mut Vec<ChannelSpec>,
relations: &'a mut Vec<Relation>,
) -> BindContext<'a> {
BindContext {
substrate,
sensor,
entities,
channels,
relations,
}
}
pub fn substrate(&self) -> &Substrate {
self.substrate
}
pub fn row_of(&self, key: &str) -> Option<u32> {
self.entities
.iter()
.position(|e| e.key == key)
.map(|i| i as u32)
}
pub fn declare_entity(&mut self, entity: Entity) -> u32 {
if let Some(row) = self.row_of(&entity.key) {
return row;
}
self.entities.push(entity);
(self.entities.len() - 1) as u32
}
pub fn declare_channel(
&mut self,
key: impl Into<String>,
unit: Unit,
semantics: Semantics,
) -> ChannelId {
let key = key.into();
if let Some(existing) = self.channels.iter().find(|c| c.key == key) {
return existing.id;
}
let id = ChannelId(self.channels.len() as u16);
self.channels.push(ChannelSpec {
id,
key,
unit,
semantics,
sensor: self.sensor,
});
id
}
pub fn declare_relation(&mut self, relation: Relation) {
self.relations.push(relation);
}
}
pub struct AvailabilityWriter<'a> {
matrix: &'a mut AvailabilityMatrix,
}
impl<'a> AvailabilityWriter<'a> {
pub fn new(matrix: &'a mut AvailabilityMatrix) -> AvailabilityWriter<'a> {
AvailabilityWriter { matrix }
}
#[inline]
pub fn set(&mut self, row: u32, channel: ChannelId, availability: Availability) {
self.matrix.set(row as usize, channel.index(), availability);
}
}
pub struct StateWriter<'a> {
matrix: &'a mut StateMatrix,
availability: AvailabilityWriter<'a>,
}
impl<'a> StateWriter<'a> {
pub fn new(
matrix: &'a mut StateMatrix,
availability: AvailabilityWriter<'a>,
) -> StateWriter<'a> {
StateWriter {
matrix,
availability,
}
}
#[inline]
pub fn set(&mut self, row: u32, channel: ChannelId, value: f64) {
let (r, c) = (row as usize, channel.index());
if r < self.matrix.rows() && c < self.matrix.cols() {
self.matrix.set(r, c, value);
self.availability.set(row, channel, Availability::Observed);
}
}
#[inline]
pub fn missing(&mut self, row: u32, channel: ChannelId, why: Availability) {
let (r, c) = (row as usize, channel.index());
if r < self.matrix.rows() && c < self.matrix.cols() {
self.availability.set(row, channel, why);
}
}
}
pub trait Sensor: Send {
fn descriptor(&self) -> SensorDescriptor;
fn bind(&mut self, ctx: &mut BindContext<'_>) -> Result<()>;
fn observe(&mut self, out: &mut StateWriter<'_>) -> SensorOutcome;
}
pub fn default_sensors() -> Vec<Box<dyn Sensor>> {
#[cfg(target_os = "windows")]
{
return windows::sensors();
}
#[allow(unreachable_code)]
linux_sensors()
}
pub fn linux_sensors() -> Vec<Box<dyn Sensor>> {
vec![
Box::new(frequency::FrequencySensor::new()),
Box::new(idle::IdleSensor::new()),
Box::new(thermal::ThermalSensor::new()),
Box::new(power::PowerSensor::new()),
Box::new(scheduler::SchedulerSensor::new()),
Box::new(interrupts::InterruptSensor::new()),
Box::new(counters::CounterSensor::new()),
]
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn no_default_sensor_claims_to_be_material() {
for sensor in default_sensors() {
let d = sensor.descriptor();
assert!(
d.perturbation.is_passive(),
"sensor `{}` declares Material perturbation and is an experiment",
d.key
);
}
}
#[test]
fn every_sensor_documents_its_physical_basis() {
for sensor in default_sensors() {
let d = sensor.descriptor();
assert!(
!d.physical_fact.is_empty(),
"{} has no physical_fact",
d.key
);
assert!(!d.source.is_empty(), "{} has no source", d.key);
assert!(
!d.uncertainty.basis.is_empty(),
"{} has no uncertainty basis",
d.key
);
assert!(
d.max_rate_hz > 0.0,
"{} declares no sampling ceiling",
d.key
);
}
}
#[test]
fn sensor_ids_and_keys_are_unique() {
let sensors = default_sensors();
let mut ids: Vec<u16> = sensors.iter().map(|s| s.descriptor().id.0).collect();
let mut keys: Vec<&str> = sensors.iter().map(|s| s.descriptor().key).collect();
ids.sort_unstable();
keys.sort_unstable();
let unique_ids = {
let mut v = ids.clone();
v.dedup();
v.len()
};
let unique_keys = {
let mut v = keys.clone();
v.dedup();
v.len()
};
assert_eq!(unique_ids, ids.len(), "duplicate sensor id");
assert_eq!(unique_keys, keys.len(), "duplicate sensor key");
}
#[test]
fn writing_a_value_marks_it_observed_and_missing_records_a_reason() {
let mut matrix = StateMatrix::new(2, 2);
let mut availability = AvailabilityMatrix::new(2, 2);
{
let mut writer =
StateWriter::new(&mut matrix, AvailabilityWriter::new(&mut availability));
writer.set(0, ChannelId(0), 5.0);
writer.missing(1, ChannelId(1), Availability::PermissionDenied);
writer.set(99, ChannelId(0), 1.0);
writer.missing(0, ChannelId(99), Availability::Unknown);
}
assert_eq!(matrix.get(0, 0), 5.0);
assert_eq!(availability.get(0, 0), Availability::Observed);
assert!(matrix.get(1, 1).is_nan());
assert_eq!(availability.get(1, 1), Availability::PermissionDenied);
assert_eq!(matrix.observed_cells(), 1);
}
}