UNPKG

@texagon/nestjs-health

Version:

A comprehensive health monitoring package for NestJS applications with metrics and Kubernetes-ready health checks

234 lines (208 loc) 6.24 kB
import { Injectable, Logger } from "@nestjs/common"; import * as os from "os"; export interface ResourceMetrics { timestamp: number; memory: { rss: number; heapTotal: number; heapUsed: number; external: number; arrayBuffers?: number; }; cpu: { user: number; system: number; percent: number; }; loadAvg: number[]; freeMemory: number; } export interface RequestMetrics { timestamp: number; method: string; path: string; statusCode: number; responseTime: number; } @Injectable() export class MetricsService { private readonly logger = new Logger(MetricsService.name); private readonly maxHistoryPoints = 100; private readonly resourceMetrics: ResourceMetrics[] = []; private readonly requestMetrics: RequestMetrics[] = []; private maxMemoryUsage = { rss: 0, heapTotal: 0, heapUsed: 0, external: 0, arrayBuffers: 0, }; private minMemoryUsage = { rss: Number.MAX_SAFE_INTEGER, heapTotal: Number.MAX_SAFE_INTEGER, heapUsed: Number.MAX_SAFE_INTEGER, external: Number.MAX_SAFE_INTEGER, arrayBuffers: Number.MAX_SAFE_INTEGER, }; private maxCpuUsage = { user: 0, system: 0, percent: 0, }; private requestStats = { total: 0, byMethod: {} as Record<string, number>, byStatusCode: {} as Record<string, number>, avgResponseTime: 0, maxResponseTime: 0, minResponseTime: Number.MAX_SAFE_INTEGER, }; private lastCpuUsage = process.cpuUsage(); private lastCpuUsageTime = Date.now(); constructor() { setInterval(() => this.collectResourceMetrics(), 5000); this.logger.log("Metrics service initialized"); } recordRequest( method: string, path: string, statusCode: number, responseTime: number, ): void { const timestamp = Date.now(); this.requestMetrics.push({ timestamp, method, path, statusCode, responseTime, }); if (this.requestMetrics.length > this.maxHistoryPoints) { this.requestMetrics.shift(); } this.requestStats.total++; this.requestStats.byMethod[method] = (this.requestStats.byMethod[method] || 0) + 1; const statusRange = `${Math.floor(statusCode / 100)}xx`; this.requestStats.byStatusCode[statusRange] = (this.requestStats.byStatusCode[statusRange] || 0) + 1; this.requestStats.byStatusCode[statusCode.toString()] = (this.requestStats.byStatusCode[statusCode.toString()] || 0) + 1; const prevTotal = this.requestStats.avgResponseTime * (this.requestStats.total - 1); this.requestStats.avgResponseTime = (prevTotal + responseTime) / this.requestStats.total; this.requestStats.maxResponseTime = Math.max( this.requestStats.maxResponseTime, responseTime, ); this.requestStats.minResponseTime = Math.min( this.requestStats.minResponseTime, responseTime, ); } private collectResourceMetrics(): void { try { const memoryUsage = process.memoryUsage(); const currentTime = Date.now(); const cpuUsage = process.cpuUsage(); const userDiff = cpuUsage.user - this.lastCpuUsage.user; const systemDiff = cpuUsage.system - this.lastCpuUsage.system; const timeDiff = currentTime - this.lastCpuUsageTime; const cpuPercent = (userDiff + systemDiff) / (timeDiff * 10); const metrics: ResourceMetrics = { timestamp: currentTime, memory: { rss: memoryUsage.rss, heapTotal: memoryUsage.heapTotal, heapUsed: memoryUsage.heapUsed, external: memoryUsage.external, arrayBuffers: memoryUsage.arrayBuffers, }, cpu: { user: cpuUsage.user, system: cpuUsage.system, percent: cpuPercent, }, loadAvg: os.loadavg(), freeMemory: os.freemem(), }; this.maxMemoryUsage.rss = Math.max( this.maxMemoryUsage.rss, memoryUsage.rss, ); this.maxMemoryUsage.heapTotal = Math.max( this.maxMemoryUsage.heapTotal, memoryUsage.heapTotal, ); this.maxMemoryUsage.heapUsed = Math.max( this.maxMemoryUsage.heapUsed, memoryUsage.heapUsed, ); this.maxMemoryUsage.external = Math.max( this.maxMemoryUsage.external, memoryUsage.external, ); this.maxMemoryUsage.arrayBuffers = Math.max( this.maxMemoryUsage.arrayBuffers || 0, memoryUsage.arrayBuffers || 0, ); this.minMemoryUsage.rss = Math.min( this.minMemoryUsage.rss, memoryUsage.rss, ); this.minMemoryUsage.heapTotal = Math.min( this.minMemoryUsage.heapTotal, memoryUsage.heapTotal, ); this.minMemoryUsage.heapUsed = Math.min( this.minMemoryUsage.heapUsed, memoryUsage.heapUsed, ); this.minMemoryUsage.external = Math.min( this.minMemoryUsage.external, memoryUsage.external, ); this.minMemoryUsage.arrayBuffers = Math.min( this.minMemoryUsage.arrayBuffers, memoryUsage.arrayBuffers || 0, ); this.maxCpuUsage.user = Math.max(this.maxCpuUsage.user, cpuUsage.user); this.maxCpuUsage.system = Math.max( this.maxCpuUsage.system, cpuUsage.system, ); this.maxCpuUsage.percent = Math.max(this.maxCpuUsage.percent, cpuPercent); this.resourceMetrics.push(metrics); if (this.resourceMetrics.length > this.maxHistoryPoints) { this.resourceMetrics.shift(); } this.lastCpuUsage = cpuUsage; this.lastCpuUsageTime = currentTime; } catch (error) { this.logger.error(`Error collecting metrics: ${error.message}`); } } getMetrics() { return { current: this.resourceMetrics.length > 0 ? this.resourceMetrics[this.resourceMetrics.length - 1] : null, max: { memory: this.maxMemoryUsage, cpu: this.maxCpuUsage, }, min: { memory: this.minMemoryUsage, }, requests: { stats: this.requestStats, recent: this.requestMetrics.slice(-10), }, history: { resources: this.resourceMetrics, }, }; } }