Solución de Profiling para ProCash

JMeter + VisualVM

Problema

El Desafío

  • Aplicación legacy: Java 1.7 + Tomcat 7
  • 9 aplicaciones web en producción
  • Problemas de rendimiento sin identificar
  • Herramientas comerciales incompatibles (YourKit, JProfiler)

Lo Que Necesitamos

  • Identificar cuellos de botella (CPU, memoria)
  • Detectar memory leaks
  • Analizar comportamiento bajo carga real
  • Sin modificar código de producción

Solución Propuesta

Herramientas Open Source

  1. JMeter 3.1 - Generación de carga realista
  2. VisualVM 1.3.9 - Profiling y análisis

¿Por qué estas herramientas?

  • Gratuitas (open source)
  • Compatible con Java 1.7
  • Sin instalación en producción (solo JVM args)
  • Estándares de industria
  • Bajo overhead (<5%)

Arquitectura de la Solución

┌─────────────────────────────────────────────────┐
│     MÁQUINA DEL OPERADOR (Local PC)             │
│                                                 │
│  ┌────────────────┐      ┌──────────────────┐   │
│  │  JMeter 3.1    │      │  VisualVM 1.3.9  │   │
│  │  Genera carga  │      │  Monitorea app   │   │
│  │  HTTP/JSON-RPC │      │  CPU/Memoria/GC  │   │
│  └────────┬───────┘      └────────┬─────────┘   │
└───────────┼──────────────────────┼──────────────┘
            │                      │
            │ HTTP (puerto 8080)   │ JMX (puerto 9999)
            │                      │
┌───────────▼──────────────────────▼──────────────┐
│     EC2 DE PRUEBAS (Test Environment)           │
│                                                 │
│  ┌────────────────────────────────────────────┐ │
│  │  Tomcat 7.0.99 + 9 Apps ProCash            │ │
│  │  Java 1.7 con JMX habilitado               │ │
│  │  GC logging activo                         │ │
│  └────────────────────────────────────────────┘ │
│                                                 │
│  Resultados guardados localmente:               │
│  - /opt/tomcat/logs/gc.log                      │
│  - Heap dumps (bajo demanda)                    │
│  - Thread dumps (bajo demanda)                  │
└─────────────────────────────────────────────────┘

Componente 1: JMeter

¿Qué hace?

  • Graba flujos de usuario reales (login, navegación, formularios)
  • Reproduce esos flujos con N usuarios concurrentes
  • Mide tiempos de respuesta, throughput, errores

Cómo funciona

  1. Actúa como proxy HTTP (puerto 8888)
  2. El navegador envía tráfico a través del proxy
  3. JMeter graba todas las peticiones HTTP/JSON-RPC
  4. Guarda el flujo como "test plan" (.jmx)
  5. Reproduce el flujo con 1, 5, 10, 20+ usuarios

Resultado

  • Carga realista (no sintética)
  • Incluye sesiones, cookies, tokens CSRF
  • Reportes HTML con gráficas de rendimiento

Componente 2: VisualVM

¿Qué hace?

  • Monitorea la JVM en tiempo real (vía JMX remoto)
  • Identifica métodos que consumen CPU (hotspots)
  • Detecta memory leaks y objetos retenidos
  • Analiza threads (deadlocks, contención)
  • Visualiza comportamiento del GC

Cómo funciona

  1. Se conecta al puerto JMX 9999 del servidor
  2. No requiere instalar agente (usa JMX estándar de Java)
  3. Observa la app mientras JMeter genera carga
  4. Toma snapshots para análisis offline

Resultado

  • Perfil de CPU (qué métodos son lentos)
  • Heap dumps (análisis de memoria)
  • Thread dumps (estado de hilos)

Integración: JMeter + VisualVM

Trabajan en Paralelo

Terminal 1:                    Terminal 2:
  $ visualvm                     $ jmeter -n -t test.jmx
  (conectado a JMX 9999)         (genera carga HTTP)
        │                               │
        └────────► Observa ◄────────────┘
                    │
              ┌─────▼─────┐
              │  Tomcat   │
              │  Apps     │
              └───────────┘

