@electron-memory/monitor 0.2.3 → 0.2.5
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.map +1 -1
- package/dist/index.d.mts +37 -1
- package/dist/index.d.ts +37 -1
- package/dist/index.js +236 -26
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +236 -26
- package/dist/index.mjs.map +1 -1
- package/dist/ui/assets/index-BTI73y9e.css +1 -0
- package/dist/ui/assets/index-mExwYeTZ.js +9 -0
- package/dist/ui/index.html +2 -2
- package/package.json +1 -1
- package/dist/ui/assets/index-BXj3TlLS.js +0 -9
- package/dist/ui/assets/index-DpEoEDgy.css +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/ipc/preload-api.ts","../src/ipc/channels.ts","../src/core/dashboard-preload.ts"],"sourcesContent":["/**\r\n * Preload API 辅助\r\n * \r\n * 定义暴露给监控面板渲染进程的 API\r\n */\r\n\r\nimport { contextBridge, ipcRenderer } from 'electron'\r\nimport { IPC_CHANNELS } from './channels'\r\n\r\n/** 监控面板 preload 注入的 API 类型 */\r\nexport interface MonitorPanelAPI {\r\n // 会话控制\r\n startSession: (label: string, description?: string) => Promise<string>\r\n stopSession: () => Promise<unknown>\r\n getSessions: () => Promise<
|
|
1
|
+
{"version":3,"sources":["../src/ipc/preload-api.ts","../src/ipc/channels.ts","../src/core/dashboard-preload.ts"],"sourcesContent":["/**\r\n * Preload API 辅助\r\n * \r\n * 定义暴露给监控面板渲染进程的 API\r\n */\r\n\r\nimport { contextBridge, ipcRenderer } from 'electron'\r\nimport { IPC_CHANNELS } from './channels'\r\nimport type { SessionsListPayload, TestSession } from '../types/session'\r\n\r\n/** 监控面板 preload 注入的 API 类型 */\r\nexport interface MonitorPanelAPI {\r\n // 会话控制\r\n startSession: (label: string, description?: string) => Promise<string>\r\n stopSession: () => Promise<unknown>\r\n getSessions: () => Promise<SessionsListPayload | TestSession[]>\r\n getSessionReport: (sessionId: string) => Promise<unknown>\r\n compareSessions: (baseId: string, targetId: string) => Promise<unknown>\r\n\r\n // 数据查询\r\n getSessionSnapshots: (sessionId: string, startTime?: number, endTime?: number, maxPoints?: number) => Promise<unknown[]>\r\n\r\n // 导入导出\r\n exportSession: (sessionId: string) => Promise<{ success: boolean; filePath?: string; error?: string }>\r\n importSession: () => Promise<{ success: boolean; session?: unknown; error?: string }>\r\n deleteSession: (sessionId: string) => Promise<boolean>\r\n\r\n // 工具\r\n triggerGC: () => Promise<unknown>\r\n takeHeapSnapshot: (filePath?: string) => Promise<string>\r\n addMark: (label: string, metadata?: Record<string, unknown>) => Promise<void>\r\n getConfig: () => Promise<unknown>\r\n\r\n // 数据订阅\r\n onSnapshot: (callback: (data: unknown) => void) => void\r\n onAnomaly: (callback: (data: unknown) => void) => void\r\n\r\n // 移除监听器\r\n removeSnapshotListener: () => void\r\n removeAnomalyListener: () => void\r\n}\r\n\r\n/** 在监控面板的 preload 中注入 API */\r\nexport function injectMonitorPanelAPI(): void {\r\n const api: MonitorPanelAPI = {\r\n // 会话控制\r\n startSession: (label: string, description?: string) =>\r\n ipcRenderer.invoke(IPC_CHANNELS.SESSION_START, { label, description }),\r\n stopSession: () =>\r\n ipcRenderer.invoke(IPC_CHANNELS.SESSION_STOP),\r\n getSessions: () =>\r\n ipcRenderer.invoke(IPC_CHANNELS.GET_SESSIONS),\r\n getSessionReport: (sessionId: string) =>\r\n ipcRenderer.invoke(IPC_CHANNELS.SESSION_REPORT, sessionId),\r\n compareSessions: (baseId: string, targetId: string) =>\r\n ipcRenderer.invoke(IPC_CHANNELS.SESSION_COMPARE, { baseId, targetId }),\r\n\r\n // 数据查询\r\n getSessionSnapshots: (sessionId: string, startTime?: number, endTime?: number, maxPoints?: number) =>\r\n ipcRenderer.invoke(IPC_CHANNELS.SESSION_SNAPSHOTS, { sessionId, startTime, endTime, maxPoints }),\r\n\r\n // 导入导出\r\n exportSession: (sessionId: string) =>\r\n ipcRenderer.invoke(IPC_CHANNELS.SESSION_EXPORT, sessionId),\r\n importSession: () =>\r\n ipcRenderer.invoke(IPC_CHANNELS.SESSION_IMPORT),\r\n deleteSession: (sessionId: string) =>\r\n ipcRenderer.invoke(IPC_CHANNELS.SESSION_DELETE, sessionId),\r\n\r\n // 工具\r\n triggerGC: () =>\r\n ipcRenderer.invoke(IPC_CHANNELS.TRIGGER_GC),\r\n takeHeapSnapshot: (filePath?: string) =>\r\n ipcRenderer.invoke(IPC_CHANNELS.HEAP_SNAPSHOT, filePath),\r\n addMark: (label: string, metadata?: Record<string, unknown>) =>\r\n ipcRenderer.invoke(IPC_CHANNELS.MARK, { label, metadata }),\r\n getConfig: () =>\r\n ipcRenderer.invoke(IPC_CHANNELS.GET_CONFIG),\r\n\r\n // 数据订阅\r\n onSnapshot: (callback: (data: unknown) => void) => {\r\n ipcRenderer.on(IPC_CHANNELS.SNAPSHOT, (_event, data) => callback(data))\r\n },\r\n onAnomaly: (callback: (data: unknown) => void) => {\r\n ipcRenderer.on(IPC_CHANNELS.ANOMALY, (_event, data) => callback(data))\r\n },\r\n\r\n // 移除监听器\r\n removeSnapshotListener: () => {\r\n ipcRenderer.removeAllListeners(IPC_CHANNELS.SNAPSHOT)\r\n },\r\n removeAnomalyListener: () => {\r\n ipcRenderer.removeAllListeners(IPC_CHANNELS.ANOMALY)\r\n },\r\n }\r\n\r\n contextBridge.exposeInMainWorld('monitorAPI', api)\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 * Dashboard Preload 脚本\r\n * 在监控面板 BrowserWindow 中使用\r\n */\r\n\r\nimport { injectMonitorPanelAPI } from '../ipc/preload-api'\r\n\r\ninjectMonitorPanelAPI()\r\n"],"mappings":";;;AAMA,sBAA2C;;;ACDpC,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;;;ADOO,SAAS,wBAA8B;AAC5C,QAAM,MAAuB;AAAA;AAAA,IAE3B,cAAc,CAAC,OAAe,gBAC5B,4BAAY,OAAO,aAAa,eAAe,EAAE,OAAO,YAAY,CAAC;AAAA,IACvE,aAAa,MACX,4BAAY,OAAO,aAAa,YAAY;AAAA,IAC9C,aAAa,MACX,4BAAY,OAAO,aAAa,YAAY;AAAA,IAC9C,kBAAkB,CAAC,cACjB,4BAAY,OAAO,aAAa,gBAAgB,SAAS;AAAA,IAC3D,iBAAiB,CAAC,QAAgB,aAChC,4BAAY,OAAO,aAAa,iBAAiB,EAAE,QAAQ,SAAS,CAAC;AAAA;AAAA,IAGvE,qBAAqB,CAAC,WAAmB,WAAoB,SAAkB,cAC7E,4BAAY,OAAO,aAAa,mBAAmB,EAAE,WAAW,WAAW,SAAS,UAAU,CAAC;AAAA;AAAA,IAGjG,eAAe,CAAC,cACd,4BAAY,OAAO,aAAa,gBAAgB,SAAS;AAAA,IAC3D,eAAe,MACb,4BAAY,OAAO,aAAa,cAAc;AAAA,IAChD,eAAe,CAAC,cACd,4BAAY,OAAO,aAAa,gBAAgB,SAAS;AAAA;AAAA,IAG3D,WAAW,MACT,4BAAY,OAAO,aAAa,UAAU;AAAA,IAC5C,kBAAkB,CAAC,aACjB,4BAAY,OAAO,aAAa,eAAe,QAAQ;AAAA,IACzD,SAAS,CAAC,OAAe,aACvB,4BAAY,OAAO,aAAa,MAAM,EAAE,OAAO,SAAS,CAAC;AAAA,IAC3D,WAAW,MACT,4BAAY,OAAO,aAAa,UAAU;AAAA;AAAA,IAG5C,YAAY,CAAC,aAAsC;AACjD,kCAAY,GAAG,aAAa,UAAU,CAAC,QAAQ,SAAS,SAAS,IAAI,CAAC;AAAA,IACxE;AAAA,IACA,WAAW,CAAC,aAAsC;AAChD,kCAAY,GAAG,aAAa,SAAS,CAAC,QAAQ,SAAS,SAAS,IAAI,CAAC;AAAA,IACvE;AAAA;AAAA,IAGA,wBAAwB,MAAM;AAC5B,kCAAY,mBAAmB,aAAa,QAAQ;AAAA,IACtD;AAAA,IACA,uBAAuB,MAAM;AAC3B,kCAAY,mBAAmB,aAAa,OAAO;AAAA,IACrD;AAAA,EACF;AAEA,gCAAc,kBAAkB,cAAc,GAAG;AACnD;;;AE1FA,sBAAsB;","names":[]}
|
package/dist/index.d.mts
CHANGED
|
@@ -175,6 +175,17 @@ interface MonitorConfig {
|
|
|
175
175
|
autoStart: boolean;
|
|
176
176
|
/** 启动后是否自动打开监控面板,默认 true */
|
|
177
177
|
openDashboardOnStart: boolean;
|
|
178
|
+
session: {
|
|
179
|
+
/**
|
|
180
|
+
* 监控启动后自动创建一条「进行中」会话并开始写入快照,无需在看板点「开始会话」。
|
|
181
|
+
* 每次进程启动一条新会话,标签带本地时间。默认 true。
|
|
182
|
+
*/
|
|
183
|
+
autoStartOnLaunch: boolean;
|
|
184
|
+
/** 自动会话标签前缀,完整标签为 `${prefix} YYYY-MM-DD HH:mm:ss` */
|
|
185
|
+
autoLabelPrefix: string;
|
|
186
|
+
/** 自动会话描述,可选 */
|
|
187
|
+
autoDescription?: string;
|
|
188
|
+
};
|
|
178
189
|
/** 采集间隔 (ms),默认 2000 */
|
|
179
190
|
collectInterval: number;
|
|
180
191
|
/** 落盘间隔 (条数),默认 60 */
|
|
@@ -243,6 +254,11 @@ interface SessionIndex {
|
|
|
243
254
|
sessions: TestSession[];
|
|
244
255
|
lastUpdated: number;
|
|
245
256
|
}
|
|
257
|
+
/** IPC 拉取会话列表时附带「当前是否在录」:以主进程内存为准,避免索引里僵尸 running */
|
|
258
|
+
interface SessionsListPayload {
|
|
259
|
+
sessions: TestSession[];
|
|
260
|
+
activeSessionId: string | null;
|
|
261
|
+
}
|
|
246
262
|
|
|
247
263
|
/**
|
|
248
264
|
* 报告与对比相关类型
|
|
@@ -277,6 +293,17 @@ interface TrendInfo {
|
|
|
277
293
|
/** 置信度 */
|
|
278
294
|
confidence: 'high' | 'medium' | 'low';
|
|
279
295
|
}
|
|
296
|
+
/** 会话中的阶段标记(写入报告,便于对照各阶段内存) */
|
|
297
|
+
interface SessionEventMark {
|
|
298
|
+
timestamp: number;
|
|
299
|
+
label: string;
|
|
300
|
+
metadata?: Record<string, unknown>;
|
|
301
|
+
/** 该标记随附快照时刻的总工作集 (KB) */
|
|
302
|
+
totalWorkingSetKB: number;
|
|
303
|
+
browserKB: number;
|
|
304
|
+
rendererKB: number;
|
|
305
|
+
gpuKB: number;
|
|
306
|
+
}
|
|
280
307
|
/** 改进建议 */
|
|
281
308
|
interface Suggestion {
|
|
282
309
|
/** 建议 ID */
|
|
@@ -339,6 +366,8 @@ interface SessionReport {
|
|
|
339
366
|
};
|
|
340
367
|
anomalies: AnomalyEvent[];
|
|
341
368
|
suggestions: Suggestion[];
|
|
369
|
+
/** 阶段标记汇总(与快照中的 marks 一致,便于表格展示) */
|
|
370
|
+
eventMarks?: SessionEventMark[];
|
|
342
371
|
dataFile: string;
|
|
343
372
|
}
|
|
344
373
|
/** 指标差异 */
|
|
@@ -438,6 +467,8 @@ declare class ElectronMemoryMonitor extends EventEmitter {
|
|
|
438
467
|
startSession(label: string, description?: string): string;
|
|
439
468
|
/** 结束当前会话 */
|
|
440
469
|
stopSession(): Promise<SessionReport | null>;
|
|
470
|
+
/** 为已落盘元数据的会话生成并写入 report.json(显式结束或被新会话顶替) */
|
|
471
|
+
private persistCompletedSessionReport;
|
|
441
472
|
/** 打开监控面板 */
|
|
442
473
|
openDashboard(): void;
|
|
443
474
|
/** 关闭监控面板 */
|
|
@@ -446,6 +477,10 @@ declare class ElectronMemoryMonitor extends EventEmitter {
|
|
|
446
477
|
getCurrentSnapshot(): MemorySnapshot | null;
|
|
447
478
|
/** 获取历史会话列表 */
|
|
448
479
|
getSessions(): Promise<TestSession[]>;
|
|
480
|
+
/**
|
|
481
|
+
* 供监控面板 IPC:列表来自磁盘索引,是否在录制以内存中的 currentSession 为准(避免索引僵尸 running)
|
|
482
|
+
*/
|
|
483
|
+
getSessionsPayloadForIpc(): SessionsListPayload;
|
|
449
484
|
/** 获取指定会话报告 */
|
|
450
485
|
getSessionReport(sessionId: string): Promise<SessionReport | null>;
|
|
451
486
|
/** 获取指定会话的快照数据(支持时间过滤和降采样) */
|
|
@@ -478,6 +513,7 @@ declare class ElectronMemoryMonitor extends EventEmitter {
|
|
|
478
513
|
getConfig(): MonitorConfig;
|
|
479
514
|
on(event: 'snapshot', handler: (data: MemorySnapshot) => void): this;
|
|
480
515
|
on(event: 'anomaly', handler: (event: AnomalyEvent) => void): this;
|
|
516
|
+
on(event: 'session-start', handler: (session: TestSession) => void): this;
|
|
481
517
|
on(event: 'session-end', handler: (report: SessionReport) => void): this;
|
|
482
518
|
private onSnapshot;
|
|
483
519
|
private mergeConfig;
|
|
@@ -515,4 +551,4 @@ declare const IPC_CHANNELS: {
|
|
|
515
551
|
readonly RENDERER_REQUEST: "emm:renderer:request";
|
|
516
552
|
};
|
|
517
553
|
|
|
518
|
-
export { type AnomalyCategory, type AnomalyEvent, type AnomalyRule, type AnomalySeverity, type CompareReport, ElectronMemoryMonitor, type EventMark, type GCResult, IPC_CHANNELS, type Improvement, type MemorySnapshot, type MetricDiff, type MetricSummary, type MonitorConfig, type ProcessMemoryInfo, type Regression, type RendererV8Detail, type SessionIndex, type SessionReport, type Suggestion, type SystemMemoryInfo, type TestSession, type TrendInfo, type V8HeapDetailStats, type V8HeapSpaceInfo, type V8HeapStats, registerDashboardSchemePrivileged };
|
|
554
|
+
export { type AnomalyCategory, type AnomalyEvent, type AnomalyRule, type AnomalySeverity, type CompareReport, ElectronMemoryMonitor, type EventMark, type GCResult, IPC_CHANNELS, type Improvement, type MemorySnapshot, type MetricDiff, type MetricSummary, type MonitorConfig, type ProcessMemoryInfo, type Regression, type RendererV8Detail, type SessionEventMark, type SessionIndex, type SessionReport, type SessionsListPayload, type Suggestion, type SystemMemoryInfo, type TestSession, type TrendInfo, type V8HeapDetailStats, type V8HeapSpaceInfo, type V8HeapStats, registerDashboardSchemePrivileged };
|
package/dist/index.d.ts
CHANGED
|
@@ -175,6 +175,17 @@ interface MonitorConfig {
|
|
|
175
175
|
autoStart: boolean;
|
|
176
176
|
/** 启动后是否自动打开监控面板,默认 true */
|
|
177
177
|
openDashboardOnStart: boolean;
|
|
178
|
+
session: {
|
|
179
|
+
/**
|
|
180
|
+
* 监控启动后自动创建一条「进行中」会话并开始写入快照,无需在看板点「开始会话」。
|
|
181
|
+
* 每次进程启动一条新会话,标签带本地时间。默认 true。
|
|
182
|
+
*/
|
|
183
|
+
autoStartOnLaunch: boolean;
|
|
184
|
+
/** 自动会话标签前缀,完整标签为 `${prefix} YYYY-MM-DD HH:mm:ss` */
|
|
185
|
+
autoLabelPrefix: string;
|
|
186
|
+
/** 自动会话描述,可选 */
|
|
187
|
+
autoDescription?: string;
|
|
188
|
+
};
|
|
178
189
|
/** 采集间隔 (ms),默认 2000 */
|
|
179
190
|
collectInterval: number;
|
|
180
191
|
/** 落盘间隔 (条数),默认 60 */
|
|
@@ -243,6 +254,11 @@ interface SessionIndex {
|
|
|
243
254
|
sessions: TestSession[];
|
|
244
255
|
lastUpdated: number;
|
|
245
256
|
}
|
|
257
|
+
/** IPC 拉取会话列表时附带「当前是否在录」:以主进程内存为准,避免索引里僵尸 running */
|
|
258
|
+
interface SessionsListPayload {
|
|
259
|
+
sessions: TestSession[];
|
|
260
|
+
activeSessionId: string | null;
|
|
261
|
+
}
|
|
246
262
|
|
|
247
263
|
/**
|
|
248
264
|
* 报告与对比相关类型
|
|
@@ -277,6 +293,17 @@ interface TrendInfo {
|
|
|
277
293
|
/** 置信度 */
|
|
278
294
|
confidence: 'high' | 'medium' | 'low';
|
|
279
295
|
}
|
|
296
|
+
/** 会话中的阶段标记(写入报告,便于对照各阶段内存) */
|
|
297
|
+
interface SessionEventMark {
|
|
298
|
+
timestamp: number;
|
|
299
|
+
label: string;
|
|
300
|
+
metadata?: Record<string, unknown>;
|
|
301
|
+
/** 该标记随附快照时刻的总工作集 (KB) */
|
|
302
|
+
totalWorkingSetKB: number;
|
|
303
|
+
browserKB: number;
|
|
304
|
+
rendererKB: number;
|
|
305
|
+
gpuKB: number;
|
|
306
|
+
}
|
|
280
307
|
/** 改进建议 */
|
|
281
308
|
interface Suggestion {
|
|
282
309
|
/** 建议 ID */
|
|
@@ -339,6 +366,8 @@ interface SessionReport {
|
|
|
339
366
|
};
|
|
340
367
|
anomalies: AnomalyEvent[];
|
|
341
368
|
suggestions: Suggestion[];
|
|
369
|
+
/** 阶段标记汇总(与快照中的 marks 一致,便于表格展示) */
|
|
370
|
+
eventMarks?: SessionEventMark[];
|
|
342
371
|
dataFile: string;
|
|
343
372
|
}
|
|
344
373
|
/** 指标差异 */
|
|
@@ -438,6 +467,8 @@ declare class ElectronMemoryMonitor extends EventEmitter {
|
|
|
438
467
|
startSession(label: string, description?: string): string;
|
|
439
468
|
/** 结束当前会话 */
|
|
440
469
|
stopSession(): Promise<SessionReport | null>;
|
|
470
|
+
/** 为已落盘元数据的会话生成并写入 report.json(显式结束或被新会话顶替) */
|
|
471
|
+
private persistCompletedSessionReport;
|
|
441
472
|
/** 打开监控面板 */
|
|
442
473
|
openDashboard(): void;
|
|
443
474
|
/** 关闭监控面板 */
|
|
@@ -446,6 +477,10 @@ declare class ElectronMemoryMonitor extends EventEmitter {
|
|
|
446
477
|
getCurrentSnapshot(): MemorySnapshot | null;
|
|
447
478
|
/** 获取历史会话列表 */
|
|
448
479
|
getSessions(): Promise<TestSession[]>;
|
|
480
|
+
/**
|
|
481
|
+
* 供监控面板 IPC:列表来自磁盘索引,是否在录制以内存中的 currentSession 为准(避免索引僵尸 running)
|
|
482
|
+
*/
|
|
483
|
+
getSessionsPayloadForIpc(): SessionsListPayload;
|
|
449
484
|
/** 获取指定会话报告 */
|
|
450
485
|
getSessionReport(sessionId: string): Promise<SessionReport | null>;
|
|
451
486
|
/** 获取指定会话的快照数据(支持时间过滤和降采样) */
|
|
@@ -478,6 +513,7 @@ declare class ElectronMemoryMonitor extends EventEmitter {
|
|
|
478
513
|
getConfig(): MonitorConfig;
|
|
479
514
|
on(event: 'snapshot', handler: (data: MemorySnapshot) => void): this;
|
|
480
515
|
on(event: 'anomaly', handler: (event: AnomalyEvent) => void): this;
|
|
516
|
+
on(event: 'session-start', handler: (session: TestSession) => void): this;
|
|
481
517
|
on(event: 'session-end', handler: (report: SessionReport) => void): this;
|
|
482
518
|
private onSnapshot;
|
|
483
519
|
private mergeConfig;
|
|
@@ -515,4 +551,4 @@ declare const IPC_CHANNELS: {
|
|
|
515
551
|
readonly RENDERER_REQUEST: "emm:renderer:request";
|
|
516
552
|
};
|
|
517
553
|
|
|
518
|
-
export { type AnomalyCategory, type AnomalyEvent, type AnomalyRule, type AnomalySeverity, type CompareReport, ElectronMemoryMonitor, type EventMark, type GCResult, IPC_CHANNELS, type Improvement, type MemorySnapshot, type MetricDiff, type MetricSummary, type MonitorConfig, type ProcessMemoryInfo, type Regression, type RendererV8Detail, type SessionIndex, type SessionReport, type Suggestion, type SystemMemoryInfo, type TestSession, type TrendInfo, type V8HeapDetailStats, type V8HeapSpaceInfo, type V8HeapStats, registerDashboardSchemePrivileged };
|
|
554
|
+
export { type AnomalyCategory, type AnomalyEvent, type AnomalyRule, type AnomalySeverity, type CompareReport, ElectronMemoryMonitor, type EventMark, type GCResult, IPC_CHANNELS, type Improvement, type MemorySnapshot, type MetricDiff, type MetricSummary, type MonitorConfig, type ProcessMemoryInfo, type Regression, type RendererV8Detail, type SessionEventMark, type SessionIndex, type SessionReport, type SessionsListPayload, type Suggestion, type SystemMemoryInfo, type TestSession, type TrendInfo, type V8HeapDetailStats, type V8HeapSpaceInfo, type V8HeapStats, registerDashboardSchemePrivileged };
|
package/dist/index.js
CHANGED
|
@@ -299,13 +299,13 @@ var DataPersister = class {
|
|
|
299
299
|
return null;
|
|
300
300
|
}
|
|
301
301
|
}
|
|
302
|
-
/**
|
|
302
|
+
/** 获取所有会话列表(按 startTime 降序,最新在前;与索引写入顺序无关) */
|
|
303
303
|
getSessions() {
|
|
304
304
|
const indexFile = path.join(this.storageDir, "sessions.json");
|
|
305
305
|
try {
|
|
306
306
|
const content = fs.readFileSync(indexFile, "utf-8");
|
|
307
307
|
const index = JSON.parse(content);
|
|
308
|
-
return index.sessions;
|
|
308
|
+
return this.sortSessionsByStartDesc(index.sessions);
|
|
309
309
|
} catch {
|
|
310
310
|
return [];
|
|
311
311
|
}
|
|
@@ -408,11 +408,19 @@ var DataPersister = class {
|
|
|
408
408
|
saveSessionIndex(sessions) {
|
|
409
409
|
const indexFile = path.join(this.storageDir, "sessions.json");
|
|
410
410
|
const index = {
|
|
411
|
-
sessions,
|
|
411
|
+
sessions: this.sortSessionsByStartDesc(sessions),
|
|
412
412
|
lastUpdated: Date.now()
|
|
413
413
|
};
|
|
414
414
|
fs.writeFileSync(indexFile, JSON.stringify(index, null, 2), "utf-8");
|
|
415
415
|
}
|
|
416
|
+
/** 统一排序:startTime 新 → 旧(同毫秒时按 id 稳定排序) */
|
|
417
|
+
sortSessionsByStartDesc(sessions) {
|
|
418
|
+
return [...sessions].sort((a, b) => {
|
|
419
|
+
const t = b.startTime - a.startTime;
|
|
420
|
+
if (t !== 0) return t;
|
|
421
|
+
return String(b.id).localeCompare(String(a.id));
|
|
422
|
+
});
|
|
423
|
+
}
|
|
416
424
|
ensureDirectory(dir) {
|
|
417
425
|
if (!fs.existsSync(dir)) {
|
|
418
426
|
fs.mkdirSync(dir, { recursive: true });
|
|
@@ -475,10 +483,14 @@ var SessionManager = class {
|
|
|
475
483
|
getCurrentSession() {
|
|
476
484
|
return this.currentSession;
|
|
477
485
|
}
|
|
478
|
-
/**
|
|
486
|
+
/** 开始新会话;若顶替了上一条进行中的会话,通过 `replaced` 返回以便主进程补写 report.json */
|
|
479
487
|
startSession(label, description) {
|
|
488
|
+
if (!this.currentSession) {
|
|
489
|
+
this.reconcileStaleRunningInIndex();
|
|
490
|
+
}
|
|
491
|
+
let replaced = null;
|
|
480
492
|
if (this.currentSession && this.currentSession.status === "running") {
|
|
481
|
-
this.endSession();
|
|
493
|
+
replaced = this.endSession();
|
|
482
494
|
}
|
|
483
495
|
const sessionId = v4();
|
|
484
496
|
const { dataFile, metaFile } = this.persister.createSessionFiles(sessionId);
|
|
@@ -494,7 +506,7 @@ var SessionManager = class {
|
|
|
494
506
|
};
|
|
495
507
|
this.currentSession = session;
|
|
496
508
|
this.persister.saveSessionMeta(session);
|
|
497
|
-
return session;
|
|
509
|
+
return { session, replaced };
|
|
498
510
|
}
|
|
499
511
|
/** 结束当前会话 */
|
|
500
512
|
endSession() {
|
|
@@ -522,6 +534,24 @@ var SessionManager = class {
|
|
|
522
534
|
getSession(sessionId) {
|
|
523
535
|
return this.persister.readSessionMeta(sessionId);
|
|
524
536
|
}
|
|
537
|
+
/**
|
|
538
|
+
* 当前内存无活动会话时,将索引中仍为 running 的条目标为 aborted(进程异常退出后残留)
|
|
539
|
+
*/
|
|
540
|
+
reconcileStaleRunningInIndex() {
|
|
541
|
+
if (this.currentSession) return;
|
|
542
|
+
const sessions = this.persister.getSessions();
|
|
543
|
+
const now = Date.now();
|
|
544
|
+
for (const s of sessions) {
|
|
545
|
+
if (s.status !== "running") continue;
|
|
546
|
+
const fixed = {
|
|
547
|
+
...s,
|
|
548
|
+
status: "aborted",
|
|
549
|
+
endTime: now,
|
|
550
|
+
duration: now - s.startTime
|
|
551
|
+
};
|
|
552
|
+
this.persister.saveSessionMeta(fixed);
|
|
553
|
+
}
|
|
554
|
+
}
|
|
525
555
|
};
|
|
526
556
|
|
|
527
557
|
// src/core/anomaly.ts
|
|
@@ -692,10 +722,38 @@ var os2 = __toESM(require("os"));
|
|
|
692
722
|
var Analyzer = class {
|
|
693
723
|
/** 生成会话报告 */
|
|
694
724
|
generateReport(sessionId, label, description, startTime, endTime, snapshots, anomalies, dataFile) {
|
|
725
|
+
const environment = this.collectEnvironment();
|
|
726
|
+
const eventMarks = this.collectEventMarks(snapshots);
|
|
695
727
|
if (snapshots.length === 0) {
|
|
696
|
-
|
|
728
|
+
const summary2 = this.emptySummary();
|
|
729
|
+
const suggestions2 = [
|
|
730
|
+
{
|
|
731
|
+
id: "no-snapshots",
|
|
732
|
+
severity: "info",
|
|
733
|
+
category: "optimization",
|
|
734
|
+
title: "\u4F1A\u8BDD\u5185\u6CA1\u6709\u53EF\u7528\u7684\u5185\u5B58\u5FEB\u7167",
|
|
735
|
+
description: "\u7ED3\u675F\u4F1A\u8BDD\u65F6\u78C1\u76D8\u4E0A\u5C1A\u672A\u5199\u5165\u4EFB\u4F55\u91C7\u6837\u70B9\uFF08\u4F8B\u5982\u521A\u542F\u52A8\u5C31\u7ACB\u523B\u7ED3\u675F\uFF0C\u6216\u91C7\u96C6\u95F4\u9694\u5C1A\u672A\u89E6\u53D1\uFF09\u3002\u62A5\u544A\u4E2D\u7684\u7EDF\u8BA1\u4E0E\u8D8B\u52BF\u65E0\u6CD5\u8BA1\u7B97\u3002",
|
|
736
|
+
suggestions: [
|
|
737
|
+
"\u4FDD\u6301\u4F1A\u8BDD\u5F00\u542F\u81F3\u5C11\u4E00\u4E2A\u91C7\u96C6\u5468\u671F\u540E\u518D\u70B9\u300C\u7ED3\u675F\u4F1A\u8BDD\u300D",
|
|
738
|
+
"\u5728\u914D\u7F6E\u4E2D\u9002\u5F53\u7F29\u77ED\u91C7\u96C6\u95F4\u9694\u4EE5\u4FBF\u66F4\u5FEB\u5F97\u5230\u6570\u636E"
|
|
739
|
+
]
|
|
740
|
+
}
|
|
741
|
+
];
|
|
742
|
+
return {
|
|
743
|
+
sessionId,
|
|
744
|
+
label,
|
|
745
|
+
description,
|
|
746
|
+
startTime,
|
|
747
|
+
endTime,
|
|
748
|
+
duration: endTime - startTime,
|
|
749
|
+
environment,
|
|
750
|
+
summary: summary2,
|
|
751
|
+
anomalies,
|
|
752
|
+
suggestions: suggestions2,
|
|
753
|
+
eventMarks,
|
|
754
|
+
dataFile
|
|
755
|
+
};
|
|
697
756
|
}
|
|
698
|
-
const environment = this.collectEnvironment();
|
|
699
757
|
const summary = this.computeSummary(snapshots);
|
|
700
758
|
const suggestions = this.generateSuggestions(snapshots, summary, anomalies);
|
|
701
759
|
return {
|
|
@@ -709,6 +767,7 @@ var Analyzer = class {
|
|
|
709
767
|
summary,
|
|
710
768
|
anomalies,
|
|
711
769
|
suggestions,
|
|
770
|
+
eventMarks,
|
|
712
771
|
dataFile
|
|
713
772
|
};
|
|
714
773
|
}
|
|
@@ -745,6 +804,53 @@ var Analyzer = class {
|
|
|
745
804
|
};
|
|
746
805
|
}
|
|
747
806
|
// ===== 私有方法 =====
|
|
807
|
+
emptySummary() {
|
|
808
|
+
const z = this.computeMetricSummary([]);
|
|
809
|
+
const stable = { slope: 0, r2: 0, direction: "stable", confidence: "low" };
|
|
810
|
+
return {
|
|
811
|
+
totalProcesses: { min: 0, max: 0, avg: 0 },
|
|
812
|
+
totalMemory: z,
|
|
813
|
+
byProcessType: {
|
|
814
|
+
browser: z,
|
|
815
|
+
renderer: [],
|
|
816
|
+
gpu: null,
|
|
817
|
+
utility: null
|
|
818
|
+
},
|
|
819
|
+
mainV8Heap: {
|
|
820
|
+
heapUsed: z,
|
|
821
|
+
heapTotal: z,
|
|
822
|
+
external: z,
|
|
823
|
+
arrayBuffers: z
|
|
824
|
+
},
|
|
825
|
+
trends: {
|
|
826
|
+
totalMemory: stable,
|
|
827
|
+
browserMemory: stable,
|
|
828
|
+
rendererMemory: stable
|
|
829
|
+
}
|
|
830
|
+
};
|
|
831
|
+
}
|
|
832
|
+
/** 从快照展平所有标记,并附上该采样点的分类内存(KB) */
|
|
833
|
+
collectEventMarks(snapshots) {
|
|
834
|
+
const out = [];
|
|
835
|
+
for (const s of snapshots) {
|
|
836
|
+
if (!s.marks?.length) continue;
|
|
837
|
+
const browserKB = s.processes.filter((p) => p.type === "Browser").reduce((sum, p) => sum + p.memory.workingSetSize, 0);
|
|
838
|
+
const rendererKB = s.processes.filter((p) => p.type === "Tab" && !p.isMonitorProcess).reduce((sum, p) => sum + p.memory.workingSetSize, 0);
|
|
839
|
+
const gpuKB = s.processes.filter((p) => p.type === "GPU").reduce((sum, p) => sum + p.memory.workingSetSize, 0);
|
|
840
|
+
for (const m of s.marks) {
|
|
841
|
+
out.push({
|
|
842
|
+
timestamp: m.timestamp,
|
|
843
|
+
label: m.label,
|
|
844
|
+
metadata: m.metadata,
|
|
845
|
+
totalWorkingSetKB: s.totalWorkingSetSize,
|
|
846
|
+
browserKB,
|
|
847
|
+
rendererKB,
|
|
848
|
+
gpuKB
|
|
849
|
+
});
|
|
850
|
+
}
|
|
851
|
+
}
|
|
852
|
+
return out;
|
|
853
|
+
}
|
|
748
854
|
collectEnvironment() {
|
|
749
855
|
const cpus2 = os2.cpus();
|
|
750
856
|
return {
|
|
@@ -1434,10 +1540,28 @@ var IPCMainHandler = class {
|
|
|
1434
1540
|
return this.monitor.startSession(args.label, args.description);
|
|
1435
1541
|
});
|
|
1436
1542
|
import_electron4.ipcMain.handle(IPC_CHANNELS.SESSION_STOP, async () => {
|
|
1437
|
-
|
|
1543
|
+
try {
|
|
1544
|
+
const report = await this.monitor.stopSession();
|
|
1545
|
+
if (!report) {
|
|
1546
|
+
return { ok: false, reason: "no_active_session" };
|
|
1547
|
+
}
|
|
1548
|
+
return {
|
|
1549
|
+
ok: true,
|
|
1550
|
+
sessionId: report.sessionId,
|
|
1551
|
+
label: report.label,
|
|
1552
|
+
durationMs: report.duration
|
|
1553
|
+
};
|
|
1554
|
+
} catch (err) {
|
|
1555
|
+
console.error("[electron-memory-monitor] SESSION_STOP failed:", err);
|
|
1556
|
+
return {
|
|
1557
|
+
ok: false,
|
|
1558
|
+
reason: "error",
|
|
1559
|
+
message: err instanceof Error ? err.message : String(err)
|
|
1560
|
+
};
|
|
1561
|
+
}
|
|
1438
1562
|
});
|
|
1439
1563
|
import_electron4.ipcMain.handle(IPC_CHANNELS.SESSION_LIST, async () => {
|
|
1440
|
-
return this.monitor.
|
|
1564
|
+
return this.monitor.getSessionsPayloadForIpc();
|
|
1441
1565
|
});
|
|
1442
1566
|
import_electron4.ipcMain.handle(IPC_CHANNELS.SESSION_REPORT, async (_event, sessionId) => {
|
|
1443
1567
|
return this.monitor.getSessionReport(sessionId);
|
|
@@ -1461,7 +1585,7 @@ var IPCMainHandler = class {
|
|
|
1461
1585
|
return this.monitor.getConfig();
|
|
1462
1586
|
});
|
|
1463
1587
|
import_electron4.ipcMain.handle(IPC_CHANNELS.GET_SESSIONS, async () => {
|
|
1464
|
-
return this.monitor.
|
|
1588
|
+
return this.monitor.getSessionsPayloadForIpc();
|
|
1465
1589
|
});
|
|
1466
1590
|
import_electron4.ipcMain.handle(IPC_CHANNELS.SESSION_EXPORT, async (_event, sessionId) => {
|
|
1467
1591
|
return this.monitor.exportSession(sessionId);
|
|
@@ -1472,8 +1596,11 @@ var IPCMainHandler = class {
|
|
|
1472
1596
|
import_electron4.ipcMain.handle(IPC_CHANNELS.SESSION_DELETE, async (_event, sessionId) => {
|
|
1473
1597
|
return this.monitor.deleteSession(sessionId);
|
|
1474
1598
|
});
|
|
1475
|
-
import_electron4.ipcMain.on(IPC_CHANNELS.RENDERER_REPORT, (
|
|
1476
|
-
this.monitor.updateRendererDetail(
|
|
1599
|
+
import_electron4.ipcMain.on(IPC_CHANNELS.RENDERER_REPORT, (event, detail) => {
|
|
1600
|
+
this.monitor.updateRendererDetail({
|
|
1601
|
+
...detail,
|
|
1602
|
+
webContentsId: event.sender.id
|
|
1603
|
+
});
|
|
1477
1604
|
});
|
|
1478
1605
|
}
|
|
1479
1606
|
/** 向监控面板推送快照数据 */
|
|
@@ -1503,6 +1630,10 @@ var DEFAULT_CONFIG = {
|
|
|
1503
1630
|
enabled: true,
|
|
1504
1631
|
autoStart: true,
|
|
1505
1632
|
openDashboardOnStart: true,
|
|
1633
|
+
session: {
|
|
1634
|
+
autoStartOnLaunch: true,
|
|
1635
|
+
autoLabelPrefix: "\u81EA\u52A8\u4F1A\u8BDD"
|
|
1636
|
+
},
|
|
1506
1637
|
collectInterval: 2e3,
|
|
1507
1638
|
persistInterval: 60,
|
|
1508
1639
|
enableRendererDetail: false,
|
|
@@ -1528,6 +1659,36 @@ var DEFAULT_CONFIG = {
|
|
|
1528
1659
|
};
|
|
1529
1660
|
|
|
1530
1661
|
// src/core/monitor.ts
|
|
1662
|
+
function mergeMarksFromExcludedSnapshots(full, sampled) {
|
|
1663
|
+
const marked = full.filter((s) => s.marks && s.marks.length > 0);
|
|
1664
|
+
if (marked.length === 0) return sampled;
|
|
1665
|
+
const sampledRefs = new Set(sampled);
|
|
1666
|
+
const result = sampled.map((s) => ({
|
|
1667
|
+
...s,
|
|
1668
|
+
marks: s.marks?.length ? [...s.marks] : void 0
|
|
1669
|
+
}));
|
|
1670
|
+
for (const src of marked) {
|
|
1671
|
+
if (sampledRefs.has(src)) continue;
|
|
1672
|
+
let bestIdx = 0;
|
|
1673
|
+
let bestDiff = Infinity;
|
|
1674
|
+
for (let i = 0; i < result.length; i++) {
|
|
1675
|
+
const d = Math.abs(result[i].timestamp - src.timestamp);
|
|
1676
|
+
if (d < bestDiff) {
|
|
1677
|
+
bestDiff = d;
|
|
1678
|
+
bestIdx = i;
|
|
1679
|
+
}
|
|
1680
|
+
}
|
|
1681
|
+
const target = result[bestIdx];
|
|
1682
|
+
target.marks = [...target.marks || [], ...src.marks];
|
|
1683
|
+
}
|
|
1684
|
+
return result;
|
|
1685
|
+
}
|
|
1686
|
+
function formatAutoSessionLabel(prefix) {
|
|
1687
|
+
const d = /* @__PURE__ */ new Date();
|
|
1688
|
+
const p2 = (n) => String(n).padStart(2, "0");
|
|
1689
|
+
const stamp = `${d.getFullYear()}-${p2(d.getMonth() + 1)}-${p2(d.getDate())} ${p2(d.getHours())}:${p2(d.getMinutes())}:${p2(d.getSeconds())}`;
|
|
1690
|
+
return `${prefix || "\u81EA\u52A8\u4F1A\u8BDD"} ${stamp}`;
|
|
1691
|
+
}
|
|
1531
1692
|
var ElectronMemoryMonitor = class extends import_events3.EventEmitter {
|
|
1532
1693
|
constructor(config) {
|
|
1533
1694
|
super();
|
|
@@ -1571,11 +1732,20 @@ var ElectronMemoryMonitor = class extends import_events3.EventEmitter {
|
|
|
1571
1732
|
});
|
|
1572
1733
|
this.collector.start();
|
|
1573
1734
|
this.anomalyDetector.start();
|
|
1735
|
+
this.persister.cleanOldSessions();
|
|
1736
|
+
this.sessionManager.reconcileStaleRunningInIndex();
|
|
1737
|
+
this.started = true;
|
|
1738
|
+
if (this.config.session.autoStartOnLaunch) {
|
|
1739
|
+
try {
|
|
1740
|
+
const label = formatAutoSessionLabel(this.config.session.autoLabelPrefix);
|
|
1741
|
+
this.startSession(label, this.config.session.autoDescription);
|
|
1742
|
+
} catch (err) {
|
|
1743
|
+
console.error("[@electron-memory/monitor] autoStartOnLaunch failed:", err);
|
|
1744
|
+
}
|
|
1745
|
+
}
|
|
1574
1746
|
if (this.config.openDashboardOnStart) {
|
|
1575
1747
|
this.openDashboard();
|
|
1576
1748
|
}
|
|
1577
|
-
this.persister.cleanOldSessions();
|
|
1578
|
-
this.started = true;
|
|
1579
1749
|
}
|
|
1580
1750
|
/** 停止监控 */
|
|
1581
1751
|
async stop() {
|
|
@@ -1604,9 +1774,16 @@ var ElectronMemoryMonitor = class extends import_events3.EventEmitter {
|
|
|
1604
1774
|
if (!this.started) {
|
|
1605
1775
|
throw new Error("Monitor is not started");
|
|
1606
1776
|
}
|
|
1607
|
-
const
|
|
1777
|
+
const anomaliesForReplaced = this.anomalyDetector.getAnomalies();
|
|
1778
|
+
const { session, replaced } = this.sessionManager.startSession(label, description);
|
|
1779
|
+
if (replaced) {
|
|
1780
|
+
void this.persistCompletedSessionReport(replaced, anomaliesForReplaced).catch((err) => {
|
|
1781
|
+
console.error("[@electron-memory/monitor] \u88AB\u9876\u66FF\u4F1A\u8BDD\u7684\u62A5\u544A\u5199\u5165\u5931\u8D25:", err);
|
|
1782
|
+
});
|
|
1783
|
+
}
|
|
1608
1784
|
this.collector.setSessionId(session.id);
|
|
1609
1785
|
this.anomalyDetector.clearAnomalies();
|
|
1786
|
+
this.emit("session-start", session);
|
|
1610
1787
|
return session.id;
|
|
1611
1788
|
}
|
|
1612
1789
|
/** 结束当前会话 */
|
|
@@ -1617,8 +1794,26 @@ var ElectronMemoryMonitor = class extends import_events3.EventEmitter {
|
|
|
1617
1794
|
const completedSession = this.sessionManager.endSession();
|
|
1618
1795
|
if (!completedSession) return null;
|
|
1619
1796
|
this.collector.setSessionId(null);
|
|
1620
|
-
const snapshots = this.persister.readSessionSnapshots(completedSession.id);
|
|
1621
1797
|
const anomalies = this.anomalyDetector.getAnomalies();
|
|
1798
|
+
const report = await this.persistCompletedSessionReport(completedSession, anomalies);
|
|
1799
|
+
return report;
|
|
1800
|
+
}
|
|
1801
|
+
/** 为已落盘元数据的会话生成并写入 report.json(显式结束或被新会话顶替) */
|
|
1802
|
+
async persistCompletedSessionReport(completedSession, anomalies) {
|
|
1803
|
+
const fs2 = await import("fs/promises");
|
|
1804
|
+
const snapshotsPath = path4.join(
|
|
1805
|
+
this.persister.getStorageDir(),
|
|
1806
|
+
completedSession.id,
|
|
1807
|
+
"snapshots.jsonl"
|
|
1808
|
+
);
|
|
1809
|
+
let snapshots = [];
|
|
1810
|
+
try {
|
|
1811
|
+
const content = await fs2.readFile(snapshotsPath, "utf-8");
|
|
1812
|
+
const lines = content.trim().split("\n").filter(Boolean);
|
|
1813
|
+
snapshots = lines.map((line) => JSON.parse(line));
|
|
1814
|
+
} catch {
|
|
1815
|
+
snapshots = this.persister.readSessionSnapshots(completedSession.id);
|
|
1816
|
+
}
|
|
1622
1817
|
const report = this.analyzer.generateReport(
|
|
1623
1818
|
completedSession.id,
|
|
1624
1819
|
completedSession.label,
|
|
@@ -1630,8 +1825,7 @@ var ElectronMemoryMonitor = class extends import_events3.EventEmitter {
|
|
|
1630
1825
|
completedSession.dataFile
|
|
1631
1826
|
);
|
|
1632
1827
|
const reportPath = path4.join(this.persister.getStorageDir(), completedSession.id, "report.json");
|
|
1633
|
-
|
|
1634
|
-
fs2.writeFileSync(reportPath, JSON.stringify(report, null, 2), "utf-8");
|
|
1828
|
+
await fs2.writeFile(reportPath, JSON.stringify(report, null, 2), "utf-8");
|
|
1635
1829
|
this.emit("session-end", report);
|
|
1636
1830
|
return report;
|
|
1637
1831
|
}
|
|
@@ -1656,6 +1850,18 @@ var ElectronMemoryMonitor = class extends import_events3.EventEmitter {
|
|
|
1656
1850
|
async getSessions() {
|
|
1657
1851
|
return this.sessionManager.getSessions();
|
|
1658
1852
|
}
|
|
1853
|
+
/**
|
|
1854
|
+
* 供监控面板 IPC:列表来自磁盘索引,是否在录制以内存中的 currentSession 为准(避免索引僵尸 running)
|
|
1855
|
+
*/
|
|
1856
|
+
getSessionsPayloadForIpc() {
|
|
1857
|
+
if (!this.started) {
|
|
1858
|
+
return { sessions: [], activeSessionId: null };
|
|
1859
|
+
}
|
|
1860
|
+
return {
|
|
1861
|
+
sessions: this.sessionManager.getSessions(),
|
|
1862
|
+
activeSessionId: this.sessionManager.getCurrentSession()?.id ?? null
|
|
1863
|
+
};
|
|
1864
|
+
}
|
|
1659
1865
|
/** 获取指定会话报告 */
|
|
1660
1866
|
async getSessionReport(sessionId) {
|
|
1661
1867
|
const fs2 = await import("fs");
|
|
@@ -1667,7 +1873,6 @@ var ElectronMemoryMonitor = class extends import_events3.EventEmitter {
|
|
|
1667
1873
|
const session = this.sessionManager.getSession(sessionId);
|
|
1668
1874
|
if (!session || !session.endTime) return null;
|
|
1669
1875
|
const snapshots = this.persister.readSessionSnapshots(sessionId);
|
|
1670
|
-
if (snapshots.length === 0) return null;
|
|
1671
1876
|
return this.analyzer.generateReport(
|
|
1672
1877
|
session.id,
|
|
1673
1878
|
session.label,
|
|
@@ -1689,17 +1894,18 @@ var ElectronMemoryMonitor = class extends import_events3.EventEmitter {
|
|
|
1689
1894
|
if (endTime != null) {
|
|
1690
1895
|
snapshots = snapshots.filter((s) => s.timestamp <= endTime);
|
|
1691
1896
|
}
|
|
1897
|
+
const beforeDownsample = snapshots;
|
|
1692
1898
|
const limit = maxPoints ?? 600;
|
|
1693
|
-
if (
|
|
1694
|
-
const step =
|
|
1899
|
+
if (beforeDownsample.length > limit) {
|
|
1900
|
+
const step = beforeDownsample.length / limit;
|
|
1695
1901
|
const sampled = [];
|
|
1696
1902
|
for (let i = 0; i < limit; i++) {
|
|
1697
|
-
sampled.push(
|
|
1903
|
+
sampled.push(beforeDownsample[Math.round(i * step)]);
|
|
1698
1904
|
}
|
|
1699
|
-
if (sampled[sampled.length - 1] !==
|
|
1700
|
-
sampled[sampled.length - 1] =
|
|
1905
|
+
if (sampled[sampled.length - 1] !== beforeDownsample[beforeDownsample.length - 1]) {
|
|
1906
|
+
sampled[sampled.length - 1] = beforeDownsample[beforeDownsample.length - 1];
|
|
1701
1907
|
}
|
|
1702
|
-
snapshots = sampled;
|
|
1908
|
+
snapshots = mergeMarksFromExcludedSnapshots(beforeDownsample, sampled);
|
|
1703
1909
|
}
|
|
1704
1910
|
return snapshots;
|
|
1705
1911
|
}
|
|
@@ -1850,6 +2056,10 @@ var ElectronMemoryMonitor = class extends import_events3.EventEmitter {
|
|
|
1850
2056
|
return {
|
|
1851
2057
|
...DEFAULT_CONFIG,
|
|
1852
2058
|
...userConfig,
|
|
2059
|
+
session: {
|
|
2060
|
+
...DEFAULT_CONFIG.session,
|
|
2061
|
+
...userConfig.session || {}
|
|
2062
|
+
},
|
|
1853
2063
|
anomaly: {
|
|
1854
2064
|
...DEFAULT_CONFIG.anomaly,
|
|
1855
2065
|
...userConfig.anomaly || {}
|