Java火焰圖在Netflix的實踐
為了分析不同軟件或軟件的不同版本使用CPU的情況,相關設計人員通常需要進行函數的堆棧性能分析。相比于定期采樣獲得數據的方式,利用定時中斷來收集程序運行時的PC寄存器值、函數地址以及整個堆棧軌跡更加高效。目前, OProfile 、 gprof 和 SystemTap 等工具都是采用該方法,給出詳細的CPU使用情況報告。然而,這些工具在處理復雜的統計數據時,給出的報告往往過于繁雜、不夠直觀、不能直接反應分析員所需要的數據。為此,Brendan Gregg開發了專門把采樣到的堆棧軌跡(Stack Trace)轉化為直觀圖片顯示的工具—— Flame Graph(火焰圖) 。但是,由于分析器與JDK環境等原因,Java程序的混合模式火焰圖之前無法生成。近期,Brendan Gregg和Martin Spier發現了一種解決該問題的方法,在Netflix內部進行了實踐,并貢獻了一篇非常詳盡的 實踐性文章 。為Java程序的性能分析提供了極大便利。接下來,本文就從該問題出現的原因開始,簡要介紹其解決該問題的思路和方法。
首先,本文對火焰圖的概念進行簡要介紹。火焰圖既是一個 開源工具 ,也是一種類型的圖片。作為一個二維圖片,火焰圖的X軸代表采樣總量,而Y軸代表棧深度。每個框就代表了一個棧里的函數,其寬度代表了所占用的CPU總時間。因此,比較寬的框就表示該函數運行時間較慢或被調用次數較多,從而占用的CPU時間多。通過火焰圖,相關設計或分析人員就可以輕松觀察到各個應用占用 CPU的情況。
但是,火焰圖本身并不具備性能檢測的能力。它需要其他性能分析工具的協助。在Java環境中,一共有兩種類型的堆棧軌跡采樣分析器——系統分析器(System Profiler)和JVM分析器(JVM Profiler)。前者(如Linux的 Perf Events )可以分析系統代碼路徑,包括libjvm internal、GC和內核,但并不能分析Java方法;后者(如 HPROF 、輕量級Java分析器和其他商業分析器)可以顯示Java方法,但不能顯示系統代碼路徑。由此可見,這兩種方法都不能同時支持系統代碼路徑和Java方法的堆棧軌跡。而分別描述二者的火焰圖又不能很好的滿足需求。因此,Brendan等人一直關注如何解決該問題。
在之前的 一次討論 中,Brendan曾經對系統分析器不能顯示Java方法的原因進行分析。這包括兩個方面——JVM編譯方法時比較快,沒有為系統分析器暴露一個符號表;JVM采用x86上的frame pointer作為一個通用寄存器,破壞了傳統的stack walking。那么,解決之前的問題,就需要分別從這兩個方面入手。對于第一個方面,Java和Linux系統的分析器進行了雙方面的努力。首先,Java開始支持利用開源的JVMTI代理 perf-map-agent 來創建perf-PID.map文本文件。該文件列舉了16進制的符號地址、大小以及符號名稱。然后,從2009年以后,Linux中的 Perf_events工具添加了對JIT符號的支持。該工具會檢查/tmp/perf-PID.map文件,從而完成對來自語言虛擬機的符號進行檢查。對于第二個方面,JVM添加了一個新的選項-XX:+PreserveFramePointer。經過Zoltán、Oracle和其他工程師的努力,最新的 JDK9 和 JDK8 已經增加了該選項,從而保存了stack walking。
在兩方面的問題都解決之后,用戶只要經過安裝Perf Events、新版JDK、perf-map-agent以及FlameGraph等軟件和配置Java(尤其是打開 -XX:+PreserveFramePointer選項)的步驟后,就可以產生系統級的火焰圖了。為了讓產生火焰圖的流程自動化,Brendan等人已經開始基于開源的實例化分析工具 Vector 進行流程的建模。
未來,Breden等人還計劃進行很多工作。其一是通過自動化收集不同日期的差分火焰圖進行規則分析。這有助于迅速理解軟件變化所導致的CPU使用率變化。此外,他們還試圖利用Perf Events進行磁盤IO、網絡、調度以及內存分配等用戶和內核級的事件記錄和分析。最后,對火焰圖和Vector進行實時更新等改進也是未來考慮增加的功能。