Linux中國

使用 ftrace 跟蹤內核

在內核層面上分析事件有很多的工具:SystemTapktapSysdigLTTNG 等等,你也可以在網路上找到關於這些工具的大量介紹文章和資料。

而對於使用 Linux 原生機制去跟蹤系統事件以及檢索/分析故障信息的方面的資料卻很少找的到。這就是 ftrace,它是添加到內核中的第一款跟蹤工具,今天我們來看一下它都能做什麼,讓我們從它的一些重要術語開始吧。

內核跟蹤和分析

內核分析 Kernel profiling 可以發現性能「瓶頸」。分析能夠幫我們發現在一個程序中性能損失的準確位置。特定的程序生成一個 概述 profile — 這是一個事件總結 — 它能夠用於幫我們找出哪個函數佔用了大量的運行時間。儘管這些程序並不能識別出為什麼會損失性能。

瓶頸經常發生在無法通過分析來識別的情況下。要推斷出為什麼會發生事件,就必須保存發生事件時的相關上下文,這就需要去 跟蹤 tracing

跟蹤可以理解為在一個正常工作的系統上活動的信息收集過程。它使用特定的工具來完成這項工作,就像錄音機來記錄聲音一樣,用它來記錄各種系統事件。

跟蹤程序能夠同時跟蹤應用級和操作系統級的事件。它們收集的信息能夠用於診斷多種系統問題。

有時候會將跟蹤與日誌比較。它們兩者確時很相似,但是也有不同的地方。

對於跟蹤,記錄的信息都是些低級別事件。它們的數量是成百上千的,甚至是成千上萬的。對於日誌,記錄的信息都是些高級別事件,數量上通常少多了。這些包含用戶登錄系統、應用程序錯誤、資料庫事務等等。

就像日誌一樣,跟蹤數據可以被原樣讀取,但是用特定的應用程序提取的信息更有用。所有的跟蹤程序都能這樣做。

在內核跟蹤和分析方面,Linux 內核有三個主要的機制:

  • 跟蹤點 tracepoint :一種基於靜態測試代碼的工作機制
  • 探針 kprobe :一種動態跟蹤機制,用於在任意時刻中斷內核代碼的運行,調用它自己的處理程序,在完成需要的操作之後再返回
  • perf_events —— 一個訪問 PMU( 性能監視單元 Performance Monitoring Unit )的介面

我並不想在這裡寫關於這些機制方面的內容,任何對它們感興趣的人可以去訪問 Brendan Gregg 的博客

使用 ftrace,我們可以與這些機制進行交互,並可以從用戶空間直接得到調試信息。下面我們將討論這方面的詳細內容。示例中的所有命令行都是在內核版本為 3.13.0-24 的 Ubuntu 14.04 中運行的。

ftrace:常用信息

ftrace 是 Function Trace 的簡寫,但它能做的遠不止這些:它可以跟蹤上下文切換、測量進程阻塞時間、計算高優先順序任務的活動時間等等。

ftrace 是由 Steven Rostedt 開發的,從 2008 年發布的內核 2.6.27 中開始就內置了。這是為記錄數據提供的一個調試 Ring 緩衝區的框架。這些數據由集成到內核中的跟蹤程序來採集。

ftrace 工作在 debugfs 文件系統上,在大多數現代 Linux 發行版中都默認掛載了。要開始使用 ftrace,你將進入到 sys/kernel/debug/tracing 目錄(僅對 root 用戶可用):

# cd /sys/kernel/debug/tracing

這個目錄的內容看起來應該像這樣:

аvailable_filter_functions  options            stack_trace_filter
available_tracers           per_cpu             trace
buffer_size_kb              printk_formats      trace_clock
buffer_total_size_kb        README              trace_marker
current_tracer              saved_cmdlines      trace_options
dyn_ftrace_total_info       set_event           trace_pipe
enabled_functions           set_ftrace_filter   trace_stat
events                      set_ftrace_notrace  tracing_cpumask
free_buffer                 set_ftrace_pid      tracing_max_latency
function_profile_enabled    set_graph_function  tracing_on
instances                   set_graph_notrace   tracing_thresh
kprobe_events               snapshot            uprobe_events
kprobe_profile              stack_max_size      uprobe_profile

我不想去描述這些文件和子目錄;它們的描述在 官方文檔 中已經寫的很詳細了。我只想去詳細介紹與我們這篇文章相關的這幾個文件:

  • available_tracers —— 可用的跟蹤程序
  • current_tracer —— 正在運行的跟蹤程序
  • tracing_on —— 負責啟用或禁用數據寫入到 Ring 緩衝區的系統文件(如果啟用它,數字 1 被添加到文件中,禁用它,數字 0 被添加)
  • trace —— 以人類友好格式保存跟蹤數據的文件

