Crea comparativas de casos de uso con Jetpack Macrobenchmark

Macrobenchmark te permite escribir pruebas de rendimiento sobre el inicio y el tiempo de ejecución directamente con tu app en dispositivos que ejecutan Android M (nivel de API 23) o versiones posteriores.

Se recomienda usar Macrobenchmark con la versión más reciente de Android Studio (2021.1.1 o posterior), ya que en esa versión del IDE se incluyen funciones nuevas que están integradas con Macrobenchmark. Los usuarios que tienen versiones anteriores de Android Studio pueden usar la instrucción adicional que aparece más tarde en este tema para trabajar con archivos de registro.

Las pruebas de comparativas se proporcionan mediante la API de la regla MacrobenchmarkRule de JUnit4 en la biblioteca de Macrobenchmark:

Kotlin

    @get:Rule
    val benchmarkRule = MacrobenchmarkRule()

    @Test
    fun startup() = benchmarkRule.measureRepeated(
        packageName = "mypackage.myapp",
        metrics = listOf(StartupTimingMetric()),
        iterations = 5,
        startupMode = StartupMode.COLD
    ) { // this = MacrobenchmarkScope
        pressHome()
        val intent = Intent()
        intent.setPackage("mypackage.myapp")
        intent.setAction("mypackage.myapp.myaction")
        startActivityAndWait(intent)
    }
  

Java

    @Rule
    MacrobenchmarkRule benchmarkRule = MacrobenchmarkRule()

    @Test
    void startup() = benchmarkRule.measureRepeated(
        "mypackage.myapp", // packageName
        listOf(StartupTimingMetric()), // metrics
        5, // iterations
        StartupMode.COLD // startupMode
    ) { scope ->
        scope.pressHome()
        Intent intent = Intent()
        intent.setPackage("mypackage.myapp")
        intent.setAction("mypackage.myapp.myaction")
        scope.startActivityAndWait(intent)
    }
  

Las métricas se muestran directamente en Android Studio y como resultados del uso de CI en un archivo JSON.

Resultados de Studio de ejemplo

Configuración de módulo

Las macrocomparativas requieren un módulo com.android.test independiente en el código de tu app en el que se ejecuten las pruebas que miden la app.

Bumblebee

En Android Studio Bumblebee, hay una plantilla disponible para simplificar la configuración del módulo de macrocomparativas.

Cómo agregar un módulo nuevo

La plantilla del módulo de comparativas crea automáticamente un módulo en el proyecto para medir la app que compila un módulo de app, incluida una comparativa de inicio de muestra.

Si deseas usar la plantilla del módulo a fin de crear un módulo nuevo, haz lo siguiente:

  1. Haz clic con el botón derecho en tu proyecto o módulo desde el panel Project en Android Studio y haz clic en New > Module.

  2. Selecciona Benchmark Module.

    Plantilla del módulo de comparativas

  3. Puedes personalizar la aplicación de destino (la app de la que obtendrás comparativas), así como el nombre del paquete y del módulo para el módulo nuevo de macrocomparativas.

  4. Haz clic en Finish.

Arctic Fox

En Arctic Fox, crearás un módulo de biblioteca y lo convertirás en un módulo de prueba.

Cómo agregar un módulo nuevo

Agrega un módulo nuevo a tu proyecto que contenga tus pruebas de Macrobenchmark.

  1. Haz clic con el botón derecho en tu proyecto o módulo desde el panel Project en Android Studio y haz clic en New > Module.
  2. Selecciona Android Library en el panel Templates.
  3. Como nombre de módulo, escribe macrobenchmark.
  4. Configura Minimum SDK en API 23: Android M.
  5. Haz clic en Finish.

Configuración de un nuevo módulo de biblioteca

Cómo modificar el archivo de Gradle

Personaliza el build.gradle del módulo de Macrobenchmark como se indica a continuación:

  1. Cambia el complemento de com.android.library a com.android.test.
  2. Agrega las propiedades adicionales y obligatorias del módulo de prueba en el bloque android {}:
  3.    targetProjectPath = ":app" // Note that your module name may be different
    
       // Enable the benchmark to run separately from the app process
       experimentalProperties["android.experimental.self-instrumenting"] = true
       buildTypes {
           // Declare a build type (release) to match the target app's build type
           release {
               debuggable = true
           }
       }
  4. Cambia todas las dependencias llamadas testImplementation o androidTestImplementation a implementation.
  5. Agrega una dependencia en la biblioteca de Macrobenchmark:
    • implementation 'androidx.benchmark:benchmark-macro-junit4:1.1.0-alpha13'
  6. Después del bloque android {}, pero antes del bloque dependencies {}, agrega lo siguiente:
  7.    androidComponents {
          beforeVariants(selector().all()) {
              // Enable only the benchmark buildType, since we only want to measure
              // release-like build performance (should match app buildType)
              enabled = buildType == 'benchmark'
          }
       }

