Narzędzia i skuteczność

Trace me if you can: Automated instrumentation and field profiling

Głośniki

  • Rahul Ravikumar

    Inżynier oprogramowania

Informacje

Informacje o wystąpieniu

Optymalizacja aplikacji na Androida wymaga szczegółowego wglądu w równoczesne przepływy pracy i zawieszenia współprogramów.

Chociaż lokalne profilowanie zapewnia przydatne informacje, skalowanie tych danych do środowiska produkcyjnego bez dodatkowych kosztów pozostaje wyzwaniem.

Podczas tej sesji przedstawimy schemat architektury pętli informacji zwrotnych dotyczących ciągłej wydajności.

androidx.tracing 2.0: dynamiczne przełączanie backendów i propagowanie kontekstów współprogramów (przepływów Perfetto) między wątkami.

Inteligentne instrumentowanie: używaj lokalnego śledzenia ART, aby znajdować punkty aktywne, a następnie automatyzuj wstrzykiwanie punktów śledzenia o niskim obciążeniu.

Testy porównawcze makro: śledź regresje i weryfikuj wzrost wydajności na urządzeniach fizycznych w CI.

Śledzenie w terenie: wyjdź poza śledzenie lokalne i CI dzięki ProfilingManager.

Podsumowanie

  • Tracing 2.0 zastępuje starsze interfejsy API do śledzenia na Androidzie, aby usunąć limity długości etykiet i działać 7 razy szybciej. Umożliwia też tworzenie niestandardowych ujść dla backendów, takich jak OpenTelemetry.
  • Propagacja kontekstu mapuje związki przyczynowo-skutkowe między wątkami, współprogramami i procesami. Możesz śledzić asynchroniczne wykonywanie za pomocą traceCoroutine lub przekazywać tokeny propagacji do kodu opartego na wywołaniach zwrotnych.
  • Jetpack Compose 1.13 dodaje śledzenie rekompozycji, używając niestandardowych metadanych, aby pokazać, dlaczego dochodzi do rekompozycji. Ślady rejestrują zmodyfikowane wartości stanu i dokładne stosy wywołań, które je wywołały.
  • Narzędzia do kompilacji na Androida używają Tracing 2.0 do wykrywania wąskich gardeł i przyspieszania kompilacji. Optymalizacje oparte na śledzeniu, które przyspieszyły zmniejszanie kodu R8 o 25% i skróciły czas przesyłania Metalava o 15%.

Gemini Notebook

Tematy

  • śledzenie
  • android-performance
  • perfetto
  • propagacja kontekstu
  • jetpack-compose
  • współprogramy
  • profilowanie

Transkrypcja

Pobierz transkrypcję

