]> git.saurik.com Git - apple/xnu.git/blob - tools/tests/memorystatus/memorystatus_groups.c
93ae8ade0ff072a0b54fc57e6a488869872ec729
[apple/xnu.git] / tools / tests / memorystatus / memorystatus_groups.c
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 */
60 int g_exit_status = 0;
61 boolean_t generate_flag = FALSE;
62 boolean_t list_flag = FALSE;
63 boolean_t verbose_flag = FALSE;
64 boolean_t do_error_flag = FALSE;
65 uint64_t delay_seconds = 0;
66 uint32_t kill_pid_indx = 0;
67 uint32_t g_new_priority = JETSAM_PRIORITY_IDLE;
68
69 typedef 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
79 static boolean_t do_get_priority_list (boolean_t before, memorystatus_priority_entry_t *mypids, size_t pid_count, pidinfo_t *pidinfo);
80 static void do_generate_test();
81 static void do_child_labor();
82 static int priority_cmp(const void *x, const void *y);
83 static void do_pidlist_test(memorystatus_priority_entry_t *list, uint32_t pid_count);
84 static void do_control_list_test(memorystatus_priority_entry_t *list, uint32_t pid_count);
85 static void dump_info_table(pidinfo_t *info, uint32_t count);
86 static void print_usage();
87
88 static char *g_testname = "GrpSetProperties";
89
90 static void
91 printTestHeader(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
105 static void
106 printTestResult(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
126 static void
127 do_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
187 int
188 main(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
304 static void
305 do_pidlist_test(memorystatus_priority_entry_t *list, uint32_t pid_count)
306 {
307
308 do_control_list_test(list, pid_count);
309 }
310
311 static void
312 do_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
437 out:
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
457 static int
458 priority_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
479 static boolean_t
480 do_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
542 static
543 void 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
594 static void
595 do_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
611 static void
612 dump_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
636 static void
637 print_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 }