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