{-# LANGUAGE RankNTypes, GADTs #-}
{-# LANGUAGE StandaloneDeriving, ScopedTypeVariables, TypeOperators #-}
module System.Metrics.Store
( MetricsStore
, newMetricsStore
, withMetricsStore
, getMetricFromStore
) where
import qualified Data.Dependent.Map as DM
import qualified Data.Text as T
import Control.Concurrent
import Control.Concurrent.STM.TQueue
import Control.Monad.Catch
import Control.Monad.Identity
import Control.Monad.STM
import Data.GADT.Compare
import Data.Proxy
import Data.Typeable(eqT)
import GHC.TypeLits
import System.Remote.Monitoring
import Type.Reflection
import Data.Dyn
import System.Metrics.Monad.Class
type DynOrd = Dyn Ord
data SomeMetric tracker where
MkSomeMetric :: (Typeable metric, TrackerLike tracker, KnownSymbol name, Ord (metric tracker name))
=> metric tracker name -> SomeMetric tracker
deriving instance Typeable (SomeMetric t)
instance GEq SomeMetric where
geq (MkSomeMetric _) (MkSomeMetric _) = eqT
instance GCompare SomeMetric where
gcompare sm1@(MkSomeMetric (m1 :: mTy1 tTy1 nTy1)) sm2@(MkSomeMetric (m2 :: mTy2 tTy2 nTy2)) =
case eqT :: Maybe (tTy1 :~: tTy2) of
Just Refl -> case compare (toDyn m1 :: DynOrd) (toDyn m2) of
LT -> GLT
EQ -> GEQ
GT -> GGT
Nothing -> case compare (someTypeRep sm1) (someTypeRep sm2) of
LT -> GLT
EQ -> error "SomeTypeReps are equal though eqT proved them wrong"
GT -> GGT
data MetricsState = MetricsState
{ server :: Server
, metrics :: DM.DMap SomeMetric Identity
}
data MetricRequest where
MetricRequest :: (TrackerLike tracker, KnownSymbol name, Typeable metric, Ord (metric tracker name))
=> metric tracker name
-> MVar tracker
-> MetricRequest
newtype MetricsStore = MetricsStore { mReqQueue :: TQueue MetricRequest }
newMetricsStore :: Server -> IO (MetricsStore, IO ())
newMetricsStore srv = do
queue <- newTQueueIO
threadId <- forkIO $ act queue $ MetricsState srv mempty
pure (MetricsStore queue, killThread threadId)
where
act queue state = do
req <- atomically (readTQueue queue)
state' <- (\(MetricRequest metric mvar) -> handleReq state metric mvar) req
act queue state'
handleReq :: forall metric tracker name. (TrackerLike tracker, KnownSymbol name, Typeable metric, Ord (metric tracker name))
=> MetricsState
-> metric tracker name
-> MVar tracker
-> IO MetricsState
handleReq state metric mvar = do
let asSome = MkSomeMetric metric
(tracker, state') <- case DM.lookup asSome $ metrics state of
Just existing -> pure (runIdentity existing, state)
Nothing -> do
let trackerName = symbolVal (Proxy :: Proxy name)
newTracker <- createTracker (T.pack trackerName) $ serverMetricStore $ server state
pure (newTracker, state { metrics = DM.insert asSome (Identity newTracker) $ metrics state })
putMVar mvar tracker
pure state'
withMetricsStore :: Server -> (MetricsStore -> IO a) -> IO a
withMetricsStore srv f = bracket
(newMetricsStore srv)
snd
(f . fst)
getMetricFromStore :: (TrackerLike tracker, KnownSymbol name, Typeable metric, Ord (metric tracker name))
=> MetricsStore
-> metric tracker name
-> IO tracker
getMetricFromStore store metric = do
mvar <- newEmptyMVar
atomically $ writeTQueue (mReqQueue store) $ MetricRequest metric mvar
takeMVar mvar