AIX 从 1990 年就开始提供 Trace 功能
- 在 AIX 内核中预先构建了挂钩(代码),并且这些挂钩大部分用于捕获内核函数的状态和参数
- 专为生产使用而设计,但需要注意,其收集信息量过多,可能会降低性能
AIX 6 ProbeVue——包含在当前 AIX 6 Open Beta 版本中,但有功能限制
- 需要时也可动态添加用户代码
- 零代码修改
- 一旦启用,将触发与 C 语言类似的 Vue
- 专为生产使用而设计,而且几乎没有性能影响
- 不会转储所有信息
- 使用脚本确定真正需要的内容或查找错误条件
可以采用交互方式运行 probevue(对我而言,这工作似乎有点艰难,因为必须键入和重复键入细节!),也可以通过脚本运行 probevue。至少可以采用五种方法,可能开始会让人有些迷惑,那么接下来我们就对各个方法进行说明,以便您决定尝试哪种方法:
- 1) 和 2) 使用脚本启动 probevue,并将脚本指定为输入参数,或将脚本重定向到命令中,如下所示(这是两种方法):
probevue myscript.e probevue <myscript.e
- 3) 自动启动脚本,并在第一行使用特殊的代码告知 shell 启动 probevue 来处理文件内容
脚本(在本例中称为 myscript)包括第一行: #!/usr/bin/probevue 将文件处理为可执行文件:chmod +x myscript 然后运行脚本:./myscript.e
- 4) 有些脚本需要参数(如 shell 脚本 $1、$2 等)。具体来说,很多需要进程 ID (PID)
查找进程 ID:# ps -ef | grep find 使用 PID 参数运行: probevue myscript 43561
- 5) 直接通过用户程序使用 probevue
probevue -X progname -A prog-arguments myscript
probevuew 脚本的结构如下(<-- 是注释,即不属于脚本的一部分)
<- optional declaring function prot-type so probevue understands your functionarguments
@@BEGIN
{
... <- optional start up code, good for initialisation of variables
}
@@<probe-spec> <- Probe point specification Tuple (see below)
when <predicate> <- Optional predicate
{
statements here;<- Probe actions in C like code
...
}
@@END
{
... <- Optional ending code. Can print out final
results. Gets runs if you hit Control+C
}
|
首先请注意,Tuple 是有序列表的雅名——我在知道这一点后帮助非常大!
目前有三种类型的 Tuple:
- 1) 针对用户编写的代码的入口或出口的用户函数跟踪探针(或 uft)
- 语法: @@uft:PID:*:FunctionName:[entry/exit]
- 示例:针对函数 foo()(主要可执行文件或库)的入口的探针,PID = 34568
- @@uft:34568:*:foo:entry
- 注意“*”是用于下一版本的占位符
- 2) 系统调用入口/出口探针(或 syscall)
- 语法: @@syscall:PID:SystemCallName:[entry/exit]
- 示例:读取系统调用所有进程的出口的探针
- @@syscall:*:read:exit
- 3) 间隔探针(以指定的时间间隔触发)
- 语法: @@interval:*:clock:milliseconds
- 示例:每 500 毫秒(墙上时钟时间)触发一次的探针
- @@interval:*:clock:500
以下是一个非常简单的脚本,此脚本实际上工作并执行一些有意义的工作。
#!/usr/bin/probevue <-- autorun
@@BEGIN
{
int count, total; <-- declare the variables (later probevue
versions will not need this)
}
@@syscall:*:read:entry <-- System call: any process ID, read() function,
at start of the function
{
count++; <-- increment the counters
total++;
}
@@interval:*:clock:1000 <-- once per second (1000 ticks per second)
{
printf("Number of reads = %d\n", count);
count=0;
}
@@END <-- After you type Control-C
{
printf("\nTotal reads = %d\n",total);
}
|
以下是一些示例输出:
# ./count.e Number of reads = 0 Number of reads = 1 Number of reads = 0 Number of reads = 0 Number of reads = 2 Number of reads = 0 Number of reads = 5 Number of reads = 269 Number of reads = 0 Number of reads = 0 ^C Total reads = 277 |
#!/usr/bin/probevue
@@BEGIN
{
int read(int fd, void *buf, int n); <-- Declare function so ProbeVue understands it
int bad;
int good;
}
@@syscall:*:read:exit
when(__rv == -1) <-- __rv means the return value
{
bad++;
}
@@syscall:*:read:exit
when(__rv != -1)
{
good++;
}
@@interval:*:clock:1000 <-- Once a second output counter
{
printf("Reads good=%d bad=%d\n",good,bad);
good=0;
}
|
以下是上面脚本的一些示例输出,我专门运行了一个程序来生成一些磁盘 I/O。注意:当程序尝试从无效的文件描述符(即未使用 open() 函数初始化文件描述符)进行读取时,将生成不良 I/O:
# ./goodandbad.e Reads good=0 bad=0 Reads good=55 bad=0 Reads good=0 bad=0 Reads good=0 bad=0 Reads good=57 bad=1 Reads good=0 bad=1 Reads good=0 bad=1 Reads good=55 bad=1 Reads good=0 bad=1 Reads good=1 bad=1 Reads good=55 bad=1 Reads good=40 bad=1 Reads good=1 bad=2 Reads good=55 bad=3 Reads good=1 bad=3 Reads good=0 bad=3 Reads good=56 bad=4 |
我使用一个小程序生成了很多 CUP 工作。此程序调用了 ncpu,其中包含函数“engine()”,此函数耗尽了 CPU 上的 CPU 时钟周期。以下脚本用于研究此函数的使用情况。注意:此程序已经编译并在运行,并且它包括符号表,probevue 使用此符号表来查找函数入口和出口点(即未剥离)。probevue 脚本包括用于输出所能找到的程序和环境的详细信息的 probevue 函数:
#!/usr/bin/probevue
double engine(int p1, int p2);
@@uft:$1:*:engine:entry
{
printf("PID=%d TID=%d PPID=%d PGID=%d UID=%d GID=%d InKernel=%d\n", __pid,
__tid, __ppid, __pgid, __uid, __euid, __kernelmode);
printf("ProgName=%s errno=%d\n", __pname, __errno);
printf("---\n");
stktrace(GET_USER_TRACE,-1);
printf("+++\n");
stktrace(PRINT_SYMBOLS|GET_USER_TRACE,-1);
}
|
以下是用于运行此脚本的命令及其输出:
# ps -ef | grep ncpu
root 299106 307250 27 14:02:00 pts/0 15:16 ./ncpu -p1 -z 90
root 315596 307250 0 16:48:47 pts/0 0:00 grep ncpu
# ./engine 299106
PID=299106 TID=630919 PPID=307250 PGID=299106 UID=0 GID=0 InKernel=0
ProgName=ncpu errno=0
---
0x100001dc
0x10000964
0x100010e8
0xd02fd66c
0x20000c80
0x10001160
+++
.__start+0x8c
.main+0x60c
.work+0x1b4
.random+0x2c
|
接下来让我们研究一下名为 engine() 的函数。该函数接受两个参数,第一个是随机生成的 1 到 1,000,000 之间的数字——但我们不确定具体是不是在此范围内。我们将随机数放入四个“桶”中。我们要确定各个桶中的随机数的数量是否大体相等——不是完全相等,但至少相近:
#!/usr/bin/probevue
double engine(int p1, int p2);
int b1, b2, b3, b4;
@@uft:$1:*:engine:entry
{
tmp = __arg1;
if( tmp < 250000 ) b1++;
if( 250000 <= tmp && tmp < 500000 ) b2++;
if( 500000 <= tmp && tmp < 750000 ) b3++;
if( tmp >750000 ) b4++;
}
@@END
{
printf("\nFour bucket results are: %d %d %d %d\n",b1,b2,b3,b4);
}
|
以下是上述脚本的对应命令和输出:
ps -ef | grep ncpu
root 299106 307250 27 14:02:00 pts/0 15:16 ./ncpu -p1 -z 90
root 315596 307250 0 16:48:47 pts/0 0:00 grep ncpu
# # ./random 299106
[[[Wait 30 seconds here]]]
^C
Four bucket results are: 101524 101262 101454 120657
|
噢,最后一个桶里中的数量更多一些,但这不是 probevue 的问题!这是我代码中的一个错误。我后来通过一个更为详细的 probevue 脚本发现,有些值大于 1,000,000!Probevue 将实际值添加到“生产”工作负载,而没有中断应用程序,也不对程序进行重新编码/重新编译/重新启动。我想不出其他能够做到这一点的方法。
我并没有讨论更为高级的功能,如:
- 试验性捕获——缓存结果,并稍后在找到要研究的点后提交/丢弃数字
- 从用户程序读取数据
- 线程处理
- 用于更改缺省行为的 probevctrl 命令
第一个 AIX6 版本有以下方面值得我们注意:
- 内核变量仅限于内存中驻留的变量
- 用户 C 代码函数入口需要符号表
- 没有 C 预处理器支持
- WPAR 限制——使用 probevue 时,您的 WPAR 不具有移动性(不能重新加载)。停止 probevue 后,WPAR 就可再次移动了。另外, syscall tuple 必须具有 PID,可从 WPAR 查看 PID(不允许使用“*”)
- 不支持线程局部字符串
相关专题
- 基于UNIX文化 自由软件用户9个共同特点 (0次浏览)
- IBM和Sun起争议 坚持不同的“开源”观点 (0次浏览)



