記一次某制造業ERP系統 CPU打爆事故分析

一:背景1.講故事前些天有位朋友微信找到我,說他的程序出現了CPU階段性爆高,過了一會就下去了,咨詢下這個爆高階段程序內部到底發生了什么? 畫個圖大概是下面這樣,你懂的 。
【記一次某制造業ERP系統 CPU打爆事故分析】

記一次某制造業ERP系統 CPU打爆事故分析

文章插圖
按經驗來說,這種情況一般是程序在做 CPU 密集型運算 , 所以讓朋友在 CPU 高的時候間隔 5~10s 抓兩個 dump 下來,然后就是用 WinDbg 分析 。
二:WinDbg 分析1. CPU 真的爆高嗎耳聽為虛,眼見為實,我們用 !tp 觀察下當前的CPU情況 。
0:000> !tpCPU utilization: 100%Worker Thread: Total: 16 Running: 2 Idle: 14 MaxLimit: 32767 MinLimit: 2Work Request in Queue: 0--------------------------------------Number of Timers: 2--------------------------------------Completion Port Thread:Total: 2 Free: 2 MaxFree: 4 CurrentLimit: 2 MaxLimit: 1000 MinLimit: 2果不其然,CPU直接打滿,接下來就是看看當前有幾個CPU邏輯核,這么不夠扛 。。。
0:000> !cpuidCPF/M/SManufacturerMHz 06,106,6<unavailable>2700 16,106,6<unavailable>2700我去,一個生產環境居然只有兩個核 。。。果然這大環境下公司活著都不夠滋潤 。
2. 到底是誰引發的既然是階段性爆高,最簡單粗暴的就是看下各個線程棧,使用 ~*e !clrstack 命令即可 , 因為只有兩核,所以理論上兩個線程就可以把 CPU 干趴下 , 掃了一下線程棧,果然有對號入座的,輸出信息如下:
0:000> ~*e !clrstackOS Thread Id: 0x146c (42)Child SPIP Call Site00000089abcfca18 00007ffc4baffdb4 [InlinedCallFrame: 00000089abcfca18] System.Drawing.SafeNativeMethods+Gdip.IntGdipDisposeImage(System.Runtime.InteropServices.HandleRef)00000089abcfca18 00007ffbdd4a7a48 [InlinedCallFrame: 00000089abcfca18] System.Drawing.SafeNativeMethods+Gdip.IntGdipDisposeImage(System.Runtime.InteropServices.HandleRef)00000089abcfc9f0 00007ffbdd4a7a48 DomainNeutralILStubClass.IL_STUB_PInvoke(System.Runtime.InteropServices.HandleRef)00000089abcfcaa0 00007ffbdd52ad0a System.Drawing.SafeNativeMethods+Gdip.GdipDisposeImage(System.Runtime.InteropServices.HandleRef)00000089abcfcae0 00007ffbdd52ac3f System.Drawing.Image.Dispose(Boolean)00000089abcfcb30 00007ffbdd556b5a System.Drawing.Image.Dispose()00000089abcfcb60 00007ffbe39397c7 NPOI.SS.Util.SheetUtil.GetCellWidth(NPOI.SS.UserModel.ICell, Int32, NPOI.SS.UserModel.DataFormatter, Boolean)00000089abcfcc00 00007ffbe3939654 NPOI.SS.Util.SheetUtil.GetCellWidth(NPOI.SS.UserModel.ICell, Int32, NPOI.SS.UserModel.DataFormatter, Boolean)00000089abcfcd30 00007ffbe39382e1 NPOI.SS.Util.SheetUtil.GetColumnWidth(NPOI.SS.UserModel.ISheet, Int32, Boolean)00000089abcfcdc0 00007ffbe39380bc NPOI.XSSF.UserModel.XSSFSheet.AutoSizeColumn(Int32, Boolean)...OS Thread Id: 0x1c8c (46)Child SPIP Call Site00000089ad43dba8 00007ffc4baffdb4 [InlinedCallFrame: 00000089ad43dba8] System.Drawing.SafeNativeMethods+Gdip.IntGdipDisposeImage(System.Runtime.InteropServices.HandleRef)00000089ad43dba8 00007ffbdd4a7a48 [InlinedCallFrame: 00000089ad43dba8] System.Drawing.SafeNativeMethods+Gdip.IntGdipDisposeImage(System.Runtime.InteropServices.HandleRef)00000089ad43db80 00007ffbdd4a7a48 DomainNeutralILStubClass.IL_STUB_PInvoke(System.Runtime.InteropServices.HandleRef)00000089ad43dc30 00007ffbdd52ad0a System.Drawing.SafeNativeMethods+Gdip.GdipDisposeImage(System.Runtime.InteropServices.HandleRef)00000089ad43dc70 00007ffbdd52ac3f System.Drawing.Image.Dispose(Boolean)00000089ad43dcc0 00007ffbdd556b5a System.Drawing.Image.Dispose()00000089ad43dcf0 00007ffbe39397c7 NPOI.SS.Util.SheetUtil.GetCellWidth(NPOI.SS.UserModel.ICell, Int32, NPOI.SS.UserModel.DataFormatter, Boolean)00000089ad43dd90 00007ffbe3939654 NPOI.SS.Util.SheetUtil.GetCellWidth(NPOI.SS.UserModel.ICell, Int32, NPOI.SS.UserModel.DataFormatter, Boolean)00000089ad43dec0 00007ffbe39382e1 NPOI.SS.Util.SheetUtil.GetColumnWidth(NPOI.SS.UserModel.ISheet, Int32, Boolean)00000089ad43df50 00007ffbe39380bc NPOI.XSSF.UserModel.XSSFSheet.AutoSizeColumn(Int32, Boolean)...00000089ad43e460 00007ffbe115b193 System.Web.Mvc.ControllerActionInvoker.InvokeActionMethod(System.Web.Mvc.ControllerContext, System.Web.Mvc.ActionDescriptor, System.Collections.Generic.IDictionary`2<System.String,System.Object>)...00000089abcfd310 00007ffbe115b147 System.Web.Mvc.Async.AsyncControllerActionInvoker+c.b__9_0(System.IAsyncResult, ActionInvocation)...有些朋友要問了,你是怎么確定就是這兩個線程呢? 其實有兩個方法可以驗證 。
  1. 使用 !whttp 看http請求
既然是 web 請求,自然就可以拿到里面的 HttpContext,這里面記錄著當前請求的運行時間,這個信息非常重要,截圖如下:
記一次某制造業ERP系統 CPU打爆事故分析

推薦閱讀