# Sistema de Monitoreo de Memoria - SLM Framework API

Sistema completo de monitoreo de memoria, detección de fugas (memory leaks) y notificaciones automáticas.

## 📁 Ubicación

```
api/slm.framework/system/memory-monitor.slm
api/slm.framework/system/notifications.slm
```

## 🎯 Características

- ✅ **Monitoreo Automático**: Medición continua cada 5 minutos (configurable)
- ✅ **Detección de Fugas**: Análisis de regresión lineal para detectar tendencias
- ✅ **Alertas por Niveles**: Warning (80%) y Critical (90%)
- ✅ **Notificaciones por Email**: Alertas automáticas vía Nodemailer
- ✅ **Heap Snapshots**: Generación de snapshots para análisis con Chrome DevTools
- ✅ **Estadísticas Históricas**: Métricas guardadas para análisis
- ✅ **Garbage Collection Manual**: Limpieza forzada cuando se detecta uso alto
- 🚧 **Slack/WhatsApp**: Preparado para implementación futura

## 🚀 Inicio Rápido

### 1. Configurar Variables de Entorno

En `api/.env`:

```env
# Memory Monitor
MEMORY_ALERT_THRESHOLD=0.80
MEMORY_CRITICAL_THRESHOLD=0.90
MEMORY_MONITOR_INTERVAL=300000
MEMORY_EMAIL_ALERTS=true
MEMORY_ALERT_EMAIL=admin@example.com
MEMORY_LEAK_SLOPE=2

# Email Notifications
NOTIFICATIONS_EMAIL_ENABLED=true
SMTP_HOST=localhost
SMTP_PORT=1025
SMTP_SECURE=false
SMTP_USER=
SMTP_PASS=
SMTP_FROM=noreply@slm-framework.local
SMTP_FROM_NAME=SLM Framework API
```

### 2. Integrar en server.js

```javascript
const memoryMonitor = require('./slm.framework/system/memory-monitor.slm');

// Iniciar monitoreo al arrancar el servidor
memoryMonitor.startMonitoring();

// Detener al cerrar
process.on('SIGTERM', () => {
    memoryMonitor.stopMonitoring();
    server.close(() => {
        process.exit(0);
    });
});
```

### 3. Instalar Dependencias

```bash
cd api
npm install nodemailer
```

## 📊 Endpoints de Debug

### GET /debug/memory

Obtiene métricas actuales de memoria:

```bash
curl -H "X-API-Key: dev-key-12345" \
  http://localhost:3000/debug/memory
```

**Respuesta:**
```json
{
  "error": false,
  "data": {
    "timestamp": 1737849600000,
    "uptime": 3600,
    "rssMB": 150,
    "heapTotalMB": 120,
    "heapUsedMB": 80,
    "heapPercentage": 67,
    "status": "healthy"
  }
}
```

### GET /debug/memory/stats

Obtiene estadísticas históricas:

```bash
curl -H "X-API-Key: dev-key-12345" \
  http://localhost:3000/debug/memory/stats
```

**Respuesta:**
```json
{
  "error": false,
  "data": {
    "average": { "heapUsedMB": 75, "heapPercentage": 62 },
    "peak": { "heapUsedMB": 95, "heapPercentage": 79 },
    "measurements": 120,
    "uptime": 36000,
    "leakDetection": null
  }
}
```

### GET /debug/memory/leak-detection

Ejecuta detección de fugas:

```bash
curl -H "X-API-Key: dev-key-12345" \
  http://localhost:3000/debug/memory/leak-detection
```

**Respuesta (sin fuga detectada):**
```json
{
  "error": false,
  "data": {
    "detected": false,
    "message": "No memory leak detected"
  }
}
```

**Respuesta (fuga detectada):**
```json
{
  "error": false,
  "data": {
    "detected": true,
    "slope": 2.5,
    "recommendation": "Create a heap snapshot and analyze with Chrome DevTools",
    "measurements": 50
  }
}
```

### GET /debug/heap-snapshot

Genera un heap snapshot para análisis:

```bash
curl -H "X-API-Key: dev-key-12345" \
  http://localhost:3000/debug/heap-snapshot
```

