]> sjero.net Git - wget/commitdiff
[svn] Better selection of POSIX clocks.
authorhniksic <devnull@localhost>
Fri, 8 Apr 2005 18:19:20 +0000 (11:19 -0700)
committerhniksic <devnull@localhost>
Fri, 8 Apr 2005 18:19:20 +0000 (11:19 -0700)
src/ChangeLog
src/ptimer.c
src/ptimer.h
src/retr.c

index 1b74862d61b16aca07515d8e4dfb0a11c658f0a8..01fcb962a76519a317da7c527d8f0fb5831d059a 100644 (file)
@@ -1,3 +1,20 @@
+2005-04-08  Hrvoje Niksic  <hniksic@xemacs.org>
+
+       * ptimer.c (posix_init): Be smarter about choosing clocks.  In
+       decreasing order of preference, use CLOCK_MONOTONIC,
+       CLOCK_HIGHRES, and CLOCK_REALTIME.
+       (ptimer_allocate): Removed.
+
+       * ptimer.c: Refactor the code by cleanly separating the
+       architecture-dependent code from the architecture-independent
+       code.
+
+2005-04-08  Hrvoje Niksic  <hniksic@xemacs.org>
+
+       * ptimer.c (ptimer_init): Explicitly check that
+       _POSIX_MONOTONIC_CLOCK is *both* defined and >=0.  (Undefined
+       symbols are >=0.)
+
 2005-04-08  Hrvoje Niksic  <hniksic@xemacs.org>
 
        * ptimer.c (ptimer_diff): Fix typo affecting Windows build.
index 4fb54cf81c2d502e0e38fb37d326c5bb2322c148..d63e69e27821b11432aa1285fd00ebfbb3a390fc 100644 (file)
@@ -73,13 +73,8 @@ extern int errno;
 #endif
 
 /* Depending on the OS and availability of gettimeofday(), one and
-   only one of PTIMER_WINDOWS, PTIMER_GETTIMEOFDAY, or PTIMER_TIME will
-   be defined.
-
-   Virtually all modern Unix systems will define PTIMER_GETTIMEOFDAY;
-   Windows will use PTIMER_WINDOWS.  PTIMER_TIME is a catch-all method
-   for non-Windows systems without gettimeofday, such as DOS or really
-   old Unix-like systems.  */
+   only one of PTIMER_POSIX, PTIMER_GETTIMEOFDAY, PTIMER_WINDOWS, or
+   PTIMER_TIME will be defined.  */
 
 #undef PTIMER_POSIX
 #undef PTIMER_GETTIMEOFDAY
@@ -100,59 +95,202 @@ extern int errno;
 # endif
 #endif
 
-/* The type ptimer_system_time holds system time. */
-
 #ifdef PTIMER_POSIX
+/* Elapsed time measurement using POSIX timers: system time is held in
+   struct timespec, time is retrieved using clock_gettime, and
+   resolution using clock_getres.
+
+   This method is used on Unix systems that implement POSIX
+   timers.  */
+
 typedef struct timespec ptimer_system_time;
