Profilowanie oparte na aktywatorach

ProfilingManager obsługuje przechwytywanie profili na podstawie wyzwalaczy systemowych. System zarządza procesem nagrywania i przekazuje wynikowy profil do Twojej aplikacji.

Aktywatory są powiązane ze zdarzeniami o kluczowym znaczeniu dla wydajności. Profile rejestrowane przez system zawierają szczegółowe informacje do debugowania dotyczące kluczowych ścieżek użytkowników powiązanych z tymi wyzwalaczami.

Przechwytywanie danych historycznych

Wiele wyzwalaczy wymaga analizy danych historycznych prowadzących do zdarzenia. Sygnał jest często konsekwencją problemu, a nie jego główną przyczyną. Jeśli rozpoczniesz profilowanie dopiero po wystąpieniu reguły, główna przyczyna może już być niedostępna.

Na przykład długo trwająca operacja w wątku interfejsu powoduje błąd Aplikacja nie odpowiada (ANR). Zanim system wykryje błąd ANR i powiadomi o nim aplikację, operacja może się już zakończyć. Rozpoczęcie profilowania w tym momencie nie uwzględnia rzeczywistego blokowania.

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

Dlaczego warto korzystać z rejestrowania opartego na wyzwalaczach?

Głównym powodem używania wyzwalaczy profilowania jest rejestrowanie 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ą.
  • Optymalizuj główne ścieżki użytkownika: analizuj i ulepszaj ścieżki, np. uruchamianie aplikacji.
  • Poznawanie zachowań użytkowników: uzyskuj statystyki dotyczące zdarzeń, np. zamykania aplikacji przez użytkowników.
.

Konfigurowanie aktywatora

Poniższy kod pokazuje, jak zarejestrować wywołanie TRIGGER_TYPE_APP_FULLY_DRAWN i zastosować do niego ograniczenie liczby wywołań.

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. Get the manager (Pobierz menedżera): pobiera usługę ProfilingManager.
  2. Zdefiniuj aktywator: tworzy ProfilingTrigger dlaTRIGGER_TYPE_APP_FULLY_DRAWN. To zdarzenie występuje, gdy aplikacja zgłosi, że zakończyła uruchamianie i jest interaktywna.
  3. Ustawianie limitów częstotliwości: stosuje 1-godzinny limit częstotliwości do tego konkretnego wyzwalacza (setRateLimitingPeriodHours(1)). Zapobiega to rejestrowaniu przez aplikację więcej niż 1 profilu uruchamiania na godzinę.
  4. Zarejestruj odbiorcę: wywołuje registerForAllProfilingResults, aby zdefiniować wywołanie zwrotne, które obsługuje wynik. To wywołanie zwrotne otrzymuje ścieżkę zapisanego profilu za pomocą parametru getResultFilePath().
  5. Add triggers: rejestruje listę reguł w ProfilingManager za pomocą addProfilingTriggers.
  6. Uruchom zdarzenie: wywołuje funkcję reportFullyDrawn(), która wysyła zdarzenie TRIGGER_TYPE_APP_FULLY_DRAWN do systemu, co powoduje uruchomienie zbierania profilu. Zakłada się, że w tle działa śledzenie systemowe i dostępny jest limit ogranicznika częstotliwości. Ten opcjonalny krok pokazuje pełny proces, ponieważ w przypadku tego aktywatora aplikacja musi wywołać reportFullyDrawn().

Pobieranie śladu

System zapisuje profile oparte na wyzwalaczach w tym samym katalogu co inne profile. Nazwa pliku ze śladami wywołanymi ma format:

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

Możesz pobrać plik za pomocą ADB. Aby na przykład pobrać ślad systemowy zarejestrowany za pomocą przykładowego kodu za pomocą ADB, możesz użyć tego polecenia:

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 śladów znajdziesz w artykule Pobieranie i analizowanie danych profilowania.

Jak działa śledzenie w tle

Aby rejestrować dane sprzed wywołania, system operacyjny okresowo uruchamia śledzenie w tle. Jeśli podczas aktywnego śledzenia w tle wystąpi wyzwalacz, na który zarejestrowana jest Twoja aplikacja, system zapisze profil śledzenia w katalogu aplikacji. Profil będzie zawierać informacje o zdarzeniach, które doprowadziły do wywołania.

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

Diagram pokazujący, jak działają migawki logów czasu w tle, z buforem pierścieniowym rejestrującym dane przed zdarzeniem wyzwalającym.
Ilustracja 1. Działanie zrzutów śledzenia w tle.

Aby zmniejszyć wpływ na wydajność urządzenia i czas pracy baterii, system nie śledzi w tle w sposób ciągły. Zamiast tego stosuje metodę próbkowania. System losowo rozpoczyna śledzenie w tle w określonym przedziale czasu (o minimalnym i maksymalnym czasie trwania). Losowe rozmieszczenie tych śladów zwiększa zasięg wyzwalania.

Profile wywoływane przez system mają zdefiniowany przez system maksymalny rozmiar, więc używają bufora pierścieniowego. Gdy bufor jest pełny, nowe dane śledzenia zastępują najstarsze dane. Jak widać na rysunku 1, zarejestrowany ślad może nie obejmować całego czasu trwania nagrywania w tle, jeśli bufor się zapełni. Zamiast tego przedstawia on najnowszą aktywność prowadzącą do wywołania.

Wdrażanie ograniczeń liczby żądań w przypadku poszczególnych wyzwalaczy

Triggery o wysokiej częstotliwości mogą szybko wyczerpać limit ogranicznika szybkości aplikacji. Aby lepiej zrozumieć ogranicznik szybkości, zapoznaj się z artykułem Jak działa ogranicznik szybkości. Aby zapobiec wyczerpaniu limitu przez jeden typ wyzwalacza, możesz wdrożyć ograniczenie liczby wywołań dla poszczególnych wyzwalaczy.

ProfilingManager obsługuje ograniczenie liczby wywołań zależne od wywoływacza zdefiniowane przez aplikację. Dzięki temu możesz dodać kolejną warstwę ograniczania na podstawie czasu oprócz istniejącego ogranicznika szybkości. Użyj interfejsu API setRateLimitingPeriodHours, aby ustawić konkretny czas oczekiwania na aktywator. Po upływie okresu oczekiwania możesz ponownie wywołać to działanie.

Debugowanie aktywatorów lokalnie

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

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

To polecenie wymusza rozpoczęcie ciągłego śledzenia w tle dla określonego pakietu, co umożliwia każdemu aktywatorowi zbieranie profilu, jeśli zezwala na to ogranicznik szybkości.

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