packages feed

darcs-benchmark 0.1.8.3 → 0.1.9

raw patch · 25 files changed

+1665/−1406 lines, 25 filesdep +SHAdep +datetimedep +strictdep ~cmdargs

Dependencies added: SHA, datetime, strict

Dependency ranges changed: cmdargs

Files

− Benchmark.hs
@@ -1,251 +0,0 @@-module Benchmark where--import Definitions-import Shellish hiding ( run )-import Data.Char-import Data.List-import Data.List.Split ( wordsBy )-import System.Directory-import System.Environment-import System.FilePath( (</>), (<.>), splitDirectories, joinPath )-import System.IO-import System.Exit-import Text.Regex.Posix( (=~) )-import Data.Time.Clock-import Control.Monad.Error-import Control.Exception( throw )-import Control.Concurrent( forkIO )-import Control.Concurrent.Chan( newChan, writeChan, readChan, Chan )-import System.Console.CmdArgs (isLoud)-import System.Process( runInteractiveProcess, runInteractiveCommand,-                       waitForProcess )-import qualified System.IO.UTF8 as UTF8--copyTree :: FilePath -> FilePath -> IO ()-copyTree from to =-    do subs <- (\\ [".", ".."]) `fmap` getDirectoryContents from-       createDirectory to-       forM_ subs $ \item -> do-         is_dir <- doesDirectoryExist (from </> item)-         is_file <- doesFileExist (from </> item)-         when is_dir $ copyTree (from </> item) (to </> item)-         when is_file $ copyFile (from </> item) (to </> item)--reset :: Command ()-reset = do-  resetMemoryUsed-  resetTimeUsed--exec :: Benchmark a -> FilePath -> TestRepo -> Command a-exec (Idempotent _ _ cmd) darcs_path tr = do-  cd "_playground"-  verbose "cd _playground"-  cmd (darcs darcs_path) tr-exec (Destructive _ _ cmd) darcs_path tr = do-  cd "_playground"-  let cleanup = verbose "cd .. ; rm -rf _playground" >> cd ".." >> rm_rf "_playground"-  res <- cmd (darcs darcs_path) tr `catchError` \e -> (cleanup >> throw e)-  cleanup-  return res-exec (Description _) _ _ = fail "Cannot run description-only benchmark."--defaultrepo, sources :: FilePath -> FilePath-defaultrepo path = path </> "_darcs" </> "prefs" </> "defaultrepo"-sources path = path </> "_darcs" </> "prefs" </> "sources"--prepare :: String -> Command ()-prepare origrepo = do-  progress "!" >> verbose "rm -rf _playground"-  rm_rf "_playground"-  liftIO $ createDirectory "_playground"-  let playrepo = "_playground" </> "repo"-  isrepo <- liftIO $ doesDirectoryExist (origrepo </> "_darcs")-  unless isrepo $ fail $ origrepo ++ ": Not a darcs repository!"-  progress "." >> verbose ("cp -a '" ++ origrepo ++ "' '" ++ playrepo ++ "'")-  liftIO $ copyTree origrepo playrepo-  progress "." >> verbose ("# sanitize " ++ playrepo)-  wd <- pwd-  liftIO $ do writeFile (defaultrepo playrepo) (wd </> origrepo)-              removeFile (sources playrepo) `catch` \_ -> return ()--prepareIfDifferent :: String -> Command ()-prepareIfDifferent origrepo = do-  let playrepo = "_playground" </> "repo"-  exist <- test_e "_playground"-  current' <- if exist then liftIO $ readFile (defaultrepo playrepo) else return ""-  let current = reverse (dropWhile (=='\n') $ reverse current')-  wd <- pwd-  if (exist && current == wd </> origrepo)-     then progress "..." >> verbose ("# leaving " ++ playrepo ++ " alone")-     else prepare origrepo---- | Run a benchmark as many times as it takes to pass a minimum threshold---   of time or iterations (whichever comes first)---   Useful for very small benchmarks-adaptive :: Double -- ^ seconds-         -> (Int,Int) -- ^ min, max iterations-         -> Command MemTime -> Command [MemTime]-adaptive thresh (iters_min,iters_max) cmd =-   cmd >> -- just once to warm up the disk cache (eliminate a source of variance)-   go thresh iters_max []- where-  go t i acc | i <= 0 || (t <= 0 && i <= iters_enough) = return acc-  go t i acc =-   do verbose $ "# adaptive: iterations remaining: " ++ show i ++ " time remaining: " ++ show t-      mt@(MemTime _ t2) <- cmd-      go (t - t2) (i - 1) (mt : acc)-  iters_enough = iters_max - iters_min--run :: Test a -> Command (Maybe MemTimeOutput)-run test@(Test benchmark tr (TestBinary bin)) = do-  (Just `fmap` run') `catchError` \e ->-      do echo_n_err $ " error: " ++ show e-         return Nothing-  where run' = do-          progress $ bin ++ " " ++ description benchmark ++ " [" ++ trName tr ++ "]: "-          verbose $ "\n# testing; binary = " ++ bin ++ ", benchmark = " ++-                    description benchmark ++ ", repository = " ++ trName tr-          exe <- which $ bin-          darcs_path <- case exe of-                          Nothing -> canonize bin-                          Just p -> return p-          times <- adaptive 10 (3,100) . sub $ do-                     prepareIfDifferent (trPath tr)-                     liftIO (maybeVMFlush darcs_path)-                     m <- timed (exec benchmark darcs_path tr)-                     return m-          let result = mkMemTimeOutput times-              spaces = 45 - (length bin + length (description benchmark) + length (trName tr))-              tu = appropriateUnit (mtTimeMean result)-              result_str = unwords $ concatMap (\f -> f tu (Cell result)) [ formatTimeResult, formatMemoryResult, formatSampleSize ]-          liftIO $ appendResult test times-          progress $ (replicate spaces ' ') ++ result_str ++ "\n"-          verbose $ "# result: " ++ result_str-          return result--timed :: Command a -> Command MemTime-timed a = do-  resetMemoryUsed-  t1 <- liftIO $ getCurrentTime-  _ <- a-  t2 <- liftIO $ getCurrentTime-  mem <- memoryUsed-  resetMemoryUsed-  return $ MemTime (fromIntegral mem) (realToFrac $ diffUTCTime t2 t1)--darcsVersion :: String -> IO Version-darcsVersion cmd = do-       (_,outH,_,procH) <- runInteractiveCommand $ cmd ++ " --version"-       out <- strictGetContents outH-       _ <- waitForProcess procH-       return $ map read . wordsBy (== '.') . takeWhile (not . isSpace) $ out--check_darcs :: String -> IO ()-check_darcs cmd = do-       out <- darcsVersion cmd-       case out of-         2:_ -> return ()-         _ -> fail $ cmd ++ ": Not darcs 2.x binary."--verbose :: String -> Command ()-verbose m = liftIO $ do loud <- isLoud-                        when loud $ UTF8.hPutStrLn stderr m--progress :: String -> Command ()-progress m = liftIO $ do loud <- isLoud-                         unless loud $ UTF8.hPutStr stderr m--drain :: Handle -> Bool -> IO (Chan String)-drain h verb = do chan <- newChan-                  let work acc = do line <- hGetLine h-                                    when verb $ putStrLn ("## " ++ line)-                                    work (acc ++ line)-                              `catch` \_ -> writeChan chan acc-                  _ <- forkIO $ work ""-                  return chan--readFile' :: FilePath -> IO String-readFile' f = do s <- readFile f-                 length s `seq` return s--darcs :: String -> [String] -> Command String-darcs cmd args' = do-    stats_f <- liftIO $-      do tmpdir <- getTemporaryDirectory-         (f, h) <- openTempFile tmpdir "darcs-stats-XXXX"-         hClose h-         return f-    let args = args' ++ ["+RTS", "-s" ++ stats_f, "-RTS"]-    loud <- liftIO isLoud-    verbose . unwords $ cmd:args-    (res, _, stats) <- liftIO $ do-       mPlayground <- seekPlayground `fmap` getCurrentDirectory-       mEnv <- case mPlayground of-                 Nothing -> return Nothing-                 Just p  -> (Just . replace "HOME" p) `fmap` getEnvironment-       (_,outH,errH,procH) <- runInteractiveProcess cmd args Nothing mEnv-       res' <- drain outH loud-       errs' <- drain errH loud-       ex <- waitForProcess procH-       stats <- do c <- readFile' stats_f-                   removeFile stats_f `catch` \e -> hPutStrLn stderr (show e)-                   return c-                `catch` \_ -> return ""-       errs <- readChan errs'-       case ex of-         ExitSuccess -> return ()-         ExitFailure n -> fail $ "darcs failed with error code "-                            ++ show n ++ "\nsaying: " ++ errs-       res <- readChan res'-       return (res, errs, stats)-    let bytes = (stats =~ "([0-9, ]+) M[bB] total memory in use") :: String-        mem = (read (filter (`elem` "0123456789") bytes) :: Int)-    recordMemoryUsed $ mem * 1024 * 1024-    return res- where-  replace k v xs = (k,v) : filter ((/= k) . fst) xs--seekPlayground :: FilePath -> Maybe FilePath-seekPlayground dir =- if playground `elem` pieces-    then Just . joinPath . reverse . dropWhile (/= playground) . reverse $ pieces-    else Nothing- where-  pieces = splitDirectories dir-  playground = "_playground"---- ------------------------------------------------------------------------- variants--- ------------------------------------------------------------------------mkVariant :: String -> String -> Variant -> Command ()-mkVariant origrepo darcs_path v =- case vId v of-  OptimizePristineVariant -> do-    isrepo <- liftIO $ doesDirectoryExist (origrepo </> "_darcs")-    unless isrepo $ fail $ origrepo ++ ": Not a darcs repository!"-    variant_isrepo  <- liftIO $ doesDirectoryExist (variant_repo </> "_darcs")-    unless variant_isrepo $ do-      echo $ "Setting up " ++ vDescription v ++ " variant of " ++ origrepo-      verbose ("cp -a '" ++ origrepo ++ "' '" ++ variant_repo ++ "'")-      liftIO $ copyTree origrepo variant_repo-      verbose ("# sanitize " ++ variant_repo)-      liftIO $ removeFile (sources variant_repo) `catch` \_ -> return ()-      darcs darcs_path [ "optimize", "--pristine", "--repodir", variant_repo ]-      return ()-  DefaultVariant -> return ()- where-  variant_repo = variantRepoName v origrepo--variantRepoName :: Variant -> String -> String-variantRepoName (Variant { vId = DefaultVariant }) x = x-variantRepoName v x = "variant" <.> stripped x ++ "-" ++ vSuffix v- where-  stripped y | "repo."   `isPrefixOf` y = stripped (drop 5 y)-             | "-hashed" `isSuffixOf` y = take (length y -  7) y-             | otherwise = y--setupVariants :: [TestRepo] -> TestBinary -> Command ()-setupVariants repos (TestBinary bin) =-  sequence_ [ mkVariant (trPath repo) bin variant-            | repo <- repos, variant <- trVariants repo ]
− Config.hs
@@ -1,28 +0,0 @@-{-# LANGUAGE DeriveDataTypeable #-}--module Config where--import System.Console.CmdArgs--data Config = Get { repos :: [String] }-            | Run { fast  :: Bool-                  , cold  :: Bool-                  , only  :: [String]-                  , dump  :: Bool-                  , extra :: [String] }-            | Dist { repo :: FilePath }-            | Report-              deriving (Show, Data, Typeable)--defaultConfig :: [Mode Config]-defaultConfig =- [ mode $ Get { repos = []   &= args & typ "REPONAME" }- , mode $ Run { fast = False &= text "Exclude the most time-consuming benchmarks"-              , cold = False &= text "Try to flush VM caches between iterations"-              , dump = False &= text "Produce machine-readable output on stdout"-              , only =  []   &= text "Only run benchmarks with one of these substrings in their names" & typ "BENCHMARK"-              , extra = []   &= args & typ "BINARY"-              }- , mode $ Dist { repo = "" &= argPos 0 & typ "DIRECTORY" }- , mode $ Report- ]
− Definitions.hs
@@ -1,308 +0,0 @@-module Definitions where--import Control.Applicative-import Data.Array.Vector-import Data.Function-import Data.IORef-import Data.List-import Data.Maybe-import Data.Ord-import Data.Time-import Network.BSD ( HostName, getHostName )-import Statistics.Sample-import System.Directory-import System.FilePath-import System.Locale-import System.IO.Unsafe-import Text.JSON-import Text.Printf--import Shellish (Command)--type Darcs = [String] -> Command String--data Test a = Test (Benchmark a) TestRepo TestBinary deriving (Show)--data TestBinary = TestBinary String deriving (Show, Eq)--data ParamStamp = Params { pHostName :: HostName-                         , pFlush :: Maybe (FilePath -> IO ()) }-type TimeStamp = UTCTime--type Version = [Int] -- we could also use Data.Version-                     -- the difference here is just that we explicitly-                     -- do not have a notion of version tags--global :: IORef (ParamStamp, TimeStamp)-global = unsafePerformIO $ do t  <- getCurrentTime-                              hn <- getHostName-                              newIORef (Params hn Nothing, t)--maybeVMFlush :: FilePath -> IO ()-maybeVMFlush darcs = do- (p,_) <- readIORef global- case pFlush p of-  Just f  -> f darcs-  Nothing -> return ()---- ------------------------------------------------------------------------- benchmarks--- ------------------------------------------------------------------------type BenchmarkCmd a = Darcs -> TestRepo -> Command a--data BenchmarkSpeed = FastB | SlowB deriving (Eq)--data Benchmark a = Idempotent String BenchmarkSpeed (BenchmarkCmd a)-                 | Destructive String BenchmarkSpeed (BenchmarkCmd a)-                 | Description String--instance Show (Benchmark a) where-    show = description -- FIXME: is this right?--description :: Benchmark a -> String-description (Idempotent d _ _) = d-description (Destructive d _ _) = d-description (Description d) = d--speed :: Benchmark a -> BenchmarkSpeed-speed (Idempotent _ s _) = s-speed (Destructive _ s _) = s-speed (Description _) = FastB---- ------------------------------------------------------------------------- repositories--- ------------------------------------------------------------------------data TestRepo = TestRepo { trName     :: String-                         , trCoreName :: String   -- ^ (variants only) name of the orig repo-                         , trPath     :: FilePath -- ^ relative to the config file-                         , trAnnotate :: Maybe FilePath -- ^ relative to repo, eg. @Just "README"@-                         , trVariants :: [Variant]-                         }- deriving (Read, Show, Eq, Ord)--instance JSON TestRepo where-  readJSON (JSObject o) =-     TestRepo <$> jlookup "name"-              <*> jlookup "name" -- 2nd time for trCoreName-              <*> jlookup "path"-              <*> jlookupMaybe "annotate"-              <*> (map toVariant . (DefaultVariant :) <$> jlookupMaybeList "variants")-    where-     jlookup a = case lookup a (fromJSObject o) of-                   Nothing -> fail "Unable to read TestRepo"-                   Just v  -> readJSON v-     jlookupMaybe a = case lookup a (fromJSObject o) of-                        Nothing     -> return Nothing-                        Just JSNull -> return Nothing-                        Just v -> Just <$> readJSON v-     jlookupMaybeList a = case lookup a (fromJSObject o) of-                            Nothing -> return []-                            Just v  -> readJSONs v-  readJSON _ = fail "Unable to read TestRepo"-  showJSON = error "showJSON not defined for TestRepo yet"---- note that the order of the variants is reflected in the tables-data VariantName = DefaultVariant | OptimizePristineVariant- deriving (Enum, Bounded, Eq, Ord, Read, Show)--instance JSON VariantName where-  readJSON (JSString s) =-    case fromJSString s of-      "optimize-pristine" -> return OptimizePristineVariant-      x -> fail $ "Unknown variant: " ++ x-  readJSON _ = fail "Unable to VariantName"-  showJSON = error "showJSON not defined for VariantName yet"--data Variant = Variant { vId :: VariantName-                       , vShortName   :: String-                       , vDescription :: String-                       , vSuffix :: String-                       }- deriving (Eq, Ord, Show, Read)--toVariant :: VariantName -> Variant-toVariant n@DefaultVariant =-  Variant n "default"  "default (hashed)" ""-toVariant n@OptimizePristineVariant =-  Variant n "opt pris" "optimize --pristine" "op"---- | Given a name of a repo like "tabular opt pris", figure out what the---   variant was.  If there are no suffixes, like "opt pris", we assume---   it's not a variant.-nameToVariant :: String -> Variant-nameToVariant n =-  case [ v | v <- variants, vShortName v `isSuffixOf` n ] of-   []    -> toVariant DefaultVariant-   (v:_) -> v- where-  variants = sortBy (compare `on` (negate . suffixLength)) -- longest suffixes first-           $ allVariants-  suffixLength = length . vShortName--allVariants :: [Variant]-allVariants = map toVariant [minBound .. maxBound]---- | The subset of variants appropriate to the given darcs version-appropriateVariants :: Version -> [Variant] -> [Variant]-appropriateVariants v | v < [2,3,97] = filter ((/= OptimizePristineVariant) . vId)-                      | v > [2,4,96] = filter ((/= DefaultVariant) . vId)-                      | otherwise    = id---- ------------------------------------------------------------------------- long-term storage--- -------------------------------------------------------------------------- TODO: machine info? hash this?-paramStampPath :: ParamStamp -> String-paramStampPath p = intercalate "-" [ pHostName p, flushStr ]- where-  flushStr = if isJust (pFlush p) then "cold" else "warm"--timeStampPath :: TimeStamp -> String-timeStampPath t = formatTime defaultTimeLocale "%Y-%m-%dT%H%M" t -- we don't need to be super-precise here--resultsDir :: IO FilePath-resultsDir =- do home <- getHomeDirectory-    return $ home </> ".darcs-benchmark"--timingsDir :: ParamStamp -> IO FilePath-timingsDir cstmp =- do d <- resultsDir-    return $ d </> paramStampPath cstmp <.> "timings"--appendResult :: Test a -> [MemTime] -> IO ()-appendResult (Test benchmark tr (TestBinary bin)) times =- do (pstmp, tstmp) <- readIORef global-    d <- resultsDir-    createDirectoryIfMissing False d-    td <- timingsDir pstmp-    createDirectoryIfMissing False td-    appendFile (td </> timeStampPath tstmp) block- where-   block = unlines $ map (intercalate "\t" . fields) times-   fields mt = [ trName tr, bin, description benchmark ] ++ fieldMt mt-   fieldMt (MemTime m t) = [ show (fromRational m :: Float), show t ]---- ------------------------------------------------------------------------- outputs--- ------------------------------------------------------------------------precision :: Int-precision = 1--data MemTime = MemTime Rational Double deriving (Read, Show, Ord, Eq)--data MemTimeOutput = MemTimeOutput { mtTimeMean    :: Double-                                   , mtTimeDev :: Double-                                   , mtSampleSize :: Int-                                   , mtMemMean     :: Rational-                                   }- deriving (Show)--mkMemTimeOutput :: [MemTime] -> MemTimeOutput-mkMemTimeOutput xs =-  MemTimeOutput { mtTimeMean = mean time_v-                , mtTimeDev  = stdDev time_v-                , mtMemMean  = toRational (mean mem_v)-                , mtSampleSize = lengthU time_v-                }- where-  time_v = toU [ t | MemTime _ t <- xs ]-  mem_v  = toU [ fromRational m | MemTime m _ <- xs ]--data RepoTable = RepoTable { rtRepo :: String-                           , rtColumns :: [String]-                           , rtRows :: [String]-                           , rtTable :: [(TimeUnit, [Maybe MemTimeOutput])]-                           }---- Reformat (test, output) array as a square table by (core) Repository-repoTables :: [Benchmark ()] -> [(Test a, Maybe MemTimeOutput)] -> [RepoTable]-repoTables benchmarks results = [RepoTable { rtRepo = reponame (head repo)-                                           , rtColumns = columns repo-                                           , rtRows = rows-                                           , rtTable = table repo-                                           } | repo <- reposResults]- where-  reposResults = groupBy (on (==) reponame) $ sortBy repoOrder results-  reponame (Test _ tr _, _) = trCoreName tr-  repoOrder = comparing reponame-  ---  rows = map description . filter hasBenchmark $ benchmarks-  hasBenchmark b = any (\ (Test tb _ _, _) -> description tb == description b) results-  ---  columns repo = map mkColName $ columnInfos repo-  columnInfos repo = nub [ (b, trName tr) | (Test _ tr b, _) <- repo ]-  mkColName (TestBinary b, tname) =-    let v = nameToVariant tname-        prefix = case vId v of-                   DefaultVariant -> ""-                   _-> vSuffix v ++ " "-    in prefix ++ cutdown b-  cutdown d | "darcs-" `isPrefixOf` d = cutdown (drop 6 d)-            | takeExtension d == ".exe" = dropExtension d-            | otherwise = d-  ---  table repo = [(tu (tableRow row), map justMemTimeOutput $ tableRow row)-               | row <- rows]-    where-      tableRow row = [find (match row col) repo | col <- columnInfos repo]-      getTime (Just (_, Just mt)) = Just (mtTimeMean mt)-      getTime _ = Nothing-      tu row = case mapMaybe getTime row of-        [] -> Milliseconds-        xs -> appropriateUnit (minimum xs)-  match bench (binary, name) (Test bench' tr binary', _) =-      bench == description bench' && binary == binary' && trName tr == name-  justMemTimeOutput (Just (_, mt)) = mt-  justMemTimeOutput Nothing = Nothing--data TimeUnit = MinutesAndSeconds | Milliseconds--formatTimeElapsed :: TimeUnit -> Double -> String-formatTimeElapsed Milliseconds raw = formatNumber (raw * 1000) ++ "ms"-formatTimeElapsed MinutesAndSeconds raw =- case mins raw of-   Nothing -> formatNumber (secs raw) ++ "s"-   Just m  -> show m ++ "m" ++ formatNumber (secs raw) ++ "s"- where-   secs x = (fromInteger (floor x `mod` 60)) + (x - fromInteger (floor x))-   mins x | x > 60 = Just (floor x `div` 60 :: Int)-          | otherwise = Nothing--appropriateUnit :: Double -> TimeUnit-appropriateUnit s | s < 2     = Milliseconds-                  | otherwise = MinutesAndSeconds--data Cell h a = ColHeader h | Cell a | MissingCell--formatNumber :: (PrintfArg a, Fractional a) => a -> String-formatNumber = printf $ "%."++(show precision)++"f"--type Formatter = TimeUnit -> Cell String MemTimeOutput -> [String]--formatSampleSize :: Formatter-formatSampleSize _ (ColHeader h) = [ h ]-formatSampleSize _ MissingCell = [ "-" ]-formatSampleSize _ (Cell mt) = [ show (mtSampleSize mt) ++ "x" ]--formatTimeResult :: Formatter-formatTimeResult _ (ColHeader h) = [ h, "sdev" ]-formatTimeResult _ MissingCell = [ "-", "-" ]-formatTimeResult tu (Cell mt) =-  [ vouchFor $ time $ mtTimeMean mt, "(" ++ time (mtTimeDev mt) ++ ")" ]- where-   time t = formatTimeElapsed tu t-   vouchFor = case mtSampleSize mt of -- FIXME: YUCK! hard coded; cf. criterion-               sz | sz < 5    -> showChar '?'-                  | sz < 20   -> showChar '~'-                  | otherwise -> id--formatMemoryResult :: Formatter-formatMemoryResult _ (ColHeader s) = [ s ]-formatMemoryResult _ MissingCell = [ "-" ]-formatMemoryResult _ (Cell mt) =-  [ formatNumber ((realToFrac (mtMemMean mt / (1024*1024))) :: Float) ++ "M" ]
− Dist.hs
@@ -1,15 +0,0 @@-module Dist where--import qualified Codec.Archive.Tar as Tar-import Codec.Compression.GZip( compress )-import qualified Data.ByteString.Lazy as BL-import Data.List ( isPrefixOf )-import System.FilePath--createTarball :: FilePath -> IO ()-createTarball dir = do-  str <- (compress . Tar.write) `fmap` Tar.pack dir [ "." ]-  let tarball = dropPrefix "repo." (takeBaseName dir) <.> "tgz"-  BL.writeFile tarball str- where-  dropPrefix p x = if p `isPrefixOf` x then drop (length p) x else x
− Download.hs
@@ -1,53 +0,0 @@-module Download where--import Network.Browser-import Network.URI-import Network.HTTP.Base--import qualified Codec.Archive.Tar as Tar ( read, unpack )-import System.FilePath-import System.Directory-import Codec.Compression.GZip( decompress )-import Control.Monad--baseurl :: String-baseurl = "http://code.haskell.org/darcs/benchmark-repos/"--exists :: FilePath -> IO Bool-exists f =- do exists_file <- doesFileExist f-    exists_dir  <- doesDirectoryExist f-    return (exists_file || exists_dir)--download :: String -> IO ()-download repo = do-  let Just repo_url   = parseURI $ baseurl  ++ repo ++ ".tgz"-      Just config_url = parseURI $ baseurl  ++ "config" <.> repo-      config_file = "config" <.> repo-  putStrLn $ "downloading and extracting: " ++ show repo_url-  repo_exists   <- exists ("repo" <.> repo)-  config_exists <- exists ("config" <.> repo)-  let go = do rsp <- fetch repo_url-              when (rspCode rsp /= (2, 0, 0)) $-                   fail ("download failed: " ++ rspReason rsp)-              createDirectory $ "repo" <.> repo-              let bits = rspBody rsp-                  entries = Tar.read $ decompress bits-              Tar.unpack ("repo" <.> repo) entries-      go_cfg = do rsp <- fetch config_url-                  case rspCode rsp of-                    (4, 0, 4) -> putStrLn $ "No config file detected for " ++ repo ++ " and that's fine!"-                    (2, 0, 0) -> writeFile ("config" <.> repo) (rspBody rsp)-                    _         -> fail ("download failed: " ++ rspReason rsp)-  if repo_exists-       then putStrLn $ "repo" <.> repo ++ " already exists, fetching config file only."-       else go-  if config_exists-       then putStrLn $ config_file ++ " already exists, skipping!"-       else go_cfg- where-  fetch url =-    do (_, rsp) <- browse $ do setCheckForProxy True-                               setOutHandler (const $ return ())-                               request (mkRequest GET url)-       return rsp
− Graph.hs
@@ -1,54 +0,0 @@-module Graph where--import Data.List-import Graphics.GChart--import Definitions--graphRepoMemory :: RepoTable -> [String]-graphRepoMemory repo = map graphUrl rows- where-  rows = [(title rowname, rtColumns repo, graphdata rowdata)-         | (rowname, (_, rowdata)) <- zip (rtRows repo) (rtTable repo)]-  title rowname = rowname ++ " (MiB)"-  graphdata rowdata = map selectMemory rowdata-  selectMemory (Just mt) = fromRational (mtMemMean mt) / (1024 * 1024)-  selectMemory _ = 0.0--graphRepoTime :: RepoTable -> [String]-graphRepoTime repo = map graphUrl rows- where-  rows = [(title tu rowname, rtColumns repo, graphdata tu rowdata)-         | (rowname, (tu, rowdata)) <- zip (rtRows repo) (rtTable repo)]-  title Milliseconds rowname = rowname ++ " (ms)"-  title MinutesAndSeconds rowname = rowname ++ " (s)"-  graphdata tu rowdata = map (selectTime tu) rowdata-  selectTime Milliseconds (Just mt) = mtTimeMean mt * 1000-  selectTime MinutesAndSeconds (Just mt) = mtTimeMean mt-  selectTime _ _ = 0.0  --graphUrl :: (String, [String], [Double]) -> String-graphUrl (title, labels, results) = getChartUrl $ do-  setChartSize 200 200-  setDataEncoding simple-  setChartType BarVerticalGrouped-  setBarWidthSpacing $ barwidthspacing 23 5 20-  setChartTitle title-  addAxis $ makeAxis { axisType = AxisBottom, axisLabels = Just labels }-  addAxis $ makeAxis { axisType = AxisLeft,-    axisRange = Just $ Range (0, realToFrac range) Nothing }-  setColors palette-  addChartData row- where-  range = case results of-    [] -> 1-    xs -> maximum xs-  row = [truncate (result * 61.0 / range) | result <- results] :: [Int]--- Color palette for bars, based on the Tango palette (Butter, Orange, Choc)-  -- TODO: Adjust palette size based on actual number of results?-  palette = [intercalate "|" ["fce94f"-                            , "c4a000"-                            , "fcaf3e"-                            , "ce5c00"-                            , "e9b96e"-                            , "8f5902"]]
− Report.hs
@@ -1,168 +0,0 @@-module Report where--import Control.Arrow-import Control.Monad.Error-import Data.Function-import Data.List-import Data.List.Split-import Data.Maybe-import qualified Data.Map as Map-import qualified Text.Tabular          as Tab-import System.Directory-import System.FilePath-import System.IO--import Definitions-import Graph-import Shellish hiding ( run )-import Standard-import TabularRST as TR---- ------------------------------------------------------------------------- tables--- ------------------------------------------------------------------------type BenchmarkTable = Tab.Table String String String--tabulateRepo :: Formatter -> RepoTable -> Tab.Table String String String-tabulateRepo format repo = Tab.Table rowhdrs colhdrs rows- where-  rowhdrs = Tab.Group Tab.NoLine $ map Tab.Header (rtRows repo)-  colhdrs = Tab.Group Tab.SingleLine $ map Tab.Header $-              concatMap (format myundefined . ColHeader) $ rtColumns repo-  myundefined = error "Formatting is undefined for column headers"-  rows = map formatRow $ rtTable repo-  formatRow (tu, rs) = concatMap (fmt tu) rs-  fmt tu (Just mt) = format tu (Cell mt)-  fmt tu Nothing = format tu MissingCell---- ------------------------------------------------------------------------- timings files--- ------------------------------------------------------------------------readAllTimings :: IO [[(Test a, Maybe MemTimeOutput)]]-readAllTimings = do-  rdir  <- resultsDir-  tdirs <- filter isTimingFile `fmap` getDirectoryContents rdir-  let pstamps = map dropExtension tdirs-  mapM readTimingsForParams pstamps- where-  isTimingFile f = takeExtension f == ".timings"--readTimingsForParams :: String -> IO [(Test a, Maybe MemTimeOutput)]-readTimingsForParams pstamp = do-  rdir  <- resultsDir-  let pdir = rdir </> pstamp <.> "timings"-  -- let ifile = replaceExtension ".timings" ".info" pdir-  tfiles   <- filter notJunk `fmap` getDirectoryContents pdir-  entries  <- concat `fmap` mapM parseTimingsFile (map (pdir </>) tfiles)-  return . map process . Map.toList . Map.fromListWith (++) . map (second (:[])) $ entries- where-  notJunk = not . (`elem` [".",".."])--process :: ((String, String, String), [MemTime]) -> (Test a, Maybe MemTimeOutput)-process ((repo, dbin, bm), times) = (key, val)- where-   key = Test (Description bm) (mkTr repo) (TestBinary dbin)-   val = Just $ mkMemTimeOutput times-   mkTr n = TestRepo n (guessCoreName n) n Nothing []--guessCoreName :: String -> String-guessCoreName n =-  case [ n `chop` (' ':s) | s <- suffixes, s `isSuffixOf` n ] of-   []    -> n-   (h:_) -> h- where-  x `chop` s = take (length x - length s) x-  suffixes   = sortBy (compare `on` (negate . length)) -- longest suffixes first-             $ map vShortName allVariants--type TimingsFileEntry = ((String,String,String),MemTime)--parseTimingsFile :: FilePath -> IO [TimingsFileEntry]-parseTimingsFile tf = do-  ms <- (map parseLine . lines) `fmap` readFile tf-  let unknowns = length $ filter isNothing ms-  when (unknowns > 0) $-    hPutStrLn stderr $ "Warning: could not understand " ++ show unknowns ++ " lines in " ++ tf-  return (catMaybes ms)--parseLine :: String -> Maybe TimingsFileEntry-parseLine l =- case wordsBy (== '\t') l of-  [ repo, dbin, bm, mem, time ] -> Just ((repo, dbin, bm), memtime time mem)-  _ -> Nothing- where-  memtime t m = MemTime (toRational (read m :: Float)) (read t)---- ------------------------------------------------------------------------- reports--- ------------------------------------------------------------------------renderMany :: [(Test a, Maybe MemTimeOutput)] -> Command ()-renderMany results  = do-  echo . unlines $-   [ "Copy and paste below"-   , "====================================================="-   , ""-   , machine_details-   , ""-   , "How to read these tables"-   , "====================================================="-   , ""-   , def "?x" "less than  5 runs used"-   , def "~x" "less than 20 runs used"-   , def "sdev" "std deviation"-   , descriptions_of_variants-   , ""-   , "Timings"-   , "===================================================="-   , ""-   , intercalate "\n" (map showT t_tables)-   , "Memory"-   , "===================================================="-   , ""-   , intercalate "\n" (map showT m_tables)-   , "Timing Graphs"-   , "===================================================="-   , ""-   , intercalate "\n" (map showG t_graphs)-   , "Memory Graphs"-   , "===================================================="-   , ""-   , intercalate "\n" (map showG m_graphs)-   ]- where-  tables = repoTables benchmarks results-  ---  machine_details = intercalate "\n" $-    map detail [ "GHC version"-               , "Machine description", "Year", "CPU", "Memory", "Hard disk"-               , "Notes" ]-  detail k = k ++ "\n    *Replace Me*"-  ---  descriptions_of_variants = intercalate "\n" $-    map (describe . toVariant) [ OptimizePristineVariant ]-  describe v = def (vSuffix v) (vDescription v ++ " variant")-  def k v = "* " ++ k ++ " = " ++ v-  ---  repoTuple tabulate repo = (rtRepo repo, tabulate repo)-  t_tables = map (repoTuple $ tabulateRepo formatTimeResult) tables-  m_tables = map (repoTuple $ tabulateRepo formatMemoryResult) tables-  showT (r,t) = intercalate "\n" [ r-                                 , replicate (length r) '-'-                                 , ""-                                 , TR.render id id id t ]-  ---  t_graphs = map (repoTuple graphRepoTime) tables-  m_graphs = map (repoTuple graphRepoMemory) tables-  showG (r,gs) = intercalate "\n" $ [ r-                                    , replicate (length r) '-'-                                    , ""-                                    ] ++ (map imgDirective gs) ++ [""]-  imgDirective = (".. image:: " ++)--printCumulativeReport :: Command ()-printCumulativeReport = do-  ts <- liftIO readAllTimings-  mapM_ renderMany ts -- TODO: split this into sections for each param stamp
− Run.hs
@@ -1,35 +0,0 @@-module Run where--import Control.Monad.Error--import Benchmark-import Definitions-import Report-import Shellish hiding ( run )-import Standard-import qualified TabularRST as TR--benchMany :: [TestRepo] -> [TestBinary] -> [Benchmark a] -> Command [(Test a, Maybe MemTimeOutput)]-benchMany repos bins benches = do-  binsVers <- liftIO $ forM bins $-    \bin@(TestBinary b) -> do v <- darcsVersion b-                              return (bin, v)-  fmap concat $ forM repos $ \r -> do-    res <- sequence-             [ do let test = Test bench repo bin-                  memtime <- run test-                  return (test, memtime)-               | (bin,ver) <- binsVers, repo <- repoAndVariants ver r, bench <- benches ]-    let tables = repoTables benchmarks res-    if length tables == 1 then-      echo_n $ TR.render id id id $ tabulateRepo formatTimeResult (head tables)-      else error "Not expecting more than one table for a repo and its variants"-    return res- where-  repoAndVariants v r = map (r `tweakVia`) (appropriateVariants v (trVariants r))-  tweakVia tr v =-   case vId v of-     DefaultVariant -> tr-     _ -> tr { trPath = variantRepoName v (trPath tr)-             , trName = trName tr ++ " " ++ vShortName v-             }
− Shellish.hs
@@ -1,166 +0,0 @@-module Shellish( cd, pwd, run, (#), withCurrentDirectory, strictGetContents,-                 shellish, silently, verbosely, Command, sub, echo, echo_n,-                 which, canonize, resetMemoryUsed, recordMemoryUsed,-                 memoryUsed, rm_rf, rm_f, whenM, test_e, test_f, test_d,-                 resetTimeUsed, timeUsed, mv, ls, echo_err, echo_n_err )-    where--import Data.List-import System.IO-import System.Directory-import System.Exit-import Control.Monad.State-import Control.Monad.Error-import Data.Time.Clock( getCurrentTime, diffUTCTime, UTCTime(..) )-import Control.Exception ( bracket, evaluate )-import qualified Data.ByteString.Char8 as B-import System.Process( runInteractiveProcess, waitForProcess )-import qualified System.IO.UTF8 as UTF8----- TODO maxMem is sort of a layering violation here... but who cares.-data St = St { sCode :: Int, sStderr :: B.ByteString , sStdout :: B.ByteString,-               sVerbose :: Bool, maxMem :: Int, startTime :: UTCTime }--type Command a = StateT St IO a--cd :: String -> Command ()-cd = liftIO . setCurrentDirectory--mv :: String -> String -> Command ()-mv a b = liftIO $ renameFile a b--ls :: String -> Command [String]-ls dir = liftIO $ (\\ [".", ".."]) `fmap` getDirectoryContents dir--pwd :: Command String-pwd = liftIO $ getCurrentDirectory--echo, echo_n, echo_err, echo_n_err :: String -> Command ()-echo = liftIO . UTF8.putStrLn-echo_n = liftIO . (>> hFlush System.IO.stdout) . UTF8.putStr-echo_err = liftIO . UTF8.hPutStrLn stderr-echo_n_err = liftIO . (>> hFlush stderr) . UTF8.hPutStr stderr--which :: String -> Command (Maybe String)-which = liftIO . findExecutable--canonize :: String -> Command String-canonize = liftIO . canonicalizePath--whenM :: Monad m => m Bool -> m () -> m ()-whenM c a = do res <- c-               when res a--test_e :: String -> Command Bool-test_e f = liftIO $ do-             dir <- doesDirectoryExist f-             file <- doesFileExist f-             return $ file || dir--test_f :: String -> Command Bool-test_f = liftIO . doesFileExist--test_d :: String -> Command Bool-test_d = liftIO . doesDirectoryExist--rm_rf :: String -> Command ()-rm_rf f = whenM (test_e f) $ liftIO $ removeDirectoryRecursive f--rm_f :: String -> Command ()-rm_f f = whenM (test_e f) $ liftIO $ removeFile f--run :: String -> [String] -> Command String-run cmd args = do-    (_,outH,errH,procH) <- liftIO $ runInteractiveProcess cmd args Nothing Nothing-    st <- get-    res <- liftIO $ B.hGetContents outH-    errs <- liftIO $ B.hGetContents errH-    ex <- liftIO $ waitForProcess procH-    when (sVerbose st) $ do-                 liftIO $ B.putStr res-                 liftIO $ B.putStr errs-    case ex of-      ExitSuccess -> return ()-      ExitFailure n -> fail $ "command " ++ cmd ++ " " ++ show args-                         ++ " failed with exit code " ++ show n-    put $ st { sCode = 0, sStderr = errs, sStdout = res }-    return $ B.unpack res--(#) :: String -> [String] -> Command String-cmd # args = run cmd args--silently :: Command a -> Command a-silently a = do-  x <- get-  put $ x { sVerbose = False }-  r <- a-  put x-  return r--verbosely :: Command a -> Command a-verbosely a = do-  x <- get-  put $ x { sVerbose = True }-  r <- a-  put x-  return r--sub :: Command a -> Command a-sub a = do-  -- TODO save environment as well?-  dir <- liftIO $ getCurrentDirectory-  r <- a `catchError` (\e -> (liftIO $ setCurrentDirectory dir) >> throwError e)-  liftIO $ setCurrentDirectory dir-  return r--shellish :: MonadIO m => Command a -> m a-shellish a = do-  dir <- liftIO $ getCurrentDirectory-  time <- liftIO getCurrentTime-  r <- liftIO $ evalStateT a $ empty time-  liftIO $ setCurrentDirectory dir-  return r-      where empty t = St { sCode = 0, sStderr = B.empty,-                           sStdout = B.empty, sVerbose = True,-                           maxMem = 0, startTime = t }--strictGetContents :: Handle -> IO String-strictGetContents h =-    do res <- hGetContents h-       evaluate (length res)-       return res--withCurrentDirectory :: FilePath -> IO a -> IO a-withCurrentDirectory name m =-    bracket-        (do cwd <- getCurrentDirectory-            when (name /= "") (setCurrentDirectory name)-            return cwd)-        (\oldwd -> setCurrentDirectory oldwd `catch` (\_ -> return ()))-        (const m)--recordMemoryUsed :: Int -> Command ()-recordMemoryUsed u = do-  x <- get-  put x { maxMem = maximum [maxMem x, u] }--resetMemoryUsed :: Command ()-resetMemoryUsed = do-  x <- get-  put x { maxMem = 0 }--resetTimeUsed :: Command ()-resetTimeUsed = do-  x <- get-  t <- liftIO getCurrentTime-  put x { startTime = t }--memoryUsed :: Command Int-memoryUsed = do x <- get-                return $ maxMem x--timeUsed :: Command Float-timeUsed = do x <- get-              t <- liftIO getCurrentTime-              return $ realToFrac $ diffUTCTime t (startTime x)
− Standard.hs
@@ -1,118 +0,0 @@-module Standard( benchmarks ) where-import System.FilePath-import Shellish-import Benchmark hiding ( darcs )-import Definitions-import Control.Monad( forM_, filterM, when )-import Control.Monad.Trans( liftIO )-import qualified Control.Monad.State as MS--check :: BenchmarkCmd ()-check darcs _ = do-  cd "repo"-  darcs [ "check", "--no-test" ]-  return ()--repair :: BenchmarkCmd ()-repair darcs _ = do-  cd "repo"-  darcs [ "repair" ]-  return ()--annotate :: BenchmarkCmd ()-annotate darcs tr = do-  cd "repo"-  whenM ((not . or) `fmap` mapM test_e files) $ fail "no files to annotate"-  sequence_ [ whenM (test_e f) $ (darcs [ "annotate", f ] >> return ())-              | f <- files ]-  return ()-    where files = maybe id (:) (trAnnotate tr) [ "Setup.hs", "Setup.lhs" ]--get :: [String] -> BenchmarkCmd ()-get param darcs _ = do-  darcs $ "get" : param ++ ["repo", "get"]-  return ()--pull :: Int -> BenchmarkCmd ()-pull n darcs _ = do-  cd "repo"-  rm_f "_darcs/patches/unrevert"-  darcs [ "unpull", "--last", show n, "--all" ]-  reset -- the benchmark starts here-  darcs [ "pull", "--all" ]-  return ()---- Oh my eyes! Oh noes! Horrible!-darcs_wh :: [String] -> BenchmarkCmd ()-darcs_wh param darcs _ = do-  state <- MS.get-  newstate <- liftIO $ catch (MS.execStateT (darcs $ "whatsnew" : param) state)-                             (\_ -> return state)-  MS.put newstate--wh :: BenchmarkCmd ()-wh darcs tr = do-  cd "repo"-  darcs_wh [] darcs tr-  return ()--wh_mod :: BenchmarkCmd ()-wh_mod darcs tr = do-  cd "repo"-  files <- filterM test_f =<< ls "."-  when (null files) $ fail "no files to modify in repo root!"-  forM_ files $ \f -> mv f $ f <.> "__foo__"-  darcs_wh [] darcs tr-  forM_ files $ \f -> mv (f <.> "__foo__") f-  return ()--wh_l :: BenchmarkCmd ()-wh_l darcs tr = do-  cd "repo"-  darcs_wh [ "--look-for-adds" ] darcs tr-  return ()---- | n patches for each file-record_mod :: BenchmarkCmd ()-record_mod darcs _ = do- cd "repo"- files <- filterM test_f =<< ls "."- forM_ files $ \f -> liftIO (appendFile f "x")- darcs [ "record", "-A", "me", "--all", "-m", "test record", "--no-test"]- darcs [ "obliterate", "--last=1", "--all" ]- return ()--revert_mod :: BenchmarkCmd ()-revert_mod darcs _ = do- cd "repo"- files <- filterM test_f =<< ls "."- forM_ files $ \f -> liftIO (appendFile f "foo")- darcs [ "revert", "--all" ]- return ()--revert_unrevert :: BenchmarkCmd ()-revert_unrevert darcs _ = do- cd "repo"- files <- filterM test_f =<< ls "."- forM_ files $ \f -> liftIO (appendFile f (show "foo"))- darcs [ "revert", "--all" ]- darcs [ "unrevert", "--all" ]- darcs [ "revert", "--all" ]- return ()--benchmarks :: [ Benchmark () ]-benchmarks =-       [ Idempotent "wh" FastB wh-       , Idempotent "wh mod" FastB wh_mod-       , Idempotent "wh -l" FastB wh_l-       , Idempotent "record mod" FastB $ record_mod-       , Idempotent "revert mod" FastB revert_mod-       , Idempotent "(un)revert mod" FastB revert_unrevert-       , Destructive "get (full)" SlowB $ get []-       , Destructive "get (lazy)" FastB $ get ["--lazy"]-       , Idempotent "pull 100"    FastB $ pull 100-       , Idempotent "pull 1000" SlowB $ pull 1000-       , Idempotent "check" SlowB check-       , Idempotent "repair" SlowB repair-       , Idempotent "annotate" SlowB annotate-       ]
− TabularRST.hs
@@ -1,70 +0,0 @@-module TabularRST where--import Data.List (intersperse, transpose)-import Text.Tabular---- RST renderer for tabular--- (being incubated; when this matures, it should become part of tabular)---- | for simplicity, we assume that each cell is rendered---   on a single line-render :: (rh -> String)-       -> (ch -> String)-       -> (a -> String)-       -> Table rh ch a-       -> String-render fr fc f (Table rh ch cells) =-  unlines $ [ bar DoubleLine-            , renderColumns sizes ch2-            , bar DoubleLine-            ] ++-            (renderRs $ fmap renderR $ zipHeader [] cells $ fmap fr rh) ++-            [ bar DoubleLine, "" ] -- note the extra blank line- where-  bar = concat . renderTHLine sizes ch2-  renderTHLine _ _ NoLine = []-  renderTHLine w h SingleLine = [renderHLine' w '-' h]-  renderTHLine w h DoubleLine = [renderHLine' w '=' h]-  -- ch2 and cell2 include the row and column labels-  ch2 = Group DoubleLine [Header "", fmap fc ch]-  cells2 = headerContents ch2-         : zipWith (\h cs -> h : map f cs) rhStrings cells-  ---  renderR (cs,h) = renderColumns sizes $ Group DoubleLine-                    [ Header h-                    , fmap fst $ zipHeader "" (map f cs) ch]-  rhStrings = map fr $ headerContents rh-  -- maximum width for each column-  sizes   = map (maximum . map length) . transpose $ cells2-  renderRs (Header s)   = [s]-  renderRs (Group p hs) = concat . intersperse sep . map renderRs $ hs-    where sep = renderHLine sizes ch2 p---- | We stop rendering on the shortest list!-renderColumns :: [Int] -- ^ max width for each column-              -> Header String-              -> String-renderColumns is h = coreLine- where-  coreLine = concatMap helper $ flattenHeader $ zipHeader 0 is h-  helper = either hsep (uncurry padLeft)-  hsep :: Properties -> String-  hsep _ = "  "--renderHLine :: [Int] -- ^ width specifications-            -> Header String-            -> Properties-            -> [String]-renderHLine _ _ _ = []--renderHLine' :: [Int] -> Char -> Header String -> String-renderHLine' is sep h = coreLine- where-  coreLine        = concatMap helper $ flattenHeader $ zipHeader 0 is h-  helper          = either vsep dashes-  dashes (i,_)    = replicate i sep-  vsep _          = "  "--padLeft :: Int -> String -> String-padLeft l s = padding ++ s- where padding = replicate (l - length s) ' '
darcs-benchmark.cabal view
@@ -1,5 +1,5 @@ name:          darcs-benchmark-version:       0.1.8.3+version:       0.1.9 synopsis:      Comparative benchmark suite for darcs.  description: A simple tool to compare performance of different Darcs 2.x@@ -22,12 +22,11 @@ executable darcs-benchmark     if impl(ghc >= 6.8)       ghc-options: -fwarn-tabs--    ghc-options:   -Wall -threaded -fno-warn-unused-do-bind+    ghc-options:   -Wall -threaded     ghc-prof-options: -prof -auto-all -threaded -    build-depends: base < 5,-                   cmdargs >= 0.1 && < 0.2,+    build-depends: base < 5, strict,+                   cmdargs,                    process, mtl, tabular >= 0.2.2.1,                    time, old-locale,                    regex-posix, html, filepath, directory,@@ -39,9 +38,10 @@                    split == 0.1.*,                    utf8-string == 0.3.*,                    hs-gchart,-                   tar, zlib+                   tar, zlib, SHA, datetime      main-is: main.hs+    hs-source-dirs: source     other-modules: Shellish                    Benchmark                    Config
− main.hs
@@ -1,134 +0,0 @@-#!/usr/bin/env runhaskell-import Shellish( shellish, rm_rf )-import Control.Arrow ( first, second )-import System.Console.CmdArgs-import System.Cmd ( system )-import System.Exit-import System.Directory-import System.IO-import Control.Monad-import Control.Monad.Trans-import Benchmark-import Config (Config(Get,Run,Dist,Report))-import Definitions-import qualified Config as C-import Report-import Run-import Standard-import Dist ( createTarball )-import Download-import Data.List-import Data.Version ( showVersion )-import Paths_darcs_benchmark-import Text.JSON-import Data.IORef--help :: String-help = unlines [ "darcs-benchmark " ++ showVersion version ++ ": run standard darcs benchmarks"-               , ""-               , "Please either specify the repositories and binaries to run on like this:"-               , "$ darcs-benchmark run <binary> [binary] [/ [repository] [repository]]"-               , ""-               , "or alternatively, to run on all available repos:"-               , "$ darcs-benchmark run <binary> [binary]"-               , ""-               , "You can also create a file called 'config' in the working directory."-               , "Put two lines in it, one with list of binaries, one with list of repos:"-               , ""-               , "binary binary binary"-               , "repo repo repo"-               , ""-               , "(again, if the second line is not there, we run on all available repos)"-               , ""-               , "Thank you for benchmarking darcs!" ]--known_repos :: [String]-known_repos = [ "tabular", "tahoe-lafs", "darcs", "ghc-hashed" ]--download_repos :: [String] -> IO ()-download_repos r = forM_ (if null r then known_repos else r) download--doVMFlush :: FilePath -> IO ()-doVMFlush path = do system "sudo sh -c 'echo 3 > /proc/sys/vm/drop_caches'"-                    system $ path ++ " --version > /dev/null"-                    return ()--config :: [TestRepo] -> C.Config -> IO ([TestRepo], [TestBinary], [Benchmark ()])-config allrepos cfg = do-  case cfg of-   Get {} -> do-      download_repos (C.repos cfg)-      exitWith ExitSuccess-   Dist {} -> do-      createTarball (C.repo cfg)-      exitWith ExitSuccess-   Report {} ->  do-      shellish printCumulativeReport-      exitWith ExitSuccess-   Run {} -> do-     haveConf <- doesFileExist "config"-     conf <- if haveConf then lines `fmap` readFile "config"-                         else return []-     let confrepos = if length conf > 1 then (words $ conf !! 1) else []-         confbins = if length conf > 0 then words $ conf !! 0 else []-         (bins,repos) = second (drop 1) $ break (== "/") (C.extra cfg)-         userepos = if null repos then confrepos else repos-         usebins = map TestBinary $ if null bins then confbins else bins-         usetests' = if C.fast cfg then filter (\b -> speed b == FastB) benchmarks else benchmarks-         usetests = case C.only cfg of-                     [] -> usetests'-                     os -> filter (\b -> any (\o -> o `isInfixOf` show b) os) usetests'-     let setParams p = p { pFlush = if C.cold cfg then Just doVMFlush else Nothing }-     modifyIORef global (first setParams)-     when (null usebins) $ do-       hPutStrLn stderr "Please specify at least one darcs binary to benchmark"-       exitWith (ExitFailure 1)-     return ( if null userepos then allrepos-                               else filter (\r -> any (matchRepo r) userepos) allrepos-            , usebins-            , usetests-            )- where-  dropPrefix p x = if p `isPrefixOf` x then drop (length p) x else x-  matchRepo tr x = trName tr == x || dropPrefix "repo." (trPath tr) == x--testRepoFromDir :: String -> TestRepo-testRepoFromDir d = TestRepo n n d Nothing [toVariant DefaultVariant]- where-  n = drop (length "repo.") d--main :: IO ()-main = do-    cfg <- cmdArgs help C.defaultConfig-    allrepos <- do listing <- getDirectoryContents "."-                   configs <- mapM readC $ filter ("config." `isPrefixOf`) listing-                   let other = [ testRepoFromDir d | d <- listing-                                                   , "repo." `isPrefixOf` d-                                                   , d `notElem` map trPath configs ]-                   return (configs ++ other)-    (repos, binaries, tests) <- config allrepos cfg-    unless (null $ repos \\ allrepos) $ do-         let name r = intercalate ", " $ map trName r-         putStrLn $ "Missing repositories: " ++ name (repos \\ allrepos)-         exitWith $ ExitFailure 2-    forM_ binaries $ \(TestBinary bin) -> check_darcs bin-    when (null repos) $ do-         putStrLn $ "Oops, no repositories! Consider doing a darcs-benchmark get."-         putStrLn $ "(Alternatively, check that you are in the right directory.)"-         exitWith $ ExitFailure 3-    shellish $ do rm_rf "_playground"-                  -- for setting up variants, we probably want the latest/greatest-                  -- version of darcs in case the variant involves some new feature-                  case reverse binaries of-                    []    -> return ()-                    (b:_) -> setupVariants repos b-                  res <- benchMany repos binaries tests-                  if C.dump cfg-                     then liftIO $ print res-                     else renderMany res- where-  readC f =-   do mj <- (resultToEither . decode) `fmap` readFile f-      case mj of-        Left e  -> fail $ "Could not read " ++ f ++ ": " ++ show e-        Right j -> return $ j
+ source/Benchmark.hs view
@@ -0,0 +1,369 @@+{-# LANGUAGE DeriveDataTypeable, ScopedTypeVariables #-}+module Benchmark where++import Prelude hiding ( readFile, catch )+import Definitions+import Shellish hiding ( run )+import Data.Typeable( Typeable )+import Data.Char+import Data.List+import Data.Maybe+import Data.DateTime( parseDateTime, startOfTime )+import Data.List.Split ( splitOn )+import System.Directory+import System.Environment+import System.FilePath( (</>), (<.>), splitDirectories, joinPath )+import System.IO hiding ( readFile )+import System.IO.Strict( readFile )+import System.Exit+import Text.Regex.Posix( (=~) )+import Data.Time.Clock+import Control.Monad.Error+import Control.Exception( Exception(..), throw, catch, SomeException )+import Control.Concurrent( forkIO )+import Control.Concurrent.Chan( newChan, writeChan, readChan, Chan )+import System.Console.CmdArgs (isLoud)+import System.Process( runInteractiveProcess, runInteractiveCommand,+                       waitForProcess )+import qualified System.IO.UTF8 as UTF8+import qualified Data.ByteString.Char8 as BS++catchany :: IO a -> (SomeException -> IO a) -> IO a+catchany = catch++copyTree :: FilePath -> FilePath -> IO ()+copyTree from to =+    do subs <- (\\ [".", ".."]) `fmap` getDirectoryContents from+       createDirectory to+       forM_ subs $ \item -> do+         is_dir <- doesDirectoryExist (from </> item)+         is_file <- doesFileExist (from </> item)+         when is_dir $ copyTree (from </> item) (to </> item)+         when is_file $ copyFile (from </> item) (to </> item)++reset :: Command ()+reset = do+  resetMemoryUsed+  resetTimeUsed++exec :: Benchmark a -> TestBinary -> TestRepo -> Command a+exec (Idempotent _ _ cmd) bin tr = do+  cd "_playground"+  verbose "cd _playground"+  cmd (vcs bin) tr+exec (Destructive _ _ cmd) bin tr = do+  cd "_playground"+  let cleanup = verbose "cd .. ; rm -rf _playground" >> cd ".." >> rm_rf "_playground"+  res <- cmd (vcs bin) tr `catch_sh` \(e :: SomeException) -> (cleanup >> throw e)+  cleanup+  return res+exec (Description _) _ _ = fail "Cannot run description-only benchmark."++defaultrepo, sources :: FilePath -> FilePath+defaultrepo path = path </> "_darcs" </> "prefs" </> "defaultrepo"+sources path = path </> "_darcs" </> "prefs" </> "sources"++prepare :: String -> Command ()+prepare origrepo = do+  progress "!" >> verbose "rm -rf _playground"+  rm_rf "_playground"+  liftIO $ createDirectory "_playground"+  let playrepo = "_playground" </> "repo"+  isrepo <- liftIO $ doesDirectoryExist (origrepo </> "_darcs")+  progress "." >> verbose ("cp -a '" ++ origrepo ++ "' '" ++ playrepo ++ "'")+  liftIO $ copyTree origrepo playrepo+  progress "." >> verbose ("# sanitize " ++ playrepo)+  wd <- pwd+  mkdir_p (playrepo </> "_darcs" </> "prefs") -- FIXME ... this is not very nice in git repos+  liftIO $ do writeFile (defaultrepo playrepo) (wd </> origrepo)+              removeFile (sources playrepo) `catchany` \_ -> return ()++prepareIfDifferent :: String -> Command ()+prepareIfDifferent origrepo = do+  let playrepo = "_playground" </> "repo"+  exist <- test_e "_playground"+  isrepo <- test_d $ playrepo </> "_darcs"+  if isrepo then do+    current' <- if exist then liftIO $ readFile (defaultrepo playrepo) else return ""+    let current = reverse (dropWhile (=='\n') $ reverse current')+    wd <- pwd+    if (exist && current == wd </> origrepo)+       then progress "..." >> verbose ("# leaving " ++ playrepo ++ " alone")+       else prepare origrepo+   else prepare origrepo++-- | Run a benchmark as many times as it takes to pass a minimum threshold+--   of time or iterations (whichever comes first)+--   Useful for very small benchmarks+adaptive :: Double -- ^ seconds+         -> (Int,Int) -- ^ min, max iterations+         -> Command MemTime -> Command [MemTime]+adaptive thresh (iters_min,iters_max) cmd =+   cmd >> -- just once to warm up the disk cache (eliminate a source of variance)+   go thresh iters_max []+ where+  go t i acc | i <= 0 || (t <= 0 && i <= iters_enough) = return acc+  go t i acc =+   do verbose $ "# adaptive: iterations remaining: " ++ show i ++ " time remaining: " ++ show t+      mt@(MemTime _ t2) <- cmd+      go (t - t2) (i - 1) (mt : acc)+  iters_enough = iters_max - iters_min++run :: Test a -> Command (Maybe MemTimeOutput)+run test@(Test benchmark tr bin) = do+  (Just `fmap` run') `catch_sh` \(e :: SomeException) ->+      do echo_err $ " error: " ++ show e+         return Nothing+  where run' = do+          progress $ cmd ++ " " ++ description benchmark ++ " [" ++ trName tr ++ "]: "+          verbose $ "\n# testing; binary = " ++ cmd ++ ", benchmark = " +++                    description benchmark ++ ", repository = " ++ trName tr+          times <- adaptive 10 (3,100) . sub $ do+                     prepareIfDifferent (trPath tr)+                     binPath bin >>= liftIO . maybeVMFlush+                     m <- timed (exec benchmark bin tr)+                     return m+          let result = mkMemTimeOutput times+              spaces = 45 - (length cmd + length (description benchmark) + length (trName tr))+              tu = appropriateUnit (mtTimeMean result)+              result_str = unwords $ concatMap (\f -> f tu (Cell result)) [ formatTimeResult, formatMemoryResult, formatSampleSize ]+          liftIO $ appendResult test times+          progress $ (replicate spaces ' ') ++ result_str ++ "\n"+          verbose $ "# result: " ++ result_str+          return result+        cmd = binCommand bin++timed :: Command a -> Command MemTime+timed a = do+  resetMemoryUsed+  t1 <- liftIO $ getCurrentTime+  _ <- a+  t2 <- liftIO $ getCurrentTime+  mem <- memoryUsed+  resetMemoryUsed+  return $ MemTime (fromIntegral mem) (realToFrac $ diffUTCTime t2 t1)++darcsMeta :: String -> [String] -> IO String+darcsMeta cmd args = do+       (_,outH,_,procH) <- runInteractiveProcess cmd args Nothing Nothing+       out <- strictGetContents outH+       _ <- waitForProcess procH+       return out++check_vcs :: String -> IO TestBinary+check_vcs cmd = do+  ver <- darcsMeta cmd ["--version"]+  case ver of+    _ | "git" `isPrefixOf` ver ->+      return TestBinary { binCommand = cmd+                        , binVCS = VCSGit+                        , binVersionString = numeric ver+                        , binDate = startOfTime+                        , binGHC = "none at all"+                        , binContext = BS.empty }+    _ | "Mercurial" `isPrefixOf` ver ->+      return TestBinary { binCommand = cmd+                        , binVCS = VCSHg+                        , binVersionString = numeric ver+                        , binDate = startOfTime+                        , binGHC = "none at all"+                        , binContext = BS.empty }+    _ -> check_darcs cmd+  where numeric str = takeWhile (`elem` "1234567890.-") $ dropWhile (`notElem` "1234567890") str+++check_darcs :: String -> IO TestBinary+check_darcs cmd = do+       version <- darcsMeta cmd ["--version"]+       [info, context] <- splitOn "Context:\n\n" `fmap` darcsMeta cmd ["--exact-version"]+       rts <- read `fmap` darcsMeta cmd ["+RTS", "--info"]+       let date' = case info of+             _ | "darcs compiled on" `isPrefixOf` info ->+               drop 18 . takeWhile (/='\n') $ info+             _ -> "<unknown date>"+           date = fromMaybe startOfTime $ parseDateTime "%b %e %Y, at %H:%M:%S" date'+           bin = TestBinary { binCommand = cmd+                            , binVCS = VCSDarcs+                            , binVersionString = takeWhile (/='\n') version+                            , binDate = date+                            , binGHC = fromMaybe "unknown" $ lookup "GHC version" rts+                            , binContext = BS.pack context }+       case binVersion bin of+         2:_ -> return bin+         _ -> fail $ cmd ++ ": Not darcs 2.x binary."++verbose :: String -> Command ()+verbose m = liftIO $ do loud <- isLoud+                        when loud $ UTF8.hPutStrLn stderr m++progress :: String -> Command ()+progress m = liftIO $ do loud <- isLoud+                         unless loud $ UTF8.hPutStr stderr m++drain :: Handle -> Bool -> IO (Chan String)+drain h verb = do chan <- newChan+                  let work acc = do line <- hGetLine h+                                    when verb $ putStrLn ("## " ++ line)+                                    work (acc ++ line ++ "\n")+                              `catchany` \_ -> writeChan chan acc+                  _ <- forkIO $ work ""+                  return chan++readFile' :: FilePath -> IO String+readFile' f = do s <- readFile f+                 length s `seq` return s++data RunFailed = RunFailed String Int String deriving (Typeable)++instance Show RunFailed where+  show (RunFailed cmd code errs) =+    "error running " ++ cmd ++ ": exit status " ++ show code ++ ":\n" ++ errs++instance Exception RunFailed++runInPlayground :: String -> [String] -> Command String+runInPlayground cmd args = do+    loud <- liftIO isLoud+    verbose . unwords $ cmd:args+    liftIO $ do+       mPlayground <- seekPlayground `fmap` getCurrentDirectory+       mEnv <- case mPlayground of+                 Nothing -> return Nothing+                 Just p  -> (Just . replace "HOME" p) `fmap` getEnvironment+       (_,outH,errH,procH) <- runInteractiveProcess cmd args Nothing mEnv+       res' <- drain outH loud+       errs' <- drain errH loud+       ex <- waitForProcess procH+       errs <- readChan errs'+       case ex of+         ExitSuccess -> return ()+         ExitFailure n -> throw $ RunFailed (cmd ++ " " ++ show args) n errs+       res <- readChan res'+       return res+ where+  replace k v xs = (k,v) : filter ((/= k) . fst) xs++binPath :: TestBinary -> Command String+binPath bin = do exe <- which (binCommand bin)+                 case exe of+                   Nothing -> canonize $ binCommand bin+                   Just p -> return p++vcs :: TestBinary -> [String] -> Command String+vcs bin args =+  do exe <- binPath bin+     case binVCS bin of+       VCSDarcs -> darcs exe args+       VCSGit -> git exe args+       VCSHg -> hg exe args++darcs :: String -> [String] -> Command String+darcs cmd args' = do+    stats_f <- liftIO $+      do tmpdir <- getTemporaryDirectory+         (f, h) <- openTempFile tmpdir "darcs-stats-XXXX"+         hClose h+         return f+    let args = args' ++ ["+RTS", "-s" ++ stats_f, "-RTS"]+    res <- runInPlayground cmd args+    stats <- liftIO $ do c <- readFile' stats_f+                         removeFile stats_f `catchany` \e -> hPutStrLn stderr (show e)+                         return c+                       `catchany` \_ -> return ""+    let bytes = (stats =~ "([0-9, ]+) M[bB] total memory in use") :: String+        mem = case length bytes of+          0 -> 0+          _ -> (read (filter (`elem` "0123456789") bytes) :: Int)+    recordMemoryUsed $ mem * 1024 * 1024+    return res++git :: String -> [String] -> Command String+git cmd args = case args of+  ["obliterate", "--last", n, "--all"] -> obliterate n+  _ -> runInPlayground cmd ("--no-pager" : args')+  where args' | ("whatsnew":rem) <- args = "diff" : rem+              | ("revert":rem) <- args = "reset" : "--hard" : filter (/="-a") rem+              | ("record":rem) <- args = "commit" : filter (/= "--no-test") rem+              | ("get":rem) <- args = "clone" : rem+              | otherwise = args+        obliterate n = do+          rev <- (last . lines) `fmap` runInPlayground cmd+                             ["rev-list", "--max-count=" ++ n, "HEAD"]+          runInPlayground cmd ["reset", "--hard", rev]++hg :: String -> [String] -> Command String+hg cmd args = case args of+  ["obliterate", "--last", "1", "--all"] -> rollback+  _ ->  runInPlayground cmd args'+  where args' | ("whatsnew":rem) <- args = "diff" : rem+              | ("record":rem) <- args = "commit" : [ if opt == "--author" then "--user" else opt+                                                    | opt <- rem, opt /= "--no-test", opt /= "--all" ]+              | ("get":rem) <- args = "clone" : rem+              | otherwise = args+        rollback = do runInPlayground cmd ["rollback"]+                      runInPlayground cmd ["revert", "-a"]++seekPlayground :: FilePath -> Maybe FilePath+seekPlayground dir =+ if playground `elem` pieces+    then Just . joinPath . reverse . dropWhile (/= playground) . reverse $ pieces+    else Nothing+ where+  pieces = splitDirectories dir+  playground = "_playground"++-- ----------------------------------------------------------------------+-- variants+-- ----------------------------------------------------------------------++mkVariant :: String -> TestBinary -> Variant -> Command ()+mkVariant origrepo bin v = do+  isrepo <- liftIO $ doesDirectoryExist (origrepo </> "_darcs")+  unless isrepo $ fail $ origrepo ++ ": Not a darcs repository!"+  case vId v of+    OptimizePristineVariant -> do+      variant_isrepo  <- liftIO $ doesDirectoryExist (variant_repo </> "_darcs")+      unless variant_isrepo $ do+        echo $ "Setting up " ++ vDescription v ++ " variant of " ++ origrepo+        verbose ("cp -a '" ++ origrepo ++ "' '" ++ variant_repo ++ "'")+        liftIO $ copyTree origrepo variant_repo+        verbose ("# sanitize " ++ variant_repo)+        liftIO $ removeFile (sources variant_repo) `catchany` \_ -> return ()+        darcs (binCommand bin) [ "optimize", "--pristine", "--repodir", variant_repo ]+        return ()+    GitVariant -> do+      variant_exists <- liftIO $ doesDirectoryExist (variant_repo </> ".git")+      unless variant_exists $ do+        mkdir_p variant_repo+        sub $ do+          cd variant_repo+          system "git init"+          system ("darcs convert --export ../" ++ origrepo ++ " | git fast-import")+          system "git checkout"+        return ()+    HgVariant -> do+      variant_exists <- liftIO $ doesDirectoryExist (variant_repo </> ".hg")+      gitvariant <- case reverse variant_repo of+            'g':'h':'-':rest -> return $ reverse rest ++ "-git"+            _ -> fail "can't figure the git variant..."+      unless variant_exists $ do+        mkVariant origrepo bin (toVariant GitVariant)+        system $ "hg convert " ++ gitvariant ++ " " ++ variant_repo+        sub $ cd variant_repo >> system "hg checkout"+        return ()+    DefaultVariant -> return ()+ where+  variant_repo = variantRepoName v origrepo++variantRepoName :: Variant -> String -> String+variantRepoName (Variant { vId = DefaultVariant }) x = x+variantRepoName v x = "variant" <.> stripped x ++ "-" ++ vSuffix v+ where+  stripped y | "repo."   `isPrefixOf` y = stripped (drop 5 y)+             | "-hashed" `isSuffixOf` y = take (length y -  7) y+             | otherwise = y++setupVariants :: [TestRepo] -> TestBinary -> Command ()+setupVariants repos bin =+  sequence_ [ mkVariant (trPath repo) bin variant+            | repo <- repos, variant <- trVariants repo ]
+ source/Config.hs view
@@ -0,0 +1,30 @@+{-# LANGUAGE DeriveDataTypeable #-}++module Config where++import System.Console.CmdArgs++data Config = Get { repos :: [String] }+            | Run { fast  :: Bool+                  , converge :: Bool+                  , cold  :: Bool+                  , only  :: [String]+                  , dump  :: Bool+                  , extra :: [String] }+            | Dist { repo :: FilePath }+            | Report+              deriving (Show, Data, Typeable)++defaultConfig :: [Mode Config]+defaultConfig =+ [ mode $ Get { repos = []   &= args & typ "REPONAME" }+ , mode $ Run { fast = False &= text "Exclude the most time-consuming benchmarks"+              , converge = False &= text "Focus on benchmarks with not enough data"+              , cold = False &= text "Try to flush VM caches between iterations"+              , dump = False &= text "Produce machine-readable output on stdout"+              , only =  []   &= text "Only run benchmarks with one of these substrings in their names" & typ "BENCHMARK"+              , extra = []   &= args & typ "BINARY"+              }+ , mode $ Dist { repo = "" &= argPos 0 & typ "DIRECTORY" }+ , mode $ Report+ ]
+ source/Definitions.hs view
@@ -0,0 +1,363 @@+module Definitions where++import Prelude hiding ( readFile )+import System.IO.Strict( readFile )+import Control.Applicative+import Data.Array.Vector+import Data.Function+import Data.IORef+import Data.List+import Data.List.Split ( wordsBy, splitOn )+import Data.Maybe+import Data.DateTime( DateTime, formatDateTime )+import Data.Ord+import Data.Time+import Data.Char+import Data.Digest.Pure.SHA+import Network.BSD ( HostName, getHostName )+import Statistics.Sample+import System.Directory+import System.FilePath+import System.Locale+import System.IO.Unsafe+import Text.JSON+import Text.Printf+import qualified Data.ByteString.Char8 as BS+import qualified Data.ByteString.Lazy.Char8 as BL++import Shellish (Command)++type Darcs = [String] -> Command String++data Test a = Test (Benchmark a) TestRepo TestBinary deriving (Show)+data VCS = VCSDarcs | VCSGit | VCSHg deriving (Eq, Ord, Show, Read)++data TestBinary = TestBinary { binCommand :: String+                             , binVersionString :: String+                             , binDate :: DateTime+                             , binGHC :: String+                             , binVCS :: VCS+                             , binContext :: BS.ByteString }+                deriving (Eq, Ord, Show, Read)++binVersion :: TestBinary -> [Int]+binVersion = parsever . binVersionString+  where parsever = map read . wordsBy (== '.') . takeWhile (`elem` "0123456789.-")++binSha1 :: TestBinary -> String+binSha1 bin = showDigest (sha1 $ BL.fromChunks [BS.pack txt, binContext bin])+  where txt = show (binVersionString bin) ++ " " +++              (formatDateTime "%Y-%m-%d %H:%M:%S" $ binDate bin) ++ " "++data ParamStamp = Params { pHostName :: HostName+                         , pFlush :: Maybe (FilePath -> IO ()) }+type TimeStamp = UTCTime++type Version = [Int] -- we could also use Data.Version+                     -- the difference here is just that we explicitly+                     -- do not have a notion of version tags++global :: IORef (ParamStamp, TimeStamp)+global = unsafePerformIO $ do t  <- getCurrentTime+                              hn <- getHostName+                              newIORef (Params hn Nothing, t)++maybeVMFlush :: FilePath -> IO ()+maybeVMFlush darcs = do+ (p,_) <- readIORef global+ case pFlush p of+  Just f  -> f darcs+  Nothing -> return ()++-- ----------------------------------------------------------------------+-- benchmarks+-- ----------------------------------------------------------------------++type BenchmarkCmd a = Darcs -> TestRepo -> Command a++data BenchmarkSpeed = FastB | SlowB deriving (Eq)++data Benchmark a = Idempotent String BenchmarkSpeed (BenchmarkCmd a)+                 | Destructive String BenchmarkSpeed (BenchmarkCmd a)+                 | Description String++instance Show (Benchmark a) where+    show = description -- FIXME: is this right?++description :: Benchmark a -> String+description (Idempotent d _ _) = d+description (Destructive d _ _) = d+description (Description d) = d++speed :: Benchmark a -> BenchmarkSpeed+speed (Idempotent _ s _) = s+speed (Destructive _ s _) = s+speed (Description _) = FastB++-- ----------------------------------------------------------------------+-- repositories+-- ----------------------------------------------------------------------++data TestRepo = TestRepo { trName     :: String+                         , trCoreName :: String   -- ^ (variants only) name of the orig repo+                         , trPath     :: FilePath -- ^ relative to the config file+                         , trAnnotate :: Maybe FilePath -- ^ relative to repo, eg. @Just "README"@+                         , trVariants :: [Variant]+                         , trSkip     :: [String] -- ^ skip benchmarks w/ description matching any of these strings+                         }+ deriving (Read, Show, Eq, Ord)++instance JSON TestRepo where+  readJSON (JSObject o) =+     TestRepo <$> jlookup "name"+              <*> jlookup "name" -- 2nd time for trCoreName+              <*> jlookup "path"+              <*> jlookupMaybe "annotate"+              <*> (map toVariant . (DefaultVariant :) <$> jlookupMaybeList "variants")+              <*> jlookupMaybeList "skip"+    where+     jlookup a = case lookup a (fromJSObject o) of+                   Nothing -> fail "Unable to read TestRepo"+                   Just v  -> readJSON v+     jlookupMaybe a = case lookup a (fromJSObject o) of+                        Nothing     -> return Nothing+                        Just JSNull -> return Nothing+                        Just v -> Just <$> readJSON v+     jlookupMaybeList a = case lookup a (fromJSObject o) of+                            Nothing -> return []+                            Just v  -> readJSONs v+  readJSON _ = fail "Unable to read TestRepo"+  showJSON = error "showJSON not defined for TestRepo yet"++-- note that the order of the variants is reflected in the tables+data VariantName = DefaultVariant | OptimizePristineVariant | GitVariant | HgVariant+ deriving (Enum, Bounded, Eq, Ord, Read, Show)++instance JSON VariantName where+  readJSON (JSString s) =+    case fromJSString s of+      "optimize-pristine" -> return OptimizePristineVariant+      "git" -> return GitVariant+      "hg" -> return HgVariant+      x -> fail $ "Unknown variant: " ++ x+  readJSON _ = fail "Unable to VariantName"+  showJSON = error "showJSON not defined for VariantName yet"++data Variant = Variant { vId :: VariantName+                       , vShortName   :: String+                       , vDescription :: String+                       , vSuffix :: String+                       }+ deriving (Eq, Ord, Show, Read)++toVariant :: VariantName -> Variant+toVariant n@DefaultVariant =+  Variant n "default"  "default (hashed)" ""+toVariant n@OptimizePristineVariant =+  Variant n "opt pris" "optimize --pristine" "op"+toVariant n@GitVariant =+  Variant n "git" "git" "git"+toVariant n@HgVariant =+  Variant n "hg" "hg" "hg"++-- | Given a name of a repo like "tabular opt pris", figure out what the+--   variant was.  If there are no suffixes, like "opt pris", we assume+--   it's not a variant.+nameToVariant :: String -> Variant+nameToVariant n =+  case [ v | v <- variants, vShortName v `isSuffixOf` n ] of+   []    -> toVariant DefaultVariant+   (v:_) -> v+ where+  variants = sortBy (compare `on` (negate . suffixLength)) -- longest suffixes first+           $ allVariants+  suffixLength = length . vShortName++allVariants :: [Variant]+allVariants = map toVariant [minBound .. maxBound]++-- | The subset of variants appropriate to the given darcs version+appropriateVariants :: TestBinary -> [Variant] -> [Variant]+appropriateVariants bin = filter (appropriate . vId)+  where appropriate GitVariant+          | vcs /= VCSGit = False+        appropriate HgVariant+          | vcs /= VCSHg = False+        appropriate OptimizePristineVariant+          | vcs /= VCSDarcs || ver < [2,3,97] = False+        appropriate DefaultVariant+          | vcs /= VCSDarcs || ver > [2,4,96] = False+        appropriate _ = True+        vcs = binVCS bin+        ver = binVersion bin++-- ----------------------------------------------------------------------+-- long-term storage+-- ----------------------------------------------------------------------++-- TODO: machine info? hash this?+paramStampPath :: ParamStamp -> String+paramStampPath p = intercalate "-" [ pHostName p, flushStr ]+ where+  flushStr = if isJust (pFlush p) then "cold" else "warm"++timeStampPath :: TimeStamp -> String+timeStampPath t = formatTime defaultTimeLocale "%Y-%m-%dT%H%M" t -- we don't need to be super-precise here++resultsDir :: IO FilePath+resultsDir =+ do home <- getHomeDirectory+    return $ home </> ".darcs-benchmark"++timingsDir :: ParamStamp -> IO FilePath+timingsDir cstmp =+ do d <- resultsDir+    return $ d </> paramStampPath cstmp <.> "timings"++appendBinary :: TestBinary -> IO ()+appendBinary bin =+ do (pstmp, _) <- readIORef global+    path <- flip replaceExtension "info" `fmap` timingsDir pstmp+    current <- (read `fmap` readFile path) `catch` \_ -> return []+    let new = (sha, bin) : [ (s, x) | (s, x) <- current, s /= sha ]+    writeFile path (show new)+   where sha = binSha1 bin++appendResult :: Test a -> [MemTime] -> IO ()+appendResult (Test benchmark tr bin) times =+ do (pstmp, tstmp) <- readIORef global+    d <- resultsDir+    createDirectoryIfMissing False d+    td <- timingsDir pstmp+    createDirectoryIfMissing False td+    appendFile (td </> timeStampPath tstmp) block+ where+   block = unlines $ map (intercalate "\t" . fields) times+   fields mt = [ trName tr, binSha1 bin, description benchmark ] ++ fieldMt mt+   fieldMt (MemTime m t) = [ show (fromRational m :: Float), show t ]++-- ----------------------------------------------------------------------+-- outputs+-- ----------------------------------------------------------------------++precision :: Int+precision = 1++data MemTime = MemTime Rational Double deriving (Read, Show, Ord, Eq)++data MemTimeOutput = MemTimeOutput { mtTimeMean    :: Double+                                   , mtTimeDev :: Double+                                   , mtSampleSize :: Int+                                   , mtMemMean     :: Rational+                                   }+ deriving (Show)++mkMemTimeOutput :: [MemTime] -> MemTimeOutput+mkMemTimeOutput xs =+  MemTimeOutput { mtTimeMean = mean time_v+                , mtTimeDev  = stdDev time_v+                , mtMemMean  = toRational (mean mem_v)+                , mtSampleSize = lengthU time_v+                }+ where+  time_v = toU [ t | MemTime _ t <- xs ]+  mem_v  = toU [ fromRational m | MemTime m _ <- xs ]++data RepoTable = RepoTable { rtRepo :: String+                           , rtColumns :: [String]+                           , rtRows :: [String]+                           , rtTable :: [(TimeUnit, [Maybe MemTimeOutput])]+                           }++-- Reformat (test, output) array as a square table by (core) Repository+repoTables :: [Benchmark ()] -> [(Test a, Maybe MemTimeOutput)] -> [RepoTable]+repoTables benchmarks results = [RepoTable { rtRepo = reponame (head repo)+                                           , rtColumns = columns repo+                                           , rtRows = rows+                                           , rtTable = table repo+                                           } | repo <- reposResults]+ where+  reposResults = groupBy (on (==) reponame) $ sortBy repoOrder results+  reponame (Test _ tr _, _) = trCoreName tr+  repoOrder = comparing reponame+  --+  rows = map description . filter hasBenchmark $ benchmarks+  hasBenchmark b = any (\ (Test tb _ _, _) -> description tb == description b) results+  --+  columns repo = map mkColName $ columnInfos repo+  columnInfos repo = nub [ (b, trName tr) | (Test _ tr b, _) <- repo ]+  mkColName (b, tname) =+    let v = nameToVariant tname+        prefix = case vId v of+                   DefaultVariant -> ""+                   _-> vSuffix v ++ " "+    in prefix ++ cutdown (binCommand b)+  cutdown d | "darcs-" `isPrefixOf` d = cutdown (drop 6 d)+            | takeExtension d == ".exe" = dropExtension d+            | otherwise = d+  --+  table repo = [(tu (tableRow row), map justMemTimeOutput $ tableRow row)+               | row <- rows]+    where+      tableRow row = [find (match row col) repo | col <- columnInfos repo]+      getTime (Just (_, Just mt)) = Just (mtTimeMean mt)+      getTime _ = Nothing+      tu row = case mapMaybe getTime row of+        [] -> Milliseconds+        xs -> appropriateUnit (minimum xs)+  match bench (binary, name) (Test bench' tr binary', _) =+      bench == description bench' && binary == binary' && trName tr == name+  justMemTimeOutput (Just (_, mt)) = mt+  justMemTimeOutput Nothing = Nothing++data TimeUnit = MinutesAndSeconds | Milliseconds++formatTimeElapsed :: TimeUnit -> Double -> String+formatTimeElapsed Milliseconds raw = formatNumber (raw * 1000) ++ "ms"+formatTimeElapsed MinutesAndSeconds raw =+ case mins raw of+   Nothing -> formatNumber (secs raw) ++ "s"+   Just m  -> show m ++ "m" ++ formatNumber (secs raw) ++ "s"+ where+   secs x = (fromInteger (floor x `mod` 60)) + (x - fromInteger (floor x))+   mins x | x > 60 = Just (floor x `div` 60 :: Int)+          | otherwise = Nothing++appropriateUnit :: Double -> TimeUnit+appropriateUnit s | s < 2     = Milliseconds+                  | otherwise = MinutesAndSeconds++data Cell h a = ColHeader h | Cell a | MissingCell++formatNumber :: (PrintfArg a, Fractional a) => a -> String+formatNumber = printf $ "%."++(show precision)++"f"++type Formatter = TimeUnit -> Cell String MemTimeOutput -> [String]++formatSampleSize :: Formatter+formatSampleSize _ (ColHeader h) = [ h ]+formatSampleSize _ MissingCell = [ "-" ]+formatSampleSize _ (Cell mt) = [ show (mtSampleSize mt) ++ "x" ]++formatTimeResult :: Formatter+formatTimeResult _ (ColHeader h) = [ h, "sdev" ]+formatTimeResult _ MissingCell = [ "-", "-" ]+formatTimeResult tu (Cell mt) =+  [ vouchFor $ time $ combineMeanStddev mt, "(" ++ time (mtTimeDev mt) ++ ")" ]+ where+   time t = formatTimeElapsed tu t+   vouchFor = case mtSampleSize mt of -- FIXME: YUCK! hard coded; cf. criterion+               sz | sz < 5    -> showChar '?'+                  | sz < 20   -> showChar '~'+                  | otherwise -> id++-- | This is simply mean + stddev, but we could potentially+--   change it in the future to, for example, mean + 2 stddev+combineMeanStddev :: MemTimeOutput -> Double+combineMeanStddev mt = mtTimeMean mt + mtTimeDev mt++formatMemoryResult :: Formatter+formatMemoryResult _ (ColHeader s) = [ s ]+formatMemoryResult _ MissingCell = [ "-" ]+formatMemoryResult _ (Cell mt) =+  [ formatNumber ((realToFrac (mtMemMean mt / (1024*1024))) :: Float) ++ "M" ]
+ source/Dist.hs view
@@ -0,0 +1,15 @@+module Dist where++import qualified Codec.Archive.Tar as Tar+import Codec.Compression.GZip( compress )+import qualified Data.ByteString.Lazy as BL+import Data.List ( isPrefixOf )+import System.FilePath++createTarball :: FilePath -> IO ()+createTarball dir = do+  str <- (compress . Tar.write) `fmap` Tar.pack dir [ "." ]+  let tarball = dropPrefix "repo." (takeBaseName dir) <.> "tgz"+  BL.writeFile tarball str+ where+  dropPrefix p x = if p `isPrefixOf` x then drop (length p) x else x
+ source/Download.hs view
@@ -0,0 +1,53 @@+module Download where++import Network.Browser+import Network.URI+import Network.HTTP.Base++import qualified Codec.Archive.Tar as Tar ( read, unpack )+import System.FilePath+import System.Directory+import Codec.Compression.GZip( decompress )+import Control.Monad++baseurl :: String+baseurl = "http://code.haskell.org/darcs/benchmark-repos/"++exists :: FilePath -> IO Bool+exists f =+ do exists_file <- doesFileExist f+    exists_dir  <- doesDirectoryExist f+    return (exists_file || exists_dir)++download :: String -> IO ()+download repo = do+  let Just repo_url   = parseURI $ baseurl  ++ repo ++ ".tgz"+      Just config_url = parseURI $ baseurl  ++ "config" <.> repo+      config_file = "config" <.> repo+  putStrLn $ "downloading and extracting: " ++ show repo_url+  repo_exists   <- exists ("repo" <.> repo)+  config_exists <- exists ("config" <.> repo)+  let go = do rsp <- fetch repo_url+              when (rspCode rsp /= (2, 0, 0)) $+                   fail ("download failed: " ++ rspReason rsp)+              createDirectory $ "repo" <.> repo+              let bits = rspBody rsp+                  entries = Tar.read $ decompress bits+              Tar.unpack ("repo" <.> repo) entries+      go_cfg = do rsp <- fetch config_url+                  case rspCode rsp of+                    (4, 0, 4) -> putStrLn $ "No config file detected for " ++ repo ++ " and that's fine!"+                    (2, 0, 0) -> writeFile ("config" <.> repo) (rspBody rsp)+                    _         -> fail ("download failed: " ++ rspReason rsp)+  if repo_exists+       then putStrLn $ "repo" <.> repo ++ " already exists, fetching config file only."+       else go+  if config_exists+       then putStrLn $ config_file ++ " already exists, skipping!"+       else go_cfg+ where+  fetch url =+    do (_, rsp) <- browse $ do setCheckForProxy True+                               setOutHandler (const $ return ())+                               request (mkRequest GET url)+       return rsp
+ source/Graph.hs view
@@ -0,0 +1,54 @@+module Graph where++import Data.List+import Graphics.GChart++import Definitions++graphRepoMemory :: RepoTable -> [String]+graphRepoMemory repo = map graphUrl rows+ where+  rows = [(title rowname, rtColumns repo, graphdata rowdata)+         | (rowname, (_, rowdata)) <- zip (rtRows repo) (rtTable repo)]+  title rowname = rowname ++ " (MiB)"+  graphdata rowdata = map selectMemory rowdata+  selectMemory (Just mt) = fromRational (mtMemMean mt) / (1024 * 1024)+  selectMemory _ = 0.0++graphRepoTime :: RepoTable -> [String]+graphRepoTime repo = map graphUrl rows+ where+  rows = [(title tu rowname, rtColumns repo, graphdata tu rowdata)+         | (rowname, (tu, rowdata)) <- zip (rtRows repo) (rtTable repo)]+  title Milliseconds rowname = rowname ++ " (ms)"+  title MinutesAndSeconds rowname = rowname ++ " (s)"+  graphdata tu rowdata = map (selectTime tu) rowdata+  selectTime Milliseconds (Just mt) = combineMeanStddev mt * 1000+  selectTime MinutesAndSeconds (Just mt) = combineMeanStddev mt+  selectTime _ _ = 0.0  ++graphUrl :: (String, [String], [Double]) -> String+graphUrl (title, labels, results) = getChartUrl $ do+  setChartSize 200 200+  setDataEncoding simple+  setChartType BarVerticalGrouped+  setBarWidthSpacing $ barwidthspacing 23 5 20+  setChartTitle title+  addAxis $ makeAxis { axisType = AxisBottom, axisLabels = Just labels }+  addAxis $ makeAxis { axisType = AxisLeft,+    axisRange = Just $ Range (0, realToFrac range) Nothing }+  setColors palette+  addChartData row+ where+  range = case results of+    [] -> 1+    xs -> maximum xs+  row = [truncate (result * 61.0 / range) | result <- results] :: [Int]+-- Color palette for bars, based on the Tango palette (Butter, Orange, Choc)+  -- TODO: Adjust palette size based on actual number of results?+  palette = [intercalate "|" ["fce94f"+                            , "c4a000"+                            , "fcaf3e"+                            , "ce5c00"+                            , "e9b96e"+                            , "8f5902"]]
+ source/Report.hs view
@@ -0,0 +1,221 @@+module Report where++import Control.Arrow+import Control.Monad.Error+import Data.Function+import Data.List+import Data.List.Split+import Data.Maybe+import Data.DateTime( formatDateTime, startOfTime )+import qualified Data.Map as Map+import qualified Text.Tabular          as Tab+import System.Directory+import System.FilePath+import System.IO++import Definitions+import Graph+import Shellish hiding ( run )+import Standard+import TabularRST as TR+import qualified Data.ByteString.Char8 as BS++-- ----------------------------------------------------------------------+-- tables+-- ----------------------------------------------------------------------++type BenchmarkTable = Tab.Table String String String++tabulateRepo :: Formatter -> RepoTable -> Tab.Table String String String+tabulateRepo format repo = Tab.Table rowhdrs colhdrs rows+ where+  rowhdrs = Tab.Group Tab.NoLine $ map Tab.Header (rtRows repo)+  colhdrs = Tab.Group Tab.SingleLine $ map Tab.Header $+              concatMap (format myundefined . ColHeader) $ rtColumns repo+  myundefined = error "Formatting is undefined for column headers"+  rows = map formatRow $ rtTable repo+  formatRow (tu, rs) = concatMap (fmt tu) rs+  fmt tu (Just mt) = format tu (Cell mt)+  fmt tu Nothing = format tu MissingCell++-- ----------------------------------------------------------------------+-- timings files+-- ----------------------------------------------------------------------++readAllTimings :: IO [[(Test a, Maybe MemTimeOutput)]]+readAllTimings = do+  rdir  <- resultsDir+  tdirs <- filter isTimingFile `fmap` getDirectoryContents rdir+  let pstamps = map dropExtension tdirs+  mapM readTimingsForParams pstamps+ where+  isTimingFile f = takeExtension f == ".timings"++unknownBinary x =+  TestBinary { binCommand = x, binVersionString = "unknown"+             , binDate = startOfTime, binGHC = "unknown", binVCS = VCSDarcs+             , binContext = BS.empty }++-- | Map an sha1 of darcs binary into the original binary description.+readInfos :: String -> (String -> TestBinary)+readInfos bits = \x -> case lookup x table of+  Nothing -> unknownBinary x+  Just b -> b+  where table' = sortBy order (case reads bits of+                                  [] -> []+                                  ((x,_):_) -> x)+        ids = [ id | (id:_:_) <- group . sort $ map (binCommand . snd) table' ]+        fixid n id ((sha, bin):rem)+          | binCommand bin == id = (sha, bin { binCommand = id ++ " " ++ show n }) :+                                       fixid (n + 1) id rem+          | otherwise = (sha, bin) : fixid n id rem+        fixid _ _ [] = []+        table = (foldl (.) id (map (fixid 0) ids)) table'+        order (_, x) (_, y) = case compare (binVersion x) (binVersion y) of+          EQ -> compare (binDate x) (binDate y)+          ord -> ord++readTimingsForParams :: String -> IO [(Test a, Maybe MemTimeOutput)]+readTimingsForParams pstamp = do+  rdir  <- resultsDir+  let pdir = rdir </> pstamp <.> "timings"+      ifile = rdir </> pstamp <.> "info"+  infos <- (readInfos `fmap` readFile ifile) `catch` \_ -> do+    hPutStrLn stderr $ "WARNING: Could read " ++ ifile+    return unknownBinary+  tfiles   <- filter notJunk `fmap` getDirectoryContents pdir+  entries  <- concat `fmap` mapM parseTimingsFile (map (pdir </>) tfiles)+  return . map (process infos) . Map.toList . Map.fromListWith (++) . map (second (:[])) $ entries+ where+  notJunk = not . (`elem` [".",".."])++process :: (String -> TestBinary)+           -> ((String, String, String), [MemTime]) -> (Test a, Maybe MemTimeOutput)+process infos ((repo, binhash, bm), times) = (key, val)+ where+   key = Test (Description bm) (mkTr repo) (infos binhash)+   val = Just $ mkMemTimeOutput times+   mkTr n = TestRepo n (guessCoreName n) n Nothing [] []++guessCoreName :: String -> String+guessCoreName n =+  case [ n `chop` (' ':s) | s <- suffixes, s `isSuffixOf` n ] of+   []    -> n+   (h:_) -> h+ where+  x `chop` s = take (length x - length s) x+  suffixes   = sortBy (compare `on` (negate . length)) -- longest suffixes first+             $ map vShortName allVariants++type TimingsFileEntry = ((String,String,String),MemTime)++parseTimingsFile :: FilePath -> IO [TimingsFileEntry]+parseTimingsFile tf = do+  ms <- (map parseLine . lines) `fmap` readFile tf+  let unknowns = length $ filter isNothing ms+  when (unknowns > 0) $+    hPutStrLn stderr $ "Warning: could not understand " ++ show unknowns ++ " lines in " ++ tf+  return (catMaybes ms)++parseLine :: String -> Maybe TimingsFileEntry+parseLine l =+ case wordsBy (== '\t') l of+  [ repo, dbin, bm, mem, time ] -> Just ((repo, dbin, bm), memtime time mem)+  _ -> Nothing+ where+  memtime t m = MemTime (toRational (read m :: Float)) (read t)++-- ----------------------------------------------------------------------+-- reports+-- ----------------------------------------------------------------------++renderMany :: [(Test a, Maybe MemTimeOutput)] -> Command ()+renderMany results = do+  echo . unlines $+   [ "Benchmark Results"+   , "====================================================="+   , ""+   , machine_details+   , ""+   , "How to read these tables"+   , "====================================================="+   , ""+   , "NB: times are reported as mean + 1 std deviation"+   , ""+   , def "?x" "less than  5 runs used"+   , def "~x" "less than 20 runs used"+   , def "sdev" "std deviation"+   , descriptions_of_variants+   , ""+   , binary_details+   , ""+   , "Timing Graphs"+   , "===================================================="+   , ""+   , intercalate "\n" (map showG t_graphs)+   , "Timings"+   , "===================================================="+   , ""+   , intercalate "\n" (map showT t_tables)+   , "Memory Graphs"+   , "===================================================="+   , ""+   , intercalate "\n" (map showG m_graphs)+   , "Memory"+   , "===================================================="+   , ""+   , intercalate "\n" (map showT m_tables)+   ]+ where+  tables = repoTables benchmarks results+  --+  machine_details = intercalate "\n" $+    map detail [ "Machine description", "Year", "CPU", "Memory", "Hard disk", "Notes" ]+  detail k = k ++ "\n    *Replace Me*"+  --+  descriptions_of_variants = intercalate "\n" $+    map (describe . toVariant) [ OptimizePristineVariant ]+  describe v = def (vSuffix v) (vDescription v ++ " variant")+  def k v = "* " ++ k ++ " = " ++ v+  --+  repoTuple tabulate repo = (rtRepo repo, tabulate repo)+  t_tables = map (repoTuple $ tabulateRepo formatTimeResult) tables+  m_tables = map (repoTuple $ tabulateRepo formatMemoryResult) tables+  showT (r,t) = intercalate "\n" [ r+                                 , replicate (length r) '-'+                                 , ""+                                 , TR.render id id id t ]+  --+  t_graphs = map (repoTuple graphRepoTime) tables+  m_graphs = map (repoTuple graphRepoMemory) tables+  showG (r,gs) = intercalate "\n" $ [ r+                                    , replicate (length r) '-'+                                    , ""+                                    ] ++ (map imgDirective gs) ++ [""]+  imgDirective = (".. image:: " ++)+  binaries = map head . group . sort $ [ bin | (Test _ _ bin, _) <- results ]+  --+  binary_details = unlines $ map describe_bin binaries+  describe_bin bin = padl 12 (binCommand bin ++ ": ") +++                     binVersionString bin ++ ",\n" ++ (replicate 12 ' ') +++                     (formatDateTime "%Y-%m-%d %H:%M:%S" $ binDate bin) +++                     ", GHC " ++ binGHC bin+  padr n x = x ++ pad n x+  padl n x = pad n x ++ x+  pad n x = take (n - length x) (repeat ' ')++printCumulativeReport :: Command ()+printCumulativeReport = do+  ts <- liftIO readAllTimings+  mapM_ renderMany ts -- TODO: split this into sections for each param stamp++-- | For each repository, the benchmarks that we have enough data for+sufficientData :: [(Test a, Maybe MemTimeOutput)] -> [ (String, [String]) ]+sufficientData results =+  map (rtRepo &&& missingPoints) tables+ where+  tables = repoTables benchmarks results+  missingPoints t = [ d | (d,mts) <- zip (rtRows t) (rtTable t), all enough (snd mts) ]+  --+  enough (Just mt) | mtSampleSize mt >= 20 = True -- TODO: avoid duplication with formatTimeResult+  enough _ = False
+ source/Run.hs view
@@ -0,0 +1,35 @@+module Run where++import Control.Monad.Error++import Benchmark+import Definitions+import Report+import Shellish hiding ( run )+import Standard+import qualified TabularRST as TR++benchMany :: [(TestRepo, [Benchmark a])] -> [TestBinary] -> Command [(Test a, Maybe MemTimeOutput)]+benchMany reposbenches bins = do+  fmap concat $ forM reposbenches $ \(r,benches) -> do+    res <- sequence+             [ do let test = Test bench repo bin+                  memtime <- run test+                  return (test, memtime)+               | bin <- bins+               , repo <- repoAndVariants bin r+               , bench <- filter (noSkip r) benches ]+    case repoTables benchmarks res of+     []  -> echo $ "No benchmarks to run for " ++ trCoreName r+     [t] -> echo_n $ TR.render id id id $ tabulateRepo formatTimeResult t+     _   -> error "Not expecting more than one table for a repo and its variants"+    return res+ where+  noSkip r b = not (description b `elem` trSkip r)+  repoAndVariants bin r = map (r `tweakVia`) (appropriateVariants bin (trVariants r))+  tweakVia tr v =+   case vId v of+     DefaultVariant -> tr+     _ -> tr { trPath = variantRepoName v (trPath tr)+             , trName = trName tr ++ " " ++ vShortName v+             }
+ source/Shellish.hs view
@@ -0,0 +1,186 @@+{-# LANGUAGE ScopedTypeVariables #-}+module Shellish( cd, pwd, run, (#), withCurrentDirectory, strictGetContents,+                 shellish, silently, verbosely, Command, sub, echo, echo_n,+                 which, canonize, resetMemoryUsed, recordMemoryUsed,+                 memoryUsed, rm_rf, rm_f, whenM, test_e, test_f, test_d,+                 resetTimeUsed, timeUsed, mv, ls, echo_err, echo_n_err,+                 mkdir_p, catch_sh, system )+    where++import Prelude hiding ( catch )+import Data.List+import System.IO+import System.Directory+import System.Exit+import Control.Exception+import Control.Monad.State+import Control.Monad.Error+import Data.Time.Clock( getCurrentTime, diffUTCTime, UTCTime(..) )+import Control.Exception ( bracket, evaluate )+import qualified Data.ByteString.Char8 as B+import System.Process( runInteractiveProcess, waitForProcess )+import qualified System.IO.UTF8 as UTF8+import qualified System.Cmd as Cmd+++-- TODO maxMem is sort of a layering violation here... but who cares.+data St = St { sCode :: Int, sStderr :: B.ByteString , sStdout :: B.ByteString,+               sVerbose :: Bool, maxMem :: Int, startTime :: UTCTime }++type Command a = StateT St IO a++-- Oh my eyes! Oh noes! Horrible!+catch_sh :: (Exception e) => Command a -> (e -> Command a) -> Command a+catch_sh action handler = do+  state <- get+  (val, newstate) <- liftIO $ catch (runStateT action state)+                                    (\e -> do runStateT (handler e) state)+  put newstate+  return val++cd :: String -> Command ()+cd = liftIO . setCurrentDirectory++mv :: String -> String -> Command ()+mv a b = liftIO $ renameFile a b++ls :: String -> Command [String]+ls dir = liftIO $ (\\ [".", ".."]) `fmap` getDirectoryContents dir++pwd :: Command String+pwd = liftIO $ getCurrentDirectory++echo, echo_n, echo_err, echo_n_err :: String -> Command ()+echo = liftIO . UTF8.putStrLn+echo_n = liftIO . (>> hFlush System.IO.stdout) . UTF8.putStr+echo_err = liftIO . UTF8.hPutStrLn stderr+echo_n_err = liftIO . (>> hFlush stderr) . UTF8.hPutStr stderr++mkdir_p :: String -> Command ()+mkdir_p = liftIO . createDirectoryIfMissing True++which :: String -> Command (Maybe String)+which = liftIO . findExecutable++canonize :: String -> Command String+canonize = liftIO . canonicalizePath++whenM :: Monad m => m Bool -> m () -> m ()+whenM c a = do res <- c+               when res a++test_e :: String -> Command Bool+test_e f = liftIO $ do+             dir <- doesDirectoryExist f+             file <- doesFileExist f+             return $ file || dir++test_f :: String -> Command Bool+test_f = liftIO . doesFileExist++test_d :: String -> Command Bool+test_d = liftIO . doesDirectoryExist++rm_rf :: String -> Command ()+rm_rf f = whenM (test_e f) $ liftIO $ removeDirectoryRecursive f++rm_f :: String -> Command ()+rm_f f = whenM (test_e f) $ liftIO $ removeFile f++system :: String -> Command ExitCode+system = liftIO . Cmd.system++run :: String -> [String] -> Command String+run cmd args = do+    (_,outH,errH,procH) <- liftIO $ runInteractiveProcess cmd args Nothing Nothing+    st <- get+    res <- liftIO $ B.hGetContents outH+    errs <- liftIO $ B.hGetContents errH+    ex <- liftIO $ waitForProcess procH+    when (sVerbose st) $ do+                 liftIO $ B.putStr res+                 liftIO $ B.putStr errs+    case ex of+      ExitSuccess -> return ()+      ExitFailure n -> fail $ "command " ++ cmd ++ " " ++ show args+                         ++ " failed with exit code " ++ show n+    put $ st { sCode = 0, sStderr = errs, sStdout = res }+    return $ B.unpack res++(#) :: String -> [String] -> Command String+cmd # args = run cmd args++silently :: Command a -> Command a+silently a = do+  x <- get+  put $ x { sVerbose = False }+  r <- a+  put x+  return r++verbosely :: Command a -> Command a+verbosely a = do+  x <- get+  put $ x { sVerbose = True }+  r <- a+  put x+  return r++sub :: Command a -> Command a+sub a = do+  -- TODO save environment as well?+  dir <- liftIO $ getCurrentDirectory+  r <- a `catch_sh` (\(e :: SomeException) -> (liftIO $ setCurrentDirectory dir) >> throw e)+  liftIO $ setCurrentDirectory dir+  return r++shellish :: MonadIO m => Command a -> m a+shellish a = do+  dir <- liftIO $ getCurrentDirectory+  time <- liftIO getCurrentTime+  r <- liftIO $ evalStateT a $ empty time+  liftIO $ setCurrentDirectory dir+  return r+      where empty t = St { sCode = 0, sStderr = B.empty,+                           sStdout = B.empty, sVerbose = True,+                           maxMem = 0, startTime = t }++strictGetContents :: Handle -> IO String+strictGetContents h =+    do res <- hGetContents h+       evaluate (length res)+       return res++withCurrentDirectory :: FilePath -> IO a -> IO a+withCurrentDirectory name m =+    bracket+        (do cwd <- getCurrentDirectory+            when (name /= "") (setCurrentDirectory name)+            return cwd)+        (\oldwd -> setCurrentDirectory oldwd `catch` (\(_ :: SomeException) -> return ()))+        (const m)++recordMemoryUsed :: Int -> Command ()+recordMemoryUsed u = do+  x <- get+  put x { maxMem = maximum [maxMem x, u] }++resetMemoryUsed :: Command ()+resetMemoryUsed = do+  x <- get+  put x { maxMem = 0 }++resetTimeUsed :: Command ()+resetTimeUsed = do+  x <- get+  t <- liftIO getCurrentTime+  put x { startTime = t }++memoryUsed :: Command Int+memoryUsed = do x <- get+                return $ maxMem x++timeUsed :: Command Float+timeUsed = do x <- get+              t <- liftIO getCurrentTime+              return $ realToFrac $ diffUTCTime t (startTime x)
+ source/Standard.hs view
@@ -0,0 +1,117 @@+module Standard( benchmarks ) where+import Prelude hiding ( catch )+import Control.Exception+import System.FilePath+import Shellish+import Benchmark hiding ( darcs )+import Definitions+import Control.Monad( forM_, filterM, when )+import Control.Monad.Trans( liftIO )+import qualified Control.Monad.State as MS++check :: BenchmarkCmd ()+check darcs _ = do+  cd "repo"+  darcs [ "check", "--no-test" ]+  return ()++repair :: BenchmarkCmd ()+repair darcs _ = do+  cd "repo"+  darcs [ "repair" ]+  return ()++annotate :: BenchmarkCmd ()+annotate darcs tr = do+  cd "repo"+  whenM ((not . or) `fmap` mapM test_e files) $ fail "no files to annotate"+  sequence_ [ whenM (test_e f) $ (darcs [ "annotate", f ] >> return ())+              | f <- files ]+  return ()+    where files = maybe id (:) (trAnnotate tr) [ "Setup.hs", "Setup.lhs" ]++get :: [String] -> BenchmarkCmd ()+get param darcs _ = do+  darcs $ "get" : param ++ ["repo", "get"]+  return ()++pull :: Int -> BenchmarkCmd ()+pull n darcs _ = do+  cd "repo"+  rm_f "_darcs/patches/unrevert"+  darcs [ "obliterate", "--last", show n, "--all" ]+  reset -- the benchmark starts here+  darcs [ "pull", "--all" ]+  return ()++darcs_wh :: [String] -> BenchmarkCmd ()+darcs_wh param darcs _ = (darcs ("whatsnew" : param) >> return ()) `catch_sh` handle+  where handle (RunFailed _ 1 _) = return ()+        handle e = throw e++wh :: BenchmarkCmd ()+wh darcs tr = do+  cd "repo"+  darcs_wh [] darcs tr+  return ()++wh_mod :: BenchmarkCmd ()+wh_mod darcs tr = do+  cd "repo"+  files <- filterM test_f =<< ls "."+  when (null files) $ fail "no files to modify in repo root!"+  forM_ files $ \f -> mv f $ f <.> "__foo__"+  darcs_wh [] darcs tr+  forM_ files $ \f -> mv (f <.> "__foo__") f+  return ()++wh_l :: BenchmarkCmd ()+wh_l darcs tr = do+  cd "repo"+  darcs_wh [ "--look-for-adds" ] darcs tr+  return ()++-- | n patches for each file+record :: BenchmarkCmd ()+record darcs _ = do+ cd "repo"+ files <- filterM test_f =<< ls "."+ forM_ files $ \f -> liftIO (appendFile f "x")+ darcs [ "record", "--author", "bench <bench@example.com>", "--all", "-m", "test record", "--no-test"]+ darcs [ "obliterate", "--last", "1", "--all" ]+ return ()++revert :: BenchmarkCmd ()+revert darcs _ = do+ cd "repo"+ files <- filterM test_f =<< ls "."+ forM_ files $ \f -> liftIO (appendFile f "foo")+ darcs [ "revert", "--all" ]+ return ()++revert_unrevert :: BenchmarkCmd ()+revert_unrevert darcs _ = do+ cd "repo"+ files <- filterM test_f =<< ls "."+ forM_ files $ \f -> liftIO (appendFile f (show "foo"))+ darcs [ "revert", "--all" ]+ darcs [ "unrevert", "--all" ]+ darcs [ "revert", "--all" ]+ return ()++benchmarks :: [ Benchmark () ]+benchmarks =+       [ Idempotent "wh" FastB wh+       , Idempotent "wh mod" FastB wh_mod+       , Idempotent "wh -l" FastB wh_l+       , Idempotent "record" FastB $ record+       , Idempotent "revert" FastB revert+       , Idempotent "(un)revert" FastB revert_unrevert+       , Destructive "get (full)" SlowB $ get []+       , Destructive "get (lazy)" FastB $ get ["--lazy"]+       , Idempotent "pull 100"    FastB $ pull 100+       , Idempotent "pull 1000" SlowB $ pull 1000+       , Idempotent "check" SlowB check+       , Idempotent "repair" SlowB repair+       , Idempotent "annotate" SlowB annotate+       ]
+ source/TabularRST.hs view
@@ -0,0 +1,70 @@+module TabularRST where++import Data.List (intersperse, transpose)+import Text.Tabular++-- RST renderer for tabular+-- (being incubated; when this matures, it should become part of tabular)++-- | for simplicity, we assume that each cell is rendered+--   on a single line+render :: (rh -> String)+       -> (ch -> String)+       -> (a -> String)+       -> Table rh ch a+       -> String+render fr fc f (Table rh ch cells) =+  unlines $ [ bar DoubleLine+            , renderColumns sizes ch2+            , bar DoubleLine+            ] +++            (renderRs $ fmap renderR $ zipHeader [] cells $ fmap fr rh) +++            [ bar DoubleLine, "" ] -- note the extra blank line+ where+  bar = concat . renderTHLine sizes ch2+  renderTHLine _ _ NoLine = []+  renderTHLine w h SingleLine = [renderHLine' w '-' h]+  renderTHLine w h DoubleLine = [renderHLine' w '=' h]+  -- ch2 and cell2 include the row and column labels+  ch2 = Group DoubleLine [Header "", fmap fc ch]+  cells2 = headerContents ch2+         : zipWith (\h cs -> h : map f cs) rhStrings cells+  --+  renderR (cs,h) = renderColumns sizes $ Group DoubleLine+                    [ Header h+                    , fmap fst $ zipHeader "" (map f cs) ch]+  rhStrings = map fr $ headerContents rh+  -- maximum width for each column+  sizes   = map (maximum . map length) . transpose $ cells2+  renderRs (Header s)   = [s]+  renderRs (Group p hs) = concat . intersperse sep . map renderRs $ hs+    where sep = renderHLine sizes ch2 p++-- | We stop rendering on the shortest list!+renderColumns :: [Int] -- ^ max width for each column+              -> Header String+              -> String+renderColumns is h = coreLine+ where+  coreLine = concatMap helper $ flattenHeader $ zipHeader 0 is h+  helper = either hsep (uncurry padLeft)+  hsep :: Properties -> String+  hsep _ = "  "++renderHLine :: [Int] -- ^ width specifications+            -> Header String+            -> Properties+            -> [String]+renderHLine _ _ _ = []++renderHLine' :: [Int] -> Char -> Header String -> String+renderHLine' is sep h = coreLine+ where+  coreLine        = concatMap helper $ flattenHeader $ zipHeader 0 is h+  helper          = either vsep dashes+  dashes (i,_)    = replicate i sep+  vsep _          = "  "++padLeft :: Int -> String -> String+padLeft l s = padding ++ s+ where padding = replicate (l - length s) ' '
+ source/main.hs view
@@ -0,0 +1,146 @@+#!/usr/bin/env runhaskell+import Shellish( shellish, rm_rf )+import Control.Arrow ( first, second )+import System.Console.CmdArgs+import System.Cmd ( system )+import System.Exit+import System.Directory+import System.IO+import Control.Monad+import Control.Monad.Trans+import Benchmark+import Config (Config(Get,Run,Dist,Report))+import Definitions+import qualified Config as C+import Report+import Run+import Standard+import Dist ( createTarball )+import Download+import Data.List+import Data.Version ( showVersion )+import Paths_darcs_benchmark+import Text.JSON+import Data.IORef++help :: String+help = unlines [ "darcs-benchmark " ++ showVersion version ++ ": run standard darcs benchmarks"+               , ""+               , "Please either specify the repositories and binaries to run on like this:"+               , "$ darcs-benchmark run <binary> [binary] [/ [repository] [repository]]"+               , ""+               , "or alternatively, to run on all available repos:"+               , "$ darcs-benchmark run <binary> [binary]"+               , ""+               , "You can also create a file called 'config' in the working directory."+               , "Put two lines in it, one with list of binaries, one with list of repos:"+               , ""+               , "binary binary binary"+               , "repo repo repo"+               , ""+               , "(again, if the second line is not there, we run on all available repos)"+               , ""+               , "Thank you for benchmarking darcs!" ]++known_repos :: [String]+known_repos = [ "tabular", "tahoe-lafs", "darcs", "ghc-hashed" ]++download_repos :: [String] -> IO ()+download_repos r = forM_ (if null r then known_repos else r) download++doVMFlush :: FilePath -> IO ()+doVMFlush path = do system "sudo sh -c 'echo 3 > /proc/sys/vm/drop_caches'"+                    system $ path ++ " --version > /dev/null"+                    return ()++config :: [TestRepo] -> C.Config -> IO ([(TestRepo,[Benchmark ()])], [String])+config allrepos cfg = do+  case cfg of+   Get {} -> do+      download_repos (C.repos cfg)+      exitWith ExitSuccess+   Dist {} -> do+      createTarball (C.repo cfg)+      exitWith ExitSuccess+   Report {} ->  do+      shellish printCumulativeReport+      exitWith ExitSuccess+   Run {} -> do+     haveConf <- doesFileExist "config"+     conf <- if haveConf then lines `fmap` readFile "config"+                         else return []+     let confrepos = if length conf > 1 then (words $ conf !! 1) else []+         confbins = if length conf > 0 then words $ conf !! 0 else []+         (bins,repos) = second (drop 1) $ break (== "/") (C.extra cfg)+         userepos = if null repos then confrepos else repos+         usebins = if null bins then confbins else bins+         usetests' = if C.fast cfg then filter (\b -> speed b == FastB) benchmarks else benchmarks+         usetests = case C.only cfg of+                     [] -> usetests'+                     os -> filter (\b -> any (\o -> o `isInfixOf` show b) os) usetests'+     let setParams p = p { pFlush = if C.cold cfg then Just doVMFlush else Nothing }+     modifyIORef global (first setParams)+     filterTests <- if C.converge cfg+                       then do pstampPath <- (paramStampPath . fst) `fmap` readIORef global+                               ts <- readTimingsForParams pstampPath+                               return $ \r bs -> case lookup (trCoreName r) (sufficientData ts) of+                                                   Nothing -> bs+                                                   Just xs -> filter (\b -> not (description b `elem` xs)) bs+                       else return (const id)+     when (null usebins) $ do+       hPutStrLn stderr "Please specify at least one darcs binary to benchmark"+       exitWith (ExitFailure 1)+     let finalrepos = if null userepos then allrepos+                         else filter (\r -> any (matchRepo r) userepos) allrepos+         finalbins  = usebins+     return ( map (\r -> (r, filterTests r usetests)) finalrepos+            , finalbins+            )+ where+  dropPrefix p x = if p `isPrefixOf` x then drop (length p) x else x+  matchRepo tr x = trName tr == x || dropPrefix "repo." (trPath tr) == x++testRepoFromDir :: String -> TestRepo+testRepoFromDir d = TestRepo n n d Nothing [toVariant DefaultVariant] []+ where+  n = drop (length "repo.") d++main :: IO ()+main = do+    cfg <- cmdArgs help C.defaultConfig+    allrepos <- do listing <- getDirectoryContents "."+                   configs <- mapM readC $+                                  filter ((/='~') . last) $+                                  filter ("config." `isPrefixOf`) listing+                   let other = [ testRepoFromDir d | d <- listing+                                                   , "repo." `isPrefixOf` d+                                                   , d `notElem` map trPath configs ]+                   return (configs ++ other)+    (reposNtests, binaries') <- config allrepos cfg+    let repos = map fst reposNtests+    unless (null $ repos \\ allrepos) $ do+         let name r = intercalate ", " $ map trName r+         putStrLn $ "Missing repositories: " ++ name (repos \\ allrepos)+         exitWith $ ExitFailure 2+    binaries <- forM binaries' check_vcs+    mapM_ appendBinary binaries+    when (null repos) $ do+         putStrLn $ "Oops, no repositories! Consider doing a darcs-benchmark get."+         putStrLn $ "(Alternatively, check that you are in the right directory.)"+         exitWith $ ExitFailure 3+    shellish $ do rm_rf "_playground"+                  -- for setting up variants, we probably want the latest/greatest+                  -- version of darcs in case the variant involves some new feature+                  case reverse binaries of+                    []    -> return ()+                    (b:_) -> setupVariants repos b+                  res <- benchMany reposNtests binaries+                  if C.dump cfg+                     then liftIO $ print res+                     else renderMany res+ where+  readC f =+   do mj <- (resultToEither . decode) `fmap` readFile f+      case mj of+        Left e  -> fail $ "Could not read " ++ f ++ ": " ++ show e+        Right j -> return $ j