# `observability/metrics` — OTel histograms and counters _(no submodules)_ ## Purpose The `observability/metrics` module owns the OpenTelemetry surface of `@suaveplan/tpm-struct`. Every public parser invocation records into one of three histograms and one of three counters; the histograms bucket wall-time by `outcome` (and by `type` / `name_alg` where it adds value), and the counters expose hit / error rates with low-cardinality attributes drawn from the bounded `TPM_ALG`, `TPM_STRUCT_CODES`, and `PublicBranch` enums. The full surface is what makes a running TPM- attestation gateway observable: a sudden spike in `tpm_struct.parse_errors_total{code="TPM_STRUCT.MAGIC_INVALID"}` is the canonical signal that an upstream is sending corrupted or non-TPM blobs. ## Features - Three histograms: `parse_tpms_attest_duration_ms{outcome}`, `parse_tpmt_public_duration_ms{type,outcome}`, `compute_name_duration_ms{name_alg}`. - Three counters: `parse_total{kind,outcome}`, `parse_errors_total{code}`, `alg_total{alg}`. - Every attribute value is a bounded enum (no free-form strings). - Lazy instrument resolution — instruments are created on first call so consumers can install a custom `MeterProvider` before any module-load side effects. - `resetTpmStructMetricsForTesting()` clears the instrument cache for tests that swap the global provider between cases. ## Usage: Basic Example ```ts import { metrics } from "@opentelemetry/api"; import { parseTpmsAttest } from "@suaveplan/tpm-struct"; // Once at app boot, install your MeterProvider. metrics.setGlobalMeterProvider(myProvider); // Subsequent calls emit metrics automatically. parseTpmsAttest(attestBytes); ``` ## Usage: Advanced Example ```ts import { metrics, type Counter, type Histogram, type Meter, type MeterProvider, } from "@opentelemetry/api"; import { parseTpmsAttest, resetTpmStructMetricsForTesting, } from "@suaveplan/tpm-struct"; // In-memory provider for assertions in tests. function recordingProvider() { const counters: { name: string; value: number; attrs?: Record }[] = []; const histograms: typeof counters = []; const meter: Partial = { createCounter: (name: string) => ({ add: (v: number, a?: Record) => counters.push({ name, value: v, attrs: a }), }) as unknown as Counter, createHistogram: (name: string) => ({ record: (v: number, a?: Record) => histograms.push({ name, value: v, attrs: a }), }) as unknown as Histogram, }; const provider: Partial = { getMeter: () => meter as Meter, }; return { provider: provider as MeterProvider, counters, histograms }; } const { provider, counters, histograms } = recordingProvider(); metrics.setGlobalMeterProvider(provider); resetTpmStructMetricsForTesting(); parseTpmsAttest(certifyBytes); console.log(counters, histograms); ``` ## API Reference - **`recordAttestDuration(outcome, durationMs)`** — record a `parse_tpms_attest_duration_ms` observation. - **`recordPublicDuration(type, outcome, durationMs)`** — record a `parse_tpmt_public_duration_ms` observation. - **`recordNameDuration(nameAlg, durationMs)`** — record a `compute_name_duration_ms` observation. - **`recordParseDuration(kind, branchOrAlg, outcome, durationMs)`** — dispatch helper used by every parser. - **`recordParseTotal(kind, outcome)`** — increment `parse_total`. - **`recordParseError(err)`** — increment `parse_errors_total{code}` using the registered code when `err` is a `TpmStructError`, or `"TPM_STRUCT.OTHER"` otherwise (sentinel label, not a registry member). - **`recordAlgObserved(alg)`** — increment `alg_total{alg}`. - **`resetTpmStructMetricsForTesting()`** — clear the cached instruments. ## Implementation Notes Instruments are cached at module scope so the per-call cost stays a single `Map.get`. The `OTHER` sentinel on `parse_errors_total` is a defensive label — the parsers only ever throw `TpmStructError`, but the helper accepts `unknown` because library consumers may forward arbitrary failures through a wrapping function.