{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE TypeFamilies #-}
{-# OPTIONS_GHC -Wno-orphans #-}
module Test.Hspec.BenchGolden
(
benchGolden
, benchGoldenWith
, benchGoldenWithExpectation
, benchGoldenSweep
, benchGoldenSweepWith
, BenchConfig(..)
, defaultBenchConfig
, BenchGolden(..)
, BenchAction(..)
, GoldenStats(..)
, BenchResult(..)
, Warning(..)
, ArchConfig(..)
, nf
, nfIO
, nfAppIO
, io
, runBenchGolden
, runBenchmark
, runBenchmarkWithRawTimings
, runSweep
, runSweepPoint
, compareStats
, checkVariance
, calculateRobustStats
, calculateTrimmedMean
, calculateMAD
, calculateIQR
, detectOutliers
, readGoldenFile
, writeGoldenFile
, writeActualFile
, getGoldenPath
, getActualPath
, module Test.Hspec.BenchGolden.Lenses
, module Test.Hspec.BenchGolden.Arch
, module Test.Hspec.BenchGolden.CSV
) where
import Data.IORef
import qualified Data.Text as T
import Lens.Micro ((^.))
import System.Environment (lookupEnv)
import Text.Printf (printf)
import qualified Text.PrettyPrint.Boxes as Box
import Test.Hspec.Core.Spec
import Test.Hspec.BenchGolden.Arch
import qualified Test.Hspec.BenchGolden.Lenses as L
import Test.Hspec.BenchGolden.Lenses hiding (Expectation)
import Test.Hspec.BenchGolden.CSV
import Test.Hspec.BenchGolden.Runner
( runBenchGolden
, runBenchmark
, runBenchmarkWithRawTimings
, runSweep
, runSweepPoint
, compareStats
, checkVariance
, calculateRobustStats
, calculateTrimmedMean
, calculateMAD
, calculateIQR
, detectOutliers
, readGoldenFile
, writeGoldenFile
, writeActualFile
, getGoldenPath
, getActualPath
, setAcceptGoldens
, setSkipBenchmarks
, nf
, nfIO
, nfAppIO
, io
)
import Test.Hspec.BenchGolden.Types
benchGolden ::
String
-> BenchAction
-> Spec
benchGolden :: String -> BenchAction -> Spec
benchGolden String
name BenchAction
action = BenchConfig -> String -> BenchAction -> Spec
benchGoldenWith BenchConfig
defaultBenchConfig String
name BenchAction
action
benchGoldenWith :: BenchConfig
-> String
-> BenchAction
-> Spec
benchGoldenWith :: BenchConfig -> String -> BenchAction -> Spec
benchGoldenWith BenchConfig
config String
name BenchAction
action =
String -> BenchGolden -> SpecWith (Arg BenchGolden)
forall a.
(HasCallStack, Example a) =>
String -> a -> SpecWith (Arg a)
it String
name (BenchGolden -> SpecWith (Arg BenchGolden))
-> BenchGolden -> SpecWith (Arg BenchGolden)
forall a b. (a -> b) -> a -> b
$ BenchGolden
{ benchName :: String
benchName = String
name
, benchAction :: BenchAction
benchAction = BenchAction
action
, benchConfig :: BenchConfig
benchConfig = BenchConfig
config
}
benchGoldenSweep ::
Show a
=> String
-> T.Text
-> [a]
-> (a -> BenchAction)
-> Spec
benchGoldenSweep :: forall a.
Show a =>
String -> Text -> [a] -> (a -> BenchAction) -> Spec
benchGoldenSweep = BenchConfig -> String -> Text -> [a] -> (a -> BenchAction) -> Spec
forall a.
Show a =>
BenchConfig -> String -> Text -> [a] -> (a -> BenchAction) -> Spec
benchGoldenSweepWith BenchConfig
defaultBenchConfig
benchGoldenSweepWith ::
Show a
=> BenchConfig
-> String
-> T.Text
-> [a]
-> (a -> BenchAction)
-> Spec
benchGoldenSweepWith :: forall a.
Show a =>
BenchConfig -> String -> Text -> [a] -> (a -> BenchAction) -> Spec
benchGoldenSweepWith BenchConfig
config String
sweepName Text
paramName [a]
paramValues a -> BenchAction
mkAction =
String -> BenchGoldenSweep a -> SpecWith (Arg (BenchGoldenSweep a))
forall a.
(HasCallStack, Example a) =>
String -> a -> SpecWith (Arg a)
it (String
sweepName String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
" [sweep]") (BenchGoldenSweep a -> SpecWith (Arg (BenchGoldenSweep a)))
-> BenchGoldenSweep a -> SpecWith (Arg (BenchGoldenSweep a))
forall a b. (a -> b) -> a -> b
$ String
-> BenchConfig
-> Text
-> [a]
-> (a -> BenchAction)
-> BenchGoldenSweep a
forall a.
String
-> BenchConfig
-> Text
-> [a]
-> (a -> BenchAction)
-> BenchGoldenSweep a
BenchGoldenSweep String
sweepName BenchConfig
config Text
paramName [a]
paramValues a -> BenchAction
mkAction
data BenchGoldenSweep a = BenchGoldenSweep
!String
!BenchConfig
!T.Text
![a]
!(a -> BenchAction)
instance Show a => Example (BenchGoldenSweep a) where
type Arg (BenchGoldenSweep a) = ()
evaluateExample :: BenchGoldenSweep a
-> Params
-> (ActionWith (Arg (BenchGoldenSweep a)) -> IO ())
-> ProgressCallback
-> IO Result
evaluateExample (BenchGoldenSweep String
sweepName BenchConfig
config Text
paramName [a]
paramValues a -> BenchAction
mkAction) Params
_params ActionWith (Arg (BenchGoldenSweep a)) -> IO ()
hook ProgressCallback
_progress = do
Maybe String
acceptEnv <- String -> IO (Maybe String)
lookupEnv String
"GOLDS_GYM_ACCEPT"
Maybe String
skipEnv <- String -> IO (Maybe String)
lookupEnv String
"GOLDS_GYM_SKIP"
let shouldAccept :: Bool
shouldAccept = case Maybe String
acceptEnv of
Just String
"1" -> Bool
True
Just String
"true" -> Bool
True
Just String
"yes" -> Bool
True
Maybe String
_ -> Bool
False
shouldSkip :: Bool
shouldSkip = case Maybe String
skipEnv of
Just String
"1" -> Bool
True
Just String
"true" -> Bool
True
Just String
"yes" -> Bool
True
Maybe String
_ -> Bool
False
Bool -> IO ()
setAcceptGoldens Bool
shouldAccept
Bool -> IO ()
setSkipBenchmarks Bool
shouldSkip
IORef Result
ref <- Result -> IO (IORef Result)
forall a. a -> IO (IORef a)
newIORef (String -> ResultStatus -> Result
Result String
"" ResultStatus
Success)
ActionWith (Arg (BenchGoldenSweep a)) -> IO ()
hook (ActionWith (Arg (BenchGoldenSweep a)) -> IO ())
-> ActionWith (Arg (BenchGoldenSweep a)) -> IO ()
forall a b. (a -> b) -> a -> b
$ \() -> do
[(a, BenchResult, GoldenStats)]
results <- String
-> BenchConfig
-> Text
-> [a]
-> (a -> BenchAction)
-> IO [(a, BenchResult, GoldenStats)]
forall a.
Show a =>
String
-> BenchConfig
-> Text
-> [a]
-> (a -> BenchAction)
-> IO [(a, BenchResult, GoldenStats)]
runSweep String
sweepName BenchConfig
config Text
paramName [a]
paramValues a -> BenchAction
mkAction
IORef Result -> Result -> IO ()
forall a. IORef a -> a -> IO ()
writeIORef IORef Result
ref (String -> Text -> [a] -> [(a, BenchResult, GoldenStats)] -> Result
forall a.
Show a =>
String -> Text -> [a] -> [(a, BenchResult, GoldenStats)] -> Result
fromSweepResults String
sweepName Text
paramName [a]
paramValues [(a, BenchResult, GoldenStats)]
results)
IORef Result -> IO Result
forall a. IORef a -> IO a
readIORef IORef Result
ref
fromSweepResults :: Show a => String -> T.Text -> [a] -> [(a, BenchResult, GoldenStats)] -> Result
fromSweepResults :: forall a.
Show a =>
String -> Text -> [a] -> [(a, BenchResult, GoldenStats)] -> Result
fromSweepResults String
sweepName Text
paramName [a]
paramValues [(a, BenchResult, GoldenStats)]
results =
let
regressions :: [(a, BenchResult)]
regressions = [(a
pv, BenchResult
r) | (a
pv, r :: BenchResult
r@(Regression GoldenStats
_ GoldenStats
_ Double
_ Double
_ Maybe Double
_), GoldenStats
_) <- [(a, BenchResult, GoldenStats)]
results]
firstRuns :: [a]
firstRuns = [a
pv | (a
pv, FirstRun GoldenStats
_, GoldenStats
_) <- [(a, BenchResult, GoldenStats)]
results]
nPoints :: Int
nPoints = [a] -> Int
forall a. [a] -> Int
forall (t :: * -> *) a. Foldable t => t a -> Int
length [a]
paramValues
in case [(a, BenchResult)]
regressions of
((a
pv, Regression GoldenStats
golden GoldenStats
actual Double
pct Double
tol Maybe Double
absTol) : [(a, BenchResult)]
_) ->
let toleranceDesc :: String
toleranceDesc :: String
toleranceDesc = case Maybe Double
absTol of
Maybe Double
Nothing -> String -> Double -> String
forall r. PrintfType r => String -> r
printf String
"tolerance: %.1f%%" Double
tol
Just Double
absMs -> String -> Double -> Double -> String
forall r. PrintfType r => String -> r
printf String
"tolerance: %.1f%% or %.3f ms" Double
tol Double
absMs
message :: String
message = String
-> String
-> String
-> String
-> Double
-> String
-> String
-> String
forall r. PrintfType r => String -> r
printf String
"Sweep '%s': regression at %s=%s\nMean time increased by %.1f%% (%s)\n\n%s"
String
sweepName (Text -> String
T.unpack Text
paramName) (a -> String
forall a. Show a => a -> String
show a
pv) Double
pct String
toleranceDesc
(GoldenStats -> GoldenStats -> String
formatRegression GoldenStats
golden GoldenStats
actual)
in String -> ResultStatus -> Result
Result String
message (Maybe Location -> FailureReason -> ResultStatus
Failure Maybe Location
forall a. Maybe a
Nothing (String -> FailureReason
Reason String
message))
[(a, BenchResult)]
_ ->
if Bool -> Bool
not ([a] -> Bool
forall a. [a] -> Bool
forall (t :: * -> *) a. Foldable t => t a -> Bool
null [a]
firstRuns)
then
let info :: String
info = String -> String -> Int -> Int -> String
forall r. PrintfType r => String -> r
printf String
"Sweep '%s': baselines created for %d point(s)\nCSV written with %d row(s)"
String
sweepName ([a] -> Int
forall a. [a] -> Int
forall (t :: * -> *) a. Foldable t => t a -> Int
length [a]
firstRuns) Int
nPoints
in String -> ResultStatus -> Result
Result String
info ResultStatus
Success
else
let info :: String
info = String -> String -> Int -> Int -> String
forall r. PrintfType r => String -> r
printf String
"Sweep '%s': all %d point(s) passed\nCSV written with %d row(s)"
String
sweepName Int
nPoints Int
nPoints
in String -> ResultStatus -> Result
Result String
info ResultStatus
Success
benchGoldenWithExpectation ::
String
-> BenchConfig
-> [L.Expectation]
-> BenchAction
-> Spec
benchGoldenWithExpectation :: String -> BenchConfig -> [Expectation] -> BenchAction -> Spec
benchGoldenWithExpectation String
name BenchConfig
config [Expectation]
expectations BenchAction
action =
String
-> BenchGoldenWithExpectations
-> SpecM (Arg BenchGoldenWithExpectations) ()
forall a.
(HasCallStack, Example a) =>
String -> a -> SpecWith (Arg a)
it String
name (BenchGoldenWithExpectations
-> SpecM (Arg BenchGoldenWithExpectations) ())
-> BenchGoldenWithExpectations
-> SpecM (Arg BenchGoldenWithExpectations) ()
forall a b. (a -> b) -> a -> b
$ String
-> BenchAction
-> BenchConfig
-> [Expectation]
-> BenchGoldenWithExpectations
BenchGoldenWithExpectations String
name BenchAction
action BenchConfig
config [Expectation]
expectations
data BenchGoldenWithExpectations = BenchGoldenWithExpectations
!String
!BenchAction
!BenchConfig
![L.Expectation]
instance Example BenchGolden where
type Arg BenchGolden = ()
evaluateExample :: BenchGolden
-> Params
-> (ActionWith (Arg BenchGolden) -> IO ())
-> ProgressCallback
-> IO Result
evaluateExample BenchGolden
bg Params
params ActionWith (Arg BenchGolden) -> IO ()
hook ProgressCallback
progress =
(() -> BenchGolden)
-> Params
-> (ActionWith (Arg (() -> BenchGolden)) -> IO ())
-> ProgressCallback
-> IO Result
forall e.
Example e =>
e
-> Params
-> (ActionWith (Arg e) -> IO ())
-> ProgressCallback
-> IO Result
evaluateExample (\() -> BenchGolden
bg) Params
params ActionWith (Arg BenchGolden) -> IO ()
ActionWith (Arg (() -> BenchGolden)) -> IO ()
hook ProgressCallback
progress
instance Example (arg -> BenchGolden) where
type Arg (arg -> BenchGolden) = arg
evaluateExample :: (arg -> BenchGolden)
-> Params
-> (ActionWith (Arg (arg -> BenchGolden)) -> IO ())
-> ProgressCallback
-> IO Result
evaluateExample arg -> BenchGolden
bgFn Params
_params ActionWith (Arg (arg -> BenchGolden)) -> IO ()
hook ProgressCallback
_progress = do
Maybe String
acceptEnv <- String -> IO (Maybe String)
lookupEnv String
"GOLDS_GYM_ACCEPT"
Maybe String
skipEnv <- String -> IO (Maybe String)
lookupEnv String
"GOLDS_GYM_SKIP"
let shouldAccept :: Bool
shouldAccept = case Maybe String
acceptEnv of
Just String
"1" -> Bool
True
Just String
"true" -> Bool
True
Just String
"yes" -> Bool
True
Maybe String
_ -> Bool
False
shouldSkip :: Bool
shouldSkip = case Maybe String
skipEnv of
Just String
"1" -> Bool
True
Just String
"true" -> Bool
True
Just String
"yes" -> Bool
True
Maybe String
_ -> Bool
False
Bool -> IO ()
setAcceptGoldens Bool
shouldAccept
Bool -> IO ()
setSkipBenchmarks Bool
shouldSkip
IORef Result
ref <- Result -> IO (IORef Result)
forall a. a -> IO (IORef a)
newIORef (String -> ResultStatus -> Result
Result String
"" ResultStatus
Success)
ActionWith (Arg (arg -> BenchGolden)) -> IO ()
hook (ActionWith (Arg (arg -> BenchGolden)) -> IO ())
-> ActionWith (Arg (arg -> BenchGolden)) -> IO ()
forall a b. (a -> b) -> a -> b
$ \Arg (arg -> BenchGolden)
arg -> do
let bg :: BenchGolden
bg = arg -> BenchGolden
bgFn arg
Arg (arg -> BenchGolden)
arg
BenchResult
result <- BenchGolden -> IO BenchResult
runBenchGolden BenchGolden
bg
IORef Result -> Result -> IO ()
forall a. IORef a -> a -> IO ()
writeIORef IORef Result
ref (BenchResult -> Result
fromBenchResult BenchResult
result)
IORef Result -> IO Result
forall a. IORef a -> IO a
readIORef IORef Result
ref
instance Example BenchGoldenWithExpectations where
type Arg BenchGoldenWithExpectations = ()
evaluateExample :: BenchGoldenWithExpectations
-> Params
-> (ActionWith (Arg BenchGoldenWithExpectations) -> IO ())
-> ProgressCallback
-> IO Result
evaluateExample (BenchGoldenWithExpectations String
name BenchAction
action BenchConfig
config [Expectation]
expectations) Params
_params ActionWith (Arg BenchGoldenWithExpectations) -> IO ()
hook ProgressCallback
_progress = do
Maybe String
acceptEnv <- String -> IO (Maybe String)
lookupEnv String
"GOLDS_GYM_ACCEPT"
Maybe String
skipEnv <- String -> IO (Maybe String)
lookupEnv String
"GOLDS_GYM_SKIP"
let shouldAccept :: Bool
shouldAccept = case Maybe String
acceptEnv of
Just String
"1" -> Bool
True
Just String
"true" -> Bool
True
Just String
"yes" -> Bool
True
Maybe String
_ -> Bool
False
shouldSkip :: Bool
shouldSkip = case Maybe String
skipEnv of
Just String
"1" -> Bool
True
Just String
"true" -> Bool
True
Just String
"yes" -> Bool
True
Maybe String
_ -> Bool
False
Bool -> IO ()
setAcceptGoldens Bool
shouldAccept
Bool -> IO ()
setSkipBenchmarks Bool
shouldSkip
IORef Result
ref <- Result -> IO (IORef Result)
forall a. a -> IO (IORef a)
newIORef (String -> ResultStatus -> Result
Result String
"" ResultStatus
Success)
ActionWith (Arg BenchGoldenWithExpectations) -> IO ()
hook (ActionWith (Arg BenchGoldenWithExpectations) -> IO ())
-> ActionWith (Arg BenchGoldenWithExpectations) -> IO ()
forall a b. (a -> b) -> a -> b
$ \() -> do
BenchResult
result <- String
-> BenchAction -> BenchConfig -> [Expectation] -> IO BenchResult
runBenchGoldenWithExpectations String
name BenchAction
action BenchConfig
config [Expectation]
expectations
IORef Result -> Result -> IO ()
forall a. IORef a -> a -> IO ()
writeIORef IORef Result
ref ([Expectation] -> BenchResult -> Result
fromBenchResultWithExpectations [Expectation]
expectations BenchResult
result)
IORef Result -> IO Result
forall a. IORef a -> IO a
readIORef IORef Result
ref
runBenchGoldenWithExpectations :: String -> BenchAction -> BenchConfig -> [L.Expectation] -> IO BenchResult
runBenchGoldenWithExpectations :: String
-> BenchAction -> BenchConfig -> [Expectation] -> IO BenchResult
runBenchGoldenWithExpectations String
name BenchAction
action BenchConfig
config [Expectation]
expectations = do
let bg :: BenchGolden
bg = String -> BenchAction -> BenchConfig -> BenchGolden
BenchGolden String
name BenchAction
action BenchConfig
config
BenchResult
result <- BenchGolden -> IO BenchResult
runBenchGolden BenchGolden
bg
let (Double
tolPct, Maybe Double
tolAbs) = case [Expectation]
expectations of
[] -> (BenchConfig -> Double
tolerancePercent BenchConfig
config, BenchConfig -> Maybe Double
absoluteToleranceMs BenchConfig
config)
(Expectation
e:[Expectation]
_) -> Expectation -> (Double, Maybe Double)
L.toleranceFromExpectation Expectation
e
case BenchResult
result of
FirstRun GoldenStats
stats -> BenchResult -> IO BenchResult
forall a. a -> IO a
forall (m :: * -> *) a. Monad m => a -> m a
return (BenchResult -> IO BenchResult) -> BenchResult -> IO BenchResult
forall a b. (a -> b) -> a -> b
$ GoldenStats -> BenchResult
FirstRun GoldenStats
stats
Pass GoldenStats
golden GoldenStats
actual [Warning]
warnings ->
let allPass :: Bool
allPass = (Expectation -> Bool) -> [Expectation] -> Bool
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
all (\Expectation
e -> Expectation -> GoldenStats -> GoldenStats -> Bool
L.checkExpectation Expectation
e GoldenStats
golden GoldenStats
actual) [Expectation]
expectations
in if Bool
allPass
then BenchResult -> IO BenchResult
forall a. a -> IO a
forall (m :: * -> *) a. Monad m => a -> m a
return (BenchResult -> IO BenchResult) -> BenchResult -> IO BenchResult
forall a b. (a -> b) -> a -> b
$ GoldenStats -> GoldenStats -> [Warning] -> BenchResult
Pass GoldenStats
golden GoldenStats
actual [Warning]
warnings
else
let lens :: (Double -> Const Double Double)
-> GoldenStats -> Const Double GoldenStats
lens = BenchConfig -> Lens' GoldenStats Double
L.metricFor BenchConfig
config
goldenVal :: Double
goldenVal = GoldenStats
golden GoldenStats
-> ((Double -> Const Double Double)
-> GoldenStats -> Const Double GoldenStats)
-> Double
forall s a. s -> Getting a s a -> a
^. (Double -> Const Double Double)
-> GoldenStats -> Const Double GoldenStats
lens
actualVal :: Double
actualVal = GoldenStats
actual GoldenStats
-> ((Double -> Const Double Double)
-> GoldenStats -> Const Double GoldenStats)
-> Double
forall s a. s -> Getting a s a -> a
^. (Double -> Const Double Double)
-> GoldenStats -> Const Double GoldenStats
lens
meanDiff :: Double
meanDiff = if Double
goldenVal Double -> Double -> Bool
forall a. Eq a => a -> a -> Bool
== Double
0
then Double
100.0
else ((Double
actualVal Double -> Double -> Double
forall a. Num a => a -> a -> a
- Double
goldenVal) Double -> Double -> Double
forall a. Fractional a => a -> a -> a
/ Double
goldenVal) Double -> Double -> Double
forall a. Num a => a -> a -> a
* Double
100
in BenchResult -> IO BenchResult
forall a. a -> IO a
forall (m :: * -> *) a. Monad m => a -> m a
return (BenchResult -> IO BenchResult) -> BenchResult -> IO BenchResult
forall a b. (a -> b) -> a -> b
$ GoldenStats
-> GoldenStats -> Double -> Double -> Maybe Double -> BenchResult
Regression GoldenStats
golden GoldenStats
actual Double
meanDiff Double
tolPct Maybe Double
tolAbs
Regression GoldenStats
golden GoldenStats
actual Double
pct Double
_tol Maybe Double
_absTol ->
let allPass :: Bool
allPass = (Expectation -> Bool) -> [Expectation] -> Bool
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
all (\Expectation
e -> Expectation -> GoldenStats -> GoldenStats -> Bool
L.checkExpectation Expectation
e GoldenStats
golden GoldenStats
actual) [Expectation]
expectations
in if Bool
allPass
then BenchResult -> IO BenchResult
forall a. a -> IO a
forall (m :: * -> *) a. Monad m => a -> m a
return (BenchResult -> IO BenchResult) -> BenchResult -> IO BenchResult
forall a b. (a -> b) -> a -> b
$ GoldenStats -> GoldenStats -> [Warning] -> BenchResult
Pass GoldenStats
golden GoldenStats
actual []
else BenchResult -> IO BenchResult
forall a. a -> IO a
forall (m :: * -> *) a. Monad m => a -> m a
return (BenchResult -> IO BenchResult) -> BenchResult -> IO BenchResult
forall a b. (a -> b) -> a -> b
$ GoldenStats
-> GoldenStats -> Double -> Double -> Maybe Double -> BenchResult
Regression GoldenStats
golden GoldenStats
actual Double
pct Double
tolPct Maybe Double
tolAbs
Improvement GoldenStats
golden GoldenStats
actual Double
pct Double
_tol Maybe Double
_absTol ->
let allPass :: Bool
allPass = (Expectation -> Bool) -> [Expectation] -> Bool
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
all (\Expectation
e -> Expectation -> GoldenStats -> GoldenStats -> Bool
L.checkExpectation Expectation
e GoldenStats
golden GoldenStats
actual) [Expectation]
expectations
in if Bool
allPass
then BenchResult -> IO BenchResult
forall a. a -> IO a
forall (m :: * -> *) a. Monad m => a -> m a
return (BenchResult -> IO BenchResult) -> BenchResult -> IO BenchResult
forall a b. (a -> b) -> a -> b
$ GoldenStats -> GoldenStats -> [Warning] -> BenchResult
Pass GoldenStats
golden GoldenStats
actual []
else BenchResult -> IO BenchResult
forall a. a -> IO a
forall (m :: * -> *) a. Monad m => a -> m a
return (BenchResult -> IO BenchResult) -> BenchResult -> IO BenchResult
forall a b. (a -> b) -> a -> b
$ GoldenStats
-> GoldenStats -> Double -> Double -> Maybe Double -> BenchResult
Improvement GoldenStats
golden GoldenStats
actual Double
pct Double
tolPct Maybe Double
tolAbs
fromBenchResultWithExpectations :: [L.Expectation] -> BenchResult -> Result
fromBenchResultWithExpectations :: [Expectation] -> BenchResult -> Result
fromBenchResultWithExpectations [Expectation]
_expectations = BenchResult -> Result
fromBenchResult
fromBenchResult :: BenchResult -> Result
fromBenchResult :: BenchResult -> Result
fromBenchResult BenchResult
result = case BenchResult
result of
FirstRun GoldenStats
stats ->
String -> ResultStatus -> Result
Result (GoldenStats -> String
formatFirstRun GoldenStats
stats) ResultStatus
Success
Pass GoldenStats
golden GoldenStats
actual [Warning]
warnings ->
let info :: String
info = GoldenStats -> GoldenStats -> String
formatPass GoldenStats
golden GoldenStats
actual
warningInfo :: String
warningInfo = [Warning] -> String
formatWarnings [Warning]
warnings
in String -> ResultStatus -> Result
Result (String
info String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
warningInfo) ResultStatus
Success
Regression GoldenStats
golden GoldenStats
actual Double
pctChange Double
tolerance Maybe Double
absToleranceMs ->
let toleranceDesc :: String
toleranceDesc :: String
toleranceDesc = case Maybe Double
absToleranceMs of
Maybe Double
Nothing -> String -> Double -> String
forall r. PrintfType r => String -> r
printf String
"tolerance: %.1f%%" Double
tolerance
Just Double
absMs -> String -> Double -> Double -> String
forall r. PrintfType r => String -> r
printf String
"tolerance: %.1f%% or %.3f ms" Double
tolerance Double
absMs
changeVerb :: String
changeVerb :: String
changeVerb = if Double
pctChange Double -> Double -> Bool
forall a. Ord a => a -> a -> Bool
>= Double
0 then String
"increased" else String
"decreased"
absPctChange :: Double
absPctChange = Double -> Double
forall a. Num a => a -> a
abs Double
pctChange
message :: String
message = String -> String -> Double -> String -> String -> String
forall r. PrintfType r => String -> r
printf String
"Mean time %s by %.1f%% (%s)\n\n%s"
String
changeVerb Double
absPctChange String
toleranceDesc (GoldenStats -> GoldenStats -> String
formatRegression GoldenStats
golden GoldenStats
actual)
in String -> ResultStatus -> Result
Result String
message (Maybe Location -> FailureReason -> ResultStatus
Failure Maybe Location
forall a. Maybe a
Nothing (String -> FailureReason
Reason String
message))
Improvement GoldenStats
golden GoldenStats
actual Double
pctChange Double
tolerance Maybe Double
absToleranceMs ->
let toleranceDesc :: String
toleranceDesc :: String
toleranceDesc = case Maybe Double
absToleranceMs of
Maybe Double
Nothing -> String -> Double -> String
forall r. PrintfType r => String -> r
printf String
"tolerance: %.1f%%" Double
tolerance
Just Double
absMs -> String -> Double -> Double -> String
forall r. PrintfType r => String -> r
printf String
"tolerance: %.1f%% or %.3f ms" Double
tolerance Double
absMs
in String -> ResultStatus -> Result
Result (String -> Double -> String -> String -> String
forall r. PrintfType r => String -> r
printf String
"Performance improved by %.1f%% (%s)\n%s"
Double
pctChange String
toleranceDesc (GoldenStats -> GoldenStats -> String
formatPass GoldenStats
golden GoldenStats
actual))
ResultStatus
Success
formatFirstRun :: GoldenStats -> String
formatFirstRun :: GoldenStats -> String
formatFirstRun GoldenStats
stats = String
"First run - baseline created\n" String -> String -> String
forall a. [a] -> [a] -> [a]
++ GoldenStats -> String
formatStats GoldenStats
stats
formatRegression :: GoldenStats -> GoldenStats -> String
formatRegression :: GoldenStats -> GoldenStats -> String
formatRegression GoldenStats
golden GoldenStats
actual =
let meanDiff :: Double
meanDiff = if GoldenStats -> Double
statsMean GoldenStats
golden Double -> Double -> Bool
forall a. Eq a => a -> a -> Bool
== Double
0
then Double
0
else ((GoldenStats -> Double
statsMean GoldenStats
actual Double -> Double -> Double
forall a. Num a => a -> a -> a
- GoldenStats -> Double
statsMean GoldenStats
golden) Double -> Double -> Double
forall a. Fractional a => a -> a -> a
/ GoldenStats -> Double
statsMean GoldenStats
golden) Double -> Double -> Double
forall a. Num a => a -> a -> a
* Double
100
stddevDiff :: Double
stddevDiff = if GoldenStats -> Double
statsStddev GoldenStats
golden Double -> Double -> Bool
forall a. Eq a => a -> a -> Bool
== Double
0
then Double
0
else ((GoldenStats -> Double
statsStddev GoldenStats
actual Double -> Double -> Double
forall a. Num a => a -> a -> a
- GoldenStats -> Double
statsStddev GoldenStats
golden) Double -> Double -> Double
forall a. Fractional a => a -> a -> a
/ GoldenStats -> Double
statsStddev GoldenStats
golden) Double -> Double -> Double
forall a. Num a => a -> a -> a
* Double
100
medianDiff :: Double
medianDiff = if GoldenStats -> Double
statsMedian GoldenStats
golden Double -> Double -> Bool
forall a. Eq a => a -> a -> Bool
== Double
0
then Double
0
else ((GoldenStats -> Double
statsMedian GoldenStats
actual Double -> Double -> Double
forall a. Num a => a -> a -> a
- GoldenStats -> Double
statsMedian GoldenStats
golden) Double -> Double -> Double
forall a. Fractional a => a -> a -> a
/ GoldenStats -> Double
statsMedian GoldenStats
golden) Double -> Double -> Double
forall a. Num a => a -> a -> a
* Double
100
metricCol :: Box
metricCol = Alignment -> [Box] -> Box
forall (f :: * -> *). Foldable f => Alignment -> f Box -> Box
Box.vcat Alignment
Box.left ([Box] -> Box) -> [Box] -> Box
forall a b. (a -> b) -> a -> b
$ (String -> Box) -> [String] -> [Box]
forall a b. (a -> b) -> [a] -> [b]
map String -> Box
Box.text
[String
"Metric", String
"------", String
"Mean", String
"Stddev", String
"Median", String
"Min", String
"Max"]
baselineCol :: Box
baselineCol = Alignment -> [Box] -> Box
forall (f :: * -> *). Foldable f => Alignment -> f Box -> Box
Box.vcat Alignment
Box.right ([Box] -> Box) -> [Box] -> Box
forall a b. (a -> b) -> a -> b
$ (String -> Box) -> [String] -> [Box]
forall a b. (a -> b) -> [a] -> [b]
map String -> Box
Box.text
[ String
"Baseline"
, String
"--------"
, String -> Double -> String
forall r. PrintfType r => String -> r
printf String
"%.3f ms" (GoldenStats -> Double
statsMean GoldenStats
golden)
, String -> Double -> String
forall r. PrintfType r => String -> r
printf String
"%.3f ms" (GoldenStats -> Double
statsStddev GoldenStats
golden)
, String -> Double -> String
forall r. PrintfType r => String -> r
printf String
"%.3f ms" (GoldenStats -> Double
statsMedian GoldenStats
golden)
, String -> Double -> String
forall r. PrintfType r => String -> r
printf String
"%.3f ms" (GoldenStats -> Double
statsMin GoldenStats
golden)
, String -> Double -> String
forall r. PrintfType r => String -> r
printf String
"%.3f ms" (GoldenStats -> Double
statsMax GoldenStats
golden)
]
actualCol :: Box
actualCol = Alignment -> [Box] -> Box
forall (f :: * -> *). Foldable f => Alignment -> f Box -> Box
Box.vcat Alignment
Box.right ([Box] -> Box) -> [Box] -> Box
forall a b. (a -> b) -> a -> b
$ (String -> Box) -> [String] -> [Box]
forall a b. (a -> b) -> [a] -> [b]
map String -> Box
Box.text
[ String
"Actual"
, String
"------"
, String -> Double -> String
forall r. PrintfType r => String -> r
printf String
"%.3f ms" (GoldenStats -> Double
statsMean GoldenStats
actual)
, String -> Double -> String
forall r. PrintfType r => String -> r
printf String
"%.3f ms" (GoldenStats -> Double
statsStddev GoldenStats
actual)
, String -> Double -> String
forall r. PrintfType r => String -> r
printf String
"%.3f ms" (GoldenStats -> Double
statsMedian GoldenStats
actual)
, String -> Double -> String
forall r. PrintfType r => String -> r
printf String
"%.3f ms" (GoldenStats -> Double
statsMin GoldenStats
actual)
, String -> Double -> String
forall r. PrintfType r => String -> r
printf String
"%.3f ms" (GoldenStats -> Double
statsMax GoldenStats
actual)
]
diffCol :: Box
diffCol = Alignment -> [Box] -> Box
forall (f :: * -> *). Foldable f => Alignment -> f Box -> Box
Box.vcat Alignment
Box.right ([Box] -> Box) -> [Box] -> Box
forall a b. (a -> b) -> a -> b
$ (String -> Box) -> [String] -> [Box]
forall a b. (a -> b) -> [a] -> [b]
map String -> Box
Box.text
[ String
"Diff"
, String
"----"
, String -> Double -> String
forall r. PrintfType r => String -> r
printf String
"%+.1f%%" Double
meanDiff
, String -> Double -> String
forall r. PrintfType r => String -> r
printf String
"%+.1f%%" Double
stddevDiff
, String -> Double -> String
forall r. PrintfType r => String -> r
printf String
"%+.1f%%" Double
medianDiff
, String
""
, String
""
]
table :: Box
table = Int -> Alignment -> [Box] -> Box
forall (f :: * -> *).
Foldable f =>
Int -> Alignment -> f Box -> Box
Box.hsep Int
2 Alignment
Box.top [Box
metricCol, Box
baselineCol, Box
actualCol, Box
diffCol]
in Box -> String
Box.render Box
table
formatPass :: GoldenStats -> GoldenStats -> String
formatPass :: GoldenStats -> GoldenStats -> String
formatPass GoldenStats
golden GoldenStats
actual =
let meanDiff :: Double
meanDiff = if GoldenStats -> Double
statsMean GoldenStats
golden Double -> Double -> Bool
forall a. Eq a => a -> a -> Bool
== Double
0
then Double
0
else ((GoldenStats -> Double
statsMean GoldenStats
actual Double -> Double -> Double
forall a. Num a => a -> a -> a
- GoldenStats -> Double
statsMean GoldenStats
golden) Double -> Double -> Double
forall a. Fractional a => a -> a -> a
/ GoldenStats -> Double
statsMean GoldenStats
golden) Double -> Double -> Double
forall a. Num a => a -> a -> a
* Double
100
stddevDiff :: Double
stddevDiff = if GoldenStats -> Double
statsStddev GoldenStats
golden Double -> Double -> Bool
forall a. Eq a => a -> a -> Bool
== Double
0
then Double
0
else ((GoldenStats -> Double
statsStddev GoldenStats
actual Double -> Double -> Double
forall a. Num a => a -> a -> a
- GoldenStats -> Double
statsStddev GoldenStats
golden) Double -> Double -> Double
forall a. Fractional a => a -> a -> a
/ GoldenStats -> Double
statsStddev GoldenStats
golden) Double -> Double -> Double
forall a. Num a => a -> a -> a
* Double
100
metricCol :: Box
metricCol = Alignment -> [Box] -> Box
forall (f :: * -> *). Foldable f => Alignment -> f Box -> Box
Box.vcat Alignment
Box.left ([Box] -> Box) -> [Box] -> Box
forall a b. (a -> b) -> a -> b
$ (String -> Box) -> [String] -> [Box]
forall a b. (a -> b) -> [a] -> [b]
map String -> Box
Box.text [String
"Metric", String
"------", String
"Mean", String
"Stddev"]
baselineCol :: Box
baselineCol = Alignment -> [Box] -> Box
forall (f :: * -> *). Foldable f => Alignment -> f Box -> Box
Box.vcat Alignment
Box.right ([Box] -> Box) -> [Box] -> Box
forall a b. (a -> b) -> a -> b
$ (String -> Box) -> [String] -> [Box]
forall a b. (a -> b) -> [a] -> [b]
map String -> Box
Box.text
[ String
"Baseline"
, String
"--------"
, String -> Double -> String
forall r. PrintfType r => String -> r
printf String
"%.3f ms" (GoldenStats -> Double
statsMean GoldenStats
golden)
, String -> Double -> String
forall r. PrintfType r => String -> r
printf String
"%.3f ms" (GoldenStats -> Double
statsStddev GoldenStats
golden)
]
actualCol :: Box
actualCol = Alignment -> [Box] -> Box
forall (f :: * -> *). Foldable f => Alignment -> f Box -> Box
Box.vcat Alignment
Box.right ([Box] -> Box) -> [Box] -> Box
forall a b. (a -> b) -> a -> b
$ (String -> Box) -> [String] -> [Box]
forall a b. (a -> b) -> [a] -> [b]
map String -> Box
Box.text
[ String
"Actual"
, String
"------"
, String -> Double -> String
forall r. PrintfType r => String -> r
printf String
"%.3f ms" (GoldenStats -> Double
statsMean GoldenStats
actual)
, String -> Double -> String
forall r. PrintfType r => String -> r
printf String
"%.3f ms" (GoldenStats -> Double
statsStddev GoldenStats
actual)
]
diffCol :: Box
diffCol = Alignment -> [Box] -> Box
forall (f :: * -> *). Foldable f => Alignment -> f Box -> Box
Box.vcat Alignment
Box.right ([Box] -> Box) -> [Box] -> Box
forall a b. (a -> b) -> a -> b
$ (String -> Box) -> [String] -> [Box]
forall a b. (a -> b) -> [a] -> [b]
map String -> Box
Box.text
[ String
"Diff"
, String
"----"
, String -> Double -> String
forall r. PrintfType r => String -> r
printf String
"%+.1f%%" Double
meanDiff
, String -> Double -> String
forall r. PrintfType r => String -> r
printf String
"%+.1f%%" Double
stddevDiff
]
table :: Box
table = Int -> Alignment -> [Box] -> Box
forall (f :: * -> *).
Foldable f =>
Int -> Alignment -> f Box -> Box
Box.hsep Int
2 Alignment
Box.top [Box
metricCol, Box
baselineCol, Box
actualCol, Box
diffCol]
in Box -> String
Box.render Box
table
formatStats :: GoldenStats -> String
formatStats :: GoldenStats -> String
formatStats GoldenStats{Double
[Double]
[(Int, Double)]
Text
UTCTime
statsMean :: GoldenStats -> Double
statsStddev :: GoldenStats -> Double
statsMedian :: GoldenStats -> Double
statsMin :: GoldenStats -> Double
statsMax :: GoldenStats -> Double
statsMean :: Double
statsStddev :: Double
statsMedian :: Double
statsMin :: Double
statsMax :: Double
statsPercentiles :: [(Int, Double)]
statsArch :: Text
statsTimestamp :: UTCTime
statsTrimmedMean :: Double
statsMAD :: Double
statsIQR :: Double
statsOutliers :: [Double]
statsOutliers :: GoldenStats -> [Double]
statsIQR :: GoldenStats -> Double
statsMAD :: GoldenStats -> Double
statsTrimmedMean :: GoldenStats -> Double
statsTimestamp :: GoldenStats -> UTCTime
statsArch :: GoldenStats -> Text
statsPercentiles :: GoldenStats -> [(Int, Double)]
..} =
let metricCol :: Box
metricCol = Alignment -> [Box] -> Box
forall (f :: * -> *). Foldable f => Alignment -> f Box -> Box
Box.vcat Alignment
Box.left ([Box] -> Box) -> [Box] -> Box
forall a b. (a -> b) -> a -> b
$ (String -> Box) -> [String] -> [Box]
forall a b. (a -> b) -> [a] -> [b]
map String -> Box
Box.text
[ String
"Metric", String
"------", String
"Mean", String
"Stddev", String
"Median", String
"Min", String
"Max", String
"Arch" ]
valueCol :: Box
valueCol = Alignment -> [Box] -> Box
forall (f :: * -> *). Foldable f => Alignment -> f Box -> Box
Box.vcat Alignment
Box.right ([Box] -> Box) -> [Box] -> Box
forall a b. (a -> b) -> a -> b
$ (String -> Box) -> [String] -> [Box]
forall a b. (a -> b) -> [a] -> [b]
map String -> Box
Box.text
[ String
"Value"
, String
"-----"
, String -> Double -> String
forall r. PrintfType r => String -> r
printf String
"%.3f ms" Double
statsMean
, String -> Double -> String
forall r. PrintfType r => String -> r
printf String
"%.3f ms" Double
statsStddev
, String -> Double -> String
forall r. PrintfType r => String -> r
printf String
"%.3f ms" Double
statsMedian
, String -> Double -> String
forall r. PrintfType r => String -> r
printf String
"%.3f ms" Double
statsMin
, String -> Double -> String
forall r. PrintfType r => String -> r
printf String
"%.3f ms" Double
statsMax
, Text -> String
T.unpack Text
statsArch
]
table :: Box
table = Int -> Alignment -> [Box] -> Box
forall (f :: * -> *).
Foldable f =>
Int -> Alignment -> f Box -> Box
Box.hsep Int
2 Alignment
Box.top [Box
metricCol, Box
valueCol]
in Box -> String
Box.render Box
table
formatWarnings :: [Warning] -> String
formatWarnings :: [Warning] -> String
formatWarnings [] = String
""
formatWarnings [Warning]
ws = String
"\nWarnings:\n" String -> String -> String
forall a. [a] -> [a] -> [a]
++ [String] -> String
unlines ((Warning -> String) -> [Warning] -> [String]
forall a b. (a -> b) -> [a] -> [b]
map Warning -> String
formatWarning [Warning]
ws)
formatWarning :: Warning -> String
formatWarning :: Warning -> String
formatWarning Warning
w = case Warning
w of
VarianceIncreased Double
golden Double
actual Double
pct Double
tolerance ->
String -> Double -> Double -> Double -> Double -> String
forall r. PrintfType r => String -> r
printf String
" ⚠ Variance increased by %.1f%% (%.3f ms -> %.3f ms, tolerance: %.1f%%)"
Double
pct Double
golden Double
actual Double
tolerance
VarianceDecreased Double
golden Double
actual Double
pct Double
tolerance ->
String -> Double -> Double -> Double -> Double -> String
forall r. PrintfType r => String -> r
printf String
" ⚠ Variance decreased by %.1f%% (%.3f ms -> %.3f ms, tolerance: %.1f%%)"
Double
pct Double
golden Double
actual Double
tolerance
HighVariance Double
cv ->
String -> Double -> String
forall r. PrintfType r => String -> r
printf String
" ⚠ High variance detected (CV = %.1f%%)" (Double
cv Double -> Double -> Double
forall a. Num a => a -> a -> a
* Double
100)
OutliersDetected Int
count [Double]
outliers ->
let outlierStr :: String
outlierStr = if Int
count Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
<= Int
5
then [String] -> String
unwords ((Double -> String) -> [Double] -> [String]
forall a b. (a -> b) -> [a] -> [b]
map (String -> Double -> String
forall r. PrintfType r => String -> r
printf String
"%.3fms") [Double]
outliers)
else [String] -> String
unwords ((Double -> String) -> [Double] -> [String]
forall a b. (a -> b) -> [a] -> [b]
map (String -> Double -> String
forall r. PrintfType r => String -> r
printf String
"%.3fms") (Int -> [Double] -> [Double]
forall a. Int -> [a] -> [a]
take Int
5 [Double]
outliers)) String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
"..."
in String -> Int -> String -> String
forall r. PrintfType r => String -> r
printf String
" ⚠ %d outlier(s) detected: %s" Int
count String
outlierStr