Skip to content

Commit a8d7f3f

Browse files
committed
refactor: add exit handler to node profiler
1 parent 959dba5 commit a8d7f3f

File tree

3 files changed

+1527
-0
lines changed

3 files changed

+1527
-0
lines changed
Lines changed: 202 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,202 @@
1+
import { MockTraceEventFileSink } from '../../../mocks/sink.mock.js';
2+
import type { PerformanceEntryEncoder } from '../performance-observer.js';
3+
import { NodejsProfiler } from './profiler-node.js';
4+
5+
describe('NodeJS Profiler Integration', () => {
6+
const simpleEncoder: PerformanceEntryEncoder<string> = entry => {
7+
if (entry.entryType === 'measure') {
8+
return [`${entry.name}:${entry.duration.toFixed(2)}ms`];
9+
}
10+
return [];
11+
};
12+
13+
let mockSink: MockTraceEventFileSink;
14+
let nodejsProfiler: NodejsProfiler<string>;
15+
const originalProfilingEnv = process.env.CP_PROFILING;
16+
const originalDebugEnv = process.env.CP_PROFILER_DEBUG;
17+
18+
beforeEach(() => {
19+
performance.clearMarks();
20+
performance.clearMeasures();
21+
// eslint-disable-next-line functional/immutable-data
22+
delete process.env.CP_PROFILING;
23+
// eslint-disable-next-line functional/immutable-data
24+
delete process.env.CP_PROFILER_DEBUG;
25+
26+
mockSink = new MockTraceEventFileSink();
27+
28+
nodejsProfiler = new NodejsProfiler({
29+
prefix: 'test',
30+
track: 'test-track',
31+
sink: mockSink,
32+
encodePerfEntry: simpleEncoder,
33+
enabled: true,
34+
});
35+
});
36+
37+
afterEach(() => {
38+
if (nodejsProfiler && nodejsProfiler.state !== 'closed') {
39+
nodejsProfiler.close();
40+
}
41+
if (originalProfilingEnv === undefined) {
42+
// eslint-disable-next-line functional/immutable-data
43+
delete process.env.CP_PROFILING;
44+
} else {
45+
// eslint-disable-next-line functional/immutable-data
46+
process.env.CP_PROFILING = originalProfilingEnv;
47+
}
48+
if (originalDebugEnv === undefined) {
49+
// eslint-disable-next-line functional/immutable-data
50+
delete process.env.CP_PROFILER_DEBUG;
51+
} else {
52+
// eslint-disable-next-line functional/immutable-data
53+
process.env.CP_PROFILER_DEBUG = originalDebugEnv;
54+
}
55+
});
56+
57+
it('should initialize with sink opened when enabled', () => {
58+
expect(mockSink.isClosed()).toBeFalse();
59+
expect(nodejsProfiler.isEnabled()).toBeTrue();
60+
expect(mockSink.open).toHaveBeenCalledOnce();
61+
});
62+
63+
it('should create performance entries and write to sink', () => {
64+
expect(nodejsProfiler.measure('test-operation', () => 'success')).toBe(
65+
'success',
66+
);
67+
});
68+
69+
it('should handle async operations', async () => {
70+
await expect(
71+
nodejsProfiler.measureAsync('async-test', async () => {
72+
await new Promise(resolve => setTimeout(resolve, 1));
73+
return 'async-result';
74+
}),
75+
).resolves.toBe('async-result');
76+
});
77+
78+
it('should disable profiling and close sink', () => {
79+
nodejsProfiler.setEnabled(false);
80+
expect(nodejsProfiler.isEnabled()).toBeFalse();
81+
expect(mockSink.isClosed()).toBeTrue();
82+
expect(mockSink.close).toHaveBeenCalledOnce();
83+
84+
expect(nodejsProfiler.measure('disabled-test', () => 'success')).toBe(
85+
'success',
86+
);
87+
88+
expect(mockSink.getWrittenItems()).toHaveLength(0);
89+
});
90+
91+
it('should re-enable profiling correctly', () => {
92+
nodejsProfiler.setEnabled(false);
93+
nodejsProfiler.setEnabled(true);
94+
95+
expect(nodejsProfiler.isEnabled()).toBeTrue();
96+
expect(mockSink.isClosed()).toBeFalse();
97+
expect(mockSink.open).toHaveBeenCalledTimes(2);
98+
99+
expect(nodejsProfiler.measure('re-enabled-test', () => 42)).toBe(42);
100+
});
101+
102+
it('should support custom tracks', () => {
103+
const profilerWithTracks = new NodejsProfiler({
104+
prefix: 'api-server',
105+
track: 'HTTP',
106+
tracks: {
107+
db: { track: 'Database', color: 'secondary' },
108+
cache: { track: 'Cache', color: 'primary' },
109+
},
110+
sink: mockSink,
111+
encodePerfEntry: simpleEncoder,
112+
});
113+
114+
expect(
115+
profilerWithTracks.measure('user-lookup', () => 'user123', {
116+
track: 'cache',
117+
}),
118+
).toBe('user123');
119+
120+
profilerWithTracks.close();
121+
});
122+
123+
it('should capture buffered entries when buffered option is enabled', () => {
124+
const bufferedProfiler = new NodejsProfiler({
125+
prefix: 'buffered-test',
126+
track: 'Test',
127+
sink: mockSink,
128+
encodePerfEntry: simpleEncoder,
129+
captureBufferedEntries: true,
130+
enabled: true,
131+
});
132+
133+
const bufferedStats = bufferedProfiler.stats;
134+
expect(bufferedStats.state).toBe('running');
135+
expect(bufferedStats.walOpen).toBeTrue();
136+
expect(bufferedStats.isSubscribed).toBeTrue();
137+
expect(bufferedStats.queued).toBe(0);
138+
expect(bufferedStats.dropped).toBe(0);
139+
expect(bufferedStats.written).toBe(0);
140+
141+
bufferedProfiler.close();
142+
});
143+
144+
it('should return correct getStats with dropped and written counts', () => {
145+
const statsProfiler = new NodejsProfiler({
146+
prefix: 'stats-test',
147+
track: 'Stats',
148+
sink: mockSink,
149+
encodePerfEntry: simpleEncoder,
150+
maxQueueSize: 2,
151+
flushThreshold: 2,
152+
enabled: true,
153+
});
154+
155+
expect(statsProfiler.measure('test-op', () => 'result')).toBe('result');
156+
157+
const stats = statsProfiler.stats;
158+
expect(stats.state).toBe('running');
159+
expect(stats.walOpen).toBeTrue();
160+
expect(stats.isSubscribed).toBeTrue();
161+
expect(typeof stats.queued).toBe('number');
162+
expect(typeof stats.dropped).toBe('number');
163+
expect(typeof stats.written).toBe('number');
164+
165+
statsProfiler.close();
166+
});
167+
168+
it('should provide comprehensive queue statistics via getStats', () => {
169+
const profiler = new NodejsProfiler({
170+
prefix: 'stats-profiler',
171+
track: 'Stats',
172+
sink: mockSink,
173+
encodePerfEntry: simpleEncoder,
174+
maxQueueSize: 3,
175+
flushThreshold: 2,
176+
enabled: true,
177+
});
178+
179+
const initialStats = profiler.stats;
180+
expect(initialStats.state).toBe('running');
181+
expect(initialStats.walOpen).toBeTrue();
182+
expect(initialStats.isSubscribed).toBeTrue();
183+
expect(initialStats.queued).toBe(0);
184+
expect(initialStats.dropped).toBe(0);
185+
expect(initialStats.written).toBe(0);
186+
187+
profiler.measure('operation-1', () => 'result1');
188+
profiler.measure('operation-2', () => 'result2');
189+
profiler.flush();
190+
expect(profiler.stats.written).toBe(0);
191+
192+
profiler.setEnabled(false);
193+
194+
const finalStats = profiler.stats;
195+
expect(finalStats.state).toBe('idle');
196+
expect(finalStats.walOpen).toBeFalse();
197+
expect(finalStats.isSubscribed).toBeFalse();
198+
expect(finalStats.queued).toBe(0);
199+
200+
profiler.close();
201+
});
202+
});

0 commit comments

Comments
 (0)