+
+#define IMPL_init posix_init
+#define IMPL_measure posix_measure
+#define IMPL_diff posix_diff
+#define IMPL_resolution posix_resolution
+
+/* clock_id to use for POSIX clocks.  This tries to use
+   CLOCK_MONOTONIC where available, CLOCK_REALTIME otherwise.  */
+static int posix_clock_id;
+
+/* Resolution of the clock, in milliseconds. */
+static double posix_millisec_resolution;
+
+/* Decide which clock_id to use.  */
+
+static void
+posix_init (void)
+{
+  /* List of clocks we want to support: some systems support monotonic
+     clocks, Solaris has "high resolution" clock (sometimes
+     unavailable except to superuser), and all should support the
+     real-time clock.  */
+#define NO_SYSCONF_CHECK -1
+  static const struct {
+    int id;
+    int sysconf_name;
+  } clocks[] = {
+#if defined(_POSIX_MONOTONIC_CLOCK) && _POSIX_MONOTONIC_CLOCK >= 0
+    { CLOCK_MONOTONIC, _SC_MONOTONIC_CLOCK },
+#endif
+#ifdef CLOCK_HIGHRES
+    { CLOCK_HIGHRES, NO_SYSCONF_CHECK },
 #endif
+    { CLOCK_REALTIME, NO_SYSCONF_CHECK },
+  };
+  int i;
+
+  /* Determine the clock we can use.  For a clock to be usable, it
+     must be confirmed with sysconf (where applicable) and with
+     clock_getres.  If no clock is found, CLOCK_REALTIME is used.  */
+
+  for (i = 0; i < countof (clocks); i++)
+    {
+      struct timespec r;
+      if (clocks[i].sysconf_name != NO_SYSCONF_CHECK)
+       if (sysconf (clocks[i].sysconf_name) < 0)
+         continue;             /* sysconf claims this clock is unavailable */
+      if (clock_getres (clocks[i].id, &r) < 0)
+       continue;               /* clock_getres doesn't work for this clock */
+      posix_clock_id = clocks[i].id;
+      posix_millisec_resolution = r.tv_sec * 1000.0 + r.tv_nsec / 1000000.0;
+      /* Guard against broken clock_getres returning nonsensical
+        values.  */
+      if (posix_millisec_resolution == 0)
+       posix_millisec_resolution = 1;
+      break;
+    }
+  if (i == countof (clocks))
+    {
+      /* If no clock was found, it means that clock_getres failed for
+        the realtime clock.  */
+      logprintf (LOG_NOTQUIET, _("Cannot get REALTIME clock frequency: %s\n"),
+                strerror (errno));
+      /* Use CLOCK_REALTIME, but invent a plausible resolution. */
+      posix_clock_id = CLOCK_REALTIME;
+      posix_millisec_resolution = 1;
+    }
+}
+
+static inline void
+posix_measure (ptimer_system_time *pst)
+{
+  clock_gettime (posix_clock_id, pst);
+}
+
+static inline double
+posix_diff (ptimer_system_time *pst1, ptimer_system_time *pst2)
+{
+  return ((pst1->tv_sec - pst2->tv_sec) * 1000.0
+         + (pst1->tv_nsec - pst2->tv_nsec) / 1000000.0);
+}
+
+static inline double
+posix_resolution (void)
+{
+  return posix_millisec_resolution;
+}
+#endif /* PTIMER_POSIX */
 
 #ifdef PTIMER_GETTIMEOFDAY
+/* Elapsed time measurement using gettimeofday: system time is held in
+   struct timeval, retrieved using gettimeofday, and resolution is
+   unknown.
+
+   This method is used Unix systems without POSIX timers.  */
+
 typedef struct timeval ptimer_system_time;
-#endif
+
+#define IMPL_measure gettimeofday_measure
+#define IMPL_diff gettimeofday_diff
+#define IMPL_resolution gettimeofday_resolution
+
+static inline void
+gettimeofday_measure (ptimer_system_time *pst)
+{
+  gettimeofday (pst, NULL);
+}
+
+static inline double
+gettimeofday_diff (ptimer_system_time *pst1, ptimer_system_time *pst2)
+{
+  return ((pst1->tv_sec - pst2->tv_sec) * 1000.0
+         + (pst1->tv_usec - pst2->tv_usec) / 1000.0);
+}
+
+static inline double
+gettimeofday_resolution (void)
+{
+  /* Granularity of gettimeofday varies wildly between architectures.
+     However, it appears that on modern machines it tends to be better
+     than 1ms.  Assume 100 usecs.  */
+  return 0.1;
+}
+#endif /* PTIMER_GETTIMEOFDAY */
 
 #ifdef PTIMER_TIME
+/* Elapsed time measurement using the time(2) call: system time is
+   held in time_t, retrieved using time, and resolution is 1 second.
+
+   This method is a catch-all for non-Windows systems without
+   gettimeofday -- e.g. DOS or really old or non-standard Unix
+   systems.  */
+
 typedef time_t ptimer_system_time;
-#endif
+
+#define IMPL_measure time_measure
+#define IMPL_diff time_diff
+#define IMPL_resolution time_resolution
+
+static inline void
+time_measure (ptimer_system_time *pst)
+{
+  time (pst);
+}
+
+static inline double
+time_diff (ptimer_system_time *pst1, ptimer_system_time *pst2)
+{
+  return 1000.0 * (*pst1 - *pst2);
+}
+
+static inline double
+time_resolution (void)
+{
+  return 1;
+}
+#endif /* PTIMER_TIME */
 
 #ifdef PTIMER_WINDOWS
