Golang程序)官方正規快三81512073

前言

最近計劃用三篇文章講述一下Golang應用性能分析,本文是第一篇,先來介紹Go語言自帶的性能分析庫pprof怎麼使用,後面兩篇會講解怎麼用pprofEcho或者Gin框架開發的應用進行性能分析以及如何使用pprof對gRPC 服務進行性能分析。node

有興趣追更的同窗歡迎微信關注「網管叨bi叨」

Golang是一個很是注重性能的語言,所以語言的內置庫裏就自帶了性能分析庫pprofgit

性能分析和採集在計算機性能調試領域使用的術語是profile,或者有時候也會使用profiling表明性能分析這個行爲。因此pprof名字裏的prof來源於對單詞profile的縮寫,profile這個單詞的原意是畫像,那麼針對程序性能的畫像就是應用使用 CPU 和內存等等這些資源的狀況。都是哪些函數在使用這些資源?每一個函數使用的比例是多少?github

Go 語言中,主要關注的程序運行狀況包括如下幾種:golang

  • CPU profile:報告程序的 CPU 使用狀況,按照必定頻率去採集應用程序在 CPU 和寄存器上面的數據
  • Memory Profile(Heap Profile):報告程序的內存使用狀況
  • Block Profile:報告致使阻塞的同步原語的狀況,能夠用來分析和查找鎖的性能瓶頸
  • Goroutine Profile:報告 goroutines 的使用狀況,有哪些 goroutine,它們的調用關係是怎樣的

工具型應用的性能分析

若是你的應用是工具類應用,執行完任務就結束退出,可使用 runtime/pprof [1]庫。web

好比要想進行 CPU Profiling,能夠調用 pprof.StartCPUProfile() 方法,它會對當前應用程序進行CPU使用狀況分析,並寫入到提供的參數中(w io.Writer),要中止調用 StopCPUProfile() 便可。正則表達式

去除錯誤處理只須要三行內容,通常把部份內容寫在 main.go 文件中,應用程序啓動以後就開始執行:json

1.  f, err := os.Create(*cpuprofile)
    
2.  ...
    
3.  pprof.StartCPUProfile(f)
    
4.  defer pprof.StopCPUProfile()

應用執行結束後,就會生成一個文件,保存了咱們應用的 CPU使用狀況數據。api

想要得到內存的數據,直接使用 WriteHeapProfile 就行,不用 startstop 這兩個步驟了:瀏覽器

1.  f, err := os.Create(*memprofile)
    
2.  pprof.WriteHeapProfile(f)
    
3.  f.Close()

咱們的文章會把重點篇幅放在服務型應用的性能分析,因此關於工具型應用的性能分析就說這麼多。安全

服務型應用性能分析

若是你的應用是一直運行的,好比 web 應用或者gRPC服務等,那麼可使用 net/http/pprof 庫,它可以在應用提供 HTTP 服務時進行分析。

若是使用了默認的 http.DefaultServeMux(一般是代碼直接使用 http.ListenAndServe("0.0.0.0:8000", nil)),只須要在代碼中添加一行,匿名引用net/http/pprof

import _ "net/http/pprof"

若是你使用自定義的 ServerMux複用器,則須要手動註冊一些路由規則:

1.  r.HandleFunc("/debug/pprof/", pprof.Index)
    
2.  r.HandleFunc("/debug/pprof/heap", pprof.Index)
    
3.  r.HandleFunc("/debug/pprof/cmdline", pprof.Cmdline)
    
4.  r.HandleFunc("/debug/pprof/profile", pprof.Profile)
    
5.  r.HandleFunc("/debug/pprof/symbol", pprof.Symbol)
    
6.  r.HandleFunc("/debug/pprof/trace", pprof.Trace)

註冊完後訪問http://localhost/debug/pprof端點,它會獲得相似下面的頁面內容:

1.  Types of profiles available:
    
2.  Count Profile
    
3.  // 下面是一些可訪問的/debug/pprof/目錄下的路由
    
4.  2 allocs
    
5.  0 block
    
6.  0 cmdline
    
7.  36 goroutine
    
8.  2 heap
    
9.  0 mutex
    
10.  0 profile
    
11.  13 threadcreate
    
12.  0 trace
    
13.  full goroutine stack dump
    
14.  Profile Descriptions:
    

16.  // 下面是對上面那些路由頁面裏展現的性能分析數據的解釋
    
17.  allocs: A sampling of all past memory allocations
    
18.  block: Stack traces that led to blocking on synchronization primitives
    
19.  cmdline: The command line invocation of the current program
    
20.  goroutine: Stack traces of all current goroutines
    
