返回 DeepSeek-Reasonix
performanceHost.ts
根目录 / desktop / electron / src / main / performanceHost.ts
1 import type { BrowserWindow, Dialog } from "electron";
2 import { analyseInWorker } from "./profileAnalysisHost.js";
3 import { RendererDiagnostics } from "./rendererDiagnostics.js";
4
5 type HeapResult = { status: "saved" | "cancelled" | "busy" | "unavailable" | "failed" };
6 export function createPerformanceHost(deps: {
7 window(): BrowserWindow | null;
8 dialog: Pick<Dialog, "showMessageBox" | "showSaveDialog">;
9 workerPath: string;
10 locale(): string;
11 /** Controlled native fixtures can pin activity without changing production policy. */
12 isForeground?(): boolean;
13 }) {
14 const isForeground = deps.isForeground ?? (() => Boolean(deps.window()?.isVisible() && deps.window()?.isFocused()));
15 const cpu = new RendererDiagnostics({
16 target: () => deps.window()?.webContents ?? null,
17 isForeground,
18 onInvalidated: (cancel) => {
19 const win = deps.window();
20 if (!win) { cancel(); return () => {}; }
21 const contents = win.webContents;
22 const navigation = (_event: unknown, _url: string, _inPlace: boolean, main: boolean) => { if (main) cancel(); };
23 const cleanup = () => {
24 win.removeListener("blur", cancel);
25 win.removeListener("hide", cancel);
26 win.removeListener("closed", cancel);
27 contents.removeListener("destroyed", cancel);
28 contents.removeListener("render-process-gone", cancel);
29 contents.removeListener("did-start-navigation", navigation);
30 };
31 try {
32 win.on("blur", cancel);
33 win.on("hide", cancel);
34 win.on("closed", cancel);
35 contents.on("destroyed", cancel);
36 contents.on("render-process-gone", cancel);
37 contents.on("did-start-navigation", navigation);
38 } catch (error) {
39 cleanup();
40 throw error;
41 }
42 return cleanup;
43 },
44 analyse: (profile) => analyseInWorker(profile, deps.workerPath),
45 });
46 let heapBusy = false;
47 let disposed = false;
48 return {
49 captureRendererProfile: (requestId?: string) => heapBusy ? Promise.resolve({ status: "busy" as const }) : cpu.capture(requestId),
50 cancelRendererProfile: (requestId?: string) => { if (requestId) cpu.cancel(requestId); },
51 dispose: () => { disposed = true; cpu.dispose(); },
52 async exportHeapSnapshot(): Promise<HeapResult> {
53 if (disposed) return { status: "unavailable" };
54 if (heapBusy || cpu.busy) return { status: "busy" };
55 const win = deps.window();
56 if (!win || !isForeground()) return { status: "unavailable" };
57 heapBusy = true;
58 let invalidated = false;
59 const contents = win.webContents;
60 const navigation = (_event: unknown, _url: string, _inPlace: boolean, main: boolean) => { if (main) invalidated = true; };
61 const valid = () => !disposed && !invalidated && !contents.isDestroyed() && deps.window() === win;
62 try {
63 contents.on("did-start-navigation", navigation);
64 const zh = deps.locale().startsWith("zh");
65 const confirmation = await deps.dialog.showMessageBox(win, {
66 type: "warning", title: zh ? "保存内存快照" : "Save heap snapshot",
67 message: zh ? "内存快照可能包含聊天、代码和密钥等敏感内容,生成时会暂停界面,并可能占用较多磁盘空间。仅保存到本地,不自动上传。" : "A heap snapshot can contain chats, code and secrets. Capturing it pauses the interface and can use substantial disk space. It is saved locally and is not uploaded automatically.",
68 buttons: zh ? ["取消", "继续保存"] : ["Cancel", "Continue"], defaultId: 0, cancelId: 0,
69 });
70 if (confirmation.response !== 1 || !valid()) return { status: "cancelled" };
71 const destination = await deps.dialog.showSaveDialog(win, {
72 title: zh ? "保存内存快照" : "Save heap snapshot", defaultPath: "reasonix-renderer.heapsnapshot",
73 filters: [{ name: "Heap snapshot", extensions: ["heapsnapshot"] }],
74 });
75 if (destination.canceled || !destination.filePath || !valid()) return { status: "cancelled" };
76 // Electron cannot preempt this operation. Retain the busy lease until
77 // its actual settlement, rather than pretending a timeout cancelled it.
78 await contents.takeHeapSnapshot(destination.filePath);
79 return { status: "saved" };
80 } catch {
81 return { status: "failed" };
82 } finally {
83 try { contents.removeListener("did-start-navigation", navigation); } catch { /* Best effort after renderer exit. */ }
84 heapBusy = false;
85 }
86 },
87 };
88 }
89
90 export type PerformanceHost = ReturnType<typeof createPerformanceHost>;
91
91 lines TYPESCRIPT