packages feed

NoSlow-0.2: NoSlow/Analyse/Main.hs

{-# LANGUAGE PatternGuards #-}
module Main where

import NoSlow.Analyse.Table
import NoSlow.Util.Tag
import qualified NoSlow.Util.Opts as O

import Criterion.Measurement ( secs )
import Text.Read
import System.Console.GetOpt
import System.Environment ( getArgs, getProgName )
import Data.Version ( showVersion )
import Control.Monad ( liftM, foldM )
import Text.Printf ( printf )
import System.IO

import Paths_NoSlow

main = do
         args <- getArgs
         case args of
           arg : args' | arg == "-d" || arg == "--diff" -> doDiff args'
           _                                            -> doTable args

data Opts = Opts {
              optOutputFile :: FilePath
            , optOutput     :: FilePath -> Table -> IO ()
            , optTransTable :: Table -> Table
            }

defaultTableOpts = Opts { optOutputFile = ""
                        , optOutput     = outputHTML
                        , optTransTable = id }

type OptM = O.OptM Opts

tbl :: (Table -> Table) -> Opts -> Opts
tbl f opts@(Opts { optTransTable = g }) = opts { optTransTable = f . g }

raw :: Table -> Table
raw t = t { tShowCell = printf "%f" }

commonOptions :: [OptDescr OptM]
commonOptions = [
    Option ['b'] ["bench"]
           (O.listArg return (tbl . sel_rows) "LIST")
           "only process some benchmarks"
  , Option ['l'] ["lib"]
           (O.selArg O.matchLib (tbl . filterTags) "LIST")
           "only process the specified libraries"
  , Option ['g'] ["group"]
           (O.selArg O.matchGroup (tbl . filterTags) "LIST")
           "only process benchmarks from the specified group"
  , Option ['o'] ["output"]
           (O.reqArg (\s opt -> opt { optOutputFile = s }) "FILE")
           "store the output in this file"
  , Option [] ["raw"]
           (O.noArg $ tbl raw)
           "output the raw data"
  , Option [] ["html"]
           (O.noArg $ \opt -> opt { optOutput = outputHTML })
           "output a HTML table"
  , Option [] ["csv"]
           (O.noArg $ \opt -> opt { optOutput = outputCSV })
           "output a CSV table"
  , Option ['V'] ["version"]
           (O.helpArg printVersion)
           "output version, then exit"
  , Option ['h','?'] ["help"]
           (O.helpArg printUsage)
           "output help"
  ]
  where
    sel_rows ss = filterRows (\s -> s `elem` ss)

printVersion :: IO ()
printVersion = putStrLn (showVersion version)

printUsage :: IO ()
printUsage = do
  p <- getProgName
  putStrLn (usageInfo ("Usage: " ++ p ++ " [OPTION | FILE] ...\n" ++
                       "       " ++ p ++ " [-d | --diff] FILE1 FILE2 [OPTIONS]") 
                      commonOptions)


doTable args
  = do
      (opts, rest) <- O.parse defaultTableOpts commonOptions args
      ts <- mapM readLog $ if null rest then ["-"] else rest
      output opts $ optTransTable opts $ foldr1 union ts

doDiff args
  = do
      (opts, rest) <- O.parse defaultTableOpts commonOptions args
      case rest of
        [file1,file2] -> do
                           t1 <- readLog file1
                           t2 <- readLog file2
                           output opts $ optTransTable opts
                                       $ intersect ratio cmp t1 t2

        _ | length rest > 2 -> O.printError "too many arguments"
          | otherwise       -> O.printError "not enough arguments"
  where
    ratio d = printf "%.3f" d

    cmp d1 d2 = d1 / d2


output :: Opts -> Table -> IO ()
output o = optOutput o (optOutputFile o)

outputHTML :: FilePath -> Table -> IO ()
outputHTML file t
  = with_file file $ \h ->
    do
      hPutStrLn h "<html><body>"
      hPutStrLn h (html $ prune t)
      hPutStrLn h "</body></html>"
  where
    with_file ""   f = f stdout
    with_file file f = withFile file WriteMode f 

outputCSV :: FilePath -> Table -> IO ()
outputCSV file t
  = with_file file $ \h ->
    hPutStrLn h (csv $ prune t)
  where
    with_file ""   f = f stdout
    with_file file f = withFile file WriteMode f 

newtype Benchmark = Benchmark { unBenchmark :: (Tag, Double) }

readLog :: FilePath -> IO Table
readLog file = (table secs . proc_lines . lines) `liftM` read_file file
  where
    proc_lines (h : r)
      | h == header = map (unBenchmark . read) r
      | otherwise = error "Invalid file"

    header = "Name,Mean,MeanLB,MeanUB,Stddev,StddevLB,StddevUB"

    read_file "-"  = getContents
    read_file file = readFile file


instance Read Benchmark where
  readPrec = do
               tag <- readPrec
               comma
               mean <- readPrec
               comma
               readPrec :: ReadPrec Double -- meanlb
               comma
               readPrec :: ReadPrec Double -- meanub
               comma
               readPrec :: ReadPrec Double -- stddev
               comma
               readPrec :: ReadPrec Double -- stddevlb
               comma
               readPrec :: ReadPrec Double -- stddevub
               case reads tag of
                 [(t, "")] 
                    -> return $ Benchmark (t, mean)
    where
      comma = do
                c <- get
                if c == ',' then return () else pfail