+/* Elapsed time measurement on Windows: where high-resolution timers
+   are available, time is stored in a LARGE_INTEGER and retrieved
+   using QueryPerformanceCounter.  Otherwise, it is stored in a DWORD
+   and retrieved using GetTickCount.
+
+   This method is used on Windows.  */
+
 typedef union {
   DWORD lores;          /* In case GetTickCount is used */
   LARGE_INTEGER hires;  /* In case high-resolution timer is used */
 } ptimer_system_time;
-#endif
 
-struct ptimer {
-  /* Whether the start time has been set. */
-  int initialized;
-
-  /* The starting point in time which, subtracted from the current
-     time, yields elapsed time. */
-  ptimer_system_time start;
+#define IMPL_init windows_init
+#define IMPL_measure windows_measure
+#define IMPL_diff windows_diff
+#define IMPL_resolution windows_resolution
 
-  /* The most recent elapsed time, calculated by ptimer_measure().
-     Measured in milliseconds.  */
-  double elapsed_last;
-
-  /* Approximately, the time elapsed between the true start of the
-     measurement and the time represented by START.  */
-  double elapsed_pre_start;
-};
-
-#ifdef PTIMER_WINDOWS
 /* Whether high-resolution timers are used.  Set by ptimer_initialize_once
-   the first time ptimer_allocate is called. */
+   the first time ptimer_new is called. */
 static int windows_hires_timers;
 
 /* Frequency of high-resolution timers -- number of updates per
-   millisecond.  Calculated the first time ptimer_allocate is called
+   millisecond.  Calculated the first time ptimer_new is called
    provided that high-resolution timers are available. */
 static double windows_hires_msfreq;
 
-/* The first time a timer is created, determine whether to use
-   high-resolution timers. */
-
 static void
-ptimer_init (void)
+windows_init (void)
 {
   LARGE_INTEGER freq;
   freq.QuadPart = 0;
@@ -163,79 +301,74 @@ ptimer_init (void)
       windows_hires_msfreq = (double) freq.QuadPart / 1000.0;
     }
 }
-#define PTIMER_INIT_DEFINED
-#endif /* PTIMER_WINDOWS */
-
-#ifdef PTIMER_POSIX
-
-/* clock_id to use for POSIX clocks.  This tries to use
-   CLOCK_MONOTONIC where available, CLOCK_REALTIME otherwise.  */
-static int posix_clock_id;
 
-/* Resolution of the clock, in milliseconds. */
-static double posix_resolution;
-
-/* Check whether the monotonic clock is available, and retrieve POSIX
-   timer resolution.  */
+static inline void
+windows_measure (ptimer_system_time *pst)
+{
+  if (windows_hires_timers)
+    QueryPerformanceCounter (&pst->hires);
+  else
+    /* Where hires counters are not available, use GetTickCount rather
+       GetSystemTime, because it is unaffected by clock skew and
+       simpler to use.  Note that overflows don't affect us because we
+       never use absolute values of the ticker, only the
+       differences.  */
+    pst->lores = GetTickCount ();
+}
 
-static void
-ptimer_init (void)
+static inline double
+windows_diff (ptimer_system_time *pst1, ptimer_system_time *pst2)
 {
-  struct timespec res;
+  if (windows_hires_timers)
+    return (pst1->hires.QuadPart - pst2->hires.QuadPart) / windows_hires_msfreq;
+  else
+    return pst1->lores - pst2->lores;
+}
 
-#if _POSIX_MONOTONIC_CLOCK >= 0                   /* -1 means not supported */
-  if (sysconf (_SC_MONOTONIC_CLOCK) > 0)
-    posix_clock_id = CLOCK_MONOTONIC;
+static double
+windows_resolution (void)
+{
+  if (windows_hires_timers)
+    return 1.0 / windows_hires_msfreq;
   else
-#endif
-    posix_clock_id = CLOCK_REALTIME;
+    return 10;                 /* according to MSDN */
+}
+#endif /* PTIMER_WINDOWS */
+\f
+/* The code below this point is independent of timer implementation. */
 
