packages feed

criterion 1.1.4.0 → 1.2.0.0

raw patch · 17 files changed

+657/−186 lines, 17 filesdep +base-compatdep +exceptionsdep +microstachedep −hastachedep ~basedep ~bytestringdep ~deepseqnew-component:exe:criterion-reportnew-uploaderPVP ok

version bump matches the API change (PVP)

Dependencies added: base-compat, exceptions, microstache

Dependencies removed: hastache

Dependency ranges changed: base, bytestring, deepseq, optparse-applicative, statistics

API changes (from Hackage documentation)

- Criterion.Types: instance Data.Data.Data Criterion.Types.DataRecord
- Criterion.Types: instance Data.Data.Data Criterion.Types.Regression
- Criterion.Types: instance Data.Data.Data Criterion.Types.Report
- Criterion.Types: instance Data.Data.Data Criterion.Types.SampleAnalysis
- Criterion.Types: newtype Benchmarkable
+ Criterion: envWithCleanup :: NFData env => IO env -> (env -> IO a) -> (env -> Benchmark) -> Benchmark
+ Criterion: perBatchEnv :: (NFData env, NFData b) => (Int64 -> IO env) -> (env -> IO b) -> Benchmarkable
+ Criterion: perBatchEnvWithCleanup :: (NFData env, NFData b) => (Int64 -> IO env) -> (Int64 -> env -> IO ()) -> (env -> IO b) -> Benchmarkable
+ Criterion: perRunEnv :: (NFData env, NFData b) => IO env -> (env -> IO b) -> Benchmarkable
+ Criterion: perRunEnvWithCleanup :: (NFData env, NFData b) => IO env -> (env -> IO ()) -> (env -> IO b) -> Benchmarkable
+ Criterion.Main: envWithCleanup :: NFData env => IO env -> (env -> IO a) -> (env -> Benchmark) -> Benchmark
+ Criterion.Main: perBatchEnv :: (NFData env, NFData b) => (Int64 -> IO env) -> (env -> IO b) -> Benchmarkable
+ Criterion.Main: perBatchEnvWithCleanup :: (NFData env, NFData b) => (Int64 -> IO env) -> (Int64 -> env -> IO ()) -> (env -> IO b) -> Benchmarkable
+ Criterion.Main: perRunEnv :: (NFData env, NFData b) => IO env -> (env -> IO b) -> Benchmarkable
+ Criterion.Main: perRunEnvWithCleanup :: (NFData env, NFData b) => IO env -> (env -> IO ()) -> (env -> IO b) -> Benchmarkable
+ Criterion.Measurement: runBenchmarkable :: Benchmarkable -> Int64 -> (a -> a -> a) -> (IO () -> IO a) -> IO a
+ Criterion.Measurement: runBenchmarkable_ :: Benchmarkable -> Int64 -> IO ()
+ Criterion.Types: [allocEnv] :: Benchmarkable -> Int64 -> IO a
+ Criterion.Types: [cleanEnv] :: Benchmarkable -> Int64 -> a -> IO ()
+ Criterion.Types: [perRun] :: Benchmarkable -> Bool
+ Criterion.Types: data Benchmarkable
+ Criterion.Types: envWithCleanup :: NFData env => IO env -> (env -> IO a) -> (env -> Benchmark) -> Benchmark
+ Criterion.Types: perBatchEnv :: (NFData env, NFData b) => (Int64 -> IO env) -> (env -> IO b) -> Benchmarkable
+ Criterion.Types: perBatchEnvWithCleanup :: (NFData env, NFData b) => (Int64 -> IO env) -> (Int64 -> env -> IO ()) -> (env -> IO b) -> Benchmarkable
+ Criterion.Types: perRunEnv :: (NFData env, NFData b) => IO env -> (env -> IO b) -> Benchmarkable
+ Criterion.Types: perRunEnvWithCleanup :: (NFData env, NFData b) => IO env -> (env -> IO ()) -> (env -> IO b) -> Benchmarkable
- Criterion.Analysis: SampleAnalysis :: [Regression] -> Double -> Estimate -> Estimate -> OutlierVariance -> SampleAnalysis
+ Criterion.Analysis: SampleAnalysis :: [Regression] -> Double -> Estimate ConfInt Double -> Estimate ConfInt Double -> OutlierVariance -> SampleAnalysis
- Criterion.Analysis: [anMean] :: SampleAnalysis -> Estimate
+ Criterion.Analysis: [anMean] :: SampleAnalysis -> Estimate ConfInt Double
- Criterion.Analysis: [anStdDev] :: SampleAnalysis -> Estimate
+ Criterion.Analysis: [anStdDev] :: SampleAnalysis -> Estimate ConfInt Double
- Criterion.Analysis: outlierVariance :: Estimate -> Estimate -> Double -> OutlierVariance
+ Criterion.Analysis: outlierVariance :: Estimate ConfInt Double -> Estimate ConfInt Double -> Double -> OutlierVariance
- Criterion.Main.Options: RunIters :: Int64 -> MatchType -> [String] -> Mode
+ Criterion.Main.Options: RunIters :: Config -> Int64 -> MatchType -> [String] -> Mode
- Criterion.Report: includeFile :: (MonadIO m) => [FilePath] -> Text -> m Text
+ Criterion.Report: includeFile :: (MonadIO m) => [FilePath] -> FilePath -> m Text
- Criterion.Report: vector :: (Monad m, Vector v a, MuVar a) => String -> v a -> MuType m
+ Criterion.Report: vector :: (Vector v a, ToJSON a) => Text -> v a -> Value
- Criterion.Report: vector2 :: (Monad m, Vector v a, Vector v b, MuVar a, MuVar b) => String -> String -> v a -> v b -> MuType m
+ Criterion.Report: vector2 :: (Vector v a, Vector v b, ToJSON a, ToJSON b) => Text -> Text -> v a -> v b -> Value
- Criterion.Types: Benchmarkable :: (Int64 -> IO ()) -> Benchmarkable
+ Criterion.Types: Benchmarkable :: (Int64 -> IO a) -> (Int64 -> a -> IO ()) -> (a -> Int64 -> IO ()) -> Bool -> Benchmarkable
- Criterion.Types: Config :: Double -> Bool -> Double -> Int -> [([String], String)] -> Maybe FilePath -> Maybe FilePath -> Maybe FilePath -> Maybe FilePath -> Maybe FilePath -> Verbosity -> FilePath -> Config
+ Criterion.Types: Config :: CL Double -> Bool -> Double -> Int -> [([String], String)] -> Maybe FilePath -> Maybe FilePath -> Maybe FilePath -> Maybe FilePath -> Maybe FilePath -> Verbosity -> FilePath -> Config
- Criterion.Types: Regression :: String -> Map String Estimate -> Estimate -> Regression
+ Criterion.Types: Regression :: String -> Map String (Estimate ConfInt Double) -> Estimate ConfInt Double -> Regression
- Criterion.Types: SampleAnalysis :: [Regression] -> Double -> Estimate -> Estimate -> OutlierVariance -> SampleAnalysis
+ Criterion.Types: SampleAnalysis :: [Regression] -> Double -> Estimate ConfInt Double -> Estimate ConfInt Double -> OutlierVariance -> SampleAnalysis
- Criterion.Types: [Environment] :: NFData env => IO env -> (env -> Benchmark) -> Benchmark
+ Criterion.Types: [Environment] :: NFData env => IO env -> (env -> IO a) -> (env -> Benchmark) -> Benchmark
- Criterion.Types: [anMean] :: SampleAnalysis -> Estimate
+ Criterion.Types: [anMean] :: SampleAnalysis -> Estimate ConfInt Double
- Criterion.Types: [anStdDev] :: SampleAnalysis -> Estimate
+ Criterion.Types: [anStdDev] :: SampleAnalysis -> Estimate ConfInt Double
- Criterion.Types: [confInterval] :: Config -> Double
+ Criterion.Types: [confInterval] :: Config -> CL Double
- Criterion.Types: [regCoeffs] :: Regression -> Map String Estimate
+ Criterion.Types: [regCoeffs] :: Regression -> Map String (Estimate ConfInt Double)
- Criterion.Types: [regRSquare] :: Regression -> Estimate
+ Criterion.Types: [regRSquare] :: Regression -> Estimate ConfInt Double
- Criterion.Types: [runRepeatedly] :: Benchmarkable -> Int64 -> IO ()
+ Criterion.Types: [runRepeatedly] :: Benchmarkable -> a -> Int64 -> IO ()

