#include #include #include #include #include #include #include #include #include #include #include #include #include #define MAXTESTPIDS 15 #define MAXPRIORITY JETSAM_PRIORITY_MAX - 1 /* * memorystatus_control support for * reprioritizing multiple processes * * This test/tool operates in one of two modes. * List mode or Generate mode. * * In generate mode (the default) * Setup: * Spin off some number of child processes. (Enforce a max) * Generate a random jetsam priority band for each process. * Kill at least one of the processes (this tests the kernel's * ability to ignore non-existant pid.) * Sprinkle the processes into their randomly assigned band. * Test: * Query the kernel for a snapshot of the jetsam priority list, * (saving the priority and the index into the overall * priority list for each pid) * * Exercise the MEMORYSTATUS_CMD_GRP_SET_PROPERTIES control call. * * Properties supported in this exercise? * [1] priority * * Query the kernel again for a second snapshot. * * Verify: * If everything works as expected, all the pids have moved * to the new priority band and relative order before the * move is the same order after the move. * * In list mode, the user passes in a list of pids from the command line. * We skip the Setup phase, but follow through with the Test and Verify * steps. * * When using generate mode, you can add a delay that takes place just * before the control call and then again just after the control call. * eg: This allows time to manaully introspect the state of * the device before and after the new property assignments. */ /* Globals */ int g_exit_status = 0; boolean_t generate_flag = FALSE; boolean_t list_flag = FALSE; boolean_t verbose_flag = FALSE; boolean_t do_error_flag = FALSE; uint64_t delay_seconds = 0; uint32_t kill_pid_indx = 0; uint32_t g_new_priority = JETSAM_PRIORITY_IDLE; typedef struct pidinfo { pid_t pid; int32_t pri_random; /* random priority for generate path */ int32_t pri_before; /* priority before idle move */ int32_t indx_before; /* jetsam bucket index before idle move */ int32_t pri_after; /* priority found after idle move test */ int32_t exp_after; /* Expect priority. Zero if moved to idle band */ int32_t indx_after; /* order it landed in the idle band */ } pidinfo_t; static boolean_t do_get_priority_list (boolean_t before, memorystatus_priority_entry_t *mypids, size_t pid_count, pidinfo_t *pidinfo); static void do_generate_test(); static void do_child_labor(); static int priority_cmp(const void *x, const void *y); static void do_pidlist_test(memorystatus_priority_entry_t *list, uint32_t pid_count); static void do_control_list_test(memorystatus_priority_entry_t *list, uint32_t pid_count); static void dump_info_table(pidinfo_t *info, uint32_t count); static void print_usage(); static char *g_testname = "GrpSetProperties"; static void printTestHeader(pid_t testPid, const char *testName, ...) { va_list va; printf("=============================================\n"); printf("[TEST] GrpSetProperty "); va_start(va, testName); vprintf(testName, va); va_end(va); printf("\n"); printf("[PID] %d\n", testPid); printf("=============================================\n"); printf("[BEGIN]\n"); } static void printTestResult(const char *testName, boolean_t didPass, const char *msg, ...) { if (msg != NULL) { va_list va; printf("\t\t"); va_start(va, msg); vprintf(msg, va); va_end(va); printf("\n"); } if (didPass) { printf("[PASS] GrpSetProperty\t%s\n\n", testName); } else { printf("[FAIL] GrpSetProperty\t%s\n\n", testName); /* Any single failure, fails full test run */ g_exit_status = -1; } } static void do_error_test () { boolean_t passflag = TRUE; int error; size_t listsize = 0; memorystatus_priority_entry_t list[MAXTESTPIDS]; listsize = (sizeof(memorystatus_priority_entry_t) * MAXTESTPIDS); memset (list, 0, listsize); list[0].pid = getpid(); list[0].priority = JETSAM_PRIORITY_MAX+10; /* out of range priority */ printTestHeader (getpid(), "NULL pointer test"); errno=0; error = memorystatus_control(MEMORYSTATUS_CMD_GRP_SET_PROPERTIES, 0, 0, NULL, listsize); printf("\t Expect: error (-1), errno (%d)\n", EINVAL); printf("\t Actual: error (%d), errno (%d)\n", error, errno); if (error == -1 && errno == EINVAL) passflag = TRUE; else passflag = FALSE; printTestResult("NULL pointer test", passflag, NULL); printTestHeader (getpid(), "zero size test"); errno=0; error = memorystatus_control(MEMORYSTATUS_CMD_GRP_SET_PROPERTIES, 0, 0, &list, 0); printf("\t Expect: error (-1), errno (%d)\n", EINVAL); printf("\t Actual: error (%d), errno (%d)\n", error, errno); if (error == -1 && errno == EINVAL) passflag = TRUE; else passflag = FALSE; printTestResult("zero size test", passflag, NULL); printTestHeader (getpid(), "bad size test"); errno=0; error = memorystatus_control(MEMORYSTATUS_CMD_GRP_SET_PROPERTIES, 0, 0, &list, (listsize-1)); printf("\t Expect: error (-1), errno (%d)\n", EINVAL); printf("\t Actual: error (%d), errno (%d)\n", error, errno); if (error == -1 && errno == EINVAL) passflag = TRUE; else passflag = FALSE; printTestResult("bad size test", passflag, NULL); printTestHeader (getpid(), "bad priority test"); errno=0; error = memorystatus_control(MEMORYSTATUS_CMD_GRP_SET_PROPERTIES, 0, 0, &list, (listsize)); printf("\t Expect: error (-1), errno (%d)\n", EINVAL); printf("\t Actual: error (%d), errno (%d)\n", error, errno); if (error == -1 && errno == EINVAL) passflag = TRUE; else passflag = FALSE; printTestResult("bad priority test", passflag, NULL); } int main(int argc, char *argv[]) { kern_return_t error; memorystatus_priority_entry_t list[MAXTESTPIDS]; uint32_t pid_count = MAXTESTPIDS; /* default */ size_t listsize = 0; int c; int i = 0; if (geteuid() != 0) { printf("\tMust be run as root\n"); exit(1); } listsize = sizeof(memorystatus_priority_entry_t) * MAXTESTPIDS; memset (list, 0, listsize); while ((c = getopt (argc, argv, "p:ed:hvg:l")) != -1) { switch (c) { case 'p': g_new_priority = strtol(optarg, NULL, 10); break; case 'e': do_error_flag = TRUE; break; case 'v': verbose_flag = TRUE; break; case 'd': delay_seconds = strtol(optarg, NULL, 10); break; case 'l': /* means a list of pids follow */ list_flag = TRUE; break; case 'g': /* dynamicall generate 'n' processes */ generate_flag = TRUE; pid_count = strtol(optarg, NULL, 10); break; case 'h': print_usage(); exit(0); case '?': default: print_usage(); exit(-1); } } argc -= optind; argv += optind; errno = 0; /* * This core part of this test has two modes only. * Default is to dynamically generate a list of pids to work on. * Else use the -l flag and pass in a list of pids. */ if (generate_flag && list_flag) { printTestResult(g_testname, FALSE, "Can't use both -g and -l options\n"); exit(g_exit_status); } if (generate_flag) { if (pid_count <= 0 || pid_count > MAXTESTPIDS) { printTestResult(g_testname, FALSE, "Pid count out of range (actual: %d), (max: %d)\n", pid_count, MAXTESTPIDS); exit(g_exit_status); } } else if (list_flag) { pid_count=0; for (; *argv; ++argv) { if (pid_count < MAXTESTPIDS){ list[pid_count].pid = strtol(*argv, NULL, 10); list[pid_count].priority = g_new_priority; pid_count++; argc--; optind++; } else { printTestResult(g_testname, FALSE, "Too many pids (actual: %d), (max: %d)\n", pid_count, MAXTESTPIDS); exit(g_exit_status); break; } } if (pid_count <= 0 ) { printTestResult(g_testname, FALSE, "Provide at least one pid (actual: %d),(max: %d)\n", pid_count, MAXTESTPIDS); exit(g_exit_status); } } else { /* set defaults */ do_error_flag = TRUE; generate_flag = TRUE; pid_count = MAXTESTPIDS; } if (do_error_flag) { do_error_test(); } if (generate_flag) { do_generate_test(list, pid_count); } if (list_flag) { do_pidlist_test (list, pid_count); } return(g_exit_status); } static void do_pidlist_test(memorystatus_priority_entry_t *list, uint32_t pid_count) { do_control_list_test(list, pid_count); } static void do_control_list_test(memorystatus_priority_entry_t *list, uint32_t pid_count) { int error = 0; int i; boolean_t passflag; pidinfo_t info[MAXTESTPIDS]; printTestHeader (getpid(), "new priority test"); memset (info, 0, MAXTESTPIDS * sizeof(pidinfo_t)); printf ("\tInput: pid_count = %d\n", pid_count); printf ("\tInput: new_priority = %d\n", g_new_priority); if (generate_flag) printf("\tIntentionally killed pid [%d]\n", list[kill_pid_indx].pid); /* random value initialization */ srandom((u_long)time(NULL)); /* In generate path, we sprinkle pids into random priority buckets */ /* initialize info structures and properties */ for (i = 0; i < pid_count; i++) { info[i].pid = list[i].pid; info[i].pri_random = random() % MAXPRIORITY; /* generate path only */ info[i].pri_before = -1; info[i].indx_before = -1; info[i].pri_after = -1; info[i].exp_after = g_new_priority; info[i].indx_after = -1; if (generate_flag) { /* Initialize properties for generated pids */ memorystatus_priority_properties_t mp; mp.priority = info[i].pri_random; mp.user_data = 0; if(memorystatus_control(MEMORYSTATUS_CMD_SET_PRIORITY_PROPERTIES, list[i].pid, 0, &mp, sizeof(mp)) == -1) { /* * If we cannot set the properties on a given * pid (for whatever reason), we'll ignore it. * But set expectations for verification phase. */ printf("\tWarning: set properties failed on pid [%d] (%s)\n", list[i].pid, strerror(errno)); info[i].exp_after = -1; errno = 0; } } } /* Get the system's current jetsam priority list, init pass */ if (do_get_priority_list(TRUE, list, pid_count, info) == FALSE) { error = 1; goto out; } if (delay_seconds > 0) { printf("\tDelay [%llu] seconds... (before move to new band)\n", delay_seconds); sleep(delay_seconds); errno = 0; } error = memorystatus_control(MEMORYSTATUS_CMD_GRP_SET_PROPERTIES, 0, 0, list, (pid_count * sizeof(memorystatus_priority_entry_t))); if (error) { printf("\tMEMORYSTATUS_CMD_GRP_SET_PROPERTIES failed (%s)\n", strerror(errno)); goto out; } /* Get the system's jetsam priority list, after move to new band */ if (do_get_priority_list(FALSE, list, pid_count, info) == FALSE) { error = 1; goto out; } if (delay_seconds > 0) { printf("\tDelay [%llu] seconds... (after move to new band)\n", delay_seconds); sleep(delay_seconds); errno = 0; } qsort ((void *)info, pid_count, sizeof(pidinfo_t),priority_cmp); /* * Verify that the list of pids have been placed in new priority band * and that they are in the same relative priority order. * The relative bucket placement before moving to the new priority * band should be the same as that after moving to the new * priority band. */ error = 0; for (i=0; i < pid_count; i++) { if (info[i].pri_before == -1){ /* skip... this pid did not exist */ continue; } /* The new priority band must meet expectations */ if (info[i].pri_after != info[i].exp_after) { error++; } if (i+1 == pid_count) break; /* Done traversing list */ if (info[i].pid == info[i+1].pid) { /* skip duplicate pids */ continue; } if (info[i].indx_before < info[i+1].indx_before && info[i].indx_after < info[i+1].indx_after && info[i].pri_before <= info[i+1].pri_before && info[i].pri_after <= info[i+1].pri_after ) { /* yay */ } else { error++; } } printf("\tFound [%d] verification errors.\n", error); if (error || errno || verbose_flag==TRUE) { dump_info_table(info, pid_count); } out: printf("\n\tExpect: error (0), errno (0)\n"); printf("\tActual: error (%d), errno (%d)\n", error, errno); if (error != 0 || errno != 0) passflag = FALSE; else passflag = TRUE; printTestResult(g_testname, passflag, NULL); } /* * The concept of jetsam priority order can actually be viewed as * the relative index of an item in a bucket from from lowest * priority bucket to highest priority bucket and then from * head bucket entry to tail bucket entry. * In reality, we have a linear, ordered list at any point * in time. */ static int priority_cmp(const void *x, const void *y) { pidinfo_t entry_x = *((pidinfo_t *)x); pidinfo_t entry_y = *((pidinfo_t *)y); if (entry_x.pri_before < entry_y.pri_before) return -1; if (entry_x.pri_before == entry_y.pri_before) { /* * Second level ordering. */ if (entry_x.indx_before < entry_y.indx_before) return -1; if (entry_x.indx_before == entry_y.indx_before) return 0; /* never */ return 1; } return 1; } static boolean_t do_get_priority_list (boolean_t before, memorystatus_priority_entry_t *mypids, size_t pid_count, pidinfo_t *pidinfo) { #pragma unused (mypids) size_t size = 0; memorystatus_priority_entry_t *list; size_t list_count = 0; int found = 0; int i, j; size = memorystatus_control(MEMORYSTATUS_CMD_GET_PRIORITY_LIST, 0, 0, NULL, 0); if (size <= 0 ) { printf("\tCan't get jetsam priority list size: %s\n", strerror(errno)); return(FALSE); } list = (memorystatus_priority_entry_t *)malloc(size); size = memorystatus_control(MEMORYSTATUS_CMD_GET_PRIORITY_LIST, 0, 0, list, size); if (size <= 0) { printf("\tCould not get jetsam priority list: %s\n", strerror(errno)); free(list); return(FALSE); } /* recompute number of entries in the list and find the pid's priority*/ list_count = size / sizeof(memorystatus_priority_entry_t); printf("\tFound [%d] jetsam bucket entries (%s move to new band).\n", (int)list_count, before? "before" : " after"); for (i=0; i < pid_count; i++) { for (j=0; j < list_count; j++) { if (list[j].pid == pidinfo[i].pid) { if (before) { /* * Save process's priority and relative index * before moving to new priority */ pidinfo[i].pri_before = list[j].priority; pidinfo[i].indx_before = j; }else { /* * Save process's priority and relative index * after moving to new priority */ pidinfo[i].pri_after = list[j].priority; pidinfo[i].indx_after = j; } break; } } } if (list) free(list); return(TRUE); } static void do_generate_test (memorystatus_priority_entry_t *list, uint32_t pid_count) { int launch_errors = 0; int i; memorystatus_priority_properties_t mp; /* Generate mode Setup phase */ if (pid_count <= 0) return; for (i=0; i < pid_count; i++) { list[i].pid = fork(); list[i].priority = g_new_priority; /*XXX introduce multiple new priorities??? */ switch (list[i].pid) { case 0: /* child */ do_child_labor(); exit(0); break; case -1: launch_errors++; break; default: continue; } } /* * Parent will set the priority of the * child processes */ if (verbose_flag && launch_errors > 0) printf("\tParent launch errors = %d\n", launch_errors); /* Introduce a case where pid is not found */ kill_pid_indx = pid_count/2 ; kill(list[kill_pid_indx].pid, SIGKILL); sleep(5); do_control_list_test (list, pid_count); for (i=0; i < pid_count; i++) { if (i != kill_pid_indx) { kill(list[i].pid, SIGKILL ); } } } static void do_child_labor() { /* * Ideally, the process should be suspended, * but letting it spin doing random * stuff should be harmless for this test. */ if (verbose_flag) printf("\tLaunched child pid [%d]\n", getpid()); while (TRUE) { random(); sleep(5); } } static void dump_info_table(pidinfo_t *info, uint32_t count) { int i; /* * The random priority value is only of interest in the * generate_flag path, and even then, it's not really * that interesting! So, not dumped here. * But it is evident in the Jetsam Priority 'before' column. */ printf("\n%10s \t%s \t\t%20s\n", "Pid", "Jetsam Priority", "Relative Bucket Index"); printf("%10s \t%s %20s\n", "", "(before | after | expected)", "(before | after)"); for (i=0; i < count; i++) { printf("%10d", info[i].pid); printf("\t(%4d |", info[i].pri_before); printf("%4d |", info[i].pri_after); printf("%4d)", info[i].exp_after); printf("\t\t(%5d |", info[i].indx_before); printf("%5d)\n", info[i].indx_after); } } static void print_usage() { printf("\nUsage:\n"); printf("[-e] [-p] [-v] [-d ][ -g | -l ]\n\n"); printf("Exercise the MEMORYSTATUS_CMD_GRP_SET_PROPERTIES command.\n"); printf("Operates on at most %d pids.\n", MAXTESTPIDS); printf("Pass in a list of pids or allow the test to generate the pids dynamically.\n\n"); printf("\t -e : exercise error tests\n"); printf("\t -p : Override default priority band.\n"); printf("\t -v : extra verbosity\n"); printf("\t -d : delay before and after idle move (default = 0)\n"); printf("\t -g : dynamically generate processes.\n"); printf("\t -l : operate on the given list of pids\n\n"); printf("\t default : generate %d pids, no delay, priority %d eg: -g %d -p %d\n\n", MAXTESTPIDS, g_new_priority, MAXTESTPIDS, g_new_priority); }