Zrozumienie Systrace

systrace to podstawowe narzędzie do analizy wydajności urządzenia z systemem Android. Jednak tak naprawdę jest to opakowanie wokół innych narzędzi. Jest to wrapper po stronie hosta wokół atrace , pliku wykonywalnego po stronie urządzenia, który kontroluje śledzenie w przestrzeni użytkownika i konfiguruje ftrace , a także podstawowy mechanizm śledzenia w jądrze Linux. systrace używa atrace do włączenia śledzenia, a następnie odczytuje bufor ftrace i umieszcza go w samodzielnej przeglądarce HTML. (Podczas gdy nowsze jądra obsługują filtr Linux Enhanced Berkeley Packet Filter (eBPF), poniższa dokumentacja dotyczy jądra 3.18 (bez eFPF), ponieważ to właśnie było używane w Pixel/Pixel XL.)

Systrace jest własnością zespołów Google Android i Google Chrome i jest open source w ramach projektu Catapult . Oprócz systrace, Catapult zawiera inne przydatne narzędzia. Na przykład ftrace ma więcej funkcji niż można bezpośrednio włączyć za pomocą systrace lub atrace i zawiera pewne zaawansowane funkcje, które mają kluczowe znaczenie dla debugowania problemów z wydajnością. (Te funkcje wymagają uprawnień administratora i często nowego jądra).

System operacyjny

Debugując jitter na Pixel/Pixel XL, zacznij od następującego polecenia:

./systrace.py sched freq idle am wm gfx view sync binder_driver irq workq input -b 96000

W połączeniu z dodatkowymi punktami śledzenia wymaganymi dla aktywności GPU i potoku wyświetlania daje to możliwość śledzenia od danych wejściowych użytkownika do klatki wyświetlanej na ekranie. Ustaw rozmiar bufora na coś dużego, aby uniknąć utraty zdarzeń (ponieważ bez dużego bufora niektóre procesory nie zawierają żadnych zdarzeń po pewnym punkcie śledzenia).

Przechodząc przez systrace, pamiętaj, że każde zdarzenie jest wywoływane przez coś na procesorze .

Ponieważ systrace jest zbudowane na ftrace, a ftrace działa na procesorze, coś na procesorze musi zapisać bufor ftrace, który rejestruje zmiany sprzętu. Oznacza to, że jeśli jesteś ciekawy, dlaczego bariera wyświetlania zmieniła stan, możesz zobaczyć, co działało na procesorze dokładnie w momencie przejścia (coś działającego na procesorze wywołało tę zmianę w dzienniku). Ta koncepcja jest podstawą analizy wydajności za pomocą systrace.

Przykład: Rama robocza

W tym przykładzie opisano systrace dla normalnego potoku interfejsu użytkownika. Aby postępować zgodnie z przykładem, pobierz plik zip ze śladami (który zawiera również inne ślady, o których mowa w tej sekcji), rozpakuj plik i otwórz plik systrace_tutorial.html w przeglądarce. Ostrzegamy, że ten systrace to duży plik; chyba że używasz systrace w swojej codziennej pracy, jest to prawdopodobnie znacznie większy ślad z dużo większą ilością informacji niż kiedykolwiek widziałeś w pojedynczym śladzie.

W przypadku spójnego, okresowego obciążenia, takiego jak TouchLatency, potok interfejsu użytkownika zawiera następujące elementy:

  1. EventThread w SurfaceFlinger wybudza wątek interfejsu użytkownika aplikacji, sygnalizując, że nadszedł czas na renderowanie nowej klatki.
  2. Aplikacja renderuje klatkę w wątku interfejsu użytkownika, RenderThread i hwuiTasks przy użyciu zasobów procesora CPU i GPU. To jest większość pojemności zużywanej na interfejs użytkownika.
  3. Aplikacja wysyła wyrenderowaną ramkę do SurfaceFlinger za pomocą bindera, a następnie SurfaceFlinger przechodzi w stan uśpienia.
  4. Drugi EventThread w SurfaceFlinger budzi SurfaceFlinger, aby wyzwolić kompozycję i wyświetlanie danych wyjściowych. Jeśli SurfaceFlinger stwierdzi, że nie ma pracy do wykonania, wraca do snu.
  5. SurfaceFlinger obsługuje kompozycję za pomocą Hardware Composer (HWC)/Hardware Composer 2 (HWC2) lub GL. Kompozycja HWC/HWC2 jest szybsza i ma mniejszą moc, ale ma ograniczenia w zależności od systemu na chipie (SoC). Zwykle zajmuje to ~4-6 ms, ale może nakładać się na krok 2, ponieważ aplikacje na Androida są zawsze potrójnie buforowane. (Podczas gdy aplikacje są zawsze potrójnie buforowane, w SurfaceFlinger może być tylko jedna oczekująca ramka, co sprawia, że ​​wygląda ona identycznie jak podwójne buforowanie.)
  6. SurfaceFlinger wysyła końcowe dane wyjściowe do wyświetlenia za pomocą sterownika dostawcy i ponownie przechodzi w stan uśpienia, czekając na przebudzenie EventThread.

