Skip to content

Instantly share code, notes, and snippets.

@jfdm
Created November 23, 2023 17:54
Show Gist options
  • Save jfdm/aa0c59495ea55e9b4d72bb4044e8f8f4 to your computer and use it in GitHub Desktop.
Save jfdm/aa0c59495ea55e9b4d72bb4044e8f8f4 to your computer and use it in GitHub Desktop.
Generating whisker plots from Hyperfine output.
#!/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