2 if(!defined('sugarEntry') || !sugarEntry) die('Not A Valid Entry Point');
3 // Copyright (c) 2009 Facebook
5 // Licensed under the Apache License, Version 2.0 (the "License");
6 // you may not use this file except in compliance with the License.
7 // You may obtain a copy of the License at
9 // http://www.apache.org/licenses/LICENSE-2.0
11 // Unless required by applicable law or agreed to in writing, software
12 // distributed under the License is distributed on an "AS IS" BASIS,
13 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 // See the License for the specific language governing permissions and
15 // limitations under the License.
19 // This file contains various XHProf library (utility) functions.
20 // Do not add any display specific code here.
23 function xhprof_error($message) {
28 * The list of possible metrics collected as part of XHProf that
29 * require inclusive/exclusive handling while reporting.
33 function xhprof_get_possible_metrics() {
34 static $possible_metrics =
35 array("wt" => array("Wall", "microsecs", "walltime"),
36 "ut" => array("User", "microsecs", "user cpu time"),
37 "st" => array("Sys", "microsecs", "system cpu time"),
38 "cpu" => array("Cpu", "microsecs", "cpu time"),
39 "mu" => array("MUse", "bytes", "memory usage"),
40 "pmu" => array("PMUse", "bytes", "peak memory usage"),
41 "samples" => array("Samples", "samples", "cpu time"));
42 return $possible_metrics;
46 * Initialize the metrics we'll display based on the information
51 function init_metrics($xhprof_data, $rep_symbol, $sort, $diff_report = false) {
56 global $sortable_columns;
58 global $display_calls;
60 $diff_mode = $diff_report;
63 if (array_key_exists($sort, $sortable_columns)) {
66 print("Invalid Sort Key $sort specified in URL");
70 // For C++ profiler runs, walltime attribute isn't present.
71 // In that case, use "samples" as the default sort column.
72 if (!isset($xhprof_data["main()"]["wt"])) {
74 if ($sort_col == "wt") {
75 $sort_col = "samples";
78 // C++ profiler data doesn't have call counts.
79 // ideally we should check to see if "ct" metric
80 // is present for "main()". But currently "ct"
81 // metric is artificially set to 1. So, relying
82 // on absence of "wt" metric instead.
83 $display_calls = false;
85 $display_calls = true;
88 // parent/child report doesn't support exclusive times yet.
89 // So, change sort hyperlinks to closest fit.
90 if (!empty($rep_symbol)) {
91 $sort_col = str_replace("excl_", "", $sort_col);
95 $stats = array("fn", "ct", "Calls%");
102 $possible_metrics = xhprof_get_possible_metrics($xhprof_data);
103 foreach ($possible_metrics as $metric => $desc) {
104 if (isset($xhprof_data["main()"][$metric])) {
105 $metrics[] = $metric;
106 // flat (top-level reports): we can compute
107 // exclusive metrics reports as well.
109 $stats[] = "I" . $desc[0] . "%";
110 $stats[] = "excl_" . $metric;
111 $stats[] = "E" . $desc[0] . "%";
113 // parent/child report for a function: we can
114 // only breakdown inclusive times correctly.
115 $pc_stats[] = $metric;
116 $pc_stats[] = "I" . $desc[0] . "%";
122 * Get the list of metrics present in $xhprof_data as an array.
126 function xhprof_get_metrics($xhprof_data) {
128 // get list of valid metrics
129 $possible_metrics = xhprof_get_possible_metrics();
131 // return those that are present in the raw data.
132 // We'll just look at the root of the subtree for this.
134 foreach ($possible_metrics as $metric => $desc) {
135 if (isset($xhprof_data["main()"][$metric])) {
136 $metrics[] = $metric;
144 * Takes a parent/child function name encoded as
145 * "a==>b" and returns array("a", "b").
149 function xhprof_parse_parent_child($parent_child) {
150 $ret = explode("==>", $parent_child);
152 // Return if both parent and child are set
153 if (isset($ret[1])) {
157 return array(null, $ret[0]);
161 * Given parent & child function name, composes the key
162 * in the format present in the raw data.
166 function xhprof_build_parent_child_key($parent, $child) {
168 return $parent . "==>" . $child;
176 * Checks if XHProf raw data appears to be valid and not corrupted.
178 * @param int $run_id Run id of run to be pruned.
179 * [Used only for reporting errors.]
180 * @param array $raw_data XHProf raw data to be pruned
183 * @return bool true on success, false on failure
187 function xhprof_valid_run($run_id, $raw_data) {
189 $main_info = $raw_data["main()"];
190 if (empty($main_info)) {
191 xhprof_error("XHProf: main() missing in raw data for Run ID: $run_id");
195 // raw data should contain either wall time or samples information...
196 if (isset($main_info["wt"])) {
198 } else if (isset($main_info["samples"])) {
201 xhprof_error("XHProf: Wall Time information missing from Run ID: $run_id");
205 foreach ($raw_data as $info) {
206 $val = $info[$metric];
208 // basic sanity checks...
210 xhprof_error("XHProf: $metric should not be negative: Run ID $run_id"
214 if ($val > (86400000000)) {
215 xhprof_error("XHProf: $metric > 1 day found in Run ID: $run_id "
225 * Return a trimmed version of the XHProf raw data. Note that the raw
226 * data contains one entry for each unique parent/child function
227 * combination.The trimmed version of raw data will only contain
228 * entries where either the parent or child function is in the list
229 * of $functions_to_keep.
231 * Note: Function main() is also always kept so that overall totals
232 * can still be obtained from the trimmed version.
234 * @param array XHProf raw data
235 * @param array array of function names
237 * @return array Trimmed XHProf Report
241 function xhprof_trim_run($raw_data, $functions_to_keep) {
243 // convert list of functions to a hash with function as the key
244 $function_map = array_fill_keys($functions_to_keep, 1);
246 // always keep main() as well so that overall totals can still
247 // be computed if need be.
248 $function_map['main()'] = 1;
250 $new_raw_data = array();
251 foreach ($raw_data as $parent_child => $info) {
252 list($parent, $child) = xhprof_parse_parent_child($parent_child);
254 if (isset($function_map[$parent]) || isset($function_map[$child])) {
255 $new_raw_data[$parent_child] = $info;
259 return $new_raw_data;
263 * Takes raw XHProf data that was aggregated over "$num_runs" number
264 * of runs averages/nomalizes the data. Essentially the various metrics
265 * collected are divided by $num_runs.
269 function xhprof_normalize_metrics($raw_data, $num_runs) {
271 if (empty($raw_data) || ($num_runs == 0)) {
275 $raw_data_total = array();
277 if (isset($raw_data["==>main()"]) && isset($raw_data["main()"])) {
278 xhprof_error("XHProf Error: both ==>main() and main() set in raw data...");
281 foreach ($raw_data as $parent_child => $info) {
282 foreach ($info as $metric => $value) {
283 $raw_data_total[$parent_child][$metric] = ($value / $num_runs);
287 return $raw_data_total;
292 * Get raw data corresponding to specified array of runs
293 * aggregated by certain weightage.
295 * Suppose you have run:5 corresponding to page1.php,
296 * run:6 corresponding to page2.php,
297 * and run:7 corresponding to page3.php
299 * and you want to accumulate these runs in a 2:4:1 ratio. You
300 * can do so by calling:
302 * xhprof_aggregate_runs(array(5, 6, 7), array(2, 4, 1));
304 * The above will return raw data for the runs aggregated
307 * @param object $xhprof_runs_impl An object that implements
308 * the iXHProfRuns interface
309 * @param array $runs run ids of the XHProf runs..
310 * @param array $wts integral (ideally) weights for $runs
311 * @param string $source source to fetch raw data for run from
312 * @param bool $use_script_name If true, a fake edge from main() to
313 * to __script::<scriptname> is introduced
314 * in the raw data so that after aggregations
315 * the script name is still preserved.
317 * @return array Return aggregated raw data
321 function xhprof_aggregate_runs($xhprof_runs_impl, $runs,
322 $wts, $source="phprof",
323 $use_script_name=false) {
325 $raw_data_total = null;
329 $run_count = count($runs);
330 $wts_count = count($wts);
332 if (($run_count == 0) ||
333 (($wts_count > 0) && ($run_count != $wts_count))) {
334 return array('description' => 'Invalid input..',
339 foreach ($runs as $idx => $run_id) {
341 $raw_data = $xhprof_runs_impl->get_run($run_id, $source, $description);
343 // use the first run to derive what metrics to aggregate on.
345 foreach ($raw_data["main()"] as $metric => $val) {
346 if ($metric != "pmu") {
347 // for now, just to keep data size small, skip "peak" memory usage
348 // data while aggregating.
349 // The "regular" memory usage data will still be tracked.
351 $metrics[] = $metric;
357 if (!xhprof_valid_run($run_id, $raw_data)) {
358 $bad_runs[] = $run_id;
362 if ($use_script_name) {
363 $page = $description;
365 // create a fake function '__script::$page', and have and edge from
366 // main() to '__script::$page'. We will also need edges to transfer
367 // all edges originating from main() to now originate from
368 // '__script::$page' to all function called from main().
370 // We also weight main() ever so slightly higher so that
371 // it shows up above the new entry in reports sorted by
372 // inclusive metrics or call counts.
374 foreach ($raw_data["main()"] as $metric => $val) {
375 $fake_edge[$metric] = $val;
376 $new_main[$metric] = $val + 0.00001;
378 $raw_data["main()"] = $new_main;
379 $raw_data[xhprof_build_parent_child_key("main()",
383 $use_script_name = false;
387 // if no weights specified, use 1 as the default weightage..
388 $wt = ($wts_count == 0) ? 1 : $wts[$idx];
390 // aggregate $raw_data into $raw_data_total with appropriate weight ($wt)
391 foreach ($raw_data as $parent_child => $info) {
392 if ($use_script_name) {
393 // if this is an old edge originating from main(), it now
394 // needs to be from '__script::$page'
395 if (substr($parent_child, 0, 9) == "main()==>") {
396 $child = substr($parent_child, 9);
397 // ignore the newly added edge from main()
398 if (substr($child, 0, 10) != "__script::") {
399 $parent_child = xhprof_build_parent_child_key("__script::$page",
405 if (!isset($raw_data_total[$parent_child])) {
406 foreach ($metrics as $metric) {
407 $raw_data_total[$parent_child][$metric] = ($wt * $info[$metric]);
410 foreach ($metrics as $metric) {
411 $raw_data_total[$parent_child][$metric] += ($wt * $info[$metric]);
417 $runs_string = implode(",", $runs);
420 $wts_string = "in the ratio (" . implode(":", $wts) . ")";
421 $normalization_count = array_sum($wts);
424 $normalization_count = $run_count;
427 $run_count = $run_count - count($bad_runs);
429 $data['description'] = "Aggregated Report for $run_count runs: ".
430 "$runs_string $wts_string\n";
431 $data['raw'] = xhprof_normalize_metrics($raw_data_total,
432 $normalization_count);
433 $data['bad_runs'] = $bad_runs;
440 * Analyze hierarchical raw data, and compute per-function (flat)
441 * inclusive and exclusive metrics.
443 * Also, store overall totals in the 2nd argument.
445 * @param array $raw_data XHProf format raw profiler data.
446 * @param array &$overall_totals OUT argument for returning
447 * overall totals for various
449 * @return array Returns a map from function name to its
450 * call count and inclusive & exclusive metrics
451 * (such as wall time, etc.).
453 * @author Kannan Muthukkaruppan
455 function xhprof_compute_flat_info($raw_data, &$overall_totals) {
457 global $display_calls;
459 $metrics = xhprof_get_metrics($raw_data);
461 $overall_totals = array("ct" => 0,
471 // compute inclusive times for each function
472 $symbol_tab = xhprof_compute_inclusive_times($raw_data);
474 /* total metric value is the metric value for "main()" */
475 foreach ($metrics as $metric) {
476 $overall_totals[$metric] = $symbol_tab["main()"][$metric];
480 * initialize exclusive (self) metric value to inclusive metric value
482 * In the same pass, also add up the total number of function calls.
484 foreach ($symbol_tab as $symbol => $info) {
485 foreach ($metrics as $metric) {
486 $symbol_tab[$symbol]["excl_" . $metric] = $symbol_tab[$symbol][$metric];
488 if ($display_calls) {
489 /* keep track of total number of calls */
490 $overall_totals["ct"] += $info["ct"];
494 /* adjust exclusive times by deducting inclusive time of children */
495 foreach ($raw_data as $parent_child => $info) {
496 list($parent, $child) = xhprof_parse_parent_child($parent_child);
499 foreach ($metrics as $metric) {
500 // make sure the parent exists hasn't been pruned.
501 if (isset($symbol_tab[$parent])) {
502 $symbol_tab[$parent]["excl_" . $metric] -= $info[$metric];
513 * Compute and return difference of two call graphs: Run2 - Run1.
517 function xhprof_compute_diff($xhprof_data1, $xhprof_data2) {
518 global $display_calls;
520 // use the second run to decide what metrics we will do the diff on
521 $metrics = xhprof_get_metrics($xhprof_data2);
523 $xhprof_delta = $xhprof_data2;
525 foreach ($xhprof_data1 as $parent_child => $info) {
527 if (!isset($xhprof_delta[$parent_child])) {
529 // this pc combination was not present in run1;
530 // initialize all values to zero.
531 if ($display_calls) {
532 $xhprof_delta[$parent_child] = array("ct" => 0);
534 $xhprof_delta[$parent_child] = array();
536 foreach ($metrics as $metric) {
537 $xhprof_delta[$parent_child][$metric] = 0;
541 if ($display_calls) {
542 $xhprof_delta[$parent_child]["ct"] -= $info["ct"];
545 foreach ($metrics as $metric) {
546 $xhprof_delta[$parent_child][$metric] -= $info[$metric];
550 return $xhprof_delta;
555 * Compute inclusive metrics for function. This code was factored out
556 * of xhprof_compute_flat_info().
558 * The raw data contains inclusive metrics of a function for each
559 * unique parent function it is called from. The total inclusive metrics
560 * for a function is therefore the sum of inclusive metrics for the
561 * function across all parents.
563 * @return array Returns a map of function name to total (across all parents)
564 * inclusive metrics for the function.
568 function xhprof_compute_inclusive_times($raw_data) {
569 global $display_calls;
571 $metrics = xhprof_get_metrics($raw_data);
573 $symbol_tab = array();
576 * First compute inclusive time for each function and total
577 * call count for each function across all parents the
578 * function is called from.
580 foreach ($raw_data as $parent_child => $info) {
582 list($parent, $child) = xhprof_parse_parent_child($parent_child);
584 if ($parent == $child) {
586 * XHProf PHP extension should never trigger this situation any more.
587 * Recursion is handled in the XHProf PHP extension by giving nested
588 * calls a unique recursion-depth appended name (for example, foo@1).
590 xhprof_error("Error in Raw Data: parent & child are both: $parent");
594 if (!isset($symbol_tab[$child])) {
596 if ($display_calls) {
597 $symbol_tab[$child] = array("ct" => $info["ct"]);
599 $symbol_tab[$child] = array();
601 foreach ($metrics as $metric) {
602 $symbol_tab[$child][$metric] = $info[$metric];
605 if ($display_calls) {
606 /* increment call count for this child */
607 $symbol_tab[$child]["ct"] += $info["ct"];
610 /* update inclusive times/metric for this child */
611 foreach ($metrics as $metric) {
612 $symbol_tab[$child][$metric] += $info[$metric];
622 * Prunes XHProf raw data:
624 * Any node whose inclusive walltime accounts for less than $prune_percent
625 * of total walltime is pruned. [It is possible that a child function isn't
626 * pruned, but one or more of its parents get pruned. In such cases, when
627 * viewing the child function's hierarchical information, the cost due to
628 * the pruned parent(s) will be attributed to a special function/symbol
631 * @param array $raw_data XHProf raw data to be pruned & validated.
632 * @param double $prune_percent Any edges that account for less than
633 * $prune_percent of time will be pruned
636 * @return array Returns the pruned raw data.
640 function xhprof_prune_run($raw_data, $prune_percent) {
642 $main_info = $raw_data["main()"];
643 if (empty($main_info)) {
644 xhprof_error("XHProf: main() missing in raw data");
648 // raw data should contain either wall time or samples information...
649 if (isset($main_info["wt"])) {
650 $prune_metric = "wt";
651 } else if (isset($main_info["samples"])) {
652 $prune_metric = "samples";
654 xhprof_error("XHProf: for main() we must have either wt "
655 ."or samples attribute set");
659 // determine the metrics present in the raw data..
661 foreach ($main_info as $metric => $val) {
663 $metrics[] = $metric;
667 $prune_threshold = (($main_info[$prune_metric] * $prune_percent) / 100.0);
669 init_metrics($raw_data, null, null, false);
670 $flat_info = xhprof_compute_inclusive_times($raw_data);
672 foreach ($raw_data as $parent_child => $info) {
674 list($parent, $child) = xhprof_parse_parent_child($parent_child);
676 // is this child's overall total from all parents less than threshold?
677 if ($flat_info[$child][$prune_metric] < $prune_threshold) {
678 unset($raw_data[$parent_child]); // prune the edge
679 } else if ($parent &&
680 ($parent != "__pruned__()") &&
681 ($flat_info[$parent][$prune_metric] < $prune_threshold)) {
683 // Parent's overall inclusive metric is less than a threshold.
684 // All edges to the parent node will get nuked, and this child will
685 // be a dangling child.
686 // So instead change its parent to be a special function __pruned__().
687 $pruned_edge = xhprof_build_parent_child_key("__pruned__()", $child);
689 if (isset($raw_data[$pruned_edge])) {
690 foreach ($metrics as $metric) {
691 $raw_data[$pruned_edge][$metric]+=$raw_data[$parent_child][$metric];
694 $raw_data[$pruned_edge] = $raw_data[$parent_child];
697 unset($raw_data[$parent_child]); // prune the edge
706 * Set one key in an array and return the array
710 function xhprof_array_set($arr, $k, $v) {
716 * Removes/unsets one key in an array and return the array
720 function xhprof_array_unset($arr, $k) {
726 * Type definitions for URL params
728 define('XHPROF_STRING_PARAM', 1);
729 define('XHPROF_UINT_PARAM', 2);
730 define('XHPROF_FLOAT_PARAM', 3);
731 define('XHPROF_BOOL_PARAM', 4);
735 * Internal helper function used by various
736 * xhprof_get_param* flavors for various
737 * types of parameters.
739 * @param string name of the URL query string param
743 function xhprof_get_param_helper($param) {
745 if (isset($_GET[$param]))
746 $val = $_GET[$param];
747 else if (isset($_POST[$param])) {
748 $val = $_POST[$param];
754 * Extracts value for string param $param from query
755 * string. If param is not specified, return the
760 function xhprof_get_string_param($param, $default = '') {
761 $val = xhprof_get_param_helper($param);
770 * Extracts value for unsigned integer param $param from
771 * query string. If param is not specified, return the
774 * If value is not a valid unsigned integer, logs error
779 function xhprof_get_uint_param($param, $default = 0) {
780 $val = xhprof_get_param_helper($param);
785 // trim leading/trailing whitespace
788 // if it only contains digits, then ok..
789 if (ctype_digit($val)) {
793 xhprof_error("$param is $val. It must be an unsigned integer.");
799 * Extracts value for a float param $param from
800 * query string. If param is not specified, return
801 * the $default value.
803 * If value is not a valid unsigned integer, logs error
808 function xhprof_get_float_param($param, $default = 0) {
809 $val = xhprof_get_param_helper($param);
814 // trim leading/trailing whitespace
817 // TBD: confirm the value is indeed a float.
818 if (true) // for now..
821 xhprof_error("$param is $val. It must be a float.");
826 * Extracts value for a boolean param $param from
827 * query string. If param is not specified, return
828 * the $default value.
830 * If value is not a valid unsigned integer, logs error
835 function xhprof_get_bool_param($param, $default = false) {
836 $val = xhprof_get_param_helper($param);
841 // trim leading/trailing whitespace
844 switch (strtolower($val)) {
860 xhprof_error("$param is $val. It must be a valid boolean string.");
869 * Initialize params from URL query string. The function
870 * creates globals variables for each of the params
871 * and if the URL query string doesn't specify a particular
872 * param initializes them with the corresponding default
873 * value specified in the input.
875 * @params array $params An array whose keys are the names
876 * of URL params who value needs to
877 * be retrieved from the URL query
878 * string. PHP globals are created
879 * with these names. The value is
880 * itself an array with 2-elems (the
881 * param type, and its default value).
882 * If a param is not specified in the
883 * query string the default value is
887 function xhprof_param_init($params) {
888 /* Create variables specified in $params keys, init defaults */
889 foreach ($params as $k => $v) {
891 case XHPROF_STRING_PARAM:
892 $p = xhprof_get_string_param($k, $v[1]);
894 case XHPROF_UINT_PARAM:
895 $p = xhprof_get_uint_param($k, $v[1]);
897 case XHPROF_FLOAT_PARAM:
898 $p = xhprof_get_float_param($k, $v[1]);
900 case XHPROF_BOOL_PARAM:
901 $p = xhprof_get_bool_param($k, $v[1]);
904 xhprof_error("Invalid param type passed to xhprof_param_init: "
909 // create a global variable using the parameter name.
916 * Given a partial query string $q return matching function names in
917 * specified XHProf run. This is used for the type ahead function
922 function xhprof_get_matching_functions($q, $xhprof_data) {
926 foreach ($xhprof_data as $parent_child => $info) {
927 list($parent, $child) = xhprof_parse_parent_child($parent_child);
928 if (stripos($parent, $q) !== false) {
929 $matches[$parent] = 1;
931 if (stripos($child, $q) !== false) {
932 $matches[$child] = 1;
936 $res = array_keys($matches);
938 // sort it so the answers are in some reliable order...