]> CyberLeo.Net >> Repos - Github/sugarcrm.git/blob - include/SugarXHprof/xhprof_lib/utils/callgraph_utils.php
Release 6.5.10
[Github/sugarcrm.git] / include / SugarXHprof / xhprof_lib / utils / callgraph_utils.php
1 <?php
2 if(!defined('sugarEntry') || !sugarEntry) die('Not A Valid Entry Point');
3 //  Copyright (c) 2009 Facebook
4 //
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
8 //
9 //      http://www.apache.org/licenses/LICENSE-2.0
10 //
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.
16 //
17
18 /*
19  * This file contains callgraph image generation related XHProf utility
20  * functions
21  *
22  */
23
24 // Supported ouput format
25 $xhprof_legal_image_types = array(
26     "jpg" => 1,
27     "gif" => 1,
28     "png" => 1,
29     "svg" => 1, // support scalable vector graphic
30     "ps"  => 1,
31     );
32
33 /**
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
37  * reject your commit.
38  *
39  * @param string  HTTP header name, like 'Location'
40  * @param string  HTTP header value, like 'http://www.example.com/'
41  *
42  */
43 function xhprof_http_header($name, $value) {
44
45   if (!$name) {
46     xhprof_error('http_header usage');
47     return null;
48   }
49
50   if (!is_string($value)) {
51     xhprof_error('http_header value not a string');
52   }
53
54   header($name.': '.$value, true);
55 }
56
57 /**
58  * Genearte and send MIME header for the output image to client browser.
59  *
60  * @author cjiang
61  */
62 function xhprof_generate_mime_header($type, $length) {
63   switch ($type) {
64     case 'jpg':
65       $mime = 'image/jpeg';
66       break;
67     case 'gif':
68       $mime = 'image/gif';
69       break;
70     case 'png':
71       $mime = 'image/png';
72       break;
73     case 'svg':
74       $mime = 'image/svg+xml'; // content type for scalable vector graphic
75       break;
76     case 'ps':
77       $mime = 'application/postscript';
78     default:
79       $mime = false;
80   }
81
82   if ($mime) {
83     xhprof_http_header('Content-type', $mime);
84     xhprof_http_header('Content-length', (string)$length);
85   }
86 }
87
88 /**
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.
92  *
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
97  *           failure.
98  *
99  * @author cjiang
100  */
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")
109        );
110
111   $cmd = " dot -T".$type;
112
113   $process = proc_open($cmd, $descriptorspec, $pipes, "/tmp", array());
114   if (is_resource($process)) {
115     fwrite($pipes[0], $dot_script);
116     fclose($pipes[0]);
117
118     $output = stream_get_contents($pipes[1]);
119
120     $err = stream_get_contents($pipes[2]);
121     if (!empty($err)) {
122       print "failed to execute cmd: \"$cmd\". stderr: `$err'\n";
123       exit;
124     }
125
126     fclose($pipes[2]);
127     fclose($pipes[1]);
128     proc_close($process);
129     return $output;
130   }
131   print "failed to execute cmd \"$cmd\"";
132   exit();
133 }
134
135 /*
136  * Get the children list of all nodes.
137  */
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);
144     } else {
145       $children_table[$parent][] = $child;
146     }
147   }
148   return $children_table;
149 }
150
151 /**
152  * Generate DOT script from the given raw phprof data.
153  *
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
158  *                   generated image.
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
163  *                             bold lines.
164  * @returns, string, the DOT script to generate image.
165  *
166  * @author cjiang
167  */
168 function xhprof_generate_dot_script($raw_data, $threshold, $source, $page,
169                                     $func, $critical_path, $right=null,
170                                     $left=null) {
171
172   $max_width = 5;
173   $max_height = 3.5;
174   $max_fontsize = 35;
175   $max_sizing_ratio = 20;
176
177   $totals;
178
179   if ($left === null) {
180     // init_metrics($raw_data, null, null);
181   }
182   $sym_table = xhprof_compute_flat_info($raw_data, $totals);
183
184   if ($critical_path) {
185     $children_table = xhprof_get_children_table($raw_data);
186     $node = "main()";
187     $path = array();
188     $path_edges = array();
189     $visited = array();
190     while ($node) {
191       $visited[$node] = true;
192       if (isset($children_table[$node])) {
193         $max_child = null;
194         foreach ($children_table[$node] as $child) {
195
196           if (isset($visited[$child])) {
197             continue;
198           }
199           if ($max_child === null ||
200             abs($raw_data[xhprof_build_parent_child_key($node,
201                                                         $child)]["wt"]) >
202             abs($raw_data[xhprof_build_parent_child_key($node,
203                                                         $max_child)]["wt"])) {
204             $max_child = $child;
205           }
206         }
207         if ($max_child !== null) {
208           $path[$max_child] = true;
209           $path_edges[xhprof_build_parent_child_key($node, $max_child)] = true;
210         }
211         $node = $max_child;
212       } else {
213         $node = null;
214       }
215     }
216   }
217
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",
224                           "xhprof_disable");
225
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]);
230       }
231     }
232   }
233
234   // use the function to filter out irrelevant functions.
235   if (!empty($func)) {
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;
242       }
243     }
244     foreach ($sym_table as $symbol => $info) {
245       if (!array_key_exists($symbol, $interested_funcs)) {
246         unset($sym_table[$symbol]);
247       }
248     }
249   }
250
251   $result = "digraph call_graph {\n";
252
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]);
261       continue;
262     }
263     if ($max_wt == 0 || $max_wt < abs($info["excl_wt"])) {
264       $max_wt = abs($info["excl_wt"]);
265     }
266     $sym_table[$symbol]["id"] = $cur_id;
267     $cur_id ++;
268   }
269
270   // Generate all nodes' information.
271   foreach ($sym_table as $symbol => $info) {
272     if ($info["excl_wt"] == 0) {
273       $sizing_factor = $max_sizing_ratio;
274     } else {
275       $sizing_factor = $max_wt / abs($info["excl_wt"]) ;
276       if ($sizing_factor > $max_sizing_ratio) {
277         $sizing_factor = $max_sizing_ratio;
278       }
279     }
280     $fillcolor = (($sizing_factor < 1.5) ?
281                   ", style=filled, fillcolor=red" : "");
282
283     if ($critical_path) {
284       // highlight nodes along critical path.
285       if (!$fillcolor && array_key_exists($symbol, $path)) {
286         $fillcolor = ", style=filled, fillcolor=yellow";
287       }
288     }
289
290     $fontsize = ", fontsize="
291                .(int)($max_fontsize / (($sizing_factor - 1) / 10 + 1));
292
293     $width = ", width=".sprintf("%.1f", $max_width / $sizing_factor);
294     $height = ", height=".sprintf("%.1f", $max_height / $sizing_factor);
295
296     if ($symbol == "main()") {
297       $shape = "octagon";
298       $name = "Total: ".($totals["wt"] / 1000.0)." ms\\n";
299       $name .= addslashes(isset($page) ? $page : $symbol);
300     } else {
301       $shape = "box";
302       $name = addslashes($symbol)."\\nInc: ". sprintf("%.3f",$info["wt"] / 1000) .
303               " ms (" . sprintf("%.1f%%", 100 * $info["wt"] / $totals["wt"]).")";
304     }
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\"";
310     } else {
311       if (isset($left[$symbol]) && isset($right[$symbol])) {
312          $label = ", label=\"".addslashes($symbol).
313                   "\\nInc: ".(sprintf("%.3f",$left[$symbol]["wt"] / 1000.0))
314                   ." ms - "
315                   .(sprintf("%.3f",$right[$symbol]["wt"] / 1000.0))." ms = "
316                   .(sprintf("%.3f",$info["wt"] / 1000.0))." ms".
317                   "\\nExcl: "
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))
328                    ." ms"."\\nExcl: "
329                    .(sprintf("%.3f",$left[$symbol]["excl_wt"] / 1000.0))
330                    ." ms - 0 ms = "
331                    .(sprintf("%.3f",$info["excl_wt"] / 1000.0))." ms".
332                   "\\nCalls: ".(sprintf("%.3f",$left[$symbol]["ct"]))." - 0 = "
333                   .(sprintf("%.3f",$info["ct"]))."\"";
334       } else {
335         $label = ", label=\"".addslashes($symbol).
336                   "\\nInc: 0 ms - "
337                   .(sprintf("%.3f",$right[$symbol]["wt"] / 1000.0))
338                   ." ms = ".(sprintf("%.3f",$info["wt"] / 1000.0))." ms".
339                   "\\nExcl: 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"]))."\"";
344       }
345     }
346     $result .= "N" . $sym_table[$symbol]["id"];
347     $result .= "[shape=$shape ".$label.$width
348                .$height.$fontsize.$fillcolor."];\n";
349   }
350
351   // Generate all the edges' information.
352   foreach ($raw_data as $parent_child => $info) {
353     list($parent, $child) = xhprof_parse_parent_child($parent_child);
354
355     if (isset($sym_table[$parent]) && isset($sym_table[$child]) &&
356         (empty($func) ||
357          (!empty($func) && ($parent == $func || $child == $func)))) {
358
359       $label = $info["ct"] == 1 ? $info["ct"]." call" : $info["ct"]." calls";
360
361       $headlabel = $sym_table[$child]["wt"] > 0 ?
362                   sprintf("%.1f%%", 100 * $info["wt"]
363                                     / $sym_table[$child]["wt"])
364                   : "0.0%";
365
366       $taillabel = ($sym_table[$parent]["wt"] > 0) ?
367         sprintf("%.1f%%",
368                 100 * $info["wt"] /
369                 ($sym_table[$parent]["wt"] - $sym_table["$parent"]["excl_wt"]))
370         : "0.0%";
371
372       $linewidth = 1;
373       $arrow_size = 1;
374
375       if ($critical_path &&
376           isset($path_edges[xhprof_build_parent_child_key($parent, $child)])) {
377         $linewidth = 10; $arrow_size = 2;
378       }
379
380       $result .= "N" . $sym_table[$parent]["id"] . " -> N"
381                  . $sym_table[$child]["id"];
382       $result .= "[arrowsize=$arrow_size, style=\"setlinewidth($linewidth)\","
383                  ." label=\""
384                  .$label."\", headlabel=\"".$headlabel
385                  ."\", taillabel=\"".$taillabel."\" ]";
386       $result .= ";\n";
387
388     }
389   }
390   $result = $result . "\n}";
391
392   return $result;
393 }
394
395 function  xhprof_render_diff_image($xhprof_runs_impl, $run1, $run2,
396                                    $type, $threshold, $source) {
397   $total1;
398   $total2;
399
400   $raw_data1 = $xhprof_runs_impl->get_run($run1, $source, $desc_unused);
401   $raw_data2 = $xhprof_runs_impl->get_run($run2, $source, $desc_unused);
402
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,
410                                        null, null, true,
411                                        $symbol_tab1, $symbol_tab2);
412   $content = xhprof_generate_image_by_dot($script, $type);
413
414   xhprof_generate_mime_header($type, strlen($content));
415   echo $content;
416 }
417
418 /**
419  * Generate image content from phprof run id.
420  *
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
430  *                   generated image.
431  * @param func, string, the focus function.
432  * @returns, string, the DOT script to generate image.
433  *
434  * @author cjiang
435  */
436 function xhprof_get_content_by_run($xhprof_runs_impl, $run_id, $type,
437                                    $threshold, $func, $source,
438                                    $critical_path) {
439   if (!$run_id)
440     return "";
441
442   $raw_data = $xhprof_runs_impl->get_run($run_id, $source, $description);
443   if (!$raw_data) {
444     xhprof_error("Raw data is empty");
445     return "";
446   }
447
448   $script = xhprof_generate_dot_script($raw_data, $threshold, $source,
449                                        $description, $func, $critical_path);
450
451   $content = xhprof_generate_image_by_dot($script, $type);
452   return $content;
453 }
454
455 /**
456  * Generate image from phprof run id and send it to client.
457  *
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
467  *                   generated image.
468  * @param func, string, the focus function.
469  * @param bool, does this run correspond to a PHProfLive run or a dev run?
470  * @author cjiang
471  */
472 function xhprof_render_image($xhprof_runs_impl, $run_id, $type, $threshold,
473                              $func, $source, $critical_path) {
474
475   $content = xhprof_get_content_by_run($xhprof_runs_impl, $run_id, $type,
476                                        $threshold,
477                                        $func, $source, $critical_path);
478   if (!$content) {
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.";
482     exit();
483   }
484
485   xhprof_generate_mime_header($type, strlen($content));
486   echo $content;
487 }