Latencias y jank con Perfetto en Android: guía completa

  • Perfetto permite capturar trazas detalladas de kernel, procesos y memoria, clave para analizar latencia y jank en Android.
  • El modo ligero simplifica trazas rápidas con atrace y ftrace, mientras que el modo normal usa configuraciones proto avanzadas.
  • dumpsys complementa a Perfetto con diagnósticos de entrada, gráficos, red, batería y memoria específicos por servicio.
  • Combinar Perfetto y dumpsys ofrece una visión completa para optimizar fluidez, consumo y estabilidad de las apps Android.

Análisis de latencia y jank con Perfetto en Android

Cuando una app de Android se siente lenta, con tirones o animaciones a trompicones (desactivar las animaciones del sistema), casi siempre hay un culpable oculto en segundo plano: la latencia y el temido jank. Android ofrece varias herramientas de diagnóstico, pero una de las más potentes y flexibles es perfetto, combinada con el clásico comando dumpsys y fuentes de datos como ftrace, atrace o heapprofd. Entender bien cómo usarlas te permite pasar de la sensación vaga de “mi app va rara” a tener números, trazas y causas concretas.

En este artículo vamos a desgranar, con calma pero sin rodeos, cómo funciona perfetto en Android, qué modos de uso tiene, qué fuentes de datos puede activar y cómo se complementa con otros comandos clave como dumpsys gfxinfo, dumpsys meminfo o dumpsys batterystats. La idea es que tengas una visión completa de todo lo que puedes medir y cómo aprovecharlo para optimizar latencias, eliminar jank y de paso mejorar memoria, red y consumo de batería.

Qué es Perfetto y por qué es tan útil para latencia y jank

perfetto es una herramienta de trazas de rendimiento integrada en Android que se invoca normalmente desde el ordenador mediante Android Debug Bridge (ADB) con comandos del estilo adb shell perfetto .... Su misión es recolectar información de bajo nivel sobre lo que está ocurriendo en el dispositivo: actividades del kernel, anotaciones de usuario, uso de memoria, estadísticas de procesos, etc., todo ello en un formato de traza que luego puedes analizar con visores como la web de perfetto.dev.

Perfetto se alimenta de varias “fuentes de datos” especializadas, entre las que destacan:

  • ftrace, que captura eventos del kernel (planificación de hilos, sistema de archivos, etc.).
  • atrace, centrado en anotaciones desde el espacio de usuario para servicios y aplicaciones.
  • heapprofd, orientado al muestreo de uso de memoria nativa en servicios y apps.

Mediante la combinación adecuada de estas fuentes, puedes registrar exactamente la información que necesitas para perseguir problemas de latencia en la IU, fotogramas que se saltan, picos de CPU o bloqueos por E/S relacionados con el almacenamiento UFS.

Herramientas de trazas de rendimiento en Android

Sintaxis básica de Perfetto y modos de funcionamiento

Perfetto se puede usar en dos grandes modos: ligero y normal. Ambos se llaman desde ADB, pero cambian mucho la forma de configurar qué se traza y cómo se guarda.

La idea general es siempre la misma: ejecutar un comando adb shell perfetto indicando duración, tamaño de buffers, fuentes de datos y archivo de salida de la traza. El fichero resultante se genera normalmente siguiendo el formato de protocolo trace.proto del AOSP, que después puedes abrir en las herramientas de análisis de Perfetto.

Opciones generales al invocar Perfetto

