Initial Commit
This commit is contained in:
@@ -0,0 +1,69 @@
|
||||
# `observability`
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
## Purpose
|
||||
|
||||
OTel meter wiring for the whole `@suaveplan/r3f-networking` package. Exposes
|
||||
two histograms, two counters, and a small recorder facade. Every
|
||||
externally-visible operation that affects multiplayer state is instrumented
|
||||
here so dashboards over the meter `@suaveplan/r3f-networking` can be
|
||||
assembled without reading code.
|
||||
|
||||
## Features
|
||||
|
||||
- Histogram `r3f_net.snapshot_dispatch_ms` (unit `ms`) — time spent dispatching
|
||||
one authoritative server snapshot through the local sync pipeline.
|
||||
- Histogram `r3f_net.prediction_divergence` (unit `m`) — magnitude of the last
|
||||
reconciliation error.
|
||||
- Counter `r3f_net.snapshots_total{outcome}` — snapshot frames received, labelled
|
||||
with `accepted | out_of_order | rejected`.
|
||||
- Counter `r3f_net.peer_events_total{event}` — peer lifecycle events, labelled
|
||||
with `connected | dropped`.
|
||||
- Convenience `recordSnapshot(outcome)` + `recordPeerEvent(event)` helpers
|
||||
with the label vocabulary locked at compile time.
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```ts
|
||||
import { recordSnapshot, snapshotDispatchMs } from "@suaveplan/r3f-networking";
|
||||
|
||||
const t0 = performance.now();
|
||||
applySnapshot(frame);
|
||||
snapshotDispatchMs.record(performance.now() - t0);
|
||||
recordSnapshot("accepted");
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
Wire the counter to a divergence histogram in the reconciliation path:
|
||||
|
||||
```ts
|
||||
import {
|
||||
predictionDivergence,
|
||||
recordSnapshot,
|
||||
} from "@suaveplan/r3f-networking";
|
||||
|
||||
const result = predictor.reconcile(authoritative, lastAckSeq);
|
||||
predictionDivergence.record(result.error);
|
||||
recordSnapshot(result.error > snapThreshold ? "rejected" : "accepted");
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
- `snapshotDispatchMs: Histogram` — duration per server-snapshot dispatch.
|
||||
- `predictionDivergence: Histogram` — magnitude of reconciliation error.
|
||||
- `snapshotsTotal: Counter` — total snapshots, with the `outcome` attribute.
|
||||
- `peerEventsTotal: Counter` — total peer events, with the `event` attribute.
|
||||
- `recordSnapshot(outcome: SnapshotOutcome): void`
|
||||
- `recordPeerEvent(event: "connected" | "dropped"): void`
|
||||
- `type SnapshotOutcome = "accepted" | "out_of_order" | "rejected"`.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
The meter is acquired once at module evaluation; consumers configuring an SDK
|
||||
later in startup will still pick up these instruments via the OTel global
|
||||
provider lazy-rebind contract. Attribute vocabularies are deliberately small
|
||||
to keep label cardinality bounded. No business-string labels (peer IDs,
|
||||
entity IDs) are emitted at the histogram or counter level.
|
||||
@@ -0,0 +1,25 @@
|
||||
import { describe, expect, it } from "@suaveplan/testing/runner";
|
||||
import {
|
||||
peerEventsTotal,
|
||||
predictionDivergence,
|
||||
recordPeerEvent,
|
||||
recordSnapshot,
|
||||
snapshotDispatchMs,
|
||||
snapshotsTotal,
|
||||
} from "./observability.js";
|
||||
|
||||
describe("observability", () => {
|
||||
it("exposes instruments and recorders without throwing", () => {
|
||||
expect(snapshotDispatchMs).toBeDefined();
|
||||
expect(predictionDivergence).toBeDefined();
|
||||
expect(snapshotsTotal).toBeDefined();
|
||||
expect(peerEventsTotal).toBeDefined();
|
||||
recordSnapshot("accepted");
|
||||
recordSnapshot("out_of_order");
|
||||
recordSnapshot("rejected");
|
||||
recordPeerEvent("connected");
|
||||
recordPeerEvent("dropped");
|
||||
snapshotDispatchMs.record(1.5);
|
||||
predictionDivergence.record(0.2);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,50 @@
|
||||
import { metrics } from "@opentelemetry/api";
|
||||
|
||||
const METER_NAME = "@suaveplan/r3f-networking";
|
||||
|
||||
const meter = metrics.getMeter(METER_NAME);
|
||||
|
||||
/** Wall-time dispatch of a server snapshot through the local pipeline. */
|
||||
export const snapshotDispatchMs = meter.createHistogram(
|
||||
"r3f_net.snapshot_dispatch_ms",
|
||||
{
|
||||
description: "Time spent dispatching one server snapshot frame.",
|
||||
unit: "ms",
|
||||
}
|
||||
);
|
||||
|
||||
/** Magnitude of last reconciliation divergence. */
|
||||
export const predictionDivergence = meter.createHistogram(
|
||||
"r3f_net.prediction_divergence",
|
||||
{
|
||||
description:
|
||||
"Euclidean error between predicted and authoritative state.",
|
||||
unit: "m",
|
||||
}
|
||||
);
|
||||
|
||||
/** Per-snapshot outcome counter: accepted | out-of-order | rejected. */
|
||||
export const snapshotsTotal = meter.createCounter("r3f_net.snapshots_total", {
|
||||
description: "Snapshot frames received by the local sync engine.",
|
||||
});
|
||||
|
||||
/** Per-peer event counter: connected | dropped. */
|
||||
export const peerEventsTotal = meter.createCounter(
|
||||
"r3f_net.peer_events_total",
|
||||
{
|
||||
description: "Peer lifecycle events on the active transport.",
|
||||
}
|
||||
);
|
||||
|
||||
/** Outcome bucket label for snapshot counters. */
|
||||
export type SnapshotOutcome = "accepted" | "out_of_order" | "rejected";
|
||||
|
||||
/** Records a snapshot outcome. */
|
||||
export function recordSnapshot(outcome: SnapshotOutcome): void {
|
||||
snapshotsTotal.add(1, { outcome });
|
||||
}
|
||||
|
||||
/** Records a peer event. */
|
||||
export function recordPeerEvent(event: "connected" | "dropped"): void {
|
||||
peerEventsTotal.add(1, { event });
|
||||
}
|
||||
Reference in New Issue
Block a user