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