Herramientas y rendimiento

Trace me if you can: Automated instrumentation and field profiling

Bocinas

  • Rahul Ravikumar

    Ingeniero de software del personal

Acerca de

Acerca de la charla

Para optimizar las apps para Android, se requiere una visibilidad profunda de los flujos de trabajo simultáneos y las suspensiones de corrutinas.

Si bien la generación de perfiles local ofrece una instantánea útil, escalar estas estadísticas a la producción sin sobrecarga sigue siendo un desafío.

En esta sesión, se ofrece un plan de arquitectura para un ciclo de reacción continuo del rendimiento.

androidx.tracing 2.0: Intercambia backends de forma dinámica y propaga contextos de corrutinas (flujos de Perfetto) entre subprocesos.

Instrumentación inteligente: Usa el seguimiento local de ART para encontrar puntos de acceso y, luego, automatiza la inserción de puntos de seguimiento de baja sobrecarga.

Macrocomparativas: Realiza un seguimiento de las regresiones y verifica las mejoras de rendimiento en dispositivos físicos en la CI.

Seguimiento de campo: Ve más allá del seguimiento local y de CI con ProfilingManager.

Resumen

  • Tracing 2.0 reemplaza las APIs de seguimiento de Android más antiguas para quitar los límites de longitud de las etiquetas y ejecutarse siete veces más rápido. También te permite compilar receptores personalizados para backends como OpenTelemetry.
  • La propagación de contexto asigna relaciones causales entre subprocesos, corrutinas y procesos. Puedes hacer un seguimiento de la ejecución asíncrona con traceCoroutine o pasar tokens de propagación al código basado en devoluciones de llamada.
  • Jetpack Compose 1.13 agrega el seguimiento de recomposiciones, que usa metadatos personalizados para mostrar por qué se producen las recomposiciones. Los seguimientos registran los valores de estado modificados y las pilas de llamadas exactas que los activaron.
  • Las herramientas de compilación de Android usan Tracing 2.0 para detectar cuellos de botella y acelerar las compilaciones. Optimizaciones guiadas por seguimiento que hicieron que la reducción de código de R8 fuera un 25% más rápida y redujeron los tiempos posteriores al envío de Metalava en un 15%

Gemini Notebook

Temas

  • seguimiento
  • android-performance
  • perfetto
  • context-propagation
  • jetpack-compose
  • corrutinas
  • generación de perfiles

Transcripción

Descargar transcripción

