Skip to content

Commit 202134d

Browse files
committed
refactor: wip
1 parent 7512bd5 commit 202134d

15 files changed

+504
-256
lines changed

packages/utils/mocks/multiprocess-profiling/utils.ts

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -45,10 +45,15 @@ export function getProfilerConfig(
4545
*/
4646
export async function createBufferedEvents(): Promise<void> {
4747
const bM1 = `buffered-mark-${process.pid}`;
48-
performance.mark(bM1, asOptions(trackEntryPayload({
49-
...getTrackConfig(),
50-
color: 'tertiary'
51-
})));
48+
performance.mark(
49+
bM1,
50+
asOptions(
51+
trackEntryPayload({
52+
...getTrackConfig(),
53+
color: 'tertiary',
54+
}),
55+
),
56+
);
5257
const intervalDelay = Math.floor(Math.random() * 50) + 25;
5358
await new Promise(resolve => setTimeout(resolve, intervalDelay));
5459
performance.measure(`buffered-${process.pid}`, {

packages/utils/mocks/omit-trace-json.ts

Lines changed: 80 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,22 @@ import {
77
frameName,
88
frameTreeNodeId,
99
} from '../src/lib/profiler/trace-file-utils.js';
10-
import type { TraceEvent, TraceEventContainer, TraceMetadata } from '../src/lib/profiler/trace-file.type';
10+
import type {
11+
TraceEvent,
12+
TraceEventContainer,
13+
TraceMetadata,
14+
} from '../src/lib/profiler/trace-file.type';
1115

1216
const BASE_TS = 1_700_000_005_000_000;
1317
const FIXED_TIME = '2026-01-28T14:29:27.995Z';
1418

1519
/* ───────────── IO ───────────── */
1620
const read = (p: string) => fs.readFile(p, 'utf8').then(s => s.trim());
17-
const parseJsonl = (s: string) => s.split('\n').filter(Boolean).map(l => JSON.parse(l));
21+
const parseJsonl = (s: string) =>
22+
s
23+
.split('\n')
24+
.filter(Boolean)
25+
.map(l => JSON.parse(l));
1826
const parseDecodeJsonl = (s: string) => parseJsonl(s).map(decodeEvent);
1927

2028
/* ───────────── Metadata ───────────── */
@@ -24,21 +32,30 @@ const normMeta = (
2432
): TraceMetadata | undefined =>
2533
m
2634
? ({
27-
...(keepGen ? m : Object.fromEntries(Object.entries(m).filter(([k]) => k !== 'generatedAt'))),
28-
startTime: FIXED_TIME,
29-
...(keepGen && { generatedAt: FIXED_TIME }),
30-
} as TraceMetadata)
35+
...(keepGen
36+
? m
37+
: Object.fromEntries(
38+
Object.entries(m).filter(([k]) => k !== 'generatedAt'),
39+
)),
40+
startTime: FIXED_TIME,
41+
...(keepGen && { generatedAt: FIXED_TIME }),
42+
} as TraceMetadata)
3143
: undefined;
3244

3345
/* ───────────── Detail ───────────── */
3446
const normalizeDetail = (d: unknown): unknown => {
3547
const o =
36-
typeof d === 'string' ? JSON.parse(d) :
37-
typeof d === 'object' && d ? d : null;
48+
typeof d === 'string'
49+
? JSON.parse(d)
50+
: typeof d === 'object' && d
51+
? d
52+
: null;
3853
const props = o?.devtools?.properties;
3954
if (!Array.isArray(props)) return d;
4055

41-
const isTransition = props.some(e => Array.isArray(e) && e[0] === 'Transition');
56+
const isTransition = props.some(
57+
e => Array.isArray(e) && e[0] === 'Transition',
58+
);
4259

4360
return {
4461
...o,
@@ -66,39 +83,66 @@ const normalizeDetail = (d: unknown): unknown => {
6683
};
6784

6885
/* ───────────── Context ───────────── */
69-
const uniq = <T>(v: (T | undefined)[]) => [...new Set(v.filter(Boolean) as T[])];
86+
const uniq = <T>(v: (T | undefined)[]) => [
87+
...new Set(v.filter(Boolean) as T[]),
88+
];
7089
const ctx = (e: TraceEvent[], base = BASE_TS) => ({
71-
pid: new Map(uniq(e.map(x => x.pid)).sort().map((v, i) => [v, 10001 + i])),
72-
tid: new Map(uniq(e.map(x => x.tid)).sort().map((v, i) => [v, i + 1])),
73-
ts: new Map(uniq(e.map(x => x.ts)).sort().map((v, i) => [v, base + i * 100])),
74-
id: new Map(uniq(e.map(x => x.id2?.local)).sort().map((v, i) => [v, `0x${(i + 1).toString(16)}`])),
90+
pid: new Map(
91+
uniq(e.map(x => x.pid))
92+
.sort()
93+
.map((v, i) => [v, 10001 + i]),
94+
),
95+
tid: new Map(
96+
uniq(e.map(x => x.tid))
97+
.sort()
98+
.map((v, i) => [v, i + 1]),
99+
),
100+
ts: new Map(
101+
uniq(e.map(x => x.ts))
102+
.sort()
103+
.map((v, i) => [v, base + i * 100]),
104+
),
105+
id: new Map(
106+
uniq(e.map(x => x.id2?.local))
107+
.sort()
108+
.map((v, i) => [v, `0x${(i + 1).toString(16)}`]),
109+
),
75110
});
76111

77112
/* ───────────── Event normalization ───────────── */
78113
const mapIf = <T, R>(v: T | undefined, m: Map<T, R>, k: string) =>
79114
v != null && m.has(v) ? { [k]: m.get(v)! } : {};
80115

81-
const normalizeEvent = (e: TraceEvent, c: ReturnType<typeof ctx>): TraceEvent => {
116+
const normalizeEvent = (
117+
e: TraceEvent,
118+
c: ReturnType<typeof ctx>,
119+
): TraceEvent => {
82120
const pid = c.pid.get(e.pid) ?? e.pid;
83121
const tid = c.tid.get(e.tid) ?? e.tid;
84122

85123
const args = e.args && {
86124
...e.args,
87-
...(e.args.detail !== undefined && { detail: normalizeDetail(e.args.detail) }),
125+
...(e.args.detail !== undefined && {
126+
detail: normalizeDetail(e.args.detail),
127+
}),
88128
...(e.args.data &&
89129
typeof e.args.data === 'object' && {
90130
data: {
91131
...(e.args.data as any),
92-
...(pid && tid && 'frameTreeNodeId' in e.args.data && {
93-
frameTreeNodeId: frameTreeNodeId(pid, tid),
94-
}),
95-
...(Array.isArray((e.args.data as any).frames) && pid && tid && {
96-
frames: (e.args.data as any).frames.map((f: any) => ({
97-
...f,
98-
processId: pid,
99-
frame: frameName(pid, tid),
100-
})),
101-
}),
132+
...(pid &&
133+
tid &&
134+
'frameTreeNodeId' in e.args.data && {
135+
frameTreeNodeId: frameTreeNodeId(pid, tid),
136+
}),
137+
...(Array.isArray((e.args.data as any).frames) &&
138+
pid &&
139+
tid && {
140+
frames: (e.args.data as any).frames.map((f: any) => ({
141+
...f,
142+
processId: pid,
143+
frame: frameName(pid, tid),
144+
})),
145+
}),
102146
},
103147
}),
104148
};
@@ -108,9 +152,10 @@ const normalizeEvent = (e: TraceEvent, c: ReturnType<typeof ctx>): TraceEvent =>
108152
...mapIf(e.pid, c.pid, 'pid'),
109153
...mapIf(e.tid, c.tid, 'tid'),
110154
...mapIf(e.ts, c.ts, 'ts'),
111-
...(e.id2?.local && c.id.has(e.id2.local) && {
112-
id2: { ...e.id2, local: c.id.get(e.id2.local)! },
113-
}),
155+
...(e.id2?.local &&
156+
c.id.has(e.id2.local) && {
157+
id2: { ...e.id2, local: c.id.get(e.id2.local)! },
158+
}),
114159
...(args && { args }),
115160
};
116161
};
@@ -133,9 +178,9 @@ export const normalizeAndFormatEvents = (
133178
typeof input === 'string'
134179
? input.trim()
135180
? normalizeTraceEvents(parseJsonl(input).map(decodeEvent), opts)
136-
.map(encodeEvent)
137-
.map(o => JSON.stringify(o))
138-
.join('\n') + (input.endsWith('\n') ? '\n' : '')
181+
.map(encodeEvent)
182+
.map(o => JSON.stringify(o))
183+
.join('\n') + (input.endsWith('\n') ? '\n' : '')
139184
: input
140185
: normalizeTraceEvents(input, opts);
141186

@@ -176,4 +221,6 @@ export const loadNormalizedTraceJson = async (
176221
export const loadNormalizedTraceJsonl = async (
177222
p: `${string}.jsonl`,
178223
): Promise<TraceEventContainer> =>
179-
createTraceFile({ traceEvents: normalizeTraceEvents(parseDecodeJsonl(await read(p))) });
224+
createTraceFile({
225+
traceEvents: normalizeTraceEvents(parseDecodeJsonl(await read(p))),
226+
});

packages/utils/src/lib/errors.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,3 +30,20 @@ export function stringifyError(
3030
}
3131
return JSON.stringify(error);
3232
}
33+
34+
/**
35+
* Extend an error with a new mamessage and keeps the original as cause.
36+
* @param error - The error to extend
37+
* @param message - The new message to add to the error
38+
* @returns A new error with the extended message and the original as cause
39+
*/
40+
export function extendError(
41+
error: unknown,
42+
message: string,
43+
{ appendMessage = false } = {},
44+
) {
45+
const errorMessage = appendMessage
46+
? `${message}\n${stringifyError(error)}`
47+
: message;
48+
return new Error(errorMessage, { cause: error });
49+
}

packages/utils/src/lib/errors.unit.test.ts

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import ansis from 'ansis';
22
import { z } from 'zod';
33
import { SchemaValidationError } from '@code-pushup/models';
4-
import { stringifyError } from './errors.js';
4+
import { extendError, stringifyError } from './errors.js';
55

66
describe('stringifyError', () => {
77
it('should use only message from plain Error instance', () => {
@@ -113,3 +113,25 @@ describe('stringifyError', () => {
113113
).toBe(`SchemaValidationError: Invalid ${ansis.bold('User')} […]`);
114114
});
115115
});
116+
117+
describe('extendError', () => {
118+
it('adds message, appends original error, and keeps cause', () => {
119+
const original = new Error('boom');
120+
121+
const extended = extendError(original, 'wrap failed', {
122+
appendMessage: true,
123+
});
124+
125+
expect(extended.message).toBe('wrap failed\nboom');
126+
expect(extended.cause).toBe(original);
127+
});
128+
129+
it('uses only the provided message by default', () => {
130+
const original = new Error('boom');
131+
132+
const extended = extendError(original, 'wrap failed');
133+
134+
expect(extended.message).toBe('wrap failed');
135+
expect(extended.cause).toBe(original);
136+
});
137+
});

packages/utils/src/lib/performance-observer.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,7 @@ export type PerformanceObserverOptions<T> = {
125125
* When true, encode failures create performance marks for debugging.
126126
*
127127
*/
128-
debug: boolean
128+
debug: boolean;
129129
};
130130

131131
/**

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

Lines changed: 26 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import fs from 'node:fs';
22
import fsPromises from 'node:fs/promises';
33
import path from 'node:path';
4+
import process from 'node:process';
45
import { fileURLToPath } from 'node:url';
56
import { afterAll, afterEach, beforeEach, expect } from 'vitest';
67
import { awaitObserverCallbackAndFlush } from '@code-pushup/test-utils';
@@ -14,7 +15,10 @@ import {
1415
asOptions,
1516
trackEntryPayload,
1617
} from '../user-timing-extensibility-api-utils.js';
17-
import type { ActionTrackEntryPayload, TrackEntryPayload } from '../user-timing-extensibility-api.type.js';
18+
import type {
19+
ActionTrackEntryPayload,
20+
TrackEntryPayload,
21+
} from '../user-timing-extensibility-api.type.js';
1822
import {
1923
PROFILER_DEBUG_ENV_VAR,
2024
PROFILER_ENABLED_ENV_VAR,
@@ -27,7 +31,6 @@ import { NodejsProfiler, type NodejsProfilerOptions } from './profiler-node.js';
2731
import { entryToTraceEvents } from './trace-file-utils.js';
2832
import type { TraceEvent } from './trace-file.type.js';
2933
import { traceEventWalFormat } from './wal-json-trace.js';
30-
import process from 'node:process';
3134

3235
describe('NodeJS Profiler Integration', () => {
3336
const traceEventEncoder: PerformanceEntryEncoder<TraceEvent> =
@@ -82,7 +85,10 @@ describe('NodeJS Profiler Integration', () => {
8285
await new Promise(resolve => setTimeout(resolve, 10));
8386

8487
expect(() =>
85-
performance.mark(`${prefix}${prefix ? ':' : ''}measure:start`, asOptions(trackEntryPayload(defaultPayload))),
88+
performance.mark(
89+
`${prefix}${prefix ? ':' : ''}measure:start`,
90+
asOptions(trackEntryPayload(defaultPayload)),
91+
),
8692
).not.toThrow();
8793

8894
const largeArray = Array.from({ length: 100_000 }, (_, i) => i);
@@ -92,7 +98,12 @@ describe('NodeJS Profiler Integration', () => {
9298
.reduce((sum, x) => sum + x, 0);
9399
expect(result).toBeGreaterThan(0);
94100
expect('sync success').toBe('sync success');
95-
expect(() => performance.mark(`${prefix}${prefix ? ':' : ''}measure:end`, asOptions(trackEntryPayload(defaultPayload)))).not.toThrow();
101+
expect(() =>
102+
performance.mark(
103+
`${prefix}${prefix ? ':' : ''}measure:end`,
104+
asOptions(trackEntryPayload(defaultPayload)),
105+
),
106+
).not.toThrow();
96107

97108
performance.measure(`${prefix}${prefix ? ':' : ''}measure`, {
98109
start: `${prefix}${prefix ? ':' : ''}measure:start`,
@@ -108,7 +119,10 @@ describe('NodeJS Profiler Integration', () => {
108119
await new Promise(resolve => setTimeout(resolve, 10));
109120

110121
expect(() =>
111-
performance.mark(`${prefix}:async-measure:start`,asOptions(trackEntryPayload(defaultPayload))),
122+
performance.mark(
123+
`${prefix}:async-measure:start`,
124+
asOptions(trackEntryPayload(defaultPayload)),
125+
),
112126
).not.toThrow();
113127
// Heavy work: More CPU-intensive operations
114128
const matrix = Array.from({ length: 1000 }, () =>
@@ -120,7 +134,12 @@ describe('NodeJS Profiler Integration', () => {
120134
await expect(Promise.resolve('async success')).resolves.toBe(
121135
'async success',
122136
);
123-
expect(() => performance.mark(`${prefix}:async-measure:end`, asOptions(trackEntryPayload(defaultPayload)))).not.toThrow();
137+
expect(() =>
138+
performance.mark(
139+
`${prefix}:async-measure:end`,
140+
asOptions(trackEntryPayload(defaultPayload)),
141+
),
142+
).not.toThrow();
124143

125144
performance.measure(`${prefix}:async-measure`, {
126145
start: `${prefix}:async-measure:start`,
@@ -134,9 +153,7 @@ describe('NodeJS Profiler Integration', () => {
134153
});
135154
}
136155

137-
async function createBasicMeasures(
138-
profiler: NodejsProfiler<TraceEvent>,
139-
) {
156+
async function createBasicMeasures(profiler: NodejsProfiler<TraceEvent>) {
140157
expect(() =>
141158
profiler.marker(`Enable profiler`, {
142159
tooltipText: 'set enable to true',
@@ -359,26 +376,6 @@ describe('NodeJS Profiler Integration', () => {
359376
profiler.close();
360377
});
361378

362-
it('should create transition markers if debugMode true', async () => {
363-
const prefix = 'debugMode-test';
364-
const measureName = prefix;
365-
const profiler = nodejsProfiler({
366-
measureName,
367-
debug: true,
368-
});
369-
370-
createBasicMeasures(profiler);
371-
await awaitObserverCallbackAndFlush(profiler);
372-
profiler.close();
373-
374-
const snapshotData = await loadNormalizedTraceJson(
375-
profiler.stats.finalFilePath as `${string}.json`,
376-
);
377-
expect(JSON.stringify(snapshotData)).toMatchFileSnapshot(
378-
`__snapshots__/${measureName}.json`,
379-
);
380-
});
381-
382379
it('should handle sharding across multiple processes', async () => {
383380
const numProcesses = 3;
384381
const startTime = performance.now();

0 commit comments

Comments
 (0)