Skip to content

Commit 02b9da8

Browse files
committed
refactor: wip
1 parent 6387ccc commit 02b9da8

18 files changed

Lines changed: 191 additions & 703 deletions

packages/utils/src/lib/profiler/__snapshots__/comprehensive-stats-trace-events.jsonl

Lines changed: 0 additions & 8 deletions
This file was deleted.

packages/utils/src/lib/profiler/__snapshots__/custom-tracks-trace-events.jsonl

Lines changed: 0 additions & 4 deletions
This file was deleted.

packages/utils/src/lib/profiler/__snapshots__/profiler.int.test.async-operations.json

Lines changed: 0 additions & 13 deletions
This file was deleted.

packages/utils/src/lib/profiler/__snapshots__/sharded-path-trace-events.jsonl

Lines changed: 0 additions & 4 deletions
This file was deleted.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{"traceEvents":[{"cat":"devtools.timeline","ph":"i","name":"TracingStartedInBrowser","pid":16094,"tid":1,"ts":1769589228526155,"args":{"data":{"frameTreeNodeId":1609401,"frames":[{"frame":"FRAME0P16094T1","isInPrimaryMainFrame":true,"isOutermostMainFrame":true,"name":"","processId":16094,"url":"generated-trace"}],"persistentIds":true}}},{"cat":"devtools.timeline","ph":"X","name":"[trace padding start]","dur":20000,"pid":16094,"tid":1,"ts":1769589228526155,"args":{}},{"cat":"blink.user_timing","ph":"i","name":"write-test:test-operation:start","pid":16094,"tid":1,"ts":1769589229526155,"args":{"detail":{"devtools":{"track":"Test","dataType":"track-entry"}}}},{"cat":"blink.user_timing","ph":"b","name":"write-test:test-operation","id2":{"local":"0x8"},"pid":16094,"tid":1,"ts":1769589229526156,"args":{"data":{"detail":{"devtools":{"track":"Test","dataType":"track-entry"}}}}},{"cat":"blink.user_timing","ph":"e","name":"write-test:test-operation","id2":{"local":"0x8"},"pid":16094,"tid":1,"ts":1769589229526190,"args":{"data":{"detail":{"devtools":{"track":"Test","dataType":"track-entry"}}}}},{"cat":"blink.user_timing","ph":"i","name":"write-test:test-operation:end","pid":16094,"tid":1,"ts":1769589229526191,"args":{"detail":{"devtools":{"track":"Test","dataType":"track-entry"}}}},{"cat":"devtools.timeline","ph":"X","name":"[trace padding end]","dur":20000,"pid":16094,"tid":1,"ts":1769589230526191,"args":{}}],"displayTimeUnit":"ms","metadata":{"source":"DevTools","startTime":"2026-01-28T08:33:49.538Z","hardwareConcurrency":1,"dataOrigin":"TraceEvents","generatedAt":"2026-01-28T08:33:49.538Z"}}

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

Lines changed: 114 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,44 @@
1-
import fs from 'node:fs';
1+
import { basename } from 'memfs/lib/node-to-fsa/util';
2+
import fsPromises from 'node:fs/promises';
23
import path from 'node:path';
34
import {
45
awaitObserverCallbackAndFlush,
5-
omitTraceJson,
6+
loadAndOmitTraceJson,
67
} from '@code-pushup/test-utils';
78
import type { PerformanceEntryEncoder } from '../performance-observer.js';
8-
import { WAL_ID_PATTERNS } from '../process-id.js';
9+
import { getUniqueInstanceId } from '../process-id.js';
10+
import { ShardedWal } from '../wal-sharded.js';
11+
import { SHARDED_WAL_COORDINATOR_ID_ENV_VAR } from './constants.js';
912
import { NodejsProfiler } from './profiler-node.js';
1013
import { entryToTraceEvents } from './trace-file-utils.js';
1114
import type { UserTimingTraceEvent } from './trace-file.type.js';
15+
import { traceEventWalFormat } from './wal-json-trace';
1216

