PerfView專題 (第十二篇):對 C# 下的 SDK 類庫進行監控(大結局)

2022-08-28 21:01:02

一:背景

本篇是我們系列文章的最後一篇,前面的文章中大多是在 CLR Runtime 以及 OS 層面進行監控來發現各種可疑的程式問題,除了這兩個層面,其實我們還可以對 SDK 中一些類進行洞察,比如說:

  1. ArrayPool

  2. Http

  3. Socket

  4. Task

更多資料可以看下:https://docs.microsoft.com/en-us/dotnet/core/diagnostics/well-known-event-providers

接下來就來個簡單的拋磚引玉

二:如何洞察

1. ArrayPool 監控

之所以對 ArrayPool 感興趣,主要還是因為在分析 Dump 的過程中,遇到過幾起 LOH 碎片化問題,比如使用第三方模板生成引擎生成 Html 導致大量臨時性 char[], byte[],終導致 LOH 破敗不堪,所以最後給出的建議是使用這種池化的 ArrayPool,如果可以監控池的租借情況,那是不是挺好的? 哈哈,還真有這樣的 ETW,截圖如下:

為了方便講解,先上一段簡單的測試程式碼:


    internal class Program
    {
        static void Main(string[] args)
        {
            var shared = ArrayPool<int>.Shared;

            var rentedArray = shared.Rent(10);

            for (int i = 0; i < 10; i++)
            {
                rentedArray[i] = i + 1;
            }

            for (int j = 0; j < 10; j++)
            {
                Console.WriteLine(rentedArray[j]);
            }

            shared.Return(rentedArray);

            Console.ReadKey();
        }
    }

接下來啟動 Perfview,在 Additional Providers 上輸入:


*System.Buffers.ArrayPoolEventSource:::@StacksEnabled=true

然後開啟 Start Collection 觀察 Array 的租借情況,稍等片刻後,在 Event 中搜尋 ArrayPool 可以看到相關的 ETW 事件,截圖如下:

Rent 列的 bufferSize="16" 中可知,當前租借了一個 size=16 的陣列。


HasStack="True" ThreadID="15,060" ProcessorNumber="10" bufferId="32,854,180" bufferSize="16" poolId="27,252,167" bucketId="-1" 

因為開啟了 Stack 功能,可以在 Time MSec 列上右鍵選擇 Open Any Stacks,在彈窗中可以輕鬆找到這個 rent 所在的程式碼,截圖如下:

2. Http 監控

對 Http 的監控也是由於最近遇到了一個比較頭疼的 dump 有感而發的,一個朋友的 dump 出現了 cpu 100% 的情況,我分析下來發現是程式在短時間內出現了大量的 Http Exception,進一步排查懷疑是 sdk 裡面的異常,由於被吞了所以上層獲取不到,也就找不到是第三方 sdk 哪裡的程式碼塊出的問題。

這裡的找不到或者很難找到是在 WinDBG 場景下,其實藉助 PerfView 還是比較好發現的,途徑就是開啟 System.Net.Http ETW 事件,它內建了 14 個,太強大了,截圖如下:

為了方便講述,先上一段測試程式碼。


    internal class Program
    {
        static void Main(string[] args)
        {
            for (int i = 0; i < 5; i++)
            {
                GetString();
            }

            Console.ReadLine();
        }

        static async void GetString()
        {
            try
            {
                HttpClient client = new HttpClient();

                var html = await client.GetStringAsync("https://cnblogs1.com");

                Console.WriteLine(html);
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
            }
        }
    }

這段程式碼會丟擲異常,然後在 catch 中給吞掉了,因為拋了異常,就可以觀察它的 RequestFailed 事件,然後找到對應的 RequestStart 事件,再觀察它的呼叫棧即可。

接下來在 PerfView 中設定 *System.Net.Http:::@StacksEnabled=true,再開啟收集按鈕,稍等片刻點選 Event 面板,蒐集 Http 事件,截圖如下:

從面板中可以清晰的觀察到當前有 5 個請求失敗,並且還帶了關聯的 ActivityID, 接下來可以找 ActivityID=/#18920/1/29/ 對應的 Request/Start 事件。

然後在 Time MSec 列上右鍵點選 Open Any Stacks 按鈕,可以輕鬆的看到,那個 Request/Start 事件是 GetString() 方法觸發的,截圖如下:

3. 總結

總的來說,在 .NET 偵錯領域,讓 PerfView 適當的配合 WinDbg,真的可以 如虎添翼 ,好了,本系列就先寫到這裡,感謝朋友們對本系列的持續關注。