packages feed

fibon-0.2.0: tools/fibon-analyse/Fibon/Analyse/ExtraStats/GhcStats.hs

{-# LANGUAGE OverloadedStrings #-}
{-# OPTIONS_GHC -fno-warn-unused-do-bind #-}
module Fibon.Analyse.ExtraStats.GhcStats(
    GhcStats(..)
  , parseMachineReadableStats
  , summarizeGhcStats
)
where

import Control.Monad
import Data.Attoparsec(maybeResult)
import Data.Attoparsec.Char8
import Data.ByteString(ByteString)
import qualified Data.ByteString.Char8 as BC
import Data.ByteString.Lex.Double
import qualified Data.Vector.Unboxed as V
import Fibon.Analyse.Metrics
import Fibon.Analyse.Statistics as Statistics

data GhcStats = GhcStats {
      bytesAllocated          :: Measurement MemSize
    , numGCs                  :: Measurement MemSize
    , averageBytesUsed        :: Measurement MemSize
    , maxBytesUsed            :: Measurement MemSize
    , numByteUsageSamples     :: Measurement MemSize
    , peakMegabytesAllocated  :: Measurement MemSize
    , initCPUSeconds          :: Measurement ExecTime
    , initWallSeconds         :: Measurement ExecTime
    , mutatorCPUSeconds       :: Measurement ExecTime
    , mutatorWallSeconds      :: Measurement ExecTime
    , gcCPUSeconds            :: Measurement ExecTime
    , gcWallSeconds           :: Measurement ExecTime

    -- derived metrics
    , ghcCpuTime              :: Measurement ExecTime
    , ghcWallTime             :: Measurement ExecTime
  }
  deriving (Read, Show)

parseMachineReadableStats :: ByteString -> Maybe GhcStats
parseMachineReadableStats s = do
  stats <- toAssocList s
  let find = flip lookup stats
  bytesA <- find "bytes allocated" >>= readMem
  numG   <- find "num_GCs" >>= readMem
  avgB   <- find "average_bytes_used" >>= readMem
  maxB   <- find "max_bytes_used" >>= readMem
  numS   <- find "num_byte_usage_samples" >>= readMem
  peakA  <- find "peak_megabytes_allocated" >>= readMem
  initC  <- find "init_cpu_seconds" >>= readTime
  initW  <- find "init_wall_seconds" >>= readTime
  mutC   <- find "mutator_cpu_seconds" >>= readTime
  mutW   <- find "mutator_wall_seconds" >>= readTime
  gcC    <- find "GC_cpu_seconds" >>= readTime
  gcW    <- find "GC_wall_seconds" >>= readTime
  ghcC   <- initC `addM` mutC >>= addM gcC
  ghcW   <- initW `addM` mutW >>= addM gcW
  return GhcStats {
      bytesAllocated          = bytesA
    , numGCs                  = numG
    , averageBytesUsed        = avgB
    , maxBytesUsed            = maxB
    , numByteUsageSamples     = numS
    , peakMegabytesAllocated  = peakA
    , initCPUSeconds          = initC
    , initWallSeconds         = initW
    , mutatorCPUSeconds       = mutC
    , mutatorWallSeconds      = mutW
    , gcCPUSeconds            = gcC
    , gcWallSeconds           = gcW
    , ghcCpuTime              = ghcC
    , ghcWallTime             = ghcW
  }
  where
    addM :: Num a => Measurement a -> Measurement a -> Maybe (Measurement a)
    addM (Single a) (Single b) = Just $ Single (a+b)
    addM _ _ = Nothing

--
-- Parsing Routines
--
toAssocList :: ByteString -> Maybe [(ByteString, ByteString)]
toAssocList = maybeResult . parse parseList . BC.unlines . drop 1 . BC.lines

readMem :: ByteString -> Maybe (Measurement MemSize)
readMem s = (Single . MemSize . fromIntegral . fst) `liftM` (BC.readInteger s)

readTime :: ByteString -> Maybe (Measurement ExecTime)
readTime s = (Single . ExecTime . fst) `liftM` (readDouble s)

parseList :: Parser [(ByteString, ByteString)]
parseList = do
  skipSpace
  char '['
  tups <- parseTuple `sepBy` (skipSpace >> char ',')
  skipSpace
  char ']'
  return tups

parseTuple :: Parser (ByteString, ByteString)
parseTuple = do
  skipSpace
  char '(' >> skipSpace
  s1 <- parseString
  char ',' >> skipSpace
  s2 <- parseString
  skipSpace
  char ')'
  return (s1, s2)

parseString :: Parser ByteString
parseString = do
  char '"'
  s <- takeTill ('"'==)
  char '"'
  return s

--
-- Analysis Functions
--
summarizeGhcStats :: [GhcStats] -> GhcStats
summarizeGhcStats stats =
  GhcStats {
      bytesAllocated          = sumMem  bytesAllocated
    , numGCs                  = sumMem  numGCs
    , averageBytesUsed        = sumMem  averageBytesUsed
    , maxBytesUsed            = sumMem  maxBytesUsed
    , numByteUsageSamples     = sumMem  numByteUsageSamples
    , peakMegabytesAllocated  = sumMem  peakMegabytesAllocated
    , initCPUSeconds          = sumTime initCPUSeconds
    , initWallSeconds         = sumTime initWallSeconds
    , mutatorCPUSeconds       = sumTime mutatorCPUSeconds
    , mutatorWallSeconds      = sumTime mutatorWallSeconds
    , gcCPUSeconds            = sumTime gcCPUSeconds
    , gcWallSeconds           = sumTime gcWallSeconds
    , ghcCpuTime              = sumTime ghcCpuTime
    , ghcWallTime             = sumTime ghcWallTime
  }
  where
    sumMem  f = Interval $ summarize stats fromIntegral round f
    sumTime f = Interval $ summarize stats fromExecTime ExecTime f

summarize :: [GhcStats]                   -- ^ Stats to summarize
          -> (a -> Double)                -- ^ Conversion to double
          -> (Double -> a)                -- ^ Conversion back from double
          -> (GhcStats -> Measurement a)  -- ^ Field accessor
          -> Estimate a
summarize stats toDouble toMeasurement f =
  fmap toMeasurement $ Statistics.computeSummary ArithMean rawNums
  where
    rawNums = V.fromList $ map (getD . f) stats
    getD (Single m)   = toDouble m
    getD (Interval e) = (toDouble . ePoint) e