UNPKG

@dudousxd/nestjs-telescope

Version:

Laravel Telescope-style observability console for NestJS — core: watchers, recorder, correlation, SQLite store, headless API.

92 lines 3.52 kB
// packages/core/src/profiling/cpu-profiler.ts import { createRequire } from 'node:module'; import { aggregateCpuProfile } from './aggregate-profile.js'; const DEFAULT_SAMPLING_INTERVAL_MICROS = 1000; /** * Wraps a V8 CPU profiling session over the inspector protocol. A single * profiler instance captures ONE profile at a time (`start` then `stop`). * * OVERHEAD GUARANTEE: constructing a profiler does no work and creates no * session. The inspector session is created (and `node:inspector` is required) * only inside `start()`. When profiling is disabled the controller never * constructs one, so there is provably zero inspector activity. */ export class CpuProfiler { sessionFactory; session = null; running = false; intervalMicros; constructor(sessionFactory = defaultSessionFactory, options = {}) { this.sessionFactory = sessionFactory; this.intervalMicros = options.samplingIntervalMicros ?? DEFAULT_SAMPLING_INTERVAL_MICROS; } get isRunning() { return this.running; } /** Begin a CPU profile. No-op if one is already running. */ async start() { if (this.running) return; const session = this.sessionFactory(); session.connect(); this.session = session; await this.postAsync(session, 'Profiler.enable'); await this.postAsync(session, 'Profiler.setSamplingInterval', { interval: this.intervalMicros, }); await this.postAsync(session, 'Profiler.start'); this.running = true; } /** * Stop the profile and aggregate it into a flame tree. Returns `null` when no * profile is running. Always disconnects the session, even on error. */ async stop() { const session = this.session; if (!this.running || session === null) return null; try { const result = (await this.postAsync(session, 'Profiler.stop')); return aggregateCpuProfile(result.profile); } finally { this.running = false; this.session = null; try { session.disconnect(); } catch { // A disconnect failure must never mask the captured profile or throw. } } } postAsync(session, method, params) { return new Promise((resolve, reject) => { const cb = (err, result) => { if (err) reject(err); else resolve(result); }; if (params === undefined) session.post(method, cb); else session.post(method, params, cb); }); } } /** * Lazily require `node:inspector` and return a fresh Session. Kept out of the * module's import graph so that importing this file (or the whole package) does * NOT load the inspector binding — that only happens the first time a capture * starts, i.e. only when profiling is enabled AND a request is sampled. */ export function defaultSessionFactory() { // `createRequire` performs a SYNCHRONOUS require of the built-in without it // appearing in this module's static import graph, so loading the package never // pulls in the inspector binding. Built only on first capture. const requireFn = createRequire(import.meta.url); const inspector = requireFn('node:inspector'); return new inspector.Session(); } //# sourceMappingURL=cpu-profiler.js.map