packages feed

fibon-0.2.0: tools/fibon-analyse/Fibon/Analyse/Analysis.hs

module Fibon.Analyse.Analysis (
    Analysis(..)
  , runAnalysis
  , computeRows
  , Normalize(..)
  , NormMethod
)
where
import Data.List
import Data.Maybe
import qualified Data.Map        as M
import Control.Monad.Error
import Fibon.Result
import Fibon.Analyse.AnalysisRoutines
import Fibon.Analyse.Parse
import Fibon.Analyse.Result
import Fibon.Analyse.Metrics
import Fibon.Analyse.Statistics
import Fibon.Analyse.Tables
import qualified Data.Vector.Unboxed as V

runAnalysis :: Analysis a -> FilePath -> IO (Maybe [ResultColumn a])
runAnalysis analysis file = do
  fibonResults <- parse file
  case fibonResults of
    Nothing -> return Nothing
    Just rs -> do x <- createResultColumns analysis rs
                  return (Just x)
  where
    parse f | ".SHOW" `isSuffixOf` f = parseShowFibonResults   f
            | otherwise              = parseBinaryFibonResults f

createResultColumns :: Analysis a
                    -> M.Map ResultLabel [FibonResult] 
                    -> IO [ResultColumn a]
createResultColumns analysis fibonResults =
  mapM (analyseResults analysis) (M.toList fibonResults)
  

analyseResults :: Analysis a
               -> (ResultLabel, [FibonResult])
               -> IO (ResultColumn a)
analyseResults analysis (resultName, fibonResults) = do
  ars <- mapM (analyseResult analysis) fibonResults
  return $ ResultColumn resultName (resMap ars)
  where
    resMap ars       = foldr create M.empty (zip ars fibonResults)
    create (ar,fr) m = M.insert (benchNameOnly fr) ar m
    benchNameOnly fr = takeWhile (/= '-') (benchName fr)
  
analyseResult :: 
                 Analysis a
              -> FibonResult 
              -> IO (AnalyseResult a)        -- ^ final result
analyseResult analysis fibonR = do
  fibonS <- (fibonAnalysis analysis) fibonR
  extraS <- case mbExtras of 
              Nothing -> return   Nothing
              Just [] -> return   Nothing
              Just es -> return . Just =<< (extraAnalysis analysis) es 
  return (AnalyseResult fibonS extraS)
  where
    mbExtras = sequence $ map (extraP.runStats) (details.runData $ fibonR)
    extraP   = extraParser analysis


-- | Functions for normalizing and computing summaries of results
--
--
type RowData = (RowName, [PerfData])
type RowName    = String
type TableError = String
type PerfMonad  = Either TableError
type NormMethod a = ResultColumn a -> Normalize a

data Normalize a =
    NormPercent (ResultColumn a)
  | NormRatio   (ResultColumn a)
  | NormNone    (ResultColumn a) -- ^ For uniform normalization use

computeRows :: [(Normalize a, ResultColumn a)]
            -> [BenchName]
            -> TableSpec a
            -> Either TableError ([RowData], [RowData])
computeRows resultColumns benchs colSpecs = do
  rows <- mapM (computeOneRow resultColumns colSpecs) benchs
  let colData = transpose $ map snd rows
      doSumm  how = mapM (summarize how) colData
  minRow   <- doSumm Min
  meanRow  <- doSumm GeoMean
  arithRow <- doSumm ArithMean
  maxRow   <- doSumm Max
  let sumRows = [
          ("min", minRow)
        , ("geomean", meanRow)
        , ("arithmean", arithRow)
        , ("max", maxRow)
        ]
  return (rows, sumRows)

computeOneRow :: [(Normalize a, ResultColumn a)]
              -> [ColSpec a]
              -> BenchName
              -> PerfMonad RowData
computeOneRow resultColumns colSpecs bench = do
  row <- mapM (\spec ->
      mapM (computeOneColumn bench spec) resultColumns
    ) colSpecs
  return (bench, concat row)

computeOneColumn :: BenchName
                 -> ColSpec a
                 -> (Normalize a, ResultColumn a)
                 -> PerfMonad PerfData
