tools: simplebench is now quickbench
This commit is contained in:
		
							parent
							
								
									bdf8f5ca88
								
							
						
					
					
						commit
						843c417079
					
				
							
								
								
									
										21
									
								
								Makefile
									
									
									
									
									
								
							
							
						
						
									
										21
									
								
								Makefile
									
									
									
									
									
								
							| @ -13,6 +13,7 @@ | |||||||
| #
 | #
 | ||||||
| # - stack, installs dependencies and drives cabal & ghc
 | # - stack, installs dependencies and drives cabal & ghc
 | ||||||
| # - shelltestrunner (latest version from hackage or possibly git), runs functional tests
 | # - shelltestrunner (latest version from hackage or possibly git), runs functional tests
 | ||||||
|  | # - quickbench (from git), runs benchmarks
 | ||||||
| # - hasktags, generates tag files for code navigation
 | # - hasktags, generates tag files for code navigation
 | ||||||
| # - profiteur, renders profiles as interactive html
 | # - profiteur, renders profiles as interactive html
 | ||||||
| # - hpack, generates cabal files from package.yaml files
 | # - hpack, generates cabal files from package.yaml files
 | ||||||
| @ -58,9 +59,6 @@ PROFRTSFLAGS=-P | |||||||
| # COVCMD=test
 | # COVCMD=test
 | ||||||
| # COVCMD=-f test-wf.csv print
 | # COVCMD=-f test-wf.csv print
 | ||||||
| 
 | 
 | ||||||
| # executables to run during "make quickbench"
 |  | ||||||
| BENCHEXES=hledger-0.27 hledger-journalupdate hledger-parsedjournal hledger |  | ||||||
| 
 |  | ||||||
| # misc. system tools
 | # misc. system tools
 | ||||||
| BROWSE=open | BROWSE=open | ||||||
| # VIEWHTML=$(BROWSE)
 | # VIEWHTML=$(BROWSE)
 | ||||||
| @ -504,10 +502,6 @@ dev-heap-upload: | |||||||
| # 	)
 | # 	)
 | ||||||
| # 	$(GHC) tools/doctest.hs
 | # 	$(GHC) tools/doctest.hs
 | ||||||
| 
 | 
 | ||||||
| tools/simplebench: tools/simplebench.hs \ |  | ||||||
| 		$(call def-help,tools/simplebench, build the standalone generic benchmark runner. Requires libs installed by stack build --bench. ) |  | ||||||
| 	$(STACK) exec -- $(GHC) tools/simplebench.hs |  | ||||||
| 
 |  | ||||||
| # tools/criterionbench: tools/criterionbench.hs \
 | # tools/criterionbench: tools/criterionbench.hs \
 | ||||||
| # 	$(call def-help,tools/criterionbench,\
 | # 	$(call def-help,tools/criterionbench,\
 | ||||||
| # 	build the criterion-based benchmark runner. Requires criterion.\
 | # 	build the criterion-based benchmark runner. Requires criterion.\
 | ||||||
| @ -678,13 +672,14 @@ cabalfiletest: \ | |||||||
| # 		&& echo $@ PASSED) || echo $@ FAILED
 | # 		&& echo $@ PASSED) || echo $@ FAILED
 | ||||||
| # #		&& cabal upload dist/$$p-$(VERSION).tar.gz --check -v3 \
 | # #		&& cabal upload dist/$$p-$(VERSION).tar.gz --check -v3 \
 | ||||||
| 
 | 
 | ||||||
| quickbench: samplejournals bench.tests tools/simplebench \ | BENCHEXES=hledger-0.27,hledger | ||||||
|  | 
 | ||||||
|  | quickbench: samplejournals bench.sh \ | ||||||
| 	$(call def-help,quickbench,\
 | 	$(call def-help,quickbench,\
 | ||||||
| 	run simple performance benchmarks without saving results\
 | 	run simple performance benchmarks without saving results\
 | ||||||
| 	Requires some commands defined in bench.tests and some BENCHEXES defined above.\
 | 	Requires some commands defined in bench.sh\
 | ||||||
| 	) | 	) | ||||||
| 	tools/simplebench -v -fbench.tests $(BENCHEXES) | 	quickbench -v -w $(BENCHEXES) | ||||||
| 	@rm -f benchresults.* |  | ||||||
| 
 | 
 | ||||||
