Commits

Bryan O'Sullivan committed e15defc

Add support for dumping all events in CSV format.

Comments (0)

Files changed (5)

 import Data.Maybe (catMaybes)
 import Data.Text (Text, pack)
 import Data.Text.Encoding (encodeUtf8)
+import Data.Text.Lazy.Builder (toLazyText)
 import Data.Time.Clock.POSIX (getPOSIXTime)
 import Network.HTTP.LoadTest (NetworkError(..), Req(..))
 import Network.HTTP.LoadTest.Analysis (analyseBasic, analyseFull)
 import Network.HTTP.LoadTest.Environment (environment)
-import Network.HTTP.LoadTest.Report (buildTime, reportBasic, reportEvents,
-                                     reportFull)
+import Network.HTTP.LoadTest.Report (buildTime, csvEvents, reportBasic,
+                                     reportEvents, reportFull)
 import Network.Socket (withSocketsDo)
 import System.CPUTime (getCPUTime)
 import System.Console.CmdArgs
 import System.Exit (ExitCode(ExitFailure), exitWith)
 import System.IO (hPutStrLn, stderr, stdout)
 import qualified Data.ByteString.Char8 as B
-import qualified Data.ByteString.Lazy as L
+import qualified Data.ByteString.Lazy as BL
 import qualified Data.Text.Format as T
+import qualified Data.Text.Lazy.IO as TL
 import qualified Network.HTTP.Enumerator as E
 import qualified Network.HTTP.LoadTest as LoadTest
 
     , literal :: Maybe String
 
     , bootstrap :: Bool
+    , dump_events :: Maybe FilePath
     , json :: Maybe FilePath
     } deriving (Eq, Show, Typeable, Data)
 
               , bootstrap = def
                 &= groupname "Analysis of results"
                 &= help "Statistically robust analysis of results"
+              , dump_events = def &= typ "FILE"
+                &= help "Save raw events in CSV format"
               , json = def &= typ "FILE"
                 &= help "Save analysis in JSON format"
               } &= verbosity
                         , "environment" .= env
                         , "analysis" .= analysis ]
       case json of
-        Just "-" -> L.putStrLn (encode dump)
-        Just f   -> L.writeFile f (encode dump)
+        Just "-" -> BL.putStrLn (encode dump)
+        Just f   -> BL.writeFile f (encode dump)
+        _        -> return ()
+      case dump_events of
+        Just "-" -> TL.putStr . toLazyText . csvEvents $ results
+        Just f   -> TL.writeFile f . toLazyText . csvEvents $ results
         _        -> return ()
       whenNormal $ do
         reportEvents stdout results

lib/Network/HTTP/LoadTest.hs

 import Prelude hiding (catch)
 import qualified Data.ByteString.Lazy as L
 import qualified Data.Vector as V
+import qualified Data.Vector.Algorithms.Intro as I
+import qualified Data.Vector.Generic as G
 import qualified System.Timeout as T
 
 run :: Config -> IO (Either [NetworkError] (V.Vector Summary))
     writeChan ch =<< try (client cfg' mgr interval)
   (errs,vs) <- partitionEithers <$> replicateM concurrency (readChan ch)
   return $ case errs of
-             [] -> Right (V.concat vs)
+             [] -> Right . G.modify I.sort . V.concat $ vs
              _  -> Left (nub errs)
 
 client :: Config -> Manager -> POSIXTime
 client Config{..} mgr interval = loop 0 [] =<< getPOSIXTime
   where
     loop !n acc now
-        | n == numRequests = return $! V.fromList (reverse acc)
+        | n == numRequests = return (V.fromList acc)
         | otherwise = do
       !evt <- timedRequest
       now' <- getPOSIXTime

lib/Network/HTTP/LoadTest/Analysis.hs

 import Prelude hiding (catch)
 import Statistics.Quantile (weightedAvg)
 import qualified Data.Vector as V
-import qualified Data.Vector.Algorithms.Intro as I
 import qualified Data.Vector.Generic as G
 import qualified Data.Vector.Unboxed as U
 import qualified Statistics.Sample as S
 
 analyseFull :: V.Vector Summary -> IO (Analysis SampleAnalysis)
-analyseFull sums = do
-  let sumv = sortBy (compare `on` summStart) sums
-      start = summStart . G.head $ sumv
+analyseFull sumv = do
+  let start = summStart . G.head $ sumv
       end = summEnd . G.last $ sumv
       elapsed = end - start
       timeSlice = min elapsed 1 / 200
     }
 
 analyseBasic :: V.Vector Summary -> Analysis Basic
