[alt.sources] [comp.protocols.time.ntp] Unix clock resolution revisited

hakanson@CSE.OGI.EDU (Marion Hakanson) (09/15/90)

Archive-name: clockres/13-Sep-90
Original-posting-by: hakanson@CSE.OGI.EDU (Marion Hakanson)
Original-subject: Unix clock resolution revisited
Reposted-by: emv@math.lsa.umich.edu (Edward Vielmetti)

[Reposted from comp.protocols.time.ntp.
Comments on this service to emv@math.lsa.umich.edu (Edward Vielmetti).]

Well, I got tired of trying to convert microseconds into the power
of two precision that ntpd and xntpd expect in their configuration
files, so I added an option to my recently-posted clockres program
to do the work for you.

This program turns out to be more useful than I thought.  On machines
with clock resolutions better than clockres can measure (like the old
SPARCstation-1), clockres ends up measuring the span of time between
two gettimeofday(2) system calls.  For consumption of any single
threaded user program (like ntpd and xntpd), this turns out to be the
perceived resolution of the clock, even if the underlying hardware
clock is better.  So the NTP precision value reported by clockres
is probably a good value to tell ntpd or xntpd to advertise (and on
most boxes, it agrees with the kernel hz value).

More trivia: A friend tells me that a Convex C2 shows up with a
resolution of 25 microseconds (again, this is the time between two
system calls -- the underlying hardware clock is better).  Who's next?
Note that this is only three times better than a SPARC-1, but I hear
you can do a lot more between system calls on a Convex.

The program is included below.  I'm still ambivalent about collecting
results.  While I'm curious about various machines (especially
"exotic" boxes or those with exceptional results), I'm not anxious to
have everyone who has a Sun workstation or a [34]86 box sending me
their results.  Certainly this program could be included with the
other NTP programs in the FTP archives, as it may come in handy for
those who have a lesser-known machine/OS, and it would save
maintaining a database of reported results.  Have at it, Louie.

Hey, don't everybody run this at once -- you'll slow down the universe
(especially if it runs SunOS :-).

-- 
Marion Hakanson         Domain: hakanson@cse.ogi.edu
                        UUCP  : {hp-pcd,tektronix}!ogicse!hakanson

===============================================
#ifndef lint
char rcsid[] = "$Id: clockres.c,v 1.11 90/09/12 16:26:27 hakanson Exp $";
#endif /* lint */

/*
 * Determine the resolution of the system clock (gettimeofday(2)).
 *   Marion Hakanson (hakanson@cse.ogi.edu)
 *   Oregon Graduate Institute of Science and Technology
 *
 * The idea is to call gettimeofday(2), then repeatedly call it again
 * until you get a different value.  The difference between the two
 * values should be the resolution (precision) of the system clock.
 * A little noise sometimes creeps in, due to adjtime(2)'s being done,
 * but in practice this appears rarely and can be factored out by running
 * this test program repeatedly (say 10 times).  There is a "-v" option
 * to print out the two differing timestamps (and the number of calls
 * it took before a difference was detected).  You may occasionally
 * see two (but never more) of these printed out before a result is
 * announced -- this is to avoid anomalies with comparing microseconds
 * when a wrap to the next full second has occurred.  The "-n" option
 * will convert the result to the NTP precision (power of two).
 *
 * The big flaw to this approach is that most 4.3bsd-based systems have
 * in their kernels a microtime() routine which is supposed to return
 * the current time to the microsecond.  A "good" implementation would
 * (e.g.) read a hardware interval timer to determine how many usec's
 * had passed since the last clock tick.  But much hardware seems to
 * lack such a timer, and as a result, most implementations of microtime()
 * fake it by adding a microsecond to the current time for each subsequent
 * call to microtime() -- just to ensure that two invocations never return
 * exactly the same value (the fake microseconds go away at the next clock
 * tick) .  Since most machines (known to mortals) these days cannot make
 * a system call in one microsecond, this program has a hack in it to
 * detect these "fake" microtime() implementations by adding the number
 * of gettimeofday(2) calls to the initial timestamp before comparing
 * with the most recent timestamp.  A ">=" test is used to work with
 * older bsd's (like 4.2), where two subsequent timestamps often are
 * the same (within the value of a tick), and the "-f" option disables
 * the "fake" microtime() workaround.  More irregular "fakes" will no
 * doubt be reported as terrific (but erroneous) clock resolutions.
 *
 * A small flaw is that if a machine has a clock resolution finer than
 * the time it takes to make a single pass through the loop, then the
 * program reports that value as the resolution of the clock, rather
 * than the actual resolution.  A warning about this is printed when
 * a real difference is detected after only one pass (it says "... XX
 * microseconds or better" instead of just "... XX microseconds").
 * Again, repeated runs can show this condition if the value seems to
 * fluctuate by a few microseconds and/or vary with system load (a
 * "genuine" result seems to stay rock solid across invocations).
 * In practice, few real machines seem to encounter this behavior,
 * and you should count yourself lucky if you have one.
 */

#include <stdio.h>
#include <sys/time.h>

extern int getopt();

main(argc, argv)
    int argc;  char *argv[];
{
register int fakemicro;
int ntpprecision;
int verbose;
int again;
struct timeval t1, t2;
register int calls;
unsigned long diff, quotient;
int power;
int c;


verbose = 0;
fakemicro = 1;
ntpprecision = 0;

while ( (c = getopt(argc, argv, "fnv")) != EOF ) {
  switch ( c ) {
    case 'f':
      fakemicro = 0;
      break;
    case 'n':
      ntpprecision = 1;
      break;
    case 'v':
      verbose = 1;
      break;
    case '?':
      fprintf(stderr, "usage: %s [-fnv]\n", argv[0]);
      exit(1);
  }
}
   

again = 0;

top:

if (again > 1) {
  fprintf (stderr, "%s: More than one second passed, giving up.\n", argv[0]);
  exit(1);
}

again++;

calls = 0;

(void) gettimeofday(&t1, NULL);

do {
  calls++;
  (void) gettimeofday(&t2, NULL);
} while ( (t1.tv_sec == t2.tv_sec)
	 && ( fakemicro ? ((t1.tv_usec+calls) >= t2.tv_usec)
	 		: (t1.tv_usec == t2.tv_usec) ) );

if (verbose) {
  printf ("t1 %d.%06.6d t2 %d.%06.6d calls %d\n",
	t1.tv_sec, t1.tv_usec, t2.tv_sec, t2.tv_usec, calls);
}

/* Check for wrap to next second */
if (t1.tv_sec != t2.tv_sec)
  goto top;

diff = t2.tv_usec - t1.tv_usec;

printf ("Clock step resolution %d microseconds", diff);

if ( calls == 1 )
  printf (" or better");

if ( ntpprecision ) {
  quotient = 1000000;
  power = 0;
  
  while ( quotient > diff ) {
    quotient >>= 1;
    --power;
  }
  printf (" (NTP precision %d)", power);
}

printf("\n");

exit(0);
}
===============================================