在51Testing论坛上,有一位网友提出一个问题:应用程序运行1小时左右,会占用CPU 近100%,持续1~2分钟后恢复正常,有没有诊断方法?
《.NET 2.0应用程序调试》和《Windows用户态程序高效排错》都指出,开发者可以用Performance Monitor监视特定的Performance Counter,当Counter的值超过预定义阈值时,Performance Monitor会调用开发者指定的任务。这个任务通常是使用调试器Windbg生成程序的内存转储文件(memory dump file),然后分析该文件,调查当时程序的状态。对于上面这个案例,应该用Performance Monitor监控该程序的“Processor Time %”,一旦它超过90,就利用调试器生成其内存转储文件。
自Windows Vista开始,Windows改变了Performance Monitor的界面,使得上述功能的“可发现性”大大降低。再加上Windows帮助和MSDN也语焉不详,使得开发者在使用上有一些困难。本文将介绍在Windows 7上,如何利用Performance Counter Alert进行调试。
1. 创建Performance Counter Alert
点击 Start > Administrative Tools > Computer Manager,展开节点System Tools > Performance > Data Collector Sets > User Defined,右键点击User Defined,创建Data Collector Set。
在弹出窗口中,将Name修改为cpu_usage_alert,选择 Create manually (Advanced),点击Next。
选择 Performance Counter Alert,点击Next。
点击 Add… ,以加入Performance counter。
在弹出窗口中,选择 Process下的 % Processor Time,然后选择目标进程(在下图中,我选择的是taskmgr,即任务管理器),然后点击 Add >>,最后点击OK。
将Limit修改为90,点击Finish,完成 Performance Alert 的创建。
在 Computer Management中,点击cpu_usage_alert,在右侧窗格中,右击DataCollector01,点击Properties。
在弹出窗口中,点击Alert Task,将 Run this task when an alert is triggered 修改为cpu_usage_alert_task,最后点击OK。
选中cpu_usage_alert,然后点击工具栏上的 Start the Data Collect Set,开始监控性能数据。
这样,我就创建一个Performance Counter Alert,它监控任务管理(taskmgr.exe)的CPU占用率,一旦其超过90%,就会触发任务cpu_usage_alert_task。在Windows XP和Windows Server 2003中,任务可以是任意程序和脚本。但是,在Windows 7中,任务必须是一个Windows Task。
2. 创建Task
在 Computer Manager中,展开节点 Task Scheduler > Task Scheduler Library,右键点击Task Scheduler Library,点击 Create Task… 。
在弹出窗口中,将General中的Name修改为cpu_usage_alert_task(即cpu_usage_alert所触发的Task的名字)。
在Actions中,点击New… 。
在弹出窗口中,将 Program/script 修改为 c:\debuggers\cpu_usage_alert.bat。最后连续点击OK,完成Task的创建。
当cpu_usage_alert_task被触发时,Action所指定的脚本cpu_usage_alert.bat将被执行,它调用Windbg生成内存转储文件。
3. cpu_usage_alert.bat
cpu_usage_alert.bat的内容很简单,只有一行脚本:
"c:\debuggers\ntsd.exe" –pn taskmgr.exe -c ".dump /ma /u C:\debuggers\taskmgr.dmp;qd"
此脚本要求将Windbg安装在 c:\debuggers 目录下。它调用调试器 ntsd.exe,对进程taskmgr.exe生成内存转储文件。ntsd命令的详细解释如下。
- -pn taskmgr.exe:对进程taskmgr.exe进行调试。
- -c ".dump /ma /u C:\debuggers\taskmgr.dmp;qd":执行调试命令.dump和qd。
- .dump /ma: 生成小型内存转储文件。
- /u:将时间戳和进程ID加入到内存转储文件名。这可以保证连续生成的内存转储文件不会同名。
- C:\debuggers\taskmgr.dmp:将内存转储文件生成到 c:\debuggers 目录下,文件名以taskmgr开始。
- qd:将调试器与taskmgr.exe分离,并关闭调试器。
于是,当cpu_usage_alert发现taskmgr.exe的CPU占用率超过90%,它会触发cpu_usage_alert_task,后者会调用cpu_usage_alert.bat,生成taskmgr.exe的内存转储文件。这样的脚本适合产品环境,它没有中断被调试对象的执行,只是保存了内存转储文件。当系统管理员将内存转储文件移交给开发团队,开发团队可以对其进行“事后分析”。
值得一提的是,如果cpu_usage_alert的采样频率是15秒,那么cpu_usage_alert.bat将会每15秒生成一个内存转储文件。如果taskmgr.exe的CPU占用率持续超过90%,cpu_usage_alert.bat将生成大量的内存转储文件,有可能使耗尽磁盘空间。因此建议将转储文件生成在非系统盘,且该磁盘拥有充足的容量。
如果在测试环境中调试,可以考虑将cpu_usage_alert.bat实现为:
"c:\debuggers\ntsd.exe" -server tcp:port=8888 -pn taskmgr.exe -c ".dump /ma /u C:\Dump\%name%.dmp"
该脚本仍旧生成内存转储文件,因为内存转储文件保留了“问题现场”,是非常重要的调试信息,要第一时间保存。它与产品环境脚本的不同之处在于:
- 不调用qd:生成内存转储文件之后,不与taskmgr.exe分离,不关闭调试器。
- 利用参数 –server tcp:port=8888开启了一个调试服务器。由于ntsd一直附加(attach)在taskmgr.exe之上,开发者可以用Windbg(远程)连接该调试服务器。详细操作请参考博文 Remote debugging with -server and -remote。
那么,开发者如何知道可以开始调试了呢?可以在cpu_usage_alert_task的Action中,再加入一条发送邮件的操作。在下图中,我利用一个IronPython脚本mail.py发送一封邮件题为 “cpu usage alert” 的邮件到我的邮箱。在Windows上有许多命令行的邮件客户端,也可以完成类似操作。
4. 小结
Performance Counter Alert是一个强大的性能诊断工具,能够及时捕获性能异常事件。配合Windbg等调试工具,可以帮助开发者在第一时间获取程序状态,进行高效的调试。