Instruments 自 2010 年发布以后,一直不温不火,去年也没有任何更新值得去关注。但在一年的沉淀后,Instruments 团队在今年终于有了个能够使人期待的发布。本文是针对 Session 410:Creating Custom Instruments 的解读。express
Instruments 是一款强大且灵活的性能分析工具,集成在 Xcode 的开发者工具集中。咱们可以用不一样的 Instrument 来分析测试各类各样的性能问题,好比 Leaks 来查内存泄漏问题,Time Profiler 来分析 App 的页面卡顿问题等等。那么今年苹果对 Instruments 作了哪些更新呢?从官方的 Xcode 10 Release Notes 里咱们能够看到有这几点:json
其中最重要的一点即是自定义本身的 Instrument 了,虽然以往的版本中也能够根据本身的须要去建立,但步骤麻烦且图形化支持简陋,而且由于数据采集是基于 DTrace 的,致使真机上并不可以使用,苹果自身也并不鼓励你们去作这个自定义。但 Instruments 10 针对这点此次可谓下了苦功——全新基于 os signpost 的架构可以支持全部平台,『标准界面(Standard UI)』 和 『分析核心(Analysis Core)』 使得自定义 Instruments 变得更加灵活并且便利。这个 Session 围绕如何建立一个自定义的组件,分为如下四个大部分展开:swift
其中将会涉及到一些关于专家系统(Expert System)和 CLIPS 语言的知识,若是有兴趣的话,能够先了解下。本文相对于 App 端开发者,可能会更适合一些测试人员,尤为是对性能测试方面比较感兴趣的同窗。缓存
咱们都知道 Instruments 中已经内置不少方便好用的工具了,这些工具苹果已经经过文档模板建立好并集成在 Instruments 的启动选择界面里。好比上文中提到的 Leaks 和 Time Profiler 相信不少人都有使用过,下图的就是 Instruments 10 的启动界面,与之前版本差异不大。 markdown
介绍 Instrument 10 的架构以前,咱们先回顾下以往苹果是怎么维护 Instruments 组件的。 网络
有了足够的理论知识后,咱们能够尝试下简单的自定义工具建立了。 数据结构
.instrpkg
的 XML 文件,全部的配置都会在这个 XML 文件中完成,其中默认已经帮我生成了包含一些基础信息。接着咱们要作的就是按照咱们的须要去写这个配置文档了。
一、导入须要使用的 Schema架构
<!-- MARK: 导入你须要使用的 schema --> <import-schema>tick</import-schema> 复制代码
二、完成 Instrument 的『标准界面』和『分析核心』配置app
<!-- MARK: 导入你须要使用的 schema --> <import-schema>tick</import-schema> <instrument> <!-- MARK: 这个 Instrument 的基本信息 --> <id>com.Parsifal.TicksDemo</id> <title>Ticks</title> <category>Behavior</category> <purpose>Instrument drawing ticks every 100ms</purpose> <icon>Generic</icon> <!-- MARK: 描述的表数据,将会由 分析核心 最终完成存储和解析提供给 标准界面 模块 --> <create-table> <id>tick-table</id> <!-- 定义了每列的数据 --> <schema-ref>tick</schema-ref> </create-table> <!-- MARK: 图形视图上面展现(可选) --> <graph> <title>Ticks</title> <lane> <title>Lane</title> <!-- 这里就是上面你定义的 table id--> <table-ref>tick-table</table-ref> <plot> <value-from>time</value-from> </plot> </lane> </graph> <!-- MARK: 这里描述你须要展现在详情视图的数据 --> <list> <title>Ticks</title> <!-- 这里就是上面你定义的 table id--> <table-ref>tick-table</table-ref> <column>time</column> </list> </instrument> 复制代码
至此咱们便已经完成了全部的编码工做了,在编写过程当中,咱们还会发现苹果为咱们准备了不少代码片断,来帮助完成这些配置,而且编译期间还会对代码进行检查,报出的错误信息也很方便咱们对其进行调试。编译运行后,在弹出的 Instruments 选择窗口里选择 Blank 就能够在测试界面的 Library 中发现咱们本身定义的工具了,直接拖入 Instruments 里就可以像使用其余内置工具同样运行。 ide
这一部分咱们会介绍一些『标准界面』和『分析中心』里更详细的内容。
『标准界面』模块里为咱们提供了不少简单又好用的元素,使用这些元素可以让咱们建立出很是酷炫又实用的 Instruments 工具。接下来列举一些经常使用的元素进行介绍,更多的元素还待苹果正式发布 Instruments 10 后你们一块儿探索。
图形通道面板
详情面板
sum
、average
和 count
等,另外这个元素还有个 hierarchy
属性,可以为不一样的纵列设置外轮廓,很是适合于大量数据的展现;这一部分咱们首先会重点谈谈『分析核心』是如何收集数据和处理数据的,这一过程主要包含如下三个步骤。而后会介绍一些『分析核心』中的相关概念以助于咱们在配置表中使用它们。
一、简化
二、搜索
接着每一个 store 将会开始尝试寻找数据的提供者。
三、优化
当咱们从各个 store 中获取到数据源后,在『分析核心』中就开始了一项称为『Binding Solution』的工做,第三步就是优化这个工做流。
一些重要的概念:
Binding Solution:Instruments 里是经过 Thread Narrative 实现的,它有如下两个有点
Schemas:咱们建立表的时候就必需要指定一个 Schema,好比咱们第一个 Demo 中的 tick
Modelers:前面提到过,Modeler 能够帮助咱们合成不一样的数据,关于它的有如下几个经常使用的元素可在 XML 配置文件中使用。
PS:Modelers 实际上是一个由 CLIPS 编写,很是强大并且高级的小型专家系统(Expert System)。它能够指定本身须要哪些输入信号来告知 Binding Solution 怎么完成剩下数据图形的填充工做。关于这一部分咱们将会在“高级应用”里详细说明。
最后,具有定义一个 schema 的能力是很重要的。今年新发布的 OS signpost API 赋予了咱们一个很棒的把数据导入到 Instruments 中的方式,并且苹果为咱们建立了一些快捷方式来使用它,好比在 XML 配置表中敲下 <os-signpost-interval-schema> 元素,就会自动生成以下代码片断。
<os-signpost-interval-schema>:定义了一个存储定距数据的 schema,并且数据由 os_signpost API 提供。这就意味着咱们能够代码的任意地方使用 os_signpost API 来将咱们须要被测试的数据直接导入到 Instruments 中。在建立这个元素的时候, Xcode 会自动生成相关的代码片断以帮助咱们来完成 Modeler 的建立。 这个 API 和 os_signpost 结合使用起来就以下:
//使用这个 os_signpost 能够再咱们代码的任意地方将你的数据传递给 Instruments,但必须记得 begin 和 end 成对使用 os_signpost(.begin, log: parsingLog, name: "Parsing", "Parsing started SIZE:%ld", data.count) // Decode the JSON we just downloaded let result = try jsonDecoder.decode(Trail.self, from: data) os_signpost(.end, log: parsingLog, name: "Parsing", "Parsing finished") 复制代码
<!-- MARK: 使用这个元素就能建立从 os_signpost 获取数据的 schema --> <os-signpost-interval-schema> <id>json-parse</id> <title>Image Download</title> <subsystem>"com.apple.trailblazer"</subsystem> <category>"Networking"</category> <name>”Parsing"</name> <start-pattern> <!-- MARK: 这里根据咱们设置好的条件输出信息 --> <message>"Parsing started SIZE:" ?data-size</message> </start-pattern> <column> <!-- MARK: Engineering Type 对应的助记词 --> <mnemonic>data-size</mnemonic> <title>JSON Data Size</title> <!-- MARK: 填写的是 Engineering Type 里定义的数据类型,好比这边看的是内存相关的,就会有 size-in-bytes --> <type>size-in-bytes</type> <!-- MARK: 由 CLIPS 语言编写的表达式做为这个列的值 --> <expression>?data-size</expression> </column> </os-signpost-interval-schema> 复制代码
在中级这一部分,苹果还为咱们演示了一个 os_signpost API 使用示例。该示例中对一个展现图片的列表页作了图片测试,监控了每个 cell 图片的下载状况。因为涉及到的知识点上面都已经描述过了,具体示例的完成过程这边就再也不赘述,相信经过视频你们能看得更直观。其中演示过程当中有提到几点比较值得注意的,这里重点抽出来讲明下。
这一部分将会着重介绍咱们怎么去建立和定义 Modelers,而且简单介绍下怎么用 CLIPS 搭建基本的专家系统。
咱们经过一个简单的例子来探秘 Modeler 世界。咱们模拟这样一种状况,个人代码里有部分危险的操做容易触发程序问题,咱们的目标就是在程序出现问题的时候,找到是哪一个操做致使的。那么咱们能够定义下图中的三个 schema,前两个做为输入项,最后一个是输出项。
将这个流程,咱们已一个更加形象直观的时序图来展现,其中虚线表明着 Modeler 本身的时钟:
其中,图上我按照时间顺序,标注除了 4 个值得注意的节点:
(1)这时的 App 出于正常运行状态,没有任何数据传输给 Modeler 的工做内存中,Modeler 的时钟并无开始走;
(2)此时虚线到达咱们的第一个 input schema 触发的节点(开始有危险操做的节点),在这里 Modeler 的工做内存中正式开始接收到数据,Modeler 的时钟从这个点开始计时。
(3)这是第二个 input schema 的触发节点(咱们 App 出现问题的节点)。这里值得一提的是,Modeler 是很机智的,它有本身的逻辑,它能区分出在这个时间节点以前的危险操做数据意义不大,而这个节点开始到 app-on-fire 这个节点结束前的数据才是咱们所须要的。
(4)到这最后一个节点,全部的输入数据都已经传输完毕了,Modeler 的时钟与这些输入数据没有交集, 它推断出这些输入数据已经再也不被须要了,于是把它们从工做内存里移除而且产生最终的输出数据。
能够回顾整个过程,能够总结出两点:
这样一种机制可以帮助咱们更清晰地定位到问题的所在,而不被那些无心义的旧数据干扰。这样一种机制是怎么实现的呢?答案就是咱们接下来要说明的 『生产系统(Production System)』。
Modeler 中对『工做内存(Working Memory)』的逻辑支持是来自咱们定义的 『生产系统』。『生产系统』能够由一系列的 『规则(Rules)』 来生成,它在『工做内存』中为 facts(CLIPS 语言中的专业术语,能够理解为字面意思事实,推理获得的事实) 服务。『规则』由三部分组成—— LHS => RHS
,左边部分 + 操做符(=>)+ 右边部分。左边部分是一个在『工做内存』中激活规则的条件,右边部分则是激活后要执行的行为。右边的行为包含为输出的数据表建立新的一行,也能够是在建模时推断出一个新的『fact』到『工做内存(Working Memory)』里。结合以前上面的时序图,咱们能够想到『facts』 来源于两个地方。一个是咱们看到的数据输入表,这些是根据『规则』自动推理出的。另外一个能够是来自于右边部分的行为,这些是经过 CLIPS 中 assert
命令主动推理的。若是咱们打算建立咱们本身的『facts』,CLIPS 提供了 fact 模板,模板容许为『fact』提供数据结构和作一些基础类型的检查。接下来就是介绍怎么来定义规则了。
咱们可使用 CLIPS 语言定义一些规则。先回顾下上面那个例子的时序咱们是怎么经过 CLIPS 设置规则实现的:
(defrule MODELER::found-cause//规则名
//LHS,左边部分指定规则激活的条件
(playing-with-matches (start-time ?t1) (who ?object))
(app-on-fire (start-time ?t2))
(test (< ?t1 ?t2))
=>
//RHS,右边部分为推理产生一个 fact
(assert (cause-of-fire (who ?object)))
)
(defrule RECORDER::record-cause//规则名
//LHS,左边部分未设定激活的条件
(app-on-fire (start-time ?start))
(cause-of-fire (who ?object))
(table (table-id ?t) (side append))
(table-attribute (table-id ?t) (has schema started-a-fire))
=>
//RHS,右边部分为产生输出数据
(create-row ?t)
(set-column time ?start)
(set-column who ?object)
)
复制代码
其中,record-cause
这条规则定义了,若是知足如下三个条件,则此次生产会推理出一个 『fact』并被压入『工做内存(Working Memory)』里。
playing-with-matches
中产生;app-on-fire
在 t2 这个时间被触发了;而 record-cause
这条规则定义了,若是知足了如下四个条件:
start-a-fire
schema;则在输出数据表中建立一行数据,而且设置时间和设置『左边部分』捕获到的中引发“着火”的值。
经过以上两个简单的规则,咱们便基本上建立了最先的『专家系统(Expert System)』。使用定义好的两条规则,就能够用来寻找咱们 App 内存在的一些问题。或许你也已经注意到,这些规则要么是为 Modeler 预先设置的,要么是为 Recorder。CLIPS 里把他们叫作『模块』,而且支持把规则分组和控制规则的执行顺序。好比说,若是你全部的规则都定义在了记录模块里生产输出数据表,那么你将不会在 Modeler 模块推断的时候写入任何的输出数据。由于在 Modeler 模块 里的规则必须在记录模块里的规则以前执行。
在前面探秘 Modeler 内部世界的时候,咱们提到过逻辑支持。逻辑支持通常与纯推理规则关联在一块儿。好比说,若是 a 和 b,那么 c。经过咱们的『生产系统』中说,就是若是 a 和 b 不存于『工做内存』了,那么 c 就要被自动地被回收。这样咱们就能够说 c 是被 a 和 b 在逻辑上支持了。这样的一种能力对于『专家系统』将『工做内存』维持在较低的水平很重要,由于它能很好地控制资源开销。同时,把无效的 facts 从『工做内存』里及时移除也是很重要的。若是 a 和 b 失效了,那么 c 也应该被移除。这样的需求在 CLIPS 里实现会很简单,只要经过 logical
命令就能够,以下面的代码。
(defrule MODELER::found-cause
//经过 logical 命令实现逻辑支持
(logical (playing-with-matches (start-time ?t1) (who ?object))
(app-on-fire (start-time ?t2))
)
(test (< ?t1 ?t2))
=>
(assert (cause-of-fire (who ?object)))
)
复制代码
前面洋洋洒洒说了那么多,最后这一部分咱们主要谈一下在开发自定义 Instruments 工具时有哪些最佳实践。
这句话不是建议咱们去不断练习写自定义的 Instrument 工具,它说的是咱们应该把 Instrument 功能作得细粒度化。举个例子,咱们已经有了一个自定义的 Instrument 工具,但这个工具的功能并不能知足如今的需求,咱们须要在它的基础上去增删一些详情或者图形的数据展现。这样一种场景,咱们会有两个方式去作,第一个在原有的基础上继续迭代,第二个是从新再写一个知足咱们当前需求的 Instrument。若是选择了第一种方案,这会致使这个 Instrument 组件变得再也不纯粹,虽然功能是更多了,但相应的 Instruments 也变得更加复杂。因此苹果会更推荐咱们用第二种方案,从新写一个符合咱们当前需求的 Instrument。若是咱们须要组合使用不一样的 Instrument,在 Instrument 组件库中拖拽对应的 Instrument 到咱们的文档中便可。若是这类组合使用场景不少,咱们也能够用 “File -> Save As Template” 保存为模板以供接下来使用。保存模板的将会展现在咱们 Instruments 的启动页面,好比内置的 Leaks、Activity Monitor 和 Time Profiler 等同样。另外这些模板,也可以很方便的在咱们的包中复用,使用 <template> 元素便可。
实时模式指的是数据的获取、分析、到最终经过 Instruments 的界面可以实时地被展现出来。苹果如今很难完成这种实时交互主要有两个缘由。第一个缘由是,实时模式的完成须要一些额外的支持,但如今苹果并无足够充裕的时间去作;第二个更重要的缘由即是定距型数据(Interval Data,统计学上的概念,具备间距特征的数据,可做加减计算)。定距变量只有起始和结束这个阶段完成时,才能被加到数据表中和被『分析核心』所获取。在启动测试记录后,Instruments 将会收到一群定距型变量的开区间,当定距变量没完成闭区间时,Modeler 的时钟并不会往前走(Modeler 里的数据是按时间排序的)。这样的机制就会致使了一个问题,若是某个定距变量的开闭区间拉的很长,那么 Modeler 就会一直停滞在那儿等待。但若是这个时候用户点击了中止记录按钮,全部的开区间定距变量就会关闭,一切都会恢复正常,数据将会被填充到 Modeler 里。应该能感受到,这是一个很很差的用户体验。一旦咱们遇到这种状况时,咱们有两个选择。第一个选择是配置咱们的 Instrument 使它不支持这种实时模式,这个能够经过 <limitations> 元素实现。第二个选择避免数据出现这种长时间的开区间,例如使用 <os-signpost-interval-schema> 替代 <interval-schema>。
当建立一个用来测试含有大量输入数据的 Instrument 时,使用『最后 5 秒记录模式』则是咱们目前最好的选择。这一选项咱们能够在 “File -> Recording Options” 里找到。以下图:
Instruments 10 提供了太多建立自定义 Instrument 的可能性了,不过这一样须要咱们花点时间来学习掌握新一套的编写方式。对于大多数客户端开发者来讲,或许并不会用到上面谈的这部分技能,但对于测试团队来讲,这无疑为 iOS App 的性能测试又打开了一扇窗。相信在将来的一年里,圈子内会陆陆续续地有高质量自定义 Instrument 的产出,让咱们一块儿期待。
查看更多 WWDC 18 相关文章请前往 老司机x知识小集xSwiftGG WWDC 18 专题目录