Here's a much simpler implementation for that sort of pattern, using channels to fan out work to threads. I added a dependency on Criterion because getCPUTime is basically useless for this kind of measurement on Mac OS X since it doesn't include the time that the process spent waiting on IO:
{-# Language OverloadedStrings #-}
import System.CPUTime
import Network.Socket hiding(recv)
import Network.Socket.ByteString
import Control.Exception (handle, IOException)
import System.Environment
import Control.Concurrent
import Control.Monad
import Data.Either (partitionEithers)
import Data.List (intercalate)
import Criterion.Measurement (time, time_, secs)
main :: IO ()
main = do
(host, port, conc, reqs) <- fmap parse getArgs
putStrLn $ "Connecting to " ++ host ++ " " ++ port
(servAddr:_) <- getAddrInfo Nothing (Just host) (Just port)
(diff, results) <- time $ process servAddr conc reqs
let (errs, succs) = partitionEithers results
numSuccs = length succs
numErrs = length errs
succTime = sum succs
succAvg = succTime / fromIntegral numSuccs
putStrLn $ unwords
[show numSuccs, "successes,", show numErrs, "errors in", secs diff]
when (numSuccs > 0) $ do
putStrLn $ "min/max/avg request time: " ++
intercalate " / " (map secs [minimum succs, maximum succs, succAvg])
putStrLn $ show (round (fromIntegral reqs / diff) :: Int) ++ " r/s"
parse :: [String] -> (String, String, Int, Int)
parse [h,p,conc,reqs] = (h, p, read conc, read reqs)
parse _ = error "usage client host port concurrency requests"
process :: AddrInfo -> Int -> Int -> IO [Either IOException Double]
process servAddr conc reqs = do
reqChan <- newChan
ackChan <- newChan
let processThread = forever $ do
_ <- readChan reqChan
handle (return . Left) (fmap Right socketAction) >>= writeChan ackChan
socketAction = time_ $ do
sock <- socket (addrFamily servAddr) Stream defaultProtocol
connect sock (addrAddress servAddr)
sendAll sock "GET /\r\n\r\n"
void $ recv sock 1024
close sock
replicateM_ reqs (writeChan reqChan ())
replicateM_ (min conc reqs) (forkIO processThread)
replicateM reqs (readChan ackChan)