返回 DeepSeek-Reasonix
processDiagnostics.ts
根目录 / desktop / electron / src / main / processDiagnostics.ts
1 import type { ProcessMetric } from "electron";
2
3 interface ProcessSample {
4 pid: number; type: string; creationTime: number;
5 cpuPercent: number | null; workingSetMb: number | null; privateMb: number | null;
6 }
7 interface Sample {
8 atMs: number; intervalMs: number | null; truncated: boolean; processes: ProcessSample[];
9 }
10 export interface MemoryGrowth {
11 pid: number; type: string; metric: "private" | "working-set";
12 baselineMb: number; currentMb: number; durationMs: number;
13 }
14
15 export function detectMemoryGrowth(samples: readonly Sample[]): MemoryGrowth[] {
16 if (samples.length < 5) return [];
17 const last = samples[samples.length - 1];
18 if (last.atMs - samples[0].atMs < 120_000) return [];
19 const growth: MemoryGrowth[] = [];
20 for (const process of last.processes) {
21 if (!Number.isFinite(process.creationTime)) continue;
22 const rows = samples.map((s) => s.processes.find((p) => p.pid === process.pid && p.creationTime === process.creationTime));
23 // Require one continuous process identity and a settled two-reading baseline.
24 if (rows.some((row) => !row)) continue;
25 const metric = rows.every((row) => row!.privateMb !== null) ? "private" : "working-set";
26 const values = rows.map((row) => metric === "private" ? row!.privateMb : row!.workingSetMb);
27 if (values.some((v) => v === null)) continue;
28 const baselineMb = Math.max(values[0]!, values[1]!);
29 const threshold = baselineMb + Math.max(256, baselineMb * 0.5);
30 if (!values.slice(-3).every((value) => value! >= threshold)) continue;
31 growth.push({ pid: process.pid, type: process.type, metric, baselineMb, currentMb: values[values.length - 1]!, durationMs: last.atMs - samples[0].atMs });
32 }
33 return growth.sort((a, b) => (b.currentMb - b.baselineMb) - (a.currentMb - a.baselineMb)).slice(0, 3);
34 }
35
36 // Numeric process metrics only. This does not include the Go service and must
37 // never be described as a leak detector or the app's exclusive physical memory.
38 export class ProcessDiagnostics {
39 private samples: Sample[] = [];
40 private previousAt: number | undefined;
41 private previousProcesses = new Set<string>();
42 constructor(
43 private readonly read: () => ProcessMetric[],
44 private readonly now = () => performance.now(),
45 private readonly foreground = () => true,
46 ) {}
47
48 sample(): void {
49 const atMs = this.now();
50 const interval = this.foreground() ? 30_000 : 60_000;
51 if (this.previousAt !== undefined && atMs - this.previousAt < interval) return;
52 try {
53 const metrics = this.read();
54 const intervalMs = this.previousAt === undefined ? null : atMs - this.previousAt;
55 const finite = (n: number | undefined) => typeof n === "number" && Number.isFinite(n) && n >= 0 ? n : null;
56 const mb = (n: number | undefined) => { const value = finite(n); return value === null ? null : value / 1024; };
57 const types = new Set(["Browser", "Tab", "GPU", "Utility", "Zygote", "Sandbox helper", "Pepper Plugin", "Pepper Plugin Broker"]);
58 const processes = metrics.slice(0, 128).map((metric) => ({
59 pid: metric.pid,
60 type: types.has(metric.type) ? metric.type : "Other",
61 creationTime: metric.creationTime,
62 cpuPercent: intervalMs === null || !this.previousProcesses.has(`${metric.pid}:${metric.creationTime}`) ? null : finite(metric.cpu?.percentCPUUsage),
63 workingSetMb: mb(metric.memory?.workingSetSize),
64 privateMb: mb(metric.memory?.privateBytes),
65 }));
66 this.previousAt = atMs;
67 this.previousProcesses = new Set(processes.map((p) => `${p.pid}:${p.creationTime}`));
68 this.samples = [...this.samples.filter((s) => atMs - s.atMs <= 300_000), { atMs, intervalMs, truncated: metrics.length > 128, processes }].slice(-12);
69 } catch { /* A failed diagnostic sample must never affect the app. */ }
70 }
71
72 snapshot() {
73 this.sample();
74 const now = this.now();
75 const samples = this.samples.filter((s) => now - s.atMs <= 300_000);
76 return {
77 scope: "electron" as const,
78 growth: detectMemoryGrowth(samples),
79 samples: samples.map(({ atMs, ...sample }) => ({ ageMs: now - atMs, ...sample })),
80 };
81 }
82 }
83
83 lines TYPESCRIPT