Introducción y evolución del seguimiento de Android

  • Rahul Ravikumar

    Buenas tardes a todos.

  • Me llamo Raúl.

  • Soy ingeniero del equipo de Experiencia del Desarrollador de Android.

  • Me enfoco en el rendimiento de las apps y las bibliotecas, y esta charla trata sobre el seguimiento.

  • Antes de profundizar en el seguimiento, quiero establecer un vocabulario compartido para que puedas seguir el ritmo.

  • Por lo tanto, cada vez que me refiera al seguimiento en esta charla, me refiero al seguimiento explícito, que consiste en instrumentar el código de forma manual para que sepas qué hace la app.

  • No me refiero a los generadores de perfiles automáticos que analizaré.

  • Por lo tanto, el seguimiento manual es el arte de dejar migas de pan en tu base de código para que puedas razonar sobre él más adelante cuando tengas un problema de rendimiento o cualquier otro problema.

  • Por lo general, es mucho mejor que los registros.

  • No sé si viste un resultado de Logcat de una app grande.

  • Por lo general, es un muro de texto gigante que no se puede analizar con facilidad.

  • Por otro lado, el seguimiento es mucho más agradable.

  • Es más fácil de visualizar.

  • Tienes duraciones.

  • Tienes cierta estructura.

  • Tienes algunas herramientas, por lo que incluso puedes desglosar y ver exactamente qué sucedió y cuándo.

  • Por lo tanto, es mucho más agradable.

  • Y puedes, ya sabes, apuntar tus agentes a un seguimiento y decir, haz preguntas.

  • Puedes usar herramientas como Trace Processor, comparativas de rendimiento y, además, convertir tus seguimientos en métricas.

  • Estas son todas las cosas que puedes hacer con los seguimientos y que es difícil hacer con Logcat.

  • Por otro lado, la generación de perfiles, y me refiero a la generación de perfiles automática, suele ser una respuesta a un problema que ya conoces.

  • Por ejemplo, es posible que hayas notado que tu app se inicia con lentitud o que algo no funciona bien, y quieres acotar el problema y averiguar qué está sucediendo.

  • En este punto, si aún no tienes seguimientos manuales que te ayuden, aquí es donde entra en juego la generación de perfiles automática.

  • Puedes usar algo como el seguimiento de métodos de ART, en el que instrumentas cada entrada y salida de método.

  • O bien, puedes consultar el muestreo de pila con herramientas como Simpleperf.

  • Por lo tanto, en lugar de usar cada entrada y salida de método y tener una sobrecarga alta, puedes observar las muestras de la pila de llamadas y ver qué hay en la ruta de acceso activa de tu app.

  • Estamos en el año 2026, la RAM es un problema en todas partes, por lo que es posible que estés analizando la memoria, las fugas de memoria y los perfiles del montón.

  • Por lo tanto, este es otro generador de perfiles que puedes aprovechar.

  • Puedes consultar las rutas de retención del montón.

  • Puedes ver qué código está asignando memoria, y esos son todos los tipos de análisis que puedes hacer con esto.

  • Así es.

  • Ahora, tenemos ese vocabulario.

  • Ahora hablemos de por qué creamos una biblioteca nueva.

  • Las APIs de seguimiento, las APIs 1.0, se introdujeron originalmente en Android Jelly Bean.

  • Eso fue en 2012.

  • Eso fue hace más de 14 años.

  • Desde entonces, las APIs no han evolucionado tanto como crees.

  • Agregamos algunas extensiones.

  • Los eventos asíncronos y los contadores se agregaron en Android 10.

  • Eso fue en 2019.

  • Y eso fue todo.

  • La API no ha evolucionado desde entonces.

  • Sin embargo, el ecosistema ha evolucionado bastante.

  • Por lo tanto, el seguimiento de la plataforma mejoró a pasos agigantados, pero nada de eso se filtró en términos de adiciones de APIs para ti.

  • Eso es algo que queremos cambiar.

  • El otro gran problema es que el seguimiento tiene un backend de seguimiento fijo en la versión 1.0, lo que significa que cada vez que agregas una sección de seguimiento en tu código, siempre termina en el búfer de seguimiento del sistema.

  • Atrace es el subsistema de seguimiento de Android que posee esos datos.

  • Por lo tanto, si quisieras tomar tus seguimientos y convertirlos en telemetría de campo, sería muy difícil, porque, en primer lugar, tendrías que darte cuenta de cómo un seguimiento almacena esos datos.

  • Es un formato bien documentado, pero potencialmente difícil de consumir.

  • Por lo tanto, eso es algo que queremos corregir.

  • Por lo tanto, queremos darte las herramientas para que lleves tus datos de seguimiento a donde quieras.

  • También teníamos una restricción en el seguimiento 1.0, en el que las etiquetas estaban limitadas a 128 caracteres.

  • Ahora bien, esa probablemente era una restricción razonable en 2012, pero en el año 2026, parece bastante extraña.

  • Lo que empeora las cosas es que tampoco puedes agregar metadatos adicionales a tu seguimiento, lo que genera más problemas.

  • Y, luego, está la sobrecarga.

  • Por lo tanto, cada vez que realices un seguimiento de tu app, debes tener muy en cuenta la sobrecarga, ya que el acto de observar un sistema no debería cambiar lo que este hace de forma interna.

  • Por lo tanto, si hay una condición de carrera que quieres reproducir, el seguimiento no debería hacer que desaparezca.

  • Por lo tanto, debes asegurarte de conservar toda la semántica que estás usando de otra manera.

  • La versión 1.0 de Tracing tenía una sobrecarga relativamente alta, y queríamos mejorarla.

  • Bien.

  • Hablé mucho sobre los problemas.

  • Ahora, veamos algunos de los problemas de forma visual y cómo se agravan en la práctica.

  • Aquí tenemos un registro de Perfetto.

  • Es para una app, com.example.app.

  • Tenemos un subproceso principal y un solo segmento llamado initializeComponent.

  • Ahora, lo ideal sería tener más detalles.

  • Así que agreguemos un detalle.

  • Ahora sabemos que initializeComponent llama a performInitialize.

  • Aún no sabemos qué componente se está inicializando.

  • Ahora, la única herramienta en nuestro cinturón de herramientas con Tracing 1 es el nombre, por lo que lo agregamos al nombre del segmento.

  • Recuerda que los nombres de los segmentos de seguimiento están restringidos a 128 caracteres, por lo que se truncan en el seguimiento.

  • Ahora, para colmo, la sobrecarga de truncamiento también es una sobrecarga en la que incurres cuando realizas el seguimiento.

  • Ahora, te encuentras con este problema bastante rápido.

  • Por lo tanto, esto es algo que realmente queríamos corregir.

  • Viste el registro de Perfetto y cómo se ve visualmente.

  • Ahora, veamos desde la perspectiva de escribir el código para emitir esas secciones de seguimiento.

  • Nuevamente, tenemos la API de initializeComponent.

  • Toma un contexto y un nombre, y llama a performInitialize.

  • Hasta el momento, todo va bien

  • Ahora, queremos agregar un seguimiento con la API de Tracing 1.0, por lo que debes importar androidx.tracing.trace y usar un bloque de seguimiento.

  • Asígnale un nombre.

  • Aquí, es performInitialize.

  • Pero, como queríamos hacer antes, queríamos saber el nombre del componente, por lo que usaremos la interpolación de cadenas de Kotlin y agregaremos el nombre.

  • Y aquí está el primer problema.

  • Aquí tenemos el límite de 128 caracteres.

