ICorProfilerCallback2:CLR分析器不记录所有离开呼叫

时间:2015-06-20 06:15:16

标签: c# .net windows performance clr-profiling-api

我正在尝试编写一个在一个进程中记录所有.Net方法调用的探查器。我们的目标是使其具有高性能,并让我们在内存中说最后5-10分钟(固定缓冲区,循环覆盖旧信息),直到用户触发该信息写入磁盘。预期用途是追踪很少再现性能问题。

我从https://github.com/appneta/SimpleCLRProfiler开始使用SimpleCLRProfiler项目。分析器使用.Net分析的 ICorProfilerCallback2 回调接口。我得到了它在我的环境中编译和工作(Win 8.1,.Net 4.5,VS2012)。但是,我注意到有时会丢失Leave调用,而是记录了Enter调用。 Console.WriteLine调用的示例(我将DbgView的输出减少到最低限度的理解):

Line 1481: Entering System.Console.WriteLine
Line 1483: Entering SyncTextWriter.WriteLine
Line 1485: Entering System.IO.TextWriter.WriteLine
Line 1537: Leaving SyncTextWriter.WriteLine

两个进入的电话没有相应的离职电话。配置文件.Net代码如下所示:

Console.WriteLine("Hello, Simple Profiler!");

相关的SimpleCLRProfiler方法是:

HRESULT CSimpleProfiler::registerGlobalCallbacks() 
{
   HRESULT hr = profilerInfo3->SetEnterLeaveFunctionHooks3WithInfo(
      (FunctionEnter3WithInfo*)MethodEntered3, 
      (FunctionEnter3WithInfo*)MethodLeft3, 
      (FunctionEnter3WithInfo*)MethodTailcall3);

   if (FAILED(hr))
      Trace_f(L"Failed to register global callbacks (%s)", _com_error(hr).ErrorMessage());

   return S_OK;
}

void CSimpleProfiler::OnEnterWithInfo(FunctionID functionId, COR_PRF_ELT_INFO eltInfo)
{
    MethodInfo info;
   HRESULT hr = info.Create(profilerInfo3, functionId);
   if (FAILED(hr)) 
      Trace_f(L"Enter() failed to create MethodInfo object (%s)", _com_error(hr).ErrorMessage());

   Trace_f(L"[%p] [%d] Entering %s.%s", functionId, GetCurrentThreadId(), info.className.c_str(), info.methodName.c_str());
}

void CSimpleProfiler::OnLeaveWithInfo(FunctionID functionId, COR_PRF_ELT_INFO eltInfo)
{
   MethodInfo info;
   HRESULT hr = info.Create(profilerInfo3, functionId);
   if (FAILED(hr)) 
      Trace_f(L"Enter() failed to create MethodInfo object (%s)", _com_error(hr).ErrorMessage());

   Trace_f(L"[%p] [%d] Leaving %s.%s", functionId, GetCurrentThreadId(), info.className.c_str(), info.methodName.c_str());
}

有没有人知道,为什么.Net Profiler不会对所有离开方法执行Leave调用?顺便说一句,我检查了OnLeaveMethod在由于异常导致的任何跟踪之前没有意外退出。它没有。

谢谢,Christoph

1 个答案:

答案 0 :(得分:3)

由于stakx似乎没有回到我的问题提供正式答案(并获得信用)所以我会为他做: 正如斯塔克斯暗示的那样,我没有记录尾部通话。事实上,我甚至都没有意识到这个概念,所以我完全忽略了这个钩子方法(它已连线但是空的)。我在这里找到了对尾调用的一个很好的解释:David Broman's CLR Profiling API Blog: Enter, Leave, Tailcall Hooks Part 2: Tall tales of tail calls

我引用上面的链接:

  

尾调用是一种编译器优化,可以保存指令的执行并节省堆栈内存的读写。当函数做的最后一件事是调用另一个函数(并且其他条件是有利的)时,编译器可以考虑将该调用实现为尾调用而不是常规调用。

考虑以下代码:

static public void Main() {
    Helper();
}

static public void Helper() {
    One();
    Three();
}

static public void Three() {
    ...
}

当调用方法 Three 时,没有尾调用优化,堆栈将如下所示。

Three
Helper
Main

使用尾调用优化,堆栈如下所示:

Three
Main

因此,在调用 Three 之前,由于优化,方法 Helper 已经弹出了堆栈,因此堆栈上只有一个方法(更少)内存使用情况)以及一些执行和内存写入操作已保存。