Build a Real-Time System Monitoring Dashboard with NestJS, Socket.IO, and Plotly.js
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
Backend Setup: The NestJS backend establishes a WebSocket connection using Socket.IO, allowing communication with the client-side dashboard.
Data Transmission: System metrics, including CPU and memory utilization, are transmitted in real-time from the server to the dashboard.
Visualization: Plotly gauges dynamically update to reflect the received metrics, providing users with a clear visualization of system performance.
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
fromsocket.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 theOnModuleInit
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 theonMessage()
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 ({}
).
- This method listens for incoming messages on the channel specified by
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 toSystemConstant.EMIT_SYSTEM_EV
if not provided.
This method uses the
server.emit()
function to broadcast thepayload
on the specifiedevent
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!