Caracterização de perfil baseada em gatilhos

No Android 16 (nível 36 da API) e versões mais recentes, o ProfilingManager permite capturar perfis automaticamente em resposta a acionadores do sistema, como erros de O app não está respondendo (ANR), falhas de falta de memória (OutOfMemoryError) ou eventos de inicialização do app. Como não é possível prever quando muitos desses eventos vão ocorrer em campo, não é prático iniciar uma gravação manualmente com antecedência. Com a criação de perfil baseada em gatilhos, o sistema gerencia a captura e entrega o perfil resultante ao app para que você possa:

  • Depurar problemas de estabilidade: diagnostique ANRs imprevisíveis e vazamentos de memória.
  • Otimizar as jornadas ideais do usuário (CUJs, na sigla em inglês): analise fluxos como a inicialização do app (TRIGGER_TYPE_COLD_START e TRIGGER_TYPE_APP_FULLY_DRAWN).
  • Entender o comportamento do usuário: inspecione eventos como saídas do app iniciadas pelo usuário.

Para ver a lista completa de gatilhos disponíveis, consulte ProfilingTrigger.

Tipos de gatilho

Os acionadores diferem na forma como o sistema captura o perfil quando o evento ocorre.

Acionadores que usam o trace em segundo plano

Muitos acionadores, como um ANR, são consequência de um problema anterior. Iniciar um rastreamento somente depois que o sistema detecta o ANR não mostra o trabalho de bloqueio que o causou. Para capturar a atividade que levou ao evento, os gatilhos a seguir salvam um snapshot do rastreamento em segundo plano ativo do sistema. Se nenhum rastreamento em segundo plano estiver em execução quando o evento ocorrer, nenhum perfil será coletado:

Acionadores que iniciam uma nova captura

Os gatilhos a seguir não dependem do trace em segundo plano. Em vez disso, o sistema inicia uma nova captura de perfil quando o evento ocorre:

Gatilhos em que o comportamento de captura varia de acordo com a anomalia

Para os seguintes acionadores de anomalias, o tipo de perfil capturado (salvar um snapshot do trace em segundo plano ativo ou iniciar uma nova captura) e o comportamento de limitação de taxa dependem da anomalia específica detectada pelo sistema:

Configurar um gatilho

O código a seguir demonstra como se registrar para o acionador TRIGGER_TYPE_APP_FULLY_DRAWN e definir um período de espera nele.

Kotlin

@RequiresApi(Build.VERSION_CODES.BAKLAVA)
fun recordWithTrigger() {
    val profilingManager =
        applicationContext.getSystemService(ProfilingManager::class.java) ?: return

    val triggers = ArrayList<ProfilingTrigger>()

    val triggerBuilder = ProfilingTrigger.Builder(ProfilingTrigger.TRIGGER_TYPE_APP_FULLY_DRAWN)
        .setRateLimitingPeriodHours(1)

    triggers.add(triggerBuilder.build())

    val callbackExecutor: Executor = Executors.newSingleThreadExecutor()

    val resultCallback = Consumer<ProfilingResult> { profilingResult ->
        if (profilingResult.errorCode == ProfilingResult.ERROR_NONE) {
            Log.d(
                "ProfileTest",
                "Received profiling result file=" + profilingResult.resultFilePath
            )
            setupProfileUploadWorker(profilingResult.resultFilePath)
        } else {
            Log.e(
                "ProfileTest",
                "Profiling failed errorcode=" + profilingResult.errorCode + " errormsg=" + profilingResult.errorMessage
            )
        }
    }

    profilingManager.registerForAllProfilingResults(callbackExecutor, resultCallback)
    profilingManager.addProfilingTriggers(triggers)

    Choreographer.getInstance().postFrameCallback { frameTimeNanos ->
        // This will cause the TRIGGER_TYPE_APP_FULLY_DRAWN to be emitted.
        reportFullyDrawn()
    }
}

Java

public void recordWithTrigger() {
  ProfilingManager profilingManager = getApplicationContext().getSystemService(
      ProfilingManager.class);
  List<ProfilingTrigger> triggers = new ArrayList<>();
  ProfilingTrigger.Builder triggerBuilder = new ProfilingTrigger.Builder(
      ProfilingTrigger.TRIGGER_TYPE_APP_FULLY_DRAWN);
  triggerBuilder.setRateLimitingPeriodHours(1);
  triggers.add(triggerBuilder.build());

  Executor callbackExecutor = 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());
          }
        }
      };
  profilingManager.registerForAllProfilingResults(callbackExecutor, resultCallback);
  profilingManager.addProfilingTriggers(triggers);

  Choreographer.getInstance().postFrameCallback((f) -> {
    // This will cause the TRIGGER_TYPE_APP_FULLY_DRAWN to be emitted.
    reportFullyDrawn();
  });
}

O código executa as seguintes etapas:

  1. Acessar o gerenciador: recupera o serviço ProfilingManager.
  2. Definir um gatilho: cria um ProfilingTrigger para TRIGGER_TYPE_APP_FULLY_DRAWN. Esse evento ocorre quando o app chama reportFullyDrawn() durante uma inicialização a frio iniciada por atividade para informar que concluiu a inicialização e está interativo.
  3. Definir um período de espera do acionador: aplica um período de espera de 1 hora a esse acionador específico usando setRateLimitingPeriodHours, além do limitador de taxa do sistema. Esse período de espera impede que o app registre mais de um perfil de inicialização por hora.
  4. Registrar listener: chama registerForAllProfilingResults para definir o callback que processa o resultado. Esse callback recebe o caminho do perfil salvo por getResultFilePath().
  5. Adicionar gatilhos: registra a lista de gatilhos com ProfilingManager usando addProfilingTriggers.
  6. Evento de disparo: publica um callback de frame Choreographer que chama reportFullyDrawn() quando o próximo frame é desenhado, o que emite o evento TRIGGER_TYPE_APP_FULLY_DRAWN para o sistema, acionando uma coleta de perfil, supondo que um rastro em segundo plano do sistema esteja em execução e que a cota do limitador de taxa esteja disponível. Para que esse acionador ocorra, verifique se o app chama reportFullyDrawn().

