packages feed

streamly-process-0.1.0: Benchmark/System/Process.hs

{-# LANGUAGE  ScopedTypeVariables #-}

module Main (main) where

import Control.Exception (finally)
import Data.Either (isRight, fromRight, isLeft, fromLeft)
import Data.Word (Word8)
import Gauge (defaultMain, bench, nfIO)
import System.Directory (removeFile, findExecutable)
import System.IO
    ( Handle
    , IOMode(..)
    , openFile
    , hClose
    )
import System.Process (proc, createProcess, waitForProcess, callCommand)

import qualified Streamly.Data.Fold as FL
import qualified Streamly.FileSystem.Handle as FH
import qualified Streamly.Prelude as S
import qualified Streamly.System.Process as Proc

-- Internal imports
import qualified Streamly.Internal.FileSystem.Handle
    as FH (toBytes, toChunks, putBytes, putChunks)
import qualified Streamly.Internal.System.Process as Proc

-- XXX replace with streamly versions once they are fixed
{-# INLINE rights #-}
rights :: (S.IsStream t, Monad m, Functor (t m)) => t m (Either a b) -> t m b
rights = fmap (fromRight undefined) . S.filter isRight

{-# INLINE lefts #-}
lefts :: (S.IsStream t, Monad m, Functor (t m)) => t m (Either a b) -> t m a
lefts = fmap (fromLeft undefined) . S.filter isLeft

-------------------------------------------------------------------------------
-- Constants and utils
-------------------------------------------------------------------------------

_a :: Word8
_a = 97

-- XXX portability on macOS
devRandom :: String
devRandom = "/dev/urandom"

devNull :: String
devNull = "/dev/null"

which :: String -> IO FilePath
which cmd = do
    r <- findExecutable cmd
    case r of
        Just path -> return path
        _ -> error $ "Required command " ++ cmd ++ " not found"

-------------------------------------------------------------------------------
-- Create a data file filled with random data
-------------------------------------------------------------------------------

ddBlockSize :: Int
ddBlockSize = 1024 * 1024

ddBlockCount :: Int
ddBlockCount = 10

largeByteFile :: String
largeByteFile = "./largeByteFile"

generateByteFile :: IO ()
generateByteFile = do
    ddPath <- which "dd"
    let procObj = proc ddPath [
                "if=" ++ devRandom,
                "of=" ++ largeByteFile,
                "count=" ++ show ddBlockCount,
                "bs=" ++ show ddBlockSize
            ]

    (_, _, _, procHandle) <- createProcess procObj
    _ <- waitForProcess procHandle
    return ()

-------------------------------------------------------------------------------
-- Create a file filled with ascii chars
-------------------------------------------------------------------------------

largeCharFile :: String
largeCharFile = "./largeCharFile"

numCharInCharFile :: Int
numCharInCharFile = 10 * 1024 * 1024

generateCharFile :: IO ()
generateCharFile = do
    handle <- openFile largeCharFile WriteMode
    FH.putBytes handle (S.replicate numCharInCharFile _a)
    hClose handle

-------------------------------------------------------------------------------
-- Create a utility that writes to stderr
-------------------------------------------------------------------------------

trToStderr :: String
trToStderr = "./writeTrToError.sh"

trToStderrContent :: String
trToStderrContent =
    "tr [a-z] [A-Z] <&0 >&2"

createExecutable :: IO ()
createExecutable = do
    writeFile trToStderr trToStderrContent
    callCommand ("chmod +x " ++ trToStderr)

-------------------------------------------------------------------------------
-- Create and delete the temp data/exec files
-------------------------------------------------------------------------------

generateFiles :: IO ()
generateFiles = do
    createExecutable
    generateByteFile
    generateCharFile

deleteFiles :: IO ()
deleteFiles = do
    removeFile trToStderr
    removeFile largeByteFile
    removeFile largeCharFile

-------------------------------------------------------------------------------
-- Benchmark functions
-------------------------------------------------------------------------------

toBytes' :: String-> Handle -> IO ()
toBytes' catPath outH =
    FH.putBytes outH
        $ rights
        $ Proc.toBytes' catPath [largeByteFile]

toChunks' :: String -> Handle -> IO ()
toChunks' catPath hdl =
    FH.putChunks hdl
        $ rights
        $ Proc.toChunks' catPath [largeByteFile]

processBytes' :: String-> Handle -> IO ()
processBytes' trPath outputHdl = do
    inputHdl <- openFile largeCharFile ReadMode
    _ <- S.fold (FL.partition (FH.write outputHdl) (FH.write outputHdl))
        $ Proc.processBytes'
            trPath
            ["[a-z]", "[A-Z]"]
        $ FH.toBytes inputHdl
    hClose inputHdl

processBytes :: String-> Handle -> IO ()
processBytes trPath outputHdl = do
    inputHdl <- openFile largeCharFile ReadMode
    FH.putBytes outputHdl
        $ Proc.processBytes
            trPath
            ["[a-z]", "[A-Z]"]
        $ FH.toBytes inputHdl
    hClose inputHdl

processBytesToStderr :: Handle -> IO ()
processBytesToStderr outputHdl = do
    inputHdl <- openFile largeCharFile ReadMode
    FH.putBytes outputHdl
        $ lefts
        $ Proc.processBytes'
            trToStderr
            ["[a-z]", "[A-Z]"]
        $ FH.toBytes inputHdl
    hClose inputHdl

processChunks :: String -> Handle -> IO ()
processChunks trPath outputHdl = do
    inputHdl <- openFile largeCharFile ReadMode
    FH.putChunks outputHdl $
        Proc.processChunks
            trPath
            ["[a-z]", "[A-Z]"]
        $ FH.toChunks inputHdl
    hClose inputHdl

processChunks' :: String -> Handle -> IO ()
processChunks' trPath outputHdl = do
    inputHdl <- openFile largeCharFile ReadMode
    _ <- S.fold
            (FL.partition
                (FH.writeChunks outputHdl) (FH.writeChunks outputHdl)
            )
        $ Proc.processChunks'
            trPath
            ["[a-z]", "[A-Z]"]
            (FH.toChunks inputHdl)
    hClose inputHdl

processChunksToStderr :: Handle -> IO ()
processChunksToStderr outputHdl = do
    inputHdl <- openFile largeCharFile ReadMode
    FH.putChunks outputHdl
        $ lefts
        $ Proc.processChunks'
            trToStderr
            ["[a-z]", "[A-Z]"]
            (FH.toChunks inputHdl)
    hClose inputHdl

-------------------------------------------------------------------------------
-- Main
-------------------------------------------------------------------------------

main :: IO ()
main = do
    putStrLn "Generating files..."
    generateFiles
    trPath <- which "tr"
    catPath <- which "cat"
    nullH <- openFile devNull WriteMode
    putStrLn "Running benchmarks..."

    defaultMain
        [ bench "toBytes'" $ nfIO $ toBytes' catPath nullH
        , bench "toChunks'" $ nfIO $ toChunks' catPath nullH
        , bench "processBytes tr" $ nfIO $ processBytes trPath nullH
        , bench "processBytes' tr" $ nfIO $ processBytes' trPath nullH
        , bench "processBytesToStderr tr" $ nfIO $ processBytesToStderr nullH
        , bench "processChunks tr" $ nfIO (processChunks trPath nullH)
        , bench "processChunks' tr" $ nfIO (processChunks' trPath nullH)
        , bench "processChunksToStderr" $ nfIO $ processChunksToStderr nullH
        ] `finally` (do
            putStrLn "cleanup ..."
            hClose nullH
            deleteFiles
           )