1- import path from 'node:path' ;
2- import { performance } from 'node:perf_hooks' ;
3- import type { PerformanceEntry } from 'node:perf_hooks' ;
41import process from 'node:process' ;
5- import { threadId } from 'node:worker_threads' ;
62import { isEnvVarEnabled } from '../env.js' ;
7- import { installExitHandlers } from '../exit-process.js' ;
8- import { PerformanceObserverSink } from '../performance-observer.js' ;
93import {
104 type ActionTrackConfigs ,
115 type MeasureCtxOptions ,
@@ -20,23 +14,25 @@ import type {
2014 DevToolsColor ,
2115 EntryMeta ,
2216} from '../user-timing-extensibility-api.type.js' ;
23- import {
24- PROFILER_DIRECTORY ,
25- PROFILER_ENABLED_ENV_VAR ,
26- PROFILER_ORIGIN_PID_ENV_VAR ,
27- } from './constants.js' ;
28- import { entryToTraceEvents } from './trace-file-utils.js' ;
29- import type { UserTimingTraceEvent } from './trace-file.type.js' ;
30- import { traceEventWalFormat } from './wal-json-trace.js' ;
31- import {
32- ShardedWal ,
33- WriteAheadLogFile ,
34- getShardId ,
35- getShardedGroupId ,
36- isLeaderWal ,
37- setLeaderWal ,
38- } from './wal.js' ;
39- import type { WalFormat } from './wal.js' ;
17+ import { PROFILER_ENABLED_ENV_VAR } from './constants.js' ;
18+
19+ /**
20+ * Configuration options for creating a Profiler instance.
21+ *
22+ * @template T - Record type defining available track names and their configurations
23+ */
24+ type ProfilerMeasureOptions < T extends ActionTrackConfigs > =
25+ MeasureCtxOptions & {
26+ /** Custom track configurations that will be merged with default settings */
27+ tracks ?: Record < keyof T , Partial < ActionTrackEntryPayload > > ;
28+ /** Whether profiling should be enabled (defaults to CP_PROFILING env var) */
29+ enabled ?: boolean ;
30+ } ;
31+
32+ /**
33+ * Options for creating a performance marker.
34+ */
35+ export type MarkerOptions = EntryMeta & { color ?: DevToolsColor } ;
4036
4137/**
4238 * Options for configuring a Profiler instance.
@@ -53,15 +49,7 @@ import type { WalFormat } from './wal.js';
5349 * @property tracks - Custom track configurations merged with defaults
5450 */
5551export type ProfilerOptions < T extends ActionTrackConfigs = ActionTrackConfigs > =
56- MeasureCtxOptions & {
57- tracks ?: Record < keyof T , Partial < ActionTrackEntryPayload > > ;
58- enabled ?: boolean ;
59- } ;
60-
61- /**
62- * Options for creating a performance marker.
63- */
64- export type MarkerOptions = EntryMeta & { color ?: DevToolsColor } ;
52+ ProfilerMeasureOptions < T > ;
6553
6654/**
6755 * Performance profiler that creates structured timing measurements with Chrome DevTools Extensibility API payloads.
@@ -117,13 +105,6 @@ export class Profiler<T extends ActionTrackConfigs> {
117105 this . #enabled = enabled ;
118106 }
119107
120- /**
121- * Close the profiler. Subclasses should override this to perform cleanup.
122- */
123- close ( ) : void {
124- // Base implementation does nothing
125- }
126-
127108 /**
128109 * Is profiling enabled?
129110 *
@@ -245,104 +226,3 @@ export class Profiler<T extends ActionTrackConfigs> {
245226 }
246227 }
247228}
248-
249- export class NodeProfiler <
250- TracksConfig extends ActionTrackConfigs = ActionTrackConfigs ,
251- CodecOutput extends UserTimingTraceEvent = UserTimingTraceEvent ,
252- > extends Profiler < TracksConfig > {
253- #shard: WriteAheadLogFile < CodecOutput > ;
254- #perfObserver: PerformanceObserverSink < CodecOutput > ;
255- #shardWal: ShardedWal < CodecOutput > ;
256- readonly #format: WalFormat < CodecOutput > ;
257- readonly #debug: boolean ;
258- #closed: boolean = false ;
259-
260- constructor (
261- options : ProfilerOptions < TracksConfig > & {
262- directory ?: string ;
263- performanceEntryEncode : ( entry : PerformanceEntry ) => CodecOutput [ ] ;
264- debug ?: boolean ;
265- } ,
266- ) {
267- // Initialize origin PID early - must happen before user code runs
268- setLeaderWal ( PROFILER_ORIGIN_PID_ENV_VAR ) ;
269-
270- const {
271- directory = PROFILER_DIRECTORY ,
272- performanceEntryEncode,
273- debug = false ,
274- ...profilerOptions
275- } = options ;
276- super ( profilerOptions ) ;
277- const walGroupId = getShardedGroupId ( ) ;
278- const shardId = getShardId ( process . pid , threadId ) ;
279-
280- this . #format = traceEventWalFormat ( { groupId : walGroupId } ) ;
281- this . #debug = debug ;
282- this . #shardWal = new ShardedWal (
283- path . join ( directory , walGroupId ) ,
284- this . #format,
285- ) ;
286- this . #shard = this . #shardWal. shard ( shardId ) ;
287-
288- this . #perfObserver = new PerformanceObserverSink ( {
289- sink : this . #shard,
290- encode : performanceEntryEncode ,
291- buffered : true ,
292- flushThreshold : 1 , // Lower threshold for immediate flushing
293- } ) ;
294-
295- this . #perfObserver. subscribe ( ) ;
296-
297- installExitHandlers ( {
298- onExit : ( ) => {
299- this . close ( ) ;
300- } ,
301- } ) ;
302- }
303-
304- getFinalPath ( ) {
305- return this . #format. finalPath ( ) ;
306- }
307-
308- /**
309- * Close the profiler and finalize files if this is the leader process.
310- * This method can be called manually to ensure proper cleanup.
311- */
312- close ( ) : void {
313- if ( this . #closed) {
314- return ;
315- }
316-
317- this . #closed = true ;
318-
319- try {
320- if ( ! this . #perfObserver || ! this . #shard || ! this . #shardWal) {
321- console . warn ( 'Warning: Profiler not fully initialized during close' ) ;
322- return ;
323- }
324-
325- this . #perfObserver. flush ( ) ;
326- this . #perfObserver. unsubscribe ( ) ;
327-
328- this . #shard. close ( ) ;
329-
330- if ( isLeaderWal ( PROFILER_ORIGIN_PID_ENV_VAR ) ) {
331- this . #shardWal. finalize ( ) ;
332- if ( ! this . #debug) {
333- this . #shardWal. cleanup ( ) ;
334- }
335- }
336- } catch ( error ) {
337- console . warn ( 'Warning: Error during profiler close:' , error ) ;
338- }
339- }
340- }
341-
342- export const profiler = new NodeProfiler ( {
343- prefix : 'cp' ,
344- track : 'CLI' ,
345- trackGroup : 'Code Pushup' ,
346- performanceEntryEncode : entryToTraceEvents ,
347- debug : process . env . CP_PROFILER_DEBUG === 'true' ,
348- } ) ;
0 commit comments