{-# LANGUAGE OverloadedStrings #-}
module System.Remote.Monitoring.Wai.App
( startServer
) where
import Control.Monad.IO.Class (liftIO)
import qualified Data.ByteString as BS
import qualified Data.ByteString.Char8 as BS8
import Control.Exception (throwIO)
import Data.Function (on)
import qualified Data.HashMap.Strict as M
import qualified Data.List as List
import Data.String
import qualified Data.Text as T
import Data.Word (Word8)
import Network.HTTP.Types.Status
import Network.Socket (NameInfoFlag(NI_NUMERICHOST), addrAddress, getAddrInfo,
getNameInfo)
import Paths_ekg_wai (getDataDir)
import Prelude hiding (read)
import System.FilePath ((</>))
import Network.Wai
import Network.Wai.Handler.Warp
import Network.Wai.Application.Static
import System.Metrics
import System.Remote.Monitoring.Wai.Json
getNumericHostAddress :: BS.ByteString -> IO BS.ByteString
getNumericHostAddress host = do
ais <- getAddrInfo Nothing (Just (BS8.unpack host)) Nothing
case ais of
[] -> unsupportedAddressError
(ai:_) -> do
ni <- getNameInfo [NI_NUMERICHOST] True False (addrAddress ai)
case ni of
(Just numericHost, _) -> return $! BS8.pack numericHost
_ -> unsupportedAddressError
where
unsupportedAddressError = throwIO $
userError $ "unsupported address: " ++ BS8.unpack host
startServer :: Store
-> BS.ByteString
-> Int
-> IO ()
startServer store host port = do
numericHost <- getNumericHostAddress host
let conf =
setHost (fromString (BS8.unpack numericHost)) $
setPort port $
defaultSettings
runSettings conf (monitor store)
monitor :: Store -> Application
monitor store req respond = do
dataDir <- liftIO getDataDir
let acceptHdr = (List.head . parseHttpAccept) <$> acceptHeader req
case acceptHdr of
Just hdr | hdr == "application/json" && requestMethod req == "GET" ->
serve store req respond
_ -> do
staticApp (defaultFileServerSettings $ dataDir </> "assets") req respond
acceptHeader :: Request -> Maybe BS.ByteString
acceptHeader req = lookup "Accept" $ requestHeaders req
serve :: Store -> Application
serve store req respond = do
response <-
case pathInfo req of
[] -> serveAll
segments -> serveOne segments
respond response
where
respHeaders = [("Content-Type","application/json")]
serveAll :: IO Response
serveAll = do
metrics <- liftIO $ sampleAll store
return $ responseLBS status200 respHeaders $ encodeAll metrics
serveOne :: [T.Text] -> IO Response
serveOne segments = do
let name = T.intercalate "." segments
metrics <- liftIO $ sampleAll store
case M.lookup name metrics of
Nothing -> return $ responseLBS status404 respHeaders "\"Metric not found\""
Just metric -> return $ responseLBS status200 respHeaders $ encodeOne metric
parseHttpAccept :: BS.ByteString -> [BS.ByteString]
parseHttpAccept = List.map fst
. List.sortBy (rcompare `on` snd)
. List.map grabQ
. BS.split 44
where
rcompare :: Double -> Double -> Ordering
rcompare = flip compare
grabQ s =
let (s', q) = breakDiscard 59 s
(_, q') = breakDiscard 61 q
in (trimWhite s', readQ $ trimWhite q')
readQ s = case reads $ BS8.unpack s of
(x, _):_ -> x
_ -> 1.0
trimWhite = BS.dropWhile (== 32)
breakDiscard :: Word8 -> BS.ByteString -> (BS.ByteString, BS.ByteString)
breakDiscard w s =
let (x, y) = BS.break (== w) s
in (x, BS.drop 1 y)