Build a Real-Time System Monitoring Dashboard with NestJS, Socket.IO, and Plotly.js

Build a Real-Time System Monitoring Dashboard with NestJS, Socket.IO, and Plotly.js

Featured on Hashnode

In the realm of system administration, maintaining an eagle eye on your system's health is paramount. This includes monitoring CPU usage, memory utilization, and other vital metrics to ensure smooth operation and prevent performance bottlenecks. A real-time dashboard that visually represents these metrics can be a valuable tool for system administrators and developers alike.

This article guides you through building a real-time system monitoring dashboard using the following powerful technologies:

Understanding the Components

NestJS: A Robust Backend Framework

NestJS provides a solid foundation for building scalable and efficient server-side applications. Leveraging its modular architecture and dependency injection system, we've crafted a backend that seamlessly integrates with other components of our monitoring solution.

Socket.IO: Facilitating Real-time Communication

Socket.IO enables bidirectional communication between clients and servers in real-time. With Socket.IO, our dashboard receives live system metrics and updates the visualization instantaneously, ensuring users have access to the latest information at all times.

Plotly: Interactive Visualization

Plotly is a powerful JavaScript library for creating interactive and visually appealing data visualizations. Utilizing Plotly, we've designed intuitive gauges to represent CPU and memory utilization metrics, providing users with a clear understanding of system performance.

Real-time System Monitoring Dashboard

Our system monitoring dashboard offers a user-friendly interface for tracking CPU and memory utilization in real-time. Here's what it includes:

  • Live System Metrics: Receive real-time updates on CPU and memory utilization.

  • Interactive Gauges: Visualize CPU and memory metrics using interactive gauges powered by Plotly.

  • Alerting: Receive alerts for high CPU or memory utilization, ensuring prompt action can be taken to address performance issues.

How It Works

  1. Backend Setup: The NestJS backend establishes a WebSocket connection using Socket.IO, allowing communication with the client-side dashboard.

  2. Data Transmission: System metrics, including CPU and memory utilization, are transmitted in real-time from the server to the dashboard.

  3. Visualization: Plotly gauges dynamically update to reflect the received metrics, providing users with a clear visualization of system performance.

  4. Alerting Mechanism: If CPU or memory utilization exceeds predefined thresholds, alerts are displayed on the dashboard to notify users of potential issues.

NestJS Service for System Metrics:

import { Injectable } from '@nestjs/common';
import { ICpuUsage } from '../common/cpu.usage.interface';
import * as os from 'os';
import { ITotalUsedMemory } from '../common/used.memeory.interface';
import { ICpuUsageMetric } from '../common/metric.cpu.interface';
@Injectable()
export class CpuService {
  getCpuMetric(): number {
    const { user, system } = this.getCpuUsageInMilliSeconds();
    const totalCores = this.getCpuCoreCount();
    const cpuPercent = ((user + system) / 1000 / totalCores) * 100;
    return cpuPercent;
  }

  getMemoryMetric(): number {
    const { usedMemory, totalMemory } = this.getTotalUsedMemory();
    const memoryPercent = (usedMemory / totalMemory) * 100;
    return memoryPercent;
  }

  getTotalMemory(): number {
    return this.bytesToGB(os.totalmem());
  }

  getFreeMemory(): number {
    return this.bytesToGB(os.freemem());
  }

  getPlatFrom(): NodeJS.Platform {
    return os.platform();
  }

  getCpuUsageInMilliSeconds(): ICpuUsage {
    const { user, system } = process.cpuUsage();
    const userInMilliSeconds = this.convertMicroToMilliSeconds(user);
    const systemInMilliSeconds = this.convertMicroToMilliSeconds(system);
    return { user: userInMilliSeconds, system: systemInMilliSeconds };
  }

  convertMicroToMilliSeconds(micro: number) {
    return micro / 1000;
  }

  getCpuCoreCount(): number {
    return os.cpus().length;
  }

  getTotalUsedMemory(): ITotalUsedMemory {
    const totalMemory = this.getTotalMemory();
    const freeMemory = this.getFreeMemory();
    const usedMemory = totalMemory - freeMemory;
    return { usedMemory, totalMemory };
  }

  getCpuUsageMetricInPercentage(): ICpuUsageMetric {
    const cpuMetric = this.getCpuMetric();
    const memMetric = this.getMemoryMetric();
    return { cpuMetric, memMetric };
  }
  bytesToGB(bytes: number): number {
    return bytes / Math.pow(1024, 3);
  }
}

This CpuService retrieves system metrics like CPU and memory utilization percentages using the os module.

NestJS Controller for Data Exposure:

import { Controller, Get } from '@nestjs/common';
import { CpuService } from './cpu.service';

@Controller('cpu')
export class CpuController {
  constructor(private readonly cpuService: CpuService) {}

  @Get('/')
  getCpuUsage() {
    const cpuUsage = this.cpuService.getCpuMetricInPercentage();
    const freeMem = this.cpuService.getFreeMemory();
    const totalUsedMemory = this.cpuService.getTotalUsedMemory();
    const memory = Object.assign(totalUsedMemory, { freeMem });
    return { cpuUsage, memory };
  }
}
//..

