-- This file is part of nagios-perfdata. -- -- Copyright 2014 Anchor Systems Pty Ltd and others. -- -- The code in this file, and the program it is a part of, is made -- available to you by its authors as open source software: you can -- redistribute it and/or modify it under the terms of the BSD license. {-# LANGUAGE OverloadedStrings #-} module Data.Nagios.Perfdata.Template( perfdataFromDefaultTemplate, ) where import Data.Nagios.Perfdata.Error import Data.Nagios.Perfdata.Metric import Control.Applicative import Data.Attoparsec.ByteString.Char8 import qualified Data.ByteString as S import Data.ByteString.Char8 (readInteger) import qualified Data.ByteString.Char8 as C import Data.Int import qualified Data.Map as M import Data.Word import Prelude hiding (takeWhile) data Item = Item { label :: S.ByteString, content :: S.ByteString } deriving (Show) -- |Matches the '::' separating items in check result output. separator :: Parser [Word8] separator = count 2 (char8 ':') "separator" -- |Matches the key in check result output. ident :: Parser S.ByteString ident = takeWhile uppercase "item identifier" where uppercase = inClass $ enumFromTo 'A' 'Z' -- |Matches the value in check result output. val :: Parser S.ByteString val = takeTill isTabOrEol "item value" where isTabOrEol c = c == '\t' || c == '\n' -- |Matches a key::value pair in check result output. item :: Parser Item item = Item `fmap` ident <* separator <*> val <* skipWhile isTab "perfdata item" where isTab = (==) '\t' -- |Matches a line of key::value pairs (i.e., the result of one check). line :: Parser [Item] line = many item "perfdata line" -- |Map from key to value for items in a check result. type ItemMap = M.Map S.ByteString S.ByteString -- |Insert items from a list into a map for easy access by key. mapItems :: [Item] -> ItemMap mapItems = foldl (\m i -> M.insert (label i) (content i) m) M.empty -- |Parse the output from a Nagios check. parseLine :: S.ByteString -> Result [Item] parseLine = parse line -- |We have no more data to give the parser at this point, so we -- either fail or succeed here and return a ParserError or an ItemMap -- respectively. extractItems :: Result [Item] -> Either ParserError ItemMap extractItems (Done _ is) = Right $ mapItems is extractItems (Fail _ ctxs err) = Left $ fmtParseError ctxs err extractItems (Partial f) = extractItems (f "") -- |Called if the check output is from a service check. Returns the -- service-specific component of the perfdata. parseServiceData :: ItemMap -> Either ParserError ServicePerfdata parseServiceData m = case M.lookup "SERVICEDESC" m of Nothing -> Left ("SERVICEDESC not found in " ++ show m) Just desc -> case M.lookup "SERVICESTATE" m of Nothing -> Left "SERVICESTATE not found" Just sState -> case parseReturnState sState of Nothing -> Left ("invalid service state " ++ C.unpack sState) Just st -> Right $ ServicePerfdata desc st -- |Whether this perfdata item is for a host check or a service check -- (or Nothing on failure to determine). parseDataType :: ItemMap -> Either ParserError HostOrService parseDataType m = case M.lookup "DATATYPE" m of Nothing -> Left "DATATYPE not found" Just s -> case s of "HOSTPERFDATA" -> Right Host "SERVICEPERFDATA" -> case parseServiceData m of Left err -> Left err Right d -> Right $ Service d _ -> Left "Invalid datatype" parseHostname :: ItemMap -> Either ParserError S.ByteString parseHostname m = case M.lookup "HOSTNAME" m of Nothing -> Left "HOSTNAME not found" Just h -> Right h parseTimestamp :: ItemMap -> Either ParserError Int64 parseTimestamp m = case M.lookup "TIMET" m of Nothing -> Left "TIMET not found" Just t -> case readInteger t of Nothing -> Left "Invalid timestamp" Just (n, _) -> Right $ fromInteger (n * nanosecondFactor) where nanosecondFactor = 1000000000 parseHostState :: ItemMap -> Either ParserError S.ByteString parseHostState m = case M.lookup "HOSTSTATE" m of Nothing -> Left "HOSTSTATE not found" Just s -> Right s parseHostMetrics :: ItemMap -> Either ParserError MetricList parseHostMetrics m = case M.lookup "HOSTPERFDATA" m of Nothing -> Left "HOSTPERFDATA not found" Just p -> parseMetricString p parseServiceMetrics :: ItemMap -> Either ParserError MetricList parseServiceMetrics m = case M.lookup "SERVICEPERFDATA" m of Nothing -> Left "SERVICEPERFDATA not found" Just p -> parseMetricString p -- |Given an item map extracted from a check result, parse and return -- the performance metrics (or store an error and return Nothing). parseMetrics :: HostOrService -> ItemMap -> Either ParserError MetricList parseMetrics typ m = case typ of Host -> parseHostMetrics m Service _ -> parseServiceMetrics m -- |Given an item map extracted from a check result, parse and return -- a Perfdata object. extractPerfdata :: ItemMap -> Either ParserError Perfdata extractPerfdata m = do typ <- parseDataType m name <- parseHostname m t <- parseTimestamp m state <- parseHostState m ms <- parseMetrics typ m return $ Perfdata typ t (C.unpack name) (Just state) ms -- |Extract perfdata from a Nagios perfdata item formatted according to -- the default template[0]. This is the format that is used in the -- perfdata spool files and consumed by pnp4nagios. -- -- [0] Default templates defined in the Nagios source (xdata/xpddefault.h). -- Service perfdata: -- "[SERVICEPERFDATA]\t$TIMET$\t$HOSTNAME$\t$SERVICEDESC$\t$SERVICEEXECUTIONTIME$\t$SERVICELATENCY$\t$SERVICEOUTPUT$\t$SERVICEPERFDATA$" -- Host perfdata: -- "[HOSTPERFDATA]\t$TIMET$\t$HOSTNAME$\t$HOSTEXECUTIONTIME$\t$HOSTOUTPUT$\t$HOSTPERFDATA$" perfdataFromDefaultTemplate :: S.ByteString -> Either ParserError Perfdata perfdataFromDefaultTemplate s = getItems s >>= extractPerfdata where getItems = extractItems . parseLine