摘要:

当一个.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();
}
} 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[]); 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();
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 == ? "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位需要修改目标平台。

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

  1. CPU使用率过高分析方法

    项目过程中发现,应用服务器经常会出现CPU使用率较高的情况,需要定位出具体代码问题. 1.用top命令,根据CPU使用率排序,找出消耗cpu最高的进程 2.找出该进程下消耗CPU最高的线程(命令:to ...

  2. 线上cpu使用率过高解决方案

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

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

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

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

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

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

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

  6. 《Troubleshooting SQL Server》读书笔记-CPU使用率过高(下)

    <Troubleshooting SQL Server>读书笔记-CPU使用率过高(下) 第三章 High CPU Utilization. CPU使用率过高的常见原因 查询优化器会尽量从 ...

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

    解决此问题的关键在于如何找到造成CPU使用率过高的SQL语句.步骤如下: 1.使用Process Explorer工具查看到Oracle进程,双击Oracle进程,在弹出的属性窗口的Threads选项 ...

  8. Java程序CPU使用率过高

    Java程序CPU使用率过高 通过top命令找到使用率过高的java进程PID 根据进程号查找线程TID:ps -mp PID -o THREAD,tid,time 将TID转换成16进制:print ...

  9. 记录一次mysql查询速度慢造成CPU使用率很高情况

    1.某日zabbix告警,某台机器CPU使用率过高. 查看慢查询日志,看到很多sql语句都超过10秒 把sql语句拿出来放在查询窗口执行.前面加上explain就可以查看详细查询信息 playcode ...

随机推荐

  1. container_of 的用法

    1.问题:如何通过结构中的某个变量获取结构本身的指针???关于container_of见kernel.h中:/*** container_of - cast a member of a structu ...

  2. Linux之正则表达式

    正则表达式与通配符的区别: 最常应用正则表达式的命令是grep(egrep),sed,awk. 正则表达式和通配符有本质区别,正则表达式用来找:[文件]内容,文本,字符串.一般只有三剑客支持.通配符用 ...

  3. ovs+dpdk numa感知特性验证

    0.介绍 本测试是为了验证这篇文章中提到的DPDK的NUMA感知特性. 简单来说,在ovs+dpdk+qemu的环境中,一个虚拟机牵涉到的内存共有三部分: DPDK为vHost User设备分配的De ...

  4. 页面打印(js/jquery)

    1.js实现(可实现局部打印)  <html> <title>js打印</title> <head></head><body>  ...

  5. Unity3D-Shader-热扭曲效果

    [旧博客转移 - 2016年1月13日 13:18 ] 前面的话: 本来我是想写一个水的原理的,但是发现涉及的知识太多,还有好多不懂的,所以就先一步一步来 最近呢,我在网上捡到了一本<热扭曲秘籍 ...

  6. Android源码博文集锦2

    Android精选源码 android简单易用的Gallery android漂亮的加载效果 这可能是RxJava 2.x 最好的入门教程示例代码 android图片可拖拽排序 android用几行代 ...

  7. ArcGisEngine图层操作(随笔,不全)

    1.加载图层: 1.1 object.AddLayer(Layer[,toindex=0]) Layer表示ILayer对象,必选,toIndex参数表示图层索引(长整型),没需求可以忽略. 1.2 ...

  8. 单片机C语言基础编程源码六则2

    1.某单片机系统的P2口接一数模转换器DAC0832输出模拟量,现在要求从DAC0832输出连续的三角波,实现的方法是从P2口连续输出按照三角波变化的数值,从0开始逐渐增大,到某一最大值后逐渐减小,直 ...

  9. SQL Server 2012 案例教程(贾祥素)——学习笔记

    第2章 SQL Server 2012概述 1.SQL(Structed Query Language),结构化查询语言. 2.SSMS(SQL Server Mangement Studio),SQ ...

  10. CDH版本的oozie安装执行bin/oozie-setup.sh prepare-war,没生成oozie.war?

    不多说,直接上干货! 前期博客 Oozie安装部署 问题描述 bin/oozie-setup.sh prepare-war 解决办法 [hadoop@bigdatamaster bin]$ pwd / ...