packages feed

bench-1.0.10: src/Main.hs

{-# LANGUAGE OverloadedStrings  #-}
{-# LANGUAGE RecordWildCards    #-}

module Main where

import Control.Applicative
import Data.Monoid ((<>))
import Data.Text (Text)
import Turtle (ExitCode(..), Parser, ShellFailed(..), s, (%))

import qualified Control.Exception
import qualified Criterion
import qualified Criterion.Main         as Criterion
import qualified Criterion.Main.Options as Criterion
import qualified Data.Text              as Text
import qualified Data.Version
import qualified Options.Applicative
import qualified Paths_bench
import qualified System.IO              as IO
import qualified System.IO.Silently     as Silently
import qualified System.Process
import qualified Turtle

version :: Text
version = Text.pack (Data.Version.showVersion Paths_bench.version)

data Options
    = Options (Maybe Text) (Maybe Text) [Text] Criterion.Mode
    | Version deriving (Show)

before :: Parser Text
before =
    Options.Applicative.strOption
        (   Options.Applicative.long "before"
        <>  Options.Applicative.help "Specify a command to run before each run of the benchmark"
        <>  Options.Applicative.metavar "command"
        )

after :: Parser Text
after =
    Options.Applicative.strOption
        (   Options.Applicative.long "after"
        <>  Options.Applicative.help "Specify a command to run after each run of the benchmark"
        <>  Options.Applicative.metavar "command"
        )

parser :: Parser Options
parser =
            Version
        <$  Options.Applicative.flag'
                ()
                (   Options.Applicative.short 'v'
                <>  Options.Applicative.long "version"
                <>  Options.Applicative.help "Version number"
                )
    <|>     Options
        <$> optional before
        <*> optional after
        <*> some
                (Turtle.argText "command(s)" "The command line(s) to benchmark")
        <*> Criterion.parseWith Criterion.defaultConfig

main :: IO ()
main = do
    x <- Turtle.options "Command-line tool to benchmark other programs" parser
    case x of
        Options maybeBefore maybeAfter commands mode -> do
            let benches = map (buildBench maybeBefore maybeAfter) commands

            let benches' = case commands of
                    [_] -> benches
                    _   -> [ Criterion.bgroup "bench" benches ]

            Criterion.runMode mode benches'
        Version -> do
            Turtle.printf ("bench version "%s%"\n") version

buildBench :: Maybe Text -> Maybe Text -> Text -> Criterion.Benchmark
buildBench maybeBefore maybeAfter command = do
    let io cmd = Silently.hSilence [IO.stdout, IO.stderr] $ do
            let createProcess =
                    (System.Process.shell (Text.unpack cmd))
                        { System.Process.std_in  = System.Process.NoStream
                        }

            exitCode <- Turtle.system createProcess empty
            case exitCode of
                ExitFailure _ -> do
                    Control.Exception.throwIO (ShellFailed cmd exitCode)
                _  -> do
                    return ()

    let benchmarkable = case (maybeBefore, maybeAfter) of
            (Just before_, Just after_) ->
                Criterion.perRunEnvWithCleanup
                    (io before_)
                    (\_ -> io after_)
                    (\_ -> io command)
            (Just before_, Nothing) ->
                Criterion.perRunEnvWithCleanup
                    (io before_)
                    (\_ -> return ())
                    (\_ -> io command)
            (Nothing, Just after_) ->
                Criterion.perRunEnvWithCleanup
                    (return ())
                    (\_ -> io after_)
                    (\_ -> io command)
            (Nothing, Nothing) ->
                Criterion.nfIO (io command)

    Criterion.bench (Text.unpack command) benchmarkable