Diagnostik

Dokumentasi ini ditujukan bagi pemrogram tingkat lanjut yang memiliki pengetahuan tentang bagaimana sebuah proses berjalan di tingkat CPU dan antar jaringan.

Pendahuluan

Ekosistem Go menyediakan sejumlah Application Programming Interface (API, atau antarmuka pemrograman aplikasi) dan perkakas untuk mendiagnosis permasalahan logika dan kinerja dalam program. Halaman dokumentasi ini memberi ringkasan perkakas yang ada dan membantu pengguna untuk memilih yang sesuai dengan permasalahannya.

Solusi dari diagnostik dapat dikategorikan ke dalam kelompok berikut:

  • Profiling: Perkakas profiling menganalisis kompleksitas dan biaya dari program seperti penggunaan memory dan fungsi yang sering dipanggil untuk mengidentifikasi bagian yang paling memakan sumber daya (siklus CPU atau memory).

  • Tracing: Tracing adalah salah satu cara untuk menganalisis latensi selama berlangsungnya sebuah pemanggilan (fungsi). Tracing menyediakan ringkasan berapa banyak latensi dari setiap komponen berkontribusi terhadap semua latensi dalam sistem. Tracing dapat dilakukan pada lebih dari satu proses.

  • Debugging: Debugging digunakan untuk menghentikan program dan memeriksa eksekusinya. Kondisi dan alur dari program dapat diverifikasi dengan cara debugging.

  • Statistik dan event runtime: Kumpulan dan analisis dari statistik dan event menyediakan ringkasan yang lebih umum dari program. Naik atau turunnya grafik dapat membantu mengidentifikasi perubahan dalam kecepatan pemrosesan, penggunaan sumber daya, dan kinerja.

Catatan: Beberapa perkakas diagnostik bisa saling mengganggu satu dengan yang lainnya. Sebagai contohnya, profiling pada penggunaan memory bisa mengubah profiling pada CPU dan profiling pada goroutine berpengaruh pada tracing latensi. Gunakan perkakas tersebut secara terisolasi (misalnya, satu per satu) untuk mendapatkan informasi yang lebih tepat.

Profiling

Profiling berguna untuk mengidentifikasi bagian kode yang sering dipanggil dan yang paling memakan sumber daya. Runtime menyediakan data profiling dalam format yang dapat dibaca oleh perkakas visualisasi pprof. Data profiling dapat dikumpulkan selama melakukan pengujian lewat go test atau dari endpoint yang disediakan oleh paket net/http/pprof. Pengguna harus mengumpulkan data profiling terlebih dahulu dan selanjutnya baru menggunakan perkakas pprof untuk menyaring dan membuat visualisasi dari jalur kode yang paling tinggi.

Profil yang disediakan oleh paket runtime/pprof:

  • cpu: Profil CPU menentukan bagian dari program yang paling banyak menghabiskan waktu eksekusi (bukan pada saat menunggu masukan atau keluaran, atau saat tertidur).

  • heap: Profil heap memberikan laporan sampel dari alokasi memory; digunakan untuk memonitor penggunaan memory yang sekarang dengan sebelumnya, dan untuk memeriksa adanya kebocoran memory.

  • threadcreate: Profil thread melaporkan bagian dari program yang menyebabkan terbuatnya sebuah thread baru pada sistem operasi.

  • goroutine: Profile goroutine melaporkan jejak stack dari semua goroutine.

  • block: Profil block memperlihatkan bagian mana dari goroutine yang terkunci menunggu sinkronisasi primitif (termasuk channel). Profil block tidak diaktifkan secara bawaan; gunakan runtime.SetBlockProfileRate untuk mengaktifkannya.

  • mutex: Profil mutex melaporkan ketidakcocokan penguncian. Saat CPU dirasa tidak sepenuhnya digunakan karena adanya ketidakcocokan mutex, gunakan profil ini. Profil mutex tidak diaktifkan secara bawaan, lihat runtime.SetMutexProfileFraction untuk mengaktifkannya.

Apakah ada jenis profil lain yang dapat digunakan dalam program Go?

Pada Linux, perkakas perf dapat digunakan untuk melakukan profiling program Go. Perkakas perf dapat memprofil dan mengulang kode cgo/SWIG dan kernel, sehingga dapat berguna untuk mengetahui lambatnya kinerja sampai ke tingkat kernel. Pada macOS, perkakas Instruments dapat digunakan untuk melakukan profiling program Go.

Bisakah memprofil program di tahap production?