Wprowadzenie do śledzenia na Androidzie i jego ewolucja

  • Rahul Ravikumar

    Dzień dobry.

  • Nazywam się Rahul.

  • Jestem inżynierem w zespole ds. doświadczeń deweloperów aplikacji na Androida.

  • Skupiam się na wydajności aplikacji i bibliotek, a ta prezentacja dotyczy śledzenia.

  • Zanim przejdziemy do szczegółów, chcę ustalić kilka wspólnych pojęć, aby ułatwić Ci zrozumienie tematu.

  • Dlatego za każdym razem, gdy w tej prezentacji wspominam o śledzeniu, mam na myśli śledzenie jawne, czyli ręczne instrumentowanie kodu, dzięki któremu wiadomo, co robi aplikacja.

  • Nie mówię o automatycznych profilerach, które omówię później.

  • Ręczne śledzenie to sztuka pozostawiania śladów w bazie kodu, aby można było później przeanalizować kod w przypadku problemów z wydajnością lub innych problemów.

  • Zwykle jest to znacznie lepsze rozwiązanie niż logi.

  • Nie wiem, czy widzieliście dane wyjściowe Logcat dużej aplikacji.

  • Zwykle jest to ogromna ściana tekstu, którą trudno jest przeanalizować.

  • Z kolei śledzenie jest znacznie przyjemniejsze.

  • Łatwiej jest je sobie wyobrazić.

  • Masz czas trwania.

  • Masz już pewną strukturę.

  • Masz do dyspozycji narzędzia, dzięki którym możesz nawet analizować dane i sprawdzać, co dokładnie się wydarzyło i kiedy.

  • Dzięki temu jest o wiele lepiej.

  • Możesz też wskazać agentom ślad i zadać pytania.

  • Możesz używać narzędzi takich jak Trace Processor i testy porównawcze, a nawet przekształcać ślady w wskaźniki.

  • To wszystko można zrobić za pomocą logów czasu, co jest trudne w przypadku Logcata.

  • Z kolei profilowanie, a mam na myśli profilowanie automatyczne, jest zwykle odpowiedzią na znany problem.

  • Możesz na przykład zauważyć, że aplikacja uruchamia się powoli lub coś działa nieprawidłowo, i chcesz dowiedzieć się, co się dzieje.

  • Jeśli nie masz jeszcze śladów ręcznych, które mogłyby Ci pomóc, możesz skorzystać z automatycznego profilowania.

  • Możesz użyć np. śledzenia metod ART, w którym instrumentujesz każde wejście i wyjście z metody.

  • Możesz też analizować próbkowanie stosu za pomocą narzędzi takich jak Simpleperf.

  • Zamiast więc używać wszystkich wejść i wyjść metod i mieć duże obciążenie, patrzysz na próbki stosu wywołań i widzisz, co znajduje się na ścieżce krytycznej aplikacji.

  • Jest rok 2026, pamięć RAM jest problemem wszędzie, więc możesz przyjrzeć się pamięci, wyciekom pamięci i profilom sterty.

  • Jest to więc kolejny profiler, z którego możesz skorzystać.

  • Możesz sprawdzić ścieżki przechowywania sterty.

  • Możesz sprawdzić, który kod przydziela pamięć. To tylko niektóre z analiz, które możesz przeprowadzić za pomocą tego narzędzia.

  • Po prawej.

  • Teraz mamy już odpowiednie słownictwo.

  • Porozmawiajmy teraz o tym, dlaczego stworzyliśmy nową bibliotekę.

  • Interfejsy API do śledzenia w wersji 1.0 zostały pierwotnie wprowadzone w Androidzie Jelly Bean.

  • To było w 2012 roku.

  • To było ponad 14 lat temu.

  • Od tego czasu interfejsy API nie zmieniły się tak bardzo, jak mogłoby się wydawać.

  • Dodaliśmy kilka rozszerzeń.

  • Zdarzenia asynchroniczne i liczniki zostały dodane w Androidzie 10.

  • To było w 2019 roku.

  • I to wszystko.

  • Od tego czasu interfejs API nie uległ zmianie.

  • Jednak ekosystem bardzo się rozwinął.

  • Śledzenie platformy znacznie się poprawiło, ale nie przełożyło się to na dodatkowe interfejsy API.

  • Chcemy to zmienić.

  • Innym dużym problemem jest to, że śledzenie ma stały backend śledzenia w wersji 1.0, co oznacza, że za każdym razem, gdy dodasz sekcję śledzenia w kodzie, zawsze trafi ona do bufora śledzenia systemu.

  • Atrace to podsystem śledzenia Androida, który jest właścicielem tych danych.

  • Jeśli więc chcesz przekonwertować ślady na dane telemetryczne, jest to bardzo trudne, ponieważ musisz wiedzieć, jak atrace przechowuje te dane.

  • Jest to dobrze udokumentowany, ale potencjalnie trudny do wykorzystania format.

  • Chcemy to naprawić.

  • Dlatego chcemy udostępnić Ci narzędzia, które pozwolą Ci przenosić dane śledzenia, gdziekolwiek chcesz.

  • W śledzeniu 1.0 obowiązywało też ograniczenie, zgodnie z którym etykiety mogły mieć maksymalnie 128 znaków.

  • W 2012 roku było to prawdopodobnie uzasadnione ograniczenie, ale w 2026 roku wydaje się dość dziwne.

  • Co gorsza, nie można też dodawać do logu czasu żadnych dodatkowych metadanych, co jest dodatkowym problemem.

  • Do tego dochodzą koszty ogólne.

  • Dlatego podczas śledzenia aplikacji musisz zwracać szczególną uwagę na narzut, ponieważ obserwowanie systemu nie powinno zmieniać jego działania.

  • Jeśli więc chcesz odtworzyć sytuację wyścigu, śledzenie nie powinno jej eliminować.

  • Dlatego musisz zadbać o zachowanie wszystkich semantycznych elementów, które stosujesz w innych przypadkach.

  • Śledzenie 1.0 miało stosunkowo duże obciążenie, które chcieliśmy zmniejszyć.

  • OK.

  • Dużo mówiłem o problemach.

  • Teraz przyjrzyjmy się niektórym problemom i zobaczmy, jak się one nakładają w praktyce.

  • Oto ślad Perfetto.

  • Dotyczy aplikacji com.example.app.

  • Mamy wątek główny i jeden wycinek o nazwie initializeComponent.

  • Teraz chcemy uzyskać więcej szczegółów.

  • Dodajmy więc szczegół.

  • Wiemy już, że initializeComponent wywołuje performInitialize.

  • Nadal nie wiemy, który komponent jest inicjowany.

  • Teraz jedynym narzędziem w naszym zestawie narzędzi z funkcją Tracing 1 jest nazwa, więc dodajemy ją do nazwy wycinka.

  • Pamiętaj, że nazwy wycinków logów czasu są ograniczone do 128 znaków, więc w logu czasu zostaną obcięte.

  • Co gorsza, narzut związany z obcinaniem jest teraz narzutem, który ponosisz podczas śledzenia.

  • W takiej sytuacji szybko pojawia się problem.

  • Chcieliśmy to zmienić.

  • Widzieliśmy ślad Perfetto i wiemy, jak wygląda wizualnie.

  • Teraz przyjrzyjmy się temu z perspektywy pisania kodu, który generuje te sekcje śladu.

  • Ponownie mamy interfejs API initializeComponent.

  • Przyjmuje kontekst i nazwę, wywołuje performInitialize.

  • Idzie Ci doskonale.

  • Teraz chcemy dodać log czasu za pomocą interfejsu Tracing 1.0 API. W tym celu importujesz androidx.tracing.trace i używasz bloku logu czasu.

  • nadajesz mu nazwę,

  • Tutaj jest performInitialize.

  • Ale tak jak wcześniej, chcemy poznać nazwę komponentu, więc użyjemy interpolacji ciągów znaków w Kotlinie i dodamy nazwę.

  • I tu pojawia się pierwszy problem.

  • W tym przypadku limit znaków wynosi 128.

