packages feed

hats-0.1.0.0: bench/Bench.hs

{-# LANGUAGE OverloadedStrings #-}
module Main
    ( main
    ) where

import Control.Monad (replicateM_, void)
import Control.Concurrent.Async (async, wait)
import Control.Concurrent.STM ( STM, TVar, atomically, modifyTVar
                              , newTVarIO, readTVar, retry
                              )
import Criterion.Main ( Benchmark, defaultMain, bgroup, bench
                      , env, nf, whnfIO
                      )
import Data.Attoparsec.ByteString.Char8 (IResult (..), Result, parse)
import Data.ByteString.Lazy.Builder (lazyByteString, toLazyByteString)

import Network.Nats
import Network.Nats.Message.Message (Message (..))
import Network.Nats.Message.Parser (parseMessage)
import Network.Nats.Message.Writer (writeMessage)

import qualified Data.ByteString.Char8 as BS
import qualified Data.ByteString.Lazy.Char8 as LBS

main :: IO ()
main = defaultMain suite

suite :: [Benchmark]
suite =
    [ bgroup "pub-writer"
        [ env smallPubMessages $ \xs ->
            bench "million * 48 bytes" $ nf writePubs xs

        , env mediumPubMessages $ \xs ->
            bench "million * 480 bytes" $ nf writePubs xs

        , env largePubMessages $ \xs ->
            bench "million * 4800 bytes" $ nf writePubs xs
        ]
    , bgroup "msg-parser"
        [ env smallMsgMessages $ \xs ->
            bench "million * 48 bytes" $ nf parseMsgs xs

        , env mediumMsgMessages $ \xs ->
            bench "million * 480 bytes" $ nf parseMsgs xs

        , env largeMsgMessages $ \xs ->
            bench "million * 4800 bytes" $ nf parseMsgs xs
        ]
    , bgroup "pubsub-nats"
        [ bench "million pub" $ whnfIO (pubPerf million)
        , bench "100000 pubsub/queue" $ whnfIO (pubSubPerf a100000)
        , bench "100000 pubsub/async" $ whnfIO (pubSubAsyncPerf a100000)
        ]
    ]

-- | Write a list of Pub messages to a list of lazy ByteStrings.
writePubs :: [Message] -> [LBS.ByteString]
writePubs = map writeMessage

-- | Parse a list of ByteStrings to a list of Msg messages.
parseMsgs :: [BS.ByteString] -> [Message]
parseMsgs = map (fromResult . parse parseMessage)
    where
      fromResult :: Result Message -> Message
      fromResult (Done _ msg)   = msg
      fromResult (Partial cont) = fromResult (cont "")
      fromResult _              = error "Shall not happen"

-- | Send the given number of Pub messages containing the payload "hello".
-- This benchmark requires a running NATS server.
pubPerf :: Int -> IO ()
pubPerf rep =
    withNats defaultSettings [defaultURI] $ \nats ->
        replicateM_ rep $ publish nats "bench" Nothing "hello"

pubSubPerf :: Int -> IO ()
pubSubPerf rep =
    withNats defaultSettings [defaultURI] $ \nats -> do
        (_, queue) <- subscribe nats "bench" Nothing
        rec <- async $ receiver queue rep

        replicateM_ rep $ publish nats "bench" Nothing "hello"
        wait rec

receiver :: MsgQueue -> Int -> IO ()
receiver queue limit = go 0
    where
      go :: Int -> IO ()
      go cnt
        | cnt == limit = return ()
        | otherwise    = do
            void $ nextMsg queue
            go (cnt + 1)

-- | Send the given number of Pub messages containing the payload "hello"
-- Subscribe to and receive - using asyncronous subscription - the 
-- same number of messages.
-- This benchmark requires a running NATS server.
pubSubAsyncPerf :: Int -> IO ()
pubSubAsyncPerf rep =
    withNats defaultSettings [defaultURI] $ \nats -> do
        cnt <- newTVarIO 0
        void $ subscribeAsync nats "bench" Nothing $ asyncReceiver cnt
        replicateM_ rep $ publish nats "bench" Nothing "hello"

        atomically $ waitForValue cnt rep

asyncReceiver :: TVar Int -> Msg -> IO ()
asyncReceiver cnt _ = atomically $ modifyTVar cnt (+ 1)

waitForValue :: TVar Int -> Int -> STM ()
waitForValue tvar value = do
    value' <- readTVar tvar
    if value' /= value
        then retry
        else return ()

million :: Int
million = 1000000

a100000 :: Int
a100000 = 100000

small :: Int
small = 1

medium :: Int
medium = 10

large :: Int
large = 100

smallPubMessages :: IO [Message]
smallPubMessages = million `pubMessages` small

mediumPubMessages :: IO [Message]
mediumPubMessages = million `pubMessages` medium

largePubMessages :: IO [Message]
largePubMessages = million `pubMessages` large

smallMsgMessages :: IO [BS.ByteString]
smallMsgMessages = million `msgMessages` small

mediumMsgMessages :: IO [BS.ByteString]
mediumMsgMessages = million `msgMessages` medium

largeMsgMessages :: IO [BS.ByteString]
largeMsgMessages = million `msgMessages` large

pubMessages :: Int -> Int -> IO [Message]
pubMessages rep size = return $ replicate rep (pubMessage size)

msgMessages :: Int -> Int -> IO [BS.ByteString]
msgMessages rep size = do
    let xs = replicate rep (msgMessage size)
    return $ map (LBS.toStrict . writeMessage) xs

pubMessage :: Int -> Message
pubMessage = PUB "TOPIC.INBOX" (Just "REPLY.INBOX") . replicatePayload

msgMessage :: Int -> Message
msgMessage = 
    MSG "TOPIC.INBOX" 123456 (Just "REPLY.INBOX") . replicatePayload

replicatePayload :: Int -> LBS.ByteString
replicatePayload n =
    let p = map lazyByteString $ replicate n payloadChunk
    in toLazyByteString $ mconcat p

-- | A basic "random" payload chunk with 48 characters.
payloadChunk :: LBS.ByteString
payloadChunk = "pq01ow92ie83ue74ur74yt65jf82nc8emr8dj48v.dksme2z"

defaultURI :: String
defaultURI = "nats://localhost:4222"