xypriss 1.2.4 → 1.3.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/README.md +51 -3
- package/dist/cjs/mods/security/src/components/cache/index.js +1 -1
- package/dist/cjs/shared/logger/Logger.js +1 -0
- package/dist/cjs/shared/logger/Logger.js.map +1 -1
- package/dist/cjs/src/cluster/bun-cluster-manager.js +1567 -0
- package/dist/cjs/src/cluster/bun-cluster-manager.js.map +1 -0
- package/dist/cjs/src/cluster/cluster-manager.js +1 -1
- package/dist/cjs/src/cluster/cluster-manager.js.map +1 -1
- package/dist/cjs/src/cluster/index.js +25 -6
- package/dist/cjs/src/cluster/index.js.map +1 -1
- package/dist/cjs/src/cluster/memory-manager.js +463 -0
- package/dist/cjs/src/cluster/memory-manager.js.map +1 -0
- package/dist/cjs/src/cluster/modules/BunIPCManager.js +603 -0
- package/dist/cjs/src/cluster/modules/BunIPCManager.js.map +1 -0
- package/dist/cjs/src/cluster/modules/ClusterFactory.js +22 -1
- package/dist/cjs/src/cluster/modules/ClusterFactory.js.map +1 -1
- package/dist/cjs/src/cluster/modules/CpuMonitor.js +658 -0
- package/dist/cjs/src/cluster/modules/CpuMonitor.js.map +1 -0
- package/dist/cjs/src/cluster/modules/ProcessMonitor.js +513 -0
- package/dist/cjs/src/cluster/modules/ProcessMonitor.js.map +1 -0
- package/dist/cjs/src/plugins/server-maintenance-plugin.js +1 -1
- package/dist/cjs/src/server/FastServer.js +64 -43
- package/dist/cjs/src/server/FastServer.js.map +1 -1
- package/dist/cjs/src/server/components/fastapi/ClusterManagerComponent.js +226 -10
- package/dist/cjs/src/server/components/fastapi/ClusterManagerComponent.js.map +1 -1
- package/dist/cjs/src/server/const/Cluster.config.js +174 -31
- package/dist/cjs/src/server/const/Cluster.config.js.map +1 -1
- package/dist/cjs/src/server/const/default.js +11 -2
- package/dist/cjs/src/server/const/default.js.map +1 -1
- package/dist/cjs/src/server/utils/PortManager.js +26 -15
- package/dist/cjs/src/server/utils/PortManager.js.map +1 -1
- package/dist/esm/mods/security/src/components/cache/index.js +1 -1
- package/dist/esm/shared/logger/Logger.js +1 -0
- package/dist/esm/shared/logger/Logger.js.map +1 -1
- package/dist/esm/src/cluster/bun-cluster-manager.js +1565 -0
- package/dist/esm/src/cluster/bun-cluster-manager.js.map +1 -0
- package/dist/esm/src/cluster/cluster-manager.js +1 -1
- package/dist/esm/src/cluster/cluster-manager.js.map +1 -1
- package/dist/esm/src/cluster/index.js +25 -6
- package/dist/esm/src/cluster/index.js.map +1 -1
- package/dist/esm/src/cluster/memory-manager.js +461 -0
- package/dist/esm/src/cluster/memory-manager.js.map +1 -0
- package/dist/esm/src/cluster/modules/BunIPCManager.js +601 -0
- package/dist/esm/src/cluster/modules/BunIPCManager.js.map +1 -0
- package/dist/esm/src/cluster/modules/ClusterFactory.js +22 -1
- package/dist/esm/src/cluster/modules/ClusterFactory.js.map +1 -1
- package/dist/esm/src/cluster/modules/CpuMonitor.js +656 -0
- package/dist/esm/src/cluster/modules/CpuMonitor.js.map +1 -0
- package/dist/esm/src/cluster/modules/ProcessMonitor.js +511 -0
- package/dist/esm/src/cluster/modules/ProcessMonitor.js.map +1 -0
- package/dist/esm/src/plugins/server-maintenance-plugin.js +1 -1
- package/dist/esm/src/server/FastServer.js +64 -43
- package/dist/esm/src/server/FastServer.js.map +1 -1
- package/dist/esm/src/server/components/fastapi/ClusterManagerComponent.js +226 -10
- package/dist/esm/src/server/components/fastapi/ClusterManagerComponent.js.map +1 -1
- package/dist/esm/src/server/const/Cluster.config.js +174 -31
- package/dist/esm/src/server/const/Cluster.config.js.map +1 -1
- package/dist/esm/src/server/const/default.js +11 -2
- package/dist/esm/src/server/const/default.js.map +1 -1
- package/dist/esm/src/server/utils/PortManager.js +26 -15
- package/dist/esm/src/server/utils/PortManager.js.map +1 -1
- package/dist/index.d.ts +78 -1
- package/package.json +3 -1
- package/dist/cjs/src/plugins/modules/network/index.js +0 -120
- package/dist/cjs/src/plugins/modules/network/index.js.map +0 -1
- package/dist/cjs/src/server/plugins/PluginEngine.js +0 -378
- package/dist/cjs/src/server/plugins/PluginEngine.js.map +0 -1
- package/dist/cjs/src/server/plugins/PluginRegistry.js +0 -339
- package/dist/cjs/src/server/plugins/PluginRegistry.js.map +0 -1
- package/dist/cjs/src/server/plugins/builtin/JWTAuthPlugin.js +0 -591
- package/dist/cjs/src/server/plugins/builtin/JWTAuthPlugin.js.map +0 -1
- package/dist/cjs/src/server/plugins/builtin/ResponseTimePlugin.js +0 -413
- package/dist/cjs/src/server/plugins/builtin/ResponseTimePlugin.js.map +0 -1
- package/dist/cjs/src/server/plugins/builtin/SmartCachePlugin.js +0 -843
- package/dist/cjs/src/server/plugins/builtin/SmartCachePlugin.js.map +0 -1
- package/dist/cjs/src/server/plugins/core/CachePlugin.js +0 -1975
- package/dist/cjs/src/server/plugins/core/CachePlugin.js.map +0 -1
- package/dist/cjs/src/server/plugins/core/PerformancePlugin.js +0 -894
- package/dist/cjs/src/server/plugins/core/PerformancePlugin.js.map +0 -1
- package/dist/cjs/src/server/plugins/core/SecurityPlugin.js +0 -799
- package/dist/cjs/src/server/plugins/core/SecurityPlugin.js.map +0 -1
- package/dist/cjs/src/server/plugins/types/PluginTypes.js +0 -47
- package/dist/cjs/src/server/plugins/types/PluginTypes.js.map +0 -1
- package/dist/esm/src/plugins/modules/network/index.js +0 -109
- package/dist/esm/src/plugins/modules/network/index.js.map +0 -1
- package/dist/esm/src/server/plugins/PluginEngine.js +0 -376
- package/dist/esm/src/server/plugins/PluginEngine.js.map +0 -1
- package/dist/esm/src/server/plugins/PluginRegistry.js +0 -337
- package/dist/esm/src/server/plugins/PluginRegistry.js.map +0 -1
- package/dist/esm/src/server/plugins/builtin/JWTAuthPlugin.js +0 -589
- package/dist/esm/src/server/plugins/builtin/JWTAuthPlugin.js.map +0 -1
- package/dist/esm/src/server/plugins/builtin/ResponseTimePlugin.js +0 -411
- package/dist/esm/src/server/plugins/builtin/ResponseTimePlugin.js.map +0 -1
- package/dist/esm/src/server/plugins/builtin/SmartCachePlugin.js +0 -841
- package/dist/esm/src/server/plugins/builtin/SmartCachePlugin.js.map +0 -1
- package/dist/esm/src/server/plugins/core/CachePlugin.js +0 -1973
- package/dist/esm/src/server/plugins/core/CachePlugin.js.map +0 -1
- package/dist/esm/src/server/plugins/core/PerformancePlugin.js +0 -872
- package/dist/esm/src/server/plugins/core/PerformancePlugin.js.map +0 -1
- package/dist/esm/src/server/plugins/core/SecurityPlugin.js +0 -797
- package/dist/esm/src/server/plugins/core/SecurityPlugin.js.map +0 -1
- package/dist/esm/src/server/plugins/types/PluginTypes.js +0 -47
- package/dist/esm/src/server/plugins/types/PluginTypes.js.map +0 -1
|
@@ -0,0 +1,656 @@
|
|
|
1
|
+
import { logger } from '../../../shared/logger/Logger.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* CPU Monitoring Module for XyPriss Cluster Management
|
|
5
|
+
* Provides sophisticated CPU usage tracking and monitoring capabilities
|
|
6
|
+
* Supports multiple platforms with fallback mechanisms
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* Enhanced CPU Monitor with sophisticated tracking capabilities
|
|
10
|
+
*/
|
|
11
|
+
class CpuMonitor {
|
|
12
|
+
constructor(config = {}) {
|
|
13
|
+
this.history = [];
|
|
14
|
+
this.processHistory = new Map();
|
|
15
|
+
this.isMonitoring = false;
|
|
16
|
+
this.config = {
|
|
17
|
+
enabled: true,
|
|
18
|
+
sampleInterval: 5000, // 5 seconds
|
|
19
|
+
historySize: 100, // Keep 100 samples (~8 minutes at 5s intervals)
|
|
20
|
+
smoothingFactor: 0.3,
|
|
21
|
+
alertThresholds: {
|
|
22
|
+
warning: 70,
|
|
23
|
+
critical: 90,
|
|
24
|
+
},
|
|
25
|
+
...config,
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Start CPU monitoring
|
|
30
|
+
*/
|
|
31
|
+
startMonitoring() {
|
|
32
|
+
if (this.isMonitoring || !this.config.enabled) {
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
this.isMonitoring = true;
|
|
36
|
+
logger.info("cluster", "Starting CPU monitoring");
|
|
37
|
+
this.monitoringInterval = setInterval(async () => {
|
|
38
|
+
try {
|
|
39
|
+
await this.collectSystemStats();
|
|
40
|
+
}
|
|
41
|
+
catch (error) {
|
|
42
|
+
logger.error("cluster", "Error collecting CPU stats:", error);
|
|
43
|
+
}
|
|
44
|
+
}, this.config.sampleInterval);
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Stop CPU monitoring
|
|
48
|
+
*/
|
|
49
|
+
stopMonitoring() {
|
|
50
|
+
if (!this.isMonitoring) {
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
this.isMonitoring = false;
|
|
54
|
+
if (this.monitoringInterval) {
|
|
55
|
+
clearInterval(this.monitoringInterval);
|
|
56
|
+
this.monitoringInterval = undefined;
|
|
57
|
+
}
|
|
58
|
+
logger.info("cluster", "Stopped CPU monitoring");
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Calculate sophisticated CPU usage for the cluster
|
|
62
|
+
*/
|
|
63
|
+
async calculateClusterCpuUsage(workers) {
|
|
64
|
+
try {
|
|
65
|
+
const activeWorkers = workers.filter((w) => w.health.status === "healthy");
|
|
66
|
+
if (activeWorkers.length === 0) {
|
|
67
|
+
return 0;
|
|
68
|
+
}
|
|
69
|
+
// Get system-wide CPU usage
|
|
70
|
+
const systemStats = await this.getSystemCpuStats();
|
|
71
|
+
// Calculate worker-specific CPU usage
|
|
72
|
+
const workerCpuPromises = activeWorkers.map(async (worker) => {
|
|
73
|
+
if (worker.subprocess && !worker.subprocess.killed) {
|
|
74
|
+
return await this.getProcessCpuUsage(worker.subprocess.pid);
|
|
75
|
+
}
|
|
76
|
+
return 0;
|
|
77
|
+
});
|
|
78
|
+
const workerCpuUsages = await Promise.all(workerCpuPromises);
|
|
79
|
+
const totalWorkerCpu = workerCpuUsages.reduce((sum, usage) => sum + usage, 0);
|
|
80
|
+
// Combine system and worker metrics with intelligent weighting
|
|
81
|
+
const systemWeight = 0.4;
|
|
82
|
+
const workerWeight = 0.6;
|
|
83
|
+
const weightedCpu = systemStats.overall * systemWeight +
|
|
84
|
+
Math.min(totalWorkerCpu, 100) * workerWeight;
|
|
85
|
+
// Apply smoothing if we have historical data
|
|
86
|
+
const smoothedCpu = this.applySmoothingToUsage(weightedCpu);
|
|
87
|
+
return Math.min(Math.round(smoothedCpu), 100);
|
|
88
|
+
}
|
|
89
|
+
catch (error) {
|
|
90
|
+
logger.error("cluster", "Error calculating cluster CPU usage:", error);
|
|
91
|
+
return 0;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Get CPU usage for a specific process with enhanced accuracy
|
|
96
|
+
*/
|
|
97
|
+
async getProcessCpuUsage(pid) {
|
|
98
|
+
try {
|
|
99
|
+
const stats = await this.getProcessCpuStats(pid);
|
|
100
|
+
// Store in history for trend analysis
|
|
101
|
+
this.updateProcessHistory(pid, stats);
|
|
102
|
+
return stats.usage;
|
|
103
|
+
}
|
|
104
|
+
catch (error) {
|
|
105
|
+
logger.debug("cluster", `Error getting CPU usage for PID ${pid}:`, error);
|
|
106
|
+
return 0;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Get detailed CPU statistics for a process
|
|
111
|
+
*/
|
|
112
|
+
async getProcessCpuStats(pid) {
|
|
113
|
+
const timestamp = Date.now();
|
|
114
|
+
try {
|
|
115
|
+
if (process.platform === "linux") {
|
|
116
|
+
return await this.getLinuxProcessCpuStats(pid, timestamp);
|
|
117
|
+
}
|
|
118
|
+
else if (process.platform === "darwin") {
|
|
119
|
+
return await this.getMacOSProcessCpuStats(pid, timestamp);
|
|
120
|
+
}
|
|
121
|
+
else if (process.platform === "win32") {
|
|
122
|
+
return await this.getWindowsProcessCpuStats(pid, timestamp);
|
|
123
|
+
}
|
|
124
|
+
else {
|
|
125
|
+
// Fallback for unsupported platforms
|
|
126
|
+
return {
|
|
127
|
+
pid,
|
|
128
|
+
usage: 0,
|
|
129
|
+
userTime: 0,
|
|
130
|
+
systemTime: 0,
|
|
131
|
+
totalTime: 0,
|
|
132
|
+
timestamp,
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
catch (error) {
|
|
137
|
+
throw new Error(`Failed to get process CPU stats for PID ${pid}: ${error}`);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Get system-wide CPU statistics
|
|
142
|
+
*/
|
|
143
|
+
async getSystemCpuStats() {
|
|
144
|
+
const timestamp = Date.now();
|
|
145
|
+
const loadAverage = process.platform !== "win32" ? require("os").loadavg() : [0, 0, 0];
|
|
146
|
+
try {
|
|
147
|
+
if (process.platform === "linux") {
|
|
148
|
+
return await this.getLinuxSystemCpuStats(timestamp, loadAverage);
|
|
149
|
+
}
|
|
150
|
+
else if (process.platform === "darwin") {
|
|
151
|
+
return await this.getMacOSSystemCpuStats(timestamp, loadAverage);
|
|
152
|
+
}
|
|
153
|
+
else if (process.platform === "win32") {
|
|
154
|
+
return await this.getWindowsSystemCpuStats(timestamp);
|
|
155
|
+
}
|
|
156
|
+
else {
|
|
157
|
+
// Fallback
|
|
158
|
+
return {
|
|
159
|
+
overall: 0,
|
|
160
|
+
cores: [],
|
|
161
|
+
loadAverage,
|
|
162
|
+
processes: 0,
|
|
163
|
+
timestamp,
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
catch (error) {
|
|
168
|
+
logger.error("cluster", "Error getting system CPU stats:", error);
|
|
169
|
+
return {
|
|
170
|
+
overall: 0,
|
|
171
|
+
cores: [],
|
|
172
|
+
loadAverage,
|
|
173
|
+
processes: 0,
|
|
174
|
+
timestamp,
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Apply exponential smoothing to CPU usage
|
|
180
|
+
*/
|
|
181
|
+
applySmoothingToUsage(currentUsage) {
|
|
182
|
+
if (this.history.length === 0) {
|
|
183
|
+
return currentUsage;
|
|
184
|
+
}
|
|
185
|
+
const lastUsage = this.history[this.history.length - 1].usage;
|
|
186
|
+
const smoothingFactor = this.config.smoothingFactor;
|
|
187
|
+
return (smoothingFactor * currentUsage + (1 - smoothingFactor) * lastUsage);
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* Update process history for trend analysis
|
|
191
|
+
*/
|
|
192
|
+
updateProcessHistory(pid, stats) {
|
|
193
|
+
if (!this.processHistory.has(pid)) {
|
|
194
|
+
this.processHistory.set(pid, []);
|
|
195
|
+
}
|
|
196
|
+
const history = this.processHistory.get(pid);
|
|
197
|
+
history.push(stats);
|
|
198
|
+
// Keep only recent history
|
|
199
|
+
if (history.length > this.config.historySize) {
|
|
200
|
+
history.shift();
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* Collect and store system-wide statistics
|
|
205
|
+
*/
|
|
206
|
+
async collectSystemStats() {
|
|
207
|
+
try {
|
|
208
|
+
const systemStats = await this.getSystemCpuStats();
|
|
209
|
+
const dataPoint = {
|
|
210
|
+
timestamp: systemStats.timestamp,
|
|
211
|
+
usage: systemStats.overall,
|
|
212
|
+
processes: new Map(),
|
|
213
|
+
};
|
|
214
|
+
this.history.push(dataPoint);
|
|
215
|
+
// Keep only recent history
|
|
216
|
+
if (this.history.length > this.config.historySize) {
|
|
217
|
+
this.history.shift();
|
|
218
|
+
}
|
|
219
|
+
// Store system stats for future reference if needed
|
|
220
|
+
// Check for alerts
|
|
221
|
+
this.checkAlertThresholds(systemStats.overall);
|
|
222
|
+
}
|
|
223
|
+
catch (error) {
|
|
224
|
+
logger.error("cluster", "Error collecting system stats:", error);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* Check if CPU usage exceeds alert thresholds
|
|
229
|
+
*/
|
|
230
|
+
checkAlertThresholds(usage) {
|
|
231
|
+
if (usage >= this.config.alertThresholds.critical) {
|
|
232
|
+
logger.warn("cluster", `Critical CPU usage detected: ${usage.toFixed(1)}%`);
|
|
233
|
+
}
|
|
234
|
+
else if (usage >= this.config.alertThresholds.warning) {
|
|
235
|
+
logger.warn("cluster", `High CPU usage detected: ${usage.toFixed(1)}%`);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
/**
|
|
239
|
+
* Get Linux process CPU statistics using /proc filesystem
|
|
240
|
+
*/
|
|
241
|
+
async getLinuxProcessCpuStats(pid, timestamp) {
|
|
242
|
+
const fs = await import('fs');
|
|
243
|
+
try {
|
|
244
|
+
// Read process stat file
|
|
245
|
+
const statData = await fs.promises.readFile(`/proc/${pid}/stat`, "utf8");
|
|
246
|
+
const statFields = statData.trim().split(/\s+/);
|
|
247
|
+
if (statFields.length < 15) {
|
|
248
|
+
throw new Error("Invalid stat file format");
|
|
249
|
+
}
|
|
250
|
+
const utime = parseInt(statFields[13]); // User time in clock ticks
|
|
251
|
+
const stime = parseInt(statFields[14]); // System time in clock ticks
|
|
252
|
+
const totalTime = utime + stime;
|
|
253
|
+
// Get system clock ticks per second
|
|
254
|
+
const clockTicks = 100; // Usually 100 on Linux, but could vary
|
|
255
|
+
// Convert to milliseconds
|
|
256
|
+
const userTimeMs = (utime / clockTicks) * 1000;
|
|
257
|
+
const systemTimeMs = (stime / clockTicks) * 1000;
|
|
258
|
+
const totalTimeMs = (totalTime / clockTicks) * 1000;
|
|
259
|
+
// Calculate CPU usage percentage
|
|
260
|
+
let usage = 0;
|
|
261
|
+
const previousStats = this.getLastProcessStats(pid);
|
|
262
|
+
if (previousStats) {
|
|
263
|
+
const timeDelta = timestamp - previousStats.timestamp;
|
|
264
|
+
const cpuTimeDelta = totalTimeMs - previousStats.totalTime;
|
|
265
|
+
if (timeDelta > 0) {
|
|
266
|
+
usage = Math.min(100, (cpuTimeDelta / timeDelta) * 100);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
return {
|
|
270
|
+
pid,
|
|
271
|
+
usage,
|
|
272
|
+
userTime: userTimeMs,
|
|
273
|
+
systemTime: systemTimeMs,
|
|
274
|
+
totalTime: totalTimeMs,
|
|
275
|
+
timestamp,
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
catch (error) {
|
|
279
|
+
throw new Error(`Failed to read Linux process stats: ${error}`);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
/**
|
|
283
|
+
* Get macOS process CPU statistics using ps command
|
|
284
|
+
*/
|
|
285
|
+
async getMacOSProcessCpuStats(pid, timestamp) {
|
|
286
|
+
const { spawn } = await import('child_process');
|
|
287
|
+
return new Promise((resolve, reject) => {
|
|
288
|
+
const ps = spawn("ps", ["-o", "pcpu=,time=", "-p", pid.toString()]);
|
|
289
|
+
let output = "";
|
|
290
|
+
ps.stdout.on("data", (data) => {
|
|
291
|
+
output += data.toString();
|
|
292
|
+
});
|
|
293
|
+
ps.on("close", (code) => {
|
|
294
|
+
if (code === 0) {
|
|
295
|
+
try {
|
|
296
|
+
const lines = output.trim().split("\n");
|
|
297
|
+
const data = lines[lines.length - 1]
|
|
298
|
+
.trim()
|
|
299
|
+
.split(/\s+/);
|
|
300
|
+
const cpuPercent = parseFloat(data[0]) || 0;
|
|
301
|
+
const timeStr = data[1] || "0:00.00";
|
|
302
|
+
// Parse time format (MM:SS.ss or HH:MM:SS.ss)
|
|
303
|
+
const timeParts = timeStr.split(":");
|
|
304
|
+
let totalSeconds = 0;
|
|
305
|
+
if (timeParts.length === 2) {
|
|
306
|
+
totalSeconds =
|
|
307
|
+
parseInt(timeParts[0]) * 60 +
|
|
308
|
+
parseFloat(timeParts[1]);
|
|
309
|
+
}
|
|
310
|
+
else if (timeParts.length === 3) {
|
|
311
|
+
totalSeconds =
|
|
312
|
+
parseInt(timeParts[0]) * 3600 +
|
|
313
|
+
parseInt(timeParts[1]) * 60 +
|
|
314
|
+
parseFloat(timeParts[2]);
|
|
315
|
+
}
|
|
316
|
+
const totalTimeMs = totalSeconds * 1000;
|
|
317
|
+
resolve({
|
|
318
|
+
pid,
|
|
319
|
+
usage: cpuPercent,
|
|
320
|
+
userTime: totalTimeMs * 0.7, // Approximate split
|
|
321
|
+
systemTime: totalTimeMs * 0.3,
|
|
322
|
+
totalTime: totalTimeMs,
|
|
323
|
+
timestamp,
|
|
324
|
+
});
|
|
325
|
+
}
|
|
326
|
+
catch (error) {
|
|
327
|
+
reject(new Error(`Failed to parse macOS ps output: ${error}`));
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
else {
|
|
331
|
+
reject(new Error(`ps command failed with code ${code}`));
|
|
332
|
+
}
|
|
333
|
+
});
|
|
334
|
+
ps.on("error", reject);
|
|
335
|
+
});
|
|
336
|
+
}
|
|
337
|
+
/**
|
|
338
|
+
* Get Windows process CPU statistics using wmic
|
|
339
|
+
*/
|
|
340
|
+
async getWindowsProcessCpuStats(pid, timestamp) {
|
|
341
|
+
const { spawn } = await import('child_process');
|
|
342
|
+
return new Promise((resolve, reject) => {
|
|
343
|
+
const wmic = spawn("wmic", [
|
|
344
|
+
"process",
|
|
345
|
+
"where",
|
|
346
|
+
`ProcessId=${pid}`,
|
|
347
|
+
"get",
|
|
348
|
+
"PageFileUsage,UserModeTime,KernelModeTime",
|
|
349
|
+
"/format:csv",
|
|
350
|
+
]);
|
|
351
|
+
let output = "";
|
|
352
|
+
wmic.stdout.on("data", (data) => {
|
|
353
|
+
output += data.toString();
|
|
354
|
+
});
|
|
355
|
+
wmic.on("close", (code) => {
|
|
356
|
+
if (code === 0) {
|
|
357
|
+
try {
|
|
358
|
+
const lines = output.trim().split("\n");
|
|
359
|
+
const dataLine = lines.find((line) => line.includes(pid.toString()));
|
|
360
|
+
if (!dataLine) {
|
|
361
|
+
throw new Error("Process not found in wmic output");
|
|
362
|
+
}
|
|
363
|
+
const fields = dataLine.split(",");
|
|
364
|
+
const kernelTime = parseInt(fields[1]) || 0;
|
|
365
|
+
const userTime = parseInt(fields[2]) || 0;
|
|
366
|
+
// Convert from 100-nanosecond intervals to milliseconds
|
|
367
|
+
const kernelTimeMs = kernelTime / 10000;
|
|
368
|
+
const userTimeMs = userTime / 10000;
|
|
369
|
+
const totalTimeMs = kernelTimeMs + userTimeMs;
|
|
370
|
+
// Calculate usage based on previous measurement
|
|
371
|
+
let usage = 0;
|
|
372
|
+
const previousStats = this.getLastProcessStats(pid);
|
|
373
|
+
if (previousStats) {
|
|
374
|
+
const timeDelta = timestamp - previousStats.timestamp;
|
|
375
|
+
const cpuTimeDelta = totalTimeMs - previousStats.totalTime;
|
|
376
|
+
if (timeDelta > 0) {
|
|
377
|
+
usage = Math.min(100, (cpuTimeDelta / timeDelta) * 100);
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
resolve({
|
|
381
|
+
pid,
|
|
382
|
+
usage,
|
|
383
|
+
userTime: userTimeMs,
|
|
384
|
+
systemTime: kernelTimeMs,
|
|
385
|
+
totalTime: totalTimeMs,
|
|
386
|
+
timestamp,
|
|
387
|
+
});
|
|
388
|
+
}
|
|
389
|
+
catch (error) {
|
|
390
|
+
reject(new Error(`Failed to parse Windows wmic output: ${error}`));
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
else {
|
|
394
|
+
reject(new Error(`wmic command failed with code ${code}`));
|
|
395
|
+
}
|
|
396
|
+
});
|
|
397
|
+
wmic.on("error", reject);
|
|
398
|
+
});
|
|
399
|
+
}
|
|
400
|
+
/**
|
|
401
|
+
* Get the last recorded stats for a process
|
|
402
|
+
*/
|
|
403
|
+
getLastProcessStats(pid) {
|
|
404
|
+
const history = this.processHistory.get(pid);
|
|
405
|
+
return history && history.length > 0
|
|
406
|
+
? history[history.length - 1]
|
|
407
|
+
: null;
|
|
408
|
+
}
|
|
409
|
+
/**
|
|
410
|
+
* Get Linux system CPU statistics using /proc/stat
|
|
411
|
+
*/
|
|
412
|
+
async getLinuxSystemCpuStats(timestamp, loadAverage) {
|
|
413
|
+
const fs = await import('fs');
|
|
414
|
+
try {
|
|
415
|
+
const statData = await fs.promises.readFile("/proc/stat", "utf8");
|
|
416
|
+
const lines = statData.split("\n");
|
|
417
|
+
// Parse overall CPU line
|
|
418
|
+
const cpuLine = lines[0];
|
|
419
|
+
const cpuTimes = cpuLine.split(/\s+/).slice(1).map(Number);
|
|
420
|
+
const idle = cpuTimes[3] || 0;
|
|
421
|
+
const iowait = cpuTimes[4] || 0;
|
|
422
|
+
const total = cpuTimes.reduce((sum, time) => sum + time, 0);
|
|
423
|
+
const overall = total > 0
|
|
424
|
+
? Math.max(0, 100 - ((idle + iowait) / total) * 100)
|
|
425
|
+
: 0;
|
|
426
|
+
// Parse per-core CPU usage
|
|
427
|
+
const cores = [];
|
|
428
|
+
for (let i = 1; i < lines.length; i++) {
|
|
429
|
+
const line = lines[i];
|
|
430
|
+
if (line.startsWith("cpu")) {
|
|
431
|
+
const coreTimes = line.split(/\s+/).slice(1).map(Number);
|
|
432
|
+
if (coreTimes.length >= 4) {
|
|
433
|
+
const coreIdle = coreTimes[3] || 0;
|
|
434
|
+
const coreIowait = coreTimes[4] || 0;
|
|
435
|
+
const coreTotal = coreTimes.reduce((sum, time) => sum + time, 0);
|
|
436
|
+
const coreUsage = coreTotal > 0
|
|
437
|
+
? Math.max(0, 100 -
|
|
438
|
+
((coreIdle + coreIowait) /
|
|
439
|
+
coreTotal) *
|
|
440
|
+
100)
|
|
441
|
+
: 0;
|
|
442
|
+
cores.push(coreUsage);
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
else {
|
|
446
|
+
break;
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
// Get process count
|
|
450
|
+
let processes = 0;
|
|
451
|
+
try {
|
|
452
|
+
const procDirs = await fs.promises.readdir("/proc");
|
|
453
|
+
processes = procDirs.filter((dir) => /^\d+$/.test(dir)).length;
|
|
454
|
+
}
|
|
455
|
+
catch {
|
|
456
|
+
processes = 0;
|
|
457
|
+
}
|
|
458
|
+
return {
|
|
459
|
+
overall,
|
|
460
|
+
cores,
|
|
461
|
+
loadAverage,
|
|
462
|
+
processes,
|
|
463
|
+
timestamp,
|
|
464
|
+
};
|
|
465
|
+
}
|
|
466
|
+
catch (error) {
|
|
467
|
+
throw new Error(`Failed to read Linux system stats: ${error}`);
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
/**
|
|
471
|
+
* Get macOS system CPU statistics using system commands
|
|
472
|
+
*/
|
|
473
|
+
async getMacOSSystemCpuStats(timestamp, loadAverage) {
|
|
474
|
+
const { spawn } = await import('child_process');
|
|
475
|
+
return new Promise((resolve, reject) => {
|
|
476
|
+
// Use iostat to get CPU usage
|
|
477
|
+
const iostat = spawn("iostat", ["-c", "1", "1"]);
|
|
478
|
+
let output = "";
|
|
479
|
+
iostat.stdout.on("data", (data) => {
|
|
480
|
+
output += data.toString();
|
|
481
|
+
});
|
|
482
|
+
iostat.on("close", async (code) => {
|
|
483
|
+
if (code === 0) {
|
|
484
|
+
try {
|
|
485
|
+
const lines = output.trim().split("\n");
|
|
486
|
+
const cpuLine = lines[lines.length - 1];
|
|
487
|
+
const values = cpuLine.trim().split(/\s+/);
|
|
488
|
+
// iostat format: %user %nice %sys %idle
|
|
489
|
+
const idle = parseFloat(values[3]) || 0;
|
|
490
|
+
// Calculate overall CPU usage (100 - idle)
|
|
491
|
+
const overall = Math.max(0, 100 - idle);
|
|
492
|
+
// Get process count
|
|
493
|
+
let processes = 0;
|
|
494
|
+
try {
|
|
495
|
+
const ps = spawn("ps", ["-A"]);
|
|
496
|
+
let psOutput = "";
|
|
497
|
+
ps.stdout.on("data", (data) => {
|
|
498
|
+
psOutput += data.toString();
|
|
499
|
+
});
|
|
500
|
+
ps.on("close", () => {
|
|
501
|
+
processes = psOutput.split("\n").length - 2; // Subtract header and empty line
|
|
502
|
+
resolve({
|
|
503
|
+
overall,
|
|
504
|
+
cores: [], // macOS per-core stats require more complex parsing
|
|
505
|
+
loadAverage,
|
|
506
|
+
processes,
|
|
507
|
+
timestamp,
|
|
508
|
+
});
|
|
509
|
+
});
|
|
510
|
+
}
|
|
511
|
+
catch {
|
|
512
|
+
resolve({
|
|
513
|
+
overall,
|
|
514
|
+
cores: [],
|
|
515
|
+
loadAverage,
|
|
516
|
+
processes: 0,
|
|
517
|
+
timestamp,
|
|
518
|
+
});
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
catch (error) {
|
|
522
|
+
reject(new Error(`Failed to parse macOS iostat output: ${error}`));
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
else {
|
|
526
|
+
reject(new Error(`iostat command failed with code ${code}`));
|
|
527
|
+
}
|
|
528
|
+
});
|
|
529
|
+
iostat.on("error", reject);
|
|
530
|
+
});
|
|
531
|
+
}
|
|
532
|
+
/**
|
|
533
|
+
* Get Windows system CPU statistics using wmic
|
|
534
|
+
*/
|
|
535
|
+
async getWindowsSystemCpuStats(timestamp) {
|
|
536
|
+
const { spawn } = await import('child_process');
|
|
537
|
+
return new Promise((resolve, reject) => {
|
|
538
|
+
const wmic = spawn("wmic", [
|
|
539
|
+
"cpu",
|
|
540
|
+
"get",
|
|
541
|
+
"loadpercentage",
|
|
542
|
+
"/value",
|
|
543
|
+
]);
|
|
544
|
+
let output = "";
|
|
545
|
+
wmic.stdout.on("data", (data) => {
|
|
546
|
+
output += data.toString();
|
|
547
|
+
});
|
|
548
|
+
wmic.on("close", async (code) => {
|
|
549
|
+
if (code === 0) {
|
|
550
|
+
try {
|
|
551
|
+
const lines = output.split("\n");
|
|
552
|
+
const loadLine = lines.find((line) => line.includes("LoadPercentage"));
|
|
553
|
+
let overall = 0;
|
|
554
|
+
if (loadLine) {
|
|
555
|
+
const match = loadLine.match(/LoadPercentage=(\d+)/);
|
|
556
|
+
if (match) {
|
|
557
|
+
overall = parseInt(match[1]);
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
// Get process count
|
|
561
|
+
let processes = 0;
|
|
562
|
+
try {
|
|
563
|
+
const tasklist = spawn("tasklist", ["/fo", "csv"]);
|
|
564
|
+
let taskOutput = "";
|
|
565
|
+
tasklist.stdout.on("data", (data) => {
|
|
566
|
+
taskOutput += data.toString();
|
|
567
|
+
});
|
|
568
|
+
tasklist.on("close", () => {
|
|
569
|
+
processes = taskOutput.split("\n").length - 2; // Subtract header and empty line
|
|
570
|
+
resolve({
|
|
571
|
+
overall,
|
|
572
|
+
cores: [], // Windows per-core stats require additional commands
|
|
573
|
+
loadAverage: [0, 0, 0], // Windows doesn't have load average
|
|
574
|
+
processes,
|
|
575
|
+
timestamp,
|
|
576
|
+
});
|
|
577
|
+
});
|
|
578
|
+
}
|
|
579
|
+
catch {
|
|
580
|
+
resolve({
|
|
581
|
+
overall,
|
|
582
|
+
cores: [],
|
|
583
|
+
loadAverage: [0, 0, 0],
|
|
584
|
+
processes: 0,
|
|
585
|
+
timestamp,
|
|
586
|
+
});
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
catch (error) {
|
|
590
|
+
reject(new Error(`Failed to parse Windows wmic output: ${error}`));
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
else {
|
|
594
|
+
reject(new Error(`wmic command failed with code ${code}`));
|
|
595
|
+
}
|
|
596
|
+
});
|
|
597
|
+
wmic.on("error", reject);
|
|
598
|
+
});
|
|
599
|
+
}
|
|
600
|
+
/**
|
|
601
|
+
* Get CPU usage trend for a process
|
|
602
|
+
*/
|
|
603
|
+
getProcessCpuTrend(pid, samples = 10) {
|
|
604
|
+
const history = this.processHistory.get(pid);
|
|
605
|
+
if (!history || history.length === 0) {
|
|
606
|
+
return [];
|
|
607
|
+
}
|
|
608
|
+
const recentHistory = history.slice(-samples);
|
|
609
|
+
return recentHistory.map((stats) => stats.usage);
|
|
610
|
+
}
|
|
611
|
+
/**
|
|
612
|
+
* Get system CPU usage trend
|
|
613
|
+
*/
|
|
614
|
+
getSystemCpuTrend(samples = 10) {
|
|
615
|
+
if (this.history.length === 0) {
|
|
616
|
+
return [];
|
|
617
|
+
}
|
|
618
|
+
const recentHistory = this.history.slice(-samples);
|
|
619
|
+
return recentHistory.map((point) => point.usage);
|
|
620
|
+
}
|
|
621
|
+
/**
|
|
622
|
+
* Get current configuration
|
|
623
|
+
*/
|
|
624
|
+
getConfig() {
|
|
625
|
+
return { ...this.config };
|
|
626
|
+
}
|
|
627
|
+
/**
|
|
628
|
+
* Update configuration
|
|
629
|
+
*/
|
|
630
|
+
updateConfig(newConfig) {
|
|
631
|
+
this.config = { ...this.config, ...newConfig };
|
|
632
|
+
// Restart monitoring if interval changed
|
|
633
|
+
if (this.isMonitoring && newConfig.sampleInterval) {
|
|
634
|
+
this.stopMonitoring();
|
|
635
|
+
this.startMonitoring();
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
/**
|
|
639
|
+
* Clear all historical data
|
|
640
|
+
*/
|
|
641
|
+
clearHistory() {
|
|
642
|
+
this.history = [];
|
|
643
|
+
this.processHistory.clear();
|
|
644
|
+
// Clear any cached system stats if needed
|
|
645
|
+
logger.info("cluster", "CPU monitoring history cleared");
|
|
646
|
+
}
|
|
647
|
+
/**
|
|
648
|
+
* Get monitoring status
|
|
649
|
+
*/
|
|
650
|
+
isActive() {
|
|
651
|
+
return this.isMonitoring;
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
export { CpuMonitor };
|
|
656
|
+
//# sourceMappingURL=CpuMonitor.js.map
|