Omówienie śledzenia 2.0 i wydajności

  • Rahul Ravikumar

    Możemy to naprawić za pomocą narzędzia Tracing 2.

  • Zamiast androidx.tracing.trace używasz Tracer.

  • Interfejs API wygląda bardzo podobnie.

  • Zamiast trace masz Tracer.global.trace.

  • Masz kategorię.

  • Później omówimy kategorie bardziej szczegółowo, ale zasadniczo masz nazwę.

  • Następnie masz blok kodu, który próbujesz prześledzić.

  • Teraz nie obowiązuje już limit 128 znaków.

  • Mówiłem o narzucie śledzenia.

  • Chcę omówić to bardziej szczegółowo i pokazać, jak ulepszyliśmy tę funkcję.

  • Na Pixelu 6a z Androidem 16 wstawienie pojedynczej sekcji początkowej i końcowej zajęło około 1200 nanosekund.

  • Może się wydawać, że to niewiele, ale jeśli masz bardzo gorący kod, który dodaje wiele sekcji śledzenia, to jest to czas, który aplikacja mogłaby poświęcić na renderowanie klatek.

  • Nie chcemy więc zabierać Ci tego czasu.

  • Wprowadziliśmy jednak znaczne ulepszenia w pakiecie SDK AndroidX Perfetto.

  • Był to eksperymentalny pakiet SDK, który wprowadziliśmy na potrzeby śledzenia kompozycji, i znacznie poprawił on sytuację.

  • Nie chcieliśmy, abyście musieli dostarczać ten pakiet, ponieważ jest to pakiet Perfetto C SDK z powiązaniami JNI, a pakiet C SDK zajmuje około 4 MB w pliku APK.

  • Nie chcieliśmy, żeby taka sytuacja miała miejsce.

  • Teraz, dzięki Tracing 2, mamy specjalnie stworzoną bibliotekę.

  • Znacznie zmniejszyliśmy narzut i teraz Tracing 2 działa 7 razy szybciej, co oznacza, że masz więcej czasu na to, co chcesz zrobić, a infrastruktura zajmuje mniej czasu.

  • Co umożliwia takie rozwiązanie, co wcześniej nie było możliwe?

  • Jednym z przykładów jest funkcja śledzenia środowiska wykonawczego Metro.

  • Metro to biblioteka do wstrzykiwania zależności.

  • Obsługuje Kotlin Multiplatform.

  • Używa wtyczki kompilatora Kotlin.

  • Generuje cały kod na potrzeby wstrzykiwania zależności i integruje się z Tracing 2.0, aby ułatwić wizualizację kosztu każdego elementu @Inject w kodzie.

  • Oto przykład.

  • Załóżmy, że mamy wykres aplikacji.

  • W Metro wszystko jest ograniczone do grafu, więc mamy jedno pole: AnalyticsTracker.

  • Mamy też powiązanie dostarczania.

  • Mamy klienta sieciowego z implementacją OkHttpClient, a implementacja AnalyticsTracker pochodzi z AnalyticsTrackerImpl. Potrzebujemy instancji klienta sieciowego, którą graf wie, jak dostarczyć.

  • Wtyczka kompilatora generuje kod, który wygląda tak.

  • Jeśli więc masz AnalyticsTracker, metoda pobierająca tworzy instancję klienta sieci i przekazuje ją do konstruktora AnalyticsTrackerImpl, co jest dobrym rozwiązaniem.

  • Jednak po włączeniu śledzenia możesz zobaczyć koszt utworzenia nie tylko implementacji trackera, ale też każdej zależności, której potrzebuje.

  • Teraz umożliwiamy wyświetlanie nie tylko kosztu utworzenia instancji typu, ale też wszystkich jego zależności w śladzie, co jest bardzo przydatne.

  • Dzięki tym informacjom Wise, aplikacja finansowa, poprawiła czas uruchamiania o 45%.

  • Udało im się odroczyć rzeczy, których nie trzeba było inicjować podczas uruchamiania aplikacji. Zidentyfikowali też kilka elementów o nieprawidłowym zakresie, w przypadku których rzeczy, które powinny być singletonami, przypadkowo nimi nie były. Są to rzeczy, które można zobaczyć i łatwo naprawić w kodzie.

  • Jeśli spojrzysz na ich dane dotyczące uruchamiania, są one naprawdę imponujące.

  • Udało się skrócić nie tylko czas do wyświetlenia pierwszej klatki, ale też czas do wyświetlenia całej reklamy.

  • Inny partner, BandLab, używa tych samych informacji w CI.

  • Dzięki temu nie tylko znasz koszt każdego powiązania @Inject, ale też wiesz, który wątek go wygenerował.

  • Gdy już to wiesz, możesz znaleźć wszystkie kosztowne wiązania @Inject w głównym wątku i w CI. Możesz uruchomić zapytanie, aby uzyskać listę wszystkich kosztownych wiązań, a następnie przypisać je do zmiany, która je wprowadziła. Teraz możesz obwinić rzeczywistą zmianę i spróbować ją naprawić.