Files

Criterion.hs view
@@ -17,6 +17,11 @@     -- * Creating a benchmark suite     , Benchmark     , env+    , envWithCleanup+    , perBatchEnv+    , perBatchEnvWithCleanup+    , perRunEnv+    , perRunEnvWithCleanup     , bench     , bgroup     -- ** Running a benchmark
Criterion/Analysis.hs view
@@ -47,10 +47,10 @@ import Statistics.Function (sort) import Statistics.Quantile (weightedAvg) import Statistics.Regression (bootstrapRegress, olsRegress)-import Statistics.Resampling (resample)+import Statistics.Resampling (Estimator(..),resample) import Statistics.Sample (mean) import Statistics.Sample.KernelDensity (kde)-import Statistics.Types (Estimator(..), Sample)+import Statistics.Types (Sample) import System.Random.MWC (GenIO) import qualified Data.List as List import qualified Data.Map as Map@@ -58,6 +58,7 @@ import qualified Data.Vector.Generic as G import qualified Data.Vector.Unboxed as U import qualified Statistics.Resampling.Bootstrap as B+import qualified Statistics.Types                as B import Prelude  -- | Classify outliers in a data set, using the boxplot technique.@@ -81,11 +82,12 @@  -- | Compute the extent to which outliers in the sample data affect -- the sample mean and standard deviation.-outlierVariance :: B.Estimate  -- ^ Bootstrap estimate of sample mean.-                -> B.Estimate  -- ^ Bootstrap estimate of sample-                               --   standard deviation.-                -> Double      -- ^ Number of original iterations.-                -> OutlierVariance+outlierVariance+  :: B.Estimate B.ConfInt Double -- ^ Bootstrap estimate of sample mean.+  -> B.Estimate B.ConfInt Double -- ^ Bootstrap estimate of sample+                                 --   standard deviation.+  -> Double                      -- ^ Number of original iterations.+  -> OutlierVariance outlierVariance µ σ a = OutlierVariance effect desc varOutMin   where     ( effect, desc ) | varOutMin < 0.01 = (Unaffected, "no")@@ -160,7 +162,7 @@   rs <- mapM (\(ps,r) -> regress gen ps r meas) $         ((["iters"],"time"):regressions)   resamps <- liftIO $ resample gen ests resamples stime-  let [estMean,estStdDev] = B.bootstrapBCA confInterval stime ests resamps+  let [estMean,estStdDev] = B.bootstrapBCA confInterval stime resamps       ov = outlierVariance estMean estStdDev (fromIntegral n)       an = SampleAnalysis {                anRegress    = rs
Criterion/Internal.hs view
@@ -22,22 +22,23 @@ import Control.DeepSeq (rnf) import Control.Exception (evaluate) import Control.Monad (foldM, forM_, void, when, unless)+import Control.Monad.Catch (MonadMask, finally) import Control.Monad.Reader (ask, asks) import Control.Monad.Trans (MonadIO, liftIO) import Control.Monad.Trans.Except-import qualified Data.Binary as Binary +import qualified Data.Binary as Binary import Data.Int (Int64) import qualified Data.ByteString.Lazy.Char8 as L import Criterion.Analysis (analyseSample, noteOutliers) import Criterion.IO (header, headerRoot, critVersion, readJSONReports, writeJSONReports) import Criterion.IO.Printf (note, printError, prolix, writeCsv)-import Criterion.Measurement (runBenchmark, secs)+import Criterion.Measurement (runBenchmark, runBenchmarkable_, secs) import Criterion.Monad (Criterion) import Criterion.Report (report) import Criterion.Types hiding (measure) import qualified Data.Map as Map import qualified Data.Vector as V-import Statistics.Resampling.Bootstrap (Estimate(..))+import Statistics.Types (Estimate(..),ConfInt(..),confidenceInterval,cl95,confidenceLevel) import System.Directory (getTemporaryDirectory, removeFile) import System.IO (IOMode(..), hClose, openTempFile, openFile, hPutStr, openBinaryFile) import Text.Printf (printf)@@ -79,10 +80,11 @@         _ <- bs r2 (regResponder ++ ":") regRSquare         forM_ (Map.toList regCoeffs) $ \(prd,val) ->           bs (printf "%.3g") ("  " ++ prd) val-      writeCsv (desc,-                estPoint anMean, estLowerBound anMean, estUpperBound anMean,-                estPoint anStdDev, estLowerBound anStdDev,-                estUpperBound anStdDev)+      writeCsv+        (desc,+         estPoint anMean,   fst $ confidenceInterval anMean,   snd $ confidenceInterval anMean,+         estPoint anStdDev, fst $ confidenceInterval anStdDev, snd $ confidenceInterval anStdDev+        )       when (verbosity == Verbose || (ovEffect > Slight && verbosity > Quiet)) $ do         when (verbosity == Verbose) $ noteOutliers reportOutliers         _ <- note "variance introduced by outliers: %d%% (%s)\n"@@ -90,13 +92,17 @@         return ()       _ <- note "\n"       return (Analysed rpt)-      where bs :: (Double -> String) -> String -> Estimate -> Criterion ()-            bs f metric Estimate{..} =+      where bs :: (Double -> String) -> String -> Estimate ConfInt Double -> Criterion ()+            bs f metric e@Estimate{..} =               note "%-20s %-10s (%s .. %s%s)\n" metric-                   (f estPoint) (f estLowerBound) (f estUpperBound)-                   (if estConfidenceLevel == 0.95 then ""-                    else printf ", ci %.3f" estConfidenceLevel)+                   (f estPoint) (f $ fst $ confidenceInterval e) (f $ snd $ confidenceInterval e)+                   (let cl = confIntCL estError+                        str | cl == cl95 = ""+                            | otherwise  = printf ", ci %.3f" (confidenceLevel cl)+                    in str+                   ) + -- | Run a single benchmark and analyse its performance. runAndAnalyseOne :: Int -> String -> Benchmarkable -> Criterion DataRecord runAndAnalyseOne i desc bm = do@@ -122,7 +128,7 @@   -- The type we write to the file is ReportFileContents, a triple.   -- But here we ASSUME that the tuple will become a JSON array.   -- This assumption lets us stream the reports to the file incrementally:-  liftIO $ hPutStr handle $ "[ \"" ++ headerRoot ++ "\", " ++ +  liftIO $ hPutStr handle $ "[ \"" ++ headerRoot ++ "\", " ++                              "\"" ++ critVersion ++ "\", [ "    for select bs $ \idx desc bm -> do@@ -139,7 +145,7 @@     res <- readJSONReports jsonFile     case res of       Left err -> error $ "error reading file "++jsonFile++":\n  "++show err-      Right (_,_,rs) -> +      Right (_,_,rs) ->        case mbJsonFile of          Just _ -> return rs          _      -> removeFile jsonFile >> return rs@@ -160,7 +166,7 @@     Just file -> liftIO $ do       handle <- openBinaryFile file ReadWriteMode       L.hPut handle header-      forM_ reports $ \rpt ->  +      forM_ reports $ \rpt ->         L.hPut handle (Binary.encode rpt)       hClose handle @@ -175,20 +181,20 @@ runFixedIters iters select bs =   for select bs $ \_idx desc bm -> do     _ <- note "benchmarking %s\n" desc-    liftIO $ runRepeatedly bm iters+    liftIO $ runBenchmarkable_ bm iters  -- | Iterate over benchmarks.-for :: MonadIO m => (String -> Bool) -> Benchmark+for :: (MonadMask m, MonadIO m) => (String -> Bool) -> Benchmark     -> (Int -> String -> Benchmarkable -> m ()) -> m () for select bs0 handle = go (0::Int) ("", bs0) >> return ()   where-    go !idx (pfx, Environment mkenv mkbench)+    go !idx (pfx, Environment mkenv cleanenv mkbench)       | shouldRun pfx mkbench = do         e <- liftIO $ do           ee <- mkenv           evaluate (rnf ee)           return ee-        go idx (pfx, mkbench e)+        go idx (pfx, mkbench e) `finally` liftIO (cleanenv e)       | otherwise = return idx     go idx (pfx, Benchmark desc b)       | select desc' = do handle idx desc' b; return $! idx + 1
Criterion/Main.hs view
@@ -31,6 +31,11 @@     , Benchmark     -- * Creating a benchmark suite     , env+    , envWithCleanup+    , perBatchEnv+    , perBatchEnvWithCleanup+    , perRunEnv+    , perRunEnvWithCleanup     , bench     , bgroup     -- ** Running a benchmark@@ -145,9 +150,9 @@   case wat of     List -> mapM_ putStrLn . sort . concatMap benchNames $ bs     Version -> putStrLn versionInfo-    RunIters iters matchType benches -> do+    RunIters cfg iters matchType benches -> do       shouldRun <- selectBenches matchType benches bsgroup-      withConfig defaultConfig $+      withConfig cfg $         runFixedIters iters shouldRun bsgroup     Run cfg matchType benches -> do       shouldRun <- selectBenches matchType benches bsgroup
Criterion/Main/Options.hs view
@@ -22,7 +22,7 @@     ) where  -- Temporary: to support pre-AMP GHC 7.8.4:-import Data.Monoid +import Data.Monoid  import Control.Monad (when) import Criterion.Analysis (validateAccessors)@@ -39,6 +39,7 @@ import Options.Applicative.Help.Pretty ((.$.)) import Options.Applicative.Types import Paths_criterion (version)+import Statistics.Types (mkCL,cl95) import Text.PrettyPrint.ANSI.Leijen (Doc, text) import qualified Data.Map as M import Prelude@@ -57,7 +58,7 @@             -- ^ List all benchmarks.           | Version             -- ^ Print the version.-          | RunIters Int64 MatchType [String]+          | RunIters Config Int64 MatchType [String]             -- ^ Run the given benchmarks, without collecting or             -- analysing performance numbers.           | Run Config MatchType [String]@@ -67,7 +68,7 @@ -- | Default benchmarking configuration. defaultConfig :: Config defaultConfig = Config {-      confInterval = 0.95+      confInterval = cl95     , forceGC      = True     , timeLimit    = 5     , resamples    = 1000@@ -93,7 +94,7 @@     (Version <$ switch (long "version" <> help "Show version info"))   where     runIters = matchNames $-      RunIters <$> option auto+      RunIters <$> config cfg <*> option auto                   (long "iters" <> short 'n' <> metavar "ITERS" <>                    help "Run benchmarks, don't analyse")     matchNames wat = wat@@ -104,7 +105,7 @@  config :: Config -> Parser Config config Config{..} = Config-  <$> option (range 0.001 0.999)+  <$> option (mkCL <$> range 0.001 0.999)       (long "ci" <> short 'I' <> metavar "CI" <> value confInterval <>        help "Confidence interval")   <*> (not <$> switch (long "no-gc" <> short 'G' <>
Criterion/Measurement.hs view
@@ -1,4 +1,5 @@ {-# LANGUAGE Trustworthy #-}+{-# LANGUAGE RecordWildCards #-} {-# LANGUAGE BangPatterns, CPP, ForeignFunctionInterface,     ScopedTypeVariables #-} @@ -23,12 +24,17 @@     , secs     , measure     , runBenchmark+    , runBenchmarkable+    , runBenchmarkable_     , measured     , applyGCStats     , threshold     ) where  import Criterion.Types (Benchmarkable(..), Measured(..))+import Control.Applicative ((<*))+import Control.DeepSeq (NFData(rnf))+import Control.Exception (finally,evaluate) import Data.Int (Int64) import Data.List (unfoldr) import Data.Word (Word64)@@ -51,12 +57,12 @@ measure :: Benchmarkable        -- ^ Operation to benchmark.         -> Int64                -- ^ Number of iterations.         -> IO (Measured, Double)-measure (Benchmarkable run) iters = do+measure bm iters = runBenchmarkable bm iters addResults $ \act -> do   startStats <- getGCStats   startTime <- getTime   startCpuTime <- getCPUTime   startCycles <- getCycles-  run iters+  act   endTime <- getTime   endCpuTime <- getCPUTime   endCycles <- getCycles@@ -68,6 +74,26 @@            , measIters   = iters            }   return (m, endTime)+  where+    addResults :: (Measured, Double) -> (Measured, Double) -> (Measured, Double)+    addResults (!m1, !d1) (!m2, !d2) = (m3, d1 + d2)+      where+        add f = f m1 + f m2++        m3 = Measured+            { measTime               = add measTime+            , measCpuTime            = add measCpuTime+            , measCycles             = add measCycles+            , measIters              = add measIters++            , measAllocated          = add measAllocated+            , measNumGcs             = add measNumGcs+            , measBytesCopied        = add measBytesCopied+            , measMutatorWallSeconds = add measMutatorWallSeconds+            , measMutatorCpuSeconds  = add measMutatorCpuSeconds+            , measGcWallSeconds      = add measGcWallSeconds+            , measGcCpuSeconds       = add measGcCpuSeconds+            } {-# INLINE measure #-}  -- | The amount of time a benchmark must run for in order for us to@@ -83,6 +109,33 @@ threshold = 0.03 {-# INLINE threshold #-} +runBenchmarkable :: Benchmarkable -> Int64 -> (a -> a -> a) -> (IO () -> IO a) -> IO a+runBenchmarkable Benchmarkable{..} i comb f+    | perRun = work >>= go (i - 1)+    | otherwise = work+  where+    go 0 result = return result+    go !n !result = work >>= go (n - 1) . comb result++    count | perRun = 1+          | otherwise = i++    work = do+        env <- allocEnv count+        let clean = cleanEnv count env+            run = runRepeatedly env count++        clean `seq` run `seq` evaluate $ rnf env++        performGC+        f run `finally` clean <* performGC+    {-# INLINE work #-}+{-# INLINE runBenchmarkable #-}++runBenchmarkable_ :: Benchmarkable -> Int64 -> IO ()+runBenchmarkable_ bm i = runBenchmarkable bm i (\() () -> ()) id+{-# INLINE runBenchmarkable_ #-}+ -- | Run a single benchmark, and return measurements collected while -- executing it, along with the amount of time the measurement process -- took.@@ -93,8 +146,8 @@              -- exceeded in order to generate enough data to perform              -- meaningful statistical analyses.              -> IO (V.Vector Measured, Double)-runBenchmark bm@(Benchmarkable run) timeLimit = do-  run 1+runBenchmark bm timeLimit = do+  runBenchmarkable_ bm 1   start <- performGC >> getTime   let loop [] !_ !_ _ = error "unpossible!"       loop (iters:niters) prev count acc = do
Criterion/Monad/Internal.hs view
@@ -18,8 +18,9 @@     ) where  -- Temporary: to support pre-AMP GHC 7.8.4:-import Control.Applicative +import Control.Applicative +import Control.Monad.Catch (MonadThrow, MonadCatch, MonadMask) import Control.Monad.Reader (MonadReader(..), ReaderT) import Control.Monad.Trans (MonadIO) import Criterion.Types (Config)@@ -36,7 +37,7 @@ -- | The monad in which most criterion code executes. newtype Criterion a = Criterion {       runCriterion :: ReaderT Crit IO a-    } deriving (Functor, Applicative, Monad, MonadIO)+    } deriving (Functor, Applicative, Monad, MonadIO, MonadThrow, MonadCatch, MonadMask)  instance MonadReader Config Criterion where     ask     = config `fmap` Criterion ask
Criterion/Report.hs view
@@ -33,28 +33,28 @@ import Control.Monad.Reader (ask) import Criterion.Monad (Criterion) import Criterion.Types-import Data.Aeson.Encode (encodeToTextBuilder)-import Data.Aeson.Types (toJSON)+import Data.Aeson (ToJSON (..), Value(..), object, (.=), Value, encode) import Data.Data (Data, Typeable) import Data.Foldable (forM_)+import Data.Monoid ((<>)) import GHC.Generics (Generic) import Paths_criterion (getDataFileName) import Statistics.Function (minMax) import System.Directory (doesFileExist) import System.FilePath ((</>), (<.>), isPathSeparator)-import Text.Hastache (MuType(..))-import Text.Hastache.Context (mkGenericContext, mkStrContext, mkStrContextM)+import Text.Microstache (Key (..), Template (..), Node (..), compileMustacheText, renderMustache)+import Prelude ()+import Prelude.Compat import qualified Control.Exception as E import qualified Data.Text as T+import qualified Data.Text.Lazy.Encoding as TLE import qualified Data.Text.IO as T import qualified Data.Text.Lazy as TL-import qualified Data.Text.Lazy.Builder as TL import qualified Data.Text.Lazy.IO as TL import qualified Data.Vector.Generic as G import qualified Data.Vector.Unboxed as U import qualified Language.Javascript.Flot as Flot import qualified Language.Javascript.JQuery as JQuery-import qualified Text.Hastache as H  -- | Trim long flat tails from a KDE plot. tidyTails :: KDE -> KDE@@ -83,107 +83,121 @@     tpl <- loadTemplate [td,"."] template     TL.writeFile name =<< formatReport reports tpl --- | Format a series of 'Report' values using the given Hastache--- template.+-- | Format a series of 'Report' values using the given Mustache template. formatReport :: [Report]-             -> T.Text    -- ^ Hastache template.+             -> TL.Text    -- ^ Mustache template.              -> IO TL.Text-formatReport reports template = do-  templates <- getTemplateDir-  let context "report"    = return $ MuList $ map inner reports-      context "json"      = return $ MuVariable (encode reports)-      context "include"   = return $ MuLambdaM $ includeFile [templates]-      context "js-jquery" = fmap MuVariable $ TL.readFile =<< JQuery.file-      context "js-flot"   = fmap MuVariable $ TL.readFile =<< Flot.file Flot.Flot-      context _           = return $ MuNothing-      encode v = TL.toLazyText . encodeToTextBuilder . toJSON $ v-      inner r@Report{..} = mkStrContextM $ \nym ->-                         case nym of-                           "name"     -> return . MuVariable . H.htmlEscape .-                                         TL.pack $ reportName-                           "json"     -> return $ MuVariable (encode r)-                           "number"   -> return $ MuVariable reportNumber-                           "iters"    -> return $ vector "x" iters-                           "times"    -> return $ vector "x" times-                           "cycles"   -> return $ vector "x" cycles-                           "kdetimes" -> return $ vector "x" kdeValues-                           "kdepdf"   -> return $ vector "x" kdePDF-                           "kde"      -> return $ vector2 "time" "pdf" kdeValues kdePDF+formatReport reports templateName = do+    template0 <- case compileMustacheText "tpl" templateName of+        Left err -> fail (show err) -- TODO: throw a template exception?+        Right x -> return x++    jQuery <- T.readFile =<< JQuery.file+    flot <- T.readFile =<< Flot.file Flot.Flot++    -- includes, only top level+    templates <- getTemplateDir+    template <- includeTemplate (includeFile [templates]) template0++    let context = object+            [ "json"      .= reports+            , "report"    .= map inner reports+            , "js-jquery" .= jQuery+            , "js-flot"   .= flot+            ]++    return (renderMustache template context)+  where+    includeTemplate :: (FilePath -> IO T.Text) -> Template -> IO Template+    includeTemplate f Template {..} = fmap+        (Template templateActual)+        (traverse (traverse (includeNode f)) templateCache)++    includeNode :: (FilePath -> IO T.Text) -> Node -> IO Node+    includeNode f (Section (Key ["include"]) [TextBlock fp]) =+        fmap TextBlock (f (T.unpack fp))+    includeNode _ n = return n++    -- Merge Report with it's analysis and outliers+    merge :: ToJSON a => a -> Value -> Value+    merge x y = case toJSON x of+        Object x' -> case y of+            Object y' -> Object (x' <> y')+            _         -> y+        _         -> y++    inner r@Report {..} = merge reportAnalysis $ merge reportOutliers $ object+        [ "name"     .= reportName+        , "json"     .= TLE.decodeUtf8 (encode r)+        , "number"   .= reportNumber+        , "iters"    .= vector "x" iters+        , "times"    .= vector "x" times+        , "cycles"   .= vector "x" cycles+        , "kdetimes" .= vector "x" kdeValues+        , "kdepdf"   .= vector "x" kdePDF+        , "kde"      .= vector2 "time" "pdf" kdeValues kdePDF+        ]+      where+        [KDE{..}]   = reportKDEs+        iters       = measure measIters reportMeasured+        times       = measure measTime reportMeasured+        cycles      = measure measCycles reportMeasured+{-                            ('a':'n':_)-> mkGenericContext reportAnalysis $                                          H.encodeStr nym                            _          -> mkGenericContext reportOutliers $                                          H.encodeStr nym-          where [KDE{..}]   = reportKDEs-                iters       = measure measIters reportMeasured-                times       = measure measTime reportMeasured-                cycles      = measure measCycles reportMeasured-      config = H.defaultConfig {-                 H.muEscapeFunc = H.emptyEscape-               , H.muTemplateFileDir = Just templates-               , H.muTemplateFileExt = Just ".tpl"-               }-  H.hastacheStr config template context+-}  -- | Render the elements of a vector. ----- For example, given this piece of Haskell:------ @'mkStrContext' $ \\name ->--- case name of---   \"foo\" -> 'vector' \"x\" foo@--- -- It will substitute each value in the vector for @x@ in the--- following Hastache template:+-- following Mustache template: -- -- > {{#foo}} -- >  {{x}} -- > {{/foo}}-vector :: (Monad m, G.Vector v a, H.MuVar a) =>-          String                -- ^ Name to use when substituting.+vector :: (G.Vector v a, ToJSON a) =>+          T.Text                -- ^ Name to use when substituting.        -> v a-       -> MuType m-{-# SPECIALIZE vector :: String -> U.Vector Double -> MuType IO #-}-vector name v = MuList . map val . G.toList $ v-    where val i = mkStrContext $ \nym ->-                  if nym == name-                  then MuVariable i-                  else MuNothing+       -> Value+{-# SPECIALIZE vector :: T.Text -> U.Vector Double -> Value #-}+vector name v = toJSON . map val . G.toList $ v where+    val i = object [ name .= i ]  -- | Render the elements of two vectors.-vector2 :: (Monad m, G.Vector v a, G.Vector v b, H.MuVar a, H.MuVar b) =>-           String               -- ^ Name for elements from the first vector.-        -> String               -- ^ Name for elements from the second vector.+vector2 :: (G.Vector v a, G.Vector v b, ToJSON a, ToJSON b) =>+           T.Text               -- ^ Name for elements from the first vector.+        -> T.Text               -- ^ Name for elements from the second vector.         -> v a                  -- ^ First vector.         -> v b                  -- ^ Second vector.-        -> MuType m-{-# SPECIALIZE vector2 :: String -> String -> U.Vector Double -> U.Vector Double-                       -> MuType IO #-}-vector2 name1 name2 v1 v2 = MuList $ zipWith val (G.toList v1) (G.toList v2)-    where val i j = mkStrContext $ \nym ->-                    case undefined of-                      _| nym == name1 -> MuVariable i-                       | nym == name2 -> MuVariable j-                       | otherwise    -> MuNothing+        -> Value+{-# SPECIALIZE vector2 :: T.Text -> T.Text -> U.Vector Double -> U.Vector Double+                       -> Value #-}+vector2 name1 name2 v1 v2 = toJSON $ zipWith val (G.toList v1) (G.toList v2) where+    val i j = object+        [ name1 .= i+        , name2 .= j+        ]  -- | Attempt to include the contents of a file based on a search path. -- Returns 'B.empty' if the search fails or the file could not be read. ----- Intended for use with Hastache's 'MuLambdaM', for example:+-- Intended for preprocessing Mustache files, e.g. replacing sections ----- @context \"include\" = 'MuLambdaM' $ 'includeFile' ['templateDir']@+-- @+-- {{#include}}file.txt{{/include}+-- @ ----- Hastache template expansion is /not/ performed within the included--- file.  No attempt is made to ensure that the included file path is--- safe, i.e. that it does not refer to an unexpected file such as--- \"@/etc/passwd@\".+-- with file contents. includeFile :: (MonadIO m) =>                [FilePath]       -- ^ Directories to search.-            -> T.Text          -- ^ Name of the file to search for.+            -> FilePath         -- ^ Name of the file to search for.             -> m T.Text-{-# SPECIALIZE includeFile :: [FilePath] -> T.Text -> IO T.Text #-}+{-# SPECIALIZE includeFile :: [FilePath] -> FilePath -> IO T.Text #-} includeFile searchPath name = liftIO $ foldr go (return T.empty) searchPath     where go dir next = do-            let path = dir </> H.decodeStr name+            let path = dir </> name             T.readFile path `E.catch` \(_::IOException) -> next  -- | A problem arose with a template.@@ -193,7 +207,7 @@  instance Exception TemplateException --- | Load a Hastache template file.+-- | Load a Mustache template file. -- -- If the name is an absolute or relative path, the search path is -- /not/ used, and the name is treated as a literal path.@@ -202,15 +216,15 @@ -- not be found, or an 'IOException' if no template could be loaded. loadTemplate :: [FilePath]      -- ^ Search path.              -> FilePath        -- ^ Name of template file.-             -> IO T.Text+             -> IO TL.Text loadTemplate paths name-    | any isPathSeparator name = T.readFile name+    | any isPathSeparator name = TL.readFile name     | otherwise                = go Nothing paths   where go me (p:ps) = do           let cur = p </> name <.> "tpl"           x <- doesFileExist cur           if x-            then T.readFile cur `E.catch` \e -> go (me `mplus` Just e) ps+            then TL.readFile cur `E.catch` \e -> go (me `mplus` Just e) ps             else go me ps         go (Just e) _ = throwIO (e::IOException)         go _        _ = throwIO . TemplateNotFound $ name
Criterion/Types.hs view
@@ -1,4 +1,5 @@ {-# LANGUAGE Trustworthy #-}+{-# LANGUAGE RankNTypes #-} {-# LANGUAGE DeriveDataTypeable, DeriveGeneric, GADTs, RecordWildCards #-} {-# OPTIONS_GHC -funbox-strict-fields #-} @@ -44,6 +45,11 @@     , rescale     -- * Benchmark construction     , env+    , envWithCleanup+    , perBatchEnv+    , perBatchEnvWithCleanup+    , perRunEnv+    , perRunEnvWithCleanup     , bench     , bgroup     , addPrefix@@ -78,7 +84,8 @@ import GHC.Generics (Generic) import qualified Data.Vector as V import qualified Data.Vector.Unboxed as U-import qualified Statistics.Resampling.Bootstrap as B+import qualified Statistics.Types as St+import           Statistics.Resampling.Bootstrap () import Prelude  -- | Control the amount of information displayed.@@ -90,7 +97,7 @@  -- | Top-level benchmarking configuration. data Config = Config {-      confInterval :: Double+      confInterval :: St.CL Double       -- ^ Confidence interval for bootstrap estimation (greater than       -- 0, less than 1).     , forceGC      :: Bool@@ -123,11 +130,26 @@       -- ^ Template file to use if writing a report.     } deriving (Eq, Read, Show, Typeable, Data, Generic) + -- | A pure function or impure action that can be benchmarked. The -- 'Int64' parameter indicates the number of times to run the given -- function or action.-newtype Benchmarkable = Benchmarkable { runRepeatedly :: Int64 -> IO () }+data Benchmarkable = forall a . NFData a =>+    Benchmarkable+      { allocEnv :: Int64 -> IO a+      , cleanEnv :: Int64 -> a -> IO ()+      , runRepeatedly :: a -> Int64 -> IO ()+      , perRun :: Bool+      } +noop :: Monad m => a -> m ()+noop = const $ return ()+{-# INLINE noop #-}++toBenchmarkable :: (Int64 -> IO ()) -> Benchmarkable+toBenchmarkable f = Benchmarkable noop (const noop) (const f) False+{-# INLINE toBenchmarkable #-}+ -- | A collection of measurements made while benchmarking. -- -- Measurements related to garbage collection are tagged with __GC__.@@ -298,35 +320,35 @@ whnf = pureFunc id {-# INLINE whnf #-} --- | Apply an argument to a function, and evaluate the result to head+-- | Apply an argument to a function, and evaluate the result to -- normal form (NF). nf :: NFData b => (a -> b) -> a -> Benchmarkable nf = pureFunc rnf {-# INLINE nf #-}  pureFunc :: (b -> c) -> (a -> b) -> a -> Benchmarkable-pureFunc reduce f0 x0 = Benchmarkable $ go f0 x0+pureFunc reduce f0 x0 = toBenchmarkable (go f0 x0)   where go f x n           | n <= 0    = return ()           | otherwise = evaluate (reduce (f x)) >> go f x (n-1) {-# INLINE pureFunc #-} --- | Perform an action, then evaluate its result to head normal form.+-- | Perform an action, then evaluate its result to normal form. -- This is particularly useful for forcing a lazy 'IO' action to be -- completely performed. nfIO :: NFData a => IO a -> Benchmarkable-nfIO = impure rnf+nfIO = toBenchmarkable . impure rnf {-# INLINE nfIO #-}  -- | Perform an action, then evaluate its result to weak head normal -- form (WHNF).  This is useful for forcing an 'IO' action whose result -- is an expression to be evaluated down to a more useful value. whnfIO :: IO a -> Benchmarkable-whnfIO = impure id+whnfIO = toBenchmarkable . impure id {-# INLINE whnfIO #-} -impure :: (a -> b) -> IO a -> Benchmarkable-impure strategy a = Benchmarkable go+impure :: (a -> b) -> IO a -> Int64 -> IO ()+impure strategy a = go   where go n           | n <= 0    = return ()           | otherwise = a >>= (evaluate . strategy) >> go (n-1)@@ -342,7 +364,8 @@ -- -- * A (possibly nested) group of 'Benchmark's, created with 'bgroup'. data Benchmark where-    Environment  :: NFData env => IO env -> (env -> Benchmark) -> Benchmark+    Environment  :: NFData env+                 => IO env -> (env -> IO a) -> (env -> Benchmark) -> Benchmark     Benchmark    :: String -> Benchmarkable -> Benchmark     BenchGroup   :: String -> [Benchmark] -> Benchmark @@ -429,8 +452,115 @@     -- ^ Take the newly created environment and make it available to     -- the given benchmarks.     -> Benchmark-env = Environment+env alloc = Environment alloc noop +-- | Same as `env`, but but allows for an additional callback+-- to clean up the environment. Resource clean up is exception safe, that is,+-- it runs even if the 'Benchmark' throws an exception.+envWithCleanup+    :: NFData env+    => IO env+    -- ^ Create the environment.  The environment will be evaluated to+    -- normal form before being passed to the benchmark.+    -> (env -> IO a)+    -- ^ Clean up the created environment.+    -> (env -> Benchmark)+    -- ^ Take the newly created environment and make it available to+    -- the given benchmarks.+    -> Benchmark+envWithCleanup = Environment++-- | Create a Benchmarkable where a fresh environment is allocated for every+-- batch of runs of the benchmarkable.+--+-- The environment is evaluated to normal form before the benchmark is run.+--+-- When using 'whnf', 'whnfIO', etc. Criterion creates a 'Benchmarkable'+-- whichs runs a batch of @N@ repeat runs of that expressions. Criterion may+-- run any number of these batches to get accurate measurements. Environments+-- created by 'env' and 'envWithCleanup', are shared across all these batches+-- of runs.+--+-- This is fine for simple benchmarks on static input, but when benchmarking+-- IO operations where these operations can modify (and especially grow) the+-- environment this means that later batches might have their accuracy effected+-- due to longer, for example, longer garbage collection pauses.+--+-- An example: Suppose we want to benchmark writing to a Chan, if we allocate+-- the Chan using environment and our benchmark consists of @writeChan env ()@,+-- the contents and thus size of the Chan will grow with every repeat. If+-- Criterion runs a 1,000 batches of 1,000 repeats, the result is that the+-- channel will have 999,000 items in it by the time the last batch is run.+-- Since GHC GC has to copy the live set for every major GC this means our last+-- set of writes will suffer a lot of noise of the previous repeats.+--+-- By allocating a fresh environment for every batch of runs this function+-- should eliminate this effect.+perBatchEnv+    :: (NFData env, NFData b)+    => (Int64 -> IO env)+    -- ^ Create an environment for a batch of N runs. The environment will be+    -- evaluated to normal form before running.+    -> (env -> IO b)+    -- ^ Function returning the IO action that should be benchmarked with the+    -- newly generated environment.+    -> Benchmarkable+perBatchEnv alloc = perBatchEnvWithCleanup alloc (const noop)++-- | Same as `perBatchEnv`, but but allows for an additional callback+-- to clean up the environment. Resource clean up is exception safe, that is,+-- it runs even if the 'Benchmark' throws an exception.+perBatchEnvWithCleanup+    :: (NFData env, NFData b)+    => (Int64 -> IO env)+    -- ^ Create an environment for a batch of N runs. The environment will be+    -- evaluated to normal form before running.+    -> (Int64 -> env -> IO ())+    -- ^ Clean up the created environment.+    -> (env -> IO b)+    -- ^ Function returning the IO action that should be benchmarked with the+    -- newly generated environment.+    -> Benchmarkable+perBatchEnvWithCleanup alloc clean work+    = Benchmarkable alloc clean (impure rnf . work) False++-- | Create a Benchmarkable where a fresh environment is allocated for every+-- run of the operation to benchmark. This is useful for benchmarking mutable+-- operations that need a fresh environment, such as sorting a mutable Vector.+--+-- As with 'env' and 'perBatchEnv' the environment is evaluated to normal form+-- before the benchmark is run.+--+-- This introduces extra noise and result in reduce accuracy compared to other+-- Criterion benchmarks. But allows easier benchmarking for mutable operations+-- than was previously possible.+perRunEnv+    :: (NFData env, NFData b)+    => IO env+    -- ^ Action that creates the environment for a single run.+    -> (env -> IO b)+    -- ^ Function returning the IO action that should be benchmarked with the+    -- newly genereted environment.+    -> Benchmarkable+perRunEnv alloc = perRunEnvWithCleanup alloc noop++-- | Same as `perRunEnv`, but but allows for an additional callback+-- to clean up the environment. Resource clean up is exception safe, that is,+-- it runs even if the 'Benchmark' throws an exception.+perRunEnvWithCleanup+    :: (NFData env, NFData b)+    => IO env+    -- ^ Action that creates the environment for a single run.+    -> (env -> IO ())+    -- ^ Clean up the created environment.+    -> (env -> IO b)+    -- ^ Function returning the IO action that should be benchmarked with the+    -- newly genereted environment.+    -> Benchmarkable+perRunEnvWithCleanup alloc clean work = bm { perRun = True }+  where+    bm = perBatchEnvWithCleanup (const alloc) (const clean) work+ -- | Create a single benchmark. bench :: String                 -- ^ A name to identify the benchmark.       -> Benchmarkable          -- ^ An activity to be benchmarked.@@ -455,12 +585,12 @@ -- | Retrieve the names of all benchmarks.  Grouped benchmarks are -- prefixed with the name of the group they're in. benchNames :: Benchmark -> [String]-benchNames (Environment _ b) = benchNames (b undefined)+benchNames (Environment _ _ b) = benchNames (b undefined) benchNames (Benchmark d _)   = [d] benchNames (BenchGroup d bs) = map (addPrefix d) . concatMap benchNames $ bs  instance Show Benchmark where-    show (Environment _ b) = "Environment _ " ++ show (b undefined)+    show (Environment _ _ b) = "Environment _ _" ++ show (b undefined)     show (Benchmark d _)   = "Benchmark " ++ show d     show (BenchGroup d _)  = "BenchGroup " ++ show d @@ -551,11 +681,11 @@ data Regression = Regression {     regResponder  :: String     -- ^ Name of the responding variable.-  , regCoeffs     :: Map String B.Estimate+  , regCoeffs     :: Map String (St.Estimate St.ConfInt Double)     -- ^ Map from name to value of predictor coefficients.-  , regRSquare    :: B.Estimate+  , regRSquare    :: St.Estimate St.ConfInt Double     -- ^ R&#0178; goodness-of-fit estimate.-  } deriving (Eq, Read, Show, Typeable, Data, Generic)+  } deriving (Eq, Read, Show, Typeable, Generic)  instance FromJSON Regression instance ToJSON Regression@@ -576,14 +706,14 @@     , anOverhead   :: Double       -- ^ Estimated measurement overhead, in seconds.  Estimation is       -- performed via linear regression.-    , anMean       :: B.Estimate+    , anMean       :: St.Estimate St.ConfInt Double       -- ^ Estimated mean.-    , anStdDev     :: B.Estimate+    , anStdDev     :: St.Estimate St.ConfInt Double       -- ^ Estimated standard deviation.     , anOutlierVar :: OutlierVariance       -- ^ Description of the effects of outliers on the estimated       -- variance.-    } deriving (Eq, Read, Show, Typeable, Data, Generic)+    } deriving (Eq, Read, Show, Typeable, Generic)  instance FromJSON SampleAnalysis instance ToJSON SampleAnalysis@@ -633,7 +763,7 @@       -- ^ Analysis of outliers.     , reportKDEs     :: [KDE]       -- ^ Data for a KDE of times.-    } deriving (Eq, Read, Show, Typeable, Data, Generic)+    } deriving (Eq, Read, Show, Typeable, Generic)  instance FromJSON Report instance ToJSON Report@@ -654,7 +784,7 @@  data DataRecord = Measurement Int String (V.Vector Measured)                 | Analysed Report-                deriving (Eq, Read, Show, Typeable, Data, Generic)+                deriving (Eq, Read, Show, Typeable, Generic)  instance Binary DataRecord where   put (Measurement i n v) = putWord8 0 >> put i >> put n >> put v
+ app/Options.hs view
@@ -0,0 +1,50 @@+{-# LANGUAGE DeriveDataTypeable, DeriveGeneric, RecordWildCards #-}+module Options+    (+      CommandLine(..)+    , commandLine+    , parseCommandLine+    , versionInfo+    ) where++import Data.Monoid ((<>), mconcat)+import Data.Version (showVersion)+import Data.Data (Data, Typeable)+import GHC.Generics (Generic)+import Paths_criterion (version)+import Options.Applicative++data CommandLine+    = Report { jsonFile :: FilePath, outputFile :: FilePath, templateFile :: FilePath }+    | Version+    deriving (Eq, Read, Show, Typeable, Data, Generic)++reportOptions :: Parser CommandLine+reportOptions = Report <$> measurements <*> outputFile <*> templateFile+  where+    measurements = strArgument $ mconcat+        [metavar "INPUT-JSON", help "Json file to read Criterion output from."]++    outputFile = strArgument $ mconcat+        [metavar "OUTPUT-FILE", help "File to output formatted report too."]++    templateFile = strOption $ mconcat+        [ long "template", short 't', metavar "FILE", value "default",+          help "Template to use for report." ]++parseCommand :: Parser CommandLine+parseCommand =+  (Version <$ switch (long "version" <> help "Show version info")) <|>+  (subparser $+    command "report" (info reportOptions (progDesc "Generate report.")))++commandLine :: ParserInfo CommandLine+commandLine = info (helper <*> parseCommand) $+  header versionInfo <>+  fullDesc++parseCommandLine :: IO CommandLine+parseCommandLine = execParser commandLine++versionInfo :: String+versionInfo = "criterion " ++ showVersion version
+ app/Report.hs view
@@ -0,0 +1,32 @@+{-# LANGUAGE RecordWildCards #-}+module Main (main) where++import System.Exit (exitSuccess, exitFailure)+import System.IO (hPutStrLn, stderr)++import Criterion.IO (readJSONReports)+import Criterion.Main (defaultConfig)+import Criterion.Monad (withConfig)+import Criterion.Report (report)+import Criterion.Types (Config(reportFile, template))++import Options++main :: IO ()+main = do+    cmd <- parseCommandLine+    case cmd of+        Version -> putStrLn versionInfo >> exitSuccess+        Report{..} -> do+            let config = defaultConfig+                  { reportFile = Just outputFile+                  , template = templateFile+                  }++            res <- readJSONReports jsonFile+            case res of+                Left err -> do+                    hPutStrLn stderr $ "Error reading file: " ++ jsonFile+                    hPutStrLn stderr $ "  " ++ show err+                    exitFailure+                Right (_,_,rpts) -> withConfig config $ report rpts
changelog.md view
@@ -1,15 +1,47 @@+1.2.0.0++* Use `statistics-0.14`.++* Replace the `hastache` dependency with `microstache`.++* Add support for per-run allocation/cleanup of the environment with+  `perRunEnv` and `perRunEnvWithCleanup`,++* Add support for per-batch allocation/cleanup with+  `perBatchEnv` and `perBatchEnvWithCleanup`.++* Add `envWithCleanup`, a variant of `env` with cleanup support.++* Add the `criterion-report` executable, which creates reports from previously+  created JSON files.+ 1.1.4.0  * Unicode output is now correctly printed on Windows. -1.1.3.1+1.1.4.0  * Add Safe Haskell annotations. -1.1.3.0- * Add `--json` option for writing reports in JSON rather than binary   format.  Also: various bugfixes related to this.++* Use the `js-jquery` and `js-flot` libraries to substitute in JavaScript code+  into the default HTML report template.++* Use the `code-page` library to ensure that `criterion` prints out Unicode+  characters (like ², which `criterion` uses in reports) in a UTF-8-compatible+  code page on Windows.++* Give an explicit implementation for `get` in the `Binary Regression`+  instance. This should fix sporadic `criterion` failures with older versions+  of `binary`.++* Use `tasty` instead of `test-framework` in the test suites.++* Restore support for 32-bit Intel CPUs.++* Restore build compatibilty with GHC 7.4.  1.1.1.0 
criterion.cabal view
@@ -1,5 +1,5 @@ name:           criterion-version:        1.1.4.0+version:        1.2.0.0 synopsis:       Robust, reliable performance measurement and analysis license:        BSD3 license-file:   LICENSE@@ -17,6 +17,13 @@   examples/*.cabal   examples/*.hs   examples/*.html+tested-with:+  GHC==7.4.2,+  GHC==7.6.3,+  GHC==7.8.4,+  GHC==7.10.3,+  GHC==8.0.2,+  GHC==8.2.1  data-files:   templates/*.css@@ -76,6 +83,7 @@     aeson >= 0.8,     ansi-wl-pprint >= 0.6.7.2,     base >= 4.5 && < 5,+    base-compat >= 0.9,     binary >= 0.5.1.0,     bytestring >= 0.9 && < 1.0,     cassava >= 0.3.0.0,@@ -83,16 +91,17 @@     containers,     deepseq >= 1.1.0.0,     directory,+    exceptions >= 0.8.2 && < 0.9,     filepath,     Glob >= 0.7.2,-    hastache >= 0.6.0,+    microstache >= 1 && < 1.1,     js-flot,     js-jquery,     mtl >= 2,     mwc-random >= 0.8.0.3,     optparse-applicative >= 0.13,     parsec >= 3.1.0,-    statistics >= 0.13.2.1,+    statistics >= 0.14 && < 0.15,     text >= 0.11,     time,     transformers,@@ -111,27 +120,20 @@   else     ghc-options: -O2 --- [2016.05.30] RRN: I'm not sure where this was going.--- It looks like it was meant for post-facto analysis of--- reports?  Will have to ask BOS.------------------------------------------------------------ executable criterion---   hs-source-dirs: app---   main-is:        App.hs---   other-modules:---     Options----   ghc-options:---     -Wall -threaded -rtsopts+Executable criterion-report+  GHC-Options:          -Wall -rtsopts+  Main-Is:              Report.hs+  Other-Modules:        Options+  Hs-Source-Dirs:       app ---   build-depends:---     base,---     criterion,---     optparse-applicative---   if impl(ghc < 7.6)---     build-depends:---       ghc-prim+  Build-Depends:+    base,+    criterion,+    optparse-applicative >= 0.13 +  if impl(ghc < 7.6)+    build-depends:+      ghc-prim  test-suite sanity   type:           exitcode-stdio-1.0@@ -148,6 +150,7 @@     base,     bytestring,     criterion,+    deepseq,     tasty,     tasty-hunit @@ -171,6 +174,24 @@     tasty-quickcheck,     vector,     aeson >= 0.8++test-suite cleanup+  type:           exitcode-stdio-1.0+  hs-source-dirs: tests+  main-is:        Cleanup.hs++  ghc-options:+    -Wall -threaded -O0 -rtsopts++  build-depends:+    HUnit,+    base,+    bytestring,+    criterion,+    deepseq,+    directory,+    tasty,+    tasty-hunit  source-repository head   type:     git
templates/default.tpl view
@@ -4,10 +4,10 @@     <meta http-equiv="Content-Type" content="text/html; charset=utf-8">     <title>criterion report</title>     <script language="javascript" type="text/javascript">-      {{js-jquery}}+      {{{js-jquery}}}     </script>     <script language="javascript" type="text/javascript">-      {{js-flot}}+      {{{js-flot}}}     </script>     <script language="javascript" type="text/javascript">       {{#include}}js/jquery.criterion.js{{/include}}@@ -73,15 +73,15 @@    </tr>    <tr>     <td>Mean execution time</td>-    <td><span class="confinterval citime">{{anMean.estLowerBound}}</span></td>+    <td><span class="confinterval citime">{{anMean.estError.confIntLDX}}</span></td>     <td><span class="time">{{anMean.estPoint}}</span></td>-    <td><span class="confinterval citime">{{anMean.estUpperBound}}</span></td>+    <td><span class="confinterval citime">{{anMean.estError.confIntUDX}}</span></td>    </tr>    <tr>     <td>Standard deviation</td>-    <td><span class="confinterval citime">{{anStdDev.estLowerBound}}</span></td>+    <td><span class="confinterval citime">{{anStdDev.estError.confIntLDX}}</span></td>     <td><span class="time">{{anStdDev.estPoint}}</span></td>-    <td><span class="confinterval citime">{{anStdDev.estUpperBound}}</span></td>+    <td><span class="confinterval citime">{{anStdDev.estError.confIntUDX}}</span></td>    </tr>   </tbody>  </table>@@ -177,19 +177,19 @@         return $.renderTime(olsTime.estPoint);       });     $(".olstimelb" + number).text(function() {-        return $.renderTime(olsTime.estLowerBound);+        return $.renderTime(olsTime.estError.confIntLDX);       });     $(".olstimeub" + number).text(function() {-        return $.renderTime(olsTime.estUpperBound);+        return $.renderTime(olsTime.estError.confIntUDX);       });     $(".olsr2pt" + number).text(function() {         return rgrs.regRSquare.estPoint.toFixed(3);       });     $(".olsr2lb" + number).text(function() {-        return rgrs.regRSquare.estLowerBound.toFixed(3);+        return rgrs.regRSquare.estError.confIntLDX.toFixed(3);       });     $(".olsr2ub" + number).text(function() {-        return rgrs.regRSquare.estUpperBound.toFixed(3);+        return rgrs.regRSquare.estError.confIntUDX.toFixed(3);       });     mean *= scale;     kdetimes = $.scaleBy(scale, kdetimes);@@ -271,7 +271,7 @@       $.addTooltip("#cycles" + number, function(x,y) { return x + ' cycles'; });     }   };-  var reports = {{json}};+  var reports = {{{json}}};   reports.map(mangulate);    var benches = [{{#report}}"{{name}}",{{/report}}];
+ tests/Cleanup.hs view
@@ -0,0 +1,111 @@+{-# LANGUAGE BangPatterns #-}+{-# LANGUAGE DeriveDataTypeable #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE ScopedTypeVariables #-}++import Criterion.Main (Benchmark, bench, nfIO)+import Criterion.Types (Config(..), Verbosity(Quiet))+import Control.Applicative (pure)+import Control.DeepSeq (NFData(..))+import Control.Exception (Exception, try, throwIO)+import Control.Monad (when)+import Data.Typeable (Typeable)+import System.Directory (doesFileExist, removeFile)+import System.Environment (withArgs)+import System.IO ( Handle, IOMode(ReadWriteMode), SeekMode(AbsoluteSeek)+                 , hClose, hFileSize, hSeek, openFile)+import Test.Tasty (TestTree, defaultMain, testGroup)+import Test.Tasty.HUnit (testCase)+import Test.HUnit (assertFailure)+import qualified Criterion.Main as C+import Data.ByteString (ByteString)+import qualified Data.ByteString as BS++instance NFData Handle where+    rnf !_ = ()++data CheckResult = ShouldThrow | WrongData deriving (Show, Typeable)++instance Exception CheckResult++type BenchmarkWithFile =+  String -> IO Handle -> (Handle -> IO ()) -> (Handle -> IO ()) -> Benchmark++perRun :: BenchmarkWithFile+perRun name alloc clean work =+  bench name $ C.perRunEnvWithCleanup alloc clean work++perBatch :: BenchmarkWithFile+perBatch name alloc clean work =+  bench name $ C.perBatchEnvWithCleanup (const alloc) (const clean) work++envWithCleanup :: BenchmarkWithFile+envWithCleanup name alloc clean work =+  C.envWithCleanup alloc clean $ bench name . nfIO . work++testCleanup :: Bool -> String -> BenchmarkWithFile -> TestTree+testCleanup shouldFail name withEnvClean = testCase name $ do+    existsBefore <- doesFileExist testFile+    when existsBefore $ failTest "Input file already exists"++    result <- runTest . withEnvClean name alloc clean $ \hnd -> do+        result <- hFileSize hnd >>= BS.hGet hnd . fromIntegral+        resetHandle hnd+        when (result /= testData) $ throwIO WrongData+        when shouldFail $ throwIO ShouldThrow++    case result of+        Left WrongData -> failTest "Incorrect result read from file"+        Left ShouldThrow -> return ()+        Right _ | shouldFail -> failTest "Failed to throw exception"+                | otherwise -> return ()++    existsAfter <- doesFileExist testFile+    when existsAfter $ do+        removeFile testFile+        failTest "Failed to delete file"+  where+    testFile :: String+    testFile = "tmp"++    testData :: ByteString+    testData = "blah"++    runTest :: Benchmark -> IO (Either CheckResult ())+    runTest = withArgs (["-n","1"]) . try . C.defaultMainWith config . pure+      where+        config = C.defaultConfig { verbosity = Quiet , timeLimit = 1 }++    failTest :: String -> IO ()+    failTest s = assertFailure $ s ++ " in test: " ++ name ++ "!"++    resetHandle :: Handle -> IO ()+    resetHandle hnd = hSeek hnd AbsoluteSeek 0++    alloc :: IO Handle+    alloc = do+        hnd <- openFile testFile ReadWriteMode+        BS.hPut hnd testData+        resetHandle hnd+        return hnd++    clean :: Handle -> IO ()+    clean hnd = do+        hClose hnd+        removeFile testFile++testSuccess :: String -> BenchmarkWithFile -> TestTree+testSuccess = testCleanup False++testFailure :: String -> BenchmarkWithFile -> TestTree+testFailure = testCleanup True++main :: IO ()+main = defaultMain $ testGroup "cleanup"+    [ testSuccess "perRun Success" perRun+    , testFailure "perRun Failure" perRun+    , testSuccess "perBatch Success" perBatch+    , testFailure "perBatch Failure" perBatch+    , testSuccess "envWithCleanup Success" envWithCleanup+    , testFailure "envWithCleanup Failure" envWithCleanup+    ]
tests/Sanity.hs view
@@ -1,3 +1,4 @@+{-# LANGUAGE CPP #-} {-# LANGUAGE ScopedTypeVariables #-}  import Criterion.Main (bench, bgroup, env, whnf)@@ -10,6 +11,10 @@ import qualified Control.Exception as E import qualified Data.ByteString as B +#if !MIN_VERSION_bytestring(0,10,0)+import Control.DeepSeq (NFData (..))+#endif+ fib :: Int -> Int fib = sum . go   where go 0 = [0]@@ -53,3 +58,8 @@ getArgEnv =   fmap words (getEnv "ARGS") `E.catch`   \(_ :: E.SomeException) -> return []++#if !MIN_VERSION_bytestring(0,10,0)+instance NFData B.ByteString where+    rnf bs = bs `seq` ()+#endif
tests/Tests.hs view
@@ -1,12 +1,10 @@-{-# LANGUAGE NegativeLiterals #-}- module Main (main) where  import Criterion.Types import qualified Data.Aeson as Aeson import qualified Data.Vector as V import Properties-import Statistics.Resampling.Bootstrap (Estimate(..))+import Statistics.Types (estimateFromErr, mkCL) import Test.Tasty (defaultMain, testGroup) import Test.Tasty.HUnit (testCase) import Test.HUnit@@ -16,7 +14,7 @@  where   m1 = Measured 4.613000783137977e-05 3.500000000000378e-05 31432 1 0 0 0 0.0 0.0 0.0 0.0   v1 = V.fromList [m1]-  est1 = Estimate 0.0 0.0 0.0 0.0+  est1 = estimateFromErr 0.0 (0.0, 0.0) (mkCL 0.0)   s1 = SampleAnalysis [] 0.0 est1 est1 (OutlierVariance Unaffected "" 0.0)  m2 :: Measured@@ -25,9 +23,9 @@               , measCycles = 6208               , measIters = 1 -              , measAllocated = -9223372036854775808-              , measNumGcs = -9223372036854775808-              , measBytesCopied = -9223372036854775808+              , measAllocated = minBound+              , measNumGcs = minBound+              , measBytesCopied = minBound                , measMutatorWallSeconds = -1/0               , measMutatorCpuSeconds = -1/0