4 #include <sys/sysctl.h>
5 #include <sys/kern_memorystatus.h>
6 #include <sys/kern_memorystatus_freeze.h>
8 #include <mach-o/dyld.h>
9 #include <mach/mach_vm.h>
10 #include <mach/vm_page_size.h> /* Needed for vm_region info */
11 #include <mach/shared_region.h>
12 #include <mach/mach.h>
17 #include <darwintest.h>
18 #include <darwintest_utils.h>
20 #include "memorystatus_assertion_helpers.h"
23 T_META_NAMESPACE("xnu.vm"),
24 T_META_CHECK_LEAKS(false)
27 #define MEM_SIZE_MB 10
28 #define NUM_ITERATIONS 5
29 #define FREEZE_PAGES_MAX 256
31 #define CREATE_LIST(X) \
33 X(TOO_FEW_ARGUMENTS) \
34 X(SYSCTL_VM_PAGESIZE_FAILED) \
35 X(VM_PAGESIZE_IS_ZERO) \
36 X(DISPATCH_SOURCE_CREATE_FAILED) \
37 X(INITIAL_SIGNAL_TO_PARENT_FAILED) \
38 X(SIGNAL_TO_PARENT_FAILED) \
39 X(MEMORYSTATUS_CONTROL_FAILED) \
40 X(IS_FREEZABLE_NOT_AS_EXPECTED) \
41 X(MEMSTAT_PRIORITY_CHANGE_FAILED) \
42 X(INVALID_ALLOCATE_PAGES_ARGUMENTS) \
44 X(FROZEN_BIT_NOT_SET) \
45 X(MEMORYSTATUS_CONTROL_ERROR) \
48 #define EXIT_CODES_ENUM(VAR) VAR,
50 CREATE_LIST(EXIT_CODES_ENUM
)
53 #define EXIT_CODES_STRING(VAR) #VAR,
54 static const char *exit_codes_str
[] = {
55 CREATE_LIST(EXIT_CODES_STRING
)
62 size_t size
= sizeof(vmpage_size
);
63 int ret
= sysctlbyname("vm.pagesize", &vmpage_size
, &size
, NULL
, 0);
64 T_QUIET
; T_ASSERT_POSIX_SUCCESS(ret
, "failed to query vm.pagesize");
65 T_QUIET
; T_ASSERT_GT(vmpage_size
, 0, "vm.pagesize is not > 0");
69 static pid_t child_pid
= -1;
70 static int freeze_count
= 0;
72 void move_to_idle_band(pid_t
);
73 void run_freezer_test(int);
74 void freeze_helper_process(void);
75 /* Gets and optionally sets the freeze pages max threshold */
76 int sysctl_freeze_pages_max(int* new_value
);
78 /* NB: in_shared_region and get_rprvt are pulled from the memorystatus unit test.
79 * We're moving away from those unit tests, so they're copied here.
82 /* Cribbed from 'top'... */
84 in_shared_region(mach_vm_address_t addr
, cpu_type_t type
)
86 mach_vm_address_t base
= 0, size
= 0;
90 base
= SHARED_REGION_BASE_ARM
;
91 size
= SHARED_REGION_SIZE_ARM
;
95 base
= SHARED_REGION_BASE_ARM64
;
96 size
= SHARED_REGION_SIZE_ARM64
;
100 case CPU_TYPE_X86_64
:
101 base
= SHARED_REGION_BASE_X86_64
;
102 size
= SHARED_REGION_SIZE_X86_64
;
106 base
= SHARED_REGION_BASE_I386
;
107 size
= SHARED_REGION_SIZE_I386
;
110 case CPU_TYPE_POWERPC
:
111 base
= SHARED_REGION_BASE_PPC
;
112 size
= SHARED_REGION_SIZE_PPC
;
115 case CPU_TYPE_POWERPC64
:
116 base
= SHARED_REGION_BASE_PPC64
;
117 size
= SHARED_REGION_SIZE_PPC64
;
123 fprintf(stderr
, "unknown CPU type: 0x%x\n", t
);
128 return addr
>= base
&& addr
< (base
+ size
);
131 /* Get the resident private memory of the given pid */
132 static unsigned long long
135 mach_port_name_t task
;
138 mach_vm_size_t rprvt
= 0;
139 mach_vm_size_t empty
= 0;
140 mach_vm_size_t fw_private
= 0;
141 mach_vm_size_t pagesize
= vm_kernel_page_size
; // The vm_region page info is reported
142 // in terms of vm_kernel_page_size.
143 mach_vm_size_t regs
= 0;
145 mach_vm_address_t addr
;
150 kr
= task_for_pid(mach_task_self(), pid
, &task
);
151 T_QUIET
; T_ASSERT_TRUE(kr
== KERN_SUCCESS
, "Unable to get task_for_pid of child");
153 for (addr
= 0;; addr
+= size
) {
154 vm_region_top_info_data_t info
;
155 mach_msg_type_number_t count
= VM_REGION_TOP_INFO_COUNT
;
156 mach_port_t object_name
;
158 kr
= mach_vm_region(task
, &addr
, &size
, VM_REGION_TOP_INFO
, (vm_region_info_t
)&info
, &count
, &object_name
);
159 if (kr
!= KERN_SUCCESS
) {
163 #if defined (__arm64__)
164 if (in_shared_region(addr
, CPU_TYPE_ARM64
)) {
166 if (in_shared_region(addr
, CPU_TYPE_ARM
)) {
169 fw_private
+= info
.private_pages_resident
* pagesize
;
172 * Check if this process has the globally shared
173 * text and data regions mapped in. If so, set
174 * split to TRUE and avoid checking
177 if (split
== FALSE
&& info
.share_mode
== SM_EMPTY
) {
178 vm_region_basic_info_data_64_t b_info
;
179 mach_vm_address_t b_addr
= addr
;
180 mach_vm_size_t b_size
= size
;
181 count
= VM_REGION_BASIC_INFO_COUNT_64
;
183 kr
= mach_vm_region(task
, &b_addr
, &b_size
, VM_REGION_BASIC_INFO_64
, (vm_region_info_t
)&b_info
, &count
, &object_name
);
184 if (kr
!= KERN_SUCCESS
) {
188 if (b_info
.reserved
) {
194 * Short circuit the loop if this isn't a shared
195 * private region, since that's the only region
196 * type we care about within the current address
199 if (info
.share_mode
!= SM_PRIVATE
) {
207 * Update counters according to the region type.
210 if (info
.share_mode
== SM_COW
&& info
.ref_count
== 1) {
211 // Treat single reference SM_COW as SM_PRIVATE
212 info
.share_mode
= SM_PRIVATE
;
215 switch (info
.share_mode
) {
217 // Treat SM_LARGE_PAGE the same as SM_PRIVATE
218 // since they are not shareable and are wired.
220 rprvt
+= info
.private_pages_resident
* pagesize
;
221 rprvt
+= info
.shared_pages_resident
* pagesize
;
231 // Treat kernel_task specially
232 if (info
.share_mode
== SM_COW
) {
233 rprvt
+= info
.private_pages_resident
* pagesize
;
238 if (info
.share_mode
== SM_COW
) {
239 rprvt
+= info
.private_pages_resident
* pagesize
;
253 move_to_idle_band(pid_t pid
)
255 memorystatus_priority_properties_t props
;
257 * Freezing a process also moves it to an elevated jetsam band in order to protect it from idle exits.
258 * So we move the child process to the idle band to mirror the typical 'idle app being frozen' scenario.
260 props
.priority
= JETSAM_PRIORITY_IDLE
;
264 * This requires us to run as root (in the absence of entitlement).
265 * Hence the T_META_ASROOT(true) in the T_HELPER_DECL.
267 if (memorystatus_control(MEMORYSTATUS_CMD_SET_PRIORITY_PROPERTIES
, pid
, 0, &props
, sizeof(props
))) {
268 exit(MEMSTAT_PRIORITY_CHANGE_FAILED
);
273 freeze_helper_process(void)
276 int ret
, freeze_enabled
, errno_freeze_sysctl
;
277 uint64_t resident_memory_before
, resident_memory_after
, vmpage_size
;
278 vmpage_size
= (uint64_t) get_vmpage_size();
279 resident_memory_before
= get_rprvt(child_pid
) / vmpage_size
;
281 T_LOG("Freezing child pid %d", child_pid
);
282 ret
= sysctlbyname("kern.memorystatus_freeze", NULL
, NULL
, &child_pid
, sizeof(child_pid
));
283 errno_freeze_sysctl
= errno
;
287 * The child process toggles its freezable state on each iteration.
288 * So a failure for every alternate freeze is expected.
290 if (freeze_count
% 2) {
291 length
= sizeof(freeze_enabled
);
292 T_QUIET
; T_ASSERT_POSIX_SUCCESS(sysctlbyname("vm.freeze_enabled", &freeze_enabled
, &length
, NULL
, 0),
293 "failed to query vm.freeze_enabled");
294 if (freeze_enabled
) {
295 errno
= errno_freeze_sysctl
;
296 T_QUIET
; T_ASSERT_POSIX_SUCCESS(ret
, "sysctl kern.memorystatus_freeze failed");
298 /* If freezer is disabled, skip the test. This can happen due to disk space shortage. */
299 T_LOG("Freeze has been disabled. Terminating early.");
302 resident_memory_after
= get_rprvt(child_pid
) / vmpage_size
;
303 uint64_t freeze_pages_max
= (uint64_t) sysctl_freeze_pages_max(NULL
);
304 T_QUIET
; T_ASSERT_LT(resident_memory_after
, resident_memory_before
, "Freeze didn't reduce resident memory set");
305 if (resident_memory_before
> freeze_pages_max
) {
306 T_QUIET
; T_ASSERT_LE(resident_memory_before
- resident_memory_after
, freeze_pages_max
, "Freeze pages froze more than the threshold.");
308 ret
= sysctlbyname("kern.memorystatus_thaw", NULL
, NULL
, &child_pid
, sizeof(child_pid
));
309 T_QUIET
; T_ASSERT_POSIX_SUCCESS(ret
, "sysctl kern.memorystatus_thaw failed");
311 T_QUIET
; T_ASSERT_TRUE(ret
!= KERN_SUCCESS
, "Freeze should have failed");
312 T_LOG("Freeze failed as expected");
317 T_QUIET
; T_ASSERT_POSIX_SUCCESS(kill(child_pid
, SIGUSR1
), "failed to send SIGUSR1 to child process");
321 skip_if_freezer_is_disabled()
324 size_t length
= sizeof(freeze_enabled
);
326 T_QUIET
; T_ASSERT_POSIX_SUCCESS(sysctlbyname("vm.freeze_enabled", &freeze_enabled
, &length
, NULL
, 0),
327 "failed to query vm.freeze_enabled");
328 if (!freeze_enabled
) {
329 /* If freezer is disabled, skip the test. This can happen due to disk space shortage. */
330 T_SKIP("Freeze has been disabled. Skipping test.");
335 run_freezer_test(int num_pages
)
339 char **launch_tool_args
;
340 char testpath
[PATH_MAX
];
341 uint32_t testpath_buf_size
;
342 dispatch_source_t ds_freeze
, ds_proc
;
344 skip_if_freezer_is_disabled();
346 signal(SIGUSR1
, SIG_IGN
);
347 ds_freeze
= dispatch_source_create(DISPATCH_SOURCE_TYPE_SIGNAL
, SIGUSR1
, 0, dispatch_get_main_queue());
348 T_QUIET
; T_ASSERT_NOTNULL(ds_freeze
, "dispatch_source_create (ds_freeze)");
350 dispatch_source_set_event_handler(ds_freeze
, ^{
351 if (freeze_count
< NUM_ITERATIONS
) {
352 freeze_helper_process();
354 kill(child_pid
, SIGKILL
);
355 dispatch_source_cancel(ds_freeze
);
358 dispatch_activate(ds_freeze
);
360 testpath_buf_size
= sizeof(testpath
);
361 ret
= _NSGetExecutablePath(testpath
, &testpath_buf_size
);
362 T_QUIET
; T_ASSERT_POSIX_ZERO(ret
, "_NSGetExecutablePath");
363 T_LOG("Executable path: %s", testpath
);
365 sprintf(sz_str
, "%d", num_pages
);
366 launch_tool_args
= (char *[]){
375 /* Spawn the child process. Suspend after launch until the exit proc handler has been set up. */
376 ret
= dt_launch_tool(&child_pid
, launch_tool_args
, true, NULL
, NULL
);
378 T_LOG("dt_launch tool returned %d with error code %d", ret
, errno
);
380 T_QUIET
; T_ASSERT_POSIX_SUCCESS(child_pid
, "dt_launch_tool");
382 ds_proc
= dispatch_source_create(DISPATCH_SOURCE_TYPE_PROC
, (uintptr_t)child_pid
, DISPATCH_PROC_EXIT
, dispatch_get_main_queue());
383 T_QUIET
; T_ASSERT_NOTNULL(ds_proc
, "dispatch_source_create (ds_proc)");
385 dispatch_source_set_event_handler(ds_proc
, ^{
386 int status
= 0, code
= 0;
387 pid_t rc
= waitpid(child_pid
, &status
, 0);
388 T_QUIET
; T_ASSERT_EQ(rc
, child_pid
, "waitpid");
389 code
= WEXITSTATUS(status
);
393 } else if (code
> 0 && code
< EXIT_CODE_MAX
) {
394 T_ASSERT_FAIL("Child exited with %s", exit_codes_str
[code
]);
396 T_ASSERT_FAIL("Child exited with unknown exit code %d", code
);
399 dispatch_activate(ds_proc
);
401 T_QUIET
; T_ASSERT_POSIX_SUCCESS(kill(child_pid
, SIGCONT
), "failed to send SIGCONT to child process");
406 allocate_pages(int num_pages
)
410 __block
int num_iter
= 0;
412 dispatch_source_t ds_signal
;
413 vmpgsize
= get_vmpage_size();
415 printf("Invalid number of pages to allocate: %d\n", num_pages
);
416 exit(INVALID_ALLOCATE_PAGES_ARGUMENTS
);
419 buf
= (char**)malloc(sizeof(char*) * (size_t)num_pages
);
421 /* Gives us the compression ratio we see in the typical case (~2.7) */
422 for (j
= 0; j
< num_pages
; j
++) {
423 buf
[j
] = (char*)malloc((size_t)vmpgsize
* sizeof(char));
425 for (i
= 0; i
< vmpgsize
; i
+= 16) {
426 memset(&buf
[j
][i
], val
, 16);
427 if (i
< 3400 * (vmpgsize
/ 4096)) {
433 dispatch_after(dispatch_time(DISPATCH_TIME_NOW
, NSEC_PER_SEC
), dispatch_get_main_queue(), ^{
434 /* Signal to the parent that we're done allocating and it's ok to freeze us */
435 printf("[%d] Sending initial signal to parent to begin freezing\n", getpid());
436 if (kill(getppid(), SIGUSR1
) != 0) {
437 exit(INITIAL_SIGNAL_TO_PARENT_FAILED
);
441 signal(SIGUSR1
, SIG_IGN
);
442 ds_signal
= dispatch_source_create(DISPATCH_SOURCE_TYPE_SIGNAL
, SIGUSR1
, 0, dispatch_get_main_queue());
443 if (ds_signal
== NULL
) {
444 exit(DISPATCH_SOURCE_CREATE_FAILED
);
447 dispatch_source_set_event_handler(ds_signal
, ^{
448 int current_state
, new_state
;
451 /* Make sure all the pages are accessed before trying to freeze again */
452 for (int x
= 0; x
< num_pages
; x
++) {
456 current_state
= memorystatus_control(MEMORYSTATUS_CMD_GET_PROCESS_IS_FREEZABLE
, getpid(), 0, NULL
, 0);
457 /* Sysprocs start off as unfreezable. Verify that first. */
458 if (num_iter
== 0 && current_state
!= 0) {
459 exit(IS_FREEZABLE_NOT_AS_EXPECTED
);
462 /* Toggle freezable state */
463 new_state
= (current_state
) ? 0: 1;
464 printf("[%d] Changing state from %s to %s\n", getpid(),
465 (current_state
) ? "freezable": "unfreezable", (new_state
) ? "freezable": "unfreezable");
466 if (memorystatus_control(MEMORYSTATUS_CMD_SET_PROCESS_IS_FREEZABLE
, getpid(), (uint32_t)new_state
, NULL
, 0) != KERN_SUCCESS
) {
467 exit(MEMORYSTATUS_CONTROL_FAILED
);
470 /* Verify that the state has been set correctly */
471 current_state
= memorystatus_control(MEMORYSTATUS_CMD_GET_PROCESS_IS_FREEZABLE
, getpid(), 0, NULL
, 0);
472 if (new_state
!= current_state
) {
473 exit(IS_FREEZABLE_NOT_AS_EXPECTED
);
477 if (kill(getppid(), SIGUSR1
) != 0) {
478 exit(SIGNAL_TO_PARENT_FAILED
);
481 dispatch_activate(ds_signal
);
482 move_to_idle_band(getpid());
487 T_HELPER_DECL(allocate_pages
,
488 "allocates pages to freeze",
489 T_META_ASROOT(true)) {
491 exit(TOO_FEW_ARGUMENTS
);
494 int num_pages
= atoi(argv
[0]);
495 allocate_pages(num_pages
);
498 T_DECL(freeze
, "VM freezer test", T_META_ASROOT(true)) {
500 (MEM_SIZE_MB
<< 20) / get_vmpage_size());
503 static int old_freeze_pages_max
= 0;
505 reset_freeze_pages_max()
507 if (old_freeze_pages_max
!= 0) {
508 sysctl_freeze_pages_max(&old_freeze_pages_max
);
513 sysctl_freeze_pages_max(int* new_value
)
515 static int set_end_handler
= false;
516 int freeze_pages_max
, ret
;
517 size_t size
= sizeof(freeze_pages_max
);
518 ret
= sysctlbyname("kern.memorystatus_freeze_pages_max", &freeze_pages_max
, &size
, new_value
, size
);
519 T_QUIET
; T_ASSERT_POSIX_SUCCESS(ret
, "Unable to query kern.memorystatus_freeze_pages_max");
520 if (!set_end_handler
) {
521 // Save the original value and instruct darwintest to restore it after the test completes
522 old_freeze_pages_max
= freeze_pages_max
;
523 T_ATEND(reset_freeze_pages_max
);
524 set_end_handler
= true;
526 return old_freeze_pages_max
;
529 T_DECL(freeze_over_max_threshold
, "Max Freeze Threshold is Enforced", T_META_ASROOT(true)) {
530 int freeze_pages_max
= FREEZE_PAGES_MAX
;
531 sysctl_freeze_pages_max(&freeze_pages_max
);
532 run_freezer_test(FREEZE_PAGES_MAX
* 2);
535 T_HELPER_DECL(frozen_background
, "Frozen background process", T_META_ASROOT(true)) {
536 kern_return_t kern_ret
;
537 /* Set the process to freezable */
538 kern_ret
= memorystatus_control(MEMORYSTATUS_CMD_SET_PROCESS_IS_FREEZABLE
, getpid(), 1, NULL
, 0);
539 T_QUIET
; T_ASSERT_EQ(kern_ret
, KERN_SUCCESS
, "set process is freezable");
540 /* Signal to our parent that we can be frozen */
541 if (kill(getppid(), SIGUSR1
) != 0) {
542 T_LOG("Unable to signal to parent process!");
550 /* Launches the frozen_background helper as a managed process. */
552 launch_background_helper(const char* variant
)
555 char **launch_tool_args
;
556 char testpath
[PATH_MAX
];
557 char *variant_cpy
= strdup(variant
);
558 uint32_t testpath_buf_size
;
561 testpath_buf_size
= sizeof(testpath
);
562 ret
= _NSGetExecutablePath(testpath
, &testpath_buf_size
);
563 printf("Launching %s\n", testpath
);
564 launch_tool_args
= (char *[]){
570 ret
= dt_launch_tool(&pid
, launch_tool_args
, false, NULL
, NULL
);
572 T_LOG("dt_launch tool returned %d with error code %d", ret
, errno
);
574 T_QUIET
; T_ASSERT_POSIX_SUCCESS(ret
, "dt_launch_tool");
575 /* Set the process's managed bit, so that the kernel treats this process like an app instead of a sysproc. */
576 ret
= memorystatus_control(MEMORYSTATUS_CMD_SET_PROCESS_IS_MANAGED
, pid
, 1, NULL
, 0);
577 T_QUIET
; T_ASSERT_POSIX_SUCCESS(ret
, "memorystatus_control");
583 freeze_process(pid_t pid
)
585 int ret
, freeze_enabled
, errno_freeze_sysctl
;
587 T_LOG("Freezing pid %d", pid
);
589 ret
= sysctlbyname("kern.memorystatus_freeze", NULL
, NULL
, &pid
, sizeof(pid
));
590 errno_freeze_sysctl
= errno
;
591 length
= sizeof(freeze_enabled
);
592 T_QUIET
; T_ASSERT_POSIX_SUCCESS(sysctlbyname("vm.freeze_enabled", &freeze_enabled
, &length
, NULL
, 0),
593 "failed to query vm.freeze_enabled");
594 if (freeze_enabled
) {
595 errno
= errno_freeze_sysctl
;
596 T_QUIET
; T_ASSERT_POSIX_SUCCESS(ret
, "sysctl kern.memorystatus_freeze failed");
598 /* If freezer is disabled, skip the test. This can happen due to disk space shortage. */
599 T_LOG("Freeze has been disabled. Terminating early.");
605 memorystatus_assertion_test_demote_frozen()
608 * 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.
609 * Then remove thee assertion, and ensure it gets demoted properly.
611 /* these values will remain fixed during testing */
612 int active_limit_mb
= 15; /* arbitrary */
613 int inactive_limit_mb
= 7; /* arbitrary */
614 __block
int demote_value
= 1;
615 /* Launch the child process, and elevate its priority */
616 int requestedpriority
;
617 dispatch_source_t ds_signal
, ds_exit
;
618 requestedpriority
= JETSAM_PRIORITY_UI_SUPPORT
;
620 /* Wait for the child process to tell us that it's ready, and then freeze it */
621 signal(SIGUSR1
, SIG_IGN
);
622 ds_signal
= dispatch_source_create(DISPATCH_SOURCE_TYPE_SIGNAL
, SIGUSR1
, 0, dispatch_get_main_queue());
623 T_QUIET
; T_ASSERT_NOTNULL(ds_signal
, "dispatch_source_create");
624 dispatch_source_set_event_handler(ds_signal
, ^{
626 /* Freeze the process, trigger agressive demotion, and check that it hasn't been demoted. */
627 freeze_process(child_pid
);
628 /* Agressive demotion */
629 sysctl_ret
= sysctlbyname("kern.memorystatus_demote_frozen_processes", NULL
, NULL
, &demote_value
, sizeof(demote_value
));
630 T_QUIET
; T_ASSERT_POSIX_SUCCESS(sysctl_ret
, "sysctl kern.memorystatus_demote_frozen_processes succeeded");
632 (void)check_properties(child_pid
, requestedpriority
, inactive_limit_mb
, 0x0, ASSERTION_STATE_IS_SET
, "Priority was set");
633 T_LOG("Relinquishing our assertion.");
634 /* Relinquish our assertion, and check that it gets demoted. */
635 relinquish_assertion_priority(child_pid
, 0x0);
636 (void)check_properties(child_pid
, JETSAM_PRIORITY_AGING_BAND2
, inactive_limit_mb
, 0x0, ASSERTION_STATE_IS_RELINQUISHED
, "Assertion was reqlinquished.");
638 T_QUIET
; T_ASSERT_POSIX_SUCCESS(kill(child_pid
, SIGKILL
), "Killed child process");
642 /* Launch the child process and set the initial properties on it. */
643 child_pid
= launch_background_helper("frozen_background");
644 set_memlimits(child_pid
, active_limit_mb
, inactive_limit_mb
, false, false);
645 set_assertion_priority(child_pid
, requestedpriority
, 0x0);
646 (void)check_properties(child_pid
, requestedpriority
, inactive_limit_mb
, 0x0, ASSERTION_STATE_IS_SET
, "Priority was set");
647 /* Listen for exit. */
648 ds_exit
= dispatch_source_create(DISPATCH_SOURCE_TYPE_PROC
, (uintptr_t)child_pid
, DISPATCH_PROC_EXIT
, dispatch_get_main_queue());
649 dispatch_source_set_event_handler(ds_exit
, ^{
650 int status
= 0, code
= 0;
651 pid_t rc
= waitpid(child_pid
, &status
, 0);
652 T_QUIET
; T_ASSERT_EQ(rc
, child_pid
, "waitpid");
653 code
= WEXITSTATUS(status
);
654 T_QUIET
; T_ASSERT_EQ(code
, 0, "Child exited cleanly");
658 dispatch_activate(ds_exit
);
659 dispatch_activate(ds_signal
);
663 T_DECL(assertion_test_demote_frozen
, "demoted frozen process goes to asserted priority.", T_META_ASROOT(true)) {
664 memorystatus_assertion_test_demote_frozen();
667 T_DECL(budget_replenishment
, "budget replenishes properly") {
670 static unsigned int kTestIntervalSecs
= 60 * 60 * 32; // 32 Hours
671 unsigned int memorystatus_freeze_daily_mb_max
, memorystatus_freeze_daily_pages_max
;
672 static unsigned int kFixedPointFactor
= 100;
673 static unsigned int kNumSecondsInDay
= 60 * 60 * 24;
674 unsigned int new_budget
, expected_new_budget_pages
;
675 size_t new_budget_ln
;
676 unsigned int page_size
= (unsigned int) get_vmpage_size();
679 * Calculate a new budget as if the previous interval expired kTestIntervalSecs
680 * ago and we used up its entire budget.
682 length
= sizeof(kTestIntervalSecs
);
683 new_budget_ln
= sizeof(new_budget
);
684 ret
= sysctlbyname("vm.memorystatus_freeze_calculate_new_budget", &new_budget
, &new_budget_ln
, &kTestIntervalSecs
, length
);
685 T_ASSERT_POSIX_SUCCESS(ret
, "vm.memorystatus_freeze_calculate_new_budget");
687 // Grab the daily budget.
688 length
= sizeof(memorystatus_freeze_daily_mb_max
);
689 ret
= sysctlbyname("kern.memorystatus_freeze_daily_mb_max", &memorystatus_freeze_daily_mb_max
, &length
, NULL
, 0);
690 T_ASSERT_POSIX_SUCCESS(ret
, "kern.memorystatus_freeze_daily_mb_max");
692 memorystatus_freeze_daily_pages_max
= memorystatus_freeze_daily_mb_max
* 1024UL * 1024UL / page_size
;
693 T_LOG("memorystatus_freeze_daily_mb_max %u", memorystatus_freeze_daily_mb_max
);
694 T_LOG("memorystatus_freeze_daily_pages_max %u", memorystatus_freeze_daily_pages_max
);
695 T_LOG("page_size %u", page_size
);
698 * We're kTestIntervalSecs past a new interval. Which means we are owed kNumSecondsInDay
701 expected_new_budget_pages
= memorystatus_freeze_daily_pages_max
;
702 T_LOG("expected_new_budget_pages before %u", expected_new_budget_pages
);
703 T_ASSERT_EQ(kTestIntervalSecs
, 60 * 60 * 32, "kTestIntervalSecs did not change");
704 expected_new_budget_pages
+= ((kTestIntervalSecs
* kFixedPointFactor
) / (kNumSecondsInDay
)
705 * memorystatus_freeze_daily_pages_max
) / kFixedPointFactor
;
706 T_LOG("expected_new_budget_pages after %u", expected_new_budget_pages
);
707 T_LOG("memorystatus_freeze_daily_pages_max after %u", memorystatus_freeze_daily_pages_max
);
709 T_QUIET
; T_ASSERT_EQ(new_budget
, expected_new_budget_pages
, "Calculate new budget behaves correctly.");
714 is_proc_in_frozen_list(pid_t pid
, char* name
, size_t name_len
)
718 global_frozen_procs_t
*frozen_procs
= malloc(sizeof(global_frozen_procs_t
));
719 T_QUIET
; T_ASSERT_NOTNULL(frozen_procs
, "malloc");
721 bytes_written
= memorystatus_control(MEMORYSTATUS_CMD_FREEZER_CONTROL
, 0, FREEZER_CONTROL_GET_PROCS
, frozen_procs
, sizeof(global_frozen_procs_t
));
722 T_QUIET
; T_ASSERT_LE((size_t) bytes_written
, sizeof(global_frozen_procs_t
), "Didn't overflow buffer");
723 T_QUIET
; T_ASSERT_GT(bytes_written
, 0, "Wrote someting");
725 for (size_t i
= 0; i
< frozen_procs
->gfp_num_frozen
; i
++) {
726 if (frozen_procs
->gfp_procs
[i
].fp_pid
== pid
) {
728 strlcpy(name
, frozen_procs
->gfp_procs
[i
].fp_name
, name_len
);
735 unset_testing_pid(void)
738 ret
= memorystatus_control(MEMORYSTATUS_CMD_SET_TESTING_PID
, 0, MEMORYSTATUS_FLAGS_UNSET_TESTING_PID
, NULL
, 0);
739 T_QUIET
; T_ASSERT_POSIX_SUCCESS(ret
, 0, "Drop ownership of jetsam snapshot");
743 set_testing_pid(void)
746 ret
= memorystatus_control(MEMORYSTATUS_CMD_SET_TESTING_PID
, 0, MEMORYSTATUS_FLAGS_SET_TESTING_PID
, NULL
, 0);
747 T_QUIET
; T_ASSERT_POSIX_SUCCESS(ret
, "Take ownership of jetsam snapshot");
748 T_ATEND(unset_testing_pid
);
752 * Retrieve a jetsam snapshot.
755 * pointer to snapshot.
757 * Caller is responsible for freeing snapshot.
760 memorystatus_jetsam_snapshot_t
*
761 get_jetsam_snapshot(uint32_t flags
, bool empty_allowed
)
763 memorystatus_jetsam_snapshot_t
* snapshot
= NULL
;
767 ret
= memorystatus_control(MEMORYSTATUS_CMD_GET_JETSAM_SNAPSHOT
, 0, flags
, NULL
, 0);
768 T_QUIET
; T_ASSERT_POSIX_SUCCESS(ret
, 0, "Get jetsam snapshot size");
769 size
= (uint32_t) ret
;
770 if (size
== 0 && empty_allowed
) {
774 snapshot
= (memorystatus_jetsam_snapshot_t
*)malloc(size
);
775 T_QUIET
; T_ASSERT_NOTNULL(snapshot
, "Allocate snapshot of size %d", size
);
777 ret
= memorystatus_control(MEMORYSTATUS_CMD_GET_JETSAM_SNAPSHOT
, 0, flags
, snapshot
, size
);
778 T_QUIET
; T_ASSERT_GT(size
, 0, "Get jetsam snapshot");
780 if (((size
- sizeof(memorystatus_jetsam_snapshot_t
)) / sizeof(memorystatus_jetsam_snapshot_entry_t
)) != snapshot
->entry_count
) {
781 T_FAIL("Malformed snapshot: %d! Expected %ld + %zd x %ld = %ld\n", size
,
782 sizeof(memorystatus_jetsam_snapshot_t
), snapshot
->entry_count
, sizeof(memorystatus_jetsam_snapshot_entry_t
),
783 sizeof(memorystatus_jetsam_snapshot_t
) + (snapshot
->entry_count
* sizeof(memorystatus_jetsam_snapshot_entry_t
)));
793 * Look for the given pid in the snapshot.
796 * pointer to pid's entry or NULL if pid is not found.
798 * Caller has ownership of snapshot before and after call.
801 memorystatus_jetsam_snapshot_entry_t
*
802 get_jetsam_snapshot_entry(memorystatus_jetsam_snapshot_t
*snapshot
, pid_t pid
)
804 T_QUIET
; T_ASSERT_NOTNULL(snapshot
, "Got snapshot");
805 for (size_t i
= 0; i
< snapshot
->entry_count
; i
++) {
806 memorystatus_jetsam_snapshot_entry_t
*curr
= &(snapshot
->entries
[i
]);
807 if (curr
->pid
== pid
) {
815 static dispatch_source_t
816 run_block_after_signal(int sig
, dispatch_block_t block
)
818 dispatch_source_t ds_signal
;
819 signal(sig
, SIG_IGN
);
820 ds_signal
= dispatch_source_create(DISPATCH_SOURCE_TYPE_SIGNAL
, (uintptr_t) sig
, 0, dispatch_get_main_queue());
821 T_QUIET
; T_ASSERT_NOTNULL(ds_signal
, "dispatch_source_create");
822 dispatch_source_set_event_handler(ds_signal
, block
);
827 * Launches the child & runs the given block after the child signals.
828 * If exit_with_child is true, the test will exit when the child exits.
831 test_after_background_helper_launches(bool exit_with_child
, const char* variant
, dispatch_block_t test_block
)
833 dispatch_source_t ds_signal
, ds_exit
;
835 ds_signal
= run_block_after_signal(SIGUSR1
, test_block
);
836 /* Launch the child process. */
837 child_pid
= launch_background_helper(variant
);
838 /* Listen for exit. */
839 if (exit_with_child
) {
840 ds_exit
= dispatch_source_create(DISPATCH_SOURCE_TYPE_PROC
, (uintptr_t)child_pid
, DISPATCH_PROC_EXIT
, dispatch_get_main_queue());
841 dispatch_source_set_event_handler(ds_exit
, ^{
842 int status
= 0, code
= 0;
843 pid_t rc
= waitpid(child_pid
, &status
, 0);
844 T_QUIET
; T_ASSERT_EQ(rc
, child_pid
, "waitpid");
845 code
= WEXITSTATUS(status
);
847 T_LOG("Child exited with error: %s", exit_codes_str
[code
]);
849 T_QUIET
; T_ASSERT_EQ(code
, 0, "Child exited cleanly");
853 dispatch_activate(ds_exit
);
855 dispatch_activate(ds_signal
);
858 T_DECL(get_frozen_procs
, "List processes in the freezer") {
859 skip_if_freezer_is_disabled();
861 test_after_background_helper_launches(true, "frozen_background", ^{
863 /* Place the child in the idle band so that it gets elevated like a typical app. */
864 move_to_idle_band(child_pid
);
865 /* Freeze the process, and check that it's in the list of frozen processes. */
866 freeze_process(child_pid
);
868 T_QUIET
; T_ASSERT_TRUE(is_proc_in_frozen_list(child_pid
, name
, sizeof(name
)), "Found proc in frozen list");
869 T_QUIET
; T_EXPECT_EQ_STR(name
, "memorystatus_freeze_test", "Proc has correct name");
871 T_QUIET
; T_ASSERT_POSIX_SUCCESS(kill(child_pid
, SIGKILL
), "Killed child process");
877 T_DECL(frozen_to_swap_accounting
, "jetsam snapshot has frozen_to_swap accounting") {
878 static const size_t kSnapshotSleepDelay
= 5;
879 static const size_t kFreezeToDiskMaxDelay
= 60;
881 skip_if_freezer_is_disabled();
883 test_after_background_helper_launches(true, "frozen_background", ^{
884 memorystatus_jetsam_snapshot_t
*snapshot
= NULL
;
885 memorystatus_jetsam_snapshot_entry_t
*child_entry
= NULL
;
886 /* Place the child in the idle band so that it gets elevated like a typical app. */
887 move_to_idle_band(child_pid
);
888 freeze_process(child_pid
);
890 * Wait until the child's pages get paged out to disk.
891 * If we don't see any pages get sent to disk before kFreezeToDiskMaxDelay seconds,
892 * something is either wrong with the compactor or the accounting.
894 for (size_t i
= 0; i
< kFreezeToDiskMaxDelay
/ kSnapshotSleepDelay
; i
++) {
895 snapshot
= get_jetsam_snapshot(MEMORYSTATUS_FLAGS_SNAPSHOT_ON_DEMAND
, false);
896 child_entry
= get_jetsam_snapshot_entry(snapshot
, child_pid
);
897 T_QUIET
; T_ASSERT_NOTNULL(child_entry
, "Found child in snapshot");
898 if (child_entry
->jse_frozen_to_swap_pages
> 0) {
902 sleep(kSnapshotSleepDelay
);
904 T_QUIET
; T_ASSERT_GT(child_entry
->jse_frozen_to_swap_pages
, 0ULL, "child has some pages in swap");
907 T_QUIET
; T_ASSERT_POSIX_SUCCESS(kill(child_pid
, SIGKILL
), "Killed child process");
913 T_DECL(freezer_snapshot
, "App kills are recorded in the freezer snapshot") {
914 /* Take ownership of the snapshot to ensure we don't race with another process trying to consume them. */
917 test_after_background_helper_launches(false, "frozen_background", ^{
919 memorystatus_jetsam_snapshot_t
*snapshot
= NULL
;
920 memorystatus_jetsam_snapshot_entry_t
*child_entry
= NULL
;
922 ret
= memorystatus_control(MEMORYSTATUS_CMD_TEST_JETSAM
, child_pid
, 0, 0, 0);
923 T_ASSERT_POSIX_SUCCESS(ret
, "jetsam'd the child");
925 snapshot
= get_jetsam_snapshot(MEMORYSTATUS_FLAGS_SNAPSHOT_FREEZER
, false);
926 T_ASSERT_NOTNULL(snapshot
, "Got freezer snapshot");
927 child_entry
= get_jetsam_snapshot_entry(snapshot
, child_pid
);
928 T_QUIET
; T_ASSERT_NOTNULL(child_entry
, "Child is in freezer snapshot");
929 T_QUIET
; T_ASSERT_EQ(child_entry
->killed
, (unsigned long long) JETSAM_REASON_GENERIC
, "Child entry was killed");
937 T_DECL(freezer_snapshot_consume
, "Freezer snapshot is consumed on read") {
938 /* Take ownership of the snapshot to ensure we don't race with another process trying to consume them. */
941 test_after_background_helper_launches(false, "frozen_background", ^{
943 memorystatus_jetsam_snapshot_t
*snapshot
= NULL
;
944 memorystatus_jetsam_snapshot_entry_t
*child_entry
= NULL
;
946 ret
= memorystatus_control(MEMORYSTATUS_CMD_TEST_JETSAM
, child_pid
, 0, 0, 0);
947 T_ASSERT_POSIX_SUCCESS(ret
, "jetsam'd the child");
949 snapshot
= get_jetsam_snapshot(MEMORYSTATUS_FLAGS_SNAPSHOT_FREEZER
, false);
950 T_ASSERT_NOTNULL(snapshot
, "Got first freezer snapshot");
951 child_entry
= get_jetsam_snapshot_entry(snapshot
, child_pid
);
952 T_QUIET
; T_ASSERT_NOTNULL(child_entry
, "Child is in first freezer snapshot");
954 snapshot
= get_jetsam_snapshot(MEMORYSTATUS_FLAGS_SNAPSHOT_FREEZER
, true);
955 if (snapshot
!= NULL
) {
956 child_entry
= get_jetsam_snapshot_entry(snapshot
, child_pid
);
957 T_QUIET
; T_ASSERT_NULL(child_entry
, "Child is not in second freezer snapshot");
966 T_DECL(freezer_snapshot_frozen_state
, "Frozen state is recorded in freezer snapshot") {
967 skip_if_freezer_is_disabled();
968 /* Take ownership of the snapshot to ensure we don't race with another process trying to consume them. */
971 test_after_background_helper_launches(false, "frozen_background", ^{
973 memorystatus_jetsam_snapshot_t
*snapshot
= NULL
;
974 memorystatus_jetsam_snapshot_entry_t
*child_entry
= NULL
;
976 move_to_idle_band(child_pid
);
977 freeze_process(child_pid
);
979 ret
= memorystatus_control(MEMORYSTATUS_CMD_TEST_JETSAM
, child_pid
, 0, 0, 0);
980 T_ASSERT_POSIX_SUCCESS(ret
, "jetsam'd the child");
982 snapshot
= get_jetsam_snapshot(MEMORYSTATUS_FLAGS_SNAPSHOT_FREEZER
, false);
983 T_ASSERT_NOTNULL(snapshot
, "Got freezer snapshot");
984 child_entry
= get_jetsam_snapshot_entry(snapshot
, child_pid
);
985 T_QUIET
; T_ASSERT_NOTNULL(child_entry
, "Child is in freezer snapshot");
986 T_QUIET
; T_ASSERT_TRUE(child_entry
->state
& kMemorystatusFrozen
, "Child entry's frozen bit is set");
994 T_DECL(freezer_snapshot_thaw_state
, "Thaw count is recorded in freezer snapshot") {
995 skip_if_freezer_is_disabled();
996 /* Take ownership of the snapshot to ensure we don't race with another process trying to consume them. */
999 test_after_background_helper_launches(false, "frozen_background", ^{
1001 memorystatus_jetsam_snapshot_t
*snapshot
= NULL
;
1002 memorystatus_jetsam_snapshot_entry_t
*child_entry
= NULL
;
1004 move_to_idle_band(child_pid
);
1005 ret
= pid_suspend(child_pid
);
1006 T_QUIET
; T_ASSERT_POSIX_SUCCESS(ret
, "child suspended");
1007 freeze_process(child_pid
);
1008 ret
= pid_resume(child_pid
);
1009 T_QUIET
; T_ASSERT_POSIX_SUCCESS(ret
, "child resumed after freeze");
1011 ret
= memorystatus_control(MEMORYSTATUS_CMD_TEST_JETSAM
, child_pid
, 0, 0, 0);
1012 T_ASSERT_POSIX_SUCCESS(ret
, "jetsam'd the child");
1014 snapshot
= get_jetsam_snapshot(MEMORYSTATUS_FLAGS_SNAPSHOT_FREEZER
, false);
1015 T_ASSERT_NOTNULL(snapshot
, "Got freezer snapshot");
1016 child_entry
= get_jetsam_snapshot_entry(snapshot
, child_pid
);
1017 T_QUIET
; T_ASSERT_NOTNULL(child_entry
, "Child is in freezer snapshot");
1018 T_QUIET
; T_ASSERT_TRUE(child_entry
->state
& kMemorystatusFrozen
, "Child entry's frozen bit is still set after thaw");
1019 T_QUIET
; T_ASSERT_TRUE(child_entry
->state
& kMemorystatusWasThawed
, "Child entry was thawed");
1020 T_QUIET
; T_ASSERT_EQ(child_entry
->jse_thaw_count
, 1ULL, "Child entry's thaw count was incremented");
1027 T_HELPER_DECL(check_frozen
, "Check frozen state", T_META_ASROOT(true)) {
1029 dispatch_source_t ds_signal
;
1030 __block
int is_frozen
;
1031 /* Set the process to freezable */
1032 kern_ret
= memorystatus_control(MEMORYSTATUS_CMD_SET_PROCESS_IS_FREEZABLE
, getpid(), 1, NULL
, 0);
1033 T_QUIET
; T_ASSERT_POSIX_SUCCESS(kern_ret
, "set process is freezable");
1035 /* We should not be frozen yet. */
1036 is_frozen
= memorystatus_control(MEMORYSTATUS_CMD_GET_PROCESS_IS_FROZEN
, getpid(), 0, NULL
, 0);
1037 if (is_frozen
== -1) {
1038 T_LOG("memorystatus_control error: %s", strerror(errno
));
1039 exit(MEMORYSTATUS_CONTROL_ERROR
);
1042 exit(FROZEN_BIT_SET
);
1045 ds_signal
= dispatch_source_create(DISPATCH_SOURCE_TYPE_SIGNAL
, SIGUSR1
, 0, dispatch_get_main_queue());
1046 if (ds_signal
== NULL
) {
1047 exit(DISPATCH_SOURCE_CREATE_FAILED
);
1050 dispatch_source_set_event_handler(ds_signal
, ^{
1051 /* We should now be frozen. */
1052 is_frozen
= memorystatus_control(MEMORYSTATUS_CMD_GET_PROCESS_IS_FROZEN
, getpid(), 0, NULL
, 0);
1053 if (is_frozen
== -1) {
1054 T_LOG("memorystatus_control error: %s", strerror(errno
));
1055 exit(MEMORYSTATUS_CONTROL_ERROR
);
1058 exit(FROZEN_BIT_NOT_SET
);
1062 dispatch_activate(ds_signal
);
1064 sig_t sig_ret
= signal(SIGUSR1
, SIG_IGN
);
1065 T_QUIET
; T_WITH_ERRNO
; T_ASSERT_NE(sig_ret
, SIG_ERR
, "signal(SIGUSR1, SIG_IGN)");
1067 /* Signal to our parent that we can be frozen */
1068 if (kill(getppid(), SIGUSR1
) != 0) {
1069 T_LOG("Unable to signal to parent process!");
1070 exit(SIGNAL_TO_PARENT_FAILED
);
1076 T_DECL(memorystatus_get_process_is_frozen
, "MEMORYSTATUS_CMD_GET_PROCESS_IS_FROZEN returns correct state") {
1077 skip_if_freezer_is_disabled();
1079 test_after_background_helper_launches(true, "check_frozen", ^{
1081 /* Freeze the child, resume it, and signal it to check its state */
1082 move_to_idle_band(child_pid
);
1083 ret
= pid_suspend(child_pid
);
1084 T_ASSERT_POSIX_SUCCESS(ret
, "child suspended");
1085 freeze_process(child_pid
);
1086 ret
= pid_resume(child_pid
);
1087 T_ASSERT_POSIX_SUCCESS(ret
, "child resumed after freeze");
1089 kill(child_pid
, SIGUSR1
);
1090 /* The child will checks its own frozen state & exit. */
1095 static unsigned int freeze_pages_min_old
;
1096 static int throttle_enabled_old
;
1097 static void cleanup_memorystatus_freeze_top_process() {
1098 sysctlbyname("kern.memorystatus_freeze_pages_min", NULL
, NULL
, &freeze_pages_min_old
, sizeof(freeze_pages_min_old
));
1099 sysctlbyname("kern.memorystatus_freeze_throttle_enabled", NULL
, NULL
, &throttle_enabled_old
, sizeof(throttle_enabled_old
));
1102 #define P_MEMSTAT_FROZEN 0x00000002
1103 T_DECL(memorystatus_freeze_top_process
, "memorystatus_freeze_top_process chooses the correct process",
1104 T_META_ASROOT(true),
1105 T_META_REQUIRES_SYSCTL_EQ("kern.development", 1),
1106 T_META_REQUIRES_SYSCTL_EQ("vm.freeze_enabled", 1)) {
1107 int32_t memorystatus_freeze_band
= 0;
1108 size_t memorystatus_freeze_band_size
= sizeof(memorystatus_freeze_band
);
1109 size_t freeze_pages_min_size
= sizeof(freeze_pages_min_old
);
1110 unsigned int freeze_pages_min_new
= 0;
1111 size_t throttle_enabled_old_size
= sizeof(throttle_enabled_old
);
1112 int throttle_enabled_new
= 1;
1113 __block errno_t ret
;
1114 __block
int maxproc
;
1115 size_t maxproc_size
= sizeof(maxproc
);
1117 ret
= sysctlbyname("kern.maxproc", &maxproc
, &maxproc_size
, NULL
, 0);
1118 T_QUIET
; T_ASSERT_POSIX_SUCCESS(ret
, "kern.maxproc");
1119 sysctlbyname("kern.memorystatus_freeze_jetsam_band", &memorystatus_freeze_band
, &memorystatus_freeze_band_size
, NULL
, 0);
1121 /* Set min pages to 0 and disable the budget to ensure we can always freeze the child. */
1122 ret
= sysctlbyname("kern.memorystatus_freeze_pages_min", &freeze_pages_min_old
, &freeze_pages_min_size
, &freeze_pages_min_new
, sizeof(freeze_pages_min_new
));
1123 T_QUIET
; T_ASSERT_POSIX_SUCCESS(ret
, "set kern.memorystatus_freeze_pages_min");
1124 ret
= sysctlbyname("kern.memorystatus_freeze_throttle_enabled", &throttle_enabled_old
, &throttle_enabled_old_size
, &throttle_enabled_new
, sizeof(throttle_enabled_new
));
1125 T_QUIET
; T_ASSERT_POSIX_SUCCESS(ret
, "set kern.memorystatus_freeze_throttle_enabled");
1126 T_ATEND(cleanup_memorystatus_freeze_top_process
);
1127 /* Take ownership of the freezer probabilities for the duration of the test so that we don't race with dasd. */
1129 test_after_background_helper_launches(true, "frozen_background", ^{
1130 int32_t child_band
= JETSAM_PRIORITY_DEFAULT
;
1131 /* Place the child in the idle band so that it gets elevated like a typical app. */
1132 move_to_idle_band(child_pid
);
1133 ret
= pid_suspend(child_pid
);
1134 T_ASSERT_POSIX_SUCCESS(ret
, "child suspended");
1136 size_t buffer_len
= sizeof(memorystatus_properties_entry_v1_t
) * (size_t) maxproc
;
1137 memorystatus_properties_entry_v1_t
*properties_list
= malloc(buffer_len
);
1138 T_QUIET
; T_ASSERT_NOTNULL(properties_list
, "malloc properties array");
1139 size_t properties_list_len
= 0;
1140 /* The child needs to age down into the idle band before it's eligible to be frozen. */
1141 T_LOG("Waiting for child to age into the idle band.");
1142 while (child_band
!= JETSAM_PRIORITY_IDLE
) {
1143 memset(properties_list
, 0, buffer_len
);
1144 properties_list_len
= 0;
1145 memorystatus_jetsam_snapshot_t
*snapshot
= get_jetsam_snapshot(MEMORYSTATUS_FLAGS_SNAPSHOT_ON_DEMAND
, false);
1148 for (size_t i
= 0; i
< snapshot
->entry_count
; i
++) {
1149 memorystatus_jetsam_snapshot_entry_t
*snapshot_entry
= &snapshot
->entries
[i
];
1150 if (snapshot_entry
->priority
<= memorystatus_freeze_band
&& !snapshot_entry
->killed
) {
1151 pid_t pid
= snapshot_entry
->pid
;
1152 memorystatus_properties_entry_v1_t
*property_entry
= &properties_list
[properties_list_len
++];
1153 property_entry
->version
= 1;
1154 property_entry
->pid
= pid
;
1155 if (pid
== child_pid
) {
1157 property_entry
->use_probability
= 1;
1158 child_band
= snapshot_entry
->priority
;
1160 property_entry
->use_probability
= 0;
1162 strncpy(property_entry
->proc_name
, snapshot_entry
->name
, MAXCOMLEN
);
1163 property_entry
->proc_name
[MAXCOMLEN
] = '\0';
1166 T_QUIET
; T_ASSERT_TRUE(found
, "Child is in on demand snapshot");
1169 ret
= memorystatus_control(MEMORYSTATUS_CMD_GRP_SET_PROPERTIES
, 0, MEMORYSTATUS_FLAGS_GRP_SET_PROBABILITY
, properties_list
, sizeof(memorystatus_properties_entry_v1_t
) * properties_list_len
);
1170 T_QUIET
; T_ASSERT_POSIX_SUCCESS(ret
, "MEMORYSTATUS_FLAGS_GRP_SET_PROBABILITY");
1171 free(properties_list
);
1173 ret
= sysctlbyname("vm.memorystatus_freeze_top_process", NULL
, NULL
, &val
, sizeof(val
));
1174 T_ASSERT_POSIX_SUCCESS(ret
, "freeze_top_process");
1175 /* Verify that the process was frozen. */
1176 memorystatus_jetsam_snapshot_t
*snapshot
= get_jetsam_snapshot(MEMORYSTATUS_FLAGS_SNAPSHOT_ON_DEMAND
, false);
1177 memorystatus_jetsam_snapshot_entry_t
*entry
= get_jetsam_snapshot_entry(snapshot
, child_pid
);
1178 T_ASSERT_NOTNULL(entry
, "child is in snapshot");
1179 if (!(entry
->state
& P_MEMSTAT_FROZEN
)) {
1180 T_LOG("Not frozen. Skip reason: %d", entry
->jse_freeze_skip_reason
);
1182 T_ASSERT_TRUE(entry
->state
& P_MEMSTAT_FROZEN
, "child is frozen");
1184 ret
= pid_resume(child_pid
);
1185 T_ASSERT_POSIX_SUCCESS(ret
, "child resumed after freeze");
1187 /* Kill the child */
1188 T_QUIET
; T_ASSERT_POSIX_SUCCESS(kill(child_pid
, SIGKILL
), "Killed child process");
1195 memorystatus_freezer_thaw_percentage(void)
1198 size_t size
= sizeof(val
);
1199 int ret
= sysctlbyname("kern.memorystatus_freezer_thaw_percentage", &val
, &size
, NULL
, 0);
1200 T_QUIET
; T_ASSERT_POSIX_SUCCESS(ret
, "failed to query kern.memorystatus_freezer_thaw_percentage");
1205 reset_interval(void)
1207 uint32_t freeze_daily_budget_mb
= 0;
1208 size_t size
= sizeof(freeze_daily_budget_mb
);
1210 uint64_t new_budget
;
1211 ret
= sysctlbyname("kern.memorystatus_freeze_daily_mb_max", &freeze_daily_budget_mb
, &size
, NULL
, 0);
1212 T_QUIET
; T_ASSERT_POSIX_SUCCESS(ret
, "failed to query kern.memorystatus_freeze_daily_mb_max");
1213 new_budget
= (freeze_daily_budget_mb
* (1UL << 20) / vm_page_size
);
1214 ret
= sysctlbyname("kern.memorystatus_freeze_budget_pages_remaining", NULL
, NULL
, &new_budget
, sizeof(new_budget
));
1215 T_QUIET
; T_ASSERT_POSIX_SUCCESS(ret
, "failed to set kern.memorystatus_freeze_budget_pages_remaining");
1218 static pid_t second_child
;
1220 cleanup_memorystatus_freezer_thaw_percentage(void)
1222 kill(second_child
, SIGKILL
);
1225 T_DECL(memorystatus_freezer_thaw_percentage
, "memorystatus_freezer_thaw_percentage updates correctly",
1226 T_META_ASROOT(true),
1227 T_META_REQUIRES_SYSCTL_EQ("kern.development", 1),
1228 T_META_REQUIRES_SYSCTL_EQ("vm.freeze_enabled", 1)) {
1229 __block dispatch_source_t first_signal_block
;
1230 /* Take ownership of the freezer probabilities for the duration of the test so that nothing new gets frozen by dasd. */
1234 /* Spawn one child that will remain frozen throughout the whole test & another that will be thawed. */
1235 first_signal_block
= run_block_after_signal(SIGUSR1
, ^{
1236 move_to_idle_band(second_child
);
1237 __block
int ret
= pid_suspend(second_child
);
1238 T_QUIET
; T_ASSERT_POSIX_SUCCESS(ret
, "child suspended");
1239 freeze_process(second_child
);
1240 T_QUIET
; T_ASSERT_EQ(memorystatus_freezer_thaw_percentage(), 0, "thaw percentage is still 0 after freeze");
1241 dispatch_source_cancel(first_signal_block
);
1242 test_after_background_helper_launches(true, "frozen_background", ^{
1244 T_QUIET
; T_ASSERT_EQ(memorystatus_freezer_thaw_percentage(), 0, "new interval starts with a thaw percentage of 0");
1245 move_to_idle_band(child_pid
);
1246 ret
= pid_suspend(child_pid
);
1247 T_QUIET
; T_ASSERT_POSIX_SUCCESS(ret
, "child suspended");
1248 freeze_process(child_pid
);
1249 ret
= pid_resume(child_pid
);
1250 T_QUIET
; T_ASSERT_POSIX_SUCCESS(ret
, "child resumed after freeze");
1251 int percentage_after_thaw
= memorystatus_freezer_thaw_percentage();
1252 T_QUIET
; T_ASSERT_GT(percentage_after_thaw
, 0, "thaw percentage is higher after thaw");
1254 ret
= pid_suspend(child_pid
);
1255 T_QUIET
; T_ASSERT_POSIX_SUCCESS(ret
, "child suspended");
1256 freeze_process(child_pid
);
1257 ret
= pid_resume(child_pid
);
1258 T_QUIET
; T_ASSERT_POSIX_SUCCESS(ret
, "child resumed after freeze");
1259 T_QUIET
; T_ASSERT_EQ(memorystatus_freezer_thaw_percentage(), percentage_after_thaw
, "thaw percentage is unchanged after second thaw");
1261 ret
= pid_suspend(child_pid
);
1262 T_QUIET
; T_ASSERT_POSIX_SUCCESS(ret
, "child suspended");
1263 freeze_process(child_pid
);
1265 T_QUIET
; T_ASSERT_EQ(memorystatus_freezer_thaw_percentage(), 0, "new interval starts with a 0 thaw percentage");
1266 ret
= pid_resume(child_pid
);
1267 T_QUIET
; T_ASSERT_POSIX_SUCCESS(ret
, "child resumed after freeze");
1268 T_QUIET
; T_ASSERT_GT(memorystatus_freezer_thaw_percentage(), 0, "thaw percentage goes back up in new interval");
1270 T_QUIET
; T_ASSERT_POSIX_SUCCESS(kill(child_pid
, SIGKILL
), "failed to kill child");
1275 second_child
= launch_background_helper("frozen_background");
1276 T_ATEND(cleanup_memorystatus_freezer_thaw_percentage
);
1277 dispatch_activate(first_signal_block
);