devtools-tracing 1.5.0 → 1.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/cli.ts +29 -5
- package/dist/cli.js +105 -9
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -31,7 +31,7 @@ npx devtools-tracing inp trace.json.gz
|
|
|
31
31
|
npx devtools-tracing stats trace.json.gz
|
|
32
32
|
|
|
33
33
|
# Source map symbolication (writes a .symbolicated.json.gz alongside the input)
|
|
34
|
-
npx devtools-tracing sourcemap trace.json.gz
|
|
34
|
+
npx devtools-tracing sourcemap trace.json.gz -H "Cookie: $cookie" -H "User-Agent: $ua"
|
|
35
35
|
```
|
|
36
36
|
|
|
37
37
|
See the [`commands/`](./commands) directory for full source.
|
package/cli.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { run as cssSelectors } from './commands/selector-stats.js';
|
|
2
2
|
import { run as inp } from './commands/inp.js';
|
|
3
|
-
import { run as sourcemap } from './commands/sourcemap.js';
|
|
3
|
+
import { run as sourcemap, type SourcemapOptions } from './commands/sourcemap.js';
|
|
4
4
|
import { run as stats } from './commands/stats.js';
|
|
5
5
|
|
|
6
|
-
const commands: Record<string, (tracePath: string) => Promise<void>> = {
|
|
6
|
+
const commands: Record<string, (tracePath: string, ...args: any[]) => Promise<void>> = {
|
|
7
7
|
'selector-stats': cssSelectors,
|
|
8
8
|
inp,
|
|
9
9
|
sourcemap,
|
|
@@ -15,12 +15,30 @@ function printUsage() {
|
|
|
15
15
|
console.log('\nCommands:');
|
|
16
16
|
console.log(' selector-stats Top CSS selectors from selector stats and invalidation tracking');
|
|
17
17
|
console.log(' inp Extract INP (Interaction to Next Paint) breakdown');
|
|
18
|
-
console.log(' sourcemap Symbolicate a trace using source maps');
|
|
18
|
+
console.log(' sourcemap Symbolicate a trace using source maps [-H "Header: value"]');
|
|
19
19
|
console.log(' stats Generate timeline category statistics');
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
+
function parseHeaders(args: string[]): Record<string, string> {
|
|
23
|
+
const headers: Record<string, string> = {};
|
|
24
|
+
for (let i = 0; i < args.length; i++) {
|
|
25
|
+
if (args[i] === '-H' && i + 1 < args.length) {
|
|
26
|
+
const value = args[++i];
|
|
27
|
+
const colonIdx = value.indexOf(':');
|
|
28
|
+
if (colonIdx === -1) {
|
|
29
|
+
console.error(`Invalid header (missing ':'): ${value}`);
|
|
30
|
+
process.exit(1);
|
|
31
|
+
}
|
|
32
|
+
headers[value.slice(0, colonIdx).trim()] = value.slice(colonIdx + 1).trim();
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return headers;
|
|
36
|
+
}
|
|
37
|
+
|
|
22
38
|
async function main() {
|
|
23
|
-
const
|
|
39
|
+
const args = process.argv.slice(2);
|
|
40
|
+
const command = args[0];
|
|
41
|
+
const tracePath = args[1];
|
|
24
42
|
|
|
25
43
|
if (!command || command === '--help' || command === '-h') {
|
|
26
44
|
printUsage();
|
|
@@ -40,7 +58,13 @@ async function main() {
|
|
|
40
58
|
process.exit(1);
|
|
41
59
|
}
|
|
42
60
|
|
|
43
|
-
|
|
61
|
+
if (command === 'sourcemap') {
|
|
62
|
+
const headers = parseHeaders(args.slice(2));
|
|
63
|
+
const options: SourcemapOptions = Object.keys(headers).length > 0 ? { headers } : {};
|
|
64
|
+
await sourcemap(tracePath, options);
|
|
65
|
+
} else {
|
|
66
|
+
await run(tracePath);
|
|
67
|
+
}
|
|
44
68
|
}
|
|
45
69
|
|
|
46
70
|
main();
|
package/dist/cli.js
CHANGED
|
@@ -176,8 +176,9 @@ async function run2(tracePath) {
|
|
|
176
176
|
var fs3 = __toESM(require("node:fs"));
|
|
177
177
|
var zlib3 = __toESM(require("node:zlib"));
|
|
178
178
|
var import__3 = require("../");
|
|
179
|
-
async function run3(tracePath) {
|
|
179
|
+
async function run3(tracePath, options) {
|
|
180
180
|
(0, import__3.initDevToolsTracing)();
|
|
181
|
+
const extraHeaders = options?.headers;
|
|
181
182
|
const fileData = fs3.readFileSync(tracePath);
|
|
182
183
|
const decompressedData = tracePath.endsWith(".gz") ? zlib3.gunzipSync(fileData) : fileData;
|
|
183
184
|
const traceData = JSON.parse(
|
|
@@ -185,9 +186,10 @@ async function run3(tracePath) {
|
|
|
185
186
|
);
|
|
186
187
|
const traceModel = import__3.Trace.TraceModel.Model.createWithAllHandlers();
|
|
187
188
|
const resolveSourceMap = (0, import__3.createSourceMapResolver)({
|
|
188
|
-
fetch: verboseFetch
|
|
189
|
+
fetch: (url) => verboseFetch(url, extraHeaders)
|
|
189
190
|
});
|
|
190
|
-
|
|
191
|
+
const preprocessedEvents = fixupElectronTraceEvents(traceData.traceEvents);
|
|
192
|
+
await traceModel.parse(preprocessedEvents, {
|
|
191
193
|
isCPUProfile: false,
|
|
192
194
|
isFreshRecording: false,
|
|
193
195
|
metadata: traceData.metadata,
|
|
@@ -238,7 +240,7 @@ async function run3(tracePath) {
|
|
|
238
240
|
console.log(
|
|
239
241
|
`Symbolicated ${result.symbolicatedFrames} frames across ${result.symbolicatedEvents} events`
|
|
240
242
|
);
|
|
241
|
-
const outPath = tracePath.replace(/(\.(json|json\.gz))$/i, ".symbolicated$1");
|
|
243
|
+
const outPath = tracePath.replace(/(\.(json|trace|json\.gz))$/i, ".symbolicated$1");
|
|
242
244
|
if (outPath === tracePath) {
|
|
243
245
|
console.error(
|
|
244
246
|
"Could not determine output path (expected .json or .json.gz extension)"
|
|
@@ -253,9 +255,80 @@ async function run3(tracePath) {
|
|
|
253
255
|
}
|
|
254
256
|
console.log(`Wrote symbolicated trace to ${outPath}`);
|
|
255
257
|
}
|
|
256
|
-
|
|
258
|
+
var { isTracingStartedInBrowser, isFunctionCall, isRundownScript, isProcessName } = import__3.Trace.Types.Events;
|
|
259
|
+
function fixupElectronTraceEvents(events) {
|
|
260
|
+
const hasFrameData = events.some(
|
|
261
|
+
(e) => isTracingStartedInBrowser(e) && e.args.data?.frames?.length
|
|
262
|
+
);
|
|
263
|
+
if (hasFrameData) {
|
|
264
|
+
return events;
|
|
265
|
+
}
|
|
266
|
+
const rundownCats = /* @__PURE__ */ new Set([
|
|
267
|
+
"disabled-by-default-devtools.v8-source-rundown",
|
|
268
|
+
"disabled-by-default-devtools.v8-source-rundown-sources"
|
|
269
|
+
]);
|
|
270
|
+
const isolateToFrame = /* @__PURE__ */ new Map();
|
|
271
|
+
const frameToPid = /* @__PURE__ */ new Map();
|
|
272
|
+
for (const event of events) {
|
|
273
|
+
if (!isFunctionCall(event)) continue;
|
|
274
|
+
const data = event.args?.data;
|
|
275
|
+
if (!data?.isolate || !data?.frame) continue;
|
|
276
|
+
isolateToFrame.set(String(data.isolate), data.frame);
|
|
277
|
+
frameToPid.set(data.frame, event.pid);
|
|
278
|
+
}
|
|
279
|
+
const frameToUrl = /* @__PURE__ */ new Map();
|
|
280
|
+
for (const event of events) {
|
|
281
|
+
if (!isRundownScript(event)) continue;
|
|
282
|
+
const data = event.args.data;
|
|
283
|
+
const url = data.url ?? "";
|
|
284
|
+
const isolate = String(data.isolate ?? "");
|
|
285
|
+
const frame = isolateToFrame.get(isolate);
|
|
286
|
+
if (!frame || !url || !url.startsWith("http")) continue;
|
|
287
|
+
const existing = frameToUrl.get(frame);
|
|
288
|
+
const isDocument = !url.match(/\.(js|cjs|mjs|wasm)(\?|$)/i);
|
|
289
|
+
const existingIsDocument = existing && !existing.match(/\.(js|cjs|mjs|wasm)(\?|$)/i);
|
|
290
|
+
if (!existing || isDocument && !existingIsDocument) {
|
|
291
|
+
frameToUrl.set(frame, url);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
const browserPid = events.find(
|
|
295
|
+
(e) => isProcessName(e) && e.args?.name === "Browser"
|
|
296
|
+
)?.pid;
|
|
297
|
+
const syntheticEvents = [];
|
|
298
|
+
for (const [frame, pid] of frameToPid) {
|
|
299
|
+
const url = frameToUrl.get(frame) ?? "";
|
|
300
|
+
syntheticEvents.push({
|
|
301
|
+
cat: "disabled-by-default-devtools.timeline",
|
|
302
|
+
name: "FrameCommittedInBrowser",
|
|
303
|
+
ph: import__3.Trace.Types.Events.Phase.INSTANT,
|
|
304
|
+
pid: browserPid ?? pid,
|
|
305
|
+
tid: 0,
|
|
306
|
+
ts: 0,
|
|
307
|
+
s: import__3.Trace.Types.Events.Scope.GLOBAL,
|
|
308
|
+
args: {
|
|
309
|
+
data: {
|
|
310
|
+
frame,
|
|
311
|
+
name: "",
|
|
312
|
+
processId: pid,
|
|
313
|
+
url
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
});
|
|
317
|
+
}
|
|
318
|
+
const regular = [];
|
|
319
|
+
const rundown = [];
|
|
320
|
+
for (const event of events) {
|
|
321
|
+
if (rundownCats.has(event.cat)) {
|
|
322
|
+
rundown.push(event);
|
|
323
|
+
} else {
|
|
324
|
+
regular.push(event);
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
return [...syntheticEvents, ...regular, ...rundown];
|
|
328
|
+
}
|
|
329
|
+
async function verboseFetch(url, headers) {
|
|
257
330
|
console.log(`[sourcemap] fetching ${url}`);
|
|
258
|
-
const response = await fetch(url);
|
|
331
|
+
const response = await fetch(url, headers ? { headers } : void 0);
|
|
259
332
|
if (!response.ok) {
|
|
260
333
|
console.warn(
|
|
261
334
|
`[sourcemap] ${url} -> ${response.status} ${response.statusText}`
|
|
@@ -323,11 +396,28 @@ function printUsage() {
|
|
|
323
396
|
console.log("\nCommands:");
|
|
324
397
|
console.log(" selector-stats Top CSS selectors from selector stats and invalidation tracking");
|
|
325
398
|
console.log(" inp Extract INP (Interaction to Next Paint) breakdown");
|
|
326
|
-
console.log(
|
|
399
|
+
console.log(' sourcemap Symbolicate a trace using source maps [-H "Header: value"]');
|
|
327
400
|
console.log(" stats Generate timeline category statistics");
|
|
328
401
|
}
|
|
402
|
+
function parseHeaders(args) {
|
|
403
|
+
const headers = {};
|
|
404
|
+
for (let i = 0; i < args.length; i++) {
|
|
405
|
+
if (args[i] === "-H" && i + 1 < args.length) {
|
|
406
|
+
const value = args[++i];
|
|
407
|
+
const colonIdx = value.indexOf(":");
|
|
408
|
+
if (colonIdx === -1) {
|
|
409
|
+
console.error(`Invalid header (missing ':'): ${value}`);
|
|
410
|
+
process.exit(1);
|
|
411
|
+
}
|
|
412
|
+
headers[value.slice(0, colonIdx).trim()] = value.slice(colonIdx + 1).trim();
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
return headers;
|
|
416
|
+
}
|
|
329
417
|
async function main() {
|
|
330
|
-
const
|
|
418
|
+
const args = process.argv.slice(2);
|
|
419
|
+
const command = args[0];
|
|
420
|
+
const tracePath = args[1];
|
|
331
421
|
if (!command || command === "--help" || command === "-h") {
|
|
332
422
|
printUsage();
|
|
333
423
|
process.exit(0);
|
|
@@ -343,6 +433,12 @@ async function main() {
|
|
|
343
433
|
console.error(`Usage: devtools-tracing ${command} <trace-file>`);
|
|
344
434
|
process.exit(1);
|
|
345
435
|
}
|
|
346
|
-
|
|
436
|
+
if (command === "sourcemap") {
|
|
437
|
+
const headers = parseHeaders(args.slice(2));
|
|
438
|
+
const options = Object.keys(headers).length > 0 ? { headers } : {};
|
|
439
|
+
await run3(tracePath, options);
|
|
440
|
+
} else {
|
|
441
|
+
await run5(tracePath);
|
|
442
|
+
}
|
|
347
443
|
}
|
|
348
444
|
main();
|