构建自定义Xcode Instruments软件包

在AppSpector,我们经常使用集成的工具(当然是我们自己的代码,包括核心部分-移动SDK)来调试客户端应用程序。 我们一直使用适用于特定平台的各种工具来加快调试速度,并使开发过程更容易且容易出错。 因此,当我们发现WWDC 2018大会410“创建自定义工具”时,我们迫不及待地想要使用新的API进行构建。

如果您只有锤子,那么一切看起来都像钉子。 值得构建自定义Instruments软件包的问题不久就出现了。 AppSpector iOS SDK的核心部分是一个消息传递模块,简而言之,它负责iOS和我们的后端之间的双向流量交换。 SDK发送大量事件,描述主机应用程序的行为和各种操作,还接收来自后端的请求。 流量足够大,需要压缩,因此我们从一开始就实现了它,现在所有消息都在SDK和后端进行了压缩/解压缩。

在某个时候,我们决定将压缩算法从LZ4更改为基于字典的zstd,并可能对我们的数据类型提供更好的压缩。 众所周知,测量任何变化的能力对于安全有效地将它们引入至关重要,因此我们决定创建一个内置机制来跟踪压缩率和性能。 当然,定制乐器是最佳选择。

潜在的工具如何提供帮助

实际上,通过简单的日志记录就可以轻松实现几乎相同的目标,但是我们想要的以及适用于Instruments的是数据采样和可视化。 想象一下,一百个消息中的单个消息遇到压缩问题,其压缩率非常低,您需要执行某些操作(例如建立蓝牙连接)才能在真实设备上触发它。 在日志中搜索此消息可能会很痛苦。 即使您使用某种服务从设备收集和保存日志消息,并且消息具有预先计算的压缩率,也不是完全可以的。 但是就像在WWDC会话上的那个家伙一样看交互式图形,能够保存收集的数据并以后比较两次运行,建立自定义选择规则以识别和聚合具有某些属性的消息-看起来很棒。


