---
name: performance-baseline
description: Establece y documenta una línea base de rendimiento para el sistema. Mide latencia p50/p95/p99, throughput, uso de memoria y CPU. Identifica cuellos de botella con herramientas de profiling. Guía la optimización con datos, no con suposiciones.
version: "1.0.0"
herramientasPermitidas: [Read, Bash]
evolvable: true  # default para skill estandar
exclusiones:
  - "No cargar para observabilidad de producción en tiempo real (Prometheus, Grafana, alertas) — para monitoreo y alertas de sistemas en producción cargar `monitoring-alertas`."
  - "No cargar para profiling de memoria o CPU con flamegraphs en producción bajo carga real sin un baseline previo — establecer primero la línea base antes de profiling avanzado."
  - "No cargar para load testing de APIs externas o terceros sin autorización — las pruebas de carga sobre infraestructura ajena sin permiso constituyen un ataque DoS."
  - "No cargar para benchmarks de hardware o comparación de proveedores cloud — este skill mide la aplicación propia, no la plataforma subyacente."
---
# Habilidad: Performance Baseline

## Cuándo NO cargar

- La tarea es monitoreo de producción en tiempo real (Prometheus, alertas, dashboards): cargar `monitoring-alertas`.
- La tarea es load testing de sistemas externos o terceros sin autorización escrita: no proceder.
- La tarea es comparar proveedores cloud o benchmarks de hardware: usar documentación del proveedor directamente.
- La tarea es profiling avanzado de un componente ya identificado como cuello de botella: iniciar con este skill para el baseline, luego proceder al profiling específico.

## Propósito

"Optimizar sin medir es adivinar." Esta habilidad establece mediciones reales
del sistema antes de cualquier optimización, documenta el baseline y guía
el trabajo de mejora con evidencia. Ninguna optimización se acepta sin una
comparación before/after con los mismos benchmarks.

## Cuándo activar

- Antes de iniciar una fase de optimización de rendimiento
- Cuando el usuario reporta "el sistema está lento" sin métricas
- Al establecer SLOs para un sistema nuevo
- Antes y después de cambios en queries, caching o arquitectura

---

## Métricas clave a medir

### Latencia (la más importante para APIs)

| Percentil | Descripción | Target típico web |
|-----------|-------------|------------------|
| p50 | Mediana — 50% de requests son más rápidos | < 100ms |
| p95 | 95% de requests terminan antes de este tiempo | < 300ms |
| p99 | El "peor caso razonable" | < 1000ms |
| p99.9 | Outliers extremos | < 3000ms |

**Por qué p95 y p99, no promedio**: El promedio oculta las colas lentas. Un
sistema con p50=50ms y p99=10000ms tiene un promedio "aceptable" pero una
experiencia terrible para 1 de cada 100 usuarios.

### Throughput

- **Requests por segundo (RPS)**: Cuántas peticiones puede manejar el sistema
- **Concurrencia máxima**: Número de requests simultáneos sin degradación
- **Punto de quiebre**: RPS donde la latencia p99 supera el threshold aceptable

### Recursos del sistema

- **CPU**: % de uso en carga normal, pico y stress
- **Memoria**: RSS basal, memoria en carga, crecimiento por hora (leak detection)
- **I/O de BD**: queries/segundo, tiempo de query p95, conexiones activas
- **Network**: bytes in/out, conexiones abiertas

---

## Herramientas de medición por stack

### Backend Python / FastAPI

**Profiling de CPU** — encontrar funciones lentas:
```python
# Con cProfile (incluido en stdlib)
import cProfile
import pstats
import io

def profile_function():
    pr = cProfile.Profile()
    pr.enable()
    # ... función a perfilar
    pr.disable()

    s = io.StringIO()
    ps = pstats.Stats(pr, stream=s).sort_stats('cumulative')
    ps.print_stats(20)  # Top 20 funciones más lentas
    print(s.getvalue())
```

**Profiling de memoria** — detectar leaks:
```bash
pip install memory-profiler
python -m memory_profiler mi_script.py
```

**Profiling de queries SQL** — con SQLAlchemy:
```python
# En desarrollo, activar logging de queries con tiempo
import logging
logging.getLogger('sqlalchemy.engine').setLevel(logging.INFO)

# O con pgBadger / pg_stat_statements en PostgreSQL
# Ver queries más lentas:
SELECT query, calls, total_time, mean_time, stddev_time
FROM pg_stat_statements
ORDER BY mean_time DESC
LIMIT 20;
```

**Load testing** — con locust:
```python
# locustfile.py
from locust import HttpUser, task, between

class APIUser(HttpUser):
    wait_time = between(0.1, 0.5)

    @task(3)  # peso 3 = ejecuta 3x más frecuente
    def listar_recursos(self):
        self.client.get("/api/v1/recursos",
                        headers={"Authorization": f"Bearer {self.token}"})

    @task(1)
    def crear_recurso(self):
        self.client.post("/api/v1/recursos",
                         json={"nombre": "test"},
                         headers={"Authorization": f"Bearer {self.token}"})
```

