Analiza wydajności produkcji za pomocą narzędzia Cloud Profiler

1. Omówienie

Programiści aplikacji klienckich i frontendowych często używają w celu poprawy wydajności kodu narzędzi takich jak Android Studio CPU Profiler czy narzędzia do profilowania dostępne w Chrome, jednak ich odpowiedniki nie są tak łatwo dostępne ani dobrze stosowane przez osoby pracujące z usługami backendu. Cloud Profiler zapewnia te same możliwości deweloperom usług niezależnie od tego, czy ich kod działa w Google Cloud Platform, czy w innym miejscu.

95c034c70c9cac22.png

Narzędzie gromadzi informacje o wykorzystaniu procesora i alokacji pamięci przez aplikacje produkcyjne. Przypisuje te informacje do kodu źródłowego aplikacji, pomagając Ci zidentyfikować te części aplikacji zużywające najwięcej zasobów i pokazując w inny sposób cechy wydajności kodu. Niski nakład pracy związany z technikami zbierania danych wykorzystywanych przez to narzędzie sprawia, że nadaje się ono do ciągłego użytku w środowiskach produkcyjnych.

Z tego ćwiczenia w Codelabs dowiesz się, jak skonfigurować Cloud Profiler na potrzeby programu w języku Go, i dowiesz się, jakie statystyki dotyczące wydajności aplikacji może przedstawiać to narzędzie.

Czego się nauczysz

  • Jak skonfigurować program w języku Go do profilowania za pomocą Cloud Profiler.
  • Jak zbierać, wyświetlać i analizować dane dotyczące wydajności za pomocą Cloud Profiler.

Czego potrzebujesz

  • Projekt Google Cloud Platform
  • przeglądarki, np. Chrome lub Firefox;
  • znajomość standardowych edytorów tekstu systemu Linux, takich jak Vim, EMAC lub Nano;

Jak wykorzystasz ten samouczek?

Tylko do przeczytania Przeczytaj go i wykonaj ćwiczenia

Jak oceniasz swoje doświadczenia z Google Cloud Platform?

Początkujący Poziom średnio zaawansowany Biegły
.

2. Konfiguracja i wymagania

Samodzielne konfigurowanie środowiska

  1. Zaloguj się w konsoli Cloud i utwórz nowy projekt lub wykorzystaj już istniejący. Jeśli nie masz jeszcze konta Gmail ani Google Workspace, musisz je utworzyć.

96a9c957bc475304.png

b9a10ebdf5b5a448.png

a1e3c01a38fa61c2.png

Zapamiętaj identyfikator projektu, unikalną nazwę we wszystkich projektach Google Cloud (powyższa nazwa jest już zajęta i nie będzie Ci odpowiadać). W dalszej części tego ćwiczenia w programie będzie ona określana jako PROJECT_ID.

  1. Następnie musisz włączyć płatności w Cloud Console, aby korzystać z zasobów Google Cloud.

Ukończenie tego ćwiczenia z programowania nie powinno kosztować zbyt wiele. Postępuj zgodnie z instrukcjami podanymi w sekcji „Czyszczenie” W tym samouczku znajdziesz wskazówki, jak wyłączyć zasoby, aby uniknąć naliczania opłat. Nowi użytkownicy Google Cloud mogą skorzystać z programu bezpłatnego okresu próbnego o wartości 300 USD.

Google Cloud Shell,

Google Cloud można obsługiwać zdalnie z Twojego laptopa, ale dla uproszczenia konfiguracji w tym ćwiczeniu w programowaniu użyjemy Google Cloud Shell – środowiska wiersza poleceń działającego w chmurze.

Aktywowanie Cloud Shell

  1. W konsoli Cloud kliknij Aktywuj Cloud Shell 4292cbf4971c9786.png.

bce75f34b2c53987.png

Jeśli dopiero zaczynasz korzystać z Cloud Shell, wyświetli się ekran pośredni (w części strony widocznej po przewinięciu) z opisem tej funkcji. W takim przypadku kliknij Dalej (nie zobaczysz go więcej). Tak wygląda ten jednorazowy ekran:

70f315d7b402b476.png

Uzyskanie dostępu do Cloud Shell i połączenie się z nim powinno zająć tylko kilka chwil.

