3.1 KiB
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. createInMemoryTelemetryrecorder 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
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
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— extendsMidiTelemetrywithsnapshot()andreset().MidiTelemetrySnapshot { counters: Record<string, number>; histograms: Record<string, number[]> }.nowNanos(): number—performance.now() * 1e6when available; falls back toDate.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.