调试IIS进程

时间:2015-06-23 11:58:09

标签: .net performance debugging iis

IIS进程的高CPU使用率

我目前正在调查我们某个生产服务器上的高CPU使用率,我已经陷入困境,并希望此处有人能够提供帮助。将CPU使用率与入站Web服务调用数(WCF和REST)进行比较会告诉我它们是无关的,每当呼叫增加或减少时,CPU使用率似乎都会上升。

设置

  • Windows 2012 R2服务器x64
  • IIS 8.5
  • .NET 4.5
  • 运行我们的应用程序的单个应用程序池
  • 00:00自动回收应用程序池

问题

最近我们的CPU使用量急剧增加,模式似乎是CPU使用率从午夜开始攀升(我回收的游泳池)并继续攀升直到游泳池再次被回收。

CPU trend the last days

数据

通过分析taskmanager和使用perfmon计数器,我已经能够确认我们的W3WP进程确实消耗了CPU。

当CPU消耗超过50%的阈值超过10秒时,我将DebugDiag 2.1配置为相隔10秒进行3次内存转储。 (此问题之前的正常CPU使用率为5-10%)

使用ntsd查看转储文件,我发现有几个线程消耗了大量的CPU:

0:047> !runaway
 User Mode Time
  Thread       Time
  47:2920      0 days 0:24:42.921
  49:1f1c      0 days 0:23:07.796
  52:2ed8      0 days 0:21:38.218
  54:1560      0 days 0:21:37.937
  48:273c      0 days 0:21:19.140
  59:2110      0 days 0:20:56.078
  45:2d90      0 days 0:20:35.906
...
  19:1c88      0 days 0:00:00.000

(这里只显示一些线程) 所以我试着看看这些线程中发生了什么,因为这不是预期的行为。所有具有长时间运行任务的线程似乎都被管理了,但是当我尝试的时候!clrstack对其中任何一个我得到:

0:047> !clrstack
OS Thread Id: 0x2920 (47)
        Child SP               IP Call Site
GetFrameContext failed: 1
0000000000000000 0000000000000000 <unknown>

这使我感到困惑,因为我期待托管堆栈。当我查看本机堆栈时,我得到了不同的东西:

