packages feed

packed-data-0.1.0.0: benchmark/OutputData.hs

{-# LANGUAGE OverloadedStrings #-}
{-# OPTIONS_GHC -Wno-x-partial -Wno-unrecognised-warning-flags #-}

module OutputData (
    RawCSVEntry (..),
    BenchmarkMode (..),
    BenchmarkResult (..),
    BenchmarkValue (..),
    readBenchmarkResults,
    genFail,
) where

import qualified Data.ByteString.Lazy as BS
import Data.Char (isDigit)
import Data.Csv
import qualified Data.List.Safe as Safe
import Data.List.Split (splitOn)
import Data.Vector (toList)
import System.Exit
import Text.Read

data RawCSVEntry = RawCSVEntry
    { _name :: String
    , _mean :: Double
    }
instance FromNamedRecord RawCSVEntry where
    parseNamedRecord m = RawCSVEntry <$> m .: "Name" <*> m .: "Mean"

newtype BenchmarkMode = BenchmarkMode String deriving (Eq)

instance Show BenchmarkMode where
    show (BenchmarkMode s) = s

instance Read BenchmarkMode where
    readsPrec _ s = [(BenchmarkMode s, "")]

-- | Processed data
data BenchmarkResult = BenchmarkResult
    { name :: String
    -- ^ Example: Build
    , values :: [(Int, [BenchmarkValue])]
    -- ^ Maps benchmark values with the depths of the related tree.
    }
    deriving (Show)

data BenchmarkValue = BenchmarkValue
    { mode :: BenchmarkMode
    , meanExecutionTime :: Double
    }
    deriving (Show)

readBenchmarkResults :: FilePath -> IO [BenchmarkResult]
readBenchmarkResults csvPath = do
    rawFile <- BS.readFile csvPath
    rawCSVEntries <-
        either
            genFail
            (return . toList . snd)
            (decodeByName rawFile)
    processedCSVEntries <-
        either
            genFail
            return
            (processCSVEntries rawCSVEntries)
    return processedCSVEntries

processCSVEntries :: [RawCSVEntry] -> Either String [BenchmarkResult]
processCSVEntries rawEntries = do
    entries <-
        mapM
            (maybe (Left "Error when processing entry") Right . processCSVEntry)
            rawEntries
    return $ foldr mergeBenchmarkEntries [] entries

processCSVEntry :: RawCSVEntry -> Maybe BenchmarkResult
processCSVEntry (RawCSVEntry rawname mean) = do
    let splitEntryName = splitOn "/" rawname
    benchName <- splitEntryName Safe.!! (length splitEntryName - 3 :: Int)
    benchMode <- readMaybe =<< Safe.last splitEntryName
    depth <- do
        rawDepth <- splitEntryName Safe.!! (length splitEntryName - 2)
        readMaybe $ takeWhile isDigit rawDepth
    let benchValue =
            BenchmarkValue
                { mode = benchMode
                , meanExecutionTime = mean
                }
    return $
        BenchmarkResult
            { name = benchName
            , values = [(depth, [benchValue])]
            }

mergeBenchmarkEntries :: BenchmarkResult -> [BenchmarkResult] -> [BenchmarkResult]
mergeBenchmarkEntries benRes [] = [benRes]
mergeBenchmarkEntries benRes (mergedRes : mergedRest) =
    let benValue = head $ snd $ head (values benRes)
        depth = fst $ head (values benRes)
     in if name benRes == name mergedRes
            then mergedRes{values = mergeBenchmarkValues depth benValue (values mergedRes)} : mergedRest
            else mergedRes : mergeBenchmarkEntries benRes mergedRest
  where
    mergeBenchmarkValues :: Int -> BenchmarkValue -> [(Int, [BenchmarkValue])] -> [(Int, [BenchmarkValue])]
    mergeBenchmarkValues depth val [] = [(depth, [val])]
    mergeBenchmarkValues valDepth val ((depth, vals) : b) =
        if valDepth == depth
            then (depth, val : vals) : b
            else (depth, vals) : mergeBenchmarkValues valDepth val b

-- | Prints error message, and exits
genFail :: (Show a) => a -> IO b
genFail msg = do
    putStrLn "An error occured while generating graphs:"
    print msg
    exitFailure