{-# LANGUAGE EmptyDataDecls #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-} {-# LANGUAGE InstanceSigs #-} {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE StandaloneDeriving #-} {-# LANGUAGE TypeFamilies #-} {-# LANGUAGE UndecidableInstances #-} {-# LANGUAGE PolyKinds #-} {-# LANGUAGE TypeApplications #-} {-# LANGUAGE RankNTypes #-} -- | -- Module: -- Reflex.Profiled -- Description: -- This module contains an instance of the 'Reflex' class that provides -- profiling/cost-center information. module Reflex.Profiled where import Control.Monad import Control.Monad.Exception import Control.Monad.Fix import Control.Monad.Primitive import Control.Monad.Reader import Control.Monad.Ref import Control.Monad.State.Strict (StateT, execStateT, modify) import Data.Coerce import Data.Dependent.Map (DMap, GCompare) import Data.FastMutableIntMap import Data.IORef import Data.List import Data.Map (Map) import qualified Data.Map.Strict as Map import Data.Monoid ((<>)) import Data.Ord import Data.Profunctor.Unsafe ((#.)) import qualified Data.Semigroup as S import Data.Type.Coercion import Foreign.Ptr import GHC.Foreign import GHC.IO.Encoding import GHC.Stack import Reflex.Class import Reflex.Host.Class import Reflex.PerformEvent.Class import System.IO.Unsafe import Unsafe.Coerce data ProfiledTimeline t {-# NOINLINE profilingData #-} profilingData :: IORef (Map (Ptr CostCentreStack) Int) profilingData = unsafePerformIO $ newIORef Map.empty data CostCentreTree = CostCentreTree { _costCentreTree_ownEntries :: !Int , _costCentreTree_cumulativeEntries :: !Int , _costCentreTree_children :: !(Map (Ptr CostCentre) CostCentreTree) } deriving (Show, Eq, Ord) instance S.Semigroup CostCentreTree where (CostCentreTree oa ea ca) <> (CostCentreTree ob eb cb) = CostCentreTree (oa + ob) (ea + eb) $ Map.unionWith (S.<>) ca cb instance Monoid CostCentreTree where mempty = CostCentreTree 0 0 mempty mappend = (S.<>) getCostCentreStack :: Ptr CostCentreStack -> IO [Ptr CostCentre] getCostCentreStack = go [] where go l ccs = if ccs == nullPtr then return l else do cc <- ccsCC ccs parent <- ccsParent ccs go (cc : l) parent toCostCentreTree :: Ptr CostCentreStack -> Int -> IO CostCentreTree toCostCentreTree ccs n = foldr (\cc child -> CostCentreTree 0 n $ Map.singleton cc child) (CostCentreTree n n mempty) <$> getCostCentreStack ccs getCostCentreTree :: IO CostCentreTree getCostCentreTree = do vals <- readIORef profilingData mconcat <$> mapM (uncurry toCostCentreTree) (Map.toList vals) formatCostCentreTree :: CostCentreTree -> IO String formatCostCentreTree cct0 = unlines . reverse <$> execStateT (go 0 cct0) [] where go :: Int -> CostCentreTree -> StateT [String] IO () go depth cct = do let children = sortOn (Down . _costCentreTree_cumulativeEntries . snd) $ Map.toList $ _costCentreTree_children cct indent = mconcat $ replicate depth " " forM_ children $ \(cc, childCct) -> do lbl <- liftIO $ peekCString utf8 =<< ccLabel cc mdl <- liftIO $ peekCString utf8 =<< ccModule cc loc <- liftIO $ peekCString utf8 =<< ccSrcSpan cc let description = mdl <> "." <> lbl <> " (" <> loc <> ")" modify $ (:) $ indent <> description <> "\t" <> show (_costCentreTree_cumulativeEntries childCct) <> "\t" <> show (_costCentreTree_ownEntries childCct) go (succ depth) childCct showProfilingData :: IO () showProfilingData = do putStr =<< formatCostCentreTree =<< getCostCentreTree writeProfilingData :: FilePath -> IO () writeProfilingData p = do writeFile p =<< formatCostCentreTree =<< getCostCentreTree newtype ProfiledM m a = ProfiledM { runProfiledM :: m a } deriving (Functor, Applicative, Monad, MonadFix, MonadException, MonadAsyncException) profileEvent :: Reflex t => Event t a -> Event t a profileEvent e = unsafePerformIO $ do stack <- getCurrentCCS e let f x = unsafePerformIO $ do modifyIORef' profilingData $ Map.insertWith (+) stack 1 return $ return $ Just x return $ pushCheap f e --TODO: Instead of profiling just the input or output of each one, profile all the inputs and all the outputs instance Reflex t => Reflex (ProfiledTimeline t) where newtype Behavior (ProfiledTimeline t) a = Behavior_Profiled { unBehavior_Profiled :: Behavior t a } newtype Event (ProfiledTimeline t) a = Event_Profiled { unEvent_Profiled :: Event t a } newtype Dynamic (ProfiledTimeline t) a = Dynamic_Profiled { unDynamic_Profiled :: Dynamic t a } newtype Incremental (ProfiledTimeline t) p = Incremental_Profiled { unIncremental_Profiled :: Incremental t p } type PushM (ProfiledTimeline t) = ProfiledM (PushM t) type PullM (ProfiledTimeline t) = ProfiledM (PullM t) never = Event_Profiled never constant = Behavior_Profiled . constant push f (Event_Profiled e) = coerce $ push (coerce f) $ profileEvent e -- Profile before rather than after; this way fanout won't count against us pushCheap f (Event_Profiled e) = coerce $ pushCheap (coerce f) $ profileEvent e pull = Behavior_Profiled . pull . coerce fanG (Event_Profiled e) = EventSelectorG $ coerce $ selectG (fanG $ profileEvent e) mergeG :: forall (k :: z -> *) q v. GCompare k => (forall a. q a -> Event (ProfiledTimeline t) (v a)) -> DMap k q -> Event (ProfiledTimeline t) (DMap k v) mergeG nt = Event_Profiled #. mergeG (coerce nt) switch (Behavior_Profiled b) = coerce $ profileEvent $ switch (coerceBehavior b) coincidence (Event_Profiled e) = coerce $ profileEvent $ coincidence (coerceEvent e) current (Dynamic_Profiled d) = coerce $ current d updated (Dynamic_Profiled d) = coerce $ profileEvent $ updated d unsafeBuildDynamic (ProfiledM a0) (Event_Profiled a') = coerce $ unsafeBuildDynamic a0 a' unsafeBuildIncremental (ProfiledM a0) (Event_Profiled a') = coerce $ unsafeBuildIncremental a0 a' mergeIncrementalG nt = (Event_Profiled . coerce) #. mergeIncrementalG nt mergeIncrementalWithMoveG nt = (Event_Profiled . coerce) #. mergeIncrementalWithMoveG nt currentIncremental (Incremental_Profiled i) = coerce $ currentIncremental i updatedIncremental (Incremental_Profiled i) = coerce $ profileEvent $ updatedIncremental i incrementalToDynamic (Incremental_Profiled i) = coerce $ incrementalToDynamic i behaviorCoercion c = Coercion `trans` behaviorCoercion @t c `trans` Coercion eventCoercion c = Coercion `trans` eventCoercion @t c `trans` Coercion dynamicCoercion c = Coercion `trans` dynamicCoercion @t c `trans` Coercion incrementalCoercion c d = Coercion `trans` incrementalCoercion @t c d `trans` Coercion mergeIntIncremental = Event_Profiled . mergeIntIncremental . coerceWith (Coercion `trans` incrementalCoercion Coercion Coercion `trans` Coercion) fanInt (Event_Profiled e) = coerce $ fanInt $ profileEvent e deriving instance Functor (Dynamic t) => Functor (Dynamic (ProfiledTimeline t)) deriving instance Applicative (Dynamic t) => Applicative (Dynamic (ProfiledTimeline t)) deriving instance Monad (Dynamic t) => Monad (Dynamic (ProfiledTimeline t)) instance MonadHold t m => MonadHold (ProfiledTimeline t) (ProfiledM m) where hold v0 (Event_Profiled v') = ProfiledM $ Behavior_Profiled <$> hold v0 v' holdDyn v0 (Event_Profiled v') = ProfiledM $ Dynamic_Profiled <$> holdDyn v0 v' holdIncremental v0 (Event_Profiled v') = ProfiledM $ Incremental_Profiled <$> holdIncremental v0 v' buildDynamic (ProfiledM v0) (Event_Profiled v') = ProfiledM $ Dynamic_Profiled <$> buildDynamic v0 v' headE (Event_Profiled e) = ProfiledM $ Event_Profiled <$> headE e instance MonadSample t m => MonadSample (ProfiledTimeline t) (ProfiledM m) where sample (Behavior_Profiled b) = ProfiledM $ sample b instance MonadTrans ProfiledM where lift = ProfiledM instance MonadIO m => MonadIO (ProfiledM m) where liftIO = lift . liftIO instance PerformEvent t m => PerformEvent (ProfiledTimeline t) (ProfiledM m) where type Performable (ProfiledM m) = Performable m performEvent_ = lift . performEvent_ . coerce performEvent = lift . fmap coerce . performEvent . coerce instance MonadRef m => MonadRef (ProfiledM m) where type Ref (ProfiledM m) = Ref m newRef = lift . newRef readRef = lift . readRef writeRef r = lift . writeRef r instance MonadReflexCreateTrigger t m => MonadReflexCreateTrigger (ProfiledTimeline t) (ProfiledM m) where newEventWithTrigger = lift . fmap coerce . newEventWithTrigger newFanEventWithTrigger f = do es <- lift $ newFanEventWithTrigger f return $ EventSelector $ \k -> coerce $ select es k instance MonadReader r m => MonadReader r (ProfiledM m) where ask = lift ask local f (ProfiledM a) = ProfiledM $ local f a reader = lift . reader instance ReflexHost t => ReflexHost (ProfiledTimeline t) where type EventTrigger (ProfiledTimeline t) = EventTrigger t type EventHandle (ProfiledTimeline t) = EventHandle t type HostFrame (ProfiledTimeline t) = ProfiledM (HostFrame t) instance MonadSubscribeEvent t m => MonadSubscribeEvent (ProfiledTimeline t) (ProfiledM m) where subscribeEvent = lift . subscribeEvent . coerce instance PrimMonad m => PrimMonad (ProfiledM m) where type PrimState (ProfiledM m) = PrimState m primitive = lift . primitive instance MonadReflexHost t m => MonadReflexHost (ProfiledTimeline t) (ProfiledM m) where type ReadPhase (ProfiledM m) = ProfiledM (ReadPhase m) fireEventsAndRead ts r = lift $ fireEventsAndRead ts $ coerce r runHostFrame = lift . runHostFrame . coerce instance MonadReadEvent t m => MonadReadEvent (ProfiledTimeline t) (ProfiledM m) where readEvent = lift . fmap coerce . readEvent