效能分析檔

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 報告的一般結構的詳細資訊,請參閱 匯入資料

第一步是以適合透過 JSON 傳送的 LNT 格式產生效能分析資料本身。若要匯入效能分析資料,請使用 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 傳送,我們必須對其進行 Base64 編碼

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 annotateperf 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)

其中,計數器是一個字典:(例如) {'cycles': 50.0},文字格式與 getDisassemblyFormat() 傳回的格式相同,而地址是一個整數。

計數器值必須是百分比(函數總計的百分比),而不是絕對數字。

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)

將設定檔序列化到給定的檔名(基本名稱)。 如果 fname 為 None,則以 bytes 實例傳回。

static upgrade(old)

在「舊」中取得先前的設定檔實作,並為此版本傳回新的 ProfileImpl。 必須支援的唯一舊版本是緊鄰的前一個版本(例如,版本 3 只需要處理從版本 2 的升級)。

您的子類別可以按照指定的方式實作所有函數,也可以執行 perf.py 所做的事情,也就是只實作 checkFile()deserialize() 靜態函數。 在此模型中,在 deserialize() 內部,您會將設定檔資料解析為簡單的字典結構,並從中建立 ProfileV1Impl 物件。 這是一個非常簡單的設定檔實作,僅適用於字典表示法

class lnt.testing.profile.profilev1impl.ProfileV1(data)

ProfileV1 檔案不以任何方式進行巧妙的處理。 它們是簡單的 Python 物件,其設定檔資料以最明顯的方式佈局,以便進行產生/消耗,然後進行醃製和壓縮。

預計它們會透過簡單地儲存到 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)

其中,計數器是一個字典:(例如) {'cycles': 50.0},文字格式與 getDisassemblyFormat() 傳回的格式相同,而地址是一個整數。

計數器值必須是百分比(函數總計的百分比),而不是絕對數字。

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)

將設定檔序列化到給定的檔名(基本名稱)。 如果 fname 為 None,則以 bytes 實例傳回。

static upgrade(old)

在「舊」中取得先前的設定檔實作,並為此版本傳回新的 ProfileImpl。 必須支援的唯一舊版本是緊鄰的前一個版本(例如,版本 3 只需要處理從版本 2 的升級)。

檢視效能分析

將效能分析提交至 LNT 後,可以透過手動輸入網址或透過「執行」頁面取得。

在執行結果頁面上,如果有效能分析資料可用,則將滑鼠懸停在表格列上時,應該會出現「檢視效能分析」連結。

備註

眾所周知,這種懸停效果不適合觸控螢幕,而且可能不太直觀。此頁面應該很快就會修改,以便讓效能分析資料連結更加明顯。

或者,也可以透過手動建構網址來檢視效能分析

db_default/v4/nts/profile/<test-id>/<run1-id>/<run2-id>

其中

  • test-id 是要顯示的測試的資料庫 TestID

  • run1-id 是要顯示在左側的執行的資料庫 RunID

  • run2-id 是要顯示在右側的執行的資料庫 RunID

顯然,這個網址有點難以建構,因此建議使用上面提到的執行頁面上的連結。