]> git.saurik.com Git - apple/xnu.git/blob - tests/memorystatus_freeze_test.c
58aa696591866d804e69c04cce248773e2473c38
[apple/xnu.git] / tests / memorystatus_freeze_test.c
1 #include <stdio.h>
2 #include <signal.h>
3 #include <sys/sysctl.h>
4 #include <sys/kern_memorystatus.h>
5 #include <sys/kern_memorystatus_freeze.h>
6 #include <time.h>
7 #include <mach-o/dyld.h>
8 #include <mach/mach_vm.h>
9 #include <mach/vm_page_size.h> /* Needed for vm_region info */
10 #include <mach/shared_region.h>
11 #include <mach/mach.h>
12
13 #ifdef T_NAMESPACE
14 #undef T_NAMESPACE
15 #endif
16 #include <darwintest.h>
17 #include <darwintest_utils.h>
18
19 #include "memorystatus_assertion_helpers.h"
20
21 T_GLOBAL_META(
22 T_META_NAMESPACE("xnu.vm"),
23 T_META_CHECK_LEAKS(false)
24 );
25
26 #define MEM_SIZE_MB 10
27 #define NUM_ITERATIONS 5
28 #define FREEZE_PAGES_MAX 256
29
30 #define CREATE_LIST(X) \
31 X(SUCCESS) \
32 X(TOO_FEW_ARGUMENTS) \
33 X(SYSCTL_VM_PAGESIZE_FAILED) \
34 X(VM_PAGESIZE_IS_ZERO) \
35 X(DISPATCH_SOURCE_CREATE_FAILED) \
36 X(INITIAL_SIGNAL_TO_PARENT_FAILED) \
37 X(SIGNAL_TO_PARENT_FAILED) \
38 X(MEMORYSTATUS_CONTROL_FAILED) \
39 X(IS_FREEZABLE_NOT_AS_EXPECTED) \
40 X(MEMSTAT_PRIORITY_CHANGE_FAILED) \
41 X(INVALID_ALLOCATE_PAGES_ARGUMENTS) \
42 X(EXIT_CODE_MAX)
43
44 #define EXIT_CODES_ENUM(VAR) VAR,
45 enum exit_codes_num {
46 CREATE_LIST(EXIT_CODES_ENUM)
47 };
48
49 #define EXIT_CODES_STRING(VAR) #VAR,
50 static const char *exit_codes_str[] = {
51 CREATE_LIST(EXIT_CODES_STRING)
52 };
53
54 static int
55 get_vmpage_size()
56 {
57 int vmpage_size;
58 size_t size = sizeof(vmpage_size);
59 int ret = sysctlbyname("vm.pagesize", &vmpage_size, &size, NULL, 0);
60 T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "failed to query vm.pagesize");
61 T_QUIET; T_ASSERT_GT(vmpage_size, 0, "vm.pagesize is not > 0");
62 return vmpage_size;
63 }
64
65 static pid_t child_pid = -1;
66 static int freeze_count = 0;
67
68 void move_to_idle_band(pid_t);
69 void run_freezer_test(int);
70 void freeze_helper_process(void);
71 /* Gets and optionally sets the freeze pages max threshold */
72 int sysctl_freeze_pages_max(int* new_value);
73
74 /* NB: in_shared_region and get_rprvt are pulled from the memorystatus unit test.
75 * We're moving away from those unit tests, so they're copied here.
76 */
77
78 /* Cribbed from 'top'... */
79 static int
80 in_shared_region(mach_vm_address_t addr, cpu_type_t type)
81 {
82 mach_vm_address_t base = 0, size = 0;
83
84 switch (type) {
85 case CPU_TYPE_ARM:
86 base = SHARED_REGION_BASE_ARM;
87 size = SHARED_REGION_SIZE_ARM;
88 break;
89
90 case CPU_TYPE_ARM64:
91 base = SHARED_REGION_BASE_ARM64;
92 size = SHARED_REGION_SIZE_ARM64;
93 break;
94
95
96 case CPU_TYPE_X86_64:
97 base = SHARED_REGION_BASE_X86_64;
98 size = SHARED_REGION_SIZE_X86_64;
99 break;
100
101 case CPU_TYPE_I386:
102 base = SHARED_REGION_BASE_I386;
103 size = SHARED_REGION_SIZE_I386;
104 break;
105
106 case CPU_TYPE_POWERPC:
107 base = SHARED_REGION_BASE_PPC;
108 size = SHARED_REGION_SIZE_PPC;
109 break;
110
111 case CPU_TYPE_POWERPC64:
112 base = SHARED_REGION_BASE_PPC64;
113 size = SHARED_REGION_SIZE_PPC64;
114 break;
115
116 default: {
117 int t = type;
118
119 fprintf(stderr, "unknown CPU type: 0x%x\n", t);
120 abort();
121 }
122 }
123
124 return addr >= base && addr < (base + size);
125 }
126
127 /* Get the resident private memory of the given pid */
128 static unsigned long long
129 get_rprvt(pid_t pid)
130 {
131 mach_port_name_t task;
132 kern_return_t kr;
133
134 mach_vm_size_t rprvt = 0;
135 mach_vm_size_t empty = 0;
136 mach_vm_size_t fw_private = 0;
137 mach_vm_size_t pagesize = vm_kernel_page_size; // The vm_region page info is reported
138 // in terms of vm_kernel_page_size.
139 mach_vm_size_t regs = 0;
140
141 mach_vm_address_t addr;
142 mach_vm_size_t size;
143
144 int split = 0;
145
146 kr = task_for_pid(mach_task_self(), pid, &task);
147 T_QUIET; T_ASSERT_TRUE(kr == KERN_SUCCESS, "Unable to get task_for_pid of child");
148
149 for (addr = 0;; addr += size) {
150 vm_region_top_info_data_t info;
151 mach_msg_type_number_t count = VM_REGION_TOP_INFO_COUNT;
152 mach_port_t object_name;
153
154 kr = mach_vm_region(task, &addr, &size, VM_REGION_TOP_INFO, (vm_region_info_t)&info, &count, &object_name);
155 if (kr != KERN_SUCCESS) {
156 break;
157 }
158
159 #if defined (__arm64__)
160 if (in_shared_region(addr, CPU_TYPE_ARM64)) {
161 #else
162 if (in_shared_region(addr, CPU_TYPE_ARM)) {
163 #endif
164 // Private Shared
165 fw_private += info.private_pages_resident * pagesize;
166
167 /*
168 * Check if this process has the globally shared
169 * text and data regions mapped in. If so, set
170 * split to TRUE and avoid checking
171 * again.
172 */
173 if (split == FALSE && info.share_mode == SM_EMPTY) {
174 vm_region_basic_info_data_64_t b_info;
175 mach_vm_address_t b_addr = addr;
176 mach_vm_size_t b_size = size;
177 count = VM_REGION_BASIC_INFO_COUNT_64;
178
179 kr = mach_vm_region(task, &b_addr, &b_size, VM_REGION_BASIC_INFO_64, (vm_region_info_t)&b_info, &count, &object_name);
180 if (kr != KERN_SUCCESS) {
181 break;
182 }
183
184 if (b_info.reserved) {
185 split = TRUE;
186 }
187 }
188
189 /*
190 * Short circuit the loop if this isn't a shared
191 * private region, since that's the only region
192 * type we care about within the current address
193 * range.
194 */
195 if (info.share_mode != SM_PRIVATE) {
196 continue;
197 }
198 }
199
200 regs++;
201
202 /*
203 * Update counters according to the region type.
204 */
205
206 if (info.share_mode == SM_COW && info.ref_count == 1) {
207 // Treat single reference SM_COW as SM_PRIVATE
208 info.share_mode = SM_PRIVATE;
209 }
210
211 switch (info.share_mode) {
212 case SM_LARGE_PAGE:
213 // Treat SM_LARGE_PAGE the same as SM_PRIVATE
214 // since they are not shareable and are wired.
215 case SM_PRIVATE:
216 rprvt += info.private_pages_resident * pagesize;
217 rprvt += info.shared_pages_resident * pagesize;
218 break;
219
220 case SM_EMPTY:
221 empty += size;
222 break;
223
224 case SM_COW:
225 case SM_SHARED:
226 if (pid == 0) {
227 // Treat kernel_task specially
228 if (info.share_mode == SM_COW) {
229 rprvt += info.private_pages_resident * pagesize;
230 }
231 break;
232 }
233
234 if (info.share_mode == SM_COW) {
235 rprvt += info.private_pages_resident * pagesize;
236 }
237 break;
238
239 default:
240 assert(0);
241 break;
242 }
243 }
244
245 return rprvt;
246 }
247
248 void
249 move_to_idle_band(pid_t pid)
250 {
251 memorystatus_priority_properties_t props;
252 /*
253 * Freezing a process also moves it to an elevated jetsam band in order to protect it from idle exits.
254 * So we move the child process to the idle band to mirror the typical 'idle app being frozen' scenario.
255 */
256 props.priority = JETSAM_PRIORITY_IDLE;
257 props.user_data = 0;
258
259 /*
260 * This requires us to run as root (in the absence of entitlement).
261 * Hence the T_META_ASROOT(true) in the T_HELPER_DECL.
262 */
263 if (memorystatus_control(MEMORYSTATUS_CMD_SET_PRIORITY_PROPERTIES, pid, 0, &props, sizeof(props))) {
264 exit(MEMSTAT_PRIORITY_CHANGE_FAILED);
265 }
266 }
267
268 void
269 freeze_helper_process(void)
270 {
271 size_t length;
272 int ret, freeze_enabled, errno_freeze_sysctl;
273 uint64_t resident_memory_before, resident_memory_after, vmpage_size;
274 vmpage_size = (uint64_t) get_vmpage_size();
275 resident_memory_before = get_rprvt(child_pid) / vmpage_size;
276
277 T_LOG("Freezing child pid %d", child_pid);
278 ret = sysctlbyname("kern.memorystatus_freeze", NULL, NULL, &child_pid, sizeof(child_pid));
279 errno_freeze_sysctl = errno;
280 sleep(1);
281
282 /*
283 * The child process toggles its freezable state on each iteration.
284 * So a failure for every alternate freeze is expected.
285 */
286 if (freeze_count % 2) {
287 length = sizeof(freeze_enabled);
288 T_QUIET; T_ASSERT_POSIX_SUCCESS(sysctlbyname("vm.freeze_enabled", &freeze_enabled, &length, NULL, 0),
289 "failed to query vm.freeze_enabled");
290 if (freeze_enabled) {
291 errno = errno_freeze_sysctl;
292 T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "sysctl kern.memorystatus_freeze failed");
293 } else {
294 /* If freezer is disabled, skip the test. This can happen due to disk space shortage. */
295 T_LOG("Freeze has been disabled. Terminating early.");
296 T_END;
297 }
298 resident_memory_after = get_rprvt(child_pid) / vmpage_size;
299 uint64_t freeze_pages_max = (uint64_t) sysctl_freeze_pages_max(NULL);
300 T_QUIET; T_ASSERT_LT(resident_memory_after, resident_memory_before, "Freeze didn't reduce resident memory set");
301 if (resident_memory_before > freeze_pages_max) {
302 T_QUIET; T_ASSERT_LE(resident_memory_before - resident_memory_after, freeze_pages_max, "Freeze pages froze more than the threshold.");
303 }
304 ret = sysctlbyname("kern.memorystatus_thaw", NULL, NULL, &child_pid, sizeof(child_pid));
305 T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "sysctl kern.memorystatus_thaw failed");
306 } else {
307 T_QUIET; T_ASSERT_TRUE(ret != KERN_SUCCESS, "Freeze should have failed");
308 T_LOG("Freeze failed as expected");
309 }
310
311 freeze_count++;
312
313 T_QUIET; T_ASSERT_POSIX_SUCCESS(kill(child_pid, SIGUSR1), "failed to send SIGUSR1 to child process");
314 }
315
316 static void
317 skip_if_freezer_is_disabled()
318 {
319 int freeze_enabled;
320 size_t length = sizeof(freeze_enabled);
321
322 T_QUIET; T_ASSERT_POSIX_SUCCESS(sysctlbyname("vm.freeze_enabled", &freeze_enabled, &length, NULL, 0),
323 "failed to query vm.freeze_enabled");
324 if (!freeze_enabled) {
325 /* If freezer is disabled, skip the test. This can happen due to disk space shortage. */
326 T_SKIP("Freeze has been disabled. Skipping test.");
327 }
328 }
329
330 void
331 run_freezer_test(int num_pages)
332 {
333 int ret;
334 char sz_str[50];
335 char **launch_tool_args;
336 char testpath[PATH_MAX];
337 uint32_t testpath_buf_size;
338 dispatch_source_t ds_freeze, ds_proc;
339
340 skip_if_freezer_is_disabled();
341
342 signal(SIGUSR1, SIG_IGN);
343 ds_freeze = dispatch_source_create(DISPATCH_SOURCE_TYPE_SIGNAL, SIGUSR1, 0, dispatch_get_main_queue());
344 T_QUIET; T_ASSERT_NOTNULL(ds_freeze, "dispatch_source_create (ds_freeze)");
345
346 dispatch_source_set_event_handler(ds_freeze, ^{
347 if (freeze_count < NUM_ITERATIONS) {
348 freeze_helper_process();
349 } else {
350 kill(child_pid, SIGKILL);
351 dispatch_source_cancel(ds_freeze);
352 }
353 });
354 dispatch_activate(ds_freeze);
355
356 testpath_buf_size = sizeof(testpath);
357 ret = _NSGetExecutablePath(testpath, &testpath_buf_size);
358 T_QUIET; T_ASSERT_POSIX_ZERO(ret, "_NSGetExecutablePath");
359 T_LOG("Executable path: %s", testpath);
360
361 sprintf(sz_str, "%d", num_pages);
362 launch_tool_args = (char *[]){
363 testpath,
364 "-n",
365 "allocate_pages",
366 "--",
367 sz_str,
368 NULL
369 };
370
371 /* Spawn the child process. Suspend after launch until the exit proc handler has been set up. */
372 ret = dt_launch_tool(&child_pid, launch_tool_args, true, NULL, NULL);
373 if (ret != 0) {
374 T_LOG("dt_launch tool returned %d with error code %d", ret, errno);
375 }
376 T_QUIET; T_ASSERT_POSIX_SUCCESS(child_pid, "dt_launch_tool");
377
378 ds_proc = dispatch_source_create(DISPATCH_SOURCE_TYPE_PROC, (uintptr_t)child_pid, DISPATCH_PROC_EXIT, dispatch_get_main_queue());
379 T_QUIET; T_ASSERT_NOTNULL(ds_proc, "dispatch_source_create (ds_proc)");
380
381 dispatch_source_set_event_handler(ds_proc, ^{
382 int status = 0, code = 0;
383 pid_t rc = waitpid(child_pid, &status, 0);
384 T_QUIET; T_ASSERT_EQ(rc, child_pid, "waitpid");
385 code = WEXITSTATUS(status);
386
387 if (code == 0) {
388 T_END;
389 } else if (code > 0 && code < EXIT_CODE_MAX) {
390 T_ASSERT_FAIL("Child exited with %s", exit_codes_str[code]);
391 } else {
392 T_ASSERT_FAIL("Child exited with unknown exit code %d", code);
393 }
394 });
395 dispatch_activate(ds_proc);
396
397 T_QUIET; T_ASSERT_POSIX_SUCCESS(kill(child_pid, SIGCONT), "failed to send SIGCONT to child process");
398 dispatch_main();
399 }
400
401 static void
402 allocate_pages(int num_pages)
403 {
404 int i, j, vmpgsize;
405 char val;
406 __block int num_iter = 0;
407 __block char **buf;
408 dispatch_source_t ds_signal;
409 vmpgsize = get_vmpage_size();
410 if (num_pages < 1) {
411 printf("Invalid number of pages to allocate: %d\n", num_pages);
412 exit(INVALID_ALLOCATE_PAGES_ARGUMENTS);
413 }
414
415 buf = (char**)malloc(sizeof(char*) * (size_t)num_pages);
416
417 /* Gives us the compression ratio we see in the typical case (~2.7) */
418 for (j = 0; j < num_pages; j++) {
419 buf[j] = (char*)malloc((size_t)vmpgsize * sizeof(char));
420 val = 0;
421 for (i = 0; i < vmpgsize; i += 16) {
422 memset(&buf[j][i], val, 16);
423 if (i < 3400 * (vmpgsize / 4096)) {
424 val++;
425 }
426 }
427 }
428
429 dispatch_after(dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC), dispatch_get_main_queue(), ^{
430 /* Signal to the parent that we're done allocating and it's ok to freeze us */
431 printf("[%d] Sending initial signal to parent to begin freezing\n", getpid());
432 if (kill(getppid(), SIGUSR1) != 0) {
433 exit(INITIAL_SIGNAL_TO_PARENT_FAILED);
434 }
435 });
436
437 signal(SIGUSR1, SIG_IGN);
438 ds_signal = dispatch_source_create(DISPATCH_SOURCE_TYPE_SIGNAL, SIGUSR1, 0, dispatch_get_main_queue());
439 if (ds_signal == NULL) {
440 exit(DISPATCH_SOURCE_CREATE_FAILED);
441 }
442
443 dispatch_source_set_event_handler(ds_signal, ^{
444 int current_state, new_state;
445 volatile int tmp;
446
447 /* Make sure all the pages are accessed before trying to freeze again */
448 for (int x = 0; x < num_pages; x++) {
449 tmp = buf[x][0];
450 }
451
452 current_state = memorystatus_control(MEMORYSTATUS_CMD_GET_PROCESS_IS_FREEZABLE, getpid(), 0, NULL, 0);
453 /* Sysprocs start off as unfreezable. Verify that first. */
454 if (num_iter == 0 && current_state != 0) {
455 exit(IS_FREEZABLE_NOT_AS_EXPECTED);
456 }
457
458 /* Toggle freezable state */
459 new_state = (current_state) ? 0: 1;
460 printf("[%d] Changing state from %s to %s\n", getpid(),
461 (current_state) ? "freezable": "unfreezable", (new_state) ? "freezable": "unfreezable");
462 if (memorystatus_control(MEMORYSTATUS_CMD_SET_PROCESS_IS_FREEZABLE, getpid(), (uint32_t)new_state, NULL, 0) != KERN_SUCCESS) {
463 exit(MEMORYSTATUS_CONTROL_FAILED);
464 }
465
466 /* Verify that the state has been set correctly */
467 current_state = memorystatus_control(MEMORYSTATUS_CMD_GET_PROCESS_IS_FREEZABLE, getpid(), 0, NULL, 0);
468 if (new_state != current_state) {
469 exit(IS_FREEZABLE_NOT_AS_EXPECTED);
470 }
471 num_iter++;
472
473 if (kill(getppid(), SIGUSR1) != 0) {
474 exit(SIGNAL_TO_PARENT_FAILED);
475 }
476 });
477 dispatch_activate(ds_signal);
478 move_to_idle_band(getpid());
479
480 dispatch_main();
481 }
482
483 T_HELPER_DECL(allocate_pages,
484 "allocates pages to freeze",
485 T_META_ASROOT(true)) {
486 if (argc < 1) {
487 exit(TOO_FEW_ARGUMENTS);
488 }
489
490 int num_pages = atoi(argv[0]);
491 allocate_pages(num_pages);
492 }
493
494 T_DECL(freeze, "VM freezer test", T_META_ASROOT(true)) {
495 run_freezer_test(
496 (MEM_SIZE_MB << 20) / get_vmpage_size());
497 }
498
499 static int old_freeze_pages_max = 0;
500 static void
501 reset_freeze_pages_max()
502 {
503 if (old_freeze_pages_max != 0) {
504 sysctl_freeze_pages_max(&old_freeze_pages_max);
505 }
506 }
507
508 int
509 sysctl_freeze_pages_max(int* new_value)
510 {
511 static int set_end_handler = false;
512 int freeze_pages_max, ret;
513 size_t size = sizeof(freeze_pages_max);
514 ret = sysctlbyname("kern.memorystatus_freeze_pages_max", &freeze_pages_max, &size, new_value, size);
515 T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "Unable to query kern.memorystatus_freeze_pages_max");
516 if (!set_end_handler) {
517 // Save the original value and instruct darwintest to restore it after the test completes
518 old_freeze_pages_max = freeze_pages_max;
519 T_ATEND(reset_freeze_pages_max);
520 set_end_handler = true;
521 }
522 return old_freeze_pages_max;
523 }
524
525 T_DECL(freeze_over_max_threshold, "Max Freeze Threshold is Enforced", T_META_ASROOT(true)) {
526 int freeze_pages_max = FREEZE_PAGES_MAX;
527 sysctl_freeze_pages_max(&freeze_pages_max);
528 run_freezer_test(FREEZE_PAGES_MAX * 2);
529 }
530
531 T_HELPER_DECL(frozen_background, "Frozen background process", T_META_ASROOT(true)) {
532 kern_return_t kern_ret;
533 /* Set the process to freezable */
534 kern_ret = memorystatus_control(MEMORYSTATUS_CMD_SET_PROCESS_IS_FREEZABLE, getpid(), 1, NULL, 0);
535 T_QUIET; T_ASSERT_EQ(kern_ret, KERN_SUCCESS, "set process is freezable");
536 /* Signal to our parent that we can be frozen */
537 if (kill(getppid(), SIGUSR1) != 0) {
538 T_LOG("Unable to signal to parent process!");
539 exit(1);
540 }
541 while (1) {
542 ;
543 }
544 }
545
546 /* Launches the frozen_background helper as a managed process. */
547 static pid_t
548 launch_frozen_background_process()
549 {
550 pid_t pid;
551 char **launch_tool_args;
552 char testpath[PATH_MAX];
553 uint32_t testpath_buf_size;
554 int ret;
555
556 testpath_buf_size = sizeof(testpath);
557 ret = _NSGetExecutablePath(testpath, &testpath_buf_size);
558 printf("Launching %s\n", testpath);
559 launch_tool_args = (char *[]){
560 testpath,
561 "-n",
562 "frozen_background",
563 NULL
564 };
565 ret = dt_launch_tool(&pid, launch_tool_args, false, NULL, NULL);
566 if (ret != 0) {
567 T_LOG("dt_launch tool returned %d with error code %d", ret, errno);
568 }
569 T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "dt_launch_tool");
570 /* Set the process's managed bit, so that the kernel treats this process like an app instead of a sysproc. */
571 ret = memorystatus_control(MEMORYSTATUS_CMD_SET_PROCESS_IS_MANAGED, pid, 1, NULL, 0);
572 T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "memorystatus_control");
573 return pid;
574 }
575
576 static void
577 freeze_process(pid_t pid)
578 {
579 int ret, freeze_enabled, errno_freeze_sysctl;
580 size_t length;
581 T_LOG("Freezing pid %d", pid);
582
583 ret = sysctlbyname("kern.memorystatus_freeze", NULL, NULL, &pid, sizeof(pid));
584 errno_freeze_sysctl = errno;
585 length = sizeof(freeze_enabled);
586 T_QUIET; T_ASSERT_POSIX_SUCCESS(sysctlbyname("vm.freeze_enabled", &freeze_enabled, &length, NULL, 0),
587 "failed to query vm.freeze_enabled");
588 if (freeze_enabled) {
589 errno = errno_freeze_sysctl;
590 T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "sysctl kern.memorystatus_freeze failed");
591 } else {
592 /* If freezer is disabled, skip the test. This can happen due to disk space shortage. */
593 T_LOG("Freeze has been disabled. Terminating early.");
594 T_END;
595 }
596 }
597
598 static void
599 memorystatus_assertion_test_demote_frozen()
600 {
601 /*
602 * Test that if we assert a priority on a process, freeze it, and then demote all frozen processes, it does not get demoted below the asserted priority.
603 * Then remove thee assertion, and ensure it gets demoted properly.
604 */
605 /* these values will remain fixed during testing */
606 int active_limit_mb = 15; /* arbitrary */
607 int inactive_limit_mb = 7; /* arbitrary */
608 int demote_value = 1;
609 /* Launch the child process, and elevate its priority */
610 int requestedpriority;
611 dispatch_source_t ds_signal, ds_exit;
612 requestedpriority = JETSAM_PRIORITY_UI_SUPPORT;
613
614 /* Wait for the child process to tell us that it's ready, and then freeze it */
615 signal(SIGUSR1, SIG_IGN);
616 ds_signal = dispatch_source_create(DISPATCH_SOURCE_TYPE_SIGNAL, SIGUSR1, 0, dispatch_get_main_queue());
617 T_QUIET; T_ASSERT_NOTNULL(ds_signal, "dispatch_source_create");
618 dispatch_source_set_event_handler(ds_signal, ^{
619 int sysctl_ret;
620 /* Freeze the process, trigger agressive demotion, and check that it hasn't been demoted. */
621 freeze_process(child_pid);
622 /* Agressive demotion */
623 sysctl_ret = sysctlbyname("kern.memorystatus_demote_frozen_processes", NULL, NULL, &demote_value, sizeof(demote_value));
624 T_QUIET; T_ASSERT_POSIX_SUCCESS(sysctl_ret, "sysctl kern.memorystatus_demote_frozen_processes succeeded");
625 /* Check */
626 (void)check_properties(child_pid, requestedpriority, inactive_limit_mb, 0x0, ASSERTION_STATE_IS_SET, "Priority was set");
627 T_LOG("Relinquishing our assertion.");
628 /* Relinquish our assertion, and check that it gets demoted. */
629 relinquish_assertion_priority(child_pid, 0x0);
630 (void)check_properties(child_pid, JETSAM_PRIORITY_AGING_BAND2, inactive_limit_mb, 0x0, ASSERTION_STATE_IS_RELINQUISHED, "Assertion was reqlinquished.");
631 /* Kill the child */
632 T_QUIET; T_ASSERT_POSIX_SUCCESS(kill(child_pid, SIGKILL), "Killed child process");
633 T_END;
634 });
635
636 /* Launch the child process and set the initial properties on it. */
637 child_pid = launch_frozen_background_process();
638 set_memlimits(child_pid, active_limit_mb, inactive_limit_mb, false, false);
639 set_assertion_priority(child_pid, requestedpriority, 0x0);
640 (void)check_properties(child_pid, requestedpriority, inactive_limit_mb, 0x0, ASSERTION_STATE_IS_SET, "Priority was set");
641 /* Listen for exit. */
642 ds_exit = dispatch_source_create(DISPATCH_SOURCE_TYPE_PROC, (uintptr_t)child_pid, DISPATCH_PROC_EXIT, dispatch_get_main_queue());
643 dispatch_source_set_event_handler(ds_exit, ^{
644 int status = 0, code = 0;
645 pid_t rc = waitpid(child_pid, &status, 0);
646 T_QUIET; T_ASSERT_EQ(rc, child_pid, "waitpid");
647 code = WEXITSTATUS(status);
648 T_QUIET; T_ASSERT_EQ(code, 0, "Child exited cleanly");
649 T_END;
650 });
651
652 dispatch_activate(ds_exit);
653 dispatch_activate(ds_signal);
654 dispatch_main();
655 }
656
657 T_DECL(assertion_test_demote_frozen, "demoted frozen process goes to asserted priority.", T_META_ASROOT(true)) {
658 memorystatus_assertion_test_demote_frozen();
659 }
660
661 T_DECL(budget_replenishment, "budget replenishes properly") {
662 size_t length;
663 int ret;
664 static unsigned int kTestIntervalSecs = 60 * 60 * 32; // 32 Hours
665 unsigned int memorystatus_freeze_daily_mb_max, memorystatus_freeze_daily_pages_max;
666 static unsigned int kFixedPointFactor = 100;
667 static unsigned int kNumSecondsInDay = 60 * 60 * 24;
668 unsigned int new_budget, expected_new_budget_pages;
669 size_t new_budget_ln;
670 unsigned int page_size = (unsigned int) get_vmpage_size();
671
672 /*
673 * Calculate a new budget as if the previous interval expired kTestIntervalSecs
674 * ago and we used up its entire budget.
675 */
676 length = sizeof(kTestIntervalSecs);
677 new_budget_ln = sizeof(new_budget);
678 ret = sysctlbyname("vm.memorystatus_freeze_calculate_new_budget", &new_budget, &new_budget_ln, &kTestIntervalSecs, length);
679 T_ASSERT_POSIX_SUCCESS(ret, "vm.memorystatus_freeze_calculate_new_budget");
680
681 // Grab the daily budget.
682 length = sizeof(memorystatus_freeze_daily_mb_max);
683 ret = sysctlbyname("kern.memorystatus_freeze_daily_mb_max", &memorystatus_freeze_daily_mb_max, &length, NULL, 0);
684 T_ASSERT_POSIX_SUCCESS(ret, "kern.memorystatus_freeze_daily_mb_max");
685
686 memorystatus_freeze_daily_pages_max = memorystatus_freeze_daily_mb_max * 1024UL * 1024UL / page_size;
687 T_LOG("memorystatus_freeze_daily_mb_max %u", memorystatus_freeze_daily_mb_max);
688 T_LOG("memorystatus_freeze_daily_pages_max %u", memorystatus_freeze_daily_pages_max);
689 T_LOG("page_size %u", page_size);
690
691 /*
692 * We're kTestIntervalSecs past a new interval. Which means we are owed kNumSecondsInDay
693 * seconds of budget.
694 */
695 expected_new_budget_pages = memorystatus_freeze_daily_pages_max;
696 T_LOG("expected_new_budget_pages before %u", expected_new_budget_pages);
697 T_ASSERT_EQ(kTestIntervalSecs, 60 * 60 * 32, "kTestIntervalSecs did not change");
698 expected_new_budget_pages += ((kTestIntervalSecs * kFixedPointFactor) / (kNumSecondsInDay)
699 * memorystatus_freeze_daily_pages_max) / kFixedPointFactor;
700 T_LOG("expected_new_budget_pages after %u", expected_new_budget_pages);
701 T_LOG("memorystatus_freeze_daily_pages_max after %u", memorystatus_freeze_daily_pages_max);
702
703 T_QUIET; T_ASSERT_EQ(new_budget, expected_new_budget_pages, "Calculate new budget behaves correctly.");
704 }
705
706
707 static bool
708 is_proc_in_frozen_list(pid_t pid, char* name, size_t name_len)
709 {
710 int bytes_written;
711 bool found = false;
712 global_frozen_procs_t *frozen_procs = malloc(sizeof(global_frozen_procs_t));
713 T_QUIET; T_ASSERT_NOTNULL(frozen_procs, "malloc");
714
715 bytes_written = memorystatus_control(MEMORYSTATUS_CMD_FREEZER_CONTROL, 0, FREEZER_CONTROL_GET_PROCS, frozen_procs, sizeof(global_frozen_procs_t));
716 T_QUIET; T_ASSERT_LE((size_t) bytes_written, sizeof(global_frozen_procs_t), "Didn't overflow buffer");
717 T_QUIET; T_ASSERT_GT(bytes_written, 0, "Wrote someting");
718
719 for (size_t i = 0; i < frozen_procs->gfp_num_frozen; i++) {
720 if (frozen_procs->gfp_procs[i].fp_pid == pid) {
721 found = true;
722 strlcpy(name, frozen_procs->gfp_procs[i].fp_name, name_len);
723 }
724 }
725 return found;
726 }
727
728 static void
729 drop_jetsam_snapshot_ownership(void)
730 {
731 int ret;
732 ret = memorystatus_control(MEMORYSTATUS_CMD_SET_JETSAM_SNAPSHOT_OWNERSHIP, 0, MEMORYSTATUS_FLAGS_SNAPSHOT_DROP_OWNERSHIP, NULL, 0);
733 T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, 0, "Drop ownership of jetsam snapshot");
734 }
735
736 static void
737 take_jetsam_snapshot_ownership(void)
738 {
739 int ret;
740 ret = memorystatus_control(MEMORYSTATUS_CMD_SET_JETSAM_SNAPSHOT_OWNERSHIP, 0, MEMORYSTATUS_FLAGS_SNAPSHOT_TAKE_OWNERSHIP, NULL, 0);
741 T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "Take ownership of jetsam snapshot");
742 T_ATEND(drop_jetsam_snapshot_ownership);
743 }
744
745 /*
746 * Retrieve a jetsam snapshot.
747 *
748 * return:
749 * pointer to snapshot.
750 *
751 * Caller is responsible for freeing snapshot.
752 */
753 static
754 memorystatus_jetsam_snapshot_t *
755 get_jetsam_snapshot(uint32_t flags, bool empty_allowed)
756 {
757 memorystatus_jetsam_snapshot_t * snapshot = NULL;
758 int ret;
759 uint32_t size;
760
761 ret = memorystatus_control(MEMORYSTATUS_CMD_GET_JETSAM_SNAPSHOT, 0, flags, NULL, 0);
762 T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, 0, "Get jetsam snapshot size");
763 size = (uint32_t) ret;
764 if (size == 0 && empty_allowed) {
765 return snapshot;
766 }
767
768 snapshot = (memorystatus_jetsam_snapshot_t*)malloc(size);
769 T_QUIET; T_ASSERT_NOTNULL(snapshot, "Allocate snapshot of size %d", size);
770
771 ret = memorystatus_control(MEMORYSTATUS_CMD_GET_JETSAM_SNAPSHOT, 0, flags, snapshot, size);
772 T_QUIET; T_ASSERT_GT(size, 0, "Get jetsam snapshot");
773
774 if (((size - sizeof(memorystatus_jetsam_snapshot_t)) / sizeof(memorystatus_jetsam_snapshot_entry_t)) != snapshot->entry_count) {
775 T_FAIL("Malformed snapshot: %d! Expected %ld + %zd x %ld = %ld\n", size,
776 sizeof(memorystatus_jetsam_snapshot_t), snapshot->entry_count, sizeof(memorystatus_jetsam_snapshot_entry_t),
777 sizeof(memorystatus_jetsam_snapshot_t) + (snapshot->entry_count * sizeof(memorystatus_jetsam_snapshot_entry_t)));
778 if (snapshot) {
779 free(snapshot);
780 }
781 }
782
783 return snapshot;
784 }
785
786 /*
787 * Look for the given pid in the snapshot.
788 *
789 * return:
790 * pointer to pid's entry or NULL if pid is not found.
791 *
792 * Caller has ownership of snapshot before and after call.
793 */
794 static
795 memorystatus_jetsam_snapshot_entry_t *
796 get_jetsam_snapshot_entry(memorystatus_jetsam_snapshot_t *snapshot, pid_t pid)
797 {
798 T_QUIET; T_ASSERT_NOTNULL(snapshot, "Got snapshot");
799 for (size_t i = 0; i < snapshot->entry_count; i++) {
800 memorystatus_jetsam_snapshot_entry_t *curr = &(snapshot->entries[i]);
801 if (curr->pid == pid) {
802 return curr;
803 }
804 }
805
806 return NULL;
807 }
808
809 /*
810 * Launches the child & runs the given block after the child signals.
811 * If exit_with_child is true, the test will exit when the child exits.
812 */
813 static void
814 test_after_frozen_background_launches(bool exit_with_child, dispatch_block_t test_block)
815 {
816 dispatch_source_t ds_signal, ds_exit;
817
818 /* Run the test block after the child launches & signals it's ready. */
819 signal(SIGUSR1, SIG_IGN);
820 ds_signal = dispatch_source_create(DISPATCH_SOURCE_TYPE_SIGNAL, SIGUSR1, 0, dispatch_get_main_queue());
821 T_QUIET; T_ASSERT_NOTNULL(ds_signal, "dispatch_source_create");
822 dispatch_source_set_event_handler(ds_signal, test_block);
823 /* Launch the child process. */
824 child_pid = launch_frozen_background_process();
825 /* Listen for exit. */
826 if (exit_with_child) {
827 ds_exit = dispatch_source_create(DISPATCH_SOURCE_TYPE_PROC, (uintptr_t)child_pid, DISPATCH_PROC_EXIT, dispatch_get_main_queue());
828 dispatch_source_set_event_handler(ds_exit, ^{
829 int status = 0, code = 0;
830 pid_t rc = waitpid(child_pid, &status, 0);
831 T_QUIET; T_ASSERT_EQ(rc, child_pid, "waitpid");
832 code = WEXITSTATUS(status);
833 T_QUIET; T_ASSERT_EQ(code, 0, "Child exited cleanly");
834 T_END;
835 });
836
837 dispatch_activate(ds_exit);
838 }
839 dispatch_activate(ds_signal);
840 dispatch_main();
841 }
842
843 T_DECL(get_frozen_procs, "List processes in the freezer") {
844 skip_if_freezer_is_disabled();
845
846 test_after_frozen_background_launches(true, ^{
847 proc_name_t name;
848 /* Place the child in the idle band so that it gets elevated like a typical app. */
849 move_to_idle_band(child_pid);
850 /* Freeze the process, and check that it's in the list of frozen processes. */
851 freeze_process(child_pid);
852 /* Check */
853 T_QUIET; T_ASSERT_TRUE(is_proc_in_frozen_list(child_pid, name, sizeof(name)), "Found proc in frozen list");
854 T_QUIET; T_EXPECT_EQ_STR(name, "memorystatus_freeze_test", "Proc has correct name");
855 /* Kill the child */
856 T_QUIET; T_ASSERT_POSIX_SUCCESS(kill(child_pid, SIGKILL), "Killed child process");
857 T_END;
858 });
859 }
860
861 T_DECL(frozen_to_swap_accounting, "jetsam snapshot has frozen_to_swap accounting") {
862 static const size_t kSnapshotSleepDelay = 5;
863 static const size_t kFreezeToDiskMaxDelay = 60;
864
865 skip_if_freezer_is_disabled();
866
867 test_after_frozen_background_launches(true, ^{
868 memorystatus_jetsam_snapshot_t *snapshot = NULL;
869 memorystatus_jetsam_snapshot_entry_t *child_entry = NULL;
870 /* Place the child in the idle band so that it gets elevated like a typical app. */
871 move_to_idle_band(child_pid);
872 freeze_process(child_pid);
873 /*
874 * Wait until the child's pages get paged out to disk.
875 * If we don't see any pages get sent to disk before kFreezeToDiskMaxDelay seconds,
876 * something is either wrong with the compactor or the accounting.
877 */
878 for (size_t i = 0; i < kFreezeToDiskMaxDelay / kSnapshotSleepDelay; i++) {
879 snapshot = get_jetsam_snapshot(MEMORYSTATUS_FLAGS_SNAPSHOT_ON_DEMAND, false);
880 child_entry = get_jetsam_snapshot_entry(snapshot, child_pid);
881 T_QUIET; T_ASSERT_NOTNULL(child_entry, "Found child in snapshot");
882 if (child_entry->jse_frozen_to_swap_pages > 0) {
883 break;
884 }
885 free(snapshot);
886 sleep(kSnapshotSleepDelay);
887 }
888 T_QUIET; T_ASSERT_GT(child_entry->jse_frozen_to_swap_pages, 0ULL, "child has some pages in swap");
889 free(snapshot);
890 /* Kill the child */
891 T_QUIET; T_ASSERT_POSIX_SUCCESS(kill(child_pid, SIGKILL), "Killed child process");
892 T_END;
893 });
894 }
895
896 T_DECL(freezer_snapshot, "App kills are recorded in the freezer snapshot") {
897 /* Take ownership of the snapshot to ensure we don't race with another process trying to consume them. */
898 take_jetsam_snapshot_ownership();
899
900 test_after_frozen_background_launches(false, ^{
901 int ret;
902 memorystatus_jetsam_snapshot_t *snapshot = NULL;
903 memorystatus_jetsam_snapshot_entry_t *child_entry = NULL;
904
905 ret = memorystatus_control(MEMORYSTATUS_CMD_TEST_JETSAM, child_pid, 0, 0, 0);
906 T_ASSERT_POSIX_SUCCESS(ret, "jetsam'd the child");
907
908 snapshot = get_jetsam_snapshot(MEMORYSTATUS_FLAGS_SNAPSHOT_FREEZER, false);
909 T_ASSERT_NOTNULL(snapshot, "Got freezer snapshot");
910 child_entry = get_jetsam_snapshot_entry(snapshot, child_pid);
911 T_QUIET; T_ASSERT_NOTNULL(child_entry, "Child is in freezer snapshot");
912 T_QUIET; T_ASSERT_EQ(child_entry->killed, (unsigned long long) JETSAM_REASON_GENERIC, "Child entry was killed");
913
914 free(snapshot);
915 T_END;
916 });
917 }
918
919 T_DECL(freezer_snapshot_consume, "Freezer snapshot is consumed on read") {
920 /* Take ownership of the snapshot to ensure we don't race with another process trying to consume them. */
921 take_jetsam_snapshot_ownership();
922
923 test_after_frozen_background_launches(false, ^{
924 int ret;
925 memorystatus_jetsam_snapshot_t *snapshot = NULL;
926 memorystatus_jetsam_snapshot_entry_t *child_entry = NULL;
927
928 ret = memorystatus_control(MEMORYSTATUS_CMD_TEST_JETSAM, child_pid, 0, 0, 0);
929 T_ASSERT_POSIX_SUCCESS(ret, "jetsam'd the child");
930
931 snapshot = get_jetsam_snapshot(MEMORYSTATUS_FLAGS_SNAPSHOT_FREEZER, false);
932 T_ASSERT_NOTNULL(snapshot, "Got first freezer snapshot");
933 child_entry = get_jetsam_snapshot_entry(snapshot, child_pid);
934 T_QUIET; T_ASSERT_NOTNULL(child_entry, "Child is in first freezer snapshot");
935
936 snapshot = get_jetsam_snapshot(MEMORYSTATUS_FLAGS_SNAPSHOT_FREEZER, true);
937 if (snapshot != NULL) {
938 child_entry = get_jetsam_snapshot_entry(snapshot, child_pid);
939 T_QUIET; T_ASSERT_NULL(child_entry, "Child is not in second freezer snapshot");
940 }
941
942 free(snapshot);
943 T_END;
944 });
945 }
946
947 T_DECL(freezer_snapshot_frozen_state, "Frozen state is recorded in freezer snapshot") {
948 skip_if_freezer_is_disabled();
949 /* Take ownership of the snapshot to ensure we don't race with another process trying to consume them. */
950 take_jetsam_snapshot_ownership();
951
952 test_after_frozen_background_launches(false, ^{
953 int ret;
954 memorystatus_jetsam_snapshot_t *snapshot = NULL;
955 memorystatus_jetsam_snapshot_entry_t *child_entry = NULL;
956
957 move_to_idle_band(child_pid);
958 freeze_process(child_pid);
959
960 ret = memorystatus_control(MEMORYSTATUS_CMD_TEST_JETSAM, child_pid, 0, 0, 0);
961 T_ASSERT_POSIX_SUCCESS(ret, "jetsam'd the child");
962
963 snapshot = get_jetsam_snapshot(MEMORYSTATUS_FLAGS_SNAPSHOT_FREEZER, false);
964 T_ASSERT_NOTNULL(snapshot, "Got freezer snapshot");
965 child_entry = get_jetsam_snapshot_entry(snapshot, child_pid);
966 T_QUIET; T_ASSERT_NOTNULL(child_entry, "Child is in freezer snapshot");
967 T_QUIET; T_ASSERT_TRUE(child_entry->state & kMemorystatusFrozen, "Child entry's frozen bit is set");
968
969 free(snapshot);
970 T_END;
971 });
972 }
973
974 T_DECL(freezer_snapshot_thaw_state, "Thaw count is recorded in freezer snapshot") {
975 skip_if_freezer_is_disabled();
976 /* Take ownership of the snapshot to ensure we don't race with another process trying to consume them. */
977 take_jetsam_snapshot_ownership();
978
979 test_after_frozen_background_launches(false, ^{
980 int ret;
981 memorystatus_jetsam_snapshot_t *snapshot = NULL;
982 memorystatus_jetsam_snapshot_entry_t *child_entry = NULL;
983
984 move_to_idle_band(child_pid);
985 ret = pid_suspend(child_pid);
986 T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "child suspended");
987 freeze_process(child_pid);
988 ret = pid_resume(child_pid);
989 T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "child resumed after freeze");
990
991 ret = memorystatus_control(MEMORYSTATUS_CMD_TEST_JETSAM, child_pid, 0, 0, 0);
992 T_ASSERT_POSIX_SUCCESS(ret, "jetsam'd the child");
993
994 snapshot = get_jetsam_snapshot(MEMORYSTATUS_FLAGS_SNAPSHOT_FREEZER, false);
995 T_ASSERT_NOTNULL(snapshot, "Got freezer snapshot");
996 child_entry = get_jetsam_snapshot_entry(snapshot, child_pid);
997 T_QUIET; T_ASSERT_NOTNULL(child_entry, "Child is in freezer snapshot");
998 T_QUIET; T_ASSERT_TRUE(child_entry->state & kMemorystatusFrozen, "Child entry's frozen bit is still set after thaw");
999 T_QUIET; T_ASSERT_TRUE(child_entry->state & kMemorystatusWasThawed, "Child entry was thawed");
1000 T_QUIET; T_ASSERT_EQ(child_entry->jse_thaw_count, 1ULL, "Child entry's thaw count was incremented");
1001
1002 free(snapshot);
1003 T_END;
1004 });
1005 }