Przekazywanie kontekstu, współprogramy i Jetpack Compose

  • Rahul Ravikumar

    OK.

  • Do tej pory mówiłem o wszystkich ulepszeniach w porównaniu z Tracing 1, ale teraz omówię nowe funkcje Tracing 2, które wcześniej nie były możliwe.

  • Jedną z głównych funkcji jest propagacja kontekstu. Ponieważ nowoczesne aplikacje stają się coraz bardziej złożone, zadania są wykonywane w wielu wątkach, współprogramach i procesach.

  • Chcesz mieć możliwość wizualizacji sposobu propagacji żądania między poszczególnymi wątkami i procesami.

  • To właśnie umożliwia propagacja kontekstu.

  • Jeśli więc podam Ci listę wycinków, nie mówiąc nic o tym, co spowodowało co, będą one przydatne, ale nie będzie łatwo wyciągnąć z nich wniosków.

  • Jeśli jednak powiem, że to był ciąg przyczynowo-skutkowy, łatwiej będzie Wam zrozumieć, co się stało, i powiązać koszt z każdym wycinkiem.

  • To właśnie umożliwia propagacja kontekstu.

  • Z przyjemnością informujemy, że propagacja kontekstu jest w pełni obsługiwana. Oto przykład.

  • Wróćmy do śladu Perfetto com.example.app. Załóżmy, że performInitialize trwa zbyt długo i chcesz odroczyć koszt lub przenieść go do innego wątku.

  • Możesz to zrobić.

  • Załóżmy, że przenoszę performInitialize do innego wątku.

  • Teraz utraciliśmy hierarchiczny charakter wycinka.

  • Teraz nie możesz już stwierdzić, że initializeComponent spowodowało performInitialize, chyba że spojrzysz na kod.

  • W idealnym świecie strzałka wskazywałaby, że A powoduje B, i można by było wywnioskować przyczynowo-skutkowy ciąg zdarzeń.

  • Być może widzieliście nawet śledzenie systemu, w którym robimy to w przypadku transakcji binder na platformie.

  • Tutaj widać, że test porównawczy Compose wykonuje transakcję binder do system_server, aby przygotować ramkę.

  • Z perspektywy śledzenia platformy zawsze było to możliwe, ale nigdy nie udostępniliśmy narzędzi deweloperom aplikacji.

  • Dlatego teraz propagacja kontekstu współprogramu Kotlin jest w pełni obsługiwana w Tracing 2.0.

  • Nie musisz nic robić.

  • Wystarczy wyjąć go z pudełka.

  • Mamy tu jedno wywołanie metody zawieszenia o nazwie fetchProfile.

  • Wywołuje metodę client.get, która jest kolejną metodą zawieszającą, a następnie przełącza się na Dispatchers.Default, aby zdeserializować odpowiedź.

  • To bardzo szablonowy kod współprogramów, z którym prawdopodobnie masz do czynienia na co dzień.

  • Załóżmy, że chcemy to prześledzić.

  • Dodajemy sekcję śledzenia.

  • W tym przypadku zamiast funkcji trace użyjemy interfejsu API traceCoroutine, ponieważ jest to blok kodu zawieszającego, a w przypadku wywołania wewnętrznego, zanim przełączymy kontekst na Dispatchers.Default, dodamy kolejną wewnętrzną sekcję śledzenia za pomocą tego samego interfejsu API traceCoroutine.

  • W śladzie zobaczysz coś takiego.

  • Zobaczysz, że nasze zadanie rozpoczęło się w worker-1, a następnie przełączyło kontekst i przeniosło się do worker-3. Wskazują na to strzałki w śladzie.

  • Co więcej, jeśli powiększymy kwadrat pośrodku, zobaczymy, że deserializacja faktycznie rozpoczęła się w instancji roboczej 1, a wywołanie withContext spowodowało przełączenie kontekstu, po czym kontynuowano ją w instancji roboczej 3, a następnie wznowiono fetchProfile w instancji roboczej 3, ponieważ mechanizm współprogramów dąży do zminimalizowania liczby przełączeń kontekstu.

  • Możesz zobaczyć, kiedy dokładnie współprogram został zawieszony, kiedy został wznowiony, a także pełny łańcuch przyczynowo-skutkowy zdarzeń.

  • Inną funkcją, którą umożliwia Tracing 2, jest możliwość dodawania niestandardowych metadanych do logu czasu oczekiwania.

  • Wcześniej jedynym narzędziem, którym dysponowaliście, była nazwa sekcji śledzenia, ale teraz możecie dodać blok metadanych, który pozwala na dodanie dowolnej liczby wpisów metadanych.

  • Możesz dodać to do śladu.

  • Gdy spojrzysz na rzeczywisty ślad, zobaczysz, że wszystkie dodane przez Ciebie metadane są w nim zawarte i mogą być wykorzystywane przez wszystkie narzędzia.

  • Teraz możesz umieścić wszystkie informacje, które chcesz przekazać.

  • Przedstawiłem kilka abstrakcyjnych funkcji, które umożliwia śledzenie, ale wszystkie te funkcje błyszczą w nowej funkcji Jetpack Compose o nazwie śledzenie rekompozycji.

  • Ta funkcja jest dostępna w Compose 1.13, więc możesz z niej korzystać już dziś.

  • Oto przykład.

  • To jest śledzenie kompozycji, które prawdopodobnie wszyscy znacie.

  • Ta funkcja istnieje od początków Compose.

  • Pokazuje względny koszt funkcji kompozycyjnej i wszystkich jej podwycinków oraz koszt każdego z nich w wątku, w którym występowały.

  • To całkiem proste.

  • Nie podaje jednak kontekstu.

  • Nie dowiesz się z niego, dlaczego tak się stało.

  • Nie podaje przyczyny ponownego tworzenia.

  • Jeśli pomniejszę widok, zobaczę obszar zainteresowania, który widzieliśmy na poprzednim slajdzie.

  • Po lewej stronie widać, że wystąpiło zdarzenie wysłania danych wejściowych.

  • Być może to spowodowało zmianę stanu i rekompozycję.

  • Nie jest to jednak jasne.

  • Nie masz pewności.

  • Jednak dzięki śledzeniu rekompozycji dodajemy teraz do śladu niestandardowe metadane, które dokładnie informują, dlaczego doszło do rekompozycji.

  • Dzięki temu łatwo możesz stwierdzić, że te zdarzenia są ze sobą powiązane.

  • Jeśli klikniesz, zobaczysz, co dokładnie się zmieniło.

  • Poinformuje Cię, że był to stan, czyli zmienna wartość stanu, która została przełączona na „false” (fałsz), a wcześniej miała wartość „true” (prawda).

  • Co więcej, podamy Ci dokładny stos wywołań, który spowodował błąd.

  • Tutaj widać, że wystąpiło zdarzenie AbstractClickableNode.performClick, które spowodowało zmianę stanu zmiennego, co z kolei doprowadziło do rekompozycji.

  • To naprawdę świetna funkcja, której wcześniej nie mieliśmy.

  • Te narzędzia są też dostępne – przepraszam, wszystkie te funkcje są też dostępne w nowym narzędziu Android Performance Analyzer, jeśli chcesz wypróbować niektóre narzędzia agentowe.

  • I prawo.

  • Wspomniałem, że propagacja kontekstu jest w pełni obsługiwana w przypadku współprogramów w języku Kotlin.

  • Co jednak z kodem, który używa kombinacji współprogramów i innych rozwiązań, albo gdy samodzielnie uruchamiasz wątki, albo masz starszy kod, który z jakiegoś powodu używa wywołań zwrotnych?

  • OK.

  • Dobra wiadomość jest taka, że nadal możesz korzystać ze wszystkich zalet propagacji kontekstu.

  • Załóżmy, że mamy interfejs API, który używa wywołania zwrotnego i wykonawcy.

  • Implementacja używa executor.execute, wywołuje runCatching, który jest szczegółem implementacji, otrzymuje odpowiedź, a następnie informuje o niej wywołanie zwrotne.

  • OK.

  • Jeśli chcę to prześledzić, wpisuję Tracer.global.trace, nadaję etykietę i otaczam blok.

  • Ponieważ jednak chcę też zachować przyczynowy łańcuch zdarzeń, zaakceptuję nowy, opcjonalny typ, który nazywa się tokenem propagacji.

  • Następnie przekażę token do wewnętrznego interfejsu API śledzenia.

  • Każdy, kto wywołuje tę metodę, może opcjonalnie podać token propagacji, dzięki czemu mogę zachować przyczynowy łańcuch zdarzeń.

  • OK.

  • Teraz element wywołujący ten interfejs API może być interfejsem API zawieszającym.

  • Mamy tu funkcję zawieszającą profileRequest, która wywoła interfejs API używający wywołania zwrotnego i wykonawcy.

  • W tym przypadku, ponieważ łączę 2 światy – świat zawieszenia ze światem wywołania zwrotnego – użyję kreatora suspendCoroutine.

  • Otrzymam kontynuację, a następnie wywołam interfejs API i wznowię kontynuację w wywołaniu zwrotnym, gdy otrzymam odpowiedź.

  • Tutaj definiuję wykonawcę o nazwie profileRequestContext.

  • Jest to wykonawca jednowątkowy.

  • Ponieważ traceCoroutine umieszcza token propagacji w kontekście współprogramu, mogę uzyskać token propagacji z kontekstu współprogramu, a następnie przekazać go jako dane wejściowe do interfejsu API brownfield, aby zachować przyczynowy łańcuch zdarzeń.

  • W śladzie jest to bardzo widoczne.

  • Mamy więc coś, co zaczęło się w worker-3, przełączyło się na wykonawcę jednowątkowego po przełączeniu kontekstu, a następnie wznowiło działanie w worker.

  • To bardzo przydatna funkcja.

