packages feed

keiro-0.22.0.0: bench/BenchSetup.hs

-- | Selection-aware fixture diagnostics shared by the benchmark groups.
module BenchSetup (selected, selectedWithCleanup, memoSetup, setupTrace, freshCounter, nextIteration, nextBoundedIteration, selectedCounter, selectedCounterWithCleanup) where

import Control.Concurrent.MVar (modifyMVar, newMVar)
import Control.DeepSeq (NFData (..))
import Data.IORef (IORef, atomicModifyIORef', newIORef, readIORef)
import System.Environment (lookupEnv)
import System.IO (hPutStrLn, stderr)
import Test.Tasty.Bench (Benchmark, envWithCleanup)
import Prelude

-- Fixture handles contain mutable state; force the handle, never its contents.
data Handle a = Handle !a

instance NFData (Handle a) where
  rnf (Handle value) = value `seq` ()

selected :: IO a -> (a -> Benchmark) -> Benchmark
selected acquire = selectedWithCleanup acquire (const (pure ()))

selectedWithCleanup :: IO a -> (a -> IO ()) -> (a -> Benchmark) -> Benchmark
selectedWithCleanup acquire cleanup build =
  envWithCleanup (Handle <$> acquire) (\(Handle value) -> cleanup value) $ \handle ->
    build (case handle of Handle value -> value)

memoSetup :: IO a -> IO (IO a)
memoSetup acquire = do
  cell <- newMVar Nothing
  pure $ modifyMVar cell $ \current -> case current of
    Just value -> pure (current, value)
    Nothing -> do
      value <- acquire
      pure (Just value, value)

setupTrace :: String -> String -> String -> IO ()
setupTrace group scenario detail = do
  enabled <- lookupEnv "KEIRO_BENCH_SETUP_TRACE"
  if enabled == Just "1"
    then hPutStrLn stderr ("BENCH_SETUP group=" <> group <> " scenario=" <> scenario <> " " <> detail)
    else pure ()

freshCounter :: String -> Int -> IO (IORef Int)
freshCounter label units = do
  setupTrace "command" label ("counter budget_iterations=" <> show (iterationBudget units) <> " units_per_iteration=" <> show units)
  newIORef 0

iterationBudget :: Int -> Int
iterationBudget units = 100000 `div` max 1 units

nextIteration :: String -> Int -> IORef Int -> IO Int
nextIteration label units = nextBoundedIteration label (iterationBudget units)

nextBoundedIteration :: String -> Int -> IORef Int -> IO Int
nextBoundedIteration label budget counter = do
  next <- atomicModifyIORef' counter (\current -> let n = current + 1 in (n, n))
  if next > budget
    then fail ("benchmark fixture growth budget exceeded: " <> label <> " iterations=" <> show next)
    else pure next

selectedCounter :: String -> Int -> IO (IORef Int) -> (IORef Int -> Benchmark) -> Benchmark
selectedCounter label units acquire = selectedCounterWithCleanup label units acquire (const (pure ()))

selectedCounterWithCleanup :: String -> Int -> IO (IORef Int) -> (Int -> IO ()) -> (IORef Int -> Benchmark) -> Benchmark
selectedCounterWithCleanup label units acquire cleanup build =
  envWithCleanup
    acquire
    ( \counter -> do
        n <- readIORef counter
        setupTrace "counts" label ("iterations=" <> show n <> " units=" <> show (n * units))
        cleanup n
    )
    build