起因
之前跟章亦春(春哥)討論如何排查系統(tǒng)的長尾問題的時候,春哥舉了他之前在 Cloudflare 排查的一個例子,一個請求比較慢,所以需要確定是網(wǎng)絡(luò)的問題亩码,還是磁盤 IO 的問題,但這幾個地方的 99.9% metric 都非常正常凡桥,所以推測應(yīng)該是長尾問題蟀伸。于是春哥在 TCP蚀同,I/O 相關(guān)的地方用 SystemTap 加上 probe缅刽,然后發(fā)現(xiàn)網(wǎng)絡(luò)能正常的處理,但在一些 I/O 操作的時候有很慢的情況蠢络,確定了 I/O 有問題衰猛,最后在追查到磁盤有一個地方有損壞。
那時候我就對 SystemTap 印象深刻了刹孔。雖然之前也有很多動態(tài)追蹤的實踐啡省,但通常還是對 perf 使用較多,對 SystemTap 的研究比較少髓霞,但這次交流之后卦睹,讓我確定對于一些長尾疑難問題,SystemTap 是一個非常好的方法方库,值得花時間去研究一下结序,
介紹
SystemTap 是一個動態(tài)追蹤工具,它使用 kprobes 組件去幫助用戶更深入的學(xué)習(xí)和監(jiān)控整個系統(tǒng)(無論是 kernel 還是 application)的狀態(tài)纵潦,而且運行的時候只對系統(tǒng)有極小的開銷徐鹤。雖然很多工具像 netstat,ps邀层,top返敬,iostat 等也能夠了解很多信息,但 SystemTap 能做到更多寥院,譬如知道 application 和 operating system 之間的交互劲赠,不同進(jìn)程的交互,不同 kernel subsystem 的交互秸谢,以及一些疑難雜癥的排查等经磅。
腳本處理
SystemTap 提供了一套腳本語言,讓用戶非常方便的編寫自定義測量以及分析工具去定位問題钮追。
當(dāng)用戶編寫一個腳本 probe 文件之后预厌,首先會經(jīng)過 parse 生成 AST,然后在 elaborate 階段元媚,解析相關(guān)的符號和引用信息轧叽,然后在轉(zhuǎn)成 C 的代碼并編譯成一個 kernel module苗沧。然后在 load module,開始 probe炭晒,運行結(jié)束之后待逞,將收集的信息高效的傳輸給用戶空間,然后 unload module网严。
腳本語言
SystemTap 的語法非常簡單识樱,如果熟悉 C,AWK 等語言的會非常容易上手震束,我們以一個非常簡單的例子稍作說明:
global bt_stats
global quit
probe begin {
warn("Start tracing. Wait for 10 sec to complete.\n")
}
probe process("/lib*/libc-2.17.so").function("__memcpy_ssse3_back") {
if (pid() == target()) {
if (quit) {
foreach (bt in bt_stats) {
print_ustack(bt)
printf("\t%d\n\n", @count(bt_stats[bt]))
}
exit()
} else {
bt = ubacktrace()
bt_stats[bt] <<< 1
}
}
}
probe timer.s(1) {
quit = 1
}
上面是我之前想看 TiKV memcpy 到底在哪里調(diào)用的一個簡單腳本怜庸。
變量
我們定義了兩個全局變量,quit
和 bt_stats
垢村,在 SystemTap 里面割疾,變量需要提前聲明,我們并不需要提前聲明變量的類型嘉栓,SystemTap 能自己判斷這個變量的使用類型宏榕。上面的 quit
是一個 int 變量,我們在 1s 之后會將其設(shè)置為 1侵佃,然后程序退出麻昼。bt_stats
是一個類似 AWK 關(guān)聯(lián)數(shù)組,它的 key 就是對應(yīng)的堆棧馋辈,而 value 則是一個統(tǒng)計變量抚芦。
統(tǒng)計變量是 SystemTap 里面用來進(jìn)行聚合操作的變量,譬如上面的 <<< 1
首有,每次匹配了 memcpy 函數(shù)之后燕垃,我們就會記錄下對應(yīng)堆棧的次數(shù)。然后在結(jié)束的時候我們可以用統(tǒng)計函數(shù)井联,譬如 count卜壕,得到實際的個數(shù)。
Probe
Probe 就是定義了實際的探針 points烙常,當(dāng)對應(yīng)的事件觸發(fā)的時候轴捎,就會去調(diào)用對應(yīng) probe 的 block 代碼,譬如上面我們 probe 了 process("/lib*/libc-2.17.so").function("__memcpy_ssse3_back")
蚕脏,我們知道 TiKV 進(jìn)程會使用 libc 的 memcpy 函數(shù)侦副,所以使用 process 從 libc 的 so 里面找到對應(yīng)的 function。上面我們是知道對應(yīng)的函數(shù)名字驼鞭,也可以通過 function(name@file:line)
等規(guī)則來匹配相關(guān)的函數(shù)秦驯。
我們也可以 probe kernel 函數(shù),譬如 probe kernel.funcion(*init*)
來匹配所有 kernel 里面的 init 函數(shù)挣棕。所以如果要精通 SystemTap 來動態(tài)追蹤译隘,我們其實需要更深刻的去理解整個 kernel 的軟件棧和 API亲桥。
SystemTap 也提供了 Tapsets 簡化了一些 kernel 的 probe 編寫,譬如我們可以通過 probe netdev.transmit
和 probe netdev.receive
來 probe 網(wǎng)絡(luò)的包收發(fā)固耘。
總結(jié)
上面簡單的介紹了 SystemTap 的相關(guān)知識题篷,可以看到,如果想更多了解厅目,可以看 SystemtTap 自己的相關(guān)文檔番枚,也可以通過官方 example 深入學(xué)習(xí)。但要更加熟練的使用 SystemTap 進(jìn)行診斷损敷,對 Linux 整個軟件棧的了解是必不可少的葫笼,所以后面看看 kernel 代碼是有必要的。