Skip to content

Commit f9d25ca

Browse files
committed
dont parse lgrpinfo for cpu/memory, be better about that
1 parent a514531 commit f9d25ca

File tree

4 files changed

+113
-48
lines changed

4 files changed

+113
-48
lines changed

Cargo.lock

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/bhyve-api/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,7 @@ impl AsRawFd for VmmCtlFd {
125125
}
126126
}
127127

128+
#[derive(Debug)]
128129
pub enum ReservoirError {
129130
/// Resizing operation was interrupted, but if a non-zero chunk size was
130131
/// specified, one or more chunk-sized adjustments to the reservoir size may

phd-tests/runner/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,11 @@ doctest = false
1212
[dependencies]
1313
anyhow.workspace = true
1414
backtrace.workspace = true
15+
bhyve_api.workspace = true
1516
camino.workspace = true
1617
clap = { workspace = true, features = ["derive"] }
1718
crossbeam-channel.workspace = true
19+
libc.workspace = true
1820
phd-framework.workspace = true
1921
phd-tests.workspace = true
2022
tokio = { workspace = true, features = ["full"] }

phd-tests/runner/src/main.rs

Lines changed: 108 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ mod config;
66
mod execute;
77
mod fixtures;
88

9+
use anyhow::{bail, Context};
910
use clap::Parser;
1011
use config::{ListOptions, ProcessArgs, RunOptions};
1112
use phd_tests::phd_testcase::{Framework, FrameworkParameters};
@@ -46,55 +47,114 @@ async fn main() -> anyhow::Result<()> {
4647
Ok(())
4748
}
4849

50+
fn guess_max_reasonable_parallelism(
51+
default_guest_cpus: u8,
52+
default_guest_memory_mib: u64,
53+
) -> anyhow::Result<u16> {
54+
// Assume no test starts more than 3 VMs. This is a really conservative
55+
// guess to make sure we don't cause tests to fail simply because we ran
56+
// too many at once.
57+
const MAX_VMS_GUESS: u64 = 3;
58+
let cpus_per_runner = default_guest_cpus as u64 * MAX_VMS_GUESS;
59+
let memory_mib_per_runner =
60+
(default_guest_memory_mib * MAX_VMS_GUESS) as usize;
61+
62+
/// Miniscule wrapper for `sysconf(3C)` calls that checks errors.
63+
fn sysconf(cfg: i32) -> std::io::Result<i64> {
64+
// Safety: sysconf is an FFI call but we don't change any system
65+
// state, it won't cause unwinding, etc.
66+
let res = unsafe { libc::sysconf(cfg) };
67+
// For the handful of variables that can be queried, the variable is
68+
// defined and won't error. Technically if the variable is
69+
// unsupported, `-1` is returned without changing `errno`. In such
70+
// cases, returning errno might be misleading!
71+
//
72+
// Instead of trying to disambiguate this, and in the knowledge
73+
// these calls as we make them should never fail, just fall back to
74+
// a more general always-factually-correct.
75+
if res == -1 {
76+
return Err(std::io::Error::new(
77+
std::io::ErrorKind::Other,
78+
format!("could not get sysconf({})", cfg),
79+
));
80+
}
81+
Ok(res)
82+
}
83+
84+
let online_cpus = sysconf(libc::_SC_NPROCESSORS_ONLN)
85+
.expect("can get number of online processors")
86+
as u64;
87+
// We're assuming that the system running tests is relatively idle other
88+
// than the test runner itself. Overprovisioning CPUs will make everyone
89+
// sad but should not fail tests, at least...
90+
let lim_by_cpus = online_cpus / cpus_per_runner;
91+
92+
let ctl = bhyve_api::VmmCtlFd::open()?;
93+
let reservoir =
94+
ctl.reservoir_query().context("failed to query reservoir")?;
95+
let mut vmm_mem_limit = reservoir.vrq_free_sz;
96+
97+
// The reservoir will be 0MiB by default if the system has not been
98+
// configured with a particular size.
99+
if reservoir.vrq_alloc_sz == 0 {
100+
// If the reservoir is not configured, we'll try to make do with
101+
// system memory and implore someone to earmark memory for test VMs in
102+
// the future.
103+
let page_size: usize = sysconf(libc::_SC_PAGESIZE)
104+
.expect("can get page size")
105+
.try_into()
106+
.expect("page size is reasonable");
107+
let total_pages: usize = sysconf(libc::_SC_PHYS_PAGES)
108+
.expect("can get physical pages in the system")
109+
.try_into()
110+
.expect("physical page count is reasonable");
111+
112+
let installed_mb = page_size * total_pages;
113+
// /!\ Arbitrary choice warning /!\
114+
//
115+
// It would be a little rude to spawn so many VMs that we cause the
116+
// system running tests to empty the whole ARC and swap. If there's no
117+
// reservior, though, we're gonna use *some* amount of memory that isn't
118+
// explicitly earmarked for bhyve, though. 1/4th is just a "feels ok"
119+
// fraction.
120+
vmm_mem_limit = installed_mb / 4;
121+
122+
eprintln!(
123+
"phd-runner sees the VMM reservior is unconfigured, and will use \
124+
up to 25% of system memory ({}MiB) for test VMs. Please consider \
125+
using `cargo run --bin rsrvrctl set <size MiB>` to set aside \
126+
memory for test VMs.",
127+
vmm_mem_limit
128+
);
129+
}
130+
131+
let lim_by_mem = vmm_mem_limit / memory_mib_per_runner;
132+
133+
Ok(std::cmp::min(lim_by_cpus as u16, lim_by_mem as u16))
134+
}
135+
49136
async fn run_tests(run_opts: &RunOptions) -> anyhow::Result<ExecutionStats> {
50-
let parallelism = run_opts.parallelism.unwrap_or_else(|| {
51-
// Assume no test starts more than 4 VMs. This is a really conservative
52-
// guess to make sure we don't cause tests to fail simply because we ran
53-
// too many at once.
54-
let cpus_per_runner = run_opts.default_guest_cpus as u64 * 4;
55-
let memory_mib_per_runner = run_opts.default_guest_memory_mib * 4;
56-
57-
// I assume there's a smarter way to do this in illumos. sorry!!
58-
let lgrpinfo = std::process::Command::new("/usr/bin/lgrpinfo")
59-
.args(["-mc", "-u", "m"])
60-
.output()
61-
.expect("can run lgrpinfo");
62-
assert_eq!(lgrpinfo.status.code(), Some(0));
63-
let lgrpinfo_output_str =
64-
String::from_utf8(lgrpinfo.stdout).expect("utf8 output");
65-
let lines: Vec<&str> = lgrpinfo_output_str.split("\n").collect();
66-
let cpuline = lines[1];
67-
let cpu_range = cpuline
68-
.strip_prefix("\tCPUS: ")
69-
.expect("`CPUs` line starts as expected");
70-
let mut cpu_range_parts = cpu_range.split("-");
71-
let cpu_low = cpu_range_parts.next().expect("range has a low");
72-
let cpu_high = cpu_range_parts.next().expect("range has a high");
73-
let ncpus = cpu_high.parse::<u64>().expect("can parse cpu_low")
74-
- cpu_low.parse::<u64>().expect("can parse cpu_high");
75-
76-
let lim_by_cpus = ncpus / cpus_per_runner;
77-
78-
let memoryline = lines[2];
79-
let memory = memoryline
80-
.strip_prefix("\tMemory: ")
81-
.expect("`Memory` line starts as expected");
82-
let installed = memory
83-
.split(",")
84-
.next()
85-
.expect("memory line is comma-separated elements");
86-
let installed_mb = installed
87-
.strip_prefix("installed ")
88-
.expect("memory line starts with installed")
89-
.strip_suffix("M")
90-
.expect("memory line ends with M")
91-
.parse::<u64>()
92-
.expect("can parse memory MB");
93-
94-
let lim_by_mem = installed_mb / memory_mib_per_runner;
95-
96-
std::cmp::min(lim_by_cpus as u16, lim_by_mem as u16)
97-
});
137+
let parallelism = if let Some(parallelism) = run_opts.parallelism {
138+
if parallelism == 0 {
139+
bail!("Parallelism of 0 was requested; cannot run tests under these conditions!");
140+
}
141+
parallelism
142+
} else {
143+
let res = guess_max_reasonable_parallelism(
144+
run_opts.default_guest_cpus,
145+
run_opts.default_guest_memory_mib,
146+
)?;
147+
if res == 0 {
148+
bail!(
149+
"Inferred a parallelism of 0; this is probably because there \
150+
is not much available memory for test VMs? Consider checking \
151+
reservoir configuration."
152+
);
153+
}
154+
res
155+
};
156+
157+
info!("running tests with max parallelism of {}", parallelism);
98158

99159
// /!\ Arbitrary choice warning /!\
100160
//

0 commit comments

Comments
 (0)