使用xhprof進行線上PHP效能追蹤及分析

Allo發表於2014-12-28

之前一直使用基於Xdebug進行PHP的效能分析,對於本地開發環境來說是夠用了,但如果是線上環境的話,xdebug消耗較大,配置也不夠靈活,因此線上環境建議使用xhprof進行PHP效能追蹤及分析

xhprof的安裝與簡易用法

xhprof是Facebook開源的輕量級PHP效能分析工具,Linux環境下可以通過pecl直接安裝,比如在Ubuntu下僅需3行指令

pecl install xhprof-beta
echo "extension=xhprof.so" > /etc/php5/fpm/conf.d/xhprof.ini
service php5-fpm restart

之後可以通過phpinfo()檢查擴充套件是否已經載入。

具體如何使用呢,xhprof專案中已經提供了示例以及簡易的UI,下載xhprof專案到web伺服器,假設可以通過http://localhost/xhprof/訪問,那麼訪問http://localhost/xhprof/examples/sample.php可以看到一些輸出,並且提示通過訪問http://<xhprof-ui-address>/index.php?run=XXX&source=xhprof_foo檢視結果。接下來訪問http://localhost/xhprof/xhprof_html/就可以看到已經儲存的結果,列出了所有函式的呼叫以及所消耗的時間。

分析一下示例程式碼sample.php,關鍵部分只有2行:

//開啟xhprof並開始記錄
xhprof_enable();
//執行一些函式
foo();
//停止記錄並取到結果
$xhprof_data = xhprof_disable();

$xhprof_data中記錄了程式單步執行過程中所有的函式呼叫時間及CPU記憶體消耗等,具體記錄哪些指標可以通過xhprof_enable的入口引數控制,之後的處理已經與xhprof擴充套件無關,大致是編寫了一個儲存類XHProfRuns_Default,將$xhprof_data序列化並儲存到某個目錄,可以通過XHProfRuns_Default(__DIR__)將結果輸出到當前目錄,如果不指定則會讀取php.ini配置檔案中的xhprof.output_dir,仍然沒有指定則會輸出到/tmp

xhprof_html/index.php將記錄的結果整理並視覺化,預設的UI裡列出了:

  • funciton name : 函式名
  • calls: 呼叫次數
  • Incl. Wall Time (microsec): 函式執行時間(包括子函式)
  • IWall%:函式執行時間(包括子函式)佔比
  • Excl. Wall Time(microsec):函式執行時間(不包括子函式)
  • EWall%:函式執行時間(不包括子函式)

每一項應該不難理解,以專案自帶的sample.php為例,示例中編寫了一個main()函式,main()函式中呼叫foo()bar()等一些子函式進行了一點字元處理。整個程式執行過程中,main()函式只執行了一次,並且由於main()函式中包括了所有的邏輯,所以main()函式的IWall%佔比為100%,但是由於main()函式的功能都是由子函式實現的,因此main()函式的EWall%只有0.3%,而foo()函式完成了主要的工作,EWall%有98.1%。因此在分析更大型的程式時,往往需要根據這幾項指標分別排序,從不同的角度審視效能消耗。

xhprof_html/index.php中還可以看到[View Full Callgraph]連結,點選後可以繪製出一張視覺化的效能分析圖,如果點選後報錯的話,可能是缺少依賴graphviz,ubuntu可以通過apt安裝

apt-get install graphviz

更好的注入方式

瞭解了上面這些,其實就已經可以將xhprof整合到任何我們已有的專案中去了。目前大部分MVC框架都有唯一的入口檔案,只需要在入口檔案的開始處注入xhprof的邏輯

//開啟xhprof
xhprof_enable(XHPROF_FLAGS_MEMORY | XHPROF_FLAGS_CPU);
//在程式結束後收集資料
register_shutdown_function(function() {
    $xhprof_data        = xhprof_disable();

    //讓資料收集程式在後臺執行
    if (function_exists('fastcgi_finish_request')) {
        fastcgi_finish_request();
    }

    //儲存xhprof資料
    ...
});

但是這樣免不了要修改專案的原始碼,其實php本身就提供了更好的注入方式,比如將上述邏輯儲存為/opt/inject.php,然後修改php fpm配置檔案

vi /etc/php5/fpm/php.ini

修改auto_prepend_file配置

