packages feed

fibon-0.2.0: tools/fibon-run/Fibon/Run/Main.hs

module Main (
  main
)
where 
import Control.Monad
import Control.Exception
import qualified Data.ByteString as B
import Data.Char
import Data.List
import Data.Maybe
import Data.Serialize
import Data.Time.Clock
import Data.Time.Format
import Data.Time.LocalTime
import Fibon.Benchmarks
import Fibon.Result
import Fibon.Run.Actions
import Fibon.Run.CommandLine
import Fibon.Run.Config
import Fibon.Run.Manifest
import Fibon.Run.BenchmarkBundle
import qualified Fibon.Run.Log as Log
import System.Directory
import System.Exit
import System.Environment
import System.FilePath
import System.Locale
import System.Time
import Text.Printf


main :: IO ()
main = do
  opts <- parseArgsOrDie
  currentDir <- getCurrentDirectory
  initConfig  <- selectConfig (optConfig opts)
  let runConfig  = mergeConfigOpts initConfig opts
      workingDir = currentDir </> "run"
      benchRoot  = currentDir </> "benchmarks/Fibon/Benchmarks"
      logPath    = currentDir </> "log"
      action     = optAction opts
  uniq       <- chooseUniqueName workingDir (configId runConfig)
  (logFile, showFile, summaryFile, binFile) <- Log.setupLogger logPath logPath uniq
  startTime <- timeStamp
  progEnv <- getEnvironment
  Log.notice ("Starting Run at   " ++ prettyTimeStamp startTime)
  Log.notice ("  log            : " ++ logFile)
  Log.notice ("  result(binary) : " ++ binFile)
  Log.notice ("  result(text)   : " ++ showFile)
  Log.notice ("  result(summary): " ++ summaryFile)
  let bundles = makeBundles runConfig workingDir benchRoot uniq progEnv
  results <- mapM (runAndReport action) bundles
  (B.writeFile binFile . encode) (catMaybes results)
  endTime <- timeStamp
  Log.notice ("Finished Run at " ++ formatEndTime startTime endTime)
  Log.notice ("  log            : " ++ logFile)
  Log.notice ("  result(binary) : " ++ binFile)
  Log.notice ("  result(text)   : " ++ showFile)
  Log.notice ("  result(summary): " ++ summaryFile)

parseArgsOrDie :: IO Opt
parseArgsOrDie = do
  args <- getArgs
  case parseCommandLine args of
    Left  msg  -> putStrLn msg >> exitFailure
    Right opts -> do
      case optHelpMsg opts of
        Just msg -> putStrLn msg >> exitSuccess
        Nothing  -> return opts

type RunResult = Maybe FibonResult
type RunCont a = (a -> IO RunResult)
runAndReport :: Action -> BenchmarkBundle -> IO RunResult
runAndReport action bundle = do
  Log.notice $ "Benchmark: "++ (bundleName bundle)++ " action="++(show action)
  case action of
    Sanity -> run sanityCheckBundle  (const $ return Nothing)
    Build  -> run buildBundle        (\(BuildData time _size) -> do
                Log.info (printf "Build completed in %0.2f seconds" time)
                return Nothing
              )
    Run    -> run runBundle          (\fr@(FibonResult n _bd rd) -> do
                Log.result(show fr)
                Log.summary(printf "%s %.4f" n ((meanTime . summary) rd))
                return (Just fr)
              )
  where
  run :: Show a => ActionRunner a -> RunCont a -> IO RunResult
  run = runAndLogErrors bundle

runAndLogErrors :: Show a
                => BenchmarkBundle
                -> ActionRunner a
                -> RunCont a
                -> IO RunResult
runAndLogErrors bundle act cont = do
  result <- try (act bundle)
  -- result could fail from an IOError, or from a failure in the RunMonad
  case result of
    Left  ioe -> logError (show (ioe :: IOError)) >> return Nothing
    Right res ->
      case res of
        Left  e -> logError (show e) >> return Nothing
        Right r -> cont r
   where
   name = bundleName bundle
   logError s = do Log.warn $ "Error running: "  ++ name
                   Log.warn $ "        =====> "  ++ s

