#![allow(
clippy::unwrap_used,
clippy::indexing_slicing,
clippy::panic,
clippy::arithmetic_side_effects
)]
use std::collections::BTreeSet;
use super::describe;
use super::plan::{Left, Plan, Processor, Topology};
use super::reading::Unread;
use super::seats::{Holder, Role, SERIAL_SEATS, Seats};
const THREADS: u64 = 4;
const MOST_SEATS: u32 = 5;
const EVENTS: usize = 6;
fn role(holder: Holder) -> Role {
match usize::try_from(holder.0).unwrap_or(usize::MAX) < SERIAL_SEATS {
true => Role::Serial,
false => Role::Lane,
}
}
#[derive(Debug, Clone, Copy)]
enum Event {
Start(Holder),
Exit(Holder),
}
#[test]
fn the_seat_table_keeps_every_property_on_every_trace() {
for count in 0..=MOST_SEATS {
let plan: Vec<Processor> = (0..count)
.map(|number| Processor {
group: 0,
number: number * 2,
})
.collect();
for trace in every_trace() {
check_trace(&plan, &trace);
}
}
}
fn check_trace(plan: &[Processor], trace: &[Event]) {
let mut table = Seats::new(plan.to_vec());
for &event in trace {
let before: Vec<Option<Processor>> = holders().map(|h| table.seat_of(h)).collect();
match event {
Event::Start(holder) => {
table.start(holder, role(holder));
check_start(&table, plan, &before, holder);
}
Event::Exit(holder) => {
table.exit(holder);
assert_eq!(
table.seat_of(holder),
None,
"an exit gives the seat back: {trace:?}"
);
}
}
let seated: Vec<Processor> = holders().filter_map(|h| table.seat_of(h)).collect();
let distinct: BTreeSet<Processor> = seated.iter().copied().collect();
assert_eq!(
distinct.len(),
seated.len(),
"one seat holds one thread: {plan:?} {trace:?}"
);
assert!(seated.iter().all(|seat| plan.contains(seat)));
for (other, was) in holders().zip(&before) {
let lives = !matches!(event, Event::Exit(h) if h == other);
if lives && let Some(seat) = was {
assert_eq!(
table.seat_of(other),
Some(*seat),
"a seat is kept: {plan:?} {trace:?}"
);
}
}
}
}
fn check_start(table: &Seats, plan: &[Processor], before: &[Option<Processor>], holder: Holder) {
let index = usize::try_from(holder.0).unwrap();
if before[index].is_some() {
return;
}
let pool = pool_of(plan, role(holder));
let first_free = pool
.iter()
.find(|seat| !before.contains(&Some(**seat)))
.copied();
assert_eq!(
table.seat_of(holder),
first_free,
"the first free seat of its own pool or none: {plan:?} {before:?}"
);
}
fn pool_of(plan: &[Processor], role: Role) -> Vec<Processor> {
let split = plan.len().saturating_sub(SERIAL_SEATS);
match role {
Role::Serial => plan.get(split..).unwrap_or_default().to_vec(),
Role::Lane => plan.get(..split).unwrap_or_default().to_vec(),
}
}
#[test]
fn lanes_never_take_the_seats_the_serial_threads_need() {
for count in 0..=MOST_SEATS {
let plan: Vec<Processor> = (0..count)
.map(|number| Processor {
group: 0,
number: number * 2,
})
.collect();
let mut table = Seats::new(plan.clone());
for holder in holders().filter(|h| role(*h) == Role::Lane) {
for _ in 0..2 {
table.start(holder, Role::Lane);
}
}
for holder in holders().filter(|h| role(*h) == Role::Serial) {
table.start(holder, Role::Serial);
let wanted = usize::try_from(holder.0).unwrap_or(usize::MAX);
let seated = table.seat_of(holder).is_some();
let seats = usize::try_from(count).unwrap_or(usize::MAX);
assert_eq!(
seated,
wanted < seats,
"every serial thread up to the plan's size is seated: {plan:?}"
);
}
}
}
fn holders() -> impl Iterator<Item = Holder> {
(0..THREADS).map(Holder)
}
fn every_trace() -> Vec<Vec<Event>> {
let events: Vec<Event> = holders()
.flat_map(|h| [Event::Start(h), Event::Exit(h)])
.collect();
(0..EVENTS).fold(vec![Vec::new()], |traces, _| {
traces
.iter()
.flat_map(|trace| {
events.iter().map(move |&event| {
let mut longer = trace.clone();
longer.push(event);
longer
})
})
.collect()
})
}
#[test]
fn the_doctor_line_names_the_classes_and_what_the_plan_did() {
use super::plan::{Access, Cpu};
let cpu = |number: u32, class: u8, core: u32, cache: u32| Cpu {
processor: Processor { group: 0, number },
class,
core,
cache,
access: Access::Allowed,
};
let hybrid = Topology::new(
(0..8)
.map(|n| cpu(n, 1, n / 2 * 2, 0))
.chain((8..16).map(|n| cpu(n, 0, n, 0)))
.collect(),
);
let line = describe(&Ok(hybrid.clone()));
let tail = if cfg!(windows) {
"hot threads prefer the 4 fastest cores, one thread each"
} else {
"this platform has no placement call; its scheduler places threads"
};
assert_eq!(
line,
format!(
"CPU: 2 classes, 4 performance cores (8 threads), 8 efficiency cores (8 threads); {tail}"
)
);
let dual_ccd = Topology::new((0..32).map(|n| cpu(n, 0, n / 2 * 2, n / 16)).collect());
assert_eq!(
describe(&Ok(dual_ccd)),
"CPU: one class, 16 cores (32 threads) in 2 cache groups; left to the operating system and its cache steering"
);
assert_eq!(
describe(&Err(Unread("sysfs is absent".to_owned()))),
"CPU: topology unread (sysfs is absent); left to the operating system"
);
assert_eq!(
super::plan::plan(&hybrid),
Plan::Seats(
(0..4)
.map(|n| Processor {
group: 0,
number: n * 2
})
.collect()
)
);
assert_ne!(super::plan::plan(&hybrid), Plan::LeftToOs(Left::OneClass));
}
#[test]
fn the_doctor_line_says_when_an_entered_ceiling_does_not_apply() {
use accounting::person::CorePlacement;
let limit = std::num::NonZeroU64::new(1 << 30).unwrap();
let notes: Vec<String> = [
(CorePlacement::SoftShares, Ok(Some(limit))),
(CorePlacement::Soft, Ok(None)),
(CorePlacement::Pinned, Ok(Some(limit))),
(
CorePlacement::SoftShares,
Err("line 2: not a number".to_owned()),
),
]
.iter()
.map(|(arm, entered)| super::ceiling_note(*arm, entered))
.collect();
assert_eq!(
notes,
[
String::new(),
String::new(),
"; the entered memory ceiling of 1073741824 bytes is not applied: [core] placement is not \"soft_shares\"".to_owned(),
"; the memory ceiling does not read, so none applies: line 2: not a number".to_owned(),
]
);
}