/** * Given a partial query string $q return matching function names in * specified uprofiler run. This is used for the type ahead function * selector. * * @author Kannan */ function uprofiler_get_matching_functions($q, $uprofiler_data) { $matches = array(); foreach ($uprofiler_data as $parent_child => $info) { list($parent, $child) = uprofiler_parse_parent_child($parent_child); if (stripos($parent, $q) !== false) { $matches[$parent] = 1; } if (stripos($child, $q) !== false) { $matches[$child] = 1; } } $res = array_keys($matches); // sort it so the answers are in some reliable order... asort($res); return $res; }
/** * Generates a report for a single function/symbol. * * @author Kannan */ function symbol_report($url_params, $run_data, $symbol_info, $sort, $rep_symbol, $run1, $symbol_info1 = null, $run2 = 0, $symbol_info2 = null) { global $vwbar; global $vbar; global $totals; global $pc_stats; global $sortable_columns; global $metrics; global $diff_mode; global $descriptions; global $format_cbk; global $sort_col; global $display_calls; global $base_path; $possible_metrics = uprofiler_get_possible_metrics(); if ($diff_mode) { $diff_text = "<b>Diff</b>"; $regr_impr = "<i style='color:red'>Regression</i>/<i style='color:green'>Improvement</i>"; } else { $diff_text = ""; $regr_impr = ""; } if ($diff_mode) { $base_url_params = uprofiler_array_unset(uprofiler_array_unset($url_params, 'run1'), 'run2'); $href1 = "{$base_path}?" . http_build_query(uprofiler_array_set($base_url_params, 'run', $run1)); $href2 = "{$base_path}?" . http_build_query(uprofiler_array_set($base_url_params, 'run', $run2)); print "<h3 align=center>{$regr_impr} summary for {$rep_symbol}<br><br></h3>"; print '<table border=1 cellpadding=2 cellspacing=1 width="30%" ' . 'rules=rows bordercolor="#bdc7d8" align=center>' . "\n"; print '<tr bgcolor="#bdc7d8" align=right>'; print "<th align=left>{$rep_symbol}</th>"; print "<th {$vwbar}><a href=" . $href1 . ">Run #{$run1}</a></th>"; print "<th {$vwbar}><a href=" . $href2 . ">Run #{$run2}</a></th>"; print "<th {$vwbar}>Diff</th>"; print "<th {$vwbar}>Diff%</th>"; print '</tr>'; print '<tr>'; if ($display_calls) { print "<td>Number of Function Calls</td>"; print_td_num($symbol_info1["ct"], $format_cbk["ct"]); print_td_num($symbol_info2["ct"], $format_cbk["ct"]); print_td_num($symbol_info2["ct"] - $symbol_info1["ct"], $format_cbk["ct"], true); print_td_pct($symbol_info2["ct"] - $symbol_info1["ct"], $symbol_info1["ct"], true); print '</tr>'; } foreach ($metrics as $metric) { $m = $metric; // Inclusive stat for metric print '<tr>'; print "<td>" . str_replace("<br>", " ", $descriptions[$m]) . "</td>"; print_td_num($symbol_info1[$m], $format_cbk[$m]); print_td_num($symbol_info2[$m], $format_cbk[$m]); print_td_num($symbol_info2[$m] - $symbol_info1[$m], $format_cbk[$m], true); print_td_pct($symbol_info2[$m] - $symbol_info1[$m], $symbol_info1[$m], true); print '</tr>'; // AVG (per call) Inclusive stat for metric print '<tr>'; print "<td>" . str_replace("<br>", " ", $descriptions[$m]) . " per call </td>"; $avg_info1 = 'N/A'; $avg_info2 = 'N/A'; if ($symbol_info1['ct'] > 0) { $avg_info1 = $symbol_info1[$m] / $symbol_info1['ct']; } if ($symbol_info2['ct'] > 0) { $avg_info2 = $symbol_info2[$m] / $symbol_info2['ct']; } print_td_num($avg_info1, $format_cbk[$m]); print_td_num($avg_info2, $format_cbk[$m]); print_td_num($avg_info2 - $avg_info1, $format_cbk[$m], true); print_td_pct($avg_info2 - $avg_info1, $avg_info1, true); print '</tr>'; // Exclusive stat for metric $m = "excl_" . $metric; print '<tr style="border-bottom: 1px solid black;">'; print "<td>" . str_replace("<br>", " ", $descriptions[$m]) . "</td>"; print_td_num($symbol_info1[$m], $format_cbk[$m]); print_td_num($symbol_info2[$m], $format_cbk[$m]); print_td_num($symbol_info2[$m] - $symbol_info1[$m], $format_cbk[$m], true); print_td_pct($symbol_info2[$m] - $symbol_info1[$m], $symbol_info1[$m], true); print '</tr>'; } print '</table>'; } print "<br><h4><center>"; print "Parent/Child {$regr_impr} report for <b>{$rep_symbol}</b>"; $callgraph_href = "{$base_path}/callgraph.php?" . http_build_query(uprofiler_array_set($url_params, 'func', $rep_symbol)); print " <a href='{$callgraph_href}'>[View Callgraph {$diff_text}]</a><br>"; print "</center></h4><br>"; print '<table border=1 cellpadding=2 cellspacing=1 width="90%" ' . 'rules=rows bordercolor="#bdc7d8" align=center>' . "\n"; print '<tr bgcolor="#bdc7d8" align=right>'; foreach ($pc_stats as $stat) { $desc = stat_description($stat); if (array_key_exists($stat, $sortable_columns)) { $href = "{$base_path}/?" . http_build_query(uprofiler_array_set($url_params, 'sort', $stat)); $header = uprofiler_render_link($desc, $href); } else { $header = $desc; } if ($stat == "fn") { print "<th align=left><nobr>{$header}</th>"; } else { print "<th " . $vwbar . "><nobr>{$header}</th>"; } } print "</tr>"; print "<tr bgcolor='#e0e0ff'><td>"; print "<b><i><center>Current Function</center></i></b>"; print "</td></tr>"; print "<tr>"; // make this a self-reference to facilitate copy-pasting snippets to e-mails print "<td><a href=''>{$rep_symbol}</a>"; print_source_link(array('fn' => $rep_symbol)); print "</td>"; if ($display_calls) { // Call Count print_td_num($symbol_info["ct"], $format_cbk["ct"]); print_td_pct($symbol_info["ct"], $totals["ct"]); } // Inclusive Metrics for current function foreach ($metrics as $metric) { print_td_num($symbol_info[$metric], $format_cbk[$metric], $sort_col == $metric); print_td_pct($symbol_info[$metric], $totals[$metric], $sort_col == $metric); } print "</tr>"; print "<tr bgcolor='#ffffff'>"; print "<td style='text-align:right;color:blue'>" . "Exclusive Metrics {$diff_text} for Current Function</td>"; if ($display_calls) { // Call Count print "<td {$vbar}></td>"; print "<td {$vbar}></td>"; } // Exclusive Metrics for current function foreach ($metrics as $metric) { print_td_num($symbol_info["excl_" . $metric], $format_cbk["excl_" . $metric], $sort_col == $metric, get_tooltip_attributes("Child", $metric)); print_td_pct($symbol_info["excl_" . $metric], $symbol_info[$metric], $sort_col == $metric, get_tooltip_attributes("Child", $metric)); } print "</tr>"; // list of callers/parent functions $results = array(); if ($display_calls) { $base_ct = $symbol_info["ct"]; } else { $base_ct = 0; } foreach ($metrics as $metric) { $base_info[$metric] = $symbol_info[$metric]; } foreach ($run_data as $parent_child => $info) { list($parent, $child) = uprofiler_parse_parent_child($parent_child); if ($child == $rep_symbol && $parent) { $info_tmp = $info; $info_tmp["fn"] = $parent; $results[] = $info_tmp; } } usort($results, 'sort_cbk'); if (count($results) > 0) { print_pc_array($url_params, $results, $base_ct, $base_info, true, $run1, $run2); } // list of callees/child functions $results = array(); $base_ct = 0; foreach ($run_data as $parent_child => $info) { list($parent, $child) = uprofiler_parse_parent_child($parent_child); if ($parent == $rep_symbol) { $info_tmp = $info; $info_tmp["fn"] = $child; $results[] = $info_tmp; if ($display_calls) { $base_ct += $info["ct"]; } } } usort($results, 'sort_cbk'); if (count($results)) { print_pc_array($url_params, $results, $base_ct, $base_info, false, $run1, $run2); } print "</table>"; // These will be used for pop-up tips/help. // Related javascript code is in: uprofiler_report.js print "\n"; print '<script language="javascript">' . "\n"; print "var func_name = '\"" . $rep_symbol . "\"';\n"; print "var total_child_ct = " . $base_ct . ";\n"; if ($display_calls) { print "var func_ct = " . $symbol_info["ct"] . ";\n"; } print "var func_metrics = new Array();\n"; print "var metrics_col = new Array();\n"; print "var metrics_desc = new Array();\n"; if ($diff_mode) { print "var diff_mode = true;\n"; } else { print "var diff_mode = false;\n"; } $column_index = 3; // First three columns are Func Name, Calls, Calls% foreach ($metrics as $metric) { print "func_metrics[\"" . $metric . "\"] = " . round($symbol_info[$metric]) . ";\n"; print "metrics_col[\"" . $metric . "\"] = " . $column_index . ";\n"; print "metrics_desc[\"" . $metric . "\"] = \"" . $possible_metrics[$metric][2] . "\";\n"; // each metric has two columns.. $column_index += 2; } print '</script>'; print "\n"; }
/** * Generate DOT script from the given raw phprof data. * * @param raw_data, phprof profile data. * @param threshold, float, the threshold value [0,1). The functions in the * raw_data whose exclusive wall times ratio are below the * threshold will be filtered out and won't apprear in the * generated image. * @param page, string(optional), the root node name. This can be used to * replace the 'main()' as the root node. * @param func, string, the focus function. * @param critical_path, bool, whether or not to display critical path with * bold lines. * @returns, string, the DOT script to generate image. * * @author cjiang */ function uprofiler_generate_dot_script($raw_data, $threshold, $source, $page, $func, $critical_path, $right = null, $left = null) { $max_width = 5; $max_height = 3.5; $max_fontsize = 35; $max_sizing_ratio = 20; $totals = array(); if ($left === null) { // init_metrics($raw_data, null, null); } $sym_table = uprofiler_compute_flat_info($raw_data, $totals); if ($critical_path) { $children_table = uprofiler_get_children_table($raw_data); $node = "main()"; $path = array(); $path_edges = array(); $visited = array(); while ($node) { $visited[$node] = true; if (isset($children_table[$node])) { $max_child = null; foreach ($children_table[$node] as $child) { if (isset($visited[$child])) { continue; } if ($max_child === null || abs($raw_data[uprofiler_build_parent_child_key($node, $child)]["wt"]) > abs($raw_data[uprofiler_build_parent_child_key($node, $max_child)]["wt"])) { $max_child = $child; } } if ($max_child !== null) { $path[$max_child] = true; $path_edges[uprofiler_build_parent_child_key($node, $max_child)] = true; } $node = $max_child; } else { $node = null; } } } // if it is a benchmark callgraph, we make the benchmarked function the root. if ($source == "bm" && array_key_exists("main()", $sym_table)) { $total_times = $sym_table["main()"]["ct"]; $remove_funcs = array("main()", "hotprofiler_disable", "call_user_func_array", "uprofiler_disable"); foreach ($remove_funcs as $cur_del_func) { if (array_key_exists($cur_del_func, $sym_table) && $sym_table[$cur_del_func]["ct"] == $total_times) { unset($sym_table[$cur_del_func]); } } } // use the function to filter out irrelevant functions. if (!empty($func)) { $interested_funcs = array(); foreach ($raw_data as $parent_child => $info) { list($parent, $child) = uprofiler_parse_parent_child($parent_child); if ($parent == $func || $child == $func) { $interested_funcs[$parent] = 1; $interested_funcs[$child] = 1; } } foreach ($sym_table as $symbol => $info) { if (!array_key_exists($symbol, $interested_funcs)) { unset($sym_table[$symbol]); } } } $result = "digraph call_graph {\n"; // Filter out functions whose exclusive time ratio is below threshold, and // also assign a unique integer id for each function to be generated. In the // meantime, find the function with the most exclusive time (potentially the // performance bottleneck). $cur_id = 0; $max_wt = 0; foreach ($sym_table as $symbol => $info) { if (empty($func) && abs($info["wt"] / $totals["wt"]) < $threshold) { unset($sym_table[$symbol]); continue; } if ($max_wt == 0 || $max_wt < abs($info["excl_wt"])) { $max_wt = abs($info["excl_wt"]); } $sym_table[$symbol]["id"] = $cur_id; $cur_id++; } // Generate all nodes' information. foreach ($sym_table as $symbol => $info) { if ($info["excl_wt"] == 0) { $sizing_factor = $max_sizing_ratio; } else { $sizing_factor = $max_wt / abs($info["excl_wt"]); if ($sizing_factor > $max_sizing_ratio) { $sizing_factor = $max_sizing_ratio; } } $fillcolor = $sizing_factor < 1.5 ? ", style=filled, fillcolor=red" : ""; if ($critical_path) { // highlight nodes along critical path. if (!$fillcolor && array_key_exists($symbol, $path)) { $fillcolor = ", style=filled, fillcolor=yellow"; } } $fontsize = ", fontsize=" . (int) ($max_fontsize / (($sizing_factor - 1) / 10 + 1)); $width = ", width=" . sprintf("%.1f", $max_width / $sizing_factor); $height = ", height=" . sprintf("%.1f", $max_height / $sizing_factor); if ($symbol == "main()") { $shape = "octagon"; $name = "Total: " . $totals["wt"] / 1000.0 . " ms\\n"; $name .= addslashes(isset($page) ? $page : $symbol); } else { $shape = "box"; $name = addslashes($symbol) . "\\nInc: " . sprintf("%.3f", $info["wt"] / 1000) . " ms (" . sprintf("%.1f%%", 100 * $info["wt"] / $totals["wt"]) . ")"; } if ($left === null) { $label = ", label=\"" . $name . "\\nExcl: " . sprintf("%.3f", $info["excl_wt"] / 1000.0) . " ms (" . sprintf("%.1f%%", 100 * $info["excl_wt"] / $totals["wt"]) . ")\\n" . $info["ct"] . " total calls\""; } else { if (isset($left[$symbol]) && isset($right[$symbol])) { $label = ", label=\"" . addslashes($symbol) . "\\nInc: " . sprintf("%.3f", $left[$symbol]["wt"] / 1000.0) . " ms - " . sprintf("%.3f", $right[$symbol]["wt"] / 1000.0) . " ms = " . sprintf("%.3f", $info["wt"] / 1000.0) . " ms" . "\\nExcl: " . sprintf("%.3f", $left[$symbol]["excl_wt"] / 1000.0) . " ms - " . sprintf("%.3f", $right[$symbol]["excl_wt"] / 1000.0) . " ms = " . sprintf("%.3f", $info["excl_wt"] / 1000.0) . " ms" . "\\nCalls: " . sprintf("%.3f", $left[$symbol]["ct"]) . " - " . sprintf("%.3f", $right[$symbol]["ct"]) . " = " . sprintf("%.3f", $info["ct"]) . "\""; } else { if (isset($left[$symbol])) { $label = ", label=\"" . addslashes($symbol) . "\\nInc: " . sprintf("%.3f", $left[$symbol]["wt"] / 1000.0) . " ms - 0 ms = " . sprintf("%.3f", $info["wt"] / 1000.0) . " ms" . "\\nExcl: " . sprintf("%.3f", $left[$symbol]["excl_wt"] / 1000.0) . " ms - 0 ms = " . sprintf("%.3f", $info["excl_wt"] / 1000.0) . " ms" . "\\nCalls: " . sprintf("%.3f", $left[$symbol]["ct"]) . " - 0 = " . sprintf("%.3f", $info["ct"]) . "\""; } else { $label = ", label=\"" . addslashes($symbol) . "\\nInc: 0 ms - " . sprintf("%.3f", $right[$symbol]["wt"] / 1000.0) . " ms = " . sprintf("%.3f", $info["wt"] / 1000.0) . " ms" . "\\nExcl: 0 ms - " . sprintf("%.3f", $right[$symbol]["excl_wt"] / 1000.0) . " ms = " . sprintf("%.3f", $info["excl_wt"] / 1000.0) . " ms" . "\\nCalls: 0 - " . sprintf("%.3f", $right[$symbol]["ct"]) . " = " . sprintf("%.3f", $info["ct"]) . "\""; } } } $result .= "N" . $sym_table[$symbol]["id"]; $result .= "[shape={$shape} " . $label . $width . $height . $fontsize . $fillcolor . "];\n"; } // Generate all the edges' information. foreach ($raw_data as $parent_child => $info) { list($parent, $child) = uprofiler_parse_parent_child($parent_child); if (isset($sym_table[$parent]) && isset($sym_table[$child]) && (empty($func) || !empty($func) && ($parent == $func || $child == $func))) { $label = $info["ct"] == 1 ? $info["ct"] . " call" : $info["ct"] . " calls"; $headlabel = $sym_table[$child]["wt"] > 0 ? sprintf("%.1f%%", 100 * $info["wt"] / $sym_table[$child]["wt"]) : "0.0%"; $taillabel = $sym_table[$parent]["wt"] > 0 ? sprintf("%.1f%%", 100 * $info["wt"] / ($sym_table[$parent]["wt"] - $sym_table["{$parent}"]["excl_wt"])) : "0.0%"; $linewidth = 1; $arrow_size = 1; if ($critical_path && isset($path_edges[uprofiler_build_parent_child_key($parent, $child)])) { $linewidth = 10; $arrow_size = 2; } $result .= "N" . $sym_table[$parent]["id"] . " -> N" . $sym_table[$child]["id"]; $result .= "[arrowsize={$arrow_size}, color=grey, style=\"setlinewidth({$linewidth})\"," . " label=\"" . $label . "\", headlabel=\"" . $headlabel . "\", taillabel=\"" . $taillabel . "\" ]"; $result .= ";\n"; } } $result = $result . "\n}"; return $result; }