-  if (clock_getres (posix_clock_id, &res) < 0)
-    {
-      logprintf (LOG_NOTQUIET, _("Cannot read clock resolution: %s\n"),
-                strerror (errno));
-      /* Assume 1 ms resolution */
-      res.tv_sec = 0;
-      res.tv_nsec = 1000000;
-    }
+struct ptimer {
+  /* Whether the start time has been set. */
+  int initialized;
 
-  posix_resolution = res.tv_sec * 1000.0 + res.tv_nsec / 1000000.0;
-  /* Guard against clock_getres reporting 0 resolution; after all, it
-     can be used for division.  */
-  if (posix_resolution == 0)
-    posix_resolution = 1;
-}
-#define PTIMER_INIT_DEFINED
-#endif
+  /* The starting point in time which, subtracted from the current
+     time, yields elapsed time. */
+  ptimer_system_time start;
 
-/* Allocate a timer.  Calling ptimer_read on the timer will return
-   zero.  It is not legal to call ptimer_measure with a freshly
-   allocated timer -- use ptimer_reset first.  */
+  /* The most recent elapsed time, calculated by ptimer_measure().
+     Measured in milliseconds.  */
+  double elapsed_last;
+
+  /* Approximately, the time elapsed between the true start of the
+     measurement and the time represented by START.  This is used for
+     adjustment when clock skew is detected.  */
+  double elapsed_pre_start;
+};
+
+/* Allocate a new timer and reset it.  Return the new timer. */
 
 struct ptimer *
-ptimer_allocate (void)
+ptimer_new (void)
 {
-  struct ptimer *wt;
-
-#ifdef PTIMER_INIT_DEFINED
+  struct ptimer *wt = xnew0 (struct ptimer);
+#ifdef IMPL_init
   static int init_done;
   if (!init_done)
     {
       init_done = 1;
-      ptimer_init ();
+      IMPL_init ();
     }
 #endif
-
-  wt = xnew0 (struct ptimer);
-  return wt;
-}
-
-/* Allocate a new timer and reset it.  Return the new timer. */
-
-struct ptimer *
-ptimer_new (void)
-{
-  struct ptimer *wt = ptimer_allocate ();
   ptimer_reset (wt);
   return wt;
 }
@@ -249,39 +382,6 @@ ptimer_destroy (struct ptimer *wt)
   xfree (wt);
 }
 
-/* Store system time to PST.  */
-
-static void
-ptimer_sys_set (ptimer_system_time *pst)
-{
-#ifdef PTIMER_POSIX
-  clock_gettime (posix_clock_id, pst);
-#endif
-
-#ifdef PTIMER_GETTIMEOFDAY
-  gettimeofday (pst, NULL);
-#endif
-
-#ifdef PTIMER_TIME
-  time (pst);
-#endif
-
-#ifdef PTIMER_WINDOWS
-  if (windows_hires_timers)
-    {
-      QueryPerformanceCounter (&pst->hires);
-    }
-  else
-    {
-      /* Where hires counters are not available, use GetTickCount rather
-         GetSystemTime, because it is unaffected by clock skew and simpler
-         to use.  Note that overflows don't affect us because we never use
-         absolute values of the ticker, only the differences.  */
-      pst->lores = GetTickCount ();
-    }
-#endif
-}
-
 /* Reset timer WT.  This establishes the starting point from which
    ptimer_read() will return the number of elapsed milliseconds.
    It is allowed to reset a previously used timer.  */
@@ -290,37 +390,12 @@ void
 ptimer_reset (struct ptimer *wt)
 {
   /* Set the start time to the current time. */
-  ptimer_sys_set (&wt->start);
+  IMPL_measure (&wt->start);
   wt->elapsed_last = 0;
   wt->elapsed_pre_start = 0;
   wt->initialized = 1;
 }
 
