# telemetry/telemetry _(no submodules)_ ## Purpose OpenTelemetry-shaped instrumentation for `@suaveplan/midi` without taking a runtime dependency on `@opentelemetry/api`. The module exposes structural `CounterLike` / `HistogramLike` interfaces (compatible with the real OTel counter/histogram shapes) and a default in-memory recorder for tests and embedded use. Consumers wire real OTel by passing `meter.createCounter(...)` / `meter.createHistogram(...)` directly. The two instruments the package emits are: `midi_messages_decoded_total{type}` (Counter) and `midi_decode_duration_ns{type}` (Histogram). Both carry the MIDI message type as a single attribute — enough to slice dashboards by note-on rate, SysEx frequency, decode-latency outliers per category, etc. ## Features - Zero runtime dep on `@opentelemetry/api` — structural typing only. - `createInMemoryTelemetry` recorder for tests, with snapshot + reset. - `nowNanos()` cross-runtime high-resolution timestamp. - `recordDecode(telemetry, type, startNs, endNs)` one-call helper that stamps both counter and histogram with the message-type attribute. ## Usage: Basic ```ts import { createInMemoryTelemetry, nowNanos, recordDecode } from "@suaveplan/midi"; const telemetry = createInMemoryTelemetry(); const start = nowNanos(); // ...decode... recordDecode(telemetry, "noteOn", start, nowNanos()); console.log(telemetry.snapshot().counters.noteOn); ``` ## Usage: Advanced ```ts import type { MidiTelemetry } from "@suaveplan/midi"; // Bridge to real OpenTelemetry without a hard dep. function createOtelTelemetry(meter: { createCounter(name: string): { add: (n: number, attrs?: object) => void }; createHistogram(name: string): { record: (n: number, attrs?: object) => void }; }): MidiTelemetry { return { messagesDecoded: meter.createCounter("midi_messages_decoded_total"), decodeDurationNs: meter.createHistogram("midi_decode_duration_ns"), }; } ``` ## API Reference - `CounterLike { add(value, attributes?): void }`. - `HistogramLike { record(value, attributes?): void }`. - `MidiTelemetry { messagesDecoded: CounterLike; decodeDurationNs: HistogramLike }`. - `createInMemoryTelemetry(): InMemoryTelemetry` — extends `MidiTelemetry` with `snapshot()` and `reset()`. - `MidiTelemetrySnapshot { counters: Record; histograms: Record }`. - `nowNanos(): number` — `performance.now() * 1e6` when available; falls back to `Date.now() * 1e6`. - `recordDecode(telemetry, type, startNs, endNs): void`. - `MIDI_TELEMETRY_NAMES` — frozen object with the canonical instrument names. ## Implementation Notes The in-memory recorder uses `Object.create(null)` for the counter / histogram backing objects so accidental `__proto__` keys cannot poison the snapshot. `nowNanos` multiplies milliseconds by `1_000_000` rather than using `process.hrtime.bigint()` because the latter is Node-only — the package is universal and must run identically in browsers. The slight precision loss (sub-microsecond) is acceptable for a coarse decode-duration histogram.