selectConfig :: ConfigId -> IO RunConfig
selectConfig configName =
  case find ((== configName) . configId) configManifest of
    Just c  -> do return c
    Nothing -> do
      Log.error $ "Unknown config: "       ++ configName
      Log.error $ "Available configs:\n  " ++ configNames
      exitFailure
  where configNames = concat (intersperse "\n  " $ map configId configManifest)

makeBundles :: RunConfig
            -> FilePath  -- ^ Working directory
            -> FilePath  -- ^ Benchmark base path
            -> String    -- ^ Unique Id
            -> [(String, String)] -- ^ Environment variables
            -> [BenchmarkBundle]
makeBundles rc workingDir benchRoot uniq progEnv = map bundle bms
  where
  bundle (bm, size, tune) =
    mkBundle rc bm workingDir benchRoot uniq size tune progEnv
  bms = sort
        [(bm, size, tune) |
                      size <- (sizeList rc),
                      bm   <- expandBenchList $ runList rc,
                      tune <- (tuneList rc)]

expandBenchList :: [BenchmarkRunSelection] -> [FibonBenchmark]
expandBenchList = concatMap expand
  where
  expand (RunSingle b) = [b]
  expand (RunGroup  g) = filter (\b -> benchGroup b == g) allBenchmarks

chooseUniqueName :: FilePath -> String -> IO String
chooseUniqueName workingDir configName = do
  wdExists <- doesDirectoryExist workingDir
  unless wdExists (createDirectory workingDir)
  dirs  <- getDirectoryContents workingDir
  let numbered = filter (\x -> length x > 0) $ map (takeWhile isDigit) dirs
  case numbered of
    [] -> return $ format (0 :: Int)
    _  -> return $ (format . (+1) . read . last . sort) numbered
  where
  format :: Int -> String
  format d = printf "%03d.%s" d configName

mergeConfigOpts :: RunConfig -> Opt -> RunConfig
mergeConfigOpts rc opt = rc {
      tuneList   = maybe (tuneList rc) (:[]) (optTuneSetting opt)
    , sizeList   = maybe (sizeList rc) (:[]) (optSizeSetting opt)
    , runList    = maybe (runList  rc)   id  (optBenchmarks  opt)
    , iterations = maybe (iterations rc) id  (optIterations  opt)
  }

type TimeStamp = (ClockTime, LocalTime)
timeStamp :: IO TimeStamp
timeStamp = do
  tz <- getCurrentTimeZone
  t  <- getCurrentTime
  ct <- getClockTime
  return $ (ct, utcToLocalTime tz t)

prettyTimeStamp :: TimeStamp -> String
prettyTimeStamp (_,lt) = formatTime defaultTimeLocale "%F %T" lt

prettyTimeDiff :: TimeStamp -> TimeStamp -> String
prettyTimeDiff (ct1,_) (ct2,_) =
  timeDiffToString . normalizeTimeDiff $ diffClockTimes ct2 ct1

formatEndTime :: TimeStamp -> TimeStamp -> String
formatEndTime startT endT =
  prettyTimeStamp endT ++ " (completed in " ++ prettyTimeDiff startT endT ++")"

{-
dumpConfig :: RunConfig -> IO ()
dumpConfig rc = do
  --putStrLn $ show $ map (uncurry benchInstance) $ sort bms
  putStrLn $ show bms
  mapM_ (dumpInstance rc) bms
  where
  bms = sort
        [(bm, size, tune) |
                      size <- (sizeList rc),
                      bm   <- expandBenchList $ runList rc,
                      tune <- (tuneList rc)]

dumpInstance :: RunConfig -> (FibonBenchmark, InputSize, TuneSetting)->IO ()
dumpInstance rc inst@(bm, size, tune) = do
  putStrLn (take 68 $ repeat '-')
  putStrLn (show inst)
  putStrLn (take 68 $ repeat '-')
  putStrLn (show $ mkFlagConfig rc bm size tune)

-}