Cómo simplificar la estructura del directorio

En un módulo com.android.test, hay un solo directorio del código fuente para todas las pruebas. Borra los otros directorios del código fuente, incluidos src/test y src/androidTest, ya que no se usan.

Consulta el módulo de Macrobenchmark de muestra como referencia.

Cómo crear una macrocomparativa

Define una clase de prueba nueva en ese módulo completando el nombre de paquete de la app:

@RunWith(AndroidJUnit4::class)
class SampleStartupBenchmark {
    @get:Rule
    val benchmarkRule = MacrobenchmarkRule()

    @Test
    fun startup() = benchmarkRule.measureRepeated(
        packageName = "mypackage.myapp",
        metrics = listOf(StartupTimingMetric()),
        iterations = 5,
        startupMode = StartupMode.COLD
    ) { // this = MacrobenchmarkScope
        pressHome()
        val intent = Intent()
        intent.setPackage("mypackage.myapp")
        intent.setAction("mypackage.myapp.myaction")
        startActivityAndWait(intent)
    }
}
   

Cómo configurar la app

A fin de obtener comparativas de una app (denominada el objetivo de la macrocomparativa), se debe poder crear un perfil de ella para que se pueda leer información de seguimiento detallada. Puedes habilitar esta función en la etiqueta <application> del AndroidManifest.xml de la app:

<application ... >
    <!-- Profileable to enable Macrobenchmark profiling -->
    <!-- Suppress AndroidElementNotAllowed -->
    <profileable android:shell="true"/>
    ...
</application>

Configura la app de la que obtendrás comparativas lo más parecido a la experiencia del usuario posible. Además, configúrala como no depurable y con la reducción activada en lo posible, lo que mejorará el rendimiento. Para ello, generalmente, debes crear una copia de benchmark de la variante de release, que tendrá el mismo rendimiento, pero se firma de manera local con claves debug:

buildTypes {
    benchmark {
        // duplicate any release build type settings for measurement accuracy,
        // such as "minifyEnabled" and "proguardFiles" in this block

        debuggable false
        signingConfig signingConfigs.debug
    }
}

Realiza una sincronización de Gradle, abre el panel Build Variants en el lado izquierdo y selecciona la variante benchmark de la app y del módulo de Macrobenchmark. De esta manera, te aseguras de que con la ejecución de las comparativas se compile y se pruebe la variante correcta de tu app.

Selecciona una variante de comparativas

Ejecutar Macrobenchmark en actividades internas requiere un paso adicional. Para obtener comparativas de una actividad interna exported=false, pasa un setupBlock a MacrobenchmarkRule.measureRepeated() a fin de navegar al código sobre el que quieres crear la comparativa y usa el measureBlock a fin de invocar el inicio de la actividad o la acción de desplazamiento que quieres medir.

Cómo personalizar macrocomparativas

CompilationMode

Las macrocomparativas pueden especificar un CompilationMode, que define cuánto de la app se debe compilar previamente desde el código de bytes DEX (el formato de código de bytes dentro de un APK) al código máquina (similar a C++ ya compilado).

De forma predeterminada, las macrocomparativas se ejecutan con CompilationMode.DEFAULT, que, en Android Nougat (API 24) y versiones posteriores, instala un perfil de Baseline (si está disponible) y, en Android Marshmallow (API 23) y versiones anteriores, compila por completo el APK (que es el comportamiento predeterminado del sistema).

Puedes instalar un perfil de Baseline si la aplicación de destino contiene este tipo de perfil y la biblioteca de ProfileInstaller.

En Android Nougat (API 24) y versiones posteriores, puedes personalizar el CompilationMode para que afecte la cantidad de compilación previa en el dispositivo, a fin de imitar diferentes niveles de compilación anticipada (AOT) o almacenamiento en caché de JIT. Consulta CompilationMode.Full, CompilationMode.Partial y CompilationMode.None.

Esta funcionalidad se basa en comandos de compilación de ART. Para garantizar que no haya interferencias entre las comparativas, cada una borrará los datos de perfil antes de comenzar.

Inicio

A fin de iniciar una actividad, puedes pasar un modo de inicio predefinido (que puede ser COLD, WARM o HOT) a la función measureRepeated(). Este parámetro cambia la manera en la que se inicia la actividad y el estado del proceso al comienzo de la actividad.

