Home | History | Annotate | Download | only in tool
      1 /*  time-schedule.c
      2 
      3     Programme to test how long a context switch takes.
      4 
      5     Copyright (C) 1998  Richard Gooch
      6 
      7     This program is free software; you can redistribute it and/or modify
      8     it under the terms of the GNU General Public License as published by
      9     the Free Software Foundation; either version 2 of the License, or
     10     (at your option) any later version.
     11 
     12     This program is distributed in the hope that it will be useful,
     13     but WITHOUT ANY WARRANTY; without even the implied warranty of
     14     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     15     GNU General Public License for more details.
     16 
     17     You should have received a copy of the GNU General Public License
     18     along with this program; if not, write to the Free Software
     19     Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
     20 
     21     Richard Gooch may be reached by email at  rgooch (at) atnf.csiro.au
     22     The postal address is:
     23       Richard Gooch, c/o ATNF, P. O. Box 76, Epping, N.S.W., 2121, Australia.
     24 */
     25 
     26 /*
     27     This programme will determine the context switch (scheduling) overhead on
     28     a system. It takes into account SMP machines. True context switches are
     29     measured.
     30 
     31     Written by      Richard Gooch   15-SEP-1998
     32 
     33     Last updated by Richard Gooch   25-SEP-1998
     34 
     35 */
     36 #include <unistd.h>
     37 #ifdef _POSIX_THREADS
     38 #ifndef _REENTRANT
     39 #define _REENTRANT
     40 #endif
     41 #ifndef _POSIX_THREAD_SAFE_FUNCTIONS
     42 #define _POSIX_THREAD_SAFE_FUNCTIONS
     43 #endif
     44 #include <pthread.h>
     45 #endif
     46 #include <stdlib.h>
     47 #include <stdio.h>
     48 #include <math.h>
     49 #include <string.h>
     50 #include <ctype.h>
     51 #include <signal.h>
     52 #include <sched.h>
     53 #include <sys/time.h>
     54 #include <sys/mman.h>
     55 #include <sys/types.h>
     56 #include <dirent.h>
     57 #include <errno.h>
     58 
     59 #ifndef __KARMA__
     60 #define mt_num_processors() 1	/*  Set to the number of processors   */
     61 #define ERRSTRING strerror(errno)
     62 #define FALSE 0
     63 #define TRUE  1
     64 #else
     65 #include <karma.h>
     66 #include <karma_mt.h>
     67 #endif
     68 
     69 #define MAX_ITERATIONS      1000
     70 
     71 static unsigned int hog_other_cpus();
     72 static void run_yielder(int use_threads, int read_fd);
     73 static void *yielder_main(void *arg);
     74 static void s_term_handler();
     75 static void run_low_priority(unsigned int num, int read_fd);
     76 static unsigned long compute_median(unsigned long values[MAX_ITERATIONS],
     77 				    unsigned long max_value);
     78 static unsigned int get_run_queue_size();
     79 static unsigned long get_num_switches();
     80 static void use_fpu_value(double val);
     81 
     82 static volatile unsigned int sched_count = 0;
     83 /*  For yielder  */
     84 static int pipe_read_fd = -1;
     85 static int pipe_write_fd = -1;
     86 static pid_t child = -1;
     87 
     88 int main(int argc, char **argv)
     89 {
     90 	int use_threads = FALSE;
     91 	int use_pipe = FALSE;
     92 	int no_test = FALSE;
     93 	int frob_fpu = FALSE;
     94 	int read_fd = -1;
     95 	int write_fd = -1;
     96 	int num_low_priority = -1;
     97 	int i, j;
     98 	int fds[2];
     99 	unsigned int count, num_yields, run_queue_size1, run_queue_size2,
    100 	    num_hogs;
    101 	unsigned long median, switches1, num_switches, num_overhead_switches;
    102 	signed long overhead, total_diffs;
    103 	signed long min_diff = 1000000000;
    104 	signed long max_diff = -1000000000;
    105 	double dcount = 0.0;
    106 	unsigned long diffs[MAX_ITERATIONS];
    107 	struct timeval before, after;
    108 	sigset_t set;
    109 	static char *usage =
    110 	    "time-schedule [-h] [-thread] [-notest] [-pipe] [-fpu] [num_running]";
    111 
    112 	setpgrp();
    113 	/*  First create pipe used to sychronise low priority processes  */
    114 	if (pipe(fds) != 0) {
    115 		fprintf(stderr, "Error creating pipe\t%s\n", ERRSTRING);
    116 		exit(1);
    117 	}
    118 	read_fd = fds[0];
    119 	pipe_write_fd = fds[1];
    120 	for (count = 1; count < argc; ++count) {
    121 		if (strcmp(argv[count], "-thread") == 0) {
    122 #ifdef _POSIX_THREADS
    123 			use_threads = TRUE;
    124 #else
    125 			fprintf(stderr, "POSIX threads not available\n");
    126 #endif
    127 		} else if (strcmp(argv[count], "-pipe") == 0)
    128 			use_pipe = TRUE;
    129 		else if (strcmp(argv[count], "-notest") == 0)
    130 			no_test = TRUE;
    131 		else if (strcmp(argv[count], "-fpu") == 0)
    132 			frob_fpu = TRUE;
    133 		else if (isdigit(argv[count][0]))
    134 			num_low_priority = atoi(argv[count]);
    135 		else {
    136 			fprintf(stderr,
    137 				"Programme to time context switches (schedules)\n");
    138 			fprintf(stderr,
    139 				"(C) 1998  Richard Gooch <rgooch (at) atnf.csiro.au>\n");
    140 			fprintf(stderr, "Usage:\t%s\n", usage);
    141 			fprintf(stderr,
    142 				"\t-thread\t\tswitch threads not processes\n");
    143 			fprintf(stderr,
    144 				"\t-pipe\t\tuse pipes not sched_yield()\n");
    145 			fprintf(stderr,
    146 				"\t-fpu\t\tpollute the FPU after each switch in main\n");
    147 			fprintf(stderr,
    148 				"\tnum_running\tnumber of extra processes\n");
    149 			exit(0);
    150 		}
    151 	}
    152 	if (no_test) {
    153 		if (num_low_priority > 0)
    154 			run_low_priority(num_low_priority, read_fd);
    155 		while (TRUE)
    156 			pause();
    157 	}
    158 	if (geteuid() == 0) {
    159 		struct sched_param sp;
    160 
    161 		memset(&sp, 0, sizeof sp);
    162 		sp.sched_priority = 10;
    163 		if (sched_setscheduler(0, SCHED_FIFO, &sp) != 0) {
    164 			fprintf(stderr, "Error changing to RT class\t%s\n",
    165 				ERRSTRING);
    166 			exit(1);
    167 		}
    168 		if (mlockall(MCL_CURRENT | MCL_FUTURE) != 0) {
    169 			fprintf(stderr, "Error locking pages\t%s\n", ERRSTRING);
    170 			exit(1);
    171 		}
    172 	} else
    173 		fprintf(stderr, "Not running with RT priority!\n");
    174 	/*  Give shell and login programme time to finish up and get off the run
    175 	   queue  */
    176 	usleep(200000);
    177 	if (use_pipe) {
    178 		if (pipe(fds) != 0) {
    179 			fprintf(stderr, "Error creating pipe\t%s\n", ERRSTRING);
    180 			exit(1);
    181 		}
    182 		pipe_read_fd = fds[0];
    183 		write_fd = fds[1];
    184 	}
    185 	num_hogs = hog_other_cpus();
    186 	/*  Determine overhead. Do it in a loop=2. The first iteration should warm
    187 	   the cache, the second will compute the overhead  */
    188 	for (j = 0; j < 2; ++j) {
    189 		switches1 = get_num_switches();
    190 		gettimeofday(&before, NULL);
    191 		for (i = 0; i < 20; ++i) {
    192 			if (use_pipe) {
    193 				char ch = 0;
    194 
    195 				write(pipe_write_fd, &ch, 1);
    196 				read(read_fd, &ch, 1);
    197 			} else
    198 				sched_yield();
    199 			if (frob_fpu)
    200 				++dcount;
    201 		}
    202 		gettimeofday(&after, NULL);
    203 		num_overhead_switches = get_num_switches() - switches1;
    204 		overhead = 1000000 * (after.tv_sec - before.tv_sec);
    205 		overhead += after.tv_usec - before.tv_usec;
    206 	}
    207 	use_fpu_value(dcount);
    208 	if (num_low_priority > 0)
    209 		run_low_priority(num_low_priority, read_fd);
    210 	/*  Set up for the benchmark  */
    211 	run_yielder(use_threads, read_fd);
    212 	memset(diffs, 0, sizeof diffs);
    213 	run_queue_size1 = get_run_queue_size();
    214 	total_diffs = 0;
    215 	switches1 = get_num_switches();
    216 	/*  Benchmark!  */
    217 	for (count = 0; count < MAX_ITERATIONS; ++count) {
    218 		signed long diff;
    219 
    220 		gettimeofday(&before, NULL);
    221 		/*  Generate 20 context switches  */
    222 		for (i = 0; i < 10; ++i) {
    223 			if (use_pipe) {
    224 				char ch = 0;
    225 
    226 				write(write_fd, &ch, 1);
    227 				read(read_fd, &ch, 1);
    228 			} else
    229 				sched_yield();
    230 			if (frob_fpu)
    231 				dcount += 1.0;
    232 		}
    233 		gettimeofday(&after, NULL);
    234 		diff = 1000000 * (after.tv_sec - before.tv_sec);
    235 		diff += after.tv_usec - before.tv_usec;
    236 		diffs[count] = diff;
    237 		total_diffs += diff;
    238 		if (diff < min_diff)
    239 			min_diff = diff;
    240 		if (diff > max_diff)
    241 			max_diff = diff;
    242 	}
    243 	num_yields = sched_count;
    244 	run_queue_size2 = get_run_queue_size();
    245 	num_switches = get_num_switches() - switches1;
    246 	if (!use_threads)
    247 		kill(child, SIGTERM);
    248 	fprintf(stderr, "Started %u hog processes\n", num_hogs);
    249 	fprintf(stderr, "Syscall%s overhead: %.1f us\n", frob_fpu ? "/FPU" : "",
    250 		(double)overhead / 20.0);
    251 	if (switches1 > 0)
    252 		fprintf(stderr, "Num switches during overhead check: %lu\n",
    253 			num_overhead_switches);
    254 	fprintf(stderr, "Minimum scheduling latency: %.1f (%.1f) us\n",
    255 		(double)min_diff / 20.0, (double)(min_diff - overhead) / 20.0);
    256 	median = compute_median(diffs, max_diff);
    257 	fprintf(stderr, "Median scheduling latency:  %.1f (%.1f) us\n",
    258 		(double)median / 20.0, (double)(median - overhead) / 20.0);
    259 	fprintf(stderr, "Average scheduling latency: %.1f (%.1f) us\n",
    260 		(double)total_diffs / (double)MAX_ITERATIONS / 20.0,
    261 		(double)(total_diffs - overhead * MAX_ITERATIONS) /
    262 		(double)MAX_ITERATIONS / 20.0);
    263 	fprintf(stderr, "Maximum scheduling latency: %.1f (%.1f) us\n",
    264 		(double)max_diff / 20.0, (double)(max_diff - overhead) / 20.0);
    265 	fprintf(stderr, "Run queue size: %u, %u\n",
    266 		run_queue_size1, run_queue_size2);
    267 	use_fpu_value(dcount);
    268 	if (use_threads)
    269 		fprintf(stderr, "Number of yields: %u\n", num_yields);
    270 	if (num_switches > 0)
    271 		fprintf(stderr, "Num switches: %lu\n", num_switches);
    272 
    273 	/*  Terminate all child processes  */
    274 	sigemptyset(&set);
    275 	sigaddset(&set, SIGTERM);
    276 	sigprocmask(SIG_BLOCK, &set, NULL);
    277 
    278 	kill(0, SIGTERM);
    279 	return (0);
    280 }				/*  End Function main  */
    281 
    282 static unsigned int hog_other_cpus()
    283 /*  [SUMMARY] Hog other CPUs with a high-priority job.
    284     [RETURNS] The number of hogged CPUs.
    285 */
    286 {
    287 	unsigned int count;
    288 
    289 	for (count = mt_num_processors(); count > 1; --count) {
    290 		switch (fork()) {
    291 		case 0:
    292 			/*  Child  */
    293 			while (TRUE) ;
    294 			break;
    295 		case -1:
    296 			/*  Error  */
    297 			fprintf(stderr, "Error forking\t%s\n", ERRSTRING);
    298 			kill(0, SIGTERM);
    299 			break;
    300 		default:
    301 			/*  Parent  */
    302 			break;
    303 		}
    304 	}
    305 	return mt_num_processors() - 1;
    306 }				/*  End Function hog_other_cpus  */
    307 
    308 static void run_yielder(int use_threads, int read_fd)
    309 /*  [SUMMARY] Run other process which will continuously yield.
    310     <use_threads> If TRUE, the yielding process is just a thread.
    311     <read_fd> The pipe to read the synchronisation byte from.
    312     [RETURNS] Nothing.
    313 */
    314 {
    315 	char ch;
    316 	struct sigaction new_action;
    317 #ifdef _POSIX_THREADS
    318 	pthread_t thread;
    319 
    320 	if (use_threads) {
    321 		if (pthread_create(&thread, NULL, yielder_main, NULL) != 0) {
    322 			fprintf(stderr, "Error creating thread\t%s\n",
    323 				ERRSTRING);
    324 			kill(0, SIGTERM);
    325 		}
    326 		read(read_fd, &ch, 1);
    327 		return;
    328 	}
    329 #endif
    330 	switch (child = fork()) {
    331 	case 0:
    332 		/*  Child  */
    333 		break;
    334 	case -1:
    335 		/*  Error  */
    336 		fprintf(stderr, "Error forking\t%s\n", ERRSTRING);
    337 		kill(0, SIGTERM);
    338 		break;
    339 	default:
    340 		/*  Parent  */
    341 		read(read_fd, &ch, 1);
    342 		return;
    343 		/*break; */
    344 	}
    345 	memset(&new_action, 0, sizeof new_action);
    346 	sigemptyset(&new_action.sa_mask);
    347 	new_action.sa_handler = s_term_handler;
    348 	if (sigaction(SIGTERM, &new_action, NULL) != 0) {
    349 		fprintf(stderr, "Error setting SIGTERM handler\t%s\n",
    350 			ERRSTRING);
    351 		exit(1);
    352 	}
    353 	yielder_main(NULL);
    354 }				/*  End Function run_yielder  */
    355 
    356 static void *yielder_main(void *arg)
    357 /*  [SUMMARY] Yielder function.
    358     <arg> An arbirtrary pointer. Ignored.
    359     [RETURNS] NULL.
    360 */
    361 {
    362 	char ch = 0;
    363 
    364 	sched_count = 0;
    365 	write(pipe_write_fd, &ch, 1);
    366 	while (TRUE) {
    367 		if (pipe_read_fd >= 0) {
    368 			read(pipe_read_fd, &ch, 1);
    369 			write(pipe_write_fd, &ch, 1);
    370 		} else
    371 			sched_yield();
    372 		++sched_count;
    373 	}
    374 	return (NULL);
    375 }				/*  End Function yielder_main  */
    376 
    377 static void s_term_handler()
    378 {
    379 	fprintf(stderr, "Number of yields: %u\n", sched_count);
    380 	exit(0);
    381 }				/*  End Function s_term_handler  */
    382 
    383 static void run_low_priority(unsigned int num, int read_fd)
    384 /*  [SUMMARY] Run low priority processes.
    385     <num> Number of processes.
    386     <read_fd> The pipe to read the synchronisation byte from.
    387     [RETURNS] Nothing.
    388 */
    389 {
    390 	char ch = 0;
    391 
    392 	for (; num > 0; --num) {
    393 		switch (fork()) {
    394 		case 0:
    395 			/*  Child  */
    396 			if (geteuid() == 0) {
    397 				struct sched_param sp;
    398 
    399 				memset(&sp, 0, sizeof sp);
    400 				sp.sched_priority = 0;
    401 				if (sched_setscheduler(0, SCHED_OTHER, &sp) !=
    402 				    0) {
    403 					fprintf(stderr,
    404 						"Error changing to SCHED_OTHER class\t%s\n",
    405 						ERRSTRING);
    406 					exit(1);
    407 				}
    408 			}
    409 			if (nice(20) != 0) {
    410 				fprintf(stderr, "Error nicing\t%s\n",
    411 					ERRSTRING);
    412 				kill(0, SIGTERM);
    413 			}
    414 			write(pipe_write_fd, &ch, 1);
    415 			while (TRUE)
    416 				sched_yield();
    417 			break;
    418 		case -1:
    419 			/*  Error  */
    420 			fprintf(stderr, "Error forking\t%s\n", ERRSTRING);
    421 			kill(0, SIGTERM);
    422 			break;
    423 		default:
    424 			/*  Parent  */
    425 			read(read_fd, &ch, 1);
    426 			break;
    427 		}
    428 	}
    429 }				/*  End Function run_low_priority  */
    430 
    431 static unsigned long compute_median(unsigned long values[MAX_ITERATIONS],
    432 				    unsigned long max_value)
    433 /*  [SUMMARY] Compute the median from an array of values.
    434     <values> The array of values.
    435     <max_value> The maximum value in the array.
    436     [RETURNS] The median value.
    437 */
    438 {
    439 	unsigned long count;
    440 	unsigned long median = 0;
    441 	unsigned long peak = 0;
    442 	unsigned long *table;
    443 
    444 	/*  Crude but effective  */
    445 	if ((table = calloc(max_value + 1, sizeof *table)) == NULL) {
    446 		fprintf(stderr, "Error allocating median table\n");
    447 		exit(1);
    448 	}
    449 	for (count = 0; count < MAX_ITERATIONS; ++count) {
    450 		++table[values[count]];
    451 	}
    452 	/*  Now search for peak. Position of peak is median  */
    453 	for (count = 0; count < max_value + 1; ++count) {
    454 		if (table[count] < peak)
    455 			continue;
    456 		peak = table[count];
    457 		median = count;
    458 	}
    459 	free(table);
    460 	return (median);
    461 }				/*  End Function compute_median  */
    462 
    463 static unsigned int get_run_queue_size()
    464 /*  [SUMMARY] Compute the current size of the run queue.
    465     [RETURNS] The length of the run queue.
    466 */
    467 {
    468 	int dummy_i;
    469 	unsigned int length = 0;
    470 	FILE *fp;
    471 	DIR *dp;
    472 	struct dirent *de;
    473 	char txt[64], dummy_str[64];
    474 
    475 	if ((dp = opendir("/proc")) == NULL)
    476 		return (0);
    477 	while ((de = readdir(dp)) != NULL) {
    478 		if (!isdigit(de->d_name[0]))
    479 			continue;
    480 		sprintf(txt, "/proc/%s/stat", de->d_name);
    481 		if ((fp = fopen(txt, "r")) == NULL)
    482 			return (length);
    483 		fscanf(fp, "%d %s %s", &dummy_i, dummy_str, txt);
    484 		if (txt[0] == 'R')
    485 			++length;
    486 		fclose(fp);
    487 	}
    488 	closedir(dp);
    489 	return (length);
    490 }				/*  End Function get_run_queue_size  */
    491 
    492 static unsigned long get_num_switches()
    493 /*  [SUMMARY] Get the number of context switches.
    494     [RETURNS] The number of context switches on success, else 0.
    495 */
    496 {
    497 	unsigned long val;
    498 	FILE *fp;
    499 	char line[256], name[64];
    500 
    501 	if ((fp = fopen("/proc/stat", "r")) == NULL)
    502 		return (0);
    503 	while (fgets(line, sizeof line, fp) != NULL) {
    504 		sscanf(line, "%s %lu", name, &val);
    505 		if (strcasecmp(name, "ctxt") != 0)
    506 			continue;
    507 		fclose(fp);
    508 		return (val);
    509 	}
    510 	fclose(fp);
    511 	return (0);
    512 }				/*  End Function get_num_switches  */
    513 
    514 static void use_fpu_value(double val)
    515 /*  [SUMMARY] Dummy function to consume FPU value. Fools compiler.
    516     <val> The value.
    517     [RETURNS] Nothing.
    518 */
    519 {
    520 }				/*  End Function use_fpu_value  */
    521