飞道的博客

Swoole整合PHP性能分析平台: Tideways+Xhgui

268人阅读  评论(0)

求打赏

写文章不易!如果你觉得文章有价值,动动手指【打赏】一个吧!

简介

最近在公司使用EasySwoole开发一个项目, 发现一些性能问题, 想安装个PHP性能调试工具监控调试一下,看看性能差在哪里。

找到了一个Facebook开源的PHP性能分析工具Xhprof。Xhprof可以报告函数级别的请求次数和各种指标,包括阻塞时间,CPU时间和内存使用情况。但是Facebook官方已经很久不更新,官方源已经显示This package is abandoned and no longer maintained(此包已废弃,不再维护),Xhprof已不支持PHP7.x以上版本。而且发现XHProf与Swoole也不兼容。
其实Swoole官方提供了一个工具:Swoole Tracker,但是这个不开源,是收费的。根据官方介绍,其实Swoole Tracker也集成了Facebook的Xhprof工具。这里要批一下国内的开源社区,发现大部分国内的开源项目都是部分开源,部分收费。更可恶的是明明集成了人家的开源项目,居然还要用于商用。

快要绝望之际,突然发现了另一个开源项目Tideways,兼容PHP7.X和Swoole,Tideways由商业公司在维护,虽然这个项目也有收费版和免费版,但是免费版基本够用。另外,我们还需结合免费的Xhprof的UI程序Xhgui
PS: 由于Xhgui官方版已经很久不更新,只支持PHP5.X,也是不支持PHP7.X,而且很多符号和单位都不适合中国用户。这里使用国人维护的一个汉化的版本,并且坚持在更新。

搭建平台

安装tideways扩展

git clone https://github.com/tideways/php-xhprof-extension.git
cd php-profiler-extension
phpize
./configure --with-php-config=/usr/local/php7/bin/php-config
make && make install

编译完成后在php.ini中加入

extension=tideways_xhprof.so

查看是否安装成功

php --ri tideways_xhprof


由于Xhgui使用了mongodb作为数据库存储数据,所以需要安装Mongodb和PHP mongodb扩展

安装mongodb扩展

访问https://pecl.php.net/package/mongodb下载对应PHP版本的拓展,我这里是PHP7,所以下载最新版就可以了。
注意:由于xhgui要求MongoDB扩展要1.3.0以上,MongoDb要求2.2.0以上。

MongoDB Extension MongoDB PHP driver. XHGui requires verison 1.3.0 or later.
MongoDB MongoDB Itself. XHGui requires version 2.2.0 or later.


编译安装

wget https://pecl.php.net/get/mongodb-1.6.1.tgz
tar -zxvf mongodb-1.6.1.tgz
cd mongodb-1.6.1
phpize
./configure --with-php-config=/usr/local/php7/bin/php-config
make && make install

编译完成后在php.ini中加入

extension=mongodb.so

查看是否安装成功

安装Mongodb

wget https://fastdl.mongodb.org/linux/mongodb-linux-x86_64-rhel62-4.0.1.tgz
mv mongodb-linux-x86_64-rhel62-4.0.1 /usr/local/mongodb

创建数据库存放目录和日志存放文件

mkdir -p /data/mongo/data
touch /data/mongo/dblogs

启动MongoDB

/usr/local/mongodb/bin/mongod --dbpath=/data/mongo/data --logpath=/data/mongo/dblogs --logappend --fork

设置开机自启

echo /usr/local/mongodb/bin/mongod --dbpath=/data/mongo/data --logpath=/data/mongo/dblogs--logappend --fork >>/etc/rc.local

查看是否启动

重点来了

安装Xhgui

安装Xhgui需要首先安装Composer包管理工具,这里不详术,可以我的另一篇博文:Linux下安装Composer

git clone https://github.com/laynefyc/xhgui-branch.git
cd xhgui-branch
php install.php


等待安装完毕,下一步需要修改一下配置文件:主要修改extension,如果你的mongodb服务不是本机,那么也要修改一下。

