go語言程式設計之旅筆記6
第六章: Go中的大殺器
-
簡介
介紹了PProf,trace,godebug,gops,metrrics,prometheus等等庫來進行效能監控等等的功能
-
PProf
- 使用net/http/pprof可以能方便的採集web服務在執行時的資料,直接import十分簡單
import ( _ "net/http/pprof" ) // 看看pprof的init發現注入了很多handler func init() { http.HandleFunc("/debug/pprof/", Index) http.HandleFunc("/debug/pprof/cmdline", Cmdline) http.HandleFunc("/debug/pprof/profile", Profile) http.HandleFunc("/debug/pprof/symbol", Symbol) http.HandleFunc("/debug/pprof/trace", Trace) }
可以通過瀏覽器或是互動式終端進行訪問,我選擇瀏覽器。-> IP地址/debug/pprof
瀏覽器有時效性,真要查問題還是用終端
go tool pprof ip/debug/pprof/profile?seconds=60
不同的路由對應的項,比如cpu/heap/goroutine等等,這就不貼了。
採集生成的profile檔案也是可以用web方式查閱的,go tool pprof -http=:6000 profile,如果報graphviz的錯說明要裝元件。
- 通過Lookup進行採集,這種方式需要寫code,支援6種類型,goroutine,threadcreate,heap,block,mutex
這種方式需要寫code,支援6種類型,goroutine,threadcreate,heap,block,mutex
package main import ( "io" "net/http" _ "net/http/pprof" "os" "runtime" "runtime/pprof" ) //go tool pprof http://localhost:6060/debug/pprof/profile?seconds=60 func main() { http.HandleFunc("/lookup/heap", func(w http.ResponseWriter, r *http.Request) { _ = pprofLookup(LookupHeap, os.Stdout) }) http.HandleFunc("/lookup/threadcreate", func(w http.ResponseWriter, r *http.Request) { _ = pprofLookup(LookupThreadcreate, os.Stdout) }) http.HandleFunc("/lookup/block", func(w http.ResponseWriter, r *http.Request) { _ = pprofLookup(LookupBlock, os.Stdout) }) http.HandleFunc("/lookup/goroutine", func(w http.ResponseWriter, r *http.Request) { _ = pprofLookup(LookupGoroutine, os.Stdout) }) _ = http.ListenAndServe("0.0.0.0:6060", nil) } type LookupType int8 const ( LookupGoroutine LookupType = iota LookupThreadcreate LookupHeap LookupAllocs LookupBlock LookupMutex ) func pprofLookup(lookupType LookupType, w io.Writer) error { var err error switch lookupType { case LookupGoroutine: p := pprof.Lookup("goroutine") err = p.WriteTo(w, 2) case LookupThreadcreate: p := pprof.Lookup("threadcreate") err = p.WriteTo(w, 2) case LookupHeap: p := pprof.Lookup("heap") err = p.WriteTo(w, 2) case LookupAllocs: p := pprof.Lookup("allocs") err = p.WriteTo(w, 2) case LookupBlock: p := pprof.Lookup("block") err = p.WriteTo(w, 2) case LookupMutex: p := pprof.Lookup("mutex") err = p.WriteTo(w, 2) } return err } func init() { runtime.SetMutexProfileFraction(1) runtime.SetBlockProfileRate(1) }
-
trace
詳細的使用方式/指標之類還是看書吧,字太多。
package main // --go run .\cmd\trace\main.go 2> trace.out // go build .\cmd\trace\main.go // .\main.exe // go tool trace trace.dat import ( "context" "fmt" "os" "runtime" "runtime/trace" "sync" ) func main() { // 為了看協程搶佔,這裡設定了一個cpu 跑 runtime.GOMAXPROCS(1) f, _ := os.Create("trace.dat") defer f.Close() _ = trace.Start(f) defer trace.Stop() ctx, task := trace.NewTask(context.Background(), "sumTask") defer task.End() var wg sync.WaitGroup wg.Add(10) for i := 0; i < 10; i++ { // 啟動10個協程,只是做一個累加運算 go func(region string) { defer wg.Done() // 標記region trace.WithRegion(ctx, region, func() { var sum, k int64 for ; k < 1000000000; k++ { sum += k } fmt.Println(region, sum) }) }(fmt.Sprintf("region_%02d", i)) } wg.Wait() }
-
godebug
這個同樣太長不寫了。環境變數可以通過vscode寫入launch.json檔案中,比如
"env": { "GODEBUG":"scheddetail=1,schedtrace=1000" }
-
程序診斷工具 gops
go get -u github.com/google/gops package main import ( "log" "net/http" "github.com/google/gops/agent" ) func main() { if err := agent.Listen(agent.Options{}); err != nil { log.Fatal("agent listen err : %v", err) } http.HandleFunc("/hello", func(w http.ResponseWriter, r *http.Request) { _, _ = w.Write([]byte("golang projecct")) }) _ = http.ListenAndServe(":6060", http.DefaultServeMux) }
gops help檢視命令,也是有很多,不細寫
-
metrics 使用expvar標準庫
code中自定義了型別,並且封裝成了gin中介軟體,可以和gin聯動了
package main import ( "expvar" _ "expvar" "fmt" "net/http" "runtime" "time" "github.com/gin-gonic/gin" ) //http://localhost:6060/debug/vars func main() { router := NewRouter() http.HandleFunc("/hello", func(w http.ResponseWriter, r *http.Request) { appleCounter.Add(1) _, _ = w.Write([]byte("go project")) }) _ = http.ListenAndServe(":6060", router) } var ( appleCounter *expvar.Int GOMAXPROCSMetrics *expvar.Int upTimeMetrice *upTimeVar ) type upTimeVar struct { value time.Time } func (v *upTimeVar) Set(date time.Time) { v.value = date } func (v *upTimeVar) Add(duration time.Duration) { v.value = v.value.Add(duration) } func (v *upTimeVar) String() string { return v.value.Format(time.UnixDate) } func init() { upTimeMetrice = &upTimeVar{value: time.Now().Local()} expvar.Publish("uptime", upTimeMetrice) appleCounter = expvar.NewInt("apple") GOMAXPROCSMetrics = expvar.NewInt("GOMAXPROCS") GOMAXPROCSMetrics.Set(int64(runtime.NumCPU())) } func Expvar(c *gin.Context) { c.Writer.Header().Set("content-type", "application/json; charset=utf-8") first := true report := func(key string, value interface{}) { if !first { fmt.Fprintf(c.Writer, ",\n") } first = false if str, ok := value.(string); ok { fmt.Fprintf(c.Writer, "%q: %q", key, str) } else { fmt.Fprintf(c.Writer, "%q: %v", key, value) } } fmt.Fprintf(c.Writer, "{\n") expvar.Do(func(kv expvar.KeyValue) { report(kv.Key, kv.Value) }) fmt.Fprintf(c.Writer, "\n}\n") } func NewRouter() *gin.Engine { r := gin.New() r.Use(gin.Logger()) r.Use(gin.Recovery()) r.GET("/debug/vars", Expvar) return r }
-
Pronmetheus
Pronmetheus還是很出名的。四大指標型別Counter累計指標,Histogram一定時間範圍內取樣,Gauge可任意變化的指標,Summary也是一定時間內取樣,他有仨指標,分位數分佈/樣本值大小總和/樣本總數。
go get -u github.com/prometheus/client_golang package main import ( "net/http" "github.com/prometheus/client_golang/prometheus/promhttp" ) func main() { http.Handle("/metrics", promhttp.Handler()) http.HandleFunc("/hello", func(w http.ResponseWriter, r *http.Request) { _, _ = w.Write([]byte("go project")) }) _ = http.ListenAndServe(":6060", http.DefaultServeMux) }
啟動後訪問 :6060/metrics
-
其他 包括附錄
- 逃逸分析,有很多情況會造成逃逸,這個還是要經驗的,初學者的我還是用命令最直接。
// 用-gcflags檢視逃逸分析過程 go build -gcflags '-m -l' main.go // 反編譯命令檢視 go tool compile -S main.go
- Go modules
去年剛開始學時用過gopath,那玩意兒能坑死,還是modules適合老子。
// go get後的模組會快取在gopath/pkg/mod 和gopath/pkg/sumdb中,如果需要清理可以執行 go clean -modcache
- 為什麼defer才能recover
panic結構是一個連結串列,defer結構中包含了一個對panic結構的引用,在gopanic(interface{})方法中,會觸發defer,如果沒有defer則會直接跳出,就不會進行接來下的recover了。
還有一些defer了也無法recover的方法,比如fatalthrow,fatalpanic等,比如併發寫入map時就會引起fatalthrow。
10種panic方法:陣列切片越界,空指標呼叫,過早關閉HTTP響應體(resp.body.calose()),除零,向關閉的chan傳送訊息,重複關閉chan,關閉未初始化的的chan,使用未初始化的map,跨goroutine處理panic,sync計數負數。
- 讓golang更適應docker
// 這個庫可以根據cgroup的掛載資訊來修改GOMAXPROCS核數 import _ "go.uber.org/automaxprocs"
完!