diff --git a/externals/xhprof/LICENSE b/externals/xhprof/LICENSE new file mode 100644 index 0000000000..f433b1a53f --- /dev/null +++ b/externals/xhprof/LICENSE @@ -0,0 +1,177 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS diff --git a/externals/xhprof/xhprof_lib.php b/externals/xhprof/xhprof_lib.php new file mode 100644 index 0000000000..fed9487039 --- /dev/null +++ b/externals/xhprof/xhprof_lib.php @@ -0,0 +1,866 @@ + array("Wall", "microsecs", "walltime" ), + "ut" => array("User", "microsecs", "user cpu time" ), + "st" => array("Sys", "microsecs", "system cpu time"), + "cpu" => array("Cpu", "microsecs", "cpu time"), + "mu" => array("MUse", "bytes", "memory usage"), + "pmu" => array("PMUse", "bytes", "peak memory usage"), + "samples" => array("Samples", "samples", "cpu time")); + return $possible_metrics; +} + +/* + * Get the list of metrics present in $xhprof_data as an array. + * + * @author Kannan + */ +function xhprof_get_metrics($xhprof_data) { + + // get list of valid metrics + $possible_metrics = xhprof_get_possible_metrics(); + + // return those that are present in the raw data. + // We'll just look at the root of the subtree for this. + $metrics = array(); + foreach ($possible_metrics as $metric => $desc) { + if (isset($xhprof_data["main()"][$metric])) { + $metrics[] = $metric; + } + } + + return $metrics; +} + +/** + * Takes a parent/child function name encoded as + * "a==>b" and returns array("a", "b"). + * + * @author Kannan + */ +function xhprof_parse_parent_child($parent_child) { + $ret = explode("==>", $parent_child); + + // Return if both parent and child are set + if (isset($ret[1])) { + return $ret; + } + + return array(null, $ret[0]); +} + +/** + * Given parent & child function name, composes the key + * in the format present in the raw data. + * + * @author Kannan + */ +function xhprof_build_parent_child_key($parent, $child) { + if ($parent) { + return $parent . "==>" . $child; + } else { + return $child; + } +} + + +/** + * Checks if XHProf raw data appears to be valid and not corrupted. + * + * @param int $run_id Run id of run to be pruned. + * [Used only for reporting errors.] + * @param array $raw_data XHProf raw data to be pruned + * & validated. + * + * @return bool true on success, false on failure + * + * @author Kannan + */ +function xhprof_valid_run($run_id, $raw_data) { + + $main_info = $raw_data["main()"]; + if (empty($main_info)) { + xhprof_error("XHProf: main() missing in raw data for Run ID: $run_id"); + return false; + } + + // raw data should contain either wall time or samples information... + if (isset($main_info["wt"])) { + $metric = "wt"; + } else if (isset($main_info["samples"])) { + $metric = "samples"; + } else { + xhprof_error("XHProf: Wall Time information missing from Run ID: $run_id"); + return false; + } + + foreach ($raw_data as $info) { + $val = $info[$metric]; + + // basic sanity checks... + if ($val < 0) { + xhprof_error("XHProf: $metric should not be negative: Run ID $run_id" + . serialize($info)); + return false; + } + if ($val > (86400000000)) { + xhprof_error("XHProf: $metric > 1 day found in Run ID: $run_id " + . serialize($info)); + return false; + } + } + return true; +} + + +/** + * Return a trimmed version of the XHProf raw data. Note that the raw + * data contains one entry for each unique parent/child function + * combination.The trimmed version of raw data will only contain + * entries where either the parent or child function is in the list + * of $functions_to_keep. + * + * Note: Function main() is also always kept so that overall totals + * can still be obtained from the trimmed version. + * + * @param array XHProf raw data + * @param array array of function names + * + * @return array Trimmed XHProf Report + * + * @author Kannan + */ +function xhprof_trim_run($raw_data, $functions_to_keep) { + + // convert list of functions to a hash with function as the key + $function_map = array_fill_keys($functions_to_keep, 1); + + // always keep main() as well so that overall totals can still + // be computed if need be. + $function_map['main()'] = 1; + + $new_raw_data = array(); + foreach ($raw_data as $parent_child => $info) { + list($parent, $child) = xhprof_parse_parent_child($parent_child); + + if (isset($function_map[$parent]) || isset($function_map[$child])) { + $new_raw_data[$parent_child] = $info; + } + } + + return $new_raw_data; +} + +/** + * Takes raw XHProf data that was aggregated over "$num_runs" number + * of runs averages/nomalizes the data. Essentially the various metrics + * collected are divided by $num_runs. + * + * @author Kannan + */ +function xhprof_normalize_metrics($raw_data, $num_runs) { + + if (empty($raw_data) || ($num_runs == 0)) { + return $raw_data; + } + + $raw_data_total = array(); + + if (isset($raw_data["==>main()"]) && isset($raw_data["main()"])) { + xhprof_error("XHProf Error: both ==>main() and main() set in raw data..."); + } + + foreach ($raw_data as $parent_child => $info) { + foreach ($info as $metric => $value) { + $raw_data_total[$parent_child][$metric] = ($value / $num_runs); + } + } + + return $raw_data_total; +} + + +/** + * Get raw data corresponding to specified array of runs + * aggregated by certain weightage. + * + * Suppose you have run:5 corresponding to page1.php, + * run:6 corresponding to page2.php, + * and run:7 corresponding to page3.php + * + * and you want to accumulate these runs in a 2:4:1 ratio. You + * can do so by calling: + * + * xhprof_aggregate_runs(array(5, 6, 7), array(2, 4, 1)); + * + * The above will return raw data for the runs aggregated + * in 2:4:1 ratio. + * + * @param object $xhprof_runs_impl An object that implements + * the iXHProfRuns interface + * @param array $runs run ids of the XHProf runs.. + * @param array $wts integral (ideally) weights for $runs + * @param string $source source to fetch raw data for run from + * @param bool $use_script_name If true, a fake edge from main() to + * to __script:: is introduced + * in the raw data so that after aggregations + * the script name is still preserved. + * + * @return array Return aggregated raw data + * + * @author Kannan + */ +function xhprof_aggregate_runs($xhprof_runs_impl, $runs, + $wts, $source="phprof", + $use_script_name=false) { + + $raw_data_total = null; + $raw_data = null; + $metrics = array(); + + $run_count = count($runs); + $wts_count = count($wts); + + if (($run_count == 0) || + (($wts_count > 0) && ($run_count != $wts_count))) { + return array('description' => 'Invalid input..', + 'raw' => null); + } + + $bad_runs = array(); + foreach($runs as $idx => $run_id) { + + $raw_data = $xhprof_runs_impl->get_run($run_id, $source, $description); + + // use the first run to derive what metrics to aggregate on. + if ($idx == 0) { + foreach ($raw_data["main()"] as $metric => $val) { + if ($metric != "pmu") { + // for now, just to keep data size small, skip "peak" memory usage + // data while aggregating. + // The "regular" memory usage data will still be tracked. + if (isset($val)) { + $metrics[] = $metric; + } + } + } + } + + if (!xhprof_valid_run($run_id, $raw_data)) { + $bad_runs[] = $run_id; + continue; + } + + if ($use_script_name) { + $page = $description; + + // create a fake function '__script::$page', and have and edge from + // main() to '__script::$page'. We will also need edges to transfer + // all edges originating from main() to now originate from + // '__script::$page' to all function called from main(). + // + // We also weight main() ever so slightly higher so that + // it shows up above the new entry in reports sorted by + // inclusive metrics or call counts. + if ($page) { + foreach($raw_data["main()"] as $metric => $val) { + $fake_edge[$metric] = $val; + $new_main[$metric] = $val + 0.00001; + } + $raw_data["main()"] = $new_main; + $raw_data[xhprof_build_parent_child_key("main()", + "__script::$page")] + = $fake_edge; + } else { + $use_script_name = false; + } + } + + // if no weights specified, use 1 as the default weightage.. + $wt = ($wts_count == 0) ? 1 : $wts[$idx]; + + // aggregate $raw_data into $raw_data_total with appropriate weight ($wt) + foreach ($raw_data as $parent_child => $info) { + if ($use_script_name) { + // if this is an old edge originating from main(), it now + // needs to be from '__script::$page' + if (substr($parent_child, 0, 9) == "main()==>") { + $child =substr($parent_child, 9); + // ignore the newly added edge from main() + if (substr($child, 0, 10) != "__script::") { + $parent_child = xhprof_build_parent_child_key("__script::$page", + $child); + } + } + } + + if (!isset($raw_data_total[$parent_child])) { + foreach ($metrics as $metric) { + $raw_data_total[$parent_child][$metric] = ($wt * $info[$metric]); + } + } else { + foreach ($metrics as $metric) { + $raw_data_total[$parent_child][$metric] += ($wt * $info[$metric]); + } + } + } + } + + $runs_string = implode(",", $runs); + + if (isset($wts)) { + $wts_string = "in the ratio (" . implode(":", $wts) . ")"; + $normalization_count = array_sum($wts); + } else { + $wts_string = ""; + $normalization_count = $run_count; + } + + $run_count = $run_count - count($bad_runs); + + $data['description'] = "Aggregated Report for $run_count runs: ". + "$runs_string $wts_string\n"; + $data['raw'] = xhprof_normalize_metrics($raw_data_total, + $normalization_count); + $data['bad_runs'] = $bad_runs; + + return $data; +} + + +/** + * Analyze hierarchical raw data, and compute per-function (flat) + * inclusive and exclusive metrics. + * + * Also, store overall totals in the 2nd argument. + * + * @param array $raw_data XHProf format raw profiler data. + * @param array &$overall_totals OUT argument for returning + * overall totals for various + * metrics. + * @return array Returns a map from function name to its + * call count and inclusive & exclusive metrics + * (such as wall time, etc.). + * + * @author Kannan Muthukkaruppan + */ +function xhprof_compute_flat_info($raw_data, &$overall_totals) { + + global $display_calls; + + $metrics = xhprof_get_metrics($raw_data); + + $overall_totals = array( "ct" => 0, + "wt" => 0, + "ut" => 0, + "st" => 0, + "cpu" => 0, + "mu" => 0, + "pmu" => 0, + "samples" => 0 + ); + + // compute inclusive times for each function + $symbol_tab = xhprof_compute_inclusive_times($raw_data); + + /* total metric value is the metric value for "main()" */ + foreach ($metrics as $metric) { + $overall_totals[$metric] = $symbol_tab["main()"][$metric]; + } + + /* + * initialize exclusive (self) metric value to inclusive metric value + * to start with. + * In the same pass, also add up the total number of function calls. + */ + foreach ($symbol_tab as $symbol => $info) { + foreach ($metrics as $metric) { + $symbol_tab[$symbol]["excl_" . $metric] = $symbol_tab[$symbol][$metric]; + } + if ($display_calls) { + /* keep track of total number of calls */ + $overall_totals["ct"] += $info["ct"]; + } + } + + /* adjust exclusive times by deducting inclusive time of children */ + foreach ($raw_data as $parent_child => $info) { + list($parent, $child) = xhprof_parse_parent_child($parent_child); + + if ($parent) { + foreach ($metrics as $metric) { + // make sure the parent exists hasn't been pruned. + if (isset($symbol_tab[$parent])) { + $symbol_tab[$parent]["excl_" . $metric] -= $info[$metric]; + } + } + } + } + + return $symbol_tab; +} + +/** + * Hierarchical diff: + * Compute and return difference of two call graphs: Run2 - Run1. + * + * @author Kannan + */ +function xhprof_compute_diff($xhprof_data1, $xhprof_data2) { + global $display_calls; + + // use the second run to decide what metrics we will do the diff on + $metrics = xhprof_get_metrics($xhprof_data2); + + $xhprof_delta = $xhprof_data2; + + foreach ($xhprof_data1 as $parent_child => $info) { + + if (!isset($xhprof_delta[$parent_child])) { + + // this pc combination was not present in run1; + // initialize all values to zero. + if ($display_calls) { + $xhprof_delta[$parent_child] = array("ct" => 0); + } else { + $xhprof_delta[$parent_child] = array(); + } + foreach ($metrics as $metric) { + $xhprof_delta[$parent_child][$metric] = 0; + } + } + + if ($display_calls) { + $xhprof_delta[$parent_child]["ct"] -= $info["ct"]; + } + + foreach ($metrics as $metric) { + $xhprof_delta[$parent_child][$metric] -= $info[$metric]; + } + } + + return $xhprof_delta; +} + + +/** + * Compute inclusive metrics for function. This code was factored out + * of xhprof_compute_flat_info(). + * + * The raw data contains inclusive metrics of a function for each + * unique parent function it is called from. The total inclusive metrics + * for a function is therefore the sum of inclusive metrics for the + * function across all parents. + * + * @return array Returns a map of function name to total (across all parents) + * inclusive metrics for the function. + * + * @author Kannan + */ +function xhprof_compute_inclusive_times($raw_data) { + global $display_calls; + + $metrics = xhprof_get_metrics($raw_data); + + $symbol_tab = array(); + + /* + * First compute inclusive time for each function and total + * call count for each function across all parents the + * function is called from. + */ + foreach ($raw_data as $parent_child => $info) { + + list($parent, $child) = xhprof_parse_parent_child($parent_child); + + if ($parent == $child) { + /* + * XHProf PHP extension should never trigger this situation any more. + * Recursion is handled in the XHProf PHP extension by giving nested + * calls a unique recursion-depth appended name (for example, foo@1). + */ + xhprof_error("Error in Raw Data: parent & child are both: $parent"); + return; + } + + if (!isset($symbol_tab[$child])) { + + if ($display_calls) { + $symbol_tab[$child] = array("ct" => $info["ct"]); + } else { + $symbol_tab[$child] = array(); + } + foreach ($metrics as $metric) { + $symbol_tab[$child][$metric] = $info[$metric]; + } + } else { + if ($display_calls) { + /* increment call count for this child */ + $symbol_tab[$child]["ct"] += $info["ct"]; + } + + /* update inclusive times/metric for this child */ + foreach ($metrics as $metric) { + $symbol_tab[$child][$metric] += $info[$metric]; + } + } + } + + return $symbol_tab; +} + + +/* + * Prunes XHProf raw data: + * + * Any node whose inclusive walltime accounts for less than $prune_percent + * of total walltime is pruned. [It is possible that a child function isn't + * pruned, but one or more of its parents get pruned. In such cases, when + * viewing the child function's hierarchical information, the cost due to + * the pruned parent(s) will be attributed to a special function/symbol + * "__pruned__()".] + * + * @param array $raw_data XHProf raw data to be pruned & validated. + * @param double $prune_percent Any edges that account for less than + * $prune_percent of time will be pruned + * from the raw data. + * + * @return array Returns the pruned raw data. + * + * @author Kannan + */ +function xhprof_prune_run($raw_data, $prune_percent) { + + $main_info = $raw_data["main()"]; + if (empty($main_info)) { + xhprof_error("XHProf: main() missing in raw data"); + return false; + } + + // raw data should contain either wall time or samples information... + if (isset($main_info["wt"])) { + $prune_metric = "wt"; + } else if (isset($main_info["samples"])) { + $prune_metric = "samples"; + } else { + xhprof_error("XHProf: for main() we must have either wt " + ."or samples attribute set"); + return false; + } + + // determine the metrics present in the raw data.. + $metrics = array(); + foreach ($main_info as $metric => $val) { + if (isset($val)) { + $metrics[] = $metric; + } + } + + $prune_threshold = (($main_info[$prune_metric] * $prune_percent) / 100.0); + + init_metrics($raw_data, null, null, false); + $flat_info = xhprof_compute_inclusive_times($raw_data); + + foreach ($raw_data as $parent_child => $info) { + + list($parent, $child) = xhprof_parse_parent_child($parent_child); + + // is this child's overall total from all parents less than threshold? + if ($flat_info[$child][$prune_metric] < $prune_threshold) { + unset($raw_data[$parent_child]); // prune the edge + } else if ($parent && + ($parent != "__pruned__()") && + ($flat_info[$parent][$prune_metric] < $prune_threshold)) { + + // Parent's overall inclusive metric is less than a threshold. + // All edges to the parent node will get nuked, and this child will + // be a dangling child. + // So instead change its parent to be a special function __pruned__(). + $pruned_edge = xhprof_build_parent_child_key("__pruned__()", $child); + + if (isset($raw_data[$pruned_edge])) { + foreach ($metrics as $metric) { + $raw_data[$pruned_edge][$metric]+=$raw_data[$parent_child][$metric]; + } + } else { + $raw_data[$pruned_edge] = $raw_data[$parent_child]; + } + + unset($raw_data[$parent_child]); // prune the edge + } + } + + return $raw_data; +} + + +/** + * Set one key in an array and return the array + * + * @author Kannan + */ +function xhprof_array_set($arr, $k, $v) { + $arr[$k] = $v; + return $arr; +} + +/** + * Removes/unsets one key in an array and return the array + * + * @author Kannan + */ +function xhprof_array_unset($arr, $k) { + unset($arr[$k]); + return $arr; +} + +/** + * Type definitions for URL params + */ +define('XHPROF_STRING_PARAM', 1); +define('XHPROF_UINT_PARAM', 2); +define('XHPROF_FLOAT_PARAM', 3); +define('XHPROF_BOOL_PARAM', 4); + + +/** + * Internal helper function used by various + * xhprof_get_param* flavors for various + * types of parameters. + * + * @param string name of the URL query string param + * + * @author Kannan + */ +function xhprof_get_param_helper($param) { + $val = null; + if (isset($_GET[$param])) + $val = $_GET[$param]; + else if (isset($_POST[$param])) { + $val = $_POST[$param]; + } + return $val; +} + +/** + * Extracts value for string param $param from query + * string. If param is not specified, return the + * $default value. + * + * @author Kannan + */ +function xhprof_get_string_param($param, $default = '') { + $val = xhprof_get_param_helper($param); + + if ($val === null) + return $default; + + return $val; +} + +/** + * Extracts value for unsigned integer param $param from + * query string. If param is not specified, return the + * $default value. + * + * If value is not a valid unsigned integer, logs error + * and returns null. + * + * @author Kannan + */ +function xhprof_get_uint_param($param, $default = 0) { + $val = xhprof_get_param_helper($param); + + if ($val === null) + $val = $default; + + // trim leading/trailing whitespace + $val = trim($val); + + // if it only contains digits, then ok.. + if (ctype_digit($val)) { + return $val; + } + + xhprof_error("$param is $val. It must be an unsigned integer."); + return null; +} + + +/** + * Extracts value for a float param $param from + * query string. If param is not specified, return + * the $default value. + * + * If value is not a valid unsigned integer, logs error + * and returns null. + * + * @author Kannan + */ +function xhprof_get_float_param($param, $default = 0) { + $val = xhprof_get_param_helper($param); + + if ($val === null) + $val = $default; + + // trim leading/trailing whitespace + $val = trim($val); + + // TBD: confirm the value is indeed a float. + if (true) // for now.. + return (float)$val; + + xhprof_error("$param is $val. It must be a float."); + return null; +} + +/** + * Extracts value for a boolean param $param from + * query string. If param is not specified, return + * the $default value. + * + * If value is not a valid unsigned integer, logs error + * and returns null. + * + * @author Kannan + */ +function xhprof_get_bool_param($param, $default = false) { + $val = xhprof_get_param_helper($param); + + if ($val === null) + $val = $default; + + // trim leading/trailing whitespace + $val = trim($val); + + switch (strtolower($val)) { + case '0': + case '1': + $val = (bool)$val; + break; + case 'true': + case 'on': + case 'yes': + $val = true; + break; + case 'false': + case 'off': + case 'no': + $val = false; + break; + default: + xhprof_error("$param is $val. It must be a valid boolean string."); + return null; + } + + return $val; + +} + +/** + * Initialize params from URL query string. The function + * creates globals variables for each of the params + * and if the URL query string doesn't specify a particular + * param initializes them with the corresponding default + * value specified in the input. + * + * @params array $params An array whose keys are the names + * of URL params who value needs to + * be retrieved from the URL query + * string. PHP globals are created + * with these names. The value is + * itself an array with 2-elems (the + * param type, and its default value). + * If a param is not specified in the + * query string the default value is + * used. + * @author Kannan + */ +function xhprof_param_init($params) { + /* Create variables specified in $params keys, init defaults */ + foreach ($params as $k => $v) { + switch ($v[0]) { + case XHPROF_STRING_PARAM: + $p = xhprof_get_string_param($k, $v[1]); + break; + case XHPROF_UINT_PARAM: + $p = xhprof_get_uint_param($k, $v[1]); + break; + case XHPROF_FLOAT_PARAM: + $p = xhprof_get_float_param($k, $v[1]); + break; + case XHPROF_BOOL_PARAM: + $p = xhprof_get_bool_param($k, $v[1]); + break; + default: + xhprof_error("Invalid param type passed to xhprof_param_init: " + . $v[0]); + exit(); + } + + // create a global variable using the parameter name. + $GLOBALS[$k] = $p; + } +} + + +/** + * Given a partial query string $q return matching function names in + * specified XHProf run. This is used for the type ahead function + * selector. + * + * @author Kannan + */ +function xhprof_get_matching_functions($q, $xhprof_data) { + + $matches = array(); + + foreach ($xhprof_data as $parent_child => $info) { + list($parent, $child) = xhprof_parse_parent_child($parent_child); + if (stripos($parent, $q) !== false) { + $matches[$parent] = 1; + } + if (stripos($child, $q) !== false) { + $matches[$child] = 1; + } + } + + $res = array_keys($matches); + + // sort it so the answers are in some reliable order... + asort($res); + + return ($res); +} + diff --git a/src/__celerity_resource_map__.php b/src/__celerity_resource_map__.php index 927ceb8589..30e05c36b6 100644 --- a/src/__celerity_resource_map__.php +++ b/src/__celerity_resource_map__.php @@ -7,6 +7,15 @@ */ celerity_register_resource_map(array( + 'aphront-dark-console-css' => + array( + 'uri' => '/res/ac3fc983/rsrc/css/aphront/dark-console.css', + 'type' => 'css', + 'requires' => + array( + ), + 'disk' => '/rsrc/css/aphront/dark-console.css', + ), 'aphront-dialog-view-css' => array( 'uri' => '/res/a05107ae/rsrc/css/aphront/dialog-view.css', @@ -54,7 +63,7 @@ celerity_register_resource_map(array( ), 'aphront-table-view-css' => array( - 'uri' => '/res/52b0191f/rsrc/css/aphront/table-view.css', + 'uri' => '/res/de3a1e4c/rsrc/css/aphront/table-view.css', 'type' => 'css', 'requires' => array( @@ -215,6 +224,15 @@ celerity_register_resource_map(array( ), 'disk' => '/rsrc/css/core/syntax.css', ), + 'javelin-behavior-dark-console' => + array( + 'uri' => '/res/453503f4/rsrc/js/application/core/behavior-dark-console.js', + 'type' => 'js', + 'requires' => + array( + ), + 'disk' => '/rsrc/js/application/core/behavior-dark-console.js', + ), 'javelin-behavior-aphront-basic-tokenizer' => array( 'uri' => '/res/8317d761/rsrc/js/application/core/behavior-tokenizer.js', @@ -237,7 +255,7 @@ celerity_register_resource_map(array( ), 'javelin-behavior-differential-edit-inline-comments' => array( - 'uri' => '/res/be5ed33e/rsrc/js/application/differential/behavior-edit-inline-comments.js', + 'uri' => '/res/f5b54891/rsrc/js/application/differential/behavior-edit-inline-comments.js', 'type' => 'js', 'requires' => array( @@ -340,7 +358,7 @@ celerity_register_resource_map(array( ), array ( 'packages' => array ( - '4bb7e37f' => + 'c5efa388' => array ( 'name' => 'core.pkg.css', 'symbols' => @@ -357,7 +375,7 @@ celerity_register_resource_map(array( 9 => 'aphront-typeahead-control-css', 10 => 'phabricator-directory-css', ), - 'uri' => '/res/pkg/4bb7e37f/core.pkg.css', + 'uri' => '/res/pkg/c5efa388/core.pkg.css', 'type' => 'css', ), 'f399aad7' => @@ -377,17 +395,17 @@ celerity_register_resource_map(array( ), 'reverse' => array ( - 'phabricator-core-css' => '4bb7e37f', - 'phabricator-core-buttons-css' => '4bb7e37f', - 'phabricator-standard-page-view' => '4bb7e37f', - 'aphront-dialog-view-css' => '4bb7e37f', - 'aphront-form-view-css' => '4bb7e37f', - 'aphront-panel-view-css' => '4bb7e37f', - 'aphront-side-nav-view-css' => '4bb7e37f', - 'aphront-table-view-css' => '4bb7e37f', - 'aphront-tokenizer-control-css' => '4bb7e37f', - 'aphront-typeahead-control-css' => '4bb7e37f', - 'phabricator-directory-css' => '4bb7e37f', + 'phabricator-core-css' => 'c5efa388', + 'phabricator-core-buttons-css' => 'c5efa388', + 'phabricator-standard-page-view' => 'c5efa388', + 'aphront-dialog-view-css' => 'c5efa388', + 'aphront-form-view-css' => 'c5efa388', + 'aphront-panel-view-css' => 'c5efa388', + 'aphront-side-nav-view-css' => 'c5efa388', + 'aphront-table-view-css' => 'c5efa388', + 'aphront-tokenizer-control-css' => 'c5efa388', + 'aphront-typeahead-control-css' => 'c5efa388', + 'phabricator-directory-css' => 'c5efa388', 'differential-core-view-css' => 'f399aad7', 'differential-changeset-view-css' => 'f399aad7', 'differential-revision-detail-css' => 'f399aad7', diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index ed66f1dc3e..3ef9c0e99b 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -68,6 +68,15 @@ phutil_register_library_map(array( 'ConduitAPI_file_upload_Method' => 'applications/conduit/method/file/upload', 'ConduitAPI_user_find_Method' => 'applications/conduit/method/user/find', 'ConduitException' => 'applications/conduit/protocol/exception', + 'DarkConsole' => 'aphront/console/api', + 'DarkConsoleController' => 'aphront/console/controller', + 'DarkConsoleCore' => 'aphront/console/core', + 'DarkConsoleErrorLogPlugin' => 'aphront/console/plugin/errorlog', + 'DarkConsolePlugin' => 'aphront/console/plugin/base', + 'DarkConsoleRequestPlugin' => 'aphront/console/plugin/request', + 'DarkConsoleServicesPlugin' => 'aphront/console/plugin/services', + 'DarkConsoleXHProfPlugin' => 'aphront/console/plugin/xhprof', + 'DarkConsoleXHProfPluginAPI' => 'aphront/console/plugin/xhprof/api', 'DifferentialAction' => 'applications/differential/constants/action', 'DifferentialAddCommentView' => 'applications/differential/view/addcomment', 'DifferentialCCWelcomeMail' => 'applications/differential/mail/ccwelcome', @@ -181,6 +190,10 @@ phutil_register_library_map(array( 'PhabricatorTypeaheadDatasourceController' => 'applications/typeahead/controller/base', 'PhabricatorUser' => 'applications/people/storage/user', 'PhabricatorUserDAO' => 'applications/people/storage/base', + 'PhabricatorXHProfController' => 'applications/xhprof/controller/base', + 'PhabricatorXHProfProfileController' => 'applications/xhprof/controller/profile', + 'PhabricatorXHProfProfileSymbolView' => 'applications/xhprof/view/symbol', + 'PhabricatorXHProfProfileTopLevelView' => 'applications/xhprof/view/toplevel', ), 'function' => array( @@ -249,6 +262,11 @@ phutil_register_library_map(array( 'ConduitAPI_differential_setdiffproperty_Method' => 'ConduitAPIMethod', 'ConduitAPI_file_upload_Method' => 'ConduitAPIMethod', 'ConduitAPI_user_find_Method' => 'ConduitAPIMethod', + 'DarkConsoleController' => 'AliteController', + 'DarkConsoleErrorLogPlugin' => 'DarkConsolePlugin', + 'DarkConsoleRequestPlugin' => 'DarkConsolePlugin', + 'DarkConsoleServicesPlugin' => 'DarkConsolePlugin', + 'DarkConsoleXHProfPlugin' => 'DarkConsolePlugin', 'DifferentialAddCommentView' => 'AphrontView', 'DifferentialCCWelcomeMail' => 'DifferentialReviewRequestMail', 'DifferentialChangeset' => 'DifferentialDAO', @@ -343,6 +361,10 @@ phutil_register_library_map(array( 'PhabricatorTypeaheadDatasourceController' => 'PhabricatorController', 'PhabricatorUser' => 'PhabricatorUserDAO', 'PhabricatorUserDAO' => 'PhabricatorLiskDAO', + 'PhabricatorXHProfController' => 'PhabricatorController', + 'PhabricatorXHProfProfileController' => 'PhabricatorXHProfController', + 'PhabricatorXHProfProfileSymbolView' => 'AphrontView', + 'PhabricatorXHProfProfileTopLevelView' => 'AphrontView', ), 'requires_interface' => array( diff --git a/src/aphront/applicationconfiguration/AphrontApplicationConfiguration.php b/src/aphront/applicationconfiguration/AphrontApplicationConfiguration.php index cba9578bb4..36d0e9408d 100644 --- a/src/aphront/applicationconfiguration/AphrontApplicationConfiguration.php +++ b/src/aphront/applicationconfiguration/AphrontApplicationConfiguration.php @@ -24,6 +24,7 @@ abstract class AphrontApplicationConfiguration { private $request; private $host; private $path; + private $console; abstract public function getApplicationName(); abstract public function getURIMap(); @@ -39,6 +40,10 @@ abstract class AphrontApplicationConfiguration { return $this->request; } + final public function getConsole() { + return $this->console; + } + final public function buildController() { $map = $this->getURIMap(); $mapper = new AphrontURIMapper($map); @@ -74,4 +79,8 @@ abstract class AphrontApplicationConfiguration { return $this->path; } + final public function willBuildRequest() { + $this->console = new DarkConsoleCore(); + } + } diff --git a/src/aphront/applicationconfiguration/__init__.php b/src/aphront/applicationconfiguration/__init__.php index fa09ec8107..ccf7d6e897 100644 --- a/src/aphront/applicationconfiguration/__init__.php +++ b/src/aphront/applicationconfiguration/__init__.php @@ -6,6 +6,7 @@ +phutil_require_module('phabricator', 'aphront/console/core'); phutil_require_module('phabricator', 'aphront/mapper'); phutil_require_module('phutil', 'symbols'); diff --git a/src/aphront/console/api/DarkConsole.php b/src/aphront/console/api/DarkConsole.php new file mode 100755 index 0000000000..df64e2f50a --- /dev/null +++ b/src/aphront/console/api/DarkConsole.php @@ -0,0 +1,21 @@ + true, + 'toggle' => true, + 'visible' => true, + 'plugin' => true, + + 'etc' => true, + )); + + foreach (array_keys($ops) as $op) { + if (isset($params[$op])) { + $this->op = $op; + break; + } + } + $this->data = $params; + } + + public function getShortControllerName() { + return 'DarkConsole'; + } + + public function shouldPreflush() { + return false; + } + + public function process() { + $request = $this->getRequest(); + + if (!$this->op) { + $this->op = 'toggle'; + } + + $coredata = $request->getCoreData(); + $console = $coredata->getConsole(); + + if ($request->isAsync()) { + $return = null; + } else { + $return = '/'; + } + + + switch ($this->op) { + case 'toggle': + $enabled = $coredata->didToggleDarkConsole(); + if ($enabled) { + if (!$console) { + $console = new DarkConsoleCore($coredata); + } + $console->setConsoleSetting( + DarkConsoleCore::SETTING_VISIBLE, + true); + } + break; + case 'tab': + $console->setConsoleSetting( + DarkConsoleCore::SETTING_TAB, + $request->getStr('tab')); + break; + case 'visible': + $console->setConsoleSetting( + DarkConsoleCore::SETTING_VISIBLE, + !$console->getConsoleSetting(DarkConsoleCore::SETTING_VISIBLE)); + break; + } +// return ; + } + +} diff --git a/src/aphront/console/controller/__init__.php b/src/aphront/console/controller/__init__.php new file mode 100644 index 0000000000..bc1a89277c --- /dev/null +++ b/src/aphront/console/controller/__init__.php @@ -0,0 +1,12 @@ +getCoreData()->getViewerContext()->getUserID(), + self::APPLICATION_ID, + $key, + $value); + $guard->release(); + if (!$okay) { + throw new Exception('Failed to set preference setting.'); + } +*/ + } + + public function getConsoleSetting($key) { +// $viewer_id = $this->getCoreData()->getViewerContext()->getUserID(); +// return idx(idx($this->settings[$viewer_id], $key), 'value'); + return true; + } + + public function getPlugin($plugin_name) { + return idx($this->plugins, $plugin_name); + } + + public function __construct() { + +/* + $this->settings = users_multiget_prefs_info( + array($coredata->getViewerContext()->getUserID()), + self::APPLICATION_ID); + + $disabled = $this->getConsoleSetting(self::SETTING_PLUGINS); + $disabled = array_flip(explode(',', $disabled)); +*/ + foreach (self::getPlugins() as $plugin_name) { + $plugin = self::newPlugin($plugin_name); + if ($plugin->isPermanent() || !isset($disabled[$plugin_name])) { + if ($plugin->shouldStartup()) { + $plugin->didStartup(); + $plugin->setConsoleCore($this); + $this->plugins[$plugin_name] = $plugin; + } + } + } + } + + public static function newPlugin($plugin) { + $class = 'DarkConsole'.$plugin.'Plugin'; + PhutilSymbolLoader::loadClass($class); + return newv($class, array()); + } + + public function getEnabledPlugins() { + return $this->plugins; + } + + public function render(AphrontRequest $request) { + + $plugins = $this->getEnabledPlugins(); + + foreach ($plugins as $plugin) { + $plugin->setRequest($request); + $plugin->willShutdown(); + } + + foreach ($plugins as $plugin) { + $plugin->didShutdown(); + } + + foreach ($plugins as $plugin) { + $plugin->setData($plugin->generateData()); + } + + $selected = 'XHProf';//true;//$this->getConsoleSetting(DarkConsoleCore::SETTING_TAB); + $visible = true;//$this->getConsoleSetting(DarkConsoleCore::SETTING_VISIBLE); + + if (!isset($plugins[$selected])) { + $selected = key($plugins); + } + + $tabs = array(); + foreach ($plugins as $key => $plugin) { + $tabs[$key] = array( + 'name' => $plugin->getName(), + 'panel' => $plugin->render(), + ); + } + + $tabs_markup = array(); + $panel_markup = array(); + foreach ($tabs as $key => $data) { + $is_selected = ($key == $selected); + if ($is_selected) { + $style = null; + $tabclass = 'dark-console-tab-selected'; + } else { + $style = 'display: none;'; + $tabclass = null; + } + + $tabs_markup[] = javelin_render_tag( + 'a', + array( + 'class' => "dark-console-tab {$tabclass}", + 'sigil' => 'dark-console-tab', + 'meta' => array( + 'key' => $key, + ), + ), + (string)$data['name']); + + $panel_markup[] = javelin_render_tag( + 'div', + array( + 'class' => 'dark-console-panel', + 'style' => $style, + 'sigil' => 'dark-console-panel', + 'meta' => array( + 'key' => $key, + ), + ), + (string)$data['panel']); + } + + $console = javelin_render_tag( + 'table', + array( + 'class' => 'dark-console', + 'sigil' => 'dark-console', + 'meta' => array( + 'visible' => true, + ), + 'style' => '', + ), + ''. + ''. + implode("\n", $tabs_markup). + ''. + ''.implode("\n", $panel_markup).''. + ''); + + Javelin::initBehavior('dark-console'); + + return "\n\n\n\n".$console."\n\n\n\n"; + } +} + diff --git a/src/aphront/console/core/__init__.php b/src/aphront/console/core/__init__.php new file mode 100644 index 0000000000..ccdf4c2167 --- /dev/null +++ b/src/aphront/console/core/__init__.php @@ -0,0 +1,16 @@ +core = $core; + return $this; + } + + public function getConsoleCore() { + return $this->core; + } + + public function generateData() { + return null; + } + + public function setData($data) { + $this->data = $data; + return $this; + } + + public function getData() { + return $this->data; + } + + public function setRequest($request) { + $this->request = $request; + return $this; + } + + public function getRequest() { + return $this->request; + } + + public function isPermanent() { + return false; + } + + public function shouldStartup() { + return true; + } + + public function didStartup() { + return null; + } + + public function willShutdown() { + return null; + } + + public function didShutdown() { + return null; + } + + public function processRequest() { + return null; + } + +} diff --git a/src/aphront/console/plugin/base/__init__.php b/src/aphront/console/plugin/base/__init__.php new file mode 100644 index 0000000000..1132baa025 --- /dev/null +++ b/src/aphront/console/plugin/base/__init__.php @@ -0,0 +1,10 @@ +getData()); + +/* + if ($count) { + return + + Error Log ({$count}) + ; + } + +*/ + return 'Error Log'; + } + + + public function getDescription() { + return 'Shows errors and warnings.'; + } + + + public function generateData() { +/* + $stub = tabconsole(); + if (!$stub) { + return array(); + } + + $errors = $stub->getErrors(); + + $data = array(); + foreach ($errors as $error) { + if (is_array($error)) { + list($err, $trace) = $error; + $trace = implode("\n", $trace); + } else { + $err = $error->getMessage(); + $trace = $error->getTraceAsString(); + } + $data[] = array( + 'error' => $err, + 'trace' => $trace, + ); + } + return $data; +*/ + } + + + public function render() { + + return '!!'; +/* + $data = $this->getData(); + if (!$data) { + return + +
No errors.
+
; + } + + $markup = ; + $alt = false; + foreach ($data as $error) { + $row = ; + + $text = $error['error']; + $text = preg_replace('/\(in .* on line \d+\)$/', '', trim($text)); + + $trace = $error['trace']; + $trace = explode("\n", $trace); + if (!$trace) { + $trace = array('unknown@0@unknown'); + } + + foreach ($trace as $idx => $traceline) { + list($file, $line, $where) = array_merge( + explode('@', $traceline), + array('?', '?', '?')); + if ($where == 'DarkConsole->addError' || + $where == 'debug_rlog') { + unset($trace[$idx]); + } + } + + $row->appendChild(); + + foreach ($trace as $traceline) { + list($file, $line, $where) = array_merge( + explode('@', $traceline), + array('?', '?', '?')); + $row->appendChild(); + $row->appendChild(); + $markup->appendChild($row); + $row = ; + } + + $alt = !$alt; + } + + return + +