可用的跟蹤程序

我們可以使用如下的命令去查看可用的跟蹤程序的一個列表:

root@andrei:/sys/kernel/debug/tracing#: cat available_tracers
blk mmiotrace function_graph wakeup_rt wakeup function nop

我們來快速瀏覽一下每個跟蹤程序的特性:

  • function —— 一個無需參數的函數調用跟蹤程序
  • function_graph —— 一個使用子調用的函數調用跟蹤程序
  • blk —— 一個與塊 I/O 跟蹤相關的調用和事件跟蹤程序(它是 blktrace 使用的)
  • mmiotrace —— 一個內存映射 I/O 操作跟蹤程序
  • nop —— 最簡單的跟蹤程序,就像它的名字所暗示的那樣,它不做任何事情(儘管在某些情況下可能會派上用場,我們將在後文中詳細解釋)

函數跟蹤程序

在開始介紹函數跟蹤程序 ftrace 之前,我們先看一個測試腳本:

#!/bin/sh

dir=/sys/kernel/debug/tracing

sysctl kernel.ftrace_enabled=1
echo function > ${dir}/current_tracer
echo 1 > ${dir}/tracing_on
sleep 1
echo 0 > ${dir}/tracing_on
less ${dir}/trace

這個腳本是非常簡單的,但是還有幾個需要注意的地方。命令 sysctl ftrace.enabled=1 啟用了函數跟蹤程序。然後我們通過寫它的名字到 current_tracer 文件來啟用 current tracer

接下來,我們寫入一個 1tracing_on,它啟用了 Ring 緩衝區。這些語法都要求在 1> 符號前後有一個空格;寫成像 echo 1> tracing_on 這樣將不能工作。一行之後我們禁用它(如果 0 寫入到 tracing_on, 緩衝區不會被清除並且 ftrace 並不會被禁用)。

我們為什麼這樣做呢?在兩個 echo 命令之間,我們看到了命令 sleep 1。我們啟用了緩衝區,運行了這個命令,然後禁用它。這將使跟蹤程序採集了這個命令運行期間發生的所有系統調用的信息。

在腳本的最後一行,我們寫了一個在控制台上顯示跟蹤數據的命令。

一旦腳本運行完成後,我們將看到下列的輸出(這裡只列出了一個小片斷):

# tracer: function
#
# entries-in-buffer/entries-written: 29571/29571   #P:2
#
#                           _-----=> irqs-off
#                           / _----=> need-resched
#                           | / _---=> hardirq/softirq
#                           || / _--=> preempt-depth
#                           ||| /   delay
#           TASK-PID   CPU#  ||||   TIMESTAMP  FUNCTION
#           | |     |   ||||    |       |
        trace.sh-1295  [000] ....   90.502874: mutex_unlock <-rb_simple_write
        trace.sh-1295  [000] ....   90.502875: __fsnotify_parent <-vfs_write
        trace.sh-1295  [000] ....   90.502876: fsnotify <-vfs_write
        trace.sh-1295  [000] ....   90.502876: __srcu_read_lock <-fsnotify
        trace.sh-1295  [000] ....   90.502876: __srcu_read_unlock <-fsnotify
        trace.sh-1295  [000] ....   90.502877: __sb_end_write <-vfs_write
        trace.sh-1295  [000] ....   90.502877: syscall_trace_leave <-int_check_syscall_exit_work
        trace.sh-1295  [000] ....   90.502878: context_tracking_user_exit <-syscall_trace_leave
        trace.sh-1295  [000] ....   90.502878: context_tracking_user_enter <-syscall_trace_leave
        trace.sh-1295  [000] d...   90.502878: vtime_user_enter <-context_tracking_user_enter
        trace.sh-1295  [000] d...   90.502878: _raw_spin_lock <-vtime_user_enter
        trace.sh-1295  [000] d...   90.502878: __vtime_account_system <-vtime_user_enter
        trace.sh-1295  [000] d...   90.502878: get_vtime_delta <-__vtime_account_system
        trace.sh-1295  [000] d...   90.502879: account_system_time <-__vtime_account_system
        trace.sh-1295  [000] d...   90.502879: cpuacct_account_field <-account_system_time
        trace.sh-1295  [000] d...   90.502879: acct_account_cputime <-account_system_time
        trace.sh-1295  [000] d...   90.502879: __acct_update_integrals <-acct_account_cputime

這個輸出以「緩衝區中的信息條目數量」和「寫入的全部條目數量」開始。這兩者的數據差異是緩衝區中事件的丟失數量(在我們的示例中沒有發生丟失)。

