Rewrite `Tracing` to allow for only one `TraceT` in the entire stack.
This PR is on top of #7789.
### Description
This PR entirely rewrites the API of the Tracing library, to make `interpTraceT` a thing of the past. Before this change, we ran traces by sticking a `TraceT` on top of whatever we were doing. This had several major drawbacks:
- we were carrying a bunch of `TraceT` across the codebase, and the entire codebase had to know about it
- we needed to carry a second class constraint around (`HasReporterM`) to be able to run all of those traces
- we kept having to do stack rewriting with `interpTraceT`, which went from inconvenient to horrible
- we had to declare several behavioral instances on `TraceT m`
This PR rewrite all of `Tracing` using a more conventional model: there is ONE `TraceT` at the bottom of the stack, and there is an associated class constraint `MonadTrace`: any part of the code that happens to satisfy `MonadTrace` is able to create new traces. We NEVER have to do stack rewriting, `interpTraceT` is gone, and `TraceT` and `Reporter` become implementation details that 99% of the code is blissfully unaware of: code that needs to do tracing only needs to declare that the monad in which it operates implements `MonadTrace`.
In doing so, this PR revealed **several bugs in the codebase**: places where we were expecting to trace something, but due to the default instance of `HasReporterM IO` we would actually not do anything. This PR also splits the code of `Tracing` in more byte-sized modules, with the goal of potentially moving to `server/lib` down the line.
### Remaining work
This PR is a draft; what's left to do is:
- [x] make Pro compile; i haven't updated `HasuraPro/Main` yet
- [x] document Tracing by writing a note that explains how to use the library, and the meaning of "reporter", "trace" and "span", as well as the pitfalls
- [x] discuss some of the trade-offs in the implementation, which is why i'm opening this PR already despite it not fully building yet
- [x] it depends on #7789 being merged first
PR-URL: https://github.com/hasura/graphql-engine-mono/pull/7791
GitOrigin-RevId: cadd32d039134c93ddbf364599a2f4dd988adea8
2023-03-13 20:37:16 +03:00
|
|
|
{-# LANGUAGE UndecidableInstances #-}
|
|
|
|
|
|
|
|
module Hasura.Tracing.Monad
|
|
|
|
( TraceT (..),
|
|
|
|
runTraceT,
|
|
|
|
ignoreTraceT,
|
|
|
|
)
|
|
|
|
where
|
|
|
|
|
|
|
|
import Control.Lens
|
|
|
|
import Control.Monad.Catch (MonadCatch, MonadMask, MonadThrow)
|
|
|
|
import Control.Monad.Morph
|
|
|
|
import Control.Monad.Trans.Control
|
|
|
|
import Data.IORef
|
|
|
|
import Hasura.Prelude
|
2023-04-25 18:55:33 +03:00
|
|
|
import Hasura.RQL.Types.Session (UserInfoM (..))
|
2023-05-11 08:19:42 +03:00
|
|
|
import Hasura.Server.Types (MonadGetPolicies (..))
|
Rewrite `Tracing` to allow for only one `TraceT` in the entire stack.
This PR is on top of #7789.
### Description
This PR entirely rewrites the API of the Tracing library, to make `interpTraceT` a thing of the past. Before this change, we ran traces by sticking a `TraceT` on top of whatever we were doing. This had several major drawbacks:
- we were carrying a bunch of `TraceT` across the codebase, and the entire codebase had to know about it
- we needed to carry a second class constraint around (`HasReporterM`) to be able to run all of those traces
- we kept having to do stack rewriting with `interpTraceT`, which went from inconvenient to horrible
- we had to declare several behavioral instances on `TraceT m`
This PR rewrite all of `Tracing` using a more conventional model: there is ONE `TraceT` at the bottom of the stack, and there is an associated class constraint `MonadTrace`: any part of the code that happens to satisfy `MonadTrace` is able to create new traces. We NEVER have to do stack rewriting, `interpTraceT` is gone, and `TraceT` and `Reporter` become implementation details that 99% of the code is blissfully unaware of: code that needs to do tracing only needs to declare that the monad in which it operates implements `MonadTrace`.
In doing so, this PR revealed **several bugs in the codebase**: places where we were expecting to trace something, but due to the default instance of `HasReporterM IO` we would actually not do anything. This PR also splits the code of `Tracing` in more byte-sized modules, with the goal of potentially moving to `server/lib` down the line.
### Remaining work
This PR is a draft; what's left to do is:
- [x] make Pro compile; i haven't updated `HasuraPro/Main` yet
- [x] document Tracing by writing a note that explains how to use the library, and the meaning of "reporter", "trace" and "span", as well as the pitfalls
- [x] discuss some of the trade-offs in the implementation, which is why i'm opening this PR already despite it not fully building yet
- [x] it depends on #7789 being merged first
PR-URL: https://github.com/hasura/graphql-engine-mono/pull/7791
GitOrigin-RevId: cadd32d039134c93ddbf364599a2f4dd988adea8
2023-03-13 20:37:16 +03:00
|
|
|
import Hasura.Tracing.Class
|
|
|
|
import Hasura.Tracing.Context
|
|
|
|
import Hasura.Tracing.Reporter
|
|
|
|
import Hasura.Tracing.Sampling
|
|
|
|
|
|
|
|
--------------------------------------------------------------------------------
|
|
|
|
-- TraceT
|
|
|
|
|
|
|
|
-- | TraceT is the standard implementation of 'MonadTrace'. Via a 'Reader', it
|
|
|
|
-- keeps track of the default policy and reporter to use thoughout the stack, as
|
|
|
|
-- well as the current trace.
|
|
|
|
newtype TraceT m a = TraceT (ReaderT (Reporter, Maybe TraceEnv) m a)
|
|
|
|
deriving
|
|
|
|
( Functor,
|
|
|
|
Applicative,
|
|
|
|
Monad,
|
|
|
|
MonadIO,
|
|
|
|
MonadFix,
|
|
|
|
MonadMask,
|
|
|
|
MonadCatch,
|
|
|
|
MonadThrow,
|
|
|
|
MonadState s,
|
|
|
|
MonadError e,
|
|
|
|
MonadBase b,
|
|
|
|
MonadBaseControl b
|
|
|
|
)
|
|
|
|
|
|
|
|
-- | Runs the 'TraceT' monad, by providing the default reporter. This does NOT
|
|
|
|
-- start a trace.
|
|
|
|
--
|
|
|
|
-- TODO: we could change this to always start a trace with a default name? This
|
|
|
|
-- would allow us to guarantee that there is always a current trace, but this
|
|
|
|
-- might not always be the correct behaviour: in practice, we would end up
|
|
|
|
-- generating one that spans the entire lifetime of the engine if 'runTraceT'
|
|
|
|
-- were to be used from 'main'.
|
|
|
|
runTraceT :: Reporter -> TraceT m a -> m a
|
|
|
|
runTraceT reporter (TraceT m) = runReaderT m (reporter, Nothing)
|
|
|
|
|
|
|
|
-- | Run the 'TraceT' monad, but without actually tracing anything: no report
|
|
|
|
-- will be emitted, even if calls to 'newTraceWith' force the trace to be
|
|
|
|
-- sampled.
|
|
|
|
ignoreTraceT :: TraceT m a -> m a
|
|
|
|
ignoreTraceT = runTraceT noReporter
|
|
|
|
|
|
|
|
instance MonadTrans TraceT where
|
|
|
|
lift = TraceT . lift
|
|
|
|
|
|
|
|
-- | Hides the fact that TraceT is a reader to the rest of the stack.
|
2023-05-24 16:51:56 +03:00
|
|
|
instance (MonadReader r m) => MonadReader r (TraceT m) where
|
Rewrite `Tracing` to allow for only one `TraceT` in the entire stack.
This PR is on top of #7789.
### Description
This PR entirely rewrites the API of the Tracing library, to make `interpTraceT` a thing of the past. Before this change, we ran traces by sticking a `TraceT` on top of whatever we were doing. This had several major drawbacks:
- we were carrying a bunch of `TraceT` across the codebase, and the entire codebase had to know about it
- we needed to carry a second class constraint around (`HasReporterM`) to be able to run all of those traces
- we kept having to do stack rewriting with `interpTraceT`, which went from inconvenient to horrible
- we had to declare several behavioral instances on `TraceT m`
This PR rewrite all of `Tracing` using a more conventional model: there is ONE `TraceT` at the bottom of the stack, and there is an associated class constraint `MonadTrace`: any part of the code that happens to satisfy `MonadTrace` is able to create new traces. We NEVER have to do stack rewriting, `interpTraceT` is gone, and `TraceT` and `Reporter` become implementation details that 99% of the code is blissfully unaware of: code that needs to do tracing only needs to declare that the monad in which it operates implements `MonadTrace`.
In doing so, this PR revealed **several bugs in the codebase**: places where we were expecting to trace something, but due to the default instance of `HasReporterM IO` we would actually not do anything. This PR also splits the code of `Tracing` in more byte-sized modules, with the goal of potentially moving to `server/lib` down the line.
### Remaining work
This PR is a draft; what's left to do is:
- [x] make Pro compile; i haven't updated `HasuraPro/Main` yet
- [x] document Tracing by writing a note that explains how to use the library, and the meaning of "reporter", "trace" and "span", as well as the pitfalls
- [x] discuss some of the trade-offs in the implementation, which is why i'm opening this PR already despite it not fully building yet
- [x] it depends on #7789 being merged first
PR-URL: https://github.com/hasura/graphql-engine-mono/pull/7791
GitOrigin-RevId: cadd32d039134c93ddbf364599a2f4dd988adea8
2023-03-13 20:37:16 +03:00
|
|
|
ask = lift ask
|
|
|
|
local f (TraceT m) = TraceT $ mapReaderT (local f) m
|
|
|
|
|
|
|
|
instance (MonadIO m, MonadBaseControl IO m) => MonadTrace (TraceT m) where
|
|
|
|
newTraceWith context policy name (TraceT body) = TraceT do
|
|
|
|
reporter <- asks fst
|
|
|
|
samplingDecision <- decideSampling (tcSamplingState context) policy
|
|
|
|
metadataRef <- liftIO $ newIORef []
|
|
|
|
let report = case samplingDecision of
|
|
|
|
SampleNever -> id
|
|
|
|
SampleAlways -> runReporter reporter context name (readIORef metadataRef)
|
|
|
|
updatedContext =
|
|
|
|
context
|
|
|
|
{ tcSamplingState = updateSamplingState samplingDecision (tcSamplingState context)
|
|
|
|
}
|
|
|
|
traceEnv = TraceEnv updatedContext metadataRef samplingDecision
|
|
|
|
report $ local (_2 .~ Just traceEnv) body
|
|
|
|
|
|
|
|
newSpanWith spanId name (TraceT body) = TraceT do
|
|
|
|
(reporter, traceEnv) <- ask
|
|
|
|
case traceEnv of
|
|
|
|
-- we are not currently in a trace: ignore this span
|
|
|
|
Nothing -> body
|
|
|
|
Just env -> case teSamplingDecision env of
|
|
|
|
-- this trace is not sampled: ignore this span
|
|
|
|
SampleNever -> body
|
|
|
|
SampleAlways -> do
|
|
|
|
metadataRef <- liftIO $ newIORef []
|
|
|
|
let subContext =
|
|
|
|
(teTraceContext env)
|
|
|
|
{ tcCurrentSpan = spanId,
|
|
|
|
tcCurrentParent = Just (tcCurrentSpan $ teTraceContext env)
|
|
|
|
}
|
|
|
|
subTraceEnv =
|
|
|
|
env
|
|
|
|
{ teTraceContext = subContext,
|
|
|
|
teMetadataRef = metadataRef
|
|
|
|
}
|
2023-05-24 16:51:56 +03:00
|
|
|
runReporter reporter subContext name (readIORef metadataRef)
|
|
|
|
$ local (_2 .~ Just subTraceEnv) body
|
Rewrite `Tracing` to allow for only one `TraceT` in the entire stack.
This PR is on top of #7789.
### Description
This PR entirely rewrites the API of the Tracing library, to make `interpTraceT` a thing of the past. Before this change, we ran traces by sticking a `TraceT` on top of whatever we were doing. This had several major drawbacks:
- we were carrying a bunch of `TraceT` across the codebase, and the entire codebase had to know about it
- we needed to carry a second class constraint around (`HasReporterM`) to be able to run all of those traces
- we kept having to do stack rewriting with `interpTraceT`, which went from inconvenient to horrible
- we had to declare several behavioral instances on `TraceT m`
This PR rewrite all of `Tracing` using a more conventional model: there is ONE `TraceT` at the bottom of the stack, and there is an associated class constraint `MonadTrace`: any part of the code that happens to satisfy `MonadTrace` is able to create new traces. We NEVER have to do stack rewriting, `interpTraceT` is gone, and `TraceT` and `Reporter` become implementation details that 99% of the code is blissfully unaware of: code that needs to do tracing only needs to declare that the monad in which it operates implements `MonadTrace`.
In doing so, this PR revealed **several bugs in the codebase**: places where we were expecting to trace something, but due to the default instance of `HasReporterM IO` we would actually not do anything. This PR also splits the code of `Tracing` in more byte-sized modules, with the goal of potentially moving to `server/lib` down the line.
### Remaining work
This PR is a draft; what's left to do is:
- [x] make Pro compile; i haven't updated `HasuraPro/Main` yet
- [x] document Tracing by writing a note that explains how to use the library, and the meaning of "reporter", "trace" and "span", as well as the pitfalls
- [x] discuss some of the trade-offs in the implementation, which is why i'm opening this PR already despite it not fully building yet
- [x] it depends on #7789 being merged first
PR-URL: https://github.com/hasura/graphql-engine-mono/pull/7791
GitOrigin-RevId: cadd32d039134c93ddbf364599a2f4dd988adea8
2023-03-13 20:37:16 +03:00
|
|
|
|
|
|
|
currentContext = TraceT $ asks $ fmap teTraceContext . snd
|
|
|
|
|
|
|
|
attachMetadata metadata = TraceT do
|
|
|
|
asks (fmap teMetadataRef . snd) >>= \case
|
|
|
|
Nothing -> pure ()
|
|
|
|
Just ref -> liftIO $ modifyIORef' ref (metadata ++)
|
|
|
|
|
2023-04-25 18:55:33 +03:00
|
|
|
instance (UserInfoM m) => UserInfoM (TraceT m) where
|
|
|
|
askUserInfo = lift askUserInfo
|
|
|
|
|
2023-05-11 08:19:42 +03:00
|
|
|
instance (MonadGetPolicies m) => MonadGetPolicies (TraceT m) where
|
|
|
|
runGetApiTimeLimit = lift runGetApiTimeLimit
|
|
|
|
runGetPrometheusMetricsGranularity = lift runGetPrometheusMetricsGranularity
|
|
|
|
|
Rewrite `Tracing` to allow for only one `TraceT` in the entire stack.
This PR is on top of #7789.
### Description
This PR entirely rewrites the API of the Tracing library, to make `interpTraceT` a thing of the past. Before this change, we ran traces by sticking a `TraceT` on top of whatever we were doing. This had several major drawbacks:
- we were carrying a bunch of `TraceT` across the codebase, and the entire codebase had to know about it
- we needed to carry a second class constraint around (`HasReporterM`) to be able to run all of those traces
- we kept having to do stack rewriting with `interpTraceT`, which went from inconvenient to horrible
- we had to declare several behavioral instances on `TraceT m`
This PR rewrite all of `Tracing` using a more conventional model: there is ONE `TraceT` at the bottom of the stack, and there is an associated class constraint `MonadTrace`: any part of the code that happens to satisfy `MonadTrace` is able to create new traces. We NEVER have to do stack rewriting, `interpTraceT` is gone, and `TraceT` and `Reporter` become implementation details that 99% of the code is blissfully unaware of: code that needs to do tracing only needs to declare that the monad in which it operates implements `MonadTrace`.
In doing so, this PR revealed **several bugs in the codebase**: places where we were expecting to trace something, but due to the default instance of `HasReporterM IO` we would actually not do anything. This PR also splits the code of `Tracing` in more byte-sized modules, with the goal of potentially moving to `server/lib` down the line.
### Remaining work
This PR is a draft; what's left to do is:
- [x] make Pro compile; i haven't updated `HasuraPro/Main` yet
- [x] document Tracing by writing a note that explains how to use the library, and the meaning of "reporter", "trace" and "span", as well as the pitfalls
- [x] discuss some of the trade-offs in the implementation, which is why i'm opening this PR already despite it not fully building yet
- [x] it depends on #7789 being merged first
PR-URL: https://github.com/hasura/graphql-engine-mono/pull/7791
GitOrigin-RevId: cadd32d039134c93ddbf364599a2f4dd988adea8
2023-03-13 20:37:16 +03:00
|
|
|
--------------------------------------------------------------------------------
|
|
|
|
-- Internal
|
|
|
|
|
|
|
|
-- | Information about the current trace and span.
|
|
|
|
data TraceEnv = TraceEnv
|
|
|
|
{ teTraceContext :: TraceContext,
|
|
|
|
teMetadataRef :: IORef TraceMetadata,
|
|
|
|
teSamplingDecision :: SamplingDecision
|
|
|
|
}
|
|
|
|
|
|
|
|
-- Helper for consistently deciding whether or not to sample a trace based on
|
|
|
|
-- trace context and sampling policy.
|
2023-05-24 16:51:56 +03:00
|
|
|
decideSampling :: (MonadIO m) => SamplingState -> SamplingPolicy -> m SamplingDecision
|
Rewrite `Tracing` to allow for only one `TraceT` in the entire stack.
This PR is on top of #7789.
### Description
This PR entirely rewrites the API of the Tracing library, to make `interpTraceT` a thing of the past. Before this change, we ran traces by sticking a `TraceT` on top of whatever we were doing. This had several major drawbacks:
- we were carrying a bunch of `TraceT` across the codebase, and the entire codebase had to know about it
- we needed to carry a second class constraint around (`HasReporterM`) to be able to run all of those traces
- we kept having to do stack rewriting with `interpTraceT`, which went from inconvenient to horrible
- we had to declare several behavioral instances on `TraceT m`
This PR rewrite all of `Tracing` using a more conventional model: there is ONE `TraceT` at the bottom of the stack, and there is an associated class constraint `MonadTrace`: any part of the code that happens to satisfy `MonadTrace` is able to create new traces. We NEVER have to do stack rewriting, `interpTraceT` is gone, and `TraceT` and `Reporter` become implementation details that 99% of the code is blissfully unaware of: code that needs to do tracing only needs to declare that the monad in which it operates implements `MonadTrace`.
In doing so, this PR revealed **several bugs in the codebase**: places where we were expecting to trace something, but due to the default instance of `HasReporterM IO` we would actually not do anything. This PR also splits the code of `Tracing` in more byte-sized modules, with the goal of potentially moving to `server/lib` down the line.
### Remaining work
This PR is a draft; what's left to do is:
- [x] make Pro compile; i haven't updated `HasuraPro/Main` yet
- [x] document Tracing by writing a note that explains how to use the library, and the meaning of "reporter", "trace" and "span", as well as the pitfalls
- [x] discuss some of the trade-offs in the implementation, which is why i'm opening this PR already despite it not fully building yet
- [x] it depends on #7789 being merged first
PR-URL: https://github.com/hasura/graphql-engine-mono/pull/7791
GitOrigin-RevId: cadd32d039134c93ddbf364599a2f4dd988adea8
2023-03-13 20:37:16 +03:00
|
|
|
decideSampling samplingState samplingPolicy =
|
|
|
|
case samplingState of
|
|
|
|
SamplingDefer -> liftIO samplingPolicy
|
|
|
|
SamplingDeny -> pure SampleNever
|
|
|
|
SamplingAccept -> pure SampleAlways
|
|
|
|
|
|
|
|
-- Helper for consistently updating the sampling state when a sampling decision
|
|
|
|
-- is made.
|
|
|
|
updateSamplingState :: SamplingDecision -> SamplingState -> SamplingState
|
|
|
|
updateSamplingState samplingDecision = \case
|
|
|
|
SamplingDefer ->
|
|
|
|
case samplingDecision of
|
|
|
|
SampleNever -> SamplingDefer
|
|
|
|
SampleAlways -> SamplingAccept
|
|
|
|
SamplingDeny -> SamplingDeny
|
|
|
|
SamplingAccept -> SamplingAccept
|