.NET定位CPU使用率过高问题

摘要:

当一个.net应用在生产环境CPU突然居高不下,如何快速准确的定位问题所在,并且对实时业务影响最小化?如何不抓Dump也不用live debug就可以知道你的应用在做什么?如何确认你的应用是由于哪个线程的执行造成的CPU升高,该线程正在执行什么代码?

分析:
CPU升高的原因有很多,
 1、有时候应用的负载大了,CPU自然会受业务请求的增加和增高;
 2、有时候因为GC回收使用了过高的CPU资源;
 3、有时候是某个线程执行的代码在某种情况下陷入了死循环;
 4、有时候是因为锁争用太激烈,某资源上的锁释放后,等待的线程去抢锁引起的;
 5、有时候是因为线程太多,上下文切换太频繁引起的。
 6、每秒抛出太多的Exception。
我们一一分析
 1、我们一般会用一些计数器来观察实际的应用的负载情况和并发请求量,比如每秒接受多少请求等,所以业务量增大引起的CPU高,很容易确定。
 2、GC使用的CPU百分比有专门的计数器,一看便知。
 3、如果某段代码陷入了死循环引起的CPU高,只抓Dump看~*e!clrstack和!runaway还是不太好定位问题,
  a)、一般都是连续抓几个dump,然后用!runaway来看哪些线程的用户态时间的差很大,然后再去看该线程的调用栈。
  b)、录制Thread\Thread Id和Thread\% Processor Time计数器,同时抓dump,从计数器里找到CPU消耗高的线程ID,然后从dump里看调用栈和调用栈的参数本地变量等。
 4、锁争用也有相关的.NET计数器可以直接看。
 5、每个进程的线程数,每秒上下文切换次数也可以有直接的计数器可看。
 6、每秒抛出的异常也有直接的计数器可看。

思路:
1、从上面看到也就是第3种地排查比较费劲,而且抓DUMP有时候容易把服务抓S,如果是一个有状态的服务,抓死服务后果很严重,所以我们得想出一种更轻量级的方法去获取服务的每个线程的调用栈。其实CLR本身有一些用于支持调试的接口,都是Com的,但.NET对此有一些包装,可以用c#来使用这些调试API,其中当然包括附加到进程,获取所有线程调用栈的功能。该DLL在.net sdk里,叫MdbgCore.dll。
2、另外获取计数器.NET也有现成的类,上篇帖子介绍过了。
3、.NET对进程的管理也有一些API,可以获取一个进程的线程数,每个线程的启动时间,用户态时间,线程状态,优先级等信息。

有了以上几个知识点,我们就可以综合起来写一个比较智能化定位高CPU问题的工具。

