如何用dtrace进行挂钟分析?或者,如何使用配置文件提供程序计算未运行流程的样本?

时间:2014-02-01 01:28:07

标签: c++ macos profiling stack-trace dtrace

我正在使用C ++为OSX上的第三方主机应用程序开发插件。它被编译为.dylib。我希望在主机应用程序中运行我的插件。

不幸的是,主机调用插件代码的速率取决于插件的(最后)执行时间。这意味着该过程的总体时间可能相对于实时变化很大。因此,使用采样分析器,插件中的“花费的时间”并没有真正基于任何有用的东西,因为它只与进程内的堆栈帧进行比较。如果我提高了插件的性能,那么主机执行插件的模式会相应地改变,并且很难测量插件中的改进。

我可以使用乐器但据我所知,我只能得到相对时间来反对该过程的CPU时间。

我已经使用dtrace来获取主机进程的用户堆栈直方图:

#!/usr/sbin/dtrace -s

#pragma ustackframes 100
#pragma D option quiet

/* $1 is pid  */
/* $2 is sample rate in Hz (e.g. 100)  */
/* $3 is duration (e.g. '20s')  */

profile-$2
/pid == $1 && arg1/
{
  @[ustack()] = count();
}

tick-$3
{
  exit(0);
}

这样可行,但它仍然只提供与进程时间相关的样本,因为谓词只匹配,然后进程在用户空间中。即使删除&& arg1条件以在进程内核调用期间触发它也无济于事。

我真正想知道的是,有多少profile-n个样本导致流程根本没有运行。然后我可以将插件中的数字与样本总数进行比较,并为插件的函数获取绝对样本值。这让我想知道 - 假设所要求的profile-n采样率得到尊重是否安全?我可以简单地花时间*采样率并用它来计算“关闭过程”时间吗?我曾经假设,比如1500Hz,它正在丢弃样本并以其他未知的速率运行,但是如果我可以确定它是以1500Hz采样那么我可以从中计算出“进程外”时间。 / p>

或者,是否有一种已知的方法可以使用dtrace进行挂钟分析?

1 个答案:

答案 0 :(得分:1)

  

这让我想知道 - 假设所要求的profile-n采样率得到尊重是否安全?

在Solaris上,它不能保证得到尊重:一些旧硬件缺乏对基于任意分辨率定时器的中断的必要支持。我认为相同的理论限制适用于OS X的DTrace。

在任何情况下,您都可以自己测试计时器分辨率。配置文件提供程序的documentation包含一个适当的脚本,并且有关于该主题的更多信息。这是另一个解决您具体问题的脚本:

bash-3.2# cat test.d
uint64_t last;

profile-1500
/cpu == 0/
{
    now = timestamp;
    @ = lquantize(now - last, 500000, 800000, 30000);
    last = now;
}   

tick-1
/i++ == 10/
{
    exit(0);
}
bash-3.2# dtrace -qs test.d


           value  ------------- Distribution ------------- count    
          560000 |                                         0        
          590000 |@@@                                      1041     
          620000 |@@@@@@@@@@                               4288     
          650000 |@@@@@@@@@@@@@@                           5680     
          680000 |@@@@@@@@@@                               3999     
          710000 |@@@@                                     1451     
          740000 |                                         0        
          770000 |                                         0        
       >= 800000 |                                         1        

bash-3.2# 

请注意,在实践中,您应该以一个素数的频率进行采样:这会阻止您与其他定期安排的系统活动同步。

根据评论中的讨论,以下是如何衡量在给定函数内花费的时间:

pid$target:mylib:myfunc:entry
/!self->depth/
{
    self->depth = ustackdepth;      /* beware recursion */
    self->start_time = timestamp;   /* for relative wall time calculations */
    self->start_vtime = vtimestamp; /* CPU time */      
}

pid$target:mylib:myfunc:return
/ustackdepth == self->depth/
{
    printf("%d ms (real) %d ms (CPU)\n",
        (timestamp - self->start_time) / 1000000,
        (vtimestamp - self->start_vtime) / 1000000);
    self->depth = 0;
}   

如果以高频率调用该函数,那么显然您可以保持经过时间的聚合,例如计算函数的平均成本。

完全有可能对库中的所有函数执行类似的练习,尽管从递归和尾调用优化中消除虚假结果可能是一项繁重的任务。为了更有用,您可能还希望从函数的成本中排除调用堆栈所花费的时间;这使得工作变得更加困难(但并非不可能)。因此,凭借上述手段创建客观基准,我更倾向于坚持分析方法,可能类似

# cat sample.d 
profile-997
/pid == $target && arg1 >= $1 && arg1 < $2/
{
    @[ufunc(arg1)] = count();
}

END
{
    trunc(@,5);
    exit(0);
}
#

这捕获了给定内存区域中最常见的五个函数。例如(并在Solaris上使用pmap来查找libc),

# dtrace -qs sample.d -p `pgrep -n firefox` 0xfc090000 0xfc200000
^C

  libc.so.1`mutex_lock_impl                                        35
  libc.so.1`clear_lockbyte                                         46
  libc.so.1`gettimeofday                                           71
  libc.so.1`memset                                                 73
  libc.so.1`memcpy                                                170
# 

这证明了采样效益的一个很好的例证:memcpy()memset()在汇编中手工编码 - 即我们发现最耗时的函数已经已经过优化。