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