mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-28 22:06:49 +00:00
107 lines
3.1 KiB
JavaScript
107 lines
3.1 KiB
JavaScript
#!/usr/bin/env node
|
|
import fs from "node:fs";
|
|
import path from "node:path";
|
|
import process from "node:process";
|
|
|
|
const DEFAULT_LIMIT = 30;
|
|
|
|
function parseArgs(argv) {
|
|
const files = [];
|
|
let limit = DEFAULT_LIMIT;
|
|
for (let index = 0; index < argv.length; index += 1) {
|
|
const arg = argv[index];
|
|
if (arg === "--limit") {
|
|
const raw = argv[index + 1];
|
|
index += 1;
|
|
const parsed = Number.parseInt(raw ?? "", 10);
|
|
if (Number.isFinite(parsed) && parsed > 0) {
|
|
limit = parsed;
|
|
}
|
|
continue;
|
|
}
|
|
files.push(arg);
|
|
}
|
|
return { files, limit };
|
|
}
|
|
|
|
function formatUrl(url) {
|
|
if (!url) {
|
|
return "(native)";
|
|
}
|
|
const cwdPrefix = `${process.cwd()}${path.sep}`;
|
|
return url
|
|
.replace(/^file:\/\//u, "")
|
|
.replace(cwdPrefix, "")
|
|
.replace(/^.*\/node_modules\//u, "node_modules/")
|
|
.replace(/^.*\/dist\//u, "dist/");
|
|
}
|
|
|
|
function groupUrl(url) {
|
|
const formatted = formatUrl(url);
|
|
if (formatted.startsWith("node:")) {
|
|
return formatted.split(":").slice(0, 2).join(":");
|
|
}
|
|
if (formatted.startsWith("node_modules/")) {
|
|
return formatted.split("/").slice(0, 3).join("/");
|
|
}
|
|
if (formatted.startsWith("dist/")) {
|
|
return formatted.split("/").slice(0, 2).join("/");
|
|
}
|
|
return formatted;
|
|
}
|
|
|
|
function add(map, key, micros) {
|
|
map.set(key, (map.get(key) ?? 0) + micros);
|
|
}
|
|
|
|
function summarizeProfile(file, limit) {
|
|
const profile = JSON.parse(fs.readFileSync(file, "utf8"));
|
|
const nodes = new Map(profile.nodes.map((node) => [node.id, node]));
|
|
const samples = Array.isArray(profile.samples) ? profile.samples : [];
|
|
const deltas = Array.isArray(profile.timeDeltas) ? profile.timeDeltas : [];
|
|
const byFrame = new Map();
|
|
const byModule = new Map();
|
|
|
|
for (let index = 0; index < samples.length; index += 1) {
|
|
const node = nodes.get(samples[index]);
|
|
if (!node) {
|
|
continue;
|
|
}
|
|
const frame = node.callFrame ?? {};
|
|
const micros = deltas[index] ?? 1000;
|
|
const url = formatUrl(frame.url ?? "");
|
|
const line =
|
|
typeof frame.lineNumber === "number" && frame.lineNumber >= 0
|
|
? `:${frame.lineNumber + 1}`
|
|
: "";
|
|
const functionName = frame.functionName || "(anonymous)";
|
|
add(byFrame, `${functionName}\t${url}${line}`, micros);
|
|
add(byModule, groupUrl(frame.url ?? ""), micros);
|
|
}
|
|
|
|
const durationMs = ((profile.endTime ?? 0) - (profile.startTime ?? 0)) / 1000;
|
|
console.log(`\n${file}`);
|
|
console.log(`duration_ms: ${durationMs.toFixed(1)} samples: ${samples.length}`);
|
|
console.log("top_frames:");
|
|
for (const [key, micros] of [...byFrame.entries()]
|
|
.toSorted((left, right) => right[1] - left[1])
|
|
.slice(0, limit)) {
|
|
console.log(`${(micros / 1000).toFixed(1)}ms\t${key}`);
|
|
}
|
|
console.log("top_modules:");
|
|
for (const [key, micros] of [...byModule.entries()]
|
|
.toSorted((left, right) => right[1] - left[1])
|
|
.slice(0, limit)) {
|
|
console.log(`${(micros / 1000).toFixed(1)}ms\t${key}`);
|
|
}
|
|
}
|
|
|
|
const { files, limit } = parseArgs(process.argv.slice(2));
|
|
if (files.length === 0) {
|
|
console.error("usage: scripts/perf/summarize-cpuprofile.mjs [--limit N] <profile...>");
|
|
process.exit(2);
|
|
}
|
|
for (const file of files) {
|
|
summarizeProfile(file, limit);
|
|
}
|