Mic*_*kan 3 linux performance profiling performance-testing perf
前段时间,我问了以下问题“如何计算进程 id 的执行指令数(包括子进程)”,@M-Iduoad 好心提供了一个解决方案来pgrep捕获所有子 PID 并将其与 perf stat 中的 -p 一起使用。效果很好!
然而,我遇到的一个问题是多线程应用程序以及当生成新线程时。由于我不是算命先生(太糟糕了!),我不知道tid新生成的线程,因此我无法将它们添加到perf stat-p 或 -t 参数中。
举个例子,假设我有一个多线程 Nodejs 服务器(作为容器部署在 Kubernetes 之上),具有以下内容pstree:
root@node2:/home/m# pstree -p 4037791\nnode(4037791)\xe2\x94\x80\xe2\x94\xac\xe2\x94\x80sh(4037824)\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80node(4037825)\xe2\x94\x80\xe2\x94\xac\xe2\x94\x80{node}(4037826)\n \xe2\x94\x82 \xe2\x94\x9c\xe2\x94\x80{node}(4037827)\n \xe2\x94\x82 \xe2\x94\x9c\xe2\x94\x80{node}(4037828)\n \xe2\x94\x82 \xe2\x94\x9c\xe2\x94\x80{node}(4037829)\n \xe2\x94\x82 \xe2\x94\x9c\xe2\x94\x80{node}(4037830)\n \xe2\x94\x82 \xe2\x94\x94\xe2\x94\x80{node}(4037831)\n \xe2\x94\x9c\xe2\x94\x80{node}(4037805)\n \xe2\x94\x9c\xe2\x94\x80{node}(4037806)\n \xe2\x94\x9c\xe2\x94\x80{node}(4037807)\n \xe2\x94\x9c\xe2\x94\x80{node}(4037808)\n \xe2\x94\x9c\xe2\x94\x80{node}(4037809)\n \xe2\x94\x9c\xe2\x94\x80{node}(4037810)\n \xe2\x94\x9c\xe2\x94\x80{node}(4037811)\n \xe2\x94\x9c\xe2\x94\x80{node}(4037812)\n \xe2\x94\x9c\xe2\x94\x80{node}(4037813)\n \xe2\x94\x94\xe2\x94\x80{node}(4037814) \nRun Code Online (Sandbox Code Playgroud)\n当然,我可以使用以下perf stat命令来观察其线程:
perf stat --per-thread -e instructions,cycles,task-clock,cpu-clock,cpu-migrations,context-switches,cache-misses,duration_time -p $(pgrep --ns 4037791 | paste -s -d ",")\nRun Code Online (Sandbox Code Playgroud)\n它适用于单线程 Nodejs 应用程序。但在多线程服务的情况下,一旦收到请求,输出pstree将如下所示:
root@node2:/home/m# pstree -p 4037791\nnode(4037791)\xe2\x94\x80\xe2\x94\xac\xe2\x94\x80sh(4037824)\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80node(4037825)\xe2\x94\x80\xe2\x94\xac\xe2\x94\x80{node}(4037826)\n \xe2\x94\x82 \xe2\x94\x9c\xe2\x94\x80{node}(4037827)\n \xe2\x94\x82 \xe2\x94\x9c\xe2\x94\x80{node}(4037828)\n \xe2\x94\x82 \xe2\x94\x9c\xe2\x94\x80{node}(4037829)\n \xe2\x94\x82 \xe2\x94\x9c\xe2\x94\x80{node}(4037830)\n \xe2\x94\x82 \xe2\x94\x9c\xe2\x94\x80{node}(4037831)\n \xe2\x94\x82 \xe2\x94\x9c\xe2\x94\x80{node}(1047898)\n \xe2\x94\x82 \xe2\x94\x9c\xe2\x94\x80{node}(1047899)\n \xe2\x94\x82 \xe2\x94\x9c\xe2\x94\x80{node}(1047900)\n \xe2\x94\x82 \xe2\x94\x9c\xe2\x94\x80{node}(1047901)\n \xe2\x94\x82 \xe2\x94\x9c\xe2\x94\x80{node}(1047902)\n \xe2\x94\x82 \xe2\x94\x9c\xe2\x94\x80{node}(1047903)\n \xe2\x94\x82 \xe2\x94\x9c\xe2\x94\x80{node}(1047904)\n \xe2\x94\x82 \xe2\x94\x9c\xe2\x94\x80{node}(1047905)\n \xe2\x94\x82 \xe2\x94\x9c\xe2\x94\x80{node}(1047906)\n \xe2\x94\x82 \xe2\x94\x9c\xe2\x94\x80{node}(1047907)\n \xe2\x94\x82 \xe2\x94\x9c\xe2\x94\x80{node}(1047908)\n \xe2\x94\x82 \xe2\x94\x9c\xe2\x94\x80{node}(1047909)\n \xe2\x94\x82 \xe2\x94\x9c\xe2\x94\x80{node}(1047910)\n \xe2\x94\x82 \xe2\x94\x9c\xe2\x94\x80{node}(1047911)\n \xe2\x94\x82 \xe2\x94\x9c\xe2\x94\x80{node}(1047913)\n \xe2\x94\x82 \xe2\x94\x9c\xe2\x94\x80{node}(1047914)\n \xe2\x94\x82 \xe2\x94\x9c\xe2\x94\x80{node}(1047919)\n \xe2\x94\x82 \xe2\x94\x9c\xe2\x94\x80{node}(1047920)\n \xe2\x94\x82 \xe2\x94\x9c\xe2\x94\x80{node}(1047921)\n \xe2\x94\x82 \xe2\x94\x94\xe2\x94\x80{node}(1047922)\n \xe2\x94\x9c\xe2\x94\x80{node}(4037805)\n \xe2\x94\x9c\xe2\x94\x80{node}(4037806)\n \xe2\x94\x9c\xe2\x94\x80{node}(4037807)\n \xe2\x94\x9c\xe2\x94\x80{node}(4037808)\n \xe2\x94\x9c\xe2\x94\x80{node}(4037809)\n \xe2\x94\x9c\xe2\x94\x80{node}(4037810)\n \xe2\x94\x9c\xe2\x94\x80{node}(4037811)\n \xe2\x94\x9c\xe2\x94\x80{node}(4037812)\n \xe2\x94\x9c\xe2\x94\x80{node}(4037813)\n \xe2\x94\x94\xe2\x94\x80{node}(4037814)\nRun Code Online (Sandbox Code Playgroud)\n因此,我之前的perf stat命令不会捕获新生成的线程的统计信息。我的意思是,它可能会捕获累积的指令,但它绝对不会以“每线程”格式显示。
有没有什么方法可以--per-thread在 perf stat 中使用并捕获多线程应用程序中新生成的线程的统计信息?它似乎只适用于-p或-t遵循启动时已存在的一组固定线程perf,并且不会遵循新线程。
这里perf record有一个类似的问题,但我正在使用perf stat. 另外,这似乎并没有通过线程分离记录的配置文件,所以它只是相当于除非perf stat node ... 有一种方法来处理记录的数据以在事后通过线程将其分离出来?
perf如果还有其他有效的方法,则不是必需的:任何其他可以帮助我动态计算给定 PID(包括新生成的线程)的每个线程的“指令、周期、任务时钟、CPU 时钟、CPU 迁移、上下文切换、缓存未命中”的潜在解决方案都是可以接受的,无论是使用perf或其他任何东西!
perf record -s和的组合perf report -T应该可以为您提供所需的信息。
为了进行演示,请采用以下使用具有明确定义的指令计数的线程的示例代码:
#include <cstdint>
#include <thread>
void work(int64_t count) {
for (int64_t i = 0; i < count; i++);
}
int main() {
std::thread first(work, 100000000ll);
std::thread second(work, 400000000ll);
std::thread third(work, 800000000ll);
first.join();
second.join();
third.join();
}
Run Code Online (Sandbox Code Playgroud)
(无需优化即可编译!)
现在,用作perf record前缀命令。它将遵循所有生成的进程和线程。
$ perf record -s -e instructions -c 1000000000 ./a.out
[ perf record: Woken up 1 times to write data ]
[ perf record: Captured and wrote 0.003 MB perf.data (5 samples) ]
Run Code Online (Sandbox Code Playgroud)
为了更好地显示统计数据:
$ perf report -T
[... snip ...]
# PID TID instructions:u
270682 270683 500003888
270682 270684 2000001866
270682 270685 4000002177
Run Code Online (Sandbox Code Playgroud)
的参数perf record有点棘手。-s用相当精确的数字写入单独的记录 - 它们不依赖于指令样本(每 1000000000 条指令生成)。然而,即使找不到单个样本,也会失败perf report。-T因此,您需要设置-c至少触发一次的指令样本计数(或频率)。任何示例都可以,不需要每个线程一个示例。
或者,您可以查看来自 的原始记录perf.data。然后你实际上可以告诉perf record不要收集任何样本。
$ perf record -s -e instructions -n ./a.out
[ perf record: Woken up 1 times to write data ]
[ perf record: Captured and wrote 0.003 MB perf.data ]
Run Code Online (Sandbox Code Playgroud)
但您需要过滤掉相关记录,并且可能还有其他记录需要您总结。
$ perf script -D | grep PERF_RECORD_READ | grep -v " 0$"
# Annotation by me PID TID
213962455637481 0x760 [0x40]: PERF_RECORD_READ: 270887 270888 instructions:u 500003881
213963194850657 0x890 [0x40]: PERF_RECORD_READ: 270887 270889 instructions:u 2000001874
213964190418415 0x9c0 [0x40]: PERF_RECORD_READ: 270887 270890 instructions:u 4000002175
Run Code Online (Sandbox Code Playgroud)