当一个.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,及其托管调用栈一目了然,很快就能定位问题。

ThreadId 10280 % Processor Time 97.1
UserProcessorTime 00:00:20.2187500 StartTime 2009-6-24 21:58:19

System.Random.Sample (source line information unavailable)
System.Random.NextDouble (source line information unavailable)
HightCPUDemo.Program.B (Program.cs:27)
System.Threading.ThreadHelper.ThreadStart_Context (source line information unavailable)
System.Threading.ExecutionContext.Run (source line information unavailable)
System.Threading.ThreadHelper.ThreadStart (source line information unavailable)

参考链接

How the .NET Debugger Works
http://www.developerfusion.com/article/4692/how-the-net-debugger-works/
Working on managed wrappers for Native Debugging API
http://blogs.msdn.com/jmstall/archive/2006/07/05/managed-wrappers-for-native-debug-api.aspx
Runtime Call Stack Analysis with .NET
http://www.ddj.com/184405715
MDbg Linkfest
http://blogs.msdn.com/jmstall/archive/2005/11/08/mdbg_linkfest.aspx
Tool to get snapshot of managed callstacks
http://blogs.msdn.com/jmstall/archive/2005/11/28/snapshot.aspx

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

.NET定位CPU使用率过高问题相关推荐

  1. Java 程序 CPU 使用率过高问题定位与修复

    问题现象:CPU 负载过高 我们线上的 jenkins 系统,时不时会发生 CPU 负载过高的现象. CPU 负载过高后,SRE 同学会收到电话告警. 在我们的监控系统中,可以看到,某些时候,CPU ...

  2. 06 | 案例篇:系统的 CPU 使用率很高,但为啥却找不到高 CPU 的应用?

    上一节我讲了 CPU 使用率是什么,并通过一个案例教你使用 top.vmstat.pidstat 等工具,排查高 CPU 使用率的进程,然后再使用 perf top 工具,定位应用内部函数的问题.不过 ...

  3. 4 系统的 CPU 使用率很高,但为啥却找不到高 CPU的应用?

    上一节讲了 CPU 使用率是什么,并通过一个案例教你使用 top.vmstat.pidstat 等工具,排查高 CPU 使用率的进程,然后再使用 perf top 工具,定位应用内部函数的问题.不过就 ...

  4. linux服务器 cpu使用率过高,服务器CPU使用率过高排查与解决思路

    发现服务器的cpu使用率特别高 排查思路: -使用top或者mpstat查看cpu的使用情况 mpstat -P ALL 2 1 Linux 2.6.32-358.el6.x86_64 (linux- ...

  5. jstack排查cpu使用率过高

    jstack排查cpu使用率过高 步骤: 1.top命令找出最高占用的进程(command为java) 2.查看高负载进程下的高负载线程:top -Hp [PID] (或 ps -mp PID -o ...

  6. mysql数据库占用太多的CPU_mysql数据库CPU使用率过高解决方案

    压测时,经常会出现mysql数据库CPU使用率过高,下面介绍一下简单的问题定位方法: 1.确定是否有慢sql语句: 1)登录数据库服务器,连接数据库:执行命令:mysql -uroot -p 密码 2 ...

  7. jstack定位CPU占用率高的线程代码

    目录 一.背景 二.jstack定位实战演示 三.关于线程的状态 四.最后 一.背景 性能测试过程中,如果我们发现应用服务器CPU使用率高(超过70%),接口TPS低的现象,此时常见的情况是由以下的原 ...

  8. linux 内核空间占用cpu百分比过高,线上linux系统故障排查之一:CPU使用率过高

    摘自: 一个应用占用CPU很高,除了确实是计算密集型应用之外,通常原因都是出现了死循环. 下面我们将一步步定位问题,详尽的介绍每一步骤的相关知识. 一.通过top命令定位占用cpu高的进程 执行top ...

  9. Java应用CPU使用率过高排查

    1.使用top命令查看使用CPU过高的进程 top top - 14:16:06 up 27 min, 3 users, load average: 0.00, 0.01, 0.02 Tasks: 8 ...

最新文章

  1. 左神讲算法——异或的高级操作(两数交换+经典面试题)
  2. python代码写完怎么运行-教你如何编写、保存与运行 Python 程序
  3. python2.7 + selenium3.4.3浏览器的选择
  4. unicode 版本 delphi (如XE2)的 TBytes 转换为 AnsiString
  5. 互联网技术架构的启示
  6. 为什么 Kafka 速度那么快?
  7. 系统安装操作优化:chapter4 多系统的安装与管理
  8. linux ip端口配置文件,Linux /etc/network/interfaces配置接口方法
  9. session的创建方式
  10. 数据预处理(normalize、scale)
  11. 宝塔控制面板忘记密码怎么找回?
  12. 【业务人员第一视角】氚云低代码开发平台测评
  13. python识别图片文字
  14. ​【汇总】CV 图像分类常见的 36 个模型
  15. java excel 边框颜色_poi生成excel整理(设置边框/字体/颜色/加粗/居中/)[转]
  16. plotly绘制简单图形4--饼形图
  17. 2023最新SSM计算机毕业设计选题大全(附源码+LW)之java小微企业ERP软件物料需求模块设计55ss9
  18. 华为路由交换学习篇-链路聚合
  19. jQuery 是一个 JavaScript 库。
  20. POJ的题目分类(两个版本)

热门文章

  1. 设计之路 -- 如何进行软件需求分析?
  2. python sympy包符号运算进行定积分计算
  3. 2023 OneTool多平台助手程序源码 开心版
  4. 诺基亚奢华手机:高配置+8.78万元
  5. Requests库爬虫详解
  6. UML时序图速查——架构设计必备技能
  7. 《中国合伙人》经典台词:一个人迫于无奈之下的选择,往往是正确的。。。
  8. 七(10)springtask-RabbitMq-内容审核
  9. 李银河应该重读王小波《摆脱童稚状态》--尼伯龙根·蜗藤
  10. ajax 报错 Content-Type is not allowed by Access-Control-Allow-Headers in preflight respon