@gazzehamine/armada-watch-agent 1.4.3 → 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 +147 -0
- package/dist/index.js +4 -1
- package/package.json +2 -2
package/dist/collector.js
CHANGED
|
@@ -10,6 +10,7 @@ exports.collectDockerContainers = collectDockerContainers;
|
|
|
10
10
|
exports.collectNginxMetrics = collectNginxMetrics;
|
|
11
11
|
exports.collectPM2Processes = collectPM2Processes;
|
|
12
12
|
exports.collectSSLCertificates = collectSSLCertificates;
|
|
13
|
+
exports.collectSystemdServices = collectSystemdServices;
|
|
13
14
|
const systeminformation_1 = __importDefault(require("systeminformation"));
|
|
14
15
|
const os_1 = __importDefault(require("os"));
|
|
15
16
|
const fs_1 = __importDefault(require("fs"));
|
|
@@ -383,3 +384,149 @@ async function collectSSLCertificates() {
|
|
|
383
384
|
return certificates;
|
|
384
385
|
}
|
|
385
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
|
@@ -77,6 +77,7 @@ async function sendMetrics() {
|
|
|
77
77
|
const dockerContainers = await (0, collector_1.collectDockerContainers)();
|
|
78
78
|
const pm2Processes = await (0, collector_1.collectPM2Processes)();
|
|
79
79
|
const nginxMetrics = await (0, collector_1.collectNginxMetrics)();
|
|
80
|
+
const systemdServices = await (0, collector_1.collectSystemdServices)();
|
|
80
81
|
// Check if it's time to refresh SSL certificates (every 5 minutes)
|
|
81
82
|
const now = Date.now();
|
|
82
83
|
const shouldCheckSSL = (now - lastSSLCheckTime) >= SSL_CHECK_INTERVAL;
|
|
@@ -97,6 +98,7 @@ async function sendMetrics() {
|
|
|
97
98
|
dockerContainers,
|
|
98
99
|
pm2Processes: pm2Processes.length > 0 ? pm2Processes : undefined,
|
|
99
100
|
nginxMetrics: nginxMetrics || undefined,
|
|
101
|
+
systemdServices: systemdServices.length > 0 ? systemdServices : undefined,
|
|
100
102
|
// Always send SSL certificates if we have them (from startup or last check)
|
|
101
103
|
sslCertificates: lastSSLCertificates.length > 0 ? lastSSLCertificates : undefined,
|
|
102
104
|
};
|
|
@@ -105,7 +107,8 @@ async function sendMetrics() {
|
|
|
105
107
|
});
|
|
106
108
|
const pm2Info = pm2Processes.length > 0 ? ` | PM2: ${pm2Processes.length}` : '';
|
|
107
109
|
const nginxInfo = nginxMetrics ? ` | Nginx: ${nginxMetrics.activeConnections} conn` : '';
|
|
108
|
-
|
|
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}`);
|
|
109
112
|
}
|
|
110
113
|
catch (error) {
|
|
111
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, PM2, and
|
|
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"
|