@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
text/typescript
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;
}
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,
},
};
}
}