| # bench: samplejournals tests/bench.tests tools/simplebench \
 | # bench: samplejournals tests/bench.tests tools/simplebench \
 | ||||||
| # 	$(call def-help,bench,\
 | # 	$(call def-help,bench,\
 | ||||||
| @ -1169,9 +1164,6 @@ $(call def-help-subsection,RELEASING:) | |||||||
| # #
 | # #
 | ||||||
| # # - The .version file must be updated manually before a release.
 | # # - The .version file must be updated manually before a release.
 | ||||||
| # #
 | # #
 | ||||||
| # # - "make simplebench" depends on version numbers in BENCHEXES, these also
 |  | ||||||
| # #   must be updated manually.
 |  | ||||||
| # #
 |  | ||||||
| # # - "make" updates the version in most other places, and defines PATCHES.
 | # # - "make" updates the version in most other places, and defines PATCHES.
 | ||||||
| # #   Note "cabal build" should also do this but doesn't yet.
 | # #   Note "cabal build" should also do this but doesn't yet.
 | ||||||
| # #
 | # #
 | ||||||
| @ -1303,7 +1295,6 @@ tagrelease: \ | |||||||
| # 	$(call def-help,showreleasestats stats,\
 | # 	$(call def-help,showreleasestats stats,\
 | ||||||
| # 	show project stats useful for release notes\
 | # 	show project stats useful for release notes\
 | ||||||
| # 	)
 | # 	)
 | ||||||
| # #	simplebench
 |  | ||||||
| # #	showerrors
 | # #	showerrors
 | ||||||
| 
 | 
 | ||||||
| # FROMTAG=.
 | # FROMTAG=.
 | ||||||
|  | |||||||
| @ -1,178 +0,0 @@ | |||||||
| -- a quick librarification of tools/simplebench.hs for cabal benchmarking |  | ||||||
| 
 |  | ||||||
| -- #!/usr/bin/env runhaskell |  | ||||||
| {-  |  | ||||||
| bench.hs - simple benchmarking of command-line programs. |  | ||||||
| Requires html and tabular. |  | ||||||
| Simon Michael 2008-2015 |  | ||||||
| 
 |  | ||||||
| Example: |  | ||||||
| 
 |  | ||||||
| $ simplebench.hs --help |  | ||||||
| ... |  | ||||||
| $ cat - >bench.tests |  | ||||||
| -f sample.ledger -s balance |  | ||||||
| -f ~/.ledger -s balance |  | ||||||
| $ simplebench.hs -v hledger "ledger --no-cache" ledger |  | ||||||
| Using bench.tests |  | ||||||
| Running 2 tests 2 times in . with 3 executables at 2008-11-26 18:52:15.776357 UTC: |  | ||||||
| 1: hledger -f sample.ledger -s balance	[0.02s] |  | ||||||
| 2: hledger -f sample.ledger -s balance	[0.01s] |  | ||||||
| 1: ledger --no-cache -f sample.ledger -s balance	[0.02s] |  | ||||||
| 2: ledger --no-cache -f sample.ledger -s balance	[0.02s] |  | ||||||
| 1: ledger -f sample.ledger -s balance	[0.02s] |  | ||||||
| 2: ledger -f sample.ledger -s balance	[0.02s] |  | ||||||
| 1: hledger -f ~/.ledger -s balance	[3.56s] |  | ||||||
| 2: hledger -f ~/.ledger -s balance	[3.56s] |  | ||||||
| 1: ledger --no-cache -f ~/.ledger -s balance	[0.10s] |  | ||||||
| 2: ledger --no-cache -f ~/.ledger -s balance	[0.10s] |  | ||||||
| 1: ledger -f ~/.ledger -s balance	[0.10s] |  | ||||||
| 2: ledger -f ~/.ledger -s balance	[0.10s] |  | ||||||
| 
 |  | ||||||
| Summary (best iteration): |  | ||||||
| 
 |  | ||||||
|                             || hledger | ledger --no-cache | ledger |  | ||||||
| ============================++=========+===================+======= |  | ||||||
| -f sample.ledger -s balance ||    0.01 |              0.02 |   0.02 |  | ||||||
|     -f ~/.ledger -s balance ||    3.56 |              0.10 |   0.10 |  | ||||||
| 
 |  | ||||||
| -} |  | ||||||
| 
 |  | ||||||
