nagios-plugin-ekg (empty) → 0.1.0.0
raw patch · 9 files changed
+336/−0 lines, 9 filesdep +HUnitdep +aesondep +basesetup-changed
Dependencies added: HUnit, aeson, base, bytestring, containers, hspec, lens, nagios-check, nagios-plugin-ekg, optparse-applicative, text, transformers, unordered-containers, wreq
Files
- LICENSE +20/−0
- README.md +31/−0
- Setup.hs +2/−0
- lib/System/Nagios/Plugin/Ekg.hs +7/−0
- lib/System/Nagios/Plugin/Ekg/Check.hs +47/−0
- lib/System/Nagios/Plugin/Ekg/Types.hs +113/−0
- nagios-plugin-ekg.cabal +61/−0
- src/Main.hs +7/−0
- tests/SampleData.hs +48/−0
+ LICENSE view
@@ -0,0 +1,20 @@+Copyright (c) 2015 Sharif Olorin++Permission is hereby granted, free of charge, to any person obtaining+a copy of this software and associated documentation files (the+"Software"), to deal in the Software without restriction, including+without limitation the rights to use, copy, modify, merge, publish,+distribute, sublicense, and/or sell copies of the Software, and to+permit persons to whom the Software is furnished to do so, subject to+the following conditions:++The above copyright notice and this permission notice shall be included+in all copies or substantial portions of the Software.++THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ README.md view
@@ -0,0 +1,31 @@+# nagios-plugin-ekg++This package provides `check_ekg`, a generic plugin for Nagios (and+compatible) monitoring systems which translates metrics exposed by+[ekg](https://hackage.haskell.org/package/ekg) into perfdata suitable+for use with pnp4nagios and similar tools.++There is currently no support for threshold checks; the plugin will+always return an `OK` result if it can obtain and parse output from+the EKG endpoint. Its primary function is to allow gathering+performance data over time from EKG-enabled Haskell applications;+there are no current plans to add support for thresholds/alerting,+though I'd consider it in the future if there was demand.++# Usage++The plugin can either be executed directly on the Nagios host or+invoked via NRPE.++Example invocation:++```+$ check_ekg -e http://haskell-app.example.com:8000+OK: perfdata only | iterations=60614c;;;; rts_gc_bytes_allocated=0c;;;; rts_gc_bytes_copied=0c;;;; rts_gc_cpu_ms=0c;;;; rts_gc_cumulative_bytes_used=0c;;;; rts_gc_current_bytes_slop=0.0;;;; rts_gc_current_bytes_used=0.0;;;; rts_gc_gc_cpu_ms=0c;;;; rts_gc_gc_wall_ms=0c;;;; rts_gc_max_bytes_slop=0.0;;;; rts_gc_max_bytes_used=0.0;;;; rts_gc_mutator_cpu_ms=0c;;;; rts_gc_mutator_wall_ms=0c;;;; rts_gc_num_bytes_usage_samples=0c;;;; rts_gc_num_gcs=0c;;;; rts_gc_par_avg_bytes_copied=0.0;;;; rts_gc_par_max_bytes_copied=0.0;;;; rts_gc_par_tot_bytes_copied=0.0;;;; rts_gc_peak_megabytes_allocated=0.0;;;; rts_gc_wall_ms=0c;;;; ekg_server_timestamp_ms=1435214338224c;;;;+$ echo $?+0+```++# Requirements++`nagios-plugin-ekg` has been tested with GHC 7.8.4 and GHC 7.10.1.
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ lib/System/Nagios/Plugin/Ekg.hs view
@@ -0,0 +1,7 @@+module System.Nagios.Plugin.Ekg (+ module System.Nagios.Plugin.Ekg.Check,+ module System.Nagios.Plugin.Ekg.Types+) where++import System.Nagios.Plugin.Ekg.Check+import System.Nagios.Plugin.Ekg.Types
+ lib/System/Nagios/Plugin/Ekg/Check.hs view
@@ -0,0 +1,47 @@+{-# LANGUAGE OverloadedStrings #-}++module System.Nagios.Plugin.Ekg.Check where++import Control.Applicative+import Control.Lens+import Control.Monad.IO.Class+import Data.Aeson+import Data.ByteString.Lazy (ByteString)+import qualified Data.ByteString.Lazy.Char8 as BL+import qualified Data.Text as T+import Network.Wreq hiding (header)+import qualified Network.Wreq as W+import Options.Applicative+import System.Nagios.Plugin++import System.Nagios.Plugin.Ekg.Types++pluginOptParser :: ParserInfo PluginOpts+pluginOptParser = info (helper <*> opts)+ ( fullDesc+ <> progDesc "Nagios plugin exposing perfdata from EKG."+ <> header "check_ekg"+ )+ where+ opts = PluginOpts <$> strOption ( long "ekg-endpoint"+ <> short 'e'+ <> metavar "EKG-ENDPOINT"+ <> value "http://localhost:8888"+ <> help "URL of the EKG endpoint."+ )++checkEkg :: NagiosPlugin ()+checkEkg = do+ opts <- liftIO $ execParser pluginOptParser+ let reqOpts = W.header "Accept" .~ ["application/json"] $ defaults+ resp <- liftIO . getWith reqOpts $ optsEndpoint opts+ case resp ^. responseStatus . statusCode of+ 200 -> checkEkg' $ resp ^. responseBody+ code -> addResult Critical . T.pack $ "EKG endpoint failed with status " <> show code++checkEkg' :: ByteString -> NagiosPlugin ()+checkEkg' bs = case (eitherDecode' bs :: Either String MetricTree) of+ Left err -> addResult Critical $ "failed to parse EKG output: " <> T.pack err+ Right meters -> do+ addPerfData meters+ addResult OK "perfdata only"
+ lib/System/Nagios/Plugin/Ekg/Types.hs view
@@ -0,0 +1,113 @@+{-# LANGUAGE OverloadedStrings #-}++module System.Nagios.Plugin.Ekg.Types where++import Control.Applicative+import Control.Monad+import Data.Aeson+import Data.Aeson.Types+import Data.Int+import Data.HashMap.Strict (HashMap)+import qualified Data.HashMap.Strict as HM+import Data.Map (Map)+import qualified Data.Map as M+import Data.Monoid+import Data.Text (Text)+import qualified Data.Text as T+import System.Nagios.Plugin++data PluginOpts = PluginOpts+ { optsEndpoint :: String }++data EkgMetric =+ -- * Nondecreasing counter, e.g., all-time number of requests.+ EkgCounter Int64+ -- * Measure of a quantity over time, e.g., number of requests per minute.+ | EkgGauge Double+ -- * Can't meaningfully turn labels into perfdata, this is a placeholder.+ | EkgLabel+ -- * Can't meaningfully turn distributions into perfdata, this is a placeholder.+ | EkgDistribution+ deriving (Eq, Show)++instance FromJSON EkgMetric where+ parseJSON (Object o) = do+ metric_type <- o .: "type"+ case metric_type of+ "c" -> EkgCounter <$> o .: "val"+ "g" -> EkgGauge <$> o .: "val"+ "l" -> return EkgLabel+ "d" -> return EkgDistribution+ x -> fail $ "Invalid metric type " <> T.unpack x+ parseJSON _ = fail "EkgMetric must be an object"++-- | A node in the 'MetricTree'; a Leaf is a single metric.+data MetricNode =+ Leaf EkgMetric+ | Branch (Map Text MetricNode)++instance FromJSON MetricNode where+ parseJSON (Object o) = do+ leaf <- isLeaf <$> parseJSON (Object o)+ if leaf+ then Leaf <$> parseJSON (Object o)+ else Branch <$> parseJSON (Object o)+ where+ -- Educated guess as to whether this object is a leaf. It'll+ -- definitely have "type"; it'll have "val" if it's a counter,+ -- gauge or label and it'll have "variance" and "mean" if it's+ -- a distribution.+ --+ -- My kingdom for a schema.+ isLeaf :: HashMap Text Value -> Bool+ isLeaf m = HM.member "type" m &&+ (HM.member "val" m ||+ (HM.member "variance" m && HM.member "mean" m)+ )+ parseJSON x = fail $ "MetricNode must be an object, not " <> show x++newtype MetricTree = MetricTree+ { unMetricTree :: Map Text MetricNode }++instance FromJSON MetricTree where+ parseJSON (Object o) = MetricTree <$> parseJSON (Object o)+ parseJSON _ = fail "MetricTree must be an object"++instance ToPerfData MetricTree where+ toPerfData (MetricTree m) = M.foldrWithKey (renderValue Nothing) [] m++-- | Build perfdata from a single metric. The Nagios perfdata format+-- doesn't allow us to sensibly represent the EKG 'Distribution' or+-- 'Label' types so we don't try.+renderMetric :: Text+ -> EkgMetric+ -> Maybe PerfDatum+renderMetric lbl (EkgCounter n) = + Just $ barePerfDatum lbl (IntegralValue n) Counter+renderMetric lbl (EkgGauge n) =+ Just $ barePerfDatum lbl (RealValue n) NullUnit+renderMetric _ EkgLabel = Nothing+renderMetric _ EkgDistribution = Nothing++-- | Build perfdata from a node in the metric tree. Produce a+-- 'PerfDatum' from a 'Leaf', recursively walk a 'Branch' and+-- mappend the leaves.+renderValue :: Maybe Text+ -> Text+ -> MetricNode+ -> [PerfDatum]+ -> [PerfDatum]+renderValue prefix lbl (Leaf val) acc =+ case renderMetric (withPrefix prefix lbl) val of+ Nothing -> acc+ Just pd -> pd : acc+renderValue prefix lbl (Branch branch) acc = acc+ <> M.foldrWithKey (renderValue (Just $ withPrefix prefix lbl)) [] branch++-- | Construct a metric name, optionally prepended with a prefix (we+-- want a prefix for every component of the name except the first one).+withPrefix :: Maybe Text+ -> Text+ -> Text+withPrefix Nothing suff = suff+withPrefix (Just prefix) suff = prefix <> "_" <> suff
+ nagios-plugin-ekg.cabal view
@@ -0,0 +1,61 @@+name: nagios-plugin-ekg+version: 0.1.0.0+synopsis: Monitor ekg metrics via Nagios+description: A generic Nagios plugin which retrieves metrics+ from an application which uses+ <http://hackage.haskell.org/package/ekg ekg>.+homepage: https://github.com/fractalcat/nagios-plugin-ekg+license: MIT+license-file: LICENSE+author: Sharif Olorin+maintainer: sio@tesser.org+copyright: 2015 Sharif Olorin and Anchor Systems+category: System+build-type: Simple+extra-source-files: README.md+cabal-version: >=1.10++source-repository head+ type: git+ location: git@github.com:fractalcat/nagios-plugin-ekg.git++library+ exposed-modules: System.Nagios.Plugin.Ekg+ System.Nagios.Plugin.Ekg.Types+ System.Nagios.Plugin.Ekg.Check+ build-depends: base >= 4.7 && <5,+ nagios-check >= 0.3.1,+ transformers,+ bytestring,+ optparse-applicative,+ lens,+ aeson,+ wreq,+ text,+ unordered-containers,+ containers+ hs-source-dirs: lib+ default-language: Haskell2010++executable check_ekg+ main-is: Main.hs+ build-depends: base >=4.7 && <5,+ nagios-check >= 0.3,+ text,+ nagios-plugin-ekg+ hs-source-dirs: src+ default-language: Haskell2010++test-suite sample-data+ hs-source-dirs: tests+ main-is: SampleData.hs+ type: exitcode-stdio-1.0+ default-language: Haskell2010+ build-depends: base >=4.5 && <5,+ HUnit,+ bytestring,+ hspec,+ nagios-check,+ nagios-plugin-ekg,+ text,+ transformers
+ src/Main.hs view
@@ -0,0 +1,7 @@+module Main where++import System.Nagios.Plugin (runNagiosPlugin)+import System.Nagios.Plugin.Ekg (checkEkg)++main :: IO ()+main = runNagiosPlugin checkEkg
+ tests/SampleData.hs view
@@ -0,0 +1,48 @@+{-# LANGUAGE OverloadedStrings #-}++module Main where++import Control.Monad.IO.Class+import qualified Data.ByteString.Lazy as BS+import qualified Data.Text as T+import Test.Hspec+import Test.HUnit.Base++import System.Nagios.Plugin+import System.Nagios.Plugin.Ekg++main :: IO ()+main = hspec suite++suite :: Spec+suite =+ describe "checkEkg'" $ do+ validJson <- runIO $ BS.readFile "tests/data/ekg_sample_output.json"+ (_, validState) <- runIO . runNagiosPlugin' $ checkEkg' validJson+ let (validStatus, validOutput) = finishState validState++ let invalidJson = "{ \"iamnotarealmetric\": \"noreallyimnot\" }"+ (_, borkedState) <- runIO . runNagiosPlugin' $ checkEkg' invalidJson+ let (borkedStatus, borkedOutput) = finishState borkedState+ + let emptyString = ""+ (_, emptyState) <- runIO . runNagiosPlugin' $ checkEkg' emptyString+ let (emptyStatus, emptyOutput) = finishState emptyState++ it "exits with OK status given valid input" $+ validStatus @?= OK++ it "outputs the correct perfdata" $+ validOutput @?= "OK: perfdata only | iterations=41734c;;;; rts_gc_bytes_allocated=0c;;;; rts_gc_bytes_copied=0c;;;; rts_gc_cpu_ms=0c;;;; rts_gc_cumulative_bytes_used=0c;;;; rts_gc_current_bytes_slop=0.0;;;; rts_gc_current_bytes_used=0.0;;;; rts_gc_gc_cpu_ms=0c;;;; rts_gc_gc_wall_ms=0c;;;; rts_gc_max_bytes_slop=0.0;;;; rts_gc_max_bytes_used=0.0;;;; rts_gc_mutator_cpu_ms=0c;;;; rts_gc_mutator_wall_ms=0c;;;; rts_gc_num_bytes_usage_samples=0c;;;; rts_gc_num_gcs=0c;;;; rts_gc_par_avg_bytes_copied=0.0;;;; rts_gc_par_max_bytes_copied=0.0;;;; rts_gc_par_tot_bytes_copied=0.0;;;; rts_gc_peak_megabytes_allocated=0.0;;;; rts_gc_wall_ms=0c;;;; ekg_server_timestamp_ms=1435212142955c;;;;"++ it "exits with CRITICAL status given invalid JSON input" $+ borkedStatus @?= Critical++ it "outputs correct failure message on invalid JSON input" $+ borkedOutput @?= "CRITICAL: failed to parse EKG output: MetricNode must be an object, not String \"noreallyimnot\""++ it "exits with CRITICAL status given empty input" $+ emptyStatus @?= Critical++ it "outputs correct failure message on empty input" $+ emptyOutput @?= "CRITICAL: failed to parse EKG output: not enough input"