packages feed

system-time-monotonic-0.2: System/Time/Monotonic/Direct.hsc

{-# 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 <Windows.h>
#else
import Foreign.C
#include <time.h>
#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 <http://lwn.net/Articles/434239/>.
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