.NET定位CPU使用率太高問題

摘要:css

當一個.net應用在生產環境CPU忽然居高不下,如何快速準確的定位問題所在,而且對實時業務影響最小化?如何不抓Dump也不用live debug就能夠知道你的應用在作什麼?如何確認你的應用是因爲哪一個線程的執行形成的CPU升高,該線程正在執行什麼代碼?html

分析:
CPU升高的緣由有不少,
 一、有時候應用的負載大了,CPU天然會受業務請求的增長和增高;
 二、有時候由於GC回收使用了太高的CPU資源;
 三、有時候是某個線程執行的代碼在某種狀況下陷入了死循環;
 四、有時候是由於鎖爭用太激烈,某資源上的鎖釋放後,等待的線程去搶鎖引發的;
 五、有時候是由於線程太多,上下文切換太頻繁引發的。
 六、每秒拋出太多的Exception。
咱們一一分析
 一、咱們通常會用一些計數器來觀察實際的應用的負載狀況和併發請求量,好比每秒接受多少請求等,因此業務量增大引發的CPU高,很容易肯定。
 二、GC使用的CPU百分比有專門的計數器,一看便知。
 三、若是某段代碼陷入了死循環引發的CPU高,只抓Dump看~*e!clrstack和!runaway仍是不太好定位問題,
  a)、通常都是連續抓幾個dump,而後用!runaway來看哪些線程的用戶態時間的差很大,而後再去看該線程的調用棧。
  b)、錄製Thread\Thread Id和Thread\% Processor Time計數器,同時抓dump,從計數器裏找到CPU消耗高的線程ID,而後從dump裏看調用棧和調用棧的參數本地變量等。
 四、鎖爭用也有相關的.NET計數器能夠直接看。
 五、每一個進程的線程數,每秒上下文切換次數也能夠有直接的計數器可看。
 六、每秒拋出的異常也有直接的計數器可看。c#

思路:
一、從上面看到也就是第3種地排查比較費勁,並且抓DUMP有時候容易把服務抓S,若是是一個有狀態的服務,抓死服務後果很嚴重,因此咱們得想出一種更輕量級的方法去獲取服務的每一個線程的調用棧。其實CLR自己有一些用於支持調試的接口,都是Com的,但.NET對此有一些包裝,能夠用c#來使用這些調試API,其中固然包括附加到進程,獲取全部線程調用棧的功能。該DLL在.net sdk裏,叫MdbgCore.dll。
二、另外獲取計數器.NET也有現成的類,上篇帖子介紹過了。
三、.NET對進程的管理也有一些API,能夠獲取一個進程的線程數,每一個線程的啓動時間,用戶態時間,線程狀態,優先級等信息。併發

有了以上幾個知識點,咱們就能夠綜合起來寫一個比較智能化定位高CPU問題的工具。dom

CPU高的DEMO
咱們先寫一個CPU高的DEMO,A方法由於有sleep因此不會太消耗CPU,而B方法沒有Sleep,執行一個浮點運算,因此會形成CPU升高(佔用一個CPU的資源)。 ide

using System;
using System.Threading;

namespace HightCPUDemo
{
    internal class Program
    {
        private static void Main(string[] args)
        {
            new Thread(A).Start();
            new Thread(B).Start();
            Console.ReadKey();
        }

        private static void A(object state)
        {
            while (true)
            {
                Thread.Sleep(1000);
            }
        }

        private static void B(object state)
        {
            while (true)
            {
                double d = new Random().NextDouble()*new Random().NextDouble();
            }
        }
    }
}

代碼實現工具

咱們的目標在該程序運行的時候,找出B方法,並確認它就是引發CPU高度緣由,代碼以下,不太想解釋了,代碼不復雜,重在思路。 單元測試

完整代碼實現
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Text;
using System.Threading;
using Microsoft.Samples.Debugging.MdbgEngine;

internal class MyThreadInfo
{
    public string CallStack = "null";
    public string Id;
    public string ProcessorTimePercentage;
    public string StartTime;
    public string UserProcessorTime;

    public override string ToString()
    {
        return
            string.Format(
                @"<table style=""width: 1000px;""><tr><td style=""width: 80px;"">ThreadId</td><td style=""width: 200px;"">{0}</td><td style=""width: 140px;"">% Processor Time</td><td>{1}</td></tr>
<tr><td style=""width: 80px;"">UserProcessorTime</td><td style=""width: 200px;"">{2}</td><td style=""width: 140px;"">StartTime</td><td>{3}</td></tr><tr><td colspan=""4"">{4}</td></tr></table>",
                Id, ProcessorTimePercentage, UserProcessorTime, StartTime, CallStack);
    }
}

internal class MyThreadCounterInfo
{
    public PerformanceCounter IdCounter;
    public PerformanceCounter ProcessorTimeCounter;

    public MyThreadCounterInfo(PerformanceCounter counter1, PerformanceCounter counter2)
    {
        IdCounter = counter1;
        ProcessorTimeCounter = counter2;
    }
}

