packages feed

redis-0.12: Benchmark.hs

{-
Copyright (c) 2010-2011, Alexander Bogdanov <andorn@gmail.com>
License: MIT
-}

import System (getArgs)
import System.Exit (exitFailure, exitSuccess)
import System.Console.GetOpt
import Data.Time.Clock
import Database.Redis.Redis
import Control.Concurrent.MVar
import Control.Concurrent
import Control.Monad (when)

makeConnections :: Int -> String -> String -> Int -> IO [Redis]
makeConnections count host port db = mapM mkConn [0 .. (count - 1)]
    where mkConn n = do r <- connect host port
                        select r db
                        return r

-- Worker :: Redis -> worker prefix -> loop number -> lock -> unlock -> IO ()
type Worker = Redis -> String -> Int -> MVar () -> MVar () -> IO ()

-- runWorkers :: Redis -> total loops count -> Worker -> IO time passed
runWorkers :: [Redis] -> Int -> Worker -> IO NominalDiffTime
runWorkers rs loops worker
           | workers > 1 = do mvs <- mapM fork $ zip [0 .. (workers - 1)] rs
                              t <- getCurrentTime
                              mapM_ (flip putMVar () . fst) mvs
                              mapM_ (takeMVar . snd ) mvs
                              flip diffUTCTime t `fmap` getCurrentTime
           | otherwise   = do mv <- newMVar ()
                              t <- getCurrentTime
                              worker (head rs) (prefix 0) loops mv mv
                              flip diffUTCTime t `fmap` getCurrentTime
    where fork (n, r) = do lock <- newEmptyMVar
                           unlock <- newEmptyMVar
                           if n < workers - 1
                             then forkIO $ worker r (prefix n) count lock unlock
                             else forkIO $ worker r (prefix n) (loops - (count * (workers - 1))) lock unlock
                           return (lock, unlock)
          count = loops `quot` (workers - 1)
          workers = length rs
          prefix n = (show n) ++ ":"

printResult name loops t = do putStrLn $ name ++ ": " ++ (show t)
                              putStrLn $ (show $ (fromIntegral loops) / t) ++ " per second"

worker_set :: Worker
worker_set r x n l u = do takeMVar l
                          loop sets
                          putMVar u ()
    where sets = zip keys vals
          keys = map ((x ++) . show) [1..n]
          vals = [1..n]
          loop [] = return ()
          loop ((k, v) : s) = set r k v >> loop s

worker_get :: Worker
worker_get r x n l u = do takeMVar l
                          loop keys
                          putMVar u ()
    where keys = map ((x ++) . show) [1..n]
          loop [] = return ()
          loop (k:s) = (get r k :: IO (Reply ())) >> loop s

worker_lpush :: Worker
worker_lpush r x n l u = do takeMVar l
                            loop vals
                            putMVar u ()
    where vals = [1..n]
          key = x ++ "lst"
          loop = mapM_ (lpush r key)

worker_lpop :: Worker
worker_lpop r x n l u = do takeMVar l
                           loop n
                           putMVar u ()
    where key = x ++ "lst"
          loop n = mapM_ (\_ -> lpop r key :: IO (Reply ())) [1..n]

data Opt = Opt { optHost     :: String,
                 optPort     :: String,
                 optDatabase :: Int,
                 optClients  :: Int,
                 optRequests :: Int,
                 optHelp     :: Bool}
           deriving Show

defaultOpts = Opt localhost defaultPort 6 50 100000 False

options :: [OptDescr (Opt -> Opt)]
options = [Option ['h'] ["host"] (OptArg (maybe id (\h o -> o{optHost = h})) "HOSTNAME") ("Server hostname (default " ++ localhost ++ ")"),
           Option ['p'] ["port"] (OptArg (maybe id (\p o -> o{optPort = p})) "PORT") ("Server port (default " ++ defaultPort ++ ")"),
           Option ['d'] ["database"] (OptArg (maybe id (\d o -> o{optDatabase = read d})) "DATABASE") "Database number (default 6)",
           Option ['c'] ["clients"] (OptArg (maybe id (\c o -> o{optClients = read c})) "CLIENTS") "Number of parallel connections (default 50)",
           Option ['n'] ["requests"] (OptArg (maybe id (\n o -> o{optRequests = read n})) "REQUESTS") "Total number of requests (default 100000)",
           Option [] ["help"] (NoArg (\o -> o{optHelp = True})) "Show this usage info"]

main :: IO ()
main = do args <- getArgs
          when (length args == 0) $ do putStrLn $ usageInfo "Usage: " options
                                       exitSuccess

          opts <- case getOpt Permute options args of
                    (o, [], []) -> return $ foldl (flip id) defaultOpts o
                    (_, n, [])  -> do putStrLn $ "Unrecognized arguments: " ++ concat n ++ usageInfo "\nUsage: " options
                                      exitFailure
                    (_, _, es)  -> do putStrLn $ concat es ++ usageInfo "\nUsage:" options
                                      exitFailure

          when (optHelp opts) $ do putStrLn $ usageInfo "Usage:" options
                                   exitSuccess

          r <- connect (optHost opts) (optPort opts)
          select r $ optDatabase opts
          flushDb r

          rs <- makeConnections (optClients opts) (optHost opts) (optPort opts) (optDatabase opts)

          t <- runWorkers rs (optRequests opts) worker_set
          printResult "set" (optRequests opts) t

          t <- runWorkers rs (optRequests opts) worker_get
          printResult "get" (optRequests opts) t

          t <- runWorkers rs (optRequests opts) worker_lpush
          printResult "lpush" (optRequests opts) t

          t <- runWorkers rs (optRequests opts) worker_lpop
          printResult "lpop" (optRequests opts) t

          putStrLn "Done."