'mode' => 'development',
    /*
     * support extension: uprofiler, tideways_xhprof, tideways, xhprof
     * default: xhprof
     */
    'extension' => 'tideways_xhprof', //主要修改这里

    // Can be either mongodb or file.
    /*
    'save.handler' => 'file',
    'save.handler.filename' => dirname(__DIR__) . '/cache/' . 'xhgui.data.' . microtime(true) . '_' . substr(md5($url), 0, 6),
    */
    'save.handler' => 'mongodb',

    // Needed for file save handler. Beware of file locking. You can adujst this file path
    // to reduce locking problems (eg uniqid, time ...)
    //'save.handler.filename' => __DIR__.'/../data/xhgui_'.date('Ymd').'.dat',
    'db.host' => 'mongodb://127.0.0.1:27017',
    'db.db' => 'xhprof',

测试MongoDB连接情况并优化索引;

当前机器安装过mongo客户端才能调用mongo命令,mongo客户端的安装方法第四步有详细说明。

$ /usr/local/mongodb/bin/mongo
> use xhprof
> db.results.ensureIndex( { 'meta.SERVER.REQUEST_TIME' : -1 } )
> db.results.ensureIndex( { 'profile.main().wt' : -1 } )
> db.results.ensureIndex( { 'profile.main().mu' : -1 } )
> db.results.ensureIndex( { 'profile.main().cpu' : -1 } )
> db.results.ensureIndex( { 'meta.url' : 1 } )

nginx配置访问xhgui站点

server {
        listen       80;
        listen  9920;
        server_name  test.xhgui.com;

        root   /data/wwwroot/xhgui-branch/webroot;
        index  index.html index.htm index.php;

        #charset koi8-r;
        access_log /dev/null;
        location / {
            if (!-e $request_filename) {
                rewrite . /index.php  last;
                break;
            }
        }

        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   html;
        }
        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   html;
        }

        location ~ \.php$ {
            access_log  /data/logs/web/test.xhgui.com.access.log  main;
            fastcgi_pass   127.0.0.1:9000;
            fastcgi_index  index.php;
            fastcgi_param  SCRIPT_FILENAME  $document_root$fastcgi_script_name;
            include        fastcgi_params;
        }

    }

重新加载配置

/usr/local/nginx/sbin/nginx -s reload -c /usr/local/nginx/conf/nginx.conf

这个时候已经可以访问xhgui界面了

重点的重点来了

整合tyideways到EasySwoole

网上找到很多教程都是说修改Nginx配置,如下面的内容:
注意:对于Swoole并不适用,因为Swoole是独立启动的,可不经过Nginx。

server {
  listen 80;
  server_name site.localhost;
  root /data/wwwroot/myweb/;
  fastcgi_param PHP_VALUE "auto_prepend_file=/data/wwwroot/xhgui-branch/external/header.php";
}

这只适合普通的使用Nginx作为服务器的PHP项目。

真正解决方案:

修改EasySwooleEvent.php的:onRequest

public static function onRequest(Request $request, Response $response): bool
    {
        //'===========开启分析生成文件==========='

        if (extension_loaded('uprofiler')) {
            uprofiler_enable(UPROFILER_FLAGS_CPU | UPROFILER_FLAGS_MEMORY);
        } else if (extension_loaded('tideways_xhprof')) {
            tideways_xhprof_enable(TIDEWAYS_XHPROF_FLAGS_MEMORY | TIDEWAYS_XHPROF_FLAGS_MEMORY_MU | TIDEWAYS_XHPROF_FLAGS_MEMORY_PMU | TIDEWAYS_XHPROF_FLAGS_CPU);
        } else if (extension_loaded('tideways')) {
            tideways_enable(TIDEWAYS_FLAGS_CPU | TIDEWAYS_FLAGS_MEMORY);
            tideways_span_create('sql');
        } else if(function_exists('xhprof_enable')){
            if (PHP_MAJOR_VERSION == 5 && PHP_MINOR_VERSION > 4) {
                xhprof_enable(XHPROF_FLAGS_CPU | XHPROF_FLAGS_MEMORY | XHPROF_FLAGS_NO_BUILTINS);
            } else {
                xhprof_enable(XHPROF_FLAGS_CPU | XHPROF_FLAGS_MEMORY);
            }
        }else{
            throw new Exception("Please check the extension name in config/config.default.php \r\n,you can use the 'php -m' command.", 1);
        }
        return true;
    }

