Pobieranie i przesyłanie profili

Niezależnie od tego, czy przechwycisz profil oparty na aplikacji czy profil oparty na aktywatorze, ProfilingManager zapisze gotowy plik na urządzeniu i wyśle ProfilingResult do zarejestrowanego wywołania zwrotnego. Wywołaj ProfilingResult.getResultFilePath, aby uzyskać ścieżkę zapisanego profilu:

Pobieranie profili z urządzenia

W tej sekcji opisano, gdzie ProfilingManager przechowuje profile na urządzeniu, jak działa automatyczne czyszczenie plików i jak pobierać pliki lokalnie za pomocą adb.

Lokalizacje plików i formaty nazw

ProfilingManager zapisuje profile z usuniętymi danymi w katalogu pamięci aplikacji w /data/user/0/<app>/files/profiling/. Format nazwy pliku zależy od sposobu przechwycenia profilu:

  • Profile oparte na aplikacjach:

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

    Jeśli podczas wysyłania żądania profilu nie podano tagu, parametr <tag>_ jest pomijany: profile_<datetime>_uid-<uid>.<profile-type-ext>.

  • Profile oparte na aktywatorach:

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

Gdzie:

  • <app> to nazwa pakietu aplikacji.
  • <tag> to pierwsze 20 znaków alfanumerycznych (i łączników) tagu podanego przez element wywołujący, który został skonfigurowany za pomocą setTag lub przekazany do requestProfiling, przekonwertowany na małe litery i pozbawiony znaków spoza zakresu a-z, 0-9 i -.
  • <trigger_type> to kod stałej liczby całkowitej dla aktywatora z ProfilingTrigger. Zawsze wywołuj w kodzie ProfilingResult.getTriggerType, zamiast analizować nazwę pliku.
  • <datetime> to data i godzina zarejestrowania profilu (yyyy-MM-dd-HH-mm-ss-SSS).
  • <uid> to identyfikator użytkownika Linuksa Twojej aplikacji (zawarty w najnowszych wersjach modułu Profiling).
  • <profile-type-ext> to rozszerzenie pliku dla przechwyconego typu profilu:
    • .perfetto-trace dla śledzenia systemu
    • .perfetto-java-heap-dump w przypadku zrzutów sterty Java
    • .perfetto-heap-profile dla profili sterty
    • .perfetto-stack-sample w przypadku profili próbkowania stosu

Pamięć na urządzeniu i czyszczenie

Aby szybko zwolnić pamięć urządzenia, usuń każdy plik profilu z procesu roboczego przesyłania natychmiast po udanym przesłaniu (ponieważ automatyczne czyszczenie jest uruchamiane tylko wtedy, gdy ProfilingManager dostarcza wyniki do aplikacji, nieprzesłane pliki mogą pozostać na dysku, jeśli aplikacja przestanie rejestrować wywołania zwrotne po zakończeniu kampanii). W przypadku ostatnich wersji modułu Profiling ProfilingManager automatycznie usuwa też dostarczone pliki w katalogu pamięci aplikacji, które są starsze niż 5 dni, gdy dostarczane są nowe wyniki.

Przesyłanie profili w środowisku produkcyjnym za pomocą WorkManagera

Pliki profilu mogą mieć rozmiar kilku megabajtów, dlatego zaplanuj przesyłanie plików produkcyjnych w tle, gdy urządzenie jest połączone z siecią bez taryfowego rozliczania danych (Wi-Fi), ładuje się i jest bezczynne.

Przed przesłaniem skompresuj plik profilu (np. za pomocą GZIPOutputStream lub ZipOutputStream). Profile protobuf Perfetto i zrzuty sterty Javy znacznie się kompresują, co zmniejsza przepustowość sieci i koszty pamięci masowej backendu.

Dodawanie zależności biblioteki WorkManager

ProfilingManager nie jest powiązany z żadną chmurą, więc możesz zaplanować przesyłanie profili do dowolnego backendu za pomocą WorkManager. Dodaj bibliotekę WorkManager do pliku build.gradle.kts:

Kotlin

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

Dynamiczny

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

Planowanie zadania przesyłania

Gdy aplikacja otrzyma ProfilingResult, zaplanuj zadanie przesyłania. W poniższym przykładzie pominięto początkowe żądanie profilowania. Pełny przykład żądania znajdziesz w artykule Rejestrowanie śledzenia systemu.

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

Przewodnik po kodzie

W powyższym przykładzie wykonywane są te czynności:

  • Zdefiniuj instancję roboczą: utwórz klasę TraceUploadWorker rozszerzającą klasę Worker. Zaimplementuj doWork, aby sprawdzić, czy plik profilu nadal istnieje, skompresuj go i prześlij za pomocą pakietu SDK backendu lub klienta HTTP, a po zakończeniu przesyłania usuń plik lokalny.
  • Zaplanuj pracę:
    • Utwórz OneTimeWorkRequest dla instancji roboczej.
    • Ustaw ograniczenia: użyj setRequiredNetworkType(NetworkType.UNMETERED), setRequiresDeviceIdle(true) i setRequiresCharging(true), aby przesyłanie odbywało się tylko wtedy, gdy urządzenie jest połączone z Wi-Fi, ładuje się i jest bezczynne.
    • Przekazywanie danych: użyj setInputData, aby przekazać ścieżkę profilu z getResultFilePath do instancji roboczej.
    • Enqueue: przesyła żądanie do WorkManager, wywołując WorkManager.enqueue.

Dalsze kroki

Po pobraniu lub przesłaniu profili możesz wyświetlić je w interfejsie Perfetto, wykonać zapytania dotyczące poszczególnych profili za pomocą PerfettoSQL, przeanalizować ślady zbiorczo lub przeanalizować profile za pomocą AI. Wskazówki dotyczące konfigurowania skalowalnego potoku analizy po stronie serwera znajdziesz w artykule Wdrażanie Bigtrace w Kubernetes.