Correct Aphlict websocket URI construction after PHP8 compatibility changes
[phabricator.git] / externals / xhprof / xhprof_lib.php
blob8f8985ed269861ab5c36be4589503ce6b042cafc
1 <?php
2 // Copyright (c) 2009 Facebook
3 //
4 // Licensed under the Apache License, Version 2.0 (the "License");
5 // you may not use this file except in compliance with the License.
6 // You may obtain a copy of the License at
7 //
8 // http://www.apache.org/licenses/LICENSE-2.0
9 //
10 // Unless required by applicable law or agreed to in writing, software
11 // distributed under the License is distributed on an "AS IS" BASIS,
12 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 // See the License for the specific language governing permissions and
14 // limitations under the License.
18 // This file contains various XHProf library (utility) functions.
19 // Do not add any display specific code here.
22 function xhprof_error($message) {
23 error_log($message);
27 * The list of possible metrics collected as part of XHProf that
28 * require inclusive/exclusive handling while reporting.
30 * @author Kannan
32 function xhprof_get_possible_metrics() {
33 static $possible_metrics =
34 array("wt" => array("Wall", "microsecs", "walltime" ),
35 "ut" => array("User", "microsecs", "user cpu time" ),
36 "st" => array("Sys", "microsecs", "system cpu time"),
37 "cpu" => array("Cpu", "microsecs", "cpu time"),
38 "mu" => array("MUse", "bytes", "memory usage"),
39 "pmu" => array("PMUse", "bytes", "peak memory usage"),
40 "samples" => array("Samples", "samples", "cpu time"));
41 return $possible_metrics;
45 * Get the list of metrics present in $xhprof_data as an array.
47 * @author Kannan
49 function xhprof_get_metrics($xhprof_data) {
51 // get list of valid metrics
52 $possible_metrics = xhprof_get_possible_metrics();
54 // return those that are present in the raw data.
55 // We'll just look at the root of the subtree for this.
56 $metrics = array();
57 foreach ($possible_metrics as $metric => $desc) {
58 if (isset($xhprof_data["main()"][$metric])) {
59 $metrics[] = $metric;
63 return $metrics;
66 /**
67 * Takes a parent/child function name encoded as
68 * "a==>b" and returns array("a", "b").
70 * @author Kannan
72 function xhprof_parse_parent_child($parent_child) {
73 $ret = explode("==>", $parent_child);
75 // Return if both parent and child are set
76 if (isset($ret[1])) {
77 return $ret;
80 return array(null, $ret[0]);
83 /**
84 * Given parent & child function name, composes the key
85 * in the format present in the raw data.
87 * @author Kannan
89 function xhprof_build_parent_child_key($parent, $child) {
90 if ($parent) {
91 return $parent . "==>" . $child;
92 } else {
93 return $child;
98 /**
99 * Checks if XHProf raw data appears to be valid and not corrupted.
101 * @param int $run_id Run id of run to be pruned.
102 * [Used only for reporting errors.]
103 * @param array $raw_data XHProf raw data to be pruned
104 * & validated.
106 * @return bool true on success, false on failure
108 * @author Kannan
110 function xhprof_valid_run($run_id, $raw_data) {
112 $main_info = $raw_data["main()"];
113 if (empty($main_info)) {
114 xhprof_error("XHProf: main() missing in raw data for Run ID: $run_id");
115 return false;
118 // raw data should contain either wall time or samples information...
119 if (isset($main_info["wt"])) {
120 $metric = "wt";
121 } else if (isset($main_info["samples"])) {
122 $metric = "samples";
123 } else {
124 xhprof_error("XHProf: Wall Time information missing from Run ID: $run_id");
125 return false;
128 foreach ($raw_data as $info) {
129 $val = $info[$metric];
131 // basic sanity checks...
132 if ($val < 0) {
133 xhprof_error("XHProf: $metric should not be negative: Run ID $run_id"
134 . serialize($info));
135 return false;
137 if ($val > (86400000000)) {
138 xhprof_error("XHProf: $metric > 1 day found in Run ID: $run_id "
139 . serialize($info));
140 return false;
143 return true;
148 * Return a trimmed version of the XHProf raw data. Note that the raw
149 * data contains one entry for each unique parent/child function
150 * combination.The trimmed version of raw data will only contain
151 * entries where either the parent or child function is in the list
152 * of $functions_to_keep.
154 * Note: Function main() is also always kept so that overall totals
155 * can still be obtained from the trimmed version.
157 * @param array XHProf raw data
158 * @param array array of function names
160 * @return array Trimmed XHProf Report
162 * @author Kannan
164 function xhprof_trim_run($raw_data, $functions_to_keep) {
166 // convert list of functions to a hash with function as the key
167 $function_map = array_fill_keys($functions_to_keep, 1);
169 // always keep main() as well so that overall totals can still
170 // be computed if need be.
171 $function_map['main()'] = 1;
173 $new_raw_data = array();
174 foreach ($raw_data as $parent_child => $info) {
175 list($parent, $child) = xhprof_parse_parent_child($parent_child);
177 if (isset($function_map[$parent]) || isset($function_map[$child])) {
178 $new_raw_data[$parent_child] = $info;
182 return $new_raw_data;
186 * Takes raw XHProf data that was aggregated over "$num_runs" number
187 * of runs averages/nomalizes the data. Essentially the various metrics
188 * collected are divided by $num_runs.
190 * @author Kannan
192 function xhprof_normalize_metrics($raw_data, $num_runs) {
194 if (empty($raw_data) || ($num_runs == 0)) {
195 return $raw_data;
198 $raw_data_total = array();
200 if (isset($raw_data["==>main()"]) && isset($raw_data["main()"])) {
201 xhprof_error("XHProf Error: both ==>main() and main() set in raw data...");
204 foreach ($raw_data as $parent_child => $info) {
205 foreach ($info as $metric => $value) {
206 $raw_data_total[$parent_child][$metric] = ($value / $num_runs);
210 return $raw_data_total;
215 * Get raw data corresponding to specified array of runs
216 * aggregated by certain weightage.
218 * Suppose you have run:5 corresponding to page1.php,
219 * run:6 corresponding to page2.php,
220 * and run:7 corresponding to page3.php
222 * and you want to accumulate these runs in a 2:4:1 ratio. You
223 * can do so by calling:
225 * xhprof_aggregate_runs(array(5, 6, 7), array(2, 4, 1));
227 * The above will return raw data for the runs aggregated
228 * in 2:4:1 ratio.
230 * @param object $xhprof_runs_impl An object that implements
231 * the iXHProfRuns interface
232 * @param array $runs run ids of the XHProf runs..
233 * @param array $wts integral (ideally) weights for $runs
234 * @param string $source source to fetch raw data for run from
235 * @param bool $use_script_name If true, a fake edge from main() to
236 * to __script::<scriptname> is introduced
237 * in the raw data so that after aggregations
238 * the script name is still preserved.
240 * @return array Return aggregated raw data
242 * @author Kannan
244 function xhprof_aggregate_runs($xhprof_runs_impl, $runs,
245 $wts, $source="phprof",
246 $use_script_name=false) {
248 $raw_data_total = null;
249 $raw_data = null;
250 $metrics = array();
252 $run_count = count($runs);
253 $wts_count = count($wts);
255 if (($run_count == 0) ||
256 (($wts_count > 0) && ($run_count != $wts_count))) {
257 return array('description' => 'Invalid input..',
258 'raw' => null);
261 $bad_runs = array();
262 foreach($runs as $idx => $run_id) {
264 $raw_data = $xhprof_runs_impl->get_run($run_id, $source, '?');
266 // use the first run to derive what metrics to aggregate on.
267 if ($idx == 0) {
268 foreach ($raw_data["main()"] as $metric => $val) {
269 if ($metric != "pmu") {
270 // for now, just to keep data size small, skip "peak" memory usage
271 // data while aggregating.
272 // The "regular" memory usage data will still be tracked.
273 if (isset($val)) {
274 $metrics[] = $metric;
280 if (!xhprof_valid_run($run_id, $raw_data)) {
281 $bad_runs[] = $run_id;
282 continue;
285 if ($use_script_name) {
286 $page = '?';
288 // create a fake function '__script::$page', and have and edge from
289 // main() to '__script::$page'. We will also need edges to transfer
290 // all edges originating from main() to now originate from
291 // '__script::$page' to all function called from main().
293 // We also weight main() ever so slightly higher so that
294 // it shows up above the new entry in reports sorted by
295 // inclusive metrics or call counts.
296 if ($page) {
297 foreach($raw_data["main()"] as $metric => $val) {
298 $fake_edge[$metric] = $val;
299 $new_main[$metric] = $val + 0.00001;
301 $raw_data["main()"] = $new_main;
302 $raw_data[xhprof_build_parent_child_key("main()",
303 "__script::$page")]
304 = $fake_edge;
305 } else {
306 $use_script_name = false;
310 // if no weights specified, use 1 as the default weightage..
311 $wt = ($wts_count == 0) ? 1 : $wts[$idx];
313 // aggregate $raw_data into $raw_data_total with appropriate weight ($wt)
314 foreach ($raw_data as $parent_child => $info) {
315 if ($use_script_name) {
316 // if this is an old edge originating from main(), it now
317 // needs to be from '__script::$page'
318 if (substr($parent_child, 0, 9) == "main()==>") {
319 $child =substr($parent_child, 9);
320 // ignore the newly added edge from main()
321 if (substr($child, 0, 10) != "__script::") {
322 $parent_child = xhprof_build_parent_child_key("__script::$page",
323 $child);
328 if (!isset($raw_data_total[$parent_child])) {
329 foreach ($metrics as $metric) {
330 $raw_data_total[$parent_child][$metric] = ($wt * $info[$metric]);
332 } else {
333 foreach ($metrics as $metric) {
334 $raw_data_total[$parent_child][$metric] += ($wt * $info[$metric]);
340 $runs_string = implode(",", $runs);
342 if (isset($wts)) {
343 $wts_string = "in the ratio (" . implode(":", $wts) . ")";
344 $normalization_count = array_sum($wts);
345 } else {
346 $wts_string = "";
347 $normalization_count = $run_count;
350 $run_count = $run_count - count($bad_runs);
352 $data['description'] = "Aggregated Report for $run_count runs: ".
353 "$runs_string $wts_string\n";
354 $data['raw'] = xhprof_normalize_metrics($raw_data_total,
355 $normalization_count);
356 $data['bad_runs'] = $bad_runs;
358 return $data;
363 * Analyze hierarchical raw data, and compute per-function (flat)
364 * inclusive and exclusive metrics.
366 * Also, store overall totals in the 2nd argument.
368 * @param array $raw_data XHProf format raw profiler data.
369 * @param array &$overall_totals OUT argument for returning
370 * overall totals for various
371 * metrics.
372 * @return array Returns a map from function name to its
373 * call count and inclusive & exclusive metrics
374 * (such as wall time, etc.).
376 * @author Kannan Muthukkaruppan
378 function xhprof_compute_flat_info($raw_data, &$overall_totals) {
380 global $display_calls;
382 $metrics = xhprof_get_metrics($raw_data);
384 $overall_totals = array( "ct" => 0,
385 "wt" => 0,
386 "ut" => 0,
387 "st" => 0,
388 "cpu" => 0,
389 "mu" => 0,
390 "pmu" => 0,
391 "samples" => 0
394 // compute inclusive times for each function
395 $symbol_tab = xhprof_compute_inclusive_times($raw_data);
397 /* total metric value is the metric value for "main()" */
398 foreach ($metrics as $metric) {
399 $overall_totals[$metric] = $symbol_tab["main()"][$metric];
403 * initialize exclusive (self) metric value to inclusive metric value
404 * to start with.
405 * In the same pass, also add up the total number of function calls.
407 foreach ($symbol_tab as $symbol => $info) {
408 foreach ($metrics as $metric) {
409 $symbol_tab[$symbol]["excl_" . $metric] = $symbol_tab[$symbol][$metric];
411 if ($display_calls) {
412 /* keep track of total number of calls */
413 $overall_totals["ct"] += $info["ct"];
417 /* adjust exclusive times by deducting inclusive time of children */
418 foreach ($raw_data as $parent_child => $info) {
419 list($parent, $child) = xhprof_parse_parent_child($parent_child);
421 if ($parent) {
422 foreach ($metrics as $metric) {
423 // make sure the parent exists hasn't been pruned.
424 if (isset($symbol_tab[$parent])) {
425 $symbol_tab[$parent]["excl_" . $metric] -= $info[$metric];
431 return $symbol_tab;
435 * Hierarchical diff:
436 * Compute and return difference of two call graphs: Run2 - Run1.
438 * @author Kannan
440 function xhprof_compute_diff($xhprof_data1, $xhprof_data2) {
441 global $display_calls;
443 // use the second run to decide what metrics we will do the diff on
444 $metrics = xhprof_get_metrics($xhprof_data2);
446 $xhprof_delta = $xhprof_data2;
448 foreach ($xhprof_data1 as $parent_child => $info) {
450 if (!isset($xhprof_delta[$parent_child])) {
452 // this pc combination was not present in run1;
453 // initialize all values to zero.
454 if ($display_calls) {
455 $xhprof_delta[$parent_child] = array("ct" => 0);
456 } else {
457 $xhprof_delta[$parent_child] = array();
459 foreach ($metrics as $metric) {
460 $xhprof_delta[$parent_child][$metric] = 0;
464 if ($display_calls) {
465 $xhprof_delta[$parent_child]["ct"] -= $info["ct"];
468 foreach ($metrics as $metric) {
469 $xhprof_delta[$parent_child][$metric] -= $info[$metric];
473 return $xhprof_delta;
478 * Compute inclusive metrics for function. This code was factored out
479 * of xhprof_compute_flat_info().
481 * The raw data contains inclusive metrics of a function for each
482 * unique parent function it is called from. The total inclusive metrics
483 * for a function is therefore the sum of inclusive metrics for the
484 * function across all parents.
486 * @return array Returns a map of function name to total (across all parents)
487 * inclusive metrics for the function.
489 * @author Kannan
491 function xhprof_compute_inclusive_times($raw_data) {
492 global $display_calls;
494 $metrics = xhprof_get_metrics($raw_data);
496 $symbol_tab = array();
499 * First compute inclusive time for each function and total
500 * call count for each function across all parents the
501 * function is called from.
503 foreach ($raw_data as $parent_child => $info) {
505 list($parent, $child) = xhprof_parse_parent_child($parent_child);
507 if ($parent == $child) {
509 * XHProf PHP extension should never trigger this situation any more.
510 * Recursion is handled in the XHProf PHP extension by giving nested
511 * calls a unique recursion-depth appended name (for example, foo@1).
513 xhprof_error("Error in Raw Data: parent & child are both: $parent");
514 return;
517 if (!isset($symbol_tab[$child])) {
519 if ($display_calls) {
520 $symbol_tab[$child] = array("ct" => $info["ct"]);
521 } else {
522 $symbol_tab[$child] = array();
524 foreach ($metrics as $metric) {
525 $symbol_tab[$child][$metric] = $info[$metric];
527 } else {
528 if ($display_calls) {
529 /* increment call count for this child */
530 $symbol_tab[$child]["ct"] += $info["ct"];
533 /* update inclusive times/metric for this child */
534 foreach ($metrics as $metric) {
535 $symbol_tab[$child][$metric] += $info[$metric];
540 return $symbol_tab;
545 * Prunes XHProf raw data:
547 * Any node whose inclusive walltime accounts for less than $prune_percent
548 * of total walltime is pruned. [It is possible that a child function isn't
549 * pruned, but one or more of its parents get pruned. In such cases, when
550 * viewing the child function's hierarchical information, the cost due to
551 * the pruned parent(s) will be attributed to a special function/symbol
552 * "__pruned__()".]
554 * @param array $raw_data XHProf raw data to be pruned & validated.
555 * @param double $prune_percent Any edges that account for less than
556 * $prune_percent of time will be pruned
557 * from the raw data.
559 * @return array Returns the pruned raw data.
561 * @author Kannan
563 function xhprof_prune_run($raw_data, $prune_percent) {
565 $main_info = $raw_data["main()"];
566 if (empty($main_info)) {
567 xhprof_error("XHProf: main() missing in raw data");
568 return false;
571 // raw data should contain either wall time or samples information...
572 if (isset($main_info["wt"])) {
573 $prune_metric = "wt";
574 } else if (isset($main_info["samples"])) {
575 $prune_metric = "samples";
576 } else {
577 xhprof_error("XHProf: for main() we must have either wt "
578 ."or samples attribute set");
579 return false;
582 // determine the metrics present in the raw data..
583 $metrics = array();
584 foreach ($main_info as $metric => $val) {
585 if (isset($val)) {
586 $metrics[] = $metric;
590 $prune_threshold = (($main_info[$prune_metric] * $prune_percent) / 100.0);
592 // init_metrics($raw_data, null, null, false);
593 $flat_info = xhprof_compute_inclusive_times($raw_data);
595 foreach ($raw_data as $parent_child => $info) {
597 list($parent, $child) = xhprof_parse_parent_child($parent_child);
599 // is this child's overall total from all parents less than threshold?
600 if ($flat_info[$child][$prune_metric] < $prune_threshold) {
601 unset($raw_data[$parent_child]); // prune the edge
602 } else if ($parent &&
603 ($parent != "__pruned__()") &&
604 ($flat_info[$parent][$prune_metric] < $prune_threshold)) {
606 // Parent's overall inclusive metric is less than a threshold.
607 // All edges to the parent node will get nuked, and this child will
608 // be a dangling child.
609 // So instead change its parent to be a special function __pruned__().
610 $pruned_edge = xhprof_build_parent_child_key("__pruned__()", $child);
612 if (isset($raw_data[$pruned_edge])) {
613 foreach ($metrics as $metric) {
614 $raw_data[$pruned_edge][$metric]+=$raw_data[$parent_child][$metric];
616 } else {
617 $raw_data[$pruned_edge] = $raw_data[$parent_child];
620 unset($raw_data[$parent_child]); // prune the edge
624 return $raw_data;
629 * Set one key in an array and return the array
631 * @author Kannan
633 function xhprof_array_set($arr, $k, $v) {
634 $arr[$k] = $v;
635 return $arr;
639 * Removes/unsets one key in an array and return the array
641 * @author Kannan
643 function xhprof_array_unset($arr, $k) {
644 unset($arr[$k]);
645 return $arr;
649 * Type definitions for URL params
651 define('XHPROF_STRING_PARAM', 1);
652 define('XHPROF_UINT_PARAM', 2);
653 define('XHPROF_FLOAT_PARAM', 3);
654 define('XHPROF_BOOL_PARAM', 4);
658 * Internal helper function used by various
659 * xhprof_get_param* flavors for various
660 * types of parameters.
662 * @param string name of the URL query string param
664 * @author Kannan
666 function xhprof_get_param_helper($param) {
667 $val = null;
668 if (isset($_GET[$param]))
669 $val = $_GET[$param];
670 else if (isset($_POST[$param])) {
671 $val = $_POST[$param];
673 return $val;
677 * Extracts value for string param $param from query
678 * string. If param is not specified, return the
679 * $default value.
681 * @author Kannan
683 function xhprof_get_string_param($param, $default = '') {
684 $val = xhprof_get_param_helper($param);
686 if ($val === null)
687 return $default;
689 return $val;
693 * Extracts value for unsigned integer param $param from
694 * query string. If param is not specified, return the
695 * $default value.
697 * If value is not a valid unsigned integer, logs error
698 * and returns null.
700 * @author Kannan
702 function xhprof_get_uint_param($param, $default = 0) {
703 $val = xhprof_get_param_helper($param);
705 if ($val === null)
706 $val = $default;
708 // trim leading/trailing whitespace
709 $val = trim($val);
711 // if it only contains digits, then ok..
712 if (ctype_digit($val)) {
713 return $val;
716 xhprof_error("$param is $val. It must be an unsigned integer.");
717 return null;
722 * Extracts value for a float param $param from
723 * query string. If param is not specified, return
724 * the $default value.
726 * If value is not a valid unsigned integer, logs error
727 * and returns null.
729 * @author Kannan
731 function xhprof_get_float_param($param, $default = 0) {
732 $val = xhprof_get_param_helper($param);
734 if ($val === null)
735 $val = $default;
737 // trim leading/trailing whitespace
738 $val = trim($val);
740 // TBD: confirm the value is indeed a float.
741 if (true) // for now..
742 return (float)$val;
744 xhprof_error("$param is $val. It must be a float.");
745 return null;
749 * Extracts value for a boolean param $param from
750 * query string. If param is not specified, return
751 * the $default value.
753 * If value is not a valid unsigned integer, logs error
754 * and returns null.
756 * @author Kannan
758 function xhprof_get_bool_param($param, $default = false) {
759 $val = xhprof_get_param_helper($param);
761 if ($val === null)
762 $val = $default;
764 // trim leading/trailing whitespace
765 $val = trim($val);
767 switch (strtolower($val)) {
768 case '0':
769 case '1':
770 $val = (bool)$val;
771 break;
772 case 'true':
773 case 'on':
774 case 'yes':
775 $val = true;
776 break;
777 case 'false':
778 case 'off':
779 case 'no':
780 $val = false;
781 break;
782 default:
783 xhprof_error("$param is $val. It must be a valid boolean string.");
784 return null;
787 return $val;
792 * Initialize params from URL query string. The function
793 * creates globals variables for each of the params
794 * and if the URL query string doesn't specify a particular
795 * param initializes them with the corresponding default
796 * value specified in the input.
798 * @params array $params An array whose keys are the names
799 * of URL params who value needs to
800 * be retrieved from the URL query
801 * string. PHP globals are created
802 * with these names. The value is
803 * itself an array with 2-elems (the
804 * param type, and its default value).
805 * If a param is not specified in the
806 * query string the default value is
807 * used.
808 * @author Kannan
810 function xhprof_param_init($params) {
811 /* Create variables specified in $params keys, init defaults */
812 foreach ($params as $k => $v) {
813 switch ($v[0]) {
814 case XHPROF_STRING_PARAM:
815 $p = xhprof_get_string_param($k, $v[1]);
816 break;
817 case XHPROF_UINT_PARAM:
818 $p = xhprof_get_uint_param($k, $v[1]);
819 break;
820 case XHPROF_FLOAT_PARAM:
821 $p = xhprof_get_float_param($k, $v[1]);
822 break;
823 case XHPROF_BOOL_PARAM:
824 $p = xhprof_get_bool_param($k, $v[1]);
825 break;
826 default:
827 xhprof_error("Invalid param type passed to xhprof_param_init: "
828 . $v[0]);
829 exit();
832 // create a global variable using the parameter name.
833 $GLOBALS[$k] = $p;
839 * Given a partial query string $q return matching function names in
840 * specified XHProf run. This is used for the type ahead function
841 * selector.
843 * @author Kannan
845 function xhprof_get_matching_functions($q, $xhprof_data) {
847 $matches = array();
849 foreach ($xhprof_data as $parent_child => $info) {
850 list($parent, $child) = xhprof_parse_parent_child($parent_child);
851 if (stripos($parent, $q) !== false) {
852 $matches[$parent] = 1;
854 if (stripos($child, $q) !== false) {
855 $matches[$child] = 1;
859 $res = array_keys($matches);
861 // sort it so the answers are in some reliable order...
862 asort($res);
864 return ($res);