PHP-stacktrace: PHP 程式外檢視函式呼叫堆疊

Oraoto發表於2017-10-10

生產環境多多少少會遇到CPU佔用很高或者卡住的PHP程式,這時怎樣才能知道這個程式在幹啥呢?

一個方法是strace跟蹤系統呼叫和引數,這樣能大概知道PHP程式在幹啥。要看到具體的PHP函式就需要用PHP擴充套件(xdebug、xhprof)或者用GDB除錯,高階點還可以用DTrace。

上週發現了ruby-stacktrace,它直接讀取ruby程式的記憶體來獲取堆疊資訊,不用GDB和擴充套件,所以效能很好,於是我也照著寫了一個php-stacktrace,算是勉強能用的玩具。

使用

使用比較簡單,下載解壓即可:

$ ./php-stacktrace --help
php-stacktrace 0.1
Sampling profiler for PHP programs

USAGE:
    php-stacktrace <COMMAND> <DEBUGINFO> <PID>

FLAGS:
    -h, --help       Prints help information
    -V, --version    Prints version information

ARGS:
    <COMMAND>      trace or top or oneshot
    <DEBUGINFO>    Path to php debuginfo
    <PID>          PID of the PHP process you want to profile

三個引數都是必填的。

COMMAND可以是tracetoponeshotoneshot只檢視一次就退出,tracetop會一直跟蹤,trace的輸出可以用來生成火焰圖,top統計函式耗時。

DEBUGINFO是除錯資訊檔案的路徑,Linux通常要獨立安裝debuginfo包,因為不會從elf裡解析路徑,所以要通過這個引數指定,通常的路徑是/usr/lib/debug/.dwz/php....(在隱藏目錄裡,是個小坑)。

PID就是要跟蹤的PHP程式ID。

順帶一提,只支援非執行緒安全的PHP 7.1。

原理

眾所周知,Zend VM是用C寫的,而各種PHP函式呼叫的資訊都會用C語言的struct/union來表示,所以只要兩步就能拿到堆疊資訊:

  1. 讀取PHP程式的記憶體
  2. 在記憶體裡找到函式呼叫堆疊資訊

第一步可以通過ptraceprocess_vm_readv實現。ptrace就是偵錯程式所用的方法,它可以暫停PHP程式然後讀取記憶體。process_vm_readv可以不暫停程式,效能可能更好,但是不可靠,因為PHP還在執行,堆疊資訊不斷變化,很容易讀到錯誤的記憶體。

第二步就需要DWARF除錯資訊了,除錯資訊裡記錄了結構體大小、欄位偏移資訊,通過這些資訊我們就可以準確地去讀記憶體然後做解析。

原理還是很簡單的。

下一步

  1. 複製vm_stack,儘量在一次process_vm_readv拿到主要的堆疊資訊
  2. 增加作用域資訊,現在只有函式名
  3. 完善錯誤處理,現在的程式碼是一團糟
  4. 多學點Rust

2017-10-12更新

新版已經移除了tracetop,增加了配置檔案,使用示例可看Readme

本作品採用《CC 協議》,轉載必須註明作者和本文連結

相關文章