packages feed

himpy (empty) → 0.5.0

raw patch · 21 files changed

+844/−0 lines, 21 filesdep +MissingHdep +NetSNMPdep +aesonsetup-changed

Dependencies added: MissingH, NetSNMP, aeson, attoparsec, base, binary, bytestring, cereal, containers, network, old-time, protobuf, regex-posix, stm, text, type-level, unordered-containers, vector

Files

+ LICENSE view
@@ -0,0 +1,19 @@+Copyright (c) 2013 Pierre-Yves Ritschard++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,50 @@+himpy: multithreaded snmp poller for riemann+============================================++himpy polls common SNMP mibs (bundled in *recipes*),+applies local thresholds and forwards results to+[riemann](http://riemann.io)++## Available MIB Recipes++* network+* storage+* load+* windows services++## Configuration++```json+{+    "logfile": "/tmp/himpy.log",+    "host": "127.0.0.1",+    "port": "5555",+    "hosts": [+        {+            "host": "127.0.0.1",+            "community": "public",+            "recipes": {+                "network": [],+                "storage": [],+                "load": [],+                "winservices": ["logstash"]+            }+        }+    ],+    "thresholds": [+        {+            "host": "(.*)",+            "service": "^/ percent",+            "warning": "30.00",+            "critical": "50.00"+        },+        {+            "host": "(.*)",+            "service": "load-all",+            "warning": "30.00",+            "critical": "50.00"+        }+    ]+}+```+
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ System/Himpy/Config.hs view
@@ -0,0 +1,97 @@+{-# LANGUAGE OverloadedStrings #-}+module System.Himpy.Config (configure) where+import System.Himpy.Types+import Data.Aeson+import System.IO (readFile)+import Data.Text (unpack, Text)++import Data.Attoparsec.Number as AN+import qualified Data.Vector as V+import qualified Data.HashMap.Strict as HM+import qualified Data.Map as M+import qualified Data.ByteString.Lazy as BL++get_rcp :: (String,[String]) -> HimpyRecipe+get_rcp ("storage",_) = StorageRecipe+get_rcp ("network", _) = NetworkRecipe+get_rcp ("load", _) = LoadRecipe+get_rcp ("juniper", _) = JuniperRecipe+get_rcp ("winservices", services) = WinSrvRecipe services++to_str (String t) = unpack t++clean_rcp :: (Text, Value) -> (String, [String])+clean_rcp (k, (Array strs)) = (unpack k, V.toList $ V.map to_str strs)++++get_host_conf :: Value -> HimpyHost+get_host_conf (Object h) = (Host (unpack host) (unpack comm) rcps) where+  (String host) = h HM.! "host"+  (String comm) = h HM.! "community"+  (Object raw_rcps) = h HM.! "recipes"+  rcps = map (get_rcp . clean_rcp) $ HM.toList raw_rcps++get_host :: Object -> String+get_host conf = case HM.lookup "host" conf of+   Nothing -> "127.0.0.1"+   Just (String x) -> unpack x++get_log :: Object -> String+get_log conf = case HM.lookup "logfile" conf of+   Nothing -> "/var/log/himpy.log"+   Just (String x) -> unpack x++get_port :: Object -> Integer+get_port conf = case HM.lookup "port" conf of+   Nothing -> 5555+   Just (String x) -> (read (unpack x) :: Integer)++get_hosts conf = case HM.lookup "hosts" conf of+   Nothing -> []+   Just (Array hosts) -> V.toList $ V.map get_host_conf hosts++get_warn :: Object -> Maybe Double+get_warn t = case HM.lookup "warning" t of+   Nothing -> Nothing+   Just (String w) -> Just (read (unpack w) :: Double)++get_threshold :: Value -> Threshold+get_threshold (Object t) =  Threshold {tHost = host, tService = service, tWarning =+                                          warning, tCritical = critical} where+  (String t_host) = t HM.! "host"+  (String t_service) = t HM.! "service"+  (String t_critical) = t HM.! "critical"+  host = unpack t_host+  service = unpack t_service+  warning = get_warn t+  critical =  (read (unpack t_critical) :: Double)+++get_thresholds conf = case HM.lookup "thresholds" conf of+   Nothing -> []+   Just (Array thresholds) -> V.toList $ V.map get_threshold thresholds++get_interval :: Object -> Integer+get_interval conf = case HM.lookup "interval" conf of+   Nothing -> 60+   Just (String x) -> (read (unpack x) :: Integer)+++get_ttl :: Object -> Float+get_ttl conf = case HM.lookup "ttl" conf of+   Nothing -> 125.0+   Just (String x) -> (read (unpack x) :: Float)++from_json :: Object -> HimpyConfig+from_json conf =+   (Hosts (get_interval conf) (get_ttl conf)+    (get_host conf) (get_port conf) (get_log conf)+    (get_hosts conf) (get_thresholds conf))+++configure path = do+   content <- BL.readFile path+   let Just json_config = decode content :: Maybe Object+   let conf = from_json json_config+   return conf
+ System/Himpy/Index.hs view
@@ -0,0 +1,23 @@+module System.Himpy.Index where+import System.Himpy.Types+import Control.Concurrent.MVar+import qualified Data.Map as M++emptyMap :: M.Map (String,String) Double+emptyMap = M.fromList []++initIndex :: IO (HIndex)+initIndex = do+  box <- newEmptyMVar+  putMVar box emptyMap+  return box++deriveMetric :: HIndex -> Integer -> (String,String) -> Double -> IO (Maybe Double)+deriveMetric box interval k metric = do+  index <- takeMVar box+  let previous = M.lookup k index+  let updated = M.insert k metric index+  putMVar box updated+  case previous of+    Nothing -> return Nothing+    Just old_metric -> return (Just ((metric - old_metric) / (fromIntegral interval :: Double)))
+ System/Himpy/Logger.hs view
@@ -0,0 +1,28 @@+module System.Himpy.Logger (log_start, log_info) where+import System.IO (openFile, hPutStrLn, hFlush, hClose,+                  IOMode (AppendMode), Handle)+import Control.Concurrent (forkIO)+import Control.Monad (void, forever)+import Control.Monad.STM (atomically)+import Control.Concurrent.STM.TChan (writeTChan, readTChan, newTChanIO, TChan)++-- Simplistic logging module++log_line :: TChan String -> String -> IO ()+log_line chan path = do+  line <- atomically $ readTChan chan+  fd <- openFile path AppendMode+  hPutStrLn fd line+  hFlush fd+  hClose fd++log_start :: String -> IO (TChan String)+log_start path = do+  chan <- newTChanIO+  void $ forkIO $ forever $ log_line chan path+  return (chan)++log_info :: TChan String -> String -> IO ()+log_info chan msg = do+  let info_msg = "[info] " ++ msg+  atomically $ writeTChan chan info_msg
+ System/Himpy/Mib.hs view
@@ -0,0 +1,36 @@+module System.Himpy.Mib where+import Network.Protocol.NetSNMP (RawOID)++ifDescr = [1, 3, 6, 1, 2, 1, 2, 2, 1, 2] :: RawOID+ifName = [1, 3, 6, 1, 2, 1, 31, 1, 1, 1, 1 ] :: RawOID+ifInOctets = [1, 3, 6, 1, 2, 1, 2, 2, 1, 10] :: RawOID+ifOutOctets = [1, 3, 6, 1, 2, 1, 2, 2, 1, 16] :: RawOID+ifOperStatus = [1, 3, 6, 1, 2, 1, 2, 2, 1, 8] :: RawOID+ifConnectorPresent = [1, 3, 6, 1, 2, 1, 2, 2, 1, 17] :: RawOID+ifAdminStatus = [1, 3, 6, 1, 2, 1, 2, 2, 1, 7] :: RawOID+sysName = [1, 3, 6, 1, 2, 1, 1, 5, 0] :: RawOID+hrStorageDescr = [1, 3, 6, 1, 2, 1, 25, 2, 3, 1, 3] :: RawOID+hrStorageSize = [1, 3, 6, 1, 2, 1, 25, 2, 3, 1, 5] :: RawOID+hrStorageUsed = [1, 3, 6, 1, 2, 1, 25, 2, 3, 1, 6] :: RawOID+hrStorageAllocationUnits = [1, 3, 6, 1, 2, 1, 25, 2, 3, 1, 4] :: RawOID+lanMgrServiceDescr = [1, 3, 6, 1, 4, 1, 77, 1, 2, 3, 1, 1] :: RawOID+lanMgrServiceStatus = [1, 3, 6, 1, 4, 1, 77, 1, 2, 3, 1, 3] :: RawOID+hrProcessorLoad = [1, 3, 6, 1, 2, 1, 25, 3, 3, 1, 2] :: RawOID+pfeCpuPercent = [1,3,6,1,4,1,2636,3,39,1,12,1,1,1,4,0] :: RawOID+pfeSessions = [1,3,6,1,4,1,2636,3,39,1,12,1,1,1,6,0] :: RawOID+pfeSessionMax = [1,3,6,1,4,1,2636,3,39,1,12,1,1,1,7,0] :: RawOID+pfePSU1 = [1,3,6,1,4,1,2636,3,1,15,1,8,2,1,0,0] :: RawOID+pfePSU2 = [1,3,6,1,4,1,2636,3,1,15,1,8,2,2,0,0] :: RawOID+hrSystemProcesses = [1,3,6,1,2,1,25,1,6,0] :: RawOID+ciscoCpuRouting = [1,3,6,1,4,1,9,9,109,1,1,1,1,8,1] :: RawOID+ciscoCpuSwitching = [1,3,6,1,4,1,9,9,109,1,1,1,1,8,2] :: RawOID+ciscoMemAvail = [1,3,6,1,4,1,9,9,48,1,1,1,6,1] :: RawOID+ciscoMemUsed = [1,3,6,1,4,1,9,9,48,1,1,1,5,1] :: RawOID+ciscoSwitchCpu = [1,3,6,1,4,1,9,9,109,1,1,1,1,7,1] :: RawOID+ciscoSwitchMem = [1,3,6,1,4,1,9,9,305,1,1,2,0] :: RawOID+ciscoRouterPSU1 = [1,3,6,1,4,1,9,9,13,1,5,1,3,1] :: RawOID+ciscoRouterPSU2 = [1,3,6,1,4,1,9,9,13,1,5,1,3,2] :: RawOID+ciscoSwitchPSU1 = [1,3,6,1,4,1,9,9,117,1,1,2,1,2,470] :: RawOID+ciscoSwitchPSU2 = [1,3,6,1,4,1,9,9,117,1,1,2,1,2,471] :: RawOID+zfsPoolDescr = [1,3,6,1,4,1,25359,1,1,1] :: RawOID+zfsPoolHealth = [1,3,6,1,4,1,25359,1,1,4] :: RawOID
+ System/Himpy/Output/Riemann.hs view
@@ -0,0 +1,101 @@+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE OverloadedStrings #-}+module System.Himpy.Output.Riemann (riemann_start, riemann_send) where+import System.Himpy.Types+import System.Himpy.Utils+import System.Himpy.Logger+import System.Himpy.Serializers.Riemann+import Network (connectTo, PortID(PortNumber), PortNumber)+import Network.Socket (withSocketsDo,+                       Socket,+                       SockAddr(SockAddrInet),+                       inet_addr,+                       Family(AF_INET),+                       SocketType(Datagram, Stream),+                       defaultProtocol,+                       socket)+import Network.Socket.ByteString (sendAllTo)+import Control.Concurrent (forkIO)+import Control.Monad (void, forever)+import Control.Monad.STM (atomically)+import Control.Concurrent.STM.TChan (writeTChan, readTChan, newTChanIO, TChan)+import System.IO+import Data.Binary.Get (runGet, getWord32be)+import Data.Word (Word32)+import Control.Exception+import Text.Regex.Posix+import qualified Data.ByteString.Lazy as BL+import qualified Data.ByteString as B++-- Simplistic riemann write module+to_state :: Metric -> String -> Metric+to_state (Metric host service _ point) state =+  (Metric host service state point)++match_threshold :: Metric -> Threshold -> Bool+match_threshold (Metric host service _ _) (Threshold { tHost = h,+                                                       tService = s }) =+  ((host =~ h) && (service =~ s))++find_threshold :: [Threshold] -> Metric -> Maybe Threshold+find_threshold thresholds metric =+  case filter (match_threshold metric) thresholds of+    [] -> Nothing+    (threshold:_) -> Just threshold++apply_threshold :: Maybe Threshold -> Metric -> Metric+apply_threshold Nothing metric= metric+apply_threshold (Just (Threshold {tWarning = Just warn, tCritical = crit})) metric =+  if point >= crit then+    to_state metric "critical"+  else if point >= warn then to_state metric "warning"+       else metric+  where (Metric _ _ _ point) = metric+++apply_thresholds :: [Threshold] -> Metric -> Metric+apply_thresholds thresholds metric = apply_threshold threshold metric where+  threshold = find_threshold thresholds metric++riemann_write_out fd hmsg = do+  B.hPut fd hmsg+  hFlush fd+  -- wait for ack now+  raw_sz <- B.hGet fd 4+  let sz = runGet getWord32be $ BL.fromChunks [raw_sz]+  -- no deserialization of payloads for now+  B.hGet fd (fromIntegral sz)+  return ()++riemann_safe_write :: TChan String -> String -> Integer -> B.ByteString -> IO ()+riemann_safe_write logchan host port hmsg = do+  let handler = (\(e :: SomeException) -> log_info logchan $ "send error: " ++ (show e))+  let pn = (fromIntegral port :: PortNumber)+  fd <- connectTo host (PortNumber pn)+  riemann_write_out fd hmsg `catch` handler `finally` hClose fd+  return ()++riemann_write :: TChan String -> TChan [Metric] -> Float -> [Threshold] -> String -> Integer -> IO ()+riemann_write logchan chan ttl thresholds host port  = do+  raw_metrics <- atomically $ readTChan chan+  let metrics = map (apply_thresholds thresholds) raw_metrics+  msg <- metrics_to_msg metrics ttl++  let hdr = B.pack $ octets $ (fromIntegral (B.length msg) :: Word32)+  let hmsg = B.concat [hdr, msg]+  log_info logchan $ "sending out: " ++ (show $ length metrics)++  let handler = (\(e :: SomeException) -> log_info logchan $ "write error: " ++ (show e))++  riemann_safe_write logchan host port hmsg `catch` handler+  return ()++riemann_start :: TChan String -> String -> Integer -> Float -> [Threshold] -> IO (TChan [Metric])+riemann_start logchan host port ttl thresholds = do+  chan <- newTChanIO+  void $ forkIO $ forever $ riemann_write logchan chan ttl thresholds host port+  return (chan)++riemann_send :: TChan [Metric] -> [Metric] -> IO ()+riemann_send chan metrics = do+  atomically $ writeTChan chan metrics
+ System/Himpy/Recipes.hs view
@@ -0,0 +1,16 @@+module System.Himpy.Recipes where+import System.Himpy.Recipes.Network+import System.Himpy.Recipes.Storage+import System.Himpy.Recipes.WinServices+import System.Himpy.Recipes.Load+import System.Himpy.Recipes.Juniper+import System.Himpy.Types++instance RecipeMod HimpyRecipe where+  recipe mod chan logchan host index =+    case mod of+         NetworkRecipe       -> net_rcp chan logchan host index+         StorageRecipe       -> storage_rcp chan logchan host+         (WinSrvRecipe srvs) -> srv_rcp srvs chan logchan host+         LoadRecipe          -> load_rcp chan logchan host+         JuniperRecipe       -> jun_rcp chan logchan host
+ System/Himpy/Recipes/Cisco.hs view
@@ -0,0 +1,27 @@+module System.Himpy.Recipes.Cisco where+import System.Himpy.Recipes.Utils+import System.Himpy.Mib+import System.Himpy.Types+import System.Himpy.Logger+import Control.Concurrent.STM.TChan (TChan)+import qualified Data.Map as M++cisco_rcp :: TChan ([Metric]) -> TChan (String) -> HimpyHost -> IO ()+cisco_rcp chan logchan (Host host comm _) = do+++  cpu_pct <- snmp_get_num host comm pfeCpuPercent+  sess_cnt <- snmp_get_num host comm pfeSessions+  sess_max <- snmp_get_num host comm pfeSessionMax+  psu1 <- snmp_get_num host comm pfePSU1+  psu2 <- snmp_get_num host comm pfePSU2++  let tuples = [("juniper.cpu",cpu_pct),+                ("juniper.sessions.count", sess_cnt),+                ("juniper.sessions.max", sess_max),+                ("juniper.sessions.pct", (sess_cnt / sess_max) * 100),+                ("juniper.psu1", psu1),+                ("juniper.psu2", psu2)]+  let mtrs = snmp_metrics host "" tuples++  log_info logchan $ "got snmp result: " ++ show (mtrs)
+ System/Himpy/Recipes/Juniper.hs view
@@ -0,0 +1,25 @@+module System.Himpy.Recipes.Juniper where+import System.Himpy.Recipes.Utils+import System.Himpy.Mib+import System.Himpy.Types+import System.Himpy.Logger+import Control.Concurrent.STM.TChan (TChan)+import qualified Data.Map as M++jun_rcp :: TChan ([Metric]) -> TChan (String) -> HimpyHost -> IO ()+jun_rcp chan logchan (Host host comm _) = do++  cpu_pct <- snmp_get_num host comm pfeCpuPercent+  sess_cnt <- snmp_get_num host comm pfeSessions+  sess_max <- snmp_get_num host comm pfeSessionMax+  psu1 <- snmp_get_num host comm pfePSU1+  psu2 <- snmp_get_num host comm pfePSU2++  let tuples = [("juniper.cpu",cpu_pct),+                ("juniper.sessions.count", sess_cnt),+                ("juniper.sessions.max", sess_max),+                ("juniper.sessions.pct", (sess_cnt / sess_max) * 100),+                ("juniper.psu1", psu1),+                ("juniper.psu2", psu2)]+  let mtrs = snmp_metrics host "" tuples+  log_info logchan $ "got snmp result: " ++ show (mtrs)
+ System/Himpy/Recipes/Load.hs view
@@ -0,0 +1,18 @@+module System.Himpy.Recipes.Load where+import System.Himpy.Recipes.Utils+import System.Himpy.Mib+import System.Himpy.Types+import System.Himpy.Logger+import System.Himpy.Output.Riemann+import Control.Concurrent.STM.TChan (TChan)++load_tuple :: (Int, Double) -> (String, Double)+load_tuple (x, y) = ("load-" ++ (show x), y)++load_rcp :: TChan ([Metric]) -> TChan (String) -> HimpyHost -> IO ()+load_rcp chan logchan (Host host comm _) = do+  loads <- snmp_walk_num host comm hrProcessorLoad+  let agg_load = (foldr (+) 0.0 loads) /+                 (fromIntegral $ length loads :: Double)+  let mtrs = snmp_metrics host "load" $ [("load-all", agg_load)]+  riemann_send chan mtrs
+ System/Himpy/Recipes/Network.hs view
@@ -0,0 +1,42 @@+module System.Himpy.Recipes.Network where+import System.Himpy.Recipes.Utils+import System.Himpy.Mib+import System.Himpy.Types+import System.Himpy.Logger+import System.Himpy.Output.Riemann+import System.Himpy.Index+import Control.Concurrent.STM.TChan (TChan)++hasMetric :: ((String,String), Maybe Double) -> Bool+hasMetric ((_,_), Nothing) = False+hasMetric ((_, _), (Just _)) = True++buildMetric ((host,service), (Just metric)) =+  Metric host service "ok" metric++net_rcp :: TChan ([Metric]) -> TChan (String) -> HimpyHost -> HIndex -> IO ()+net_rcp chan logchan (Host host comm _) index = do+  names <- snmp_walk_str host comm ifName+  opstatus <- snmp_walk_num host comm ifOperStatus++  rx <- snmp_walk_num host comm ifInOctets+  tx <- snmp_walk_num host comm ifOutOctets++  let bw_in_keys = map (\x -> (host, x ++ " bandwidth in")) names+  let bw_out_keys = map (\x -> (host, x ++ " bandwidth out")) names++  all_bw_in <- mapM (\(x,y) -> (deriveMetric index 10 x y)) (zip bw_in_keys rx)+  all_bw_out <- mapM (\(x,y) -> (deriveMetric index 10 x y)) (zip bw_out_keys tx)++  let bw_in = [buildMetric t | t <- (zip bw_in_keys all_bw_in), hasMetric t]+  let bw_out = [buildMetric t | t <- (zip bw_out_keys all_bw_out), hasMetric t]++  conn <- snmp_walk_num host comm ifConnectorPresent+  adminstatus <- snmp_walk_num host comm ifAdminStatus++  let mtrs =  concat [snmp_metrics host "opstatus" $ zip names opstatus,+                      bw_in,+                      bw_out,+                      snmp_metrics host "conn" $ zip names conn,+                      snmp_metrics host "adminstatus" $ zip names adminstatus]+  riemann_send chan mtrs
+ System/Himpy/Recipes/Storage.hs view
@@ -0,0 +1,29 @@+module System.Himpy.Recipes.Storage where+import System.Himpy.Recipes.Utils+import System.Himpy.Mib+import System.Himpy.Types+import System.Himpy.Logger+import System.Himpy.Output.Riemann+import Data.List+import Control.Concurrent.STM.TChan (TChan)++sanitize :: String -> String+sanitize input = h where (h,_) = break (== ':') input++storage_pct :: (Double, Double) -> Double+storage_pct (used,size) = (used / size) * 100++storage_realsize :: (Double, Double) -> Double+storage_realsize (size,allocunits) = size * allocunits++storage_rcp :: TChan ([Metric]) -> TChan (String) -> HimpyHost -> IO ()+storage_rcp chan logchan (Host host comm _) = do++  names <- snmp_walk_str host comm hrStorageDescr+  sizes <- snmp_walk_num host comm hrStorageSize+  used <- snmp_walk_num host comm hrStorageUsed+  allocs <- snmp_walk_num host comm hrStorageAllocationUnits++  let pcts = map storage_pct $ zip used sizes+  let mtrs = snmp_metrics host "percent" $ zip (map sanitize names) pcts+  riemann_send chan mtrs
+ System/Himpy/Recipes/Utils.hs view
@@ -0,0 +1,66 @@+module System.Himpy.Recipes.Utils where+import System.Himpy.Types+import Data.List+import qualified Data.ByteString.Char8 as B+import qualified Network.Protocol.NetSNMP as Snmp++non_nul :: Char -> Bool+non_nul '\0' = False+non_nul _ = True++snmp_metric :: String -> String -> (String, Double) -> Metric+snmp_metric host suffix (ifname,metric) =+  Metric host (ifname ++ " " ++ suffix) "ok" metric++snmp_metrics :: String -> String -> [(String, Double)] -> [Metric]+snmp_metrics host suffix metrics =+  [snmp_metric host suffix m | m <- metrics]++snmp_last_str :: Snmp.ASNValue -> String+snmp_last_str v =  Snmp.showASNValue v++snmp_last_strs :: Either String [Snmp.SnmpResult] -> [String]+snmp_last_strs (Right results) =+  [show r | r <- results]++snmp_num :: Snmp.ASNValue -> Double+snmp_num (Snmp.Integer32 v) = fromIntegral v :: Double+snmp_num (Snmp.Integer64 v) = fromIntegral v :: Double+snmp_num (Snmp.Counter32 v) = fromIntegral v :: Double+snmp_num (Snmp.Counter64 v) = fromIntegral v :: Double+snmp_num (Snmp.Unsigned32 v) = fromIntegral v :: Double+snmp_num (Snmp.Unsigned64 v) = fromIntegral v :: Double+snmp_num (Snmp.Gauge32 v) = fromIntegral v :: Double++snmp_nums :: Either String [Snmp.SnmpResult] -> [Double]+snmp_nums (Right results) =+  [snmp_num value | (Snmp.SnmpResult oid value) <- results]++snmp_str :: Snmp.ASNValue -> String+snmp_str (Snmp.OctetString v _) = filter non_nul $ B.unpack v+snmp_str v = "unknown: " ++ (Snmp.showASNValue v)++snmp_strs :: Either String [Snmp.SnmpResult] -> [String]+snmp_strs (Right results) =+  [snmp_str value | (Snmp.SnmpResult oid value) <- results]++snmp_walk_str :: String -> String -> Snmp.RawOID -> IO ([String])+snmp_walk_str host comm oid = do+  v <- Snmp.snmpWalk Snmp.snmp_version_2c (B.pack host) (B.pack comm) oid+  return (snmp_strs v)++snmp_walk_num :: String -> String -> Snmp.RawOID -> IO ([Double])+snmp_walk_num host comm oid = do+  v <- Snmp.snmpWalk Snmp.snmp_version_2c (B.pack host) (B.pack comm) oid+  return (snmp_nums v)++snmp_walk_last_str :: String -> String -> Snmp.RawOID -> IO ([String])+snmp_walk_last_str host comm oid = do+  v <- Snmp.snmpWalk Snmp.snmp_version_2c (B.pack host) (B.pack comm) oid+  return (snmp_last_strs v)++snmp_get_num :: String -> String -> Snmp.RawOID -> IO (Double)+snmp_get_num host comm oid = do+  v <- Snmp.snmpGet Snmp.snmp_version_2c (B.pack host) (B.pack comm) oid+  let out = case v of (Right (Snmp.SnmpResult oid value)) -> snmp_num value+  return out
+ System/Himpy/Recipes/WinServices.hs view
@@ -0,0 +1,20 @@+module System.Himpy.Recipes.WinServices where+import System.Himpy.Recipes.Utils+import System.Himpy.Mib+import System.Himpy.Types+import System.Himpy.Logger+import System.Himpy.Output.Riemann+import Control.Concurrent.STM.TChan (TChan)+import qualified Data.Map as M++srv_rcp :: [String] -> TChan ([Metric]) -> TChan (String) -> HimpyHost -> IO ()+srv_rcp srvs chan logchan (Host host comm _) = do++  names <- snmp_walk_str host comm lanMgrServiceDescr+  statuses <- snmp_walk_num host comm lanMgrServiceStatus++  let seen = M.fromList $ zip names statuses+  let defaults = M.fromList $ zip srvs $ repeat 0.0++  let flat = M.assocs $ M.union seen defaults+  riemann_send chan $ snmp_metrics host "service" flat
+ System/Himpy/Serializers/Riemann.hs view
@@ -0,0 +1,105 @@+{-# LANGUAGE DeriveGeneric #-}+module System.Himpy.Serializers.Riemann where+import System.Himpy.Types+import System.Himpy.Utils+import Data.Int+import Data.Text+import Data.ProtocolBuffers+import Data.Monoid (mempty)+import Data.Serialize (runPut)+import Data.Serialize.Put (Put)+import Data.ByteString as B+import Data.TypeLevel (D1, D2, D3, D4, D5, D6, D7, D8,+                       D9, D10, D11, D12, D13, D14, D15)+import GHC.Generics (Generic)++data ProtoAttr = ProtoAttr {+  key :: Optional D1 (Value Text),+  value :: Optional D2 (Value Text)+  } deriving (Generic, Show)++data ProtoEvent = ProtoEvent {+  time  :: Optional D1 (Value Int64),+  state :: Optional D2 (Value Text),+  service :: Optional D3 (Value Text),+  host :: Optional D4 (Value Text),+  description :: Optional D5 (Value Text),+  tags :: Repeated D7 (Value Text),+  ttl :: Optional D8 (Value Float),++  attributes :: Repeated D9 (Message ProtoAttr),++  metric_sint64 :: Optional D13 (Value Int64),+  metric_d      :: Optional D14 (Value Double),+  metric_f      :: Optional D15 (Value Double)+  } deriving (Generic, Show)++data ProtoQuery = ProtoQuery {+  query :: Optional D1 (Value Text)+  } deriving (Generic, Show)+++data ProtoState = ProtoState {+  s_time :: Optional D1 (Value Int64),+  s_state :: Optional D2 (Value Text),+  s_service :: Optional D3 (Value Text),+  s_host :: Optional D4 (Value Text),+  s_description :: Optional D5 (Value Text),+  s_once :: Optional D6 (Value Bool),+  s_tags :: Repeated D7 (Value Text),+  s_ttl :: Optional D8 (Value Double)+  } deriving (Generic, Show)++data ProtoMsg = ProtoMsg {+  m_ok :: Optional D2 (Value Bool),+  m_error :: Optional D3 (Value Text),+  m_states :: Repeated D4 (Message ProtoState),+  m_query :: Optional D5 (Message ProtoQuery),+  m_events :: Repeated D6  (Message ProtoEvent)+} deriving (Generic, Show)+++instance Encode ProtoAttr+instance Decode ProtoAttr+instance Encode ProtoEvent+instance Decode ProtoEvent+instance Encode ProtoMsg+instance Decode ProtoMsg+instance Encode ProtoQuery+instance Decode ProtoQuery+instance Encode ProtoState+instance Decode ProtoState+++pack_text = Data.Text.pack++metric_to_protoevent :: Integer -> Metric -> Float -> ProtoEvent+metric_to_protoevent tstamp (Metric host service state metric) dttl =+  ProtoEvent {+          time = putField $ Just (fromIntegral tstamp :: Int64),+          state = putField $ Just $ pack_text state,+          service = putField $ Just $ pack_text service,+          host = putField $ Just $ pack_text host,+          description = putField Nothing,+          tags = putField [pack_text "snmp", pack_text "himpy"],+          ttl = putField $ (Just dttl),+          attributes = putField mempty,+          metric_sint64 = putField Nothing,+          metric_d = putField $ Just metric,+          metric_f = putField Nothing+          }++metrics_to_msg :: [Metric] -> Float -> IO (B.ByteString)+metrics_to_msg metrics dttl = do+  Prelude.putStrLn $ "got mtm ttl: " ++ (show dttl)+  tstamp <- timestamp+  let events = [metric_to_protoevent tstamp m dttl | m <- metrics]+  let msg = ProtoMsg {+        m_ok = putField $ Just True,+        m_error = putField mempty,+        m_query = putField Nothing,+        m_states = putField mempty,+        m_events = putField events+        }+  let encoded = runPut $ encodeMessage msg+  return encoded
+ System/Himpy/Types.hs view
@@ -0,0 +1,29 @@+module System.Himpy.Types where+import Control.Concurrent.MVar (MVar)+import Data.Map (Map)+import Control.Concurrent.STM.TChan (TChan)++data Threshold = Threshold {+  tHost     :: String,+  tService  :: String,+  tWarning  :: Maybe Double,+  tCritical :: Double+  } deriving (Show, Read)++data Metric = Metric String String String Double deriving (Show, Read)++data HimpyConfig = Hosts Integer Float String Integer String [HimpyHost] [Threshold] deriving (Show, Read)++data HimpyHost = Host String String [HimpyRecipe] deriving (Show, Read)++data HimpyRecipe = WinSrvRecipe [String] |+                   StorageRecipe |+                   LoadRecipe |+                   JuniperRecipe |+                   NetworkRecipe deriving (Show, Read)++type HInternalIndex = Map (String,String) Double+type HIndex = MVar HInternalIndex++class RecipeMod mod where+  recipe :: mod -> TChan [Metric] -> TChan String -> HimpyHost -> HIndex -> IO ()
+ System/Himpy/Utils.hs view
@@ -0,0 +1,20 @@+module System.Himpy.Utils (timestamp, octets) where+import Data.Bits+import Data.Word+import qualified System.Time as T+import qualified System.Time.Utils as TU++timestamp :: IO (Integer)+timestamp = do+  t <- T.getClockTime+  let ct = T.toUTCTime t+  epoch <- TU.timelocal ct+  return epoch++octets :: Word32 -> [Word8]+octets w =+  [ fromIntegral (w `shiftR` 24)+  , fromIntegral (w `shiftR` 16)+  , fromIntegral (w `shiftR` 8)+  , fromIntegral w+  ]
+ himpy.cabal view
@@ -0,0 +1,41 @@+-- Initial himpy.cabal generated by cabal init.  For further documentation,+--  see http://haskell.org/cabal/users-guide/++name:                himpy+version:             0.5.0+synopsis:            multithreaded snmp poller for riemann+description:         +     Himpy provides a multi-threaded snmp poller which reports+     to riemann.+     .+     Polled MIBs are grouped in recipes and produce relative results+     whereever possible (for instance, the storage recipe reports percents).+homepage:            https://github.com/pyr/himpy+license:             MIT+license-file:        LICENSE+author:              Pierre-Yves Ritschard+maintainer:          pyr@spootnik.org+-- copyright:           +category:            System+build-type:          Simple+extra-source-files:  README.md+                     System/Himpy/*.hs+                     System/Himpy/Recipes/*.hs+                     System/Himpy/Serializers/*.hs+                     System/Himpy/Output/*.hs+cabal-version:       >=1.10++executable himpy+  main-is:           himpy.hs+  ghc-options:       -threaded+  -- other-modules:       +  -- other-extensions:    +  build-depends:       base >=4.6 && <4.7, NetSNMP >= 0.3.0.6, bytestring >= 0.10.0.2,+                       stm >= 2.4.2, containers >= 0.5.0.0, type-level >= 0.2.4,+                       cereal >= 0.4.0.1, protobuf >= 0.1.1, text >= 0.11.2.3,+                       MissingH >= 1.2.0.2, old-time >= 1.1.0.1, network >= 2.4.2.0,+                       regex-posix >= 0.93.1, binary >= 0.5.1.1, aeson >= 0.6.1.0,+                       unordered-containers >= 0.2.3.3, attoparsec >= 0.11.1.0, vector >= 0.10.9.1,+                       stm >= 2.4.2+  -- hs-source-dirs:      +  default-language:    Haskell2010
+ himpy.hs view
@@ -0,0 +1,50 @@+import System.Himpy.Config (configure)+import System.Himpy.Logger (log_start, log_info)+import System.Himpy.Recipes+import System.Himpy.Types+import System.Himpy.Serializers.Riemann+import System.Himpy.Output.Riemann+import System.Himpy.Index+import System.IO+import System.Environment+import Data.List (concatMap)+import Control.Concurrent (forkIO, threadDelay)+import Control.Monad (void, forever)+import Control.Monad.STM (atomically)+import Control.Concurrent.STM.TChan (newTChanIO, TChan)+import qualified Data.ByteString.Char8 as B+import qualified Network.Protocol.NetSNMP as Snmp++collect_profiles :: TChan ([Metric]) -> TChan (String) -> Integer -> HimpyHost -> HIndex -> IO ()+collect_profiles chan logchan ival (Host host comm (prof:profs)) index = do+  recipe prof chan logchan (Host host comm [prof]) index+  collect_profiles chan logchan ival (Host host comm profs) index+collect_profiles chan logchan ival (Host host _ []) index = do+  log_info logchan $ "ticking for host: " ++ host+  threadDelay (fromIntegral (ival * 1000000) :: Int)++start_collector ::  TChan ([Metric]) -> TChan (String) -> Integer -> HimpyHost -> HIndex -> IO ()+start_collector chan logchan ival host index =+  void $ forkIO $ forever $ collect_profiles chan logchan ival host index++start_collectors :: TChan ([Metric]) -> TChan (String) -> HimpyConfig -> HIndex -> IO ()+start_collectors chan logchan (Hosts ival t x y z (host:hosts) _) index = do+  start_collector chan logchan ival host index+  start_collectors chan logchan (Hosts ival t x y z hosts []) index+start_collectors chan logchan (Hosts _ _ _ _ _ [] _) _ = do return ()++get_conf_path [] = "/etc/himpy.conf"+get_conf_path (path:_) = path++main :: IO ()+main = do+  Snmp.initialize+  index <- initIndex+  args <- getArgs+  config <- configure $ get_conf_path args+  let (Hosts intval ttl host port logfile _ thresholds) = config+  logchan <- log_start logfile+  chan <- riemann_start logchan host port ttl thresholds+  log_info logchan "starting himpy"+  start_collectors chan logchan config index+  void $ forever $ threadDelay (fromIntegral (intval * 1000000) :: Int)