**Respuesta:**
```json
{
  "error": false,
  "data": {
    "success": true,
    "file": "/app/api/heap-1737849600000.heapsnapshot",
    "size": "25.3 MB",
    "message": "Download and analyze in Chrome DevTools"
  }
}
```

### POST /debug/gc

Fuerza garbage collection (requiere `--expose-gc`):

```bash
curl -X POST \
  -H "X-API-Key: dev-key-12345" \
  http://localhost:3000/debug/gc
```

**Respuesta:**
```json
{
  "error": false,
  "data": {
    "success": true,
    "before": { "heapUsedMB": 95, "heapPercentage": 79 },
    "after": { "heapUsedMB": 65, "heapPercentage": 54 },
    "freed": 30
  }
}
```

## 🧠 Buenas Prácticas para Prevenir Memory Leaks

### 1. Event Listeners No Removidos

❌ **MAL - Fuga de Memoria:**
```javascript
module.exports = async (req, res, next, slmapi) => {
    // Se crea un listener cada vez que se llama el endpoint
    process.on('SIGTERM', () => {
        console.log('Cleanup...');
    });

    // Listener nunca se remueve
    res.json({ success: true });
};
```

✅ **BIEN - Sin Fuga:**
```javascript
let cleanupRegistered = false;

module.exports = async (req, res, next, slmapi) => {
    if (!cleanupRegistered) {
        const cleanup = () => {
            console.log('Cleanup...');
        };

        process.once('SIGTERM', cleanup); // 'once' se auto-remueve
        cleanupRegistered = true;
    }

    res.json({ success: true });
};
```

**O usar removeListener explícitamente:**
```javascript
const EventEmitter = require('events');
const emitter = new EventEmitter();

function myHandler(data) {
    console.log(data);
}

// Agregar listener
emitter.on('event', myHandler);

// SIEMPRE removerlo cuando no se necesite
emitter.removeListener('event', myHandler);
```

---

### 2. Closures con Referencias a Objetos Grandes

❌ **MAL - Fuga de Memoria:**
```javascript
let cache = {};

module.exports = async (req, res, next, slmapi) => {
    const userId = req.params.id;

    // Se acumula en memoria indefinidamente
    cache[userId] = {
        userData: await slmapi.getObjectById(userId),
        timestamp: Date.now(),
        largeData: Buffer.alloc(10 * 1024 * 1024) // 10MB
    };

    res.json(cache[userId]);
};
```

✅ **BIEN - Con Límite y Expiración:**
```javascript
const NodeCache = require('node-cache');
const cache = new NodeCache({
    stdTTL: 600,        // 10 minutos de TTL
    maxKeys: 100,       // Máximo 100 elementos
    checkperiod: 120    // Limpieza cada 2 minutos
});

module.exports = async (req, res, next, slmapi) => {
    const userId = req.params.id;

    let userData = cache.get(userId);

    if (!userData) {
        userData = await slmapi.getObjectById(userId);
        cache.set(userId, userData);
    }

    res.json(userData);
};
```

---

### 3. Timers y Intervals No Limpiados

❌ **MAL - Fuga de Memoria:**
```javascript
module.exports = async (req, res, next, slmapi) => {
    // Se crea un intervalo cada vez
    setInterval(() => {
        console.log('Checking status...');
    }, 5000);

    res.json({ success: true });
};
```

✅ **BIEN - Con Limpieza:**
```javascript
let statusInterval = null;

function startStatusCheck() {
    if (statusInterval) return;

    statusInterval = setInterval(() => {
        console.log('Checking status...');
    }, 5000);
}

function stopStatusCheck() {
    if (statusInterval) {
        clearInterval(statusInterval);
        statusInterval = null;
    }
}

// Limpiar al cerrar
process.on('SIGTERM', stopStatusCheck);

module.exports = async (req, res, next, slmapi) => {
    startStatusCheck();
    res.json({ success: true });
};
```

---

### 4. Variables Globales que Crecen Indefinidamente

