Menganalisis performa produksi dengan Stackdriver Profiler

Meskipun developer aplikasi klien dan developer web frontend biasanya menggunakan alat seperti CPU Profiler Android Studio atau alat pembuatan profil yang disertakan dalam Chrome untuk meningkatkan performa kodenya, teknik yang setara belum sepenuhnya dapat diakses atau diadopsi dengan baik oleh mereka yang bekerja pada layanan backend. Stackdriver Profiler menghadirkan kemampuan yang sama ini bagi developer layanan, terlepas dari apakah kode mereka berjalan di Google Cloud Platform atau di tempat lainnya.

Alat ini mengumpulkan informasi penggunaan CPU dan alokasi memori dari aplikasi produksi. Atribut tersebut mengaitkan informasi dengan kode sumber aplikasi, membantu Anda mengidentifikasi bagian aplikasi yang paling banyak menggunakan resource, dan menjelaskan karakteristik performa kode. Biaya overhead yang rendah dari teknik pengumpulan yang digunakan oleh alat ini membuatnya cocok untuk penggunaan berkelanjutan di lingkungan produksi.

Dalam codelab ini, Anda akan mempelajari cara menyiapkan Stackdriver Profiler untuk program Go dan mempelajari jenis insight tentang performa aplikasi yang dapat dihadirkan alat ini.

Yang akan Anda pelajari

  • Cara mengonfigurasi program Go untuk pembuatan profil dengan Stackdriver Profiler.
  • Cara mengumpulkan, melihat, dan menganalisis data performa dengan Stackdriver Profiler.

Yang Anda butuhkan

  • Project Google Cloud Platform
  • Browser, seperti Chrome atau Firefox
  • Pemahaman tentang editor teks Linux standar, seperti Vim, EMAC, atau Nano

Bagaimana Anda akan menggunakan tutorial ini?

Hanya membacanya Membacanya dan menyelesaikan latihan

Bagaimana penilaian Anda terhadap pengalaman dengan Google Cloud Platform?

Pemula Menengah Mahir

Penyiapan lingkungan mandiri

Jika belum memiliki Akun Google (Gmail atau Google Apps), Anda harus membuatnya. Login ke Google Cloud Platform console (console.cloud.google.com) dan buat project baru:

Screenshot dari 10-02-2016 12:45:26.png

Ingat project ID, nama unik di semua project Google Cloud (maaf, nama di atas telah digunakan dan tidak akan berfungsi untuk Anda!) Project ID tersebut selanjutnya akan dirujuk di codelab ini sebagai PROJECT_ID.

Selanjutnya, Anda harus mengaktifkan penagihan di Cloud Console untuk menggunakan resource Google Cloud.

Menjalankan melalui codelab ini tidak akan menghabiskan biaya lebih dari beberapa dolar, tetapi bisa lebih jika Anda memutuskan untuk menggunakan lebih banyak resource atau jika Anda membiarkannya berjalan (lihat bagian "pembersihan" di akhir dokumen ini).

Pengguna baru Google Cloud Platform memenuhi syarat untuk mendapatkan uji coba gratis senilai $300.

Google Cloud Shell

Meskipun Google Cloud dapat dioperasikan dari jarak jauh dari laptop Anda, untuk mempermudah penyiapan dalam codelab ini, kami akan menggunakan Google Cloud Shell, lingkungan command line yang berjalan di Cloud.

Mengaktifkan Google Cloud Shell

Dari GCP Console, klik ikon Cloud Shell di toolbar kanan atas:

Kemudian klik "Mulai Cloud Shell":

Hanya perlu waktu beberapa saat untuk penyediaan dan terhubung ke lingkungan:

Mesin virtual ini berisi semua alat pengembangan yang Anda perlukan. Layanan ini menawarkan direktori beranda tetap sebesar 5 GB, dan berjalan di Google Cloud, sehingga sangat meningkatkan performa dan autentikasi jaringan. Sebagian besar pekerjaan Anda di lab ini dapat dilakukan hanya dengan browser atau Google Chromebook.

Setelah terhubung ke Cloud Shell, Anda akan melihat bahwa Anda sudah diautentikasi dan project sudah ditetapkan ke PROJECT_ID.

Jalankan perintah berikut di Cloud Shell untuk mengonfirmasi bahwa Anda telah diautentikasi:

gcloud auth list

Output perintah

Credentialed accounts:
 - <myaccount>@<mydomain>.com (active)
