{-# LANGUAGE ScopedTypeVariables #-}
module Debug.TimeStats
(
measureM
, measurePure
, printTimeStats
, hPrintTimeStats
, reset
, TimeStats(..)
, collect
, asText
, scope
, TimeStatsRef
, lookupTimeStatsRef
, updateTimeStatsRef
) where
import Control.Exception (evaluate)
import Control.Monad (forM, forM_, unless)
import Data.IORef
import Data.Map (Map)
import Data.Maybe (isJust)
import qualified Data.Map as Map
import Data.Text (Text)
import qualified Data.Text as Text
import qualified Data.Text.IO as Text
import Data.Word (Word64)
import Debug.TimeStats.Internal (formatIntWithSeparator)
import GHC.Clock (getMonotonicTimeNSec)
import Text.Printf (printf)
import System.Environment (lookupEnv)
import System.IO (Handle, stderr)
import System.IO.Unsafe (unsafePerformIO)
{-# INLINE measureM #-}
measureM :: Monad m => String -> m a -> m a
measureM :: forall (m :: * -> *) a. Monad m => String -> m a -> m a
measureM String
label =
if Bool
enabled then do
let ref :: TimeStatsRef
ref = IO TimeStatsRef -> TimeStatsRef
forall a. IO a -> a
unsafePerformIO (IO TimeStatsRef -> TimeStatsRef)
-> IO TimeStatsRef -> TimeStatsRef
forall a b. (a -> b) -> a -> b
$ String -> IO TimeStatsRef
lookupTimeStatsRef String
label
in \m a
action -> TimeStatsRef -> m a -> m a
forall (m :: * -> *) a. Monad m => TimeStatsRef -> m a -> m a
measureMWith TimeStatsRef
ref m a
action
else
m a -> m a
forall a. a -> a
id
{-# INLINE measurePure #-}
measurePure :: String -> a -> a
measurePure :: forall a. String -> a -> a
measurePure String
label =
if Bool
enabled then
IO a -> a
forall a. IO a -> a
unsafePerformIO (IO a -> a) -> (a -> IO a) -> a -> a
forall b c a. (b -> c) -> (a -> b) -> a -> c
. String -> IO a -> IO a
forall (m :: * -> *) a. Monad m => String -> m a -> m a
measureM String
label (IO a -> IO a) -> (a -> IO a) -> a -> IO a
forall b c a. (b -> c) -> (a -> b) -> a -> c
. a -> IO a
forall a. a -> IO a
evaluate
else
a -> a
forall a. a -> a
id
{-# NOINLINE enabled #-}
enabled :: Bool
enabled :: Bool
enabled = IO Bool -> Bool
forall a. IO a -> a
unsafePerformIO (IO Bool -> Bool) -> IO Bool -> Bool
forall a b. (a -> b) -> a -> b
$ Maybe String -> Bool
forall a. Maybe a -> Bool
isJust (Maybe String -> Bool) -> IO (Maybe String) -> IO Bool
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> String -> IO (Maybe String)
lookupEnv String
"DEBUG_TIMESTATS_ENABLE"
{-# NOINLINE labelStatsMapRef #-}
labelStatsMapRef :: IORef (Map String TimeStatsRef)
labelStatsMapRef :: IORef (Map String TimeStatsRef)
labelStatsMapRef = IO (IORef (Map String TimeStatsRef))
-> IORef (Map String TimeStatsRef)
forall a. IO a -> a
unsafePerformIO (IO (IORef (Map String TimeStatsRef))
-> IORef (Map String TimeStatsRef))
-> IO (IORef (Map String TimeStatsRef))
-> IORef (Map String TimeStatsRef)
forall a b. (a -> b) -> a -> b
$ Map String TimeStatsRef -> IO (IORef (Map String TimeStatsRef))
forall a. a -> IO (IORef a)
newIORef Map String TimeStatsRef
forall k a. Map k a
Map.empty
reset :: Monad m => m ()
reset :: forall (m :: * -> *). Monad m => m ()
reset = IO () -> m ()
forall a (m :: * -> *). Monad m => IO a -> m a
intersperseIOinM (IO () -> m ()) -> IO () -> m ()
forall a b. (a -> b) -> a -> b
$
if Bool
enabled then do
Map String TimeStatsRef
m <- IORef (Map String TimeStatsRef) -> IO (Map String TimeStatsRef)
forall a. IORef a -> IO a
readIORef IORef (Map String TimeStatsRef)
labelStatsMapRef
[TimeStatsRef] -> (TimeStatsRef -> IO ()) -> IO ()
forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
t a -> (a -> m b) -> m ()
forM_ (Map String TimeStatsRef -> [TimeStatsRef]
forall k a. Map k a -> [a]
Map.elems Map String TimeStatsRef
m) ((TimeStatsRef -> IO ()) -> IO ())
-> (TimeStatsRef -> IO ()) -> IO ()
forall a b. (a -> b) -> a -> b
$ \(TimeStatsRef IORef TimeStats
ref) ->
IORef TimeStats -> TimeStats -> IO ()
forall a. IORef a -> a -> IO ()
writeIORef IORef TimeStats
ref TimeStats
initialTimeStats
else
() -> IO ()
forall a. a -> IO a
forall (m :: * -> *) a. Monad m => a -> m a
return ()
scope :: Monad m => m a -> m a
scope :: forall (m :: * -> *) a. Monad m => m a -> m a
scope =
if Bool
enabled then
\m a
m -> do
m ()
forall (m :: * -> *). Monad m => m ()
reset
a
a <- m a
m
Handle -> m ()
forall (m :: * -> *). Monad m => Handle -> m ()
hPrintTimeStats Handle
stderr
a -> m a
forall a. a -> m a
forall (m :: * -> *) a. Monad m => a -> m a
return a
a
else
m a -> m a
forall a. a -> a
id
lookupTimeStatsRef :: String -> IO TimeStatsRef
lookupTimeStatsRef :: String -> IO TimeStatsRef
lookupTimeStatsRef String
label = do
TimeStatsRef
r0 <- IO TimeStatsRef
forall (m :: * -> *). Monad m => m TimeStatsRef
newTimeStatsRef
IORef (Map String TimeStatsRef)
-> (Map String TimeStatsRef
-> (Map String TimeStatsRef, TimeStatsRef))
-> IO TimeStatsRef
forall a b. IORef a -> (a -> (a, b)) -> IO b
atomicModifyIORef IORef (Map String TimeStatsRef)
labelStatsMapRef ((Map String TimeStatsRef
-> (Map String TimeStatsRef, TimeStatsRef))
-> IO TimeStatsRef)
-> (Map String TimeStatsRef
-> (Map String TimeStatsRef, TimeStatsRef))
-> IO TimeStatsRef
forall a b. (a -> b) -> a -> b
$ \Map String TimeStatsRef
m ->
case String -> Map String TimeStatsRef -> Maybe TimeStatsRef
forall k a. Ord k => k -> Map k a -> Maybe a
Map.lookup String
label Map String TimeStatsRef
m of
Maybe TimeStatsRef
Nothing -> (String
-> TimeStatsRef
-> Map String TimeStatsRef
-> Map String TimeStatsRef
forall k a. Ord k => k -> a -> Map k a -> Map k a
Map.insert String
label TimeStatsRef
r0 Map String TimeStatsRef
m, TimeStatsRef
r0)
Just TimeStatsRef
r -> (Map String TimeStatsRef
m, TimeStatsRef
r)
collect :: Monad m => m [(String, TimeStats)]
collect :: forall (m :: * -> *). Monad m => m [(String, TimeStats)]
collect = IO [(String, TimeStats)] -> m [(String, TimeStats)]
forall a (m :: * -> *). Monad m => IO a -> m a
intersperseIOinM (IO [(String, TimeStats)] -> m [(String, TimeStats)])
-> IO [(String, TimeStats)] -> m [(String, TimeStats)]
forall a b. (a -> b) -> a -> b
$ do
Map String TimeStatsRef
m <- IORef (Map String TimeStatsRef) -> IO (Map String TimeStatsRef)
forall a. IORef a -> IO a
readIORef IORef (Map String TimeStatsRef)
labelStatsMapRef
[(String, TimeStatsRef)]
-> ((String, TimeStatsRef) -> IO (String, TimeStats))
-> IO [(String, TimeStats)]
forall (t :: * -> *) (m :: * -> *) a b.
(Traversable t, Monad m) =>
t a -> (a -> m b) -> m (t b)
forM (Map String TimeStatsRef -> [(String, TimeStatsRef)]
forall k a. Map k a -> [(k, a)]
Map.toList Map String TimeStatsRef
m) (((String, TimeStatsRef) -> IO (String, TimeStats))
-> IO [(String, TimeStats)])
-> ((String, TimeStatsRef) -> IO (String, TimeStats))
-> IO [(String, TimeStats)]
forall a b. (a -> b) -> a -> b
$ \(String
label, TimeStatsRef IORef TimeStats
ref) ->
(,) String
label (TimeStats -> (String, TimeStats))
-> IO TimeStats -> IO (String, TimeStats)
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> IORef TimeStats -> IO TimeStats
forall a. IORef a -> IO a
readIORef IORef TimeStats
ref
hPrintTimeStats :: Monad m => Handle -> m ()
hPrintTimeStats :: forall (m :: * -> *). Monad m => Handle -> m ()
hPrintTimeStats Handle
h = IO () -> m ()
forall a (m :: * -> *). Monad m => IO a -> m a
intersperseIOinM (IO () -> m ()) -> IO () -> m ()
forall a b. (a -> b) -> a -> b
$ do
[(String, TimeStats)]
xs <- IO [(String, TimeStats)]
forall (m :: * -> *). Monad m => m [(String, TimeStats)]
collect
Bool -> IO () -> IO ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
unless ([(String, TimeStats)] -> Bool
forall a. [a] -> Bool
forall (t :: * -> *) a. Foldable t => t a -> Bool
null [(String, TimeStats)]
xs) (IO () -> IO ()) -> IO () -> IO ()
forall a b. (a -> b) -> a -> b
$
Handle -> Text -> IO ()
Text.hPutStrLn Handle
h ([(String, TimeStats)] -> Text
asText [(String, TimeStats)]
xs)
printTimeStats :: Monad m => m ()
printTimeStats :: forall (m :: * -> *). Monad m => m ()
printTimeStats = Handle -> m ()
forall (m :: * -> *). Monad m => Handle -> m ()
hPrintTimeStats Handle
stderr
asText :: [(String, TimeStats)] -> Text
asText :: [(String, TimeStats)] -> Text
asText [(String, TimeStats)]
stats =
let ([String]
lbls, [TimeStats]
timestats) = [(String, TimeStats)] -> ([String], [TimeStats])
forall a b. [(a, b)] -> ([a], [b])
unzip [(String, TimeStats)]
stats
([String]
times, [String]
counts) = [(String, String)] -> ([String], [String])
forall a b. [(a, b)] -> ([a], [b])
unzip ([(String, String)] -> ([String], [String]))
-> [(String, String)] -> ([String], [String])
forall a b. (a -> b) -> a -> b
$ (TimeStats -> (String, String))
-> [TimeStats] -> [(String, String)]
forall a b. (a -> b) -> [a] -> [b]
map TimeStats -> (String, String)
formatTimeStats [TimeStats]
timestats
widthLbls :: Int
widthLbls = [Int] -> Int
forall a. Ord a => [a] -> a
forall (t :: * -> *) a. (Foldable t, Ord a) => t a -> a
maximum ([Int] -> Int) -> [Int] -> Int
forall a b. (a -> b) -> a -> b
$ (String -> Int) -> [String] -> [Int]
forall a b. (a -> b) -> [a] -> [b]
map String -> Int
forall a. [a] -> Int
forall (t :: * -> *) a. Foldable t => t a -> Int
length [String]
lbls
widthTimes :: Int
widthTimes = [Int] -> Int
forall a. Ord a => [a] -> a
forall (t :: * -> *) a. (Foldable t, Ord a) => t a -> a
maximum ([Int] -> Int) -> [Int] -> Int
forall a b. (a -> b) -> a -> b
$ (String -> Int) -> [String] -> [Int]
forall a b. (a -> b) -> [a] -> [b]
map String -> Int
forall a. [a] -> Int
forall (t :: * -> *) a. Foldable t => t a -> Int
length [String]
times
widthCounts :: Int
widthCounts = [Int] -> Int
forall a. Ord a => [a] -> a
forall (t :: * -> *) a. (Foldable t, Ord a) => t a -> a
maximum ([Int] -> Int) -> [Int] -> Int
forall a b. (a -> b) -> a -> b
$ (String -> Int) -> [String] -> [Int]
forall a b. (a -> b) -> [a] -> [b]
map String -> Int
forall a. [a] -> Int
forall (t :: * -> *) a. Foldable t => t a -> Int
length [String]
counts
in [Text] -> Text
Text.unlines ([Text] -> Text) -> [Text] -> Text
forall a b. (a -> b) -> a -> b
$
((String, String, String) -> Text)
-> [(String, String, String)] -> [Text]
forall a b. (a -> b) -> [a] -> [b]
map (String -> Text
Text.pack (String -> Text)
-> ((String, String, String) -> String)
-> (String, String, String)
-> Text
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Int -> Int -> Int -> (String, String, String) -> String
printStat Int
widthLbls Int
widthTimes Int
widthCounts) ([(String, String, String)] -> [Text])
-> [(String, String, String)] -> [Text]
forall a b. (a -> b) -> a -> b
$
[String] -> [String] -> [String] -> [(String, String, String)]
forall a b c. [a] -> [b] -> [c] -> [(a, b, c)]
zip3 [String]
lbls [String]
times [String]
counts
where
formatTimeStats :: TimeStats -> (String, String)
formatTimeStats :: TimeStats -> (String, String)
formatTimeStats TimeStats
t =
( String -> Double -> String
forall r. PrintfType r => String -> r
printf String
"%.3f" (Word64 -> Double
forall a b. (Integral a, Num b) => a -> b
fromIntegral (TimeStats -> Word64
timeStat TimeStats
t) Double -> Double -> Double
forall a. Fractional a => a -> a -> a
/ Double
1e9 :: Double)
, Char -> Int -> String -> String
formatIntWithSeparator Char
'_' (TimeStats -> Int
countStat TimeStats
t) String
""
)
printStat :: Int -> Int -> Int -> (String, String, String) -> String
printStat :: Int -> Int -> Int -> (String, String, String) -> String
printStat Int
widthLbls Int
widthTimes Int
widthCounts (String
label, String
time, String
count) =
let fmt :: String
fmt = [String] -> String
forall (t :: * -> *) a. Foldable t => t [a] -> [a]
concat
[ String
"%", Int -> String
forall a. Show a => a -> String
show Int
widthLbls
, String
"s: %", Int -> String
forall a. Show a => a -> String
show Int
widthTimes
, String
"ss count: %", Int -> String
forall a. Show a => a -> String
show Int
widthCounts, String
"s"
]
in String -> Text -> String -> String -> String
forall r. PrintfType r => String -> r
printf String
fmt (String -> Text
Text.pack String
label) String
time String
count
newtype TimeStatsRef = TimeStatsRef (IORef TimeStats)
data TimeStats = TimeStats
{ TimeStats -> Word64
timeStat :: {-# UNPACK #-} !Word64
, TimeStats -> Int
countStat :: {-# UNPACK #-} !Int
}
deriving Int -> TimeStats -> String -> String
[TimeStats] -> String -> String
TimeStats -> String
(Int -> TimeStats -> String -> String)
-> (TimeStats -> String)
-> ([TimeStats] -> String -> String)
-> Show TimeStats
forall a.
(Int -> a -> String -> String)
-> (a -> String) -> ([a] -> String -> String) -> Show a
$cshowsPrec :: Int -> TimeStats -> String -> String
showsPrec :: Int -> TimeStats -> String -> String
$cshow :: TimeStats -> String
show :: TimeStats -> String
$cshowList :: [TimeStats] -> String -> String
showList :: [TimeStats] -> String -> String
Show
initialTimeStats :: TimeStats
initialTimeStats :: TimeStats
initialTimeStats = Word64 -> Int -> TimeStats
TimeStats Word64
0 Int
0
newTimeStatsRef :: Monad m => m TimeStatsRef
newTimeStatsRef :: forall (m :: * -> *). Monad m => m TimeStatsRef
newTimeStatsRef = IO TimeStatsRef -> m TimeStatsRef
forall a (m :: * -> *). Monad m => IO a -> m a
intersperseIOinM (IO TimeStatsRef -> m TimeStatsRef)
-> IO TimeStatsRef -> m TimeStatsRef
forall a b. (a -> b) -> a -> b
$ IORef TimeStats -> TimeStatsRef
TimeStatsRef (IORef TimeStats -> TimeStatsRef)
-> IO (IORef TimeStats) -> IO TimeStatsRef
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> TimeStats -> IO (IORef TimeStats)
forall a. a -> IO (IORef a)
newIORef TimeStats
initialTimeStats
measureMWith :: Monad m => TimeStatsRef -> m a -> m a
measureMWith :: forall (m :: * -> *) a. Monad m => TimeStatsRef -> m a -> m a
measureMWith TimeStatsRef
tref m a
m = do
Word64
t0 <- IO Word64 -> m Word64
forall a (m :: * -> *). Monad m => IO a -> m a
intersperseIOinM IO Word64
getMonotonicTimeNSec
a
a <- m a
m
IO () -> m ()
forall a (m :: * -> *). Monad m => IO a -> m a
intersperseIOinM (IO () -> m ()) -> IO () -> m ()
forall a b. (a -> b) -> a -> b
$ do
Word64
tf <- IO Word64
getMonotonicTimeNSec
TimeStatsRef -> (TimeStats -> TimeStats) -> IO ()
updateTimeStatsRef TimeStatsRef
tref ((TimeStats -> TimeStats) -> IO ())
-> (TimeStats -> TimeStats) -> IO ()
forall a b. (a -> b) -> a -> b
$ \TimeStats
st ->
TimeStats
st
{ timeStat = (tf - t0) + timeStat st
, countStat = 1 + countStat st
}
a -> m a
forall a. a -> m a
forall (m :: * -> *) a. Monad m => a -> m a
return a
a
updateTimeStatsRef :: TimeStatsRef -> (TimeStats -> TimeStats) -> IO ()
updateTimeStatsRef :: TimeStatsRef -> (TimeStats -> TimeStats) -> IO ()
updateTimeStatsRef (TimeStatsRef IORef TimeStats
ref) TimeStats -> TimeStats
f =
IORef TimeStats -> (TimeStats -> (TimeStats, ())) -> IO ()
forall a b. IORef a -> (a -> (a, b)) -> IO b
atomicModifyIORef' IORef TimeStats
ref ((TimeStats -> (TimeStats, ())) -> IO ())
-> (TimeStats -> (TimeStats, ())) -> IO ()
forall a b. (a -> b) -> a -> b
$ \TimeStats
st -> (TimeStats -> TimeStats
f TimeStats
st, ())
intersperseIOinM :: forall a m. Monad m => IO a -> m a
intersperseIOinM :: forall a (m :: * -> *). Monad m => IO a -> m a
intersperseIOinM IO a
m = do
Int
s <- m Int
getStateM
a -> m a
forall a. a -> m a
forall (f :: * -> *) a. Applicative f => a -> f a
pure (a -> m a) -> a -> m a
forall a b. (a -> b) -> a -> b
$! (Int, a) -> a
forall a b. (a, b) -> b
snd ((Int, a) -> a) -> (Int, a) -> a
forall a b. (a -> b) -> a -> b
$ IO (Int, a) -> (Int, a)
forall a. IO a -> a
unsafePerformIO (IO (Int, a) -> (Int, a)) -> IO (Int, a) -> (Int, a)
forall a b. (a -> b) -> a -> b
$ do
a
r <- IO a
m
(Int, a) -> IO (Int, a)
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure (Int
s, a
r)
where
{-# NOINLINE getStateM #-}
getStateM :: m Int
getStateM :: m Int
getStateM = Int -> m Int
forall a. a -> m a
forall (f :: * -> *) a. Applicative f => a -> f a
pure Int
0