Descripción general del seguimiento 2.0 y el rendimiento

  • Rahul Ravikumar

    Podemos solucionar este problema con Tracing 2.

  • En lugar de usar androidx.tracing.trace, usas Tracer.

  • La API se ve muy similar.

  • En lugar de trace, tienes Tracer.global.trace.

  • Tienes una categoría.

  • Más adelante, hablaremos sobre las categorías con más detalle, pero, básicamente, tienes un nombre.

  • Y, luego, todavía tienes el bloque de código que intentas rastrear.

  • Y ahora, ya no tienes la limitación de 128 caracteres.

  • Hablé sobre la sobrecarga de seguimiento.

  • Quiero entrar en un poco más de detalle y ver cómo mejoramos esto.

  • En un Pixel 6a con Android 16, el acto de insertar una sola sección de inicio y de finalización tardó alrededor de 1,200 nanosegundos.

  • Ahora bien, puede que no parezca mucho, pero si tienes un código muy activo que agrega muchas secciones de seguimiento, entonces es tiempo que tu app podría estar dedicando a renderizar fotogramas.

  • Por lo tanto, no queremos quitarte ese tiempo.

  • De hecho, mejoramos bastante las cosas con el SDK de Perfetto de AndroidX.

  • Este era un SDK experimental que presentamos para el seguimiento de la composición, y mejoró mucho la situación.

  • En realidad, nunca quisimos que enviaras esto porque es el SDK de Perfetto C con vinculaciones de JNI, y el SDK de C tiene un tamaño de alrededor de 4 MB dentro de tu APK.

  • No es algo que quisiéramos que enviaras.

  • Ahora, con Tracing 2, tenemos una biblioteca diseñada específicamente para este propósito.

  • Mejoramos mucho más la sobrecarga, y ahora te alegrará saber que Tracing 2 es 7 veces más rápido, lo que significa que tienes más tiempo para hacer lo que quieras y, ya sabes, la infraestructura lleva menos tiempo.

  • ¿Qué hace posible algo como esto que antes no era posible?

  • Uno de los ejemplos es la función de seguimiento del entorno de ejecución de Metro.

  • Para quienes no lo saben, Metro es una biblioteca de inserción de dependencias.

  • Admite Kotlin Multiplatform.

  • Usa un complemento del compilador de Kotlin.

  • Genera todo el código para la inserción de dependencias y se integra con Tracing 2.0 para facilitar la visualización del costo de cada @Inject en tu código.

  • Este es un ejemplo.

  • Supongamos que tenemos un gráfico de la app.

  • En Metro, todo tiene como alcance un grafo, por lo que tenemos un solo campo, AnalyticsTracker.

  • También tenemos una vinculación de proveedor.

  • Tenemos un cliente de red con OkHttpClient como impl, y la implementación de AnalyticsTracker proviene de AnalyticsTrackerImpl. Necesitamos una instancia del cliente de red, que el grafo sabe cómo proporcionar.

  • En segundo plano, el complemento del compilador genera un código similar a este.

  • Por lo tanto, si tienes AnalyticsTracker, el método get crea una instancia del cliente de red y la pasa al constructor de AnalyticsTrackerImpl, lo que es bueno.

  • Pero ahora, con el seguimiento habilitado, puedes ver el costo de creación no solo de la implementación del rastreador, sino también de cada dependencia que necesita.

  • Ahora, te permitimos ver no solo el costo de crear una instancia del tipo, sino también todas sus dependencias en un seguimiento, lo que es muy útil.

  • Por lo tanto, con esta información, Wise, que es una app de finanzas, mejoró el inicio de su app en un 45%.

  • Pudieron aplazar las cosas que no eran necesarias para inicializar durante el inicio de la app y también identificaron algunos elementos con un alcance incorrecto, en los que las cosas que deberían haber sido un singleton no lo eran por accidente, y estas son cosas que puedes ver y corregir fácilmente en tu código.

  • Si observas sus métricas de inicio, son realmente impresionantes.

  • No solo mejoraron el tiempo hasta el fotograma inicial, sino que también mejoraron el tiempo hasta la visualización completa.

  • Otro socio, BandLab, usa la misma información sobre la IC.

  • Por lo tanto, no solo conoces el costo de cada vinculación de @Inject, sino que también sabes qué subproceso generó ese costo.

  • Una vez que sepas eso, podrás encontrar todas las vinculaciones de @Inject costosas en el subproceso principal y, en tu CI, podrás ejecutar una consulta para obtener una lista de todas las vinculaciones costosas y, luego, atribuirlas al cambio que las introdujo. De esta manera, podrás identificar el cambio real y tratar de corregirlo.

