{-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE ScopedTypeVariables #-} module Main where import Control.Exception (catch, ErrorCall(..)) import Data.Char (isSpace) import Data.List (reverse, sortOn, isPrefixOf) -- import Data.List (stripPrefix) import Data.List.Split (splitOn) import Data.Maybe (catMaybes) -- import Data.Maybe (fromJust) import System.Exit (ExitCode(..)) import System.Process.Typed (readProcess) import BenchShow import WithCli (withCli) import Data.List import qualified Data.Text.Lazy as T import qualified Data.Text.Lazy.Encoding as T -- pairs of benchmark group titles and corresponding list of benchmark -- prefixes i.e. without the package name at the end. charts :: [(String, [String])] charts = [ ( "Elimination Operations" , [ "elimination/drain" , "elimination/last" , "elimination/foldl'" ] ) , ( "Transformation Operations" , [ "transformation/map" , "transformation/mapM" , "transformation/scan" ] ) , ( "Transformation Operations x 4" , [ "transformationX4/map x 4" , "transformationX4/mapM x 4" , "transformationX4/scan x 4" ] ) , ( "Filtering Operations" , [ "filtering/filter-all-out" , "filtering/filter-all-in" , "filtering/drop-all" , "filtering/takeWhile-true" , "filtering/take-all" , "filtering/dropWhile-true" , "filtering/filter-even" , "filtering/drop-one" , "filtering/dropWhile-false" ] ) , ( "Filtering Operations x 4" , [ "filteringX4/filter-all-out x 4" , "filteringX4/takeWhile-true x 4" , "filteringX4/filter-all-in x 4" , "filteringX4/take-all x 4" , "filteringX4/filter-even x 4" , "filteringX4/drop-all x 4" , "filteringX4/dropWhile-true x 4" , "filteringX4/dropWhile-false x 4" , "filteringX4/drop-one x 4" ] ) , ( "Mixed Operations x 4" , [ "mixedX4/filter-map x 4" , "mixedX4/take-map x 4" , "mixedX4/drop-map x 4" , "mixedX4/filter-drop x 4" , "mixedX4/filter-take x 4" , "mixedX4/take-drop x 4" , "mixedX4/scan-map x 4" , "mixedX4/filter-scan x 4" , "mixedX4/take-scan x 4" , "mixedX4/drop-scan x 4" ] ) , ( "Iterated Operations" , [ "iterated/mapM" , "iterated/scan[x0.01]" , "iterated/filterEven" , "iterated/takeAll" , "iterated/dropOne" , "iterated/dropWhileFalse[x0.01]" , "iterated/dropWhileTrue" ] ) , ( "Append Operations" , [ "appendR" -- , "appendL" ] ) , ( "Zip Operation" , [ "zip" ] ) , ( "Concat Operation" , [ "concatMap" , "concatMapFoldable" ] ) , ( "Conversion Operations" , [ "elimination/toList" ] ) ] ------------------------------------------------------------------------------- -- returns [(packagename, version)] getPkgVersions :: [String] -> IO [(String, String)] getPkgVersions packages = do (ecode, out, _) <- readProcess "stack list-dependencies --bench" case ecode of ExitSuccess -> do -- Get our streaming packages and their versions let match [] = Nothing match (_ : []) = Nothing match (x : y : _) = case elem x packages of False -> Nothing True -> Just (x, y) in return $ catMaybes $ map match $ map words (lines (T.unpack $ T.decodeUtf8 out)) ExitFailure _ -> do putStrLn $ "Warning! Cannot determine package versions, " ++ "the 'stack list-dependencies' command failed." return [] -- suffix versions to packages suffixVersion :: [(String, String)] -> String -> String suffixVersion pkginfo p = case lookup p pkginfo of Nothing -> p Just v -> p ++ "-" ++ v ignoringErr :: IO () -> IO () ignoringErr a = catch a (\(ErrorCall err :: ErrorCall) -> putStrLn $ "Failed with error:\n" ++ err ++ "\nSkipping.") createCharts :: String -> String -> Bool -> String -> Bool -> IO () createCharts input pkgList graphs delta versions = do let packages = splitOn "," pkgList pkgInfo <- if versions then getPkgVersions packages else return [] let cmpStyle = case delta of "absolute" -> Absolute "multiples" -> Multiples "percent" -> PercentDiff x -> error $ "Unknown compare option: " ++ show x let bsort pxs bs = let i = intersect (map (last . splitOn "/") pxs) bs in i ++ (bs \\ i) let cfg (t, prefixes) = defaultConfig { mkTitle = Just t , outputDir = Just "charts" , presentation = Groups cmpStyle , diffStrategy = SingleEstimator , omitBaseline = True , classifyBenchmark = \bm -> case any (`isPrefixOf` bm) prefixes of True -> let xs = reverse (splitOn "/" bm) grp = xs !! 0 bench = xs !! 1 in case grp `elem` packages of True -> Just (suffixVersion pkgInfo grp, bench) False -> Nothing False -> Nothing , selectGroups = \gs -> let gs' = map fst gs i = intersect (map (suffixVersion pkgInfo) packages) gs' new = i ++ (gs' \\ i) in nub $ concat $ map (\x -> filter (\(y,_) -> y == x) gs) new , selectBenchmarks = \g -> bsort prefixes $ either error (map fst) $ g (ColumnIndex 0) Nothing } -- links in README.rst eat up the space so we match the same let toOutfile t = filter (not . isSpace) (takeWhile (/= '(') t) packages' = packages {- let packages' = map (\x -> if "pure-" `isPrefixOf` x then fromJust (stripPrefix "pure-" x) else x) packages let selectByRegression f = reverse $ fmap fst $ either (const $ either error id $ f (ColumnIndex 0) Nothing) (sortOn snd) $ f (ColumnIndex 1) Nothing let makeOneGraph infile (t, prefixes) = do let title' fname = if delta /= "absolute" then t ++ " (" ++ fname ++ ") relative to " ++ packages' !! 0 else t ++ " (" ++ fname ++ ") (Lower is Better)" cfg' = cfg (title', prefixes) cfg'' = if delta /= "absolute" then cfg' { selectBenchmarks = selectByRegression } else cfg' if graphs then ignoringErr $ graph infile (toOutfile t) cfg'' else ignoringErr $ report infile Nothing cfg'' mapM_ (makeOneGraph input) charts -} let cutOffByRegression p f = let cmp = if delta == "absolute" then Multiples else cmpStyle in reverse $ fmap fst $ filter (\(_,y) -> p y) . (sortOn snd) $ either (const $ either error id $ f (ColumnIndex 0) (Just cmp)) id $ f (ColumnIndex 1) (Just cmp) -- Make a graph of all operations sorted based on performance regression in -- descending order and operations below a 10% threshold filtered out. let makeDiffGraph infile prefixes t p = do let cfg' = (cfg (t, prefixes)) { presentation = Groups cmpStyle , selectBenchmarks = cutOffByRegression p } if graphs then ignoringErr $ graph infile (toOutfile (t "")) cfg' else ignoringErr $ report infile Nothing cfg' -- compare two packages for best and worst operations let makeTitle fname = if delta /= "absolute" then let prefix = if delta == "percent" then "Extra % " else "" connector = if delta == "multiples" then "as multiples of" else "wrt" field = case fname of "time" -> "time taken" "maxrss" -> "memory used" x -> x pkg = if length packages' == 2 then "by '" ++ packages' !! 1 ++ "'" else "" in prefix ++ field ++ " " ++ pkg ++ " " ++ connector ++ " '" ++ packages' !! 0 ++ "'" else fname ++ " (Lower is Better)" let p x = case delta of "absolute" -> True "multiples" -> x < (-1.1) || x > 1.1 "percent" -> x < (-10) || x > 10 y -> error $ "Unknown compare option: " ++ show y makeDiffGraph input (concatMap snd charts) makeTitle p -- Pass main :: IO () main = withCli createCharts