0:047> !dumpstack
OS Thread Id: 0x2920 (47)
Current frame: ntdll!NtWaitForSingleObject+0xa
Child-SP         RetAddr          Caller, Callee
0000009c46eae730 00007fff0c131118 KERNELBASE!WaitForSingleObjectEx+0x94, calling ntdll!NtWaitForSingleObject
0000009c46eae7d0 00007fff051f91eb clr!CLREventWaitHelper2+0x38, calling kernel32!WaitForSingleObjectEx
0000009c46eae7e0 00007fff0c13155c KERNELBASE!SetEvent+0xc, calling ntdll!NtSetEvent
0000009c46eae810 00007fff051f9197 clr!CLREventWaitHelper+0x1f, calling clr!CLREventWaitHelper2
0000009c46eae870 00007fff051f9120 clr!CLREventBase::WaitEx+0x70, calling clr!CLREventWaitHelper
0000009c46eae8b0 00007fff052890e6 clr!SVR::t_join::join+0x106, calling clr!CLREventBase::WaitEx
0000009c46eae900 00007fff053d5913 clr!SVR::gc_heap::bgc_thread_function+0x97, calling clr!CLREventBase::WaitEx
0000009c46eae940 00007fff0533fcb6 clr!Thread::intermediateThreadProc+0x7d
0000009c46eaeb10 00007fff0ef4086d ntdll!RtlAllocateHeap+0x17d, calling ntdll!RtlpAllocateHeap
0000009c46eaec20 00007fff0ef40073 ntdll!RtlpSubSegmentInitialize+0x2f3, calling ntdll!RtlpHeapGenerateRandomValue32
0000009c46eaeca0 00007fff0ef40c65 ntdll!RtlpLowFragHeapAllocFromContext+0x355, calling ntdll!memset
0000009c46eaed10 00007fff0ef40c65 ntdll!RtlpLowFragHeapAllocFromContext+0x355, calling ntdll!memset
0000009c46eaed70 00007fff0ef407c7 ntdll!RtlAllocateHeap+0xd7, calling ntdll!RtlpLowFragHeapAllocFromContext
0000009c46eaede0 00007fff0ef407c7 ntdll!RtlAllocateHeap+0xd7, calling ntdll!RtlpLowFragHeapAllocFromContext
0000009c46eaee80 00007fff05f5a89a mscoree!calloc_impl+0x72, calling ntdll!RtlAllocateHeap
0000009c46eaeeb0 00007fff0c135ac4 KERNELBASE!SetTEBLangID+0x2c, calling ntdll!RtlSetLastWin32ErrorAndNtStatusFromNtStatus
0000009c46eaeef0 00007fff05cf15e6 mscoreei!calloc_impl+0x5d, calling ntdll!RtlAllocateHeap
0000009c46eaef20 00007fff05cf145b mscoreei!initptd+0xb7, calling mscoreei!unlock
0000009c46eaef30 00007fff0ef40c65 ntdll!RtlpLowFragHeapAllocFromContext+0x355, calling ntdll!memset
0000009c46eaef50 00007fff05cf138e mscoreei!CRT_INIT+0x135, calling kernel32!GetCurrentThreadId
0000009c46eaef80 00007fff05cf11ee mscoreei!__DllMainCRTStartup+0x8a, calling mscoreei!DllMain
0000009c46eaefe0 00007fff0c3b1387 00007fff0c3b1387
0000009c46eaf030 00007fff04c111d2 00007fff04c111d2, calling 00007fff04c11070
0000009c46eaf0c0 00007fff0ef407c7 ntdll!RtlAllocateHeap+0xd7, calling ntdll!RtlpLowFragHeapAllocFromContext
0000009c46eaf0f0 00007fff0ef2c187 ntdll!RtlDeactivateActivationContextUnsafeFast+0xc7, calling ntdll!_security_check_cookie
0000009c46eaf110 00007fff05085c6e MSVCR120_CLR0400!calloc_impl+0x5d, calling ntdll!RtlAllocateHeap
0000009c46eaf120 00007fff0ef2c2a3 ntdll!RtlActivateActivationContextUnsafeFast+0x93, calling ntdll!_security_check_cookie
0000009c46eaf140 00007fff05085d9b MSVCR120_CLR0400!initptd+0xb7, calling MSVCR120_CLR0400!unlock
0000009c46eaf160 00007fff03064d9c clrjit!__DllMainCRTStartup+0x8d, calling clrjit!DllMain
0000009c46eaf190 00007fff0ef4b9b8 ntdll!LdrpReleaseModuleEnumLock+0x1c, calling ntdll!RtlReleaseSRWLockShared
0000009c46eaf1a0 00007fff0ef2c324 ntdll!LdrpCallInitRoutine+0x4c
0000009c46eaf1c0 00007fff0ef4b96b ntdll!LdrpReleaseLoaderLock+0x27, calling ntdll!LdrpReleaseModuleEnumLock
0000009c46eaf200 00007fff0ef2c083 ntdll!LdrpInitializeThread+0x1f3, calling ntdll!LdrpReleaseLoaderLock
0000009c46eaf270 00007fff0ef2bfc3 ntdll!LdrpInitializeThread+0x133, calling ntdll!RtlActivateActivationContextUnsafeFast
0000009c46eaf278 00007fff0ef2bff6 ntdll!LdrpInitializeThread+0x166, calling ntdll!RtlDeactivateActivationContextUnsafeFast
0000009c46eaf2e0 00007fff0ef28fa3 ntdll!_LdrpInitialize+0x93, calling ntdll!NtTestAlert
0000009c46eaf350 00007fff0ef28ec8 ntdll!LdrInitializeThunk+0x18, calling ntdll!NtContinue
0000009c46eaf7c0 00007fff0533fc9f clr!Thread::intermediateThreadProc+0x66, calling clr!_chkstk
0000009c46eaf800 00007fff0e7713d2 kernel32!BaseThreadInitThunk+0x22
0000009c46eaf830 00007fff0ef25444 ntdll!RtlUserThreadStart+0x34

