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 callgraph image generation related XHProf utility
24 // Supported ouput format
25 $xhprof_legal_image_types = array(
29 "svg" => 1, // support scalable vector graphic
34 * Send an HTTP header with the response. You MUST use this function instead
35 * of header() so that we can debug header issues because they're virtually
36 * impossible to debug otherwise. If you try to commit header(), SVN will
39 * @param string HTTP header name, like 'Location'
40 * @param string HTTP header value, like 'http://www.example.com/'
43 function xhprof_http_header($name, $value) {
46 xhprof_error('http_header usage');
50 if (!is_string($value)) {
51 xhprof_error('http_header value not a string');
54 header($name.': '.$value, true);
58 * Genearte and send MIME header for the output image to client browser.
62 function xhprof_generate_mime_header($type, $length) {
74 $mime = 'image/svg+xml'; // content type for scalable vector graphic
77 $mime = 'application/postscript';
83 xhprof_http_header('Content-type', $mime);
84 xhprof_http_header('Content-length', (string)$length);
89 * Generate image according to DOT script. This function will spawn a process
90 * with "dot" command and pipe the "dot_script" to it and pipe out the
91 * generated image content.
93 * @param dot_script, string, the script for DOT to generate the image.
94 * @param type, one of the supported image types, see
95 * $xhprof_legal_image_types.
96 * @returns, binary content of the generated image on success. empty string on
101 function xhprof_generate_image_by_dot($dot_script, $type) {
102 $descriptorspec = array(
103 // stdin is a pipe that the child will read from
104 0 => array("pipe", "r"),
105 // stdout is a pipe that the child will write to
106 1 => array("pipe", "w"),
107 // stderr is a pipe that the child will write to
108 2 => array("pipe", "w")
111 $cmd = " dot -T".$type;
113 $process = proc_open($cmd, $descriptorspec, $pipes, "/tmp", array());
114 if (is_resource($process)) {
115 fwrite($pipes[0], $dot_script);
118 $output = stream_get_contents($pipes[1]);
120 $err = stream_get_contents($pipes[2]);
122 print "failed to execute cmd: \"$cmd\". stderr: `$err'\n";
128 proc_close($process);
131 print "failed to execute cmd \"$cmd\"";
136 * Get the children list of all nodes.
138 function xhprof_get_children_table($raw_data) {
139 $children_table = array();
140 foreach ($raw_data as $parent_child => $info) {
141 list($parent, $child) = xhprof_parse_parent_child($parent_child);
142 if (!isset($children_table[$parent])) {
143 $children_table[$parent] = array($child);
145 $children_table[$parent][] = $child;
148 return $children_table;
152 * Generate DOT script from the given raw phprof data.
154 * @param raw_data, phprof profile data.
155 * @param threshold, float, the threshold value [0,1). The functions in the
156 * raw_data whose exclusive wall times ratio are below the
157 * threshold will be filtered out and won't apprear in the
159 * @param page, string(optional), the root node name. This can be used to
160 * replace the 'main()' as the root node.
161 * @param func, string, the focus function.
162 * @param critical_path, bool, whether or not to display critical path with
164 * @returns, string, the DOT script to generate image.
168 function xhprof_generate_dot_script($raw_data, $threshold, $source, $page,
169 $func, $critical_path, $right=null,
175 $max_sizing_ratio = 20;
179 if ($left === null) {
180 // init_metrics($raw_data, null, null);
182 $sym_table = xhprof_compute_flat_info($raw_data, $totals);
184 if ($critical_path) {
185 $children_table = xhprof_get_children_table($raw_data);
188 $path_edges = array();
191 $visited[$node] = true;
192 if (isset($children_table[$node])) {
194 foreach ($children_table[$node] as $child) {
196 if (isset($visited[$child])) {
199 if ($max_child === null ||
200 abs($raw_data[xhprof_build_parent_child_key($node,
202 abs($raw_data[xhprof_build_parent_child_key($node,
203 $max_child)]["wt"])) {
207 if ($max_child !== null) {
208 $path[$max_child] = true;
209 $path_edges[xhprof_build_parent_child_key($node, $max_child)] = true;
218 // if it is a benchmark callgraph, we make the benchmarked function the root.
219 if ($source == "bm" && array_key_exists("main()", $sym_table)) {
220 $total_times = $sym_table["main()"]["ct"];
221 $remove_funcs = array("main()",
222 "hotprofiler_disable",
223 "call_user_func_array",
226 foreach ($remove_funcs as $cur_del_func) {
227 if (array_key_exists($cur_del_func, $sym_table) &&
228 $sym_table[$cur_del_func]["ct"] == $total_times) {
229 unset($sym_table[$cur_del_func]);
234 // use the function to filter out irrelevant functions.
236 $interested_funcs = array();
237 foreach ($raw_data as $parent_child => $info) {
238 list($parent, $child) = xhprof_parse_parent_child($parent_child);
239 if ($parent == $func || $child == $func) {
240 $interested_funcs[$parent] = 1;
241 $interested_funcs[$child] = 1;
244 foreach ($sym_table as $symbol => $info) {
245 if (!array_key_exists($symbol, $interested_funcs)) {
246 unset($sym_table[$symbol]);
251 $result = "digraph call_graph {\n";
253 // Filter out functions whose exclusive time ratio is below threshold, and
254 // also assign a unique integer id for each function to be generated. In the
255 // meantime, find the function with the most exclusive time (potentially the
256 // performance bottleneck).
257 $cur_id = 0; $max_wt = 0;
258 foreach ($sym_table as $symbol => $info) {
259 if (empty($func) && abs($info["wt"] / $totals["wt"]) < $threshold) {
260 unset($sym_table[$symbol]);
263 if ($max_wt == 0 || $max_wt < abs($info["excl_wt"])) {
264 $max_wt = abs($info["excl_wt"]);
266 $sym_table[$symbol]["id"] = $cur_id;
270 // Generate all nodes' information.
271 foreach ($sym_table as $symbol => $info) {
272 if ($info["excl_wt"] == 0) {
273 $sizing_factor = $max_sizing_ratio;
275 $sizing_factor = $max_wt / abs($info["excl_wt"]) ;
276 if ($sizing_factor > $max_sizing_ratio) {
277 $sizing_factor = $max_sizing_ratio;
280 $fillcolor = (($sizing_factor < 1.5) ?
281 ", style=filled, fillcolor=red" : "");
283 if ($critical_path) {
284 // highlight nodes along critical path.
285 if (!$fillcolor && array_key_exists($symbol, $path)) {
286 $fillcolor = ", style=filled, fillcolor=yellow";
290 $fontsize = ", fontsize="
291 .(int)($max_fontsize / (($sizing_factor - 1) / 10 + 1));
293 $width = ", width=".sprintf("%.1f", $max_width / $sizing_factor);
294 $height = ", height=".sprintf("%.1f", $max_height / $sizing_factor);
296 if ($symbol == "main()") {
298 $name = "Total: ".($totals["wt"] / 1000.0)." ms\\n";
299 $name .= addslashes(isset($page) ? $page : $symbol);
302 $name = addslashes($symbol)."\\nInc: ". sprintf("%.3f",$info["wt"] / 1000) .
303 " ms (" . sprintf("%.1f%%", 100 * $info["wt"] / $totals["wt"]).")";
305 if ($left === null) {
306 $label = ", label=\"".$name."\\nExcl: "
307 .(sprintf("%.3f",$info["excl_wt"] / 1000.0))." ms ("
308 .sprintf("%.1f%%", 100 * $info["excl_wt"] / $totals["wt"])
309 . ")\\n".$info["ct"]." total calls\"";
311 if (isset($left[$symbol]) && isset($right[$symbol])) {
312 $label = ", label=\"".addslashes($symbol).
313 "\\nInc: ".(sprintf("%.3f",$left[$symbol]["wt"] / 1000.0))
315 .(sprintf("%.3f",$right[$symbol]["wt"] / 1000.0))." ms = "
316 .(sprintf("%.3f",$info["wt"] / 1000.0))." ms".
318 .(sprintf("%.3f",$left[$symbol]["excl_wt"] / 1000.0))
319 ." ms - ".(sprintf("%.3f",$right[$symbol]["excl_wt"] / 1000.0))
320 ." ms = ".(sprintf("%.3f",$info["excl_wt"] / 1000.0))." ms".
321 "\\nCalls: ".(sprintf("%.3f",$left[$symbol]["ct"]))." - "
322 .(sprintf("%.3f",$right[$symbol]["ct"]))." = "
323 .(sprintf("%.3f",$info["ct"]))."\"";
324 } else if (isset($left[$symbol])) {
325 $label = ", label=\"".addslashes($symbol).
326 "\\nInc: ".(sprintf("%.3f",$left[$symbol]["wt"] / 1000.0))
327 ." ms - 0 ms = ".(sprintf("%.3f",$info["wt"] / 1000.0))
329 .(sprintf("%.3f",$left[$symbol]["excl_wt"] / 1000.0))
331 .(sprintf("%.3f",$info["excl_wt"] / 1000.0))." ms".
332 "\\nCalls: ".(sprintf("%.3f",$left[$symbol]["ct"]))." - 0 = "
333 .(sprintf("%.3f",$info["ct"]))."\"";
335 $label = ", label=\"".addslashes($symbol).
337 .(sprintf("%.3f",$right[$symbol]["wt"] / 1000.0))
338 ." ms = ".(sprintf("%.3f",$info["wt"] / 1000.0))." ms".
340 .(sprintf("%.3f",$right[$symbol]["excl_wt"] / 1000.0))
341 ." ms = ".(sprintf("%.3f",$info["excl_wt"] / 1000.0))." ms".
342 "\\nCalls: 0 - ".(sprintf("%.3f",$right[$symbol]["ct"]))
343 ." = ".(sprintf("%.3f",$info["ct"]))."\"";
346 $result .= "N" . $sym_table[$symbol]["id"];
347 $result .= "[shape=$shape ".$label.$width
348 .$height.$fontsize.$fillcolor."];\n";
351 // Generate all the edges' information.
352 foreach ($raw_data as $parent_child => $info) {
353 list($parent, $child) = xhprof_parse_parent_child($parent_child);
355 if (isset($sym_table[$parent]) && isset($sym_table[$child]) &&
357 (!empty($func) && ($parent == $func || $child == $func)))) {
359 $label = $info["ct"] == 1 ? $info["ct"]." call" : $info["ct"]." calls";
361 $headlabel = $sym_table[$child]["wt"] > 0 ?
362 sprintf("%.1f%%", 100 * $info["wt"]
363 / $sym_table[$child]["wt"])
366 $taillabel = ($sym_table[$parent]["wt"] > 0) ?
369 ($sym_table[$parent]["wt"] - $sym_table["$parent"]["excl_wt"]))
375 if ($critical_path &&
376 isset($path_edges[xhprof_build_parent_child_key($parent, $child)])) {
377 $linewidth = 10; $arrow_size = 2;
380 $result .= "N" . $sym_table[$parent]["id"] . " -> N"
381 . $sym_table[$child]["id"];
382 $result .= "[arrowsize=$arrow_size, style=\"setlinewidth($linewidth)\","
384 .$label."\", headlabel=\"".$headlabel
385 ."\", taillabel=\"".$taillabel."\" ]";
390 $result = $result . "\n}";
395 function xhprof_render_diff_image($xhprof_runs_impl, $run1, $run2,
396 $type, $threshold, $source) {
400 $raw_data1 = $xhprof_runs_impl->get_run($run1, $source, $desc_unused);
401 $raw_data2 = $xhprof_runs_impl->get_run($run2, $source, $desc_unused);
403 // init_metrics($raw_data1, null, null);
404 $children_table1 = xhprof_get_children_table($raw_data1);
405 $children_table2 = xhprof_get_children_table($raw_data2);
406 $symbol_tab1 = xhprof_compute_flat_info($raw_data1, $total1);
407 $symbol_tab2 = xhprof_compute_flat_info($raw_data2, $total2);
408 $run_delta = xhprof_compute_diff($raw_data1, $raw_data2);
409 $script = xhprof_generate_dot_script($run_delta, $threshold, $source,
411 $symbol_tab1, $symbol_tab2);
412 $content = xhprof_generate_image_by_dot($script, $type);
414 xhprof_generate_mime_header($type, strlen($content));
419 * Generate image content from phprof run id.
421 * @param object $xhprof_runs_impl An object that implements
422 * the iXHProfRuns interface
423 * @param run_id, integer, the unique id for the phprof run, this is the
424 * primary key for phprof database table.
425 * @param type, string, one of the supported image types. See also
426 * $xhprof_legal_image_types.
427 * @param threshold, float, the threshold value [0,1). The functions in the
428 * raw_data whose exclusive wall times ratio are below the
429 * threshold will be filtered out and won't apprear in the
431 * @param func, string, the focus function.
432 * @returns, string, the DOT script to generate image.
436 function xhprof_get_content_by_run($xhprof_runs_impl, $run_id, $type,
437 $threshold, $func, $source,
442 $raw_data = $xhprof_runs_impl->get_run($run_id, $source, $description);
444 xhprof_error("Raw data is empty");
448 $script = xhprof_generate_dot_script($raw_data, $threshold, $source,
449 $description, $func, $critical_path);
451 $content = xhprof_generate_image_by_dot($script, $type);
456 * Generate image from phprof run id and send it to client.
458 * @param object $xhprof_runs_impl An object that implements
459 * the iXHProfRuns interface
460 * @param run_id, integer, the unique id for the phprof run, this is the
461 * primary key for phprof database table.
462 * @param type, string, one of the supported image types. See also
463 * $xhprof_legal_image_types.
464 * @param threshold, float, the threshold value [0,1). The functions in the
465 * raw_data whose exclusive wall times ratio are below the
466 * threshold will be filtered out and won't apprear in the
468 * @param func, string, the focus function.
469 * @param bool, does this run correspond to a PHProfLive run or a dev run?
472 function xhprof_render_image($xhprof_runs_impl, $run_id, $type, $threshold,
473 $func, $source, $critical_path) {
475 $content = xhprof_get_content_by_run($xhprof_runs_impl, $run_id, $type,
477 $func, $source, $critical_path);
479 print "Error: either we can not find profile data for run_id ".$run_id
480 ." or the threshold ".$threshold." is too small or you do not"
481 ." have 'dot' image generation utility installed.";
485 xhprof_generate_mime_header($type, strlen($content));