packages feed

sync-mht-0.3.8.0: src/Benchmarks.hs

{-# LANGUAGE DeriveGeneric #-}
import Control.Monad
import Data.Aeson hiding (Result)
import qualified Data.ByteString.Lazy as BS
import Data.List
import Data.Ratio
import Data.Time.Clock
import GHC.Generics
import System.Directory
import System.IO.Temp
import System.FilePath
import System.Process
import System.Environment

data Times
    = Times
      { t_syncMht :: Integer
      , t_rsync :: Integer
      , t_size :: Integer
      }
      deriving Generic
instance ToJSON Times

data Result
    = Result
      { r_full :: Times
      , r_patch :: Times
      , r_sample :: Sample
      }
      deriving Generic
instance ToJSON Result

data Summary
    = Summary
    { s_results :: [Result]
    , s_tag :: String
    }
    deriving Generic
instance ToJSON Summary

_RESOLUTION_ :: (Fractional a) => a
_RESOLUTION_ = fromRational $ 1000 % 1

withTime :: IO () -> IO Integer
withTime action =
    do start <- getCurrentTime
       action
       end <- getCurrentTime
       return $ round $ _RESOLUTION_ * (diffUTCTime end start)

runCmd :: [String] -> IO ()
runCmd = callCommand . intercalate " "

_SSH_PREFIX_ :: String
_SSH_PREFIX_ = "ssh"

_LOCALHOST_ :: String
_LOCALHOST_ = "127.0.0.1"

realFile :: String -> Bool
realFile = (`notElem` [".", ".."])

data Sample
    = Sample
    { s_ghAccount :: String
    , s_ghRepo :: String
    , s_startTag :: String
    , s_diffTag :: String
    }
    deriving Generic
instance ToJSON Sample

samples :: [Sample]
samples =
    [ Sample
      { s_ghAccount = "gionkunz"
      , s_ghRepo = "chartist-js"
      , s_startTag = "60e94d35e2e023f90e14b8907eb1115c9270e7ad"
      , s_diffTag = "ffddebc1c819f14d3982af5c185f9f619249020c"
      }
    , Sample
      { s_ghAccount = "JuliaLang"
      , s_ghRepo = "julia"
      , s_startTag = "3cb9b4879d9da4e14caa79fa6a1ed71fa93871e7"
      , s_diffTag = "5b40f2bd35d87b267dc1ff1cfc6fd7e07670e89c"
      }
    , Sample
      { s_ghAccount = "google"
      , s_ghRepo = "closure-compiler"
      , s_startTag = "2b748b89f8737f3338c0cbe027aa09df1dc8fcaa"
      , s_diffTag = "7e74ada6effa6d429401e2f363a79594a123f269"
      }
    , Sample
      { s_ghAccount = "atom"
      , s_ghRepo = "atom"
      , s_diffTag = "321bf690d214d229b132a4bc03c2f249aa670340"
      , s_startTag = "27cc9cdff3521d5fd5b1725f8e354a2df91117b1"
      }
    ]

sourceDir :: FilePath -> FilePath
sourceDir = (</> "src")

sourceDataDir :: FilePath -> Sample -> FilePath
sourceDataDir baseDir s = (sourceDir baseDir) </> (s_ghRepo s ++ "-" ++ s_startTag s)

targetDir :: FilePath -> String -> FilePath
targetDir fp s = (fp </> "src" </> s)

prepare :: Sample -> FilePath -> IO (FilePath, Integer, Integer)
prepare s benchmarks =
    do createDirectoryIfMissing True benchmarks
       createDirectory baseDir
       createDirectory src
       mapM (createDirectory . targetDir baseDir) ["rsync", "sync-mht"]
       mapM (runCmd . (["wget","-P",src]++) . (:[]) . (gitHubPrefix ++))
           [ "/archive/" ++ startTagZip
           , "/commit/" ++ (s_diffTag s) ++ ".diff"
           ]
       runCmd ["unzip", src </> startTagZip, "-d", src]
       fSize <- readCreateProcess
           (shell "zip -0 -r - . | wc -c") { cwd = Just $ sourceDataDir baseDir s } ""
       pSize <- readCreateProcess
           (shell $ concat ["cat ", sourceDir baseDir </> (s_diffTag s ++ ".diff"), " | wc -c"]) ""
       return (baseDir, read fSize, read pSize)
    where
      gitHubPrefix = "https://github.com/" ++ (s_ghAccount s) ++ "/" ++ (s_ghRepo s)
      startTagZip = (s_startTag s) ++ ".zip"
      src = sourceDir baseDir
      baseDir = benchmarks </> (s_ghAccount s ++ "-" ++ s_ghRepo s)

measure :: Integer -> String -> Sample -> FilePath -> IO Times
measure size syncMhtPath s baseDir =
    do let srcData = sourceDataDir baseDir s
       tSyncMht <- withTime $ runCmd
           [ syncMhtPath, "-a", "-u", "--delete", "-s", srcData, "-d"
           , "remote:" ++ (targetDir baseDir "sync-mht"), "-r"
           , "'" ++ _SSH_PREFIX_ ++ " " ++ _LOCALHOST_ ++ " " ++ syncMhtPath ++ "'"
           ]
       tRsync <- withTime $ runCmd
           [ "rsync", "-rtz", "--delete", "-e", "'"++_SSH_PREFIX_++"'", srcData
           , _LOCALHOST_ ++ ":" ++ (targetDir baseDir "rsync")
           ]
       return $
           Times
           { t_syncMht = tSyncMht
           , t_rsync = tRsync
           , t_size = size
           }

-- | Compare synchronization speeds of rsync vs. sync-mht using (patches of) GitHub repositories
-- Note: To make the comparison fair, we need to run sync-mht (just as rsync using a shell call).
-- Requires: patch, ssh, sshd listening on port 22, passwordless login for loopback ssh
main :: IO ()
main = withSystemTempDirectory "bench" $ \benchmarks ->
    do [syncMhtPath,tag] <- getArgs
       results <- forM samples $ \s ->
           do (baseDir, fSize, pSize) <- prepare s benchmarks
              fullTimes <- measure fSize syncMhtPath s baseDir
              runCmd
                  [ "patch", "-p1", "-d", sourceDataDir baseDir s, "-i"
                  , sourceDir baseDir </> (s_diffTag s ++ ".diff")
                  ]
              patchTimes <- measure pSize syncMhtPath s baseDir
              return $
                  Result
                  { r_full = fullTimes
                  , r_patch = patchTimes
                  , r_sample = s
                  }
       BS.writeFile "benchmarks.json" $ encode $
           Summary
           { s_results = results
           , s_tag = tag
           }