Profilowanie oparte na aktywatorach

ProfilingManager obsługuje przechwytywanie profili na podstawie aktywatorów systemowych. System zarządza procesem nagrywania i udostępnia wynikowy profil aplikacji.

Aktywatory są powiązane ze zdarzeniami, które mają kluczowe znaczenie dla wydajności. Profile zarejestrowane przez system zawierają szczegółowe informacje na potrzeby debugowania dotyczące głównych ścieżek użytkownika powiązanych z tymi aktywatorami.

Przechwytywanie danych historycznych

Wiele reguł wymaga analizy danych historycznych poprzedzających zdarzenie. Samo wywołanie jest często konsekwencją problemu, a nie jego główną przyczyną. Jeśli uruchomisz profil dopiero po wystąpieniu aktywatora, główna przyczyna może już zostać utracona.

Na przykład długo trwająca operacja w wątku UI powoduje błąd Aplikacja nie odpowiada (ANR). Zanim system wykryje błąd ANR i powiadomi aplikację, operacja może się już zakończyć. Rozpoczęcie profilowania w tym momencie nie pozwoli na wykrycie rzeczywistej blokady.

Dokładne przewidzenie, kiedy wystąpią niektóre aktywatory, jest niemożliwe, co uniemożliwia ręczne uruchomienie profilu z wyprzedzeniem.

Dlaczego warto korzystać z przechwytywania na podstawie aktywatora?

Głównym powodem używania aktywatorów profilowania jest przechwytywanie danych dotyczących nieprzewidywalnych zdarzeń, w przypadku których aplikacja nie może rozpocząć nagrywania ręcznie przed ich wystąpieniem. Aktywatory profilowania mogą służyć do:

  • Debugowanie problemów z wydajnością: diagnozowanie błędów ANR, wycieków pamięci i innych problemów ze stabilnością.
  • Optymalizowanie głównych ścieżek użytkownika: analizowanie i ulepszanie procesów, np. uruchamiania aplikacji.
  • Poznawanie zachowań użytkowników: możesz uzyskiwać statystyki dotyczące zdarzeń, np. wyjść z aplikacji zainicjowanych przez użytkownika.

Konfigurowanie aktywatora

Poniższy kod pokazuje, jak zarejestrować regułę TRIGGER_TYPE_APP_FULLY_DRAWN i zastosować do niej ograniczanie liczby żądań.

Kotlin

fun recordWithTrigger() {
    val profilingManager = applicationContext.getSystemService(ProfilingManager::class.java)

    val triggers = ArrayList<ProfilingTrigger>()

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

    triggers.add(triggerBuilder.build())

    val mainExecutor: 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(mainExecutor, resultCallback)
    profilingManager.addProfilingTriggers(triggers)

}

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

}

Kod wykonuje te czynności:

  1. Pobieranie menedżera: pobiera usługę ProfilingManager.
  2. Zdefiniuj aktywator: tworzy ProfilingTrigger dla TRIGGER_TYPE_APP_FULLY_DRAWN. To zdarzenie występuje, gdy aplikacja zgłasza, że zakończyła uruchamianie i jest interaktywna.
  3. Ustaw limity: stosuje limit 1 godziny do tego konkretnego aktywatora (setRateLimitingPeriodHours(1)). Zapobiega to rejestrowaniu przez aplikację więcej niż 1 profilu uruchamiania na godzinę.
  4. Zarejestruj detektor: wywołuje registerForAllProfilingResults, aby zdefiniować wywołanie zwrotne, które obsługuje wynik. To wywołanie zwrotne otrzymuje ścieżkę zapisanego profilu przez getResultFilePath().
  5. Dodaj aktywatory: rejestruje listę aktywatorów w ProfilingManager za pomocą addProfilingTriggers.
  6. Uruchom zdarzenie: wywołuje reportFullyDrawn(), co powoduje wysłanie zdarzenia TRIGGER_TYPE_APP_FULLY_DRAWN do systemu, który uruchamia zbieranie profili, zakładając, że ślad systemowy w tle był uruchomiony i dostępny jest limit ograniczenia liczby żądań. Ten opcjonalny krok pokazuje cały proces, ponieważ aplikacja musi wywołać reportFullyDrawn() w przypadku tego aktywatora.

Pobieranie śladu

