{-# LANGUAGE ExistentialQuantification #-} {-# LANGUAGE ForeignFunctionInterface #-} {-# OPTIONS_GHC -fno-warn-unused-imports #-} -- | -- Module: System.Time.Monotonic.Direct -- Copyright: (c) Joseph Adams 2012 -- License: BSD3 -- Maintainer: joeyadams3.14159@gmail.com -- Portability: Tested on Linux and Windows -- -- This module provides more direct access to the system's monotonic clock, -- but provides less protection against wraparound. -- -- More specifically, in the higher-level "System.Time.Monotonic" API, -- 'System.Time.Monotonic.Clock' updates its internal disposition every time -- 'System.Time.Monotonic.clockGetTime' is called. The only way to get a -- wraparound issue with the higher-level API is to call -- 'System.Time.Monotonic.clockGetTime' very seldomly (e.g. less than once -- every 24.8 days, if @GetTickCount@ is being used). module System.Time.Monotonic.Direct ( getSystemClock, SomeSystemClock(..), SystemClock(..), -- * Implementation(s) -- | The set of definitions below is platform-dependent. #if mingw32_HOST_OS systemClock_GetTickCount, systemClock_GetTickCount64, systemClock_QueryPerformanceCounter, #else systemClock_MONOTONIC, CTimeSpec, #endif ) where import Data.Bits (isSigned) import Data.Int import Data.Time.Clock (DiffTime) import Data.Word import Foreign (Ptr, FunPtr, allocaBytes, nullFunPtr, peekByteOff) #if mingw32_HOST_OS import Data.Ratio ((%)) #include #else import Foreign.C #include #endif -- | Existentially-quantified wrapper around 'SystemClock' data SomeSystemClock = forall time cumtime. SomeSystemClock (SystemClock time cumtime) instance Show SomeSystemClock where showsPrec d (SomeSystemClock sc) = showParen (d > 10) $ showString "SomeSystemClock " . showsPrec 11 (systemClockName sc) -- | A 'SystemClock' is a driver module used by 'System.Time.Monotonic.Clock' -- to access a particular implementation of monotonic time support. -- -- * @time@: Type of value returned by the system's time-getting function. -- -- * @cumtime@: Type for accumulating differences between consecutive(-ish) -- calls to 'systemClockGetTime', in case @time@ wraps around. -- The reason we don't simply use 'DiffTime' is this: if the implementation -- has to divide the result by a clock frequency, it could end up with a -- number that is not an integral number of picoseconds. Truncating to -- 'DiffTime' would lose precision, and that precision loss could add up, at -- least in theory. data SystemClock time cumtime = SystemClock { systemClockGetTime :: IO time , systemClockDiffTime :: time -> time -> cumtime -- ^ @systemClockDiffTime new old@ returns the amount of time that has -- elapsed between two calls to @systemClockGetTime@. -- -- >systemClockDiffTime new old = new - old -- -- This function should handle wraparound properly. Also, bear in mind -- that @new@ may be earlier than @old@. This can happen if multiple -- threads are accessing a 'System.Time.Monotonic.Clock' -- simultaneously. -- -- Lastly, @systemClockDiffTime@ should not truncate precision in -- conversion to cumtime. Otherwise, repeated calls to -- 'System.Time.Monotonic.clockGetTime' could degrade accuracy, due to -- lost precision adding up. , systemClockZeroCumTime :: cumtime -- ^ The number @0@. , systemClockAddCumTime :: cumtime -> cumtime -> cumtime -- ^ Add two @cumtime@ values. This should not overflow or lose -- precision. , systemClockCumToDiff :: cumtime -> DiffTime -- ^ Convert a cumulative total of 'systemClockDiffTime' results to -- 'DiffTime'. This may truncate precision if it needs to. , systemClockName :: String -- ^ Label identifying this clock, like -- @\"clock_gettime(CLOCK_MONOTONIC)\"@ or -- @\"GetTickCount\"@. This label is used for the 'Show' -- instances of 'SystemClock' and 'SomeSystemClock', and for -- 'System.Time.Monotonic.clockDriverName'. } instance Show (SystemClock time cumtime) where showsPrec d sc = showParen (d > 10) $ showString "SystemClock " . showsPrec 11 (systemClockName sc) -- | Return a module used for accessing the system's monotonic clock. The -- reason this is an 'IO' action, rather than simply a 'SystemClock' value, is -- that the implementation may need to make a system call to determine what -- monotonic time source to use, and how to use it. getSystemClock :: IO SomeSystemClock #if mingw32_HOST_OS getSystemClock = do m <- systemClock_GetTickCount64 case m of Just gtc64 -> return $ SomeSystemClock gtc64 Nothing -> return $ SomeSystemClock systemClock_GetTickCount #else getSystemClock = return $ SomeSystemClock systemClock_MONOTONIC #endif #if mingw32_HOST_OS diffMSec32 :: Word32 -> Word32 -> DiffTime diffMSec32 a b = fromIntegral (fromIntegral (a - b :: Word32) :: Int32) / 1000 -- Do the subtraction modulo 2^32, to handle wraparound properly. -- However, convert it from unsigned to signed, to avoid -- a bogus result if a is earlier than b. diffMSec64 :: Word64 -> Word64 -> DiffTime diffMSec64 a b = fromIntegral (fromIntegral (a - b :: Word64) :: Int64) / 1000 -- | Use @GetTickCount@. This is the default on Windows when @GetTickCount64@ -- is not available. -- -- @GetTickCount@ has a 49.7 day wraparound, due to the type of the return -- value (milliseconds as an unsigned 32-bit integer). systemClock_GetTickCount :: SystemClock Word32 DiffTime systemClock_GetTickCount = SystemClock { systemClockGetTime = c_GetTickCount , systemClockDiffTime = diffMSec32 , systemClockZeroCumTime = 0 , systemClockAddCumTime = (+) , systemClockCumToDiff = id , systemClockName = "GetTickCount" } foreign import stdcall "Windows.h GetTickCount" c_GetTickCount :: IO #{type DWORD} -- | Uses @GetTickCount64@, which was introduced in Windows Vista and -- Windows Server 2008. This function tests, at runtime, if @GetTickCount64@ -- is available. systemClock_GetTickCount64 :: IO (Maybe (SystemClock Word64 DiffTime)) systemClock_GetTickCount64 = do fun <- system_time_monotonic_load_GetTickCount64 if fun == nullFunPtr then return Nothing else return $ Just $ clock $ mkGetTickCount64 fun where clock getTickCount64 = SystemClock { systemClockGetTime = getTickCount64 , systemClockDiffTime = diffMSec64 , systemClockZeroCumTime = 0 , systemClockAddCumTime = (+) , systemClockCumToDiff = id , systemClockName = "GetTickCount64" } type C_GetTickCount64 = IO #{type ULONGLONG} -- Defined in cbits/dll.c foreign import ccall system_time_monotonic_load_GetTickCount64 :: IO (FunPtr C_GetTickCount64) foreign import stdcall "dynamic" mkGetTickCount64 :: FunPtr C_GetTickCount64 -> C_GetTickCount64 qpcDiffTime :: Int64 -> Int64 -> Integer qpcDiffTime new old = fromIntegral (new - old) qpcCumToDiff :: Int64 -> Integer -> DiffTime qpcCumToDiff freq cum = fromRational (cum % fromIntegral freq) -- | Uses @QueryPerformanceCounter@. This is not the default because it is -- less reliable in the long run than @GetTickCount@. On my laptop, it skips -- ahead roughly 2 seconds when the computer goes into sleep mode, but counts -- time spent sleeping. systemClock_QueryPerformanceCounter :: IO (Maybe (SystemClock Int64 Integer)) systemClock_QueryPerformanceCounter = do mfreq <- callQP c_QueryPerformanceFrequency case mfreq of Nothing -> return Nothing Just 0 -> return Nothing -- Shouldn't happen; just a safeguard to -- prevent zero denominator in 'qpcDiffTime'. Just freq -> return $ Just SystemClock { systemClockGetTime = do m <- callQP c_QueryPerformanceCounter case m of Just t -> return t Nothing -> fail "QueryPerformanceCounter failed,\ \ even though QueryPerformanceFrequency\ \ succeeded earlier" , systemClockDiffTime = qpcDiffTime , systemClockZeroCumTime = 0 , systemClockAddCumTime = (+) , systemClockCumToDiff = qpcCumToDiff freq , systemClockName = "QueryPerformanceCounter" } callQP :: QPFunc -> IO (Maybe Int64) callQP qpfunc = allocaBytes #{size LARGE_INTEGER} $ \ptr -> do ok <- qpfunc ptr if ok /= 0 then do n <- #{peek LARGE_INTEGER, QuadPart} ptr return (Just n) else return Nothing type QPFunc = Ptr Int64 -> IO #{type BOOL} foreign import stdcall "Windows.h QueryPerformanceFrequency" c_QueryPerformanceFrequency :: QPFunc foreign import stdcall "Windows.h QueryPerformanceCounter" c_QueryPerformanceCounter :: QPFunc #else type Time_t = #{type time_t} data CTimeSpec = CTimeSpec { tv_sec :: !Time_t -- ^ seconds , tv_nsec :: !CLong -- ^ nanoseconds. 1 second = 10^9 nanoseconds } deriving Show diffCTimeSpec :: CTimeSpec -> CTimeSpec -> DiffTime diffCTimeSpec a b = diffCTime (tv_sec a) (tv_sec b) + fromIntegral (tv_nsec a - tv_nsec b) / 1000000000 diffCTime :: Time_t -> Time_t -> DiffTime diffCTime a b | isSigned a = fromIntegral (a - b) | otherwise = error "System.Time.Monotonic.Direct: time_t is unsigned" -- time_t is supposed to be signed on POSIX systems. -- If a is earlier than b, unsigned subtraction will produce an -- enormous result. peekCTimeSpec :: Ptr CTimeSpec -> IO CTimeSpec peekCTimeSpec ptr = do sec <- #{peek struct timespec, tv_sec} ptr nsec <- #{peek struct timespec, tv_nsec} ptr return CTimeSpec { tv_sec = sec , tv_nsec = nsec } -- | Uses @clock_gettime@ with @CLOCK_MONOTONIC@. -- -- /Warning:/ on Linux, this clock stops when the computer is suspended. -- See . systemClock_MONOTONIC :: SystemClock CTimeSpec DiffTime systemClock_MONOTONIC = SystemClock { systemClockGetTime = clock_gettime #{const CLOCK_MONOTONIC} , systemClockDiffTime = diffCTimeSpec , systemClockZeroCumTime = 0 , systemClockAddCumTime = (+) , systemClockCumToDiff = id , systemClockName = "clock_gettime(CLOCK_MONOTONIC)" } -- CLOCK_MONOTONIC_RAW is more reliable, but requires -- a recent kernel and glibc. -- -- -- | @clock_gettime(CLOCK_MONOTONIC_RAW)@ -- systemClock_MONOTONIC_RAW :: SystemClock CTimeSpec -- systemClock_MONOTONIC_RAW = -- SystemClock -- { systemClockGetTime = clock_gettime #{const CLOCK_MONOTONIC_RAW} -- , systemClockDiffTime = diffCTimeSpec -- , systemClockAddCumTime = (+) -- , systemClockCumToDiff = id -- , systemClockName = "clock_gettime(CLOCK_MONOTONIC_RAW)" -- } clock_gettime :: #{type clockid_t} -> IO CTimeSpec clock_gettime clk_id = allocaBytes #{size struct timespec} $ \ptr -> do throwErrnoIfMinus1_ "clock_gettime" $ c_clock_gettime clk_id ptr peekCTimeSpec ptr foreign import ccall "time.h clock_gettime" c_clock_gettime :: #{type clockid_t} -> Ptr CTimeSpec -> IO CInt #endif