internal class Program
{
    // Skip past fake attach events. 
    private static void DrainAttach(MDbgEngine debugger, MDbgProcess proc)
    {
        bool fOldStatus = debugger.Options.StopOnNewThread;
        debugger.Options.StopOnNewThread = false; // skip while waiting for AttachComplete

        proc.Go().WaitOne();
        Debug.Assert(proc.StopReason is AttachCompleteStopReason);

        debugger.Options.StopOnNewThread = true; // needed for attach= true; // needed for attach

        // Drain the rest of the thread create events.
        while (proc.CorProcess.HasQueuedCallbacks(null))
        {
            proc.Go().WaitOne();
            Debug.Assert(proc.StopReason is ThreadCreatedStopReason);
        }

        debugger.Options.StopOnNewThread = fOldStatus;
    }

    // Expects 1 arg, the pid as a decimal string
    private static void Main(string[] args)
    {
        try
        {
            int pid = int.Parse(args[0]);

            var sb = new StringBuilder();
            Process process = Process.GetProcessById(pid);
            var counters = new Dictionary<string, MyThreadCounterInfo>();
            var threadInfos = new Dictionary<string, MyThreadInfo>();

            sb.AppendFormat(
                @"<html><head><title>{0}</title><style type=""text/css"">table, td{{border: 1px solid #000;border-collapse: collapse;}}</style></head><body>",
                process.ProcessName);

            Console.WriteLine("一、正在收集計數器");

            var cate = new PerformanceCounterCategory("Thread");
            string[] instances = cate.GetInstanceNames();
            foreach (string instance in instances)
            {
                if (instance.StartsWith(process.ProcessName, StringComparison.CurrentCultureIgnoreCase))
                {
                    var counter1 =
                        new PerformanceCounter("Thread", "ID Thread", instance, true);
                    var counter2 =
                        new PerformanceCounter("Thread", "% Processor Time", instance, true);
                    counters.Add(instance, new MyThreadCounterInfo(counter1, counter2));
                }
            }

            foreach (var pair in counters)
            {
                pair.Value.IdCounter.NextValue();
                pair.Value.ProcessorTimeCounter.NextValue();
            }
            Thread.Sleep(1000);
            foreach (var pair in counters)
            {
                try
                {
                    var info = new MyThreadInfo();
                    info.Id = pair.Value.IdCounter.NextValue().ToString();
                    info.ProcessorTimePercentage = pair.Value.ProcessorTimeCounter.NextValue().ToString("0.0");

                    threadInfos.Add(info.Id, info);
                }
                catch
                {
                }
            }

            Console.WriteLine("二、正在收集線程信息");
            ProcessThreadCollection collection = process.Threads;
            foreach (ProcessThread thread in collection)
            {
                try
                {
                    MyThreadInfo info;
                    if (threadInfos.TryGetValue(thread.Id.ToString(), out info))
                    {
                        info.UserProcessorTime = thread.UserProcessorTime.ToString();
                        info.StartTime = thread.StartTime.ToString();
                    }
                }
                catch
                {
                }
            }

            var debugger = new MDbgEngine();

            MDbgProcess proc = null;
            try
            {
                proc = debugger.Attach(pid);
                DrainAttach(debugger, proc);

                MDbgThreadCollection tc = proc.Threads;
                Console.WriteLine("三、正在附加到進程{0}獲取調用棧", pid);
                foreach (MDbgThread t in tc)
                {
                    var tempStrs = new StringBuilder();
                    foreach (MDbgFrame f in t.Frames)
                    {
                        tempStrs.AppendFormat("<br />" + f);
                    }
                    MyThreadInfo info;
                    if (threadInfos.TryGetValue(t.Id.ToString(), out info))
                    {
                        info.CallStack = tempStrs.Length == 0 ? "no managment call stack" : tempStrs.ToString();
                    }
                }
            }
            finally
            {
                if (proc != null)
                {
                    proc.Detach().WaitOne();
                }
            }
            foreach (var info in threadInfos)
            {
                sb.Append(info.Value.ToString());
                sb.Append("<hr />");
            }
            sb.Append("</body></html>");

            Console.WriteLine("四、正在生成報表");
            using (var sw = new StreamWriter(process.ProcessName + ".htm", false,
                                             Encoding.Default))
            {
                sw.Write(sb.ToString());
            }

            Process.Start(process.ProcessName + ".htm");
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex);
        }
    }單元測試

找出HeightCPUDemo的進程ID,好比是8724,而後執行PrintStack.exe 8724,輸出結果以下
E:\study\ThreadStack\PrintStack\bin\Debug>PrintStack.exe 8724
一、正在收集計數器...
二、正在收集線程信息...
三、正在附加到進程8724獲取調用棧...
四、正在生成報表...測試

最終會在當前目錄生成一個HightCPUDemo.htm的報表,其中哪一個線程耗費了不少的CPU,及其託管調用棧一目瞭然,很快就能定位問題。ui

本文轉自:http://www.cnblogs.com/xgw2004058/archive/2011/11/01/2232026.html

原文DEMO有些問題,附上調整後得DEMO下載地址:點我下載

注意一點:項目默認正對X64平臺生成,若是須要調試32位須要修改目標平臺。

相關文章
相關標籤/搜索