tracer的使用
- 性能工具
- 2024-08-27
- 210热度
- 0评论
irqsoff
当关闭中断时,CPU就无法响应中断了(NMI和SMI除外),无法响应外部事件做出反应。这会阻止定时器触发或鼠标中断触发,导致系统延迟。
irqsoff跟踪器跟踪中断被禁用的时间,当达到新的最大延迟时,跟踪器会保存导致该延迟点的跟踪,一边每次达到新的最大值,旧的保存的跟踪会被丢弃,新的跟踪会被保存。如果要重置最大值,用echo 0写到tracing_max_latency中。
# echo 0 > options/function-trace
# echo irqsoff > current_tracer
# echo 1 > tracing_on
# echo 0 > tracing_max_latency
# ls -ltr
[...]
# echo 0 > tracing_on
# cat trace
上图示例可以最大延迟为3603us,在_default_idle_call和__do_softirq中禁用了中断。上面示例中,将funciton-trace关掉了,没有启用此tracer过程的函数跟踪。如果设置function-trace,就会有很多的打印,会将此过程中的函数执行trace打印出来。echo 1 > options/function-trace。
如果想要以函数图调用的方式打印,那么with echo 1 > options/display-graph。
function
function为函数跟踪器,可以从调试文件系统启动函数跟踪器,echo function > current_tracer。
# echo function > current_tracer
# echo 1 > tracing_on
# usleep 1
# echo 0 > tracing_on
# cat trace
需要注意的是,function tracer使用环形缓冲区来存储上述数据,最新数据可能会覆盖最旧的数据,有时使用echo 来停止跟踪器是不够的,因为跟踪可能会覆盖您想要记录的数据。因此最好直接从程序中禁用跟踪,允许您在到达你感兴趣的部分时停止跟踪,如果要从C程序禁用跟踪,可以使用类似下面代码;
int trace_fd;
[...]
int main(int argc, char *argv[]) {
[...]
trace_fd = open(tracing_file("tracing_on"), O_WRONLY);
[...]
if (condition_hit()) {
write(trace_fd, "0", 1);
}
[...]
}
单个线程的跟踪,
# cat set_ftrace_pid
no pid
# echo 3111 > set_ftrace_pid
# cat set_ftrace_pid
3111
# echo function > current_tracer
# cat trace | head
如果想要trace一个函数在启动运行时,可以使用下面的示例程序。
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#define _STR(x) #x
#define STR(x) _STR(x)
#define MAX_PATH 256
const char *find_tracefs(void)
{
static char tracefs[MAX_PATH+1];
static int tracefs_found;
char type[100];
FILE *fp;
if (tracefs_found)
return tracefs;
if ((fp = fopen("/proc/mounts","r")) == NULL) {
perror("/proc/mounts");
return NULL;
}
while (fscanf(fp, "
STR(MAX_PATH)
"s
tracefs, type) == 2) {
if (strcmp(type, "tracefs") == 0)
break;
}
fclose(fp);
if (strcmp(type, "tracefs") != 0) {
fprintf(stderr, "tracefs not mounted");
return NULL;
}
strcat(tracefs, "/tracing/");
tracefs_found = 1;
return tracefs;
}
const char *tracing_file(const char *file_name)
{
static char trace_file[MAX_PATH+1];
snprintf(trace_file, MAX_PATH, "
return trace_file;
}
int main (int argc, char **argv)
{
if (argc < 1)
exit(-1);
if (fork() > 0) {
int fd, ffd;
char line[64];
int s;
ffd = open(tracing_file("current_tracer"), O_WRONLY);
if (ffd < 0)
exit(-1);
write(ffd, "nop", 3);
fd = open(tracing_file("set_ftrace_pid"), O_WRONLY);
s = sprintf(line, "
write(fd, line, s);
write(ffd, "function", 8);
close(fd);
close(ffd);
execvp(argv[1], argv+1);
}
return 0;
}
当然也可以使用简单的脚步来实现
Or this simple script!
::
#!/bin/bash
tracefs=`sed -ne \'s/^tracefs \\(.*\\) tracefs.*/\\1/p\' /proc/mounts`
echo 0 > tracefs/tracing_on
echo>tracefs/set_ftrace_pid
echo function > tracefs/current_tracer
echo 1>tracefs/tracing_on
exec \"$@\"
function graph tracer
function graph tracer与function tracer类似,不同之处在于它会在函数进入和退出时对其进行探测,这是通过每个task_struct中使用动态分配的返回地址堆栈来实现的。在函数进入时,跟踪器会覆盖跟踪每个函数的返回地址以设置自定义探测器,因此原始返回地址存储在task_struct中的返回地址堆栈中。在函数两端进行探测可实现特殊功能,例如:测量函数的执行时间,拥有可靠调用堆栈来绘制函数的调用图。这种跟踪器在以下几种情况很有用:
- 找到奇怪内核行为的原因,详细了解任何区域发生的情况。
- 遇到奇怪的延迟,但很难找到根源。
- 快速找到特定函数的调用路径。
- 窥视正在运行的内核并查看发生了什么。
有几列是可以动态启用和禁止的。
- cpu number是默认会启动函数执行的cpu编号,有时候最好只跟踪一个cpu(tracing_cpu_mask),否则在cpu跟踪是,会看到无须的函数调用。隐藏cpu: echo nofuncgraph-cpu > trace_options。
- duration表示函数执行的时间,会显示在函数结束括号行上。如果是叶函数,则显示在当前函数的同一行。如果要关掉,则echo nofuncgraph-duration > trace_options。
如果函数的开头不在跟踪缓冲区,则函数名称可以显示到函数的右括号后面,可以echo funcgraph-tail > trace_options进行使能。
dynamic ftrace
如果使能了CONFIG_DYNAMIC_FTRACE,则在禁用函数跟踪时,系统将几乎不产生任何开销。其工作原理是使能了gcc的-pg参数会自动在内核的函数开头插桩mcount函数(与架构有关系,gcc 4.6版本开始,x86架构添加mfentry,它调用“fentry”而不是“mcount”)。
在编译时,每个C文件对象通过recordmcount程序(位于脚本目录)运行,该程序将解析C对象的ELF标头,查找.text部分中调用mcount的所有位置。
NOTE:注意的是并非所有的section都被跟踪,可以通过notrace或其他办法来不让其跟踪,并且不会跟踪所有的内联函数,可以cat available_filter_functions节点来查看可以跟踪那些函数。
创建一个“__mcount_loc”的段(section),该段中记录了所有包含在.text中对mcount调用点的引用位置。最后__mcount_loc在链接时统一链接到一个__mcount_loc中。
具体的过程如上图所示,在系统启动时,初始化SMP之前,动态ftrace代码会扫描此表并将所有位置更新为替换为nop指令,同时还会记录位置,这些位置被添加到available_filter_functions表中。在模块加载和执行之前进行处理,卸载模块时,它还会从ftrace函数列表中删除其函数。
在启动动态跟踪后,修改函数跟踪点的过程取决于具体的arch。修改函数跟踪点的方法时要修改位置放置一个断点,同步所有的CPU。接着修改其指令,同步给所有的CPU再把断点移除。通过这样动态的方式,可以做到有选择的跟踪指定函数,其他不想跟踪的函数就的位置执行的是nop指令,不至于影响性能。
内核中使用两个文件用于启动和禁用指定的函数跟踪分别是set_ftrace_filter和set_ftrace_notrace。可以通过available_filter_functions来查看跟踪的函数。
# echo sys_nanosleep hrtimer_interrupt > set_ftrace_filter
# echo function > current_tracer
# echo 1 > tracing_on
# usleep 1
# echo 0 > tracing_on
# cat trace
设置set_ftrace_filter可以使用通配符匹配,示例如下:
``<match>*`` :匹配<match>开头的函数
``*<match>``:匹配<match>结尾的函数
``*<match>*``:匹配其中包含<match>的函数
``<match1>*<match2>``:匹配<match1>开头并以<match2>结尾的函数
设置set_ftrace_filter接口支持过滤命令,格式为 上面解释了function tracer和function graph tracer,但有些特殊功能只在function graph tracer中可用。如果跟踪一个函数及其子函数,只需要函数将其名称写到set_graph_function中。 ftrace有一个总开关,/proc/sys/kernel/ftrace_enabled,向其写0或1表示关闭和使能,默认是开启的状态。更多细节参考:Documentation/trace/ftrace.rst。-mod: 启用每个模块的功能过滤,如只需要ext3模块中的write*功能
echo \'write*:mod:ext3\' > set_ftrace_filter
-traceon/traceoff: 指定函数打开和关闭时跟踪,参数确定跟踪系统打开和关闭的次数,如果为指定,则没有限制,例如在前5此遇到错误时禁止跟踪
echo \'__schedule_bug:traceoff:5\' > set_ftrace_filter
dynamic ftrace with function graph tracer
echo function_graph > current_tracer
echo __do_fault > set_graph_function
echo 1 > tracing_on
...
echo 0 > tracing_on
other