❌ **MAL - Fuga de Memoria:**
```javascript
const logs = []; // Array global sin límite

module.exports = async (req, res, next, slmapi) => {
    // Se acumula infinitamente
    logs.push({
        timestamp: Date.now(),
        user: req.user,
        action: 'something'
    });

    res.json({ logs: logs.length });
};
```

✅ **BIEN - Con Límite de Tamaño:**
```javascript
const MAX_LOGS = 1000;
const logs = [];

function addLog(entry) {
    logs.push(entry);

    // Mantener solo los últimos MAX_LOGS
    if (logs.length > MAX_LOGS) {
        logs.shift(); // Remover el más antiguo
    }
}

module.exports = async (req, res, next, slmapi) => {
    addLog({
        timestamp: Date.now(),
        user: req.user,
        action: 'something'
    });

    res.json({ logs: logs.length });
};
```

---

### 5. Promesas No Manejadas Correctamente

❌ **MAL - Fuga de Memoria:**
```javascript
module.exports = async (req, res, next, slmapi) => {
    const promises = [];

    // Se crean muchas promesas sin esperar
    for (let i = 0; i < 10000; i++) {
        promises.push(
            slmapi.getObjectById(`id-${i}`)
                .then(result => {
                    // Guardar en algún lado...
                    cache[`id-${i}`] = result;
                })
        );
    }

    // No se esperan, se acumulan en memoria
    res.json({ started: promises.length });
};
```

✅ **BIEN - Con Promise.all y Límite:**
```javascript
const pLimit = require('p-limit');
const limit = pLimit(10); // Solo 10 concurrentes

module.exports = async (req, res, next, slmapi) => {
    const ids = Array.from({ length: 10000 }, (_, i) => `id-${i}`);

    // Procesar en lotes con límite de concurrencia
    const promises = ids.map(id =>
        limit(async () => {
            const result = await slmapi.getObjectById(id);
            return { id, result };
        })
    );

    // Esperar a que terminen
    const results = await Promise.all(promises);

    res.json({ processed: results.length });
};
```

---

### 6. Streams No Cerrados

❌ **MAL - Fuga de Memoria:**
```javascript
const fs = require('fs');

module.exports = async (req, res, next, slmapi) => {
    const stream = fs.createReadStream('/large-file.log');

    stream.on('data', (chunk) => {
        // Procesar chunk...
    });

    // Stream nunca se cierra explícitamente
    res.json({ success: true });
};
```

✅ **BIEN - Con Limpieza:**
```javascript
const fs = require('fs');

module.exports = async (req, res, next, slmapi) => {
    const stream = fs.createReadStream('/large-file.log');

    stream.on('data', (chunk) => {
        // Procesar chunk...
    });

    stream.on('end', () => {
        stream.destroy(); // Limpiar recursos
    });

    stream.on('error', (err) => {
        stream.destroy();
        throw err;
    });

    res.json({ success: true });
};
```

---

### 7. Referencias Circulares

❌ **MAL - Fuga de Memoria:**
```javascript
module.exports = async (req, res, next, slmapi) => {
    const objA = { name: 'A' };
    const objB = { name: 'B' };

    // Referencia circular
    objA.ref = objB;
    objB.ref = objA;

    // Se almacena con referencia circular
    await slmapi.setNewObject(objA);

    res.json({ success: true });
};
```

✅ **BIEN - Sin Referencias Circulares:**
```javascript
module.exports = async (req, res, next, slmapi) => {
    const objA = {
        id: 'a',
        name: 'A',
        relatedId: 'b' // Solo el ID, no la referencia
    };

    const objB = {
        id: 'b',
        name: 'B',
        relatedId: 'a'
    };

    await slmapi.setNewObject(objA);
    await slmapi.setNewObject(objB);

    res.json({ success: true });
};
```

---

### 8. Axios/HTTP Clients Sin Timeout

❌ **MAL - Fuga de Memoria:**
```javascript
const axios = require('axios');

module.exports = async (req, res, next, slmapi) => {
    // Sin timeout, puede quedar colgado indefinidamente
    const response = await axios.get('https://slow-api.com/data');

    res.json(response.data);
};
```

