Created
November 23, 2023 17:54
-
-
Save jfdm/aa0c59495ea55e9b4d72bb4044e8f8f4 to your computer and use it in GitHub Desktop.
Generating whisker plots from Hyperfine output.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env cabal | |
{- cabal: | |
build-depends: base | |
, yaml | |
, text | |
, bytestring | |
-} | |
{-# LANGUAGE DeriveGeneric, OverloadedStrings, DuplicateRecordFields, DeriveAnyClass #-} | |
module Main where | |
import Prelude hiding (div) | |
import GHC.Generics | |
import Data.Yaml | |
import Data.List | |
import Numeric | |
import System.Environment | |
import Data.Char (toLower,isSpace) | |
data Param | |
= Param | |
{ benchmark :: String | |
} | |
deriving (Show, Eq, Generic, ToJSON,FromJSON) | |
data HFResult | |
= HFResult | |
{ command :: String | |
, mean :: Float | |
, stddev :: Float | |
, median :: Float | |
, user :: Float | |
, system :: Float | |
, min :: Float | |
, max :: Float | |
, times :: [Float] | |
, exit_codes :: [Int] | |
, parameters :: Maybe Param | |
} | |
deriving (Show, Eq, Generic, ToJSON, FromJSON) | |
data HFResults | |
= HFResults | |
{ results :: [HFResult] } | |
deriving (Show, Eq, Generic, ToJSON, FromJSON) | |
------------------------------------------------------------------------------ | |
-- @TODO InterQuartile Ranges | |
whisker :: HFResult -> String | |
whisker r | |
= Prelude.concat | |
[ "\\boxplot" | |
, wrap (command r) | |
, wrapF (showF (median r)) | |
, wrap 0 | |
, wrap 0 | |
, wrapF (showF (Main.min r)) | |
, wrapF (showF (Main.max r)) | |
] | |
genWhiskers :: [ HFResult ] -> String | |
genWhiskers | |
= (Data.List.unlines . Data.List.map whisker) | |
------------------------------------------------------------------------------ | |
genOutput :: Output -> [HFResult] -> IO () | |
genOutput Whiskers | |
= (putStrLn . whisker_plot . apply (genTicks,genWhiskers)) | |
getResults :: FilePath -> IO (Either ParseException HFResults) | |
getResults = decodeFileEither | |
myMain :: Main.Options -> IO () | |
myMain opts | |
= do str <- getResults(file opts) | |
either printLn | |
(genOutput (output opts) . results) | |
str | |
main :: IO () | |
main | |
= do args <- getArgs | |
maybe (putStrLn usageStr) | |
myMain | |
(parseArgs args) | |
-- [ UI Misc ] | |
data Output = Whiskers | |
data Options | |
= Options | |
{ output :: Output | |
, file :: String | |
} | |
parseArgs :: [String] -> Maybe Main.Options | |
parseArgs (x:_) | |
= Just (Options Whiskers x) | |
parseArgs _ | |
= Nothing | |
usageStr :: String | |
usageStr | |
= Prelude.unlines | |
[ "No arguments specified" | |
, "" | |
, "Usage cabal v2-run GeneratePlot.hs -- file" | |
, "" | |
, "FILE is input YAML/JSON specification" | |
] | |
-- [ Misc ] | |
printLn :: Show a => a -> IO () | |
printLn s | |
= do print s | |
putStr "\n" | |
wrap :: Show a => a -> String | |
wrap s = Prelude.concat ["{", show s, "}"] | |
wrapF s = Prelude.concat ["{", s, "}"] | |
showF s = showFFloat Nothing s "" | |
genTicks :: [ HFResult ] -> String | |
genTicks rs | |
= Data.List.unlines $ Data.List.map (\f -> f rs) [genTicksValues, genTicksLabels] | |
genTicksValues :: Show a => [ a ] -> String | |
genTicksValues xs | |
= let ts = [ show (x+1) | x <- [0..Data.List.length xs]] | |
in Data.List.concat $ ["ytick={"] ++ (Data.List.intersperse "," ts) ++ ["},"] | |
genTicksLabels :: [ HFResult ] -> String | |
genTicksLabels xs | |
= let ts = Data.List.map command xs | |
in Data.List.concat $ ["yticklabels={"] ++ (Data.List.intersperse "," ts) ++ ["}"] | |
apply :: (a -> b, a -> c) -> a -> (b,c) | |
apply (f,g) a = (f a, g a) | |
trim = Data.List.dropWhileEnd isSpace . Data.List.dropWhile isSpace | |
whisker_plot :: (String, String) -> String | |
whisker_plot (ticks, plots) | |
= Prelude.unlines | |
[ "\\documentclass[border=5mm]{standalone}" | |
, "" | |
, "\\usepackage{pgf}" | |
, "\\usepackage{pgfplots}" | |
, "\\pgfplotsset{compat=newest}" | |
, "\\usepgfplotslibrary{statistics}" | |
, "" | |
, "\\newcommand*{\\boxplot}[6]{%" | |
, " \\addplot+[" | |
, " line width=.2mm," | |
, " black," | |
, " boxplot prepared={" | |
, " lower whisker={#5}," | |
, " %lower quartile={#3}," | |
, " median={#2}," | |
, " %upper quartile={#4}," | |
, " upper whisker={#6}," | |
, " }" | |
, " ]" | |
, " % No outliers" | |
, " coordinates{};" | |
, "}" | |
, "\\begin{document}" | |
, " \\begin{tikzpicture}" | |
, " \\begin{axis}[%" | |
, " ymin=0," | |
, " xmin=0," | |
, " boxplot/every median/.style={draw=red}," | |
, " scaled ticks=false," | |
, " cycle list={{black}}," | |
, trim ticks | |
, " ]" | |
, trim plots | |
, "\\end{axis}" | |
, "\\end{tikzpicture}" | |
, "\\end{document}" | |
] |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment