1#include <AvailabilityMacros.h> 2#include <mach/thread_policy.h> 3#include <mach/mach.h> 4#include <mach/mach_traps.h> 5#include <mach/mach_error.h> 6#include <mach/mach_time.h> 7#include <signal.h> 8#include <stdio.h> 9#include <stdlib.h> 10#include <string.h> 11#include <unistd.h> 12#include <sys/errno.h> 13#include <sys/kern_memorystatus.h> 14 15#define MAXTESTPIDS 15 16#define MAXPRIORITY JETSAM_PRIORITY_MAX - 1 17 18/* 19 * <rdar://problem/15976217> memorystatus_control support for 20 * reprioritizing multiple processes 21 * 22 * This test/tool operates in one of two modes. 23 * List mode or Generate mode. 24 * 25 * In generate mode (the default) 26 * Setup: 27 * Spin off some number of child processes. (Enforce a max) 28 * Generate a random jetsam priority band for each process. 29 * Kill at least one of the processes (this tests the kernel's 30 * ability to ignore non-existant pid.) 31 * Sprinkle the processes into their randomly assigned band. 32 * Test: 33 * Query the kernel for a snapshot of the jetsam priority list, 34 * (saving the priority and the index into the overall 35 * priority list for each pid) 36 * 37 * Exercise the MEMORYSTATUS_CMD_GRP_SET_PROPERTIES control call. 38 * 39 * Properties supported in this exercise? 40 * [1] priority 41 * 42 * Query the kernel again for a second snapshot. 43 * 44 * Verify: 45 * If everything works as expected, all the pids have moved 46 * to the new priority band and relative order before the 47 * move is the same order after the move. 48 * 49 * In list mode, the user passes in a list of pids from the command line. 50 * We skip the Setup phase, but follow through with the Test and Verify 51 * steps. 52 * 53 * When using generate mode, you can add a delay that takes place just 54 * before the control call and then again just after the control call. 55 * eg: This allows time to manaully introspect the state of 56 * the device before and after the new property assignments. 57 */ 58 59/* Globals */ 60int g_exit_status = 0; 61boolean_t generate_flag = FALSE; 62boolean_t list_flag = FALSE; 63boolean_t verbose_flag = FALSE; 64boolean_t do_error_flag = FALSE; 65uint64_t delay_seconds = 0; 66uint32_t kill_pid_indx = 0; 67uint32_t g_new_priority = JETSAM_PRIORITY_IDLE; 68 69typedef struct pidinfo { 70 pid_t pid; 71 int32_t pri_random; /* random priority for generate path */ 72 int32_t pri_before; /* priority before idle move */ 73 int32_t indx_before; /* jetsam bucket index before idle move */ 74 int32_t pri_after; /* priority found after idle move test */ 75 int32_t exp_after; /* Expect priority. Zero if moved to idle band */ 76 int32_t indx_after; /* order it landed in the idle band */ 77} pidinfo_t; 78 79static boolean_t do_get_priority_list (boolean_t before, memorystatus_priority_entry_t *mypids, size_t pid_count, pidinfo_t *pidinfo); 80static void do_generate_test(); 81static void do_child_labor(); 82static int priority_cmp(const void *x, const void *y); 83static void do_pidlist_test(memorystatus_priority_entry_t *list, uint32_t pid_count); 84static void do_control_list_test(memorystatus_priority_entry_t *list, uint32_t pid_count); 85static void dump_info_table(pidinfo_t *info, uint32_t count); 86static void print_usage(); 87 88static char *g_testname = "GrpSetProperties"; 89 90static void 91printTestHeader(pid_t testPid, const char *testName, ...) 92{ 93 va_list va; 94 printf("=============================================\n"); 95 printf("[TEST] GrpSetProperty "); 96 va_start(va, testName); 97 vprintf(testName, va); 98 va_end(va); 99 printf("\n"); 100 printf("[PID] %d\n", testPid); 101 printf("=============================================\n"); 102 printf("[BEGIN]\n"); 103} 104 105static void 106printTestResult(const char *testName, boolean_t didPass, const char *msg, ...) 107{ 108 if (msg != NULL) { 109 va_list va; 110 printf("\t\t"); 111 va_start(va, msg); 112 vprintf(msg, va); 113 va_end(va); 114 printf("\n"); 115 } 116 if (didPass) { 117 printf("[PASS] GrpSetProperty\t%s\n\n", testName); 118 } else { 119 printf("[FAIL] GrpSetProperty\t%s\n\n", testName); 120 121 /* Any single failure, fails full test run */ 122 g_exit_status = -1; 123 } 124} 125 126static void 127do_error_test () 128{ 129 boolean_t passflag = TRUE; 130 int error; 131 size_t listsize = 0; 132 memorystatus_priority_entry_t list[MAXTESTPIDS]; 133 134 listsize = (sizeof(memorystatus_priority_entry_t) * MAXTESTPIDS); 135 memset (list, 0, listsize); 136 137 list[0].pid = getpid(); 138 list[0].priority = JETSAM_PRIORITY_MAX+10; /* out of range priority */ 139 140 printTestHeader (getpid(), "NULL pointer test"); 141 errno=0; 142 error = memorystatus_control(MEMORYSTATUS_CMD_GRP_SET_PROPERTIES, 0, 0, NULL, listsize); 143 printf("\t Expect: error (-1), errno (%d)\n", EINVAL); 144 printf("\t Actual: error (%d), errno (%d)\n", error, errno); 145 if (error == -1 && errno == EINVAL) 146 passflag = TRUE; 147 else 148 passflag = FALSE; 149 printTestResult("NULL pointer test", passflag, NULL); 150 151 152 printTestHeader (getpid(), "zero size test"); 153 errno=0; 154 error = memorystatus_control(MEMORYSTATUS_CMD_GRP_SET_PROPERTIES, 0, 0, &list, 0); 155 printf("\t Expect: error (-1), errno (%d)\n", EINVAL); 156 printf("\t Actual: error (%d), errno (%d)\n", error, errno); 157 if (error == -1 && errno == EINVAL) 158 passflag = TRUE; 159 else 160 passflag = FALSE; 161 printTestResult("zero size test", passflag, NULL); 162 163 164 printTestHeader (getpid(), "bad size test"); 165 errno=0; 166 error = memorystatus_control(MEMORYSTATUS_CMD_GRP_SET_PROPERTIES, 0, 0, &list, (listsize-1)); 167 printf("\t Expect: error (-1), errno (%d)\n", EINVAL); 168 printf("\t Actual: error (%d), errno (%d)\n", error, errno); 169 if (error == -1 && errno == EINVAL) 170 passflag = TRUE; 171 else 172 passflag = FALSE; 173 printTestResult("bad size test", passflag, NULL); 174 175 printTestHeader (getpid(), "bad priority test"); 176 errno=0; 177 error = memorystatus_control(MEMORYSTATUS_CMD_GRP_SET_PROPERTIES, 0, 0, &list, (listsize)); 178 printf("\t Expect: error (-1), errno (%d)\n", EINVAL); 179 printf("\t Actual: error (%d), errno (%d)\n", error, errno); 180 if (error == -1 && errno == EINVAL) 181 passflag = TRUE; 182 else 183 passflag = FALSE; 184 printTestResult("bad priority test", passflag, NULL); 185} 186 187int 188main(int argc, char *argv[]) 189{ 190 kern_return_t error; 191 192 memorystatus_priority_entry_t list[MAXTESTPIDS]; 193 uint32_t pid_count = MAXTESTPIDS; /* default */ 194 size_t listsize = 0; 195 int c; 196 int i = 0; 197 198 if (geteuid() != 0) { 199 printf("\tMust be run as root\n"); 200 exit(1); 201 } 202 203 listsize = sizeof(memorystatus_priority_entry_t) * MAXTESTPIDS; 204 memset (list, 0, listsize); 205 206 while ((c = getopt (argc, argv, "p:ed:hvg:l")) != -1) { 207 switch (c) { 208 case 'p': 209 g_new_priority = strtol(optarg, NULL, 10); 210 break; 211 case 'e': 212 do_error_flag = TRUE; 213 break; 214 case 'v': 215 verbose_flag = TRUE; 216 break; 217 case 'd': 218 delay_seconds = strtol(optarg, NULL, 10); 219 break; 220 case 'l': 221 /* means a list of pids follow */ 222 list_flag = TRUE; 223 break; 224 case 'g': 225 /* dynamicall generate 'n' processes */ 226 generate_flag = TRUE; 227 pid_count = strtol(optarg, NULL, 10); 228 break; 229 case 'h': 230 print_usage(); 231 exit(0); 232 case '?': 233 default: 234 print_usage(); 235 exit(-1); 236 } 237 } 238 239 argc -= optind; 240 argv += optind; 241 errno = 0; 242 243 /* 244 * This core part of this test has two modes only. 245 * Default is to dynamically generate a list of pids to work on. 246 * Else use the -l flag and pass in a list of pids. 247 */ 248 if (generate_flag && list_flag) { 249 printTestResult(g_testname, FALSE, "Can't use both -g and -l options\n"); 250 exit(g_exit_status); 251 } 252 253 if (generate_flag) { 254 if (pid_count <= 0 || pid_count > MAXTESTPIDS) { 255 printTestResult(g_testname, FALSE, 256 "Pid count out of range (actual: %d), (max: %d)\n", pid_count, MAXTESTPIDS); 257 exit(g_exit_status); 258 } 259 } else if (list_flag) { 260 pid_count=0; 261 for (; *argv; ++argv) { 262 if (pid_count < MAXTESTPIDS){ 263 list[pid_count].pid = strtol(*argv, NULL, 10); 264 list[pid_count].priority = g_new_priority; 265 pid_count++; 266 argc--; 267 optind++; 268 } else { 269 printTestResult(g_testname, FALSE, 270 "Too many pids (actual: %d), (max: %d)\n", pid_count, MAXTESTPIDS); 271 exit(g_exit_status); 272 break; 273 } 274 } 275 if (pid_count <= 0 ) { 276 printTestResult(g_testname, FALSE, 277 "Provide at least one pid (actual: %d),(max: %d)\n", pid_count, MAXTESTPIDS); 278 exit(g_exit_status); 279 } 280 } else { 281 /* set defaults */ 282 do_error_flag = TRUE; 283 generate_flag = TRUE; 284 pid_count = MAXTESTPIDS; 285 } 286 287 if (do_error_flag) { 288 do_error_test(); 289 } 290 291 if (generate_flag) { 292 do_generate_test(list, pid_count); 293 } 294 295 if (list_flag) { 296 do_pidlist_test (list, pid_count); 297 } 298 299 return(g_exit_status); 300 301} 302 303 304static void 305do_pidlist_test(memorystatus_priority_entry_t *list, uint32_t pid_count) 306{ 307 308 do_control_list_test(list, pid_count); 309} 310 311static void 312do_control_list_test(memorystatus_priority_entry_t *list, uint32_t pid_count) 313{ 314 int error = 0; 315 int i; 316 boolean_t passflag; 317 pidinfo_t info[MAXTESTPIDS]; 318 319 printTestHeader (getpid(), "new priority test"); 320 memset (info, 0, MAXTESTPIDS * sizeof(pidinfo_t)); 321 printf ("\tInput: pid_count = %d\n", pid_count); 322 printf ("\tInput: new_priority = %d\n", g_new_priority); 323 324 if (generate_flag) 325 printf("\tIntentionally killed pid [%d]\n", list[kill_pid_indx].pid); 326 327 /* random value initialization */ 328 srandom((u_long)time(NULL)); 329 330 /* In generate path, we sprinkle pids into random priority buckets */ 331 332 /* initialize info structures and properties */ 333 for (i = 0; i < pid_count; i++) { 334 info[i].pid = list[i].pid; 335 info[i].pri_random = random() % MAXPRIORITY; /* generate path only */ 336 info[i].pri_before = -1; 337 info[i].indx_before = -1; 338 info[i].pri_after = -1; 339 info[i].exp_after = g_new_priority; 340 info[i].indx_after = -1; 341 342 if (generate_flag) { 343 /* Initialize properties for generated pids */ 344 memorystatus_priority_properties_t mp; 345 mp.priority = info[i].pri_random; 346 mp.user_data = 0; 347 if(memorystatus_control(MEMORYSTATUS_CMD_SET_PRIORITY_PROPERTIES, list[i].pid, 0, &mp, sizeof(mp)) == -1) { 348 /* 349 * If we cannot set the properties on a given 350 * pid (for whatever reason), we'll ignore it. 351 * But set expectations for verification phase. 352 */ 353 printf("\tWarning: set properties failed on pid [%d] (%s)\n", list[i].pid, strerror(errno)); 354 info[i].exp_after = -1; 355 errno = 0; 356 } 357 } 358 } 359 360 /* Get the system's current jetsam priority list, init pass */ 361 if (do_get_priority_list(TRUE, list, pid_count, info) == FALSE) { 362 error = 1; 363 goto out; 364 } 365 366 if (delay_seconds > 0) { 367 printf("\tDelay [%llu] seconds... (before move to new band)\n", delay_seconds); 368 sleep(delay_seconds); 369 errno = 0; 370 } 371 372 error = memorystatus_control(MEMORYSTATUS_CMD_GRP_SET_PROPERTIES, 0, 0, 373 list, (pid_count * sizeof(memorystatus_priority_entry_t))); 374 if (error) { 375 printf("\tMEMORYSTATUS_CMD_GRP_SET_PROPERTIES failed (%s)\n", strerror(errno)); 376 goto out; 377 } 378 379 /* Get the system's jetsam priority list, after move to new band */ 380 if (do_get_priority_list(FALSE, list, pid_count, info) == FALSE) { 381 error = 1; 382 goto out; 383 } 384 385 if (delay_seconds > 0) { 386 printf("\tDelay [%llu] seconds... (after move to new band)\n", delay_seconds); 387 sleep(delay_seconds); 388 errno = 0; 389 } 390 391 qsort ((void *)info, pid_count, sizeof(pidinfo_t),priority_cmp); 392 393 /* 394 * Verify that the list of pids have been placed in new priority band 395 * and that they are in the same relative priority order. 396 * The relative bucket placement before moving to the new priority 397 * band should be the same as that after moving to the new 398 * priority band. 399 */ 400 error = 0; 401 for (i=0; i < pid_count; i++) { 402 if (info[i].pri_before == -1){ 403 /* skip... this pid did not exist */ 404 continue; 405 } 406 407 /* The new priority band must meet expectations */ 408 if (info[i].pri_after != info[i].exp_after) { 409 error++; 410 } 411 412 if (i+1 == pid_count) 413 break; /* Done traversing list */ 414 415 if (info[i].pid == info[i+1].pid) { 416 /* skip duplicate pids */ 417 continue; 418 } 419 420 if (info[i].indx_before < info[i+1].indx_before && 421 info[i].indx_after < info[i+1].indx_after && 422 info[i].pri_before <= info[i+1].pri_before && 423 info[i].pri_after <= info[i+1].pri_after ) { 424 /* yay */ 425 } 426 else { 427 error++; 428 } 429 } 430 431 printf("\tFound [%d] verification errors.\n", error); 432 433 if (error || errno || verbose_flag==TRUE) { 434 dump_info_table(info, pid_count); 435 } 436 437out: 438 printf("\n\tExpect: error (0), errno (0)\n"); 439 printf("\tActual: error (%d), errno (%d)\n", error, errno); 440 if (error != 0 || errno != 0) 441 passflag = FALSE; 442 else 443 passflag = TRUE; 444 printTestResult(g_testname, passflag, NULL); 445} 446 447/* 448 * The concept of jetsam priority order can actually be viewed as 449 * the relative index of an item in a bucket from from lowest 450 * priority bucket to highest priority bucket and then from 451 * head bucket entry to tail bucket entry. 452 * In reality, we have a linear, ordered list at any point 453 * in time. 454 */ 455 456 457static int 458priority_cmp(const void *x, const void *y) 459{ 460 pidinfo_t entry_x = *((pidinfo_t *)x); 461 pidinfo_t entry_y = *((pidinfo_t *)y); 462 463 if (entry_x.pri_before < entry_y.pri_before) 464 return -1; 465 if (entry_x.pri_before == entry_y.pri_before) { 466 /* 467 * Second level ordering. 468 */ 469 if (entry_x.indx_before < entry_y.indx_before) 470 return -1; 471 if (entry_x.indx_before == entry_y.indx_before) 472 return 0; /* never */ 473 return 1; 474 } 475 return 1; 476} 477 478 479static boolean_t 480do_get_priority_list (boolean_t before, memorystatus_priority_entry_t *mypids, size_t pid_count, pidinfo_t *pidinfo) 481{ 482#pragma unused (mypids) 483 484 size_t size = 0; 485 memorystatus_priority_entry_t *list; 486 size_t list_count = 0; 487 int found = 0; 488 int i, j; 489 490 size = memorystatus_control(MEMORYSTATUS_CMD_GET_PRIORITY_LIST, 0, 0, NULL, 0); 491 if (size <= 0 ) { 492 printf("\tCan't get jetsam priority list size: %s\n", strerror(errno)); 493 return(FALSE); 494 } 495 496 list = (memorystatus_priority_entry_t *)malloc(size); 497 498 size = memorystatus_control(MEMORYSTATUS_CMD_GET_PRIORITY_LIST, 0, 0, list, size); 499 if (size <= 0) { 500 printf("\tCould not get jetsam priority list: %s\n", strerror(errno)); 501 free(list); 502 return(FALSE); 503 } 504 505 /* recompute number of entries in the list and find the pid's priority*/ 506 list_count = size / sizeof(memorystatus_priority_entry_t); 507 508 printf("\tFound [%d] jetsam bucket entries (%s move to new band).\n", 509 (int)list_count, before? "before" : " after"); 510 511 for (i=0; i < pid_count; i++) { 512 for (j=0; j < list_count; j++) { 513 if (list[j].pid == pidinfo[i].pid) { 514 if (before) { 515 /* 516 * Save process's priority and relative index 517 * before moving to new priority 518 */ 519 pidinfo[i].pri_before = list[j].priority; 520 pidinfo[i].indx_before = j; 521 }else { 522 /* 523 * Save process's priority and relative index 524 * after moving to new priority 525 */ 526 pidinfo[i].pri_after = list[j].priority; 527 pidinfo[i].indx_after = j; 528 } 529 break; 530 } 531 } 532 } 533 534 if (list) 535 free(list); 536 537 return(TRUE); 538} 539 540 541 542static 543void do_generate_test (memorystatus_priority_entry_t *list, uint32_t pid_count) 544{ 545 int launch_errors = 0; 546 int i; 547 memorystatus_priority_properties_t mp; 548 549 /* Generate mode Setup phase */ 550 551 if (pid_count <= 0) 552 return; 553 554 for (i=0; i < pid_count; i++) { 555 list[i].pid = fork(); 556 list[i].priority = g_new_priority; /*XXX introduce multiple 557 new priorities??? */ 558 switch (list[i].pid) { 559 case 0: /* child */ 560 do_child_labor(); 561 exit(0); 562 break; 563 case -1: 564 launch_errors++; 565 break; 566 default: 567 continue; 568 } 569 } 570 571 /* 572 * Parent will set the priority of the 573 * child processes 574 */ 575 576 if (verbose_flag && launch_errors > 0) 577 printf("\tParent launch errors = %d\n", launch_errors); 578 579 /* Introduce a case where pid is not found */ 580 kill_pid_indx = pid_count/2 ; 581 kill(list[kill_pid_indx].pid, SIGKILL); 582 sleep(5); 583 584 do_control_list_test (list, pid_count); 585 586 for (i=0; i < pid_count; i++) { 587 if (i != kill_pid_indx) { 588 kill(list[i].pid, SIGKILL ); 589 } 590 } 591} 592 593 594static void 595do_child_labor() 596{ 597 /* 598 * Ideally, the process should be suspended, 599 * but letting it spin doing random 600 * stuff should be harmless for this test. 601 */ 602 if (verbose_flag) 603 printf("\tLaunched child pid [%d]\n", getpid()); 604 while (TRUE) { 605 random(); 606 sleep(5); 607 } 608} 609 610 611static void 612dump_info_table(pidinfo_t *info, uint32_t count) 613{ 614 int i; 615 616 /* 617 * The random priority value is only of interest in the 618 * generate_flag path, and even then, it's not really 619 * that interesting! So, not dumped here. 620 * But it is evident in the Jetsam Priority 'before' column. 621 */ 622 623 printf("\n%10s \t%s \t\t%20s\n", "Pid", "Jetsam Priority", "Relative Bucket Index"); 624 printf("%10s \t%s %20s\n", "", "(before | after | expected)", "(before | after)"); 625 626 for (i=0; i < count; i++) { 627 printf("%10d", info[i].pid); 628 printf("\t(%4d |", info[i].pri_before); 629 printf("%4d |", info[i].pri_after); 630 printf("%4d)", info[i].exp_after); 631 printf("\t\t(%5d |", info[i].indx_before); 632 printf("%5d)\n", info[i].indx_after); 633 } 634} 635 636static void 637print_usage() { 638 639 printf("\nUsage:\n"); 640 printf("[-e] [-p] [-v] [-d <seconds>][ -g <count> | -l <list of pids>]\n\n"); 641 printf("Exercise the MEMORYSTATUS_CMD_GRP_SET_PROPERTIES command.\n"); 642 printf("Operates on at most %d pids.\n", MAXTESTPIDS); 643 printf("Pass in a list of pids or allow the test to generate the pids dynamically.\n\n"); 644 645 printf("\t -e : exercise error tests\n"); 646 printf("\t -p <priority> : Override default priority band.\n"); 647 printf("\t -v : extra verbosity\n"); 648 printf("\t -d <seconds> : delay before and after idle move (default = 0)\n"); 649 printf("\t -g <count> : dynamically generate <count> processes.\n"); 650 printf("\t -l <list of pids> : operate on the given list of pids\n\n"); 651 printf("\t default : generate %d pids, no delay, priority %d eg: -g %d -p %d\n\n", 652 MAXTESTPIDS, g_new_priority, MAXTESTPIDS, g_new_priority); 653} 654