diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -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.
diff --git a/README.md b/README.md
new file mode 100644
--- /dev/null
+++ b/README.md
@@ -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"
+        }
+    ]
+}
+```
+
diff --git a/Setup.hs b/Setup.hs
new file mode 100644
--- /dev/null
+++ b/Setup.hs
@@ -0,0 +1,2 @@
+import Distribution.Simple
+main = defaultMain
diff --git a/System/Himpy/Config.hs b/System/Himpy/Config.hs
new file mode 100644
--- /dev/null
+++ b/System/Himpy/Config.hs
@@ -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
diff --git a/System/Himpy/Index.hs b/System/Himpy/Index.hs
new file mode 100644
--- /dev/null
+++ b/System/Himpy/Index.hs
@@ -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)))
diff --git a/System/Himpy/Logger.hs b/System/Himpy/Logger.hs
new file mode 100644
--- /dev/null
+++ b/System/Himpy/Logger.hs
@@ -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
diff --git a/System/Himpy/Mib.hs b/System/Himpy/Mib.hs
new file mode 100644
--- /dev/null
+++ b/System/Himpy/Mib.hs
@@ -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
diff --git a/System/Himpy/Output/Riemann.hs b/System/Himpy/Output/Riemann.hs
new file mode 100644
--- /dev/null
+++ b/System/Himpy/Output/Riemann.hs
@@ -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
diff --git a/System/Himpy/Recipes.hs b/System/Himpy/Recipes.hs
new file mode 100644
--- /dev/null
+++ b/System/Himpy/Recipes.hs
@@ -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
diff --git a/System/Himpy/Recipes/Cisco.hs b/System/Himpy/Recipes/Cisco.hs
new file mode 100644
--- /dev/null
+++ b/System/Himpy/Recipes/Cisco.hs
@@ -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)
diff --git a/System/Himpy/Recipes/Juniper.hs b/System/Himpy/Recipes/Juniper.hs
new file mode 100644
--- /dev/null
+++ b/System/Himpy/Recipes/Juniper.hs
@@ -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)
diff --git a/System/Himpy/Recipes/Load.hs b/System/Himpy/Recipes/Load.hs
new file mode 100644
--- /dev/null
+++ b/System/Himpy/Recipes/Load.hs
@@ -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
diff --git a/System/Himpy/Recipes/Network.hs b/System/Himpy/Recipes/Network.hs
new file mode 100644
--- /dev/null
+++ b/System/Himpy/Recipes/Network.hs
@@ -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
diff --git a/System/Himpy/Recipes/Storage.hs b/System/Himpy/Recipes/Storage.hs
new file mode 100644
--- /dev/null
+++ b/System/Himpy/Recipes/Storage.hs
@@ -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
diff --git a/System/Himpy/Recipes/Utils.hs b/System/Himpy/Recipes/Utils.hs
new file mode 100644
--- /dev/null
+++ b/System/Himpy/Recipes/Utils.hs
@@ -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
diff --git a/System/Himpy/Recipes/WinServices.hs b/System/Himpy/Recipes/WinServices.hs
new file mode 100644
--- /dev/null
+++ b/System/Himpy/Recipes/WinServices.hs
@@ -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
diff --git a/System/Himpy/Serializers/Riemann.hs b/System/Himpy/Serializers/Riemann.hs
new file mode 100644
--- /dev/null
+++ b/System/Himpy/Serializers/Riemann.hs
@@ -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
diff --git a/System/Himpy/Types.hs b/System/Himpy/Types.hs
new file mode 100644
--- /dev/null
+++ b/System/Himpy/Types.hs
@@ -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 ()
diff --git a/System/Himpy/Utils.hs b/System/Himpy/Utils.hs
new file mode 100644
--- /dev/null
+++ b/System/Himpy/Utils.hs
@@ -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
+  ]
diff --git a/himpy.cabal b/himpy.cabal
new file mode 100644
--- /dev/null
+++ b/himpy.cabal
@@ -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
diff --git a/himpy.hs b/himpy.hs
new file mode 100644
--- /dev/null
+++ b/himpy.hs
@@ -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)
