@gazzehamine/armada-watch-agent 1.4.2 → 1.4.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/collector.js +204 -0
- package/dist/index.js +8 -2
- package/package.json +2 -2
package/dist/collector.js
CHANGED
|
@@ -7,8 +7,10 @@ exports.getSystemInfo = getSystemInfo;
|
|
|
7
7
|
exports.collectMetrics = collectMetrics;
|
|
8
8
|
exports.collectProcesses = collectProcesses;
|
|
9
9
|
exports.collectDockerContainers = collectDockerContainers;
|
|
10
|
+
exports.collectNginxMetrics = collectNginxMetrics;
|
|
10
11
|
exports.collectPM2Processes = collectPM2Processes;
|
|
11
12
|
exports.collectSSLCertificates = collectSSLCertificates;
|
|
13
|
+
exports.collectSystemdServices = collectSystemdServices;
|
|
12
14
|
const systeminformation_1 = __importDefault(require("systeminformation"));
|
|
13
15
|
const os_1 = __importDefault(require("os"));
|
|
14
16
|
const fs_1 = __importDefault(require("fs"));
|
|
@@ -263,6 +265,62 @@ async function getCertificateInfo(certPath, domain) {
|
|
|
263
265
|
return null;
|
|
264
266
|
}
|
|
265
267
|
}
|
|
268
|
+
/**
|
|
269
|
+
* Collect Nginx metrics from stub_status
|
|
270
|
+
*/
|
|
271
|
+
async function collectNginxMetrics() {
|
|
272
|
+
try {
|
|
273
|
+
// Try common nginx stub_status URLs
|
|
274
|
+
const urls = [
|
|
275
|
+
'http://localhost/nginx_status',
|
|
276
|
+
'http://localhost:80/nginx_status',
|
|
277
|
+
'http://127.0.0.1/nginx_status',
|
|
278
|
+
'http://localhost/status',
|
|
279
|
+
];
|
|
280
|
+
for (const url of urls) {
|
|
281
|
+
try {
|
|
282
|
+
const { stdout } = await execAsync(`curl -s ${url} --max-time 2`, { timeout: 3000 });
|
|
283
|
+
if (stdout && stdout.includes('Active connections')) {
|
|
284
|
+
// Parse nginx stub_status output
|
|
285
|
+
// Example:
|
|
286
|
+
// Active connections: 2
|
|
287
|
+
// server accepts handled requests
|
|
288
|
+
// 123 123 456
|
|
289
|
+
// Reading: 0 Writing: 1 Waiting: 1
|
|
290
|
+
const activeMatch = stdout.match(/Active connections:\s+(\d+)/);
|
|
291
|
+
const serverMatch = stdout.match(/\s+(\d+)\s+(\d+)\s+(\d+)/);
|
|
292
|
+
const rwwMatch = stdout.match(/Reading:\s+(\d+)\s+Writing:\s+(\d+)\s+Waiting:\s+(\d+)/);
|
|
293
|
+
if (activeMatch && serverMatch && rwwMatch) {
|
|
294
|
+
const accepts = parseInt(serverMatch[1]);
|
|
295
|
+
const handled = parseInt(serverMatch[2]);
|
|
296
|
+
const requests = parseInt(serverMatch[3]);
|
|
297
|
+
// Calculate per-second rates (will be calculated from deltas in the backend)
|
|
298
|
+
return {
|
|
299
|
+
activeConnections: parseInt(activeMatch[1]),
|
|
300
|
+
accepts,
|
|
301
|
+
handled,
|
|
302
|
+
requests,
|
|
303
|
+
reading: parseInt(rwwMatch[1]),
|
|
304
|
+
writing: parseInt(rwwMatch[2]),
|
|
305
|
+
waiting: parseInt(rwwMatch[3]),
|
|
306
|
+
requestsPerSecond: 0, // Will be calculated from deltas
|
|
307
|
+
connectionsPerSecond: 0, // Will be calculated from deltas
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
catch (error) {
|
|
313
|
+
// Try next URL
|
|
314
|
+
continue;
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
// Nginx not available or stub_status not configured
|
|
318
|
+
return null;
|
|
319
|
+
}
|
|
320
|
+
catch (error) {
|
|
321
|
+
return null;
|
|
322
|
+
}
|
|
323
|
+
}
|
|
266
324
|
/**
|
|
267
325
|
* Collect PM2 process information
|
|
268
326
|
*/
|
|
@@ -326,3 +384,149 @@ async function collectSSLCertificates() {
|
|
|
326
384
|
return certificates;
|
|
327
385
|
}
|
|
328
386
|
}
|
|
387
|
+
/**
|
|
388
|
+
* Collect systemd service information with auto-discovery
|
|
389
|
+
*/
|
|
390
|
+
async function collectSystemdServices() {
|
|
391
|
+
try {
|
|
392
|
+
// Check if systemd is available
|
|
393
|
+
try {
|
|
394
|
+
await execAsync('which systemctl');
|
|
395
|
+
}
|
|
396
|
+
catch (error) {
|
|
397
|
+
// systemd not available (e.g., not a Linux system or using a different init system)
|
|
398
|
+
return [];
|
|
399
|
+
}
|
|
400
|
+
const services = [];
|
|
401
|
+
// Common services to monitor
|
|
402
|
+
const COMMON_SERVICES = [
|
|
403
|
+
'nginx', 'apache2', 'httpd', // Web servers
|
|
404
|
+
'docker', 'containerd', // Containers
|
|
405
|
+
'postgresql', 'mysql', 'mariadb', // Databases
|
|
406
|
+
'mongodb', 'redis', 'memcached',
|
|
407
|
+
'ssh', 'sshd', // System services
|
|
408
|
+
];
|
|
409
|
+
// Get all PM2 services (pm2-*)
|
|
410
|
+
let pm2Services = [];
|
|
411
|
+
try {
|
|
412
|
+
const { stdout } = await execAsync('systemctl list-units --type=service --all --no-pager --no-legend | grep "pm2-"');
|
|
413
|
+
if (stdout) {
|
|
414
|
+
pm2Services = stdout
|
|
415
|
+
.split('\n')
|
|
416
|
+
.filter(line => line.trim())
|
|
417
|
+
.map(line => line.split(/\s+/)[0].replace('.service', ''));
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
catch (error) {
|
|
421
|
+
// No PM2 services or error listing them
|
|
422
|
+
}
|
|
423
|
+
// Get all failed services
|
|
424
|
+
let failedServices = [];
|
|
425
|
+
try {
|
|
426
|
+
const { stdout } = await execAsync('systemctl list-units --type=service --state=failed --no-pager --no-legend');
|
|
427
|
+
if (stdout) {
|
|
428
|
+
failedServices = stdout
|
|
429
|
+
.split('\n')
|
|
430
|
+
.filter(line => line.trim())
|
|
431
|
+
.map(line => line.split(/\s+/)[0].replace('.service', ''));
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
catch (error) {
|
|
435
|
+
// No failed services or error listing them
|
|
436
|
+
}
|
|
437
|
+
// Combine all services to monitor (remove duplicates)
|
|
438
|
+
const servicesToMonitor = Array.from(new Set([...COMMON_SERVICES, ...pm2Services, ...failedServices]));
|
|
439
|
+
// Collect info for each service
|
|
440
|
+
for (const serviceName of servicesToMonitor) {
|
|
441
|
+
try {
|
|
442
|
+
const serviceInfo = await getServiceInfo(serviceName);
|
|
443
|
+
if (serviceInfo) {
|
|
444
|
+
services.push(serviceInfo);
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
catch (error) {
|
|
448
|
+
// Service doesn't exist or error getting info, skip it
|
|
449
|
+
continue;
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
return services;
|
|
453
|
+
}
|
|
454
|
+
catch (error) {
|
|
455
|
+
console.error('Error collecting systemd services:', error);
|
|
456
|
+
return [];
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
/**
|
|
460
|
+
* Get detailed information for a single systemd service
|
|
461
|
+
*/
|
|
462
|
+
async function getServiceInfo(serviceName) {
|
|
463
|
+
try {
|
|
464
|
+
// Get detailed service properties
|
|
465
|
+
const { stdout } = await execAsync(`systemctl show ${serviceName}.service --no-pager`, { timeout: 5000 });
|
|
466
|
+
// Parse the output into key-value pairs
|
|
467
|
+
const props = {};
|
|
468
|
+
stdout.split('\n').forEach(line => {
|
|
469
|
+
const [key, ...valueParts] = line.split('=');
|
|
470
|
+
if (key && valueParts.length > 0) {
|
|
471
|
+
props[key.trim()] = valueParts.join('=').trim();
|
|
472
|
+
}
|
|
473
|
+
});
|
|
474
|
+
// Check if service exists
|
|
475
|
+
if (props.LoadState === 'not-found') {
|
|
476
|
+
return null;
|
|
477
|
+
}
|
|
478
|
+
// Extract service information
|
|
479
|
+
const activeState = props.ActiveState || 'unknown';
|
|
480
|
+
const subState = props.SubState || 'unknown';
|
|
481
|
+
const unitFileState = props.UnitFileState || 'unknown';
|
|
482
|
+
const enabled = ['enabled', 'enabled-runtime', 'static'].includes(unitFileState);
|
|
483
|
+
// Parse timestamps
|
|
484
|
+
const activeEnterTimestamp = props.ActiveEnterTimestamp ? new Date(props.ActiveEnterTimestamp).getTime() : 0;
|
|
485
|
+
const now = Date.now();
|
|
486
|
+
// Calculate uptime (in seconds)
|
|
487
|
+
let uptime = 0;
|
|
488
|
+
if (activeState === 'active' && activeEnterTimestamp > 0) {
|
|
489
|
+
uptime = Math.floor((now - activeEnterTimestamp) / 1000);
|
|
490
|
+
}
|
|
491
|
+
// Get restart count
|
|
492
|
+
const restartCount = parseInt(props.NRestarts || '0', 10);
|
|
493
|
+
// Get main PID
|
|
494
|
+
const mainPID = parseInt(props.MainPID || '0', 10);
|
|
495
|
+
// Get CPU and Memory usage
|
|
496
|
+
let cpuPercent = 0;
|
|
497
|
+
let memoryBytes = 0;
|
|
498
|
+
if (mainPID > 0) {
|
|
499
|
+
try {
|
|
500
|
+
// Use ps to get CPU and memory for the main PID
|
|
501
|
+
const { stdout: psOutput } = await execAsync(`ps -p ${mainPID} -o %cpu=,%mem=,rss= --no-headers`, { timeout: 2000 });
|
|
502
|
+
if (psOutput) {
|
|
503
|
+
const parts = psOutput.trim().split(/\s+/);
|
|
504
|
+
if (parts.length >= 3) {
|
|
505
|
+
cpuPercent = parseFloat(parts[0]) || 0;
|
|
506
|
+
// RSS is in KB, convert to bytes
|
|
507
|
+
memoryBytes = (parseInt(parts[2], 10) || 0) * 1024;
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
catch (error) {
|
|
512
|
+
// Process might have ended, use 0 values
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
return {
|
|
516
|
+
name: serviceName,
|
|
517
|
+
status: activeState,
|
|
518
|
+
subState: subState,
|
|
519
|
+
enabled: enabled,
|
|
520
|
+
uptime: uptime,
|
|
521
|
+
startedAt: activeEnterTimestamp,
|
|
522
|
+
restartCount: restartCount,
|
|
523
|
+
cpuPercent: cpuPercent,
|
|
524
|
+
memoryBytes: memoryBytes,
|
|
525
|
+
pid: mainPID,
|
|
526
|
+
};
|
|
527
|
+
}
|
|
528
|
+
catch (error) {
|
|
529
|
+
// Service doesn't exist or error getting info
|
|
530
|
+
return null;
|
|
531
|
+
}
|
|
532
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -76,6 +76,8 @@ async function sendMetrics() {
|
|
|
76
76
|
const processes = await (0, collector_1.collectProcesses)();
|
|
77
77
|
const dockerContainers = await (0, collector_1.collectDockerContainers)();
|
|
78
78
|
const pm2Processes = await (0, collector_1.collectPM2Processes)();
|
|
79
|
+
const nginxMetrics = await (0, collector_1.collectNginxMetrics)();
|
|
80
|
+
const systemdServices = await (0, collector_1.collectSystemdServices)();
|
|
79
81
|
// Check if it's time to refresh SSL certificates (every 5 minutes)
|
|
80
82
|
const now = Date.now();
|
|
81
83
|
const shouldCheckSSL = (now - lastSSLCheckTime) >= SSL_CHECK_INTERVAL;
|
|
@@ -95,14 +97,18 @@ async function sendMetrics() {
|
|
|
95
97
|
processes,
|
|
96
98
|
dockerContainers,
|
|
97
99
|
pm2Processes: pm2Processes.length > 0 ? pm2Processes : undefined,
|
|
100
|
+
nginxMetrics: nginxMetrics || undefined,
|
|
101
|
+
systemdServices: systemdServices.length > 0 ? systemdServices : undefined,
|
|
98
102
|
// Always send SSL certificates if we have them (from startup or last check)
|
|
99
103
|
sslCertificates: lastSSLCertificates.length > 0 ? lastSSLCertificates : undefined,
|
|
100
104
|
};
|
|
101
105
|
await axios_1.default.post(`${SERVER_URL}/api/metrics`, payload, {
|
|
102
106
|
timeout: 5000,
|
|
103
107
|
});
|
|
104
|
-
const pm2Info = pm2Processes.length > 0 ? ` | PM2: ${pm2Processes.length}
|
|
105
|
-
|
|
108
|
+
const pm2Info = pm2Processes.length > 0 ? ` | PM2: ${pm2Processes.length}` : '';
|
|
109
|
+
const nginxInfo = nginxMetrics ? ` | Nginx: ${nginxMetrics.activeConnections} conn` : '';
|
|
110
|
+
const systemdInfo = systemdServices.length > 0 ? ` | Services: ${systemdServices.length}` : '';
|
|
111
|
+
console.log(`✓ Metrics sent successfully - CPU: ${metrics.cpuUsage.toFixed(1)}% | Memory: ${metrics.memoryUsage.toFixed(1)}%${pm2Info}${nginxInfo}${systemdInfo}`);
|
|
106
112
|
}
|
|
107
113
|
catch (error) {
|
|
108
114
|
if (axios_1.default.isAxiosError(error)) {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gazzehamine/armada-watch-agent",
|
|
3
|
-
"version": "1.4.
|
|
4
|
-
"description": "Monitoring agent for Armada Watch - EC2 instance monitoring with SSL
|
|
3
|
+
"version": "1.4.4",
|
|
4
|
+
"description": "Monitoring agent for Armada Watch - EC2 instance monitoring with SSL, PM2, Nginx, and Systemd monitoring",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"bin": {
|
|
7
7
|
"armada-watch-agent": "dist/index.js"
|