From: hniksic Date: Fri, 8 Apr 2005 18:19:20 +0000 (-0700) Subject: [svn] Better selection of POSIX clocks. X-Git-Tag: v1.13~1221 X-Git-Url: http://sjero.net/git/?p=wget;a=commitdiff_plain;h=14a2d25b2e0f6d565c3ca69ab81d09bfa775d083 [svn] Better selection of POSIX clocks. --- diff --git a/src/ChangeLog b/src/ChangeLog index 1b74862d..01fcb962 100644 --- a/src/ChangeLog +++ b/src/ChangeLog @@ -1,3 +1,20 @@ +2005-04-08 Hrvoje Niksic + + * 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 + + * ptimer.c (ptimer_init): Explicitly check that + _POSIX_MONOTONIC_CLOCK is *both* defined and >=0. (Undefined + symbols are >=0.) + 2005-04-08 Hrvoje Niksic * ptimer.c (ptimer_diff): Fix typo affecting Windows build. diff --git a/src/ptimer.c b/src/ptimer.c index 4fb54cf8..d63e69e2 100644 --- a/src/ptimer.c +++ b/src/ptimer.c @@ -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 */ + +/* 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 (); } diff --git a/src/ptimer.h b/src/ptimer.h index 244c1836..481c0f1b 100644 --- a/src/ptimer.h +++ b/src/ptimer.h @@ -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 */ diff --git a/src/retr.c b/src/retr.c index a5198b7e..59a1af38 100644 --- a/src/retr.c +++ b/src/retr.c @@ -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)