Śledzenie globalne i inicjowanie niestandardowe

  • Rahul Ravikumar

    OK.

  • Możesz też dostosować Tracer.global.

  • Interfejs Tracing 2.0 API zapewnia bardzo dobre domyślne ustawienia dla Tracer.global, ale jeśli chcesz, możesz je dostosować.

  • Ponadto Tracing 2.0 obsługuje JVM i Androida, ponieważ chcemy, aby śledzenie było w pełni obsługiwane zarówno w przypadku bibliotek Javy, jak i Androida.

  • Trzy kroki inicjowania to: utworzenie ujścia logów śledzenia,

  • Ujście logów czasu to sposób przechowywania pakietów logów czasu.

  • Określa format przesyłania i lokalizację danych wyjściowych logów czasu, w której są przechowywane.

  • Udostępniamy gotową implementację ujścia śladów, która obsługuje format Perfetto, ale oczywiście możesz napisać własne ujście śladów.

  • Wracamy do problemu, który polegał na tym, że atrace był jedynym backendem dostępnym w Tracing 1.

  • Chcieliśmy to zmienić.

  • Jeśli więc chcesz napisać ujście OpenTelemetry lub użyć własnego, jest to całkowicie możliwe.

  • Możesz to zmienić.

  • Gdy masz już ujście, możesz utworzyć sterownik śledzenia i przypisać mu ujście.

  • Możesz też opcjonalnie wybrać kategorie, które chcesz włączyć.

  • W tym miejscu kategorie są naprawdę przydatne.

  • Możesz więc zdecydować, że w wersji produkcyjnej niektóre kategorie są ważniejsze niż inne w porównaniu z wersją debugowania, i włączyć te kategorie.

  • Możesz też powiedzieć: „Chcę włączyć wszystkie kategorie niezależnie od tego, skąd pochodzą”.

  • To zupełnie w porządku.

  • Możesz też dodać atrybuty niestandardowe, które są globalne dla Twojego śladu.

  • Możesz więc umieścić informacje o wersji kompilacji, jej rodzaju lub inne metadane, które chcesz dołączyć do śladu, za pomocą interfejsu API addAttribute.

  • Na koniec możesz zarejestrować obiekt śledzący, wywołując funkcję setGlobalTracer.

  • To właśnie wiąże Tracer.global z tą instancją modułu śledzącego i jest dostępne dla procesu.

  • W przypadku Androida procesy mają znacznie lepiej zdefiniowany cykl życia, więc wystarczy zaimplementować AbstractTraceDriver.Factory, zastąpić metodę create i wykonać dokładnie te same czynności co wcześniej.

  • Najpierw tworzysz ujście, a potem sterownik.

  • Nie musisz się tu rejestrować.

  • Zarejestrujemy Cię, ponieważ wiemy, że to był pierwszy krok.

  • OK.

  • Dobrze, chcę jeszcze raz podkreślić, że śledzenie jest gotowe do działania na wielu platformach.

  • To bardzo celowy wybór, ponieważ wiemy, że Compose działa na wielu platformach, i chcemy umożliwić Ci śledzenie w jak największej liczbie miejsc.

  • Obecnie obsługujemy Androida i JVM, ale nasza implementacja jest całkowicie otwarta na obsługę przyszłych platform, jeśli zajdzie taka potrzeba.

