]> git.saurik.com Git - apple/xnu.git/blob - tools/tests/memorystatus/memorystatus.c
xnu-2422.115.4.tar.gz
[apple/xnu.git] / tools / tests / memorystatus / memorystatus.c
1 #include <asl.h>
2 #include <assert.h>
3 #include <fcntl.h>
4 #include <pthread.h>
5 #include <signal.h>
6 #include <stdio.h>
7 #include <stdlib.h>
8 #include <string.h>
9 #include <time.h>
10 #include <unistd.h>
11
12 #include <libproc.h>
13
14 #include <mach/mach.h>
15 #include <mach/mach_types.h>
16 #include <mach/mach_vm.h>
17 #include <mach/shared_region.h>
18 #include <mach/task_info.h>
19 #include <mach/vm_map.h>
20
21 #include <sys/event.h>
22 #include <sys/ipc.h>
23 #include <sys/kern_memorystatus.h>
24 #include <sys/mman.h>
25 #include <sys/shm.h>
26 #include <sys/stat.h>
27 #include <sys/sysctl.h>
28 #include <sys/wait.h>
29
30 #include <xpc/xpc.h>
31 #include <xpc/private.h>
32
33 #include <CoreFoundation/CoreFoundation.h>
34
35 #include <Security/Security.h>
36 #include <ServiceManagement/ServiceManagement.h>
37 #include <ServiceManagement/SMErrors.h>
38
39 #include <Kernel/kern/ledger.h>
40
41 #include <sys/spawn_internal.h>
42 #include <spawn_private.h>
43
44 #define CR_JOB "com.apple.ReportCrash.Jetsam"
45 #define CR_JOB_PLIST_PATH "/System/Library/LaunchDaemons/com.apple.ReportCrash.Jetsam.plist"
46
47 #define ERR_BUF_LEN 1024
48
49 #ifndef VM_PAGE_SIZE
50 #define VM_PAGE_SIZE 4096
51 #endif
52
53 /*
54 * TODO: import header (currently vm_pageout.h) without pulling in extraneous definitions;
55 * see <rdar://problem/13374916>.
56 */
57 #ifndef VM_PAGER_FREEZER_DEFAULT
58 #define VM_PAGER_FREEZER_DEFAULT 0x8 /* Freezer backed by default pager.*/
59 #endif
60
61 /*
62 * Special note to ourselves: the jetsam cause to look out for is *either*
63 * a high watermark kill, *or* a per-process kill.
64 */
65 #define CAUSE_HIWAT_OR_PERPROC -1
66
67 typedef enum jetsam_test {
68 kSimpleJetsamTest = 1,
69 kPressureJetsamTestFG,
70 kPressureJetsamTestBG,
71 kHighwaterJetsamTest,
72 kVnodeJetsamTest,
73 kBackgroundJetsamTest
74 } jetsam_test_t;
75
76 typedef enum idle_exit_test {
77 kDeferTimeoutCleanTest = 1,
78 kDeferTimeoutDirtyTest,
79 kCancelTimeoutCleanTest,
80 kCancelTimeoutDirtyTest
81 } idle_exit_test_t;
82
83 typedef struct shared_mem_t {
84 pthread_mutex_t mutex;
85 pthread_cond_t cv;
86 boolean_t completed;
87 boolean_t pressure_event_fired;
88 } shared_mem_t;
89
90 shared_mem_t *g_shared = NULL;
91 unsigned long g_physmem = 0;
92 int g_ledger_count = -1, g_footprint_index = -1;
93 int64_t g_per_process_limit = -1;
94
95 #if TARGET_OS_EMBEDDED
96 static boolean_t set_priority(pid_t pid, int32_t priority, uint64_t user_data);
97 #endif
98
99 extern int ledger(int cmd, caddr_t arg1, caddr_t arg2, caddr_t arg3);
100 static boolean_t check_properties(pid_t pid, int32_t requested_priority, int32_t requested_limit_mb, uint64_t requested_user_data, const char *test);
101
102 /* Utilities. */
103
104 static void
105 printTestHeader(pid_t testPid, const char *testName, ...)
106 {
107 va_list va;
108 printf("========================================\n");
109 printf("[TEST] ");
110 va_start(va, testName);
111 vprintf(testName, va);
112 va_end(va);
113 printf("\n");
114 printf("[PID] %d\n", testPid);
115 printf("========================================\n");
116 printf("[BEGIN]\n");
117 }
118
119 static void
120 printTestResult(const char *testName, boolean_t didPass, const char *msg, ...)
121 {
122 if (msg != NULL) {
123 va_list va;
124 printf("\t\t");
125 va_start(va, msg);
126 vprintf(msg, va);
127 va_end(va);
128 printf("\n");
129 }
130 if (didPass) {
131 printf("[PASS]\t%s\n\n", testName);
132 } else {
133 printf("[FAIL]\t%s\n\n", testName);
134 }
135 }
136
137 static CFDictionaryRef create_dictionary_from_plist(const char *path) {
138 void *bytes = NULL;
139 CFDataRef data = NULL;
140 CFDictionaryRef options = NULL;
141 size_t bufLen;
142 int fd = open(path, O_RDONLY, 0);
143 if (fd == -1) {
144 goto exit;
145 }
146 struct stat sb;
147 if (fstat(fd, &sb) == -1) {
148 goto exit;
149 }
150
151 bufLen = (size_t)sb.st_size;
152 bytes = malloc(bufLen);
153 if (bytes == NULL) {
154 goto exit;
155 }
156
157 if (read(fd, bytes, bufLen) != bufLen) {
158 goto exit;
159 }
160
161 data = CFDataCreateWithBytesNoCopy(kCFAllocatorDefault, (const UInt8 *) bytes, bufLen, kCFAllocatorNull);
162 if (data == NULL) {
163 goto exit;
164 }
165
166 options = (CFDictionaryRef) CFPropertyListCreateFromXMLData(kCFAllocatorDefault, data, kCFPropertyListImmutable, NULL);
167 if (options == NULL) {
168 }
169
170 exit:
171 if (data != NULL) {
172 CFRelease(data);
173 }
174 if (bytes != NULL) {
175 free(bytes);
176 }
177 if (fd != -1) {
178 close(fd);
179 }
180
181 return options;
182 }
183
184 #if TARGET_OS_EMBEDDED
185
186 static void disable_crashreporter(void) {
187 if (!SMJobRemove(kSMDomainSystemLaunchd, CFSTR(CR_JOB), NULL, true, NULL)) {
188 printf ("\t\tCould not unload %s\n", CR_JOB);
189 }
190 }
191
192 static void enable_crashreporter(void) {
193 CFDictionaryRef job_dict;
194
195 job_dict = create_dictionary_from_plist(CR_JOB_PLIST_PATH);
196 if (!job_dict) {
197 printf("\t\tCould not create dictionary from %s\n", CR_JOB_PLIST_PATH);
198 }
199
200 if (!SMJobSubmit(kSMDomainSystemLaunchd, job_dict, NULL, NULL)) {
201 printf ("\t\tCould not submit %s\n", CR_JOB);
202 }
203
204 CFRelease(job_dict);
205 }
206
207 static boolean_t verify_snapshot(pid_t pid, int32_t priority, uint32_t kill_cause, uint64_t user_data, bool expecting_snapshot) {
208 int size;
209 memorystatus_jetsam_snapshot_t *snapshot = NULL;
210 int i;
211 boolean_t res = false;
212
213 if (kill_cause == CAUSE_HIWAT_OR_PERPROC) {
214 kill_cause = kMemorystatusKilledHiwat|kMemorystatusKilledVMPageShortage;
215 }
216
217 size = memorystatus_control(MEMORYSTATUS_CMD_GET_JETSAM_SNAPSHOT, 0, 0, NULL, 0);
218 if (size <= 0) {
219 if (expecting_snapshot) {
220 printf("\t\tCan't get snapshot size: %d!\n", size);
221 }
222 goto exit;
223 }
224
225 snapshot = (memorystatus_jetsam_snapshot_t*)malloc(size);
226 if (!snapshot) {
227 printf("\t\tCan't allocate snapshot!\n");
228 goto exit;
229 }
230
231 size = memorystatus_control(MEMORYSTATUS_CMD_GET_JETSAM_SNAPSHOT, 0, 0, snapshot, size);
232 if (size <= 0) {
233 printf("\t\tCan't retrieve snapshot (%d)!\n", size);
234 goto exit;
235 }
236
237 if (((size - sizeof(memorystatus_jetsam_snapshot_t)) / sizeof(memorystatus_jetsam_snapshot_entry_t)) != snapshot->entry_count) {
238 printf("\t\tMalformed snapshot: %d! Expected %ld + %zd x %ld = %ld\n", size,
239 sizeof(memorystatus_jetsam_snapshot_t), snapshot->entry_count, sizeof(memorystatus_jetsam_snapshot_entry_t),
240 sizeof(memorystatus_jetsam_snapshot_t) + (snapshot->entry_count * sizeof(memorystatus_jetsam_snapshot_entry_t)));
241 goto exit;
242 }
243
244 if (pid == -1) {
245 /* Just flushing the buffer */
246 res = true;
247 goto exit;
248 }
249
250 /* Locate */
251 for (i = 0; i < snapshot->entry_count; i++) {
252 if (snapshot->entries[i].pid == pid) {
253 res = 0;
254 if ((priority == snapshot->entries[i].priority) && ((kill_cause | snapshot->entries[i].killed) == kill_cause) && (user_data == snapshot->entries[i].user_data)) {
255 res = true;
256 } else {
257 printf("\t\tMismatched snapshot properties for pid %d (expected/actual): priority %d/%d : kill cause 0x%x/0x%x : user data 0x%llx/0x%llx\n",
258 pid, priority, snapshot->entries[i].priority, kill_cause, snapshot->entries[i].killed, user_data, snapshot->entries[i].user_data);
259 }
260 goto exit;
261 }
262 }
263
264 exit:
265 free(snapshot);
266
267 return res;
268 }
269
270 #endif /* TARGET_OS_EMBEDDED */
271
272 static void cleanup_and_exit(int status) {
273 #if TARGET_OS_EMBEDDED
274 /* Cleanup */
275 enable_crashreporter();
276 #endif
277
278 /* Exit. Pretty literal. */
279 exit(status);
280 }
281
282 static void child_ready() {
283 pthread_mutex_lock(&g_shared->mutex);
284 pthread_cond_signal(&g_shared->cv);
285 pthread_mutex_unlock(&g_shared->mutex);
286 }
287
288 static pid_t init_and_fork() {
289 int pid;
290
291 g_shared->completed = 0;
292 g_shared->pressure_event_fired = 0;
293
294 pthread_mutex_lock(&g_shared->mutex);
295
296 pid = fork();
297 if (pid == 0) {
298 return 0;
299 } else if (pid == -1) {
300 printTestResult(__func__, false, "Fork error!\n");
301 cleanup_and_exit(-1);
302 }
303
304 /* Wait for child's signal */
305 pthread_cond_wait(&g_shared->cv, &g_shared->mutex);
306 pthread_mutex_unlock(&g_shared->mutex);
307 return (pid_t)pid;
308 }
309
310 static memorystatus_priority_entry_t *get_priority_list(int *size) {
311 memorystatus_priority_entry_t *list = NULL;
312
313 assert(size);
314
315 *size = memorystatus_control(MEMORYSTATUS_CMD_GET_PRIORITY_LIST, 0, 0, NULL, 0);
316 if (*size <= 0) {
317 printf("\t\tCan't get list size: %d!\n", *size);
318 goto exit;
319 }
320
321 list = (memorystatus_priority_entry_t*)malloc(*size);
322 if (!list) {
323 printf("\t\tCan't allocate list!\n");
324 goto exit;
325 }
326
327 *size = memorystatus_control(MEMORYSTATUS_CMD_GET_PRIORITY_LIST, 0, 0, list, *size);
328 if (*size <= 0) {
329 printf("\t\tCan't retrieve list!\n");
330 goto exit;
331 }
332
333 exit:
334 return list;
335 }
336
337 /* Tests */
338
339 #if TARGET_OS_EMBEDDED
340
341 /* Spawn tests */
342
343 static void spawn_test() {
344 int page_delta = 32768; /* 128MB */
345 char *mem;
346 unsigned long total = 0;
347
348 /* Spin */
349 while (1) {
350 /* Priority will be shifted during this time... */
351 sleep(1);
352
353 /* ...then process will be backgrounded and hopefully killed by the memory limit */
354 while(1) {
355 int i;
356 mem = malloc(page_delta * VM_PAGE_SIZE);
357 if (!mem) {
358 fprintf(stderr, "Failed to allocate memory!\n");
359 while (1) {
360 sleep(1);
361 }
362 }
363
364 total += page_delta;
365 memset(mem, 0xFF, page_delta * VM_PAGE_SIZE);
366
367 set_priority(getpid(), JETSAM_PRIORITY_BACKGROUND, 0);
368
369 while(1) {
370 sleep(1);
371 }
372 }
373 }
374 }
375
376 #endif
377
378 static boolean_t get_ledger_info(pid_t pid, int64_t *balance_mb, int64_t *limit_mb) {
379 struct ledger_entry_info *lei;
380 uint64_t count;
381 boolean_t res = false;
382
383 lei = (struct ledger_entry_info *)malloc((size_t)(g_ledger_count * sizeof (*lei)));
384 if (lei) {
385 void *arg;
386
387 arg = (void *)(long)pid;
388 count = g_ledger_count;
389
390 if ((ledger(LEDGER_ENTRY_INFO, arg, (caddr_t)lei, (caddr_t)&count) >= 0) && (g_footprint_index < count)) {
391 if (balance_mb) {
392 *balance_mb = lei[g_footprint_index].lei_balance;
393 }
394 if (limit_mb) {
395 *limit_mb = lei[g_footprint_index].lei_limit;
396 }
397 res = true;
398 }
399
400 free(lei);
401 }
402
403 return res;
404 }
405
406 static boolean_t get_priority_props(pid_t pid, int32_t *priority, int32_t *limit_mb, uint64_t *user_data) {
407 int size;
408 memorystatus_priority_entry_t *entries = NULL;
409 int i;
410 boolean_t res = false;
411
412 entries = get_priority_list(&size);
413 if (!entries) {
414 goto exit;
415 }
416
417 /* Locate */
418 for (i = 0; i < size/sizeof(memorystatus_priority_entry_t); i++ ){
419 if (entries[i].pid == pid) {
420 int64_t limit;
421
422 *priority = entries[i].priority;
423 *user_data = entries[i].user_data;
424 #if 1
425 *limit_mb = entries[i].limit;
426 res = true;
427 #else
428 res = get_ledger_info(entries[i].pid, NULL, &limit);
429 if (false == res) {
430 printf("Failed to get highwater!\n");
431 }
432 /* The limit is retrieved in bytes, but set in MB, so rescale */
433 *limit_mb = (int32_t)(limit/(1024 * 1024));
434 #endif
435 goto exit;
436 }
437 }
438
439 printf("\t\tCan't find pid: %d!\n", pid);
440
441 exit:
442 free(entries);
443
444 return res;
445 }
446
447 static boolean_t check_properties(pid_t pid, int32_t requested_priority, int32_t requested_limit_mb, uint64_t requested_user_data, const char *test) {
448 const char *PROP_GET_ERROR_STRING = "failed to get properties";
449 const char *PROP_CHECK_ERROR_STRING = "property mismatch";
450
451 int32_t actual_priority, actual_hiwat;
452 uint64_t actual_user_data;
453
454 if (!get_priority_props(pid, &actual_priority, &actual_hiwat, &actual_user_data)) {
455 printf("\t\t%s test failed: %s\n", test, PROP_GET_ERROR_STRING);
456 return false;
457 }
458
459 /* -1 really means the default per-process limit, which varies per device */
460 if (requested_limit_mb <= 0) {
461 requested_limit_mb = g_per_process_limit;
462 }
463
464 if (actual_priority != requested_priority || actual_hiwat != requested_limit_mb || actual_user_data != requested_user_data) {
465 printf("\t\t%s test failed: %s\n", test, PROP_CHECK_ERROR_STRING);
466 printf("priority is %d, should be %d\n", actual_priority, requested_priority);
467 printf("hiwat is %d, should be %d\n", actual_hiwat, requested_limit_mb);
468 printf("user data is 0x%llx, should be 0x%llx\n", actual_user_data, requested_user_data);
469 return false;
470 }
471
472 printf("\t\t%s test ok...\n", test);
473
474 return true;
475 }
476
477 #if TARGET_OS_EMBEDDED
478
479 static void spin() {
480 child_ready();
481
482 /* Spin */
483 while (1) {
484 sleep(10);
485 }
486 }
487
488 /* Priority tests */
489
490 static boolean_t set_priority(pid_t pid, int32_t priority, uint64_t user_data) {
491 int ret;
492 memorystatus_priority_properties_t props;
493
494 props.priority = priority;
495 props.user_data = (uint32_t)user_data;
496
497 return memorystatus_control(MEMORYSTATUS_CMD_SET_PRIORITY_PROPERTIES, pid, 0, &props, sizeof(props));
498 }
499
500 static boolean_t set_memlimit(pid_t pid, int32_t limit_mb) {
501 return memorystatus_control(MEMORYSTATUS_CMD_SET_JETSAM_HIGH_WATER_MARK, pid, limit_mb, NULL, 0);
502 }
503
504 static boolean_t set_priority_properties(pid_t pid, int32_t priority, int32_t limit_mb, uint64_t user_data, const char *stage, boolean_t show_error) {
505 int ret;
506
507 ret = set_priority(pid, priority, user_data);
508 if (ret == 0) {
509 ret = set_memlimit(pid, limit_mb);
510 }
511
512 if (ret) {
513 if (show_error) {
514 printf("\t\t%s stage: failed to set properties!\n", stage);
515 }
516
517 return false;
518 }
519
520 return true;
521 }
522
523 static void start_priority_test() {
524 const char *DEFAULT_TEST_STR = "Default";
525 const char *INVALID_NEGATIVE_TEST_STR = "Invalid (Negative)";
526 const char *INVALID_POSITIVE_TEST_STR = "Invalid (Positive)";
527 const char *IDLE_ALIAS_TEST_STR = "Idle Alias";
528 const char *DEFERRED_TEST_STR = "Deferred";
529 const char *SUSPENDED_TEST_STR = "Suspended";
530 const char *FOREGROUND_TEST_STR = "Foreground";
531 const char *HIGHPRI_TEST_STR = "Highpri";
532
533 pid_t pid;
534 int status;
535 int success = false;
536
537 pid = init_and_fork();
538 if (pid == 0) {
539 spin();
540 } else {
541 printTestHeader(pid, "Priority test");
542 }
543
544 /* Check the default properties */
545 if (!check_properties(pid, JETSAM_PRIORITY_DEFAULT, -1, 0, DEFAULT_TEST_STR)) {
546 goto exit;
547 }
548
549 /* Check that setting a negative value (other than -1) leaves properties unchanged */
550 if (set_priority_properties(pid, -100, 0xABABABAB, 0, INVALID_NEGATIVE_TEST_STR, false) || !check_properties(pid, JETSAM_PRIORITY_DEFAULT, -1, 0, INVALID_NEGATIVE_TEST_STR)) {
551 goto exit;
552 }
553
554 /* Check that setting an out-of-range positive value leaves properties unchanged */
555 if (set_priority_properties(pid, 100, 0xCBCBCBCB, 0, INVALID_POSITIVE_TEST_STR, false) || !check_properties(pid, JETSAM_PRIORITY_DEFAULT, -1, 0, INVALID_POSITIVE_TEST_STR)) {
556 goto exit;
557 }
558
559 /* Idle-deferred - this should be adjusted down to idle */
560 if (!set_priority_properties(pid, JETSAM_PRIORITY_IDLE_DEFERRED, 0, 0xBEEF, DEFERRED_TEST_STR, true) || !check_properties(pid, JETSAM_PRIORITY_IDLE, 0, 0xBEEF, DEFERRED_TEST_STR)) {
561 goto exit;
562 }
563
564 /* Suspended */
565 if (!set_priority_properties(pid, JETSAM_PRIORITY_IDLE, 0, 0xCAFE, SUSPENDED_TEST_STR, true) || !check_properties(pid, JETSAM_PRIORITY_IDLE, 0, 0xCAFE, SUSPENDED_TEST_STR)) {
566 goto exit;
567 }
568
569 /* Foreground */
570 if (!set_priority_properties(pid, JETSAM_PRIORITY_FOREGROUND, 50, 0xBEEFF00D, FOREGROUND_TEST_STR, true) || !check_properties(pid, JETSAM_PRIORITY_FOREGROUND, 50, 0xBEEFF00D, FOREGROUND_TEST_STR)) {
571 goto exit;
572 }
573
574 /* Hipri */
575 if (!set_priority_properties(pid, JETSAM_PRIORITY_DEFAULT - 1, 0, 0x01234567, HIGHPRI_TEST_STR, true) || !check_properties(pid, JETSAM_PRIORITY_DEFAULT - 1, 0, 0x01234567, HIGHPRI_TEST_STR)) {
576 goto exit;
577 }
578
579 /* Foreground again (to test that the limit is restored) */
580 if (!set_priority_properties(pid, JETSAM_PRIORITY_FOREGROUND, 50, 0xBEEFF00D, FOREGROUND_TEST_STR, true) || !check_properties(pid, JETSAM_PRIORITY_FOREGROUND, 50, 0xBEEFF00D, FOREGROUND_TEST_STR)) {
581 goto exit;
582 }
583
584 /* Set foreground priority again; this would have caught <rdar://problem/13056007> */
585 if (!set_priority_properties(pid, JETSAM_PRIORITY_FOREGROUND, 50, 0xFEEDF00D, FOREGROUND_TEST_STR, true) || !check_properties(pid, JETSAM_PRIORITY_FOREGROUND, 50, 0xFEEDF00D, FOREGROUND_TEST_STR)) {
586 goto exit;
587 }
588
589 /* Set foreground priority again but pass a large memory limit; this would have caught <rdar://problem/13116445> */
590 if (!set_priority_properties(pid, JETSAM_PRIORITY_FOREGROUND, 4096, 0xBEEFF00D, FOREGROUND_TEST_STR, true) || !check_properties(pid, JETSAM_PRIORITY_FOREGROUND, 4096, 0xBEEFF00D, FOREGROUND_TEST_STR)) {
591 goto exit;
592 }
593
594 /* Check that -1 aliases to JETSAM_PRIORITY_DEFAULT */
595 if (!set_priority_properties(pid, -1, 0, 0xFADEF00D, IDLE_ALIAS_TEST_STR, true) || !check_properties(pid, JETSAM_PRIORITY_DEFAULT, 0, 0xFADEF00D, IDLE_ALIAS_TEST_STR)) {
596 goto exit;
597 }
598
599 success = true;
600
601 exit:
602
603 /* Done here... */
604 kill(pid, SIGKILL);
605
606 /* Wait for exit */
607 waitpid(pid, &status, 0);
608
609 printTestResult("Priority test", success, NULL);
610 }
611
612 /* Reordering */
613
614 static boolean_t check_reorder_priorities(pid_t pid1, pid_t pid2, int priority) {
615 int size;
616 memorystatus_priority_entry_t *entries = NULL;
617 int i;
618 boolean_t res = false;
619
620 entries = get_priority_list(&size);
621 if (!entries) {
622 goto exit;
623 }
624
625 /* Check relative priorities */
626 for (i = 0; i < size/sizeof(memorystatus_priority_entry_t); i++ ){
627 if (entries[i].pid == pid1) {
628 /* First process. The priority should match... */
629 if (entries[i].priority != priority) {
630 goto exit;
631 }
632
633 /* There should be one more daemon to follow... */
634 if ((i + 1) >= size) {
635 goto exit;
636 }
637
638 /* The next process should be pid2 */
639 if (entries[i + 1].pid != pid2) {
640 goto exit;
641 }
642
643 /* The priority should also match... */
644 if (entries[i + 1].priority != priority) {
645 goto exit;
646 }
647
648 break;
649 }
650 }
651
652 res = true;
653
654 exit:
655
656 return res;
657 }
658
659 static void start_fs_priority_test() {
660 const char *REORDER_TEST_STR = "Reorder";
661 const int test_priority = JETSAM_PRIORITY_FOREGROUND_SUPPORT;
662
663 pid_t pid1, pid2;
664 int status;
665 int success = false;
666
667 pid1 = init_and_fork();
668 if (pid1 == 0) {
669 spin();
670 }
671
672 pid2 = init_and_fork();
673 if (pid2 == 0) {
674 spin();
675 }
676
677 printTestHeader(pid1, "Reorder test");
678
679 /* pid2 should follow pid1 in the bucket */
680 if (!set_priority_properties(pid1, test_priority, 0, 0, REORDER_TEST_STR, true) || !set_priority_properties(pid2, test_priority, 0, 0, REORDER_TEST_STR, true)) {
681 printf("Cannot set priorities - #1!\n");
682 goto exit;
683 }
684
685 /* Check relative priorities */
686 if (!check_reorder_priorities(pid1, pid2, test_priority)) {
687 printf("Bad pid1 -> pid2 priorities - #2!\n");
688 goto exit;
689 }
690
691 /* pid 1 should move to the back... */
692 if (!set_priority_properties(pid1, test_priority, 0, 0, REORDER_TEST_STR, true)) {
693 printf("Cannot set priorities - #3!\n");
694 goto exit;
695 }
696
697 /* ...so validate */
698 if (!check_reorder_priorities(pid2, pid1, test_priority)) {
699 printf("Bad pid2 -> pid1 priorities - #4!\n");
700 goto exit;
701 }
702
703 /* Again, pid 2 should move to the back... */
704 if (!set_priority_properties(pid2, test_priority, 0, 0, REORDER_TEST_STR, true)) {
705 printf("Cannot set priorities - #5!\n");
706 goto exit;
707 }
708
709 /* ...so validate for the last time */
710 if (!check_reorder_priorities(pid1, pid2, test_priority)) {
711 printf("Bad pid1 -> pid2 priorities - #6!\n");
712 goto exit;
713 }
714
715 success = true;
716
717 exit:
718
719 /* Done here... */
720 kill(pid1, SIGKILL);
721 kill(pid2, SIGKILL);
722
723 /* Wait for exit */
724 waitpid(pid1, &status, 0);
725 waitpid(pid2, &status, 0);
726
727 printTestResult("Reorder test", success, NULL);
728 }
729
730 /* Jetsam tests */
731
732 /*
733
734 ASL message format:
735
736 Message is ReadUID 0
737 Message is ReadGID 80
738 Message is ASLMessageID 703
739 Message is Level 7
740 Message is Time 1333155901
741 Message is Sender kernel
742 Message is Facility kern
743
744 */
745
746 static void vnode_test(int page_delta, int interval, int verbose, int32_t priority, uint64_t user_data) {
747 memorystatus_priority_properties_t props;
748
749 props.priority = priority;
750 props.user_data = user_data;
751
752 if (memorystatus_control(MEMORYSTATUS_CMD_SET_PRIORITY_PROPERTIES, getpid(), 0, &props, sizeof(props))) {
753 /*printf("\t\tFailed to set jetsam priority!\n");*/
754 printTestResult(__func__, false, "Failed to set jetsam priority!");
755 cleanup_and_exit(-1);
756 }
757
758 /* Initialized... */
759 child_ready();
760
761 /* ...so start stealing vnodes */
762 while(1) {
763 sleep(1);
764 }
765 }
766
767 static void *wait_for_pressure_event(void *s) {
768 int kq;
769 int res;
770 struct kevent event, mevent;
771 char errMsg[ERR_BUF_LEN + 1];
772
773 kq = kqueue();
774
775 EV_SET(&mevent, 0, EVFILT_VM, EV_ADD, NOTE_VM_PRESSURE, 0, 0);
776
777 res = kevent(kq, &mevent, 1, NULL, 0, NULL);
778 if (res != 0) {
779 /*printf("\t\tKevent registration failed - returning: %d!\n", res);*/
780 snprintf(errMsg, ERR_BUF_LEN, "Kevent registration failed - returning: %d!",res);
781 printTestResult(__func__, false, errMsg);
782 cleanup_and_exit(-1);
783 }
784
785 while (1) {
786 memset(&event, 0, sizeof(struct kevent));
787 res = kevent(kq, NULL, 0, &event, 1, NULL);
788 g_shared->pressure_event_fired = 1;
789 }
790 }
791
792 static void wait_for_exit_event(int pid, uint32_t kill_cause) {
793 int kq;
794 int res;
795 uint32_t expected_flag, received_flag;
796 struct kevent event, mevent;
797 char errMsg[ERR_BUF_LEN + 1];
798
799 switch (kill_cause) {
800 case kMemorystatusKilledVnodes: expected_flag = NOTE_EXIT_MEMORY_VNODE; break;
801 case kMemorystatusKilledVMPageShortage: expected_flag = NOTE_EXIT_MEMORY_VMPAGESHORTAGE; break;
802 case kMemorystatusKilledVMThrashing: expected_flag = NOTE_EXIT_MEMORY_VMTHRASHING; break;
803 case kMemorystatusKilledHiwat: expected_flag = NOTE_EXIT_MEMORY_HIWAT; break;
804 case kMemorystatusKilledPerProcessLimit: expected_flag = NOTE_EXIT_MEMORY_PID; break;
805 case kMemorystatusKilledIdleExit: expected_flag = NOTE_EXIT_MEMORY_IDLE; break;
806 case CAUSE_HIWAT_OR_PERPROC: expected_flag = NOTE_EXIT_MEMORY_HIWAT|NOTE_EXIT_MEMORY_PID; break;
807 }
808
809 kq = kqueue();
810
811 EV_SET(&mevent, pid, EVFILT_PROC, EV_ADD, NOTE_EXIT | NOTE_EXIT_DETAIL, 0, 0);
812
813 res = kevent(kq, &mevent, 1, NULL, 0, NULL);
814 if (res != 0) {
815 snprintf(errMsg,ERR_BUF_LEN,"Exit kevent registration failed - returning: %d!",res);
816 printTestResult(__func__, false, errMsg);
817 cleanup_and_exit(-1);
818 }
819
820 res = kevent(kq, NULL, 0, &event, 1, NULL);
821
822 /* Check if appropriate flags are set */
823 if (!event.fflags & NOTE_EXIT_MEMORY) {
824 printTestResult(__func__, false, "Exit event fflags do not contain NOTE_EXIT_MEMORY\n");
825 cleanup_and_exit(-1);
826 }
827
828 received_flag = event.data & NOTE_EXIT_MEMORY_DETAIL_MASK;
829 if ((received_flag | expected_flag) != expected_flag) {
830 printTestResult(__func__, false, "Exit event data does not contain the expected jetsam flag for cause %x.\n"
831 "\t\t(expected %x, got %x)", kill_cause, expected_flag, received_flag);
832 cleanup_and_exit(-1);
833 }
834 }
835
836 static void munch_test(int page_delta, int interval, int verbose, int32_t priority, int32_t highwater, uint64_t user_data) {
837 const char *MUNCH_TEST_STR = "Munch";
838 char *mem;
839 unsigned long total = 0;
840 pthread_t pe_thread;
841 int res;
842
843 /* Start thread to watch for pressure messages */
844 res = pthread_create(&pe_thread, NULL, wait_for_pressure_event, (void*)g_shared);
845 if (res) {
846 printTestResult(__func__, false, "Error creating pressure event thread!\n");
847 cleanup_and_exit(-1);
848 }
849
850 if (set_priority_properties(getpid(), priority, highwater, user_data, MUNCH_TEST_STR, false) == false) {
851 printTestResult(__func__, false, "Failed to set jetsam priority!");
852 cleanup_and_exit(-1);
853 }
854
855 if (!page_delta) {
856 page_delta = 4096;
857 }
858
859 sleep(1);
860
861 /* Initialized... */
862 child_ready();
863
864 /* ...so start munch */
865 while(1) {
866 int i;
867 mem = malloc(page_delta * VM_PAGE_SIZE);
868 if (!mem) {
869 fprintf(stderr, "Failed to allocate memory!\n");
870 while (1) {
871 sleep(1);
872 }
873 }
874
875 total += page_delta;
876 memset(mem, 0xFF, page_delta * VM_PAGE_SIZE);
877
878 if (verbose) {
879 printf("\t\t%lu pages dirtied...\n", total);
880 }
881
882 sleep(interval);
883 }
884 }
885
886 static bool is_pressure_test(test) {
887 return ((test == kPressureJetsamTestFG) || (test == kPressureJetsamTestBG));
888 }
889
890 static bool verify_exit(pid_t pid, uint32_t kill_cause, time_t start_time, uint32_t test_pri, uint64_t test_user_data, jetsam_test_t test, bool expecting_snapshot) {
891 const char *msg_key = "Message";
892 const char *time_key = "Time";
893 aslmsg query;
894 aslresponse response;
895 aslmsg message;
896 char pid_buffer[16];
897 const char *val;
898 int got_jetsam = 0;
899 bool got_snapshot = 0;
900 bool success;
901
902 /* Wait for exit */
903 wait_for_exit_event(pid, kill_cause);
904
905 /* Let the messages filter through to the log - arbitrary */
906 sleep(3);
907
908 query = asl_new(ASL_TYPE_QUERY);
909 asl_set_query(query, ASL_KEY_SENDER, "kernel", ASL_QUERY_OP_EQUAL);
910 asl_set_query(query, ASL_KEY_MSG, "memorystatus", ASL_QUERY_OP_EQUAL|ASL_QUERY_OP_SUBSTRING);
911 snprintf(pid_buffer, sizeof(pid_buffer) - 1, "%d", pid);
912 asl_set_query(query, ASL_KEY_MSG, pid_buffer, ASL_QUERY_OP_EQUAL|ASL_QUERY_OP_SUBSTRING);
913 response = asl_search(NULL, query);
914 asl_free(query);
915
916 while (NULL != (message = aslresponse_next(response)))
917 {
918 val = asl_get(message, time_key);
919 if (val) {
920 uint32_t msg_time = atoi(val);
921 if (msg_time > start_time) {
922 val = asl_get(message, msg_key);
923 if (val) {
924 printf("\t\tFound: %s\n", val);
925 got_jetsam = 1;
926 }
927 }
928 }
929 }
930
931 if (got_jetsam) {
932 got_snapshot = verify_snapshot(pid, test_pri, kill_cause, test_user_data, expecting_snapshot);
933 } else {
934 printf("\t\tCouldn't find jetsam message in log!\n");
935 }
936
937 aslresponse_free(response);
938
939 success = got_jetsam && (expecting_snapshot == got_snapshot) && (!(is_pressure_test(test)) || (is_pressure_test(test) && g_shared->pressure_event_fired));
940 printTestResult("munch_test", success, "(test: %d, got_jetsam: %d, got_snapshot: %d, fired: %d)", test, got_jetsam, got_snapshot, g_shared->pressure_event_fired);
941
942 return success;
943 }
944
945 static void start_jetsam_test(jetsam_test_t test, const char *description) {
946 const char *msg_key = "Message";
947 const char *time_key = "Time";
948 const char *val;
949 aslmsg query;
950 aslresponse response;
951 aslmsg message;
952 time_t start_time;
953 pid_t pid;
954 char pid_buffer[16];
955 int status;
956 int got_jetsam = 0;
957 int got_snapshot = 0;
958 uint32_t test_pri = 0;
959 uint64_t test_user_data = 0;
960 uint32_t kill_cause;
961 int success;
962 boolean_t expecting_snapshot = TRUE;
963 boolean_t big_mem = (g_physmem > 512 * 1024 * 1024);
964
965 if (big_mem) {
966 /*
967 * On big memory machines (1GB+), there is a per-task memory limit.
968 * A munch test could fail because of this, if they manage to cross it;
969 * *or* because the high watermark was crossed, and the system was under
970 * enough mem pressure to go looking for a high watermark victim to kill.
971 */
972 kill_cause = CAUSE_HIWAT_OR_PERPROC;
973 } else if (test == kHighwaterJetsamTest) {
974 /*
975 * On systems without the per-task memory limit, we shouldn't see any
976 * such kills; so that leaves high watermark kills as the only legitimate
977 * reason to kill a munch test that has a high watermark set.
978 */
979 kill_cause = kMemorystatusKilledHiwat;
980 } else {
981 /*
982 * If this is a standard munch test and we're on a machine without the
983 * per-task memory limit, the only reason for kill should be that we need
984 * memory.
985 */
986 kill_cause = kMemorystatusKilledVMPageShortage;
987 }
988
989 start_time = time(NULL);
990
991 switch (test) {
992 case kPressureJetsamTestFG:
993 test_pri = JETSAM_PRIORITY_FOREGROUND; /* Test that FG processes get pressure events */
994 test_user_data = 0xDEADBEEF;
995 break;
996 case kPressureJetsamTestBG:
997 test_pri = JETSAM_PRIORITY_UI_SUPPORT; /* Test that BG processes get pressure events */
998 test_user_data = 0xFADEBEEF;
999 break;
1000 case kSimpleJetsamTest:
1001 /*
1002 * On 1GB devices, we should see a snapshot as the per-process limit is hit.
1003 * On 512MB devices, we should see a normal jetsam, and no snapshot.
1004 */
1005 expecting_snapshot = big_mem ? TRUE : FALSE;
1006 test_pri = JETSAM_PRIORITY_IDLE; /* Suspended */
1007 test_user_data = 0xFACEF00D;
1008 break;
1009 default:
1010 test_pri = JETSAM_PRIORITY_IDLE; /* Suspended */
1011 test_user_data = 0xCAFEF00D;
1012 break;
1013 }
1014
1015 pid = init_and_fork();
1016
1017 if (pid == 0) {
1018 switch (test) {
1019 case kVnodeJetsamTest:
1020 vnode_test(0, 0, 0, test_pri, test_user_data);
1021 break;
1022 case kHighwaterJetsamTest:
1023 munch_test(0, 0, 0, test_pri, 8, test_user_data);
1024 break;
1025 default:
1026 munch_test(0, 0, 0, test_pri, -1, test_user_data);
1027 break;
1028 }
1029 }
1030 else {
1031 printTestHeader(pid, "%s test", description);
1032 }
1033
1034 verify_exit(pid, kill_cause, start_time, test_pri, test_user_data, test, expecting_snapshot);
1035 }
1036
1037 static void start_jetsam_test_background(const char *path) {
1038 const char *argv[] = {
1039 path,
1040 "-s",
1041 NULL
1042 };
1043
1044 const uint32_t memlimit = 100; /* 100 MB */
1045
1046 time_t start_time;
1047 pid_t pid = 1;
1048 int status;
1049 uint32_t test_pri = 0;
1050 posix_spawnattr_t spattr;
1051 int32_t pf_balance;
1052 bool success;
1053
1054 start_time = time(NULL);
1055
1056 pid = 1;
1057 status = 1;
1058
1059 posix_spawnattr_init(&spattr);
1060 posix_spawnattr_setjetsam(&spattr, (POSIX_SPAWN_JETSAM_USE_EFFECTIVE_PRIORITY | POSIX_SPAWN_JETSAM_HIWATER_BACKGROUND), JETSAM_PRIORITY_UI_SUPPORT, 100);
1061
1062 if (posix_spawn(&pid, path, NULL, &spattr, (char *const *)argv, NULL) < 0) {
1063 printf("posix_spawn() failed!\n");
1064 goto exit;
1065 }
1066
1067 printTestHeader(pid, "Background memory limit test");
1068
1069 /* Starts in background */
1070 if (!check_properties(pid, JETSAM_PRIORITY_UI_SUPPORT, memlimit, 0x0, "jetsam_test_background - #1 BG")) {
1071 goto exit;
1072 }
1073
1074 /* Set to foreground - priority and memlimit should change */
1075 set_priority(pid, JETSAM_PRIORITY_FOREGROUND, 0);
1076 if (!check_properties(pid, JETSAM_PRIORITY_FOREGROUND, 0, 0x0, "jetsam_test_background - #2 FG")) {
1077 goto exit;
1078 }
1079
1080 /* ...and back */
1081 set_priority(pid, JETSAM_PRIORITY_BACKGROUND, 0);
1082 if (!check_properties(pid, JETSAM_PRIORITY_BACKGROUND, memlimit, 0x0, "jetsam_test_background - #3 BG")) {
1083 goto exit;
1084 }
1085
1086 /* ...and again */
1087 set_priority(pid, JETSAM_PRIORITY_FOREGROUND, 0);
1088 if (!check_properties(pid, JETSAM_PRIORITY_FOREGROUND, 0, 0x0, "jetsam_test_background - #4 FG")) {
1089 goto exit;
1090 }
1091
1092 #if 1
1093 /*
1094 * For now, this is all we can do. Limitations of the ledger mean that this process is credited with
1095 * the dirty pages, *not* the child. At least the memory limit is reported to have shifted dynamically
1096 * by this point. Kill the child and continue.
1097 */
1098 kill(pid, SIGKILL);
1099 #else
1100 /* Let the process dirty 128MB of memory, then background itself */
1101 verify_exit(pid, kMemorystatusKilledPerProcessLimit, start_time, test_pri, 0, kBackgroundJetsamTest);
1102 #endif
1103
1104 success = true;
1105
1106 exit:
1107 if (pid != -1) {
1108 kill(pid, SIGKILL);
1109 }
1110
1111 /* Wait for exit */
1112 waitpid(pid, &status, 0);
1113
1114 printTestResult("Background test", success, NULL);
1115 }
1116
1117 /* Freeze tests */
1118
1119 /* Cribbed from 'top'... */
1120 static int
1121 in_shared_region(mach_vm_address_t addr, cpu_type_t type) {
1122 mach_vm_address_t base = 0, size = 0;
1123
1124 switch(type) {
1125 case CPU_TYPE_ARM:
1126 base = SHARED_REGION_BASE_ARM;
1127 size = SHARED_REGION_SIZE_ARM;
1128 break;
1129
1130 case CPU_TYPE_X86_64:
1131 base = SHARED_REGION_BASE_X86_64;
1132 size = SHARED_REGION_SIZE_X86_64;
1133 break;
1134
1135 case CPU_TYPE_I386:
1136 base = SHARED_REGION_BASE_I386;
1137 size = SHARED_REGION_SIZE_I386;
1138 break;
1139
1140 case CPU_TYPE_POWERPC:
1141 base = SHARED_REGION_BASE_PPC;
1142 size = SHARED_REGION_SIZE_PPC;
1143 break;
1144
1145 case CPU_TYPE_POWERPC64:
1146 base = SHARED_REGION_BASE_PPC64;
1147 size = SHARED_REGION_SIZE_PPC64;
1148 break;
1149
1150 default: {
1151 int t = type;
1152
1153 fprintf(stderr, "unknown CPU type: 0x%x\n", t);
1154 abort();
1155 }
1156 break;
1157 }
1158
1159 return(addr >= base && addr < (base + size));
1160 }
1161
1162 static unsigned long long get_rprvt(mach_port_t task, pid_t pid) {
1163 kern_return_t kr;
1164
1165 mach_vm_size_t rprvt = 0;
1166 mach_vm_size_t empty = 0;
1167 mach_vm_size_t fw_private = 0;
1168 mach_vm_size_t pagesize = VM_PAGE_SIZE;
1169 mach_vm_size_t regs = 0;
1170
1171 mach_vm_address_t addr;
1172 mach_vm_size_t size;
1173
1174 int split = 0;
1175
1176 for (addr = 0; ; addr += size) {
1177 vm_region_top_info_data_t info;
1178 mach_msg_type_number_t count = VM_REGION_TOP_INFO_COUNT;
1179 mach_port_t object_name;
1180
1181 kr = mach_vm_region(task, &addr, &size, VM_REGION_TOP_INFO, (vm_region_info_t)&info, &count, &object_name);
1182 if (kr != KERN_SUCCESS) break;
1183
1184 if (in_shared_region(addr, CPU_TYPE_ARM)) {
1185 // Private Shared
1186 fw_private += info.private_pages_resident * pagesize;
1187
1188 /*
1189 * Check if this process has the globally shared
1190 * text and data regions mapped in. If so, set
1191 * split to TRUE and avoid checking
1192 * again.
1193 */
1194 if (split == FALSE && info.share_mode == SM_EMPTY) {
1195 vm_region_basic_info_data_64_t b_info;
1196 mach_vm_address_t b_addr = addr;
1197 mach_vm_size_t b_size = size;
1198 count = VM_REGION_BASIC_INFO_COUNT_64;
1199
1200 kr = mach_vm_region(task, &b_addr, &b_size, VM_REGION_BASIC_INFO, (vm_region_info_t)&b_info, &count, &object_name);
1201 if (kr != KERN_SUCCESS) break;
1202
1203 if (b_info.reserved) {
1204 split = TRUE;
1205 }
1206 }
1207
1208 /*
1209 * Short circuit the loop if this isn't a shared
1210 * private region, since that's the only region
1211 * type we care about within the current address
1212 * range.
1213 */
1214 if (info.share_mode != SM_PRIVATE) {
1215 continue;
1216 }
1217 }
1218
1219 regs++;
1220
1221 /*
1222 * Update counters according to the region type.
1223 */
1224
1225 if (info.share_mode == SM_COW && info.ref_count == 1) {
1226 // Treat single reference SM_COW as SM_PRIVATE
1227 info.share_mode = SM_PRIVATE;
1228 }
1229
1230 switch (info.share_mode) {
1231 case SM_LARGE_PAGE:
1232 // Treat SM_LARGE_PAGE the same as SM_PRIVATE
1233 // since they are not shareable and are wired.
1234 case SM_PRIVATE:
1235 rprvt += info.private_pages_resident * pagesize;
1236 rprvt += info.shared_pages_resident * pagesize;
1237 break;
1238
1239 case SM_EMPTY:
1240 empty += size;
1241 break;
1242
1243 case SM_COW:
1244 case SM_SHARED:
1245 if (pid == 0) {
1246 // Treat kernel_task specially
1247 if (info.share_mode == SM_COW) {
1248 rprvt += info.private_pages_resident * pagesize;
1249 }
1250 break;
1251 }
1252
1253 if (info.share_mode == SM_COW) {
1254 rprvt += info.private_pages_resident * pagesize;
1255 }
1256 break;
1257
1258 default:
1259 assert(0);
1260 break;
1261 }
1262 }
1263
1264 return rprvt;
1265 }
1266
1267 static void freeze_test() {
1268 const unsigned long DIRTY_ALLOC = 16 * 1024 * 1024;
1269 unsigned long *ptr;
1270 task_port_t task = mach_task_self();
1271
1272 child_ready();
1273
1274 /* Needs to be vm_allocate() here; otherwise the compiler will optimize memset away */
1275 vm_allocate(task, (vm_address_t *)&ptr, DIRTY_ALLOC, TRUE);
1276 if (ptr) {
1277 int i;
1278 int pid = getpid();
1279 unsigned long long baseline_rprvt, half_rprvt, rprvt;
1280
1281 /* Get baseline */
1282 baseline_rprvt = get_rprvt(task, pid);
1283
1284 /* Dirty half */
1285 memset(ptr, 0xAB, DIRTY_ALLOC / 2);
1286
1287 /* Check RPRVT */
1288 half_rprvt = get_rprvt(task, pid);
1289 printf("\t\trprvt is %llu\n", half_rprvt);
1290
1291 if (half_rprvt != (baseline_rprvt + (DIRTY_ALLOC / 2)))
1292 {
1293 printTestResult(__func__, false, "Failed to dirty memory");
1294 cleanup_and_exit(-1);
1295 }
1296
1297 /* Freeze */
1298 sysctlbyname("kern.memorystatus_freeze", NULL, 0, &pid, sizeof(pid));
1299
1300 sleep(2);
1301
1302 /* Check RPRVT */
1303 rprvt = get_rprvt(task, pid);
1304 printf("\t\trprvt is %llu\n", rprvt);
1305
1306 if ((rprvt > (half_rprvt - (DIRTY_ALLOC / 2))) || (rprvt > (64 * 1024)) /* Sanity */)
1307 {
1308 printTestResult(__func__, false, "Failed to freeze memory");
1309 cleanup_and_exit(-1);
1310 }
1311
1312 /* Thaw */
1313 sysctlbyname("kern.memorystatus_thaw", NULL, 0, &pid, sizeof(pid));
1314
1315 sleep(2);
1316
1317 /* Check RPRVT */
1318 rprvt = get_rprvt(task, pid);
1319 printf("\t\trprvt is %llu\n", rprvt);
1320
1321 if (rprvt < (baseline_rprvt + (DIRTY_ALLOC / 2)))
1322 {
1323 printTestResult(__func__, false, "Failed to thaw memory");
1324 cleanup_and_exit(-1);
1325 }
1326
1327 /* Dirty the rest */
1328 memset(ptr + (DIRTY_ALLOC / (2 * sizeof(unsigned long))), 0xBC, DIRTY_ALLOC / 2);
1329
1330 /* Check RPRVT */
1331 rprvt = get_rprvt(task, pid);
1332 printf("\t\trprvt is %llu\n", rprvt);
1333
1334 if (rprvt < (baseline_rprvt + DIRTY_ALLOC))
1335 {
1336 printTestResult(__func__, false, "Failed to dirty memory");
1337 cleanup_and_exit(-1);
1338 }
1339
1340 g_shared->completed = 1;
1341 cleanup_and_exit(0);
1342 }
1343
1344 printTestResult(__func__, false, "Something bad happened...");
1345 cleanup_and_exit(-1);
1346 }
1347
1348 static void start_freeze_test() {
1349 pid_t pid;
1350 int status;
1351 int mode;
1352 size_t size;
1353
1354 /* Check to see if the test is applicable */
1355 size = sizeof(mode);
1356 if (sysctlbyname("vm.compressor_mode", &mode, &size, NULL, 0) != 0) {
1357 printTestHeader(getpid(), "Freeze test");
1358 printTestResult(__func__, false, "Failed to retrieve compressor config");
1359 cleanup_and_exit(-1);
1360 }
1361
1362 if (mode != VM_PAGER_FREEZER_DEFAULT) {
1363 printTestHeader(getpid(), "Freeze test");
1364 printTestResult(__func__, true, "Freeze disabled; skipping test");
1365 return;
1366 }
1367
1368 /* Reset */
1369 memset(g_shared, 0, sizeof(shared_mem_t));
1370
1371 pid = init_and_fork();
1372 if (pid == 0) {
1373 freeze_test();
1374 } else {
1375 printTestHeader(pid, "Freeze test");
1376 }
1377
1378 /* Wait for exit */
1379 waitpid(pid, &status, 0);
1380
1381 printTestResult("Freeze test", g_shared->completed, NULL);
1382 }
1383
1384 #endif
1385
1386 static void start_list_validation_test() {
1387 int size;
1388 memorystatus_priority_entry_t *entries = NULL;
1389 int i;
1390 boolean_t valid = false;
1391
1392 printTestHeader(getpid(), "List validation test");
1393
1394 entries = get_priority_list(&size);
1395 if (!entries) {
1396 printf("Can't get entries!\n");
1397 goto exit;
1398 }
1399
1400 /* Validate */
1401 for (i = 0; i < size/sizeof(memorystatus_priority_entry_t); i++ ) {
1402 int dirty_ret;
1403 uint32_t dirty_flags;
1404
1405 /* Make sure launchd isn't in the list - <rdar://problem/13168754> */
1406 if (entries[i].pid <= 1) {
1407 printf("\t\tBad process (%d) in list!\n", entries[i].pid);
1408 goto exit;
1409 }
1410
1411 /* Sanity check idle exit state */
1412 dirty_ret = proc_get_dirty(entries[i].pid, &dirty_flags);
1413 if (dirty_ret != 0) {
1414 dirty_flags = 0;
1415 }
1416
1417 if (dirty_flags & PROC_DIRTY_ALLOWS_IDLE_EXIT) {
1418 /* Check that the process isn't at idle priority when dirty */
1419 if ((entries[i].priority == JETSAM_PRIORITY_IDLE) && (dirty_flags & PROC_DIRTY_IS_DIRTY)) {
1420 printf("\t\tProcess %d at idle priority when dirty (priority %d, flags 0x%x)!\n", entries[i].pid, entries[i].priority, dirty_flags);
1421 goto exit;
1422 }
1423 /* Check that the process is at idle (or deferred) priority when clean. */
1424 if ((entries[i].priority > JETSAM_PRIORITY_IDLE_DEFERRED) && !(dirty_flags & PROC_DIRTY_IS_DIRTY)) {
1425 printf("\t\tProcess %d not at non-idle priority when clean(priority %d, flags 0x%x)\n", entries[i].pid, entries[i].priority, dirty_flags);
1426 goto exit;
1427 }
1428 }
1429 }
1430
1431 valid = true;
1432
1433 exit:
1434 free(entries);
1435
1436 printTestResult("List validation test", valid, NULL);
1437 }
1438
1439 /* Random individual tests */
1440 static void start_general_sanity_test() {
1441 int ret, size;
1442 memorystatus_priority_entry_t *entries = NULL;
1443 int i;
1444 boolean_t valid = false;
1445
1446 printTestHeader(getpid(), "Sanity test");
1447
1448 /* Should not be able to set the priority of launchd... */
1449 ret = set_priority(1, JETSAM_PRIORITY_FOREGROUND, 0);
1450 if (ret != -1 || errno != EPERM) {
1451 printf("\t\tAble to set priority of launchd (%d/%d)!\n", ret, errno);
1452 goto exit;
1453 } else {
1454 printf("\t\tlaunchd priority test OK!\n");
1455 }
1456
1457 /* ...nor the memory limit... */
1458 ret = set_memlimit(1, 100);
1459 if (ret != -1 || errno != EPERM) {
1460 printf("\t\tNo EPERM setting launchd memlimit (%d/%d)!\n", ret, errno);
1461 goto exit;
1462 } else {
1463 printf("\t\tlaunchd memlimit test OK!\n");
1464 }
1465
1466 /* ...nor tinker with transactions */
1467 ret = proc_track_dirty(1, PROC_DIRTY_TRACK | PROC_DIRTY_ALLOW_IDLE_EXIT | PROC_DIRTY_DEFER);
1468 if (ret != EPERM) {
1469 printf("\t\tNo EPERM tracking launchd (%d/%d)!\n", ret, errno);
1470 goto exit;
1471 } else {
1472 printf("\t\tlaunchd track test OK!\n");
1473 }
1474
1475 ret = proc_set_dirty(1, true);
1476 if (ret != EPERM) {
1477 printf("\t\tNo EPERM setting launchd dirty state (%d/%d)!\n", ret, errno);
1478 goto exit;
1479 } else {
1480 printf("\t\tlaunchd dirty test OK!\n");
1481 }
1482
1483 valid = true;
1484
1485 exit:
1486 free(entries);
1487
1488 printTestResult("Idle exit test", valid, NULL);
1489 }
1490
1491 static void idle_exit_deferral_test(idle_exit_test_t test) {
1492 int secs = DEFERRED_IDLE_EXIT_TIME_SECS;
1493
1494 child_ready();
1495
1496 if (!check_properties(getpid(), JETSAM_PRIORITY_DEFAULT, -1, 0x0, "#1 - pre xpc_track_activity()")) {
1497 goto exit;
1498 }
1499
1500 proc_track_dirty(getpid(), PROC_DIRTY_TRACK | PROC_DIRTY_ALLOW_IDLE_EXIT | PROC_DIRTY_DEFER);
1501
1502 if (!check_properties(getpid(), JETSAM_PRIORITY_IDLE_DEFERRED, -1, 0x0, "#2 - post xpc_track_activity()")) {
1503 goto exit;
1504 }
1505
1506 /* Toggle */
1507 proc_set_dirty(getpid(), true);
1508 proc_set_dirty(getpid(), false);
1509 proc_set_dirty(getpid(), true);
1510 proc_set_dirty(getpid(), false);
1511
1512 switch (test) {
1513 case kDeferTimeoutCleanTest:
1514 if (!check_properties(getpid(), JETSAM_PRIORITY_IDLE_DEFERRED, -1, 0x0, "#3 - post toggle")) {
1515 goto exit;
1516 }
1517
1518 /* Approximate transition check */
1519 sleep(secs - 1);
1520
1521 if (!check_properties(getpid(), JETSAM_PRIORITY_IDLE_DEFERRED, -1, 0x0, "#4 - pre timeout")) {
1522 goto exit;
1523 }
1524
1525 sleep(2);
1526
1527 if (!check_properties(getpid(), JETSAM_PRIORITY_IDLE, -1, 0x0, "#5 - post timeout")) {
1528 goto exit;
1529 }
1530
1531 proc_set_dirty(getpid(), true);
1532
1533 if (!check_properties(getpid(), JETSAM_PRIORITY_DEFAULT, -1, 0x0, "#6 - post dirty")) {
1534 goto exit;
1535 }
1536
1537 proc_set_dirty(getpid(), false);
1538
1539 if (!check_properties(getpid(), JETSAM_PRIORITY_IDLE, -1, 0x0, "#7 - post clean")) {
1540 goto exit;
1541 }
1542
1543 break;
1544 case kDeferTimeoutDirtyTest:
1545 proc_set_dirty(getpid(), true);
1546
1547 if (!check_properties(getpid(), JETSAM_PRIORITY_DEFAULT, -1, 0x0, "#3 - post dirty")) {
1548 goto exit;
1549 }
1550
1551 /* Approximate transition check */
1552 sleep(secs - 1);
1553
1554 if (!check_properties(getpid(), JETSAM_PRIORITY_DEFAULT, -1, 0x0, "#4 - pre timeout")) {
1555 goto exit;
1556 }
1557
1558 sleep(2);
1559
1560 if (!check_properties(getpid(), JETSAM_PRIORITY_DEFAULT, -1, 0x0, "#5 - post timeout")) {
1561 goto exit;
1562 }
1563
1564 proc_set_dirty(getpid(), false);
1565
1566 if (!check_properties(getpid(), JETSAM_PRIORITY_IDLE, -1, 0x0, "#6 - post clean")) {
1567 goto exit;
1568 }
1569
1570 break;
1571 case kCancelTimeoutDirtyTest:
1572 proc_set_dirty(getpid(), true);
1573
1574 if (!check_properties(getpid(), JETSAM_PRIORITY_DEFAULT, -1, 0x0, "#3 - post toggle")) {
1575 goto exit;
1576 }
1577
1578 proc_track_dirty(getpid(), PROC_DIRTY_TRACK | PROC_DIRTY_ALLOW_IDLE_EXIT);
1579
1580 if (!check_properties(getpid(), JETSAM_PRIORITY_DEFAULT, -1, 0x0, "#4 - post deferral cancellation")) {
1581 goto exit;
1582 }
1583
1584 proc_set_dirty(getpid(), false);
1585
1586 if (!check_properties(getpid(), JETSAM_PRIORITY_IDLE, -1, 0x0, "#5 - post toggle")) {
1587 goto exit;
1588 }
1589
1590 break;
1591 case kCancelTimeoutCleanTest:
1592 if (!check_properties(getpid(), JETSAM_PRIORITY_IDLE_DEFERRED, -1, 0x0, "#3 - post toggle")) {
1593 goto exit;
1594 }
1595
1596 proc_track_dirty(getpid(), PROC_DIRTY_TRACK | PROC_DIRTY_ALLOW_IDLE_EXIT);
1597
1598 if (!check_properties(getpid(), JETSAM_PRIORITY_IDLE, -1, 0x0, "#4 - post deferral cancellation")) {
1599 goto exit;
1600 }
1601
1602 proc_set_dirty(getpid(), true);
1603
1604 if (!check_properties(getpid(), JETSAM_PRIORITY_DEFAULT, -1, 0x0, "#5 - post dirty")) {
1605 goto exit;
1606 }
1607
1608 proc_set_dirty(getpid(), false);
1609
1610 if (!check_properties(getpid(), JETSAM_PRIORITY_IDLE, -1, 0x0, "#6 - post clean")) {
1611 goto exit;
1612 }
1613
1614 break;
1615 }
1616
1617 g_shared->completed = 1;
1618 cleanup_and_exit(0);
1619
1620 exit:
1621 printTestResult(__func__, false, "Something bad happened...");
1622 cleanup_and_exit(-1);
1623 }
1624
1625 static void start_idle_exit_defer_test(idle_exit_test_t test) {
1626 pid_t pid;
1627 int status;
1628
1629 /* Reset */
1630 memset(g_shared, 0, sizeof(shared_mem_t));
1631
1632 pid = init_and_fork();
1633 if (pid == 0) {
1634 idle_exit_deferral_test(test);
1635 }
1636 else {
1637 printTestHeader(pid, "Idle exit deferral test");
1638 }
1639
1640 /* Wait for exit */
1641 waitpid(pid, &status, 0);
1642 /* Idle exit not reported on embedded */
1643 // wait_for_exit_event(pid, kMemorystatusKilledIdleExit);
1644
1645 printTestResult("Idle exit deferral test", g_shared->completed, NULL);
1646 }
1647
1648 static void ledger_init(void) {
1649 const char *physFootprintName = "phys_footprint";
1650 struct ledger_info li;
1651 int64_t template_cnt;
1652 struct ledger_template_info *templateInfo;
1653 void *arg;
1654 int i;
1655
1656 /* Grab ledger entries */
1657 arg = (void *)(long)getpid();
1658 if (ledger(LEDGER_INFO, arg, (caddr_t)&li, NULL) < 0) {
1659 exit(-1);
1660 }
1661
1662 g_ledger_count = template_cnt = li.li_entries;
1663
1664 templateInfo = malloc(template_cnt * sizeof (struct ledger_template_info));
1665 if (templateInfo == NULL) {
1666 exit (-1);
1667 }
1668
1669 if (!(ledger(LEDGER_TEMPLATE_INFO, (caddr_t)templateInfo, (caddr_t)&template_cnt, NULL) < 0)) {
1670 for (i = 0; i < template_cnt; i++) {
1671 if (!strncmp(templateInfo[i].lti_name, physFootprintName, strlen(physFootprintName))) {
1672 g_footprint_index = i;
1673 break;
1674 }
1675 }
1676 }
1677
1678 free(templateInfo);
1679 }
1680
1681 static void run_tests(const char *path) {
1682 /* Embedded-only */
1683 #if TARGET_OS_EMBEDDED
1684 start_jetsam_test(kSimpleJetsamTest, "Simple munch");
1685 start_jetsam_test(kHighwaterJetsamTest, "Highwater munch");
1686 start_jetsam_test(kPressureJetsamTestBG, "Background pressure munch");
1687 start_jetsam_test(kPressureJetsamTestFG, "Foreground Pressure munch");
1688 start_jetsam_test_background(path);
1689 start_freeze_test();
1690 start_priority_test();
1691 start_fs_priority_test();
1692 #else
1693 #pragma unused(path)
1694 #endif
1695
1696 /* Generic */
1697 start_general_sanity_test();
1698 start_list_validation_test();
1699 start_idle_exit_defer_test(kDeferTimeoutCleanTest);
1700 start_idle_exit_defer_test(kDeferTimeoutDirtyTest);
1701 start_idle_exit_defer_test(kCancelTimeoutCleanTest);
1702 start_idle_exit_defer_test(kCancelTimeoutDirtyTest);
1703 }
1704
1705 #if TARGET_OS_EMBEDDED
1706
1707 static void
1708 sigterm(int sig)
1709 {
1710 /* Reload crash reporter job */
1711 enable_crashreporter();
1712
1713 /* Reset signal handlers and re-raise signal */
1714 signal(SIGTERM, SIG_DFL);
1715 signal(SIGINT, SIG_DFL);
1716
1717 kill(getpid(), sig);
1718 }
1719
1720 #endif
1721
1722 int main(int argc, char **argv)
1723 {
1724 pthread_mutexattr_t attr;
1725 pthread_condattr_t cattr;
1726 size_t size;
1727 #if TARGET_OS_EMBEDDED
1728 struct sigaction sa;
1729 #endif
1730
1731 /* Must be run as root for priority retrieval */
1732 if (getuid() != 0) {
1733 fprintf(stderr, "%s must be run as root.\n", getprogname());
1734 exit(EXIT_FAILURE);
1735 }
1736
1737 #if TARGET_OS_EMBEDDED
1738 /* Spawn test */
1739 if ((argc == 2) && !strcmp(argv[1], "-s")) {
1740 spawn_test();
1741 }
1742
1743 sa.sa_flags = 0;
1744 sa.sa_handler = sigterm;
1745 sigemptyset(&sa.sa_mask);
1746
1747 /* Ensure we can reinstate CrashReporter on exit */
1748 sigaction(SIGTERM, &sa, NULL);
1749 sigaction(SIGINT, &sa, NULL);
1750
1751 /* Unload */
1752 disable_crashreporter();
1753
1754 /* Flush the jetsam snapshot */
1755 verify_snapshot(-1, 0, 0, 0, FALSE);
1756 #endif
1757
1758 /* Memory */
1759 size = sizeof(g_physmem);
1760 if (sysctlbyname("hw.physmem", &g_physmem, &size, NULL, 0) != 0 || !g_physmem) {
1761 printTestResult(__func__, false, "Failed to retrieve system memory");
1762 cleanup_and_exit(-1);
1763 }
1764
1765 /* Ledger; default limit applies to this process, so grab it here */
1766 ledger_init();
1767 if ((-1 == g_ledger_count) || (-1 == g_footprint_index) || (false == get_ledger_info(getpid(), NULL, &g_per_process_limit))) {
1768 printTestResult("setup", false, "Unable to init ledger!\n");
1769 cleanup_and_exit(-1);
1770 }
1771
1772 /* Rescale to MB */
1773 g_per_process_limit /= (1024 * 1024);
1774
1775 /* Shared memory */
1776 g_shared = mmap(NULL, sizeof(shared_mem_t), PROT_WRITE|PROT_READ, MAP_ANON|MAP_SHARED, 0, 0);
1777 if (!g_shared) {
1778 printTestResult(__func__, false, "Failed mmap");
1779 cleanup_and_exit(-1);
1780 }
1781
1782 pthread_mutexattr_init(&attr);
1783 pthread_mutexattr_setpshared(&attr, PTHREAD_PROCESS_SHARED );
1784
1785 pthread_condattr_init(&cattr);
1786 pthread_condattr_setpshared(&cattr, PTHREAD_PROCESS_SHARED);
1787
1788 if (pthread_mutex_init(&g_shared->mutex, &attr) || pthread_cond_init(&g_shared->cv, &cattr)) {
1789 printTestResult("setup", false, "Unable to init condition variable!\n");
1790 cleanup_and_exit(-1);
1791 }
1792
1793 run_tests(argv[0]);
1794
1795 /* Teardown */
1796 pthread_mutex_destroy(&g_shared->mutex);
1797 pthread_cond_destroy(&g_shared->cv);
1798
1799 pthread_mutexattr_destroy(&attr);
1800 pthread_condattr_destroy(&cattr);
1801
1802 #if TARGET_OS_EMBEDDED
1803 /* Reload crash reporter */
1804 enable_crashreporter();
1805 #endif
1806
1807 return 0;
1808 }