Ciclo de vida do gatilho

Os acionadores registrados persistem após as reinicializações do app e do sistema. Não é necessário registrar novamente os acionadores sempre que o app é iniciado ou após uma reinicialização do sistema. Depois que você registra os acionadores usando addProfilingTriggers, o sistema os mantém automaticamente para o app até que você os remova ou o app seja desinstalado.

Quando cancelar o registro de acionadores

Como os acionadores registrados persistem nas reinicializações do app e do sistema, pular addProfilingTriggers em inicializações subsequentes não interrompe os acionadores registrados em uma sessão anterior. Para interromper a captura de perfis de eventos específicos ou de todos os eventos, chame explicitamente removeProfilingTriggersByType ou clearProfilingTriggers.

Cancele o registro de acionadores nas seguintes situações:

  • Segmentar coortes ou reverter flags remotas: é possível segmentar a criação de perfis em coortes usando a Configuração remota ou flags de teste A/B. Sempre que um dispositivo sair da coorte de amostra ou uma flag for desativada, cancele o registro dos acionadores.
  • Finalizar uma campanha de depuração: depois de coletar perfis suficientes para diagnosticar um problema ou enviar uma correção, remova o gatilho. O cancelamento da inscrição de acionadores desnecessários preserva o armazenamento do dispositivo e a cota do limitador de taxa do app para outras solicitações de criação de perfil.

Ciclo de vida do callback

Ao contrário das solicitações de criação de perfil direta, os perfis baseados em acionadores só mostram resultados para um listener global registrado com registerForAllProfilingResults. No callback, chame ProfilingResult.getTriggerType para identificar qual acionador iniciou a captura e chame ProfilingResult.getResultFilePath para localizar o arquivo de perfil salvo. Para detalhes sobre nomes de arquivos de perfil de acionador, extração de arquivos com adb e upload de resultados em segundo plano, consulte Recuperar e fazer upload de perfis.

Embora os acionadores persistam nas reinicializações do app e do sistema, o callback de resultado não. Registre o callback em um Executor em segundo plano após a conclusão da inicialização do app (para não desacelerar a inicialização a frio) para receber perfis nos dois cenários a seguir:

  • Enquanto o app está em execução: para acionadores que não interrompem ou travam o app, o sistema invoca o callback no Executor fornecido quando o acionador ocorre e o perfil é salvo.
  • Após o encerramento do app: para acionadores que geralmente envolvem ou precedem o encerramento do app (como TRIGGER_TYPE_ANR, TRIGGER_TYPE_OOM ou TRIGGER_TYPE_KILL_RECENTS) ou se o app for interrompido antes da conclusão da captura, o sistema vai salvar o perfil em segundo plano e colocar o resultado em fila nas reinicializações do app e do sistema. O sistema retém os resultados não entregues na fila por até sete dias e os entrega na próxima vez que o app for executado e chamar registerForAllProfilingResults.

Como funciona o rastreamento em segundo plano

Para equilibrar a sobrecarga de criação de perfil com a captura de artefatos úteis, o sistema não executa traces em segundo plano continuamente. Em vez disso, ele faz amostragens periodicamente, iniciando traces em segundo plano em intervalos aleatórios.

Os rastreamentos em segundo plano acionados pelo sistema usam um buffer circular com um tamanho máximo definido pelo sistema. Quando o buffer é preenchido, os novos dados de trace substituem os mais antigos. Como mostrado na Figura 1, quando um acionador registrado é disparado enquanto um trace em segundo plano está ativo e a cota do limitador de taxa está disponível, o sistema salva o snapshot do buffer circular que contém a atividade mais recente que levou ao acionador.

Diagrama mostrando como os snapshots de rastreamento em segundo plano funcionam, com um buffer circular capturando dados antes de um evento de acionamento.
Figura 1. Como funcionam os snapshots de rastreamento em segundo plano.

Definir um período de espera específico para o acionador

Os acionadores de alta frequência podem consumir rapidamente a cota do limitador de taxa do app. Para evitar que um único tipo de gatilho esgote sua cota, use setRateLimitingPeriodHours para adicionar um período de espera mínimo por gatilho além do limitador de taxa do sistema.

Depurar gatilhos localmente

Como os traces em segundo plano são executados em intervalos aleatórios na produção, o teste de acionadores localmente exige que você force um trace em segundo plano contínuo e ignore o limitador de taxa no nível do sistema para seu pacote (qualquer período de espera por acionador definido com setRateLimitingPeriodHours ainda se aplica, e rate_limiter.disabled se aplica apenas a chamadas requestProfiling orientadas pelo app).

Para que os comandos adb forcem os traces em segundo plano, simulem acionadores na linha de comando e inspecionem o estado do serviço, consulte Forçar a ativação de traces em segundo plano para testes de acionadores em Comandos de depuração para criação de perfil local.