Creación de perfiles basada en la app

En esta página, se muestra cómo registrar un seguimiento del sistema con la API de ProfilingManager.

ProfilingManager también puede registrar otros tipos de perfiles. Este proceso es similar al registro de un seguimiento del sistema, pero cada tipo usa un compilador diferente. Los perfiles compatibles y sus compiladores son los siguientes:

  • Seguimientos del sistema: Se registran con SystemTraceRequestBuilder, que son útiles para el análisis de latencia y la depuración general del rendimiento.

  • Volcados de montón: Se registran con JavaHeapDumpRequestBuilder, que son útiles para la detección y la optimización de fugas de memoria.

  • Perfiles de montón: Se registran con HeapProfileRequestBuilder, que son útiles para la optimización de la memoria.

  • Perfiles de pila de llamadas: Se registran con StackSamplingRequestBuilder, que son útiles para comprender la ejecución del código y el análisis de latencia.

Cómo agregar dependencias

Para obtener la mejor experiencia con la API de ProfilingManager, agrega las siguientes bibliotecas de Jetpack a tu archivo build.gradle.kts.

Kotlin

   dependencies {
       implementation("androidx.tracing:tracing-ktx:2.0.1")
       implementation("androidx.core:core:1.19.0")
   }
   

Groovy

   dependencies {
       implementation 'androidx.tracing:tracing:2.0.1'
       implementation 'androidx.core:core:1.19.0'
   }
   

Cómo registrar un seguimiento del sistema

Después de agregar las dependencias necesarias, usa el siguiente código para registrar un seguimiento del sistema. En este ejemplo, se muestra cómo iniciar una sesión de generación de perfiles desde un elemento componible mientras se administran de forma segura las operaciones pesadas fuera del subproceso principal.

Kotlin

@RequiresApi(Build.VERSION_CODES.VANILLA_ICE_CREAM)
@Composable
fun ProfiledScreen(modifier: Modifier = Modifier) {
    // Use the application context: requestProfiling resolves the ProfilingManager
    // system service from it, so there's no reason to hand it a short-lived Activity.
    val appContext = LocalContext.current.applicationContext
    val scope = rememberCoroutineScope()

    Button(
        onClick = {
            // Run the orchestration off the main thread. Profiling a heavy operation
            // on the UI thread would freeze the UI (ANR) and distort the very metrics
            // you're trying to capture.
            //
            // Note: this scope is tied to composition. If the user leaves this screen
            // mid-session, the coroutine is cancelled and stopSignal.cancel() might not
            // run, but setDurationMs() acts as a safety net and ends the trace.
            scope.launch(Dispatchers.Default) {
                val callbackExecutor = Dispatchers.IO.asExecutor()
                val resultCallback = Consumer<ProfilingResult> { profilingResult ->
                    if (profilingResult.errorCode == ProfilingResult.ERROR_NONE) {
                        Log.d("ProfileTest", "Result file: ${profilingResult.resultFilePath}")
                    } else {
                        // errorMessage explains the failure (e.g., rate limiting); keep it.
                        Log.e(
                            "ProfileTest",
                            "Profiling failed errorCode=${profilingResult.errorCode} " +
                                "errorMessage=${profilingResult.errorMessage}"
                        )
                    }
                }

                val stopSignal = CancellationSignal()
                val requestBuilder = SystemTraceRequestBuilder().apply {
                    setCancellationSignal(stopSignal)
                    setTag("FOO") // Caller-supplied tag for identification.
                    setDurationMs(60000) // Hard cap: ends the session if cancel() never fires.
                    setBufferFillPolicy(BufferFillPolicy.RING_BUFFER)
                    setBufferSizeKb(32768)
                }

                // 1. Start the session. This is asynchronous system IPC. The tracing
                //    engine takes a moment to start and allocate buffers.
                requestProfiling(appContext, requestBuilder.build(), callbackExecutor, resultCallback)

                // 2. The API exposes no "profiling started" signal, so pad with a short,
                //    best-effort delay before running the code you care about. This is
                //    approximate. Increase it on slower or heavily loaded devices.
                delay(STARTUP_PADDING_MS)

                // 3. The session is already recording every thread in your app. This slice
                //    doesn't scope what's captured. It just labels this region of the
                //    timeline so heavyOperation() is easier to find. trace { } closes the
                //    section even if the block throws.

                trace("MyApp:HeavyOperation") {
                    heavyOperation()
                }

                // 4. Stop recording. Until this fires or the setDurationMs() cap is
                //    reached (whichever comes first), the session keeps capturing app-wide
                //    activity.

                stopSignal.cancel()
            }
        }
    ) {
        Text("Run & Profile Heavy Operation")
    }
}

// Best-effort wait for the system trace engine to initialize before profiling.
// There is no deterministic start callback; tune this for your target devices.
private const val STARTUP_PADDING_MS = 100L

fun heavyOperation() {
    // Background computations to profile.
}

Java

void heavyOperation() {
  // Computations you want to profile
}

void sampleRecordSystemTrace() {
  Executor mainExecutor = Executors.newSingleThreadExecutor();
  Consumer<ProfilingResult> resultCallback =
      new Consumer<ProfilingResult>() {
        @Override
        public void accept(ProfilingResult profilingResult) {
          if (profilingResult.getErrorCode() == ProfilingResult.ERROR_NONE) {
            Log.d(
                "ProfileTest",
                "Received profiling result file=" + profilingResult.getResultFilePath());
            setupProfileUploadWorker(profilingResult.getResultFilePath());
          } else {
            Log.e(
                "ProfileTest",
                "Profiling failed errorcode="

                    + profilingResult.getErrorCode()
                    + " errormsg="
                    + profilingResult.getErrorMessage());
          }
        }
      };
  CancellationSignal stopSignal = new CancellationSignal();

  SystemTraceRequestBuilder requestBuilder = new SystemTraceRequestBuilder();
  requestBuilder.setCancellationSignal(stopSignal);
  requestBuilder.setTag("FOO");
  requestBuilder.setDurationMs(60000);
  requestBuilder.setBufferFillPolicy(BufferFillPolicy.RING_BUFFER);
  requestBuilder.setBufferSizeKb(32768);
  Profiling.requestProfiling(getApplicationContext(), requestBuilder.build(), mainExecutor,
      resultCallback);

  // Wait some time for profiling to start.

  Trace.beginSection("MyApp:HeavyOperation");
  heavyOperation();
  Trace.endSection();

  // Once the interesting code section is profiled, stop profile
  stopSignal.cancel();
}

El código de muestra configura y administra la sesión de generación de perfiles siguiendo estos pasos:

  1. Configura el ejecutor. Crea un Executor para definir el subproceso que recibirá los resultados de la generación de perfiles. La generación de perfiles se realiza en segundo plano. El uso de un ejecutor de subprocesos que no sea de la IU ayuda a evitar errores de aplicación no responde (ANR) si agregas más procesamiento a la devolución de llamada más adelante.

  2. Controla los resultados de la generación de perfiles. Crea un objeto Consumer<ProfilingResult>. El sistema usa este objeto para enviar los resultados de la generación de perfiles de ProfilingManager a tu app.

  3. Compila la solicitud de generación de perfiles. Crea un SystemTraceRequestBuilder para configurar tu sesión de generación de perfiles. Este compilador te permite personalizar la configuración de seguimiento de ProfilingManager. La personalización del compilador es opcional; si no lo haces, el sistema usa la configuración predeterminada.

    • Define una etiqueta. Usa setTag() para agregar una etiqueta al nombre del seguimiento. Esta etiqueta te ayuda a identificar el seguimiento.
    • Opcional: Establece la duración. Usa setDurationMs() para especificar cuánto tiempo se generará el perfil en milisegundos. Por ejemplo, 60000 establece un seguimiento de 60 segundos. El seguimiento finaliza automáticamente después de la duración especificada si CancellationSignal no se activa antes.
    • Elige una política de búfer. Usa setBufferFillPolicy() para definir cómo se almacenan los datos de seguimiento. BufferFillPolicy.RING_BUFFER significa que, cuando el búfer está lleno, los datos nuevos reemplazan a los más antiguos, lo que mantiene un registro continuo de la actividad reciente.
    • Establece un tamaño de búfer. Usa setBufferSizeKb() para especificar un tamaño de búfer para el seguimiento que puedes usar para controlar el tamaño del archivo de seguimiento de salida.
  4. Opcional: Administra el ciclo de vida de la sesión. Crea un CancellationSignal. Este objeto te permite detener la sesión de generación de perfiles cuando quieras, lo que te brinda un control preciso sobre su duración.

  5. Inicia y recibe resultados. Cuando llamas a requestProfiling(), ProfilingManager inicia una sesión de generación de perfiles en segundo plano. Una vez que se completa la generación de perfiles, envía el ProfilingResult a tu método resultCallback#accept. Si la generación de perfiles finaliza correctamente, the ProfilingResult proporciona la ruta de acceso en la que se guardó el seguimiento en tu dispositivo a través de ProfilingResult#getResultFilePath. Puedes obtener este archivo de forma programática o, para la generación de perfiles local, ejecutando adb pull <trace_path> desde tu computadora.

  6. Agrega puntos de seguimiento personalizados. Puedes agregar puntos de seguimiento personalizados en el código de tu app. En el ejemplo de código anterior, el trace("MyApp:HeavyOperation") { ... } bloque crea un segmento personalizado en el perfil generado.