上面两篇讲解了dtrace的基本概念,这次来看下dtrace的进阶应用,在源码中添加自定义的探测器
Solaris(包括 OpenSolaris)、FreeBSD 和 Mac OS X都支持使用标准的 DTrace 探测。这包括在代码中不同函数边界由操作系统实现的那些探测。这些探测称为 Function Boundary Tracing (FBT),可以通过它们探测特定函数的启动或停止。这个功能的局限是,只能使用它探测函数,而不能探测应用程序的功能性片段。如果想探测组成同一操作的多个函数,或者想检查某一函数的片段,FBT 就无能为力了。
对于自己的应用程序,可以使用 User-land Statically Defined Tracing (USDT) 解决这个问题。USDT 让开发人员可以在代码中重要的位置添加特定的探测。还可以使用 USDT 从正在运行的应用程序获取数据,这些数据可作为跟踪应用程序的探测的参数而被访问。
总而言之,标准探测只能告诉你什么时候调用了哪些函数,传了哪些参数,至于函数内部用户自定义了哪些算法,具体在干什么,只能通过在源码中添加USDT来暴露给dtrace
1.创建探测定义文件;其中包含希望插入代码中的每个探测,包括希望通过每个探测共享的参数的定义。这个文件的格式与 C 语言相似。必须指定一个或多个提供者,在每个提供者中指定希望在代码中支持的探测,见provider.d
root@IPS_TEST # cat provider.d
provider ipstest {
/* test */
probe a1__done(char *p1,int p2);
};
provider 是在应用程序中安装探测之后提供者的名称。DTrace 中的探测由提供者、模块、函数和探测名标识:provider:module:function:name。对于 USDT 探测,可以只指定其中的 provider 和 name 部分。
探测的名称取自 probe 关键字后面的字符串。可以用双下划线分隔探测名中的单词。在跟踪期间希望使用探测时,把双下划线改为单一连字符。例如,这个文件中的探测名 a1__done() 可以表示为ipstest::a1-done(提供者和探测名的组合)。
定义中每个探测的参数用来标识 C 代码中参数的数据类型。在跟踪期间用 arg0、arg1、argN 等引用参数。
2.用 dtrace 命令把探测定义转换为头文件, 见provider.h
root@IPS_TEST # dtrace -o provider.h -h -s provider.d
root@IPS_TEST # ls -lt
total 134
-rw-r–r-- 1 root root 567 Sep 18 17:10 provider.h
-rw-r–r-- 1 root root 68 Sep 18 17:10 provider.d
provider.h
/*
* Generated by dtrace(1M).
*/
#ifndef _PROVIDER_H
#define _PROVIDER_H
#include <unistd.h>
#ifdef __cplusplus
extern "C" {
#endif
#if _DTRACE_VERSION
#define IPSTEST_A1_DONE(arg0, arg1) \
__dtrace_ipstest___a1__done(arg0, arg1)
#define IPSTEST_A1_DONE_ENABLED() \
__dtraceenabled_ipstest___a1__done()
extern void __dtrace_ipstest___a1__done(char *, int);
extern int __dtraceenabled_ipstest___a1__done(void);
#else
#define IPSTEST_A1_DONE(arg0, arg1)
#define IPSTEST_A1_DONE_ENABLED() (0)
#endif
#ifdef __cplusplus
}
#endif
#endif /* _PROVIDER_H */
上面的命令指定输出文件名 (-o)、希望生成头文件 (-h) 以及源探测定义文件名 (-s)。
产生的头文件包含宏,可以通过在代码中放置这些宏插入探测。可以在代码中希望触发探测的任何地方使用它们,次数不限。
探测宏的名称取决于您定义的探测名。例如,a1__done 的宏是 IPSTEST_A1_DONE。既然有了探测定义文件(在构建应用程序时还要使用它)和头文件,现在就该在 C 源代码中插入探测了。
3.修改应用主程序源码,添加探测器,见mydtrace.c
#include <stdio.h>
#include <string.h>
#include "provider.h"
void a(char *p1, int p2);
int a_1(char *p1, int p2);
int a_1_1(char *p1, int p2);
void b(char *p1, int p2);
int b_1(char *p1, int p2);
void c(char *p1, int p2);
int main ()
{
while (1)
{
a("dtrace-test-in-a",100);
b("dtrace-test-in-b",200);
c("dtrace-test-in-c",300);
}
}
void a(char *p1, int p2)
{ char str[100];
int rst;
printf("a(%s,%d)\n",p1,p2);
strcpy(str,p1);
strcat(str,"&a_1");
rst=a_1(str,p2+10);
printf("a_1=%d\n",rst);
}
int a_1(char *p1, int p2)
{
int rst;
char str[100];
printf("a_1(%s,%d)\n",p1,p2);
//backup&change p1
strcpy(str,p1);
strcpy(p1,"MyProbe-In-A_1");
p2=p2-1;
IPSTEST_A1_DONE(p1, p2);
//recover p1
strcpy(p1,str);
rst=a_1_1(strcat(p1,"&a_1_1"),p2+1);
printf("a_1_1=%d\n",rst);
sleep(2);
return rst;
}
int a_1_1(char *p1, int p2)
{
int rst;
rst=p2+1;
printf("a_1_1(%s,%d)\n",p1,p2);
return rst;
}
void b(char *p1, int p2)
{ char str[100];
int rst;
strcpy(str,p1);
strcat(str,"&b_1");
printf("b(%s,%d)\n",p1,p2);
rst=b_1(str,p2+10);
printf("b_1=%d\n",rst);
}
int b_1(char *p1, int p2)
{
int rst;
rst=p2+1;
printf("b_1(%s,%d)\n",p1,p2);
sleep(3);
return rst;
}
void c(char *p1, int p2)
{
printf("c(%s,%d)\n",p1,p2);
}
4.把主程序源文件编译为对象文件
root@IPS_TEST # gcc -c mydtrace.c
root@IPS_TEST # ls -lt
-rw-r–r-- 1 root root 2568 Sep 18 17:15 mydtrace.o
-rw-r–r-- 1 root root 1367 Sep 18 17:13 mydtrace.c
-rw-r–r-- 1 root root 567 Sep 18 17:10 provider.h
-rw-r–r-- 1 root root 68 Sep 18 17:10 provider.d
5.链接主程序对象文件,生成探测对象文件
root@IPS_TEST # dtrace -G -s provider.d -o provider.o mydtrace.o
root@IPS_TEST # ls -lt
-rw-r–r-- 1 root root 8672 Sep 18 17:16 provider.o
-rw-r–r-- 1 root root 2568 Sep 18 17:15 mydtrace.o
-rw-r–r-- 1 root root 1367 Sep 18 17:13 mydtrace.c
-rw-r–r-- 1 root root 567 Sep 18 17:10 provider.h
-rw-r–r-- 1 root root 68 Sep 18 17:10 provider.d
6.链接主程序对象文件和探测对象文件,生成最终的模拟守护进程的应用程序mydtraced(这个程序等同以后生成环境debug的实际应用mysqld或oracle进程)
root@IPS_TEST # gcc -o mydtraced mydtrace.o provider.o
root@IPS_TEST # ls -lt
-rwxr-xr-x 1 root root 13832 Sep 18 17:17 mydtraced
-rw-r–r-- 1 root root 8672 Sep 18 17:16 provider.o
-rw-r–r-- 1 root root 2568 Sep 18 17:15 mydtrace.o
-rw-r–r-- 1 root root 1367 Sep 18 17:13 mydtrace.c
-rw-r–r-- 1 root root 567 Sep 18 17:10 provider.h
-rw-r–r-- 1 root root 68 Sep 18 17:10 provider.d
7.编写脚本使用探测,见mydtrace.sh
root@IPS_TEST # cat mydtrace.sh
#!/usr/sbin/dtrace -s
#pragma D option quiet
ipstest*:::a1-done
{
printf("parameters pass into a1_done: %s,%d\n",copyinstr(arg0),arg1);
}
8.运行编译好的模拟守护进程mydtraced
root@IPS_TEST # ./mydtraced
a(dtrace-test-in-a,100)
a_1(dtrace-test-in-a&a_1,110)
a_1_1(dtrace-test-in-a&a_1&a_1_1,110)
a_1_1=111
a_1=111
b(dtrace-test-in-b,200)
b_1(dtrace-test-in-b&b_1,210)
b_1=211
c(dtrace-test-in-c,300)
a(dtrace-test-in-a,100)
a_1(dtrace-test-in-a&a_1,110)
9.运行探测脚本,查看输出结果
root@IPS_TEST # ./mydtrace.sh
parameters pass into a1_done: MyProbe-In-A_1,109
parameters pass into a1_done: MyProbe-In-A_1,109
^C