wandres.dev
CONSOLE I · Más que console.log

Contar y medir: count, time y sus límites

Los contadores y cronómetros de la consola, para qué sirven de verdad, y por qué medir rendimiento con console.time te da un número que no significa lo que crees.

⏱ 14 min

Dos preguntas concretas se responden con dos funciones de una línea: cuántas veces ha pasado esto, y cuánto ha tardado aquello. La primera, console.count, es una de las herramientas de diagnóstico con mejor relación entre esfuerzo y respuesta que existe, porque un contador inesperado revela renders duplicados, listeners registrados dos veces y bucles que no terminan. La segunda, console.time, es mucho menos fiable de lo que parece y conviene saber exactamente qué mide.

🎯 Al terminar esta lección sabrás
  • Usar contadores con etiqueta para detectar ejecuciones duplicadas o inesperadas.
  • Medir intervalos con la precisión adecuada y conocer los límites de esa medida.
  • Colocar marcadores en un perfil de rendimiento desde el código.
  • Explicar por qué un número de console.time no se puede comparar con otro tomado en otras condiciones.

console.count

Incrementa y muestra un contador asociado a una etiqueta. Sin etiqueta usa default. console.countReset lo pone a cero.

function render(props) {
  console.count('render de ListaPedidos');
  // ...
}

Su valor está en las respuestas que da sin que tengas que pensar. Los cuatro diagnósticos que resuelve casi solo.

Renders duplicados. Un contador que sube de dos en dos con cada interacción indica que algo está provocando dos renderizados donde debería haber uno. En React con el modo estricto activado esto es esperado en desarrollo, y saberlo evita perseguir un fantasma.

Listeners registrados varias veces. Un manejador que se registra en cada render sin quitarse acumula copias, y el contador dentro del manejador sube de uno en uno la primera vez, de dos en dos la segunda, de tres en tres la tercera. Ese patrón creciente es inconfundible y diagnostica el bug entero.

Bucles que no terminan. Un contador que sube sin parar en un efecto indica una dependencia que cambia de identidad en cada pasada.

Ramas muertas. Un contador que nunca aparece indica código que no se ejecuta, y a veces esa es la respuesta a por qué algo no pasa.

La técnica que lo hace más potente es etiquetar con un valor, no con una cadena fija.

// Cuantas veces se pide cada URL
const original = window.fetch;
window.fetch = function (recurso, opciones) {
  console.count('fetch ' + String(recurso instanceof Request ? recurso.url : recurso).split('?')[0]);
  return original.apply(this, arguments);
};

Con eso puesto, la consola muestra un contador por URL distinta, y la que suba a doce cuando esperabas una es tu petición duplicada. Para deshacerlo, recarga.

💡
Tip

console.count está disponible también como logpoint, así que se puede contar sin tocar el código. Un logpoint con el texto console.count('aqui') en la línea que te interese da el mismo resultado sin modificar ni un fichero, y se quita de golpe con el resto de breakpoints.

console.time y sus tres funciones

console.time(etiqueta) arranca un cronómetro, console.timeLog(etiqueta) muestra el tiempo transcurrido sin pararlo, y console.timeEnd(etiqueta) lo muestra y lo detiene. Se pueden tener varios simultáneos con etiquetas distintas.

console.time('parseo');
const datos = JSON.parse(texto);
console.timeLog('parseo', 'json listo');
const normalizados = datos.items.map(normalizar);
console.timeEnd('parseo');

timeLog acepta argumentos adicionales que se muestran junto al tiempo, lo que permite marcar hitos dentro de una operación larga.

Qué mide realmente y por qué no es lo que crees

Aquí está el contenido importante de la lección. Un número de console.time es tiempo de reloj de pared entre dos puntos del código, y eso incluye cosas que no tienen nada que ver con lo que quieres medir.

Incluye el tiempo que el hilo estuvo haciendo otra cosa. Si entre el inicio y el fin el navegador atendió un evento, ejecutó un setTimeout, hizo un layout o recogió basura, ese tiempo está dentro de tu número.

Incluye el coste de la propia instrumentación, que no es cero cuando la consola está conectada.