Flujo de Trabajo

  1. Iniciar VisualVM → Conectar a puerto 9999
  2. Activar profiling de CPU en VisualVM
  3. Ejecutar JMeter → Carga progresiva (1→5→10→20 usuarios)
  4. Observar en VisualVM → Identificar hotspots
  5. Guardar snapshots → Análisis posterior

¿Qué se Instala en Producción/Pruebas?

Respuesta: NADA adicional

Solo se necesitan argumentos JVM en setenv.sh:

# JMX Remote (para VisualVM)
-Dcom.sun.management.jmxremote
-Dcom.sun.management.jmxremote.port=9999
-Dcom.sun.management.jmxremote.authenticate=false
-Dcom.sun.management.jmxremote.ssl=false

# GC Logging (para análisis de memoria)
-verbose:gc
-XX:+PrintGCDetails
-XX:+PrintGCTimeStamps
-Xloggc:/opt/tomcat/logs/gc.log

Esto ya está configurado

Ubicación de Resultados

Opción 1: Resultados en Máquina Local (Recomendado)

~/profiling-results/
├── jmeter-testplans/
│   └── procash-workflow.jmx         # Test plan grabado
│
├── jmeter-results/
│   ├── results-20251006.jtl         # Datos raw de JMeter
│   └── html-report/                 # Dashboard HTML
│       └── index.html               # ← Abrir en navegador
│
├── visualvm-snapshots/
│   ├── cpu-profile-20251006.nps     # Perfil de CPU
│   ├── heap-dump-20251006.hprof     # Análisis de memoria
│   └── thread-dump-20251006.txt     # Estado de threads
│
└── gc-logs/
    └── gc-20251006.log              # Log de GC (copiado de servidor)

Ventajas:

  • Todos los datos en tu PC
  • Análisis offline (sin conexión al servidor)
  • Fácil de compartir (carpeta comprimida)
  • No consume espacio en servidor

Ubicación de Resultados

Opción 2: Resultados en Servidor EC2

# En el servidor EC2 de pruebas
/opt/profiling-results/
├── jmeter-reports/
│   └── 20251006/index.html
├── heap-dumps/
│   └── heap-20251006.hprof
└── gc-logs/
    └── gc.log

Ventajas:

  • Centralizado (un solo lugar)
  • Automatizable (cron jobs)

Desventaja: Consume espacio en disco

Descarga de Resultados y Dashboards

Descargar Resultados del Servidor

# Opción 1: SCP (copiar archivos individuales)
scp ec2-user@server:/opt/tomcat/logs/gc.log ~/profiling-results/

# Opción 2: Rsync (sincronizar carpetas completas)
rsync -avz ec2-user@server:/opt/profiling-results/ ~/profiling-results/

# Opción 3: Comprimir y descargar
ssh ec2-user@server "tar czf /tmp/results.tar.gz /opt/profiling-results"
scp ec2-user@server:/tmp/results.tar.gz ~/
tar xzf results.tar.gz

Visualizar Localmente

# Reporte JMeter (dashboard HTML)
firefox ~/profiling-results/jmeter-results/html-report/index.html

# VisualVM snapshots
visualvm --openfile ~/profiling-results/visualvm-snapshots/cpu-profile.nps

# Heap dumps
visualvm --openfile ~/profiling-results/heap-dump.hprof

Arquitectura Completa: Test + Producción

┌──────────────────────────────────────────────────────────┐
│  MÁQUINA LOCAL (Operador)                                │
│  - JMeter: genera carga                                  │
│  - VisualVM: analiza resultados                          │
│  - Todos los resultados guardados localmente             │
└────────┬──────────────────────────┬──────────────────────┘
         │                          │
         │                          │
