Skip to content

Commit a58c5fa

Browse files
committed
refactor: fix lint
1 parent 0a36fda commit a58c5fa

File tree

5 files changed

+101
-63
lines changed

5 files changed

+101
-63
lines changed

packages/utils/src/lib/profiler/profiler-node.int.test.ts

Lines changed: 40 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { MockTraceEventFileSink } from '../../../mocks/sink.mock.js';
1+
import path from 'node:path';
22
import type { PerformanceEntryEncoder } from '../performance-observer.js';
33
import { NodejsProfiler } from './profiler-node.js';
44

@@ -10,7 +10,6 @@ describe('NodeJS Profiler Integration', () => {
1010
return [];
1111
};
1212

13-
let mockSink: MockTraceEventFileSink;
1413
let nodejsProfiler: NodejsProfiler<string>;
1514
const originalProfilingEnv = process.env.CP_PROFILING;
1615
const originalDebugEnv = process.env.CP_PROFILER_DEBUG;
@@ -23,13 +22,11 @@ describe('NodeJS Profiler Integration', () => {
2322
// eslint-disable-next-line functional/immutable-data
2423
delete process.env.CP_PROFILER_DEBUG;
2524

26-
mockSink = new MockTraceEventFileSink();
27-
2825
nodejsProfiler = new NodejsProfiler({
2926
prefix: 'test',
3027
track: 'test-track',
31-
sink: mockSink,
3228
encodePerfEntry: simpleEncoder,
29+
filename: path.join(process.cwd(), 'tmp', 'int', 'utils', 'trace.json'),
3330
enabled: true,
3431
});
3532
});
@@ -55,9 +52,8 @@ describe('NodeJS Profiler Integration', () => {
5552
});
5653

5754
it('should initialize with sink opened when enabled', () => {
58-
expect(mockSink.isClosed()).toBeFalse();
5955
expect(nodejsProfiler.isEnabled()).toBeTrue();
60-
expect(mockSink.open).toHaveBeenCalledOnce();
56+
expect(nodejsProfiler.stats.walOpen).toBeTrue();
6157
});
6258

6359
it('should create performance entries and write to sink', () => {
@@ -75,26 +71,24 @@ describe('NodeJS Profiler Integration', () => {
7571
).resolves.toBe('async-result');
7672
});
7773

78-
it('should disable profiling and keep sink open', () => {
74+
it('should disable profiling and close sink', () => {
7975
nodejsProfiler.setEnabled(false);
8076
expect(nodejsProfiler.isEnabled()).toBeFalse();
81-
expect(mockSink.isClosed()).toBeFalse();
82-
expect(mockSink.close).not.toHaveBeenCalled();
77+
expect(nodejsProfiler.stats.walOpen).toBeFalse();
8378

8479
expect(nodejsProfiler.measure('disabled-test', () => 'success')).toBe(
8580
'success',
8681
);
87-
88-
expect(mockSink.getWrittenItems()).toHaveLength(0);
8982
});
9083

9184
it('should re-enable profiling correctly', () => {
9285
nodejsProfiler.setEnabled(false);
86+
expect(nodejsProfiler.stats.walOpen).toBeFalse();
87+
9388
nodejsProfiler.setEnabled(true);
9489

9590
expect(nodejsProfiler.isEnabled()).toBeTrue();
96-
expect(mockSink.isClosed()).toBeFalse();
97-
expect(mockSink.open).toHaveBeenCalledTimes(2);
91+
expect(nodejsProfiler.stats.walOpen).toBeTrue();
9892

9993
expect(nodejsProfiler.measure('re-enabled-test', () => 42)).toBe(42);
10094
});
@@ -107,8 +101,14 @@ describe('NodeJS Profiler Integration', () => {
107101
db: { track: 'Database', color: 'secondary' },
108102
cache: { track: 'Cache', color: 'primary' },
109103
},
110-
sink: mockSink,
111104
encodePerfEntry: simpleEncoder,
105+
filename: path.join(
106+
process.cwd(),
107+
'tmp',
108+
'int',
109+
'utils',
110+
'trace-tracks.json',
111+
),
112112
});
113113

114114
expect(
@@ -124,15 +124,21 @@ describe('NodeJS Profiler Integration', () => {
124124
const bufferedProfiler = new NodejsProfiler({
125125
prefix: 'buffered-test',
126126
track: 'Test',
127-
sink: mockSink,
128127
encodePerfEntry: simpleEncoder,
129128
captureBufferedEntries: true,
129+
filename: path.join(
130+
process.cwd(),
131+
'tmp',
132+
'int',
133+
'utils',
134+
'trace-buffered.json',
135+
),
130136
enabled: true,
131137
});
132138

133139
const bufferedStats = bufferedProfiler.stats;
134140
expect(bufferedStats.state).toBe('running');
135-
expect(bufferedStats.sinkOpen).toBeTrue();
141+
expect(bufferedStats.walOpen).toBeTrue();
136142
expect(bufferedStats.isSubscribed).toBeTrue();
137143
expect(bufferedStats.queued).toBe(0);
138144
expect(bufferedStats.dropped).toBe(0);
@@ -145,18 +151,24 @@ describe('NodeJS Profiler Integration', () => {
145151
const statsProfiler = new NodejsProfiler({
146152
prefix: 'stats-test',
147153
track: 'Stats',
148-
sink: mockSink,
149154
encodePerfEntry: simpleEncoder,
150155
maxQueueSize: 2,
151156
flushThreshold: 2,
157+
filename: path.join(
158+
process.cwd(),
159+
'tmp',
160+
'int',
161+
'utils',
162+
'trace-stats.json',
163+
),
152164
enabled: true,
153165
});
154166

155167
expect(statsProfiler.measure('test-op', () => 'result')).toBe('result');
156168

157169
const stats = statsProfiler.stats;
158170
expect(stats.state).toBe('running');
159-
expect(stats.sinkOpen).toBeTrue();
171+
expect(stats.walOpen).toBeTrue();
160172
expect(stats.isSubscribed).toBeTrue();
161173
expect(typeof stats.queued).toBe('number');
162174
expect(typeof stats.dropped).toBe('number');
@@ -169,16 +181,22 @@ describe('NodeJS Profiler Integration', () => {
169181
const profiler = new NodejsProfiler({
170182
prefix: 'stats-profiler',
171183
track: 'Stats',
172-
sink: mockSink,
173184
encodePerfEntry: simpleEncoder,
174185
maxQueueSize: 3,
175186
flushThreshold: 2,
187+
filename: path.join(
188+
process.cwd(),
189+
'tmp',
190+
'int',
191+
'utils',
192+
'trace-stats-comprehensive.json',
193+
),
176194
enabled: true,
177195
});
178196

179197
const initialStats = profiler.stats;
180198
expect(initialStats.state).toBe('running');
181-
expect(initialStats.sinkOpen).toBeTrue();
199+
expect(initialStats.walOpen).toBeTrue();
182200
expect(initialStats.isSubscribed).toBeTrue();
183201
expect(initialStats.queued).toBe(0);
184202
expect(initialStats.dropped).toBe(0);
@@ -193,7 +211,7 @@ describe('NodeJS Profiler Integration', () => {
193211

194212
const finalStats = profiler.stats;
195213
expect(finalStats.state).toBe('idle');
196-
expect(finalStats.sinkOpen).toBeTrue();
214+
expect(finalStats.walOpen).toBeFalse();
197215
expect(finalStats.isSubscribed).toBeFalse();
198216
expect(finalStats.queued).toBe(0);
199217

packages/utils/src/lib/profiler/profiler-node.ts

Lines changed: 28 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import path from 'node:path';
12
import { isEnvVarEnabled } from '../env.js';
23
import { subscribeProcessExit } from '../exit-process.js';
34
import {
@@ -10,7 +11,7 @@ import type {
1011
ActionTrackEntryPayload,
1112
MarkerPayload,
1213
} from '../user-timing-extensibility-api.type.js';
13-
import type { AppendableSink } from '../wal.js';
14+
import { type AppendableSink, WriteAheadLogFile, stringCodec } from '../wal.js';
1415
import {
1516
PROFILER_DEBUG_ENV_VAR,
1617
PROFILER_ENABLED_ENV_VAR,
@@ -30,10 +31,12 @@ export type NodejsProfilerOptions<
3031
> = ProfilerOptions<Tracks> &
3132
Omit<PerformanceObserverOptions<DomainEvents>, 'sink'> & {
3233
/**
33-
* Sink for buffering and flushing performance data
34+
* File path for the WriteAheadLogFile sink.
35+
* If not provided, defaults to `trace.json` in the current working directory.
36+
*
37+
* @default path.join(process.cwd(), 'trace.json')
3438
*/
35-
sink: AppendableSink<DomainEvents>;
36-
39+
filename?: string;
3740
/**
3841
* Name of the environment variable to check for debug mode.
3942
* When the env var is set to 'true', profiler state transitions create performance marks for debugging.
@@ -47,12 +50,11 @@ export type NodejsProfilerOptions<
4750
* Performance profiler with automatic process exit handling for buffered performance data.
4851
*
4952
* This class extends the base {@link Profiler} with automatic flushing of performance data
50-
* when the process exits. It accepts a {@link PerformanceObserverSink} that buffers performance
51-
* entries and ensures they are written out during process termination, even for unexpected exits.
53+
* when the process exits. It automatically creates a {@link WriteAheadLogFile} sink that buffers
54+
* performance entries and ensures they are written out during process termination, even for unexpected exits.
5255
*
53-
* The sink defines the output format for performance data, enabling flexible serialization
54-
* to various formats such as DevTools TraceEvent JSON, OpenTelemetry protocol buffers,
55-
* or custom domain-specific formats.
56+
* The sink uses a default codec for serializing performance data to JSON format,
57+
* enabling compatibility with Chrome DevTools trace file format.
5658
*
5759
* The profiler automatically subscribes to the performance observer when enabled and installs
5860
* exit handlers that flush buffered data on process termination (signals, fatal errors, or normal exit).
@@ -76,27 +78,31 @@ export class NodejsProfiler<
7678

7779
/**
7880
* Creates a NodejsProfiler instance.
79-
* @param options - Configuration with required sink
81+
* A WriteAheadLogFile sink is automatically created for buffering performance data.
82+
* @param options - Configuration options
8083
*/
8184
constructor(options: NodejsProfilerOptions<DomainEvents, Tracks>) {
8285
const {
83-
sink,
8486
encodePerfEntry,
8587
captureBufferedEntries,
8688
flushThreshold,
8789
maxQueueSize,
8890
enabled,
91+
filename,
8992
debugEnvVar = PROFILER_DEBUG_ENV_VAR,
9093
...profilerOptions
9194
} = options;
9295
const initialEnabled = enabled ?? isEnvVarEnabled(PROFILER_ENABLED_ENV_VAR);
9396
super({ ...profilerOptions, enabled: initialEnabled });
9497

95-
this.#sink = sink;
98+
this.#sink = new WriteAheadLogFile({
99+
file: filename ?? path.join(process.cwd(), 'trace.json'),
100+
codec: stringCodec<DomainEvents>(),
101+
});
96102
this.#debug = isEnvVarEnabled(debugEnvVar);
97103

98104
this.#performanceObserverSink = new PerformanceObserverSink({
99-
sink,
105+
sink: this.#sink,
100106
encodePerfEntry,
101107
captureBufferedEntries,
102108
flushThreshold,
@@ -155,9 +161,14 @@ export class NodejsProfiler<
155161
*/
156162
#handleFatalError(
157163
error: unknown,
158-
_kind: 'uncaughtException' | 'unhandledRejection',
164+
kind: 'uncaughtException' | 'unhandledRejection',
159165
): void {
160-
this.marker('Fatal Error', errorToMarkerPayload(error));
166+
this.marker(
167+
'Fatal Error',
168+
errorToMarkerPayload(error, {
169+
tooltipText: `${kind} caused fatal error`,
170+
}),
171+
);
161172
this.close(); // Ensures buffers flush and sink finalizes
162173
}
163174

@@ -166,7 +177,7 @@ export class NodejsProfiler<
166177
*
167178
* State transitions enforce lifecycle invariants:
168179
* - `idle -> running`: Enables profiling, opens sink, and subscribes to performance observer
169-
* - `running -> idle`: Disables profiling and unsubscribes (sink remains open for potential re-enable)
180+
* - `running -> idle`: Disables profiling, unsubscribes, and closes sink (sink will be reopened on re-enable)
170181
* - `running -> closed`: Disables profiling, unsubscribes, and closes sink (irreversible)
171182
* - `idle -> closed`: Closes sink if it was opened (irreversible)
172183
*
@@ -191,11 +202,6 @@ export class NodejsProfiler<
191202
break;
192203

193204
case 'running->idle':
194-
super.setEnabled(false);
195-
this.#performanceObserverSink.unsubscribe();
196-
// DO NOT close sink - it must remain open for potential re-enable
197-
break;
198-
199205
case 'running->closed':
200206
super.setEnabled(false);
201207
this.#performanceObserverSink.unsubscribe();
@@ -255,7 +261,7 @@ export class NodejsProfiler<
255261
...this.#performanceObserverSink.getStats(),
256262
debug: this.#debug,
257263
state: this.#state,
258-
sinkOpen: !this.#sink.isClosed(),
264+
walOpen: !this.#sink.isClosed(),
259265
};
260266
}
261267

0 commit comments

Comments
 (0)