.netcore利用perf分析高cpu使用率

找不到一個滿意的暱稱發表於2021-01-13

教程:官方文件 https://docs.microsoft.com/zh-cn/dotnet/core/diagnostics/debug-highcpu?tabs=linux
環境:Linux、Docker、.NET Core 3.1 SDK及更高版本
示例程式碼:https://github.com/dotnet/samples/tree/master/core/diagnostics/DiagnosticScenarios

利用dotnet-dump分析docker容器記憶體洩露 跟上篇一樣,繼續使用官方示例程式碼演示CPU佔用情況。

一 在宿主機執行perf

依舊跟隨官網教程的步伐,唯一不同的是,我的示例執行在容器中。
還是跟上一篇的觀念一樣,當生產環境出現問題時一定要當場把問題樣本儲存下來用於事後分析。
在宿主機中安裝好perf工具,然後捕獲容器內佔用過高的dotnet程式,最後生成火焰圖,整個過程一氣呵成非常簡單;但問題就出現在生成的火焰圖根本不顯示JIT生成的函式,只顯示perf-PID.map,如下圖

問題的原因可能是:捕獲程式時無法生成CPU地址和JIT生成函式的對映檔案,生成火焰圖時找不到待分析程式所依賴的庫。
接下來,依然本著不汙染宿主機的原則,選擇在容器內完成一切工作。

二 容器內安裝perf

1,重新構建映象

修改Dockerfile,在原先的基礎上加上一個環境變數COMPlus_PerfMapEnabled=1,它的作用是使.NET Core應用在/tmp目錄中有許可權建立map檔案,perf使用此map檔案按名稱將CPU地址對映到JIT生成的函式。
進入到容器執行export COMPlus_PerfMapEnabled=1,然後perf是無法建立map檔案的,所以我們必須在啟動容器時就要加上。

FROM mcr.microsoft.com/dotnet/core/aspnet:3.1-buster-slim AS base
WORKDIR /app
EXPOSE 80

COPY  bin/Release/netcoreapp3.1 .

ENV COMPlus_PerfMapEnabled=1

ENTRYPOINT ["dotnet", "DiagnosticScenarios.dll"]

修改完儲存之後,構建映象,啟動容器

[root@localhost Diagnostic_scenarios_sample_debug_target]# docker build -t dumptest .
Sending build context to Docker daemon   1.47MB
...
Successfully tagged dumptest:latest
[root@localhost Diagnostic_scenarios_sample_debug_target]# docker rm -f diagnostic
diagnostic
[root@localhost Diagnostic_scenarios_sample_debug_target]# docker run --name diagnostic --privileged=true -p 888:80 -d dumptest
d2a7037858ed6af0676990bc56ae2b73519c3a8c073db06af639ef30d9588628

2,下載火焰圖生成指令碼

進入容器之前,可以先做一個準備工作,將生成火焰圖需要用到的pl指令碼先clone到宿主機(以後會反覆用到),當然這一步也可以到容器內操作。

[root@localhost ~]# cd ~
[root@localhost ~]# git clone --depth=1 https://github.com/BrendanGregg/FlameGraph
Cloning into 'FlameGraph'...
Resolving deltas: 100% (61/61), done.
[root@localhost ~]# ls
Diagnostic_scenarios_sample_debug_target  FlameGraph

又get到新的知識點git clone --depth=1
加了個--depth=1的好處就是限制clone的深度,不會下載git協作的歷史記錄,這樣可以大大加快克隆的速度。
接下來把FlameGraph拷貝到容器內,然後進入容器:

[root@localhost ~]# docker cp FlameGraph diagnostic:/app/FlameGraph
[root@localhost ~]# docker exec -it diagnostic bash
root@822a953f9f53:/app# ls
DiagnosticScenarios            DiagnosticScenarios.dll  DiagnosticScenarios.runtimeconfig.dev.json  FlameGraph                          Microsoft.AspNetCore.Mvc.NewtonsoftJson.dll  Newtonsoft.Json.dll  appsettings.Development.json
DiagnosticScenarios.deps.json  DiagnosticScenarios.pdb  DiagnosticScenarios.runtimeconfig.json      Microsoft.AspNetCore.JsonPatch.dll  Newtonsoft.Json.Bson.dll                     Properties           appsettings.json

