]> git.saurik.com Git - apple/xnu.git/blame - tests/memorystatus_freeze_test.c
xnu-6153.141.1.tar.gz
[apple/xnu.git] / tests / memorystatus_freeze_test.c
CommitLineData
d9a64523
A
1#include <stdio.h>
2#include <signal.h>
3#include <sys/sysctl.h>
4#include <sys/kern_memorystatus.h>
ea3f0419 5#include <time.h>
d9a64523 6#include <mach-o/dyld.h>
cb323159
A
7#include <mach/mach_vm.h>
8#include <mach/vm_page_size.h> /* Needed for vm_region info */
9#include <mach/shared_region.h>
10#include <mach/mach.h>
d9a64523
A
11
12#ifdef T_NAMESPACE
13#undef T_NAMESPACE
14#endif
15#include <darwintest.h>
16#include <darwintest_utils.h>
17
cb323159
A
18#include "memorystatus_assertion_helpers.h"
19
d9a64523
A
20T_GLOBAL_META(
21 T_META_NAMESPACE("xnu.vm"),
22 T_META_CHECK_LEAKS(false)
0a7de745 23 );
d9a64523 24
0a7de745
A
25#define MEM_SIZE_MB 10
26#define NUM_ITERATIONS 5
cb323159 27#define FREEZE_PAGES_MAX 256
d9a64523
A
28
29#define CREATE_LIST(X) \
30 X(SUCCESS) \
31 X(TOO_FEW_ARGUMENTS) \
32 X(SYSCTL_VM_PAGESIZE_FAILED) \
33 X(VM_PAGESIZE_IS_ZERO) \
d9a64523
A
34 X(DISPATCH_SOURCE_CREATE_FAILED) \
35 X(INITIAL_SIGNAL_TO_PARENT_FAILED) \
36 X(SIGNAL_TO_PARENT_FAILED) \
37 X(MEMORYSTATUS_CONTROL_FAILED) \
38 X(IS_FREEZABLE_NOT_AS_EXPECTED) \
39 X(MEMSTAT_PRIORITY_CHANGE_FAILED) \
ea3f0419 40 X(INVALID_ALLOCATE_PAGES_ARGUMENTS) \
d9a64523
A
41 X(EXIT_CODE_MAX)
42
43#define EXIT_CODES_ENUM(VAR) VAR,
44enum exit_codes_num {
45 CREATE_LIST(EXIT_CODES_ENUM)
46};
47
48#define EXIT_CODES_STRING(VAR) #VAR,
49static const char *exit_codes_str[] = {
50 CREATE_LIST(EXIT_CODES_STRING)
51};
52
cb323159
A
53static int
54get_vmpage_size()
55{
56 int vmpage_size;
57 size_t size = sizeof(vmpage_size);
58 int ret = sysctlbyname("vm.pagesize", &vmpage_size, &size, NULL, 0);
59 T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "failed to query vm.pagesize");
60 T_QUIET; T_ASSERT_GT(vmpage_size, 0, "vm.pagesize is not > 0");
61 return vmpage_size;
62}
d9a64523 63
cb323159 64static pid_t child_pid = -1;
d9a64523
A
65static int freeze_count = 0;
66
67void move_to_idle_band(void);
cb323159 68void run_freezer_test(int);
d9a64523 69void freeze_helper_process(void);
cb323159
A
70/* Gets and optionally sets the freeze pages max threshold */
71int sysctl_freeze_pages_max(int* new_value);
72
73/* NB: in_shared_region and get_rprvt are pulled from the memorystatus unit test.
74 * We're moving away from those unit tests, so they're copied here.
75 */
76
77/* Cribbed from 'top'... */
78static int
79in_shared_region(mach_vm_address_t addr, cpu_type_t type)
80{
81 mach_vm_address_t base = 0, size = 0;
82
83 switch (type) {
84 case CPU_TYPE_ARM:
85 base = SHARED_REGION_BASE_ARM;
86 size = SHARED_REGION_SIZE_ARM;
87 break;
88
89 case CPU_TYPE_ARM64:
90 base = SHARED_REGION_BASE_ARM64;
91 size = SHARED_REGION_SIZE_ARM64;
92 break;
93
94
95 case CPU_TYPE_X86_64:
96 base = SHARED_REGION_BASE_X86_64;
97 size = SHARED_REGION_SIZE_X86_64;
98 break;
99
100 case CPU_TYPE_I386:
101 base = SHARED_REGION_BASE_I386;
102 size = SHARED_REGION_SIZE_I386;
103 break;
104
105 case CPU_TYPE_POWERPC:
106 base = SHARED_REGION_BASE_PPC;
107 size = SHARED_REGION_SIZE_PPC;
108 break;
109
110 case CPU_TYPE_POWERPC64:
111 base = SHARED_REGION_BASE_PPC64;
112 size = SHARED_REGION_SIZE_PPC64;
113 break;
114
115 default: {
116 int t = type;
117
118 fprintf(stderr, "unknown CPU type: 0x%x\n", t);
119 abort();
120 }
121 }
122
123 return addr >= base && addr < (base + size);
124}
125
126/* Get the resident private memory of the given pid */
127static unsigned long long
128get_rprvt(pid_t pid)
129{
130 mach_port_name_t task;
131 kern_return_t kr;
132
133 mach_vm_size_t rprvt = 0;
134 mach_vm_size_t empty = 0;
135 mach_vm_size_t fw_private = 0;
136 mach_vm_size_t pagesize = vm_kernel_page_size; // The vm_region page info is reported
137 // in terms of vm_kernel_page_size.
138 mach_vm_size_t regs = 0;
139
140 mach_vm_address_t addr;
141 mach_vm_size_t size;
142
143 int split = 0;
144
145 kr = task_for_pid(mach_task_self(), pid, &task);
146 T_QUIET; T_ASSERT_TRUE(kr == KERN_SUCCESS, "Unable to get task_for_pid of child");
147
148 for (addr = 0;; addr += size) {
149 vm_region_top_info_data_t info;
150 mach_msg_type_number_t count = VM_REGION_TOP_INFO_COUNT;
151 mach_port_t object_name;
152
153 kr = mach_vm_region(task, &addr, &size, VM_REGION_TOP_INFO, (vm_region_info_t)&info, &count, &object_name);
154 if (kr != KERN_SUCCESS) {
155 break;
156 }
157
158#if defined (__arm64__)
159 if (in_shared_region(addr, CPU_TYPE_ARM64)) {
160#else
161 if (in_shared_region(addr, CPU_TYPE_ARM)) {
162#endif
163 // Private Shared
164 fw_private += info.private_pages_resident * pagesize;
165
166 /*
167 * Check if this process has the globally shared
168 * text and data regions mapped in. If so, set
169 * split to TRUE and avoid checking
170 * again.
171 */
172 if (split == FALSE && info.share_mode == SM_EMPTY) {
173 vm_region_basic_info_data_64_t b_info;
174 mach_vm_address_t b_addr = addr;
175 mach_vm_size_t b_size = size;
176 count = VM_REGION_BASIC_INFO_COUNT_64;
177
178 kr = mach_vm_region(task, &b_addr, &b_size, VM_REGION_BASIC_INFO_64, (vm_region_info_t)&b_info, &count, &object_name);
179 if (kr != KERN_SUCCESS) {
180 break;
181 }
182
183 if (b_info.reserved) {
184 split = TRUE;
185 }
186 }
187
188 /*
189 * Short circuit the loop if this isn't a shared
190 * private region, since that's the only region
191 * type we care about within the current address
192 * range.
193 */
194 if (info.share_mode != SM_PRIVATE) {
195 continue;
196 }
197 }
d9a64523 198
cb323159
A
199 regs++;
200
201 /*
202 * Update counters according to the region type.
203 */
204
205 if (info.share_mode == SM_COW && info.ref_count == 1) {
206 // Treat single reference SM_COW as SM_PRIVATE
207 info.share_mode = SM_PRIVATE;
208 }
209
210 switch (info.share_mode) {
211 case SM_LARGE_PAGE:
212 // Treat SM_LARGE_PAGE the same as SM_PRIVATE
213 // since they are not shareable and are wired.
214 case SM_PRIVATE:
215 rprvt += info.private_pages_resident * pagesize;
216 rprvt += info.shared_pages_resident * pagesize;
217 break;
218
219 case SM_EMPTY:
220 empty += size;
221 break;
222
223 case SM_COW:
224 case SM_SHARED:
225 if (pid == 0) {
226 // Treat kernel_task specially
227 if (info.share_mode == SM_COW) {
228 rprvt += info.private_pages_resident * pagesize;
229 }
230 break;
231 }
232
233 if (info.share_mode == SM_COW) {
234 rprvt += info.private_pages_resident * pagesize;
235 }
236 break;
237
238 default:
239 assert(0);
240 break;
241 }
242 }
243
244 return rprvt;
245}
d9a64523 246
0a7de745
A
247void
248move_to_idle_band(void)
249{
d9a64523
A
250 memorystatus_priority_properties_t props;
251 /*
252 * Freezing a process also moves it to an elevated jetsam band in order to protect it from idle exits.
253 * So we move the child process to the idle band to mirror the typical 'idle app being frozen' scenario.
254 */
255 props.priority = JETSAM_PRIORITY_IDLE;
256 props.user_data = 0;
257
258 /*
259 * This requires us to run as root (in the absence of entitlement).
260 * Hence the T_META_ASROOT(true) in the T_HELPER_DECL.
261 */
262 if (memorystatus_control(MEMORYSTATUS_CMD_SET_PRIORITY_PROPERTIES, getpid(), 0, &props, sizeof(props))) {
263 exit(MEMSTAT_PRIORITY_CHANGE_FAILED);
264 }
265}
266
0a7de745
A
267void
268freeze_helper_process(void)
269{
270 size_t length;
271 int ret, freeze_enabled, errno_freeze_sysctl;
cb323159
A
272 uint64_t resident_memory_before, resident_memory_after, vmpage_size;
273 vmpage_size = (uint64_t) get_vmpage_size();
274 resident_memory_before = get_rprvt(child_pid) / vmpage_size;
d9a64523 275
cb323159
A
276 T_LOG("Freezing child pid %d", child_pid);
277 ret = sysctlbyname("kern.memorystatus_freeze", NULL, NULL, &child_pid, sizeof(child_pid));
0a7de745 278 errno_freeze_sysctl = errno;
d9a64523
A
279 sleep(1);
280
0a7de745
A
281 /*
282 * The child process toggles its freezable state on each iteration.
283 * So a failure for every alternate freeze is expected.
284 */
cb323159 285 if (freeze_count % 2) {
0a7de745
A
286 length = sizeof(freeze_enabled);
287 T_QUIET; T_ASSERT_POSIX_SUCCESS(sysctlbyname("vm.freeze_enabled", &freeze_enabled, &length, NULL, 0),
288 "failed to query vm.freeze_enabled");
289 if (freeze_enabled) {
290 errno = errno_freeze_sysctl;
291 T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "sysctl kern.memorystatus_freeze failed");
292 } else {
293 /* If freezer is disabled, skip the test. This can happen due to disk space shortage. */
294 T_LOG("Freeze has been disabled. Terminating early.");
295 T_END;
296 }
cb323159
A
297 resident_memory_after = get_rprvt(child_pid) / vmpage_size;
298 uint64_t freeze_pages_max = (uint64_t) sysctl_freeze_pages_max(NULL);
299 T_QUIET; T_ASSERT_LT(resident_memory_after, resident_memory_before, "Freeze didn't reduce resident memory set");
300 if (resident_memory_before > freeze_pages_max) {
301 T_QUIET; T_ASSERT_LE(resident_memory_before - resident_memory_after, freeze_pages_max, "Freeze pages froze more than the threshold.");
302 }
303 ret = sysctlbyname("kern.memorystatus_thaw", NULL, NULL, &child_pid, sizeof(child_pid));
d9a64523
A
304 T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "sysctl kern.memorystatus_thaw failed");
305 } else {
306 T_QUIET; T_ASSERT_TRUE(ret != KERN_SUCCESS, "Freeze should have failed");
307 T_LOG("Freeze failed as expected");
308 }
309
310 freeze_count++;
311
cb323159 312 T_QUIET; T_ASSERT_POSIX_SUCCESS(kill(child_pid, SIGUSR1), "failed to send SIGUSR1 to child process");
d9a64523
A
313}
314
0a7de745 315void
cb323159 316run_freezer_test(int num_pages)
0a7de745
A
317{
318 int ret, freeze_enabled;
d9a64523
A
319 char sz_str[50];
320 char **launch_tool_args;
321 char testpath[PATH_MAX];
322 uint32_t testpath_buf_size;
323 dispatch_source_t ds_freeze, ds_proc;
0a7de745
A
324 size_t length;
325
326 length = sizeof(freeze_enabled);
327 T_QUIET; T_ASSERT_POSIX_SUCCESS(sysctlbyname("vm.freeze_enabled", &freeze_enabled, &length, NULL, 0),
328 "failed to query vm.freeze_enabled");
329 if (!freeze_enabled) {
330 /* If freezer is disabled, skip the test. This can happen due to disk space shortage. */
331 T_SKIP("Freeze has been disabled. Skipping test.");
332 }
d9a64523
A
333
334 signal(SIGUSR1, SIG_IGN);
335 ds_freeze = dispatch_source_create(DISPATCH_SOURCE_TYPE_SIGNAL, SIGUSR1, 0, dispatch_get_main_queue());
336 T_QUIET; T_ASSERT_NOTNULL(ds_freeze, "dispatch_source_create (ds_freeze)");
337
338 dispatch_source_set_event_handler(ds_freeze, ^{
339 if (freeze_count < NUM_ITERATIONS) {
0a7de745 340 freeze_helper_process();
d9a64523 341 } else {
cb323159 342 kill(child_pid, SIGKILL);
0a7de745 343 dispatch_source_cancel(ds_freeze);
d9a64523
A
344 }
345 });
346 dispatch_activate(ds_freeze);
347
348 testpath_buf_size = sizeof(testpath);
349 ret = _NSGetExecutablePath(testpath, &testpath_buf_size);
350 T_QUIET; T_ASSERT_POSIX_ZERO(ret, "_NSGetExecutablePath");
351 T_LOG("Executable path: %s", testpath);
352
cb323159 353 sprintf(sz_str, "%d", num_pages);
d9a64523
A
354 launch_tool_args = (char *[]){
355 testpath,
356 "-n",
357 "allocate_pages",
358 "--",
359 sz_str,
360 NULL
361 };
362
363 /* Spawn the child process. Suspend after launch until the exit proc handler has been set up. */
cb323159 364 ret = dt_launch_tool(&child_pid, launch_tool_args, true, NULL, NULL);
d9a64523
A
365 if (ret != 0) {
366 T_LOG("dt_launch tool returned %d with error code %d", ret, errno);
367 }
cb323159 368 T_QUIET; T_ASSERT_POSIX_SUCCESS(child_pid, "dt_launch_tool");
d9a64523 369
cb323159 370 ds_proc = dispatch_source_create(DISPATCH_SOURCE_TYPE_PROC, (uintptr_t)child_pid, DISPATCH_PROC_EXIT, dispatch_get_main_queue());
d9a64523
A
371 T_QUIET; T_ASSERT_NOTNULL(ds_proc, "dispatch_source_create (ds_proc)");
372
373 dispatch_source_set_event_handler(ds_proc, ^{
374 int status = 0, code = 0;
cb323159
A
375 pid_t rc = waitpid(child_pid, &status, 0);
376 T_QUIET; T_ASSERT_EQ(rc, child_pid, "waitpid");
d9a64523
A
377 code = WEXITSTATUS(status);
378
379 if (code == 0) {
0a7de745 380 T_END;
d9a64523 381 } else if (code > 0 && code < EXIT_CODE_MAX) {
0a7de745 382 T_ASSERT_FAIL("Child exited with %s", exit_codes_str[code]);
d9a64523 383 } else {
0a7de745 384 T_ASSERT_FAIL("Child exited with unknown exit code %d", code);
d9a64523
A
385 }
386 });
387 dispatch_activate(ds_proc);
388
cb323159 389 T_QUIET; T_ASSERT_POSIX_SUCCESS(kill(child_pid, SIGCONT), "failed to send SIGCONT to child process");
d9a64523
A
390 dispatch_main();
391}
392
cb323159
A
393static void
394allocate_pages(int num_pages)
395{
396 int i, j, vmpgsize;
d9a64523 397 char val;
cb323159 398 __block int num_iter = 0;
d9a64523
A
399 __block char **buf;
400 dispatch_source_t ds_signal;
cb323159
A
401 vmpgsize = get_vmpage_size();
402 if (num_pages < 1) {
403 printf("Invalid number of pages to allocate: %d\n", num_pages);
404 exit(INVALID_ALLOCATE_PAGES_ARGUMENTS);
d9a64523
A
405 }
406
d9a64523
A
407 buf = (char**)malloc(sizeof(char*) * (size_t)num_pages);
408
409 /* Gives us the compression ratio we see in the typical case (~2.7) */
410 for (j = 0; j < num_pages; j++) {
411 buf[j] = (char*)malloc((size_t)vmpgsize * sizeof(char));
412 val = 0;
413 for (i = 0; i < vmpgsize; i += 16) {
414 memset(&buf[j][i], val, 16);
415 if (i < 3400 * (vmpgsize / 4096)) {
416 val++;
417 }
418 }
419 }
420
421 dispatch_after(dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC), dispatch_get_main_queue(), ^{
422 /* Signal to the parent that we're done allocating and it's ok to freeze us */
0a7de745 423 printf("[%d] Sending initial signal to parent to begin freezing\n", getpid());
d9a64523 424 if (kill(getppid(), SIGUSR1) != 0) {
0a7de745 425 exit(INITIAL_SIGNAL_TO_PARENT_FAILED);
d9a64523
A
426 }
427 });
428
429 signal(SIGUSR1, SIG_IGN);
430 ds_signal = dispatch_source_create(DISPATCH_SOURCE_TYPE_SIGNAL, SIGUSR1, 0, dispatch_get_main_queue());
431 if (ds_signal == NULL) {
432 exit(DISPATCH_SOURCE_CREATE_FAILED);
433 }
434
435 dispatch_source_set_event_handler(ds_signal, ^{
436 int current_state, new_state;
437 volatile int tmp;
438
439 /* Make sure all the pages are accessed before trying to freeze again */
440 for (int x = 0; x < num_pages; x++) {
0a7de745 441 tmp = buf[x][0];
d9a64523
A
442 }
443
444 current_state = memorystatus_control(MEMORYSTATUS_CMD_GET_PROCESS_IS_FREEZABLE, getpid(), 0, NULL, 0);
cb323159
A
445 /* Sysprocs start off as unfreezable. Verify that first. */
446 if (num_iter == 0 && current_state != 0) {
447 exit(IS_FREEZABLE_NOT_AS_EXPECTED);
448 }
d9a64523
A
449
450 /* Toggle freezable state */
451 new_state = (current_state) ? 0: 1;
0a7de745
A
452 printf("[%d] Changing state from %s to %s\n", getpid(),
453 (current_state) ? "freezable": "unfreezable", (new_state) ? "freezable": "unfreezable");
d9a64523 454 if (memorystatus_control(MEMORYSTATUS_CMD_SET_PROCESS_IS_FREEZABLE, getpid(), (uint32_t)new_state, NULL, 0) != KERN_SUCCESS) {
0a7de745 455 exit(MEMORYSTATUS_CONTROL_FAILED);
d9a64523
A
456 }
457
458 /* Verify that the state has been set correctly */
459 current_state = memorystatus_control(MEMORYSTATUS_CMD_GET_PROCESS_IS_FREEZABLE, getpid(), 0, NULL, 0);
460 if (new_state != current_state) {
0a7de745 461 exit(IS_FREEZABLE_NOT_AS_EXPECTED);
d9a64523
A
462 }
463 num_iter++;
464
465 if (kill(getppid(), SIGUSR1) != 0) {
0a7de745 466 exit(SIGNAL_TO_PARENT_FAILED);
d9a64523
A
467 }
468 });
469 dispatch_activate(ds_signal);
470 move_to_idle_band();
471
472 dispatch_main();
473}
474
cb323159
A
475T_HELPER_DECL(allocate_pages,
476 "allocates pages to freeze",
477 T_META_ASROOT(true)) {
478 if (argc < 1) {
479 exit(TOO_FEW_ARGUMENTS);
480 }
481
482 int num_pages = atoi(argv[0]);
483 allocate_pages(num_pages);
484}
485
486T_DECL(freeze, "VM freezer test", T_META_ASROOT(true)) {
487 run_freezer_test(
488 (MEM_SIZE_MB << 20) / get_vmpage_size());
489}
490
491static int old_freeze_pages_max = 0;
492static void
493reset_freeze_pages_max()
494{
495 if (old_freeze_pages_max != 0) {
496 sysctl_freeze_pages_max(&old_freeze_pages_max);
497 }
498}
499
500int
501sysctl_freeze_pages_max(int* new_value)
502{
503 static int set_end_handler = false;
504 int freeze_pages_max, ret;
505 size_t size = sizeof(freeze_pages_max);
506 ret = sysctlbyname("kern.memorystatus_freeze_pages_max", &freeze_pages_max, &size, new_value, size);
507 T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "Unable to query kern.memorystatus_freeze_pages_max");
508 if (!set_end_handler) {
509 // Save the original value and instruct darwintest to restore it after the test completes
510 old_freeze_pages_max = freeze_pages_max;
511 T_ATEND(reset_freeze_pages_max);
512 set_end_handler = true;
513 }
514 return old_freeze_pages_max;
515}
516
517T_DECL(freeze_over_max_threshold, "Max Freeze Threshold is Enforced", T_META_ASROOT(true)) {
518 int freeze_pages_max = FREEZE_PAGES_MAX;
519 sysctl_freeze_pages_max(&freeze_pages_max);
520 run_freezer_test(FREEZE_PAGES_MAX * 2);
521}
522
523T_HELPER_DECL(frozen_background, "Frozen background process", T_META_ASROOT(true)) {
524 kern_return_t kern_ret;
525 /* Set the process to freezable */
526 kern_ret = memorystatus_control(MEMORYSTATUS_CMD_SET_PROCESS_IS_FREEZABLE, getpid(), 1, NULL, 0);
527 T_QUIET; T_ASSERT_EQ(kern_ret, KERN_SUCCESS, "set process is freezable");
528 /* Signal to our parent that we can be frozen */
529 if (kill(getppid(), SIGUSR1) != 0) {
530 T_LOG("Unable to signal to parent process!");
531 exit(1);
532 }
533 while (1) {
534 ;
535 }
536}
537
538/* Launches the frozen_background helper as a managed process. */
539static pid_t
540launch_frozen_background_process()
541{
542 pid_t pid;
543 char **launch_tool_args;
544 char testpath[PATH_MAX];
545 uint32_t testpath_buf_size;
546 int ret;
547
548 testpath_buf_size = sizeof(testpath);
549 ret = _NSGetExecutablePath(testpath, &testpath_buf_size);
550 printf("Launching %s\n", testpath);
551 launch_tool_args = (char *[]){
552 testpath,
553 "-n",
554 "frozen_background",
555 NULL
556 };
557 ret = dt_launch_tool(&pid, launch_tool_args, false, NULL, NULL);
558 if (ret != 0) {
559 T_LOG("dt_launch tool returned %d with error code %d", ret, errno);
560 }
561 T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "dt_launch_tool");
562 /* Set the process's managed bit, so that the kernel treats this process like an app instead of a sysproc. */
563 ret = memorystatus_control(MEMORYSTATUS_CMD_SET_PROCESS_IS_MANAGED, pid, 1, NULL, 0);
564 T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "memorystatus_control");
565 return pid;
566}
567
568static void
569freeze_process(pid_t pid)
570{
571 int ret, freeze_enabled, errno_freeze_sysctl;
572 size_t length;
573 T_LOG("Freezing pid %d", pid);
574
575 ret = sysctlbyname("kern.memorystatus_freeze", NULL, NULL, &pid, sizeof(pid));
576 errno_freeze_sysctl = errno;
577 length = sizeof(freeze_enabled);
578 T_QUIET; T_ASSERT_POSIX_SUCCESS(sysctlbyname("vm.freeze_enabled", &freeze_enabled, &length, NULL, 0),
579 "failed to query vm.freeze_enabled");
580 if (freeze_enabled) {
581 errno = errno_freeze_sysctl;
582 T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "sysctl kern.memorystatus_freeze failed");
583 } else {
584 /* If freezer is disabled, skip the test. This can happen due to disk space shortage. */
585 T_LOG("Freeze has been disabled. Terminating early.");
586 T_END;
587 }
588}
589
590static void
591memorystatus_assertion_test_demote_frozen()
592{
593#if !CONFIG_EMBEDDED
594 T_SKIP("Freezing processes is only supported on embedded");
595#endif
596 /*
597 * 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.
598 * Then remove thee assertion, and ensure it gets demoted properly.
599 */
600 /* these values will remain fixed during testing */
601 int active_limit_mb = 15; /* arbitrary */
602 int inactive_limit_mb = 7; /* arbitrary */
ea3f0419 603 int demote_value = 1;
cb323159
A
604 /* Launch the child process, and elevate its priority */
605 int requestedpriority;
606 dispatch_source_t ds_signal, ds_exit;
607 requestedpriority = JETSAM_PRIORITY_UI_SUPPORT;
608
609 /* Wait for the child process to tell us that it's ready, and then freeze it */
610 signal(SIGUSR1, SIG_IGN);
611 ds_signal = dispatch_source_create(DISPATCH_SOURCE_TYPE_SIGNAL, SIGUSR1, 0, dispatch_get_main_queue());
612 T_QUIET; T_ASSERT_NOTNULL(ds_signal, "dispatch_source_create");
613 dispatch_source_set_event_handler(ds_signal, ^{
614 int sysctl_ret;
615 /* Freeze the process, trigger agressive demotion, and check that it hasn't been demoted. */
616 freeze_process(child_pid);
617 /* Agressive demotion */
ea3f0419
A
618 sysctl_ret = sysctlbyname("kern.memorystatus_demote_frozen_processes", NULL, NULL, &demote_value, sizeof(demote_value));
619 T_QUIET; T_ASSERT_POSIX_SUCCESS(sysctl_ret, "sysctl kern.memorystatus_demote_frozen_processes succeeded");
cb323159
A
620 /* Check */
621 (void)check_properties(child_pid, requestedpriority, inactive_limit_mb, 0x0, ASSERTION_STATE_IS_SET, "Priority was set");
622 T_LOG("Relinquishing our assertion.");
623 /* Relinquish our assertion, and check that it gets demoted. */
624 relinquish_assertion_priority(child_pid, 0x0);
625 (void)check_properties(child_pid, JETSAM_PRIORITY_AGING_BAND2, inactive_limit_mb, 0x0, ASSERTION_STATE_IS_RELINQUISHED, "Assertion was reqlinquished.");
626 /* Kill the child */
ea3f0419 627 T_QUIET; T_ASSERT_POSIX_SUCCESS(kill(child_pid, SIGKILL), "Killed child process");
cb323159
A
628 T_END;
629 });
630
631 /* Launch the child process and set the initial properties on it. */
632 child_pid = launch_frozen_background_process();
633 set_memlimits(child_pid, active_limit_mb, inactive_limit_mb, false, false);
634 set_assertion_priority(child_pid, requestedpriority, 0x0);
635 (void)check_properties(child_pid, requestedpriority, inactive_limit_mb, 0x0, ASSERTION_STATE_IS_SET, "Priority was set");
636 /* Listen for exit. */
637 ds_exit = dispatch_source_create(DISPATCH_SOURCE_TYPE_PROC, (uintptr_t)child_pid, DISPATCH_PROC_EXIT, dispatch_get_main_queue());
638 dispatch_source_set_event_handler(ds_exit, ^{
639 int status = 0, code = 0;
640 pid_t rc = waitpid(child_pid, &status, 0);
641 T_QUIET; T_ASSERT_EQ(rc, child_pid, "waitpid");
642 code = WEXITSTATUS(status);
643 T_QUIET; T_ASSERT_EQ(code, 0, "Child exited cleanly");
644 T_END;
645 });
646
647 dispatch_activate(ds_exit);
648 dispatch_activate(ds_signal);
649 dispatch_main();
650}
651
652T_DECL(assertion_test_demote_frozen, "demoted frozen process goes to asserted priority.", T_META_ASROOT(true)) {
653 memorystatus_assertion_test_demote_frozen();
d9a64523 654}
ea3f0419
A
655
656T_DECL(budget_replenishment, "budget replenishes properly") {
657 size_t length;
658 int ret;
659 static unsigned int kTestIntervalSecs = 60 * 60 * 32; // 32 Hours
660 unsigned int memorystatus_freeze_daily_mb_max, memorystatus_freeze_daily_pages_max;
661 static unsigned int kFixedPointFactor = 100;
662 static unsigned int kNumSecondsInDay = 60 * 60 * 24;
663 unsigned int new_budget, expected_new_budget_pages;
664 size_t new_budget_ln;
665 unsigned int page_size = (unsigned int) get_vmpage_size();
666
667 /*
668 * Calculate a new budget as if the previous interval expired kTestIntervalSecs
669 * ago and we used up its entire budget.
670 */
671 length = sizeof(kTestIntervalSecs);
672 new_budget_ln = sizeof(new_budget);
673 ret = sysctlbyname("vm.memorystatus_freeze_calculate_new_budget", &new_budget, &new_budget_ln, &kTestIntervalSecs, length);
bca245ac 674 T_ASSERT_POSIX_SUCCESS(ret, "vm.memorystatus_freeze_calculate_new_budget");
ea3f0419
A
675
676 // Grab the daily budget.
677 length = sizeof(memorystatus_freeze_daily_mb_max);
678 ret = sysctlbyname("kern.memorystatus_freeze_daily_mb_max", &memorystatus_freeze_daily_mb_max, &length, NULL, 0);
bca245ac 679 T_ASSERT_POSIX_SUCCESS(ret, "kern.memorystatus_freeze_daily_mb_max");
ea3f0419 680
bca245ac
A
681 memorystatus_freeze_daily_pages_max = memorystatus_freeze_daily_mb_max * 1024UL * 1024UL / page_size;
682 T_LOG("memorystatus_freeze_daily_mb_max %u", memorystatus_freeze_daily_mb_max);
683 T_LOG("memorystatus_freeze_daily_pages_max %u", memorystatus_freeze_daily_pages_max);
684 T_LOG("page_size %u", page_size);
ea3f0419
A
685
686 /*
687 * We're kTestIntervalSecs past a new interval. Which means we are owed kNumSecondsInDay
688 * seconds of budget.
689 */
690 expected_new_budget_pages = memorystatus_freeze_daily_pages_max;
bca245ac
A
691 T_LOG("expected_new_budget_pages before %u", expected_new_budget_pages);
692 T_ASSERT_EQ(kTestIntervalSecs, 60 * 60 * 32, "kTestIntervalSecs did not change");
ea3f0419
A
693 expected_new_budget_pages += ((kTestIntervalSecs * kFixedPointFactor) / (kNumSecondsInDay)
694 * memorystatus_freeze_daily_pages_max) / kFixedPointFactor;
bca245ac
A
695 T_LOG("expected_new_budget_pages after %u", expected_new_budget_pages);
696 T_LOG("memorystatus_freeze_daily_pages_max after %u", memorystatus_freeze_daily_pages_max);
ea3f0419
A
697
698 T_QUIET; T_ASSERT_EQ(new_budget, expected_new_budget_pages, "Calculate new budget behaves correctly.");
699}