refactor: reorganize imports and update file paths for consistency

This commit is contained in:
2025-09-27 16:00:12 -05:00
parent 39b105789f
commit db44e148d3
20 changed files with 59 additions and 28 deletions

View File

@@ -0,0 +1,50 @@
// Monitor ligero de memoria y event loop.
// Se activa si defines MEMORY_LOG_INTERVAL_SECONDS.
import { monitorEventLoopDelay } from 'node:perf_hooks';
export interface MemoryMonitorOptions {
intervalSeconds: number;
warnHeapRatio?: number; // 0-1 fracción del máximo estimado antes de warning
}
export function startMemoryMonitor(opts: MemoryMonitorOptions) {
const { intervalSeconds, warnHeapRatio = 0.8 } = opts;
if (intervalSeconds <= 0) return;
const h = monitorEventLoopDelay({ resolution: 20 });
h.enable();
const formatMB = (n: number) => (n / 1024 / 1024).toFixed(1) + 'MB';
// Intento de obtener el límite de heap (puede variar según flags)
let heapLimit = 0;
try {
// @ts-ignore
const v8 = require('v8');
const stats = v8.getHeapStatistics();
heapLimit = stats.heap_size_limit || 0;
} catch {}
setInterval(() => {
const m = process.memoryUsage();
const rss = formatMB(m.rss);
const heapUsed = formatMB(m.heapUsed);
const heapTotal = formatMB(m.heapTotal);
const external = formatMB(m.external);
const elDelay = h.mean / 1e6; // ms
let warn = '';
if (heapLimit) {
const ratio = m.heapUsed / heapLimit;
if (ratio >= warnHeapRatio) {
warn = ` ⚠ heap ${(ratio * 100).toFixed(1)}% del límite (~${formatMB(heapLimit)})`;
}
}
console.log(`[MEM] rss=${rss} heapUsed=${heapUsed} heapTotal=${heapTotal} ext=${external} evLoopDelay=${elDelay.toFixed(2)}ms${warn}`);
// Resetear métricas de event loop delay para la siguiente ventana
h.reset();
}, intervalSeconds * 1000).unref();
}

View File

@@ -0,0 +1,78 @@
// Sistema adicional de optimización de memoria para complementar el monitor existente
export interface MemoryOptimizerOptions {
forceGCInterval?: number; // minutos
maxHeapUsageBeforeGC?: number; // MB
logGCStats?: boolean;
}
export class MemoryOptimizer {
private gcTimer?: NodeJS.Timeout;
private options: Required<MemoryOptimizerOptions>;
constructor(options: MemoryOptimizerOptions = {}) {
this.options = {
forceGCInterval: options.forceGCInterval ?? 15, // cada 15 min por defecto
maxHeapUsageBeforeGC: options.maxHeapUsageBeforeGC ?? 200, // 200MB
logGCStats: options.logGCStats ?? false
};
}
start() {
// Solo habilitar si está disponible el GC manual
if (typeof global.gc !== 'function') {
console.warn('⚠️ Manual GC no disponible. Inicia con --expose-gc para habilitar optimizaciones adicionales.');
return;
}
// Timer para GC forzado periódico
if (this.options.forceGCInterval > 0) {
this.gcTimer = setInterval(() => {
this.performGC('scheduled');
}, this.options.forceGCInterval * 60 * 1000);
this.gcTimer.unref(); // No bloquear el cierre del proceso
}
console.log(`✅ Memory Optimizer iniciado - GC cada ${this.options.forceGCInterval}min, umbral: ${this.options.maxHeapUsageBeforeGC}MB`);
}
stop() {
if (this.gcTimer) {
clearInterval(this.gcTimer);
this.gcTimer = undefined;
}
}
// Método público para forzar GC cuando sea necesario
checkAndOptimize() {
const memUsage = process.memoryUsage();
const heapUsedMB = memUsage.heapUsed / 1024 / 1024;
if (heapUsedMB > this.options.maxHeapUsageBeforeGC) {
this.performGC('threshold');
return true;
}
return false;
}
private performGC(reason: string) {
if (typeof global.gc !== 'function') return;
const before = process.memoryUsage();
const startTime = Date.now();
global.gc();
if (this.options.logGCStats) {
const after = process.memoryUsage();
const duration = Date.now() - startTime;
const heapFreed = (before.heapUsed - after.heapUsed) / 1024 / 1024;
console.log(`🗑️ GC ${reason}: liberó ${heapFreed.toFixed(1)}MB en ${duration}ms`);
}
}
}
// Instancia singleton exportable
export const memoryOptimizer = new MemoryOptimizer();