[Android][kernel][Linux]使用ftrace抓取IO相关信息
ftrace介绍
官方介绍可以在kernel/Documentation/trace/ftrace.txt中查看,如下是截取片段后的翻译结果:
ftrace (function trace) 是旨在帮助开发者探究内核内部运行逻辑的一个追踪器。可用于调试或分析内核里面的高延迟性能问题;
虽然ftrace从命名来看是针对函数的追踪器,但是实际上它已经成为了一个整合了多种追踪器的工具框架。其不仅可以显示中断开关的耗时,也可以追踪到任务从唤醒到加入执行队列之间由于资源抢占导致的时间差;
ftrace中一个最常用的使用方式是事件追踪(event tracing)。其可以通过debugfs文件系统启用内核中数百个静态事件埋点,来跟踪内核中某一特定模块的运行情况;
ftrace常用概念
tracer
tracer - 顾名思义,追踪器,不同追踪器各司其职,用在不同的场景。以Android kernel 3.18为例,目前调试的手机有如下三种追踪器可用:
xxx:/ # cat /sys/kernel/debug/tracing/available_tracers
function_graph function nop
根据官方介绍,如下是翻译后的解释:
-
function - 记录kernel内部所有函数调用
-
function_graph - 与function类似,但是function仅仅记录函数入口,而function_graph会记录入口与出口,从而可以生成一份类似C语言源代码格式的图表;
-
nop - 'trace nothing’跟踪器,顾名思义,当current_tracer节点为nop时,不会启用任何追踪器;
要切换追踪器很简单,只需要将如上可用的追踪器名字写入current_tracer节点即可:
echo function_graph > /sys/kernel/debug/tracing/current_tracer
event
event - 事件,用于过滤需要记录的事件,只有设置在set_event节点中的事件才会被抓取;默认该节点内容为空;
xxx:/ # cat /sys/kernel/debug/tracing/set_event
xxx:/ #
至于可用的event标签,则可以通过/sys/kernel/debug/tracing/available_events节点查看,由于太多,此处就不一一列举;
另外还有一个方式可以查看支持的event标签,那就是/sys/kernel/debug/tracing/events/目录下的子目录:
xxx:/ # ls /sys/kernel/debug/tracing/events/
almk cma enable filelock i2c jbd2 mmc msm_vidc power rcu rpm_smd skb thermal wil6210
android_fs compaction exception filemap iommu kgsl module napi printk regmap sched sock timer workqueue
asoc cpufreq_interactive ext3 ftrace ipa kmem msm_bus net process_reclaim regulator scm spi udp writeback
binder devfreq ext4 gpio ipi lowmemorykiller msm_cam oom random rmnet_data scsi swiotlb ufs xhci-hcd
block dwc3 f2fs header_event irq mdss msm_core pagemap ras rndis_ipa sde_rotator sync v4l2
cfg80211 emulation fence header_page jbd migrate msm_low_power perf_trace_counters raw_syscalls rpm signal task vmscan
因此,启用某个event也有两种方式:
- 使用echo 1 > /sys/kernel/debug/tracing/events/<$event>/enable来启用某类标签;
- 使用echo <$event> > /sys/kernel/debug/tracing/set_event来启用某类标签;
两者是等效的,并且可以在另一个方式所示的节点中通过cat来确认状态;
xxx:/ # cat /sys/kernel/debug/tracing/events/android_fs/enable
0
xxx:/ # echo android_fs > /sys/kernel/debug/tracing/set_event
xxx:/ # cat /sys/kernel/debug/tracing/events/android_fs/enable
1
filter
filter - 过滤器,用于过滤函数,避免抓取的trace无效信息过多;
可用的filter很多,可从节点/sys/kernel/debug/tracing/available_filter_functions中查看;
cat /sys/kernel/debug/tracing/available_filter_functions
默认不进行过滤,若需要过滤函数,则将对应的函数名写入/sys/kernel/debug/tracing/set_ftrace_filter,例如:
echo vfs_write > /sys/kernel/debug/tracing/set_ftrace_filter
抓取
ftrace一般抓取信息量很大,因此建议增大buffer以确保信息完整:
echo 32768 > /sys/kernel/debug/tracing/buffer_size_kb
抓取前后建议清理cache,以保证结果不受缓存影响:
echo 3 > /proc/sys/vm/drop_caches
如果不想保留之前抓取的trace,则可以将之前的记录清空:
echo "" > /sys/kernel/debug/tracing/trace
准备就绪后,就可以复现问题并抓取了:
echo 1 > /sys/kernel/debug/tracing/tracing_on
抓取时尽量保证操作单一,操作不要太复杂,否则trace可阅读性会比较差;
复现完毕后,通过如下指令关闭ftrace录制:
echo 0 > /sys/kernel/debug/tracing/tracing_on
最后,将录制的trace文件拉出并使用文本编辑器打开阅读:
adb pull /sys/kernel/debug/tracing/trace ~/Desktop/trace.txt
trace文件一般比较大,建议使用比较省内存的文本编辑器打开(图形化工具推荐sublime text);
示例
比如,如果遇到了文件写入速度慢的问题,想确认一下到底是哪一阶段出现了问题,那么可以通过如下步骤抓取:
echo 'mmc android_fs block sched_switch sched_wakeup sched_blocked_reason *****' > /sys/kernel/debug/tracing/set_event
echo 'function_graph' > /sys/kernel/debug/tracing/current_tracer
echo 32768 > /sys/kernel/debug/tracing/buffer_size_kb
echo vfs_write > /sys/kernel/debug/tracing/set_ftrace_filter
echo "" > /sys/kernel/debug/tracing/trace
echo 3 > /proc/sys/vm/drop_caches
echo 1 > /sys/kernel/debug/tracing/tracing_on
dd if=/dev/zero of=/sdcard/test count=1
echo 0 > /sys/kernel/debug/tracing/tracing_on
注意:抓取会导致读写性能明显下降,因此时间的绝对值不能作为参考,只能通过对比相对大小来定位问题;
此处由于我只抓取了vfs层的写入,因此trace文件很小,并且很容易定位到对应位置:
------------------------------------------
4) | vfs_write() {
4) | vfs_write() {
4) | /* android_fs_datawrite_start: entry_name /media/0/test, offset 0, bytes 512, cmdline dd, pid 3024, i_size 0, ino 8751 */
4) | /* android_fs_datawrite_end: ino 8751, offset 0, bytes 512 */
4) ! 124.479 us | }
4) ! 138.073 us | }
4) | vfs_write() {
4) | /* sched_wakeup: comm=kworker/4:4 pid=2944 prio=120 success=1 target_cpu=004 */
4) | /* sched_switch: prev_comm=dd prev_pid=3024 prev_prio=120 prev_state=R+ ==> next_comm=kworker/4:4 next_pid=2944 next_prio=120 */
------------------------------------------
4) dd-3024 => kworker-2944
------------------------------------------
后记
最近由于工作需要,在研究IO方面的问题,因此需要深度研究IO的流程,用ftrace抓取函数调用可以做到事半功倍,比一句一句跟代码,或者一句一句打log要高效得多;
更多用法,还请自己多多发掘;
文笔有限,若有纰漏,还请多多指教;