Independientemente del modo (ligero o normal), hay una serie de banderas comunes que controlan cómo se ejecuta la captura, qué se hace con el archivo generado y cómo se integra con sistemas de alerta o subida remota:

  • --background o -d: hace que perfetto salga de la interfaz de línea de comandos y continúe grabando en segundo plano.
  • --background-wait o -D: similar a la anterior, pero espera hasta 30 segundos a que todas las fuentes de datos confirmen que han arrancado. El código de salida será 0 si todo va bien y distinto de 0 si hay error o timeout.
  • --alert-id, --config-id, --config-uid y --subscription-id: identificadores que enlazan la traza con alertas o configuraciones de disparo definidas en el sistema, útiles en escenarios de monitorización automatizada.
  • --out OUT_FILE o -o OUT_FILE: ruta completa donde se guardará el fichero de traza, o - si prefieres que vaya a stdout. Generalmente se usa un directorio como /data/misc/perfetto-traces.
  • --upload: al finalizar la captura, entrega el archivo de traza al paquete especificado por el mensaje IncidentReportConfig en la configuración de proto.
  • --no-guardrails y --reset-guardrails: controlan los mecanismos de seguridad y limitación de recursos cuando se activa la subida automática (--upload), pensados para pruebas y no tanto para producción.
  • --rsave-for-bugreport: si la captura tiene un bugreport_score mayor que 0, guarda la traza en un archivo y muestra la ruta al terminar, de cara a adjuntarlo fácilmente en informes de errores.
  • --query y --query-raw: consultan el estado del servicio de trazas. La primera da una salida legible, la segunda devuelve el contenido protocodificado de tracing_service_state.proto.
  • --help o -h: imprime la ayuda integrada de la herramienta.

Modo ligero de Perfetto: rápido y similar a systrace

El modo ligero de Perfetto está pensado para trazas rápidas, muy parecido a cómo se usaba históricamente systrace. Permite seleccionar solo un subconjunto básico de fuentes: esencialmente atrace y ftrace, y es útil junto a apps para optimizar el rendimiento.

La sintaxis típica en modo ligero es algo así:

adb shell perfetto ... --out FILE

Entre las opciones específicas más relevantes del modo ligero encontramos:

  • --time TIME o -t TIME: duración de la traza en segundos, minutos u horas. Por ejemplo, --time 1m captura durante un minuto. Si no se indica nada, se usan 10 segundos por defecto.
  • --buffer SIZE o -b SIZE: tamaño del buffer circular en memoria. El valor predeterminado suele ser algo como --buffer 32mb.
  • --size SIZE o -s SIZE: límite máximo del tamaño del fichero en disco. Si no se configura, Perfetto puede grabar solo en el buffer de memoria.
  • --app o -a: nombre del paquete de la app Android para usar en anotaciones de atrace.

Tras estas banderas se listan “especificadores de eventos”, que determinan qué categorías o eventos se van a registrar:

  • ATRACE_CAT: categorías de atrace que quieres habilitar (por ejemplo, wm para el WindowManager). Un comando típico sería: adb shell perfetto --out FILE wm.
  • FTRACE_GROUP/FTRACE_NAME: eventos concretos de ftrace, como sched/sched_switch. Podrías ejecutar: adb shell perfetto --out FILE sched/sched_switch.

Modo ligero de Perfetto para trazas rápidas

Modo normal de Perfetto: máximo control y más fuentes

El modo normal de Perfetto es mucho más potente y configurable. En lugar de pasar categorías sueltas, se le entrega un archivo de configuración (un proto) que describe en detalle qué fuentes de datos activar, cómo muestrear, qué buffers usar, etc.

La sintaxis habitual para el modo normal es:

adb shell perfetto --config CONFIG_FILE --out FILE

Las banderas específicas clave en este modo son:

  • --config CONFIG_FILE o -c CONFIG_FILE: ruta al archivo de configuración que sigue el esquema de trace_config.proto en AOSP. Dentro de este proto se usan elementos como TraceConfig y DataSourceConfig (definido en data_source_config.proto) para seleccionar y parametrizar las fuentes de datos.
  • --txt: indica que el fichero de configuración está en formato de texto pbtxt en lugar de binario. Muy cómodo para prototipar localmente, pero no se recomienda como formato definitivo en producción.

Fuentes de datos compatibles con Perfetto

La verdadera fuerza de Perfetto está en las distintas fuentes que puede habilitar. Cada una se configura desde el proto a través de un bloque de DataSourceConfig, y según el dispositivo, la versión de Android y el kernel, tendrás más o menos opciones disponibles.

Fuentes de datos de rendimiento en Android

Eventos de kernel con ftrace

La fuente ftrace permite a Perfetto capturar eventos internos del kernel, lo cual es oro puro cuando quieres entender por qué un hilo no se está planificando a tiempo o qué está bloqueando la CPU en un momento dado.