Przejdźmy przez klatkę rozpoczynającą się od 15409 ms:

Normalny potok interfejsu użytkownika z uruchomionym EventThread
Rysunek 1. Normalny potok interfejsu użytkownika, uruchomiony EventThread

Rysunek 1 to normalna ramka otoczona normalnymi ramkami, więc jest to dobry punkt wyjścia do zrozumienia, jak działa potok interfejsu użytkownika. Wiersz wątku interfejsu użytkownika dla TouchLatency zawiera różne kolory w różnym czasie. Słupki oznaczają różne stany wątku:

  • Szary . Spanie.
  • Niebieski. Runnable (może działać, ale harmonogram nie wybrał go jeszcze do uruchomienia).
  • Zielony. Aktywnie uruchomiony (program planujący myśli, że działa).
  • Czerwony. Nieprzerwany sen (zwykle spanie na kłódce w jądrze). Może wskazywać na obciążenie we/wy. Niezwykle przydatne do debugowania problemów z wydajnością.
  • Pomarańczowy. Nieprzerwany tryb uśpienia z powodu obciążenia we/wy.

Aby wyświetlić przyczynę nieprzerywanego uśpienia (dostępną w sched_blocked_reason ), wybierz czerwony wycinek uśpienia bezprzerwowego.

Gdy EventThread jest uruchomiony, wątek interfejsu użytkownika dla TouchLatency staje się możliwy do uruchomienia. Aby zobaczyć, co go obudziło, kliknij niebieską sekcję.

Wątek interfejsu użytkownika dla TouchLatency
Rysunek 2. Wątek interfejsu użytkownika dla TouchLatency

Rysunek 2 pokazuje, że wątek TouchLatency UI został obudzony przez tid 6843, co odpowiada EventThread. Wątek interfejsu użytkownika budzi się, renderuje ramkę i umieszcza ją w kolejce do wykorzystania przez SurfaceFlinger.

Wątek interfejsu użytkownika budzi się, renderuje ramkę i umieszcza ją w kolejce, aby SurfaceFlinger mógł ją wykorzystać
Rysunek 3. Wątek interfejsu użytkownika wybudza się, renderuje klatkę i umieszcza ją w kolejce do wykorzystania przez SurfaceFlinger

Jeśli tag binder_driver jest włączony w śledzeniu, możesz wybrać transakcję spinacza, aby wyświetlić informacje o wszystkich procesach zaangażowanych w tę transakcję.

Rysunek 4. Transakcja na segregatorze

Rysunek 4 pokazuje, że przy 15 423,65 ms Binder:6832_1 w SurfaceFlinger zaczyna działać z powodu tid 9579, który jest RenderThread TouchLatency. Możesz również zobaczyć queueBuffer po obu stronach transakcji spinacza.

Podczas kolejkiBuffer po stronie SurfaceFlinger liczba oczekujących ramek z TouchLatency wzrasta od 1 do 2.

Liczba ramek oczekujących wynosi od 1 do 2
Rysunek 5. Oczekujące ramki zmieniają się od 1 do 2

Rysunek 5 pokazuje potrójne buforowanie, gdzie są dwie ukończone klatki, a aplikacja wkrótce zacznie renderować trzecią. Dzieje się tak, ponieważ porzuciliśmy już niektóre ramki, więc aplikacja zachowuje dwie oczekujące ramki zamiast jednej, aby uniknąć kolejnych pominiętych ramek.

Niedługo potem główny wątek SurfaceFlingera zostaje wybudzony przez drugi wątek EventThread, dzięki czemu może wyświetlić na wyświetlaczu starszą oczekującą ramkę:

Główny wątek SurfaceFlingera budzi drugi EventThread
Rysunek 6. Główny wątek SurfaceFlingera jest obudzony przez drugi EventThread

SurfaceFlinger najpierw blokuje starszy oczekujący bufor, co powoduje zmniejszenie liczby oczekujących buforów z 2 do 1.

SurfaceFlinger najpierw zatrzaskuje się na starszym oczekującym buforze
Rysunek 7. SurfaceFlinger najpierw zatrzaskuje się na starszym oczekującym buforze

Po zatrzaśnięciu bufora SurfaceFlinger ustawia kompozycję i przesyła ostateczną klatkę na wyświetlacz. (Niektóre z tych sekcji są włączone jako część punktu mdss , więc mogą nie być uwzględnione w SoC).

SurfaceFlinger ustawia kompozycję i przesyła ostateczną klatkę
Rysunek 8. SurfaceFlinger ustawia kompozycję i przesyła ostateczną klatkę

Następnie mdss_fb0 budzi się na CPU 0. mdss_fb0 to wątek jądra potoku wyświetlania, który wysyła wyrenderowaną ramkę na wyświetlacz. Możemy zobaczyć mdss_fb0 jako własny wiersz w śladzie (przewiń w dół, aby wyświetlić).

mdss_fb0 budzi się na CPU 0
Rysunek 9. mdss_fb0 wybudza się na CPU 0

mdss_fb0 budzi się, działa krótko, przechodzi w nieprzerwany sen, a następnie budzi się ponownie.

Przykład: Niedziałająca rama

Ten przykład opisuje systrace używane do debugowania jittera Pixel/Pixel XL. Aby postępować zgodnie z przykładem, pobierz plik zip ze śladami (który zawiera inne ślady, o których mowa w tej sekcji), rozpakuj plik i otwórz plik systrace_tutorial.html w przeglądarce.

Kiedy otworzysz systrace, zobaczysz coś takiego:

TouchLatency działa na Pixel XL z włączoną większością opcji
Rysunek 10. TouchLatency działający na Pixel XL (większość włączonych opcji, w tym punkty śledzenia mdss i kgsl)

Szukając dżonka, sprawdź wiersz FrameMissed pod SurfaceFlinger. FrameMissed to poprawa jakości życia zapewniana przez HWC2. Podczas przeglądania systrace dla innych urządzeń wiersz FrameMissed może nie być obecny, jeśli urządzenie nie korzysta z HWC2. W obu przypadkach FrameMissed jest skorelowany z SurfaceFlingerem, w którym brakuje jednego z jego niezwykle regularnych środowisk wykonawczych i niezmienionej liczby buforów oczekujących dla aplikacji ( com.prefabulated.touchlatency ) w vsync.

FrameMissed korelacja z SurfaceFlinger
Rysunek 11. Korelacja FrameMissed z SurfaceFlinger

Rysunek 11 pokazuje brakującą klatkę przy 15598,29&nbps;ms. SurfaceFlinger obudził się na krótko w interwale vsync i ponownie zasnął bez wykonywania żadnej pracy, co oznacza, że ​​SurfaceFlinger uznał, że nie warto ponownie wysyłać ramki na wyświetlacz. Czemu?

Aby zrozumieć, w jaki sposób potok uległ awarii dla tej ramki, najpierw przejrzyj powyższy przykład ramki roboczej, aby zobaczyć, jak wygląda normalny potok interfejsu użytkownika w systrace. Gdy będziesz gotowy, wróć do pominiętej klatki i pracuj do tyłu. Zauważ, że SurfaceFlinger budzi się i natychmiast zasypia. Podczas przeglądania liczby oczekujących ramek z TouchLatency są dwie ramki (dobra wskazówka, aby pomóc dowiedzieć się, co się dzieje).

SurfaceFlinger budzi się i natychmiast zasypia
Rysunek 12. SurfaceFlinger budzi się i natychmiast zasypia

Ponieważ w SurfaceFlingerze mamy ramki, nie jest to problem z aplikacją. Ponadto SurfaceFlinger budzi się we właściwym czasie, więc nie jest to problem z SurfaceFlingerem. Jeśli SurfaceFlinger i aplikacja wyglądają normalnie, prawdopodobnie jest to problem ze sterownikiem.

