DTrace

DTrace 是 Sun Microsystems 创建的一种动态追踪工具,被苹果集成在 OS X 中。它允许开发者在不重启应用的情况下,对生产环境进行调试。DTrace 由探针和脚本组成,探针可以零成本地添加到代码中,而脚本则用于分析数据。本文介绍了如何在 OS X 和 iOS 中使用 DTrace,以及如何定义和使用静态探针,强调了其在生产环境中的优势和使用场景。
摘要由CSDN通过智能技术生成

很少有人听过 DTrace,它是隐藏在 OS 中的小宝藏。DTrace 是强大的 debug 工具 - 因为它拥有极其灵活的特性,并且因为与其它工具差异很大而可能相对不那么有名。

许多时候你的 app 的真正的用户或测试人员会看到一些意外的行为。DTrace 可以让你无需重启 app 就能够在生产版本上回答关于 app 的任何问题。

动态追踪

大概 10 年前,Sun Microsystems 创建了 DTrace,它的名字是 Dynamic Trace 的缩写。2007 年底,苹果公司将它集成在自己的 操作系统 中。

DTrace 是一个提供了 zero disable cost 的动态追踪框架,也就是说当代码中的探针关闭时,不会有额外的资源消耗 - 即使在生产版本中我们也可以将探针留在代码中。只有使用的时候才产生消耗。

DTrace 是动态的,也就是说我们可以将它附加在一个已经在运行的程序上,也可以不打断程序将它剥离。不需要重新编译或启动。

本文我们将重点介绍如何使用 DTrace 检查我们的程序,但值得注意的是 DTrace 是系统级的: 例如,一个单独的脚本可以观察到系统中所有进程的内存分配操作。可以查看 /usr/share/examples/DTTk 来深入了解一些非常好的例子。

OS X vs. iOS

正如你现在可能已经猜到的,DTrace 只能在 OS X 上运行。苹果也在 iOS 上使用 DTrace,用以支持像 Instruments 这样的工具,但对于第三方开发者,DTrace 只能运行于 OS X 或 iOS 模拟器。

在 Wire,即使我们被限制仅能在 iOS 模拟器上使用 DTrace,它也在 iOS 开发中非常有用。如果你读到本文并且认为在 iOS 设备上支持 DTrace 是个好提议,请提交 enhancement request 给苹果。


探针和脚本

DTrace 有两部分:DTrace 探针,及附加在上面的 DTrace 脚本。

探针

你可以将内置 (所谓静态的) 探针加入代码中。IA 探针看起来和普通的 C 函数非常相似。在 Wire,我们的同步代码有一个内部状态机器,我们定义了如下两个探针:

provider syncengine_sync {
    probe strategy_go_to_state(int);
}

探针被分组成所谓的 providers。参数 int 是正要进入的状态。在我们的 Objective-C (或 Swift) 代码中,简单的插入以下代码即可:

- (void)goToState:(ZMSyncState *)state
{
    [self.currentState didLeaveState];
    self.currentState = state;
    SYNCENGINE_SYNC_STRATEGY_GO_TO_STATE(state.identifier);
    [self.currentState didEnterState];
}

我们后面会讨论如何整合并且把流程说清楚一些。

脚本

现在我们可以编写一个 DTrace 小脚本来展示状态转变:

syncengine_sync*:::strategy_go_to_state
{
    printf("Transitioning to state %d\n", arg0);
}

(后面我们会详细展示 DTrace 脚本如何工作。)

如果将 DTrace 保存进 state.d,接下来我们可以使用 dtrace(1) 命令行工具 来运行它:

% sudo dtrace -q -s state.d

我们可以看到:

Transitioning to state 1
Transitioning to state 2
Transitioning to state 5

正如我们所预期的,并没什么让人激动的。最后使用 ^C 可以退出 DTrace。

一个定时例子

因为 DTrace 消耗非常小,所以非常适合用来测试性能 - 即使需要测试的时间非常短。DTrace 中的时间单位是纳秒。

如果扩展上面的小例子,我们可以输出每个状态所花费的时间:

uint64_t last_state;
uint64_t last_state_timestamp;

dtrace:::BEGIN
{
    syncState[4] = "EventProcessing";
    syncState[5] = "QuickSync1";
    syncState[6] = "QuickSync2";
}

syncengine_sync*:::strategy_go_to_state
/ last_state_timestamp != 0 /
{
    t = (walltimestamp - last_state_timestamp) / 1000000;
    printf("Spent %d ms in state %s\n", t, syncState[last_state]);
}

syncengine_sync*:::strategy_go_to_state
{
    printf("Transitioning to state %s\n", syncState[arg0]);
    last_state = arg0;
    last_state_timestamp = walltimestamp;
}

这些代码会输出:

Transitioning to state QuickSync1
Spent 2205 ms in state QuickSync1
Transitioning to state QuickSync2
Spent 115 ms in state QuickSync2
Transitioning to state EventProcessing

脚本中有些新东西。dtrace:::BEGIN 语句在脚本开始时运行。脚本退出时有一个相应的 END

我们还给第一个探针增加了一个断言 (predicate),/ last_state_timestamp != 0 /

最后我们使用全局变量来追踪最后的状态,以及什么时候进入该状态。

内置的 walltimestamp 变量返回当前时间相对于 Unix epoch 时间以来的纳秒数。

还有一个虚拟的单位为纳秒的时间戳变量,vtimestamp。它表示当前的线程在 CPU 上运行的时间减去在 DTrace 上花费的时间。最后,machtimestamp 对应 mach_absolute_time()

对于上面的脚本,执行的顺序非常重要。我们有两个所谓的语句对应同一个探针,(syncengine_sync*:::strategy_go_to_state)。它们会按照在 D 程序中出现的顺序执行。

结合系统探针

操作系统,尤其是 kernel,提供了数以千计的探针,被分成不同的提供者 (provider) 组。其中的很多在 Oracle 的 DTrace 文档中可以找到。

通过下面的脚本,我们可以用 ip 提供者中的 send 探针来检查转换到下一个状态之前通过网络发送了多少字节:

uint64_t bytes_sent;

syncengine_sync$target:::strategy_go_to_state
{
    printf("Transitioning to state %d\n", arg0);
    printf("Sent %d bytes in previous state\n", bytes_sent);
    bytes_sent = 0;
}

ip:::send
/ pid == $target /
{
    bytes_sent += args[2]->ip_plength;
}

这次我们的目标为某个特定的进程 - ip:::send 会匹配系统的所有进程,而我们只对 Wire 进程感兴趣。我们运行如下的脚本:

sudo dtrace -q -s sample-timing-3.d -p 198

这里 198 是进程标识 (亦称 PID)。我们可以在活动监视器这个 app 中找到这个数字,或者使用 ps(1) 命令行工具

我们会得到:

Transitioning to state 6
Sent 2043 bytes in previous state
Transitioning to state 4
Sent 581 bytes in previous state
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值