一:背景
准备开个系列来聊一下 PerfView 这款工具,熟悉我的朋友都知道我喜欢用 WinDbg,这东西虽然很牛,但也不是万能的,也有一些场景他解决不了或者很难解决,这时候借助一些其他的工具来辅助,是一个很不错的主意。
很多朋友喜欢在项目中以记录日志的方式来监控项目的流转情况,其实 CoreCLR 也是这样的,参考如下代码:
void gc_heap::fix_allocation_context (alloc_context* acontext, BOOL for_gc_p,BOOL record_ac_p)
{dprintf (3, ("Fixing allocation context %Ix: ptr: %Ix, limit: %Ix",(size_t)acontext,(size_t)acontext->alloc_ptr, (size_t)acontext->alloc_limit));
}void gc_heap::background_sweep()
{//concurrent_print_time_delta ("finished with mark and start with sweep");concurrent_print_time_delta ("Sw");dprintf (2, ("---- (GC%d)Background Sweep Phase ----", VolatileLoad(&settings.gc_index)));//block concurrent allocation for large objectsdprintf (3, ("lh state: planning"));
}void gc_heap::background_ephemeral_sweep()
{dprintf (3, ("bgc ephemeral sweep"));
}
那这些日志会送到哪里去呢,当然是 Windows 的 ETW 了,那有什么工具可以方便提取呢?PerfView 就是这么其中一款。
这一篇我们做一个 CPU 爆高的场景下如何寻找 热点函数
的例子,看看如何用 PerfView 去挖。
二:PerfView 寻找热点函数
很多场景下的 CPU 高,是因为某个或者某几个线程在高频的执行某个方法,有可能是死循环,有可能是陷入了CPU密集型方法内,解决这个问题一个好的思路就是对 CPU 进行采样,比如我的 12 核电脑。
0:000> !cpuid
CP F/M/S Manufacturer MHz0 6,5,2 25921 6,5,2 25922 6,5,2 25923 6,5,2 25924 6,5,2 25925 6,5,2 25926 6,5,2 25927 6,5,2 25928 6,5,2 25929 6,5,2 2592
10 6,5,2 2592
11 6,5,2 2592
1. 如何采样
采样的原理就是周期性的去看下当前的 CPU 核中运行的几个线程正在执行什么方法, 当采样到了几万个或者几十万个样本之后,就可以对这些采集到的方法进行分组排序来找到 topN,那些 TopN 的方法自然就是导致 CPU 爆高可能的诱因。
windbg 有一个 !running
命令可以用来显示当前处理器中正在运行的线程。
lkd> !runningSystem Processors: (0000000000000fff)Idle Processors: (000000000000065e)Prcbs Current (pri) Next (pri) Idle0 fffff80268a33180 ffffaf8ec9bd8080 (15) fffff8026b526600 ................5 ffffd900e1700180 ffffaf8eca36b080 ( 8) ffffd900e170b340 ................7 ffffd900e1900180 ffffaf8ec2f18080 ( 8) ffffd900e190b340 ................8 ffffd900e1a00180 ffffd900e1a0b340 ( 0) ffffd900e1a0b340 ................11 ffffd900e1d00180 ffffaf8eb6bee080 ( 8) ffffd900e1d0b340 ................
接下来写一个程序,让其中一个线程无限循环,然后通过 PerfView 去找这个热点。
internal class Program{static void Main(string[] args){Task.Run(() => Test1()); //Test1 故意死循环Task.Run(() => Test2()); //Test2 是一个正常函数Console.WriteLine("我是主线程!");Console.ReadLine();}static void Test1(){var i = 10;var b = true;while (i > 0){b = !b;}}static void Test2(){for (int i = 0; i < 10000; i++){var j = string.Join(",", Enumerable.Range(0, 100));}Console.WriteLine("Test执行结束");}}
2. 使用 PerfView 采样
点击菜单中的 Collect -> Collect
,弹出如下面板。
在这个面板中,选中如下几项。
1)CPU Samples:
设置对 CPU 进行采样。
2)CPU Sample Interval Msec
设置采样的频次是 1ms/次。
3)Max Collect Sec
设置总共采样多少秒,这里设置为 15 秒。
4).NET Symbol Collection
用来从微软符号服务器上拉取符号,和采样无关哈。
上面都设置完毕后,就可以点击 Start Collection
采集了,不出意外的话,15s 之后你就会看到如下的截图。
接下来点击 CPU Stacks
,在弹出的面板中选中我们的 程序
,双击之后就可以打开如下面板。
从图中可以看到,当前采样了 15622
个样本,符合 15 * 1000
,接下来把上面的 GroupPats
默认分组给清掉,截图如下:
从图中可以看到当前 Test1()
方法在 15622 个样本中占比 97.9%
,命中次数高达 15290 次,很明显这是一个绝对的 热点函数
,接下来就是翻源码为什么 Test1 这么高频?
如果你想看鸡肋的 火焰图
,可以点击 Flame Graph
列表项。
好了,本篇就先聊这么多吧。