]> git.saurik.com Git - apple/xnu.git/blame - tests/memorystatus_freeze_test.c
xnu-7195.101.1.tar.gz
[apple/xnu.git] / tests / memorystatus_freeze_test.c
CommitLineData
d9a64523
A
1#include <stdio.h>
2#include <signal.h>
c3c9b80d 3#include <sys/proc.h>
d9a64523
A
4#include <sys/sysctl.h>
5#include <sys/kern_memorystatus.h>
f427ee49 6#include <sys/kern_memorystatus_freeze.h>
ea3f0419 7#include <time.h>
d9a64523 8#include <mach-o/dyld.h>
cb323159
A
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>
d9a64523
A
13
14#ifdef T_NAMESPACE
15#undef T_NAMESPACE
16#endif
17#include <darwintest.h>
18#include <darwintest_utils.h>
19
cb323159
A
20#include "memorystatus_assertion_helpers.h"
21
d9a64523
A
22T_GLOBAL_META(
23 T_META_NAMESPACE("xnu.vm"),
24 T_META_CHECK_LEAKS(false)
0a7de745 25 );
d9a64523 26
0a7de745
A
27#define MEM_SIZE_MB 10
28#define NUM_ITERATIONS 5
cb323159 29#define FREEZE_PAGES_MAX 256
d9a64523
A
30
31#define CREATE_LIST(X) \
32 X(SUCCESS) \
33 X(TOO_FEW_ARGUMENTS) \
34 X(SYSCTL_VM_PAGESIZE_FAILED) \
35 X(VM_PAGESIZE_IS_ZERO) \
d9a64523
A
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) \
ea3f0419 42 X(INVALID_ALLOCATE_PAGES_ARGUMENTS) \
2a1bd2d3
A
43 X(FROZEN_BIT_SET) \
44 X(FROZEN_BIT_NOT_SET) \
45 X(MEMORYSTATUS_CONTROL_ERROR) \
46 X(EXIT_CODE_MAX) \
d9a64523
A
47
48#define EXIT_CODES_ENUM(VAR) VAR,
49enum exit_codes_num {
50 CREATE_LIST(EXIT_CODES_ENUM)
51};
52
53#define EXIT_CODES_STRING(VAR) #VAR,
54static const char *exit_codes_str[] = {
55 CREATE_LIST(EXIT_CODES_STRING)
56};
57
cb323159
A
58static int
59get_vmpage_size()
60{
61 int vmpage_size;
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");
66 return vmpage_size;
67}
d9a64523 68
cb323159 69static pid_t child_pid = -1;
d9a64523
A
70static int freeze_count = 0;
71
f427ee49 72void move_to_idle_band(pid_t);
cb323159 73void run_freezer_test(int);
d9a64523 74void freeze_helper_process(void);
cb323159
A
75/* Gets and optionally sets the freeze pages max threshold */
76int sysctl_freeze_pages_max(int* new_value);
77
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.
80 */
81
82/* Cribbed from 'top'... */
83static int
84in_shared_region(mach_vm_address_t addr, cpu_type_t type)
85{
86 mach_vm_address_t base = 0, size = 0;
87
88 switch (type) {
89 case CPU_TYPE_ARM:
90 base = SHARED_REGION_BASE_ARM;
91 size = SHARED_REGION_SIZE_ARM;
92 break;
93
94 case CPU_TYPE_ARM64:
95 base = SHARED_REGION_BASE_ARM64;
96 size = SHARED_REGION_SIZE_ARM64;
97 break;
98
99
100 case CPU_TYPE_X86_64:
101 base = SHARED_REGION_BASE_X86_64;
102 size = SHARED_REGION_SIZE_X86_64;
103 break;
104
105 case CPU_TYPE_I386:
106 base = SHARED_REGION_BASE_I386;
107 size = SHARED_REGION_SIZE_I386;
108 break;
109
110 case CPU_TYPE_POWERPC:
111 base = SHARED_REGION_BASE_PPC;
112 size = SHARED_REGION_SIZE_PPC;
113 break;
114
115 case CPU_TYPE_POWERPC64:
116 base = SHARED_REGION_BASE_PPC64;
117 size = SHARED_REGION_SIZE_PPC64;
118 break;
119
120 default: {
121 int t = type;
122
123 fprintf(stderr, "unknown CPU type: 0x%x\n", t);
124 abort();
125 }
126 }
127
128 return addr >= base && addr < (base + size);
129}
130
131/* Get the resident private memory of the given pid */
132static unsigned long long
133get_rprvt(pid_t pid)
134{
135 mach_port_name_t task;
136 kern_return_t kr;
137
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;
144
145 mach_vm_address_t addr;
146 mach_vm_size_t size;
147
148 int split = 0;
149
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");
152
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;
157
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) {
160 break;
161 }
162
163#if defined (__arm64__)
164 if (in_shared_region(addr, CPU_TYPE_ARM64)) {
165#else
166 if (in_shared_region(addr, CPU_TYPE_ARM)) {
167#endif
168 // Private Shared
169 fw_private += info.private_pages_resident * pagesize;
170
171 /*
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
175 * again.
176 */
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;
182
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) {
185 break;
186 }
187
188 if (b_info.reserved) {
189 split = TRUE;
190 }
191 }
192
193 /*
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
197 * range.
198 */
199 if (info.share_mode != SM_PRIVATE) {
200 continue;
201 }
202 }
d9a64523 203
cb323159
A
204 regs++;
205
206 /*
207 * Update counters according to the region type.
208 */
209
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;
213 }
214
215 switch (info.share_mode) {
216 case SM_LARGE_PAGE:
217 // Treat SM_LARGE_PAGE the same as SM_PRIVATE
218 // since they are not shareable and are wired.
219 case SM_PRIVATE:
220 rprvt += info.private_pages_resident * pagesize;
221 rprvt += info.shared_pages_resident * pagesize;
222 break;
223
224 case SM_EMPTY:
225 empty += size;
226 break;
227
228 case SM_COW:
229 case SM_SHARED:
230 if (pid == 0) {
231 // Treat kernel_task specially
232 if (info.share_mode == SM_COW) {
233 rprvt += info.private_pages_resident * pagesize;
234 }
235 break;
236 }
237
238 if (info.share_mode == SM_COW) {
239 rprvt += info.private_pages_resident * pagesize;
240 }
241 break;
242
243 default:
244 assert(0);
245 break;
246 }
247 }
248
249 return rprvt;
250}
d9a64523 251
0a7de745 252void
f427ee49 253move_to_idle_band(pid_t pid)
0a7de745 254{
d9a64523
A
255 memorystatus_priority_properties_t props;
256 /*
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.
259 */
260 props.priority = JETSAM_PRIORITY_IDLE;
261 props.user_data = 0;
262
263 /*
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.
266 */
f427ee49 267 if (memorystatus_control(MEMORYSTATUS_CMD_SET_PRIORITY_PROPERTIES, pid, 0, &props, sizeof(props))) {
d9a64523
A
268 exit(MEMSTAT_PRIORITY_CHANGE_FAILED);
269 }
270}
271
0a7de745
A
272void
273freeze_helper_process(void)
274{
275 size_t length;
276 int ret, freeze_enabled, errno_freeze_sysctl;
cb323159
A
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;
d9a64523 280
cb323159
A
281 T_LOG("Freezing child pid %d", child_pid);
282 ret = sysctlbyname("kern.memorystatus_freeze", NULL, NULL, &child_pid, sizeof(child_pid));
0a7de745 283 errno_freeze_sysctl = errno;
d9a64523
A
284 sleep(1);
285
0a7de745
A
286 /*
287 * The child process toggles its freezable state on each iteration.
288 * So a failure for every alternate freeze is expected.
289 */
cb323159 290 if (freeze_count % 2) {
0a7de745
A
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");
297 } else {
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.");
300 T_END;
301 }
cb323159
A
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.");
307 }
308 ret = sysctlbyname("kern.memorystatus_thaw", NULL, NULL, &child_pid, sizeof(child_pid));
d9a64523
A
309 T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "sysctl kern.memorystatus_thaw failed");
310 } else {
311 T_QUIET; T_ASSERT_TRUE(ret != KERN_SUCCESS, "Freeze should have failed");
312 T_LOG("Freeze failed as expected");
313 }
314
315 freeze_count++;
316
cb323159 317 T_QUIET; T_ASSERT_POSIX_SUCCESS(kill(child_pid, SIGUSR1), "failed to send SIGUSR1 to child process");
d9a64523
A
318}
319
f427ee49
A
320static void
321skip_if_freezer_is_disabled()
322{
323 int freeze_enabled;
324 size_t length = sizeof(freeze_enabled);
325
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.");
331 }
332}
333
0a7de745 334void
cb323159 335run_freezer_test(int num_pages)
0a7de745 336{
f427ee49 337 int ret;
d9a64523
A
338 char sz_str[50];
339 char **launch_tool_args;
340 char testpath[PATH_MAX];
341 uint32_t testpath_buf_size;
342 dispatch_source_t ds_freeze, ds_proc;
0a7de745 343
f427ee49 344 skip_if_freezer_is_disabled();
d9a64523
A
345
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)");
349
350 dispatch_source_set_event_handler(ds_freeze, ^{
351 if (freeze_count < NUM_ITERATIONS) {
0a7de745 352 freeze_helper_process();
d9a64523 353 } else {
cb323159 354 kill(child_pid, SIGKILL);
0a7de745 355 dispatch_source_cancel(ds_freeze);
d9a64523
A
356 }
357 });
358 dispatch_activate(ds_freeze);
359
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);
364
cb323159 365 sprintf(sz_str, "%d", num_pages);
d9a64523
A
366 launch_tool_args = (char *[]){
367 testpath,
368 "-n",
369 "allocate_pages",
370 "--",
371 sz_str,
372 NULL
373 };
374
375 /* Spawn the child process. Suspend after launch until the exit proc handler has been set up. */
cb323159 376 ret = dt_launch_tool(&child_pid, launch_tool_args, true, NULL, NULL);
d9a64523
A
377 if (ret != 0) {
378 T_LOG("dt_launch tool returned %d with error code %d", ret, errno);
379 }
cb323159 380 T_QUIET; T_ASSERT_POSIX_SUCCESS(child_pid, "dt_launch_tool");
d9a64523 381
cb323159 382 ds_proc = dispatch_source_create(DISPATCH_SOURCE_TYPE_PROC, (uintptr_t)child_pid, DISPATCH_PROC_EXIT, dispatch_get_main_queue());
d9a64523
A
383 T_QUIET; T_ASSERT_NOTNULL(ds_proc, "dispatch_source_create (ds_proc)");
384
385 dispatch_source_set_event_handler(ds_proc, ^{
386 int status = 0, code = 0;
cb323159
A
387 pid_t rc = waitpid(child_pid, &status, 0);
388 T_QUIET; T_ASSERT_EQ(rc, child_pid, "waitpid");
d9a64523
A
389 code = WEXITSTATUS(status);
390
391 if (code == 0) {
0a7de745 392 T_END;
d9a64523 393 } else if (code > 0 && code < EXIT_CODE_MAX) {
0a7de745 394 T_ASSERT_FAIL("Child exited with %s", exit_codes_str[code]);
d9a64523 395 } else {
0a7de745 396 T_ASSERT_FAIL("Child exited with unknown exit code %d", code);
d9a64523
A
397 }
398 });
399 dispatch_activate(ds_proc);
400
cb323159 401 T_QUIET; T_ASSERT_POSIX_SUCCESS(kill(child_pid, SIGCONT), "failed to send SIGCONT to child process");
d9a64523
A
402 dispatch_main();
403}
404
cb323159
A
405static void
406allocate_pages(int num_pages)
407{
408 int i, j, vmpgsize;
d9a64523 409 char val;
cb323159 410 __block int num_iter = 0;
d9a64523
A
411 __block char **buf;
412 dispatch_source_t ds_signal;
cb323159
A
413 vmpgsize = get_vmpage_size();
414 if (num_pages < 1) {
415 printf("Invalid number of pages to allocate: %d\n", num_pages);
416 exit(INVALID_ALLOCATE_PAGES_ARGUMENTS);
d9a64523
A
417 }
418
d9a64523
A
419 buf = (char**)malloc(sizeof(char*) * (size_t)num_pages);
420
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));
424 val = 0;
425 for (i = 0; i < vmpgsize; i += 16) {
426 memset(&buf[j][i], val, 16);
427 if (i < 3400 * (vmpgsize / 4096)) {
428 val++;
429 }
430 }
431 }
432
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 */
0a7de745 435 printf("[%d] Sending initial signal to parent to begin freezing\n", getpid());
d9a64523 436 if (kill(getppid(), SIGUSR1) != 0) {
0a7de745 437 exit(INITIAL_SIGNAL_TO_PARENT_FAILED);
d9a64523
A
438 }
439 });
440
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);
445 }
446
447 dispatch_source_set_event_handler(ds_signal, ^{
448 int current_state, new_state;
449 volatile int tmp;
450
451 /* Make sure all the pages are accessed before trying to freeze again */
452 for (int x = 0; x < num_pages; x++) {
0a7de745 453 tmp = buf[x][0];
d9a64523
A
454 }
455
456 current_state = memorystatus_control(MEMORYSTATUS_CMD_GET_PROCESS_IS_FREEZABLE, getpid(), 0, NULL, 0);
cb323159
A
457 /* Sysprocs start off as unfreezable. Verify that first. */
458 if (num_iter == 0 && current_state != 0) {
459 exit(IS_FREEZABLE_NOT_AS_EXPECTED);
460 }
d9a64523
A
461
462 /* Toggle freezable state */
463 new_state = (current_state) ? 0: 1;
0a7de745
A
464 printf("[%d] Changing state from %s to %s\n", getpid(),
465 (current_state) ? "freezable": "unfreezable", (new_state) ? "freezable": "unfreezable");
d9a64523 466 if (memorystatus_control(MEMORYSTATUS_CMD_SET_PROCESS_IS_FREEZABLE, getpid(), (uint32_t)new_state, NULL, 0) != KERN_SUCCESS) {
0a7de745 467 exit(MEMORYSTATUS_CONTROL_FAILED);
d9a64523
A
468 }
469
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) {
0a7de745 473 exit(IS_FREEZABLE_NOT_AS_EXPECTED);
d9a64523
A
474 }
475 num_iter++;
476
477 if (kill(getppid(), SIGUSR1) != 0) {
0a7de745 478 exit(SIGNAL_TO_PARENT_FAILED);
d9a64523
A
479 }
480 });
481 dispatch_activate(ds_signal);
f427ee49 482 move_to_idle_band(getpid());
d9a64523
A
483
484 dispatch_main();
485}
486
cb323159
A
487T_HELPER_DECL(allocate_pages,
488 "allocates pages to freeze",
489 T_META_ASROOT(true)) {
490 if (argc < 1) {
491 exit(TOO_FEW_ARGUMENTS);
492 }
493
494 int num_pages = atoi(argv[0]);
495 allocate_pages(num_pages);
496}
497
498T_DECL(freeze, "VM freezer test", T_META_ASROOT(true)) {
499 run_freezer_test(
500 (MEM_SIZE_MB << 20) / get_vmpage_size());
501}
502
503static int old_freeze_pages_max = 0;
504static void
505reset_freeze_pages_max()
506{
507 if (old_freeze_pages_max != 0) {
508 sysctl_freeze_pages_max(&old_freeze_pages_max);
509 }
510}
511
512int
513sysctl_freeze_pages_max(int* new_value)
514{
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;
525 }
526 return old_freeze_pages_max;
527}
528
529T_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);
533}
534
535T_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!");
543 exit(1);
544 }
545 while (1) {
546 ;
547 }
548}
549
550/* Launches the frozen_background helper as a managed process. */
551static pid_t
2a1bd2d3 552launch_background_helper(const char* variant)
cb323159
A
553{
554 pid_t pid;
555 char **launch_tool_args;
556 char testpath[PATH_MAX];
c3c9b80d 557 char *variant_cpy = strdup(variant);
cb323159
A
558 uint32_t testpath_buf_size;
559 int ret;
560
561 testpath_buf_size = sizeof(testpath);
562 ret = _NSGetExecutablePath(testpath, &testpath_buf_size);
563 printf("Launching %s\n", testpath);
564 launch_tool_args = (char *[]){
565 testpath,
566 "-n",
c3c9b80d 567 variant_cpy,
cb323159
A
568 NULL
569 };
570 ret = dt_launch_tool(&pid, launch_tool_args, false, NULL, NULL);
571 if (ret != 0) {
572 T_LOG("dt_launch tool returned %d with error code %d", ret, errno);
573 }
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");
c3c9b80d 578 free(variant_cpy);
cb323159
A
579 return pid;
580}
581
582static void
583freeze_process(pid_t pid)
584{
585 int ret, freeze_enabled, errno_freeze_sysctl;
586 size_t length;
587 T_LOG("Freezing pid %d", pid);
588
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");
597 } else {
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.");
600 T_END;
601 }
602}
603
604static void
605memorystatus_assertion_test_demote_frozen()
606{
cb323159
A
607 /*
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.
610 */
611 /* these values will remain fixed during testing */
612 int active_limit_mb = 15; /* arbitrary */
613 int inactive_limit_mb = 7; /* arbitrary */
c3c9b80d 614 __block int demote_value = 1;
cb323159
A
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;
619
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, ^{
625 int sysctl_ret;
626 /* Freeze the process, trigger agressive demotion, and check that it hasn't been demoted. */
627 freeze_process(child_pid);
628 /* Agressive demotion */
ea3f0419
A
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");
cb323159
A
631 /* Check */
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.");
637 /* Kill the child */
ea3f0419 638 T_QUIET; T_ASSERT_POSIX_SUCCESS(kill(child_pid, SIGKILL), "Killed child process");
cb323159
A
639 T_END;
640 });
641
642 /* Launch the child process and set the initial properties on it. */
2a1bd2d3 643 child_pid = launch_background_helper("frozen_background");
cb323159
A
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");
655 T_END;
656 });
657
658 dispatch_activate(ds_exit);
659 dispatch_activate(ds_signal);
660 dispatch_main();
661}
662
663T_DECL(assertion_test_demote_frozen, "demoted frozen process goes to asserted priority.", T_META_ASROOT(true)) {
664 memorystatus_assertion_test_demote_frozen();
d9a64523 665}
ea3f0419
A
666
667T_DECL(budget_replenishment, "budget replenishes properly") {
668 size_t length;
669 int ret;
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();
677
678 /*
679 * Calculate a new budget as if the previous interval expired kTestIntervalSecs
680 * ago and we used up its entire budget.
681 */
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);
bca245ac 685 T_ASSERT_POSIX_SUCCESS(ret, "vm.memorystatus_freeze_calculate_new_budget");
ea3f0419
A
686
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);
bca245ac 690 T_ASSERT_POSIX_SUCCESS(ret, "kern.memorystatus_freeze_daily_mb_max");
ea3f0419 691
bca245ac
A
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);
ea3f0419
A
696
697 /*
698 * We're kTestIntervalSecs past a new interval. Which means we are owed kNumSecondsInDay
699 * seconds of budget.
700 */
701 expected_new_budget_pages = memorystatus_freeze_daily_pages_max;
bca245ac
A
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");
ea3f0419
A
704 expected_new_budget_pages += ((kTestIntervalSecs * kFixedPointFactor) / (kNumSecondsInDay)
705 * memorystatus_freeze_daily_pages_max) / kFixedPointFactor;
bca245ac
A
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);
ea3f0419
A
708
709 T_QUIET; T_ASSERT_EQ(new_budget, expected_new_budget_pages, "Calculate new budget behaves correctly.");
710}
f427ee49
A
711
712
713static bool
714is_proc_in_frozen_list(pid_t pid, char* name, size_t name_len)
715{
716 int bytes_written;
717 bool found = false;
718 global_frozen_procs_t *frozen_procs = malloc(sizeof(global_frozen_procs_t));
719 T_QUIET; T_ASSERT_NOTNULL(frozen_procs, "malloc");
720
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");
724
725 for (size_t i = 0; i < frozen_procs->gfp_num_frozen; i++) {
726 if (frozen_procs->gfp_procs[i].fp_pid == pid) {
727 found = true;
728 strlcpy(name, frozen_procs->gfp_procs[i].fp_name, name_len);
729 }
730 }
731 return found;
732}
733
734static void
c3c9b80d 735unset_testing_pid(void)
f427ee49
A
736{
737 int ret;
c3c9b80d 738 ret = memorystatus_control(MEMORYSTATUS_CMD_SET_TESTING_PID, 0, MEMORYSTATUS_FLAGS_UNSET_TESTING_PID, NULL, 0);
f427ee49
A
739 T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, 0, "Drop ownership of jetsam snapshot");
740}
741
742static void
c3c9b80d 743set_testing_pid(void)
f427ee49
A
744{
745 int ret;
c3c9b80d 746 ret = memorystatus_control(MEMORYSTATUS_CMD_SET_TESTING_PID, 0, MEMORYSTATUS_FLAGS_SET_TESTING_PID, NULL, 0);
f427ee49 747 T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "Take ownership of jetsam snapshot");
c3c9b80d 748 T_ATEND(unset_testing_pid);
f427ee49
A
749}
750
751/*
752 * Retrieve a jetsam snapshot.
753 *
754 * return:
755 * pointer to snapshot.
756 *
757 * Caller is responsible for freeing snapshot.
758 */
759static
760memorystatus_jetsam_snapshot_t *
761get_jetsam_snapshot(uint32_t flags, bool empty_allowed)
762{
763 memorystatus_jetsam_snapshot_t * snapshot = NULL;
764 int ret;
765 uint32_t size;
766
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) {
771 return snapshot;
772 }
773
774 snapshot = (memorystatus_jetsam_snapshot_t*)malloc(size);
775 T_QUIET; T_ASSERT_NOTNULL(snapshot, "Allocate snapshot of size %d", size);
776
777 ret = memorystatus_control(MEMORYSTATUS_CMD_GET_JETSAM_SNAPSHOT, 0, flags, snapshot, size);
778 T_QUIET; T_ASSERT_GT(size, 0, "Get jetsam snapshot");
779
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)));
784 if (snapshot) {
785 free(snapshot);
786 }
787 }
788
789 return snapshot;
790}
791
792/*
793 * Look for the given pid in the snapshot.
794 *
795 * return:
796 * pointer to pid's entry or NULL if pid is not found.
797 *
798 * Caller has ownership of snapshot before and after call.
799 */
800static
801memorystatus_jetsam_snapshot_entry_t *
802get_jetsam_snapshot_entry(memorystatus_jetsam_snapshot_t *snapshot, pid_t pid)
803{
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) {
808 return curr;
809 }
810 }
811
812 return NULL;
813}
814
c3c9b80d
A
815static dispatch_source_t
816run_block_after_signal(int sig, dispatch_block_t block)
817{
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);
823 return ds_signal;
824}
825
f427ee49
A
826/*
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.
829 */
830static void
2a1bd2d3 831test_after_background_helper_launches(bool exit_with_child, const char* variant, dispatch_block_t test_block)
f427ee49
A
832{
833 dispatch_source_t ds_signal, ds_exit;
834
c3c9b80d 835 ds_signal = run_block_after_signal(SIGUSR1, test_block);
f427ee49 836 /* Launch the child process. */
2a1bd2d3 837 child_pid = launch_background_helper(variant);
f427ee49
A
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);
2a1bd2d3
A
846 if (code != 0) {
847 T_LOG("Child exited with error: %s", exit_codes_str[code]);
848 }
f427ee49
A
849 T_QUIET; T_ASSERT_EQ(code, 0, "Child exited cleanly");
850 T_END;
851 });
852
853 dispatch_activate(ds_exit);
854 }
855 dispatch_activate(ds_signal);
f427ee49
A
856}
857
858T_DECL(get_frozen_procs, "List processes in the freezer") {
859 skip_if_freezer_is_disabled();
860
2a1bd2d3 861 test_after_background_helper_launches(true, "frozen_background", ^{
f427ee49
A
862 proc_name_t name;
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);
867 /* Check */
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");
870 /* Kill the child */
871 T_QUIET; T_ASSERT_POSIX_SUCCESS(kill(child_pid, SIGKILL), "Killed child process");
872 T_END;
873 });
c3c9b80d 874 dispatch_main();
f427ee49
A
875}
876
877T_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;
880
881 skip_if_freezer_is_disabled();
882
2a1bd2d3 883 test_after_background_helper_launches(true, "frozen_background", ^{
f427ee49
A
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);
889 /*
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.
893 */
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) {
899 break;
900 }
901 free(snapshot);
902 sleep(kSnapshotSleepDelay);
903 }
904 T_QUIET; T_ASSERT_GT(child_entry->jse_frozen_to_swap_pages, 0ULL, "child has some pages in swap");
905 free(snapshot);
906 /* Kill the child */
907 T_QUIET; T_ASSERT_POSIX_SUCCESS(kill(child_pid, SIGKILL), "Killed child process");
908 T_END;
909 });
c3c9b80d 910 dispatch_main();
f427ee49
A
911}
912
913T_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. */
c3c9b80d 915 set_testing_pid();
f427ee49 916
2a1bd2d3 917 test_after_background_helper_launches(false, "frozen_background", ^{
f427ee49
A
918 int ret;
919 memorystatus_jetsam_snapshot_t *snapshot = NULL;
920 memorystatus_jetsam_snapshot_entry_t *child_entry = NULL;
921
922 ret = memorystatus_control(MEMORYSTATUS_CMD_TEST_JETSAM, child_pid, 0, 0, 0);
923 T_ASSERT_POSIX_SUCCESS(ret, "jetsam'd the child");
924
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");
930
931 free(snapshot);
932 T_END;
933 });
c3c9b80d 934 dispatch_main();
f427ee49
A
935}
936
937T_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. */
c3c9b80d 939 set_testing_pid();
f427ee49 940
2a1bd2d3 941 test_after_background_helper_launches(false, "frozen_background", ^{
f427ee49
A
942 int ret;
943 memorystatus_jetsam_snapshot_t *snapshot = NULL;
944 memorystatus_jetsam_snapshot_entry_t *child_entry = NULL;
945
946 ret = memorystatus_control(MEMORYSTATUS_CMD_TEST_JETSAM, child_pid, 0, 0, 0);
947 T_ASSERT_POSIX_SUCCESS(ret, "jetsam'd the child");
948
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");
953
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");
958 }
959
960 free(snapshot);
961 T_END;
962 });
c3c9b80d 963 dispatch_main();
f427ee49
A
964}
965
966T_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. */
c3c9b80d 969 set_testing_pid();
f427ee49 970
2a1bd2d3 971 test_after_background_helper_launches(false, "frozen_background", ^{
f427ee49
A
972 int ret;
973 memorystatus_jetsam_snapshot_t *snapshot = NULL;
974 memorystatus_jetsam_snapshot_entry_t *child_entry = NULL;
975
976 move_to_idle_band(child_pid);
977 freeze_process(child_pid);
978
979 ret = memorystatus_control(MEMORYSTATUS_CMD_TEST_JETSAM, child_pid, 0, 0, 0);
980 T_ASSERT_POSIX_SUCCESS(ret, "jetsam'd the child");
981
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");
987
988 free(snapshot);
989 T_END;
990 });
c3c9b80d 991 dispatch_main();
f427ee49
A
992}
993
994T_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. */
c3c9b80d 997 set_testing_pid();
f427ee49 998
2a1bd2d3 999 test_after_background_helper_launches(false, "frozen_background", ^{
f427ee49
A
1000 int ret;
1001 memorystatus_jetsam_snapshot_t *snapshot = NULL;
1002 memorystatus_jetsam_snapshot_entry_t *child_entry = NULL;
1003
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");
1010
1011 ret = memorystatus_control(MEMORYSTATUS_CMD_TEST_JETSAM, child_pid, 0, 0, 0);
1012 T_ASSERT_POSIX_SUCCESS(ret, "jetsam'd the child");
1013
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");
1021
1022 free(snapshot);
1023 T_END;
1024 });
1025}
2a1bd2d3
A
1026
1027T_HELPER_DECL(check_frozen, "Check frozen state", T_META_ASROOT(true)) {
1028 int kern_ret;
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");
2a1bd2d3
A
1034
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);
1040 }
1041 if (is_frozen) {
1042 exit(FROZEN_BIT_SET);
1043 }
1044
2a1bd2d3
A
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);
1048 }
1049
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);
1056 }
1057 if (!is_frozen) {
1058 exit(FROZEN_BIT_NOT_SET);
1059 }
1060 exit(SUCCESS);
1061 });
1062 dispatch_activate(ds_signal);
1063
c3c9b80d
A
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)");
1066
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);
1071 }
1072
2a1bd2d3
A
1073 dispatch_main();
1074}
1075
1076T_DECL(memorystatus_get_process_is_frozen, "MEMORYSTATUS_CMD_GET_PROCESS_IS_FROZEN returns correct state") {
1077 skip_if_freezer_is_disabled();
1078
1079 test_after_background_helper_launches(true, "check_frozen", ^{
1080 int ret;
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");
1088
1089 kill(child_pid, SIGUSR1);
1090 /* The child will checks its own frozen state & exit. */
1091 });
c3c9b80d
A
1092 dispatch_main();
1093}
1094
1095static unsigned int freeze_pages_min_old;
1096static int throttle_enabled_old;
1097static 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));
1100}
1101
1102#define P_MEMSTAT_FROZEN 0x00000002
1103T_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);
1116
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);
1120
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. */
1128 set_testing_pid();
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");
1135
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);
1146
1147 bool found = 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) {
1156 found = true;
1157 property_entry->use_probability = 1;
1158 child_band = snapshot_entry->priority;
1159 } else {
1160 property_entry->use_probability = 0;
1161 }
1162 strncpy(property_entry->proc_name, snapshot_entry->name, MAXCOMLEN);
1163 property_entry->proc_name[MAXCOMLEN] = '\0';
1164 }
1165 }
1166 T_QUIET; T_ASSERT_TRUE(found, "Child is in on demand snapshot");
1167 free(snapshot);
1168 }
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);
1172 int val = 1;
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);
1181 }
1182 T_ASSERT_TRUE(entry->state & P_MEMSTAT_FROZEN, "child is frozen");
1183 free(snapshot);
1184 ret = pid_resume(child_pid);
1185 T_ASSERT_POSIX_SUCCESS(ret, "child resumed after freeze");
1186
1187 /* Kill the child */
1188 T_QUIET; T_ASSERT_POSIX_SUCCESS(kill(child_pid, SIGKILL), "Killed child process");
1189 T_END;
1190 });
1191 dispatch_main();
1192}
1193
1194static int
1195memorystatus_freezer_thaw_percentage(void)
1196{
1197 int val;
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");
1201 return val;
1202}
1203
1204static void
1205reset_interval(void)
1206{
1207 uint32_t freeze_daily_budget_mb = 0;
1208 size_t size = sizeof(freeze_daily_budget_mb);
1209 int ret;
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");
1216}
1217
1218static pid_t second_child;
1219static void
1220cleanup_memorystatus_freezer_thaw_percentage(void)
1221{
1222 kill(second_child, SIGKILL);
1223}
1224
1225T_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. */
1231 set_testing_pid();
1232 reset_interval();
1233
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", ^{
1243 reset_interval();
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");
1253
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");
1260
1261 ret = pid_suspend(child_pid);
1262 T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "child suspended");
1263 freeze_process(child_pid);
1264 reset_interval();
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");
1269
1270 T_QUIET; T_ASSERT_POSIX_SUCCESS(kill(child_pid, SIGKILL), "failed to kill child");
1271 T_END;
1272 });
1273 });
1274
1275 second_child = launch_background_helper("frozen_background");
1276 T_ATEND(cleanup_memorystatus_freezer_thaw_percentage);
1277 dispatch_activate(first_signal_block);
1278 dispatch_main();
2a1bd2d3 1279}