21.  heap: A sampling of memory allocations of live objects. You can specify the gc GET parameter to run GC before taking the heap sample.
    
22.  mutex: Stack traces of holders of contended mutexes
    
23.  profile: CPU profile. You can specify the duration in the seconds GET parameter. After you get the profile file, use the go tool pprof command to investigate the profile.
    
24.  threadcreate: Stack traces that led to the creation of new OS threads
    
25.  trace: A trace of execution of the current program. You can specify the duration in the seconds GET parameter. After you get the trace file, use the go tool trace command to investigate the trace.

這個路徑下幾個須要重點關注的子頁面有:

  • /debug/pprof/profile:訪問這個連接會自動進行 CPU profiling,持續 30s,並生成一個文件供下載,能夠經過帶參數?=seconds=60進行60秒的數據採集
  • /debug/pprof/heap:Memory Profiling 的路徑,訪問這個連接會獲得一個內存 Profiling 結果的文件
  • /debug/pprof/block:block Profiling 的路徑
  • /debug/pprof/goroutines:運行的 goroutines 列表,以及調用關係

直接訪問這些頁面產生的性能分析數據咱們是分析不過來什麼的,Go在1.11版本後在它自帶的工具集go tool裏內置了pprof工具來分析由pprof庫生成的數據文件。

使用go tool pprof

經過上面的設置能夠獲取服務的性能數據後,接下來就可使用go tool pprof工具對這些數據進行分析和保存了,通常都是使用pprof經過HTTP訪問上面列的那些路由端點直接獲取到數據後再進行分析,獲取到數據後pprof會自動讓終端進入交互模式。在交互模式裏pprof爲咱們提供了很多分析各類指標的子命令,在交互模式下鍵入help後就會列出全部子命令。

NOTE pprof子命令的使用方法能夠參考 pprof --help 或者 pprof 文檔[2]。

CPU性能分析

進行CPU性能分析直接用go tool pprof訪問上面說的/debug/pprof/profile端點便可,等數據採集完會自動進入命令行交互模式。

1.  ➜ go tool pprof http://localhost/debug/pprof/profile
    
2.  Fetching profile over HTTP from http://localhost/debug/pprof/profile
    
3.  Saved profile in /Users/Kev/pprof/pprof.samples.cpu.005.pb.gz
    
4.  Type: cpu
    
5.  Time: Nov 15, 2020 at 3:32pm (CST)
    
6.  Duration: 30.01s, Total samples = 0
    
7.  No samples were found with the default sample value type.
    
8.  Try "sample_index" command to analyze different sample values.
    
9.  Entering interactive mode (type "help" for commands, "o" for options)
    
10.  (pprof)

默認採集時長是 30s,若是在 url 最後加上 ?seconds=60 參數能夠調整採集數據的時間爲 60s。

採集完成咱們就進入了一個交互式命令行,能夠對解析的結果進行查看和導出。能夠經過 help 來查看支持的子命令有哪些。

NOTE: 若是 pprof用性能數據生成分析圖的話、包括後面的go-torch火焰圖都依賴軟件 graphviz

Mac 用戶直接brew install graphviz就能安裝,其餘系統官網下載頁面也有提供安裝包,請訪問https://graphviz.org/download/

列出最耗時的地方

一個有用的命令是 topN,它列出最耗時間的地方:

1.  (pprof) top10
    
2.  130ms of 360ms total (36.11%)
    
3.  Showing top 10 nodes out of 180 (cum >= 10ms)
    
4.        flat  flat%   sum%        cum   cum%
    
5.        20ms  5.56%  5.56%      100ms 27.78%  encoding/json.(*decodeState).object
    
6.        20ms  5.56% 11.11%       20ms  5.56%  runtime.(*mspan).refillAllocCache
    
7.        20ms  5.56% 16.67%       20ms  5.56%  runtime.futex
    
8.        10ms  2.78% 19.44%       10ms  2.78%  encoding/json.(*decodeState).literalStore
    
9.        10ms  2.78% 22.22%       10ms  2.78%  encoding/json.(*decodeState).scanWhile
    
10.        10ms  2.78% 25.00%       40ms 11.11%  encoding/json.checkValid
    
11.        10ms  2.78% 27.78%       10ms  2.78%  encoding/json.simpleLetterEqualFold
    
12.        10ms  2.78% 30.56%       10ms  2.78%  encoding/json.stateBeginValue
    
13.        10ms  2.78% 33.33%       10ms  2.78%  encoding/json.stateEndValue
    
14.        10ms  2.78% 36.11%       10ms  2.78%  encoding/json.stateInString