┌────────▼──────────────┐  ┌────────▼──────────────────────┐
│  EC2 TEST             │  │  EC2 PRODUCCIÓN               │
│  - Profiling activo   │  │  - Solo monitoreo básico      │
│  - JMeter load tests  │  │  - JMX pasivo                 │
│  - VisualVM profiling │  │  - GC logging                 │
│  - Heap dumps         │  │  - Sin profiling CPU          │
└───────────────────────┘  └───────────────────────────────┘
         │                          │
         │                          │
         └──────► Compara ◄─────────┘
                Resultados
        (Test vs Producción)

Flujo de Datos: De la Grabación al Dashboard

Paso 1: Grabación (Una sola vez)

Navegador ──proxy──► JMeter Recorder ──► test-plan.jmx

Paso 2: Ejecución en Test

JMeter (local) ──HTTP──► Tomcat (test EC2)
VisualVM (local) ──JMX──► Tomcat (test EC2)

Paso 3: Recolección de Resultados

Test EC2:
  /opt/tomcat/logs/gc.log ──┐
                             ├──► Descargar vía SCP
JMeter (local):              │
  results.jtl ──────────────┤
                             │
VisualVM (local):            │
  cpu-profile.nps ──────────┘

Paso 4: Análisis y Dashboard

~/profiling-results/ ──► JMeter HTML Report
                     ──► VisualVM Analysis
                     ──► Excel Charts
                     ──► Jupyter Notebook
                     ──► Grafana (opcional)

Ejemplo de Resultados: Dashboard JMeter

Métricas Incluidas

  • Response Time Over Time (gráfica de tiempo de respuesta)
  • Throughput (peticiones por segundo)
  • Active Threads (usuarios concurrentes)
  • Response Time Percentiles (p50, p95, p99)
  • Error Rate (% de errores)
  • Top 5 Slowest Requests (peticiones más lentas)

Ejemplo de Resultados: VisualVM

CPU Profiling

Método                          Tiempo Total    Self Time
============================================================
ServletLogin.doPost()           45.2%           12.3%
  ├─ DatabaseService.query()    32.9%           28.1%
  │   └─ ResultSet.next()        4.8%            4.8%
  └─ SessionManager.create()     0.0%            0.0%

Conclusión: El query de login consume 28% del CPU total

Memory Analysis (Heap Dump)

Clase                    Instancias    Tamaño (MB)
====================================================
char[]                   1,245,892     487.2
String                     892,103     142.7
SessionData                 12,500      89.3  ← Posible leak
byte[]                     456,789      78.9

Conclusión: 12,500 sesiones acumuladas (memory leak)

Comparación: Solución Propuesta vs Alternativas

Característica JMeter + VisualVM YourKit JProfiler AWS CodeGuru
Costo $0 licencia licencia $43/mes
Java 1.7 ✅ Sí ❌ No ❌ No ✅ Sí
Instalación Solo JVM args Agente Agente Agente
Overhead <5% 10-20% 10-20% 5-10%
Offline ✅ Sí ✅ Sí ✅ Sí ❌ No
Carga real ✅ JMeter ❌ No ❌ No ✅ Sí

Ganador: JMeter + VisualVM por costo cero y compatibilidad total

Proceso de Implementación

Fase 1: Configuración (Una vez)

  1. Configurar JVM args en test EC2 (ya hecho)
  2. Instalar JMeter en máquina local (script disponible)
  3. Instalar VisualVM en máquina local
  4. Crear túnel SSH o VPN a test EC2

Fase 2: Grabación de Workflows (Una vez por flujo)

  1. Iniciar JMeter recorder
  2. Configurar proxy en navegador
  3. Ejecutar flujo de usuario real
  4. Guardar test plan (.jmx)

Fase 3: Profiling (Repetible)

  1. Conectar VisualVM a test EC2
  2. Ejecutar JMeter load test
  3. Observar hotspots en VisualVM
  4. Guardar snapshots
  5. Descargar logs del servidor

Automatización

Script de Profiling Automático

#!/bin/bash
# profiling-automatico.sh

FECHA=$(date +%Y%m%d_%H%M%S)
RESULTADOS=~/profiling-results/$FECHA