| module SimpleBench |  | ||||||
| where |  | ||||||
| 
 |  | ||||||
| import Data.List |  | ||||||
| import System.Environment |  | ||||||
| -- import System.FilePath |  | ||||||
| import System.Process |  | ||||||
| import System.IO |  | ||||||
| import Text.Tabular |  | ||||||
| import qualified Text.Tabular.AsciiArt as TA |  | ||||||
| -- import qualified Text.Tabular.Html     as TH |  | ||||||
| -- import Text.Html ((+++), renderHtml, stringToHtml) |  | ||||||
| import System.Exit |  | ||||||
| import Text.Printf |  | ||||||
| import Data.Time.Clock |  | ||||||
| import Data.Time.Format () |  | ||||||
| import Control.Monad |  | ||||||
| import System.Console.GetOpt |  | ||||||
| 
 |  | ||||||
| usagehdr = "bench [-f testsfile] [-n iterations] [-p precision] executable1 [executable2 ...]\n" ++ |  | ||||||
|            "\n" ++ |  | ||||||
|            "Run some functional tests with each of the specified executables,\n" ++ |  | ||||||
|            "where a test is \"zero or more arguments supported by all executables\",\n" ++ |  | ||||||
|            "and report the best execution times.\n" |  | ||||||
|             |  | ||||||
| options = [ |  | ||||||
|   Option "f" ["testsfile"] (ReqArg File "testsfile") "file containing tests, one per line, default: bench.tests" |  | ||||||
|  ,Option "n" ["iterations"] (ReqArg Num "iterations") "number of test iterations to run, default: 1" |  | ||||||
|  ,Option "p" ["precision"] (ReqArg Prec "precision") "show times with this precision, default: 2" |  | ||||||
|  ,Option "v" ["verbose"] (NoArg Verbose) "show intermediate results" |  | ||||||
|  ,Option "h" ["help"] (NoArg Help) "show this help" |  | ||||||
|  ]              |  | ||||||
| 
 |  | ||||||
| usageftr = "\n" ++ |  | ||||||
|            "Tips:\n" ++ |  | ||||||
|            "- executables may have arguments if enclosed in quotes\n" ++ |  | ||||||
|            "- tests can be commented out with #\n" ++ |  | ||||||
|            "- results are saved in benchresults.{html,txt}\n" |  | ||||||
| 
 |  | ||||||
| usage = usageInfo usagehdr options ++ usageftr |  | ||||||
| 
 |  | ||||||
| -- an option value |  | ||||||
| data Opt = File {value::String}  |  | ||||||
|          | Num  {value::String}  |  | ||||||
|          | Prec {value::String}  |  | ||||||
| -- I don't know how optValuesForConstructor etc. can have that  |  | ||||||
| -- type signature with these, but it works.. |  | ||||||
| --       | Some Int |  | ||||||
|          | Verbose |  | ||||||
|          | Help |  | ||||||
|            deriving (Eq,Show) |  | ||||||
| 
 |  | ||||||
| -- option value getters. |  | ||||||
| fileopt :: [Opt] -> String |  | ||||||
| fileopt = optValueWithDefault File "bench.tests" |  | ||||||
| 
 |  | ||||||
| precisionopt :: [Opt] -> Int |  | ||||||
| precisionopt = read . optValueWithDefault Prec "2" |  | ||||||
| 
 |  | ||||||
| numopt :: [Opt] -> Int |  | ||||||
| numopt = read . optValueWithDefault Num "1" |  | ||||||
| 
 |  | ||||||