Propagación de contexto, corrutinas y Jetpack Compose

  • Rahul Ravikumar

    Bien.

  • Hasta ahora, hablé de todo lo que mejoramos en comparación con Tracing 1, pero ahora voy a hablar de las nuevas funciones de Tracing 2 que antes no eran posibles.

  • Por lo tanto, una de las funciones principales se llama propagación de contexto y, debido a que las apps modernas son cada vez más complejas, las cargas de trabajo se ejecutan en varios subprocesos, varias corrutinas y varios procesos.

  • Quieres poder visualizar cómo se propaga una solicitud entre cada subproceso y proceso.

  • Y eso es lo que hace posible la propagación de contexto.

  • Por lo tanto, si solo te diera una lista de porciones sin decirte nada sobre qué causó qué, serían útiles, pero no sería muy fácil razonar sobre ellas.

  • Pero si, en cambio, te dijera que esta fue la cadena causal de eventos que ocurrieron, te resultaría mucho más fácil descubrir qué estaba pasando y, luego, asociar el costo de cada porción.

  • Esto es lo que la propagación de contexto pone a disposición.

  • Te alegrará saber que la propagación de contexto es totalmente compatible y, por lo tanto, aquí tienes un ejemplo real.

  • Si volvemos a mirar el registro de Perfetto de com.example.app, supongamos que performInitialize tardaba demasiado y querías diferir el costo o moverlo a un subproceso diferente.

  • Algo que puedes hacer sin problemas.

  • Supongamos que moví performInitialize a otro hilo.

  • Ahora, perdiste esa naturaleza jerárquica de la porción.

  • Por lo tanto, ahora ya no puedes saber que initializeComponent fue lo que causó que ocurriera performInitialize, a menos que mires el código.

  • En un mundo ideal, querrías que una flecha te dijera que A causó B y, luego, podrías razonar sobre la cadena causal de eventos.

  • Es posible que incluso hayas visto un seguimiento del sistema en el que hacemos esto para las transacciones de Binder en la plataforma.

  • Aquí puedes ver que la comparativa de Compose realiza una transacción de Binder a system_server para preparar un fotograma.

  • Esto siempre fue posible desde la perspectiva del seguimiento de la plataforma, pero nunca pusimos las herramientas a disposición de los desarrolladores de apps.

  • Por lo tanto, ahora la propagación de contexto de corrutinas de Kotlin es totalmente compatible con Tracing 2.0.

  • No tiene que realizar ninguna acción.

  • Solo tienes que sacarlo de la caja.

  • Aquí, tenemos un solo método de suspensión llamado fetchProfile.

  • Llama a un client.get, que es otro método de suspensión, y, luego, cambia a Dispatchers.Default para deserializar la respuesta.

  • Es un código de corrutinas muy estándar, algo con lo que probablemente lidias todos los días.

  • Ahora, supongamos que queremos hacer un seguimiento de esto.

  • Agregamos una sección de seguimiento.

  • Aquí, en lugar de trace, usaremos la API de traceCoroutine, ya que es un bloque de código de suspensión y, luego, para la llamada interna antes de cambiar el contexto a Dispatchers.Default, agregaremos otra sección de seguimiento interna con la misma API de traceCoroutine.

  • Ahora, en un seguimiento, verás algo como esto.

  • Verás que nuestro trabajo comenzó en worker-1 y, luego, cambió de contexto y se movió a worker-3. Verás flechas útiles en el registro que lo indican.

  • No solo eso, si acercas el cuadrado del medio, puedes ver que la deserialización en realidad comenzó en worker-1 y la llamada a withContext es lo que causó el cambio de contexto y, luego, continuó en worker-3 y, luego, fetchProfile se reanudó en worker-3 porque la maquinaria de corrutinas quiere minimizar la cantidad de cambios de contexto que ocurren.

  • Puedes ver exactamente cuándo se suspendió una corrutina, cuándo se reanudó y la cadena causal completa de eventos.

  • Otra cosa que permite Tracing 2 es agregar metadatos personalizados a un seguimiento.

  • En el pasado, la única herramienta que tenías era el nombre de la sección de seguimiento, pero ahora, junto con eso, puedes agregar un bloque de metadatos que te permite agregar tantas entradas de metadatos como desees.

  • Y puedes agregar eso al registro.

  • Cuando observas el seguimiento real, puedes ver que todos los metadatos que agregaste están en el seguimiento para que todas las herramientas los consuman y usen.

  • Ahora, puedes incorporar todo lo que sabes, toda la información adicional que siempre quisiste.

  • Te di un montón de funciones abstractas que el seguimiento hace posible, pero donde todo esto se destaca es en una nueva función de Jetpack Compose llamada Recomposition Tracing.

  • Esto se incluye como parte de Compose 1.13, por lo que ya está listo para que lo uses.

  • Este es un ejemplo.

  • Este es el seguimiento de composición con el que probablemente todos estén familiarizados.

  • Esto existe desde los inicios de Compose.

  • Te indica el costo relativo de un elemento componible y todas sus, ya sabes, subporciones, y el costo de cada uno en el subproceso en el que se estaban produciendo.

  • Esto es bastante sencillo.

  • Lo que no te dice es el contexto.

  • No te dice por qué sucedió esto.

  • No te indica el motivo por el que se volvieron a componer los elementos.

  • Si alejamos la imagen, esa es la región de interés que acabamos de ver en la diapositiva anterior.

  • Y, a la izquierda, puedes ver que se produjo un evento de entrada de envío.

  • Tal vez eso es lo que cambió algún estado y lo que causó la recomposición.

  • Pero no está claro.

  • No lo sabes con certeza.

  • Sin embargo, con el seguimiento de recomposición, ahora agregamos metadatos personalizados al seguimiento que te indican exactamente por qué se produjeron las recomposiciones.

  • No solo eso, sino que ahora es muy fácil para ti saber que estos eventos están relacionados.

  • Si haces clic en ellas, te indicarán exactamente qué estado cambió.

  • Te indicará que era un estado, un valor de estado mutable que se cambió a falso, que antes era verdadero.

  • No solo eso, sino que incluso te indicaremos la pila de llamadas exacta que lo causó.

  • Aquí puedes ver que se produjo AbstractClickableNode.performClick, lo que provocó que cambiara el estado mutable y que se produjera la recomposición.

  • Esto es realmente genial, algo que no habíamos podido hacer antes.

  • Estas herramientas también están disponibles, perdón, todas estas funciones también están disponibles en la nueva herramienta Android Performance Analyzer, por si quieres probar algunas de las herramientas de agentes.

  • Y, claro,

  • Te dije que la propagación de contexto es totalmente compatible con las corrutinas de Kotlin.

  • Pero ¿qué pasa con el código que usa una combinación de corrutinas y código que no es de corrutinas, o tal vez estás ejecutando subprocesos por tu cuenta, o tal vez tienes algún código brownfield que es demasiado antiguo y que usa devoluciones de llamada por algún motivo?

  • Bien.

  • La buena noticia es que puedes obtener todos los beneficios de la propagación de contexto allí.

  • Supongamos que tenemos una API de brownfield que usa una devolución de llamada y un ejecutor.

  • La implementación usa executor.execute, llama a runCatching, que es el detalle de la implementación, obtiene la respuesta y, luego, le informa a la devolución de llamada sobre la respuesta.

  • Bien.

  • Ahora, si quisiera hacer un seguimiento de esto, haría Tracer.global.trace, le daría una etiqueta y, luego, rodearía el bloque.

  • Pero como también quiero preservar la cadena causal de eventos, aceptaré un tipo nuevo y opcional, que se llama token de propagación.

  • Lo que haré es pasar el token a la API de seguimiento interna.

  • Por lo tanto, quien llame a este método puede, de forma opcional, darme el token de propagación para que pueda preservar la cadena causal de eventos que ocurren.

  • Bien.

  • Ahora, el emisor de esa API puede ser una API de suspensión.

  • Aquí tengo una función de suspensión profileRequest, que ahora llamará a la API que usa una devolución de llamada y un ejecutor.

  • Aquí, como estoy uniendo dos mundos, el de suspensión y el de devolución de llamada, usaré el compilador suspendCoroutine.

  • Obtendré una continuación y, luego, invocaré la API y reanudaré la continuación en la devolución de llamada, una vez que obtenga la respuesta.

  • Y aquí está el ejecutor que estoy definiendo, que se llama profileRequestContext.

  • Es un ejecutor de un solo subproceso.

  • Y, como traceCoroutine incorpora un token de propagación dentro del contexto de la corrutina, puedo obtener el token de propagación desde dentro del contexto de la corrutina y, luego, pasarlo como entrada a la API de Brownfield, de modo que pueda conservar la cadena causal de eventos.

  • Y en el seguimiento, se hace muy evidente.

  • Aquí tienes algo que comenzó en worker-3, que cambió al ejecutor de un solo subproceso, una vez que hicimos el cambio de contexto, y luego se reanudó en worker.

  • Es bastante genial.

