packages feed

wai-middleware-metrics (empty) → 0.2.0

raw patch · 5 files changed

+264/−0 lines, 5 filesdep +QuickCheckdep +basedep +bytestringsetup-changed

Dependencies added: QuickCheck, base, bytestring, ekg-core, http-types, scotty, tasty, tasty-hunit, tasty-quickcheck, time, transformers, wai, wai-extra, wai-middleware-metrics

Files

+ LICENSE view
@@ -0,0 +1,30 @@+Copyright (c) 2015, Sebastian de Bellefon++All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are met:++    * Redistributions of source code must retain the above copyright+      notice, this list of conditions and the following disclaimer.++    * Redistributions in binary form must reproduce the above+      copyright notice, this list of conditions and the following+      disclaimer in the documentation and/or other materials provided+      with the distribution.++    * Neither the name of Sebastian de Bellefon nor the names of other+      contributors may be used to endorse or promote products derived+      from this software without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ Network/Wai/Metrics.hs view
@@ -0,0 +1,91 @@+{-# LANGUAGE OverloadedStrings #-}+{-|+Module      : Network.Wai.Metrics+License     : BSD3+Stability   : experimental++A <http://hackage.haskell.org/package/wai WAI> middleware to collect the following <https://ocharles.org.uk/blog/posts/2012-12-11-24-day-of-hackage-ekg.html EKG> metrics from compatible web servers:++* number of requests (counter @wai.request_count@)+* number of server errors (counter @wai.server_error_count@)+* latency distribution (distribution @wai.latency_distribution@)+++Here's an example of reading these metrics from a Scotty server, and displaying them with EKG.++> -- Compile with GHC option `-with-rtsopts=-T` for GC metrics+> import Web.Scotty+> import Control.Applicative+> import System.Remote.Monitoring (serverMetricStore, forkServer)+> import Network.Wai.Metrics+>+> main :: IO()+> main = do+>   store <- serverMetricStore <$> forkServer "localhost" 8000+>   waiMetrics <- registerWaiMetrics store+>   scotty 3000 $ do+>     middleware (metrics waiMetrics)+>     get "/" $ html "Ping"++Now have a look at <http://localhost:8000 your local EKG instance> and display the request count by clicking on 'wai.request_count'.++WAI metrics can also be stored in a bare EKG store, with no UI and no GC metrics. Use ekg-core's newStore function.++Compatible web servers include the following:++*Yesod+*Scotty+*Spock+*Servant+*Warp+-}+module Network.Wai.Metrics (+  registerWaiMetrics,+  WaiMetrics(..),+  metrics) where++import Network.Wai+import System.Metrics+import Control.Monad (when)+import Data.Time.Clock+import qualified System.Metrics.Counter as Counter+import qualified System.Metrics.Distribution as Distribution+import Network.HTTP.Types.Status (statusIsServerError)++{-|+The metrics to feed in WAI and register in EKG.+-}+data WaiMetrics = WaiMetrics {+  requestCounter :: Counter.Counter+ ,serverErrorCounter :: Counter.Counter+ ,latencyDistribution :: Distribution.Distribution+}++{-|+Register in EKG a number of metrics related to web server activity.++* @wai.request_count@+* @wai.server_error_count@+* @wai.latency_distribution@+-}+registerWaiMetrics :: Store -> IO WaiMetrics+registerWaiMetrics store = do+  req <- createCounter "wai.request_count" store+  err <- createCounter "wai.server_error_count" store+  tim <- createDistribution "wai.latency_distribution" store+  return $ WaiMetrics req err tim++{-|+Create a middleware to be added to a WAI-based webserver.+-}+metrics :: WaiMetrics -> Middleware+metrics waiMetrics app req respond = do+  Counter.inc (requestCounter waiMetrics)+  start <- getCurrentTime+  app req (respond' start)+    where respond' :: UTCTime -> Response -> IO ResponseReceived+          respond' start res = do+            when (statusIsServerError $ responseStatus res) (Counter.inc (serverErrorCounter waiMetrics))+            end <- getCurrentTime+            Distribution.add (latencyDistribution waiMetrics) (realToFrac $ diffUTCTime end start)+            respond res
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ tests.hs view
@@ -0,0 +1,91 @@+{-# LANGUAGE OverloadedStrings #-}++module Main where++import Control.Monad (replicateM_, liftM)+import Control.Concurrent (threadDelay)+import Control.Monad.IO.Class (liftIO)+import Data.Int (Int64)++import Test.Tasty+import Test.Tasty.HUnit+import qualified Test.Tasty.QuickCheck as QC+import qualified Test.QuickCheck.Monadic as QCM+import qualified Data.ByteString as BS++import Web.Scotty (scottyApp, middleware, get, html, raise)++import Network.Wai (Application)+import qualified Network.Wai.Test as WT++import System.Metrics+import qualified System.Metrics.Counter as Counter+import qualified System.Metrics.Distribution as Distribution++import Network.Wai.Metrics++-- Send a GET request to a WAI Application+httpGet :: BS.ByteString -> Application -> IO WT.SResponse+httpGet path =  WT.runSession (WT.srequest (WT.SRequest req ""))+  where req = WT.setRawPathInfo WT.defaultRequest path++between :: Ord a => a -> a -> a -> Bool+between low high x = low <= x && x <= high++-- Return the state of Wai Metrics after running n times+-- an action over a fresh scotty server+testServer :: (Application -> IO a) -> Int -> IO WaiMetrics+testServer action times = do+  store <- newStore+  waiMetrics <- registerWaiMetrics store+  app <- scottyApp $ do+    middleware (metrics waiMetrics)+    get "/" $ html "Ping"+    get "/error" $ raise "error"+    get "/wait" $ liftIO (threadDelay 100000) >> html "Ping"+  replicateM_ times (action app)+  return waiMetrics++-- Return the number of requests after running n times+-- an action over a fresh scotty server+readRequestCounter :: (Application -> IO a) -> Int -> IO Int64+readRequestCounter action times = do+  waiMetrics <- testServer action times+  Counter.read (requestCounter waiMetrics)++-- Return the number of server errors after running n times+-- an action over a fresh scotty server+readErrorCounter :: (Application -> IO a) -> Int -> IO Int64+readErrorCounter action times = do+  waiMetrics <- testServer action times+  Counter.read (serverErrorCounter waiMetrics)++-- Return the response time distribution after running n times+-- an action over a fresh scotty server+readResponseTime :: (Application -> IO a) -> Int -> IO Distribution.Stats+readResponseTime action times = do+  waiMetrics <- testServer action times+  Distribution.read (latencyDistribution waiMetrics)++testRequestCounterScotty :: QC.NonNegative Int -> QC.Property+testRequestCounterScotty (QC.NonNegative n) =  QCM.monadicIO test+  where test = do c <- QCM.run $ readRequestCounter (httpGet "") n+                  QCM.assert $ fromIntegral c == n++testErrorCounterScotty :: QC.NonNegative Int -> QC.Property+testErrorCounterScotty (QC.NonNegative n) =  QCM.monadicIO test+  where test = do c <- QCM.run $ readErrorCounter (httpGet "/error") n+                  QCM.assert $ fromIntegral c == n++testResponseTimeScotty :: IO()+testResponseTimeScotty =  do s <- readResponseTime (httpGet "/wait") 3+                             assert $ between 0.1 0.11 (Distribution.mean s)++tests :: TestTree+tests = testGroup "Metrics tests" [+    QC.testProperty "Request counter must be incremented in middleware" testRequestCounterScotty+  , QC.testProperty "Error counter must be incremented in middleware" testErrorCounterScotty+  , testCase "Request time average must be measured in middleware" testResponseTimeScotty]++main :: IO()+main = defaultMain tests
+ wai-middleware-metrics.cabal view
@@ -0,0 +1,50 @@+name:                wai-middleware-metrics+version:             0.2.0+synopsis:            A WAI middleware to collect EKG request metrics+description:         This WAI middleware counts the number of requests, the number of server errors (http status >= 500) and keeps a latency distribution.+                     .+                     It can be added to any WAI-based webserver, such as Yesod, Scotty, Spock and Servant.+                     .+                     The counters are EKG Counters from ekg-core. <https://ocharles.org.uk/blog/posts/2012-12-11-24-day-of-hackage-ekg.html>+license:             BSD3+license-file:        LICENSE+author:              Sebastian de Bellefon+maintainer:          arnaudpourseb@gmail.com+homepage:            https://github.com/Helkafen/wai-middleware-metrics+category:            Web+build-type:          Simple+cabal-version:       >=1.10+Tested-with:         GHC == 7.6.2, GHC == 7.6.3, GHC == 7.8.1, GHC == 7.8.2, GHC == 7.8.3, GHC == 7.8.4, GHC == 7.10.1, GHC == 7.10.2++library+  exposed-modules:     Network.Wai.Metrics+  build-depends:       base >=4.6 && < 5+                     , wai >= 3.0.0+                     , ekg-core >= 0.1+                     , http-types >= 0.8+                     , time >= 1.4+  default-language:    Haskell2010+  ghc-options:        -Wall++test-suite unit+  build-depends:       base >=4.6 && < 5+                     , wai >= 3.0+                     , ekg-core >= 0.1+                     , wai-middleware-metrics+                     , tasty >= 0.10.1+                     , tasty-hunit >= 0.8+                     , tasty-quickcheck >= 0.8.2+                     , QuickCheck == 2.7+                     , scotty >= 0.8.0+                     , transformers >= 0.3+                     , bytestring >= 0.10.0.2+                     , wai-extra >= 3.0.0+                     , http-types >= 0.8+                     , time >= 1.4+  type:                exitcode-stdio-1.0+  main-is:             tests.hs+  default-language:    Haskell2010++source-repository head+  type:     git+  location: git://github.com/Helkafen/wai-middleware-metrics.git