[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要高效得多;
更多用法,還請自己多多發掘;
文筆有限,若有紕漏,還請多多指教;