Seguimiento global e inicialización personalizada

  • Rahul Ravikumar

    Bien.

  • Otra de las cosas que puedes hacer es personalizar Tracer.global.

  • Hasta ahora, la API de Tracing 2.0 te brinda valores predeterminados muy buenos para lo que debería ser Tracer.global, pero si alguna vez quisieras personalizarlo, puedes hacerlo.

  • Además, Tracing 2.0 admite JVM y Android porque queremos que el seguimiento sea totalmente compatible con las bibliotecas de Java y Android.

  • Por lo tanto, los tres pasos para la inicialización son los siguientes: primero, creas un receptor de seguimiento.

  • Un receptor de seguimiento es, en efecto, la forma en que se almacenan los paquetes de seguimiento.

  • Define el formato de conexión y la ubicación de los seguimientos de salida en los que se almacenan.

  • Te proporcionamos una implementación lista para usar de un receptor de registros que admite el formato de Perfetto, pero, por supuesto, puedes escribir tu propio receptor de registros.

  • Esto se remonta al intento de resolver el problema de que atrace era el único backend disponible en Tracing 1.

  • Queríamos solucionar ese problema.

  • Por lo tanto, ahora, si quieres escribir un receptor de OpenTelemetry o quieres traer tu propio receptor, es totalmente posible.

  • Puedes anular esa opción.

  • Una vez que tengas un receptor, puedes crear un controlador de seguimiento y asignarle el receptor.

  • Y, luego, también puedes elegir de forma opcional qué categorías quieres, ya sabes, activar.

  • Aquí es donde las categorías realmente se destacan.

  • Por lo tanto, puedes decidir que, en la versión, algunas categorías son más importantes que otras, en comparación con la depuración, y puedes decir que esas son las categorías que quieres activar.

  • O también puedes decir: "Quiero activar todas las categorías, independientemente de su origen".

  • No pasa nada.

  • También puedes agregar atributos personalizados que sean globales para tu seguimiento.

  • Por lo tanto, puedes incorporar elementos como la versión de compilación, el tipo o cualquier otro metadato que quieras adjuntar a un seguimiento con la API de addAttribute.

  • Por último, puedes registrar un rastreador llamando a setGlobalTracer.

  • Esto es lo que vincula Tracer.global a esta instancia de tracer y, posteriormente, lo que está disponible para el proceso.

  • En Android, como los procesos tienen un ciclo de vida mucho mejor definido, todo lo que tienes que hacer es implementar AbstractTraceDriver.Factory, anular su método create y hacer exactamente lo que hiciste antes.

  • Por lo tanto, creas el receptor y, luego, el controlador.

  • No es necesario que te registres aquí.

  • Solo haremos el registro por ti, porque sabemos que esto es lo que sucedió primero.

  • Bien.

  • Muy bien, quiero reiterar que el seguimiento está listo para varias plataformas.

  • Esta es una elección muy intencional porque sabemos que Compose se ejecuta en varias plataformas y queremos que puedas realizar un seguimiento en la mayor cantidad de lugares posible.

  • Actualmente, admitimos Android y JVM, pero nuestra implementación deja la puerta abierta para que admitamos otras plataformas en el futuro si así lo deseamos.

