效能分析¶
LNT 支援儲存和顯示效能分析。這些分析的目的是為了揭露測試樣本之間程式碼產生的差異,並允許輕鬆識別程式碼的熱點區段。
LNT 中分析的原則¶
LNT 中的分析以自訂格式表示。使用者介面完全基於對此自訂格式的查詢進行操作。編寫了轉接器以將其他格式轉換為 LNT 的分析格式。分析資料作為正常 JSON 報告的一部分上傳到 LNT 伺服器。
產生分析資料¶
分析產生可以透過 python API 呼叫直接驅動(其中 lnt profile
是一個封裝器),或使用 lnt runtests
工具。
透過 lnt runtests test-suite
產生分析資料¶
注意
目前僅在 Linux 系統上支援透過 LNT 收集分析,因為目前編寫的唯一轉接器使用 Linux 的 perf
基礎架構。當編寫更多轉接器後,LNT 可以擴展對它們的支援。
如果您的測試系統已經使用 lnt runtests
來建置和執行測試,則產生分析的最簡單方法是簡單地新增一個參數
--use-perf=all
--use-perf
選項指定了 Linux Perf 的用途。選項包括
none
:完全不使用perf
time
:使用perf
來測量編譯和執行時間。這可能比time
更準確。profile
:僅使用perf
進行分析。all
:使用perf
進行分析和計時。
產生的分析與每個測試可執行檔位於同一位置,名為 $TEST.perf_data
。這些分析在測試執行結束時被處理並轉換為 LNT 的分析格式,並插入到產生的 report.json
中。
不使用 lnt runtests test-suite
產生分析資料¶
LNT 的一個支援的使用案例是使用 LNT 伺服器進行效能追蹤,但使用與 lnt runtests
不同的測試驅動程式來實際建置、執行和收集測試的統計資料。
分析資料位於提交給 LNT 的 JSON 報告中。本節將描述如何將分析資料新增到現有的 JSON 報告中;有關 JSON 報告的總體結構的詳細資訊,請參閱 匯入資料。
第一步是以 LNT 格式產生分析資料本身,適合透過 JSON 發送。要匯入分析,請使用 lnt profile upgrade
命令
lnt profile upgrade my_profile.perf_data /tmp/my_profile.lntprof
這裡假設 my_profile.perf_data
採用 Linux Perf 格式,但可以是已註冊轉接器的任何格式(目前僅為 Linux Perf,但預計隨著時間的推移會新增更多格式)。
/tmp/my_profile.lntprof
現在是以空間效率高的二進制形式存在的 LNT 分析。為了準備透過 JSON 發送它,我們必須對其進行 base-64 編碼
base64 -i /tmp/my_profile.lntprof > /tmp/my_profile.txt
現在我們只需要將其新增到報告中。分析看起來與雜湊類似,因為它們是具有字串資料的樣本
{
"format_version": "2",
"machine": {
...
},
"run": {
...
},
"tests": [
{
"name": "nts.suite1/program1",
"execution_time": [ 0.1056, 0.1055 ],
"profile": "eJxNj8EOgjAMhu99Cm9wULMOEHgBE888QdkASWCQFWJ8e1v04JIt+9f//7qmfkVoEj8yMXdzO70v/RJn2hJYrRQiveSWATdJvwe3jUtgecgh9Wsh9T6gyJvKUjm0kegK0mmt9UCjJUSgB5q8KsobUJOQ96dozr8tAbRApPbssOeCcm83ddoLC7ijMcA/RGUUwXt7iviPEDLJN92yh62LR7I8aBUMysgLnaKNFNzzMo8y7uGplQ4sa/j6rfn60WYaGdRhtT9fP5+JUW4="
}
]
}
支援的格式¶
Linux Perf¶
Perf 分析直接從二進制 perf.data
檔案讀取,而無需使用 perf
封裝器工具或任何 Linux/GPL 標頭。這使其可以在非 Linux 平台上執行,儘管這僅在除錯時真正有用,因為預期被分析的二進制檔案/函式庫是可讀取的。
perf 匯入程式碼使用了一個名為 cPerf 的 C++ 擴充功能,它是為 LNT 專案編寫的。它的功能不如 perf annotate
或 perf report
,但以大約快 6 倍的速度產生大致相同的機器可讀資料。它以 C++ 編寫,因為編寫在二進制資料上高效執行的可讀 Python 很困難。一旦事件流被聚合,就會建立一個 python 字典物件,並且處理返回到 Python。速度在這個階段很重要,因為分析匯入可能在較舊或效能較低的硬體上執行,並且 LLVM 的測試套件包含數百個必須匯入的測試!
注意
在最近版本的 Perf 中,存在一個新的子命令:perf data
。這以 CTF 格式輸出事件追蹤,然後可以使用 babeltrace 及其 Python 綁定進行查詢。只要效能相似,這將允許移除 LNT 中的許多自訂程式碼。
新增對新分析格式的支援¶
要建立新的分析轉接器,必須在 lnt.testing.profile
套件中建立一個新的 Python 類別,該類別繼承 ProfileImpl
類別
- class lnt.testing.profile.profile.ProfileImpl¶
- static checkFile(fname)¶
如果 'fname' 是此分析實作的序列化版本,則返回 True。
- static deserialize(fobj)¶
從 'fobj' 讀取分析,返回新的分析物件。這可以是延遲載入。
- getCodeForFunction(fname)¶
返回一個產生器,它將為每次調用返回一個三元組
(counters, address, text)
其中 counters 是一個字典:(例如)
{'cycles': 50.0}
,text 的格式與 getDisassemblyFormat() 返回的格式相同,address 是一個整數。計數器值必須是百分比(函數總計的百分比),而不是絕對數字。
- getDisassemblyFormat()¶
返回 getCodeForFunction() 返回的反組譯字串的格式。可能的值為
raw
- 無法進行解譯;純字串。
marked-up-disassembly
- LLVM 標記的反組譯格式。
- getFunctions()¶
返回一個字典,其中包含函數名稱以及關於該函數的資訊。
資訊字典包含
counters
- 函數的計數器值。length
- 調用 getCodeForFunction 以獲取所有指令的次數。
字典不應包含反組譯/函數內容。計數器值必須是百分比,而不是絕對數字。
例如:
{'main': {'counters': {'cycles': 50.0, 'branch-misses': 0}, 'length': 200}, 'dotest': {'counters': {'cycles': 50.0, 'branch-misses': 0}, 'length': 4} }
- getTopLevelCounters()¶
返回一個字典,其中包含整個分析的計數器。這些將是絕對數字:例如
{'cycles': 5000.0}
。
- getVersion()¶
返回分析版本。
- serialize(fname=None)¶
將分析序列化為給定的檔案名(base)。如果 fname 為 None,則作為位元組實例返回。
- static upgrade(old)¶
在 'old' 中取得先前的分析實作,並為此版本返回新的 ProfileImpl。唯一必須支援的舊版本是緊鄰的先前版本(例如,版本 3 僅需處理從版本 2 的升級)。
您的子類別可以實作所有指定的函數,或者執行 perf.py
所做的事情,即僅實作 checkFile()
和 deserialize()
靜態函數。在此模型中,在 deserialize()
內部,您會將分析資料解析為簡單的字典結構,並從中建立 ProfileV1Impl
物件。這是一個非常簡單的分析實作,它僅從字典表示形式工作
- class lnt.testing.profile.profilev1impl.ProfileV1(data)¶
ProfileV1 檔案沒有任何巧妙之處。它們是簡單的 Python 物件,其分析資料以最明顯的方式佈局以進行生產/使用,然後被 pickle 和壓縮。
它們預期透過簡單地儲存到
self.data
成員中來建立。self.data
成員具有以下格式{ counters: {'cycles': 12345.0, 'branch-misses': 200.0}, # absolute values. disassembly-format: 'raw', functions: { name: { counters: {'cycles': 45.0, ...}, # Note counters are now percentages. data: [ [463464, {'cycles': 23.0, ...}, ' add r0, r0, r1'}], ... ] } } }
- static checkFile(fn)¶
如果 'fname' 是此分析實作的序列化版本,則返回 True。
- static deserialize(fobj)¶
從 'fobj' 讀取分析,返回新的分析物件。這可以是延遲載入。
- getCodeForFunction(fname)¶
返回一個產生器,它將為每次調用返回一個三元組
(counters, address, text)
其中 counters 是一個字典:(例如)
{'cycles': 50.0}
,text 的格式與 getDisassemblyFormat() 返回的格式相同,address 是一個整數。計數器值必須是百分比(函數總計的百分比),而不是絕對數字。
- getDisassemblyFormat()¶
返回 getCodeForFunction() 返回的反組譯字串的格式。可能的值為
raw
- 無法進行解譯;純字串。
marked-up-disassembly
- LLVM 標記的反組譯格式。
- getFunctions()¶
返回一個字典,其中包含函數名稱以及關於該函數的資訊。
資訊字典包含
counters
- 函數的計數器值。length
- 調用 getCodeForFunction 以獲取所有指令的次數。
字典不應包含反組譯/函數內容。計數器值必須是百分比,而不是絕對數字。
例如:
{'main': {'counters': {'cycles': 50.0, 'branch-misses': 0}, 'length': 200}, 'dotest': {'counters': {'cycles': 50.0, 'branch-misses': 0}, 'length': 4} }
- getTopLevelCounters()¶
返回一個字典,其中包含整個分析的計數器。這些將是絕對數字:例如
{'cycles': 5000.0}
。
- getVersion()¶
返回分析版本。
- serialize(fname=None)¶
將分析序列化為給定的檔案名(base)。如果 fname 為 None,則作為位元組實例返回。
- static upgrade(old)¶
在 'old' 中取得先前的分析實作,並為此版本返回新的 ProfileImpl。唯一必須支援的舊版本是緊鄰的先前版本(例如,版本 3 僅需處理從版本 2 的升級)。
檢視分析¶
一旦分析提交到 LNT,它們可以透過手動 URL 或透過「執行」頁面取得。
在執行結果頁面上,如果分析資料可用,當滑鼠懸停在表格行上時,應該會出現「檢視分析」連結。
注意
已知這種懸停效果對觸控螢幕不友善,並且可能不直觀。此頁面應盡快修改,以使分析資料連結更明顯。
或者,可以透過手動建構 URL 來檢視分析
db_default/v4/nts/profile/<test-id>/<run1-id>/<run2-id>
其中
test-id
是要顯示的測試的資料庫 TestIDrun1-id
是要顯示在顯示畫面左側的執行的資料庫 RunIDrun2-id
是要顯示在顯示畫面右側的執行的資料庫 RunID
顯然,此 URL 有點難以建構,因此建議使用上面執行頁面中的連結。