在這裡有一個包含下列信息的函數列表:

  • 進程標識符(PID)
  • 運行這個進程的 CPU(CPU#)
  • 進程開始時間(TIMESTAMP)
  • 被跟蹤函數的名字以及調用它的父級函數;例如,在我們輸出的第一行,rb_simple_write 調用了 mutex-unlock 函數。

function_graph 跟蹤程序

function_graph 跟蹤程序的工作和函數跟蹤程序一樣,但是它更詳細:它顯示了每個函數的進入和退出點。使用這個跟蹤程序,我們可以跟蹤函數的子調用並且測量每個函數的運行時間。

我們來編輯一下最後一個示例的腳本:

#!/bin/sh

dir=/sys/kernel/debug/tracing

sysctl kernel.ftrace_enabled=1
echo function_graph > ${dir}/current_tracer
echo 1 > ${dir}/tracing_on
sleep 1
echo 0 > ${dir}/tracing_on
less ${dir}/trace

運行這個腳本之後,我們將得到如下的輸出:

# tracer: function_graph
#
# CPU  DURATION                 FUNCTION CALLS
# |     |   |                   |   |   |   |
 0)   0.120 us  |                               } /* resched_task */
 0)   1.877 us  |                               } /* check_preempt_curr */
 0)   4.264 us  |                           } /* ttwu_do_wakeup */
 0) + 29.053 us   |                         } /* ttwu_do_activate.constprop.74 */
 0)   0.091 us  |                           _raw_spin_unlock();
 0)   0.260 us  |                           ttwu_stat();
 0)   0.133 us  |                           _raw_spin_unlock_irqrestore();
 0) + 37.785 us   |                         } /* try_to_wake_up */
 0) + 38.478 us   |                     } /* default_wake_function */
 0) + 39.203 us   |                     } /* pollwake */
 0) + 40.793 us   |                 } /* __wake_up_common */
 0)   0.104 us  |                   _raw_spin_unlock_irqrestore();
 0) + 42.920 us   |                 } /* __wake_up_sync_key */
 0) + 44.160 us   |             } /* sock_def_readable */
 0) ! 192.850 us  |             } /* tcp_rcv_established */
 0) ! 197.445 us  |         } /* tcp_v4_do_rcv */
 0)   0.113 us  |           _raw_spin_unlock();
 0) ! 205.655 us  |         } /* tcp_v4_rcv */
 0) ! 208.154 us  |     } /* ip_local_deliver_finish */

在這個圖中,DURATION 展示了花費在每個運行的函數上的時間。注意使用 +! 符號標記的地方。加號(+)意思是這個函數花費的時間超過 10 毫秒;而感嘆號(!)意思是這個函數花費的時間超過了 100 毫秒。

FUNCTION_CALLS 下面,我們可以看到每個函數調用的信息。

