packages feed

criterion-cmp-0.1.0.0: src/CriterionCompare.hs

{-# LANGUAGE FlexibleContexts  #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RankNTypes        #-}
{-# LANGUAGE RecordWildCards   #-}

import Control.Applicative (many)
import Data.Bifunctor      (first)
import Data.Foldable       (foldl')
import Data.Map            (Map)
import Data.Traversable    (for)
import System.FilePath     (dropExtension, takeFileName)

import qualified Data.Map.Strict        as Map
import qualified Options.Applicative    as O
import qualified Text.PrettyPrint.Boxes as B

import CsvParse
import Table
import Types

-------------------------------------------------------------------------------
-- Options
-------------------------------------------------------------------------------

data Options = Options
    { optRunNames :: [RunName]
    , optOutput   :: FilePath
    , optRunPaths :: [FilePath]
    }

options :: O.Parser Options
options = Options
    <$> many (O.strOption $ O.short 'l' <> O.long "label" <> O.help "label")
    <*> O.strOption (O.short 'o' <> O.long "output" <> O.metavar "FILE" <> O.help "output file name" <> O.value "-")
    <*> many (O.strArgument $ O.metavar "FILE" <> O.help "CSV file name")

-------------------------------------------------------------------------------
-- Utilities
-------------------------------------------------------------------------------

flipFiniteMap :: (Ord a, Ord b) => Map a (Map b v) -> Map b (Map a v)
flipFiniteMap abv = Map.unionsWith Map.union
    [ Map.singleton b $ Map.singleton a v
    | (a, bv) <- Map.toList abv
    , (b, v)  <- Map.toList bv
    ]

-------------------------------------------------------------------------------
-- Geometric mean
-------------------------------------------------------------------------------

gmean :: Traversable f => f Stats -> Double
gmean = post . foldl' f (A 1.0 0 0) . fmap statsMean where
    f (A acc es n) d = A c (es + e) (n + 1) where
        (c, e) = split (acc * d)

    split :: Double -> (Double, Int)
    split d  = (d / 2 ^^ e, e) where e = exponent d

    post :: A -> Double
    post (A acc es n) = acc ** (1 / fromIntegral n)
                      * 2 ** (fromIntegral es / fromIntegral n)

-- | @A x e n@ is @n@ elements which product is @x * 2 ^^ e@
data A = A !Double !Int !Int

-------------------------------------------------------------------------------
-- Main
-------------------------------------------------------------------------------

main :: IO ()
main = do
    Options{..} <- O.execParser $ O.info (O.helper <*> options) mempty

    let runs :: [(RunName, FilePath)]
        runs = zipWith f (map Just optRunNames ++ repeat Nothing) optRunPaths
          where
            f (Just n) fp = (n, fp)
            f Nothing  fp = (RunName $ dropExtension $ takeFileName fp, fp)

    let names' :: [RunName]
        names' = map fst runs

    case names' of
        [] -> return () -- nothing to do
        fname : names -> do
            results0 <- fmap Map.fromList $ for runs $ \(name, fp) ->
                (,) name . Map.fromList . map (first RowBenchName) <$> readResults fp

            let results1 :: Map RunName (Map RowName Stats)
                results1 = fmap addGMean results0 where
                    -- zzz will make the line appear last.
                    addGMean m = Map.insert RowMean (Stats (gmean m) 0 0 0 0 0) m

            let results :: Map RowName (Map RunName Stats)
                results = flipFiniteMap results1

            let header :: Row V1 B.Box
                header = makeHeader fname names

            let table :: [Row V2 B.Box]
                table = makeTable fname names results

            let table1 :: Row V2 [B.Box]
                table1 = sequenceA table

            let table2 :: Row V2 B.Box
                table2 = case table1 of
                    Row f n xs -> Row
                        (B.vcat B.left f)
                        (B.vcat B.right n)
                        (fmap (fmap (B.vcat B.right)) xs)

            let table3 :: Row V1 B.Box
                table3 = hoistRow (\(V2 x y) -> V1 (x B.<+> y)) table2

            let table4 = pure (B.//) <*> header <*> table3

            B.printBox $ B.hsep 2 B.left table4