UNPKG

@dudousxd/nestjs-telescope

Version:

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

99 lines 5.24 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/pulse/pulse.service.ts import { Inject, Injectable, Optional } from '@nestjs/common'; import { collectEntriesInWindow } from '../metrics/collect-window.js'; import { TELESCOPE_STORAGE } from '../nest/telescope.options.js'; import { aggregatePulse, finalizePulse } from './pulse-summary.js'; const DEFAULT_TOP_N = 5; const DEFAULT_N_PLUS_ONE_THRESHOLD = 5; const DEFAULT_SLOW_ROUTE_MIN_COUNT = 1; /** * Default p99 (ms) a route must reach to count as a slow-route hotspot. Matches * the `slow` request-tag threshold (`SLOW_THRESHOLD_MS` in tagging/tagger.ts) * and the HttpClientWatcher `slowMs` default, so "hotspot" lines up with the * `slow` tag used everywhere else. See {@link PulseOptions.slowRouteMs}. */ const DEFAULT_SLOW_ROUTE_MS = 1000; /** Computes a health snapshot by aggregating stored entries over a time window. * Reads the store (source of truth) on demand — no live counters. */ let PulseService = class PulseService { storage; pageSize; scanCap; topN; nPlusOneThreshold; slowRouteMinCount; slowRouteMs; constructor(storage, options) { this.storage = storage; this.pageSize = options?.pageSize; this.scanCap = options?.scanCap; this.topN = options?.topN ?? DEFAULT_TOP_N; this.nPlusOneThreshold = options?.nPlusOneThreshold ?? DEFAULT_N_PLUS_ONE_THRESHOLD; this.slowRouteMinCount = options?.slowRouteMinCount ?? DEFAULT_SLOW_ROUTE_MIN_COUNT; this.slowRouteMs = options?.slowRouteMs ?? DEFAULT_SLOW_ROUTE_MS; } async getHealth(windowMs) { if (!Number.isFinite(windowMs) || windowMs <= 0) { throw new RangeError(`windowMs must be a positive, finite number (got ${windowMs}).`); } const windowEnd = new Date(); const windowStart = new Date(windowEnd.getTime() - windowMs); // Pass 1: scan the whole window over content-less columns only. This is the // heavy loop (every entry in the window), so omitting the content blob is // where the time is saved. const { entries, scanned, truncated } = await collectEntriesInWindow(this.storage, { after: windowStart, omitContent: true }, { ...(this.pageSize !== undefined ? { pageSize: this.pageSize } : {}), ...(this.scanCap !== undefined ? { scanCap: this.scanCap } : {}), }); const aggregates = aggregatePulse(entries, windowStart, windowEnd, { topN: this.topN, nPlusOneThreshold: this.nPlusOneThreshold, slowRouteMinCount: this.slowRouteMinCount, slowRouteMs: this.slowRouteMs, }); // Pass 2: hydrate content for ONLY the handful of displayed rows — the top-N // slowest, one representative per reported exception family, and one per // reported N+1 family. Bounded by topN, so at most a few dozen reads. const contentById = await this.hydrate(aggregates.hydrationIds); const summary = finalizePulse(aggregates, (id) => contentById.has(id) ? contentById.get(id) : undefined); return { ...summary, scanned, truncated }; } /** Fetch content for the deduplicated set of ids the pulse output displays. */ async hydrate(ids) { const uniqueIds = new Set([...ids.slowest, ...ids.exceptions, ...ids.nPlusOne]); uniqueIds.delete(''); const contentById = new Map(); if (uniqueIds.size === 0) return contentById; // ONE batched query for every displayed id, instead of N per-id find() round- // trips. Each find() hit the remote DB separately; this collapses them into a // single fetch (e.g. `id IN (...)` / MGET) — the pulse latency win on a remote // store. `limit` is sized to the id count so the page can hold all of them. const idList = [...uniqueIds]; const page = await this.storage.get({ ids: idList, limit: idList.length }); for (const entry of page.data) { contentById.set(entry.id, entry.content); } return contentById; } }; PulseService = __decorate([ Injectable(), __param(0, Inject(TELESCOPE_STORAGE)), __param(1, Optional()), __metadata("design:paramtypes", [Object, Object]) ], PulseService); export { PulseService }; //# sourceMappingURL=pulse.service.js.map