The CpuController exposes an endpoint (/cpu) that returns an object containing CPU and memory usage data retrieved from the CpuService.

NestJS Cron Job for Periodic Updates:

import { Injectable } from '@nestjs/common';
import { Cron, CronExpression } from '@nestjs/schedule';
import { GatewayService } from '../gateway/gateway.service';
import { CpuService } from '../cpu/cpu.service';

@Injectable()
export class CronService {
  constructor(
    private readonly ws: GatewayService,
    private readonly cpuService: CpuService,
  ) {}

  @Cron(CronExpression.EVERY_5_SECONDS) // Adjust interval as needed
  updateCpuUsage() {
    const cpuUsageMetric = this.cpuService.getCpuUsageMetricInPercentage();
    this.ws.sendMessage(cpuUsageMetric, 'system-metrics');
  }
}

The CronService employs a cron job to collect CPU usage data (along with memory usage if desired) every 5 seconds (adjust the interval based on your needs) and sends it to the GatewayService using a WebSocket channel named system-metrics.

NestJS Gateway Service for Real-Time Communication:

import { OnModuleInit } from '@nestjs/common';
import {
  SubscribeMessage,
  WebSocketGateway,
  WebSocketServer,
} from '@nestjs/websockets';
import { SystemConstant } from '../common/constant';
import { Server } from 'socket.io';

@WebSocketGateway({
  namespace: 'system',
  cors: {
    origin: '*:*',
  },
})
export class GatewayService implements OnModuleInit {
  constructor() {}
  onModuleInit() {
    this.server.on('connection', (socket) => {
      console.log(`/system | socket id: ${socket.id}`);
    });
  }
  @WebSocketServer()
  server: Server;
  @SubscribeMessage(SystemConstant.EMIT_SYSTEM_EV)
  onMessage() {}

  sendMessage(payload: any, event?: string) {
    this.server.emit(event ?? SystemConstant.EMIT_SYSTEM_EV, payload);
  }
}

The provided code snippet defines a NestJS WebSocket Gateway service named GatewayService that facilitates communication between the server and connected clients. Here's a breakdown with explanations:

Imports:

  • Necessary decorators for WebSocket functionality (@nestjs/websockets).

  • SystemConstant (likely for event names).

  • Server from socket.io (underlying server instance).

Class Definition:

  • GatewayService class decorated with @WebSocketGateway({}): Marks the class as a WebSocket gateway. Configuration options:

    • namespace: 'system': Defines the namespace for this gateway (/system).

    • cors: { origin: '*:*' }: Enables Cross-Origin Resource Sharing (CORS) for any origin (not recommended for production due to security concerns).

Methods:

  • constructor(): Empty constructor.

  • onModuleInit(): Implements the OnModuleInit interface.

    • Gets the server instance using the @WebSocketServer() decorator.

    • Attaches a listener to the 'connection' event of the server. This logs information whenever a client connects.

  • server: Server: Property decorated with @WebSocketServer(). This holds the underlying WebSocket server instance.

  • @SubscribeMessage(SystemConstant.EMIT_SYSTEM_EV): Decorates the onMessage() method.

    • This method listens for incoming messages on the channel specified by SystemConstant.EMIT_SYSTEM_EV (presumably a constant defined elsewhere). However, the current implementation has an empty body ({}).
  • sendMessage(payload: any, event?: string): This method allows sending messages to connected clients.

    • It takes two arguments:

      • payload: The data to be sent.

      • event (optional): The event name on which to send the message. Defaults to SystemConstant.EMIT_SYSTEM_EV if not provided.

    • This method uses the server.emit() function to broadcast the payload on the specified event channel.pen_spark

Functionality:

This GatewayService acts as a bridge between the server and connected clients. It defines a namespace (/system) for WebSocket communication and allows sending and receiving messages through events. The onMessage() method currently doesn't handle incoming messages, but it could be implemented to process data received from clients.

Potential Improvements:

  • Consider tightening the CORS configuration in production to restrict allowed origins for security reasons.

  • Implement logic within onMessage() to handle incoming messages from clients if needed.

  • Add error handling for potential issues during message sending or receiving.

This article has guided you through building a real-time system monitoring dashboard using NestJS for server-side functionality, Socket.IO for real-time communication, and Plotly.js for creating interactive gauges. By combining these technologies, you can gain valuable insights into your system's health, allowing for proactive monitoring and improved performance.

Putting It All Together:

The complete code for this project is available on GitHub at https://github.com/sumanmanna134/cpu-realtime. This repository includes the NestJS application, the client-side HTML page, and the necessary configuration files. Feel free to clone the repository, explore the code further, and customize it to fit your specific monitoring needs.

Remember to adjust the CORS configuration in the NestJS application for production environments to restrict access to authorized origins. You can also extend this project by adding more gauges for other system metrics (e.g., network traffic, disk usage), implementing user authentication, and potentially storing historical data for trend analysis.

We hope this article empowers you to create an effective system monitoring solution for your needs!