packages feed

perf-0.14.0.0: src/Perf/Chart.hs

{-# LANGUAGE OverloadedLabels #-}
{-# LANGUAGE OverloadedStrings #-}

module Perf.Chart where

import Chart
import Control.Category ((>>>))
import Data.Bifunctor
import Data.Bool
import Data.List qualified as List
import Data.Map.Strict qualified as Map
import Data.Maybe
import Data.Text (Text)
import Data.Text qualified as Text
import GHC.Generics
import Optics.Core
import Options.Applicative
import Perf.Stats as Perf
import Prettychart

--     m <- fromDump defaultPerfDumpOptions
data PerfChartOptions
  = PerfChartOptions
  { doChart :: Bool,
    chartFilepath :: FilePath,
    truncateAt :: Double,
    doSmallChart :: Bool,
    doBigChart :: Bool,
    doHistChart :: Bool,
    doAveragesLegend :: Bool,
    averagesStyle :: Style,
    averagesPaletteStart :: Int,
    averagesLegend :: LegendOptions,
    smallStyle :: Style,
    smallHud :: HudOptions,
    bigStyle :: Style,
    bigHud :: HudOptions,
    titleSize :: Double,
    histGrain :: Int,
    bigWidth :: Double,
    excludeZeros :: Bool
  }
  deriving (Eq, Show, Generic)

defaultPerfChartOptions :: PerfChartOptions
defaultPerfChartOptions = PerfChartOptions False "other/perf.svg" 10 True True True True (defaultGlyphStyle & set #size 0.05) 2 (defaultLegendOptions & set #place PlaceBottom & set #numStacks 3 & set #scaleChartsBy 0.2 & set #legendSize 0.3 & set #alignCharts AlignLeft & set #hgap (-0.2) & set #vgap (-0.1)) (defaultGlyphStyle & set #size 0.01 & over #color (rgb (palette 0)) & set (#color % opac') 0.3 & set (#borderColor % opac') 0.3 & set #glyphShape (gpalette 0)) defaultHudOptions (defaultGlyphStyle & set #size 0.06 & over #color (rgb (palette 0)) & set #glyphShape (gpalette 0) & set (#color % opac') 0.3 & set (#borderColor % opac') 1) (defaultHudOptions & set (#axes % each % #item % #ticks % #textTick %? #style % #size) 0.07 & over #axes (drop 1) & set (#axes % each % #item % #ticks % #tick % numTicks') (Just 2)) 0.08 100 0.2 True

-- | Parse charting options.
parsePerfChartOptions :: PerfChartOptions -> Parser PerfChartOptions
parsePerfChartOptions def =
  (\c fp trunAt small big hist avs -> PerfChartOptions c fp trunAt small big hist avs (view #averagesStyle def) (view #averagesPaletteStart def) (view #averagesLegend def) (view #smallStyle def) (view #smallHud def) (view #bigStyle def) (view #bigHud def) (view #titleSize def) (view #histGrain def) (view #bigWidth def) (view #excludeZeros def))
    <$> switch (long "chart" <> short 'c' <> help "chart the result")
    <*> option str (value (view #chartFilepath def) <> showDefault <> long "chartpath" <> metavar "FILE" <> help "chart file name")
    <*> option auto (value (view #truncateAt def) <> showDefaultWith show <> long "truncateat" <> help "truncate chart data (multiple of median)")
    <*> switch (long "small")
    <*> switch (long "big")
    <*> switch (long "histogram")
    <*> switch (long "averages")

perfCharts :: PerfChartOptions -> Maybe [Text] -> Map.Map Text [[Double]] -> ChartOptions
perfCharts cfg labels m = bool (stackCO stackn AlignLeft NoAlign 0.1 cs) (head cs) (length cs == 1)
  where
    stackn = length cs & fromIntegral & sqrt @Double & ceiling
    cs = uncurry (perfChart cfg) <$> ps'
    ps = mconcat $ fmap (uncurry zip . bimap (\t -> fmap ((t <> ": ") <>) (fromMaybe (Text.pack . show @Int <$> [0 ..]) labels)) List.transpose) (Map.toList m)
    ps' = filter ((> 0) . sum . snd) ps

perfChart :: PerfChartOptions -> Text -> [Double] -> ChartOptions
perfChart cfg t xs = finalChart
  where
    xsSmall = xs & xify & filter (_y >>> (< upperCutoff)) & filter (\x -> view #excludeZeros cfg && (_y x > 0))
    xsBig = xs & xify & filter (_y >>> (>= upperCutoff))
    med = median xs
    best = tenth xs
    av = Perf.average xs
    upperCutoff = view #truncateAt cfg * med

    labels =
      [ "average: " <> comma (Just 3) av,
        "median: " <> comma (Just 3) med,
        "best: " <> comma (Just 3) best
      ]
    (Rect _ _ y' w') = fromMaybe one $ space1 xsSmall
    (Range x' z') = Range zero (fromIntegral $ length xs)
    rectx = BlankChart defaultStyle [Rect x' z' y' w']
    averagesCT = named "averages" $ zipWith (\x i -> GlyphChart (view #averagesStyle cfg & set #color (palette i) & set #borderColor (palette i) & set #glyphShape (gpalette i)) [Point zero x]) [av, med, best] [(view #averagesPaletteStart cfg) ..]

    (smallDot, smallHist) = dotHistChart (view #histGrain cfg) (view #smallStyle cfg) (mempty @ChartOptions & set #chartTree (averagesCT <> named "xrange" [rectx]) & set #hudOptions (view #smallHud cfg)) xsSmall

    minb = minimum (_y <$> xsBig)
    bigrange = Rect x' z' (bool minb zero (length xsBig == 1)) minb
    (bigDot, bigHist) = dotHistChart (view #histGrain cfg) (view #bigStyle cfg) (mempty @ChartOptions & set #hudOptions (view #bigHud cfg) & set #chartTree (named "xrange" [BlankChart defaultStyle [bigrange]])) xsBig

    (Rect bdX bdW _ _) = fromMaybe one $ view styleBox' (asChartTree bigDot)
    bdr = Just $ Rect bdX bdW (-(view #bigWidth cfg)) (view #bigWidth cfg)
    (Rect bdhX bdhW _ _) = fromMaybe one $ view styleBox' (asChartTree bigHist)
    bhr = Just $ Rect bdhX bdhW (-(view #bigWidth cfg)) (view #bigWidth cfg)

    finalChart =
      mempty @ChartOptions
        & set
          #chartTree
          ( stack
              2
              NoAlign
              NoAlign
              0
              ( bool (asChartTree bigDot & set styleBox' bdr & pure) mempty (null xsBig)
                  <> bool (asChartTree bigHist & set styleBox' bhr & pure) mempty (null xsBig)
                  <> [ asChartTree smallDot,
                       asChartTree smallHist
                     ]
              )
          )
        & set
          (#hudOptions % #legends)
          [Priority 10 (view #averagesLegend cfg & set #legendCharts (zipWith (\t' c -> (t', [c])) labels (toListOf chart' averagesCT)))]
        & set
          (#hudOptions % #titles)
          [Priority 5 (defaultTitleOptions t & set (#style % #size) (view #titleSize cfg))]

dotHistChart :: Int -> Style -> ChartOptions -> [Point Double] -> (ChartOptions, ChartOptions)
dotHistChart grain gstyle co xs = (dotCO, histCO)
  where
    dotCT = named "dot" [GlyphChart gstyle xs]
    ys = fmap _y xs
    (Range l u) = fromMaybe one (space1 ys)
    r' = bool (Range l u) (Range 0 l) (l == u)
    r = computeRangeTick r' (fromMaybe defaultTick (co & preview (#hudOptions % #axes % ix 1 % #item % #ticks % #tick)))
    (y, w) = let (Range y' w') = r in bool (y', w') (y' - 0.5, y' + 0.5) (y' == w')

    histCO = hhistChart r grain ys & set (#markupOptions % #chartAspect) (CanvasAspect 0.3) & over #chartTree (<> unnamed [BlankChart defaultStyle [Rect 0 0 y w]])
    dotCO = co & over #chartTree (dotCT <>)

compareCharts :: [(PerfChartOptions, Text, [Double])] -> ChartOptions
compareCharts xs = finalChart
  where
    xs' = xs & fmap (\(_, _, x) -> x)
    cfg' = xs & fmap (\(x, _, _) -> x)
    t' = xs & fmap (\(_, x, _) -> x)
    cfg = head cfg'
    xsSmall = xs' & fmap (xify >>> filter (_y >>> (< upperCutoff)) >>> filter (\x -> view #excludeZeros cfg && (_y x > 0)))
    xsBig = xs' & fmap (xify >>> filter (_y >>> (>= upperCutoff)))
    med = median <$> xs'
    upperCutoff = view #truncateAt cfg * maximum med

    (Rect _ _ y' w') = fromMaybe one $ space1 $ mconcat xsSmall
    (Range x' z') = Range zero (fromIntegral $ maximum (length <$> xs'))
    rectx = BlankChart defaultStyle [Rect x' z' y' w']

    (smallDot, smallHist) = dotHistCharts (view #histGrain cfg) (mempty @ChartOptions & set #hudOptions (view #smallHud cfg) & set #chartTree (unnamed [rectx])) (zip (view #smallStyle <$> cfg') xsSmall)

    minb = minimum (_y <$> mconcat xsBig)
    bigrange = Rect x' z' (bool minb zero (length (mconcat xsBig) == 1)) minb
    (bigDot, bigHist) = dotHistCharts (view #histGrain cfg) (mempty @ChartOptions & set #hudOptions (view #bigHud cfg) & set #chartTree (named "xrange" [BlankChart defaultStyle [bigrange]])) (zip (view #bigStyle <$> cfg') xsBig)

    (Rect bdX bdW _ _) = fromMaybe one $ view styleBox' (asChartTree bigDot)
    bdr = Just $ Rect bdX bdW (-(view #bigWidth cfg)) (view #bigWidth cfg)
    (Rect bdhX bdhW _ _) = fromMaybe one $ view styleBox' (asChartTree bigHist)
    bhr = Just $ Rect bdhX bdhW (-(view #bigWidth cfg)) (view #bigWidth cfg)

    finalChart =
      mempty @ChartOptions
        & set
          #chartTree
          ( stack
              2
              NoAlign
              NoAlign
              0
              ( bool (asChartTree bigDot & set styleBox' bdr & pure) mempty (null xsBig)
                  <> bool (asChartTree bigHist & set styleBox' bhr & pure) mempty (null xsBig)
                  <> [ asChartTree smallDot,
                       asChartTree smallHist
                     ]
              )
          )
        & set
          (#hudOptions % #legends)
          [Priority 10 (view #averagesLegend cfg & set #legendCharts (zipWith (\t'' c -> (t'', [c])) t' (toListOf (#chartTree % chart') smallDot)))]

dotHistCharts :: Int -> ChartOptions -> [(Style, [Point Double])] -> (ChartOptions, ChartOptions)
dotHistCharts grain co xs = (dotCO, histCO)
  where
    dotCTs = named "dot" (uncurry GlyphChart <$> xs)
    ys = fmap _y . snd <$> xs
    (Range l u) = fromMaybe one (space1 (mconcat ys))
    r' = bool (Range l u) (Range 0 l) (l == u)
    r = computeRangeTick r' (fromMaybe defaultTick (co & preview (#hudOptions % #axes % ix 1 % #item % #ticks % #tick)))
    (y, w) = let (Range y' w') = r in bool (y', w') (y' - 0.5, y' + 0.5) (y' == w')

    histCO = hhistCharts r grain (zip (fst <$> xs) ys) & set (#markupOptions % #chartAspect) (CanvasAspect 0.3) & over #chartTree (<> unnamed [BlankChart defaultStyle [Rect 0 0 y w]])
    dotCO = co & over #chartTree (dotCTs <>)