大多数浏览器和
Developer App 均支持流媒体播放。
-
创建自定 Instrument
了解自定 instrument 的用处以及何时应该使用自定 instrument。深入了解自定 instrument 的架构以及如何创建自定 instrument。了解优秀 instrument 应该具备哪些属性。深入了解高级建模以及如何使用 CLIPS 语言。
资源
相关视频
WWDC19
WWDC18
-
下载
上午好 我是 Chad Woolf 我是一名 Apple 的 性能工具工程师 欢迎各位参加今天的 410 号讨论会 我们将讨论 如何在 Instruments 10 中 创建自定义 Instrument 今天的讨论会是这样的 我们将讨论一下 为什么要创建 自定义 Instrument 我们将回顾一下 Instruments 的结构 我们今天有很多内容 所以讨论将会分为 三个部分 入门 中级和进阶部分 在那之后 我们会讨论一些最佳实践 以及一些在我们 编写我们自己的 Instrument 时 所学到的东西 首先 为什么要创建自定义 Instrument Instruments 已经集成了很多 非常强大的工具 例如这里我们有系统跟踪 你可以看到 App 如何与调度器 和虚拟内存交互
今年我们有一个新的 游戏性能模板 它结合了 系统跟踪和 Metal 系统跟踪 来帮助你发现 App 中的 故障和丢失的帧 在 App 的网络部分 我们也有网络连接 Instrument 它可以显示进出 App 的 TCP/IP 流量 当然还有 很多人都熟悉的时间分析器 时间分析器可以帮助你 查看 App 在何处花费时间 无论是网络层 游戏引擎还是其他部分 普遍来说 如果你了解 你正在编辑的代码 这些都是非常有用的 如果你知道这些 IP 地址 是什么意思 知道不同的函数 是什么意思 以及时间分析器的调用堆栈 它会让事情变得更简单 然而 如果有人打算分析你的 App 但他们不熟悉其中的代码 那该怎么办呢 要是他们只是想知道 App 是否在网络层上 花费了大量的时间 如果是 它在做什么 自定义 Instrument 的一个 很好的用途就是 尝试以一种 使原本无法理解代码的人 能够理解和欣赏代码的方式 讲述你的某一层或 App 正在做什么
在进阶部分中 我们将向你展示如何 利用在 Instruments 内部构建的 专家系统技术 以便你可以创建一个 即使你不亲力亲为 也能够查找错误模式 并在代码中 发现反面模式的 Instrument
好的 让我们来看看
要做到这一点 我们必须从这里开始 回到原始版本 起初的 Instruments 的 工作原理和今天的差不多 同样有一个库 你仍然可以拖出 Instrument 并将它们放入跟踪文档中 然后按下记录按钮 多个性能工具就可以同时运行 那时和现在的 Instruments 之间的 主要区别是 当时的 Instruments 基础结构 并无法帮助我们 快速编写 Instrument 在当时这样还好 因为我们已经 继承了一些我们已有的 素材和性能工具 它们都有自己的 记录技术和分析逻辑 我们所要做的就是建立一个 自定义的存储机制 来获取跟踪中的数据 以及一个自定义的 UI 来帮助它 与 App 的其他部分集成 随着时间的推移 Instruments 的维护成本 和模型的维护成本都上升了 原因就是 每次我们想要 添加新特性时 我们必须修改 7 个自定义 UI 和 7 个自定义存储机制 但这不是我们想让你们 学习的处理方式 我们不想让你们 承担这种维护成本 所以在我们讨论 定制 Instrument 的特性之前 我们首先需要解决这个问题 我认为我们做到了 在新版本的 Instruments 中 我们没有自定义 UI 和自定义存储机制 而是有两个 标准化的组件 它们是 “Standard UI(标准 UI)”和 “Analysis Core(分析核心)” 标准 UI 实现了新版 Instruments 的 整个用户界面 它与分析核心 紧密相连 而分析核心 你可以将其视为 数据库和专家系统的结合 这两种都针对 时间序列数据进行了优化 使得它们成为构建 Instrument 的重要基础 当你使用新型架构 构建一个 Instrument 时 实际上你所做的是 创建标准 UI 和分析核心 的自定义配置
如果你看一下 我一开始展示的那些强大的 Instrument 的屏幕快照 我们有系统跟踪 游戏性能模板 网络连接模板 以及时间分析器 所有这些文档中的所有 Instrument 都是完全基于标准 UI 和分析核心生成的 所以你也同样可以做 它们能做的事情 在 Xcode 10 和 Instruments 10 中 我们提供了相同的工具 来构建你的 Instrument Xcode 集成的 Instrument 和你生成的 Instrument 之间的 唯一区别就是 谁生成了它 你的 Instrument 会显示在我们的库中 你可以看到顶部的活动监视器 就像这样 你可以将你的 Instrument 拖放到 跟踪文档中并进行记录 接下来 Instruments 将用数据 填充分析核心 标准 UI 将创建图形 和表格视图 Instruments 有两种 显示数据的方式 在顶部有一个图形化的视图 我们称之为轨道视图 一个 Instrument 可以根据需要 定义多个轨道
如果你想 在定义 Instrument 的图之间 进行选择 在 Instrument 图标上 有一个小控件 你可以把它 从 “CPU” 改为 “Network(网络)”
每个图都被允许 定义一定数量的轨道 这里我们定义了 3 个轨道 并绘制了 3 种不同类型的 CPU 利用情况 这里的每一个轨道 都被绑定到分析核心中的 不同的表上 或者它可以被绑定到同一个表上 但是你看到的是 这个表中的另一列
这个 Instrument 的另一部分 就在下方 它同样也很重要 它叫做细节视图 在这里你可以看到 事件列表 以及数据的任何类型的 聚合和总结 就像这些轨道 抱歉 就像这些图一样 你可以为你的 Instrument 定义一些细节 你也可以通过点按跳转栏的这个部分 然后选择你定义的细节标题 来选择哪个细节是活跃的
就像图形视图中的轨道一样 所有的细节都被 绑定到分析核心的一个表上 这就是它们 接收数据的地方 记录开始 表格被填入 UI 就会做出反应 不需要你执行任何特殊代码 从标准 UI 的角度来看 分析核心中的所有内容 似乎都是一个表格 我们来谈谈表格
表格是行的集合 它们有一个 由表格 Schema 定义的结构 所以它和数据库 App 非常相似 这个 Schema 定义了 列 列的名称 以及列的类型 分析核心使用了一个 非常丰富的类型系统 叫做工程类型 它告诉我们如何存储数据 以及如何在标准 UI 中 可视化和分析数据
除此之外 当 Schema 描述 表的结构时 你还可以使用 Key/value 这对属性 来描述内容 这能帮助我们描述表中的内容 你可以将 Schema 视为 Objective-C 或 Swift 中的一个类 而行则类似于实例 就像 Objective-C 中的 类名是单数的一样 你的 Schema 的名称 也必须是单数的 比如 NSString 而不是 NSStrings 当我们讲到进阶部分的时候 这一点会更为重要 但是我现在想把它强调一下 到时我们就能知道 我们在讨论什么了 Schema 的一个例子是 Tick 它是 Instruments 内部的一种 Schema 用于创建 合成时钟节拍表 稍后我们将在建模器中 使用它进行统计计算 Tick Schema 很简单 它只有一个列要被定义 那就是 time time 使用的工程类型是 sample-time Tick Schema 还定义了 一个可以附加到表格实例中的可选属性 它叫做 frequency 如果你为 Tick Schema 创建一个 频率为 10 的表格 那么数据的提供者 就会知道它需要 每秒填充 10 个时间戳 这样你就可以表达 你想要填入表的内容
现在 我认为 我们有足够的信息 来开始入门部分 我们将展示 如何在 Xcode 中 创建你自己的 Instruments 包项目 还将向你展示 如何创建你的第一个 Instrument 来绘制这些 Tick 并在细节视图中 显示这些 Tick 为此我想请 我的同事 Kacper 给大家做一个展示 谢谢大家 现在我将向你们展示 如何开始创建和运行 你们的第一个自定义 Instrument
你们将会使用 由 Chad 之前展示的 Tick Schema 并在频率恒定的节拍中 制作一个 Instrument 你们将会学到如何描述你们的包 如何使用 Xcode 对其进行迭代 以及如何在 Instruments 中进行测试
让我们开始吧
和在 Xcode 中 以前的操作一样 创建新的 Instruments 包项目 你需要点击 “New Xcode Project” 选择 “macOS” 作为平台 并点击 “Instruments Package”
你需要填写文件名称 这将成为这个 Instrument 包的默认名称 我们就命名为 “Ticks” 吧 点击 “Next(下一步)”和“Create(创建)”
Xcode 已经创建了 带有包 Target 的项目和一个文件 里面有对包的定义 让我们来看看 包是基于 XML 语法描述的 首先 每个包最前面有 标识符 标题和所有者 当有人试图安装你的包时 这些栏将是可见的
通常 你可以从 定义自己的 Schema 和可选的建模器开始 但是因为这里我们将使用 预定义的 Tick Schema 我们将删除这些指示
要从基础包中导入 Tick Schema 你只需指定 import-schema 元素 和 Schema 的名称 “tick”
现在它可以被我们的 Instrument 使用了
为了让你们更容易地定义 更复杂的元素 我们在 Xcode 中配置了一些代码片段 要使用它们 只需写入元素名称 比如 “instrument” 然后按回车 你需要填写 你的 Instrument 的唯一标识符
以及稍后出现在 Instrument 库中的一些属性
这里是 “Instrument drawing ticks every 10ms”
现在是创建表格的时候了 当这个 Instrument 从库中 被拖到跟踪文档时 这个表格将被实例化
在这个 Instrument 的定义中 表格标识符必须是唯一的 我们叫它 “tick-table” 在 中 我们需要引用以前 导入的 Schema“tick” 现在我们需要定义 在轨道视图和细节视图中 会出现的内容 我将使用 graph 元素 我们需要为我们的图形 填写 title 我会命名为 “Ticks” 轨道的标题也一样 我需要按照前面创建的 标识符来引用表格 因此我将引用 “tick-table”
现在我们要为我们的图 指定绘制
我会使用 plot 元素 在最基本的形式中 它只需要你传递 包含要绘制的值的 列的助记符 我们将绘制 “time”
我希望所有的时间戳 都能在表格中显示出来 为此 我将使用 list 元素
我们需要为一个 将会出现在 Instrument 中的列表 填写 title 元素 “table-ref” 就像之前的 lane 元素一样 以及我们想要看到的 column
现在我们的包已经准备好 在 Instruments 中构建并运行了
为此 你需要使用 “Xcode Scheme Run” 操作 我们就这样做吧 你可以看到出现了构建错误 在生成 Instruments 包时 你拥有完整的 IDE 支持 这里 出现了一行错误 告诉我们在 Tick Schema 中 找不到 “timestamp” 这一列 没错 因为它不是 “timestamp” 而应该是 “time” 我将修复这个问题并再次运行它 你可以看到它在运行 因为这个新的 Instrument 副本出现了 你可以通过不同的图标 识别特殊的副本
它仅为这个运行会话 加载你的包 它允许你更容易地 对包进行迭代 为了确保你的包已经加载 我们可以在 新建包管理的 UI 中检查它 你可以在 “Instruments”- “Preferences(偏好设置)” -“Packages(包)”标签页中找到它 你可以在这里看到 我们新创建的包以及调试的下标 这意味着它已经被 临时加载 你也可以在这里看到 所有的系统包 你可以通过使用副标题 使用并链接它们 在这里 我们的 Ticks 包里包含了 Ticks Instrument 现在让我们用空白模板测试它 我将把 Target 切换到我的 MacBook
然后在 Instruments 库中 搜索我的 Instrument 我将填入 “Ticks” 然后它将显示在这里 包含从包定义中 填充的所有属性 让我们将它拖放到跟踪中
并记录一秒钟
你可以看到底部的面板 是以每 10 毫秒生成的数据 进行传递的
细节和图形是相互协调的 当我点击行时 你可以看到检查线在这里移动
我还可以通过 Option 点按并拖动 来而放大一个图 在这里 你可以看到 Ticks 确实被画出来了
这样你就可以创建 第一个 Instrument 包 现在让我们请回 Chad 他会告诉你更多 关于标准 UI 的内容 好的 谢谢 Kacper 我们已经知道了 如何创建一个非常基本的 Instrument 也了解了如何开始 在 Xcode 中创建你的第一个项目 现在让我们来谈谈 我们拥有的不同类型的图 不同类型的细节 以及如何利用真实数据进行操作 让我们从图形轨道开始
你已经看到了 Kacper 是如何使用我们称之为 plot 的元素 来定义一个图形和轨道的 plot 元素可以 指示标准 UI 获取表的全部内容 并尝试在特定的 轨道中绘制它 plot 元素 通过查看 Schema 和获取值的目标列 来决定如何绘制这个内容 以及如何处理图形 如果是一个区间 Schema 这意味着它有一个时间点 和一个持续时间 如果是一个点 Schema 这意味着它只是一个时间戳 我们需要用不同的方式处理 如果目标列有一个长度 这意味着可以 通过它画一个条形图 就像这样 另一种选择是 生命周期轨道 它仍然是一个区间 Schema 但是我们针对的是 一个状态列 而状态本身并没有长度 所以在这里画条形图 是没有意义的 标准 UI 会自动选择 状态风格的处理方法 包括用圆角矩形样式的标签 绘制这些区间 这样你就可以 把它与平面条形图区分开来 标准 UI 能够为你选择 这些处理的功能 是非常重要的 因为这可以保持 Instruments UI 的一致性 如果你定义一个状态图的同时 我们也定义一个状态图 标准 UI 就会强制它们 看起来一样 这样 Instruments 用户就可以 更容易在 Instrument 之间 进行切换 如果你想 基于数据的内容 动态地创建图形或轨道数目 你可以定义所谓的 “Plot Template” Plot 模板的定义 与 Plot 非常类似 除了其中有一个额外的元素 允许你选择 表中的一列 它将为该列中的每个唯一值 创建单独的行
如果你正在寻找 活动的峰值或周期 我们有所谓的直方图 你所要做的就是 在不同的点 或区间相交时 打破超过某个特定大小的 存储器中的时间轴 假设 100 毫秒 然后使用像 count() sum() min() 或 max() 这样的函数 来提升这些存储器的大小 这是一种寻找活动峰值的好方法 例如 在系统跟踪中 我们在环境切换 或虚拟内存中 寻找活动峰值 现在我们来谈谈细节 细节在这个 UI 的下半部分
你们已经看过了第一个 也就是列表 它是表格 分析核心 以及 UI 中表视图之间的 非常简单的映射 还有 “Aggregation(聚合)” 当你想要 去除时间分量 概览你的数据 而且想要在表格中应用一些 统计数据的时候 聚合是很适合的选择 当我们定义一个聚合 我们需要注意 列现在是函数 你可以使用 sum() average() count() 以及其他的一些统计函数 来帮助你创建 你想要创建的聚合视图
聚合的好处在于 你也可以定义 一个层级结构 这里我们在 虚拟内存操作结构中 定义了一个进程线程 我们可以看到它被分解为 进程 然后是 进程中的每个线程 然后是这个线程中的 这个进程中的 每个类型的操作 所以聚合是一种很好 很强大的方式 来总结很多数据
另一种类型的聚合 称为 “Call Tree(调用树)”
当你有一个列是回溯 而且另一个列是 权重的时候 调用树就会很有用处 你可以使用调用树 创建加权回溯 或加权调用的树视图 就像在时间分析器中看到的那样
另一种样式叫做 “Narrative(叙事)” 当你想要传达 只有技术语言的信息 比如专家系统的输出 以及和叙事工程类型 紧密相连的信息时 叙事是常用的选择
最后一种细节类型 叫做 “Time Slice(时间片)” 时间片看起来 很像一个列表 但是其中的内容被过滤 只包含与图形中蓝色线 相交的区间 这条蓝色线叫做检查线 当你把检查线 移到图形上时 列表的内容将被过滤 从而与检查线 相交的部分匹配
所有这些 UI 都与 分析核心中的表格绑定 当你开始记录时 数据进入 Instruments App 并填入分析核心中 让我们详细谈谈 这个过程是如何进行的
在开始记录之前的第一步 分析核心将提取 在其中创建的表格 它将映射表格 并在核心中为表分配存储 如果一个表有 相同的 Schema 和相同的属性 那么根据定义 它就是相同的数据 所以它会被映射到相同的存储中
对于每个存储 第二步就是尝试为数据 找到一个提供者 有时 我们可以从 从 Target 中通过数据流 直接记录数据 有时 我们则必须使用 建模器来合成数据
建模器可以请求自己的输入 这些输入可以 是其他建模器的输出 或者直接从数据流中记录 我们就是这样合成 我们本来不知道如何直接记录的 其余数据的
现在我们已经得到了 分析核心中 所有存储的数据源 也就是所谓的绑定方案 第三步是优化绑定方案 在这里 你可以看到 Instruments 将自己的 绑定方案可视化 我们称之为线程叙事
关于绑定方案的 下一部分是 它是 “Trace-wide(广跟踪)”的 当你将 Instrument 拖放到跟踪中时 Instruments 将计算 尽可能最好的记录方案 以尽量减少 对 Target 的记录影响 当你创建自己的表格 或表实例时 你必须给它们一个 Schema Instruments 已经定义了 超过 100 种 Schema 所有这些 Schema 都可以使用 并存在于在包管理的 UI 中 所看到的包里面 你只需将 Schema 导入到 自己的包中 如果该 Schema 包含在 一个不是基础包的包中 你需要在 Xcode 中的 构建设置里面 将包设置为 “Linked Instruments Packages” 这样我们就可以在生成时 找到你涉及的额外的包 并做一些类型检查
因为所有这些 Schema 都是在其他包中定义的 当你开始记录时 所有具有这些 Schema 的表格 都会被填充 因为要么它们定义了建模器 要么我们知道如何 从数据流中记录它们 这些都是你的 Instrument 的 出色的构件 但它们更是 编写建模器的 优秀输入 现在你已经可以编写一个建模器 或者在你的 Instrument 包中 用 modeler 元素 定义一个建模器 你还可以为该建模器 创建一个自定义输出 Schema 你可以只对一个时间点 使用 point-schema 或者如果你有一个时间点和一个区间 你可以使用 interval-schema 建模器可以定义 它需要的输入 这就告诉了绑定方案 如何填充 数据流图的其余部分 这样你的建模器 将融合到绑定方案中 建模器实际上是 微型专家系统 它们是用 CLIPS 语言编写的 这意味着 它们非常强大 同时非常先进 关于如何创建 建模器的细节 我们将留在进阶部分讨论 然而 能够定义自己的 Schema 是非常重要的 我们今年有了一个新的 os_signpost API 它是把数据 导入 Instruments 的绝佳方式 我们为它创造了一条捷径
在你的包中 你可以定义所谓的 os-signpost-interval-schema 它既可以定义 Schema 也可以为我们提供足够的指令 以便能够代替你 生成一个建模器 在那里你可以 捕获在 os_signpost 调用的 元数据中 记录的数据 你可以使用捕获的元数据 和表达式来定义 如何填充你的 Schema 的列
我们来看一个非常简单的例子 假设我们要做 JSON 解码 我们有一个 os_signpost 标记了解码 Activity 的开始 和解码 Activity 的结束 在开始时 我们还需捕获一些元数据 以指示我们将要解析的 JSON 对象的大小
在你的 Instrument 包定义中 你可以创建一个 os-signpost-interval-schema 并在这里定义你的 Schema 名称 你可以选择 要记录的 signpost 包括 signpost 名称 然后在这里可以使用语法 从起始元数据消息中 捕获不同的 元数据片段 这里 我们将使用 这个捕获的值 我们将用它作为表达式 来教我们如何 填充我们刚才定义的 数据大小的列
在 405 号讨论会中 即 “Measuring Performance Using Logging” 我演示了《Trailblazer》App 还展示了一个 Instrument 你们可以根据这个 Instrument 里面的 signpost 来进行编写 现在我们对如何编写 自定义 Instrument 有了更多的了解 我想邀请 Kacper 回到台上 来给大家演示一下 我们是如何创建这个包的 谢谢你 Chad
《Trailblazer》 App 是一款 iOS App 它可以显示你附近 流行的徒步旅行路线 作为 UI 组件 它使用了 UITabelView
每个单元异步加载 路线的图像
为了防止出现故障且作为优化 当单元被重复使用时 我们取消了下载
为了可视化我的下载流 我将每个下载都置入到 os_signpost 调用中 让我们看一下
当我的保存的单元显示时 调用 startImageDownload() 方法 我们创建了 downloader 和 signpost ID 它包含了 os 日志句柄 和 downloader 对象 然后我们获取 UI 表格视图 单元的地址 并调用 os_signpost(.begin) 它自于 signpostlog.networking 让我们看一下 这个日志以 App 的 标识符为子系统 networking 为类别
我们传入 “Background Image” 这个名称 之前创建的 “signpostID” 以及信息格式 其中包含 “Image name”
在这里 我们将它放置在全局说明符中 因为它是一个字符串 还有 “Caller” 它是一个单元的地址
我们的下载可以 通过两种方法完成 我们现在来看看 当下载以这样的方式完成 就会调用委托方法
我们像之前一样创建 signpostID 并调用 os_signpost(.end)
这次我们传入 “Status” 和 “Size”
“Status” 值是 “Completed”
“Size” 设置为图像大小 接下来让我们看一下 我们为重写所做的准备 当 downloader 正在运行时 我们会取消它
我们创建了 signpostID 并使用相同的格式字符串 调用我们的 os_signpost(.end) 但是这里 值是 “Canceled” 并且 “Size” 为 “0” 因为下载没有成功 让我们来看看 我们的 os-signpost-interval-schema 定义 以及我们如何在包中 捕获这些 signpost
我们定义了具有唯一 id 和 title 的 os-signpost-interval-schema 然后我们定义 subsystem 和 category 它与我们在创建 日志句柄时 传递的 category 相对应
我们创建 name 元素 它与我们在 os_signpost 调用 start-pattern 和 end-pattern 中 传递的 name 相对应 这两个都对应于 我们在 os_signpost 中传入的 开始和结束调用
message 元素与 传递的格式字符串相同 但是在调用 os_signpost 时 为了捕获传入的值 你传递的将是这里的变量 而不是格式化参数 让我们看看如何 在列中填入这些值
在这里 你可以看到 status 列 它是字符串类型 因为它只可能是 “Completed” 或者 “Canceled”
所以我们用状态变量的值 来填充它
因为 expression 元素可以采用 任意的 CLIPS 表达式 所以我们可以在其中 做更复杂的事情 在这里我们可以通过查看图像大小 来计算事件影响 如果它大于 3.5MB 我们可以判定影响很大 否则操作的影响很小
这就是我们对 os-signpost-interval-schema 所做的定义 现在让我们来看看表格的创建
对于 schema-ref 我们传入 os-signpost-interval-schema 的标识符 并为这个特定表格 创建唯一标识符
然后 我们可以在 UI 定义中引用它
对于 graph 我们创建一个单轨道
它利用我们的表 这次它通过使用 Plot 模板来绘制图形 Plot 模板 是创建图形的动态方法 它查看在实例中 按元素传递的列 并为该列的每个唯一值创建 Plot label-format 元素 允许我们为这个 Plot 创建格式标题 这里是 img 列和 image-name 列中的值 我们传入 image-name 作为我们的 Plot 的值
我们的每个轨道 都会以 impact 列上色 我们轨道上的标签 会从 image-size 中提取出来
接下来 我们看看 list 你已经在 Ticks 的例子中 见过它
这里 我们将传入 你希望看到的所有列 接下来是 aggregation 这个 aggregation 将跟踪 所有已完成的下载 因为我们的表 包含已完成和已取消的下载 所以我们需要应用 slice 元素来过滤一些数据
在 slice 元素中 我们可以指定 应用于 slice 的列 以及需要匹配的谓词值 在这里 我们只想从这个表中 取出 “Completed” 的行
我们定义了 hierarchy 它是只有一个层级的结构 具有 image-name 和可见的列 对于每个 image-name 我们将指定 count 和 image-size 所以我们要将图像的大小求和 接下来我们有 time-slice
抱歉 我们指定所有 将会可见的列
为了更容易地使用我们的 Instrument 我们可以指定我们的 自定义模板 我们现在尝试构建和运行 我们的包
你可以看到这里的模板 我可以选中它
Target 是我的 iPhone 和《Trailblazer》App
我需要记录一会
可以看到 跟踪视图是以数据传递的
每一个图像名称都创建了 一个绘图
你可以看到标签格式 与我们在包定义中 传入的格式相匹配
如果下载 高于 3.5 MB 那么我们的轨道将是红色的
具体的大小可以在轨道上查看 接下来我们看看 所有的细节
首先 我们看一下下载列表 这是刚才发生的 所有下载的列表 我们可以选择聚合 按照图像名称 划分所有下载 你可以在上面看到 我们下载了 12 张图像 “location7” 的图像 被下载了两次 接下来 我们可以查看活动请求 你可以在这里看到 当我拖动我的检查线时
细节视图中的数据发生了变化 我们可以跟踪 多个活动请求 并查看截至当前检查线为止的 持续时间
如果你想从不同的角度 查看你的数据 并想查看你的存储和建模器 我们通过 Instrument 检查器 为你提供了这个功能 它是调试自定义 Instrument 的 一种方法 在这里你可以看到 我选择了存储步骤 并看到创建 os-signpost 的存储 它属于网络类别 和 com.apple.trailblazer 子系统 我们在这里收集了 24 行
然后我们可以看到 创建的表格 image-download 它有 12 行
在底部的区域 你可以看到该表的整个内容
接下来 我们跳转到建模器 我们可以看到这里有 GENERATED-OS-LOG 建模器 它使用了 24 行 输出了 12 行
在右边 你可以看到绑定方案 所以我们的 GENERATED-OS-LOG 建模器 从 os-signpost 表格中获取数据 并将其放入图像下载表格中
然后被我们的 Instrument 使用 我们就是这样 捕获 os-signpost 调用 创建 UI 并使用 Instrument 检查器 查看数据 现在让我们请回 Chad 他会告诉你们更多 关于进阶建模的内容
好的 谢谢 Kacper
现在我们已经了解了 如何将 os-signpost 数据 与自定义 Instrument 结合起来 我们认为你们 能把这个结合做得很好 现在 我们可以讨论一些 进阶的内容 特别是如何创建 和定义建模器
建模器在概念上是 非常简单的机器 它需要一系列的输入 它对这些输入做出推理 然后进行输出 建模器的输入 是完全按照时间排序的 因此如果你请求 几个不同的输入表 这些表将首先按时间排序 然后合并到一个 按时间排序的队列中 该队列将提供工作内存 当我们把这些事件 一个接一个地取出时 它们被输入到所谓的 建模器的工作内存中 当建模器看到 这个工作内存的增长时 它就可以得出推论 当建模器看到需要为其 进行输出的模板时 它只需将其 写入对外输出表格 让我们以一个 非常有趣的例子 来介绍如何使用建模器 假设你定义了一个 Schema 叫做 playing-with-matches 它是一个 os-signpost-interval-schema 可以使已经定义的 os_signpost 在你的代码中 做一些危险的操作 我们又定义了另一个 Schema 叫做 app-on-fire 它也是一个 signpost Schema 但是这些 signpost 意味着 App 进入了一个糟糕的状态 我们真的很想知道原因
还要创建一个输出 Schema 它是一个 point-schema 它会保存 playing-with-matches 的对象 以及对象“起火”的时间 我们打算将它 命名为 started-a-fire 建模器看起来是这样的 所有的输入 都按时间顺序设置好了 左边的虚线就是 所谓的建模器时钟
当我们获取第一个输入 将其置入到工作内存中时 建模器时钟 将移动到区间的起始处 然后我们获取下一个输入 建模器时钟 再次移动到区间的起始处 我们将其置入到工作内存中 建模器观察 工作内存的这两个区间 建模器可以观察 playing-with-matches 是否在 app-on-fire 之前开始 这其实没有多大区别 如果反过来 app-on-fire 也已经开始了 我们就可以得出一个合乎逻辑的结论
叫做 cause-of-fire 并将它置入工作内存中
当我们获取第三个输入时 你会注意到 建模器时钟已经移动 它不再与前两个输入相交 所以它们将被从工作内存中移除 如果 cause-of-fire 有 所谓的逻辑支持 它也会被从内存中移除 回顾一下 时钟总是被设置为 当前输入的时间戳 对于留在工作内存中的 输入而言 它必须与建模器中的 当前时钟相交 这可以帮助我们建立重合 它能让我们删除旧数据 也能让我们了解 是否存在 可能与时间相关的输入 建模器对其 工作内存的解释 是通过所谓的 “Production System” 来定义的
生产系统处理 工作内存中的 “Facts(事实)” 它们由具有左手边 (LHS) 一个生产操作符 和右手边 (RHS) 的规则定义 LHS 是工作内存 中的一种模板 它能够激活规则 而 RHS 则是规则触发时 发生的动作 这些操作可以包括 向输出表中添加一行 或者在建模过程进行时 将一个新的事实 添加到工作内存中
事实有两个来源 首先 它们可以来自 你看到过的表格输入 通过使用我在建模时钟中 展示的规则 表输入将被判定为事实 事实也可以通过 来自生产的 RHS 的 判定生成
如果你要创建你自己的事实 CLIPS 允许你 找到所谓的“事实模板” 你可以为你的事实 提供结构 并做一些基本的类型检查 让我们来看看 CLIPS 中的 一些规则 我们要讲的第一个规则 叫做 found-cause
它指的是 如果有一个对象 它的 playing-with-matches 在 t1 时开始 app-on-fire 在 t2 时开始 且 t1 比 t2 提前 那么在这个生产的 RHS 中 我们可以判定一个新的事实 叫做 cause-of-fire 以及导致“起火”的对象 它将进入到 工作内存中
我们接下来讨论第二个规则 也就是 record-cause 如果我们有一个 App 在某个开始时间“起火” 并且我们知道“起火”的原因 我们有一个绑定到 side append 的表格 它是建模器的输出端 而且这个表是我们定义的 叫做 started-a-fire 的 Schema 我们可以创建一个表格的行 然后设置时间 以及导致 模板中所得的值 “起火”的 Schema 通过这两个规则 我们基本上创建了 第一个专家系统来查找 App 中的不良模式
现在你可能已经注意到 规则是由 MODELER:: 或 RECORDER:: 预先编写的 这些都是 CLIPS 中的模块 它们允许你 同时把规则分组 并控制规则的执行顺序 举个例子 如果你保持了所有的 为 RECORDER:: 模块中的输出表格 生产输出的规则
你就可以确保 你不会在建模器的 推理过程中进行输出 因为在 MODELER:: 中的所有规则 必须在 RECORDER:: 中的规则 执行之前执行 我之前提到过的 逻辑支持 逻辑支持通常与 所谓的对等推理规则 联系在一起 这些规则就是 比如说 如果 A 和 B 那么 C 向生产中 添加逻辑支持 意思就是 如果 A 和 B 不再 处于工作内存中 那么 C 应该被自动收回 所以我们说 C 受 A 和 B 的存在的 逻辑支持 这很重要 因为它限制了工作内存膨胀 这有助于资源消耗 但从工作内存中 删除不再有效的事实 同样也很重要 如果 A 和 B 不再有效 那么你应该移除 C 为了向你的生产 添加逻辑支持 这里是相关的规则 你只需将关键字 logical 填入模板 那么在向前移动的过程中 规则中的 RHS 所判定的任何内容 都将被自动收回 你们应该注意到了 来自我们的 Schema 的 这两个规则 抱歉 这两个事实 它们都是输入 所以当建模器时钟 向前移动时 这些将自动被收回
好的 现在我们已经了解了 如何在包中创建 建模器的基本内容 并且看到了一些 CLIPS 语言和规则 让我们来看看 是否可以在我们的网络 Instrument 中 添加一个专家系统 以查找我们的网络层中的 不良模式和潜在的误用 为此 让我们邀请 Kacper 上台 做最后一个演示
有了现有的日志记录 我将尝试编写建模器 来检测 App 的 网络行为中的 一些反面模式 我在运行我的 《Trailblazer》 App 如果我滚动得很快 这里就会出现 一些小故障 图像被多次替换 所以我怀疑我们的取消 并没有真正起作用
我想编写建模器来检测它
让我们来看看 我们的包定义 我们将从编写 modeler 元素开始 modeler 有 id title 和 purpose 几个栏 这些栏将被 提取到文档中
我们为 modeler 指定了 包含所有逻辑的 production system 的 path
然后 我们定义了 modeler 的 output 它将是一个 downloader-narrative Schema 我们的建模器的 required-input 将是 os-signpost 表格 这个表包含开始 和结束事件
现在让我们看一下 downloader-narrative Schema 的定义
它是一个 point-schema 定义了两个列 timestamp 跟踪记录诊断消息的时间
description 含有 运行错误的信息
然后 我们可以在 Instrument 定义中 创建这个表 我们传入 downloader-narrative 的 schema-ref 和唯一的 id
然后我们可以在 narrative 元素定义中使用它
在这里我们定义 narrative 我们为之前创建的表格 传入 table-ref 定义 time-column
和 narrative-column 我们已经准备好 为我们的建模器定义逻辑
为此 我将创建 之前在 modeler 定义中 引用的文件 为了创建 CLIPS 文件 前往 “File(文件)”-“New(新建)”
选择 “macOS” 作为平台 “Other(其他)”部分的 “CLIPS File(CLIPS 文件)”
我把名称填好并创建 下面展示的将是 检测一个单元 是否同时执行 多个请求的算法 我们将在工作内存中 追踪每一个作为事实的请求 首先 我们需要 为这个事实创建模板
每个事实都会有 存储 time caller-address 也就是单元地址 我们捕获到的 signpost-id 以及我们请求的 image-name 我们将这个事实叫做 started-download
然后编写 在工作内存中 创建此事件的建模器规则
这个规则查看 os-signpost 表格 我们指定 subsystem name 并把 “Begin” 作为 even-type 的值 并捕获我们想要的所有信息 我们需要捕获 ?image-name ?caller-address time 以及 ?identifier 然后 我们向工作内存判定新的事实
要在下载完成后清理它 我们需要从工作内存中 收回这一事实
这里我们查看的是同一个表格 但是我们只查看 event-type “End”
我们捕获 signpost 的 identifier 在这里我们使用的事实 signpost “Begin” 和 “End” 必须有相同的标识符
我们在工作内存中 寻找一个具有我们捕获到的 signpost identifier 的事实 并收回这个事实
然后我们可以编写 RECORDER:: 规则 来生成所有的叙事数据
这个 RECORDER:: 规则 查看所有 started-download 事实 并捕获它们 我们捕获 time caller-address 以及 image-name
如果这里是 true 有另一个具有相同 caller-address 的 started-download 事实 你就可以注意到 这里引用的变量是相同的 并且在第一个事实之前发生 我们注意到 存在一些反向模式 而且在请求中存在重叠
我们可以检查 是否可以访问 downloader-narrative Schema 在其中创建新的行 将列的时间设置为第一个事实的时间 并设置列的描述 你需要输出 关于这个问题的一些信息 以便之后有人可以调试它 现在我可以在 App 上运行 Instruments 让我们再次运行它
再次选择《Trailblazer》网络模板并记录 我将尝试执行一些 快速滚动并查看我的叙事表
你可以看到 叙事表包含大量 正在输出的诊断消息 我们可以看到 存在一些问题 之后再研究它们
你可以看到 叙事表是可互动的细节视图 例如 你可以检查 所有传入的参数 并进行筛选 我们可以添加这个调用者地址 成为一个细节过滤器 并拥有这个细节过滤器
现在 让我们请回 Chad 他将告诉你更多 关于开发 Instruments 的最佳实践经验
好的 谢谢 Kacper 我们已经看到如何在 Instruments 中 创建一些基本的专家系统 好的 我们来谈谈 在这个过程中 我们学到的一些最佳实践经验 第一个是 编写多个 Instrument 我的意思并不是 练习编写 Instrument 而是 如果你 已经拥有了一个 Instrument 并且想给它添加一些特性 有时候 给你的 Instrument 添加额外的图形或细节 真的很容易 但是你应该 真正地思考 这样的话 它还可以成为自身的 Instrument 吗 这样想的原因是 如果你创建更细粒度的 Instrument 你会给这个 Instrument 的用户 更多的选择 他们可以从库中 拖出他们想要的 Instrument 这将最小化 对目标的记录影响 如果你把注意力集中在一个 有很多功能的 Instrument 上 那对用户来说 将是一个全盘接受 与被迫放弃之间的选择 如果你想创建 针对某个问题 的一组 Instrument 你一定想要看到 所有这些 Instrument 同时被使用 那么你所能做的就是 像我们创建网络模板那样 创建自己的自定义模板 所以你要做的应该是 创建一个文档 以你想要的方式拖拽 Instrument 配置它们 进入文件 然后保存为模板 然后你就可以在你的包中 使用那个模板 同时使用 Kacper 在我们的网络模板中添加的元素 使用多种 Instrument 编写 是使用工具更好的方式
第二 即时模式很难 即时模式指的是 Instruments 的记录模式 它可以在接近实时的时候 将数据可视化 有两个原因导致它很难 第一个原因是 它需要一些额外的支持 尽管今天我们很想向你们介绍这点 但是很遗憾没有办法 时间有限 所以我们将会 为此编写文档 第二个原因 也是更重要的原因 就是区间数据 区间不能被输入到 分析核心的表中 除非它们被关闭 这意味着这时我们已经 看到了开始和结束 所以当你查看 现场记录的时候 你会发现一堆所谓的“开区间” 如果你的建模器 需要它们作为输入 这是完全可行的 而且你会注意到 如果上游有一个开区间 那么下游的所有建模器时钟 都必须停止 直到那个区间关闭 因为建模器的视图 都是按时间排序的 除非所有的区间 都被关闭 否则无法把时钟向前移动 所以如果你有一些 占用时间很长的区间 你会注意到 建模器的输出似乎停止了 当用户点击 停止记录按钮 所有的开启的区间都被关闭时 所有的进程才将正常 数据也才会涌入 但这种用户体验并不好 如果停止录制 你将有两种选择 第一个是将 Instruments 退出 即时模式支持 为此你可以给你的 Instrument 添加一个限制元素 第二个是不再将区间数据 作为建模器的输入 就像我们在演示中 为我们的专家系统 所做的那样 我们实际上使用的是 os-signpost-point 事件 而不是 os-signpost-interval
我知道我们让它看起来简单 但是即时模式实现起来 确实有点棘手 第三 非常重要的一点 如果你要创建的 Instrument 针对的是 大量的输入数据 那么最后 5 秒 记录模式 是最有效的 你可以在跟踪文档的 记录选项中进行切换 之后你将看到 你可以在即时 延迟 和最后 n 秒模式之间 进行选择 更加有效的原因是 它允许记录技术 使用缓冲 来提高性能 这样它就不会一直尝试 实时向 Instruments 提供数据 这将产生深远的影响 它会对 signpost 数据 产生巨大的影响 在最后 5 秒模式下 速度可以提升 10 倍 当然 为此的权衡是 你只能看到 最后 5 秒的数据 但是对于产生大量 数据的 Instrument 来说 这通常是件好事 这使得它成为 系统跟踪和 Metal 系统跟踪 以及游戏性能模板的通用模式 如果你的目标是这类 App 我也会建议 你的 Instrument 不要支持即时模式 这样你的用户体验 就不会很糟糕 Instruments 在获取数据时 也不会出现延后 你在区间中也不会遇到类似的问题
我们的讨论会差不多结束了 我们今天谈论很多 关于创建 Instrument 特性的内容 我们真的非常兴奋 因为我们能够 在今年向你们展示这一切 我们迫不及待地想知道 你们能通过 Instruments 创造什么样的成果 如果你想和我们 谈谈自定义 Instrument 我们将在今天下午 3 点 8 号实验室等待各位 另外 405 号讨论会详细介绍了 如何使用 os_signpost API 你可以通过它将数据输入 Instruments 请享受接下来的讨论会 [ 掌声 ]
-
-
正在查找特定内容?在上方输入一个主题,就能直接跳转到相应的精彩内容。
提交你查询的内容时出现错误。请检查互联网连接,然后再试一次。