Si quieres obtener más información sobre los tipos de inicio, consulta la documentación de inicio de Android Vitals.

Desplazamiento y animación

A diferencia de la mayoría de las pruebas de IU de Android, las de Macrobenchmark se ejecutan en un proceso independiente de la app. Esto es necesario para realizar acciones como finalizar el proceso de la app y compilarlo con comandos shell.

Puedes controlar tu app con la biblioteca de UI Automator o algún otro mecanismo que pueda controlar la aplicación objetivo del proceso de prueba. Los enfoques como Espresso o ActivityScenario no funcionarán porque pretenden ejecutarse en un proceso compartido con la app.

En el siguiente ejemplo, se encuentra una RecyclerView que usa su ID de recurso y se desplaza hacia abajo varias veces:

@Test
fun measureScroll() {
    benchmarkRule.measureRepeated(
        packageName = "mypackage.myapp",
        metrics = listOf(FrameTimingMetric()),
        iterations = 5,
        setupBlock = {
            // before starting to measure, navigate to the UI to be measured
            val intent = Intent()
            intent.action = ACTION
            startActivityAndWait(intent)
        }
    ) {
        val recycler = device.findObject(By.res("mypackage.myapp", "recycler_id"))
        // Set gesture margin to avoid triggering gesture nav
        // with input events from automation.
        recycler.setGestureMargin(device.displayWidth / 5)

        // Scroll down several times
        for (i in 1..10) {
            recycler.scroll(Direction.DOWN, 2f)
            device.waitForIdle()
        }
    }
}

Como la prueba especifica una FrameTimingMetric, se registra e informa el tiempo de los fotogramas como un resumen de nivel superior de la distribución de la latencia de fotogramas: percentil 50, 90, 95 y 99.

No es necesario que tus comparativas se desplacen por la IU; en su lugar, pueden ejecutar una animación, por ejemplo. Tampoco es necesario que usen específicamente UI Automator. Siempre y cuando el sistema de vistas produzca los fotogramas (incluidos los que genera Compose), se recopilarán métricas de rendimiento. Ten en cuenta que los mecanismos del proceso, como Espresso, no funcionarán, dado que la app se debe controlar desde el proceso de la aplicación de prueba.

Cómo ejecutar macrocomparativas

Ejecuta la prueba desde Android Studio para medir el rendimiento de la app en tu dispositivo. Ten en cuenta que debes ejecutar la prueba en un dispositivo físico, no en un emulador, ya que estos no producen números de rendimiento representativos de la experiencia del usuario final.

Consulta la sección Comparativas en CI para obtener información sobre la manera en que debes ejecutar y supervisar comparativas en la integración continua.

También puedes ejecutar todas las comparativas desde la línea de comandos mediante connectedCheck, como se muestra a continuación:

$ ./gradlew :macrobenchmark:connectedCheck

Errores de configuración

Si la app está mal configurada (es depurable o no permite que se generen perfiles), Macrobenchmark arrojará un error, en lugar de informar una medición incorrecta o incompleta. Puedes suprimir esos errores mediante el argumento androidx.benchmark.suppressErrors.

Además, se arrojan errores cuando se intenta medir un emulador o si el dispositivo tiene poca batería, dado que se podría comprometer la disponibilidad principal y la velocidad del reloj.

Cómo inspeccionar un registro

Cada iteración medida captura un registro del sistema diferente. Para abrir estos registros de resultado, haz clic en uno de los vínculos que se incluyen en el panel Test Results, como se muestra en la imagen de la sección Jetpack Macrobenchmark de este tema. Cuando se carga el registro, Android Studio te solicita que selecciones el proceso a analizar. La selección se propaga previamente con el proceso de la app de destino, como se muestra a continuación:

Selección del proceso de registro de Studio

Una vez cargado el archivo de registro, Studio muestra los resultados en la herramienta del Generador de perfiles de CPU:

Registro de Studio

Cómo acceder manualmente a los archivos de registro

Si utilizas una versión posterior de Android Studio (anterior a 2020.3.1) o usas la herramienta Perfetto para analizar un archivo de registro, necesitarás pasos adicionales.

Primero, extrae el archivo de registro del dispositivo.

# The following command pulls all files ending in .perfetto-trace from the directory
# hierarchy starting at the root /storage/emulated/0/Android.
$ adb shell find /storage/emulated/0/Android/ -name "*.perfetto-trace" \
    | tr -d '\r' | xargs -n1 adb pull

Ten en cuenta que la ruta de acceso del archivo de salida puede ser diferente si la personalizas con el argumento additionalTestOutputDir. Puedes buscar registros de rutas de acceso en Logcat para consultar dónde están escritos. Por ejemplo:

I PerfettoCapture: Writing to /storage/emulated/0/Android/data/androidx.benchmark.integration.macrobenchmark.test/cache/TrivialStartupBenchmark_startup[mode=COLD]_iter002.perfetto-trace.

Si, en su lugar, invocas las pruebas mediante la línea de comandos de Gradle (como ./gradlew macrobenchmark:connectedCheck), puedes copiar los archivos del resultado de la prueba en un directorio de resultados de pruebas, en tu sistema host. Para ello, agrega la siguiente línea al archivo gradle.properties del proyecto:

android.enableAdditionalTestOutput=true

Los archivos de resultado de las ejecuciones de prueba se muestran en el directorio de compilación de tu proyecto de la siguiente manera:

build/outputs/connected_android_test_additional_output/debugAndroidTest/connected/<device-name>/TrivialStartupBenchmark_startup[mode=COLD]_iter002.perfetto-trace

Cuando tengas el archivo de registro en tu sistema host, puedes abrirlo en Android Studio con las opciones File > Open del menú. Esta opción muestra la vista de la herramienta del generador de perfiles que se proporcionó en la sección anterior.

Como alternativa, puedes usar la herramienta Perfetto, que te permite inspeccionar todos los procesos que se realizan en el dispositivo durante el registro. En cambio, el Generador de perfiles de CPU de Android Studio limita la inspección a un único proceso.

Mejora los datos de registro con eventos personalizados

Te puede resultar útil instrumentar la aplicación con eventos de registro personalizados, que se muestran con el resto del informe de seguimiento y pueden ayudar a identificar problemas específicos de tu app. Si quieres obtener más información sobre la creación de eventos de registro personalizados, consulta la guía Cómo definir eventos personalizados.

Comparativas en CI

Es habitual ejecutar pruebas en la CI sin Gradle, o bien de forma local si usas un sistema de compilación diferente. En esta sección, se explica cómo configurar Macrobenchmark para el uso de CI durante el tiempo de ejecución.

Archivos de resultado: JSON y registros

Macrobenchmark muestra como resultado un archivo JSON y varios archivos de registro: uno por iteración medida de cada bucle MacrobenchmarkRule.measureRepeated.

Para definir el lugar en el que se escriben estos archivos, pasa el siguiente argumento de instrumentación durante el tiempo de ejecución:

-e additionalTestOutputDir "device_path_you_can_write_to"

Ten en cuenta que para simplificar el proceso, puedes especificar una ruta de acceso en /sdcard/, pero debes inhabilitar el almacenamiento específico mediante la configuración de requestLegacyExternalStorage en true, en tu módulo de Macrobenchmark:

<manifest ... >
  <application android:requestLegacyExternalStorage="true" ... >
    ...
  </application>
</manifest>

También puedes pasar un argumento de instrumentación para omitir el almacenamiento específico en la prueba, como se muestra a continuación:

-e no-isolated-storage 1

Archivo JSON de muestra

A continuación, se muestra un ejemplo de resultado JSON para una única comparativa de inicio:

{
    "context": {
        "build": {
            "device": "walleye",
            "fingerprint": "google/walleye/walleye:10/QQ3A.200805.001/6578210:userdebug/dev-keys",
            "model": "Pixel 2",
            "version": {
                "sdk": 29
            }
        },
        "cpuCoreCount": 8,
        "cpuLocked": false,
        "cpuMaxFreqHz": 2457600000,
        "memTotalBytes": 3834605568,
        "sustainedPerformanceModeEnabled": false
    },
    "benchmarks": [
        {
            "name": "startup",
            "params": {},
            "className": "androidx.benchmark.integration.macrobenchmark.SampleStartupBenchmark",
            "totalRunTimeNs": 77969052767,
            "metrics": {
                "startupMs": {
                    "minimum": 228,
                    "maximum": 283,
                    "median": 242,
                    "runs": [
                        238,
                        283,
                        256,
                        228,
                        242
                    ]
                }
            },
            "warmupIterations": 3,
            "repeatIterations": 5,
            "thermalThrottleSleepSeconds": 0
        }
    ]
}

Recursos adicionales

En GitHub, encontrarás un proyecto de ejemplo en el repositorio de Android/muestras de rendimiento.

Si quieres obtener orientación para detectar regresiones de rendimiento, consulta Cómo resolver regresiones con comparativas en CI.

Comentarios

Si deseas informar errores o enviar solicitudes de funciones de Jetpack Macrobenchmark, consulta la Herramienta de seguimiento de errores pública.