Ponieważ mdss i sync są włączone, możemy uzyskać informacje o ogrodzeniach (współdzielonych przez sterownik ekranu i SurfaceFlinger), które kontrolują przesyłanie ramek do ekranu. Ogrodzenia te są wymienione pod mdss_fb0_retire , co oznacza, że ​​ramka jest wyświetlana. Te ogrodzenia są dostarczane jako część kategorii śledzenia sync . To, które ogrodzenia odpowiadają konkretnym zdarzeniom w SurfaceFlinger, zależy od Twojego SOC i stosu sterowników, więc współpracuj z dostawcą SOC, aby zrozumieć znaczenie kategorii ogrodzeń w Twoich śladach.

mdss_fb0_retire ogrodzenia
Rysunek 13. Ogrodzenia mdss_fb0_retire

Rysunek 13 pokazuje klatkę, która była wyświetlana przez 33 ms, a nie 16,7 ms, jak oczekiwano. W połowie tego wycinka ta ramka powinna była zostać zastąpiona nową, ale tak się nie stało. Wyświetl poprzednią klatkę i poszukaj czegokolwiek.

Klatka poprzedzająca zepsutą ramkę
Rysunek 14. Klatka przed zepsutą ramką

Rysunek 14 pokazuje 14,482 ms na ramkę. Zepsuty dwuklatkowy segment wynosił 33,6 ms, czyli mniej więcej tyle, ile oczekiwalibyśmy dla dwóch klatek (renderujemy przy 60 Hz, 16,7 ms na klatkę, czyli blisko). Ale 14,482 ms wcale nie jest zbliżone do 16,7 ms, co sugeruje, że coś jest nie tak z rurą wyświetlającą.

Zbadaj dokładnie, gdzie kończy się to ogrodzenie, aby ustalić, co je kontroluje.

Zbadaj koniec ogrodzenia
Rysunek 15. Zbadaj koniec ogrodzenia

Kolejka robocza zawiera __vsync_retire_work_handler , który jest uruchomiony, gdy zmienia się ogrodzenie. Przeglądając źródła jądra, możesz zobaczyć, że jest to część sterownika ekranu. Wygląda na to, że znajduje się na krytycznej ścieżce potoku wyświetlania, więc musi działać tak szybko, jak to możliwe. Można go uruchomić przez około 70 nas (nie jest to długie opóźnienie w harmonogramie), ale jest to kolejka robocza i może nie zostać dokładnie zaplanowany.

Sprawdź poprzednią klatkę, aby ustalić, czy to się przyczyniło; czasami jitter może się sumować z czasem i ostatecznie spowodować przekroczenie terminu.

Poprzednia klatka
Rysunek 16. Poprzednia ramka

Wiersz uruchamialny w kworker jest niewidoczny, ponieważ przeglądarka zmienia kolor na biały, gdy jest zaznaczony, ale statystyki mówią prawdę: 2,3 ms opóźnienia harmonogramu dla części krytycznej ścieżki potoku wyświetlania jest złe . Przed kontynuowaniem napraw opóźnienie, które polega na przeniesieniu tej części krytycznej ścieżki potoku wyświetlania z kolejki roboczej (która działa jako wątek SCHED_OTHER CFS) do dedykowanego wątku k SCHED_FIFO . Ta funkcja wymaga gwarancji czasu, których kolejki robocze nie mogą (i nie są przeznaczone) zapewnić.

Czy to jest powód szarpnięcia? Trudno powiedzieć jednoznacznie. Poza łatwymi do zdiagnozowania przypadkami, takimi jak rywalizacja o blokadę jądra powodująca uśpienie wątków krytycznych dla wyświetlania, ślady zwykle nie określają problemu. Czy ten jitter mógł być przyczyną wypadnięcia klatki? Absolutnie. Czasy ogrodzenia powinny wynosić 16,7 ms, ale w klatkach prowadzących do opuszczonej klatki wcale nie są zbliżone. Biorąc pod uwagę, jak ściśle powiązany jest potok wyświetlania, możliwe jest, że drgania wokół taktowania ogrodzenia spowodowały utratę klatki.

W tym przykładzie rozwiązanie obejmowało konwersję __vsync_retire_work_handler z kolejki roboczej na dedykowany kthread. Spowodowało to zauważalną poprawę jittera i zmniejszenie szarpnięć w teście odbijającej się piłki. Kolejne ślady pokazują czasy ogrodzenia, które oscylują bardzo blisko 16,7 ms.