cli: a cabal bench test that works
This commit is contained in:
		
							parent
							
								
									335deec496
								
							
						
					
					
						commit
						745f2dd788
					
				
							
								
								
									
										40000
									
								
								hledger/bench/10000x1000x10.journal
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										40000
									
								
								hledger/bench/10000x1000x10.journal
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										178
									
								
								hledger/bench/SimpleBench.hs
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										178
									
								
								hledger/bench/SimpleBench.hs
									
									
									
									
									
										Executable file
									
								
							| @ -0,0 +1,178 @@ | |||||||
|  | -- 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" | ||||||
							
								
								
									
										15
									
								
								hledger/bench/bench.hs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								hledger/bench/bench.hs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,15 @@ | |||||||
|  | import Control.Concurrent (threadDelay) | ||||||
|  | import SimpleBench        (defaultMain) | ||||||
|  | import System.Environment (withArgs) | ||||||
|  | 
 | ||||||
|  | main = do | ||||||
|  |   -- expects to be run from the parent directory, as by cabal | ||||||
|  |   withArgs [ | ||||||
|  |     "-fbench/default.bench" | ||||||
|  |    ,"dist/build/hledger/hledger" | ||||||
|  |    -- ,"-v" | ||||||
|  |    ] defaultMain | ||||||
|  | 
 | ||||||
|  |   -- a little delay to avoid truncation of final output by stack | ||||||
|  |   -- in a slow-rendering terminal, such as an emacs shell | ||||||
|  |   threadDelay 500000 | ||||||
							
								
								
									
										6
									
								
								hledger/bench/default.bench
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								hledger/bench/default.bench
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,6 @@ | |||||||
|  | # commands to be run by simplebench (executable is specified separately) | ||||||
|  | 
 | ||||||
|  | -f bench/10000x1000x10.journal print | ||||||
|  | -f bench/10000x1000x10.journal register | ||||||
|  | -f bench/10000x1000x10.journal balance | ||||||
|  | -f bench/10000x1000x10.journal stats | ||||||
| @ -201,11 +201,10 @@ test-suite test | |||||||
|     build-depends: time >= 1.5 |     build-depends: time >= 1.5 | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| -- not a standard cabal bench test, I think |  | ||||||
| benchmark bench | benchmark bench | ||||||
|   type:             exitcode-stdio-1.0 |   type:             exitcode-stdio-1.0 | ||||||
|   -- hs-source-dirs:    |   hs-source-dirs:   bench | ||||||
|   main-is:          ../tools/simplebench.hs |   main-is:          bench.hs | ||||||
|   ghc-options: -Wall -fno-warn-unused-do-bind -fno-warn-name-shadowing -fno-warn-missing-signatures |   ghc-options: -Wall -fno-warn-unused-do-bind -fno-warn-name-shadowing -fno-warn-missing-signatures | ||||||
|   ghc-options: -fno-warn-type-defaults -fno-warn-orphans |   ghc-options: -fno-warn-type-defaults -fno-warn-orphans | ||||||
|   default-language: Haskell2010 |   default-language: Haskell2010 | ||||||
|  | |||||||
| @ -1,34 +0,0 @@ | |||||||
| # tests for "make bench" |  | ||||||
| # one command per line, without the executable |  | ||||||
| 
 |  | ||||||
| -f data/100x100x10.journal     balance |  | ||||||
| -f data/1000x1000x10.journal   balance |  | ||||||
| # -f data/1000x10000x10.journal  balance |  | ||||||
| -f data/10000x1000x10.journal  balance |  | ||||||
| -f data/10000x1000x10.journal  balance aa |  | ||||||
| # -f data/10000x10000x10.journal balance |  | ||||||
| # -f data/100000x1000x10.journal balance |  | ||||||
| 
 |  | ||||||
| -f data/100x100x10.journal     register |  | ||||||
| -f data/1000x1000x10.journal   register |  | ||||||
| # -f data/1000x10000x10.journal  register |  | ||||||
| -f data/10000x1000x10.journal  register |  | ||||||
| -f data/10000x1000x10.journal  register aa |  | ||||||
| # -f data/10000x10000x10.journal register |  | ||||||
| # -f data/100000x1000x10.journal register |  | ||||||
| 
 |  | ||||||
| -f data/100x100x10.journal     print |  | ||||||
| -f data/1000x1000x10.journal   print |  | ||||||
| # -f data/1000x10000x10.journal  print |  | ||||||
| -f data/10000x1000x10.journal  print |  | ||||||
| -f data/10000x1000x10.journal  print aa |  | ||||||
| # -f data/10000x10000x10.journal print |  | ||||||
| # -f data/100000x1000x10.journal print |  | ||||||
| 
 |  | ||||||
| -f data/100x100x10.journal     stat |  | ||||||
| -f data/1000x1000x10.journal   stat |  | ||||||
| # -f data/1000x10000x10.journal  stat |  | ||||||
| -f data/10000x1000x10.journal  stat |  | ||||||
| -f data/10000x1000x10.journal  stat aa |  | ||||||
| # -f data/10000x10000x10.journal stat |  | ||||||
| # -f data/100000x1000x10.journal stat |  | ||||||
		Loading…
	
		Reference in New Issue
	
	Block a user