Errors

+
{$markup}
+
; +*/ + } + +} diff --git a/src/aphront/console/plugin/errorlog/__init__.php b/src/aphront/console/plugin/errorlog/__init__.php new file mode 100644 index 0000000000..0137999ed9 --- /dev/null +++ b/src/aphront/console/plugin/errorlog/__init__.php @@ -0,0 +1,12 @@ + $_REQUEST, + 'Server' => $_SERVER, + ); + } + + public function render() { + + $data = $this->getData(); + + $sections = array( + 'Basics' => array( + 'Host' => $data['Server']['SERVER_ADDR'], + 'Hostname' => gethostbyaddr($data['Server']['SERVER_ADDR']), + ), + ); + + $sections = array_merge($sections, $data); + +/* + $out = ; + foreach ($sections as $header => $map) { + $list =
{$text}{$file}:{$line}{$where}()
; + foreach ($map as $key => $value) { + if (!is_scalar($value)) { + $value = fb_json_encode($value); + } + $value = {$value}; + $list->appendChild( + ); + } + $out->appendChild( + +

{$header}

+ {$list} +
); + } + + return $out; +*/ + return "REQUEST"; + } +} diff --git a/src/aphront/console/plugin/request/__init__.php b/src/aphront/console/plugin/request/__init__.php new file mode 100644 index 0000000000..191e9cd6e0 --- /dev/null +++ b/src/aphront/console/plugin/request/__init__.php @@ -0,0 +1,12 @@ +observations = cacheobserver(); + } + + public function render() { + return '!'; + } +} + diff --git a/src/aphront/console/plugin/services/__init__.php b/src/aphront/console/plugin/services/__init__.php new file mode 100644 index 0000000000..aeab5c172b --- /dev/null +++ b/src/aphront/console/plugin/services/__init__.php @@ -0,0 +1,12 @@ +getData(); + + if ($run) { + return ' XHProf'; + } + + return 'XHProf'; + } + + public function getDescription() { + return 'Provides detailed PHP profiling information through XHProf.'; + } + + public function generateData() { + return $this->xhprofID; + } + + public function getXHProfRunID() { + return $this->xhprofID; + } + + public function render() { + if (!DarkConsoleXHProfPluginAPI::isProfilerAvailable()) { + return + '

The "xhprof" PHP extension is not available. Install xhprof '. + 'to enable the XHProf plugin.'; + } + + return '...'; + } + +} +/* + + public function render() { + $run = $this->getData(); + + if ($run) { + $uri = 'http://www.intern.facebook.com/intern/phprof/?run='.$run; + return + +

XHProf Results

+
+ Permalink +
{$key}{$value}