Files
2026-05-27 23:29:53 +01:00

6.9 KiB

Tasks: enterprise-profiler

Change ID: enterprise-profiler Package: @suaveplan/profiler Target path: universal/platform/profiler Prerequisite packages on Verdaccio: @suaveplan/error, @suaveplan/types, @suaveplan/events, @suaveplan/logger


0. Pre-flight (non-negotiable)

  • 0.1 Confirm clean working tree (git status reports nothing) and no in-flight worktrees with uncommitted work (git worktree list then git status in each)
  • 0.2 If any worktrees exist under .claude/worktrees/, account for each one: still-active agent, work-pending-merge, or stale-and-prune. Stale worktrees MUST be removed (git worktree remove) before opening a new one — code that has been lost twice on main is the failure mode this guards against.
  • 0.3 Confirm the package set in MANIFEST.md matches find packages -maxdepth 3 -name package.json count. Drift here means a prior change archived without regenerating the manifest.

1. Scaffold

  • Create universal/platform/profiler/ with src/, tests/, src/__benchmarks__/, and all feature subdirectories per the layout in proposal.md.
  • Write package.json at version 0.1.0:
    • "type": "module", "sideEffects": false
    • exports map: "." barrel + "./core/profiler/profiler", "./core/session/session", "./sinks/console-sink/console-sink", "./sinks/json-sink/json-sink", "./utils/aggregate", "./utils/flamegraph", "./types/common"
    • dependencies: @suaveplan/error workspace:^, @suaveplan/types workspace:^, @suaveplan/events workspace:^, @suaveplan/logger workspace:^, zod catalog:forms, @opentelemetry/api catalog:observability, tslib catalog:build
    • devDependencies: vitest catalog:testing, @vitest/coverage-istanbul catalog:testing, fast-check catalog:testing, mitata catalog:testing, @suaveplan/testing workspace:^
  • Write tsconfig.json extending @suaveplan/typescript-config/library.json.
  • Write vitest.config.ts using createTestConfig().
  • Add lint, typecheck, test, test:coverage, build, docs:check, bench scripts.

2. Types and error codes

  • Define all public types in src/types/common.ts: ProfileSample, StackFrame, ProfileSession, FlamegraphNode, ProfileAggregate, ProfilerOptions, SessionOptions, ProfilerSink, ConsoleSinkOptions, JsonSinkOptions.
  • Write src/types/common.md (≥200 words, simple + advanced examples).
  • Define error codes in src/error-codes.ts: ProfilerErrorCode union, metadata, registerProfilerErrorCodes().
  • Write Zod schemas in src/types/schemas.ts + src/types/schemas.md.

3. Aggregate utilities (src/utils/aggregate.ts)

  • Implement aggregateSamples(samples): compute count, totalCpuMs, avgCpuMs, p50/p95/p99 (insertion-sort on the cpu array), peakHeapMb, avgHeapMb.
  • Implement formatDuration(ms): format as Xms, Xs, Xm Xs.
  • Write aggregate.test.ts (≤500 lines): empty array, single sample, percentile correctness for known data.
  • Write aggregate.md (≥200 words).

4. Flamegraph utilities (src/utils/flamegraph.ts)

  • Implement buildFlamegraph(session): group samples by stack frame path, build tree by folding frames into children.
  • Each node's value = number of samples that include this frame.
  • Produce sorted children (descending by value).
  • Write flamegraph.test.ts (≤500 lines): empty session, single frame, multi-frame stacks, shared frames merge correctly.
  • Write flamegraph.md (≥200 words).

5. Profiler session (src/core/session/)

  • Implement ProfilerSession (internal class):
    • sample() — captures clock.now() + heap stats via process.memoryUsage() (guarded for non-Node runtimes); returns ProfileSample.
    • stop() — sets endTime, returns immutable ProfileSession snapshot.
    • export('json', signal?) — JSON-serialises session.
    • export('flamegraph', signal?) — calls buildFlamegraph, JSON-serialises.
    • toFlamegraph() — synchronous buildFlamegraph call.
  • Write session.test.ts (≤500 lines): sample, stop, export json, export flamegraph, abort on export.
  • Write session.md (≥200 words).

6. Profiler (src/core/profiler/)

  • Implement createProfiler(options?): validates options with Zod; stores sessions in Map<string, ProfilerSession>.
  • Implement profiler.startSession(name, options?) — generates UUID id, stores session, emits OTel span profiler.session.start.
  • Implement profiler.getSession(id), listSessions(), clearSessions().
  • Implement profiler.use(sink) — registers sink; on session stop, calls sink.onSessionEnd(session) asynchronously.
  • On session stop, emit OTel span profiler.session.stop, histogram profiler.session_duration_ms.
  • Write profiler.test.ts (≤500 lines): session lifecycle, sink called on stop, multiple sessions.
  • Write profiler.md (≥200 words).

7. Sinks (src/sinks/)

7a. Console sink

  • Implement createConsoleSink(options?) — on session end, prints a summary table via console.log with aggregate stats.
  • Write console-sink.test.ts: output contains session name and p95 duration.
  • Write console-sink.md.

7b. JSON sink

  • Implement createJsonSink(options: { path: string }) — on session end, appends JSON line to file via Bun.write (or fs.appendFile fallback).
  • Write json-sink.test.ts: file written, valid JSON, append-safe.
  • Write json-sink.md.

8. Property-based tests (tests/property.test.ts)

  • aggregateSamples invariants: avg ≤ max, p50 ≤ p95 ≤ p99, count equals input length.
  • buildFlamegraph node value sum: root node value ≤ total samples.
  • Session stop idempotency: calling stop() twice on a session should be a no-op after the first call (endTime does not change).

9. Benchmarks (src/__benchmarks__/profiler.bench.ts)

  • sample_hot: 1000 session.sample() calls.
  • buildFlamegraph_1k, buildFlamegraph_10k: flamegraph on 1k and 10k sample sessions.
  • aggregateSamples_1k, aggregateSamples_10k: aggregate on 1k and 10k samples.

10. Docs

  • All per-file .md files per layout in proposal.md.
  • universal/platform/profiler/README.md: purpose, installation, quick-start, API section, dependency table, benchmarks table.

11. Verify and publish

  • bunx turbo lint --filter=@suaveplan/profiler — zero errors.
  • bunx turbo typecheck --filter=@suaveplan/profiler — zero errors.
  • bunx turbo test --filter=@suaveplan/profiler — all tests pass.
  • bunx turbo test:coverage --filter=@suaveplan/profiler — 100% on all touched modules.
  • bun run docs:check --filter=@suaveplan/profiler — zero errors.
  • bun scripts/publish.ts --package @suaveplan/profiler.
  • Update CLAUDE.md package listing.
  • Archive: move openspec/changes/enterprise-profiler/ to openspec/archive/2026-04-24-enterprise-profiler/.