✅ **BIEN - Con Timeout y Manejo de Errores:**
```javascript
const axios = require('axios');

const client = axios.create({
    timeout: 10000, // 10 segundos
    maxRedirects: 5,
    maxContentLength: 50 * 1024 * 1024 // 50MB máximo
});

module.exports = async (req, res, next, slmapi) => {
    try {
        const response = await client.get('https://slow-api.com/data', {
            timeout: 5000 // Timeout específico para esta request
        });

        res.json(response.data);
    } catch (error) {
        if (error.code === 'ECONNABORTED') {
            return res.status(504).json({ error: 'Request timeout' });
        }
        throw error;
    }
};
```

---

### 9. Caché Sin Estrategia de Invalidación

❌ **MAL - Fuga de Memoria:**
```javascript
const cache = new Map();

module.exports = async (req, res, next, slmapi) => {
    const key = req.params.id;

    if (!cache.has(key)) {
        const data = await slmapi.getObjectById(key);
        cache.set(key, data); // Nunca se invalida
    }

    res.json(cache.get(key));
};
```

✅ **BIEN - Con TTL y Límite de Tamaño:**
```javascript
class LRUCache {
    constructor(maxSize = 100) {
        this.cache = new Map();
        this.maxSize = maxSize;
    }

    get(key) {
        if (!this.cache.has(key)) return null;

        const item = this.cache.get(key);

        // Verificar expiración
        if (Date.now() > item.expires) {
            this.cache.delete(key);
            return null;
        }

        // LRU: mover al final
        this.cache.delete(key);
        this.cache.set(key, item);

        return item.value;
    }

    set(key, value, ttl = 600000) { // 10 min default
        // Si está lleno, remover el más antiguo
        if (this.cache.size >= this.maxSize) {
            const firstKey = this.cache.keys().next().value;
            this.cache.delete(firstKey);
        }

        this.cache.set(key, {
            value,
            expires: Date.now() + ttl
        });
    }
}

const cache = new LRUCache(100);

module.exports = async (req, res, next, slmapi) => {
    const key = req.params.id;

    let data = cache.get(key);

    if (!data) {
        data = await slmapi.getObjectById(key);
        cache.set(key, data, 300000); // 5 min TTL
    }

    res.json(data);
};
```

---

### 10. Logging Excesivo en Producción

❌ **MAL - Fuga de Memoria:**
```javascript
const debugLogs = [];

module.exports = async (req, res, next, slmapi) => {
    // Se guardan TODOS los logs en memoria
    debugLogs.push({
        timestamp: Date.now(),
        user: req.user,
        body: req.body,
        headers: req.headers
    });

    // Procesar...

    res.json({ logs: debugLogs.length });
};
```

✅ **BIEN - Logging Externo:**
```javascript
const logger = require('./slm.framework/system/logger.slm');

module.exports = async (req, res, next, slmapi) => {
    // Logger escribe a archivo/servicio externo
    logger.info('Request received', {
        user: req.user?.id,
        path: req.path
    });

    // Solo en desarrollo
    if (process.env.NODE_ENV === 'development') {
        console.log('Debug info:', req.body);
    }

    res.json({ success: true });
};
```

---

## 🔬 Análisis de Heap Snapshots

### 1. Generar Snapshot

```bash
curl -H "X-API-Key: dev-key-12345" \
  http://localhost:3000/debug/heap-snapshot
```

### 2. Descargar el Archivo

```bash
docker cp slm-api:/app/api/heap-1737849600000.heapsnapshot ./
```

### 3. Analizar en Chrome DevTools

1. Abrir Chrome DevTools (F12)
2. Ir a la pestaña "Memory"
3. Click en "Load" y seleccionar el archivo `.heapsnapshot`
4. Buscar objetos que crecen indefinidamente:
   - Ordenar por "Retained Size"
   - Buscar constructores con muchas instancias
   - Revisar referencias usando "Retainers"

### 4. Qué Buscar

- **Arrays o Maps muy grandes**: Posible caché sin límite
- **Closures con objetos retenidos**: Event listeners no removidos
- **Timers activos**: Setinterval/setTimeout no limpiados
- **Strings duplicados**: Posible fuga en logging
- **Objects con referencias circulares**: Relaciones bidireccionales