# 1. Ejecutar JMeter
jmeter -n -t workflow.jmx \
  -Jusers=20 \
  -l $RESULTADOS/results.jtl \
  -e -o $RESULTADOS/html-report/

# 2. Descargar logs del servidor
scp ec2-user@test-server:/opt/tomcat/logs/gc.log $RESULTADOS/

# 3. Tomar heap dump (vía JMX)
jmap -dump:live,format=b,file=$RESULTADOS/heap.hprof <PID>

# 4. Generar reporte
echo "Profiling completado: $RESULTADOS"
firefox $RESULTADOS/html-report/index.html

Programar con Cron

Entregables

1. Reportes Técnicos

  • Reporte HTML de JMeter (métricas de carga)
  • Snapshots de VisualVM (CPU, memoria, threads)
  • Análisis de GC logs (comportamiento del garbage collector)

2. Documentación

  • Test plans (.jmx) - reproducibles
  • Guías de ejecución (paso a paso)
  • Scripts de automatización (bash)

3. Presentaciones

  • Dashboard ejecutivo (resumen de hallazgos)
  • Recomendaciones técnicas (optimizaciones prioritarias)
  • Plan de acción (qué optimizar primero)

Ejemplo: Hallazgos Típicos

CPU Hotspots

❌ Problema: ServletLogin.doPost() consume 45% del CPU
✅ Solución: Agregar índice en tabla usuarios (columna email)
📊 Impacto: Reduce CPU a 12%, mejora respuesta en 300ms

Memory Leaks

❌ Problema: 12,500 objetos SessionData retenidos (89 MB)
✅ Solución: Implementar timeout de sesión (30 minutos)
📊 Impacto: Reduce uso de heap en 65%

GC Thrashing

❌ Problema: GC cada 5 segundos, pausas de 200ms
✅ Solución: Aumentar heap de 1GB a 2GB, ajustar CMSInitiatingOccupancyFraction
📊 Impacto: GC cada 20 segundos, pausas de 50ms

Beneficios de esta Solución

Técnicos

  • Sin cambios de código (no invasivo)
  • Bajo overhead (seguro en test)
  • Resultados accionables (identificación precisa)
  • Reproducible (test plans guardados)

Operacionales

  • Costo cero (no licencias)
  • Control total (open source, datos propios)
  • Escalable (múltiples servidores sin costo adicional)
  • Transferencia de conocimiento (herramientas estándar)

Preguntas Frecuentes

¿Cuánto tiempo toma una sesión de profiling?

  • Grabación de workflow: 15-30 minutos
  • Ejecución de test: 10-30 minutos (según duración)
  • Análisis de resultados: 1-2 horas
  • Total: Medio día por sesión completa

¿Los resultados se quedan en el servidor?

No (recomendado):

  • JMeter guarda resultados en tu PC local (.jtl, HTML report)
  • VisualVM guarda snapshots en tu PC local (.nps, .hprof)
  • GC logs se descargan del servidor después del test
  • Ventaja: Análisis offline, no consume espacio en servidor

¿Se puede crear un dashboard?

Sí, varias opciones:

  • Simple: Reporte HTML de JMeter (ya incluido)
  • Medio: Excel/Google Sheets (importar CSV)
  • Avanzado: Grafana + InfluxDB (tiempo real)
  • Custom: Jupyter Notebook + Python (análisis personalizado)

¿Qué pasa si encontramos un problema?

Flujo de resolución:

  1. VisualVM identifica el método problemático (e.g., getUserData())
  2. Reporte incluye stack trace y tiempo de CPU
  3. Desarrollador revisa código del método
  4. Se implementa optimización
  5. Se re-profila para validar mejora

¿Cuántos usuarios podemos simular?

Depende de:

  • Hardware de la máquina con JMeter
  • Complejidad del workflow
  • Típicamente: 50-100 usuarios concurrentes sin problema
  • Con múltiples máquinas JMeter: 1000+ usuarios

Referencias

Solución de Profiling para ProCash

By Juan G

Solución de Profiling para ProCash

  • 34