Para activar ftrace desde la configuración hay que definir el campo ftrace_config dentro de DataSourceConfig, seleccionando qué eventos específicos queremos rastrear. Algunos ejemplos habituales relacionados con programación de procesos son:

  • sched/sched_switch
  • sched/sched_wakeup
  • sched/sched_wakeup_new
  • sched/sched_process_exec
  • sched/sched_process_exit
  • sched/sched_process_fork
  • sched/sched_process_free
  • sched/sched_process_hang
  • sched/sched_process_wait

También se pueden habilitar eventos del sistema de archivos y anotaciones de atrace, de forma que una misma traza recoja tanto la parte de kernel como eventos de más alto nivel. La lista de eventos reales dependerá siempre del dispositivo y su kernel, así que conviene consultar los protocolos de configuración correspondientes.

Estadísticas de procesos y sistema

Otra fuente muy práctica es la de estadísticas de procesos y del propio sistema. Permite obtener contadores periódicos del uso de recursos tanto globales como por proceso individual, ideal para correlacionar picos de CPU o memoria con momentos concretos de jank y con aplicaciones en segundo plano.

Para utilizarla hay que configurar los campos process_stats_config y sys_stats_config dentro de DataSourceConfig. Los datos que se obtienen incluyen, entre otros, información de tiempo de CPU, uso de memoria, y más métricas que pueden variar según el dispositivo y la versión del sistema operativo.

Perfiles de memoria nativa con heapprofd

heapprofd es la pieza clave cuando necesitas entender el uso de memoria nativa de tu aplicación o servicios del sistema. Funciona mediante muestreo, generando perfiles que indican qué partes del código están reservando memoria.

Para encender heapprofd en una traza de Perfetto hay que rellenar el apartado heapprofd_config de DataSourceConfig. El resultado son ProfilePackets con la información de pilas de llamadas, incluyendo marcos Java cuando están disponibles. Es una manera potente de cazar fugas nativas o patrones de asignación ineficientes.

La documentación pública en perfetto.dev profundiza en cómo configurar estos perfiles, filtrar por procesos concretos, ajustar la frecuencia de muestreo, etc., lo que te permite adaptar el coste de la instrumentación al nivel de detalle que necesitas.

Otras fuentes de datos adicionales

Además de las anteriores, hay más fuentes disponibles dependiendo del dispositivo y la versión de Android. Algunas están orientadas a energía, otras a red o a métricas más específicas del framework. Para verlas en detalle hay que revisar los distintos esquemas de configuración de las data sources de Perfetto publicados en AOSP.

En cualquier caso, el patrón se repite siempre: eliges la fuente, configuras su bloque DataSourceConfig en el proto de TraceConfig y lanzas la traza con perfetto. Después, analizas el archivo resultante con las herramientas de visualización.

dumpsys: el complemento perfecto para medir latencias y rendimiento

Aunque Perfetto es la estrella para trazas de bajo nivel, la herramienta veterana dumpsys sigue siendo imprescindible cuando quieres diagnósticos de alto nivel, agrupados por servicio del sistema: entrada, gráfico, red, batería, memoria, etc.

dumpsys se ejecuta en el dispositivo Android y se invoca desde ADB con comandos como adb shell dumpsys. Si lo ejecutas sin parámetros, vuelca información de todos los servicios del sistema, algo que suele ser excesivo. Lo normal es especificar el servicio concreto que te interesa para centrarse solo en esa parte.

Sintaxis general del comando dumpsys

La forma genérica de llamar a dumpsys es:

adb shell dumpsys | -c | -h]

Algunos usos comunes serían:

  • adb shell dumpsys: vuelca todos los servicios (muy verboso).
  • adb shell dumpsys input: estado del sistema de entrada (teclados, pantallas táctiles, etc.).
  • adb shell dumpsys -l: lista todos los servicios disponibles.