Ya. Melakukan profiling program dalam sistem production adalah aman, namun mengaktifkannya (misalnya, profil CPU) menambah biaya. Kita akan melihat adanya penurunan kinerja. Biaya dari kinerja ini dapat dihitung dengan mengukur batas atas dari profil sebelum mengaktifkannya dalam tahap production.

profiling bisa dilakukan secara periodik terhadap program-program di production. Terutama dalam sistem yang memiliki banyak replika proses, memilih sebuah replika secara acak secara periodik adalah salah satu opsi yang aman. Pilih sebuah program dalam production sistem, lakukan profiling selama X detik untuk setiap Y detik dan simpan hasilnya untuk visualisasi dan analisis; kemudian ulangi secara periodik. Hasilnya bisa secara manual atau otomatis ditinjau untuk menemukan adanya masalah. Kumpulan profil bisa saling mengganggu satu sama lain, jadi direkomendasikan untuk mengumpulkan hanya satu jenis profil pada satu waktu.

Apakah cara terbaik untuk memvisualisasikan data profiling?

Go menyediakan visualisasi dalam bentuk teks, grafik, dan callgrind dari data profil menggunakan go tool pprof. Bacalah Profiling Go programs untuk melihat bagaimana cara melakukannya.

Daftar fungsi yang paling sering dipanggil dalam
format teks
Figure 1. Daftar fungsi yang paling sering dipanggil dalam format teks
Daftar fungsi yang paling sering dipanggil dalam
format grafik
Figure 2. Daftar fungsi yang paling sering dipanggil dalam bentuk grafik

Format weblist menampilkan bagian paling mahal dari sumber kode baris per baris dalam sebuah halaman HTML. Dalam contoh berikut, 530ms dihabiskan oleh runtime.concatstrings dan biaya dari setiap baris kode ditampilkan dalam daftar tersebut.

Daftar fungsi yang paling sering dipanggil
dalam format weblist
Figure 3. Daftar fungsi yang paling sering dipanggil dengan format weblist

Cara lain untuk memvisualisasikan data profil yaitu dengan grafik flame. Grafik flame bisa digunakan dengan berpindah-pindah dari satu path ke path yang lain, untuk melihat bagian kode tertentu lebih detail. Program pprof mendukung grafik flame ini.

Grafik flame
Figure 4. Grafik flame menampilkan visualisasi untuk mencari kode yang paling mahal

Apakah terbatas hanya menggunakan profil bawaan?

Kita dapat membuat profil kostum sendiri lewat pprof.Profile dan menggunakan perkakas yang sudah ada untuk memeriksanya.

Bisakah mengubah path dan port handler dari profiler (/debug/pprof/…​)?

Ya. Paket net/http/pprof meregistrasi handler-nya ke variabel global mux yang ada pada net/http, tetapi juga bisa diregister dengan menggunakan handler yang diekspor pada paket tersebut.

Sebagai contohnya, kode berikut akan melayani pprof.Profile pada port :7777 pada path "/custom_debug_path/profile":

package main

import (
	"log"
	"net/http"
	"net/http/pprof"
)

func main() {
	mux := http.NewServeMux()
	mux.HandleFunc("/custom_debug_path/profile", pprof.Profile)
	log.Fatal(http.ListenAndServe(":7777", mux))
}

Tracing (pelacakan)

Tracing adalah salah satu cara untuk menganalisis latensi lewat rangkaian pemanggilan fungsi dalam sebuah rantai siklus. Go menyediakan paket golang.org/x/net/trace sebagai backend tracing yang minimalis dan menyediakan pustaka instrumentasi dengan dashboard yang sederhana. Go juga menyediakan pelacak eksekusi untuk melacak event saat program berjalan.

Tracing digunakan untuk:

  • Mengukur dan menganalisis latensi aplikasi dalam proses.

  • Mengukur biaya dari pemanggilan fungsi tertentu dalam sebuah rantaian pemanggilan.

  • Mencari tahu penggunaan dan kinerja yang bisa ditingkatkan. Lambatnya pemrosesan terkadang tidak terlihat tanpa adanya data hasil dari tracing.

Dalam sistem monolitik, cukup mudah untuk mengumpulkan data diagnostik dari blok-blok pembangun program. Semua modul berada dalam satu proses dan memiliki sumber daya yang sama untuk melaporkan pencatatan, eror, dan informasi diagnostik lainnya. Saat sistem berkembang lebih dari satu proses dan mulai terdistribusi, maka akan lebih sulit untuk mengikuti sebuah pemanggilan server web dari front-end sampai back-end sampai respons dikembalikan lagi ke user. Di bagian inilah tracing terdistribusi memainkan peran penting untuk mengukur dan menganalisis sistem di tahap production.

