@electron-memory/monitor 0.1.0
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/dashboard-preload.js +75 -0
- package/dist/dashboard-preload.js.map +1 -0
- package/dist/index.d.mts +510 -0
- package/dist/index.d.ts +510 -0
- package/dist/index.js +1640 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +1602 -0
- package/dist/index.mjs.map +1 -0
- package/dist/preload.d.mts +18 -0
- package/dist/preload.d.ts +18 -0
- package/dist/preload.js +91 -0
- package/dist/preload.js.map +1 -0
- package/dist/preload.mjs +66 -0
- package/dist/preload.mjs.map +1 -0
- package/dist/ui/assets/index-BXj3TlLS.js +9 -0
- package/dist/ui/assets/index-DpEoEDgy.css +1 -0
- package/dist/ui/assets/vendor-BMPuFM9B.js +104 -0
- package/dist/ui/index.html +14 -0
- package/package.json +70 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../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 * 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":["app","path","v8","EventEmitter","EventEmitter","EventEmitter","os","cpus","BrowserWindow","path","EventEmitter","app","fs"]}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 渲染进程 V8 内存上报注入
|
|
3
|
+
*
|
|
4
|
+
* 这是可选的 Level 2 接入:在业务项目的 preload.ts 中调用
|
|
5
|
+
* 用于采集渲染进程自身的 V8 堆详情
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* 注入渲染进程内存上报器
|
|
9
|
+
* 在业务项目的 preload.ts 中调用:
|
|
10
|
+
*
|
|
11
|
+
* ```ts
|
|
12
|
+
* import { injectRendererReporter } from '@electron-memory/monitor/preload'
|
|
13
|
+
* injectRendererReporter()
|
|
14
|
+
* ```
|
|
15
|
+
*/
|
|
16
|
+
declare function injectRendererReporter(interval?: number): () => void;
|
|
17
|
+
|
|
18
|
+
export { injectRendererReporter };
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 渲染进程 V8 内存上报注入
|
|
3
|
+
*
|
|
4
|
+
* 这是可选的 Level 2 接入:在业务项目的 preload.ts 中调用
|
|
5
|
+
* 用于采集渲染进程自身的 V8 堆详情
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* 注入渲染进程内存上报器
|
|
9
|
+
* 在业务项目的 preload.ts 中调用:
|
|
10
|
+
*
|
|
11
|
+
* ```ts
|
|
12
|
+
* import { injectRendererReporter } from '@electron-memory/monitor/preload'
|
|
13
|
+
* injectRendererReporter()
|
|
14
|
+
* ```
|
|
15
|
+
*/
|
|
16
|
+
declare function injectRendererReporter(interval?: number): () => void;
|
|
17
|
+
|
|
18
|
+
export { injectRendererReporter };
|
package/dist/preload.js
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/preload/inject.ts
|
|
21
|
+
var inject_exports = {};
|
|
22
|
+
__export(inject_exports, {
|
|
23
|
+
injectRendererReporter: () => injectRendererReporter
|
|
24
|
+
});
|
|
25
|
+
module.exports = __toCommonJS(inject_exports);
|
|
26
|
+
var import_electron = require("electron");
|
|
27
|
+
|
|
28
|
+
// src/ipc/channels.ts
|
|
29
|
+
var IPC_CHANNELS = {
|
|
30
|
+
// === 数据推送(主进程 → 监控面板)===
|
|
31
|
+
SNAPSHOT: "emm:snapshot",
|
|
32
|
+
ANOMALY: "emm:anomaly",
|
|
33
|
+
// === 会话控制(面板 → 主进程)===
|
|
34
|
+
SESSION_START: "emm:session:start",
|
|
35
|
+
SESSION_STOP: "emm:session:stop",
|
|
36
|
+
SESSION_LIST: "emm:session:list",
|
|
37
|
+
SESSION_REPORT: "emm:session:report",
|
|
38
|
+
SESSION_COMPARE: "emm:session:compare",
|
|
39
|
+
// === 数据查询(面板 → 主进程)===
|
|
40
|
+
SESSION_SNAPSHOTS: "emm:session:snapshots",
|
|
41
|
+
// === 工具操作(面板 → 主进程)===
|
|
42
|
+
TRIGGER_GC: "emm:gc",
|
|
43
|
+
HEAP_SNAPSHOT: "emm:heap-snapshot",
|
|
44
|
+
MARK: "emm:mark",
|
|
45
|
+
CONFIG_UPDATE: "emm:config:update",
|
|
46
|
+
GET_CONFIG: "emm:config:get",
|
|
47
|
+
GET_SESSIONS: "emm:sessions:get",
|
|
48
|
+
// === 导入导出(面板 → 主进程)===
|
|
49
|
+
SESSION_EXPORT: "emm:session:export",
|
|
50
|
+
SESSION_IMPORT: "emm:session:import",
|
|
51
|
+
SESSION_DELETE: "emm:session:delete",
|
|
52
|
+
// === 渲染进程上报(可选)===
|
|
53
|
+
RENDERER_REPORT: "emm:renderer:report",
|
|
54
|
+
RENDERER_REQUEST: "emm:renderer:request"
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
// src/preload/inject.ts
|
|
58
|
+
function injectRendererReporter(interval = 2e3) {
|
|
59
|
+
let timer = null;
|
|
60
|
+
const report = () => {
|
|
61
|
+
try {
|
|
62
|
+
const mem = process.memoryUsage();
|
|
63
|
+
import_electron.ipcRenderer.send(IPC_CHANNELS.RENDERER_REPORT, {
|
|
64
|
+
webContentsId: -1,
|
|
65
|
+
// 由主进程根据 sender 重写
|
|
66
|
+
pid: process.pid,
|
|
67
|
+
heapUsed: mem.heapUsed,
|
|
68
|
+
heapTotal: mem.heapTotal,
|
|
69
|
+
external: mem.external,
|
|
70
|
+
arrayBuffers: mem.arrayBuffers
|
|
71
|
+
});
|
|
72
|
+
} catch {
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
import_electron.ipcRenderer.on(IPC_CHANNELS.RENDERER_REQUEST, () => {
|
|
76
|
+
report();
|
|
77
|
+
});
|
|
78
|
+
timer = setInterval(report, interval);
|
|
79
|
+
return () => {
|
|
80
|
+
if (timer) {
|
|
81
|
+
clearInterval(timer);
|
|
82
|
+
timer = null;
|
|
83
|
+
}
|
|
84
|
+
import_electron.ipcRenderer.removeAllListeners(IPC_CHANNELS.RENDERER_REQUEST);
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
88
|
+
0 && (module.exports = {
|
|
89
|
+
injectRendererReporter
|
|
90
|
+
});
|
|
91
|
+
//# sourceMappingURL=preload.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/preload/inject.ts","../src/ipc/channels.ts"],"sourcesContent":["/**\r\n * 渲染进程 V8 内存上报注入\r\n * \r\n * 这是可选的 Level 2 接入:在业务项目的 preload.ts 中调用\r\n * 用于采集渲染进程自身的 V8 堆详情\r\n */\r\n\r\nimport { ipcRenderer } from 'electron'\r\nimport { IPC_CHANNELS } from '../ipc/channels'\r\n\r\n/**\r\n * 注入渲染进程内存上报器\r\n * 在业务项目的 preload.ts 中调用:\r\n * \r\n * ```ts\r\n * import { injectRendererReporter } from '@electron-memory/monitor/preload'\r\n * injectRendererReporter()\r\n * ```\r\n */\r\nexport function injectRendererReporter(interval = 2000): () => void {\r\n let timer: ReturnType<typeof setInterval> | null = null\r\n\r\n const report = () => {\r\n try {\r\n const mem = process.memoryUsage()\r\n ipcRenderer.send(IPC_CHANNELS.RENDERER_REPORT, {\r\n webContentsId: -1, // 由主进程根据 sender 重写\r\n pid: process.pid,\r\n heapUsed: mem.heapUsed,\r\n heapTotal: mem.heapTotal,\r\n external: mem.external,\r\n arrayBuffers: mem.arrayBuffers,\r\n })\r\n } catch {\r\n // 忽略错误\r\n }\r\n }\r\n\r\n // 监听主进程请求上报\r\n ipcRenderer.on(IPC_CHANNELS.RENDERER_REQUEST, () => {\r\n report()\r\n })\r\n\r\n // 定时上报\r\n timer = setInterval(report, interval)\r\n\r\n // 返回清理函数\r\n return () => {\r\n if (timer) {\r\n clearInterval(timer)\r\n timer = null\r\n }\r\n ipcRenderer.removeAllListeners(IPC_CHANNELS.RENDERER_REQUEST)\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"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAOA,sBAA4B;;;ACFrB,IAAM,eAAe;AAAA;AAAA,EAE1B,UAAU;AAAA,EACV,SAAS;AAAA;AAAA,EAGT,eAAe;AAAA,EACf,cAAc;AAAA,EACd,cAAc;AAAA,EACd,gBAAgB;AAAA,EAChB,iBAAiB;AAAA;AAAA,EAGjB,mBAAmB;AAAA;AAAA,EAGnB,YAAY;AAAA,EACZ,eAAe;AAAA,EACf,MAAM;AAAA,EACN,eAAe;AAAA,EACf,YAAY;AAAA,EACZ,cAAc;AAAA;AAAA,EAGd,gBAAgB;AAAA,EAChB,gBAAgB;AAAA,EAChB,gBAAgB;AAAA;AAAA,EAGhB,iBAAiB;AAAA,EACjB,kBAAkB;AACpB;;;ADjBO,SAAS,uBAAuB,WAAW,KAAkB;AAClE,MAAI,QAA+C;AAEnD,QAAM,SAAS,MAAM;AACnB,QAAI;AACF,YAAM,MAAM,QAAQ,YAAY;AAChC,kCAAY,KAAK,aAAa,iBAAiB;AAAA,QAC7C,eAAe;AAAA;AAAA,QACf,KAAK,QAAQ;AAAA,QACb,UAAU,IAAI;AAAA,QACd,WAAW,IAAI;AAAA,QACf,UAAU,IAAI;AAAA,QACd,cAAc,IAAI;AAAA,MACpB,CAAC;AAAA,IACH,QAAQ;AAAA,IAER;AAAA,EACF;AAGA,8BAAY,GAAG,aAAa,kBAAkB,MAAM;AAClD,WAAO;AAAA,EACT,CAAC;AAGD,UAAQ,YAAY,QAAQ,QAAQ;AAGpC,SAAO,MAAM;AACX,QAAI,OAAO;AACT,oBAAc,KAAK;AACnB,cAAQ;AAAA,IACV;AACA,gCAAY,mBAAmB,aAAa,gBAAgB;AAAA,EAC9D;AACF;","names":[]}
|
package/dist/preload.mjs
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
// src/preload/inject.ts
|
|
2
|
+
import { ipcRenderer } from "electron";
|
|
3
|
+
|
|
4
|
+
// src/ipc/channels.ts
|
|
5
|
+
var IPC_CHANNELS = {
|
|
6
|
+
// === 数据推送(主进程 → 监控面板)===
|
|
7
|
+
SNAPSHOT: "emm:snapshot",
|
|
8
|
+
ANOMALY: "emm:anomaly",
|
|
9
|
+
// === 会话控制(面板 → 主进程)===
|
|
10
|
+
SESSION_START: "emm:session:start",
|
|
11
|
+
SESSION_STOP: "emm:session:stop",
|
|
12
|
+
SESSION_LIST: "emm:session:list",
|
|
13
|
+
SESSION_REPORT: "emm:session:report",
|
|
14
|
+
SESSION_COMPARE: "emm:session:compare",
|
|
15
|
+
// === 数据查询(面板 → 主进程)===
|
|
16
|
+
SESSION_SNAPSHOTS: "emm:session:snapshots",
|
|
17
|
+
// === 工具操作(面板 → 主进程)===
|
|
18
|
+
TRIGGER_GC: "emm:gc",
|
|
19
|
+
HEAP_SNAPSHOT: "emm:heap-snapshot",
|
|
20
|
+
MARK: "emm:mark",
|
|
21
|
+
CONFIG_UPDATE: "emm:config:update",
|
|
22
|
+
GET_CONFIG: "emm:config:get",
|
|
23
|
+
GET_SESSIONS: "emm:sessions:get",
|
|
24
|
+
// === 导入导出(面板 → 主进程)===
|
|
25
|
+
SESSION_EXPORT: "emm:session:export",
|
|
26
|
+
SESSION_IMPORT: "emm:session:import",
|
|
27
|
+
SESSION_DELETE: "emm:session:delete",
|
|
28
|
+
// === 渲染进程上报(可选)===
|
|
29
|
+
RENDERER_REPORT: "emm:renderer:report",
|
|
30
|
+
RENDERER_REQUEST: "emm:renderer:request"
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
// src/preload/inject.ts
|
|
34
|
+
function injectRendererReporter(interval = 2e3) {
|
|
35
|
+
let timer = null;
|
|
36
|
+
const report = () => {
|
|
37
|
+
try {
|
|
38
|
+
const mem = process.memoryUsage();
|
|
39
|
+
ipcRenderer.send(IPC_CHANNELS.RENDERER_REPORT, {
|
|
40
|
+
webContentsId: -1,
|
|
41
|
+
// 由主进程根据 sender 重写
|
|
42
|
+
pid: process.pid,
|
|
43
|
+
heapUsed: mem.heapUsed,
|
|
44
|
+
heapTotal: mem.heapTotal,
|
|
45
|
+
external: mem.external,
|
|
46
|
+
arrayBuffers: mem.arrayBuffers
|
|
47
|
+
});
|
|
48
|
+
} catch {
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
ipcRenderer.on(IPC_CHANNELS.RENDERER_REQUEST, () => {
|
|
52
|
+
report();
|
|
53
|
+
});
|
|
54
|
+
timer = setInterval(report, interval);
|
|
55
|
+
return () => {
|
|
56
|
+
if (timer) {
|
|
57
|
+
clearInterval(timer);
|
|
58
|
+
timer = null;
|
|
59
|
+
}
|
|
60
|
+
ipcRenderer.removeAllListeners(IPC_CHANNELS.RENDERER_REQUEST);
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
export {
|
|
64
|
+
injectRendererReporter
|
|
65
|
+
};
|
|
66
|
+
//# sourceMappingURL=preload.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/preload/inject.ts","../src/ipc/channels.ts"],"sourcesContent":["/**\r\n * 渲染进程 V8 内存上报注入\r\n * \r\n * 这是可选的 Level 2 接入:在业务项目的 preload.ts 中调用\r\n * 用于采集渲染进程自身的 V8 堆详情\r\n */\r\n\r\nimport { ipcRenderer } from 'electron'\r\nimport { IPC_CHANNELS } from '../ipc/channels'\r\n\r\n/**\r\n * 注入渲染进程内存上报器\r\n * 在业务项目的 preload.ts 中调用:\r\n * \r\n * ```ts\r\n * import { injectRendererReporter } from '@electron-memory/monitor/preload'\r\n * injectRendererReporter()\r\n * ```\r\n */\r\nexport function injectRendererReporter(interval = 2000): () => void {\r\n let timer: ReturnType<typeof setInterval> | null = null\r\n\r\n const report = () => {\r\n try {\r\n const mem = process.memoryUsage()\r\n ipcRenderer.send(IPC_CHANNELS.RENDERER_REPORT, {\r\n webContentsId: -1, // 由主进程根据 sender 重写\r\n pid: process.pid,\r\n heapUsed: mem.heapUsed,\r\n heapTotal: mem.heapTotal,\r\n external: mem.external,\r\n arrayBuffers: mem.arrayBuffers,\r\n })\r\n } catch {\r\n // 忽略错误\r\n }\r\n }\r\n\r\n // 监听主进程请求上报\r\n ipcRenderer.on(IPC_CHANNELS.RENDERER_REQUEST, () => {\r\n report()\r\n })\r\n\r\n // 定时上报\r\n timer = setInterval(report, interval)\r\n\r\n // 返回清理函数\r\n return () => {\r\n if (timer) {\r\n clearInterval(timer)\r\n timer = null\r\n }\r\n ipcRenderer.removeAllListeners(IPC_CHANNELS.RENDERER_REQUEST)\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"],"mappings":";AAOA,SAAS,mBAAmB;;;ACFrB,IAAM,eAAe;AAAA;AAAA,EAE1B,UAAU;AAAA,EACV,SAAS;AAAA;AAAA,EAGT,eAAe;AAAA,EACf,cAAc;AAAA,EACd,cAAc;AAAA,EACd,gBAAgB;AAAA,EAChB,iBAAiB;AAAA;AAAA,EAGjB,mBAAmB;AAAA;AAAA,EAGnB,YAAY;AAAA,EACZ,eAAe;AAAA,EACf,MAAM;AAAA,EACN,eAAe;AAAA,EACf,YAAY;AAAA,EACZ,cAAc;AAAA;AAAA,EAGd,gBAAgB;AAAA,EAChB,gBAAgB;AAAA,EAChB,gBAAgB;AAAA;AAAA,EAGhB,iBAAiB;AAAA,EACjB,kBAAkB;AACpB;;;ADjBO,SAAS,uBAAuB,WAAW,KAAkB;AAClE,MAAI,QAA+C;AAEnD,QAAM,SAAS,MAAM;AACnB,QAAI;AACF,YAAM,MAAM,QAAQ,YAAY;AAChC,kBAAY,KAAK,aAAa,iBAAiB;AAAA,QAC7C,eAAe;AAAA;AAAA,QACf,KAAK,QAAQ;AAAA,QACb,UAAU,IAAI;AAAA,QACd,WAAW,IAAI;AAAA,QACf,UAAU,IAAI;AAAA,QACd,cAAc,IAAI;AAAA,MACpB,CAAC;AAAA,IACH,QAAQ;AAAA,IAER;AAAA,EACF;AAGA,cAAY,GAAG,aAAa,kBAAkB,MAAM;AAClD,WAAO;AAAA,EACT,CAAC;AAGD,UAAQ,YAAY,QAAQ,QAAQ;AAGpC,SAAO,MAAM;AACX,QAAI,OAAO;AACT,oBAAc,KAAK;AACnB,cAAQ;AAAA,IACV;AACA,gBAAY,mBAAmB,aAAa,gBAAgB;AAAA,EAC9D;AACF;","names":[]}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import{r as Ce,a as Pe,g as Me,b as d,R as ne,L as $e,C as Be,X as ke,Y as Re,T as ie,c as le,d as k,e as Te,P as Ae,f as Fe,h as Le,i as Ie}from"./vendor-BMPuFM9B.js";(function(){const a=document.createElement("link").relList;if(a&&a.supports&&a.supports("modulepreload"))return;for(const n of document.querySelectorAll('link[rel="modulepreload"]'))o(n);new MutationObserver(n=>{for(const m of n)if(m.type==="childList")for(const v of m.addedNodes)v.tagName==="LINK"&&v.rel==="modulepreload"&&o(v)}).observe(document,{childList:!0,subtree:!0});function l(n){const m={};return n.integrity&&(m.integrity=n.integrity),n.referrerPolicy&&(m.referrerPolicy=n.referrerPolicy),n.crossOrigin==="use-credentials"?m.credentials="include":n.crossOrigin==="anonymous"?m.credentials="omit":m.credentials="same-origin",m}function o(n){if(n.ep)return;n.ep=!0;const m=l(n);fetch(n.href,m)}})();var D={exports:{}},R={};/**
|
|
2
|
+
* @license React
|
|
3
|
+
* react-jsx-runtime.production.min.js
|
|
4
|
+
*
|
|
5
|
+
* Copyright (c) Facebook, Inc. and its affiliates.
|
|
6
|
+
*
|
|
7
|
+
* This source code is licensed under the MIT license found in the
|
|
8
|
+
* LICENSE file in the root directory of this source tree.
|
|
9
|
+
*/var X;function Ue(){if(X)return R;X=1;var s=Ce(),a=Symbol.for("react.element"),l=Symbol.for("react.fragment"),o=Object.prototype.hasOwnProperty,n=s.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.ReactCurrentOwner,m={key:!0,ref:!0,__self:!0,__source:!0};function v(c,h,t){var j,f={},g=null,N=null;t!==void 0&&(g=""+t),h.key!==void 0&&(g=""+h.key),h.ref!==void 0&&(N=h.ref);for(j in h)o.call(h,j)&&!m.hasOwnProperty(j)&&(f[j]=h[j]);if(c&&c.defaultProps)for(j in h=c.defaultProps,h)f[j]===void 0&&(f[j]=h[j]);return{$$typeof:a,type:c,key:g,ref:N,props:f,_owner:n.current}}return R.Fragment=l,R.jsx=v,R.jsxs=v,R}var Q;function Ee(){return Q||(Q=1,D.exports=Ue()),D.exports}var e=Ee(),_={},ee;function Ke(){if(ee)return _;ee=1;var s=Pe();return _.createRoot=s.createRoot,_.hydrateRoot=s.hydrateRoot,_}var _e=Ke();const Ge=Me(_e),M=({title:s,value:a,unit:l,trend:o,trendValue:n,color:m="#646cff",icon:v})=>{const c=o==="up"?"↑":o==="down"?"↓":"→",h=o==="up"?"#ff4d4f":o==="down"?"#52c41a":"#faad14";return e.jsxs("div",{className:"metric-card",style:{borderTopColor:m},children:[e.jsxs("div",{className:"metric-card-header",children:[v&&e.jsx("span",{className:"metric-card-icon",children:v}),e.jsx("span",{className:"metric-card-title",children:s})]}),e.jsxs("div",{className:"metric-card-value",children:[e.jsx("span",{className:"metric-card-number",children:a}),l&&e.jsx("span",{className:"metric-card-unit",children:l})]}),o&&n&&e.jsxs("div",{className:"metric-card-trend",style:{color:h},children:[e.jsx("span",{children:c}),e.jsx("span",{children:n})]})]})},se=s=>s>1024*1024?`${(s/1024/1024).toFixed(1)} GB`:s>1024?`${(s/1024).toFixed(1)} MB`:`${Math.round(s)} KB`,Oe=s=>{switch(s){case"Browser":return"#646cff";case"Tab":return"#61dafb";case"GPU":return"#f5a623";case"Utility":return"#8b8b8b";default:return"#999"}},Ve=s=>{switch(s){case"Browser":return"主进程";case"Tab":return"渲染进程";case"GPU":return"GPU";case"Utility":return"辅助进程";default:return s}},oe=({processes:s})=>{const a=[...s].sort((l,o)=>o.memory.workingSetSize-l.memory.workingSetSize);return e.jsx("div",{className:"process-table-container",children:e.jsxs("table",{className:"process-table",children:[e.jsx("thead",{children:e.jsxs("tr",{children:[e.jsx("th",{children:"PID"}),e.jsx("th",{children:"类型"}),e.jsx("th",{children:"名称"}),e.jsx("th",{children:"工作集"}),e.jsx("th",{children:"峰值"}),e.jsx("th",{children:"CPU"})]})}),e.jsx("tbody",{children:a.map(l=>e.jsxs("tr",{className:l.isMonitorProcess?"monitor-process":"",children:[e.jsx("td",{className:"pid",children:l.pid}),e.jsx("td",{children:e.jsx("span",{className:"process-type-badge",style:{backgroundColor:Oe(l.type)},children:Ve(l.type)})}),e.jsxs("td",{className:"process-name",children:[l.isMonitorProcess?"🔍 ":"",l.name||l.windowTitle||"-"]}),e.jsx("td",{className:"memory-value",children:se(l.memory.workingSetSize)}),e.jsx("td",{className:"memory-value",children:se(l.memory.peakWorkingSetSize)}),e.jsxs("td",{className:"cpu-value",children:[l.cpu.percentCPUUsage.toFixed(1),"%"]})]},l.pid))})]})})},te=s=>{const a=new Date(s);return`${a.getHours().toString().padStart(2,"0")}:${a.getMinutes().toString().padStart(2,"0")}:${a.getSeconds().toString().padStart(2,"0")}`},T={total:"#646cff",browser:"#f5a623",renderer:"#61dafb",gpu:"#ff6b6b",other:"#8b8b8b"},ce=({snapshots:s,height:a=300})=>{const l=d.useMemo(()=>s.map(n=>{const m=n.processes.filter(t=>t.type==="Browser").reduce((t,j)=>t+j.memory.workingSetSize,0),v=n.processes.filter(t=>t.type==="Tab"&&!t.isMonitorProcess).reduce((t,j)=>t+j.memory.workingSetSize,0),c=n.processes.filter(t=>t.type==="GPU").reduce((t,j)=>t+j.memory.workingSetSize,0),h=n.processes.filter(t=>!t.isMonitorProcess&&t.type!=="Browser"&&t.type!=="GPU"&&t.type!=="Tab").reduce((t,j)=>t+j.memory.workingSetSize,0);return{time:te(n.timestamp),timestamp:n.timestamp,total:Math.round(n.totalWorkingSetSize/1024*10)/10,browser:Math.round(m/1024*10)/10,renderer:Math.round(v/1024*10)/10,gpu:Math.round(c/1024*10)/10,other:Math.round(h/1024*10)/10}}),[s]),o=d.useMemo(()=>{const n=[];for(const m of s)m.marks&&n.push(...m.marks);return n},[s]);return l.length===0?e.jsx("div",{className:"chart-empty",children:"等待数据采集..."}):e.jsx(ne,{width:"100%",height:a,children:e.jsxs($e,{data:l,margin:{top:5,right:30,left:20,bottom:5},children:[e.jsx(Be,{strokeDasharray:"3 3",stroke:"rgba(255,255,255,0.1)"}),e.jsx(ke,{dataKey:"time",stroke:"rgba(255,255,255,0.5)",fontSize:12,interval:"preserveStartEnd"}),e.jsx(Re,{stroke:"rgba(255,255,255,0.5)",fontSize:12,tickFormatter:n=>`${n} MB`}),e.jsx(ie,{contentStyle:{background:"rgba(26, 26, 46, 0.95)",border:"1px solid rgba(255,255,255,0.1)",borderRadius:8,color:"#e0e0e0"},formatter:(n,m)=>[`${n.toFixed(1)} MB`,m]}),e.jsx(le,{}),e.jsx(k,{type:"monotone",dataKey:"total",name:"总内存",stroke:T.total,dot:!1,strokeWidth:2}),e.jsx(k,{type:"monotone",dataKey:"browser",name:"主进程",stroke:T.browser,dot:!1,strokeWidth:1.5}),e.jsx(k,{type:"monotone",dataKey:"renderer",name:"渲染进程",stroke:T.renderer,dot:!1,strokeWidth:1.5}),e.jsx(k,{type:"monotone",dataKey:"gpu",name:"GPU",stroke:T.gpu,dot:!1,strokeWidth:1.5}),e.jsx(k,{type:"monotone",dataKey:"other",name:"其他",stroke:T.other,dot:!1,strokeWidth:1,strokeDasharray:"4 2"}),o.map((n,m)=>e.jsx(Te,{x:te(n.timestamp),stroke:"#faad14",strokeDasharray:"3 3",label:{value:n.label,position:"top",fill:"#faad14",fontSize:10}},m))]})})},ze={Browser:"#7c83ff",Tab:"#61dafb",GPU:"#ffb347",Utility:"#a8a8a8",Zygote:"#ff8a8a"},De={Browser:"主进程",Tab:"渲染进程",GPU:"GPU",Utility:"辅助进程",Zygote:"Zygote"},ae=Math.PI/180,He=({cx:s,cy:a,midAngle:l,outerRadius:o,name:n,value:m,percent:v})=>{const c=o+25,h=s+c*Math.cos(-l*ae),t=a+c*Math.sin(-l*ae);return e.jsx("text",{x:h,y:t,fill:"#e0e0e0",textAnchor:h>s?"start":"end",dominantBaseline:"central",fontSize:12,children:`${n} ${m}MB (${(v*100).toFixed(1)}%)`})},de=({processes:s,height:a=280})=>{const l=d.useMemo(()=>{const o=new Map;for(const n of s){if(n.isMonitorProcess)continue;const m=n.type;o.set(m,(o.get(m)||0)+n.memory.workingSetSize)}return Array.from(o.entries()).map(([n,m])=>({name:De[n]||n,value:Math.round(m/1024),color:ze[n]||"#bbb"})).sort((n,m)=>m.value-n.value)},[s]);return e.jsx(ne,{width:"100%",height:a,children:e.jsxs(Ae,{children:[e.jsx(Fe,{data:l,dataKey:"value",nameKey:"name",cx:"50%",cy:"50%",outerRadius:80,innerRadius:28,label:He,labelLine:{stroke:"rgba(255,255,255,0.4)",strokeWidth:1},paddingAngle:2,isAnimationActive:!1,children:l.map((o,n)=>e.jsx(Le,{fill:o.color,stroke:"rgba(0,0,0,0.3)",strokeWidth:1},n))}),e.jsx(ie,{contentStyle:{background:"rgba(26, 26, 46, 0.95)",border:"1px solid rgba(255,255,255,0.1)",borderRadius:8,color:"#e0e0e0"},formatter:o=>[`${o} MB`,"内存"]}),e.jsx(le,{})]})})},We={info:{icon:"ℹ️",color:"#1890ff",bg:"rgba(24, 144, 255, 0.1)"},warning:{icon:"⚠️",color:"#faad14",bg:"rgba(250, 173, 20, 0.1)"},critical:{icon:"🔴",color:"#ff4d4f",bg:"rgba(255, 77, 79, 0.1)"}},qe=({anomalies:s,onClear:a})=>s.length===0?e.jsxs("div",{className:"alert-panel-empty",children:[e.jsx("span",{className:"alert-panel-empty-icon",children:"✅"}),e.jsx("span",{children:"暂无异常检测到"})]}):e.jsxs("div",{className:"alert-panel",children:[e.jsxs("div",{className:"alert-panel-header",children:[e.jsxs("span",{children:["异常告警 (",s.length,")"]}),e.jsx("button",{className:"alert-panel-clear",onClick:a,children:"清除"})]}),e.jsx("div",{className:"alert-panel-list",children:s.slice(-10).reverse().map(l=>{const o=We[l.severity];return e.jsxs("div",{className:"alert-item",style:{borderLeftColor:o.color,background:o.bg},children:[e.jsxs("div",{className:"alert-item-header",children:[e.jsx("span",{className:"alert-item-icon",children:o.icon}),e.jsx("span",{className:"alert-item-title",children:l.title}),e.jsx("span",{className:"alert-item-time",children:new Date(l.timestamp).toLocaleTimeString()})]}),e.jsx("div",{className:"alert-item-desc",children:l.description})]},l.id)})})]}),Je=({isRunning:s,currentSessionId:a,onStart:l,onStop:o,onTriggerGC:n,onAddMark:m})=>{const[v,c]=d.useState(""),[h,t]=d.useState(""),j=()=>{v.trim()&&(l(v.trim()),c(""))},f=()=>{h.trim()&&(m(h.trim()),t(""))};return e.jsxs("div",{className:"session-control",children:[e.jsxs("div",{className:"session-control-status",children:[e.jsx("span",{className:`status-dot ${s?"running":"idle"}`}),e.jsx("span",{children:s?`会话进行中: ${a==null?void 0:a.slice(0,8)}...`:"未开始会话"})]}),e.jsxs("div",{className:"session-control-actions",children:[s?e.jsx("button",{className:"btn btn-danger",onClick:o,children:"⏹ 结束会话"}):e.jsxs("div",{className:"session-start-form",children:[e.jsx("input",{type:"text",placeholder:"会话标签(如 v1.2.0 空载基准)",value:v,onChange:g=>c(g.target.value),onKeyDown:g=>g.key==="Enter"&&j()}),e.jsx("button",{className:"btn btn-primary",onClick:j,disabled:!v.trim(),children:"▶ 开始会话"})]}),e.jsxs("div",{className:"session-tools",children:[e.jsx("button",{className:"btn btn-secondary",onClick:n,title:"手动触发垃圾回收",children:"🗑️ GC"}),e.jsxs("div",{className:"mark-form",children:[e.jsx("input",{type:"text",placeholder:"事件标记",value:h,onChange:g=>t(g.target.value),onKeyDown:g=>g.key==="Enter"&&f()}),e.jsx("button",{className:"btn btn-secondary",onClick:f,disabled:!h.trim(),children:"📌 标记"})]})]})]})]})},b=s=>s==null||isNaN(s)||s===0?"0 B":s>1024*1024*1024?`${(s/1024/1024/1024).toFixed(2)} GB`:s>1024*1024?`${(s/1024/1024).toFixed(1)} MB`:s>1024?`${(s/1024).toFixed(1)} KB`:`${s} B`,me=({v8Detail:s})=>{const a=(s==null?void 0:s.heapTotal)>0?Math.round((s.heapUsed||0)/s.heapTotal*100):0;return e.jsxs("div",{className:"v8-heap-detail",children:[e.jsx("h3",{children:"主进程 V8 堆详情"}),e.jsxs("div",{className:"v8-overview",children:[e.jsxs("div",{className:"v8-stat",children:[e.jsx("span",{className:"v8-stat-label",children:"Heap Used"}),e.jsx("span",{className:"v8-stat-value",children:b(s.heapUsed)})]}),e.jsxs("div",{className:"v8-stat",children:[e.jsx("span",{className:"v8-stat-label",children:"Heap Total"}),e.jsx("span",{className:"v8-stat-value",children:b(s.heapTotal)})]}),e.jsxs("div",{className:"v8-stat",children:[e.jsx("span",{className:"v8-stat-label",children:"使用率"}),e.jsxs("span",{className:`v8-stat-value ${a>80?"danger":a>60?"warn":""}`,children:[a,"%"]})]}),e.jsxs("div",{className:"v8-stat",children:[e.jsx("span",{className:"v8-stat-label",children:"External"}),e.jsx("span",{className:"v8-stat-value",children:b(s.external)})]}),e.jsxs("div",{className:"v8-stat",children:[e.jsx("span",{className:"v8-stat-label",children:"ArrayBuffers"}),e.jsx("span",{className:"v8-stat-value",children:b(s.arrayBuffers)})]}),e.jsxs("div",{className:"v8-stat",children:[e.jsx("span",{className:"v8-stat-label",children:"RSS"}),e.jsx("span",{className:"v8-stat-value",children:b(s.rss)})]}),e.jsxs("div",{className:"v8-stat",children:[e.jsx("span",{className:"v8-stat-label",children:"Heap Limit"}),e.jsx("span",{className:"v8-stat-value",children:b(s.heapSizeLimit)})]}),e.jsxs("div",{className:"v8-stat",children:[e.jsx("span",{className:"v8-stat-label",children:"Malloced"}),e.jsx("span",{className:"v8-stat-value",children:b(s.mallocedMemory)})]}),e.jsxs("div",{className:"v8-stat",children:[e.jsx("span",{className:"v8-stat-label",children:"Detached Contexts"}),e.jsx("span",{className:`v8-stat-value ${(s.numberOfDetachedContexts??0)>0?"danger":""}`,children:s.numberOfDetachedContexts??0})]}),e.jsxs("div",{className:"v8-stat",children:[e.jsx("span",{className:"v8-stat-label",children:"Native Contexts"}),e.jsx("span",{className:"v8-stat-value",children:s.numberOfNativeContexts??0})]})]}),s.heapSpaces&&s.heapSpaces.length>0&&e.jsxs("div",{className:"v8-heap-spaces",children:[e.jsx("h4",{children:"堆空间详情"}),e.jsxs("table",{className:"v8-spaces-table",children:[e.jsx("thead",{children:e.jsxs("tr",{children:[e.jsx("th",{children:"空间"}),e.jsx("th",{children:"大小"}),e.jsx("th",{children:"已使用"}),e.jsx("th",{children:"使用率"}),e.jsx("th",{children:"可用"})]})}),e.jsx("tbody",{children:s.heapSpaces.map(l=>{const o=l.size??0,n=l.usedSize??0,m=l.availableSize??0,v=o>0?Math.round(n/o*100):0;return e.jsxs("tr",{children:[e.jsx("td",{className:"space-name",children:l.name}),e.jsx("td",{children:b(o)}),e.jsx("td",{children:b(n)}),e.jsx("td",{children:e.jsxs("div",{className:"usage-bar",children:[e.jsx("div",{className:"usage-bar-fill",style:{width:`${v}%`,backgroundColor:v>80?"#ff4d4f":v>60?"#faad14":"#52c41a"}}),e.jsxs("span",{className:"usage-bar-text",children:[v,"%"]})]})}),e.jsx("td",{children:b(m)})]},l.name)})})]})]})]})};function Ye(){const[s,a]=d.useState([]),[l,o]=d.useState(null),[n,m]=d.useState([]),[v,c]=d.useState(!1),h=d.useRef([]);d.useEffect(()=>{var i,u;const g=p=>{o(p),c(!0),h.current=[...h.current,p].slice(-300),a(h.current)},N=p=>{m(y=>[...y,p])};return(i=window.monitorAPI)==null||i.onSnapshot(g),(u=window.monitorAPI)==null||u.onAnomaly(N),()=>{var p,y;(p=window.monitorAPI)==null||p.removeSnapshotListener(),(y=window.monitorAPI)==null||y.removeAnomalyListener()}},[]);const t=d.useCallback(async()=>{var g;return(g=window.monitorAPI)==null?void 0:g.triggerGC()},[]),j=d.useCallback(async g=>{var N;return(N=window.monitorAPI)==null?void 0:N.addMark(g)},[]),f=d.useCallback(()=>{m([])},[]);return{snapshots:s,latestSnapshot:l,anomalies:n,isCollecting:v,triggerGC:t,addMark:j,clearAnomalies:f}}function W(){const[s,a]=d.useState(null),[l,o]=d.useState(!1),[n,m]=d.useState([]),v=d.useCallback(async(u,p)=>{var w;const y=await((w=window.monitorAPI)==null?void 0:w.startSession(u,p));return y&&(a(y),o(!0)),y},[]),c=d.useCallback(async()=>{var p;const u=await((p=window.monitorAPI)==null?void 0:p.stopSession());return a(null),o(!1),await h(),u},[]),h=d.useCallback(async()=>{var p;const u=await((p=window.monitorAPI)==null?void 0:p.getSessions());u&&m(u)},[]),t=d.useCallback(async u=>{var p;return await((p=window.monitorAPI)==null?void 0:p.getSessionReport(u))},[]),j=d.useCallback(async(u,p,y,w)=>{var $;return await(($=window.monitorAPI)==null?void 0:$.getSessionSnapshots(u,p,y,w))||[]},[]),f=d.useCallback(async(u,p)=>{var y;return(y=window.monitorAPI)==null?void 0:y.compareSessions(u,p)},[]),g=d.useCallback(async u=>{var y;return await((y=window.monitorAPI)==null?void 0:y.exportSession(u))},[]),N=d.useCallback(async()=>{var p;const u=await((p=window.monitorAPI)==null?void 0:p.importSession());return u!=null&&u.success&&await h(),u},[h]),i=d.useCallback(async u=>{var y;const p=await((y=window.monitorAPI)==null?void 0:y.deleteSession(u));return p&&await h(),p},[h]);return{currentSessionId:s,isRunning:l,sessions:n,startSession:v,stopSession:c,refreshSessions:h,getSessionReport:t,getSessionSnapshots:j,compareSessions:f,exportSession:g,importSession:N,deleteSession:i}}const H=s=>s==null||isNaN(s)?"0 KB":s>1024*1024?`${(s/1024/1024).toFixed(2)} GB`:s>1024?`${(s/1024).toFixed(1)} MB`:`${Math.round(s)} KB`,Ze=s=>s==null||isNaN(s)||s===0?"0 B":s>1024*1024*1024?`${(s/1024/1024/1024).toFixed(2)} GB`:s>1024*1024?`${(s/1024/1024).toFixed(1)} MB`:s>1024?`${(s/1024).toFixed(1)} KB`:`${s} B`,Xe=()=>{const{snapshots:s,latestSnapshot:a,anomalies:l,triggerGC:o,addMark:n,clearAnomalies:m}=Ye(),{currentSessionId:v,isRunning:c,startSession:h,stopSession:t}=W();if(!a)return e.jsxs("div",{className:"dashboard-loading",children:[e.jsx("div",{className:"loading-spinner"}),e.jsx("p",{children:"等待内存数据..."})]});const j=a.processes.find(N=>N.type==="Browser"),f=a.processes.filter(N=>N.type==="Tab"&&!N.isMonitorProcess),g=f.reduce((N,i)=>N+i.memory.workingSetSize,0);return e.jsxs("div",{className:"dashboard",children:[e.jsx(Je,{isRunning:c,currentSessionId:v,onStart:h,onStop:t,onTriggerGC:o,onAddMark:n}),e.jsxs("div",{className:"metric-cards-row",children:[e.jsx(M,{icon:"💻",title:"总内存",value:H(a.totalWorkingSetSize),color:"#646cff"}),e.jsx(M,{icon:"🧠",title:"主进程",value:H((j==null?void 0:j.memory.workingSetSize)||0),color:"#f5a623"}),e.jsx(M,{icon:"🖼️",title:"渲染进程",value:H(g),unit:`(${f.length}个)`,color:"#61dafb"}),e.jsx(M,{icon:"📊",title:"V8 Heap Used",value:Ze(a.mainProcessMemory.heapUsed),color:"#52c41a"}),e.jsx(M,{icon:"⚙️",title:"系统内存",value:`${a.system.usagePercent}%`,color:"#ff6b6b"}),e.jsx(M,{icon:"🔢",title:"进程数",value:`${a.processes.length}`,color:"#8b8b8b"})]}),e.jsxs("div",{className:"charts-row",children:[e.jsxs("div",{className:"chart-container chart-wide",children:[e.jsx("h3",{children:"📈 内存趋势"}),e.jsx(ce,{snapshots:s,height:300})]}),e.jsxs("div",{className:"chart-container chart-narrow",children:[e.jsx("h3",{children:"🥧 内存分布"}),e.jsx(de,{processes:a.processes,height:300})]})]}),e.jsxs("div",{className:"section",children:[e.jsx("h3",{children:"📋 进程详情"}),e.jsx(oe,{processes:a.processes})]}),a.mainProcessV8Detail&&e.jsx("div",{className:"section",children:e.jsx(me,{v8Detail:a.mainProcessV8Detail})}),e.jsxs("div",{className:"section",children:[e.jsx("h3",{children:"🚨 异常告警"}),e.jsx(qe,{anomalies:l,onClear:m})]})]})},Qe={info:{icon:"ℹ️",color:"#1890ff"},warning:{icon:"⚠️",color:"#faad14"},critical:{icon:"🔴",color:"#ff4d4f"}},es=({suggestions:s})=>s.length===0?e.jsx("div",{className:"suggestion-panel-empty",children:e.jsx("span",{children:"✅ 未发现需要改进的地方"})}):e.jsx("div",{className:"suggestion-panel",children:s.map(a=>{const l=Qe[a.severity];return e.jsxs("div",{className:"suggestion-item",style:{borderLeftColor:l.color},children:[e.jsxs("div",{className:"suggestion-header",children:[e.jsx("span",{className:"suggestion-icon",children:l.icon}),e.jsx("span",{className:"suggestion-title",children:a.title}),e.jsx("span",{className:"suggestion-category",children:a.category})]}),e.jsx("p",{className:"suggestion-desc",children:a.description}),e.jsx("ul",{className:"suggestion-list",children:a.suggestions.map((o,n)=>e.jsx("li",{children:o},n))}),a.relatedCode&&a.relatedCode.length>0&&e.jsx("div",{className:"suggestion-code",children:a.relatedCode.map((o,n)=>e.jsx("code",{children:o},n))})]},a.id)})}),re=s=>{const a=Math.floor(s/1e3),l=Math.floor(a/60),o=Math.floor(l/60);return o>0?`${o}h ${l%60}m ${a%60}s`:l>0?`${l}m ${a%60}s`:`${a}s`},A=s=>s==null||isNaN(s)||s===0?"0 B":s>1024*1024*1024?`${(s/1024/1024/1024).toFixed(2)} GB`:s>1024*1024?`${(s/1024/1024).toFixed(1)} MB`:s>1024?`${(s/1024).toFixed(1)} KB`:`${s} B`,S=s=>s==null||isNaN(s)?"0 KB":s>1024*1024?`${(s/1024/1024).toFixed(2)} GB`:s>1024?`${(s/1024).toFixed(1)} MB`:`${Math.round(s)} KB`,G=s=>{const a=new Date(s);return`${a.getHours().toString().padStart(2,"0")}:${a.getMinutes().toString().padStart(2,"0")}:${a.getSeconds().toString().padStart(2,"0")}`},ss=[{label:"全部",value:"all"},{label:"最近 1 分钟",value:"1m"},{label:"最近 5 分钟",value:"5m"},{label:"最近 10 分钟",value:"10m"},{label:"最近 30 分钟",value:"30m"},{label:"自定义",value:"custom"}],ts=[{label:"自动",value:0},{label:"最多 200 点",value:200},{label:"最多 400 点",value:400},{label:"最多 600 点",value:600},{label:"最多 1000 点",value:1e3}],as=()=>{const{sessions:s,refreshSessions:a,getSessionReport:l,getSessionSnapshots:o,exportSession:n,importSession:m,deleteSession:v}=W(),[c,h]=d.useState(null),[t,j]=d.useState(null),[f,g]=d.useState([]),[N,i]=d.useState(!1),[u,p]=d.useState(!1),[y,w]=d.useState(!1),[O,$]=d.useState(!1),[F,q]=d.useState("all"),[L,he]=d.useState(""),[I,ue]=d.useState(""),[U,xe]=d.useState(0),[V,pe]=d.useState(!0),[E,je]=d.useState(!0),[B,ve]=d.useState(!0),[z,fe]=d.useState(!0);d.useEffect(()=>{a()},[a]);const ge=async r=>{h(r),i(!0),q("all");try{const x=await l(r.id);j(x)}catch(x){console.error("Failed to load report:",x)}i(!1)},K=d.useMemo(()=>{if(!t)return{start:void 0,end:void 0};const r=t.endTime;switch(F){case"1m":return{start:r-60*1e3,end:r};case"5m":return{start:r-300*1e3,end:r};case"10m":return{start:r-600*1e3,end:r};case"30m":return{start:r-1800*1e3,end:r};case"custom":{const x=L?new Date(L).getTime():void 0,P=I?new Date(I).getTime():void 0;return{start:x&&!isNaN(x)?x:void 0,end:P&&!isNaN(P)?P:void 0}}default:return{start:void 0,end:void 0}}},[t,F,L,I]),J=d.useMemo(()=>{if(U>0)return U;if(!t)return 600;const r=t.duration/6e4;return r>30?300:r>10?400:600},[U,t]),Y=d.useCallback(async()=>{if(c){p(!0);try{const r=await o(c.id,K.start,K.end,J);g(r)}catch(r){console.error("Failed to load snapshots:",r)}p(!1)}},[c,o,K.start,K.end,J]);d.useEffect(()=>{c&&t&&Y()},[c,t,Y]);const Ne=async r=>{w(!0);try{const x=await n(r);x!=null&&x.success?console.log("导出成功:",x.filePath):x!=null&&x.error&&x.error!=="用户取消"&&(console.error("导出失败:",x.error),alert("导出失败: "+x.error))}catch(x){console.error("导出异常:",x)}w(!1)},ye=async()=>{$(!0);try{const r=await m();r!=null&&r.success?console.log("导入成功"):r!=null&&r.error&&r.error!=="用户取消"&&(console.error("导入失败:",r.error),alert("导入失败: "+r.error))}catch(r){console.error("导入异常:",r)}$(!1)},be=async(r,x)=>{if(r.stopPropagation(),!confirm("确定要删除这个会话吗?此操作不可恢复。"))return;await v(x)&&(c==null?void 0:c.id)===x&&(h(null),j(null),g([]))},C=d.useMemo(()=>f.length>0?f[f.length-1]:null,[f]),Z=d.useMemo(()=>f.length===0?null:f[Math.floor(f.length/2)],[f]);return e.jsx("div",{className:"report-page",children:e.jsxs("div",{className:"report-layout",children:[e.jsxs("div",{className:"session-list",children:[e.jsx("h3",{children:"📋 历史会话"}),e.jsxs("div",{className:"session-list-actions",children:[e.jsx("button",{className:"btn btn-secondary btn-sm",onClick:a,children:"🔄 刷新"}),e.jsx("button",{className:"btn btn-secondary btn-sm",onClick:ye,disabled:O,children:O?"⏳ 导入中...":"📥 导入"})]}),s.length===0?e.jsxs("div",{className:"session-list-empty",children:[e.jsx("p",{children:"暂无历史会话"}),e.jsx("p",{className:"hint",children:"在实时监控页面开始一个测试会话"})]}):e.jsx("div",{className:"session-items",children:[...s].reverse().map(r=>e.jsxs("div",{className:`session-item ${(c==null?void 0:c.id)===r.id?"active":""}`,onClick:()=>ge(r),children:[e.jsxs("div",{className:"session-item-header",children:[e.jsx("div",{className:"session-item-label",children:r.label}),e.jsxs("div",{className:"session-item-actions",children:[e.jsx("button",{className:"btn-icon",title:"导出会话",onClick:x=>{x.stopPropagation(),Ne(r.id)},disabled:y,children:"📤"}),e.jsx("button",{className:"btn-icon btn-icon-danger",title:"删除会话",onClick:x=>be(x,r.id),children:"🗑️"})]})]}),e.jsxs("div",{className:"session-item-meta",children:[e.jsx("span",{children:new Date(r.startTime).toLocaleDateString()}),e.jsx("span",{children:r.duration?re(r.duration):"进行中"}),e.jsxs("span",{children:[r.snapshotCount," 条"]})]}),e.jsx("span",{className:`session-status ${r.status}`,children:r.status})]},r.id))})]}),e.jsxs("div",{className:"report-detail",children:[N&&e.jsx("div",{className:"report-loading",children:"加载中..."}),!N&&!t&&e.jsxs("div",{className:"report-placeholder",children:[e.jsx("span",{className:"report-placeholder-icon",children:"📊"}),e.jsx("p",{children:"选择左侧的会话查看报告"})]}),!N&&t&&e.jsxs("div",{className:"report-content",children:[e.jsx("h2",{children:t.label}),t.description&&e.jsx("p",{className:"report-desc",children:t.description}),e.jsxs("div",{className:"report-meta-bar",children:[e.jsxs("span",{children:["⏱️ ",re(t.duration)]}),e.jsxs("span",{children:["📅 ",new Date(t.startTime).toLocaleString()," ~ ",new Date(t.endTime).toLocaleString()]})]}),e.jsxs("div",{className:"report-section",children:[e.jsx("h3",{children:"🖥️ 运行环境"}),e.jsxs("div",{className:"env-grid",children:[e.jsxs("div",{className:"env-item",children:[e.jsx("span",{className:"env-label",children:"Electron"}),e.jsxs("span",{className:"env-value",children:["v",t.environment.electronVersion]})]}),e.jsxs("div",{className:"env-item",children:[e.jsx("span",{className:"env-label",children:"Chrome"}),e.jsxs("span",{className:"env-value",children:["v",t.environment.chromeVersion]})]}),e.jsxs("div",{className:"env-item",children:[e.jsx("span",{className:"env-label",children:"Node.js"}),e.jsxs("span",{className:"env-value",children:["v",t.environment.nodeVersion]})]}),e.jsxs("div",{className:"env-item",children:[e.jsx("span",{className:"env-label",children:"平台"}),e.jsxs("span",{className:"env-value",children:[t.environment.platform,"/",t.environment.arch]})]}),e.jsxs("div",{className:"env-item",children:[e.jsx("span",{className:"env-label",children:"系统内存"}),e.jsx("span",{className:"env-value",children:A(t.environment.totalSystemMemory)})]}),e.jsxs("div",{className:"env-item",children:[e.jsx("span",{className:"env-label",children:"CPU"}),e.jsxs("span",{className:"env-value",children:[t.environment.cpuCores," 核"]})]})]})]}),e.jsxs("div",{className:"report-section",children:[e.jsx("h3",{children:"📊 统计汇总"}),e.jsxs("table",{className:"report-stats-table",children:[e.jsx("thead",{children:e.jsxs("tr",{children:[e.jsx("th",{children:"指标"}),e.jsx("th",{children:"初始值"}),e.jsx("th",{children:"最终值"}),e.jsx("th",{children:"平均值"}),e.jsx("th",{children:"P95"}),e.jsx("th",{children:"变化"})]})}),e.jsxs("tbody",{children:[e.jsxs("tr",{children:[e.jsx("td",{children:"总内存"}),e.jsx("td",{children:S(t.summary.totalMemory.initial)}),e.jsx("td",{children:S(t.summary.totalMemory.final)}),e.jsx("td",{children:S(t.summary.totalMemory.avg)}),e.jsx("td",{children:S(t.summary.totalMemory.p95)}),e.jsxs("td",{className:t.summary.totalMemory.deltaPercent>5?"degraded":"",children:[t.summary.totalMemory.deltaPercent>0?"+":"",t.summary.totalMemory.deltaPercent.toFixed(1),"%"]})]}),e.jsxs("tr",{children:[e.jsx("td",{children:"主进程"}),e.jsx("td",{children:S(t.summary.byProcessType.browser.initial)}),e.jsx("td",{children:S(t.summary.byProcessType.browser.final)}),e.jsx("td",{children:S(t.summary.byProcessType.browser.avg)}),e.jsx("td",{children:S(t.summary.byProcessType.browser.p95)}),e.jsxs("td",{className:t.summary.byProcessType.browser.deltaPercent>10?"degraded":"",children:[t.summary.byProcessType.browser.deltaPercent>0?"+":"",t.summary.byProcessType.browser.deltaPercent.toFixed(1),"%"]})]}),e.jsxs("tr",{children:[e.jsx("td",{children:"V8 Heap Used"}),e.jsx("td",{children:A(t.summary.mainV8Heap.heapUsed.initial)}),e.jsx("td",{children:A(t.summary.mainV8Heap.heapUsed.final)}),e.jsx("td",{children:A(t.summary.mainV8Heap.heapUsed.avg)}),e.jsx("td",{children:A(t.summary.mainV8Heap.heapUsed.p95)}),e.jsxs("td",{className:t.summary.mainV8Heap.heapUsed.deltaPercent>10?"degraded":"",children:[t.summary.mainV8Heap.heapUsed.deltaPercent>0?"+":"",t.summary.mainV8Heap.heapUsed.deltaPercent.toFixed(1),"%"]})]})]})]})]}),e.jsxs("div",{className:"report-section report-visual-section",children:[e.jsx("h3",{children:"📈 数据可视化"}),e.jsxs("div",{className:"report-time-controls",children:[e.jsxs("div",{className:"time-presets",children:[e.jsx("span",{className:"control-label",children:"时间范围:"}),ss.map(r=>e.jsx("button",{className:`btn btn-sm ${F===r.value?"btn-primary":"btn-secondary"}`,onClick:()=>q(r.value),children:r.label},r.value))]}),F==="custom"&&e.jsxs("div",{className:"time-custom-range",children:[e.jsx("span",{className:"control-label",children:"自定义范围:"}),e.jsx("input",{type:"text",placeholder:`起始 (如 ${G(t.startTime)})`,value:L,onChange:r=>he(r.target.value)}),e.jsx("span",{children:"~"}),e.jsx("input",{type:"text",placeholder:`结束 (如 ${G(t.endTime)})`,value:I,onChange:r=>ue(r.target.value)})]}),e.jsxs("div",{className:"granularity-control",children:[e.jsx("span",{className:"control-label",children:"显示粒度:"}),e.jsx("select",{value:U,onChange:r=>xe(Number(r.target.value)),children:ts.map(r=>e.jsx("option",{value:r.value,children:r.label},r.value))}),e.jsx("span",{className:"data-info",children:u?"加载中...":`已加载 ${f.length} 个数据点`})]})]}),e.jsxs("div",{className:"report-panel-toggles",children:[e.jsx("button",{className:`panel-toggle ${V?"active":""}`,onClick:()=>pe(!V),children:"📈 内存趋势"}),e.jsx("button",{className:`panel-toggle ${E?"active":""}`,onClick:()=>je(!E),children:"🥧 内存分布"}),e.jsx("button",{className:`panel-toggle ${B?"active":""}`,onClick:()=>ve(!B),children:"📋 进程详情"}),e.jsx("button",{className:`panel-toggle ${z?"active":""}`,onClick:()=>fe(!z),children:"🔧 V8 堆详情"})]}),u&&e.jsxs("div",{className:"report-snapshots-loading",children:[e.jsx("div",{className:"loading-spinner",style:{width:24,height:24}}),e.jsx("span",{children:"正在加载快照数据..."})]}),!u&&f.length===0&&e.jsx("div",{className:"report-no-data",children:e.jsx("span",{children:"该时间范围内无数据"})}),!u&&f.length>0&&e.jsxs(e.Fragment,{children:[V&&e.jsx("div",{className:"report-chart-panel",children:e.jsxs("div",{className:"chart-container",style:{margin:0},children:[e.jsxs("h4",{children:["📈 内存趋势(",G(f[0].timestamp)," ~ ",G(f[f.length-1].timestamp),")"]}),e.jsx(ce,{snapshots:f,height:320})]})}),(E||B)&&e.jsxs("div",{className:"report-charts-row",children:[E&&Z&&e.jsxs("div",{className:"chart-container",style:{margin:0,flex:B?"0 0 360px":"1"},children:[e.jsx("h4",{children:"🥧 内存分布(中间时刻快照)"}),e.jsx(de,{processes:Z.processes,height:280})]}),B&&C&&e.jsxs("div",{className:"chart-container",style:{margin:0,flex:1,overflow:"auto"},children:[e.jsx("h4",{children:"📋 进程详情(最后时刻快照)"}),e.jsx(oe,{processes:C.processes})]})]}),z&&(C==null?void 0:C.mainProcessV8Detail)&&e.jsx("div",{className:"report-chart-panel",children:e.jsx("div",{className:"chart-container",style:{margin:0},children:e.jsx(me,{v8Detail:C.mainProcessV8Detail})})})]})]}),e.jsxs("div",{className:"report-section",children:[e.jsx("h3",{children:"📈 趋势分析"}),e.jsx("div",{className:"trend-grid",children:["totalMemory","browserMemory","rendererMemory"].map(r=>{const x=t.summary.trends[r],P=x.direction==="growing"?"📈":x.direction==="shrinking"?"📉":"→",Se=x.direction==="growing"?"增长":x.direction==="shrinking"?"下降":"稳定",we=r==="totalMemory"?"总内存":r==="browserMemory"?"主进程":"渲染进程";return e.jsxs("div",{className:"trend-item",children:[e.jsx("span",{className:"trend-label",children:we}),e.jsxs("span",{className:"trend-direction",children:[P," ",Se]}),e.jsxs("span",{className:"trend-slope",children:[x.slope.toFixed(2)," KB/s"]}),e.jsxs("span",{className:"trend-r2",children:["R²=",x.r2.toFixed(3)]}),e.jsx("span",{className:`trend-confidence ${x.confidence}`,children:x.confidence})]},r)})})]}),e.jsxs("div",{className:"report-section",children:[e.jsx("h3",{children:"💡 改进建议"}),e.jsx(es,{suggestions:t.suggestions})]}),t.anomalies.length>0&&e.jsxs("div",{className:"report-section",children:[e.jsxs("h3",{children:["🚨 异常事件 (",t.anomalies.length,")"]}),e.jsx("div",{className:"anomaly-timeline",children:t.anomalies.map(r=>e.jsxs("div",{className:`anomaly-item severity-${r.severity}`,children:[e.jsx("span",{className:"anomaly-time",children:new Date(r.timestamp).toLocaleTimeString()}),e.jsx("span",{className:"anomaly-title",children:r.title}),e.jsx("span",{className:"anomaly-desc",children:r.description})]},r.id))})]})]})]})]})})},rs=s=>s>1024*1024?`${(s/1024/1024).toFixed(2)} GB`:s>1024?`${(s/1024).toFixed(1)} MB`:`${Math.round(s)} KB`,ns=s=>s===0?"0 B":s>1024*1024*1024?`${(s/1024/1024/1024).toFixed(2)} GB`:s>1024*1024?`${(s/1024/1024).toFixed(1)} MB`:s>1024?`${(s/1024).toFixed(1)} KB`:`${s} B`,is=()=>{const{sessions:s,refreshSessions:a,compareSessions:l}=W(),[o,n]=d.useState(""),[m,v]=d.useState(""),[c,h]=d.useState(null),[t,j]=d.useState(!1);d.useEffect(()=>{a()},[a]);const f=async()=>{if(!(!o||!m)){j(!0);try{const i=await l(o,m);h(i)}catch(i){console.error("Compare failed:",i)}j(!1)}},g=s.filter(i=>i.status==="completed"),N={pass:{icon:"✅",color:"#52c41a",label:"PASS - 通过"},warn:{icon:"🟡",color:"#faad14",label:"WARN - 存在轻微劣化"},fail:{icon:"🔴",color:"#ff4d4f",label:"FAIL - 存在严重劣化"}};return e.jsxs("div",{className:"compare-page",children:[e.jsx("h2",{children:"🔄 迭代对比"}),e.jsxs("div",{className:"compare-selector",children:[e.jsxs("div",{className:"compare-select-group",children:[e.jsx("label",{children:"基准会话 (旧版本)"}),e.jsxs("select",{value:o,onChange:i=>n(i.target.value),children:[e.jsx("option",{value:"",children:"-- 选择基准会话 --"}),g.map(i=>e.jsxs("option",{value:i.id,children:[i.label," (",new Date(i.startTime).toLocaleDateString(),")"]},i.id))]})]}),e.jsx("span",{className:"compare-arrow",children:"→"}),e.jsxs("div",{className:"compare-select-group",children:[e.jsx("label",{children:"对比会话 (新版本)"}),e.jsxs("select",{value:m,onChange:i=>v(i.target.value),children:[e.jsx("option",{value:"",children:"-- 选择对比会话 --"}),g.map(i=>e.jsxs("option",{value:i.id,children:[i.label," (",new Date(i.startTime).toLocaleDateString(),")"]},i.id))]})]}),e.jsx("button",{className:"btn btn-primary",onClick:f,disabled:!o||!m||t,children:t?"对比中...":"开始对比"})]}),c&&e.jsxs("div",{className:"compare-result",children:[e.jsxs("div",{className:"compare-verdict",style:{borderColor:N[c.verdict].color},children:[e.jsx("span",{className:"verdict-icon",children:N[c.verdict].icon}),e.jsx("span",{className:"verdict-label",style:{color:N[c.verdict].color},children:N[c.verdict].label}),e.jsx("p",{className:"verdict-reason",children:c.verdictReason})]}),e.jsxs("div",{className:"compare-section",children:[e.jsx("h3",{children:"📊 指标对比"}),e.jsxs("table",{className:"compare-table",children:[e.jsx("thead",{children:e.jsxs("tr",{children:[e.jsx("th",{children:"指标"}),e.jsx("th",{children:c.base.label}),e.jsx("th",{children:c.target.label}),e.jsx("th",{children:"变化"}),e.jsx("th",{children:"状态"})]})}),e.jsx("tbody",{children:[{name:"总内存 (avg)",diff:c.overall.totalMemory,isBytes:!1},{name:"主进程 (avg)",diff:c.overall.browserMemory,isBytes:!1},{name:"渲染进程 (avg)",diff:c.overall.rendererMemory,isBytes:!1},...c.overall.gpuMemory?[{name:"GPU (avg)",diff:c.overall.gpuMemory,isBytes:!1}]:[],{name:"V8 Heap Used",diff:c.v8Heap.heapUsed,isBytes:!0},{name:"V8 Heap Total",diff:c.v8Heap.heapTotal,isBytes:!0},{name:"V8 External",diff:c.v8Heap.external,isBytes:!0}].map(i=>{const u=i.diff.status==="improved"?"✅":i.diff.status==="degraded"?"🔴":"➖",p=i.isBytes?ns:rs;return e.jsxs("tr",{className:`status-${i.diff.status}`,children:[e.jsx("td",{children:i.name}),e.jsx("td",{children:p(i.diff.base)}),e.jsx("td",{children:p(i.diff.target)}),e.jsxs("td",{className:i.diff.deltaPercent>5?"degraded":i.diff.deltaPercent<-3?"improved":"",children:[i.diff.deltaPercent>0?"+":"",i.diff.deltaPercent.toFixed(1),"%"]}),e.jsx("td",{children:u})]},i.name)})})]})]}),e.jsxs("div",{className:"compare-section",children:[e.jsx("h3",{children:"📈 趋势变化"}),e.jsx("div",{className:"trend-changes",children:c.trendChanges.map(i=>{const u=i.change==="improved"?"✅":i.change==="degraded"?"🔴":"➖";return e.jsxs("div",{className:`trend-change-item ${i.change}`,children:[e.jsx("span",{className:"trend-change-metric",children:i.metric}),e.jsxs("span",{children:["基准: ",i.baseSlope.toFixed(2)," KB/s"]}),e.jsxs("span",{children:["目标: ",i.targetSlope.toFixed(2)," KB/s"]}),e.jsx("span",{children:u})]},i.metric)})})]}),c.regressions.length>0&&e.jsxs("div",{className:"compare-section",children:[e.jsxs("h3",{children:["⚠️ 劣化项 (",c.regressions.length,")"]}),e.jsx("div",{className:"regression-list",children:c.regressions.map((i,u)=>e.jsxs("div",{className:`regression-item severity-${i.severity}`,children:[e.jsxs("div",{className:"regression-header",children:[e.jsx("span",{className:"regression-metric",children:i.metric}),e.jsxs("span",{className:"regression-delta",children:["+",i.deltaPercent.toFixed(1),"%"]}),e.jsx("span",{className:`regression-severity ${i.severity}`,children:i.severity})]}),e.jsx("p",{className:"regression-desc",children:i.description}),e.jsxs("p",{className:"regression-suggestion",children:["💡 ",i.suggestion]})]},u))})]}),c.improvements.length>0&&e.jsxs("div",{className:"compare-section",children:[e.jsxs("h3",{children:["✅ 改进项 (",c.improvements.length,")"]}),e.jsx("div",{className:"improvement-list",children:c.improvements.map((i,u)=>e.jsxs("div",{className:"improvement-item",children:[e.jsx("span",{className:"improvement-metric",children:i.metric}),e.jsxs("span",{className:"improvement-delta",children:[i.deltaPercent.toFixed(1),"%"]}),e.jsx("span",{className:"improvement-desc",children:i.description})]},u))})]})]})]})},ls=()=>{const[s,a]=d.useState("dashboard");return e.jsxs("div",{className:"monitor-app",children:[e.jsxs("nav",{className:"monitor-nav",children:[e.jsxs("div",{className:"monitor-nav-brand",children:[e.jsx("span",{className:"monitor-nav-icon",children:"📊"}),e.jsx("span",{className:"monitor-nav-title",children:"Electron Memory Monitor"})]}),e.jsxs("div",{className:"monitor-nav-tabs",children:[e.jsx("button",{className:`monitor-nav-tab ${s==="dashboard"?"active":""}`,onClick:()=>a("dashboard"),children:"🔴 实时监控"}),e.jsx("button",{className:`monitor-nav-tab ${s==="report"?"active":""}`,onClick:()=>a("report"),children:"📋 历史报告"}),e.jsx("button",{className:`monitor-nav-tab ${s==="compare"?"active":""}`,onClick:()=>a("compare"),children:"🔄 迭代对比"})]})]}),e.jsxs("main",{className:"monitor-main",children:[s==="dashboard"&&e.jsx(Xe,{}),s==="report"&&e.jsx(as,{}),s==="compare"&&e.jsx(is,{})]})]})};Ge.createRoot(document.getElementById("monitor-root")).render(e.jsx(Ie.StrictMode,{children:e.jsx(ls,{})}));
|