#![allow(
clippy::unwrap_used,
clippy::indexing_slicing,
clippy::panic,
clippy::arithmetic_side_effects
)]
use std::collections::BTreeSet;
use proptest::prelude::*;
use super::{Access, Cpu, Left, Plan, Processor, Topology, plan};
#[derive(Debug, Clone)]
struct Drawn {
class: u8,
threads: u32,
cache: u32,
barred: Vec<bool>,
}
fn drawn() -> impl Strategy<Value = Drawn> {
(
0u8..4,
1u32..3,
0u32..3,
proptest::collection::vec(any::<bool>(), 2),
)
.prop_map(|(class, threads, cache, barred)| Drawn {
class,
threads,
cache,
barred,
})
}
fn topology() -> impl Strategy<Value = Vec<Cpu>> {
(proptest::collection::vec(drawn(), 0..10), 0u8..8)
.prop_map(|(cores, spoil)| {
let mut cpus = Vec::new();
for (core, d) in (0u32..).zip(&cores) {
for thread in 0..d.threads {
let index = usize::try_from(thread).unwrap();
cpus.push(Cpu {
processor: Processor {
group: u16::from(core >= 6),
number: core * 2 + thread,
},
class: d.class,
core,
cache: d.cache,
access: if d.barred[index] {
Access::Barred
} else {
Access::Allowed
},
});
}
}
match (spoil, cpus.first().copied()) {
(0, Some(first)) => cpus.push(first),
(1, Some(first)) => cpus.push(Cpu {
processor: Processor {
group: first.processor.group,
number: 999,
},
class: first.class.wrapping_add(1),
..first
}),
_ => {}
}
cpus
})
.prop_shuffle()
}
fn seats(plan: &Plan) -> &[Processor] {
match plan {
Plan::Seats(seats) => seats,
Plan::LeftToOs(_) => &[],
}
}
fn usable(cpus: &[Cpu]) -> impl Iterator<Item = &Cpu> {
cpus.iter().filter(|cpu| cpu.access == Access::Allowed)
}
fn consistent(cpus: &[Cpu]) -> bool {
let ids: BTreeSet<Processor> = cpus.iter().map(|cpu| cpu.processor).collect();
ids.len() == cpus.len()
&& cpus.iter().all(|a| {
cpus.iter().all(|b| {
(a.processor.group, a.core) != (b.processor.group, b.core) || a.class == b.class
})
})
}
proptest! {
#![proptest_config(ProptestConfig::with_cases(2_000))]
#[test]
fn the_plan_keeps_every_property_of_the_model(cpus in topology()) {
let got = plan(&Topology::new(cpus.clone()));
let seats = seats(&got);
let classes: BTreeSet<u8> = usable(&cpus).map(|cpu| cpu.class).collect();
let seated: Vec<&Cpu> = seats
.iter()
.map(|seat| cpus.iter().find(|cpu| cpu.processor == *seat).unwrap())
.collect();
prop_assert!(seated.iter().all(|cpu| cpu.access == Access::Allowed));
if !consistent(&cpus) {
prop_assert_eq!(&got, &Plan::LeftToOs(Left::Inconsistent));
}
if classes.len() < 2 {
prop_assert!(seats.is_empty());
}
let cores: BTreeSet<(u16, u32)> =
seated.iter().map(|cpu| (cpu.processor.group, cpu.core)).collect();
prop_assert_eq!(cores.len(), seats.len());
if let Some(slowest) = classes.first() {
prop_assert!(seated.iter().all(|cpu| cpu.class != *slowest));
}
if consistent(&cpus) && classes.len() >= 2 {
let fastest = *classes.last().unwrap();
let top: BTreeSet<(u16, u32)> = usable(&cpus)
.filter(|cpu| cpu.class == fastest)
.map(|cpu| (cpu.processor.group, cpu.core))
.collect();
prop_assert_eq!(seats.len(), top.len());
}
prop_assert!(seats.windows(2).all(|pair| pair[0] < pair[1]));
prop_assert!(!matches!(&got, Plan::Seats(seats) if seats.is_empty()));
}
#[test]
fn the_plan_ignores_order_and_caches(cpus in topology(), cache in 0u32..5) {
let got = plan(&Topology::new(cpus.clone()));
let mut reversed = cpus.clone();
reversed.reverse();
prop_assert_eq!(&plan(&Topology::new(reversed)), &got);
let recached: Vec<Cpu> = cpus.iter().map(|cpu| Cpu { cache, ..*cpu }).collect();
prop_assert_eq!(&plan(&Topology::new(recached)), &got);
}
}
fn part(runs: &[(u8, u32, u32, u32)]) -> Vec<Cpu> {
let mut cpus = Vec::new();
let mut number = 0u32;
let mut core = 0u32;
for &(class, cores, threads, cache) in runs {
for _ in 0..cores {
for _ in 0..threads {
let processor = Processor { group: 0, number };
cpus.push(Cpu {
processor,
class,
core,
cache,
access: Access::Allowed,
});
number += 1;
}
core += threads;
}
}
cpus
}
fn numbers(numbers: &[u32]) -> Plan {
Plan::Seats(
numbers
.iter()
.map(|&number| Processor { group: 0, number })
.collect(),
)
}
fn even(below: u32) -> Vec<u32> {
(0..below).step_by(2).collect()
}
fn i5_1340p() -> Vec<Cpu> {
part(&[(1, 4, 2, 0), (0, 8, 1, 0)])
}
#[test]
fn a_raptor_lake_laptop_seats_one_thread_per_performance_core() {
assert_eq!(plan(&Topology::new(i5_1340p())), numbers(&even(8)));
}
#[test]
fn a_raptor_lake_desktop_seats_its_eight_performance_cores() {
let cpus = part(&[(1, 8, 2, 0), (0, 16, 1, 0)]);
assert_eq!(plan(&Topology::new(cpus)), numbers(&even(16)));
}
#[test]
fn a_meteor_lake_laptop_seats_performance_cores_and_never_the_low_power_tier() {
let cpus = part(&[(2, 6, 2, 0), (1, 8, 1, 0), (0, 2, 1, 1)]);
assert_eq!(plan(&Topology::new(cpus)), numbers(&even(12)));
}
#[test]
fn a_meteor_lake_container_without_performance_cores_prefers_the_e_cores_not_the_low_power_ones() {
let cpus: Vec<Cpu> = part(&[(2, 6, 2, 0), (1, 8, 1, 0), (0, 2, 1, 1)])
.into_iter()
.map(|cpu| Cpu {
access: if cpu.class == 2 {
Access::Barred
} else {
Access::Allowed
},
..cpu
})
.collect();
assert_eq!(
plan(&Topology::new(cpus)),
numbers(&(12..20).collect::<Vec<_>>())
);
}
#[test]
fn a_lunar_lake_laptop_seats_its_four_performance_cores() {
let cpus = part(&[(1, 4, 1, 0), (0, 4, 1, 1)]);
assert_eq!(plan(&Topology::new(cpus)), numbers(&[0, 1, 2, 3]));
}
#[test]
fn a_single_ccd_x3d_is_left_to_the_operating_system() {
let cpus = part(&[(0, 8, 2, 0)]);
assert_eq!(plan(&Topology::new(cpus)), Plan::LeftToOs(Left::OneClass));
}
#[test]
fn a_dual_ccd_x3d_is_left_to_the_operating_system_and_its_cache_steering() {
let topology = Topology::new(part(&[(0, 8, 2, 0), (0, 8, 2, 1)]));
assert_eq!(plan(&topology), Plan::LeftToOs(Left::OneClass));
assert_eq!(topology.shape().caches, 2);
}
#[test]
fn a_zen_5_and_zen_5c_laptop_seats_its_four_zen_5_cores() {
let cpus = part(&[(1, 4, 2, 0), (0, 8, 2, 1)]);
assert_eq!(plan(&Topology::new(cpus)), numbers(&even(8)));
}
#[test]
fn a_threadripper_in_two_groups_is_left_to_the_operating_system() {
let cpus: Vec<Cpu> = part(&[(0, 64, 2, 0)])
.into_iter()
.map(|cpu| {
let group = u16::from(cpu.processor.number >= 64);
Cpu {
processor: Processor {
group,
number: cpu.processor.number % 64,
},
core: cpu.core % 64,
cache: cpu.processor.number / 16,
access: if group == 0 {
Access::Allowed
} else {
Access::Barred
},
..cpu
}
})
.collect();
let topology = Topology::new(cpus);
assert_eq!(plan(&topology), Plan::LeftToOs(Left::OneClass));
assert_eq!(
(topology.shape().usable, topology.shape().logical),
(64, 128)
);
}
#[test]
fn a_snapdragon_x_with_one_class_is_left_to_the_operating_system() {
let cpus = part(&[(0, 4, 1, 0), (0, 4, 1, 1), (0, 4, 1, 2)]);
assert_eq!(plan(&Topology::new(cpus)), Plan::LeftToOs(Left::OneClass));
}
#[test]
fn a_four_vcpu_virtual_machine_is_left_to_the_operating_system() {
let cpus = part(&[(0, 4, 1, 0)]);
assert_eq!(plan(&Topology::new(cpus)), Plan::LeftToOs(Left::OneClass));
}
#[test]
fn a_virtual_machine_that_reports_one_core_in_two_classes_is_left_to_the_operating_system() {
let mut cpus = part(&[(1, 2, 1, 0), (0, 2, 1, 0)]);
cpus[3].core = cpus[0].core;
assert_eq!(
plan(&Topology::new(cpus)),
Plan::LeftToOs(Left::Inconsistent)
);
}
#[test]
fn a_container_limited_to_two_e_cores_is_left_to_the_operating_system() {
let cpus: Vec<Cpu> = i5_1340p()
.into_iter()
.map(|cpu| Cpu {
access: if matches!(cpu.processor.number, 8 | 9) {
Access::Allowed
} else {
Access::Barred
},
..cpu
})
.collect();
assert_eq!(plan(&Topology::new(cpus)), Plan::LeftToOs(Left::OneClass));
}
#[test]
fn a_container_limited_to_one_p_core_and_one_e_core_seats_the_p_core_it_may_use() {
let cpus: Vec<Cpu> = i5_1340p()
.into_iter()
.map(|cpu| Cpu {
access: if matches!(cpu.processor.number, 3 | 8) {
Access::Allowed
} else {
Access::Barred
},
..cpu
})
.collect();
assert_eq!(plan(&Topology::new(cpus)), numbers(&[3]));
}
#[test]
fn a_process_that_may_use_nothing_is_left_to_the_operating_system() {
let cpus: Vec<Cpu> = i5_1340p()
.into_iter()
.map(|cpu| Cpu {
access: Access::Barred,
..cpu
})
.collect();
assert_eq!(
plan(&Topology::new(cpus)),
Plan::LeftToOs(Left::NothingUsable)
);
}