System zapisuje profile oparte na aktywatorach w tym samym katalogu co inne profile. Nazwa pliku wywołanych logów czasu ma ten format:

profile_trigger_<profile_type_code>_<datetime>.<profile-type-name>

Możesz pobrać plik za pomocą ADB. Na przykład aby pobrać śledzenie systemu zarejestrowane za pomocą przykładowego kodu przy użyciu ADB, może to wyglądać tak:

adb pull /data/user/0/com.example.sampleapp/files/profiling/profile_trigger_1_2025-05-06-14-12-40.perfetto-trace

Szczegółowe informacje o wizualizacji tych logów czasu znajdziesz w artykule Pobieranie i analizowanie danych profilowania.

Jak działa śledzenie w tle

Aby przechwytywać dane sprzed aktywacji, system operacyjny okresowo uruchamia śledzenie w tle. Jeśli podczas działania śledzenia w tle wystąpi zdarzenie aktywujące, a aplikacja jest zarejestrowana do jego obsługi, system zapisze profil śledzenia w katalogu aplikacji. Profil będzie wtedy zawierać informacje, które doprowadziły do aktywacji.

Po zapisaniu profilu system powiadamia aplikację za pomocą wywołania zwrotnego przekazanego do registerForAllProfilingResults. To wywołanie zwrotne podaje ścieżkę do przechwyconego profilu, do którego można uzyskać dostęp, wywołując funkcję ProfilingResult#getResultFilePath().

Diagram pokazujący, jak działają zrzuty logów czasu w tle, z buforem pierścieniowym przechwytującym dane przed zdarzeniem wyzwalającym.
Ilustracja 1. Jak działają zrzuty śladów w tle.

Aby zmniejszyć wpływ na wydajność urządzenia i czas pracy na baterii, system nie uruchamia śladów w tle w sposób ciągły. Zamiast tego używa metody próbkowania. System losowo rozpoczyna śledzenie w tle w ustalonym przedziale czasu (z minimalnym i maksymalnym czasem trwania). Losowe rozmieszczenie tych śladów poprawia pokrycie aktywacji.

Profile wywoływane przez system mają maksymalny rozmiar określony przez system, więc używają bufora pierścieniowego. Gdy bufor się zapełni, nowe dane śledzenia zastąpią najstarsze dane. Jak widać na rysunku 1, przechwycony log czasu może nie obejmować całego czasu nagrywania w tle, jeśli bufor się zapełni. Zamiast tego reprezentuje on najnowszą aktywność prowadzącą do aktywacji.

Wdrażanie ograniczania liczby żądań na podstawie reguł

Wyzwalacze o wysokiej częstotliwości mogą szybko wyczerpać limit ogranicznika szybkości aplikacji. Aby lepiej zrozumieć, jak działa ogranicznik liczby żądań, zapoznaj się z artykułem Jak działa ogranicznik liczby żądań. Aby zapobiec wyczerpaniu limitu przez jeden typ aktywatora, możesz wdrożyć ograniczanie liczby żądań dla konkretnego aktywatora.

ProfilingManager obsługuje zdefiniowane przez aplikację ograniczanie liczby żądań dla konkretnych reguł. Dzięki temu możesz dodać kolejną warstwę ograniczania na podstawie czasu oprócz istniejącego ogranicznika liczby żądań. Aby ustawić konkretny czas oczekiwania dla aktywatora, użyj interfejsu API setRateLimitingPeriodHours. Po upływie tego czasu możesz ponownie uruchomić tę funkcję.

Debugowanie aktywatorów lokalnie

Ślady w tle są uruchamiane w losowych momentach, więc debugowanie aktywatorów lokalnie jest trudne. Aby wymusić śledzenie w tle na potrzeby testów, użyj tego polecenia ADB:

adb shell device_config put profiling_testing system_triggered_profiling.testing_package_name <com.example.myapp>

To polecenie wymusza na systemie rozpoczęcie ciągłego śledzenia w tle dla określonego pakietu, dzięki czemu każdy aktywator może zebrać profil, jeśli pozwala na to ograniczanie liczby żądań.

Możesz też włączyć inne opcje debugowania, np. wyłączyć ogranicznik szybkości podczas debugowania lokalnego. Więcej informacji znajdziesz w artykule na temat poleceń debugowania na potrzeby lokalnego profilowania.