-static double
-ptimer_diff (ptimer_system_time *pst1, ptimer_system_time *pst2)
-{
-#ifdef PTIMER_POSIX
-  return ((pst1->tv_sec - pst2->tv_sec) * 1000.0
-         + (pst1->tv_nsec - pst2->tv_nsec) / 1000000.0);
-#endif
-
-#ifdef PTIMER_GETTIMEOFDAY
-  return ((pst1->tv_sec - pst2->tv_sec) * 1000.0
-         + (pst1->tv_usec - pst2->tv_usec) / 1000.0);
-#endif
-
-#ifdef PTIMER_TIME
-  return 1000 * (*pst1 - *pst2);
-#endif
-
-#ifdef WINDOWS
-  if (windows_hires_timers)
-    return (pst1->hires.QuadPart - pst2->hires.QuadPart) / windows_hires_msfreq;
-  else
-    return pst1->lores - pst2->lores;
-#endif
-}
-
 /* Measure the elapsed time since timer creation/reset and return it
    to the caller.  The value remains stored for further reads by
    ptimer_read.
@@ -340,8 +415,8 @@ ptimer_measure (struct ptimer *wt)
 
   assert (wt->initialized != 0);
 
-  ptimer_sys_set (&now);
-  elapsed = wt->elapsed_pre_start + ptimer_diff (&now, &wt->start);
+  IMPL_measure (&now);
+  elapsed = wt->elapsed_pre_start + IMPL_diff (&now, &wt->start);
 
   /* Ideally we'd just return the difference between NOW and
      wt->start.  However, the system timer can be set back, and we
@@ -356,8 +431,8 @@ ptimer_measure (struct ptimer *wt)
      elapsed time and increment all future calculations by that
      amount.
 
-     This cannot happen with Windows and CLOCK_MONOTONIC timers, but
-     the check is not expensive.  */
+     This cannot happen with Windows and POSIX monotonic/highres
+     timers, but the check is not expensive.  */
 
   if (elapsed < wt->elapsed_last)
     {
@@ -379,34 +454,12 @@ ptimer_read (const struct ptimer *wt)
   return wt->elapsed_last;
 }
 
-/* Return the assessed granularity of the timer implementation, in
+/* Return the assessed resolution of the timer implementation, in
    milliseconds.  This is used by code that tries to substitute a
    better value for timers that have returned zero.  */
 
 double
-ptimer_granularity (void)
+ptimer_resolution (void)
 {
-#ifdef PTIMER_POSIX
-  /* POSIX timers give us a way to measure granularity. */
-  assert (posix_resolution != 0);
-  return posix_resolution;
-#endif
-
-#ifdef PTIMER_GETTIMEOFDAY
-  /* Granularity of gettimeofday varies wildly between architectures.
-     However, it appears that on modern machines it tends to be better
-     than 1ms.  Assume 100 usecs.  */
-  return 0.1;
-#endif
-
-#ifdef PTIMER_TIME
-  return 1000;
-#endif
-
-#ifdef PTIMER_WINDOWS
-  if (windows_hires_timers)
-    return 1.0 / windows_hires_msfreq;
-  else
-    return 10;  /* according to MSDN */
-#endif
+  return IMPL_resolution ();
 }
index 244c18367c8869445b4d9426b4b857d9c6f9ca63..481c0f1b6a29c5ac1175749e86c8df4ba033c8c3 100644 (file)
@@ -33,7 +33,6 @@ so, delete this exception statement from your version.  */
 struct ptimer;                 /* forward declaration; all struct
                                    members are private */
 
-struct ptimer *ptimer_allocate PARAMS ((void));
 struct ptimer *ptimer_new PARAMS ((void));
 void ptimer_destroy PARAMS ((struct ptimer *));
 
@@ -41,6 +40,6 @@ void ptimer_reset PARAMS ((struct ptimer *));
 double ptimer_measure PARAMS ((struct ptimer *));
 double ptimer_read PARAMS ((const struct ptimer *));
 
-double ptimer_granularity PARAMS ((void));
+double ptimer_resolution PARAMS ((void));
 
 #endif /* PTIMER_H */
index a5198b7e591171792bf8b11360003d01c8dc4fed..59a1af3886d88f4aa6e325ac064d615e57dc44e3 100644 (file)
@@ -534,10 +534,10 @@ calc_rate (wgint bytes, double msecs, int *units)
 
   if (msecs == 0)
     /* If elapsed time is exactly zero, it means we're under the
-       granularity of the timer.  This can easily happen on systems
+       resolution of the timer.  This can easily happen on systems
        that use time() for the timer.  Since the interval lies between
-       0 and the timer's granularity, assume half the granularity.  */
-    msecs = ptimer_granularity () / 2.0;
+       0 and the timer's resolution, assume half the resolution.  */
+    msecs = ptimer_resolution () / 2.0;
 
   dlrate = 1000.0 * bytes / msecs;
   if (dlrate < 1024.0)