Depende del estado de optimización del motor. La primera ejecución de una función es interpretada; después de unas cuantas, el motor la compila y optimiza. Medir la primera pasada da un número que puede ser diez veces peor que el estacionario, y medir después de un bucle de calentamiento da uno mucho mejor que el real en producción. Ninguno de los dos es representativo por sí solo.

Tiene precisión limitada a propósito. Los relojes de alta resolución del navegador están deliberadamente redondeados y con ruido añadido como mitigación contra ataques de canal lateral basados en temporización. Para intervalos de milisegundos no importa; para medir microsegundos, el número es ruido.

No distingue el trabajo asíncrono. Un console.timeEnd dentro de un then mide desde el inicio hasta que la promesa resolvió, lo que incluye toda la espera de red y el tiempo en cola de microtareas.

La conclusión no es que sea inútil: es que sirve para comparar dos ejecuciones de lo mismo en las mismas condiciones, y no para afirmar cuánto cuesta algo. Si una operación pasa de 400 a 40 milisegundos con tu cambio, esa mejora es real. Si tarda 12 milisegundos, ese 12 no significa que en el móvil de un usuario vaya a tardar 12.

Para medidas serias hay dos alternativas mejores dentro del propio navegador.

// La API de rendimiento, con marcas y medidas que ademas aparecen en el perfil
performance.mark('inicio-parseo');
const datos = JSON.parse(texto);
performance.mark('fin-parseo');
performance.measure('parseo', 'inicio-parseo', 'fin-parseo');
console.table(performance.getEntriesByName('parseo').map(m => ({ nombre: m.name, ms: m.duration.toFixed(2) })));

La ventaja de performance.measure sobre console.time es doble: los datos quedan en la línea temporal de rendimiento y se pueden consultar programáticamente, y las medidas aparecen dibujadas en la grabación del panel de Performance, lo que permite correlacionar tu instrumentación con lo que estaba haciendo el navegador.

Marcadores en el perfil

console.timeStamp(etiqueta) coloca una marca vertical en la grabación del panel de rendimiento en el instante en que se ejecuta. No mide nada: señala.

Es la forma de responder a la pregunta “¿qué estaba haciendo el navegador exactamente cuando mi código llegó a este punto?”. Poner un timeStamp al principio y al final de una operación sospechosa, grabar un perfil, y mirar qué hay entre las dos marcas suele explicar de golpe una lentitud que los números no explicaban.

El número que mides con la consola es el número de tu máquina en tu estado

La trampa profunda de medir desde la consola no está en la precisión del reloj sino en la representatividad, y afecta por igual a console.time, a performance.measure y a cualquier medición que hagas en tu portátil. Tu máquina no se parece a la de tus usuarios en ninguna de las tres dimensiones que importan. Tu CPU es varias veces más rápida que el móvil de gama media donde de verdad se ejecuta tu código. Tu red es mucho mejor y con latencia mucho menor. Y tu caché está caliente, tu perfil tiene los recursos descargados, y tu base de datos local tiene tres registros en vez de cuatro mil. Sobre eso se apila el efecto de las propias DevTools, que ralentizan lo instrumentado y desoptimizan lo depurado. El resultado es que un número absoluto medido en desarrollo no predice absolutamente nada sobre el rendimiento real, y sin embargo se usa continuamente para tomar decisiones: se descarta una optimización porque “solo tarda 8 milisegundos”, cuando en el dispositivo objetivo tarda 80 y ocurre veinte veces. Lo que sí es válido y hay que usar sin complejos es la comparación relativa en condiciones idénticas: la misma máquina, el mismo estado, el mismo conjunto de datos, midiendo antes y después de un cambio. Eso es un experimento controlado y su resultado se sostiene. Y cuando necesites acercarte al absoluto, hay dos correcciones baratas que cambian mucho el resultado: aplica throttling de CPU al factor que corresponda a tu dispositivo objetivo, y mide con un conjunto de datos del tamaño del peor caso real, no con los tres registros de tu entorno de pruebas. Con esas dos correcciones, los números dejan de ser fantasía y empiezan a ser una cota superior útil.