Merge branch 'wip-MDL-60313-master' of git://github.com/marinaglancy/moodle
[moodle.git] / lib / xhprof / xhprof_lib / utils / callgraph_utils.php
CommitLineData
6af80cae
EL
1<?php
2// Copyright (c) 2009 Facebook
3//
4// Licensed under the Apache License, Version 2.0 (the "License");
5// you may not use this file except in compliance with the License.
6// You may obtain a copy of the License at
7//
8// http://www.apache.org/licenses/LICENSE-2.0
9//
10// Unless required by applicable law or agreed to in writing, software
11// distributed under the License is distributed on an "AS IS" BASIS,
12// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13// See the License for the specific language governing permissions and
14// limitations under the License.
15//
16
17/*
18 * This file contains callgraph image generation related XHProf utility
19 * functions
20 *
21 */
22
23// Supported ouput format
24$xhprof_legal_image_types = array(
25 "jpg" => 1,
26 "gif" => 1,
27 "png" => 1,
6919693c 28 "svg" => 1, // support scalable vector graphic
6af80cae
EL
29 "ps" => 1,
30 );
31
32/**
33 * Send an HTTP header with the response. You MUST use this function instead
34 * of header() so that we can debug header issues because they're virtually
35 * impossible to debug otherwise. If you try to commit header(), SVN will
36 * reject your commit.
37 *
38 * @param string HTTP header name, like 'Location'
39 * @param string HTTP header value, like 'http://www.example.com/'
40 *
41 */
42function xhprof_http_header($name, $value) {
43
44 if (!$name) {
45 xhprof_error('http_header usage');
46 return null;
47 }
48
49 if (!is_string($value)) {
50 xhprof_error('http_header value not a string');
51 }
52
53 header($name.': '.$value, true);
54}
55
56/**
57 * Genearte and send MIME header for the output image to client browser.
58 *
59 * @author cjiang
60 */
61function xhprof_generate_mime_header($type, $length) {
62 switch ($type) {
63 case 'jpg':
64 $mime = 'image/jpeg';
65 break;
66 case 'gif':
67 $mime = 'image/gif';
68 break;
69 case 'png':
70 $mime = 'image/png';
71 break;
6919693c
EL
72 case 'svg':
73 $mime = 'image/svg+xml'; // content type for scalable vector graphic
74 break;
6af80cae
EL
75 case 'ps':
76 $mime = 'application/postscript';
77 default:
78 $mime = false;
79 }
80
81 if ($mime) {
82 xhprof_http_header('Content-type', $mime);
83 xhprof_http_header('Content-length', (string)$length);
84 }
85}
86
87/**
88 * Generate image according to DOT script. This function will spawn a process
89 * with "dot" command and pipe the "dot_script" to it and pipe out the
90 * generated image content.
91 *
92 * @param dot_script, string, the script for DOT to generate the image.
93 * @param type, one of the supported image types, see
94 * $xhprof_legal_image_types.
95 * @returns, binary content of the generated image on success. empty string on
96 * failure.
97 *
98 * @author cjiang
99 */
100function xhprof_generate_image_by_dot($dot_script, $type) {
101 $descriptorspec = array(
102 // stdin is a pipe that the child will read from
103 0 => array("pipe", "r"),
104 // stdout is a pipe that the child will write to
105 1 => array("pipe", "w"),
106 // stderr is a pipe that the child will write to
107 2 => array("pipe", "w")
108 );
109
6919693c
EL
110 // Start moodle modification: use $CFG->pathtodot for executing this.
111 // $cmd = " dot -T".$type;
6af80cae
EL
112 global $CFG;
113 $cmd = (!empty($CFG->pathtodot) ? $CFG->pathtodot : 'dot') . ' -T' . $type;
6919693c 114 // End moodle modification.
6af80cae 115
ffb41a8f 116 $process = proc_open( $cmd, $descriptorspec, $pipes, sys_get_temp_dir(), array( 'PATH' => getenv( 'PATH' ) ) );
6af80cae
EL
117 if (is_resource($process)) {
118 fwrite($pipes[0], $dot_script);
119 fclose($pipes[0]);
120
121 $output = stream_get_contents($pipes[1]);
122
123 $err = stream_get_contents($pipes[2]);
124 if (!empty($err)) {
125 print "failed to execute cmd: \"$cmd\". stderr: `$err'\n";
126 exit;
127 }
128
129 fclose($pipes[2]);
130 fclose($pipes[1]);
131 proc_close($process);
132 return $output;
133 }
134 print "failed to execute cmd \"$cmd\"";
135 exit();
136}
137
138/*
139 * Get the children list of all nodes.
140 */
141function xhprof_get_children_table($raw_data) {
142 $children_table = array();
143 foreach ($raw_data as $parent_child => $info) {
144 list($parent, $child) = xhprof_parse_parent_child($parent_child);
145 if (!isset($children_table[$parent])) {
146 $children_table[$parent] = array($child);
147 } else {
148 $children_table[$parent][] = $child;
149 }
150 }
151 return $children_table;
152}
153
154/**
155 * Generate DOT script from the given raw phprof data.
156 *
157 * @param raw_data, phprof profile data.
158 * @param threshold, float, the threshold value [0,1). The functions in the
159 * raw_data whose exclusive wall times ratio are below the
160 * threshold will be filtered out and won't apprear in the
161 * generated image.
162 * @param page, string(optional), the root node name. This can be used to
163 * replace the 'main()' as the root node.
164 * @param func, string, the focus function.
165 * @param critical_path, bool, whether or not to display critical path with
166 * bold lines.
167 * @returns, string, the DOT script to generate image.
168 *
169 * @author cjiang
170 */
171function xhprof_generate_dot_script($raw_data, $threshold, $source, $page,
172 $func, $critical_path, $right=null,
173 $left=null) {
174
175 $max_width = 5;
176 $max_height = 3.5;
177 $max_fontsize = 35;
178 $max_sizing_ratio = 20;
179
180 $totals;
181
182 if ($left === null) {
183 // init_metrics($raw_data, null, null);
184 }
185 $sym_table = xhprof_compute_flat_info($raw_data, $totals);
186
187 if ($critical_path) {
188 $children_table = xhprof_get_children_table($raw_data);
189 $node = "main()";
190 $path = array();
191 $path_edges = array();
192 $visited = array();
193 while ($node) {
194 $visited[$node] = true;
195 if (isset($children_table[$node])) {
196 $max_child = null;
197 foreach ($children_table[$node] as $child) {
198
199 if (isset($visited[$child])) {
200 continue;
201 }
202 if ($max_child === null ||
203 abs($raw_data[xhprof_build_parent_child_key($node,
204 $child)]["wt"]) >
205 abs($raw_data[xhprof_build_parent_child_key($node,
206 $max_child)]["wt"])) {
207 $max_child = $child;
208 }
209 }
210 if ($max_child !== null) {
211 $path[$max_child] = true;
212 $path_edges[xhprof_build_parent_child_key($node, $max_child)] = true;
213 }
214 $node = $max_child;
215 } else {
216 $node = null;
217 }
218 }
219 }
220
221 // if it is a benchmark callgraph, we make the benchmarked function the root.
222 if ($source == "bm" && array_key_exists("main()", $sym_table)) {
223 $total_times = $sym_table["main()"]["ct"];
224 $remove_funcs = array("main()",
225 "hotprofiler_disable",
226 "call_user_func_array",
227 "xhprof_disable");
228
229 foreach ($remove_funcs as $cur_del_func) {
230 if (array_key_exists($cur_del_func, $sym_table) &&
231 $sym_table[$cur_del_func]["ct"] == $total_times) {
232 unset($sym_table[$cur_del_func]);
233 }
234 }
235 }
236
237 // use the function to filter out irrelevant functions.
238 if (!empty($func)) {
239 $interested_funcs = array();
240 foreach ($raw_data as $parent_child => $info) {
241 list($parent, $child) = xhprof_parse_parent_child($parent_child);
242 if ($parent == $func || $child == $func) {
243 $interested_funcs[$parent] = 1;
244 $interested_funcs[$child] = 1;
245 }
246 }
247 foreach ($sym_table as $symbol => $info) {
248 if (!array_key_exists($symbol, $interested_funcs)) {
249 unset($sym_table[$symbol]);
250 }
251 }
252 }
253
254 $result = "digraph call_graph {\n";
255
256 // Filter out functions whose exclusive time ratio is below threshold, and
257 // also assign a unique integer id for each function to be generated. In the
258 // meantime, find the function with the most exclusive time (potentially the
259 // performance bottleneck).
260 $cur_id = 0; $max_wt = 0;
261 foreach ($sym_table as $symbol => $info) {
262 if (empty($func) && abs($info["wt"] / $totals["wt"]) < $threshold) {
263 unset($sym_table[$symbol]);
264 continue;
265 }
266 if ($max_wt == 0 || $max_wt < abs($info["excl_wt"])) {
267 $max_wt = abs($info["excl_wt"]);
268 }
269 $sym_table[$symbol]["id"] = $cur_id;
270 $cur_id ++;
271 }
272
273 // Generate all nodes' information.
274 foreach ($sym_table as $symbol => $info) {
275 if ($info["excl_wt"] == 0) {
276 $sizing_factor = $max_sizing_ratio;
277 } else {
278 $sizing_factor = $max_wt / abs($info["excl_wt"]) ;
279 if ($sizing_factor > $max_sizing_ratio) {
280 $sizing_factor = $max_sizing_ratio;
281 }
282 }
283 $fillcolor = (($sizing_factor < 1.5) ?
284 ", style=filled, fillcolor=red" : "");
285
286 if ($critical_path) {
287 // highlight nodes along critical path.
288 if (!$fillcolor && array_key_exists($symbol, $path)) {
289 $fillcolor = ", style=filled, fillcolor=yellow";
290 }
291 }
292
293 $fontsize = ", fontsize="
294 .(int)($max_fontsize / (($sizing_factor - 1) / 10 + 1));
295
296 $width = ", width=".sprintf("%.1f", $max_width / $sizing_factor);
297 $height = ", height=".sprintf("%.1f", $max_height / $sizing_factor);
298
299 if ($symbol == "main()") {
300 $shape = "octagon";
301 $name = "Total: ".($totals["wt"] / 1000.0)." ms\\n";
302 $name .= addslashes(isset($page) ? $page : $symbol);
303 } else {
304 $shape = "box";
305 $name = addslashes($symbol)."\\nInc: ". sprintf("%.3f",$info["wt"] / 1000) .
306 " ms (" . sprintf("%.1f%%", 100 * $info["wt"] / $totals["wt"]).")";
307 }
308 if ($left === null) {
309 $label = ", label=\"".$name."\\nExcl: "
310 .(sprintf("%.3f",$info["excl_wt"] / 1000.0))." ms ("
311 .sprintf("%.1f%%", 100 * $info["excl_wt"] / $totals["wt"])
312 . ")\\n".$info["ct"]." total calls\"";
313 } else {
314 if (isset($left[$symbol]) && isset($right[$symbol])) {
315 $label = ", label=\"".addslashes($symbol).
316 "\\nInc: ".(sprintf("%.3f",$left[$symbol]["wt"] / 1000.0))
317 ." ms - "
318 .(sprintf("%.3f",$right[$symbol]["wt"] / 1000.0))." ms = "
319 .(sprintf("%.3f",$info["wt"] / 1000.0))." ms".
320 "\\nExcl: "
321 .(sprintf("%.3f",$left[$symbol]["excl_wt"] / 1000.0))
322 ." ms - ".(sprintf("%.3f",$right[$symbol]["excl_wt"] / 1000.0))
323 ." ms = ".(sprintf("%.3f",$info["excl_wt"] / 1000.0))." ms".
324 "\\nCalls: ".(sprintf("%.3f",$left[$symbol]["ct"]))." - "
325 .(sprintf("%.3f",$right[$symbol]["ct"]))." = "
326 .(sprintf("%.3f",$info["ct"]))."\"";
327 } else if (isset($left[$symbol])) {
328 $label = ", label=\"".addslashes($symbol).
329 "\\nInc: ".(sprintf("%.3f",$left[$symbol]["wt"] / 1000.0))
330 ." ms - 0 ms = ".(sprintf("%.3f",$info["wt"] / 1000.0))
331 ." ms"."\\nExcl: "
332 .(sprintf("%.3f",$left[$symbol]["excl_wt"] / 1000.0))
333 ." ms - 0 ms = "
334 .(sprintf("%.3f",$info["excl_wt"] / 1000.0))." ms".
335 "\\nCalls: ".(sprintf("%.3f",$left[$symbol]["ct"]))." - 0 = "
336 .(sprintf("%.3f",$info["ct"]))."\"";
337 } else {
338 $label = ", label=\"".addslashes($symbol).
339 "\\nInc: 0 ms - "
340 .(sprintf("%.3f",$right[$symbol]["wt"] / 1000.0))
341 ." ms = ".(sprintf("%.3f",$info["wt"] / 1000.0))." ms".
342 "\\nExcl: 0 ms - "
343 .(sprintf("%.3f",$right[$symbol]["excl_wt"] / 1000.0))
344 ." ms = ".(sprintf("%.3f",$info["excl_wt"] / 1000.0))." ms".
345 "\\nCalls: 0 - ".(sprintf("%.3f",$right[$symbol]["ct"]))
346 ." = ".(sprintf("%.3f",$info["ct"]))."\"";
347 }
348 }
349 $result .= "N" . $sym_table[$symbol]["id"];
350 $result .= "[shape=$shape ".$label.$width
351 .$height.$fontsize.$fillcolor."];\n";
352 }
353
354 // Generate all the edges' information.
355 foreach ($raw_data as $parent_child => $info) {
356 list($parent, $child) = xhprof_parse_parent_child($parent_child);
357
358 if (isset($sym_table[$parent]) && isset($sym_table[$child]) &&
359 (empty($func) ||
360 (!empty($func) && ($parent == $func || $child == $func)))) {
361
362 $label = $info["ct"] == 1 ? $info["ct"]." call" : $info["ct"]." calls";
363
364 $headlabel = $sym_table[$child]["wt"] > 0 ?
365 sprintf("%.1f%%", 100 * $info["wt"]
366 / $sym_table[$child]["wt"])
367 : "0.0%";
368
369 $taillabel = ($sym_table[$parent]["wt"] > 0) ?
370 sprintf("%.1f%%",
371 100 * $info["wt"] /
372 ($sym_table[$parent]["wt"] - $sym_table["$parent"]["excl_wt"]))
373 : "0.0%";
374
375 $linewidth = 1;
376 $arrow_size = 1;
377
378 if ($critical_path &&
379 isset($path_edges[xhprof_build_parent_child_key($parent, $child)])) {
380 $linewidth = 10; $arrow_size = 2;
381 }
382
383 $result .= "N" . $sym_table[$parent]["id"] . " -> N"
384 . $sym_table[$child]["id"];
ffb41a8f 385 $result .= "[arrowsize=$arrow_size, color=grey, style=\"setlinewidth($linewidth)\","
6af80cae
EL
386 ." label=\""
387 .$label."\", headlabel=\"".$headlabel
388 ."\", taillabel=\"".$taillabel."\" ]";
389 $result .= ";\n";
390
391 }
392 }
393 $result = $result . "\n}";
394
395 return $result;
396}
397
398function xhprof_render_diff_image($xhprof_runs_impl, $run1, $run2,
399 $type, $threshold, $source) {
400 $total1;
401 $total2;
402
403 $raw_data1 = $xhprof_runs_impl->get_run($run1, $source, $desc_unused);
404 $raw_data2 = $xhprof_runs_impl->get_run($run2, $source, $desc_unused);
405
406 // init_metrics($raw_data1, null, null);
407 $children_table1 = xhprof_get_children_table($raw_data1);
408 $children_table2 = xhprof_get_children_table($raw_data2);
409 $symbol_tab1 = xhprof_compute_flat_info($raw_data1, $total1);
410 $symbol_tab2 = xhprof_compute_flat_info($raw_data2, $total2);
411 $run_delta = xhprof_compute_diff($raw_data1, $raw_data2);
412 $script = xhprof_generate_dot_script($run_delta, $threshold, $source,
413 null, null, true,
414 $symbol_tab1, $symbol_tab2);
415 $content = xhprof_generate_image_by_dot($script, $type);
416
417 xhprof_generate_mime_header($type, strlen($content));
418 echo $content;
419}
420
421/**
422 * Generate image content from phprof run id.
423 *
424 * @param object $xhprof_runs_impl An object that implements
425 * the iXHProfRuns interface
426 * @param run_id, integer, the unique id for the phprof run, this is the
427 * primary key for phprof database table.
428 * @param type, string, one of the supported image types. See also
429 * $xhprof_legal_image_types.
430 * @param threshold, float, the threshold value [0,1). The functions in the
431 * raw_data whose exclusive wall times ratio are below the
432 * threshold will be filtered out and won't apprear in the
433 * generated image.
434 * @param func, string, the focus function.
435 * @returns, string, the DOT script to generate image.
436 *
437 * @author cjiang
438 */
439function xhprof_get_content_by_run($xhprof_runs_impl, $run_id, $type,
440 $threshold, $func, $source,
441 $critical_path) {
442 if (!$run_id)
443 return "";
444
445 $raw_data = $xhprof_runs_impl->get_run($run_id, $source, $description);
446 if (!$raw_data) {
447 xhprof_error("Raw data is empty");
448 return "";
449 }
450
451 $script = xhprof_generate_dot_script($raw_data, $threshold, $source,
452 $description, $func, $critical_path);
453
454 $content = xhprof_generate_image_by_dot($script, $type);
455 return $content;
456}
457
458/**
459 * Generate image from phprof run id and send it to client.
460 *
461 * @param object $xhprof_runs_impl An object that implements
462 * the iXHProfRuns interface
463 * @param run_id, integer, the unique id for the phprof run, this is the
464 * primary key for phprof database table.
465 * @param type, string, one of the supported image types. See also
466 * $xhprof_legal_image_types.
467 * @param threshold, float, the threshold value [0,1). The functions in the
468 * raw_data whose exclusive wall times ratio are below the
469 * threshold will be filtered out and won't apprear in the
470 * generated image.
471 * @param func, string, the focus function.
472 * @param bool, does this run correspond to a PHProfLive run or a dev run?
473 * @author cjiang
474 */
475function xhprof_render_image($xhprof_runs_impl, $run_id, $type, $threshold,
476 $func, $source, $critical_path) {
477
478 $content = xhprof_get_content_by_run($xhprof_runs_impl, $run_id, $type,
479 $threshold,
480 $func, $source, $critical_path);
481 if (!$content) {
482 print "Error: either we can not find profile data for run_id ".$run_id
483 ." or the threshold ".$threshold." is too small or you do not"
484 ." have 'dot' image generation utility installed.";
485 exit();
486 }
487
488 xhprof_generate_mime_header($type, strlen($content));
489 echo $content;
490}