node-watch-tower
Version: 
Node.js monitoring utility to track uptime, CPU, and memory usage and report to a central admin server.
104 lines (88 loc) • 3.29 kB
JavaScript
const os = require('os');
const axios = require('axios');
const process = require('process');
const { monitorEventLoopDelay } = require('perf_hooks');
class NodeWatchTower {
  constructor(config) {
    this.adminUrl = "https://prutech.co.in/nodeMonitor/api";
    this.interval = 1000 * 60;
    this.token = config.token || null;
    this.registered = false;
    this.id = config.id || null;
    // Setup event loop delay monitor
    this.eventLoopMonitor = monitorEventLoopDelay({ resolution: 10 });
    this.eventLoopMonitor.enable();
    this.start();
  }
  async start() {
    await this.register();
    this.sendStatsLoop();
  }
  async register() {
    try {
      const res = await axios.post(`${this.adminUrl}/app/register`, {
        id: this.id,
        hostname: os.hostname(),
        pid: process.pid,
        token: this.token,
      });
      if (res.data?.status == 1) {
        this.registered = true;
        console.log('\x1b[32m[NodeWatchTower]\x1b[0m ✅ Registered with Admin Monitor');
        this.interval = res.data?.data?.heartBeatInterval * 1000 || this.interval;
      } else {
        console.log('\x1b[31m[NodeWatchTower]\x1b[0m ❌ Registration Failed:', res.data.message);
      }
    } catch (err) {
      console.log('\x1b[31m[NodeWatchTower]\x1b[0m ❌ Registration Failed:', err?.response?.data?.message || err.message);
    }
  }
  async sendStatsLoop() {
    setInterval(async () => {
      if (!this.registered) return;
      try {
        const stats = this.getStats();
        await axios.post(`${this.adminUrl}/app/heartbeat`, {
          id: this.id,
          stats,
          token: this.token,
        });
      } catch (err) {
        console.log('\x1b[31m[NodeWatchTower]\x1b[0m ❌ Heartbeat failed:', err.message);
      }
    }, this.interval);
  }
  getStats() {
    const memory = process.memoryUsage();
    const heapUsedMB = memory.heapUsed / 1024 / 1024;
    const heapTotalMB = memory.heapTotal / 1024 / 1024;
    const eventLoop = this.eventLoopMonitor;
    const p95 = eventLoop.percentile(95) / 1_000_000; // ns to ms
    const meanLatency = eventLoop.mean / 1_000_000;
    return {
      timestamp: new Date(),
      cpu: this.getAppCpuUsagePercent(),
      memory: {
        rss: (memory.rss / 1024 / 1024).toFixed(2),
        heapUsed: heapUsedMB.toFixed(2),
        heapTotal: heapTotalMB.toFixed(2),
        heapUsagePercent: ((heapUsedMB / heapTotalMB) * 100).toFixed(2),
      },
      uptime: process.uptime().toFixed(2),
      eventLoopLatencyMs: meanLatency.toFixed(2),
      eventLoopLatencyP95Ms: p95.toFixed(2),
      activeHandles: process._getActiveHandles().length,
      activeRequests: process._getActiveRequests().length,
    };
  }
  getAppCpuUsagePercent() {
    const cpuUsage = process.cpuUsage(); // in microseconds
    const elapsedTime = process.uptime(); // in seconds
    const totalMicroSec = cpuUsage.user + cpuUsage.system;
    const cpuUsedSeconds = totalMicroSec / 1_000_000;
    const cpuCores = os.cpus().length;
    const totalAvailableCpuTime = elapsedTime * cpuCores;
    return ((cpuUsedSeconds / totalAvailableCpuTime) * 100).toFixed(2);
  }
}
module.exports = NodeWatchTower;