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 的啓動界面,與之前版本差異不大。 網絡
介紹 Instrument 10 的架構以前,咱們先回顧下以往蘋果是怎麼維護 Instruments 組件的。 數據結構
有了足夠的理論知識後,咱們能夠嘗試下簡單的自定義工具建立了。 架構
.instrpkg
的 XML 文件,全部的配置都會在這個 XML 文件中完成,其中默認已經幫我生成了包含一些基礎信息。接着咱們要作的就是按照咱們的須要去寫這個配置文檔了。
一、導入須要使用的 Schemaapp
<!-- MARK: 導入你須要使用的 schema -->
<import-schema>tick</import-schema>
複製代碼
二、完成 Instrument 的『標準界面』和『分析核心』配置ide
<!-- 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 裏就可以像使用其餘內置工具同樣運行。 函數
這一部分咱們會介紹一些『標準界面』和『分析中心』裏更詳細的內容。
『標準界面』模塊裏爲咱們提供了不少簡單又好用的元素,使用這些元素可以讓咱們建立出很是酷炫又實用的 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 專題目錄