什么是動(dòng)態(tài)追蹤(Dynamic Tracing)
舉個(gè)簡(jiǎn)單例子,一個(gè)人正在健身房里跑步,我們用攝像機(jī)??偷偷對(duì)他進(jìn)行錄像,事后我們就可以使用錄像對(duì)這個(gè)人運(yùn)動(dòng)過(guò)程中的步頻、速度、呼吸節(jié)奏等數(shù)據(jù)進(jìn)行分析研究,以便為這個(gè)人提供更加科學(xué)的付費(fèi)健身咨詢服務(wù)(這個(gè)人并不知道被錄像了)。仔細(xì)想想這不是一個(gè)很有價(jià)值的事嗎?
對(duì)這個(gè)例子的整個(gè)過(guò)程分析,它大致有幾個(gè)特點(diǎn):
1、對(duì)健身者是無(wú)感知,不影響他當(dāng)前正在進(jìn)行的運(yùn)動(dòng),也不需要他配合(低影響)
2、我們錄像的時(shí)機(jī)可以隨時(shí)開(kāi)始,隨時(shí)結(jié)束(低耦合)
3、錄像的對(duì)象可以是人,也可以是物體(可移植)
這就是動(dòng)態(tài)追蹤技術(shù)(Dynamic Tracing),在計(jì)算機(jī)領(lǐng)域是一種后現(xiàn)代的高級(jí)調(diào)試技術(shù),可以幫助開(kāi)發(fā)者在非常短的時(shí)間內(nèi),回答一些很難的關(guān)于軟件系統(tǒng)方面的問(wèn)題,從而更快速地排查和解決問(wèn)題,也可以幫助產(chǎn)品決策者實(shí)時(shí)了解當(dāng)前產(chǎn)品的線上運(yùn)行情況。在火箭??般飛速發(fā)展的移動(dòng)互聯(lián)網(wǎng)時(shí)代,APP的用戶規(guī)模,業(yè)務(wù)種類繁多,邏輯越來(lái)越復(fù)雜,代碼編寫(xiě)也不再是單一語(yǔ)言。作為開(kāi)發(fā)者、產(chǎn)品決策者正在面臨無(wú)法掌控整個(gè)系統(tǒng)的巨大壓力,動(dòng)態(tài)追蹤技術(shù)實(shí)際就能幫助我們實(shí)現(xiàn)這種愿景。
動(dòng)態(tài)追蹤的原理
動(dòng)態(tài)追蹤技術(shù)通常是基于操作系統(tǒng)內(nèi)核來(lái)實(shí)現(xiàn)的。操作系統(tǒng)內(nèi)核其實(shí)可以控制整個(gè)軟件世界,因?yàn)樗鋵?shí)是處于"造物主"這樣的一個(gè)地位。它擁有絕對(duì)的權(quán)限,同時(shí)它可以確保我們針對(duì)軟件系統(tǒng)發(fā)出的各種"查詢"不會(huì)影響到軟件系統(tǒng)本身的正常運(yùn)行。換句話說(shuō),我們的這種查詢必須是足夠安全的,是可以在生產(chǎn)環(huán)境上大量使用的。一般是通過(guò)探針這樣的機(jī)制來(lái)發(fā)起查詢。我們會(huì)在軟件系統(tǒng)的某一個(gè)層次,或者某幾個(gè)層次上面,安置一些探針(測(cè)試人員術(shù)語(yǔ)叫打樁,產(chǎn)品人員術(shù)語(yǔ)叫埋點(diǎn)),然后我們會(huì)自己定義這些探針?biāo)P(guān)聯(lián)的處理程序,當(dāng)關(guān)聯(lián)程序不運(yùn)行時(shí),這些探針在系統(tǒng)中不會(huì)產(chǎn)生影響。而這些關(guān)聯(lián)程序可以通過(guò)這些探針實(shí)時(shí)地向我們反饋信息,幫助解決許多重大問(wèn)題。
這有點(diǎn)像中醫(yī)里面的針灸,就是說(shuō)如果我們把軟件系統(tǒng)看成是一個(gè)人,我們可以往他的一些穴位上扎一些“針”,那么這些針頭上面通常會(huì)有我們自己定義的一些“傳感器”,我們可以自由地采集所需要的那些穴位上的關(guān)鍵信息,然后把這些信息匯總起來(lái),產(chǎn)生可靠的病因診斷和可行的治療方案。這里的追蹤通常涉及兩個(gè)緯度。一個(gè)是時(shí)間緯度,因?yàn)檫@個(gè)軟件還一直在運(yùn)行,它便有一個(gè)在時(shí)間線上的連續(xù)的變化過(guò)程。另一個(gè)緯度則是空間緯度,因?yàn)榭赡芩婕暗蕉鄠€(gè)不同的進(jìn)程,包含內(nèi)核進(jìn)程,而每個(gè)進(jìn)程經(jīng)常會(huì)有自己的內(nèi)存空間、進(jìn)程空間,那么在不同的層次之間,以及在同一層次的內(nèi)存空間里面,我可以同時(shí)沿縱向和橫向,獲取很多在空間上的寶貴信息。這有點(diǎn)兒像蛛蛛在蛛網(wǎng)上搜索獵物。
<摘錄至https://openresty.org/posts/dynamic-tracing>
DTrace初探
DTrace 是動(dòng)態(tài)追蹤技術(shù)的鼻祖,它于 21 世紀(jì)初誕生于 Solaris 操作系統(tǒng),是由原來(lái)的 Sun Microsystems 公司的工程師編寫(xiě)的,先后被移植到 Linux、FreeBSD、NetBSD 及 Mac OS X 等操作系統(tǒng)上。iOS 系統(tǒng)也有,大名鼎鼎的 Instrument 工具就是基于 DTrace 實(shí)現(xiàn)的,而且更多的功能還在隨著 iOS 系統(tǒng)進(jìn)行版本迭代。
DTrace 工具組件包括提供器和探測(cè)器:
- 提供器:由
dtrace內(nèi)核驅(qū)動(dòng)命令及附加在上面的 DTrace 腳本組成(后綴名.d)。Mac OS X 默認(rèn)就安裝了dtrace工具;腳本使用D語(yǔ)言編寫(xiě),也叫 d 腳本,Mac OS X的/usr/share/examples/DTTk/目錄下有很多例子; - 探測(cè)器(即探針):由提供器啟動(dòng),可標(biāo)識(shí)所檢測(cè)的模塊和函數(shù),其名稱標(biāo)準(zhǔn)格式為
提供器:模塊:函數(shù):名稱,每個(gè)探針還具有一個(gè)唯一的整數(shù)標(biāo)識(shí)符。在蘋(píng)果開(kāi)源的 xnu 中可以看到蘋(píng)果版的 DTrace 源碼,打包為內(nèi)核模塊來(lái)收集跟蹤數(shù)據(jù),它提供接口通過(guò)dtrace內(nèi)核驅(qū)動(dòng)命令訪問(wèn)內(nèi)核數(shù)據(jù),在內(nèi)核源碼中很多帶有provider關(guān)鍵字都屬于標(biāo)識(shí)某個(gè)模塊數(shù)據(jù)的探針。其定義如下:
// 探針定義
typedef struct sdt_provider {
const char *sdtp_name; /* name of provider */
const char *sdtp_prefix; /* prefix for probe names */
dtrace_pattr_t *sdtp_attr; /* stability attributes */
dtrace_provider_id_t sdtp_id; /* provider ID */
} sdt_provider_t;
// xnu中在使用的一些探針
sdt_provider_t sdt_providers[] = {
{ "vtrace", "__vtrace____", &vtrace_attr, 0 },
{ "sysinfo", "__cpu_sysinfo____", &info_attr, 0 },
{ "vminfo", "__vminfo____", &info_attr, 0 },
{ "fpuinfo", "__fpuinfo____", &fpu_attr, 0 },
{ "sched", "__sched____", &stab_attr, 0 },
{ "proc", "__proc____", &stab_attr, 0 },
{ "io", "__io____", &stab_attr, 0 },
{ "ip", "__ip____", &stab_attr, 0 },
{ "tcp", "__tcp____", &stab_attr, 0 },
{ "mptcp", "__mptcp____", &stab_attr, 0 },
{ "mib", "__mib____", &stab_attr, 0 },
{ "fsinfo", "__fsinfo____", &fsinfo_attr, 0 },
{ "nfsv3", "__nfsv3____", &stab_attr, 0 },
{ "nfsv4", "__nfsv4____", &stab_attr, 0 },
{ "sysevent", "__sysevent____", &stab_attr, 0 },
{ "sdt", "__sdt____", &sdt_attr, 0 },
{ "boost", "__boost____", &stab_attr, 0},
{ NULL, NULL, NULL, 0 }
};
D語(yǔ)言
這里的 D語(yǔ)言 語(yǔ)法大部分跟 C語(yǔ)言 非常相似(這就帶來(lái)了很好的可移植性),但總體架構(gòu)是不同的。每個(gè)腳本由若干個(gè)探針語(yǔ)句組成。它們都符合如下的形式:
probe descriptions
/ predicate /
{
action statements
}
其中斷言 (predicate) 和動(dòng)作語(yǔ)句 (action statement) 部分都是可選的。
probe descriptions即探針描述定義了語(yǔ)句匹配什么類型的探針,結(jié)構(gòu)就是之前提到的提供器:模塊:函數(shù):名稱 — provider:module:function:name,所有的部分都可以省略。
其中BEGIN語(yǔ)句在所有探針開(kāi)始之前運(yùn)行,END語(yǔ)句在腳本退出時(shí)候執(zhí)行。
語(yǔ)法很簡(jiǎn)單,設(shè)計(jì)很復(fù)雜,更詳細(xì)的介紹參見(jiàn)。,本文只介紹后面Demo例子中會(huì)使用到的方法:
內(nèi)建變量:
pid —— 當(dāng)前進(jìn)程的進(jìn)程id
tid —— 當(dāng)前線程的線程id
uid —— 當(dāng)前進(jìn)程的用戶id
timestamp —— 一個(gè)納秒級(jí)的計(jì)數(shù)器的當(dāng)前時(shí)間戳
probemod —— 當(dāng)前探針描述的模塊名稱部分
probefunc —— 當(dāng)前探針描述的函數(shù)名稱部分
常用函數(shù):
void trace(expression) —— 最基本的操作,將將 D 表達(dá)式用作其參數(shù)并跟蹤結(jié)果到定向緩沖區(qū)
void printf(string format, ...) —— 與trace操作一樣,printf跟蹤 D 表達(dá)式。不過(guò),printf允許格式化輸出
當(dāng)運(yùn)行dtrace工具時(shí),我們傳入的腳本被編譯成字節(jié)碼。接著字節(jié)碼被傳入安插了探針的代碼中(通常是 kernel)。在 kernel 中有一個(gè)解釋器來(lái)運(yùn)行這些字節(jié)碼。當(dāng)將靜態(tài)探針加入可執(zhí)行程序 (一個(gè) app 或 framework),它們被作為S_DTRACE_DOF(Dtrace Object Format) 部分被加入,并且在程序運(yùn)行時(shí)被加載進(jìn) kernel。這樣 DTrace 就知道當(dāng)前的靜態(tài)探針。
Dtrace示例
使用dtrace -l可以查看 Mac OS X 系統(tǒng)上的所有探針,dtrace -l -m <module_name>可以查看指定模塊的探針
注意
1、如果出現(xiàn)錯(cuò)誤:
dtrace: failed to initialize dtrace: DTrace requires additional privileges
可以這樣提升dtrace工具的權(quán)限:
sudo dtrace -l
2、如果出現(xiàn)錯(cuò)誤:
dtrace: system integrity protection is on, some features will not be available
可以從安全模式關(guān)閉
csrutil disable
舉個(gè)例子,假設(shè)需求是要跟蹤系統(tǒng)malloc方法的所有分配內(nèi)存大小,可以設(shè)計(jì)探針的定義文件DTraceDemo.probe:
provider DTraceDemo {
probe malloc_log(void *ptr, size_t size);
};
然后,執(zhí)行下面的命令生成探針的頭文件,后面帶入測(cè)試工程中編譯
/*
* 更多dtrace用法,參見(jiàn)sudo dtrace --help
*(其實(shí)沒(méi)有help參數(shù),習(xí)慣而已)
*/
sudo dtrace -h -o DTraceDemo.h -s DTraceDemo.probe
測(cè)試代碼如下:
int main(int argc, const char * argv[]) {
@autoreleasepool {
NSInteger count = 0;
BOOL stop = NO;
while ( !stop ) {
// 分配10次,每次10M內(nèi)存
size_t len = 10*1024*1024;
void *ptr = (void *)malloc(len);
memset(ptr, '\0', len);
// 插入探針
DTRACEDEMO_MALLOC_LOG(ptr, len);
if (count > 10) { stop = YES; }
count += 1;
sleep(2);
}
}
return 0;
}
最后,創(chuàng)建自己的腳本vim DTraceDemo.d,文件名必須以 .d 后綴結(jié)尾,這是 D 腳本的約定結(jié)尾。更多d腳本例子參見(jiàn)。
#!/usr/sbin/dtrace -s
#pragma D option quiet
BEGIN
{
trace("Begin trace malloc!\n");
}
DTraceDemo*:::malloc_log
{
printf("malloc ptr:0x%p size:%lld thread:%lu\n", arg0, arg1, tid);
}
END
{
printf("End trace!");
}
使用sudo dtrace -s DTraceDemo.d命令開(kāi)始 dtrace 測(cè)試,接著啟動(dòng)測(cè)試功能得到輸出如下:
Begin trace malloc!
malloc ptr:0x105000000 size:10485760 thread:381125
malloc ptr:0x105a00000 size:10485760 thread:381125
malloc ptr:0x106400000 size:10485760 thread:381125
malloc ptr:0x106e00000 size:10485760 thread:381125
malloc ptr:0x107800000 size:10485760 thread:381125
malloc ptr:0x108200000 size:10485760 thread:381125
malloc ptr:0x108c00000 size:10485760 thread:381125
malloc ptr:0x109600000 size:10485760 thread:381125
malloc ptr:0x10a000000 size:10485760 thread:381125
malloc ptr:0x10aa00000 size:10485760 thread:381125
malloc ptr:0x10b400000 size:10485760 thread:381125
malloc ptr:0x10be00000 size:10485760 thread:381125
^C
End trace!
結(jié)束語(yǔ)
在許多不同的情況下,需要跟蹤應(yīng)用程序。對(duì)于開(kāi)發(fā)人員來(lái)說(shuō),可以通過(guò)跟蹤應(yīng)用程序來(lái)診斷問(wèn)題,這可能比使用調(diào)試器更方便。DTrace 等工具的跟蹤能力更強(qiáng),可以獲得非常有針對(duì)性的豐富的應(yīng)用程序信息。在 iOS 開(kāi)發(fā)中經(jīng)常使用的調(diào)試工具 instruments,其核心數(shù)據(jù)采集原理即 DTrace。
最后附上源碼傳送門(mén)以及Mac OS X系統(tǒng)上的探針圖:
