fix(profile): expose sampler availability
Some checks are pending
Check / Node 22 (push) Waiting to run
Some checks are pending
Check / Node 22 (push) Waiting to run
This commit is contained in:
parent
7b5f706398
commit
e8fb9b4380
@ -58,6 +58,8 @@ export async function buildCiSummary(options = {}) {
|
||||
importLoopP95Ms: reports.importLoop?.summary?.p95WallMs ?? 0,
|
||||
importLoopMaxRssMb: reports.importLoop?.summary?.maxPeakRssMb ?? 0,
|
||||
importLoopMaxCpuMs: reports.importLoop?.summary?.maxCpuMsEstimate ?? 0,
|
||||
importLoopRssSampleCount: metricSampleCount(reports.importLoop, "rss", "maxPeakRssMb"),
|
||||
importLoopCpuSampleCount: metricSampleCount(reports.importLoop, "cpu", "maxCpuMsEstimate"),
|
||||
},
|
||||
topIssues: topIssues(reports.compatibility),
|
||||
refRegressions: (reports.refDiff?.regressions ?? []).slice(0, 20),
|
||||
@ -148,7 +150,7 @@ export function renderCiSummaryMarkdown(summary) {
|
||||
["Jiti loader candidates", summary.summary.loaderJitiCandidates],
|
||||
[
|
||||
"Import loop",
|
||||
`p50 ${summary.summary.importLoopP50Ms} ms / p95 ${summary.summary.importLoopP95Ms} ms / max RSS ${summary.summary.importLoopMaxRssMb} MB / CPU ${summary.summary.importLoopMaxCpuMs} ms`,
|
||||
`p50 ${summary.summary.importLoopP50Ms} ms / p95 ${summary.summary.importLoopP95Ms} ms / max RSS ${formatSampledMetric(summary.summary.importLoopMaxRssMb, summary.summary.importLoopRssSampleCount)} / CPU ${formatSampledMetric(summary.summary.importLoopMaxCpuMs, summary.summary.importLoopCpuSampleCount, "ms")}`,
|
||||
],
|
||||
],
|
||||
["Metric", "Value"],
|
||||
@ -221,3 +223,35 @@ function topIssues(report) {
|
||||
function markdownTable(rows, headers) {
|
||||
return renderPaddedMarkdownTable(rows, headers, { nullValue: "-" });
|
||||
}
|
||||
|
||||
function metricSampleCount(report, kind, maxMetric) {
|
||||
const summaryKey = kind === "rss" ? "rssSampleCount" : "cpuSampleCount";
|
||||
const summaryCount = report?.summary?.[summaryKey];
|
||||
if (Number.isFinite(summaryCount)) {
|
||||
return summaryCount;
|
||||
}
|
||||
const sampleCount = inferSampleCount(report?.samples, kind);
|
||||
if (sampleCount > 0) {
|
||||
return sampleCount;
|
||||
}
|
||||
return (report?.summary?.[maxMetric] ?? 0) > 0 ? 1 : 0;
|
||||
}
|
||||
|
||||
function inferSampleCount(samples = [], kind) {
|
||||
if (!Array.isArray(samples)) {
|
||||
return 0;
|
||||
}
|
||||
return samples.reduce((sum, sample) => {
|
||||
if (kind === "rss") {
|
||||
return sum + (sample.rssSampleCount ?? (sample.peakRssMb > 0 ? 1 : 0));
|
||||
}
|
||||
return sum + (sample.cpuSampleCount ?? 0);
|
||||
}, 0);
|
||||
}
|
||||
|
||||
function formatSampledMetric(value, count, unit = "MB") {
|
||||
if ((count ?? 0) <= 0) {
|
||||
return "n/a";
|
||||
}
|
||||
return `${value} ${unit}`;
|
||||
}
|
||||
|
||||
@ -30,6 +30,9 @@ export async function buildImportLoopProfile(options = {}) {
|
||||
}
|
||||
|
||||
const wallMs = samples.map((sample) => sample.wallMs).sort((left, right) => left - right);
|
||||
const rssSampleCount = samples.reduce((sum, sample) => sum + (sample.rssSampleCount ?? (sample.peakRssMb > 0 ? 1 : 0)), 0);
|
||||
const cpuSampleCount = samples.reduce((sum, sample) => sum + (sample.cpuSampleCount ?? 0), 0);
|
||||
const statSampleCount = samples.reduce((sum, sample) => sum + (sample.statSampleCount ?? 0), 0);
|
||||
return {
|
||||
generatedAt: options.generatedAt ?? defaultImportLoopProfileOptions.generatedAt,
|
||||
mode: options.mode ?? "subprocess-cold-import-loop",
|
||||
@ -40,6 +43,9 @@ export async function buildImportLoopProfile(options = {}) {
|
||||
p95WallMs: percentile(wallMs, 0.95),
|
||||
maxPeakRssMb: Math.max(0, ...samples.map((sample) => sample.peakRssMb)),
|
||||
maxCpuMsEstimate: Math.max(0, ...samples.map((sample) => sample.cpuMsEstimate)),
|
||||
statSampleCount,
|
||||
rssSampleCount,
|
||||
cpuSampleCount,
|
||||
capturedCount: samples.reduce((sum, sample) => sum + sample.capturedCount, 0),
|
||||
failCount: samples.filter((sample) => sample.exitCode !== 0 || sample.status !== "captured").length,
|
||||
},
|
||||
@ -85,7 +91,7 @@ export function renderImportLoopProfileMarkdown(report, options = {}) {
|
||||
"",
|
||||
"## Summary",
|
||||
"",
|
||||
markdownTable(Object.entries(report.summary).map(([key, value]) => [key, value]), ["Metric", "Value"]),
|
||||
markdownTable(summaryRows(report), ["Metric", "Value"]),
|
||||
"",
|
||||
"## Samples",
|
||||
"",
|
||||
@ -95,11 +101,12 @@ export function renderImportLoopProfileMarkdown(report, options = {}) {
|
||||
sample.status,
|
||||
sample.capturedCount,
|
||||
`${sample.wallMs} ms`,
|
||||
`${sample.peakRssMb} MB`,
|
||||
`${sample.cpuMsEstimate} ms`,
|
||||
formatSampledMetric(sample.peakRssMb, sample.rssSampleCount),
|
||||
formatSampledMetric(sample.cpuMsEstimate, sample.cpuSampleCount, "ms"),
|
||||
`${sample.rssSampleCount ?? 0}/${sample.cpuSampleCount ?? 0}`,
|
||||
sample.exitCode,
|
||||
]),
|
||||
["Run", "Status", "Captured", "Wall", "Peak RSS", "CPU Estimate", "Exit"],
|
||||
["Run", "Status", "Captured", "Wall", "Peak RSS", "CPU Estimate", "RSS/CPU samples", "Exit"],
|
||||
),
|
||||
].join("\n");
|
||||
}
|
||||
@ -130,10 +137,35 @@ async function runCaptureSample(options) {
|
||||
peakRssMb: profile.peakRssMb,
|
||||
peakCpuPercent: profile.peakCpuPercent,
|
||||
cpuMsEstimate: profile.cpuMsEstimate,
|
||||
statSampleCount: profile.statSampleCount,
|
||||
rssSampleCount: profile.rssSampleCount,
|
||||
cpuSampleCount: profile.cpuSampleCount,
|
||||
stderrPreview: profile.stderrPreview,
|
||||
};
|
||||
}
|
||||
|
||||
function summaryRows(report) {
|
||||
return [
|
||||
["runs", report.summary.runs],
|
||||
["p50WallMs", report.summary.p50WallMs],
|
||||
["p95WallMs", report.summary.p95WallMs],
|
||||
["maxPeakRssMb", formatSampledMetric(report.summary.maxPeakRssMb, report.summary.rssSampleCount)],
|
||||
["maxCpuMsEstimate", formatSampledMetric(report.summary.maxCpuMsEstimate, report.summary.cpuSampleCount, "ms")],
|
||||
["statSampleCount", report.summary.statSampleCount ?? 0],
|
||||
["rssSampleCount", report.summary.rssSampleCount ?? 0],
|
||||
["cpuSampleCount", report.summary.cpuSampleCount ?? 0],
|
||||
["capturedCount", report.summary.capturedCount],
|
||||
["failCount", report.summary.failCount],
|
||||
];
|
||||
}
|
||||
|
||||
function formatSampledMetric(value, count, unit = "MB") {
|
||||
if ((count ?? 0) <= 0) {
|
||||
return "n/a";
|
||||
}
|
||||
return `${value} ${unit}`;
|
||||
}
|
||||
|
||||
function buildCaptureCommand(options) {
|
||||
if (typeof options.captureCommand === "function") {
|
||||
return options.captureCommand({
|
||||
|
||||
@ -7,8 +7,12 @@ export async function runProfiledProcess(options) {
|
||||
let firstRssKb = 0;
|
||||
let peakRssKb = 0;
|
||||
let peakCpuPercent = 0;
|
||||
let statSampleCount = 0;
|
||||
let rssSampleCount = 0;
|
||||
let cpuSampleCount = 0;
|
||||
const cpuSamples = [];
|
||||
let pollInFlight = false;
|
||||
const pendingStats = new Set();
|
||||
|
||||
const child = spawn(options.command, options.args ?? [], {
|
||||
cwd: options.cwd,
|
||||
@ -21,27 +25,43 @@ export async function runProfiledProcess(options) {
|
||||
child.stderr?.on("data", (chunk) => stderr.push(chunk));
|
||||
|
||||
const recordStats = (stats) => {
|
||||
if (stats.rssKb > 0 && firstRssKb === 0) {
|
||||
if (stats.rssAvailable || stats.cpuAvailable) {
|
||||
statSampleCount += 1;
|
||||
}
|
||||
if (stats.rssAvailable) {
|
||||
rssSampleCount += 1;
|
||||
}
|
||||
if (stats.cpuAvailable) {
|
||||
cpuSampleCount += 1;
|
||||
}
|
||||
if (stats.rssAvailable && stats.rssKb > 0 && firstRssKb === 0) {
|
||||
firstRssKb = stats.rssKb;
|
||||
}
|
||||
peakRssKb = Math.max(peakRssKb, stats.rssKb);
|
||||
peakCpuPercent = Math.max(peakCpuPercent, stats.cpuPercent);
|
||||
if (stats.cpuPercent > 0) {
|
||||
if (stats.rssAvailable) {
|
||||
peakRssKb = Math.max(peakRssKb, stats.rssKb);
|
||||
}
|
||||
if (stats.cpuAvailable) {
|
||||
peakCpuPercent = Math.max(peakCpuPercent, stats.cpuPercent);
|
||||
cpuSamples.push(stats.cpuPercent);
|
||||
}
|
||||
};
|
||||
|
||||
const poll = setInterval(() => {
|
||||
const sampleStats = () => {
|
||||
if (pollInFlight) {
|
||||
return;
|
||||
}
|
||||
pollInFlight = true;
|
||||
readProcessStats(child.pid)
|
||||
const pending = readProcessStats(child.pid)
|
||||
.then(recordStats)
|
||||
.finally(() => {
|
||||
pollInFlight = false;
|
||||
pendingStats.delete(pending);
|
||||
});
|
||||
}, options.pollMs ?? 100);
|
||||
pendingStats.add(pending);
|
||||
};
|
||||
|
||||
sampleStats();
|
||||
const poll = setInterval(sampleStats, options.pollMs ?? 25);
|
||||
|
||||
const exitCode = await new Promise((resolve, reject) => {
|
||||
child.on("error", (error) => {
|
||||
@ -51,16 +71,10 @@ export async function runProfiledProcess(options) {
|
||||
child.on("exit", (code) => resolve(code ?? 1));
|
||||
});
|
||||
clearInterval(poll);
|
||||
await Promise.allSettled([...pendingStats]);
|
||||
|
||||
const finalStats = await readProcessStats(child.pid);
|
||||
if (finalStats.rssKb > 0 && firstRssKb === 0) {
|
||||
firstRssKb = finalStats.rssKb;
|
||||
}
|
||||
peakRssKb = Math.max(peakRssKb, finalStats.rssKb);
|
||||
peakCpuPercent = Math.max(peakCpuPercent, finalStats.cpuPercent);
|
||||
if (finalStats.cpuPercent > 0) {
|
||||
cpuSamples.push(finalStats.cpuPercent);
|
||||
}
|
||||
recordStats(finalStats);
|
||||
|
||||
const wallMs = Math.round(performance.now() - start);
|
||||
const averageCpuPercent =
|
||||
@ -79,6 +93,9 @@ export async function runProfiledProcess(options) {
|
||||
peakCpuPercent: Math.round(peakCpuPercent * 10) / 10,
|
||||
cpuMsEstimate: Math.round((wallMs * cpuPercentForEstimate) / 100),
|
||||
harnessHeapDeltaMb: Math.round((heapUsedMb() - heapStartMb) * 10) / 10,
|
||||
statSampleCount,
|
||||
rssSampleCount,
|
||||
cpuSampleCount,
|
||||
exitCode,
|
||||
stdoutPreview: previewLines(stdout),
|
||||
stderrPreview: previewLines(stderr),
|
||||
@ -87,7 +104,7 @@ export async function runProfiledProcess(options) {
|
||||
|
||||
async function readProcessStats(pid) {
|
||||
if (!pid || process.platform === "win32") {
|
||||
return { rssKb: 0, cpuPercent: 0 };
|
||||
return { rssAvailable: false, rssKb: 0, cpuAvailable: false, cpuPercent: 0 };
|
||||
}
|
||||
return new Promise((resolve) => {
|
||||
const ps = spawn("ps", ["-o", "rss=", "-o", "%cpu=", "-p", String(pid)], {
|
||||
@ -95,14 +112,18 @@ async function readProcessStats(pid) {
|
||||
});
|
||||
const chunks = [];
|
||||
ps.stdout.on("data", (chunk) => chunks.push(chunk));
|
||||
ps.on("error", () => resolve({ rssKb: 0, cpuPercent: 0 }));
|
||||
ps.on("error", () => resolve({ rssAvailable: false, rssKb: 0, cpuAvailable: false, cpuPercent: 0 }));
|
||||
ps.on("exit", () => {
|
||||
const [rssRaw, cpuRaw] = Buffer.concat(chunks).toString("utf8").trim().split(/\s+/);
|
||||
const rssKb = Number.parseInt(rssRaw, 10);
|
||||
const cpuPercent = Number.parseFloat(cpuRaw);
|
||||
const rssAvailable = Number.isFinite(rssKb);
|
||||
const cpuAvailable = Number.isFinite(cpuPercent);
|
||||
resolve({
|
||||
rssKb: Number.isFinite(rssKb) ? rssKb : 0,
|
||||
cpuPercent: Number.isFinite(cpuPercent) ? cpuPercent : 0,
|
||||
rssAvailable,
|
||||
rssKb: rssAvailable ? rssKb : 0,
|
||||
cpuAvailable,
|
||||
cpuPercent: cpuAvailable ? cpuPercent : 0,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@ -46,8 +46,8 @@ export async function buildRuntimeProfile(options = {}) {
|
||||
os: process.platform,
|
||||
arch: process.arch,
|
||||
node: process.version,
|
||||
rssSampler: process.platform === "win32" ? "unavailable" : "ps",
|
||||
cpuSampler: process.platform === "win32" ? "unavailable" : "ps-percent",
|
||||
rssSampler: process.platform === "win32" ? "unavailable" : "ps-immediate-25ms",
|
||||
cpuSampler: process.platform === "win32" ? "unavailable" : "ps-percent-immediate-25ms",
|
||||
},
|
||||
summary: summarizeProfile(commands),
|
||||
groups: summarizeCommandGroups(commands),
|
||||
@ -65,8 +65,8 @@ export function validateRuntimeProfile(profile) {
|
||||
errors.push(`${command.id}: missing wall time`);
|
||||
}
|
||||
}
|
||||
if (profile.platform?.rssSampler !== "unavailable" && profile.commands.every((command) => command.peakRssMb.max <= 0)) {
|
||||
errors.push("all commands are missing peak RSS");
|
||||
if (profile.platform?.rssSampler !== "unavailable" && profile.commands.every((command) => !hasRssSample(command))) {
|
||||
errors.push("all commands are missing peak RSS samples");
|
||||
}
|
||||
return errors;
|
||||
}
|
||||
@ -98,10 +98,17 @@ export function renderRuntimeProfileMarkdown(profile, options = {}) {
|
||||
[
|
||||
["Commands", profile.summary.commandCount],
|
||||
["P50 wall time", `${profile.summary.p50WallMs} ms`],
|
||||
["P95 wall time", `${profile.summary.p95WallMs} ms`],
|
||||
["Max peak RSS", `${profile.summary.maxPeakRssMb} MB`],
|
||||
["Max RSS delta", `${profile.summary.maxRssDeltaMb} MB`],
|
||||
["Max CPU estimate", `${profile.summary.maxCpuMsEstimate} ms`],
|
||||
["Command P95 wall time", `${profile.summary.p95WallMs} ms`],
|
||||
["Wall time basis", profile.summary.wallTimeBasis ?? "command-median-p95"],
|
||||
["Profile samples", profile.summary.sampleCount ?? sampleCount(profile.commands)],
|
||||
["RSS samples", profile.summary.rssSampleCount ?? rssSampleCount(profile.commands)],
|
||||
["CPU samples", profile.summary.cpuSampleCount ?? cpuSampleCount(profile.commands)],
|
||||
["Max peak RSS", formatSampledMetric(profile.summary.maxPeakRssMb, profile.summary.rssSampleCount ?? rssSampleCount(profile.commands))],
|
||||
["Max RSS delta", formatSampledMetric(profile.summary.maxRssDeltaMb, profile.summary.rssSampleCount ?? rssSampleCount(profile.commands))],
|
||||
[
|
||||
"Max CPU estimate",
|
||||
formatSampledMetric(profile.summary.maxCpuMsEstimate, profile.summary.cpuSampleCount ?? cpuSampleCount(profile.commands), "ms"),
|
||||
],
|
||||
["Max harness heap delta", `${profile.summary.maxHarnessHeapDeltaMb} MB`],
|
||||
],
|
||||
["Metric", "Value"],
|
||||
@ -129,13 +136,14 @@ export function renderRuntimeProfileMarkdown(profile, options = {}) {
|
||||
command.label,
|
||||
`${command.wallMs.median} ms`,
|
||||
`${command.wallMs.max} ms`,
|
||||
`${command.peakRssMb.max} MB`,
|
||||
`${command.rssDeltaMb.max} MB`,
|
||||
`${command.cpuMsEstimate.max} ms`,
|
||||
formatSampledMetric(command.peakRssMb.max, command.rssSampleCount),
|
||||
formatSampledMetric(command.rssDeltaMb.max, command.rssSampleCount),
|
||||
formatSampledMetric(command.cpuMsEstimate.max, command.cpuSampleCount, "ms"),
|
||||
`${command.harnessHeapDeltaMb.max} MB`,
|
||||
`${command.rssSampleCount ?? 0}/${command.cpuSampleCount ?? 0}`,
|
||||
command.exitCodes.join(", "),
|
||||
]),
|
||||
["ID", "Label", "Median wall", "Max wall", "Max peak RSS", "Max RSS delta", "CPU estimate", "Heap delta", "Exit codes"],
|
||||
["ID", "Label", "Median wall", "Max wall", "Max peak RSS", "Max RSS delta", "CPU estimate", "Heap delta", "RSS/CPU samples", "Exit codes"],
|
||||
),
|
||||
"",
|
||||
"## Category Rollups",
|
||||
@ -146,11 +154,12 @@ export function renderRuntimeProfileMarkdown(profile, options = {}) {
|
||||
group.commandCount,
|
||||
`${group.p50WallMs} ms`,
|
||||
`${group.p95WallMs} ms`,
|
||||
`${group.maxPeakRssMb} MB`,
|
||||
`${group.maxCpuMsEstimate} ms`,
|
||||
formatSampledMetric(group.maxPeakRssMb, group.rssSampleCount),
|
||||
formatSampledMetric(group.maxCpuMsEstimate, group.cpuSampleCount, "ms"),
|
||||
`${group.rssSampleCount ?? 0}/${group.cpuSampleCount ?? 0}`,
|
||||
group.commands.join(", "),
|
||||
]),
|
||||
["Category", "Commands", "P50 wall", "P95 wall", "Max peak RSS", "CPU estimate", "Command IDs"],
|
||||
["Category", "Commands", "P50 wall", "P95 wall", "Max peak RSS", "CPU estimate", "RSS/CPU samples", "Command IDs"],
|
||||
),
|
||||
].join("\n");
|
||||
}
|
||||
@ -189,8 +198,15 @@ function summarizeProfile(commands) {
|
||||
const maxRssDeltaMb = Math.max(0, ...commands.map((command) => command.rssDeltaMb.max));
|
||||
const maxCpuMsEstimate = Math.max(0, ...commands.map((command) => command.cpuMsEstimate.max));
|
||||
const maxHarnessHeapDeltaMb = Math.max(0, ...commands.map((command) => command.harnessHeapDeltaMb.max));
|
||||
const totalSampleCount = sampleCount(commands);
|
||||
const totalRssSampleCount = rssSampleCount(commands);
|
||||
const totalCpuSampleCount = cpuSampleCount(commands);
|
||||
return {
|
||||
commandCount: commands.length,
|
||||
sampleCount: totalSampleCount,
|
||||
rssSampleCount: totalRssSampleCount,
|
||||
cpuSampleCount: totalCpuSampleCount,
|
||||
wallTimeBasis: "command-median-p95",
|
||||
p50WallMs: percentile(wallTimes, 0.5),
|
||||
p95WallMs: percentile(wallTimes, 0.95),
|
||||
maxPeakRssMb,
|
||||
@ -206,6 +222,12 @@ function summarizeCommand(command, samples) {
|
||||
const rssDeltaMb = samples.map((sample) => sample.rssDeltaMb).sort((left, right) => left - right);
|
||||
const peakCpuPercent = samples.map((sample) => sample.peakCpuPercent).sort((left, right) => left - right);
|
||||
const cpuMsEstimate = samples.map((sample) => sample.cpuMsEstimate).sort((left, right) => left - right);
|
||||
const statSampleCount = samples.reduce((sum, sample) => sum + (sample.statSampleCount ?? 0), 0);
|
||||
const rssSampleTotal = samples.reduce(
|
||||
(sum, sample) => sum + (sample.rssSampleCount ?? (sample.peakRssMb > 0 ? 1 : 0)),
|
||||
0,
|
||||
);
|
||||
const cpuSampleTotal = samples.reduce((sum, sample) => sum + (sample.cpuSampleCount ?? 0), 0);
|
||||
const harnessHeapDeltaMb = samples
|
||||
.map((sample) => sample.harnessHeapDeltaMb)
|
||||
.sort((left, right) => left - right);
|
||||
@ -222,6 +244,9 @@ function summarizeCommand(command, samples) {
|
||||
peakCpuPercent: summarizeNumbers(peakCpuPercent),
|
||||
cpuMsEstimate: summarizeNumbers(cpuMsEstimate),
|
||||
harnessHeapDeltaMb: summarizeNumbers(harnessHeapDeltaMb),
|
||||
statSampleCount,
|
||||
rssSampleCount: rssSampleTotal,
|
||||
cpuSampleCount: cpuSampleTotal,
|
||||
exitCodes: [...new Set(samples.map((sample) => sample.exitCode))].sort(),
|
||||
};
|
||||
}
|
||||
@ -244,6 +269,8 @@ function summarizeCommandGroups(commands) {
|
||||
const cpuMs = categoryCommands
|
||||
.flatMap((command) => command.samples.map((sample) => sample.cpuMsEstimate))
|
||||
.sort((left, right) => left - right);
|
||||
const groupRssSampleCount = rssSampleCount(categoryCommands);
|
||||
const groupCpuSampleCount = cpuSampleCount(categoryCommands);
|
||||
return {
|
||||
category,
|
||||
commandCount: categoryCommands.length,
|
||||
@ -251,11 +278,36 @@ function summarizeCommandGroups(commands) {
|
||||
p95WallMs: percentile(wallTimes, 0.95),
|
||||
maxPeakRssMb: peakRss.at(-1) ?? 0,
|
||||
maxCpuMsEstimate: cpuMs.at(-1) ?? 0,
|
||||
rssSampleCount: groupRssSampleCount,
|
||||
cpuSampleCount: groupCpuSampleCount,
|
||||
commands: categoryCommands.map((command) => command.id),
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
function hasRssSample(command) {
|
||||
return (command.rssSampleCount ?? (command.peakRssMb?.max > 0 ? 1 : 0)) > 0;
|
||||
}
|
||||
|
||||
function sampleCount(commands) {
|
||||
return commands.reduce((sum, command) => sum + (command.samples?.length ?? 0), 0);
|
||||
}
|
||||
|
||||
function rssSampleCount(commands) {
|
||||
return commands.reduce((sum, command) => sum + (command.rssSampleCount ?? (command.peakRssMb?.max > 0 ? 1 : 0)), 0);
|
||||
}
|
||||
|
||||
function cpuSampleCount(commands) {
|
||||
return commands.reduce((sum, command) => sum + (command.cpuSampleCount ?? 0), 0);
|
||||
}
|
||||
|
||||
function formatSampledMetric(value, count, unit = "MB") {
|
||||
if ((count ?? 0) <= 0) {
|
||||
return "n/a";
|
||||
}
|
||||
return `${value} ${unit}`;
|
||||
}
|
||||
|
||||
function summarizeNumbers(values) {
|
||||
return {
|
||||
min: values[0],
|
||||
|
||||
@ -97,6 +97,8 @@ test("ci summary rolls up compatibility, policy, ref diff, and profile findings"
|
||||
p95WallMs: 75,
|
||||
maxPeakRssMb: 40,
|
||||
maxCpuMsEstimate: 30,
|
||||
rssSampleCount: 2,
|
||||
cpuSampleCount: 2,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
Loading…
Reference in New Issue
Block a user