3,安裝linux-perf

root@822a953f9f53:/app# apt update && apt install -y linux-perf vim
...
root@822a953f9f53:/app# perf --version
/usr/bin/perf: line 13: exec: perf_3.10: not found
E: linux-tools-3.10 is not installed.

安裝成功後檢視perf版本會提示perf_3.10: not found和linux-tools-3.10 is not installed。
linux-tools誤導,一頓抓瞎折騰半天之後,找遍全網沒有任何有用的資訊。
索性回到perf本身看看/usr/bin/perf第13行到底是個啥,結果發現好像是核心版本和perf不匹配的原因,uname -r得到的結果是Linux 3.10...
然而在/usr/bin目錄下並沒有找到perf_3.10只有perf_4.19,直接把下圖中的perf_$version替換成perf_4.19。


root@822a953f9f53:/app# vi /usr/bin/perf
root@822a953f9f53:/usr/bin# perf --version
perf version 4.19.160

三 CPU佔用分析

1,perf record捕獲程式

先執行能引發高CPU的連結60000ms, http://192.168.0.161:888/api/diagscenario/highcpu/60000
然後在CPU飆升期間執行perf record捕獲程式,容器裡面只執行了一個應用,所以程式id為1,這裡就不在查詢了。

root@1834a15133ed:/app# perf record -p 1 -g -- sleep 20
[ perf record: Woken up 72 times to write data ]
[ perf record: Captured and wrote 17.330 MB perf.data (80022 samples) ]
root@1834a15133ed:/app# ls
DiagnosticScenarios            DiagnosticScenarios.pdb                     FlameGraph                                   Newtonsoft.Json.Bson.dll  appsettings.Development.json
DiagnosticScenarios.deps.json  DiagnosticScenarios.runtimeconfig.dev.json  Microsoft.AspNetCore.JsonPatch.dll           Newtonsoft.Json.dll       appsettings.json
DiagnosticScenarios.dll        DiagnosticScenarios.runtimeconfig.json      Microsoft.AspNetCore.Mvc.NewtonsoftJson.dll  Properties                perf.data

捕獲完成之後會在當前目錄下生成一個perf.data檔案,在/tmp目錄下生成perf-PID.mapperfinfo-PID.map檔案。

root@d2a7037858ed:/app# cd /tmp
root@d2a7037858ed:/tmp# ls
clr-debug-pipe-1-32163779-in  clr-debug-pipe-1-32163779-out  dotnet-diagnostic-1-32163779-socket  perf-1.map  perfinfo-1.map

2,生成火焰圖

之前cp到/app目錄下的FlameGraph專案在此刻派上用場。

root@d2a7037858ed:/tmp# cd /app
root@d2a7037858ed:/app# perf script | FlameGraph/stackcollapse-perf.pl | FlameGraph/flamegraph.pl > flamegraph.svg
...
root@d2a7037858ed:/app# ls
DiagnosticScenarios            DiagnosticScenarios.pdb                     FlameGraph                                   Newtonsoft.Json.Bson.dll  appsettings.Development.json  perf.data
DiagnosticScenarios.deps.json  DiagnosticScenarios.runtimeconfig.dev.json  Microsoft.AspNetCore.JsonPatch.dll           Newtonsoft.Json.dll       appsettings.json
DiagnosticScenarios.dll        DiagnosticScenarios.runtimeconfig.json      Microsoft.AspNetCore.Mvc.NewtonsoftJson.dll  Properties                flamegraph.svg

生成完之後取出flamegraph.svg檔案,到瀏覽器開啟。
找到了這個又高又寬的“大平頂”,是由highcpu這個Action導致。
關於如何看懂火焰圖,可以自行百度,底部參考也有給出基礎教程連結。


示例專案中就這一個方法在執行,所在找起來非常簡單。
實際專案中如果業務複雜、函式多、呼叫鏈深話,開啟圖就是一片密密麻麻。


參考:
Linux效能優化實戰學習筆記:第十七講 https://www.cnblogs.com/luoahong/p/10833689.html
如何讀懂火焰圖? - 阮一峰的網路日誌 http://www.ruanyifeng.com/blog/2017/09/flame-graph.html

相關文章