from : http://blog.csdn.net/hlchou/article/details/6441272
[
ftrace 简介 http://www.ibm.com/developerworks/cn/linux/l-cn-ftrace/index.html#author1
]
在软体开发时, 通常都会面临到系统效能调教的需求, 我们希望知道哪些区块的程式码或函式被执行的次数频繁, 或是占据较高的处理器时间, 以便借此优化程式码撰写的行为, 或是改善耗CPU 时间的算法, 以Linux 平台来说,OProfile(http://oprofile.sourceforge.net ) 会是一个大家常推荐的工具,OProfile 支持Time-based 透过系统Timer 中断搜集当下执行环境资讯, 并加以统计, 或基于Event-based, 以ARM 来说就是PerformanceMonitor Unit(CP15) 硬体支援的Performance 控制单元( 更多资讯可以参考:http://infocenter.arm.com/help/ topic/com.arm.doc.dai0195b/index.html),ARMPMU 提供例如Instruction/DataCache 使用状况(miss,write-back,write-buffer..etc) ,Memory/MMU 存取的状况,IRQ/FIQ Latency ,Branch预测统计,I/D-TCMStatus..etc, 基于这机制的Profiling 可以在影响系统效能最少的情况下, 进行System-wide 的性能统计资讯. 另一种选择, 则是透过ARM 接上JTAG 介面, 藉由ICE 软体的Profiling 功能进行分析.
然而, 如果我们希望更明确的知道每个Function 被执行的次数(OProfileTime-based 的统计时间够长就可得出对应的比例,做为决定的依据), 执行的流程, 与个别时间成本, 或是系统在排程(Scheduling andWake-up), 中断,Block/Net, 或Kernel 记忆体配置.. 等, 与LinuxKernel 核心物件有关资讯的话, 其实Ftrace 会是另一个可以辅助的资讯来源, 不同于OProfile ,Ftrace 会透过gcc-pg 把每个函式前面都插入呼叫mcount 函式的动作, 在Branch 统计部分, 也会把if 或是透过likely/unlikely 巨集, 进行植入是的统计, 因此,Ftrace 相比OProfile 虽然可以提供比较完整的Kernel 层级统计资讯, 但因为OProfile 主要是透过ARM 或其他处理器平台的PerformanceMonitor 单元, 因此,OProfile 可以在影响系统效能较低的情况下进行统计( ㄟ ... Time-basedFunctionprofiling 也是会影响到被测端的效能的.), 但总体而言, 都比mcount 植入每个函式中, 对系统效能的影响更算是轻量. 如何决定应用哪个模块进行效能分析, 还是要依据当下开发时的目的与所遇到的问题来做决定.
Ftrace 最应该参考的文件就是LinuxKernel 原始码中位于Documentation/ftrace.txt 的文件, 参考该文件资讯与Google 一下,Ftrace 作者为在RedHat 服务的Steven Rostedt, 主要目的是为LinuxKernel 提供一个系统效能分析的工具, 以便用以除错或是改善/ 优化系统效能,Ftrace 为一个以FunctionTrace 为基础的工具, 并包含了包括行程Context-Switch,Wake-Up/Ready到执行的时间成本, 中断关闭的时间, 以及是哪些函式呼叫所触发的, 这都有助于在复杂的系统执行下, 提供必要资讯以便定位问题.
接下来, 我们将介绍GCC 对于FtraceProfiling 上, 在编译器层级的支援, 以及有关的builtin 函式, 让各位清楚这些机制底层运作的原理, 最后, 并以Ftrace 为主, 说明个机制的内容, 但本文并不会深入探究到各Ftrace 模组机制的实作部分, 主要只以笔者自己认为值得说明的区块, 来加以说明, 对各项细节有兴趣的开发者, 建议可以自行探究.
GCC“-pg” Profiling 机制与builtin 函式对FtraceBranch Profiling 的支援
Ftrace 支援在有加入“ likely/unlikely” 条件判断式位置的BrnchProfiling 与对整个核心if 条件判断式的BrnchProfiling ( 当然,选择后者对效能影响也比较明显... 要做记录的地方变多了.) . 使用者可以透过Kernelhacking --->Tracers --->Branch Profiling ---> 来选择“ Nobranch profiling”,”Trace likely/unlikely profiler” 或“ Profile all ifconditionalss”. 对系统进行BranchProfiling 的动作.( Ftrace 在config 中有这四个设定跟BranchProfiling 有关CONFIG_TRACE_BRANCH_PROFILING,CONFIG_BRANCH_PROFILE_NONE,CONFIG_PROFILE_ANNOTATED_BRANCHES与CONFIG_PROFILE_ALL_BRANCHES)
参考include/linux/compiler.h 中的实作, 如果选择“ Profileall if conditionalss”, 就会把全部的if 条件判断字元, 透过gccprecompile 定义为巨集__trace_if, 如下所示
#defineif(cond, ...) __trace_if( (cond , ## __VA_ARGS__) )
#define__trace_if(cond) /
if(__builtin_constant_p((cond)) ? !!(cond) : /
({ /
int______r; /
staticstruct ftrace_branch_data /
__attribute__((__aligned__(4))) /
__attribute__((section("_ftrace_branch"))) /
______f= { /
.func= __func__, /
.file= __FILE__, /
.line= __LINE__, /
}; /
______r= !!(cond); /
______f.miss_hit[______r]++; /
______r; /
}))
如果if 条件式为常数( 也就是说编译器可以在编译阶段就决定好if/else 路径了), 就不纳入统计, 反之, 就会根据条件式的结果(______r=0 or 1) 统计命中的次数, 作为if/else 条件设计的参考.( 其实, 透过likely/unlikely 优化编译阶段的BranchPredition 是很有帮助的).
如果是设定为” Tracelikely/unlikely profiler”, 就会把likely 与unlikely 巨集定义如下所示
/*
*Using __builtin_constant_p(x) to ignore cases where the return
*value is always the same. This idea is taken from a similar patch
*written by Daniel Walker.
*/
#ifndef likely
# define likely(x) (__builtin_constant_p(x) ? !!(x) :__branch_check__(x, 1))
#endif
#ifndef unlikely
# define unlikely(x) (__builtin_constant_p(x) ? !!(x) :__branch_check__(x, 0))
#endif
其中__branch_check__ 定义如下
#definelikely_notrace(x) __builtin_expect(!!(x), 1)
#defineunlikely_notrace(x) __builtin_expect(!!(x), 0)
#define__branch_check__(x, expect) ({ /
int______r; /
staticstruct ftrace_branch_data /
__attribute__((__aligned__(4))) /
__attribute__((section("_ftrace_annotated_branch")))/
______f= { /
.func= __func__, /
.file= __FILE__, /
.line= __LINE__, /
}; /
______r= likely_notrace(x); /
ftrace_likely_update(&______f,______r, expect); /
______r; /
})
函式ftrace_likely_update( 位置在kernel/trace/trace_branch.c) 实作如下所示,
voidftrace_likely_update(struct ftrace_branch_data *f, int val, intexpect)
{
/*
*I would love to have a trace point here instead, but the
*trace point code is so inundated with unlikely and likely
*conditions that the recursive nightmare that exists is too
*much to try to get working. At least for now.
*/
trace_likely_condition(f,val, expect);
/*FIXME: Make this atomic! */
if(val == expect)
f->correct++;
else
f->incorrect++;
}
有关函式trace_likely_condition 的行为在此就不追踪, 只谈函式ftrace_likely_update, 这函式会统计开发者使用likely/unlikely 定义好的if/else 区块顺序, 跟实际执行时,if/else 执行的结果, 透过correct/incorrect 累加, 我们可以根据Run-Time 实际统计的结果, 看是否原本likely/unlikely 有需要修正的空间( 往统计正确的方向去, 就可避免处理器PipelineFlush 的机会), 以便得到更好的执行效能.
若没有启动任何BranchProfiling 的动作, 则likely 与unlikely 就只会透过Builtin 函式_builtin_expect( 在GCC2.96 版本之后支援) 进行编译阶段的BranchPredition 优化动作, 如下宣告
#define likely(x) __builtin_expect(!!(x), 1)
#define unlikely(x) __builtin_expect(!!(x), 0)
编译器支援的Builtin 函式__builtin_constant_p, 主要的功能为判断所给予的值是否为常数(__builtin_constant_p 会返回1,若不是常数__builtin_constant_p 会返回0), 若是则可在编译时期安排好对应的执行动作, 就不需要把全部的行为, 都放到编译后的程式码, 透过执行时期才决定.
以如下程式码来验证行为,
#defineY 6
voidFunc(int Z)
{
intvResult;
vResult=__builtin_constant_p(Z)?(Z*100):-1;
printf("5:Result:%ld/n",vResult);
vResult=(Z*100);
printf("6:Result:%ld/n",vResult);
}
intmain()
{
intX=5;
intvResult;
vResult=__builtin_constant_p(X)?(X*100):-1;
printf("1:Result:%ld/n",vResult);
vResult=(X*100);
printf("2:Result:%ld/n",vResult);
vResult=__builtin_constant_p(Y)?(Y*100):-1;
printf("3:Result:%ld/n",vResult);
vResult=(Y*100);
printf("4:Result:%ld/n",vResult);
Func(7);
return;
}
以gcc 版本4.1.2 来验证, 若以-O0 编译,X 为一个区域变数,__builtin_constant_p(X) 会返回0, 反之,Y 为一个定义的常数,__builtin_constant_p(Y) 会返回1, 而如果把一个常数透过函式参数Z 传递, 因为这个值会被放到堆叠( 根据每个处理器的CallingConvention, 在ARM 上会先放到暂存器R0-R3), 导致__builtin_constant_p(Z) 返回0, 若是以-O1 或-O2 编译, 则编译器可以判断区域变数X 的值,__builtin_constant_p(X) 会返回1, 若是函式函式传递的参数Z,_builtin_constant_p(Z) 还是会传回0. 优化的区块还是以Function 本身为单位, 并且有打开优化选项, 可以让Builtin 函式发挥更好的效能.
参考GCC 文件,__builtin_constant_p 也可以作为Constant 变数初始值的指定( 文件建议要在GCC3.0.1 版本之后), 如下所示, 若EXPRESSION 为常数, 则table 初始值为该常数, 反之则初始值为0.
staticconst int table[] = {
__builtin_constant_p(EXPRESSION) ? (EXPRESSION) : -1,
};
另一个需要介绍的Builtin 函式为__builtin_expect, 这函式的功能主要在提供编译器BranchPrediction 的能力, 如以下的程式码
voidFuncA(int X)
{
if(__builtin_expect(X,1))
{
printf("FuncA1:%ld/n",X*0x100);
}
else
{
printf("FuncA2:%ld/n",X);
}
}
voidFuncB(int X)
{
if(__builtin_expect(X,0))
{
printf("FuncB1:%ld/n",X*0x100);
}
else
{
printf("FuncB2:%ld/n",X);
}
}
intmain()
{
FuncA(7);
FuncB(8);
return;
}
执行结果为
FuncA1:700h
FuncB1:800h
以gcc4.1.2 搭配-O2 进行编译( 在这验证环境下, 使用-O0, 函式__builtin_expect 会没有效果), 执行结果一致, 透过反组译程式码结果如下
FuncA/B (-O0) | FuncA (-O2) -if(__builtin_expect(X,1)) | FuncB(-O2)-if(__builtin_expect(X,0)) |
push %ebp mov %esp,%ebp sub $0x8,%esp mov 0x8(%ebp),%eax test %eax,%eax je 80483a9 <FuncA+0x25> mov 0x8(%ebp),%eax shl $0x8,%eax mov %eax,0x4(%esp) movl $0x8048500,(%esp) call 8048298 <printf@plt> jmp 80483bc <FuncA+0x38> 80483a9: mov 0x8(%ebp),%eax mov %eax,0x4(%esp) movl $0x804850d,(%esp) call 8048298 <printf@plt> leave ret | push %ebp mov %esp,%ebp sub $0x8,%esp mov 0x8(%ebp),%eax test %eax,%eax je 80483f2 <FuncA+0x22> shl $0x8,%eax mov %eax,0x4(%esp) movl $0x804853a,(%esp) call 8048298 <printf@plt> leave ret 80483f2: movl $0x0,0x4(%esp) movl $0x8048547,(%esp) call 8048298 <printf@plt> leave ret | push %ebp mov %esp,%ebp sub $0x8,%esp mov 0x8(%ebp),%eax test %eax,%eax jne 80483b3 <FuncB+0x23> movl $0x0,0x4(%esp) movl $0x804852d,(%esp) call 8048298 <printf@plt> leave ret 80483b3: shl $0x8,%eax mov %eax,0x4(%esp) movl $0x8048520,(%esp) call 8048298 <printf@plt> leave ret |
我们可以看到__builtin_expect(X,1) 会优先把if 的执行区块放到连续的程式码中, 而__builtin_expect(X,0) 则是会把else 的执行区块, 放到前面连续的程式码中, 至于执行时期会执行到if 或else 的区块, 就根据X 条件是否为0 来决定. 参考GCC文件, 我们也可以搭配条件判断的写法
可以参考LinuxKernel 中include/linux/compiler.h 中的实作, 如下所示
#define likely(x) __builtin_expect(!!(x), 1)
#define unlikely(x) __builtin_expect(!!(x), 0)
如果开发者判断,if 的区块是较常被执行到的, 那就应该用likely, 例如
if(likely(success))
{….}
else //else 区块可能为error 处理逻辑, 多数的情况应该希望走的是if 的逻辑
{….}
如果开发者判断else 区块, 是希望较常被执行到的, 就可以使用unlikely, 例如
if(unlikely(error))
{….}
else //success
{….}
处理器本身也有BranchPredition 的能力, 如果不在有被处理器记忆到的BPEntry 中, 处理器通常会循序 Fetch 指令进来,一旦发现分支预测错误 , 就会 FlushPipeline, 透过函式__builtin_expect, 我们可以在编译时期, 依据传入值的结果, 决定if/else 编译为机械码时程式码排列的顺序, 减少处理器Pipeline 被Flush 的机率, 这对执行效能也是有很大的帮助.
GCCsupport for the GNU profiler gprof
有关GNUgprof 的介绍, 可以参考网页http://www.cs.utah.edu/dept/old/texinfo/as/gprof.html . 基于GCC 对Profiling 的支援, 开发端可以透过-pg 的编译参数, 让gcc 把profiling 的功能加入到程式码中,
以如下程式码为例,
intFuncA()
{
inti;
intvRet=0;
for(i=0;i<20000;i++)
{
vRet+=i;
}
returnvRet;
}
intFuncB(int I)
{
inti;
intvRet=0;
for(i=0;i<9999;i++)
{
vRet+=I+FuncA();
}
returnvRet;
}
intmain()
{
intvResult;
vResult=FuncA();
vResult=FuncB(vResult);
printf("Result:%ld/n",vResult);
return0;
}
透过gcc 编译后, 有加上-pg 与没加上的差异如下
函式名称 | 无-pg | 有加上-pg |
main | lea 0x4(%esp),%ecx and $0xfffffff0,%esp pushl 0xfffffffc(%ecx) push %ebp mov %esp,%ebp push %ecx sub $0x24,%esp call 8048384 <FuncA> mov %eax,0xfffffff8(%ebp) mov 0xfffffff8(%ebp),%eax mov %eax,(%esp) call 80483b2 <FuncB> mov %eax,0xfffffff8(%ebp) mov 0xfffffff8(%ebp),%eax mov %eax,0x4(%esp) movl $0x8048500,(%esp) call 8048298 <printf@plt> mov $0x0,%eax add $0x24,%esp pop %ecx pop %ebp lea 0xfffffffc(%ecx),%esp ret | lea 0x4(%esp),%ecx and $0xfffffff0,%esp pushl 0xfffffffc(%ecx) push %ebp mov %esp,%ebp push %ecx sub $0x24,%esp call 804837c <mcount@plt> =>Glibc 提供的mcount 函式 call 80484b4 <FuncA> mov %eax,0xfffffff8(%ebp) mov 0xfffffff8(%ebp),%eax mov %eax,(%esp) call 80484e7 <FuncB> mov %eax,0xfffffff8(%ebp) mov 0xfffffff8(%ebp),%eax mov %eax,0x4(%esp) movl $0x8048680,(%esp) call 804835c <printf@plt> mov $0x0,%eax add $0x24,%esp pop %ecx pop %ebp lea 0xfffffffc(%ecx),%esp ret |
FuncA | push %ebp mov %esp,%ebp sub $0x10,%esp movl $0x0,0xfffffffc(%ebp) movl $0x0,0xfffffff8(%ebp) jmp 80483a4 <FuncA+0x20> mov 0xfffffff8(%ebp),%eax add %eax,0xfffffffc(%ebp) addl $0x1,0xfffffff8(%ebp) cmpl $0x4e1f,0xfffffff8(%ebp) jle 804839a <FuncA+0x16> mov 0xfffffffc(%ebp),%eax leave ret | push %ebp mov %esp,%ebp sub $0x10,%esp call 804837c <mcount@plt> =>Glibc 提供的mcount 函式 movl $0x0,0xfffffffc(%ebp) movl $0x0,0xfffffff8(%ebp) jmp 80484d9 <FuncA+0x25> mov 0xfffffff8(%ebp),%eax add %eax,0xfffffffc(%ebp) addl $0x1,0xfffffff8(%ebp) cmpl $0x4e1f,0xfffffff8(%ebp) jle 80484cf <FuncA+0x1b> mov 0xfffffffc(%ebp),%eax leave ret |
FuncB | push %ebp mov %esp,%ebp sub $0x10,%esp movl $0x0,0xfffffffc(%ebp) movl $0x0,0xfffffff8(%ebp) jmp 80483d7 <FuncB+0x25> call 8048384 <FuncA> add 0x8(%ebp),%eax add %eax,0xfffffffc(%ebp) addl $0x1,0xfffffff8(%ebp) cmpl $0x270e,0xfffffff8(%ebp) jle 80483c8 <FuncB+0x16> mov 0xfffffffc(%ebp),%eax leave ret | push %ebp mov %esp,%ebp sub $0x10,%esp call 804837c <mcount@plt> =>Glibc 提供的mcount 函式 movl $0x0,0xfffffffc(%ebp) movl $0x0,0xfffffff8(%ebp) jmp 8048511 <FuncB+0x2a> call 80484b4 <FuncA> add 0x8(%ebp),%eax add %eax,0xfffffffc(%ebp) addl $0x1,0xfffffff8(%ebp) cmpl $0x270e,0xfffffff8(%ebp) jle 8048502 <FuncB+0x1b> mov 0xfffffffc(%ebp),%eax leave ret |
[root@localhostpg]# gcc -g -pg pg.c -o pg
[root@localhostpg]# ./pg
Result:785467424
[root@localhostpg]# ls -l gmon.out
-rw-r--r--1 root root 464 May 13 06:32 gmon.out
[root@localhostpg]# gprof --brief ./pg
[root@localhostpg]# gprof --brief ./pg
Flatprofile:
Eachsample counts as 0.01 seconds.
% time | cumulative (seconds) | self (seconds) | calls | Self (ms/call) | Total (ms/call) | name |
100.54 | 0.47 | 0.47 | 10000 | 0.05 | 0.05 | FuncA |
0 | 0.47 | 0 | 1 | 0 | 472.49 | FuncB |
=> 简要说明: 每次呼叫FuncA 需要0.05ms, 总共呼叫了10000 次, 耗时0.47 秒( ㄟ也许应该说每次呼叫FuncA 需要0.047ms 会比较精确一点), 而FnucB 被呼叫1 次, 耗时472.49ms( 在funcB 中会执行9999 次FuncA).
Callgraph
granularity:each sample hit covers 2 byte(s) for 2.12% of 0.47 seconds
index | % time | self | children | called | name |
|
| 0 | 0 | 1/10000 | main [2] |
|
| 0.47 | 0 | 9999/10000 | FuncB [3] |
[1] | 100 | 0.47 | 0 | 10000 | FuncA [1] |
=> 简要说明: 以FuncA 为主体来对比,FuncA 执行了10000 次共0.47 秒的时间,FuncB 呼叫了FuncA9999 ( 占总数9999/10000),main 呼叫了FuncA1 次 ( 占总数1/10000)
index | % time | self | children | called | name |
[2] | 100 | 0 | 0.47 |
| main [2] |
|
| 0 | 0.47 | 1 | FuncB [3] |
|
| 0 | 0 | 1/10000 | FuncA [1] |
=> 简要说明: 以main 为主体来对比,main 呼叫的函式(children) 花了0.47 秒, 分别呼叫了FuncB 占了9999/10000(约等于1) 的时间, 与呼叫了FuncA 占了1/ 10000 的时间.
index | % time | self | children | called | name |
|
| 0 | 0.47 | 1 | main [2] |
[3] | 100 | 0 | 0.47 | 1 | FuncB [3] |
|
| 0.47 | 0 | 9999/10000 | FuncA [1] |
=> 简要说明: 以funcB 为主体来对比,main 呼叫的函式FuncA 与funcB(children) 花了0.47 秒,FuncB 呼叫的函式FuncA 花了0.47 秒( 因为最小单位为0.01 秒, 太接近的两个数字如果差距是在0.01 秒以内, 数字就有机会变成一样),FuncB 被呼叫1 次,FuncA 被FuncB 呼叫9999 次
Indexby function name
[1]FuncA [3] FuncB
Infrastructurefor profiling code inserted by 'gcc -pg'.
http://www.network-theory.co.uk/docs/gccintro/gccintro_80.html
在LinuxKernel 中EnableTracer
要在核心中支援Ftrace, 除了GCC 编译器要支援-pg 外, 所采用的C 函式库也要支援相关mcount 的函式, 笔者用AndroidSDK 带的arm-eabi-gcc4.4.0 会产生编译错误“ undefinedreference to `__gnu_mcount_nc' “. 笔者后来是到Sourcery网址http://www.codesourcery.com/sgpp/lite/arm/portal/subscription3053下载arm-2007q3 版本的编译器与函式库环境, 以此为基础进行ARM 环境LinuxKernel 开启Ftrace 的编译, 就可以解决无法Link'__gnu_mcount_nc' 的错误了.( 有关编译环境与KernelSource Code 版本的选择, 会随着版本演进而有所差异, 如有遇到编译错误发生时, 还请以各位自己所选择的环境为主来判断.)
接下来执行 make menuconfig.
如果是在x86 的机器上, 要Enable“Kernel Function Graph Trace” 的功能, 就要透过Generalsetup --->Optimize for size , 选择关闭“ Optimize for size “. 而在ARM 平台上, 目前没有“ KernelFunction Graph Trace” 的选项, 所以就不受此限制.
此外, 在LinuxKernel 2.6.31 给ARM 环境的组态中, 目前并不支援DynamicFtrace, 所以像是set_ftrace_filter与set_ftrace_nontrace 这类可以动态设定函式名称过滤原则的机制, 在笔者环境中就无法支援, 我在说明时, 会以x86 版本来替代, 后续支援与否请以LinuxKernel 版本演进为主.
在选单中进入Kernelhacking ---> Tracers ---> 就可以选择要开启哪些Tracers 功能, 以笔者手中针对ARM 版本的Config内容如下( 我是都勾选了...)
-*-Kernel Function Tracer
[*]Interrupts-off Latency Tracer
[*]Scheduling Latency Tracer
-*-Trace process context switches
[*]Trace boot initcalls
[*]Trace likely/unlikely profiler
[*] Profile all if conditionals
[*]Trace likely/unlikely instances
[*]Trace max stack
最后确认DebugFilesystem 是否有被勾选, 路径为Kernelhacking --->-*- Debug Filesystem.
退出选单, 由于刚才已经勾选“ CONFIG_FUNCTION_TRACER” 组态, 可以参考档案kernel/trace/Makefile 的内容, 设定CONFIG_FUNCTION_TRACER 后, 就会把KBUILD_CFLAGS 加上-pg, 每个核心函式中都会被置入mcount 函式的呼叫
ifdefCONFIG_FUNCTION_TRACER
ORIG_CFLAGS:= $(KBUILD_CFLAGS)
KBUILD_CFLAGS= $(subst -pg,,$(ORIG_CFLAGS))
...
endif
Kernel 内部有关除错, 或是Ftrace 相关的档案, 会移除-pg 的编译参数, 避免影响统计的结果, 例如在kernel/Makefile 档案中
ifdefCONFIG_FUNCTION_TRACER
# Donot trace debug files and internal ftrace files
CFLAGS_REMOVE_lockdep.o= -pg
CFLAGS_REMOVE_lockdep_proc.o= -pg
CFLAGS_REMOVE_mutex-debug.o= -pg
… ............
endif
比较一下, 有打开FunctionTracer 跟没有打开FunctionTracer 的核心编译结果如下所示, 可以看到GCC 编译器, 对编译结果的影响.
没有打开Ftracer 的do_fork 函式 | 打开Ftracer 的do_fork 函式 |
00001458<do_fork>: push {r4, r5, r6, r7, r8, r9, sl, lr} tst r0, #268435456 ; 0x10000000 sub sp, sp, #32 ; 0x20 mov r5, r0 mov r7, r1 mov r6, r2 mov sl, r3 bne 1688 <do_fork+0x230> ands r4, r5, #33554432 ; 0x2000000 moveq r9, #0 ; 0x0 movne r9, #1 ; 0x1 bne 16c0 <do_fork+0x268> ldr r3, [r6, #64] tst r3, #15 ; 0xf bne 149c <do_fork+0x44> tst r5, #8388608 ; 0x800000 beq 1604 <do_fork+0x1ac> mov r8, #0 ; 0x0 ldr ip, [sp, #68] mov r1, r7 str ip, [sp] mov r2, r6 | 000018b0<do_fork>: mov ip, sp push {r4, r5, r6, r7, r8, r9, sl, fp, ip, lr, pc} sub fp, ip, #4 ; 0x4 sub sp, sp, #52 ; 0x34 mov ip, lr bl 0 <mcount> .word 0x00000040 tst r0, #268435456 ; 0x10000000 mov r7, r0 mov sl, r1 mov r6, r2 str r3, [fp, #-72] bne 1c7c <do_fork+0x3cc> ands r5, r7, #33554432 ; 0x2000000 moveq r8, #0 ; 0x0 movne r8, #1 ; 0x1 mov r1, r8 ldr r0, [pc, #1060] ; 1d20 <do_fork+0x470> mov r2, #0 ; 0x0 bl 0 <ftrace_likely_update> subs r1, r8, #0 ; 0x0 |
DebugFSFileSystem
Ftrace 会使用虚拟的档案系统debugfs 做为设定档与输出结果的储存位置, 我们可以先在根目录产生/debug 档案, 然后mountdebugfs 到/debug 目录下(Ftrace 文件建议可以mount 到/sys/kernel/debug 目录下)
#mkdir /debug
#mount -t debugfs nodev /debug
之后进入/debug/tracing 目录下
# cd/debug/tracing
查询系统中支援哪些Tracer
$ catavailable_tracers
function_graphfunction_duration function sched_switch nop
选择sched_switch 作为目前运作的Tracer
#echo sched_switch > current_tracer
可以检视目录下的trace 档案, 观察sched_switch 输出的结果
#cat trace
#tracer: sched_switch
#
# TASK-PID CPU# TIMESTAMP FUNCTION
# | | | | |
<idle>-0 [000] 745.934676: 0:140:R ==> [000] 2892:120:R
gnome-terminal-2892 [000] 745.934770: 2892:120:S ==> [000] 0:140:R
<idle>-0 [000] 745.934861: 0:140:R ==> [000] 2892:120:R
gnome-terminal-2892 [000] 745.934937: 2892:120:S ==> [000] 0:140:R
bash-5175 [001] 745.934960: 5175:120:S + [000] 2892:120:S
bash-5175 [001] 745.935149: 5175:120:S + [000] 2892:120:S
暂停Ftrace 的运作,
#echo nop > current_tracer
上述步骤, 为透过debugfs 操作Ftrace 的基本行为, 接下来让我们初步认识Ftrace 相关的控制档案
介绍Ftrace 目录tracing 下的档案
我们以AndroidARM 平台搭配LinuxKernel 2.6.31 为例子, 首先建立/data/debug 目录, 并且把debugfs 虚拟档案系统载入
# mkdir/data/debug
# mount-t debugfs nodev /data/debug
# cd/data/debug
浏览目录下的内容,
# ls
sched_features
mmc0
tracing
bdi
#
进入目录tracing 后,ls 浏览该目录的结果
# cd/data/debug
# ls -l
-r--r--r--root root 0 1970-01-01 00:00 stack_trace
-rw-r--r--root root 0 1970-01-01 00:00 stack_max_size
drwxr-xr-xroot root 1970-01-01 00:00 events
-rw-r--r--root root 0 1970-01-01 00:00 set_event
-r--r--r--root root 0 1970-01-01 00:00 available_events
-r--r--r--root root 0 1970-01-01 00:00 printk_formats
drwxr-xr-xroot root 1970-01-01 00:00 per_cpu
drwxr-xr-xroot root 1970-01-01 00:00 options
-r--r--r--root root 0 1970-01-01 00:00 saved_cmdlines
--w--w----root root 0 1970-01-01 00:00 trace_marker
-rw-r--r--root root 0 1970-01-01 00:00 buffer_size_kb
-r--r--r--root root 0 1970-01-01 00:00 trace_pipe
-r--r--r--root root 0 1970-01-01 00:00 README
-rw-r--r--root root 0 1970-01-01 00:00 tracing_thresh
-rw-r--r--root root 0 1970-01-01 00:00 tracing_max_latency
-rw-r--r--root root 0 1970-01-01 00:00 current_tracer
-r--r--r--root root 0 1970-01-01 00:00 available_tracers
-rw-r--r--root root 0 1970-01-01 00:00 trace
-rw-r--r--root root 0 1970-01-01 00:00 tracing_cpumask
-rw-r--r--root root 0 1970-01-01 00:00 trace_options
-rw-r--r--root root 0 1970-01-01 00:00 tracing_enabled
-rw-r--r--root root 0 1970-01-01 00:00 tracing_on
-rw-r--r--root root 0 1970-01-01 00:00 function_profile_enabled
drwxr-xr-xroot root 1970-01-01 00:00 trace_stat
-rw-r--r--root root 0 1970-01-01 00:00 set_ftrace_pid
接下来, 笔者以这目录下的档案, 挑选认为应该说明的部份, 如下所示
档案名称 | 说明 |
current_tracer | 用来显示目前被设定的Tracer. 如果没有任何被设定的Tracer 就会返回nop # cat current_tracer nop
以 sched_switch 设定到 current_tracer 来示范这档案的功能
# echo >/data/debug/tracing/trace => 清空trace 内容 # echo sched_switch >/data/debug/tracing/current_tracer # echo 1 >/data/debug/tracing/tracing_enabled # sleep 1 # echo 0 >/data/debug/tracing/tracing_enabled #cat/data/debug/tracing/trace
#tracer: sched_switch # # TASK-PID CPU# TIMESTAMP FUNCTION # | | | | | bash-9560 [000] 52138.209612: 9560:120:R ==> [000] 9538:120:Rkworker/0:1 kworker/0:1-9538 [000] 52138.209903: 9538:120:S ==> [000] 9558:120:Rin.telnetd in.telnetd-9558 [000] 52138.211017: 9558:120:S ==> [000] 9560:120:R bash bash-9560 [000] 52138.211412: 9560:120:S + [000] 9560:120:S bash bash-9560 [000] 52138.211613: 9560:120:R + [000] 9538:120:Rkworker/0:1 bash-9560 [000] 52138.211820: 9560:120:R ==> [000] 9538:120:Rkworker/0:1 kworker/0:1-9538 [000] 52138.211990: 9538:120:R + [000] 9558:120:R in.telnetd kworker/0:1-9538 [000] 52138.212148: 9538:120:S ==> [000] 9558:120:Rin.telnetd in.telnetd-9558 [000] 52138.212953: 9558:120:S ==> [000] 9560:120:R bash bash-9560 [000] 52138.213221: 9560:120:S ==> [000] 0:120:R <idle> <idle>-0 [000] 52138.414815: 0:120:R + [000] 9538:120:Rkworker/0:1 <idle>-0 [000] 52138.414976: 0:120:R ==> [000] 9538:120:Rkworker/0:1 kworker/0:1-9538 [000] 52138.415243: 9538:120:S ==> [000] 0:120:R <idle> <idle>-0 [000] 52138.417111: 0:120:R + [000] 9538:120:Rkworker/0:1 <idle>-0 [000] 52138.417312: 0:120:R ==> [000] 9538:120:Rkworker/0:1 kworker/0:1-9538 [000] 52138.417541: 9538:120:S ==> [000] 0:120:R <idle> <idle>-0 [000] 52138.448254: 0:120:R + [000] 1811:120:R pcscd <idle>-0 [000] 52138.448547: 0:120:R ==> [000] 1811:120:Rpcscd pcscd-1811 [000] 52138.450170: 1811:120:S ==> [000] 0:120:R <idle> <idle>-0 [000] 52138.540657: 0:120:R + [000] 1892:139:Rgam_server
|
available_tracers | 列出目前LinuxKernel 中在编译时有被启用的Tracer. 我们只要把这些对应的名字设定到current_tracer 中就可以启用对应的Tracer. 如下所示为笔者环境中所支持的Tracer.
# cat available_tracers blk kmemtrace branchwakeup_rt wakeup irqsoff function sched_switch initcall nop
|
tracing_enabled | 用以控制Tracer 的启动或是停止, 当为1 时表示Tracer 启动, 反之, 为0 就是停止, 如下所示
启动Tracer #echo1 > /data/debug/tracing/tracing_enabled
暂停Tracer #echo0 > /data/debug/tracing/tracing_enabled |
trace | 用以把Tracer 的结果呈现出人可阅读的格式. #more/data/debug/tracing/trace
清空 Tracer 结果内容 #echo >/data/debug/tracing/trace |
trace_pipe | 支援以pipe 的机制读取跟trace 档案一样的内容, 并以Block 的机制读取, 直到有资料进来后才会返回, 每次资料读取后, 下一次读取, 就会读取到新的资料, 适合用来循序读取, 把Trace 内容依序读出来.
如果希望每次都读取到完整的 tracebuffer 内容, 就从trace 档案中读取, 反之, 如果是每次都要读到没读取到的资料, 就可以从trace_pipe 中读取. |
trace_options | 可以用来控制trace 要显示的资料内容.( ㄟ也必须要该Plug-inTracer 有支援对应的Options). #cat trace_options print-parent nosym-offset nosym-addr noverbose noraw nohex nobin noblock nostacktrace nosched-tree trace_printk noftrace_preempt nobranch annotate nouserstacktrace nosym-userobj noprintk-msg-only context-info nolatency-format noglobal-clock sleep-time graph-time
控制方式为, 把Option 内容选择加上no ( 关闭) 或是移除no( 开启), 例如:
关闭print-parent #echo noprint-parent > /data/debug/tracing/trace_options
重新cattrace_options 就会看到print-parent 变成noprint-parent 反之, 原本设定为nosym-addr, 透过 #echosym-addr > /data/debug/tracing/trace_options
重新cattrace_options 就会看到nosym-addr 变成 sym-addr
|
tracing_max_latency | 用以显示刚才的 Tracer 进行 Latency 统计时 ( 例如 :irqsoff 或是 wakeup...etc), 最大的 Latency 数值为何 ( 单位为 us), 以提供核心设计者 , 找出系统中比较缺乏效率的部份 , 加以改善 .
#echo irqsoff > /data/debug/tracing/ current_tracer #echo 1 > /data/debug/tracing/ tracing_enabled … .dosomehting....sleep.... #echo 0 > /data/debug/tracing/ tracing_enabled #more tracing_max_latency 7367 #more /data/debug/tracing/ trace #tracer: irqsoff # #irqsoff latency trace v1.1.5 on 2.6.38.6 #------------------------------------------------- ------------------- #latency: 7367 us, #58/58, CPU#0 | (M:desktop VP:0, KP:0, SP:0 HP:0#P:1) # ----------------- # | task: bash-1905 (uid:0 nice:0 policy:0 rt_prio:0) # ----------------- # => started at: apic_timer_interrupt # => ended at: call_on_stack # # # _------=> CPU# # / _-----=> irqs-off # | / _----=> need-resched # || / _---=> hardirq/softirq # ||| / _--=> preempt-depth # |||| /_--=> lock-depth # |||||/ delay # cmd pid |||||| time | caller # / / |||||| / | / ...etc |
buffer_size_kb | 用来设定每个处理器要用多少记忆体空间当做TracerBuffer ( 单位为kbytes)( 一般而言一个Page 单位为4kbytes)( 每个处理器都会依据此值配置一样大的空间), 例如: #cat buffer_size_kb 1408 就表示在笔者环境中为每个处理器用1.408MB 当做TracerBuffer. 这个值只有在current_tracer 内容为“ nop” 时才可以修改.( 也就是说要没有启用任何TracerPlug-in 才可以修改这个值)
|
tracing_cpumask | 用来设定Tracer 要在哪个处理器上搜集资讯. 显示的为字串16 进位的字串. 在笔者单核ARM 的环境中执行如下 #cat tracing_cpumask 1 # |
set_ftrace_filter | 用在动态Ftrace 的组态下, 程式码在编译过程中被gcc-pg 置入呼叫mcount 函式的行为, 在支援动态Ftrace(Dynamic Ftrace) 的环境中, 只有在透过这档案设定的函式, 才会被纳入FunctionTrace 中.
设定TraceFuncton 开头为 account 与 run 的函式呼叫行为 , 如下指令所示
#echo >/debug/tracing/trace #echo 'account*' 'run*' >/debug/tracing/set_ftrace_filter #echo function >/debug/tracing/current_tracer #echo 1 >/debug/tracing/tracing_enabled #sleep 1 #echo 0 >/debug/tracing/tracing_enabled #cat /debug/tracing/trace #tracer: function # # TASK-PID CPU# TIMESTAMP FUNCTION # | | | | | bash-1951 [000] 2602.286489: account_process_tick <-update_process_times bash-1951 [000] 2602.286501: account_system_time <-account_process_tick bash-1951 [000] 2602.286510: run_local_timers <-update_process_times bash-1951 [000] 2602.286565: run_posix_cpu_timers <-update_process_times bash-1951 [000] 2602.286605: run_timer_softirq <-__do_softirq kworker/0:0-4 [000] 2602.287494: account_process_tick<-update_process_times kworker/0:0-4 [000] 2602.287503: account_system_time <-account_process_tick kworker/0:0-4 [000] 2602.287509: run_local_timers <-update_process_times kworker/0:0-4 [000] 2602.287565: run_posix_cpu_timers<-update_process_times kworker/0:0-4 [000] 2602.287605: run_timer_softirq <-__do_softirq … .
在笔者的LinuxKernel 2.6.31 ARM 的设置中, 目前并没有支援HAVE_DYNAMIC_FTRACE, 也就是说在ARM 版本中这个档案属性会无法使用.(还请以各位手中的LinuxKernel 版本为主, 看是否有新的更新.) |
set_ftrace_notrace | 跟set_ftrace_filter 相反, 在动态Ftrace(DynamicFtrace) 的组态下, 那设定到这档案中的函式名称, 都会被取消呼叫mcount 的动作, 不纳入FunctionTrace 的机制中. 如果同一个函式同时被设定到set_ftrace_filter 与set_ftrace_notrace, 则该函式将会以set_ftrace_notrace 为主, 将不纳入Trace 中.
设定不要Trace 有包含cpu 与align 字元的函式名称, 如下指令所示 # echo '*cpu*' '*align*' >/debug/tracing/set_ftrace_notrace #echo function >/debug/tracing/current_tracer #echo 1 >/debug/tracing/tracing_enabled #sleep 1 #echo 0 >/debug/tracing/tracing_enabled #cat /debug/tracing/trace # # TASK-PID CPU# TIMESTAMP FUNCTION # | | | | | bash-1951 [000] 2331.630911: calc_global_load <-do_timer bash-1951 [000] 2331.630921: update_process_times <-tick_periodic bash-1951 [000] 2331.630929: account_process_tick <-update_process_times bash-1951 [000] 2331.630937: account_system_time <-account_process_tick bash-1951 [000] 2331.630945: run_local_timers <-update_process_times bash-1951 [000] 2331.630953: hrtimer_run_queues <-run_local_timers bash-1951 [000] 2331.630967: __current_kernel_time <-hrtimer_run_queues bash-1951 [000] 2331.630974: __get_wall_to_monotonic <-hrtimer_run_queues bash-1951 [000] 2331.630984: _raw_spin_lock <-hrtimer_run_queues bash-1951 [000] 2331.630995: raise_softirq <-run_local_timers bash-1951 [000] 2331.631006: rcu_check_callbacks <-update_process_times bash-1951 [000] 2331.631014: rcu_bh_qs <-rcu_check_callbacks bash-1951 [000] 2331.631022: __rcu_pending <-rcu_check_callbacks … .
在笔者的LinuxKernel 2.6.31 ARM 的设置中, 目前并没有支援HAVE_DYNAMIC_FTRACE, 也就是说在ARM 版本中这个档案属性会无法使用.(还请以各位手中的LinuxKernel 版本为主, 看是否有新的更新.)
|
set_ftrace_pid | 设定Functiontracer 只针对特定的PID 进行搜集.( 如果不设定这个值, 而是对全系统搜集, 很有可能会让系统Hang 住=> 在我的ARM 环境中不设定PID 缩小范围, 就会Hang 住...@_@)
操作范例如下, #cat/debug/tracing/set_ftrace_pid no pid #ps => 找一个要Trace 的ProcessID USER PID PPID VSIZE RSS WCHAN PC NAME … . system 65 31 152436 25296 ffffffff afd0db4c S system_server app_23 128 31 111664 20728 ffffffff afd0eb08 S com.android.launcher … . #echo 65 > /data/debug/tracing/set_ftrace_pid #echo function > /data/debug/tracing/current_tracer #echo 1 > /data/debug/tracing/tracing_enabled … .dosomething.... #cat/data/debug/tracing/trace
… . 此时 trace 中就只会显示该 PID 的 tracing 结果 ...
|
available_filter_functions | 这个档案会列出目前所有可以供 "set_ftrace_filter" 或 "set_ftrace_notrace" 用来设定过滤条件的函式名称 .
例如: # more/debug/tracing/available_filter_functions _stext do_one_initcall run_init_process init_post name_to_dev_t match_dev_by_uuid thread_saved_pc get_wchan prepare_to_copy release_thread copy_thread start_thread __show_regs cpu_idle setup_sigcontext align_sigframe signal_fault sys_sigaltstack restore_sigcontext sys_sigaction sys_rt_sigreturn do_notify_resume … .etc |
|
|
介绍Ftrace 每个模组
Ftrace 的设计概念为提供Plug-inFramework 的机制, 随着核心模组的演进, 如果需要针对新的核心模组进行效能上的统计分析时, 就可以透过Plug-in 的方式, 撰写新的Ftrace 模组达成新的tracer 目的, 作为一个可延展性的Tracer 架构, 本段落会介绍Ftrace 主要支援的Tracer 模组, 这些模组的数量或是功能, 都有可能随着LinuxKernel 演进而有所改动( 例如:Kernel2.6.39 移除 KernelLock), 版本的更迭, 都请以最新的LinuxKernel Source Code 为主要依据.
名称 | 说明 | ||||||||||||||||||||||||||||||||||||||||
Function tracer
| 设定路径为Kernelhacking --->Tracers --->Kernel Function Tracer 用以显示 ,Function 呼叫的流程与顺序
操作范例如下所示 : #echo 1855 >/debug/tracing/set_ftrace_pid => 选择vsftpd 的PID(=1855) #echo function >/debug/tracing/current_tracer => 选择FunctionTracer #echo 1 >/debug/tracing/tracing_enabled => 之后透过另一台电脑经由FTP 连线到这主机来, 就可以记录到这DaemonFunction操作的行为 #echo 0 >/debug/tracing/tracing_enabled #cat /debug/tracing/trace >/trace.txt #echo nop >/debug/tracing/current_tracer => 移除Tracer 再来看trace.txt 中结果如下
#tracer: function # # TASK-PID CPU# TIMESTAMP FUNCTION # | | | | | vsftpd-1964 [000] 272.754213: irq_exit <-smp_apic_timer_interrupt vsftpd-1964 [000] 272.754222: do_softirq <-irq_exit vsftpd-1964 [000] 272.754229: call_on_stack <-do_softirq vsftpd-1964 [000] 272.754235: __do_softirq <-call_on_stack vsftpd-1964 [000] 272.754241: __local_bh_disable <-__do_softirq vsftpd-1964 [000] 272.754279: run_timer_softirq <-__do_softirq vsftpd-1964 [000] 272.754302: hrtimer_run_pending <-run_timer_softirq vsftpd-1964 [000] 272.754324: _raw_spin_lock_irq <-run_timer_softirq vsftpd-1964 [000] 272.754347: rcu_bh_qs <-__do_softirq vsftpd-1964 [000] 272.754378: rcu_process_callbacks <-__do_softirq vsftpd-1964 [000] 272.754400: __rcu_process_callbacks<-rcu_process_callbacks vsftpd-1964 [000] 272.754422: force_quiescent_state<-__rcu_process_callbacks vsftpd-1964 [000] 272.754444: rcu_gp_in_progress <-force_quiescent_state … ... 可以看到在vsftpd 对应处理Client 需求的行程运作时,LinuxKernel 这所对应调用的函式行为与时间值.
| ||||||||||||||||||||||||||||||||||||||||
Dynamicselection of functions
设定路径为Kernelhacking --->Tracers --->enable/disable ftrace tracepointsdynamically
由于对全部FunctionTracing 的成本很高, 以笔者所用的ARM 环境而言, 几乎会导致系统无法正常的反应(mmm, 如果你是用 ARMCortex A 系列的处理器 , 应该就会很顺了 ...) ,Ftrace也提供了动态选择Function(Dynamicselection of functions) 进行Tracing 的机制, 以笔者所验证的LinuxKernel 2.6.31 而言, 目前在ARM 平台尚未支援这机制, 而X86 平台则可以支援.
使用范例如下的写法, 选择只追踪函式名称包含有spin 与process 字串的LinuxKernel Function 呼叫
#echo '*spin*' '*process*'> /debug/tracing/set_ftrace_filter 执行结果如下所示 #tracer: function # # TASK-PID CPU# TIMESTAMP FUNCTION # | | | | | vsftpd-1855 [000] 5064.651690: _raw_spin_lock_bh <-lock_sock_nested vsftpd-1855 [000] 5064.651739: _raw_spin_lock_bh <-release_sock vsftpd-1855 [000] 5064.651760: _raw_spin_unlock_bh <-release_sock vsftpd-1855 [000] 5064.651780: _raw_spin_lock_bh <-lock_sock_nested vsftpd-1855 [000] 5064.651800: _raw_spin_lock_bh <-release_sock vsftpd-1855 [000] 5064.651819: _raw_spin_unlock_bh <-release_sock vsftpd-1855 [000] 5064.651840: _raw_spin_lock <-fd_install vsftpd-1855 [000] 5064.651871: _raw_spin_lock_irq <-sigprocmask vsftpd-1855 [000] 5064.651924: _raw_spin_lock_irq <-sigprocmask vsftpd-1855 [000] 5064.651974: _raw_spin_lock <-tick_periodic vsftpd-1855 [000] 5064.652009: update_process_times <-tick_periodic vsftpd-1855 [000] 5064.652017: account_process_tick <-update_process _times vsftpd-1855 [000] 5064.652034: _raw_spin_lock <-hrtimer_run_queues vsftpd-1855 [000] 5064.652082: _raw_spin_lock <-scheduler_tick vsftpd-1855 [000] 5064.652164: _raw_spin_lock_irq <-run_timer_softir q vsftpd-1855 [000] 5064.652241: copy_process <-do_fork vsftpd-1855 [000] 5064.652278: _raw_spin_lock <-cache_alloc_refill … .. 就只会看到有包含spin 与process 字串的函式呼叫,
接下来, 如果要把有包含raw 与times 字元的函式过滤掉, 可透过如下设定 # echo '*raw*' '*times*' >/debug/tracing/set_ftrace_notrace
执行结果如下所示 #tracer: function # # TASK-PID CPU# TIMESTAMP FUNCTION # | | | | | vsftpd-2099 [000] 5220.419859: tcp_rcv_state_process <-tcp_v4_do_rcv vsftpd-2099 [000] 5220.419996: account_process_tick <-update_process_times vsftpd-2099 [000] 5220.420520: tcp_child_process <-tcp_v4_do_rcv vsftpd-2099 [000] 5220.420542: tcp_rcv_state_process <-tcp_child_process vsftpd-2099 [000] 5220.420913: wake_up_process <-wake_up_worker vsftpd-2099 [000] 5220.421737: account_process_tick <-update_process_times vsftpd-1855 [000] 5220.422045: account_process_tick <-update_process_times vsftpd-1855 [000] 5220.422358: copy_process <-do_fork vsftpd-1855 [000] 5220.422953: account_process_tick <-update_process_times vsftpd-2099 [000] 5220.423830: tcp_rcv_state_process <-tcp_v4_do_rcv vsftpd-2099 [000] 5220.423903: kick_process <-signal_wake_up vsftpd-2099 [000] 5220.423961: account_process_tick <-update_process_times vsftpd-2099 [000] 5220.424257: kick_process <-signal_wake_up … ..
可以看到最后的结果, 就是会包含有spin 与process 字串的函式呼叫, 并且把其中有包括raw与times 字串的函式呼叫过滤掉. | |||||||||||||||||||||||||||||||||||||||||
Functiongraph tracer
设定路径为Kernelhacking --->Tracers --->Kernel Function Tracer --->KernelFunction Graph Tracer
以笔者所验证的LinuxKernel 2.6.31 而言, 目前在ARM 平台尚未支援这机制, 而X86 平台则可以支援.
操作范例如下所示 , #echo '*' >/debug/tracing/set_ftrace_filter => 恢复对所有函式的Tracing #echo function_graph >/debug/tracing/current_tracer #echo 1 >/debug/tracing/tracing_enabled => 让FTP Client 连进来 # echo 0 >/debug/tracing/tracing_enabled 执行结果如下所示 #tracer: function_graph # # TIME CPU DURATION FUNCTION CALLS # | | | | | | | | 0) 7.525 us | } /* idle_cpu */ 0)+ 36.032 us | } /* irq_enter */ 0) | tick_handle_periodic() { 0) | tick_periodic() { 0) | do_timer() { 0)+ 16.017 us | update_wall_time(); 0) 6.749 us | calc_global_load(); 0)+ 43.684 us | } 0) | account_process_tick() { 0) 6.852 us | account_system_time(); 0)+ 20.041 us | } 0) | run_local_timers() { 0) | hrtimer_run_queues() { 0) 6.243 us | __current_kernel_time(); 0) 6.183 us | __get_wall_to_monotonic(); 0)+ 35.781 us | } 0) 9.591 us | raise_softirq(); 0)+ 65.198 us | } 0) | rcu_check_callbacks() { 0) 6.357 us | idle_cpu(); 0) | __rcu_pending() { 0) 6.199 us | rcu_gp_in_progress(); 0) 6.218 us | cpu_has_callbacks_ready_to_invoke(); 0) 6.265 us | cpu_needs_another_gp(); 0) 6.175 us | rcu_gp_in_progress(); 0)+ 59.513 us | } 0) 6.867 us | raise_softirq(); 0)+ 98.833 us | } 0) 6.292 us | printk_tick(); 0) | scheduler_tick() { 0)+ 11.142 us | ktime_get(); 0) 7.172 us | update_rq_clock(); 0) | sched_avg_update() { 0) 6.353 us | sched_avg_period(); 0)+ 19.685 us | } 0) | task_tick_fair() { 0) 6.826 us | update_curr(); 0) | sched_slice() { 0) 6.851 us | calc_delta_mine(); 0)+ 20.522 us | }
原本结果显示的是时间差, 也可以透过 trace_options 选择显示绝对时间值
# echo funcgraph-abstime>/debug/tracing/trace_options 执行结果如下所示
#tracer: function_graph # # TIME CPU DURATION FUNCTION CALLS # | | | | | | | | 8919.132197| 0) ! 201.630 us | } /* __do_softirq */ 8919.132218| 0) ! 265.733 us | } /* do_softirq */ 8919.132229| 0) ! 297.422 us | } /* irq_exit */ ------------------------------------------ 0) vsftpd-2125 => <...>-1855 ------------------------------------------
8919.132798| 0) + 50.964 us | irq_enter(); 8919.132908| 0) + 25.462 us | raise_softirq(); 8919.133053| 0) | irq_exit() { 8919.133076| 0) | do_softirq() { 8919.133095| 0) | __do_softirq() { 8919.133153| 0) + 43.064 us | run_timer_softirq(); 8919.133245| 0) ! 148.201 us | } 8919.133260| 0) ! 185.542 us | } 8919.133272| 0) ! 221.503 us | } 8919.134684| 0) + 47.945 us | irq_enter(); 8919.134785| 0) + 19.704 us | raise_softirq(); 8919.134911| 0) | irq_exit() { 8919.134931| 0) | do_softirq() { 8919.134949| 0) | __do_softirq() { 8919.135019| 0) + 17.437 us | irq_enter(); 8919.135080| 0) + 18.143 us | raise_softirq(); 8919.135165| 0) + 19.679 us | irq_exit(); 8919.135230| 0) + 39.134 us | run_timer_softirq(); 8919.135307| 0) + 33.181 us | run_timer_softirq(); 8919.135384| 0) ! 434.587 us | } 8919.135398| 0) ! 468.233 us | } 8919.135408| 0) ! 500.772 us | } ------------------------------------------ 0) <...>-1855 => vsftpd-2125 ------------------------------------------
8919.135778| 0) + 43.128 us | irq_enter(); 8919.135900| 0) + 18.773 us | raise_softirq(); 8919.136019| 0) | irq_exit() { 8919.136037| 0) | do_softirq() { 8919.136052| 0) | __do_softirq() { 8919.136100| 0) + 37.945 us | run_timer_softirq(); 8919.136179| 0) ! 125.562 us | } 8919.136192| 0) ! 155.517 us | } 8919.136201| 0) ! 184.853 us | }
| |||||||||||||||||||||||||||||||||||||||||
Latency Tracing | 1,irqsoff tracer
设定路径为Kernelhacking --->Tracers --->Interrupts-off Latency Tracer
主要用来Tracing 中断被关闭的行为, 并且会把关闭最久的时间与对应的呼叫动作显示出来, 提供系统效能分析的判断之用, 例如: 当在中断中做了过多I/O 的行为, 就会对系统效能造成影响, 透过分析可以把这类设计移到Bottom-Half(HISR) 中处理, 减少系统把中断关闭所导致的效能影响 .( 属于 I/O 的等待应该放到系统闲置的时间才处理 , 在中断里要越快结束越好 , 把处理器执行时间让给优先级高的 Task).
执行范例如下所示 # echo nop >current_tracer # echo irqsoff> current_tracer # echo 1 >tracing_enabled # sleep 3 # echo 0>tracing_enabled
显示结果如下
#tracer: irqsoff # #irqsoff latency trace v1.1.5 on 2.6.38.6 #------------------------------------------------- ------------------- #latency: 10747 us, #123/123, CPU#0 | (M:desktop VP:0, KP:0, SP:0HP:0 #P:1) # ----------------- # | task: vsftpd-1961 (uid:0 nice:0 policy:0 rt_prio:0) # ----------------- # => started at: __make_request # => ended at: __blk_run_queue # # # _------=> CPU# # / _-----=> irqs-off # | / _----=> need-resched # || / _---=> hardirq/softirq # ||| / _--=> preempt-depth # |||| /_--=> lock-depth # |||||/ delay # cmd pid |||||| time | caller # / / |||||| / | / vsftpd-1961 0dN... 23us+: _raw_spin_lock_irq <-__make_request vsftpd-1961 0dN... 103us+: cpu_coregroup_mask <-__make_request vsftpd-1961 0dN... 118us!: elv_queue_empty <-__make_request vsftpd-1961 0dN... 997us+: cfq_queue_empty <-elv_queue_empty vsftpd-1961 0dN... 1055us+: blk_plug_device <-__make_request vsftpd-1961 0dN... 1109us!: mod_timer <-blk_plug_device vsftpd-1961 0dN... 1216us+: lock_timer_base <-mod_timer vsftpd-1961 0dN... 1231us+: _raw_spin_lock_irqsave <-lock_timer_base vsftpd-1961 0dN... 1253us+: internal_add_timer <-mod_timer vsftpd-1961 0dN... 1270us+: _raw_spin_unlock_irqrestore <-mod_timer vsftpd-1961 0dN... 1369us!: drive_stat_acct <-__make_request vsftpd-1961 0dN... 1480us!: disk_map_sector_rcu <-drive_stat_acct vsftpd-1961 0dN... 1713us+: part_round_stats <-drive_stat_acct vsftpd-1961 0dN... 1782us+: part_round_stats_single <-part_round_stats vsftpd-1961 0dN... 1832us!: part_round_stats_single <-part_round_stats vsftpd-1961 0dN... 1938us+: __elv_add_request <-__make_request vsftpd-1961 0dN... 1993us!: elv_insert <-__elv_add_request vsftpd-1961 0dN... 2100us!: elv_rqhash_add <-elv_insert vsftpd-1961 0dN... 2259us+: cfq_insert_request <-elv_insert vsftpd-1961 0dN... 2330us+: cfq_init_prio_data <-cfq_insert_request vsftpd-1961 0dN... 2421us+: cfq_add_rq_rb <-cfq_insert_request vsftpd-1961 0dN... 2489us!: elv_rb_add <-cfq_add_rq_rb vsftpd-1961 0dN... 2637us+: cfq_resort_rr_list <-cfq_add_rq_rb vsftpd-1961 0dN... 2689us+: cfq_service_tree_add <-cfq_resort_rr_list vsftpd-1961 0dN... 2724us+: cfqq_type <-cfq_service_tree_add … .. vsftpd-1961 0dN... 10463us+: _raw_spin_lock_irqsave <-lock_timer_base vsftpd-1961 0dN... 10476us+: internal_add_timer <-mod_timer vsftpd-1961 0dN... 10490us+: _raw_spin_unlock_irqrestore <-mod_timer vsftpd-1961 0dN... 10562us!: _raw_spin_lock <-scsi_request_fn vsftpd-1961 0dN... 10736us+: scsi_request_fn <-__blk_run_queue vsftpd-1961 0dN... 10760us+: trace_hardirqs_on <-__blk_run_queue … .
其中, 中间六个属性数值意义分别为 1,CPU : 该任务所运作的CPUID 2,irqs-off : 'd'= 关闭中断,'.'= 中断没关闭,'X'= 该平台不支援读取IRQFlag 3,need-resched:'N'=need_resched 被设置( 表示行程会重新排程),'.'= 前项不成立. 4,hardirq/softirq:'H'= 在软体中断进行过程中, 发生的硬体中断, 'h' = 正在硬体中断处理中, 's'= 正在软体中断处理中,'.' = 一般行程运作中 5,preempt-depth: 表示preempt_disabled 的Level 6,lock-depth:
接下来属于time 的栏位, 单位为us, 代表的是从呼叫的函式到被呼叫的函式所经过的时间,这段时间如果超过preempt_mark_thresh(default 100) 的值, 时间后面就会标示' !', 如果超过1个us, 就会标示'+', 如果小于或等于1us 则部会标示任何符号.
以这例子来说, 起点会在函式__make_request( 实作在block/blk-core.c 中) 呼叫spin_lock_irq关闭硬体中断, 最后在函式__blk_run_queue( 实作在block/blk-core.c 中) 后, 恢复硬体中断运作. => 恢复中断的流程, 是呼叫函式trace_hardirqs_on( 实作在kernel/lockdep.c 中), 再呼叫到函式trace_hardirqs_on_caller( 实作在kernel/lockdep.c 中), 完成恢复硬体中断的动作.
2,Wakeup tracer
设定路径为Kernelhacking --->Tracers --->Scheduling Latency Tracer
可用以显示 Real-TimeTask 取得执行权所需等待的时间 , 操作范例如下所示
# echo wakeup >current_tracer # echo 1 >tracing_enabled # chrt -f 10 ps ==> 用 real-Time 权限 10 执行 ps 指令
执行结果如下
#tracer: wakeup # #wakeup latency trace v1.1.5 on 2.6.38.6 #------------------------------------------------- ------------------- #latency: 5961 us, #149/149, CPU#0 | (M:desktop VP:0, KP:0, SP:0HP:0 #P:1) # ----------------- # | task: kworker/0:1-9481 (uid:0 nice:0 policy:0 rt_prio:0) # ----------------- # # _------=> CPU# # / _-----=> irqs-off # | / _----=> need-resched # || / _---=> hardirq/softirq # ||| / _--=> preempt-depth # |||| /_--=> lock-depth # |||||/ delay # cmd pid |||||| time | caller # / / |||||| / | / gam_serv-1892 0dNs.. 15us!: 1892:139:R + [000] 9481:120:R kworker/0:1 gam_serv-1892 0dNs.. 171us+: wake_up_process <-wake_up_worker gam_serv-1892 0dNs.. 187us+: check_preempt_curr <-try_to_wake_up gam_serv-1892 0dNs.. 199us+: check_preempt_wakeup <-check_preempt_curr gam_serv-1892 0dNs.. 214us+: update_curr <-check_preempt_wakeup … . gam_serv-1892 0.N... 5392us+: mutex_lock <-inotify_poll gam_serv-1892 0.N... 5416us+: _cond_resched <-mutex_lock gam_serv-1892 0.N... 5431us!: __cond_resched <-_cond_resched gam_serv-1892 0.N... 5668us+: schedule <-__cond_resched gam_serv-1892 0.N... 5697us+: rcu_note_context_switch <-schedule gam_serv-1892 0.N... 5727us+: rcu_sched_qs <-rcu_note_context_switch gam_serv-1892 0.N... 5755us+: _raw_spin_lock_irq <-schedule gam_serv-1892 0dN... 5785us+: update_rq_clock <-schedule gam_serv-1892 0dN... 5798us+: put_prev_task_fair <-schedule gam_serv-1892 0dN... 5805us+: update_curr <-put_prev_task_fair gam_serv-1892 0dN... 5812us+: check_spread <-put_prev_task_fair gam_serv-1892 0dN... 5821us+: __enqueue_entity <-put_prev_task_fair gam_serv-1892 0dN... 5839us+: pick_next_task_fair <-schedule gam_serv-1892 0dN... 5845us+: __pick_next_entity <-pick_next_task_fair gam_serv-1892 0dN... 5853us+: clear_buddies <-pick_next_task_fair gam_serv-1892 0dN... 5862us+: set_next_entity <-pick_next_task_fair gam_serv-1892 0dN... 5869us+: update_stats_wait_end <-set_next_entity gam_serv-1892 0dN... 5880us+: __dequeue_entity <-set_next_entity gam_serv-1892 0d.... 5937us+: schedule <-__cond_resched gam_serv-1892 0d.... 5943us : 1892:139:R ==> [000] 9481:120:Rkworker/0:1
根据上述的结果, 我们可以看到花了约5961us 从由kworker 交出执行权到重新取得执行权,上面的列表中, 包括了超过1us 的函式呼叫标示为'+', 与超过100us 的函式呼叫标示为'!', 以如下的行为来说, 就是从函式set_next_entity 呼叫函式update_stats_wait_end 到函式set_next_entity 呼叫函式__dequeue_entity, 中间间隔了5880-5869=11us 的时间( 因为超过1us 所以标示为'+') gam_serv-1892 0dN...5869us+: update_stats_wait_end <-set_next_entity gam_serv-1892 0dN...5880us+: __dequeue_entity <-set_next_entity 或是 gam_serv-1892 0.N...5431us!: __cond_resched <-_cond_resched gam_serv-1892 0.N...5668us+: schedule <-__cond_resched
| ||||||||||||||||||||||||||||||||||||||||
Task Context-SwitchSchedule tracer | 设定路径为Kernelhacking --->Tracers --->Scheduling Latency Tracer
用以解析 TaskContext-Switch 的资讯, 可以参考如下的范例
# echo >/data/debug/tracing/trace => 清空trace 内容 # echo sched_switch >/data/debug/tracing/current_tracer # echo 1 >/data/debug/tracing/tracing_enabled # sleep 1 # echo 0 >/data/debug/tracing/tracing_enabled #cat/data/debug/tracing/trace #tracer: sched_switch # # TASK-PID CPU# TIMESTAMP FUNCTION # | | | | |
events/0-4 [000] 1044.598519: 4:115:S ==> [000] 87:120:RAlarmManager AlarmManager-87 [000] 1044.598749: 87:120:R + [000] 69:118:SActivityManager AlarmManager-87 [000] 1044.599057: 87:120:R ==> [000] 69:118:RActivityManager ActivityManager-69 [000] 1044.599411: 69:118:S ==> [000] 87:120:RAlarmManager AlarmManager-87 [000] 1044.599643: 87:120:R + [000] 69:118:SActivityManager AlarmManager-87 [000] 1044.599957: 87:120:R ==> [000] 69:118:RActivityManager ActivityManager-69 [000] 1044.600498: 69:118:S ==> [000] 87:120:RAlarmManager AlarmManager-87 [000] 1044.600929: 87:120:R + [000] 69:118:SActivityManager AlarmManager-87 [000] 1044.601509: 87:120:R ==> [000] 69:118:RActivityManager ActivityManager-69 [000] 1044.602102: 69:118:R + [000] 72:120:SProcessStats ActivityManager-69 [000] 1044.604484: 69:118:R + [000] 67:118:Ser.ServerThread ActivityManager-69 [000] 1044.604661: 69:118:R ==> [000] 67:118:Rer.ServerThread er.ServerThread-67 [000] 1044.605145: 67:118:S ==> [000] 72:120:RProcessStats ProcessStats-72 [000] 1044.611966: 72:120:R ==> [000] 69:118:RActivityManager
如上所示, 所列出的数据分别包含TaskID/Name, 运作的处理器ID( 供多核心的环境分析),每次触发的时间值, 如下以上述例子做一些说明 AlarmManager-87 [000] 1044.600929: 87:120:R + [000] 69:118:SActivityManager =>PID87, 执行优先级120,Task 状态为Running, 运作在处理器0 的AlarmManager, 会唤醒PID69, 执行优先级118,Task 状态为Sleep 运作在处理器0 的ActivityManager AlarmManager-87 [000] 1044.601509: 87:120:R ==> [000] 69:118:RActivityManager =>PID87, 执行优先级120,Task 状态为Running, 运作在处理器0 的AlarmManager, 会把执行权切换(Context-Switch) 到PID69, 执行优先级118,Task 状态为Running 运作在处理器0 的ActivityManager ActivityManager-69 [000] 1044.602102: 69:118:R + [000] 72:120:SProcessStats =>PID69, 执行优先级118,Task 状态为Running, 运作在处理器0 的ActivityManager,会唤醒PID72, 执行优先级120,Task 状态为Sleep 运作在处理器0 的ProcessStats ActivityManager-69 [000] 1044.604484: 69:118:R + [000] 67:118:Ser.ServerThread =>PID69, 执行优先级118,Task 状态为Running, 运作在处理器0 的ActivityManager,会唤醒PID67, 执行优先级118,Task 状态为Sleep 运作在处理器0 的er.ServerThread ActivityManager-69 [000] 1044.604661: 69:118:R ==> [000] 67:118:Rer.ServerThread =>PID69, 执行优先级118,Task 状态为Running, 运作在处理器0 的ActivityManager,会把执行权切换(Context-Switch) 到PID67, 执行优先级118,Task 状态为Running 运作在处理器0 的er.ServerThread
有关TaskPriority 参考如下表 ( 技术部分也可以参考笔者这篇文章Linux 2.4/2.6 核心排程机制剖析”http://loda.hala01.com/2009/04/linux-2426-%e6%a0%b8%e5%bf%83 %e6%8e%92%e7%a8%8b%e6%a9%9f%e5%88%b6%e5%89%96%e6%9e%90/”)
有关Task 状态参考如下表
| ||||||||||||||||||||||||||||||||||||||||
Block Device Tracer | 设定路径为Kernelhacking --->Tracers --->Support for tracing block ioactions
可以用来监控储存装置的使用状况, 与行为 例如笔者要启动BlockDevice Tracer 监控目前正在编译程式码的储存装置sda2, 如下操作指令
#echo 0 >/debug/tracing/tracing_enabled #echo 1>/sys/block/sda/sda2/trace/enable # echo blk >current_tracer #echo 1 >/debug/tracing/tracing_enabled … .. 执行编译的行为.... #echo 0 >/debug/tracing/tracing_enabled #echo 0>/sys/block/sda/sda2/trace/enable 再来检视trace 中的结果如下所示, #tracer: blk # mv-21550[000] 6663.719323: 8,2 m N cfq21550 sl_used=4 disp=1charge=4 iops=0 sect=8 mv-21550[000] 6663.719341: 8,2 m N cfq21550 del_from_rr mv-21550[000] 6663.719367: 8,2 m N cfq21550 put_queue cc1-21557[000] 6663.920569: 8,2 AR 221728392 + 56 <- (253,0)221728008 cc1-21557[000] 6663.920576: 8,2 AR 221937237 + 56 <- (8,2)221728392 cc1-21557[000] 6663.920577: 8,2 QR 221937237 + 56 [cc1] cc1-21557[000] 6663.920751: 8,2 m N cfq21557 alloced cc1-21557[000] 6663.920816: 8,2 GR 221937237 + 56 [cc1] cc1-21557[000] 6663.920875: 8,2 PN [cc1] cc1-21557[000] 6663.921844: 8,2 IR 221937237 + 56 [cc1] cc1-21557[000] 6663.921870: 8,2 m N cfq21557 insert_request cc1-21557[000] 6663.921906: 8,2 m N cfq21557 add_to_rr as-21558[000] 6663.923906: 8,2 UT N [as] 1 kworker/0:1-16318[000] 6663.924047: 8,2 UN [kworker/0:1] 1 kworker/0:1-16318[000] 6663.924102: 8,2 m N cfq workload slice:100 kworker/0:1-16318[000] 6663.924146: 8,2 m N cfq21557 set_active wl_prio:0wl_type:2 … .............. ld-21578[000] 6665.826767: 8,2 UN [ld] 0 ld-21578[000] 6665.826865: 8,2 UN [ld] 0 ld-21578[000] 6665.829130: 8,2 UN [ld] 0 ld-21578[000] 6665.831853: 8,2 UN [ld] 0 ld-21578[000] 6665.839538: 8,2 m N cfq21578 put_queue <...>-21532[000] 6665.902373: 8,2 m N cfq21532 put_queue make-21583[000] 6665.920115: 8,2 AR 221728880 + 8 <- (253,0)221728496 make-21583[000] 6665.920121: 8,2 AR 221937725 + 8 <- (8,2)221728880 make-21583[000] 6665.920123: 8,2 QR 221937725 + 8 [make] make-21583[000] 6665.920468: 8,2 m N cfq21583 alloced make-21583[000] 6665.920646: 8,2 GR 221937725 + 8 [make] make-21583[000] 6665.920691: 8,2 PN [make] make-21583[000] 6665.920741: 8,2 IR 221937725 + 8 [make] make-21583[000] 6665.920770: 8,2 m N cfq21583 insert_request make-21583[000] 6665.920800: 8,2 m N cfq21583 add_to_rr make-21583[000] 6665.920861: 8,2 UN [make] 1 make-21583[000] 6665.920897: 8,2 m N cfq workload slice:100 make-21583[000] 6665.920923: 8,2 m N cfq21583 set_active wl_prio:0wl_type:2 … ..............
| ||||||||||||||||||||||||||||||||||||||||
Branch tracer | 1,Trace likely/unlikely profiler 设定路径为Kernelhacking --->Tracers --->Branch Profiling --->Tracelikely/unlikely profiler
用以显示 Kernel 基于 likely/unlikely 的 Run-Time 统计结果
操作范例如下所示
#echo 0 >tracing_enabled # echo branch >current_tracer #echo 1 >tracing_enabled … ..do something..... #echo 0 >tracing_enabled 查看结果如下
#tracer: branch # # TASK-PID CPU# TIMESTAMP CORRECT FUNC:FILE:LINE # | | | | | | <idle>-0 [000] 55415.397835: [ ok ] read_seqretry:seqlock.h:110 <idle>-0 [000] 55415.397848: [ ok ] native_sched_clock:tsc.c:55 <idle>-0 [000] 55415.397859: [ ok ] native_sched_clock:tsc.c:55 … . <idle>-0 [000] 55415.398682: [ ok ] __local_bh_enable:softirq.c:138 <idle>-0 [000] 55415.398695: [ ok ]update_wall_time:timekeeping.c:789 <idle>-0 [000] 55415.398710: [ MISS ]timekeeping_adjust:timekeeping.c:711 <idle>-0 [000] 55415.398725: [ ok ]update_wall_time:timekeeping.c:839 <idle>-0 [000] 55415.398734: [ ok ]update_wall_time:timekeeping.c:859 … .. <idle>-0 [000] 55415.401654: [ ok ]update_wall_time:timekeeping.c:789 <idle>-0 [000] 55415.401668: [ MISS ]timekeeping_adjust:timekeeping.c:711 <idle>-0 [000] 55415.401677: [ ok ]update_wall_time:timekeeping.c:839 <idle>-0 [000] 55415.401687: [ ok ]update_wall_time:timekeeping.c:859 … ..
针对MISS 比较多的段落写法, 我们可以透过修改likely/unlikely 的配置,让gcc__builtin_expect 的函式发挥作用, 把真正的热区放在循序的执行过程中, 非热区放在需要Jump/Branch 的位址, 减少处理器Pipeline 被Flush 的机会, 从而也可以让系统的运作效率更佳.
2,Profile all if conditionals
设定路径为Kernelhacking --->Tracers --->Branch Profiling --->Profileall if conditionals
操作范例如下所示
#echo 0 >tracing_enabled # echo branch >current_tracer #echo 1 >tracing_enabled … ..do something..... #echo 0 >tracing_enabled
查看结果如下
#tracer: branch # # TASK-PID CPU# TIMESTAMP CORRECT FUNC:FILE:LINE # | | | | | | <...>-1806 [000] 297.131833: [ ok ] native_sched_clock:tsc.c:55 <...>-1806 [000] 297.131843: [ ok ] native_sched_clock:tsc.c:55 <...>-1806 [000] 297.131850: [ ok ] sched_clock_local:sched_cloc kc:149 <...>-1806 [000] 297.131860: [ ok ] sched_clock_cpu:sched_clock. c:219 <...>-1806 [000] 297.131866: [ ok ] sched_clock_cpu:sched_clock. c:219 <...>-1806 [000] 297.131873: [ ok ] sched_clock_cpu:sched_clock. c:224 <...>-1806 [000] 297.131879: [ ok ] native_sched_clock:tsc.c:55 <...>-1806 [000] 297.131886: [ ok ] sched_clock_local:sched_cloc kc:149 <...>-1806 [000] 297.131896: [ ok ] update_curr:sched_fair.c:576 <...>-1806 [000] 297.131903: [ ok ] calc_delta_fair:sched_fair.c :477 <...>-1806 [000] 297.131913: [ ok ] trace_sched_stat_runtime:sch ed.h:363: <...>-1806 [000] 297.131922: [ ok ] __sched_period:sched_fair.c:496 <...>-1806 [000] 297.131928: [ ok ] sched_slice:sched_fair.c:521 <...>-1806 [000] 297.131935: [ MISS ] calc_delta_mine:sched.c:1327 <...>-1806 [000] 297.131942: [ ok ] resched_task:sched.c:1158 <...>-1806 [000] 297.131949: [ MISS ] test_tsk_need_resched:sched.h:2378 <...>-1806 [000] 297.131956: [ ok ]perf_event_task_tick:perf_event.c:1828 <...>-1806 [000] 297.131963: [ ok ]perf_event_task_tick:perf_event.c:1828 <...>-1806 [000] 297.131970: [ MISS ]trigger_load_balance:sched_fair.c:3989 <...>-1806 [000] 297.131978: [ ok ]run_posix_cpu_timers:posix-cpu-timers.c:1312 <...>-1806 [000] 297.131995: [ ok ] __local_bh_disable:softirq.c:98 <...>-1806 [000] 297.132002: [ ok ] __local_bh_disable:softirq.c:98 <...>-1806 [000] 297.132032: [ ok ] trace_softirq_entry:irq.h:117 <...>-1806 [000] 297.132054: [ ok ] trace_softirq_exit:irq.h:131
| ||||||||||||||||||||||||||||||||||||||||
Kernel memory tracer | 内存tracer 主要用来跟踪slaballocator 的分配情况。包括kfree ,kmem_cache_alloc 等API的调用情况,用户程序可以根据tracer 收集到的信息分析内部碎片情况,找出内存分配最频繁的代码片断,等等
设定路径为Kernelhacking --->Tracers --->Trace SLAB allocations
操作范例如下所示
# echo 0 > tracing_enabled # echo kmemtrace >current_tracer # echo 1 > tracing_enabled # echo 0 > tracing_enabled … .etc
| ||||||||||||||||||||||||||||||||||||||||
Workqueue statisticaltracer | 这是一个statistictracer ,主要用来统计系统所有workqueue 现况 , 可以知道有多少 work 被插入到 queue 中 , 以及有多 少被执行 , 与 work 的名称 , 可用以提供开发端对于 workqueue机制实现的参考 .
设定路径为Kernelhacking --->Tracers --->Traceworkqueues, 系统启动后, 可以到/data/debug/tracing/trace_stat/workqueues 下, 查看内容, 如下所示 # CPU INSERTED EXECUTED NAME # | | | | 0 2139 2139 events/0 0 0 0 khelper 0 0 0 suspend 0 1 1 kblockd/0 0 1 1 kmmcd 0 0 0 aio/0 0 0 0 crypto/0 0 0 0 rpciod/0
| ||||||||||||||||||||||||||||||||||||||||
Profiling | 1,unlikely/likely
可用以显示 likely/unlikely 条件判断式的统计结果
统计结果位于/debug/tracing/trace_stat/branch_annotated 如下所示
correctincorrect % Function File Line ---------------- - -------- ---- ---- 0 1 100 tcp_synack_options tcp_output.c 535 0 1 100 tcp_synack_options tcp_output.c 529 0 1 100 tcp_synack_options tcp_output.c 524 0 9 100 tcp_options_write tcp_output.c 397 0 3622 100 tcp_established_options tcp_output.c 562 0 7 100 sys_inotify_add_watch inotify_user.c 750 0 7 100 fput_light file.h 29 0 17920 100 get_futex_key futex.c 269 0 28 100 clocksource_adjust timekeeping.c 472 0 4 100 blocking_notifier_chain_regist notifier.c 220 0 1 100 signal_pending sched.h 2251 0 6849 100 trace_workqueue_execution workqueue.h 53 0 8 100 trace_workqueue_creation workqueue.h 76 0 6849 100 trace_workqueue_insertion workqueue.h 31 0 38 100 yield_task_fair sched_fair.c 1016 0 25617 100 _pick_next_task_rt sched_rt.c 1041 0 92856 100 sched_info_switch sched_stats.h 269 0 85509 100 sched_info_queued sched_stats.h 222 0 54733 100 sched_info_dequeued sched_stats.h 177 0 1 100 consistent_init dma-mapping.c 470 9 1710 99 tcp_options_write tcp_output.c 396 2 354 99 disk_put_part genhd.h 209 606 67234 99 fput_light file.h 29 35 9260 99 trace_kmalloc kmem.h 79 313 12957 97 fput_light file.h 29 542 10337 95 sys_gettimeofday time.c 111 70 770 91 blk_update_request blk-core.c 1968 28116 135585 82 fget_light file_table.c 332 2 8 80 trace_kmalloc kmem.h 79 24893 53424 68 fput_light file.h 29 4547 8951 66 need_resched sched.h 2273 750 1338 64 fput_light file.h 29 15323 25008 62 wakeup_gran sched_fair.c 1382 44 61 58 mmdrop sched.h 2024 821 1006 55 pskb_trim_rcsum skbuff.h 1690 84797 95325 52 calc_delta_fair sched_fair.c 400 1 1 50 remap_pte_range memory.c 1634 270 270 50 isolate_lru_pages vmscan.c 926 32028 27195 45 rmqueue_bulk page_alloc.c 907 1970 1505 43 copy_pte_range memory.c 618 24549 17984 42 trace_kfree kmem.h 208
2,brancheall
可用以显示所有 if 条件判断式的统计结果
统计结果位于/debug/tracing/trace_stat/branch_all 如下所示
miss hit % Function File Line ---------------- - -------- ---- ---- 0 1 100 i386_start_kernel head32.c 50 1 0 0 reserve_ebda_region head.c 51 1 0 0 reserve_ebda_region head.c 47 0 1 100 reserve_ebda_region head.c 42 0 0 X nrcpus main.c 167 0 0 X maxcpus main.c 178 1098 0 0 do_one_initcall main.c 773 1098 0 0 do_one_initcall main.c 769 1098 0 0 do_one_initcall main.c 765 1098 0 0 do_one_initcall main.c 762 1098 0 0 do_one_initcall main.c 755 1 0 0 start_kernel main.c 682 0 1 100 start_kernel main.c 675 1 0 0 start_kernel main.c 661 1 0 0 start_kernel main.c 647 1 0 0 start_kernel main.c 630 1 0 0 start_kernel main.c 611 1 0 0 kernel_init main.c 914 0 1 100 kernel_init main.c 911 1 0 0 kernel_init main.c 901 1 0 0 smp_init main.c 399 1 0 0 smp_init main.c 397 1 1 50 cpumask_next cpumask.h 172 0 0 X init_post main.c 850 0 1 100 init_post main.c 838 0 0 X unknown_bootoption main.c 321 0 0 X unknown_bootoption main.c 313 0 0 X unknown_bootoption main.c 309 0 0 X unknown_bootoption main.c 305 0 0 X unknown_bootoption main.c 302 0 0 X unknown_bootoption main.c 299 0 2 100 unknown_bootoption main.c 295 0 0 X unknown_bootoption main.c 286 0 1 100 unknown_bootoption main.c 284 1 1 50 unknown_bootoption main.c 282 0 2 100 obsolete_checksetup main.c 235 2 0 0 obsolete_checksetup main.c 231 0 0 X obsolete_checksetup main.c 229 2 0 0 obsolete_checksetup main.c 224 28 2 6 obsolete_checksetup main.c 223 1 1 50 parse_early_param main.c 496 0 0 X do_early_param main.c 476 368 0 0 do_early_param main.c 475 1 0 0 readonly do_mounts.c 44
3,Functions
可用以显示所有函式执行次数与时间的统计结果 .
操作范例如下所示
# echo 1 >/debug/tracing/function_profile_enabled … ..do something..... # echo 0 >/debug/tracing/function_profile_enabled #cat/debug/tracing/trace_stat/function0
可看到如下结果
Function Hit Time Avg s^2 -------- --- ---- ------ schedule 752 99800000 us 132712.7 us2635264000 us poll_schedule_timeout 57 37432000 us 656701.7 us 2143996599 us schedule_hrtimeout_range 55 37429000 us 680527.2 us 339918459 us schedule_hrtimeout_range_clock 55 37424000 us 680436.3 us 237544596 us sys_select 87 25028000 us 287678.1 us306802216 us core_sys_select 87 25012000 us 287494.2 us155578451 us do_select 87 24875000 us 285919.5 us323446561 us smp_apic_timer_interrupt 15677 16056000 us 1024.175 us 76796.05 us do_timer 15677 15677000 us 1000.000 us0.000 us tick_periodic 15677 15677000 us 1000.000 us 0.000us tick_handle_periodic 15677 15677000 us 1000.000 us 0.000 us sys_poll 14 14227000 us 1016214 us 3 503583162 us do_sys_poll 14 14217000 us 1015500 us 3379054126 us sys_nanosleep 15 13957000 us 930466.6 us1834329984 us hrtimer_nanosleep 15 13945000 us 929666.6 us 1719729983us do_nanosleep 15 13944000 us 929600.0 us1710319131 us sys_read 167 13503000 us 80856.28 us1298120022 us vfs_read 172 13490000 us 78430.23 us2981769264 us tty_read 61 13297000 us 217983.6 us2678381427 us n_tty_read 61 13256000 us 217311.4 us2834883001 us schedule_timeout 20 12966000 us 648300.0 us2458710875 us
| ||||||||||||||||||||||||||||||||||||||||
Max Stack Tracer | 可用以显示核心函式呼叫最深的 Stack 大小与在该情况下的 CallStack 路径 .
设定路径为Kernelhacking --->Tracers --->Trace max stack
# echo 1 >/proc/sys/kernel/stack_tracer_enabled # sleep 10 # echo 0 >/proc/sys/kernel/stack_tracer_enabled # cat stack_max_size 2152 # cat stack_trace Depth Size Location (22 entries) ----- ---- -------- 0) 2084 68 update_curr+0x100/0x210 1) 2016 56 enqueue_task_fair+0x90/0x4e0 2) 1960 24 enqueue_task+0x8d/0xb0 3) 1936 12 activate_task+0x25/0x50 4) 1924 88 try_to_wake_up+0x24a/0x5f0 5) 1836 8 wake_up_process+0x14/0x20 6) 1828 8 hrtimer_wakeup+0x2c/0x30 7) 1820 80 hrtimer_run_queues+0x1d4/0x480 8) 1740 8 run_local_timers+0xd/0x20 9) 1732 20 update_process_times+0x34/0x90 10) 1712 8 tick_periodic+0x7a/0x90 11) 1704 32 tick_handle_periodic+0x1e/0xb0 12) 1672 8 smp_apic_timer_interrupt+0x9c/0x9e 13) 1664 108 apic_timer_interrupt+0x2f/0x34 14) 1556 232 schedule+0x62b/0x10f0 15) 1324 76 schedule_hrtimeout_range_clock+0x14b/0x170 16) 1248 12 schedule_hrtimeout_range+0x17/0x20 17) 1236 20 poll_schedule_timeout+0x4c/0x70 18) 1216 764 do_select+0x789/0x7c0 19) 452 332 core_sys_select+0x27d/0x460 20) 120 40 sys_select+0x40/0xe0 21) 80 80 syscall_call+0x7/0xb
主要会列出目前最深的StackSize, 与根据该StackSize 发生的情况下, 完整的CallStack 流程与每个函式所占的空间( 会包括区域变数与FunctionParameters 所占的Stack 空间). 让开发者, 可以根据这流程去检讨函式与呼叫流程设计的合理性.
| ||||||||||||||||||||||||||||||||||||||||
Event tracing | Event 几乎是整个Ftrace 中, 各种资讯的集合, 浏览/debug/tracing/events 目录中的档案名称如下所示 drwxr-xr-x 4 root root 0XXX 20 23:55 bkl drwxr-xr-x 20 root root 0XXX 20 23:55 block -rw-r--r-- 1 root root 0XXX 20 23:55 enable drwxr-xr-x 14 root root 0XXX 20 23:55 ftrace -r--r--r-- 1 root root 0XXX 20 23:55 header_event -r--r--r-- 1 root root 0XXX 20 23:55 header_page drwxr-xr-x 7 root root 0XXX 20 23:55 irq drwxr-xr-x 14 root root 0XXX 20 23:55 kmem drwxr-xr-x 3 root root 0XXX 20 23:55 mce drwxr-xr-x 7 root root 0XXX 20 23:55 module drwxr-xr-x 3 root root 0XXX 20 23:55 napi drwxr-xr-x 6 root root 0XXX 20 23:55 net drwxr-xr-x 12 root root 0XXX 20 23:55 power drwxr-xr-x 4 root root 0XXX 20 23:55 raw_syscalls drwxr-xr-x 18 root root 0XXX 20 23:55 sched drwxr-xr-x 7 root root 0XXX 20 23:55 scsi drwxr-xr-x 6 root root 0XXX 20 23:55 signal drwxr-xr-x 5 root root 0XXX 20 23:55 skb drwxr-xr-x 634 root root 0XXX 20 23:55 syscalls drwxr-xr-x 14 root root 0XXX 20 23:55 timer drwxr-xr-x 15 root root 0XXX 20 23:55 vmscan drwxr-xr-x 6 root root 0XXX 20 23:55 workqueue drwxr-xr-x 23 root root 0XXX 20 23:55 writeback 除了enable,header_event 与header_page 这三个控制档案外, 其他每个目录都包含一种类别的Event 参数, 以目前笔者所使用的环境,Event 包含了以下的类别
| ||||||||||||||||||||||||||||||||||||||||
|
|
结尾,
在平台移植与开发上, 效能调教对于产品化的影响很深, 本文主要介绍Ftrace 的概念与操作范例, 对笔者而言, 这套工具可以帮助在复杂行为下, 去分析系统效能的瓶颈, 例如, 是否有中断关闭过长的行为, 或是修改过的核心函式, 不适当的行为占据过多的处理器时间. 目前, 在Linux 平台上, 能使用的工具不少( 或是也可以使用商业的ICE 产品), 对这些工具内部运作原理掌握的程度越高, 我们就会有足够的知识背景帮助团队在问题发生时, 借重适当的工具去分析问题的原因,Ftrace 只是提供系统问题一部分的资讯, 其它可用的工具与分析问题的知识背景, 会是有志于在这产业的开发者, 需要持续努力的.