Módulo 3: Métricas — latencia, throughput y errores
6. Leer el resumen de `k6 run`
Descripción
Ya tienes los tres instrumentos —latencia, throughput y errores— y sabes calcularlos con tus manos. Esta lección junta las piezas y te enseña a leerlos en su formato nativo: el resumen de fin de prueba que k6 imprime cuando termina una corrida. Es la pantalla que verás cada vez que ejecutes k6 run, y saber recorrerla línea por línea —qué mide cada fila, dónde está el p95, dónde la tasa de error, dónde el RPS— es la habilidad práctica que todo este módulo venía construyendo. Al terminar, ese bloque de números dejará de ser intimidante y será un tablero que lees de un vistazo.
Una honestidad por delante, la de siempre en esta guía: k6 no está instalado en este entorno (es un binario de Go con su propio runtime, y ni node ni python lo ejecutan). Así que todos los bloques de salida de k6 de esta lección van como contenido rotulado: son fieles al formato oficial de k6 —los verifiqué contra su documentación— pero no fueron ejecutados aquí, y nunca te los presento como si lo hubieran sido. La gracia es que no necesitas ejecutar k6 para aprender a leer su resumen, porque cada número que aparece en él ya lo calculaste tú en Python en las lecciones anteriores. La última sección de esta lección hace precisamente ese puente: mapear cada línea del resumen de k6 a la métrica que tú mismo produjiste.
Conexión con el módulo: esta lección es la síntesis. Las lecciones 2-3 te dieron la latencia y los percentiles (que aquí lees en http_req_duration); la 4, el throughput (http_reqs, iterations); la 5, la tasa de error (http_req_failed). Aquí las ves las tres en una sola pantalla. El script de k6 que produce este resumen es la anatomía del módulo 2, que reusamos sin reexplicar. Y los thresholds que aparecerán arriba del resumen (la sección THRESHOLDS) son un adelanto del módulo 5: aquí solo los nombramos; hacer que gaten el deploy es allá.
El recibo detallado del supermercado
Cuando terminas la compra grande del mes, la caja te da un recibo largo. No es una sola cifra: está organizado en secciones —los lácteos, las frutas, la limpieza— y al final, los totales: subtotal, impuestos, total a pagar. Si sabes leerlo, de un vistazo ves cuánto gastaste en cada categoría y dónde se te fue el dinero. Si no sabes, es una columna de números que asusta. Aprender a leer el recibo no es aprender a sumar —eso ya lo sabes—; es aprender dónde está cada cosa y qué significa cada línea.
El resumen de k6 run es ese recibo. Está organizado en secciones con etiquetas (HTTP, EXECUTION, NETWORK) y, arriba del todo, los "totales" que más te importan si pusiste umbrales (THRESHOLDS). Cada línea es una métrica con su resumen estadístico. La habilidad que instala esta lección no es calcular nada —ya sabes— sino navegar el recibo: saber que la latencia vive en la sección HTTP bajo http_req_duration, que el p95 es la columna p(95), que la tasa de error es http_req_failed, que el RPS es la tasa /s de http_reqs. Una vez que sabes dónde mirar, el resumen se lee en diez segundos.
El script que produce el resumen (contexto, del módulo 2)
Para tener contexto, este es el tipo de script de k6 que genera un resumen como los de abajo. Su anatomía —la función default, http.post, check, sleep, options— es del módulo 2; lo incluimos solo para que sepas de dónde salen los números, no para reexplicarlo.
// load-test.js — CONTENIDO (k6 no está instalado; así se ve un script de k6)
import http from 'k6/http';
import { check, sleep } from 'k6';
export const options = {
vus: 50, // 50 usuarios virtuales
duration: '60s', // durante 60 segundos
};
export default function () {
const url = 'http://127.0.0.1:8080/quote';
const payload = JSON.stringify({ room: 'Focus', tier: 'basic', hours: 3 });
const params = { headers: { 'Content-Type': 'application/json' } };
const res = http.post(url, payload, params);
check(res, {
'status is 200': (r) => r.status === 200,
'price is 7500': (r) => r.json('price_cents') === 7500,
});
sleep(1); // think time: el VU "piensa" 1 s entre iteraciones
}
El resumen de k6 run, línea por línea
Cuando corres k6 run load-test.js, k6 primero imprime una cabecera con la configuración de la corrida y luego, al terminar, el resumen. Este es el aspecto de esa salida —contenido rotulado, fiel al formato oficial de k6, no ejecutado aquí—:
# CONTENIDO (así se ve `k6 run`; k6 no está instalado en este entorno)
execution: local
script: load-test.js
output: -
scenarios: (100.00%) 1 scenario, 50 max VUs, 1m30s max duration (incl. graceful stop):
* default: 50 looping VUs for 1m0s (gracefulStop: 30s)
█ TOTAL RESULTS
checks_total.......................: 2940 48.99/s
checks_succeeded...................: 100.00% 5880 out of 5880
checks_failed......................: 0.00% 0 out of 5880
✓ status is 200
✓ price is 7500
HTTP
http_req_duration..................: avg=13.2ms min=0.9ms med=6.1ms max=254ms p(90)=39ms p(95)=183ms
{ expected_response:true }.......: avg=13.2ms min=0.9ms med=6.1ms max=254ms p(90)=39ms p(95)=183ms
http_req_failed....................: 0.00% 0 out of 2940
http_reqs..........................: 2940 48.99/s
EXECUTION
iteration_duration.................: avg=1.01s min=1.0s med=1.0s max=1.25s p(90)=1.04s p(95)=1.18s
iterations.........................: 2940 48.99/s
vus................................: 50 min=50 max=50
vus_max............................: 50 min=50 max=50
NETWORK
data_received......................: 388 kB 6.5 kB/s
data_sent..........................: 511 kB 8.5 kB/s
Ahora recórrelo por secciones, que es como se lee.
La cabecera (execution / scenarios). Antes del resumen, k6 te recuerda qué corrió: 1 escenario, 50 VUs máximos, durante 1 minuto. Es el contexto sin el cual las métricas no significan nada —"183 ms de p95" solo tiene sentido si sabes que fue con 50 VUs—.
TOTAL RESULTS → los checks. Las tres primeras líneas (checks_total, checks_succeeded, checks_failed) resumen los check() del script: aquí, 5880 verificaciones (dos por iteración: "status is 200" y "price is 7500"), todas exitosas. Los checks verifican la corrección bajo carga (que el precio siga siendo 7500 incluso con 50 VUs) y son el tema del módulo 6; aquí solo reconoce dónde aparecen.
Sección HTTP → latencia, errores, throughput. Es el corazón del recibo, y donde viven tus tres instrumentos:
| Línea del resumen | Qué es | De qué lección |
|---|---|---|
http_req_duration | La latencia, con avg/min/med/max/p(90)/p(95). El p95 (p(95)=183ms) es tu métrica de la cola. | Lecciones 2-3 |
{ expected_response:true } | La misma latencia, pero solo de las peticiones que k6 considera exitosas. Útil para no ensuciar el percentil con la latencia de los errores. | Lección 5 |
http_req_failed | La tasa de error (0.00% 0 out of 2940): porcentaje y conteo de peticiones fallidas. | Lección 5 |
http_reqs | El throughput: total (2940) y tasa (48.99/s, el RPS). | Lección 4 |
Sección EXECUTION → iteraciones y VUs. iterations cuenta las ejecuciones del script (aquí coincide con http_reqs porque cada iteración hace una petición); iteration_duration mide cuánto tardó cada iteración completa (fíjate: ~1.01 s, dominado por el sleep(1) de think time). vus y vus_max confirman cuántos usuarios virtuales estuvieron activos (50). Nota cómo la cabecera, iterations y http_reqs cuentan la misma historia del throughput desde tres ángulos.
Sección NETWORK → datos. data_received y data_sent: cuántos bytes movió la prueba, como total y como tasa. Rara vez es tu métrica principal, pero delata respuestas anormalmente grandes (si data_received explota, quizás estás descargando payloads enormes, y eso infla http_req_receiving).
El puente: cada línea de k6 es algo que ya calculaste
Aquí está la razón por la que no necesitas ejecutar k6 para entender su resumen: cada línea corresponde exactamente a una métrica que tú produjiste en Python en las lecciones anteriores. Fíjate en la equivalencia:
| Línea del resumen de k6 | Lo que tú calculaste en Python |
|---|---|
http_req_duration ... p(95)=183ms | statistics.quantiles(latencies, n=100)[94] → tu p95 (lección 3) |
http_req_duration ... med=6.1ms | statistics.median(latencies) o tu p50 |
http_req_duration ... avg=13.2ms | statistics.fmean(latencies) → el promedio (que sabes que miente si hay cola) |
http_req_failed ... 0.00% | errors / total → tu tasa de error (lección 5) |
http_reqs ... 48.99/s | total / wall_time → tu RPS (lección 4) |
iterations | el número de tareas que lanzó tu generador |
vus_max | el max_workers de tu ThreadPoolExecutor (tus "VUs") |
Dicho de otro modo: k6 no hace magia estadística que tú no puedas reproducir. Hace lo mismo que tu generador de Python —lanzar VUs, medir latencias, contar errores, calcular percentiles— pero industrializado (más VUs, más protocolos, más precisión) y con una salida estandarizada. Que tú hayas calculado el p95 con statistics.quantiles es exactamente lo que te permite mirar p(95)=183ms en el recibo de k6 y saber, sin dudar, qué significa y de dónde salió.
Nota sobre versiones y formatos
El formato del resumen ha cambiado un poco entre versiones de k6. El que ves arriba —con secciones TOTAL RESULTS, HTTP, EXECUTION, NETWORK y checks_succeeded/checks_failed— es el de las versiones recientes de k6. Versiones más antiguas mostraban las mismas métricas con un formato ligeramente distinto (por ejemplo, checks.....: 100.00% ✓ 5880 ✗ 0). Los nombres de las métricas no cambian (http_req_duration, http_req_failed, http_reqs son estables); solo cambia la presentación. Si tu resumen se ve algo distinto al de aquí, busca las mismas métricas por su nombre: siguen ahí.
Errores comunes
Leer el avg de http_req_duration como "la latencia". Qué pasa: alguien mira avg=13.2ms en el recibo y reporta "la API va a 13 ms", ignorando que en la misma línea p(95)=183ms. Por qué pasa: el avg es la primera columna y la más familiar. Cómo detectarlo: si tu latencia reportada es el avg y no el p(95), cometiste el error de las lecciones 2-3 dentro del resumen de k6. Cómo corregirlo: en http_req_duration, lee el p(95) (y el med), no el avg. El resumen te da ambos precisamente para que compares y veas la cola.
Ignorar la cabecera scenarios y comparar corridas incomparables. Qué pasa: alguien compara el p95 de una corrida de 10 VUs con el de otra de 200 VUs y concluye "la latencia empeoró", cuando lo que cambió fue la carga. Por qué pasa: se salta la cabecera y solo mira el resumen. Cómo detectarlo: si comparas dos resúmenes sin verificar que la sección scenarios (VUs, duración) sea la misma, comparas peras con manzanas. Cómo corregirlo: lee siempre la cabecera primero; un p95 solo es comparable con otro medido bajo la misma carga.
Confundir checks con http_req_failed. Qué pasa: alguien ve checks_succeeded: 100.00% y cree que no hubo errores, o ve un check fallido y cree que la petición falló. Por qué pasa: ambos suenan a "¿salió bien?", pero miden cosas distintas. Cómo detectarlo: http_req_failed es sobre el transporte (¿respondió el servidor con status < 400?); checks es sobre lo que tú verificaste del contenido (¿el precio fue 7500?). Una petición puede tener http_req_failed: 0% (respondió 200) pero un check fallido (el 200 traía el precio equivocado). Cómo corregirlo: lee las dos: la tasa de error para disponibilidad, los checks para corrección (módulo 6).
Ejercicios
Ejercicio 1 — Encuentra cada instrumento en el recibo. Usando el resumen de arriba, di en qué línea y columna lees: (a) el throughput (RPS), (b) el p95 de latencia, (c) la tasa de error, (d) cuántos VUs corrieron.
Ver solución
- (a) Throughput (RPS): la línea
http_reqs, en su tasa:48.99/s. (El total, 2940, es el número de peticiones.) - (b) p95 de latencia: la línea
http_req_duration, columnap(95):183ms. - (c) Tasa de error: la línea
http_req_failed:0.00%(0 out of 2940). - (d) VUs: la línea
vus_max(o la cabecerascenarios):50.
Ejercicio 2 — Traduce un resumen a un veredicto. Te dan este extracto de un k6 run: http_req_duration ... med=8ms p(95)=45ms, http_req_failed ... 3.20% 320 out of 10000, http_reqs ... 10000 500/s, umbral de error acordado: 1%. (a) ¿Pasa la prueba? (b) ¿Qué métrica leíste primero y por qué? (c) ¿El p95 de 45 ms cambia tu veredicto?
Ver solución
- (a) No pasa. La tasa de error es 3.20%, por encima del umbral acordado de 1%.
- (b) La tasa de error (
http_req_failed), porque valida todo lo demás (lección 5): si falla, el veredicto ya está, sin importar la latencia. Y falla: 3.20% > 1%. - (c) No. Con 3.20% de error, la prueba ya falló; el p95 de 45 ms (que de por sí es razonable) no la rescata. La latencia solo significaría algo si la tasa de error hubiera pasado. El p95 sirve luego para diagnosticar, no para rescatar el veredicto.
Ejercicio 3 — iterations vs http_reqs en el recibo. En un resumen, iterations: 2940 y http_reqs: 8820. (a) ¿Cuántas peticiones hace cada iteración? (b) ¿Qué tipo de script produce eso? (c) Si el RPS (http_reqs /s) es 147/s, ¿cuál es la tasa de iteraciones/s?
Ver solución
- (a)
8820 / 2940 = 3peticiones por iteración. - (b) Un script cuya función
defaulthace tres peticiones HTTP —por ejemplo, un flujoGET /rooms→POST /quote→POST /book— por cada ejecución. (El del módulo 6.) - (c) La tasa de iteraciones es un tercio de la de peticiones:
147 / 3 = 49iteraciones/s. (Cada iteración = un usuario completando el flujo; 49 flujos completos por segundo.)
Resumen y siguiente paso
En esta lección aprendiste a leer el resumen de k6 run como un recibo detallado: navegar sus secciones (TOTAL RESULTS, HTTP, EXECUTION, NETWORK) y saber dónde vive cada instrumento —la latencia en http_req_duration (con su p(95)), la tasa de error en http_req_failed, el throughput en http_reqs y su tasa /s, los VUs en vus_max—. Viste que ese bloque, aunque va como contenido rotulado (k6 no está instalado), no esconde ninguna magia: cada línea corresponde a algo que tú ya calculaste en Python —el p(95) es tu statistics.quantiles, el RPS es tu total / wall_time, la tasa de error es tu errors / total—.
La habilidad que instalaste no es calcular (ya sabías) sino navegar: leer un resumen de k6 de un vistazo y sacar el veredicto en el orden correcto (tasa de error primero, luego latencia en percentiles, luego throughput con su contexto de VUs). Antes de avanzar deberías poder: ubicar los tres instrumentos en un resumen de k6; distinguir checks de http_req_failed; y explicar por qué no necesitas ejecutar k6 para entender su salida.
Lo que sigue es el clímax del módulo. En la lección 7 volvemos sobre la trampa del promedio, pero ahora con toda la fuerza numérica: sobre la distribución con cola de /quote_slow, verás medido cómo el promedio queda por encima de lo que vivió el 88% de la gente, y por qué las SLO de latencia del mundo real se escriben en percentiles y jamás en promedio.
Recursos
- k6 — Resumen de fin de prueba (end-of-test summary) — la referencia oficial del bloque de resumen: sus secciones, qué métrica va en cada línea y cómo se formatean. La fuente contra la que verificamos el contenido rotulado de esta lección.
- k6 — Métricas integradas (referencia) — el catálogo de
http_req_duration,http_req_failed,http_reqs,iterations,vusy las demás, para consultar qué mide cada línea del recibo. - k6 — Resultados y salidas — el panorama de las formas de sacar resultados de k6 (resumen, JSON, CSV, salidas en streaming); analizar y exportar a fondo es el módulo 7.
- Módulo 2 de esta guía — El script de k6 y los usuarios virtuales — la anatomía del script (
options,default,http.post,check,sleep) que produce este resumen, reusada aquí sin reexplicar.