.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();
}
} 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使用率过高问题的更多相关文章
- CPU使用率过高分析方法
项目过程中发现,应用服务器经常会出现CPU使用率较高的情况,需要定位出具体代码问题. 1.用top命令,根据CPU使用率排序,找出消耗cpu最高的进程 2.找出该进程下消耗CPU最高的线程(命令:to ...
- 线上cpu使用率过高解决方案
一个应用占用CPU很高,除了确实是计算密集型应用之外,通常原因都是出现了死循环. 下面我们将一步步定位问题,详尽的介绍每一步骤的相关知识. 一.通过top命令定位占用cpu高的进程 执行top命令得到 ...
- 06 案例篇:系统的 CPU 使用率很高,但为啥却找不到高 CPU 的应用?
上一节我讲了 CPU 使用率是什么,并通过一个案例教你使用 top.vmstat.pidstat 等工具,排查高 CPU 使用率的进程,然后再使用 perf top 工具,定位应用内部函数的问题.不过 ...
- 4 系统的 CPU 使用率很高,但为啥却找不到高 CPU的应用?
上一节讲了 CPU 使用率是什么,并通过一个案例教你使用 top.vmstat.pidstat 等工具,排查高 CPU 使用率的进程,然后再使用 perf top 工具,定位应用内部函数的问题.不过就 ...
- 服务器CPU使用率过高排查与解决思路
发现服务器的cpu使用率特别高 排查思路: -使用top或者mpstat查看cpu的使用情况# mpstat -P ALL 2 1Linux 2.6.32-358.el6.x86_64 (linux— ...
- 《Troubleshooting SQL Server》读书笔记-CPU使用率过高(下)
<Troubleshooting SQL Server>读书笔记-CPU使用率过高(下) 第三章 High CPU Utilization. CPU使用率过高的常见原因 查询优化器会尽量从 ...
- Oracle查询语句导致CPU使用率过高问题处理
解决此问题的关键在于如何找到造成CPU使用率过高的SQL语句.步骤如下: 1.使用Process Explorer工具查看到Oracle进程,双击Oracle进程,在弹出的属性窗口的Threads选项 ...
- Java程序CPU使用率过高
Java程序CPU使用率过高 通过top命令找到使用率过高的java进程PID 根据进程号查找线程TID:ps -mp PID -o THREAD,tid,time 将TID转换成16进制:print ...
- 记录一次mysql查询速度慢造成CPU使用率很高情况
1.某日zabbix告警,某台机器CPU使用率过高. 查看慢查询日志,看到很多sql语句都超过10秒 把sql语句拿出来放在查询窗口执行.前面加上explain就可以查看详细查询信息 playcode ...
随机推荐
- 微信小程序开发者注册流程
一,首先打开浏览器,搜索微信公众平台 点击进入,此时还没有注册微信小程序开发账号,我们需要点击注册 进入注册页面,会出现四种账号,我们选择小程序账号 然后根据提示就可以进行注册了 注册时,需填写一下个 ...
- Spring MVC 项目搭建 -2- 添加service ,dao,junit
Spring MVC 项目搭建 -2- 添加service ,dao,junit 1.dao public class Hero { private String name; public Strin ...
- Vijos 1025 小飞侠的游园方案 0-1背包
描述 经过抽签选择,小智将军第一个进入考场. 菜虫:(身上散射出华贵(?)的光芒)欢迎你,第一位挑战者!! 小智:--(走到菜虫身后,关灯)女王陛下,虽然我们国家现在很富裕,但也请您不要浪费电来用这么 ...
- Vue--props
组件实例的作用域是孤立的.这意味着不能 (也不应该) 在子组件的模板内直接引用父组件的数据.要让子组件使用父组件的数据,我们需要通过子组件的 props 选项. 字面量语法 vs 动态语法 初学者常犯 ...
- Android反编译odex然后重新打包
#Android反编译odex然后重新打包 最近不知道怎么回事,突然把我那刷了氧OS的root了,然后就开始好奇起来氢OS所带有的那些本地化的东西,比如通话录音就是典型的一个之一.其中也做了很多的尝试 ...
- Maven入门1-在Eclipse中新建Maven Web项目
在eclipse中新建Maven Web项目 很多时候开发效率低下,大部分原因是IDE环境不熟悉.配置不会配置:因此在学习一项技能之前,有必要对基本的环境配置有所了解,正所谓磨刀不误砍柴工.这篇文章主 ...
- 【PHP】PHP面向对象编程--phpOOP入门
PHP从入门到精通 之PHP的面相对象编程 面向对象编程(Object Oriented Programming, OOP, 面向对象程序设计)是一种计算机编程架构,OOP的一条基本原则是计算机程序 ...
- rsync定时同步配置
附上脚本 三大配置文件请看rsync安装与配置 #!/bin/sh #linuxsir.org home backup #/usr/bin/rsync -avzP --password-file=/e ...
- Centos 7部署大众点评CAT(二)——双服务器部署
在单机上部署CAT,只是在做实验,在生产环境则不可能只用单台服务器监控多个应用. 下面简单介绍一下双服务器的部署,各位有更多硬件资源作为监控服务端的朋友,如果对CAT集群有兴趣,可以参看这篇拙作. 资 ...
- apt-get 安装ubuntu-tweak
Ubuntu Tweak是一款专门为Ubuntu(GNOME桌面)准备的配置.调整工具.主要面向新手级的普通用户.它可以设置很多并不能在系统首选项中设置的隐藏选项,以满足用户自定义的乐趣.即使是新手, ...