Как получить и загрузить профили

Независимо от того, какой профиль вы создаете, управляемый приложением или на основе триггера, ProfilingManager сохраняет готовый файл на устройстве и отправляет ProfilingResult на зарегистрированный обратный вызов. Вызовите ProfilingResult.getResultFilePath, чтобы получить путь к сохраненному профилю:

Как получить профили с устройства

В этом разделе описано, где ProfilingManager хранит профили на устройстве, как работает автоматическая очистка файлов и как извлекать файлы локально с помощью adb.

Расположение файлов и форматы названий

ProfilingManager сохраняет отредактированные профили в каталоге хранилища приложения по пути /data/user/0/<app>/files/profiling/. Формат названия файла зависит от того, как был получен профиль:

  • Профили, управляемые приложением.

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

    Если при запросе профиля тег не был указан, <tag>_ опускается: profile_<datetime>_uid-<uid>.<profile-type-ext>.

  • Профили на основе триггеров

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

Место:

  • <app> – название пакета приложения.
  • <tag> – это первые 20 букв и цифр (и дефисов) тега, предоставленного вызывающим абонентом и настроенного с помощью setTag или переданного в requestProfiling, преобразованные в нижний регистр. При этом все символы, кроме a-z, 0-9 и -, удаляются.
  • <trigger_type> – целочисленный код константы для триггера из ProfilingTrigger. Всегда вызывайте в коде ProfilingResult.getTriggerType, а не анализируйте название файла.
  • <datetime> – дата и время записи профиля (yyyy-MM-dd-HH-mm-ss-SSS).
  • <uid> – идентификатор пользователя Linux в приложении (включен в последние версии модуля Profiling).
  • <profile-type-ext> – расширение имени файла для типа профиля:
    • .perfetto-trace для трассировки системы
    • .perfetto-java-heap-dump для дампов кучи Java
    • .perfetto-heap-profile для профилей кучи
    • .perfetto-stack-sample для профилей выборки стека

Хранилище устройства и очистка

Чтобы быстро освободить хранилище устройства, удаляйте каждый файл профиля в рабочем процессе загрузки сразу после успешной загрузки. Автоматическая очистка запускается, только когда ProfilingManager передает результаты вашему приложению. Если приложение перестанет регистрировать обратные вызовы после завершения кампании, незагруженные файлы могут остаться на диске. В качестве резервного варианта в последних версиях модуля Profiling библиотека ProfilingManager также автоматически удаляет доставленные файлы в каталоге хранилища, предназначенном для приложения, если они старше пяти дней, когда доставляются новые результаты.

Загрузка профилей в рабочую версию с помощью WorkManager

Поскольку размер файлов профиля может составлять несколько мегабайт, запланируйте загрузку в фоновом режиме, когда устройство подключено к безлимитной сети (Wi-Fi), заряжается и не используется.

Перед загрузкой сжимайте файл профиля (например, с помощью GZIPOutputStream или ZipOutputStream). Профили Perfetto protobuf и дампы кучи Java значительно сжимаются, что снижает требования к пропускной способности сети и затраты на хранение данных.

Добавление зависимостей WorkManager

ProfilingManager не зависит от облака, поэтому вы можете запланировать загрузку профилей в любой бэкенд с помощью WorkManager. Добавьте библиотеку WorkManager в файл build.gradle.kts:

Kotlin

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

Яркий

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

Как запланировать загрузку

Когда приложение получит ProfilingResult, запланируйте загрузку. В примере ниже не показан первоначальный запрос на профилирование. Полный пример запроса можно найти в разделе Как записать трассировку системы.

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());
}

Анализ кода

В приведенном выше примере выполняются следующие действия:

  • Определите воркер. Создайте класс TraceUploadWorker, расширяющий Worker. Реализуйте doWork, чтобы убедиться, что файл профиля все ещё существует, сжать и загрузить его с помощью SDK серверной части или HTTP-клиента, а затем удалить локальный файл после успешной загрузки.
  • Составьте расписание.
    • Создайте OneTimeWorkRequest для сотрудника.
    • Задайте ограничения. Используйте setRequiredNetworkType(NetworkType.UNMETERED), setRequiresDeviceIdle(true) и setRequiresCharging(true), чтобы загрузка выполнялась, только когда устройство подключено к Wi-Fi, заряжается и не используется.
    • Передача данных. Используйте setInputData, чтобы передать путь к профилю из getResultFilePath в рабочий процесс.
    • Постановка в очередь. Отправьте запрос в WorkManager, вызвав метод WorkManager.enqueue.

Дальнейшие действия

После получения или загрузки профилей визуализируйте их в Perfetto UI, запросите отдельные профили с помощью PerfettoSQL, проанализируйте трассировки в массовом порядке или проанализируйте профили с помощью ИИ. Инструкции по настройке масштабируемого конвейера анализа на стороне сервера можно найти в статье Развертывание Bigtrace в Kubernetes.