@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 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} processes` : '';
105
- console.log(`✓ Metrics sent successfully - CPU: ${metrics.cpuUsage.toFixed(1)}% | Memory: ${metrics.memoryUsage.toFixed(1)}%${pm2Info}`);
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.2",
4
- "description": "Monitoring agent for Armada Watch - EC2 instance monitoring with SSL certificate tracking and PM2 process monitoring",
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"