├── bin
└── xhprofile
├── extension
├── tests
│ ├── xhprof_010_append.php
│ ├── xhprof_011_prepend.php
│ ├── xhprof_011.phpt
│ ├── xhprof_010.phpt
│ ├── xhprof_009.phpt
│ ├── xhprof_004_inc.php
│ ├── xhprof_004_require.php
│ ├── xhprof_012.phpt
│ ├── common.php
│ ├── xhprof_003.phpt
│ ├── xhprof_008.phpt
│ ├── xhprof_005.phpt
│ ├── xhprof_002.phpt
│ ├── xhprof_004.phpt
│ ├── xhprof_006.phpt
│ ├── xhprof_001.phpt
│ └── xhprof_007.phpt
├── config.m4
├── php_xhprof.h
└── Makefile.local
├── support
└── libxhprof
│ ├── __phutil_library_init__.php
│ ├── __phutil_library_map__.php
│ └── unit
│ └── XHProfUnitTestEngine.php
├── xhprof_html
├── jquery
│ ├── indicator.gif
│ ├── jquery.tooltip.css
│ ├── jquery.autocomplete.css
│ ├── jquery.tooltip.js
│ └── jquery.autocomplete.js
├── docs
│ ├── sample-flat-view.jpg
│ ├── sample-callgraph-image.jpg
│ ├── sample-parent-child-view.jpg
│ ├── sample-diff-report-flat-view.jpg
│ ├── sample-diff-report-parent-child-view.jpg
│ ├── index.html
│ └── index-fr.html
├── typeahead.php
├── css
│ └── xhprof.css
├── index.php
├── callgraph.php
└── js
│ └── xhprof_report.js
├── .arcconfig
├── README
├── CREDITS
├── .gitignore
├── .travis.yml
├── scripts
└── xhprofile.php
├── .arclint
├── examples
└── sample.php
├── xhprof_lib
├── display
│ └── typeahead_common.php
└── utils
│ ├── xhprof_runs.php
│ └── callgraph_utils.php
├── CHANGELOG
├── package.xml
└── LICENSE
/bin/xhprofile:
--------------------------------------------------------------------------------
1 | ../scripts/xhprofile.php
--------------------------------------------------------------------------------
/extension/tests/xhprof_010_append.php:
--------------------------------------------------------------------------------
1 | [View Documentation]
6 |
--------------------------------------------------------------------------------
/extension/config.m4:
--------------------------------------------------------------------------------
1 | PHP_ARG_ENABLE(xhprof, whether to enable xhprof support,
2 | [ --enable-xhprof Enable xhprof support])
3 |
4 | if test "$PHP_XHPROF" != "no"; then
5 | PHP_NEW_EXTENSION(xhprof, xhprof.c, $ext_shared)
6 | fi
7 |
--------------------------------------------------------------------------------
/extension/tests/xhprof_011.phpt:
--------------------------------------------------------------------------------
1 | --TEST--
2 | XHProf: Crash with auto_prepend_file
3 | Author: epriestley
4 | --INI--
5 | include_path={PWD}
6 | auto_prepend_file=xhprof_011_prepend.php
7 | --FILE--
8 |
13 | --EXPECTF--
14 | PREPENDED
15 | MAIN
16 |
--------------------------------------------------------------------------------
/extension/tests/xhprof_010.phpt:
--------------------------------------------------------------------------------
1 | --TEST--
2 | XHProf: Crash with auto_append_file
3 | Author: epriestley
4 | --INI--
5 | include_path={PWD}
6 | auto_append_file=xhprof_010_append.php
7 | --FILE--
8 |
14 | --EXPECTF--
15 | MAIN
16 | APPENDED
17 |
--------------------------------------------------------------------------------
/CREDITS:
--------------------------------------------------------------------------------
1 | Originally developed at Facebook, XHProf was open sourced in Mar, 2009.
2 |
3 | XHProf is now maintained by Phacility.
4 |
5 | Creators:
6 | Changhao Jiang
7 | Kannan Muthukkaruppan
8 | Venkat Venkataramani
9 | Haiping Zhao
10 |
11 | Additional Contributors:
12 | George Cabrera - UI enhancements
13 | Paul Saab - FreeBSD port
14 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | extension/*
2 | !extension/config.m4
3 | !extension/Makefile.local
4 | !extension/php_xhprof.h
5 | !extension/tests/
6 | !extension/xhprof.c
7 |
8 | extension/tests/*.sh
9 | extension/tests/*.exp
10 | extension/tests/*.out
11 | extension/tests/*.diff
12 | extension/tests/*.log
13 | extension/tests/*.php
14 |
15 | support/libxhprof/.phutil_module_cache
16 |
--------------------------------------------------------------------------------
/extension/tests/xhprof_009.phpt:
--------------------------------------------------------------------------------
1 | --TEST--
2 | XHProf: PHP 5.5 crash in hp_execute_internal
3 | Author: epriestley
4 | --FILE--
5 |
9 | }
10 |
11 | spl_autoload_register('loader', $throw = true);
12 |
13 | xhprof_enable();
14 |
15 | class_exists('ThisClassDoesNotExist');
16 | echo "OK\n";
17 |
18 | --EXPECT--
19 | OK
20 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: php
2 |
3 | notifications:
4 | email: false
5 |
6 | php:
7 | - 5.5
8 | - 5.4
9 | - 5.3
10 | - 5.3.3
11 |
12 | env:
13 | global:
14 | - TEST_PHP_ARGS="-q"
15 | - REPORT_EXIT_STATUS=1
16 |
17 | before_script:
18 | - cd extension
19 | - phpize
20 | - ./configure
21 | - make -j4
22 |
23 | script: make -f Makefile.local test_with_exit_status
24 |
--------------------------------------------------------------------------------
/scripts/xhprofile.php:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env php
2 | ');
8 | }
9 |
10 | $__xhprof_target__ = $argv[1];
11 |
12 | $argv = array_slice($argv, 1);
13 | $argc = count($argv);
14 |
15 | xhprof_enable();
16 | require_once $__xhprof_target__;
17 | $xhprof_data = xhprof_disable();
18 |
19 | var_dump($xhprof_data);
20 |
--------------------------------------------------------------------------------
/support/libxhprof/__phutil_library_map__.php:
--------------------------------------------------------------------------------
1 | 2,
11 | 'class' => array(
12 | 'XHProfExtensionUnitTestEngine' => 'unit/XHProfUnitTestEngine.php',
13 | ),
14 | 'function' => array(),
15 | 'xmap' => array(
16 | 'XHProfExtensionUnitTestEngine' => 'ArcanistUnitTestEngine',
17 | ),
18 | ));
19 |
--------------------------------------------------------------------------------
/extension/tests/xhprof_004_inc.php:
--------------------------------------------------------------------------------
1 | "
13 | // which represents the initialization block of a file.
14 | //
15 |
16 | $result1 = explode(" ", "abc def ghi");
17 |
18 | $result2 = implode(",", $result1);
19 |
20 | echo $result2 . "\n";
21 |
22 | foo();
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/extension/tests/xhprof_004_require.php:
--------------------------------------------------------------------------------
1 | "
13 | // which represents the initialization block of a file.
14 | //
15 |
16 | $result1 = explode(" ", "abc def ghi");
17 |
18 | $result2 = implode(",", $result1);
19 |
20 | $result3 = strlen($result2);
21 |
22 | echo $result3 . "\n";
23 |
24 | bar();
25 |
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/.arclint:
--------------------------------------------------------------------------------
1 | {
2 | "exclude": [
3 | "(^xhprof_html/jquery/)",
4 | "(^xhprof_html/docs/)",
5 | "(^extension/Makefile.local)"
6 | ],
7 | "linters": {
8 | "chmod": {
9 | "type": "chmod"
10 | },
11 | "filename": {
12 | "type": "filename"
13 | },
14 | "generated": {
15 | "type": "generated"
16 | },
17 | "merge-conflict": {
18 | "type": "merge-conflict"
19 | },
20 | "nolint": {
21 | "type": "nolint"
22 | },
23 | "text-without-length": {
24 | "type": "text",
25 | "severity": {
26 | "3": "disabled"
27 | }
28 | }
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/xhprof_html/jquery/jquery.tooltip.css:
--------------------------------------------------------------------------------
1 | /*
2 | * jQuery Tooltip plugin 1.3
3 | *
4 | * http://bassistance.de/jquery-plugins/jquery-plugin-tooltip/
5 | * http://docs.jquery.com/Plugins/Tooltip
6 | *
7 | * Copyright (c) 2006 - 2008 Jörn Zaefferer
8 | *
9 | * $Id$
10 | *
11 | * Dual licensed under the MIT and GPL licenses:
12 | * http://www.opensource.org/licenses/mit-license.php
13 | * http://www.gnu.org/licenses/gpl.html
14 | */
15 |
16 | #tooltip {
17 | position: absolute;
18 | z-index: 3000;
19 | border: 1px solid #111;
20 | background-color: lightyellow;
21 | padding: 5px;
22 | opacity: 0.9;
23 | }
24 | #tooltip h3, #tooltip div { margin: 0; }
25 |
--------------------------------------------------------------------------------
/extension/tests/xhprof_012.phpt:
--------------------------------------------------------------------------------
1 | --TEST--
2 | XHProf: Memory Leak in Ignored Functions
3 | Author: epriestley
4 | --FILE--
5 | array($large)));
14 | xhprof_disable();
15 | unset($large);
16 |
17 | xhprof_enable();
18 | xhprof_disable();
19 |
20 | $new = memory_get_usage();
21 |
22 | $missing = ($new - $old);
23 |
24 | if ($missing >= (1024 * 1024 * 16)) {
25 | echo "LEAKED A LOT OF MEMORY\n";
26 | } else {
27 | echo "DID NOT LEAK A LOT OF MEMORY\n";
28 | }
29 |
30 | ?>
31 | --EXPECTF--
32 | DID NOT LEAK A LOT OF MEMORY
33 |
--------------------------------------------------------------------------------
/extension/tests/common.php:
--------------------------------------------------------------------------------
1 | $metrics) {
17 | echo str_pad($func, 40) . ":";
18 | ksort($metrics);
19 | foreach ($metrics as $name => $value) {
20 |
21 | // Only call counts are stable.
22 | // Wild card everything else. We still print
23 | // the metric name to ensure it was collected.
24 | if ($name != "ct") {
25 | $value = "*";
26 | } else {
27 | $value = str_pad($value, 8, " ", STR_PAD_LEFT);
28 | }
29 |
30 | echo " {$name}={$value};";
31 | }
32 | echo "\n";
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/examples/sample.php:
--------------------------------------------------------------------------------
1 | 0) {
5 | bar($x - 1);
6 | }
7 | }
8 |
9 | function foo() {
10 | for ($idx = 0; $idx < 5; $idx++) {
11 | bar($idx);
12 | $x = strlen("abc");
13 | }
14 | }
15 |
16 | // start profiling
17 | xhprof_enable();
18 |
19 | // run program
20 | foo();
21 |
22 | // stop profiler
23 | $xhprof_data = xhprof_disable();
24 |
25 | // display raw xhprof data for the profiler run
26 | print_r($xhprof_data);
27 |
28 |
29 | $XHPROF_ROOT = realpath(dirname(__FILE__) .'/..');
30 | include_once $XHPROF_ROOT . "/xhprof_lib/utils/xhprof_lib.php";
31 | include_once $XHPROF_ROOT . "/xhprof_lib/utils/xhprof_runs.php";
32 |
33 | // save raw data for this profiler run using default
34 | // implementation of iXHProfRuns.
35 | $xhprof_runs = new XHProfRuns_Default();
36 |
37 | // save the run under a namespace "xhprof_foo"
38 | $run_id = $xhprof_runs->save_run($xhprof_data, "xhprof_foo");
39 |
40 | echo "---------------\n".
41 | "Assuming you have set up the http based UI for \n".
42 | "XHProf at some address, you can view run at \n".
43 | "http:// XHProf is a hierarchical profiler for PHP. It reports
35 | function-level call counts and inclusive and
36 | exclusive metrics such as wall (elapsed)
37 | time, CPU time and memory usage. A function's profile can be broken
38 | down by callers or callees. The raw data collection component is
39 | implemented in C as a PHP Zend extension called
40 | XHProf reports can often be helpful in understanding the structure
46 | of the code being executed. The hierarchical nature of the reports can
47 | be used to determine, for example, what chain of calls led to a
48 | particular function getting called.
49 |
50 | XHProf supports ability to compare two runs (a.k.a. "diff" reports)
51 | or aggregate data from multiple runs. Diff and aggregate reports, much
52 | like single run reports, offer "flat" as well as "hierarchical" views
53 | of the profile.
54 |
55 | XHProf is a light-weight instrumentation based profiler. During the
56 | data collection phase, it keeps track of call counts and inclusive
57 | metrics for arcs in the dynamic callgraph of a program. It computes
58 | exclusive metrics in the reporting/post processing phase. XHProf
59 | handles recursive functions by detecting cycles in the callgraph at
60 | data collection time itself and avoiding the cycles by giving unique
61 | depth qualified names for the recursive invocations.
62 | XHProf's light-weight nature and aggregation capabilities make it
65 | well suited for collecting "function-level" performance statistics
66 | from production environments. [See additional notes for use in production.]
68 |
69 | XHProfLive (not part of the open source kit), for example, is a
73 | system-wide performance monitoring system in use at Facebook that is
74 | built on top of XHProf. XHProfLive continually gathers function-level
75 | profiler data from production tier by running a sample of page
76 | requests under XHProf. XHProfLive then aggregates the profile data
77 | corresponding to individual requests by various dimensions such as
78 | time, page type, and can help answer a variety of questions such as:
79 | What is the function-level profile for a specific page? How expensive
80 | is function "foo" across all pages, or on a specific page? What
81 | functions regressed most in the last hour/day/week? What is the
82 | historical trend for execution time of a page/function? and so on.
83 |
84 | Originally developed at Facebook, XHProf was open sourced in Mar, 2009. XHProf provides:
97 |
98 | Function-level summary information such as number of calls,
102 | inclusive/exclusive wall time, memory usage, and CPU time.
103 |
104 | For each function, it provides a breakdown of calls and times per
108 | parent (caller) & child (callee), such as:
109 |
110 | You may want to compare data from two XHProf runs for various
123 | reasons-- to figure out what's causing a regression between one
124 | version of the code base to another, to evaluate the performance
125 | improvement of a code change you are making, and so on.
126 |
127 | A diff report takes two runs as input and provides both flat
128 | function-level diff information, and hierarchical information
129 | (breakdown of diff by parent/children functions) for each function.
130 |
131 | The "flat" view (sample screenshot) in the diff report points out the top
133 | regressions & improvements.
134 |
135 | Clicking on functions in the "flat" view of the diff report, leads
136 | to the "hierarchical" (or parent/child) diff view of a function (sample screenshot). We can get a
138 | breakdown of the diff by parent/children functions.
139 |
140 |
141 | The profile data can also be viewed as a callgraph. The callgraph
145 | view highlights the critical path of the program.
146 |
147 |
148 | XHProf's memory profile mode helps track functions that
151 | allocate lots of memory.
152 |
153 | It is worth clarifying that that XHProf doesn't strictly track each
154 | allocation/free operation. Rather it uses a more simplistic
155 | scheme. It tracks the increase/decrease in the amount of memory
156 | allocated to PHP between each function's entry and exit. It also
157 | tracks increase/decrease in the amount of peak memory allocated to
158 | PHP for each function.
159 |
160 | XHProf tracks PHP For example, an include "lib/common.php"; operation will
197 | result in two XHProf function entries:
198 |
199 | True hierarchical profilers keep track of a full call stack at
224 | every data gathering point, and are later able to answer questions
225 | like: what was the cost of the 3rd invokation of foo()? or what was
226 | the cost of bar() when the call stack looked like
227 | a()->b()->bar()?
228 |
229 | XHProf keeps track of only 1-level of calling context and is
232 | therefore only able to answer questions about a function looking
233 | either 1-level up or 1-level down. It turns out that in practice this
234 | is sufficient for most use cases.
235 | To make this more concrete, take for instance the following
238 | example.
239 | While XHProf can tell you that d() was called from c() 50 times, it
249 | cannot tell you how many of those calls were triggered due to a()
250 | vs. b(). [We could speculate that perhaps 25 were due to a() and 25
251 | due to b(), but that's not necessarily true.]
252 | In practice however, this isn't a very big limitation.
255 | The extension lives in the "extension/" sub-directory.
260 |
261 | Note: A windows port hasn't been implemented yet. We have
264 | tested Version 0.9.2 and above of XHProf is also expected to work on Mac
267 | OS. [We have tested on Mac OS 10.5.]
268 |
269 | Note: XHProf uses the RDTSC instruction (time stamp counter)
270 | to implement a really low overhead timer for elapsed time. So at the
271 | moment XHProf's RDTSC based timer functionality doesn't work correctly if
277 | SpeedStep technology is turned on. This technology is available on
278 | some Intel processors. [Note: Mac desktops and laptops typically have
279 | SpeedStep turned on by default. To use XHProf, you'll need to disable
280 | SpeedStep.]
281 |
282 | The steps
285 | below should work for Linux/Unix environments.
286 |
287 |
288 | php.ini file: You can update your
299 | php.ini file to automatically load your extension. Add the following
300 | to your php.ini file.
301 |
302 | Test generating raw profiler data using a sample test program like:
317 |
318 | foo.php
320 | Run the above test program:
351 |
352 | You should get an output like:
357 |
358 | Note: The raw data only contains "inclusive" metrics. For
401 | example, the wall time metric in the raw data represents inclusive
402 | time in microsecs. Exclusive times for any function are computed
403 | during the analysis/reporting phase.
404 |
405 | Note: By default only call counts & elapsed time is profiled.
406 | You can optionally also profile CPU time and/or memory usage. Replace,
407 |
408 | You should now get an output like:
417 |
418 | Skipping builtin functions during profiling
479 |
480 | By default PHP builtin functions (such as Ignoring specific functions during profiling (0.9.2 or higher)
493 |
494 | Starting with release 0.9.2 of xhprof, you can tell XHProf to
495 | ignore a specified list of functions during profiling. This allows you
496 | to ignore, for example, functions used for indirect function calls
497 | such as To specify the list of functions to be ignored during profiling
504 | use the 2nd (optional) argument to The XHProf UI is implemented in PHP. The code resides in two
534 | subdirectories, The The Web server config: You'll need to make sure that the
550 | Managing XHProf Runs
554 |
555 | Clients have flexibility in how they save the XHProf raw data
556 | obtained from an XHProf run. The XHProf UI layer exposes an interface
557 | iXHProfRuns (see xhprof_lib/utils/xhprof_runs.php) that clients can
558 | implement. This allows the clients to tell the UI layer how to fetch
559 | the data corresponding to a XHProf run.
560 |
561 | The XHProf UI libaries come with a default file based
562 | implementation of the iXHProfRuns interface, namely
563 | "XHProfRuns_Default" (also in xhprof_lib/utils/xhprof_runs.php).
564 | This default implementation stores runs in the directory specified by
565 | xhprof.output_dir INI parameter.
566 |
567 | A XHProf run must be uniquely identified by a namespace and a run
568 | id.
569 |
570 |
571 |
572 | a) Saving XHProf data persistently:
573 |
574 | Assuming you are using the default implementation
575 | The above should save the run as a file in the directory specified
617 | by the b) Using your own implementation of iXHProfRuns
627 |
628 | If you decide you want your XHProf runs to be stored differently
629 | (either in a compressed format, in an alternate place such as DB,
630 | etc.) database, you'll need to implement a class that implements the
631 | iXHProfRuns() interface.
632 |
633 | You'll also need to modify the 3 main PHP entry pages (index.php,
634 | callgraph.php, typeahead.php) in the "xhprof_html/" directory to use
635 | the new class instead of the default class You'll also need to "include" the file that implements your class in
643 | the above files.
644 |
645 |
646 | Accessing runs from UI
647 |
648 | a) Viewing a Single Run Report
649 |
650 | To view the report for run id say <run_id> and namespace
651 | <namespace> use a URL of the form:
652 |
653 | For example,
658 | b) Viewing a Diff Report
664 |
665 | To view the report for run ids say <run_id1> and
666 | <run_id2> in namespace <namespace> use a URL of the form:
667 |
668 | c) Aggregate Report
673 |
674 | You can also specify a set of run ids for which you want an aggregated view/report.
675 |
676 | Say you have three XHProf runs with ids 1, 2 & 3 in namespace
677 | "benchmark". To view an aggregate report of these runs:
678 |
679 | Weighted aggregations: Further suppose that the above three runs
684 | correspond to three types of programs p1.php, p2.php and p3.php that
685 | typically occur in a mix of 20%, 30%, 50% respectively. To view an
686 | aggregate report that corresponds to a weighted average of these runs
687 | using:
688 |
689 | Some observations/guidelines. Your mileage may vary:
699 |
700 | We recommend using elapsed time + memory profiling mode in
708 | production. [Note: The additional overhead of memory profiling
709 | mode is really low.]
710 |
711 |
Existing runs:\n\n";
151 | $files = glob("{$this->dir}/*.{$this->suffix}");
152 | usort($files, function($a, $b) {
153 | return filemtime($b) - filemtime($a);
154 | });
155 | foreach ($files as $file) {
156 | list($run,$source) = explode('.', basename($file));
157 | echo '
\n";
164 | }
165 | }
166 | }
167 |
--------------------------------------------------------------------------------
/xhprof_html/js/xhprof_report.js:
--------------------------------------------------------------------------------
1 | /* Copyright (c) 2009 Facebook
2 | *
3 | * Licensed under the Apache License, Version 2.0 (the "License");
4 | * you may not use this file except in compliance with the License.
5 | * You may obtain a copy of the License at
6 | *
7 | * http://www.apache.org/licenses/LICENSE-2.0
8 | *
9 | * Unless required by applicable law or agreed to in writing, software
10 | * distributed under the License is distributed on an "AS IS" BASIS,
11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | * See the License for the specific language governing permissions and
13 | * limitations under the License.
14 | */
15 |
16 | /**
17 | * Helper javascript functions for XHProf report tooltips.
18 | *
19 | * @author Kannan Muthukkaruppan
20 | */
21 |
22 | // Take a string which is actually a number in comma separated format
23 | // and return a string representing the absolute value of the number.
24 | function stringAbs(x) {
25 | return x.replace("-", "");
26 | }
27 |
28 | // Takes a number in comma-separated string format, and
29 | // returns a boolean to indicate if the number is negative
30 | // or not.
31 | function isNegative(x) {
32 |
33 | return (x.indexOf("-") == 0);
34 |
35 | }
36 |
37 | function addCommas(nStr)
38 | {
39 | nStr += '';
40 | x = nStr.split('.');
41 | x1 = x[0];
42 | x2 = x.length > 1 ? '.' + x[1] : '';
43 | var rgx = /(\d+)(\d{3})/;
44 | while (rgx.test(x1)) {
45 | x1 = x1.replace(rgx, '$1' + ',' + '$2');
46 | }
47 | return x1 + x2;
48 | }
49 |
50 | // Mouseover tips for parent rows in parent/child report..
51 | function ParentRowToolTip(cell, metric)
52 | {
53 | var metric_val;
54 | var parent_metric_val;
55 | var parent_metric_pct_val;
56 | var col_index;
57 | var diff_text;
58 |
59 | row = cell.parentNode;
60 | tds = row.getElementsByTagName("td");
61 |
62 | parent_func = tds[0].innerHTML; // name
63 |
64 | if (diff_mode) {
65 | diff_text = " diff ";
66 | } else {
67 | diff_text = "";
68 | }
69 |
70 | s = '
';
82 |
83 | text = " of diff in calls ";
84 | } else {
85 | text = " of calls ";
86 | }
87 |
88 | s += parent_ct_pct + text + '(' + parent_ct + '/' + func_ct + ') to '
89 | + func_name + ' are from ' + parent_func + '
';
90 | } else {
91 |
92 | // help for other metrics such as wall time, user cpu time, memory usage
93 | col_index = metrics_col[metric];
94 | parent_metric_val = tds[col_index].innerHTML;
95 | parent_metric_pct_val = tds[col_index+1].innerHTML;
96 |
97 | metric_val = addCommas(func_metrics[metric]);
98 |
99 | s += parent_metric_pct_val + '(' + parent_metric_val + '/' + metric_val
100 | + ') of ' + metrics_desc[metric] +
101 | (diff_mode ? ((isNegative(parent_metric_val) ?
102 | " decrease" : " increase")) : "") +
103 | ' in ' + func_name + ' is due to calls from ' + parent_func + '
';
104 | }
105 |
106 | s += '
';
141 | s += 'This accounts for ' + child_ct_pct + ' (' + child_ct
142 | + '/' + total_child_ct
143 | + ') of function calls made by ' + func_name + '.';
144 |
145 | } else {
146 |
147 | // help for other metrics such as wall time, user cpu time, memory usage
148 | col_index = metrics_col[metric];
149 | child_metric_val = tds[col_index].innerHTML;
150 | child_metric_pct_val = tds[col_index+1].innerHTML;
151 |
152 | metric_val = addCommas(func_metrics[metric]);
153 |
154 | if (child_func.indexOf("Exclusive Metrics") != -1) {
155 | s += 'The exclusive ' + metrics_desc[metric] + diff_text
156 | + ' for ' + func_name
157 | + ' is ' + child_metric_val + "
";
158 |
159 | s += "which is " + child_metric_pct_val + " of the inclusive "
160 | + metrics_desc[metric]
161 | + diff_text + " for " + func_name + " (" + metric_val + ").";
162 |
163 | } else {
164 |
165 | s += child_func + ' when called from ' + func_name
166 | + ' takes ' + stringAbs(child_metric_val)
167 | + (diff_mode ? (isNegative(child_metric_val) ? " less" : " more") : "")
168 | + " of " + metrics_desc[metric] + "
";
169 |
170 | s += "which is " + child_metric_pct_val + " of the inclusive "
171 | + metrics_desc[metric]
172 | + diff_text + " for " + func_name + " (" + metric_val + ").";
173 | }
174 | }
175 |
176 | s += '
");
162 | helper.body.append(part);
163 | }
164 | helper.body.hideWhenEmpty();
165 | } else {
166 | helper.title.html(title).show();
167 | helper.body.hide();
168 | }
169 |
170 | // if element has href or src, add and show it, otherwise hide it
171 | if( settings(this).showURL && $(this).url() )
172 | helper.url.html( $(this).url().replace('http://', '') ).show();
173 | else
174 | helper.url.hide();
175 |
176 | // add an optional class for this tip
177 | helper.parent.addClass(settings(this).extraClass);
178 |
179 | // fix PNG background for IE
180 | if (settings(this).fixPNG )
181 | helper.parent.fixPNG();
182 |
183 | handle.apply(this, arguments);
184 | }
185 |
186 | // delete timeout and show helper
187 | function show() {
188 | tID = null;
189 | if ((!IE || !$.fn.bgiframe) && settings(current).fade) {
190 | if (helper.parent.is(":animated"))
191 | helper.parent.stop().show().fadeTo(settings(current).fade, current.tOpacity);
192 | else
193 | helper.parent.is(':visible') ? helper.parent.fadeTo(settings(current).fade, current.tOpacity) : helper.parent.fadeIn(settings(current).fade);
194 | } else {
195 | helper.parent.show();
196 | }
197 | update();
198 | }
199 |
200 | /**
201 | * callback for mousemove
202 | * updates the helper position
203 | * removes itself when no current element
204 | */
205 | function update(event) {
206 | if($.tooltip.blocked)
207 | return;
208 |
209 | if (event && event.target.tagName == "OPTION") {
210 | return;
211 | }
212 |
213 | // stop updating when tracking is disabled and the tooltip is visible
214 | if ( !track && helper.parent.is(":visible")) {
215 | $(document.body).unbind('mousemove', update)
216 | }
217 |
218 | // if no current element is available, remove this listener
219 | if( current == null ) {
220 | $(document.body).unbind('mousemove', update);
221 | return;
222 | }
223 |
224 | // remove position helper classes
225 | helper.parent.removeClass("viewport-right").removeClass("viewport-bottom");
226 |
227 | var left = helper.parent[0].offsetLeft;
228 | var top = helper.parent[0].offsetTop;
229 | if (event) {
230 | // position the helper 15 pixel to bottom right, starting from mouse position
231 | left = event.pageX + settings(current).left;
232 | top = event.pageY + settings(current).top;
233 | var right='auto';
234 | if (settings(current).positionLeft) {
235 | right = $(window).width() - left;
236 | left = 'auto';
237 | }
238 | helper.parent.css({
239 | left: left,
240 | right: right,
241 | top: top
242 | });
243 | }
244 |
245 | var v = viewport(),
246 | h = helper.parent[0];
247 | // check horizontal position
248 | if (v.x + v.cx < h.offsetLeft + h.offsetWidth) {
249 | left -= h.offsetWidth + 20 + settings(current).left;
250 | helper.parent.css({left: left + 'px'}).addClass("viewport-right");
251 | }
252 | // check vertical position
253 | if (v.y + v.cy < h.offsetTop + h.offsetHeight) {
254 | top -= h.offsetHeight + 20 + settings(current).top;
255 | helper.parent.css({top: top + 'px'}).addClass("viewport-bottom");
256 | }
257 | }
258 |
259 | function viewport() {
260 | return {
261 | x: $(window).scrollLeft(),
262 | y: $(window).scrollTop(),
263 | cx: $(window).width(),
264 | cy: $(window).height()
265 | };
266 | }
267 |
268 | // hide helper and restore added classes and the title
269 | function hide(event) {
270 | if($.tooltip.blocked)
271 | return;
272 | // clear timeout if possible
273 | if(tID)
274 | clearTimeout(tID);
275 | // no more current element
276 | current = null;
277 |
278 | var tsettings = settings(this);
279 | function complete() {
280 | helper.parent.removeClass( tsettings.extraClass ).hide().css("opacity", "");
281 | }
282 | if ((!IE || !$.fn.bgiframe) && tsettings.fade) {
283 | if (helper.parent.is(':animated'))
284 | helper.parent.stop().fadeTo(tsettings.fade, 0, complete);
285 | else
286 | helper.parent.stop().fadeOut(tsettings.fade, complete);
287 | } else
288 | complete();
289 |
290 | if( settings(this).fixPNG )
291 | helper.parent.unfixPNG();
292 | }
293 |
294 | })(jQuery);
295 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
--------------------------------------------------------------------------------
/extension/tests/xhprof_007.phpt:
--------------------------------------------------------------------------------
1 | --TEST--
2 | XHProf: Test excluding call_user_func and similar functions
3 | Author: mpal
4 | --FILE--
5 |
10 | array('call_user_func',
11 | 'call_user_func_array',
12 | 'my_call_user_func_safe',
13 | 'my_call_user_func_array_safe'));
14 | function bar() {
15 | return 1;
16 | }
17 |
18 | function foo($x) {
19 | $sum = 0;
20 | for ($idx = 0; $idx < 2; $idx++) {
21 | $sum += bar();
22 | }
23 | echo @"hello: {$x}\n" ;
24 | return @strlen("hello: {$x}");
25 | }
26 |
27 | function foo_array($x1, $x2 = 'test') {
28 | $sum = 0;
29 | $x = array($x1, $x2);
30 | foreach ($x as $idx) {
31 | $sum += bar();
32 | }
33 | echo @"hello: {$x[0]}{$x[1]}\n";
34 | return @strlen("hello: {$x[0]} {$x[1]}");
35 | }
36 |
37 | function my_call_user_func_safe($function, $args = 'my_safe') {
38 | if (!is_callable($function, true)) {
39 | throw new Exception('my_call_user_func_safe() invoked without ' .
40 | 'a valid callable.');
41 | }
42 |
43 | call_user_func($function, array($args));
44 | }
45 |
46 | function my_call_user_func_array_safe($function, $args = array()) {
47 | if (!is_callable($function, true)) {
48 | throw new Exception('my_call_user_func_array_safe() invoked without ' .
49 | 'a valid callable.');
50 | }
51 |
52 | call_user_func_array($function, $args);
53 | }
54 |
55 |
56 | class test_call_user_func {
57 | function test_call_user_func($test_func = 'foo',
58 | $arg1 = 'user_func test') {
59 | call_user_func($test_func, $arg1);
60 | }
61 | }
62 |
63 | function test_call_user_func_array($test_func = 'foo_array',
64 | $arg1 = array(0 => 'user_func_array',
65 | 'test')) {
66 | call_user_func_array($test_func, $arg1);
67 | }
68 |
69 | function test_my_call_user_func_safe($test_func = 'foo',
70 | $arg1 = 'my_user_func_safe test') {
71 | my_call_user_func_safe($test_func, $arg1);
72 | }
73 |
74 | function test_my_call_user_func_array_safe(
75 | $test_func = 'foo_array',
76 | $arg1 = array('my_user_func_array_safe',
77 | 'test')) {
78 | my_call_user_func_array_safe($test_func, $arg1);
79 | }
80 |
81 |
82 | // 1: Sanity test a simple profile run
83 | echo "Part 1: Default Flags\n";
84 | xhprof_enable(0, $xhprof_ignored_functions);
85 | foo("this is a test");
86 | $array_arg = array();
87 | $array_arg[] = 'calling ';
88 | $array_arg[] = 'foo_array';
89 | foo_array($array_arg);
90 |
91 | $output = xhprof_disable();
92 | echo "Part 1 output:\n";
93 | print_canonical($output);
94 | echo "\n";
95 |
96 | // 2a: Sanity test ignoring call_user_func
97 | echo "Part 2a: Ignore call_user_func\n";
98 | xhprof_enable(0, $xhprof_ignored_functions);
99 | $indirect_foo = new test_call_user_func('foo');
100 | $output = xhprof_disable();
101 | echo "Part 2a output:\n";
102 | print_canonical($output);
103 | echo "\n";
104 |
105 | // 2b: Confirm that profiling without parameters still works
106 | echo "Part 2b: Standard profile without parameters\n";
107 | xhprof_enable();
108 | $indirect_foo = new test_call_user_func('foo');
109 | $output = xhprof_disable();
110 | echo "Part 2b output:\n";
111 | print_canonical($output);
112 | echo "\n";
113 |
114 | // 2c: Confirm that empty array of ignored functions works
115 | echo "Part 2c: Standard profile with empty array of ignored functions\n";
116 | xhprof_enable(0, array());
117 | $indirect_foo = new test_call_user_func('foo');
118 | $output = xhprof_disable();
119 | echo "Part 2c output:\n";
120 | print_canonical($output);
121 | echo "\n";
122 |
123 | // 3: Sanity test ignoring call_user_func_array
124 | echo "Part 3: Ignore call_user_func_array\n";
125 | xhprof_enable(XHPROF_FLAGS_CPU, $xhprof_ignored_functions);
126 | test_call_user_func_array('foo_array', $array_arg);
127 | $output = xhprof_disable();
128 | echo "Part 3 output:\n";
129 | print_canonical($output);
130 | echo "\n";
131 |
132 | // 4: Sanity test ignoring my_call_user_func_safe
133 | echo "Part 4: Ignore my_call_user_func_safe\n";
134 | xhprof_enable(0, $xhprof_ignored_functions);
135 | test_my_call_user_func_safe('foo');
136 | $output = xhprof_disable();
137 | echo "Part 4 output:\n";
138 | print_canonical($output);
139 | echo "\n";
140 |
141 | // 5a: Sanity test ignoring my_call_user_func_array_safe and strlen
142 | echo "Part 5a: Ignore my_call_user_func_array_safe and strlen\n";
143 | $tmp1 = $xhprof_ignored_functions['ignored_functions'];
144 | $tmp1[] = 'strlen';
145 | $ignore_strlen_also = array('ignored_functions' => $tmp1);
146 | xhprof_enable(XHPROF_FLAGS_MEMORY, $ignore_strlen_also);
147 | test_my_call_user_func_array_safe('foo_array');
148 | $output = xhprof_disable();
149 | echo "Part 5a output:\n";
150 | print_canonical($output);
151 | echo "\n";
152 |
153 | // 5b: Sanity test to not ignore call_user_func variants
154 | echo "Part 5b: Profile call_user_func_array and my_call_user_func_array_safe\n";
155 | xhprof_enable(XHPROF_FLAGS_MEMORY, array());
156 | test_my_call_user_func_array_safe('foo_array');
157 | $output = xhprof_disable();
158 | echo "Part 5b output:\n";
159 | print_canonical($output);
160 | echo "\n";
161 |
162 | // 5c: Sanity test to only ignore my_call_user_func_array_safe
163 | echo "Part 5c: Only ignore call_user_func_array\n";
164 | $xhprof_ignored_functions = array('ignored_functions' =>
165 | 'my_call_user_func_array_safe');
166 | xhprof_enable(XHPROF_FLAGS_MEMORY, $xhprof_ignored_functions);
167 | test_my_call_user_func_array_safe('foo_array');
168 | $output = xhprof_disable();
169 | echo "Part 5c output:\n";
170 | print_canonical($output);
171 | echo "\n";
172 |
173 | ?>
174 | --EXPECT--
175 | Part 1: Default Flags
176 | hello: this is a test
177 | hello: Arraytest
178 | Part 1 output:
179 | foo==>bar : ct= 2; wt=*;
180 | foo==>strlen : ct= 1; wt=*;
181 | foo_array==>bar : ct= 2; wt=*;
182 | foo_array==>strlen : ct= 1; wt=*;
183 | main() : ct= 1; wt=*;
184 | main()==>foo : ct= 1; wt=*;
185 | main()==>foo_array : ct= 1; wt=*;
186 | main()==>xhprof_disable : ct= 1; wt=*;
187 |
188 | Part 2a: Ignore call_user_func
189 | hello: user_func test
190 | Part 2a output:
191 | foo==>bar : ct= 2; wt=*;
192 | foo==>strlen : ct= 1; wt=*;
193 | main() : ct= 1; wt=*;
194 | main()==>test_call_user_func::test_call_user_func: ct= 1; wt=*;
195 | main()==>xhprof_disable : ct= 1; wt=*;
196 | test_call_user_func::test_call_user_func==>foo: ct= 1; wt=*;
197 |
198 | Part 2b: Standard profile without parameters
199 | hello: user_func test
200 | Part 2b output:
201 | call_user_func==>foo : ct= 1; wt=*;
202 | foo==>bar : ct= 2; wt=*;
203 | foo==>strlen : ct= 1; wt=*;
204 | main() : ct= 1; wt=*;
205 | main()==>test_call_user_func::test_call_user_func: ct= 1; wt=*;
206 | main()==>xhprof_disable : ct= 1; wt=*;
207 | test_call_user_func::test_call_user_func==>call_user_func: ct= 1; wt=*;
208 |
209 | Part 2c: Standard profile with empty array of ignored functions
210 | hello: user_func test
211 | Part 2c output:
212 | call_user_func==>foo : ct= 1; wt=*;
213 | foo==>bar : ct= 2; wt=*;
214 | foo==>strlen : ct= 1; wt=*;
215 | main() : ct= 1; wt=*;
216 | main()==>test_call_user_func::test_call_user_func: ct= 1; wt=*;
217 | main()==>xhprof_disable : ct= 1; wt=*;
218 | test_call_user_func::test_call_user_func==>call_user_func: ct= 1; wt=*;
219 |
220 | Part 3: Ignore call_user_func_array
221 | hello: calling foo_array
222 | Part 3 output:
223 | foo_array==>bar : cpu=*; ct= 2; wt=*;
224 | foo_array==>strlen : cpu=*; ct= 1; wt=*;
225 | main() : cpu=*; ct= 1; wt=*;
226 | main()==>test_call_user_func_array : cpu=*; ct= 1; wt=*;
227 | main()==>xhprof_disable : cpu=*; ct= 1; wt=*;
228 | test_call_user_func_array==>foo_array : cpu=*; ct= 1; wt=*;
229 |
230 | Part 4: Ignore my_call_user_func_safe
231 | hello: Array
232 | Part 4 output:
233 | foo==>bar : ct= 2; wt=*;
234 | foo==>strlen : ct= 1; wt=*;
235 | main() : ct= 1; wt=*;
236 | main()==>test_my_call_user_func_safe : ct= 1; wt=*;
237 | main()==>xhprof_disable : ct= 1; wt=*;
238 | test_my_call_user_func_safe==>foo : ct= 1; wt=*;
239 | test_my_call_user_func_safe==>is_callable: ct= 1; wt=*;
240 |
241 | Part 5a: Ignore my_call_user_func_array_safe and strlen
242 | hello: my_user_func_array_safetest
243 | Part 5a output:
244 | foo_array==>bar : ct= 2; mu=*; pmu=*; wt=*;
245 | main() : ct= 1; mu=*; pmu=*; wt=*;
246 | main()==>test_my_call_user_func_array_safe: ct= 1; mu=*; pmu=*; wt=*;
247 | main()==>xhprof_disable : ct= 1; mu=*; pmu=*; wt=*;
248 | test_my_call_user_func_array_safe==>foo_array: ct= 1; mu=*; pmu=*; wt=*;
249 | test_my_call_user_func_array_safe==>is_callable: ct= 1; mu=*; pmu=*; wt=*;
250 |
251 | Part 5b: Profile call_user_func_array and my_call_user_func_array_safe
252 | hello: my_user_func_array_safetest
253 | Part 5b output:
254 | call_user_func_array==>foo_array : ct= 1; mu=*; pmu=*; wt=*;
255 | foo_array==>bar : ct= 2; mu=*; pmu=*; wt=*;
256 | foo_array==>strlen : ct= 1; mu=*; pmu=*; wt=*;
257 | main() : ct= 1; mu=*; pmu=*; wt=*;
258 | main()==>test_my_call_user_func_array_safe: ct= 1; mu=*; pmu=*; wt=*;
259 | main()==>xhprof_disable : ct= 1; mu=*; pmu=*; wt=*;
260 | my_call_user_func_array_safe==>call_user_func_array: ct= 1; mu=*; pmu=*; wt=*;
261 | my_call_user_func_array_safe==>is_callable: ct= 1; mu=*; pmu=*; wt=*;
262 | test_my_call_user_func_array_safe==>my_call_user_func_array_safe: ct= 1; mu=*; pmu=*; wt=*;
263 |
264 | Part 5c: Only ignore call_user_func_array
265 | hello: my_user_func_array_safetest
266 | Part 5c output:
267 | call_user_func_array==>foo_array : ct= 1; mu=*; pmu=*; wt=*;
268 | foo_array==>bar : ct= 2; mu=*; pmu=*; wt=*;
269 | foo_array==>strlen : ct= 1; mu=*; pmu=*; wt=*;
270 | main() : ct= 1; mu=*; pmu=*; wt=*;
271 | main()==>test_my_call_user_func_array_safe: ct= 1; mu=*; pmu=*; wt=*;
272 | main()==>xhprof_disable : ct= 1; mu=*; pmu=*; wt=*;
273 | test_my_call_user_func_array_safe==>call_user_func_array: ct= 1; mu=*; pmu=*; wt=*;
274 | test_my_call_user_func_array_safe==>is_callable: ct= 1; mu=*; pmu=*; wt=*;
275 |
--------------------------------------------------------------------------------
/xhprof_lib/utils/callgraph_utils.php:
--------------------------------------------------------------------------------
1 | 1,
26 | "gif" => 1,
27 | "png" => 1,
28 | "svg" => 1, // support scalable vector graphic
29 | "ps" => 1,
30 | );
31 |
32 | /**
33 | * Send an HTTP header with the response. You MUST use this function instead
34 | * of header() so that we can debug header issues because they're virtually
35 | * impossible to debug otherwise. If you try to commit header(), SVN will
36 | * reject your commit.
37 | *
38 | * @param string HTTP header name, like 'Location'
39 | * @param string HTTP header value, like 'http://www.example.com/'
40 | *
41 | */
42 | function xhprof_http_header($name, $value) {
43 |
44 | if (!$name) {
45 | xhprof_error('http_header usage');
46 | return null;
47 | }
48 |
49 | if (!is_string($value)) {
50 | xhprof_error('http_header value not a string');
51 | }
52 |
53 | header($name.': '.$value, true);
54 | }
55 |
56 | /**
57 | * Genearte and send MIME header for the output image to client browser.
58 | *
59 | * @author cjiang
60 | */
61 | function xhprof_generate_mime_header($type, $length) {
62 | switch ($type) {
63 | case 'jpg':
64 | $mime = 'image/jpeg';
65 | break;
66 | case 'gif':
67 | $mime = 'image/gif';
68 | break;
69 | case 'png':
70 | $mime = 'image/png';
71 | break;
72 | case 'svg':
73 | $mime = 'image/svg+xml'; // content type for scalable vector graphic
74 | break;
75 | case 'ps':
76 | $mime = 'application/postscript';
77 | default:
78 | $mime = false;
79 | }
80 |
81 | if ($mime) {
82 | xhprof_http_header('Content-type', $mime);
83 | xhprof_http_header('Content-length', (string)$length);
84 | }
85 | }
86 |
87 | /**
88 | * Generate image according to DOT script. This function will spawn a process
89 | * with "dot" command and pipe the "dot_script" to it and pipe out the
90 | * generated image content.
91 | *
92 | * @param dot_script, string, the script for DOT to generate the image.
93 | * @param type, one of the supported image types, see
94 | * $xhprof_legal_image_types.
95 | * @returns, binary content of the generated image on success. empty string on
96 | * failure.
97 | *
98 | * @author cjiang
99 | */
100 | function xhprof_generate_image_by_dot($dot_script, $type) {
101 | $descriptorspec = array(
102 | // stdin is a pipe that the child will read from
103 | 0 => array("pipe", "r"),
104 | // stdout is a pipe that the child will write to
105 | 1 => array("pipe", "w"),
106 | // stderr is a pipe that the child will write to
107 | 2 => array("pipe", "w")
108 | );
109 |
110 | $cmd = " dot -T".$type;
111 |
112 | $process = proc_open( $cmd, $descriptorspec, $pipes, sys_get_temp_dir(), array( 'PATH' => getenv( 'PATH' ) ) );
113 | if (is_resource($process)) {
114 | fwrite($pipes[0], $dot_script);
115 | fclose($pipes[0]);
116 |
117 | $output = stream_get_contents($pipes[1]);
118 |
119 | $err = stream_get_contents($pipes[2]);
120 | if (!empty($err)) {
121 | print "failed to execute cmd: \"$cmd\". stderr: `$err'\n";
122 | exit;
123 | }
124 |
125 | fclose($pipes[2]);
126 | fclose($pipes[1]);
127 | proc_close($process);
128 | return $output;
129 | }
130 | print "failed to execute cmd \"$cmd\"";
131 | exit();
132 | }
133 |
134 | /*
135 | * Get the children list of all nodes.
136 | */
137 | function xhprof_get_children_table($raw_data) {
138 | $children_table = array();
139 | foreach ($raw_data as $parent_child => $info) {
140 | list($parent, $child) = xhprof_parse_parent_child($parent_child);
141 | if (!isset($children_table[$parent])) {
142 | $children_table[$parent] = array($child);
143 | } else {
144 | $children_table[$parent][] = $child;
145 | }
146 | }
147 | return $children_table;
148 | }
149 |
150 | /**
151 | * Generate DOT script from the given raw phprof data.
152 | *
153 | * @param raw_data, phprof profile data.
154 | * @param threshold, float, the threshold value [0,1). The functions in the
155 | * raw_data whose exclusive wall times ratio are below the
156 | * threshold will be filtered out and won't apprear in the
157 | * generated image.
158 | * @param page, string(optional), the root node name. This can be used to
159 | * replace the 'main()' as the root node.
160 | * @param func, string, the focus function.
161 | * @param critical_path, bool, whether or not to display critical path with
162 | * bold lines.
163 | * @returns, string, the DOT script to generate image.
164 | *
165 | * @author cjiang
166 | */
167 | function xhprof_generate_dot_script($raw_data, $threshold, $source, $page,
168 | $func, $critical_path, $right=null,
169 | $left=null) {
170 |
171 | $max_width = 5;
172 | $max_height = 3.5;
173 | $max_fontsize = 35;
174 | $max_sizing_ratio = 20;
175 |
176 | $totals;
177 |
178 | if ($left === null) {
179 | // init_metrics($raw_data, null, null);
180 | }
181 | $sym_table = xhprof_compute_flat_info($raw_data, $totals);
182 |
183 | if ($critical_path) {
184 | $children_table = xhprof_get_children_table($raw_data);
185 | $node = "main()";
186 | $path = array();
187 | $path_edges = array();
188 | $visited = array();
189 | while ($node) {
190 | $visited[$node] = true;
191 | if (isset($children_table[$node])) {
192 | $max_child = null;
193 | foreach ($children_table[$node] as $child) {
194 |
195 | if (isset($visited[$child])) {
196 | continue;
197 | }
198 | if ($max_child === null ||
199 | abs($raw_data[xhprof_build_parent_child_key($node,
200 | $child)]["wt"]) >
201 | abs($raw_data[xhprof_build_parent_child_key($node,
202 | $max_child)]["wt"])) {
203 | $max_child = $child;
204 | }
205 | }
206 | if ($max_child !== null) {
207 | $path[$max_child] = true;
208 | $path_edges[xhprof_build_parent_child_key($node, $max_child)] = true;
209 | }
210 | $node = $max_child;
211 | } else {
212 | $node = null;
213 | }
214 | }
215 | }
216 |
217 | // if it is a benchmark callgraph, we make the benchmarked function the root.
218 | if ($source == "bm" && array_key_exists("main()", $sym_table)) {
219 | $total_times = $sym_table["main()"]["ct"];
220 | $remove_funcs = array("main()",
221 | "hotprofiler_disable",
222 | "call_user_func_array",
223 | "xhprof_disable");
224 |
225 | foreach ($remove_funcs as $cur_del_func) {
226 | if (array_key_exists($cur_del_func, $sym_table) &&
227 | $sym_table[$cur_del_func]["ct"] == $total_times) {
228 | unset($sym_table[$cur_del_func]);
229 | }
230 | }
231 | }
232 |
233 | // use the function to filter out irrelevant functions.
234 | if (!empty($func)) {
235 | $interested_funcs = array();
236 | foreach ($raw_data as $parent_child => $info) {
237 | list($parent, $child) = xhprof_parse_parent_child($parent_child);
238 | if ($parent == $func || $child == $func) {
239 | $interested_funcs[$parent] = 1;
240 | $interested_funcs[$child] = 1;
241 | }
242 | }
243 | foreach ($sym_table as $symbol => $info) {
244 | if (!array_key_exists($symbol, $interested_funcs)) {
245 | unset($sym_table[$symbol]);
246 | }
247 | }
248 | }
249 |
250 | $result = "digraph call_graph {\n";
251 |
252 | // Filter out functions whose exclusive time ratio is below threshold, and
253 | // also assign a unique integer id for each function to be generated. In the
254 | // meantime, find the function with the most exclusive time (potentially the
255 | // performance bottleneck).
256 | $cur_id = 0; $max_wt = 0;
257 | foreach ($sym_table as $symbol => $info) {
258 | if (empty($func) && abs($info["wt"] / $totals["wt"]) < $threshold) {
259 | unset($sym_table[$symbol]);
260 | continue;
261 | }
262 | if ($max_wt == 0 || $max_wt < abs($info["excl_wt"])) {
263 | $max_wt = abs($info["excl_wt"]);
264 | }
265 | $sym_table[$symbol]["id"] = $cur_id;
266 | $cur_id ++;
267 | }
268 |
269 | // Generate all nodes' information.
270 | foreach ($sym_table as $symbol => $info) {
271 | if ($info["excl_wt"] == 0) {
272 | $sizing_factor = $max_sizing_ratio;
273 | } else {
274 | $sizing_factor = $max_wt / abs($info["excl_wt"]) ;
275 | if ($sizing_factor > $max_sizing_ratio) {
276 | $sizing_factor = $max_sizing_ratio;
277 | }
278 | }
279 | $fillcolor = (($sizing_factor < 1.5) ?
280 | ", style=filled, fillcolor=red" : "");
281 |
282 | if ($critical_path) {
283 | // highlight nodes along critical path.
284 | if (!$fillcolor && array_key_exists($symbol, $path)) {
285 | $fillcolor = ", style=filled, fillcolor=yellow";
286 | }
287 | }
288 |
289 | $fontsize = ", fontsize="
290 | .(int)($max_fontsize / (($sizing_factor - 1) / 10 + 1));
291 |
292 | $width = ", width=".sprintf("%.1f", $max_width / $sizing_factor);
293 | $height = ", height=".sprintf("%.1f", $max_height / $sizing_factor);
294 |
295 | if ($symbol == "main()") {
296 | $shape = "octagon";
297 | $name = "Total: ".($totals["wt"] / 1000.0)." ms\\n";
298 | $name .= addslashes(isset($page) ? $page : $symbol);
299 | } else {
300 | $shape = "box";
301 | $name = addslashes($symbol)."\\nInc: ". sprintf("%.3f",$info["wt"] / 1000) .
302 | " ms (" . sprintf("%.1f%%", 100 * $info["wt"] / $totals["wt"]).")";
303 | }
304 | if ($left === null) {
305 | $label = ", label=\"".$name."\\nExcl: "
306 | .(sprintf("%.3f",$info["excl_wt"] / 1000.0))." ms ("
307 | .sprintf("%.1f%%", 100 * $info["excl_wt"] / $totals["wt"])
308 | . ")\\n".$info["ct"]." total calls\"";
309 | } else {
310 | if (isset($left[$symbol]) && isset($right[$symbol])) {
311 | $label = ", label=\"".addslashes($symbol).
312 | "\\nInc: ".(sprintf("%.3f",$left[$symbol]["wt"] / 1000.0))
313 | ." ms - "
314 | .(sprintf("%.3f",$right[$symbol]["wt"] / 1000.0))." ms = "
315 | .(sprintf("%.3f",$info["wt"] / 1000.0))." ms".
316 | "\\nExcl: "
317 | .(sprintf("%.3f",$left[$symbol]["excl_wt"] / 1000.0))
318 | ." ms - ".(sprintf("%.3f",$right[$symbol]["excl_wt"] / 1000.0))
319 | ." ms = ".(sprintf("%.3f",$info["excl_wt"] / 1000.0))." ms".
320 | "\\nCalls: ".(sprintf("%.3f",$left[$symbol]["ct"]))." - "
321 | .(sprintf("%.3f",$right[$symbol]["ct"]))." = "
322 | .(sprintf("%.3f",$info["ct"]))."\"";
323 | } else if (isset($left[$symbol])) {
324 | $label = ", label=\"".addslashes($symbol).
325 | "\\nInc: ".(sprintf("%.3f",$left[$symbol]["wt"] / 1000.0))
326 | ." ms - 0 ms = ".(sprintf("%.3f",$info["wt"] / 1000.0))
327 | ." ms"."\\nExcl: "
328 | .(sprintf("%.3f",$left[$symbol]["excl_wt"] / 1000.0))
329 | ." ms - 0 ms = "
330 | .(sprintf("%.3f",$info["excl_wt"] / 1000.0))." ms".
331 | "\\nCalls: ".(sprintf("%.3f",$left[$symbol]["ct"]))." - 0 = "
332 | .(sprintf("%.3f",$info["ct"]))."\"";
333 | } else {
334 | $label = ", label=\"".addslashes($symbol).
335 | "\\nInc: 0 ms - "
336 | .(sprintf("%.3f",$right[$symbol]["wt"] / 1000.0))
337 | ." ms = ".(sprintf("%.3f",$info["wt"] / 1000.0))." ms".
338 | "\\nExcl: 0 ms - "
339 | .(sprintf("%.3f",$right[$symbol]["excl_wt"] / 1000.0))
340 | ." ms = ".(sprintf("%.3f",$info["excl_wt"] / 1000.0))." ms".
341 | "\\nCalls: 0 - ".(sprintf("%.3f",$right[$symbol]["ct"]))
342 | ." = ".(sprintf("%.3f",$info["ct"]))."\"";
343 | }
344 | }
345 | $result .= "N" . $sym_table[$symbol]["id"];
346 | $result .= "[shape=$shape ".$label.$width
347 | .$height.$fontsize.$fillcolor."];\n";
348 | }
349 |
350 | // Generate all the edges' information.
351 | foreach ($raw_data as $parent_child => $info) {
352 | list($parent, $child) = xhprof_parse_parent_child($parent_child);
353 |
354 | if (isset($sym_table[$parent]) && isset($sym_table[$child]) &&
355 | (empty($func) ||
356 | (!empty($func) && ($parent == $func || $child == $func)))) {
357 |
358 | $label = $info["ct"] == 1 ? $info["ct"]." call" : $info["ct"]." calls";
359 |
360 | $headlabel = $sym_table[$child]["wt"] > 0 ?
361 | sprintf("%.1f%%", 100 * $info["wt"]
362 | / $sym_table[$child]["wt"])
363 | : "0.0%";
364 |
365 | $taillabel = ($sym_table[$parent]["wt"] > 0) ?
366 | sprintf("%.1f%%",
367 | 100 * $info["wt"] /
368 | ($sym_table[$parent]["wt"] - $sym_table["$parent"]["excl_wt"]))
369 | : "0.0%";
370 |
371 | $linewidth = 1;
372 | $arrow_size = 1;
373 |
374 | if ($critical_path &&
375 | isset($path_edges[xhprof_build_parent_child_key($parent, $child)])) {
376 | $linewidth = 10; $arrow_size = 2;
377 | }
378 |
379 | $result .= "N" . $sym_table[$parent]["id"] . " -> N"
380 | . $sym_table[$child]["id"];
381 | $result .= "[arrowsize=$arrow_size, color=grey, style=\"setlinewidth($linewidth)\","
382 | ." label=\""
383 | .$label."\", headlabel=\"".$headlabel
384 | ."\", taillabel=\"".$taillabel."\" ]";
385 | $result .= ";\n";
386 |
387 | }
388 | }
389 | $result = $result . "\n}";
390 |
391 | return $result;
392 | }
393 |
394 | function xhprof_render_diff_image($xhprof_runs_impl, $run1, $run2,
395 | $type, $threshold, $source) {
396 | $total1;
397 | $total2;
398 |
399 | $raw_data1 = $xhprof_runs_impl->get_run($run1, $source, $desc_unused);
400 | $raw_data2 = $xhprof_runs_impl->get_run($run2, $source, $desc_unused);
401 |
402 | // init_metrics($raw_data1, null, null);
403 | $children_table1 = xhprof_get_children_table($raw_data1);
404 | $children_table2 = xhprof_get_children_table($raw_data2);
405 | $symbol_tab1 = xhprof_compute_flat_info($raw_data1, $total1);
406 | $symbol_tab2 = xhprof_compute_flat_info($raw_data2, $total2);
407 | $run_delta = xhprof_compute_diff($raw_data1, $raw_data2);
408 | $script = xhprof_generate_dot_script($run_delta, $threshold, $source,
409 | null, null, true,
410 | $symbol_tab1, $symbol_tab2);
411 | $content = xhprof_generate_image_by_dot($script, $type);
412 |
413 | xhprof_generate_mime_header($type, strlen($content));
414 | echo $content;
415 | }
416 |
417 | /**
418 | * Generate image content from phprof run id.
419 | *
420 | * @param object $xhprof_runs_impl An object that implements
421 | * the iXHProfRuns interface
422 | * @param run_id, integer, the unique id for the phprof run, this is the
423 | * primary key for phprof database table.
424 | * @param type, string, one of the supported image types. See also
425 | * $xhprof_legal_image_types.
426 | * @param threshold, float, the threshold value [0,1). The functions in the
427 | * raw_data whose exclusive wall times ratio are below the
428 | * threshold will be filtered out and won't apprear in the
429 | * generated image.
430 | * @param func, string, the focus function.
431 | * @returns, string, the DOT script to generate image.
432 | *
433 | * @author cjiang
434 | */
435 | function xhprof_get_content_by_run($xhprof_runs_impl, $run_id, $type,
436 | $threshold, $func, $source,
437 | $critical_path) {
438 | if (!$run_id)
439 | return "";
440 |
441 | $raw_data = $xhprof_runs_impl->get_run($run_id, $source, $description);
442 | if (!$raw_data) {
443 | xhprof_error("Raw data is empty");
444 | return "";
445 | }
446 |
447 | $script = xhprof_generate_dot_script($raw_data, $threshold, $source,
448 | $description, $func, $critical_path);
449 |
450 | $content = xhprof_generate_image_by_dot($script, $type);
451 | return $content;
452 | }
453 |
454 | /**
455 | * Generate image from phprof run id and send it to client.
456 | *
457 | * @param object $xhprof_runs_impl An object that implements
458 | * the iXHProfRuns interface
459 | * @param run_id, integer, the unique id for the phprof run, this is the
460 | * primary key for phprof database table.
461 | * @param type, string, one of the supported image types. See also
462 | * $xhprof_legal_image_types.
463 | * @param threshold, float, the threshold value [0,1). The functions in the
464 | * raw_data whose exclusive wall times ratio are below the
465 | * threshold will be filtered out and won't apprear in the
466 | * generated image.
467 | * @param func, string, the focus function.
468 | * @param bool, does this run correspond to a PHProfLive run or a dev run?
469 | * @author cjiang
470 | */
471 | function xhprof_render_image($xhprof_runs_impl, $run_id, $type, $threshold,
472 | $func, $source, $critical_path) {
473 |
474 | $content = xhprof_get_content_by_run($xhprof_runs_impl, $run_id, $type,
475 | $threshold,
476 | $func, $source, $critical_path);
477 | if (!$content) {
478 | print "Error: either we can not find profile data for run_id ".$run_id
479 | ." or the threshold ".$threshold." is too small or you do not"
480 | ." have 'dot' image generation utility installed.";
481 | exit();
482 | }
483 |
484 | xhprof_generate_mime_header($type, strlen($content));
485 | echo $content;
486 | }
487 |
--------------------------------------------------------------------------------
/xhprof_html/jquery/jquery.autocomplete.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Autocomplete - jQuery plugin 1.0.2
3 | *
4 | * Copyright (c) 2007 Dylan Verheul, Dan G. Switzer, Anjesh Tuladhar, Jörn Zaefferer
5 | *
6 | * Dual licensed under the MIT and GPL licenses:
7 | * http://www.opensource.org/licenses/mit-license.php
8 | * http://www.gnu.org/licenses/gpl.html
9 | *
10 | * Revision: $Id: jquery.autocomplete.js,v 1.1.1.1 2009-03-17 18:35:18 kannan Exp $
11 | *
12 | */
13 |
14 | ;(function($) {
15 |
16 | $.fn.extend({
17 | autocomplete: function(urlOrData, options) {
18 | var isUrl = typeof urlOrData == "string";
19 | options = $.extend({}, $.Autocompleter.defaults, {
20 | url: isUrl ? urlOrData : null,
21 | data: isUrl ? null : urlOrData,
22 | delay: isUrl ? $.Autocompleter.defaults.delay : 10,
23 | max: options && !options.scroll ? 10 : 150
24 | }, options);
25 |
26 | // if highlight is set to false, replace it with a do-nothing function
27 | options.highlight = options.highlight || function(value) { return value; };
28 |
29 | // if the formatMatch option is not specified, then use formatItem for backwards compatibility
30 | options.formatMatch = options.formatMatch || options.formatItem;
31 |
32 | return this.each(function() {
33 | new $.Autocompleter(this, options);
34 | });
35 | },
36 | result: function(handler) {
37 | return this.bind("result", handler);
38 | },
39 | search: function(handler) {
40 | return this.trigger("search", [handler]);
41 | },
42 | flushCache: function() {
43 | return this.trigger("flushCache");
44 | },
45 | setOptions: function(options){
46 | return this.trigger("setOptions", [options]);
47 | },
48 | unautocomplete: function() {
49 | return this.trigger("unautocomplete");
50 | }
51 | });
52 |
53 | $.Autocompleter = function(input, options) {
54 |
55 | var KEY = {
56 | UP: 38,
57 | DOWN: 40,
58 | DEL: 46,
59 | TAB: 9,
60 | RETURN: 13,
61 | ESC: 27,
62 | COMMA: 188,
63 | PAGEUP: 33,
64 | PAGEDOWN: 34,
65 | BACKSPACE: 8
66 | };
67 |
68 | // Create $ object for input element
69 | var $input = $(input).attr("autocomplete", "off").addClass(options.inputClass);
70 |
71 | var timeout;
72 | var previousValue = "";
73 | var cache = $.Autocompleter.Cache(options);
74 | var hasFocus = 0;
75 | var lastKeyPressCode;
76 | var config = {
77 | mouseDownOnSelect: false
78 | };
79 | var select = $.Autocompleter.Select(options, input, selectCurrent, config);
80 |
81 | var blockSubmit;
82 |
83 | // prevent form submit in opera when selecting with return key
84 | $.browser.opera && $(input.form).bind("submit.autocomplete", function() {
85 | if (blockSubmit) {
86 | blockSubmit = false;
87 | return false;
88 | }
89 | });
90 |
91 | // only opera doesn't trigger keydown multiple times while pressed, others don't work with keypress at all
92 | $input.bind(($.browser.opera ? "keypress" : "keydown") + ".autocomplete", function(event) {
93 | // track last key pressed
94 | lastKeyPressCode = event.keyCode;
95 | switch(event.keyCode) {
96 |
97 | case KEY.UP:
98 | event.preventDefault();
99 | if ( select.visible() ) {
100 | select.prev();
101 | } else {
102 | onChange(0, true);
103 | }
104 | break;
105 |
106 | case KEY.DOWN:
107 | event.preventDefault();
108 | if ( select.visible() ) {
109 | select.next();
110 | } else {
111 | onChange(0, true);
112 | }
113 | break;
114 |
115 | case KEY.PAGEUP:
116 | event.preventDefault();
117 | if ( select.visible() ) {
118 | select.pageUp();
119 | } else {
120 | onChange(0, true);
121 | }
122 | break;
123 |
124 | case KEY.PAGEDOWN:
125 | event.preventDefault();
126 | if ( select.visible() ) {
127 | select.pageDown();
128 | } else {
129 | onChange(0, true);
130 | }
131 | break;
132 |
133 | // matches also semicolon
134 | case options.multiple && $.trim(options.multipleSeparator) == "," && KEY.COMMA:
135 | case KEY.TAB:
136 | case KEY.RETURN:
137 | if( selectCurrent() ) {
138 | // stop default to prevent a form submit, Opera needs special handling
139 | event.preventDefault();
140 | blockSubmit = true;
141 | return false;
142 | }
143 | break;
144 |
145 | case KEY.ESC:
146 | select.hide();
147 | break;
148 |
149 | default:
150 | clearTimeout(timeout);
151 | timeout = setTimeout(onChange, options.delay);
152 | break;
153 | }
154 | }).focus(function(){
155 | // track whether the field has focus, we shouldn't process any
156 | // results if the field no longer has focus
157 | hasFocus++;
158 | }).blur(function() {
159 | hasFocus = 0;
160 | if (!config.mouseDownOnSelect) {
161 | hideResults();
162 | }
163 | }).click(function() {
164 | // show select when clicking in a focused field
165 | if ( hasFocus++ > 1 && !select.visible() ) {
166 | onChange(0, true);
167 | }
168 | }).bind("search", function() {
169 | // TODO why not just specifying both arguments?
170 | var fn = (arguments.length > 1) ? arguments[1] : null;
171 | function findValueCallback(q, data) {
172 | var result;
173 | if( data && data.length ) {
174 | for (var i=0; i < data.length; i++) {
175 | if( data[i].result.toLowerCase() == q.toLowerCase() ) {
176 | result = data[i];
177 | break;
178 | }
179 | }
180 | }
181 | if( typeof fn == "function" ) fn(result);
182 | else $input.trigger("result", result && [result.data, result.value]);
183 | }
184 | $.each(trimWords($input.val()), function(i, value) {
185 | request(value, findValueCallback, findValueCallback);
186 | });
187 | }).bind("flushCache", function() {
188 | cache.flush();
189 | }).bind("setOptions", function() {
190 | $.extend(options, arguments[1]);
191 | // if we've updated the data, repopulate
192 | if ( "data" in arguments[1] )
193 | cache.populate();
194 | }).bind("unautocomplete", function() {
195 | select.unbind();
196 | $input.unbind();
197 | $(input.form).unbind(".autocomplete");
198 | });
199 |
200 |
201 | function selectCurrent() {
202 | var selected = select.selected();
203 | if( !selected )
204 | return false;
205 |
206 | var v = selected.result;
207 | previousValue = v;
208 |
209 | if ( options.multiple ) {
210 | var words = trimWords($input.val());
211 | if ( words.length > 1 ) {
212 | v = words.slice(0, words.length - 1).join( options.multipleSeparator ) + options.multipleSeparator + v;
213 | }
214 | v += options.multipleSeparator;
215 | }
216 |
217 | $input.val(v);
218 | hideResultsNow();
219 | $input.trigger("result", [selected.data, selected.value]);
220 | return true;
221 | }
222 |
223 | function onChange(crap, skipPrevCheck) {
224 | if( lastKeyPressCode == KEY.DEL ) {
225 | select.hide();
226 | return;
227 | }
228 |
229 | var currentValue = $input.val();
230 |
231 | if ( !skipPrevCheck && currentValue == previousValue )
232 | return;
233 |
234 | previousValue = currentValue;
235 |
236 | currentValue = lastWord(currentValue);
237 | if ( currentValue.length >= options.minChars) {
238 | $input.addClass(options.loadingClass);
239 | if (!options.matchCase)
240 | currentValue = currentValue.toLowerCase();
241 | request(currentValue, receiveData, hideResultsNow);
242 | } else {
243 | stopLoading();
244 | select.hide();
245 | }
246 | };
247 |
248 | function trimWords(value) {
249 | if ( !value ) {
250 | return [""];
251 | }
252 | var words = value.split( options.multipleSeparator );
253 | var result = [];
254 | $.each(words, function(i, value) {
255 | if ( $.trim(value) )
256 | result[i] = $.trim(value);
257 | });
258 | return result;
259 | }
260 |
261 | function lastWord(value) {
262 | if ( !options.multiple )
263 | return value;
264 | var words = trimWords(value);
265 | return words[words.length - 1];
266 | }
267 |
268 | // fills in the input box w/the first match (assumed to be the best match)
269 | // q: the term entered
270 | // sValue: the first matching result
271 | function autoFill(q, sValue){
272 | // autofill in the complete box w/the first match as long as the user hasn't entered in more data
273 | // if the last user key pressed was backspace, don't autofill
274 | if( options.autoFill && (lastWord($input.val()).toLowerCase() == q.toLowerCase()) && lastKeyPressCode != KEY.BACKSPACE ) {
275 | // fill in the value (keep the case the user has typed)
276 | $input.val($input.val() + sValue.substring(lastWord(previousValue).length));
277 | // select the portion of the value not typed by the user (so the next character will erase)
278 | $.Autocompleter.Selection(input, previousValue.length, previousValue.length + sValue.length);
279 | }
280 | };
281 |
282 | function hideResults() {
283 | clearTimeout(timeout);
284 | timeout = setTimeout(hideResultsNow, 200);
285 | };
286 |
287 | function hideResultsNow() {
288 | var wasVisible = select.visible();
289 | select.hide();
290 | clearTimeout(timeout);
291 | stopLoading();
292 | if (options.mustMatch) {
293 | // call search and run callback
294 | $input.search(
295 | function (result){
296 | // if no value found, clear the input box
297 | if( !result ) {
298 | if (options.multiple) {
299 | var words = trimWords($input.val()).slice(0, -1);
300 | $input.val( words.join(options.multipleSeparator) + (words.length ? options.multipleSeparator : "") );
301 | }
302 | else
303 | $input.val( "" );
304 | }
305 | }
306 | );
307 | }
308 | if (wasVisible)
309 | // position cursor at end of input field
310 | $.Autocompleter.Selection(input, input.value.length, input.value.length);
311 | };
312 |
313 | function receiveData(q, data) {
314 | if ( data && data.length && hasFocus ) {
315 | stopLoading();
316 | select.display(data, q);
317 | autoFill(q, data[0].value);
318 | select.show();
319 | } else {
320 | hideResultsNow();
321 | }
322 | };
323 |
324 | function request(term, success, failure) {
325 | if (!options.matchCase)
326 | term = term.toLowerCase();
327 | var data = cache.load(term);
328 | // recieve the cached data
329 | if (data && data.length) {
330 | success(term, data);
331 | // if an AJAX url has been supplied, try loading the data now
332 | } else if( (typeof options.url == "string") && (options.url.length > 0) ){
333 |
334 | var extraParams = {
335 | timestamp: +new Date()
336 | };
337 | $.each(options.extraParams, function(key, param) {
338 | extraParams[key] = typeof param == "function" ? param() : param;
339 | });
340 |
341 | $.ajax({
342 | // try to leverage ajaxQueue plugin to abort previous requests
343 | mode: "abort",
344 | // limit abortion to this input
345 | port: "autocomplete" + input.name,
346 | dataType: options.dataType,
347 | url: options.url,
348 | data: $.extend({
349 | q: lastWord(term),
350 | limit: options.max
351 | }, extraParams),
352 | success: function(data) {
353 | var parsed = options.parse && options.parse(data) || parse(data);
354 | cache.add(term, parsed);
355 | success(term, parsed);
356 | }
357 | });
358 | } else {
359 | // if we have a failure, we need to empty the list -- this prevents the the [TAB] key from selecting the last successful match
360 | select.emptyList();
361 | failure(term);
362 | }
363 | };
364 |
365 | function parse(data) {
366 | var parsed = [];
367 | var rows = data.split("\n");
368 | for (var i=0; i < rows.length; i++) {
369 | var row = $.trim(rows[i]);
370 | if (row) {
371 | row = row.split("|");
372 | parsed[parsed.length] = {
373 | data: row,
374 | value: row[0],
375 | result: options.formatResult && options.formatResult(row, row[0]) || row[0]
376 | };
377 | }
378 | }
379 | return parsed;
380 | };
381 |
382 | function stopLoading() {
383 | $input.removeClass(options.loadingClass);
384 | };
385 |
386 | };
387 |
388 | $.Autocompleter.defaults = {
389 | inputClass: "ac_input",
390 | resultsClass: "ac_results",
391 | loadingClass: "ac_loading",
392 | minChars: 1,
393 | delay: 400,
394 | matchCase: false,
395 | matchSubset: true,
396 | matchContains: false,
397 | cacheLength: 10,
398 | max: 100,
399 | mustMatch: false,
400 | extraParams: {},
401 | selectFirst: true,
402 | formatItem: function(row) { return row[0]; },
403 | formatMatch: null,
404 | autoFill: false,
405 | width: 0,
406 | multiple: false,
407 | multipleSeparator: ", ",
408 | highlight: function(value, term) {
409 | return value.replace(new RegExp("(?![^&;]+;)(?!<[^<>]*)(" + term.replace(/([\^\$\(\)\[\]\{\}\*\.\+\?\|\\])/gi, "\\$1") + ")(?![^<>]*>)(?![^&;]+;)", "gi"), "$1");
410 | },
411 | scroll: true,
412 | scrollHeight: 180
413 | };
414 |
415 | $.Autocompleter.Cache = function(options) {
416 |
417 | var data = {};
418 | var length = 0;
419 |
420 | function matchSubset(s, sub) {
421 | if (!options.matchCase)
422 | s = s.toLowerCase();
423 | var i = s.indexOf(sub);
424 | if (i == -1) return false;
425 | return i == 0 || options.matchContains;
426 | };
427 |
428 | function add(q, value) {
429 | if (length > options.cacheLength){
430 | flush();
431 | }
432 | if (!data[q]){
433 | length++;
434 | }
435 | data[q] = value;
436 | }
437 |
438 | function populate(){
439 | if( !options.data ) return false;
440 | // track the matches
441 | var stMatchSets = {},
442 | nullData = 0;
443 |
444 | // no url was specified, we need to adjust the cache length to make sure it fits the local data store
445 | if( !options.url ) options.cacheLength = 1;
446 |
447 | // track all options for minChars = 0
448 | stMatchSets[""] = [];
449 |
450 | // loop through the array and create a lookup structure
451 | for ( var i = 0, ol = options.data.length; i < ol; i++ ) {
452 | var rawValue = options.data[i];
453 | // if rawValue is a string, make an array otherwise just reference the array
454 | rawValue = (typeof rawValue == "string") ? [rawValue] : rawValue;
455 |
456 | var value = options.formatMatch(rawValue, i+1, options.data.length);
457 | if ( value === false )
458 | continue;
459 |
460 | var firstChar = value.charAt(0).toLowerCase();
461 | // if no lookup array for this character exists, look it up now
462 | if( !stMatchSets[firstChar] )
463 | stMatchSets[firstChar] = [];
464 |
465 | // if the match is a string
466 | var row = {
467 | value: value,
468 | data: rawValue,
469 | result: options.formatResult && options.formatResult(rawValue) || value
470 | };
471 |
472 | // push the current match into the set list
473 | stMatchSets[firstChar].push(row);
474 |
475 | // keep track of minChars zero items
476 | if ( nullData++ < options.max ) {
477 | stMatchSets[""].push(row);
478 | }
479 | };
480 |
481 | // add the data items to the cache
482 | $.each(stMatchSets, function(i, value) {
483 | // increase the cache size
484 | options.cacheLength++;
485 | // add to the cache
486 | add(i, value);
487 | });
488 | }
489 |
490 | // populate any existing data
491 | setTimeout(populate, 25);
492 |
493 | function flush(){
494 | data = {};
495 | length = 0;
496 | }
497 |
498 | return {
499 | flush: flush,
500 | add: add,
501 | populate: populate,
502 | load: function(q) {
503 | if (!options.cacheLength || !length)
504 | return null;
505 | /*
506 | * if dealing w/local data and matchContains than we must make sure
507 | * to loop through all the data collections looking for matches
508 | */
509 | if( !options.url && options.matchContains ){
510 | // track all matches
511 | var csub = [];
512 | // loop through all the data grids for matches
513 | for( var k in data ){
514 | // don't search through the stMatchSets[""] (minChars: 0) cache
515 | // this prevents duplicates
516 | if( k.length > 0 ){
517 | var c = data[k];
518 | $.each(c, function(i, x) {
519 | // if we've got a match, add it to the array
520 | if (matchSubset(x.value, q)) {
521 | csub.push(x);
522 | }
523 | });
524 | }
525 | }
526 | return csub;
527 | } else
528 | // if the exact item exists, use it
529 | if (data[q]){
530 | return data[q];
531 | } else
532 | if (options.matchSubset) {
533 | for (var i = q.length - 1; i >= options.minChars; i--) {
534 | var c = data[q.substr(0, i)];
535 | if (c) {
536 | var csub = [];
537 | $.each(c, function(i, x) {
538 | if (matchSubset(x.value, q)) {
539 | csub[csub.length] = x;
540 | }
541 | });
542 | return csub;
543 | }
544 | }
545 | }
546 | return null;
547 | }
548 | };
549 | };
550 |
551 | $.Autocompleter.Select = function (options, input, select, config) {
552 | var CLASSES = {
553 | ACTIVE: "ac_over"
554 | };
555 |
556 | var listItems,
557 | active = -1,
558 | data,
559 | term = "",
560 | needsInit = true,
561 | element,
562 | list;
563 |
564 | // Create results
565 | function init() {
566 | if (!needsInit)
567 | return;
568 | element = $("")
569 | .hide()
570 | .addClass(options.resultsClass)
571 | .css("position", "absolute")
572 | .appendTo(document.body);
573 |
574 | list = $("").appendTo(element).mouseover( function(event) {
575 | if(target(event).nodeName && target(event).nodeName.toUpperCase() == 'LI') {
576 | active = $("li", list).removeClass(CLASSES.ACTIVE).index(target(event));
577 | $(target(event)).addClass(CLASSES.ACTIVE);
578 | }
579 | }).click(function(event) {
580 | $(target(event)).addClass(CLASSES.ACTIVE);
581 | select();
582 | // TODO provide option to avoid setting focus again after selection? useful for cleanup-on-focus
583 | input.focus();
584 | return false;
585 | }).mousedown(function() {
586 | config.mouseDownOnSelect = true;
587 | }).mouseup(function() {
588 | config.mouseDownOnSelect = false;
589 | });
590 |
591 | if( options.width > 0 )
592 | element.css("width", options.width);
593 |
594 | needsInit = false;
595 | }
596 |
597 | function target(event) {
598 | var element = event.target;
599 | while(element && element.tagName != "LI")
600 | element = element.parentNode;
601 | // more fun with IE, sometimes event.target is empty, just ignore it then
602 | if(!element)
603 | return [];
604 | return element;
605 | }
606 |
607 | function moveSelect(step) {
608 | listItems.slice(active, active + 1).removeClass(CLASSES.ACTIVE);
609 | movePosition(step);
610 | var activeItem = listItems.slice(active, active + 1).addClass(CLASSES.ACTIVE);
611 | if(options.scroll) {
612 | var offset = 0;
613 | listItems.slice(0, active).each(function() {
614 | offset += this.offsetHeight;
615 | });
616 | if((offset + activeItem[0].offsetHeight - list.scrollTop()) > list[0].clientHeight) {
617 | list.scrollTop(offset + activeItem[0].offsetHeight - list.innerHeight());
618 | } else if(offset < list.scrollTop()) {
619 | list.scrollTop(offset);
620 | }
621 | }
622 | };
623 |
624 | function movePosition(step) {
625 | active += step;
626 | if (active < 0) {
627 | active = listItems.size() - 1;
628 | } else if (active >= listItems.size()) {
629 | active = 0;
630 | }
631 | }
632 |
633 | function limitNumberOfItems(available) {
634 | return options.max && options.max < available
635 | ? options.max
636 | : available;
637 | }
638 |
639 | function fillList() {
640 | list.empty();
641 | var max = limitNumberOfItems(data.length);
642 | for (var i=0; i < max; i++) {
643 | if (!data[i])
644 | continue;
645 | var formatted = options.formatItem(data[i].data, i+1, max, data[i].value, term);
646 | if ( formatted === false )
647 | continue;
648 | var li = $("").html( options.highlight(formatted, term) ).addClass(i%2 == 0 ? "ac_even" : "ac_odd").appendTo(list)[0];
649 | $.data(li, "ac_data", data[i]);
650 | }
651 | listItems = list.find("li");
652 | if ( options.selectFirst ) {
653 | listItems.slice(0, 1).addClass(CLASSES.ACTIVE);
654 | active = 0;
655 | }
656 | // apply bgiframe if available
657 | if ( $.fn.bgiframe )
658 | list.bgiframe();
659 | }
660 |
661 | return {
662 | display: function(d, q) {
663 | init();
664 | data = d;
665 | term = q;
666 | fillList();
667 | },
668 | next: function() {
669 | moveSelect(1);
670 | },
671 | prev: function() {
672 | moveSelect(-1);
673 | },
674 | pageUp: function() {
675 | if (active != 0 && active - 8 < 0) {
676 | moveSelect( -active );
677 | } else {
678 | moveSelect(-8);
679 | }
680 | },
681 | pageDown: function() {
682 | if (active != listItems.size() - 1 && active + 8 > listItems.size()) {
683 | moveSelect( listItems.size() - 1 - active );
684 | } else {
685 | moveSelect(8);
686 | }
687 | },
688 | hide: function() {
689 | element && element.hide();
690 | listItems && listItems.removeClass(CLASSES.ACTIVE);
691 | active = -1;
692 | },
693 | visible : function() {
694 | return element && element.is(":visible");
695 | },
696 | current: function() {
697 | return this.visible() && (listItems.filter("." + CLASSES.ACTIVE)[0] || options.selectFirst && listItems[0]);
698 | },
699 | show: function() {
700 | var offset = $(input).offset();
701 | element.css({
702 | width: typeof options.width == "string" || options.width > 0 ? options.width : $(input).width(),
703 | top: offset.top + input.offsetHeight,
704 | left: offset.left
705 | }).show();
706 | if(options.scroll) {
707 | list.scrollTop(0);
708 | list.css({
709 | maxHeight: options.scrollHeight,
710 | overflow: 'auto'
711 | });
712 |
713 | if($.browser.msie && typeof document.body.style.maxHeight === "undefined") {
714 | var listHeight = 0;
715 | listItems.each(function() {
716 | listHeight += this.offsetHeight;
717 | });
718 | var scrollbarsVisible = listHeight > options.scrollHeight;
719 | list.css('height', scrollbarsVisible ? options.scrollHeight : listHeight );
720 | if (!scrollbarsVisible) {
721 | // IE doesn't recalculate width when scrollbar disappears
722 | listItems.width( list.width() - parseInt(listItems.css("padding-left")) - parseInt(listItems.css("padding-right")) );
723 | }
724 | }
725 |
726 | }
727 | },
728 | selected: function() {
729 | var selected = listItems && listItems.filter("." + CLASSES.ACTIVE).removeClass(CLASSES.ACTIVE);
730 | return selected && selected.length && $.data(selected[0], "ac_data");
731 | },
732 | emptyList: function (){
733 | list && list.empty();
734 | },
735 | unbind: function() {
736 | element && element.remove();
737 | }
738 | };
739 | };
740 |
741 | $.Autocompleter.Selection = function(field, start, end) {
742 | if( field.createTextRange ){
743 | var selRange = field.createTextRange();
744 | selRange.collapse(true);
745 | selRange.moveStart("character", start);
746 | selRange.moveEnd("character", end);
747 | selRange.select();
748 | } else if( field.setSelectionRange ){
749 | field.setSelectionRange(start, end);
750 | } else {
751 | if( field.selectionStart ){
752 | field.selectionStart = start;
753 | field.selectionEnd = end;
754 | }
755 | }
756 | field.focus();
757 | };
758 |
759 | })(jQuery);
--------------------------------------------------------------------------------
/xhprof_html/docs/index.html:
--------------------------------------------------------------------------------
1 |
2 |
XHProf Documentation (Draft)
7 |
8 | Contents
9 |
10 |
30 |
31 |
32 |
Introduction
33 |
34 | xhprof. XHProf has a simple HTML based user
41 | interface (written in PHP). The browser based UI for viewing profiler
42 | results makes it easy to view results or to share results with peers.
43 | A callgraph image view is also supported.
44 |
45 |
70 |
86 |
87 |
88 |
71 |
72 |
85 | XHProf Overview
95 |
96 |
99 |
167 |
168 |
111 |
112 |
119 |
120 | include, include_once, require and
161 | require_once operations as if they were functions. The name of
162 | the file being included is used to generate the name for these "fake" functions.
164 |
165 |
166 | Terminology
169 |
170 |
171 |
184 | Naming convention for special functions
185 |
186 |
187 |
219 |
220 |
221 | main(): a fictitious function that is at the root of the call graph.
188 |
189 |
190 | load::<filename>
191 | and run_init::<filename>:
192 |
193 | include/require operations as
194 | function calls.
195 |
196 |
200 |
201 |
211 |
212 | load::lib/common.php - This represents the work done by the
202 | interpreter to compile/load the file. [Note: If you are using a PHP
203 | opcode cache like APC, then the compile only happens on a cache miss
204 | in APC.]
205 |
206 | run_init::lib/common.php - This represents
207 | initialization code executed at the file scope as a result of the
208 | include operation.
209 |
210 | foo@<n>: Implies that this is a
213 | recursive invocation of foo(), where <n> represents
214 | the recursion depth. The recursion may be direct (such as due to
215 | foo() --> foo()), or indirect (such as
216 | due to foo() --> goo() --> foo()).
217 |
218 | Limitations
222 |
223 |
242 | Say you have:
243 | 1 call from a() --> c()
244 | 1 call from b() --> c()
245 | 50 calls from c() --> d()
246 |
247 |
248 | Installing the XHProf Extension
258 |
259 |
283 |
284 |
262 |
263 | xhprof on Linux/FreeBSD so far.
265 |
266 | xhprof only works on x86 architecture.
272 | Also, since RDTSC values may not be synchronized across CPUs,
273 | xhprof binds the program to a single CPU during the
274 | profiling period.
275 |
276 |
289 | % cd <xhprof_source_directory>/extension/
290 | % phpize
291 | % ./configure --with-php-config=<path to php-config>
292 | % make
293 | % make install
294 | % make test
295 |
296 |
297 |
298 |
303 | [xhprof]
304 | extension=xhprof.so
305 | ;
306 | ; directory used by default implementation of the iXHProfRuns
307 | ; interface (namely, the XHProfRuns_Default class) for storing
308 | ; XHProf runs.
309 | ;
310 | xhprof.output_dir=<directory_for_storing_xhprof_runs>
311 |
312 |
313 |
314 | Profiling using XHProf
315 |
316 |
319 |
321 | <?php
322 |
323 | function bar($x) {
324 | if ($x > 0) {
325 | bar($x - 1);
326 | }
327 | }
328 |
329 | function foo() {
330 | for ($idx = 0; $idx < 2; $idx++) {
331 | bar($idx);
332 | $x = strlen("abc");
333 | }
334 | }
335 |
336 | // start profiling
337 | xhprof_enable();
338 |
339 | // run program
340 | foo();
341 |
342 | // stop profiler
343 | $xhprof_data = xhprof_disable();
344 |
345 | // display raw xhprof data for the profiler run
346 | print_r($xhprof_data);
347 |
348 |
349 |
350 |
353 | % php -dextension=xhprof.so foo.php
354 |
355 |
356 |
359 | Array
360 | (
361 | [foo==>bar] => Array
362 | (
363 | [ct] => 2 # 2 calls to bar() from foo()
364 | [wt] => 27 # inclusive time in bar() when called from foo()
365 | )
366 |
367 | [foo==>strlen] => Array
368 | (
369 | [ct] => 2
370 | [wt] => 2
371 | )
372 |
373 | [bar==>bar@1] => Array # a recursive call to bar()
374 | (
375 | [ct] => 1
376 | [wt] => 2
377 | )
378 |
379 | [main()==>foo] => Array
380 | (
381 | [ct] => 1
382 | [wt] => 74
383 | )
384 |
385 | [main()==>xhprof_disable] => Array
386 | (
387 | [ct] => 1
388 | [wt] => 0
389 | )
390 |
391 | [main()] => Array # fake symbol representing root
392 | (
393 | [ct] => 1
394 | [wt] => 83
395 | )
396 |
397 | )
398 |
399 |
400 |
409 | xhprof_enable();
410 |
411 | in the above program with, for example:
412 |
413 | xhprof_enable(XHPROF_FLAGS_CPU + XHPROF_FLAGS_MEMORY);
414 |
415 |
416 |
419 | Array
420 | (
421 | [foo==>bar] => Array
422 | (
423 | [ct] => 2 # number of calls to bar() from foo()
424 | [wt] => 37 # time in bar() when called from foo()
425 | [cpu] => 0 # cpu time in bar() when called from foo()
426 | [mu] => 2208 # change in PHP memory usage in bar() when called from foo()
427 | [pmu] => 0 # change in PHP peak memory usage in bar() when called from foo()
428 | )
429 |
430 | [foo==>strlen] => Array
431 | (
432 | [ct] => 2
433 | [wt] => 3
434 | [cpu] => 0
435 | [mu] => 624
436 | [pmu] => 0
437 | )
438 |
439 | [bar==>bar@1] => Array
440 | (
441 | [ct] => 1
442 | [wt] => 2
443 | [cpu] => 0
444 | [mu] => 856
445 | [pmu] => 0
446 | )
447 |
448 | [main()==>foo] => Array
449 | (
450 | [ct] => 1
451 | [wt] => 104
452 | [cpu] => 0
453 | [mu] => 4168
454 | [pmu] => 0
455 | )
456 |
457 | [main()==>xhprof_disable] => Array
458 | (
459 | [ct] => 1
460 | [wt] => 1
461 | [cpu] => 0
462 | [mu] => 344
463 | [pmu] => 0
464 | )
465 |
466 | [main()] => Array
467 | (
468 | [ct] => 1
469 | [wt] => 139
470 | [cpu] => 0
471 | [mu] => 5936
472 | [pmu] => 0
473 | )
474 |
475 | )
476 |
477 |
478 | strlen) are
481 | profiled. If you do not want to profile builtin functions (to either
482 | reduce the overhead of profiling further or size of generated raw
483 | data), you can use the XHPROF_FLAGS_NO_BUILTINS
484 | flag as in for example:
485 |
486 |
487 | // do not profile builtin functions
488 | xhprof_enable(XHPROF_FLAGS_NO_BUILTINS);
489 |
490 |
491 |
492 | call_user_func and
498 | call_user_func_array. These intermediate functions
499 | unnecessarily complicate the call hierarchy and make the XHProf
500 | reports harder to interpret since they muddle the parent-child
501 | relationship for functions called indirectly.
502 |
503 | xhprof_enable.
505 | For example,
506 |
507 |
508 |
509 |
510 | // elapsed time profiling; ignore call_user_func* during profiling
511 | xhprof_enable(0,
512 | array('ignored_functions' => array('call_user_func',
513 | 'call_user_func_array')));
514 |
515 | or,
516 |
517 | // elapsed time + memory profiling; ignore call_user_func* during profiling
518 | xhprof_enable(XHPROF_FLAGS_MEMORY,
519 | array('ignored_functions' => array('call_user_func',
520 | 'call_user_func_array')));
521 |
522 |
523 |
524 |
525 | Setting up XHProf UI
528 |
529 |
530 |
531 |
532 |
695 |
696 | xhprof_html/ and xhprof_lib/.
535 |
536 | xhprof_html directory contains the 3 top-level PHP pages.
537 |
538 |
539 |
544 |
545 | index.php: For viewing a single run or diff report.
540 | callgraph.php: For viewing a callgraph of a XHProf run as an image.
541 | typeahead.php: Used implicitly for the function typeahead form
542 | on a XHProf report.
543 | xhprof_lib directory contains supporting code for
546 | display as well as analysis (computing flat profile info, computing
547 | diffs, aggregating data from multiple runs, etc.).
548 |
549 | xhprof_html/ directory is accessible from your web server, and that
551 | your web server is setup to serve PHP scripts.
552 |
553 | XHProfRuns_Default of the
576 | iXHProfRuns interface, a typical XHProf run
577 | followed by the save step might look something like:
578 |
579 |
580 |
581 | // start profiling
582 | xhprof_enable();
583 |
584 | // run program
585 | ....
586 |
587 | // stop profiler
588 | $xhprof_data = xhprof_disable();
589 |
590 | //
591 | // Saving the XHProf run
592 | // using the default implementation of iXHProfRuns.
593 | //
594 | include_once $XHPROF_ROOT . "/xhprof_lib/utils/xhprof_lib.php";
595 | include_once $XHPROF_ROOT . "/xhprof_lib/utils/xhprof_runs.php";
596 |
597 | $xhprof_runs = new XHProfRuns_Default();
598 |
599 | // Save the run under a namespace "xhprof_foo".
600 | //
601 | // **NOTE**:
602 | // By default save_run() will automatically generate a unique
603 | // run id for you. [You can override that behavior by passing
604 | // a run id (optional arg) to the save_run() method instead.]
605 | //
606 | $run_id = $xhprof_runs->save_run($xhprof_data, "xhprof_foo");
607 |
608 | echo "---------------\n".
609 | "Assuming you have set up the http based UI for \n".
610 | "XHProf at some address, you can view run at \n".
611 | "http://<xhprof-ui-address>/index.php?run=$run_id&source=xhprof_foo\n".
612 | "---------------\n";
613 |
614 |
615 |
616 | xhprof.output_dir INI parameter. The file's
618 | name might be something like
619 | 49bafaa3a3f66.xhprof_foo; the two parts being the
620 | run id ("49bafaa3a3f66") and the namespace ("xhprof_foo"). [If you
621 | want to create/assign run ids yourself (such as a database sequence
622 | number, or a timestamp), you can explicitly pass in the run id to the
623 | save_run method.
624 |
625 |
626 | XHProfRuns_Default.
636 | Change this line in the 3 files.
637 |
638 |
639 | $xhprof_runs_impl = new XHProfRuns_Default();
640 |
641 |
642 |
654 | http://<xhprof-ui-address>/index.php?run=<run_id>&source=<namespace>
655 |
656 |
657 |
659 | http://<xhprof-ui-address>/index.php?run=49bafaa3a3f66&source=xhprof_foo
660 |
661 |
662 |
663 |
669 | http://<xhprof-ui-address>/index.php?run1=<run_id1>&run2=<run_id2>&source=<namespace>
670 |
671 |
672 |
682 |
683 |
680 | http://<xhprof-ui-address>/index.php?run=1,2,3&source=benchmark
681 |
692 |
693 |
694 |
690 | http://<xhprof-ui-address>/index.php?run=1,2,3&wts=20,30,50&source=benchmark
691 | Notes on using XHProf in production
697 |
698 |
701 |
702 |
712 | // elapsed time profiling (default) + memory profiling
713 | xhprof_enable(XHPROF_FLAGS_MEMORY);
714 |
715 |
To profile say 1/10000 of your requests, instrument the beginning of 722 | your request processing with something along the lines of: 723 | 724 |
725 | if (mt_rand(1, 10000) == 1) {
726 | xhprof_enable(XHPROF_FLAGS_MEMORY);
727 | $xhprof_on = true;
728 | }
729 |
730 |
731 | At the end of the request (or in a request shutdown function), you might 732 | then do something like: 733 | 734 |
735 | if ($xhprof_on) {
736 | // stop profiler
737 | $xhprof_data = xhprof_disable();
738 |
739 | // save $xhprof_data somewhere (say a central DB)
740 | ...
741 | }
742 |
743 |
744 | You can then rollup/aggregate these individual profiles by time
745 | (e.g., 5 minutely/hourly/daily basis), page/request type,or other
746 | dimensions using xhprof_aggregate_runs().
747 |
748 |
749 |
750 |
751 |
The xhprof extension also provides a very light weight sampling 754 | mode. The sampling interval is 0.1 secs. Samples record the full 755 | function call stack. The sampling mode can be useful if an extremely 756 | low overhead means of doing performance monitoring and diagnostics is 757 | desired. 758 | 759 |
The relevant functions exposed by the extension for using the
760 | sampling mode are xhprof_sample_enable() and
761 | xhprof_sample_disable().
762 |
763 |
764 |
[TBD: more detailed documentation on sampling mode.] 765 | 766 | 767 |
The xhprof_lib/utils/xhprof_lib.php file contains
770 | additional library functions that can be used for manipulating/
771 | aggregating XHProf runs.
772 |
773 |
For example: 774 | 775 |
xhprof_aggregate_runs():
779 | can be used to aggregate multiple XHProf runs into a single run. This
780 | can be helpful for building a system-wide "function-level" performance
781 | monitoring tool using XHProf. [For example, you might to roll up
782 | XHProf runs sampled from production periodically to generate hourly,
783 | daily, reports.]
784 |
785 | xhprof_prune_run(): Aggregating large number of
786 | XHProf runs (especially if they correspond to different types of
787 | programs) can result in the callgraph size becoming too large. You can
788 | use xhprof_prune_run function to prune the callgraph data
789 | by editing out subtrees that account for a very small portion of the
790 | total time.
791 |
792 | xhprof_html/jquery subdirectory.
807 |
808 | The HTML-based navigational interface for browsing profiler results 817 | is inspired by that of a similar tool that exists for Oracle's stored 818 | procedure language, PL/SQL. But that's where the similarity ends; the 819 | internals of the profiler itself are quite different. 820 | 821 |
XHProf est un outil de profilage hiérarchique pour PHP. Il relève
38 | les appels au niveau des fonctions et mesure inclusivement et
39 | exclusivement des métriques telles que le temps écoulé
40 | la charge CPU ou l’usage de la mémoire. Un profil de fonction peut être divisé selon ses appelants, ou ses appelés. Le composant qui extrait les données brutes est écrit en C
41 | et implémenté telle une extension PHP Zend.
42 | xhprof. XHProf a une interface utilisateur simple en HTML, (écrite en PHP).
43 | L’interface permet de visualiser et de partager facilement le résultat des profilages dans un navigateur.
44 | Un rendu sous forme de graphique est également disponible.
45 |
46 |
Les rapports fournis par XHProf permettent souvent de mieux comprendre 47 | la structure du code qui est éxécuté. 48 | Le rendu hiérarchique des rapports permet par exemple de déterminer 49 | quelle chaîne d’appels mène à une fonction particulière. 50 | 51 |
XHProf propose également de comparer deux runs (résultat de profilage) 52 | pour analyser les différences ou aggréger les résultat de multiples runs afin 53 | d’analyser des données consolidées. 54 | Les comparaisons et aggrégations de données permettent surtout de visualiser des données plates 55 | 56 |
XHProf est un outil de profilage très léger. Pendant la phase de collecte 57 | Il garde une trace du nombre d’appels et des métriques inclusives viualisables en courbes dans le graphe d’appels dynamique d’un programme. 58 | Il calcule les métriques exclusives dans la phase de rapport. 59 | XHProf supporte les fonctions recursives en détectant les cycles dans la pile d’appels dès la capture des données et en utilisant un nom unique pour l’invocation principale.
60 | 61 |La nature légère d’XHProf, ses performances et ses possibilités de consolidations de données 62 | en font un outil taillé pour les environnements de production [Voir les notes sur l’usage en production.] 64 | 65 |
XHProfLive (qui ne fait pas partie de ce kit open source), par exemple, 69 | est un système de monitoring de performance utilsé chez Facebook et qui est bâti sur XHProf. 70 | XHProfLive récupère en permanence les données de profilage en production en lançant XHProf sur un échantillon de pages 71 | XHProfLive aggrège ensuite les données suivant des critères tels que le temps, type de pages, et peut aider à répondre à tout type de questions comme : 72 | Quel est le profil de la pile d’appel pour une page spécifique ? Quel est le coût de la méthode "foo" dans toutes les pages, ou sur une page spécifique ? quelles fonctions ont régressé le plus depuis une heure, un jour pou un mois ? Quel est l’historique des tendances, des temps d’executions pour une page ou une fonction … 73 | 74 |
Développé à l’origine par Facebook, XHProf est maintenant open source depuis mars 2009.
79 | 80 | 81 | 82 | 83 | 84 |XHProf offre: 87 | 88 |
Un résumé des appels de fonctions avec des informations telles que le nombre d’appels, 92 | inclusivement et exclusivement, les temps, la charge mémoire, et le temps processeur. 93 | 94 |
Pour chaque fonction, il fournit le détail des appels et le temps par 98 | parent (appelant) & enfant (appelé), tel que : 99 | 100 |
Vous pouvez comparer les données de deux appels à XHProf pour des raisons diverses; 113 | Pour voir ce qui cause une régression entre une version du code et une autre, 114 | Pour évaluer l’impact sur les performances d’une évolution dans le code … 115 | 116 |
Une comparaison de rapport prends deux runs en entrée et produit à la fois des informations différencielles au niveau de la fonction, mais aussi des informations hiérarchiques (séparation des différences par fonction parente/enfant) pour chaque fonction. 117 | 118 |
La vue tabulaire (copie d’écran) du rapport différentiel pointe les plus grosses améliorations et régressions. 120 | 121 |
Cliquer sur un nom de fonction dans la bue tabulaire du rapport différentiel, mène à la vue hiérarchique 122 | (ou vue parent/enfant) différentielle d’une fonction (copie d’écran). On peut ainsi avoir une séparation des différences par fonctions parent/enfant. 124 | 125 |
Les données du rapport peuvent également être visualisées sous forme de graphique. 129 | Cette vue permet de mettre en lumière les chemins crtiques du programme. 130 | 131 |
Le mode profilage mémoire d’XHProf aide à isoler les fonctions qui occupent trop de mémoire. 134 | 135 |
On ne peut pas dire qu’XHProf trace exactement chaque opération 136 | d’allocation/libération de mémoire, en effet il utilise un schéma simplistique; 137 | Il trace les hausses et les baisse de besoin en mémoire allouée à PHP à chaque entré ou sortie de fonction. 138 | Il trace aussi les hausses et baisses de pics mémoire alloués à chaque fonction PHP. 139 | 140 |
include, include_once, require and
141 | require_once comme si c’était des fonctions. Le nom du fichier inclus est utilisé pour nommer "fausses" fonctions.
143 |
144 |
145 | main(): Une fonction fictive qui est à la racine de la pile d’appel.
164 |
165 |
166 | load::<filename>
167 | et run_init::<filename>:
168 |
169 | XHProf trace les appels include/require comme des appels de fonction.
170 |
171 |
Par exemple, une inclusion include "lib/common.php"; va donner deux entrées pour XHProf : 172 | 173 |
load::lib/common.php - Cela représente le travail fait par l’interpréteur pour charger et compiler le fichier.
176 | [Note: Si vous utilisez un cache d’opcode PHP comme APC, alors la compilation intervient uniquement si le cahce est manquant dans APC.]
177 |
178 | run_init::lib/common.php - Cela répresente le code exécuté au niveau du fichier, soit le résultat de l’inclusion.
179 |
180 | foo@<n>: Implique un appel récursif de foo(), ou <n> représente le niveau de récursion.
183 | Cette récursion peut être directe comme foo() --> foo()), ou indirecte comme foo() --> goo() --> foo().
184 |
185 | Un vrai profileur hiérarchique trace toute la pile d’appel pour chaque donnée., et est capables de répondre aux questions comme : Quel était le coût du 3e appel de foo(), ou quel était le coût de bar() quand il était appelé par a()->b()->bar()? 191 | 192 |
193 | 194 |XHProf garde une trace d’un seul niveau dans le contexte de l’appel et est seulement capable de répondre aux questions à propos 195 | d’une fonction qui regarde un niveau en dessus ou en dessous. 196 | Il appraît que dans la majorité des cas c’est bien suffisant. 197 |
198 | 199 |Pour mieux comprendre, regaredez l’exemple suivant : 200 |
201 | 202 |203 | Vous avez: 204 | 1 appel de a() --> c() 205 | 1 appel de b() --> c() 206 | 50 appels de c() --> d() 207 |208 | 209 |
Quand XHProf peut vous dire que d() a été appelé par c() 50 fois, il ne peut pas vous dire 210 | combien d’appels dont dus à a() ou b(). 211 | [On peut imaginer que c’est peut être 25 pour a() et 25 pour b(), mais ce n’est pas nécéssairement vrai.] 212 |
213 | 214 |De toutes façons en pratique ce n’est pas vraiment une limitation. 215 |
216 | 217 |L’extension se trouve dans le sous-répertoire "extension/". 220 | 221 |
Note: Le portage pour Windows n’est pas encore implémenté. Nous avons testé XHProf sur Linux/FreeBSD.
224 | [NDT : Il existe un fork avec un portage Windows sur Github]
225 |
226 |
La version 0.9.2 et les précédentes sont aussi censées fonctionner sur Mac 227 | OS. [Cela a été testé sur Mac OS 10.5.] 228 | 229 |
Note: XHProf utilise les insctructions RDTSC (time stamp counter)
230 | pour implémenter un compteur de temps vraiment bas niveau. C’est pourquoi actuellement xhprof fonctionne uniquement sur une architecture x86.
231 | Aussi tant que les valeurs de RDTSC ne pourront pas être synchronisées entre plusieurs CPUs,
232 | xhprof n’en utilisera qu’un seul lors du profilage.
233 |
234 |
Le timer XHProf bzasé sur RDTSC ne fonctionen pas parfaitement si la techno 235 | SpeedStep est activée. Cette technologie est disponible sur certains processeurs Intel. 236 | [Note: Les Macs ont typiquement cette fonctionnalité d’activée par défaut, il faut donc la désactiver pour utiliser XHProf.] 237 | 238 |
Les étapes suivantes sont prévues pour un environnement Linux/Unix. 241 | 242 | 243 |
244 | % cd <repertoire_source_xhprof>/extension/ 245 | % phpize 246 | % ./configure --with-php-config=<chemin vers php-config> 247 | % make 248 | % make install 249 | % make test 250 |251 | 252 | 253 |
php.ini file: Vous pouvez mettre à jour votre fichier 254 | php.ini file afin qu’il charge automatiquement votre extension en ajoutant le code suivant : 255 | 256 |
257 | [xhprof] 258 | extension=xhprof.so 259 | ; 260 | ; répertoire utilisé par l’implémentation par défaut de l’interface iXHProfRuns 261 | ; (nommée, XHProfRuns_Default class) pour stocker les runs XHProf. 262 | ; 263 | xhprof.output_dir=<repertoire_pour_stocker_les_runs_xhprof> 264 |265 | 266 | 267 |
Test de génération de donées brutes avec l’exemple simple d’un programme tel que : 270 | 271 |
foo.php 273 |
274 | <?php
275 |
276 | function bar($x) {
277 | if ($x > 0) {
278 | bar($x - 1);
279 | }
280 | }
281 |
282 | function foo() {
283 | for ($idx = 0; $idx < 2; $idx++) {
284 | bar($idx);
285 | $x = strlen("abc");
286 | }
287 | }
288 |
289 | // début du profileur
290 | xhprof_enable();
291 |
292 | // début du programme
293 | foo();
294 |
295 | // attêt du profileur
296 | $xhprof_data = xhprof_disable();
297 |
298 | // affichage des données de profilage brutes
299 | print_r($xhprof_data);
300 |
301 |
302 |
303 | Lancez ce programme : 304 | 305 |
306 | % php -dextension=xhprof.so foo.php 307 |308 | 309 |
Vous devez avoir un résultat tel que : 310 | 311 |
312 | Array 313 | ( 314 | [foo==>bar] => Array 315 | ( 316 | [ct] => 2 # 2 appels de bar() depuis foo() 317 | [wt] => 27 # temps inclusif dans bar() quand il est appelé par foo() 318 | ) 319 | 320 | [foo==>strlen] => Array 321 | ( 322 | [ct] => 2 323 | [wt] => 2 324 | ) 325 | 326 | [bar==>bar@1] => Array # un appelrécursif à bar() 327 | ( 328 | [ct] => 1 329 | [wt] => 2 330 | ) 331 | 332 | [main()==>foo] => Array 333 | ( 334 | [ct] => 1 335 | [wt] => 74 336 | ) 337 | 338 | [main()==>xhprof_disable] => Array 339 | ( 340 | [ct] => 1 341 | [wt] => 0 342 | ) 343 | 344 | [main()] => Array # fausse fonction représentant la racine 345 | ( 346 | [ct] => 1 347 | [wt] => 83 348 | ) 349 | 350 | ) 351 |352 | 353 |
Note: Les données brutes contienent uniquement les métriques inclusives. 354 | Par exemple les données brutes du tableau de données temporelles represente les temps inclusifs en microsecondes. 355 | Les temps exclusifs sont calculés pour chaque fonction lors de la phase d’analyse et de rapport. 356 | 357 |
Note: Par défault suelemnt le nombre d’appel & et le temps passé sont profilés. 358 | Vous pouvez aussi profilerle temps CPU et/ou la charge mémoire. Remplacez, 359 | 360 |
361 | xhprof_enable(); 362 |363 | dans le programme précédent avec, par exemple : 364 |
365 | xhprof_enable(XHPROF_FLAGS_CPU + XHPROF_FLAGS_MEMORY); 366 |367 | 368 |
Vous aurez en sortie : 369 | 370 |
371 | Array 372 | ( 373 | [foo==>bar] => Array 374 | ( 375 | [ct] => 2 # nombre d’appel à bar() depuis foo() 376 | [wt] => 37 # tempas passé dans bar() quand appel de foo() 377 | [cpu] => 0 # temps cpu time dans bar() quand appel de foo() 378 | [mu] => 2208 # changement dans l’usage de la mémoire par PHP dans bar() quand appel de foo() 379 | [pmu] => 0 # changement dans l’usage de pic mémoire par PHP pour bar() quand appel de foo() 380 | ) 381 | 382 | [foo==>strlen] => Array 383 | ( 384 | [ct] => 2 385 | [wt] => 3 386 | [cpu] => 0 387 | [mu] => 624 388 | [pmu] => 0 389 | ) 390 | 391 | [bar==>bar@1] => Array 392 | ( 393 | [ct] => 1 394 | [wt] => 2 395 | [cpu] => 0 396 | [mu] => 856 397 | [pmu] => 0 398 | ) 399 | 400 | [main()==>foo] => Array 401 | ( 402 | [ct] => 1 403 | [wt] => 104 404 | [cpu] => 0 405 | [mu] => 4168 406 | [pmu] => 0 407 | ) 408 | 409 | [main()==>xhprof_disable] => Array 410 | ( 411 | [ct] => 1 412 | [wt] => 1 413 | [cpu] => 0 414 | [mu] => 344 415 | [pmu] => 0 416 | ) 417 | 418 | [main()] => Array 419 | ( 420 | [ct] => 1 421 | [wt] => 139 422 | [cpu] => 0 423 | [mu] => 5936 424 | [pmu] => 0 425 | ) 426 | 427 | ) 428 |429 | 430 |
Éviter les fonctions natives lors du profilage 431 | 432 |
Par défault les fonctions natives de PHP (comme strlen) sont profilées.
433 | Si vous ne voulez pas les profiler (pour simplifier le résultat et la taille des données brutes générées),
434 | Vous pouvez utiliser le drapeau XHPROF_FLAGS_NO_BUILTINS comme dans l’exemple ci-dessous :
435 |
436 |
437 | // ne pas profiler les fonctions natives 438 | xhprof_enable(XHPROF_FLAGS_NO_BUILTINS); 439 |440 | 441 | 442 |
Ignorer des fonctions spécfiques lors du profilage (0.9.2 ou plus récent) 443 | 444 |
À partir de la version 0.9.2 d’XHProf, vous pouvez spécifier une liste de
445 | fonctions à ignorer pendant le profilage. Cela vous permet de ne pas prendre en compte par exemple
446 | des fonctions utilisées pour des appels indirects comme call_user_func et call_user_func_array.
447 | Ces fonctions intermédiaires compliquent inutilement la hirarchie des appels et rendent plus ardue l’interprétation des rapports en brouillant les relations parent/enfant.
448 |
449 |
Pour spécifier cette liste de fonctions à ignorer durant le profilage, il suffit d’utiliser le second paramètre (optionnel) de xhprof_enable.
450 | Par exemple,
451 |
452 |
453 |
454 |
455 | // temps passé en profilage; ignore les appels de call_user_func* pendant le profilage
456 | xhprof_enable(0,
457 | array('ignored_functions' => array('call_user_func',
458 | 'call_user_func_array')));
459 |
460 | or,
461 |
462 | // tempas pasé en profilage + profilage mémoire; ignore call_user_func* durant le profilage
463 | xhprof_enable(XHPROF_FLAGS_MEMORY,
464 | array('ignored_functions' => array('call_user_func',
465 | 'call_user_func_array')));
466 |
467 |
468 |
469 |
470 | l’interface graphique d’XHProf est implémentée en PHP. Le code est divisé en deux sous-répertoires,
479 | xhprof_html/ and xhprof_lib/.
480 |
481 |
Le répertoire xhprof_html contient les 3 pages PHP principales.
482 |
483 |
index.php: Pour visualiser un run ou un différentiel entre deux runs.
485 | callgraph.php: Pour visualiser sous la forme de graphique avec un rendu en image.
486 | typeahead.php: Utilisé implicitement pour les fonctions de gestion de pile sur un rapport XHProf.
487 | Le répertoire xhprof_lib contient le code pour l’analyse et l’affichage.
490 | (calcul sur les informations de profilage, calcul des différentiels, aggrégation de données, etc.).
491 |
492 |
Configuration du server web : Vous devez vous assurer que le répertoire
493 | xhprof_html/ est accessible depuis le serveur web, et qu’il est configuré pour éxécuter des scripts PHP.
494 |
495 |
Gérer les runs XHProf 496 | 497 |
Les clients web ont une certaine souplesse dans la manière de sauvegarder les données brutes fournies par XHProf. 498 | XHProf expose une interface utilisateur nommée iXHProfRuns (voir xhprof_lib/utils/xhprof_runs.php) que les clients peuvent implémenter. 499 | Cela permet aux clients de préciser comment afficher les donées des runs. 500 | 501 |
L’interface utilisateur d’XHProf fournit une implementation par défaut nommée, 502 | "XHProfRuns_Default" (aussi dans xhprof_lib/utils/xhprof_runs.php). 503 | L’implementation par d"faut stocke les runs dans le répertoire définit par le paramètre INI : 504 | xhprof.output_dir. 505 | 506 |
Un run XHProf doit être définit de manière unique par un espace de nom et un identifiant de run. 507 | 508 |
a) Sauver les données XHProf de façon persistente : 509 | 510 |
Soit si vous utilisez l’interface par défaut,
511 | XHProfRuns_Default qui implémente
512 | iXHProfRuns, Un run XHProf sauvegardé ressemble au code suivant :
513 |
514 |
515 |
516 | // début du profilage 517 | xhprof_enable(); 518 | 519 | // lancement du programme 520 | ... 521 | 522 | // fin du profilage 523 | $xhprof_data = xhprof_disable(); 524 | 525 | // 526 | // Sauvegarde du run XHProf 527 | // en utilisant l’implementation par défaut de iXHProfRuns. 528 | // 529 | include_once $XHPROF_ROOT . "/xhprof_lib/utils/xhprof_lib.php"; 530 | include_once $XHPROF_ROOT . "/xhprof_lib/utils/xhprof_runs.php"; 531 | 532 | $xhprof_runs = new XHProfRuns_Default(); 533 | 534 | // sauvegarde du run avec l’espace de nom "xhprof_foo". 535 | // 536 | // **NOTE**: 537 | // par défault save_run() va automatiquement générer un identifiant de run 538 | // unique. [Vous pouvez surcharger cette donnée en passant l’identifiant en paramètre optionnel 539 | // à la méthode save_run().] 540 | // 541 | $run_id = $xhprof_runs->save_run($xhprof_data, "xhprof_foo"); 542 | 543 | echo "---------------\n". 544 | "En partant du principe que vous avez parametré l’interface utilisateur http \n". 545 | "XHProf, vous pouvez visualiser les runs avec l’adresse : \n". 546 | "http://<adresse-interface-utilisateur-xhprof>/index.php?run=$run_id&source=xhprof_foo\n". 547 | "---------------\n"; 548 | 549 |550 | 551 |
La suite permet de sauvegarder le run sous forme d’un fichier dans le répertoire spécifié
552 | par le paramètre ini xhprof.output_dir. Le nom du fichier doit être de la forme
553 | 49bafaa3a3f66.xhprof_foo; Les deux parties du nom sont formées par l’identifiant du run
554 | ("49bafaa3a3f66") et l’espace de nom ("xhprof_foo"). [Si vous souhaitez créer un identifiant de run vous-même
555 | (comme une sequence de base de données, ou un timestamp), vous pouvez explicitementpasser l’identifiant
556 | du run à la méthode save_run.
557 |
558 |
b) En utilisant votre propre implementation d’iXHProfRuns 559 | 560 |
Si vous décidez de stocker différement les runs XHProf 561 | (soit dans un format compressé, dans une base de données, 562 | etc.), vous aurez besoin d’implémenter une classe qui implémente l’interface 563 | iXHProfRuns(). 564 | 565 |
Vous devrez également modifier les 3 pages PHP d’entrée (index.php,
566 | callgraph.php, typeahead.php) dans le répertoire "xhprof_html/" pour utiliser la
567 | nouvelle interface au lieu de celle par défaut (XHProfRuns_Default),
568 | changez cette ligne dans les 3 fichier.
569 |
570 |
571 | $xhprof_runs_impl = new XHProfRuns_Default(); 572 |573 | 574 |
Vous aurez aussi besoin d’inclure le fichier qui implémente votre classe dans les fichiers cités. 575 | 576 |
Acceéder aux runs depuis l’interface utilisateur 577 | 578 |
a) Voir un rapport simple 579 | 580 |
Pour voir un rapport avec l’identifiant <run_id> et l’espace de nom 581 | <namespace> utilisez une url de la forme : 582 | 583 |
584 | http://<adresse-interface-utilisateur-xhprof>/index.php?run=<run_id>&source=<namespace>
585 |
586 |
587 |
Par example, 588 |
589 | http://<adresse-interface-utilisateur-xhprof>/index.php?run=49bafaa3a3f66&source=xhprof_foo
590 |
591 |
592 |
b) Voir un rapport différentiel 593 | 594 |
Pour voir un rapport avec les identifiants <run_id1> et 595 | <run_id2> et l’espace de nom <namespace> utilisez une url de la forme : 596 | 597 |
598 | http://<adresse-interface-utilisateur-xhprof>/index.php?run1=<run_id1>&run2=<run_id2>&source=<namespace>
599 |
600 |
601 |
c) Voir un rapport d’aggrégation 602 | 603 |
Vous pouvez aussi spécifier un ensemble de runspour lesquels vous souhaitez un rapport d’aggrégation. 604 | 605 |
Si vous avez trois runs XHProf avec les identifiants 1, 2 & 3 pour l’espace de noms 606 | "benchmark". Pour voir l’aggrégation de ces trois runs : 607 | 608 |
609 | http://<adresse-interface-utilisateur-xhprof>/index.php?run=1,2,3&source=benchmark
610 |
Aggrégations pondérées: En supposant que les trois runs 613 | correspondent à trois types de programmes p1.php, p2.php and p3.php 614 | qui occupent chacun respectivement 20%, 30% et 50%. Pour voir un rapport d’aggrégation 615 | pondéré par les poids des runs : 616 | 617 |
618 | http://<adresse-interface-utilisateur-xhprof>/index.php?run=1,2,3&wts=20,30,50&source=benchmark
619 |
Quelques observations qui peuvent faire varier votre expérience : 626 | 627 |
Nous recommandons d’utiliser le mode de profilage "temps passé" + "memoire" en production. 634 | [Note: Le surplus de temps passé par le mode de profilage mémoire est non significatif.] 635 | 636 |
637 | // profilage du temps passé (par défault) + profilage mémoire 638 | xhprof_enable(XHPROF_FLAGS_MEMORY); 639 |640 |
Pour profiler 1/10000 de vos requêtes, définissez le début du profilage avec un code dans l’esprit de celui-ci : 647 | 648 |
649 | if (mt_rand(1, 10000) == 1) {
650 | xhprof_enable(XHPROF_FLAGS_MEMORY);
651 | $xhprof_on = true;
652 | }
653 |
654 |
655 | À la fin de la requête (ou dans une fonction de finalisation de la requête), vous pouvez faire quelque chose comme : 656 | 657 |
658 | if ($xhprof_on) {
659 | // fin du profilage
660 | $xhprof_data = xhprof_disable();
661 |
662 | // sauvegarde $xhprof_data quelquepart (base de données centralisée …)
663 | ...
664 | }
665 |
666 |
667 | Vous pouvez alors récupérer et aggréger ces profilages par horaire
668 | (par exemple 5 minutes, par jour, par jour …), par page ou type de requête, ou n’importe quel
669 | paramètre utilisé par xhprof_aggregate_runs().
670 |
671 |
672 |
673 |
L’extension XHProf propose aussi un mode très léger d’échantillonage. 676 | L’intervalle est de 0,1 seconde. Les échantillons enregistrent l’ensemble des données. 677 | Ce mode peut être très utile pour avoir un impact le plus négligeable possible, et permettre 678 | Le mode sample peut être utile si vous désirez un moyen avec peu de dépassement de faire de la surveillance de performances et des diagnostics. 679 | 680 |
Les très pertinentes fonctions utilisées par l’extension pour utiliser le mode
681 | d’échantillonage sont xhprof_sample_enable() et xhprof_sample_disable().
682 |
683 |
[TBD: Documentation plus détaillée pour le mode d’échantillonage.] 684 | 685 |
Le fichier XHProf_lib/utils/xhprof_lib.php contient
688 | des librairies de fonctions additionellesqui peuvent être utilisées pour manipuler
689 | et aggréger les runs XHProf.
690 |
691 |
Par exemple: 692 | 693 |
xhprof_aggregate_runs():
697 | peut être utilisé pour aggréger de multiples runs XHProf runs dans un seul run.
698 | Cela peut être très utile pour fabriquer un outil de monitoring utilisant XHProf et à l’échelle voulue.
699 | [Par exemple, vous pouvez mixer des runs XHProf issus périodiquement
700 | d’échantillonage de la production pour générer des rapport journalier.]
701 |
702 | xhprof_prune_run(): Aggréger une grande quantité de runs
703 | (particulièrement si ils correspondent à des zones différentes du programme) peut créer un rendu
704 | graphique beaucoup trop gros. Vous pouvez donc utiliser la fonction xhprof_prune_run
705 | élaguer les données à afficher. En supprimant des branches qui compte pour une partie négligeable du temps passé.
706 |
707 | xhprof_html/jquery.
720 |
721 | Le rendu HTML et l’interface de navigation pour consulter les résultat du profilage sont inspirés par un outil similaire 729 | qui existe pour les procédures stockées PL/SQL d’Oracle. Mais c’est là que la comparaison s’arrête; 730 | Le fonctionnement interne du profileur étant assez différent 731 | 732 | [NDT : Merci à Rudy Rigot (@rudyrigot) pour sa relecture attentive ] 733 |