Optymalizacje narzędzi do kompilacji (R8 i metalava)

  • Rahul Ravikumar

    Narzędzie R8, które służy do zmniejszania i optymalizowania kodu źródłowego na Androidzie, korzysta z funkcji Tracing 2.0.

  • Dzięki wykorzystaniu śladów zoptymalizowaliśmy R8, aby szybciej zmniejszać i optymalizować kod – o 25%.

  • Oznacza to krótszy czas kompilacji i krótszy czas oczekiwania na optymalizację.

  • Nie będę omawiać wszystkich optymalizacji, które wprowadziliśmy w R8, ale chcę omówić jedną, aby pokazać, co umożliwia śledzenie.

  • R8 jest więc w zasadzie transpilatorem.

  • Otrzymujesz więc zestaw plików JAR, które zmniejszasz i optymalizujesz, a następnie tworzysz kolejny zestaw plików JAR, które możesz przekonwertować na pliki DEX.

  • W tym przypadku AGP zaczyna od odczytania plików JAR, które zawierają nie tylko aplikację, ale też wszystkie jej zależności.

  • Dlatego używa procesów roboczych Gradle, aby rozpocząć odczytywanie wszystkich wejściowych plików JAR.

  • Masz wiele plików JAR, które chcesz odczytać.

  • Po zakończeniu odczytywania plików JAR R8 zaczyna analizować kod i tworzyć model tego, co reprezentuje kod, oraz wszystkich jego punktów wejścia, aby móc rozpocząć optymalizację i zmniejszanie kodu.

  • Ten fragment, await, reprezentuje moment, w którym R8 jest naprawdę gotowy do rozpoczęcia procesu optymalizacji.

  • Możesz zauważyć puste miejsce pośrodku i zastanawiać się, dlaczego mamy ten niewykorzystany czas pracy procesora.

  • Wynika to z tego, że w przypadku starego interfejsu API czekaliśmy, aż wszyscy pracownicy odczytają wszystkie pliki, zanim zaczniemy je analizować.

  • Oczywiście możemy to naprawić i właśnie to zrobiliśmy.

  • W AGP 9 po zakończeniu odczytywania wejściowych plików JAR od razu zaczynamy je przetwarzać.

  • Nie musieliśmy czekać, aż wszystkie instancje robocze skończą odczytywać dane wejściowe.

  • Dzięki temu czas oczekiwania na odczytanie wszystkich danych wejściowych znacznie się skrócił.

  • Jest to więc oczywista optymalizacja, którą łatwo zauważyć, gdy patrzy się na ślad, ale nie jest to takie oczywiste, gdy patrzy się tylko na kod źródłowy, aby zobaczyć, co warto zoptymalizować.

  • Innym narzędziem, którego używamy codziennie jako inżynierowie AndroidX, jest Metalava.

  • Metalava to narzędzie, którego używamy, aby mieć pewność, że wszystkie udostępniane przez nas interfejsy API mają gwarancje zgodności, których oczekujesz, i mogą być w pełni używane przez osoby z zewnątrz bez żadnych problemów, których chcemy uniknąć.

  • To narzędzie, które działa w każdym publicznym i ograniczonym interfejsie API, więc każda zaoszczędzona sekunda to czas, który odzyskujemy jako inżynierowie AndroidX.

  • Metalava wykorzystała też śledzenie, aby skrócić czas po przesłaniu o aż 15%.

  • Przejdźmy do śladu, aby wyjaśnić, co się stało. Metalava przeprowadza 2 rodzaje testów: pierwszy sprawdza interfejsy API z ograniczonym dostępem, a drugi – publiczne interfejsy API.

  • Możesz jednak zauważyć pewien wzorzec.

  • Przeprowadzamy wiele czynności konfiguracyjnych, aby przeprowadzić te testy dwukrotnie.

  • Wszystkie dane wejściowe programu są odczytywane, a następnie tworzymy PSI, czyli interfejs struktury programu, który dokładnie odzwierciedla strukturę kodu.

  • Następnie tworzymy AST, zanim zaczniemy stosować wszystkie kontrole linta, które chcemy.

  • I wykonujemy tę kosztowną pracę dwukrotnie.

  • Jak rozwiązaliśmy ten problem?

  • Zrobiliśmy to tylko raz.

  • Zamiast więc wykonywać całą tę pracę konfiguracyjną dwukrotnie, zrobiliśmy to tylko raz i wykorzystaliśmy te dane wejściowe zarówno do kontroli z ograniczonym dostępem, jak i kontroli publicznych, co pozwoliło nam zaoszczędzić sporo czasu.

  • Tak.

  • OK.

