3 #include <sys/sysctl.h>
4 #include <sys/kern_memorystatus.h>
5 #include <mach-o/dyld.h>
6 #include <perfcheck_keys.h>
11 #include <darwintest.h>
12 #include <darwintest_utils.h>
15 T_META_NAMESPACE("xnu.vm.perf"),
16 T_META_CHECK_LEAKS(false),
27 #define CREATE_LIST(X) \
29 X(TOO_FEW_ARGUMENTS) \
30 X(SYSCTL_VM_PAGESIZE_FAILED) \
31 X(VM_PAGESIZE_IS_ZERO) \
32 X(UNKNOWN_PAGE_TYPE) \
33 X(DISPATCH_SOURCE_CREATE_FAILED) \
34 X(INITIAL_SIGNAL_TO_PARENT_FAILED) \
35 X(SIGNAL_TO_PARENT_FAILED) \
36 X(MEMORYSTATUS_CONTROL_FAILED) \
37 X(IS_FREEZABLE_NOT_AS_EXPECTED) \
40 #define EXIT_CODES_ENUM(VAR) VAR,
42 CREATE_LIST(EXIT_CODES_ENUM
)
45 #define EXIT_CODES_STRING(VAR) #VAR,
46 static const char *exit_codes_str
[] = {
47 CREATE_LIST(EXIT_CODES_STRING
)
50 #define SYSCTL_FREEZE_TO_MEMORY "kern.memorystatus_freeze_to_memory=1"
52 static pid_t pid
= -1;
53 static dt_stat_t ratio
;
54 static dt_stat_time_t compr_time
;
55 static dt_stat_time_t decompr_time
;
57 void allocate_zero_pages(char **buf
, int num_pages
, int vmpgsize
);
58 void allocate_mostly_zero_pages(char **buf
, int num_pages
, int vmpgsize
);
59 void allocate_random_pages(char **buf
, int num_pages
, int vmpgsize
);
60 void allocate_representative_pages(char **buf
, int num_pages
, int vmpgsize
);
61 void run_compressor_test(int size_mb
, int page_type
);
62 void freeze_helper_process(void);
66 allocate_zero_pages(char **buf
, int num_pages
, int vmpgsize
)
70 for (i
= 0; i
< num_pages
; i
++) {
71 buf
[i
] = (char*)malloc((size_t)vmpgsize
* sizeof(char));
72 memset(buf
[i
], 0, vmpgsize
);
77 allocate_mostly_zero_pages(char **buf
, int num_pages
, int vmpgsize
)
81 for (i
= 0; i
< num_pages
; i
++) {
82 buf
[i
] = (char*)malloc((size_t)vmpgsize
* sizeof(char));
83 memset(buf
[i
], 0, vmpgsize
);
84 for (j
= 0; j
< 40; j
++) {
85 buf
[i
][j
] = (char)(j
+ 1);
91 allocate_random_pages(char **buf
, int num_pages
, int vmpgsize
)
95 for (i
= 0; i
< num_pages
; i
++) {
96 buf
[i
] = (char*)malloc((size_t)vmpgsize
* sizeof(char));
97 arc4random_buf((void*)buf
[i
], (size_t)vmpgsize
);
101 // Gives us the compression ratio we see in the typical case (~2.7)
103 allocate_representative_pages(char **buf
, int num_pages
, int vmpgsize
)
108 for (j
= 0; j
< num_pages
; j
++) {
109 buf
[j
] = (char*)malloc((size_t)vmpgsize
* sizeof(char));
111 for (i
= 0; i
< vmpgsize
; i
+= 16) {
112 memset(&buf
[j
][i
], val
, 16);
113 if (i
< 3400 * (vmpgsize
/ 4096)) {
121 freeze_helper_process(void)
123 int ret
, freeze_enabled
;
124 int64_t compressed_before
, compressed_after
, input_before
, input_after
;
126 int errno_sysctl_freeze
;
128 length
= sizeof(compressed_before
);
129 T_QUIET
; T_ASSERT_POSIX_SUCCESS(sysctlbyname("vm.compressor_compressed_bytes", &compressed_before
, &length
, NULL
, 0),
130 "failed to query vm.compressor_compressed_bytes");
131 length
= sizeof(input_before
);
132 T_QUIET
; T_ASSERT_POSIX_SUCCESS(sysctlbyname("vm.compressor_input_bytes", &input_before
, &length
, NULL
, 0),
133 "failed to query vm.compressor_input_bytes");
135 T_STAT_MEASURE(compr_time
) {
136 ret
= sysctlbyname("kern.memorystatus_freeze", NULL
, NULL
, &pid
, sizeof(pid
));
137 errno_sysctl_freeze
= errno
;
140 length
= sizeof(compressed_after
);
141 T_QUIET
; T_ASSERT_POSIX_SUCCESS(sysctlbyname("vm.compressor_compressed_bytes", &compressed_after
, &length
, NULL
, 0),
142 "failed to query vm.compressor_compressed_bytes");
143 length
= sizeof(input_after
);
144 T_QUIET
; T_ASSERT_POSIX_SUCCESS(sysctlbyname("vm.compressor_input_bytes", &input_after
, &length
, NULL
, 0),
145 "failed to query vm.compressor_input_bytes");
147 length
= sizeof(freeze_enabled
);
148 T_QUIET
; T_ASSERT_POSIX_SUCCESS(sysctlbyname("vm.freeze_enabled", &freeze_enabled
, &length
, NULL
, 0),
149 "failed to query vm.freeze_enabled");
150 if (freeze_enabled
) {
151 errno
= errno_sysctl_freeze
;
152 T_QUIET
; T_ASSERT_POSIX_SUCCESS(ret
, "sysctl kern.memorystatus_freeze failed");
154 /* If freezer is disabled, skip the test. This can happen due to disk space shortage. */
155 T_LOG("Freeze has been disabled. Terminating early.");
159 dt_stat_add(ratio
, (double)(input_after
- input_before
) / (double)(compressed_after
- compressed_before
));
161 ret
= sysctlbyname("kern.memorystatus_thaw", NULL
, NULL
, &pid
, sizeof(pid
));
162 T_QUIET
; T_ASSERT_POSIX_SUCCESS(ret
, "sysctl kern.memorystatus_thaw failed");
164 T_QUIET
; T_ASSERT_POSIX_SUCCESS(kill(pid
, SIGUSR1
), "failed to send SIGUSR1 to child process");
170 /* No helper process. */
174 /* Kill the helper process. */
179 run_compressor_test(int size_mb
, int page_type
)
184 char **launch_tool_args
;
185 char testpath
[PATH_MAX
];
186 uint32_t testpath_buf_size
;
187 dispatch_source_t ds_freeze
, ds_proc
, ds_decompr
;
190 __block
bool decompr_latency_is_stable
= false;
192 length
= sizeof(freeze_enabled
);
193 T_QUIET
; T_ASSERT_POSIX_SUCCESS(sysctlbyname("vm.freeze_enabled", &freeze_enabled
, &length
, NULL
, 0),
194 "failed to query vm.freeze_enabled");
195 if (!freeze_enabled
) {
196 /* If freezer is disabled, skip the test. This can happen due to disk space shortage. */
197 T_SKIP("Freeze has been disabled. Skipping test.");
202 ratio
= dt_stat_create("(input bytes / compressed bytes)", "compression_ratio");
203 compr_time
= dt_stat_time_create("compressor_latency");
205 // This sets the A/B failure threshold at 50% of baseline for compressor_latency
206 dt_stat_set_variable((struct dt_stat
*)compr_time
, kPCFailureThresholdPctVar
, 50.0);
208 signal(SIGUSR2
, SIG_IGN
);
209 ds_decompr
= dispatch_source_create(DISPATCH_SOURCE_TYPE_SIGNAL
, SIGUSR2
, 0, dispatch_get_main_queue());
210 T_QUIET
; T_ASSERT_NOTNULL(ds_decompr
, "dispatch_source_create (ds_decompr)");
212 dispatch_source_set_event_handler(ds_decompr
, ^{
213 decompr_latency_is_stable
= true;
215 dispatch_activate(ds_decompr
);
217 signal(SIGUSR1
, SIG_IGN
);
218 ds_freeze
= dispatch_source_create(DISPATCH_SOURCE_TYPE_SIGNAL
, SIGUSR1
, 0, dispatch_get_main_queue());
219 T_QUIET
; T_ASSERT_NOTNULL(ds_freeze
, "dispatch_source_create (ds_freeze)");
221 dispatch_source_set_event_handler(ds_freeze
, ^{
222 if (!(dt_stat_stable(compr_time
) && decompr_latency_is_stable
)) {
223 freeze_helper_process();
225 dt_stat_finalize(compr_time
);
226 dt_stat_finalize(ratio
);
229 dispatch_source_cancel(ds_freeze
);
230 dispatch_source_cancel(ds_decompr
);
233 dispatch_activate(ds_freeze
);
235 testpath_buf_size
= sizeof(testpath
);
236 ret
= _NSGetExecutablePath(testpath
, &testpath_buf_size
);
237 T_QUIET
; T_ASSERT_POSIX_ZERO(ret
, "_NSGetExecutablePath");
238 T_LOG("Executable path: %s", testpath
);
240 sprintf(sz_str
, "%d", size_mb
);
241 sprintf(pt_str
, "%d", page_type
);
242 launch_tool_args
= (char *[]){
252 /* Spawn the child process. Suspend after launch until the exit proc handler has been set up. */
253 ret
= dt_launch_tool(&pid
, launch_tool_args
, true, NULL
, NULL
);
255 T_LOG("dt_launch tool returned %d with error code %d", ret
, errno
);
257 T_QUIET
; T_ASSERT_POSIX_SUCCESS(pid
, "dt_launch_tool");
259 ds_proc
= dispatch_source_create(DISPATCH_SOURCE_TYPE_PROC
, (uintptr_t)pid
, DISPATCH_PROC_EXIT
, dispatch_get_main_queue());
260 T_QUIET
; T_ASSERT_NOTNULL(ds_proc
, "dispatch_source_create (ds_proc)");
262 dispatch_source_set_event_handler(ds_proc
, ^{
263 int status
= 0, code
= 0;
264 pid_t rc
= waitpid(pid
, &status
, 0);
265 T_QUIET
; T_ASSERT_EQ(rc
, pid
, "waitpid");
266 code
= WEXITSTATUS(status
);
270 } else if (code
> 0 && code
< EXIT_CODE_MAX
) {
271 T_ASSERT_FAIL("Child exited with %s", exit_codes_str
[code
]);
273 T_ASSERT_FAIL("Child exited with unknown exit code %d", code
);
276 dispatch_activate(ds_proc
);
278 T_QUIET
; T_ASSERT_POSIX_SUCCESS(kill(pid
, SIGCONT
), "failed to send SIGCONT to child process");
282 T_HELPER_DECL(allocate_pages
, "allocates pages to compress") {
283 int i
, j
, ret
, size_mb
, page_type
, vmpgsize
, freezable_state
;
284 size_t vmpgsize_length
;
285 __block
int num_pages
;
287 dispatch_source_t ds_signal
;
289 vmpgsize_length
= sizeof(vmpgsize
);
290 ret
= sysctlbyname("vm.pagesize", &vmpgsize
, &vmpgsize_length
, NULL
, 0);
292 exit(SYSCTL_VM_PAGESIZE_FAILED
);
295 exit(VM_PAGESIZE_IS_ZERO
);
299 exit(TOO_FEW_ARGUMENTS
);
302 size_mb
= atoi(argv
[0]);
303 page_type
= atoi(argv
[1]);
304 num_pages
= size_mb
* 1024 * 1024 / vmpgsize
;
305 buf
= (char**)malloc(sizeof(char*) * (size_t)num_pages
);
307 // Switch on the type of page requested
310 allocate_zero_pages(buf
, num_pages
, vmpgsize
);
313 allocate_mostly_zero_pages(buf
, num_pages
, vmpgsize
);
316 allocate_random_pages(buf
, num_pages
, vmpgsize
);
319 allocate_representative_pages(buf
, num_pages
, vmpgsize
);
322 exit(UNKNOWN_PAGE_TYPE
);
325 for (j
= 0; j
< num_pages
; j
++) {
329 decompr_time
= dt_stat_time_create("decompression_latency");
331 /* Opt in to freezing. */
332 printf("[%d] Setting state to freezable\n", getpid());
333 if (memorystatus_control(MEMORYSTATUS_CMD_SET_PROCESS_IS_FREEZABLE
, getpid(), 1, NULL
, 0) != KERN_SUCCESS
) {
334 exit(MEMORYSTATUS_CONTROL_FAILED
);
337 /* Verify that the state has been set correctly */
338 freezable_state
= memorystatus_control(MEMORYSTATUS_CMD_GET_PROCESS_IS_FREEZABLE
, getpid(), 0, NULL
, 0);
339 if (freezable_state
!= 1) {
340 exit(IS_FREEZABLE_NOT_AS_EXPECTED
);
343 dispatch_after(dispatch_time(DISPATCH_TIME_NOW
, NSEC_PER_SEC
), dispatch_get_main_queue(), ^{
344 /* Signal to the parent that we're done allocating and it's ok to freeze us */
345 printf("[%d] Sending initial signal to parent to begin freezing\n", getpid());
346 if (kill(getppid(), SIGUSR1
) != 0) {
347 exit(INITIAL_SIGNAL_TO_PARENT_FAILED
);
351 signal(SIGUSR1
, SIG_IGN
);
352 ds_signal
= dispatch_source_create(DISPATCH_SOURCE_TYPE_SIGNAL
, SIGUSR1
, 0, dispatch_get_main_queue());
353 if (ds_signal
== NULL
) {
354 exit(DISPATCH_SOURCE_CREATE_FAILED
);
357 __block
bool collect_dt_stat_measurements
= true;
359 dispatch_source_set_event_handler(ds_signal
, ^{
361 uint64_t decompr_start_time
, decompr_end_time
;
363 decompr_start_time
= mach_absolute_time();
365 /* Make sure all the pages are accessed before trying to freeze again */
366 for (int x
= 0; x
< num_pages
; x
++) {
370 decompr_end_time
= mach_absolute_time();
372 if (collect_dt_stat_measurements
) {
373 if (dt_stat_stable(decompr_time
)) {
374 collect_dt_stat_measurements
= false;
375 dt_stat_finalize(decompr_time
);
376 if (kill(getppid(), SIGUSR2
) != 0) {
377 exit(SIGNAL_TO_PARENT_FAILED
);
380 dt_stat_mach_time_add(decompr_time
, decompr_end_time
- decompr_start_time
);
384 if (kill(getppid(), SIGUSR1
) != 0) {
385 exit(SIGNAL_TO_PARENT_FAILED
);
388 dispatch_activate(ds_signal
);
393 // Numbers for 10MB and above are fairly reproducible. Anything smaller shows a lot of variation.
395 // Keeping just the 100MB version for iOSMark
397 T_DECL(compr_10MB_zero
,
398 "Compression latency for 10MB - zero pages",
400 T_META_SYSCTL_INT(SYSCTL_FREEZE_TO_MEMORY
)) {
401 run_compressor_test(10, ALL_ZEROS
);
404 T_DECL(compr_10MB_mostly_zero
,
405 "Compression latency for 10MB - mostly zero pages",
407 T_META_SYSCTL_INT(SYSCTL_FREEZE_TO_MEMORY
)) {
408 run_compressor_test(10, MOSTLY_ZEROS
);
411 T_DECL(compr_10MB_random
,
412 "Compression latency for 10MB - random pages",
414 T_META_SYSCTL_INT(SYSCTL_FREEZE_TO_MEMORY
)) {
415 run_compressor_test(10, RANDOM
);
418 T_DECL(compr_10MB_typical
,
419 "Compression latency for 10MB - typical pages",
421 T_META_SYSCTL_INT(SYSCTL_FREEZE_TO_MEMORY
)) {
422 run_compressor_test(10, TYPICAL
);
425 T_DECL(compr_100MB_zero
,
426 "Compression latency for 100MB - zero pages",
428 T_META_SYSCTL_INT(SYSCTL_FREEZE_TO_MEMORY
)) {
429 run_compressor_test(100, ALL_ZEROS
);
432 T_DECL(compr_100MB_mostly_zero
,
433 "Compression latency for 100MB - mostly zero pages",
435 T_META_SYSCTL_INT(SYSCTL_FREEZE_TO_MEMORY
)) {
436 run_compressor_test(100, MOSTLY_ZEROS
);
439 T_DECL(compr_100MB_random
,
440 "Compression latency for 100MB - random pages",
442 T_META_SYSCTL_INT(SYSCTL_FREEZE_TO_MEMORY
)) {
443 run_compressor_test(100, RANDOM
);
447 T_DECL(compr_100MB_typical
,
448 "Compression latency for 100MB - typical pages",
450 T_META_SYSCTL_INT(SYSCTL_FREEZE_TO_MEMORY
)) {
451 run_compressor_test(100, TYPICAL
);