效能分析

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

其中 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 是要顯示的測試的資料庫 TestID

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

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

顯然,此 URL 有點難以建構,因此建議使用上面執行頁面中的連結。