├── 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:///index.php?run=$run_id&source=xhprof_foo\n". 44 | "---------------\n"; 45 | -------------------------------------------------------------------------------- /xhprof_html/typeahead.php: -------------------------------------------------------------------------------- 1 | =");') 4 | ifeq ($(GOODPHP), 1) 5 | TESTTARGET = test 6 | else 7 | TESTTARGET = test_from_php55 8 | endif 9 | 10 | test_with_exit_status: $(TESTTARGET) 11 | 12 | # Fixed test target from PHP 5.5. 13 | test_from_php55: 14 | @if test ! -z "$(PHP_EXECUTABLE)" && test -x "$(PHP_EXECUTABLE)"; then \ 15 | INI_FILE=`$(PHP_EXECUTABLE) -d 'display_errors=stderr' -r 'echo php_ini_loaded_file();' 2> /dev/null`; \ 16 | if test "$$INI_FILE"; then \ 17 | $(EGREP) -h -v $(PHP_DEPRECATED_DIRECTIVES_REGEX) "$$INI_FILE" > $(top_builddir)/tmp-php.ini; \ 18 | else \ 19 | echo > $(top_builddir)/tmp-php.ini; \ 20 | fi; \ 21 | INI_SCANNED_PATH=`$(PHP_EXECUTABLE) -d 'display_errors=stderr' -r '$$a = explode(",\n", trim(php_ini_scanned_files())); echo $$a[0];' 2> /dev/null`; \ 22 | if test "$$INI_SCANNED_PATH"; then \ 23 | INI_SCANNED_PATH=`$(top_srcdir)/build/shtool path -d $$INI_SCANNED_PATH`; \ 24 | $(EGREP) -h -v $(PHP_DEPRECATED_DIRECTIVES_REGEX) "$$INI_SCANNED_PATH"/*.ini >> $(top_builddir)/tmp-php.ini; \ 25 | fi; \ 26 | TEST_PHP_EXECUTABLE=$(PHP_EXECUTABLE) \ 27 | TEST_PHP_SRCDIR=$(top_srcdir) \ 28 | CC="$(CC)" \ 29 | $(PHP_EXECUTABLE) -n -c $(top_builddir)/tmp-php.ini $(PHP_TEST_SETTINGS) $(top_srcdir)/run-tests.php -n -c $(top_builddir)/tmp-php.ini -d extension_dir=$(top_builddir)/modules/ $(PHP_TEST_SHARED_EXTENSIONS) $(TESTS); \ 30 | TEST_RESULT_EXIT_CODE=$$?; \ 31 | rm $(top_builddir)/tmp-php.ini; \ 32 | exit $$TEST_RESULT_EXIT_CODE; \ 33 | else \ 34 | echo "ERROR: Cannot run tests without CLI sapi."; \ 35 | fi 36 | -------------------------------------------------------------------------------- /support/libxhprof/unit/XHProfUnitTestEngine.php: -------------------------------------------------------------------------------- 1 | getWorkingCopy()->getProjectRoot().'/extension/'; 7 | 8 | $start_time = microtime(true); 9 | 10 | id(new ExecFuture('phpize && ./configure && make -j4')) 11 | ->setCWD($root) 12 | ->resolvex(); 13 | 14 | $out = id(new ExecFuture('make -f Makefile.local test_with_exit_status')) 15 | ->setCWD($root) 16 | ->setEnv( 17 | array( 18 | 'TEST_PHP_ARGS' => '-q', 19 | )) 20 | ->resolvex(); 21 | 22 | // NOTE: REPORT_EXIT_STATUS doesn't seem to work properly in some versions 23 | // of PHP. Just "parse" stdout to approximate the results. 24 | 25 | list($stdout) = $out; 26 | 27 | $tests = array(); 28 | 29 | foreach (phutil_split_lines($stdout) as $line) { 30 | $matches = null; 31 | 32 | // NOTE: The test script writes the name of the test originally, then 33 | // uses "\r" to erase it and write the result. This splits as a single 34 | // line. 35 | if (preg_match('/^TEST .*\r(PASS|FAIL) (.*)/', $line, $matches)) { 36 | if ($matches[1] == 'PASS') { 37 | $result = ArcanistUnitTestResult::RESULT_PASS; 38 | } else { 39 | $result = ArcanistUnitTestResult::RESULT_FAIL; 40 | } 41 | 42 | $name = trim($matches[2]); 43 | 44 | $tests[] = id(new ArcanistUnitTestResult()) 45 | ->setName($name) 46 | ->setResult($result) 47 | ->setDuration(microtime(true) - $start_time); 48 | } 49 | } 50 | 51 | return $tests; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /extension/tests/xhprof_003.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | XHProf: Test Class Methods, Constructors, Destructors. 3 | Author: Kannan 4 | --FILE-- 5 | _attr = $attr; 15 | } 16 | 17 | private static function inner_static() { 18 | return C::$_static_attr; 19 | } 20 | 21 | public static function outer_static() { 22 | return C::inner_static(); 23 | } 24 | 25 | public function get_attr() { 26 | return $this->_attr; 27 | } 28 | 29 | function __destruct() { 30 | echo "Destroying class {$this->_attr}\n"; 31 | } 32 | } 33 | 34 | 35 | xhprof_enable(); 36 | 37 | // static methods 38 | echo C::outer_static() . "\n"; 39 | 40 | // constructor 41 | $obj = new C("Hello World"); 42 | 43 | // instance methods 44 | $obj->get_attr(); 45 | 46 | // destructor 47 | $obj = null; 48 | 49 | 50 | $output = xhprof_disable(); 51 | 52 | echo "Profiler data for 'Class' tests:\n"; 53 | print_canonical($output); 54 | echo "\n"; 55 | 56 | ?> 57 | --EXPECT-- 58 | i am a class static 59 | In constructor... 60 | Destroying class Hello World 61 | Profiler data for 'Class' tests: 62 | C::outer_static==>C::inner_static : ct= 1; wt=*; 63 | main() : ct= 1; wt=*; 64 | main()==>C::__construct : ct= 1; wt=*; 65 | main()==>C::__destruct : ct= 1; wt=*; 66 | main()==>C::get_attr : ct= 1; wt=*; 67 | main()==>C::outer_static : ct= 1; wt=*; 68 | main()==>xhprof_disable : ct= 1; wt=*; 69 | -------------------------------------------------------------------------------- /extension/tests/xhprof_008.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | XHProf: Sampling Mode Test 3 | Author: kannan 4 | --FILE-- 5 | goo==>bar==>foo==>usleep") { 38 | $count1++; 39 | } 40 | } 41 | 42 | // how many usleep samples did we get in two calls to goo()? 43 | $count2 = 0; 44 | foreach ($output2 as $sample) { 45 | if ($sample == "main()==>goo==>bar==>foo==>usleep") { 46 | $count2++; 47 | } 48 | } 49 | 50 | // 51 | // our default sampling frequency is 0.1 seconds. So 52 | // we would expect about 8 samples (given that foo() 53 | // sleeps for 0.8 seconds). However, we might in future 54 | // allow the sampling frequency to be modified. So rather 55 | // than depend on the absolute number of samples, we'll 56 | // check to see if $count2 is roughly double of $count1. 57 | // 58 | 59 | if (($count1 == 0) 60 | || (($count2 / $count1) > 2.5) 61 | || (($count2 / $count1) < 1.5)) { 62 | echo "Test failed\n"; 63 | echo "Count of usleep samples in one call to goo(): $count1\n"; 64 | echo "Count of usleep samples in two calls to goo(): $count2\n"; 65 | echo "Samples in one call to goo(): \n"; 66 | var_dump($output1); 67 | echo "Samples in two calls to goo(): \n"; 68 | var_dump($output2); 69 | } else { 70 | echo "Test passed\n"; 71 | } 72 | 73 | ?> 74 | --EXPECT-- 75 | Test passed 76 | -------------------------------------------------------------------------------- /xhprof_html/css/xhprof.css: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2009 Facebook 2 | * 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | 16 | td.sorted { 17 | color:#0000FF; 18 | } 19 | 20 | td.vbar, th.vbar { 21 | text-align: right; 22 | border-left: 23 | solid 1px #bdc7d8; 24 | } 25 | 26 | td.vbbar, th.vbar { 27 | text-align: right; 28 | border-left: 29 | solid 1px #bdc7d8; 30 | color:blue; 31 | } 32 | 33 | /* diff reports: display regressions in red */ 34 | td.vrbar { 35 | text-align: right; 36 | border-left:solid 1px #bdc7d8; 37 | color:red; 38 | } 39 | 40 | /* diff reports: display improvements in green */ 41 | td.vgbar { 42 | text-align: right; 43 | border-left: solid 1px #bdc7d8; 44 | color:green; 45 | } 46 | 47 | td.vwbar, th.vwbar { 48 | text-align: right; 49 | border-left: solid 1px white; 50 | } 51 | 52 | td.vwlbar, th.vwlbar { 53 | text-align: left; 54 | border-left: solid 1px white; 55 | } 56 | 57 | p.blue { 58 | color:blue 59 | } 60 | 61 | .bubble { 62 | background-color:#C3D9FF 63 | } 64 | 65 | ul.xhprof_actions { 66 | float: right; 67 | padding-left: 16px; 68 | list-style-image: none; 69 | list-style-type: none; 70 | margin:10px 10px 10px 3em; 71 | position:relative; 72 | } 73 | 74 | ul.xhprof_actions li { 75 | border-bottom:1px solid #D8DFEA; 76 | } 77 | 78 | ul.xhprof_actions li a:hover { 79 | background:#3B5998 none repeat scroll 0 0; 80 | color:#FFFFFF; 81 | } 82 | -------------------------------------------------------------------------------- /extension/tests/xhprof_005.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | XHProf: Timer Tests 3 | Author: Kannan 4 | --FILE-- 5 | $range_high)) { 54 | echo "Failed ${description}. Expected: ${expected} microsecs. ". 55 | "Actual: ${actual} microsecs.\n"; 56 | } else { 57 | echo "OK: ${description}\n"; 58 | } 59 | echo "-------------\n"; 60 | } 61 | 62 | verify(10000, 63 | $output["sleep_10000_micro==>usleep"]["wt"], 64 | "sleep_10000_micro"); 65 | verify(20000, 66 | $output["sleep_20000_micro==>usleep"]["wt"], 67 | "sleep_20000_micro"); 68 | verify(50000, 69 | $output["sleep_50000_micro==>usleep"]["wt"], 70 | "sleep_50000_micro"); 71 | 72 | ?> 73 | --EXPECT-- 74 | Verifying sleep_10000_micro... 75 | OK: sleep_10000_micro 76 | ------------- 77 | Verifying sleep_20000_micro... 78 | OK: sleep_20000_micro 79 | ------------- 80 | Verifying sleep_50000_micro... 81 | OK: sleep_50000_micro 82 | ------------- 83 | -------------------------------------------------------------------------------- /extension/tests/xhprof_002.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | XHProf: Test (direct and indirect) recursive function calls. 3 | Author: Kannan 4 | --FILE-- 5 | 0) { 16 | if ($use_direct_recursion) 17 | foo($depth - 1, $use_direct_recursion); 18 | else 19 | bar($depth - 1, $use_direct_recursion); 20 | } 21 | } 22 | 23 | 24 | xhprof_enable(); 25 | foo(4, true); 26 | $output = xhprof_disable(); 27 | 28 | echo "Direct Recursion\n"; 29 | print_canonical($output); 30 | echo "\n"; 31 | 32 | 33 | xhprof_enable(); 34 | foo(4, false); 35 | $output = xhprof_disable(); 36 | 37 | echo "Indirect Recursion\n"; 38 | print_canonical($output); 39 | echo "\n"; 40 | 41 | ?> 42 | --EXPECT-- 43 | Direct Recursion 44 | foo==>foo@1 : ct= 1; wt=*; 45 | foo@1==>foo@2 : ct= 1; wt=*; 46 | foo@2==>foo@3 : ct= 1; wt=*; 47 | foo@3==>foo@4 : ct= 1; wt=*; 48 | main() : ct= 1; wt=*; 49 | main()==>foo : ct= 1; wt=*; 50 | main()==>xhprof_disable : ct= 1; wt=*; 51 | 52 | Indirect Recursion 53 | bar==>foo@1 : ct= 1; wt=*; 54 | bar@1==>foo@2 : ct= 1; wt=*; 55 | bar@2==>foo@3 : ct= 1; wt=*; 56 | bar@3==>foo@4 : ct= 1; wt=*; 57 | foo==>bar : ct= 1; wt=*; 58 | foo@1==>bar@1 : ct= 1; wt=*; 59 | foo@2==>bar@2 : ct= 1; wt=*; 60 | foo@3==>bar@3 : ct= 1; wt=*; 61 | main() : ct= 1; wt=*; 62 | main()==>foo : ct= 1; wt=*; 63 | main()==>xhprof_disable : ct= 1; wt=*; 64 | -------------------------------------------------------------------------------- /extension/tests/xhprof_004.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | XHProf: Test Include File (load/run_init operations) 3 | Author: Kannan 4 | --FILE-- 5 | 38 | --EXPECT-- 39 | abc,def,ghi 40 | I am in foo()... 41 | 11 42 | I am in bar()... 43 | Test for 'include_once' & 'require_once' operation 44 | main() : ct= 1; wt=*; 45 | main()==>dirname : ct= 6; wt=*; 46 | main()==>load::tests/xhprof_004_inc.php : ct= 1; wt=*; 47 | main()==>load::tests/xhprof_004_require.php: ct= 1; wt=*; 48 | main()==>run_init::tests/xhprof_004_inc.php: ct= 1; wt=*; 49 | main()==>run_init::tests/xhprof_004_require.php: ct= 1; wt=*; 50 | main()==>xhprof_disable : ct= 1; wt=*; 51 | run_init::tests/xhprof_004_inc.php==>explode: ct= 1; wt=*; 52 | run_init::tests/xhprof_004_inc.php==>foo: ct= 1; wt=*; 53 | run_init::tests/xhprof_004_inc.php==>implode: ct= 1; wt=*; 54 | run_init::tests/xhprof_004_require.php==>bar: ct= 1; wt=*; 55 | run_init::tests/xhprof_004_require.php==>explode: ct= 1; wt=*; 56 | run_init::tests/xhprof_004_require.php==>implode: ct= 1; wt=*; 57 | run_init::tests/xhprof_004_require.php==>strlen: ct= 1; wt=*; 58 | -------------------------------------------------------------------------------- /extension/tests/xhprof_006.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | XHProf: Basic Sampling Test 3 | Author: mpal 4 | --FILE-- 5 | 70 | --EXPECT-- 71 | Part 1: Sampling Profile 72 | Test Case : Percent of Total Time 73 | Individual : 74 | Folded : 75 | Part 1: output 76 | In general, sampling output depends upon execution speed. 77 | Currently checking that this runs to completion. 78 | 79 | -------------------------------------------------------------------------------- /xhprof_lib/display/typeahead_common.php: -------------------------------------------------------------------------------- 1 | array(XHPROF_STRING_PARAM, ''), 32 | 'run' => array(XHPROF_STRING_PARAM, ''), 33 | 'run1' => array(XHPROF_STRING_PARAM, ''), 34 | 'run2' => array(XHPROF_STRING_PARAM, ''), 35 | 'source' => array(XHPROF_STRING_PARAM, 'xhprof'), 36 | ); 37 | 38 | // pull values of these params, and create named globals for each param 39 | xhprof_param_init($params); 40 | 41 | if (!empty($run)) { 42 | 43 | // single run mode 44 | $raw_data = $xhprof_runs_impl->get_run($run, $source, $desc_unused); 45 | $functions = xhprof_get_matching_functions($q, $raw_data); 46 | 47 | } else if (!empty($run1) && !empty($run2)) { 48 | 49 | // diff mode 50 | $raw_data = $xhprof_runs_impl->get_run($run1, $source, $desc_unused); 51 | $functions1 = xhprof_get_matching_functions($q, $raw_data); 52 | 53 | $raw_data = $xhprof_runs_impl->get_run($run2, $source, $desc_unused); 54 | $functions2 = xhprof_get_matching_functions($q, $raw_data); 55 | 56 | 57 | $functions = array_unique(array_merge($functions1, $functions2)); 58 | asort($functions); 59 | } else { 60 | xhprof_error("no valid runs specified to typeahead endpoint"); 61 | $functions = array(); 62 | } 63 | 64 | // If exact match is present move it to the front 65 | if (in_array($q, $functions)) { 66 | $old_functions = $functions; 67 | 68 | $functions = array($q); 69 | foreach ($old_functions as $f) { 70 | // exact match case has already been added to the front 71 | if ($f != $q) { 72 | $functions[] = $f; 73 | } 74 | } 75 | } 76 | 77 | foreach ($functions as $f) { 78 | echo $f."\n"; 79 | } 80 | -------------------------------------------------------------------------------- /xhprof_html/index.php: -------------------------------------------------------------------------------- 1 | array(XHPROF_STRING_PARAM, ''), 42 | 'wts' => array(XHPROF_STRING_PARAM, ''), 43 | 'symbol' => array(XHPROF_STRING_PARAM, ''), 44 | 'sort' => array(XHPROF_STRING_PARAM, 'wt'), // wall time 45 | 'run1' => array(XHPROF_STRING_PARAM, ''), 46 | 'run2' => array(XHPROF_STRING_PARAM, ''), 47 | 'source' => array(XHPROF_STRING_PARAM, 'xhprof'), 48 | 'all' => array(XHPROF_UINT_PARAM, 0), 49 | ); 50 | 51 | // pull values of these params, and create named globals for each param 52 | xhprof_param_init($params); 53 | 54 | /* reset params to be a array of variable names to values 55 | by the end of this page, param should only contain values that need 56 | to be preserved for the next page. unset all unwanted keys in $params. 57 | */ 58 | foreach ($params as $k => $v) { 59 | $params[$k] = $$k; 60 | 61 | // unset key from params that are using default values. So URLs aren't 62 | // ridiculously long. 63 | if ($params[$k] == $v[1]) { 64 | unset($params[$k]); 65 | } 66 | } 67 | 68 | echo ""; 69 | 70 | echo "XHProf: Hierarchical Profiler Report"; 71 | xhprof_include_js_css(); 72 | echo ""; 73 | 74 | echo ""; 75 | 76 | $vbar = ' class="vbar"'; 77 | $vwbar = ' class="vwbar"'; 78 | $vwlbar = ' class="vwlbar"'; 79 | $vbbar = ' class="vbbar"'; 80 | $vrbar = ' class="vrbar"'; 81 | $vgbar = ' class="vgbar"'; 82 | 83 | $xhprof_runs_impl = new XHProfRuns_Default(); 84 | 85 | displayXHProfReport($xhprof_runs_impl, $params, $source, $run, $wts, 86 | $symbol, $sort, $run1, $run2); 87 | 88 | 89 | echo ""; 90 | echo ""; 91 | -------------------------------------------------------------------------------- /extension/tests/xhprof_001.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | XHProf: Basic Profiling Test 3 | Author: Kannan 4 | --FILE-- 5 | 67 | --EXPECT-- 68 | Part 1: Default Flags 69 | foo==>bar : ct= 2; wt=*; 70 | foo==>strlen : ct= 1; wt=*; 71 | main() : ct= 1; wt=*; 72 | main()==>foo : ct= 1; wt=*; 73 | main()==>xhprof_disable : ct= 1; wt=*; 74 | 75 | Part 2: CPU 76 | foo==>bar : cpu=*; ct= 2; wt=*; 77 | foo==>strlen : cpu=*; ct= 1; wt=*; 78 | main() : cpu=*; ct= 1; wt=*; 79 | main()==>foo : cpu=*; ct= 1; wt=*; 80 | main()==>xhprof_disable : cpu=*; ct= 1; wt=*; 81 | 82 | Part 3: No Builtins 83 | foo==>bar : ct= 2; wt=*; 84 | main() : ct= 1; wt=*; 85 | main()==>foo : ct= 1; wt=*; 86 | 87 | Part 4: Memory 88 | foo==>bar : ct= 2; mu=*; pmu=*; wt=*; 89 | foo==>strlen : ct= 1; mu=*; pmu=*; wt=*; 90 | main() : ct= 1; mu=*; pmu=*; wt=*; 91 | main()==>foo : ct= 1; mu=*; pmu=*; wt=*; 92 | main()==>xhprof_disable : ct= 1; mu=*; pmu=*; wt=*; 93 | 94 | Part 5: Memory & CPU 95 | foo==>bar : cpu=*; ct= 2; mu=*; pmu=*; wt=*; 96 | foo==>strlen : cpu=*; ct= 1; mu=*; pmu=*; wt=*; 97 | main() : cpu=*; ct= 1; mu=*; pmu=*; wt=*; 98 | main()==>foo : cpu=*; ct= 1; mu=*; pmu=*; wt=*; 99 | main()==>xhprof_disable : cpu=*; ct= 1; mu=*; pmu=*; wt=*; 100 | 101 | 102 | -------------------------------------------------------------------------------- /xhprof_html/callgraph.php: -------------------------------------------------------------------------------- 1 | array(XHPROF_STRING_PARAM, ''), 42 | 43 | // source/namespace/type of run 44 | 'source' => array(XHPROF_STRING_PARAM, 'xhprof'), 45 | 46 | // the focus function, if it is set, only directly 47 | // parents/children functions of it will be shown. 48 | 'func' => array(XHPROF_STRING_PARAM, ''), 49 | 50 | // image type, can be 'jpg', 'gif', 'ps', 'png' 51 | 'type' => array(XHPROF_STRING_PARAM, 'png'), 52 | 53 | // only functions whose exclusive time over the total time 54 | // is larger than this threshold will be shown. 55 | // default is 0.01. 56 | 'threshold' => array(XHPROF_FLOAT_PARAM, 0.01), 57 | 58 | // whether to show critical_path 59 | 'critical' => array(XHPROF_BOOL_PARAM, true), 60 | 61 | // first run in diff mode. 62 | 'run1' => array(XHPROF_STRING_PARAM, ''), 63 | 64 | // second run in diff mode. 65 | 'run2' => array(XHPROF_STRING_PARAM, '') 66 | ); 67 | 68 | // pull values of these params, and create named globals for each param 69 | xhprof_param_init($params); 70 | 71 | // if invalid value specified for threshold, then use the default 72 | if ($threshold < 0 || $threshold > 1) { 73 | $threshold = $params['threshold'][1]; 74 | } 75 | 76 | // if invalid value specified for type, use the default 77 | if (!array_key_exists($type, $xhprof_legal_image_types)) { 78 | $type = $params['type'][1]; // default image type. 79 | } 80 | 81 | $xhprof_runs_impl = new XHProfRuns_Default(); 82 | 83 | if (!empty($run)) { 84 | // single run call graph image generation 85 | xhprof_render_image($xhprof_runs_impl, $run, $type, 86 | $threshold, $func, $source, $critical); 87 | } else { 88 | // diff report call graph image generation 89 | xhprof_render_diff_image($xhprof_runs_impl, $run1, $run2, 90 | $type, $threshold, $source); 91 | } 92 | -------------------------------------------------------------------------------- /CHANGELOG: -------------------------------------------------------------------------------- 1 | Modification History: 2 | 3 | *** NOTE *** 4 | DO NOT UPDATE THIS FILE. UPDATE package.xml INSTEAD. 5 | This file contains the CHANGELOG for the initial release. For subsequent 6 | releases, the CHANGLELOG is maintained in the package.xml file itself. 7 | Please edit package.xml instead. 8 | ************ 9 | 10 | 03/02/2008 kannan, checkin xhprof_html/ and xhprof_lib/ directories. 11 | cjiang [These contain PHP sources for the UI as well 12 | as various supporting libraries to compute 13 | "flat" info, diff reports, aggregate results 14 | of multiple runs, typeahead support, etc.] 15 | 02/20/2008 kannan add basic sanity tests for the extension 16 | 02/19/2008 kannan register constants for optional profiler flags; 17 | add xhprof.output_dir ini setting. 18 | 01/22/2008 ps port cpu affinity functions to FreeBSD 19 | 01/15/2008 kannan intercept builtins even if zend_execute_internal 20 | were null to begin with 21 | 01/14/2008 kannan track builtins by default; 22 | fix compiler warnings with fwd decls 23 | 12/22/2008 cjiang Further refactoring of the code for open sourcing: 24 | (1). Remove level 1 profiling mode. 25 | (2). Add xhprof_sample_enable, xhprof_sample_disable. 26 | (3). Unifiy function and global variable prefix. 27 | (4). Group relevant functions together. 28 | (5). Migrate change history to CHANAGELOG file. 29 | 12/19/2008 kannan First step refactoring for open sourcing: 30 | (1). Put basic direcotry structure 31 | (2). Rename extension and function names 32 | (3). Add LICENCE header. 33 | 06/17/2008 veeve use cycle_timer() for XHPROF_MODE_SAMPLED 34 | 03/27/2008 cjiang Add a 'hash-based' filter to reduce the number 35 | of expensive call-stack tranversal on recursion 36 | detection. 37 | 03/17/2008 kannan must not keep state on C stack to handle 38 | exit (which causes _zend_bailout to longjmp 39 | 02/25/2008 kannan add xhprof_flags to toggle various metric 40 | collections (buitins on/off, cpu metric on/off 41 | memory stats on/off) 42 | 02/14/2008 cjiang Use cycle_timer based on 'rdtsc' instruction 43 | on x86 machines to replace gettimeofday. rdtsc 44 | is extremely cheap compared with gettimeofday 45 | or getrusage. 46 | 12/06/2007 veeve bump version 1.1.2, 47 | added hp_global_t 48 | added mode callbacks, made modes extensible 49 | added sampler mode 50 | 12/05/2007 veeve added doc; house cleaning 51 | 11/28/2007 kannan split include accounting into load/run_init 52 | 11/09/2007 kannan memory usage profiling 53 | 10/27/2007 kannan handle recursive calls, "include" operations 54 | 10/20/2007 kannan add hierarchical profiling; incl vs. exclusive 55 | function times; browser based UI; diff and 56 | aggregation support 57 | 10/10/2007 hzhao creation (flat function profiles) 58 | 59 | Authors: 60 | Haiping Zhao hzhao@facebook.com 61 | Kannan Muthukkaruppan kannan@facebook.com 62 | Venkat Venkataramani veeve@facebook.com 63 | Changhao Jiang cjiang@facebook.com 64 | -------------------------------------------------------------------------------- /xhprof_lib/utils/xhprof_runs.php: -------------------------------------------------------------------------------- 1 | suffix; 80 | 81 | if (!empty($this->dir)) { 82 | $file = $this->dir . "/" . $file; 83 | } 84 | return $file; 85 | } 86 | 87 | public function __construct($dir = null) { 88 | 89 | // if user hasn't passed a directory location, 90 | // we use the xhprof.output_dir ini setting 91 | // if specified, else we default to the directory 92 | // in which the error_log file resides. 93 | 94 | if (empty($dir)) { 95 | $dir = ini_get("xhprof.output_dir"); 96 | if (empty($dir)) { 97 | 98 | $dir = sys_get_temp_dir(); 99 | 100 | xhprof_error("Warning: Must specify directory location for XHProf runs. ". 101 | "Trying {$dir} as default. You can either pass the " . 102 | "directory location as an argument to the constructor ". 103 | "for XHProfRuns_Default() or set xhprof.output_dir ". 104 | "ini param."); 105 | } 106 | } 107 | $this->dir = $dir; 108 | } 109 | 110 | public function get_run($run_id, $type, &$run_desc) { 111 | $file_name = $this->file_name($run_id, $type); 112 | 113 | if (!file_exists($file_name)) { 114 | xhprof_error("Could not find file $file_name"); 115 | $run_desc = "Invalid Run Id = $run_id"; 116 | return null; 117 | } 118 | 119 | $contents = file_get_contents($file_name); 120 | $run_desc = "XHProf Run (Namespace=$type)"; 121 | return unserialize($contents); 122 | } 123 | 124 | public function save_run($xhprof_data, $type, $run_id = null) { 125 | 126 | // Use PHP serialize function to store the XHProf's 127 | // raw profiler data. 128 | $xhprof_data = serialize($xhprof_data); 129 | 130 | if ($run_id === null) { 131 | $run_id = $this->gen_run_id($type); 132 | } 133 | 134 | $file_name = $this->file_name($run_id, $type); 135 | $file = fopen($file_name, 'w'); 136 | 137 | if ($file) { 138 | fwrite($file, $xhprof_data); 139 | fclose($file); 140 | } else { 141 | xhprof_error("Could not open $file_name\n"); 142 | } 143 | 144 | // echo "Saved run in {$file_name}.\nRun id = {$run_id}.\n"; 145 | return $run_id; 146 | } 147 | 148 | function list_runs() { 149 | if (is_dir($this->dir)) { 150 | echo "
Existing runs:\n\n"; 162 | } 163 | } 164 | } 165 | -------------------------------------------------------------------------------- /xhprof_html/js/xhprof_report.js: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2009 Facebook 2 | * 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | 16 | /** 17 | * Helper javascript functions for XHProf report tooltips. 18 | * 19 | * @author Kannan Muthukkaruppan 20 | */ 21 | 22 | // Take a string which is actually a number in comma separated format 23 | // and return a string representing the absolute value of the number. 24 | function stringAbs(x) { 25 | return x.replace("-", ""); 26 | } 27 | 28 | // Takes a number in comma-separated string format, and 29 | // returns a boolean to indicate if the number is negative 30 | // or not. 31 | function isNegative(x) { 32 | 33 | return (x.indexOf("-") == 0); 34 | 35 | } 36 | 37 | function addCommas(nStr) 38 | { 39 | nStr += ''; 40 | x = nStr.split('.'); 41 | x1 = x[0]; 42 | x2 = x.length > 1 ? '.' + x[1] : ''; 43 | var rgx = /(\d+)(\d{3})/; 44 | while (rgx.test(x1)) { 45 | x1 = x1.replace(rgx, '$1' + ',' + '$2'); 46 | } 47 | return x1 + x2; 48 | } 49 | 50 | // Mouseover tips for parent rows in parent/child report.. 51 | function ParentRowToolTip(cell, metric) 52 | { 53 | var metric_val; 54 | var parent_metric_val; 55 | var parent_metric_pct_val; 56 | var col_index; 57 | var diff_text; 58 | 59 | row = cell.parentNode; 60 | tds = row.getElementsByTagName("td"); 61 | 62 | parent_func = tds[0].innerHTML; // name 63 | 64 | if (diff_mode) { 65 | diff_text = " diff "; 66 | } else { 67 | diff_text = ""; 68 | } 69 | 70 | s = '
'; 71 | 72 | if (metric == "ct") { 73 | parent_ct = tds[1].innerHTML; // calls 74 | parent_ct_pct = tds[2].innerHTML; 75 | 76 | func_ct = addCommas(func_ct); 77 | 78 | if (diff_mode) { 79 | s += 'There are ' + stringAbs(parent_ct) + 80 | (isNegative(parent_ct) ? ' fewer ' : ' more ') + 81 | ' calls to ' + func_name + ' from ' + parent_func + '
'; 82 | 83 | text = " of diff in calls "; 84 | } else { 85 | text = " of calls "; 86 | } 87 | 88 | s += parent_ct_pct + text + '(' + parent_ct + '/' + func_ct + ') to ' 89 | + func_name + ' are from ' + parent_func + '
'; 90 | } else { 91 | 92 | // help for other metrics such as wall time, user cpu time, memory usage 93 | col_index = metrics_col[metric]; 94 | parent_metric_val = tds[col_index].innerHTML; 95 | parent_metric_pct_val = tds[col_index+1].innerHTML; 96 | 97 | metric_val = addCommas(func_metrics[metric]); 98 | 99 | s += parent_metric_pct_val + '(' + parent_metric_val + '/' + metric_val 100 | + ') of ' + metrics_desc[metric] + 101 | (diff_mode ? ((isNegative(parent_metric_val) ? 102 | " decrease" : " increase")) : "") + 103 | ' in ' + func_name + ' is due to calls from ' + parent_func + '
'; 104 | } 105 | 106 | s += '
'; 107 | 108 | return s; 109 | } 110 | 111 | // Mouseover tips for child rows in parent/child report.. 112 | function ChildRowToolTip(cell, metric) 113 | { 114 | var metric_val; 115 | var child_metric_val; 116 | var child_metric_pct_val; 117 | var col_index; 118 | var diff_text; 119 | 120 | row = cell.parentNode; 121 | tds = row.getElementsByTagName("td"); 122 | 123 | child_func = tds[0].innerHTML; // name 124 | 125 | if (diff_mode) { 126 | diff_text = " diff "; 127 | } else { 128 | diff_text = ""; 129 | } 130 | 131 | s = '
'; 132 | 133 | if (metric == "ct") { 134 | 135 | child_ct = tds[1].innerHTML; // calls 136 | child_ct_pct = tds[2].innerHTML; 137 | 138 | s += func_name + ' called ' + child_func + ' ' + stringAbs(child_ct) + 139 | (diff_mode ? (isNegative(child_ct) ? " fewer" : " more") : "" ) 140 | + ' times.
'; 141 | s += 'This accounts for ' + child_ct_pct + ' (' + child_ct 142 | + '/' + total_child_ct 143 | + ') of function calls made by ' + func_name + '.'; 144 | 145 | } else { 146 | 147 | // help for other metrics such as wall time, user cpu time, memory usage 148 | col_index = metrics_col[metric]; 149 | child_metric_val = tds[col_index].innerHTML; 150 | child_metric_pct_val = tds[col_index+1].innerHTML; 151 | 152 | metric_val = addCommas(func_metrics[metric]); 153 | 154 | if (child_func.indexOf("Exclusive Metrics") != -1) { 155 | s += 'The exclusive ' + metrics_desc[metric] + diff_text 156 | + ' for ' + func_name 157 | + ' is ' + child_metric_val + "
"; 158 | 159 | s += "which is " + child_metric_pct_val + " of the inclusive " 160 | + metrics_desc[metric] 161 | + diff_text + " for " + func_name + " (" + metric_val + ")."; 162 | 163 | } else { 164 | 165 | s += child_func + ' when called from ' + func_name 166 | + ' takes ' + stringAbs(child_metric_val) 167 | + (diff_mode ? (isNegative(child_metric_val) ? " less" : " more") : "") 168 | + " of " + metrics_desc[metric] + "
"; 169 | 170 | s += "which is " + child_metric_pct_val + " of the inclusive " 171 | + metrics_desc[metric] 172 | + diff_text + " for " + func_name + " (" + metric_val + ")."; 173 | } 174 | } 175 | 176 | s += '
'; 177 | 178 | return s; 179 | } 180 | 181 | $(document).ready(function() { 182 | $('td[@metric]').tooltip( 183 | { bodyHandler: function() { 184 | var type = $(this).attr('type'); 185 | var metric = $(this).attr('metric'); 186 | if (type == 'Parent') { 187 | return ParentRowToolTip(this, metric); 188 | } else if (type == 'Child') { 189 | return ChildRowToolTip(this, metric); 190 | } 191 | }, 192 | showURL : false 193 | }); 194 | var cur_params = {} ; 195 | $.each(location.search.replace('?','').split('&'), function(i, x) { 196 | var y = x.split('='); cur_params[y[0]] = y[1]; 197 | }); 198 | $('input.function_typeahead') 199 | .autocomplete('typeahead.php', { extraParams : cur_params }) 200 | .result(function(event, item) { 201 | cur_params['symbol'] = item; 202 | location.search = '?' + jQuery.param(cur_params); 203 | }); 204 | }); 205 | -------------------------------------------------------------------------------- /package.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | xhprof 7 | pecl.php.net 8 | XHProf: A Hierarchical Profiler for PHP 9 | 10 | XHProf is a function-level hierarchical profiler for PHP and has a simple HTML based navigational interface. The raw data collection component is implemented in C (as a PHP extension). The reporting/UI layer is all in PHP. It is capable of reporting function-level inclusive and exclusive wall times, memory usage, CPU times and number of calls for each function. Additionally, it supports ability to compare two runs (hierarchical DIFF reports), or aggregate results from multiple runs. 11 | 12 | 13 | Kannan Muthukkaruppan 14 | kannan 15 | kannan@php.net 16 | no 17 | 18 | 19 | Venkat Venkataramani 20 | veeve 21 | veeve@php.net 22 | no 23 | 24 | 25 | Changhao Jiang 26 | cjiang 27 | cjiang@php.net 28 | no 29 | 30 | 31 | Haiping Zhao 32 | haiping 33 | haiping@php.net 34 | no 35 | 36 | 37 | Carlos Bueno 38 | aristus 39 | cmb@fb.com 40 | no 41 | 42 | 43 | Bill Fumerola 44 | billf 45 | bill@fb.com 46 | yes 47 | 48 | 49 | Scott MacVicar 50 | macvicar 51 | scott@fb.com 52 | yes 53 | 54 | 2009-03-28 55 | 56 | 0.9.2 57 | 0.9.2 58 | 59 | 60 | beta 61 | beta 62 | 63 | Apache 2.0 64 | 65 | -- Request #16544: Mac port for XHProf (svilen spasov) 66 | -- fix #16574: require/require_once not special cased like include/include_once (kannan) 67 | -- add a sanity test for sampling mode in xhprof (kannan) 68 | -- add support to ignore functions (such as call_user_func) during profiling (mike paleczny) 69 | -- fix #16098: suppress notices due to use of FILE_BINARY (kannan) 70 | -- add a sanity test for timer (kannan) 71 | -- fix for compile error on debian linux (russ) 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 5.2.0 144 | 145 | 146 | 1.4.0 147 | 148 | 149 | 150 | xhprof 151 | 152 | 153 | 154 | 155 | 0.9.1 156 | 0.9.1 157 | 158 | 2009-03-21 159 | 160 | beta 161 | beta 162 | 163 | 164 | -- doc improvements/fixes 165 | 166 | 167 | 168 | 169 | 0.9.0 170 | 0.9.0 171 | 172 | 2009-03-17 173 | 174 | beta 175 | beta 176 | 177 | 178 | -- initial release of xhprof 179 | 180 | 181 | 182 | 183 | 184 | 185 | -------------------------------------------------------------------------------- /xhprof_html/jquery/jquery.tooltip.js: -------------------------------------------------------------------------------- 1 | /* 2 | * jQuery Tooltip plugin 1.3 3 | * 4 | * http://bassistance.de/jquery-plugins/jquery-plugin-tooltip/ 5 | * http://docs.jquery.com/Plugins/Tooltip 6 | * 7 | * Copyright (c) 2006 - 2008 Jörn Zaefferer 8 | * 9 | * $Id: jquery.tooltip.js,v 1.1.1.1 2009-03-17 18:35:18 kannan Exp $ 10 | * 11 | * Dual licensed under the MIT and GPL licenses: 12 | * http://www.opensource.org/licenses/mit-license.php 13 | * http://www.gnu.org/licenses/gpl.html 14 | */ 15 | 16 | ;(function($) { 17 | 18 | // the tooltip element 19 | var helper = {}, 20 | // the current tooltipped element 21 | current, 22 | // the title of the current element, used for restoring 23 | title, 24 | // timeout id for delayed tooltips 25 | tID, 26 | // IE 5.5 or 6 27 | IE = $.browser.msie && /MSIE\s(5\.5|6\.)/.test(navigator.userAgent), 28 | // flag for mouse tracking 29 | track = false; 30 | 31 | $.tooltip = { 32 | blocked: false, 33 | defaults: { 34 | delay: 200, 35 | fade: false, 36 | showURL: true, 37 | extraClass: "", 38 | top: 15, 39 | left: 15, 40 | id: "tooltip" 41 | }, 42 | block: function() { 43 | $.tooltip.blocked = !$.tooltip.blocked; 44 | } 45 | }; 46 | 47 | $.fn.extend({ 48 | tooltip: function(settings) { 49 | settings = $.extend({}, $.tooltip.defaults, settings); 50 | createHelper(settings); 51 | return this.each(function() { 52 | $.data(this, "tooltip", settings); 53 | this.tOpacity = helper.parent.css("opacity"); 54 | // copy tooltip into its own expando and remove the title 55 | this.tooltipText = this.title; 56 | $(this).removeAttr("title"); 57 | // also remove alt attribute to prevent default tooltip in IE 58 | this.alt = ""; 59 | }) 60 | .mouseover(save) 61 | .mouseout(hide) 62 | .click(hide); 63 | }, 64 | fixPNG: IE ? function() { 65 | return this.each(function () { 66 | var image = $(this).css('backgroundImage'); 67 | if (image.match(/^url\(["']?(.*\.png)["']?\)$/i)) { 68 | image = RegExp.$1; 69 | $(this).css({ 70 | 'backgroundImage': 'none', 71 | 'filter': "progid:DXImageTransform.Microsoft.AlphaImageLoader(enabled=true, sizingMethod=crop, src='" + image + "')" 72 | }).each(function () { 73 | var position = $(this).css('position'); 74 | if (position != 'absolute' && position != 'relative') 75 | $(this).css('position', 'relative'); 76 | }); 77 | } 78 | }); 79 | } : function() { return this; }, 80 | unfixPNG: IE ? function() { 81 | return this.each(function () { 82 | $(this).css({'filter': '', backgroundImage: ''}); 83 | }); 84 | } : function() { return this; }, 85 | hideWhenEmpty: function() { 86 | return this.each(function() { 87 | $(this)[ $(this).html() ? "show" : "hide" ](); 88 | }); 89 | }, 90 | url: function() { 91 | return this.attr('href') || this.attr('src'); 92 | } 93 | }); 94 | 95 | function createHelper(settings) { 96 | // there can be only one tooltip helper 97 | if( helper.parent ) 98 | return; 99 | // create the helper, h3 for title, div for url 100 | helper.parent = $('

') 101 | // add to document 102 | .appendTo(document.body) 103 | // hide it at first 104 | .hide(); 105 | 106 | // apply bgiframe if available 107 | if ( $.fn.bgiframe ) 108 | helper.parent.bgiframe(); 109 | 110 | // save references to title and url elements 111 | helper.title = $('h3', helper.parent); 112 | helper.body = $('div.body', helper.parent); 113 | helper.url = $('div.url', helper.parent); 114 | } 115 | 116 | function settings(element) { 117 | return $.data(element, "tooltip"); 118 | } 119 | 120 | // main event handler to start showing tooltips 121 | function handle(event) { 122 | // show helper, either with timeout or on instant 123 | if( settings(this).delay ) 124 | tID = setTimeout(show, settings(this).delay); 125 | else 126 | show(); 127 | 128 | // if selected, update the helper position when the mouse moves 129 | track = !!settings(this).track; 130 | $(document.body).bind('mousemove', update); 131 | 132 | // update at least once 133 | update(event); 134 | } 135 | 136 | // save elements title before the tooltip is displayed 137 | function save() { 138 | // if this is the current source, or it has no title (occurs with click event), stop 139 | if ( $.tooltip.blocked || this == current || (!this.tooltipText && !settings(this).bodyHandler) ) 140 | return; 141 | 142 | // save current 143 | current = this; 144 | title = this.tooltipText; 145 | 146 | if ( settings(this).bodyHandler ) { 147 | helper.title.hide(); 148 | var bodyContent = settings(this).bodyHandler.call(this); 149 | if (bodyContent.nodeType || bodyContent.jquery) { 150 | helper.body.empty().append(bodyContent) 151 | } else { 152 | helper.body.html( bodyContent ); 153 | } 154 | helper.body.show(); 155 | } else if ( settings(this).showBody ) { 156 | var parts = title.split(settings(this).showBody); 157 | helper.title.html(parts.shift()).show(); 158 | helper.body.empty(); 159 | for(var i = 0, part; (part = parts[i]); i++) { 160 | if(i > 0) 161 | helper.body.append("
"); 162 | helper.body.append(part); 163 | } 164 | helper.body.hideWhenEmpty(); 165 | } else { 166 | helper.title.html(title).show(); 167 | helper.body.hide(); 168 | } 169 | 170 | // if element has href or src, add and show it, otherwise hide it 171 | if( settings(this).showURL && $(this).url() ) 172 | helper.url.html( $(this).url().replace('http://', '') ).show(); 173 | else 174 | helper.url.hide(); 175 | 176 | // add an optional class for this tip 177 | helper.parent.addClass(settings(this).extraClass); 178 | 179 | // fix PNG background for IE 180 | if (settings(this).fixPNG ) 181 | helper.parent.fixPNG(); 182 | 183 | handle.apply(this, arguments); 184 | } 185 | 186 | // delete timeout and show helper 187 | function show() { 188 | tID = null; 189 | if ((!IE || !$.fn.bgiframe) && settings(current).fade) { 190 | if (helper.parent.is(":animated")) 191 | helper.parent.stop().show().fadeTo(settings(current).fade, current.tOpacity); 192 | else 193 | helper.parent.is(':visible') ? helper.parent.fadeTo(settings(current).fade, current.tOpacity) : helper.parent.fadeIn(settings(current).fade); 194 | } else { 195 | helper.parent.show(); 196 | } 197 | update(); 198 | } 199 | 200 | /** 201 | * callback for mousemove 202 | * updates the helper position 203 | * removes itself when no current element 204 | */ 205 | function update(event) { 206 | if($.tooltip.blocked) 207 | return; 208 | 209 | if (event && event.target.tagName == "OPTION") { 210 | return; 211 | } 212 | 213 | // stop updating when tracking is disabled and the tooltip is visible 214 | if ( !track && helper.parent.is(":visible")) { 215 | $(document.body).unbind('mousemove', update) 216 | } 217 | 218 | // if no current element is available, remove this listener 219 | if( current == null ) { 220 | $(document.body).unbind('mousemove', update); 221 | return; 222 | } 223 | 224 | // remove position helper classes 225 | helper.parent.removeClass("viewport-right").removeClass("viewport-bottom"); 226 | 227 | var left = helper.parent[0].offsetLeft; 228 | var top = helper.parent[0].offsetTop; 229 | if (event) { 230 | // position the helper 15 pixel to bottom right, starting from mouse position 231 | left = event.pageX + settings(current).left; 232 | top = event.pageY + settings(current).top; 233 | var right='auto'; 234 | if (settings(current).positionLeft) { 235 | right = $(window).width() - left; 236 | left = 'auto'; 237 | } 238 | helper.parent.css({ 239 | left: left, 240 | right: right, 241 | top: top 242 | }); 243 | } 244 | 245 | var v = viewport(), 246 | h = helper.parent[0]; 247 | // check horizontal position 248 | if (v.x + v.cx < h.offsetLeft + h.offsetWidth) { 249 | left -= h.offsetWidth + 20 + settings(current).left; 250 | helper.parent.css({left: left + 'px'}).addClass("viewport-right"); 251 | } 252 | // check vertical position 253 | if (v.y + v.cy < h.offsetTop + h.offsetHeight) { 254 | top -= h.offsetHeight + 20 + settings(current).top; 255 | helper.parent.css({top: top + 'px'}).addClass("viewport-bottom"); 256 | } 257 | } 258 | 259 | function viewport() { 260 | return { 261 | x: $(window).scrollLeft(), 262 | y: $(window).scrollTop(), 263 | cx: $(window).width(), 264 | cy: $(window).height() 265 | }; 266 | } 267 | 268 | // hide helper and restore added classes and the title 269 | function hide(event) { 270 | if($.tooltip.blocked) 271 | return; 272 | // clear timeout if possible 273 | if(tID) 274 | clearTimeout(tID); 275 | // no more current element 276 | current = null; 277 | 278 | var tsettings = settings(this); 279 | function complete() { 280 | helper.parent.removeClass( tsettings.extraClass ).hide().css("opacity", ""); 281 | } 282 | if ((!IE || !$.fn.bgiframe) && tsettings.fade) { 283 | if (helper.parent.is(':animated')) 284 | helper.parent.stop().fadeTo(tsettings.fade, 0, complete); 285 | else 286 | helper.parent.stop().fadeOut(tsettings.fade, complete); 287 | } else 288 | complete(); 289 | 290 | if( settings(this).fixPNG ) 291 | helper.parent.unfixPNG(); 292 | } 293 | 294 | })(jQuery); 295 | -------------------------------------------------------------------------------- /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 = $(" 92 | 93 | 94 |
  • XHProf Overview

    95 | 96 |

    XHProf provides: 97 | 98 |

      99 |
    • Flat profile (screenshot) 100 | 101 |

      Function-level summary information such as number of calls, 102 | inclusive/exclusive wall time, memory usage, and CPU time. 103 | 104 |

    • Hierarchical profile (Parent/Child View) 105 | (screenshot) 106 | 107 |

      For each function, it provides a breakdown of calls and times per 108 | parent (caller) & child (callee), such as: 109 | 110 |

        111 | 112 |
      • what functions call a particular function and how many times? 113 | 114 |
      • what functions does a particular function call? 115 | 116 |
      • The total time spent under a function when called from a particular parent. 117 | 118 |
      119 | 120 |

    • Diff Reports 121 | 122 |

      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 |

    • Callgraph View (sample screenshot) 143 | 144 |

      The profile data can also be viewed as a callgraph. The callgraph 145 | view highlights the critical path of the program. 146 | 147 | 148 |

    • Memory Profile 149 | 150 |

      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 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 |
    167 | 168 |

    Terminology

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

    Naming convention for special functions

    185 | 186 |
      187 |

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

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

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

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

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

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

    Limitations

    222 | 223 |

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

    230 | 231 |

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

    236 | 237 |

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

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

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

    253 | 254 |

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

    256 | 257 |
  • Installing the XHProf Extension

    258 | 259 |

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


      262 | 263 |

      Note: A windows port hasn't been implemented yet. We have 264 | tested xhprof on Linux/FreeBSD so far. 265 | 266 |

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

      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 |


    283 | 284 |

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

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

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

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

    315 | 316 |

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

      319 |

      foo.php 320 |

      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 |

    Run the above test program: 351 | 352 |

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

    You should get an output like: 357 | 358 |

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

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

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

      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 |

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

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

    Skipping builtin functions during profiling 479 | 480 |

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

      487 | // do not profile builtin functions
      488 | xhprof_enable(XHPROF_FLAGS_NO_BUILTINS);
      489 | 
    490 | 491 | 492 |

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

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

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

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

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

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

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

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

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

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

    3. Managing XHProf Runs 554 | 555 |

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

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

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

      a) Saving XHProf data persistently: 573 | 574 |

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

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

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

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

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

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

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

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

    4. Accessing runs from UI 647 | 648 |

      a) Viewing a Single Run Report 649 | 650 |

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

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

      For example, 658 |

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

      b) Viewing a Diff Report 664 | 665 |

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

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

      c) Aggregate Report 673 | 674 |

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

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

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

      682 | 683 |

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

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

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

    697 | 698 |

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

      701 | 702 |
    • CPU timer (getrusage) on Linux has high overheads. It is also 703 | coarse grained (millisec accuracy rather than microsec level) to be 704 | useful at function level. Therefore, the skew in reported numbers 705 | when using XHPROF_FLAGS_CPU mode tends to be higher. 706 | 707 |

      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 |

        
        712 |   // elapsed time profiling (default) + memory profiling
        713 |   xhprof_enable(XHPROF_FLAGS_MEMORY);
        714 | 
        715 |

      716 | 717 | 718 |
    • Profiling a random sample of pages/requests works well in capturing 719 | data that is representative of your production workload. 720 | 721 |

      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 |
  • Lightweight Sampling Mode

    752 | 753 |

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

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

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

  • Additional Features

  • 768 | 769 |

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

    For example: 774 | 775 |

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

  • 800 | 801 | 814 |
  • Acknowledgements

    815 | 816 |

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

  • 822 | 823 | 824 | 825 | 826 | 827 | -------------------------------------------------------------------------------- /xhprof_html/docs/index-fr.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Documentation XHProf (Brouillon) 7 | 8 | 9 |

    Documentation XHProf (Brouillon)

    10 | 11 | Sommaire 12 |
      13 |
    1. Introduction
    2. 14 | 15 |
    3. Présentation
    4. 16 | 17 |
    5. Installer l’extension XHProf
    6. 18 | 19 |
    7. Profiler avec XHProf
    8. 20 | 21 |
    9. Définir un environnement graphique pour XHProf
    10. 22 | 23 |
    11. Notes sur l’utilisation d’XHProf en production
    12. 24 | 25 |
    13. Mode aléatoire et léger 26 | 27 |
    14. Fonctionnalités supplémentaires
    15. 28 | 29 |
    16. Dépendences
    17. 30 | 31 |
    18. Remerciements
    19. 32 |
    33 | 34 |
      35 |
    1. Introduction

      36 | 37 |

      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 |

        66 |
        67 | 68 |

        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 |


        75 |
      76 | 77 | 78 |

      Développé à l’origine par Facebook, XHProf est maintenant open source depuis mars 2009.

      79 | 80 | 81 | 82 | 83 | 84 |
    2. Présentation

      85 | 86 |

      XHProf offre: 87 | 88 |

        89 |
      • Un rendu tabulaire (copie d’écran) 90 | 91 |

        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 |

      • Un rendu hiérarchique (Vue parent/enfant) 95 | (copie d’écran) 96 | 97 |

        Pour chaque fonction, il fournit le détail des appels et le temps par 98 | parent (appelant) & enfant (appelé), tel que : 99 | 100 |

          101 | 102 |
        • quelle fonctions appelle quelle fonction précisement et combien de fois ? 103 | 104 |
        • Quelles fonctions font un appel particulier ? 105 | 106 |
        • Le temps total passé dans une fonction appelé par un parent bien précis. 107 | 108 |
        109 | 110 |

      • Comparateur de rapports 111 | 112 |

        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 |

      • Callgraph View (copie d’écran) 127 | 128 |

        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 |

      • Profilage mémoire 132 | 133 |

        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 |

      • XHProf trace les opération 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 |
      146 | 147 |

      Terminologie

      148 |
        149 | 150 |
      1. Temps inclusive (ou temps du sous-ensemble): 151 | Inclus le temps passé dans la fonction et celui passé dans les fonctions descendantes (filles). 152 | 153 |
      2. Temps exclusive (ou temps "propre"): Mesure le temps passé dans la fonction elle-même et n’inclus pas le temps passé dans les fonctions descendantes. 154 | 155 |
      3. Wall Time: Temps passé ou temps ressenti. 156 | 157 |
      4. CPU Time: Charge CPU sur les process utilisateur + charge CPU sur les process noyaux 158 | 159 |
      160 |

      Convention de nommage pour les fonctions spéciales

      161 | 162 |
        163 |

      1. main(): Une fonction fictive qui est à la racine de la pile d’appel. 164 | 165 | 166 |

      2. 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 |

          174 | 175 |
        • 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 |
        181 | 182 |

      3. 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 |
      186 | 187 | 188 |

      Limitations

      189 | 190 |

      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 |
    3. Installer l’extension XHProf

      218 | 219 |

      L’extension se trouve dans le sous-répertoire "extension/". 220 | 221 |


        222 | 223 |

        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 |


      239 | 240 |

      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 |
    4. Profiler avec XHProf

      268 | 269 |

      Test de génération de donées brutes avec l’exemple simple d’un programme tel que : 270 | 271 |

        272 |

        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 |
    5. 471 | 472 |
    6. Définir un environnement graphique pour XHProf

      473 | 474 | 475 |
        476 | 477 |
      1. Structure de la source PHP 478 |

        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 |

          484 |
        • 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 |
        488 | 489 |

        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 |

      2. 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 |

      3. 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 |

      4. 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 |

        611 | 612 |

        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 |

        620 | 621 |
      622 | 623 |
    7. Notes sur l’utilisation d’XHProf en production

      624 | 625 |

      Quelques observations qui peuvent faire varier votre expérience : 626 | 627 |

        628 | 629 |
      • Le timer CPU (getrusage) sur Linux peut avoir des dépassements de capacité. Il a également un rendu granuleux 630 | (Une précision à la milliseconde plutôt qu’à la microseconde) pour être efficace au niveau des méthodes. 631 | En conséquence, les valeurs rapportées en utilisant le mode XHPROF_FLAGS_CPU on tendance à être plus élevés. 632 | 633 |

        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 |

        641 | 642 | 643 |
      • Profiler une plage aléatoire de pages/requêtes est efficace pour récupérer des données représentatives 644 | de votre environnement de production. 645 | 646 |

        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 |
    8. Mode d’échantillonage léger

      674 | 675 |

      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 |

    9. Fonctionnalités supplémentaires

    10. 686 | 687 |

      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 |

        694 | 695 | 696 |

      • 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 |
      708 | 709 |
        710 | 711 |
      712 | 713 |
    11. Dependances

    12. 714 | 715 |
        716 |
      • JQuery Javascript: Pour les bulles d’aides et les noms de fonctions de pile, 717 | nous utilisons la librairie Javascript, JQuery. JQuery est disponible sous les licences MIT et GPL 718 | (http://docs.jquery.com/Licensing). Ce code JQuery, utilisé par XHProf, se trouve dans le 719 | sous répertoire xhprof_html/jquery. 720 | 721 |
      • dot (utilitaire de génération d’image): Le fonctionnalité de rendu graphique 722 | ([View Callgraph]) est présente grâce à la présence de Graphviz "dot" dans votre path. 723 | "dot" est un utilitaire de dessin et de gén"ration d’image. 724 | 725 |
      726 |
    13. Remerciements

      727 | 728 |

      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 |

    14. 734 | 735 |
    736 | 737 | 738 | 739 | --------------------------------------------------------------------------------