| verboseopt :: [Opt] -> Bool |  | ||||||
| verboseopt = (Verbose `elem`) |  | ||||||
| 
 |  | ||||||
| -- options utilities |  | ||||||
| parseargs :: [String] -> ([Opt],[String]) |  | ||||||
| parseargs as = |  | ||||||
|   case (getOpt Permute options as) of |  | ||||||
|     (opts,args,[]) -> (opts,args) |  | ||||||
|     (_,_,errs)     -> error (concat errs ++ usage) |  | ||||||
| 
 |  | ||||||
| optValueWithDefault :: (String -> Opt) -> String -> [Opt] -> String |  | ||||||
| optValueWithDefault optcons def opts =  |  | ||||||
|     last $ def : optValuesForConstructor optcons opts |  | ||||||
| 
 |  | ||||||
| optValuesForConstructor :: (String -> Opt) -> [Opt] -> [String] |  | ||||||
| optValuesForConstructor optcons opts = concatMap get opts |  | ||||||
|     where get o = [v | optcons v == o] where v = value o |  | ||||||
| 
 |  | ||||||
| defaultMain = do |  | ||||||
|   args <- getArgs |  | ||||||
|   let (opts,exes) = parseargs args |  | ||||||
|   when (null exes) $ error $ "at least one executable needed\n" ++ usage |  | ||||||
|   let (file, num) = (fileopt opts, numopt opts) |  | ||||||
|   tests <- liftM (filter istest . lines) (readFile file) |  | ||||||
|   now <- getCurrentTime |  | ||||||
|   putStrLn $ printf "Using %s" file |  | ||||||
|   putStrLn $ printf "Running %d tests %d times with %d executables at %s:"  |  | ||||||
|                (length tests) num (length exes) (show now) |  | ||||||
|   let doexe t e = mapM (doiteration opts t e) [1..num] |  | ||||||
|   let dotest t = mapM (doexe t) exes |  | ||||||
|   hSetBuffering stdout NoBuffering |  | ||||||
|   results <- mapM dotest tests |  | ||||||
|   summarise opts tests exes results  |  | ||||||
| 
 |  | ||||||
| istest s = not (null s' || ("#" `isPrefixOf` s')) where s' = clean s |  | ||||||
| clean = unwords . words |  | ||||||
| 
 |  | ||||||
| doiteration :: [Opt] -> String -> String -> Int -> IO Float |  | ||||||
| doiteration opts test exe iteration = do |  | ||||||
|   let cmd = unwords [exe,clean test] |  | ||||||
|   when (verboseopt opts) $ putStr $ show iteration ++ ": " ++ cmd |  | ||||||
|   hFlush stdout |  | ||||||
|   t <- time cmd |  | ||||||
|   when (verboseopt opts) $ printf "\t[%ss]\n" (showtime opts t) |  | ||||||
|   return t |  | ||||||
| 
 |  | ||||||
| time :: String -> IO Float |  | ||||||
| time cmd = do |  | ||||||
|   t1 <- getCurrentTime |  | ||||||
|   ret <- system $ cmd ++ " >/dev/null 2>&1" |  | ||||||
|   case ret of |  | ||||||
|     ExitSuccess -> return () |  | ||||||
|     ExitFailure f -> putStr $ printf " (error %d)" f |  | ||||||
|   t2 <- getCurrentTime |  | ||||||
|   return $ realToFrac $ diffUTCTime t2 t1 |  | ||||||
| 
 |  | ||||||
| summarise :: [Opt] -> [String] -> [String] -> [[[Float]]] -> IO () |  | ||||||
| summarise opts tests exes results = do |  | ||||||
|   putStrLn "\nSummary (best iteration):\n" |  | ||||||
|   let t = maketable opts tests exes results |  | ||||||
|   putStrLn $ TA.render id id id t |  | ||||||
|   -- let outname = "benchresults" |  | ||||||
|   -- writeFile (outname <.> "txt") $ TA.render id id id t |  | ||||||
|   -- writeFile (outname <.> "html") $ renderHtml $ TH.css TH.defaultCss +++ TH.render stringToHtml stringToHtml stringToHtml t |  | ||||||
| 
 |  | ||||||