Entre las opciones de línea de comandos principales destacan:

  • -t timeout: tiempo máximo en segundos que se le da a dumpsys para completar la operación (por defecto, 10s).
  • --help: ayuda de la herramienta genérica.
  • -l: listado de servicios del sistema.
  • --skip services: indica uno o varios servicios que quieres excluir de la salida cuando no especifiques ninguno concreto.
  • service : servicio concreto que quieres inspeccionar, con argumentos opcionales. Si dudas, muchos servicios aceptan -h para mostrar su propia ayuda, por ejemplo adb shell dumpsys procstats -h.
  • -c: hace que ciertos servicios devuelvan datos en un formato más apto para consumo por scripts o herramientas.
  • -h: en algunos servicios, imprime ayuda adicional específica.

Diagnósticos de entrada: toques, teclados y latencias de eventos

Para problemas de latencia relacionados con la entrada táctil o de teclado, el servicio clave es input. Con adb shell dumpsys input obtienes un volcado del estado de los dispositivos de entrada y del flujo de eventos desde que se generan hasta que llegan a las ventanas.

La salida incluye tres bloques lógicos importantes: el estado del Event Hub, el estado de InputReader y el estado de InputDispatcher. Cada uno te ayuda a detectar fallos en una parte del recorrido del evento.

Event Hub: dispositivos disponibles y su configuración

El “Event Hub State” lista todos los dispositivos de entrada que el sistema conoce, con información como la ruta de dispositivo, la clase, los ficheros de keylayout, keychars y configuración, además del identificador de teclado integrado (BuiltInKeyboardId).

Al revisar esta sección conviene comprobar:

  • Que todos los dispositivos físicos esperados aparecen correctamente listados.
  • Que cada uno tiene asignado su archivo de diseño de teclas, mapa de caracteres y fichero de configuración; si faltan o tienen errores de sintaxis, no se cargarán y la experiencia de entrada se resentirá.
  • Que el campo Classes tenga los bits adecuados, mapeados a constantes como INPUT_DEVICE_CLASS_TOUCH_MT en EventHub.h.
  • Que BuiltInKeyboardId sea -2 cuando no hay teclado integrado, o coincida con el ID del teclado interno en caso contrario. Si ves que no es -2 y debería serlo, probablemente falte un mapa de caracteres especial para algún teclado de funciones, que debería contener solo type SPECIAL_FUNCTION.

InputReader: cómo se interpretan los eventos de entrada

InputReader se encarga de “traducir” los eventos de bajo nivel del kernel a algo que el framework pueda entender: coordenadas táctiles, presión, tamaño de toque, etc. En su volcado verás la configuración detallada de cada dispositivo (por ejemplo, una pantalla táctil concreta) y las últimas acciones realizadas.

En el caso de pantallas táctiles es crucial revisar:

  • Los rangos de X e Y (mínimos, máximos, precisión, tolerancias).
  • Los parámetros de calibración (escalas de tamaño, presión, orientación, etc.).
  • El tamaño de la superficie (anchura y altura en píxeles).
  • Los factores de traducción y escalado, que determinan cómo se mapean las coordenadas crudas al espacio de pantalla.

Al final de esta sección también se listan parámetros globales como el intervalo de toques, los umbrales de velocidad del puntero o las configuraciones de gestos (tiempo de doble toque, distancia mínima, etc.), que afectan de forma directa a la sensación de fluidez.

InputDispatcher: envío de eventos a ventanas y ANR

InputDispatcher maneja el envío de los eventos de entrada a las distintas ventanas. Su estado muestra qué ventana está enfocada, cuáles son táctiles, cómo están las colas de entrada y si hay algún ANR (Application Not Responding) en curso.

En la práctica, esta sección permite comprobar:

  • Qué ventana estaba recibiendo los toques en el momento de hacer el dumpsys.
  • Si hay eventos pendientes o colas bloqueadas que puedan estar aumentando la latencia percibida.
  • Cómo se distribuyen las conexiones de entrada entre distintas ventanas y si alguna está saturando su cola.

Una comprobación sencilla pero muy reveladora es tocar la pantalla, lanzar inmediatamente adb shell dumpsys input y ver si la línea de TouchStates identifica correctamente la ventana que has tocado. Si no es así, hay algo raro en la gestión de foco o en el mapeo de regiones táctiles.

