Telemetry & the read plane · dive 02
Zero cost when absent
Make the telemetry dependency optional, and pay nothing when the host has not opted in.
A library that meters itself has a choice: depend on the telemetry library and force
it on every host, or make the dependency optional and pay nothing when the host has not opted in.
EchoMQ.Meter takes the second path, on a single guard. The property is worth stating
plainly: the bus carries no :telemetry dependency edge. A host that wants
metering adds :telemetry itself; a host that does not is never charged for the emitter
calls in the lifecycle. Flip the dependency on the right to see what an emit does in each world.
The guard · one boolean decides
The guard — function_exported(:telemetry, :execute, 3)
Before any emission, the meter asks one question: is :telemetry actually
loaded? It answers with a runtime check —
:erlang.function_exported(:telemetry, :execute, 3) — which is
true only if the module is present and that function is exported. If the dependency was
never added, the check is false, and:
emit/3 does nothing and answers :ok — a
no-op. attach/4 does nothing and answers :ok
— the handler is never registered, and that is harmless, because the event it would have caught never fires
either. So an emit in a hot lifecycle path is, when telemetry is absent, a branch on a boolean and a return: no
event constructed, no handler list walked, no dependency loaded. The cost is a function-export lookup, not a
metering pipeline.
# echo_mq — EchoMQ.Meter # The single guard: is :telemetry actually loaded? Answered at runtime by the # function-export check — true only if the module is present and execute/3 is # exported. If the dependency was never added, this is false, so every emission # is a no-op. The bus carries NO :telemetry dependency edge — a host opts in by # adding :telemetry itself. defp loaded?, do: :erlang.function_exported(:telemetry, :execute, 3) # emit/3: with :telemetry loaded, fire the rooted event; without it, do nothing # and answer :ok. An emit in a hot path is a branch on a boolean, never a # metering pipeline. def emit(event_suffix, measurements, metadata) when is_list(event_suffix) do if loaded?() do apply(:telemetry, :execute, [[@root | event_suffix], measurements, metadata]) end :ok end
The same guard wraps attach/4, attach_many/4, and
span/3. The one careful asymmetry is in span/3: the guard protects
only the events, never the work. With telemetry absent, span/3
runs the wrapped function directly and returns its result — the metering vanishes, the computation does not. A
block you wrapped to measure is never skipped because nobody is listening.
The cost, both worlds
The claim — zero cost when absent — is meant literally, so it is worth pricing both worlds
side by side. The same line of lifecycle code, Meter.job_completed(…), runs in both;
what differs is only whether the host added the dependency. Pick a world to see what that one call does.
One emit call · what it costs
A queue is infrastructure — it sits beneath an application with its own opinions about observability. The opt-in guard makes metering a decision the host makes, not a tax the library levies: the cost of the feature, to a host that does not use it, is zero — and the guard makes that claim literally true.
The name is collision-avoidance, not decoration
There is a second reason the module is called EchoMQ.Meter, and it is load-bearing. A
frozen first-generation reference already defines a telemetry module under the obvious name, and both load
on one code path. A same-named module would shadow the bus's telemetry surface
non-deterministically — whichever loaded last would win, and the answer would depend on load
order. The capability here is that same metering surface re-derived; the name is chosen to be
collision-free so the bus's meter and the frozen reference never contend for one module name. Teaching the
surface, the name to reach for is EchoMQ.Meter — that is the module the bus exposes,
and the one this whole module is about.
This is the same instinct the BCS law expresses at the system boundary: a surface should not collide with, or depend on, a neighbour to hold itself up. The meter avoids both — no telemetry dependency edge (the guard), and no module-name collision (the name) — so it slots into any host cleanly, metered or not.
Pattern & implementation
Instrument the system, but do not make instrumentation mandatory — a library's observability should be the host's choice. Production Operations teaches running the tier.
Every emission guards function_exported(:telemetry, :execute, 3), so with no
:telemetry loaded an emit is a no-op and an attach answers :ok
— no dependency edge — and the module name is chosen collision-free so it never shadows the frozen
reference on the shared code path.
The pattern says: instrument, but do not impose. The implementation imposes nothing — a guard makes the dependency optional, and a chosen name keeps it from colliding.
Recap — optional dependency, chosen name
One runtime check — :erlang.function_exported(:telemetry, :execute, 3) — makes
the telemetry dependency optional: with it absent, every emit is a no-op and every attach answers
:ok, so the bus carries no :telemetry dependency edge and a host
opts in by adding the dep. The guard protects only the events, never the wrapped work. And the module is named
EchoMQ.Meter to stay collision-free against the frozen reference on the shared code path.
The next dive turns from push to pull: the read plane, which has no dependency to guard at all.
Next in the module: The read plane — EchoMQ.Metrics,
the pull side that answers a question by reading the structures, never by changing them.
References
Where the zero-cost guard and the optional-dependency property are grounded.
Sources
- Erlang/OTP — erlang:function_exported/3 — the runtime check the zero-cost guard is built on.
- Elixir — the :telemetry library — the optional dependency a host opts into.
- Erlang/OTP — :telemetry.execute/3 — the execute the guard gates.
Related in this course
- Telemetry & the read plane — the module this dive belongs to.
- The telemetry surface — the events the guard protects.
- The read plane — the pull side, with no dependency to guard.
- The Protocol — the owned wire the connector meters on the same tree.
- The Branded Component System · together — the manuscript chapter where the four libraries are one umbrella.