返回 DeepSeek-Reasonix
performanceReportFormat.ts
根目录 / desktop / frontend / src / lib / performanceReportFormat.ts
1 import type { PerformanceSnapshot } from "./crash";
2
3 export function fmtNumber(n: number, digits = 0): string {
4 return Number.isFinite(n) ? n.toFixed(digits) : "0";
5 }
6
7 export function fmtMb(n: number): string {
8 return `${fmtNumber(n, 1)} MB`;
9 }
10
11 export function formatPerformanceContext(snapshot: PerformanceSnapshot): string {
12 const lines = [
13 `reason: ${snapshot.reason}`,
14 `uptime: ${fmtNumber(snapshot.uptimeMs / 1000, 1)}s`,
15 `visibility: ${snapshot.visibility || "unknown"}`,
16 `focused: ${snapshot.focused ? "true" : "false"}`,
17 `online: ${snapshot.online ? "true" : "false"}`,
18 `hardware concurrency: ${snapshot.hardwareConcurrency || "unknown"}`,
19 ];
20 if (snapshot.deviceMemoryGb) lines.push(`device memory: ${snapshot.deviceMemoryGb} GB`);
21 if (snapshot.jsHeap) {
22 const pct =
23 snapshot.jsHeap.usagePercent !== undefined ? `, ${fmtNumber(snapshot.jsHeap.usagePercent)}% of limit` : "";
24 lines.push(
25 `js heap: ${fmtMb(snapshot.jsHeap.usedMb)} used, ${fmtMb(snapshot.jsHeap.totalMb)} allocated, ${fmtMb(snapshot.jsHeap.limitMb)} limit${pct}`,
26 );
27 }
28 if (snapshot.eventLoopLag) {
29 lines.push(
30 `event loop lag: current ${fmtNumber(snapshot.eventLoopLag.currentMs)}ms, max ${fmtNumber(snapshot.eventLoopLag.maxMs)}ms, avg ${fmtNumber(snapshot.eventLoopLag.avgMs)}ms over ${snapshot.eventLoopLag.samples} samples`,
31 );
32 }
33 if (snapshot.longTasks) {
34 const recent = snapshot.longTasks.recent
35 .map(
36 (t) =>
37 `${fmtNumber(t.durationMs)}ms @ ${fmtNumber(t.startMs / 1000, 1)}s${t.attribution ? ` (${t.attribution})` : ""}`,
38 )
39 .join("; ");
40 lines.push(
41 `long tasks: ${snapshot.longTasks.count} in the last 60s, max ${fmtNumber(snapshot.longTasks.maxMs)}ms, total ${fmtNumber(snapshot.longTasks.totalMs)}ms`,
42 );
43 if (recent) lines.push(`recent long tasks: ${recent}`);
44 }
45 if (snapshot.longTaskFrames?.length) {
46 lines.push("long task top frames (sampled):");
47 for (const frame of snapshot.longTaskFrames) lines.push(` ${frame.samples}x ${frame.label}`);
48 }
49 if (snapshot.cpuProfile) {
50 const profile = snapshot.cpuProfile;
51 lines.push(`CPU profile after trigger: ${profile.status}${profile.durationMs === undefined ? "" : `, ${fmtNumber(profile.durationMs)}ms`}; does not reconstruct the original long task`);
52 for (const frame of profile.frames ?? []) lines.push(` ${fmtNumber(frame.selfMs, 1)}ms / ${frame.samples} samples: ${frame.label}`);
53 }
54 if (snapshot.processes) {
55 lines.push("process samples: Electron only (Go service excluded); working sets may share pages; CPU is interval average");
56 const samples = snapshot.processes.samples;
57 for (const sample of samples) {
58 const total = sample.processes.every((p) => p.workingSetMb !== null)
59 ? fmtMb(sample.processes.reduce((sum, p) => sum + (p.workingSetMb ?? 0), 0)) : "unavailable";
60 lines.push(` ${fmtNumber(sample.ageMs)}ms ago: ${sample.processes.length} processes${sample.truncated ? " (truncated)" : ""}, summed working set ${total}`);
61 }
62 for (const growth of snapshot.processes.growth ?? []) {
63 lines.push(`sustained memory growth (not proof of a leak): PID ${growth.pid} ${growth.type}, ${growth.metric} ${fmtMb(growth.baselineMb)} → ${fmtMb(growth.currentMb)} over ${fmtNumber(growth.durationMs / 1000)}s`);
64 }
65 const latest = samples[samples.length - 1];
66 if (latest) {
67 lines.push(`latest process CPU interval: ${latest.intervalMs === null ? "baseline unavailable" : `${fmtNumber(latest.intervalMs)}ms`}`);
68 for (const p of latest.processes) {
69 lines.push(` PID ${p.pid} ${p.type}: CPU ${p.cpuPercent === null ? "unavailable" : `${fmtNumber(p.cpuPercent, 1)}%`}, working set ${p.workingSetMb === null ? "unavailable" : fmtMb(p.workingSetMb)}, private ${p.privateMb === null ? "unavailable" : fmtMb(p.privateMb)}`);
70 }
71 }
72 }
73 if (snapshot.connection) {
74 const parts = [
75 snapshot.connection.effectiveType,
76 snapshot.connection.rttMs !== undefined ? `${snapshot.connection.rttMs}ms rtt` : "",
77 snapshot.connection.downlinkMbps !== undefined ? `${snapshot.connection.downlinkMbps} Mbps` : "",
78 snapshot.connection.saveData !== undefined ? `saveData ${snapshot.connection.saveData ? "true" : "false"}` : "",
79 ].filter(Boolean);
80 if (parts.length) lines.push(`connection: ${parts.join(", ")}`);
81 }
82 const pipeline = snapshot.sessionPipeline;
83 if (pipeline?.activation) {
84 const a = pipeline.activation;
85 const parts = [`request ${a.requestId}`];
86 if (a.tabId) parts.push(`tab ${a.tabId}`);
87 if (a.ticketToStartingMs !== undefined) parts.push(`ticket→starting ${fmtNumber(a.ticketToStartingMs)}ms`);
88 if (a.startingToReadyMs !== undefined) parts.push(`starting→ready ${fmtNumber(a.startingToReadyMs)}ms`);
89 if (a.totalMs !== undefined) parts.push(`total ${fmtNumber(a.totalMs)}ms`);
90 if (a.outcome) parts.push(`outcome ${a.outcome}`);
91 if (a.failureClass) parts.push(`failure ${a.failureClass}`);
92 lines.push(`activation: ${parts.join(", ")}`);
93 }
94 if (pipeline?.history) {
95 const h = pipeline.history;
96 lines.push(
97 `history page: ${h.entries} entries, ${fmtNumber(h.inlineBytes / 1024, 1)} KiB inline, ${fmtNumber(h.durationMs)}ms, source ${h.source || "unknown"}${h.stale ? ", stale" : ""} ` +
98 `(pages ${h.pages}, stale ${h.staleCount}, index hits ${h.indexHits}, misses ${h.indexMisses})`,
99 );
100 }
101 if (pipeline?.mountedRows) {
102 lines.push(`mounted rows: ${pipeline.mountedRows.mounted} of ${pipeline.mountedRows.total}`);
103 }
104 if (pipeline?.markdownWorker) {
105 const w = pipeline.markdownWorker;
106 lines.push(
107 `markdown worker: ${w.pending} pending, ${w.completed} parsed, avg ${fmtNumber(w.avgParseMs, 1)}ms, max ${fmtNumber(w.maxParseMs)}ms` +
108 `${w.fallbackActive ? ", fallback active" : ""}${w.workerFailures > 0 ? `, ${w.workerFailures} worker failures` : ""}`,
109 );
110 }
111 if (pipeline?.transcriptCache) {
112 const c = pipeline.transcriptCache;
113 lines.push(
114 `transcript cache: ${c.residentSessions}/${c.maxResidentSessions} resident sessions, ` +
115 `bodies ${fmtMb(c.bodyBytes / 1048576)} of ${fmtMb(c.bodyBudgetBytes / 1048576)}, ` +
116 `markdown ${fmtMb(c.markdownBytes / 1048576)} of ${fmtMb(c.markdownBudgetBytes / 1048576)}, ` +
117 `evictions ${c.historyEvictions} history + ${c.markdownEvictions} markdown, ` +
118 `window ${c.residentWindowEntries} entries over ${c.windowMaxPages} pages, ${c.reclaimedPages} reclaimed`,
119 );
120 }
121 return lines.join("\n");
122 }
123
123 lines TYPESCRIPT