和 C 語言一樣使用了花括弧({)標記每個函數的邊界,它展示了每個函數的開始和結束,一個用於開始,一個用於結束;不能調用其它任何函數的葉子函數用一個分號(;)標記。

函數過濾器

ftrace 輸出可能會很大,精確找出你所需要的內容可能會非常困難。我們可以使用過濾器去簡化我們的搜索:輸出中將只顯示與我們感興趣的函數相關的信息。為實現過濾,我們只需要在 set_ftrace_filter 文件中寫入我們需要過濾的函數的名字即可。例如:

root@andrei:/sys/kernel/debug/tracing# echo kfree > set_ftrace_filter

如果禁用過濾器,我們只需要在這個文件中添加一個空白行即可:

root@andrei:/sys/kernel/debug/tracing# echo  > set_ftrace_filter

通過運行這個命令:

root@andrei:/sys/kernel/debug/tracing# echo kfree > set_ftrace_notrace 

我們將得到相反的結果:輸出將包含除了 kfree() 以外的任何函數的信息。

另一個有用的選項是 set_ftrace_pid。它是為在一個特定的進程運行期間調用跟蹤函數準備的。

ftrace 還有很多過濾選項。對於它們更詳細的介紹,你可以去查看 Steven Rostedt 在 LWN.net 上的文章。

跟蹤事件

我們在上面提到到跟蹤點機制。跟蹤點是插入的觸發系統事件的特定代碼。跟蹤點可以是動態的(意味著可能會在它們上面附加幾個檢查),也可以是靜態的(意味著不會附加任何檢查)。

靜態跟蹤點不會對系統有任何影響;它們只是在測試的函數末尾增加幾個位元組的函數調用以及在一個獨立的節上增加一個數據結構。

當相關代碼片斷運行時,動態跟蹤點調用一個跟蹤函數。跟蹤數據是寫入到 Ring 緩衝區。

跟蹤點可以設置在代碼的任何位置;事實上,它們確實可以在許多的內核函數中找到。我們來看一下 kmem_cache_alloc 函數(取自 這裡):

{
    void *ret = slab_alloc(cachep, flags, _RET_IP_);

    trace_kmem_cache_alloc(_RET_IP_, ret,
                            cachep->object_size, cachep->size, flags);
         return ret;
         }

trace_kmem_cache_alloc 它本身就是一個跟蹤點。我們可以通過查看其它內核函數的源代碼找到這樣無數的例子。

在 Linux 內核中為了從用戶空間使用跟蹤點,它有一個專門的 API。在 /sys/kernel/debug/tracing 目錄中,這裡有一個事件目錄,它是為了保存系統事件。這些只是為了跟蹤系統事件。在這個上下文中系統事件可以理解為包含在內核中的跟蹤點。

可以通過運行如下的命令來查看這個事件列表:

root@andrei:/sys/kernel/debug/tracing# cat available_events

這個命令將在控制台中輸出一個很長的列表。這樣看起來很不方便。我們可以使用如下的命令來列出一個結構化的列表:

root@andrei:/sys/kernel/debug/tracing# ls events

block           gpio        mce     random      skb     vsyscall
btrfs           header_event  migrate  ras          sock    workqueue
compaction      header_page   module   raw_syscalls  spi    writeback
context_tracking  iommu         napi    rcu         swiotlb   xen
enable          irq         net     regmap      syscalls  xfs
exceptions      irq_vectors   nmi   regulator   task    xhci-hcd
ext4            jbd2        oom     rpm         timer
filemap         kmem        pagemap  sched      udp
fs              kvm         power   scsi        vfs
ftrace          kvmmmu      printk   signal     vmscan

所有可能的事件都按子系統分組到子目錄中。在我們開始跟蹤事件之前,我們要先確保啟用了 Ring 緩衝區寫入:

root@andrei:/sys/kernel/debug/tracing# cat tracing_on

如果在控制台中顯示的是數字 0,那麼,我們可以運行如下的命令來啟用它:

root@andrei:/sys/kernel/debug/tracing# echo 1 > tracing_on

在我們上一篇的文章中,我們寫了關於 chroot() 系統調用的內容;我們來跟蹤訪問一下這個系統調用。對於我們的跟蹤程序,我們使用 nop 因為函數跟蹤程序和 function_graph 跟蹤程序記錄的信息太多,它包含了我們不感興趣的事件信息。

root@andrei:/sys/kernel/debug/tracing# echo nop > current_tracer

所有事件相關的系統調用都保存在系統調用目錄下。在這裡我們將找到一個進入和退出各種系統調用的目錄。我們需要在相關的文件中通過寫入數字 1 來激活跟蹤點:

root@andrei:/sys/kernel/debug/tracing# echo 1 > events/syscalls/sys_enter_chroot/enable

然後我們使用 chroot 來創建一個獨立的文件系統(更多內容,請查看 之前這篇文章)。在我們執行完我們需要的命令之後,我們將禁用跟蹤程序,以便於不需要的信息或者過量信息不會出現在輸出中:

root@andrei:/sys/kernel/debug/tracing# echo 0 > tracing_on

然後,我們去查看 Ring 緩衝區的內容。在輸出的結束部分,我們找到了有關的系統調用信息(這裡只是一個節選)。

root@andrei:/sys/kernel/debug/tracing# сat trace

......
          chroot-11321 [000] ....  4606.265208: sys_chroot(filename: 7fff785ae8c2)
          chroot-11325 [000] ....  4691.677767: sys_chroot(filename: 7fff242308cc)
            bash-11338 [000] ....  4746.971300: sys_chroot(filename: 7fff1efca8cc)
            bash-11351 [000] ....  5379.020609: sys_chroot(filename: 7fffbf9918cc)

關於配置事件跟蹤的更的信息可以在 這裡 找到。

結束語

在這篇文篇中,我們做了一個 ftrace 的功能概述。我們非常感謝你的任何意見或者補充。如果你想深入研究這個主題,我們為你推薦下列的資源:

via:https://blog.selectel.com/kernel-tracing-ftrace/

作者:Andrej Yemelianov 譯者:qhwdw 校對:wxy

本文由 LCTT 原創編譯,Linux中國 榮譽推出


本文轉載來自 Linux 中國: https://github.com/Linux-CN/archive

對這篇文章感覺如何?

太棒了
0
不錯
0
愛死了
0
不太好
0
感覺很糟
0
雨落清風。心向陽

    You may also like

    Leave a reply

    您的電子郵箱地址不會被公開。 必填項已用 * 標註

    此站點使用Akismet來減少垃圾評論。了解我們如何處理您的評論數據

    More in:Linux中國