修改EasySwooleEvent.php的:afterRequest

public static function afterRequest(Request $request, Response $response): void
    {
        //'===========性能分析生成文件==========='

        $server = ServerManager::getInstance()->getSwooleServer();

        $_SERVER = $request->getServerParams();
        $_SERVER = array_change_key_case($_SERVER, CASE_UPPER);
        $_GET = $request->getQueryParams();
        $_ENV = null;

        if (extension_loaded('uprofiler')) {
            $data['profile'] = uprofiler_disable();
        } else if (extension_loaded('tideways_xhprof')) {
            $data['profile'] = tideways_xhprof_disable();
        } else if (extension_loaded('tideways')) {
            $data['profile'] = tideways_disable();
            $sqlData = tideways_get_spans();
            $data['sql'] = array();
            if(isset($sqlData[1])){
                foreach($sqlData as $val){
                    if(isset($val['n'])&&$val['n'] === 'sql'&&isset($val['a'])&&isset($val['a']['sql'])){
                        $_time_tmp = (isset($val['b'][0])&&isset($val['e'][0]))?($val['e'][0]-$val['b'][0]):0;
                        if(!empty($val['a']['sql'])){
                            $data['sql'][] = [
                                'time' => $_time_tmp,
                                'sql' => $val['a']['sql']
                            ];
                        }
                    }
                }
            }
        } else {
            $data['profile'] = xhprof_disable();
        }

        $uri = array_key_exists('REQUEST_URI', $_SERVER)
            ? $_SERVER['REQUEST_URI']
            : null;
        if (empty($uri) && isset($_SERVER['argv'])) {
            $cmd = basename($_SERVER['argv'][0]);
            $uri = $cmd . ' ' . implode(' ', array_slice($_SERVER['argv'], 1));
        }

        $time = array_key_exists('REQUEST_TIME', $_SERVER)
            ? $_SERVER['REQUEST_TIME']
            : time();
        $requestTimeFloat = explode('.', $_SERVER['REQUEST_TIME_FLOAT']);
        if (!isset($requestTimeFloat[1])) {
            $requestTimeFloat[1] = 0;
        }

        $requestTs = array('sec' => $time, 'usec' => 0);
        $requestTsMicro = array('sec' => $requestTimeFloat[0], 'usec' => $requestTimeFloat[1]);

        $uri .= strrpos($uri,'?')===false?'?'.http_build_query($_GET):http_build_query($_GET);

        $data['meta'] = array(
            'url' => $uri,
            'SERVER' => $_SERVER,
            'get' => $_GET,
            'env' => $_ENV,
            'simple_url' => preg_replace('/\=\d+/', '', $uri),
            'request_ts' => $requestTs,
            'request_ts_micro' => $requestTsMicro,
            'request_date' => date('Y-m-d', $time),
        );

        //print_r($data);

        $dirname = './Log/xhprof';
        if (!is_dir($dirname)){
            mkdir($dirname,0755,true);
        }
        $filename = $dirname.'/'.date('Ymd').'.xhprof';
        file_put_contents($filename, json_encode($data)."\n", FILE_APPEND);
    }

其实我只是把 Xhgui的 external/header.php文件内容拷贝出来,然后修改一下兼容Swoole而已。
配置完毕,重启EasySwoole

php easyswoole start

这个时候访问接口,会在EasySwoole的/Log/xhprof目录生成一些日志文件。

导入日志

使用xhgui的导入工具,导入生成的日志到xhgui。

php import.php -f=/data/wwwroot/myweb/Log/xhprof/20200111.xhprof

导入成功后,再次访问Xhgui平台,可以看到有数据了:

点击任意一条记录的方法POST或GET:

点击查看火焰图:

哇哇哇。。。给自己鼓掌!!欢呼吧,折腾了两天终于成功搭建好了。特意记录下来,供朋友们参考!

写文章不易,谢谢你的支持!


转载:https://blog.csdn.net/uisoul/article/details/103936873
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场