Optimizaciones de herramientas de compilación (R8 y metalava)

  • Rahul Ravikumar

    Te alegrará saber que R8, la herramienta que se usa para reducir y optimizar el código fuente en Android, usa Tracing 2.0.

  • Y, aprovechando los seguimientos, optimizamos R8 para que reduzca y optimice tu código un 25% más rápido.

  • Esto significa tiempos de compilación más cortos y, ya sabes, tiempos de espera más cortos para los pases de optimización.

  • No repasaré cada optimización que hicimos en R8, pero quiero repasar una, solo para darte una idea de lo que hace posible el seguimiento.

  • Por lo tanto, R8, en esencia, es un transformador de código fuente a código fuente.

  • Por lo tanto, obtienes un montón de archivos JAR, los reduces y optimizas, y produces otro conjunto de archivos JAR que puedes dexificar.

  • Aquí, el AGP comienza leyendo los JAR, que no solo son tu app, sino también todas sus dependencias.

  • Por lo tanto, usa trabajadores de Gradle para comenzar a leer todos los JAR de entrada.

  • Tienes muchos archivos JAR que quieres leer.

  • Luego, una vez que termina de leer los JAR, R8 comienza a analizar el código y a compilar un modelo de lo que representa tu código y todos sus puntos de entrada, de modo que pueda comenzar a optimizar y reducir tu código.

  • Ahora, ese es el segmento, await, que representa cuando R8 está realmente listo para iniciar el proceso de optimización, ¿verdad?

  • Es posible que veas un espacio muerto en el medio y te preguntes por qué tenemos ese tiempo de CPU sin usar.

  • Esto se debe a que, con la API anterior, esperábamos a que todos los trabajadores leyeran todos sus archivos antes de analizar cualquier cosa.

  • Obviamente, podemos corregir esto, que es exactamente lo que hicimos.

  • Por lo tanto, en AGP 9, puedes ver que, en el momento en que terminamos de leer los JAR de entrada, comenzamos a procesarlos de inmediato.

  • Y no tuvimos que esperar a que cada trabajador terminara de leer sus entradas.

  • Esto redujo mucho el tiempo que esperamos para leer todas las entradas.

  • Esta es una optimización evidente que puedes ver con mucha claridad cuando observas un registro, pero no tanto cuando solo miras el código fuente para ver qué vale la pena optimizar.

  • Otra herramienta que usamos todos los días como ingenieros de AndroidX es Metalava.

  • Metalava es una herramienta que usamos para asegurarnos de que todas las APIs que enviamos tengan las garantías de compatibilidad de APIs que esperas y que las personas externas puedan usarlas por completo sin ningún problema que queramos evitar.

  • Esta es una herramienta que se ejecuta en cada API pública y restringida, por lo que cualquier tiempo que ahorremos aquí es, en realidad, tiempo que recuperamos como ingenieros de AndroidX.

  • Metalava también usó el seguimiento para mejorar los tiempos posteriores al envío en un asombroso 15%.

  • Y, si entramos en el seguimiento para explicar lo que sucedió, Metalava ejecuta dos tipos de verificaciones: la primera es donde verifica las APIs restringidas y la segunda es donde verifica las APIs públicas.

  • Pero tal vez notes un patrón.

  • Estamos realizando una gran cantidad de configuraciones para ejecutar esas verificaciones dos veces.

  • Entonces, lo que sucede es que se leen todas las entradas del programa, compilamos la PSI, que es la interfaz de estructura del programa, o, ya sabes, exactamente cuál es la forma de tu código sobre la que se puede razonar.

  • Luego, compilamos el AST antes de comenzar a aplicar todas las verificaciones de lint que queremos.

  • Y estamos haciendo este trabajo costoso dos veces.

  • ¿Cómo solucionamos este problema?

  • Solo lo hicimos una vez.

  • Por lo tanto, en lugar de realizar todo ese trabajo de configuración dos veces, lo hicimos una sola vez y reutilizamos esas entradas para las verificaciones restringidas y las públicas, lo que nos permitió ahorrar todo ese tiempo.

  • Sí.

  • Bien.

