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 +19/−0
- README.md +50/−0
- Setup.hs +2/−0
- System/Himpy/Config.hs +97/−0
- System/Himpy/Index.hs +23/−0
- System/Himpy/Logger.hs +28/−0
- System/Himpy/Mib.hs +36/−0
- System/Himpy/Output/Riemann.hs +101/−0
- System/Himpy/Recipes.hs +16/−0
- System/Himpy/Recipes/Cisco.hs +27/−0
- System/Himpy/Recipes/Juniper.hs +25/−0
- System/Himpy/Recipes/Load.hs +18/−0
- System/Himpy/Recipes/Network.hs +42/−0
- System/Himpy/Recipes/Storage.hs +29/−0
- System/Himpy/Recipes/Utils.hs +66/−0
- System/Himpy/Recipes/WinServices.hs +20/−0
- System/Himpy/Serializers/Riemann.hs +105/−0
- System/Himpy/Types.hs +29/−0
- System/Himpy/Utils.hs +20/−0
- himpy.cabal +41/−0
- himpy.hs +50/−0
+ 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)