packages feed

perf-0.10.0: src/Perf/Stats.hs

{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RankNTypes #-}
{-# OPTIONS_GHC -Wall #-}

-- | Statistical choices for multiple performance measurements.
module Perf.Stats
  ( average,
    median,
    tenth,
    averageI,
    averageSecs,
    StatDType (..),
    statD,
    statDs,
    parseStatD,
    -- stat reporting
    addStat,
    ordy,
    allStats,
    statify,
  )
where

import Control.Monad.State.Lazy
import qualified Data.List as List
import qualified Data.Map.Strict as Map
import Data.Text (Text, pack)
import NumHask.Space (quantile)
import Options.Applicative

-- | Compute the median
median :: [Double] -> Double
median = quantile 0.5

-- | Compute the average
average :: [Double] -> Double
average xs = sum xs / (fromIntegral . length $ xs)

-- | Compute the tenth percentile
tenth :: [Double] -> Double
tenth = quantile 0.1

-- | Compute the average of an Integral
averageI :: (Integral a) => [a] -> Double
averageI xs = sum (fromIntegral <$> xs) / (fromIntegral . length $ xs)

-- | Compute the average time in seconds.
averageSecs :: [Double] -> Double
averageSecs xs = sum xs / (fromIntegral . length $ xs) / 2.5e9

-- | Command-line options for type of statistic.
data StatDType = StatAverage | StatMedian | StatBest | StatSecs deriving (Eq, Show)

-- | Compute a statistic.
statD :: StatDType -> [Double] -> Double
statD StatBest = tenth
statD StatMedian = median
statD StatAverage = average
statD StatSecs = averageSecs

-- | Compute a list of statistics.
statDs :: StatDType -> [[Double]] -> [Double]
statDs StatBest = fmap tenth . List.transpose
statDs StatMedian = fmap median . List.transpose
statDs StatAverage = fmap average . List.transpose
statDs StatSecs = fmap averageSecs . List.transpose

-- | Parse command-line 'StatDType' options.
parseStatD :: Parser StatDType
parseStatD =
  flag' StatBest (long "best" <> help "report upper decile")
    <|> flag' StatMedian (long "median" <> help "report median")
    <|> flag' StatAverage (long "average" <> help "report average")
    <|> flag' StatSecs (long "averagesecs" <> help "report average in seconds")
    <|> pure StatAverage

-- | Add a statistic to a State Map
addStat :: (Ord k, Monad m) => k -> s -> StateT (Map.Map k s) m ()
addStat label s = do
  modify (Map.insert label s)

-- | Linguistic conversion of an ordinal
ordy :: Int -> [Text]
ordy f = zipWith (\x s -> (pack . show) x <> s) [1 .. f] (["st", "nd", "rd"] <> repeat "th")

-- | Compute all stats.
allStats :: Int -> Map.Map [Text] [[Double]] -> Map.Map [Text] [Double]
allStats f m =
  Map.fromList $
    mconcat
      [ mconcat ((\(ks, xss) -> zipWith (\l xs -> (ks <> [l], xs)) (ordy f) xss) <$> mlist),
        (\(ks, xss) -> (ks <> ["best"], quantile 0.1 <$> List.transpose xss)) <$> mlist,
        (\(ks, xss) -> (ks <> ["median"], quantile 0.5 <$> List.transpose xss)) <$> mlist,
        (\(ks, xss) -> (ks <> ["average"], av <$> List.transpose xss)) <$> mlist
      ]
  where
    mlist = Map.toList m
    av xs = sum xs / (fromIntegral . length $ xs)

-- | Convert a Map of performance result to a statistic.
statify :: (Ord a) => StatDType -> Map.Map a [[Double]] -> Map.Map [a] [Double]
statify s m = fmap (statD s) . List.transpose <$> Map.mapKeys (: []) m