streamly-0.8.1: benchmark/bench-report/BenchReport.hs
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE ScopedTypeVariables #-}
module Main where
import Control.Exception (catch, ErrorCall(..))
import Control.Monad (mzero)
import Control.Monad.IO.Class (liftIO)
import Control.Monad.Trans.Maybe
import Control.Monad.Trans.State
import Data.Char (toLower)
import Data.List
import GHC.Real (infinity)
import System.Environment (getArgs)
import Text.Read (readMaybe)
import BenchShow
------------------------------------------------------------------------------
-- Command line parsing
------------------------------------------------------------------------------
data BenchType
= Compare String
| Standard String
deriving Show
data Options = Options
{ genGraphs :: Bool
, sortByName :: Bool
, useGauge :: Bool
, benchType :: Maybe BenchType
, fields :: [String]
, diffStyle :: GroupStyle
, cutOffPercent :: Double
} deriving Show
defaultOptions :: Options
defaultOptions = Options False False False Nothing ["time"] PercentDiff 0
setGenGraphs :: Monad m => Bool -> StateT (a, Options) m ()
setGenGraphs val = do
(args, opts) <- get
put (args, opts { genGraphs = val })
setSortByName :: Monad m => Bool -> StateT (a, Options) m ()
setSortByName val = do
(args, opts) <- get
put (args, opts { sortByName = val })
setUseGauge :: Monad m => Bool -> StateT (a, Options) m ()
setUseGauge val = do
(args, opts) <- get
put (args, opts { useGauge = val })
setBenchType :: Monad m => BenchType -> StateT (a, Options) m ()
setBenchType val = do
(args, opts) <- get
put (args, opts { benchType = Just val })
setFields :: Monad m => [String] -> StateT (a, Options) m ()
setFields val = do
(args, opts) <- get
put (args, opts { fields = val })
setDiff :: Monad m => String -> StateT (a, Options) m ()
setDiff val = do
(args, opts) <- get
let cmpStyle =
case val of
"absolute" -> Absolute
"multiples" -> Multiples
"percent" -> PercentDiff
x -> error $ "Unknown diff option: " ++ show x
in put (args, opts { diffStyle = cmpStyle })
setCutOff :: Monad m => String -> StateT (a, Options) m ()
setCutOff val = do
(args, opts) <- get
case readMaybe val of
Just x -> put (args, opts { cutOffPercent = x })
Nothing -> error $ "Invalid cutoff value: " ++ show val
-- Like the shell "shift" to shift the command line arguments
shift :: StateT ([String], Options) (MaybeT IO) (Maybe String)
shift = do
s <- get
case s of
([], _) -> return Nothing
(x : xs, opts) -> put (xs, opts) >> return (Just x)
parseBench :: StateT ([String], Options) (MaybeT IO) ()
parseBench = do
x <- shift
case x of
Just str | "_cmp" `isSuffixOf` str -> setBenchType (Compare str)
Just str -> setBenchType (Standard str)
Nothing -> do
liftIO $ putStrLn "please provide a benchmark type "
mzero
parseFields :: StateT ([String], Options) (MaybeT IO) ()
parseFields = do
x <- shift
case x of
Just str -> setFields (words str)
Nothing -> do
liftIO $ putStrLn
"please provide a list of fields after --fields"
mzero
parseDiff :: StateT ([String], Options) (MaybeT IO) ()
parseDiff = do
x <- shift
case x of
Just str -> setDiff str
Nothing -> do
liftIO $ putStrLn "please provide a diff type"
mzero
parseCutOff :: StateT ([String], Options) (MaybeT IO) ()
parseCutOff = do
x <- shift
case x of
Just str -> setCutOff str
Nothing -> do
liftIO $ putStrLn "please provide a cutoff percent"
mzero
-- totally imperative style option parsing
parseOptions :: IO (Maybe Options)
parseOptions = do
args <- getArgs
runMaybeT $ flip evalStateT (args, defaultOptions) $ do
parseLoop
fmap snd get
where
parseOpt opt =
case opt of
"--graphs" -> setGenGraphs True
"--sort-by-name" -> setSortByName True
"--use-gauge" -> setUseGauge True
"--benchmark" -> parseBench
"--fields" -> parseFields
"--diff-style" -> parseDiff
"--diff-cutoff-percent" -> parseCutOff
str -> do
liftIO $ putStrLn $ "Unrecognized option " <> str
mzero
parseLoop = do
next <- shift
case next of
Just opt -> parseOpt opt >> parseLoop
Nothing -> return ()
ignoringErr :: IO () -> IO ()
ignoringErr a = catch a (\(ErrorCall err :: ErrorCall) ->
putStrLn $ "Failed with error:\n" <> err <> "\nSkipping.")
------------------------------------------------------------------------------
-- Generic
------------------------------------------------------------------------------
makeGraphs :: String -> Config -> String -> IO ()
makeGraphs name cfg@Config{..} inputFile =
ignoringErr $ graph inputFile name cfg
------------------------------------------------------------------------------
-- Arrays
------------------------------------------------------------------------------
showComparisons :: Options -> Config -> FilePath -> FilePath -> IO ()
showComparisons Options{..} cfg inp out =
let cfg1 = cfg { classifyBenchmark = classifyComparison }
in if genGraphs
then ignoringErr $ graph inp "comparison"
cfg1 { outputDir = Just out
, presentation = Groups Absolute
}
else ignoringErr $ report inp Nothing cfg1
where
separator = if useGauge then '/' else '.'
dropComponent sep = dropWhile (== sep) . dropWhile (/= sep)
-- In case of tasty-bench the names could be like
-- All.Data.Array.Prim.Pinned/o-1-space.generation.show
-- All.Data.Array.Foreign/o-1-space.generation.show
classifyComparison b =
let b1 =
if useGauge
then b
else dropComponent separator b --- drop "All." at the beginning
in Just
( takeWhile (/= '/') b1
, dropComponent '/' b1 -- for tasty-bench drop up to "/"
)
------------------------------------------------------------------------------
-- text reports
------------------------------------------------------------------------------
selectBench
:: Options
-> (SortColumn -> Maybe GroupStyle -> Either String [(String, Double)])
-> [String]
selectBench Options{..} f =
reverse
$ fmap fst
$ filter (\(_,y) -> filterPred y)
$ either
(const $ either error sortFunc $ f (ColumnIndex 0) (Just PercentDiff))
sortFunc
$ f (ColumnIndex 1) (Just PercentDiff)
where
sortFunc = if sortByName then sortOn fst else sortOn snd
cutOffMultiples = 1 + cutOffPercent / 100
filterPred x =
case benchType of
Just (Compare _) ->
x <= negate cutOffPercent || x >= cutOffPercent
_ -> True
benchShow ::
Options
-> Config
-> (Config -> String -> IO ())
-> String
-> FilePath
-> IO ()
benchShow Options{..} cfg func inp out =
if genGraphs
then func cfg {outputDir = Just out} inp
else ignoringErr $ report inp Nothing cfg
main :: IO ()
main = do
res <- parseOptions
case res of
Nothing -> do
putStrLn "cannot parse options"
return ()
Just opts@Options{fields = fs, benchType = btype} ->
let cfg = defaultConfig
{ presentation = Groups (diffStyle opts)
, selectBenchmarks = selectBench opts
, selectFields = filter
( flip elem (fmap (fmap toLower) fs)
. fmap toLower
)
}
in case btype of
Just (Compare str) ->
showComparisons opts cfg
{ title = Just str }
("charts/" ++ str ++ "/results.csv")
("charts/" ++ str)
Just (Standard str) ->
benchShow opts cfg
{ title = Just str }
(makeGraphs str)
("charts/" ++ str ++ "/results.csv")
("charts/" ++ str)
Nothing ->
error "Please specify a benchmark using --benchmark."