packages feed

arbor-monad-metric (empty) → 0.0.1

raw patch · 11 files changed

+356/−0 lines, 11 filesdep +arbor-datadogdep +arbor-monad-metricdep +basesetup-changed

Dependencies added: arbor-datadog, arbor-monad-metric, base, bytestring, containers, generic-lens, hedgehog, hspec, hw-hspec-hedgehog, lens, mtl, network, resourcet, stm, transformers

Files

+ ChangeLog.md view
@@ -0,0 +1,3 @@+# Changelog for arbor-monad-metric++## Unreleased changes
+ LICENSE view
@@ -0,0 +1,21 @@+MIT License++Copyright (c) 2018 Arbor Networks++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,6 @@+# arbor-monad-metric++[![CircleCI](https://circleci.com/gh/arbor/arbor-monad-metric.svg?style=svg)](https://circleci.com/gh/arbor/arbor-monad-metric)++This is a fork of `arbor-monad-counter` into a module namespace to add support for varied metric types.+The `arbor-monad-counter` is retained for backwards compatibility.
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ arbor-monad-metric.cabal view
@@ -0,0 +1,76 @@+name:           arbor-monad-metric+version:        0.0.1+description:    Please see the README on Github at <https://github.com/arbor/arbor-monad-metric#readme>+category:       Services+homepage:       https://github.com/arbor/arbor-monad-metric#readme+bug-reports:    https://github.com/arbor/arbor-monad-metric/issues+author:         Arbor Networks+maintainer:     mayhem@arbor.net+copyright:      Arbor Networks+license:        MIT+license-file:   LICENSE+build-type:     Simple+cabal-version:  >= 1.10+extra-source-files:+    ChangeLog.md+    README.md++source-repository head+  type: git+  location: https://github.com/arbor/arbor-monad-metric++library+  exposed-modules:+      Arbor.Monad.Metric+      Arbor.Monad.Metric.Generic+      Arbor.Monad.Metric.Type+  other-modules:+      Paths_arbor_monad_metric+  hs-source-dirs:+      src+  default-extensions: BangPatterns FlexibleContexts FlexibleInstances GeneralizedNewtypeDeriving MultiParamTypeClasses OverloadedStrings TupleSections+  ghc-options: -Wall -Wcompat -Wincomplete-record-updates -Wincomplete-uni-patterns -Wredundant-constraints+  build-depends:+      arbor-datadog+    , base >=4.7 && <5+    , containers+    , generic-lens+    , lens+    , mtl+    , resourcet+    , stm+    , transformers+  default-language: Haskell2010++test-suite arbor-monad-metric-test+  type: exitcode-stdio-1.0+  main-is: Spec.hs+  other-modules:+      Arbor.Monad.MetricSpec+      Arbor.Monad.UdpServer+      Arbor.Monad.Metric+      Arbor.Monad.Metric.Generic+      Arbor.Monad.Metric.Type+      Paths_arbor_monad_metric+  hs-source-dirs:+      test+      src+  default-extensions: BangPatterns FlexibleContexts FlexibleInstances GeneralizedNewtypeDeriving MultiParamTypeClasses OverloadedStrings TupleSections+  ghc-options: -threaded -rtsopts -with-rtsopts=-N+  build-depends:+      arbor-datadog+    , arbor-monad-metric+    , base >=4.7 && <5+    , bytestring+    , containers+    , generic-lens+    , hedgehog+    , hspec+    , hw-hspec-hedgehog+    , lens+    , mtl+    , network+    , resourcet+    , stm+    , transformers+  default-language: Haskell2010
+ src/Arbor/Monad/Metric.hs view
@@ -0,0 +1,39 @@+{-# LANGUAGE DataKinds           #-}+{-# LANGUAGE ScopedTypeVariables #-}++module Arbor.Monad.Metric+  ( MonadMetrics+  , Z.getMetrics+  , metric+  , Counter(..)+  , Gauge(..)+  , Metrics(..)+  , newMetricsIO+  , extractValues+  ) where++import Arbor.Monad.Metric.Generic  (metric)+import Arbor.Monad.Metric.Type     (Counter (..), Gauge (..), MetricFamily (..), MetricMap, Metrics (Metrics), MonadMetrics)+import Control.Concurrent.STM.TVar+import Control.Monad.STM           (STM)+import Data.Proxy++import qualified Arbor.Monad.Metric.Type as Z+import qualified Control.Concurrent.STM  as STM+import qualified Data.Map.Strict         as M++newMetricsIO :: IO Metrics+newMetricsIO = Metrics+  <$> STM.newTVarIO M.empty+  <*> STM.newTVarIO M.empty++extractValues :: forall k. ()+  => MetricFamily k+  => Proxy k+  -> MetricMap k (MetricState k)+  -> STM ([(k, MetricValue k)], [TVar (MetricState k)])+extractValues pk m = do+  let names = M.keys m+  let tvars = M.elems m+  nums <- fmap (metricStateToValue pk <$>) . sequence $ readTVar <$> tvars+  return (zip names nums, tvars)
+ src/Arbor/Monad/Metric/Generic.hs view
@@ -0,0 +1,49 @@+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TypeApplications    #-}++module Arbor.Monad.Metric.Generic+  ( metric+  ) where++import Arbor.Monad.Metric.Type     (MetricFamily (..), Metrics, MonadMetrics)+import Control.Concurrent.STM.TVar+import Control.Monad.IO.Class+import Data.Semigroup (Semigroup, (<>))+import Data.Proxy++import qualified Arbor.Monad.Metric.Type as Z+import qualified Control.Concurrent.STM  as STM+import qualified Data.Map.Strict         as M++-- Modify the current value with the supplied function+metric :: ()+  => Ord k+  => Semigroup (MetricState k)+  => MetricFamily k+  => MonadMetrics m+  => k+  -> MetricValue k+  -> m ()+metric key v = do+  metrics <- Z.getMetrics+  liftIO $ metric' key v metrics++-- Modify the current value with the supplied function+metric' :: forall k . ()+  => Ord k+  => MetricFamily k+  => Semigroup (MetricState k)+  => k+  -> MetricValue k+  -> Metrics+  -> IO ()+metric' key value metrics = do+  let tCounters = metricMapTVarOf metrics :: STM.TVar (Z.MetricMap k (MetricState k))+  STM.atomically $ do+    counters <- STM.readTVar tCounters+    case counters M.!? key of+      Just tv -> modifyTVar tv (<> metricValueToState (Proxy @k) value)+      Nothing -> do+        tv <- STM.newTVar (metricValueToState (Proxy @k) value)+        let counters' = M.insert key tv counters+        STM.writeTVar tCounters counters'
+ src/Arbor/Monad/Metric/Type.hs view
@@ -0,0 +1,85 @@+{-# LANGUAGE DataKinds             #-}+{-# LANGUAGE DeriveGeneric         #-}+{-# LANGUAGE DuplicateRecordFields #-}+{-# LANGUAGE TypeApplications      #-}+{-# LANGUAGE TypeFamilies          #-}++module Arbor.Monad.Metric.Type where++import Control.Lens+import Control.Monad.Except+import Control.Monad.Reader+import Control.Monad.State+import Control.Monad.Trans.Identity+import Control.Monad.Trans.Maybe+import Control.Monad.Trans.Resource+import Data.Generics.Product.Any+import Data.Proxy+import Data.Semigroup+import GHC.Generics++import qualified Control.Concurrent.STM as STM+import qualified Data.Map.Strict        as M++newtype Counter = Counter+  { name :: String+  } deriving (Eq, Ord, Show)++newtype Gauge = Gauge+  { name :: String+  } deriving (Eq, Ord, Show)++type MetricMap k v = M.Map k (STM.TVar v)++data Metrics = Metrics+  { counters :: STM.TVar (MetricMap Counter (MetricState Counter))+  , gauges   :: STM.TVar (MetricMap Gauge   (MetricState Gauge  ))+  } deriving (Generic)++class (Monad m, MonadIO m) => MonadMetrics m where+  getMetrics :: m Metrics++instance MonadMetrics m => MonadMetrics (ExceptT e m) where+  getMetrics = lift getMetrics++instance MonadMetrics m => MonadMetrics (IdentityT m) where+  getMetrics = lift getMetrics++instance MonadMetrics m => MonadMetrics (MaybeT m) where+  getMetrics = lift getMetrics++instance MonadMetrics m => MonadMetrics (ReaderT e m) where+  getMetrics = lift getMetrics++instance MonadMetrics m => MonadMetrics (ResourceT m) where+  getMetrics = lift getMetrics++instance MonadMetrics m => MonadMetrics (StateT s m) where+  getMetrics = lift getMetrics++class MetricFamily k where+  type MetricValue k+  type MetricState k+  metricMapTVarOf :: Metrics -> STM.TVar (MetricMap k (MetricState k))+  metricValueToState :: Proxy k -> MetricValue k -> MetricState k+  metricStateToValue :: Proxy k -> MetricState k -> MetricValue k++getMetricMapTVar :: (MetricFamily k, MonadMetrics m) => m (STM.TVar (MetricMap k (MetricState k)))+getMetricMapTVar = getMetrics <&> metricMapTVarOf++getMetricMap :: (MetricFamily k, MonadMetrics m) => m (MetricMap k (MetricState k))+getMetricMap = liftIO . STM.readTVarIO =<< getMetricMapTVar++instance MetricFamily Counter where+  type MetricValue Counter = Int+  type MetricState Counter = Sum Int+  metricMapTVarOf = (^. the @"counters")+  metricValueToState _ = Sum+  metricStateToValue _ = getSum++instance MetricFamily Gauge where+  type MetricValue Gauge = Double+  type MetricState Gauge = Last Double+  metricMapTVarOf = (^. the @"gauges")+  metricValueToState _ = Last+  metricStateToValue _ = getLast
+ test/Arbor/Monad/MetricSpec.hs view
@@ -0,0 +1,38 @@+module Arbor.Monad.MetricSpec+  ( spec+  ) where++import Control.Concurrent+import Control.Exception      (bracket)+import Control.Monad.IO.Class++import qualified Arbor.Monad.UdpServer     as UDP+import qualified Control.Concurrent.STM    as STM+import qualified Data.ByteString.Char8     as BS+import qualified Network.Socket            as S hiding (recv, recvFrom, send, sendTo)+import qualified Network.Socket.ByteString as S++import HaskellWorks.Hspec.Hedgehog+import Hedgehog+import Test.Hspec++{-# ANN module ("HLint: ignore Redundant do"        :: String) #-}+{-# ANN module ("HLint: ignore Reduce duplication"  :: String) #-}+{-# ANN module ("HLint: redundant bracket"          :: String) #-}++handler :: STM.TVar [BS.ByteString] -> UDP.UdpHandler+handler tMsgs addr msg = do+  STM.atomically $ STM.modifyTVar tMsgs (msg:)+  putStrLn $ "From " ++ show addr ++ ": " ++ show msg++spec :: Spec+spec = describe "Arbor.Monad.MetricSpec" $ do+  it "Metrics library actually sends statsd messages over UDP" $ requireTest $ do+    tMessages <- liftIO $ STM.newTVarIO []+    sock <- liftIO $ UDP.createUdpServer "5555"+    threadId <- liftIO $ forkIO $ do+      UDP.runUdpServer sock (handler tMessages)+    liftIO $ threadDelay 1000000+    liftIO $ killThread threadId+    messages <- liftIO $ STM.readTVarIO tMessages+    messages === []
+ test/Arbor/Monad/UdpServer.hs view
@@ -0,0 +1,36 @@+module Arbor.Monad.UdpServer+  ( createUdpServer+  , runUdpServer+  , UdpHandler+  ) where++import Network.Socket++import qualified Data.ByteString           as BS+import qualified Network.Socket.ByteString as BS++type UdpHandler = SockAddr -> BS.ByteString -> IO ()++createUdpServer :: ()+    => String       -- ^ Port number or name; 514 is default+    -> IO Socket+createUdpServer port = withSocketsDo $ do+  addrinfos <- getAddrInfo+              (Just (defaultHints {addrFlags = [AI_PASSIVE]}))+              Nothing (Just port)+  let serveraddr = head addrinfos++  sock <- socket (addrFamily serveraddr) Datagram defaultProtocol++  bind sock (addrAddress serveraddr)+  return sock++runUdpServer :: ()+  => Socket+  -> UdpHandler+  -> IO ()+runUdpServer sock handler = withSocketsDo $ procMessages sock+  where procMessages sock = do+          (msg, addr) <- BS.recvFrom sock 1024+          handler addr msg+          procMessages sock
+ test/Spec.hs view
@@ -0,0 +1,1 @@+{-# OPTIONS_GHC -F -pgmF hspec-discover #-}