火焰圖
火焰圖有什麼用?
火焰圖是一種視覺化函式中 CPU 時間消耗的方式。它們可以幫助你確定在哪些同步操作上花費了過多時間。
如何建立火焰圖
你可能聽說過為 Node.js 建立火焰圖很困難,但這已不再是事實。生成火焰圖不再需要 Solaris 虛擬機器!
火焰圖是根據 perf 的輸出生成的,它不是一個特定於 Node.js 的工具。雖然這是視覺化 CPU 時間消耗最強大的方法,但它可能在 Node.js 8 及以上版本中對 JavaScript 程式碼的最佳化方式存在問題。請參閱下文的 perf 輸出問題部分。
使用預打包的工具
如果你想要一步就在本地生成火焰圖,可以試試 0x
對於診斷生產部署,請閱讀這些說明:0x 生產伺服器。
使用系統 perf 工具建立火焰圖
本指南旨在展示建立火焰圖所涉及的步驟,並讓你掌控每一步。
如果你想更好地理解每一步,請檢視接下來的部分,我們將進行更詳細的介紹。
現在讓我們開始工作吧。
-
安裝
perf(如果尚未安裝,通常可透過 linux-tools-common 包獲得) -
嘗試執行
perf- 它可能會抱怨缺少核心模組,也請一併安裝 -
在啟用 perf 的情況下執行 Node.js(有關特定於 Node.js 版本的提示,請參閱 perf 輸出問題)
perf record -e cycles:u -g -- node --perf-basic-prof --interpreted-frames-native-stack app.js -
忽略警告,除非它們是說你因缺少軟體包而無法執行 perf;你可能會收到一些關於無法訪問核心模組樣本的警告,但你本來也不需要它們。
-
執行
perf script > perfs.out來生成你稍後將要視覺化的資料檔案。進行一些清理以獲得更易讀的圖表會很有幫助 -
克隆 Brendan Gregg 的 FlameGraph 工具:https://github.com/brendangregg/FlameGraph
-
執行
cat perfs.out | ./FlameGraph/stackcollapse-perf.pl | ./FlameGraph/flamegraph.pl --colors=js > profile.svg
現在在你喜歡的瀏覽器中開啟火焰圖檔案,觀察它的燃燒。它經過了顏色編碼,因此你可以首先關注最飽和的橙色條。它們很可能代表了 CPU 密集型函式。
值得一提的是 - 如果你點選火焰圖的一個元素,它會放大你點選的部分。
使用 perf 對正在執行的程序進行取樣
這對於從一個你不想中斷的正在執行的程序中記錄火焰圖資料非常有用。想象一下一個生產環境中的程序,其問題難以重現。
perf record -F99 -p `pgrep -n node` -g -- sleep 3
等等,那個 sleep 3 是幹什麼用的?它的作用是保持 perf 執行 - 儘管 -p 選項指向了另一個 pid,但該命令需要在某個程序上執行並隨之結束。perf 會在你傳遞給它的命令的整個生命週期內執行,無論你是否真的在分析那個命令。sleep 3 確保了 perf 執行 3 秒鐘。
為什麼 -F(分析頻率)設定為 99?這是一個合理的預設值。你可以根據需要調整。-F99 告訴 perf 每秒採集 99 個樣本,要獲得更高的精度可以增加該值。較低的值應該會產生較少的輸出,但結果精度也較低。你需要的精度取決於你的 CPU 密集型函式實際執行的時長。如果你正在尋找導致明顯減速的原因,每秒 99 幀應該綽綽有餘。
在你獲得那 3 秒的 perf 記錄後,繼續使用上面的最後兩個步驟生成火焰圖。
過濾掉 Node.js 內部函式
通常,你只想檢視你的函式呼叫的效能,所以過濾掉 Node.js 和 V8 的內部函式可以讓圖表更易於閱讀。你可以用以下命令清理你的 perf 檔案
sed -i -r \
-e "/( __libc_start| LazyCompile | v8::internal::| Builtin:| Stub:| LoadIC:|\[unknown\]| LoadPolymorphicIC:)/d" \
-e 's/ LazyCompile:[*~]?/ /' \
perfs.out
如果你在閱讀火焰圖時覺得它看起來很奇怪,好像在佔用大部分時間的關鍵函式中缺少了什麼,請嘗試在不使用過濾器的情況下生成火焰圖 - 也許你遇到了一個罕見的 Node.js 自身的問題。
Node.js 的分析選項
--perf-basic-prof-only-functions 和 --perf-basic-prof 是對除錯你的 JavaScript 程式碼有用的兩個選項。其他選項用於分析 Node.js 本身,這超出了本指南的範圍。
--perf-basic-prof-only-functions 產生的輸出更少,因此是開銷最小的選項。
我為什麼需要它們?
嗯,沒有這些選項,你仍然可以得到一個火焰圖,但大多數條形圖的標籤會是 v8::Function::Call。
perf 輸出問題
Node.js 8.x V8 管道變更
Node.js 8.x 及以上版本附帶了 V8 引擎中 JavaScript 編譯管道的新最佳化,這有時會使 perf 無法訪問函式名稱/引用。(它被稱為 Turbofan)
結果是,你可能無法在火焰圖中正確地看到你的函式名稱。
你會注意到在你期望看到函式名稱的地方出現了 ByteCodeHandler:。
0x 內建了一些針對此問題的緩解措施。
詳情請見
- https://github.com/nodejs/benchmarking/issues/168
- https://github.com/nodejs/diagnostics/issues/148#issuecomment-369348961
Node.js 10+
Node.js 10.x 使用 --interpreted-frames-native-stack 標誌解決了 Turbofan 的問題。
執行 node --interpreted-frames-native-stack --perf-basic-prof-only-functions 可以在火焰圖中獲取函式名稱,無論 V8 使用哪個管道來編譯你的 JavaScript。
火焰圖中標籤損壞
如果你看到的標籤是這樣的
node`_ZN2v88internal11interpreter17BytecodeGenerator15VisitStatementsEPNS0_8ZoneListIPNS0_9StatementEEE
這意味著你使用的 Linux perf 編譯時沒有包含 demangle 支援,例如請參見 https://bugs.launchpad.net/ubuntu/+source/linux/+bug/1396654
示例
透過火焰圖練習來自己練習捕獲火焰圖吧!