Sprawdzone metody śledzenia i testy porównawcze

  • Rahul Ravikumar

    Teraz, gdy już wiecie, co umożliwia śledzenie 2.0, warto pamiętać, że choć śledzenie jest ważne, równie ważne jest wiedzieć, co śledzić.

  • Nie chcesz zostawiać śladów wszędzie.

  • Musisz bardzo ostrożnie wybierać, co chcesz śledzić. Postaram się podać Ci kilka wskazówek, co warto śledzić.

  • Pierwszą rzeczą, którą należy zrobić, jest znalezienie niewyjaśnionych luk w śladzie.

  • Jeśli więc wątki są w stanie uruchomienia lub gotowości do uruchomienia, a w śledzeniu systemu widać wyraźne luki, których nie można wyjaśnić, i nie wiadomo, co robi aplikacja, warto to zbadać.

  • W takim przypadku mogę utworzyć ślad metody nałożony na śledzenie systemu i sprawdzić, co warto śledzić, a następnie dodać odpowiednie sekcje śledzenia.

  • Chcesz też ustalić priorytety na podstawie ścieżek użytkowników.

  • Warto więc przyjrzeć się uruchamianiu aplikacji.

  • Chcesz przyjrzeć się ścieżkom użytkowników w aplikacji i sprawdzić, czy możesz je wyjaśnić, analizując ślad.

  • Dlatego zawsze, gdy nie możesz czegoś zrozumieć, warto to prześledzić.

  • Warto też przyjrzeć się głębokim stosom wywołań lub niewyjaśnionemu wykorzystaniu procesora w śladzie i spróbować dowiedzieć się, co dokładnie się dzieje, a następnie przekonwertować te części bazy kodu na ślady.

  • Możesz więc je prześledzić.

  • Jeśli wątki są uśpione, musisz wiedzieć, dlaczego tak się dzieje.

  • Powinny oczekiwać na operacje wejścia-wyjścia, transakcję binder lub zdarzenia zewnętrzne, które powinny się wydarzyć.

  • Jeśli jednak masz niewyjaśnione problemy ze snem, warto je zbadać, ponieważ może się za nimi coś kryć.

  • Gdy już dodasz wszystkie sekcje śledzenia w kodzie, przekształć je w testy porównawcze, aby móc śledzić zmiany.

  • W miarę rozwoju bazy kodu możesz sprawdzać, czy dane się zmieniają i czy panele odzwierciedlają duże zmiany, ponieważ wtedy wiesz, że zmieniło się coś ważnego.

  • Możesz też pominąć wszystkie poprzednie kroki i poprosić o to agenta.

  • Weź te wytyczne, przekształć je w umiejętność, a następnie wskaż ją swojemu ulubionemu agentowi i pozwól mu działać.

  • Tak.

