system-time-monotonic [1] provides access to the system's monotonic
clock. Usage looks like this:
* Use 'newClock' to create a new monotonic clock
* Use 'clockGetTime' to see how much time has elapsed since the clock
was created.
This package currently supports Linux and Windows. It might also work
on BSD, but I haven't tested it there.
Mac OS X support is currently not implemented, but patches are
welcome. GHC uses mach_absolute_time and mach_timebase_info; see
ticket #5865 [2].
I also added a handy utility function 'delay', a variant of
threadDelay taking a DiffTime instead of Int microseconds. Thus:
delay 1.5
Waits 1.5 seconds. Note that since 'delay' simply calls 'threadDelay'
in a loop, it is disrupted by system clock changes (again, see ticket
#5865).
[1]: http://hackage.haskell.org/package/system-time-monotonic
[2]: http://hackage.haskell.org/trac/ghc/ticket/5865
---
The rest of this email describes various hurdles involved in
implementing this package, and how they were addressed.
## GetTickCount
The most obvious one is that GetTickCount has a short wraparound (49.7
days). Two ways to address this:
* Don't use GetTickCount; use QueryPerformanceCounter (or similar)
instead. This is currently how it's done in GHC HEAD.
* Use GetTickCount, but avoid comparing times that are far apart by
tracking the total difference each time we look at the clock.
I took the second approach, because I found out that
QueryPerformanceCounter is actually less accurate in the long run than
GetTickCount. In particular, QueryPerformanceCounter stops ticking
(or maybe ticks slower, I forget) when the computer is asleep.
Here's the trick I use to work around GetTickCount's wraparound (pseudocode):
st1 :: Word32
t1 :: DiffTime
newClock:
st1 := GetTickCount()
t1 := 0
clockGetTime:
st2 := GetTickCount()
t2 := t1 + (Int32)(st2 - st1 modulo 2^32)
st1 := st2
t1 := t2
return t2
This workaround only works if clockGetTime is called at least once
every 24.8 days.
It's important that st2 - st1 be done modulo 2^32, to compensate for
wraparound. However, the result should be converted to a signed
32-bit integer, in case st2 was recorded earlier than st1 (which can
easily happen in a concurrent context).
In particular, here's what you should *not* do (which GHC currently
does, when QueryPerformanceCounter is not available):
st1 = (bigger_type) GetTickCount();
...
st2 = (bigger_type) GetTickCount();
return (st2 - st1)
This will return a bogus value if a wraparound occurred between st1 and st2.
system-time-monotonic tests, at runtime, if GetTickCount64 is
available. If so, it uses it. Otherwise, it falls back to
GetTickCount. Here's the code I used to do the run-time system call
lookup:
/* cbits/dll.c */
typedef ULONGLONG (WINAPI *GetTickCount64_t)(void);
GetTickCount64_t system_time_monotonic_load_GetTickCount64(void)
{
return (GetTickCount64_t)
GetProcAddress(GetModuleHandle(TEXT("kernel32.dll")),
"GetTickCount64");
}
-- System/Time/Monotonic/Direct.hsc
type C_GetTickCount64 = IO #{type ULONGLONG}
foreign import ccall
system_time_monotonic_load_GetTickCount64 :: IO (FunPtr
C_GetTickCount64)
foreign import stdcall "dynamic"
mkGetTickCount64 :: FunPtr C_GetTickCount64 -> C_GetTickCount64
Did I do this right? In particular:
* Can I assume the kernel32.dll HMODULE won't be pulled out from under me?
* Is `foreign import stdcall "dynamic"` the right incantation for
using a pointer to a WINAPI function?
## CLOCK_MONOTONIC is not actually monotonic on Linux
CLOCK_MONOTONIC is subject to NTP adjustments. Worse, CLOCK_MONOTONIC
stops when the computer is put to sleep (unlike GetTickCount, which
does the right thing).
Two clocks were introduced very recently in Linux:
* CLOCK_MONOTONIC_RAW
* CLOCK_BOOTTIME
I'd like to support CLOCK_BOOTTIME at some point. I'm not sure if
it's subject to NTP adjustments or not, since the announcement [3]
says:
CLOCK_BOOTTIME is identical to CLOCK_MONOTONIC, except it also
includes any time spent in suspend (as currently measured by
read_persistent_clock()). This allows applications to get a
suspend aware monotonic clock.
One idea would be to hard-code the clockid_t of CLOCK_BOOTTIME and
CLOCK_MONOTONIC_RAW, and try calling clock_gettime with each of these.
If one of these succeeds, use it. Otherwise, fall back to
CLOCK_MONOTONIC. Thus, the compiled binary can test, at runtime, if a
new enough kernel is available.
For now, system-time-monotonic simply uses CLOCK_MONOTONIC.
[3]: http://lwn.net/Articles/428176/