auto_prepend_file = /opt/inject.php

這樣所有的php-fpm請求的php檔案前都會自動注入/opt/inject.php檔案

如果使用Nginx的話,還可以通過Nginx的配置檔案設定,這樣侵入性更小,並且可以實現基於站點的注入。

fastcgi_param PHP_VALUE "auto_prepend_file=/opt/inject.php";

更好的分析工具:xhprof.io還是xhpgui

注入程式碼後我們還需要實現儲存xhprof資料以及展示資料的UI,聽起來似乎又是一大堆工作,有現成的輪子可以用嗎?

經過搜尋和比較,貌似比較好的選擇有xhprof.io以及xhpgui

兩個專案做得事情差不多,都提供了xhprof資料儲存功能以及一套索引展示資料的UI,下面是一些比較

xhprof.io

  • ✗ 年久失修
  • ✗ 儲存xhprof資料到MySQL
  • ✓ 支援域名、URI等多個維度的資料索引
  • ✓ 函式呼叫記錄完整,核心級別函式都能顯示
  • ✗ 無法針對個別URI開啟
  • ✗ 注入被分割成兩個檔案,如果程式被強制中斷時xhprof資料將無法收集

xhgui

  • ✓ 儲存xhprof資料到MongoDB
  • ✗ 不支援域名索引
  • ✗ 函式呼叫記錄不完整,部分核心級別函式(如擴充套件內)無法顯示
  • ✓ 有配置檔案可以控制開啟條件
  • ✓ 注入只有一個檔案
  • ✓ 狂拽酷炫的基於D3.js的呼叫關係動態圖

可以看到其實兩個專案都不夠完善,相對而言xhgui不支援域名索引對於線上除錯來說是無法忍受的,因此我最後的選擇是使用xhprof.io,但是自己進行了微量的調整,修改後的xhprof.io修正版支援:

  • ✓ 增加開啟開關配置,可以針對個別URI開啟
  • ✓ 注入檔案合併為一個

xhprof.io修正版安裝與使用

安裝及配置方法如下,假設web伺服器根目錄為/opt/htdocs

cd /opt/htdocs
git clone https://github.com/EvaEngine/xhprof.io.git
cd xhprof.io/
composer install
cp xhprof/includes/config.inc.sample.php xhprof/includes/config.inc.php 
vi xhprof/includes/config.inc.php

在MySQL中建立xhprof.io資料庫,假設資料庫名為xhprof,然後匯入xhprof/setup/database.sql

配置檔案config.inc.php中需要調整

  • 'url_base' => 'http://localhost/xhprof.io/', 這是xhprof.io介面所在路徑
  • 'pdo' => new PDO('mysql:dbname=xhprof;host=localhost;charset=utf8', 'root', 'password'), 根據MySQL實際情況調整配置
  • enable 這是一個匿名函式,當匿名函式返回true時啟用xhprof資料收集

通過配置enable項,就可以實現線上除錯的需求,比如

始終開啟xhprof

'enable' => function() {
    return true;
}

1/100概率隨機開啟xhprof

'enable' => function() {
    return rand(0, 100) === 1;
}

網頁攜帶引數debug=1時開啟xhprof

'enable' => function() {
    return !empty($_GET['debug']);
}

網頁URL為特定路徑時開啟

'enable' => function() {
    return strpos($_SERVER['REQUEST_URI'], '/testurl') === 0;
}

最後按上文所述,在要配置的專案中包含xhprof.io/inc/inject.php即可。

線上環境操作時務必要膽大心細,如果沒有結果尤其注意需要檢查xhprof擴充套件是否安裝

附錄:xhpgui的安裝方法

apt-get install mongodb php5-mongo php5-mcrypt
cp /etc/php5/mods-available/mcrypt.ini /etc/php5/fpm/conf.d/
cp /etc/php5/mods-available/mcrypt.ini /etc/php5/cli/conf.d/
cd /opt/htdocs
git clone https://github.com/perftools/xhgui.git
cd xhgui
composer install
cp config/config.default.php config/config.php
chown www-data.www-data -R cache

編輯Nginx配置檔案加入

fastcgi_param PHP_VALUE "auto_prepend_file=/opt/htdocs/xhgui/external/header.php";

收集資料過多時可以清空mongodb

mongo
use xhprof;
db.dropDatabase();

相關文章