aetherframework-cluster 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 +90 -0
- package/README.md +1049 -0
- package/index.js +288 -0
- package/package.json +41 -0
- package/src/core/ClusterManager.js +109 -0
- package/src/core/HealthMonitor.js +571 -0
- package/src/core/LoadBalancer.js +531 -0
- package/src/core/WorkerManager.js +619 -0
- package/src/examples/advanced-cluster.js +150 -0
- package/src/examples/basic-cluster.js +107 -0
- package/src/examples/benchmark-cluster.js +112 -0
- package/src/examples/simple-app.js +52 -0
- package/src/middleware/cluster-health.js +330 -0
- package/src/middleware/graceful-shutdown.js +443 -0
- package/src/middleware/process-monitor.js +925 -0
- package/src/middleware/worker-stats.js +879 -0
- package/src/utils/cpu-detector.js +78 -0
- package/src/utils/env-loader.js +140 -0
- package/src/utils/signal-handler.js +90 -0
|
@@ -0,0 +1,925 @@
|
|
|
1
|
+
// packages/cluster/src/middleware/process-monitor.js
|
|
2
|
+
import { EventEmitter } from 'events';
|
|
3
|
+
import os from 'os';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Process Monitor Middleware - Monitors system and process metrics
|
|
7
|
+
* Provides real-time monitoring of CPU, memory, and process statistics
|
|
8
|
+
*/
|
|
9
|
+
class ProcessMonitorMiddleware extends EventEmitter {
|
|
10
|
+
constructor(options = {}) {
|
|
11
|
+
super();
|
|
12
|
+
|
|
13
|
+
this.options = {
|
|
14
|
+
path: options.path || '/cluster/monitor',
|
|
15
|
+
auth: options.auth || null,
|
|
16
|
+
updateInterval: options.updateInterval || 5000, // 5 seconds
|
|
17
|
+
historySize: options.historySize || 100,
|
|
18
|
+
enableMetrics: options.enableMetrics !== false,
|
|
19
|
+
enableAlerts: options.enableAlerts !== false,
|
|
20
|
+
...options
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
this.metrics = {
|
|
24
|
+
cpu: [],
|
|
25
|
+
memory: [],
|
|
26
|
+
process: [],
|
|
27
|
+
system: []
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
this.alerts = [];
|
|
31
|
+
this.monitoringInterval = null;
|
|
32
|
+
this.lastUpdate = null;
|
|
33
|
+
|
|
34
|
+
// Alert thresholds
|
|
35
|
+
this.thresholds = {
|
|
36
|
+
cpu: options.cpuThreshold || 0.8, // 80%
|
|
37
|
+
memory: options.memoryThreshold || 0.8, // 80%
|
|
38
|
+
heapUsed: options.heapThreshold || 0.9, // 90%
|
|
39
|
+
eventLoopDelay: options.eventLoopThreshold || 100, // 100ms
|
|
40
|
+
...options.thresholds
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
// Initialize monitoring
|
|
44
|
+
if (this.options.enableMetrics) {
|
|
45
|
+
this.startMonitoring();
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Get middleware function for HTTP framework integration
|
|
51
|
+
* @returns {Function} Middleware function
|
|
52
|
+
*/
|
|
53
|
+
middleware() {
|
|
54
|
+
return async (ctx, next) => {
|
|
55
|
+
// Only handle monitor routes
|
|
56
|
+
if (!ctx.path.startsWith(this.options.path)) {
|
|
57
|
+
return next();
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Check authentication if required
|
|
61
|
+
if (this.options.auth && !this.checkAuth(ctx)) {
|
|
62
|
+
ctx.status = 401;
|
|
63
|
+
ctx.body = {
|
|
64
|
+
status: 'error',
|
|
65
|
+
message: 'Unauthorized',
|
|
66
|
+
timestamp: new Date().toISOString()
|
|
67
|
+
};
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
try {
|
|
72
|
+
// Handle different monitor endpoints
|
|
73
|
+
if (ctx.path === this.options.path) {
|
|
74
|
+
await this.handleMonitorOverview(ctx);
|
|
75
|
+
} else if (ctx.path === `${this.options.path}/metrics`) {
|
|
76
|
+
await this.handleMetrics(ctx);
|
|
77
|
+
} else if (ctx.path === `${this.options.path}/metrics/:metric`) {
|
|
78
|
+
await this.handleMetricDetail(ctx);
|
|
79
|
+
} else if (ctx.path === `${this.options.path}/alerts`) {
|
|
80
|
+
await this.handleAlerts(ctx);
|
|
81
|
+
} else if (ctx.path === `${this.options.path}/system`) {
|
|
82
|
+
await this.handleSystemInfo(ctx);
|
|
83
|
+
} else if (ctx.path === `${this.options.path}/process`) {
|
|
84
|
+
await this.handleProcessInfo(ctx);
|
|
85
|
+
} else if (ctx.path === `${this.options.path}/history`) {
|
|
86
|
+
await this.handleHistory(ctx);
|
|
87
|
+
} else if (ctx.path === `${this.options.path}/reset`) {
|
|
88
|
+
await this.handleReset(ctx);
|
|
89
|
+
} else {
|
|
90
|
+
ctx.status = 404;
|
|
91
|
+
ctx.body = {
|
|
92
|
+
status: 'error',
|
|
93
|
+
message: 'Monitor endpoint not found',
|
|
94
|
+
timestamp: new Date().toISOString()
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
} catch (error) {
|
|
98
|
+
console.error('Process monitor middleware error:', error);
|
|
99
|
+
|
|
100
|
+
ctx.status = 500;
|
|
101
|
+
ctx.body = {
|
|
102
|
+
status: 'error',
|
|
103
|
+
timestamp: new Date().toISOString(),
|
|
104
|
+
message: 'Failed to get monitor data',
|
|
105
|
+
error: error.message,
|
|
106
|
+
stack: process.env.NODE_ENV === 'development' ? error.stack : undefined
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Handle monitor overview request
|
|
114
|
+
* @param {Object} ctx - Context object
|
|
115
|
+
*/
|
|
116
|
+
async handleMonitorOverview(ctx) {
|
|
117
|
+
const metrics = this.collectMetrics();
|
|
118
|
+
const alerts = this.getActiveAlerts();
|
|
119
|
+
|
|
120
|
+
ctx.status = 200;
|
|
121
|
+
ctx.body = {
|
|
122
|
+
status: 'success',
|
|
123
|
+
timestamp: new Date().toISOString(),
|
|
124
|
+
overview: {
|
|
125
|
+
cpu: {
|
|
126
|
+
usage: metrics.cpu.usage,
|
|
127
|
+
cores: metrics.cpu.cores,
|
|
128
|
+
loadAverage: metrics.cpu.loadAverage,
|
|
129
|
+
threshold: this.thresholds.cpu,
|
|
130
|
+
status: metrics.cpu.usage > this.thresholds.cpu ? 'warning' : 'normal'
|
|
131
|
+
},
|
|
132
|
+
memory: {
|
|
133
|
+
usage: metrics.memory.usage,
|
|
134
|
+
total: metrics.memory.total,
|
|
135
|
+
free: metrics.memory.free,
|
|
136
|
+
threshold: this.thresholds.memory,
|
|
137
|
+
status: metrics.memory.usage > this.thresholds.memory ? 'warning' : 'normal'
|
|
138
|
+
},
|
|
139
|
+
process: {
|
|
140
|
+
uptime: metrics.process.uptime,
|
|
141
|
+
memory: metrics.process.memory,
|
|
142
|
+
cpu: metrics.process.cpu,
|
|
143
|
+
eventLoop: metrics.process.eventLoop,
|
|
144
|
+
status: this.getProcessStatus(metrics.process)
|
|
145
|
+
},
|
|
146
|
+
system: {
|
|
147
|
+
platform: metrics.system.platform,
|
|
148
|
+
arch: metrics.system.arch,
|
|
149
|
+
uptime: metrics.system.uptime,
|
|
150
|
+
hostname: metrics.system.hostname
|
|
151
|
+
}
|
|
152
|
+
},
|
|
153
|
+
alerts: {
|
|
154
|
+
active: alerts.length,
|
|
155
|
+
critical: alerts.filter(a => a.severity === 'critical').length,
|
|
156
|
+
warning: alerts.filter(a => a.severity === 'warning').length,
|
|
157
|
+
info: alerts.filter(a => a.severity === 'info').length
|
|
158
|
+
},
|
|
159
|
+
history: {
|
|
160
|
+
cpu: this.metrics.cpu.length,
|
|
161
|
+
memory: this.metrics.memory.length,
|
|
162
|
+
process: this.metrics.process.length,
|
|
163
|
+
system: this.metrics.system.length
|
|
164
|
+
}
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Handle metrics request
|
|
170
|
+
* @param {Object} ctx - Context object
|
|
171
|
+
*/
|
|
172
|
+
async handleMetrics(ctx) {
|
|
173
|
+
const query = ctx.query || {};
|
|
174
|
+
const limit = parseInt(query.limit) || 50;
|
|
175
|
+
const offset = parseInt(query.offset) || 0;
|
|
176
|
+
|
|
177
|
+
const metrics = {
|
|
178
|
+
cpu: this.metrics.cpu.slice(-limit - offset).slice(0, limit),
|
|
179
|
+
memory: this.metrics.memory.slice(-limit - offset).slice(0, limit),
|
|
180
|
+
process: this.metrics.process.slice(-limit - offset).slice(0, limit),
|
|
181
|
+
system: this.metrics.system.slice(-limit - offset).slice(0, limit)
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
ctx.status = 200;
|
|
185
|
+
ctx.body = {
|
|
186
|
+
status: 'success',
|
|
187
|
+
timestamp: new Date().toISOString(),
|
|
188
|
+
metrics,
|
|
189
|
+
pagination: {
|
|
190
|
+
limit,
|
|
191
|
+
offset,
|
|
192
|
+
total: {
|
|
193
|
+
cpu: this.metrics.cpu.length,
|
|
194
|
+
memory: this.metrics.memory.length,
|
|
195
|
+
process: this.metrics.process.length,
|
|
196
|
+
system: this.metrics.system.length
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Handle metric detail request
|
|
204
|
+
* @param {Object} ctx - Context object
|
|
205
|
+
*/
|
|
206
|
+
async handleMetricDetail(ctx) {
|
|
207
|
+
const metric = ctx.params.metric;
|
|
208
|
+
const query = ctx.query || {};
|
|
209
|
+
const limit = parseInt(query.limit) || 100;
|
|
210
|
+
const offset = parseInt(query.offset) || 0;
|
|
211
|
+
|
|
212
|
+
if (!['cpu', 'memory', 'process', 'system'].includes(metric)) {
|
|
213
|
+
ctx.status = 400;
|
|
214
|
+
ctx.body = {
|
|
215
|
+
status: 'error',
|
|
216
|
+
message: 'Invalid metric type',
|
|
217
|
+
timestamp: new Date().toISOString(),
|
|
218
|
+
validMetrics: ['cpu', 'memory', 'process', 'system']
|
|
219
|
+
};
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
const metricData = this.metrics[metric];
|
|
224
|
+
const data = metricData.slice(-limit - offset).slice(0, limit);
|
|
225
|
+
|
|
226
|
+
ctx.status = 200;
|
|
227
|
+
ctx.body = {
|
|
228
|
+
status: 'success',
|
|
229
|
+
timestamp: new Date().toISOString(),
|
|
230
|
+
metric,
|
|
231
|
+
data,
|
|
232
|
+
summary: this.getMetricSummary(metric, data),
|
|
233
|
+
pagination: {
|
|
234
|
+
limit,
|
|
235
|
+
offset,
|
|
236
|
+
total: metricData.length
|
|
237
|
+
}
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* Handle alerts request
|
|
243
|
+
* @param {Object} ctx - Context object
|
|
244
|
+
*/
|
|
245
|
+
async handleAlerts(ctx) {
|
|
246
|
+
const query = ctx.query || {};
|
|
247
|
+
const severity = query.severity;
|
|
248
|
+
const resolved = query.resolved === 'true';
|
|
249
|
+
const limit = parseInt(query.limit) || 50;
|
|
250
|
+
const offset = parseInt(query.offset) || 0;
|
|
251
|
+
|
|
252
|
+
let alerts = this.alerts;
|
|
253
|
+
|
|
254
|
+
// Filter by severity
|
|
255
|
+
if (severity) {
|
|
256
|
+
alerts = alerts.filter(alert => alert.severity === severity);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// Filter by resolved status
|
|
260
|
+
alerts = alerts.filter(alert => alert.resolved === resolved);
|
|
261
|
+
|
|
262
|
+
// Apply pagination
|
|
263
|
+
const paginatedAlerts = alerts.slice(-limit - offset).slice(0, limit);
|
|
264
|
+
|
|
265
|
+
ctx.status = 200;
|
|
266
|
+
ctx.body = {
|
|
267
|
+
status: 'success',
|
|
268
|
+
timestamp: new Date().toISOString(),
|
|
269
|
+
alerts: paginatedAlerts,
|
|
270
|
+
summary: {
|
|
271
|
+
total: this.alerts.length,
|
|
272
|
+
active: this.alerts.filter(a => !a.resolved).length,
|
|
273
|
+
critical: this.alerts.filter(a => a.severity === 'critical' && !a.resolved).length,
|
|
274
|
+
warning: this.alerts.filter(a => a.severity === 'warning' && !a.resolved).length,
|
|
275
|
+
info: this.alerts.filter(a => a.severity === 'info' && !a.resolved).length
|
|
276
|
+
},
|
|
277
|
+
pagination: {
|
|
278
|
+
limit,
|
|
279
|
+
offset,
|
|
280
|
+
total: alerts.length
|
|
281
|
+
}
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* Handle system info request
|
|
287
|
+
* @param {Object} ctx - Context object
|
|
288
|
+
*/
|
|
289
|
+
async handleSystemInfo(ctx) {
|
|
290
|
+
const systemInfo = this.getSystemInfo();
|
|
291
|
+
|
|
292
|
+
ctx.status = 200;
|
|
293
|
+
ctx.body = {
|
|
294
|
+
status: 'success',
|
|
295
|
+
timestamp: new Date().toISOString(),
|
|
296
|
+
system: systemInfo
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* Handle process info request
|
|
302
|
+
* @param {Object} ctx - Context object
|
|
303
|
+
*/
|
|
304
|
+
async handleProcessInfo(ctx) {
|
|
305
|
+
const processInfo = this.getProcessInfo();
|
|
306
|
+
|
|
307
|
+
ctx.status = 200;
|
|
308
|
+
ctx.body = {
|
|
309
|
+
status: 'success',
|
|
310
|
+
timestamp: new Date().toISOString(),
|
|
311
|
+
process: processInfo
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
/**
|
|
316
|
+
* Handle history request
|
|
317
|
+
* @param {Object} ctx - Context object
|
|
318
|
+
*/
|
|
319
|
+
async handleHistory(ctx) {
|
|
320
|
+
const query = ctx.query || {};
|
|
321
|
+
const duration = parseInt(query.duration) || 3600000; // 1 hour in milliseconds
|
|
322
|
+
const now = Date.now();
|
|
323
|
+
|
|
324
|
+
const filteredMetrics = {
|
|
325
|
+
cpu: this.metrics.cpu.filter(m => now - m.timestamp <= duration),
|
|
326
|
+
memory: this.metrics.memory.filter(m => now - m.timestamp <= duration),
|
|
327
|
+
process: this.metrics.process.filter(m => now - m.timestamp <= duration),
|
|
328
|
+
system: this.metrics.system.filter(m => now - m.timestamp <= duration)
|
|
329
|
+
};
|
|
330
|
+
|
|
331
|
+
ctx.status = 200;
|
|
332
|
+
ctx.body = {
|
|
333
|
+
status: 'success',
|
|
334
|
+
timestamp: new Date().toISOString(),
|
|
335
|
+
duration,
|
|
336
|
+
metrics: filteredMetrics,
|
|
337
|
+
summary: {
|
|
338
|
+
cpu: this.getMetricSummary('cpu', filteredMetrics.cpu),
|
|
339
|
+
memory: this.getMetricSummary('memory', filteredMetrics.memory),
|
|
340
|
+
process: this.getMetricSummary('process', filteredMetrics.process)
|
|
341
|
+
}
|
|
342
|
+
};
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
/**
|
|
346
|
+
* Handle reset request
|
|
347
|
+
* @param {Object} ctx - Context object
|
|
348
|
+
*/
|
|
349
|
+
async handleReset(ctx) {
|
|
350
|
+
// Check authentication if required
|
|
351
|
+
if (this.options.auth && !this.checkAuth(ctx)) {
|
|
352
|
+
ctx.status = 401;
|
|
353
|
+
ctx.body = {
|
|
354
|
+
status: 'error',
|
|
355
|
+
message: 'Unauthorized',
|
|
356
|
+
timestamp: new Date().toISOString()
|
|
357
|
+
};
|
|
358
|
+
return;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
this.resetMetrics();
|
|
362
|
+
|
|
363
|
+
ctx.status = 200;
|
|
364
|
+
ctx.body = {
|
|
365
|
+
status: 'success',
|
|
366
|
+
message: 'Metrics reset successfully',
|
|
367
|
+
timestamp: new Date().toISOString()
|
|
368
|
+
};
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
/**
|
|
372
|
+
* Start monitoring
|
|
373
|
+
*/
|
|
374
|
+
startMonitoring() {
|
|
375
|
+
if (this.monitoringInterval) {
|
|
376
|
+
console.warn('Process monitor already started');
|
|
377
|
+
return;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
console.log('🚀 Starting process monitor...');
|
|
381
|
+
|
|
382
|
+
// Collect initial metrics
|
|
383
|
+
this.collectAndStoreMetrics();
|
|
384
|
+
|
|
385
|
+
// Start periodic monitoring
|
|
386
|
+
this.monitoringInterval = setInterval(() => {
|
|
387
|
+
this.collectAndStoreMetrics();
|
|
388
|
+
}, this.options.updateInterval);
|
|
389
|
+
|
|
390
|
+
console.log(`✅ Process monitor started with ${this.options.updateInterval}ms interval`);
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
/**
|
|
394
|
+
* Stop monitoring
|
|
395
|
+
*/
|
|
396
|
+
stopMonitoring() {
|
|
397
|
+
if (this.monitoringInterval) {
|
|
398
|
+
clearInterval(this.monitoringInterval);
|
|
399
|
+
this.monitoringInterval = null;
|
|
400
|
+
console.log('🛑 Process monitor stopped');
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
/**
|
|
405
|
+
* Collect and store metrics
|
|
406
|
+
*/
|
|
407
|
+
collectAndStoreMetrics() {
|
|
408
|
+
try {
|
|
409
|
+
const metrics = this.collectMetrics();
|
|
410
|
+
const timestamp = Date.now();
|
|
411
|
+
|
|
412
|
+
// Store metrics
|
|
413
|
+
this.metrics.cpu.push({
|
|
414
|
+
timestamp,
|
|
415
|
+
usage: metrics.cpu.usage,
|
|
416
|
+
loadAverage: metrics.cpu.loadAverage,
|
|
417
|
+
cores: metrics.cpu.cores
|
|
418
|
+
});
|
|
419
|
+
|
|
420
|
+
this.metrics.memory.push({
|
|
421
|
+
timestamp,
|
|
422
|
+
usage: metrics.memory.usage,
|
|
423
|
+
total: metrics.memory.total,
|
|
424
|
+
free: metrics.memory.free,
|
|
425
|
+
used: metrics.memory.used
|
|
426
|
+
});
|
|
427
|
+
|
|
428
|
+
this.metrics.process.push({
|
|
429
|
+
timestamp,
|
|
430
|
+
memory: metrics.process.memory,
|
|
431
|
+
cpu: metrics.process.cpu,
|
|
432
|
+
eventLoop: metrics.process.eventLoop,
|
|
433
|
+
uptime: metrics.process.uptime
|
|
434
|
+
});
|
|
435
|
+
|
|
436
|
+
this.metrics.system.push({
|
|
437
|
+
timestamp,
|
|
438
|
+
platform: metrics.system.platform,
|
|
439
|
+
arch: metrics.system.arch,
|
|
440
|
+
uptime: metrics.system.uptime,
|
|
441
|
+
hostname: metrics.system.hostname
|
|
442
|
+
});
|
|
443
|
+
|
|
444
|
+
// Limit history size
|
|
445
|
+
this.limitHistory();
|
|
446
|
+
|
|
447
|
+
// Check for alerts
|
|
448
|
+
this.checkAlerts(metrics);
|
|
449
|
+
|
|
450
|
+
this.lastUpdate = timestamp;
|
|
451
|
+
this.emit('metrics:collected', { metrics, timestamp });
|
|
452
|
+
|
|
453
|
+
} catch (error) {
|
|
454
|
+
console.error('Error collecting metrics:', error);
|
|
455
|
+
this.emit('metrics:error', { error });
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
/**
|
|
460
|
+
* Collect current metrics
|
|
461
|
+
* @returns {Object} Collected metrics
|
|
462
|
+
*/
|
|
463
|
+
collectMetrics() {
|
|
464
|
+
const cpuUsage = this.getCpuUsage();
|
|
465
|
+
const memoryUsage = this.getMemoryUsage();
|
|
466
|
+
const processInfo = this.getProcessInfo();
|
|
467
|
+
const systemInfo = this.getSystemInfo();
|
|
468
|
+
|
|
469
|
+
return {
|
|
470
|
+
cpu: {
|
|
471
|
+
usage: cpuUsage,
|
|
472
|
+
loadAverage: os.loadavg(),
|
|
473
|
+
cores: os.cpus().length,
|
|
474
|
+
timestamp: Date.now()
|
|
475
|
+
},
|
|
476
|
+
memory: {
|
|
477
|
+
usage: memoryUsage.usage,
|
|
478
|
+
total: memoryUsage.total,
|
|
479
|
+
free: memoryUsage.free,
|
|
480
|
+
used: memoryUsage.used,
|
|
481
|
+
timestamp: Date.now()
|
|
482
|
+
},
|
|
483
|
+
process: {
|
|
484
|
+
memory: processInfo.memory,
|
|
485
|
+
cpu: processInfo.cpu,
|
|
486
|
+
eventLoop: processInfo.eventLoop,
|
|
487
|
+
uptime: processInfo.uptime,
|
|
488
|
+
timestamp: Date.now()
|
|
489
|
+
},
|
|
490
|
+
system: {
|
|
491
|
+
platform: systemInfo.platform,
|
|
492
|
+
arch: systemInfo.arch,
|
|
493
|
+
uptime: systemInfo.uptime,
|
|
494
|
+
hostname: systemInfo.hostname,
|
|
495
|
+
timestamp: Date.now()
|
|
496
|
+
}
|
|
497
|
+
};
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
/**
|
|
501
|
+
* Get CPU usage
|
|
502
|
+
* @returns {number} CPU usage percentage
|
|
503
|
+
*/
|
|
504
|
+
getCpuUsage() {
|
|
505
|
+
const cpus = os.cpus();
|
|
506
|
+
let totalIdle = 0;
|
|
507
|
+
let totalTick = 0;
|
|
508
|
+
|
|
509
|
+
cpus.forEach(cpu => {
|
|
510
|
+
for (const type in cpu.times) {
|
|
511
|
+
totalTick += cpu.times[type];
|
|
512
|
+
}
|
|
513
|
+
totalIdle += cpu.times.idle;
|
|
514
|
+
});
|
|
515
|
+
|
|
516
|
+
return 1 - (totalIdle / totalTick);
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
/**
|
|
520
|
+
* Get memory usage
|
|
521
|
+
* @returns {Object} Memory usage information
|
|
522
|
+
*/
|
|
523
|
+
getMemoryUsage() {
|
|
524
|
+
const total = os.totalmem();
|
|
525
|
+
const free = os.freemem();
|
|
526
|
+
const used = total - free;
|
|
527
|
+
const usage = used / total;
|
|
528
|
+
|
|
529
|
+
return {
|
|
530
|
+
total,
|
|
531
|
+
free,
|
|
532
|
+
used,
|
|
533
|
+
usage,
|
|
534
|
+
totalGB: (total / 1024 / 1024 / 1024).toFixed(2),
|
|
535
|
+
freeGB: (free / 1024 / 1024 / 1024).toFixed(2),
|
|
536
|
+
usedGB: (used / 1024 / 1024 / 1024).toFixed(2),
|
|
537
|
+
usagePercent: (usage * 100).toFixed(2)
|
|
538
|
+
};
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
/**
|
|
542
|
+
* Get process information
|
|
543
|
+
* @returns {Object} Process information
|
|
544
|
+
*/
|
|
545
|
+
getProcessInfo() {
|
|
546
|
+
const memoryUsage = process.memoryUsage();
|
|
547
|
+
const cpuUsage = process.cpuUsage();
|
|
548
|
+
const eventLoop = this.getEventLoopDelay();
|
|
549
|
+
|
|
550
|
+
return {
|
|
551
|
+
pid: process.pid,
|
|
552
|
+
uptime: process.uptime(),
|
|
553
|
+
memory: {
|
|
554
|
+
rss: memoryUsage.rss,
|
|
555
|
+
heapTotal: memoryUsage.heapTotal,
|
|
556
|
+
heapUsed: memoryUsage.heapUsed,
|
|
557
|
+
external: memoryUsage.external,
|
|
558
|
+
arrayBuffers: memoryUsage.arrayBuffers,
|
|
559
|
+
rssMB: (memoryUsage.rss / 1024 / 1024).toFixed(2),
|
|
560
|
+
heapUsedMB: (memoryUsage.heapUsed / 1024 / 1024).toFixed(2),
|
|
561
|
+
heapTotalMB: (memoryUsage.heapTotal / 1024 / 1024).toFixed(2)
|
|
562
|
+
},
|
|
563
|
+
cpu: {
|
|
564
|
+
user: cpuUsage.user,
|
|
565
|
+
system: cpuUsage.system,
|
|
566
|
+
userMS: (cpuUsage.user / 1000).toFixed(2),
|
|
567
|
+
systemMS: (cpuUsage.system / 1000).toFixed(2)
|
|
568
|
+
},
|
|
569
|
+
eventLoop: {
|
|
570
|
+
delay: eventLoop,
|
|
571
|
+
threshold: this.thresholds.eventLoopDelay,
|
|
572
|
+
status: eventLoop > this.thresholds.eventLoopDelay ? 'slow' : 'normal'
|
|
573
|
+
},
|
|
574
|
+
versions: process.versions,
|
|
575
|
+
argv: process.argv,
|
|
576
|
+
execArgv: process.execArgv,
|
|
577
|
+
env: process.env.NODE_ENV || 'development'
|
|
578
|
+
};
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
/**
|
|
582
|
+
* Get system information
|
|
583
|
+
* @returns {Object} System information
|
|
584
|
+
*/
|
|
585
|
+
getSystemInfo() {
|
|
586
|
+
return {
|
|
587
|
+
platform: os.platform(),
|
|
588
|
+
arch: os.arch(),
|
|
589
|
+
release: os.release(),
|
|
590
|
+
type: os.type(),
|
|
591
|
+
uptime: os.uptime(),
|
|
592
|
+
hostname: os.hostname(),
|
|
593
|
+
cpus: os.cpus().length,
|
|
594
|
+
totalMemory: os.totalmem(),
|
|
595
|
+
freeMemory: os.freemem(),
|
|
596
|
+
loadAverage: os.loadavg(),
|
|
597
|
+
networkInterfaces: os.networkInterfaces()
|
|
598
|
+
};
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
/**
|
|
602
|
+
* Get event loop delay
|
|
603
|
+
* @returns {number} Event loop delay in milliseconds
|
|
604
|
+
*/
|
|
605
|
+
getEventLoopDelay() {
|
|
606
|
+
const start = process.hrtime.bigint();
|
|
607
|
+
// Do a small synchronous operation
|
|
608
|
+
for (let i = 0; i < 1000000; i++) {}
|
|
609
|
+
const end = process.hrtime.bigint();
|
|
610
|
+
|
|
611
|
+
// Convert nanoseconds to milliseconds
|
|
612
|
+
return Number(end - start) / 1000000;
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
/**
|
|
616
|
+
* Check for alerts based on metrics
|
|
617
|
+
* @param {Object} metrics - Current metrics
|
|
618
|
+
*/
|
|
619
|
+
checkAlerts(metrics) {
|
|
620
|
+
const alerts = [];
|
|
621
|
+
|
|
622
|
+
// Check CPU threshold
|
|
623
|
+
if (metrics.cpu.usage > this.thresholds.cpu) {
|
|
624
|
+
alerts.push({
|
|
625
|
+
type: 'cpu',
|
|
626
|
+
severity: 'warning',
|
|
627
|
+
message: `High CPU usage: ${(metrics.cpu.usage * 100).toFixed(2)}%`,
|
|
628
|
+
threshold: this.thresholds.cpu,
|
|
629
|
+
value: metrics.cpu.usage,
|
|
630
|
+
timestamp: Date.now(),
|
|
631
|
+
resolved: false
|
|
632
|
+
});
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
// Check memory threshold
|
|
636
|
+
if (metrics.memory.usage > this.thresholds.memory) {
|
|
637
|
+
alerts.push({
|
|
638
|
+
type: 'memory',
|
|
639
|
+
severity: 'warning',
|
|
640
|
+
message: `High memory usage: ${(metrics.memory.usage * 100).toFixed(2)}%`,
|
|
641
|
+
threshold: this.thresholds.memory,
|
|
642
|
+
value: metrics.memory.usage,
|
|
643
|
+
timestamp: Date.now(),
|
|
644
|
+
resolved: false
|
|
645
|
+
});
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
// Check heap usage threshold
|
|
649
|
+
const heapUsage = metrics.process.memory.heapUsed / metrics.process.memory.heapTotal;
|
|
650
|
+
if (heapUsage > this.thresholds.heapUsed) {
|
|
651
|
+
alerts.push({
|
|
652
|
+
type: 'heap',
|
|
653
|
+
severity: 'critical',
|
|
654
|
+
message: `High heap usage: ${(heapUsage * 100).toFixed(2)}%`,
|
|
655
|
+
threshold: this.thresholds.heapUsed,
|
|
656
|
+
value: heapUsage,
|
|
657
|
+
timestamp: Date.now(),
|
|
658
|
+
resolved: false
|
|
659
|
+
});
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
// Check event loop delay
|
|
663
|
+
if (metrics.process.eventLoop.delay > this.thresholds.eventLoopDelay) {
|
|
664
|
+
alerts.push({
|
|
665
|
+
type: 'eventLoop',
|
|
666
|
+
severity: 'warning',
|
|
667
|
+
message: `High event loop delay: ${metrics.process.eventLoop.delay.toFixed(2)}ms`,
|
|
668
|
+
threshold: this.thresholds.eventLoopDelay,
|
|
669
|
+
value: metrics.process.eventLoop.delay,
|
|
670
|
+
timestamp: Date.now(),
|
|
671
|
+
resolved: false
|
|
672
|
+
});
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
// Add new alerts
|
|
676
|
+
alerts.forEach(alert => {
|
|
677
|
+
this.alerts.push(alert);
|
|
678
|
+
this.emit('alert:created', alert);
|
|
679
|
+
});
|
|
680
|
+
|
|
681
|
+
// Check for resolved alerts
|
|
682
|
+
this.checkResolvedAlerts(metrics);
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
/**
|
|
686
|
+
* Check for resolved alerts
|
|
687
|
+
* @param {Object} metrics - Current metrics
|
|
688
|
+
*/
|
|
689
|
+
checkResolvedAlerts(metrics) {
|
|
690
|
+
const now = Date.now();
|
|
691
|
+
|
|
692
|
+
this.alerts.forEach(alert => {
|
|
693
|
+
if (!alert.resolved) {
|
|
694
|
+
let isResolved = false;
|
|
695
|
+
|
|
696
|
+
switch (alert.type) {
|
|
697
|
+
case 'cpu':
|
|
698
|
+
isResolved = metrics.cpu.usage <= this.thresholds.cpu * 0.8; // Resolved when below 80% of threshold
|
|
699
|
+
break;
|
|
700
|
+
case 'memory':
|
|
701
|
+
isResolved = metrics.memory.usage <= this.thresholds.memory * 0.8;
|
|
702
|
+
break;
|
|
703
|
+
case 'heap':
|
|
704
|
+
const heapUsage = metrics.process.memory.heapUsed / metrics.process.memory.heapTotal;
|
|
705
|
+
isResolved = heapUsage <= this.thresholds.heapUsed * 0.8;
|
|
706
|
+
break;
|
|
707
|
+
case 'eventLoop':
|
|
708
|
+
isResolved = metrics.process.eventLoop.delay <= this.thresholds.eventLoopDelay * 0.8;
|
|
709
|
+
break;
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
if (isResolved) {
|
|
713
|
+
alert.resolved = true;
|
|
714
|
+
alert.resolvedAt = now;
|
|
715
|
+
alert.resolutionTime = now - alert.timestamp;
|
|
716
|
+
this.emit('alert:resolved', alert);
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
});
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
/**
|
|
723
|
+
* Get active alerts
|
|
724
|
+
* @returns {Array} Active alerts
|
|
725
|
+
*/
|
|
726
|
+
getActiveAlerts() {
|
|
727
|
+
return this.alerts.filter(alert => !alert.resolved);
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
/**
|
|
731
|
+
* Get process status
|
|
732
|
+
* @param {Object} processInfo - Process information
|
|
733
|
+
* @returns {string} Process status
|
|
734
|
+
*/
|
|
735
|
+
getProcessStatus(processInfo) {
|
|
736
|
+
const heapUsage = processInfo.memory.heapUsed / processInfo.memory.heapTotal;
|
|
737
|
+
|
|
738
|
+
if (heapUsage > this.thresholds.heapUsed) {
|
|
739
|
+
return 'critical';
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
if (processInfo.eventLoop.delay > this.thresholds.eventLoopDelay) {
|
|
743
|
+
return 'warning';
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
return 'normal';
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
/**
|
|
750
|
+
* Get metric summary
|
|
751
|
+
* @param {string} metric - Metric type
|
|
752
|
+
* @param {Array} data - Metric data
|
|
753
|
+
* @returns {Object} Metric summary
|
|
754
|
+
*/
|
|
755
|
+
getMetricSummary(metric, data) {
|
|
756
|
+
if (data.length === 0) {
|
|
757
|
+
return {
|
|
758
|
+
count: 0,
|
|
759
|
+
average: 0,
|
|
760
|
+
min: 0,
|
|
761
|
+
max: 0,
|
|
762
|
+
latest: null
|
|
763
|
+
};
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
let values = [];
|
|
767
|
+
|
|
768
|
+
switch (metric) {
|
|
769
|
+
case 'cpu':
|
|
770
|
+
values = data.map(d => d.usage);
|
|
771
|
+
break;
|
|
772
|
+
case 'memory':
|
|
773
|
+
values = data.map(d => d.usage);
|
|
774
|
+
break;
|
|
775
|
+
case 'process':
|
|
776
|
+
values = data.map(d => d.memory.heapUsed / d.memory.heapTotal);
|
|
777
|
+
break;
|
|
778
|
+
default:
|
|
779
|
+
values = [];
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
const sum = values.reduce((a, b) => a + b, 0);
|
|
783
|
+
const average = sum / values.length;
|
|
784
|
+
const min = Math.min(...values);
|
|
785
|
+
const max = Math.max(...values);
|
|
786
|
+
const latest = data[data.length - 1];
|
|
787
|
+
|
|
788
|
+
return {
|
|
789
|
+
count: data.length,
|
|
790
|
+
average: average.toFixed(4),
|
|
791
|
+
min: min.toFixed(4),
|
|
792
|
+
max: max.toFixed(4),
|
|
793
|
+
latest: latest ? {
|
|
794
|
+
value: values[values.length - 1],
|
|
795
|
+
timestamp: latest.timestamp
|
|
796
|
+
} : null,
|
|
797
|
+
trend: this.getTrend(values)
|
|
798
|
+
};
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
/**
|
|
802
|
+
* Get trend from values
|
|
803
|
+
* @param {Array} values - Array of values
|
|
804
|
+
* @returns {string} Trend (up, down, stable)
|
|
805
|
+
*/
|
|
806
|
+
getTrend(values) {
|
|
807
|
+
if (values.length < 2) {
|
|
808
|
+
return 'stable';
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
const recent = values.slice(-5); // Last 5 values
|
|
812
|
+
const first = recent[0];
|
|
813
|
+
const last = recent[recent.length - 1];
|
|
814
|
+
|
|
815
|
+
if (last > first * 1.1) {
|
|
816
|
+
return 'up';
|
|
817
|
+
} else if (last < first * 0.9) {
|
|
818
|
+
return 'down';
|
|
819
|
+
} else {
|
|
820
|
+
return 'stable';
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
/**
|
|
825
|
+
* Limit history size
|
|
826
|
+
*/
|
|
827
|
+
limitHistory() {
|
|
828
|
+
if (this.metrics.cpu.length > this.options.historySize) {
|
|
829
|
+
this.metrics.cpu = this.metrics.cpu.slice(-this.options.historySize);
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
if (this.metrics.memory.length > this.options.historySize) {
|
|
833
|
+
this.metrics.memory = this.metrics.memory.slice(-this.options.historySize);
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
if (this.metrics.process.length > this.options.historySize) {
|
|
837
|
+
this.metrics.process = this.metrics.process.slice(-this.options.historySize);
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
if (this.metrics.system.length > this.options.historySize) {
|
|
841
|
+
this.metrics.system = this.metrics.system.slice(-this.options.historySize);
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
if (this.alerts.length > this.options.historySize * 2) {
|
|
845
|
+
this.alerts = this.alerts.slice(-this.options.historySize * 2);
|
|
846
|
+
}
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
/**
|
|
850
|
+
* Reset metrics
|
|
851
|
+
*/
|
|
852
|
+
resetMetrics() {
|
|
853
|
+
this.metrics = {
|
|
854
|
+
cpu: [],
|
|
855
|
+
memory: [],
|
|
856
|
+
process: [],
|
|
857
|
+
system: []
|
|
858
|
+
};
|
|
859
|
+
|
|
860
|
+
this.alerts = [];
|
|
861
|
+
this.lastUpdate = null;
|
|
862
|
+
|
|
863
|
+
console.log('🔄 Process monitor metrics reset');
|
|
864
|
+
this.emit('metrics:reset');
|
|
865
|
+
}
|
|
866
|
+
|
|
867
|
+
/**
|
|
868
|
+
* Check authentication
|
|
869
|
+
* @param {Object} ctx - Context object
|
|
870
|
+
* @returns {boolean} Authentication status
|
|
871
|
+
*/
|
|
872
|
+
checkAuth(ctx) {
|
|
873
|
+
if (typeof this.options.auth === 'function') {
|
|
874
|
+
return this.options.auth(ctx);
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
if (typeof this.options.auth === 'object') {
|
|
878
|
+
// Check API key
|
|
879
|
+
if (this.options.auth.apiKey) {
|
|
880
|
+
const apiKey = ctx.headers['x-api-key'] || ctx.query.apiKey;
|
|
881
|
+
return apiKey === this.options.auth.apiKey;
|
|
882
|
+
}
|
|
883
|
+
|
|
884
|
+
// Check basic auth
|
|
885
|
+
if (this.options.auth.username && this.options.auth.password) {
|
|
886
|
+
const authHeader = ctx.headers.authorization;
|
|
887
|
+
if (!authHeader || !authHeader.startsWith('Basic ')) {
|
|
888
|
+
return false;
|
|
889
|
+
}
|
|
890
|
+
|
|
891
|
+
const credentials = Buffer.from(authHeader.slice(6), 'base64').toString();
|
|
892
|
+
const [username, password] = credentials.split(':');
|
|
893
|
+
return username === this.options.auth.username && password === this.options.auth.password;
|
|
894
|
+
}
|
|
895
|
+
}
|
|
896
|
+
|
|
897
|
+
return true; // No auth required
|
|
898
|
+
}
|
|
899
|
+
|
|
900
|
+
/**
|
|
901
|
+
* Get monitoring configuration
|
|
902
|
+
* @returns {Object} Monitoring configuration
|
|
903
|
+
*/
|
|
904
|
+
getConfig() {
|
|
905
|
+
return {
|
|
906
|
+
...this.options,
|
|
907
|
+
thresholds: this.thresholds,
|
|
908
|
+
isMonitoring: !!this.monitoringInterval,
|
|
909
|
+
lastUpdate: this.lastUpdate,
|
|
910
|
+
metricsCount: {
|
|
911
|
+
cpu: this.metrics.cpu.length,
|
|
912
|
+
memory: this.metrics.memory.length,
|
|
913
|
+
process: this.metrics.process.length,
|
|
914
|
+
system: this.metrics.system.length
|
|
915
|
+
},
|
|
916
|
+
alertsCount: {
|
|
917
|
+
total: this.alerts.length,
|
|
918
|
+
active: this.getActiveAlerts().length,
|
|
919
|
+
resolved: this.alerts.filter(a => a.resolved).length
|
|
920
|
+
}
|
|
921
|
+
};
|
|
922
|
+
}
|
|
923
|
+
}
|
|
924
|
+
|
|
925
|
+
export default ProcessMonitorMiddleware;
|