UNPKG

memoru

Version:

A hash-based LRU cache that evicts entries based on memory usage rather than time or item count.

233 lines (232 loc) 9.01 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.MemoryStatsMonitor = exports.ProcessMemoryStat = exports.HeapSpace = exports.GCKind = void 0; const events_1 = require("events"); const node_perf_hooks_1 = require("node:perf_hooks"); const v8_1 = __importDefault(require("v8")); /** * Enum of garbage collection kinds for monitoring. * @public */ var GCKind; (function (GCKind) { GCKind["Minor"] = "minor"; GCKind["Major"] = "major"; GCKind["Incremental"] = "incremental"; GCKind["WeakCallback"] = "weak_callback"; })(GCKind || (exports.GCKind = GCKind = {})); /** * Enum of V8 heap space names for memory monitoring. * @public */ var HeapSpace; (function (HeapSpace) { HeapSpace["ReadOnly"] = "read_only_space"; HeapSpace["New"] = "new_space"; HeapSpace["Old"] = "old_space"; HeapSpace["Code"] = "code_space"; HeapSpace["Shared"] = "shared_space"; HeapSpace["Trusted"] = "trusted_space"; HeapSpace["NewLargeObject"] = "new_large_object_space"; HeapSpace["LargeObject"] = "large_object_space"; HeapSpace["CodeLargeObject"] = "code_large_object_space"; HeapSpace["SharedLargeObject"] = "shared_large_object_space"; HeapSpace["TrustedLargeObject"] = "trusted_large_object_space"; })(HeapSpace || (exports.HeapSpace = HeapSpace = {})); /** * Enum of process memory stats for monitoring. * @public */ var ProcessMemoryStat; (function (ProcessMemoryStat) { ProcessMemoryStat["RSS"] = "rss"; ProcessMemoryStat["HeapUsed"] = "heapUsed"; })(ProcessMemoryStat || (exports.ProcessMemoryStat = ProcessMemoryStat = {})); /** * Periodically monitors V8 and process memory stats, emitting events when thresholds are reached. * @public */ class MemoryStatsMonitor extends events_1.EventEmitter { /** * Create a new MemoryStatsMonitor. * @param options - Configuration for what to monitor and thresholds * @public */ constructor(options) { super(); this.isGCInProgress = false; this.lastGCTime = 0; this.options = options; this.start(); if (options.monitorGC) { this.setupGCMonitoring(); } } /** * Set up garbage collection event monitoring. * @internal */ setupGCMonitoring() { try { if (!this.gcObserver) { // Create a performance observer for GC events this.gcObserver = new node_perf_hooks_1.PerformanceObserver((list) => { var _a, _b; const entries = list.getEntries(); for (const entry of entries) { if (entry.entryType === 'gc') { const detail = entry.detail; const kind = (_a = detail === null || detail === void 0 ? void 0 : detail.kind) !== null && _a !== void 0 ? _a : 0; const gcKind = this.mapGCKind(kind); const shouldMonitor = !this.options.gcKinds || this.options.gcKinds.includes(gcKind); if (shouldMonitor) { this.isGCInProgress = true; this.lastGCTime = Date.now(); this.emit('gc:start', { type: gcKind, kind, duration: entry.duration, startTime: entry.startTime, }); // After cooldown period, mark GC as completed const cooldownTime = (_b = this.options.gcCooldown) !== null && _b !== void 0 ? _b : 500; setTimeout(() => { this.isGCInProgress = false; this.emit('gc:end', { type: gcKind, kind, duration: entry.duration, startTime: entry.startTime, }); }, cooldownTime); } } } }); // Subscribe to GC notifications this.gcObserver.observe({ entryTypes: ['gc'] }); } } catch (error) { console.warn('Failed to set up GC monitoring:', error); } } /** * Map Node.js GC kinds to our enum values. * @param kind - The GC kind number as reported by PerformanceObserver * @internal */ mapGCKind(kind) { // Map numeric GC kind to our enum values // Based on Node.js GC kind constants: // 1: Scavenge (minor GC) // 2: Mark-Sweep-Compact (major GC) // 4: Incremental marking // 8: Weak callbacks switch (kind) { case 1: return GCKind.Minor; case 2: return GCKind.Major; case 4: return GCKind.Incremental; case 8: return GCKind.WeakCallback; default: return `unknown-${kind.toString()}`; } } /** * Check if garbage collection is currently in progress. * @returns true if GC is in progress, false otherwise * @public */ isGCActive() { return this.isGCInProgress; } /** * Get the time elapsed since the last GC event in milliseconds. * @returns Number of milliseconds since last GC, or Infinity if no GC has occurred * @public */ timeSinceLastGC() { return this.lastGCTime > 0 ? Date.now() - this.lastGCTime : Infinity; } /** * Start the periodic monitoring loop. * @internal */ start() { var _a; this.intervalId = setInterval(() => { // Skip threshold checks if GC is currently in progress if (this.options.monitorGC && this.isGCInProgress) { return; } // Optimization: fetch stats only once per interval const stats = v8_1.default.getHeapSpaceStatistics(); const statsMap = new Map(stats.map((s) => [s.space_name, s])); const mem = process.memoryUsage(); for (const monitor of this.options.monitored) { if (Object.values(HeapSpace).includes(monitor.stat)) { const spaceStat = statsMap.get(monitor.stat.toString()); if (spaceStat) { const statValue = spaceStat['space_used_size']; if (typeof statValue === 'number' && statValue >= monitor.threshold) { this.emit('threshold', { type: 'v8', stat: monitor.stat, value: statValue, threshold: monitor.threshold, gcActive: this.isGCInProgress, timeSinceLastGC: this.timeSinceLastGC(), }); } } } else if (monitor.stat === ProcessMemoryStat.RSS || monitor.stat === ProcessMemoryStat.HeapUsed) { const value = monitor.stat === ProcessMemoryStat.RSS ? mem.rss : mem.heapUsed; if (typeof value === 'number' && value >= monitor.threshold) { this.emit('threshold', { type: 'process', stat: monitor.stat, value, threshold: monitor.threshold, gcActive: this.isGCInProgress, timeSinceLastGC: this.timeSinceLastGC(), }); } } } }, (_a = this.options.interval) !== null && _a !== void 0 ? _a : 1000).unref(); } /** * Stop the monitoring loop. * @public */ stop() { if (this.intervalId) { clearInterval(this.intervalId); this.intervalId = undefined; } if (this.gcObserver) { this.gcObserver.disconnect(); this.gcObserver = undefined; } } /** * Check if the monitor is currently active. * @returns true if monitoring is active, false otherwise * @public */ isMonitoring() { return this.intervalId !== undefined; } } exports.MemoryStatsMonitor = MemoryStatsMonitor;