# `telemetry` _(no submodules)_ ## Purpose The telemetry module owns every OpenTelemetry instrument published by `@suaveplan/react-audio-stream`. Concentrating the OTel surface in one file means the session class never touches `@opentelemetry/api` and tests can verify call counts by spying on a single verb-shaped object instead of low-level meter handles. The shape matches Requirement 7 of the parent spec — two counters, a gauge, two more counters, and a histogram — so the runtime instrument set can be cross-checked against the spec on every change. Every emission is no-op until a host registers an OTel SDK provider via `@suaveplan/opentelemetry`. The module-scope meter handles are cheap (one map lookup) so we can keep them at file scope without inflating cold-start cost. ## Features - Counter `react_audio_stream_peer_connect_total{transport}`. - Counter `react_audio_stream_peer_disconnect_total{reason}`, where `reason` covers every `InterruptionClass` plus `explicit`. - UpDown counter `react_audio_stream_active_sessions` (gauge-equivalent via OTel's synchronous up/down counter). - Counter `react_audio_stream_reconnect_attempt_total`. - Counter `react_audio_stream_reconnect_failed_total`. - Histogram `react_audio_stream_rtt_ms{participant}` for the transport's RTT probe. - Verbs (`peerConnect`, `peerDisconnect`, …) keep call sites tidy and give tests one knob per spec requirement. ## Usage: Basic ```ts import { streamTelemetry } from "@suaveplan/react-audio-stream"; streamTelemetry.peerConnect("webrtc"); streamTelemetry.recordRtt("alice", 42); ``` ## Usage: Advanced ```ts import { streamTelemetry } from "@suaveplan/react-audio-stream"; class ReconnectLadder { async run(verdict: InterruptionVerdict): Promise { for (let attempt = 1; attempt <= this.max; attempt++) { streamTelemetry.reconnectAttempt(); try { await this.session.connect(this.roomId); return; } catch { await this.backoff(attempt); } } streamTelemetry.reconnectFailed(); } } ``` ## API Reference - `interface StreamTelemetry` — verb surface consumed by the session. - `const streamTelemetry: StreamTelemetry` — singleton implementation. - `type DisconnectReason` — `"network" | "media-permission" | "signalling" | "peer" | "unknown" | "explicit"`. - `type TransportLabel` — `"webrtc" | "websocket" | "trystero" | "local"`. ## Implementation Notes OTel's `Gauge` requires async observable callbacks which complicates the testable verb surface; the spec wording reads as "gauge tracks active sessions" and OTel's `UpDownCounter` is the synchronous equivalent that satisfies the same semantics — every monitoring backend renders it as a gauge. The reason labels are constrained to the `DisconnectReason` union so a typo in the call site is a compile error rather than a silent cardinality leak. Transport labels are constrained the same way — adding a new transport in chunk B requires extending the union (one line) so backend dashboards stay in sync with the code automatically. The participant id is used as a histogram attribute even though it is high-cardinality because RTT samples are per-peer by nature; consumers wiring a low-cardinality backend (Prometheus) should drop the attribute via a view in their SDK configuration rather than us removing it here.