@@ -6,6 +6,7 @@ mod config;
6
6
mod execute;
7
7
mod fixtures;
8
8
9
+ use anyhow:: { bail, Context } ;
9
10
use clap:: Parser ;
10
11
use config:: { ListOptions , ProcessArgs , RunOptions } ;
11
12
use phd_tests:: phd_testcase:: { Framework , FrameworkParameters } ;
@@ -46,55 +47,114 @@ async fn main() -> anyhow::Result<()> {
46
47
Ok ( ( ) )
47
48
}
48
49
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
+
49
136
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 ( "\t CPUS: " )
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 ( "\t Memory: " )
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) ;
98
158
99
159
// /!\ Arbitrary choice warning /!\
100
160
//
0 commit comments