4.2 KiB
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
MeterProviderbefore any module-load side effects. resetTpmStructMetricsForTesting()clears the instrument cache for tests that swap the global provider between cases.
Usage: Basic Example
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
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<string, string> }[] = [];
const histograms: typeof counters = [];
const meter: Partial<Meter> = {
createCounter: (name: string) =>
({
add: (v: number, a?: Record<string, string>) =>
counters.push({ name, value: v, attrs: a }),
}) as unknown as Counter,
createHistogram: (name: string) =>
({
record: (v: number, a?: Record<string, string>) =>
histograms.push({ name, value: v, attrs: a }),
}) as unknown as Histogram,
};
const provider: Partial<MeterProvider> = {
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 aparse_tpms_attest_duration_msobservation.recordPublicDuration(type, outcome, durationMs)— record aparse_tpmt_public_duration_msobservation.recordNameDuration(nameAlg, durationMs)— record acompute_name_duration_msobservation.recordParseDuration(kind, branchOrAlg, outcome, durationMs)— dispatch helper used by every parser.recordParseTotal(kind, outcome)— incrementparse_total.recordParseError(err)— incrementparse_errors_total{code}using the registered code whenerris aTpmStructError, or"TPM_STRUCT.OTHER"otherwise (sentinel label, not a registry member).recordAlgObserved(alg)— incrementalg_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.