Tracing terdistribusi adalah salah satu cara mengukur program untuk menganalisis latensi dari sebuah siklus permintaan (masukan) dari pengguna. Saat sistem telah terdistribusi dan perkakas profiling dan debugging yang konvensional tidak bisa menganalisisnya, maka perkakas tracing yang terdistribusi bisa digunakan untuk menganalisis kinerja dari permintaan pengguna dan Remote Procedure Call (RPC).

Tracing terdistribusi digunakan untuk:

  • Mengukur dan melakukan profiling latensi aplikasi dalam sistem yang besar.

  • Melacak semua RPC dalam satu siklus permintaan dari pengguna dan melihat isu-isu integrasi yang hanya terjadi pada tahap production.

  • Mencari tahu peningkatan kinerja yang bisa diterapkan pada sistem. Umumnya proses yang lambat tidak terlihat sebelum data tracing dikumpulkan.

Ekosistem Go menyediakan berbagai pustaka untuk melakukan tracing terdistribusi per sistem dan back-end yang berdiri sendiri.

Apakah ada cara otomatis menangkap pemanggilan fungsi dan melakukan pelacakan?

Go tidak menyediakan cara otomatis menghentikan setiap pemanggilan fungsi dan membuat rentang pelacakan. Masih diperlukan pengaturan kode secara manual untuk membuat, menghentikan, dan memberi rentang anotasi.

Bagaimana cara memperluas pelacakan sampai ke dalam pustaka Go?

Kita bisa memperluas identifikasi dan tag pelacakan menggunakan context.Context. Belum ada representasi umum atau kunci pelacakan kanonis dari header pelacakan. Setiap penyedia perkakas tracing bertanggung jawab menyediakan peralatan untuk memperluas ke dalam pustaka Go.

Apa saja event dari pustaka bawaan atau runtime yang bisa diikutkan selama pelacakan?

Pustaka bawaan dan runtime memiliki beberapa API untuk memberi notifikasi pada tingkat paling rendah dari event internal. Sebagai contohnya, httptrace.ClientTrace menyediakan API untuk menelusuri event paling bawah dalam sebuah siklus dari request ke luar. Proses untuk mendapatkan event runtime paling bawah dari pelacak eksekusi runtime yang membolehkan pengguna menentukan dan menyimpan event -nya sendiri masih dalam tahap pengembangan.

Debugging

Debugging adalah proses untuk mengidentifikasi kenapa program berjalan tidak sesuai yang diinginkan. Perkakas untuk debugging disebut dengan debugger. Debugger digunakan untuk memahami alur eksekusi program dan keadaan sekarang dari program. Ada beberapa cara dalam melakukan debugging; bagian ini hanya fokus dengan menambatkan debugger ke program dan kepada berkas core dump.

Pengguna Go umumnya menggunakan debugger berikut:

  • Delve: Delve adalah debugger yang mendukung konsep runtime dan tipe bawaan. Delve adalah debugger yang kaya dengan fitur dan tepercaya.

  • GDB: GDB menyediakan dukungan Go lewat compiler Go bawaan dan gccgo. Manajemen stack, thread, dan runtime memiliki aspek yang berbeda jauh dari model eksekusi yang diharapkan oleh GDB yang terkadang membingungkan debugger, bahkan pada program yang di- compile dengan gccgo. Walaupun GDB dapat dilakukan untuk men- debug program Go, ia tidak ideal dan bisa membingungkan.

Seberapa bagus debugger bekerja dengan program Go?

Compiler gc melakukan pengoptimalan seperti inlining fungsi dan registrasi variabel. Pengoptimalan ini terkadang membuat proses debugging menjadi sukar. Upaya untuk meningkatkan kualitas informasi DWARF yang dihasilkan untuk program yang dioptimalkan sedang dikembangkan saat ini. Sampai peningkatan tersebut siap digunakan, dianjurkan tidak mengaktifkan pengoptimalan saat membangun kode yang akan di- debug. Perintah berikut membuat paket yang tidak optimal:

$ go build -gcflags=all="-N -l"

Sebagai bagian dari peningkatan, Go 1.10 memperkenalkan opsi baru -dwarflocationlists. Opsi tersebut membuat compiler menambah daftar lokasi yang dapat membantu debugger bekerja pada program yang dioptimalkan. Perintah berikut membuat paket yang optimal tetapi dengan daftar lokasi DWARF:

$ go build -gcflags="-dwarflocationlists=true"

Antarmuka debugger mana yang dianjurkan?

