|
Moodle
2.2.1
http://www.collinsharper.com
|
00001 <?php 00002 // Copyright (c) 2009 Facebook 00003 // 00004 // Licensed under the Apache License, Version 2.0 (the "License"); 00005 // you may not use this file except in compliance with the License. 00006 // You may obtain a copy of the License at 00007 // 00008 // http://www.apache.org/licenses/LICENSE-2.0 00009 // 00010 // Unless required by applicable law or agreed to in writing, software 00011 // distributed under the License is distributed on an "AS IS" BASIS, 00012 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 00013 // See the License for the specific language governing permissions and 00014 // limitations under the License. 00015 // 00016 00017 /* 00018 * This file contains callgraph image generation related XHProf utility 00019 * functions 00020 * 00021 */ 00022 00023 // Supported ouput format 00024 $xhprof_legal_image_types = array( 00025 "jpg" => 1, 00026 "gif" => 1, 00027 "png" => 1, 00028 "ps" => 1, 00029 ); 00030 00041 function xhprof_http_header($name, $value) { 00042 00043 if (!$name) { 00044 xhprof_error('http_header usage'); 00045 return null; 00046 } 00047 00048 if (!is_string($value)) { 00049 xhprof_error('http_header value not a string'); 00050 } 00051 00052 header($name.': '.$value, true); 00053 } 00054 00060 function xhprof_generate_mime_header($type, $length) { 00061 switch ($type) { 00062 case 'jpg': 00063 $mime = 'image/jpeg'; 00064 break; 00065 case 'gif': 00066 $mime = 'image/gif'; 00067 break; 00068 case 'png': 00069 $mime = 'image/png'; 00070 break; 00071 case 'ps': 00072 $mime = 'application/postscript'; 00073 default: 00074 $mime = false; 00075 } 00076 00077 if ($mime) { 00078 xhprof_http_header('Content-type', $mime); 00079 xhprof_http_header('Content-length', (string)$length); 00080 } 00081 } 00082 00096 function xhprof_generate_image_by_dot($dot_script, $type) { 00097 $descriptorspec = array( 00098 // stdin is a pipe that the child will read from 00099 0 => array("pipe", "r"), 00100 // stdout is a pipe that the child will write to 00101 1 => array("pipe", "w"), 00102 // stderr is a pipe that the child will write to 00103 2 => array("pipe", "w") 00104 ); 00105 00106 // start moodle modification: use $CFG->pathtodot for executing this 00107 //$cmd = " dot -T".$type; 00108 global $CFG; 00109 $cmd = (!empty($CFG->pathtodot) ? $CFG->pathtodot : 'dot') . ' -T' . $type; 00110 // end moodle modification 00111 00112 $process = proc_open($cmd, $descriptorspec, $pipes, "/tmp", array()); 00113 if (is_resource($process)) { 00114 fwrite($pipes[0], $dot_script); 00115 fclose($pipes[0]); 00116 00117 $output = stream_get_contents($pipes[1]); 00118 00119 $err = stream_get_contents($pipes[2]); 00120 if (!empty($err)) { 00121 print "failed to execute cmd: \"$cmd\". stderr: `$err'\n"; 00122 exit; 00123 } 00124 00125 fclose($pipes[2]); 00126 fclose($pipes[1]); 00127 proc_close($process); 00128 return $output; 00129 } 00130 print "failed to execute cmd \"$cmd\""; 00131 exit(); 00132 } 00133 00134 /* 00135 * Get the children list of all nodes. 00136 */ 00137 function xhprof_get_children_table($raw_data) { 00138 $children_table = array(); 00139 foreach ($raw_data as $parent_child => $info) { 00140 list($parent, $child) = xhprof_parse_parent_child($parent_child); 00141 if (!isset($children_table[$parent])) { 00142 $children_table[$parent] = array($child); 00143 } else { 00144 $children_table[$parent][] = $child; 00145 } 00146 } 00147 return $children_table; 00148 } 00149 00167 function xhprof_generate_dot_script($raw_data, $threshold, $source, $page, 00168 $func, $critical_path, $right=null, 00169 $left=null) { 00170 00171 $max_width = 5; 00172 $max_height = 3.5; 00173 $max_fontsize = 35; 00174 $max_sizing_ratio = 20; 00175 00176 $totals; 00177 00178 if ($left === null) { 00179 // init_metrics($raw_data, null, null); 00180 } 00181 $sym_table = xhprof_compute_flat_info($raw_data, $totals); 00182 00183 if ($critical_path) { 00184 $children_table = xhprof_get_children_table($raw_data); 00185 $node = "main()"; 00186 $path = array(); 00187 $path_edges = array(); 00188 $visited = array(); 00189 while ($node) { 00190 $visited[$node] = true; 00191 if (isset($children_table[$node])) { 00192 $max_child = null; 00193 foreach ($children_table[$node] as $child) { 00194 00195 if (isset($visited[$child])) { 00196 continue; 00197 } 00198 if ($max_child === null || 00199 abs($raw_data[xhprof_build_parent_child_key($node, 00200 $child)]["wt"]) > 00201 abs($raw_data[xhprof_build_parent_child_key($node, 00202 $max_child)]["wt"])) { 00203 $max_child = $child; 00204 } 00205 } 00206 if ($max_child !== null) { 00207 $path[$max_child] = true; 00208 $path_edges[xhprof_build_parent_child_key($node, $max_child)] = true; 00209 } 00210 $node = $max_child; 00211 } else { 00212 $node = null; 00213 } 00214 } 00215 } 00216 00217 // if it is a benchmark callgraph, we make the benchmarked function the root. 00218 if ($source == "bm" && array_key_exists("main()", $sym_table)) { 00219 $total_times = $sym_table["main()"]["ct"]; 00220 $remove_funcs = array("main()", 00221 "hotprofiler_disable", 00222 "call_user_func_array", 00223 "xhprof_disable"); 00224 00225 foreach ($remove_funcs as $cur_del_func) { 00226 if (array_key_exists($cur_del_func, $sym_table) && 00227 $sym_table[$cur_del_func]["ct"] == $total_times) { 00228 unset($sym_table[$cur_del_func]); 00229 } 00230 } 00231 } 00232 00233 // use the function to filter out irrelevant functions. 00234 if (!empty($func)) { 00235 $interested_funcs = array(); 00236 foreach ($raw_data as $parent_child => $info) { 00237 list($parent, $child) = xhprof_parse_parent_child($parent_child); 00238 if ($parent == $func || $child == $func) { 00239 $interested_funcs[$parent] = 1; 00240 $interested_funcs[$child] = 1; 00241 } 00242 } 00243 foreach ($sym_table as $symbol => $info) { 00244 if (!array_key_exists($symbol, $interested_funcs)) { 00245 unset($sym_table[$symbol]); 00246 } 00247 } 00248 } 00249 00250 $result = "digraph call_graph {\n"; 00251 00252 // Filter out functions whose exclusive time ratio is below threshold, and 00253 // also assign a unique integer id for each function to be generated. In the 00254 // meantime, find the function with the most exclusive time (potentially the 00255 // performance bottleneck). 00256 $cur_id = 0; $max_wt = 0; 00257 foreach ($sym_table as $symbol => $info) { 00258 if (empty($func) && abs($info["wt"] / $totals["wt"]) < $threshold) { 00259 unset($sym_table[$symbol]); 00260 continue; 00261 } 00262 if ($max_wt == 0 || $max_wt < abs($info["excl_wt"])) { 00263 $max_wt = abs($info["excl_wt"]); 00264 } 00265 $sym_table[$symbol]["id"] = $cur_id; 00266 $cur_id ++; 00267 } 00268 00269 // Generate all nodes' information. 00270 foreach ($sym_table as $symbol => $info) { 00271 if ($info["excl_wt"] == 0) { 00272 $sizing_factor = $max_sizing_ratio; 00273 } else { 00274 $sizing_factor = $max_wt / abs($info["excl_wt"]) ; 00275 if ($sizing_factor > $max_sizing_ratio) { 00276 $sizing_factor = $max_sizing_ratio; 00277 } 00278 } 00279 $fillcolor = (($sizing_factor < 1.5) ? 00280 ", style=filled, fillcolor=red" : ""); 00281 00282 if ($critical_path) { 00283 // highlight nodes along critical path. 00284 if (!$fillcolor && array_key_exists($symbol, $path)) { 00285 $fillcolor = ", style=filled, fillcolor=yellow"; 00286 } 00287 } 00288 00289 $fontsize = ", fontsize=" 00290 .(int)($max_fontsize / (($sizing_factor - 1) / 10 + 1)); 00291 00292 $width = ", width=".sprintf("%.1f", $max_width / $sizing_factor); 00293 $height = ", height=".sprintf("%.1f", $max_height / $sizing_factor); 00294 00295 if ($symbol == "main()") { 00296 $shape = "octagon"; 00297 $name = "Total: ".($totals["wt"] / 1000.0)." ms\\n"; 00298 $name .= addslashes(isset($page) ? $page : $symbol); 00299 } else { 00300 $shape = "box"; 00301 $name = addslashes($symbol)."\\nInc: ". sprintf("%.3f",$info["wt"] / 1000) . 00302 " ms (" . sprintf("%.1f%%", 100 * $info["wt"] / $totals["wt"]).")"; 00303 } 00304 if ($left === null) { 00305 $label = ", label=\"".$name."\\nExcl: " 00306 .(sprintf("%.3f",$info["excl_wt"] / 1000.0))." ms (" 00307 .sprintf("%.1f%%", 100 * $info["excl_wt"] / $totals["wt"]) 00308 . ")\\n".$info["ct"]." total calls\""; 00309 } else { 00310 if (isset($left[$symbol]) && isset($right[$symbol])) { 00311 $label = ", label=\"".addslashes($symbol). 00312 "\\nInc: ".(sprintf("%.3f",$left[$symbol]["wt"] / 1000.0)) 00313 ." ms - " 00314 .(sprintf("%.3f",$right[$symbol]["wt"] / 1000.0))." ms = " 00315 .(sprintf("%.3f",$info["wt"] / 1000.0))." ms". 00316 "\\nExcl: " 00317 .(sprintf("%.3f",$left[$symbol]["excl_wt"] / 1000.0)) 00318 ." ms - ".(sprintf("%.3f",$right[$symbol]["excl_wt"] / 1000.0)) 00319 ." ms = ".(sprintf("%.3f",$info["excl_wt"] / 1000.0))." ms". 00320 "\\nCalls: ".(sprintf("%.3f",$left[$symbol]["ct"]))." - " 00321 .(sprintf("%.3f",$right[$symbol]["ct"]))." = " 00322 .(sprintf("%.3f",$info["ct"]))."\""; 00323 } else if (isset($left[$symbol])) { 00324 $label = ", label=\"".addslashes($symbol). 00325 "\\nInc: ".(sprintf("%.3f",$left[$symbol]["wt"] / 1000.0)) 00326 ." ms - 0 ms = ".(sprintf("%.3f",$info["wt"] / 1000.0)) 00327 ." ms"."\\nExcl: " 00328 .(sprintf("%.3f",$left[$symbol]["excl_wt"] / 1000.0)) 00329 ." ms - 0 ms = " 00330 .(sprintf("%.3f",$info["excl_wt"] / 1000.0))." ms". 00331 "\\nCalls: ".(sprintf("%.3f",$left[$symbol]["ct"]))." - 0 = " 00332 .(sprintf("%.3f",$info["ct"]))."\""; 00333 } else { 00334 $label = ", label=\"".addslashes($symbol). 00335 "\\nInc: 0 ms - " 00336 .(sprintf("%.3f",$right[$symbol]["wt"] / 1000.0)) 00337 ." ms = ".(sprintf("%.3f",$info["wt"] / 1000.0))." ms". 00338 "\\nExcl: 0 ms - " 00339 .(sprintf("%.3f",$right[$symbol]["excl_wt"] / 1000.0)) 00340 ." ms = ".(sprintf("%.3f",$info["excl_wt"] / 1000.0))." ms". 00341 "\\nCalls: 0 - ".(sprintf("%.3f",$right[$symbol]["ct"])) 00342 ." = ".(sprintf("%.3f",$info["ct"]))."\""; 00343 } 00344 } 00345 $result .= "N" . $sym_table[$symbol]["id"]; 00346 $result .= "[shape=$shape ".$label.$width 00347 .$height.$fontsize.$fillcolor."];\n"; 00348 } 00349 00350 // Generate all the edges' information. 00351 foreach ($raw_data as $parent_child => $info) { 00352 list($parent, $child) = xhprof_parse_parent_child($parent_child); 00353 00354 if (isset($sym_table[$parent]) && isset($sym_table[$child]) && 00355 (empty($func) || 00356 (!empty($func) && ($parent == $func || $child == $func)))) { 00357 00358 $label = $info["ct"] == 1 ? $info["ct"]." call" : $info["ct"]." calls"; 00359 00360 $headlabel = $sym_table[$child]["wt"] > 0 ? 00361 sprintf("%.1f%%", 100 * $info["wt"] 00362 / $sym_table[$child]["wt"]) 00363 : "0.0%"; 00364 00365 $taillabel = ($sym_table[$parent]["wt"] > 0) ? 00366 sprintf("%.1f%%", 00367 100 * $info["wt"] / 00368 ($sym_table[$parent]["wt"] - $sym_table["$parent"]["excl_wt"])) 00369 : "0.0%"; 00370 00371 $linewidth = 1; 00372 $arrow_size = 1; 00373 00374 if ($critical_path && 00375 isset($path_edges[xhprof_build_parent_child_key($parent, $child)])) { 00376 $linewidth = 10; $arrow_size = 2; 00377 } 00378 00379 $result .= "N" . $sym_table[$parent]["id"] . " -> N" 00380 . $sym_table[$child]["id"]; 00381 $result .= "[arrowsize=$arrow_size, style=\"setlinewidth($linewidth)\"," 00382 ." label=\"" 00383 .$label."\", headlabel=\"".$headlabel 00384 ."\", taillabel=\"".$taillabel."\" ]"; 00385 $result .= ";\n"; 00386 00387 } 00388 } 00389 $result = $result . "\n}"; 00390 00391 return $result; 00392 } 00393 00394 function xhprof_render_diff_image($xhprof_runs_impl, $run1, $run2, 00395 $type, $threshold, $source) { 00396 $total1; 00397 $total2; 00398 00399 $raw_data1 = $xhprof_runs_impl->get_run($run1, $source, $desc_unused); 00400 $raw_data2 = $xhprof_runs_impl->get_run($run2, $source, $desc_unused); 00401 00402 // init_metrics($raw_data1, null, null); 00403 $children_table1 = xhprof_get_children_table($raw_data1); 00404 $children_table2 = xhprof_get_children_table($raw_data2); 00405 $symbol_tab1 = xhprof_compute_flat_info($raw_data1, $total1); 00406 $symbol_tab2 = xhprof_compute_flat_info($raw_data2, $total2); 00407 $run_delta = xhprof_compute_diff($raw_data1, $raw_data2); 00408 $script = xhprof_generate_dot_script($run_delta, $threshold, $source, 00409 null, null, true, 00410 $symbol_tab1, $symbol_tab2); 00411 $content = xhprof_generate_image_by_dot($script, $type); 00412 00413 xhprof_generate_mime_header($type, strlen($content)); 00414 echo $content; 00415 } 00416 00435 function xhprof_get_content_by_run($xhprof_runs_impl, $run_id, $type, 00436 $threshold, $func, $source, 00437 $critical_path) { 00438 if (!$run_id) 00439 return ""; 00440 00441 $raw_data = $xhprof_runs_impl->get_run($run_id, $source, $description); 00442 if (!$raw_data) { 00443 xhprof_error("Raw data is empty"); 00444 return ""; 00445 } 00446 00447 $script = xhprof_generate_dot_script($raw_data, $threshold, $source, 00448 $description, $func, $critical_path); 00449 00450 $content = xhprof_generate_image_by_dot($script, $type); 00451 return $content; 00452 } 00453 00471 function xhprof_render_image($xhprof_runs_impl, $run_id, $type, $threshold, 00472 $func, $source, $critical_path) { 00473 00474 $content = xhprof_get_content_by_run($xhprof_runs_impl, $run_id, $type, 00475 $threshold, 00476 $func, $source, $critical_path); 00477 if (!$content) { 00478 print "Error: either we can not find profile data for run_id ".$run_id 00479 ." or the threshold ".$threshold." is too small or you do not" 00480 ." have 'dot' image generation utility installed."; 00481 exit(); 00482 } 00483 00484 xhprof_generate_mime_header($type, strlen($content)); 00485 echo $content; 00486 }