Prácticas recomendadas para el seguimiento y comparativas

  • Rahul Ravikumar

    Ahora que te di una idea de lo que hace posible Tracing 2.0, es importante saber que, si bien el seguimiento es importante, saber qué seguir también lo es.

  • No querrás esparcir seguimientos por todas partes.

  • Debes tener mucho cuidado con lo que eliges, y trataré de darte algunas pautas sobre lo que vale la pena rastrear.

  • Por lo tanto, lo primero que debes hacer es encontrar brechas inexplicables en tu registro.

  • Por lo tanto, cada vez que tus subprocesos estén en estado de ejecución o de ejecución posible, y veas brechas obvias en el registro del sistema, y no puedas razonar sobre ellas, y no sepas qué está haciendo tu app, podría valer la pena investigar.

  • En ese punto, podría tomar un seguimiento de método superpuesto con el seguimiento del sistema y ver qué vale la pena seguir, y podría agregar esas secciones de seguimiento pertinentes.

  • También debes priorizar los recorridos del usuario.

  • Por lo tanto, debes analizar el inicio de la app.

  • Supongamos que quieres analizar los recorridos del usuario principales de tu app y determinar si puedes explicarlos con un seguimiento.

  • Por lo tanto, cada vez que no puedas razonar sobre algo, vale la pena realizar un seguimiento.

  • También deberías observar las pilas de llamadas profundas o el uso de CPU sin explicación de un seguimiento y tratar de averiguar exactamente qué está sucediendo. Luego, deberías convertir esas partes de tu base de código en seguimientos.

  • Por lo tanto, puedes rastrearlos.

  • Y, luego, cada vez que tus subprocesos estén inactivos, debes asegurarte de poder comprender por qué lo están.

  • Deberían estar esperando una E/S, una transacción de Binder o algunos eventos de entrada externos que esperas que ocurran.

  • Pero si tienes, ya sabes, un sueño inexplicable, entonces vale la pena investigarlo porque, obviamente, podría haber algo oculto allí.

  • Y, luego, una vez que hayas, ya sabes, distribuido todas las secciones de seguimiento en ese código, deberías convertirlas en comparativas para poder hacer un seguimiento de los cambios en ellas.

  • Por lo tanto, a medida que evoluciona tu base de código, puedes ver si las métricas están cambiando, si tus paneles realmente reflejan grandes cambios, porque entonces sabes que algo importante cambió.

  • O puedes omitir todos los pasos anteriores y dejar que tu agente lo haga.

  • Toma estos lineamientos, conviértelos en una habilidad, asígnala a tu agente favorito y haz que la pruebe.

  • Sí.