CPU高的DEMO
我们先写一个CPU高的DEMO,A方法因为有sleep所以不会太消耗CPU,而B方法没有Sleep,执行一个浮点运算,所以会造成CPU升高(占用一个CPU的资源)。

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("1、正在收集计数器");

            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("2、正在收集线程信息");
            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("3、正在附加到进程{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("4、正在生成报表");
            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
1、正在收集计数器...
2、正在收集线程信息...
3、正在附加到进程8724获取调用栈...
4、正在生成报表...

最终会在当前目录生成一个HightCPUDemo.htm的报表,其中哪个线程耗费了很多的CPU,及其托管调用栈一目了然,很快就能定位问题。

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

原文DEMO有些问题,附上调整后得DEMO下载地址:点我下载

注意一点:项目默认正对X64平台生成,如果需要调试32位需要修改目标平台。

时间: 2024-10-12 11:26:46

.NET定位CPU使用率过高问题的相关文章

Oracle查询语句导致CPU使用率过高问题处理

解决此问题的关键在于如何找到造成CPU使用率过高的SQL语句.步骤如下: 1.使用Process Explorer工具查看到Oracle进程,双击Oracle进程,在弹出的属性窗口的Threads选项卡中查看占用CPU较高的线程号(TID). 2.在PL/SQL工具中执行以下SQL语句: --根据sql_id获取对应的Sql语句(sql_text,sql_fulltext)select * from v$sqlarea where sql_id in ( --根据addr获取sql_id sel

服务器CPU使用率过高排查与解决思路

发现服务器的cpu使用率特别高 排查思路: -使用top或者mpstat查看cpu的使用情况# mpstat -P ALL 2 1Linux 2.6.32-358.el6.x86_64 (linux—host) 01/05/2016 _x86_64_ (24 CPU) 04:41:13 PM CPU %usr %nice %sys %iowait %irq %soft %steal %guest %idle04:41:15 PM all 0.56 0.00 0.25 0.00 0.00 0.04

(原创)性能测试中,Oracle服务器定位CPU使用率高的瓶颈(SQL)

本篇博客记录一次性能测试过程中,定位对CPU使用率高的瓶颈问题,主要定位SQL为准 一.用SQL命令定位1.首先用TOP命令监控系统资源,如果是AIX系统,就用topas,进入TOP命令的滚动刷新数据时,发现userCPU高达98%!! 保持top的状态下,按shift+p,可以将所有进程按CPU使用率高低排序,这样可以了解消耗CPU最多的进程是哪些 可以看到,当前userCPU使用率高达98%,且此时TPS不再随并发数上升了,可以认为已经达到性能瓶颈了,且是由CPU瓶颈造成的 2.排序完后,将

服务器CPU使用率过高的处理

最近发现公司服务器搭建的网站访问缓慢,服务器输入命令也反应慢,处理步骤如下: 1.通过top命令查看服务器CPU.内存.IO等使用情况 发现CPU基本在80%以上:内存还好,有富余:CPU平均加载率Load Average也是达到40左右 2.通过vmstat.iostat参看相关参数,确认是CPU占用很高,CPU不够用,当时以为服务器CPU被用完了,但是应用不是很多,两CPU是够用的 3.后来慢慢看进程和服务线程以及端口号占用和包发送,(w.procinfo.ps.uptime.netstat

查找CPU使用率过高的线程

1.在编写程序中有时候设置不恰当,休眠时间不够,一般情况下4核的电脑CPU使用率一直大于23%,8核的大于13%就有可能是这种情况 解决方法: 在VS查看并行线程利用CPU使用工具ProcessExplorer,查看CPU占用率过高的线程查看线程ID 和 并行线程ID 相同的然后仔细看那个并行线程的代码 2. 查看代码中是否有死锁的部分,死循环或者是耗时代码,一些需要托管在其它硬件执行的程序 3.其它: 硬件原因:散热不良,驱动问题 病毒爬虫等外界恶意软件: 重复调用一个进程而没有结束:

linux CPU使用率过高或负载过高的处理思路

1.查看系统CPU负载及使用率的命令为:top    vmstat top 命令:查看进程级别的cpu使用情况. vmstat 命令:查看系统级别的cpu使用情况. 下面通过具体的图例来分析: 1.1  top 命令可以查看进程的CPU.内存等资源的使用情况.       在top命令运行过程中可以通过top的内部命令做显示方式的控制. 1- 开启或关闭显示所有cpu使用详细情况 l - 关闭或开启第一部分第一行 top 信息的表示 t - 关闭或开启第一部分第二行 Tasks 和第三行 Cpu

记一次mysql 导致的CPU使用率过高问题

前几天更新完程序,测试功能时,发现应用登录非常慢,以为是应用有问题,但是等待较长一段时间后发现应用登录是ok的.检查应用日志,发现提示数据池已满,无法创建更多的连接.然后赶紧登录数据库检查, 1.top查看发现mysql进程cpu使用率竟然高达1300%多,我勒个去,这是什么鬼! 2.登录mysql查看线程 # show processlist 发现大量的从应用服务器过来的连接,而且全部是Query状态.想着看看慢查询日志有哪些SQL运行时间较长,导致连接一直保持Query状态. 3.开启慢查询

如何定位cpu占用率高的java线程

工具: 1 jstack:jstack用于打印出给定的java进程ID或core file或远程调试服务的Java堆栈信息,如果是在64位机器上,需要指定选项"-J-d64",Windows的jstack使用方式只支持以下的这种方式. pidstat :pidstat主要用于监控全部或指定进程占用系统资源的情况,如CPU,内存.设备IO.任务切换.线程等.pidstat首次运行时显示自系统启动开始的各项统计信息,之后运行pidstat将显示自上次运行该命令以后的统计信息.用户可以通过指

代码死循环导致cpu使用率过高

1. top命令查看进程pid  27081 2. ps -mp pid -o THREAD,tid,time  (tid:31128) 3.printf “%x\n” number  #将tid转换成16进制,number代表tid, 输入tid:31128,输出7998 4. jstack pid 27081 #查看问题程序,nid和tid的16进制相同的程序 原文地址:https://www.cnblogs.com/changeEveryDay/p/11658809.html