1. Übersicht
Entwickler von Client-Apps und Frontend-Webentwicklern verwenden häufig Tools wie den Android Studio CPU Profiler oder die in Chrome enthaltenen Profiling-Tools, um die Leistung ihres Codes zu verbessern. Äquivalente Techniken sind jedoch bei Backend-Diensten noch nicht annähernd so zugänglich oder gut umgesetzt. Cloud Profiler bietet Dienstentwicklern diese Funktionen, unabhängig davon, ob ihr Code auf der Google Cloud Platform oder anderswo ausgeführt wird.
Das Tool erfasst Informationen zur CPU-Nutzung und Arbeitsspeicherzuweisung aus Ihren Produktionsanwendungen. Anschließend werden diese Informationen dem Quellcode der Anwendung zugeordnet. So können Sie feststellen, welche Teile der Anwendung die meisten Ressourcen beanspruchen, und außerdem die Leistungsmerkmale des Codes unter die Lupe nehmen. Aufgrund des geringen Aufwands für die eingesetzten Erfassungstechniken eignet sich das Tool für den kontinuierlichen Einsatz in Produktionsumgebungen.
In diesem Codelab erfahren Sie, wie Sie Cloud Profiler für ein Go-Programm einrichten. Außerdem lernen Sie, welche Informationen zur Anwendungsleistung das Tool liefern kann.
Aufgaben in diesem Lab
- Go-Programm für die Profilerstellung mit Cloud Profiler konfigurieren
- Leistungsdaten mit Cloud Profiler erfassen, ansehen und analysieren
Voraussetzungen
- Ein Google Cloud Platform-Projekt
- Browser, z. B. Chrome oder Firefox
- Erfahrung mit standardmäßigen Linux-Texteditoren wie vim, EMAC oder Nano
Wie möchten Sie diese Anleitung nutzen?
<ph type="x-smartling-placeholder">Wie würden Sie Ihre Erfahrung mit der Google Cloud Platform bewerten?
<ph type="x-smartling-placeholder">2. Einrichtung und Anforderungen
Umgebung für das selbstbestimmte Lernen einrichten
- Melden Sie sich in der Cloud Console an und erstellen Sie ein neues Projekt oder verwenden Sie ein vorhandenes Projekt. Wenn Sie noch kein Gmail- oder Google Workspace-Konto haben, müssen Sie eines erstellen.
Notieren Sie sich die Projekt-ID, also den projektübergreifend nur einmal vorkommenden Namen eines Google Cloud-Projekts. Der oben angegebene Name ist bereits vergeben und kann leider nicht mehr verwendet werden. Sie wird in diesem Codelab später als PROJECT_ID
bezeichnet.
- Als Nächstes müssen Sie in der Cloud Console die Abrechnung aktivieren, um Google Cloud-Ressourcen nutzen zu können.
Dieses Codelab sollte ohne großen Aufwand betrieben werden. Folgen Sie der Anleitung im Abschnitt „Bereinigen“, . Hier erfahren Sie, wie Sie Ressourcen herunterfahren, damit Ihnen über dieses Tutorial hinaus keine Kosten entstehen. Neue Google Cloud-Nutzer haben Anspruch auf eine kostenlose Testversion von 300$.
Google Cloud Shell
Sie können Google Cloud zwar von Ihrem Laptop aus der Ferne bedienen, zur Vereinfachung der Einrichtung in diesem Codelab verwenden wir jedoch Google Cloud Shell, eine Befehlszeilenumgebung, die in der Cloud ausgeführt wird.
Cloud Shell aktivieren
- Klicken Sie in der Cloud Console auf Cloud Shell aktivieren .
Wenn Sie Cloud Shell noch nie gestartet haben, wird ein Zwischenbildschirm (below the fold) angezeigt, in dem beschrieben wird, worum es sich dabei handelt. Klicken Sie in diesem Fall auf Weiter. Der Chat wird nie wieder angezeigt. So sieht dieser einmalige Bildschirm aus:
Die Bereitstellung und Verbindung mit Cloud Shell dauert nur einen Moment.
Diese virtuelle Maschine verfügt über alle Entwicklungstools, die Sie benötigen. Es bietet ein Basisverzeichnis mit 5 GB nichtflüchtigem Speicher und wird in Google Cloud ausgeführt. Dadurch werden die Netzwerkleistung und die Authentifizierung erheblich verbessert. Viele, wenn nicht sogar alle Arbeiten in diesem Codelab können Sie ganz einfach mit einem Browser oder Ihrem Chromebook erledigen.
Sobald Sie mit Cloud Shell verbunden sind, sollten Sie sehen, dass Sie bereits authentifiziert sind und dass das Projekt bereits auf Ihre Projekt-ID eingestellt ist.
- Führen Sie in Cloud Shell den folgenden Befehl aus, um zu prüfen, ob Sie authentifiziert sind:
gcloud auth list
Befehlsausgabe
Credentialed Accounts ACTIVE ACCOUNT * <my_account>@<my_domain.com> To set the active account, run: $ gcloud config set account `ACCOUNT`
- Führen Sie in Cloud Shell den folgenden Befehl aus, um zu prüfen, ob der gcloud-Befehl Ihr Projekt kennt:
gcloud config list project
Befehlsausgabe
[core] project = <PROJECT_ID>
Ist dies nicht der Fall, können Sie die Einstellung mit diesem Befehl vornehmen:
gcloud config set project <PROJECT_ID>
Befehlsausgabe
Updated property [core/project].
3. Cloud Profiler aufrufen
Klicken Sie in der Cloud Console auf „Profiler“, um die Profiler-Benutzeroberfläche aufzurufen. in der linken Navigationsleiste:
Alternativ können Sie die Suchleiste der Cloud Console verwenden, um die Profiler-Benutzeroberfläche aufzurufen. Geben Sie dazu einfach "Cloud Profiler" ein. und wählen Sie das gefundene Element aus. In beiden Fällen sollte die Profiler-Benutzeroberfläche mit der Meldung "Keine anzuzeigenden Daten" angezeigt werden. wie unten dargestellt. Das Projekt ist neu, daher wurden noch keine Profildaten erhoben.
Jetzt ist es an der Zeit, ein Profil zu erstellen.
4. Profil der Benchmark erstellen
Wir verwenden eine einfache synthetische Go-Anwendung, die auf GitHub verfügbar ist. Führen Sie im noch geöffneten Cloud Shell-Terminal den folgenden Befehl aus, während in der Profiler-Benutzeroberfläche die Meldung "No data to display" (Keine anzuzeigenden Daten) angezeigt wird:
$ go get -u github.com/GoogleCloudPlatform/golang-samples/profiler/...
Wechseln Sie dann in das Anwendungsverzeichnis:
$ cd ~/gopath/src/github.com/GoogleCloudPlatform/golang-samples/profiler/hotapp
Das Verzeichnis enthält die Datei „main.go“ -Datei, wobei es sich um eine synthetische Anwendung handelt, für die der Profiler-Agent aktiviert ist:
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)
}
...
}
Der Profiler-Agent erfasst standardmäßig CPU-, Heap- und Thread-Profile. Der hier angegebene Code ermöglicht die Erfassung von Mutex-Profilen (auch als „Konflikt“ bezeichnet).
Führen Sie nun das Programm aus:
$ go run main.go
Während das Programm läuft, erfasst der Profiler-Agent regelmäßig Profile der fünf konfigurierten Typen. Die Sammlung wird im Laufe der Zeit zufällig angeordnet (mit einer durchschnittlichen Rate von einem Profil pro Minute für jeden der Typen), sodass es bis zu drei Minuten dauern kann, um jeden der erfassten Typen abzurufen. Das Programm informiert Sie, wenn ein Profil erstellt wird. Die Meldungen werden in der obigen Konfiguration durch das Flag DebugLogging
aktiviert. Andernfalls wird der Agent im Hintergrund ausgeführt:
$ 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 ...
Die Benutzeroberfläche wird aktualisiert, kurz nachdem das erste Profil erfasst wurde. Danach wird sie nicht mehr automatisch aktualisiert. Sie müssen also die Profiler-Benutzeroberfläche manuell aktualisieren, um die neuen Daten zu sehen. Klicken Sie dazu zweimal in der Zeitintervallauswahl auf die Schaltfläche „Now“ (Jetzt):
Nach der Aktualisierung der Benutzeroberfläche sehen Sie in etwa Folgendes:
In der Profiltypauswahl werden die fünf verfügbaren Profiltypen angezeigt:
Sehen wir uns nun die einzelnen Profiltypen und einige wichtige UI-Funktionen an und führen dann einige Tests durch. Zu diesem Zeitpunkt benötigen Sie das Cloud Shell-Terminal nicht mehr, sodass Sie es beenden können, indem Sie Strg+C drücken und "exit" eingeben.
5. Profiler-Daten analysieren
Nachdem wir einige Daten gesammelt haben, sehen wir uns diese genauer an. Wir verwenden eine synthetische App (die Quelle ist auf GitHub verfügbar), die Verhaltensweisen simuliert, die für verschiedene Arten von Leistungsproblemen in der Produktion typisch sind.
CPU-intensiver Code
Wählen Sie den CPU-Profiltyp aus. Nachdem die UI geladen wurde, sehen Sie im Flame-Diagramm die vier Blattblöcke für die Funktion load
, die zusammen die gesamte CPU-Nutzung ausmachen:
Diese Funktion ist speziell so geschrieben, dass sie viele CPU-Zyklen verbraucht, indem eine enge Schleife ausgeführt wird:
main.go
func load() {
for i := 0; i < (1 << 20); i++ {
}
}
Die Funktion wird indirekt über busyloop
() über vier Aufrufpfade aufgerufen: busyloop
→ {foo1
, foo2
} → {bar
, baz
} → load
. Die Breite eines Funktionsfelds entspricht den relativen Kosten des spezifischen Aufrufpfads. In diesem Fall haben alle vier Pfade in etwa die gleichen Kosten. In einem echten Programm möchten Sie sich auf die Optimierung der für die Leistung wichtigsten Anrufpfade konzentrieren. Das Flame-Diagramm, das die teureren Pfade mit größeren Feldern visuell betont, macht diese Pfade leicht zu erkennen.
Mit dem Profildatenfilter können Sie die Anzeige weiter eingrenzen. Fügen Sie beispielsweise „Stacks anzeigen“ hinzu Filter mit „baz“ als Filterstring verwenden. Es sollte in etwa so aussehen wie im Screenshot unten, in dem nur zwei der vier Aufrufpfade für load()
zu sehen sind. Diese beiden Pfade sind die einzigen, die eine Funktion mit der Zeichenfolge „baz“ durchlaufen in seinem Namen enthält. Eine solche Filterung ist nützlich, wenn Sie sich auf einen Teil eines größeren Programms konzentrieren möchten (z. B. weil Sie nur einen Teil davon besitzen).
Speicherintensiver Code
Wechseln Sie jetzt zum Heap. Profiltyp. Entfernen Sie alle Filter, die Sie in vorherigen Tests erstellt haben. Sie sollten jetzt ein Flame-Diagramm sehen, in dem allocImpl
, aufgerufen von alloc
, als Hauptnutzer des Arbeitsspeichers in der App angezeigt wird:
Die Übersichtstabelle über dem Flame-Diagramm zeigt an, dass die Gesamtmenge des verwendeten Arbeitsspeichers in der App durchschnittlich etwa 57,4 MiB beträgt.Der Großteil davon wird von der Funktion allocImpl
zugewiesen. Angesichts der Implementierung dieser Funktion ist dies nicht überraschend:
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))
}
}
Die Funktion wird einmal ausgeführt. Dabei werden 64 MiB in kleineren Blöcken zugewiesen. Dann werden Verweise auf diese Blöcke in einer globalen Variablen gespeichert, um sie vor der Speicherbereinigung zu schützen. Beachten Sie, dass sich die vom Profiler angezeigte Speichermenge geringfügig von der von 64 MiB unterscheidet: Der Go-Heap-Profiler ist ein statistisches Tool, sodass die Messungen mit geringem Overhead erfolgen, aber nicht bytegenau sind. Seien Sie nicht überrascht, wenn Sie einen Unterschied wie hier sehen.
E/A-intensiver Code
Wenn Sie „Threads“ auswählen In der Profiltypauswahl wechselt die Anzeige zu einem Flame-Diagramm, in dem der größte Teil der Breite von den Funktionen wait
und waitImpl
eingenommen wird:
In der Zusammenfassung über dem Flame-Diagramm sehen Sie, dass es 100 Goroutinen gibt, deren Aufrufstack aus der Funktion wait
erweitert wird. Das ist genau richtig, vorausgesetzt, der Code, der diese Wartezeiten initiiert, sieht wie folgt aus:
main.go
func main() {
...
// Simulate some waiting goroutines.
for i := 0; i < 100; i++ {
go wait()
}
Dieser Profiltyp ist hilfreich, um nachzuvollziehen, ob das Programm unerwartete Zeit in Warteschleifen verbringt (z. B. E/A). Solche Aufrufstacks werden normalerweise nicht vom CPU-Profiler abgetastet, da sie keinen beträchtlichen Teil der CPU-Zeit verbrauchen. Die Option „Stacks ausblenden“ Filter mit Threads-Profilen, um beispielsweise alle Stacks auszublenden, die mit einem Aufruf von gopark,
enden, da es sich oft um inaktive Goroutinen handelt, die weniger interessant sind als solche, die auf die E/A warten.
Mit dem Profiltyp „Threads“ lassen sich auch Punkte im Programm ermitteln, an denen Threads lange auf einen Mutex warten, der einem anderen Teil des Programms gehört. Hierfür ist der folgende Profiltyp jedoch nützlicher.
Konfliktintensiver Code
Mit dem Profiltyp „Konflikt“ wird das am meisten „gewünschte“ Sperren des Programms. Dieser Profiltyp ist für Go-Programme verfügbar, muss aber explizit durch Angabe von „MutexProfiling: true
“ aktiviert werden im Agent-Konfigurationscode ein. Die Sammlung zeichnet unter dem Messwert „Konflikte“ auf, wie oft eine bestimmte Sperre beim Entriegeln durch eine Goroutine A von einer anderen Goroutine B darauf wartet, dass das Schloss entsperrt wird. Außerdem wird (unter dem Messwert „Delay“) die Zeit aufgezeichnet, die die blockierte Goroutine auf die Sperre gewartet hat. In diesem Beispiel gibt es einen einzelnen Konfliktstack und die Gesamtwartezeit für die Sperre betrug 10, 5 Sekunden:
Der Code, der dieses Profil generiert, besteht aus vier Goroutinen, die sich um einen Mutex kämpfen:
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. Zusammenfassung
In diesem Lab haben Sie gelernt, wie ein Go-Programm zur Verwendung mit Cloud Profiler konfiguriert werden kann. Außerdem haben Sie gelernt, wie Sie Leistungsdaten mit diesem Tool erfassen, ansehen und analysieren. Sie können Ihre neu erworbenen Kenntnisse jetzt auf die echten Dienste anwenden, die Sie auf der Google Cloud Platform ausführen.
7. Glückwunsch!
Sie haben gelernt, wie Sie Cloud Profiler konfigurieren und verwenden.
Weitere Informationen
- Cloud Profiler: https://cloud.google.com/profiler/
- Von Cloud Profiler verwendetes Go-Laufzeit-/pprof-Paket: https://golang.org/pkg/runtime/pprof/
Lizenz
Dieser Text ist mit einer Creative Commons Attribution 2.0 Generic License lizenziert.