UNPKG

@dudousxd/nestjs-telescope

Version:

Laravel Telescope-style observability console for NestJS — core: watchers, recorder, correlation, SQLite store, headless API.

140 lines 5.91 kB
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; return c > 3 && r && Object.defineProperty(target, key, r), r; }; var __metadata = (this && this.__metadata) || function (k, v) { if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); }; var __param = (this && this.__param) || function (paramIndex, decorator) { return function (target, key) { decorator(target, key, paramIndex); } }; // packages/core/src/metrics/server-stats.service.ts import * as perfHooks from 'node:perf_hooks'; import { Inject, Injectable, Optional } from '@nestjs/common'; import { TELESCOPE_CONFIG } from '../nest/telescope.options.js'; const DEFAULT_MAX_SAMPLES = 120; const BYTES_PER_MB = 1024 * 1024; const NS_PER_MS = 1e6; const US_PER_MS = 1e3; function toMb(bytes) { return Math.round((bytes / BYTES_PER_MB) * 100) / 100; } /** * Point-in-time snapshot of the Node process health (memory, CPU, uptime, * event-loop delay). The event-loop histogram is started once at construction * and read per request so its mean reflects the whole process lifetime. When * `perf_hooks.monitorEventLoopDelay` is unavailable, the delay degrades to * `null` rather than throwing — every other field is always present. */ let ServerStatsService = class ServerStatsService { config; loopMonitor; maxSamples; /** CPU/mem history ring buffer (oldest first). */ samples = []; /** Previous (cpuUsage, wall ms) for deriving the per-interval cpuPercent. */ lastCpu = null; lastSampleMs = 0; constructor(config, options) { this.config = config; this.loopMonitor = startEventLoopMonitor(); this.maxSamples = Math.max(1, Math.floor(options?.maxSamples ?? DEFAULT_MAX_SAMPLES)); } onApplicationShutdown() { this.loopMonitor?.disable(); } getStats() { const memory = process.memoryUsage(); const cpu = process.cpuUsage(); const eventLoopDelayMs = this.loopMonitor === null ? null : roundMs(this.loopMonitor.mean / NS_PER_MS); this.recordSample(memory, cpu, eventLoopDelayMs); return { uptimeSec: Math.round(process.uptime() * 100) / 100, memory: { rssMb: toMb(memory.rss), heapUsedMb: toMb(memory.heapUsed), heapTotalMb: toMb(memory.heapTotal), }, cpu: { userMs: roundMs(cpu.user / US_PER_MS), systemMs: roundMs(cpu.system / US_PER_MS), }, eventLoopDelayMs, instanceId: this.config.instanceId, }; } /** The CPU/mem history captured so far (oldest first), for the dashboard's * CPU/mem-history card. Cheap copy of the ring buffer. */ getHistory() { return { samples: [...this.samples] }; } /** * Append one history sample, deriving cpuPercent from the CPU-time delta since * the previous sample over the wall-clock interval (percent of ONE core). The * first sample has no prior interval, so its cpuPercent is 0. Evicts the oldest * once the ring buffer exceeds its cap. */ recordSample(memory, cpu, eventLoopDelayMs) { const nowMs = Date.now(); let cpuPercent = 0; if (this.lastCpu !== null && this.lastSampleMs > 0) { const cpuDeltaUs = cpu.user - this.lastCpu.user + (cpu.system - this.lastCpu.system); const wallDeltaMs = nowMs - this.lastSampleMs; if (wallDeltaMs > 0) { // cpuDeltaUs is microseconds of CPU; wallDeltaMs*1000 is the interval in // microseconds. The ratio is the fraction of one core, ×100 for percent. cpuPercent = Math.max(0, Math.round((cpuDeltaUs / (wallDeltaMs * US_PER_MS)) * 10000) / 100); } } this.lastCpu = cpu; this.lastSampleMs = nowMs; this.samples.push({ atMs: nowMs, rssMb: toMb(memory.rss), heapUsedMb: toMb(memory.heapUsed), cpuPercent, eventLoopDelayMs, }); while (this.samples.length > this.maxSamples) this.samples.shift(); } }; ServerStatsService = __decorate([ Injectable(), __param(0, Inject(TELESCOPE_CONFIG)), __param(1, Optional()), __metadata("design:paramtypes", [Object, Object]) ], ServerStatsService); export { ServerStatsService }; function roundMs(value) { return Number.isFinite(value) ? Math.round(value * 1000) / 1000 : 0; } /** Start an enabled event-loop-delay monitor, or `null` if perf_hooks lacks it. */ function startEventLoopMonitor() { const factory = perfHooks.monitorEventLoopDelay; if (typeof factory !== 'function') return null; try { const monitor = factory(); if (!isEventLoopDelayMonitor(monitor)) return null; monitor.enable(); return monitor; } catch { return null; } } /** Structural guard: confirms a value exposes the monitor surface we read. */ function isEventLoopDelayMonitor(value) { if (typeof value !== 'object' || value === null) return false; if (!('enable' in value) || !('disable' in value) || !('mean' in value)) return false; return (typeof value.enable === 'function' && typeof value.disable === 'function' && typeof value.mean === 'number'); } //# sourceMappingURL=server-stats.service.js.map