Initial Commit
This commit is contained in:
@@ -0,0 +1,56 @@
|
||||
# `otel` — OpenTelemetry instrumentation for OPFS storage
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## Purpose
|
||||
|
||||
Centralises every metric, histogram, and span emitted by `OpfsStorage` behind a single lazy instrument factory. Lazy construction lets consumers install a custom global `MeterProvider` / `TracerProvider` before the storage path executes, and lets tests pin a real in-memory `MeterProvider` against which the recorded attribute set is asserted. The module exposes a small `recordOp({op, outcome, durationMs, bytes?, direction?})` helper that every operation in the storage class calls once.
|
||||
|
||||
## Features
|
||||
|
||||
- Six instruments: 4 counters (`reads_total`, `writes_total`, `deletes_total`, `errors_total`) and 2 histograms (`op_duration_ms`, `io_bytes`).
|
||||
- Stable label space: `{op, outcome}` on every counter / duration, `{op, direction}` on `io_bytes`.
|
||||
- Aborted operations count toward `errors_total` with `outcome: "aborted"` so SLO dashboards can split them out.
|
||||
- `setStorageBrowserOpfsMeterProvider(provider)` for runtime provider injection (used by `@suaveplan/opentelemetry`).
|
||||
- `resetInstruments()` rebuilds the cache between test blocks.
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```ts
|
||||
import { recordOp } from "@suaveplan/storage-browser-opfs/otel";
|
||||
|
||||
recordOp({ op: "set", outcome: "success", durationMs: 12, bytes: 100, direction: "write" });
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
```ts
|
||||
import { metrics } from "@opentelemetry/api";
|
||||
import {
|
||||
getInstruments,
|
||||
setStorageBrowserOpfsMeterProvider,
|
||||
resetInstruments,
|
||||
} from "@suaveplan/storage-browser-opfs/otel";
|
||||
|
||||
// In a Bun/Node test:
|
||||
class CapturingMeterProvider { /* real in-memory MeterProvider */ }
|
||||
const provider = new CapturingMeterProvider();
|
||||
setStorageBrowserOpfsMeterProvider(provider);
|
||||
resetInstruments();
|
||||
const { writesTotal } = getInstruments(true);
|
||||
writesTotal.add(1, { op: "set", outcome: "success" });
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
- `setStorageBrowserOpfsMeterProvider(provider | undefined): void`.
|
||||
- `getInstruments(force?: boolean): StorageBrowserOpfsInstruments` — cached unless `force` is `true`.
|
||||
- `recordOp(input: RecordOpInput): void`.
|
||||
- `resetInstruments(): void`.
|
||||
- Types: `StorageBrowserOpfsOp` (14 op names), `StorageBrowserOpfsOpOutcome` (`success | error | aborted`), `RecordOpInput`.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
The dispatch in `recordOp` is intentionally a flat `if/else` chain rather than a lookup table: TypeScript can statically verify exhaustiveness over `StorageBrowserOpfsOp`, and the cost of a 14-arm chain is negligible compared to the underlying counter add. `aborted` outcomes are routed to `errors_total` (not a separate counter) on purpose — operationally they're failures from the caller's perspective even though they're not exceptions on the server side.
|
||||
Reference in New Issue
Block a user