当前位置: 首页 > 架构相关 > xhprof 在线上生产项目中的使用思路、方法

xhprof 在线上生产项目中的使用思路、方法

之前的一片文章xhprof安装、搭建详细笔记简单的介绍了xhprof并介绍了xhprof的安装与调试方法,这篇文章主要讲在线上生产项目搭建xhprof的思路与具体实现方法,最后的实现效果点击 xhprof监控实例 查看。

需要知道的两个点:

①php.ini 中有个配置项 auto_prepend_file = "xxxxx"

使用require()将页眉和脚注加入到每个页面中,除了传统的直接require/include以外,还有一种办法,就是使用配置文件php.ini中的两个选项auto_prepend_file和auto_append_file。

通过这两个选项来设置页眉和脚注,可以保证它们在每个页面的前后被载入。

②函数register_shutdown_function

可以在脚本执行完毕后回调某个函数


可能出现的错误:

用php.ini的auto_prepend_file方法后可能会出现无法画图,程序画图后就显示一个加载图片失败的缩略图。我在两台服务器上都试过了,一台是可以的,但是另一台不可以,所以上面才说'可能'。我一直在找源码的问题。我在第二台台服务器重新试了一遍才发现问题的根源。只要使用了auto_prepend_file,只有重新启动php-fpm后画图才正常,但是我的xhprof.php中没有输出内容,不应该是因为有输出内容造成的画图失败。nginx中用fastcgi_param PHP_VALUE "auto_prepend_file=/xxx/xhprof.php";也是同样的问题。一个比较好的解决方法就是在入口文件中include,这种方式没有问题,而且可以根据项目来定制监控。其实也可以忽略这种无法画图的问题,因为监控服务器与业务服务器分开着,监控服务器不需要进行代码分析,所以业务服务器不需要画图,只需要拿数据。

思路:

监控服务器上布置xhprof的实现与要求:

监控代码是以内部私有云方式发布,可以理解为代码是无状态的,以监控服务器上的代码为起点,将代码同步分发到每一台业务服务器上。这也是为什么xhprof产生的分析数据不采用缓存形式而是要保存到数据库里面的原因。所有的分析都是在监控服务器上完成,业务生产服务器只需要安装xhprof扩展,然后在入口文件引入一段监控代码或者直接将监控代码注册到配置文件中即可。做到监控统一管理,完全不影响业务。

xhprof在生产项目中的代码部署逻辑:

监控代码是独立的一个文件,是所有项目的入口文件,这个文件将随机的抽取脚本进行分析,可设置随机生成监控数据的频率,比如设置rand,大概500次访问,只生成一次分析数据,避免频繁读写数据库。可根据参数设定某次访问为强制生成分析数据,以便保证可控性。分析的数据都放在数据库中,所以需要建立两张表,表xhprof_date存储所访问url的基本信息,另一张表xhdata_date存分析数据,两张表结构查看下面的xhprof.php文件。这样做的目的是可以对多台服务器脚本进行分析,以便统一进行管理。然后建立一个列表页将分析过的脚本列出来,并在列表页面给出每个脚本的性能页面和性能图片页面的超链接,点击即可查看分析结果。具体步骤如下:

第一步:

在xhprof监控项目中创建xhprof.php 文件,该文件是所有程序入口文件,作用就是将分析数据入库。该页面需要在php.ini中进行配置,以便作为入口。

auto_prepend_file = "/www/eagle/xhprof/xhprof.php"

如果是nginx的话也可以 

fastcgi_param PHP_VALUE "auto_prepend_file=/www/xhprof.php";//可以针对具体项目监控