gcloud config list project

Output perintah

[core]
project = <PROJECT_ID>

Jika tidak, Anda dapat menyetelnya dengan perintah ini:

gcloud config set project <PROJECT_ID>

Output perintah

Updated property [core/project].

Di Cloud Console, buka UI Profiler dengan mengklik "Profiler" di menu navigasi sebelah kiri:

Atau, Anda dapat menggunakan kotak penelusuran Cloud Console untuk membuka UI Profiler: cukup ketik "Stackdriver Profiler" dan pilih item yang ditemukan. Anda akan melihat UI Profiler dengan pesan "Tidak ada data untuk ditampilkan" seperti di bawah ini. Project ini baru, sehingga belum ada data profiling yang dikumpulkan.

Sekarang saatnya membuat profil!

Kami akan menggunakan aplikasi Go sintetis sederhana yang tersedia di GitHub. Di terminal Cloud Shell yang masih terbuka (dan saat pesan "Tidak ada data untuk ditampilkan" masih ditampilkan di UI Profiler), jalankan perintah berikut:

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

Lalu, beralihlah ke direktori aplikasi:

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

Direktori ini berisi file "main.go" yang merupakan aplikasi sintetis yang mengaktifkan agen pembuatan profil:

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)
        }
        ...
}

Agen profiling mengumpulkan profil CPU, heap, dan thread secara default. Kode di sini memungkinkan pengumpulan profil mutex (juga dikenal sebagai "contention").

Sekarang, jalankan program:

$ go run main.go

Saat program berjalan, agen pembuatan profil akan mengumpulkan profil dari lima jenis yang dikonfigurasi secara berkala. Koleksi akan diacak dari waktu ke waktu (dengan rata-rata kecepatan satu profil per menit untuk setiap jenis), jadi mungkin perlu waktu hingga tiga menit untuk mengumpulkan setiap jenis. Program ini akan memberi tahu Anda saat membuat profil. Pesan diaktifkan oleh flag DebugLogging pada konfigurasi di atas; jika tidak, agen akan berjalan tanpa suara:

$ 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
...

UI akan diperbarui sendiri segera setelah profil pertama dikumpulkan. Ini tidak akan diupdate otomatis setelah itu, jadi untuk melihat data baru, Anda perlu memuat ulang UI Profiler secara manual. Untuk melakukannya, klik tombol Now di alat pilih interval waktu dua kali:

Setelah UI dimuat ulang, Anda akan melihat tampilan seperti ini:

Pemilih jenis profil menampilkan lima jenis profil yang tersedia:

Sekarang, mari kita tinjau setiap jenis profil dan beberapa kemampuan UI penting, lalu melakukan beberapa eksperimen. Pada tahap ini, terminal Cloud Shell tidak diperlukan lagi. Anda dapat keluar dari terminal dengan menekan CTRL-C dan mengetik "exit".

Setelah mengumpulkan beberapa data, mari kita lihat lebih dekat. Kami menggunakan aplikasi sintetis (sumbernya tersedia di GitHub) yang menyimulasikan perilaku standar dari berbagai jenis masalah performa dalam produksi.

Kode yang menggunakan CPU secara intensif

Pilih jenis profil CPU. Setelah UI memuatnya, Anda akan melihat empat blok daun untuk fungsi load dalam grafik api, yang secara kolektif memperhitungkan semua konsumsi CPU:

Fungsi ini ditulis secara khusus untuk menggunakan banyak siklus CPU dengan menjalankan loop ketat:

main.go

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

Fungsi ini dipanggil secara tidak langsung dari busyloop() melalui empat jalur panggilan: busyloop → {foo1, foo2} → {bar, baz} → load. Lebar kotak fungsi mewakili biaya relatif dari jalur panggilan tertentu. Dalam hal ini, keempat jalur tersebut memiliki biaya yang kurang lebih sama. Dalam program yang sebenarnya, Anda ingin fokus pada mengoptimalkan jalur panggilan yang paling penting dalam hal performa. Grafik api, yang secara visual menekankan jalur yang lebih mahal dengan kotak yang lebih besar, membuat jalur tersebut mudah diidentifikasi.

Anda dapat menggunakan filter data profil untuk menyaring tampilan lebih lanjut. Misalnya, coba tambahkan "Tampilkan stack" filter yang menentukan "baz" sebagai string filter. Anda akan melihat sesuatu seperti screenshot di bawah, dengan hanya dua dari empat jalur panggilan ke load() yang ditampilkan. Kedua jalur ini adalah satu-satunya yang melewati fungsi dengan string "baz" dalam namanya. Pemfilteran tersebut berguna saat Anda ingin berfokus pada subbagian program yang lebih besar (misalnya, karena Anda hanya memiliki sebagian).