Medición de rendimiento de la IU con gfxinfo y framestats

Cuando la preocupación principal es el jank en animaciones y scroll, el servicio gfxinfo es tu amigo. A través de dumpsys gfxinfo puedes obtener datos sobre los fotogramas renderizados para una app concreta.

El comando básico para una app concreta es:

adb shell dumpsys gfxinfo package-name

Si añades la opción framestats, el diagnóstico se vuelve todavía más detallado:

adb shell dumpsys gfxinfo package-name framestats

Con esto obtendrás estadísticas de latencia fotograma a fotograma de las animaciones recientes, lo que ayuda mucho a localizar transiciones o pantallas concretas donde se dispara el tiempo de renderizado. Esta información se puede integrar en pruebas automatizadas o macrobenchmarks para vigilar regresiones entre versiones de la app.

Diagnósticos de red con dumpsys netstats

Para entender si la latencia de la IU está relacionada con red lenta o picos de tráfico, el servicio netstats resulta muy útil. Recoge estadísticas de uso de red desde que el dispositivo arrancó.

El comando típico con más detalle es:

adb shell dumpsys netstats detail

La salida se organiza en varias secciones:

  • Interfaces activas e interfaces UID activas, donde suelen coincidir nombres como wlan0 y su identificador de red.
  • Estadísticas “Dev” y “Xt”, que muestran históricos con bucket de tiempo (por ejemplo, en tramos de una hora) y campos como bytes recibidos (rb), paquetes recibidos (rp), bytes transmitidos (tb), etc.
  • Estadísticas por UID, donde puedes desglosar el consumo de red de una app concreta, diferenciando móvil y Wi‑Fi.

Para localizar el UID de tu app, se usa algo como:

adb shell dumpsys package your-package-name | grep userId

Una vez sepas el valor de userId, puedes buscar en la salida de netstats las líneas con uid=ese_valor y ver, por ejemplo, cuántos bytes y paquetes ha usado en cada periodo de dos horas, y si estaba en primer plano (set=DEFAULT) o en segundo plano (set=BACKGROUND).

Uso de batería y energía con batterystats

La latencia y el jank no solo se arreglan con más rendimiento; a veces conviene mirar también cómo afectan las optimizaciones al consumo energético. dumpsys batterystats da un informe muy amplio sobre uso de batería por UID y por componente.

El comando base para este servicio es:

adb shell dumpsys batterystats options

Si quieres centrarte en una app concreta desde la última carga, se utiliza:

adb shell dumpsys batterystats --charged package-name

La salida habitual incluye:

  • Historial de eventos relacionados con la batería.
  • Estadísticas globales del dispositivo.
  • Estimaciones de uso energético por UID y por componentes del sistema.
  • Tiempo de uso de red móvil por app y paquete.
  • Estadísticas globales de UID del sistema y de apps.

Formato checkin (CSV) para análisis automatizado

Si necesitas procesar los datos de batería con scripts o herramientas externas, puedes generar una salida “para máquinas” con:

adb shell dumpsys batterystats --checkin

Este formato presenta cada observación en una línea CSV, con un identificador de sección que determina cómo interpretar el resto de campos. Algunos ejemplos de secciones son:

  • vers: versiones de checkin, parcel y plataforma.
  • uid: relación UID – nombre de paquete.
  • apk, pr, sr, vib, fg, etc.: uso de procesos, sensores, vibrador, tiempo en primer plano, sincronizaciones, jobs, etc.
  • nt y gn: estadísticas de red (bytes y paquetes móviles/Wi‑Fi, tiempos activos, etc.).
  • bt, dc, lv: datos de batería como tiempos, niveles y descargas.
  • wfl, gwfl, gble: información específica de Wi‑Fi y Bluetooth, incluyendo tiempos y consumo estimado en mAh.

En versiones recientes de Android (6.0 en adelante), el uso de energía de Wi‑Fi, radio móvil y Bluetooth se reporta como elementos pwi con etiquetas dedicadas (wifi, blue, cell), mientras que en versiones antiguas se agrupaba en la sección m (miscelánea).

Inspección de memoria a lo largo del tiempo con procstats

