@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
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
|
|
@@ -654,10 +684,38 @@ import * as os2 from "os";
|
|
|
654
684
|
var Analyzer = class {
|
|
655
685
|
/** 生成会话报告 */
|
|
656
686
|
generateReport(sessionId, label, description, startTime, endTime, snapshots, anomalies, dataFile) {
|
|
687
|
+
const environment = this.collectEnvironment();
|
|
688
|
+
const eventMarks = this.collectEventMarks(snapshots);
|
|
657
689
|
if (snapshots.length === 0) {
|
|
658
|
-
|
|
690
|
+
const summary2 = this.emptySummary();
|
|
691
|
+
const suggestions2 = [
|
|
692
|
+
{
|
|
693
|
+
id: "no-snapshots",
|
|
694
|
+
severity: "info",
|
|
695
|
+
category: "optimization",
|
|
696
|
+
title: "\u4F1A\u8BDD\u5185\u6CA1\u6709\u53EF\u7528\u7684\u5185\u5B58\u5FEB\u7167",
|
|
697
|
+
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",
|
|
698
|
+
suggestions: [
|
|
699
|
+
"\u4FDD\u6301\u4F1A\u8BDD\u5F00\u542F\u81F3\u5C11\u4E00\u4E2A\u91C7\u96C6\u5468\u671F\u540E\u518D\u70B9\u300C\u7ED3\u675F\u4F1A\u8BDD\u300D",
|
|
700
|
+
"\u5728\u914D\u7F6E\u4E2D\u9002\u5F53\u7F29\u77ED\u91C7\u96C6\u95F4\u9694\u4EE5\u4FBF\u66F4\u5FEB\u5F97\u5230\u6570\u636E"
|
|
701
|
+
]
|
|
702
|
+
}
|
|
703
|
+
];
|
|
704
|
+
return {
|
|
705
|
+
sessionId,
|
|
706
|
+
label,
|
|
707
|
+
description,
|
|
708
|
+
startTime,
|
|
709
|
+
endTime,
|
|
710
|
+
duration: endTime - startTime,
|
|
711
|
+
environment,
|
|
712
|
+
summary: summary2,
|
|
713
|
+
anomalies,
|
|
714
|
+
suggestions: suggestions2,
|
|
715
|
+
eventMarks,
|
|
716
|
+
dataFile
|
|
717
|
+
};
|
|
659
718
|
}
|
|
660
|
-
const environment = this.collectEnvironment();
|
|
661
719
|
const summary = this.computeSummary(snapshots);
|
|
662
720
|
const suggestions = this.generateSuggestions(snapshots, summary, anomalies);
|
|
663
721
|
return {
|
|
@@ -671,6 +729,7 @@ var Analyzer = class {
|
|
|
671
729
|
summary,
|
|
672
730
|
anomalies,
|
|
673
731
|
suggestions,
|
|
732
|
+
eventMarks,
|
|
674
733
|
dataFile
|
|
675
734
|
};
|
|
676
735
|
}
|
|
@@ -707,6 +766,53 @@ var Analyzer = class {
|
|
|
707
766
|
};
|
|
708
767
|
}
|
|
709
768
|
// ===== 私有方法 =====
|
|
769
|
+
emptySummary() {
|
|
770
|
+
const z = this.computeMetricSummary([]);
|
|
771
|
+
const stable = { slope: 0, r2: 0, direction: "stable", confidence: "low" };
|
|
772
|
+
return {
|
|
773
|
+
totalProcesses: { min: 0, max: 0, avg: 0 },
|
|
774
|
+
totalMemory: z,
|
|
775
|
+
byProcessType: {
|
|
776
|
+
browser: z,
|
|
777
|
+
renderer: [],
|
|
778
|
+
gpu: null,
|
|
779
|
+
utility: null
|
|
780
|
+
},
|
|
781
|
+
mainV8Heap: {
|
|
782
|
+
heapUsed: z,
|
|
783
|
+
heapTotal: z,
|
|
784
|
+
external: z,
|
|
785
|
+
arrayBuffers: z
|
|
786
|
+
},
|
|
787
|
+
trends: {
|
|
788
|
+
totalMemory: stable,
|
|
789
|
+
browserMemory: stable,
|
|
790
|
+
rendererMemory: stable
|
|
791
|
+
}
|
|
792
|
+
};
|
|
793
|
+
}
|
|
794
|
+
/** 从快照展平所有标记,并附上该采样点的分类内存(KB) */
|
|
795
|
+
collectEventMarks(snapshots) {
|
|
796
|
+
const out = [];
|
|
797
|
+
for (const s of snapshots) {
|
|
798
|
+
if (!s.marks?.length) continue;
|
|
799
|
+
const browserKB = s.processes.filter((p) => p.type === "Browser").reduce((sum, p) => sum + p.memory.workingSetSize, 0);
|
|
800
|
+
const rendererKB = s.processes.filter((p) => p.type === "Tab" && !p.isMonitorProcess).reduce((sum, p) => sum + p.memory.workingSetSize, 0);
|
|
801
|
+
const gpuKB = s.processes.filter((p) => p.type === "GPU").reduce((sum, p) => sum + p.memory.workingSetSize, 0);
|
|
802
|
+
for (const m of s.marks) {
|
|
803
|
+
out.push({
|
|
804
|
+
timestamp: m.timestamp,
|
|
805
|
+
label: m.label,
|
|
806
|
+
metadata: m.metadata,
|
|
807
|
+
totalWorkingSetKB: s.totalWorkingSetSize,
|
|
808
|
+
browserKB,
|
|
809
|
+
rendererKB,
|
|
810
|
+
gpuKB
|
|
811
|
+
});
|
|
812
|
+
}
|
|
813
|
+
}
|
|
814
|
+
return out;
|
|
815
|
+
}
|
|
710
816
|
collectEnvironment() {
|
|
711
817
|
const cpus2 = os2.cpus();
|
|
712
818
|
return {
|
|
@@ -1396,10 +1502,28 @@ var IPCMainHandler = class {
|
|
|
1396
1502
|
return this.monitor.startSession(args.label, args.description);
|
|
1397
1503
|
});
|
|
1398
1504
|
ipcMain.handle(IPC_CHANNELS.SESSION_STOP, async () => {
|
|
1399
|
-
|
|
1505
|
+
try {
|
|
1506
|
+
const report = await this.monitor.stopSession();
|
|
1507
|
+
if (!report) {
|
|
1508
|
+
return { ok: false, reason: "no_active_session" };
|
|
1509
|
+
}
|
|
1510
|
+
return {
|
|
1511
|
+
ok: true,
|
|
1512
|
+
sessionId: report.sessionId,
|
|
1513
|
+
label: report.label,
|
|
1514
|
+
durationMs: report.duration
|
|
1515
|
+
};
|
|
1516
|
+
} catch (err) {
|
|
1517
|
+
console.error("[electron-memory-monitor] SESSION_STOP failed:", err);
|
|
1518
|
+
return {
|
|
1519
|
+
ok: false,
|
|
1520
|
+
reason: "error",
|
|
1521
|
+
message: err instanceof Error ? err.message : String(err)
|
|
1522
|
+
};
|
|
1523
|
+
}
|
|
1400
1524
|
});
|
|
1401
1525
|
ipcMain.handle(IPC_CHANNELS.SESSION_LIST, async () => {
|
|
1402
|
-
return this.monitor.
|
|
1526
|
+
return this.monitor.getSessionsPayloadForIpc();
|
|
1403
1527
|
});
|
|
1404
1528
|
ipcMain.handle(IPC_CHANNELS.SESSION_REPORT, async (_event, sessionId) => {
|
|
1405
1529
|
return this.monitor.getSessionReport(sessionId);
|
|
@@ -1423,7 +1547,7 @@ var IPCMainHandler = class {
|
|
|
1423
1547
|
return this.monitor.getConfig();
|
|
1424
1548
|
});
|
|
1425
1549
|
ipcMain.handle(IPC_CHANNELS.GET_SESSIONS, async () => {
|
|
1426
|
-
return this.monitor.
|
|
1550
|
+
return this.monitor.getSessionsPayloadForIpc();
|
|
1427
1551
|
});
|
|
1428
1552
|
ipcMain.handle(IPC_CHANNELS.SESSION_EXPORT, async (_event, sessionId) => {
|
|
1429
1553
|
return this.monitor.exportSession(sessionId);
|
|
@@ -1434,8 +1558,11 @@ var IPCMainHandler = class {
|
|
|
1434
1558
|
ipcMain.handle(IPC_CHANNELS.SESSION_DELETE, async (_event, sessionId) => {
|
|
1435
1559
|
return this.monitor.deleteSession(sessionId);
|
|
1436
1560
|
});
|
|
1437
|
-
ipcMain.on(IPC_CHANNELS.RENDERER_REPORT, (
|
|
1438
|
-
this.monitor.updateRendererDetail(
|
|
1561
|
+
ipcMain.on(IPC_CHANNELS.RENDERER_REPORT, (event, detail) => {
|
|
1562
|
+
this.monitor.updateRendererDetail({
|
|
1563
|
+
...detail,
|
|
1564
|
+
webContentsId: event.sender.id
|
|
1565
|
+
});
|
|
1439
1566
|
});
|
|
1440
1567
|
}
|
|
1441
1568
|
/** 向监控面板推送快照数据 */
|
|
@@ -1465,6 +1592,10 @@ var DEFAULT_CONFIG = {
|
|
|
1465
1592
|
enabled: true,
|
|
1466
1593
|
autoStart: true,
|
|
1467
1594
|
openDashboardOnStart: true,
|
|
1595
|
+
session: {
|
|
1596
|
+
autoStartOnLaunch: true,
|
|
1597
|
+
autoLabelPrefix: "\u81EA\u52A8\u4F1A\u8BDD"
|
|
1598
|
+
},
|
|
1468
1599
|
collectInterval: 2e3,
|
|
1469
1600
|
persistInterval: 60,
|
|
1470
1601
|
enableRendererDetail: false,
|
|
@@ -1490,6 +1621,36 @@ var DEFAULT_CONFIG = {
|
|
|
1490
1621
|
};
|
|
1491
1622
|
|
|
1492
1623
|
// src/core/monitor.ts
|
|
1624
|
+
function mergeMarksFromExcludedSnapshots(full, sampled) {
|
|
1625
|
+
const marked = full.filter((s) => s.marks && s.marks.length > 0);
|
|
1626
|
+
if (marked.length === 0) return sampled;
|
|
1627
|
+
const sampledRefs = new Set(sampled);
|
|
1628
|
+
const result = sampled.map((s) => ({
|
|
1629
|
+
...s,
|
|
1630
|
+
marks: s.marks?.length ? [...s.marks] : void 0
|
|
1631
|
+
}));
|
|
1632
|
+
for (const src of marked) {
|
|
1633
|
+
if (sampledRefs.has(src)) continue;
|
|
1634
|
+
let bestIdx = 0;
|
|
1635
|
+
let bestDiff = Infinity;
|
|
1636
|
+
for (let i = 0; i < result.length; i++) {
|
|
1637
|
+
const d = Math.abs(result[i].timestamp - src.timestamp);
|
|
1638
|
+
if (d < bestDiff) {
|
|
1639
|
+
bestDiff = d;
|
|
1640
|
+
bestIdx = i;
|
|
1641
|
+
}
|
|
1642
|
+
}
|
|
1643
|
+
const target = result[bestIdx];
|
|
1644
|
+
target.marks = [...target.marks || [], ...src.marks];
|
|
1645
|
+
}
|
|
1646
|
+
return result;
|
|
1647
|
+
}
|
|
1648
|
+
function formatAutoSessionLabel(prefix) {
|
|
1649
|
+
const d = /* @__PURE__ */ new Date();
|
|
1650
|
+
const p2 = (n) => String(n).padStart(2, "0");
|
|
1651
|
+
const stamp = `${d.getFullYear()}-${p2(d.getMonth() + 1)}-${p2(d.getDate())} ${p2(d.getHours())}:${p2(d.getMinutes())}:${p2(d.getSeconds())}`;
|
|
1652
|
+
return `${prefix || "\u81EA\u52A8\u4F1A\u8BDD"} ${stamp}`;
|
|
1653
|
+
}
|
|
1493
1654
|
var ElectronMemoryMonitor = class extends EventEmitter3 {
|
|
1494
1655
|
constructor(config) {
|
|
1495
1656
|
super();
|
|
@@ -1533,11 +1694,20 @@ var ElectronMemoryMonitor = class extends EventEmitter3 {
|
|
|
1533
1694
|
});
|
|
1534
1695
|
this.collector.start();
|
|
1535
1696
|
this.anomalyDetector.start();
|
|
1697
|
+
this.persister.cleanOldSessions();
|
|
1698
|
+
this.sessionManager.reconcileStaleRunningInIndex();
|
|
1699
|
+
this.started = true;
|
|
1700
|
+
if (this.config.session.autoStartOnLaunch) {
|
|
1701
|
+
try {
|
|
1702
|
+
const label = formatAutoSessionLabel(this.config.session.autoLabelPrefix);
|
|
1703
|
+
this.startSession(label, this.config.session.autoDescription);
|
|
1704
|
+
} catch (err) {
|
|
1705
|
+
console.error("[@electron-memory/monitor] autoStartOnLaunch failed:", err);
|
|
1706
|
+
}
|
|
1707
|
+
}
|
|
1536
1708
|
if (this.config.openDashboardOnStart) {
|
|
1537
1709
|
this.openDashboard();
|
|
1538
1710
|
}
|
|
1539
|
-
this.persister.cleanOldSessions();
|
|
1540
|
-
this.started = true;
|
|
1541
1711
|
}
|
|
1542
1712
|
/** 停止监控 */
|
|
1543
1713
|
async stop() {
|
|
@@ -1566,9 +1736,16 @@ var ElectronMemoryMonitor = class extends EventEmitter3 {
|
|
|
1566
1736
|
if (!this.started) {
|
|
1567
1737
|
throw new Error("Monitor is not started");
|
|
1568
1738
|
}
|
|
1569
|
-
const
|
|
1739
|
+
const anomaliesForReplaced = this.anomalyDetector.getAnomalies();
|
|
1740
|
+
const { session, replaced } = this.sessionManager.startSession(label, description);
|
|
1741
|
+
if (replaced) {
|
|
1742
|
+
void this.persistCompletedSessionReport(replaced, anomaliesForReplaced).catch((err) => {
|
|
1743
|
+
console.error("[@electron-memory/monitor] \u88AB\u9876\u66FF\u4F1A\u8BDD\u7684\u62A5\u544A\u5199\u5165\u5931\u8D25:", err);
|
|
1744
|
+
});
|
|
1745
|
+
}
|
|
1570
1746
|
this.collector.setSessionId(session.id);
|
|
1571
1747
|
this.anomalyDetector.clearAnomalies();
|
|
1748
|
+
this.emit("session-start", session);
|
|
1572
1749
|
return session.id;
|
|
1573
1750
|
}
|
|
1574
1751
|
/** 结束当前会话 */
|
|
@@ -1579,8 +1756,26 @@ var ElectronMemoryMonitor = class extends EventEmitter3 {
|
|
|
1579
1756
|
const completedSession = this.sessionManager.endSession();
|
|
1580
1757
|
if (!completedSession) return null;
|
|
1581
1758
|
this.collector.setSessionId(null);
|
|
1582
|
-
const snapshots = this.persister.readSessionSnapshots(completedSession.id);
|
|
1583
1759
|
const anomalies = this.anomalyDetector.getAnomalies();
|
|
1760
|
+
const report = await this.persistCompletedSessionReport(completedSession, anomalies);
|
|
1761
|
+
return report;
|
|
1762
|
+
}
|
|
1763
|
+
/** 为已落盘元数据的会话生成并写入 report.json(显式结束或被新会话顶替) */
|
|
1764
|
+
async persistCompletedSessionReport(completedSession, anomalies) {
|
|
1765
|
+
const fs2 = await import("fs/promises");
|
|
1766
|
+
const snapshotsPath = path4.join(
|
|
1767
|
+
this.persister.getStorageDir(),
|
|
1768
|
+
completedSession.id,
|
|
1769
|
+
"snapshots.jsonl"
|
|
1770
|
+
);
|
|
1771
|
+
let snapshots = [];
|
|
1772
|
+
try {
|
|
1773
|
+
const content = await fs2.readFile(snapshotsPath, "utf-8");
|
|
1774
|
+
const lines = content.trim().split("\n").filter(Boolean);
|
|
1775
|
+
snapshots = lines.map((line) => JSON.parse(line));
|
|
1776
|
+
} catch {
|
|
1777
|
+
snapshots = this.persister.readSessionSnapshots(completedSession.id);
|
|
1778
|
+
}
|
|
1584
1779
|
const report = this.analyzer.generateReport(
|
|
1585
1780
|
completedSession.id,
|
|
1586
1781
|
completedSession.label,
|
|
@@ -1592,8 +1787,7 @@ var ElectronMemoryMonitor = class extends EventEmitter3 {
|
|
|
1592
1787
|
completedSession.dataFile
|
|
1593
1788
|
);
|
|
1594
1789
|
const reportPath = path4.join(this.persister.getStorageDir(), completedSession.id, "report.json");
|
|
1595
|
-
|
|
1596
|
-
fs2.writeFileSync(reportPath, JSON.stringify(report, null, 2), "utf-8");
|
|
1790
|
+
await fs2.writeFile(reportPath, JSON.stringify(report, null, 2), "utf-8");
|
|
1597
1791
|
this.emit("session-end", report);
|
|
1598
1792
|
return report;
|
|
1599
1793
|
}
|
|
@@ -1618,6 +1812,18 @@ var ElectronMemoryMonitor = class extends EventEmitter3 {
|
|
|
1618
1812
|
async getSessions() {
|
|
1619
1813
|
return this.sessionManager.getSessions();
|
|
1620
1814
|
}
|
|
1815
|
+
/**
|
|
1816
|
+
* 供监控面板 IPC:列表来自磁盘索引,是否在录制以内存中的 currentSession 为准(避免索引僵尸 running)
|
|
1817
|
+
*/
|
|
1818
|
+
getSessionsPayloadForIpc() {
|
|
1819
|
+
if (!this.started) {
|
|
1820
|
+
return { sessions: [], activeSessionId: null };
|
|
1821
|
+
}
|
|
1822
|
+
return {
|
|
1823
|
+
sessions: this.sessionManager.getSessions(),
|
|
1824
|
+
activeSessionId: this.sessionManager.getCurrentSession()?.id ?? null
|
|
1825
|
+
};
|
|
1826
|
+
}
|
|
1621
1827
|
/** 获取指定会话报告 */
|
|
1622
1828
|
async getSessionReport(sessionId) {
|
|
1623
1829
|
const fs2 = await import("fs");
|
|
@@ -1629,7 +1835,6 @@ var ElectronMemoryMonitor = class extends EventEmitter3 {
|
|
|
1629
1835
|
const session = this.sessionManager.getSession(sessionId);
|
|
1630
1836
|
if (!session || !session.endTime) return null;
|
|
1631
1837
|
const snapshots = this.persister.readSessionSnapshots(sessionId);
|
|
1632
|
-
if (snapshots.length === 0) return null;
|
|
1633
1838
|
return this.analyzer.generateReport(
|
|
1634
1839
|
session.id,
|
|
1635
1840
|
session.label,
|
|
@@ -1651,17 +1856,18 @@ var ElectronMemoryMonitor = class extends EventEmitter3 {
|
|
|
1651
1856
|
if (endTime != null) {
|
|
1652
1857
|
snapshots = snapshots.filter((s) => s.timestamp <= endTime);
|
|
1653
1858
|
}
|
|
1859
|
+
const beforeDownsample = snapshots;
|
|
1654
1860
|
const limit = maxPoints ?? 600;
|
|
1655
|
-
if (
|
|
1656
|
-
const step =
|
|
1861
|
+
if (beforeDownsample.length > limit) {
|
|
1862
|
+
const step = beforeDownsample.length / limit;
|
|
1657
1863
|
const sampled = [];
|
|
1658
1864
|
for (let i = 0; i < limit; i++) {
|
|
1659
|
-
sampled.push(
|
|
1865
|
+
sampled.push(beforeDownsample[Math.round(i * step)]);
|
|
1660
1866
|
}
|
|
1661
|
-
if (sampled[sampled.length - 1] !==
|
|
1662
|
-
sampled[sampled.length - 1] =
|
|
1867
|
+
if (sampled[sampled.length - 1] !== beforeDownsample[beforeDownsample.length - 1]) {
|
|
1868
|
+
sampled[sampled.length - 1] = beforeDownsample[beforeDownsample.length - 1];
|
|
1663
1869
|
}
|
|
1664
|
-
snapshots = sampled;
|
|
1870
|
+
snapshots = mergeMarksFromExcludedSnapshots(beforeDownsample, sampled);
|
|
1665
1871
|
}
|
|
1666
1872
|
return snapshots;
|
|
1667
1873
|
}
|
|
@@ -1812,6 +2018,10 @@ var ElectronMemoryMonitor = class extends EventEmitter3 {
|
|
|
1812
2018
|
return {
|
|
1813
2019
|
...DEFAULT_CONFIG,
|
|
1814
2020
|
...userConfig,
|
|
2021
|
+
session: {
|
|
2022
|
+
...DEFAULT_CONFIG.session,
|
|
2023
|
+
...userConfig.session || {}
|
|
2024
|
+
},
|
|
1815
2025
|
anomaly: {
|
|
1816
2026
|
...DEFAULT_CONFIG.anomaly,
|
|
1817
2027
|
...userConfig.anomaly || {}
|