```bash
# Ejecutar con 100 usuarios, rampa de 10/segundo
locust -f locustfile.py --headless -u 100 -r 10 --run-time 2m \
  --host http://localhost:8000
```

### Frontend Angular

**Core Web Vitals** (métricas que Google y usuarios ven):
| Métrica | Descripción | Target |
|---------|-------------|--------|
| LCP | Largest Contentful Paint — tiempo hasta que el contenido principal es visible | < 2.5s |
| FID / INP | First Input Delay / Interaction to Next Paint | < 100ms |
| CLS | Cumulative Layout Shift — estabilidad visual | < 0.1 |
| TTFB | Time to First Byte | < 600ms |

```bash
# Lighthouse CLI
npm install -g lighthouse
lighthouse http://localhost:4200 --output=json --output-path=lighthouse-report.json

# Analizar bundle size
ng build --stats-json
npx webpack-bundle-analyzer dist/*/stats.json
```

**Profiling de Angular en desarrollo**:
```typescript
// Habilitar profiler de Angular
import { enableProdMode } from '@angular/core';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';

// En main.ts para development
platformBrowserDynamic().bootstrapModule(AppModule)
  .then(() => {
    // Chrome DevTools > Performance > Profile
    // Buscar "Angular" en la traza para ver change detection cycles
  });
```

---

## Profiling browser-based — Trazas de rendimiento real

Lighthouse da un score sintético. Las trazas de rendimiento dan datos reales de lo que
ocurre frame-a-frame en el navegador. Preferir trazas cuando se necesita diagnosticar
**por qué** un Web Vital falla, no solo **que** falla.

**Workflow de profiling con DevTools** (manual o vía MCP con `chrome-devtools-mcp`):

1. Navegar a la página bajo prueba con throttling de CPU 4x y red "Fast 3G"
2. Grabar traza de rendimiento (botón Record → interactuar → Stop)
3. Extraer Performance Insights — cada uno indica una causa raíz específica:

| Performance Insight | Qué detecta | Acción correctiva |
|--------------------|--------------|--------------------|
| **LCPBreakdown** | Desglose de LCP: TTFB + load delay + load time + render delay | Optimizar el componente con mayor porcentaje |
| **DocumentLatency** | TTFB alto del documento principal | Revisar server response time, CDN, redirects |
| **RenderBlocking** | Scripts/CSS que bloquean el primer render | `async`/`defer` en scripts, inline critical CSS |
| **ThirdPartyIssues** | Scripts de terceros que consumen main thread | Lazy-load o eliminar; mover a web worker si es posible |
| **LayoutShift** | Elementos que causan CLS (imágenes sin dimensiones, fonts) | `width`/`height` explícitos, `font-display: swap` |
| **LongTask** | Tareas JS > 50ms que bloquean interactividad (INP) | Code-split, `requestIdleCallback`, web workers |

**Gotcha crítico**: Lighthouse scores (lab data) ≠ experiencia real del usuario (field data).
Un LCP de 1.8s en Lighthouse puede ser 4.2s en un dispositivo móvil real con red 4G.
Para datos de campo reales usar CrUX (Chrome User Experience Report) o web-vitals library
en producción.

---

## Plantilla de documento baseline

### `.planning/PERFORMANCE-BASELINE.md`

```markdown
# PERFORMANCE-BASELINE.md
**Fecha de medición**: [fecha]
**Versión del sistema**: [git commit hash]
**Entorno**: [staging / producción / local con datos reales]
**Herramientas usadas**: [locust, pytest-benchmark, lighthouse, etc.]

## Condiciones del test
- Datos en BD: [número de registros en tablas principales]
- Concurrencia simulada: [N usuarios simultáneos]
- Duración del test: [N minutos]
- Hardware del servidor: [specs o instancia cloud]

## Resultados de latencia (API)

| Endpoint | p50 | p95 | p99 | RPS máx |
|----------|-----|-----|-----|---------|
| GET /api/v1/[recurso] | Xms | Xms | Xms | N |
| POST /api/v1/[recurso] | Xms | Xms | Xms | N |

## Resultados de recursos

| Métrica | Basal | Bajo carga | Pico |
|---------|-------|-----------|------|
| CPU % | | | |
| Memoria RSS | | | |
| Conexiones BD activas | | | |
| Queries/segundo | | | |

## Core Web Vitals (si aplica)

| Métrica | Valor medido | Target | Estado |
|---------|-------------|--------|--------|
| LCP | | < 2.5s | PASS/FAIL |
| INP | | < 100ms | PASS/FAIL |
| CLS | | < 0.1 | PASS/FAIL |

## Top 5 queries más lentas
| Query | Duración media | Llamadas/min | Plan de optimización |
|-------|---------------|-------------|---------------------|
|       |               |             |                     |

## Cuellos de botella identificados
1. [Descripción, causa raíz, impacto estimado]

## SLOs propuestos (basados en baseline)
| Métrica | SLO | Alerta si supera |
|---------|-----|-----------------|
| Latencia p95 | < Xms | > Xms |
| Error rate | < 0.1% | > 0.5% |
| Disponibilidad | > 99.5% | < 99.0% |
```

