packages feed

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 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"