| maketable :: [Opt] -> [String] -> [String] -> [[[Float]]] -> Table String String String |  | ||||||
| maketable opts rownames colnames results = Table rowhdrs colhdrs rows |  | ||||||
|  where |  | ||||||
|   rowhdrs = Group NoLine $ map Header $ padright rownames |  | ||||||
|   colhdrs = Group SingleLine $ map Header colnames |  | ||||||
|   rows = map (map (showtime opts . minimum)) results |  | ||||||
|   padright ss = map (printf (printf "%%-%ds" w)) ss |  | ||||||
|       where w = maximum $ map length ss |  | ||||||
| 
 |  | ||||||
| showtime :: [Opt] -> (Float -> String) |  | ||||||
| showtime opts = printf $ "%." ++ show (precisionopt opts) ++ "f" |  | ||||||
| @ -1,10 +1,10 @@ | |||||||
| -- bench | -- bench | ||||||
| -- By default, show approximate times for some standard hledger operations on a sample journal. | -- By default, show approximate times for some standard hledger operations on a sample journal. | ||||||
| -- With --criterion, show accurate times (slow). | -- With --criterion, show accurate times (slow). | ||||||
| -- With --simplebench, show approximate times for the commands in default.bench, using the first hledger executable on $PATH. | -- TODO With --quickbench, show approximate times for the commands in default.bench, using the first hledger executable on $PATH. | ||||||
| 
 | 
 | ||||||
| import Criterion.Main     (defaultMainWith, defaultConfig, bench, nfIO) | import Criterion.Main     (defaultMainWith, defaultConfig, bench, nfIO) | ||||||
| import SimpleBench        (defaultMain) | -- import QuickBench        (defaultMain) | ||||||
| import System.Directory   (getCurrentDirectory) | import System.Directory   (getCurrentDirectory) | ||||||
| import System.Environment (getArgs, withArgs) | import System.Environment (getArgs, withArgs) | ||||||
| import System.Info        (os) | import System.Info        (os) | ||||||
| @ -20,13 +20,13 @@ outputfile = "/dev/null" -- hide output of benchmarked commands (XXX unixism) | |||||||
| -- outputfile = "-" -- show output of benchmarked commands | -- outputfile = "-" -- show output of benchmarked commands | ||||||
| 
 | 
 | ||||||
| main = do | main = do | ||||||
|  -- withArgs ["--simplebench"] $ do |  -- withArgs ["--quickbench"] $ do | ||||||
|  -- withArgs ["--criterion"] $ do |  -- withArgs ["--criterion"] $ do | ||||||
|   args <- getArgs |   args <- getArgs | ||||||
|   if "--criterion" `elem` args |   if "--criterion" `elem` args | ||||||
|     then withArgs [] benchWithCriterion |     then withArgs [] benchWithCriterion | ||||||
|     else if "--simplebench" `elem` args |     else if "--quickbench" `elem` args | ||||||
|          then benchWithSimplebench |          then benchWithQuickbench | ||||||
|          else benchWithTimeit |          else benchWithTimeit | ||||||
| 
 | 
 | ||||||
| benchWithTimeit = do | benchWithTimeit = do | ||||||
| @ -57,12 +57,12 @@ benchWithCriterion = do | |||||||
|     bench ("stats")            $ nfIO $ stats    opts j |     bench ("stats")            $ nfIO $ stats    opts j | ||||||
|     ] |     ] | ||||||
| 
 | 
 | ||||||