Walaupun delve dan gdb menyediakan Command Line Interface (CLI, antarmuka baris perintah), kebanyakan integrasi editor dan IDE menyediakan antarmuka debugging yang spesifik.

Apakah memungkinkan melakukan debugging setelah program Go mati?

Berkas core dump yaitu berkas yang berisi memory dari proses yang berjalan dan statusnya. Berkas ini khusus digunakan untuk debugging setelah program mati dan untuk memahami keadaannya saat program berjalan. Dua kasus ini membuat debugging pada core dump menjadi diagnostik yang bagus untuk program yang crash dan untuk menganalisis servis di production. Untuk mendapatkan berkas ini dari sebuah program dan menggunakan delve atau gdb untuk debugging, lihat halaman wiki debugging core dump untuk panduan langkah demi langkah.

Statistik dan event runtime

Runtime menyediakan statistik dan pelaporan dari event internal untuk mendiagnosis kinerja dan permasalahan pada tingkat runtime.

Kita dapat memonitor statistik tersebut untuk memahami lebih lanjut tentang kesehatan dan kinerja dari program. Beberapa statistik dan status yang sering dimonitor:

  • runtime.ReadMemStats melaporkan grafik yang berkaitan dengan alokasi pada heap dan garbage collection. Statistik memory berguna untuk memonitor berapa banyak memory yang proses konsumsi, apakah proses menggunakan memory dengan benar, dan untuk menangkap adanya kebocoran memory.

  • debug.ReadGCStats memberikan statistik dari garbage collection (GC). Sangat berguna untuk melihat berapa banyak sumber daya yang digunakan saat terjadi GC. Ia juga melaporkan waktu terjadinya GC dan persentil dari lamanya GC.

  • debug.Stack mengembalikan stack yang ada sekarang. Pelacakan stack bermanfaat untuk melihat berapa banyak goroutine yang sedang berjalan, melihat apa yang goroutine lakukan, dan melihat apakah ada yang terkunci atau tidak.

  • debug.WriteHeapDump menghentikan eksekusi semua goroutine dan menulis isi heap ke dalam berkas. Isi heap adalah potret dari memory yang digunakan proses pada waktu tertentu. Ia berisi alokasi objek seperti goroutine, finalisasi, dan lainnya.

  • runtime.NumGoroutine mengembalikan jumlah goroutine. Nilai ini dimonitor untuk melihat apakah goroutine benar digunakan, atau mendeteksi adanya kebocoran goroutine.

Pelacak eksekusi

Go memiliki pelacak eksekusi runtime untuk menangkap sejumlah event pada runtime. Scheduling, syscall, garbage collection, ukuran heap, dan event lainnya dikumpulkan oleh runtime dan dapat divisualisasikan dengan perkakas go tool trace. Anda bisa membedah sebaiknya apa penggunaan CPU, dan apakah networking atau syscall yang menyebabkan terkunci goroutine.

Pelacakan berguna untuk:

  • Memahami bagaimana goroutine dieksekusi.

  • Memahami beberapa event runtime inti, seperti GC.

  • Mengidentifikasi buruknya eksekusi

Pelacakan tidak bagus untuk mengidentifikasi bagian-bagian seperti menganalisis penggunaan memory atau CPU yang terlalu banyak atau terlalu tinggi. Gunakan, perkakas profiling terlebih dahulu untuk mencari bagian tersebut.

Contoh pelacakan goroutine

Pada gambar di atas, visualisasi dari go tool trace memperlihatkan eksekusi mula-mula berjalan benar, dan kemudian mulai serial. Ia memberitahu bahwa kemungkinan ada penguncian pada sumber daya yang dibagi yang menyebabkan pemampatan.

Lihat go tool trace untuk mengumpulkan dan menganalisis pelacakan runtime.

GODEBUG

Runtime juga mengeluarkan event dan informasi tertentu bila variabel lingkungan GODEBUG diset.

  • GODEBUG=gctrace=1 mencetak even dari garbage collector, menghitung penggunaan memory dan lama berjalannya GC.

  • GODEBUG=schedtrace=X mencetak penskedulan event setiap X milidetik.

Variabel lingkungan GODEBUG juga bisa digunakan untuk menonaktifkan penggunaan ekstensi instruksi pada pustaka bawaan dan runtime.

  • GODEBUG=cpu.all=off menonaktifkan penggunaan semua ekstensi instruksi yang opsional.

  • GODEBUG=cpu.extension=off menonaktifkan penggunaan instruksi dari "extension" yang ditentukan. "extension" adalah nama dengan huruf kecil dari ekstensi instruksi seperti sse41 atau avx.