Skip to content

Commit a86caac

Browse files
author
John Doe
committed
refactor: add trace event utils
1 parent 7d3681c commit a86caac

File tree

4 files changed

+1022
-0
lines changed

4 files changed

+1022
-0
lines changed
Lines changed: 208 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,208 @@
1+
import os from 'node:os';
2+
import {
3+
type PerformanceMark,
4+
type PerformanceMeasure,
5+
performance,
6+
} from 'node:perf_hooks';
7+
import { threadId } from 'node:worker_threads';
8+
import { defaultClock } from './clock-epoch';
9+
import type {
10+
BeginEvent,
11+
CompleteEvent,
12+
EndEvent,
13+
InstantEvent,
14+
InstantEventArgs,
15+
SpanEvent,
16+
SpanEventArgs,
17+
StartTracingEvent,
18+
TraceEvent,
19+
TraceEventContainer,
20+
TraceFile,
21+
} from './trace-file.type.js';
22+
23+
export const entryToTraceTimestamp = (
24+
entry: PerformanceEntry,
25+
asEnd = false,
26+
): number =>
27+
defaultClock.fromPerfMs(
28+
entry.startTime +
29+
(entry.entryType === 'measure' && asEnd ? entry.duration : 0),
30+
);
31+
32+
export const nextId2 = (() => {
33+
let i = 1;
34+
return () => ({ local: `0x${i++}` });
35+
})();
36+
37+
const defaults = (opt?: { pid?: number; tid?: number; ts?: number }) => ({
38+
pid: opt?.pid ?? process.pid,
39+
tid: opt?.tid ?? threadId,
40+
ts: opt?.ts ?? defaultClock.fromPerfMs(performance.now()),
41+
});
42+
43+
export const frameTreeNodeId = (pid: number, tid: number) =>
44+
Number.parseInt(`${pid}0${tid}`, 10);
45+
export const frameName = (pid: number, tid: number) => `FRAME0P${pid}T${tid}`;
46+
47+
export const getInstantEvent = (opt: {
48+
name: string;
49+
ts?: number;
50+
pid?: number;
51+
tid?: number;
52+
args?: InstantEventArgs;
53+
}): InstantEvent => ({
54+
cat: 'blink.user_timing',
55+
ph: 'i',
56+
s: 't',
57+
name: opt.name,
58+
...defaults(opt),
59+
args: opt.args ?? {},
60+
});
61+
62+
export const getStartTracing = (opt: {
63+
url: string;
64+
ts?: number;
65+
pid?: number;
66+
tid?: number;
67+
}): StartTracingEvent => {
68+
const { pid, tid, ts } = defaults(opt);
69+
const id = frameTreeNodeId(pid, tid);
70+
71+
return {
72+
cat: 'devtools.timeline',
73+
ph: 'i',
74+
s: 't',
75+
name: 'TracingStartedInBrowser',
76+
pid,
77+
tid,
78+
ts,
79+
args: {
80+
data: {
81+
frameTreeNodeId: id,
82+
frames: [
83+
{
84+
frame: frameName(pid, tid),
85+
isInPrimaryMainFrame: true,
86+
isOutermostMainFrame: true,
87+
name: '',
88+
processId: pid,
89+
url: opt.url,
90+
},
91+
],
92+
persistentIds: true,
93+
},
94+
},
95+
};
96+
};
97+
98+
export const getCompleteEvent = (opt: {
99+
name: string;
100+
dur: number;
101+
ts?: number;
102+
pid?: number;
103+
tid?: number;
104+
}): CompleteEvent => ({
105+
cat: 'devtools.timeline',
106+
ph: 'X',
107+
name: opt.name,
108+
dur: opt.dur,
109+
...defaults(opt),
110+
args: {},
111+
});
112+
113+
type SpanOpt = {
114+
name: string;
115+
id2: { local: string };
116+
ts?: number;
117+
pid?: number;
118+
tid?: number;
119+
args?: SpanEventArgs;
120+
};
121+
122+
export function getSpanEvent(ph: 'b', opt: SpanOpt): BeginEvent;
123+
export function getSpanEvent(ph: 'e', opt: SpanOpt): EndEvent;
124+
export function getSpanEvent(ph: 'b' | 'e', opt: SpanOpt): SpanEvent {
125+
return {
126+
cat: 'blink.user_timing',
127+
ph,
128+
s: 't',
129+
name: opt.name,
130+
id2: opt.id2,
131+
...defaults(opt),
132+
args: opt.args?.data?.detail
133+
? { data: { detail: opt.args.data.detail } }
134+
: {},
135+
} as SpanEvent;
136+
}
137+
138+
export const getSpan = (opt: {
139+
name: string;
140+
tsB: number;
141+
tsE: number;
142+
id2?: { local: string };
143+
pid?: number;
144+
tid?: number;
145+
args?: SpanEventArgs;
146+
tsMarkerPadding?: number;
147+
}): [BeginEvent, EndEvent] => {
148+
// tsMarkerPadding is here to make the measure slightly smaller so the markers align perfectly.
149+
// Otherwise, the marker is visible at the start of the measure below the frame
150+
// No padding Padding
151+
// spans: ======== |======|
152+
// marks: | |
153+
const pad = opt.tsMarkerPadding ?? 1;
154+
const id2 = opt.id2 ?? nextId2();
155+
156+
return [
157+
getSpanEvent('b', {
158+
...opt,
159+
id2,
160+
ts: opt.tsB + pad,
161+
name: opt.name,
162+
args: opt.args,
163+
}),
164+
getSpanEvent('e', {
165+
...opt,
166+
id2,
167+
ts: opt.tsE - pad,
168+
name: opt.name,
169+
args: opt.args,
170+
}),
171+
];
172+
};
173+
174+
export const markToInstantEvent = (
175+
entry: PerformanceMark,
176+
opt?: { name?: string; pid?: number; tid?: number },
177+
): InstantEvent =>
178+
getInstantEvent({
179+
...opt,
180+
name: opt?.name ?? entry.name,
181+
ts: defaultClock.fromEntryStartTimeMs(entry.startTime),
182+
args: entry.detail ? { detail: entry.detail } : {},
183+
});
184+
185+
export const measureToSpanEvents = (
186+
entry: PerformanceMeasure,
187+
opt?: { name?: string; pid?: number; tid?: number },
188+
): [BeginEvent, EndEvent] =>
189+
getSpan({
190+
...opt,
191+
name: opt?.name ?? entry.name,
192+
tsB: entryToTraceTimestamp(entry),
193+
tsE: entryToTraceTimestamp(entry, true),
194+
args: entry.detail ? { data: { detail: entry.detail } } : {},
195+
});
196+
197+
export const getTraceFile = (opt: {
198+
traceEvents: TraceEvent[];
199+
startTime?: string;
200+
}): TraceEventContainer => ({
201+
traceEvents: opt.traceEvents,
202+
displayTimeUnit: 'ms',
203+
metadata: {
204+
source: 'Node.js UserTiming',
205+
startTime: opt.startTime ?? new Date().toISOString(),
206+
hardwareConcurrency: os.cpus().length,
207+
},
208+
});

0 commit comments

Comments
 (0)