wandres.dev
EL HILO PRINCIPAL · Long tasks y bloqueo

Las tareas largas en el panel de rendimiento: leer el flame chart

Cómo grabar un perfil que sirva, qué significa cada pista del timeline, cómo se lee un flame chart de arriba abajo y de izquierda a derecha, y los cuatro perfiles de forma que identifican un tipo de problema.

⏱ 20 min

La instrumentación en producción te dice que hay bloqueo y aproximadamente dónde. El perfilador te dice exactamente qué función, en qué línea y con qué pila de llamadas. Es la herramienta definitiva de este nivel y la que más gente usa mal, porque un perfil sin preparar produce una imagen ilegible llena de ruido de extensiones, de código de desarrollo y de la propia herramienta.

🎯 Al terminar esta lección sabrás
  • Preparar una grabación que produzca datos comparables con producción.
  • Leer las pistas del timeline y saber cuál mirar según el síntoma.
  • Interpretar un flame chart y distinguir tiempo propio de tiempo total.
  • Reconocer las cuatro formas típicas de perfil y su diagnóstico.

Grabar un perfil que sirva

Cinco condiciones. Saltarse cualquiera invalida los datos.

Perfil limpio del navegador o modo incógnito. Las extensiones inyectan guiones que aparecen en el perfil y compiten por el hilo. Un bloqueador de anuncios puede ser el mayor consumidor de un perfil y no está en el navegador de tus usuarios.

Compilación de producción, servida por un servidor real. El servidor de desarrollo sirve módulos sin empaquetar, con cientos de peticiones, con código sin minificar y con comprobaciones extra. Es otro programa.

Estrangulamiento de CPU calibrado. No el valor por defecto: el factor que hayas medido para tu máquina frente al dispositivo de referencia.

Estrangulamiento de red si mides carga. Sin él, todo llega instantáneamente y las cascadas desaparecen.

Caché vaciada o caliente, según lo que quieras medir, y siendo consciente de cuál de las dos estás midiendo. Las dos son válidas y responden preguntas distintas.

Y una condición sobre el propio acto de grabar: empieza a grabar antes de la acción y para justo después. Un perfil de treinta segundos con la acción interesante en el segundo veintidós es mucho más difícil de leer que uno de tres segundos.

Para medir la carga, la opción de recargar y grabar automáticamente evita el error clásico de empezar a grabar tarde.

Las pistas del timeline

De arriba abajo, lo que importa:

Las métricas de rendimiento. Marcadores de primer pintado con contenido, del elemento del LCP y de los desplazamientos de layout. Sirven para orientarse temporalmente: todo lo que esté antes del marcador del LCP es lo que afecta a esa métrica.

La pista de red. El waterfall, con las barras de cada petición. Aquí se leen las cascadas.

Los fotogramas. Una barra por fotograma presentado, coloreada según si se presentó a tiempo. Huecos largos entre fotogramas significan bloqueo.

El hilo principal. La pista central. Aquí está el flame chart, y aquí es donde se pasa el 90 por ciento del tiempo de análisis.

Otros hilos. El compositor, los rasterizadores, los trabajadores. Un rasterizador ocupado con el hilo principal libre apunta a pintura cara, no a JavaScript.

Las interacciones. Cuando grabas mientras interactúas, aparece una pista con cada interacción y su duración, desglosada. Es la vista directa de las tres partes del INP.

Leer un flame chart

El flame chart representa la pila de llamadas en el tiempo. El eje horizontal es tiempo; el vertical es profundidad de pila. Una función que llama a otra aparece encima de ella.

Las tres lecturas que hay que saber hacer:

El ancho de un bloque es su duración total, incluyendo todo lo que llamó. Una función que aparece ancha puede no hacer nada por sí misma y estar simplemente llamando a algo caro.

El tiempo propio de una función es su ancho menos el ancho de todos sus hijos. Es el hueco visible entre un bloque y los que tiene encima. Una función ancha con hijos que la cubren entera no tiene tiempo propio: el problema está más arriba. Una función ancha sin hijos es donde de verdad se va el tiempo.

Las tareas están delimitadas. El panel marca cada tarea, y las que superan el umbral llevan una marca visual distinta con un triángulo rojo en la esquina. Esas son las tareas largas.

Para pasar de la vista temporal a la agregada, la pestaña inferior con el árbol de llamadas ordenado por tiempo propio es lo que responde a la pregunta «dónde se va el tiempo». Y la opción de agrupar por actividad da el reparto entre scripting, renderizado, pintura y sistema, que es la primera cifra que hay que mirar en cualquier perfil.

Un ajuste que cambia mucho la legibilidad y que casi nadie activa: ocultar el código de terceros y de bibliotecas mediante la lista de ignorados. Con el código del marco y de las dependencias plegado, el flame chart muestra solo tus funciones, y una pila de treinta niveles se convierte en una de cuatro.

Las cuatro formas típicas

Con la práctica, la forma del perfil se reconoce antes de leer un solo nombre de función.

Forma uno: un bloque macizo y ancho. Una sola tarea de cientos de milisegundos, con una pila profunda y estable. Es evaluación de bundle o hidratación. Diagnóstico: demasiado código en el arranque. Solución: troceado y menos trabajo inicial.

