]> git.saurik.com Git - apple/xnu.git/blame_incremental - tools/tests/darwintests/memorystatus_zone_test.c
xnu-4570.41.2.tar.gz
[apple/xnu.git] / tools / tests / darwintests / memorystatus_zone_test.c
... / ...
CommitLineData
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-o/dyld.h>
6#include <sys/sysctl.h>
7#include <sys/kdebug.h>
8#include <sys/mman.h>
9#include <sys/kern_memorystatus.h>
10#include <ktrace/session.h>
11#include <dispatch/private.h>
12
13#ifdef T_NAMESPACE
14#undef T_NAMESPACE
15#endif
16#include <darwintest.h>
17#include <darwintest_utils.h>
18
19T_GLOBAL_META(
20 T_META_NAMESPACE("xnu.vm"),
21 T_META_CHECK_LEAKS(false)
22);
23
24#define TIMEOUT_SECS 1500
25
26#if TARGET_OS_EMBEDDED
27#define ALLOCATION_SIZE_VM_REGION (16*1024) /* 16 KB */
28#define ALLOCATION_SIZE_VM_OBJECT ALLOCATION_SIZE_VM_REGION
29#else
30#define ALLOCATION_SIZE_VM_REGION (1024*1024*100) /* 100 MB */
31#define ALLOCATION_SIZE_VM_OBJECT (16*1024) /* 16 KB */
32#endif
33#define MAX_CHILD_PROCS 100
34
35#define ZONEMAP_JETSAM_LIMIT_SYSCTL "kern.zone_map_jetsam_limit=60"
36
37#define VME_ZONE_TEST_OPT "allocate_vm_regions"
38#define VM_OBJECTS_ZONE_TEST_OPT "allocate_vm_objects"
39#define GENERIC_ZONE_TEST_OPT "allocate_from_generic_zone"
40
41#define VM_TAG1 100
42#define VM_TAG2 101
43
44enum {
45 VME_ZONE_TEST = 0,
46 VM_OBJECTS_ZONE_TEST,
47 GENERIC_ZONE_TEST,
48};
49
50static int current_test_index = 0;
51static int num_children = 0;
52static bool test_ending = false;
53static bool within_dispatch_source_handler = false;
54static dispatch_source_t ds_signal = NULL;
55static ktrace_session_t session = NULL;
56
57static char testpath[PATH_MAX];
58static pid_t child_pids[MAX_CHILD_PROCS];
59static pthread_mutex_t test_ending_mtx;
60
61static void allocate_vm_regions(void);
62static void allocate_vm_objects(void);
63static void allocate_from_generic_zone(void);
64static void cleanup_and_end_test(void);
65static void setup_ktrace_session(void);
66static void spawn_child_process(void);
67static void run_test_for_zone(int index);
68
69extern void mach_zone_force_gc(host_t host);
70
71static void allocate_vm_regions(void)
72{
73 uint64_t alloc_size = ALLOCATION_SIZE_VM_REGION, i = 0;
74
75 printf("[%d] Allocating VM regions, each of size %lld KB\n", getpid(), (alloc_size>>10));
76 for (i = 0; ; i++) {
77 mach_vm_address_t addr = (mach_vm_address_t)NULL;
78
79 /* Alternate VM tags between consecutive regions to prevent coalescing */
80 int flags = VM_MAKE_TAG((i % 2)? VM_TAG1: VM_TAG2) | VM_FLAGS_ANYWHERE;
81
82 if ((mach_vm_allocate(mach_task_self(), &addr, (mach_vm_size_t)alloc_size, flags)) != KERN_SUCCESS) {
83 break;
84 }
85 }
86 printf("[%d] Number of allocations: %lld\n", getpid(), i);
87
88 /* Signal to the parent that we're done allocating */
89 kill(getppid(), SIGUSR1);
90
91 while (1) {
92 pause();
93 }
94}
95
96static void allocate_vm_objects(void)
97{
98 uint64_t alloc_size = ALLOCATION_SIZE_VM_OBJECT, i = 0;
99
100 printf("[%d] Allocating VM regions, each of size %lld KB, each backed by a VM object\n", getpid(), (alloc_size>>10));
101 for (i = 0; ; i++) {
102 mach_vm_address_t addr = (mach_vm_address_t)NULL;
103
104 /* Alternate VM tags between consecutive regions to prevent coalescing */
105 int flags = VM_MAKE_TAG((i % 2)? VM_TAG1: VM_TAG2) | VM_FLAGS_ANYWHERE;
106
107 if ((mach_vm_allocate(mach_task_self(), &addr, (mach_vm_size_t)alloc_size, flags)) != KERN_SUCCESS) {
108 break;
109 }
110 /* Touch the region so the VM object can actually be created */
111 *((int *)addr) = 0;
112 /* OK to free this page. Keeps us from holding a lot of dirty pages */
113 madvise((void *)addr, (size_t)alloc_size, MADV_FREE);
114 }
115 printf("[%d] Number of allocations: %lld\n", getpid(), i);
116
117 /* Signal to the parent that we're done allocating */
118 kill(getppid(), SIGUSR1);
119
120 while (1) {
121 pause();
122 }
123}
124
125static void allocate_from_generic_zone(void)
126{
127 uint64_t i = 0;
128
129 printf("[%d] Allocating mach_ports\n", getpid());
130 for (i = 0; ; i++) {
131 mach_port_t port;
132
133 if ((mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, &port)) != KERN_SUCCESS) {
134 break;
135 }
136 }
137 printf("[%d] Number of allocations: %lld\n", getpid(), i);
138
139 /* Signal to the parent that we're done allocating */
140 kill(getppid(), SIGUSR1);
141
142 while (1) {
143 pause();
144 }
145}
146
147static void cleanup_and_end_test(void)
148{
149 int i;
150
151 /*
152 * The atend handler executes on a different dispatch queue.
153 * We want to do the cleanup only once.
154 */
155 pthread_mutex_lock(&test_ending_mtx);
156 if (test_ending) {
157 pthread_mutex_unlock(&test_ending_mtx);
158 return;
159 }
160 test_ending = true;
161 pthread_mutex_unlock(&test_ending_mtx);
162
163 T_LOG("Number of processes spawned: %d", num_children);
164 T_LOG("Cleaning up...");
165
166 /* Disable signal handler that spawns child processes, only if we're not in the event handler's context */
167 if (ds_signal != NULL && !within_dispatch_source_handler) {
168 dispatch_source_cancel_and_wait(ds_signal);
169 }
170
171 /* Kill all the child processes that were spawned */
172 for (i = 0; i < num_children; i++) {
173 kill(child_pids[i], SIGKILL);
174 }
175 for (i = 0; i < num_children; i++) {
176 int status = 0;
177 if (waitpid(child_pids[i], &status, 0) < 0) {
178 T_LOG("waitpid returned status %d", status);
179 }
180 }
181 sleep(1);
182
183 /* Force zone_gc before starting test for another zone or exiting */
184 mach_zone_force_gc(mach_host_self());
185
186 /* End ktrace session */
187 if (session != NULL) {
188 ktrace_end(session, 1);
189 }
190}
191
192static void setup_ktrace_session(void)
193{
194 int ret = 0;
195
196 T_LOG("Setting up ktrace session...");
197 session = ktrace_session_create();
198 T_QUIET; T_ASSERT_NOTNULL(session, "ktrace_session_create");
199
200 ktrace_set_completion_handler(session, ^{
201 ktrace_session_destroy(session);
202 T_END;
203 });
204
205 /* Listen for memorystatus_do_kill trace events */
206 ret = ktrace_events_single(session, (BSDDBG_CODE(DBG_BSD_MEMSTAT, BSD_MEMSTAT_DO_KILL)) | DBG_FUNC_END, ^(ktrace_event_t event) {
207 int i;
208 bool received_jetsam_event = false;
209
210 /* We don't care about jetsams for any other reason except zone-map-exhaustion */
211 if (event->arg2 == kMemorystatusKilledZoneMapExhaustion) {
212 T_LOG("[memorystatus_do_kill] jetsam reason: zone-map-exhaustion, pid: %lu", event->arg1);
213 if (current_test_index == VME_ZONE_TEST || current_test_index == VM_OBJECTS_ZONE_TEST) {
214 /*
215 * For the VM map entries zone we try to kill the leaking process.
216 * Verify that we jetsammed one of the processes we spawned.
217 */
218 for (i = 0; i < num_children; i++) {
219 if (child_pids[i] == (pid_t)event->arg1) {
220 received_jetsam_event = true;
221 break;
222 }
223 }
224 } else {
225 received_jetsam_event = true;
226 }
227
228 T_ASSERT_TRUE(received_jetsam_event, "Received jetsam event as expected");
229 cleanup_and_end_test();
230 }
231 });
232 T_QUIET; T_ASSERT_POSIX_ZERO(ret, "ktrace_events_single");
233
234 ret = ktrace_start(session, dispatch_get_main_queue());
235 T_QUIET; T_ASSERT_POSIX_ZERO(ret, "ktrace_start");
236}
237
238static void spawn_child_process(void)
239{
240 pid_t pid = -1;
241 char *launch_tool_args[4];
242 within_dispatch_source_handler = true;
243
244 T_QUIET; T_ASSERT_LT(num_children, MAX_CHILD_PROCS, "Spawned %d children. Timing out...", MAX_CHILD_PROCS);
245
246 launch_tool_args[0] = testpath;
247 launch_tool_args[1] = "-n";
248 launch_tool_args[3] = NULL;
249
250 if (current_test_index == VME_ZONE_TEST) {
251 launch_tool_args[2] = VME_ZONE_TEST_OPT;
252 } else if (current_test_index == VM_OBJECTS_ZONE_TEST) {
253 launch_tool_args[2] = VM_OBJECTS_ZONE_TEST_OPT;
254 } else if (current_test_index == GENERIC_ZONE_TEST) {
255 launch_tool_args[2] = GENERIC_ZONE_TEST_OPT;
256 }
257
258 /* Spawn the child process */
259 int rc = dt_launch_tool(&pid, launch_tool_args, false, NULL, NULL);
260 if (rc != 0) {
261 T_LOG("dt_launch tool returned %d with error code %d", rc, errno);
262 }
263 T_QUIET; T_ASSERT_POSIX_SUCCESS(pid, "dt_launch_tool");
264
265 child_pids[num_children++] = pid;
266 within_dispatch_source_handler = false;
267}
268
269static void run_test_for_zone(int index)
270{
271 int ret, dev;
272 size_t dev_size = sizeof(dev);
273 uint32_t testpath_buf_size = sizeof(testpath);
274
275 T_ATEND(cleanup_and_end_test);
276 T_SETUPBEGIN;
277
278 current_test_index = index;
279
280 ret = sysctlbyname("kern.development", &dev, &dev_size, NULL, 0);
281 T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "sysctl kern.development failed");
282 if (dev == 0) {
283 T_SKIP("Skipping test on release kernel");
284 }
285
286 ret = _NSGetExecutablePath(testpath, &testpath_buf_size);
287 T_QUIET; T_ASSERT_POSIX_ZERO(ret, "_NSGetExecutablePath");
288 T_LOG("Executable path: %s", testpath);
289
290 /*
291 * If the timeout specified by T_META_TIMEOUT is hit, the atend handler does not get called.
292 * So we're queueing a dispatch block to fire after TIMEOUT_SECS seconds, so we can exit cleanly.
293 */
294 dispatch_after(dispatch_time(DISPATCH_TIME_NOW, TIMEOUT_SECS * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
295 T_ASSERT_FAIL("Timed out after %d seconds", TIMEOUT_SECS);
296 });
297
298 /*
299 * Create a dispatch source for the signal SIGUSR1. When a child is done allocating zone memory, it
300 * sends SIGUSR1 to the parent. Only then does the parent spawn another child. This prevents us from
301 * spawning many children at once and creating a lot of memory pressure.
302 */
303 signal(SIGUSR1, SIG_IGN);
304 ds_signal = dispatch_source_create(DISPATCH_SOURCE_TYPE_SIGNAL, SIGUSR1, 0, dispatch_get_main_queue());
305 T_QUIET; T_ASSERT_NOTNULL(ds_signal, "dispatch_source_create");
306
307 dispatch_source_set_event_handler(ds_signal, ^{
308 /* Wait a few seconds before spawning another child. Keeps us from allocating too aggressively */
309 sleep(5);
310 spawn_child_process();
311 });
312 dispatch_activate(ds_signal);
313
314 /* Set up a ktrace session to listen for jetsam events */
315 setup_ktrace_session();
316
317 T_SETUPEND;
318
319 /* Spawn the first child process */
320 T_LOG("Spawning child processes to allocate zone memory...\n\n");
321 spawn_child_process();
322
323 dispatch_main();
324}
325
326T_HELPER_DECL(allocate_vm_regions, "allocates VM regions")
327{
328 allocate_vm_regions();
329}
330
331T_HELPER_DECL(allocate_vm_objects, "allocates VM objects and VM regions")
332{
333 allocate_vm_objects();
334}
335
336T_HELPER_DECL(allocate_from_generic_zone, "allocates from a generic zone")
337{
338 memorystatus_priority_properties_t props;
339
340 /*
341 * We want to move the processes we spawn into the idle band, so that jetsam can target them first.
342 * This prevents other important BATS tasks from getting killed, specially in LTE where we have very few
343 * processes running.
344 */
345 props.priority = JETSAM_PRIORITY_IDLE;
346 props.user_data = 0;
347
348 if (memorystatus_control(MEMORYSTATUS_CMD_SET_PRIORITY_PROPERTIES, getpid(), 0, &props, sizeof(props))) {
349 printf("memorystatus call to change jetsam priority failed\n");
350 exit(-1);
351 }
352
353 allocate_from_generic_zone();
354}
355
356/*
357 * T_META_SYSCTL_INT(ZONEMAP_JETSAM_LIMIT_SYSCTL) changes the zone_map_jetsam_limit to a
358 * lower value, so that the test can complete faster.
359 * The test allocates zone memory pretty aggressively which can cause the system to panic
360 * if the jetsam limit is quite high; a lower value keeps us from panicking.
361 */
362T_DECL( memorystatus_vme_zone_test,
363 "allocates elements from the VM map entries zone, verifies zone-map-exhaustion jetsams",
364 T_META_ASROOT(true),
365 T_META_TIMEOUT(1800),
366/* T_META_LTEPHASE(LTE_POSTINIT),
367 */
368 T_META_SYSCTL_INT(ZONEMAP_JETSAM_LIMIT_SYSCTL))
369{
370 run_test_for_zone(VME_ZONE_TEST);
371}
372
373T_DECL( memorystatus_vm_objects_zone_test,
374 "allocates elements from the VM objects and the VM map entries zones, verifies zone-map-exhaustion jetsams",
375 T_META_ASROOT(true),
376 T_META_TIMEOUT(1800),
377/* T_META_LTEPHASE(LTE_POSTINIT),
378 */
379 T_META_SYSCTL_INT(ZONEMAP_JETSAM_LIMIT_SYSCTL))
380{
381 run_test_for_zone(VM_OBJECTS_ZONE_TEST);
382}
383
384T_DECL( memorystatus_generic_zone_test,
385 "allocates elements from a zone that doesn't have an optimized jetsam path, verifies zone-map-exhaustion jetsams",
386 T_META_ASROOT(true),
387 T_META_TIMEOUT(1800),
388/* T_META_LTEPHASE(LTE_POSTINIT),
389 */
390 T_META_SYSCTL_INT(ZONEMAP_JETSAM_LIMIT_SYSCTL))
391{
392 run_test_for_zone(GENERIC_ZONE_TEST);
393}