本文将介绍如何在 Open MPI 应用程序中使用 Solaris 动态跟踪(DTrace)工具。DTrace 是一款全面的动态跟踪工具,可用于监控应用程序和操作系统本身的行为。我们可以在实时生产式系统( production system)中使用 DTrace 理解这些系统的行为并跟踪可能发生的任何问题。
D 编程语言用于为 DTrace 程序创建源代码。
阅读本文需具备 D 语言的基本知识并了解如何使用 DTrace。有关 D 语言和 DTrace 的更多信息,请参阅 。这篇指南是 的一部分。
注意:本文所提到的程序和脚本位于下面这个目录:
/opt/SUNWhpc/examples/mpi/dtrace
内容
检查 mpirun
权限
在 mpirun
下使用 DTrace 运行程序之前,需要确定对整个集群拥有正确的 mpirun
权限。所需的权限为 dtrace_proc
和 dtrace_user
。如果没有这些权限,DTrace 将返回以下错误消息:
dtrace: failed to initialize dtrace: DTrace requires additional privileges
|
要确定是否拥有集群的正确权限,请遵循以下步骤:
- 使用喜好的文本编辑器创建下面这个 shell 脚本,名称为
mpppriv.sh
:
#!/bin/sh
# mpppriv.sh - run ppriv under a shell so you can get the privileges
# of the process that mpirun creates
ppriv $$
|
- 键入以下命令,将
host1
和 host2
替换为您的集群中的主机的名称:
mpirun -np 2 --host host1,host2 mpppriv.sh
|
如果 ppriv
输出显示 E 权限集拥有 dtrace
权限,那么您可以在 mpirun
下运行 dtrace
(参见下面的两个示例)。否则,必须调整系统获得 dtrace
访问权限。
以下示例显示了未设置权限时 ppriv
的输出。
%
ppriv $$
4084: -csh
flags =
E: basic
I: basic
P: basic
L: all
|
以下示例显示了设置权限后 ppriv
的输出。
%
ppriv $$
2075: tcsh
flags =
E:basic,dtrace_proc,dtrace_user
I:basic,dtrace_proc,dtrace_user
P:basic,dtrace_proc,dtrace_user
L: all
|
注意:要更新您的权限,可以让系统管理员在 /etc/user_attr
文件中向您的帐号添加 dtrace_user
和 dtrace_proc
权限。
权限修改完成之后,可以使用 ppriv
命令查看修改后的权限。
在 MPI 程序中运行 DTrace
可以使用两种方法在 MPI 程序中使用动态跟踪:
- 直接在 DTrace 下运行 MPI 程序
- 将 DTrace 到正在运行的 MPI 程序
在 DTrace 下运行 MPI 程序
假设有一个名称为 mpiapp
的程序。
要使用 mpitrace.d
脚本跟踪程序,键入以下命令:
% mpirun -np 4 dtrace -s mpitrace.d -c mpiapp
|
使用这种方式跟踪 MPI 程序的好处是从一开始便可以跟踪作业中的所有流程。这种方法可能最适合用于性能测定:需要从头开始启动应用程序并且需要作业中的所有进程都参与收集数据。
同时,这种方法也存在一些缺点。比如说,在上面的示例中,四个进程的所有跟踪输出都将贯注在标准输出上( stdout
)。解决这种问题的方法之一是创建一个类似于以下示例的脚本:
#!/bin/sh
# partrace.sh - a helper script to dtrace Open MPI jobs from the
# start of the job.
dtrace -s $1 -c $2 -o $2.$OMPI_MCA_ns_nds_vpid.trace
|
注意: OMPI_MCA_ns_nds_vpid.trace
变量的状态不稳定并且经常变化。请谨慎使用此变量。
要跟踪并行程序并获取单独的跟踪文件,请遵循以下步骤:
- 创建一个类似于以下内容的 shell 脚本(在本例中的名称为
partrace.sh
):
#!/bin/sh
# partrace.sh - a helper script to dtrace Open MPI jobs from the
# start of the job.
dtrace -s $1 -c $2 -o $2.$OMPI_MCA_ns_nds_vpid.trace
|
- 键入以下命令运行
partrace.sh
shell 脚本:
% mpirun -np 4 partrace.sh mpitrace.d mpiapp
|
这样将使用 mpitrace.d
脚本在 Dtrace 下运行 mpiapp
。脚本会将作业中每个进程的跟踪输出都保存在一个单独的文件中,这将根据程序名称和进程的等级进行。注意,以后的运行将把数据附加在已有跟踪文件中。
将 DTrace 附加到正在运行的 MPI 程序
在 Open MPI 中使用 dtrace
第二种方法是将 dtrace
附加到正在运行的 MPI 程序。
要将 DTrace 附加到正在运行的 MPI 程序,请遵循以下步骤:
- 登录要运行程序的节点。
- 键入类似于以下示例的命令,获取正在节点上运行的程序的进程 ID(PID):
% prstat 0 1 | grep mpiapp
24768 joeuser 526M 3492K sleep 59 0 0:00:08 0.1% mpiapp/1
24770 joeuser 518M 3228K sleep 59 0 0:00:08 0.1% mpiapp/1
|
- 决定要附加
dtrace
的进程等级。
通常,PID 号越低则表示在节点上的等级也越低。
- 键入以下命令附加到等级为 1 的进程(通过进程 ID 标识,在本例中为 24770)并运行 DTrace 脚本
mpitrace.d
:
dtrace -p 24770 -s mpitrace.d
|
简单 MPI 跟踪
DTrace 使我们能够方便地跟踪程序。MPI 标准定义了超过 200 个函数。结合 MPI 使用时,用户可以通过 DTrace 轻易地判断哪些函数可能会在调试过程中出错。确定显示错误的函数之后,可以轻易地找到所需的作业、进程和运行脚本的等级。上面已经演示,DTrace 允许我们在程序运行的过程中执行这些测定。
虽然 MPI 标准提供了一个 MPI 分析接口,但是使用 DTrace 可以提供许多优势。使用 DTrace 的好处包括:
- 每次修改库之后,PMPI 接口都要求重启作业。
- DTrace 允许我们定义探测器(probe),用于捕获与 MPI 有关的跟踪信息,而不需要编写各个函数的详细代码。
- DTrace 脚本语言(D)含有一些内建的函数,可用于帮助调试有问题的程序。
以下示例显示了一个简单的脚本,用于跟踪所有 MPI API 调用中的输入和退出。
mpitrace.d:
pid$target:libmpi:MPI_*:entry
{
printf("Entered %s...", probefunc);
}
pid$target:libmpi:MPI_*:return
{
printf("exiting, return value = %d\n", arg1);
}
|
当您使用这个示例脚本将 DTrace 附加到一个执行 send
和 recv
操作的作业时,输出将类似于以下内容。
%
dtrace -q -p 24770 -s mpitrace.d
Entered MPI_Send...exiting, return value = 0
Entered MPI_Recv...exiting, return value = 0
Entered MPI_Send...exiting, return value = 0
Entered MPI_Recv...exiting, return value = 0
Entered MPI_Send...exiting, return value = 0 ...
|
可以方便地修改 mpitrace.d
脚本,使它包含一个参数列表。结果输出类似于 truss
输出,如下所示。
mpitruss.d:
pid$target:libmpi:MPI_Send:entry,
pid$target:libmpi:MPI_*send:entry,
pid$target:libmpi:MPI_Recv:entry,
pid$target:libmpi:MPI_*recv:entry
{
printf("%s(0x%x, %d, 0x%x, %d, %d, 0x%x)",probefunc, arg0, arg1, arg2, arg3, arg4, arg5);
}
pid$target:libmpi:MPI_Send:return,
pid$target:libmpi:MPI_*send:return,
pid$target:libmpi:MPI_Recv:return,
pid$target:libmpi:MPI_*recv:return
{
printf("\t\t = %d\n", arg1);
}
|
mpitruss.d
脚本演示了如何通过指定通配符匹配各个函数。两个探测器都匹配 MPI 库中的所有发送和接收类型的函数调用。第一个探测器演示了如何使用内建的 arg
变量打印输出被跟踪函数的 arglist。
使用通配符表示输入点和格式化参数输出时需要格外小心,因为可能会为特定的函数打印输出过多参数或者输出的参数不够。比如说,在上面例子中, MPI_Irecv
和 MPI_Isend
函数没有打印输出它们的 Request 处理参数。
下面这个示例展示了 mpitruss.d
脚本的输出。
%
dtrace -q -p 24770 -s mpitruss.d
MPI_Send(0x80470b0, 1, 0x8060f48, 0, 1,0x8060d48) = 0
MPI_Recv(0x80470a8, 1, 0x8060f48, 0, 0, 0x8060d48) = 0
MPI_Send(0x80470b0, 1, 0x8060f48, 0, 1, 0x8060d48) = 0
MPI_Recv(0x80470a8, 1, 0x8060f48, 0, 0, 0x8060d48) = 0 ...
|
跟踪资源泄漏
编程中最大的问题之一便就是意外的资源泄漏,比如说内存泄漏。在 MPI 中,跟踪和修复资源泄漏是比较具有挑战性的一件工作,因为泄漏的对象位于中间件中,并且通过内存检查工具很难检测到它们。
DTrace 可以使用变量、分析提供程序和一个调用栈函数帮助调试此类问题。 mpicommcheck.d
脚本(如上例所示)将探测分配和释放通信程序的所有 MPI 通信程序调用,并跟踪每次调用函数时的栈信息。每隔 10 秒种,脚本会转储 MPI 通信程序调用的当前计数和分配及释放通信程序的总调用数量。当 dtrace
会话结束时(如果附加到运行中的 MPI 程序,通常按 Ctrl-C 结束会话),脚本将打印输出总计数据和各种栈跟踪的数据,以及这些栈栈跟踪的次数。
要执行这些任务,脚本将使用一些 DTrace 特性,比如说变量、关联数据、内建函数( count
, ustack
)和预先定义的变量 probefunc
。
下面这个示例展示了 mpicommcheck.d
脚本。
mpicommcheck.d:
BEGIN
{
allocations = 0;
deallocations = 0;
prcnt = 0;
}
pid$target:libmpi:MPI_Comm_create:entry,
pid$target:libmpi:MPI_Comm_dup:entry,
pid$target:libmpi:MPI_Comm_split:entry
{
++allocations;
@counts[probefunc] = count();
@stacks[ustack()] = count();
}
pid$target:libmpi:MPI_Comm_free:entry
{
++deallocations;
@counts[probefunc] = count();
@stacks[ustack()] = count();
}
profile:::tick-1sec
/++prcnt > 10/
{
printf("=====================================================================");
printa(@counts);
printf("Communicator Allocations = %d \n", allocations);
printf("Communicator Deallocations = %d\n", deallocations);
prcnt = 0;
}
END
{
printf("Communicator Allocations = %d, Communicator Deallocations = %d\n",
allocations, deallocations);
}
|
该脚本将 dtrace
附加到程序代码的可疑部分(更确切地说,就是可能包含资源泄漏的代码部分)。如果在运行脚本的过程中发现输出的分配和释放总计数据开始稳定的下滑,那么代码中可能含有资源泄漏。根据程序设计的不同,可能需要一段时间观察分配和释放的总计数据才能明确地确定代码中含有资源泄漏。当您确定代码确实发生了资源泄漏时,可以使用 Ctrl-C 组合键停止 dtrace
会话。接下来,可以使用已转储的栈跟踪信息尝试确定问题可能发生的位置。
以下示例展示了一段含有资源泄漏的代码,我们将使用 mpicommcheck.d
脚本显示代码的输出。
含有资源泄漏的示例 MPI 程序的名称为 mpicommleak
。该程序将执行 3 个 MPI_Comm_dup 操作和 2 个 MPI_Comm_free 操作。因此,程序每次迭代循环时都“泄漏”了一个通信程序操作。
使用上面的 mpicommcheck.d
脚本将 dtrace
附加到 mpicommleak
时,您将发现程序每隔 10 秒会定期输出一次。输出显示通信程序的分配数量的增长速度快于它的释放数量。
最后,使用 Ctrl-C 组合键结束 dtrace
会话时,会话将输出 5 个栈跟踪的总计数据,分别显示 3 个 MPI_Comm_dup 和 2 个 MPI_Comm_free 调用栈的数据,以及各个调用栈的次数。
下面是示例代码。
% prstat 0 1 | grep mpicommleak
24952 joeuser 518M 3212K sleep 59 0 0:00:01 1.8% mpicommleak/1
24950 joeuser 518M 3212K sleep 59 0 0:00:00 0.2% mpicommleak/1
% dtrace -q -p 24952 -s mpicommcheck.d
=====================================================================
MPI_Comm_free 4
MPI_Comm_dup 6
Communicator Allocations = 6
Communicator Deallocations = 4
=====================================================================
MPI_Comm_free 8
MPI_Comm_dup 12
Communicator Allocations = 12
Communicator Deallocations = 8
=====================================================================
MPI_Comm_free 12
MPI_Comm_dup 18
Communicator Allocations = 18
Communicator Deallocations = 12
^C
Communicator Allocations = 21, Communicator Deallocations = 14
libmpi.so.0.0.0`MPI_Comm_free
mpicommleak`deallocate_comms+0x19
mpicommleak`main+0x6d
mpicommleak`0x805081a
7
libmpi.so.0.0.0`MPI_Comm_free
mpicommleak`deallocate_comms+0x26
mpicommleak`main+0x6d
mpicommleak`0x805081a
7
libmpi.so.0.0.0`MPI_Comm_dup
mpicommleak`allocate_comms+0x1e
mpicommleak`main+0x5b
mpicommleak`0x805081a
7
libmpi.so.0.0.0`MPI_Comm_dup
mpicommleak`allocate_comms+0x30
mpicommleak`main+0x5b
mpicommleak`0x805081a
7
libmpi.so.0.0.0`MPI_Comm_dup
mpicommleak`allocate_comms+0x42
mpicommleak`main+0x5b
mpicommleak`0x805081a
7 以上文章转自于 : http://developers.sun.com.cn/
|