{-# LANGUAGE PatternGuards #-}

-- | Running benchmarks and collecting timings. 
--
--  These functions expect the given `Build` commands to succeed,
--  throwing an error if they don't. If you're not sure whether your command will succeed then test it first.
module BuildBox.Benchmark
	( module BuildBox.Benchmark.TimeAspect
	, module BuildBox.Benchmark.Pretty
	, module BuildBox.Benchmark.Compare
	
	-- * Types
	, Benchmark(..)
	, Timing(..)
	, BenchRunResult(..)
	, BenchResult(..)
	
	-- * Benchmarking
	, runTimedCommand
	, runBenchmarkOnce
	, outRunBenchmarkOnce
	, outRunBenchmarkAgainst
	, outRunBenchmarkWith)
where
import BuildBox.Build	
import BuildBox.Pretty
import BuildBox.Benchmark.Base
import BuildBox.Benchmark.TimeAspect
import BuildBox.Benchmark.Pretty
import BuildBox.Benchmark.Compare
import Data.Time
import Data.List
import Control.Monad


-- Running Commands -------------------------------------------------------------------------------
-- | Run a command, returning its elapsed time.
runTimedCommand 
	:: Build a
	-> Build (NominalDiffTime, a) 
		
runTimedCommand cmd
 = do	start	<- io $ getCurrentTime
	result	<- cmd
	finish	<- io $ getCurrentTime
	return (diffUTCTime finish start, result)


-- | Run a benchmark once.
runBenchmarkOnce
	:: Benchmark 
	-> Build BenchRunResult
	
runBenchmarkOnce bench
 = do	-- Run the setup command
	benchmarkSetup bench

	(diffTime, mKernelTimings)	
		<- runTimedCommand 
		$  benchmarkCommand bench
	
	benchmarkCheck bench
	
	return	$ BenchRunResult
		{ benchRunResultElapsed		= fromRational $ toRational diffTime
		, benchRunResultKernel		= mKernelTimings }


-- | Run a benchmark once, logging activity and timings to the console.
outRunBenchmarkOnce
	:: Benchmark
	-> Build BenchRunResult
	
outRunBenchmarkOnce bench
 = do	out $ "Running " ++ benchmarkName bench ++ "..."
	result	<- runBenchmarkOnce bench
	outLn "ok"
	outLn $ text "    elapsed        = " <> (pprFloatTime $ benchRunResultElapsed result)
		
	maybe (return ()) (\t -> outLn $ text "    kernel elapsed = " <> pprFloatTime t) 
		$ takeTimeAspectOfBenchRunResult TimeAspectKernelElapsed result

	maybe (return ()) (\t -> outLn $ text "    kernel cpu     = " <> pprFloatTime t) 
		$ takeTimeAspectOfBenchRunResult TimeAspectKernelCpu result

	maybe (return ()) (\t -> outLn $ text "    kernel system  = " <> pprFloatTime t)
		$ takeTimeAspectOfBenchRunResult TimeAspectKernelSys result
	
	outBlank
	
	return result


-- | Run a benchmark several times, logging activity to the console.
--   Optionally print a comparison with a prior results.
outRunBenchmarkAgainst
	:: Int			-- ^ Number of iterations.
	-> Maybe BenchResult	-- ^ Optional previous result for comparison.
	-> Benchmark		-- ^ Benchmark to run.
	-> Build BenchResult
	
outRunBenchmarkAgainst iterations mPrior bench  
 = do	out $ "Running " ++ benchmarkName bench ++ " " ++ show iterations ++ " times..."
	runResults	<- replicateM iterations (runBenchmarkOnce bench) 
	outLn "ok"

	let result	= BenchResult
			{ benchResultName	= benchmarkName bench
			, benchResultRuns	= runResults }

	outLn pprBenchResultAspectHeader
	
	maybe (return ()) outLn	$ pprBenchResultAspect TimeAspectElapsed	mPrior result
	maybe (return ()) outLn	$ pprBenchResultAspect TimeAspectKernelElapsed	mPrior result
	maybe (return ()) outLn	$ pprBenchResultAspect TimeAspectKernelCpu	mPrior result
	maybe (return ()) outLn	$ pprBenchResultAspect TimeAspectKernelSys	mPrior result
		
	outBlank
	return	result


-- | Run a benchmark serveral times, logging activity to the console.
--   Also lookup prior results for comparison from the given list.
--   If there is no matching entry then run the benchmark anyway, but don't print the comparison.
outRunBenchmarkWith
	:: Int			-- ^ Number of times to run each benchmark to get averages.
	-> [BenchResult]	-- ^ List of prior results.
	-> Benchmark		-- ^ The benchmark to run.
	-> Build BenchResult

outRunBenchmarkWith iterations priors bench
 = let	mPrior	= find (\b -> benchResultName b == benchmarkName bench) priors
   in	outRunBenchmarkAgainst iterations mPrior bench