Adding xhprof to tests directory to improve tuning processes - this will not be part of the production packages anyway

skala
Yannick Warnier 13 years ago
parent 237f49d081
commit be0121c630
  1. 11
      tests/xhprof/footer.php
  2. 6
      tests/xhprof/header.php
  3. 91
      tests/xhprof/xhprof_html/callgraph.php
  4. 82
      tests/xhprof/xhprof_html/css/xhprof.css
  5. 826
      tests/xhprof/xhprof_html/docs/index.html
  6. BIN
      tests/xhprof/xhprof_html/docs/sample-callgraph-image.jpg
  7. BIN
      tests/xhprof/xhprof_html/docs/sample-diff-report-flat-view.jpg
  8. BIN
      tests/xhprof/xhprof_html/docs/sample-diff-report-parent-child-view.jpg
  9. BIN
      tests/xhprof/xhprof_html/docs/sample-flat-view.jpg
  10. BIN
      tests/xhprof/xhprof_html/docs/sample-parent-child-view.jpg
  11. 90
      tests/xhprof/xhprof_html/index.php
  12. BIN
      tests/xhprof/xhprof_html/jquery/indicator.gif
  13. 3549
      tests/xhprof/xhprof_html/jquery/jquery-1.2.6.js
  14. 61
      tests/xhprof/xhprof_html/jquery/jquery.autocomplete.css
  15. 759
      tests/xhprof/xhprof_html/jquery/jquery.autocomplete.js
  16. 24
      tests/xhprof/xhprof_html/jquery/jquery.tooltip.css
  17. 294
      tests/xhprof/xhprof_html/jquery/jquery.tooltip.js
  18. 204
      tests/xhprof/xhprof_html/js/xhprof_report.js
  19. 32
      tests/xhprof/xhprof_html/typeahead.php
  20. 80
      tests/xhprof/xhprof_lib/display/typeahead_common.php
  21. 1499
      tests/xhprof/xhprof_lib/display/xhprof.php
  22. 476
      tests/xhprof/xhprof_lib/utils/callgraph_utils.php
  23. 866
      tests/xhprof/xhprof_lib/utils/xhprof_lib.php
  24. 147
      tests/xhprof/xhprof_lib/utils/xhprof_runs.php

@ -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::&lt;filename&gt;</b></code>
and <code><b>run_init::&lt;filename&gt;</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@&lt;n&gt;</b></code>: Implies that this is a
recursive invocation of <code>foo()</code>, where <code>&lt;n&gt;</code> represents
the recursion depth. The recursion may be direct (such as due to
<code>foo()</code> --&gt; <code>foo()</code>), or indirect (such as
due to </code>foo()</code> --&gt; <code>goo()</code> --&gt; 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()-&gt;b()-&gt;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() --&gt; c()
1 call from b() --&gt; c()
50 calls from c() --&gt; 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 &lt;xhprof_source_directory&gt;/extension/
% phpize
% ./configure --with-php-config=&lt;path to php-config&gt;
% 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=&lt;directory_for_storing_xhprof_runs&gt;
</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>
&lt;?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://&lt;xhprof-ui-address&gt;/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 &lt;run_id&gt; and namespace
&lt;namespace&gt; use a URL of the form:
<p><code>
http://&lt;xhprof-ui-address&gt;/index.php?run=&lt;run_id&gt;&source=&lt;namespace&gt;
</code>
<p>For example,
<p><code>
http://&lt;xhprof-ui-address&gt;/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 &lt;run_id1&gt; and
&lt;run_id2&gt; in namespace &lt;namespace&gt; use a URL of the form:
<p><code>
http://&lt;xhprof-ui-address&gt;/index.php?<b>run1=&lt;run_id1&gt;&run2=&lt;run_id2&gt;</b>&source=&lt;namespace&gt;
</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://&lt;xhprof-ui-address&gt;/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://&lt;xhprof-ui-address&gt;/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>

Binary file not shown.

After

Width:  |  Height:  |  Size: 146 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 290 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 238 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 268 KiB

Binary file not shown.

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>";

Binary file not shown.

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…
Cancel
Save