xhprof 是 Facebook 09 年出的一個很優秀的 PHP profiler 工具,但 Facebook 後來遷移到 hhvm,早已不再維護,它在 PHP7 下有諸多 bug。
本文使用的是github上別人做的兼容PHP7的xprof項目。
安裝
cd ~/source
git clone https://github.com/longxinH/xhprof
cd xhprof/extension/
查找php-config位置
which php-config
安裝插件
phpize
./configure --with-php-config=/usr/local/php/bin/php-config --enable-xhprof
make
make install
修改php.ini,先用php --ini 查找到php.ini文件的位置,再在其中加入如下內容:
[xhprof]
extension=xhprof.so;
xhprof.output_dir=/var/tmp/xhprof
其中 xhprof.output_dir 是 xhprof 的輸出目錄,每次執行 xhprof 的 save_run 方法時都會生成一個 run_id.project_name.xhprof 文件。這個目錄在哪裏並不重要。
重啓php-fpm
service php-fpm restart
php -m | grep xhprof
此時可以看到顯示結果,xhprof擴展已安裝成功
nginx配置訪問
當生成 .xhprof 文件之後,我們就可以利用 xhprof_lib 來展示結果了。
server {
listen 80;
root /var/www/html/xhprof/xhprof_html;
server_name your_host;
location = / {
index index.php;
}
location ~ \.php {
fastcgi_pass 127.0.0.1:9000;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}
}
將 xhprof 中的 xhprof_lib 和 xhprof_html 兩個文件夾複製到 /var/www/html/xhprof/ 下,然後訪問 http://your_host/xhprof_html/index.php 即可看到如下圖所示生成的數據分析文件。
文件保存的路徑爲上邊配置的:
使用方法
方法一
使用如下代碼將要檢查的代碼包裹起來即可:
<?php
//header
xhprof_enable(XHPROF_FLAGS_CPU + XHPROF_FLAGS_MEMORY);
// 要檢查性能的代碼
//footer
$xhprof_data = xhprof_disable();
include_once '/var/www/html/xhprof/xhprof_lib/utils/xhprof_lib.php';
include_once '/var/www/html/xhprof/xhprof_lib/utils/xhprof_runs.php';
$xhprof_runs = new \XHProfRuns_Default();
$run_id = $xhprof_runs->save_run($xhprof_data, 'test');//test爲生成文件內包含的一個關鍵字區分哪個接口的,可自定義
也可將前後代碼單獨分開寫成PHP文件:header.php和footer.php
// header.php
<?php
if (extension_loaded('xhprof')) {
include_once 'xhprof_lib/utils/xhprof_lib.php';
include_once 'xhprof_lib/utils/xhprof_runs.php';
xhprof_enable(XHPROF_FLAGS_CPU + XHPROF_FLAGS_MEMORY);
}
?>
// footer.php
<?php
if (extension_loaded('xhprof')) {
$profiler_namespace = 'your_project';
$xhprof_data = xhprof_disable();
$xhprof_runs = new XHProfRuns_Default();
$run_id = $xhprof_runs->save_run($xhprof_data, $profiler_namespace);
}
?>
然後用這個兩個文件包裹代碼如下所示:
<?php
include_once '/var/www/html/xhprof/header.php';
// 要檢查性能的代碼
include_once '/var/www/html/xhprof/footer.php';
示例:
include_once '/var/www/html/xhprof/header.php';
function hallo() {
}
function simpleucall($n) {
for ($i = 0; $i < $n; $i++)
hallo();
}
function simpleudcall($n) {
for ($i = 0; $i < $n; $i++)
hallo2();
}
function hallo2() {
}
function simpleicall($n) {
for ($i = 0; $i < $n; $i++)
func_num_args();
}
class Foo {
static $a = 0;
public $b = 0;
const TEST = 0;
static function read_static($n) {
for ($i = 0; $i < $n; ++$i) {
$x = self::$a;
}
}
static function write_static($n) {
for ($i = 0; $i < $n; ++$i) {
self::$a = 0;
}
}
static function isset_static($n) {
for ($i = 0; $i < $n; ++$i) {
$x = isset(self::$a);
}
}
static function empty_static($n) {
for ($i = 0; $i < $n; ++$i) {
$x = empty(self::$a);
}
}
static function f() {
}
static function call_static($n) {
for ($i = 0; $i < $n; ++$i) {
self::f();
}
}
function read_prop($n) {
for ($i = 0; $i < $n; ++$i) {
$x = $this->b;
}
}
function write_prop($n) {
for ($i = 0; $i < $n; ++$i) {
$this->b = 0;
}
}
function assign_add_prop($n) {
for ($i = 0; $i < $n; ++$i) {
$this->b += 2;
}
}
function pre_inc_prop($n) {
for ($i = 0; $i < $n; ++$i) {
++$this->b;
}
}
function pre_dec_prop($n) {
for ($i = 0; $i < $n; ++$i) {
--$this->b;
}
}
function post_inc_prop($n) {
for ($i = 0; $i < $n; ++$i) {
$this->b++;
}
}
function post_dec_prop($n) {
for ($i = 0; $i < $n; ++$i) {
$this->b--;
}
}
function isset_prop($n) {
for ($i = 0; $i < $n; ++$i) {
$x = isset($this->b);
}
}
function empty_prop($n) {
for ($i = 0; $i < $n; ++$i) {
$x = empty($this->b);
}
}
function g() {
}
function call($n) {
for ($i = 0; $i < $n; ++$i) {
$this->g();
}
}
function read_const($n) {
for ($i = 0; $i < $n; ++$i) {
$x = $this::TEST;
}
}
}
function read_static($n) {
for ($i = 0; $i < $n; ++$i) {
$x = Foo::$a;
}
}
function write_static($n) {
for ($i = 0; $i < $n; ++$i) {
Foo::$a = 0;
}
}
function isset_static($n) {
for ($i = 0; $i < $n; ++$i) {
$x = isset(Foo::$a);
}
}
function empty_static($n) {
for ($i = 0; $i < $n; ++$i) {
$x = empty(Foo::$a);
}
}
function call_static($n) {
for ($i = 0; $i < $n; ++$i) {
Foo::f();
}
}
function create_object($n) {
for ($i = 0; $i < $n; ++$i) {
$x = new Foo();
}
}
define('TEST', null);
function read_const($n) {
for ($i = 0; $i < $n; ++$i) {
$x = TEST;
}
}
function read_auto_global($n) {
for ($i = 0; $i < $n; ++$i) {
$x = $_GET;
}
}
$g_var = 0;
function read_global_var($n) {
for ($i = 0; $i < $n; ++$i) {
$x = $GLOBALS['g_var'];
}
}
function read_hash($n) {
$hash = array('test' => 0);
for ($i = 0; $i < $n; ++$i) {
$x = $hash['test'];
}
}
function read_str_offset($n) {
$str = "test";
for ($i = 0; $i < $n; ++$i) {
$x = $str[1];
}
}
function issetor($n) {
$val = array(0,1,2,3,4,5,6,7,8,9);
for ($i = 0; $i < $n; ++$i) {
$x = $val ?: null;
}
}
function issetor2($n) {
$f = false; $j = 0;
for ($i = 0; $i < $n; ++$i) {
$x = $f ?: $j + 1;
}
}
function ternary($n) {
$val = array(0,1,2,3,4,5,6,7,8,9);
$f = false;
for ($i = 0; $i < $n; ++$i) {
$x = $f ? null : $val;
}
}
function ternary2($n) {
$f = false; $j = 0;
for ($i = 0; $i < $n; ++$i) {
$x = $f ? $f : $j + 1;
}
}
/*****/
function empty_loop($n) {
for ($i = 0; $i < $n; ++$i) {
}
}
function gethrtime()
{
$hrtime = hrtime();
return (($hrtime[0]*1000000000 + $hrtime[1]) / 1000000000);
}
function start_test()
{
ob_start();
return gethrtime();
}
function end_test($start, $name, $overhead = null)
{
global $total;
global $last_time;
$end = gethrtime();
ob_end_clean();
$last_time = $end-$start;
$total += $last_time;
$num = number_format($last_time,3);
$pad = str_repeat(" ", 24-strlen($name)-strlen($num));
if (is_null($overhead)) {
echo $name.$pad.$num."\n";
} else {
$num2 = number_format($last_time - $overhead,3);
echo $name.$pad.$num." ".$num2."\n";
}
ob_start();
return gethrtime();
}
function total()
{
global $total;
$pad = str_repeat("-", 24);
echo $pad."\n";
$num = number_format($total,3);
$pad = str_repeat(" ", 24-strlen("Total")-strlen($num));
echo "Total".$pad.$num."\n";
}
const N = 5000000;
$t0 = $t = start_test();
empty_loop(N);
$t = end_test($t, 'empty_loop');
$overhead = $last_time;
simpleucall(N);
$t = end_test($t, 'func()', $overhead);
simpleudcall(N);
$t = end_test($t, 'undef_func()', $overhead);
simpleicall(N);
$t = end_test($t, 'int_func()', $overhead);
Foo::read_static(N);
$t = end_test($t, '$x = self::$x', $overhead);
Foo::write_static(N);
$t = end_test($t, 'self::$x = 0', $overhead);
Foo::isset_static(N);
$t = end_test($t, 'isset(self::$x)', $overhead);
Foo::empty_static(N);
$t = end_test($t, 'empty(self::$x)', $overhead);
read_static(N);
$t = end_test($t, '$x = Foo::$x', $overhead);
write_static(N);
$t = end_test($t, 'Foo::$x = 0', $overhead);
isset_static(N);
$t = end_test($t, 'isset(Foo::$x)', $overhead);
empty_static(N);
$t = end_test($t, 'empty(Foo::$x)', $overhead);
Foo::call_static(N);
$t = end_test($t, 'self::f()', $overhead);
call_static(N);
$t = end_test($t, 'Foo::f()', $overhead);
$x = new Foo();
$x->read_prop(N);
$t = end_test($t, '$x = $this->x', $overhead);
$x->write_prop(N);
$t = end_test($t, '$this->x = 0', $overhead);
$x->assign_add_prop(N);
$t = end_test($t, '$this->x += 2', $overhead);
$x->pre_inc_prop(N);
$t = end_test($t, '++$this->x', $overhead);
$x->pre_dec_prop(N);
$t = end_test($t, '--$this->x', $overhead);
$x->post_inc_prop(N);
$t = end_test($t, '$this->x++', $overhead);
$x->post_dec_prop(N);
$t = end_test($t, '$this->x--', $overhead);
$x->isset_prop(N);
$t = end_test($t, 'isset($this->x)', $overhead);
$x->empty_prop(N);
$t = end_test($t, 'empty($this->x)', $overhead);
$x->call(N);
$t = end_test($t, '$this->f()', $overhead);
$x->read_const(N);
$t = end_test($t, '$x = Foo::TEST', $overhead);
create_object(N);
$t = end_test($t, 'new Foo()', $overhead);
read_const(N);
$t = end_test($t, '$x = TEST', $overhead);
read_auto_global(N);
$t = end_test($t, '$x = $_GET', $overhead);
read_global_var(N);
$t = end_test($t, '$x = $GLOBALS[\'v\']', $overhead);
read_hash(N);
$t = end_test($t, '$x = $hash[\'v\']', $overhead);
read_str_offset(N);
$t = end_test($t, '$x = $str[0]', $overhead);
issetor(N);
$t = end_test($t, '$x = $a ?: null', $overhead);
issetor2(N);
$t = end_test($t, '$x = $f ?: tmp', $overhead);
ternary(N);
$t = end_test($t, '$x = $f ? $f : $a', $overhead);
ternary2(N);
$t = end_test($t, '$x = $f ? $f : tmp', $overhead);
total($t0, "Total");
include_once '/var/www/html/xhprof/footer.php';
運行:
php index.php
訪問http://your_host/xhprof_html/index.php:
點擊進去,是程序運行過程中各個方法所用時間和執行次數等數據的統計的表格,可進行排序查看:
選擇:[View Full Callgraph]查看生成的時序圖,紅色的是顯示用時最多的,黃色其次,其中包含有執行的方法+用時+調用次數:
方法二:在框架內使用
直接將xhprof_lib作爲第三方庫引入到框架lib文件夾內調用。
在框架入口文件index.php所有代碼外層包裹上上邊的header和footer文件的代碼內容即可,並保證能正確引用到xhprof_lib.php和xhprof_runs.php。
以yaf入口爲例:
<?php
//header
xhprof_enable(XHPROF_FLAGS_CPU + XHPROF_FLAGS_MEMORY);
//code
define('APPLICATION_PATH', dirname(__FILE__)."/../");
$application = new Yaf_Application( APPLICATION_PATH . "/conf/application.ini");
$application->bootstrap()->run();
//footer
$XHPROF_PATH = APPLICATION_PATH . "library/ThirdParty/";
include_once $XHPROF_PATH . 'xhprof_lib/utils/xhprof_lib.php';
include_once $XHPROF_PATH . 'xhprof_lib/utils/xhprof_runs.php';
$profiler_namespace = 'test';
$xhprof_data = xhprof_disable();
$xhprof_runs = new XHProfRuns_Default();
$run_id = $xhprof_runs->save_run($xhprof_data, $profiler_namespace);
?>
總結
以上測試的話可在開發測試環境搞一下即可,上線的話還是要把代碼刪除的。
- 如果遇到錯誤 failed to execute cmd: " dot -Tpng". stderr: ` (process:24220): Pango-WARNING **: Invalid UTF-8 string passed to pango_layout_set_text() '。暫時不清楚怎麼解決,可以選擇避開它。將 xhprof_lib/utils/callgraph_utils.php 的 121,122 行的打印和 exit 註釋掉。
- 如果遇到錯誤 Error: either we can not find profile data for run_id xxx or the threshold 0.01 is too small or you do not have 'dot' image generation utility installed,無法生成 png 圖片,可能是因爲生成的文件中有不能識別的字符,修復如下3:
$cmd = " dot -T".$type; // 在 cmd 之後添加一個轉碼工作就可以了 $dot_script = iconv("UTF-8", "ASCII//IGNORE", $dot_script);
- 如果想要更好看的 UI,可以參考以下鏈接1,鏈接2,鏈接3 (手動搭免費)。
- 下面是一些參數說明
性能點 | 描述 |
---|---|
Inclusive Time | 包括子函數所有執行時間 |
Exclusive Time/Self Time | 函數執行本身花費的時間,不包括子樹執行時間 |
Wall Time | 花去了的時間或掛鐘時間 |
CPU Time | 用戶耗的時間 + 內核耗的時間 |
Inclusive CPU | 包括子函數一起所佔用的 CPU |
Exclusive CPU | 函數自身所佔用的 CPU |
-
xhprof issue 和某博文提到了一些替代 repo,除此之外還有 tideways 等 ↩
-
nginx 的配置參考了 某 gitbook 和 nginx wiki ↩