Konfiguracja, podsumowanie i wnioski

  • Rahul Ravikumar

    Jeśli udało mi się przekonać Cię, że śledzenie jest warte zachodu i powinno się je stosować, oto konfiguracja projektu.

  • To całkiem proste.

  • Śledzenie to po prostu tradycyjna biblioteka Javy, więc definiujesz zależność od androidx.tracing:tracing-wire, czyli kombinacji sterownika śledzenia zgodnego z Perfetto i śledzenia. Obecna wersja stabilna to 2.0.5.

  • Jeśli używasz biblioteki Java, jest to dokładnie to samo. To interfejs API w pełni wieloplatformowy.

  • Chcemy rozszerzyć tę funkcję na inne platformy, gdy będzie to miało sens.

  • Chcę tylko krótko podsumować.

  • Omówiliśmy już wszystkie nowe, ciekawe funkcje, które dodaliśmy.

  • Przekazywanie kontekstu jest więc bardzo przydatne.

  • Możemy umieszczać niestandardowe informacje, metadane, informacje o stosie wywołań i inne elementy.

  • Nie wspomniałem o wielu rzeczach.

  • Możesz oczywiście zmienić format śledzenia, ujście śledzenia, a także przenieść dane śledzenia do OpenTelemetry.

  • To wszystko możesz zrobić.

  • Śledzenie jest wieloplatformowe.

  • This is again a very, very conscious decision because we want as many people to be able to take advantage of this.

  • Wszystkie narzędzia, które udostępniamy, takie jak Perfetto, AndroidX Benchmarks, nowy analizator wydajności czy stary Android Profiler, obsługują Tracing 2.0.

  • Dziękuję za uwagę.

  • Życzę udanego szczytu dla deweloperów i dziękuję.