每一行表示一個函數的信息。前兩列表示函數在 CPU 上運行的時間以及百分比;第三列是當前全部函數累加使用 CPU 的比例;第四列和第五列表明這個函數以及子函數運行所佔用的時間和比例(也被稱爲累加值 cumulative),應該大於等於前兩列的值;最後一列就是函數的名字。若是應用程序有性能問題,上面這些信息應該能告訴咱們時間都花費在哪些函數的執行上。

生成函數調用圖

pprof 不只能打印出最耗時的地方(top),還能列出函數代碼以及對應的取樣數據(list)、彙編代碼以及對應的取樣數據(disasm),並且能以各類樣式進行輸出,好比 svggifpng等等。

其中一個很是便利的是 web 命令,在交互模式下輸入 web,就能自動生成一個 svg 文件,並跳轉到瀏覽器打開,生成了一個函數調用圖(這個功能須要安裝graphviz後才能使用)。

圖中每一個方框對應應用程序運行的一個函數,方框越大表明函數執行的時間越久(函數執行時間會包含它調用的子函數的執行時間,但並非正比的關係);方框之間的箭頭表明着調用關係,箭頭上的數字表明被調用函數的執行時間。

這裏還要提兩個比較有用的方法,若是應用比較複雜,生成的調用圖特別大,看起來很亂,有兩個辦法能夠優化:

  • 使用 web funcName 的方式,只打印和某個函數相關的內容
  • 運行 go tool pprof 命令時加上 --nodefration 參數,能夠忽略內存使用較少的函數,好比--nodefration=0.05表示若是調用的子函數使用的 CPU、memory 不超過 5%,就忽略它,不要顯示在圖片中。

分析函數性能

想更細緻分析,就要精確到代碼級別了,看看每行代碼的耗時,直接定位到出現性能問題的那行代碼。pprof 也能作到,list 命令後面跟着一個正則表達式,就能查看匹配函數的代碼以及每行代碼的耗時:

1.  (pprof) list podFitsOnNode
    
2.  Total: 120ms
    
3.  ROUTINE ======================== k8s.io/kubernetes/plugin/pkg/scheduler.podFitsOnNode in /home/cizixs/go/src/k8s.io/kubernetes/_output/local/go/src/k8s.io/kubernetes/plugin/pkg/scheduler/generic_scheduler.go
    
4.           0       20ms (flat, cum) 16.67% of Total
    
5.           .          .    230:
    
6.           .          .    231:// Checks whether node with a given name and NodeInfo satisfies all predicateFuncs.
    
