返回 DeepSeek-Reasonix
performance-benchmark.mjs
根目录 / desktop / electron / scripts / performance-benchmark.mjs
1 // Synthetic native A/B: identical renderer work in fresh Electron processes.
2 // This measures diagnostic overhead, not the original Windows user workload.
3 import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
4 import { createHash } from "node:crypto";
5 import { join, resolve } from "node:path";
6 import { execFileSync } from "node:child_process";
7 import { performanceFixture } from "./performance-fixture.mjs";
8
9 const rows = [];
10 const modes = ["off", "monitor", "capture"];
11 const artifactDir = resolve(import.meta.dirname, "../artifacts/performance");
12 mkdirSync(artifactDir, { recursive: true });
13 writeFileSync(join(artifactDir, "overhead.json"), JSON.stringify({ status: "running", rows }));
14 const metricSnapshot = (app) => app.evaluate(({ app }) => app.getAppMetrics().map((m) => ({
15 pid: m.pid, creationTime: m.creationTime, type: m.type,
16 cpuSeconds: m.cpu.cumulativeCPUUsage ?? null,
17 cpuPercent: m.cpu.percentCPUUsage,
18 workingSetMb: m.memory?.workingSetSize === undefined ? null : m.memory.workingSetSize / 1024,
19 })));
20 const sumMemory = (metrics) => metrics.every((m) => m.workingSetMb !== null) ? metrics.reduce((sum, m) => sum + m.workingSetMb, 0) : null;
21 try {
22 for (let trial = 0; trial < 3; trial++) {
23 for (let index = 0; index < modes.length; index++) {
24 const mode = modes[(index + trial) % modes.length];
25 const fixture = await performanceFixture(mode !== "off", { benchmark: true });
26 try {
27 const { app, page, temp } = fixture;
28 const rendererBuildSha256 = createHash("sha256").update(readFileSync(join(temp, "assets/workload.js"))).digest("hex");
29 await page.evaluate(() => window.diagnosticFixture.run(1000));
30 // Measure after the real monitor's 15s startup grace, and across the
31 // native sampler's 30s tick. Short startup-only trials miss both costs.
32 await page.waitForFunction(() => performance.now() >= 16_000);
33 const before = await metricSnapshot(app);
34 const started = performance.now();
35 const [work, profile] = await Promise.all([
36 page.evaluate(() => window.diagnosticFixture.run(16_000)),
37 mode === "capture" ? page.evaluate(() => window.reasonixDesktop.native.captureRendererProfile()) : Promise.resolve(null),
38 ]);
39 const elapsedMs = performance.now() - started;
40 const after = await metricSnapshot(app);
41 if (mode === "capture" && profile.status !== "captured") throw new Error(`capture was ${profile.status}; invalid benchmark trial`);
42 const cpuSeconds = after.every((m) => m.cpuSeconds !== null) && before.every((m) => m.cpuSeconds !== null)
43 ? after.reduce((sum, m) => sum + Math.max(0, m.cpuSeconds - (before.find((b) => b.pid === m.pid && b.creationTime === m.creationTime)?.cpuSeconds ?? 0)), 0) : null;
44 const metricCost = await app.evaluate(({ app }) => {
45 const samples = [];
46 for (let i = 0; i < 30; i++) { const start = performance.now(); app.getAppMetrics(); samples.push(performance.now() - start); }
47 samples.sort((a, b) => a - b);
48 return { p95Ms: samples[Math.floor(samples.length * .95)], maxMs: samples[samples.length - 1] };
49 });
50 const row = { trial, mode, rendererBuildSha256, ...work, elapsedMs, cpuSeconds, workingSetBeforeMb: sumMemory(before), workingSetAfterMb: sumMemory(after), metricCost, profileStatus: profile?.status ?? "off" };
51 rows.push(row);
52 console.log(JSON.stringify(row));
53 } finally { await fixture.close(); }
54 }
55 }
56 if (new Set(rows.map((row) => row.rendererBuildSha256)).size !== 1) throw new Error("renderer bundles differ between benchmark modes");
57 } catch (error) {
58 writeFileSync(join(artifactDir, "overhead.json"), JSON.stringify({ status: "failed", rows, failure: String(error) }, null, 2));
59 throw error;
60 }
61 const median = (values) => values.filter((v) => v !== null).sort((a, b) => a - b)[Math.floor(values.length / 2)] ?? null;
62 const summary = modes.map((mode) => {
63 const subset = rows.filter((r) => r.mode === mode);
64 return { mode, frames: median(subset.map((r) => r.frames)), frameP95Ms: median(subset.map((r) => r.frameP95Ms)), cpuSeconds: median(subset.map((r) => r.cpuSeconds)), workingSetAfterMb: median(subset.map((r) => r.workingSetAfterMb)), metricP95Ms: median(subset.map((r) => r.metricCost.p95Ms)) };
65 });
66 writeFileSync(join(artifactDir, "overhead.json"), JSON.stringify({
67 status: "completed",
68 platform: process.platform, arch: process.arch,
69 sourceHead: execFileSync("git", ["rev-parse", "HEAD"], { encoding: "utf8", cwd: resolve(import.meta.dirname, "..") }).trim(),
70 sourceStatus: execFileSync("git", ["status", "--short", "--", "src", "scripts", "../frontend/src"], { encoding: "utf8", cwd: resolve(import.meta.dirname, "..") }).trim(),
71 scope: "Synthetic Electron workload with controlled foreground signals and background throttling disabled; three fresh-process trials per mode; heap snapshots excluded; production activity lifecycle separately tested by native smoke", rows, summary,
72 }, null, 2));
73 console.log(JSON.stringify({ summary }));
74
74 lines Plain Text