@electron-memory/monitor 0.1.0 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.mts +9 -1
- package/dist/index.d.ts +9 -1
- package/dist/index.js +197 -37
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +176 -17
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/core/monitor.ts","../src/core/collector.ts","../src/core/persister.ts","../src/core/utils.ts","../src/core/session.ts","../src/core/anomaly.ts","../src/core/analyzer.ts","../src/core/dashboard.ts","../src/ipc/main-handler.ts","../src/ipc/channels.ts","../src/types/config.ts"],"sourcesContent":["/**\r\n * @electron-memory/monitor\r\n * \r\n * Electron 内存监控 SDK\r\n * 零代码入侵 - 一行代码接入任何 Electron 项目\r\n */\r\n\r\nexport { ElectronMemoryMonitor } from './core/monitor'\r\n\r\n// 类型导出\r\nexport type { MonitorConfig } from './types/config'\r\nexport type {\r\n MemorySnapshot,\r\n ProcessMemoryInfo,\r\n V8HeapStats,\r\n V8HeapDetailStats,\r\n V8HeapSpaceInfo,\r\n SystemMemoryInfo,\r\n RendererV8Detail,\r\n EventMark,\r\n} from './types/snapshot'\r\nexport type { TestSession, SessionIndex } from './types/session'\r\nexport type { AnomalyEvent, AnomalySeverity, AnomalyCategory, AnomalyRule } from './types/anomaly'\r\nexport type {\r\n SessionReport,\r\n CompareReport,\r\n MetricSummary,\r\n TrendInfo,\r\n Suggestion,\r\n MetricDiff,\r\n Regression,\r\n Improvement,\r\n GCResult,\r\n} from './types/report'\r\nexport { IPC_CHANNELS } from './ipc/channels'\r\n","/**\r\n * ElectronMemoryMonitor - SDK 主入口\r\n * \r\n * 门面类(Facade Pattern),提供简洁的 API\r\n * 一行代码即可接入:new ElectronMemoryMonitor()\r\n */\r\n\r\nimport { app } from 'electron'\r\nimport * as path from 'path'\r\nimport * as v8 from 'v8'\r\nimport { EventEmitter } from 'events'\r\nimport { MemoryCollector } from './collector'\r\nimport { DataPersister } from './persister'\r\nimport { SessionManager } from './session'\r\nimport { AnomalyDetector } from './anomaly'\r\nimport { Analyzer } from './analyzer'\r\nimport { DashboardManager } from './dashboard'\r\nimport { IPCMainHandler } from '../ipc/main-handler'\r\nimport { DEFAULT_CONFIG, type MonitorConfig } from '../types/config'\r\nimport type { MemorySnapshot, RendererV8Detail } from '../types/snapshot'\r\nimport type { TestSession } from '../types/session'\r\nimport type { AnomalyEvent } from '../types/anomaly'\r\nimport type { SessionReport, CompareReport, GCResult } from '../types/report'\r\n\r\nexport class ElectronMemoryMonitor extends EventEmitter {\r\n private config: MonitorConfig\r\n private collector: MemoryCollector\r\n private persister!: DataPersister\r\n private sessionManager!: SessionManager\r\n private anomalyDetector: AnomalyDetector\r\n private analyzer: Analyzer\r\n private dashboard: DashboardManager\r\n private ipcHandler!: IPCMainHandler\r\n private started = false\r\n private latestSnapshot: MemorySnapshot | null = null\r\n\r\n constructor(config?: Partial<MonitorConfig>) {\r\n super()\r\n\r\n // 合并配置\r\n this.config = this.mergeConfig(config)\r\n\r\n // 如果 disabled,则不做任何事\r\n if (!this.config.enabled) {\r\n this.collector = null as unknown as MemoryCollector\r\n this.anomalyDetector = null as unknown as AnomalyDetector\r\n this.analyzer = null as unknown as Analyzer\r\n this.dashboard = null as unknown as DashboardManager\r\n return\r\n }\r\n\r\n // 初始化各模块\r\n this.collector = new MemoryCollector(this.config)\r\n this.anomalyDetector = new AnomalyDetector(this.config)\r\n this.analyzer = new Analyzer()\r\n this.dashboard = new DashboardManager(this.config)\r\n\r\n // 如果 autoStart,则自动启动\r\n if (this.config.autoStart) {\r\n this.start()\r\n }\r\n }\r\n\r\n // ============ 生命周期 ============\r\n\r\n /** 启动监控 */\r\n async start(): Promise<void> {\r\n if (!this.config.enabled || this.started) return\r\n\r\n // 等待 app ready\r\n if (!app.isReady()) {\r\n await app.whenReady()\r\n }\r\n\r\n // 初始化存储目录\r\n const storageDir = this.config.storage.directory || path.join(app.getPath('userData'), 'memory-monitor')\r\n this.persister = new DataPersister(this.config, storageDir)\r\n this.sessionManager = new SessionManager(this.persister)\r\n\r\n // 注册 IPC\r\n this.ipcHandler = new IPCMainHandler(this)\r\n this.ipcHandler.register()\r\n\r\n // 连接采集器事件\r\n this.collector.on('snapshot', (snapshot: MemorySnapshot) => {\r\n this.onSnapshot(snapshot)\r\n })\r\n\r\n // 连接异常检测事件\r\n this.anomalyDetector.on('anomaly', (anomaly: AnomalyEvent) => {\r\n this.emit('anomaly', anomaly)\r\n this.ipcHandler.pushAnomaly(this.dashboard.getWindow(), anomaly)\r\n })\r\n\r\n // 启动采集\r\n this.collector.start()\r\n\r\n // 启动异常检测\r\n this.anomalyDetector.start()\r\n\r\n // 打开监控面板\r\n if (this.config.openDashboardOnStart) {\r\n this.openDashboard()\r\n }\r\n\r\n // 清理过期会话\r\n this.persister.cleanOldSessions()\r\n\r\n this.started = true\r\n }\r\n\r\n /** 停止监控 */\r\n async stop(): Promise<void> {\r\n if (!this.started) return\r\n\r\n this.collector.stop()\r\n this.anomalyDetector.stop()\r\n\r\n // 如果有正在运行的会话,结束它\r\n const currentSession = this.sessionManager.getCurrentSession()\r\n if (currentSession) {\r\n await this.stopSession()\r\n }\r\n\r\n this.persister.close()\r\n this.started = false\r\n }\r\n\r\n /** 销毁实例 */\r\n async destroy(): Promise<void> {\r\n await this.stop()\r\n this.dashboard.destroy()\r\n if (this.ipcHandler) {\r\n this.ipcHandler.unregister()\r\n }\r\n this.removeAllListeners()\r\n }\r\n\r\n // ============ 会话控制 ============\r\n\r\n /** 开始新会话 */\r\n startSession(label: string, description?: string): string {\r\n if (!this.started) {\r\n throw new Error('Monitor is not started')\r\n }\r\n\r\n const session = this.sessionManager.startSession(label, description)\r\n this.collector.setSessionId(session.id)\r\n this.anomalyDetector.clearAnomalies()\r\n\r\n return session.id\r\n }\r\n\r\n /** 结束当前会话 */\r\n async stopSession(): Promise<SessionReport | null> {\r\n if (!this.started) return null\r\n\r\n const session = this.sessionManager.getCurrentSession()\r\n if (!session) return null\r\n\r\n // 结束会话\r\n const completedSession = this.sessionManager.endSession()\r\n if (!completedSession) return null\r\n\r\n this.collector.setSessionId(null)\r\n\r\n // 生成报告\r\n const snapshots = this.persister.readSessionSnapshots(completedSession.id)\r\n const anomalies = this.anomalyDetector.getAnomalies()\r\n\r\n const report = this.analyzer.generateReport(\r\n completedSession.id,\r\n completedSession.label,\r\n completedSession.description,\r\n completedSession.startTime,\r\n completedSession.endTime!,\r\n snapshots,\r\n anomalies,\r\n completedSession.dataFile\r\n )\r\n\r\n // 保存报告\r\n const reportPath = path.join(this.persister.getStorageDir(), completedSession.id, 'report.json')\r\n const fs = await import('fs')\r\n fs.writeFileSync(reportPath, JSON.stringify(report, null, 2), 'utf-8')\r\n\r\n this.emit('session-end', report)\r\n return report\r\n }\r\n\r\n // ============ 监控面板 ============\r\n\r\n /** 打开监控面板 */\r\n openDashboard(): void {\r\n this.dashboard.open()\r\n\r\n // 设置监控窗口 ID,用于在进程列表中标记\r\n const wcId = this.dashboard.getWebContentsId()\r\n this.collector.setMonitorWindowId(wcId)\r\n }\r\n\r\n /** 关闭监控面板 */\r\n closeDashboard(): void {\r\n this.dashboard.close()\r\n this.collector.setMonitorWindowId(null)\r\n }\r\n\r\n // ============ 数据访问 ============\r\n\r\n /** 获取当前最新快照 */\r\n getCurrentSnapshot(): MemorySnapshot | null {\r\n return this.latestSnapshot\r\n }\r\n\r\n /** 获取历史会话列表 */\r\n async getSessions(): Promise<TestSession[]> {\r\n return this.sessionManager.getSessions()\r\n }\r\n\r\n /** 获取指定会话报告 */\r\n async getSessionReport(sessionId: string): Promise<SessionReport | null> {\r\n const fs = await import('fs')\r\n const reportPath = path.join(this.persister.getStorageDir(), sessionId, 'report.json')\r\n\r\n try {\r\n const content = fs.readFileSync(reportPath, 'utf-8')\r\n return JSON.parse(content) as SessionReport\r\n } catch {\r\n // 报告不存在,尝试从原始数据重新生成\r\n const session = this.sessionManager.getSession(sessionId)\r\n if (!session || !session.endTime) return null\r\n\r\n const snapshots = this.persister.readSessionSnapshots(sessionId)\r\n if (snapshots.length === 0) return null\r\n\r\n return this.analyzer.generateReport(\r\n session.id,\r\n session.label,\r\n session.description,\r\n session.startTime,\r\n session.endTime,\r\n snapshots,\r\n [],\r\n session.dataFile\r\n )\r\n }\r\n }\r\n\r\n /** 获取指定会话的快照数据(支持时间过滤和降采样) */\r\n async getSessionSnapshots(\r\n sessionId: string,\r\n startTime?: number,\r\n endTime?: number,\r\n maxPoints?: number\r\n ): Promise<MemorySnapshot[]> {\r\n let snapshots = this.persister.readSessionSnapshots(sessionId)\r\n\r\n // 时间范围过滤\r\n if (startTime != null) {\r\n snapshots = snapshots.filter((s) => s.timestamp >= startTime)\r\n }\r\n if (endTime != null) {\r\n snapshots = snapshots.filter((s) => s.timestamp <= endTime)\r\n }\r\n\r\n // 降采样:如果数据点超过 maxPoints,均匀采样\r\n const limit = maxPoints ?? 600\r\n if (snapshots.length > limit) {\r\n const step = snapshots.length / limit\r\n const sampled: MemorySnapshot[] = []\r\n for (let i = 0; i < limit; i++) {\r\n sampled.push(snapshots[Math.round(i * step)])\r\n }\r\n // 确保包含最后一个点\r\n if (sampled[sampled.length - 1] !== snapshots[snapshots.length - 1]) {\r\n sampled[sampled.length - 1] = snapshots[snapshots.length - 1]\r\n }\r\n snapshots = sampled\r\n }\r\n\r\n return snapshots\r\n }\r\n\r\n /** 对比两个会话 */\r\n async compareSessions(baseId: string, targetId: string): Promise<CompareReport | null> {\r\n const baseReport = await this.getSessionReport(baseId)\r\n const targetReport = await this.getSessionReport(targetId)\r\n\r\n if (!baseReport || !targetReport) return null\r\n\r\n return this.analyzer.compareReports(baseReport, targetReport)\r\n }\r\n\r\n /** 导出会话数据(供 IPC 调用,弹出保存对话框) */\r\n async exportSession(sessionId: string): Promise<{ success: boolean; filePath?: string; error?: string }> {\r\n try {\r\n const { dialog } = await import('electron')\r\n const session = this.sessionManager.getSession(sessionId)\r\n if (!session) return { success: false, error: '会话不存在' }\r\n\r\n const defaultName = `emm-${session.label.replace(/[^a-zA-Z0-9\\u4e00-\\u9fa5_-]/g, '_')}-${new Date(session.startTime).toISOString().slice(0, 10)}.emmsession`\r\n\r\n const result = await dialog.showSaveDialog({\r\n title: '导出会话数据',\r\n defaultPath: defaultName,\r\n filters: [\r\n { name: 'EMM Session', extensions: ['emmsession'] },\r\n { name: 'JSON 文件', extensions: ['json'] },\r\n { name: '所有文件', extensions: ['*'] },\r\n ],\r\n })\r\n\r\n if (result.canceled || !result.filePath) {\r\n return { success: false, error: '用户取消' }\r\n }\r\n\r\n const exportData = this.persister.exportSession(sessionId)\r\n const fs = await import('fs')\r\n const fileContent = JSON.stringify({\r\n version: 1,\r\n exportTime: Date.now(),\r\n ...exportData,\r\n }, null, 2)\r\n fs.writeFileSync(result.filePath, fileContent, 'utf-8')\r\n\r\n return { success: true, filePath: result.filePath }\r\n } catch (err) {\r\n return { success: false, error: String(err) }\r\n }\r\n }\r\n\r\n /** 导入会话数据(供 IPC 调用,弹出打开对话框) */\r\n async importSession(): Promise<{ success: boolean; session?: TestSession; error?: string }> {\r\n try {\r\n const { dialog } = await import('electron')\r\n\r\n const result = await dialog.showOpenDialog({\r\n title: '导入会话数据',\r\n filters: [\r\n { name: 'EMM Session', extensions: ['emmsession'] },\r\n { name: 'JSON 文件', extensions: ['json'] },\r\n { name: '所有文件', extensions: ['*'] },\r\n ],\r\n properties: ['openFile'],\r\n })\r\n\r\n if (result.canceled || result.filePaths.length === 0) {\r\n return { success: false, error: '用户取消' }\r\n }\r\n\r\n const fs = await import('fs')\r\n const content = fs.readFileSync(result.filePaths[0], 'utf-8')\r\n const parsed = JSON.parse(content)\r\n\r\n if (!parsed.meta || !parsed.snapshots) {\r\n return { success: false, error: '文件格式不正确,缺少 meta 或 snapshots 数据' }\r\n }\r\n\r\n const session = this.persister.importSession({\r\n meta: parsed.meta,\r\n snapshots: parsed.snapshots,\r\n report: parsed.report || null,\r\n })\r\n\r\n return { success: true, session }\r\n } catch (err) {\r\n return { success: false, error: String(err) }\r\n }\r\n }\r\n\r\n /** 删除指定会话 */\r\n async deleteSession(sessionId: string): Promise<boolean> {\r\n return this.persister.deleteSession(sessionId)\r\n }\r\n\r\n // ============ 工具方法 ============\r\n\r\n /** 手动触发 GC */\r\n async triggerGC(): Promise<GCResult> {\r\n const beforeMem = process.memoryUsage()\r\n\r\n if (global.gc) {\r\n global.gc()\r\n } else {\r\n // 尝试通过 v8 flag 触发\r\n try {\r\n v8.writeHeapSnapshot // 触发 GC 的 workaround\r\n } catch {\r\n // 忽略\r\n }\r\n }\r\n\r\n // 等待一小段时间让 GC 完成\r\n await new Promise((resolve) => setTimeout(resolve, 100))\r\n\r\n const afterMem = process.memoryUsage()\r\n const freed = beforeMem.heapUsed - afterMem.heapUsed\r\n\r\n return {\r\n beforeHeapUsed: beforeMem.heapUsed,\r\n afterHeapUsed: afterMem.heapUsed,\r\n freed,\r\n freedPercent: beforeMem.heapUsed > 0 ? (freed / beforeMem.heapUsed) * 100 : 0,\r\n timestamp: Date.now(),\r\n }\r\n }\r\n\r\n /** 导出堆快照 */\r\n async takeHeapSnapshot(filePath?: string): Promise<string> {\r\n const snapshotPath = filePath || path.join(\r\n this.persister.getStorageDir(),\r\n `heap-${Date.now()}.heapsnapshot`\r\n )\r\n v8.writeHeapSnapshot(snapshotPath)\r\n return snapshotPath\r\n }\r\n\r\n /** 添加事件标记 */\r\n mark(label: string, metadata?: Record<string, unknown>): void {\r\n this.collector.addMark(label, metadata)\r\n }\r\n\r\n /** 更新渲染进程 V8 详情 */\r\n updateRendererDetail(detail: RendererV8Detail): void {\r\n this.collector.updateRendererDetail(detail)\r\n }\r\n\r\n /** 获取当前配置 */\r\n getConfig(): MonitorConfig {\r\n return { ...this.config }\r\n }\r\n\r\n // ============ 事件类型重载 ============\r\n\r\n on(event: 'snapshot', handler: (data: MemorySnapshot) => void): this\r\n on(event: 'anomaly', handler: (event: AnomalyEvent) => void): this\r\n on(event: 'session-end', handler: (report: SessionReport) => void): this\r\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\r\n on(event: string | symbol, handler: (...args: any[]) => void): this {\r\n return super.on(event, handler)\r\n }\r\n\r\n // ============ 私有方法 ============\r\n\r\n private onSnapshot(snapshot: MemorySnapshot): void {\r\n this.latestSnapshot = snapshot\r\n\r\n // 写入持久化\r\n if (this.sessionManager.getCurrentSession()) {\r\n this.persister.writeSnapshot(snapshot)\r\n this.sessionManager.incrementSnapshotCount()\r\n }\r\n\r\n // 喂给异常检测\r\n this.anomalyDetector.addSnapshot(snapshot)\r\n\r\n // 推送给面板\r\n this.ipcHandler?.pushSnapshot(this.dashboard.getWindow(), snapshot)\r\n\r\n // 触发事件\r\n this.emit('snapshot', snapshot)\r\n }\r\n\r\n private mergeConfig(userConfig?: Partial<MonitorConfig>): MonitorConfig {\r\n if (!userConfig) return { ...DEFAULT_CONFIG }\r\n\r\n return {\r\n ...DEFAULT_CONFIG,\r\n ...userConfig,\r\n anomaly: {\r\n ...DEFAULT_CONFIG.anomaly,\r\n ...(userConfig.anomaly || {}),\r\n },\r\n storage: {\r\n ...DEFAULT_CONFIG.storage,\r\n ...(userConfig.storage || {}),\r\n },\r\n dashboard: {\r\n ...DEFAULT_CONFIG.dashboard,\r\n ...(userConfig.dashboard || {}),\r\n },\r\n processLabels: {\r\n ...DEFAULT_CONFIG.processLabels,\r\n ...(userConfig.processLabels || {}),\r\n },\r\n }\r\n }\r\n}\r\n","/**\r\n * MemoryCollector - 内存数据采集器\r\n * \r\n * 核心职责:定时从各种数据源采集内存信息,组装成 MemorySnapshot\r\n * 运行在主进程中,不需要渲染进程配合即可获取所有进程的内存概览\r\n */\r\n\r\nimport { app, BrowserWindow, webContents } from 'electron'\r\nimport * as v8 from 'v8'\r\nimport * as os from 'os'\r\nimport { EventEmitter } from 'events'\r\nimport type {\r\n MemorySnapshot,\r\n ProcessMemoryInfo,\r\n V8HeapStats,\r\n V8HeapDetailStats,\r\n V8HeapSpaceInfo,\r\n SystemMemoryInfo,\r\n EventMark,\r\n RendererV8Detail,\r\n} from '../types/snapshot'\r\nimport type { MonitorConfig } from '../types/config'\r\n\r\nexport class MemoryCollector extends EventEmitter {\r\n private config: MonitorConfig\r\n private timer: ReturnType<typeof setInterval> | null = null\r\n private seq = 0\r\n private currentSessionId: string | null = null\r\n private pendingMarks: EventMark[] = []\r\n private rendererDetails: Map<number, RendererV8Detail> = new Map()\r\n private monitorWindowId: number | null = null\r\n\r\n constructor(config: MonitorConfig) {\r\n super()\r\n this.config = config\r\n }\r\n\r\n /** 设置监控面板的 webContents ID,用于标记 */\r\n setMonitorWindowId(id: number | null): void {\r\n this.monitorWindowId = id\r\n }\r\n\r\n /** 设置当前会话 ID */\r\n setSessionId(sessionId: string | null): void {\r\n this.currentSessionId = sessionId\r\n }\r\n\r\n /** 添加事件标记 */\r\n addMark(label: string, metadata?: Record<string, unknown>): void {\r\n this.pendingMarks.push({\r\n timestamp: Date.now(),\r\n label,\r\n metadata,\r\n })\r\n }\r\n\r\n /** 更新渲染进程 V8 详情 */\r\n updateRendererDetail(detail: RendererV8Detail): void {\r\n this.rendererDetails.set(detail.webContentsId, detail)\r\n }\r\n\r\n /** 开始采集 */\r\n start(): void {\r\n if (this.timer) return\r\n\r\n // 立即采集一次\r\n this.collect()\r\n\r\n this.timer = setInterval(() => {\r\n this.collect()\r\n }, this.config.collectInterval)\r\n }\r\n\r\n /** 停止采集 */\r\n stop(): void {\r\n if (this.timer) {\r\n clearInterval(this.timer)\r\n this.timer = null\r\n }\r\n }\r\n\r\n /** 执行一次采集 */\r\n private collect(): void {\r\n try {\r\n const snapshot = this.buildSnapshot()\r\n this.emit('snapshot', snapshot)\r\n } catch (err) {\r\n this.emit('error', err)\r\n }\r\n }\r\n\r\n /** 构建完整的内存快照 */\r\n private buildSnapshot(): MemorySnapshot {\r\n const timestamp = Date.now()\r\n const processes = this.collectProcesses()\r\n const mainProcessMemory = this.collectMainProcessMemory()\r\n const mainProcessV8Detail = this.collectMainProcessV8Detail()\r\n const system = this.collectSystemMemory()\r\n\r\n // 计算总工作集大小(排除监控面板自身进程)\r\n const totalWorkingSetSize = processes.reduce(\r\n (sum, p) => p.isMonitorProcess ? sum : sum + p.memory.workingSetSize,\r\n 0\r\n )\r\n\r\n // 收集待处理的事件标记\r\n const marks = this.pendingMarks.length > 0 ? [...this.pendingMarks] : undefined\r\n this.pendingMarks = []\r\n\r\n // 收集渲染进程 V8 详情\r\n const rendererDetails = this.rendererDetails.size > 0\r\n ? Array.from(this.rendererDetails.values())\r\n : undefined\r\n\r\n const snapshot: MemorySnapshot = {\r\n timestamp,\r\n sessionId: this.currentSessionId ?? undefined,\r\n seq: this.seq++,\r\n processes,\r\n totalWorkingSetSize,\r\n mainProcessMemory,\r\n mainProcessV8Detail,\r\n system,\r\n rendererDetails,\r\n marks,\r\n }\r\n\r\n return snapshot\r\n }\r\n\r\n /** 采集所有进程信息 */\r\n private collectProcesses(): ProcessMemoryInfo[] {\r\n const metrics = app.getAppMetrics()\r\n const wcList = webContents.getAllWebContents()\r\n\r\n // 构建 PID → webContents 映射\r\n const pidToWc = new Map<number, Electron.WebContents>()\r\n for (const wc of wcList) {\r\n try {\r\n const pid = wc.getOSProcessId()\r\n pidToWc.set(pid, wc)\r\n } catch {\r\n // webContents 可能已销毁\r\n }\r\n }\r\n\r\n // 构建 webContentsId → BrowserWindow 标题映射\r\n const wcIdToTitle = new Map<number, string>()\r\n const allWindows = BrowserWindow.getAllWindows()\r\n for (const win of allWindows) {\r\n try {\r\n wcIdToTitle.set(win.webContents.id, win.getTitle())\r\n } catch {\r\n // 窗口可能已销毁\r\n }\r\n }\r\n\r\n return metrics.map((metric) => {\r\n const wc = pidToWc.get(metric.pid)\r\n let windowTitle: string | undefined\r\n let webContentsId: number | undefined\r\n let isMonitorProcess = false\r\n\r\n if (wc) {\r\n webContentsId = wc.id\r\n windowTitle = wcIdToTitle.get(wc.id)\r\n\r\n // 检查是否是监控面板自身的进程\r\n if (this.monitorWindowId !== null && wc.id === this.monitorWindowId) {\r\n isMonitorProcess = true\r\n windowTitle = '[Memory Monitor]'\r\n }\r\n }\r\n\r\n // 应用用户自定义标签\r\n let name = windowTitle\r\n if (windowTitle && this.config.processLabels[windowTitle]) {\r\n name = this.config.processLabels[windowTitle]\r\n }\r\n\r\n const info: ProcessMemoryInfo = {\r\n pid: metric.pid,\r\n type: metric.type,\r\n name,\r\n isMonitorProcess,\r\n cpu: {\r\n percentCPUUsage: metric.cpu.percentCPUUsage,\r\n idleWakeupsPerSecond: metric.cpu.idleWakeupsPerSecond,\r\n },\r\n memory: {\r\n workingSetSize: metric.memory.workingSetSize,\r\n peakWorkingSetSize: metric.memory.peakWorkingSetSize,\r\n privateBytes: (metric.memory as Record<string, number>).privateBytes,\r\n },\r\n webContentsId,\r\n windowTitle,\r\n }\r\n\r\n return info\r\n })\r\n }\r\n\r\n /** 采集主进程 Node.js 内存 */\r\n private collectMainProcessMemory(): V8HeapStats {\r\n const mem = process.memoryUsage()\r\n return {\r\n heapUsed: mem.heapUsed,\r\n heapTotal: mem.heapTotal,\r\n external: mem.external,\r\n arrayBuffers: mem.arrayBuffers,\r\n rss: mem.rss,\r\n }\r\n }\r\n\r\n /** 采集主进程 V8 详细统计 */\r\n private collectMainProcessV8Detail(): V8HeapDetailStats {\r\n const mem = process.memoryUsage()\r\n // 注意:v8.getHeapStatistics() 返回 snake_case 字段名\r\n const heapStats = v8.getHeapStatistics() as Record<string, number>\r\n\r\n let heapSpaces: V8HeapSpaceInfo[] | undefined\r\n if (this.config.enableV8HeapSpaces) {\r\n // 注意:v8.getHeapSpaceStatistics() 返回 snake_case 字段名\r\n heapSpaces = v8.getHeapSpaceStatistics().map((space: Record<string, unknown>) => ({\r\n name: (space.space_name ?? space.spaceName) as string,\r\n size: (space.space_size ?? space.spaceSize) as number,\r\n usedSize: (space.space_used_size ?? space.spaceUsedSize) as number,\r\n availableSize: (space.space_available_size ?? space.spaceAvailableSize) as number,\r\n physicalSize: (space.physical_space_size ?? space.physicalSpaceSize) as number,\r\n }))\r\n }\r\n\r\n return {\r\n heapUsed: mem.heapUsed,\r\n heapTotal: mem.heapTotal,\r\n external: mem.external,\r\n arrayBuffers: mem.arrayBuffers,\r\n rss: mem.rss,\r\n totalHeapSize: (heapStats.total_heap_size ?? heapStats.totalHeapSize) as number,\r\n usedHeapSize: (heapStats.used_heap_size ?? heapStats.usedHeapSize) as number,\r\n heapSizeLimit: (heapStats.heap_size_limit ?? heapStats.heapSizeLimit) as number,\r\n mallocedMemory: (heapStats.malloced_memory ?? heapStats.mallocedMemory) as number,\r\n peakMallocedMemory: (heapStats.peak_malloced_memory ?? heapStats.peakMallocedMemory) as number,\r\n numberOfDetachedContexts: (heapStats.number_of_detached_contexts ?? heapStats.numberOfDetachedContexts) as number,\r\n numberOfNativeContexts: (heapStats.number_of_native_contexts ?? heapStats.numberOfNativeContexts) as number,\r\n heapSpaces,\r\n }\r\n }\r\n\r\n /** 采集系统内存 */\r\n private collectSystemMemory(): SystemMemoryInfo {\r\n const total = os.totalmem()\r\n const free = os.freemem()\r\n const used = total - free\r\n return {\r\n total,\r\n free,\r\n used,\r\n usagePercent: Math.round((used / total) * 10000) / 100,\r\n }\r\n }\r\n}\r\n","/**\r\n * DataPersister - 数据持久化模块\r\n * \r\n * 使用 JSONL (JSON Lines) 格式流式存储快照数据\r\n * 追加写入,不阻塞采集\r\n */\r\n\r\nimport * as fs from 'fs'\r\nimport * as path from 'path'\r\nimport type { MemorySnapshot } from '../types/snapshot'\r\nimport type { TestSession, SessionIndex } from '../types/session'\r\nimport type { MonitorConfig } from '../types/config'\r\n\r\nexport class DataPersister {\r\n private config: MonitorConfig\r\n private storageDir: string\r\n private buffer: MemorySnapshot[] = []\r\n private currentStream: fs.WriteStream | null = null\r\n private currentDataFile: string | null = null\r\n\r\n constructor(config: MonitorConfig, storageDir: string) {\r\n this.config = config\r\n this.storageDir = storageDir\r\n this.ensureDirectory(this.storageDir)\r\n }\r\n\r\n /** 获取存储目录 */\r\n getStorageDir(): string {\r\n return this.storageDir\r\n }\r\n\r\n /** 创建新的会话数据文件 */\r\n createSessionFiles(sessionId: string): { dataFile: string; metaFile: string } {\r\n const sessionDir = path.join(this.storageDir, sessionId)\r\n this.ensureDirectory(sessionDir)\r\n\r\n const dataFile = path.join(sessionDir, 'snapshots.jsonl')\r\n const metaFile = path.join(sessionDir, 'meta.json')\r\n\r\n // 关闭之前的流\r\n this.closeStream()\r\n\r\n // 打开新的写入流\r\n this.currentDataFile = dataFile\r\n this.currentStream = fs.createWriteStream(dataFile, { flags: 'a' })\r\n\r\n return { dataFile, metaFile }\r\n }\r\n\r\n /** 写入快照数据 */\r\n writeSnapshot(snapshot: MemorySnapshot): void {\r\n this.buffer.push(snapshot)\r\n\r\n // 达到批量写入阈值\r\n if (this.buffer.length >= this.config.persistInterval) {\r\n this.flush()\r\n }\r\n }\r\n\r\n /** 刷新缓冲区到磁盘 */\r\n flush(): void {\r\n if (this.buffer.length === 0 || !this.currentStream) return\r\n\r\n const lines = this.buffer.map((s) => JSON.stringify(s)).join('\\n') + '\\n'\r\n this.currentStream.write(lines)\r\n this.buffer = []\r\n }\r\n\r\n /** 保存会话元信息 */\r\n saveSessionMeta(session: TestSession): void {\r\n const metaFile = path.join(this.storageDir, session.id, 'meta.json')\r\n fs.writeFileSync(metaFile, JSON.stringify(session, null, 2), 'utf-8')\r\n\r\n // 更新索引\r\n this.updateSessionIndex(session)\r\n }\r\n\r\n /** 读取会话元信息 */\r\n readSessionMeta(sessionId: string): TestSession | null {\r\n const metaFile = path.join(this.storageDir, sessionId, 'meta.json')\r\n try {\r\n const content = fs.readFileSync(metaFile, 'utf-8')\r\n return JSON.parse(content) as TestSession\r\n } catch {\r\n return null\r\n }\r\n }\r\n\r\n /** 获取所有会话列表 */\r\n getSessions(): TestSession[] {\r\n const indexFile = path.join(this.storageDir, 'sessions.json')\r\n try {\r\n const content = fs.readFileSync(indexFile, 'utf-8')\r\n const index = JSON.parse(content) as SessionIndex\r\n return index.sessions\r\n } catch {\r\n return []\r\n }\r\n }\r\n\r\n /** 读取会话的所有快照数据 */\r\n readSessionSnapshots(sessionId: string): MemorySnapshot[] {\r\n const dataFile = path.join(this.storageDir, sessionId, 'snapshots.jsonl')\r\n try {\r\n const content = fs.readFileSync(dataFile, 'utf-8')\r\n const lines = content.trim().split('\\n').filter(Boolean)\r\n return lines.map((line) => JSON.parse(line) as MemorySnapshot)\r\n } catch {\r\n return []\r\n }\r\n }\r\n\r\n /** 关闭流并刷新缓冲区 */\r\n close(): void {\r\n this.flush()\r\n this.closeStream()\r\n }\r\n\r\n /** 清理过期会话 */\r\n cleanOldSessions(): void {\r\n const sessions = this.getSessions()\r\n if (sessions.length <= this.config.storage.maxSessions) return\r\n\r\n // 删除最旧的会话\r\n const toRemove = sessions\r\n .sort((a, b) => a.startTime - b.startTime)\r\n .slice(0, sessions.length - this.config.storage.maxSessions)\r\n\r\n for (const session of toRemove) {\r\n const sessionDir = path.join(this.storageDir, session.id)\r\n try {\r\n fs.rmSync(sessionDir, { recursive: true, force: true })\r\n } catch {\r\n // 忽略删除失败\r\n }\r\n }\r\n\r\n // 更新索引\r\n const remaining = sessions.filter((s) => !toRemove.includes(s))\r\n this.saveSessionIndex(remaining)\r\n }\r\n\r\n /** 导出会话数据为单个 JSON 包 */\r\n exportSession(sessionId: string): {\r\n meta: TestSession | null\r\n snapshots: string\r\n report: string | null\r\n } {\r\n const meta = this.readSessionMeta(sessionId)\r\n const snapshotsFile = path.join(this.storageDir, sessionId, 'snapshots.jsonl')\r\n const reportFile = path.join(this.storageDir, sessionId, 'report.json')\r\n\r\n let snapshots = ''\r\n try { snapshots = fs.readFileSync(snapshotsFile, 'utf-8') } catch { /* empty */ }\r\n\r\n let report: string | null = null\r\n try { report = fs.readFileSync(reportFile, 'utf-8') } catch { /* empty */ }\r\n\r\n return { meta, snapshots, report }\r\n }\r\n\r\n /** 导入会话数据 */\r\n importSession(data: {\r\n meta: TestSession\r\n snapshots: string\r\n report: string | null\r\n }): TestSession {\r\n const { meta, snapshots, report } = data\r\n const sessionDir = path.join(this.storageDir, meta.id)\r\n this.ensureDirectory(sessionDir)\r\n\r\n // 写入快照文件\r\n const snapshotsFile = path.join(sessionDir, 'snapshots.jsonl')\r\n fs.writeFileSync(snapshotsFile, snapshots, 'utf-8')\r\n\r\n // 更新路径为本地路径\r\n meta.dataFile = snapshotsFile\r\n meta.metaFile = path.join(sessionDir, 'meta.json')\r\n\r\n // 写入元信息\r\n fs.writeFileSync(meta.metaFile, JSON.stringify(meta, null, 2), 'utf-8')\r\n\r\n // 写入报告\r\n if (report) {\r\n const reportFile = path.join(sessionDir, 'report.json')\r\n fs.writeFileSync(reportFile, report, 'utf-8')\r\n }\r\n\r\n // 更新索引\r\n this.updateSessionIndex(meta)\r\n\r\n return meta\r\n }\r\n\r\n /** 删除指定会话 */\r\n deleteSession(sessionId: string): boolean {\r\n const sessionDir = path.join(this.storageDir, sessionId)\r\n try {\r\n fs.rmSync(sessionDir, { recursive: true, force: true })\r\n } catch {\r\n return false\r\n }\r\n\r\n // 更新索引\r\n const sessions = this.getSessions().filter((s) => s.id !== sessionId)\r\n this.saveSessionIndex(sessions)\r\n return true\r\n }\r\n\r\n // ===== 私有方法 =====\r\n\r\n private closeStream(): void {\r\n if (this.currentStream) {\r\n this.currentStream.end()\r\n this.currentStream = null\r\n this.currentDataFile = null\r\n }\r\n }\r\n\r\n private updateSessionIndex(session: TestSession): void {\r\n const sessions = this.getSessions()\r\n const existingIdx = sessions.findIndex((s) => s.id === session.id)\r\n if (existingIdx >= 0) {\r\n sessions[existingIdx] = session\r\n } else {\r\n sessions.push(session)\r\n }\r\n this.saveSessionIndex(sessions)\r\n }\r\n\r\n private saveSessionIndex(sessions: TestSession[]): void {\r\n const indexFile = path.join(this.storageDir, 'sessions.json')\r\n const index: SessionIndex = {\r\n sessions,\r\n lastUpdated: Date.now(),\r\n }\r\n fs.writeFileSync(indexFile, JSON.stringify(index, null, 2), 'utf-8')\r\n }\r\n\r\n private ensureDirectory(dir: string): void {\r\n if (!fs.existsSync(dir)) {\r\n fs.mkdirSync(dir, { recursive: true })\r\n }\r\n }\r\n}\r\n","/**\r\n * 工具函数\r\n */\r\n\r\n/** 简单的 UUID v4 生成(不依赖外部库) */\r\nexport function v4(): string {\r\n return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {\r\n const r = (Math.random() * 16) | 0\r\n const v = c === 'x' ? r : (r & 0x3) | 0x8\r\n return v.toString(16)\r\n })\r\n}\r\n\r\n/** 格式化字节数 */\r\nexport function formatBytes(bytes: number | undefined | null): string {\r\n if (bytes == null || isNaN(bytes)) return '0 B'\r\n if (bytes === 0) return '0 B'\r\n const k = 1024\r\n const sizes = ['B', 'KB', 'MB', 'GB']\r\n const i = Math.floor(Math.log(Math.abs(bytes)) / Math.log(k))\r\n const idx = Math.min(i, sizes.length - 1)\r\n return parseFloat((bytes / Math.pow(k, idx)).toFixed(2)) + ' ' + sizes[idx]\r\n}\r\n\r\n/** KB 转换为 bytes */\r\nexport function kbToBytes(kb: number): number {\r\n return kb * 1024\r\n}\r\n\r\n/** bytes 转换为 KB */\r\nexport function bytesToKb(bytes: number): number {\r\n return bytes / 1024\r\n}\r\n\r\n/** bytes 转换为 MB */\r\nexport function bytesToMb(bytes: number): number {\r\n return bytes / (1024 * 1024)\r\n}\r\n\r\n/** 计算数组的百分位数 */\r\nexport function percentile(arr: number[], p: number): number {\r\n if (arr.length === 0) return 0\r\n const sorted = [...arr].sort((a, b) => a - b)\r\n const idx = (p / 100) * (sorted.length - 1)\r\n const lower = Math.floor(idx)\r\n const upper = Math.ceil(idx)\r\n if (lower === upper) return sorted[lower]\r\n return sorted[lower] + (sorted[upper] - sorted[lower]) * (idx - lower)\r\n}\r\n\r\n/** 计算平均值 */\r\nexport function average(arr: number[]): number {\r\n if (arr.length === 0) return 0\r\n return arr.reduce((a, b) => a + b, 0) / arr.length\r\n}\r\n\r\n/** 线性回归 */\r\nexport function linearRegression(values: number[], timestamps: number[]): { slope: number; r2: number; intercept: number } {\r\n const n = values.length\r\n if (n < 2) return { slope: 0, r2: 0, intercept: values[0] || 0 }\r\n\r\n // 标准化时间戳(秒)\r\n const t0 = timestamps[0]\r\n const xs = timestamps.map((t) => (t - t0) / 1000)\r\n\r\n const sumX = xs.reduce((a, b) => a + b, 0)\r\n const sumY = values.reduce((a, b) => a + b, 0)\r\n const sumXY = xs.reduce((sum, x, i) => sum + x * values[i], 0)\r\n const sumX2 = xs.reduce((sum, x) => sum + x * x, 0)\r\n const sumY2 = values.reduce((sum, y) => sum + y * y, 0)\r\n\r\n const denominator = n * sumX2 - sumX * sumX\r\n if (denominator === 0) return { slope: 0, r2: 0, intercept: sumY / n }\r\n\r\n const slope = (n * sumXY - sumX * sumY) / denominator\r\n const intercept = (sumY - slope * sumX) / n\r\n\r\n // R² 拟合优度\r\n const meanY = sumY / n\r\n const ssTotal = sumY2 - n * meanY * meanY\r\n const ssResidual = values.reduce((sum, y, i) => {\r\n const predicted = intercept + slope * xs[i]\r\n return sum + (y - predicted) ** 2\r\n }, 0)\r\n\r\n const r2 = ssTotal === 0 ? 0 : 1 - ssResidual / ssTotal\r\n\r\n return { slope, r2, intercept }\r\n}\r\n","/**\r\n * SessionManager - 会话管理\r\n * \r\n * 管理测试会话的生命周期:创建、结束、查询\r\n */\r\n\r\nimport { v4 as generateId } from './utils'\r\nimport type { TestSession } from '../types/session'\r\nimport type { DataPersister } from './persister'\r\n\r\nexport class SessionManager {\r\n private persister: DataPersister\r\n private currentSession: TestSession | null = null\r\n\r\n constructor(persister: DataPersister) {\r\n this.persister = persister\r\n }\r\n\r\n /** 获取当前正在运行的会话 */\r\n getCurrentSession(): TestSession | null {\r\n return this.currentSession\r\n }\r\n\r\n /** 开始新会话 */\r\n startSession(label: string, description?: string): TestSession {\r\n // 如果有正在运行的会话,先结束它\r\n if (this.currentSession && this.currentSession.status === 'running') {\r\n this.endSession()\r\n }\r\n\r\n const sessionId = generateId()\r\n const { dataFile, metaFile } = this.persister.createSessionFiles(sessionId)\r\n\r\n const session: TestSession = {\r\n id: sessionId,\r\n label,\r\n description,\r\n startTime: Date.now(),\r\n status: 'running',\r\n snapshotCount: 0,\r\n dataFile,\r\n metaFile,\r\n }\r\n\r\n this.currentSession = session\r\n this.persister.saveSessionMeta(session)\r\n\r\n return session\r\n }\r\n\r\n /** 结束当前会话 */\r\n endSession(): TestSession | null {\r\n if (!this.currentSession) return null\r\n\r\n this.currentSession.endTime = Date.now()\r\n this.currentSession.duration = this.currentSession.endTime - this.currentSession.startTime\r\n this.currentSession.status = 'completed'\r\n\r\n this.persister.flush()\r\n this.persister.saveSessionMeta(this.currentSession)\r\n\r\n const session = { ...this.currentSession }\r\n this.currentSession = null\r\n\r\n return session\r\n }\r\n\r\n /** 增加当前会话的快照计数 */\r\n incrementSnapshotCount(): void {\r\n if (this.currentSession) {\r\n this.currentSession.snapshotCount++\r\n }\r\n }\r\n\r\n /** 获取所有会话 */\r\n getSessions(): TestSession[] {\r\n return this.persister.getSessions()\r\n }\r\n\r\n /** 获取指定会话 */\r\n getSession(sessionId: string): TestSession | null {\r\n return this.persister.readSessionMeta(sessionId)\r\n }\r\n}\r\n","/**\r\n * AnomalyDetector - 异常检测引擎\r\n * \r\n * 基于滑动窗口的内存异常检测\r\n * 内置 4 种检测策略:持续增长、突增、阈值、分离上下文\r\n */\r\n\r\nimport { EventEmitter } from 'events'\r\nimport { v4 as generateId, linearRegression, average } from './utils'\r\nimport type { MemorySnapshot } from '../types/snapshot'\r\nimport type { AnomalyEvent, AnomalyRule } from '../types/anomaly'\r\nimport type { MonitorConfig } from '../types/config'\r\n\r\nexport class AnomalyDetector extends EventEmitter {\r\n private config: MonitorConfig\r\n private snapshots: MemorySnapshot[] = []\r\n private timer: ReturnType<typeof setInterval> | null = null\r\n private maxWindowSize = 300 // 保留最近 300 条(5 分钟 @1s间隔)\r\n private detectedAnomalies: AnomalyEvent[] = []\r\n private builtinRules: AnomalyRule[]\r\n\r\n constructor(config: MonitorConfig) {\r\n super()\r\n this.config = config\r\n this.builtinRules = this.createBuiltinRules()\r\n }\r\n\r\n /** 添加快照到检测窗口 */\r\n addSnapshot(snapshot: MemorySnapshot): void {\r\n this.snapshots.push(snapshot)\r\n if (this.snapshots.length > this.maxWindowSize) {\r\n this.snapshots.shift()\r\n }\r\n }\r\n\r\n /** 获取所有检测到的异常 */\r\n getAnomalies(): AnomalyEvent[] {\r\n return [...this.detectedAnomalies]\r\n }\r\n\r\n /** 清空异常记录 */\r\n clearAnomalies(): void {\r\n this.detectedAnomalies = []\r\n }\r\n\r\n /** 开始定时检测 */\r\n start(): void {\r\n if (!this.config.anomaly.enabled || this.timer) return\r\n\r\n this.timer = setInterval(() => {\r\n this.runDetection()\r\n }, this.config.anomaly.checkInterval)\r\n }\r\n\r\n /** 停止检测 */\r\n stop(): void {\r\n if (this.timer) {\r\n clearInterval(this.timer)\r\n this.timer = null\r\n }\r\n }\r\n\r\n /** 执行一次检测 */\r\n private runDetection(): void {\r\n if (this.snapshots.length < 10) return // 数据不足\r\n\r\n const latest = this.snapshots[this.snapshots.length - 1]\r\n const allRules = [...this.builtinRules, ...this.config.anomaly.rules]\r\n\r\n for (const rule of allRules) {\r\n if (!rule.enabled) continue\r\n try {\r\n const anomaly = rule.detect(this.snapshots, latest)\r\n if (anomaly) {\r\n this.detectedAnomalies.push(anomaly)\r\n this.emit('anomaly', anomaly)\r\n }\r\n } catch {\r\n // 忽略规则执行错误\r\n }\r\n }\r\n }\r\n\r\n /** 创建内置检测规则 */\r\n private createBuiltinRules(): AnomalyRule[] {\r\n return [\r\n // 规则1:总内存持续增长\r\n {\r\n id: 'continuous-growth',\r\n name: '总内存持续增长',\r\n enabled: true,\r\n detect: (snapshots: MemorySnapshot[]) => {\r\n if (snapshots.length < 60) return null // 至少 1 分钟数据\r\n\r\n const values = snapshots.map((s) => s.totalWorkingSetSize)\r\n const timestamps = snapshots.map((s) => s.timestamp)\r\n const { slope, r2 } = linearRegression(values, timestamps)\r\n\r\n // 斜率 > 10 KB/s 且 R² > 0.7\r\n if (slope > 10 && r2 > 0.7) {\r\n return {\r\n id: generateId(),\r\n timestamp: Date.now(),\r\n severity: r2 > 0.9 ? 'critical' : 'warning',\r\n category: 'memory-leak',\r\n title: '总内存持续增长',\r\n description: `内存以 ${slope.toFixed(2)} KB/s 的速率持续增长 (R²=${r2.toFixed(3)})`,\r\n value: slope,\r\n threshold: 10,\r\n }\r\n }\r\n return null\r\n },\r\n },\r\n\r\n // 规则2:内存突增(spike)\r\n {\r\n id: 'memory-spike',\r\n name: '内存突增',\r\n enabled: true,\r\n detect: (snapshots: MemorySnapshot[], latest: MemorySnapshot) => {\r\n if (snapshots.length < 10) return null\r\n\r\n const recentValues = snapshots.slice(-30).map((s) => s.totalWorkingSetSize)\r\n const avg = average(recentValues)\r\n const current = latest.totalWorkingSetSize\r\n\r\n // 当前值超过近期平均值的 50%\r\n if (avg > 0 && (current - avg) / avg > 0.5) {\r\n return {\r\n id: generateId(),\r\n timestamp: Date.now(),\r\n severity: 'warning',\r\n category: 'spike',\r\n title: '内存突增',\r\n description: `总内存从 ${Math.round(avg)} KB 突增到 ${current} KB (+${(((current - avg) / avg) * 100).toFixed(1)}%)`,\r\n value: current,\r\n threshold: avg * 1.5,\r\n }\r\n }\r\n return null\r\n },\r\n },\r\n\r\n // 规则3:分离上下文检测\r\n {\r\n id: 'detached-contexts',\r\n name: '分离上下文',\r\n enabled: true,\r\n detect: (_snapshots: MemorySnapshot[], latest: MemorySnapshot) => {\r\n const detached = latest.mainProcessV8Detail?.numberOfDetachedContexts\r\n if (detached && detached > 0) {\r\n return {\r\n id: generateId(),\r\n timestamp: Date.now(),\r\n severity: 'critical',\r\n category: 'detached-context',\r\n title: `检测到 ${detached} 个分离的 V8 上下文`,\r\n description: '存在未正确销毁的 BrowserWindow 或 WebContents,可能导致内存泄漏',\r\n value: detached,\r\n threshold: 0,\r\n }\r\n }\r\n return null\r\n },\r\n },\r\n\r\n // 规则4:V8 堆使用率过高\r\n {\r\n id: 'heap-usage-high',\r\n name: 'V8 堆使用率过高',\r\n enabled: true,\r\n detect: (_snapshots: MemorySnapshot[], latest: MemorySnapshot) => {\r\n const { heapUsed, heapTotal } = latest.mainProcessMemory\r\n if (heapTotal > 0) {\r\n const usagePercent = heapUsed / heapTotal\r\n if (usagePercent > 0.85) {\r\n return {\r\n id: generateId(),\r\n timestamp: Date.now(),\r\n severity: usagePercent > 0.95 ? 'critical' : 'warning',\r\n category: 'threshold',\r\n title: `V8 堆使用率 ${(usagePercent * 100).toFixed(1)}%`,\r\n description: `主进程 V8 堆使用 ${Math.round(heapUsed / 1024 / 1024)} MB / ${Math.round(heapTotal / 1024 / 1024)} MB`,\r\n value: usagePercent * 100,\r\n threshold: 85,\r\n }\r\n }\r\n }\r\n return null\r\n },\r\n },\r\n ]\r\n }\r\n}\r\n","/**\r\n * Analyzer - 报告分析 & 改进建议引擎\r\n * \r\n * 负责:\r\n * 1. 从快照数据生成统计汇总(SessionReport)\r\n * 2. 对比两个会话(CompareReport)\r\n * 3. 基于模式识别生成改进建议\r\n */\r\n\r\nimport * as os from 'os'\r\nimport { percentile, average, linearRegression, kbToBytes } from './utils'\r\nimport type { MemorySnapshot } from '../types/snapshot'\r\nimport type { AnomalyEvent } from '../types/anomaly'\r\nimport type {\r\n SessionReport,\r\n MetricSummary,\r\n TrendInfo,\r\n Suggestion,\r\n CompareReport,\r\n MetricDiff,\r\n Regression,\r\n Improvement,\r\n} from '../types/report'\r\n\r\nexport class Analyzer {\r\n /** 生成会话报告 */\r\n generateReport(\r\n sessionId: string,\r\n label: string,\r\n description: string | undefined,\r\n startTime: number,\r\n endTime: number,\r\n snapshots: MemorySnapshot[],\r\n anomalies: AnomalyEvent[],\r\n dataFile: string\r\n ): SessionReport {\r\n if (snapshots.length === 0) {\r\n throw new Error('No snapshots to analyze')\r\n }\r\n\r\n const environment = this.collectEnvironment()\r\n const summary = this.computeSummary(snapshots)\r\n const suggestions = this.generateSuggestions(snapshots, summary, anomalies)\r\n\r\n return {\r\n sessionId,\r\n label,\r\n description,\r\n startTime,\r\n endTime,\r\n duration: endTime - startTime,\r\n environment,\r\n summary,\r\n anomalies,\r\n suggestions,\r\n dataFile,\r\n }\r\n }\r\n\r\n /** 对比两个会话报告 */\r\n compareReports(base: SessionReport, target: SessionReport): CompareReport {\r\n const overall = {\r\n totalMemory: this.diffMetric(base.summary.totalMemory, target.summary.totalMemory),\r\n browserMemory: this.diffMetric(base.summary.byProcessType.browser, target.summary.byProcessType.browser),\r\n rendererMemory: this.diffMetricArrayAvg(\r\n base.summary.byProcessType.renderer,\r\n target.summary.byProcessType.renderer\r\n ),\r\n gpuMemory: base.summary.byProcessType.gpu && target.summary.byProcessType.gpu\r\n ? this.diffMetric(base.summary.byProcessType.gpu, target.summary.byProcessType.gpu)\r\n : null,\r\n }\r\n\r\n const v8Heap = {\r\n heapUsed: this.diffMetric(base.summary.mainV8Heap.heapUsed, target.summary.mainV8Heap.heapUsed),\r\n heapTotal: this.diffMetric(base.summary.mainV8Heap.heapTotal, target.summary.mainV8Heap.heapTotal),\r\n external: this.diffMetric(base.summary.mainV8Heap.external, target.summary.mainV8Heap.external),\r\n }\r\n\r\n const trendChanges = this.compareTrends(base.summary.trends, target.summary.trends)\r\n const regressions = this.findRegressions(overall, v8Heap)\r\n const improvements = this.findImprovements(overall, v8Heap)\r\n const { verdict, verdictReason } = this.determineVerdict(regressions, overall)\r\n\r\n return {\r\n base: { sessionId: base.sessionId, label: base.label },\r\n target: { sessionId: target.sessionId, label: target.label },\r\n overall,\r\n v8Heap,\r\n trendChanges,\r\n regressions,\r\n improvements,\r\n verdict,\r\n verdictReason,\r\n }\r\n }\r\n\r\n // ===== 私有方法 =====\r\n\r\n private collectEnvironment(): SessionReport['environment'] {\r\n const cpus = os.cpus()\r\n return {\r\n electronVersion: process.versions.electron || 'unknown',\r\n chromeVersion: process.versions.chrome || 'unknown',\r\n nodeVersion: process.versions.node || 'unknown',\r\n platform: process.platform,\r\n arch: process.arch,\r\n totalSystemMemory: os.totalmem(),\r\n cpuModel: cpus.length > 0 ? cpus[0].model : 'unknown',\r\n cpuCores: cpus.length,\r\n }\r\n }\r\n\r\n private computeSummary(snapshots: MemorySnapshot[]): SessionReport['summary'] {\r\n const timestamps = snapshots.map((s) => s.timestamp)\r\n\r\n // 总进程数\r\n const processCounts = snapshots.map((s) => s.processes.length)\r\n\r\n // 总内存 (KB)\r\n const totalMemoryValues = snapshots.map((s) => s.totalWorkingSetSize)\r\n\r\n // 按类型分组\r\n const browserValues = snapshots.map((s) =>\r\n s.processes.filter((p) => p.type === 'Browser').reduce((sum, p) => sum + p.memory.workingSetSize, 0)\r\n )\r\n\r\n // 渲染进程 - 收集每个渲染进程的数据\r\n const rendererSummaries = this.computeRendererSummaries(snapshots)\r\n\r\n // GPU 进程\r\n const gpuValues = snapshots.map((s) =>\r\n s.processes.filter((p) => p.type === 'GPU').reduce((sum, p) => sum + p.memory.workingSetSize, 0)\r\n )\r\n const hasGpu = gpuValues.some((v) => v > 0)\r\n\r\n // Utility 进程\r\n const utilityValues = snapshots.map((s) =>\r\n s.processes.filter((p) => p.type === 'Utility').reduce((sum, p) => sum + p.memory.workingSetSize, 0)\r\n )\r\n const hasUtility = utilityValues.some((v) => v > 0)\r\n\r\n // V8 堆(bytes)\r\n const heapUsedValues = snapshots.map((s) => s.mainProcessMemory.heapUsed)\r\n const heapTotalValues = snapshots.map((s) => s.mainProcessMemory.heapTotal)\r\n const externalValues = snapshots.map((s) => s.mainProcessMemory.external)\r\n const arrayBufferValues = snapshots.map((s) => s.mainProcessMemory.arrayBuffers)\r\n\r\n // 渲染进程总内存\r\n const rendererTotalValues = snapshots.map((s) =>\r\n s.processes.filter((p) => p.type === 'Tab' && !p.isMonitorProcess).reduce((sum, p) => sum + p.memory.workingSetSize, 0)\r\n )\r\n\r\n return {\r\n totalProcesses: {\r\n min: Math.min(...processCounts),\r\n max: Math.max(...processCounts),\r\n avg: Math.round(average(processCounts)),\r\n },\r\n totalMemory: this.computeMetricSummary(totalMemoryValues),\r\n byProcessType: {\r\n browser: this.computeMetricSummary(browserValues),\r\n renderer: rendererSummaries,\r\n gpu: hasGpu ? this.computeMetricSummary(gpuValues) : null,\r\n utility: hasUtility ? this.computeMetricSummary(utilityValues) : null,\r\n },\r\n mainV8Heap: {\r\n heapUsed: this.computeMetricSummary(heapUsedValues),\r\n heapTotal: this.computeMetricSummary(heapTotalValues),\r\n external: this.computeMetricSummary(externalValues),\r\n arrayBuffers: this.computeMetricSummary(arrayBufferValues),\r\n },\r\n trends: {\r\n totalMemory: this.computeTrend(totalMemoryValues, timestamps),\r\n browserMemory: this.computeTrend(browserValues, timestamps),\r\n rendererMemory: this.computeTrend(rendererTotalValues, timestamps),\r\n },\r\n }\r\n }\r\n\r\n private computeRendererSummaries(snapshots: MemorySnapshot[]): MetricSummary[] {\r\n // 收集所有出现过的渲染进程 PID(排除监控面板自身)\r\n const allPids = new Set<number>()\r\n for (const snapshot of snapshots) {\r\n for (const p of snapshot.processes) {\r\n if (p.type === 'Tab' && !p.isMonitorProcess) {\r\n allPids.add(p.pid)\r\n }\r\n }\r\n }\r\n\r\n // 对每个 PID 计算汇总\r\n const summaries: MetricSummary[] = []\r\n for (const pid of allPids) {\r\n const values = snapshots\r\n .map((s) => {\r\n const proc = s.processes.find((p) => p.pid === pid)\r\n return proc ? proc.memory.workingSetSize : null\r\n })\r\n .filter((v): v is number => v !== null)\r\n\r\n if (values.length > 0) {\r\n summaries.push(this.computeMetricSummary(values))\r\n }\r\n }\r\n\r\n return summaries\r\n }\r\n\r\n private computeMetricSummary(values: number[]): MetricSummary {\r\n if (values.length === 0) {\r\n return { initial: 0, final: 0, min: 0, max: 0, avg: 0, p50: 0, p95: 0, p99: 0, delta: 0, deltaPercent: 0 }\r\n }\r\n\r\n const initial = values[0]\r\n const final = values[values.length - 1]\r\n const delta = final - initial\r\n const deltaPercent = initial !== 0 ? (delta / initial) * 100 : 0\r\n\r\n return {\r\n initial,\r\n final,\r\n min: Math.min(...values),\r\n max: Math.max(...values),\r\n avg: Math.round(average(values)),\r\n p50: Math.round(percentile(values, 50)),\r\n p95: Math.round(percentile(values, 95)),\r\n p99: Math.round(percentile(values, 99)),\r\n delta: Math.round(delta),\r\n deltaPercent: Math.round(deltaPercent * 100) / 100,\r\n }\r\n }\r\n\r\n private computeTrend(values: number[], timestamps: number[]): TrendInfo {\r\n if (values.length < 10) {\r\n return { slope: 0, r2: 0, direction: 'stable', confidence: 'low' }\r\n }\r\n\r\n const { slope, r2 } = linearRegression(values, timestamps)\r\n\r\n let direction: TrendInfo['direction'] = 'stable'\r\n if (slope > 1 && r2 > 0.3) direction = 'growing'\r\n else if (slope < -1 && r2 > 0.3) direction = 'shrinking'\r\n\r\n let confidence: TrendInfo['confidence'] = 'low'\r\n if (r2 > 0.8) confidence = 'high'\r\n else if (r2 > 0.5) confidence = 'medium'\r\n\r\n return { slope, r2, direction, confidence }\r\n }\r\n\r\n private generateSuggestions(\r\n snapshots: MemorySnapshot[],\r\n summary: SessionReport['summary'],\r\n _anomalies: AnomalyEvent[]\r\n ): Suggestion[] {\r\n const suggestions: Suggestion[] = []\r\n const latest = snapshots[snapshots.length - 1]\r\n\r\n // 规则1:分离上下文\r\n if (latest.mainProcessV8Detail?.numberOfDetachedContexts > 0) {\r\n suggestions.push({\r\n id: 'detached-contexts',\r\n severity: 'critical',\r\n category: 'memory-leak',\r\n title: '检测到分离的 V8 上下文 (Detached Contexts)',\r\n description: `发现 ${latest.mainProcessV8Detail.numberOfDetachedContexts} 个分离上下文,通常意味着存在未正确销毁的 BrowserWindow 或 WebContents 实例。`,\r\n suggestions: [\r\n '检查所有 BrowserWindow 是否在关闭时调用了 destroy()',\r\n '检查是否有闭包持有已关闭窗口的 webContents 引用',\r\n '使用 Chrome DevTools Memory 面板做堆快照,搜索 \"Detached\" 关键字',\r\n '检查 ipcMain.on 监听器是否在窗口关闭后正确移除',\r\n ],\r\n relatedCode: [\r\n 'win.on(\"closed\", () => { win = null })',\r\n 'win.destroy() // 而不仅仅是 win.close()',\r\n ],\r\n })\r\n }\r\n\r\n // 规则2:主进程内存持续增长\r\n if (summary.trends.browserMemory.direction === 'growing' && summary.trends.browserMemory.confidence === 'high') {\r\n suggestions.push({\r\n id: 'main-process-leak',\r\n severity: 'warning',\r\n category: 'memory-leak',\r\n title: '主进程内存存在持续增长趋势',\r\n description: `主进程内存以 ${summary.trends.browserMemory.slope.toFixed(2)} KB/s 的速率增长 (R²=${summary.trends.browserMemory.r2.toFixed(3)})`,\r\n suggestions: [\r\n '检查主进程中是否有未清理的 setInterval/setTimeout',\r\n '检查 ipcMain.on 是否存在重复注册',\r\n '检查是否有持续增长的 Map/Set/Array 缓存未设置上限',\r\n '检查 EventEmitter 监听器是否正确移除',\r\n '运行 --expose-gc 并手动触发 GC,观察内存是否回落',\r\n ],\r\n })\r\n }\r\n\r\n // 规则3:渲染进程内存过高\r\n const highRenderers = summary.byProcessType.renderer.filter((r) => r.max > 300 * 1024)\r\n if (highRenderers.length > 0) {\r\n suggestions.push({\r\n id: 'renderer-memory-high',\r\n severity: 'warning',\r\n category: 'optimization',\r\n title: '渲染进程内存占用过高',\r\n description: `有 ${highRenderers.length} 个渲染进程内存峰值超过 300MB`,\r\n suggestions: [\r\n '检查是否加载了过大的图片资源(考虑懒加载/压缩)',\r\n '检查 DOM 节点数量(超过 1500 个节点会显著增加内存)',\r\n '检查是否有大量未销毁的 React 组件实例',\r\n '考虑使用虚拟列表(Virtual List)替代长列表',\r\n '检查 Canvas/WebGL 资源是否正确释放',\r\n ],\r\n })\r\n }\r\n\r\n // 规则4:V8 堆使用率过高\r\n const { heapUsed, heapTotal } = summary.mainV8Heap\r\n if (heapTotal.avg > 0 && heapUsed.avg / heapTotal.avg > 0.8) {\r\n suggestions.push({\r\n id: 'gc-ineffective',\r\n severity: 'warning',\r\n category: 'memory-leak',\r\n title: 'V8 堆使用率长期偏高 (>80%)',\r\n description: '堆使用率长期超过 80%,GC 无法有效释放内存,疑似存在内存泄漏',\r\n suggestions: [\r\n '导出堆快照 (Heap Snapshot),使用 Chrome DevTools 分析对象留存',\r\n '对比两个时间点的堆快照,查找 \"Allocated between snapshots\" 中的泄漏对象',\r\n '检查 Event Listeners 是否正确清理',\r\n '检查 Promise 链是否有未处理的 rejection 导致引用未释放',\r\n ],\r\n })\r\n }\r\n\r\n // 规则5:ArrayBuffer 偏高\r\n if (summary.mainV8Heap.arrayBuffers.avg > 50 * 1024 * 1024) {\r\n suggestions.push({\r\n id: 'arraybuffer-high',\r\n severity: 'info',\r\n category: 'optimization',\r\n title: 'ArrayBuffer 内存占用偏高',\r\n description: 'ArrayBuffer 平均占用超过 50MB',\r\n suggestions: [\r\n '检查 Buffer.alloc / Buffer.from 的使用,确保用完后不再持有引用',\r\n '如果使用 IPC 传输大数据,考虑分片传输或使用 MessagePort',\r\n '检查 Blob/File 对象是否及时释放',\r\n ],\r\n })\r\n }\r\n\r\n // 规则6:进程数过多\r\n if (summary.totalProcesses.max > 10) {\r\n suggestions.push({\r\n id: 'too-many-processes',\r\n severity: 'warning',\r\n category: 'architecture',\r\n title: `进程数量偏多 (最高 ${summary.totalProcesses.max} 个)`,\r\n description: '过多的进程会显著增加内存开销',\r\n suggestions: [\r\n '检查是否创建了不必要的 BrowserWindow',\r\n '考虑复用窗口而非每次创建新窗口',\r\n '使用 webContents.setBackgroundThrottling(true) 减少后台进程开销',\r\n ],\r\n })\r\n }\r\n\r\n // 规则7:old_space 占比过高\r\n if (latest.mainProcessV8Detail?.heapSpaces) {\r\n const oldSpace = latest.mainProcessV8Detail.heapSpaces.find((s) => s.name === 'old_space')\r\n const totalUsed = latest.mainProcessV8Detail.heapSpaces.reduce((sum, s) => sum + s.usedSize, 0)\r\n if (oldSpace && totalUsed > 0 && oldSpace.usedSize / totalUsed > 0.85) {\r\n suggestions.push({\r\n id: 'old-space-dominant',\r\n severity: 'info',\r\n category: 'optimization',\r\n title: 'V8 old_space 占比超过 85%',\r\n description: '大量对象存活到 old generation,可能存在长生命周期的大对象或缓存未回收',\r\n suggestions: [\r\n '使用堆快照分析 old_space 中的大对象',\r\n '检查全局缓存是否设置了过期策略或容量上限',\r\n '考虑使用 WeakMap/WeakRef 替代强引用缓存',\r\n '检查闭包是否意外持有大量外部变量',\r\n ],\r\n })\r\n }\r\n }\r\n\r\n return suggestions\r\n }\r\n\r\n private diffMetric(base: MetricSummary, target: MetricSummary): MetricDiff {\r\n const delta = target.avg - base.avg\r\n const deltaPercent = base.avg !== 0 ? (delta / base.avg) * 100 : 0\r\n\r\n let status: MetricDiff['status'] = 'unchanged'\r\n if (deltaPercent > 3) status = 'degraded'\r\n else if (deltaPercent < -3) status = 'improved'\r\n\r\n let severity: MetricDiff['severity']\r\n if (Math.abs(deltaPercent) > 15) severity = 'critical'\r\n else if (Math.abs(deltaPercent) > 5) severity = 'major'\r\n else severity = 'minor'\r\n\r\n return {\r\n base: base.avg,\r\n target: target.avg,\r\n delta: Math.round(delta),\r\n deltaPercent: Math.round(deltaPercent * 100) / 100,\r\n status,\r\n severity,\r\n }\r\n }\r\n\r\n private diffMetricArrayAvg(baseArr: MetricSummary[], targetArr: MetricSummary[]): MetricDiff {\r\n const baseAvg = baseArr.length > 0 ? average(baseArr.map((s) => s.avg)) : 0\r\n const targetAvg = targetArr.length > 0 ? average(targetArr.map((s) => s.avg)) : 0\r\n const baseSummary: MetricSummary = {\r\n initial: 0, final: 0, min: 0, max: 0, avg: baseAvg,\r\n p50: 0, p95: 0, p99: 0, delta: 0, deltaPercent: 0,\r\n }\r\n const targetSummary: MetricSummary = {\r\n initial: 0, final: 0, min: 0, max: 0, avg: targetAvg,\r\n p50: 0, p95: 0, p99: 0, delta: 0, deltaPercent: 0,\r\n }\r\n return this.diffMetric(baseSummary, targetSummary)\r\n }\r\n\r\n private compareTrends(\r\n baseTrends: SessionReport['summary']['trends'],\r\n targetTrends: SessionReport['summary']['trends']\r\n ): CompareReport['trendChanges'] {\r\n const metrics = ['totalMemory', 'browserMemory', 'rendererMemory'] as const\r\n return metrics.map((metric) => {\r\n const baseSlope = baseTrends[metric].slope\r\n const targetSlope = targetTrends[metric].slope\r\n\r\n let change: 'improved' | 'degraded' | 'unchanged' = 'unchanged'\r\n if (targetSlope > baseSlope + 1) change = 'degraded'\r\n else if (targetSlope < baseSlope - 1) change = 'improved'\r\n\r\n return { metric, baseSlope, targetSlope, change }\r\n })\r\n }\r\n\r\n private findRegressions(\r\n overall: CompareReport['overall'],\r\n v8Heap: CompareReport['v8Heap']\r\n ): Regression[] {\r\n const regressions: Regression[] = []\r\n\r\n const checks: { metric: string; diff: MetricDiff; warnThreshold: number; failThreshold: number }[] = [\r\n { metric: '总内存', diff: overall.totalMemory, warnThreshold: 5, failThreshold: 15 },\r\n { metric: '主进程内存', diff: overall.browserMemory, warnThreshold: 10, failThreshold: 25 },\r\n { metric: '渲染进程内存', diff: overall.rendererMemory, warnThreshold: 10, failThreshold: 25 },\r\n { metric: 'V8 Heap Used', diff: v8Heap.heapUsed, warnThreshold: 10, failThreshold: 30 },\r\n ]\r\n\r\n for (const check of checks) {\r\n if (check.diff.deltaPercent > check.warnThreshold) {\r\n regressions.push({\r\n metric: check.metric,\r\n description: `${check.metric}增长 ${check.diff.deltaPercent.toFixed(1)}%`,\r\n baseValue: check.diff.base,\r\n targetValue: check.diff.target,\r\n deltaPercent: check.diff.deltaPercent,\r\n severity: check.diff.deltaPercent > check.failThreshold ? 'critical' : 'major',\r\n suggestion: `${check.metric}增长超过预期,建议检查新增代码中的内存使用`,\r\n })\r\n }\r\n }\r\n\r\n return regressions\r\n }\r\n\r\n private findImprovements(\r\n overall: CompareReport['overall'],\r\n v8Heap: CompareReport['v8Heap']\r\n ): Improvement[] {\r\n const improvements: Improvement[] = []\r\n\r\n const checks: { metric: string; diff: MetricDiff }[] = [\r\n { metric: '总内存', diff: overall.totalMemory },\r\n { metric: '主进程内存', diff: overall.browserMemory },\r\n { metric: 'V8 Heap Used', diff: v8Heap.heapUsed },\r\n ]\r\n\r\n for (const check of checks) {\r\n if (check.diff.deltaPercent < -3) {\r\n improvements.push({\r\n metric: check.metric,\r\n description: `${check.metric}减少 ${Math.abs(check.diff.deltaPercent).toFixed(1)}%`,\r\n baseValue: check.diff.base,\r\n targetValue: check.diff.target,\r\n deltaPercent: check.diff.deltaPercent,\r\n })\r\n }\r\n }\r\n\r\n return improvements\r\n }\r\n\r\n private determineVerdict(\r\n regressions: Regression[],\r\n overall: CompareReport['overall']\r\n ): { verdict: CompareReport['verdict']; verdictReason: string } {\r\n const critical = regressions.filter((r) => r.severity === 'critical')\r\n const major = regressions.filter((r) => r.severity === 'major')\r\n\r\n if (critical.length > 0) {\r\n return {\r\n verdict: 'fail',\r\n verdictReason: `存在 ${critical.length} 项严重劣化:${critical.map((r) => r.metric).join('、')}`,\r\n }\r\n }\r\n\r\n if (major.length > 0 || overall.totalMemory.deltaPercent > 5) {\r\n return {\r\n verdict: 'warn',\r\n verdictReason: `存在 ${major.length} 项劣化,总内存变化 ${overall.totalMemory.deltaPercent.toFixed(1)}%`,\r\n }\r\n }\r\n\r\n return {\r\n verdict: 'pass',\r\n verdictReason: '所有内存指标在正常范围内',\r\n }\r\n }\r\n}\r\n","/**\r\n * DashboardManager - 监控面板窗口管理\r\n * \r\n * 创建和管理独立的监控面板 BrowserWindow\r\n * 面板 UI 预编译为静态资源,打包在 SDK 内\r\n */\r\n\r\nimport { BrowserWindow } from 'electron'\r\nimport * as path from 'path'\r\nimport type { MonitorConfig } from '../types/config'\r\n\r\nexport class DashboardManager {\r\n private config: MonitorConfig\r\n private window: BrowserWindow | null = null\r\n\r\n constructor(config: MonitorConfig) {\r\n this.config = config\r\n }\r\n\r\n /** 获取面板窗口 */\r\n getWindow(): BrowserWindow | null {\r\n return this.window\r\n }\r\n\r\n /** 获取面板 webContents ID */\r\n getWebContentsId(): number | null {\r\n if (this.window && !this.window.isDestroyed()) {\r\n return this.window.webContents.id\r\n }\r\n return null\r\n }\r\n\r\n /** 打开监控面板 */\r\n open(): void {\r\n if (this.window && !this.window.isDestroyed()) {\r\n this.window.focus()\r\n return\r\n }\r\n\r\n // 创建 preload 脚本路径\r\n const preloadPath = path.join(__dirname, 'dashboard-preload.js')\r\n\r\n this.window = new BrowserWindow({\r\n width: this.config.dashboard.width,\r\n height: this.config.dashboard.height,\r\n title: 'Electron Memory Monitor',\r\n alwaysOnTop: this.config.dashboard.alwaysOnTop,\r\n webPreferences: {\r\n preload: preloadPath,\r\n contextIsolation: true,\r\n nodeIntegration: false,\r\n },\r\n })\r\n\r\n // 加载面板 UI\r\n const uiPath = path.join(__dirname, 'ui', 'index.html')\r\n this.window.loadFile(uiPath)\r\n\r\n this.window.on('closed', () => {\r\n this.window = null\r\n })\r\n }\r\n\r\n /** 关闭监控面板 */\r\n close(): void {\r\n if (this.window && !this.window.isDestroyed()) {\r\n this.window.close()\r\n this.window = null\r\n }\r\n }\r\n\r\n /** 销毁面板 */\r\n destroy(): void {\r\n if (this.window && !this.window.isDestroyed()) {\r\n this.window.destroy()\r\n this.window = null\r\n }\r\n }\r\n}\r\n","/**\r\n * IPC 主进程处理器\r\n * \r\n * 注册所有 emm:* IPC 通道的 handler\r\n * 桥接监控面板(渲染进程)与 SDK 核心(主进程)\r\n */\r\n\r\nimport { ipcMain, BrowserWindow } from 'electron'\r\nimport { IPC_CHANNELS } from './channels'\r\nimport type { ElectronMemoryMonitor } from '../core/monitor'\r\n\r\nexport class IPCMainHandler {\r\n private monitor: ElectronMemoryMonitor\r\n\r\n constructor(monitor: ElectronMemoryMonitor) {\r\n this.monitor = monitor\r\n }\r\n\r\n /** 注册所有 IPC handlers */\r\n register(): void {\r\n // 会话控制\r\n ipcMain.handle(IPC_CHANNELS.SESSION_START, (_event, args: { label: string; description?: string }) => {\r\n return this.monitor.startSession(args.label, args.description)\r\n })\r\n\r\n ipcMain.handle(IPC_CHANNELS.SESSION_STOP, async () => {\r\n return this.monitor.stopSession()\r\n })\r\n\r\n ipcMain.handle(IPC_CHANNELS.SESSION_LIST, async () => {\r\n return this.monitor.getSessions()\r\n })\r\n\r\n ipcMain.handle(IPC_CHANNELS.SESSION_REPORT, async (_event, sessionId: string) => {\r\n return this.monitor.getSessionReport(sessionId)\r\n })\r\n\r\n ipcMain.handle(IPC_CHANNELS.SESSION_COMPARE, async (_event, args: { baseId: string; targetId: string }) => {\r\n return this.monitor.compareSessions(args.baseId, args.targetId)\r\n })\r\n\r\n ipcMain.handle(IPC_CHANNELS.SESSION_SNAPSHOTS, async (_event, args: { sessionId: string; startTime?: number; endTime?: number; maxPoints?: number }) => {\r\n return this.monitor.getSessionSnapshots(args.sessionId, args.startTime, args.endTime, args.maxPoints)\r\n })\r\n\r\n // 工具操作\r\n ipcMain.handle(IPC_CHANNELS.TRIGGER_GC, async () => {\r\n return this.monitor.triggerGC()\r\n })\r\n\r\n ipcMain.handle(IPC_CHANNELS.HEAP_SNAPSHOT, async (_event, filePath?: string) => {\r\n return this.monitor.takeHeapSnapshot(filePath)\r\n })\r\n\r\n ipcMain.handle(IPC_CHANNELS.MARK, (_event, args: { label: string; metadata?: Record<string, unknown> }) => {\r\n this.monitor.mark(args.label, args.metadata)\r\n })\r\n\r\n ipcMain.handle(IPC_CHANNELS.GET_CONFIG, () => {\r\n return this.monitor.getConfig()\r\n })\r\n\r\n ipcMain.handle(IPC_CHANNELS.GET_SESSIONS, async () => {\r\n return this.monitor.getSessions()\r\n })\r\n\r\n // 导入导出\r\n ipcMain.handle(IPC_CHANNELS.SESSION_EXPORT, async (_event, sessionId: string) => {\r\n return this.monitor.exportSession(sessionId)\r\n })\r\n\r\n ipcMain.handle(IPC_CHANNELS.SESSION_IMPORT, async () => {\r\n return this.monitor.importSession()\r\n })\r\n\r\n ipcMain.handle(IPC_CHANNELS.SESSION_DELETE, async (_event, sessionId: string) => {\r\n return this.monitor.deleteSession(sessionId)\r\n })\r\n\r\n // 渲染进程上报(可选)\r\n ipcMain.on(IPC_CHANNELS.RENDERER_REPORT, (_event, detail) => {\r\n this.monitor.updateRendererDetail(detail)\r\n })\r\n }\r\n\r\n /** 向监控面板推送快照数据 */\r\n pushSnapshot(dashboardWindow: BrowserWindow | null, data: unknown): void {\r\n if (dashboardWindow && !dashboardWindow.isDestroyed()) {\r\n dashboardWindow.webContents.send(IPC_CHANNELS.SNAPSHOT, data)\r\n }\r\n }\r\n\r\n /** 向监控面板推送异常事件 */\r\n pushAnomaly(dashboardWindow: BrowserWindow | null, data: unknown): void {\r\n if (dashboardWindow && !dashboardWindow.isDestroyed()) {\r\n dashboardWindow.webContents.send(IPC_CHANNELS.ANOMALY, data)\r\n }\r\n }\r\n\r\n /** 移除所有注册的 handlers */\r\n unregister(): void {\r\n const channels = Object.values(IPC_CHANNELS)\r\n for (const channel of channels) {\r\n ipcMain.removeHandler(channel)\r\n ipcMain.removeAllListeners(channel)\r\n }\r\n }\r\n}\r\n","/**\r\n * IPC 通道常量定义\r\n * 所有通道以 'emm:' 为前缀,避免与业务 IPC 冲突\r\n */\r\n\r\nexport const IPC_CHANNELS = {\r\n // === 数据推送(主进程 → 监控面板)===\r\n SNAPSHOT: 'emm:snapshot',\r\n ANOMALY: 'emm:anomaly',\r\n\r\n // === 会话控制(面板 → 主进程)===\r\n SESSION_START: 'emm:session:start',\r\n SESSION_STOP: 'emm:session:stop',\r\n SESSION_LIST: 'emm:session:list',\r\n SESSION_REPORT: 'emm:session:report',\r\n SESSION_COMPARE: 'emm:session:compare',\r\n\r\n // === 数据查询(面板 → 主进程)===\r\n SESSION_SNAPSHOTS: 'emm:session:snapshots',\r\n\r\n // === 工具操作(面板 → 主进程)===\r\n TRIGGER_GC: 'emm:gc',\r\n HEAP_SNAPSHOT: 'emm:heap-snapshot',\r\n MARK: 'emm:mark',\r\n CONFIG_UPDATE: 'emm:config:update',\r\n GET_CONFIG: 'emm:config:get',\r\n GET_SESSIONS: 'emm:sessions:get',\r\n\r\n // === 导入导出(面板 → 主进程)===\r\n SESSION_EXPORT: 'emm:session:export',\r\n SESSION_IMPORT: 'emm:session:import',\r\n SESSION_DELETE: 'emm:session:delete',\r\n\r\n // === 渲染进程上报(可选)===\r\n RENDERER_REPORT: 'emm:renderer:report',\r\n RENDERER_REQUEST: 'emm:renderer:request',\r\n} as const\r\n","/**\r\n * SDK 配置类型\r\n */\r\n\r\nimport type { AnomalyRule } from './anomaly'\r\n\r\nexport interface MonitorConfig {\r\n // ===== 开关控制 =====\r\n /** 总开关,默认 true */\r\n enabled: boolean\r\n /** 实例化后是否自动开始采集,默认 true */\r\n autoStart: boolean\r\n /** 启动后是否自动打开监控面板,默认 true */\r\n openDashboardOnStart: boolean\r\n\r\n // ===== 采集配置 =====\r\n /** 采集间隔 (ms),默认 2000 */\r\n collectInterval: number\r\n /** 落盘间隔 (条数),默认 60 */\r\n persistInterval: number\r\n /** 是否采集渲染进程 V8 详情(需要 preload 配合),默认 false */\r\n enableRendererDetail: boolean\r\n /** 是否采集 V8 堆空间详情,默认 true */\r\n enableV8HeapSpaces: boolean\r\n\r\n // ===== 异常检测 =====\r\n anomaly: {\r\n /** 是否启用异常检测,默认 true */\r\n enabled: boolean\r\n /** 检测间隔 (ms),默认 30000 */\r\n checkInterval: number\r\n /** 自定义检测规则(追加到内置规则) */\r\n rules: AnomalyRule[]\r\n }\r\n\r\n // ===== 存储配置 =====\r\n storage: {\r\n /** 数据存储目录,默认 app.getPath('userData') + '/memory-monitor' */\r\n directory: string\r\n /** 最大保留会话数,默认 50 */\r\n maxSessions: number\r\n /** 单次会话最大时长 (ms),默认 24h */\r\n maxSessionDuration: number\r\n }\r\n\r\n // ===== 监控面板配置 =====\r\n dashboard: {\r\n /** 窗口宽度,默认 1400 */\r\n width: number\r\n /** 窗口高度,默认 900 */\r\n height: number\r\n /** 是否置顶,默认 false */\r\n alwaysOnTop: boolean\r\n }\r\n\r\n // ===== 进程标注 =====\r\n /** 给窗口进程打标签,方便识别 */\r\n processLabels: Record<string, string>\r\n}\r\n\r\n/** 默认配置 */\r\nexport const DEFAULT_CONFIG: MonitorConfig = {\r\n enabled: true,\r\n autoStart: true,\r\n openDashboardOnStart: true,\r\n\r\n collectInterval: 2000,\r\n persistInterval: 60,\r\n enableRendererDetail: false,\r\n enableV8HeapSpaces: true,\r\n\r\n anomaly: {\r\n enabled: true,\r\n checkInterval: 30000,\r\n rules: [],\r\n },\r\n\r\n storage: {\r\n directory: '', // 运行时由 app.getPath('userData') 填充\r\n maxSessions: 50,\r\n maxSessionDuration: 24 * 60 * 60 * 1000,\r\n },\r\n\r\n dashboard: {\r\n width: 1400,\r\n height: 900,\r\n alwaysOnTop: false,\r\n },\r\n\r\n processLabels: {},\r\n}\r\n"],"mappings":"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","names":["import_electron","path","v8","import_events","import_events","os","cpus","import_electron","path","import_electron","fs"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/core/monitor.ts","../src/core/collector.ts","../src/core/persister.ts","../src/core/utils.ts","../src/core/session.ts","../src/core/anomaly.ts","../src/core/analyzer.ts","../src/core/dashboard.ts","../src/core/dashboard-protocol.ts","../src/core/dashboard-window-hooks.ts","../src/ipc/main-handler.ts","../src/ipc/channels.ts","../src/types/config.ts"],"sourcesContent":["/**\r\n * @electron-memory/monitor\r\n * \r\n * Electron 内存监控 SDK\r\n * 零代码入侵 - 一行代码接入任何 Electron 项目\r\n */\r\n\r\nexport { ElectronMemoryMonitor } from './core/monitor'\r\n/** 若延迟到 app.ready 之后才 new ElectronMemoryMonitor,请在本文件最顶部先调用此函数 */\r\nexport { registerDashboardSchemePrivileged } from './core/dashboard-protocol'\r\n\r\n// 类型导出\r\nexport type { MonitorConfig } from './types/config'\r\nexport type {\r\n MemorySnapshot,\r\n ProcessMemoryInfo,\r\n V8HeapStats,\r\n V8HeapDetailStats,\r\n V8HeapSpaceInfo,\r\n SystemMemoryInfo,\r\n RendererV8Detail,\r\n EventMark,\r\n} from './types/snapshot'\r\nexport type { TestSession, SessionIndex } from './types/session'\r\nexport type { AnomalyEvent, AnomalySeverity, AnomalyCategory, AnomalyRule } from './types/anomaly'\r\nexport type {\r\n SessionReport,\r\n CompareReport,\r\n MetricSummary,\r\n TrendInfo,\r\n Suggestion,\r\n MetricDiff,\r\n Regression,\r\n Improvement,\r\n GCResult,\r\n} from './types/report'\r\nexport { IPC_CHANNELS } from './ipc/channels'\r\n","/**\r\n * ElectronMemoryMonitor - SDK 主入口\r\n * \r\n * 门面类(Facade Pattern),提供简洁的 API\r\n * 一行代码即可接入:new ElectronMemoryMonitor()\r\n */\r\n\r\nimport { app } from 'electron'\r\nimport * as path from 'node:path'\r\nimport * as v8 from 'v8'\r\nimport { EventEmitter } from 'events'\r\nimport { MemoryCollector } from './collector'\r\nimport { DataPersister } from './persister'\r\nimport { SessionManager } from './session'\r\nimport { AnomalyDetector } from './anomaly'\r\nimport { Analyzer } from './analyzer'\r\nimport { DashboardManager } from './dashboard'\r\nimport { registerDashboardSchemePrivileged } from './dashboard-protocol'\r\nimport { IPCMainHandler } from '../ipc/main-handler'\r\nimport { DEFAULT_CONFIG, type MonitorConfig } from '../types/config'\r\nimport type { MemorySnapshot, RendererV8Detail } from '../types/snapshot'\r\nimport type { TestSession } from '../types/session'\r\nimport type { AnomalyEvent } from '../types/anomaly'\r\nimport type { SessionReport, CompareReport, GCResult } from '../types/report'\r\n\r\nexport class ElectronMemoryMonitor extends EventEmitter {\r\n private config: MonitorConfig\r\n private collector: MemoryCollector\r\n private persister!: DataPersister\r\n private sessionManager!: SessionManager\r\n private anomalyDetector: AnomalyDetector\r\n private analyzer: Analyzer\r\n private dashboard: DashboardManager\r\n private ipcHandler!: IPCMainHandler\r\n private started = false\r\n private latestSnapshot: MemorySnapshot | null = null\r\n\r\n constructor(config?: Partial<MonitorConfig>) {\r\n super()\r\n\r\n // 合并配置\r\n this.config = this.mergeConfig(config)\r\n\r\n // 如果 disabled,则不做任何事\r\n if (!this.config.enabled) {\r\n this.collector = null as unknown as MemoryCollector\r\n this.anomalyDetector = null as unknown as AnomalyDetector\r\n this.analyzer = null as unknown as Analyzer\r\n this.dashboard = null as unknown as DashboardManager\r\n return\r\n }\r\n\r\n // 必须在 app ready 之前注册自定义协议(与 loadFile 相比的第一性原理修复)\r\n registerDashboardSchemePrivileged()\r\n\r\n // 初始化各模块\r\n this.collector = new MemoryCollector(this.config)\r\n this.anomalyDetector = new AnomalyDetector(this.config)\r\n this.analyzer = new Analyzer()\r\n this.dashboard = new DashboardManager(this.config)\r\n\r\n // 如果 autoStart,则自动启动\r\n if (this.config.autoStart) {\r\n this.start()\r\n }\r\n }\r\n\r\n // ============ 生命周期 ============\r\n\r\n /** 启动监控 */\r\n async start(): Promise<void> {\r\n if (!this.config.enabled || this.started) return\r\n\r\n // 等待 app ready\r\n if (!app.isReady()) {\r\n await app.whenReady()\r\n }\r\n\r\n // 初始化存储目录\r\n const storageDir = this.config.storage.directory || path.join(app.getPath('userData'), 'memory-monitor')\r\n this.persister = new DataPersister(this.config, storageDir)\r\n this.sessionManager = new SessionManager(this.persister)\r\n\r\n // 注册 IPC\r\n this.ipcHandler = new IPCMainHandler(this)\r\n this.ipcHandler.register()\r\n\r\n // 连接采集器事件\r\n this.collector.on('snapshot', (snapshot: MemorySnapshot) => {\r\n this.onSnapshot(snapshot)\r\n })\r\n\r\n // 连接异常检测事件\r\n this.anomalyDetector.on('anomaly', (anomaly: AnomalyEvent) => {\r\n this.emit('anomaly', anomaly)\r\n this.ipcHandler.pushAnomaly(this.dashboard.getWindow(), anomaly)\r\n })\r\n\r\n // 启动采集\r\n this.collector.start()\r\n\r\n // 启动异常检测\r\n this.anomalyDetector.start()\r\n\r\n // 打开监控面板\r\n if (this.config.openDashboardOnStart) {\r\n this.openDashboard()\r\n }\r\n\r\n // 清理过期会话\r\n this.persister.cleanOldSessions()\r\n\r\n this.started = true\r\n }\r\n\r\n /** 停止监控 */\r\n async stop(): Promise<void> {\r\n if (!this.started) return\r\n\r\n this.collector.stop()\r\n this.anomalyDetector.stop()\r\n\r\n // 如果有正在运行的会话,结束它\r\n const currentSession = this.sessionManager.getCurrentSession()\r\n if (currentSession) {\r\n await this.stopSession()\r\n }\r\n\r\n this.persister.close()\r\n this.started = false\r\n }\r\n\r\n /** 销毁实例 */\r\n async destroy(): Promise<void> {\r\n await this.stop()\r\n this.dashboard.destroy()\r\n if (this.ipcHandler) {\r\n this.ipcHandler.unregister()\r\n }\r\n this.removeAllListeners()\r\n }\r\n\r\n // ============ 会话控制 ============\r\n\r\n /** 开始新会话 */\r\n startSession(label: string, description?: string): string {\r\n if (!this.started) {\r\n throw new Error('Monitor is not started')\r\n }\r\n\r\n const session = this.sessionManager.startSession(label, description)\r\n this.collector.setSessionId(session.id)\r\n this.anomalyDetector.clearAnomalies()\r\n\r\n return session.id\r\n }\r\n\r\n /** 结束当前会话 */\r\n async stopSession(): Promise<SessionReport | null> {\r\n if (!this.started) return null\r\n\r\n const session = this.sessionManager.getCurrentSession()\r\n if (!session) return null\r\n\r\n // 结束会话\r\n const completedSession = this.sessionManager.endSession()\r\n if (!completedSession) return null\r\n\r\n this.collector.setSessionId(null)\r\n\r\n // 生成报告\r\n const snapshots = this.persister.readSessionSnapshots(completedSession.id)\r\n const anomalies = this.anomalyDetector.getAnomalies()\r\n\r\n const report = this.analyzer.generateReport(\r\n completedSession.id,\r\n completedSession.label,\r\n completedSession.description,\r\n completedSession.startTime,\r\n completedSession.endTime!,\r\n snapshots,\r\n anomalies,\r\n completedSession.dataFile\r\n )\r\n\r\n // 保存报告\r\n const reportPath = path.join(this.persister.getStorageDir(), completedSession.id, 'report.json')\r\n const fs = await import('fs')\r\n fs.writeFileSync(reportPath, JSON.stringify(report, null, 2), 'utf-8')\r\n\r\n this.emit('session-end', report)\r\n return report\r\n }\r\n\r\n // ============ 监控面板 ============\r\n\r\n /** 打开监控面板 */\r\n openDashboard(): void {\r\n this.dashboard.open()\r\n\r\n // 设置监控窗口 ID,用于在进程列表中标记\r\n const wcId = this.dashboard.getWebContentsId()\r\n this.collector.setMonitorWindowId(wcId)\r\n }\r\n\r\n /** 关闭监控面板 */\r\n closeDashboard(): void {\r\n this.dashboard.close()\r\n this.collector.setMonitorWindowId(null)\r\n }\r\n\r\n // ============ 数据访问 ============\r\n\r\n /** 获取当前最新快照 */\r\n getCurrentSnapshot(): MemorySnapshot | null {\r\n return this.latestSnapshot\r\n }\r\n\r\n /** 获取历史会话列表 */\r\n async getSessions(): Promise<TestSession[]> {\r\n return this.sessionManager.getSessions()\r\n }\r\n\r\n /** 获取指定会话报告 */\r\n async getSessionReport(sessionId: string): Promise<SessionReport | null> {\r\n const fs = await import('fs')\r\n const reportPath = path.join(this.persister.getStorageDir(), sessionId, 'report.json')\r\n\r\n try {\r\n const content = fs.readFileSync(reportPath, 'utf-8')\r\n return JSON.parse(content) as SessionReport\r\n } catch {\r\n // 报告不存在,尝试从原始数据重新生成\r\n const session = this.sessionManager.getSession(sessionId)\r\n if (!session || !session.endTime) return null\r\n\r\n const snapshots = this.persister.readSessionSnapshots(sessionId)\r\n if (snapshots.length === 0) return null\r\n\r\n return this.analyzer.generateReport(\r\n session.id,\r\n session.label,\r\n session.description,\r\n session.startTime,\r\n session.endTime,\r\n snapshots,\r\n [],\r\n session.dataFile\r\n )\r\n }\r\n }\r\n\r\n /** 获取指定会话的快照数据(支持时间过滤和降采样) */\r\n async getSessionSnapshots(\r\n sessionId: string,\r\n startTime?: number,\r\n endTime?: number,\r\n maxPoints?: number\r\n ): Promise<MemorySnapshot[]> {\r\n let snapshots = this.persister.readSessionSnapshots(sessionId)\r\n\r\n // 时间范围过滤\r\n if (startTime != null) {\r\n snapshots = snapshots.filter((s) => s.timestamp >= startTime)\r\n }\r\n if (endTime != null) {\r\n snapshots = snapshots.filter((s) => s.timestamp <= endTime)\r\n }\r\n\r\n // 降采样:如果数据点超过 maxPoints,均匀采样\r\n const limit = maxPoints ?? 600\r\n if (snapshots.length > limit) {\r\n const step = snapshots.length / limit\r\n const sampled: MemorySnapshot[] = []\r\n for (let i = 0; i < limit; i++) {\r\n sampled.push(snapshots[Math.round(i * step)])\r\n }\r\n // 确保包含最后一个点\r\n if (sampled[sampled.length - 1] !== snapshots[snapshots.length - 1]) {\r\n sampled[sampled.length - 1] = snapshots[snapshots.length - 1]\r\n }\r\n snapshots = sampled\r\n }\r\n\r\n return snapshots\r\n }\r\n\r\n /** 对比两个会话 */\r\n async compareSessions(baseId: string, targetId: string): Promise<CompareReport | null> {\r\n const baseReport = await this.getSessionReport(baseId)\r\n const targetReport = await this.getSessionReport(targetId)\r\n\r\n if (!baseReport || !targetReport) return null\r\n\r\n return this.analyzer.compareReports(baseReport, targetReport)\r\n }\r\n\r\n /** 导出会话数据(供 IPC 调用,弹出保存对话框) */\r\n async exportSession(sessionId: string): Promise<{ success: boolean; filePath?: string; error?: string }> {\r\n try {\r\n const { dialog } = await import('electron')\r\n const session = this.sessionManager.getSession(sessionId)\r\n if (!session) return { success: false, error: '会话不存在' }\r\n\r\n const defaultName = `emm-${session.label.replace(/[^a-zA-Z0-9\\u4e00-\\u9fa5_-]/g, '_')}-${new Date(session.startTime).toISOString().slice(0, 10)}.emmsession`\r\n\r\n const result = await dialog.showSaveDialog({\r\n title: '导出会话数据',\r\n defaultPath: defaultName,\r\n filters: [\r\n { name: 'EMM Session', extensions: ['emmsession'] },\r\n { name: 'JSON 文件', extensions: ['json'] },\r\n { name: '所有文件', extensions: ['*'] },\r\n ],\r\n })\r\n\r\n if (result.canceled || !result.filePath) {\r\n return { success: false, error: '用户取消' }\r\n }\r\n\r\n const exportData = this.persister.exportSession(sessionId)\r\n const fs = await import('fs')\r\n const fileContent = JSON.stringify({\r\n version: 1,\r\n exportTime: Date.now(),\r\n ...exportData,\r\n }, null, 2)\r\n fs.writeFileSync(result.filePath, fileContent, 'utf-8')\r\n\r\n return { success: true, filePath: result.filePath }\r\n } catch (err) {\r\n return { success: false, error: String(err) }\r\n }\r\n }\r\n\r\n /** 导入会话数据(供 IPC 调用,弹出打开对话框) */\r\n async importSession(): Promise<{ success: boolean; session?: TestSession; error?: string }> {\r\n try {\r\n const { dialog } = await import('electron')\r\n\r\n const result = await dialog.showOpenDialog({\r\n title: '导入会话数据',\r\n filters: [\r\n { name: 'EMM Session', extensions: ['emmsession'] },\r\n { name: 'JSON 文件', extensions: ['json'] },\r\n { name: '所有文件', extensions: ['*'] },\r\n ],\r\n properties: ['openFile'],\r\n })\r\n\r\n if (result.canceled || result.filePaths.length === 0) {\r\n return { success: false, error: '用户取消' }\r\n }\r\n\r\n const fs = await import('fs')\r\n const content = fs.readFileSync(result.filePaths[0], 'utf-8')\r\n const parsed = JSON.parse(content)\r\n\r\n if (!parsed.meta || !parsed.snapshots) {\r\n return { success: false, error: '文件格式不正确,缺少 meta 或 snapshots 数据' }\r\n }\r\n\r\n const session = this.persister.importSession({\r\n meta: parsed.meta,\r\n snapshots: parsed.snapshots,\r\n report: parsed.report || null,\r\n })\r\n\r\n return { success: true, session }\r\n } catch (err) {\r\n return { success: false, error: String(err) }\r\n }\r\n }\r\n\r\n /** 删除指定会话 */\r\n async deleteSession(sessionId: string): Promise<boolean> {\r\n return this.persister.deleteSession(sessionId)\r\n }\r\n\r\n // ============ 工具方法 ============\r\n\r\n /** 手动触发 GC */\r\n async triggerGC(): Promise<GCResult> {\r\n const beforeMem = process.memoryUsage()\r\n\r\n if (global.gc) {\r\n global.gc()\r\n } else {\r\n // 尝试通过 v8 flag 触发\r\n try {\r\n v8.writeHeapSnapshot // 触发 GC 的 workaround\r\n } catch {\r\n // 忽略\r\n }\r\n }\r\n\r\n // 等待一小段时间让 GC 完成\r\n await new Promise((resolve) => setTimeout(resolve, 100))\r\n\r\n const afterMem = process.memoryUsage()\r\n const freed = beforeMem.heapUsed - afterMem.heapUsed\r\n\r\n return {\r\n beforeHeapUsed: beforeMem.heapUsed,\r\n afterHeapUsed: afterMem.heapUsed,\r\n freed,\r\n freedPercent: beforeMem.heapUsed > 0 ? (freed / beforeMem.heapUsed) * 100 : 0,\r\n timestamp: Date.now(),\r\n }\r\n }\r\n\r\n /** 导出堆快照 */\r\n async takeHeapSnapshot(filePath?: string): Promise<string> {\r\n const snapshotPath = filePath || path.join(\r\n this.persister.getStorageDir(),\r\n `heap-${Date.now()}.heapsnapshot`\r\n )\r\n v8.writeHeapSnapshot(snapshotPath)\r\n return snapshotPath\r\n }\r\n\r\n /** 添加事件标记 */\r\n mark(label: string, metadata?: Record<string, unknown>): void {\r\n this.collector.addMark(label, metadata)\r\n }\r\n\r\n /** 更新渲染进程 V8 详情 */\r\n updateRendererDetail(detail: RendererV8Detail): void {\r\n this.collector.updateRendererDetail(detail)\r\n }\r\n\r\n /** 获取当前配置 */\r\n getConfig(): MonitorConfig {\r\n return { ...this.config }\r\n }\r\n\r\n // ============ 事件类型重载 ============\r\n\r\n on(event: 'snapshot', handler: (data: MemorySnapshot) => void): this\r\n on(event: 'anomaly', handler: (event: AnomalyEvent) => void): this\r\n on(event: 'session-end', handler: (report: SessionReport) => void): this\r\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\r\n on(event: string | symbol, handler: (...args: any[]) => void): this {\r\n return super.on(event, handler)\r\n }\r\n\r\n // ============ 私有方法 ============\r\n\r\n private onSnapshot(snapshot: MemorySnapshot): void {\r\n this.latestSnapshot = snapshot\r\n\r\n // 写入持久化\r\n if (this.sessionManager.getCurrentSession()) {\r\n this.persister.writeSnapshot(snapshot)\r\n this.sessionManager.incrementSnapshotCount()\r\n }\r\n\r\n // 喂给异常检测\r\n this.anomalyDetector.addSnapshot(snapshot)\r\n\r\n // 推送给面板\r\n this.ipcHandler?.pushSnapshot(this.dashboard.getWindow(), snapshot)\r\n\r\n // 触发事件\r\n this.emit('snapshot', snapshot)\r\n }\r\n\r\n private mergeConfig(userConfig?: Partial<MonitorConfig>): MonitorConfig {\r\n if (!userConfig) {\r\n return {\r\n ...DEFAULT_CONFIG,\r\n dashboard: {\r\n ...DEFAULT_CONFIG.dashboard,\r\n openDevToolsOnStart: process.env.LAUNCHER_MEMORY_MONITOR_DEVTOOLS === '1',\r\n },\r\n }\r\n }\r\n\r\n return {\r\n ...DEFAULT_CONFIG,\r\n ...userConfig,\r\n anomaly: {\r\n ...DEFAULT_CONFIG.anomaly,\r\n ...(userConfig.anomaly || {}),\r\n },\r\n storage: {\r\n ...DEFAULT_CONFIG.storage,\r\n ...(userConfig.storage || {}),\r\n },\r\n dashboard: {\r\n ...DEFAULT_CONFIG.dashboard,\r\n ...(userConfig.dashboard || {}),\r\n openDevToolsOnStart:\r\n userConfig.dashboard?.openDevToolsOnStart !== undefined\r\n ? userConfig.dashboard.openDevToolsOnStart\r\n : process.env.LAUNCHER_MEMORY_MONITOR_DEVTOOLS === '1',\r\n },\r\n processLabels: {\r\n ...DEFAULT_CONFIG.processLabels,\r\n ...(userConfig.processLabels || {}),\r\n },\r\n }\r\n }\r\n}\r\n","/**\r\n * MemoryCollector - 内存数据采集器\r\n * \r\n * 核心职责:定时从各种数据源采集内存信息,组装成 MemorySnapshot\r\n * 运行在主进程中,不需要渲染进程配合即可获取所有进程的内存概览\r\n */\r\n\r\nimport { app, BrowserWindow, webContents } from 'electron'\r\nimport * as v8 from 'v8'\r\nimport * as os from 'os'\r\nimport { EventEmitter } from 'events'\r\nimport type {\r\n MemorySnapshot,\r\n ProcessMemoryInfo,\r\n V8HeapStats,\r\n V8HeapDetailStats,\r\n V8HeapSpaceInfo,\r\n SystemMemoryInfo,\r\n EventMark,\r\n RendererV8Detail,\r\n} from '../types/snapshot'\r\nimport type { MonitorConfig } from '../types/config'\r\n\r\nexport class MemoryCollector extends EventEmitter {\r\n private config: MonitorConfig\r\n private timer: ReturnType<typeof setInterval> | null = null\r\n private seq = 0\r\n private currentSessionId: string | null = null\r\n private pendingMarks: EventMark[] = []\r\n private rendererDetails: Map<number, RendererV8Detail> = new Map()\r\n private monitorWindowId: number | null = null\r\n\r\n constructor(config: MonitorConfig) {\r\n super()\r\n this.config = config\r\n }\r\n\r\n /** 设置监控面板的 webContents ID,用于标记 */\r\n setMonitorWindowId(id: number | null): void {\r\n this.monitorWindowId = id\r\n }\r\n\r\n /** 设置当前会话 ID */\r\n setSessionId(sessionId: string | null): void {\r\n this.currentSessionId = sessionId\r\n }\r\n\r\n /** 添加事件标记 */\r\n addMark(label: string, metadata?: Record<string, unknown>): void {\r\n this.pendingMarks.push({\r\n timestamp: Date.now(),\r\n label,\r\n metadata,\r\n })\r\n }\r\n\r\n /** 更新渲染进程 V8 详情 */\r\n updateRendererDetail(detail: RendererV8Detail): void {\r\n this.rendererDetails.set(detail.webContentsId, detail)\r\n }\r\n\r\n /** 开始采集 */\r\n start(): void {\r\n if (this.timer) return\r\n\r\n // 立即采集一次\r\n this.collect()\r\n\r\n this.timer = setInterval(() => {\r\n this.collect()\r\n }, this.config.collectInterval)\r\n }\r\n\r\n /** 停止采集 */\r\n stop(): void {\r\n if (this.timer) {\r\n clearInterval(this.timer)\r\n this.timer = null\r\n }\r\n }\r\n\r\n /** 执行一次采集 */\r\n private collect(): void {\r\n try {\r\n const snapshot = this.buildSnapshot()\r\n this.emit('snapshot', snapshot)\r\n } catch (err) {\r\n this.emit('error', err)\r\n }\r\n }\r\n\r\n /** 构建完整的内存快照 */\r\n private buildSnapshot(): MemorySnapshot {\r\n const timestamp = Date.now()\r\n const processes = this.collectProcesses()\r\n const mainProcessMemory = this.collectMainProcessMemory()\r\n const mainProcessV8Detail = this.collectMainProcessV8Detail()\r\n const system = this.collectSystemMemory()\r\n\r\n // 计算总工作集大小(排除监控面板自身进程)\r\n const totalWorkingSetSize = processes.reduce(\r\n (sum, p) => p.isMonitorProcess ? sum : sum + p.memory.workingSetSize,\r\n 0\r\n )\r\n\r\n // 收集待处理的事件标记\r\n const marks = this.pendingMarks.length > 0 ? [...this.pendingMarks] : undefined\r\n this.pendingMarks = []\r\n\r\n // 收集渲染进程 V8 详情\r\n const rendererDetails = this.rendererDetails.size > 0\r\n ? Array.from(this.rendererDetails.values())\r\n : undefined\r\n\r\n const snapshot: MemorySnapshot = {\r\n timestamp,\r\n sessionId: this.currentSessionId ?? undefined,\r\n seq: this.seq++,\r\n processes,\r\n totalWorkingSetSize,\r\n mainProcessMemory,\r\n mainProcessV8Detail,\r\n system,\r\n rendererDetails,\r\n marks,\r\n }\r\n\r\n return snapshot\r\n }\r\n\r\n /** 采集所有进程信息 */\r\n private collectProcesses(): ProcessMemoryInfo[] {\r\n const metrics = app.getAppMetrics()\r\n const wcList = webContents.getAllWebContents()\r\n\r\n // 构建 PID → webContents 映射\r\n const pidToWc = new Map<number, Electron.WebContents>()\r\n for (const wc of wcList) {\r\n try {\r\n const pid = wc.getOSProcessId()\r\n pidToWc.set(pid, wc)\r\n } catch {\r\n // webContents 可能已销毁\r\n }\r\n }\r\n\r\n // 构建 webContentsId → BrowserWindow 标题映射\r\n const wcIdToTitle = new Map<number, string>()\r\n const allWindows = BrowserWindow.getAllWindows()\r\n for (const win of allWindows) {\r\n try {\r\n wcIdToTitle.set(win.webContents.id, win.getTitle())\r\n } catch {\r\n // 窗口可能已销毁\r\n }\r\n }\r\n\r\n return metrics.map((metric) => {\r\n const wc = pidToWc.get(metric.pid)\r\n let windowTitle: string | undefined\r\n let webContentsId: number | undefined\r\n let isMonitorProcess = false\r\n\r\n if (wc) {\r\n webContentsId = wc.id\r\n windowTitle = wcIdToTitle.get(wc.id)\r\n\r\n // 检查是否是监控面板自身的进程\r\n if (this.monitorWindowId !== null && wc.id === this.monitorWindowId) {\r\n isMonitorProcess = true\r\n windowTitle = '[Memory Monitor]'\r\n }\r\n }\r\n\r\n // 应用用户自定义标签\r\n let name = windowTitle\r\n if (windowTitle && this.config.processLabels[windowTitle]) {\r\n name = this.config.processLabels[windowTitle]\r\n }\r\n\r\n const info: ProcessMemoryInfo = {\r\n pid: metric.pid,\r\n type: metric.type,\r\n name,\r\n isMonitorProcess,\r\n cpu: {\r\n percentCPUUsage: metric.cpu.percentCPUUsage,\r\n idleWakeupsPerSecond: metric.cpu.idleWakeupsPerSecond,\r\n },\r\n memory: {\r\n workingSetSize: metric.memory.workingSetSize,\r\n peakWorkingSetSize: metric.memory.peakWorkingSetSize,\r\n privateBytes: (metric.memory as Record<string, number>).privateBytes,\r\n },\r\n webContentsId,\r\n windowTitle,\r\n }\r\n\r\n return info\r\n })\r\n }\r\n\r\n /** 采集主进程 Node.js 内存 */\r\n private collectMainProcessMemory(): V8HeapStats {\r\n const mem = process.memoryUsage()\r\n return {\r\n heapUsed: mem.heapUsed,\r\n heapTotal: mem.heapTotal,\r\n external: mem.external,\r\n arrayBuffers: mem.arrayBuffers,\r\n rss: mem.rss,\r\n }\r\n }\r\n\r\n /** 采集主进程 V8 详细统计 */\r\n private collectMainProcessV8Detail(): V8HeapDetailStats {\r\n const mem = process.memoryUsage()\r\n // 注意:v8.getHeapStatistics() 返回 snake_case 字段名\r\n const heapStats = v8.getHeapStatistics() as Record<string, number>\r\n\r\n let heapSpaces: V8HeapSpaceInfo[] | undefined\r\n if (this.config.enableV8HeapSpaces) {\r\n // 注意:v8.getHeapSpaceStatistics() 返回 snake_case 字段名\r\n heapSpaces = v8.getHeapSpaceStatistics().map((space: Record<string, unknown>) => ({\r\n name: (space.space_name ?? space.spaceName) as string,\r\n size: (space.space_size ?? space.spaceSize) as number,\r\n usedSize: (space.space_used_size ?? space.spaceUsedSize) as number,\r\n availableSize: (space.space_available_size ?? space.spaceAvailableSize) as number,\r\n physicalSize: (space.physical_space_size ?? space.physicalSpaceSize) as number,\r\n }))\r\n }\r\n\r\n return {\r\n heapUsed: mem.heapUsed,\r\n heapTotal: mem.heapTotal,\r\n external: mem.external,\r\n arrayBuffers: mem.arrayBuffers,\r\n rss: mem.rss,\r\n totalHeapSize: (heapStats.total_heap_size ?? heapStats.totalHeapSize) as number,\r\n usedHeapSize: (heapStats.used_heap_size ?? heapStats.usedHeapSize) as number,\r\n heapSizeLimit: (heapStats.heap_size_limit ?? heapStats.heapSizeLimit) as number,\r\n mallocedMemory: (heapStats.malloced_memory ?? heapStats.mallocedMemory) as number,\r\n peakMallocedMemory: (heapStats.peak_malloced_memory ?? heapStats.peakMallocedMemory) as number,\r\n numberOfDetachedContexts: (heapStats.number_of_detached_contexts ?? heapStats.numberOfDetachedContexts) as number,\r\n numberOfNativeContexts: (heapStats.number_of_native_contexts ?? heapStats.numberOfNativeContexts) as number,\r\n heapSpaces,\r\n }\r\n }\r\n\r\n /** 采集系统内存 */\r\n private collectSystemMemory(): SystemMemoryInfo {\r\n const total = os.totalmem()\r\n const free = os.freemem()\r\n const used = total - free\r\n return {\r\n total,\r\n free,\r\n used,\r\n usagePercent: Math.round((used / total) * 10000) / 100,\r\n }\r\n }\r\n}\r\n","/**\r\n * DataPersister - 数据持久化模块\r\n * \r\n * 使用 JSONL (JSON Lines) 格式流式存储快照数据\r\n * 追加写入,不阻塞采集\r\n */\r\n\r\nimport * as fs from 'fs'\r\nimport * as path from 'path'\r\nimport type { MemorySnapshot } from '../types/snapshot'\r\nimport type { TestSession, SessionIndex } from '../types/session'\r\nimport type { MonitorConfig } from '../types/config'\r\n\r\nexport class DataPersister {\r\n private config: MonitorConfig\r\n private storageDir: string\r\n private buffer: MemorySnapshot[] = []\r\n private currentStream: fs.WriteStream | null = null\r\n private currentDataFile: string | null = null\r\n\r\n constructor(config: MonitorConfig, storageDir: string) {\r\n this.config = config\r\n this.storageDir = storageDir\r\n this.ensureDirectory(this.storageDir)\r\n }\r\n\r\n /** 获取存储目录 */\r\n getStorageDir(): string {\r\n return this.storageDir\r\n }\r\n\r\n /** 创建新的会话数据文件 */\r\n createSessionFiles(sessionId: string): { dataFile: string; metaFile: string } {\r\n const sessionDir = path.join(this.storageDir, sessionId)\r\n this.ensureDirectory(sessionDir)\r\n\r\n const dataFile = path.join(sessionDir, 'snapshots.jsonl')\r\n const metaFile = path.join(sessionDir, 'meta.json')\r\n\r\n // 关闭之前的流\r\n this.closeStream()\r\n\r\n // 打开新的写入流\r\n this.currentDataFile = dataFile\r\n this.currentStream = fs.createWriteStream(dataFile, { flags: 'a' })\r\n\r\n return { dataFile, metaFile }\r\n }\r\n\r\n /** 写入快照数据 */\r\n writeSnapshot(snapshot: MemorySnapshot): void {\r\n this.buffer.push(snapshot)\r\n\r\n // 达到批量写入阈值\r\n if (this.buffer.length >= this.config.persistInterval) {\r\n this.flush()\r\n }\r\n }\r\n\r\n /** 刷新缓冲区到磁盘 */\r\n flush(): void {\r\n if (this.buffer.length === 0 || !this.currentStream) return\r\n\r\n const lines = this.buffer.map((s) => JSON.stringify(s)).join('\\n') + '\\n'\r\n this.currentStream.write(lines)\r\n this.buffer = []\r\n }\r\n\r\n /** 保存会话元信息 */\r\n saveSessionMeta(session: TestSession): void {\r\n const metaFile = path.join(this.storageDir, session.id, 'meta.json')\r\n fs.writeFileSync(metaFile, JSON.stringify(session, null, 2), 'utf-8')\r\n\r\n // 更新索引\r\n this.updateSessionIndex(session)\r\n }\r\n\r\n /** 读取会话元信息 */\r\n readSessionMeta(sessionId: string): TestSession | null {\r\n const metaFile = path.join(this.storageDir, sessionId, 'meta.json')\r\n try {\r\n const content = fs.readFileSync(metaFile, 'utf-8')\r\n return JSON.parse(content) as TestSession\r\n } catch {\r\n return null\r\n }\r\n }\r\n\r\n /** 获取所有会话列表 */\r\n getSessions(): TestSession[] {\r\n const indexFile = path.join(this.storageDir, 'sessions.json')\r\n try {\r\n const content = fs.readFileSync(indexFile, 'utf-8')\r\n const index = JSON.parse(content) as SessionIndex\r\n return index.sessions\r\n } catch {\r\n return []\r\n }\r\n }\r\n\r\n /** 读取会话的所有快照数据 */\r\n readSessionSnapshots(sessionId: string): MemorySnapshot[] {\r\n const dataFile = path.join(this.storageDir, sessionId, 'snapshots.jsonl')\r\n try {\r\n const content = fs.readFileSync(dataFile, 'utf-8')\r\n const lines = content.trim().split('\\n').filter(Boolean)\r\n return lines.map((line) => JSON.parse(line) as MemorySnapshot)\r\n } catch {\r\n return []\r\n }\r\n }\r\n\r\n /** 关闭流并刷新缓冲区 */\r\n close(): void {\r\n this.flush()\r\n this.closeStream()\r\n }\r\n\r\n /** 清理过期会话 */\r\n cleanOldSessions(): void {\r\n const sessions = this.getSessions()\r\n if (sessions.length <= this.config.storage.maxSessions) return\r\n\r\n // 删除最旧的会话\r\n const toRemove = sessions\r\n .sort((a, b) => a.startTime - b.startTime)\r\n .slice(0, sessions.length - this.config.storage.maxSessions)\r\n\r\n for (const session of toRemove) {\r\n const sessionDir = path.join(this.storageDir, session.id)\r\n try {\r\n fs.rmSync(sessionDir, { recursive: true, force: true })\r\n } catch {\r\n // 忽略删除失败\r\n }\r\n }\r\n\r\n // 更新索引\r\n const remaining = sessions.filter((s) => !toRemove.includes(s))\r\n this.saveSessionIndex(remaining)\r\n }\r\n\r\n /** 导出会话数据为单个 JSON 包 */\r\n exportSession(sessionId: string): {\r\n meta: TestSession | null\r\n snapshots: string\r\n report: string | null\r\n } {\r\n const meta = this.readSessionMeta(sessionId)\r\n const snapshotsFile = path.join(this.storageDir, sessionId, 'snapshots.jsonl')\r\n const reportFile = path.join(this.storageDir, sessionId, 'report.json')\r\n\r\n let snapshots = ''\r\n try { snapshots = fs.readFileSync(snapshotsFile, 'utf-8') } catch { /* empty */ }\r\n\r\n let report: string | null = null\r\n try { report = fs.readFileSync(reportFile, 'utf-8') } catch { /* empty */ }\r\n\r\n return { meta, snapshots, report }\r\n }\r\n\r\n /** 导入会话数据 */\r\n importSession(data: {\r\n meta: TestSession\r\n snapshots: string\r\n report: string | null\r\n }): TestSession {\r\n const { meta, snapshots, report } = data\r\n const sessionDir = path.join(this.storageDir, meta.id)\r\n this.ensureDirectory(sessionDir)\r\n\r\n // 写入快照文件\r\n const snapshotsFile = path.join(sessionDir, 'snapshots.jsonl')\r\n fs.writeFileSync(snapshotsFile, snapshots, 'utf-8')\r\n\r\n // 更新路径为本地路径\r\n meta.dataFile = snapshotsFile\r\n meta.metaFile = path.join(sessionDir, 'meta.json')\r\n\r\n // 写入元信息\r\n fs.writeFileSync(meta.metaFile, JSON.stringify(meta, null, 2), 'utf-8')\r\n\r\n // 写入报告\r\n if (report) {\r\n const reportFile = path.join(sessionDir, 'report.json')\r\n fs.writeFileSync(reportFile, report, 'utf-8')\r\n }\r\n\r\n // 更新索引\r\n this.updateSessionIndex(meta)\r\n\r\n return meta\r\n }\r\n\r\n /** 删除指定会话 */\r\n deleteSession(sessionId: string): boolean {\r\n const sessionDir = path.join(this.storageDir, sessionId)\r\n try {\r\n fs.rmSync(sessionDir, { recursive: true, force: true })\r\n } catch {\r\n return false\r\n }\r\n\r\n // 更新索引\r\n const sessions = this.getSessions().filter((s) => s.id !== sessionId)\r\n this.saveSessionIndex(sessions)\r\n return true\r\n }\r\n\r\n // ===== 私有方法 =====\r\n\r\n private closeStream(): void {\r\n if (this.currentStream) {\r\n this.currentStream.end()\r\n this.currentStream = null\r\n this.currentDataFile = null\r\n }\r\n }\r\n\r\n private updateSessionIndex(session: TestSession): void {\r\n const sessions = this.getSessions()\r\n const existingIdx = sessions.findIndex((s) => s.id === session.id)\r\n if (existingIdx >= 0) {\r\n sessions[existingIdx] = session\r\n } else {\r\n sessions.push(session)\r\n }\r\n this.saveSessionIndex(sessions)\r\n }\r\n\r\n private saveSessionIndex(sessions: TestSession[]): void {\r\n const indexFile = path.join(this.storageDir, 'sessions.json')\r\n const index: SessionIndex = {\r\n sessions,\r\n lastUpdated: Date.now(),\r\n }\r\n fs.writeFileSync(indexFile, JSON.stringify(index, null, 2), 'utf-8')\r\n }\r\n\r\n private ensureDirectory(dir: string): void {\r\n if (!fs.existsSync(dir)) {\r\n fs.mkdirSync(dir, { recursive: true })\r\n }\r\n }\r\n}\r\n","/**\r\n * 工具函数\r\n */\r\n\r\n/** 简单的 UUID v4 生成(不依赖外部库) */\r\nexport function v4(): string {\r\n return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {\r\n const r = (Math.random() * 16) | 0\r\n const v = c === 'x' ? r : (r & 0x3) | 0x8\r\n return v.toString(16)\r\n })\r\n}\r\n\r\n/** 格式化字节数 */\r\nexport function formatBytes(bytes: number | undefined | null): string {\r\n if (bytes == null || isNaN(bytes)) return '0 B'\r\n if (bytes === 0) return '0 B'\r\n const k = 1024\r\n const sizes = ['B', 'KB', 'MB', 'GB']\r\n const i = Math.floor(Math.log(Math.abs(bytes)) / Math.log(k))\r\n const idx = Math.min(i, sizes.length - 1)\r\n return parseFloat((bytes / Math.pow(k, idx)).toFixed(2)) + ' ' + sizes[idx]\r\n}\r\n\r\n/** KB 转换为 bytes */\r\nexport function kbToBytes(kb: number): number {\r\n return kb * 1024\r\n}\r\n\r\n/** bytes 转换为 KB */\r\nexport function bytesToKb(bytes: number): number {\r\n return bytes / 1024\r\n}\r\n\r\n/** bytes 转换为 MB */\r\nexport function bytesToMb(bytes: number): number {\r\n return bytes / (1024 * 1024)\r\n}\r\n\r\n/** 计算数组的百分位数 */\r\nexport function percentile(arr: number[], p: number): number {\r\n if (arr.length === 0) return 0\r\n const sorted = [...arr].sort((a, b) => a - b)\r\n const idx = (p / 100) * (sorted.length - 1)\r\n const lower = Math.floor(idx)\r\n const upper = Math.ceil(idx)\r\n if (lower === upper) return sorted[lower]\r\n return sorted[lower] + (sorted[upper] - sorted[lower]) * (idx - lower)\r\n}\r\n\r\n/** 计算平均值 */\r\nexport function average(arr: number[]): number {\r\n if (arr.length === 0) return 0\r\n return arr.reduce((a, b) => a + b, 0) / arr.length\r\n}\r\n\r\n/** 线性回归 */\r\nexport function linearRegression(values: number[], timestamps: number[]): { slope: number; r2: number; intercept: number } {\r\n const n = values.length\r\n if (n < 2) return { slope: 0, r2: 0, intercept: values[0] || 0 }\r\n\r\n // 标准化时间戳(秒)\r\n const t0 = timestamps[0]\r\n const xs = timestamps.map((t) => (t - t0) / 1000)\r\n\r\n const sumX = xs.reduce((a, b) => a + b, 0)\r\n const sumY = values.reduce((a, b) => a + b, 0)\r\n const sumXY = xs.reduce((sum, x, i) => sum + x * values[i], 0)\r\n const sumX2 = xs.reduce((sum, x) => sum + x * x, 0)\r\n const sumY2 = values.reduce((sum, y) => sum + y * y, 0)\r\n\r\n const denominator = n * sumX2 - sumX * sumX\r\n if (denominator === 0) return { slope: 0, r2: 0, intercept: sumY / n }\r\n\r\n const slope = (n * sumXY - sumX * sumY) / denominator\r\n const intercept = (sumY - slope * sumX) / n\r\n\r\n // R² 拟合优度\r\n const meanY = sumY / n\r\n const ssTotal = sumY2 - n * meanY * meanY\r\n const ssResidual = values.reduce((sum, y, i) => {\r\n const predicted = intercept + slope * xs[i]\r\n return sum + (y - predicted) ** 2\r\n }, 0)\r\n\r\n const r2 = ssTotal === 0 ? 0 : 1 - ssResidual / ssTotal\r\n\r\n return { slope, r2, intercept }\r\n}\r\n","/**\r\n * SessionManager - 会话管理\r\n * \r\n * 管理测试会话的生命周期:创建、结束、查询\r\n */\r\n\r\nimport { v4 as generateId } from './utils'\r\nimport type { TestSession } from '../types/session'\r\nimport type { DataPersister } from './persister'\r\n\r\nexport class SessionManager {\r\n private persister: DataPersister\r\n private currentSession: TestSession | null = null\r\n\r\n constructor(persister: DataPersister) {\r\n this.persister = persister\r\n }\r\n\r\n /** 获取当前正在运行的会话 */\r\n getCurrentSession(): TestSession | null {\r\n return this.currentSession\r\n }\r\n\r\n /** 开始新会话 */\r\n startSession(label: string, description?: string): TestSession {\r\n // 如果有正在运行的会话,先结束它\r\n if (this.currentSession && this.currentSession.status === 'running') {\r\n this.endSession()\r\n }\r\n\r\n const sessionId = generateId()\r\n const { dataFile, metaFile } = this.persister.createSessionFiles(sessionId)\r\n\r\n const session: TestSession = {\r\n id: sessionId,\r\n label,\r\n description,\r\n startTime: Date.now(),\r\n status: 'running',\r\n snapshotCount: 0,\r\n dataFile,\r\n metaFile,\r\n }\r\n\r\n this.currentSession = session\r\n this.persister.saveSessionMeta(session)\r\n\r\n return session\r\n }\r\n\r\n /** 结束当前会话 */\r\n endSession(): TestSession | null {\r\n if (!this.currentSession) return null\r\n\r\n this.currentSession.endTime = Date.now()\r\n this.currentSession.duration = this.currentSession.endTime - this.currentSession.startTime\r\n this.currentSession.status = 'completed'\r\n\r\n this.persister.flush()\r\n this.persister.saveSessionMeta(this.currentSession)\r\n\r\n const session = { ...this.currentSession }\r\n this.currentSession = null\r\n\r\n return session\r\n }\r\n\r\n /** 增加当前会话的快照计数 */\r\n incrementSnapshotCount(): void {\r\n if (this.currentSession) {\r\n this.currentSession.snapshotCount++\r\n }\r\n }\r\n\r\n /** 获取所有会话 */\r\n getSessions(): TestSession[] {\r\n return this.persister.getSessions()\r\n }\r\n\r\n /** 获取指定会话 */\r\n getSession(sessionId: string): TestSession | null {\r\n return this.persister.readSessionMeta(sessionId)\r\n }\r\n}\r\n","/**\r\n * AnomalyDetector - 异常检测引擎\r\n * \r\n * 基于滑动窗口的内存异常检测\r\n * 内置 4 种检测策略:持续增长、突增、阈值、分离上下文\r\n */\r\n\r\nimport { EventEmitter } from 'events'\r\nimport { v4 as generateId, linearRegression, average } from './utils'\r\nimport type { MemorySnapshot } from '../types/snapshot'\r\nimport type { AnomalyEvent, AnomalyRule } from '../types/anomaly'\r\nimport type { MonitorConfig } from '../types/config'\r\n\r\nexport class AnomalyDetector extends EventEmitter {\r\n private config: MonitorConfig\r\n private snapshots: MemorySnapshot[] = []\r\n private timer: ReturnType<typeof setInterval> | null = null\r\n private maxWindowSize = 300 // 保留最近 300 条(5 分钟 @1s间隔)\r\n private detectedAnomalies: AnomalyEvent[] = []\r\n private builtinRules: AnomalyRule[]\r\n\r\n constructor(config: MonitorConfig) {\r\n super()\r\n this.config = config\r\n this.builtinRules = this.createBuiltinRules()\r\n }\r\n\r\n /** 添加快照到检测窗口 */\r\n addSnapshot(snapshot: MemorySnapshot): void {\r\n this.snapshots.push(snapshot)\r\n if (this.snapshots.length > this.maxWindowSize) {\r\n this.snapshots.shift()\r\n }\r\n }\r\n\r\n /** 获取所有检测到的异常 */\r\n getAnomalies(): AnomalyEvent[] {\r\n return [...this.detectedAnomalies]\r\n }\r\n\r\n /** 清空异常记录 */\r\n clearAnomalies(): void {\r\n this.detectedAnomalies = []\r\n }\r\n\r\n /** 开始定时检测 */\r\n start(): void {\r\n if (!this.config.anomaly.enabled || this.timer) return\r\n\r\n this.timer = setInterval(() => {\r\n this.runDetection()\r\n }, this.config.anomaly.checkInterval)\r\n }\r\n\r\n /** 停止检测 */\r\n stop(): void {\r\n if (this.timer) {\r\n clearInterval(this.timer)\r\n this.timer = null\r\n }\r\n }\r\n\r\n /** 执行一次检测 */\r\n private runDetection(): void {\r\n if (this.snapshots.length < 10) return // 数据不足\r\n\r\n const latest = this.snapshots[this.snapshots.length - 1]\r\n const allRules = [...this.builtinRules, ...this.config.anomaly.rules]\r\n\r\n for (const rule of allRules) {\r\n if (!rule.enabled) continue\r\n try {\r\n const anomaly = rule.detect(this.snapshots, latest)\r\n if (anomaly) {\r\n this.detectedAnomalies.push(anomaly)\r\n this.emit('anomaly', anomaly)\r\n }\r\n } catch {\r\n // 忽略规则执行错误\r\n }\r\n }\r\n }\r\n\r\n /** 创建内置检测规则 */\r\n private createBuiltinRules(): AnomalyRule[] {\r\n return [\r\n // 规则1:总内存持续增长\r\n {\r\n id: 'continuous-growth',\r\n name: '总内存持续增长',\r\n enabled: true,\r\n detect: (snapshots: MemorySnapshot[]) => {\r\n if (snapshots.length < 60) return null // 至少 1 分钟数据\r\n\r\n const values = snapshots.map((s) => s.totalWorkingSetSize)\r\n const timestamps = snapshots.map((s) => s.timestamp)\r\n const { slope, r2 } = linearRegression(values, timestamps)\r\n\r\n // 斜率 > 10 KB/s 且 R² > 0.7\r\n if (slope > 10 && r2 > 0.7) {\r\n return {\r\n id: generateId(),\r\n timestamp: Date.now(),\r\n severity: r2 > 0.9 ? 'critical' : 'warning',\r\n category: 'memory-leak',\r\n title: '总内存持续增长',\r\n description: `内存以 ${slope.toFixed(2)} KB/s 的速率持续增长 (R²=${r2.toFixed(3)})`,\r\n value: slope,\r\n threshold: 10,\r\n }\r\n }\r\n return null\r\n },\r\n },\r\n\r\n // 规则2:内存突增(spike)\r\n {\r\n id: 'memory-spike',\r\n name: '内存突增',\r\n enabled: true,\r\n detect: (snapshots: MemorySnapshot[], latest: MemorySnapshot) => {\r\n if (snapshots.length < 10) return null\r\n\r\n const recentValues = snapshots.slice(-30).map((s) => s.totalWorkingSetSize)\r\n const avg = average(recentValues)\r\n const current = latest.totalWorkingSetSize\r\n\r\n // 当前值超过近期平均值的 50%\r\n if (avg > 0 && (current - avg) / avg > 0.5) {\r\n return {\r\n id: generateId(),\r\n timestamp: Date.now(),\r\n severity: 'warning',\r\n category: 'spike',\r\n title: '内存突增',\r\n description: `总内存从 ${Math.round(avg)} KB 突增到 ${current} KB (+${(((current - avg) / avg) * 100).toFixed(1)}%)`,\r\n value: current,\r\n threshold: avg * 1.5,\r\n }\r\n }\r\n return null\r\n },\r\n },\r\n\r\n // 规则3:分离上下文检测\r\n {\r\n id: 'detached-contexts',\r\n name: '分离上下文',\r\n enabled: true,\r\n detect: (_snapshots: MemorySnapshot[], latest: MemorySnapshot) => {\r\n const detached = latest.mainProcessV8Detail?.numberOfDetachedContexts\r\n if (detached && detached > 0) {\r\n return {\r\n id: generateId(),\r\n timestamp: Date.now(),\r\n severity: 'critical',\r\n category: 'detached-context',\r\n title: `检测到 ${detached} 个分离的 V8 上下文`,\r\n description: '存在未正确销毁的 BrowserWindow 或 WebContents,可能导致内存泄漏',\r\n value: detached,\r\n threshold: 0,\r\n }\r\n }\r\n return null\r\n },\r\n },\r\n\r\n // 规则4:V8 堆使用率过高\r\n {\r\n id: 'heap-usage-high',\r\n name: 'V8 堆使用率过高',\r\n enabled: true,\r\n detect: (_snapshots: MemorySnapshot[], latest: MemorySnapshot) => {\r\n const { heapUsed, heapTotal } = latest.mainProcessMemory\r\n if (heapTotal > 0) {\r\n const usagePercent = heapUsed / heapTotal\r\n if (usagePercent > 0.85) {\r\n return {\r\n id: generateId(),\r\n timestamp: Date.now(),\r\n severity: usagePercent > 0.95 ? 'critical' : 'warning',\r\n category: 'threshold',\r\n title: `V8 堆使用率 ${(usagePercent * 100).toFixed(1)}%`,\r\n description: `主进程 V8 堆使用 ${Math.round(heapUsed / 1024 / 1024)} MB / ${Math.round(heapTotal / 1024 / 1024)} MB`,\r\n value: usagePercent * 100,\r\n threshold: 85,\r\n }\r\n }\r\n }\r\n return null\r\n },\r\n },\r\n ]\r\n }\r\n}\r\n","/**\r\n * Analyzer - 报告分析 & 改进建议引擎\r\n * \r\n * 负责:\r\n * 1. 从快照数据生成统计汇总(SessionReport)\r\n * 2. 对比两个会话(CompareReport)\r\n * 3. 基于模式识别生成改进建议\r\n */\r\n\r\nimport * as os from 'os'\r\nimport { percentile, average, linearRegression, kbToBytes } from './utils'\r\nimport type { MemorySnapshot } from '../types/snapshot'\r\nimport type { AnomalyEvent } from '../types/anomaly'\r\nimport type {\r\n SessionReport,\r\n MetricSummary,\r\n TrendInfo,\r\n Suggestion,\r\n CompareReport,\r\n MetricDiff,\r\n Regression,\r\n Improvement,\r\n} from '../types/report'\r\n\r\nexport class Analyzer {\r\n /** 生成会话报告 */\r\n generateReport(\r\n sessionId: string,\r\n label: string,\r\n description: string | undefined,\r\n startTime: number,\r\n endTime: number,\r\n snapshots: MemorySnapshot[],\r\n anomalies: AnomalyEvent[],\r\n dataFile: string\r\n ): SessionReport {\r\n if (snapshots.length === 0) {\r\n throw new Error('No snapshots to analyze')\r\n }\r\n\r\n const environment = this.collectEnvironment()\r\n const summary = this.computeSummary(snapshots)\r\n const suggestions = this.generateSuggestions(snapshots, summary, anomalies)\r\n\r\n return {\r\n sessionId,\r\n label,\r\n description,\r\n startTime,\r\n endTime,\r\n duration: endTime - startTime,\r\n environment,\r\n summary,\r\n anomalies,\r\n suggestions,\r\n dataFile,\r\n }\r\n }\r\n\r\n /** 对比两个会话报告 */\r\n compareReports(base: SessionReport, target: SessionReport): CompareReport {\r\n const overall = {\r\n totalMemory: this.diffMetric(base.summary.totalMemory, target.summary.totalMemory),\r\n browserMemory: this.diffMetric(base.summary.byProcessType.browser, target.summary.byProcessType.browser),\r\n rendererMemory: this.diffMetricArrayAvg(\r\n base.summary.byProcessType.renderer,\r\n target.summary.byProcessType.renderer\r\n ),\r\n gpuMemory: base.summary.byProcessType.gpu && target.summary.byProcessType.gpu\r\n ? this.diffMetric(base.summary.byProcessType.gpu, target.summary.byProcessType.gpu)\r\n : null,\r\n }\r\n\r\n const v8Heap = {\r\n heapUsed: this.diffMetric(base.summary.mainV8Heap.heapUsed, target.summary.mainV8Heap.heapUsed),\r\n heapTotal: this.diffMetric(base.summary.mainV8Heap.heapTotal, target.summary.mainV8Heap.heapTotal),\r\n external: this.diffMetric(base.summary.mainV8Heap.external, target.summary.mainV8Heap.external),\r\n }\r\n\r\n const trendChanges = this.compareTrends(base.summary.trends, target.summary.trends)\r\n const regressions = this.findRegressions(overall, v8Heap)\r\n const improvements = this.findImprovements(overall, v8Heap)\r\n const { verdict, verdictReason } = this.determineVerdict(regressions, overall)\r\n\r\n return {\r\n base: { sessionId: base.sessionId, label: base.label },\r\n target: { sessionId: target.sessionId, label: target.label },\r\n overall,\r\n v8Heap,\r\n trendChanges,\r\n regressions,\r\n improvements,\r\n verdict,\r\n verdictReason,\r\n }\r\n }\r\n\r\n // ===== 私有方法 =====\r\n\r\n private collectEnvironment(): SessionReport['environment'] {\r\n const cpus = os.cpus()\r\n return {\r\n electronVersion: process.versions.electron || 'unknown',\r\n chromeVersion: process.versions.chrome || 'unknown',\r\n nodeVersion: process.versions.node || 'unknown',\r\n platform: process.platform,\r\n arch: process.arch,\r\n totalSystemMemory: os.totalmem(),\r\n cpuModel: cpus.length > 0 ? cpus[0].model : 'unknown',\r\n cpuCores: cpus.length,\r\n }\r\n }\r\n\r\n private computeSummary(snapshots: MemorySnapshot[]): SessionReport['summary'] {\r\n const timestamps = snapshots.map((s) => s.timestamp)\r\n\r\n // 总进程数\r\n const processCounts = snapshots.map((s) => s.processes.length)\r\n\r\n // 总内存 (KB)\r\n const totalMemoryValues = snapshots.map((s) => s.totalWorkingSetSize)\r\n\r\n // 按类型分组\r\n const browserValues = snapshots.map((s) =>\r\n s.processes.filter((p) => p.type === 'Browser').reduce((sum, p) => sum + p.memory.workingSetSize, 0)\r\n )\r\n\r\n // 渲染进程 - 收集每个渲染进程的数据\r\n const rendererSummaries = this.computeRendererSummaries(snapshots)\r\n\r\n // GPU 进程\r\n const gpuValues = snapshots.map((s) =>\r\n s.processes.filter((p) => p.type === 'GPU').reduce((sum, p) => sum + p.memory.workingSetSize, 0)\r\n )\r\n const hasGpu = gpuValues.some((v) => v > 0)\r\n\r\n // Utility 进程\r\n const utilityValues = snapshots.map((s) =>\r\n s.processes.filter((p) => p.type === 'Utility').reduce((sum, p) => sum + p.memory.workingSetSize, 0)\r\n )\r\n const hasUtility = utilityValues.some((v) => v > 0)\r\n\r\n // V8 堆(bytes)\r\n const heapUsedValues = snapshots.map((s) => s.mainProcessMemory.heapUsed)\r\n const heapTotalValues = snapshots.map((s) => s.mainProcessMemory.heapTotal)\r\n const externalValues = snapshots.map((s) => s.mainProcessMemory.external)\r\n const arrayBufferValues = snapshots.map((s) => s.mainProcessMemory.arrayBuffers)\r\n\r\n // 渲染进程总内存\r\n const rendererTotalValues = snapshots.map((s) =>\r\n s.processes.filter((p) => p.type === 'Tab' && !p.isMonitorProcess).reduce((sum, p) => sum + p.memory.workingSetSize, 0)\r\n )\r\n\r\n return {\r\n totalProcesses: {\r\n min: Math.min(...processCounts),\r\n max: Math.max(...processCounts),\r\n avg: Math.round(average(processCounts)),\r\n },\r\n totalMemory: this.computeMetricSummary(totalMemoryValues),\r\n byProcessType: {\r\n browser: this.computeMetricSummary(browserValues),\r\n renderer: rendererSummaries,\r\n gpu: hasGpu ? this.computeMetricSummary(gpuValues) : null,\r\n utility: hasUtility ? this.computeMetricSummary(utilityValues) : null,\r\n },\r\n mainV8Heap: {\r\n heapUsed: this.computeMetricSummary(heapUsedValues),\r\n heapTotal: this.computeMetricSummary(heapTotalValues),\r\n external: this.computeMetricSummary(externalValues),\r\n arrayBuffers: this.computeMetricSummary(arrayBufferValues),\r\n },\r\n trends: {\r\n totalMemory: this.computeTrend(totalMemoryValues, timestamps),\r\n browserMemory: this.computeTrend(browserValues, timestamps),\r\n rendererMemory: this.computeTrend(rendererTotalValues, timestamps),\r\n },\r\n }\r\n }\r\n\r\n private computeRendererSummaries(snapshots: MemorySnapshot[]): MetricSummary[] {\r\n // 收集所有出现过的渲染进程 PID(排除监控面板自身)\r\n const allPids = new Set<number>()\r\n for (const snapshot of snapshots) {\r\n for (const p of snapshot.processes) {\r\n if (p.type === 'Tab' && !p.isMonitorProcess) {\r\n allPids.add(p.pid)\r\n }\r\n }\r\n }\r\n\r\n // 对每个 PID 计算汇总\r\n const summaries: MetricSummary[] = []\r\n for (const pid of allPids) {\r\n const values = snapshots\r\n .map((s) => {\r\n const proc = s.processes.find((p) => p.pid === pid)\r\n return proc ? proc.memory.workingSetSize : null\r\n })\r\n .filter((v): v is number => v !== null)\r\n\r\n if (values.length > 0) {\r\n summaries.push(this.computeMetricSummary(values))\r\n }\r\n }\r\n\r\n return summaries\r\n }\r\n\r\n private computeMetricSummary(values: number[]): MetricSummary {\r\n if (values.length === 0) {\r\n return { initial: 0, final: 0, min: 0, max: 0, avg: 0, p50: 0, p95: 0, p99: 0, delta: 0, deltaPercent: 0 }\r\n }\r\n\r\n const initial = values[0]\r\n const final = values[values.length - 1]\r\n const delta = final - initial\r\n const deltaPercent = initial !== 0 ? (delta / initial) * 100 : 0\r\n\r\n return {\r\n initial,\r\n final,\r\n min: Math.min(...values),\r\n max: Math.max(...values),\r\n avg: Math.round(average(values)),\r\n p50: Math.round(percentile(values, 50)),\r\n p95: Math.round(percentile(values, 95)),\r\n p99: Math.round(percentile(values, 99)),\r\n delta: Math.round(delta),\r\n deltaPercent: Math.round(deltaPercent * 100) / 100,\r\n }\r\n }\r\n\r\n private computeTrend(values: number[], timestamps: number[]): TrendInfo {\r\n if (values.length < 10) {\r\n return { slope: 0, r2: 0, direction: 'stable', confidence: 'low' }\r\n }\r\n\r\n const { slope, r2 } = linearRegression(values, timestamps)\r\n\r\n let direction: TrendInfo['direction'] = 'stable'\r\n if (slope > 1 && r2 > 0.3) direction = 'growing'\r\n else if (slope < -1 && r2 > 0.3) direction = 'shrinking'\r\n\r\n let confidence: TrendInfo['confidence'] = 'low'\r\n if (r2 > 0.8) confidence = 'high'\r\n else if (r2 > 0.5) confidence = 'medium'\r\n\r\n return { slope, r2, direction, confidence }\r\n }\r\n\r\n private generateSuggestions(\r\n snapshots: MemorySnapshot[],\r\n summary: SessionReport['summary'],\r\n _anomalies: AnomalyEvent[]\r\n ): Suggestion[] {\r\n const suggestions: Suggestion[] = []\r\n const latest = snapshots[snapshots.length - 1]\r\n\r\n // 规则1:分离上下文\r\n if (latest.mainProcessV8Detail?.numberOfDetachedContexts > 0) {\r\n suggestions.push({\r\n id: 'detached-contexts',\r\n severity: 'critical',\r\n category: 'memory-leak',\r\n title: '检测到分离的 V8 上下文 (Detached Contexts)',\r\n description: `发现 ${latest.mainProcessV8Detail.numberOfDetachedContexts} 个分离上下文,通常意味着存在未正确销毁的 BrowserWindow 或 WebContents 实例。`,\r\n suggestions: [\r\n '检查所有 BrowserWindow 是否在关闭时调用了 destroy()',\r\n '检查是否有闭包持有已关闭窗口的 webContents 引用',\r\n '使用 Chrome DevTools Memory 面板做堆快照,搜索 \"Detached\" 关键字',\r\n '检查 ipcMain.on 监听器是否在窗口关闭后正确移除',\r\n ],\r\n relatedCode: [\r\n 'win.on(\"closed\", () => { win = null })',\r\n 'win.destroy() // 而不仅仅是 win.close()',\r\n ],\r\n })\r\n }\r\n\r\n // 规则2:主进程内存持续增长\r\n if (summary.trends.browserMemory.direction === 'growing' && summary.trends.browserMemory.confidence === 'high') {\r\n suggestions.push({\r\n id: 'main-process-leak',\r\n severity: 'warning',\r\n category: 'memory-leak',\r\n title: '主进程内存存在持续增长趋势',\r\n description: `主进程内存以 ${summary.trends.browserMemory.slope.toFixed(2)} KB/s 的速率增长 (R²=${summary.trends.browserMemory.r2.toFixed(3)})`,\r\n suggestions: [\r\n '检查主进程中是否有未清理的 setInterval/setTimeout',\r\n '检查 ipcMain.on 是否存在重复注册',\r\n '检查是否有持续增长的 Map/Set/Array 缓存未设置上限',\r\n '检查 EventEmitter 监听器是否正确移除',\r\n '运行 --expose-gc 并手动触发 GC,观察内存是否回落',\r\n ],\r\n })\r\n }\r\n\r\n // 规则3:渲染进程内存过高\r\n const highRenderers = summary.byProcessType.renderer.filter((r) => r.max > 300 * 1024)\r\n if (highRenderers.length > 0) {\r\n suggestions.push({\r\n id: 'renderer-memory-high',\r\n severity: 'warning',\r\n category: 'optimization',\r\n title: '渲染进程内存占用过高',\r\n description: `有 ${highRenderers.length} 个渲染进程内存峰值超过 300MB`,\r\n suggestions: [\r\n '检查是否加载了过大的图片资源(考虑懒加载/压缩)',\r\n '检查 DOM 节点数量(超过 1500 个节点会显著增加内存)',\r\n '检查是否有大量未销毁的 React 组件实例',\r\n '考虑使用虚拟列表(Virtual List)替代长列表',\r\n '检查 Canvas/WebGL 资源是否正确释放',\r\n ],\r\n })\r\n }\r\n\r\n // 规则4:V8 堆使用率过高\r\n const { heapUsed, heapTotal } = summary.mainV8Heap\r\n if (heapTotal.avg > 0 && heapUsed.avg / heapTotal.avg > 0.8) {\r\n suggestions.push({\r\n id: 'gc-ineffective',\r\n severity: 'warning',\r\n category: 'memory-leak',\r\n title: 'V8 堆使用率长期偏高 (>80%)',\r\n description: '堆使用率长期超过 80%,GC 无法有效释放内存,疑似存在内存泄漏',\r\n suggestions: [\r\n '导出堆快照 (Heap Snapshot),使用 Chrome DevTools 分析对象留存',\r\n '对比两个时间点的堆快照,查找 \"Allocated between snapshots\" 中的泄漏对象',\r\n '检查 Event Listeners 是否正确清理',\r\n '检查 Promise 链是否有未处理的 rejection 导致引用未释放',\r\n ],\r\n })\r\n }\r\n\r\n // 规则5:ArrayBuffer 偏高\r\n if (summary.mainV8Heap.arrayBuffers.avg > 50 * 1024 * 1024) {\r\n suggestions.push({\r\n id: 'arraybuffer-high',\r\n severity: 'info',\r\n category: 'optimization',\r\n title: 'ArrayBuffer 内存占用偏高',\r\n description: 'ArrayBuffer 平均占用超过 50MB',\r\n suggestions: [\r\n '检查 Buffer.alloc / Buffer.from 的使用,确保用完后不再持有引用',\r\n '如果使用 IPC 传输大数据,考虑分片传输或使用 MessagePort',\r\n '检查 Blob/File 对象是否及时释放',\r\n ],\r\n })\r\n }\r\n\r\n // 规则6:进程数过多\r\n if (summary.totalProcesses.max > 10) {\r\n suggestions.push({\r\n id: 'too-many-processes',\r\n severity: 'warning',\r\n category: 'architecture',\r\n title: `进程数量偏多 (最高 ${summary.totalProcesses.max} 个)`,\r\n description: '过多的进程会显著增加内存开销',\r\n suggestions: [\r\n '检查是否创建了不必要的 BrowserWindow',\r\n '考虑复用窗口而非每次创建新窗口',\r\n '使用 webContents.setBackgroundThrottling(true) 减少后台进程开销',\r\n ],\r\n })\r\n }\r\n\r\n // 规则7:old_space 占比过高\r\n if (latest.mainProcessV8Detail?.heapSpaces) {\r\n const oldSpace = latest.mainProcessV8Detail.heapSpaces.find((s) => s.name === 'old_space')\r\n const totalUsed = latest.mainProcessV8Detail.heapSpaces.reduce((sum, s) => sum + s.usedSize, 0)\r\n if (oldSpace && totalUsed > 0 && oldSpace.usedSize / totalUsed > 0.85) {\r\n suggestions.push({\r\n id: 'old-space-dominant',\r\n severity: 'info',\r\n category: 'optimization',\r\n title: 'V8 old_space 占比超过 85%',\r\n description: '大量对象存活到 old generation,可能存在长生命周期的大对象或缓存未回收',\r\n suggestions: [\r\n '使用堆快照分析 old_space 中的大对象',\r\n '检查全局缓存是否设置了过期策略或容量上限',\r\n '考虑使用 WeakMap/WeakRef 替代强引用缓存',\r\n '检查闭包是否意外持有大量外部变量',\r\n ],\r\n })\r\n }\r\n }\r\n\r\n return suggestions\r\n }\r\n\r\n private diffMetric(base: MetricSummary, target: MetricSummary): MetricDiff {\r\n const delta = target.avg - base.avg\r\n const deltaPercent = base.avg !== 0 ? (delta / base.avg) * 100 : 0\r\n\r\n let status: MetricDiff['status'] = 'unchanged'\r\n if (deltaPercent > 3) status = 'degraded'\r\n else if (deltaPercent < -3) status = 'improved'\r\n\r\n let severity: MetricDiff['severity']\r\n if (Math.abs(deltaPercent) > 15) severity = 'critical'\r\n else if (Math.abs(deltaPercent) > 5) severity = 'major'\r\n else severity = 'minor'\r\n\r\n return {\r\n base: base.avg,\r\n target: target.avg,\r\n delta: Math.round(delta),\r\n deltaPercent: Math.round(deltaPercent * 100) / 100,\r\n status,\r\n severity,\r\n }\r\n }\r\n\r\n private diffMetricArrayAvg(baseArr: MetricSummary[], targetArr: MetricSummary[]): MetricDiff {\r\n const baseAvg = baseArr.length > 0 ? average(baseArr.map((s) => s.avg)) : 0\r\n const targetAvg = targetArr.length > 0 ? average(targetArr.map((s) => s.avg)) : 0\r\n const baseSummary: MetricSummary = {\r\n initial: 0, final: 0, min: 0, max: 0, avg: baseAvg,\r\n p50: 0, p95: 0, p99: 0, delta: 0, deltaPercent: 0,\r\n }\r\n const targetSummary: MetricSummary = {\r\n initial: 0, final: 0, min: 0, max: 0, avg: targetAvg,\r\n p50: 0, p95: 0, p99: 0, delta: 0, deltaPercent: 0,\r\n }\r\n return this.diffMetric(baseSummary, targetSummary)\r\n }\r\n\r\n private compareTrends(\r\n baseTrends: SessionReport['summary']['trends'],\r\n targetTrends: SessionReport['summary']['trends']\r\n ): CompareReport['trendChanges'] {\r\n const metrics = ['totalMemory', 'browserMemory', 'rendererMemory'] as const\r\n return metrics.map((metric) => {\r\n const baseSlope = baseTrends[metric].slope\r\n const targetSlope = targetTrends[metric].slope\r\n\r\n let change: 'improved' | 'degraded' | 'unchanged' = 'unchanged'\r\n if (targetSlope > baseSlope + 1) change = 'degraded'\r\n else if (targetSlope < baseSlope - 1) change = 'improved'\r\n\r\n return { metric, baseSlope, targetSlope, change }\r\n })\r\n }\r\n\r\n private findRegressions(\r\n overall: CompareReport['overall'],\r\n v8Heap: CompareReport['v8Heap']\r\n ): Regression[] {\r\n const regressions: Regression[] = []\r\n\r\n const checks: { metric: string; diff: MetricDiff; warnThreshold: number; failThreshold: number }[] = [\r\n { metric: '总内存', diff: overall.totalMemory, warnThreshold: 5, failThreshold: 15 },\r\n { metric: '主进程内存', diff: overall.browserMemory, warnThreshold: 10, failThreshold: 25 },\r\n { metric: '渲染进程内存', diff: overall.rendererMemory, warnThreshold: 10, failThreshold: 25 },\r\n { metric: 'V8 Heap Used', diff: v8Heap.heapUsed, warnThreshold: 10, failThreshold: 30 },\r\n ]\r\n\r\n for (const check of checks) {\r\n if (check.diff.deltaPercent > check.warnThreshold) {\r\n regressions.push({\r\n metric: check.metric,\r\n description: `${check.metric}增长 ${check.diff.deltaPercent.toFixed(1)}%`,\r\n baseValue: check.diff.base,\r\n targetValue: check.diff.target,\r\n deltaPercent: check.diff.deltaPercent,\r\n severity: check.diff.deltaPercent > check.failThreshold ? 'critical' : 'major',\r\n suggestion: `${check.metric}增长超过预期,建议检查新增代码中的内存使用`,\r\n })\r\n }\r\n }\r\n\r\n return regressions\r\n }\r\n\r\n private findImprovements(\r\n overall: CompareReport['overall'],\r\n v8Heap: CompareReport['v8Heap']\r\n ): Improvement[] {\r\n const improvements: Improvement[] = []\r\n\r\n const checks: { metric: string; diff: MetricDiff }[] = [\r\n { metric: '总内存', diff: overall.totalMemory },\r\n { metric: '主进程内存', diff: overall.browserMemory },\r\n { metric: 'V8 Heap Used', diff: v8Heap.heapUsed },\r\n ]\r\n\r\n for (const check of checks) {\r\n if (check.diff.deltaPercent < -3) {\r\n improvements.push({\r\n metric: check.metric,\r\n description: `${check.metric}减少 ${Math.abs(check.diff.deltaPercent).toFixed(1)}%`,\r\n baseValue: check.diff.base,\r\n targetValue: check.diff.target,\r\n deltaPercent: check.diff.deltaPercent,\r\n })\r\n }\r\n }\r\n\r\n return improvements\r\n }\r\n\r\n private determineVerdict(\r\n regressions: Regression[],\r\n overall: CompareReport['overall']\r\n ): { verdict: CompareReport['verdict']; verdictReason: string } {\r\n const critical = regressions.filter((r) => r.severity === 'critical')\r\n const major = regressions.filter((r) => r.severity === 'major')\r\n\r\n if (critical.length > 0) {\r\n return {\r\n verdict: 'fail',\r\n verdictReason: `存在 ${critical.length} 项严重劣化:${critical.map((r) => r.metric).join('、')}`,\r\n }\r\n }\r\n\r\n if (major.length > 0 || overall.totalMemory.deltaPercent > 5) {\r\n return {\r\n verdict: 'warn',\r\n verdictReason: `存在 ${major.length} 项劣化,总内存变化 ${overall.totalMemory.deltaPercent.toFixed(1)}%`,\r\n }\r\n }\r\n\r\n return {\r\n verdict: 'pass',\r\n verdictReason: '所有内存指标在正常范围内',\r\n }\r\n }\r\n}\r\n","/**\r\n * DashboardManager - 监控面板窗口管理\r\n * \r\n * 创建和管理独立的监控面板 BrowserWindow\r\n * 面板 UI 预编译为静态资源,打包在 SDK 内\r\n */\r\n\r\nimport { BrowserWindow } from 'electron'\r\nimport * as path from 'node:path'\r\nimport type { MonitorConfig } from '../types/config'\r\nimport { ensureDashboardProtocolHandler, getDashboardPageURL } from './dashboard-protocol'\r\nimport { attachDashboardWindowHooks } from './dashboard-window-hooks'\r\n\r\nexport class DashboardManager {\r\n private config: MonitorConfig\r\n private window: BrowserWindow | null = null\r\n\r\n constructor(config: MonitorConfig) {\r\n this.config = config\r\n }\r\n\r\n /** 获取面板窗口 */\r\n getWindow(): BrowserWindow | null {\r\n return this.window\r\n }\r\n\r\n /** 获取面板 webContents ID */\r\n getWebContentsId(): number | null {\r\n if (this.window && !this.window.isDestroyed()) {\r\n return this.window.webContents.id\r\n }\r\n return null\r\n }\r\n\r\n /** 打开监控面板 */\r\n open(): void {\r\n if (this.window && !this.window.isDestroyed()) {\r\n this.window.focus()\r\n return\r\n }\r\n\r\n // 创建 preload 脚本路径\r\n const preloadPath = path.join(__dirname, 'dashboard-preload.js')\r\n\r\n this.window = new BrowserWindow({\r\n width: this.config.dashboard.width,\r\n height: this.config.dashboard.height,\r\n title: 'Electron Memory Monitor',\r\n alwaysOnTop: this.config.dashboard.alwaysOnTop,\r\n webPreferences: {\r\n preload: preloadPath,\r\n contextIsolation: true,\r\n nodeIntegration: false,\r\n devTools: true,\r\n },\r\n })\r\n\r\n attachDashboardWindowHooks(this.window, {\r\n openDevToolsOnStart: this.config.dashboard.openDevToolsOnStart,\r\n })\r\n\r\n // 通过自定义协议加载 UI(避免 file:// + ESM 在打包/asar 下白屏)\r\n const uiRoot = path.join(__dirname, 'ui')\r\n ensureDashboardProtocolHandler(uiRoot)\r\n this.window.loadURL(getDashboardPageURL())\r\n\r\n this.window.on('closed', () => {\r\n this.window = null\r\n })\r\n }\r\n\r\n /** 关闭监控面板 */\r\n close(): void {\r\n if (this.window && !this.window.isDestroyed()) {\r\n this.window.close()\r\n this.window = null\r\n }\r\n }\r\n\r\n /** 销毁面板 */\r\n destroy(): void {\r\n if (this.window && !this.window.isDestroyed()) {\r\n this.window.destroy()\r\n this.window = null\r\n }\r\n }\r\n}\r\n","/**\r\n * 看板静态资源通过自定义协议提供,避免 loadFile(file://) + ES module 在 asar 下白屏。\r\n * 使用显式 Content-Type 响应,避免 net.fetch(file://) 在部分环境下返回错误 MIME 导致模块脚本不执行。\r\n */\r\nimport { app, protocol } from 'electron'\r\nimport { readFile } from 'node:fs/promises'\r\nimport * as path from 'node:path'\r\n\r\nconst SCHEME = 'emm-dashboard'\r\n\r\nlet privilegedRegistered = false\r\nlet handlerRegistered = false\r\n\r\nconst MIME_BY_EXT: Record<string, string> = {\r\n '.html': 'text/html; charset=utf-8',\r\n '.js': 'text/javascript; charset=utf-8',\r\n '.mjs': 'text/javascript; charset=utf-8',\r\n '.css': 'text/css; charset=utf-8',\r\n '.json': 'application/json',\r\n '.map': 'application/json',\r\n '.svg': 'image/svg+xml',\r\n '.png': 'image/png',\r\n '.jpg': 'image/jpeg',\r\n '.jpeg': 'image/jpeg',\r\n '.gif': 'image/gif',\r\n '.webp': 'image/webp',\r\n '.ico': 'image/x-icon',\r\n '.woff': 'font/woff',\r\n '.woff2': 'font/woff2',\r\n '.ttf': 'font/ttf',\r\n '.txt': 'text/plain; charset=utf-8',\r\n}\r\n\r\nfunction isPathInsideRoot(filePath: string, root: string): boolean {\r\n const f = path.resolve(filePath)\r\n const r = path.resolve(root)\r\n if (f === r) {\r\n return true\r\n }\r\n const sep = r.endsWith(path.sep) ? r : `${r}${path.sep}`\r\n return f.startsWith(sep)\r\n}\r\n\r\n/**\r\n * 必须在 app.on('ready') 之前调用(与 new ElectronMemoryMonitor 同文件顶部或更早)。\r\n * 由 ElectronMemoryMonitor 在启用时于构造函数内调用。\r\n */\r\nexport function registerDashboardSchemePrivileged(): void {\r\n if (privilegedRegistered) {\r\n return\r\n }\r\n privilegedRegistered = true\r\n protocol.registerSchemesAsPrivileged([\r\n {\r\n scheme: SCHEME,\r\n privileges: {\r\n standard: true,\r\n secure: true,\r\n supportFetchAPI: true,\r\n corsEnabled: true,\r\n stream: true,\r\n },\r\n },\r\n ])\r\n}\r\n\r\n/**\r\n * 注册协议处理器;须在 app.isReady() 之后调用(SDK 在 start() → openDashboard 路径上满足)。\r\n * uiRoot:指向包内 dist/ui 目录(含 index.html 与 assets)。\r\n */\r\nexport function ensureDashboardProtocolHandler(uiRoot: string): void {\r\n if (handlerRegistered) {\r\n return\r\n }\r\n if (!app.isReady()) {\r\n throw new Error(\r\n '[@electron-memory/monitor] ensureDashboardProtocolHandler: app must be ready before opening dashboard',\r\n )\r\n }\r\n\r\n handlerRegistered = true\r\n const base = path.resolve(uiRoot)\r\n\r\n protocol.handle(SCHEME, async (request) => {\r\n try {\r\n const { pathname } = new URL(request.url)\r\n let rel = pathname.startsWith('/') ? pathname.slice(1) : pathname\r\n if (!rel) {\r\n rel = 'index.html'\r\n }\r\n const filePath = path.resolve(path.join(base, rel))\r\n if (!isPathInsideRoot(filePath, base)) {\r\n return new Response('Forbidden', { status: 403 })\r\n }\r\n const body = await readFile(filePath)\r\n const ext = path.extname(filePath).toLowerCase()\r\n const mime = MIME_BY_EXT[ext] || 'application/octet-stream'\r\n return new Response(body, {\r\n status: 200,\r\n headers: {\r\n 'Content-Type': mime,\r\n 'Cache-Control': 'no-store',\r\n },\r\n })\r\n }\r\n catch (err) {\r\n const msg = err instanceof Error ? err.message : String(err)\r\n return new Response(msg, { status: 404 })\r\n }\r\n })\r\n}\r\n\r\n/** 固定 host,保证相对资源 URL 解析正确(./assets/... → 同 origin) */\r\nexport function getDashboardPageURL(): string {\r\n return `${SCHEME}://electron/index.html`\r\n}\r\n","/**\r\n * 看板窗口:快捷键打开 DevTools、加载失败打日志、可选启动即开控制台\r\n */\r\nimport type { BrowserWindow, Input } from 'electron'\r\n\r\nfunction shouldOpenDevToolsShortcut(input: Input): boolean {\r\n if (input.type !== 'keyDown') {\r\n return false\r\n }\r\n if (input.key === 'F12') {\r\n return true\r\n }\r\n const k = input.key.toLowerCase()\r\n if (k !== 'i') {\r\n return false\r\n }\r\n // Windows / Linux: Ctrl+Shift+I(与 Chrome 一致)\r\n if (input.control && input.shift) {\r\n return true\r\n }\r\n // macOS: Cmd+Option+I\r\n if (process.platform === 'darwin' && input.meta && input.alt) {\r\n return true\r\n }\r\n return false\r\n}\r\n\r\nexport function attachDashboardWindowHooks(\r\n win: BrowserWindow,\r\n options: { openDevToolsOnStart: boolean },\r\n): void {\r\n const wc = win.webContents\r\n\r\n wc.on('before-input-event', (event, input) => {\r\n if (!shouldOpenDevToolsShortcut(input)) {\r\n return\r\n }\r\n event.preventDefault()\r\n if (wc.isDevToolsOpened()) {\r\n wc.closeDevTools()\r\n }\r\n else {\r\n wc.openDevTools({ mode: 'detach' })\r\n }\r\n })\r\n\r\n wc.on('did-fail-load', (_e, code, desc, url) => {\r\n console.error('[@electron-memory/monitor] dashboard did-fail-load', code, desc, url)\r\n })\r\n\r\n let autoOpened = false\r\n wc.on('did-finish-load', () => {\r\n if (options.openDevToolsOnStart && !autoOpened) {\r\n autoOpened = true\r\n wc.openDevTools({ mode: 'detach' })\r\n }\r\n })\r\n}\r\n","/**\r\n * IPC 主进程处理器\r\n * \r\n * 注册所有 emm:* IPC 通道的 handler\r\n * 桥接监控面板(渲染进程)与 SDK 核心(主进程)\r\n */\r\n\r\nimport { ipcMain, BrowserWindow } from 'electron'\r\nimport { IPC_CHANNELS } from './channels'\r\nimport type { ElectronMemoryMonitor } from '../core/monitor'\r\n\r\nexport class IPCMainHandler {\r\n private monitor: ElectronMemoryMonitor\r\n\r\n constructor(monitor: ElectronMemoryMonitor) {\r\n this.monitor = monitor\r\n }\r\n\r\n /** 注册所有 IPC handlers */\r\n register(): void {\r\n // 会话控制\r\n ipcMain.handle(IPC_CHANNELS.SESSION_START, (_event, args: { label: string; description?: string }) => {\r\n return this.monitor.startSession(args.label, args.description)\r\n })\r\n\r\n ipcMain.handle(IPC_CHANNELS.SESSION_STOP, async () => {\r\n return this.monitor.stopSession()\r\n })\r\n\r\n ipcMain.handle(IPC_CHANNELS.SESSION_LIST, async () => {\r\n return this.monitor.getSessions()\r\n })\r\n\r\n ipcMain.handle(IPC_CHANNELS.SESSION_REPORT, async (_event, sessionId: string) => {\r\n return this.monitor.getSessionReport(sessionId)\r\n })\r\n\r\n ipcMain.handle(IPC_CHANNELS.SESSION_COMPARE, async (_event, args: { baseId: string; targetId: string }) => {\r\n return this.monitor.compareSessions(args.baseId, args.targetId)\r\n })\r\n\r\n ipcMain.handle(IPC_CHANNELS.SESSION_SNAPSHOTS, async (_event, args: { sessionId: string; startTime?: number; endTime?: number; maxPoints?: number }) => {\r\n return this.monitor.getSessionSnapshots(args.sessionId, args.startTime, args.endTime, args.maxPoints)\r\n })\r\n\r\n // 工具操作\r\n ipcMain.handle(IPC_CHANNELS.TRIGGER_GC, async () => {\r\n return this.monitor.triggerGC()\r\n })\r\n\r\n ipcMain.handle(IPC_CHANNELS.HEAP_SNAPSHOT, async (_event, filePath?: string) => {\r\n return this.monitor.takeHeapSnapshot(filePath)\r\n })\r\n\r\n ipcMain.handle(IPC_CHANNELS.MARK, (_event, args: { label: string; metadata?: Record<string, unknown> }) => {\r\n this.monitor.mark(args.label, args.metadata)\r\n })\r\n\r\n ipcMain.handle(IPC_CHANNELS.GET_CONFIG, () => {\r\n return this.monitor.getConfig()\r\n })\r\n\r\n ipcMain.handle(IPC_CHANNELS.GET_SESSIONS, async () => {\r\n return this.monitor.getSessions()\r\n })\r\n\r\n // 导入导出\r\n ipcMain.handle(IPC_CHANNELS.SESSION_EXPORT, async (_event, sessionId: string) => {\r\n return this.monitor.exportSession(sessionId)\r\n })\r\n\r\n ipcMain.handle(IPC_CHANNELS.SESSION_IMPORT, async () => {\r\n return this.monitor.importSession()\r\n })\r\n\r\n ipcMain.handle(IPC_CHANNELS.SESSION_DELETE, async (_event, sessionId: string) => {\r\n return this.monitor.deleteSession(sessionId)\r\n })\r\n\r\n // 渲染进程上报(可选)\r\n ipcMain.on(IPC_CHANNELS.RENDERER_REPORT, (_event, detail) => {\r\n this.monitor.updateRendererDetail(detail)\r\n })\r\n }\r\n\r\n /** 向监控面板推送快照数据 */\r\n pushSnapshot(dashboardWindow: BrowserWindow | null, data: unknown): void {\r\n if (dashboardWindow && !dashboardWindow.isDestroyed()) {\r\n dashboardWindow.webContents.send(IPC_CHANNELS.SNAPSHOT, data)\r\n }\r\n }\r\n\r\n /** 向监控面板推送异常事件 */\r\n pushAnomaly(dashboardWindow: BrowserWindow | null, data: unknown): void {\r\n if (dashboardWindow && !dashboardWindow.isDestroyed()) {\r\n dashboardWindow.webContents.send(IPC_CHANNELS.ANOMALY, data)\r\n }\r\n }\r\n\r\n /** 移除所有注册的 handlers */\r\n unregister(): void {\r\n const channels = Object.values(IPC_CHANNELS)\r\n for (const channel of channels) {\r\n ipcMain.removeHandler(channel)\r\n ipcMain.removeAllListeners(channel)\r\n }\r\n }\r\n}\r\n","/**\r\n * IPC 通道常量定义\r\n * 所有通道以 'emm:' 为前缀,避免与业务 IPC 冲突\r\n */\r\n\r\nexport const IPC_CHANNELS = {\r\n // === 数据推送(主进程 → 监控面板)===\r\n SNAPSHOT: 'emm:snapshot',\r\n ANOMALY: 'emm:anomaly',\r\n\r\n // === 会话控制(面板 → 主进程)===\r\n SESSION_START: 'emm:session:start',\r\n SESSION_STOP: 'emm:session:stop',\r\n SESSION_LIST: 'emm:session:list',\r\n SESSION_REPORT: 'emm:session:report',\r\n SESSION_COMPARE: 'emm:session:compare',\r\n\r\n // === 数据查询(面板 → 主进程)===\r\n SESSION_SNAPSHOTS: 'emm:session:snapshots',\r\n\r\n // === 工具操作(面板 → 主进程)===\r\n TRIGGER_GC: 'emm:gc',\r\n HEAP_SNAPSHOT: 'emm:heap-snapshot',\r\n MARK: 'emm:mark',\r\n CONFIG_UPDATE: 'emm:config:update',\r\n GET_CONFIG: 'emm:config:get',\r\n GET_SESSIONS: 'emm:sessions:get',\r\n\r\n // === 导入导出(面板 → 主进程)===\r\n SESSION_EXPORT: 'emm:session:export',\r\n SESSION_IMPORT: 'emm:session:import',\r\n SESSION_DELETE: 'emm:session:delete',\r\n\r\n // === 渲染进程上报(可选)===\r\n RENDERER_REPORT: 'emm:renderer:report',\r\n RENDERER_REQUEST: 'emm:renderer:request',\r\n} as const\r\n","/**\r\n * SDK 配置类型\r\n */\r\n\r\nimport type { AnomalyRule } from './anomaly'\r\n\r\nexport interface MonitorConfig {\r\n // ===== 开关控制 =====\r\n /** 总开关,默认 true */\r\n enabled: boolean\r\n /** 实例化后是否自动开始采集,默认 true */\r\n autoStart: boolean\r\n /** 启动后是否自动打开监控面板,默认 true */\r\n openDashboardOnStart: boolean\r\n\r\n // ===== 采集配置 =====\r\n /** 采集间隔 (ms),默认 2000 */\r\n collectInterval: number\r\n /** 落盘间隔 (条数),默认 60 */\r\n persistInterval: number\r\n /** 是否采集渲染进程 V8 详情(需要 preload 配合),默认 false */\r\n enableRendererDetail: boolean\r\n /** 是否采集 V8 堆空间详情,默认 true */\r\n enableV8HeapSpaces: boolean\r\n\r\n // ===== 异常检测 =====\r\n anomaly: {\r\n /** 是否启用异常检测,默认 true */\r\n enabled: boolean\r\n /** 检测间隔 (ms),默认 30000 */\r\n checkInterval: number\r\n /** 自定义检测规则(追加到内置规则) */\r\n rules: AnomalyRule[]\r\n }\r\n\r\n // ===== 存储配置 =====\r\n storage: {\r\n /** 数据存储目录,默认 app.getPath('userData') + '/memory-monitor' */\r\n directory: string\r\n /** 最大保留会话数,默认 50 */\r\n maxSessions: number\r\n /** 单次会话最大时长 (ms),默认 24h */\r\n maxSessionDuration: number\r\n }\r\n\r\n // ===== 监控面板配置 =====\r\n dashboard: {\r\n /** 窗口宽度,默认 1400 */\r\n width: number\r\n /** 窗口高度,默认 900 */\r\n height: number\r\n /** 是否置顶,默认 false */\r\n alwaysOnTop: boolean\r\n /** 打开看板时是否自动打开 DevTools,默认 false;为 true 或环境变量 LAUNCHER_MEMORY_MONITOR_DEVTOOLS=1 时开启 */\r\n openDevToolsOnStart: boolean\r\n }\r\n\r\n // ===== 进程标注 =====\r\n /** 给窗口进程打标签,方便识别 */\r\n processLabels: Record<string, string>\r\n}\r\n\r\n/** 默认配置 */\r\nexport const DEFAULT_CONFIG: MonitorConfig = {\r\n enabled: true,\r\n autoStart: true,\r\n openDashboardOnStart: true,\r\n\r\n collectInterval: 2000,\r\n persistInterval: 60,\r\n enableRendererDetail: false,\r\n enableV8HeapSpaces: true,\r\n\r\n anomaly: {\r\n enabled: true,\r\n checkInterval: 30000,\r\n rules: [],\r\n },\r\n\r\n storage: {\r\n directory: '', // 运行时由 app.getPath('userData') 填充\r\n maxSessions: 50,\r\n maxSessionDuration: 24 * 60 * 60 * 1000,\r\n },\r\n\r\n dashboard: {\r\n width: 1400,\r\n height: 900,\r\n alwaysOnTop: false,\r\n openDevToolsOnStart: false,\r\n },\r\n\r\n processLabels: {},\r\n}\r\n"],"mappings":"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","names":["import_electron","path","v8","import_events","import_events","os","cpus","import_electron","path","import_electron","path","sep","import_electron","fs","resolve"]}
|