haxl-2.0.0.0: Haxl/Core/Profile.hs
-- Copyright (c) 2014-present, Facebook, Inc.
-- All rights reserved.
--
-- This source code is distributed under the terms of a BSD license,
-- found in the LICENSE file.
{-# LANGUAGE MagicHash #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE ScopedTypeVariables #-}
-- | Implementation of lightweight profiling. Most users should
-- import "Haxl.Core" instead.
--
module Haxl.Core.Profile
( withLabel
, withFingerprintLabel
, addProfileFetch
, incrementMemoHitCounterFor
, collectProfileData
, profileCont
) where
import Data.IORef
import Data.Hashable
import Data.Monoid
import Data.Text (Text)
import Data.Typeable
import qualified Data.HashMap.Strict as HashMap
import qualified Data.HashSet as HashSet
import GHC.Exts
import qualified Data.Text as Text
import Haxl.Core.DataSource
import Haxl.Core.Flags
import Haxl.Core.Stats
import Haxl.Core.Monad
-- -----------------------------------------------------------------------------
-- Profiling
-- | Label a computation so profiling data is attributed to the label.
withLabel :: ProfileLabel -> GenHaxl u a -> GenHaxl u a
withLabel l (GenHaxl m) = GenHaxl $ \env ->
if report (flags env) < 4
then m env
else collectProfileData l m env
-- | Label a computation so profiling data is attributed to the label.
-- Intended only for internal use by 'memoFingerprint'.
withFingerprintLabel :: Addr# -> Addr# -> GenHaxl u a -> GenHaxl u a
withFingerprintLabel mnPtr nPtr (GenHaxl m) = GenHaxl $ \env ->
if report (flags env) < 4
then m env
else collectProfileData
(Text.unpackCString# mnPtr <> "." <> Text.unpackCString# nPtr)
m env
-- | Collect profiling data and attribute it to given label.
collectProfileData
:: ProfileLabel
-> (Env u -> IO (Result u a))
-> Env u
-> IO (Result u a)
collectProfileData l m env = do
a0 <- getAllocationCounter
r <- m env{profLabel=l} -- what if it throws?
a1 <- getAllocationCounter
modifyProfileData env l (a0 - a1)
-- So we do not count the allocation overhead of modifyProfileData
setAllocationCounter a1
case r of
Done a -> return (Done a)
Throw e -> return (Throw e)
Blocked ivar k -> return (Blocked ivar (Cont (withLabel l (toHaxl k))))
{-# INLINE collectProfileData #-}
modifyProfileData :: Env u -> ProfileLabel -> AllocCount -> IO ()
modifyProfileData env label allocs =
modifyIORef' (profRef env) $ \ p ->
p { profile =
HashMap.insertWith updEntry label newEntry .
HashMap.insertWith updCaller caller newCaller $
profile p }
where caller = profLabel env
newEntry =
emptyProfileData
{ profileAllocs = allocs
, profileDeps = HashSet.singleton caller }
updEntry _ old =
old { profileAllocs = profileAllocs old + allocs
, profileDeps = HashSet.insert caller (profileDeps old) }
-- subtract allocs from caller, so they are not double counted
-- we don't know the caller's caller, but it will get set on
-- the way back out, so an empty hashset is fine for now
newCaller =
emptyProfileData { profileAllocs = -allocs }
updCaller _ old =
old { profileAllocs = profileAllocs old - allocs }
-- Like collectProfileData, but intended to be run from the scheduler.
--
-- * doesn't add a dependency (the original withLabel did this)
--
-- * doesn't subtract allocs from the caller (we're evaluating this
-- cont from the top level, so we don't need this)
--
-- * doesn't wrap a Blocked continuation in withLabel (the scheduler
-- will call profileCont the next time this cont runs)
--
profileCont
:: (Env u -> IO (Result u a))
-> Env u
-> IO (Result u a)
profileCont m env = do
a0 <- getAllocationCounter
r <- m env
a1 <- getAllocationCounter
let
allocs = a0 - a1
newEntry = emptyProfileData { profileAllocs = allocs }
updEntry _ old = old { profileAllocs = profileAllocs old + allocs }
modifyIORef' (profRef env) $ \ p ->
p { profile =
HashMap.insertWith updEntry (profLabel env) newEntry $
profile p }
-- So we do not count the allocation overhead of modifyProfileData
setAllocationCounter a1
return r
{-# INLINE profileCont #-}
incrementMemoHitCounterFor :: ProfileLabel -> Profile -> Profile
incrementMemoHitCounterFor lbl p =
p { profile = HashMap.adjust incrementMemoHitCounter lbl (profile p) }
incrementMemoHitCounter :: ProfileData -> ProfileData
incrementMemoHitCounter pd = pd { profileMemoHits = succ (profileMemoHits pd) }
{-# NOINLINE addProfileFetch #-}
addProfileFetch
:: forall r u a . (DataSourceName r, Eq (r a), Hashable (r a), Typeable (r a))
=> Env u -> r a -> IO ()
addProfileFetch env _req = do
c <- getAllocationCounter
modifyIORef' (profRef env) $ \ p ->
let
dsName :: Text
dsName = dataSourceName (Proxy :: Proxy r)
upd :: ProfileData -> ProfileData
upd d = d { profileFetches =
HashMap.insertWith (+) dsName 1 (profileFetches d) }
in p { profile = HashMap.adjust upd (profLabel env) (profile p) }
-- So we do not count the allocation overhead of addProfileFetch
setAllocationCounter c