]> git.saurik.com Git - apple/xnu.git/blob - tests/memorystatus_zone_test.c
xnu-6153.101.6.tar.gz
[apple/xnu.git] / tests / memorystatus_zone_test.c
1 #include <stdio.h>
2 #include <mach/mach_vm.h>
3 #include <mach/mach_port.h>
4 #include <mach/mach_host.h>
5 #include <mach/mach_error.h>
6 #include <mach-o/dyld.h>
7 #include <sys/sysctl.h>
8 #include <sys/kdebug.h>
9 #include <sys/mman.h>
10 #include <sys/kern_memorystatus.h>
11 #include <ktrace/session.h>
12 #include <dispatch/private.h>
13
14 #ifdef T_NAMESPACE
15 #undef T_NAMESPACE
16 #endif
17 #include <darwintest.h>
18 #include <darwintest_utils.h>
19
20 T_GLOBAL_META(
21 T_META_NAMESPACE("xnu.vm"),
22 T_META_CHECK_LEAKS(false)
23 );
24
25 #define TIMEOUT_SECS 10 * 60 /* abort if test takes > 10 minutes */
26
27 #if (TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR)
28 #define ALLOCATION_SIZE_VM_REGION (16*1024) /* 16 KB */
29 #define ALLOCATION_SIZE_VM_OBJECT ALLOCATION_SIZE_VM_REGION
30 #else
31 #define ALLOCATION_SIZE_VM_REGION (1024*1024*100) /* 100 MB */
32 #define ALLOCATION_SIZE_VM_OBJECT (16*1024) /* 16 KB */
33 #endif
34 #define MAX_CHILD_PROCS 100
35
36 #define NUM_GIVE_BACK 5
37 #define NUM_GIVE_BACK_PORTS 20
38
39 /* 60% is too high on bridgeOS to achieve without vm-pageshortage jetsams. Set it to 40%. */
40 #if TARGET_OS_BRIDGE
41 #define ZONEMAP_JETSAM_LIMIT_SYSCTL "kern.zone_map_jetsam_limit=40"
42 #else
43 #define ZONEMAP_JETSAM_LIMIT_SYSCTL "kern.zone_map_jetsam_limit=60"
44 #endif
45
46 #define VME_ZONE_TEST_OPT "allocate_vm_regions"
47 #define VM_OBJECTS_ZONE_TEST_OPT "allocate_vm_objects"
48 #define GENERIC_ZONE_TEST_OPT "allocate_from_generic_zone"
49
50 #define VME_ZONE "VM map entries"
51 #define VMOBJECTS_ZONE "vm objects"
52 #define VMENTRY_TO_VMOBJECT_COMPARISON_RATIO 98
53
54 #define VM_TAG1 100
55 #define VM_TAG2 101
56
57 #define LARGE_MEM_GB 32
58 #define LARGE_MEM_JETSAM_LIMIT 40
59 #define JETSAM_LIMIT_LOWEST 10
60
61 enum {
62 VME_ZONE_TEST = 0,
63 VM_OBJECTS_ZONE_TEST,
64 GENERIC_ZONE_TEST,
65 };
66
67 typedef struct test_config_struct {
68 int test_index;
69 int num_zones;
70 const char *helper_func;
71 mach_zone_name_array_t zone_names;
72 } test_config_struct;
73
74 static test_config_struct current_test;
75 static dispatch_source_t ds_signal = NULL;
76 static dispatch_source_t ds_timer = NULL;
77 static dispatch_queue_t dq_spawn = NULL;
78 static ktrace_session_t session = NULL;
79
80 static mach_zone_info_array_t zone_info_array = NULL;
81 static mach_zone_name_t largest_zone_name;
82 static mach_zone_info_t largest_zone_info;
83
84 static pthread_mutex_t test_mtx = PTHREAD_MUTEX_INITIALIZER; /* protects the next 3 things */
85 static bool test_ending = false;
86 static int num_children = 0;
87 static pid_t child_pids[MAX_CHILD_PROCS];
88
89 static char testpath[PATH_MAX];
90 static void allocate_vm_stuff(int);
91 static void allocate_from_generic_zone(void);
92 static void begin_test_teardown(void);
93 static void cleanup_and_end_test(void);
94 static void setup_ktrace_session(void);
95 static void spawn_child_process(void);
96 static void run_test(void);
97 static bool verify_generic_jetsam_criteria(void);
98 static bool vme_zone_compares_to_vm_objects(void);
99 static int query_zone_map_size(void);
100 static void query_zone_info(void);
101 static void print_zone_info(mach_zone_name_t *zn, mach_zone_info_t *zi);
102
103 extern void mach_zone_force_gc(host_t host);
104 extern kern_return_t mach_zone_info_for_largest_zone(
105 host_priv_t host,
106 mach_zone_name_t *name,
107 mach_zone_info_t *info
108 );
109
110 static bool
111 check_time(time_t start, int timeout)
112 {
113 return start + timeout < time(NULL);
114 }
115
116 /*
117 * flag values for allocate_vm_stuff()
118 */
119 #define REGIONS 1
120 #define OBJECTS 2
121
122 static void
123 allocate_vm_stuff(int flags)
124 {
125 uint64_t alloc_size, i;
126 time_t start = time(NULL);
127 mach_vm_address_t give_back[NUM_GIVE_BACK];
128 char *msg;
129
130 if (flags == REGIONS) {
131 alloc_size = ALLOCATION_SIZE_VM_REGION;
132 msg = "";
133 } else {
134 alloc_size = ALLOCATION_SIZE_VM_OBJECT;
135 msg = " each region backed by a VM object";
136 }
137
138 printf("[%d] Allocating VM regions, each of size %lld KB%s\n", getpid(), (alloc_size >> 10), msg);
139
140 for (i = 0;; i++) {
141 mach_vm_address_t addr = (mach_vm_address_t)NULL;
142
143 /* Alternate VM tags between consecutive regions to prevent coalescing */
144 int vmflags = VM_MAKE_TAG((i % 2)? VM_TAG1: VM_TAG2) | VM_FLAGS_ANYWHERE;
145
146 if ((mach_vm_allocate(mach_task_self(), &addr, (mach_vm_size_t)alloc_size, vmflags)) != KERN_SUCCESS) {
147 break;
148 }
149
150 /*
151 * If interested in objects, touch the region so the VM object is created,
152 * then free this page. Keeps us from holding a lot of dirty pages.
153 */
154 if (flags == OBJECTS) {
155 *((int *)addr) = 0;
156 madvise((void *)addr, (size_t)alloc_size, MADV_FREE);
157 }
158
159 if (check_time(start, TIMEOUT_SECS)) {
160 printf("[%d] child timeout during allocations\n", getpid());
161 exit(0);
162 }
163
164 if (i < NUM_GIVE_BACK) {
165 give_back[i] = addr;
166 }
167 }
168
169 /* return some of the resource to avoid O-O-M problems */
170 for (uint64_t j = 0; j < NUM_GIVE_BACK && j < i; ++j) {
171 mach_vm_deallocate(mach_task_self(), give_back[j], (mach_vm_size_t)alloc_size);
172 }
173
174 printf("[%d] Number of allocations: %lld\n", getpid(), i);
175
176 /* Signal to the parent that we're done allocating */
177 kill(getppid(), SIGUSR1);
178
179 while (1) {
180 sleep(2);
181 /* Exit if parent has exited. Ensures child processes don't linger around after the test exits */
182 if (getppid() == 1) {
183 exit(0);
184 }
185
186 if (check_time(start, TIMEOUT_SECS)) {
187 printf("[%d] child timeout while waiting\n", getpid());
188 exit(0);
189 }
190 }
191 }
192
193
194 static void
195 allocate_from_generic_zone(void)
196 {
197 uint64_t i = 0;
198 time_t start = time(NULL);
199 mach_port_t give_back[NUM_GIVE_BACK_PORTS];
200
201 printf("[%d] Allocating mach_ports\n", getpid());
202 for (i = 0;; i++) {
203 mach_port_t port;
204
205 if ((mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, &port)) != KERN_SUCCESS) {
206 break;
207 }
208
209 if (check_time(start, TIMEOUT_SECS)) {
210 printf("[%d] child timeout during allocations\n", getpid());
211 exit(0);
212 }
213
214 if (i < NUM_GIVE_BACK_PORTS) {
215 give_back[i] = port;
216 }
217 }
218
219 /* return some of the resource to avoid O-O-M problems */
220 for (uint64_t j = 0; j < NUM_GIVE_BACK_PORTS && j < i; ++j) {
221 mach_port_deallocate(mach_task_self(), give_back[j]);
222 }
223 printf("[%d] Number of allocations: %lld\n", getpid(), i);
224
225 /* Signal to the parent that we're done allocating */
226 kill(getppid(), SIGUSR1);
227
228 while (1) {
229 sleep(2);
230 /* Exit if parent has exited. Ensures child processes don't linger around after the test exits */
231 if (getppid() == 1) {
232 exit(0);
233 }
234
235 if (check_time(start, TIMEOUT_SECS)) {
236 printf("[%d] child timeout while waiting\n", getpid());
237 exit(0);
238 }
239 }
240 }
241
242 static void
243 print_zone_info(mach_zone_name_t *zn, mach_zone_info_t *zi)
244 {
245 T_LOG("ZONE NAME: %-35sSIZE: %-25lluELEMENTS: %llu",
246 zn->mzn_name, zi->mzi_cur_size, zi->mzi_count);
247 }
248
249 static time_t main_start;
250
251 static void
252 query_zone_info(void)
253 {
254 int i;
255 kern_return_t kr;
256 static uint64_t num_calls = 0;
257
258 if (check_time(main_start, TIMEOUT_SECS)) {
259 T_ASSERT_FAIL("Global timeout expired");
260 }
261 for (i = 0; i < current_test.num_zones; i++) {
262 kr = mach_zone_info_for_zone(mach_host_self(), current_test.zone_names[i], &(zone_info_array[i]));
263 T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "mach_zone_info_for_zone(%s) returned %d [%s]", current_test.zone_names[i].mzn_name, kr, mach_error_string(kr));
264 }
265 kr = mach_zone_info_for_largest_zone(mach_host_self(), &largest_zone_name, &largest_zone_info);
266 T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "mach_zone_info_for_largest_zone returned %d [%s]", kr, mach_error_string(kr));
267
268 num_calls++;
269 if (num_calls % 5 != 0) {
270 return;
271 }
272
273 /* Print out size and element count for zones relevant to the test */
274 for (i = 0; i < current_test.num_zones; i++) {
275 print_zone_info(&(current_test.zone_names[i]), &(zone_info_array[i]));
276 }
277 }
278
279 static bool
280 vme_zone_compares_to_vm_objects(void)
281 {
282 int i;
283 uint64_t vm_object_element_count = 0, vm_map_entry_element_count = 0;
284
285 T_LOG("Comparing element counts of \"VM map entries\" and \"vm objects\" zones");
286 for (i = 0; i < current_test.num_zones; i++) {
287 if (!strcmp(current_test.zone_names[i].mzn_name, VME_ZONE)) {
288 vm_map_entry_element_count = zone_info_array[i].mzi_count;
289 } else if (!strcmp(current_test.zone_names[i].mzn_name, VMOBJECTS_ZONE)) {
290 vm_object_element_count = zone_info_array[i].mzi_count;
291 }
292 print_zone_info(&(current_test.zone_names[i]), &(zone_info_array[i]));
293 }
294
295 T_LOG("# VM map entries as percentage of # vm objects = %llu", (vm_map_entry_element_count * 100) / vm_object_element_count);
296 if (vm_map_entry_element_count >= ((vm_object_element_count * VMENTRY_TO_VMOBJECT_COMPARISON_RATIO) / 100)) {
297 T_LOG("Number of VM map entries is comparable to vm objects\n\n");
298 return true;
299 }
300 T_LOG("Number of VM map entries is NOT comparable to vm objects\n\n");
301 return false;
302 }
303
304 static bool
305 verify_generic_jetsam_criteria(void)
306 {
307 T_LOG("Largest zone info");
308 print_zone_info(&largest_zone_name, &largest_zone_info);
309
310 /* If VM map entries is not the largest zone */
311 if (strcmp(largest_zone_name.mzn_name, VME_ZONE)) {
312 /* If vm objects is the largest zone and the VM map entries zone had comparable # of elements, return false */
313 if (!strcmp(largest_zone_name.mzn_name, VMOBJECTS_ZONE) && vme_zone_compares_to_vm_objects()) {
314 return false;
315 }
316 return true;
317 }
318 return false;
319 }
320
321 static void
322 begin_test_teardown(void)
323 {
324 int ret, old_limit = 95;
325
326 /*
327 * Restore kern.zone_map_jetsam_limit to the default high value, to prevent further jetsams.
328 * We should change the value of old_limit if ZONE_MAP_JETSAM_LIMIT_DEFAULT changes in the kernel.
329 * We don't have a way to capture what the original value was before the test, because the
330 * T_META_SYSCTL_INT macro will have changed the value before the test starts running.
331 */
332 ret = sysctlbyname("kern.zone_map_jetsam_limit", NULL, NULL, &old_limit, sizeof(old_limit));
333 T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "sysctl kern.zone_map_jetsam_limit failed");
334 T_LOG("kern.zone_map_jetsam_limit set to %d%%", old_limit);
335
336
337 /* End ktrace session */
338 if (session != NULL) {
339 T_LOG("Ending ktrace session...");
340 ktrace_end(session, 1);
341 }
342
343 dispatch_sync(dq_spawn, ^{
344 T_LOG("Cancelling dispatch sources...");
345
346 /* Disable the timer that queries and prints zone info periodically */
347 if (ds_timer != NULL) {
348 dispatch_source_cancel(ds_timer);
349 }
350
351 /* Disable signal handler that spawns child processes */
352 if (ds_signal != NULL) {
353 /*
354 * No need for a dispatch_source_cancel_and_wait here.
355 * We're queueing this on the spawn queue, so no further
356 * processes will be spawned after the source is cancelled.
357 */
358 dispatch_source_cancel(ds_signal);
359 }
360 });
361 }
362
363 static void
364 cleanup_and_end_test(void)
365 {
366 int i;
367
368 /*
369 * The atend handler executes on a different dispatch queue.
370 * We want to do the cleanup only once.
371 */
372 pthread_mutex_lock(&test_mtx);
373 if (test_ending) {
374 pthread_mutex_unlock(&test_mtx);
375 return;
376 }
377 test_ending = TRUE;
378 pthread_mutex_unlock(&test_mtx);
379
380 dispatch_async(dq_spawn, ^{
381 /*
382 * If the test succeeds, we will call dispatch_source_cancel twice, which is fine since
383 * the operation is idempotent. Just make sure to not drop all references to the dispatch sources
384 * (in this case we're not, we have globals holding references to them), or we can end up with
385 * use-after-frees which would be a problem.
386 */
387 /* Disable the timer that queries and prints zone info periodically */
388 if (ds_timer != NULL) {
389 dispatch_source_cancel(ds_timer);
390 }
391
392 /* Disable signal handler that spawns child processes */
393 if (ds_signal != NULL) {
394 dispatch_source_cancel(ds_signal);
395 }
396 });
397
398 pthread_mutex_lock(&test_mtx);
399 T_LOG("Number of processes spawned: %d", num_children);
400 T_LOG("Killing child processes...");
401
402 /* Kill all the child processes that were spawned */
403 for (i = 0; i < num_children; i++) {
404 pid_t pid = child_pids[i];
405 int status = 0;
406
407 /*
408 * Kill and wait for each child to exit
409 * Without this we were seeing hw_lock_bit timeouts in BATS.
410 */
411 kill(pid, SIGKILL);
412 pthread_mutex_unlock(&test_mtx);
413 if (waitpid(pid, &status, 0) < 0) {
414 T_LOG("waitpid returned status %d", status);
415 }
416 pthread_mutex_lock(&test_mtx);
417 }
418 sleep(1);
419
420 /* Force zone_gc before starting test for another zone or exiting */
421 mach_zone_force_gc(mach_host_self());
422
423 /* End ktrace session */
424 if (session != NULL) {
425 ktrace_end(session, 1);
426 }
427
428 if (current_test.num_zones > 0) {
429 T_LOG("Relevant zone info at the end of the test:");
430 for (i = 0; i < current_test.num_zones; i++) {
431 print_zone_info(&(current_test.zone_names[i]), &(zone_info_array[i]));
432 }
433 }
434 }
435
436 static void
437 setup_ktrace_session(void)
438 {
439 int ret = 0;
440
441 T_LOG("Setting up ktrace session...");
442 session = ktrace_session_create();
443 T_QUIET; T_ASSERT_NOTNULL(session, "ktrace_session_create");
444
445 ktrace_set_interactive(session);
446
447 ktrace_set_dropped_events_handler(session, ^{
448 T_FAIL("Dropped ktrace events; might have missed an expected jetsam event. Terminating early.");
449 });
450
451 ktrace_set_completion_handler(session, ^{
452 ktrace_session_destroy(session);
453 T_END;
454 });
455
456 /* Listen for memorystatus_do_kill trace events */
457 ret = ktrace_events_single(session, (BSDDBG_CODE(DBG_BSD_MEMSTAT, BSD_MEMSTAT_DO_KILL)), ^(ktrace_event_t event) {
458 int i;
459 bool received_jetsam_event = false;
460
461 /*
462 * libktrace does not support DBG_FUNC_START/END in the event filter. It simply ignores it.
463 * So we need to explicitly check for the end event (a successful jetsam kill) here,
464 * instead of passing in ((BSDDBG_CODE(DBG_BSD_MEMSTAT, BSD_MEMSTAT_DO_KILL)) | DBG_FUNC_START).
465 */
466 if (!(event->debugid & DBG_FUNC_START)) {
467 return;
468 }
469
470 /* Check for zone-map-exhaustion jetsam. */
471 if (event->arg2 == kMemorystatusKilledZoneMapExhaustion) {
472 begin_test_teardown();
473 T_LOG("[memorystatus_do_kill] jetsam reason: zone-map-exhaustion, pid: %d\n\n", (int)event->arg1);
474 if (current_test.test_index == VME_ZONE_TEST || current_test.test_index == VM_OBJECTS_ZONE_TEST) {
475 /*
476 * For the VM map entries zone we try to kill the leaking process.
477 * Verify that we jetsammed one of the processes we spawned.
478 *
479 * For the vm objects zone we pick the leaking process via the VM map entries
480 * zone, if the number of vm objects and VM map entries are comparable.
481 * The test simulates this scenario, we should see a targeted jetsam for the
482 * vm objects zone too.
483 */
484 pthread_mutex_lock(&test_mtx);
485 for (i = 0; i < num_children; i++) {
486 if (child_pids[i] == (pid_t)event->arg1) {
487 received_jetsam_event = true;
488 T_LOG("Received jetsam event for a child");
489 break;
490 }
491 }
492 pthread_mutex_unlock(&test_mtx);
493 /*
494 * If we didn't see a targeted jetsam, verify that the largest zone actually
495 * fulfilled the criteria for generic jetsams.
496 */
497 if (!received_jetsam_event && verify_generic_jetsam_criteria()) {
498 received_jetsam_event = true;
499 T_LOG("Did not receive jetsam event for a child, but generic jetsam criteria holds");
500 }
501 } else {
502 received_jetsam_event = true;
503 T_LOG("Received generic jetsam event");
504 }
505
506 T_QUIET; T_ASSERT_TRUE(received_jetsam_event, "Jetsam event not as expected");
507 } else {
508 /*
509 * The test relies on the children being able to send a signal to the parent, to continue spawning new processes
510 * that leak more zone memory. If a child is jetsammed for some other reason, the parent can get stuck waiting for
511 * a signal from the child, never being able to make progress (We spawn only a single process at a time to rate-limit
512 * the zone memory bloat.). If this happens, the test eventually times out. So if a child is jetsammed for some
513 * reason other than zone-map-exhaustion, end the test early.
514 *
515 * This typically happens when we end up triggering vm-pageshortage jetsams before zone-map-exhaustion jetsams.
516 * Lowering the zone_map_jetsam_limit if the zone map size was initially low should help with this too.
517 * See sysctlbyname("kern.zone_map_jetsam_limit"...) in run_test() below.
518 */
519 pthread_mutex_lock(&test_mtx);
520 for (i = 0; i < num_children; i++) {
521 if (child_pids[i] == (pid_t)event->arg1) {
522 begin_test_teardown();
523 T_PASS("Child pid %d was jetsammed due to reason %d. Terminating early.",
524 (int)event->arg1, (int)event->arg2);
525 }
526 }
527 pthread_mutex_unlock(&test_mtx);
528 }
529 });
530 T_QUIET; T_ASSERT_POSIX_ZERO(ret, "ktrace_events_single");
531
532 ret = ktrace_start(session, dispatch_get_main_queue());
533 T_QUIET; T_ASSERT_POSIX_ZERO(ret, "ktrace_start");
534 }
535
536 static int
537 query_zone_map_size(void)
538 {
539 int ret;
540 uint64_t zstats[2];
541 size_t zstats_size = sizeof(zstats);
542
543 ret = sysctlbyname("kern.zone_map_size_and_capacity", &zstats, &zstats_size, NULL, 0);
544 T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "sysctl kern.zone_map_size_and_capacity failed");
545
546 T_LOG("Zone map capacity: %-30lldZone map size: %lld [%lld%% full]", zstats[1], zstats[0], (zstats[0] * 100) / zstats[1]);
547
548 #if (TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR)
549 int memstat_level;
550 size_t memstat_level_size = sizeof(memstat_level);
551 ret = sysctlbyname("kern.memorystatus_level", &memstat_level, &memstat_level_size, NULL, 0);
552 T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "sysctl kern.memorystatus_level failed");
553
554 T_LOG("kern.memorystatus_level = %d%%", memstat_level);
555 #endif
556 return (int)(zstats[0] * 100 / zstats[1]);
557 }
558
559 static void
560 spawn_child_process(void)
561 {
562 pid_t pid = -1;
563 char helper_func[50];
564 char *launch_tool_args[4];
565
566 pthread_mutex_lock(&test_mtx);
567 if (!test_ending) {
568 if (num_children == MAX_CHILD_PROCS) {
569 pthread_mutex_unlock(&test_mtx);
570 T_ASSERT_FAIL("Spawned too many children. Aborting test");
571 /* not reached */
572 }
573
574 strlcpy(helper_func, current_test.helper_func, sizeof(helper_func));
575 launch_tool_args[0] = testpath;
576 launch_tool_args[1] = "-n";
577 launch_tool_args[2] = helper_func;
578 launch_tool_args[3] = NULL;
579
580 /* Spawn the child process */
581 int rc = dt_launch_tool(&pid, launch_tool_args, false, NULL, NULL);
582 if (rc != 0) {
583 T_LOG("dt_launch tool returned %d with error code %d", rc, errno);
584 }
585 T_QUIET; T_ASSERT_POSIX_SUCCESS(pid, "dt_launch_tool");
586
587 child_pids[num_children++] = pid;
588 }
589 pthread_mutex_unlock(&test_mtx);
590 }
591
592 static void
593 run_test(void)
594 {
595 uint64_t mem;
596 uint32_t testpath_buf_size, pages;
597 int ret, dev, pgsz, initial_zone_occupancy, old_limit, new_limit = 0;
598 size_t sysctl_size;
599
600 T_ATEND(cleanup_and_end_test);
601 T_SETUPBEGIN;
602
603 main_start = time(NULL);
604 dev = 0;
605 sysctl_size = sizeof(dev);
606 ret = sysctlbyname("kern.development", &dev, &sysctl_size, NULL, 0);
607 T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "sysctl kern.development failed");
608 if (dev == 0) {
609 T_SKIP("Skipping test on release kernel");
610 }
611
612 testpath_buf_size = sizeof(testpath);
613 ret = _NSGetExecutablePath(testpath, &testpath_buf_size);
614 T_QUIET; T_ASSERT_POSIX_ZERO(ret, "_NSGetExecutablePath");
615 T_LOG("Executable path: %s", testpath);
616
617 sysctl_size = sizeof(mem);
618 ret = sysctlbyname("hw.memsize", &mem, &sysctl_size, NULL, 0);
619 T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "sysctl hw.memsize failed");
620 T_LOG("hw.memsize: %llu", mem);
621
622 sysctl_size = sizeof(pgsz);
623 ret = sysctlbyname("vm.pagesize", &pgsz, &sysctl_size, NULL, 0);
624 T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "sysctl vm.pagesize failed");
625 T_LOG("vm.pagesize: %d", pgsz);
626
627 sysctl_size = sizeof(pages);
628 ret = sysctlbyname("vm.pages", &pages, &sysctl_size, NULL, 0);
629 T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "sysctl vm.pages failed");
630 T_LOG("vm.pages: %d", pages);
631
632 sysctl_size = sizeof(old_limit);
633 ret = sysctlbyname("kern.zone_map_jetsam_limit", &old_limit, &sysctl_size, NULL, 0);
634 T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "sysctl kern.zone_map_jetsam_limit failed");
635 T_LOG("kern.zone_map_jetsam_limit: %d", old_limit);
636
637 initial_zone_occupancy = query_zone_map_size();
638
639 /* On large memory systems, set the zone_map jetsam limit lower so we can hit it without timing out. */
640 if (mem > (uint64_t)LARGE_MEM_GB * 1024 * 1024 * 1024) {
641 new_limit = LARGE_MEM_JETSAM_LIMIT;
642 }
643
644 /*
645 * If we start out with the zone map < 5% full, aim for 10% as the limit, so we don't time out.
646 * For anything else aim for 2x the initial size, capped by whatever value was set by T_META_SYSCTL_INT,
647 * or LARGE_MEM_JETSAM_LIMIT for large memory systems.
648 */
649 if (initial_zone_occupancy < 5) {
650 new_limit = JETSAM_LIMIT_LOWEST;
651 } else {
652 new_limit = initial_zone_occupancy * 2;
653 }
654
655 if (new_limit > 0 && new_limit < old_limit) {
656 /*
657 * We should be fine messing with the zone_map_jetsam_limit here, i.e. outside of T_META_SYSCTL_INT.
658 * When the test ends, T_META_SYSCTL_INT will restore the zone_map_jetsam_limit to what it was
659 * before the test anyway.
660 */
661 ret = sysctlbyname("kern.zone_map_jetsam_limit", NULL, NULL, &new_limit, sizeof(new_limit));
662 T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "sysctl kern.zone_map_jetsam_limit failed");
663 T_LOG("kern.zone_map_jetsam_limit set to %d%%", new_limit);
664 }
665
666 zone_info_array = (mach_zone_info_array_t) calloc((unsigned long)current_test.num_zones, sizeof *zone_info_array);
667
668 /*
669 * If the timeout specified by T_META_TIMEOUT is hit, the atend handler does not get called.
670 * So we're queueing a dispatch block to fire after TIMEOUT_SECS seconds, so we can exit cleanly.
671 */
672 dispatch_after(dispatch_time(DISPATCH_TIME_NOW, TIMEOUT_SECS * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
673 T_ASSERT_FAIL("Timed out after %d seconds", TIMEOUT_SECS);
674 });
675
676 /*
677 * Create a dispatch source for the signal SIGUSR1. When a child is done allocating zone memory, it
678 * sends SIGUSR1 to the parent. Only then does the parent spawn another child. This prevents us from
679 * spawning many children at once and creating a lot of memory pressure.
680 */
681 signal(SIGUSR1, SIG_IGN);
682 dq_spawn = dispatch_queue_create("spawn_queue", DISPATCH_QUEUE_SERIAL);
683 ds_signal = dispatch_source_create(DISPATCH_SOURCE_TYPE_SIGNAL, SIGUSR1, 0, dq_spawn);
684 T_QUIET; T_ASSERT_NOTNULL(ds_signal, "dispatch_source_create: signal");
685
686 dispatch_source_set_event_handler(ds_signal, ^{
687 (void)query_zone_map_size();
688
689 /* Wait a few seconds before spawning another child. Keeps us from allocating too aggressively */
690 sleep(5);
691 spawn_child_process();
692 });
693 dispatch_activate(ds_signal);
694
695 /* Timer to query jetsam-relevant zone info every second. Print it every 5 seconds. */
696 ds_timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_queue_create("timer_queue", NULL));
697 T_QUIET; T_ASSERT_NOTNULL(ds_timer, "dispatch_source_create: timer");
698 dispatch_source_set_timer(ds_timer, dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC), NSEC_PER_SEC, 0);
699
700 dispatch_source_set_event_handler(ds_timer, ^{
701 query_zone_info();
702 });
703 dispatch_activate(ds_timer);
704
705 /* Set up a ktrace session to listen for jetsam events */
706 setup_ktrace_session();
707
708 T_SETUPEND;
709
710 /* Spawn the first child process */
711 T_LOG("Spawning child processes to allocate zone memory...\n\n");
712 spawn_child_process();
713
714 dispatch_main();
715 }
716
717 static void
718 move_to_idle_band(void)
719 {
720 memorystatus_priority_properties_t props;
721
722 /*
723 * We want to move the processes we spawn into the idle band, so that jetsam can target them first.
724 * This prevents other important BATS tasks from getting killed, specially in LTE where we have very few
725 * processes running.
726 *
727 * This is only needed for tests which (are likely to) lead us down the generic jetsam path.
728 */
729 props.priority = JETSAM_PRIORITY_IDLE;
730 props.user_data = 0;
731
732 if (memorystatus_control(MEMORYSTATUS_CMD_SET_PRIORITY_PROPERTIES, getpid(), 0, &props, sizeof(props))) {
733 printf("memorystatus call to change jetsam priority failed\n");
734 exit(-1);
735 }
736 }
737
738 T_HELPER_DECL(allocate_vm_regions, "allocates VM regions")
739 {
740 move_to_idle_band();
741 allocate_vm_stuff(REGIONS);
742 }
743
744 T_HELPER_DECL(allocate_vm_objects, "allocates VM objects and VM regions")
745 {
746 move_to_idle_band();
747 allocate_vm_stuff(OBJECTS);
748 }
749
750 T_HELPER_DECL(allocate_from_generic_zone, "allocates from a generic zone")
751 {
752 move_to_idle_band();
753 allocate_from_generic_zone();
754 }
755
756 /*
757 * T_META_SYSCTL_INT(ZONEMAP_JETSAM_LIMIT_SYSCTL) changes the zone_map_jetsam_limit to a
758 * lower value, so that the test can complete faster.
759 * The test allocates zone memory pretty aggressively which can cause the system to panic
760 * if the jetsam limit is quite high; a lower value keeps us from panicking.
761 */
762 T_DECL( memorystatus_vme_zone_test,
763 "allocates elements from the VM map entries zone, verifies zone-map-exhaustion jetsams",
764 T_META_ASROOT(true),
765 T_META_TIMEOUT(1800),
766 /* T_META_LTEPHASE(LTE_POSTINIT),
767 */
768 T_META_SYSCTL_INT(ZONEMAP_JETSAM_LIMIT_SYSCTL))
769 {
770 current_test = (test_config_struct) {
771 .test_index = VME_ZONE_TEST,
772 .helper_func = VME_ZONE_TEST_OPT,
773 .num_zones = 1,
774 .zone_names = (mach_zone_name_t[]){
775 { .mzn_name = VME_ZONE }
776 }
777 };
778 run_test();
779 }
780
781 T_DECL( memorystatus_vm_objects_zone_test,
782 "allocates elements from the VM objects and the VM map entries zones, verifies zone-map-exhaustion jetsams",
783 T_META_ASROOT(true),
784 T_META_TIMEOUT(1800),
785 /* T_META_LTEPHASE(LTE_POSTINIT),
786 */
787 T_META_SYSCTL_INT(ZONEMAP_JETSAM_LIMIT_SYSCTL))
788 {
789 current_test = (test_config_struct) {
790 .test_index = VM_OBJECTS_ZONE_TEST,
791 .helper_func = VM_OBJECTS_ZONE_TEST_OPT,
792 .num_zones = 2,
793 .zone_names = (mach_zone_name_t[]){
794 { .mzn_name = VME_ZONE },
795 { .mzn_name = VMOBJECTS_ZONE}
796 }
797 };
798 run_test();
799 }
800
801 T_DECL( memorystatus_generic_zone_test,
802 "allocates elements from a zone that doesn't have an optimized jetsam path, verifies zone-map-exhaustion jetsams",
803 T_META_ASROOT(true),
804 T_META_TIMEOUT(1800),
805 /* T_META_LTEPHASE(LTE_POSTINIT),
806 */
807 T_META_SYSCTL_INT(ZONEMAP_JETSAM_LIMIT_SYSCTL))
808 {
809 current_test = (test_config_struct) {
810 .test_index = GENERIC_ZONE_TEST,
811 .helper_func = GENERIC_ZONE_TEST_OPT,
812 .num_zones = 0,
813 .zone_names = NULL
814 };
815 run_test();
816 }