Forma dos: un peine. Muchas tareas cortas y regulares, muy juntas, con el mismo perfil de pila. Es un oyente de alta frecuencia —desplazamiento, movimiento de puntero, redimensionado— o un intervalo. Diagnóstico: trabajo por evento demasiado caro. Solución: marcar el oyente como pasivo, limitar la frecuencia, o mover el trabajo.

Forma tres: una escalera con barras alternas. Dentro de una sola tarea, bloques de scripting alternados con bloques púrpura de recálculo de estilo y layout, repetidos decenas de veces. Es el patrón de layout forzado síncrono: código que escribe y lee geometría alternadamente. Diagnóstico inequívoco, y el panel suele etiquetarlo con un aviso de reflow forzado.

Forma cuatro: hilo principal vacío y otro hilo ocupado. El hilo principal libre, con el compositor o los rasterizadores al máximo. No es un problema de JavaScript: es pintura o composición cara. Sospechosos: filtros, sombras grandes, capas mal promovidas, desenfoques. La herramienta correcta aquí no es el flame chart sino los overlays de renderizado.

La marca de usuario es la herramienta más infravalorada del panel, y convierte un perfil ilegible en uno anotado

Un flame chart de una aplicación con marco es una sopa de funciones internas con nombres como performWorkUntilDeadline o flushSync, y ninguna te dice qué estaba haciendo tu aplicación en ese momento. La solución existe desde hace años, cuesta dos líneas y casi nadie la usa: las marcas y medidas de usuario aparecen como una pista propia en el timeline, alineadas con el flame chart.

performance.mark('filtro:inicio');
const resultado = filtrarYOrdenar(items, criterios);
performance.mark('filtro:fin');
performance.measure('Filtrar listado', 'filtro:inicio', 'filtro:fin');

Esa medida aparece como una barra etiquetada encima del flame chart, y de repente sabes que los 180 milisegundos de funciones internas ilegibles corresponden a tu filtrado. En una aplicación instrumentada con diez o quince medidas en los puntos clave, leer un perfil deja de ser arqueología.

La versión moderna y más cómoda permite pasar el detalle directamente y admite tiempos ya conocidos, lo cual sirve para medir trabajo asíncrono:

const t0 = performance.now();
const datos = await pedirYProcesar();
performance.measure('Cargar y procesar pedidos', {
  start: t0,
  end: performance.now(),
  detail: { n: datos.length, ruta: location.pathname },
});

Tres usos que justifican por sí solos instrumentar la aplicación entera.

Uno: marcar las fronteras de fase. Arranque del marco, hidratación, primera consulta de datos, primer renderizado completo. Con eso, la pregunta «cuánto tarda la hidratación» se responde leyendo una barra en lugar de sumando bloques a ojo.

Dos: marcar las interacciones lentas en producción. Las medidas de usuario están disponibles vía el registro de rendimiento en el navegador del usuario, así que la misma instrumentación que hace legible el perfil local sirve para telemetría:

for (const m of performance.getEntriesByType('measure')) {
  if (m.duration > 100) enviarMedidaLenta(m.name, m.duration);
}

Tres, y es el que más ahorra: dejar las marcas en producción. Cuestan microsegundos —son una entrada en un buffer— y convierten cualquier perfil que grabes o que te envíe un compañero en un documento legible. El coste es tan bajo que la práctica correcta es instrumentar y no quitarlo. Lo único que conviene vigilar es el tamaño del buffer de entradas en sesiones muy largas, que se controla con performance.clearMeasures() periódicamente si de verdad llegas a acumular miles.

Un último apunte sobre lo que no se puede hacer: no marques dentro de bucles calientes. Una marca por iteración sobre diez mil elementos añade trabajo real y ensucia el timeline hasta hacerlo inútil. Marca fases, no iteraciones.

De la forma a la corrección

El puente entre leer el perfil y arreglar el problema es siempre el mismo: encontrar la función con más tiempo propio dentro de la tarea más larga, y decidir qué hacer con ella. Las opciones son cuatro y las verás desarrolladas en los niveles siguientes:

Hacer menos trabajo. Un algoritmo mejor, menos elementos, menos renderizado. Siempre es la primera opción y la más ignorada.

Hacerlo más tarde. Diferir a después del pintado, a tiempo ocioso, a la primera interacción.

Hacerlo en trozos. Trocear con cesión del hilo, que es el nivel siguiente.

Hacerlo en otro hilo. Un trabajador, cuando la naturaleza del trabajo lo permite.

Y una advertencia final de método: mide después de cada cambio, no después de los cinco. Es frecuente que una optimización razonable no produzca ninguna mejora porque el cuello de botella estaba en otro sitio, y si has hecho cinco cambios a la vez no sabes cuál sirvió. El perfil antes y después de un solo cambio es la única forma de aprender qué funciona en tu aplicación concreta.

⚔️ Reto práctico

Graba tres perfiles de tu aplicación con las cinco condiciones respetadas: uno de carga en frío, uno de carga con caché caliente y uno mientras completas la interacción más importante. Para cada uno, identifica la forma dominante entre las cuatro y la función con más tiempo propio. Después instrumenta con marcas de usuario las cuatro fases principales y vuelve a grabar: compara cuánto tiempo te lleva llegar al diagnóstico con y sin las marcas.