Kode yang menggunakan banyak memori

Sekarang beralih ke jenis profil "Heap". Pastikan untuk menghapus filter apa pun yang Anda buat di eksperimen sebelumnya. Sekarang Anda akan melihat grafik api di mana allocImpl, yang dipanggil oleh alloc, ditampilkan sebagai konsumen utama memori dalam aplikasi:

Tabel ringkasan di atas grafik flame menunjukkan bahwa jumlah total memori yang digunakan dalam aplikasi rata-rata ~57,4 MiB, yang sebagian besar dialokasikan oleh fungsi allocImpl. Hal ini tidak mengejutkan, mengingat implementasi fungsi ini:

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))
        }
}

Fungsi ini dieksekusi satu kali, mengalokasikan 64 MiB dalam potongan yang lebih kecil, lalu menyimpan pointer ke potongan tersebut dalam variabel global untuk melindunginya dari pembersihan sampah memori. Perhatikan bahwa jumlah memori yang ditampilkan sebagai digunakan oleh profiler sedikit berbeda dari 64 MiB: profiler heap Go adalah alat statistik, sehingga pengukurannya rendah rendah tetapi tidak akurat byte. Jangan kaget saat melihat perbedaan ~10% seperti ini.

Kode yang berfokus pada IO

Jika Anda memilih "Threads" di pemilih jenis profil, layar akan beralih ke grafik api dengan sebagian besar lebar diambil oleh fungsi wait dan waitImpl:

Pada ringkasan di atas grafik flame, Anda dapat melihat bahwa ada 100 goroutine yang menumbuhkan stack panggilan dari fungsi wait. Benar sekali, mengingat kode yang memulai proses tunggu ini akan terlihat seperti ini:

main.go

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

Jenis profil ini berguna untuk memahami apakah program menghabiskan waktu yang tidak terduga dalam waktu tunggu (seperti I/O). Stack panggilan tersebut biasanya tidak akan diambil sampelnya oleh CPU profiler karena tidak menggunakan bagian waktu CPU yang signifikan. Anda akan sering menggunakan "Sembunyikan stack" filter dengan profil Thread - misalnya, untuk menyembunyikan semua stack yang diakhiri dengan panggilan ke gopark, karena sering kali memiliki status tidak aktif dan kurang menarik dibandingkan dengan yang menunggu di I/O.

Jenis profil thread juga dapat membantu mengidentifikasi titik dalam program, tempat thread menunggu mutex yang dimiliki oleh bagian lain dari program dalam jangka waktu yang lama, tetapi jenis profil berikut lebih berguna untuk hal itu.

Kode Intensif Pertentangan

Jenis profil Pertentangan mengidentifikasi kunci yang paling "dicari" dalam program. Jenis profil ini tersedia untuk program Go tetapi harus diaktifkan secara eksplisit dengan menentukan "MutexProfiling: true" dalam kode konfigurasi agen. Koleksi bekerja dengan merekam (di bawah metrik "Pertentangan") berapa kali kunci tertentu, ketika tidak terkunci oleh aviutine A, memiliki Furine B lagi yang menunggu kunci dibuka. Fitur ini juga mencatat (di bawah metrik "Tunda") untuk waktu goroutine yang diblokir menunggu kunci. Dalam contoh ini, terdapat satu tumpukan pertentangan dan total waktu tunggu untuk kunci adalah 11,03 detik:

Kode yang menghasilkan profil ini terdiri dari 4 huruf yang dipertaruhkan melalui mutex:

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)
        }
}

Di lab ini, Anda telah mempelajari cara program Go dapat dikonfigurasi untuk digunakan dengan Stackdriver Profiler. Anda juga telah mempelajari cara mengumpulkan, melihat, dan menganalisis data performa dengan alat ini. Anda kini dapat menerapkan keterampilan baru ke layanan sebenarnya yang Anda jalankan di Google Cloud Platform.

Anda telah mempelajari cara mengonfigurasi dan menggunakan Stackdriver Profiler.

Pelajari Lebih Lanjut

Lisensi

Karya ini dilisensikan berdasarkan Lisensi Umum Creative Commons Attribution 2.0.