前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >一文了解perf script中[unknwon]出现的原因

一文了解perf script中[unknwon]出现的原因

作者头像
AshinZ
发布2023-11-01 17:04:17
3070
发布2023-11-01 17:04:17
举报

大家好,我是程栩,一个专注于性能的大厂程序员,分享包括但不限于计算机体系结构、性能优化、云原生的知识。

今天我们来聊一聊perf的相关命令,更进一步的了解perf。本文是perf系列的第七篇文章,后续会继续介绍perf,包括用法、原理和相关的经典文章。

在前面的文章中,我们介绍了可以通过perf script命令解析perf.data,可以通过perf report命令查看函数的调用占比,可以通过perf annotate命令查看热点汇编,那么这些能力perf究竟是怎么实现的呢?

为了解答这个问题,笔者尝试过去阅读源码,但是源码阅读需要非常多的时间,容易在一些细枝末节的问题上纠结。因此,笔者尝试通过strace和对比实验的方法来尝试猜测以下几个问题的答案:

  • perf是如何将perf.data中的地址转换成函数名的?为什么解析出来经常出现[unknown]
  • perf report是如何进行函数调用占比的计算的?
  • perf annotate是如何得到函数的热点汇编的?

今天我们主要尝试解答第一个问题。

工欲善其事必先利其器

首先我们来简单的回顾一下perf scriptperf reportperf annotate

perf script可以帮助我们把perf.data转换成可以阅读的形式:

代码语言:javascript
复制
[root@VM-16-2-centos ~]# perf script
            perf 3376223 [000] 13088049.893744: sched:sched_stat_runtime: comm=perf pid=3376223 runtime=25348 [ns] vruntime=22835234754203 [ns]
            perf 3376223 [000] 13088049.893747:       sched:sched_waking: comm=migration/0 pid=12 prio=0 target_cpu=000
            perf 3376223 [000] 13088049.893748: sched:sched_stat_runtime: comm=perf pid=3376223 runtime=4759 [ns] vruntime=22835234758962 [ns]
            perf 3376223 [000] 13088049.893748:       sched:sched_switch: prev_comm=perf prev_pid=3376223 prev_prio=120 prev_state=R+ ==> next_comm=migration/0 next_pid=12 next_prio=0
     migration/0    12 [000] 13088049.893750: sched:sched_migrate_task: comm=perf pid=3376223 prio=120 orig_cpu=0 dest_cpu=1

perf report可以图形化的展示收集到的调用栈信息:

perf report

perf annotate可以输出perf.data文件对应的汇编信息:

perf annotate

我们再介绍一下本文中经常用到的strace工具。strace是一种Linux系统下的工具,它可以帮助你跟踪和调试进程的系统调用。系统调用是应用程序和操作系统之间的接口,它们允许应用程序访问操作系统提供的各种服务。strace可以记录这些系统调用,包括它们的参数和返回值,以及调用的时间和持续时间。

举个例子,如果你想了解一个程序为什么崩溃了,你可以使用strace来查看它的系统调用。你只需要在终端中输入"strace <你的程序>"即可开始跟踪。strace会输出程序执行期间的所有系统调用,你可以通过查看输出来找到导致崩溃的原因。

perf script解析:众里寻他千百度

现在,我们来探索第一个问题:perf script是如何解析perf.data的?

在机器上我们通过一个c++程序来制造负载:

代码语言:javascript
复制
#include <iostream>
#include <cmath>
#include <chrono>

using namespace std;

void function1() {
    for (int i = 0; i < 1000000; i++) {
        pow(i, 2);
    }
}

void function2() {
    for (int i = 0; i < 1000000; i++) {
        sin(i);
    }
}

int main() {
    auto start = chrono::high_resolution_clock::now();

    for (int i = 0; i < 1000; i++) {
        function1();
        function2();
    }

    auto end = chrono::high_resolution_clock::now();
    auto duration = chrono::duration_cast<chrono::milliseconds>(end - start);

    cout << "Execution time: " << duration.count() << " milliseconds" << endl;

    return 0;
}

记得编译的时候带上-g选项:

代码语言:javascript
复制
g++ -g -o test test.c

追踪转换一条龙:

代码语言:javascript
复制
sudo perf record -ag -F 999 -- sleep 2
sudo perf script

可以看到成功的抓到了我们的负载:

成功解析的调用栈

尝试用strace追踪perf script的过程并将结果保存到文件中:

代码语言:javascript
复制
 sudo strace -o strace perf script

在结果中,我们找到了和这个负载文件有关的一些调用:

strace结果

stat("/root/workplace/test", {st_mode=S_IFREG|0755, st_size=60016, ...}) = 0:这个系统调用是用来获取指定文件的元数据信息,包括文件的权限、大小等等。在这个例子中,它返回了0,表示获取成功。其中st_mode表示文件的权限和类型,st_size表示文件的大小。

openat(AT_FDCWD, "/root/workplace/test", O_RDONLY) = 41:这个系统调用是用来打开指定的文件,其中O_RDONLY表示以只读方式打开。在这个例子中,它返回了文件描述符41,表示打开成功。

可以看到perf script去读了记录路径的文件,那如果这个文件不存在会发生什么呢?我们将负载程序移动走,重新perf script,依然看到了对应的结果:

成功解析调用栈

这是为什么呢?我们再用strace抓取一下:

移除源文件后strcae结果

可以看到这里多了几行,后面去读取的什么.debug文件里的东西是什么?注意到后面有个build-id,好像perf中也有和这个相关的功能,我们不妨来看看build-id是什么:buildid是一个用于标识可执行文件和共享库的唯一标识符。它是由编译器在编译时生成的,通常包含在ELF格式的可执行文件和共享库中。buildid可以用来识别不同版本的程序,以及检查程序是否被篡改过。在调试时,它还可以用来定位程序崩溃的原因。我们用file查看负载文件信息:

代码语言:javascript
复制
[root@VM-16-2-centos workplace]# file test1
test1: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=bdb424b13087ec31173954f3be40fa6498d1bc95, with debug_info, not stripped

可以看到后面的buildid和系统调用中后面去寻找的目录是一致的:

寻找buildid

那如果这里也找不到会怎么样呢?我们通过perf buildid-cache功能清除掉全部的缓存:

查看buildid-list

清理buildid-cache

我们再去通过straceperf script来进行转换:

解析调用栈失败

可以看到这里出现了多个unknown,说明此时perf script已经搞不清楚这里到底是什么了。我们来看看strace的结果:

清理buildid-cache后的strace结果

可以看出来,当源目录和$HOME/.debug/.build-id目录下不存在时,perf script还会去找/usr/lob/debug下的一些文件,尝试去寻找到源文件,或者说源文件对应的debug信息。

至此,我们可以得出一个结论:perf script需要依赖源文件的信息进行解析,首先会去寻找源目录下的文件,当找不到时会去寻找$HOME/.debug目录下的文件,最后会去/usr/lib下的信息,当都找不到时,perf script会解析成[unknown]

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2023-06-18,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 程栩的性能优化笔记 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 工欲善其事必先利其器
  • perf script解析:众里寻他千百度
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档