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.
- package/.env.example +5 -0
- package/LICENSE +21 -0
- package/README.md +160 -0
- package/bin/systemlens.js +127 -0
- package/package.json +73 -0
- package/server.js +106 -0
- package/src/analyzers/cpu.analyzer.js +124 -0
- package/src/analyzers/memory.analyzer.js +107 -0
- package/src/analyzers/process.analyzer.js +136 -0
- package/src/analyzers/spike.detector.js +135 -0
- package/src/classifiers/process.classifier.js +127 -0
- package/src/collectors/cpu.collector.js +48 -0
- package/src/collectors/disk.collector.js +41 -0
- package/src/collectors/memory.collector.js +41 -0
- package/src/collectors/process.collector.js +65 -0
- package/src/engines/ai.engine.js +105 -0
- package/src/engines/explanation.engine.js +348 -0
- package/src/engines/suggestion.engine.js +242 -0
- package/src/history/history.tracker.js +114 -0
- package/src/index.js +130 -0
- package/src/monitor/realtime.monitor.js +145 -0
- package/src/renderer/cli.renderer.js +318 -0
- package/src/utils/constants.js +80 -0
- package/src/utils/helpers.js +110 -0
- package/web/app.js +352 -0
- package/web/index.html +209 -0
- package/web/style.css +886 -0
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
// ─── Suggestion Engine ───────────────────────────────────────────
|
|
2
|
+
// Provides actionable, prioritized suggestions based on detected issues
|
|
3
|
+
|
|
4
|
+
export class SuggestionEngine {
|
|
5
|
+
/**
|
|
6
|
+
* Generate prioritized suggestions based on analysis
|
|
7
|
+
* @param {Object} analysis - Full analysis results
|
|
8
|
+
* @param {Object} explanation - Explanation engine output
|
|
9
|
+
* @returns {Array} Prioritized list of suggestions
|
|
10
|
+
*/
|
|
11
|
+
suggest(analysis, explanation) {
|
|
12
|
+
const suggestions = [];
|
|
13
|
+
const { cpu, memory, process: processAnalysis, devEnvironment, patterns } = analysis;
|
|
14
|
+
|
|
15
|
+
// ── CPU-related suggestions ──
|
|
16
|
+
if (cpu.overall >= 80) {
|
|
17
|
+
this._addCpuSuggestions(suggestions, cpu, processAnalysis);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// ── Memory-related suggestions ──
|
|
21
|
+
if (memory.usedPercent >= 70) {
|
|
22
|
+
this._addMemorySuggestions(suggestions, memory, processAnalysis);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// ── Process-specific suggestions ──
|
|
26
|
+
this._addProcessSuggestions(suggestions, processAnalysis);
|
|
27
|
+
|
|
28
|
+
// ── Browser suggestions ──
|
|
29
|
+
this._addBrowserSuggestions(suggestions, processAnalysis);
|
|
30
|
+
|
|
31
|
+
// ── Dev-specific suggestions ──
|
|
32
|
+
if (devEnvironment?.isDevActive) {
|
|
33
|
+
this._addDevSuggestions(suggestions, devEnvironment, processAnalysis);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// ── Pattern-based suggestions ──
|
|
37
|
+
if (patterns) {
|
|
38
|
+
this._addPatternSuggestions(suggestions, patterns);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// ── Swap suggestions ──
|
|
42
|
+
const swapIssue = memory.issues.find(i => i.type === 'swap_active');
|
|
43
|
+
if (swapIssue) {
|
|
44
|
+
suggestions.push({
|
|
45
|
+
priority: 1,
|
|
46
|
+
emoji: '💾',
|
|
47
|
+
category: 'Memory',
|
|
48
|
+
action: 'Close some applications to free up RAM',
|
|
49
|
+
reason: 'Your system is using swap (disk as memory replacement), which is extremely slow. Freeing up RAM will immediately improve performance.',
|
|
50
|
+
impact: 'high',
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// ── Sort by priority ──
|
|
55
|
+
suggestions.sort((a, b) => a.priority - b.priority);
|
|
56
|
+
|
|
57
|
+
// Remove duplicates and limit
|
|
58
|
+
const seen = new Set();
|
|
59
|
+
const unique = suggestions.filter(s => {
|
|
60
|
+
const key = s.action;
|
|
61
|
+
if (seen.has(key)) return false;
|
|
62
|
+
seen.add(key);
|
|
63
|
+
return true;
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
return unique.slice(0, 8); // Max 8 suggestions to avoid overwhelming
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
_addCpuSuggestions(suggestions, cpu, processAnalysis) {
|
|
70
|
+
// Single process hogging CPU
|
|
71
|
+
const singleHog = processAnalysis.responsibilities.find(r => r.type === 'single_cpu_dominant');
|
|
72
|
+
if (singleHog) {
|
|
73
|
+
const proc = singleHog.process;
|
|
74
|
+
suggestions.push({
|
|
75
|
+
priority: 1,
|
|
76
|
+
emoji: '🎯',
|
|
77
|
+
category: 'CPU',
|
|
78
|
+
action: `Consider closing or restarting "${proc.name}" (PID: ${proc.pid})`,
|
|
79
|
+
reason: `It's consuming ${proc.cpu}% of your CPU, which is the primary cause of slowness.`,
|
|
80
|
+
impact: 'high',
|
|
81
|
+
processTarget: proc,
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Core imbalance
|
|
86
|
+
const coreIssue = cpu.issues.find(i => i.type === 'core_imbalance');
|
|
87
|
+
if (coreIssue) {
|
|
88
|
+
suggestions.push({
|
|
89
|
+
priority: 3,
|
|
90
|
+
emoji: '⚖️',
|
|
91
|
+
category: 'CPU',
|
|
92
|
+
action: 'Check for single-threaded intensive operations',
|
|
93
|
+
reason: 'One CPU core is much busier than others. If you\'re running a script or computation, consider if it can be parallelized.',
|
|
94
|
+
impact: 'medium',
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Temperature
|
|
99
|
+
const tempIssue = cpu.issues.find(i => i.type === 'cpu_temp_high');
|
|
100
|
+
if (tempIssue) {
|
|
101
|
+
suggestions.push({
|
|
102
|
+
priority: 2,
|
|
103
|
+
emoji: '🌡️',
|
|
104
|
+
category: 'Hardware',
|
|
105
|
+
action: 'Improve ventilation — ensure fans are unblocked and working',
|
|
106
|
+
reason: `CPU temperature is at ${tempIssue.value}°C. High heat causes throttling (reduced CPU speed) and can damage hardware over time.`,
|
|
107
|
+
impact: 'high',
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
_addMemorySuggestions(suggestions, memory, processAnalysis) {
|
|
113
|
+
const memHogs = processAnalysis.responsibilities.filter(r => r.type === 'memory_hog');
|
|
114
|
+
|
|
115
|
+
if (memHogs.length > 0) {
|
|
116
|
+
for (const hog of memHogs.slice(0, 3)) {
|
|
117
|
+
suggestions.push({
|
|
118
|
+
priority: 2,
|
|
119
|
+
emoji: '🧠',
|
|
120
|
+
category: 'Memory',
|
|
121
|
+
action: `Review "${hog.process.name}" — it's using ${hog.process.memRssFormatted} of memory`,
|
|
122
|
+
reason: `This is a significant portion of your total RAM. If you're not actively using it, closing it will free up memory.`,
|
|
123
|
+
impact: 'high',
|
|
124
|
+
processTarget: hog.process,
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (memory.usedPercent >= 85) {
|
|
130
|
+
suggestions.push({
|
|
131
|
+
priority: 1,
|
|
132
|
+
emoji: '🚨',
|
|
133
|
+
category: 'Memory',
|
|
134
|
+
action: 'Urgently free up memory — close unused applications',
|
|
135
|
+
reason: 'Memory is critically low. The system will start using swap soon (or already is), causing major slowdowns.',
|
|
136
|
+
impact: 'critical',
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
_addProcessSuggestions(suggestions, processAnalysis) {
|
|
142
|
+
const distributed = processAnalysis.issues.find(i => i.type === 'distributed_load');
|
|
143
|
+
if (distributed) {
|
|
144
|
+
suggestions.push({
|
|
145
|
+
priority: 3,
|
|
146
|
+
emoji: '🔄',
|
|
147
|
+
category: 'System',
|
|
148
|
+
action: 'Review background applications — several are collectively causing load',
|
|
149
|
+
reason: `${distributed.processes.length} processes are each using a small amount of CPU, but together they add up. Check your startup applications and system tray.`,
|
|
150
|
+
impact: 'medium',
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
_addBrowserSuggestions(suggestions, processAnalysis) {
|
|
156
|
+
const browserIssue = processAnalysis.issues.find(i => i.type === 'browser_heavy');
|
|
157
|
+
if (browserIssue) {
|
|
158
|
+
suggestions.push({
|
|
159
|
+
priority: 2,
|
|
160
|
+
emoji: '🌐',
|
|
161
|
+
category: 'Browser',
|
|
162
|
+
action: 'Close unused browser tabs and disable heavy extensions',
|
|
163
|
+
reason: `Your browser is using significant resources. Browsers create multiple background processes per tab (for rendering, extensions, GPU, etc.), so even a few tabs can add up. Close tabs you're not actively using, especially video/streaming tabs.`,
|
|
164
|
+
impact: 'high',
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
suggestions.push({
|
|
168
|
+
priority: 4,
|
|
169
|
+
emoji: '🧩',
|
|
170
|
+
category: 'Browser',
|
|
171
|
+
action: 'Consider using a tab suspender extension',
|
|
172
|
+
reason: 'Extensions like "The Great Suspender" can automatically pause inactive tabs, significantly reducing memory and CPU usage.',
|
|
173
|
+
impact: 'medium',
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
_addDevSuggestions(suggestions, devEnvironment, processAnalysis) {
|
|
179
|
+
const devHighCpu = processAnalysis.insights.filter(i => i.type === 'dev_process_high_cpu');
|
|
180
|
+
|
|
181
|
+
for (const insight of devHighCpu) {
|
|
182
|
+
if (insight.process.cpu >= 80) {
|
|
183
|
+
suggestions.push({
|
|
184
|
+
priority: 1,
|
|
185
|
+
emoji: '🐛',
|
|
186
|
+
category: 'Development',
|
|
187
|
+
action: `Check your code for infinite loops or expensive operations — "${insight.process.name}" is using ${insight.process.cpu}% CPU`,
|
|
188
|
+
reason: 'Very high CPU from a development process often indicates an infinite loop, uncontrolled recursion, or an expensive computation running repeatedly.',
|
|
189
|
+
impact: 'critical',
|
|
190
|
+
});
|
|
191
|
+
} else if (insight.process.cpu >= 30) {
|
|
192
|
+
suggestions.push({
|
|
193
|
+
priority: 3,
|
|
194
|
+
emoji: '🔧',
|
|
195
|
+
category: 'Development',
|
|
196
|
+
action: `Restart your dev server if "${insight.process.name}" CPU usage (${insight.process.cpu}%) seems unusual`,
|
|
197
|
+
reason: 'Dev servers can sometimes enter states of high CPU usage. A restart often resolves the issue.',
|
|
198
|
+
impact: 'medium',
|
|
199
|
+
processTarget: insight.process,
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
if (devEnvironment.hasHotReload) {
|
|
205
|
+
suggestions.push({
|
|
206
|
+
priority: 5,
|
|
207
|
+
emoji: '💡',
|
|
208
|
+
category: 'Development',
|
|
209
|
+
action: 'For faster builds, consider excluding large directories from file watching (e.g. node_modules)',
|
|
210
|
+
reason: 'Hot reload watches files for changes. The more files it tracks, the more CPU it uses. Configuring proper ignore patterns can reduce overhead.',
|
|
211
|
+
impact: 'low',
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
_addPatternSuggestions(suggestions, patterns) {
|
|
217
|
+
// Sustained high CPU
|
|
218
|
+
const sustainedCpu = patterns.cpu?.patterns?.find(p => p.type === 'sustained_high');
|
|
219
|
+
if (sustainedCpu && sustainedCpu.duration > 120000) {
|
|
220
|
+
suggestions.push({
|
|
221
|
+
priority: 2,
|
|
222
|
+
emoji: '⏱️',
|
|
223
|
+
category: 'Performance',
|
|
224
|
+
action: 'CPU has been high for an extended period — identify and address the root cause',
|
|
225
|
+
reason: `${sustainedCpu.message}. This isn't a temporary spike — something is consistently consuming resources.`,
|
|
226
|
+
impact: 'high',
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// Rising memory trend
|
|
231
|
+
if (patterns.memory?.trend === 'rising') {
|
|
232
|
+
suggestions.push({
|
|
233
|
+
priority: 3,
|
|
234
|
+
emoji: '📈',
|
|
235
|
+
category: 'Memory',
|
|
236
|
+
action: 'Memory usage is gradually increasing — watch for memory leaks',
|
|
237
|
+
reason: 'If memory keeps climbing without you opening new apps, an application may have a memory leak. Restarting the application usually fixes it temporarily.',
|
|
238
|
+
impact: 'medium',
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
// ─── History Tracker ─────────────────────────────────────────────
|
|
2
|
+
// Stores system snapshots over time for trend analysis
|
|
3
|
+
import { HISTORY_MAX_ENTRIES } from '../utils/constants.js';
|
|
4
|
+
|
|
5
|
+
export class HistoryTracker {
|
|
6
|
+
constructor() {
|
|
7
|
+
this.snapshots = [];
|
|
8
|
+
this.maxEntries = HISTORY_MAX_ENTRIES;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Record a system snapshot
|
|
13
|
+
* @param {Object} snapshot - { cpu, memory, processes, analysis, explanation }
|
|
14
|
+
*/
|
|
15
|
+
record(snapshot) {
|
|
16
|
+
this.snapshots.push({
|
|
17
|
+
...snapshot,
|
|
18
|
+
timestamp: Date.now(),
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
// Trim old entries
|
|
22
|
+
if (this.snapshots.length > this.maxEntries) {
|
|
23
|
+
this.snapshots = this.snapshots.slice(-this.maxEntries);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Get the most recent snapshot
|
|
29
|
+
* @returns {Object|null}
|
|
30
|
+
*/
|
|
31
|
+
getLatest() {
|
|
32
|
+
return this.snapshots.length > 0
|
|
33
|
+
? this.snapshots[this.snapshots.length - 1]
|
|
34
|
+
: null;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Get the previous snapshot (for comparison)
|
|
39
|
+
* @returns {Object|null}
|
|
40
|
+
*/
|
|
41
|
+
getPrevious() {
|
|
42
|
+
return this.snapshots.length > 1
|
|
43
|
+
? this.snapshots[this.snapshots.length - 2]
|
|
44
|
+
: null;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Get snapshots from the last N seconds
|
|
49
|
+
* @param {number} seconds
|
|
50
|
+
* @returns {Array}
|
|
51
|
+
*/
|
|
52
|
+
getRecentSnapshots(seconds) {
|
|
53
|
+
const cutoff = Date.now() - (seconds * 1000);
|
|
54
|
+
return this.snapshots.filter(s => s.timestamp >= cutoff);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Get CPU history for charting
|
|
59
|
+
* @returns {Array<{time: number, value: number}>}
|
|
60
|
+
*/
|
|
61
|
+
getCpuTimeline() {
|
|
62
|
+
return this.snapshots.map(s => ({
|
|
63
|
+
time: s.timestamp,
|
|
64
|
+
value: s.cpu?.load?.overall || 0,
|
|
65
|
+
}));
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Get memory history for charting
|
|
70
|
+
* @returns {Array<{time: number, value: number}>}
|
|
71
|
+
*/
|
|
72
|
+
getMemoryTimeline() {
|
|
73
|
+
return this.snapshots.map(s => ({
|
|
74
|
+
time: s.timestamp,
|
|
75
|
+
value: s.memory?.ram?.usedPercent || 0,
|
|
76
|
+
}));
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Get a compact summary of all history
|
|
81
|
+
*/
|
|
82
|
+
getSummary() {
|
|
83
|
+
if (this.snapshots.length === 0) return null;
|
|
84
|
+
|
|
85
|
+
const cpuValues = this.snapshots.map(s => s.cpu?.load?.overall || 0);
|
|
86
|
+
const memValues = this.snapshots.map(s => s.memory?.ram?.usedPercent || 0);
|
|
87
|
+
|
|
88
|
+
return {
|
|
89
|
+
totalSnapshots: this.snapshots.length,
|
|
90
|
+
timespan: this.snapshots.length > 1
|
|
91
|
+
? this.snapshots[this.snapshots.length - 1].timestamp - this.snapshots[0].timestamp
|
|
92
|
+
: 0,
|
|
93
|
+
cpu: {
|
|
94
|
+
min: Math.min(...cpuValues),
|
|
95
|
+
max: Math.max(...cpuValues),
|
|
96
|
+
avg: parseFloat((cpuValues.reduce((s, v) => s + v, 0) / cpuValues.length).toFixed(1)),
|
|
97
|
+
current: cpuValues[cpuValues.length - 1],
|
|
98
|
+
},
|
|
99
|
+
memory: {
|
|
100
|
+
min: Math.min(...memValues),
|
|
101
|
+
max: Math.max(...memValues),
|
|
102
|
+
avg: parseFloat((memValues.reduce((s, v) => s + v, 0) / memValues.length).toFixed(1)),
|
|
103
|
+
current: memValues[memValues.length - 1],
|
|
104
|
+
},
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Clear all history
|
|
110
|
+
*/
|
|
111
|
+
clear() {
|
|
112
|
+
this.snapshots = [];
|
|
113
|
+
}
|
|
114
|
+
}
|
package/src/index.js
ADDED
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
// ─── SystemLens Main Orchestrator ────────────────────────────────
|
|
2
|
+
// Wires all layers together and provides the public API
|
|
3
|
+
import { CpuCollector } from './collectors/cpu.collector.js';
|
|
4
|
+
import { MemoryCollector } from './collectors/memory.collector.js';
|
|
5
|
+
import { DiskCollector } from './collectors/disk.collector.js';
|
|
6
|
+
import { ProcessCollector } from './collectors/process.collector.js';
|
|
7
|
+
import { ProcessClassifier } from './classifiers/process.classifier.js';
|
|
8
|
+
import { CpuAnalyzer } from './analyzers/cpu.analyzer.js';
|
|
9
|
+
import { MemoryAnalyzer } from './analyzers/memory.analyzer.js';
|
|
10
|
+
import { ProcessAnalyzer } from './analyzers/process.analyzer.js';
|
|
11
|
+
import { SpikeDetector } from './analyzers/spike.detector.js';
|
|
12
|
+
import { ExplanationEngine } from './engines/explanation.engine.js';
|
|
13
|
+
import { SuggestionEngine } from './engines/suggestion.engine.js';
|
|
14
|
+
import { AIEngine } from './engines/ai.engine.js';
|
|
15
|
+
import { HistoryTracker } from './history/history.tracker.js';
|
|
16
|
+
import { RealtimeMonitor } from './monitor/realtime.monitor.js';
|
|
17
|
+
import { CliRenderer } from './renderer/cli.renderer.js';
|
|
18
|
+
|
|
19
|
+
export class SystemLens {
|
|
20
|
+
constructor() {
|
|
21
|
+
// ── Data Collection Layer ──
|
|
22
|
+
this.collectors = {
|
|
23
|
+
cpu: new CpuCollector(),
|
|
24
|
+
memory: new MemoryCollector(),
|
|
25
|
+
disk: new DiskCollector(),
|
|
26
|
+
process: new ProcessCollector(),
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
// ── Classification Layer ──
|
|
30
|
+
this.classifier = new ProcessClassifier();
|
|
31
|
+
|
|
32
|
+
// ── Analysis Layer ──
|
|
33
|
+
this.analyzers = {
|
|
34
|
+
cpu: new CpuAnalyzer(),
|
|
35
|
+
memory: new MemoryAnalyzer(),
|
|
36
|
+
process: new ProcessAnalyzer(),
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
// ── Pattern Detection ──
|
|
40
|
+
this.spikeDetector = new SpikeDetector();
|
|
41
|
+
|
|
42
|
+
// ── Engine Layer ──
|
|
43
|
+
this.engines = {
|
|
44
|
+
explanation: new ExplanationEngine(),
|
|
45
|
+
suggestion: new SuggestionEngine(),
|
|
46
|
+
ai: new AIEngine(),
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
// ── History ──
|
|
50
|
+
this.historyTracker = new HistoryTracker();
|
|
51
|
+
|
|
52
|
+
// ── Rendering ──
|
|
53
|
+
this.renderer = new CliRenderer();
|
|
54
|
+
|
|
55
|
+
// ── Monitor ──
|
|
56
|
+
this.monitor = new RealtimeMonitor({
|
|
57
|
+
collectors: this.collectors,
|
|
58
|
+
classifier: this.classifier,
|
|
59
|
+
analyzers: this.analyzers,
|
|
60
|
+
spikeDetector: this.spikeDetector,
|
|
61
|
+
engines: this.engines,
|
|
62
|
+
historyTracker: this.historyTracker,
|
|
63
|
+
renderer: this.renderer,
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Start real-time monitoring mode
|
|
69
|
+
*/
|
|
70
|
+
async startMonitoring() {
|
|
71
|
+
await this.monitor.start();
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Run a single analysis and print results
|
|
76
|
+
*/
|
|
77
|
+
async runOnce() {
|
|
78
|
+
return await this.monitor.runOnce();
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Get the raw analysis data (for API/web use)
|
|
83
|
+
*/
|
|
84
|
+
async analyze() {
|
|
85
|
+
// Collect
|
|
86
|
+
const [cpuData, memData, diskData, processData] = await Promise.all([
|
|
87
|
+
this.collectors.cpu.collect(),
|
|
88
|
+
this.collectors.memory.collect(),
|
|
89
|
+
this.collectors.disk.collect(),
|
|
90
|
+
this.collectors.process.collect(),
|
|
91
|
+
]);
|
|
92
|
+
|
|
93
|
+
// Classify
|
|
94
|
+
const classifiedProcesses = this.classifier.classifyAll(processData.all);
|
|
95
|
+
const devEnvironment = this.classifier.detectDevEnvironment(classifiedProcesses);
|
|
96
|
+
|
|
97
|
+
// Analyze
|
|
98
|
+
const prev = this.historyTracker.getPrevious();
|
|
99
|
+
const cpuAnalysis = this.analyzers.cpu.analyze(cpuData, prev?.cpu || null);
|
|
100
|
+
const memAnalysis = this.analyzers.memory.analyze(memData, prev?.memory || null);
|
|
101
|
+
const processAnalysis = this.analyzers.process.analyze(classifiedProcesses, cpuAnalysis, memAnalysis);
|
|
102
|
+
const patterns = this.spikeDetector.record(cpuAnalysis, memAnalysis);
|
|
103
|
+
|
|
104
|
+
const analysis = {
|
|
105
|
+
cpu: cpuAnalysis,
|
|
106
|
+
memory: memAnalysis,
|
|
107
|
+
process: processAnalysis,
|
|
108
|
+
patterns,
|
|
109
|
+
devEnvironment,
|
|
110
|
+
rawProcesses: {
|
|
111
|
+
topByCpu: classifiedProcesses.sort((a, b) => b.cpu - a.cpu).slice(0, 10),
|
|
112
|
+
topByMemory: [...classifiedProcesses].sort((a, b) => b.mem - a.mem).slice(0, 10),
|
|
113
|
+
},
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
const explanation = this.engines.explanation.explain(analysis);
|
|
117
|
+
const suggestions = this.engines.suggestion.suggest(analysis, explanation);
|
|
118
|
+
|
|
119
|
+
// Record
|
|
120
|
+
this.historyTracker.record({ cpu: cpuData, memory: memData, disk: diskData, processes: processData });
|
|
121
|
+
|
|
122
|
+
return {
|
|
123
|
+
analysis,
|
|
124
|
+
explanation,
|
|
125
|
+
suggestions,
|
|
126
|
+
snapshot: { cpu: cpuData, memory: memData, disk: diskData, processes: processData },
|
|
127
|
+
history: this.historyTracker.getSummary(),
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
}
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
// ─── Real-Time Monitor ───────────────────────────────────────────
|
|
2
|
+
// Orchestrates continuous data collection, analysis, and rendering
|
|
3
|
+
import { MONITORING_INTERVAL } from '../utils/constants.js';
|
|
4
|
+
|
|
5
|
+
export class RealtimeMonitor {
|
|
6
|
+
constructor({ collectors, classifier, analyzers, spikeDetector, engines, historyTracker, renderer }) {
|
|
7
|
+
this.collectors = collectors;
|
|
8
|
+
this.classifier = classifier;
|
|
9
|
+
this.analyzers = analyzers;
|
|
10
|
+
this.spikeDetector = spikeDetector;
|
|
11
|
+
this.engines = engines;
|
|
12
|
+
this.historyTracker = historyTracker;
|
|
13
|
+
this.renderer = renderer;
|
|
14
|
+
this.intervalId = null;
|
|
15
|
+
this.isRunning = false;
|
|
16
|
+
this.cycleCount = 0;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Start the monitoring loop
|
|
21
|
+
*/
|
|
22
|
+
async start() {
|
|
23
|
+
this.isRunning = true;
|
|
24
|
+
|
|
25
|
+
// Immediate first run
|
|
26
|
+
await this._cycle();
|
|
27
|
+
|
|
28
|
+
// Continuous monitoring
|
|
29
|
+
this.intervalId = setInterval(async () => {
|
|
30
|
+
if (!this.isRunning) return;
|
|
31
|
+
try {
|
|
32
|
+
await this._cycle();
|
|
33
|
+
} catch (error) {
|
|
34
|
+
console.error('Monitor cycle error:', error.message);
|
|
35
|
+
}
|
|
36
|
+
}, MONITORING_INTERVAL);
|
|
37
|
+
|
|
38
|
+
// Graceful shutdown
|
|
39
|
+
process.on('SIGINT', () => this.stop());
|
|
40
|
+
process.on('SIGTERM', () => this.stop());
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Stop the monitoring loop
|
|
45
|
+
*/
|
|
46
|
+
stop() {
|
|
47
|
+
this.isRunning = false;
|
|
48
|
+
if (this.intervalId) {
|
|
49
|
+
clearInterval(this.intervalId);
|
|
50
|
+
this.intervalId = null;
|
|
51
|
+
}
|
|
52
|
+
console.log('\n\n 👋 SystemLens stopped. Goodbye!\n');
|
|
53
|
+
process.exit(0);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Run a single collection + analysis + render cycle
|
|
58
|
+
*/
|
|
59
|
+
async _cycle() {
|
|
60
|
+
this.cycleCount++;
|
|
61
|
+
|
|
62
|
+
// ─── 1. Collect Data ──────────────────────────────────
|
|
63
|
+
const [cpuData, memData, diskData, processData] = await Promise.all([
|
|
64
|
+
this.collectors.cpu.collect(),
|
|
65
|
+
this.collectors.memory.collect(),
|
|
66
|
+
this.collectors.disk.collect(),
|
|
67
|
+
this.collectors.process.collect(),
|
|
68
|
+
]);
|
|
69
|
+
|
|
70
|
+
// ─── 2. Classify Processes ────────────────────────────
|
|
71
|
+
const classifiedProcesses = this.classifier.classifyAll(processData.all);
|
|
72
|
+
const devEnvironment = this.classifier.detectDevEnvironment(classifiedProcesses);
|
|
73
|
+
|
|
74
|
+
// ─── 3. Get Previous Data for Comparison ──────────────
|
|
75
|
+
const prev = this.historyTracker.getPrevious();
|
|
76
|
+
const previousCpu = prev?.cpu || null;
|
|
77
|
+
const previousMem = prev?.memory || null;
|
|
78
|
+
|
|
79
|
+
// ─── 4. Analyze ───────────────────────────────────────
|
|
80
|
+
const cpuAnalysis = this.analyzers.cpu.analyze(cpuData, previousCpu);
|
|
81
|
+
const memAnalysis = this.analyzers.memory.analyze(memData, previousMem);
|
|
82
|
+
const processAnalysis = this.analyzers.process.analyze(classifiedProcesses, cpuAnalysis, memAnalysis);
|
|
83
|
+
|
|
84
|
+
// ─── 5. Detect Patterns ───────────────────────────────
|
|
85
|
+
const patterns = this.spikeDetector.record(cpuAnalysis, memAnalysis);
|
|
86
|
+
|
|
87
|
+
// ─── 6. Build Full Analysis Object ────────────────────
|
|
88
|
+
const analysis = {
|
|
89
|
+
cpu: cpuAnalysis,
|
|
90
|
+
memory: memAnalysis,
|
|
91
|
+
process: processAnalysis,
|
|
92
|
+
patterns,
|
|
93
|
+
devEnvironment,
|
|
94
|
+
rawProcesses: {
|
|
95
|
+
topByCpu: classifiedProcesses.sort((a, b) => b.cpu - a.cpu).slice(0, 10),
|
|
96
|
+
topByMemory: [...classifiedProcesses].sort((a, b) => b.mem - a.mem).slice(0, 10),
|
|
97
|
+
},
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
// ─── 7. Generate Explanation ──────────────────────────
|
|
101
|
+
const explanation = this.engines.explanation.explain(analysis);
|
|
102
|
+
|
|
103
|
+
// ─── 8. Generate Suggestions ──────────────────────────
|
|
104
|
+
const suggestions = this.engines.suggestion.suggest(analysis, explanation);
|
|
105
|
+
|
|
106
|
+
// ─── 9. AI Enhancement (every 5th cycle) ──────────────
|
|
107
|
+
let aiInsights = null;
|
|
108
|
+
if (this.cycleCount % 5 === 0 && this.engines.ai?.isAvailable()) {
|
|
109
|
+
aiInsights = await this.engines.ai.enhance(analysis, explanation);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// ─── 10. Record History ───────────────────────────────
|
|
113
|
+
this.historyTracker.record({
|
|
114
|
+
cpu: cpuData,
|
|
115
|
+
memory: memData,
|
|
116
|
+
disk: diskData,
|
|
117
|
+
processes: processData,
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
// ─── 11. Render ───────────────────────────────────────
|
|
121
|
+
const report = {
|
|
122
|
+
analysis,
|
|
123
|
+
explanation,
|
|
124
|
+
suggestions,
|
|
125
|
+
aiInsights,
|
|
126
|
+
snapshot: {
|
|
127
|
+
cpu: cpuData,
|
|
128
|
+
memory: memData,
|
|
129
|
+
disk: diskData,
|
|
130
|
+
processes: processData,
|
|
131
|
+
},
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
this.renderer.render(report);
|
|
135
|
+
|
|
136
|
+
return report;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Run a single analysis (non-monitoring mode)
|
|
141
|
+
*/
|
|
142
|
+
async runOnce() {
|
|
143
|
+
return await this._cycle();
|
|
144
|
+
}
|
|
145
|
+
}
|