Adding xhprof to tests directory to improve tuning processes - this will not be part of the production packages anyway
parent
237f49d081
commit
be0121c630
@ -0,0 +1,11 @@ |
|||||||
|
<?php |
||||||
|
if (extension_loaded('xhprof')) { |
||||||
|
$profiler_namespace = 'chamilolms'; // namespace for your application |
||||||
|
$xhprof_data = xhprof_disable(); |
||||||
|
$xhprof_runs = new XHProfRuns_Default(); |
||||||
|
$run_id = $xhprof_runs->save_run($xhprof_data, $profiler_namespace); |
||||||
|
|
||||||
|
// url to the XHProf UI libraries (change the host name and path) |
||||||
|
$profiler_url = sprintf('http://my.chamilo.net/tests/xhprof/xhprof_html/index.php?run=%s&source=%s', $run_id, $profiler_namespace); |
||||||
|
echo '<a href="'. $profiler_url .'" target="_blank">Profiler output</a>'; |
||||||
|
} |
@ -0,0 +1,6 @@ |
|||||||
|
<?php |
||||||
|
if (extension_loaded('xhprof')) { |
||||||
|
include_once dirname(__FILE__).'/xhprof_lib/utils/xhprof_lib.php'; |
||||||
|
include_once dirname(__FILE__).'/xhprof_lib/utils/xhprof_runs.php'; |
||||||
|
xhprof_enable(XHPROF_FLAGS_CPU + XHPROF_FLAGS_MEMORY); |
||||||
|
} |
@ -0,0 +1,91 @@ |
|||||||
|
<?php |
||||||
|
// Copyright (c) 2009 Facebook |
||||||
|
// |
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License"); |
||||||
|
// you may not use this file except in compliance with the License. |
||||||
|
// You may obtain a copy of the License at |
||||||
|
// |
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0 |
||||||
|
// |
||||||
|
// Unless required by applicable law or agreed to in writing, software |
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
// See the License for the specific language governing permissions and |
||||||
|
// limitations under the License. |
||||||
|
// |
||||||
|
|
||||||
|
/** |
||||||
|
* |
||||||
|
* A callgraph generator for XHProf. |
||||||
|
* |
||||||
|
* * This file is part of the UI/reporting component, |
||||||
|
* used for viewing results of XHProf runs from a |
||||||
|
* browser. |
||||||
|
* |
||||||
|
* Modification History: |
||||||
|
* 02/15/2008 - cjiang - The first version of callgraph visualizer |
||||||
|
* based on Graphviz's DOT tool. |
||||||
|
* |
||||||
|
* @author Changhao Jiang (cjiang@facebook.com) |
||||||
|
*/ |
||||||
|
|
||||||
|
// by default assume that xhprof_html & xhprof_lib directories |
||||||
|
// are at the same level. |
||||||
|
$GLOBALS['XHPROF_LIB_ROOT'] = dirname(__FILE__) . '/../xhprof_lib'; |
||||||
|
|
||||||
|
include_once $GLOBALS['XHPROF_LIB_ROOT'].'/display/xhprof.php'; |
||||||
|
|
||||||
|
ini_set('max_execution_time', 100); |
||||||
|
|
||||||
|
$params = array(// run id param |
||||||
|
'run' => array(XHPROF_STRING_PARAM, ''), |
||||||
|
|
||||||
|
// source/namespace/type of run |
||||||
|
'source' => array(XHPROF_STRING_PARAM, 'xhprof'), |
||||||
|
|
||||||
|
// the focus function, if it is set, only directly |
||||||
|
// parents/children functions of it will be shown. |
||||||
|
'func' => array(XHPROF_STRING_PARAM, ''), |
||||||
|
|
||||||
|
// image type, can be 'jpg', 'gif', 'ps', 'png' |
||||||
|
'type' => array(XHPROF_STRING_PARAM, 'png'), |
||||||
|
|
||||||
|
// only functions whose exclusive time over the total time |
||||||
|
// is larger than this threshold will be shown. |
||||||
|
// default is 0.01. |
||||||
|
'threshold' => array(XHPROF_FLOAT_PARAM, 0.01), |
||||||
|
|
||||||
|
// whether to show critical_path |
||||||
|
'critical' => array(XHPROF_BOOL_PARAM, true), |
||||||
|
|
||||||
|
// first run in diff mode. |
||||||
|
'run1' => array(XHPROF_STRING_PARAM, ''), |
||||||
|
|
||||||
|
// second run in diff mode. |
||||||
|
'run2' => array(XHPROF_STRING_PARAM, '') |
||||||
|
); |
||||||
|
|
||||||
|
// pull values of these params, and create named globals for each param |
||||||
|
xhprof_param_init($params); |
||||||
|
|
||||||
|
// if invalid value specified for threshold, then use the default |
||||||
|
if ($threshold < 0 || $threshold > 1) { |
||||||
|
$threshold = $params['threshold'][1]; |
||||||
|
} |
||||||
|
|
||||||
|
// if invalid value specified for type, use the default |
||||||
|
if (!array_key_exists($type, $xhprof_legal_image_types)) { |
||||||
|
$type = $params['type'][1]; // default image type. |
||||||
|
} |
||||||
|
|
||||||
|
$xhprof_runs_impl = new XHProfRuns_Default(); |
||||||
|
|
||||||
|
if (!empty($run)) { |
||||||
|
// single run call graph image generation |
||||||
|
xhprof_render_image($xhprof_runs_impl, $run, $type, |
||||||
|
$threshold, $func, $source, $critical); |
||||||
|
} else { |
||||||
|
// diff report call graph image generation |
||||||
|
xhprof_render_diff_image($xhprof_runs_impl, $run1, $run2, |
||||||
|
$type, $threshold, $source); |
||||||
|
} |
@ -0,0 +1,82 @@ |
|||||||
|
/* Copyright (c) 2009 Facebook |
||||||
|
* |
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||||
|
* you may not use this file except in compliance with the License. |
||||||
|
* You may obtain a copy of the License at |
||||||
|
* |
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0 |
||||||
|
* |
||||||
|
* Unless required by applicable law or agreed to in writing, software |
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
* See the License for the specific language governing permissions and |
||||||
|
* limitations under the License. |
||||||
|
*/ |
||||||
|
|
||||||
|
td.sorted { |
||||||
|
color:#0000FF; |
||||||
|
} |
||||||
|
|
||||||
|
td.vbar, th.vbar { |
||||||
|
text-align: right; |
||||||
|
border-left: |
||||||
|
solid 1px #bdc7d8; |
||||||
|
} |
||||||
|
|
||||||
|
td.vbbar, th.vbar { |
||||||
|
text-align: right; |
||||||
|
border-left: |
||||||
|
solid 1px #bdc7d8; |
||||||
|
color:blue; |
||||||
|
} |
||||||
|
|
||||||
|
/* diff reports: display regressions in red */ |
||||||
|
td.vrbar { |
||||||
|
text-align: right; |
||||||
|
border-left:solid 1px #bdc7d8; |
||||||
|
color:red; |
||||||
|
} |
||||||
|
|
||||||
|
/* diff reports: display improvements in green */ |
||||||
|
td.vgbar { |
||||||
|
text-align: right; |
||||||
|
border-left: solid 1px #bdc7d8; |
||||||
|
color:green; |
||||||
|
} |
||||||
|
|
||||||
|
td.vwbar, th.vwbar { |
||||||
|
text-align: right; |
||||||
|
border-left: solid 1px white; |
||||||
|
} |
||||||
|
|
||||||
|
td.vwlbar, th.vwlbar { |
||||||
|
text-align: left; |
||||||
|
border-left: solid 1px white; |
||||||
|
} |
||||||
|
|
||||||
|
p.blue { |
||||||
|
color:blue |
||||||
|
} |
||||||
|
|
||||||
|
.bubble { |
||||||
|
background-color:#C3D9FF |
||||||
|
} |
||||||
|
|
||||||
|
ul.xhprof_actions { |
||||||
|
float: right; |
||||||
|
padding-left: 16px; |
||||||
|
list-style-image: none; |
||||||
|
list-style-type: none; |
||||||
|
margin:10px 10px 10px 3em; |
||||||
|
position:relative; |
||||||
|
} |
||||||
|
|
||||||
|
ul.xhprof_actions li { |
||||||
|
border-bottom:1px solid #D8DFEA; |
||||||
|
} |
||||||
|
|
||||||
|
ul.xhprof_actions li a:hover { |
||||||
|
background:#3B5998 none repeat scroll 0 0; |
||||||
|
color:#FFFFFF; |
||||||
|
} |
||||||
|
|
@ -0,0 +1,826 @@ |
|||||||
|
<html> |
||||||
|
<title> |
||||||
|
XHProf Documentation (Draft) |
||||||
|
</title> |
||||||
|
<body> |
||||||
|
<h3>XHProf Documentation (Draft)</h3> |
||||||
|
|
||||||
|
<b>Contents</b> |
||||||
|
<ol> |
||||||
|
<li><a href="#introduction">Introduction</a></li> |
||||||
|
|
||||||
|
<li><a href="#overview">XHProf Overview</a></li> |
||||||
|
|
||||||
|
<li><a href="#installation">Installing the XHProf extension</a></li> |
||||||
|
|
||||||
|
<li><a href="#using_extension">Profiling using XHProf</a></li> |
||||||
|
|
||||||
|
<li><a href="#ui_setup">Setting up the XHProf UI</a></li> |
||||||
|
|
||||||
|
<li><a href="#production_notes">Notes on using XHProf in production</a></li> |
||||||
|
|
||||||
|
<li><a href="#sampling_mode">Lightweight Sampling Mode</a> |
||||||
|
|
||||||
|
<li><a href="#misc">Additional features</a></li> |
||||||
|
|
||||||
|
<li><a href="#dependencies">Dependencies</a></li> |
||||||
|
|
||||||
|
<li><a href="#credits">Acknowledgements</a></li> |
||||||
|
</ol> |
||||||
|
|
||||||
|
<ol> |
||||||
|
<li><a name="introduction"><h2>Introduction</h2></a> |
||||||
|
|
||||||
|
<p>XHProf is a hierarchical profiler for PHP. It reports |
||||||
|
function-level call counts and <a href="#inclusive">inclusive</a> and |
||||||
|
<a href="#exclusive">exclusive</a> metrics such as wall (elapsed) |
||||||
|
time, CPU time and memory usage. A function's profile can be broken |
||||||
|
down by callers or callees. The raw data collection component is |
||||||
|
implemented in C as a PHP Zend extension called |
||||||
|
<code><b>xhprof</b></code>. XHProf has a simple HTML based user |
||||||
|
interface (written in PHP). The browser based UI for viewing profiler |
||||||
|
results makes it easy to view results or to share results with peers. |
||||||
|
A callgraph image view is also supported. |
||||||
|
|
||||||
|
<p>XHProf reports can often be helpful in understanding the structure |
||||||
|
of the code being executed. The hierarchical nature of the reports can |
||||||
|
be used to determine, for example, what chain of calls led to a |
||||||
|
particular function getting called. |
||||||
|
|
||||||
|
<p>XHProf supports ability to compare two runs (a.k.a. "diff" reports) |
||||||
|
or aggregate data from multiple runs. Diff and aggregate reports, much |
||||||
|
like single run reports, offer "flat" as well as "hierarchical" views |
||||||
|
of the profile. |
||||||
|
|
||||||
|
<p>XHProf is a light-weight instrumentation based profiler. During the |
||||||
|
data collection phase, it keeps track of call counts and inclusive |
||||||
|
metrics for arcs in the dynamic callgraph of a program. It computes |
||||||
|
exclusive metrics in the reporting/post processing phase. XHProf |
||||||
|
handles recursive functions by detecting cycles in the callgraph at |
||||||
|
data collection time itself and avoiding the cycles by giving unique |
||||||
|
depth qualified names for the recursive invocations. |
||||||
|
</p> |
||||||
|
|
||||||
|
<p>XHProf's light-weight nature and aggregation capabilities make it |
||||||
|
well suited for collecting "function-level" performance statistics |
||||||
|
from production environments. [See <a |
||||||
|
href="#production_notes">additional notes</a> for use in production.] |
||||||
|
|
||||||
|
<ul> |
||||||
|
<hr> |
||||||
|
|
||||||
|
<p>XHProfLive (not part of the open source kit), for example, is a |
||||||
|
system-wide performance monitoring system in use at Facebook that is |
||||||
|
built on top of XHProf. XHProfLive continually gathers function-level |
||||||
|
profiler data from production tier by running a sample of page |
||||||
|
requests under XHProf. XHProfLive then aggregates the profile data |
||||||
|
corresponding to individual requests by various dimensions such a |
||||||
|
time, page type, and can help answer a variety of questions such as: |
||||||
|
What is the function-level profile for a specific page? How expensive |
||||||
|
is function "foo" across all pages, or on a specific page? What |
||||||
|
functions regressed most in the last hour/day/week? What is the |
||||||
|
historical trend for execution time of a page/function? and so on. |
||||||
|
|
||||||
|
<hr> |
||||||
|
</ul> |
||||||
|
|
||||||
|
|
||||||
|
<p>Originally developed at Facebook, XHProf was open sourced in Mar, 2009.</p> |
||||||
|
|
||||||
|
|
||||||
|
</ul> |
||||||
|
|
||||||
|
|
||||||
|
<li><a name="overview"><h2>XHProf Overview</h2></a> |
||||||
|
|
||||||
|
<p>XHProf provides: |
||||||
|
|
||||||
|
<ul> |
||||||
|
<li><b>Flat profile</b> (<a href="sample-flat-view.jpg" >screenshot</a>) |
||||||
|
|
||||||
|
<p>Provides function-level summary information such number of calls, |
||||||
|
inclusive/exclusive wall time, memory usage, and CPU time. |
||||||
|
|
||||||
|
<p><li><b>Hierarchical profile (Parent/Child View)</b> |
||||||
|
(<a href="sample-parent-child-view.jpg" >screenshot</a>) |
||||||
|
|
||||||
|
<p>For each function, it provides a breakdown of calls and times per |
||||||
|
parent (caller) & child (callee), such as: |
||||||
|
|
||||||
|
<ul> |
||||||
|
|
||||||
|
<li> what functions call a particular function and how many times? |
||||||
|
|
||||||
|
<li> what functions does a particular function call? |
||||||
|
|
||||||
|
<li> The total time spent under a function when called from a particular parent. |
||||||
|
|
||||||
|
</ul> |
||||||
|
|
||||||
|
<p><li><b>Diff Reports</b> |
||||||
|
|
||||||
|
<p>You may want to compare data from two XHProf runs for various |
||||||
|
reasons-- to figure out what's causing a regression between one |
||||||
|
version of the code base to another, to evaluate the performance |
||||||
|
improvement of a code change you are making, and so on. |
||||||
|
|
||||||
|
<p>A diff report takes two runs as input and provides both flat |
||||||
|
function-level diff information, and hierarchical information |
||||||
|
(breakdown of diff by parent/children functions) for each function. |
||||||
|
|
||||||
|
<p>The "flat" view (<a href="sample-diff-report-flat-view.jpg" |
||||||
|
>sample screenshot</a>) in the diff report points out the top |
||||||
|
regressions & improvements. |
||||||
|
|
||||||
|
<p>Clicking on functions in the "flat" view of the diff report, leads |
||||||
|
to the "hierarchical" (or parent/child) diff view of a function (<a href="sample-diff-report-parent-child-view.jpg" |
||||||
|
>sample screenshot</a>). We can get a |
||||||
|
breakdown of the diff by parent/children functions. |
||||||
|
|
||||||
|
|
||||||
|
<p><li><b>Callgraph View</b> (<a href="sample-callgraph-image.jpg" |
||||||
|
>sample screenshot</a>) |
||||||
|
|
||||||
|
<p>The profile data can also be viewed as a callgraph. The callgraph |
||||||
|
view highlights the critical path of the program. |
||||||
|
|
||||||
|
|
||||||
|
<p><li><b>Memory Profile</b> |
||||||
|
|
||||||
|
<p>XHProf's memory profile mode helps track functions that |
||||||
|
allocate lots of memory. |
||||||
|
|
||||||
|
<p>It is worth clarifying that that XHProf doesn't strictly track each |
||||||
|
allocation/free operation. Rather it uses a more simplistic |
||||||
|
scheme. It tracks the increase/decrease in the amount of memory |
||||||
|
allocated to PHP between each function's entry and exit. It also |
||||||
|
tracks increase/decrease in the amount of <b>peak</b> memory allocated to |
||||||
|
PHP for each function. |
||||||
|
|
||||||
|
<li>XHProf tracks <code>include, include_once, require and |
||||||
|
require_once</code> operations as if they were functions. The name of |
||||||
|
the file being included is used to generate the name for these <a |
||||||
|
href="#include_funcs">"fake" functions</a>. |
||||||
|
|
||||||
|
|
||||||
|
</ul> |
||||||
|
|
||||||
|
<a name="Terminology"></a><h2>Terminology</h2> |
||||||
|
<ol> |
||||||
|
|
||||||
|
<a name="inclusive"></a><li><b>Inclusive Time (or Subtree Time)</b>: |
||||||
|
Includes time spent in the function as well as in descendant functions |
||||||
|
called from a given function. |
||||||
|
|
||||||
|
<a name="exclusive"></a><li><b>Exclusive Time/Self Time</b>: Measures |
||||||
|
time spent in the function itself. Does not include time in descendant |
||||||
|
functions. |
||||||
|
|
||||||
|
<li><b>Wall Time</b>: a.k.a. Elapsed time or wall clock time. |
||||||
|
|
||||||
|
<li><b>CPU Time</b>: CPU time in user space + CPU time in kernel space |
||||||
|
|
||||||
|
</ol> |
||||||
|
<a name="Naming_convention_for_special_functions"></a><h2>Naming convention for special functions</h2> |
||||||
|
|
||||||
|
<ol> |
||||||
|
<p><li><code><b>main()</b></code>: a fictitious function that is at the root of the call graph. |
||||||
|
|
||||||
|
<a name="include_funcs"></a> |
||||||
|
<p><li><code><b>load::<filename></b></code> |
||||||
|
and <code><b>run_init::<filename></b></code>: |
||||||
|
|
||||||
|
<p>XHProf tracks PHP <code>include/require</code> operations as |
||||||
|
function calls. |
||||||
|
|
||||||
|
<p>For example, an <b>include "lib/common.php";</b> operation will |
||||||
|
result in two XHProf function entries: |
||||||
|
|
||||||
|
<ul> |
||||||
|
|
||||||
|
<li> <code><b>load::lib/common.php</b></code> - This represents the work done by the |
||||||
|
interpreter to compile/load the file. [Note: If you are using a PHP |
||||||
|
opcode cache like APC, then the compile only happens on a cache miss |
||||||
|
in APC.] |
||||||
|
|
||||||
|
<li> <code><b>run_init::lib/common.php</b></code> - This represents |
||||||
|
initialization code executed at the file scope as a result of the |
||||||
|
include operation. |
||||||
|
|
||||||
|
</ul> |
||||||
|
|
||||||
|
<p><li><code><b>foo@<n></b></code>: Implies that this is a |
||||||
|
recursive invocation of <code>foo()</code>, where <code><n></code> represents |
||||||
|
the recursion depth. The recursion may be direct (such as due to |
||||||
|
<code>foo()</code> --> <code>foo()</code>), or indirect (such as |
||||||
|
due to </code>foo()</code> --> <code>goo()</code> --> foo()). |
||||||
|
|
||||||
|
</ol> |
||||||
|
|
||||||
|
|
||||||
|
<a name="Limitations"></a><h2>Limitations</h2> |
||||||
|
|
||||||
|
<p>True hierarchical profilers keep track of a full call stack at |
||||||
|
every data gathering point, and are later able to answer questions |
||||||
|
like: what was the cost of the 3rd invokation of foo()? or what was |
||||||
|
the cost of bar() when the call stack looked like |
||||||
|
a()->b()->bar()? |
||||||
|
|
||||||
|
</p> |
||||||
|
|
||||||
|
<p>XHProf keeps track of only 1-level of calling context and is |
||||||
|
therefore only able to answer questions about a function looking |
||||||
|
either 1-level up or 1-level down. It turns out that in practice this |
||||||
|
is sufficient for most use cases. |
||||||
|
</p> |
||||||
|
|
||||||
|
<p>To make this more concrete, take for instance the following |
||||||
|
example. |
||||||
|
</p> |
||||||
|
|
||||||
|
<pre> |
||||||
|
Say you have: |
||||||
|
1 call from a() --> c() |
||||||
|
1 call from b() --> c() |
||||||
|
50 calls from c() --> d() |
||||||
|
</pre> |
||||||
|
|
||||||
|
<p>While XHProf can tell you that d() was called from c() 50 times, it |
||||||
|
cannot tell you how many of those calls were triggered due to a() |
||||||
|
vs. b(). [We could speculate that perhaps 25 were due to a() and 25 |
||||||
|
due to b(), but that's not necessarily true.] |
||||||
|
</p> |
||||||
|
|
||||||
|
<p>In practice however, this isn't a very big limitation. |
||||||
|
</p> |
||||||
|
|
||||||
|
<li><a name="installation"><h2>Installing the XHProf Extension</h2></a> |
||||||
|
|
||||||
|
<p> The extension lives in the "extension/" sub-directory. |
||||||
|
|
||||||
|
<ul><hr> |
||||||
|
|
||||||
|
<p><b>Note:</b> A windows port hasn't been implemented yet. We have |
||||||
|
tested <code>xhprof</code> on <b>Linux/FreeBSD</b> so far. |
||||||
|
|
||||||
|
<p>Version 0.9.2 and above of XHProf is also expected to work on <b>Mac |
||||||
|
OS</b>. [We have tested on Mac OS 10.5.] |
||||||
|
|
||||||
|
<p><b>Note:</b> XHProf uses the RDTSC instruction (time stamp counter) |
||||||
|
to implement a really low overhead timer for elapsed time. So at the |
||||||
|
moment <code>xhprof</code> only works on <b>x86</b> architecture. |
||||||
|
Also, since RDTSC values may not be synchronized across CPUs, |
||||||
|
<code>xhprof</code> binds the program to a single CPU during the |
||||||
|
profiling period. |
||||||
|
|
||||||
|
<p>XHProf's RDTSC based timer functionality doesn't work correctly if |
||||||
|
<b>SpeedStep</b> technology is turned on. This technology is available on |
||||||
|
some Intel processors. [Note: Mac desktops and laptops typically have |
||||||
|
SpeedStep turned on by default. To use XHProf, you'll need to disable |
||||||
|
SpeedStep.] |
||||||
|
|
||||||
|
<hr></ul> |
||||||
|
|
||||||
|
<p> The steps |
||||||
|
below should work for Linux/Unix environments. |
||||||
|
|
||||||
|
|
||||||
|
<pre> |
||||||
|
% cd <xhprof_source_directory>/extension/ |
||||||
|
% phpize |
||||||
|
% ./configure --with-php-config=<path to php-config> |
||||||
|
% make |
||||||
|
% make install |
||||||
|
% make test |
||||||
|
</pre> |
||||||
|
|
||||||
|
|
||||||
|
<p><a name="ini_file"></a><b>php.ini file</b>: You can update your |
||||||
|
php.ini file to automatically load your extension. Add the following |
||||||
|
to your php.ini file. |
||||||
|
|
||||||
|
<pre> |
||||||
|
[xhprof] |
||||||
|
extension=xhprof.so |
||||||
|
; |
||||||
|
; directory used by default implementation of the iXHProfRuns |
||||||
|
; interface (namely, the XHProfRuns_Default class) for storing |
||||||
|
; XHProf runs. |
||||||
|
; |
||||||
|
xhprof.output_dir=<directory_for_storing_xhprof_runs> |
||||||
|
</pre> |
||||||
|
|
||||||
|
|
||||||
|
<li><a name="using_extension"><h2>Profiling using XHProf</h2></a> |
||||||
|
|
||||||
|
<p>Test generating raw profiler data using a sample test program like: |
||||||
|
|
||||||
|
<ul> |
||||||
|
<p><b>foo.php</b> |
||||||
|
<pre> |
||||||
|
<?php |
||||||
|
|
||||||
|
function bar($x) { |
||||||
|
if ($x > 0) { |
||||||
|
bar($x - 1); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
function foo() { |
||||||
|
for ($idx = 0; $idx < 2; $idx++) { |
||||||
|
bar($idx); |
||||||
|
$x = strlen("abc"); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// start profiling |
||||||
|
<b>xhprof_enable();</b> |
||||||
|
|
||||||
|
// run program |
||||||
|
foo(); |
||||||
|
|
||||||
|
// stop profiler |
||||||
|
<b>$xhprof_data = xhprof_disable();</b> |
||||||
|
|
||||||
|
// display raw xhprof data for the profiler run |
||||||
|
print_r($xhprof_data); |
||||||
|
</pre> |
||||||
|
</ul> |
||||||
|
|
||||||
|
<p><b>Run the above test program:</b> |
||||||
|
|
||||||
|
<pre> |
||||||
|
% php -dextension=xhprof.so foo.php |
||||||
|
</pre> |
||||||
|
|
||||||
|
<p><b>You should get an output like:</b> |
||||||
|
|
||||||
|
<pre> |
||||||
|
Array |
||||||
|
( |
||||||
|
[foo==>bar] => Array |
||||||
|
( |
||||||
|
[ct] => 2 # 2 calls to bar() from foo() |
||||||
|
[wt] => 27 # inclusive time in bar() when called from foo() |
||||||
|
) |
||||||
|
|
||||||
|
[foo==>strlen] => Array |
||||||
|
( |
||||||
|
[ct] => 2 |
||||||
|
[wt] => 2 |
||||||
|
) |
||||||
|
|
||||||
|
[bar==>bar@1] => Array # a recursive call to bar() |
||||||
|
( |
||||||
|
[ct] => 1 |
||||||
|
[wt] => 2 |
||||||
|
) |
||||||
|
|
||||||
|
[main()==>foo] => Array |
||||||
|
( |
||||||
|
[ct] => 1 |
||||||
|
[wt] => 74 |
||||||
|
) |
||||||
|
|
||||||
|
[main()==>xhprof_disable] => Array |
||||||
|
( |
||||||
|
[ct] => 1 |
||||||
|
[wt] => 0 |
||||||
|
) |
||||||
|
|
||||||
|
[main()] => Array # fake symbol representing root |
||||||
|
( |
||||||
|
[ct] => 1 |
||||||
|
[wt] => 83 |
||||||
|
) |
||||||
|
|
||||||
|
) |
||||||
|
</pre> |
||||||
|
|
||||||
|
<p><b>Note:</b> The raw data only contains "inclusive" metrics. For |
||||||
|
example, the wall time metric in the raw data represents inclusive |
||||||
|
time in microsecs. Exclusive times for any function are computed |
||||||
|
during the analysis/reporting phase. |
||||||
|
|
||||||
|
<p><b>Note:</b> By default only call counts & elapsed time is profiled. |
||||||
|
You can optionally also profile CPU time and/or memory usage. Replace, |
||||||
|
|
||||||
|
<ul><pre> |
||||||
|
<b>xhprof_enable();</b> |
||||||
|
</pre></ul> |
||||||
|
in the above program with, for example: |
||||||
|
<ul><pre> |
||||||
|
<b>xhprof_enable(XHPROF_FLAGS_CPU + XHPROF_FLAGS_MEMORY)</b>; |
||||||
|
</pre></ul> |
||||||
|
|
||||||
|
<p><b>You should now get an output like:</b> |
||||||
|
|
||||||
|
<pre> |
||||||
|
Array |
||||||
|
( |
||||||
|
[foo==>bar] => Array |
||||||
|
( |
||||||
|
[ct] => 2 # number of calls to bar() from foo() |
||||||
|
[wt] => 37 # time in bar() when called from foo() |
||||||
|
[cpu] => 0 # cpu time in bar() when called from foo() |
||||||
|
[mu] => 2208 # change in PHP memory usage in bar() when called from foo() |
||||||
|
[pmu] => 0 # change in PHP peak memory usage in bar() when called from foo() |
||||||
|
) |
||||||
|
|
||||||
|
[foo==>strlen] => Array |
||||||
|
( |
||||||
|
[ct] => 2 |
||||||
|
[wt] => 3 |
||||||
|
[cpu] => 0 |
||||||
|
[mu] => 624 |
||||||
|
[pmu] => 0 |
||||||
|
) |
||||||
|
|
||||||
|
[bar==>bar@1] => Array |
||||||
|
( |
||||||
|
[ct] => 1 |
||||||
|
[wt] => 2 |
||||||
|
[cpu] => 0 |
||||||
|
[mu] => 856 |
||||||
|
[pmu] => 0 |
||||||
|
) |
||||||
|
|
||||||
|
[main()==>foo] => Array |
||||||
|
( |
||||||
|
[ct] => 1 |
||||||
|
[wt] => 104 |
||||||
|
[cpu] => 0 |
||||||
|
[mu] => 4168 |
||||||
|
[pmu] => 0 |
||||||
|
) |
||||||
|
|
||||||
|
[main()==>xhprof_disable] => Array |
||||||
|
( |
||||||
|
[ct] => 1 |
||||||
|
[wt] => 1 |
||||||
|
[cpu] => 0 |
||||||
|
[mu] => 344 |
||||||
|
[pmu] => 0 |
||||||
|
) |
||||||
|
|
||||||
|
[main()] => Array |
||||||
|
( |
||||||
|
[ct] => 1 |
||||||
|
[wt] => 139 |
||||||
|
[cpu] => 0 |
||||||
|
[mu] => 5936 |
||||||
|
[pmu] => 0 |
||||||
|
) |
||||||
|
|
||||||
|
) |
||||||
|
</pre> |
||||||
|
|
||||||
|
<p><b>Skipping builtin functions during profiling</b> |
||||||
|
|
||||||
|
<p>By default PHP builtin functions (such as <code>strlen</code>) are |
||||||
|
profiled. If you do not want to profile builtin functions (to either |
||||||
|
reduce the overhead of profiling further or size of generated raw |
||||||
|
data), you can use the <code><b>XHPROF_FLAGS_NO_BUILTINS</b></code> |
||||||
|
flag as in for example: |
||||||
|
|
||||||
|
<ul><pre> |
||||||
|
// do not profile builtin functions |
||||||
|
<b>xhprof_enable(XHPROF_FLAGS_NO_BUILTINS)</b>; |
||||||
|
</pre></ul> |
||||||
|
|
||||||
|
|
||||||
|
<p><b>Ignoring specific functions during profiling (0.9.2 or higher)</b> |
||||||
|
|
||||||
|
<p>Starting with release 0.9.2 of xhprof, you can tell XHProf to |
||||||
|
ignore a specified list of functions during profiling. This allows you |
||||||
|
to ignore, for example, functions used for indirect function calls |
||||||
|
such as <code>call_user_func</code> and |
||||||
|
<code>call_user_func_array</code>. These intermediate functions |
||||||
|
unnecessarily complicate the call hierarchy and make the XHProf |
||||||
|
reports harder to interpret since they muddle the parent-child |
||||||
|
relationship for functions called indirectly. |
||||||
|
|
||||||
|
<p> To specify the list of functions to be ignored during profiling |
||||||
|
use the 2nd (optional) argument to <code>xhprof_enable</code>. |
||||||
|
For example, |
||||||
|
|
||||||
|
<pre> |
||||||
|
<ul><pre> |
||||||
|
<b> |
||||||
|
// elapsed time profiling; ignore call_user_func* during profiling |
||||||
|
xhprof_enable(0, |
||||||
|
array('ignored_functions' => array('call_user_func', |
||||||
|
'call_user_func_array'))); |
||||||
|
</b> |
||||||
|
or, |
||||||
|
<b> |
||||||
|
// elapsed time + memory profiling; ignore call_user_func* during profiling |
||||||
|
xhprof_enable(XHPROF_FLAGS_MEMORY, |
||||||
|
array('ignored_functions' => array('call_user_func', |
||||||
|
'call_user_func_array'))); |
||||||
|
</b> |
||||||
|
</pre></ul> |
||||||
|
|
||||||
|
|
||||||
|
</li> |
||||||
|
|
||||||
|
<li><a name="ui_setup"><h2>Setting up XHProf UI</h2></a> |
||||||
|
|
||||||
|
|
||||||
|
<ol> |
||||||
|
|
||||||
|
<li><b>PHP source structure</b> |
||||||
|
<p>The XHProf UI is implemented in PHP. The code resides in two |
||||||
|
subdirectories, <code>xhprof_html/</code> and <code>xhprof_lib/</code>. |
||||||
|
|
||||||
|
<p>The <code>xhprof_html</code> directory contains the 3 top-level PHP pages. |
||||||
|
|
||||||
|
<ul> |
||||||
|
<li><code>index.php</code>: For viewing a single run or diff report. |
||||||
|
<li><code>callgraph.php</code>: For viewing a callgraph of a XHProf run as an image. |
||||||
|
<li><code>typeahead.php</code>: Used implicitly for the function typeahead form |
||||||
|
on a XHProf report. |
||||||
|
</ul> |
||||||
|
|
||||||
|
<p>The <code>xhprof_lib</code> directory contains supporting code for |
||||||
|
display as well as analysis (computing flat profile info, computing |
||||||
|
diffs, aggregating data from multiple runs, etc.). |
||||||
|
|
||||||
|
<li><p><b>Web server config: </b> You'll need to make sure that the |
||||||
|
<code>xhprof_html/</code> directory is accessible from your web server, and that |
||||||
|
your web server is setup to serve PHP scripts. |
||||||
|
|
||||||
|
<li><p><b>Managing XHProf Runs</b> |
||||||
|
|
||||||
|
<p>Clients have flexibility in how they save the XHProf raw data |
||||||
|
obtained from an XHProf run. The XHProf UI layer exposes an interface |
||||||
|
iXHProfRuns (see xhprof_lib/utils/xhprof_runs.php) that clients can |
||||||
|
implement. This allows the clients to tell the UI layer how to fetch |
||||||
|
the data corresponding to a XHProf run. |
||||||
|
|
||||||
|
<p>The XHProf UI libaries come with a default file based |
||||||
|
implementation of the iXHProfRuns interface, namely |
||||||
|
"XHProfRuns_Default" (also in xhprof_lib/utils/xhprof_runs.php). |
||||||
|
This default implementation stores runs in the directory specified by |
||||||
|
<a href="#ini_file"><b>xhprof.output_dir</b></a> INI parameter. |
||||||
|
|
||||||
|
<p>A XHProf run must be uniquely identified by a namespace and a run |
||||||
|
id. |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<p><b>a) Saving XHProf data persistently</b>: |
||||||
|
|
||||||
|
<p>Assuming you are using the default implementation |
||||||
|
<code><b>XHProfRuns_Default</b></code> of the |
||||||
|
<code><b>iXHProfRuns</b></code> interface, a typical XHProf run |
||||||
|
followed by the save step might look something like: |
||||||
|
|
||||||
|
|
||||||
|
<pre> |
||||||
|
// start profiling |
||||||
|
xhprof_enable(); |
||||||
|
|
||||||
|
// run program |
||||||
|
.... |
||||||
|
|
||||||
|
// stop profiler |
||||||
|
$xhprof_data = xhprof_disable(); |
||||||
|
|
||||||
|
// |
||||||
|
// Saving the XHProf run |
||||||
|
// using the default implementation of iXHProfRuns. |
||||||
|
// |
||||||
|
include_once $XHPROF_ROOT . "/xhprof_lib/utils/xhprof_lib.php"; |
||||||
|
include_once $XHPROF_ROOT . "/xhprof_lib/utils/xhprof_runs.php"; |
||||||
|
|
||||||
|
$xhprof_runs = new <b>XHProfRuns_Default()</b>; |
||||||
|
|
||||||
|
// Save the run under a namespace "xhprof_foo". |
||||||
|
// |
||||||
|
// **NOTE**: |
||||||
|
// By default save_run() will automatically generate a unique |
||||||
|
// run id for you. [You can override that behavior by passing |
||||||
|
// a run id (optional arg) to the save_run() method instead.] |
||||||
|
// |
||||||
|
<b>$run_id = $xhprof_runs->save_run($xhprof_data, "xhprof_foo");</b> |
||||||
|
|
||||||
|
echo "---------------\n". |
||||||
|
"Assuming you have set up the http based UI for \n". |
||||||
|
"XHProf at some address, you can view run at \n". |
||||||
|
"http://<xhprof-ui-address>/index.php?run=$run_id&source=xhprof_foo\n". |
||||||
|
"---------------\n"; |
||||||
|
|
||||||
|
</pre> |
||||||
|
|
||||||
|
<p>The above should save the run as a file in the directory specified |
||||||
|
by the <code><b>xhprof.output_dir</b></code> INI parameter. The file's |
||||||
|
name might be something like |
||||||
|
<b><code>49bafaa3a3f66.xhprof_foo</code></b>; the two parts being the |
||||||
|
run id ("49bafaa3a3f66") and the namespace ("xhprof_foo"). [If you |
||||||
|
want to create/assign run ids yourself (such as a database sequence |
||||||
|
number, or a timestamp), you can explicitly pass in the run id to the |
||||||
|
<code>save_run</code> method. |
||||||
|
|
||||||
|
|
||||||
|
<p><b>b) Using your own implementation of iXHProfRuns</b> |
||||||
|
|
||||||
|
<p> If you decide you want your XHProf runs to be stored differently |
||||||
|
(either in a compressed format, in an alternate place such as DB, |
||||||
|
etc.) database, you'll need to implement a class that implements the |
||||||
|
iXHProfRuns() interface. |
||||||
|
|
||||||
|
<p> You'll also need to modify the 3 main PHP entry pages (index.php, |
||||||
|
callgraph.php, typeahead.php) in the "xhprof_html/" directory to use |
||||||
|
the new class instead of the default class <code>XHProfRuns_Default</code>. |
||||||
|
Change this line in the 3 files. |
||||||
|
|
||||||
|
<pre> |
||||||
|
$xhprof_runs_impl = new XHProfRuns_Default(); |
||||||
|
</pre> |
||||||
|
|
||||||
|
<p>You'll also need to "include" the file that implements your class in |
||||||
|
the above files. |
||||||
|
|
||||||
|
|
||||||
|
<li><p><b>Accessing runs from UI</b> |
||||||
|
|
||||||
|
<p><b>a) Viewing a Single Run Report</b> |
||||||
|
|
||||||
|
<p>To view the report for run id say <run_id> and namespace |
||||||
|
<namespace> use a URL of the form: |
||||||
|
|
||||||
|
<p><code> |
||||||
|
http://<xhprof-ui-address>/index.php?run=<run_id>&source=<namespace> |
||||||
|
</code> |
||||||
|
|
||||||
|
<p>For example, |
||||||
|
<p><code> |
||||||
|
http://<xhprof-ui-address>/index.php?run=49bafaa3a3f66&source=xhprof_foo |
||||||
|
</code> |
||||||
|
|
||||||
|
|
||||||
|
<p><b>b) Viewing a Diff Report</b> |
||||||
|
|
||||||
|
<p>To view the report for run ids say <run_id1> and |
||||||
|
<run_id2> in namespace <namespace> use a URL of the form: |
||||||
|
|
||||||
|
<p><code> |
||||||
|
http://<xhprof-ui-address>/index.php?<b>run1=<run_id1>&run2=<run_id2></b>&source=<namespace> |
||||||
|
</code> |
||||||
|
|
||||||
|
<p><b>c) Aggregate Report</b> |
||||||
|
|
||||||
|
<p>You can also specify a set of run ids for which you want an aggregated view/report. |
||||||
|
|
||||||
|
<p>Say you have three XHProf runs with ids 1, 2 & 3 in namespace |
||||||
|
"benchmark". To view an aggregate report of these runs: |
||||||
|
|
||||||
|
<ul><p><code> |
||||||
|
http://<xhprof-ui-address>/index.php?<b>run=1,2,3</b>&source=benchmark |
||||||
|
</code></p></ul> |
||||||
|
|
||||||
|
<p><b>Weighted aggregations</b>: Further suppose that the above three runs |
||||||
|
correspond to three types of programs p1.php, p2.php and p3.php that |
||||||
|
typically occur in a mix of 20%, 30%, 50% respectively. To view an |
||||||
|
aggregate report that corresponds to a weighted average of these runs |
||||||
|
using: |
||||||
|
|
||||||
|
<ul><p><code> |
||||||
|
http://<xhprof-ui-address>/index.php?<b>run=1,2,3&wts=20,30,50</b>&source=benchmark |
||||||
|
</code></p></ul> |
||||||
|
|
||||||
|
|
||||||
|
</ol> |
||||||
|
|
||||||
|
<li><a name="production_notes"><h2>Notes on using XHProf in production</h2></a> |
||||||
|
|
||||||
|
<p>Some observations/guidelines. Your mileage may vary: |
||||||
|
|
||||||
|
<ul> |
||||||
|
|
||||||
|
<li>CPU timer (getrusage) on Linux has high overheads. It is also |
||||||
|
coarse grained (millisec accuracy rather than microsec level) to be |
||||||
|
useful at function level. Therefore, the skew in reported numbers |
||||||
|
when using XHPROF_FLAGS_CPU mode tends to be higher. |
||||||
|
|
||||||
|
<p>We recommend using elapsed time + memory profiling mode in |
||||||
|
production. [Note: The additional overhead of memory profiling |
||||||
|
mode is really low.] |
||||||
|
|
||||||
|
<p><ul><pre><b> |
||||||
|
// elapsed time profiling (default) + memory profiling |
||||||
|
xhprof_enable(XHPROF_FLAGS_MEMORY); |
||||||
|
</b></pre> |
||||||
|
</ul></p> |
||||||
|
|
||||||
|
|
||||||
|
<li>Profiling a random sample of pages/requests works well in capturing |
||||||
|
data that is representative of your production workload. |
||||||
|
|
||||||
|
<p>To profile say 1/10000 of your requests, instrument the beginning of |
||||||
|
your request processing with something along the lines of: |
||||||
|
|
||||||
|
<p><ul><pre><code> |
||||||
|
if (mt_rand(1, 10000) == 1) { |
||||||
|
xhprof_enable(XHPROF_FLAGS_MEMORY); |
||||||
|
$xhprof_on = true; |
||||||
|
} |
||||||
|
</code></pre></ul></p> |
||||||
|
|
||||||
|
<p>At the end of the request (or in a request shutdown function), you might |
||||||
|
then do something like: |
||||||
|
|
||||||
|
<p><ul><pre><code> |
||||||
|
if ($xhprof_on) { |
||||||
|
// stop profiler |
||||||
|
$xhprof_data = xhprof_disable(); |
||||||
|
|
||||||
|
// save $xhprof_data somewhere (say a central DB) |
||||||
|
... |
||||||
|
} |
||||||
|
</code></pre></ul></p> |
||||||
|
|
||||||
|
<p> You can then rollup/aggregate these individual profiles by time |
||||||
|
(e.g., 5 minutely/hourly/daily basis), page/request type,or other |
||||||
|
dimensions using <a href="#xhprof_aggregate_runs"><code><b>xhprof_aggregate_runs()</b></code></a>. |
||||||
|
|
||||||
|
</ul> |
||||||
|
|
||||||
|
|
||||||
|
<li><a name="sampling_mode"><h2>Lightweight Sampling Mode</h2></a> |
||||||
|
|
||||||
|
<p>The xhprof extension also provides a very light weight <b>sampling |
||||||
|
mode</b>. The sampling interval is 0.1 secs. Samples record the full |
||||||
|
function call stack. The sampling mode can be useful if an extremely |
||||||
|
low overhead means of doing performance monitoring and diagnostics is |
||||||
|
desired. |
||||||
|
|
||||||
|
<p> The relevant functions exposed by the extension for using the |
||||||
|
sampling mode are <code><b>xhprof_sample_enable()</b></code> and |
||||||
|
<code><b>xhprof_sample_disable()</b></code>. |
||||||
|
|
||||||
|
|
||||||
|
<p>[<b>TBD</b>: more detailed documentation on sampling mode.] |
||||||
|
|
||||||
|
|
||||||
|
<li><a name="misc"><h2>Additional Features</h2></a></li> |
||||||
|
|
||||||
|
<p>The <code><b>xhprof_lib/utils/xhprof_lib.php</b></code> file contains |
||||||
|
additional library functions that can be used for manipulating/ |
||||||
|
aggregating XHProf runs. |
||||||
|
|
||||||
|
<p>For example: |
||||||
|
|
||||||
|
<ul> |
||||||
|
|
||||||
|
<a name="xhprof_aggregate_runs"></a> |
||||||
|
<p><li><code><b>xhprof_aggregate_runs()</b></code>: |
||||||
|
can be used to aggregate multiple XHProf runs into a single run. This |
||||||
|
can be helpful for building a system-wide "function-level" performance |
||||||
|
monitoring tool using XHProf. [For example, you might to roll up |
||||||
|
XHProf runs sampled from production periodically to generate hourly, |
||||||
|
daily, reports.] |
||||||
|
|
||||||
|
<p><li><code><b>xhprof_prune_run()</b></code>: Aggregating large number of |
||||||
|
XHProf runs (especially if they correspond to different types of |
||||||
|
programs) can result in the callgraph size becoming too large. You can |
||||||
|
use <code>xhprof_prune_run</code> function to prune the callgraph data |
||||||
|
by editing out subtrees that account for a very small portion of the |
||||||
|
total time. |
||||||
|
|
||||||
|
</ul> |
||||||
|
|
||||||
|
|
||||||
|
<ol> |
||||||
|
|
||||||
|
</ol> |
||||||
|
|
||||||
|
<li><a name="dependencies"><h2>Dependencies</h2></a></li> |
||||||
|
|
||||||
|
<ul> |
||||||
|
<li><b>JQuery Javascript</b>: For tooltips and function name |
||||||
|
typeahead, we make use of JQuery's javascript libraries. JQuery is |
||||||
|
available under both a MIT and GPL licencse |
||||||
|
(http://docs.jquery.com/Licensing). The relevant JQuery code, used by |
||||||
|
XHProf, is in the <code>xhprof_html/jquery</code> subdirectory. |
||||||
|
|
||||||
|
<li><b>dot (image generation utility):</b> The callgraph image |
||||||
|
visualization ([View Callgraph]) feature relies on the presence of |
||||||
|
Graphviz "dot" utility in your path. "dot" is a utility to |
||||||
|
draw/generate an image for a directed graph. |
||||||
|
|
||||||
|
</ul> |
||||||
|
<li><a name="credits"><h2>Acknowledgements</h2></a> |
||||||
|
|
||||||
|
<p>The HTML-based navigational interface for browsing profiler results |
||||||
|
is inspired by that of a similar tool that exists for Oracle's stored |
||||||
|
procedure language, PL/SQL. But that's where the similarity ends; the |
||||||
|
internals of the profiler itself are quite different. |
||||||
|
|
||||||
|
</li> |
||||||
|
|
||||||
|
</ol> |
||||||
|
|
||||||
|
</body> |
||||||
|
</html> |
After Width: | Height: | Size: 146 KiB |
After Width: | Height: | Size: 290 KiB |
After Width: | Height: | Size: 238 KiB |
After Width: | Height: | Size: 268 KiB |
After Width: | Height: | Size: 138 KiB |
@ -0,0 +1,90 @@ |
|||||||
|
<?php |
||||||
|
// Copyright (c) 2009 Facebook |
||||||
|
// |
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License"); |
||||||
|
// you may not use this file except in compliance with the License. |
||||||
|
// You may obtain a copy of the License at |
||||||
|
// |
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0 |
||||||
|
// |
||||||
|
// Unless required by applicable law or agreed to in writing, software |
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
// See the License for the specific language governing permissions and |
||||||
|
// limitations under the License. |
||||||
|
// |
||||||
|
|
||||||
|
// |
||||||
|
// XHProf: A Hierarchical Profiler for PHP |
||||||
|
// |
||||||
|
// XHProf has two components: |
||||||
|
// |
||||||
|
// * This module is the UI/reporting component, used |
||||||
|
// for viewing results of XHProf runs from a browser. |
||||||
|
// |
||||||
|
// * Data collection component: This is implemented |
||||||
|
// as a PHP extension (XHProf). |
||||||
|
// |
||||||
|
// |
||||||
|
// |
||||||
|
// @author(s) Kannan Muthukkaruppan |
||||||
|
// Changhao Jiang |
||||||
|
// |
||||||
|
|
||||||
|
// by default assume that xhprof_html & xhprof_lib directories |
||||||
|
// are at the same level. |
||||||
|
$GLOBALS['XHPROF_LIB_ROOT'] = dirname(__FILE__) . '/../xhprof_lib'; |
||||||
|
|
||||||
|
include_once $GLOBALS['XHPROF_LIB_ROOT'].'/display/xhprof.php'; |
||||||
|
|
||||||
|
// param name, its type, and default value |
||||||
|
$params = array('run' => array(XHPROF_STRING_PARAM, ''), |
||||||
|
'wts' => array(XHPROF_STRING_PARAM, ''), |
||||||
|
'symbol' => array(XHPROF_STRING_PARAM, ''), |
||||||
|
'sort' => array(XHPROF_STRING_PARAM, 'wt'), // wall time |
||||||
|
'run1' => array(XHPROF_STRING_PARAM, ''), |
||||||
|
'run2' => array(XHPROF_STRING_PARAM, ''), |
||||||
|
'source' => array(XHPROF_STRING_PARAM, 'xhprof'), |
||||||
|
'all' => array(XHPROF_UINT_PARAM, 0), |
||||||
|
); |
||||||
|
|
||||||
|
// pull values of these params, and create named globals for each param |
||||||
|
xhprof_param_init($params); |
||||||
|
|
||||||
|
/* reset params to be a array of variable names to values |
||||||
|
by the end of this page, param should only contain values that need |
||||||
|
to be preserved for the next page. unset all unwanted keys in $params. |
||||||
|
*/ |
||||||
|
foreach ($params as $k => $v) { |
||||||
|
$params[$k] = $$k; |
||||||
|
|
||||||
|
// unset key from params that are using default values. So URLs aren't |
||||||
|
// ridiculously long. |
||||||
|
if ($params[$k] == $v[1]) { |
||||||
|
unset($params[$k]); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
echo "<html>"; |
||||||
|
|
||||||
|
echo "<head><title>XHProf: Hierarchical Profiler Report</title>"; |
||||||
|
xhprof_include_js_css(); |
||||||
|
echo "</head>"; |
||||||
|
|
||||||
|
echo "<body>"; |
||||||
|
|
||||||
|
$vbar = ' class="vbar"'; |
||||||
|
$vwbar = ' class="vwbar"'; |
||||||
|
$vwlbar = ' class="vwlbar"'; |
||||||
|
$vbbar = ' class="vbbar"'; |
||||||
|
$vrbar = ' class="vrbar"'; |
||||||
|
$vgbar = ' class="vgbar"'; |
||||||
|
|
||||||
|
$xhprof_runs_impl = new XHProfRuns_Default(); |
||||||
|
|
||||||
|
displayXHProfReport($xhprof_runs_impl, $params, $source, $run, $wts, |
||||||
|
$symbol, $sort, $run1, $run2); |
||||||
|
|
||||||
|
|
||||||
|
echo "</body>"; |
||||||
|
echo "</html>"; |
After Width: | Height: | Size: 1.5 KiB |
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,61 @@ |
|||||||
|
/* |
||||||
|
* Autocomplete - jQuery plugin 1.0.2 |
||||||
|
* |
||||||
|
* Copyright (c) 2007 Dylan Verheul, Dan G. Switzer, Anjesh Tuladhar, Jörn Zaefferer |
||||||
|
* |
||||||
|
* Dual licensed under the MIT and GPL licenses: |
||||||
|
* http://www.opensource.org/licenses/mit-license.php |
||||||
|
* http://www.gnu.org/licenses/gpl.html |
||||||
|
* |
||||||
|
* Revision: $Id: jquery.autocomplete.css,v 1.1.1.1 2009/03/17 18:35:18 kannan Exp $ |
||||||
|
* |
||||||
|
*/ |
||||||
|
|
||||||
|
.ac_results { |
||||||
|
padding: 0px; |
||||||
|
border: 1px solid black; |
||||||
|
background-color: white; |
||||||
|
overflow: hidden; |
||||||
|
z-index: 99999; |
||||||
|
} |
||||||
|
|
||||||
|
.ac_results ul { |
||||||
|
width: 100%; |
||||||
|
list-style-position: outside; |
||||||
|
list-style: none; |
||||||
|
padding: 0; |
||||||
|
margin: 0; |
||||||
|
} |
||||||
|
|
||||||
|
.ac_results li { |
||||||
|
margin: 0px; |
||||||
|
padding: 2px 5px; |
||||||
|
cursor: default; |
||||||
|
display: block; |
||||||
|
/* |
||||||
|
if width will be 100% horizontal scrollbar will apear |
||||||
|
when scroll mode will be used |
||||||
|
*/ |
||||||
|
/*width: 100%;*/ |
||||||
|
font: menu; |
||||||
|
font-size: 12px; |
||||||
|
/* |
||||||
|
it is very important, if line-height not setted or setted |
||||||
|
in relative units scroll will be broken in firefox |
||||||
|
*/ |
||||||
|
line-height: 16px; |
||||||
|
overflow: hidden; |
||||||
|
} |
||||||
|
|
||||||
|
.ac_loading { |
||||||
|
background: white url('indicator.gif') right center no-repeat; |
||||||
|
} |
||||||
|
|
||||||
|
.ac_odd { |
||||||
|
background-color: #eee; |
||||||
|
} |
||||||
|
|
||||||
|
.ac_over { |
||||||
|
background-color: #0A246A; |
||||||
|
color: white; |
||||||
|
} |
@ -0,0 +1,759 @@ |
|||||||
|
/* |
||||||
|
* Autocomplete - jQuery plugin 1.0.2 |
||||||
|
* |
||||||
|
* Copyright (c) 2007 Dylan Verheul, Dan G. Switzer, Anjesh Tuladhar, Jörn Zaefferer |
||||||
|
* |
||||||
|
* Dual licensed under the MIT and GPL licenses: |
||||||
|
* http://www.opensource.org/licenses/mit-license.php
|
||||||
|
* http://www.gnu.org/licenses/gpl.html
|
||||||
|
* |
||||||
|
* Revision: $Id: jquery.autocomplete.js,v 1.1.1.1 2009/03/17 18:35:18 kannan Exp $ |
||||||
|
* |
||||||
|
*/ |
||||||
|
|
||||||
|
;(function($) { |
||||||
|
|
||||||
|
$.fn.extend({ |
||||||
|
autocomplete: function(urlOrData, options) { |
||||||
|
var isUrl = typeof urlOrData == "string"; |
||||||
|
options = $.extend({}, $.Autocompleter.defaults, { |
||||||
|
url: isUrl ? urlOrData : null, |
||||||
|
data: isUrl ? null : urlOrData, |
||||||
|
delay: isUrl ? $.Autocompleter.defaults.delay : 10, |
||||||
|
max: options && !options.scroll ? 10 : 150 |
||||||
|
}, options); |
||||||
|
|
||||||
|
// if highlight is set to false, replace it with a do-nothing function
|
||||||
|
options.highlight = options.highlight || function(value) { return value; }; |
||||||
|
|
||||||
|
// if the formatMatch option is not specified, then use formatItem for backwards compatibility
|
||||||
|
options.formatMatch = options.formatMatch || options.formatItem; |
||||||
|
|
||||||
|
return this.each(function() { |
||||||
|
new $.Autocompleter(this, options); |
||||||
|
}); |
||||||
|
}, |
||||||
|
result: function(handler) { |
||||||
|
return this.bind("result", handler); |
||||||
|
}, |
||||||
|
search: function(handler) { |
||||||
|
return this.trigger("search", [handler]); |
||||||
|
}, |
||||||
|
flushCache: function() { |
||||||
|
return this.trigger("flushCache"); |
||||||
|
}, |
||||||
|
setOptions: function(options){ |
||||||
|
return this.trigger("setOptions", [options]); |
||||||
|
}, |
||||||
|
unautocomplete: function() { |
||||||
|
return this.trigger("unautocomplete"); |
||||||
|
} |
||||||
|
}); |
||||||
|
|
||||||
|
$.Autocompleter = function(input, options) { |
||||||
|
|
||||||
|
var KEY = { |
||||||
|
UP: 38, |
||||||
|
DOWN: 40, |
||||||
|
DEL: 46, |
||||||
|
TAB: 9, |
||||||
|
RETURN: 13, |
||||||
|
ESC: 27, |
||||||
|
COMMA: 188, |
||||||
|
PAGEUP: 33, |
||||||
|
PAGEDOWN: 34, |
||||||
|
BACKSPACE: 8 |
||||||
|
}; |
||||||
|
|
||||||
|
// Create $ object for input element
|
||||||
|
var $input = $(input).attr("autocomplete", "off").addClass(options.inputClass); |
||||||
|
|
||||||
|
var timeout; |
||||||
|
var previousValue = ""; |
||||||
|
var cache = $.Autocompleter.Cache(options); |
||||||
|
var hasFocus = 0; |
||||||
|
var lastKeyPressCode; |
||||||
|
var config = { |
||||||
|
mouseDownOnSelect: false |
||||||
|
}; |
||||||
|
var select = $.Autocompleter.Select(options, input, selectCurrent, config); |
||||||
|
|
||||||
|
var blockSubmit; |
||||||
|
|
||||||
|
// prevent form submit in opera when selecting with return key
|
||||||
|
$.browser.opera && $(input.form).bind("submit.autocomplete", function() { |
||||||
|
if (blockSubmit) { |
||||||
|
blockSubmit = false; |
||||||
|
return false; |
||||||
|
} |
||||||
|
}); |
||||||
|
|
||||||
|
// only opera doesn't trigger keydown multiple times while pressed, others don't work with keypress at all
|
||||||
|
$input.bind(($.browser.opera ? "keypress" : "keydown") + ".autocomplete", function(event) { |
||||||
|
// track last key pressed
|
||||||
|
lastKeyPressCode = event.keyCode; |
||||||
|
switch(event.keyCode) { |
||||||
|
|
||||||
|
case KEY.UP: |
||||||
|
event.preventDefault(); |
||||||
|
if ( select.visible() ) { |
||||||
|
select.prev(); |
||||||
|
} else { |
||||||
|
onChange(0, true); |
||||||
|
} |
||||||
|
break; |
||||||
|
|
||||||
|
case KEY.DOWN: |
||||||
|
event.preventDefault(); |
||||||
|
if ( select.visible() ) { |
||||||
|
select.next(); |
||||||
|
} else { |
||||||
|
onChange(0, true); |
||||||
|
} |
||||||
|
break; |
||||||
|
|
||||||
|
case KEY.PAGEUP: |
||||||
|
event.preventDefault(); |
||||||
|
if ( select.visible() ) { |
||||||
|
select.pageUp(); |
||||||
|
} else { |
||||||
|
onChange(0, true); |
||||||
|
} |
||||||
|
break; |
||||||
|
|
||||||
|
case KEY.PAGEDOWN: |
||||||
|
event.preventDefault(); |
||||||
|
if ( select.visible() ) { |
||||||
|
select.pageDown(); |
||||||
|
} else { |
||||||
|
onChange(0, true); |
||||||
|
} |
||||||
|
break; |
||||||
|
|
||||||
|
// matches also semicolon
|
||||||
|
case options.multiple && $.trim(options.multipleSeparator) == "," && KEY.COMMA: |
||||||
|
case KEY.TAB: |
||||||
|
case KEY.RETURN: |
||||||
|
if( selectCurrent() ) { |
||||||
|
// stop default to prevent a form submit, Opera needs special handling
|
||||||
|
event.preventDefault(); |
||||||
|
blockSubmit = true; |
||||||
|
return false; |
||||||
|
} |
||||||
|
break; |
||||||
|
|
||||||
|
case KEY.ESC: |
||||||
|
select.hide(); |
||||||
|
break; |
||||||
|
|
||||||
|
default: |
||||||
|
clearTimeout(timeout); |
||||||
|
timeout = setTimeout(onChange, options.delay); |
||||||
|
break; |
||||||
|
} |
||||||
|
}).focus(function(){ |
||||||
|
// track whether the field has focus, we shouldn't process any
|
||||||
|
// results if the field no longer has focus
|
||||||
|
hasFocus++; |
||||||
|
}).blur(function() { |
||||||
|
hasFocus = 0; |
||||||
|
if (!config.mouseDownOnSelect) { |
||||||
|
hideResults(); |
||||||
|
} |
||||||
|
}).click(function() { |
||||||
|
// show select when clicking in a focused field
|
||||||
|
if ( hasFocus++ > 1 && !select.visible() ) { |
||||||
|
onChange(0, true); |
||||||
|
} |
||||||
|
}).bind("search", function() { |
||||||
|
// TODO why not just specifying both arguments?
|
||||||
|
var fn = (arguments.length > 1) ? arguments[1] : null; |
||||||
|
function findValueCallback(q, data) { |
||||||
|
var result; |
||||||
|
if( data && data.length ) { |
||||||
|
for (var i=0; i < data.length; i++) { |
||||||
|
if( data[i].result.toLowerCase() == q.toLowerCase() ) { |
||||||
|
result = data[i]; |
||||||
|
break; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
if( typeof fn == "function" ) fn(result); |
||||||
|
else $input.trigger("result", result && [result.data, result.value]); |
||||||
|
} |
||||||
|
$.each(trimWords($input.val()), function(i, value) { |
||||||
|
request(value, findValueCallback, findValueCallback); |
||||||
|
}); |
||||||
|
}).bind("flushCache", function() { |
||||||
|
cache.flush(); |
||||||
|
}).bind("setOptions", function() { |
||||||
|
$.extend(options, arguments[1]); |
||||||
|
// if we've updated the data, repopulate
|
||||||
|
if ( "data" in arguments[1] ) |
||||||
|
cache.populate(); |
||||||
|
}).bind("unautocomplete", function() { |
||||||
|
select.unbind(); |
||||||
|
$input.unbind(); |
||||||
|
$(input.form).unbind(".autocomplete"); |
||||||
|
}); |
||||||
|
|
||||||
|
|
||||||
|
function selectCurrent() { |
||||||
|
var selected = select.selected(); |
||||||
|
if( !selected ) |
||||||
|
return false; |
||||||
|
|
||||||
|
var v = selected.result; |
||||||
|
previousValue = v; |
||||||
|
|
||||||
|
if ( options.multiple ) { |
||||||
|
var words = trimWords($input.val()); |
||||||
|
if ( words.length > 1 ) { |
||||||
|
v = words.slice(0, words.length - 1).join( options.multipleSeparator ) + options.multipleSeparator + v; |
||||||
|
} |
||||||
|
v += options.multipleSeparator; |
||||||
|
} |
||||||
|
|
||||||
|
$input.val(v); |
||||||
|
hideResultsNow(); |
||||||
|
$input.trigger("result", [selected.data, selected.value]); |
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
function onChange(crap, skipPrevCheck) { |
||||||
|
if( lastKeyPressCode == KEY.DEL ) { |
||||||
|
select.hide(); |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
var currentValue = $input.val(); |
||||||
|
|
||||||
|
if ( !skipPrevCheck && currentValue == previousValue ) |
||||||
|
return; |
||||||
|
|
||||||
|
previousValue = currentValue; |
||||||
|
|
||||||
|
currentValue = lastWord(currentValue); |
||||||
|
if ( currentValue.length >= options.minChars) { |
||||||
|
$input.addClass(options.loadingClass); |
||||||
|
if (!options.matchCase) |
||||||
|
currentValue = currentValue.toLowerCase(); |
||||||
|
request(currentValue, receiveData, hideResultsNow); |
||||||
|
} else { |
||||||
|
stopLoading(); |
||||||
|
select.hide(); |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
function trimWords(value) { |
||||||
|
if ( !value ) { |
||||||
|
return [""]; |
||||||
|
} |
||||||
|
var words = value.split( options.multipleSeparator ); |
||||||
|
var result = []; |
||||||
|
$.each(words, function(i, value) { |
||||||
|
if ( $.trim(value) ) |
||||||
|
result[i] = $.trim(value); |
||||||
|
}); |
||||||
|
return result; |
||||||
|
} |
||||||
|
|
||||||
|
function lastWord(value) { |
||||||
|
if ( !options.multiple ) |
||||||
|
return value; |
||||||
|
var words = trimWords(value); |
||||||
|
return words[words.length - 1]; |
||||||
|
} |
||||||
|
|
||||||
|
// fills in the input box w/the first match (assumed to be the best match)
|
||||||
|
// q: the term entered
|
||||||
|
// sValue: the first matching result
|
||||||
|
function autoFill(q, sValue){ |
||||||
|
// autofill in the complete box w/the first match as long as the user hasn't entered in more data
|
||||||
|
// if the last user key pressed was backspace, don't autofill
|
||||||
|
if( options.autoFill && (lastWord($input.val()).toLowerCase() == q.toLowerCase()) && lastKeyPressCode != KEY.BACKSPACE ) { |
||||||
|
// fill in the value (keep the case the user has typed)
|
||||||
|
$input.val($input.val() + sValue.substring(lastWord(previousValue).length)); |
||||||
|
// select the portion of the value not typed by the user (so the next character will erase)
|
||||||
|
$.Autocompleter.Selection(input, previousValue.length, previousValue.length + sValue.length); |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
function hideResults() { |
||||||
|
clearTimeout(timeout); |
||||||
|
timeout = setTimeout(hideResultsNow, 200); |
||||||
|
}; |
||||||
|
|
||||||
|
function hideResultsNow() { |
||||||
|
var wasVisible = select.visible(); |
||||||
|
select.hide(); |
||||||
|
clearTimeout(timeout); |
||||||
|
stopLoading(); |
||||||
|
if (options.mustMatch) { |
||||||
|
// call search and run callback
|
||||||
|
$input.search( |
||||||
|
function (result){ |
||||||
|
// if no value found, clear the input box
|
||||||
|
if( !result ) { |
||||||
|
if (options.multiple) { |
||||||
|
var words = trimWords($input.val()).slice(0, -1); |
||||||
|
$input.val( words.join(options.multipleSeparator) + (words.length ? options.multipleSeparator : "") ); |
||||||
|
} |
||||||
|
else |
||||||
|
$input.val( "" ); |
||||||
|
} |
||||||
|
} |
||||||
|
); |
||||||
|
} |
||||||
|
if (wasVisible) |
||||||
|
// position cursor at end of input field
|
||||||
|
$.Autocompleter.Selection(input, input.value.length, input.value.length); |
||||||
|
}; |
||||||
|
|
||||||
|
function receiveData(q, data) { |
||||||
|
if ( data && data.length && hasFocus ) { |
||||||
|
stopLoading(); |
||||||
|
select.display(data, q); |
||||||
|
autoFill(q, data[0].value); |
||||||
|
select.show(); |
||||||
|
} else { |
||||||
|
hideResultsNow(); |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
function request(term, success, failure) { |
||||||
|
if (!options.matchCase) |
||||||
|
term = term.toLowerCase(); |
||||||
|
var data = cache.load(term); |
||||||
|
// recieve the cached data
|
||||||
|
if (data && data.length) { |
||||||
|
success(term, data); |
||||||
|
// if an AJAX url has been supplied, try loading the data now
|
||||||
|
} else if( (typeof options.url == "string") && (options.url.length > 0) ){ |
||||||
|
|
||||||
|
var extraParams = { |
||||||
|
timestamp: +new Date() |
||||||
|
}; |
||||||
|
$.each(options.extraParams, function(key, param) { |
||||||
|
extraParams[key] = typeof param == "function" ? param() : param; |
||||||
|
}); |
||||||
|
|
||||||
|
$.ajax({ |
||||||
|
// try to leverage ajaxQueue plugin to abort previous requests
|
||||||
|
mode: "abort", |
||||||
|
// limit abortion to this input
|
||||||
|
port: "autocomplete" + input.name, |
||||||
|
dataType: options.dataType, |
||||||
|
url: options.url, |
||||||
|
data: $.extend({ |
||||||
|
q: lastWord(term), |
||||||
|
limit: options.max |
||||||
|
}, extraParams), |
||||||
|
success: function(data) { |
||||||
|
var parsed = options.parse && options.parse(data) || parse(data); |
||||||
|
cache.add(term, parsed); |
||||||
|
success(term, parsed); |
||||||
|
} |
||||||
|
}); |
||||||
|
} else { |
||||||
|
// if we have a failure, we need to empty the list -- this prevents the the [TAB] key from selecting the last successful match
|
||||||
|
select.emptyList(); |
||||||
|
failure(term); |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
function parse(data) { |
||||||
|
var parsed = []; |
||||||
|
var rows = data.split("\n"); |
||||||
|
for (var i=0; i < rows.length; i++) { |
||||||
|
var row = $.trim(rows[i]); |
||||||
|
if (row) { |
||||||
|
row = row.split("|"); |
||||||
|
parsed[parsed.length] = { |
||||||
|
data: row, |
||||||
|
value: row[0], |
||||||
|
result: options.formatResult && options.formatResult(row, row[0]) || row[0] |
||||||
|
}; |
||||||
|
} |
||||||
|
} |
||||||
|
return parsed; |
||||||
|
}; |
||||||
|
|
||||||
|
function stopLoading() { |
||||||
|
$input.removeClass(options.loadingClass); |
||||||
|
}; |
||||||
|
|
||||||
|
}; |
||||||
|
|
||||||
|
$.Autocompleter.defaults = { |
||||||
|
inputClass: "ac_input", |
||||||
|
resultsClass: "ac_results", |
||||||
|
loadingClass: "ac_loading", |
||||||
|
minChars: 1, |
||||||
|
delay: 400, |
||||||
|
matchCase: false, |
||||||
|
matchSubset: true, |
||||||
|
matchContains: false, |
||||||
|
cacheLength: 10, |
||||||
|
max: 100, |
||||||
|
mustMatch: false, |
||||||
|
extraParams: {}, |
||||||
|
selectFirst: true, |
||||||
|
formatItem: function(row) { return row[0]; }, |
||||||
|
formatMatch: null, |
||||||
|
autoFill: false, |
||||||
|
width: 0, |
||||||
|
multiple: false, |
||||||
|
multipleSeparator: ", ", |
||||||
|
highlight: function(value, term) { |
||||||
|
return value.replace(new RegExp("(?![^&;]+;)(?!<[^<>]*)(" + term.replace(/([\^\$\(\)\[\]\{\}\*\.\+\?\|\\])/gi, "\\$1") + ")(?![^<>]*>)(?![^&;]+;)", "gi"), "<strong>$1</strong>"); |
||||||
|
}, |
||||||
|
scroll: true, |
||||||
|
scrollHeight: 180 |
||||||
|
}; |
||||||
|
|
||||||
|
$.Autocompleter.Cache = function(options) { |
||||||
|
|
||||||
|
var data = {}; |
||||||
|
var length = 0; |
||||||
|
|
||||||
|
function matchSubset(s, sub) { |
||||||
|
if (!options.matchCase)
|
||||||
|
s = s.toLowerCase(); |
||||||
|
var i = s.indexOf(sub); |
||||||
|
if (i == -1) return false; |
||||||
|
return i == 0 || options.matchContains; |
||||||
|
}; |
||||||
|
|
||||||
|
function add(q, value) { |
||||||
|
if (length > options.cacheLength){ |
||||||
|
flush(); |
||||||
|
} |
||||||
|
if (!data[q]){
|
||||||
|
length++; |
||||||
|
} |
||||||
|
data[q] = value; |
||||||
|
} |
||||||
|
|
||||||
|
function populate(){ |
||||||
|
if( !options.data ) return false; |
||||||
|
// track the matches
|
||||||
|
var stMatchSets = {}, |
||||||
|
nullData = 0; |
||||||
|
|
||||||
|
// no url was specified, we need to adjust the cache length to make sure it fits the local data store
|
||||||
|
if( !options.url ) options.cacheLength = 1; |
||||||
|
|
||||||
|
// track all options for minChars = 0
|
||||||
|
stMatchSets[""] = []; |
||||||
|
|
||||||
|
// loop through the array and create a lookup structure
|
||||||
|
for ( var i = 0, ol = options.data.length; i < ol; i++ ) { |
||||||
|
var rawValue = options.data[i]; |
||||||
|
// if rawValue is a string, make an array otherwise just reference the array
|
||||||
|
rawValue = (typeof rawValue == "string") ? [rawValue] : rawValue; |
||||||
|
|
||||||
|
var value = options.formatMatch(rawValue, i+1, options.data.length); |
||||||
|
if ( value === false ) |
||||||
|
continue; |
||||||
|
|
||||||
|
var firstChar = value.charAt(0).toLowerCase(); |
||||||
|
// if no lookup array for this character exists, look it up now
|
||||||
|
if( !stMatchSets[firstChar] )
|
||||||
|
stMatchSets[firstChar] = []; |
||||||
|
|
||||||
|
// if the match is a string
|
||||||
|
var row = { |
||||||
|
value: value, |
||||||
|
data: rawValue, |
||||||
|
result: options.formatResult && options.formatResult(rawValue) || value |
||||||
|
}; |
||||||
|
|
||||||
|
// push the current match into the set list
|
||||||
|
stMatchSets[firstChar].push(row); |
||||||
|
|
||||||
|
// keep track of minChars zero items
|
||||||
|
if ( nullData++ < options.max ) { |
||||||
|
stMatchSets[""].push(row); |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
// add the data items to the cache
|
||||||
|
$.each(stMatchSets, function(i, value) { |
||||||
|
// increase the cache size
|
||||||
|
options.cacheLength++; |
||||||
|
// add to the cache
|
||||||
|
add(i, value); |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
// populate any existing data
|
||||||
|
setTimeout(populate, 25); |
||||||
|
|
||||||
|
function flush(){ |
||||||
|
data = {}; |
||||||
|
length = 0; |
||||||
|
} |
||||||
|
|
||||||
|
return { |
||||||
|
flush: flush, |
||||||
|
add: add, |
||||||
|
populate: populate, |
||||||
|
load: function(q) { |
||||||
|
if (!options.cacheLength || !length) |
||||||
|
return null; |
||||||
|
/* |
||||||
|
* if dealing w/local data and matchContains than we must make sure |
||||||
|
* to loop through all the data collections looking for matches |
||||||
|
*/ |
||||||
|
if( !options.url && options.matchContains ){ |
||||||
|
// track all matches
|
||||||
|
var csub = []; |
||||||
|
// loop through all the data grids for matches
|
||||||
|
for( var k in data ){ |
||||||
|
// don't search through the stMatchSets[""] (minChars: 0) cache
|
||||||
|
// this prevents duplicates
|
||||||
|
if( k.length > 0 ){ |
||||||
|
var c = data[k]; |
||||||
|
$.each(c, function(i, x) { |
||||||
|
// if we've got a match, add it to the array
|
||||||
|
if (matchSubset(x.value, q)) { |
||||||
|
csub.push(x); |
||||||
|
} |
||||||
|
}); |
||||||
|
} |
||||||
|
}
|
||||||
|
return csub; |
||||||
|
} else
|
||||||
|
// if the exact item exists, use it
|
||||||
|
if (data[q]){ |
||||||
|
return data[q]; |
||||||
|
} else |
||||||
|
if (options.matchSubset) { |
||||||
|
for (var i = q.length - 1; i >= options.minChars; i--) { |
||||||
|
var c = data[q.substr(0, i)]; |
||||||
|
if (c) { |
||||||
|
var csub = []; |
||||||
|
$.each(c, function(i, x) { |
||||||
|
if (matchSubset(x.value, q)) { |
||||||
|
csub[csub.length] = x; |
||||||
|
} |
||||||
|
}); |
||||||
|
return csub; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
return null; |
||||||
|
} |
||||||
|
}; |
||||||
|
}; |
||||||
|
|
||||||
|
$.Autocompleter.Select = function (options, input, select, config) { |
||||||
|
var CLASSES = { |
||||||
|
ACTIVE: "ac_over" |
||||||
|
}; |
||||||
|
|
||||||
|
var listItems, |
||||||
|
active = -1, |
||||||
|
data, |
||||||
|
term = "", |
||||||
|
needsInit = true, |
||||||
|
element, |
||||||
|
list; |
||||||
|
|
||||||
|
// Create results
|
||||||
|
function init() { |
||||||
|
if (!needsInit) |
||||||
|
return; |
||||||
|
element = $("<div/>") |
||||||
|
.hide() |
||||||
|
.addClass(options.resultsClass) |
||||||
|
.css("position", "absolute") |
||||||
|
.appendTo(document.body); |
||||||
|
|
||||||
|
list = $("<ul/>").appendTo(element).mouseover( function(event) { |
||||||
|
if(target(event).nodeName && target(event).nodeName.toUpperCase() == 'LI') { |
||||||
|
active = $("li", list).removeClass(CLASSES.ACTIVE).index(target(event)); |
||||||
|
$(target(event)).addClass(CLASSES.ACTIVE);
|
||||||
|
} |
||||||
|
}).click(function(event) { |
||||||
|
$(target(event)).addClass(CLASSES.ACTIVE); |
||||||
|
select(); |
||||||
|
// TODO provide option to avoid setting focus again after selection? useful for cleanup-on-focus
|
||||||
|
input.focus(); |
||||||
|
return false; |
||||||
|
}).mousedown(function() { |
||||||
|
config.mouseDownOnSelect = true; |
||||||
|
}).mouseup(function() { |
||||||
|
config.mouseDownOnSelect = false; |
||||||
|
}); |
||||||
|
|
||||||
|
if( options.width > 0 ) |
||||||
|
element.css("width", options.width); |
||||||
|
|
||||||
|
needsInit = false; |
||||||
|
}
|
||||||
|
|
||||||
|
function target(event) { |
||||||
|
var element = event.target; |
||||||
|
while(element && element.tagName != "LI") |
||||||
|
element = element.parentNode; |
||||||
|
// more fun with IE, sometimes event.target is empty, just ignore it then
|
||||||
|
if(!element) |
||||||
|
return []; |
||||||
|
return element; |
||||||
|
} |
||||||
|
|
||||||
|
function moveSelect(step) { |
||||||
|
listItems.slice(active, active + 1).removeClass(CLASSES.ACTIVE); |
||||||
|
movePosition(step); |
||||||
|
var activeItem = listItems.slice(active, active + 1).addClass(CLASSES.ACTIVE); |
||||||
|
if(options.scroll) { |
||||||
|
var offset = 0; |
||||||
|
listItems.slice(0, active).each(function() { |
||||||
|
offset += this.offsetHeight; |
||||||
|
}); |
||||||
|
if((offset + activeItem[0].offsetHeight - list.scrollTop()) > list[0].clientHeight) { |
||||||
|
list.scrollTop(offset + activeItem[0].offsetHeight - list.innerHeight()); |
||||||
|
} else if(offset < list.scrollTop()) { |
||||||
|
list.scrollTop(offset); |
||||||
|
} |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
function movePosition(step) { |
||||||
|
active += step; |
||||||
|
if (active < 0) { |
||||||
|
active = listItems.size() - 1; |
||||||
|
} else if (active >= listItems.size()) { |
||||||
|
active = 0; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
function limitNumberOfItems(available) { |
||||||
|
return options.max && options.max < available |
||||||
|
? options.max |
||||||
|
: available; |
||||||
|
} |
||||||
|
|
||||||
|
function fillList() { |
||||||
|
list.empty(); |
||||||
|
var max = limitNumberOfItems(data.length); |
||||||
|
for (var i=0; i < max; i++) { |
||||||
|
if (!data[i]) |
||||||
|
continue; |
||||||
|
var formatted = options.formatItem(data[i].data, i+1, max, data[i].value, term); |
||||||
|
if ( formatted === false ) |
||||||
|
continue; |
||||||
|
var li = $("<li/>").html( options.highlight(formatted, term) ).addClass(i%2 == 0 ? "ac_even" : "ac_odd").appendTo(list)[0]; |
||||||
|
$.data(li, "ac_data", data[i]); |
||||||
|
} |
||||||
|
listItems = list.find("li"); |
||||||
|
if ( options.selectFirst ) { |
||||||
|
listItems.slice(0, 1).addClass(CLASSES.ACTIVE); |
||||||
|
active = 0; |
||||||
|
} |
||||||
|
// apply bgiframe if available
|
||||||
|
if ( $.fn.bgiframe ) |
||||||
|
list.bgiframe(); |
||||||
|
} |
||||||
|
|
||||||
|
return { |
||||||
|
display: function(d, q) { |
||||||
|
init(); |
||||||
|
data = d; |
||||||
|
term = q; |
||||||
|
fillList(); |
||||||
|
}, |
||||||
|
next: function() { |
||||||
|
moveSelect(1); |
||||||
|
}, |
||||||
|
prev: function() { |
||||||
|
moveSelect(-1); |
||||||
|
}, |
||||||
|
pageUp: function() { |
||||||
|
if (active != 0 && active - 8 < 0) { |
||||||
|
moveSelect( -active ); |
||||||
|
} else { |
||||||
|
moveSelect(-8); |
||||||
|
} |
||||||
|
}, |
||||||
|
pageDown: function() { |
||||||
|
if (active != listItems.size() - 1 && active + 8 > listItems.size()) { |
||||||
|
moveSelect( listItems.size() - 1 - active ); |
||||||
|
} else { |
||||||
|
moveSelect(8); |
||||||
|
} |
||||||
|
}, |
||||||
|
hide: function() { |
||||||
|
element && element.hide(); |
||||||
|
listItems && listItems.removeClass(CLASSES.ACTIVE); |
||||||
|
active = -1; |
||||||
|
}, |
||||||
|
visible : function() { |
||||||
|
return element && element.is(":visible"); |
||||||
|
}, |
||||||
|
current: function() { |
||||||
|
return this.visible() && (listItems.filter("." + CLASSES.ACTIVE)[0] || options.selectFirst && listItems[0]); |
||||||
|
}, |
||||||
|
show: function() { |
||||||
|
var offset = $(input).offset(); |
||||||
|
element.css({ |
||||||
|
width: typeof options.width == "string" || options.width > 0 ? options.width : $(input).width(), |
||||||
|
top: offset.top + input.offsetHeight, |
||||||
|
left: offset.left |
||||||
|
}).show(); |
||||||
|
if(options.scroll) { |
||||||
|
list.scrollTop(0); |
||||||
|
list.css({ |
||||||
|
maxHeight: options.scrollHeight, |
||||||
|
overflow: 'auto' |
||||||
|
}); |
||||||
|
|
||||||
|
if($.browser.msie && typeof document.body.style.maxHeight === "undefined") { |
||||||
|
var listHeight = 0; |
||||||
|
listItems.each(function() { |
||||||
|
listHeight += this.offsetHeight; |
||||||
|
}); |
||||||
|
var scrollbarsVisible = listHeight > options.scrollHeight; |
||||||
|
list.css('height', scrollbarsVisible ? options.scrollHeight : listHeight ); |
||||||
|
if (!scrollbarsVisible) { |
||||||
|
// IE doesn't recalculate width when scrollbar disappears
|
||||||
|
listItems.width( list.width() - parseInt(listItems.css("padding-left")) - parseInt(listItems.css("padding-right")) ); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
}, |
||||||
|
selected: function() { |
||||||
|
var selected = listItems && listItems.filter("." + CLASSES.ACTIVE).removeClass(CLASSES.ACTIVE); |
||||||
|
return selected && selected.length && $.data(selected[0], "ac_data"); |
||||||
|
}, |
||||||
|
emptyList: function (){ |
||||||
|
list && list.empty(); |
||||||
|
}, |
||||||
|
unbind: function() { |
||||||
|
element && element.remove(); |
||||||
|
} |
||||||
|
}; |
||||||
|
}; |
||||||
|
|
||||||
|
$.Autocompleter.Selection = function(field, start, end) { |
||||||
|
if( field.createTextRange ){ |
||||||
|
var selRange = field.createTextRange(); |
||||||
|
selRange.collapse(true); |
||||||
|
selRange.moveStart("character", start); |
||||||
|
selRange.moveEnd("character", end); |
||||||
|
selRange.select(); |
||||||
|
} else if( field.setSelectionRange ){ |
||||||
|
field.setSelectionRange(start, end); |
||||||
|
} else { |
||||||
|
if( field.selectionStart ){ |
||||||
|
field.selectionStart = start; |
||||||
|
field.selectionEnd = end; |
||||||
|
} |
||||||
|
} |
||||||
|
field.focus(); |
||||||
|
}; |
||||||
|
|
||||||
|
})(jQuery); |
@ -0,0 +1,24 @@ |
|||||||
|
/* |
||||||
|
* jQuery Tooltip plugin 1.3 |
||||||
|
* |
||||||
|
* http://bassistance.de/jquery-plugins/jquery-plugin-tooltip/ |
||||||
|
* http://docs.jquery.com/Plugins/Tooltip |
||||||
|
* |
||||||
|
* Copyright (c) 2006 - 2008 Jörn Zaefferer |
||||||
|
* |
||||||
|
* $Id: jquery.tooltip.css,v 1.1.1.1 2009/03/17 18:35:18 kannan Exp $ |
||||||
|
* |
||||||
|
* Dual licensed under the MIT and GPL licenses: |
||||||
|
* http://www.opensource.org/licenses/mit-license.php |
||||||
|
* http://www.gnu.org/licenses/gpl.html |
||||||
|
*/ |
||||||
|
|
||||||
|
#tooltip { |
||||||
|
position: absolute; |
||||||
|
z-index: 3000; |
||||||
|
border: 1px solid #111; |
||||||
|
background-color: lightyellow; |
||||||
|
padding: 5px; |
||||||
|
opacity: 0.9; |
||||||
|
} |
||||||
|
#tooltip h3, #tooltip div { margin: 0; } |
@ -0,0 +1,294 @@ |
|||||||
|
/* |
||||||
|
* jQuery Tooltip plugin 1.3 |
||||||
|
* |
||||||
|
* http://bassistance.de/jquery-plugins/jquery-plugin-tooltip/
|
||||||
|
* http://docs.jquery.com/Plugins/Tooltip
|
||||||
|
* |
||||||
|
* Copyright (c) 2006 - 2008 Jörn Zaefferer |
||||||
|
* |
||||||
|
* $Id: jquery.tooltip.js,v 1.1.1.1 2009/03/17 18:35:18 kannan Exp $ |
||||||
|
*
|
||||||
|
* Dual licensed under the MIT and GPL licenses: |
||||||
|
* http://www.opensource.org/licenses/mit-license.php
|
||||||
|
* http://www.gnu.org/licenses/gpl.html
|
||||||
|
*/ |
||||||
|
|
||||||
|
;(function($) { |
||||||
|
|
||||||
|
// the tooltip element
|
||||||
|
var helper = {}, |
||||||
|
// the current tooltipped element
|
||||||
|
current, |
||||||
|
// the title of the current element, used for restoring
|
||||||
|
title, |
||||||
|
// timeout id for delayed tooltips
|
||||||
|
tID, |
||||||
|
// IE 5.5 or 6
|
||||||
|
IE = $.browser.msie && /MSIE\s(5\.5|6\.)/.test(navigator.userAgent), |
||||||
|
// flag for mouse tracking
|
||||||
|
track = false; |
||||||
|
|
||||||
|
$.tooltip = { |
||||||
|
blocked: false, |
||||||
|
defaults: { |
||||||
|
delay: 200, |
||||||
|
fade: false, |
||||||
|
showURL: true, |
||||||
|
extraClass: "", |
||||||
|
top: 15, |
||||||
|
left: 15, |
||||||
|
id: "tooltip" |
||||||
|
}, |
||||||
|
block: function() { |
||||||
|
$.tooltip.blocked = !$.tooltip.blocked; |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
$.fn.extend({ |
||||||
|
tooltip: function(settings) { |
||||||
|
settings = $.extend({}, $.tooltip.defaults, settings); |
||||||
|
createHelper(settings); |
||||||
|
return this.each(function() { |
||||||
|
$.data(this, "tooltip", settings); |
||||||
|
this.tOpacity = helper.parent.css("opacity"); |
||||||
|
// copy tooltip into its own expando and remove the title
|
||||||
|
this.tooltipText = this.title; |
||||||
|
$(this).removeAttr("title"); |
||||||
|
// also remove alt attribute to prevent default tooltip in IE
|
||||||
|
this.alt = ""; |
||||||
|
}) |
||||||
|
.mouseover(save) |
||||||
|
.mouseout(hide) |
||||||
|
.click(hide); |
||||||
|
}, |
||||||
|
fixPNG: IE ? function() { |
||||||
|
return this.each(function () { |
||||||
|
var image = $(this).css('backgroundImage'); |
||||||
|
if (image.match(/^url\(["']?(.*\.png)["']?\)$/i)) { |
||||||
|
image = RegExp.$1; |
||||||
|
$(this).css({ |
||||||
|
'backgroundImage': 'none', |
||||||
|
'filter': "progid:DXImageTransform.Microsoft.AlphaImageLoader(enabled=true, sizingMethod=crop, src='" + image + "')" |
||||||
|
}).each(function () { |
||||||
|
var position = $(this).css('position'); |
||||||
|
if (position != 'absolute' && position != 'relative') |
||||||
|
$(this).css('position', 'relative'); |
||||||
|
}); |
||||||
|
} |
||||||
|
}); |
||||||
|
} : function() { return this; }, |
||||||
|
unfixPNG: IE ? function() { |
||||||
|
return this.each(function () { |
||||||
|
$(this).css({'filter': '', backgroundImage: ''}); |
||||||
|
}); |
||||||
|
} : function() { return this; }, |
||||||
|
hideWhenEmpty: function() { |
||||||
|
return this.each(function() { |
||||||
|
$(this)[ $(this).html() ? "show" : "hide" ](); |
||||||
|
}); |
||||||
|
}, |
||||||
|
url: function() { |
||||||
|
return this.attr('href') || this.attr('src'); |
||||||
|
} |
||||||
|
}); |
||||||
|
|
||||||
|
function createHelper(settings) { |
||||||
|
// there can be only one tooltip helper
|
||||||
|
if( helper.parent ) |
||||||
|
return; |
||||||
|
// create the helper, h3 for title, div for url
|
||||||
|
helper.parent = $('<div id="' + settings.id + '"><h3></h3><div class="body"></div><div class="url"></div></div>') |
||||||
|
// add to document
|
||||||
|
.appendTo(document.body) |
||||||
|
// hide it at first
|
||||||
|
.hide(); |
||||||
|
|
||||||
|
// apply bgiframe if available
|
||||||
|
if ( $.fn.bgiframe ) |
||||||
|
helper.parent.bgiframe(); |
||||||
|
|
||||||
|
// save references to title and url elements
|
||||||
|
helper.title = $('h3', helper.parent); |
||||||
|
helper.body = $('div.body', helper.parent); |
||||||
|
helper.url = $('div.url', helper.parent); |
||||||
|
} |
||||||
|
|
||||||
|
function settings(element) { |
||||||
|
return $.data(element, "tooltip"); |
||||||
|
} |
||||||
|
|
||||||
|
// main event handler to start showing tooltips
|
||||||
|
function handle(event) { |
||||||
|
// show helper, either with timeout or on instant
|
||||||
|
if( settings(this).delay ) |
||||||
|
tID = setTimeout(show, settings(this).delay); |
||||||
|
else |
||||||
|
show(); |
||||||
|
|
||||||
|
// if selected, update the helper position when the mouse moves
|
||||||
|
track = !!settings(this).track; |
||||||
|
$(document.body).bind('mousemove', update); |
||||||
|
|
||||||
|
// update at least once
|
||||||
|
update(event); |
||||||
|
} |
||||||
|
|
||||||
|
// save elements title before the tooltip is displayed
|
||||||
|
function save() { |
||||||
|
// if this is the current source, or it has no title (occurs with click event), stop
|
||||||
|
if ( $.tooltip.blocked || this == current || (!this.tooltipText && !settings(this).bodyHandler) ) |
||||||
|
return; |
||||||
|
|
||||||
|
// save current
|
||||||
|
current = this; |
||||||
|
title = this.tooltipText; |
||||||
|
|
||||||
|
if ( settings(this).bodyHandler ) { |
||||||
|
helper.title.hide(); |
||||||
|
var bodyContent = settings(this).bodyHandler.call(this); |
||||||
|
if (bodyContent.nodeType || bodyContent.jquery) { |
||||||
|
helper.body.empty().append(bodyContent) |
||||||
|
} else { |
||||||
|
helper.body.html( bodyContent ); |
||||||
|
} |
||||||
|
helper.body.show(); |
||||||
|
} else if ( settings(this).showBody ) { |
||||||
|
var parts = title.split(settings(this).showBody); |
||||||
|
helper.title.html(parts.shift()).show(); |
||||||
|
helper.body.empty(); |
||||||
|
for(var i = 0, part; (part = parts[i]); i++) { |
||||||
|
if(i > 0) |
||||||
|
helper.body.append("<br/>"); |
||||||
|
helper.body.append(part); |
||||||
|
} |
||||||
|
helper.body.hideWhenEmpty(); |
||||||
|
} else { |
||||||
|
helper.title.html(title).show(); |
||||||
|
helper.body.hide(); |
||||||
|
} |
||||||
|
|
||||||
|
// if element has href or src, add and show it, otherwise hide it
|
||||||
|
if( settings(this).showURL && $(this).url() ) |
||||||
|
helper.url.html( $(this).url().replace('http://', '') ).show(); |
||||||
|
else
|
||||||
|
helper.url.hide(); |
||||||
|
|
||||||
|
// add an optional class for this tip
|
||||||
|
helper.parent.addClass(settings(this).extraClass); |
||||||
|
|
||||||
|
// fix PNG background for IE
|
||||||
|
if (settings(this).fixPNG ) |
||||||
|
helper.parent.fixPNG(); |
||||||
|
|
||||||
|
handle.apply(this, arguments); |
||||||
|
} |
||||||
|
|
||||||
|
// delete timeout and show helper
|
||||||
|
function show() { |
||||||
|
tID = null; |
||||||
|
if ((!IE || !$.fn.bgiframe) && settings(current).fade) { |
||||||
|
if (helper.parent.is(":animated")) |
||||||
|
helper.parent.stop().show().fadeTo(settings(current).fade, current.tOpacity); |
||||||
|
else |
||||||
|
helper.parent.is(':visible') ? helper.parent.fadeTo(settings(current).fade, current.tOpacity) : helper.parent.fadeIn(settings(current).fade); |
||||||
|
} else { |
||||||
|
helper.parent.show(); |
||||||
|
} |
||||||
|
update(); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* callback for mousemove |
||||||
|
* updates the helper position |
||||||
|
* removes itself when no current element |
||||||
|
*/ |
||||||
|
function update(event) { |
||||||
|
if($.tooltip.blocked) |
||||||
|
return; |
||||||
|
|
||||||
|
if (event && event.target.tagName == "OPTION") { |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
// stop updating when tracking is disabled and the tooltip is visible
|
||||||
|
if ( !track && helper.parent.is(":visible")) { |
||||||
|
$(document.body).unbind('mousemove', update) |
||||||
|
} |
||||||
|
|
||||||
|
// if no current element is available, remove this listener
|
||||||
|
if( current == null ) { |
||||||
|
$(document.body).unbind('mousemove', update); |
||||||
|
return;
|
||||||
|
} |
||||||
|
|
||||||
|
// remove position helper classes
|
||||||
|
helper.parent.removeClass("viewport-right").removeClass("viewport-bottom"); |
||||||
|
|
||||||
|
var left = helper.parent[0].offsetLeft; |
||||||
|
var top = helper.parent[0].offsetTop; |
||||||
|
if (event) { |
||||||
|
// position the helper 15 pixel to bottom right, starting from mouse position
|
||||||
|
left = event.pageX + settings(current).left; |
||||||
|
top = event.pageY + settings(current).top; |
||||||
|
var right='auto'; |
||||||
|
if (settings(current).positionLeft) { |
||||||
|
right = $(window).width() - left; |
||||||
|
left = 'auto'; |
||||||
|
} |
||||||
|
helper.parent.css({ |
||||||
|
left: left, |
||||||
|
right: right, |
||||||
|
top: top |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
var v = viewport(), |
||||||
|
h = helper.parent[0]; |
||||||
|
// check horizontal position
|
||||||
|
if (v.x + v.cx < h.offsetLeft + h.offsetWidth) { |
||||||
|
left -= h.offsetWidth + 20 + settings(current).left; |
||||||
|
helper.parent.css({left: left + 'px'}).addClass("viewport-right"); |
||||||
|
} |
||||||
|
// check vertical position
|
||||||
|
if (v.y + v.cy < h.offsetTop + h.offsetHeight) { |
||||||
|
top -= h.offsetHeight + 20 + settings(current).top; |
||||||
|
helper.parent.css({top: top + 'px'}).addClass("viewport-bottom"); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
function viewport() { |
||||||
|
return { |
||||||
|
x: $(window).scrollLeft(), |
||||||
|
y: $(window).scrollTop(), |
||||||
|
cx: $(window).width(), |
||||||
|
cy: $(window).height() |
||||||
|
}; |
||||||
|
} |
||||||
|
|
||||||
|
// hide helper and restore added classes and the title
|
||||||
|
function hide(event) { |
||||||
|
if($.tooltip.blocked) |
||||||
|
return; |
||||||
|
// clear timeout if possible
|
||||||
|
if(tID) |
||||||
|
clearTimeout(tID); |
||||||
|
// no more current element
|
||||||
|
current = null; |
||||||
|
|
||||||
|
var tsettings = settings(this); |
||||||
|
function complete() { |
||||||
|
helper.parent.removeClass( tsettings.extraClass ).hide().css("opacity", ""); |
||||||
|
} |
||||||
|
if ((!IE || !$.fn.bgiframe) && tsettings.fade) { |
||||||
|
if (helper.parent.is(':animated')) |
||||||
|
helper.parent.stop().fadeTo(tsettings.fade, 0, complete); |
||||||
|
else |
||||||
|
helper.parent.stop().fadeOut(tsettings.fade, complete); |
||||||
|
} else |
||||||
|
complete(); |
||||||
|
|
||||||
|
if( settings(this).fixPNG ) |
||||||
|
helper.parent.unfixPNG(); |
||||||
|
} |
||||||
|
|
||||||
|
})(jQuery); |
@ -0,0 +1,204 @@ |
|||||||
|
/* Copyright (c) 2009 Facebook |
||||||
|
* |
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||||
|
* you may not use this file except in compliance with the License. |
||||||
|
* You may obtain a copy of the License at |
||||||
|
* |
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
* |
||||||
|
* Unless required by applicable law or agreed to in writing, software |
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
* See the License for the specific language governing permissions and |
||||||
|
* limitations under the License. |
||||||
|
*/ |
||||||
|
|
||||||
|
/** |
||||||
|
* Helper javascript functions for XHProf report tooltips. |
||||||
|
* |
||||||
|
* @author Kannan Muthukkaruppan |
||||||
|
*/ |
||||||
|
|
||||||
|
// Take a string which is actually a number in comma separated format
|
||||||
|
// and return a string representing the absolute value of the number.
|
||||||
|
function stringAbs(x) { |
||||||
|
return x.replace("-", ""); |
||||||
|
} |
||||||
|
|
||||||
|
// Takes a number in comma-separated string format, and
|
||||||
|
// returns a boolean to indicate if the number is negative
|
||||||
|
// or not.
|
||||||
|
function isNegative(x) { |
||||||
|
|
||||||
|
return (x.indexOf("-") == 0); |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
function addCommas(nStr) |
||||||
|
{ |
||||||
|
nStr += ''; |
||||||
|
x = nStr.split('.'); |
||||||
|
x1 = x[0]; |
||||||
|
x2 = x.length > 1 ? '.' + x[1] : ''; |
||||||
|
var rgx = /(\d+)(\d{3})/; |
||||||
|
while (rgx.test(x1)) { |
||||||
|
x1 = x1.replace(rgx, '$1' + ',' + '$2'); |
||||||
|
} |
||||||
|
return x1 + x2; |
||||||
|
} |
||||||
|
|
||||||
|
// Mouseover tips for parent rows in parent/child report..
|
||||||
|
function ParentRowToolTip(cell, metric) |
||||||
|
{ |
||||||
|
var metric_val; |
||||||
|
var parent_metric_val; |
||||||
|
var parent_metric_pct_val; |
||||||
|
var col_index; |
||||||
|
var diff_text; |
||||||
|
|
||||||
|
row = cell.parentNode; |
||||||
|
tds = row.getElementsByTagName("td"); |
||||||
|
|
||||||
|
parent_func = tds[0].innerHTML; // name
|
||||||
|
|
||||||
|
if (diff_mode) { |
||||||
|
diff_text = " diff "; |
||||||
|
} else { |
||||||
|
diff_text = ""; |
||||||
|
} |
||||||
|
|
||||||
|
s = '<center>'; |
||||||
|
|
||||||
|
if (metric == "ct") { |
||||||
|
parent_ct = tds[1].innerHTML; // calls
|
||||||
|
parent_ct_pct = tds[2].innerHTML; |
||||||
|
|
||||||
|
func_ct = addCommas(func_ct); |
||||||
|
|
||||||
|
if (diff_mode) { |
||||||
|
s += 'There are ' + stringAbs(parent_ct) + |
||||||
|
(isNegative(parent_ct) ? ' fewer ' : ' more ') + |
||||||
|
' calls to ' + func_name + ' from ' + parent_func + '<br>'; |
||||||
|
|
||||||
|
text = " of diff in calls "; |
||||||
|
} else { |
||||||
|
text = " of calls "; |
||||||
|
} |
||||||
|
|
||||||
|
s += parent_ct_pct + text + '(' + parent_ct + '/' + func_ct + ') to ' |
||||||
|
+ func_name + ' are from ' + parent_func + '<br>'; |
||||||
|
} else { |
||||||
|
|
||||||
|
// help for other metrics such as wall time, user cpu time, memory usage
|
||||||
|
col_index = metrics_col[metric]; |
||||||
|
parent_metric_val = tds[col_index].innerHTML; |
||||||
|
parent_metric_pct_val = tds[col_index+1].innerHTML; |
||||||
|
|
||||||
|
metric_val = addCommas(func_metrics[metric]); |
||||||
|
|
||||||
|
s += parent_metric_pct_val + '(' + parent_metric_val + '/' + metric_val |
||||||
|
+ ') of ' + metrics_desc[metric] + |
||||||
|
(diff_mode ? ((isNegative(parent_metric_val) ? |
||||||
|
" decrease" : " increase")) : "") + |
||||||
|
' in ' + func_name + ' is due to calls from ' + parent_func + '<br>'; |
||||||
|
} |
||||||
|
|
||||||
|
s += '</center>'; |
||||||
|
|
||||||
|
return s; |
||||||
|
} |
||||||
|
|
||||||
|
// Mouseover tips for child rows in parent/child report..
|
||||||
|
function ChildRowToolTip(cell, metric) |
||||||
|
{ |
||||||
|
var metric_val; |
||||||
|
var child_metric_val; |
||||||
|
var child_metric_pct_val; |
||||||
|
var col_index; |
||||||
|
var diff_text; |
||||||
|
|
||||||
|
row = cell.parentNode; |
||||||
|
tds = row.getElementsByTagName("td"); |
||||||
|
|
||||||
|
child_func = tds[0].innerHTML; // name
|
||||||
|
|
||||||
|
if (diff_mode) { |
||||||
|
diff_text = " diff "; |
||||||
|
} else { |
||||||
|
diff_text = ""; |
||||||
|
} |
||||||
|
|
||||||
|
s = '<center>'; |
||||||
|
|
||||||
|
if (metric == "ct") { |
||||||
|
|
||||||
|
child_ct = tds[1].innerHTML; // calls
|
||||||
|
child_ct_pct = tds[2].innerHTML; |
||||||
|
|
||||||
|
s += func_name + ' called ' + child_func + ' ' + stringAbs(child_ct) + |
||||||
|
(diff_mode ? (isNegative(child_ct) ? " fewer" : " more") : "" ) |
||||||
|
+ ' times.<br>'; |
||||||
|
s += 'This accounts for ' + child_ct_pct + ' (' + child_ct |
||||||
|
+ '/' + total_child_ct |
||||||
|
+ ') of function calls made by ' + func_name + '.'; |
||||||
|
|
||||||
|
} else { |
||||||
|
|
||||||
|
// help for other metrics such as wall time, user cpu time, memory usage
|
||||||
|
col_index = metrics_col[metric]; |
||||||
|
child_metric_val = tds[col_index].innerHTML; |
||||||
|
child_metric_pct_val = tds[col_index+1].innerHTML; |
||||||
|
|
||||||
|
metric_val = addCommas(func_metrics[metric]); |
||||||
|
|
||||||
|
if (child_func.indexOf("Exclusive Metrics") != -1) { |
||||||
|
s += 'The exclusive ' + metrics_desc[metric] + diff_text |
||||||
|
+ ' for ' + func_name |
||||||
|
+ ' is ' + child_metric_val + " <br>"; |
||||||
|
|
||||||
|
s += "which is " + child_metric_pct_val + " of the inclusive " |
||||||
|
+ metrics_desc[metric] |
||||||
|
+ diff_text + " for " + func_name + " (" + metric_val + ")."; |
||||||
|
|
||||||
|
} else { |
||||||
|
|
||||||
|
s += child_func + ' when called from ' + func_name |
||||||
|
+ ' takes ' + stringAbs(child_metric_val) |
||||||
|
+ (diff_mode ? (isNegative(child_metric_val) ? " less" : " more") : "") |
||||||
|
+ " of " + metrics_desc[metric] + " <br>"; |
||||||
|
|
||||||
|
s += "which is " + child_metric_pct_val + " of the inclusive " |
||||||
|
+ metrics_desc[metric] |
||||||
|
+ diff_text + " for " + func_name + " (" + metric_val + ")."; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
s += '</center>'; |
||||||
|
|
||||||
|
return s; |
||||||
|
} |
||||||
|
|
||||||
|
$(document).ready(function() { |
||||||
|
$('td[@metric]').tooltip( |
||||||
|
{ bodyHandler: function() { |
||||||
|
var type = $(this).attr('type'); |
||||||
|
var metric = $(this).attr('metric'); |
||||||
|
if (type == 'Parent') { |
||||||
|
return ParentRowToolTip(this, metric); |
||||||
|
} else if (type == 'Child') { |
||||||
|
return ChildRowToolTip(this, metric); |
||||||
|
} |
||||||
|
}, |
||||||
|
showURL : false |
||||||
|
}); |
||||||
|
var cur_params = {} ; |
||||||
|
$.each(location.search.replace('?','').split('&'), function(i, x) { |
||||||
|
var y = x.split('='); cur_params[y[0]] = y[1]; |
||||||
|
}); |
||||||
|
$('input.function_typeahead') |
||||||
|
.autocomplete('typeahead.php', { extraParams : cur_params }) |
||||||
|
.result(function(event, item) { |
||||||
|
cur_params['symbol'] = item; |
||||||
|
location.search = '?' + jQuery.param(cur_params); |
||||||
|
}); |
||||||
|
}); |
@ -0,0 +1,32 @@ |
|||||||
|
<?php |
||||||
|
// Copyright (c) 2009 Facebook |
||||||
|
// |
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License"); |
||||||
|
// you may not use this file except in compliance with the License. |
||||||
|
// You may obtain a copy of the License at |
||||||
|
// |
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0 |
||||||
|
// |
||||||
|
// Unless required by applicable law or agreed to in writing, software |
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
// See the License for the specific language governing permissions and |
||||||
|
// limitations under the License. |
||||||
|
// |
||||||
|
|
||||||
|
/** |
||||||
|
* AJAX endpoint for XHProf function name typeahead. |
||||||
|
* |
||||||
|
* @author(s) Kannan Muthukkaruppan |
||||||
|
* Changhao Jiang |
||||||
|
*/ |
||||||
|
|
||||||
|
// by default assume that xhprof_html & xhprof_lib directories |
||||||
|
// are at the same level. |
||||||
|
$GLOBALS['XHPROF_LIB_ROOT'] = dirname(__FILE__) . '/../xhprof_lib'; |
||||||
|
|
||||||
|
include_once $GLOBALS['XHPROF_LIB_ROOT'].'/display/xhprof.php'; |
||||||
|
|
||||||
|
$xhprof_runs_impl = new XHProfRuns_Default(); |
||||||
|
|
||||||
|
include_once $GLOBALS['XHPROF_LIB_ROOT'].'/display/typeahead_common.php'; |
@ -0,0 +1,80 @@ |
|||||||
|
<?php |
||||||
|
// Copyright (c) 2009 Facebook |
||||||
|
// |
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License"); |
||||||
|
// you may not use this file except in compliance with the License. |
||||||
|
// You may obtain a copy of the License at |
||||||
|
// |
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0 |
||||||
|
// |
||||||
|
// Unless required by applicable law or agreed to in writing, software |
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
// See the License for the specific language governing permissions and |
||||||
|
// limitations under the License. |
||||||
|
// |
||||||
|
|
||||||
|
/** |
||||||
|
* AJAX endpoint for XHProf function name typeahead is implemented |
||||||
|
* as a thin wrapper around this file. The wrapper must set up |
||||||
|
* the global $xhprof_runs_impl to correspond to an object that |
||||||
|
* implements the iXHProfRuns interface. |
||||||
|
* |
||||||
|
* @author(s) Kannan Muthukkaruppan |
||||||
|
* Changhao Jiang |
||||||
|
*/ |
||||||
|
|
||||||
|
|
||||||
|
include_once $GLOBALS['XHPROF_LIB_ROOT'].'/utils/xhprof_lib.php'; |
||||||
|
|
||||||
|
// param name, its type, and default value |
||||||
|
$params = array('q' => array(XHPROF_STRING_PARAM, ''), |
||||||
|
'run' => array(XHPROF_STRING_PARAM, ''), |
||||||
|
'run1' => array(XHPROF_STRING_PARAM, ''), |
||||||
|
'run2' => array(XHPROF_STRING_PARAM, ''), |
||||||
|
'source' => array(XHPROF_STRING_PARAM, 'xhprof'), |
||||||
|
); |
||||||
|
|
||||||
|
// pull values of these params, and create named globals for each param |
||||||
|
xhprof_param_init($params); |
||||||
|
|
||||||
|
if (!empty($run)) { |
||||||
|
|
||||||
|
// single run mode |
||||||
|
$raw_data = $xhprof_runs_impl->get_run($run, $source, $desc_unused); |
||||||
|
$functions = xhprof_get_matching_functions($q, $raw_data); |
||||||
|
|
||||||
|
} else if (!empty($run1) && !empty($run2)) { |
||||||
|
|
||||||
|
// diff mode |
||||||
|
$raw_data = $xhprof_runs_impl->get_run($run1, $source, $desc_unused); |
||||||
|
$functions1 = xhprof_get_matching_functions($q, $raw_data); |
||||||
|
|
||||||
|
$raw_data = $xhprof_runs_impl->get_run($run2, $source, $desc_unused); |
||||||
|
$functions2 = xhprof_get_matching_functions($q, $raw_data); |
||||||
|
|
||||||
|
|
||||||
|
$functions = array_unique(array_merge($functions1, $functions2)); |
||||||
|
asort($functions); |
||||||
|
} else { |
||||||
|
xhprof_error("no valid runs specified to typeahead endpoint"); |
||||||
|
$functions = array(); |
||||||
|
} |
||||||
|
|
||||||
|
// If exact match is present move it to the front |
||||||
|
if (in_array($q, $functions)) { |
||||||
|
$old_functions = $functions; |
||||||
|
|
||||||
|
$functions = array($q); |
||||||
|
foreach ($old_functions as $f) { |
||||||
|
// exact match case has already been added to the front |
||||||
|
if ($f != $q) { |
||||||
|
$functions[] =$f; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
foreach ($functions as $f) { |
||||||
|
echo $f."\n"; |
||||||
|
} |
||||||
|
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,476 @@ |
|||||||
|
<?php |
||||||
|
// Copyright (c) 2009 Facebook |
||||||
|
// |
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License"); |
||||||
|
// you may not use this file except in compliance with the License. |
||||||
|
// You may obtain a copy of the License at |
||||||
|
// |
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0 |
||||||
|
// |
||||||
|
// Unless required by applicable law or agreed to in writing, software |
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
// See the License for the specific language governing permissions and |
||||||
|
// limitations under the License. |
||||||
|
// |
||||||
|
|
||||||
|
/* |
||||||
|
* This file contains callgraph image generation related XHProf utility |
||||||
|
* functions |
||||||
|
* |
||||||
|
*/ |
||||||
|
|
||||||
|
// Supported ouput format |
||||||
|
$xhprof_legal_image_types = array( |
||||||
|
"jpg" => 1, |
||||||
|
"gif" => 1, |
||||||
|
"png" => 1, |
||||||
|
"ps" => 1, |
||||||
|
); |
||||||
|
|
||||||
|
/** |
||||||
|
* Send an HTTP header with the response. You MUST use this function instead |
||||||
|
* of header() so that we can debug header issues because they're virtually |
||||||
|
* impossible to debug otherwise. If you try to commit header(), SVN will |
||||||
|
* reject your commit. |
||||||
|
* |
||||||
|
* @param string HTTP header name, like 'Location' |
||||||
|
* @param string HTTP header value, like 'http://www.example.com/' |
||||||
|
* |
||||||
|
*/ |
||||||
|
function xhprof_http_header($name, $value) { |
||||||
|
|
||||||
|
if (!$name) { |
||||||
|
xhprof_error('http_header usage'); |
||||||
|
return null; |
||||||
|
} |
||||||
|
|
||||||
|
if (!is_string($value)) { |
||||||
|
xhprof_error('http_header value not a string'); |
||||||
|
} |
||||||
|
|
||||||
|
header($name.': '.$value, true); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Genearte and send MIME header for the output image to client browser. |
||||||
|
* |
||||||
|
* @author cjiang |
||||||
|
*/ |
||||||
|
function xhprof_generate_mime_header($type, $length) { |
||||||
|
switch ($type) { |
||||||
|
case 'jpg': |
||||||
|
$mime = 'image/jpeg'; |
||||||
|
break; |
||||||
|
case 'gif': |
||||||
|
$mime = 'image/gif'; |
||||||
|
break; |
||||||
|
case 'png': |
||||||
|
$mime = 'image/png'; |
||||||
|
break; |
||||||
|
case 'ps': |
||||||
|
$mime = 'application/postscript'; |
||||||
|
default: |
||||||
|
$mime = false; |
||||||
|
} |
||||||
|
|
||||||
|
if ($mime) { |
||||||
|
xhprof_http_header('Content-type', $mime); |
||||||
|
xhprof_http_header('Content-length', (string)$length); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Generate image according to DOT script. This function will spawn a process |
||||||
|
* with "dot" command and pipe the "dot_script" to it and pipe out the |
||||||
|
* generated image content. |
||||||
|
* |
||||||
|
* @param dot_script, string, the script for DOT to generate the image. |
||||||
|
* @param type, one of the supported image types, see |
||||||
|
* $xhprof_legal_image_types. |
||||||
|
* @returns, binary content of the generated image on success. empty string on |
||||||
|
* failure. |
||||||
|
* |
||||||
|
* @author cjiang |
||||||
|
*/ |
||||||
|
function xhprof_generate_image_by_dot($dot_script, $type) { |
||||||
|
$descriptorspec = array( |
||||||
|
// stdin is a pipe that the child will read from |
||||||
|
0 => array("pipe", "r"), |
||||||
|
// stdout is a pipe that the child will write to |
||||||
|
1 => array("pipe", "w"), |
||||||
|
// stderr is a file to write to |
||||||
|
2 => array("file", "/dev/null", "a") |
||||||
|
); |
||||||
|
|
||||||
|
$cmd = " dot -T".$type; |
||||||
|
|
||||||
|
$process = proc_open($cmd, $descriptorspec, $pipes, "/tmp", array()); |
||||||
|
|
||||||
|
if (is_resource($process)) { |
||||||
|
fwrite($pipes[0], $dot_script); |
||||||
|
fclose($pipes[0]); |
||||||
|
|
||||||
|
$output = stream_get_contents($pipes[1]); |
||||||
|
fclose($pipes[1]); |
||||||
|
|
||||||
|
proc_close($process); |
||||||
|
return $output; |
||||||
|
} |
||||||
|
print "failed to shell execute cmd=\"$cmd\"\n"; |
||||||
|
exit(); |
||||||
|
} |
||||||
|
|
||||||
|
/* |
||||||
|
* Get the children list of all nodes. |
||||||
|
*/ |
||||||
|
function xhprof_get_children_table($raw_data) { |
||||||
|
$children_table = array(); |
||||||
|
foreach ($raw_data as $parent_child => $info) { |
||||||
|
list($parent, $child) = xhprof_parse_parent_child($parent_child); |
||||||
|
if (!isset($children_table[$parent])) { |
||||||
|
$children_table[$parent] = array($child); |
||||||
|
} else { |
||||||
|
$children_table[$parent][] = $child; |
||||||
|
} |
||||||
|
} |
||||||
|
return $children_table; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Generate DOT script from the given raw phprof data. |
||||||
|
* |
||||||
|
* @param raw_data, phprof profile data. |
||||||
|
* @param threshold, float, the threshold value [0,1). The functions in the |
||||||
|
* raw_data whose exclusive wall times ratio are below the |
||||||
|
* threshold will be filtered out and won't apprear in the |
||||||
|
* generated image. |
||||||
|
* @param page, string(optional), the root node name. This can be used to |
||||||
|
* replace the 'main()' as the root node. |
||||||
|
* @param func, string, the focus function. |
||||||
|
* @param critical_path, bool, whether or not to display critical path with |
||||||
|
* bold lines. |
||||||
|
* @returns, string, the DOT script to generate image. |
||||||
|
* |
||||||
|
* @author cjiang |
||||||
|
*/ |
||||||
|
function xhprof_generate_dot_script($raw_data, $threshold, $source, $page, |
||||||
|
$func, $critical_path, $right=null, |
||||||
|
$left=null) { |
||||||
|
|
||||||
|
$max_width = 5; |
||||||
|
$max_height = 3.5; |
||||||
|
$max_fontsize = 35; |
||||||
|
$max_sizing_ratio = 20; |
||||||
|
|
||||||
|
$totals; |
||||||
|
|
||||||
|
if ($left === null) { |
||||||
|
// init_metrics($raw_data, null, null); |
||||||
|
} |
||||||
|
$sym_table = xhprof_compute_flat_info($raw_data, $totals); |
||||||
|
|
||||||
|
if ($critical_path) { |
||||||
|
$children_table = xhprof_get_children_table($raw_data); |
||||||
|
$node = "main()"; |
||||||
|
$path = array(); |
||||||
|
$path_edges = array(); |
||||||
|
$visited = array(); |
||||||
|
while ($node) { |
||||||
|
$visited[$node] = true; |
||||||
|
if (isset($children_table[$node])) { |
||||||
|
$max_child = null; |
||||||
|
foreach ($children_table[$node] as $child) { |
||||||
|
|
||||||
|
if (isset($visited[$child])) { |
||||||
|
continue; |
||||||
|
} |
||||||
|
if ($max_child === null || |
||||||
|
abs($raw_data[xhprof_build_parent_child_key($node, |
||||||
|
$child)]["wt"]) > |
||||||
|
abs($raw_data[xhprof_build_parent_child_key($node, |
||||||
|
$max_child)]["wt"])) { |
||||||
|
$max_child = $child; |
||||||
|
} |
||||||
|
} |
||||||
|
if ($max_child !== null) { |
||||||
|
$path[$max_child] = true; |
||||||
|
$path_edges[xhprof_build_parent_child_key($node, $max_child)] = true; |
||||||
|
} |
||||||
|
$node = $max_child; |
||||||
|
} else { |
||||||
|
$node = null; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// if it is a benchmark callgraph, we make the benchmarked function the root. |
||||||
|
if ($source == "bm" && array_key_exists("main()", $sym_table)) { |
||||||
|
$total_times = $sym_table["main()"]["ct"]; |
||||||
|
$remove_funcs = array("main()", |
||||||
|
"hotprofiler_disable", |
||||||
|
"call_user_func_array", |
||||||
|
"xhprof_disable"); |
||||||
|
|
||||||
|
foreach ($remove_funcs as $cur_del_func) { |
||||||
|
if (array_key_exists($cur_del_func, $sym_table) && |
||||||
|
$sym_table[$cur_del_func]["ct"] == $total_times) { |
||||||
|
unset($sym_table[$cur_del_func]); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// use the function to filter out irrelevant functions. |
||||||
|
if (!empty($func)) { |
||||||
|
$interested_funcs = array(); |
||||||
|
foreach ($raw_data as $parent_child => $info) { |
||||||
|
list($parent, $child) = xhprof_parse_parent_child($parent_child); |
||||||
|
if ($parent == $func || $child == $func) { |
||||||
|
$interested_funcs[$parent] = 1; |
||||||
|
$interested_funcs[$child] = 1; |
||||||
|
} |
||||||
|
} |
||||||
|
foreach ($sym_table as $symbol => $info) { |
||||||
|
if (!array_key_exists($symbol, $interested_funcs)) { |
||||||
|
unset($sym_table[$symbol]); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
$result = "digraph call_graph {\n"; |
||||||
|
|
||||||
|
// Filter out functions whose exclusive time ratio is below threshold, and |
||||||
|
// also assign a unique integer id for each function to be generated. In the |
||||||
|
// meantime, find the function with the most exclusive time (potentially the |
||||||
|
// performance bottleneck). |
||||||
|
$cur_id = 0; $max_wt = 0; |
||||||
|
foreach ($sym_table as $symbol => $info) { |
||||||
|
if (empty($func) && abs($info["wt"] / $totals["wt"]) < $threshold) { |
||||||
|
unset($sym_table[$symbol]); |
||||||
|
continue; |
||||||
|
} |
||||||
|
if ($max_wt == 0 || $max_wt < abs($info["excl_wt"])) { |
||||||
|
$max_wt = abs($info["excl_wt"]); |
||||||
|
} |
||||||
|
$sym_table[$symbol]["id"] = $cur_id; |
||||||
|
$cur_id ++; |
||||||
|
} |
||||||
|
|
||||||
|
// Generate all nodes' information. |
||||||
|
foreach ($sym_table as $symbol => $info) { |
||||||
|
if ($info["excl_wt"] == 0) { |
||||||
|
$sizing_factor = $max_sizing_ratio; |
||||||
|
} else { |
||||||
|
$sizing_factor = $max_wt / abs($info["excl_wt"]) ; |
||||||
|
if ($sizing_factor > $max_sizing_ratio) { |
||||||
|
$sizing_factor = $max_sizing_ratio; |
||||||
|
} |
||||||
|
} |
||||||
|
$fillcolor = (($sizing_factor < 1.5) ? |
||||||
|
", style=filled, fillcolor=red" : ""); |
||||||
|
|
||||||
|
if ($critical_path) { |
||||||
|
// highlight nodes along critical path. |
||||||
|
if (!$fillcolor && array_key_exists($symbol, $path)) { |
||||||
|
$fillcolor = ", style=filled, fillcolor=yellow"; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
$fontsize =", fontsize=" |
||||||
|
.(int)($max_fontsize / (($sizing_factor - 1) / 10 + 1)); |
||||||
|
|
||||||
|
$width = ", width=".sprintf("%.1f", $max_width / $sizing_factor); |
||||||
|
$height = ", height=".sprintf("%.1f", $max_height / $sizing_factor); |
||||||
|
|
||||||
|
if ($symbol == "main()") { |
||||||
|
$shape = "octagon"; |
||||||
|
$name ="Total: ".($totals["wt"]/1000.0)." ms\\n"; |
||||||
|
$name .= addslashes(isset($page) ? $page : $symbol); |
||||||
|
} else { |
||||||
|
$shape = "box"; |
||||||
|
$name = addslashes($symbol)."\\nInc: ". sprintf("%.3f",$info["wt"]/1000) . |
||||||
|
" ms (" . sprintf("%.1f%%", 100 * $info["wt"]/$totals["wt"]).")"; |
||||||
|
} |
||||||
|
if ($left === null) { |
||||||
|
$label = ", label=\"".$name."\\nExcl: " |
||||||
|
.(sprintf("%.3f",$info["excl_wt"]/1000.0))." ms (" |
||||||
|
.sprintf("%.1f%%", 100 * $info["excl_wt"]/$totals["wt"]) |
||||||
|
. ")\\n".$info["ct"]." total calls\""; |
||||||
|
} else { |
||||||
|
if (isset($left[$symbol]) && isset($right[$symbol])) { |
||||||
|
$label = ", label=\"".addslashes($symbol). |
||||||
|
"\\nInc: ".(sprintf("%.3f",$left[$symbol]["wt"]/1000.0)) |
||||||
|
." ms - " |
||||||
|
.(sprintf("%.3f",$right[$symbol]["wt"]/1000.0))." ms = " |
||||||
|
.(sprintf("%.3f",$info["wt"]/1000.0))." ms". |
||||||
|
"\\nExcl: " |
||||||
|
.(sprintf("%.3f",$left[$symbol]["excl_wt"]/1000.0)) |
||||||
|
." ms - ".(sprintf("%.3f",$right[$symbol]["excl_wt"]/1000.0)) |
||||||
|
." ms = ".(sprintf("%.3f",$info["excl_wt"]/1000.0))." ms". |
||||||
|
"\\nCalls: ".(sprintf("%.3f",$left[$symbol]["ct"]))." - " |
||||||
|
.(sprintf("%.3f",$right[$symbol]["ct"]))." = " |
||||||
|
.(sprintf("%.3f",$info["ct"]))."\""; |
||||||
|
} else if (isset($left[$symbol])) { |
||||||
|
$label = ", label=\"".addslashes($symbol). |
||||||
|
"\\nInc: ".(sprintf("%.3f",$left[$symbol]["wt"]/1000.0)) |
||||||
|
." ms - 0 ms = ".(sprintf("%.3f",$info["wt"]/1000.0)) |
||||||
|
." ms"."\\nExcl: " |
||||||
|
.(sprintf("%.3f",$left[$symbol]["excl_wt"]/1000.0)) |
||||||
|
." ms - 0 ms = " |
||||||
|
.(sprintf("%.3f",$info["excl_wt"]/1000.0))." ms". |
||||||
|
"\\nCalls: ".(sprintf("%.3f",$left[$symbol]["ct"]))." - 0 = " |
||||||
|
.(sprintf("%.3f",$info["ct"]))."\""; |
||||||
|
} else { |
||||||
|
$label = ", label=\"".addslashes($symbol). |
||||||
|
"\\nInc: 0 ms - " |
||||||
|
.(sprintf("%.3f",$right[$symbol]["wt"]/1000.0)) |
||||||
|
." ms = ".(sprintf("%.3f",$info["wt"]/1000.0))." ms". |
||||||
|
"\\nExcl: 0 ms - " |
||||||
|
.(sprintf("%.3f",$right[$symbol]["excl_wt"]/1000.0)) |
||||||
|
." ms = ".(sprintf("%.3f",$info["excl_wt"]/1000.0))." ms". |
||||||
|
"\\nCalls: 0 - ".(sprintf("%.3f",$right[$symbol]["ct"])) |
||||||
|
." = ".(sprintf("%.3f",$info["ct"]))."\""; |
||||||
|
} |
||||||
|
} |
||||||
|
$result .= "N" . $sym_table[$symbol]["id"]; |
||||||
|
$result .= "[shape=$shape ".$label.$width |
||||||
|
.$height.$fontsize.$fillcolor."];\n"; |
||||||
|
} |
||||||
|
|
||||||
|
// Generate all the edges' information. |
||||||
|
foreach ($raw_data as $parent_child => $info) { |
||||||
|
list($parent, $child) = xhprof_parse_parent_child($parent_child); |
||||||
|
|
||||||
|
if (isset($sym_table[$parent]) && isset($sym_table[$child]) && |
||||||
|
(empty($func) || |
||||||
|
(!empty($func) && ($parent == $func || $child == $func)) )) { |
||||||
|
|
||||||
|
$label = $info["ct"] == 1 ? $info["ct"]." call" : $info["ct"]." calls"; |
||||||
|
|
||||||
|
$headlabel = $sym_table[$child]["wt"] > 0 ? |
||||||
|
sprintf("%.1f%%", 100 * $info["wt"] |
||||||
|
/ $sym_table[$child]["wt"]) |
||||||
|
: "0.0%"; |
||||||
|
|
||||||
|
$taillabel = ($sym_table[$parent]["wt"] > 0) ? |
||||||
|
sprintf("%.1f%%", |
||||||
|
100 * $info["wt"] / |
||||||
|
($sym_table[$parent]["wt"] - $sym_table["$parent"]["excl_wt"])) |
||||||
|
: "0.0%"; |
||||||
|
|
||||||
|
$linewidth= 1; |
||||||
|
$arrow_size = 1; |
||||||
|
|
||||||
|
if ($critical_path && |
||||||
|
isset($path_edges[xhprof_build_parent_child_key($parent, $child)])) { |
||||||
|
$linewidth = 10; $arrow_size=2; |
||||||
|
} |
||||||
|
|
||||||
|
$result .= "N" . $sym_table[$parent]["id"] . " -> N" |
||||||
|
. $sym_table[$child]["id"]; |
||||||
|
$result .= "[arrowsize=$arrow_size, style=\"setlinewidth($linewidth)\"," |
||||||
|
." label=\"" |
||||||
|
.$label."\", headlabel=\"".$headlabel |
||||||
|
."\", taillabel=\"".$taillabel."\" ]"; |
||||||
|
$result .= ";\n"; |
||||||
|
|
||||||
|
} |
||||||
|
} |
||||||
|
$result = $result . "\n}"; |
||||||
|
|
||||||
|
return $result; |
||||||
|
} |
||||||
|
|
||||||
|
function xhprof_render_diff_image($xhprof_runs_impl, $run1, $run2, |
||||||
|
$type, $threshold, $source) { |
||||||
|
$total1; |
||||||
|
$total2; |
||||||
|
|
||||||
|
$raw_data1 = $xhprof_runs_impl->get_run($run1, $source, $desc_unused); |
||||||
|
$raw_data2 = $xhprof_runs_impl->get_run($run2, $source, $desc_unused); |
||||||
|
|
||||||
|
// init_metrics($raw_data1, null, null); |
||||||
|
$children_table1 = xhprof_get_children_table($raw_data1); |
||||||
|
$children_table2 = xhprof_get_children_table($raw_data2); |
||||||
|
$symbol_tab1 = xhprof_compute_flat_info($raw_data1, $total1); |
||||||
|
$symbol_tab2 = xhprof_compute_flat_info($raw_data2, $total2); |
||||||
|
$run_delta = xhprof_compute_diff($raw_data1, $raw_data2); |
||||||
|
$script = xhprof_generate_dot_script($run_delta, $threshold, $source, |
||||||
|
null, null, true, |
||||||
|
$symbol_tab1, $symbol_tab2); |
||||||
|
$content = xhprof_generate_image_by_dot($script, $type); |
||||||
|
|
||||||
|
xhprof_generate_mime_header($type, strlen($content)); |
||||||
|
echo $content; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Generate image content from phprof run id. |
||||||
|
* |
||||||
|
* @param object $xhprof_runs_impl An object that implements |
||||||
|
* the iXHProfRuns interface |
||||||
|
* @param run_id, integer, the unique id for the phprof run, this is the |
||||||
|
* primary key for phprof database table. |
||||||
|
* @param type, string, one of the supported image types. See also |
||||||
|
* $xhprof_legal_image_types. |
||||||
|
* @param threshold, float, the threshold value [0,1). The functions in the |
||||||
|
* raw_data whose exclusive wall times ratio are below the |
||||||
|
* threshold will be filtered out and won't apprear in the |
||||||
|
* generated image. |
||||||
|
* @param func, string, the focus function. |
||||||
|
* @returns, string, the DOT script to generate image. |
||||||
|
* |
||||||
|
* @author cjiang |
||||||
|
*/ |
||||||
|
function xhprof_get_content_by_run($xhprof_runs_impl, $run_id, $type, |
||||||
|
$threshold, $func, $source, |
||||||
|
$critical_path) { |
||||||
|
if (!$run_id) |
||||||
|
return ""; |
||||||
|
|
||||||
|
$raw_data = $xhprof_runs_impl->get_run($run_id, $source, $description); |
||||||
|
if (!$raw_data) { |
||||||
|
xhprof_error("Raw data is empty"); |
||||||
|
return ""; |
||||||
|
} |
||||||
|
|
||||||
|
$script = xhprof_generate_dot_script($raw_data, $threshold, $source, |
||||||
|
$description, $func, $critical_path); |
||||||
|
|
||||||
|
$content = xhprof_generate_image_by_dot($script, $type); |
||||||
|
return $content; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Generate image from phprof run id and send it to client. |
||||||
|
* |
||||||
|
* @param object $xhprof_runs_impl An object that implements |
||||||
|
* the iXHProfRuns interface |
||||||
|
* @param run_id, integer, the unique id for the phprof run, this is the |
||||||
|
* primary key for phprof database table. |
||||||
|
* @param type, string, one of the supported image types. See also |
||||||
|
* $xhprof_legal_image_types. |
||||||
|
* @param threshold, float, the threshold value [0,1). The functions in the |
||||||
|
* raw_data whose exclusive wall times ratio are below the |
||||||
|
* threshold will be filtered out and won't apprear in the |
||||||
|
* generated image. |
||||||
|
* @param func, string, the focus function. |
||||||
|
* @param bool, does this run correspond to a PHProfLive run or a dev run? |
||||||
|
* @author cjiang |
||||||
|
*/ |
||||||
|
function xhprof_render_image($xhprof_runs_impl, $run_id, $type, $threshold, |
||||||
|
$func, $source, $critical_path) { |
||||||
|
|
||||||
|
$content = xhprof_get_content_by_run($xhprof_runs_impl, $run_id, $type, |
||||||
|
$threshold, |
||||||
|
$func, $source, $critical_path); |
||||||
|
if (!$content) { |
||||||
|
print "Error: either we can not find profile data for run_id ".$run_id |
||||||
|
." or the threshold ".$threshold." is too small or you do not" |
||||||
|
." have 'dot' image generation utility installed."; |
||||||
|
exit(); |
||||||
|
} |
||||||
|
|
||||||
|
xhprof_generate_mime_header($type, strlen($content)); |
||||||
|
echo $content; |
||||||
|
} |
@ -0,0 +1,866 @@ |
|||||||
|
<?php |
||||||
|
// Copyright (c) 2009 Facebook |
||||||
|
// |
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License"); |
||||||
|
// you may not use this file except in compliance with the License. |
||||||
|
// You may obtain a copy of the License at |
||||||
|
// |
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0 |
||||||
|
// |
||||||
|
// Unless required by applicable law or agreed to in writing, software |
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
// See the License for the specific language governing permissions and |
||||||
|
// limitations under the License. |
||||||
|
// |
||||||
|
|
||||||
|
// |
||||||
|
// This file contains various XHProf library (utility) functions. |
||||||
|
// Do not add any display specific code here. |
||||||
|
// |
||||||
|
|
||||||
|
function xhprof_error($message) { |
||||||
|
error_log($message); |
||||||
|
} |
||||||
|
|
||||||
|
/* |
||||||
|
* The list of possible metrics collected as part of XHProf that |
||||||
|
* require inclusive/exclusive handling while reporting. |
||||||
|
* |
||||||
|
* @author Kannan |
||||||
|
*/ |
||||||
|
function xhprof_get_possible_metrics() { |
||||||
|
static $possible_metrics = |
||||||
|
array("wt" => array("Wall", "microsecs", "walltime" ), |
||||||
|
"ut" => array("User", "microsecs", "user cpu time" ), |
||||||
|
"st" => array("Sys", "microsecs", "system cpu time"), |
||||||
|
"cpu" => array("Cpu", "microsecs", "cpu time"), |
||||||
|
"mu" => array("MUse", "bytes", "memory usage"), |
||||||
|
"pmu" => array("PMUse", "bytes", "peak memory usage"), |
||||||
|
"samples" => array("Samples", "samples", "cpu time")); |
||||||
|
return $possible_metrics; |
||||||
|
} |
||||||
|
|
||||||
|
/* |
||||||
|
* Get the list of metrics present in $xhprof_data as an array. |
||||||
|
* |
||||||
|
* @author Kannan |
||||||
|
*/ |
||||||
|
function xhprof_get_metrics($xhprof_data) { |
||||||
|
|
||||||
|
// get list of valid metrics |
||||||
|
$possible_metrics = xhprof_get_possible_metrics(); |
||||||
|
|
||||||
|
// return those that are present in the raw data. |
||||||
|
// We'll just look at the root of the subtree for this. |
||||||
|
$metrics = array(); |
||||||
|
foreach ($possible_metrics as $metric => $desc) { |
||||||
|
if (isset($xhprof_data["main()"][$metric])) { |
||||||
|
$metrics[] = $metric; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return $metrics; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Takes a parent/child function name encoded as |
||||||
|
* "a==>b" and returns array("a", "b"). |
||||||
|
* |
||||||
|
* @author Kannan |
||||||
|
*/ |
||||||
|
function xhprof_parse_parent_child($parent_child) { |
||||||
|
$ret = explode("==>", $parent_child); |
||||||
|
|
||||||
|
// Return if both parent and child are set |
||||||
|
if (isset($ret[1])) { |
||||||
|
return $ret; |
||||||
|
} |
||||||
|
|
||||||
|
return array(null, $ret[0]); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Given parent & child function name, composes the key |
||||||
|
* in the format present in the raw data. |
||||||
|
* |
||||||
|
* @author Kannan |
||||||
|
*/ |
||||||
|
function xhprof_build_parent_child_key($parent, $child) { |
||||||
|
if ($parent) { |
||||||
|
return $parent . "==>" . $child; |
||||||
|
} else { |
||||||
|
return $child; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
/** |
||||||
|
* Checks if XHProf raw data appears to be valid and not corrupted. |
||||||
|
* |
||||||
|
* @param int $run_id Run id of run to be pruned. |
||||||
|
* [Used only for reporting errors.] |
||||||
|
* @param array $raw_data XHProf raw data to be pruned |
||||||
|
* & validated. |
||||||
|
* |
||||||
|
* @return bool true on success, false on failure |
||||||
|
* |
||||||
|
* @author Kannan |
||||||
|
*/ |
||||||
|
function xhprof_valid_run($run_id, $raw_data) { |
||||||
|
|
||||||
|
$main_info = $raw_data["main()"]; |
||||||
|
if (empty($main_info)) { |
||||||
|
xhprof_error("XHProf: main() missing in raw data for Run ID: $run_id"); |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
// raw data should contain either wall time or samples information... |
||||||
|
if (isset($main_info["wt"])) { |
||||||
|
$metric = "wt"; |
||||||
|
} else if (isset($main_info["samples"])) { |
||||||
|
$metric = "samples"; |
||||||
|
} else { |
||||||
|
xhprof_error("XHProf: Wall Time information missing from Run ID: $run_id"); |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
foreach ($raw_data as $info) { |
||||||
|
$val = $info[$metric]; |
||||||
|
|
||||||
|
// basic sanity checks... |
||||||
|
if ($val < 0) { |
||||||
|
xhprof_error("XHProf: $metric should not be negative: Run ID $run_id" |
||||||
|
. serialize($info)); |
||||||
|
return false; |
||||||
|
} |
||||||
|
if ($val > (86400000000)) { |
||||||
|
xhprof_error("XHProf: $metric > 1 day found in Run ID: $run_id " |
||||||
|
. serialize($info)); |
||||||
|
return false; |
||||||
|
} |
||||||
|
} |
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
/** |
||||||
|
* Return a trimmed version of the XHProf raw data. Note that the raw |
||||||
|
* data contains one entry for each unique parent/child function |
||||||
|
* combination.The trimmed version of raw data will only contain |
||||||
|
* entries where either the parent or child function is in the list |
||||||
|
* of $functions_to_keep. |
||||||
|
* |
||||||
|
* Note: Function main() is also always kept so that overall totals |
||||||
|
* can still be obtained from the trimmed version. |
||||||
|
* |
||||||
|
* @param array XHProf raw data |
||||||
|
* @param array array of function names |
||||||
|
* |
||||||
|
* @return array Trimmed XHProf Report |
||||||
|
* |
||||||
|
* @author Kannan |
||||||
|
*/ |
||||||
|
function xhprof_trim_run($raw_data, $functions_to_keep) { |
||||||
|
|
||||||
|
// convert list of functions to a hash with function as the key |
||||||
|
$function_map = array_fill_keys($functions_to_keep, 1); |
||||||
|
|
||||||
|
// always keep main() as well so that overall totals can still |
||||||
|
// be computed if need be. |
||||||
|
$function_map['main()'] = 1; |
||||||
|
|
||||||
|
$new_raw_data = array(); |
||||||
|
foreach ($raw_data as $parent_child => $info) { |
||||||
|
list($parent, $child) = xhprof_parse_parent_child($parent_child); |
||||||
|
|
||||||
|
if (isset($function_map[$parent]) || isset($function_map[$child])) { |
||||||
|
$new_raw_data[$parent_child] = $info; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return $new_raw_data; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Takes raw XHProf data that was aggregated over "$num_runs" number |
||||||
|
* of runs averages/nomalizes the data. Essentially the various metrics |
||||||
|
* collected are divided by $num_runs. |
||||||
|
* |
||||||
|
* @author Kannan |
||||||
|
*/ |
||||||
|
function xhprof_normalize_metrics($raw_data, $num_runs) { |
||||||
|
|
||||||
|
if (empty($raw_data) || ($num_runs == 0)) { |
||||||
|
return $raw_data; |
||||||
|
} |
||||||
|
|
||||||
|
$raw_data_total = array(); |
||||||
|
|
||||||
|
if (isset($raw_data["==>main()"]) && isset($raw_data["main()"])) { |
||||||
|
xhprof_error("XHProf Error: both ==>main() and main() set in raw data..."); |
||||||
|
} |
||||||
|
|
||||||
|
foreach ($raw_data as $parent_child => $info) { |
||||||
|
foreach ($info as $metric => $value) { |
||||||
|
$raw_data_total[$parent_child][$metric] = ($value / $num_runs); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return $raw_data_total; |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
/** |
||||||
|
* Get raw data corresponding to specified array of runs |
||||||
|
* aggregated by certain weightage. |
||||||
|
* |
||||||
|
* Suppose you have run:5 corresponding to page1.php, |
||||||
|
* run:6 corresponding to page2.php, |
||||||
|
* and run:7 corresponding to page3.php |
||||||
|
* |
||||||
|
* and you want to accumulate these runs in a 2:4:1 ratio. You |
||||||
|
* can do so by calling: |
||||||
|
* |
||||||
|
* xhprof_aggregate_runs(array(5, 6, 7), array(2, 4, 1)); |
||||||
|
* |
||||||
|
* The above will return raw data for the runs aggregated |
||||||
|
* in 2:4:1 ratio. |
||||||
|
* |
||||||
|
* @param object $xhprof_runs_impl An object that implements |
||||||
|
* the iXHProfRuns interface |
||||||
|
* @param array $runs run ids of the XHProf runs.. |
||||||
|
* @param array $wts integral (ideally) weights for $runs |
||||||
|
* @param string $source source to fetch raw data for run from |
||||||
|
* @param bool $use_script_name If true, a fake edge from main() to |
||||||
|
* to __script::<scriptname> is introduced |
||||||
|
* in the raw data so that after aggregations |
||||||
|
* the script name is still preserved. |
||||||
|
* |
||||||
|
* @return array Return aggregated raw data |
||||||
|
* |
||||||
|
* @author Kannan |
||||||
|
*/ |
||||||
|
function xhprof_aggregate_runs($xhprof_runs_impl, $runs, |
||||||
|
$wts, $source="phprof", |
||||||
|
$use_script_name=false) { |
||||||
|
|
||||||
|
$raw_data_total = null; |
||||||
|
$raw_data = null; |
||||||
|
$metrics = array(); |
||||||
|
|
||||||
|
$run_count = count($runs); |
||||||
|
$wts_count = count($wts); |
||||||
|
|
||||||
|
if (($run_count == 0) || |
||||||
|
(($wts_count > 0) && ($run_count != $wts_count))) { |
||||||
|
return array('description' => 'Invalid input..', |
||||||
|
'raw' => null); |
||||||
|
} |
||||||
|
|
||||||
|
$bad_runs = array(); |
||||||
|
foreach($runs as $idx => $run_id) { |
||||||
|
|
||||||
|
$raw_data = $xhprof_runs_impl->get_run($run_id, $source, $description); |
||||||
|
|
||||||
|
// use the first run to derive what metrics to aggregate on. |
||||||
|
if ($idx == 0) { |
||||||
|
foreach ($raw_data["main()"] as $metric => $val) { |
||||||
|
if ($metric != "pmu") { |
||||||
|
// for now, just to keep data size small, skip "peak" memory usage |
||||||
|
// data while aggregating. |
||||||
|
// The "regular" memory usage data will still be tracked. |
||||||
|
if (isset($val)) { |
||||||
|
$metrics[] = $metric; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
if (!xhprof_valid_run($run_id, $raw_data)) { |
||||||
|
$bad_runs[] = $run_id; |
||||||
|
continue; |
||||||
|
} |
||||||
|
|
||||||
|
if ($use_script_name) { |
||||||
|
$page = $description; |
||||||
|
|
||||||
|
// create a fake function '__script::$page', and have and edge from |
||||||
|
// main() to '__script::$page'. We will also need edges to transfer |
||||||
|
// all edges originating from main() to now originate from |
||||||
|
// '__script::$page' to all function called from main(). |
||||||
|
// |
||||||
|
// We also weight main() ever so slightly higher so that |
||||||
|
// it shows up above the new entry in reports sorted by |
||||||
|
// inclusive metrics or call counts. |
||||||
|
if ($page) { |
||||||
|
foreach($raw_data["main()"] as $metric => $val) { |
||||||
|
$fake_edge[$metric] = $val; |
||||||
|
$new_main[$metric] = $val + 0.00001; |
||||||
|
} |
||||||
|
$raw_data["main()"] = $new_main; |
||||||
|
$raw_data[xhprof_build_parent_child_key("main()", |
||||||
|
"__script::$page")] |
||||||
|
= $fake_edge; |
||||||
|
} else { |
||||||
|
$use_script_name = false; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// if no weights specified, use 1 as the default weightage.. |
||||||
|
$wt = ($wts_count == 0) ? 1 : $wts[$idx]; |
||||||
|
|
||||||
|
// aggregate $raw_data into $raw_data_total with appropriate weight ($wt) |
||||||
|
foreach ($raw_data as $parent_child => $info) { |
||||||
|
if ($use_script_name) { |
||||||
|
// if this is an old edge originating from main(), it now |
||||||
|
// needs to be from '__script::$page' |
||||||
|
if (substr($parent_child, 0, 9) == "main()==>") { |
||||||
|
$child =substr($parent_child, 9); |
||||||
|
// ignore the newly added edge from main() |
||||||
|
if (substr($child, 0, 10) != "__script::") { |
||||||
|
$parent_child = xhprof_build_parent_child_key("__script::$page", |
||||||
|
$child); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
if (!isset($raw_data_total[$parent_child])) { |
||||||
|
foreach ($metrics as $metric) { |
||||||
|
$raw_data_total[$parent_child][$metric] = ($wt * $info[$metric]); |
||||||
|
} |
||||||
|
} else { |
||||||
|
foreach ($metrics as $metric) { |
||||||
|
$raw_data_total[$parent_child][$metric] += ($wt * $info[$metric]); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
$runs_string = implode(",", $runs); |
||||||
|
|
||||||
|
if (isset($wts)) { |
||||||
|
$wts_string = "in the ratio (" . implode(":", $wts) . ")"; |
||||||
|
$normalization_count = array_sum($wts); |
||||||
|
} else { |
||||||
|
$wts_string = ""; |
||||||
|
$normalization_count = $run_count; |
||||||
|
} |
||||||
|
|
||||||
|
$run_count = $run_count - count($bad_runs); |
||||||
|
|
||||||
|
$data['description'] = "Aggregated Report for $run_count runs: ". |
||||||
|
"$runs_string $wts_string\n"; |
||||||
|
$data['raw'] = xhprof_normalize_metrics($raw_data_total, |
||||||
|
$normalization_count); |
||||||
|
$data['bad_runs'] = $bad_runs; |
||||||
|
|
||||||
|
return $data; |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
/** |
||||||
|
* Analyze hierarchical raw data, and compute per-function (flat) |
||||||
|
* inclusive and exclusive metrics. |
||||||
|
* |
||||||
|
* Also, store overall totals in the 2nd argument. |
||||||
|
* |
||||||
|
* @param array $raw_data XHProf format raw profiler data. |
||||||
|
* @param array &$overall_totals OUT argument for returning |
||||||
|
* overall totals for various |
||||||
|
* metrics. |
||||||
|
* @return array Returns a map from function name to its |
||||||
|
* call count and inclusive & exclusive metrics |
||||||
|
* (such as wall time, etc.). |
||||||
|
* |
||||||
|
* @author Kannan Muthukkaruppan |
||||||
|
*/ |
||||||
|
function xhprof_compute_flat_info($raw_data, &$overall_totals) { |
||||||
|
|
||||||
|
global $display_calls; |
||||||
|
|
||||||
|
$metrics = xhprof_get_metrics($raw_data); |
||||||
|
|
||||||
|
$overall_totals = array( "ct" => 0, |
||||||
|
"wt" => 0, |
||||||
|
"ut" => 0, |
||||||
|
"st" => 0, |
||||||
|
"cpu" => 0, |
||||||
|
"mu" => 0, |
||||||
|
"pmu" => 0, |
||||||
|
"samples" => 0 |
||||||
|
); |
||||||
|
|
||||||
|
// compute inclusive times for each function |
||||||
|
$symbol_tab = xhprof_compute_inclusive_times($raw_data); |
||||||
|
|
||||||
|
/* total metric value is the metric value for "main()" */ |
||||||
|
foreach ($metrics as $metric) { |
||||||
|
$overall_totals[$metric] = $symbol_tab["main()"][$metric]; |
||||||
|
} |
||||||
|
|
||||||
|
/* |
||||||
|
* initialize exclusive (self) metric value to inclusive metric value |
||||||
|
* to start with. |
||||||
|
* In the same pass, also add up the total number of function calls. |
||||||
|
*/ |
||||||
|
foreach ($symbol_tab as $symbol => $info) { |
||||||
|
foreach ($metrics as $metric) { |
||||||
|
$symbol_tab[$symbol]["excl_" . $metric] = $symbol_tab[$symbol][$metric]; |
||||||
|
} |
||||||
|
if ($display_calls) { |
||||||
|
/* keep track of total number of calls */ |
||||||
|
$overall_totals["ct"] += $info["ct"]; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/* adjust exclusive times by deducting inclusive time of children */ |
||||||
|
foreach ($raw_data as $parent_child => $info) { |
||||||
|
list($parent, $child) = xhprof_parse_parent_child($parent_child); |
||||||
|
|
||||||
|
if ($parent) { |
||||||
|
foreach ($metrics as $metric) { |
||||||
|
// make sure the parent exists hasn't been pruned. |
||||||
|
if (isset($symbol_tab[$parent])) { |
||||||
|
$symbol_tab[$parent]["excl_" . $metric] -= $info[$metric]; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return $symbol_tab; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Hierarchical diff: |
||||||
|
* Compute and return difference of two call graphs: Run2 - Run1. |
||||||
|
* |
||||||
|
* @author Kannan |
||||||
|
*/ |
||||||
|
function xhprof_compute_diff($xhprof_data1, $xhprof_data2) { |
||||||
|
global $display_calls; |
||||||
|
|
||||||
|
// use the second run to decide what metrics we will do the diff on |
||||||
|
$metrics = xhprof_get_metrics($xhprof_data2); |
||||||
|
|
||||||
|
$xhprof_delta = $xhprof_data2; |
||||||
|
|
||||||
|
foreach ($xhprof_data1 as $parent_child => $info) { |
||||||
|
|
||||||
|
if (!isset($xhprof_delta[$parent_child])) { |
||||||
|
|
||||||
|
// this pc combination was not present in run1; |
||||||
|
// initialize all values to zero. |
||||||
|
if ($display_calls) { |
||||||
|
$xhprof_delta[$parent_child] = array("ct" => 0); |
||||||
|
} else { |
||||||
|
$xhprof_delta[$parent_child] = array(); |
||||||
|
} |
||||||
|
foreach ($metrics as $metric) { |
||||||
|
$xhprof_delta[$parent_child][$metric] = 0; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
if ($display_calls) { |
||||||
|
$xhprof_delta[$parent_child]["ct"] -= $info["ct"]; |
||||||
|
} |
||||||
|
|
||||||
|
foreach ($metrics as $metric) { |
||||||
|
$xhprof_delta[$parent_child][$metric] -= $info[$metric]; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return $xhprof_delta; |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
/** |
||||||
|
* Compute inclusive metrics for function. This code was factored out |
||||||
|
* of xhprof_compute_flat_info(). |
||||||
|
* |
||||||
|
* The raw data contains inclusive metrics of a function for each |
||||||
|
* unique parent function it is called from. The total inclusive metrics |
||||||
|
* for a function is therefore the sum of inclusive metrics for the |
||||||
|
* function across all parents. |
||||||
|
* |
||||||
|
* @return array Returns a map of function name to total (across all parents) |
||||||
|
* inclusive metrics for the function. |
||||||
|
* |
||||||
|
* @author Kannan |
||||||
|
*/ |
||||||
|
function xhprof_compute_inclusive_times($raw_data) { |
||||||
|
global $display_calls; |
||||||
|
|
||||||
|
$metrics = xhprof_get_metrics($raw_data); |
||||||
|
|
||||||
|
$symbol_tab = array(); |
||||||
|
|
||||||
|
/* |
||||||
|
* First compute inclusive time for each function and total |
||||||
|
* call count for each function across all parents the |
||||||
|
* function is called from. |
||||||
|
*/ |
||||||
|
foreach ($raw_data as $parent_child => $info) { |
||||||
|
|
||||||
|
list($parent, $child) = xhprof_parse_parent_child($parent_child); |
||||||
|
|
||||||
|
if ($parent == $child) { |
||||||
|
/* |
||||||
|
* XHProf PHP extension should never trigger this situation any more. |
||||||
|
* Recursion is handled in the XHProf PHP extension by giving nested |
||||||
|
* calls a unique recursion-depth appended name (for example, foo@1). |
||||||
|
*/ |
||||||
|
xhprof_error("Error in Raw Data: parent & child are both: $parent"); |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
if (!isset($symbol_tab[$child])) { |
||||||
|
|
||||||
|
if ($display_calls) { |
||||||
|
$symbol_tab[$child] = array("ct" => $info["ct"]); |
||||||
|
} else { |
||||||
|
$symbol_tab[$child] = array(); |
||||||
|
} |
||||||
|
foreach ($metrics as $metric) { |
||||||
|
$symbol_tab[$child][$metric] = $info[$metric]; |
||||||
|
} |
||||||
|
} else { |
||||||
|
if ($display_calls) { |
||||||
|
/* increment call count for this child */ |
||||||
|
$symbol_tab[$child]["ct"] += $info["ct"]; |
||||||
|
} |
||||||
|
|
||||||
|
/* update inclusive times/metric for this child */ |
||||||
|
foreach ($metrics as $metric) { |
||||||
|
$symbol_tab[$child][$metric] += $info[$metric]; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return $symbol_tab; |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
/* |
||||||
|
* Prunes XHProf raw data: |
||||||
|
* |
||||||
|
* Any node whose inclusive walltime accounts for less than $prune_percent |
||||||
|
* of total walltime is pruned. [It is possible that a child function isn't |
||||||
|
* pruned, but one or more of its parents get pruned. In such cases, when |
||||||
|
* viewing the child function's hierarchical information, the cost due to |
||||||
|
* the pruned parent(s) will be attributed to a special function/symbol |
||||||
|
* "__pruned__()".] |
||||||
|
* |
||||||
|
* @param array $raw_data XHProf raw data to be pruned & validated. |
||||||
|
* @param double $prune_percent Any edges that account for less than |
||||||
|
* $prune_percent of time will be pruned |
||||||
|
* from the raw data. |
||||||
|
* |
||||||
|
* @return array Returns the pruned raw data. |
||||||
|
* |
||||||
|
* @author Kannan |
||||||
|
*/ |
||||||
|
function xhprof_prune_run($raw_data, $prune_percent) { |
||||||
|
|
||||||
|
$main_info = $raw_data["main()"]; |
||||||
|
if (empty($main_info)) { |
||||||
|
xhprof_error("XHProf: main() missing in raw data"); |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
// raw data should contain either wall time or samples information... |
||||||
|
if (isset($main_info["wt"])) { |
||||||
|
$prune_metric = "wt"; |
||||||
|
} else if (isset($main_info["samples"])) { |
||||||
|
$prune_metric = "samples"; |
||||||
|
} else { |
||||||
|
xhprof_error("XHProf: for main() we must have either wt " |
||||||
|
."or samples attribute set"); |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
// determine the metrics present in the raw data.. |
||||||
|
$metrics = array(); |
||||||
|
foreach ($main_info as $metric => $val) { |
||||||
|
if (isset($val)) { |
||||||
|
$metrics[] = $metric; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
$prune_threshold = (($main_info[$prune_metric] * $prune_percent) / 100.0); |
||||||
|
|
||||||
|
init_metrics($raw_data, null, null, false); |
||||||
|
$flat_info = xhprof_compute_inclusive_times($raw_data); |
||||||
|
|
||||||
|
foreach ($raw_data as $parent_child => $info) { |
||||||
|
|
||||||
|
list($parent, $child) = xhprof_parse_parent_child($parent_child); |
||||||
|
|
||||||
|
// is this child's overall total from all parents less than threshold? |
||||||
|
if ($flat_info[$child][$prune_metric] < $prune_threshold) { |
||||||
|
unset($raw_data[$parent_child]); // prune the edge |
||||||
|
} else if ($parent && |
||||||
|
($parent != "__pruned__()") && |
||||||
|
($flat_info[$parent][$prune_metric] < $prune_threshold)) { |
||||||
|
|
||||||
|
// Parent's overall inclusive metric is less than a threshold. |
||||||
|
// All edges to the parent node will get nuked, and this child will |
||||||
|
// be a dangling child. |
||||||
|
// So instead change its parent to be a special function __pruned__(). |
||||||
|
$pruned_edge = xhprof_build_parent_child_key("__pruned__()", $child); |
||||||
|
|
||||||
|
if (isset($raw_data[$pruned_edge])) { |
||||||
|
foreach ($metrics as $metric) { |
||||||
|
$raw_data[$pruned_edge][$metric]+=$raw_data[$parent_child][$metric]; |
||||||
|
} |
||||||
|
} else { |
||||||
|
$raw_data[$pruned_edge] = $raw_data[$parent_child]; |
||||||
|
} |
||||||
|
|
||||||
|
unset($raw_data[$parent_child]); // prune the edge |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return $raw_data; |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
/** |
||||||
|
* Set one key in an array and return the array |
||||||
|
* |
||||||
|
* @author Kannan |
||||||
|
*/ |
||||||
|
function xhprof_array_set($arr, $k, $v) { |
||||||
|
$arr[$k] = $v; |
||||||
|
return $arr; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Removes/unsets one key in an array and return the array |
||||||
|
* |
||||||
|
* @author Kannan |
||||||
|
*/ |
||||||
|
function xhprof_array_unset($arr, $k) { |
||||||
|
unset($arr[$k]); |
||||||
|
return $arr; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Type definitions for URL params |
||||||
|
*/ |
||||||
|
define('XHPROF_STRING_PARAM', 1); |
||||||
|
define('XHPROF_UINT_PARAM', 2); |
||||||
|
define('XHPROF_FLOAT_PARAM', 3); |
||||||
|
define('XHPROF_BOOL_PARAM', 4); |
||||||
|
|
||||||
|
|
||||||
|
/** |
||||||
|
* Internal helper function used by various |
||||||
|
* xhprof_get_param* flavors for various |
||||||
|
* types of parameters. |
||||||
|
* |
||||||
|
* @param string name of the URL query string param |
||||||
|
* |
||||||
|
* @author Kannan |
||||||
|
*/ |
||||||
|
function xhprof_get_param_helper($param) { |
||||||
|
$val = null; |
||||||
|
if (isset($_GET[$param])) |
||||||
|
$val = $_GET[$param]; |
||||||
|
else if (isset($_POST[$param])) { |
||||||
|
$val = $_POST[$param]; |
||||||
|
} |
||||||
|
return $val; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Extracts value for string param $param from query |
||||||
|
* string. If param is not specified, return the |
||||||
|
* $default value. |
||||||
|
* |
||||||
|
* @author Kannan |
||||||
|
*/ |
||||||
|
function xhprof_get_string_param($param, $default = '') { |
||||||
|
$val = xhprof_get_param_helper($param); |
||||||
|
|
||||||
|
if ($val === null) |
||||||
|
return $default; |
||||||
|
|
||||||
|
return $val; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Extracts value for unsigned integer param $param from |
||||||
|
* query string. If param is not specified, return the |
||||||
|
* $default value. |
||||||
|
* |
||||||
|
* If value is not a valid unsigned integer, logs error |
||||||
|
* and returns null. |
||||||
|
* |
||||||
|
* @author Kannan |
||||||
|
*/ |
||||||
|
function xhprof_get_uint_param($param, $default = 0) { |
||||||
|
$val = xhprof_get_param_helper($param); |
||||||
|
|
||||||
|
if ($val === null) |
||||||
|
$val = $default; |
||||||
|
|
||||||
|
// trim leading/trailing whitespace |
||||||
|
$val = trim($val); |
||||||
|
|
||||||
|
// if it only contains digits, then ok.. |
||||||
|
if (ctype_digit($val)) { |
||||||
|
return $val; |
||||||
|
} |
||||||
|
|
||||||
|
xhprof_error("$param is $val. It must be an unsigned integer."); |
||||||
|
return null; |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
/** |
||||||
|
* Extracts value for a float param $param from |
||||||
|
* query string. If param is not specified, return |
||||||
|
* the $default value. |
||||||
|
* |
||||||
|
* If value is not a valid unsigned integer, logs error |
||||||
|
* and returns null. |
||||||
|
* |
||||||
|
* @author Kannan |
||||||
|
*/ |
||||||
|
function xhprof_get_float_param($param, $default = 0) { |
||||||
|
$val = xhprof_get_param_helper($param); |
||||||
|
|
||||||
|
if ($val === null) |
||||||
|
$val = $default; |
||||||
|
|
||||||
|
// trim leading/trailing whitespace |
||||||
|
$val = trim($val); |
||||||
|
|
||||||
|
// TBD: confirm the value is indeed a float. |
||||||
|
if (true) // for now.. |
||||||
|
return (float)$val; |
||||||
|
|
||||||
|
xhprof_error("$param is $val. It must be a float."); |
||||||
|
return null; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Extracts value for a boolean param $param from |
||||||
|
* query string. If param is not specified, return |
||||||
|
* the $default value. |
||||||
|
* |
||||||
|
* If value is not a valid unsigned integer, logs error |
||||||
|
* and returns null. |
||||||
|
* |
||||||
|
* @author Kannan |
||||||
|
*/ |
||||||
|
function xhprof_get_bool_param($param, $default = false) { |
||||||
|
$val = xhprof_get_param_helper($param); |
||||||
|
|
||||||
|
if ($val === null) |
||||||
|
$val = $default; |
||||||
|
|
||||||
|
// trim leading/trailing whitespace |
||||||
|
$val = trim($val); |
||||||
|
|
||||||
|
switch (strtolower($val)) { |
||||||
|
case '0': |
||||||
|
case '1': |
||||||
|
$val = (bool)$val; |
||||||
|
break; |
||||||
|
case 'true': |
||||||
|
case 'on': |
||||||
|
case 'yes': |
||||||
|
$val = true; |
||||||
|
break; |
||||||
|
case 'false': |
||||||
|
case 'off': |
||||||
|
case 'no': |
||||||
|
$val = false; |
||||||
|
break; |
||||||
|
default: |
||||||
|
xhprof_error("$param is $val. It must be a valid boolean string."); |
||||||
|
return null; |
||||||
|
} |
||||||
|
|
||||||
|
return $val; |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Initialize params from URL query string. The function |
||||||
|
* creates globals variables for each of the params |
||||||
|
* and if the URL query string doesn't specify a particular |
||||||
|
* param initializes them with the corresponding default |
||||||
|
* value specified in the input. |
||||||
|
* |
||||||
|
* @params array $params An array whose keys are the names |
||||||
|
* of URL params who value needs to |
||||||
|
* be retrieved from the URL query |
||||||
|
* string. PHP globals are created |
||||||
|
* with these names. The value is |
||||||
|
* itself an array with 2-elems (the |
||||||
|
* param type, and its default value). |
||||||
|
* If a param is not specified in the |
||||||
|
* query string the default value is |
||||||
|
* used. |
||||||
|
* @author Kannan |
||||||
|
*/ |
||||||
|
function xhprof_param_init($params) { |
||||||
|
/* Create variables specified in $params keys, init defaults */ |
||||||
|
foreach ($params as $k => $v) { |
||||||
|
switch ($v[0]) { |
||||||
|
case XHPROF_STRING_PARAM: |
||||||
|
$p = xhprof_get_string_param($k, $v[1]); |
||||||
|
break; |
||||||
|
case XHPROF_UINT_PARAM: |
||||||
|
$p = xhprof_get_uint_param($k, $v[1]); |
||||||
|
break; |
||||||
|
case XHPROF_FLOAT_PARAM: |
||||||
|
$p = xhprof_get_float_param($k, $v[1]); |
||||||
|
break; |
||||||
|
case XHPROF_BOOL_PARAM: |
||||||
|
$p = xhprof_get_bool_param($k, $v[1]); |
||||||
|
break; |
||||||
|
default: |
||||||
|
xhprof_error("Invalid param type passed to xhprof_param_init: " |
||||||
|
. $v[0]); |
||||||
|
exit(); |
||||||
|
} |
||||||
|
|
||||||
|
// create a global variable using the parameter name. |
||||||
|
$GLOBALS[$k] = $p; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
/** |
||||||
|
* Given a partial query string $q return matching function names in |
||||||
|
* specified XHProf run. This is used for the type ahead function |
||||||
|
* selector. |
||||||
|
* |
||||||
|
* @author Kannan |
||||||
|
*/ |
||||||
|
function xhprof_get_matching_functions($q, $xhprof_data) { |
||||||
|
|
||||||
|
$matches = array(); |
||||||
|
|
||||||
|
foreach ($xhprof_data as $parent_child => $info) { |
||||||
|
list($parent, $child) = xhprof_parse_parent_child($parent_child); |
||||||
|
if (stripos($parent, $q) !== false) { |
||||||
|
$matches[$parent] = 1; |
||||||
|
} |
||||||
|
if (stripos($child, $q) !== false) { |
||||||
|
$matches[$child] = 1; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
$res = array_keys($matches); |
||||||
|
|
||||||
|
// sort it so the answers are in some reliable order... |
||||||
|
asort($res); |
||||||
|
|
||||||
|
return ($res); |
||||||
|
} |
||||||
|
|
@ -0,0 +1,147 @@ |
|||||||
|
<?php |
||||||
|
// |
||||||
|
// Copyright (c) 2009 Facebook |
||||||
|
// |
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License"); |
||||||
|
// you may not use this file except in compliance with the License. |
||||||
|
// You may obtain a copy of the License at |
||||||
|
// |
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0 |
||||||
|
// |
||||||
|
// Unless required by applicable law or agreed to in writing, software |
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
// See the License for the specific language governing permissions and |
||||||
|
// limitations under the License. |
||||||
|
// |
||||||
|
|
||||||
|
// |
||||||
|
// This file defines the interface iXHProfRuns and also provides a default |
||||||
|
// implementation of the interface (class XHProfRuns). |
||||||
|
// |
||||||
|
|
||||||
|
/** |
||||||
|
* iXHProfRuns interface for getting/saving a XHProf run. |
||||||
|
* |
||||||
|
* Clients can either use the default implementation, |
||||||
|
* namely XHProfRuns_Default, of this interface or define |
||||||
|
* their own implementation. |
||||||
|
* |
||||||
|
* @author Kannan |
||||||
|
*/ |
||||||
|
interface iXHProfRuns { |
||||||
|
|
||||||
|
/** |
||||||
|
* Returns XHProf data given a run id ($run) of a given |
||||||
|
* type ($type). |
||||||
|
* |
||||||
|
* Also, a brief description of the run is returned via the |
||||||
|
* $run_desc out parameter. |
||||||
|
*/ |
||||||
|
public function get_run($run_id, $type, &$run_desc); |
||||||
|
|
||||||
|
/** |
||||||
|
* Save XHProf data for a profiler run of specified type |
||||||
|
* ($type). |
||||||
|
* |
||||||
|
* The caller may optionally pass in run_id (which they |
||||||
|
* promise to be unique). If a run_id is not passed in, |
||||||
|
* the implementation of this method must generated a |
||||||
|
* unique run id for this saved XHProf run. |
||||||
|
* |
||||||
|
* Returns the run id for the saved XHProf run. |
||||||
|
* |
||||||
|
*/ |
||||||
|
public function save_run($xhprof_data, $type, $run_id = null); |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
/** |
||||||
|
* XHProfRuns_Default is the default implementation of the |
||||||
|
* iXHProfRuns interface for saving/fetching XHProf runs. |
||||||
|
* |
||||||
|
* It stores/retrieves runs to/from a filesystem directory |
||||||
|
* specified by the "xhprof.output_dir" ini parameter. |
||||||
|
* |
||||||
|
* @author Kannan |
||||||
|
*/ |
||||||
|
class XHProfRuns_Default implements iXHProfRuns { |
||||||
|
|
||||||
|
private $dir = ''; |
||||||
|
|
||||||
|
private function gen_run_id($type) { |
||||||
|
return uniqid(); |
||||||
|
} |
||||||
|
|
||||||
|
private function file_name($run_id, $type) { |
||||||
|
|
||||||
|
$file = "$run_id.$type"; |
||||||
|
|
||||||
|
if (!empty($this->dir)) { |
||||||
|
$file = $this->dir . "/" . $file; |
||||||
|
} |
||||||
|
return $file; |
||||||
|
} |
||||||
|
|
||||||
|
public function __construct($dir = null) { |
||||||
|
|
||||||
|
// if user hasn't passed a directory location, |
||||||
|
// we use the xhprof.output_dir ini setting |
||||||
|
// if specified, else we default to the directory |
||||||
|
// in which the error_log file resides. |
||||||
|
|
||||||
|
if (empty($dir)) { |
||||||
|
$dir = ini_get("xhprof.output_dir"); |
||||||
|
if (empty($dir)) { |
||||||
|
|
||||||
|
// some default that at least works on unix... |
||||||
|
$dir = "/tmp"; |
||||||
|
|
||||||
|
xhprof_error("Warning: Must specify directory location for XHProf runs. ". |
||||||
|
"Trying {$dir} as default. You can either pass the " . |
||||||
|
"directory location as an argument to the constructor ". |
||||||
|
"for XHProfRuns_Default() or set xhprof.output_dir ". |
||||||
|
"ini param."); |
||||||
|
} |
||||||
|
} |
||||||
|
$this->dir = $dir; |
||||||
|
} |
||||||
|
|
||||||
|
public function get_run($run_id, $type, &$run_desc) { |
||||||
|
$file_name = $this->file_name($run_id, $type); |
||||||
|
|
||||||
|
if (!file_exists($file_name)) { |
||||||
|
xhprof_error("Could not find file $file_name"); |
||||||
|
$run_desc = "Invalid Run Id = $run_id"; |
||||||
|
return null; |
||||||
|
} |
||||||
|
|
||||||
|
$contents = file_get_contents($file_name); |
||||||
|
$run_desc = "XHProf Run (Namespace=$type)"; |
||||||
|
return unserialize($contents); |
||||||
|
} |
||||||
|
|
||||||
|
public function save_run($xhprof_data, $type, $run_id = null) { |
||||||
|
|
||||||
|
// Use PHP serialize function to store the XHProf's |
||||||
|
// raw profiler data. |
||||||
|
$xhprof_data = serialize($xhprof_data); |
||||||
|
|
||||||
|
if ($run_id === null) { |
||||||
|
$run_id = $this->gen_run_id($type); |
||||||
|
} |
||||||
|
|
||||||
|
$file_name = $this->file_name($run_id, $type); |
||||||
|
$file = fopen($file_name, 'w'); |
||||||
|
|
||||||
|
if ($file) { |
||||||
|
fwrite($file, $xhprof_data); |
||||||
|
fclose($file); |
||||||
|
} else { |
||||||
|
xhprof_error("Could not open $file_name\n"); |
||||||
|
} |
||||||
|
|
||||||
|
// echo "Saved run in {$file_name}.\nRun id = {$run_id}.\n"; |
||||||
|
return $run_id; |
||||||
|
} |
||||||
|
} |
Loading…
Reference in new issue