3 #include <sys/sysctl.h>
4 #include <sys/kern_memorystatus.h>
5 #include <sys/kern_memorystatus_freeze.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>
16 #include <darwintest.h>
17 #include <darwintest_utils.h>
19 #include "memorystatus_assertion_helpers.h"
22 T_META_NAMESPACE("xnu.vm"),
23 T_META_CHECK_LEAKS(false)
26 #define MEM_SIZE_MB 10
27 #define NUM_ITERATIONS 5
28 #define FREEZE_PAGES_MAX 256
30 #define CREATE_LIST(X) \
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) \
44 #define EXIT_CODES_ENUM(VAR) VAR,
46 CREATE_LIST(EXIT_CODES_ENUM
)
49 #define EXIT_CODES_STRING(VAR) #VAR,
50 static const char *exit_codes_str
[] = {
51 CREATE_LIST(EXIT_CODES_STRING
)
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");
65 static pid_t child_pid
= -1;
66 static int freeze_count
= 0;
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
);
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.
78 /* Cribbed from 'top'... */
80 in_shared_region(mach_vm_address_t addr
, cpu_type_t type
)
82 mach_vm_address_t base
= 0, size
= 0;
86 base
= SHARED_REGION_BASE_ARM
;
87 size
= SHARED_REGION_SIZE_ARM
;
91 base
= SHARED_REGION_BASE_ARM64
;
92 size
= SHARED_REGION_SIZE_ARM64
;
97 base
= SHARED_REGION_BASE_X86_64
;
98 size
= SHARED_REGION_SIZE_X86_64
;
102 base
= SHARED_REGION_BASE_I386
;
103 size
= SHARED_REGION_SIZE_I386
;
106 case CPU_TYPE_POWERPC
:
107 base
= SHARED_REGION_BASE_PPC
;
108 size
= SHARED_REGION_SIZE_PPC
;
111 case CPU_TYPE_POWERPC64
:
112 base
= SHARED_REGION_BASE_PPC64
;
113 size
= SHARED_REGION_SIZE_PPC64
;
119 fprintf(stderr
, "unknown CPU type: 0x%x\n", t
);
124 return addr
>= base
&& addr
< (base
+ size
);
127 /* Get the resident private memory of the given pid */
128 static unsigned long long
131 mach_port_name_t task
;
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;
141 mach_vm_address_t addr
;
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");
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
;
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
) {
159 #if defined (__arm64__)
160 if (in_shared_region(addr
, CPU_TYPE_ARM64
)) {
162 if (in_shared_region(addr
, CPU_TYPE_ARM
)) {
165 fw_private
+= info
.private_pages_resident
* pagesize
;
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
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
;
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
) {
184 if (b_info
.reserved
) {
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
195 if (info
.share_mode
!= SM_PRIVATE
) {
203 * Update counters according to the region type.
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
;
211 switch (info
.share_mode
) {
213 // Treat SM_LARGE_PAGE the same as SM_PRIVATE
214 // since they are not shareable and are wired.
216 rprvt
+= info
.private_pages_resident
* pagesize
;
217 rprvt
+= info
.shared_pages_resident
* pagesize
;
227 // Treat kernel_task specially
228 if (info
.share_mode
== SM_COW
) {
229 rprvt
+= info
.private_pages_resident
* pagesize
;
234 if (info
.share_mode
== SM_COW
) {
235 rprvt
+= info
.private_pages_resident
* pagesize
;
249 move_to_idle_band(pid_t pid
)
251 memorystatus_priority_properties_t props
;
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.
256 props
.priority
= JETSAM_PRIORITY_IDLE
;
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.
263 if (memorystatus_control(MEMORYSTATUS_CMD_SET_PRIORITY_PROPERTIES
, pid
, 0, &props
, sizeof(props
))) {
264 exit(MEMSTAT_PRIORITY_CHANGE_FAILED
);
269 freeze_helper_process(void)
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
;
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
;
283 * The child process toggles its freezable state on each iteration.
284 * So a failure for every alternate freeze is expected.
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");
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.");
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.");
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");
307 T_QUIET
; T_ASSERT_TRUE(ret
!= KERN_SUCCESS
, "Freeze should have failed");
308 T_LOG("Freeze failed as expected");
313 T_QUIET
; T_ASSERT_POSIX_SUCCESS(kill(child_pid
, SIGUSR1
), "failed to send SIGUSR1 to child process");
317 skip_if_freezer_is_disabled()
320 size_t length
= sizeof(freeze_enabled
);
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.");
331 run_freezer_test(int num_pages
)
335 char **launch_tool_args
;
336 char testpath
[PATH_MAX
];
337 uint32_t testpath_buf_size
;
338 dispatch_source_t ds_freeze
, ds_proc
;
340 skip_if_freezer_is_disabled();
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)");
346 dispatch_source_set_event_handler(ds_freeze
, ^{
347 if (freeze_count
< NUM_ITERATIONS
) {
348 freeze_helper_process();
350 kill(child_pid
, SIGKILL
);
351 dispatch_source_cancel(ds_freeze
);
354 dispatch_activate(ds_freeze
);
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
);
361 sprintf(sz_str
, "%d", num_pages
);
362 launch_tool_args
= (char *[]){
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
);
374 T_LOG("dt_launch tool returned %d with error code %d", ret
, errno
);
376 T_QUIET
; T_ASSERT_POSIX_SUCCESS(child_pid
, "dt_launch_tool");
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)");
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
);
389 } else if (code
> 0 && code
< EXIT_CODE_MAX
) {
390 T_ASSERT_FAIL("Child exited with %s", exit_codes_str
[code
]);
392 T_ASSERT_FAIL("Child exited with unknown exit code %d", code
);
395 dispatch_activate(ds_proc
);
397 T_QUIET
; T_ASSERT_POSIX_SUCCESS(kill(child_pid
, SIGCONT
), "failed to send SIGCONT to child process");
402 allocate_pages(int num_pages
)
406 __block
int num_iter
= 0;
408 dispatch_source_t ds_signal
;
409 vmpgsize
= get_vmpage_size();
411 printf("Invalid number of pages to allocate: %d\n", num_pages
);
412 exit(INVALID_ALLOCATE_PAGES_ARGUMENTS
);
415 buf
= (char**)malloc(sizeof(char*) * (size_t)num_pages
);
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));
421 for (i
= 0; i
< vmpgsize
; i
+= 16) {
422 memset(&buf
[j
][i
], val
, 16);
423 if (i
< 3400 * (vmpgsize
/ 4096)) {
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
);
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
);
443 dispatch_source_set_event_handler(ds_signal
, ^{
444 int current_state
, new_state
;
447 /* Make sure all the pages are accessed before trying to freeze again */
448 for (int x
= 0; x
< num_pages
; x
++) {
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
);
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
);
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
);
473 if (kill(getppid(), SIGUSR1
) != 0) {
474 exit(SIGNAL_TO_PARENT_FAILED
);
477 dispatch_activate(ds_signal
);
478 move_to_idle_band(getpid());
483 T_HELPER_DECL(allocate_pages
,
484 "allocates pages to freeze",
485 T_META_ASROOT(true)) {
487 exit(TOO_FEW_ARGUMENTS
);
490 int num_pages
= atoi(argv
[0]);
491 allocate_pages(num_pages
);
494 T_DECL(freeze
, "VM freezer test", T_META_ASROOT(true)) {
496 (MEM_SIZE_MB
<< 20) / get_vmpage_size());
499 static int old_freeze_pages_max
= 0;
501 reset_freeze_pages_max()
503 if (old_freeze_pages_max
!= 0) {
504 sysctl_freeze_pages_max(&old_freeze_pages_max
);
509 sysctl_freeze_pages_max(int* new_value
)
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;
522 return old_freeze_pages_max
;
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);
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!");
546 /* Launches the frozen_background helper as a managed process. */
548 launch_frozen_background_process()
551 char **launch_tool_args
;
552 char testpath
[PATH_MAX
];
553 uint32_t testpath_buf_size
;
556 testpath_buf_size
= sizeof(testpath
);
557 ret
= _NSGetExecutablePath(testpath
, &testpath_buf_size
);
558 printf("Launching %s\n", testpath
);
559 launch_tool_args
= (char *[]){
565 ret
= dt_launch_tool(&pid
, launch_tool_args
, false, NULL
, NULL
);
567 T_LOG("dt_launch tool returned %d with error code %d", ret
, errno
);
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");
577 freeze_process(pid_t pid
)
579 int ret
, freeze_enabled
, errno_freeze_sysctl
;
581 T_LOG("Freezing pid %d", pid
);
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");
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.");
599 memorystatus_assertion_test_demote_frozen()
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.
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
;
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
, ^{
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");
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.");
632 T_QUIET
; T_ASSERT_POSIX_SUCCESS(kill(child_pid
, SIGKILL
), "Killed child process");
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");
652 dispatch_activate(ds_exit
);
653 dispatch_activate(ds_signal
);
657 T_DECL(assertion_test_demote_frozen
, "demoted frozen process goes to asserted priority.", T_META_ASROOT(true)) {
658 memorystatus_assertion_test_demote_frozen();
661 T_DECL(budget_replenishment
, "budget replenishes properly") {
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();
673 * Calculate a new budget as if the previous interval expired kTestIntervalSecs
674 * ago and we used up its entire budget.
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");
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");
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
);
692 * We're kTestIntervalSecs past a new interval. Which means we are owed kNumSecondsInDay
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
);
703 T_QUIET
; T_ASSERT_EQ(new_budget
, expected_new_budget_pages
, "Calculate new budget behaves correctly.");
708 is_proc_in_frozen_list(pid_t pid
, char* name
, size_t name_len
)
712 global_frozen_procs_t
*frozen_procs
= malloc(sizeof(global_frozen_procs_t
));
713 T_QUIET
; T_ASSERT_NOTNULL(frozen_procs
, "malloc");
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");
719 for (size_t i
= 0; i
< frozen_procs
->gfp_num_frozen
; i
++) {
720 if (frozen_procs
->gfp_procs
[i
].fp_pid
== pid
) {
722 strlcpy(name
, frozen_procs
->gfp_procs
[i
].fp_name
, name_len
);
729 drop_jetsam_snapshot_ownership(void)
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");
737 take_jetsam_snapshot_ownership(void)
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
);
746 * Retrieve a jetsam snapshot.
749 * pointer to snapshot.
751 * Caller is responsible for freeing snapshot.
754 memorystatus_jetsam_snapshot_t
*
755 get_jetsam_snapshot(uint32_t flags
, bool empty_allowed
)
757 memorystatus_jetsam_snapshot_t
* snapshot
= NULL
;
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
) {
768 snapshot
= (memorystatus_jetsam_snapshot_t
*)malloc(size
);
769 T_QUIET
; T_ASSERT_NOTNULL(snapshot
, "Allocate snapshot of size %d", size
);
771 ret
= memorystatus_control(MEMORYSTATUS_CMD_GET_JETSAM_SNAPSHOT
, 0, flags
, snapshot
, size
);
772 T_QUIET
; T_ASSERT_GT(size
, 0, "Get jetsam snapshot");
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
)));
787 * Look for the given pid in the snapshot.
790 * pointer to pid's entry or NULL if pid is not found.
792 * Caller has ownership of snapshot before and after call.
795 memorystatus_jetsam_snapshot_entry_t
*
796 get_jetsam_snapshot_entry(memorystatus_jetsam_snapshot_t
*snapshot
, pid_t pid
)
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
) {
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.
814 test_after_frozen_background_launches(bool exit_with_child
, dispatch_block_t test_block
)
816 dispatch_source_t ds_signal
, ds_exit
;
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");
837 dispatch_activate(ds_exit
);
839 dispatch_activate(ds_signal
);
843 T_DECL(get_frozen_procs
, "List processes in the freezer") {
844 skip_if_freezer_is_disabled();
846 test_after_frozen_background_launches(true, ^{
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
);
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");
856 T_QUIET
; T_ASSERT_POSIX_SUCCESS(kill(child_pid
, SIGKILL
), "Killed child process");
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;
865 skip_if_freezer_is_disabled();
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
);
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.
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) {
886 sleep(kSnapshotSleepDelay
);
888 T_QUIET
; T_ASSERT_GT(child_entry
->jse_frozen_to_swap_pages
, 0ULL, "child has some pages in swap");
891 T_QUIET
; T_ASSERT_POSIX_SUCCESS(kill(child_pid
, SIGKILL
), "Killed child process");
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();
900 test_after_frozen_background_launches(false, ^{
902 memorystatus_jetsam_snapshot_t
*snapshot
= NULL
;
903 memorystatus_jetsam_snapshot_entry_t
*child_entry
= NULL
;
905 ret
= memorystatus_control(MEMORYSTATUS_CMD_TEST_JETSAM
, child_pid
, 0, 0, 0);
906 T_ASSERT_POSIX_SUCCESS(ret
, "jetsam'd the child");
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");
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();
923 test_after_frozen_background_launches(false, ^{
925 memorystatus_jetsam_snapshot_t
*snapshot
= NULL
;
926 memorystatus_jetsam_snapshot_entry_t
*child_entry
= NULL
;
928 ret
= memorystatus_control(MEMORYSTATUS_CMD_TEST_JETSAM
, child_pid
, 0, 0, 0);
929 T_ASSERT_POSIX_SUCCESS(ret
, "jetsam'd the child");
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");
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");
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();
952 test_after_frozen_background_launches(false, ^{
954 memorystatus_jetsam_snapshot_t
*snapshot
= NULL
;
955 memorystatus_jetsam_snapshot_entry_t
*child_entry
= NULL
;
957 move_to_idle_band(child_pid
);
958 freeze_process(child_pid
);
960 ret
= memorystatus_control(MEMORYSTATUS_CMD_TEST_JETSAM
, child_pid
, 0, 0, 0);
961 T_ASSERT_POSIX_SUCCESS(ret
, "jetsam'd the child");
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");
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();
979 test_after_frozen_background_launches(false, ^{
981 memorystatus_jetsam_snapshot_t
*snapshot
= NULL
;
982 memorystatus_jetsam_snapshot_entry_t
*child_entry
= NULL
;
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");
991 ret
= memorystatus_control(MEMORYSTATUS_CMD_TEST_JETSAM
, child_pid
, 0, 0, 0);
992 T_ASSERT_POSIX_SUCCESS(ret
, "jetsam'd the child");
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");