packages feed

wrecker-0.1.1.0: src/Wrecker/Options.hs

{-# LANGUAGE RecordWildCards, CPP #-}
module Wrecker.Options where
import Options.Applicative.Builder
import Options.Applicative
import Control.Exception
import Wrecker.Logger
import Data.Monoid

data RunType = RunCount Int | RunTimed Int
  deriving (Show, Eq)

data DisplayMode = Interactive | NonInteractive
  deriving (Show, Eq, Read)

data Options = Options
  { concurrency           :: Int
  -- ^ The number of simulatanous connections
  , binCount              :: Int
  -- ^ The number of bins for the histogram
  , runStyle              :: RunType
  -- ^ runStyle determines if the 'wrecker' runs for a specified
  --   time period or for a specified number of runs.
  , timeoutTime           :: Int
  -- ^ How long to wait after the first benchmark for the other threads
  --   to finish
  , displayMode           :: DisplayMode
  -- ^ This controls the command line display. It can be either Interactive
  --   of NonInteractive
  , logLevel              :: LogLevel
  -- ^
  , match                 :: String
  -- ^ Set this to filter the benchmarks using a pattern
  , requestNameColumnSize :: Maybe Int
  -- ^ Limit the request name column to the given size
  , outputFilePath        :: Maybe FilePath
  -- ^ Dump the results to JSON file
  , silent                :: Bool
  -- ^ Set 'silent' to true to disable all output.
  } deriving (Show, Eq)

-- | 'defaultOptions' provides sensible default for the 'Options'
--   types
defaultOptions :: Options
defaultOptions = Options
  { concurrency           = 1
  , binCount              = 20
  , runStyle              = RunCount 1
  , timeoutTime           = 100000000
  , displayMode           = NonInteractive
  , logLevel              = LevelError
  , match                 = ""
  , requestNameColumnSize = Nothing
  , outputFilePath        = Nothing
  , silent                = False
  }

data PartialOptions = PartialOptions
  { mConcurrency           :: Maybe Int
  , mBinCount              :: Maybe Int
  , mRunStyle              :: Maybe RunType
  , mTimeoutTime           :: Maybe Int
  , mDisplayMode           :: Maybe DisplayMode
  , mLogLevel              :: Maybe LogLevel
  , mMatch                 :: Maybe String
  , mRequestNameColumnSize :: Maybe Int
  , mOutputFilePath        :: Maybe FilePath
  , mSilent                :: Maybe Bool
  } deriving (Show, Eq)

instance Monoid PartialOptions where
  mempty = PartialOptions
            { mConcurrency           = Just $ concurrency           defaultOptions
            , mBinCount              = Just $ binCount              defaultOptions
            , mRunStyle              = Just $ runStyle              defaultOptions
            , mTimeoutTime           = Just $ timeoutTime           defaultOptions
            , mDisplayMode           = Just $ displayMode           defaultOptions
            , mLogLevel              = Just $ logLevel              defaultOptions
            , mMatch                 = Just $ match                 defaultOptions
            , mRequestNameColumnSize = requestNameColumnSize        defaultOptions
            , mOutputFilePath        = outputFilePath               defaultOptions
            , mSilent                = Just $ silent                defaultOptions
            }
  mappend x y = PartialOptions
                  { mConcurrency           =  mConcurrency x <|> mConcurrency y
                  , mBinCount              =  mBinCount    x <|> mBinCount    y
                  , mRunStyle              =  mRunStyle    x <|> mRunStyle    y
                  , mTimeoutTime           =  mTimeoutTime x <|> mTimeoutTime y
                  , mDisplayMode           =  mDisplayMode x <|> mDisplayMode y
                  , mLogLevel              =  mLogLevel    x <|> mLogLevel    y
                  , mMatch                 =  mMatch       x <|> mMatch       y
                  , mRequestNameColumnSize =  mRequestNameColumnSize x
                                          <|> mRequestNameColumnSize y
                  , mOutputFilePath        =  mOutputFilePath x
                                          <|> mOutputFilePath y
                  , mSilent                =  mSilent      x <|> mSilent      y
                  }

completeOptions :: PartialOptions -> Maybe Options
completeOptions options =
  case options <> mempty of
    PartialOptions
      { mConcurrency           = Just concurrency
      , mBinCount              = Just binCount
      , mRunStyle              = Just runStyle
      , mTimeoutTime           = Just timeoutTime
      , mDisplayMode           = Just displayMode
      , mLogLevel              = Just logLevel
      , mMatch                 = Just match
      , mRequestNameColumnSize = requestNameColumnSize
      , mOutputFilePath        = outputFilePath
      , mSilent                = Just silent
      } -> Just $ Options {..}
    _ -> Nothing

optionalOption :: Read a => Mod OptionFields a -> Parser (Maybe a)
optionalOption = optional . option auto

optionalStrOption :: Mod OptionFields String -> Parser (Maybe String)
optionalStrOption = optional . strOption

optionalSwitch :: Mod FlagFields Bool -> Parser (Maybe Bool)
optionalSwitch = optional . switch

pPartialOptions :: Parser PartialOptions
pPartialOptions
   =  PartialOptions
  <$> optionalOption
      (  long "concurrency"
      <> help "Number of threads for concurrent requests"
      )
  <*> optionalOption
      (  long "bin-count"
      <> help "Number of bins for latency histogram"
      )
  <*> optional
      (  RunCount <$> option auto
                   (  long "run-count"
                  <>  help "number of times to repeat "
                   )
     <|> RunTimed <$> option auto
                   (  long "run-timed"
                  <>  help "number of seconds to repeat "
                   )
      )
  <*> optionalOption
      (  long "timeout-time"
      <> help "How long to wait for all requests to finish"
      )
  <*> optionalOption
      (  long "display-mode"
      <> help "Display results interactively"
      )
  <*> optionalOption
      (  long "log-level"
      <> help "Log to stderr events of criticality greater than the LOG_LEVEL"
      )
  <*> optionalStrOption
      (  long "match"
      <> help "Only run tests that match the glob"
      )
  <*> optionalOption
      (  long "request-name-size"
      <> help "Request name size for the terminal display"
      )
  <*> optionalStrOption
      (  long "output-path"
      <> help "Save a JSON file of the the statistics to given path"
      )
  <*> optionalSwitch
      (  long "silent"
      <> help "Disable all output"
      )

runParser :: IO Options
runParser = do
  let opts = info (helper <*> pPartialOptions)
               ( fullDesc
               <> progDesc "Welcome to wrecker"
               <> header "wrecker - HTTP stress tester and benchmarker"
               )

  partialOptions <- execParser opts
  case completeOptions partialOptions of
    Nothing -> throwIO $ userError ""
    Just x  -> return x