content: {-
    Copyright (c) Meta Platforms, Inc. and affiliates.
    All rights reserved.
  
    This source code is licensed under the BSD-style license found in the
    LICENSE file in the root directory of this source tree.
  -}
  
  -- | Utilities for timing things and working with time differences
  module Util.Timing
    ( reportTime
    , reportAndShowTime
    , showAllocs
    , showTime
    , timeIt
    , timeNF
    , timeItNoGC
    ) where
  
  import Control.DeepSeq (NFData, force)
  import Control.Exception (evaluate)
  import Control.Monad.IO.Class
  import Data.Int
  import Text.Printf
  import GHC.Stats
  import GHC.Conc
  import System.IO (stderr)
  
  import Util.PrettyPrint
  import Util.Time
  
  -- | Runs an 'IO' operation and prints how long it took. Also returns
  -- the timing value for use.
  reportAndShowTime :: MonadIO m => String -> m a -> m (Double, Int64, a)
  reportAndShowTime name io = do
    (t, b, a) <- timeIt io
    liftIO $ hPrintf stderr "%s: %s, %s\n" name (showTime t) (showAllocs b)
    return (t, b, a)
  
  -- | Runs an 'IO' operation and reports how long it took. Useful for
  -- ad-hoc benchmarking.
  reportTime :: MonadIO m => String -> m a -> m a
  reportTime name io = do
    (_, _, a) <- reportAndShowTime name io
    return a
  
  -- | Converts time in seconds to a human friendly string.
  showTime :: Double -> String
  showTime t = printf "%.2f%s" val unit
    where
     unit :: String
     (val, unit)
          | t >= 1    = (t, "s")
          | t >= 1e-3 = (t * 1e3, "ms")
          | t >= 1e-6 = (t * 1e6, "us")
          | otherwise = (t * 1e9, "ns")
  
  -- | Converts a number of bytes to a human-friendsly string.
  showAllocs :: Int64 -> String
  showAllocs = renderBytesString . fromIntegral
  
  -- | Runs an 'IO' action and returns a triple of the time it consumed (in sec),
  -- the number of bytes it allocated, and the result it returned.
  timeIt :: MonadIO m => m a -> m (Double, Int64, a)
  timeIt action = do
    t0 <- liftIO getTimePoint
    a0 <- liftIO getAllocationCounter
    ret <- action
    a1 <- liftIO getAllocationCounter
    t <- liftIO $ getElapsedTime t0
    return (toDiffSeconds t, a0 - a1, ret)
  
  -- | Runs an 'IO' action, reduces the result to normal form and returns a pair
  -- of the time it consumed and the result it returned.
  timeNF :: NFData a => IO a -> IO (Double, Int64, a)
  timeNF action = timeIt $ evaluate . force =<< action
  
  -- | Records the time to run an action excluding GC time.  Probably
  -- more expensive than 'timeIt'.
  timeItNoGC :: IO a -> IO (Double, Int64, a)
  timeItNoGC io = do
    t0 <- getRTSStats
    a0 <- getAllocationCounter
    ret <- io
    a1 <- getAllocationCounter
    t1 <- getRTSStats
    let diff_ns = mutator_elapsed_ns t1 - mutator_elapsed_ns t0
    return (fromIntegral diff_ns / 1000000000, a0 - a1, ret)
  