Recuperar e fazer upload de perfis

Seja para capturar um perfil orientado por app ou um perfil baseado em acionador, o ProfilingManager salva o arquivo concluído no dispositivo e envia um ProfilingResult para o callback registrado. Chame ProfilingResult.getResultFilePath para receber o caminho do perfil salvo:

Recuperar perfis de um dispositivo

Nesta seção, descrevemos onde o ProfilingManager armazena perfis no dispositivo, como funciona a limpeza automática de arquivos e como extrair arquivos localmente usando o adb.

Locais de arquivos e formatos de nomenclatura

O ProfilingManager salva perfis editados no diretório de armazenamento específico do app em /data/user/0/<app>/files/profiling/. O formato do nome do arquivo depende de como o perfil foi capturado:

  • Perfis orientados por apps:

    profile_<tag>_<datetime>_uid-<uid>.<profile-type-ext>
    

    Se nenhuma tag tiver sido fornecida ao solicitar o perfil, <tag>_ será omitido: profile_<datetime>_uid-<uid>.<profile-type-ext>.

  • Perfis baseados em gatilhos:

    profile_trigger-type-<trigger_type>_<datetime>_uid-<uid>.<profile-type-ext>
    

Em que:

  • <app> é o nome do pacote do seu app.
  • <tag> é composto pelos primeiros 20 caracteres alfanuméricos (e hifens) da tag fornecida pelo autor da chamada configurada com setTag ou transmitida para requestProfiling, convertida em minúsculas com todos os caracteres fora de a-z, 0-9 e - removidos.
  • <trigger_type> é o código de constante de número inteiro para o acionador de ProfilingTrigger. Sempre chame ProfilingResult.getTriggerType no código em vez de analisar o nome do arquivo.
  • <datetime> é a data e a hora em que o perfil foi registrado (yyyy-MM-dd-HH-mm-ss-SSS).
  • <uid> é o ID do usuário do Linux do seu app (incluído nas versões recentes do módulo Profiling).
  • <profile-type-ext> é a extensão de arquivo do tipo de perfil capturado:
    • .perfetto-trace para rastreamentos do sistema
    • .perfetto-java-heap-dump para heap dumps do Java
    • .perfetto-heap-profile para perfis de alocação heap
    • .perfetto-stack-sample para perfis de amostragem de pilha

Armazenamento e limpeza no dispositivo

Para liberar o armazenamento do dispositivo imediatamente, exclua cada arquivo de perfil no worker de upload logo após um upload bem-sucedido. Como a limpeza automática só é executada quando o ProfilingManager envia resultados ao app, os arquivos não enviados podem permanecer no disco se o app parar de registrar callbacks após o término de uma campanha. Como alternativa nas versões recentes do módulo Profiling, o ProfilingManager também exclui automaticamente os arquivos entregues no diretório de armazenamento específico do app que têm mais de cinco dias quando novos resultados são entregues.

Fazer upload de perfis na produção com o WorkManager

Como os arquivos de perfil podem ter vários megabytes, programe uploads de produção em segundo plano quando o dispositivo estiver em uma rede ilimitada (Wi-Fi), carregando e ocioso.

Antes de fazer o upload, compacte o arquivo de perfil (por exemplo, usando GZIPOutputStream ou ZipOutputStream). Os perfis de protobuf do Perfetto e os heap dumps do Java são compactados significativamente, reduzindo a largura de banda da rede e os custos de armazenamento de back-end.

Adicionar dependências do WorkManager

O ProfilingManager é independente de nuvem, então é possível programar o upload de perfis para qualquer back-end usando WorkManager. Adicione a biblioteca WorkManager ao arquivo build.gradle.kts:

Kotlin

   dependencies {
       implementation("androidx.work:work-runtime:2.12.0")
   }
   

Groovy

   dependencies {
       implementation 'androidx.work:work-runtime:2.12.0'
   }
   

Programar um job de upload

Quando o app receber um ProfilingResult, programe o job de upload. O exemplo a seguir omite a solicitação de criação de perfil inicial. Para ver um exemplo de solicitação completo, consulte Gravar um rastreamento do sistema.

Kotlin

class TraceUploadWorker(context: Context, workerParams: WorkerParameters) : Worker(context, workerParams) {
    override fun doWork(): Result {
        // Perform your uploading work here
        Log.d("ProfileTest", "Uploading trace: " + inputData.getString("PROFILE_PATH"))

        return Result.success()
    }
}

fun setupProfileUploadWorker(profileFilepath: String?) {
    val workMgr = WorkManager.getInstance(applicationContext)
    val workRequestBuilder = OneTimeWorkRequest.Builder(TraceUploadWorker::class)

    val constraints = Constraints.Builder()
        .setRequiredNetworkType(NetworkType.UNMETERED)
        .setRequiresDeviceIdle(true)
        .setRequiresCharging(true)
        .build()
    workRequestBuilder.setConstraints(constraints)

    val inputDataBuilder = Data.Builder()
    inputDataBuilder.putString("PROFILE_PATH", profileFilepath)
    workRequestBuilder.setInputData(inputDataBuilder.build())

    workMgr.enqueue(workRequestBuilder.build())
}

Java

public static class TraceUploadWorker extends Worker {

  public TraceUploadWorker(
      @androidx.annotation.NonNull Context context,
      @androidx.annotation.NonNull WorkerParameters workerParams) {
    super(context, workerParams);
  }

  @androidx.annotation.NonNull
  @Override
  public Result doWork() {
    // Perform your uploading work here
    Log.d("ProfileTest", "Uploading trace: " + getInputData().getString("PROFILE_PATH"));

    return Result.success();
  }
}

public void setupProfileUploadWorker(String profileFilepath) {
  WorkManager workMgr = WorkManager.getInstance(getApplicationContext());
  OneTimeWorkRequest.Builder workRequestBuilder = new OneTimeWorkRequest.Builder(
      TraceUploadWorker.class);

  Constraints constraints = new Constraints.Builder()
      .setRequiredNetworkType(NetworkType.UNMETERED)
      .setRequiresDeviceIdle(true)
      .setRequiresCharging(true)
      .build();
  workRequestBuilder.setConstraints(constraints);

  Data.Builder inputDataBuilder = new Data.Builder();
  inputDataBuilder.putString("PROFILE_PATH", profileFilepath);
  workRequestBuilder.setInputData(inputDataBuilder.build());

  workMgr.enqueue(workRequestBuilder.build());
}

Tutorial do código

O exemplo anterior executa as seguintes etapas:

  • Defina o worker: crie uma classe TraceUploadWorker que estenda Worker. Implemente doWork para verificar se o arquivo de perfil ainda existe, compacte e faça upload dele usando o SDK de back-end ou o cliente HTTP e exclua o arquivo local após o upload.
  • Programar o trabalho:
    • Crie um OneTimeWorkRequest para o worker.
    • Definir restrições: use setRequiredNetworkType(NetworkType.UNMETERED), setRequiresDeviceIdle(true) e setRequiresCharging(true) para que o upload seja executado apenas quando o dispositivo estiver conectado ao Wi-Fi, carregando e ocioso.
    • Transmitir dados: use setInputData para transmitir o caminho do perfil de getResultFilePath para o worker.
    • Enqueue: envie a solicitação para WorkManager chamando WorkManager.enqueue.

Próximas etapas

Depois de recuperar ou fazer upload de perfis, visualize-os na interface do Perfetto, consulte perfis individuais com o PerfettoSQL, analise traces em massa ou analise perfis com IA. Para orientações sobre como configurar um pipeline de análise escalonável do lado do servidor, consulte Como implantar o Bigtrace no Kubernetes.