first commit

This commit is contained in:
2026-05-29 08:20:56 +01:00
commit 949299f58d
6368 changed files with 527176 additions and 0 deletions

View File

@@ -0,0 +1,114 @@
# `otel.ts` — OpenTelemetry surface for `audio-ir-loader`
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
This module owns the loader's OpenTelemetry instrumentation. It lazily
builds a single, cached `AudioIrLoaderInstruments` bag bound to either the
global `MeterProvider` or a per-process override installed via
`setAudioIrLoaderMeterProvider`. The bag carries every counter and
histogram the spec requires (`bytes_read_total`, `frames_out_total`,
`cache_hits_total`, `cache_misses_total`, `errors_total`, plus the
`load_duration_ms` histogram).
The split between this module and the loader/cache modules is deliberate:
all metric definitions live here so dashboards can grep for instrument
names in one place, and the loader/cache modules call recorder helpers
(`recordSuccess`, `recordCacheHit`, `recordCacheMiss`, `recordError`)
that close over the cached bag.
## Features
- Real `MeterProvider` override for tests — no mocks; tests install an
in-memory recorder via `setAudioIrLoaderMeterProvider`.
- Histogram + five counters covering success / cache-hit / cache-miss /
error outcomes.
- Span-attribute helper `uriKindOf(uri)` resolves the `audio_ir.uri_kind`
dimension from a URI's scheme without re-parsing the whole URI.
## Usage: Basic
```ts
import {
recordSuccess,
setAudioIrLoaderMeterProvider,
} from "@suaveplan/audio-ir-loader";
import { metrics } from "@opentelemetry/api";
// In an app bootstrap:
setAudioIrLoaderMeterProvider(metrics.getMeterProvider());
recordSuccess("room", 100000, 25000, 12.5);
```
## Usage: Advanced
Tests install a real in-memory provider that records every observation:
```ts
import {
getInstruments,
setAudioIrLoaderMeterProvider,
} from "@suaveplan/audio-ir-loader";
import type { MeterProvider } from "@opentelemetry/api";
const observed: { name: string; value: number; attrs: Record<string, unknown> }[] = [];
const provider = {
getMeter: () => ({
createCounter: (name: string) => ({
add: (v: number, a?: Record<string, unknown>) =>
observed.push({ name, value: v, attrs: a ?? {} }),
}),
createHistogram: (name: string) => ({
record: (v: number, a?: Record<string, unknown>) =>
observed.push({ name, value: v, attrs: a ?? {} }),
}),
}),
} as unknown as MeterProvider;
setAudioIrLoaderMeterProvider(provider);
getInstruments(true);
```
## API Reference
### `setAudioIrLoaderMeterProvider(provider | undefined): void`
Install a meter provider override. Pass `undefined` to revert.
### `getInstruments(force?: boolean): AudioIrLoaderInstruments`
Lazily builds (and caches) the instrument bag.
### `resetInstruments(): void`
Drops the cache — used between test provider swaps.
### `recordSuccess(family, bytes, frames, durationMs): void`
Increments `bytes_read_total{family}` and `frames_out_total{family}`, then
records `load_duration_ms{family, outcome:"success"}`.
### `recordCacheHit(family, durationMs): void` / `recordCacheMiss(family): void`
Counter + duration bookkeeping for cache outcomes.
### `recordError(code, family, durationMs): void`
Increments `errors_total{code}` and records the duration with
`outcome:"error"`.
### `uriKindOf(uri): "bundle" | "fallback" | "https" | "drive" | "other"`
Maps a URI's leading scheme to the span-attribute dimension.
## Implementation Notes
The cached instrument bag is rebuilt every time `setAudioIrLoaderMeterProvider`
swaps the provider — otherwise tests would observe stale instruments
bound to the previous provider's meter. The `force` parameter on
`getInstruments` exists exclusively to let tests immediately rebind
after `setAudioIrLoaderMeterProvider` to avoid a wasted check on the
next call. `uriKindOf` deliberately treats `http` and `https` identically
because callers care about the on-the-wire transport, not the TLS bit.