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, 28 "svg" => 1, // support scalable vector graphic 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 */ 42 function 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 */ 61 function 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; 72 case 'svg': 73 $mime = 'image/svg+xml'; // content type for scalable vector graphic 74 break; 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 */ 100 function 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 110 // Start moodle modification: use $CFG->pathtodot for executing this. 111 // $cmd = " dot -T".$type; 112 global $CFG; 113 $cmd = (!empty($CFG->pathtodot) ? $CFG->pathtodot : 'dot') . ' -T' . $type; 114 // End moodle modification. 115 116 $process = proc_open( $cmd, $descriptorspec, $pipes, sys_get_temp_dir(), array( 'PATH' => getenv( 'PATH' ) ) ); 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 */ 141 function 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 */ 171 function 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"]; 385 $result .= "[arrowsize=$arrow_size, color=grey, style=\"setlinewidth($linewidth)\"," 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 398 function 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 */ 439 function 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 */ 475 function 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 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body