use crate::api::{
AdmissionPolicy, Advice, AutoPlan, CheckStatus, Peaks, ProfilePlan, ProfileSizing, Resize,
Sizing, SizingChange, SizingHold, SizingRefused, Spread, Starts, Tone, need,
};
const G: u64 = 1 << 30;
fn profile(name: &str, max: u64, advice: Advice) -> ProfileSizing {
ProfileSizing {
name: name.to_owned(),
memory: 4 * G,
cpus: 4,
slots: 2,
peaks: Peaks {
jobs: 3,
at_cap: 0,
memory: Spread {
p50: max,
p95: max,
p99: max,
max,
},
cpu: Spread::default(),
on_cap: 0,
},
wait: Spread::default(),
starts: Starts::default(),
busy: 0,
wants_slots: false,
advice,
revert: None,
jobs: vec![],
}
}
fn sizing(profiles: Vec<ProfileSizing>) -> Sizing {
Sizing {
since: 1_000,
until: 2_000,
margin: 15,
sample_interval: 2,
min_jobs: 10,
memory_max: Some(64 * G),
admission: AdmissionPolicy::Caps,
profiles,
auto: true,
changes: vec![],
auto_plan: None,
}
}
#[test]
fn too_few_runs_are_marked_at_what_the_advice_would_size_for() {
let profile = profile("jvm", G, Advice::TooFew { jobs: 3 });
let marked = sizing(vec![]).need(&profile.peaks, profile.advice);
assert_eq!(marked.memory, 2 * G);
assert_eq!(marked.memory, need(G, 15), "the daemon's own arithmetic");
assert_eq!(marked.reason, "its largest peak + 1G for page cache");
assert_eq!(need(10 * G, 15), 11776 << 20, "11.5G, a multiple of 256M");
}
#[test]
fn a_hold_is_judged_by_the_readers_clock_not_by_when_the_plan_was_made() {
let plan = AutoPlan {
every: 300,
interval: 86_400,
raise_interval: 3_600,
max_step: 25,
next: vec![],
held: vec![SizingHold {
profile: "jvm".to_owned(),
changed: 0,
until: 3_600,
lower_until: 86_400,
}],
blocked: None,
refused: Vec::new(),
};
assert!(
matches!(plan.profile_plan("jvm", 60), ProfilePlan::Held { .. }),
"an hour holds every change"
);
let later = plan.profile_plan("jvm", 7_200);
assert!(
matches!(later, ProfilePlan::LowerHeld { .. }),
"past the raise interval, only a lowering waits"
);
assert_eq!(later.until(), Some(86_400));
assert_eq!(later.headline(&plan), "No lowering");
assert_eq!(
plan.profile_plan("jvm", 90_000),
ProfilePlan::Nothing,
"a hold that ended holds nothing"
);
}
#[test]
fn a_cpu_raise_auto_sizing_cannot_make_shows_beside_the_memory_change_it_makes() {
let plan = AutoPlan {
every: 300,
interval: 86_400,
raise_interval: 3_600,
max_step: 25,
next: vec![SizingChange {
at: 0,
profile: "jvm".to_owned(),
from: Resize {
memory: 9 * G,
slots: 2,
cpus: None,
},
to: Resize {
memory: 10 * G,
slots: 2,
cpus: None,
},
reason: "raise memory".to_owned(),
uncertain: false,
}],
held: Vec::new(),
blocked: None,
refused: vec![SizingRefused {
profile: "jvm".to_owned(),
reason: "3 runs of halyard/ledger CI / test averaged 7.9 of its 8 CPUs (median): the cap holds them \
back; 9 CPUs for each of its 2 slots do not fit the CPU budget beside the other profiles'"
.to_owned(),
}],
};
assert!(
matches!(plan.profile_plan("jvm", 60), ProfilePlan::Next { .. }),
"the memory change is due"
);
assert_eq!(
plan.refusal("jvm").as_deref(),
Some(
"No CPU raise: 3 runs of halyard/ledger CI / test averaged 7.9 of its 8 CPUs (median): the cap holds \
them back; 9 CPUs for each of its 2 slots do not fit the CPU budget beside the other profiles'"
),
"and why its cpus cannot rise, beside it"
);
assert_eq!(plan.refusal("node"), None);
}
#[test]
fn doctor_warns_only_when_a_profile_needs_more_than_its_cap() {
let lower = profile(
"node",
G,
Advice::Lower {
memory: 2 * G,
slots: 2,
},
);
let check = sizing(vec![lower.clone()]).doctor_check();
assert_eq!(check.status, CheckStatus::Info, "{check:?}");
assert_eq!(lower.advice.tone(), Tone::Warn);
let raise = profile(
"jvm",
6 * G,
Advice::Raise {
memory: 7 * G,
slots: 2,
},
);
let check = sizing(vec![lower, raise.clone()]).doctor_check();
assert_eq!(check.status, CheckStatus::Warn, "{check:?}");
assert_eq!(raise.advice.tone(), Tone::Bad, "red in every client");
}