Kernel trace tools(二):內核態執行時間跟蹤
摘要:trace_noschedule 只會針對內核態執行時間超過閾值不調度的進程記錄 stack trace。stack_trace 記錄佔用 CPU 時間超過閾值不調度進程的棧。
本文是由字節跳動系統部 STE 團隊出品的“kernel trace tools”系列文章之一,以介紹團隊自研多類延遲問題追蹤工具。
在實際工作中,會遇到由於內核態不調度而引發的高網絡延遲或者卡頓問題。但是,對於該類問題的定位和追蹤缺乏行之有效的方案或客觀依據,需要耗費大量時間和精力用於問題排查,trace-noschedule 工具便是在該背景下誕生的自研工具。
目前,trace-noschedule 已開源,感興趣詳見 Open Source Repo:
( https://github.com/bytedance/trace-noschedule )
1. 問題背景
在實際項目實踐中,我們經常會遇到延遲高導致的問題。由於我們服務器的內核默認配成內核態不支持搶佔,因此不搶佔這點也是可能導致問題的原因之一。例如 A 進程陷入內核態執行時間過長,必然影響其他希望在該核運行的進程。此時就會導致調度延遲。針對這種 case,我們開發了一款工具專門跟蹤陷入內核態長時間不調度的進程。這對於我們排查問題可以有一定的指導方向。同時如果是此類原因導致的延遲,也可以快速定位問題原因。
2. 我們想做什麼
當一個進程陷入內核態時,我們應該記錄其時間戳。當離開內核時,再一次記錄時間戳。然後求差值即可得到進程在內核態運行的時間。思路比較清晰。
3. 如何實現
我們知道應用程序陷入內核態的方式主要是系統調用。我們是否可以通過系統調用提供的 tracepoint 完成呢?當系統調用開始,記錄時間戳,系統調用退出,再次記錄時間戳。求差值,就是本次系統調用花費的時間。這種方法是否可靠?很明顯不可靠。因爲進程在內核態很可能由於資源不滿足導致主動 schedule。這種情況下,根本不是一直佔用 CPU 不調度的情況。因此,這種方法不可行。
既然系統調用這條路走不通,我們就換一種方式。我們知道一個線程執行的生命週期,起於 schedule,止於 schedule。所以我們可以知道一個線程從開始執行到主動或被動放棄 CPU 之間的時間差。這部分時間是總時間,即 user+kernel 的時間。我們是否有辦法獲取總時間呢?很幸運我們有 schedule 的 tracepoint 可以獲取。
static void __sched notrace __schedule(bool preempt) { /* ... */ trace_sched_switch(preempt, prev, next); /* ... */ }
我們只需要 enable sched_switch 即可獲取每個線程執行的總時間。是不是激動的心,顫抖的手,是不是馬上想用 bcc 行動了?但是有個新的問題擺在我們面前,如何過濾掉 user 態執行的時間?因爲用戶態支持搶佔,內核態不支持搶佔。所以統計內核態的時間纔是有意義的。
過濾用戶態執行時間可以間接的通過定時器實現。我們知道週期性定時中斷,可以獲取被打斷的上下文是用戶態或者內核態。所以開始時間戳 last 需要不斷的更新。第一次是從 schedule 開始,後面每次更新是在定時器中斷中,如果發現當前中斷是打斷的用戶態,那麼更新 last 時間戳,否則不更新。此刻認爲用戶線程在內核態執行。我們看下示例代碼。
static enum hrtimer_restart trace_nosched_hrtimer_handler(struct hrtimer *hrtimer) { /* * Skip the idle task and make sure we are not only the * running task on the CPU. If we are interrupted from * user mode, it indicate that we are not executing in * the kernel space, so we should also skip it. */ if (!is_idle_task(current) && regs && !user_mode(regs) && !single_task_running()) { /* The kernel mode */ /* * If the execution time exceeds the threshold, record * the call trace. */ } else { /* The user mode */ stack_trace->last_timestamp = now; } }
我們看到不更新時間戳的條件比較苛刻。主要考慮以下幾種情況:
-
如果中斷時從用戶態發生,則更新 last_timestamp 時間戳。
-
如果當前 CPU 運行隊列只有一個 task,更新 last_timestamp 時間戳。因爲這種情況下,佔有 CPU 不放沒有任何問題。
-
如果當前進程是 idle 進程,同樣更新 last_timestamp 時間戳。因爲 idle 進程本身就可能在內核態長時間運行,沒有任何問題。
我們再看下 tracepoint 的 hook 函數實現。
static void probe_sched_switch(void *priv, bool preempt, struct task_struct *prev, struct task_struct *next) { u64 now = local_clock(); u64 last = cpu_stack_trace->last_timestamp; cpu_stack_trace->last_timestamp = now; /* * Skip the idle task and make sure we are not only the * running task on the CPU. */ if (!is_idle_task(prev) && !single_task_running()) hist_update(cpu_stack_trace, now - last); }
記錄時間差值,差不多可以認爲是內核態執行的時間。當操作設定閾值,我們更新柱狀圖統計計數。
4. 如何安裝
安裝 trace-noschedule 工具很簡單,git clone 代碼後執行如下命令即可安裝。
make -j8 make install
5. 如何使用
安裝 trace-noschedule 工具成功後。會創建如下 /proc/trace_noschedule 目錄。
root@n18-061-206:/proc/trace_noschedule# ls distribution enable stack_trace threshold
/proc/trace_noschedule 目錄下存在 4 個文件,分別:distribution, enable, stack_trace 和 threshold。工具安裝後,默認是關閉狀態。
5.1 打開 tracer
執行以下命令打開 tracer。
echo 1 > /proc/trace_noschedule/enable
5.2 關閉 tracer
執行如下命令關閉 tracer。
echo 0 > /proc/trace_noschedule/enable
Note : debug 問題後請記得關閉 tracer。因爲模塊內部實現基於 sched tracepoint,overhead 不能忽略。
5.3 設置閾值
trace_noschedule 只會針對內核態執行時間超過閾值不調度的進程記錄 stack trace。爲了更高效的運作,我們有必要設定一個合理閾值。例如設置 60ms 的閾值(單位:ns):
echo 60000000 > /proc/trace_noschedule/threshold
5.4 查看內核態長時間未調度進程執行的時間分佈。
root@n18-061-206:/proc/trace_noschedule# cat distribution Trace noschedule thread: msecs : count distribution 20 -> 39 : 1 |********** | 40 -> 79 : 0 | | 80 -> 159 : 4 |****************************************| 160 -> 319 : 2 |******************** |
在內核態有 4 次執行時間在[80, 159]ms 範圍內沒有調度。
5.5 是誰佔用 CPU 不調度
stack_trace 記錄佔用 CPU 時間超過閾值不調度進程的棧。
root@n18-061-206:/proc/trace_noschedule# cat stack_trace cpu: 0 COMM: sh PID: 1270013 DURATION: 100ms delay_tsc+0x21/0x50 nosched_test_write+0x53/0x90 [trace_noschedule] proc_reg_write+0x36/0x60 __vfs_write+0x33/0x190 vfs_write+0xb0/0x190 ksys_write+0x52/0xc0 do_syscall_64+0x4f/0xe0 entry_SYSCALL_64_after_hwframe+0x44/0xa9
這是個內核態測試的 case,在內核態執行 mdelay(100)佔用 CPU 100ms 不調度。此時記錄的棧如上面所示。"DURATION"記錄的就是執行持續時間。
5.6 清除 stack trace
如果我們需要清除 stack trace 記錄的信息(stack trace buffer 是有大小限制的,必要的時候需要 clear)。
echo 0 > /proc/trace_noschedule/stack_trace
6. 案例展示
這裏以一個實際問題帶你瞭解工具的使用。問題是背景是,業務發現 docker 好像有問題,pull 不下來鏡像。登錄機器會卡頓,執行很多命令也會卡頓。
6.1 首先看下 CPU 使用情況。
有幾個 CPU 100%,而且還是 sys 態使用。系統究竟在幹嗎?
6.2 使用 perf 工具
使用 perf top 看下 CPU 情況,發現 60%左右的時間在 spinlock 上。但是沒看出內核哪個路徑的 spinlock。使用 trace-noschedule 工具看看是否有發現。
6.3 trace-noschedule 跟蹤
root@n131-103-013:/proc/trace_noschedule# cat distribution Trace noschedule thread: msecs : count distribution 20 -> 39 : 760 |****************************************| 40 -> 79 : 488 |************************* | 80 -> 159 : 73 |*** |
內核態長時間未調度,而且時間很長。看下工具記錄的棧信息,截取如下:
COMM: runc PID: 499045 DURATION: 75ms native_queued_spin_lock_slowpath+0x112/0x190 _raw_spin_lock+0x1d/0x20 prepend_path+0x266/0x2d0 __d_path+0x4b/0x80 seq_path_root+0x53/0xb0 show_mountinfo+0xb8/0x290 seq_read+0x321/0x3f0 __vfs_read+0x33/0x160 vfs_read+0x91/0x130 SyS_read+0x52/0xc0 do_syscall_64+0x68/0x100 entry_SYSCALL_64_after_hwframe+0x3d/0xa2 COMM: falcon-agent PID: 3002056 DURATION: 114ms native_queued_spin_lock_slowpath+0x10f/0x190 _raw_spin_lock+0x1d/0x20 prepend_path+0x266/0x2d0 __d_path+0x4b/0x80 seq_path_root+0x53/0xb0 show_vfsmnt+0x7a/0x170 seq_read+0x321/0x3f0 __vfs_read+0x33/0x160 vfs_read+0x91/0x130 SyS_read+0x52/0xc0 do_syscall_64+0x68/0x100 entry_SYSCALL_64_after_hwframe+0x3d/0xa2
說明內核態確實一直在 spin。主要集中在以上兩個路徑文件的讀取。分別是 /proc/$pid/mounts
和 /proc/$pid/mountinfo
文件。
6.4 查看 mountinfo 信息
可能是 mount 太多,導致遍歷時間很久。而且長時間持鎖。
root@n131-103-013:/var/log/atop# mount -v | wc -l 65803
確實很多,65803 多個。大量的/dev/shm 掛載。以下信息只是其中一部分展示。既然知道了問題原因就可以對症下藥了,下一步就是排查爲什麼/dev/shm 掛載很多。這讓我們很快定位問題原因。
7. 總結
根據字節內部實踐來看,trace-noschedule 安裝便捷且使用靈活度高,能將問題定位耗時縮短至分鐘級別,使用收益可觀,並且已經通過該工具定位一些,提高了工作效率。
更多分享
Kernel trace tools(一):中斷和軟中斷關閉時間過長問題追蹤
gdb 提示 coredump 文件 truncated 問題排查
字節跳動系統部 STE 團隊
字節跳動系統部 STE 團隊一直致力於操作系統內核與虛擬化、系統基礎軟件與基礎庫的構建和性能優化、超大規模數據中心的穩定性和可靠性建設、新硬件與軟件的協同設計等基礎技術領域的研發與工程化落地,具備全面的基礎軟件工程能力,爲字節上層業務保駕護航。同時,團隊積極關注社區技術動向,擁抱開源和標準。歡迎更多有志之士加入,如有意向可發送簡歷至 [email protected] 。
歡迎關注字節跳動技術 團 隊
點擊閱讀原文,快來加入我們吧!