X-Git-Url: https://git.saurik.com/apple/xnu.git/blobdiff_plain/5ba3f43ea354af8ad55bea84372a2bc834d8757c..d26ffc64f583ab2d29df48f13518685602bc8832:/tools/tests/darwintests/memorystatus_zone_test.c diff --git a/tools/tests/darwintests/memorystatus_zone_test.c b/tools/tests/darwintests/memorystatus_zone_test.c index 1d0223a15..f652725bb 100644 --- a/tools/tests/darwintests/memorystatus_zone_test.c +++ b/tools/tests/darwintests/memorystatus_zone_test.c @@ -2,6 +2,7 @@ #include #include #include +#include #include #include #include @@ -21,25 +22,29 @@ T_GLOBAL_META( T_META_CHECK_LEAKS(false) ); -#define TIMEOUT_SECS 1500 +#define TIMEOUT_SECS 1500 #if TARGET_OS_EMBEDDED -#define ALLOCATION_SIZE_VM_REGION (16*1024) /* 16 KB */ -#define ALLOCATION_SIZE_VM_OBJECT ALLOCATION_SIZE_VM_REGION +#define ALLOCATION_SIZE_VM_REGION (16*1024) /* 16 KB */ +#define ALLOCATION_SIZE_VM_OBJECT ALLOCATION_SIZE_VM_REGION #else -#define ALLOCATION_SIZE_VM_REGION (1024*1024*100) /* 100 MB */ -#define ALLOCATION_SIZE_VM_OBJECT (16*1024) /* 16 KB */ +#define ALLOCATION_SIZE_VM_REGION (1024*1024*100) /* 100 MB */ +#define ALLOCATION_SIZE_VM_OBJECT (16*1024) /* 16 KB */ #endif -#define MAX_CHILD_PROCS 100 +#define MAX_CHILD_PROCS 100 -#define ZONEMAP_JETSAM_LIMIT_SYSCTL "kern.zone_map_jetsam_limit=60" +#define ZONEMAP_JETSAM_LIMIT_SYSCTL "kern.zone_map_jetsam_limit=60" -#define VME_ZONE_TEST_OPT "allocate_vm_regions" -#define VM_OBJECTS_ZONE_TEST_OPT "allocate_vm_objects" -#define GENERIC_ZONE_TEST_OPT "allocate_from_generic_zone" +#define VME_ZONE_TEST_OPT "allocate_vm_regions" +#define VM_OBJECTS_ZONE_TEST_OPT "allocate_vm_objects" +#define GENERIC_ZONE_TEST_OPT "allocate_from_generic_zone" -#define VM_TAG1 100 -#define VM_TAG2 101 +#define VME_ZONE "VM map entries" +#define VMOBJECTS_ZONE "vm objects" +#define VMENTRY_TO_VMOBJECT_COMPARISON_RATIO 98 + +#define VM_TAG1 100 +#define VM_TAG2 101 enum { VME_ZONE_TEST = 0, @@ -47,13 +52,26 @@ enum { GENERIC_ZONE_TEST, }; -static int current_test_index = 0; +typedef struct test_config_struct { + int test_index; + int num_zones; + const char *helper_func; + mach_zone_name_array_t zone_names; +} test_config_struct; + +static test_config_struct current_test; static int num_children = 0; static bool test_ending = false; -static bool within_dispatch_source_handler = false; +static bool within_dispatch_signal_handler = false; +static bool within_dispatch_timer_handler = false; static dispatch_source_t ds_signal = NULL; +static dispatch_source_t ds_timer = NULL; static ktrace_session_t session = NULL; +static mach_zone_info_array_t zone_info_array = NULL; +static mach_zone_name_t largest_zone_name; +static mach_zone_info_t largest_zone_info; + static char testpath[PATH_MAX]; static pid_t child_pids[MAX_CHILD_PROCS]; static pthread_mutex_t test_ending_mtx; @@ -64,9 +82,19 @@ static void allocate_from_generic_zone(void); static void cleanup_and_end_test(void); static void setup_ktrace_session(void); static void spawn_child_process(void); -static void run_test_for_zone(int index); +static void run_test(void); +static bool verify_generic_jetsam_criteria(void); +static bool vme_zone_compares_to_vm_objects(void); +static void print_zone_map_size(void); +static void query_zone_info(void); +static void print_zone_info(mach_zone_name_t *zn, mach_zone_info_t *zi); extern void mach_zone_force_gc(host_t host); +extern kern_return_t mach_zone_info_for_largest_zone( + host_priv_t host, + mach_zone_name_t *name, + mach_zone_info_t *info +); static void allocate_vm_regions(void) { @@ -144,6 +172,76 @@ static void allocate_from_generic_zone(void) } } +static void print_zone_info(mach_zone_name_t *zn, mach_zone_info_t *zi) +{ + T_LOG("ZONE NAME: %-35sSIZE: %-25lluELEMENTS: %llu", + zn->mzn_name, zi->mzi_cur_size, zi->mzi_count); +} + +static void query_zone_info(void) +{ + int i; + kern_return_t kr; + static uint64_t num_calls = 0; + + for (i = 0; i < current_test.num_zones; i++) { + kr = mach_zone_info_for_zone(mach_host_self(), current_test.zone_names[i], &(zone_info_array[i])); + 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)); + } + kr = mach_zone_info_for_largest_zone(mach_host_self(), &largest_zone_name, &largest_zone_info); + T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "mach_zone_info_for_largest_zone returned %d [%s]", kr, mach_error_string(kr)); + + num_calls++; + if (num_calls % 10 != 0) { + return; + } + + /* Print out size and element count for zones relevant to the test */ + for (i = 0; i < current_test.num_zones; i++) { + print_zone_info(&(current_test.zone_names[i]), &(zone_info_array[i])); + } +} + +static bool vme_zone_compares_to_vm_objects(void) +{ + int i; + uint64_t vm_object_element_count = 0, vm_map_entry_element_count = 0; + + T_LOG("Comparing element counts of \"VM map entries\" and \"vm objects\" zones"); + for (i = 0; i < current_test.num_zones; i++) { + if (!strcmp(current_test.zone_names[i].mzn_name, VME_ZONE)) { + vm_map_entry_element_count = zone_info_array[i].mzi_count; + } else if (!strcmp(current_test.zone_names[i].mzn_name, VMOBJECTS_ZONE)) { + vm_object_element_count = zone_info_array[i].mzi_count; + } + print_zone_info(&(current_test.zone_names[i]), &(zone_info_array[i])); + } + + T_LOG("# VM map entries as percentage of # vm objects = %llu", (vm_map_entry_element_count * 100)/ vm_object_element_count); + if (vm_map_entry_element_count >= ((vm_object_element_count * VMENTRY_TO_VMOBJECT_COMPARISON_RATIO) / 100)) { + T_LOG("Number of VM map entries is comparable to vm objects\n\n"); + return true; + } + T_LOG("Number of VM map entries is NOT comparable to vm objects\n\n"); + return false; +} + +static bool verify_generic_jetsam_criteria(void) +{ + T_LOG("Largest zone info"); + print_zone_info(&largest_zone_name, &largest_zone_info); + + /* If VM map entries is not the largest zone */ + if (strcmp(largest_zone_name.mzn_name, VME_ZONE)) { + /* If vm objects is the largest zone and the VM map entries zone had comparable # of elements, return false */ + if (!strcmp(largest_zone_name.mzn_name, VMOBJECTS_ZONE) && vme_zone_compares_to_vm_objects()) { + return false; + } + return true; + } + return false; +} + static void cleanup_and_end_test(void) { int i; @@ -163,8 +261,13 @@ static void cleanup_and_end_test(void) T_LOG("Number of processes spawned: %d", num_children); T_LOG("Cleaning up..."); + /* Disable the timer that queries and prints zone info periodically */ + if (ds_timer != NULL && !within_dispatch_timer_handler) { + dispatch_source_cancel(ds_timer); + } + /* Disable signal handler that spawns child processes, only if we're not in the event handler's context */ - if (ds_signal != NULL && !within_dispatch_source_handler) { + if (ds_signal != NULL && !within_dispatch_signal_handler) { dispatch_source_cancel_and_wait(ds_signal); } @@ -187,6 +290,10 @@ static void cleanup_and_end_test(void) if (session != NULL) { ktrace_end(session, 1); } + + for (i = 0; i < current_test.num_zones; i++) { + print_zone_info(&(current_test.zone_names[i]), &(zone_info_array[i])); + } } static void setup_ktrace_session(void) @@ -197,6 +304,8 @@ static void setup_ktrace_session(void) session = ktrace_session_create(); T_QUIET; T_ASSERT_NOTNULL(session, "ktrace_session_create"); + ktrace_set_interactive(session); + ktrace_set_completion_handler(session, ^{ ktrace_session_destroy(session); T_END; @@ -209,11 +318,17 @@ static void setup_ktrace_session(void) /* We don't care about jetsams for any other reason except zone-map-exhaustion */ if (event->arg2 == kMemorystatusKilledZoneMapExhaustion) { - T_LOG("[memorystatus_do_kill] jetsam reason: zone-map-exhaustion, pid: %lu", event->arg1); - if (current_test_index == VME_ZONE_TEST || current_test_index == VM_OBJECTS_ZONE_TEST) { + cleanup_and_end_test(); + T_LOG("[memorystatus_do_kill] jetsam reason: zone-map-exhaustion, pid: %lu\n\n", event->arg1); + if (current_test.test_index == VME_ZONE_TEST || current_test.test_index == VM_OBJECTS_ZONE_TEST) { /* * For the VM map entries zone we try to kill the leaking process. * Verify that we jetsammed one of the processes we spawned. + * + * For the vm objects zone we pick the leaking process via the VM map entries + * zone, if the number of vm objects and VM map entries are comparable. + * The test simulates this scenario, we should see a targeted jetsam for the + * vm objects zone too. */ for (i = 0; i < num_children; i++) { if (child_pids[i] == (pid_t)event->arg1) { @@ -221,12 +336,18 @@ static void setup_ktrace_session(void) break; } } + /* + * If we didn't see a targeted jetsam, verify that the largest zone actually + * fulfilled the criteria for generic jetsams. + */ + if (!received_jetsam_event && verify_generic_jetsam_criteria()) { + received_jetsam_event = true; + } } else { received_jetsam_event = true; } - T_ASSERT_TRUE(received_jetsam_event, "Received jetsam event as expected"); - cleanup_and_end_test(); + T_ASSERT_TRUE(received_jetsam_event, "Received zone-map-exhaustion jetsam event as expected"); } }); T_QUIET; T_ASSERT_POSIX_ZERO(ret, "ktrace_events_single"); @@ -235,26 +356,32 @@ static void setup_ktrace_session(void) T_QUIET; T_ASSERT_POSIX_ZERO(ret, "ktrace_start"); } +static void print_zone_map_size(void) +{ + int ret; + uint64_t zstats[2]; + size_t zstats_size = sizeof(zstats); + + ret = sysctlbyname("kern.zone_map_size_and_capacity", &zstats, &zstats_size, NULL, 0); + T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "sysctl kern.zone_map_size_and_capacity failed"); + + T_LOG("Zone map capacity: %-30lldZone map size: %lld [%lld%% full]", zstats[1], zstats[0], (zstats[0] * 100)/zstats[1]); +} + static void spawn_child_process(void) { pid_t pid = -1; + char helper_func[50]; char *launch_tool_args[4]; - within_dispatch_source_handler = true; T_QUIET; T_ASSERT_LT(num_children, MAX_CHILD_PROCS, "Spawned %d children. Timing out...", MAX_CHILD_PROCS); + strlcpy(helper_func, current_test.helper_func, sizeof(helper_func)); launch_tool_args[0] = testpath; launch_tool_args[1] = "-n"; + launch_tool_args[2] = helper_func; launch_tool_args[3] = NULL; - if (current_test_index == VME_ZONE_TEST) { - launch_tool_args[2] = VME_ZONE_TEST_OPT; - } else if (current_test_index == VM_OBJECTS_ZONE_TEST) { - launch_tool_args[2] = VM_OBJECTS_ZONE_TEST_OPT; - } else if (current_test_index == GENERIC_ZONE_TEST) { - launch_tool_args[2] = GENERIC_ZONE_TEST_OPT; - } - /* Spawn the child process */ int rc = dt_launch_tool(&pid, launch_tool_args, false, NULL, NULL); if (rc != 0) { @@ -263,30 +390,50 @@ static void spawn_child_process(void) T_QUIET; T_ASSERT_POSIX_SUCCESS(pid, "dt_launch_tool"); child_pids[num_children++] = pid; - within_dispatch_source_handler = false; } -static void run_test_for_zone(int index) +static void run_test(void) { - int ret, dev; - size_t dev_size = sizeof(dev); - uint32_t testpath_buf_size = sizeof(testpath); + uint64_t mem; + uint32_t testpath_buf_size, pages; + int ret, dev, pgsz; + size_t sysctl_size; T_ATEND(cleanup_and_end_test); T_SETUPBEGIN; - current_test_index = index; - - ret = sysctlbyname("kern.development", &dev, &dev_size, NULL, 0); + dev = 0; + sysctl_size = sizeof(dev); + ret = sysctlbyname("kern.development", &dev, &sysctl_size, NULL, 0); T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "sysctl kern.development failed"); if (dev == 0) { T_SKIP("Skipping test on release kernel"); } + testpath_buf_size = sizeof(testpath); ret = _NSGetExecutablePath(testpath, &testpath_buf_size); T_QUIET; T_ASSERT_POSIX_ZERO(ret, "_NSGetExecutablePath"); T_LOG("Executable path: %s", testpath); + sysctl_size = sizeof(mem); + ret = sysctlbyname("hw.memsize", &mem, &sysctl_size, NULL, 0); + T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "sysctl hw.memsize failed"); + T_LOG("hw.memsize: %llu", mem); + + sysctl_size = sizeof(pgsz); + ret = sysctlbyname("vm.pagesize", &pgsz, &sysctl_size, NULL, 0); + T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "sysctl vm.pagesize failed"); + T_LOG("vm.pagesize: %d", pgsz); + + sysctl_size = sizeof(pages); + ret = sysctlbyname("vm.pages", &pages, &sysctl_size, NULL, 0); + T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "sysctl vm.pages failed"); + T_LOG("vm.pages: %d", pages); + + zone_info_array = (mach_zone_info_array_t) calloc((unsigned long)current_test.num_zones, sizeof *zone_info_array); + + print_zone_map_size(); + /* * If the timeout specified by T_META_TIMEOUT is hit, the atend handler does not get called. * So we're queueing a dispatch block to fire after TIMEOUT_SECS seconds, so we can exit cleanly. @@ -302,15 +449,31 @@ static void run_test_for_zone(int index) */ signal(SIGUSR1, SIG_IGN); ds_signal = dispatch_source_create(DISPATCH_SOURCE_TYPE_SIGNAL, SIGUSR1, 0, dispatch_get_main_queue()); - T_QUIET; T_ASSERT_NOTNULL(ds_signal, "dispatch_source_create"); + T_QUIET; T_ASSERT_NOTNULL(ds_signal, "dispatch_source_create: signal"); dispatch_source_set_event_handler(ds_signal, ^{ + within_dispatch_signal_handler = true; + print_zone_map_size(); + /* Wait a few seconds before spawning another child. Keeps us from allocating too aggressively */ sleep(5); spawn_child_process(); + within_dispatch_signal_handler = false; }); dispatch_activate(ds_signal); + /* Timer to query jetsam-relevant zone info every second. Print it every 10 seconds. */ + ds_timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_queue_create("timer_queue", NULL)); + T_QUIET; T_ASSERT_NOTNULL(ds_timer, "dispatch_source_create: timer"); + dispatch_source_set_timer(ds_timer, dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC), NSEC_PER_SEC, 0); + + dispatch_source_set_event_handler(ds_timer, ^{ + within_dispatch_timer_handler = true; + query_zone_info(); + within_dispatch_timer_handler = false; + }); + dispatch_activate(ds_timer); + /* Set up a ktrace session to listen for jetsam events */ setup_ktrace_session(); @@ -323,17 +486,7 @@ static void run_test_for_zone(int index) dispatch_main(); } -T_HELPER_DECL(allocate_vm_regions, "allocates VM regions") -{ - allocate_vm_regions(); -} - -T_HELPER_DECL(allocate_vm_objects, "allocates VM objects and VM regions") -{ - allocate_vm_objects(); -} - -T_HELPER_DECL(allocate_from_generic_zone, "allocates from a generic zone") +static void move_to_idle_band(void) { memorystatus_priority_properties_t props; @@ -341,6 +494,8 @@ T_HELPER_DECL(allocate_from_generic_zone, "allocates from a generic zone") * We want to move the processes we spawn into the idle band, so that jetsam can target them first. * This prevents other important BATS tasks from getting killed, specially in LTE where we have very few * processes running. + * + * This is only needed for tests which (are likely to) lead us down the generic jetsam path. */ props.priority = JETSAM_PRIORITY_IDLE; props.user_data = 0; @@ -349,7 +504,22 @@ T_HELPER_DECL(allocate_from_generic_zone, "allocates from a generic zone") printf("memorystatus call to change jetsam priority failed\n"); exit(-1); } +} + +T_HELPER_DECL(allocate_vm_regions, "allocates VM regions") +{ + allocate_vm_regions(); +} + +T_HELPER_DECL(allocate_vm_objects, "allocates VM objects and VM regions") +{ + move_to_idle_band(); + allocate_vm_objects(); +} +T_HELPER_DECL(allocate_from_generic_zone, "allocates from a generic zone") +{ + move_to_idle_band(); allocate_from_generic_zone(); } @@ -367,7 +537,15 @@ T_DECL( memorystatus_vme_zone_test, */ T_META_SYSCTL_INT(ZONEMAP_JETSAM_LIMIT_SYSCTL)) { - run_test_for_zone(VME_ZONE_TEST); + current_test = (test_config_struct) { + .test_index = VME_ZONE_TEST, + .helper_func = VME_ZONE_TEST_OPT, + .num_zones = 1, + .zone_names = (mach_zone_name_t []){ + { .mzn_name = VME_ZONE } + } + }; + run_test(); } T_DECL( memorystatus_vm_objects_zone_test, @@ -378,7 +556,16 @@ T_DECL( memorystatus_vm_objects_zone_test, */ T_META_SYSCTL_INT(ZONEMAP_JETSAM_LIMIT_SYSCTL)) { - run_test_for_zone(VM_OBJECTS_ZONE_TEST); + current_test = (test_config_struct) { + .test_index = VM_OBJECTS_ZONE_TEST, + .helper_func = VM_OBJECTS_ZONE_TEST_OPT, + .num_zones = 2, + .zone_names = (mach_zone_name_t []){ + { .mzn_name = VME_ZONE }, + { .mzn_name = VMOBJECTS_ZONE} + } + }; + run_test(); } T_DECL( memorystatus_generic_zone_test, @@ -389,5 +576,11 @@ T_DECL( memorystatus_generic_zone_test, */ T_META_SYSCTL_INT(ZONEMAP_JETSAM_LIMIT_SYSCTL)) { - run_test_for_zone(GENERIC_ZONE_TEST); + current_test = (test_config_struct) { + .test_index = GENERIC_ZONE_TEST, + .helper_func = GENERIC_ZONE_TEST_OPT, + .num_zones = 0, + .zone_names = NULL + }; + run_test(); }