Initial Commit
This commit is contained in:
@@ -0,0 +1,186 @@
|
||||
# OpenSpec Change: enterprise-events-metrics
|
||||
|
||||
- **Change ID**: enterprise-events-metrics
|
||||
- **Package**: `@suaveplan/events-metrics`
|
||||
- **Category**: universal
|
||||
- **Target path**: `universal/events/events-metrics`
|
||||
- **Status**: PROPOSED
|
||||
- **Date**: 2026-04-24
|
||||
- **Reference spec**: `/mnt/s/web/suaveplan/enterprise/events-metrics.md`
|
||||
|
||||
---
|
||||
|
||||
## Summary
|
||||
|
||||
Deliver `@suaveplan/events-metrics` on the genesis branch from scratch at enterprise quality. The package is an opt-in metrics companion for `@suaveplan/events`: it attaches to the event bus via the public interceptor/companion API (never monkey-patching), records dispatch totals, listener error counts, dispatch latency histograms, queue depth gauges, and active subscriber counts. All metric names follow the `suaveplan.events.*` convention with stable attribute keys. Sub-microsecond hot-path overhead is a hard requirement.
|
||||
|
||||
---
|
||||
|
||||
## Current-state audit (genesis)
|
||||
|
||||
The package does not exist on genesis yet — fresh build. No prior source, no test results, no coverage data.
|
||||
|
||||
---
|
||||
|
||||
## Available workspace deps on genesis
|
||||
|
||||
| Package | Role |
|
||||
|---|---|
|
||||
| `@suaveplan/events` (`workspace:^`) | Event bus and interceptor API |
|
||||
| `@suaveplan/opentelemetry` (`workspace:^`) | `MeterProvider` factory, counter/histogram/gauge helpers |
|
||||
| `@suaveplan/error` (`workspace:^`) | Base class for `EventsMetricsError`; `defineCodes` for `EVENTS_METRICS.*` codes |
|
||||
| `@suaveplan/types` (`workspace:^`) | `LoggerLike`, `Clock` injection interfaces |
|
||||
| `zod` (`catalog:forms`) | Zod schemas for all public option types |
|
||||
| `@opentelemetry/api` (`catalog:observability`) | `Meter`, `Counter`, `Histogram`, `ObservableGauge` |
|
||||
| `tslib` (`catalog:build`) | Runtime helpers |
|
||||
|
||||
Dev deps: `vitest`, `@vitest/coverage-istanbul`, `fast-check`, `mitata` (all `catalog:testing`).
|
||||
|
||||
---
|
||||
|
||||
## Public API surface
|
||||
|
||||
### Setup
|
||||
|
||||
```ts
|
||||
// Attaches metrics companion to an existing EventBus
|
||||
function attachEventMetrics(bus: EventBusLike, options?: EventMetricsOptions): EventMetricsHandle
|
||||
|
||||
interface EventMetricsOptions {
|
||||
meter?: Meter // default: global meter from @opentelemetry/api
|
||||
eventFilter?: (name: string) => boolean // optional: record only matching events
|
||||
clock?: Clock
|
||||
logger?: LoggerLike
|
||||
}
|
||||
|
||||
interface EventMetricsHandle {
|
||||
detach(): void
|
||||
snapshot(): MetricsSnapshot
|
||||
}
|
||||
```
|
||||
|
||||
### Metric definitions
|
||||
|
||||
```ts
|
||||
// All metric names and attribute keys as stable constants
|
||||
const METRIC_NAMES = {
|
||||
DISPATCH_TOTAL: "suaveplan.events.dispatch.total",
|
||||
LISTENER_ERROR_TOTAL: "suaveplan.events.listener.error.total",
|
||||
DISPATCH_LATENCY_MS: "suaveplan.events.dispatch.latency_ms",
|
||||
QUEUE_DEPTH: "suaveplan.events.queue.depth",
|
||||
ACTIVE_SUBSCRIBERS: "suaveplan.events.subscribers.active",
|
||||
} as const
|
||||
|
||||
const ATTRIBUTE_KEYS = {
|
||||
EVENT_NAME: "event.name",
|
||||
EVENT_CATEGORY: "event.category",
|
||||
LISTENER_ID: "listener.id",
|
||||
BUS_ID: "bus.id",
|
||||
} as const
|
||||
```
|
||||
|
||||
### Snapshot
|
||||
|
||||
```ts
|
||||
interface MetricsSnapshot {
|
||||
dispatchTotal: Record<string, number> // keyed by event.name
|
||||
listenerErrorTotal: Record<string, number>
|
||||
avgDispatchLatencyMs: Record<string, number>
|
||||
queueDepth: number
|
||||
activeSubscribers: number
|
||||
}
|
||||
```
|
||||
|
||||
### Error handling
|
||||
|
||||
```ts
|
||||
class EventsMetricsError extends SuaveplanError<EventsMetricsErrorCode>
|
||||
|
||||
type EventsMetricsErrorCode =
|
||||
| "EVENTS_METRICS_ATTACH_FAILED"
|
||||
| "EVENTS_METRICS_ALREADY_ATTACHED"
|
||||
| "EVENTS_METRICS_DETACHED"
|
||||
|
||||
function registerEventsMetricsErrorCodes(): void
|
||||
```
|
||||
|
||||
### Zod schemas
|
||||
|
||||
```ts
|
||||
const EventMetricsOptionsSchema: ZodSchema
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## New additions for genesis
|
||||
|
||||
### 1. Companion-API only
|
||||
|
||||
`attachEventMetrics` uses only the public `EventBus` interceptor/companion surface from `@suaveplan/events`. No access to private fields, no prototype patching. If the bus is replaced or re-created, callers must call `detach()` and `attachEventMetrics` again.
|
||||
|
||||
### 2. Low-overhead hot path
|
||||
|
||||
The dispatch interceptor is a thin closure: one array lookup for the counter and one `performance.now()` call for the histogram. Allocation on the critical path is zero when the event name was previously seen (name-to-attributes map is pre-cached).
|
||||
|
||||
### 3. Observable gauge for queue depth and subscribers
|
||||
|
||||
Queue depth and active subscriber count are reported as OTel `ObservableGauge` (polled at collection time), not recorded on every dispatch, to avoid per-dispatch overhead.
|
||||
|
||||
### 4. Property-based tests (fast-check)
|
||||
|
||||
- Counter monotonicity: `dispatchTotal[name]` never decreases after more dispatches
|
||||
- Error counter accuracy: `listenerErrorTotal` equals the number of listener throws injected
|
||||
- Histogram latency: average latency is within 1ms of a synthetic injected delay
|
||||
|
||||
### 5. Benchmark suite (`src/__benchmarks__/`)
|
||||
|
||||
- Overhead of `attachEventMetrics` on dispatch hot path: compare instrumented vs uninstrumented bus at 1M events/sec
|
||||
- Snapshot retrieval latency at 1k distinct event names
|
||||
|
||||
---
|
||||
|
||||
## File layout
|
||||
|
||||
```text
|
||||
universal/events/events-metrics/
|
||||
package.json
|
||||
tsconfig.json
|
||||
vitest.config.ts
|
||||
src/
|
||||
index.ts
|
||||
error-codes.ts
|
||||
constants/
|
||||
index.ts
|
||||
core/
|
||||
attach-event-metrics/
|
||||
attach-event-metrics.ts
|
||||
attach-event-metrics.md
|
||||
attach-event-metrics.types.ts
|
||||
attach-event-metrics.test.ts
|
||||
index.ts
|
||||
metrics/
|
||||
metric-names/
|
||||
metric-names.ts
|
||||
metric-names.md
|
||||
index.ts
|
||||
snapshot/
|
||||
snapshot.ts
|
||||
snapshot.md
|
||||
snapshot.test.ts
|
||||
index.ts
|
||||
__benchmarks__/
|
||||
events-metrics.bench.ts
|
||||
tests/
|
||||
integration.test.ts
|
||||
property.test.ts
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Impact
|
||||
|
||||
**Downstream packages**: any application wiring `@suaveplan/events` that needs production observability without code changes to event publishers or subscribers.
|
||||
|
||||
**Workspace dep prerequisites**: `@suaveplan/events`, `@suaveplan/opentelemetry`, `@suaveplan/error`, `@suaveplan/types` must be published to Verdaccio.
|
||||
|
||||
**Breaking changes**: none (new package).
|
||||
Reference in New Issue
Block a user