

















@@ -0,0 +1,106 @@
1+#!/usr/bin/env node
2+import fs from "node:fs";
3+import path from "node:path";
4+import process from "node:process";
5+6+const DEFAULT_LIMIT = 30;
7+8+function parseArgs(argv) {
9+const files = [];
10+let limit = DEFAULT_LIMIT;
11+for (let index = 0; index < argv.length; index += 1) {
12+const arg = argv[index];
13+if (arg === "--limit") {
14+const raw = argv[index + 1];
15+index += 1;
16+const parsed = Number.parseInt(raw ?? "", 10);
17+if (Number.isFinite(parsed) && parsed > 0) {
18+limit = parsed;
19+}
20+continue;
21+}
22+files.push(arg);
23+}
24+return { files, limit };
25+}
26+27+function formatUrl(url) {
28+if (!url) {
29+return "(native)";
30+}
31+const cwdPrefix = `${process.cwd()}${path.sep}`;
32+return url
33+.replace(/^file:\/\//u, "")
34+.replace(cwdPrefix, "")
35+.replace(/^.*\/node_modules\//u, "node_modules/")
36+.replace(/^.*\/dist\//u, "dist/");
37+}
38+39+function groupUrl(url) {
40+const formatted = formatUrl(url);
41+if (formatted.startsWith("node:")) {
42+return formatted.split(":").slice(0, 2).join(":");
43+}
44+if (formatted.startsWith("node_modules/")) {
45+return formatted.split("/").slice(0, 3).join("/");
46+}
47+if (formatted.startsWith("dist/")) {
48+return formatted.split("/").slice(0, 2).join("/");
49+}
50+return formatted;
51+}
52+53+function add(map, key, micros) {
54+map.set(key, (map.get(key) ?? 0) + micros);
55+}
56+57+function summarizeProfile(file, limit) {
58+const profile = JSON.parse(fs.readFileSync(file, "utf8"));
59+const nodes = new Map(profile.nodes.map((node) => [node.id, node]));
60+const samples = Array.isArray(profile.samples) ? profile.samples : [];
61+const deltas = Array.isArray(profile.timeDeltas) ? profile.timeDeltas : [];
62+const byFrame = new Map();
63+const byModule = new Map();
64+65+for (let index = 0; index < samples.length; index += 1) {
66+const node = nodes.get(samples[index]);
67+if (!node) {
68+continue;
69+}
70+const frame = node.callFrame ?? {};
71+const micros = deltas[index] ?? 1000;
72+const url = formatUrl(frame.url ?? "");
73+const line =
74+typeof frame.lineNumber === "number" && frame.lineNumber >= 0
75+ ? `:${frame.lineNumber + 1}`
76+ : "";
77+const functionName = frame.functionName || "(anonymous)";
78+add(byFrame, `${functionName}\t${url}${line}`, micros);
79+add(byModule, groupUrl(frame.url ?? ""), micros);
80+}
81+82+const durationMs = ((profile.endTime ?? 0) - (profile.startTime ?? 0)) / 1000;
83+console.log(`\n${file}`);
84+console.log(`duration_ms: ${durationMs.toFixed(1)} samples: ${samples.length}`);
85+console.log("top_frames:");
86+for (const [key, micros] of [...byFrame.entries()]
87+.sort((left, right) => right[1] - left[1])
88+.slice(0, limit)) {
89+console.log(`${(micros / 1000).toFixed(1)}ms\t${key}`);
90+}
91+console.log("top_modules:");
92+for (const [key, micros] of [...byModule.entries()]
93+.sort((left, right) => right[1] - left[1])
94+.slice(0, limit)) {
95+console.log(`${(micros / 1000).toFixed(1)}ms\t${key}`);
96+}
97+}
98+99+const { files, limit } = parseArgs(process.argv.slice(2));
100+if (files.length === 0) {
101+console.error("usage: scripts/perf/summarize-cpuprofile.mjs [--limit N] <profile...>");
102+process.exit(2);
103+}
104+for (const file of files) {
105+summarizeProfile(file, limit);
106+}
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。