systemlens 1.0.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.
@@ -0,0 +1,107 @@
1
+ // ─── Memory Analyzer ─────────────────────────────────────────────
2
+ import { THRESHOLDS, SEVERITY } from '../utils/constants.js';
3
+ import { formatBytes } from '../utils/helpers.js';
4
+
5
+ export class MemoryAnalyzer {
6
+ /**
7
+ * Analyze memory data and return insights
8
+ * @param {Object} memData - From MemoryCollector
9
+ * @param {Object|null} previousData - Previous snapshot for comparison
10
+ * @returns {Object} Analysis results
11
+ */
12
+ analyze(memData, previousData = null) {
13
+ const usedPercent = memData.ram.usedPercent;
14
+ const issues = [];
15
+ const insights = [];
16
+
17
+ // ── Severity classification ──
18
+ let severity;
19
+ if (usedPercent >= THRESHOLDS.MEMORY_HIGH) {
20
+ severity = SEVERITY.CRITICAL;
21
+ issues.push({
22
+ type: 'memory_high',
23
+ severity: 'critical',
24
+ message: `Memory usage is critically high at ${usedPercent}% (${memData.ram.formatted.used} of ${memData.ram.formatted.total})`,
25
+ value: usedPercent,
26
+ });
27
+ } else if (usedPercent >= THRESHOLDS.MEMORY_WARNING) {
28
+ severity = SEVERITY.WARNING;
29
+ issues.push({
30
+ type: 'memory_warning',
31
+ severity: 'warning',
32
+ message: `Memory usage is elevated at ${usedPercent}% (${memData.ram.formatted.used} of ${memData.ram.formatted.total})`,
33
+ value: usedPercent,
34
+ });
35
+ } else if (usedPercent >= THRESHOLDS.MEMORY_MODERATE) {
36
+ severity = SEVERITY.INFO;
37
+ insights.push({
38
+ type: 'memory_moderate',
39
+ message: `Memory usage is moderate at ${usedPercent}%`,
40
+ value: usedPercent,
41
+ });
42
+ } else {
43
+ severity = SEVERITY.OK;
44
+ insights.push({
45
+ type: 'memory_ok',
46
+ message: `Memory usage is healthy at ${usedPercent}% — plenty of free RAM available`,
47
+ value: usedPercent,
48
+ });
49
+ }
50
+
51
+ // ── Available memory warning ──
52
+ const availableGB = memData.ram.available / (1024 * 1024 * 1024);
53
+ if (availableGB < 1 && usedPercent > 50) {
54
+ issues.push({
55
+ type: 'low_available_memory',
56
+ severity: 'warning',
57
+ message: `Only ${formatBytes(memData.ram.available)} of RAM is available — system may start swapping soon`,
58
+ value: availableGB,
59
+ });
60
+ }
61
+
62
+ // ── Swap usage ──
63
+ if (memData.swap.usedPercent > 20) {
64
+ issues.push({
65
+ type: 'swap_active',
66
+ severity: memData.swap.usedPercent > 50 ? 'critical' : 'warning',
67
+ message: `Swap is being used (${memData.swap.usedPercent}% — ${memData.swap.formatted.used}). This means RAM has been exhausted and the system is using slower disk storage as memory, causing significant slowdown.`,
68
+ value: memData.swap.usedPercent,
69
+ });
70
+ } else if (memData.swap.usedPercent > 0 && memData.swap.total > 0) {
71
+ insights.push({
72
+ type: 'swap_minor',
73
+ message: `Minimal swap usage detected (${memData.swap.usedPercent}%) — generally not a concern`,
74
+ value: memData.swap.usedPercent,
75
+ });
76
+ }
77
+
78
+ // ── Memory spike ──
79
+ if (previousData) {
80
+ const prevUsed = previousData.ram.usedPercent;
81
+ const delta = usedPercent - prevUsed;
82
+
83
+ if (delta >= THRESHOLDS.SPIKE_THRESHOLD) {
84
+ issues.push({
85
+ type: 'memory_spike',
86
+ severity: 'warning',
87
+ message: `Memory usage spiked by ${delta.toFixed(1)}% (from ${prevUsed}% to ${usedPercent}%) — something just allocated a large chunk of memory`,
88
+ value: delta,
89
+ });
90
+ }
91
+ }
92
+
93
+ return {
94
+ usedPercent,
95
+ severity,
96
+ issues,
97
+ insights,
98
+ details: {
99
+ total: memData.ram.total,
100
+ used: memData.ram.active,
101
+ available: memData.ram.available,
102
+ swapUsed: memData.swap.usedPercent,
103
+ formatted: memData.ram.formatted,
104
+ },
105
+ };
106
+ }
107
+ }
@@ -0,0 +1,136 @@
1
+ // ─── Process Analyzer ────────────────────────────────────────────
2
+ // Identifies responsibility and patterns in process behavior
3
+ import { THRESHOLDS, SEVERITY } from '../utils/constants.js';
4
+
5
+ export class ProcessAnalyzer {
6
+ /**
7
+ * Analyze processes and determine what's causing load
8
+ * @param {Array} classifiedProcesses - Processes with classification
9
+ * @param {Object} cpuAnalysis - Results from CpuAnalyzer
10
+ * @param {Object} memAnalysis - Results from MemoryAnalyzer
11
+ * @returns {Object} Process-level insights
12
+ */
13
+ analyze(classifiedProcesses, cpuAnalysis, memAnalysis) {
14
+ const issues = [];
15
+ const insights = [];
16
+ const responsibilities = [];
17
+
18
+ // ── Identify CPU hogs ──
19
+ const cpuHogs = classifiedProcesses.filter(p => p.cpu >= THRESHOLDS.PROCESS_CPU_HIGH);
20
+ const cpuWarners = classifiedProcesses.filter(
21
+ p => p.cpu >= THRESHOLDS.PROCESS_CPU_WARNING && p.cpu < THRESHOLDS.PROCESS_CPU_HIGH
22
+ );
23
+
24
+ if (cpuHogs.length === 1) {
25
+ const hog = cpuHogs[0];
26
+ responsibilities.push({
27
+ type: 'single_cpu_dominant',
28
+ process: hog,
29
+ message: `"${hog.name}" is the primary CPU consumer at ${hog.cpu}%`,
30
+ severity: 'critical',
31
+ });
32
+ } else if (cpuHogs.length > 1) {
33
+ responsibilities.push({
34
+ type: 'multiple_cpu_hogs',
35
+ processes: cpuHogs,
36
+ message: `${cpuHogs.length} processes are competing for CPU: ${cpuHogs.map(p => `${p.name} (${p.cpu}%)`).join(', ')}`,
37
+ severity: 'critical',
38
+ });
39
+ }
40
+
41
+ // ── Identify memory hogs ──
42
+ const memHogs = classifiedProcesses.filter(p => p.mem >= THRESHOLDS.PROCESS_MEM_HIGH);
43
+
44
+ if (memHogs.length > 0) {
45
+ for (const hog of memHogs) {
46
+ responsibilities.push({
47
+ type: 'memory_hog',
48
+ process: hog,
49
+ message: `"${hog.name}" is consuming ${hog.mem}% of memory (${hog.memRssFormatted})`,
50
+ severity: hog.mem >= 30 ? 'critical' : 'warning',
51
+ });
52
+ }
53
+ }
54
+
55
+ // ── Detect "death by a thousand cuts" (many small processes adding up) ──
56
+ const smallProcs = classifiedProcesses.filter(
57
+ p => p.cpu >= 2 && p.cpu < THRESHOLDS.PROCESS_CPU_WARNING
58
+ );
59
+ const smallProcTotalCpu = smallProcs.reduce((sum, p) => sum + p.cpu, 0);
60
+
61
+ if (smallProcs.length >= 5 && smallProcTotalCpu > 30) {
62
+ issues.push({
63
+ type: 'distributed_load',
64
+ severity: 'warning',
65
+ message: `${smallProcs.length} background processes are collectively using ${smallProcTotalCpu.toFixed(1)}% CPU — no single culprit, but the combined load is significant`,
66
+ processes: smallProcs,
67
+ });
68
+ }
69
+
70
+ // ── Browser analysis ──
71
+ const browserProcs = classifiedProcesses.filter(p => p.classification.isBrowser);
72
+ if (browserProcs.length > 3) {
73
+ const totalBrowserCpu = browserProcs.reduce((sum, p) => sum + p.cpu, 0);
74
+ const totalBrowserMem = browserProcs.reduce((sum, p) => sum + p.mem, 0);
75
+ // Browsers create ~2-3 OS processes per tab (renderer, GPU, extensions, etc.)
76
+ // So process count ≠ tab count — estimate actual tabs
77
+ const estimatedTabs = Math.max(1, Math.round(browserProcs.length / 2));
78
+
79
+ if (totalBrowserCpu > 20 || totalBrowserMem > 20) {
80
+ issues.push({
81
+ type: 'browser_heavy',
82
+ severity: totalBrowserCpu > 40 ? 'critical' : 'warning',
83
+ message: `Browser is using ${totalBrowserCpu.toFixed(1)}% CPU and ${totalBrowserMem.toFixed(1)}% memory across ~${estimatedTabs} tabs (${browserProcs.length} internal processes)`,
84
+ processes: browserProcs,
85
+ estimatedTabs,
86
+ });
87
+ }
88
+ }
89
+
90
+ // ── Dev server detection ──
91
+ const devProcs = classifiedProcesses.filter(p => p.classification.isDev);
92
+ const highCpuDev = devProcs.filter(p => p.cpu >= THRESHOLDS.PROCESS_CPU_WARNING);
93
+
94
+ if (highCpuDev.length > 0) {
95
+ for (const devProc of highCpuDev) {
96
+ const isHotReload = (devProc.command || '').match(/--watch|hmr|hot|dev|vite|next/i);
97
+ const isPossibleLoop = devProc.cpu >= 80;
98
+
99
+ insights.push({
100
+ type: 'dev_process_high_cpu',
101
+ message: isPossibleLoop
102
+ ? `🔄 "${devProc.name}" is using ${devProc.cpu}% CPU — possible infinite loop or expensive computation in your code`
103
+ : isHotReload
104
+ ? `🔄 "${devProc.name}" (${devProc.cpu}% CPU) — likely running with hot reload which can intensify CPU usage during rebuilds`
105
+ : `🛠️ "${devProc.name}" is using ${devProc.cpu}% CPU during development`,
106
+ process: devProc,
107
+ severity: isPossibleLoop ? 'critical' : 'warning',
108
+ });
109
+ }
110
+ }
111
+
112
+ // ── Determine overall responsibility ──
113
+ let primaryCause = 'balanced';
114
+ if (cpuHogs.length === 1 && cpuHogs[0].cpu > 50) {
115
+ primaryCause = 'single_process';
116
+ } else if (cpuHogs.length > 1) {
117
+ primaryCause = 'multiple_processes';
118
+ } else if (smallProcs.length >= 5 && smallProcTotalCpu > 30) {
119
+ primaryCause = 'distributed';
120
+ }
121
+
122
+ return {
123
+ primaryCause,
124
+ responsibilities,
125
+ issues,
126
+ insights,
127
+ summary: {
128
+ cpuHogCount: cpuHogs.length,
129
+ memHogCount: memHogs.length,
130
+ totalProcesses: classifiedProcesses.length,
131
+ browserProcessCount: browserProcs.length,
132
+ devProcessCount: devProcs.length,
133
+ },
134
+ };
135
+ }
136
+ }
@@ -0,0 +1,135 @@
1
+ // ─── Spike Detector ──────────────────────────────────────────────
2
+ // Tracks system metrics over time to detect patterns and sustained issues
3
+ import { THRESHOLDS } from '../utils/constants.js';
4
+
5
+ export class SpikeDetector {
6
+ constructor() {
7
+ this.cpuHistory = [];
8
+ this.memHistory = [];
9
+ this.maxHistory = 60; // ~3 minutes at 3s intervals
10
+ }
11
+
12
+ /**
13
+ * Record a new data point and detect patterns
14
+ * @param {Object} cpuAnalysis - Current CPU analysis results
15
+ * @param {Object} memAnalysis - Current Memory analysis results
16
+ * @returns {Object} Pattern detection results
17
+ */
18
+ record(cpuAnalysis, memAnalysis) {
19
+ const now = Date.now();
20
+
21
+ this.cpuHistory.push({ value: cpuAnalysis.overall, time: now });
22
+ this.memHistory.push({ value: memAnalysis.usedPercent, time: now });
23
+
24
+ // Trim to max history size
25
+ if (this.cpuHistory.length > this.maxHistory) this.cpuHistory.shift();
26
+ if (this.memHistory.length > this.maxHistory) this.memHistory.shift();
27
+
28
+ return {
29
+ cpu: this._analyzeHistory(this.cpuHistory, 'CPU'),
30
+ memory: this._analyzeHistory(this.memHistory, 'Memory'),
31
+ };
32
+ }
33
+
34
+ /**
35
+ * Analyze a metric history for patterns
36
+ */
37
+ _analyzeHistory(history, label) {
38
+ if (history.length < 3) {
39
+ return { trend: 'insufficient_data', patterns: [] };
40
+ }
41
+
42
+ const patterns = [];
43
+ const values = history.map(h => h.value);
44
+ const recent = values.slice(-5);
45
+ const older = values.slice(-15, -5);
46
+
47
+ // ── Trend detection ──
48
+ const recentAvg = recent.reduce((s, v) => s + v, 0) / recent.length;
49
+ const olderAvg = older.length > 0
50
+ ? older.reduce((s, v) => s + v, 0) / older.length
51
+ : recentAvg;
52
+
53
+ let trend = 'stable';
54
+ if (recentAvg > olderAvg + 10) trend = 'rising';
55
+ else if (recentAvg < olderAvg - 10) trend = 'falling';
56
+
57
+ // ── Sustained high detection ──
58
+ const highThreshold = label === 'CPU' ? THRESHOLDS.CPU_HIGH : THRESHOLDS.MEMORY_HIGH;
59
+ const sustainedHigh = this._detectSustained(history, highThreshold);
60
+
61
+ if (sustainedHigh) {
62
+ patterns.push({
63
+ type: 'sustained_high',
64
+ message: `${label} has been above ${highThreshold}% for ${sustainedHigh.durationLabel}`,
65
+ duration: sustainedHigh.duration,
66
+ severity: sustainedHigh.duration > 120000 ? 'critical' : 'warning',
67
+ });
68
+ }
69
+
70
+ // ── Oscillation detection (usage bouncing up and down) ──
71
+ if (values.length >= 6) {
72
+ const diffs = [];
73
+ for (let i = 1; i < values.length; i++) {
74
+ diffs.push(values[i] - values[i - 1]);
75
+ }
76
+ const directionChanges = diffs.filter((d, i) =>
77
+ i > 0 && Math.sign(d) !== Math.sign(diffs[i - 1]) && Math.abs(d) > 5
78
+ ).length;
79
+
80
+ if (directionChanges >= 4) {
81
+ patterns.push({
82
+ type: 'oscillating',
83
+ message: `${label} usage is oscillating — frequently jumping up and down, possibly due to periodic tasks or unstable workloads`,
84
+ severity: 'info',
85
+ });
86
+ }
87
+ }
88
+
89
+ return {
90
+ trend,
91
+ recentAvg: parseFloat(recentAvg.toFixed(1)),
92
+ patterns,
93
+ };
94
+ }
95
+
96
+ /**
97
+ * Detect sustained high usage
98
+ */
99
+ _detectSustained(history, threshold) {
100
+ if (history.length < 2) return null;
101
+
102
+ let startIdx = -1;
103
+ for (let i = history.length - 1; i >= 0; i--) {
104
+ if (history[i].value < threshold) {
105
+ startIdx = i + 1;
106
+ break;
107
+ }
108
+ }
109
+
110
+ if (startIdx === -1) startIdx = 0;
111
+ if (startIdx >= history.length) return null;
112
+
113
+ const duration = Date.now() - history[startIdx].time;
114
+ if (duration < 6000) return null; // Need at least 6 seconds
115
+
116
+ const seconds = Math.round(duration / 1000);
117
+ let durationLabel;
118
+ if (seconds < 60) durationLabel = `${seconds} seconds`;
119
+ else if (seconds < 3600) durationLabel = `${Math.round(seconds / 60)} minutes`;
120
+ else durationLabel = `${(seconds / 3600).toFixed(1)} hours`;
121
+
122
+ return { duration, durationLabel };
123
+ }
124
+
125
+ /**
126
+ * Get current pattern summary
127
+ */
128
+ getSummary() {
129
+ return {
130
+ dataPoints: this.cpuHistory.length,
131
+ cpuHistory: this.cpuHistory.map(h => h.value),
132
+ memHistory: this.memHistory.map(h => h.value),
133
+ };
134
+ }
135
+ }
@@ -0,0 +1,127 @@
1
+ // ─── Process Classifier ─────────────────────────────────────────
2
+ // Classifies processes into meaningful categories for human understanding
3
+ import { PROCESS_CATEGORIES } from '../utils/constants.js';
4
+
5
+ export class ProcessClassifier {
6
+ constructor() {
7
+ // Pre-compile patterns for fast matching
8
+ this.compiledPatterns = {};
9
+ for (const [key, category] of Object.entries(PROCESS_CATEGORIES)) {
10
+ if (key === 'UNKNOWN') continue;
11
+ this.compiledPatterns[key] = {
12
+ ...category,
13
+ regex: new RegExp(
14
+ category.patterns.map(p => `(^|[/\\\\])${p}(\\d*|[-_.]|$)`).join('|'),
15
+ 'i'
16
+ ),
17
+ };
18
+ }
19
+ }
20
+
21
+ /**
22
+ * Classify a single process
23
+ * @param {Object} process - Process data { name, command, path }
24
+ * @returns {Object} Category info
25
+ */
26
+ classify(process) {
27
+ const searchStr = `${process.name} ${process.command || ''} ${process.path || ''}`.toLowerCase();
28
+
29
+ for (const [key, compiled] of Object.entries(this.compiledPatterns)) {
30
+ if (compiled.regex.test(searchStr)) {
31
+ return {
32
+ category: key,
33
+ label: compiled.label,
34
+ color: compiled.color,
35
+ isSystem: key === 'SYSTEM' || key === 'BACKGROUND',
36
+ isDev: key === 'DEV_TOOLS' || key === 'EDITORS',
37
+ isBrowser: key === 'BROWSER',
38
+ };
39
+ }
40
+ }
41
+
42
+ return {
43
+ category: 'UNKNOWN',
44
+ label: PROCESS_CATEGORIES.UNKNOWN.label,
45
+ color: PROCESS_CATEGORIES.UNKNOWN.color,
46
+ isSystem: false,
47
+ isDev: false,
48
+ isBrowser: false,
49
+ };
50
+ }
51
+
52
+ /**
53
+ * Classify all processes and return enriched list
54
+ * @param {Array} processes - Array of process objects
55
+ * @returns {Array} Processes with classification added
56
+ */
57
+ classifyAll(processes) {
58
+ return processes.map(proc => ({
59
+ ...proc,
60
+ classification: this.classify(proc),
61
+ }));
62
+ }
63
+
64
+ /**
65
+ * Group processes by category
66
+ * @param {Array} classifiedProcesses - Array of classified process objects
67
+ * @returns {Object} Processes grouped by category key
68
+ */
69
+ groupByCategory(classifiedProcesses) {
70
+ const groups = {};
71
+
72
+ for (const proc of classifiedProcesses) {
73
+ const cat = proc.classification.category;
74
+ if (!groups[cat]) {
75
+ groups[cat] = {
76
+ ...PROCESS_CATEGORIES[cat],
77
+ processes: [],
78
+ totalCpu: 0,
79
+ totalMem: 0,
80
+ };
81
+ }
82
+ groups[cat].processes.push(proc);
83
+ groups[cat].totalCpu += proc.cpu || 0;
84
+ groups[cat].totalMem += proc.mem || 0;
85
+ }
86
+
87
+ // Round totals
88
+ for (const group of Object.values(groups)) {
89
+ group.totalCpu = parseFloat(group.totalCpu.toFixed(1));
90
+ group.totalMem = parseFloat(group.totalMem.toFixed(1));
91
+ }
92
+
93
+ return groups;
94
+ }
95
+
96
+ /**
97
+ * Detect if there are development-related processes running
98
+ * @param {Array} classifiedProcesses
99
+ * @returns {Object} Dev environment summary
100
+ */
101
+ detectDevEnvironment(classifiedProcesses) {
102
+ const devProcs = classifiedProcesses.filter(p => p.classification.isDev);
103
+ const editorProcs = classifiedProcesses.filter(p => p.classification.category === 'EDITORS');
104
+
105
+ const hasNodeServer = devProcs.some(p =>
106
+ /node\b/.test(p.name) && (p.command || '').match(/server|dev|start|next|vite|webpack/)
107
+ );
108
+
109
+ const hasHotReload = devProcs.some(p =>
110
+ (p.command || '').match(/--watch|hmr|hot|dev/)
111
+ );
112
+
113
+ const hasTests = devProcs.some(p =>
114
+ (p.command || '').match(/jest|vitest|mocha|test|spec/)
115
+ );
116
+
117
+ return {
118
+ isDevActive: devProcs.length > 0,
119
+ devProcessCount: devProcs.length,
120
+ editorActive: editorProcs.length > 0,
121
+ hasNodeServer,
122
+ hasHotReload,
123
+ hasTests,
124
+ devProcesses: devProcs,
125
+ };
126
+ }
127
+ }
@@ -0,0 +1,48 @@
1
+ // ─── CPU Data Collector ──────────────────────────────────────────
2
+ import si from 'systeminformation';
3
+
4
+ export class CpuCollector {
5
+ /**
6
+ * Collect comprehensive CPU data
7
+ * @returns {Object} CPU info + current load + temperature
8
+ */
9
+ async collect() {
10
+ const [cpuInfo, load, speed, temp] = await Promise.all([
11
+ si.cpu(),
12
+ si.currentLoad(),
13
+ si.cpuCurrentSpeed(),
14
+ si.cpuTemperature().catch(() => null),
15
+ ]);
16
+
17
+ return {
18
+ info: {
19
+ manufacturer: cpuInfo.manufacturer,
20
+ brand: cpuInfo.brand,
21
+ cores: cpuInfo.physicalCores,
22
+ threads: cpuInfo.cores,
23
+ speed: cpuInfo.speed,
24
+ },
25
+ load: {
26
+ overall: parseFloat(load.currentLoad?.toFixed(1) || 0),
27
+ user: parseFloat(load.currentLoadUser?.toFixed(1) || 0),
28
+ system: parseFloat(load.currentLoadSystem?.toFixed(1) || 0),
29
+ idle: parseFloat(load.currentLoadIdle?.toFixed(1) || 0),
30
+ perCore: (load.cpus || []).map((c, i) => ({
31
+ core: i,
32
+ load: parseFloat(c.load?.toFixed(1) || 0),
33
+ })),
34
+ },
35
+ speed: {
36
+ current: speed.avg,
37
+ min: speed.min,
38
+ max: speed.max,
39
+ },
40
+ temperature: temp ? {
41
+ main: temp.main,
42
+ max: temp.max,
43
+ cores: temp.cores,
44
+ } : null,
45
+ timestamp: Date.now(),
46
+ };
47
+ }
48
+ }
@@ -0,0 +1,41 @@
1
+ // ─── Disk Data Collector ─────────────────────────────────────────
2
+ import si from 'systeminformation';
3
+ import { formatBytes, percentage } from '../utils/helpers.js';
4
+
5
+ export class DiskCollector {
6
+ /**
7
+ * Collect disk usage and I/O data
8
+ * @returns {Object} Filesystem usage + disk I/O
9
+ */
10
+ async collect() {
11
+ const [fsSize, fsStats] = await Promise.all([
12
+ si.fsSize(),
13
+ si.disksIO().catch(() => null),
14
+ ]);
15
+
16
+ const filesystems = fsSize.map(fs => ({
17
+ mount: fs.mount,
18
+ type: fs.type,
19
+ size: fs.size,
20
+ used: fs.used,
21
+ available: fs.available,
22
+ usedPercent: parseFloat(fs.use?.toFixed(1) || 0),
23
+ formatted: {
24
+ size: formatBytes(fs.size),
25
+ used: formatBytes(fs.used),
26
+ available: formatBytes(fs.available),
27
+ },
28
+ }));
29
+
30
+ return {
31
+ filesystems,
32
+ io: fsStats ? {
33
+ readPerSecond: fsStats.rIO_sec || 0,
34
+ writePerSecond: fsStats.wIO_sec || 0,
35
+ totalReadBytes: fsStats.rIO || 0,
36
+ totalWriteBytes: fsStats.wIO || 0,
37
+ } : null,
38
+ timestamp: Date.now(),
39
+ };
40
+ }
41
+ }
@@ -0,0 +1,41 @@
1
+ // ─── Memory Data Collector ───────────────────────────────────────
2
+ import si from 'systeminformation';
3
+ import { formatBytes, percentage } from '../utils/helpers.js';
4
+
5
+ export class MemoryCollector {
6
+ /**
7
+ * Collect comprehensive memory data
8
+ * @returns {Object} RAM + swap info
9
+ */
10
+ async collect() {
11
+ const mem = await si.mem();
12
+
13
+ return {
14
+ ram: {
15
+ total: mem.total,
16
+ used: mem.used,
17
+ free: mem.free,
18
+ active: mem.active,
19
+ available: mem.available,
20
+ usedPercent: percentage(mem.active, mem.total),
21
+ formatted: {
22
+ total: formatBytes(mem.total),
23
+ used: formatBytes(mem.active),
24
+ free: formatBytes(mem.available),
25
+ },
26
+ },
27
+ swap: {
28
+ total: mem.swaptotal,
29
+ used: mem.swapused,
30
+ free: mem.swapfree,
31
+ usedPercent: mem.swaptotal > 0 ? percentage(mem.swapused, mem.swaptotal) : 0,
32
+ formatted: {
33
+ total: formatBytes(mem.swaptotal),
34
+ used: formatBytes(mem.swapused),
35
+ free: formatBytes(mem.swapfree),
36
+ },
37
+ },
38
+ timestamp: Date.now(),
39
+ };
40
+ }
41
+ }