@electron-memory/monitor 0.2.3 → 0.2.6
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 +50 -1
- package/dist/index.d.ts +50 -1
- package/dist/index.js +283 -30
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +283 -30
- package/dist/index.mjs.map +1 -1
- package/dist/ui/assets/index-DnuSyhKD.js +9 -0
- package/dist/ui/assets/index-DrFTVGFf.css +1 -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
package/dist/index.mjs
CHANGED
|
@@ -261,13 +261,13 @@ var DataPersister = class {
|
|
|
261
261
|
return null;
|
|
262
262
|
}
|
|
263
263
|
}
|
|
264
|
-
/**
|
|
264
|
+
/** 获取所有会话列表(按 startTime 降序,最新在前;与索引写入顺序无关) */
|
|
265
265
|
getSessions() {
|
|
266
266
|
const indexFile = path.join(this.storageDir, "sessions.json");
|
|
267
267
|
try {
|
|
268
268
|
const content = fs.readFileSync(indexFile, "utf-8");
|
|
269
269
|
const index = JSON.parse(content);
|
|
270
|
-
return index.sessions;
|
|
270
|
+
return this.sortSessionsByStartDesc(index.sessions);
|
|
271
271
|
} catch {
|
|
272
272
|
return [];
|
|
273
273
|
}
|
|
@@ -370,11 +370,19 @@ var DataPersister = class {
|
|
|
370
370
|
saveSessionIndex(sessions) {
|
|
371
371
|
const indexFile = path.join(this.storageDir, "sessions.json");
|
|
372
372
|
const index = {
|
|
373
|
-
sessions,
|
|
373
|
+
sessions: this.sortSessionsByStartDesc(sessions),
|
|
374
374
|
lastUpdated: Date.now()
|
|
375
375
|
};
|
|
376
376
|
fs.writeFileSync(indexFile, JSON.stringify(index, null, 2), "utf-8");
|
|
377
377
|
}
|
|
378
|
+
/** 统一排序:startTime 新 → 旧(同毫秒时按 id 稳定排序) */
|
|
379
|
+
sortSessionsByStartDesc(sessions) {
|
|
380
|
+
return [...sessions].sort((a, b) => {
|
|
381
|
+
const t = b.startTime - a.startTime;
|
|
382
|
+
if (t !== 0) return t;
|
|
383
|
+
return String(b.id).localeCompare(String(a.id));
|
|
384
|
+
});
|
|
385
|
+
}
|
|
378
386
|
ensureDirectory(dir) {
|
|
379
387
|
if (!fs.existsSync(dir)) {
|
|
380
388
|
fs.mkdirSync(dir, { recursive: true });
|
|
@@ -437,10 +445,14 @@ var SessionManager = class {
|
|
|
437
445
|
getCurrentSession() {
|
|
438
446
|
return this.currentSession;
|
|
439
447
|
}
|
|
440
|
-
/**
|
|
448
|
+
/** 开始新会话;若顶替了上一条进行中的会话,通过 `replaced` 返回以便主进程补写 report.json */
|
|
441
449
|
startSession(label, description) {
|
|
450
|
+
if (!this.currentSession) {
|
|
451
|
+
this.reconcileStaleRunningInIndex();
|
|
452
|
+
}
|
|
453
|
+
let replaced = null;
|
|
442
454
|
if (this.currentSession && this.currentSession.status === "running") {
|
|
443
|
-
this.endSession();
|
|
455
|
+
replaced = this.endSession();
|
|
444
456
|
}
|
|
445
457
|
const sessionId = v4();
|
|
446
458
|
const { dataFile, metaFile } = this.persister.createSessionFiles(sessionId);
|
|
@@ -456,7 +468,7 @@ var SessionManager = class {
|
|
|
456
468
|
};
|
|
457
469
|
this.currentSession = session;
|
|
458
470
|
this.persister.saveSessionMeta(session);
|
|
459
|
-
return session;
|
|
471
|
+
return { session, replaced };
|
|
460
472
|
}
|
|
461
473
|
/** 结束当前会话 */
|
|
462
474
|
endSession() {
|
|
@@ -484,6 +496,24 @@ var SessionManager = class {
|
|
|
484
496
|
getSession(sessionId) {
|
|
485
497
|
return this.persister.readSessionMeta(sessionId);
|
|
486
498
|
}
|
|
499
|
+
/**
|
|
500
|
+
* 当前内存无活动会话时,将索引中仍为 running 的条目标为 aborted(进程异常退出后残留)
|
|
501
|
+
*/
|
|
502
|
+
reconcileStaleRunningInIndex() {
|
|
503
|
+
if (this.currentSession) return;
|
|
504
|
+
const sessions = this.persister.getSessions();
|
|
505
|
+
const now = Date.now();
|
|
506
|
+
for (const s of sessions) {
|
|
507
|
+
if (s.status !== "running") continue;
|
|
508
|
+
const fixed = {
|
|
509
|
+
...s,
|
|
510
|
+
status: "aborted",
|
|
511
|
+
endTime: now,
|
|
512
|
+
duration: now - s.startTime
|
|
513
|
+
};
|
|
514
|
+
this.persister.saveSessionMeta(fixed);
|
|
515
|
+
}
|
|
516
|
+
}
|
|
487
517
|
};
|
|
488
518
|
|
|
489
519
|
// src/core/anomaly.ts
|
|
@@ -567,7 +597,18 @@ var AnomalyDetector = class extends EventEmitter2 {
|
|
|
567
597
|
title: "\u603B\u5185\u5B58\u6301\u7EED\u589E\u957F",
|
|
568
598
|
description: `\u5185\u5B58\u4EE5 ${slope.toFixed(2)} KB/s \u7684\u901F\u7387\u6301\u7EED\u589E\u957F (R\xB2=${r2.toFixed(3)})`,
|
|
569
599
|
value: slope,
|
|
570
|
-
threshold: 10
|
|
600
|
+
threshold: 10,
|
|
601
|
+
suggestions: [
|
|
602
|
+
"\u5BFC\u51FA\u5806\u5FEB\u7167 (Heap Snapshot)\uFF0C\u4F7F\u7528 Chrome DevTools \u7684 Memory \u9762\u677F\u52A0\u8F7D\u5206\u6790",
|
|
603
|
+
'\u5BF9\u6BD4\u4E24\u4E2A\u65F6\u95F4\u70B9\u7684\u5806\u5FEB\u7167\uFF0C\u67E5\u627E "Allocated between snapshots" \u4E2D\u65B0\u589E\u7684\u5927\u5BF9\u8C61',
|
|
604
|
+
"\u68C0\u67E5\u4E3B\u8FDB\u7A0B\u4E2D\u662F\u5426\u6709\u672A\u6E05\u7406\u7684 setInterval / setTimeout \u56DE\u8C03",
|
|
605
|
+
"\u68C0\u67E5 ipcMain.on \u662F\u5426\u5B58\u5728\u91CD\u590D\u6CE8\u518C\uFF08\u6BCF\u6B21\u7A97\u53E3\u521B\u5EFA\u90FD\u6CE8\u518C\u4F46\u4E0D\u79FB\u9664\uFF09",
|
|
606
|
+
"\u68C0\u67E5\u662F\u5426\u6709\u6301\u7EED\u589E\u957F\u7684 Map / Set / Array \u7F13\u5B58\u672A\u8BBE\u7F6E\u4E0A\u9650\u6216\u8FC7\u671F\u7B56\u7565"
|
|
607
|
+
],
|
|
608
|
+
actions: [
|
|
609
|
+
{ id: "take-heap-snapshot", label: "\u{1F4F8} \u5BFC\u51FA\u5806\u5FEB\u7167", type: "heap-snapshot" },
|
|
610
|
+
{ id: "trigger-gc", label: "\u{1F5D1}\uFE0F \u89E6\u53D1 GC", type: "trigger-gc" }
|
|
611
|
+
]
|
|
571
612
|
};
|
|
572
613
|
}
|
|
573
614
|
return null;
|
|
@@ -592,7 +633,18 @@ var AnomalyDetector = class extends EventEmitter2 {
|
|
|
592
633
|
title: "\u5185\u5B58\u7A81\u589E",
|
|
593
634
|
description: `\u603B\u5185\u5B58\u4ECE ${Math.round(avg)} KB \u7A81\u589E\u5230 ${current} KB (+${((current - avg) / avg * 100).toFixed(1)}%)`,
|
|
594
635
|
value: current,
|
|
595
|
-
threshold: avg * 1.5
|
|
636
|
+
threshold: avg * 1.5,
|
|
637
|
+
suggestions: [
|
|
638
|
+
"\u7ACB\u5373\u5BFC\u51FA\u5806\u5FEB\u7167\uFF0C\u4E0E\u7A81\u589E\u524D\u7684\u5FEB\u7167\u5BF9\u6BD4\uFF0C\u5B9A\u4F4D\u65B0\u589E\u7684\u5927\u5BF9\u8C61",
|
|
639
|
+
"\u68C0\u67E5\u662F\u5426\u6709\u5927\u91CF\u65B0\u7A97\u53E3/\u6807\u7B7E\u9875\u540C\u65F6\u521B\u5EFA",
|
|
640
|
+
"\u68C0\u67E5\u662F\u5426\u52A0\u8F7D\u4E86\u5927\u6587\u4EF6\u6216\u5927\u91CF\u56FE\u7247\u8D44\u6E90",
|
|
641
|
+
"\u68C0\u67E5 IPC \u901A\u4FE1\u662F\u5426\u4F20\u8F93\u4E86\u8D85\u5927\u6570\u636E\uFF08\u5EFA\u8BAE\u5206\u7247\u6216\u4F7F\u7528 MessagePort\uFF09",
|
|
642
|
+
"\u89E6\u53D1 GC \u540E\u89C2\u5BDF\u5185\u5B58\u662F\u5426\u56DE\u843D\uFF0C\u5982\u4E0D\u56DE\u843D\u5219\u4E3A\u771F\u5B9E\u6CC4\u6F0F"
|
|
643
|
+
],
|
|
644
|
+
actions: [
|
|
645
|
+
{ id: "take-heap-snapshot", label: "\u{1F4F8} \u5BFC\u51FA\u5806\u5FEB\u7167", type: "heap-snapshot" },
|
|
646
|
+
{ id: "trigger-gc", label: "\u{1F5D1}\uFE0F \u89E6\u53D1 GC", type: "trigger-gc" }
|
|
647
|
+
]
|
|
596
648
|
};
|
|
597
649
|
}
|
|
598
650
|
return null;
|
|
@@ -614,7 +666,17 @@ var AnomalyDetector = class extends EventEmitter2 {
|
|
|
614
666
|
title: `\u68C0\u6D4B\u5230 ${detached} \u4E2A\u5206\u79BB\u7684 V8 \u4E0A\u4E0B\u6587`,
|
|
615
667
|
description: "\u5B58\u5728\u672A\u6B63\u786E\u9500\u6BC1\u7684 BrowserWindow \u6216 WebContents\uFF0C\u53EF\u80FD\u5BFC\u81F4\u5185\u5B58\u6CC4\u6F0F",
|
|
616
668
|
value: detached,
|
|
617
|
-
threshold: 0
|
|
669
|
+
threshold: 0,
|
|
670
|
+
suggestions: [
|
|
671
|
+
'\u5728 Chrome DevTools Memory \u9762\u677F\u5BFC\u51FA\u5806\u5FEB\u7167\uFF0C\u641C\u7D22 "Detached" \u67E5\u627E\u6B8B\u7559\u7684 DOM \u6811\u548C JS \u4E0A\u4E0B\u6587',
|
|
672
|
+
"\u68C0\u67E5\u6240\u6709 BrowserWindow \u662F\u5426\u5728\u5173\u95ED\u65F6\u8C03\u7528\u4E86 destroy()\uFF08\u800C\u975E\u4EC5 close()\uFF09",
|
|
673
|
+
'\u68C0\u67E5 BrowserWindow.on("closed", ...) \u56DE\u8C03\u4E2D\u662F\u5426\u5C06\u7A97\u53E3\u5F15\u7528\u7F6E\u4E3A null',
|
|
674
|
+
"\u68C0\u67E5\u662F\u5426\u6709\u95ED\u5305\uFF08\u5982 ipcMain.on \u56DE\u8C03\uFF09\u6301\u6709\u5DF2\u5173\u95ED\u7A97\u53E3\u7684 webContents \u5F15\u7528",
|
|
675
|
+
"\u68C0\u67E5 ipcMain.on / ipcMain.handle \u662F\u5426\u5728\u7A97\u53E3\u5173\u95ED\u540E\u6B63\u786E\u79FB\u9664\u76D1\u542C"
|
|
676
|
+
],
|
|
677
|
+
actions: [
|
|
678
|
+
{ id: "take-heap-snapshot", label: "\u{1F4F8} \u5BFC\u51FA\u5806\u5FEB\u7167", type: "heap-snapshot" }
|
|
679
|
+
]
|
|
618
680
|
};
|
|
619
681
|
}
|
|
620
682
|
return null;
|
|
@@ -638,7 +700,18 @@ var AnomalyDetector = class extends EventEmitter2 {
|
|
|
638
700
|
title: `V8 \u5806\u4F7F\u7528\u7387 ${(usagePercent * 100).toFixed(1)}%`,
|
|
639
701
|
description: `\u4E3B\u8FDB\u7A0B V8 \u5806\u4F7F\u7528 ${Math.round(heapUsed / 1024 / 1024)} MB / ${Math.round(heapTotal / 1024 / 1024)} MB`,
|
|
640
702
|
value: usagePercent * 100,
|
|
641
|
-
threshold: 85
|
|
703
|
+
threshold: 85,
|
|
704
|
+
suggestions: [
|
|
705
|
+
"\u5BFC\u51FA\u5806\u5FEB\u7167 (Heap Snapshot)\uFF0C\u4F7F\u7528 Chrome DevTools \u7684 Memory \u9762\u677F\u5206\u6790\u5BF9\u8C61\u7559\u5B58",
|
|
706
|
+
'\u5BF9\u6BD4\u4E24\u4E2A\u65F6\u95F4\u70B9\u7684\u5806\u5FEB\u7167\uFF0C\u67E5\u627E "Allocated between snapshots" \u4E2D\u7684\u6CC4\u6F0F\u5BF9\u8C61',
|
|
707
|
+
"\u68C0\u67E5 Event Listeners \u662F\u5426\u6B63\u786E\u6E05\u7406\uFF08\u7279\u522B\u662F ipcMain / EventEmitter \u4E0A\u7684\u76D1\u542C\u5668\uFF09",
|
|
708
|
+
"\u68C0\u67E5 Promise \u94FE\u662F\u5426\u6709\u672A\u5904\u7406\u7684 rejection \u5BFC\u81F4\u5F15\u7528\u672A\u91CA\u653E",
|
|
709
|
+
"\u89E6\u53D1 GC \u540E\u89C2\u5BDF\u4F7F\u7528\u7387\u662F\u5426\u4E0B\u964D\uFF0C\u82E5\u4E0D\u964D\u5219\u786E\u8BA4\u4E3A\u6CC4\u6F0F"
|
|
710
|
+
],
|
|
711
|
+
actions: [
|
|
712
|
+
{ id: "take-heap-snapshot", label: "\u{1F4F8} \u5BFC\u51FA\u5806\u5FEB\u7167", type: "heap-snapshot" },
|
|
713
|
+
{ id: "trigger-gc", label: "\u{1F5D1}\uFE0F \u89E6\u53D1 GC", type: "trigger-gc" }
|
|
714
|
+
]
|
|
642
715
|
};
|
|
643
716
|
}
|
|
644
717
|
}
|
|
@@ -654,10 +727,38 @@ import * as os2 from "os";
|
|
|
654
727
|
var Analyzer = class {
|
|
655
728
|
/** 生成会话报告 */
|
|
656
729
|
generateReport(sessionId, label, description, startTime, endTime, snapshots, anomalies, dataFile) {
|
|
730
|
+
const environment = this.collectEnvironment();
|
|
731
|
+
const eventMarks = this.collectEventMarks(snapshots);
|
|
657
732
|
if (snapshots.length === 0) {
|
|
658
|
-
|
|
733
|
+
const summary2 = this.emptySummary();
|
|
734
|
+
const suggestions2 = [
|
|
735
|
+
{
|
|
736
|
+
id: "no-snapshots",
|
|
737
|
+
severity: "info",
|
|
738
|
+
category: "optimization",
|
|
739
|
+
title: "\u4F1A\u8BDD\u5185\u6CA1\u6709\u53EF\u7528\u7684\u5185\u5B58\u5FEB\u7167",
|
|
740
|
+
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",
|
|
741
|
+
suggestions: [
|
|
742
|
+
"\u4FDD\u6301\u4F1A\u8BDD\u5F00\u542F\u81F3\u5C11\u4E00\u4E2A\u91C7\u96C6\u5468\u671F\u540E\u518D\u70B9\u300C\u7ED3\u675F\u4F1A\u8BDD\u300D",
|
|
743
|
+
"\u5728\u914D\u7F6E\u4E2D\u9002\u5F53\u7F29\u77ED\u91C7\u96C6\u95F4\u9694\u4EE5\u4FBF\u66F4\u5FEB\u5F97\u5230\u6570\u636E"
|
|
744
|
+
]
|
|
745
|
+
}
|
|
746
|
+
];
|
|
747
|
+
return {
|
|
748
|
+
sessionId,
|
|
749
|
+
label,
|
|
750
|
+
description,
|
|
751
|
+
startTime,
|
|
752
|
+
endTime,
|
|
753
|
+
duration: endTime - startTime,
|
|
754
|
+
environment,
|
|
755
|
+
summary: summary2,
|
|
756
|
+
anomalies,
|
|
757
|
+
suggestions: suggestions2,
|
|
758
|
+
eventMarks,
|
|
759
|
+
dataFile
|
|
760
|
+
};
|
|
659
761
|
}
|
|
660
|
-
const environment = this.collectEnvironment();
|
|
661
762
|
const summary = this.computeSummary(snapshots);
|
|
662
763
|
const suggestions = this.generateSuggestions(snapshots, summary, anomalies);
|
|
663
764
|
return {
|
|
@@ -671,6 +772,7 @@ var Analyzer = class {
|
|
|
671
772
|
summary,
|
|
672
773
|
anomalies,
|
|
673
774
|
suggestions,
|
|
775
|
+
eventMarks,
|
|
674
776
|
dataFile
|
|
675
777
|
};
|
|
676
778
|
}
|
|
@@ -707,6 +809,53 @@ var Analyzer = class {
|
|
|
707
809
|
};
|
|
708
810
|
}
|
|
709
811
|
// ===== 私有方法 =====
|
|
812
|
+
emptySummary() {
|
|
813
|
+
const z = this.computeMetricSummary([]);
|
|
814
|
+
const stable = { slope: 0, r2: 0, direction: "stable", confidence: "low" };
|
|
815
|
+
return {
|
|
816
|
+
totalProcesses: { min: 0, max: 0, avg: 0 },
|
|
817
|
+
totalMemory: z,
|
|
818
|
+
byProcessType: {
|
|
819
|
+
browser: z,
|
|
820
|
+
renderer: [],
|
|
821
|
+
gpu: null,
|
|
822
|
+
utility: null
|
|
823
|
+
},
|
|
824
|
+
mainV8Heap: {
|
|
825
|
+
heapUsed: z,
|
|
826
|
+
heapTotal: z,
|
|
827
|
+
external: z,
|
|
828
|
+
arrayBuffers: z
|
|
829
|
+
},
|
|
830
|
+
trends: {
|
|
831
|
+
totalMemory: stable,
|
|
832
|
+
browserMemory: stable,
|
|
833
|
+
rendererMemory: stable
|
|
834
|
+
}
|
|
835
|
+
};
|
|
836
|
+
}
|
|
837
|
+
/** 从快照展平所有标记,并附上该采样点的分类内存(KB) */
|
|
838
|
+
collectEventMarks(snapshots) {
|
|
839
|
+
const out = [];
|
|
840
|
+
for (const s of snapshots) {
|
|
841
|
+
if (!s.marks?.length) continue;
|
|
842
|
+
const browserKB = s.processes.filter((p) => p.type === "Browser").reduce((sum, p) => sum + p.memory.workingSetSize, 0);
|
|
843
|
+
const rendererKB = s.processes.filter((p) => p.type === "Tab" && !p.isMonitorProcess).reduce((sum, p) => sum + p.memory.workingSetSize, 0);
|
|
844
|
+
const gpuKB = s.processes.filter((p) => p.type === "GPU").reduce((sum, p) => sum + p.memory.workingSetSize, 0);
|
|
845
|
+
for (const m of s.marks) {
|
|
846
|
+
out.push({
|
|
847
|
+
timestamp: m.timestamp,
|
|
848
|
+
label: m.label,
|
|
849
|
+
metadata: m.metadata,
|
|
850
|
+
totalWorkingSetKB: s.totalWorkingSetSize,
|
|
851
|
+
browserKB,
|
|
852
|
+
rendererKB,
|
|
853
|
+
gpuKB
|
|
854
|
+
});
|
|
855
|
+
}
|
|
856
|
+
}
|
|
857
|
+
return out;
|
|
858
|
+
}
|
|
710
859
|
collectEnvironment() {
|
|
711
860
|
const cpus2 = os2.cpus();
|
|
712
861
|
return {
|
|
@@ -1396,10 +1545,28 @@ var IPCMainHandler = class {
|
|
|
1396
1545
|
return this.monitor.startSession(args.label, args.description);
|
|
1397
1546
|
});
|
|
1398
1547
|
ipcMain.handle(IPC_CHANNELS.SESSION_STOP, async () => {
|
|
1399
|
-
|
|
1548
|
+
try {
|
|
1549
|
+
const report = await this.monitor.stopSession();
|
|
1550
|
+
if (!report) {
|
|
1551
|
+
return { ok: false, reason: "no_active_session" };
|
|
1552
|
+
}
|
|
1553
|
+
return {
|
|
1554
|
+
ok: true,
|
|
1555
|
+
sessionId: report.sessionId,
|
|
1556
|
+
label: report.label,
|
|
1557
|
+
durationMs: report.duration
|
|
1558
|
+
};
|
|
1559
|
+
} catch (err) {
|
|
1560
|
+
console.error("[electron-memory-monitor] SESSION_STOP failed:", err);
|
|
1561
|
+
return {
|
|
1562
|
+
ok: false,
|
|
1563
|
+
reason: "error",
|
|
1564
|
+
message: err instanceof Error ? err.message : String(err)
|
|
1565
|
+
};
|
|
1566
|
+
}
|
|
1400
1567
|
});
|
|
1401
1568
|
ipcMain.handle(IPC_CHANNELS.SESSION_LIST, async () => {
|
|
1402
|
-
return this.monitor.
|
|
1569
|
+
return this.monitor.getSessionsPayloadForIpc();
|
|
1403
1570
|
});
|
|
1404
1571
|
ipcMain.handle(IPC_CHANNELS.SESSION_REPORT, async (_event, sessionId) => {
|
|
1405
1572
|
return this.monitor.getSessionReport(sessionId);
|
|
@@ -1423,7 +1590,7 @@ var IPCMainHandler = class {
|
|
|
1423
1590
|
return this.monitor.getConfig();
|
|
1424
1591
|
});
|
|
1425
1592
|
ipcMain.handle(IPC_CHANNELS.GET_SESSIONS, async () => {
|
|
1426
|
-
return this.monitor.
|
|
1593
|
+
return this.monitor.getSessionsPayloadForIpc();
|
|
1427
1594
|
});
|
|
1428
1595
|
ipcMain.handle(IPC_CHANNELS.SESSION_EXPORT, async (_event, sessionId) => {
|
|
1429
1596
|
return this.monitor.exportSession(sessionId);
|
|
@@ -1434,8 +1601,11 @@ var IPCMainHandler = class {
|
|
|
1434
1601
|
ipcMain.handle(IPC_CHANNELS.SESSION_DELETE, async (_event, sessionId) => {
|
|
1435
1602
|
return this.monitor.deleteSession(sessionId);
|
|
1436
1603
|
});
|
|
1437
|
-
ipcMain.on(IPC_CHANNELS.RENDERER_REPORT, (
|
|
1438
|
-
this.monitor.updateRendererDetail(
|
|
1604
|
+
ipcMain.on(IPC_CHANNELS.RENDERER_REPORT, (event, detail) => {
|
|
1605
|
+
this.monitor.updateRendererDetail({
|
|
1606
|
+
...detail,
|
|
1607
|
+
webContentsId: event.sender.id
|
|
1608
|
+
});
|
|
1439
1609
|
});
|
|
1440
1610
|
}
|
|
1441
1611
|
/** 向监控面板推送快照数据 */
|
|
@@ -1465,6 +1635,10 @@ var DEFAULT_CONFIG = {
|
|
|
1465
1635
|
enabled: true,
|
|
1466
1636
|
autoStart: true,
|
|
1467
1637
|
openDashboardOnStart: true,
|
|
1638
|
+
session: {
|
|
1639
|
+
autoStartOnLaunch: true,
|
|
1640
|
+
autoLabelPrefix: "\u81EA\u52A8\u4F1A\u8BDD"
|
|
1641
|
+
},
|
|
1468
1642
|
collectInterval: 2e3,
|
|
1469
1643
|
persistInterval: 60,
|
|
1470
1644
|
enableRendererDetail: false,
|
|
@@ -1490,6 +1664,36 @@ var DEFAULT_CONFIG = {
|
|
|
1490
1664
|
};
|
|
1491
1665
|
|
|
1492
1666
|
// src/core/monitor.ts
|
|
1667
|
+
function mergeMarksFromExcludedSnapshots(full, sampled) {
|
|
1668
|
+
const marked = full.filter((s) => s.marks && s.marks.length > 0);
|
|
1669
|
+
if (marked.length === 0) return sampled;
|
|
1670
|
+
const sampledRefs = new Set(sampled);
|
|
1671
|
+
const result = sampled.map((s) => ({
|
|
1672
|
+
...s,
|
|
1673
|
+
marks: s.marks?.length ? [...s.marks] : void 0
|
|
1674
|
+
}));
|
|
1675
|
+
for (const src of marked) {
|
|
1676
|
+
if (sampledRefs.has(src)) continue;
|
|
1677
|
+
let bestIdx = 0;
|
|
1678
|
+
let bestDiff = Infinity;
|
|
1679
|
+
for (let i = 0; i < result.length; i++) {
|
|
1680
|
+
const d = Math.abs(result[i].timestamp - src.timestamp);
|
|
1681
|
+
if (d < bestDiff) {
|
|
1682
|
+
bestDiff = d;
|
|
1683
|
+
bestIdx = i;
|
|
1684
|
+
}
|
|
1685
|
+
}
|
|
1686
|
+
const target = result[bestIdx];
|
|
1687
|
+
target.marks = [...target.marks || [], ...src.marks];
|
|
1688
|
+
}
|
|
1689
|
+
return result;
|
|
1690
|
+
}
|
|
1691
|
+
function formatAutoSessionLabel(prefix) {
|
|
1692
|
+
const d = /* @__PURE__ */ new Date();
|
|
1693
|
+
const p2 = (n) => String(n).padStart(2, "0");
|
|
1694
|
+
const stamp = `${d.getFullYear()}-${p2(d.getMonth() + 1)}-${p2(d.getDate())} ${p2(d.getHours())}:${p2(d.getMinutes())}:${p2(d.getSeconds())}`;
|
|
1695
|
+
return `${prefix || "\u81EA\u52A8\u4F1A\u8BDD"} ${stamp}`;
|
|
1696
|
+
}
|
|
1493
1697
|
var ElectronMemoryMonitor = class extends EventEmitter3 {
|
|
1494
1698
|
constructor(config) {
|
|
1495
1699
|
super();
|
|
@@ -1533,11 +1737,20 @@ var ElectronMemoryMonitor = class extends EventEmitter3 {
|
|
|
1533
1737
|
});
|
|
1534
1738
|
this.collector.start();
|
|
1535
1739
|
this.anomalyDetector.start();
|
|
1740
|
+
this.persister.cleanOldSessions();
|
|
1741
|
+
this.sessionManager.reconcileStaleRunningInIndex();
|
|
1742
|
+
this.started = true;
|
|
1743
|
+
if (this.config.session.autoStartOnLaunch) {
|
|
1744
|
+
try {
|
|
1745
|
+
const label = formatAutoSessionLabel(this.config.session.autoLabelPrefix);
|
|
1746
|
+
this.startSession(label, this.config.session.autoDescription);
|
|
1747
|
+
} catch (err) {
|
|
1748
|
+
console.error("[@electron-memory/monitor] autoStartOnLaunch failed:", err);
|
|
1749
|
+
}
|
|
1750
|
+
}
|
|
1536
1751
|
if (this.config.openDashboardOnStart) {
|
|
1537
1752
|
this.openDashboard();
|
|
1538
1753
|
}
|
|
1539
|
-
this.persister.cleanOldSessions();
|
|
1540
|
-
this.started = true;
|
|
1541
1754
|
}
|
|
1542
1755
|
/** 停止监控 */
|
|
1543
1756
|
async stop() {
|
|
@@ -1566,9 +1779,16 @@ var ElectronMemoryMonitor = class extends EventEmitter3 {
|
|
|
1566
1779
|
if (!this.started) {
|
|
1567
1780
|
throw new Error("Monitor is not started");
|
|
1568
1781
|
}
|
|
1569
|
-
const
|
|
1782
|
+
const anomaliesForReplaced = this.anomalyDetector.getAnomalies();
|
|
1783
|
+
const { session, replaced } = this.sessionManager.startSession(label, description);
|
|
1784
|
+
if (replaced) {
|
|
1785
|
+
void this.persistCompletedSessionReport(replaced, anomaliesForReplaced).catch((err) => {
|
|
1786
|
+
console.error("[@electron-memory/monitor] \u88AB\u9876\u66FF\u4F1A\u8BDD\u7684\u62A5\u544A\u5199\u5165\u5931\u8D25:", err);
|
|
1787
|
+
});
|
|
1788
|
+
}
|
|
1570
1789
|
this.collector.setSessionId(session.id);
|
|
1571
1790
|
this.anomalyDetector.clearAnomalies();
|
|
1791
|
+
this.emit("session-start", session);
|
|
1572
1792
|
return session.id;
|
|
1573
1793
|
}
|
|
1574
1794
|
/** 结束当前会话 */
|
|
@@ -1579,8 +1799,26 @@ var ElectronMemoryMonitor = class extends EventEmitter3 {
|
|
|
1579
1799
|
const completedSession = this.sessionManager.endSession();
|
|
1580
1800
|
if (!completedSession) return null;
|
|
1581
1801
|
this.collector.setSessionId(null);
|
|
1582
|
-
const snapshots = this.persister.readSessionSnapshots(completedSession.id);
|
|
1583
1802
|
const anomalies = this.anomalyDetector.getAnomalies();
|
|
1803
|
+
const report = await this.persistCompletedSessionReport(completedSession, anomalies);
|
|
1804
|
+
return report;
|
|
1805
|
+
}
|
|
1806
|
+
/** 为已落盘元数据的会话生成并写入 report.json(显式结束或被新会话顶替) */
|
|
1807
|
+
async persistCompletedSessionReport(completedSession, anomalies) {
|
|
1808
|
+
const fs2 = await import("fs/promises");
|
|
1809
|
+
const snapshotsPath = path4.join(
|
|
1810
|
+
this.persister.getStorageDir(),
|
|
1811
|
+
completedSession.id,
|
|
1812
|
+
"snapshots.jsonl"
|
|
1813
|
+
);
|
|
1814
|
+
let snapshots = [];
|
|
1815
|
+
try {
|
|
1816
|
+
const content = await fs2.readFile(snapshotsPath, "utf-8");
|
|
1817
|
+
const lines = content.trim().split("\n").filter(Boolean);
|
|
1818
|
+
snapshots = lines.map((line) => JSON.parse(line));
|
|
1819
|
+
} catch {
|
|
1820
|
+
snapshots = this.persister.readSessionSnapshots(completedSession.id);
|
|
1821
|
+
}
|
|
1584
1822
|
const report = this.analyzer.generateReport(
|
|
1585
1823
|
completedSession.id,
|
|
1586
1824
|
completedSession.label,
|
|
@@ -1592,8 +1830,7 @@ var ElectronMemoryMonitor = class extends EventEmitter3 {
|
|
|
1592
1830
|
completedSession.dataFile
|
|
1593
1831
|
);
|
|
1594
1832
|
const reportPath = path4.join(this.persister.getStorageDir(), completedSession.id, "report.json");
|
|
1595
|
-
|
|
1596
|
-
fs2.writeFileSync(reportPath, JSON.stringify(report, null, 2), "utf-8");
|
|
1833
|
+
await fs2.writeFile(reportPath, JSON.stringify(report, null, 2), "utf-8");
|
|
1597
1834
|
this.emit("session-end", report);
|
|
1598
1835
|
return report;
|
|
1599
1836
|
}
|
|
@@ -1618,6 +1855,18 @@ var ElectronMemoryMonitor = class extends EventEmitter3 {
|
|
|
1618
1855
|
async getSessions() {
|
|
1619
1856
|
return this.sessionManager.getSessions();
|
|
1620
1857
|
}
|
|
1858
|
+
/**
|
|
1859
|
+
* 供监控面板 IPC:列表来自磁盘索引,是否在录制以内存中的 currentSession 为准(避免索引僵尸 running)
|
|
1860
|
+
*/
|
|
1861
|
+
getSessionsPayloadForIpc() {
|
|
1862
|
+
if (!this.started) {
|
|
1863
|
+
return { sessions: [], activeSessionId: null };
|
|
1864
|
+
}
|
|
1865
|
+
return {
|
|
1866
|
+
sessions: this.sessionManager.getSessions(),
|
|
1867
|
+
activeSessionId: this.sessionManager.getCurrentSession()?.id ?? null
|
|
1868
|
+
};
|
|
1869
|
+
}
|
|
1621
1870
|
/** 获取指定会话报告 */
|
|
1622
1871
|
async getSessionReport(sessionId) {
|
|
1623
1872
|
const fs2 = await import("fs");
|
|
@@ -1629,7 +1878,6 @@ var ElectronMemoryMonitor = class extends EventEmitter3 {
|
|
|
1629
1878
|
const session = this.sessionManager.getSession(sessionId);
|
|
1630
1879
|
if (!session || !session.endTime) return null;
|
|
1631
1880
|
const snapshots = this.persister.readSessionSnapshots(sessionId);
|
|
1632
|
-
if (snapshots.length === 0) return null;
|
|
1633
1881
|
return this.analyzer.generateReport(
|
|
1634
1882
|
session.id,
|
|
1635
1883
|
session.label,
|
|
@@ -1651,17 +1899,18 @@ var ElectronMemoryMonitor = class extends EventEmitter3 {
|
|
|
1651
1899
|
if (endTime != null) {
|
|
1652
1900
|
snapshots = snapshots.filter((s) => s.timestamp <= endTime);
|
|
1653
1901
|
}
|
|
1902
|
+
const beforeDownsample = snapshots;
|
|
1654
1903
|
const limit = maxPoints ?? 600;
|
|
1655
|
-
if (
|
|
1656
|
-
const step =
|
|
1904
|
+
if (beforeDownsample.length > limit) {
|
|
1905
|
+
const step = beforeDownsample.length / limit;
|
|
1657
1906
|
const sampled = [];
|
|
1658
1907
|
for (let i = 0; i < limit; i++) {
|
|
1659
|
-
sampled.push(
|
|
1908
|
+
sampled.push(beforeDownsample[Math.round(i * step)]);
|
|
1660
1909
|
}
|
|
1661
|
-
if (sampled[sampled.length - 1] !==
|
|
1662
|
-
sampled[sampled.length - 1] =
|
|
1910
|
+
if (sampled[sampled.length - 1] !== beforeDownsample[beforeDownsample.length - 1]) {
|
|
1911
|
+
sampled[sampled.length - 1] = beforeDownsample[beforeDownsample.length - 1];
|
|
1663
1912
|
}
|
|
1664
|
-
snapshots = sampled;
|
|
1913
|
+
snapshots = mergeMarksFromExcludedSnapshots(beforeDownsample, sampled);
|
|
1665
1914
|
}
|
|
1666
1915
|
return snapshots;
|
|
1667
1916
|
}
|
|
@@ -1812,6 +2061,10 @@ var ElectronMemoryMonitor = class extends EventEmitter3 {
|
|
|
1812
2061
|
return {
|
|
1813
2062
|
...DEFAULT_CONFIG,
|
|
1814
2063
|
...userConfig,
|
|
2064
|
+
session: {
|
|
2065
|
+
...DEFAULT_CONFIG.session,
|
|
2066
|
+
...userConfig.session || {}
|
|
2067
|
+
},
|
|
1815
2068
|
anomaly: {
|
|
1816
2069
|
...DEFAULT_CONFIG.anomaly,
|
|
1817
2070
|
...userConfig.anomaly || {}
|