| benchWithSimplebench = do | benchWithQuickbench = do | ||||||
|   let whichcmd = if os == "mingw32" then "where" else "which" |   let whichcmd = if os == "mingw32" then "where" else "which" | ||||||
|   exe <- init <$> readProcess whichcmd ["hledger"] "" |   exe <- init <$> readProcess whichcmd ["hledger"] "" | ||||||
|   pwd <- getCurrentDirectory |   pwd <- getCurrentDirectory | ||||||
|   printf "Benchmarking %s in %s with simplebench and shell\n" exe pwd |   printf "Benchmarking %s in %s with quickbench and shell\n" exe pwd | ||||||
|   flip withArgs SimpleBench.defaultMain [ |   flip withArgs QuickBench.defaultMain [ | ||||||
|      "-fbench/default.bench" |      "-fbench/default.bench" | ||||||
|     ,"-v" |     ,"-v" | ||||||
|     ,"hledger" |     ,"hledger" | ||||||
|  | |||||||
| @ -283,8 +283,6 @@ benchmark bench | |||||||
|   hs-source-dirs: |   hs-source-dirs: | ||||||
|       bench |       bench | ||||||
|   main-is: bench.hs |   main-is: bench.hs | ||||||
|   other-modules: |  | ||||||
|       SimpleBench |  | ||||||
|   ghc-options: -Wall -fno-warn-unused-do-bind -fno-warn-name-shadowing -fno-warn-missing-signatures -fno-warn-type-defaults -fno-warn-orphans |   ghc-options: -Wall -fno-warn-unused-do-bind -fno-warn-name-shadowing -fno-warn-missing-signatures -fno-warn-type-defaults -fno-warn-orphans | ||||||
|   default-language: Haskell2010 |   default-language: Haskell2010 | ||||||
|   build-depends: |   build-depends: | ||||||
|  | |||||||
| @ -1,173 +0,0 @@ | |||||||
| #!/usr/bin/env runhaskell |  | ||||||
| {-  |  | ||||||
| bench.hs - simple benchmarking of command-line programs. |  | ||||||
| Requires html and tabular. |  | ||||||
| Simon Michael 2008-2013 |  | ||||||
| 
 |  | ||||||
| Example: |  | ||||||
| 
 |  | ||||||
| $ simplebench.hs --help |  | ||||||
| ... |  | ||||||
| $ cat - >bench.tests |  | ||||||
| -f sample.ledger -s balance |  | ||||||
| -f ~/.ledger -s balance |  | ||||||
| $ simplebench.hs -v hledger "ledger --no-cache" ledger |  | ||||||
| Using bench.tests |  | ||||||
| Running 2 tests 2 times in . with 3 executables at 2008-11-26 18:52:15.776357 UTC: |  | ||||||
| 1: hledger -f sample.ledger -s balance	[0.02s] |  | ||||||
| 2: hledger -f sample.ledger -s balance	[0.01s] |  | ||||||
| 1: ledger --no-cache -f sample.ledger -s balance	[0.02s] |  | ||||||
| 2: ledger --no-cache -f sample.ledger -s balance	[0.02s] |  | ||||||
| 1: ledger -f sample.ledger -s balance	[0.02s] |  | ||||||
| 2: ledger -f sample.ledger -s balance	[0.02s] |  | ||||||
| 1: hledger -f ~/.ledger -s balance	[3.56s] |  | ||||||
| 2: hledger -f ~/.ledger -s balance	[3.56s] |  | ||||||
| 1: ledger --no-cache -f ~/.ledger -s balance	[0.10s] |  | ||||||
| 2: ledger --no-cache -f ~/.ledger -s balance	[0.10s] |  | ||||||
| 1: ledger -f ~/.ledger -s balance	[0.10s] |  | ||||||
| 2: ledger -f ~/.ledger -s balance	[0.10s] |  | ||||||
| 
 |  | ||||||
| Summary (best iteration): |  | ||||||
| 
 |  | ||||||
|                             || hledger | ledger --no-cache | ledger |  | ||||||
| ============================++=========+===================+======= |  | ||||||
| -f sample.ledger -s balance ||    0.01 |              0.02 |   0.02 |  | ||||||
|     -f ~/.ledger -s balance ||    3.56 |              0.10 |   0.10 |  | ||||||
| 
 |  | ||||||
| -} |  | ||||||
| 
 |  | ||||||
| import Data.List |  | ||||||
| import System.Environment |  | ||||||
| import System.FilePath |  | ||||||
| import System.Process |  | ||||||
| import System.IO |  | ||||||
| import Text.Tabular |  | ||||||
| import qualified Text.Tabular.AsciiArt as TA |  | ||||||
| import qualified Text.Tabular.Html     as TH |  | ||||||
| import Text.Html ((+++), renderHtml, stringToHtml) |  | ||||||
| import System.Exit |  | ||||||
| import Text.Printf |  | ||||||
| import Data.Time.Clock |  | ||||||
| import Data.Time.Format () |  | ||||||
| import Control.Monad |  | ||||||
| import System.Console.GetOpt |  | ||||||
| 
 |  | ||||||
| usagehdr = "bench [-f testsfile] [-n iterations] [-p precision] executable1 [executable2 ...]\n" ++ |  | ||||||
|            "\n" ++ |  | ||||||
|            "Run some functional tests with each of the specified executables,\n" ++ |  | ||||||
|            "where a test is \"zero or more arguments supported by all executables\",\n" ++ |  | ||||||
|            "and report the best execution times.\n" |  | ||||||
|             |  | ||||||
| options = [ |  | ||||||
|   Option "f" ["testsfile"] (ReqArg File "testsfile") "file containing tests, one per line, default: bench.tests" |  | ||||||
|  ,Option "n" ["iterations"] (ReqArg Num "iterations") "number of test iterations to run, default: 2" |  | ||||||
|  ,Option "p" ["precision"] (ReqArg Prec "precision") "show times with this precision, default: 2" |  | ||||||
|  ,Option "v" ["verbose"] (NoArg Verbose) "show intermediate results" |  | ||||||
|  ,Option "h" ["help"] (NoArg Help) "show this help" |  | ||||||
|  ]              |  | ||||||
| 
 |  | ||||||
| usageftr = "\n" ++ |  | ||||||
|            "Tips:\n" ++ |  | ||||||
|            "- executables may have arguments if enclosed in quotes\n" ++ |  | ||||||
|            "- tests can be commented out with #\n" ++ |  | ||||||
|            "- results are saved in benchresults.{html,txt}\n" |  | ||||||
| 
 |  | ||||||
| usage = usageInfo usagehdr options ++ usageftr |  | ||||||
| 
 |  | ||||||
| -- an option value |  | ||||||
| data Opt = File {value::String}  |  | ||||||
|          | Num  {value::String}  |  | ||||||
|          | Prec {value::String}  |  | ||||||
| -- I don't know how optValuesForConstructor etc. can have that  |  | ||||||
| -- type signature with these, but it works.. |  | ||||||
| --       | Some Int |  | ||||||
|          | Verbose |  | ||||||
|          | Help |  | ||||||
|            deriving (Eq,Show) |  | ||||||
| 
 |  | ||||||
| -- option value getters. |  | ||||||
| fileopt :: [Opt] -> String |  | ||||||
| fileopt = optValueWithDefault File "bench.tests" |  | ||||||
| 
 |  | ||||||
| precisionopt :: [Opt] -> Int |  | ||||||
| precisionopt = read . optValueWithDefault Prec "2" |  | ||||||
| 
 |  | ||||||
| numopt :: [Opt] -> Int |  | ||||||
| numopt = read . optValueWithDefault Num "2" |  | ||||||
| 
 |  | ||||||
| verboseopt :: [Opt] -> Bool |  | ||||||
| verboseopt = (Verbose `elem`) |  | ||||||
| 
 |  | ||||||
| -- options utilities |  | ||||||
| parseargs :: [String] -> ([Opt],[String]) |  | ||||||
| parseargs as = |  | ||||||
|   case (getOpt Permute options as) of |  | ||||||
|     (opts,args,[]) -> (opts,args) |  | ||||||
|     (_,_,errs)     -> error (concat errs ++ usage) |  | ||||||
| 
 |  | ||||||
| optValueWithDefault :: (String -> Opt) -> String -> [Opt] -> String |  | ||||||
| optValueWithDefault optcons def opts =  |  | ||||||
|     last $ def : optValuesForConstructor optcons opts |  | ||||||
| 
 |  | ||||||
| optValuesForConstructor :: (String -> Opt) -> [Opt] -> [String] |  | ||||||
| optValuesForConstructor optcons opts = concatMap get opts |  | ||||||
|     where get o = [v | optcons v == o] where v = value o |  | ||||||
| 
 |  | ||||||
| main = do |  | ||||||
|   args <- getArgs |  | ||||||
|   let (opts,exes) = parseargs args |  | ||||||
|   when (null exes) $ error $ "at least one executable needed\n" ++ usage |  | ||||||
|   let (file, num) = (fileopt opts, numopt opts) |  | ||||||
|   tests <- liftM (filter istest . lines) (readFile file) |  | ||||||
|   now <- getCurrentTime |  | ||||||
|   putStrLn $ printf "Using %s" file |  | ||||||
|   putStrLn $ printf "Running %d tests %d times with %d executables at %s:"  |  | ||||||
|                (length tests) num (length exes) (show now) |  | ||||||
|   let doexe t e = mapM (doiteration opts t e) [1..num] |  | ||||||
|   let dotest t = mapM (doexe t) exes |  | ||||||
|   hSetBuffering stdout NoBuffering |  | ||||||
|   results <- mapM dotest tests |  | ||||||
|   summarise opts tests exes results  |  | ||||||
| 
 |  | ||||||
| istest s = not (null s' || ("#" `isPrefixOf` s')) where s' = clean s |  | ||||||
| clean = unwords . words |  | ||||||
| 
 |  | ||||||
| doiteration :: [Opt] -> String -> String -> Int -> IO Float |  | ||||||
| doiteration opts test exe iteration = do |  | ||||||
|   let cmd = unwords [exe,clean test] |  | ||||||
|   when (verboseopt opts) $ putStr $ show iteration ++ ": " ++ cmd |  | ||||||
|   hFlush stdout |  | ||||||
|   t <- time cmd |  | ||||||
|   when (verboseopt opts) $ printf "\t[%ss]\n" (showtime opts t) |  | ||||||
|   return t |  | ||||||
| 
 |  | ||||||
| time :: String -> IO Float |  | ||||||
| time cmd = do |  | ||||||
|   t1 <- getCurrentTime |  | ||||||
|   ret <- system $ cmd ++ " >/dev/null 2>&1" |  | ||||||
|   case ret of |  | ||||||
|     ExitSuccess -> return () |  | ||||||
|     ExitFailure f -> putStr $ printf " (error %d)" f |  | ||||||
|   t2 <- getCurrentTime |  | ||||||
|   return $ realToFrac $ diffUTCTime t2 t1 |  | ||||||
| 
 |  | ||||||
| summarise :: [Opt] -> [String] -> [String] -> [[[Float]]] -> IO () |  | ||||||
| summarise opts tests exes results = do |  | ||||||
|   putStrLn "\nSummary (best iteration):\n" |  | ||||||
|   let t = maketable opts tests exes results |  | ||||||
|   putStrLn $ TA.render id id id t |  | ||||||
|   let outname = "benchresults" |  | ||||||
|   writeFile (outname <.> "txt") $ TA.render id id id t |  | ||||||
|   writeFile (outname <.> "html") $ renderHtml $ TH.css TH.defaultCss +++ TH.render stringToHtml stringToHtml stringToHtml t |  | ||||||
| 
 |  | ||||||
| maketable :: [Opt] -> [String] -> [String] -> [[[Float]]] -> Table String String String |  | ||||||
| maketable opts rownames colnames results = Table rowhdrs colhdrs rows |  | ||||||
|  where |  | ||||||
|   rowhdrs = Group NoLine $ map Header $ padright rownames |  | ||||||
|   colhdrs = Group SingleLine $ map Header colnames |  | ||||||
|   rows = map (map (showtime opts . minimum)) results |  | ||||||
|   padright ss = map (printf (printf "%%-%ds" w)) ss |  | ||||||
|       where w = maximum $ map length ss |  | ||||||
| 
 |  | ||||||
| showtime :: [Opt] -> (Float -> String) |  | ||||||
| showtime opts = printf $ "%." ++ show (precisionopt opts) ++ "f" |  | ||||||
		Loading…
	
		Reference in New Issue
	
	Block a user