use serde::{Deserialize, Serialize};
pub const RELATION_ATTRS: usize = 4;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
#[repr(u16)]
pub enum RelationKind {
Contains = 1,
SmtSibling = 2,
CacheMember = 3,
NumaLocal = 4,
FrequencyDomain = 5,
ThermalDomain = 6,
PowerDomain = 7,
InterruptAffinity = 8,
Unclassified = 0,
}
impl RelationKind {
pub fn as_u16(self) -> u16 {
self as u16
}
pub fn from_u16(value: u16) -> RelationKind {
match value {
1 => RelationKind::Contains,
2 => RelationKind::SmtSibling,
3 => RelationKind::CacheMember,
4 => RelationKind::NumaLocal,
5 => RelationKind::FrequencyDomain,
6 => RelationKind::ThermalDomain,
7 => RelationKind::PowerDomain,
8 => RelationKind::InterruptAffinity,
_ => RelationKind::Unclassified,
}
}
pub fn label(self) -> &'static str {
match self {
RelationKind::Contains => "contains",
RelationKind::SmtSibling => "smt_sibling",
RelationKind::CacheMember => "cache_member",
RelationKind::NumaLocal => "numa_local",
RelationKind::FrequencyDomain => "frequency_domain",
RelationKind::ThermalDomain => "thermal_domain",
RelationKind::PowerDomain => "power_domain",
RelationKind::InterruptAffinity => "interrupt_affinity",
RelationKind::Unclassified => "unclassified",
}
}
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
pub struct Relation {
pub source: u32,
pub target: u32,
pub kind: RelationKind,
#[serde(with = "crate::nanjson::attrs")]
pub attributes: [f64; RELATION_ATTRS],
}
impl PartialEq for Relation {
fn eq(&self, other: &Relation) -> bool {
self.source == other.source
&& self.target == other.target
&& self.kind == other.kind
&& self
.attributes
.iter()
.zip(&other.attributes)
.all(|(a, b)| same_value(*a, *b))
}
}
#[inline]
pub(crate) fn same_value(a: f64, b: f64) -> bool {
a == b || (a.is_nan() && b.is_nan())
}
impl Relation {
pub fn new(source: u32, target: u32, kind: RelationKind) -> Relation {
Relation {
source,
target,
kind,
attributes: [f64::NAN; RELATION_ATTRS],
}
}
pub fn with(mut self, slot: usize, value: f64) -> Relation {
self.attributes[slot] = value;
self
}
}
#[derive(Debug, Clone, Copy)]
pub struct RelationView<'a> {
relations: &'a [Relation],
}
impl<'a> RelationView<'a> {
pub fn new(relations: &'a [Relation]) -> RelationView<'a> {
RelationView { relations }
}
pub fn all(&self) -> &'a [Relation] {
self.relations
}
pub fn from(&self, source: u32) -> impl Iterator<Item = &'a Relation> + '_ {
self.relations.iter().filter(move |r| r.source == source)
}
pub fn to(&self, target: u32) -> impl Iterator<Item = &'a Relation> + '_ {
self.relations.iter().filter(move |r| r.target == target)
}
pub fn of_kind(&self, kind: RelationKind) -> impl Iterator<Item = &'a Relation> + '_ {
self.relations.iter().filter(move |r| r.kind == kind)
}
pub fn connected(&self, a: u32, b: u32, kind: RelationKind) -> bool {
self.relations
.iter()
.any(|r| r.kind == kind && r.source == a && r.target == b)
}
}
#[cfg(test)]
mod tests {
use super::*;
fn fixture() -> Vec<Relation> {
vec![
Relation::new(0, 1, RelationKind::Contains),
Relation::new(1, 2, RelationKind::Contains),
Relation::new(2, 3, RelationKind::SmtSibling),
Relation::new(3, 2, RelationKind::SmtSibling),
Relation::new(4, 2, RelationKind::CacheMember)
.with(0, 3.0)
.with(1, 32.0),
]
}
#[test]
fn unobserved_attributes_do_not_break_equality() {
let a = Relation::new(0, 1, RelationKind::Contains);
let b = Relation::new(0, 1, RelationKind::Contains);
assert_eq!(a, b);
assert_ne!(a, Relation::new(0, 2, RelationKind::Contains));
assert_ne!(a, a.with(0, 1.0));
}
#[test]
fn attributes_default_to_unobserved() {
let r = Relation::new(0, 1, RelationKind::Contains);
assert!(r.attributes.iter().all(|a| a.is_nan()));
}
#[test]
fn attributes_can_be_set_positionally() {
let r = Relation::new(4, 2, RelationKind::CacheMember).with(0, 3.0);
assert_eq!(r.attributes[0], 3.0);
assert!(r.attributes[1].is_nan());
}
#[test]
fn edges_can_be_walked_in_both_directions() {
let relations = fixture();
let view = RelationView::new(&relations);
assert_eq!(view.from(1).count(), 1);
assert_eq!(view.to(2).count(), 3, "contains, smt sibling, cache member");
}
#[test]
fn symmetric_relations_are_emitted_both_ways() {
let relations = fixture();
let view = RelationView::new(&relations);
assert!(view.connected(2, 3, RelationKind::SmtSibling));
assert!(view.connected(3, 2, RelationKind::SmtSibling));
}
#[test]
fn filtering_by_kind_works() {
let relations = fixture();
let view = RelationView::new(&relations);
assert_eq!(view.of_kind(RelationKind::Contains).count(), 2);
assert_eq!(view.of_kind(RelationKind::PowerDomain).count(), 0);
}
#[test]
fn unknown_kinds_degrade_rather_than_fail() {
assert_eq!(RelationKind::from_u16(4242), RelationKind::Unclassified);
for kind in [
RelationKind::Contains,
RelationKind::SmtSibling,
RelationKind::CacheMember,
RelationKind::NumaLocal,
RelationKind::ThermalDomain,
RelationKind::PowerDomain,
] {
assert_eq!(RelationKind::from_u16(kind.as_u16()), kind);
}
}
}