-analyseBasic sums = Analysis {
+analyseBasic sumv = Analysis {
                       latency = Basic {
-                                  mean = S.mean . G.map summElapsed $ sums
-                                , stdDev = S.stdDev . G.map summElapsed $ sums
+                                  mean = S.mean . G.map summElapsed $ sumv
+                                , stdDev = S.stdDev . G.map summElapsed $ sumv
                                 }
-                    , latency99 = weightedAvg 99 100 . G.map summElapsed $ sums
-                    , latency999 = weightedAvg 999 1000 . G.map summElapsed $ sums
+                    , latency99 = weightedAvg 99 100 . G.map summElapsed $ sumv
+                    , latency999 = weightedAvg 999 1000 . G.map summElapsed $ sumv
                     , throughput = Basic {
                                      mean = S.mean slices / timeSlice
                                    , stdDev = S.stdDev slices / timeSlice
                                    }
                     , throughput10 = (/ timeSlice) . weightedAvg 10 100 $ slices
                     }
- where sumv = sortBy (compare `on` summStart) sums
-       start = summStart . G.head $ sumv
+ where start = summStart . G.head $ sumv
        end = summEnd . G.last $ sumv
        elapsed = end - start
        timeSlice = min elapsed 1 / 200
          where go (v,i) = let (a,b) = G.span (\s -> summStart s <= t) v
                               t = start + (i * timeSlice)
                           in Just (fromIntegral $ G.length a,(b,i+1))
-
--- | Sort a vector.
-sortBy :: (G.Vector v e) => I.Comparison e -> v e -> v e
-sortBy cmp = G.modify (I.sortBy cmp)
-{-# INLINE sortBy #-}

lib/Network/HTTP/LoadTest/Report.hs

       reportBasic
     , reportEvents
     , reportFull
+    -- * Other reports
+    , csvEvents
     -- * Helper functions
     , buildTime
     ) where
 import Criterion.Analysis (SampleAnalysis(..), OutlierEffect(..),
                            OutlierVariance(..))
 import Data.List (sort)
-import Data.Monoid (mappend)
+import Data.Monoid (mappend, mconcat, mempty)
 import Data.Text (Text)
 import Data.Text.Buildable (build)
-import Data.Text.Format (prec)
+import Data.Text.Format (prec, shortest)
 import Data.Text.Lazy.Builder (Builder)
 import Data.Vector (Vector)
 import Network.HTTP.LoadTest.Types (Analysis(..), Basic(..), Event(..),
         nameOf k = "HTTP " `mappend` build k
     T.hprint h "    {} {}\n" (nameOf e, T.left 7 ' ' n)
   T.hprint h "\n" ()
+
+csvEvents :: Vector Summary -> Builder
+csvEvents sums = "start,elapsed,event\n" `mappend` G.foldr go mempty sums
+  where
+    firstStart = summStart (G.head sums)
+    go Summary{..} b = mconcat [
+                         shortest $ summStart - firstStart
+                       , ","
+                       , shortest summElapsed
+                       , ","
+                       , classify summEvent
+                       , "\n"
+                       ] `mappend` b
+    classify Timeout          = "timeout"
+    classify HttpResponse{..} = build respCode
     http-types,
     statistics,
     text,
-    text-format,
+    text-format >= 0.3.0.4,
     time,
     unix-compat >= 0.2.2,
     unordered-containers >= 0.1.4.0,