{-# 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