这让我相信线程正在等待一些资源(这是正确的吗?),这就是我真正迷失的地方! 在跟踪中间的那些行中发生了什么:

0000009c46eaefe0 00007fff0c3b1387 00007fff0c3b1387
0000009c46eaf030 00007fff04c111d2 00007fff04c111d2, calling 00007fff04c11070

我的猜测是有些托管的东西在这里,但为什么呢!clrstack不工作呢?  通过查看第一帧,它看起来像是在等待一些资源处理。我看起来手柄是0xa,但我对此并不确定。用句柄查看句柄0xa ff我得到这个:

0:047> !handle 0xa ff
Handle 000000000000000a
  Type          File
  Attributes    0
  GrantedAccess 0x100020:
         Synch
         Execute/Traverse
  HandleCount   2
  PointerCount  65535
  No object specific information available

告诉我,这指向一个文件,但是什么文件,我该怎么从这里继续?看看其他顶级跑道线程给了我相同的图片。

求助的呼声

我知道这是一个巨大的任务,但我真的需要从这里继续前进的帮助。我是在正确的轨道还是我只是在黑暗中探索? 任何帮助将不胜感激!

新闻

制作我们的IT部门之后记录一个perfMon数据集给我一些我觉得有趣的计数器我得出结论:1)我们正在泄漏线程2)GC变得疯狂(可能是因为泄漏)。 关于如何检测导致线程泄漏的原因的任何想法? 请参阅此处的计数器:2

在发现我们正在泄漏线程之后,我一直在查看我们的代码并找到了一些感兴趣的代码:

// Initialize TimerExecutionEveryMinute timer
const double timeConversion = 60 * 1000; //convert one minute to milliseconds

var timer1 = new System.Timers.Timer { Enabled = true, Interval = timeConversion };
timer1.Elapsed += TimerExecutionEveryMinute;

然后:

// Execution every minute
public static void TimerExecutionEveryMinute(object sender, EventArgs e)
{
    var jpsLogger = KernelContainer.Kernel.Get<IJpsLogger>();

    // Initialize MemBag
    MemBag.Log.ActivityIdReset(Guid.NewGuid());
    MemBag.Log.BaseType = "TimerExecution";
    MemBag.Log.BaseClass = "Timer";
    MemBag.Log.BaseMethod = "TimerExecutionEveryMinute";

    // Statistic timer job
    var t1 = jpsLogger.Trace.SpecializedDebug("Analyses.OneMinuteTimer", "One minute timer begin");

        var t2 = jpsLogger.Trace.SpecializedDebug("Analyses.OneMinuteTimer", "    Method.WriteDB begin");
            Method.WriteDB();
        jpsLogger.Trace.SpecializedDebug("Analyses.OneMinuteTimer", "    Method.WriteDB end", t2);

        t2 = jpsLogger.Trace.SpecializedDebug("Analyses.OneMinuteTimer", "    Memory.LogCurrentState begin");
            Memory.LogCurrentState();
        jpsLogger.Trace.SpecializedDebug("Analyses.OneMinuteTimer", "    Memory.LogCurrentState end", t2);


        //Calculates the CPU load based on samples taken at every timer step
        t2 = jpsLogger.Trace.SpecializedDebug("Analyses.OneMinuteTimer", "    CPU load begin");
            CPULogger.LogCpu();
        jpsLogger.Trace.SpecializedDebug("Analyses.OneMinuteTimer", "    CPU load end", t2);

        // Dump log information to file
        t2 = jpsLogger.Trace.SpecializedDebug("Analyses.OneMinuteTimer", "    FileLogger.WriteAsync begin");
            FileLogger.WriteAsync();
        jpsLogger.Trace.SpecializedDebug("Analyses.OneMinuteTimer", "    FileLogger.WriteAsync end", t2);

        jpsLogger.Trace.SpecializedDebug("Analyses.OneMinuteTimer", "One minute timer end", t1);
}

