Per-request accumulator for the real per-phase durations reported in the
Server-Timing response header (Bier.Plugs.Observability).
PostgREST measures each request phase — JWT verification, query-string parse,
SQL planning, the database transaction, and response rendering — and reports
them as named Server-Timing metrics. Bier mirrors that by timing each phase
where the work actually happens (measure/2 wraps the real call site) and
accumulating the durations here, keyed to the current request.
Why the process dictionary
A request is handled start to finish — router pipeline, controller, query
executor, and the Plug.Conn.register_before_send/2 callback that writes the
header — in a single Bandit process, and the phases are spread across modules
at different call depths. Threading a Plug.Conn accumulator through every one
would be invasive and, worse, lossy: error paths discard the inner conn (e.g.
Bier.Plugs.ActionController hands the original conn to the fallback
controller), so a JWT or parse timing measured on a request that then fails
would never reach the header. Process-scoped state survives those hand-offs and
is naturally request-scoped, since each request runs in its own process.
reset/1 is called once at the top of the pipeline so a connection reused
across keep-alive requests never carries stale phases. Timing is collected only
when server-timing-enabled is set for the instance; otherwise measure/2
runs its function with no instrumentation and no process-dictionary writes.
Summary
Functions
The raw accumulator state, for handing off to another process.
Time fun, accumulate its elapsed wall-clock under phase, and return its
result unchanged.
Initialise (or clear) the accumulator for the current request.
The phases accumulated so far for the current request, as a map of
phase => milliseconds (float). Empty when timing is disabled or nothing was
recorded — Bier.Plugs.Observability reports a phase absent from this map as
0.0 (truthful: no time was spent there) rather than a fabricated value.
Functions
@spec export() :: term()
The raw accumulator state, for handing off to another process.
Bier.Cancellation runs the request's database work in a task; the task
adopts the request's state via restore/1, and the phases it measures are
handed back the same way. Opaque — only meaningful to restore/1.
@spec measure(atom(), (-> result)) :: result when result: var
Time fun, accumulate its elapsed wall-clock under phase, and return its
result unchanged.
A no-op wrapper (just fun.(), no timing) when server-timing is disabled for
this request, or when called outside an initialised request. Repeated measures
of the same phase accumulate, so a phase that issues several round-trips
(e.g. a read plus an exact-count query) reports their sum.
@spec reset(boolean()) :: :ok
Initialise (or clear) the accumulator for the current request.
enabled? mirrors the instance's server-timing-enabled: when false the
accumulator is put into a disabled state so measure/2 skips instrumentation
entirely. Called once per request by Bier.Plugs.Observability.
@spec restore(term()) :: :ok
Adopt an accumulator state export/0-ed from another process. A nil
state (the source was outside an initialised request) is a no-op, keeping
measure/2 uninstrumented.
The phases accumulated so far for the current request, as a map of
phase => milliseconds (float). Empty when timing is disabled or nothing was
recorded — Bier.Plugs.Observability reports a phase absent from this map as
0.0 (truthful: no time was spent there) rather than a fabricated value.