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@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
main(int argc,char ** argv)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@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
hog_other_cpus()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
run_yielder(int use_threads,int read_fd)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
yielder_main(void * arg)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
s_term_handler()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
run_low_priority(unsigned int num,int read_fd)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
compute_median(unsigned long values[MAX_ITERATIONS],unsigned long max_value)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
get_run_queue_size()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
get_num_switches()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
use_fpu_value(double val)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