Configuración, resumen y conclusión

  • Rahul Ravikumar

    Si te convencí de que el seguimiento es una tarea que vale la pena y que deberías realizar, aquí tienes la configuración del proyecto.

  • Es bastante simple.

  • Tracing es solo una biblioteca de Java tradicional, por lo que defines una dependencia en androidx.tracing:tracing-wire, que es el controlador de seguimiento compatible con Perfetto más el combo de seguimiento, y la versión estable actual es la 2.0.5.

  • Si eres una biblioteca de Java, es exactamente lo mismo. De nuevo, esta es una API completamente multiplataforma.

  • Queremos extenderlo a otras plataformas cuando sea conveniente.

  • Y sí, solo quería hacer un resumen rápido.

  • Hablé de todas estas funciones nuevas y geniales que agregamos.

  • Por lo tanto, la propagación de contexto es muy, muy potente.

  • Tenemos la capacidad de incorporar información personalizada, metadatos, información de la pila de llamadas y todo tipo de datos adicionales.

  • No he hablado de muchas, muchas cosas.

  • Obviamente, puedes cambiar el formato de seguimiento, puedes cambiar el receptor de seguimiento, puedes, ya sabes, llevar tus datos de seguimiento a OpenTelemetry.

  • Todas esas son cosas que puedes hacer.

  • El seguimiento es multiplataforma.

  • Esta es, de nuevo, una decisión muy, muy consciente, porque queremos que la mayor cantidad posible de personas puedan aprovechar esto.

  • Y todas las herramientas que enviamos, ya sea Perfetto, las comparativas de AndroidX, el nuevo analizador de rendimiento o el antiguo Generador de perfiles de Android, todas admiten Tracing 2.0.

  • Con esto, muchas gracias por escuchar.

  • Espero que disfrutes de la cumbre de desarrolladores. Gracias.