computeOneColumn bench (ColSpec _ metric) (normType, resultColumn) =
  maybe (return NoResult) doNormalize (getRawPerf resultColumn)
  where
    doNormalize peak =
      case normType of
        NormPercent base -> normToBase base normalizePercent
        NormRatio   base -> normToBase base normalizeRatio
        NormNone      _  -> return (mkRaw peak)
      where
        mkRaw  = Basic . Raw
        mkNorm = Basic . Norm
        normToBase base normFun = maybe (return NoResult)
                                        (\b -> mkNorm `liftM` normFun b peak)
                                        (getRawPerf base)
    getRawPerf rc = perf $ fmap metric ((M.lookup bench . results) rc)

type NormFun a =(a -> Double) -> a -> a -> NormPerf
normalizePercent :: RawPerf -> RawPerf -> PerfMonad NormPerf
normalizePercent = normalize normP

normalizeRatio :: RawPerf -> RawPerf -> PerfMonad NormPerf
normalizeRatio = normalize normR

normalize :: NormFun RawPerf
          -> RawPerf
          -> RawPerf
          -> PerfMonad NormPerf
normalize n base@(RawTime _) peak@(RawTime _) =
  return(n rawPerfToDouble base peak)
normalize n base@(RawSize _) peak@(RawSize _) =
  return(n rawPerfToDouble base peak)
normalize _ _ _ = throwError "Can not normalize a size by time"

normP :: NormFun a
normP = norm Percent (\base peak -> (peak / base) * 100)

normR :: NormFun a
normR = norm Ratio (\base peak -> (base / peak))

-- TODO: use the intervals to compute the resulting interval
norm :: (Estimate Double -> NormPerf)  -- ^ NormPerf constructor
     -> (Double -> Double -> Double)   -- ^ Normalizing function
     -> (a -> Double)                  -- ^ Conversion to double
     -> a -> a                         -- ^ Values to normalize
     -> NormPerf
norm c f toDouble base peak =
  c (mkPointEstimate mkStddev (f (toDouble base) (toDouble peak)))
  where mkStddev = fromIntegral :: Int -> Double

summarize :: Summary -> [PerfData] -> PerfMonad PerfData
summarize how perfData =
  case (normData, rawData) of
    ([], []) -> return NoResult
    (nd, []) -> summarizeNorm how nd
    ([], rd) -> summarizeRaw  how rd
    _        -> throwError "Mixed raw and norm results in column"
  where
    normData = getData (\p -> case p of Basic (Norm n) -> Just n ; _ -> Nothing)
    rawData  = getData (\p -> case p of Basic (Raw  r) -> Just r ; _ -> Nothing)
    getData  f = (catMaybes . map f) perfData

summarizeRaw :: Summary -> [RawPerf] -> PerfMonad PerfData
summarizeRaw how rawPerfs =
  case (isTime rawPerfs, isSize rawPerfs) of
    (True, _) -> summarizeRaw' how ExecTime          RawTime rawPerfs
    (_, True) -> summarizeRaw' how (MemSize . round) RawSize rawPerfs
    _         -> throwError "Can only summarize column with time or size"
  where
    isTime = all (\r -> case r of RawTime _ -> True; RawSize _ -> False)
    isSize = all (\r -> case r of RawSize _ -> True; RawTime _ -> False)

summarizeRaw' :: Summary                 -- ^ what kind of summary
             -> (Double -> a)            -- ^ rounding function
             -> (Estimate a -> RawPerf)  -- ^ RawPerf constructor
             -> [RawPerf]                -- ^ Performance numbers to summary
             -> PerfMonad PerfData
summarizeRaw' how roundFun makeRaw rawPerfs =
  return $ Summary how (Raw (makeRaw (fmap roundFun (computeSummary how vec))))
  where
    vec = V.fromList (map rawPerfToDouble rawPerfs)

summarizeNorm :: Summary -> [NormPerf] -> PerfMonad PerfData
summarizeNorm how normPerfs =
  case (isPercent normPerfs, isRatio normPerfs) of
    (True, _) -> summarizeNorm' how Percent normPerfs
    (_, True) -> summarizeNorm' how Ratio   normPerfs
    _         -> throwError "Can only summarize column with percent or ratio"
    where
      isPercent = all (\r -> case r of Percent _ -> True;  Ratio _ -> False)
      isRatio   = all (\r -> case r of Percent _ -> False; Ratio _ -> True)

summarizeNorm' :: Summary
              -> (Estimate Double -> NormPerf)
              -> [NormPerf]
              -> PerfMonad PerfData
summarizeNorm' how makeNorm normPerfs =
  return $ Summary how (Norm (makeNorm (computeSummary how vec)))
  where
    vec = V.fromList (map normPerfToDouble normPerfs)