Si sospechas que el jank viene de fugas de memoria o de apps pesadas en segundo plano, dumpsys procstats te permite ver cómo se comporta tu aplicación a lo largo del tiempo: cuánto ha estado activa, en qué estado y con qué consumo de memoria.

Un uso típico es pedir estadísticas de las últimas horas, por ejemplo:

adb shell dumpsys procstats --hours 3

La salida resume para cada app el porcentaje de tiempo que ha estado viva, junto con valores de PSS, USS y RSS en forma de mínimo, promedio y máximo, junto con el número de muestras tomadas. También muestra un resumen de memoria por tipo de proceso (kernel, nativo, persistente, top, servicios, procesos en caché, etc.).

Esta visión agregada ayuda a detectar patrones como aplicaciones que permanecen demasiado tiempo en estados en segundo plano, o que mantienen un PSS medio o máximo muy elevado, posibles candidatas a optimización o incluso a estar causando presión de memoria que degrada el rendimiento del sistema.

Instantánea detallada de memoria con meminfo

Cuando necesitas una foto fija muy granular del uso de memoria de un proceso concreto, la herramienta es dumpsys meminfo. Con ella puedes ver cómo se reparte la RAM entre montones nativos, Dalvik/ART, mapas de código, buffers gráficos, etc.

La sintaxis básica es:

adb shell dumpsys meminfo package_name|pid

La opción -d amplía la información para Dalvik/ART, mostrando detalles internos como el espacio de objetos grandes (.LOS), sobrecarga del GC, caché del JIT, espacio de Zygote, etc. Con -h puedes ver todas las banderas disponibles.

Las columnas clave a observar suelen ser Pss Total y Private Dirty, que reflejan respectivamente el uso de RAM ajustado por compartición y la cantidad de memoria privada modificada que se liberará solo al destruir el proceso. En ocasiones también son interesantes Private Clean y Heap Alloc. Si detectas consumos anómalos, revisa cómo liberar memoria RAM para mitigarlos.

Algunas categorías relevantes en la tabla:

  • Native Heap y Dalvik Heap: uso de memoria de montones nativo y de la VM. Heap Alloc muestra lo que los asignadores creen reservado, que puede ser superior a PSS por el efecto de Zygote y memoria compartida.
  • .so mmap, .dex mmap, .oat mmap y .art mmap: RAM usada por código nativo y bytecode, incluidas las imágenes de ART y sus espacios compartidos.
  • .Heap, .LOS, .GC, .JITCache, .Zygote, .NonMoving, .IndirectRef (con -d): desglosan aún más el uso interno de la memoria gestionada por ART.
  • Unknown: páginas que no se pudieron clasificar en categorías anteriores, a menudo asignaciones nativas vistas a través de ASLR.

La línea TOTAL agrupa el PSS total del proceso, que se puede comparar con el PSS de otros procesos y con la RAM disponible. Private Dirty + Private Clean indican la memoria que se liberará al matar el proceso.

Además, al final del informe se listan contadores de objetos de alto nivel como número de ViewRootImpl, AppContexts o Activities, lo que ayuda a detectar fugas típicas de contextos o actividades retenidas por referencias estáticas o por vistas/drawables que siguen apuntando a ellas.

Conviene recordar que una View o un Drawable también mantienen referencia a la Activity de la que salen, así que conservarlos de forma indebida puede implicar fugas de toda la actividad y sus recursos asociados.

Combinando las trazas detalladas de Perfetto con los diagnósticos de dumpsys (entrada, gráficos, memoria, red y batería) se consigue una visión de 360 grados sobre cómo se comporta una app Android: desde cuándo se programa un hilo hasta cuántos milisegundos tarda en pintar un frame o cuánta memoria y energía consume de más. Usar estas herramientas de forma habitual en el desarrollo y las pruebas marca la diferencia entre una app que “más o menos funciona” y otra que se percibe realmente fluida, estable y eficiente incluso en dispositivos modestos al optimizar Android.

soluciones para mejorar el rendimiento de Android lento
Artículo relacionado:
Soluciones Completa para Mejorar el Rendimiento de un Android Lento: Guía Total