3 #include <sys/sysctl.h>
4 #include <mach-o/dyld.h>
5 #include <perfcheck_keys.h>
10 #include <darwintest.h>
11 #include <darwintest_utils.h>
14 T_META_NAMESPACE("xnu.vm.perf"),
15 T_META_CHECK_LEAKS(false),
26 #define CREATE_LIST(X) \
28 X(TOO_FEW_ARGUMENTS) \
29 X(SYSCTL_VM_PAGESIZE_FAILED) \
30 X(VM_PAGESIZE_IS_ZERO) \
31 X(UNKNOWN_PAGE_TYPE) \
32 X(DISPATCH_SOURCE_CREATE_FAILED) \
33 X(INITIAL_SIGNAL_TO_PARENT_FAILED) \
34 X(SIGNAL_TO_PARENT_FAILED) \
37 #define EXIT_CODES_ENUM(VAR) VAR,
39 CREATE_LIST(EXIT_CODES_ENUM
)
42 #define EXIT_CODES_STRING(VAR) #VAR,
43 static const char *exit_codes_str
[] = {
44 CREATE_LIST(EXIT_CODES_STRING
)
47 #define SYSCTL_FREEZE_TO_MEMORY "kern.memorystatus_freeze_to_memory=1"
49 static pid_t pid
= -1;
51 static dt_stat_time_t s
;
53 void allocate_zero_pages(char **buf
, int num_pages
, int vmpgsize
);
54 void allocate_mostly_zero_pages(char **buf
, int num_pages
, int vmpgsize
);
55 void allocate_random_pages(char **buf
, int num_pages
, int vmpgsize
);
56 void allocate_representative_pages(char **buf
, int num_pages
, int vmpgsize
);
57 void run_compressor_test(int size_mb
, int page_type
);
58 void freeze_helper_process(void);
62 allocate_zero_pages(char **buf
, int num_pages
, int vmpgsize
)
66 for (i
= 0; i
< num_pages
; i
++) {
67 buf
[i
] = (char*)malloc((size_t)vmpgsize
* sizeof(char));
68 memset(buf
[i
], 0, vmpgsize
);
73 allocate_mostly_zero_pages(char **buf
, int num_pages
, int vmpgsize
)
77 for (i
= 0; i
< num_pages
; i
++) {
78 buf
[i
] = (char*)malloc((size_t)vmpgsize
* sizeof(char));
79 memset(buf
[i
], 0, vmpgsize
);
80 for (j
= 0; j
< 40; j
++) {
81 buf
[i
][j
] = (char)(j
+ 1);
87 allocate_random_pages(char **buf
, int num_pages
, int vmpgsize
)
91 for (i
= 0; i
< num_pages
; i
++) {
92 buf
[i
] = (char*)malloc((size_t)vmpgsize
* sizeof(char));
93 arc4random_buf((void*)buf
[i
], (size_t)vmpgsize
);
97 // Gives us the compression ratio we see in the typical case (~2.7)
99 allocate_representative_pages(char **buf
, int num_pages
, int vmpgsize
)
104 for (j
= 0; j
< num_pages
; j
++) {
105 buf
[j
] = (char*)malloc((size_t)vmpgsize
* sizeof(char));
107 for (i
= 0; i
< vmpgsize
; i
+= 16) {
108 memset(&buf
[j
][i
], val
, 16);
109 if (i
< 3400 * (vmpgsize
/ 4096)) {
117 freeze_helper_process(void)
119 int ret
, freeze_enabled
;
120 int64_t compressed_before
, compressed_after
, input_before
, input_after
;
122 int errno_sysctl_freeze
;
124 length
= sizeof(compressed_before
);
125 T_QUIET
; T_ASSERT_POSIX_SUCCESS(sysctlbyname("vm.compressor_compressed_bytes", &compressed_before
, &length
, NULL
, 0),
126 "failed to query vm.compressor_compressed_bytes");
127 length
= sizeof(input_before
);
128 T_QUIET
; T_ASSERT_POSIX_SUCCESS(sysctlbyname("vm.compressor_input_bytes", &input_before
, &length
, NULL
, 0),
129 "failed to query vm.compressor_input_bytes");
132 ret
= sysctlbyname("kern.memorystatus_freeze", NULL
, NULL
, &pid
, sizeof(pid
));
133 errno_sysctl_freeze
= errno
;
136 length
= sizeof(compressed_after
);
137 T_QUIET
; T_ASSERT_POSIX_SUCCESS(sysctlbyname("vm.compressor_compressed_bytes", &compressed_after
, &length
, NULL
, 0),
138 "failed to query vm.compressor_compressed_bytes");
139 length
= sizeof(input_after
);
140 T_QUIET
; T_ASSERT_POSIX_SUCCESS(sysctlbyname("vm.compressor_input_bytes", &input_after
, &length
, NULL
, 0),
141 "failed to query vm.compressor_input_bytes");
143 length
= sizeof(freeze_enabled
);
144 T_QUIET
; T_ASSERT_POSIX_SUCCESS(sysctlbyname("vm.freeze_enabled", &freeze_enabled
, &length
, NULL
, 0),
145 "failed to query vm.freeze_enabled");
146 if (freeze_enabled
) {
147 errno
= errno_sysctl_freeze
;
148 T_QUIET
; T_ASSERT_POSIX_SUCCESS(ret
, "sysctl kern.memorystatus_freeze failed");
150 /* If freezer is disabled, skip the test. This can happen due to disk space shortage. */
151 T_LOG("Freeze has been disabled. Terminating early.");
155 dt_stat_add(r
, (double)(input_after
- input_before
) / (double)(compressed_after
- compressed_before
));
157 ret
= sysctlbyname("kern.memorystatus_thaw", NULL
, NULL
, &pid
, sizeof(pid
));
158 T_QUIET
; T_ASSERT_POSIX_SUCCESS(ret
, "sysctl kern.memorystatus_thaw failed");
160 T_QUIET
; T_ASSERT_POSIX_SUCCESS(kill(pid
, SIGUSR1
), "failed to send SIGUSR1 to child process");
168 /* No helper process. */
172 /* Kill the helper process. */
177 run_compressor_test(int size_mb
, int page_type
)
182 char **launch_tool_args
;
183 char testpath
[PATH_MAX
];
184 uint32_t testpath_buf_size
;
185 dispatch_source_t ds_freeze
, ds_proc
;
189 length
= sizeof(freeze_enabled
);
190 T_QUIET
; T_ASSERT_POSIX_SUCCESS(sysctlbyname("vm.freeze_enabled", &freeze_enabled
, &length
, NULL
, 0),
191 "failed to query vm.freeze_enabled");
192 if (!freeze_enabled
) {
193 /* If freezer is disabled, skip the test. This can happen due to disk space shortage. */
194 T_SKIP("Freeze has been disabled. Skipping test.");
199 r
= dt_stat_create("(input bytes / compressed bytes)", "compression_ratio");
200 s
= dt_stat_time_create("compressor_latency");
201 // This sets the A/B failure threshold at 50% of baseline for compressor_latency
202 dt_stat_set_variable(s
, kPCFailureThresholdPctVar
, 50.0);
204 signal(SIGUSR1
, SIG_IGN
);
205 ds_freeze
= dispatch_source_create(DISPATCH_SOURCE_TYPE_SIGNAL
, SIGUSR1
, 0, dispatch_get_main_queue());
206 T_QUIET
; T_ASSERT_NOTNULL(ds_freeze
, "dispatch_source_create (ds_freeze)");
208 dispatch_source_set_event_handler(ds_freeze
, ^{
209 if (!dt_stat_stable(s
)) {
210 freeze_helper_process();
216 dispatch_source_cancel(ds_freeze
);
219 dispatch_activate(ds_freeze
);
221 testpath_buf_size
= sizeof(testpath
);
222 ret
= _NSGetExecutablePath(testpath
, &testpath_buf_size
);
223 T_QUIET
; T_ASSERT_POSIX_ZERO(ret
, "_NSGetExecutablePath");
224 T_LOG("Executable path: %s", testpath
);
226 sprintf(sz_str
, "%d", size_mb
);
227 sprintf(pt_str
, "%d", page_type
);
228 launch_tool_args
= (char *[]){
238 /* Spawn the child process. Suspend after launch until the exit proc handler has been set up. */
239 ret
= dt_launch_tool(&pid
, launch_tool_args
, true, NULL
, NULL
);
241 T_LOG("dt_launch tool returned %d with error code %d", ret
, errno
);
243 T_QUIET
; T_ASSERT_POSIX_SUCCESS(pid
, "dt_launch_tool");
245 ds_proc
= dispatch_source_create(DISPATCH_SOURCE_TYPE_PROC
, (uintptr_t)pid
, DISPATCH_PROC_EXIT
, dispatch_get_main_queue());
246 T_QUIET
; T_ASSERT_NOTNULL(ds_proc
, "dispatch_source_create (ds_proc)");
248 dispatch_source_set_event_handler(ds_proc
, ^{
249 int status
= 0, code
= 0;
250 pid_t rc
= waitpid(pid
, &status
, 0);
251 T_QUIET
; T_ASSERT_EQ(rc
, pid
, "waitpid");
252 code
= WEXITSTATUS(status
);
256 } else if (code
> 0 && code
< EXIT_CODE_MAX
) {
257 T_ASSERT_FAIL("Child exited with %s", exit_codes_str
[code
]);
259 T_ASSERT_FAIL("Child exited with unknown exit code %d", code
);
262 dispatch_activate(ds_proc
);
264 T_QUIET
; T_ASSERT_POSIX_SUCCESS(kill(pid
, SIGCONT
), "failed to send SIGCONT to child process");
268 T_HELPER_DECL(allocate_pages
, "allocates pages to compress") {
269 int i
, j
, ret
, size_mb
, page_type
, vmpgsize
;
270 size_t vmpgsize_length
;
271 __block
int num_pages
;
273 dispatch_source_t ds_signal
;
275 vmpgsize_length
= sizeof(vmpgsize
);
276 ret
= sysctlbyname("vm.pagesize", &vmpgsize
, &vmpgsize_length
, NULL
, 0);
278 exit(SYSCTL_VM_PAGESIZE_FAILED
);
281 exit(VM_PAGESIZE_IS_ZERO
);
285 exit(TOO_FEW_ARGUMENTS
);
288 size_mb
= atoi(argv
[0]);
289 page_type
= atoi(argv
[1]);
290 num_pages
= size_mb
* 1024 * 1024 / vmpgsize
;
291 buf
= (char**)malloc(sizeof(char*) * (size_t)num_pages
);
293 // Switch on the type of page requested
296 allocate_zero_pages(buf
, num_pages
, vmpgsize
);
299 allocate_mostly_zero_pages(buf
, num_pages
, vmpgsize
);
302 allocate_random_pages(buf
, num_pages
, vmpgsize
);
305 allocate_representative_pages(buf
, num_pages
, vmpgsize
);
308 exit(UNKNOWN_PAGE_TYPE
);
311 for (j
= 0; j
< num_pages
; j
++) {
315 dispatch_after(dispatch_time(DISPATCH_TIME_NOW
, NSEC_PER_SEC
), dispatch_get_main_queue(), ^{
316 /* Signal to the parent that we're done allocating and it's ok to freeze us */
317 printf("[%d] Sending initial signal to parent to begin freezing\n", getpid());
318 if (kill(getppid(), SIGUSR1
) != 0) {
319 exit(INITIAL_SIGNAL_TO_PARENT_FAILED
);
323 signal(SIGUSR1
, SIG_IGN
);
324 ds_signal
= dispatch_source_create(DISPATCH_SOURCE_TYPE_SIGNAL
, SIGUSR1
, 0, dispatch_get_main_queue());
325 if (ds_signal
== NULL
) {
326 exit(DISPATCH_SOURCE_CREATE_FAILED
);
329 dispatch_source_set_event_handler(ds_signal
, ^{
332 /* Make sure all the pages are accessed before trying to freeze again */
333 for (int x
= 0; x
< num_pages
; x
++) {
336 if (kill(getppid(), SIGUSR1
) != 0) {
337 exit(SIGNAL_TO_PARENT_FAILED
);
340 dispatch_activate(ds_signal
);
345 // Numbers for 10MB and above are fairly reproducible. Anything smaller shows a lot of variation.
347 // Keeping just the 100MB version for iOSMark
349 T_DECL(compr_10MB_zero
,
350 "Compression latency for 10MB - zero pages",
351 T_META_SYSCTL_INT(SYSCTL_FREEZE_TO_MEMORY
)) {
352 run_compressor_test(10, ALL_ZEROS
);
355 T_DECL(compr_10MB_mostly_zero
,
356 "Compression latency for 10MB - mostly zero pages",
357 T_META_SYSCTL_INT(SYSCTL_FREEZE_TO_MEMORY
)) {
358 run_compressor_test(10, MOSTLY_ZEROS
);
361 T_DECL(compr_10MB_random
,
362 "Compression latency for 10MB - random pages",
363 T_META_SYSCTL_INT(SYSCTL_FREEZE_TO_MEMORY
)) {
364 run_compressor_test(10, RANDOM
);
367 T_DECL(compr_10MB_typical
,
368 "Compression latency for 10MB - typical pages",
369 T_META_SYSCTL_INT(SYSCTL_FREEZE_TO_MEMORY
)) {
370 run_compressor_test(10, TYPICAL
);
373 T_DECL(compr_100MB_zero
,
374 "Compression latency for 100MB - zero pages",
375 T_META_SYSCTL_INT(SYSCTL_FREEZE_TO_MEMORY
)) {
376 run_compressor_test(100, ALL_ZEROS
);
379 T_DECL(compr_100MB_mostly_zero
,
380 "Compression latency for 100MB - mostly zero pages",
381 T_META_SYSCTL_INT(SYSCTL_FREEZE_TO_MEMORY
)) {
382 run_compressor_test(100, MOSTLY_ZEROS
);
385 T_DECL(compr_100MB_random
,
386 "Compression latency for 100MB - random pages",
387 T_META_SYSCTL_INT(SYSCTL_FREEZE_TO_MEMORY
)) {
388 run_compressor_test(100, RANDOM
);
392 T_DECL(compr_100MB_typical
,
393 "Compression latency for 100MB - typical pages",
394 T_META_SYSCTL_INT(SYSCTL_FREEZE_TO_MEMORY
)) {
395 run_compressor_test(100, TYPICAL
);