fbe3a0674c982259.png

Ta maszyna wirtualna ma wszystkie potrzebne narzędzia dla programistów. Zawiera stały katalog domowy o pojemności 5 GB i działa w Google Cloud, co znacznie zwiększa wydajność sieci i uwierzytelnianie. Większość czynności z tego ćwiczenia z programowania można wykonać w przeglądarce lub na Chromebooku.

Po nawiązaniu połączenia z Cloud Shell powinno pojawić się potwierdzenie, że użytkownik jest już uwierzytelniony i że projekt jest już ustawiony na identyfikator Twojego projektu.

  1. Uruchom to polecenie w Cloud Shell, aby potwierdzić, że jesteś uwierzytelniony:
gcloud auth list

Dane wyjściowe polecenia

 Credentialed Accounts
ACTIVE  ACCOUNT
*       <my_account>@<my_domain.com>

To set the active account, run:
    $ gcloud config set account `ACCOUNT`
  1. Uruchom to polecenie w Cloud Shell, aby sprawdzić, czy polecenie gcloud zna Twój projekt:
gcloud config list project

Dane wyjściowe polecenia

[core]
project = <PROJECT_ID>

Jeśli tak nie jest, możesz go ustawić za pomocą tego polecenia:

gcloud config set project <PROJECT_ID>

Dane wyjściowe polecenia

Updated property [core/project].

3. Przechodzenie do usługi Cloud Profiler

W konsoli Cloud przejdź do interfejsu programu profilującego, klikając „Profiler”. na lewym pasku nawigacyjnym:

37ad0df7ddb2ad17.png

Możesz też użyć paska wyszukiwania konsoli Cloud, aby przejść do interfejsu programu profilującego. Wpisz w nim „Cloud Profiler” i wybierz znaleziony element. W obu przypadkach w interfejsie programu profilującego powinien pojawić się komunikat „Brak danych do wyświetlenia”. jak poniżej. Projekt jest nowy, więc nie zostały jeszcze zebrane żadne dane profilowania.

d275a5f61ed31fb2.png

Nadszedł czas, żeby trochę profilować.

4. Profilowanie testu porównawczego

Użyjemy prostej syntetycznej aplikacji w języku Go dostępnej na GitHubie. W otwartym terminalu Cloud Shell (i mimo że w interfejsie programu profilującego nadal wyświetla się komunikat „Brak danych do wyświetlenia”), uruchom to polecenie:

$ go get -u github.com/GoogleCloudPlatform/golang-samples/profiler/...

Następnie przejdź do katalogu aplikacji:

$ cd ~/gopath/src/github.com/GoogleCloudPlatform/golang-samples/profiler/hotapp

Katalog zawiera plik „main.go”. to aplikacja syntetyczna, która ma włączonego agenta profilowania:

main.go

...
import (
        ...
        "cloud.google.com/go/profiler"
)
...
func main() {
        err := profiler.Start(profiler.Config{
                Service:        "hotapp-service",
                DebugLogging:   true,
                MutexProfiling: true,
        })
        if err != nil {
                log.Fatalf("failed to start the profiler: %v", err)
        }
        ...
}

Agent profilowania domyślnie zbiera profile procesorów, sterty i wątków. Podany tu kod umożliwia zbieranie profili muteksu (nazywanych też „rywalizacją”).

Teraz uruchom program:

$ go run main.go

W trakcie działania programu agent profilowania będzie okresowo zbierać profile 5 skonfigurowanych typów. W miarę upływu czasu dane są zbierane w sposób losowy (średnio dla każdego typu profilu na minutę), więc zebranie danych każdego typu może potrwać do 3 minut. Program poinformuje Cię, kiedy utworzy profil. Komunikaty są włączone przez flagę DebugLogging w powyższej konfiguracji; W przeciwnym razie agent działa dyskretnie:

$ go run main.go
2018/03/28 15:10:24 profiler has started
2018/03/28 15:10:57 successfully created profile THREADS
2018/03/28 15:10:57 start uploading profile
2018/03/28 15:11:19 successfully created profile CONTENTION
2018/03/28 15:11:30 start uploading profile
2018/03/28 15:11:40 successfully created profile CPU
2018/03/28 15:11:51 start uploading profile
2018/03/28 15:11:53 successfully created profile CONTENTION
2018/03/28 15:12:03 start uploading profile
2018/03/28 15:12:04 successfully created profile HEAP
2018/03/28 15:12:04 start uploading profile
2018/03/28 15:12:04 successfully created profile THREADS
2018/03/28 15:12:04 start uploading profile
2018/03/28 15:12:25 successfully created profile HEAP
2018/03/28 15:12:25 start uploading profile
2018/03/28 15:12:37 successfully created profile CPU
...

Interfejs użytkownika zaktualizuje się wkrótce po zebraniu pierwszych profili. Po tym czasie nie będzie automatycznie aktualizowany, więc aby zobaczyć nowe dane, musisz ręcznie odświeżyć interfejs programu profilującego. Aby to zrobić, kliknij dwukrotnie przycisk Teraz w selektorze przedziału czasu:

650051097b651b91.png

Po odświeżeniu interfejsu zobaczysz coś takiego:

47a763d4dc78b6e8.png

Selektor typu profilu pokazuje pięć dostępnych typów profili:

b5d7b4b5051687c9.png

Przyjrzyjmy się teraz każdemu typowi profilu i ważnym funkcjom interfejsu użytkownika, a następnie przeprowadź kilka eksperymentów. Na tym etapie nie potrzebujesz już terminala Cloud Shell, więc możesz go zamknąć, naciskając CTRL+C i wpisując „exit”.

5. Analizowanie danych programu profilującego

Skoro mamy już trochę danych, przyjrzyjmy się im bliżej. Używamy aplikacji syntetycznej (źródło jest dostępne na GitHubie), która symuluje działanie typowe dla różnych rodzajów problemów z wydajnością w środowisku produkcyjnym.

Kod obciążający procesor

Wybierz typ profilu procesora. Po załadowaniu interfejsu zobaczysz na wykresie płomieniowym cztery bloki liści dla funkcji load, które łącznie odpowiadają całemu wykorzystaniu procesora:

fae661c9fe6c58df.png

Ta funkcja została opracowana z myślą o zużywaniu dużej liczby cykli procesora w ramach ścisłej pętli:

main.go

func load() {
        for i := 0; i < (1 << 20); i++ {
        }
}

Funkcja jest wywoływana pośrednio z busyloop() przez 4 ścieżki wywołania: busyloop → {foo1, foo2} → {bar, baz} → load. Szerokość pola funkcji reprezentuje względny koszt konkretnej ścieżki wywołania. W tym przypadku wszystkie cztery ścieżki mają mniej więcej taki sam koszt. W prawdziwym programie chcesz skupić się na optymalizacji ścieżek połączeń, które mają największe znaczenie pod względem skuteczności. Wykres płomieniowy, który wizualnie uwydatnia droższe ścieżki z większymi prostokątami, ułatwia ich identyfikację.

Aby jeszcze bardziej zawęzić wyświetlane dane, możesz użyć filtra danych z profilu. Możesz na przykład dodać opcję „Pokaż stosy”, filtr z określeniem „baz” jako ciągu znaków filtra. Powinien wyświetlić się zrzut ekranu podobny do tego poniżej, na którym widoczne są tylko 2 z 4 ścieżek wywołania load(). Te dwie ścieżki są jedynymi, które przechodzą przez funkcję z ciągiem „baz” w jej nazwie. Takie filtrowanie jest przydatne, gdy chcesz skupić się na części większego programu (np. dlatego, że posiadasz tylko jego część).

eb1d97491782b03f.png

Kod, który wymaga dużej ilości pamięci

Teraz przełącz na „Stosunek” typ profilu. Pamiętaj, aby usunąć wszystkie filtry utworzone we wcześniejszych eksperymentach. Zobaczysz wykres płomienia, na którym funkcja allocImpl, wywoływana przez alloc, jest przedstawiona jako główny konsument pamięci w aplikacji:

f6311c8c841d04c4.png

Tabela podsumowania nad wykresem płomienia wskazuje, że całkowita ilość używanej pamięci przez aplikację wynosi średnio ok.57,4 MiB, a większość z nich jest przydzielona przez funkcję allocImpl. Biorąc pod uwagę implementację tej funkcji, nie jest to zaskoczeniem:

main.go

func allocImpl() {
        // Allocate 64 MiB in 64 KiB chunks
        for i := 0; i < 64*16; i++ {
                mem = append(mem, make([]byte, 64*1024))
        }
}

Funkcja jest wykonywana raz, przydzielając 64 MiB na mniejsze fragmenty, a następnie zapisuje wskaźniki do tych fragmentów w zmiennej globalnej, aby chronić je przed odśmiecaniem pamięci. Zauważ, że ilość pamięci wykorzystywana przez program profilujący różni się nieco od 64 MiB. Program profilujący sterty Go jest narzędziem statystycznym, więc pomiary są niewielkie, ale nie dokładne w postaci bajtów. Nie zdziw się, jeśli różnica wynosi ok. 10%.

Kod wymagający dużych nakładów reklamowych

Jeśli wybierzesz „Wątki” w selektorze typu profilu ekran przełączy się na wykres płomieniowy, którego większość szerokości jest zajęta przez funkcje wait i waitImpl:

ebd57fdff01dede9.png

W podsumowaniu nad wykresem płomieniowym widać, że istnieje 100 reguł, które zwiększają swój stos wywołań za pomocą funkcji wait. Zgadza się, bo kod inicjujący te oczekiwania wygląda tak:

main.go

func main() {
        ...
        // Simulate some waiting goroutines.
        for i := 0; i < 100; i++ {
                go wait()
        }

Ten typ profilu pozwala się dowiedzieć, czy program spędza w oczekiwaniach jakieś nieoczekiwane czasy (np. I/O). Takie stosy wywołań nie będą zwykle próbkowane przez program profilujący procesora, ponieważ nie zużywają one istotnej części czasu pracy procesora. Często przydatna jest opcja „Ukryj stosy”. filtry z profilami Threads, na przykład aby ukryć wszystkie stosy z wywołaniem gopark,, ponieważ są to często nieaktywne reguły i mniej interesujące niż te oczekujące na I/O.

Typ profilu wątków może również pomóc w zidentyfikowaniu punktów w programie, w których wątki oczekują na muteks należący do innej części programu przez długi czas. W tym przypadku bardziej przydatny jest jednak poniższy typ profilu.

Kod wymagający rywalizacji

Typ profilu rywalizacji wskazuje najbardziej „pożądane” co się zmienia w programie. Ten typ profilu jest dostępny w programach Go, ale trzeba go włączyć, określając „MutexProfiling: true” w kodzie konfiguracji agenta. Gromadzenie danych polega na rejestrowaniu (w ramach wskaźnika „Treści”) liczby przypadków, w których podczas odblokowywania konkretnego zamka przez gorutynę A czekała na niego inna reguła B. Rejestruje również (w ramach wskaźnika „Opóźnienie) czas oczekiwania na blokadę przez zablokowany kod. W tym przykładzie mamy 1 stos rywalizacji, a całkowity czas oczekiwania na blokadę wyniósł 10,5 sekundy:

83f00dca4a0f768e.png

Kod, który generuje ten profil, składa się z 4 gorutynów walczących z muteksem:

main.go

func contention(d time.Duration) {
        contentionImpl(d)
}

func contentionImpl(d time.Duration) {
        for {
                mu.Lock()
                time.Sleep(d)
                mu.Unlock()
        }
}
...
func main() {
        ...
        for i := 0; i < 4; i++ {
                go contention(time.Duration(i) * 50 * time.Millisecond)
        }
}

6. Podsumowanie

W tym module pokazaliśmy, jak skonfigurować program w języku Go pod kątem Cloud Profiler. Wiesz już również, jak zbierać, wyświetlać i analizować dane dotyczące skuteczności w tym narzędziu. Teraz możesz wykorzystać nową umiejętność w prawdziwych usługach, które działają w Google Cloud Platform.

7. Gratulacje!

Wiesz już, jak skonfigurować usługę Cloud Profiler i jak z niej korzystać.

Więcej informacji

Licencja

To zadanie jest licencjonowane na podstawie ogólnej licencji Creative Commons Attribution 2.0.