这可能是泄漏线程的地方吗?我相信System.Timers.Timer每次创建事件时都会旋转新线程并且它是线程安全的所以我在执行代码周围创建锁,每隔一分钟执行的代码写入日志文件,我的论文如果访问文件被阻塞,线程不断堆积,这可以解释2

中逻辑线程的线性增加数量

3 个答案:

答案 0 :(得分:1)

我没有答案,但让我尝试提供一些提示。

在你的问题中你提到

  

最近我们的CPU使用量急剧增加......

这是否意味着应用程序之前工作正常?正如您所知道的没有异常的CPU峰值?

如果是这种情况,那么您需要查看最近发生的变化:

  • 您的应用是否已部署任何新代码,特别是分配大量数据的任何内容?

  • 服务器上是否安装了任何更新,如果是,您是否可以审核它们并检查任何Microsoft知识库文章(或只是Google的更新名称,看看是否有任何博客提及它们),这些文章可能会提及您的症状#39;重新遇到。

在您的情况下,垃圾收集器看起来像是乱七八糟。我要做的就是查看已部署的任何新代码 - 可能是代码(或该代码的副作用)正在分配具有GC副作用的对象进入过载状态处理记忆压力。

考虑到这一点,为什么不下载trisl版本的.NET内存管理工具,如RedGate's ANTS Memory Profiler,并在峰值期间拍摄内存转储的一些快照。使用这样的工具可以更容易地在转储之间进行比较,比如告诉你分配/解除分配的对象数量,使用的内存量等等。它可能会给你一个线索。

答案 1 :(得分:1)

好的,这是对你的问题的部分答案。看起来CPU使用率很高有多种原因,但我设法找到了GC疯狂的原因之一。

在我们的代码中,开发人员已经插入了一段代码,该代码每分钟记录当前ram的使用情况(用于监视和调试目的)。这是这样实现的:

GC.GetTotalMemory(true)

在文档中查找此方法告诉我,bool param实际上每次引发此方法时都会强制执行完整的GC - 完整的GC只需一分钟!难怪我们在GC中的CPU和时间飙升了。将此更改为false会使CPU使用率减半。我们仍然存在性能/资源问题,但这是我迈出的一大步。

CPU usage graph with deploy

希望这会对某人有所帮助。

答案 2 :(得分:0)

评论有点长,所以社区维基分享我的经验。

前段时间我们遇到了一个类似的问题,这个问题是由Sybase .NET驱动程序中的代码很差(如Reflector; p中所示)引起的,它锁定了每个数据库命令,而不仅仅是在需要它时(IIRC用于事务) )。通过更细粒度的锁定更新到更新的驱动程序解决了这个问题,从那时起服务器一直运行顺畅。

查看您的信息,它看起来与我们经历的非常相似。 DebugDiag可靠地告诉我锁定是一个问题,并引导我找到解决方案。确保正确配置转储。

如果您有一些可以通过加载(例如JMeter)调试DebugDiag的登台服务器,这也会有所帮助。

另一种方法,虽然单调乏味,但是转储所有已加载的程序集,不包括框架程序集。然后在您最喜欢的反编译器中检查它们,查找您的应用中使用的Monitor.Enter(...)引用(如果您可以模拟外部生产,可能会运行覆盖,以查看使用的内容)。然后尝试识别过度乐观锁定。如果你自己的代码,你可以修复它,否则联系供应商。

相关问题