├── webman.png ├── .gitignore ├── webman-gpl.png ├── displaying_top.png ├── src ├── config │ └── plugin │ │ └── tinywan │ │ └── xhprof │ │ ├── app.php │ │ └── process.php ├── xhprof │ ├── xhprof_html │ │ ├── jquery │ │ │ ├── indicator.gif │ │ │ ├── jquery.tooltip.css │ │ │ ├── jquery.autocomplete.css │ │ │ ├── jquery.tooltip.js │ │ │ └── jquery.autocomplete.js │ │ ├── docs │ │ │ ├── sample-flat-view.jpg │ │ │ ├── sample-callgraph-image.jpg │ │ │ ├── sample-parent-child-view.jpg │ │ │ ├── sample-diff-report-flat-view.jpg │ │ │ ├── sample-diff-report-parent-child-view.jpg │ │ │ └── index.html │ │ ├── typeahead.php │ │ ├── css │ │ │ └── xhprof.css │ │ ├── index.php │ │ ├── callgraph.php │ │ └── js │ │ │ └── xhprof_report.js │ └── xhprof_lib │ │ ├── display │ │ ├── typeahead_common.php │ │ └── xhprof.php │ │ └── utils │ │ ├── xhprof_runs.php │ │ ├── callgraph_utils.php │ │ └── xhprof_lib.php ├── XhprofMiddleware.php ├── HttpProxy.php └── Install.php ├── composer.json ├── LICENSE └── README.md /webman.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tinywan/webman-xhprof/HEAD/webman.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | vendor 3 | .idea 4 | .vscode 5 | .phpunit* 6 | composer.lock -------------------------------------------------------------------------------- /webman-gpl.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tinywan/webman-xhprof/HEAD/webman-gpl.png -------------------------------------------------------------------------------- /displaying_top.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tinywan/webman-xhprof/HEAD/displaying_top.png -------------------------------------------------------------------------------- /src/config/plugin/tinywan/xhprof/app.php: -------------------------------------------------------------------------------- 1 | true, 4 | ]; -------------------------------------------------------------------------------- /src/xhprof/xhprof_html/jquery/indicator.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tinywan/webman-xhprof/HEAD/src/xhprof/xhprof_html/jquery/indicator.gif -------------------------------------------------------------------------------- /src/xhprof/xhprof_html/docs/sample-flat-view.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tinywan/webman-xhprof/HEAD/src/xhprof/xhprof_html/docs/sample-flat-view.jpg -------------------------------------------------------------------------------- /src/xhprof/xhprof_html/docs/sample-callgraph-image.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tinywan/webman-xhprof/HEAD/src/xhprof/xhprof_html/docs/sample-callgraph-image.jpg -------------------------------------------------------------------------------- /src/xhprof/xhprof_html/docs/sample-parent-child-view.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tinywan/webman-xhprof/HEAD/src/xhprof/xhprof_html/docs/sample-parent-child-view.jpg -------------------------------------------------------------------------------- /src/xhprof/xhprof_html/docs/sample-diff-report-flat-view.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tinywan/webman-xhprof/HEAD/src/xhprof/xhprof_html/docs/sample-diff-report-flat-view.jpg -------------------------------------------------------------------------------- /src/xhprof/xhprof_html/docs/sample-diff-report-parent-child-view.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tinywan/webman-xhprof/HEAD/src/xhprof/xhprof_html/docs/sample-diff-report-parent-child-view.jpg -------------------------------------------------------------------------------- /src/config/plugin/tinywan/xhprof/process.php: -------------------------------------------------------------------------------- 1 | [ 12 | 'handler'=> \Tinywan\Xhprof\HttpProxy::class, 13 | 'listen' => 'http://0.0.0.0:8686', 14 | 'count' => 1, 15 | ] 16 | ]; -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tinywan/webman-xhprof", 3 | "type": "library", 4 | "license": "MIT", 5 | "description": "Webman plugin tinywan/webman-xhprof", 6 | "require": { 7 | "ext-xhprof": "*", 8 | "php": ">=7.2", 9 | "workerman/webman-framework": "^1.4.1" 10 | }, 11 | "autoload": { 12 | "psr-4": { 13 | "Tinywan\\Xhprof\\": "src" 14 | } 15 | }, 16 | "repositories": { 17 | "packagist": { 18 | "type": "composer", 19 | "url": "https://mirrors.aliyun.com/composer/" 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /src/xhprof/xhprof_html/jquery/jquery.tooltip.css: -------------------------------------------------------------------------------- 1 | /* 2 | * jQuery Tooltip plugin 1.3 3 | * 4 | * http://bassistance.de/jquery-plugins/jquery-plugin-tooltip/ 5 | * http://docs.jquery.com/Plugins/Tooltip 6 | * 7 | * Copyright (c) 2006 - 2008 Jörn Zaefferer 8 | * 9 | * $Id$ 10 | * 11 | * Dual licensed under the MIT and GPL licenses: 12 | * http://www.opensource.org/licenses/mit-license.php 13 | * http://www.gnu.org/licenses/gpl.html 14 | */ 15 | 16 | #tooltip { 17 | position: absolute; 18 | z-index: 3000; 19 | border: 1px solid #111; 20 | background-color: lightyellow; 21 | padding: 5px; 22 | opacity: 0.9; 23 | } 24 | #tooltip h3, #tooltip div { margin: 0; } 25 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 webman 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/xhprof/xhprof_html/typeahead.php: -------------------------------------------------------------------------------- 1 | get('xhprof', 0); 29 | $extension = extension_loaded('xhprof'); 30 | if ($xhprof && $extension) { 31 | include_once "xhprof/xhprof_lib/utils/xhprof_lib.php"; 32 | include_once "xhprof/xhprof_lib/utils/xhprof_runs.php"; 33 | xhprof_enable(XHPROF_FLAGS_NO_BUILTINS + XHPROF_FLAGS_CPU + XHPROF_FLAGS_MEMORY); 34 | } 35 | 36 | $response = $handler($request); 37 | if ($xhprof && $extension) { 38 | $data = xhprof_disable(); 39 | $objXhprofRun = new \XHProfRuns_Default(); 40 | $objXhprofRun->save_run($data, sprintf('%s', date("YmdHis"))); 41 | } 42 | 43 | return $response; 44 | } 45 | } -------------------------------------------------------------------------------- /src/xhprof/xhprof_html/jquery/jquery.autocomplete.css: -------------------------------------------------------------------------------- 1 | /* 2 | * Autocomplete - jQuery plugin 1.0.2 3 | * 4 | * Copyright (c) 2007 Dylan Verheul, Dan G. Switzer, Anjesh Tuladhar, Jörn Zaefferer 5 | * 6 | * Dual licensed under the MIT and GPL licenses: 7 | * http://www.opensource.org/licenses/mit-license.php 8 | * http://www.gnu.org/licenses/gpl.html 9 | * 10 | * Revision: $Id$ 11 | * 12 | */ 13 | 14 | .ac_results { 15 | padding: 0px; 16 | border: 1px solid black; 17 | background-color: white; 18 | overflow: hidden; 19 | z-index: 99999; 20 | } 21 | 22 | .ac_results ul { 23 | width: 100%; 24 | list-style-position: outside; 25 | list-style: none; 26 | padding: 0; 27 | margin: 0; 28 | } 29 | 30 | .ac_results li { 31 | margin: 0px; 32 | padding: 2px 5px; 33 | cursor: default; 34 | display: block; 35 | /* 36 | if width will be 100% horizontal scrollbar will apear 37 | when scroll mode will be used 38 | */ 39 | /*width: 100%;*/ 40 | font: menu; 41 | font-size: 12px; 42 | /* 43 | it is very important, if line-height not setted or setted 44 | in relative units scroll will be broken in firefox 45 | */ 46 | line-height: 16px; 47 | overflow: hidden; 48 | } 49 | 50 | .ac_loading { 51 | background: white url('indicator.gif') right center no-repeat; 52 | } 53 | 54 | .ac_odd { 55 | background-color: #eee; 56 | } 57 | 58 | .ac_over { 59 | background-color: #0A246A; 60 | color: white; 61 | } 62 | -------------------------------------------------------------------------------- /src/HttpProxy.php: -------------------------------------------------------------------------------- 1 | 'api.ai.com', 32 | 'discord.example.com' => 'discord.com', 33 | 'cdn.example.com' => 'cdn.discordapp.com', 34 | 'gateway.example.com' => 'gateway.discord.gg', 35 | ]; 36 | $host = $request->host(true); 37 | if (!isset($replace[$host])) { 38 | return $connection->send(response('404 not found', 404)); 39 | } 40 | $host = $replace[$host]; 41 | $buffer = (string)$request; 42 | $con = new AsyncTcpConnection("tcp://$host:443", ['ssl' =>[ 43 | 'verify_peer' => false 44 | ]]); 45 | $buffer = preg_replace("/Host: ?(.*?)\r\n/", "Host: $host\r\n", $buffer); 46 | $con->transport = 'ssl'; 47 | $connection->protocol = null; 48 | $con->send($buffer); 49 | $con->pipe($connection); 50 | $connection->pipe($con); 51 | $con->connect(); 52 | } 53 | } -------------------------------------------------------------------------------- /src/xhprof/xhprof_html/css/xhprof.css: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2009 Facebook 2 | * 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | 16 | td.sorted { 17 | color:#0000FF; 18 | } 19 | 20 | td.vbar, th.vbar { 21 | text-align: right; 22 | border-left: 23 | solid 1px #bdc7d8; 24 | } 25 | 26 | td.vbbar, th.vbar { 27 | text-align: right; 28 | border-left: 29 | solid 1px #bdc7d8; 30 | color:blue; 31 | } 32 | 33 | /* diff reports: display regressions in red */ 34 | td.vrbar { 35 | text-align: right; 36 | border-left:solid 1px #bdc7d8; 37 | color:red; 38 | } 39 | 40 | /* diff reports: display improvements in green */ 41 | td.vgbar { 42 | text-align: right; 43 | border-left: solid 1px #bdc7d8; 44 | color:green; 45 | } 46 | 47 | td.vwbar, th.vwbar { 48 | text-align: right; 49 | border-left: solid 1px white; 50 | } 51 | 52 | td.vwlbar, th.vwlbar { 53 | text-align: left; 54 | border-left: solid 1px white; 55 | } 56 | 57 | p.blue { 58 | color:blue 59 | } 60 | 61 | .bubble { 62 | background-color:#C3D9FF 63 | } 64 | 65 | ul.xhprof_actions { 66 | float: right; 67 | padding-left: 16px; 68 | list-style-image: none; 69 | list-style-type: none; 70 | margin:10px 10px 10px 3em; 71 | position:relative; 72 | } 73 | 74 | ul.xhprof_actions li { 75 | border-bottom:1px solid #D8DFEA; 76 | } 77 | 78 | ul.xhprof_actions li a:hover { 79 | background:#3B5998 none repeat scroll 0 0; 80 | color:#FFFFFF; 81 | } 82 | -------------------------------------------------------------------------------- /src/Install.php: -------------------------------------------------------------------------------- 1 | 'config/plugin/tinywan/xhprof' 20 | ]; 21 | 22 | /** 23 | * Install 24 | * @return void 25 | */ 26 | public static function install() 27 | { 28 | static::installByRelation(); 29 | } 30 | 31 | /** 32 | * Uninstall 33 | * @return void 34 | */ 35 | public static function uninstall() 36 | { 37 | self::uninstallByRelation(); 38 | } 39 | 40 | /** 41 | * installByRelation 42 | * @return void 43 | */ 44 | public static function installByRelation() 45 | { 46 | foreach (static::$pathRelation as $source => $dest) { 47 | if ($pos = strrpos($dest, '/')) { 48 | $parent_dir = base_path().'/'.substr($dest, 0, $pos); 49 | if (!is_dir($parent_dir)) { 50 | mkdir($parent_dir, 0777, true); 51 | } 52 | } 53 | copy_dir(__DIR__ . "/$source", base_path()."/$dest"); 54 | echo "Create $dest"; 55 | } 56 | } 57 | 58 | /** 59 | * uninstallByRelation 60 | * @return void 61 | */ 62 | public static function uninstallByRelation() 63 | { 64 | foreach (static::$pathRelation as $source => $dest) { 65 | $path = base_path()."/$dest"; 66 | if (!is_dir($path) && !is_file($path)) { 67 | continue; 68 | } 69 | echo "Remove $dest"; 70 | if (is_file($path) || is_link($path)) { 71 | unlink($path); 72 | continue; 73 | } 74 | remove_dir($path); 75 | } 76 | } 77 | 78 | } -------------------------------------------------------------------------------- /src/xhprof/xhprof_lib/display/typeahead_common.php: -------------------------------------------------------------------------------- 1 | array(XHPROF_STRING_PARAM, ''), 32 | 'run' => array(XHPROF_STRING_PARAM, ''), 33 | 'run1' => array(XHPROF_STRING_PARAM, ''), 34 | 'run2' => array(XHPROF_STRING_PARAM, ''), 35 | 'source' => array(XHPROF_STRING_PARAM, 'xhprof'), 36 | ); 37 | 38 | // pull values of these params, and create named globals for each param 39 | xhprof_param_init($params); 40 | 41 | if (!empty($run)) { 42 | 43 | // single run mode 44 | $raw_data = $xhprof_runs_impl->get_run($run, $source, $desc_unused); 45 | $functions = xhprof_get_matching_functions($q, $raw_data); 46 | 47 | } else if (!empty($run1) && !empty($run2)) { 48 | 49 | // diff mode 50 | $raw_data = $xhprof_runs_impl->get_run($run1, $source, $desc_unused); 51 | $functions1 = xhprof_get_matching_functions($q, $raw_data); 52 | 53 | $raw_data = $xhprof_runs_impl->get_run($run2, $source, $desc_unused); 54 | $functions2 = xhprof_get_matching_functions($q, $raw_data); 55 | 56 | 57 | $functions = array_unique(array_merge($functions1, $functions2)); 58 | asort($functions); 59 | } else { 60 | xhprof_error("no valid runs specified to typeahead endpoint"); 61 | $functions = array(); 62 | } 63 | 64 | // If exact match is present move it to the front 65 | if (in_array($q, $functions)) { 66 | $old_functions = $functions; 67 | 68 | $functions = array($q); 69 | foreach ($old_functions as $f) { 70 | // exact match case has already been added to the front 71 | if ($f != $q) { 72 | $functions[] = $f; 73 | } 74 | } 75 | } 76 | 77 | foreach ($functions as $f) { 78 | echo $f."\n"; 79 | } 80 | -------------------------------------------------------------------------------- /src/xhprof/xhprof_html/index.php: -------------------------------------------------------------------------------- 1 | array(XHPROF_STRING_PARAM, ''), 42 | 'wts' => array(XHPROF_STRING_PARAM, ''), 43 | 'symbol' => array(XHPROF_STRING_PARAM, ''), 44 | 'sort' => array(XHPROF_STRING_PARAM, 'wt'), // wall time 45 | 'run1' => array(XHPROF_STRING_PARAM, ''), 46 | 'run2' => array(XHPROF_STRING_PARAM, ''), 47 | 'source' => array(XHPROF_STRING_PARAM, 'xhprof'), 48 | 'all' => array(XHPROF_UINT_PARAM, 0), 49 | ); 50 | 51 | // pull values of these params, and create named globals for each param 52 | xhprof_param_init($params); 53 | 54 | /* reset params to be a array of variable names to values 55 | by the end of this page, param should only contain values that need 56 | to be preserved for the next page. unset all unwanted keys in $params. 57 | */ 58 | foreach ($params as $k => $v) { 59 | $params[$k] = $$k; 60 | 61 | // unset key from params that are using default values. So URLs aren't 62 | // ridiculously long. 63 | if ($params[$k] == $v[1]) { 64 | unset($params[$k]); 65 | } 66 | } 67 | 68 | echo ""; 69 | 70 | echo "XHProf: Hierarchical Profiler Report"; 71 | xhprof_include_js_css(); 72 | echo ""; 73 | 74 | echo ""; 75 | 76 | $vbar = ' class="vbar"'; 77 | $vwbar = ' class="vwbar"'; 78 | $vwlbar = ' class="vwlbar"'; 79 | $vbbar = ' class="vbbar"'; 80 | $vrbar = ' class="vrbar"'; 81 | $vgbar = ' class="vgbar"'; 82 | 83 | $xhprof_runs_impl = new XHProfRuns_Default(); 84 | 85 | displayXHProfReport($xhprof_runs_impl, $params, $source, $run, $wts, 86 | $symbol, $sort, $run1, $run2); 87 | 88 | 89 | echo ""; 90 | echo ""; 91 | -------------------------------------------------------------------------------- /src/xhprof/xhprof_html/callgraph.php: -------------------------------------------------------------------------------- 1 | array(XHPROF_STRING_PARAM, ''), 42 | 43 | // source/namespace/type of run 44 | 'source' => array(XHPROF_STRING_PARAM, 'xhprof'), 45 | 46 | // the focus function, if it is set, only directly 47 | // parents/children functions of it will be shown. 48 | 'func' => array(XHPROF_STRING_PARAM, ''), 49 | 50 | // image type, can be 'jpg', 'gif', 'ps', 'png', 'svg' 51 | 'type' => array(XHPROF_STRING_PARAM, 'svg'), 52 | 53 | // only functions whose exclusive time over the total time 54 | // is larger than this threshold will be shown. 55 | // default is 0.01. 56 | 'threshold' => array(XHPROF_FLOAT_PARAM, 0.01), 57 | 58 | // whether to show critical_path 59 | 'critical' => array(XHPROF_BOOL_PARAM, true), 60 | 61 | // first run in diff mode. 62 | 'run1' => array(XHPROF_STRING_PARAM, ''), 63 | 64 | // second run in diff mode. 65 | 'run2' => array(XHPROF_STRING_PARAM, '') 66 | ); 67 | 68 | // pull values of these params, and create named globals for each param 69 | xhprof_param_init($params); 70 | 71 | // if invalid value specified for threshold, then use the default 72 | if ($threshold < 0 || $threshold > 1) { 73 | $threshold = $params['threshold'][1]; 74 | } 75 | 76 | // if invalid value specified for type, use the default 77 | if (!array_key_exists($type, $xhprof_legal_image_types)) { 78 | $type = $params['type'][1]; // default image type. 79 | } 80 | 81 | $xhprof_runs_impl = new XHProfRuns_Default(); 82 | 83 | if (!empty($run)) { 84 | // single run call graph image generation 85 | xhprof_render_image($xhprof_runs_impl, $run, $type, 86 | $threshold, $func, $source, $critical); 87 | } else { 88 | // diff report call graph image generation 89 | xhprof_render_diff_image($xhprof_runs_impl, $run1, $run2, 90 | $type, $threshold, $source); 91 | } 92 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # XHProf PHP tool for webman plugin 2 | [![Latest Stable Version](http://poser.pugx.org/tinywan/webman-xhprof/v)](https://packagist.org/packages/tinywan/webman-xhprof) [![Total Downloads](http://poser.pugx.org/tinywan/webman-xhprof/downloads)](https://packagist.org/packages/tinywan/webman-xhprof) [![Latest Unstable Version](http://poser.pugx.org/tinywan/webman-xhprof/v/unstable)](https://packagist.org/packages/tinywan/webman-xhprof) [![License](http://poser.pugx.org/tinywan/webman-xhprof/license)](https://packagist.org/packages/tinywan/webman-xhprof) [![PHP Version Require](http://poser.pugx.org/tinywan/webman-xhprof/require/php)](https://packagist.org/packages/tinywan/webman-xhprof) 3 | 4 | # 简介 5 | 6 | [XHProf](https://www.php.net/manual/zh/intro.xhprof.php) 是一个分层PHP性能分析工具。它报告函数级别的请求次数和各种指标,包括阻塞时间,CPU时间和内存使用情况。一个函数的开销,可细分成调用者和被调用者的开销,XHProf数据收集阶段,它记录调用次数的追踪和包容性的指标弧在动态callgraph的一个程序。它独有的数据计算的报告/后处理阶段。 7 | 8 | 在数据收集时,XHProfd通过检测循环来处理递归的函数调用,并通过给递归调用中每个深度的调用一个有用的命名来避开死循环。XHProf分析报告有助于理解被执行的代码的结构,它有一个简单的HTML的用户界面( PHP写成的)。基于浏览器的性能分析用户界面能更容易查看,或是与同行们分享成果。也能绘制调用关系图。 9 | 10 | # 安装 11 | 12 | ```php 13 | composer require tinywan/webman-xhprof 14 | ``` 15 | 16 | # [XHProf](https://www.php.net/manual/zh/intro.xhprof.php) 安装 17 | 18 | 下载 19 | ```javascript 20 | wget https://pecl.php.net/get/xhprof-2.3.9.tgz 21 | tar -zxvf xhprof-2.3.9.tgz 22 | cd ./xhprof-2.3.9/extension 23 | phpize 24 | ./configure --with-php-config=/usr/local/php-7.4/bin/php-config 25 | make 26 | make install 27 | ``` 28 | 29 | `php.ini` 配置 30 | 31 | ```php 32 | [xhprof] 33 | extension=xhprof.so; 34 | xhprof.output_dir=/tmp/xhprof; 35 | ``` 36 | 37 | 确认是否安装成功 38 | 39 | ```javascript 40 | /var/www # php --ri xhprof 41 | 42 | xhprof 43 | 44 | xhprof support => enabled 45 | Version => 2.3.9 46 | ``` 47 | # 使用 48 | 49 | 配置 `config/middleware.php` 50 | 51 | ```php 52 | return [ 53 | '' => [ 54 | \Tinywan\Xhprof\XhprofMiddleware::class, 55 | ] 56 | ]; 57 | ``` 58 | 59 | # 查看 60 | 61 | 访问地址 `http://webman.xhprof.com/xhprof/xhprof_html/index.php` 62 | 63 | > Existing runs 64 | 65 | ![displaying_top.png](./displaying_top.png) 66 | 67 | > Webman\App::Webman\{closure} 68 | 69 | ![Webman\App::Webman\{closure}](./webman.png) 70 | 71 | > 图形化展示 72 | 73 | ![图形化展示](./webman-gpl.png) 74 | 75 | # 其他 76 | 77 | ```shell 78 | Function Name:方法名称。 79 | Calls:方法被调用的次数。 80 | Calls%:方法调用次数在同级方法总数调用次数中所占的百分比。 81 | Incl.Wall Time(microsec):方法执行花费的时间,包括子方法的执行时间。(单位:微秒) 82 | IWall%:方法执行花费的时间百分比。 83 | Excl. Wall Time(microsec):方法本身执行花费的时间,不包括子方法的执行时间。(单位:微秒) 84 | EWall%:方法本身执行花费的时间百分比。 85 | Incl. CPU(microsecs):方法执行花费的CPU时间,包括子方法的执行时间。(单位:微秒) 86 | ICpu%:方法执行花费的CPU时间百分比。 87 | Excl. CPU(microsec):方法本身执行花费的CPU时间,不包括子方法的执行时间。(单位:微秒) 88 | ECPU%:方法本身执行花费的CPU时间百分比。 89 | Incl.MemUse(bytes):方法执行占用的内存,包括子方法执行占用的内存。(单位:字节) 90 | IMemUse%:方法执行占用的内存百分比。 91 | Excl.MemUse(bytes):方法本身执行占用的内存,不包括子方法执行占用的内存。(单位:字节) 92 | EMemUse%:方法本身执行占用的内存百分比。 93 | Incl.PeakMemUse(bytes):Incl.MemUse峰值。(单位:字节) 94 | IPeakMemUse%:Incl.MemUse峰值百分比。 95 | Excl.PeakMemUse(bytes):Excl.MemUse峰值。单位:(字节) 96 | EPeakMemUse%:Excl.MemUse峰值百分比。 97 | ``` 98 | 99 | ## xhprof 简单代理配置 100 | 101 | ``` 102 | server { 103 | server_name webman.xhprof.com; 104 | 105 | root /var/www/webman-admin/vendor/tinywan/webman-xhprof/src; 106 | 107 | location / { 108 | if (!-e $request_filename) { 109 | rewrite ^(.*)$ /index.php?s=/$1 last; 110 | } 111 | } 112 | 113 | location ~ \.php(.*)$ { 114 | try_files $fastcgi_script_name =404; 115 | 116 | fastcgi_pass php74:9000; 117 | fastcgi_index index.php; 118 | fastcgi_split_path_info ^((?U).+\.php)(/?.+)$; 119 | fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; 120 | fastcgi_param PATH_INFO $fastcgi_path_info; 121 | fastcgi_param PATH_TRANSLATED $document_root$fastcgi_path_info; 122 | 123 | include fastcgi_params; 124 | } 125 | } 126 | ``` 127 | 128 | 访问地址:`http://webman.xhprof.com/xhprof/xhprof_html/index.php` -------------------------------------------------------------------------------- /src/xhprof/xhprof_lib/utils/xhprof_runs.php: -------------------------------------------------------------------------------- 1 | suffix; 80 | 81 | if (!empty($this->dir)) { 82 | if (!is_dir($this->dir)) { 83 | mkdir($this->dir, 0777); 84 | } 85 | $file = $this->dir . "/" . $file; 86 | } 87 | return $file; 88 | } 89 | 90 | public function __construct($dir = null) { 91 | 92 | // if user hasn't passed a directory location, 93 | // we use the xhprof.output_dir ini setting 94 | // if specified, else we default to the directory 95 | // in which the error_log file resides. 96 | 97 | if (empty($dir) && !($dir = getenv('XHPROF_OUTPUT_DIR'))) { 98 | $dir = ini_get("xhprof.output_dir"); 99 | if (empty($dir)) { 100 | 101 | $dir = sys_get_temp_dir(); 102 | 103 | xhprof_error("Warning: Must specify directory location for XHProf runs. ". 104 | "Trying {$dir} as default. You can either pass the " . 105 | "directory location as an argument to the constructor ". 106 | "for XHProfRuns_Default() or set xhprof.output_dir ". 107 | "ini param, or set XHPROF_OUTPUT_DIR environment variable."); 108 | } 109 | } 110 | $this->dir = $dir; 111 | } 112 | 113 | public function get_run($run_id, $type, &$run_desc) { 114 | $file_name = $this->file_name($run_id, $type); 115 | 116 | if (!file_exists($file_name)) { 117 | xhprof_error("Could not find file $file_name"); 118 | $run_desc = "Invalid Run Id = $run_id"; 119 | return null; 120 | } 121 | 122 | $contents = file_get_contents($file_name); 123 | $run_desc = "XHProf Run (Namespace=$type)"; 124 | return unserialize($contents); 125 | } 126 | 127 | public function save_run($xhprof_data, $type, $run_id = null) { 128 | 129 | // Use PHP serialize function to store the XHProf's 130 | // raw profiler data. 131 | $xhprof_data = serialize($xhprof_data); 132 | 133 | if ($run_id === null) { 134 | $run_id = $this->gen_run_id($type); 135 | } 136 | 137 | $file_name = $this->file_name($run_id, $type); 138 | $file = fopen($file_name, 'w'); 139 | 140 | if ($file) { 141 | fwrite($file, $xhprof_data); 142 | fclose($file); 143 | } else { 144 | xhprof_error("Could not open $file_name\n"); 145 | } 146 | 147 | // echo "Saved run in {$file_name}.\nRun id = {$run_id}.\n"; 148 | return $run_id; 149 | } 150 | 151 | function list_runs() { 152 | if (is_dir($this->dir)) { 153 | echo "
Existing runs:\n\n"; 165 | } 166 | } 167 | } 168 | -------------------------------------------------------------------------------- /src/xhprof/xhprof_html/js/xhprof_report.js: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2009 Facebook 2 | * 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | 16 | /** 17 | * Helper javascript functions for XHProf report tooltips. 18 | * 19 | * @author Kannan Muthukkaruppan 20 | */ 21 | 22 | // Take a string which is actually a number in comma separated format 23 | // and return a string representing the absolute value of the number. 24 | function stringAbs(x) { 25 | return x.replace("-", ""); 26 | } 27 | 28 | // Takes a number in comma-separated string format, and 29 | // returns a boolean to indicate if the number is negative 30 | // or not. 31 | function isNegative(x) { 32 | 33 | return (x.indexOf("-") == 0); 34 | 35 | } 36 | 37 | function addCommas(nStr) 38 | { 39 | nStr += ''; 40 | x = nStr.split('.'); 41 | x1 = x[0]; 42 | x2 = x.length > 1 ? '.' + x[1] : ''; 43 | var rgx = /(\d+)(\d{3})/; 44 | while (rgx.test(x1)) { 45 | x1 = x1.replace(rgx, '$1' + ',' + '$2'); 46 | } 47 | return x1 + x2; 48 | } 49 | 50 | // Mouseover tips for parent rows in parent/child report.. 51 | function ParentRowToolTip(cell, metric) 52 | { 53 | var metric_val; 54 | var parent_metric_val; 55 | var parent_metric_pct_val; 56 | var col_index; 57 | var diff_text; 58 | 59 | row = cell.parentNode; 60 | tds = row.getElementsByTagName("td"); 61 | 62 | parent_func = tds[0].innerHTML; // name 63 | 64 | if (diff_mode) { 65 | diff_text = " diff "; 66 | } else { 67 | diff_text = ""; 68 | } 69 | 70 | s = '
'; 71 | 72 | if (metric == "ct") { 73 | parent_ct = tds[1].innerHTML; // calls 74 | parent_ct_pct = tds[2].innerHTML; 75 | 76 | func_ct = addCommas(func_ct); 77 | 78 | if (diff_mode) { 79 | s += 'There are ' + stringAbs(parent_ct) + 80 | (isNegative(parent_ct) ? ' fewer ' : ' more ') + 81 | ' calls to ' + func_name + ' from ' + parent_func + '
'; 82 | 83 | text = " of diff in calls "; 84 | } else { 85 | text = " of calls "; 86 | } 87 | 88 | s += parent_ct_pct + text + '(' + parent_ct + '/' + func_ct + ') to ' 89 | + func_name + ' are from ' + parent_func + '
'; 90 | } else { 91 | 92 | // help for other metrics such as wall time, user cpu time, memory usage 93 | col_index = metrics_col[metric]; 94 | parent_metric_val = tds[col_index].innerHTML; 95 | parent_metric_pct_val = tds[col_index+1].innerHTML; 96 | 97 | metric_val = addCommas(func_metrics[metric]); 98 | 99 | s += parent_metric_pct_val + '(' + parent_metric_val + '/' + metric_val 100 | + ') of ' + metrics_desc[metric] + 101 | (diff_mode ? ((isNegative(parent_metric_val) ? 102 | " decrease" : " increase")) : "") + 103 | ' in ' + func_name + ' is due to calls from ' + parent_func + '
'; 104 | } 105 | 106 | s += '
'; 107 | 108 | return s; 109 | } 110 | 111 | // Mouseover tips for child rows in parent/child report.. 112 | function ChildRowToolTip(cell, metric) 113 | { 114 | var metric_val; 115 | var child_metric_val; 116 | var child_metric_pct_val; 117 | var col_index; 118 | var diff_text; 119 | 120 | row = cell.parentNode; 121 | tds = row.getElementsByTagName("td"); 122 | 123 | child_func = tds[0].innerHTML; // name 124 | 125 | if (diff_mode) { 126 | diff_text = " diff "; 127 | } else { 128 | diff_text = ""; 129 | } 130 | 131 | s = '
'; 132 | 133 | if (metric == "ct") { 134 | 135 | child_ct = tds[1].innerHTML; // calls 136 | child_ct_pct = tds[2].innerHTML; 137 | 138 | s += func_name + ' called ' + child_func + ' ' + stringAbs(child_ct) + 139 | (diff_mode ? (isNegative(child_ct) ? " fewer" : " more") : "" ) 140 | + ' times.
'; 141 | s += 'This accounts for ' + child_ct_pct + ' (' + child_ct 142 | + '/' + total_child_ct 143 | + ') of function calls made by ' + func_name + '.'; 144 | 145 | } else { 146 | 147 | // help for other metrics such as wall time, user cpu time, memory usage 148 | col_index = metrics_col[metric]; 149 | child_metric_val = tds[col_index].innerHTML; 150 | child_metric_pct_val = tds[col_index+1].innerHTML; 151 | 152 | metric_val = addCommas(func_metrics[metric]); 153 | 154 | if (child_func.indexOf("Exclusive Metrics") != -1) { 155 | s += 'The exclusive ' + metrics_desc[metric] + diff_text 156 | + ' for ' + func_name 157 | + ' is ' + child_metric_val + "
"; 158 | 159 | s += "which is " + child_metric_pct_val + " of the inclusive " 160 | + metrics_desc[metric] 161 | + diff_text + " for " + func_name + " (" + metric_val + ")."; 162 | 163 | } else { 164 | 165 | s += child_func + ' when called from ' + func_name 166 | + ' takes ' + stringAbs(child_metric_val) 167 | + (diff_mode ? (isNegative(child_metric_val) ? " less" : " more") : "") 168 | + " of " + metrics_desc[metric] + "
"; 169 | 170 | s += "which is " + child_metric_pct_val + " of the inclusive " 171 | + metrics_desc[metric] 172 | + diff_text + " for " + func_name + " (" + metric_val + ")."; 173 | } 174 | } 175 | 176 | s += '
'; 177 | 178 | return s; 179 | } 180 | 181 | $(document).ready(function() { 182 | $('td[@metric]').tooltip( 183 | { bodyHandler: function() { 184 | var type = $(this).attr('type'); 185 | var metric = $(this).attr('metric'); 186 | if (type == 'Parent') { 187 | return ParentRowToolTip(this, metric); 188 | } else if (type == 'Child') { 189 | return ChildRowToolTip(this, metric); 190 | } 191 | }, 192 | showURL : false 193 | }); 194 | var cur_params = {} ; 195 | $.each(location.search.replace('?','').split('&'), function(i, x) { 196 | var y = x.split('='); cur_params[y[0]] = y[1]; 197 | }); 198 | $('input.function_typeahead') 199 | .autocomplete('typeahead.php', { extraParams : cur_params }) 200 | .result(function(event, item) { 201 | cur_params['symbol'] = item; 202 | location.search = '?' + jQuery.param(cur_params); 203 | }); 204 | }); 205 | -------------------------------------------------------------------------------- /src/xhprof/xhprof_html/jquery/jquery.tooltip.js: -------------------------------------------------------------------------------- 1 | /* 2 | * jQuery Tooltip plugin 1.3 3 | * 4 | * http://bassistance.de/jquery-plugins/jquery-plugin-tooltip/ 5 | * http://docs.jquery.com/Plugins/Tooltip 6 | * 7 | * Copyright (c) 2006 - 2008 Jörn Zaefferer 8 | * 9 | * $Id: jquery.tooltip.js,v 1.1.1.1 2009-03-17 18:35:18 kannan Exp $ 10 | * 11 | * Dual licensed under the MIT and GPL licenses: 12 | * http://www.opensource.org/licenses/mit-license.php 13 | * http://www.gnu.org/licenses/gpl.html 14 | */ 15 | 16 | ;(function($) { 17 | 18 | // the tooltip element 19 | var helper = {}, 20 | // the current tooltipped element 21 | current, 22 | // the title of the current element, used for restoring 23 | title, 24 | // timeout id for delayed tooltips 25 | tID, 26 | // IE 5.5 or 6 27 | IE = $.browser.msie && /MSIE\s(5\.5|6\.)/.test(navigator.userAgent), 28 | // flag for mouse tracking 29 | track = false; 30 | 31 | $.tooltip = { 32 | blocked: false, 33 | defaults: { 34 | delay: 200, 35 | fade: false, 36 | showURL: true, 37 | extraClass: "", 38 | top: 15, 39 | left: 15, 40 | id: "tooltip" 41 | }, 42 | block: function() { 43 | $.tooltip.blocked = !$.tooltip.blocked; 44 | } 45 | }; 46 | 47 | $.fn.extend({ 48 | tooltip: function(settings) { 49 | settings = $.extend({}, $.tooltip.defaults, settings); 50 | createHelper(settings); 51 | return this.each(function() { 52 | $.data(this, "tooltip", settings); 53 | this.tOpacity = helper.parent.css("opacity"); 54 | // copy tooltip into its own expando and remove the title 55 | this.tooltipText = this.title; 56 | $(this).removeAttr("title"); 57 | // also remove alt attribute to prevent default tooltip in IE 58 | this.alt = ""; 59 | }) 60 | .mouseover(save) 61 | .mouseout(hide) 62 | .click(hide); 63 | }, 64 | fixPNG: IE ? function() { 65 | return this.each(function () { 66 | var image = $(this).css('backgroundImage'); 67 | if (image.match(/^url\(["']?(.*\.png)["']?\)$/i)) { 68 | image = RegExp.$1; 69 | $(this).css({ 70 | 'backgroundImage': 'none', 71 | 'filter': "progid:DXImageTransform.Microsoft.AlphaImageLoader(enabled=true, sizingMethod=crop, src='" + image + "')" 72 | }).each(function () { 73 | var position = $(this).css('position'); 74 | if (position != 'absolute' && position != 'relative') 75 | $(this).css('position', 'relative'); 76 | }); 77 | } 78 | }); 79 | } : function() { return this; }, 80 | unfixPNG: IE ? function() { 81 | return this.each(function () { 82 | $(this).css({'filter': '', backgroundImage: ''}); 83 | }); 84 | } : function() { return this; }, 85 | hideWhenEmpty: function() { 86 | return this.each(function() { 87 | $(this)[ $(this).html() ? "show" : "hide" ](); 88 | }); 89 | }, 90 | url: function() { 91 | return this.attr('href') || this.attr('src'); 92 | } 93 | }); 94 | 95 | function createHelper(settings) { 96 | // there can be only one tooltip helper 97 | if( helper.parent ) 98 | return; 99 | // create the helper, h3 for title, div for url 100 | helper.parent = $('

') 101 | // add to document 102 | .appendTo(document.body) 103 | // hide it at first 104 | .hide(); 105 | 106 | // apply bgiframe if available 107 | if ( $.fn.bgiframe ) 108 | helper.parent.bgiframe(); 109 | 110 | // save references to title and url elements 111 | helper.title = $('h3', helper.parent); 112 | helper.body = $('div.body', helper.parent); 113 | helper.url = $('div.url', helper.parent); 114 | } 115 | 116 | function settings(element) { 117 | return $.data(element, "tooltip"); 118 | } 119 | 120 | // main event handler to start showing tooltips 121 | function handle(event) { 122 | // show helper, either with timeout or on instant 123 | if( settings(this).delay ) 124 | tID = setTimeout(show, settings(this).delay); 125 | else 126 | show(); 127 | 128 | // if selected, update the helper position when the mouse moves 129 | track = !!settings(this).track; 130 | $(document.body).bind('mousemove', update); 131 | 132 | // update at least once 133 | update(event); 134 | } 135 | 136 | // save elements title before the tooltip is displayed 137 | function save() { 138 | // if this is the current source, or it has no title (occurs with click event), stop 139 | if ( $.tooltip.blocked || this == current || (!this.tooltipText && !settings(this).bodyHandler) ) 140 | return; 141 | 142 | // save current 143 | current = this; 144 | title = this.tooltipText; 145 | 146 | if ( settings(this).bodyHandler ) { 147 | helper.title.hide(); 148 | var bodyContent = settings(this).bodyHandler.call(this); 149 | if (bodyContent.nodeType || bodyContent.jquery) { 150 | helper.body.empty().append(bodyContent) 151 | } else { 152 | helper.body.html( bodyContent ); 153 | } 154 | helper.body.show(); 155 | } else if ( settings(this).showBody ) { 156 | var parts = title.split(settings(this).showBody); 157 | helper.title.html(parts.shift()).show(); 158 | helper.body.empty(); 159 | for(var i = 0, part; (part = parts[i]); i++) { 160 | if(i > 0) 161 | helper.body.append("
"); 162 | helper.body.append(part); 163 | } 164 | helper.body.hideWhenEmpty(); 165 | } else { 166 | helper.title.html(title).show(); 167 | helper.body.hide(); 168 | } 169 | 170 | // if element has href or src, add and show it, otherwise hide it 171 | if( settings(this).showURL && $(this).url() ) 172 | helper.url.html( $(this).url().replace('http://', '') ).show(); 173 | else 174 | helper.url.hide(); 175 | 176 | // add an optional class for this tip 177 | helper.parent.addClass(settings(this).extraClass); 178 | 179 | // fix PNG background for IE 180 | if (settings(this).fixPNG ) 181 | helper.parent.fixPNG(); 182 | 183 | handle.apply(this, arguments); 184 | } 185 | 186 | // delete timeout and show helper 187 | function show() { 188 | tID = null; 189 | if ((!IE || !$.fn.bgiframe) && settings(current).fade) { 190 | if (helper.parent.is(":animated")) 191 | helper.parent.stop().show().fadeTo(settings(current).fade, current.tOpacity); 192 | else 193 | helper.parent.is(':visible') ? helper.parent.fadeTo(settings(current).fade, current.tOpacity) : helper.parent.fadeIn(settings(current).fade); 194 | } else { 195 | helper.parent.show(); 196 | } 197 | update(); 198 | } 199 | 200 | /** 201 | * callback for mousemove 202 | * updates the helper position 203 | * removes itself when no current element 204 | */ 205 | function update(event) { 206 | if($.tooltip.blocked) 207 | return; 208 | 209 | if (event && event.target.tagName == "OPTION") { 210 | return; 211 | } 212 | 213 | // stop updating when tracking is disabled and the tooltip is visible 214 | if ( !track && helper.parent.is(":visible")) { 215 | $(document.body).unbind('mousemove', update) 216 | } 217 | 218 | // if no current element is available, remove this listener 219 | if( current == null ) { 220 | $(document.body).unbind('mousemove', update); 221 | return; 222 | } 223 | 224 | // remove position helper classes 225 | helper.parent.removeClass("viewport-right").removeClass("viewport-bottom"); 226 | 227 | var left = helper.parent[0].offsetLeft; 228 | var top = helper.parent[0].offsetTop; 229 | if (event) { 230 | // position the helper 15 pixel to bottom right, starting from mouse position 231 | left = event.pageX + settings(current).left; 232 | top = event.pageY + settings(current).top; 233 | var right='auto'; 234 | if (settings(current).positionLeft) { 235 | right = $(window).width() - left; 236 | left = 'auto'; 237 | } 238 | helper.parent.css({ 239 | left: left, 240 | right: right, 241 | top: top 242 | }); 243 | } 244 | 245 | var v = viewport(), 246 | h = helper.parent[0]; 247 | // check horizontal position 248 | if (v.x + v.cx < h.offsetLeft + h.offsetWidth) { 249 | left -= h.offsetWidth + 20 + settings(current).left; 250 | helper.parent.css({left: left + 'px'}).addClass("viewport-right"); 251 | } 252 | // check vertical position 253 | if (v.y + v.cy < h.offsetTop + h.offsetHeight) { 254 | top -= h.offsetHeight + 20 + settings(current).top; 255 | helper.parent.css({top: top + 'px'}).addClass("viewport-bottom"); 256 | } 257 | } 258 | 259 | function viewport() { 260 | return { 261 | x: $(window).scrollLeft(), 262 | y: $(window).scrollTop(), 263 | cx: $(window).width(), 264 | cy: $(window).height() 265 | }; 266 | } 267 | 268 | // hide helper and restore added classes and the title 269 | function hide(event) { 270 | if($.tooltip.blocked) 271 | return; 272 | // clear timeout if possible 273 | if(tID) 274 | clearTimeout(tID); 275 | // no more current element 276 | current = null; 277 | 278 | var tsettings = settings(this); 279 | function complete() { 280 | helper.parent.removeClass( tsettings.extraClass ).hide().css("opacity", ""); 281 | } 282 | if ((!IE || !$.fn.bgiframe) && tsettings.fade) { 283 | if (helper.parent.is(':animated')) 284 | helper.parent.stop().fadeTo(tsettings.fade, 0, complete); 285 | else 286 | helper.parent.stop().fadeOut(tsettings.fade, complete); 287 | } else 288 | complete(); 289 | 290 | if( settings(this).fixPNG ) 291 | helper.parent.unfixPNG(); 292 | } 293 | 294 | })(jQuery); 295 | -------------------------------------------------------------------------------- /src/xhprof/xhprof_lib/utils/callgraph_utils.php: -------------------------------------------------------------------------------- 1 | 1, 26 | "gif" => 1, 27 | "png" => 1, 28 | "svg" => 1, // support scalable vector graphic 29 | "ps" => 1, 30 | ); 31 | 32 | /** 33 | * Send an HTTP header with the response. You MUST use this function instead 34 | * of header() so that we can debug header issues because they're virtually 35 | * impossible to debug otherwise. If you try to commit header(), SVN will 36 | * reject your commit. 37 | * 38 | * @param string HTTP header name, like 'Location' 39 | * @param string HTTP header value, like 'http://www.example.com/' 40 | * 41 | */ 42 | function xhprof_http_header($name, $value) { 43 | 44 | if (!$name) { 45 | xhprof_error('http_header usage'); 46 | return null; 47 | } 48 | 49 | if (!is_string($value)) { 50 | xhprof_error('http_header value not a string'); 51 | } 52 | 53 | header($name.': '.$value, true); 54 | } 55 | 56 | /** 57 | * Genearte and send MIME header for the output image to client browser. 58 | * 59 | * @author cjiang 60 | */ 61 | function xhprof_generate_mime_header($type, $length) { 62 | switch ($type) { 63 | case 'jpg': 64 | $mime = 'image/jpeg'; 65 | break; 66 | case 'gif': 67 | $mime = 'image/gif'; 68 | break; 69 | case 'png': 70 | $mime = 'image/png'; 71 | break; 72 | case 'svg': 73 | $mime = 'image/svg+xml'; // content type for scalable vector graphic 74 | break; 75 | case 'ps': 76 | $mime = 'application/postscript'; 77 | default: 78 | $mime = false; 79 | } 80 | 81 | if ($mime) { 82 | xhprof_http_header('Content-type', $mime); 83 | xhprof_http_header('Content-length', (string)$length); 84 | } 85 | } 86 | 87 | /** 88 | * Generate image according to DOT script. This function will spawn a process 89 | * with "dot" command and pipe the "dot_script" to it and pipe out the 90 | * generated image content. 91 | * 92 | * @param dot_script, string, the script for DOT to generate the image. 93 | * @param type, one of the supported image types, see 94 | * $xhprof_legal_image_types. 95 | * @returns, binary content of the generated image on success. empty string on 96 | * failure. 97 | * 98 | * @author cjiang 99 | */ 100 | function xhprof_generate_image_by_dot($dot_script, $type) { 101 | $descriptorspec = array( 102 | // stdin is a pipe that the child will read from 103 | 0 => array("pipe", "r"), 104 | // stdout is a pipe that the child will write to 105 | 1 => array("pipe", "w"), 106 | // stderr is a pipe that the child will write to 107 | 2 => array("pipe", "w") 108 | ); 109 | 110 | $cmd = " dot -T".$type; 111 | 112 | $process = proc_open( $cmd, $descriptorspec, $pipes, sys_get_temp_dir(), array( 'PATH' => getenv( 'PATH' ) ) ); 113 | if (is_resource($process)) { 114 | fwrite($pipes[0], $dot_script); 115 | fclose($pipes[0]); 116 | 117 | $output = stream_get_contents($pipes[1]); 118 | 119 | $err = stream_get_contents($pipes[2]); 120 | if (!empty($err)) { 121 | print "failed to execute cmd: \"$cmd\". stderr: `$err'\n"; 122 | exit; 123 | } 124 | 125 | fclose($pipes[2]); 126 | fclose($pipes[1]); 127 | proc_close($process); 128 | return $output; 129 | } 130 | print "failed to execute cmd \"$cmd\""; 131 | exit(); 132 | } 133 | 134 | /* 135 | * Get the children list of all nodes. 136 | */ 137 | function xhprof_get_children_table($raw_data) { 138 | $children_table = array(); 139 | foreach ($raw_data as $parent_child => $info) { 140 | list($parent, $child) = xhprof_parse_parent_child($parent_child); 141 | if (!isset($children_table[$parent])) { 142 | $children_table[$parent] = array($child); 143 | } else { 144 | $children_table[$parent][] = $child; 145 | } 146 | } 147 | return $children_table; 148 | } 149 | 150 | /** 151 | * Generate DOT script from the given raw phprof data. 152 | * 153 | * @param raw_data, phprof profile data. 154 | * @param threshold, float, the threshold value [0,1). The functions in the 155 | * raw_data whose exclusive wall times ratio are below the 156 | * threshold will be filtered out and won't apprear in the 157 | * generated image. 158 | * @param page, string(optional), the root node name. This can be used to 159 | * replace the 'main()' as the root node. 160 | * @param func, string, the focus function. 161 | * @param critical_path, bool, whether or not to display critical path with 162 | * bold lines. 163 | * @returns, string, the DOT script to generate image. 164 | * 165 | * @author cjiang 166 | */ 167 | function xhprof_generate_dot_script($raw_data, $threshold, $source, $page, 168 | $func, $critical_path, $right=null, 169 | $left=null) { 170 | 171 | $max_width = 5; 172 | $max_height = 3.5; 173 | $max_fontsize = 35; 174 | $max_sizing_ratio = 20; 175 | 176 | $totals; 177 | 178 | if ($left === null) { 179 | // init_metrics($raw_data, null, null); 180 | } 181 | $sym_table = xhprof_compute_flat_info($raw_data, $totals); 182 | 183 | if ($critical_path) { 184 | $children_table = xhprof_get_children_table($raw_data); 185 | $node = "main()"; 186 | $path = array(); 187 | $path_edges = array(); 188 | $visited = array(); 189 | while ($node) { 190 | $visited[$node] = true; 191 | if (isset($children_table[$node])) { 192 | $max_child = null; 193 | foreach ($children_table[$node] as $child) { 194 | 195 | if (isset($visited[$child])) { 196 | continue; 197 | } 198 | if ($max_child === null || 199 | abs($raw_data[xhprof_build_parent_child_key($node, 200 | $child)]["wt"]) > 201 | abs($raw_data[xhprof_build_parent_child_key($node, 202 | $max_child)]["wt"])) { 203 | $max_child = $child; 204 | } 205 | } 206 | if ($max_child !== null) { 207 | $path[$max_child] = true; 208 | $path_edges[xhprof_build_parent_child_key($node, $max_child)] = true; 209 | } 210 | $node = $max_child; 211 | } else { 212 | $node = null; 213 | } 214 | } 215 | } 216 | 217 | // if it is a benchmark callgraph, we make the benchmarked function the root. 218 | if ($source == "bm" && array_key_exists("main()", $sym_table)) { 219 | $total_times = $sym_table["main()"]["ct"]; 220 | $remove_funcs = array("main()", 221 | "hotprofiler_disable", 222 | "call_user_func_array", 223 | "xhprof_disable"); 224 | 225 | foreach ($remove_funcs as $cur_del_func) { 226 | if (array_key_exists($cur_del_func, $sym_table) && 227 | $sym_table[$cur_del_func]["ct"] == $total_times) { 228 | unset($sym_table[$cur_del_func]); 229 | } 230 | } 231 | } 232 | 233 | // use the function to filter out irrelevant functions. 234 | if (!empty($func)) { 235 | $interested_funcs = array(); 236 | foreach ($raw_data as $parent_child => $info) { 237 | list($parent, $child) = xhprof_parse_parent_child($parent_child); 238 | if ($parent == $func || $child == $func) { 239 | $interested_funcs[$parent] = 1; 240 | $interested_funcs[$child] = 1; 241 | } 242 | } 243 | foreach ($sym_table as $symbol => $info) { 244 | if (!array_key_exists($symbol, $interested_funcs)) { 245 | unset($sym_table[$symbol]); 246 | } 247 | } 248 | } 249 | 250 | $result = "digraph call_graph {\n"; 251 | 252 | // Filter out functions whose exclusive time ratio is below threshold, and 253 | // also assign a unique integer id for each function to be generated. In the 254 | // meantime, find the function with the most exclusive time (potentially the 255 | // performance bottleneck). 256 | $cur_id = 0; $max_wt = 0; 257 | foreach ($sym_table as $symbol => $info) { 258 | if (empty($func) && abs($info["wt"] / $totals["wt"]) < $threshold) { 259 | unset($sym_table[$symbol]); 260 | continue; 261 | } 262 | if ($max_wt == 0 || $max_wt < abs($info["excl_wt"])) { 263 | $max_wt = abs($info["excl_wt"]); 264 | } 265 | $sym_table[$symbol]["id"] = $cur_id; 266 | $cur_id ++; 267 | } 268 | 269 | // Generate all nodes' information. 270 | foreach ($sym_table as $symbol => $info) { 271 | if ($info["excl_wt"] == 0) { 272 | $sizing_factor = $max_sizing_ratio; 273 | } else { 274 | $sizing_factor = $max_wt / abs($info["excl_wt"]) ; 275 | if ($sizing_factor > $max_sizing_ratio) { 276 | $sizing_factor = $max_sizing_ratio; 277 | } 278 | } 279 | $fillcolor = (($sizing_factor < 1.5) ? 280 | ", style=filled, fillcolor=red" : ""); 281 | 282 | if ($critical_path) { 283 | // highlight nodes along critical path. 284 | if (!$fillcolor && array_key_exists($symbol, $path)) { 285 | $fillcolor = ", style=filled, fillcolor=yellow"; 286 | } 287 | } 288 | 289 | $fontsize = ", fontsize=" 290 | .(int)($max_fontsize / (($sizing_factor - 1) / 10 + 1)); 291 | 292 | $width = ", width=".sprintf("%.1f", $max_width / $sizing_factor); 293 | $height = ", height=".sprintf("%.1f", $max_height / $sizing_factor); 294 | 295 | if ($symbol == "main()") { 296 | $shape = "octagon"; 297 | $name = "Total: ".($totals["wt"] / 1000.0)." ms\\n"; 298 | $name .= addslashes(isset($page) ? $page : $symbol); 299 | } else { 300 | $shape = "box"; 301 | $name = addslashes($symbol)."\\nInc: ". sprintf("%.3f",$info["wt"] / 1000) . 302 | " ms (" . sprintf("%.1f%%", 100 * $info["wt"] / $totals["wt"]).")"; 303 | } 304 | if ($left === null) { 305 | $label = ", label=\"".$name."\\nExcl: " 306 | .(sprintf("%.3f",$info["excl_wt"] / 1000.0))." ms (" 307 | .sprintf("%.1f%%", 100 * $info["excl_wt"] / $totals["wt"]) 308 | . ")\\n".$info["ct"]." total calls\""; 309 | } else { 310 | if (isset($left[$symbol]) && isset($right[$symbol])) { 311 | $label = ", label=\"".addslashes($symbol). 312 | "\\nInc: ".(sprintf("%.3f",$left[$symbol]["wt"] / 1000.0)) 313 | ." ms - " 314 | .(sprintf("%.3f",$right[$symbol]["wt"] / 1000.0))." ms = " 315 | .(sprintf("%.3f",$info["wt"] / 1000.0))." ms". 316 | "\\nExcl: " 317 | .(sprintf("%.3f",$left[$symbol]["excl_wt"] / 1000.0)) 318 | ." ms - ".(sprintf("%.3f",$right[$symbol]["excl_wt"] / 1000.0)) 319 | ." ms = ".(sprintf("%.3f",$info["excl_wt"] / 1000.0))." ms". 320 | "\\nCalls: ".(sprintf("%.3f",$left[$symbol]["ct"]))." - " 321 | .(sprintf("%.3f",$right[$symbol]["ct"]))." = " 322 | .(sprintf("%.3f",$info["ct"]))."\""; 323 | } else if (isset($left[$symbol])) { 324 | $label = ", label=\"".addslashes($symbol). 325 | "\\nInc: ".(sprintf("%.3f",$left[$symbol]["wt"] / 1000.0)) 326 | ." ms - 0 ms = ".(sprintf("%.3f",$info["wt"] / 1000.0)) 327 | ." ms"."\\nExcl: " 328 | .(sprintf("%.3f",$left[$symbol]["excl_wt"] / 1000.0)) 329 | ." ms - 0 ms = " 330 | .(sprintf("%.3f",$info["excl_wt"] / 1000.0))." ms". 331 | "\\nCalls: ".(sprintf("%.3f",$left[$symbol]["ct"]))." - 0 = " 332 | .(sprintf("%.3f",$info["ct"]))."\""; 333 | } else { 334 | $label = ", label=\"".addslashes($symbol). 335 | "\\nInc: 0 ms - " 336 | .(sprintf("%.3f",$right[$symbol]["wt"] / 1000.0)) 337 | ." ms = ".(sprintf("%.3f",$info["wt"] / 1000.0))." ms". 338 | "\\nExcl: 0 ms - " 339 | .(sprintf("%.3f",$right[$symbol]["excl_wt"] / 1000.0)) 340 | ." ms = ".(sprintf("%.3f",$info["excl_wt"] / 1000.0))." ms". 341 | "\\nCalls: 0 - ".(sprintf("%.3f",$right[$symbol]["ct"])) 342 | ." = ".(sprintf("%.3f",$info["ct"]))."\""; 343 | } 344 | } 345 | $result .= "N" . $sym_table[$symbol]["id"]; 346 | $result .= "[shape=$shape ".$label.$width 347 | .$height.$fontsize.$fillcolor."];\n"; 348 | } 349 | 350 | // Generate all the edges' information. 351 | foreach ($raw_data as $parent_child => $info) { 352 | list($parent, $child) = xhprof_parse_parent_child($parent_child); 353 | 354 | if (isset($sym_table[$parent]) && isset($sym_table[$child]) && 355 | (empty($func) || 356 | (!empty($func) && ($parent == $func || $child == $func)))) { 357 | 358 | $label = $info["ct"] == 1 ? $info["ct"]." call" : $info["ct"]." calls"; 359 | 360 | $headlabel = $sym_table[$child]["wt"] > 0 ? 361 | sprintf("%.1f%%", 100 * $info["wt"] 362 | / $sym_table[$child]["wt"]) 363 | : "0.0%"; 364 | 365 | $taillabel = ($sym_table[$parent]["wt"] > 0) ? 366 | sprintf("%.1f%%", 367 | 100 * $info["wt"] / 368 | ($sym_table[$parent]["wt"] - $sym_table["$parent"]["excl_wt"])) 369 | : "0.0%"; 370 | 371 | $linewidth = 1; 372 | $arrow_size = 1; 373 | 374 | if ($critical_path && 375 | isset($path_edges[xhprof_build_parent_child_key($parent, $child)])) { 376 | $linewidth = 10; $arrow_size = 2; 377 | } 378 | 379 | $result .= "N" . $sym_table[$parent]["id"] . " -> N" 380 | . $sym_table[$child]["id"]; 381 | $result .= "[arrowsize=$arrow_size, color=grey, style=\"setlinewidth($linewidth)\"," 382 | ." label=\"" 383 | .$label."\", headlabel=\"".$headlabel 384 | ."\", taillabel=\"".$taillabel."\" ]"; 385 | $result .= ";\n"; 386 | 387 | } 388 | } 389 | $result = $result . "\n}"; 390 | 391 | return $result; 392 | } 393 | 394 | function xhprof_render_diff_image($xhprof_runs_impl, $run1, $run2, 395 | $type, $threshold, $source) { 396 | $total1; 397 | $total2; 398 | 399 | $raw_data1 = $xhprof_runs_impl->get_run($run1, $source, $desc_unused); 400 | $raw_data2 = $xhprof_runs_impl->get_run($run2, $source, $desc_unused); 401 | 402 | // init_metrics($raw_data1, null, null); 403 | $children_table1 = xhprof_get_children_table($raw_data1); 404 | $children_table2 = xhprof_get_children_table($raw_data2); 405 | $symbol_tab1 = xhprof_compute_flat_info($raw_data1, $total1); 406 | $symbol_tab2 = xhprof_compute_flat_info($raw_data2, $total2); 407 | $run_delta = xhprof_compute_diff($raw_data1, $raw_data2); 408 | $script = xhprof_generate_dot_script($run_delta, $threshold, $source, 409 | null, null, true, 410 | $symbol_tab1, $symbol_tab2); 411 | $content = xhprof_generate_image_by_dot($script, $type); 412 | 413 | xhprof_generate_mime_header($type, strlen($content)); 414 | echo $content; 415 | } 416 | 417 | /** 418 | * Generate image content from phprof run id. 419 | * 420 | * @param object $xhprof_runs_impl An object that implements 421 | * the iXHProfRuns interface 422 | * @param run_id, integer, the unique id for the phprof run, this is the 423 | * primary key for phprof database table. 424 | * @param type, string, one of the supported image types. See also 425 | * $xhprof_legal_image_types. 426 | * @param threshold, float, the threshold value [0,1). The functions in the 427 | * raw_data whose exclusive wall times ratio are below the 428 | * threshold will be filtered out and won't apprear in the 429 | * generated image. 430 | * @param func, string, the focus function. 431 | * @returns, string, the DOT script to generate image. 432 | * 433 | * @author cjiang 434 | */ 435 | function xhprof_get_content_by_run($xhprof_runs_impl, $run_id, $type, 436 | $threshold, $func, $source, 437 | $critical_path) { 438 | if (!$run_id) 439 | return ""; 440 | 441 | $raw_data = $xhprof_runs_impl->get_run($run_id, $source, $description); 442 | if (!$raw_data) { 443 | xhprof_error("Raw data is empty"); 444 | return ""; 445 | } 446 | 447 | $script = xhprof_generate_dot_script($raw_data, $threshold, $source, 448 | $description, $func, $critical_path); 449 | 450 | $content = xhprof_generate_image_by_dot($script, $type); 451 | return $content; 452 | } 453 | 454 | /** 455 | * Generate image from phprof run id and send it to client. 456 | * 457 | * @param object $xhprof_runs_impl An object that implements 458 | * the iXHProfRuns interface 459 | * @param run_id, integer, the unique id for the phprof run, this is the 460 | * primary key for phprof database table. 461 | * @param type, string, one of the supported image types. See also 462 | * $xhprof_legal_image_types. 463 | * @param threshold, float, the threshold value [0,1). The functions in the 464 | * raw_data whose exclusive wall times ratio are below the 465 | * threshold will be filtered out and won't apprear in the 466 | * generated image. 467 | * @param func, string, the focus function. 468 | * @param bool, does this run correspond to a PHProfLive run or a dev run? 469 | * @author cjiang 470 | */ 471 | function xhprof_render_image($xhprof_runs_impl, $run_id, $type, $threshold, 472 | $func, $source, $critical_path) { 473 | 474 | $content = xhprof_get_content_by_run($xhprof_runs_impl, $run_id, $type, 475 | $threshold, 476 | $func, $source, $critical_path); 477 | if (!$content) { 478 | print "Error: either we can not find profile data for run_id ".$run_id 479 | ." or the threshold ".$threshold." is too small or you do not" 480 | ." have 'dot' image generation utility installed."; 481 | exit(); 482 | } 483 | 484 | xhprof_generate_mime_header($type, strlen($content)); 485 | echo $content; 486 | } 487 | -------------------------------------------------------------------------------- /src/xhprof/xhprof_html/jquery/jquery.autocomplete.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Autocomplete - jQuery plugin 1.0.2 3 | * 4 | * Copyright (c) 2007 Dylan Verheul, Dan G. Switzer, Anjesh Tuladhar, Jörn Zaefferer 5 | * 6 | * Dual licensed under the MIT and GPL licenses: 7 | * http://www.opensource.org/licenses/mit-license.php 8 | * http://www.gnu.org/licenses/gpl.html 9 | * 10 | * Revision: $Id: jquery.autocomplete.js,v 1.1.1.1 2009-03-17 18:35:18 kannan Exp $ 11 | * 12 | */ 13 | 14 | ;(function($) { 15 | 16 | $.fn.extend({ 17 | autocomplete: function(urlOrData, options) { 18 | var isUrl = typeof urlOrData == "string"; 19 | options = $.extend({}, $.Autocompleter.defaults, { 20 | url: isUrl ? urlOrData : null, 21 | data: isUrl ? null : urlOrData, 22 | delay: isUrl ? $.Autocompleter.defaults.delay : 10, 23 | max: options && !options.scroll ? 10 : 150 24 | }, options); 25 | 26 | // if highlight is set to false, replace it with a do-nothing function 27 | options.highlight = options.highlight || function(value) { return value; }; 28 | 29 | // if the formatMatch option is not specified, then use formatItem for backwards compatibility 30 | options.formatMatch = options.formatMatch || options.formatItem; 31 | 32 | return this.each(function() { 33 | new $.Autocompleter(this, options); 34 | }); 35 | }, 36 | result: function(handler) { 37 | return this.bind("result", handler); 38 | }, 39 | search: function(handler) { 40 | return this.trigger("search", [handler]); 41 | }, 42 | flushCache: function() { 43 | return this.trigger("flushCache"); 44 | }, 45 | setOptions: function(options){ 46 | return this.trigger("setOptions", [options]); 47 | }, 48 | unautocomplete: function() { 49 | return this.trigger("unautocomplete"); 50 | } 51 | }); 52 | 53 | $.Autocompleter = function(input, options) { 54 | 55 | var KEY = { 56 | UP: 38, 57 | DOWN: 40, 58 | DEL: 46, 59 | TAB: 9, 60 | RETURN: 13, 61 | ESC: 27, 62 | COMMA: 188, 63 | PAGEUP: 33, 64 | PAGEDOWN: 34, 65 | BACKSPACE: 8 66 | }; 67 | 68 | // Create $ object for input element 69 | var $input = $(input).attr("autocomplete", "off").addClass(options.inputClass); 70 | 71 | var timeout; 72 | var previousValue = ""; 73 | var cache = $.Autocompleter.Cache(options); 74 | var hasFocus = 0; 75 | var lastKeyPressCode; 76 | var config = { 77 | mouseDownOnSelect: false 78 | }; 79 | var select = $.Autocompleter.Select(options, input, selectCurrent, config); 80 | 81 | var blockSubmit; 82 | 83 | // prevent form submit in opera when selecting with return key 84 | $.browser.opera && $(input.form).bind("submit.autocomplete", function() { 85 | if (blockSubmit) { 86 | blockSubmit = false; 87 | return false; 88 | } 89 | }); 90 | 91 | // only opera doesn't trigger keydown multiple times while pressed, others don't work with keypress at all 92 | $input.bind(($.browser.opera ? "keypress" : "keydown") + ".autocomplete", function(event) { 93 | // track last key pressed 94 | lastKeyPressCode = event.keyCode; 95 | switch(event.keyCode) { 96 | 97 | case KEY.UP: 98 | event.preventDefault(); 99 | if ( select.visible() ) { 100 | select.prev(); 101 | } else { 102 | onChange(0, true); 103 | } 104 | break; 105 | 106 | case KEY.DOWN: 107 | event.preventDefault(); 108 | if ( select.visible() ) { 109 | select.next(); 110 | } else { 111 | onChange(0, true); 112 | } 113 | break; 114 | 115 | case KEY.PAGEUP: 116 | event.preventDefault(); 117 | if ( select.visible() ) { 118 | select.pageUp(); 119 | } else { 120 | onChange(0, true); 121 | } 122 | break; 123 | 124 | case KEY.PAGEDOWN: 125 | event.preventDefault(); 126 | if ( select.visible() ) { 127 | select.pageDown(); 128 | } else { 129 | onChange(0, true); 130 | } 131 | break; 132 | 133 | // matches also semicolon 134 | case options.multiple && $.trim(options.multipleSeparator) == "," && KEY.COMMA: 135 | case KEY.TAB: 136 | case KEY.RETURN: 137 | if( selectCurrent() ) { 138 | // stop default to prevent a form submit, Opera needs special handling 139 | event.preventDefault(); 140 | blockSubmit = true; 141 | return false; 142 | } 143 | break; 144 | 145 | case KEY.ESC: 146 | select.hide(); 147 | break; 148 | 149 | default: 150 | clearTimeout(timeout); 151 | timeout = setTimeout(onChange, options.delay); 152 | break; 153 | } 154 | }).focus(function(){ 155 | // track whether the field has focus, we shouldn't process any 156 | // results if the field no longer has focus 157 | hasFocus++; 158 | }).blur(function() { 159 | hasFocus = 0; 160 | if (!config.mouseDownOnSelect) { 161 | hideResults(); 162 | } 163 | }).click(function() { 164 | // show select when clicking in a focused field 165 | if ( hasFocus++ > 1 && !select.visible() ) { 166 | onChange(0, true); 167 | } 168 | }).bind("search", function() { 169 | // TODO why not just specifying both arguments? 170 | var fn = (arguments.length > 1) ? arguments[1] : null; 171 | function findValueCallback(q, data) { 172 | var result; 173 | if( data && data.length ) { 174 | for (var i=0; i < data.length; i++) { 175 | if( data[i].result.toLowerCase() == q.toLowerCase() ) { 176 | result = data[i]; 177 | break; 178 | } 179 | } 180 | } 181 | if( typeof fn == "function" ) fn(result); 182 | else $input.trigger("result", result && [result.data, result.value]); 183 | } 184 | $.each(trimWords($input.val()), function(i, value) { 185 | request(value, findValueCallback, findValueCallback); 186 | }); 187 | }).bind("flushCache", function() { 188 | cache.flush(); 189 | }).bind("setOptions", function() { 190 | $.extend(options, arguments[1]); 191 | // if we've updated the data, repopulate 192 | if ( "data" in arguments[1] ) 193 | cache.populate(); 194 | }).bind("unautocomplete", function() { 195 | select.unbind(); 196 | $input.unbind(); 197 | $(input.form).unbind(".autocomplete"); 198 | }); 199 | 200 | 201 | function selectCurrent() { 202 | var selected = select.selected(); 203 | if( !selected ) 204 | return false; 205 | 206 | var v = selected.result; 207 | previousValue = v; 208 | 209 | if ( options.multiple ) { 210 | var words = trimWords($input.val()); 211 | if ( words.length > 1 ) { 212 | v = words.slice(0, words.length - 1).join( options.multipleSeparator ) + options.multipleSeparator + v; 213 | } 214 | v += options.multipleSeparator; 215 | } 216 | 217 | $input.val(v); 218 | hideResultsNow(); 219 | $input.trigger("result", [selected.data, selected.value]); 220 | return true; 221 | } 222 | 223 | function onChange(crap, skipPrevCheck) { 224 | if( lastKeyPressCode == KEY.DEL ) { 225 | select.hide(); 226 | return; 227 | } 228 | 229 | var currentValue = $input.val(); 230 | 231 | if ( !skipPrevCheck && currentValue == previousValue ) 232 | return; 233 | 234 | previousValue = currentValue; 235 | 236 | currentValue = lastWord(currentValue); 237 | if ( currentValue.length >= options.minChars) { 238 | $input.addClass(options.loadingClass); 239 | if (!options.matchCase) 240 | currentValue = currentValue.toLowerCase(); 241 | request(currentValue, receiveData, hideResultsNow); 242 | } else { 243 | stopLoading(); 244 | select.hide(); 245 | } 246 | }; 247 | 248 | function trimWords(value) { 249 | if ( !value ) { 250 | return [""]; 251 | } 252 | var words = value.split( options.multipleSeparator ); 253 | var result = []; 254 | $.each(words, function(i, value) { 255 | if ( $.trim(value) ) 256 | result[i] = $.trim(value); 257 | }); 258 | return result; 259 | } 260 | 261 | function lastWord(value) { 262 | if ( !options.multiple ) 263 | return value; 264 | var words = trimWords(value); 265 | return words[words.length - 1]; 266 | } 267 | 268 | // fills in the input box w/the first match (assumed to be the best match) 269 | // q: the term entered 270 | // sValue: the first matching result 271 | function autoFill(q, sValue){ 272 | // autofill in the complete box w/the first match as long as the user hasn't entered in more data 273 | // if the last user key pressed was backspace, don't autofill 274 | if( options.autoFill && (lastWord($input.val()).toLowerCase() == q.toLowerCase()) && lastKeyPressCode != KEY.BACKSPACE ) { 275 | // fill in the value (keep the case the user has typed) 276 | $input.val($input.val() + sValue.substring(lastWord(previousValue).length)); 277 | // select the portion of the value not typed by the user (so the next character will erase) 278 | $.Autocompleter.Selection(input, previousValue.length, previousValue.length + sValue.length); 279 | } 280 | }; 281 | 282 | function hideResults() { 283 | clearTimeout(timeout); 284 | timeout = setTimeout(hideResultsNow, 200); 285 | }; 286 | 287 | function hideResultsNow() { 288 | var wasVisible = select.visible(); 289 | select.hide(); 290 | clearTimeout(timeout); 291 | stopLoading(); 292 | if (options.mustMatch) { 293 | // call search and run callback 294 | $input.search( 295 | function (result){ 296 | // if no value found, clear the input box 297 | if( !result ) { 298 | if (options.multiple) { 299 | var words = trimWords($input.val()).slice(0, -1); 300 | $input.val( words.join(options.multipleSeparator) + (words.length ? options.multipleSeparator : "") ); 301 | } 302 | else 303 | $input.val( "" ); 304 | } 305 | } 306 | ); 307 | } 308 | if (wasVisible) 309 | // position cursor at end of input field 310 | $.Autocompleter.Selection(input, input.value.length, input.value.length); 311 | }; 312 | 313 | function receiveData(q, data) { 314 | if ( data && data.length && hasFocus ) { 315 | stopLoading(); 316 | select.display(data, q); 317 | autoFill(q, data[0].value); 318 | select.show(); 319 | } else { 320 | hideResultsNow(); 321 | } 322 | }; 323 | 324 | function request(term, success, failure) { 325 | if (!options.matchCase) 326 | term = term.toLowerCase(); 327 | var data = cache.load(term); 328 | // recieve the cached data 329 | if (data && data.length) { 330 | success(term, data); 331 | // if an AJAX url has been supplied, try loading the data now 332 | } else if( (typeof options.url == "string") && (options.url.length > 0) ){ 333 | 334 | var extraParams = { 335 | timestamp: +new Date() 336 | }; 337 | $.each(options.extraParams, function(key, param) { 338 | extraParams[key] = typeof param == "function" ? param() : param; 339 | }); 340 | 341 | $.ajax({ 342 | // try to leverage ajaxQueue plugin to abort previous requests 343 | mode: "abort", 344 | // limit abortion to this input 345 | port: "autocomplete" + input.name, 346 | dataType: options.dataType, 347 | url: options.url, 348 | data: $.extend({ 349 | q: lastWord(term), 350 | limit: options.max 351 | }, extraParams), 352 | success: function(data) { 353 | var parsed = options.parse && options.parse(data) || parse(data); 354 | cache.add(term, parsed); 355 | success(term, parsed); 356 | } 357 | }); 358 | } else { 359 | // if we have a failure, we need to empty the list -- this prevents the the [TAB] key from selecting the last successful match 360 | select.emptyList(); 361 | failure(term); 362 | } 363 | }; 364 | 365 | function parse(data) { 366 | var parsed = []; 367 | var rows = data.split("\n"); 368 | for (var i=0; i < rows.length; i++) { 369 | var row = $.trim(rows[i]); 370 | if (row) { 371 | row = row.split("|"); 372 | parsed[parsed.length] = { 373 | data: row, 374 | value: row[0], 375 | result: options.formatResult && options.formatResult(row, row[0]) || row[0] 376 | }; 377 | } 378 | } 379 | return parsed; 380 | }; 381 | 382 | function stopLoading() { 383 | $input.removeClass(options.loadingClass); 384 | }; 385 | 386 | }; 387 | 388 | $.Autocompleter.defaults = { 389 | inputClass: "ac_input", 390 | resultsClass: "ac_results", 391 | loadingClass: "ac_loading", 392 | minChars: 1, 393 | delay: 400, 394 | matchCase: false, 395 | matchSubset: true, 396 | matchContains: false, 397 | cacheLength: 10, 398 | max: 100, 399 | mustMatch: false, 400 | extraParams: {}, 401 | selectFirst: true, 402 | formatItem: function(row) { return row[0]; }, 403 | formatMatch: null, 404 | autoFill: false, 405 | width: 0, 406 | multiple: false, 407 | multipleSeparator: ", ", 408 | highlight: function(value, term) { 409 | return value.replace(new RegExp("(?![^&;]+;)(?!<[^<>]*)(" + term.replace(/([\^\$\(\)\[\]\{\}\*\.\+\?\|\\])/gi, "\\$1") + ")(?![^<>]*>)(?![^&;]+;)", "gi"), "$1"); 410 | }, 411 | scroll: true, 412 | scrollHeight: 180 413 | }; 414 | 415 | $.Autocompleter.Cache = function(options) { 416 | 417 | var data = {}; 418 | var length = 0; 419 | 420 | function matchSubset(s, sub) { 421 | if (!options.matchCase) 422 | s = s.toLowerCase(); 423 | var i = s.indexOf(sub); 424 | if (i == -1) return false; 425 | return i == 0 || options.matchContains; 426 | }; 427 | 428 | function add(q, value) { 429 | if (length > options.cacheLength){ 430 | flush(); 431 | } 432 | if (!data[q]){ 433 | length++; 434 | } 435 | data[q] = value; 436 | } 437 | 438 | function populate(){ 439 | if( !options.data ) return false; 440 | // track the matches 441 | var stMatchSets = {}, 442 | nullData = 0; 443 | 444 | // no url was specified, we need to adjust the cache length to make sure it fits the local data store 445 | if( !options.url ) options.cacheLength = 1; 446 | 447 | // track all options for minChars = 0 448 | stMatchSets[""] = []; 449 | 450 | // loop through the array and create a lookup structure 451 | for ( var i = 0, ol = options.data.length; i < ol; i++ ) { 452 | var rawValue = options.data[i]; 453 | // if rawValue is a string, make an array otherwise just reference the array 454 | rawValue = (typeof rawValue == "string") ? [rawValue] : rawValue; 455 | 456 | var value = options.formatMatch(rawValue, i+1, options.data.length); 457 | if ( value === false ) 458 | continue; 459 | 460 | var firstChar = value.charAt(0).toLowerCase(); 461 | // if no lookup array for this character exists, look it up now 462 | if( !stMatchSets[firstChar] ) 463 | stMatchSets[firstChar] = []; 464 | 465 | // if the match is a string 466 | var row = { 467 | value: value, 468 | data: rawValue, 469 | result: options.formatResult && options.formatResult(rawValue) || value 470 | }; 471 | 472 | // push the current match into the set list 473 | stMatchSets[firstChar].push(row); 474 | 475 | // keep track of minChars zero items 476 | if ( nullData++ < options.max ) { 477 | stMatchSets[""].push(row); 478 | } 479 | }; 480 | 481 | // add the data items to the cache 482 | $.each(stMatchSets, function(i, value) { 483 | // increase the cache size 484 | options.cacheLength++; 485 | // add to the cache 486 | add(i, value); 487 | }); 488 | } 489 | 490 | // populate any existing data 491 | setTimeout(populate, 25); 492 | 493 | function flush(){ 494 | data = {}; 495 | length = 0; 496 | } 497 | 498 | return { 499 | flush: flush, 500 | add: add, 501 | populate: populate, 502 | load: function(q) { 503 | if (!options.cacheLength || !length) 504 | return null; 505 | /* 506 | * if dealing w/local data and matchContains than we must make sure 507 | * to loop through all the data collections looking for matches 508 | */ 509 | if( !options.url && options.matchContains ){ 510 | // track all matches 511 | var csub = []; 512 | // loop through all the data grids for matches 513 | for( var k in data ){ 514 | // don't search through the stMatchSets[""] (minChars: 0) cache 515 | // this prevents duplicates 516 | if( k.length > 0 ){ 517 | var c = data[k]; 518 | $.each(c, function(i, x) { 519 | // if we've got a match, add it to the array 520 | if (matchSubset(x.value, q)) { 521 | csub.push(x); 522 | } 523 | }); 524 | } 525 | } 526 | return csub; 527 | } else 528 | // if the exact item exists, use it 529 | if (data[q]){ 530 | return data[q]; 531 | } else 532 | if (options.matchSubset) { 533 | for (var i = q.length - 1; i >= options.minChars; i--) { 534 | var c = data[q.substr(0, i)]; 535 | if (c) { 536 | var csub = []; 537 | $.each(c, function(i, x) { 538 | if (matchSubset(x.value, q)) { 539 | csub[csub.length] = x; 540 | } 541 | }); 542 | return csub; 543 | } 544 | } 545 | } 546 | return null; 547 | } 548 | }; 549 | }; 550 | 551 | $.Autocompleter.Select = function (options, input, select, config) { 552 | var CLASSES = { 553 | ACTIVE: "ac_over" 554 | }; 555 | 556 | var listItems, 557 | active = -1, 558 | data, 559 | term = "", 560 | needsInit = true, 561 | element, 562 | list; 563 | 564 | // Create results 565 | function init() { 566 | if (!needsInit) 567 | return; 568 | element = $("
") 569 | .hide() 570 | .addClass(options.resultsClass) 571 | .css("position", "absolute") 572 | .appendTo(document.body); 573 | 574 | list = $(" 92 | 93 | 94 |
  • XHProf Overview

    95 | 96 |

    XHProf provides: 97 | 98 |

    167 | 168 |

    Terminology

    169 |
      170 | 171 |
    1. Inclusive Time (or Subtree Time): 172 | Includes time spent in the function as well as in descendant functions 173 | called from a given function. 174 | 175 |
    2. Exclusive Time/Self Time: Measures 176 | time spent in the function itself. Does not include time in descendant 177 | functions. 178 | 179 |
    3. Wall Time: a.k.a. Elapsed time or wall clock time. 180 | 181 |
    4. CPU Time: CPU time in user space + CPU time in kernel space 182 | 183 |
    184 |

    Naming convention for special functions

    185 | 186 |
      187 |

    1. main(): a fictitious function that is at the root of the call graph. 188 | 189 | 190 |

    2. load::<filename> 191 | and run_init::<filename>: 192 | 193 |

      XHProf tracks PHP include/require operations as 194 | function calls. 195 | 196 |

      For example, an include "lib/common.php"; operation will 197 | result in two XHProf function entries: 198 | 199 |

        200 | 201 |
      • load::lib/common.php - This represents the work done by the 202 | interpreter to compile/load the file. [Note: If you are using a PHP 203 | opcode cache like APC, then the compile only happens on a cache miss 204 | in APC.] 205 | 206 |
      • run_init::lib/common.php - This represents 207 | initialization code executed at the file scope as a result of the 208 | include operation. 209 | 210 |
      211 | 212 |

    3. foo@<n>: Implies that this is a 213 | recursive invocation of foo(), where <n> represents 214 | the recursion depth. The recursion may be direct (such as due to 215 | foo() --> foo()), or indirect (such as 216 | due to foo() --> goo() --> foo()). 217 | 218 |
    219 | 220 | 221 |

    Limitations

    222 | 223 |

    True hierarchical profilers keep track of a full call stack at 224 | every data gathering point, and are later able to answer questions 225 | like: what was the cost of the 3rd invokation of foo()? or what was 226 | the cost of bar() when the call stack looked like 227 | a()->b()->bar()? 228 | 229 |

    230 | 231 |

    XHProf keeps track of only 1-level of calling context and is 232 | therefore only able to answer questions about a function looking 233 | either 1-level up or 1-level down. It turns out that in practice this 234 | is sufficient for most use cases. 235 |

    236 | 237 |

    To make this more concrete, take for instance the following 238 | example. 239 |

    240 | 241 |
    242 | Say you have:
    243 |  1 call from a() --> c()
    244 |  1 call from b() --> c()
    245 |  50 calls from c() --> d()
    246 | 
    247 | 248 |

    While XHProf can tell you that d() was called from c() 50 times, it 249 | cannot tell you how many of those calls were triggered due to a() 250 | vs. b(). [We could speculate that perhaps 25 were due to a() and 25 251 | due to b(), but that's not necessarily true.] 252 |

    253 | 254 |

    In practice however, this isn't a very big limitation. 255 |

    256 | 257 |
  • Installing the XHProf Extension

    258 | 259 |

    The extension lives in the "extension/" sub-directory. 260 | 261 |

    283 | 284 |

    The steps 285 | below should work for Linux/Unix environments. 286 | 287 | 288 |

    289 | % cd <xhprof_source_directory>/extension/
    290 | % phpize
    291 | % ./configure --with-php-config=<path to php-config>
    292 | % make
    293 | % make install
    294 | % make test
    295 | 
    296 | 297 | 298 |

    php.ini file: You can update your 299 | php.ini file to automatically load your extension. Add the following 300 | to your php.ini file. 301 | 302 |

    303 | [xhprof]
    304 | extension=xhprof.so
    305 | ;
    306 | ; directory used by default implementation of the iXHProfRuns
    307 | ; interface (namely, the XHProfRuns_Default class) for storing
    308 | ; XHProf runs.
    309 | ;
    310 | xhprof.output_dir=<directory_for_storing_xhprof_runs>
    311 | 
    312 | 313 | 314 |
  • Profiling using XHProf

    315 | 316 |

    Test generating raw profiler data using a sample test program like: 317 | 318 |

    349 | 350 |

    Run the above test program: 351 | 352 |

    353 | % php -dextension=xhprof.so foo.php
    354 | 
    355 | 356 |

    You should get an output like: 357 | 358 |

    359 | Array
    360 | (
    361 |     [foo==>bar] => Array
    362 |         (
    363 |             [ct] => 2         # 2 calls to bar() from foo()
    364 |             [wt] => 27        # inclusive time in bar() when called from foo()
    365 |         )
    366 | 
    367 |     [foo==>strlen] => Array
    368 |         (
    369 |             [ct] => 2
    370 |             [wt] => 2
    371 |         )
    372 | 
    373 |     [bar==>bar@1] => Array    # a recursive call to bar()
    374 |         (
    375 |             [ct] => 1
    376 |             [wt] => 2
    377 |         )
    378 | 
    379 |     [main()==>foo] => Array
    380 |         (
    381 |             [ct] => 1
    382 |             [wt] => 74
    383 |         )
    384 | 
    385 |     [main()==>xhprof_disable] => Array
    386 |         (
    387 |             [ct] => 1
    388 |             [wt] => 0
    389 |         )
    390 | 
    391 |     [main()] => Array         # fake symbol representing root
    392 |         (
    393 |             [ct] => 1
    394 |             [wt] => 83
    395 |         )
    396 | 
    397 | )
    398 | 
    399 | 400 |

    Note: The raw data only contains "inclusive" metrics. For 401 | example, the wall time metric in the raw data represents inclusive 402 | time in microsecs. Exclusive times for any function are computed 403 | during the analysis/reporting phase. 404 | 405 |

    Note: By default only call counts & elapsed time is profiled. 406 | You can optionally also profile CPU time and/or memory usage. Replace, 407 | 408 |

    411 | in the above program with, for example: 412 | 415 | 416 |

    You should now get an output like: 417 | 418 |

    419 | Array
    420 | (
    421 |     [foo==>bar] => Array
    422 |         (
    423 |             [ct] => 2        # number of calls to bar() from foo()
    424 |             [wt] => 37       # time in bar() when called from foo()
    425 |             [cpu] => 0       # cpu time in bar() when called from foo()
    426 |             [mu] => 2208     # change in PHP memory usage in bar() when called from foo()
    427 |             [pmu] => 0       # change in PHP peak memory usage in bar() when called from foo()
    428 |         )
    429 | 
    430 |     [foo==>strlen] => Array
    431 |         (
    432 |             [ct] => 2
    433 |             [wt] => 3
    434 |             [cpu] => 0
    435 |             [mu] => 624
    436 |             [pmu] => 0
    437 |         )
    438 | 
    439 |     [bar==>bar@1] => Array
    440 |         (
    441 |             [ct] => 1
    442 |             [wt] => 2
    443 |             [cpu] => 0
    444 |             [mu] => 856
    445 |             [pmu] => 0
    446 |         )
    447 | 
    448 |     [main()==>foo] => Array
    449 |         (
    450 |             [ct] => 1
    451 |             [wt] => 104
    452 |             [cpu] => 0
    453 |             [mu] => 4168
    454 |             [pmu] => 0
    455 |         )
    456 | 
    457 |     [main()==>xhprof_disable] => Array
    458 |         (
    459 |             [ct] => 1
    460 |             [wt] => 1
    461 |             [cpu] => 0
    462 |             [mu] => 344
    463 |             [pmu] => 0
    464 |         )
    465 | 
    466 |     [main()] => Array
    467 |         (
    468 |             [ct] => 1
    469 |             [wt] => 139
    470 |             [cpu] => 0
    471 |             [mu] => 5936
    472 |             [pmu] => 0
    473 |         )
    474 | 
    475 | )
    476 | 
    477 | 478 |

    Skipping builtin functions during profiling 479 | 480 |

    By default PHP builtin functions (such as strlen) are 481 | profiled. If you do not want to profile builtin functions (to either 482 | reduce the overhead of profiling further or size of generated raw 483 | data), you can use the XHPROF_FLAGS_NO_BUILTINS 484 | flag as in for example: 485 | 486 |

    490 | 491 | 492 |

    Ignoring specific functions during profiling (0.9.2 or higher) 493 | 494 |

    Starting with release 0.9.2 of xhprof, you can tell XHProf to 495 | ignore a specified list of functions during profiling. This allows you 496 | to ignore, for example, functions used for indirect function calls 497 | such as call_user_func and 498 | call_user_func_array. These intermediate functions 499 | unnecessarily complicate the call hierarchy and make the XHProf 500 | reports harder to interpret since they muddle the parent-child 501 | relationship for functions called indirectly. 502 | 503 |

    To specify the list of functions to be ignored during profiling 504 | use the 2nd (optional) argument to xhprof_enable. 505 | For example, 506 | 507 |

    508 | 
      509 | 
      510 | // elapsed time profiling; ignore call_user_func* during profiling
      511 | xhprof_enable(0,
      512 |              array('ignored_functions' =>  array('call_user_func',
      513 |                                                  'call_user_func_array')));
      514 | 
      515 | or,
      516 | 
      517 | // elapsed time + memory profiling; ignore call_user_func* during profiling
      518 | xhprof_enable(XHPROF_FLAGS_MEMORY,
      519 |               array('ignored_functions' =>  array('call_user_func',
      520 |                                                   'call_user_func_array')));
      521 | 
      522 | 
    523 | 524 | 525 |
  • 526 | 527 |
  • Setting up XHProf UI

    528 | 529 | 530 |
      531 | 532 |
    1. PHP source structure 533 |

      The XHProf UI is implemented in PHP. The code resides in two 534 | subdirectories, xhprof_html/ and xhprof_lib/. 535 | 536 |

      The xhprof_html directory contains the 3 top-level PHP pages. 537 | 538 |

        539 |
      • index.php: For viewing a single run or diff report. 540 |
      • callgraph.php: For viewing a callgraph of a XHProf run as an image. 541 |
      • typeahead.php: Used implicitly for the function typeahead form 542 | on a XHProf report. 543 |
      544 | 545 |

      The xhprof_lib directory contains supporting code for 546 | display as well as analysis (computing flat profile info, computing 547 | diffs, aggregating data from multiple runs, etc.). 548 | 549 |

    2. Web server config: You'll need to make sure that the 550 | xhprof_html/ directory is accessible from your web server, and that 551 | your web server is setup to serve PHP scripts. 552 | 553 |

    3. Managing XHProf Runs 554 | 555 |

      Clients have flexibility in how they save the XHProf raw data 556 | obtained from an XHProf run. The XHProf UI layer exposes an interface 557 | iXHProfRuns (see xhprof_lib/utils/xhprof_runs.php) that clients can 558 | implement. This allows the clients to tell the UI layer how to fetch 559 | the data corresponding to a XHProf run. 560 | 561 |

      The XHProf UI libaries come with a default file based 562 | implementation of the iXHProfRuns interface, namely 563 | "XHProfRuns_Default" (also in xhprof_lib/utils/xhprof_runs.php). 564 | This default implementation stores runs in the directory specified by 565 | xhprof.output_dir INI parameter. 566 | 567 |

      A XHProf run must be uniquely identified by a namespace and a run 568 | id. 569 | 570 | 571 | 572 |

      a) Saving XHProf data persistently: 573 | 574 |

      Assuming you are using the default implementation 575 | XHProfRuns_Default of the 576 | iXHProfRuns interface, a typical XHProf run 577 | followed by the save step might look something like: 578 | 579 | 580 |

      581 | // start profiling
      582 | xhprof_enable();
      583 | 
      584 | // run program
      585 | ....
      586 | 
      587 | // stop profiler
      588 | $xhprof_data = xhprof_disable();
      589 | 
      590 | //
      591 | // Saving the XHProf run
      592 | // using the default implementation of iXHProfRuns.
      593 | //
      594 | include_once $XHPROF_ROOT . "/xhprof_lib/utils/xhprof_lib.php";
      595 | include_once $XHPROF_ROOT . "/xhprof_lib/utils/xhprof_runs.php";
      596 | 
      597 | $xhprof_runs = new XHProfRuns_Default();
      598 | 
      599 | // Save the run under a namespace "xhprof_foo".
      600 | //
      601 | // **NOTE**:
      602 | // By default save_run() will automatically generate a unique
      603 | // run id for you. [You can override that behavior by passing
      604 | // a run id (optional arg) to the save_run() method instead.]
      605 | //
      606 | $run_id = $xhprof_runs->save_run($xhprof_data, "xhprof_foo");
      607 | 
      608 | echo "---------------\n".
      609 |      "Assuming you have set up the http based UI for \n".
      610 |      "XHProf at some address, you can view run at \n".
      611 |      "http://<xhprof-ui-address>/index.php?run=$run_id&source=xhprof_foo\n".
      612 |      "---------------\n";
      613 | 
      614 | 
      615 | 616 |

      The above should save the run as a file in the directory specified 617 | by the xhprof.output_dir INI parameter. The file's 618 | name might be something like 619 | 49bafaa3a3f66.xhprof_foo; the two parts being the 620 | run id ("49bafaa3a3f66") and the namespace ("xhprof_foo"). [If you 621 | want to create/assign run ids yourself (such as a database sequence 622 | number, or a timestamp), you can explicitly pass in the run id to the 623 | save_run method. 624 | 625 | 626 |

      b) Using your own implementation of iXHProfRuns 627 | 628 |

      If you decide you want your XHProf runs to be stored differently 629 | (either in a compressed format, in an alternate place such as DB, 630 | etc.) database, you'll need to implement a class that implements the 631 | iXHProfRuns() interface. 632 | 633 |

      You'll also need to modify the 3 main PHP entry pages (index.php, 634 | callgraph.php, typeahead.php) in the "xhprof_html/" directory to use 635 | the new class instead of the default class XHProfRuns_Default. 636 | Change this line in the 3 files. 637 | 638 |

      639 | $xhprof_runs_impl = new XHProfRuns_Default();
      640 | 
      641 | 642 |

      You'll also need to "include" the file that implements your class in 643 | the above files. 644 | 645 | 646 |

    4. Accessing runs from UI 647 | 648 |

      a) Viewing a Single Run Report 649 | 650 |

      To view the report for run id say <run_id> and namespace 651 | <namespace> use a URL of the form: 652 | 653 |

      654 | http://<xhprof-ui-address>/index.php?run=<run_id>&source=<namespace> 655 | 656 | 657 |

      For example, 658 |

      659 | http://<xhprof-ui-address>/index.php?run=49bafaa3a3f66&source=xhprof_foo 660 | 661 | 662 | 663 |

      b) Viewing a Diff Report 664 | 665 |

      To view the report for run ids say <run_id1> and 666 | <run_id2> in namespace <namespace> use a URL of the form: 667 | 668 |

      669 | http://<xhprof-ui-address>/index.php?run1=<run_id1>&run2=<run_id2>&source=<namespace> 670 | 671 | 672 |

      c) Aggregate Report 673 | 674 |

      You can also specify a set of run ids for which you want an aggregated view/report. 675 | 676 |

      Say you have three XHProf runs with ids 1, 2 & 3 in namespace 677 | "benchmark". To view an aggregate report of these runs: 678 | 679 |

        680 | http://<xhprof-ui-address>/index.php?run=1,2,3&source=benchmark 681 |

      682 | 683 |

      Weighted aggregations: Further suppose that the above three runs 684 | correspond to three types of programs p1.php, p2.php and p3.php that 685 | typically occur in a mix of 20%, 30%, 50% respectively. To view an 686 | aggregate report that corresponds to a weighted average of these runs 687 | using: 688 | 689 |

        690 | http://<xhprof-ui-address>/index.php?run=1,2,3&wts=20,30,50&source=benchmark 691 |

      692 | 693 | 694 |
    695 | 696 |
  • Notes on using XHProf in production

    697 | 698 |

    Some observations/guidelines. Your mileage may vary: 699 | 700 |

    749 | 750 | 751 |
  • Lightweight Sampling Mode

    752 | 753 |

    The xhprof extension also provides a very light weight sampling 754 | mode. The sampling interval is 0.1 secs. Samples record the full 755 | function call stack. The sampling mode can be useful if an extremely 756 | low overhead means of doing performance monitoring and diagnostics is 757 | desired. 758 | 759 |

    The relevant functions exposed by the extension for using the 760 | sampling mode are xhprof_sample_enable() and 761 | xhprof_sample_disable(). 762 | 763 | 764 |

    [TBD: more detailed documentation on sampling mode.] 765 | 766 | 767 |

  • Additional Features

  • 768 | 769 |

    The xhprof_lib/utils/xhprof_lib.php file contains 770 | additional library functions that can be used for manipulating/ 771 | aggregating XHProf runs. 772 | 773 |

    For example: 774 | 775 |

    793 | 794 | 795 |
      796 | 797 |
    798 | 799 |
  • Dependencies

  • 800 | 801 | 814 |
  • Acknowledgements

    815 | 816 |

    The HTML-based navigational interface for browsing profiler results 817 | is inspired by that of a similar tool that exists for Oracle's stored 818 | procedure language, PL/SQL. But that's where the similarity ends; the 819 | internals of the profiler itself are quite different. 820 | 821 |

  • 822 | 823 | 824 | 825 | 826 | 827 | -------------------------------------------------------------------------------- /src/xhprof/xhprof_lib/utils/xhprof_lib.php: -------------------------------------------------------------------------------- 1 | array("Wall", "microsecs", "walltime"), 37 | "ut" => array("User", "microsecs", "user cpu time"), 38 | "st" => array("Sys", "microsecs", "system cpu time"), 39 | "cpu" => array("Cpu", "microsecs", "cpu time"), 40 | "mu" => array("MUse", "bytes", "memory usage"), 41 | "pmu" => array("PMUse", "bytes", "peak memory usage"), 42 | "samples" => array("Samples", "samples", "cpu time")); 43 | return $possible_metrics; 44 | } 45 | 46 | /** 47 | * Initialize the metrics we'll display based on the information 48 | * in the raw data. 49 | * 50 | * @author Kannan 51 | */ 52 | function init_metrics($xhprof_data, $rep_symbol, $sort, $diff_report = false) { 53 | global $stats; 54 | global $pc_stats; 55 | global $metrics; 56 | global $diff_mode; 57 | global $sortable_columns; 58 | global $sort_col; 59 | global $display_calls; 60 | 61 | $diff_mode = $diff_report; 62 | 63 | if (!empty($sort)) { 64 | if (array_key_exists($sort, $sortable_columns)) { 65 | $sort_col = $sort; 66 | } else { 67 | print("Invalid Sort Key $sort specified in URL"); 68 | } 69 | } 70 | 71 | // For C++ profiler runs, walltime attribute isn't present. 72 | // In that case, use "samples" as the default sort column. 73 | if (!isset($xhprof_data["main()"]["wt"])) { 74 | 75 | if ($sort_col == "wt") { 76 | $sort_col = "samples"; 77 | } 78 | 79 | // C++ profiler data doesn't have call counts. 80 | // ideally we should check to see if "ct" metric 81 | // is present for "main()". But currently "ct" 82 | // metric is artificially set to 1. So, relying 83 | // on absence of "wt" metric instead. 84 | $display_calls = false; 85 | } else { 86 | $display_calls = true; 87 | } 88 | 89 | // parent/child report doesn't support exclusive times yet. 90 | // So, change sort hyperlinks to closest fit. 91 | if (!empty($rep_symbol)) { 92 | $sort_col = str_replace("excl_", "", $sort_col); 93 | } 94 | 95 | if ($display_calls) { 96 | $stats = array("fn", "ct", "Calls%"); 97 | } else { 98 | $stats = array("fn"); 99 | } 100 | 101 | $pc_stats = $stats; 102 | 103 | $possible_metrics = xhprof_get_possible_metrics(); 104 | foreach ($possible_metrics as $metric => $desc) { 105 | if (isset($xhprof_data["main()"][$metric])) { 106 | $metrics[] = $metric; 107 | // flat (top-level reports): we can compute 108 | // exclusive metrics reports as well. 109 | $stats[] = $metric; 110 | $stats[] = "I" . $desc[0] . "%"; 111 | $stats[] = "excl_" . $metric; 112 | $stats[] = "E" . $desc[0] . "%"; 113 | 114 | // parent/child report for a function: we can 115 | // only breakdown inclusive times correctly. 116 | $pc_stats[] = $metric; 117 | $pc_stats[] = "I" . $desc[0] . "%"; 118 | } 119 | } 120 | } 121 | 122 | /* 123 | * Get the list of metrics present in $xhprof_data as an array. 124 | * 125 | * @author Kannan 126 | */ 127 | function xhprof_get_metrics($xhprof_data) { 128 | 129 | // get list of valid metrics 130 | $possible_metrics = xhprof_get_possible_metrics(); 131 | 132 | // return those that are present in the raw data. 133 | // We'll just look at the root of the subtree for this. 134 | $metrics = array(); 135 | foreach ($possible_metrics as $metric => $desc) { 136 | if (isset($xhprof_data["main()"][$metric])) { 137 | $metrics[] = $metric; 138 | } 139 | } 140 | 141 | return $metrics; 142 | } 143 | 144 | /** 145 | * Takes a parent/child function name encoded as 146 | * "a==>b" and returns array("a", "b"). 147 | * 148 | * @author Kannan 149 | */ 150 | function xhprof_parse_parent_child($parent_child) { 151 | $ret = explode("==>", $parent_child); 152 | 153 | // Return if both parent and child are set 154 | if (isset($ret[1])) { 155 | return $ret; 156 | } 157 | 158 | return array(null, $ret[0]); 159 | } 160 | 161 | /** 162 | * Given parent & child function name, composes the key 163 | * in the format present in the raw data. 164 | * 165 | * @author Kannan 166 | */ 167 | function xhprof_build_parent_child_key($parent, $child) { 168 | if ($parent) { 169 | return $parent . "==>" . $child; 170 | } else { 171 | return $child; 172 | } 173 | } 174 | 175 | 176 | /** 177 | * Checks if XHProf raw data appears to be valid and not corrupted. 178 | * 179 | * @param int $run_id Run id of run to be pruned. 180 | * [Used only for reporting errors.] 181 | * @param array $raw_data XHProf raw data to be pruned 182 | * & validated. 183 | * 184 | * @return bool true on success, false on failure 185 | * 186 | * @author Kannan 187 | */ 188 | function xhprof_valid_run($run_id, $raw_data) { 189 | 190 | $main_info = $raw_data["main()"]; 191 | if (empty($main_info)) { 192 | xhprof_error("XHProf: main() missing in raw data for Run ID: $run_id"); 193 | return false; 194 | } 195 | 196 | // raw data should contain either wall time or samples information... 197 | if (isset($main_info["wt"])) { 198 | $metric = "wt"; 199 | } else if (isset($main_info["samples"])) { 200 | $metric = "samples"; 201 | } else { 202 | xhprof_error("XHProf: Wall Time information missing from Run ID: $run_id"); 203 | return false; 204 | } 205 | 206 | foreach ($raw_data as $info) { 207 | $val = $info[$metric]; 208 | 209 | // basic sanity checks... 210 | if ($val < 0) { 211 | xhprof_error("XHProf: $metric should not be negative: Run ID $run_id" 212 | . serialize($info)); 213 | return false; 214 | } 215 | if ($val > (86400000000)) { 216 | xhprof_error("XHProf: $metric > 1 day found in Run ID: $run_id " 217 | . serialize($info)); 218 | return false; 219 | } 220 | } 221 | return true; 222 | } 223 | 224 | 225 | /** 226 | * Return a trimmed version of the XHProf raw data. Note that the raw 227 | * data contains one entry for each unique parent/child function 228 | * combination.The trimmed version of raw data will only contain 229 | * entries where either the parent or child function is in the list 230 | * of $functions_to_keep. 231 | * 232 | * Note: Function main() is also always kept so that overall totals 233 | * can still be obtained from the trimmed version. 234 | * 235 | * @param array XHProf raw data 236 | * @param array array of function names 237 | * 238 | * @return array Trimmed XHProf Report 239 | * 240 | * @author Kannan 241 | */ 242 | function xhprof_trim_run($raw_data, $functions_to_keep) { 243 | 244 | // convert list of functions to a hash with function as the key 245 | $function_map = array_fill_keys($functions_to_keep, 1); 246 | 247 | // always keep main() as well so that overall totals can still 248 | // be computed if need be. 249 | $function_map['main()'] = 1; 250 | 251 | $new_raw_data = array(); 252 | foreach ($raw_data as $parent_child => $info) { 253 | list($parent, $child) = xhprof_parse_parent_child($parent_child); 254 | 255 | if (isset($function_map[$parent]) || isset($function_map[$child])) { 256 | $new_raw_data[$parent_child] = $info; 257 | } 258 | } 259 | 260 | return $new_raw_data; 261 | } 262 | 263 | /** 264 | * Takes raw XHProf data that was aggregated over "$num_runs" number 265 | * of runs averages/nomalizes the data. Essentially the various metrics 266 | * collected are divided by $num_runs. 267 | * 268 | * @author Kannan 269 | */ 270 | function xhprof_normalize_metrics($raw_data, $num_runs) { 271 | 272 | if (empty($raw_data) || ($num_runs == 0)) { 273 | return $raw_data; 274 | } 275 | 276 | $raw_data_total = array(); 277 | 278 | if (isset($raw_data["==>main()"]) && isset($raw_data["main()"])) { 279 | xhprof_error("XHProf Error: both ==>main() and main() set in raw data..."); 280 | } 281 | 282 | foreach ($raw_data as $parent_child => $info) { 283 | foreach ($info as $metric => $value) { 284 | $raw_data_total[$parent_child][$metric] = ($value / $num_runs); 285 | } 286 | } 287 | 288 | return $raw_data_total; 289 | } 290 | 291 | 292 | /** 293 | * Get raw data corresponding to specified array of runs 294 | * aggregated by certain weightage. 295 | * 296 | * Suppose you have run:5 corresponding to page1.php, 297 | * run:6 corresponding to page2.php, 298 | * and run:7 corresponding to page3.php 299 | * 300 | * and you want to accumulate these runs in a 2:4:1 ratio. You 301 | * can do so by calling: 302 | * 303 | * xhprof_aggregate_runs(array(5, 6, 7), array(2, 4, 1)); 304 | * 305 | * The above will return raw data for the runs aggregated 306 | * in 2:4:1 ratio. 307 | * 308 | * @param object $xhprof_runs_impl An object that implements 309 | * the iXHProfRuns interface 310 | * @param array $runs run ids of the XHProf runs.. 311 | * @param array $wts integral (ideally) weights for $runs 312 | * @param string $source source to fetch raw data for run from 313 | * @param bool $use_script_name If true, a fake edge from main() to 314 | * to __script:: is introduced 315 | * in the raw data so that after aggregations 316 | * the script name is still preserved. 317 | * 318 | * @return array Return aggregated raw data 319 | * 320 | * @author Kannan 321 | */ 322 | function xhprof_aggregate_runs($xhprof_runs_impl, $runs, 323 | $wts, $source="phprof", 324 | $use_script_name=false) { 325 | 326 | $raw_data_total = null; 327 | $raw_data = null; 328 | $metrics = array(); 329 | 330 | $run_count = count($runs); 331 | $wts_count = count($wts); 332 | 333 | if (($run_count == 0) || 334 | (($wts_count > 0) && ($run_count != $wts_count))) { 335 | return array('description' => 'Invalid input..', 336 | 'raw' => null); 337 | } 338 | 339 | $bad_runs = array(); 340 | foreach ($runs as $idx => $run_id) { 341 | 342 | $raw_data = $xhprof_runs_impl->get_run($run_id, $source, $description); 343 | 344 | // use the first run to derive what metrics to aggregate on. 345 | if ($idx == 0) { 346 | foreach ($raw_data["main()"] as $metric => $val) { 347 | if ($metric != "pmu") { 348 | // for now, just to keep data size small, skip "peak" memory usage 349 | // data while aggregating. 350 | // The "regular" memory usage data will still be tracked. 351 | if (isset($val)) { 352 | $metrics[] = $metric; 353 | } 354 | } 355 | } 356 | } 357 | 358 | if (!xhprof_valid_run($run_id, $raw_data)) { 359 | $bad_runs[] = $run_id; 360 | continue; 361 | } 362 | 363 | if ($use_script_name) { 364 | $page = $description; 365 | 366 | // create a fake function '__script::$page', and have and edge from 367 | // main() to '__script::$page'. We will also need edges to transfer 368 | // all edges originating from main() to now originate from 369 | // '__script::$page' to all function called from main(). 370 | // 371 | // We also weight main() ever so slightly higher so that 372 | // it shows up above the new entry in reports sorted by 373 | // inclusive metrics or call counts. 374 | if ($page) { 375 | foreach ($raw_data["main()"] as $metric => $val) { 376 | $fake_edge[$metric] = $val; 377 | $new_main[$metric] = $val + 0.00001; 378 | } 379 | $raw_data["main()"] = $new_main; 380 | $raw_data[xhprof_build_parent_child_key("main()", 381 | "__script::$page")] 382 | = $fake_edge; 383 | } else { 384 | $use_script_name = false; 385 | } 386 | } 387 | 388 | // if no weights specified, use 1 as the default weightage.. 389 | $wt = ($wts_count == 0) ? 1 : $wts[$idx]; 390 | 391 | // aggregate $raw_data into $raw_data_total with appropriate weight ($wt) 392 | foreach ($raw_data as $parent_child => $info) { 393 | if ($use_script_name) { 394 | // if this is an old edge originating from main(), it now 395 | // needs to be from '__script::$page' 396 | if (substr($parent_child, 0, 9) == "main()==>") { 397 | $child = substr($parent_child, 9); 398 | // ignore the newly added edge from main() 399 | if (substr($child, 0, 10) != "__script::") { 400 | $parent_child = xhprof_build_parent_child_key("__script::$page", 401 | $child); 402 | } 403 | } 404 | } 405 | 406 | if (!isset($raw_data_total[$parent_child])) { 407 | foreach ($metrics as $metric) { 408 | $raw_data_total[$parent_child][$metric] = ($wt * $info[$metric]); 409 | } 410 | } else { 411 | foreach ($metrics as $metric) { 412 | $raw_data_total[$parent_child][$metric] += ($wt * $info[$metric]); 413 | } 414 | } 415 | } 416 | } 417 | 418 | $runs_string = implode(",", $runs); 419 | 420 | if (isset($wts)) { 421 | $wts_string = "in the ratio (" . implode(":", $wts) . ")"; 422 | $normalization_count = array_sum($wts); 423 | } else { 424 | $wts_string = ""; 425 | $normalization_count = $run_count; 426 | } 427 | 428 | $run_count = $run_count - count($bad_runs); 429 | 430 | $data['description'] = "Aggregated Report for $run_count runs: ". 431 | "$runs_string $wts_string\n"; 432 | $data['raw'] = xhprof_normalize_metrics($raw_data_total, 433 | $normalization_count); 434 | $data['bad_runs'] = $bad_runs; 435 | 436 | return $data; 437 | } 438 | 439 | 440 | /** 441 | * Analyze hierarchical raw data, and compute per-function (flat) 442 | * inclusive and exclusive metrics. 443 | * 444 | * Also, store overall totals in the 2nd argument. 445 | * 446 | * @param array $raw_data XHProf format raw profiler data. 447 | * @param array &$overall_totals OUT argument for returning 448 | * overall totals for various 449 | * metrics. 450 | * @return array Returns a map from function name to its 451 | * call count and inclusive & exclusive metrics 452 | * (such as wall time, etc.). 453 | * 454 | * @author Kannan Muthukkaruppan 455 | */ 456 | function xhprof_compute_flat_info($raw_data, &$overall_totals) { 457 | 458 | global $display_calls; 459 | 460 | $metrics = xhprof_get_metrics($raw_data); 461 | 462 | $overall_totals = array("ct" => 0, 463 | "wt" => 0, 464 | "ut" => 0, 465 | "st" => 0, 466 | "cpu" => 0, 467 | "mu" => 0, 468 | "pmu" => 0, 469 | "samples" => 0 470 | ); 471 | 472 | // compute inclusive times for each function 473 | $symbol_tab = xhprof_compute_inclusive_times($raw_data); 474 | 475 | /* total metric value is the metric value for "main()" */ 476 | foreach ($metrics as $metric) { 477 | $overall_totals[$metric] = $symbol_tab["main()"][$metric]; 478 | } 479 | 480 | /* 481 | * initialize exclusive (self) metric value to inclusive metric value 482 | * to start with. 483 | * In the same pass, also add up the total number of function calls. 484 | */ 485 | foreach ($symbol_tab as $symbol => $info) { 486 | foreach ($metrics as $metric) { 487 | $symbol_tab[$symbol]["excl_" . $metric] = $symbol_tab[$symbol][$metric]; 488 | } 489 | if ($display_calls) { 490 | /* keep track of total number of calls */ 491 | $overall_totals["ct"] += $info["ct"]; 492 | } 493 | } 494 | 495 | /* adjust exclusive times by deducting inclusive time of children */ 496 | foreach ($raw_data as $parent_child => $info) { 497 | list($parent, $child) = xhprof_parse_parent_child($parent_child); 498 | 499 | if ($parent) { 500 | foreach ($metrics as $metric) { 501 | // make sure the parent exists hasn't been pruned. 502 | if (isset($symbol_tab[$parent])) { 503 | $symbol_tab[$parent]["excl_" . $metric] -= $info[$metric]; 504 | } 505 | } 506 | } 507 | } 508 | 509 | return $symbol_tab; 510 | } 511 | 512 | /** 513 | * Hierarchical diff: 514 | * Compute and return difference of two call graphs: Run2 - Run1. 515 | * 516 | * @author Kannan 517 | */ 518 | function xhprof_compute_diff($xhprof_data1, $xhprof_data2) { 519 | global $display_calls; 520 | 521 | // use the second run to decide what metrics we will do the diff on 522 | $metrics = xhprof_get_metrics($xhprof_data2); 523 | 524 | $xhprof_delta = $xhprof_data2; 525 | 526 | foreach ($xhprof_data1 as $parent_child => $info) { 527 | 528 | if (!isset($xhprof_delta[$parent_child])) { 529 | 530 | // this pc combination was not present in run1; 531 | // initialize all values to zero. 532 | if ($display_calls) { 533 | $xhprof_delta[$parent_child] = array("ct" => 0); 534 | } else { 535 | $xhprof_delta[$parent_child] = array(); 536 | } 537 | foreach ($metrics as $metric) { 538 | $xhprof_delta[$parent_child][$metric] = 0; 539 | } 540 | } 541 | 542 | if ($display_calls) { 543 | $xhprof_delta[$parent_child]["ct"] -= $info["ct"]; 544 | } 545 | 546 | foreach ($metrics as $metric) { 547 | $xhprof_delta[$parent_child][$metric] -= $info[$metric]; 548 | } 549 | } 550 | 551 | return $xhprof_delta; 552 | } 553 | 554 | 555 | /** 556 | * Compute inclusive metrics for function. This code was factored out 557 | * of xhprof_compute_flat_info(). 558 | * 559 | * The raw data contains inclusive metrics of a function for each 560 | * unique parent function it is called from. The total inclusive metrics 561 | * for a function is therefore the sum of inclusive metrics for the 562 | * function across all parents. 563 | * 564 | * @return array Returns a map of function name to total (across all parents) 565 | * inclusive metrics for the function. 566 | * 567 | * @author Kannan 568 | */ 569 | function xhprof_compute_inclusive_times($raw_data) { 570 | global $display_calls; 571 | 572 | $metrics = xhprof_get_metrics($raw_data); 573 | 574 | $symbol_tab = array(); 575 | 576 | /* 577 | * First compute inclusive time for each function and total 578 | * call count for each function across all parents the 579 | * function is called from. 580 | */ 581 | foreach ($raw_data as $parent_child => $info) { 582 | 583 | list($parent, $child) = xhprof_parse_parent_child($parent_child); 584 | 585 | if ($parent == $child) { 586 | /* 587 | * XHProf PHP extension should never trigger this situation any more. 588 | * Recursion is handled in the XHProf PHP extension by giving nested 589 | * calls a unique recursion-depth appended name (for example, foo@1). 590 | */ 591 | xhprof_error("Error in Raw Data: parent & child are both: $parent"); 592 | return; 593 | } 594 | 595 | if (!isset($symbol_tab[$child])) { 596 | 597 | if ($display_calls) { 598 | $symbol_tab[$child] = array("ct" => $info["ct"]); 599 | } else { 600 | $symbol_tab[$child] = array(); 601 | } 602 | foreach ($metrics as $metric) { 603 | $symbol_tab[$child][$metric] = $info[$metric]; 604 | } 605 | } else { 606 | if ($display_calls) { 607 | /* increment call count for this child */ 608 | $symbol_tab[$child]["ct"] += $info["ct"]; 609 | } 610 | 611 | /* update inclusive times/metric for this child */ 612 | foreach ($metrics as $metric) { 613 | $symbol_tab[$child][$metric] += $info[$metric]; 614 | } 615 | } 616 | } 617 | 618 | return $symbol_tab; 619 | } 620 | 621 | 622 | /* 623 | * Prunes XHProf raw data: 624 | * 625 | * Any node whose inclusive walltime accounts for less than $prune_percent 626 | * of total walltime is pruned. [It is possible that a child function isn't 627 | * pruned, but one or more of its parents get pruned. In such cases, when 628 | * viewing the child function's hierarchical information, the cost due to 629 | * the pruned parent(s) will be attributed to a special function/symbol 630 | * "__pruned__()".] 631 | * 632 | * @param array $raw_data XHProf raw data to be pruned & validated. 633 | * @param double $prune_percent Any edges that account for less than 634 | * $prune_percent of time will be pruned 635 | * from the raw data. 636 | * 637 | * @return array Returns the pruned raw data. 638 | * 639 | * @author Kannan 640 | */ 641 | function xhprof_prune_run($raw_data, $prune_percent) { 642 | 643 | $main_info = $raw_data["main()"]; 644 | if (empty($main_info)) { 645 | xhprof_error("XHProf: main() missing in raw data"); 646 | return false; 647 | } 648 | 649 | // raw data should contain either wall time or samples information... 650 | if (isset($main_info["wt"])) { 651 | $prune_metric = "wt"; 652 | } else if (isset($main_info["samples"])) { 653 | $prune_metric = "samples"; 654 | } else { 655 | xhprof_error("XHProf: for main() we must have either wt " 656 | ."or samples attribute set"); 657 | return false; 658 | } 659 | 660 | // determine the metrics present in the raw data.. 661 | $metrics = array(); 662 | foreach ($main_info as $metric => $val) { 663 | if (isset($val)) { 664 | $metrics[] = $metric; 665 | } 666 | } 667 | 668 | $prune_threshold = (($main_info[$prune_metric] * $prune_percent) / 100.0); 669 | 670 | init_metrics($raw_data, null, null, false); 671 | $flat_info = xhprof_compute_inclusive_times($raw_data); 672 | 673 | foreach ($raw_data as $parent_child => $info) { 674 | 675 | list($parent, $child) = xhprof_parse_parent_child($parent_child); 676 | 677 | // is this child's overall total from all parents less than threshold? 678 | if ($flat_info[$child][$prune_metric] < $prune_threshold) { 679 | unset($raw_data[$parent_child]); // prune the edge 680 | } else if ($parent && 681 | ($parent != "__pruned__()") && 682 | ($flat_info[$parent][$prune_metric] < $prune_threshold)) { 683 | 684 | // Parent's overall inclusive metric is less than a threshold. 685 | // All edges to the parent node will get nuked, and this child will 686 | // be a dangling child. 687 | // So instead change its parent to be a special function __pruned__(). 688 | $pruned_edge = xhprof_build_parent_child_key("__pruned__()", $child); 689 | 690 | if (isset($raw_data[$pruned_edge])) { 691 | foreach ($metrics as $metric) { 692 | $raw_data[$pruned_edge][$metric]+=$raw_data[$parent_child][$metric]; 693 | } 694 | } else { 695 | $raw_data[$pruned_edge] = $raw_data[$parent_child]; 696 | } 697 | 698 | unset($raw_data[$parent_child]); // prune the edge 699 | } 700 | } 701 | 702 | return $raw_data; 703 | } 704 | 705 | 706 | /** 707 | * Set one key in an array and return the array 708 | * 709 | * @author Kannan 710 | */ 711 | function xhprof_array_set($arr, $k, $v) { 712 | $arr[$k] = $v; 713 | return $arr; 714 | } 715 | 716 | /** 717 | * Removes/unsets one key in an array and return the array 718 | * 719 | * @author Kannan 720 | */ 721 | function xhprof_array_unset($arr, $k) { 722 | unset($arr[$k]); 723 | return $arr; 724 | } 725 | 726 | /** 727 | * Type definitions for URL params 728 | */ 729 | define('XHPROF_STRING_PARAM', 1); 730 | define('XHPROF_UINT_PARAM', 2); 731 | define('XHPROF_FLOAT_PARAM', 3); 732 | define('XHPROF_BOOL_PARAM', 4); 733 | 734 | 735 | /** 736 | * Internal helper function used by various 737 | * xhprof_get_param* flavors for various 738 | * types of parameters. 739 | * 740 | * @param string name of the URL query string param 741 | * 742 | * @author Kannan 743 | */ 744 | function xhprof_get_param_helper($param) { 745 | $val = null; 746 | if (isset($_GET[$param])) 747 | $val = $_GET[$param]; 748 | else if (isset($_POST[$param])) { 749 | $val = $_POST[$param]; 750 | } 751 | return $val; 752 | } 753 | 754 | /** 755 | * Extracts value for string param $param from query 756 | * string. If param is not specified, return the 757 | * $default value. 758 | * 759 | * @author Kannan 760 | */ 761 | function xhprof_get_string_param($param, $default = '') { 762 | $val = xhprof_get_param_helper($param); 763 | 764 | if ($val === null) 765 | return $default; 766 | 767 | return $val; 768 | } 769 | 770 | /** 771 | * Extracts value for unsigned integer param $param from 772 | * query string. If param is not specified, return the 773 | * $default value. 774 | * 775 | * If value is not a valid unsigned integer, logs error 776 | * and returns null. 777 | * 778 | * @author Kannan 779 | */ 780 | function xhprof_get_uint_param($param, $default = 0) { 781 | $val = xhprof_get_param_helper($param); 782 | 783 | if ($val === null) 784 | $val = $default; 785 | 786 | // trim leading/trailing whitespace 787 | $val = trim($val); 788 | 789 | // if it only contains digits, then ok.. 790 | if (ctype_digit($val)) { 791 | return $val; 792 | } 793 | 794 | xhprof_error("$param is $val. It must be an unsigned integer."); 795 | return null; 796 | } 797 | 798 | 799 | /** 800 | * Extracts value for a float param $param from 801 | * query string. If param is not specified, return 802 | * the $default value. 803 | * 804 | * If value is not a valid unsigned integer, logs error 805 | * and returns null. 806 | * 807 | * @author Kannan 808 | */ 809 | function xhprof_get_float_param($param, $default = 0) { 810 | $val = xhprof_get_param_helper($param); 811 | 812 | if ($val === null) 813 | $val = $default; 814 | 815 | // trim leading/trailing whitespace 816 | $val = trim($val); 817 | 818 | // TBD: confirm the value is indeed a float. 819 | if (true) // for now.. 820 | return (float)$val; 821 | 822 | xhprof_error("$param is $val. It must be a float."); 823 | return null; 824 | } 825 | 826 | /** 827 | * Extracts value for a boolean param $param from 828 | * query string. If param is not specified, return 829 | * the $default value. 830 | * 831 | * If value is not a valid unsigned integer, logs error 832 | * and returns null. 833 | * 834 | * @author Kannan 835 | */ 836 | function xhprof_get_bool_param($param, $default = false) { 837 | $val = xhprof_get_param_helper($param); 838 | 839 | if ($val === null) 840 | $val = $default; 841 | 842 | // trim leading/trailing whitespace 843 | $val = trim($val); 844 | 845 | switch (strtolower($val)) { 846 | case '0': 847 | case '1': 848 | $val = (bool)$val; 849 | break; 850 | case 'true': 851 | case 'on': 852 | case 'yes': 853 | $val = true; 854 | break; 855 | case 'false': 856 | case 'off': 857 | case 'no': 858 | $val = false; 859 | break; 860 | default: 861 | xhprof_error("$param is $val. It must be a valid boolean string."); 862 | return null; 863 | } 864 | 865 | return $val; 866 | 867 | } 868 | 869 | /** 870 | * Initialize params from URL query string. The function 871 | * creates globals variables for each of the params 872 | * and if the URL query string doesn't specify a particular 873 | * param initializes them with the corresponding default 874 | * value specified in the input. 875 | * 876 | * @params array $params An array whose keys are the names 877 | * of URL params who value needs to 878 | * be retrieved from the URL query 879 | * string. PHP globals are created 880 | * with these names. The value is 881 | * itself an array with 2-elems (the 882 | * param type, and its default value). 883 | * If a param is not specified in the 884 | * query string the default value is 885 | * used. 886 | * @author Kannan 887 | */ 888 | function xhprof_param_init($params) { 889 | /* Create variables specified in $params keys, init defaults */ 890 | foreach ($params as $k => $v) { 891 | switch ($v[0]) { 892 | case XHPROF_STRING_PARAM: 893 | $p = xhprof_get_string_param($k, $v[1]); 894 | break; 895 | case XHPROF_UINT_PARAM: 896 | $p = xhprof_get_uint_param($k, $v[1]); 897 | break; 898 | case XHPROF_FLOAT_PARAM: 899 | $p = xhprof_get_float_param($k, $v[1]); 900 | break; 901 | case XHPROF_BOOL_PARAM: 902 | $p = xhprof_get_bool_param($k, $v[1]); 903 | break; 904 | default: 905 | xhprof_error("Invalid param type passed to xhprof_param_init: " 906 | . $v[0]); 907 | exit(); 908 | } 909 | 910 | if ($k === 'run') { 911 | $p = implode(',', array_filter(explode(',', $p), 'ctype_xdigit')); 912 | } 913 | 914 | if ($k == 'symbol') { 915 | $p = strip_tags($p); 916 | } 917 | 918 | // create a global variable using the parameter name. 919 | $GLOBALS[$k] = $p; 920 | } 921 | } 922 | 923 | 924 | /** 925 | * Given a partial query string $q return matching function names in 926 | * specified XHProf run. This is used for the type ahead function 927 | * selector. 928 | * 929 | * @author Kannan 930 | */ 931 | function xhprof_get_matching_functions($q, $xhprof_data) { 932 | 933 | $matches = array(); 934 | 935 | foreach ($xhprof_data as $parent_child => $info) { 936 | list($parent, $child) = xhprof_parse_parent_child($parent_child); 937 | if (stripos($parent, $q) !== false) { 938 | $matches[$parent] = 1; 939 | } 940 | if (stripos($child, $q) !== false) { 941 | $matches[$child] = 1; 942 | } 943 | } 944 | 945 | $res = array_keys($matches); 946 | 947 | // sort it so the answers are in some reliable order... 948 | asort($res); 949 | 950 | return ($res); 951 | } 952 | -------------------------------------------------------------------------------- /src/xhprof/xhprof_lib/display/xhprof.php: -------------------------------------------------------------------------------- 1 | "; 68 | echo ""; 70 | echo ""; 72 | 73 | // javascript 74 | echo ""; 76 | echo ""; 78 | echo ""; 80 | echo ""; 81 | } 82 | 83 | 84 | /* 85 | * Formats call counts for XHProf reports. 86 | * 87 | * Description: 88 | * Call counts in single-run reports are integer values. 89 | * However, call counts for aggregated reports can be 90 | * fractional. This function will print integer values 91 | * without decimal point, but with commas etc. 92 | * 93 | * 4000 ==> 4,000 94 | * 95 | * It'll round fractional values to decimal precision of 3 96 | * 4000.1212 ==> 4,000.121 97 | * 4000.0001 ==> 4,000 98 | * 99 | */ 100 | function xhprof_count_format($num) { 101 | $num = round($num, 3); 102 | if (round($num) == $num) { 103 | return number_format($num); 104 | } else { 105 | return number_format($num, 3); 106 | } 107 | } 108 | 109 | function xhprof_percent_format($s, $precision = 1) { 110 | return sprintf('%.'.$precision.'f%%', 100 * $s); 111 | } 112 | 113 | /** 114 | * Implodes the text for a bunch of actions (such as links, forms, 115 | * into a HTML list and returns the text. 116 | */ 117 | function xhprof_render_actions($actions) { 118 | $out = array(); 119 | 120 | if (count($actions)) { 121 | $out[] = ''; 126 | } 127 | 128 | return implode('', $out); 129 | } 130 | 131 | 132 | /** 133 | * @param html-str $content the text/image/innerhtml/whatever for the link 134 | * @param raw-str $href 135 | * @param raw-str $class 136 | * @param raw-str $id 137 | * @param raw-str $title 138 | * @param raw-str $target 139 | * @param raw-str $onclick 140 | * @param raw-str $style 141 | * @param raw-str $access 142 | * @param raw-str $onmouseover 143 | * @param raw-str $onmouseout 144 | * @param raw-str $onmousedown 145 | * @param raw-str $dir 146 | * @param raw-str $rel 147 | */ 148 | function xhprof_render_link($content, $href, $class='', $id='', $title='', 149 | $target='', 150 | $onclick='', $style='', $access='', $onmouseover='', 151 | $onmouseout='', $onmousedown='') { 152 | 153 | if (!$content) { 154 | return ''; 155 | } 156 | 157 | if ($href) { 158 | $link = ' 1, 217 | "ct" => 1, 218 | "wt" => 1, 219 | "excl_wt" => 1, 220 | "ut" => 1, 221 | "excl_ut" => 1, 222 | "st" => 1, 223 | "excl_st" => 1, 224 | "mu" => 1, 225 | "excl_mu" => 1, 226 | "pmu" => 1, 227 | "excl_pmu" => 1, 228 | "cpu" => 1, 229 | "excl_cpu" => 1, 230 | "samples" => 1, 231 | "excl_samples" => 1 232 | ); 233 | 234 | // Textual descriptions for column headers in "single run" mode 235 | $descriptions = array( 236 | "fn" => "Function Name", 237 | "ct" => "Calls", 238 | "Calls%" => "Calls%", 239 | 240 | "wt" => "Incl. Wall Time
    (microsec)", 241 | "IWall%" => "IWall%", 242 | "excl_wt" => "Excl. Wall Time
    (microsec)", 243 | "EWall%" => "EWall%", 244 | 245 | "ut" => "Incl. User
    (microsecs)", 246 | "IUser%" => "IUser%", 247 | "excl_ut" => "Excl. User
    (microsec)", 248 | "EUser%" => "EUser%", 249 | 250 | "st" => "Incl. Sys
    (microsec)", 251 | "ISys%" => "ISys%", 252 | "excl_st" => "Excl. Sys
    (microsec)", 253 | "ESys%" => "ESys%", 254 | 255 | "cpu" => "Incl. CPU
    (microsecs)", 256 | "ICpu%" => "ICpu%", 257 | "excl_cpu" => "Excl. CPU
    (microsec)", 258 | "ECpu%" => "ECPU%", 259 | 260 | "mu" => "Incl.
    MemUse
    (bytes)", 261 | "IMUse%" => "IMemUse%", 262 | "excl_mu" => "Excl.
    MemUse
    (bytes)", 263 | "EMUse%" => "EMemUse%", 264 | 265 | "pmu" => "Incl.
    PeakMemUse
    (bytes)", 266 | "IPMUse%" => "IPeakMemUse%", 267 | "excl_pmu" => "Excl.
    PeakMemUse
    (bytes)", 268 | "EPMUse%" => "EPeakMemUse%", 269 | 270 | "samples" => "Incl. Samples", 271 | "ISamples%" => "ISamples%", 272 | "excl_samples" => "Excl. Samples", 273 | "ESamples%" => "ESamples%", 274 | ); 275 | 276 | // Formatting Callback Functions... 277 | $format_cbk = array( 278 | "fn" => "", 279 | "ct" => "xhprof_count_format", 280 | "Calls%" => "xhprof_percent_format", 281 | 282 | "wt" => "number_format", 283 | "IWall%" => "xhprof_percent_format", 284 | "excl_wt" => "number_format", 285 | "EWall%" => "xhprof_percent_format", 286 | 287 | "ut" => "number_format", 288 | "IUser%" => "xhprof_percent_format", 289 | "excl_ut" => "number_format", 290 | "EUser%" => "xhprof_percent_format", 291 | 292 | "st" => "number_format", 293 | "ISys%" => "xhprof_percent_format", 294 | "excl_st" => "number_format", 295 | "ESys%" => "xhprof_percent_format", 296 | 297 | "cpu" => "number_format", 298 | "ICpu%" => "xhprof_percent_format", 299 | "excl_cpu" => "number_format", 300 | "ECpu%" => "xhprof_percent_format", 301 | 302 | "mu" => "number_format", 303 | "IMUse%" => "xhprof_percent_format", 304 | "excl_mu" => "number_format", 305 | "EMUse%" => "xhprof_percent_format", 306 | 307 | "pmu" => "number_format", 308 | "IPMUse%" => "xhprof_percent_format", 309 | "excl_pmu" => "number_format", 310 | "EPMUse%" => "xhprof_percent_format", 311 | 312 | "samples" => "number_format", 313 | "ISamples%" => "xhprof_percent_format", 314 | "excl_samples" => "number_format", 315 | "ESamples%" => "xhprof_percent_format", 316 | ); 317 | 318 | 319 | // Textual descriptions for column headers in "diff" mode 320 | $diff_descriptions = array( 321 | "fn" => "Function Name", 322 | "ct" => "Calls Diff", 323 | "Calls%" => "Calls
    Diff%", 324 | 325 | "wt" => "Incl. Wall
    Diff
    (microsec)", 326 | "IWall%" => "IWall
    Diff%", 327 | "excl_wt" => "Excl. Wall
    Diff
    (microsec)", 328 | "EWall%" => "EWall
    Diff%", 329 | 330 | "ut" => "Incl. User Diff
    (microsec)", 331 | "IUser%" => "IUser
    Diff%", 332 | "excl_ut" => "Excl. User
    Diff
    (microsec)", 333 | "EUser%" => "EUser
    Diff%", 334 | 335 | "cpu" => "Incl. CPU Diff
    (microsec)", 336 | "ICpu%" => "ICpu
    Diff%", 337 | "excl_cpu" => "Excl. CPU
    Diff
    (microsec)", 338 | "ECpu%" => "ECpu
    Diff%", 339 | 340 | "st" => "Incl. Sys Diff
    (microsec)", 341 | "ISys%" => "ISys
    Diff%", 342 | "excl_st" => "Excl. Sys Diff
    (microsec)", 343 | "ESys%" => "ESys
    Diff%", 344 | 345 | "mu" => "Incl.
    MemUse
    Diff
    (bytes)", 346 | "IMUse%" => "IMemUse
    Diff%", 347 | "excl_mu" => "Excl.
    MemUse
    Diff
    (bytes)", 348 | "EMUse%" => "EMemUse
    Diff%", 349 | 350 | "pmu" => "Incl.
    PeakMemUse
    Diff
    (bytes)", 351 | "IPMUse%" => "IPeakMemUse
    Diff%", 352 | "excl_pmu" => "Excl.
    PeakMemUse
    Diff
    (bytes)", 353 | "EPMUse%" => "EPeakMemUse
    Diff%", 354 | 355 | "samples" => "Incl. Samples Diff", 356 | "ISamples%" => "ISamples Diff%", 357 | "excl_samples" => "Excl. Samples Diff", 358 | "ESamples%" => "ESamples Diff%", 359 | ); 360 | 361 | // columns that'll be displayed in a top-level report 362 | $stats = array(); 363 | 364 | // columns that'll be displayed in a function's parent/child report 365 | $pc_stats = array(); 366 | 367 | // Various total counts 368 | $totals = 0; 369 | $totals_1 = 0; 370 | $totals_2 = 0; 371 | 372 | /* 373 | * The subset of $possible_metrics that is present in the raw profile data. 374 | */ 375 | $metrics = null; 376 | 377 | /** 378 | * Callback comparison operator (passed to usort() for sorting array of 379 | * tuples) that compares array elements based on the sort column 380 | * specified in $sort_col (global parameter). 381 | * 382 | * @author Kannan 383 | */ 384 | function sort_cbk($a, $b) { 385 | global $sort_col; 386 | global $diff_mode; 387 | 388 | if ($sort_col == "fn") { 389 | 390 | // case insensitive ascending sort for function names 391 | $left = strtoupper($a["fn"]); 392 | $right = strtoupper($b["fn"]); 393 | 394 | if ($left == $right) 395 | return 0; 396 | return ($left < $right) ? -1 : 1; 397 | 398 | } else { 399 | 400 | // descending sort for all others 401 | $left = $a[$sort_col]; 402 | $right = $b[$sort_col]; 403 | 404 | // if diff mode, sort by absolute value of regression/improvement 405 | if ($diff_mode) { 406 | $left = abs($left); 407 | $right = abs($right); 408 | } 409 | 410 | if ($left == $right) 411 | return 0; 412 | return ($left > $right) ? -1 : 1; 413 | } 414 | } 415 | 416 | /** 417 | * Get the appropriate description for a statistic 418 | * (depending upon whether we are in diff report mode 419 | * or single run report mode). 420 | * 421 | * @author Kannan 422 | */ 423 | function stat_description($stat) { 424 | global $descriptions; 425 | global $diff_descriptions; 426 | global $diff_mode; 427 | 428 | if ($diff_mode) { 429 | return $diff_descriptions[$stat]; 430 | } else { 431 | return $descriptions[$stat]; 432 | } 433 | } 434 | 435 | 436 | /** 437 | * Analyze raw data & generate the profiler report 438 | * (common for both single run mode and diff mode). 439 | * 440 | * @author: Kannan 441 | */ 442 | function profiler_report ($url_params, 443 | $rep_symbol, 444 | $sort, 445 | $run1, 446 | $run1_desc, 447 | $run1_data, 448 | $run2 = 0, 449 | $run2_desc = "", 450 | $run2_data = array()) { 451 | global $totals; 452 | global $totals_1; 453 | global $totals_2; 454 | global $stats; 455 | global $pc_stats; 456 | global $diff_mode; 457 | global $base_url; 458 | 459 | // if we are reporting on a specific function, we can trim down 460 | // the report(s) to just stuff that is relevant to this function. 461 | // That way compute_flat_info()/compute_diff() etc. do not have 462 | // to needlessly work hard on churning irrelevant data. 463 | if (!empty($rep_symbol)) { 464 | $run1_data = xhprof_trim_run($run1_data, array($rep_symbol)); 465 | if ($diff_mode) { 466 | $run2_data = xhprof_trim_run($run2_data, array($rep_symbol)); 467 | } 468 | } 469 | 470 | if ($diff_mode) { 471 | $run_delta = xhprof_compute_diff($run1_data, $run2_data); 472 | $symbol_tab = xhprof_compute_flat_info($run_delta, $totals); 473 | $symbol_tab1 = xhprof_compute_flat_info($run1_data, $totals_1); 474 | $symbol_tab2 = xhprof_compute_flat_info($run2_data, $totals_2); 475 | } else { 476 | $symbol_tab = xhprof_compute_flat_info($run1_data, $totals); 477 | } 478 | 479 | $run1_txt = sprintf("Run #%s: %s", 480 | $run1, $run1_desc); 481 | 482 | $base_url_params = xhprof_array_unset(xhprof_array_unset($url_params, 483 | 'symbol'), 484 | 'all'); 485 | 486 | $top_link_query_string = "$base_url?" . http_build_query($base_url_params); 487 | 488 | if ($diff_mode) { 489 | $diff_text = "Diff"; 490 | $base_url_params = xhprof_array_unset($base_url_params, 'run1'); 491 | $base_url_params = xhprof_array_unset($base_url_params, 'run2'); 492 | $run1_link = xhprof_render_link('View Run #' . $run1, 493 | "$base_url?" . 494 | http_build_query(xhprof_array_set($base_url_params, 495 | 'run', 496 | $run1))); 497 | $run2_txt = sprintf("Run #%s: %s", 498 | $run2, $run2_desc); 499 | 500 | $run2_link = xhprof_render_link('View Run #' . $run2, 501 | "$base_url?" . 502 | http_build_query(xhprof_array_set($base_url_params, 503 | 'run', 504 | $run2))); 505 | } else { 506 | $diff_text = "Run"; 507 | } 508 | 509 | // set up the action links for operations that can be done on this report 510 | $links = array(); 511 | $links [] = xhprof_render_link("View Top Level $diff_text Report", 512 | $top_link_query_string); 513 | 514 | if ($diff_mode) { 515 | $inverted_params = $url_params; 516 | $inverted_params['run1'] = $url_params['run2']; 517 | $inverted_params['run2'] = $url_params['run1']; 518 | 519 | // view the different runs or invert the current diff 520 | $links [] = $run1_link; 521 | $links [] = $run2_link; 522 | $links [] = xhprof_render_link('Invert ' . $diff_text . ' Report', 523 | "$base_url?". 524 | http_build_query($inverted_params)); 525 | } 526 | 527 | // lookup function typeahead form 528 | $links [] = ''; 530 | 531 | echo xhprof_render_actions($links); 532 | 533 | 534 | echo 535 | '
    ' . 536 | '
    ' . $diff_text . ' Report
    ' . 537 | '
    ' . ($diff_mode ? 538 | $run1_txt . '
    vs.
    ' . $run2_txt : 539 | $run1_txt) . 540 | '
    ' . 541 | '
    Tip
    ' . 542 | '
    Click a function name below to drill down.
    ' . 543 | '
    ' . 544 | '
    '; 545 | 546 | // data tables 547 | if (!empty($rep_symbol)) { 548 | if (!isset($symbol_tab[$rep_symbol])) { 549 | echo "
    Symbol $rep_symbol not found in XHProf run
    "; 550 | return; 551 | } 552 | 553 | /* single function report with parent/child information */ 554 | if ($diff_mode) { 555 | $info1 = isset($symbol_tab1[$rep_symbol]) ? 556 | $symbol_tab1[$rep_symbol] : null; 557 | $info2 = isset($symbol_tab2[$rep_symbol]) ? 558 | $symbol_tab2[$rep_symbol] : null; 559 | symbol_report($url_params, $run_delta, $symbol_tab[$rep_symbol], 560 | $sort, $rep_symbol, 561 | $run1, $info1, 562 | $run2, $info2); 563 | } else { 564 | symbol_report($url_params, $run1_data, $symbol_tab[$rep_symbol], 565 | $sort, $rep_symbol, $run1); 566 | } 567 | } else { 568 | /* flat top-level report of all functions */ 569 | full_report($url_params, $symbol_tab, $sort, $run1, $run2); 570 | } 571 | } 572 | 573 | /** 574 | * Computes percentage for a pair of values, and returns it 575 | * in string format. 576 | */ 577 | function pct($a, $b) { 578 | if ($b == 0) { 579 | return "N/A"; 580 | } else { 581 | $res = (round(($a * 1000 / $b)) / 10); 582 | return $res; 583 | } 584 | } 585 | 586 | /** 587 | * Given a number, returns the td class to use for display. 588 | * 589 | * For instance, negative numbers in diff reports comparing two runs (run1 & run2) 590 | * represent improvement from run1 to run2. We use green to display those deltas, 591 | * and red for regression deltas. 592 | */ 593 | function get_print_class($num, $bold) { 594 | global $vbar; 595 | global $vbbar; 596 | global $vrbar; 597 | global $vgbar; 598 | global $diff_mode; 599 | 600 | if ($bold) { 601 | if ($diff_mode) { 602 | if ($num <= 0) { 603 | $class = $vgbar; // green (improvement) 604 | } else { 605 | $class = $vrbar; // red (regression) 606 | } 607 | } else { 608 | $class = $vbbar; // blue 609 | } 610 | } 611 | else { 612 | $class = $vbar; // default (black) 613 | } 614 | 615 | return $class; 616 | } 617 | 618 | /** 619 | * Prints a element with a numeric value. 620 | */ 621 | function print_td_num($num, $fmt_func, $bold=false, $attributes=null) { 622 | 623 | $class = get_print_class($num, $bold); 624 | 625 | if (!empty($fmt_func) && is_numeric($num) ) { 626 | $num = call_user_func($fmt_func, $num); 627 | } 628 | 629 | print("$num\n"); 630 | } 631 | 632 | /** 633 | * Prints a element with a pecentage. 634 | */ 635 | function print_td_pct($numer, $denom, $bold=false, $attributes=null) { 636 | global $vbar; 637 | global $vbbar; 638 | global $diff_mode; 639 | 640 | $class = get_print_class($numer, $bold); 641 | 642 | if ($denom == 0) { 643 | $pct = "N/A%"; 644 | } else { 645 | $pct = xhprof_percent_format($numer / abs($denom)); 646 | } 647 | 648 | print("$pct\n"); 649 | } 650 | 651 | /** 652 | * Print "flat" data corresponding to one function. 653 | * 654 | * @author Kannan 655 | */ 656 | function print_function_info($url_params, $info, $sort, $run1, $run2) { 657 | static $odd_even = 0; 658 | 659 | global $totals; 660 | global $sort_col; 661 | global $metrics; 662 | global $format_cbk; 663 | global $display_calls; 664 | global $base_url; 665 | 666 | // Toggle $odd_or_even 667 | $odd_even = 1 - $odd_even; 668 | 669 | if ($odd_even) { 670 | print(""); 671 | } 672 | else { 673 | print(''); 674 | } 675 | 676 | $href = "$base_url?" . 677 | http_build_query(xhprof_array_set($url_params, 678 | 'symbol', $info["fn"])); 679 | 680 | print(''); 681 | print(xhprof_render_link($info["fn"], $href)); 682 | print_source_link($info); 683 | print("\n"); 684 | 685 | if ($display_calls) { 686 | // Call Count.. 687 | print_td_num($info["ct"], $format_cbk["ct"], ($sort_col == "ct")); 688 | print_td_pct($info["ct"], $totals["ct"], ($sort_col == "ct")); 689 | } 690 | 691 | // Other metrics.. 692 | foreach ($metrics as $metric) { 693 | // Inclusive metric 694 | print_td_num($info[$metric], $format_cbk[$metric], 695 | ($sort_col == $metric)); 696 | print_td_pct($info[$metric], $totals[$metric], 697 | ($sort_col == $metric)); 698 | 699 | // Exclusive Metric 700 | print_td_num($info["excl_" . $metric], 701 | $format_cbk["excl_" . $metric], 702 | ($sort_col == "excl_" . $metric)); 703 | print_td_pct($info["excl_" . $metric], 704 | $totals[$metric], 705 | ($sort_col == "excl_" . $metric)); 706 | } 707 | 708 | print("\n"); 709 | } 710 | 711 | /** 712 | * Print non-hierarchical (flat-view) of profiler data. 713 | * 714 | * @author Kannan 715 | */ 716 | function print_flat_data($url_params, $title, $flat_data, $sort, $run1, $run2, $limit) { 717 | 718 | global $stats; 719 | global $sortable_columns; 720 | global $vwbar; 721 | global $base_url; 722 | 723 | $size = count($flat_data); 724 | if (!$limit) { // no limit 725 | $limit = $size; 726 | $display_link = ""; 727 | } else { 728 | $display_link = xhprof_render_link(" [ display all ]", 729 | "$base_url?" . 730 | http_build_query(xhprof_array_set($url_params, 731 | 'all', 1))); 732 | } 733 | 734 | print("

    $title $display_link


    "); 735 | 736 | print(''); 738 | print(''); 739 | 740 | foreach ($stats as $stat) { 741 | $desc = stat_description($stat); 742 | if (array_key_exists($stat, $sortable_columns)) { 743 | $href = "$base_url?" 744 | . http_build_query(xhprof_array_set($url_params, 'sort', $stat)); 745 | $header = xhprof_render_link($desc, $href); 746 | } else { 747 | $header = $desc; 748 | } 749 | 750 | if ($stat == "fn") 751 | print(""); 752 | else print(""); 753 | } 754 | print("\n"); 755 | 756 | if ($limit >= 0) { 757 | $limit = min($size, $limit); 758 | for ($i = 0; $i < $limit; $i++) { 759 | print_function_info($url_params, $flat_data[$i], $sort, $run1, $run2); 760 | } 761 | } else { 762 | // if $limit is negative, print abs($limit) items starting from the end 763 | $limit = min($size, abs($limit)); 764 | for ($i = 0; $i < $limit; $i++) { 765 | print_function_info($url_params, $flat_data[$size - $i - 1], $sort, $run1, $run2); 766 | } 767 | } 768 | print("
    $header$header
    "); 769 | 770 | // let's print the display all link at the bottom as well... 771 | if ($display_link) { 772 | echo '
    ' . $display_link . '
    '; 773 | } 774 | 775 | } 776 | 777 | /** 778 | * Generates a tabular report for all functions. This is the top-level report. 779 | * 780 | * @author Kannan 781 | */ 782 | function full_report($url_params, $symbol_tab, $sort, $run1, $run2) { 783 | global $vwbar; 784 | global $vbar; 785 | global $totals; 786 | global $totals_1; 787 | global $totals_2; 788 | global $metrics; 789 | global $diff_mode; 790 | global $descriptions; 791 | global $sort_col; 792 | global $format_cbk; 793 | global $display_calls; 794 | global $base_path; 795 | global $base_url; 796 | 797 | $possible_metrics = xhprof_get_possible_metrics(); 798 | 799 | if ($diff_mode) { 800 | 801 | $base_url_params = xhprof_array_unset(xhprof_array_unset($url_params, 802 | 'run1'), 803 | 'run2'); 804 | $href1 = "$base_url?" . 805 | http_build_query(xhprof_array_set($base_url_params, 806 | 'run', $run1)); 807 | $href2 = "$base_url?" . 808 | http_build_query(xhprof_array_set($base_url_params, 809 | 'run', $run2)); 810 | 811 | print("

    Overall Diff Summary

    "); 812 | print('' . "\n"); 814 | print(''); 815 | print(""); 816 | print(""); 817 | print(""); 818 | print(""); 819 | print(""); 820 | print(''); 821 | 822 | if ($display_calls) { 823 | print(''); 824 | print(""); 825 | print_td_num($totals_1["ct"], $format_cbk["ct"]); 826 | print_td_num($totals_2["ct"], $format_cbk["ct"]); 827 | print_td_num($totals_2["ct"] - $totals_1["ct"], $format_cbk["ct"], true); 828 | print_td_pct($totals_2["ct"] - $totals_1["ct"], $totals_1["ct"], true); 829 | print(''); 830 | } 831 | 832 | foreach ($metrics as $metric) { 833 | $m = $metric; 834 | print(''); 835 | print(""); 836 | print_td_num($totals_1[$m], $format_cbk[$m]); 837 | print_td_num($totals_2[$m], $format_cbk[$m]); 838 | print_td_num($totals_2[$m] - $totals_1[$m], $format_cbk[$m], true); 839 | print_td_pct($totals_2[$m] - $totals_1[$m], $totals_1[$m], true); 840 | print(''); 841 | } 842 | print('
    " . xhprof_render_link("Run #$run1", $href1) . "" . xhprof_render_link("Run #$run2", $href2) . "DiffDiff%
    Number of Function Calls
    " . str_replace("
    ", " ", $descriptions[$m]) . "
    '); 843 | 844 | $callgraph_report_title = '[View Regressions/Improvements using Callgraph Diff]'; 845 | 846 | } else { 847 | print("

    \n"); 848 | 849 | print('' . "\n"); 851 | echo ""; 852 | echo ""; 853 | echo ""; 854 | echo ""; 855 | 856 | foreach ($metrics as $metric) { 857 | echo ""; 858 | echo ""; 860 | echo ""; 862 | echo ""; 863 | } 864 | 865 | if ($display_calls) { 866 | echo ""; 867 | echo ""; 868 | echo ""; 869 | echo ""; 870 | } 871 | 872 | echo "
    Overall Summary
    Total " 859 | . str_replace("
    ", " ", stat_description($metric)) . ":
    " . number_format($totals[$metric]) . " " 861 | . $possible_metrics[$metric][1] . "
    Number of Function Calls:" . number_format($totals['ct']) . "
    "; 873 | print("

    \n"); 874 | 875 | $callgraph_report_title = '[View Full Callgraph]'; 876 | } 877 | 878 | print("

    " . 879 | xhprof_render_link($callgraph_report_title, 880 | "$base_path/callgraph.php" . "?" . http_build_query($url_params)) 881 | . "

    "); 882 | 883 | 884 | $flat_data = array(); 885 | foreach ($symbol_tab as $symbol => $info) { 886 | $tmp = $info; 887 | $tmp["fn"] = $symbol; 888 | $flat_data[] = $tmp; 889 | } 890 | usort($flat_data, 'sort_cbk'); 891 | 892 | print("
    "); 893 | 894 | if (!empty($url_params['all'])) { 895 | $all = true; 896 | $limit = 0; // display all rows 897 | } else { 898 | $all = false; 899 | $limit = 100; // display only limited number of rows 900 | } 901 | 902 | $desc = str_replace("
    ", " ", $descriptions[$sort_col]); 903 | 904 | if ($diff_mode) { 905 | if ($all) { 906 | $title = "Total Diff Report: ' 907 | .'Sorted by absolute value of regression/improvement in $desc"; 908 | } else { 909 | $title = "Top 100 Regressions/" 910 | . "Improvements: " 911 | . "Sorted by $desc Diff"; 912 | } 913 | } else { 914 | if ($all) { 915 | $title = "Sorted by $desc"; 916 | } else { 917 | $title = "Displaying top $limit functions: Sorted by $desc"; 918 | } 919 | } 920 | print_flat_data($url_params, $title, $flat_data, $sort, $run1, $run2, $limit); 921 | } 922 | 923 | 924 | /** 925 | * Return attribute names and values to be used by javascript tooltip. 926 | */ 927 | function get_tooltip_attributes($type, $metric) { 928 | return "type='$type' metric='$metric'"; 929 | } 930 | 931 | /** 932 | * Print info for a parent or child function in the 933 | * parent & children report. 934 | * 935 | * @author Kannan 936 | */ 937 | function pc_info($info, $base_ct, $base_info, $parent) { 938 | global $sort_col; 939 | global $metrics; 940 | global $format_cbk; 941 | global $display_calls; 942 | 943 | if ($parent) 944 | $type = "Parent"; 945 | else $type = "Child"; 946 | 947 | if ($display_calls) { 948 | $mouseoverct = get_tooltip_attributes($type, "ct"); 949 | /* call count */ 950 | print_td_num($info["ct"], $format_cbk["ct"], ($sort_col == "ct"), $mouseoverct); 951 | print_td_pct($info["ct"], $base_ct, ($sort_col == "ct"), $mouseoverct); 952 | } 953 | 954 | /* Inclusive metric values */ 955 | foreach ($metrics as $metric) { 956 | print_td_num($info[$metric], $format_cbk[$metric], 957 | ($sort_col == $metric), 958 | get_tooltip_attributes($type, $metric)); 959 | print_td_pct($info[$metric], $base_info[$metric], ($sort_col == $metric), 960 | get_tooltip_attributes($type, $metric)); 961 | } 962 | } 963 | 964 | function print_pc_array($url_params, $results, $base_ct, $base_info, $parent, 965 | $run1, $run2) { 966 | global $base_url; 967 | 968 | // Construct section title 969 | if ($parent) { 970 | $title = 'Parent function'; 971 | } 972 | else { 973 | $title = 'Child function'; 974 | } 975 | if (count($results) > 1) { 976 | $title .= 's'; 977 | } 978 | 979 | print(""); 980 | print("
    " . $title . "
    "); 981 | print(""); 982 | 983 | $odd_even = 0; 984 | foreach ($results as $info) { 985 | $href = "$base_url?" . 986 | http_build_query(xhprof_array_set($url_params, 987 | 'symbol', $info["fn"])); 988 | 989 | $odd_even = 1 - $odd_even; 990 | 991 | if ($odd_even) { 992 | print(''); 993 | } 994 | else { 995 | print(''); 996 | } 997 | 998 | print("" . xhprof_render_link($info["fn"], $href)); 999 | print_source_link($info); 1000 | print(""); 1001 | pc_info($info, $base_ct, $base_info, $parent); 1002 | print(""); 1003 | } 1004 | } 1005 | 1006 | function print_source_link($info) { 1007 | if (strncmp($info['fn'], 'run_init', 8) && $info['fn'] !== 'main()') { 1008 | if (defined('XHPROF_SYMBOL_LOOKUP_URL')) { 1009 | $link = xhprof_render_link( 1010 | 'source', 1011 | XHPROF_SYMBOL_LOOKUP_URL . '?symbol='.rawurlencode($info["fn"])); 1012 | print(' ('.$link.')'); 1013 | } 1014 | } 1015 | } 1016 | 1017 | 1018 | function print_symbol_summary($symbol_info, $stat, $base) { 1019 | 1020 | $val = $symbol_info[$stat]; 1021 | $desc = str_replace("
    ", " ", stat_description($stat)); 1022 | 1023 | print("$desc: "); 1024 | print(number_format($val)); 1025 | print(" (" . pct($val, $base) . "% of overall)"); 1026 | if (substr($stat, 0, 4) == "excl") { 1027 | $func_base = $symbol_info[str_replace("excl_", "", $stat)]; 1028 | print(" (" . pct($val, $func_base) . "% of this function)"); 1029 | } 1030 | print("
    "); 1031 | } 1032 | 1033 | /** 1034 | * Generates a report for a single function/symbol. 1035 | * 1036 | * @author Kannan 1037 | */ 1038 | function symbol_report($url_params, 1039 | $run_data, $symbol_info, $sort, $rep_symbol, 1040 | $run1, 1041 | $symbol_info1 = null, 1042 | $run2 = 0, 1043 | $symbol_info2 = null) { 1044 | global $vwbar; 1045 | global $vbar; 1046 | global $totals; 1047 | global $pc_stats; 1048 | global $sortable_columns; 1049 | global $metrics; 1050 | global $diff_mode; 1051 | global $descriptions; 1052 | global $format_cbk; 1053 | global $sort_col; 1054 | global $display_calls; 1055 | global $base_path; 1056 | global $base_url; 1057 | 1058 | $possible_metrics = xhprof_get_possible_metrics(); 1059 | 1060 | if ($diff_mode) { 1061 | $diff_text = "Diff"; 1062 | $regr_impr = "Regression/Improvement"; 1063 | } else { 1064 | $diff_text = ""; 1065 | $regr_impr = ""; 1066 | } 1067 | 1068 | if ($diff_mode) { 1069 | 1070 | $base_url_params = xhprof_array_unset(xhprof_array_unset($url_params, 1071 | 'run1'), 1072 | 'run2'); 1073 | $href1 = "$base_url?" 1074 | . http_build_query(xhprof_array_set($base_url_params, 'run', $run1)); 1075 | $href2 = "$base_url?" 1076 | . http_build_query(xhprof_array_set($base_url_params, 'run', $run2)); 1077 | 1078 | print("

    $regr_impr summary for $rep_symbol

    "); 1079 | print('' . "\n"); 1081 | print(''); 1082 | print(""); 1083 | print(""); 1084 | print(""); 1085 | print(""); 1086 | print(""); 1087 | print(''); 1088 | print(''); 1089 | 1090 | if ($display_calls) { 1091 | print(""); 1092 | print_td_num($symbol_info1["ct"], $format_cbk["ct"]); 1093 | print_td_num($symbol_info2["ct"], $format_cbk["ct"]); 1094 | print_td_num($symbol_info2["ct"] - $symbol_info1["ct"], 1095 | $format_cbk["ct"], true); 1096 | print_td_pct($symbol_info2["ct"] - $symbol_info1["ct"], 1097 | $symbol_info1["ct"], true); 1098 | print(''); 1099 | } 1100 | 1101 | 1102 | foreach ($metrics as $metric) { 1103 | $m = $metric; 1104 | 1105 | // Inclusive stat for metric 1106 | print(''); 1107 | print(""); 1108 | print_td_num($symbol_info1[$m], $format_cbk[$m]); 1109 | print_td_num($symbol_info2[$m], $format_cbk[$m]); 1110 | print_td_num($symbol_info2[$m] - $symbol_info1[$m], $format_cbk[$m], true); 1111 | print_td_pct($symbol_info2[$m] - $symbol_info1[$m], $symbol_info1[$m], true); 1112 | print(''); 1113 | 1114 | // AVG (per call) Inclusive stat for metric 1115 | print(''); 1116 | print(""); 1117 | $avg_info1 = 'N/A'; 1118 | $avg_info2 = 'N/A'; 1119 | if ($symbol_info1['ct'] > 0) { 1120 | $avg_info1 = ($symbol_info1[$m] / $symbol_info1['ct']); 1121 | } 1122 | if ($symbol_info2['ct'] > 0) { 1123 | $avg_info2 = ($symbol_info2[$m] / $symbol_info2['ct']); 1124 | } 1125 | print_td_num($avg_info1, $format_cbk[$m]); 1126 | print_td_num($avg_info2, $format_cbk[$m]); 1127 | print_td_num($avg_info2 - $avg_info1, $format_cbk[$m], true); 1128 | print_td_pct($avg_info2 - $avg_info1, $avg_info1, true); 1129 | print(''); 1130 | 1131 | // Exclusive stat for metric 1132 | $m = "excl_" . $metric; 1133 | print(''); 1134 | print(""); 1135 | print_td_num($symbol_info1[$m], $format_cbk[$m]); 1136 | print_td_num($symbol_info2[$m], $format_cbk[$m]); 1137 | print_td_num($symbol_info2[$m] - $symbol_info1[$m], $format_cbk[$m], true); 1138 | print_td_pct($symbol_info2[$m] - $symbol_info1[$m], $symbol_info1[$m], true); 1139 | print(''); 1140 | } 1141 | 1142 | print('
    $rep_symbolRun #$run1Run #$run2DiffDiff%
    Number of Function Calls
    " . str_replace("
    ", " ", $descriptions[$m]) . "
    " . str_replace("
    ", " ", $descriptions[$m]) . " per call
    " . str_replace("
    ", " ", $descriptions[$m]) . "
    '); 1143 | } 1144 | 1145 | print("

    "); 1146 | print("Parent/Child $regr_impr report for $rep_symbol"); 1147 | 1148 | $callgraph_href = "$base_path/callgraph.php?" 1149 | . http_build_query(xhprof_array_set($url_params, 'func', $rep_symbol)); 1150 | 1151 | print(" [View Callgraph $diff_text]
    "); 1152 | 1153 | print("


    "); 1154 | 1155 | print('' . "\n"); 1157 | print(''); 1158 | 1159 | foreach ($pc_stats as $stat) { 1160 | $desc = stat_description($stat); 1161 | if (array_key_exists($stat, $sortable_columns)) { 1162 | 1163 | $href = "$base_url?" . 1164 | http_build_query(xhprof_array_set($url_params, 1165 | 'sort', $stat)); 1166 | $header = xhprof_render_link($desc, $href); 1167 | } else { 1168 | $header = $desc; 1169 | } 1170 | 1171 | if ($stat == "fn") 1172 | print(""); 1173 | else print(""); 1174 | } 1175 | print(""); 1176 | 1177 | print(""); 1180 | 1181 | print(""); 1182 | // make this a self-reference to facilitate copy-pasting snippets to e-mails 1183 | print(""); 1186 | 1187 | if ($display_calls) { 1188 | // Call Count 1189 | print_td_num($symbol_info["ct"], $format_cbk["ct"]); 1190 | print_td_pct($symbol_info["ct"], $totals["ct"]); 1191 | } 1192 | 1193 | // Inclusive Metrics for current function 1194 | foreach ($metrics as $metric) { 1195 | print_td_num($symbol_info[$metric], $format_cbk[$metric], ($sort_col == $metric)); 1196 | print_td_pct($symbol_info[$metric], $totals[$metric], ($sort_col == $metric)); 1197 | } 1198 | print(""); 1199 | 1200 | print(""); 1201 | print(""); 1203 | 1204 | if ($display_calls) { 1205 | // Call Count 1206 | print(""); 1207 | print(""); 1208 | } 1209 | 1210 | // Exclusive Metrics for current function 1211 | foreach ($metrics as $metric) { 1212 | print_td_num($symbol_info["excl_" . $metric], $format_cbk["excl_" . $metric], 1213 | ($sort_col == $metric), 1214 | get_tooltip_attributes("Child", $metric)); 1215 | print_td_pct($symbol_info["excl_" . $metric], $symbol_info[$metric], 1216 | ($sort_col == $metric), 1217 | get_tooltip_attributes("Child", $metric)); 1218 | } 1219 | print(""); 1220 | 1221 | // list of callers/parent functions 1222 | $results = array(); 1223 | if ($display_calls) { 1224 | $base_ct = $symbol_info["ct"]; 1225 | } else { 1226 | $base_ct = 0; 1227 | } 1228 | foreach ($metrics as $metric) { 1229 | $base_info[$metric] = $symbol_info[$metric]; 1230 | } 1231 | foreach ($run_data as $parent_child => $info) { 1232 | list($parent, $child) = xhprof_parse_parent_child($parent_child); 1233 | if (($child == $rep_symbol) && ($parent)) { 1234 | $info_tmp = $info; 1235 | $info_tmp["fn"] = $parent; 1236 | $results[] = $info_tmp; 1237 | } 1238 | } 1239 | usort($results, 'sort_cbk'); 1240 | 1241 | if (count($results) > 0) { 1242 | print_pc_array($url_params, $results, $base_ct, $base_info, true, 1243 | $run1, $run2); 1244 | } 1245 | 1246 | // list of callees/child functions 1247 | $results = array(); 1248 | $base_ct = 0; 1249 | foreach ($run_data as $parent_child => $info) { 1250 | list($parent, $child) = xhprof_parse_parent_child($parent_child); 1251 | if ($parent == $rep_symbol) { 1252 | $info_tmp = $info; 1253 | $info_tmp["fn"] = $child; 1254 | $results[] = $info_tmp; 1255 | if ($display_calls) { 1256 | $base_ct += $info["ct"]; 1257 | } 1258 | } 1259 | } 1260 | usort($results, 'sort_cbk'); 1261 | 1262 | if (count($results)) { 1263 | print_pc_array($url_params, $results, $base_ct, $base_info, false, 1264 | $run1, $run2); 1265 | } 1266 | 1267 | print("
    $header$header
    "); 1178 | print("
    Current Function
    "); 1179 | print("
    $rep_symbol"); 1184 | print_source_link(array('fn' => $rep_symbol)); 1185 | print("
    " 1202 | ."Exclusive Metrics $diff_text for Current Function
    "); 1268 | 1269 | // These will be used for pop-up tips/help. 1270 | // Related javascript code is in: xhprof_report.js 1271 | print("\n"); 1272 | print(''); 1296 | print("\n"); 1297 | 1298 | } 1299 | 1300 | /** 1301 | * Generate the profiler report for a single run. 1302 | * 1303 | * @author Kannan 1304 | */ 1305 | function profiler_single_run_report ($url_params, 1306 | $xhprof_data, 1307 | $run_desc, 1308 | $rep_symbol, 1309 | $sort, 1310 | $run) { 1311 | 1312 | init_metrics($xhprof_data, $rep_symbol, $sort, false); 1313 | 1314 | profiler_report($url_params, $rep_symbol, $sort, $run, $run_desc, 1315 | $xhprof_data); 1316 | } 1317 | 1318 | 1319 | 1320 | /** 1321 | * Generate the profiler report for diff mode (delta between two runs). 1322 | * 1323 | * @author Kannan 1324 | */ 1325 | function profiler_diff_report($url_params, 1326 | $xhprof_data1, 1327 | $run1_desc, 1328 | $xhprof_data2, 1329 | $run2_desc, 1330 | $rep_symbol, 1331 | $sort, 1332 | $run1, 1333 | $run2) { 1334 | 1335 | 1336 | // Initialize what metrics we'll display based on data in Run2 1337 | init_metrics($xhprof_data2, $rep_symbol, $sort, true); 1338 | 1339 | profiler_report($url_params, 1340 | $rep_symbol, 1341 | $sort, 1342 | $run1, 1343 | $run1_desc, 1344 | $xhprof_data1, 1345 | $run2, 1346 | $run2_desc, 1347 | $xhprof_data2); 1348 | } 1349 | 1350 | 1351 | /** 1352 | * Generate a XHProf Display View given the various URL parameters 1353 | * as arguments. The first argument is an object that implements 1354 | * the iXHProfRuns interface. 1355 | * 1356 | * @param object $xhprof_runs_impl An object that implements 1357 | * the iXHProfRuns interface 1358 | *. 1359 | * @param array $url_params Array of non-default URL params. 1360 | * 1361 | * @param string $source Category/type of the run. The source in 1362 | * combination with the run id uniquely 1363 | * determines a profiler run. 1364 | * 1365 | * @param string $run run id, or comma separated sequence of 1366 | * run ids. The latter is used if an aggregate 1367 | * report of the runs is desired. 1368 | * 1369 | * @param string $wts Comma separate list of integers. 1370 | * Represents the weighted ratio in 1371 | * which which a set of runs will be 1372 | * aggregated. [Used only for aggregate 1373 | * reports.] 1374 | * 1375 | * @param string $symbol Function symbol. If non-empty then the 1376 | * parent/child view of this function is 1377 | * displayed. If empty, a flat-profile view 1378 | * of the functions is displayed. 1379 | * 1380 | * @param string $run1 Base run id (for diff reports) 1381 | * 1382 | * @param string $run2 New run id (for diff reports) 1383 | * 1384 | */ 1385 | function displayXHProfReport($xhprof_runs_impl, $url_params, $source, 1386 | $run, $wts, $symbol, $sort, $run1, $run2) { 1387 | 1388 | if ($run) { // specific run to display? 1389 | 1390 | // run may be a single run or a comma separate list of runs 1391 | // that'll be aggregated. If "wts" (a comma separated list 1392 | // of integral weights is specified), the runs will be 1393 | // aggregated in that ratio. 1394 | // 1395 | $runs_array = explode(",", $run); 1396 | 1397 | if (count($runs_array) == 1) { 1398 | $xhprof_data = $xhprof_runs_impl->get_run($runs_array[0], 1399 | $source, 1400 | $description); 1401 | } else { 1402 | if (!empty($wts)) { 1403 | $wts_array = explode(",", $wts); 1404 | } else { 1405 | $wts_array = null; 1406 | } 1407 | $data = xhprof_aggregate_runs($xhprof_runs_impl, 1408 | $runs_array, $wts_array, $source, false); 1409 | $xhprof_data = $data['raw']; 1410 | $description = $data['description']; 1411 | } 1412 | 1413 | 1414 | profiler_single_run_report($url_params, 1415 | $xhprof_data, 1416 | $description, 1417 | $symbol, 1418 | $sort, 1419 | $run); 1420 | 1421 | } else if ($run1 && $run2) { // diff report for two runs 1422 | 1423 | $xhprof_data1 = $xhprof_runs_impl->get_run($run1, $source, $description1); 1424 | $xhprof_data2 = $xhprof_runs_impl->get_run($run2, $source, $description2); 1425 | 1426 | profiler_diff_report($url_params, 1427 | $xhprof_data1, 1428 | $description1, 1429 | $xhprof_data2, 1430 | $description2, 1431 | $symbol, 1432 | $sort, 1433 | $run1, 1434 | $run2); 1435 | 1436 | } else { 1437 | echo "No XHProf runs specified in the URL."; 1438 | if (method_exists($xhprof_runs_impl, 'list_runs')) { 1439 | $xhprof_runs_impl->list_runs(); 1440 | } 1441 | } 1442 | } 1443 | --------------------------------------------------------------------------------