我正在尝试创建一个适用于linux的采样分析器,我不确定如何发送中断或如何获取程序计数器(pc)以便我可以找到程序在我中断时的重点。
我尝试过使用signal(SIGUSR1,Foo *)并调用backtrace,但是当我引发(SIGUSR1)而不是正在运行程序的线程时,我得到了我所在线程的堆栈。 我不确定这是否是正确的方式......
有什么建议吗?
答案 0 :(得分:3)
如果你必须编写一个分析器,我建议你使用一个好的(Zoom)作为你的模型,而不是一个坏的( gprof ) 。 这是它的特点。
有两个阶段。首先是数据收集阶段:
当它需要一个样本时,它会读取整个调用堆栈,而不仅仅是程序计数器。
即使由于I / O,睡眠或其他原因导致进程被阻止,也可以采样。
您可以打开/关闭采样,以便在您关心的时间内只采样。例如,在等待用户输入内容时,抽样是没有意义的。
其次是数据呈现阶段。 你拥有的是堆栈样本的集合,其中堆栈样本是内存地址的向量,几乎都是返回地址。 每个返回地址表示函数中的一行代码,除非它在某些系统例程中没有符号信息。
有用信息的关键部分是驻留分数(通常表示为百分比)。 如果总共有 m 堆栈样本,并且代码行L出现在样本的 n 的任何位置,则其驻留分数 n / m < / em>的。 即使L在样本上出现的次数多一次也是如此,这仍然只是它出现的一个样本。 居住分数的重要性在于它直接表明时间陈述L负责的部分。 如果你已经采用 m = 1000 样本,并且L出现在 n = 300 上,那么L's驻留率为300/1000或30%。 这意味着如果可以删除L,则总时间将减少30%。 它通常称为包含百分比。
您不仅可以确定代码行的居住分数,还可以确定您可以描述的其他内容。例如,代码行L在某个函数F内。 因此,您可以确定函数的驻留分数,而不是代码行。 这将为您提供包含功能的百分比。 您可以查看函数对,比如您看到函数F调用函数G的样本分数。 这将为您提供构成调用图的信息。
您可以从堆栈样本中获取各种信息。 经常看到的是“蝴蝶视图”,其中您在一行L或函数F上有一个“焦点”,并且在一侧显示堆栈样本中紧邻其上方的所有行或函数,另一侧紧接着它下方的所有功能线。 在每个上面,您都可以显示居住率。 您可以在此处单击以尝试查找具有高驻留分数的代码行,您可以找到消除或减少的方法。 这就是你加速代码的方式。
无论你做什么输出,我认为允许用户实际检查随机选择的少数这些非常重要。 它们传达的信息远远超过任何凝聚信息的方法。
重要的是要了解分析器应该做什么,知道不要做什么也很重要,即使许多其他分析器都这样做:
自我时间。一个无用的号码。看一些合理大小的程序,你会明白为什么。
调用计数。找到具有高居住率的代码没有任何帮助,无论如何你无法单独获得样本。
高频采样。令人惊讶的是,有多少人,当然是探查器构建者,认为获得批次样本非常重要。假设线L在1000个样本的30%上。然后它的真实包容百分比是30 +/- 1.4%。另一方面,如果它是 10 样本的30%,则其包含百分比为30 +/- 14%。 它仍然非常大 - 足以修复。在大多数剖析器中发生的事情是人们认为他们需要“数值精度”,因此他们需要大量样本并累积他们所谓的“统计”,然后抛出远离样品。这就像挖掘钻石,称重钻石,扔掉钻石一样。真正的价值在于样本本身,因为它们告诉你问题是什么。
答案 1 :(得分:2)
您可以使用目标线程的pthread_kill
和tid(gettid()
)向特定线程发送信号。
创建简单剖析器的正确方法是使用setitimer
,它可以发送周期信号(SIGALRM
或SIGPROF
),例如,每10 ms;或posix定时器(timer_create,timer_settime或timerfd),无需单独的线程来发送分析信号。检查google-perftools(gperftools)的来源,他们使用setitimer或posix计时器并收集带有回溯的配置文件。
gprof还使用setitimer
来实现cpu时间分析(9.1 Implementation of Profiling - “Linux 2.0 ..安排内核定期向进程发送信号(通常通过setitimer())” )。
例如:gperftools来源中setitimer
的代码搜索结果:https://code.google.com/p/gperftools/codesearch#search/&q=setitimer&sq=package:gperftools&type=cs
void ProfileHandler::StartTimer() {
if (!allowed_) {
return;
}
struct itimerval timer;
timer.it_interval.tv_sec = 0;
timer.it_interval.tv_usec = 1000000 / frequency_;
timer.it_value = timer.it_interval;
setitimer(timer_type_, &timer, 0);
}
您应该知道setitimer在fork
和clone
方面存在问题;它不适用于多线程应用程序。尝试创建帮助包装器:http://sam.zoy.org/writings/programming/gprof.html(错误的)但我不remember,它是否正常工作(setitimer
通常发送进程范围的信号,而不是线程范围的)。 UPD:似乎自Linux内核2.6.12,setitimer's signal is directed to the process as whole(任何线程都可以得到它)。
要将来自timer_create的信号指向特定线程,您需要gettid()
(#include <sys/syscall.h>
,syscall(__NR_gettid)
)和SIGEV_THREAD_ID flag
。不检查如何使用thread_create创建周期性posix计时器(可能使用timer_settime and non-zero it_interval)。
PS:wikibooks中有一些概要分析概述:http://en.wikibooks.org/wiki/Introduction_to_Software_Engineering/Tools/Profiling