7.           .          .    232:func podFitsOnNode(pod *api.Pod, meta interface{}, info *schedulercache.NodeInfo, predicateFuncs map[string]algorithm.FitPredicate) (bool, []algorithm.PredicateFailureReason, error) {
    
8.           .          .    233:    var failedPredicates []algorithm.PredicateFailureReason
    
9.           .          .    234:    for _, predicate := range predicateFuncs {
    
10.           .       20ms    235:        fit, reasons, err := predicate(pod, meta, info)
    
11.           .          .    236:        if err != nil {
    
12.           .          .    237:            err := fmt.Errorf("SchedulerPredicates failed due to %v, which is unexpected.", err)
    
13.           .          .    238:            return false, []algorithm.PredicateFailureReason{}, err
    
14.           .          .    239:        }
    
15.           .          .    240:        if !fit {

內存性能分析

要想得到內存使用 Profiling 信息,只須要把數據源修改一下就行(對於 HTTP 方式來講就是修改 url 的地址,從 /debug/pprof/profile改爲 /debug/pprof/heap):

1.  ➜  go tool pprof  http://localhost/debug/pprof/heap 
    
2.  Fetching profile from http://localhost/debug/pprof/heap 
    
3.  Saved profile in 
    
4.  ......
    
5.  (pprof)

和 CPU Profiling 使用同樣,使用 top N 能夠打印出使用內存最多的函數列表:

1.  (pprof) top
    
2.  11712.11kB of 14785.10kB total (79.22%)
    
3.  Dropped 580 nodes (cum <= 73.92kB)
    
4.  Showing top 10 nodes out of 146 (cum >= 512.31kB)
    
5.        flat  flat%   sum%        cum   cum%
    
6.   2072.09kB 14.01% 14.01%  2072.09kB 14.01%  k8s.io/kubernetes/vendor/github.com/beorn7/perks/quantile.NewTargeted
    
7.   2049.25kB 13.86% 27.87%  2049.25kB 13.86%  k8s.io/kubernetes/pkg/api/v1.(*ResourceRequirements).Unmarshal
    
8.   1572.28kB 10.63% 38.51%  1572.28kB 10.63%  k8s.io/kubernetes/vendor/github.com/beorn7/perks/quantile.(*stream).merge
    
9.   1571.34kB 10.63% 49.14%  1571.34kB 10.63%  regexp.(*bitState).reset
    
10.   1184.27kB  8.01% 57.15%  1184.27kB  8.01%  bytes.makeSlice
    
11.   1024.16kB  6.93% 64.07%  1024.16kB  6.93%  k8s.io/kubernetes/pkg/api/v1.(*ObjectMeta).Unmarshal
    
12.    613.99kB  4.15% 68.23%  2150.63kB 14.55%  k8s.io/kubernetes/pkg/api/v1.(*PersistentVolumeClaimList).Unmarshal
    
13.    591.75kB  4.00% 72.23%  1103.79kB  7.47%  reflect.Value.call
    
14.    520.67kB  3.52% 75.75%   520.67kB  3.52%  k8s.io/kubernetes/vendor/github.com/gogo/protobuf/proto.RegisterType
    
15.    512.31kB  3.47% 79.22%   512.31kB  3.47%  k8s.io/kubernetes/pkg/api/v1.(*PersistentVolumeClaimStatus).Unmarshal

每一列的含義也是相似的,只不過從 CPU 使用時長變成了內存使用大小,就很少解釋了。

相似的,web 命令也能生成 svg 圖片在瀏覽器中打開,從中能夠看到函數調用關係,以及每一個函數的內存使用多少。

須要注意的是,默認狀況下,統計的是內存使用大小,若是執行命令的時候加上 --inuse_objects 能夠查看每一個函數分配的對象數;--alloc-space 查看分配的內存空間大小。

go-torch 和火焰圖

火焰圖(Flame Graph)是 Bredan Gregg 建立的一種性能分析圖表,由於它的樣子近似火焰而得名。上面的 profiling結果也轉換成火焰圖,這裏咱們要介紹一個工具:go-torch[3]。這是 uber 開源的一個工具,能夠直接讀取 pprofprofiling 數據,並生成一個火焰圖的 svg 文件。

img

火焰圖 svg 文件能夠經過瀏覽器打開,它對於調用圖的優勢是:能夠經過點擊每一個方塊來分析它上面的內容

火焰圖的調用順序從下到上,每一個方塊表明一個函數,它上面一層表示這個函數會調用哪些函數,方塊的大小表明了佔用 CPU 使用的長短。火焰圖的配色並無特殊的意義,默認的紅、黃配色是爲了更像火焰而已。

go-torch 工具的使用很是簡單,沒有任何參數的話,它會嘗試從 http://localhost/debug/pprof/profile 獲取 profiling 數據。它有三個經常使用的參數能夠調整:

  • -u --url:要訪問的 URL,這裏只是主機和端口部分
  • -s --suffix:pprof profile 的路徑,默認爲 /debug/pprof/profile
  • --seconds:要執行 profiling 的時間長度,默認爲 30s

要生成火焰圖,須要事先安裝 FlameGraph[4]工具,這個工具的安裝很簡單,只要把對應的可執行文件放到 $PATH 目錄下就行。

總結

今天的文章把Go語言的性能分析庫pprof的安裝和使用方法大致流程走了一遍,重點講解了一下最經常使用的幾個性能分析命令以及如何用pprof採集的profile數據找出程序裏最耗費性能的部分。相信有了pprof的幫助在遇到須要優化程序性能的時候咱們能有更多的參照指標從而有的放矢地對程序性能進行優化改進。

在使用pprof採集數據的時候必定要注意下面兩點:

  1. 只有在有訪問量的時候才能採集到這些性能指標數據。我是在公司的壓測環境對接口壓測時用pprof拿到的數據,若是你是在本地運行程序的話最好用Postman或者Jmeter這些工具作個簡單的併發訪問。
  2. 除非有健全的安全策略,不然最好只在測試和壓測環境加上pprof使用的那些路由,不要在生產環境上應用。

這篇文章就說這麼多,後面的文章會介紹怎麼在EchoGin框架下使用pprof,以及如何用pprof分析gRPC服務的性能。求關注、求點贊、求轉發!我是網管,會在這裏每週堅持輸出原創,咱們下期再見吧。

引用連接

[1]

runtime/pprof : https://golang.org/pkg/runtim...

[2]

pprof 文檔: https://github.com/google/ppr...

[3]

go-torch: https://github.com/uber/go-torch

[4]

FlameGraph: https://github.com/brendangre...

文章推薦

三種傳遞gRPC動態參數方式的使用體驗

看Kubernetes源碼,學習怎麼用Go實現調度隊列

  • END -
相關文章
相關標籤/搜索