defmodule Aerospike.Telemetry do @moduledoc """ Event-name constants for the telemetry taxonomy emitted by this driver. All events live under the `[:aerospike, ...]` namespace. Callers attach handlers via `:telemetry.attach/4` (or `:telemetry.attach_many/4`) using the event names returned by the functions in this module. Emitters call `:telemetry.span/3` or emit the concrete event names directly via `:telemetry.execute/3` with those same names. Using the constants from here is the contract. Handler code relies on these exact event names and metadata keys; emitters must not invent new names or forget to route through this module. ## Span versus instant events Functions whose name ends with `_span` return the *prefix* expected by `:telemetry.span/3`. `:telemetry` appends `:start`, `:stop`, or `:exception` itself, producing three event names per span: prefix = Aerospike.Telemetry.pool_checkout_span() :telemetry.span(prefix, start_metadata, fn -> {result, stop_metadata} end) Functions without the `_span` suffix return the full event name for an instant event dispatched via `:telemetry.execute/3`: :telemetry.execute( Aerospike.Telemetry.node_transition(), measurements, metadata ) Operators that want one handler for the whole driver can attach to `handler_events/0`, which expands the current taxonomy into concrete event names suitable for `:telemetry.attach_many/4`. The Telemetry And Runtime Metrics guide provides the operator-facing metadata and measurement contract for each family. ## Taxonomy Span events (prefix + `:start | :stop | :exception`): * `[:aerospike, :pool, :checkout]` — wraps every per-node pool checkout. Metadata: `:node_name`, `:pool_pid`, `:telemetry_span_context`. Measurements: `:system_time` and `:monotonic_time` on start; `:duration` on stop and exception. * `[:aerospike, :command, :send]` — wraps the send side of a per-attempt command on the transport. Metadata: `:node_name`, `:attempt` (0-indexed, matching the retry driver), `:deadline_ms`. Measurements: `:system_time` on start; `:duration` on stop and exception. * `[:aerospike, :command, :recv]` — wraps the receive side of a per-attempt command. Metadata: `:node_name`, `:attempt`, `:deadline_ms`. Measurements: `:duration` on stop and exception, plus `:bytes` metadata on stop. * `[:aerospike, :info, :rpc]` — wraps every info RPC (Tender cycles and transport login/authenticate handshakes). Metadata: `:node_name`, `:commands` (the list of info keys requested, or `[:login]` for auth). Measurements: `:system_time` on start; `:duration` on stop and exception. * `[:aerospike, :tender, :tend_cycle]` — wraps one full tend cycle. Cluster-wide (one pair per cycle). Metadata: none. Measurements: `:system_time` on start; `:duration` on stop and exception. * `[:aerospike, :tender, :partition_map_refresh]` — wraps the partition-map refresh stage of the tend cycle (the most expensive sub-step). Cluster-wide, nested inside a `:tend_cycle` span. Metadata: none. Measurements: `:system_time` on start; `:duration` on stop and exception. Instant events (dispatched via `:telemetry.execute/3`): * `[:aerospike, :node, :transition]` — lifecycle flip for a node. Metadata: `:node_name`, `:from` (`:active | :inactive | :unknown`), `:to` (same enum), `:reason` (`:bootstrap | :peer_discovery | :recovery | :failure_threshold | :dropped`). Measurements: `:system_time`. * `[:aerospike, :retry, :attempt]` — one event per retry attempt beyond the first. Metadata: `:classification` (`:rebalance | :transport | :circuit_open`), `:attempt`, `:node_name`. Measurements: `:remaining_budget_ms`. """ @pool_checkout_span [:aerospike, :pool, :checkout] @command_send_span [:aerospike, :command, :send] @command_recv_span [:aerospike, :command, :recv] @info_rpc_span [:aerospike, :info, :rpc] @tend_cycle_span [:aerospike, :tender, :tend_cycle] @partition_map_refresh_span [:aerospike, :tender, :partition_map_refresh] @node_transition [:aerospike, :node, :transition] @retry_attempt [:aerospike, :retry, :attempt] @span_suffixes [:start, :stop, :exception] @typedoc "Telemetry event name used with `:telemetry.attach/4`." @type event_name :: [atom()] @typedoc """ Telemetry span prefix used with `:telemetry.span/3`. Concrete span events are produced by appending `:start`, `:stop`, or `:exception` to this prefix. """ @type span_prefix :: [atom()] @typedoc "Concrete telemetry span event name after a suffix has been appended." @type span_event_name :: [atom()] @typedoc "Retry classification reported by `emit_retry_attempt/4`." @type retry_classification :: :rebalance | :transport | :circuit_open | atom() @doc """ Event prefix for the pool-checkout span. This prefix names the checkout telemetry family. The current emitter dispatches the concrete `:start`, `:stop`, and `:exception` names directly so it can keep the same metadata on every suffix. """ @spec pool_checkout_span() :: span_prefix() def pool_checkout_span, do: @pool_checkout_span @doc """ Event prefix for the command-send span. Wraps the send side of a single attempt against one node. Retry iterations emit a new span per attempt; `:attempt` in metadata distinguishes them. """ @spec command_send_span() :: span_prefix() def command_send_span, do: @command_send_span @doc """ Event prefix for the command-recv span. Paired with the command-send span. Measurements include `:bytes` on `:stop` so handlers can track payload size alongside latency. """ @spec command_recv_span() :: span_prefix() def command_recv_span, do: @command_recv_span @doc """ Event prefix for the info-RPC span. Covers Tender info calls and transport login/authenticate RPCs. Metadata `:commands` carries the list of info keys, or `[:login]` for auth handshakes. """ @spec info_rpc_span() :: span_prefix() def info_rpc_span, do: @info_rpc_span @doc """ Event prefix for the tend-cycle span. Wraps one tend-cycle invocation. The partition map refresh is a nested span; both fire during a normal cycle. """ @spec tend_cycle_span() :: span_prefix() def tend_cycle_span, do: @tend_cycle_span @doc """ Event prefix for the partition-map-refresh span. Wraps the `maybe_refresh_partition_maps/1` stage specifically so operators can separate fetching partition-generation from decoding the full replicas payload. """ @spec partition_map_refresh_span() :: span_prefix() def partition_map_refresh_span, do: @partition_map_refresh_span @doc """ Event name for a node lifecycle transition. Instant event dispatched via `:telemetry.execute/3`. Metadata carries `:from`, `:to`, `:reason`, and `:node_name`. """ @spec node_transition() :: event_name() def node_transition, do: @node_transition @doc """ Event name for a retry attempt beyond the first. Instant event dispatched via `:telemetry.execute/3`. Metadata carries the retry classification, attempt index, and node name. """ @spec retry_attempt() :: event_name() def retry_attempt, do: @retry_attempt @doc """ Emits one retry-attempt event beyond the first command attempt. Unary and batch executors share this helper so the retry metadata stays aligned across execution paths. Measurements: * `:remaining_budget_ms` — non-negative retry budget remaining after this attempt decision Metadata: * `:classification` — retry reason, usually `:rebalance`, `:transport`, or `:circuit_open` * `:attempt` — zero-based attempt index * `:node_name` — node associated with the retry, or `nil` """ @spec emit_retry_attempt( String.t() | nil, non_neg_integer(), retry_classification(), integer() ) :: :ok def emit_retry_attempt(node_name, attempt, classification, remaining_budget_ms) when is_integer(attempt) and attempt >= 0 and is_integer(remaining_budget_ms) do :telemetry.execute( retry_attempt(), %{remaining_budget_ms: max(remaining_budget_ms, 0)}, %{ classification: classification, attempt: attempt, node_name: node_name } ) end @doc """ Returns every span prefix in the supported telemetry taxonomy. This is the stable list for code that needs to derive concrete `:start`/`:stop`/`:exception` event names without hard-coding them in multiple places. """ @spec span_prefixes() :: [span_prefix()] def span_prefixes do [ pool_checkout_span(), command_send_span(), command_recv_span(), info_rpc_span(), tend_cycle_span(), partition_map_refresh_span() ] end @doc """ Returns every instant-event name in the supported telemetry taxonomy. """ @spec instant_event_names() :: [event_name()] def instant_event_names do [ node_transition(), retry_attempt() ] end @doc """ Returns the concrete event names operators can attach to directly. Span prefixes are expanded to the standard `:start`, `:stop`, and `:exception` event names, then instant events are appended. """ @spec handler_events() :: [span_event_name() | event_name()] def handler_events do Enum.flat_map(span_prefixes(), &span_event_names/1) ++ instant_event_names() end defp span_event_names(prefix) do Enum.map(@span_suffixes, &(prefix ++ [&1])) end end