跟踪在概念上与插桩化非常相似,但略有不同。代码插桩化假设用户可以编排他们应用程序的代码。另一方面,跟踪依赖于程序的外部依赖项的现有插桩化。例如,strace
工具使我们能够跟踪系统调用,并可以被视为对Linux内核的插桩化。英特尔处理器跟踪(见附录D)使您能够记录程序执行的指令,并可以被视为对CPU的插桩化。跟踪可以从事先适当插桩化的组件中获得,并且不受更改的影响。跟踪通常被用作黑匣子方法,其中用户无法修改应用程序的代码,但他们希望了解程序在幕后执行的操作。
@lst:strace提供了使用Linux strace
工具跟踪系统调用的示例,显示了运行git status
命令时输出的前几行。通过使用strace
跟踪系统调用,可以得知每个系统调用的时间戳(最左边的列),其退出状态以及每个系统调用的持续时间(在尖括号内)。
代码清单:使用strace跟踪系统调用
$ strace -tt -T -- git status
17:46:16.798861 execve("/usr/bin/git", ["git", "status"], 0x7ffe705dcd78
/* 75 vars */) = 0 <0.000300>
17:46:16.799493 brk(NULL) = 0x55f81d929000 <0.000062>
17:46:16.799692 access("/etc/ld.so.nohwcap", F_OK) = -1 ENOENT
(No such file or directory) <0.000063>
17:46:16.799863 access("/etc/ld.so.preload", R_OK) = -1 ENOENT
(No such file or directory) <0.000074>
17:46:16.800032 openat(AT_FDCWD, "/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
<0.000072>
17:46:16.800255 fstat(3, {st_mode=S_IFREG|0644, st_size=144852, ...}) = 0
<0.000058>
17:46:16.800408 mmap(NULL, 144852, PROT_READ, MAP_PRIVATE, 3, 0)
= 0x7f6ea7e48000 <0.000066>
17:46:16.800619 close(3) = 0 <0.000123>
...
跟踪的开销非常取决于我们尝试跟踪的内容。例如,如果我们跟踪的程序几乎不进行系统调用,那么在strace
下运行它的开销将接近零。另一方面,如果我们跟踪的程序严重依赖于系统调用,那么开销可能会非常大,例如,增加了100倍1。此外,跟踪可能会生成大量数据,因为它不会跳过任何样本。为了补偿这一点,跟踪工具提供了过滤器,使您能够将数据收集限制为特定的时间片段或特定代码段。
通常,类似于插桩化的跟踪用于探查系统中的异常情况。例如,您可能想要确定在程序出现10秒不响应的情况下应用程序中发生了什么。正如您将在后面看到的,采样方法并不是为此设计的,但是通过跟踪,您可以看到是什么导致了程序不响应。例如,使用英特尔PT,您可以重构程序的控制流并确切地知道执行了哪些指令。
跟踪对调试也非常有用。其底层特性支持基于记录的跟踪的“记录和重放”用例。Mozilla的一个这样的工具是rr调试器,它执行进程的记录和重放,支持向后单步执行等等。大多数跟踪工具都能够为事件添加时间戳,这使我们能够与在那段时间内发生的外部事件进行相关。也就是说,当我们观察到程序中出现故障时,我们可以查看我们应用程序的跟踪,并将此故障与在该时间段内整个系统中发生的情况进行关联