-- | -- Module : BenchShow.Graph -- Copyright : (c) 2018 Composewell Technologies -- -- License : BSD3 -- Maintainer : harendra.kumar@gmail.com -- Stability : experimental -- Portability : GHC -- {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE RecordWildCards #-} {-# LANGUAGE TupleSections #-} module BenchShow.Graph ( graph ) where import Control.Arrow (second) import Control.Monad (forM_, when) import Control.Monad.Trans.State.Lazy (get, put) import Data.Maybe (fromMaybe) import Graphics.Rendering.Chart.Easy import Graphics.Rendering.Chart.Backend.Diagrams import BenchShow.Common ------------------------------------------------------------------------------- -- Benchmarking field specific handling ------------------------------------------------------------------------------- -- XXX need the ability to specify Units in the scale yindexes :: Maybe (Double, Double) -> Maybe FieldTick -> Double -> Maybe [Double] yindexes fieldRange granularity multiplier = case (fieldRange, granularity) of (Just (rangeMin, rangeMax), Just g) -> let range = rangeMax - rangeMin (size, count) = case g of TickSize n -> (fromIntegral n, round $ range / fromIntegral n) TickCount n -> (range / fromIntegral n, n) in let size' = size / multiplier rmin = rangeMin / multiplier in Just $ take (count + 1) [rmin, rmin + size'..] _ -> Nothing ------------------------------------------------------------------------------- transformColumns :: [ReportColumn] -> [ReportColumn] transformColumns columns = if length columns == 1 -- workaround for a bug that renders the plot badly when using -- a single cluster in the bar chart. then columns ++ [ReportColumn { colName = "" , colUnit = RelativeUnit "" 1 , colValues = [] }] else columns genGroupGraph :: RawReport -> Config -> IO () genGroupGraph RawReport{..} cfg@Config{..} = do let outputFile = fromMaybe undefined reportOutputFile fieldRange = getFieldRange reportIdentifier cfg granularity = getFieldTick reportIdentifier cfg -- XXX assert that the unit for all columns is the same RelativeUnit ulabel multiplier = colUnit (head reportColumns) replaceMu 'μ' = 'u' replaceMu x = x unitLabel = map replaceMu ulabel columns = transformColumns reportColumns diffStr = if length reportColumns > 1 then diffString presentation diffStrategy else Nothing atitle = makeTitle reportIdentifier diffStr cfg toFile def outputFile $ do layout_title .= atitle layout_title_style . font_size .= 25 layout_x_axis . laxis_generate .= autoIndexAxis (map (map replaceMu . colName) columns) layout_x_axis . laxis_style . axis_label_style . font_size .= 16 layout_y_axis . laxis_style . axis_label_style . font_size .= 14 layout <- get case _layout_legend layout of Nothing -> return () Just style@LegendStyle{..} -> do let s = style { _legend_plot_size = 22 -- , _legend_margin = 40 -- This is not available in versions <= 1.8.2 -- , _legend_position = LegendBelow , _legend_label_style = _legend_label_style { _font_size = 14 } } put $ layout { _layout_legend = Just s } -- layout_y_axis . laxis_override .= axisGridAtTicks let modifyLabels ad = ad { _axis_labels = map (map (second (++ " " ++ unitLabel))) (_axis_labels ad) } when (presentation /= Fields) $ layout_y_axis . laxis_override .= modifyLabels case yindexes fieldRange granularity multiplier of Nothing -> return () Just indexes -> layout_y_axis . laxis_override .= \_ -> makeAxis (let f = floor :: Double -> Int in map ((++ " " ++ unitLabel) . show . f)) (indexes, [], []) plot $ fmap plotBars $ bars reportRowIds $ (addIndexes $ map colValues columns) -- | Presents the benchmark results in a CSV input file as graphical bar charts -- according to the provided configuration. The first parameter is the input -- file name, the second parameter is the name prefix for the output SVG image -- file(s). One or more output files may be generated depending on the -- 'Presentation' setting. The last parameter is the configuration to -- customize the graph, you can start with 'defaultConfig' as the base and -- override any of the fields that you may want to change. -- -- For example: -- -- @ -- graph "bench-results.csv" "output-graph" 'defaultConfig' -- @ -- -- @since 0.2.0 graph :: FilePath -> FilePath -> Config -> IO () graph inputFile outputFile cfg@Config{..} = do let dir = fromMaybe "." outputDir (csvlines, fields) <- prepareToReport inputFile cfg (runs, matrices) <- prepareGroupMatrices cfg csvlines fields case presentation of Groups style -> forM_ fields $ reportComparingGroups style dir (Just outputFile) GraphicalChart runs cfg genGroupGraph matrices Fields -> do forM_ matrices $ reportPerGroup dir (Just outputFile) GraphicalChart cfg genGroupGraph Solo -> let funcs = map (\mx -> reportComparingGroups Absolute dir (Just $ outputFile ++ "-" ++ groupName mx) GraphicalChart runs cfg genGroupGraph [mx]) matrices in sequence_ $ funcs <*> fields