---

## 📧 Notificaciones por Email

### Configurar SMTP

**Desarrollo (MailHog):**
```env
NOTIFICATIONS_EMAIL_ENABLED=true
SMTP_HOST=localhost
SMTP_PORT=1025
SMTP_SECURE=false
SMTP_USER=
SMTP_PASS=
```

**Producción (Gmail/SendGrid/etc):**
```env
NOTIFICATIONS_EMAIL_ENABLED=true
SMTP_HOST=smtp.gmail.com
SMTP_PORT=587
SMTP_SECURE=true
SMTP_USER=your-email@gmail.com
SMTP_PASS=your-app-password
```

### Tipos de Alertas

1. **Warning Alert** (80% heap usage):
```
Subject: [WARNING] Memory Alert - SLM Framework API
Body: Heap usado: 96 MB / 120 MB (80%)
```

2. **Critical Alert** (90% heap usage):
```
Subject: [CRITICAL] Memory Alert - SLM Framework API
Body: Heap usado: 108 MB / 120 MB (90%)
```

3. **Memory Leak Detected**:
```
Subject: [CRITICAL] Memory Leak Detected - SLM Framework API
Body: Fuga detectada con pendiente de 2.5 MB/medición
Recomendación: Crear heap snapshot y analizar
```

### Enviar Email de Prueba

```javascript
const notif = require('./slm.framework/system/notifications.slm');

await notif.sendTestEmail('admin@example.com');
```

---

## 🔧 Configuración Avanzada

### Ajustar Límites de Memoria de Node.js

```bash
# Aumentar heap máximo a 4GB
node --max-old-space-size=4096 server.js

# Habilitar garbage collection manual
node --expose-gc server.js

# Mostrar estadísticas de GC
node --trace-gc server.js
```

### Docker Compose

```yaml
services:
  api:
    image: node:20-alpine
    environment:
      NODE_OPTIONS: "--max-old-space-size=2048 --expose-gc"
    command: node server.js
```

---

## 📈 Monitoreo en Producción

### Métricas Recomendadas

1. **Heap Usage Percentage**: Mantener bajo 70%
2. **RSS (Resident Set Size)**: Monitorear crecimiento
3. **Garbage Collection Frequency**: No debe ser muy frecuente
4. **Response Time**: Aumenta si hay presión de memoria

### Herramientas Externas

- **New Relic**: Monitoreo completo de Node.js
- **DataDog**: APM con análisis de memoria
- **Prometheus + Grafana**: Métricas customizadas
- **PM2**: Gestión de procesos con auto-restart

---

## ✅ Checklist de Prevención

- [ ] Usar `node-cache` o similar con TTL para cachés
- [ ] Limitar tamaño de arrays/objects globales
- [ ] Siempre remover event listeners con `removeListener()`
- [ ] Usar `once()` en vez de `on()` cuando sea posible
- [ ] Limpiar timers con `clearInterval()`/`clearTimeout()`
- [ ] Configurar timeouts en requests HTTP
- [ ] Evitar referencias circulares en objetos
- [ ] Cerrar streams explícitamente
- [ ] Usar streaming para archivos grandes
- [ ] Implementar paginación en queries de DB
- [ ] Monitorear memoria en desarrollo
- [ ] Generar heap snapshots periódicamente
- [ ] Configurar alertas por email
- [ ] Documentar límites de recursos

---

## 🚀 Recursos Adicionales

- [Node.js Memory Management](https://nodejs.org/en/docs/guides/simple-profiling/)
- [V8 Heap Snapshots](https://developer.chrome.com/docs/devtools/memory-problems/)
- [Memory Leaks in Node.js](https://blog.appsignal.com/2020/05/06/avoiding-memory-leaks-in-nodejs.html)
- [Node.js Best Practices](https://github.com/goldbergyoni/nodebestpractices)

---

**Versión:** 1.0.0
**Última actualización:** Enero 2026
**Autor:** SLM Framework Team