1317
describe('NodeJS Profiler Integration', () => {
1418
const traceEventEncoder: PerformanceEntryEncoder<UserTimingTraceEvent> =
1519
entryToTraceEvents;
1620

1721
let nodejsProfiler: NodejsProfiler<UserTimingTraceEvent>;
1822

19-
beforeEach(() => {
23+
beforeEach(async () => {
2024
performance.clearMarks();
2125
performance.clearMeasures();
2226
vi.stubEnv('CP_PROFILING', undefined!);
2327
vi.stubEnv('DEBUG', undefined!);
2428

2529
// Clean up trace files from previous test runs
2630
const traceFilesDir = path.join(process.cwd(), 'tmp', 'int', 'utils');
27-
// eslint-disable-next-line n/no-sync
28-
if (fs.existsSync(traceFilesDir)) {
29-
// eslint-disable-next-line n/no-sync
30-
const files = fs.readdirSync(traceFilesDir);
31+
try {
32+
await fsPromises.access(traceFilesDir);
33+
const files = await fsPromises.readdir(traceFilesDir);
3134
// eslint-disable-next-line functional/no-loop-statements
3235
for (const file of files) {
3336
if (file.endsWith('.json') || file.endsWith('.jsonl')) {
34-
// eslint-disable-next-line n/no-sync
35-
fs.unlinkSync(path.join(traceFilesDir, file));
37+
await fsPromises.unlink(path.join(traceFilesDir, file));
3638
}
3739
}
40+
} catch {
41+
// Directory doesn't exist, skip cleanup
3842
}
3943

4044
nodejsProfiler = new NodejsProfiler({
@@ -44,6 +48,7 @@ describe('NodeJS Profiler Integration', () => {
4448
encodePerfEntry: traceEventEncoder,
4549
},
4650
filename: path.join(process.cwd(), 'tmp', 'int', 'utils', 'trace.json'),
51+
measureName: 'test-profiler',
4752
enabled: true,
4853
});
4954
});
@@ -58,7 +63,7 @@ describe('NodeJS Profiler Integration', () => {
5863

5964
it('should initialize with sink opened when enabled', () => {
6065
expect(nodejsProfiler.isEnabled()).toBeTrue();
61-
expect(nodejsProfiler.stats.walOpen).toBeTrue();
66+
expect(nodejsProfiler.stats.shardOpen).toBeTrue();
6267
});
6368

6469
it('should create performance entries and write to sink', () => {
@@ -79,7 +84,7 @@ describe('NodeJS Profiler Integration', () => {
7984
it('should disable profiling and close sink', () => {
8085
nodejsProfiler.setEnabled(false);
8186
expect(nodejsProfiler.isEnabled()).toBeFalse();
82-
expect(nodejsProfiler.stats.walOpen).toBeFalse();
87+
expect(nodejsProfiler.stats.shardOpen).toBeFalse();
8388

8489
expect(nodejsProfiler.measure('disabled-test', () => 'success')).toBe(
8590
'success',
@@ -88,12 +93,12 @@ describe('NodeJS Profiler Integration', () => {
8893

8994
it('should re-enable profiling correctly', () => {
9095
nodejsProfiler.setEnabled(false);
91-
expect(nodejsProfiler.stats.walOpen).toBeFalse();
96+
expect(nodejsProfiler.stats.shardOpen).toBeFalse();
9297

9398
nodejsProfiler.setEnabled(true);
9499

95100
expect(nodejsProfiler.isEnabled()).toBeTrue();
96-
expect(nodejsProfiler.stats.walOpen).toBeTrue();
101+
expect(nodejsProfiler.stats.shardOpen).toBeTrue();
97102

98103
expect(nodejsProfiler.measure('re-enabled-test', () => 42)).toBe(42);
99104
});
@@ -117,6 +122,7 @@ describe('NodeJS Profiler Integration', () => {
117122
encodePerfEntry: traceEventEncoder,
118123
},
119124
filename: traceTracksFile,
125+
measureName: 'custom-tracks',
120126
enabled: true,
121127
});
122128

@@ -131,10 +137,28 @@ describe('NodeJS Profiler Integration', () => {
131137
await awaitObserverCallbackAndFlush(profilerWithTracks);
132138
profilerWithTracks.close();
133139

134-
// eslint-disable-next-line n/no-sync
135-
const content = fs.readFileSync(traceTracksFile, 'utf8');
136-
const normalizedContent = omitTraceJson(content);
137-
await expect(normalizedContent).toMatchInlineSnapshot();
140+
// When measureName is provided, files are written to tmp/profiles/{measureName}/
141+
// even when filename is specified. Find the actual file in that directory.
142+
const profilesDir = path.join(
143+
process.cwd(),
144+
'tmp',
145+
'profiles',
146+
'custom-tracks',
147+
);
148+
const files = await fsPromises.readdir(profilesDir);
149+
const shardFile = files.find(
150+
f => f.endsWith('.log') || f.endsWith('.jsonl'),
151+
);
152+
expect(shardFile).toBeDefined();
153+
const actualFilePath = path.join(profilesDir, shardFile!);
154+
const normalizedContent = await loadAndOmitTraceJson(actualFilePath);
155+
await expect(normalizedContent).toMatchInlineSnapshot(`
156+
"{"cat":"blink.user_timing","ph":"i","name":"api-server:user-lookup:start","pid":10001,"tid":1,"ts":1700000005000000,"args":{"detail":{"devtools":{"track":"cache","dataType":"track-entry"}}}}
157+
{"cat":"blink.user_timing","ph":"b","name":"api-server:user-lookup","id2":{"local":"0x1"},"pid":10001,"tid":1,"ts":1700000005000001,"args":{"data":{"detail":{"devtools":{"track":"cache","dataType":"track-entry"}}}}}
158+
{"cat":"blink.user_timing","ph":"e","name":"api-server:user-lookup","id2":{"local":"0x1"},"pid":10001,"tid":1,"ts":1700000005000002,"args":{"data":{"detail":{"devtools":{"track":"cache","dataType":"track-entry"}}}}}
159+
{"cat":"blink.user_timing","ph":"i","name":"api-server:user-lookup:end","pid":10001,"tid":1,"ts":1700000005000003,"args":{"detail":{"devtools":{"track":"cache","dataType":"track-entry"}}}}
160+
"
161+
`);
138162
});
139163

140164
it('should capture buffered entries when buffered option is enabled', () => {
@@ -152,12 +176,13 @@ describe('NodeJS Profiler Integration', () => {
152176
'utils',
153177
'trace-buffered.json',
154178
),
179+
measureName: 'buffered-test',
155180
enabled: true,
156181
});
157182

158183
const bufferedStats = bufferedProfiler.stats;
159-
expect(bufferedStats.state).toBe('running');
160-
expect(bufferedStats.walOpen).toBeTrue();
184+
expect(bufferedStats.profilerState).toBe('running');
185+
expect(bufferedStats.shardOpen).toBeTrue();
161186
expect(bufferedStats.isSubscribed).toBeTrue();
162187
expect(bufferedStats.queued).toBe(0);
163188
expect(bufferedStats.dropped).toBe(0);
@@ -182,14 +207,15 @@ describe('NodeJS Profiler Integration', () => {
182207
'utils',
183208
'trace-stats.json',
184209
),
210+
measureName: 'stats-test',
185211
enabled: true,
186212
});
187213

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

190216
const stats = statsProfiler.stats;
191-
expect(stats.state).toBe('running');
192-
expect(stats.walOpen).toBeTrue();
217+
expect(stats.profilerState).toBe('running');
218+
expect(stats.shardOpen).toBeTrue();
193219
expect(stats.isSubscribed).toBeTrue();
194220
expect(typeof stats.queued).toBe('number');
195221
expect(typeof stats.dropped).toBe('number');
@@ -215,12 +241,13 @@ describe('NodeJS Profiler Integration', () => {
215241
maxQueueSize: 3,
216242
flushThreshold: 2,
217243
filename: traceStatsFile,
244+
measureName: 'stats-comprehensive',
218245
enabled: true,
219246
});
220247

221248
const initialStats = profiler.stats;
222-
expect(initialStats.state).toBe('running');
223-
expect(initialStats.walOpen).toBeTrue();
249+
expect(initialStats.profilerState).toBe('running');
250+
expect(initialStats.shardOpen).toBeTrue();
224251
expect(initialStats.isSubscribed).toBeTrue();
225252
expect(initialStats.queued).toBe(0);
226253
expect(initialStats.dropped).toBe(0);
@@ -236,30 +263,40 @@ describe('NodeJS Profiler Integration', () => {
236263
profiler.setEnabled(false);
237264

238265
const finalStats = profiler.stats;
239-
expect(finalStats.state).toBe('idle');
240-
expect(finalStats.walOpen).toBeFalse();
266+
expect(finalStats.profilerState).toBe('idle');
267+
expect(finalStats.shardOpen).toBeFalse();
241268
expect(finalStats.isSubscribed).toBeFalse();
242269
expect(finalStats.queued).toBe(0);
243270

244271
profiler.flush();
245272
profiler.close();
246273

247-
// eslint-disable-next-line n/no-sync
248-
const content = fs.readFileSync(traceStatsFile, 'utf8');
249-
const normalizedContent = omitTraceJson(content);
250-
await expect(normalizedContent).toMatchFileSnapshot(
251-
'__snapshots__/comprehensive-stats-trace-events.jsonl',
274+
// When measureName is provided, files are written to tmp/profiles/{measureName}/
275+
// even when filename is specified. Find the actual file in that directory.
276+
const profilesDir = path.join(
277+
process.cwd(),
278+
'tmp',
279+
'profiles',
280+
'stats-comprehensive',
281+
);
282+
const files = await fsPromises.readdir(profilesDir);
283+
const shardFile = files.find(
284+
f => f.endsWith('.log') || f.endsWith('.jsonl'),
252285
);
286+
expect(shardFile).toBeDefined();
253287
});
254288

255289
describe('sharded path structure', () => {
256-
it('should create sharded path structure when filename is not provided', () => {
290+
it('should create sharded path structure when filename is not provided', async () => {
257291
const profiler = new NodejsProfiler({
258292
prefix: 'sharded-test',
259293
track: 'Test',
260294
format: {
261295
encodePerfEntry: traceEventEncoder,
296+
baseName: 'trace',
297+
walExtension: '.jsonl',
262298
},
299+
measureName: 'sharded-test',
263300
enabled: true,
264301
});
265302

@@ -271,61 +308,88 @@ describe('NodeJS Profiler Integration', () => {
271308
const groupIdDir = pathParts.at(-2);
272309
const fileName = pathParts.at(-1);
273310

274-
expect(groupIdDir).toMatch(WAL_ID_PATTERNS.GROUP_ID);
275-
expect(fileName).toMatch(/^trace\.\d{8}-\d{6}-\d{3}(?:\.\d+){3}\.jsonl$/);
311+
// When measureName is provided, it's used as the groupId (folder name)
312+
expect(groupIdDir).toBe('sharded-test');
313+
// Filename format: baseName.timeId.pid.threadId.counter.extension
314+
expect(fileName).toMatch(
315+
/^trace\.\d{8}-\d{6}-\d{3}\.\d+\.\d+\.\d+\.jsonl$/,
316+
);
276317

277318
const groupIdDirPath = path.dirname(filePath);
278-
// eslint-disable-next-line n/no-sync
279-
expect(fs.existsSync(groupIdDirPath)).toBeTrue();
319+
await expect(fsPromises.access(groupIdDirPath)).resolves.not.toThrow();
280320

281321
profiler.close();
282322
});
283323

284-
it('should create correct folder structure for sharded paths', () => {
324+
it('should create correct folder structure for sharded paths', async () => {
285325
const profiler = new NodejsProfiler({
286326
prefix: 'folder-test',
287327
track: 'Test',
288328
format: {
289329
encodePerfEntry: traceEventEncoder,
290330
},
331+
measureName: 'folder-test',
291332
enabled: true,
292333
});
293334

294335
const filePath = profiler.filePath;
295336
const dirPath = path.dirname(filePath);
296337
const groupId = path.basename(dirPath);
297338

298-
expect(groupId).toMatch(WAL_ID_PATTERNS.GROUP_ID);
299-
// eslint-disable-next-line n/no-sync
300-
expect(fs.existsSync(dirPath)).toBeTrue();
301-
// eslint-disable-next-line n/no-sync
302-
expect(fs.statSync(dirPath).isDirectory()).toBeTrue();
339+
// When measureName is provided, it's used as the groupId (folder name)
340+
expect(groupId).toBe('folder-test');
341+
await expect(fsPromises.access(dirPath)).resolves.not.toThrow();
342+
const stat = await fsPromises.stat(dirPath);
343+
expect(stat.isDirectory()).toBeTrue();
303344

304345
profiler.close();
305346
});
306347

307-
it('should write trace events to sharded path file', async () => {
348+
it('should write trace events to .jsonl and .json', async () => {
349+
// Clean up any existing files from previous test runs
350+
const measureName = 'write-test';
308351
const profiler = new NodejsProfiler({
309-
prefix: 'write-test',
310352
track: 'Test',
311353
format: {
312354
encodePerfEntry: traceEventEncoder,
355+
baseName: 'trace',
356+
walExtension: '.jsonl',
357+
finalExtension: '.json',
313358
},
359+
measureName,
314360
enabled: true,
315361
});
316362

363+
// Set this instance as the coordinator by setting the env var to match its ID
364+
// The ShardedWal instance ID is generated during construction, so we need to
365+
// manually finalize since the coordinator check happens at construction time
317366
profiler.measure('test-operation', () => 'result');
318367

319368
await awaitObserverCallbackAndFlush(profiler);
369+
profiler.flush();
370+
371+
expect(profiler.stats.shardPath).toBe('1s2');
372+
/*await expect(loadAndOmitTraceJson(profiler.stats.shardPath)).resolves.toMatchFileSnapshot(
373+
`__snapshots__/${path.basename(profiler.stats.shardPath)}`,
374+
);*/
375+
320376
profiler.close();
321377

322-
const filePath = profiler.filePath;
323-
// eslint-disable-next-line n/no-sync
324-
const content = fs.readFileSync(filePath, 'utf8');
325-
const normalizedContent = omitTraceJson(content);
326-
await expect(normalizedContent).toMatchFileSnapshot(
327-
'__snapshots__/sharded-path-trace-events.jsonl',
378+
// Verify the final file exists and matches snapshot
379+
/*const finalFilePath = profiler.stats.finalFilePath;
380+
await expect(loadAndOmitTraceJson(finalFilePath)).resolves.toMatchFileSnapshot(
381+
`__snapshots__/${path.basename(finalFilePath)}`,
328382
);
383+
384+
// Restore original coordinator ID and instance count
385+
if (originalCoordinatorId) {
386+
// eslint-disable-next-line functional/immutable-data
387+
process.env[SHARDED_WAL_COORDINATOR_ID_ENV_VAR] = originalCoordinatorId;
388+
} else {
389+
// eslint-disable-next-line functional/immutable-data
390+
delete process.env[SHARDED_WAL_COORDINATOR_ID_ENV_VAR];
391+
}
392+
ShardedWal.instanceCount = originalCount;*/
329393
});
330394
});
331395
});

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

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -290,11 +290,15 @@ export class NodejsProfiler<
290290

291291
/** @returns Queue statistics and profiling state for monitoring */
292292
get stats() {
293+
const { state: sharderState, ...sharderStats } = this.#sharder.getStats();
293294
return {
294-
...this.#performanceObserverSink.getStats(),
295-
state: this.#state,
296-
walOpen: !this.#shard.isClosed(),
295+
profilerState: this.#state,
297296
debug: this.isDebugMode(),
297+
sharderState,
298+
...sharderStats,
299+
shardOpen: !this.#shard.isClosed(),
300+
shardPath: this.#shard.getPath(),
301+
...this.#performanceObserverSink.getStats(),
298302
};
299303
}
300304

0 commit comments

Comments
 (0)