fibon-0.2.0: tools/fibon-run/Fibon/Run/BenchmarkRunner.hs
module Fibon.Run.BenchmarkRunner (
RunResult(..)
, RunFailure(..)
, Fibon.Run.BenchmarkRunner.run
)
where
import Control.Concurrent
import Control.Monad
import Control.Exception
import qualified Data.ByteString as B
import Data.Maybe
import Data.Time.Clock
import qualified Data.Vector.Unboxed as Vector
import Fibon.BenchmarkInstance
import Fibon.Result
import Fibon.Run.BenchmarkBundle
import Fibon.Run.Log as Log
import qualified Fibon.Run.SysTools as SysTools
import Statistics.Sample
import System.Directory
import System.Exit
import System.FilePath
import System.IO
import System.Process
import Text.Printf
data RunResult =
Success {runSummary :: RunSummary, runDetails :: [RunDetail]}
| Failure [RunFailure]
deriving (Read, Show)
data RunFailure =
MissingOutput FilePath
| DiffError String
| Timeout
deriving (Read, Show)
run :: BenchmarkBundle -> IO RunResult
run bb = do
let bmk = (bundleName bb)
pwd = (pathToExeBuildDir bb)
cmd = (prettyRunCommand bb)
Log.info $ "Running Benchmark "
Log.info $ " BMK: " ++ bmk
Log.info $ " PWD: " ++ pwd
Log.info $ " CMD: " ++ cmd
Log.info $ printf "\n@%s|%s|%s" bmk pwd cmd
runDirect bb
{-
-- Move this to analysis time
analyze :: Sample -> ExtraStats -> Int -> Double -> IO RunSummary
analyze times ghcStats numResamples ci = do
let ests = [mean, stdDev]
res <- withSystemRandom $ \gen ->
resample gen ests numResamples times :: IO [Resample]
let [em,es] = bootstrapBCA ci times ests res
let runData = RunSummary {
timeSummary =
TimeMeasurement {
meanTime = estPoint em
, meanTimeLB = estLowerBound em
, meanTimeUB = estUpperBound em
, meanStddev = estPoint es
, meanStddevUB = estLowerBound es
, meanStddevLB = estUpperBound es
, confidence = ci
}
, statsSummary = ghcStats
}
return runData
-}
checkResult :: BenchmarkBundle -> IO (Maybe [RunFailure])
checkResult bb = do
rs <- mapM (checkOutput bb) (output . benchDetails $ bb)
let errs = filter isJust rs
case errs of
[] -> return $ Nothing
es -> return $ Just (catMaybes es)
checkOutput :: BenchmarkBundle -> OutputDescription -> IO (Maybe RunFailure)
checkOutput bb (o, Exists) = do
let f = (destinationToRealFile bb o)
e <- doesFileExist f
if e then return Nothing
else return $ Just $ MissingOutput ("File "++f++" does not exist")
checkOutput bb (o, Diff diffFile) = do
e1 <- checkOutput bb (o, Exists)
e2 <- checkOutput bb (d, Exists)
e3 <- runDiff f1 f2
return $ msum [e1, e2, e3]
where
d = OutputFile diffFile
f1 = (destinationToRealFile bb o)
f2 = (destinationToRealFile bb d)
runDiff :: FilePath -> FilePath -> IO (Maybe RunFailure)
runDiff f1 f2 = do
Log.info $ "Diffing files: "++f1++" "++f2
(r, o, _) <- readProcessWithExitCode (SysTools.diff) [f1, f2] ""
if r == ExitSuccess then Log.info "No diff error" >>
return Nothing
else Log.info "Diff error" >>
(return $ Just $ DiffError o)
destinationToRealFile :: BenchmarkBundle -> OutputDestination -> FilePath
destinationToRealFile bb (OutputFile f) = (pathToExeRunDir bb) </> f
destinationToRealFile bb Stdout = (pathToStdoutFile bb)
destinationToRealFile bb Stderr = (pathToStderrFile bb)
readExtraStats :: BenchmarkBundle -> IO ExtraStats
readExtraStats bb = do
let mbStatsFile = extraStats bb
statsFile = fromJust mbStatsFile
logReadE :: IOException -> IO ExtraStats
logReadE e =
Log.warn ("Error reading stats file: "++statsFile++"\n "++show e)
>> return B.empty
case mbStatsFile of
Nothing -> return B.empty
Just f -> do
handle logReadE $
bracket (openFile ((pathToExeRunDir bb) </> f) ReadMode)
(hClose)
(\h -> B.hGetContents h >>= \s -> B.length s `seq` return s)
--stats <- hGetContents h
-- drop header line in machine readable stats
--let body = (unlines . drop 1 . lines) stats
--case reads body of
-- [(p, _)] -> return p
-- _ -> logParseE)
type RunStepResult = IO (Either [RunFailure] RunDetail)
runDirect :: BenchmarkBundle -> IO RunResult
runDirect bb = do
mbDetails <- go count []
case mbDetails of
Left e -> return $ Failure e
Right ds -> return $ Success (summarize ds) ds
where
go 0 ds = return $ Right (reverse ds)
go n ds = do
res <- runB bb
case res of
Right d -> go (n-1) (d:ds)
Left e -> return $ Left e
runB = maybe runBenchmarkWithoutTimeout runBenchmarkWithTimeout limit
limit = timeout bb
count = (iters bb)
summarize :: [RunDetail] -> RunSummary
summarize ds = RunSummary {
meanTime = mean times
, stdDevTime = stdDev times
, statsSummary = stats
}
where
times = (Vector.fromList $ map runTime ds)
stats = case ds of (x:_) -> runStats x; _ -> B.empty
type TimeoutLength = Int
runBenchmarkWithTimeout :: TimeoutLength -> BenchmarkBundle -> RunStepResult
runBenchmarkWithTimeout us bb = do
resMVar <- newEmptyMVar
pidMVar <- newEmptyMVar
tid1 <- forkIO $ (putMVar resMVar . Just) =<< timeBenchmarkExe bb (Just pidMVar)
_ <- forkIO $ threadDelay us >> putMVar resMVar Nothing
res <- takeMVar resMVar
case res of
Nothing -> do
Log.info $ "benchmark timed out after "++(show us)++" us"
-- try to kill the subprocess
pid <- tryTakeMVar pidMVar
maybe pass terminateProcess pid
-- kill the haskell thread
killThread tid1
return $ Left [Timeout]
Just runDetail -> do
maybe (Right runDetail) Left `liftM` checkResult bb
runBenchmarkWithoutTimeout :: BenchmarkBundle -> RunStepResult
runBenchmarkWithoutTimeout bb = do
runDetail <- timeBenchmarkExe bb Nothing
maybe (Right runDetail) Left `liftM` checkResult bb
timeBenchmarkExe :: BenchmarkBundle -- benchmark to run
-> Maybe (MVar ProcessHandle) -- in case we need to kill it
-> IO RunDetail
timeBenchmarkExe bb pidMVar = do
p <- bundleProcessSpec bb
start <- getCurrentTime
(_, _, _, pid) <- createProcess p
maybe pass (flip putMVar pid) pidMVar
_ <- waitForProcess pid
end <- getCurrentTime
mapM_ closeStdIO [std_in p, std_out p, std_err p]
stats <- readExtraStats bb
return $ RunDetail (realToFrac (diffUTCTime end start)) stats
closeStdIO :: StdStream -> IO ()
closeStdIO (UseHandle h) = hClose h
closeStdIO _ = return ()
pass :: IO ()
pass = return()