如果要分析 Linux上的 .NET程式 CPU 爆高,按以往的個性我肯定是抓個 dump 下來做事後分析,這種分析模式雖然不重但也不輕,還需要一定的底層知識,那有沒有傻瓜式的 CPU 爆高分析方式呢?
相信有很多朋友知道 B站713事件,最終就是用 perf 找到了那個讓 cpu 100% 的 lua 函數,截圖如下:
這裡我們也藉助 perf 這款工具實現 .NET程式的 cpu 爆高洞察, perf 就不過多介紹了,它是Linux系統
中提供的一款效能分析工具,類似 Windows 的 ETW 跟蹤,所以對他的瞭解是非常重要的。
這裡要注意的是我們並不直接使用,而是用微軟提供的基於 perf 的高層封裝工具 perfCollect,它不僅能收集 perf 能收集的事件,還能收集 .NET 中的 EventSource 事件,簡直是福音哈。
為了能夠讓 CPU 爆高,我們故意讓其中一個方法死迴圈,一個方法執行一段時間正常結束,參考程式碼如下:
namespace ConsoleApp1
{
internal class Program
{
static void Main(string[] args)
{
Task.Run(() =>
{
Test1();
});
Task.Run(() =>
{
Test2();
});
Console.ReadLine();
}
static void Test1()
{
int i = 1;
bool b = false;
while (i > 0)
{
b = !b;
}
}
static void Test2()
{
for (int i = 0; i < short.MaxValue; i++)
{
}
}
}
}
程式碼有了就可以 publish 到 centos 上,接下來在 /etc/profile
中增加一個環境變數 export COMPlus_PerfMapEnabled=1
,目的是讓 RIP 能夠成功解析到 C# 的方法名,截圖如下:
有了這些前置基礎,接下來就是把程式跑起來,用 htop 觀察下 CPU 的利用率。
[root@localhost data2]# vim /etc/profile
[root@localhost data2]# source /etc/profile
[root@localhost data2]# ls
ConsoleApp1 ConsoleApp1.deps.json ConsoleApp1.dll ConsoleApp1.pdb ConsoleApp1.runtimeconfig.json
[root@localhost data2]# dotnet ConsoleApp1.dll
剛才也說了 PerfCollect
是微軟提供的一款工具,整合了 perf
+ LTTng
兩塊,前者用於捕獲Linux系統級事件,後者用於捕獲 CoreCLR 以及 EventSource 事件,接下來就是下載,賦許可權,安裝。
[root@localhost data3]# curl -OL https://aka.ms/perfcollect
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
0 0 0 0 0 0 0 0 --:--:-- 0:00:02 --:--:-- 0
100 68590 100 68590 0 0 17540 0 0:00:03 0:00:03 --:--:-- 72658
[root@localhost data3]# chmod +x perfcollect
[root@localhost data3]# sudo ./perfcollect install
Loaded plugins: fastestmirror, langpacks
Loading mirror speeds from cached hostfile
epel/x86_64/metalink | 28 kB 00:00:00
* base: ftp.sjtu.edu.cn
* epel: d2lzkl7pfhq30w.cloudfront.net
* extras: mirror.lzu.edu.cn
* updates: mirror.lzu.edu.cn
base | 3.6 kB 00:00:00
docker-ce-stable | 3.5 kB 00:00:00
extras | 2.9 kB 00:00:00
packages-microsoft-com-prod | 1.5 kB 00:00:00
updates | 2.9 kB 00:00:00
Package perf-3.10.0-1160.92.1.el7.x86_64 already installed and latest version
Package zip-3.0-11.el7.x86_64 already installed and latest version
Package unzip-6.0-24.el7_9.x86_64 already installed and latest version
Nothing to do
LTTng already installed.
安裝好之後進行 10s 採集,採集完之後就會生成一個 ConsoleApp.trace.zip
檔案,輸出如下:
[root@localhost data3]# ./perfcollect collect ConsoleApp -collectsec 10
Collection started. Collection will automatically stop in 10 second(s). Press CTRL+C to stop early.
...STOPPED.
Starting post-processing. This may take some time.
Generating native image symbol files
...FINISHED
Saving native symbols
...FINISHED
Resolving JIT and R2R symbols
...FINISHED
Exporting perf.data file
...FINISHED
Compressing trace files
...FINISHED
Cleaning up artifacts
...FINISHED
Trace saved to ConsoleApp.trace.zip
最後把 ConsoleApp.trace.zip
複製到 Windows 平臺上用 PerfView 分析。
說句良心話,Perfview 真的是太強大了,什麼檔案都能從中提取有用資訊,比如 .dmp,.nettrace 還有這裡的 .zip ,用 Perfview 開啟 zip 之後,雙擊 CPU Stacks
選項,找到我們的 PID 程序即(.NET ThreadPool),截圖如下:
[root@localhost data3]# ps -ef | grep dotnet
root 6027 3171 99 23:33 pts/1 00:02:01 dotnet ConsoleApp1.dll
root 6529 5240 0 23:35 pts/2 00:00:00 grep --color=auto dotnet
雙擊開啟之後,去掉 GroupPats
資訊,可以看到佔比最高的是 Program::Test1()
方法。
有朋友可能要問這個資訊怎麼解讀呢?其實非常簡單,perf 也是按照 1ms 取樣一次的方式,所以 10s 的樣本數: 1w =10 * 1000
。
從上圖中可以看到,總的取樣到了 9999
個樣本,其中 Program::Test1()
佔據了 9993
,佔比高達 99.9%
,到這裡我們就定位出了原來這個函數就是 hot 函數。
不知道大家發現沒有,在 Windows 上很容易監控的東西,在 Linux 上就要麻煩的多,其實很容易理解,Windows 是微軟的, .NET 也是微軟的,自然是一等公民的存在。