如果上面的两种方式,运行后能出现report报告页面但是无法加载画图分析,那就根据项目在入口文件处 include这个文件,总有一种解决方法。读代码就是屡思路,是吧,难道不是吗?监控代码如下(代码格式有点乱,但是没有错误,复制后可以去这里http://web.chacuo.net/formatphp美化一下):

<?php
// +----------------------------------------------------------------------+
// | @author hongbo819@163.com                                            |
// +----------------------------------------------------------------------+
$seed = 100;
if (isset($_GET['_xhprofFileInfo'])) {
    switch ($_GET['_xhprofFileInfo']) {
        case 'lines':
            //所运行的文件有多少行
            echo count(file($_SERVER['SCRIPT_FILENAME']));
            exit;
            break;

        case 'includes':
            //所允许的脚本 require/include文件总个数
            ob_start(create_function('', '$_=get_included_files();
            array_shift($_);return count($_);'));
            break;

        case 'all_lines':
            //本文件行数+include/require文件的总行数
            ob_start(create_function('', 
            '$_=get_included_files();array_shift($_);
            $l=0;foreach($_ as $f){$l+=count(file($f));}
            return $l;'));
            break;
    }
} else if ((isset($_GET['_xhprofForceEnable']) 
&& $_SERVER['REMOTE_ADDR'] == '210.51.19.2') || !rand(0, $seed)) {
    if (isset($_GET['_xhprofForceEnable']) && $_GET['_xhprofForceEnable'] > 1) {
        ob_start(); //防止header前的输出程序导致错误
        
    }
    xhprof_enable();
    function _xhprofCallback() {
        error_reporting(0);
        set_time_limit(1);
        $xhprofData = xhprof_disable();
        $data = serialize($xhprofData);
        $dbcon = mysql_connect('127.0.0.1', 'puser', '');
        if (!is_resource($dbcon)) exit;
        mysql_select_db('ljl_eagleeye', $dbcon) or exit;
        $date = date('Ymd');
        $id = uniqid();
        $ip = isset($_SERVER['SERVER_ADDR']) ? $_SERVER['SERVER_ADDR'] : '';
        $host = isset($_SERVER['HTTP_HOST']) ? $_SERVER['HTTP_HOST'] : $ip;
        $file = $_SERVER['SCRIPT_FILENAME'];
        $phpself = $_SERVER['PHP_SELF'];
        $uri = $_SERVER['REQUEST_URI'];
        $method = isset($_SERVER['REQUEST_METHOD']) 
        ? $_SERVER['REQUEST_METHOD'] : 'CLI';
        $sql = sprintf("INSERT IGNORE INTO `xhdata_%d` 
        (`id`,`data`) VALUES ('%s','%s')", $date, $id, mysql_escape_string($data));
        if (mysql_query($sql, $dbcon)) {
            $totalTime = array_pop($xhprofData);
            $sql = sprintf("INSERT IGNORE INTO `xhprof_%d` 
            (`id`,`ip`,`host`,`file`,`phpself`,`uri`,`method`,`time`,`wt`) 
            VALUES ('%s','%s','%s','%s','%s','%s','%s',%d, %d)", 
            $date, $id, $ip, $host, mysql_escape_string($file) ,
             mysql_escape_string($phpself) , mysql_escape_string($uri) ,
              mysql_escape_string($method) , time() , $totalTime['wt']);
            if (!mysql_query($sql, $dbcon) && mysql_errno($dbcon) == 1146) {
                $sql = sprintf("create table if not exists `xhprof_%d`
                                                (
                                                 `id` varchar(14) not null,
                                                 `ip` varchar(15) not null,
                                                 `host` varchar(32) not null,
                                                 `file` varchar(255) not null,
                                                 `phpself` varchar(255) not null,
                                                 `uri`  varchar(255) not null,
                                                 `method` varchar(4) not null,
                                                 `time` int(10) not null,
                                                 `data` longtext not null,
                                                 `ct` int(10) not null,
                                                 `wt` bigint(15) not null,
                                                 primary key(`id`),
                                                 key `host` (`host`)
                                                ) engine=myisam", $date);
                mysql_query($sql, $dbcon);
                $sql = sprintf("INSERT IGNORE INTO `xhprof_%d` 
                (`id`,`ip`,`host`,`file`,`phpself`,`uri`,`method`,`time`,`wt`)
                 VALUES ('%s','%s','%s','%s','%s','%s','%s',%d,%d)", 
                 $date, $id, $ip, $host, mysql_escape_string($file) , 
                 mysql_escape_string($phpself) , mysql_escape_string($uri) , 
                 mysql_escape_string($method) , time() , $totalTime['wt']);
                mysql_query($sql, $dbcon);
            }
        } else if (mysql_errno($dbcon) == 1146) {
            $sql = sprintf("create table if not exists `xhdata_%d`
                                        (
                                         `id` varchar(14) not null,
                                         `data` longtext not null,
                                         primary key(`id`)
                                        ) engine=myisam", $date);
            mysql_query($sql, $dbcon);
            $sql = sprintf("INSERT IGNORE INTO `xhdata_%d` 
            (`id`,`data`) VALUES ('%s','%s')", $date, $id, 
            mysql_escape_string($data));
            mysql_query($sql, $dbcon);
        }
        if (isset($_GET['_xhprofForceEnable']) && $_GET['_xhprofForceEnable'] > 1) {
            ob_clean();
            if ($_GET['_xhprofForceEnable'] == 2) { //报告页
header("Location: http://xhprof.eagle.zhbor.com/html/report.php
                ?source=$date&ip=$ip&host=$host&run=$id");
                exit;
            }
            if ($_GET['_xhprofForceEnable'] == 3) { //分析图
header("Location: http://xhprof.eagle.zhbor.com/html/callgraph.php?source=$date&run=$id");
                exit;
            }
        }
    }
    register_shutdown_function('_xhprofCallback');
}
?>

$xhprof_data = xhprof_disable(); $xhprof_data中的子数组是这个样子的:array('ct'=>3, 'wt'=>5),其中ct代表的是该函数调用的次数,wt是该函数调用所用的时间。ct需要累加,wt在数组最后一项有总时间。

第二步:

创建list.php文件 , 该文件的作用是列出脚本列表,并给出每个脚本列表的性能分析页面和性能图片页面的url。缓存文件的生成就是在这里生成,查看某个页面分析才会生成这个页面的缓存分析数据文件,你应该知道为什么。思路都在代码里,代码如下:

<?php
// +----------------------------------------------------------------------+
// | @author hongbo819@163.com                                            |
// +----------------------------------------------------------------------+
include_once "./xhprof_lib/utils/xhprof_lib.php"; //应用程序所在的目录
include_once "./xhprof_lib/utils/xhprof_runs.php"; //
$dataDir = ini_get("xhprof.output_dir");
$date = $_GET['source'] ? $_GET['source'] : date('Ymd');
$dbcon = mysql_connect('127.0.0.1', 'puser', '');
mysql_select_db('ljl_eagleeye', $dbcon) or exit;
if ($_GET['action'] === 'report' || $_GET['action'] === 'graph') {
    $xhprofDir = $dataDir . '/' . $date; //设置文件存放目录
    make_dir($xhprofDir);
    $xhprof_runs = new XHProfRuns_Default($xhprofDir);
    $sql = "select data from xhdata_{$date} where id='{$_GET['run']}'";
    $res = mysql_query($sql);
    $xhprofDate = unserialize(mysql_result($res, 0));
    $xhprof_runs->save_run($xhprofDate, $date, $_GET['run']);
    if ($_GET['action'] === 'report') {
        header("Location: http://xhprof.eagle.zhbor.com/xhprof/html/report.php
        ?run={$_GET['run']}&source=$date");
        exit;
    } else {
        header("Location: http://xhprof.eagle.zhbor.com/xhprof/html/callgraph.php
        ?run={$_GET['run']}&source=$date");
        exit;
    }
}
$sql = "select * from xhprof_{$date} order by wt desc limit 20";
$res = mysql_query($sql);
if ($res && mysql_num_rows($res)) {
    $str = "<table border=\"1\" cellspacing=\"0\" cellpadding=\"10\">
    <tr><th>服务器ip</th><th>>域名</th><th>php文件</th><th>uri</th>
    <th>访问时间</th><th>报告连接</th><th>分析图连接</th><th>用时排行</th></tr>";
    while ($row = mysql_fetch_assoc($res)) {
$htmlUrl = "http://xhprof.eagle.zhbor.com/xhprof/list.php
?action=report&run={$row['id']}&source=$date";
$graphUrl = "http://xhprof.eagle.zhbor.com/xhprof/list.php
?action=graph&run={$row['id']}&source=$date";
        $str.= "<tr><td>{$row['ip']}</td><td>{$row['host']}</td>
        <td>{$row['phpself']}</td><td>{$row['uri']}</td><td>" 
        . date('Y-m-d H:i:s', $row['time']) . 
        "</td><td><a href='{$htmlUrl}'>查看报告</a></td>
        <td><a href='{$graphUrl}'>查看分析图</a></td><td>{$row['wt']}</td></tr>";        
    }
    $str.= "</table>";
    echo $str;
}

function make_dir($dir){
     $dir = rtrim($dir, '/').'/';
      if(!file_exists($dir)){
            @mkdir($dir,0777,true);
            @chmod($dir,0777);
            @chmod(dirname($dir),0777);
      }
}

第三步:

创建性能分析的脚本report.php和callgraph.php,这两个脚本是xhprof原有的脚本,在xhprof_html文件夹中对应的是index.php和callgraph.php,分别生成性能页面和性能图片页面。这两个文件里面唯一修改的地方就是xhprofDir ,将这个xhprofDir修改成上面list.php文件中生成的目录。至此xhprof搭建全部完成。点击查看监控效果:xhprof监控实例

callgraph生成的效果图还是挺震撼的:

以下是一些效果图:

本服务器上xhprof目录格式:

xhprof分析列表页效果图:

点击列表页的查看报告,生成xhprof分析报告页效果图:

点击报告页面的view Full callgraph或者点击列表页的查看分析图,查看xhprof性能分析直观效果图:


转载时请以 超链接的形式 注明:转自Ferman

                  

About me