SLJ Test  1.0
System Latency Jitter Test
 All Classes Files Functions Variables Typedefs Enumerations Enumerator Macros Pages
sljtest.c
Go to the documentation of this file.
1 
75 #define _GNU_SOURCE
76 
77 #include <stdio.h>
78 #include <stdarg.h>
79 #include <stdint.h>
80 #include <stdlib.h>
81 #include <inttypes.h>
82 #include <unistd.h>
83 #include <string.h>
84 #include <math.h>
85 #include <sys/time.h>
86 
87 #ifdef _WIN32
88 #include <windows.h>
89 #include "getopt.h"
90 #else /* _WIN32 */
91 #include "replgetopt.h"
92 #endif /* _WIN32 */
93 
94 #ifdef CPU_AFFINITY
95 #define __USE_GNU
96 #include <sched.h>
97 #include "bitmask.h"
98 #endif /* CPU_AFFINITY */
99 
100 #if defined(_WIN32)
101 #include <sys/timeb.h>
102 #define WIN32_HIGHRES_TIME
103 extern int optind;
104 extern char *optarg;
105 int getopt(int, char *const *, const char *);
107 # define SLEEP_SEC(x) Sleep((x)*1000)
108 
109 # define SLEEP_MSEC(x) Sleep(x)
110 
111 #else
112 
113 # define SLEEP_SEC(x) sleep(x)
114 
115 # define SLEEP_MSEC(x) \
116  do{ \
117  if ((x) >= 1000){ \
118  sleep((x) / 1000); \
119  usleep((x) % 1000 * 1000); \
120  } \
121  else{ \
122  usleep((x)*1000); \
123  } \
124  }while (0)
125 #endif /* _WIN32 */
126 
127 
129 #define DEF_BINS 20
130 
131 #define DEF_CPU NULL
132 
133 #define DEF_OUTFILE NULL
134 
135 #define DEF_KNEE 50
136 
137 #define DEF_MIN 10
138 
139 #define DEF_OUTBUF 10000
140 
141 #define DEF_PAUSE 0
142 
143 #define DEF_RUNTIME 1
144 
145 #define DEF_SUM 0
146 
147 #define DEF_LINEWID 79
148 
152 #define rdtsc(x) \
153  do { \
154  uint32_t hi, lo; \
155  asm volatile ("rdtsc" : "=a" (lo), "=d" (hi)); \
156  x = (uint64_t)hi << 32 | lo; \
157  } while (0)
158 
160 #define ARRAY_SIZE(a) (sizeof(a)/sizeof(a[0]))
161 
163 typedef struct args_stct {
165  int bins;
166 #ifdef CPU_AFFINITY
167 
168  char *cpu;
169 #endif /* CPU_AFFINITY */
170 
171  char *outfile;
173  uint64_t knee;
175  uint64_t min;
177  int outbuf;
179  int pause;
181  int runtime;
183  int sum;
185  int linewid;
186 } args_t;
187 
189 typedef struct bin_stct {
191  uint64_t ub;
196 } bin_t;
197 
199 typedef struct outlier_stct {
201  uint64_t when;
203  uint64_t delta;
204 } outlier_t;
205 
208  DEF_BINS,
209 #ifdef CPU_AFFINITY
210  DEF_CPU,
211 #endif /* CPU_AFFINITY */
212  DEF_OUTFILE,
213  DEF_KNEE,
214  DEF_MIN,
215  DEF_OUTBUF,
216  DEF_PAUSE,
217  DEF_RUNTIME,
218  DEF_SUM,
219  DEF_LINEWID,
220 };
221 
223 const struct option OptTable[] = {
224  {"bins", required_argument, NULL, 'b'},
225 #ifdef CPU_AFFINITY
226  {"cpu", required_argument, NULL, 'c'},
227 #endif /* CPU_AFFINITY */
228  {"outfile", required_argument, NULL, 'f'},
229  {"help", no_argument, NULL, 'h'},
230  {"knee", required_argument, NULL, 'k'},
231  {"min", required_argument, NULL, 'm'},
232  {"outbuf", required_argument, NULL, 'o'},
233  {"pause", required_argument, NULL, 'p'},
234  {"runtime", required_argument, NULL, 'r'},
235  {"sum", no_argument, NULL, 's'},
236  {"width", required_argument, NULL, 'w'},
237 };
238 
239 #ifdef CPU_AFFINITY
240 const char *OptString = "b:c:f:hk:m:o:p:r:sw:";
241 const char *usage = "[-b bins] [-c cpu] [-f file] [-h] [-k knee] [-m min] [-o outbuf] [-p pause] [-r runtime] [-s] [-w width]";
242 
243 #else /* CPU_AFFINITY */
244 const char *OptString = "b:f:hk:m:o:p:r:sw:";
245 const char *usage = "[-b bins] [-f file] [-h] [-k knee] [-m min] [-o outbuf] [-p pause] [-r runtime] [-s] [-w width]";
246 #endif /* CPU_AFFINITY */
247 
249 const char *version = "SLJ Test 1.0";
250 
253 
257 FILE *outfile = NULL;
258 
259 #ifdef CPU_AFFINITY
260 
264 static void
265 set_affinity(const char* affinity) {
266  struct bitmask *bm;
267 
268  fprintf(stderr, "Requesting CPU affinity on core %s\n", affinity);
269  if ((bm=bitmask_alloc(__CPU_SETSIZE)) == NULL) {
270  fprintf(stderr, "bitmask_alloc failed\n");
271  exit(1);
272  }
273  if (bitmask_parselist(affinity, bm) != 0) {
274  fprintf(stderr, "bitmask_parselist failed\n");
275  exit(1);
276  }
277  if (sched_setaffinity(0, bitmask_nbytes(bm),
278  (cpu_set_t *)bitmask_mask(bm)) < 0) {
279  fprintf(stderr, "%s: failed to set affinity to %s\n", affinity);
280  }
281 }
282 #endif /* CPU_AFFINITY */
283 
284 #ifndef HAVE_ASPRINTF
285 
294 int
295 #define RET_MAX_LEN 10 /* Maximum length of return string */
296 asprintf(char **ret, const char *format, ...) {
297  va_list ap;
298 
299  va_start(ap, format);
300  if ((*ret=malloc(RET_MAX_LEN)) == NULL) {
301  fprintf(stderr, "asprintf: malloc() failed\n");
302  return(-1);
303  }
304  vsnprintf(*ret, RET_MAX_LEN, format, ap);
305  va_end(ap);
306  return(0);
307 }
308 #endif /* HAVE_ASPRINTF */
309 
318 char *
319 t2ts(uint64_t ticks, double tpns) {
320  double ns = ticks/tpns;
321  char *s;
322 
323  if (ns < 1E3 ) asprintf(&s, "%4.3gns", ns);
324  else if (ns < 1E6 ) asprintf(&s, "%4.3gus", ns/1E3);
325  else if (ns < 1E9 ) asprintf(&s, "%4.3gms", ns/1E6);
326  else if (ns < 1E12) asprintf(&s, "%4.3gs", ns/1E9);
327  else asprintf(&s, "Infini");
328 
329  return (s);
330 }
331 
332 /*
333  * \brief Parse command line arguments.
334  * \param argc Count of arguments as passed to main().
335  * \param argv Argument vector as passed to main().
336  * \return 0 if no parse errors, 1 otherwise.
337  */
338 int
339 args_parse(int argc, char *argv[]) {
340  int c;
341 
342  while ((c=getopt_long(argc, argv, OptString, OptTable, NULL)) != EOF) {
343  switch (c) {
344 
345  case 'b':
346  args.bins = atoi(optarg);
347  break;
348 
349 #ifdef CPU_AFFINITY
350  case 'c':
351  args.cpu = strdup(optarg);
352  break;
353 #endif /* CPU_AFFINITY */
354 
355  case 'f':
356  args.outfile = strdup(optarg);
357  break;
358 
359  case 'k':
360  args.knee = atoi(optarg);
361  break;
362 
363  case 'm':
364  args.min = atoi(optarg);
365  break;
366 
367  case 'p':
368  args.pause = atoi(optarg);
369  break;
370 
371  case 'r':
372  args.runtime = atoi(optarg);
373  break;
374 
375  case 's':
376  args.sum++;
377  break;
378 
379  case 'w':
380  args.linewid = atoi(optarg);
381  break;
382 
383  case 'h':
384  default:
385  return (1);
386  }
387  }
388  return (0);
389 }
390 
393 void
395  if ((outbuf=(outlier_t *)calloc(args.outbuf, sizeof(outlier_t))) == NULL) {
396  fprintf(stderr, "Couldn't allocate memory for outlier buffer\n");
397  exit(1);
398  }
399  if ((outfile=fopen(args.outfile, "w")) == NULL) {
400  fprintf(stderr, "Unable to create outliers file %s\n",
401  args.outfile);
402  perror(args.outfile);
403  exit(1);
404  }
405 }
406 
407 void
409  bin_t *bp;
410 
411  /* Allocate memory for histogram */
412  if ((histo=(bin_t *)malloc(sizeof(bin_t)*args.bins)) == NULL) {
413  fprintf(stderr, "Couldn't allocate memory for histogram bins\n");
414  exit(1);
415  }
416 
417  /*
418  * Fill first half of histogram table with values up to knee.
419  * Evenly divide upper bound values through knee among bins.
420  */
421  for (bp=histo; bp<histo+(args.bins/2); bp++) {
422  bp->ub = args.min+(args.knee-args.min)*(bp-histo+1)/(args.bins/2);
423  bp->delta_count = 0;
424  bp->delta_sum = 0;
425  }
426  /* Fill second half of histogram table with values above knee */
427  /* Advance each bin upper bound by 1/2 an order of magnitude */
428  uint64_t mult = args.knee;
429  for ( ; bp<histo+args.bins; bp++) {
430  bp->ub = mult*2;
431  bp->delta_count = 0;
432  bp->delta_sum = 0;
433  bp++;
434 
435  mult *= 10;
436  bp->ub = mult;
437  bp->delta_count = 0;
438  bp->delta_sum = 0;
439  }
440  histo[args.bins-1].ub = UINT64_MAX; /* Sentinel */
441 }
442 
448 int
449 main(int argc, char *argv[]) {
450  int errflag;
451  uint64_t start_us, stop_us, now_us; /* Start, stop, and time now in microseconds */
452  uint64_t start_tsc, stop_tsc; /* Start and stop in TSC ticks */
453  bin_t *bp;
454  uint64_t deltas[10], *dp;
455  uint64_t min, max; /* Min and max deltas (ticks) */
456  struct timeval now_gtod; /* Time now as timeval */
457  /* The following two headers are assumed to have the same string length */
458  const char *cnt_graph_hdr = "Time Ticks Count Percent Cumulative ";
459  const char *sum_graph_hdr = "Time Ticks Sum Percent Cumulative ";
460  const char *graph_str = "*******************************************************************";
461  outlier_t *obp; /* Pointer to next open entry in outlier buffer */
462  int didwrap; /* True when outlier buffer wrapped around */
463 
464  errflag = args_parse(argc, argv);
465 
466  /* Argument validity checks */
467  if (args.knee <= args.min) {
468  fprintf(stderr, "Min (%" PRIu64
469  ") must be < knee (%" PRIu64 ")\n",
470  args.min, args.knee);
471  errflag++;
472  }
473  if (args.knee-args.min < args.bins/2) {
474  fprintf(stderr, "Too few (%" PRIu64
475  ") discrete values between min (%" PRIu64
476  ") and knee (%" PRIu64 ") for linear histogram bins (%d)\n",
477  args.knee-args.min, args.min, args.knee, args.bins/2);
478  errflag++;
479  }
480  if (args.linewid < strlen(cnt_graph_hdr)+1) {
481  fprintf(stderr, "Minimum line width is %zd\n", strlen(cnt_graph_hdr)+1);
482  errflag++;
483  }
484  if (args.linewid > strlen(cnt_graph_hdr)+strlen(graph_str)) {
485  fprintf(stderr, "Maximum line width is %zd\n",
486  strlen(cnt_graph_hdr)+strlen(graph_str));
487  errflag++;
488  }
489 
490  if (errflag) {
491  fprintf(stderr, "%s\n%s %s\n", version, argv[0], usage);
492  exit(1);
493  }
494 
495 #ifdef CPU_AFFINITY
496  if (args.cpu != NULL)
497  set_affinity(args.cpu);
498 #endif /* CPU_AFFINITY */
499 
500  if (args.outfile!=NULL && args.outbuf!=0) {
501  outliers_setup(); /* Set up memory and output file for outliers */
502  }
503 
504  histo_setup(); /* Set up histogram memory and data structures */
505 
506  /*
507  * XXX Note that it might get much eaiser to modularize this if in the
508  * future if we set up for multiple threads with a per-thread struct here.
509  */
510 
511  uint64_t timing_ticks = 0; /* TSC ticks actually spent in timing measurements */
512  uint64_t delta_count = 0; /* Global count of deltas taken */
513  uint64_t delta_sum = 0; /* Global sum of TSC deltas measured */
514  min = UINT64_MAX;
515  max = 0;
516 
517  obp = outbuf;
518  didwrap = 0;
519 
520  /*
521  * Variables for computing average and standard deviation.
522  * See http://en.wikipedia.org/wiki/Algorithms_for_calculating_variance#On-line_algorithm
523  */
524  double avg = 0.0; /* Average delta (ticks) */
525  double last_avg; /* Last average (needed for deviation */
526  double svn = 0.0; /* Standard Variance Numerator */
527 
528  rdtsc(start_tsc);
529  gettimeofday(&now_gtod, NULL);
530  start_us = now_gtod.tv_sec * 1000000 + now_gtod.tv_usec;
531  stop_us = start_us + 1000000*args.runtime;
532 
533  do {
534  dp = deltas;
535 
536  if (args.pause)
537  SLEEP_MSEC(args.pause);
538 
539  register uint64_t t0, t1, t2, t3, t4, t5, t6, t7, t8, t9, t10;
540 
541  /*
542  * Take a block of 11 timestamps and compute their differences into
543  * a 10-element array.
544  */
545  /* Do an "unrolled loop" so there's no branching or other work */
546  rdtsc(t0);
547  rdtsc(t1);
548  rdtsc(t2);
549  rdtsc(t3);
550  rdtsc(t4);
551  rdtsc(t5);
552  rdtsc(t6);
553  rdtsc(t7);
554  rdtsc(t8);
555  rdtsc(t9);
556  rdtsc(t10);
557  *dp++ = t1 -t0;
558  *dp++ = t2 -t1;
559  *dp++ = t3 -t2;
560  *dp++ = t4 -t3;
561  *dp++ = t5 -t4;
562  *dp++ = t6 -t5;
563  *dp++ = t7 -t6;
564  *dp++ = t8 -t7;
565  *dp++ = t9 -t8;
566  *dp++ = t10-t9;
567 
568  timing_ticks += t10-t0;
569 
570  /*
571  * Now that we're out of the timing loop, we can take all the
572  * CPU we need for analysis.
573  */
574  for (dp=deltas; dp<deltas+ARRAY_SIZE(deltas); dp++) {
575  if (*dp < min)
576  min = *dp;
577  if (*dp > max)
578  max = *dp;
579 
580  /* Find bin to count this delta */
581  /* Note: no end test is needed because of infinite sentinel */
582  for (bp=histo; *dp>bp->ub; bp++) {}
583 
584  bp->delta_count++;
585  bp->delta_sum += *dp;
586 
587  delta_count++;
588  delta_sum += *dp;
589 
590  last_avg = avg;
591  avg += ((double)*dp-avg)/delta_count;
592  svn += ((double)*dp-avg)*((double)*dp-last_avg);
593 
594  /* If an outlier should be recorded */
595  if (outbuf!=NULL && *dp>args.knee) {
596  /* Assume it happened in middle of block */
597  obp->when = t5;
598  obp->delta = *dp;
599  obp++;
600  /* Wrap around if needed */
601  if (obp-outbuf >= args.outbuf) {
602  obp = outbuf;
603  didwrap = 1;
604  }
605  }
606  }
607  rdtsc(stop_tsc);
608  gettimeofday(&now_gtod, NULL);
609  now_us = now_gtod.tv_sec * 1000000 + now_gtod.tv_usec;
610 
611  } while (now_us < stop_us);
612 
613  /* Compute Ticks Per NanoSecond for duration of test */
614  double tpns = (stop_tsc-start_tsc)/1000.0/(stop_us-start_us);
615 
616  /* Print histogram headers */
617  printf("%sGraph ln(Count-e)\n", (args.sum) ? sum_graph_hdr : cnt_graph_hdr);
618 
619  /* Find maximum delta count and sum in any histogram bin for scaling graph */
620  uint64_t max_count=0, max_sum=0;
621  for (bp=histo; bp<histo+args.bins; bp++) {
622  if (bp->delta_count > max_count)
623  max_count = bp->delta_count;
624  if (bp->delta_sum > max_sum )
625  max_sum = bp->delta_sum ;
626  }
627 
628  /* Find a graph scaling value so that the maximum bin is full line width */
629  double graph_scale;
630  if (args.sum) {
631  graph_scale = (double)(args.linewid-strlen(cnt_graph_hdr))/log(((double)max_sum )-M_E);
632  } else {
633  graph_scale = (double)(args.linewid-strlen(cnt_graph_hdr))/log(((double)max_count)-M_E);
634  }
635 
636  /* Print histogram */
637  uint64_t c_count = 0; /* Cumulative delta count as we step through bins */
638  uint64_t mid_count = 0; /* Cumulative delta count at histogram midpoint */
639  uint64_t c_sum = 0; /* Cumulative delta sum as we step through bins */
640  uint64_t mid_sum = 0; /* Cumulative delta sum at histogram midpoint */
641  for (bp=histo; bp<histo+args.bins; bp++) {
642  /*
643  * If at midpoint in histogram, record c_count for
644  * knee tuning advice later.
645  */
646  if (bp == histo+(args.bins/2)) {
647  mid_count = c_count;
648  mid_sum = c_sum;
649  }
650 
651  c_count += bp->delta_count;
652  c_sum += bp->delta_sum;
653 
654  /* Format bin upper bound nicely into ub_str */
655  char *ub_str, ubbuf[99];
656  if (bp->ub == UINT64_MAX)
657  ub_str = "Infinite";
658  else {
659  sprintf(ubbuf, "%-8" PRIu64, bp->ub);
660  ub_str = ubbuf;
661  }
662 
663  /*
664  * Compute a logrithmic function on bin delta count or sum
665  * that looks good.
666  */
667  int graphwid;
668  if (args.sum) {
669  graphwid = graph_scale*log(((double)bp->delta_sum )-M_E);
670  } else {
671  graphwid = graph_scale*log(((double)bp->delta_count)-M_E);
672  }
673 
674  if (graphwid < 0)
675  graphwid = 0;
676  /*
677  * Any nonzero delta count deserves a star, even though floating
678  * point sometimes rounds down to zero stars.
679  */
680  if (graphwid==0 && bp->delta_count!=0)
681  graphwid = 1;
682 
683  /* Print a row for each bin */
684  if (args.sum) {
685  printf("%s %s %-12d %7.4f%% %8.4f%% %*.*s\n",
686  t2ts(bp->ub, tpns), ub_str, bp->delta_sum,
687  100.0*bp->delta_sum/delta_sum, 100.0*c_sum/delta_sum,
688  graphwid, graphwid, graph_str);
689  } else {
690  printf("%s %s %-12d %7.4f%% %8.4f%% %*.*s\n",
691  t2ts(bp->ub, tpns), ub_str, bp->delta_count,
692  100.0*bp->delta_count/delta_count, 100.0*c_count/delta_count,
693  graphwid, graphwid, graph_str);
694  }
695 
696  /* Separate lower 1/2 of histogram from upper 1/2 */
697  if (bp-histo+1 == args.bins/2)
698  printf("\n");
699  }
700 
701  /*
702  * Population variance is svn sum computed above over size of population.
703  * Population standard deviation is square root of population variance.
704  */
705  double std_dev = sqrt(svn/delta_count);
706 
707  /* Print some useful statistics */
708  printf("\nTiming was measured for %s, %5.2f%% of runtime\n",
709  t2ts(timing_ticks, tpns), 100.0*timing_ticks/(stop_tsc-start_tsc));
710  printf("CPU speed measured : %7.2f MHz over %" PRIu64 " iterations\n",
711  (double)(stop_tsc-start_tsc)/(stop_us-start_us), delta_count);
712  printf("Min / Average / Std Dev / Max : %" PRIu64 " / %" PRIu64 " / %3.0f / %" PRIu64 " ticks\n",
713  min, delta_sum/delta_count, std_dev, max);
714  printf("Min / Average / Std Dev / Max : %s / %s / %s / %s\n",
715  t2ts(min, tpns), t2ts(delta_sum/delta_count, tpns),
716  t2ts((uint64_t)std_dev, tpns), t2ts(max, tpns));
717 
718  /* Analyze histogram to give advice on setting better min and knee */
719  if (min<args.min || args.min<0.80*min) {
720  printf("Recommend min setting of %3.0f ticks\n", 0.80*min);
721  }
722  if (outbuf==NULL && 100.0*mid_count/delta_count<90.0) {
723  printf("Recommend increasing knee setting from %" PRIu64 " ticks\n",
724  args.knee);
725  }
726  if (outbuf==NULL && 100.0*mid_count/delta_count>99.0) {
727  printf("Recommend decreasing knee setting from %" PRIu64 " ticks\n",
728  args.knee);
729  }
730 
731  /* Dump log of outliers to outfile */
732  /* XXX Why isn't this a compound && test like the above call to outliers_setup()? */
733  if (outbuf != NULL) {
734  if (didwrap) {
735  printf("Recommend increasing knee setting from %" PRIu64 " ticks\n",
736  args.knee);
737  } else if (obp-outbuf < args.outbuf/4) {
738  printf("Recommend decreasing knee setting from %" PRIu64 " ticks\n",
739  args.knee);
740  }
741  for (obp=outbuf; obp<outbuf+args.outbuf; obp++) {
742  if (obp->when == 0)
743  continue;
744  fprintf(outfile, "%f, %f\n",
745  (obp->when-start_tsc)/tpns/1000000.0,
746  obp->delta/tpns/1000.0);
747  }
748  fclose(outfile);
749  }
750  return (0);
751 }