Semua alat pada section ini — debugger, log, pprof, tracer, race detector — berbagi satu batas: alat-alat itu menghentikan program, merekam berkas, atau mengunci CPU di mesin pengembang. Production menuntut pendekatan terbalik: aplikasi terus melayani traffic, sementara diagnostik diambil dari proses yang sedang berjalan lewat endpoint HTTP dan metrik runtime. Go menyiapkan semuanya di standard library: net/http/pprof mengekspos profil, runtime/metrics menyediakan angka untuk dipantau, dan slog mencatat jejak operasional. Pada chapter penutup ini kita menggabungkan ketiganya dalam satu program yang bisa dites tanpa membuka port.
Endpoint /debug/pprof/ dari net/http/pprof #
Cukup dengan satu import kosong _ "net/http/pprof", package tersebut mendaftarkan seluruh handler diagnosis ke http.DefaultServeMux. Aplikasi production biasanya menjalankan http.ListenAndServe pada port internal; untuk contoh yang bisa diverifikasi tanpa jaringan, berkas contoh.go di bawah halaman memakai httptest untuk menyajikan permintaan langsung ke mux yang sama:
package main
import (
"fmt"
"log/slog"
"net/http"
"net/http/httptest"
_ "net/http/pprof" // mendaftarkan handler /debug/pprof/ ke DefaultServeMux
"os"
"runtime"
"runtime/metrics"
"strings"
)
// buangWaktu menjadikan log contoh ini deterministik.
func buangWaktu(groups []string, a slog.Attr) slog.Attr {
if a.Key == slog.TimeKey {
return slog.Attr{}
}
return a
}
// bacaMetrikUint64 membaca satu metrik runtime/metrics bertipe uint64.
func bacaMetrikUint64(nama string) uint64 {
for _, deskripsi := range metrics.All() {
if deskripsi.Name == nama {
sampel := []metrics.Sample{{Name: nama}}
metrics.Read(sampel)
if sampel[0].Value.Kind() == metrics.KindUint64 {
return sampel[0].Value.Uint64()
}
}
}
return 0
}
// susunLog membangun sekumpulan baris log menjadi satu buffer.
func susunLog(jumlahBaris int) string {
var b strings.Builder
for i := range jumlahBaris {
fmt.Fprintf(&b, "req-%06d user=Budi status=200\n", i)
}
return b.String()
}
func main() {
// Gauge konfigurasi: memastikan tuning GC yang aktif di production.
fmt.Println("metrik /gc/gogc:percent =", bacaMetrikUint64("/gc/gogc:percent"))
// Pemeriksaan endpoint pprof tanpa membuka port: permintaan disajikan
// langsung ke DefaultServeMux memakai httptest.
for _, path := range []string{"/debug/pprof/", "/debug/pprof/heap", "/debug/pprof/goroutine", "/debug/pprof/allocs"} {
req := httptest.NewRequest(http.MethodGet, path, nil)
rec := httptest.NewRecorder()
http.DefaultServeMux.ServeHTTP(rec, req)
fmt.Printf("GET %-22s -> %d (%s)\n", path, rec.Code, rec.Header().Get("Content-Type"))
}
// Laju alokasi: selisih metrik kumulatif sekitar beban kerja tetap.
awal := bacaMetrikUint64("/gc/heap/allocs:bytes")
bufferLog := susunLog(110_000)
runtime.GC() // paksakan GC agar akuntansi alokasi diperbarui sebelum dibaca
akhir := bacaMetrikUint64("/gc/heap/allocs:bytes")
fmt.Println("panjang buffer log:", len(bufferLog), "byte")
fmt.Println("laju alokasi 110 ribu baris log:", (akhir-awal)/1024/1024, "MiB")
handler := slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{ReplaceAttr: buangWaktu})
logger := slog.New(handler)
logger.Info("diagnostik aktif", "endpoint", "/debug/pprof", "goroutine", runtime.NumGoroutine())
}Output yang Dihasilkan:
metrik /gc/gogc:percent = 100
GET /debug/pprof/ -> 200 (text/html; charset=utf-8)
GET /debug/pprof/heap -> 200 (application/octet-stream)
GET /debug/pprof/goroutine -> 200 (application/octet-stream)
GET /debug/pprof/allocs -> 200 (application/octet-stream)
panjang buffer log: 3520000 byte
laju alokasi 110 ribu baris log: 21 MiB
level=INFO msg="diagnostik aktif" endpoint=/debug/pprof goroutine=1Keempat endpoint menjawab 200 dengan content-type yang benar: halaman indeks berupa HTML, tiga profil lain berupa binary protobuf — itulah isi application/octet-stream — yang bisa langsung diunduh dan dibuka dengan go tool pprof. Analisisnya sama persis dengan chapter-chapter sebelumnya: go tool pprof -top https://contoh.internal/debug/pprof/heap (nama host sesuai lingkungan Anda) menghasilkan tabel peringkat yang sudah kita latih baca.
Peta Endpoint dan Metrik #
| Endpoint | Isi | Catatan pemakaian |
|---|---|---|
/debug/pprof/ | Halaman indeks semua profil | Berisi tautan + jumlah sampel per profil |
/debug/pprof/heap | Heap profile (snapshot) | Analisis inuse/alloc dengan -sample_index |
/debug/pprof/allocs | Profil alokasi kumulatif | Default alloc_space |
/debug/pprof/goroutine | Stack semua goroutine | Deteksi kebocoran goroutine |
/debug/pprof/profile?seconds=30 | CPU profile 30 detik | Merekam sambil melayani traffic |
/debug/pprof/trace?seconds=5 | Execution trace 5 detik | Untuk masalah penjadwalan |
/debug/pprof/block, /debug/pprof/mutex | Profil blocking dan kontensi | Aktif setelah runtime.SetBlockProfileRate/SetMutexProfileFraction |
Tabel di atas adalah kontrak operasional: tim dapat menarik profil tanpa restart, tanpa akses shell, kapan pun insiden terjadi. Endpoint profile dan trace memakai parameter seconds karena keduanya harus merekam selama interval waktu — berbeda dari snapshot yang selesai seketika.
Metrik Runtime untuk Pemantauan #
Package runtime/metrics menyediakan puluhan metrik bernama — semuanya bisa ditemukan lewat metrics.All() dan dibaca massal lewat metrics.Read dengan slice metrics.Sample, jauh lebih murah daripada ReadMemStats yang menghentikan dunia. Contoh kita membaca dua metrik: /gc/gogc:percent sebagai konfirmasi tuning GC yang aktif, dan /gc/heap/allocs:bytes sebagai alokasi kumulatif. Selisih dua pembacaan menghasilkan laju alokasi — 21 MiB untuk 110 ribu baris log pada program deterministik ini; di production, laju yang melonjak setelah deploy adalah alarm dini GC pressure.
Bab Metrics melanjutkan metrik runtime ini ke tumpukan pemantauan penuh (Prometheus, OpenTelemetry), sementara Health, Readiness, dan Liveness membahas endpoint kesehatan yang sebaiknya dipisahkan dari endpoint diagnosis.
Dengan sembilan chapter di belakang kita — dari membaca pesan compiler sampai mengekspor diagnostik dari proses hidup — alur diagnosis yang utuh sudah lengkap: pahami galat, amati eksekusi, ukur waktu dan memori, buru race, lalu pantau hasilnya di production. Section berikutnya membawa performa terukur ini ke langkah berikutnya: optimasi yang sadar-angka.
Referensi #
- Package net/http/pprof
— dokumentasi resmi endpoint
/debug/pprof/dan cara memasangnya. - Package runtime/metrics — daftar metrik, tipe nilai, dan pola pembacaan.
- Diagnostics — strategi resmi debugging, profiling, dan tracing Go.
- Package runtime
—
NumGoroutinedan fungsi runtime lain untuk diagnostik.
Berkas Sumber
Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.
contoh.go
Unduh berkaspackage main
import (
"fmt"
"log/slog"
"net/http"
"net/http/httptest"
_ "net/http/pprof" // mendaftarkan handler /debug/pprof/ ke DefaultServeMux
"os"
"runtime"
"runtime/metrics"
"strings"
)
// buangWaktu menjadikan log contoh ini deterministik.
func buangWaktu(groups []string, a slog.Attr) slog.Attr {
if a.Key == slog.TimeKey {
return slog.Attr{}
}
return a
}
// bacaMetrikUint64 membaca satu metrik runtime/metrics bertipe uint64.
func bacaMetrikUint64(nama string) uint64 {
for _, deskripsi := range metrics.All() {
if deskripsi.Name == nama {
sampel := []metrics.Sample{{Name: nama}}
metrics.Read(sampel)
if sampel[0].Value.Kind() == metrics.KindUint64 {
return sampel[0].Value.Uint64()
}
}
}
return 0
}
// susunLog membangun sekumpulan baris log menjadi satu buffer.
func susunLog(jumlahBaris int) string {
var b strings.Builder
for i := range jumlahBaris {
fmt.Fprintf(&b, "req-%06d user=Budi status=200\n", i)
}
return b.String()
}
func main() {
// Gauge konfigurasi: memastikan tuning GC yang aktif di production.
fmt.Println("metrik /gc/gogc:percent =", bacaMetrikUint64("/gc/gogc:percent"))
// Pemeriksaan endpoint pprof tanpa membuka port: permintaan disajikan
// langsung ke DefaultServeMux memakai httptest.
for _, path := range []string{"/debug/pprof/", "/debug/pprof/heap", "/debug/pprof/goroutine", "/debug/pprof/allocs"} {
req := httptest.NewRequest(http.MethodGet, path, nil)
rec := httptest.NewRecorder()
http.DefaultServeMux.ServeHTTP(rec, req)
fmt.Printf("GET %-22s -> %d (%s)\n", path, rec.Code, rec.Header().Get("Content-Type"))
}
// Laju alokasi: selisih metrik kumulatif sekitar beban kerja tetap.
awal := bacaMetrikUint64("/gc/heap/allocs:bytes")
bufferLog := susunLog(110_000)
runtime.GC() // paksakan GC agar akuntansi alokasi diperbarui sebelum dibaca
akhir := bacaMetrikUint64("/gc/heap/allocs:bytes")
fmt.Println("panjang buffer log:", len(bufferLog), "byte")
fmt.Println("laju alokasi 110 ribu baris log:", (akhir-awal)/1024/1024, "MiB")
handler := slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{ReplaceAttr: buangWaktu})
logger := slog.New(handler)
logger.Info("diagnostik aktif", "endpoint", "/debug/pprof", "goroutine", runtime.NumGoroutine())
}