---

## Proceso de optimización guiada por datos

```
1. Medir → documentar baseline
2. Identificar el cuello de botella más costoso (no el más fácil)
3. Hipótesis: "Creo que X es lento porque Y"
4. Implementar UNA optimización
5. Medir con los MISMOS benchmarks del baseline
6. Comparar before/after
7. Documentar resultado en PERFORMANCE-BASELINE.md
8. Si mejoró: hacer commit, continuar con siguiente cuello de botella
9. Si no mejoró: revertir, revisar hipótesis
```

### Optimizaciones por orden de impacto típico

1. **Índices de BD faltantes** — impacto: 10x-1000x en queries lentas
2. **N+1 queries** — impacto: reducción de O(n) queries a O(1)
3. **Caching de resultados costosos** — impacto: 5x-50x en datos calientes
4. **Connection pooling** — impacto: elimina overhead de conexiones
5. **Paginación de resultados grandes** — impacto: reducción de tiempo y memoria
6. **Bundle splitting en frontend** — impacto: reducción de tiempo de carga inicial

**Anti-patrón a evitar**: optimizar código Python/TypeScript antes de verificar
que las queries de BD no son el problema real. El 80% de los problemas de
rendimiento en aplicaciones web tienen origen en la capa de datos.

---

## Gotchas / Errores comunes no obvios

**Locust reporta latencia p99 dentro del target pero los usuarios se quejan de lentitud porque Locust mide solo el tiempo de respuesta de red y no incluye el tiempo de renderizado del navegador ni los assets**: en una prueba de carga Locust con 100 usuarios y p99=400ms, los usuarios reales experimentan 2.8s porque el bundle JS pesa 3MB y se descarga en paralelo con la API. Causa: Locust mide exclusivamente el tiempo de la llamada HTTP al endpoint, sin incluir la carga de assets estáticos, scripts ni el tiempo de parseo del navegador. Fix: complementar Locust con Lighthouse y trazas de Chrome DevTools. El baseline real de experiencia de usuario combina ambas mediciones, no solo la capa API.

**`memory_profiler` en Python muestra consumo de memoria estable pero la aplicación en producción tiene un leak real porque `memory_profiler` mide solo el proceso Python y no la memoria del pool de conexiones de SQLAlchemy ni los buffers del driver de PostgreSQL**: después de 4 horas de carga, el RSS del proceso crece de 150MB a 1.2GB sin que `@profile` muestre ninguna función acumulando objetos. Causa: `psycopg2` y `asyncpg` usan buffers de memoria C nativos no rastreados por el GC de Python. Fix: medir el RSS completo con `psutil.Process().memory_info().rss` cada N requests en producción, y revisar `pg_stat_activity` para conexiones huérfanas. Si el pool no tiene `pool_recycle` configurado, las conexiones viejas retienen buffers indefinidamente.

**Los resultados de `pg_stat_statements` son inestables en la primera semana de activarlo porque el módulo sólo agrega estadísticas de queries ejecutadas después de su activación**: hacer `CREATE EXTENSION pg_stat_statements` en una BD existente con meses de operación muestra historial vacío; las queries con `mean_time` alto son outliers de las primeras horas, no el comportamiento habitual. Causa: `pg_stat_statements` no retroactiva — solo mide desde su activación. Fix: esperar al menos 48 horas bajo carga normal antes de sacar conclusiones del baseline de queries. Hacer `SELECT pg_stat_statements_reset()` al inicio para tener un período de medición limpio y definido.

**El throughput máximo medido con `locust --headless` es artificialmente bajo cuando el servidor de prueba corre en la misma máquina que la aplicación porque ambos compiten por CPU**: en desarrollo local con 4 núcleos, Locust consume 1.5 núcleos para simular 200 usuarios, dejando solo 2.5 núcleos para la aplicación. El baseline registra 340 RPS como máximo, pero en staging dedicado el mismo sistema alcanza 820 RPS. Causa: la contención de recursos en la máquina local es un factor de confusión sistemático. Fix: ejecutar siempre las pruebas de carga desde una máquina separada (o en CI con runner dedicado) hacia la aplicación en un servidor aislado. Documentar en el baseline si la prueba fue local o remota.
