{-# 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