从技术上讲,Instruments是最强大的调试平台之一DTrace的前端。 它是从Sun Solaris进入OSX的,详细描述它可以占用整本书(实际上是这样:https://www.amazon.com/DTrace-Dynamic-Tracing-Solaris-FreeBSD/dp/0132091518)。 DTrace使用以D语言编写的特殊跟踪程序(称为探针)进行操作。

在Apple推出Instruments软件包之前,您可以通过编写自己的D脚本作为数据提供程序并将其包装在Instrument模板中来构建自定义乐器。 我们将使用os_signpost API向我们的仪器提供数据。

项目设置

为了方便起见,我们将构建一个示例项目来说明一个简单的Instruments软件包。 除了在示例项目中用于模拟压缩和解压缩消息大小的随机生成的事件值之外,我们的压缩配置文件工具的工作方式相同。 我们建议克隆示例项目并在阅读时参考它。

Xcode提供了一个新的目标类型,称为“ Instruments Package”,位于“ MacOS”->“ Other”部分下。 目标模板没有任何作用,只是添加了一个扩展名为.instrpkg的文件,它实际上是一个描述我们的仪器布局和功能的XML文件。 构建软件包的大部分工作只是用原始XML编写。 让我们希望我们能够尽快得到一些特定于任务的编辑器,例如InterfaceBuilder或CoreData模型编辑器。

仪器架构

我们将构建的工具将使用os_signpost调用作为数据提供者。 该API允许定义事件的开始和结束,还可以传递一些格式化的字符串以及开始和结束调用。 就像使用“ scanf()”一样,通过根据字符串的格式解析该字符串来获取在Instruments端的事件数据。

我们将在Swift中使用os_signpost(),如果您使用Objective-C编写代码,则应使用os_signpost_interval_begin / os_signpost_interval_end调用。 要开始使用这些API,我们需要实例化“ log”实例,因为os_signpost使用os_logging子系统发送消息和唯一的路标标识符:

 静态let log = OSLog(子系统:“ com.package”,类别:“行为”)static let signpostID = OSSignpostID(log:AppDelegate.log,对象:self作为AnyObject) 

请注意,子系统和类别参数已传递给os_log初始化程序。 这些值将在以后的包装说明中用于匹配我们的路标调用。 我们必须在感兴趣的事件开始之前调用“开始”,并在其结束时通过传递先前创建的log和signpost_id实例作为参数来调用“结束”:

  os_signpost(.begin,日志:AppDelegate.log,名称:“ event-tracking”,signpostID:AppDelegate.signpostID,“ size:%llu”,之前)//您在此处的操作... os_signpost(.end,日志:AppDelegate .log,名称:“事件跟踪”,signpostID:AppDelegate.signpostID,“ size:%llu”,后) 

实际上,这就是我们应用程序代码中所需要的,我们可以继续进行“乐器”部分。

模式结构

仪器软件包文件是一个长XML文件,我们将其分为三个部分:软件包元数据,架构和实现。 让我们从上到下浏览示例项目的instrpkg文件。 编写大量的XML是困难的,但是Xcode团队为包结构的主要部分添加了一堆模板,因此,当您开始键入一些标签时,几乎总是会收到带有基本子标签的模板建议。 在“ package”标签之后,您可以看到带有“ id”,“ title”和“ owner”信息的元数据部分。 Instruments应用程序将使用此信息来识别和安装您的软件包。 当您选择使用时,“所有者”值将显示在包装信息中。 您还可以在此处提供“版本”和“注释”标签。

模式描述

下一部分是我们的架构描述。 本节说明仪器将要运行的数据,并以“ id”和“ title”标签开头。 “ id”是一个唯一值,我们稍后将使用它从Instrument UI描述中引用我们的架构,“ title”是在Instruments UI中的图形视图附近显示的值。 接下来,我们有用于标识路标调用的“子系统”,“类别”和“名称”标记,它们必须与我们在应用程序代码中用于创建os_log子系统句柄的标记匹配。 接下来的标签“ start-pattern”和“ end-pattern”定义了解析消息的格式。 此格式将应用于我们传递给开始/结束调用的字符串参数。 以问号“ size-before”和“ size-after”开头的字符串是保存我们从应用程序端传递的数据的变量的名称。 这是我们遇到CLIPS表达式的第一个地方。 CLIPS是Apple在Instruments中用于数据处理的一种语言,即使其简要概述也应另作文章。 如果您有兴趣,可以导航到CLIPS参考以获取有关语言本身的更多信息。 在模式的末尾,我们有一堆“ column”标签,它们描述了可用于我们包的数据列。 我们为每个字段使用4个字段:“助记符”是唯一的列引用,“标题”和“类型”非常不言自明(类型的完整列表可在Instruments文档中找到),“ expression”标记包含CLIPS表达式,结果将用作列值。 对于前两列,我们使用在开始模式/结束模式部分中接收到的输入数据,对第三列使用简单的速率计算。 最后两列将包含字符串值,第一个包含高/低值,第二个包含颜色名称。 这些值将根据费率值进行计算,稍后我们将在构建Instrument UI时使用它们。

仪器说明

本部分介绍了仪器的UI组件以及如何填充数据。 我们在开始时有一些元数据,然后是图,表和聚合部分。 元数据包括与我们用于创建os_log实例的子系统值相匹配的软件包ID,用于在仪器用户界面中进行引用的“标题”,“类别”和“用途”字段,以及用于为您的图标选择图标的“图标”仪器。

如果您以前使用过Instruments,则可能记得几乎所有的Instrument UI都分为两个主要部分:顶部为图形,底部为数据表。 标签“ graph”和“ list”用于描述这两个部分。 首先,要定义一个表,我们需要使用引用我们架构ID的’create-table’标签来创建它。 这将使我们能够使用上述列中的数据。 之后,我们描述UI的一个图形部分,该图形部分由一个车道组成。 在这一点上,我们参考表并说明应使用哪些列进行绘图。 列表标记描述了引用表ID的UI底部,就像前面的图元素和要显示的列表列一样。 并不是说我们在这里跳过了’rate-color’列,因为我们仅将其用于为图形着色,并且将其包括在表中没有任何意义。

实际上,该软件包已经差不多完成了,但是对于我们的压缩性能分析,我们希望有一个单独的数据视图来显示压缩率低于0.15%的消息。 由于很难在所有消息中找到它们,因此我们决定使用聚合标签来描述在Instrument UI的表格部分可用的单独视图。 汇总部分包括“ slice”标签,该标签告诉仪器在“ rate-status”列中仅显示具有“ High”值的数据,并显示要为每个条目显示的列的列表。 我们决定只显示压缩前后的大小。

跑步包

要运行程序包并使用产生路标事件的应用程序对其进行测试,您需要先构建应用程序,然后使用“ My Mac”目标构建程序包方案。 构建完成后,Xcode将为您运行Instruments的特殊实例。 该实例包含您的嵌入式包。 它的图标是灰色的,以区别于普通运行的仪器。 启动仪器后,将显示一个模板选择窗口。 选择“空白”,然后按新窗口右上角的“加号”图标。 您将看到可用软件包的列表以及其中的软件包。 它将用“ debug”徽章标记,表明它是从Xcode运行的。 将其拖动到“仪器”窗口或双击以添加,在左上角选择设备和应用,然后按“记录”按钮。

如果要与同事共享新建的软件包,则只​​需Xcode中“产品”文件夹下的.instrdst文件。 运行它会显示一个带有软件包信息和“安装”按钮的小窗口。 安装它,然后您便会在Instruments软件包列表中找到它。


调试的大部分时间都是要寻找问题的根源,而不是解决问题,因为与查看日志,尝试重现问题以及所有其他探索性活动相比,解决问题的时间通常要少得多。 选择合适的工具是最重要的部分。 当然,您可以采用旧方法,例如仅通过查看内存转储或记录对象生存期来检测泄漏,但是使用泄漏仪器,它将更快,更轻松。 仪器可让您选择特定的角度来观察您的应用程序或其部分,有时,这就是识别问题所需要的。 这个工具已经很棒,但是Xcode团队设法通过允许开发人员构建自定义工具来使其更加强大。

制作自定义乐器既简单又有趣,您只需要花费一个小时左右的时间即可节省更多时间。 当然,拥有更多的文档和一些软件包文件编辑器会很棒,但是即使在当前状态下,它也可能非常有用,特别是对于中型和大型项目。


  • WWDC 2018,第410节“创建自定义工具”,有关此主题的主要资源
  • John Sundell关于os_signpost API的文章,路标API的简要介绍及其在Instruments中的集成

感谢Kacper Harasim,他非常友善地回答了问题,并在理解一切工作原理方面提供了很多帮助。


AppSpector是适用于iOS和Android应用程序的远程调试和自检工具。 使用AppSpector,您可以调试在同一个房间或另一个大陆上运行的应用程序。 您可以实时评估应用程序性能,查看CoreData和SQLite内容,日志,网络请求等。 就像您一样,我们多年来一直在努力寻找愚蠢的错误并梦想着拥有更好的本地工具,最后我们决定构建它们。 这是您一直在寻找的乐器。