241 lines
6.5 KiB
Markdown
241 lines
6.5 KiB
Markdown
# OpenSpec Change: enterprise-profiler
|
|
|
|
- **Change ID**: enterprise-profiler
|
|
- **Package**: `@suaveplan/profiler`
|
|
- **Category**: universal/platform
|
|
- **Target path**: `universal/platform/profiler`
|
|
- **Status**: PROPOSED
|
|
- **Date**: 2026-04-24
|
|
- **Reference spec**: `/mnt/s/web/suaveplan/enterprise/profiler.md`
|
|
|
|
---
|
|
|
|
## Summary
|
|
|
|
Deliver `@suaveplan/profiler` on the genesis branch from scratch at enterprise quality. The package provides runtime performance profiling primitives: CPU/wall-time sampling, memory snapshots, flame-graph serialization, profile sessions with start/stop/export, and a plugin interface for sink adapters (console, file, remote). Genesis adds structured errors via `@suaveplan/error`, Zod schemas for all profile types, OTel integration (profiles forwarded as OTel log records), `AbortSignal` on async session exports, injectable `Clock` and `ProcessSampler` for deterministic testing, and property-based tests for statistical aggregation.
|
|
|
|
---
|
|
|
|
## Current-state audit (genesis)
|
|
|
|
The package does not exist on genesis yet — this is a fresh build.
|
|
|
|
---
|
|
|
|
## Available workspace deps on genesis
|
|
|
|
| Package | Role |
|
|
|---|---|
|
|
| `@suaveplan/error` (`workspace:^`) | Base class for `ProfilerError`; stable error codes |
|
|
| `@suaveplan/types` (`workspace:^`) | `LoggerLike`, `Clock` injectable interfaces |
|
|
| `@suaveplan/events` (`workspace:^`) | Event bus for profile-session lifecycle events |
|
|
| `@suaveplan/logger` (`workspace:^`) | Structured logging with OTel trace correlation |
|
|
| `zod` (`catalog:forms`) | Zod schemas for all public profile types |
|
|
| `@opentelemetry/api` (`catalog:observability`) | Spans + counters on session start/stop/export; profiler telemetry |
|
|
| `tslib` (`catalog:build`) | Runtime helpers |
|
|
|
|
Dev deps: `vitest`, `@vitest/coverage-istanbul`, `fast-check`, `mitata` (all `catalog:testing`).
|
|
|
|
---
|
|
|
|
## Public API surface
|
|
|
|
### Core types
|
|
|
|
```ts
|
|
interface ProfileSample {
|
|
readonly timestamp: number
|
|
readonly stackFrames: ReadonlyArray<StackFrame>
|
|
readonly cpuUsage?: number
|
|
readonly heapUsed?: number
|
|
readonly heapTotal?: number
|
|
readonly externalMemory?: number
|
|
}
|
|
|
|
interface StackFrame {
|
|
readonly functionName: string
|
|
readonly scriptId?: string
|
|
readonly url?: string
|
|
readonly lineNumber?: number
|
|
readonly columnNumber?: number
|
|
}
|
|
|
|
interface ProfileSession {
|
|
readonly id: string
|
|
readonly name: string
|
|
readonly startTime: number
|
|
readonly endTime?: number
|
|
readonly samples: ReadonlyArray<ProfileSample>
|
|
}
|
|
|
|
interface FlamegraphNode {
|
|
readonly name: string
|
|
readonly value: number
|
|
readonly children: ReadonlyArray<FlamegraphNode>
|
|
}
|
|
```
|
|
|
|
### Profiler API
|
|
|
|
```ts
|
|
function createProfiler(options?: ProfilerOptions): Profiler
|
|
|
|
interface Profiler {
|
|
startSession(name: string, options?: SessionOptions): ProfilerSession
|
|
getSession(id: string): ProfilerSession | null
|
|
listSessions(): ReadonlyArray<ProfileSession>
|
|
clearSessions(): void
|
|
use(sink: ProfilerSink): this
|
|
}
|
|
|
|
interface ProfilerSession {
|
|
readonly id: string
|
|
readonly name: string
|
|
stop(): ProfileSession
|
|
sample(): ProfileSample
|
|
export(format: 'json' | 'flamegraph', signal?: AbortSignal): Promise<string>
|
|
toFlamegraph(): FlamegraphNode
|
|
}
|
|
```
|
|
|
|
### Options
|
|
|
|
```ts
|
|
interface ProfilerOptions {
|
|
sampleIntervalMs?: number
|
|
maxSamples?: number
|
|
collectMemory?: boolean
|
|
collectCpu?: boolean
|
|
clock?: Clock
|
|
logger?: LoggerLike
|
|
eventBus?: EventBusLike
|
|
}
|
|
|
|
interface SessionOptions {
|
|
tags?: Record<string, string>
|
|
autoStop?: boolean
|
|
autoStopAfterMs?: number
|
|
}
|
|
```
|
|
|
|
### Plugin/sink interface
|
|
|
|
```ts
|
|
interface ProfilerSink {
|
|
readonly name: string
|
|
onSessionEnd(session: ProfileSession): void | Promise<void>
|
|
}
|
|
|
|
function defineProfilerSink(sink: ProfilerSink): ProfilerSink
|
|
function createConsoleSink(options?: ConsoleSinkOptions): ProfilerSink
|
|
function createJsonSink(options: JsonSinkOptions): ProfilerSink
|
|
```
|
|
|
|
### Utilities
|
|
|
|
```ts
|
|
function aggregateSamples(samples: ProfileSample[]): ProfileAggregate
|
|
function buildFlamegraph(session: ProfileSession): FlamegraphNode
|
|
function formatDuration(ms: number): string
|
|
|
|
interface ProfileAggregate {
|
|
readonly count: number
|
|
readonly totalCpuMs: number
|
|
readonly avgCpuMs: number
|
|
readonly p50CpuMs: number
|
|
readonly p95CpuMs: number
|
|
readonly p99CpuMs: number
|
|
readonly peakHeapMb: number
|
|
readonly avgHeapMb: number
|
|
}
|
|
```
|
|
|
|
### Error codes
|
|
|
|
```ts
|
|
type ProfilerErrorCode =
|
|
| 'PROFILER_SESSION_NOT_FOUND'
|
|
| 'PROFILER_SESSION_ALREADY_STOPPED'
|
|
| 'PROFILER_EXPORT_FAILED'
|
|
| 'PROFILER_SAMPLE_FAILED'
|
|
| 'PROFILER_SINK_FAILED'
|
|
|
|
function registerProfilerErrorCodes(): void
|
|
```
|
|
|
|
---
|
|
|
|
## Genesis additions
|
|
|
|
1. **Structured `ProfilerError`** extending `SuaveplanError` with stable `PROFILER.*` codes
|
|
2. **Injectable `Clock`** so sample timestamps are deterministic in tests
|
|
3. **`ProfilerSink` plugin interface** — `use()` on `Profiler`; console and JSON sinks built-in
|
|
4. **`AbortSignal` on `session.export()`**
|
|
5. **OTel integration** — span `profiler.session` with attributes `session.name`, `session.sample_count`; histogram `profiler.session_duration_ms`
|
|
6. **`aggregateSamples` with percentile stats** (p50/p95/p99) computed via insertion sort on small sample sets
|
|
7. **Property-based tests** for `aggregateSamples` statistical invariants, `buildFlamegraph` tree structure
|
|
8. **Benchmark suite** — `sample()` hot path, `buildFlamegraph` on 1k/10k sample sessions
|
|
|
|
---
|
|
|
|
## File layout
|
|
|
|
```text
|
|
universal/platform/profiler/
|
|
package.json
|
|
tsconfig.json
|
|
vitest.config.ts
|
|
src/
|
|
index.ts
|
|
error-codes.ts
|
|
constants/
|
|
index.ts
|
|
types/
|
|
common.ts
|
|
common.md
|
|
core/
|
|
profiler/
|
|
profiler.ts
|
|
profiler.md
|
|
profiler.types.ts
|
|
profiler.test.ts
|
|
index.ts
|
|
session/
|
|
session.ts
|
|
session.md
|
|
session.types.ts
|
|
session.test.ts
|
|
index.ts
|
|
sinks/
|
|
console-sink/
|
|
console-sink.ts
|
|
console-sink.md
|
|
console-sink.test.ts
|
|
index.ts
|
|
json-sink/
|
|
json-sink.ts
|
|
json-sink.md
|
|
json-sink.test.ts
|
|
index.ts
|
|
utils/
|
|
aggregate.ts
|
|
aggregate.md
|
|
aggregate.test.ts
|
|
flamegraph.ts
|
|
flamegraph.md
|
|
flamegraph.test.ts
|
|
__benchmarks__/
|
|
profiler.bench.ts
|
|
tests/
|
|
integration.test.ts
|
|
property.test.ts
|
|
```
|
|
|
|
---
|
|
|
|
## Impact
|
|
|
|
**Downstream packages**: Any package needing runtime performance profiling. No existing genesis package currently depends on it.
|
|
|
|
**Workspace dep prerequisites**: `@suaveplan/error`, `@suaveplan/types`, `@suaveplan/events`, `@suaveplan/logger` must be published to Verdaccio.
|