Skip to content

Commit

Permalink
Optionally shade plan nodes by time taken
Browse files Browse the repository at this point in the history
Change-Id: If0a7759c2b6cd350453f225b5d49c5f72231b2c3
Reviewed-on: http://gerrit.cloudera.org:8080/73
Reviewed-by: Henry Robinson <henry@cloudera.com>
Tested-by: Internal Jenkins
  • Loading branch information
Henry Robinson authored and Internal Jenkins committed Feb 28, 2015
1 parent 7b837d5 commit ac8b010
Show file tree
Hide file tree
Showing 2 changed files with 41 additions and 10 deletions.
11 changes: 6 additions & 5 deletions be/src/service/impala-server-callbacks.cc
Expand Up @@ -448,7 +448,7 @@ void PlanToJsonHelper(const map<TPlanNodeId, TPlanNodeExecSummary>& summaries,
int64_t total_time = 0;
BOOST_FOREACH(const TExecStats& stat, summary->second.exec_stats) {
if (summary->second.is_broadcast) {
// Avoid multiple-cardinalitying for recipients of broadcasts.
// Avoid multiple-counting for recipients of broadcasts.
cardinality = ::max(cardinality, stat.cardinality);
} else {
cardinality += stat.cardinality;
Expand All @@ -464,17 +464,18 @@ void PlanToJsonHelper(const map<TPlanNodeId, TPlanNodeExecSummary>& summaries,
}

const string& max_time_str = PrettyPrinter::Print(max_time, TUnit::TIME_NS);
Value max_time_val(max_time_str.c_str(), document->GetAllocator());
value->AddMember("max_time", max_time_val, document->GetAllocator());
Value max_time_str_json(max_time_str.c_str(), document->GetAllocator());
value->AddMember("max_time", max_time_str_json, document->GetAllocator());
value->AddMember("max_time_val", max_time, document->GetAllocator());

// Round to the nearest ns, to workaround a bug in pretty-printing a fraction of a
// ns. See IMPALA-1800.
const string& avg_time_str = PrettyPrinter::Print(
// A bug may occasionally cause 1-instance nodes to appear to have 0 instances.
total_time / ::max(static_cast<int>(summary->second.exec_stats.size()), 1),
TUnit::TIME_NS);
Value avg_time_val(avg_time_str.c_str(), document->GetAllocator());
value->AddMember("avg_time", avg_time_val, document->GetAllocator());
Value avg_time_str_json(avg_time_str.c_str(), document->GetAllocator());
value->AddMember("avg_time", avg_time_str_json, document->GetAllocator());
}

int num_children = (*it)->num_children;
Expand Down
40 changes: 35 additions & 5 deletions www/query_plan.tmpl
Expand Up @@ -37,6 +37,13 @@ limitations under the License.

<h3>Plan</h3>

<div>
<label>
<input type="checkbox" checked="true" id="colour_scheme"/>
Shade nodes according to time spent (if unchecked, shade according to plan fragment)
</label>
</div>

<svg style="border: 1px solid darkgray" width=1200 height=600 class="panel"><g/></svg>

{{> www/common-footer.tmpl }}
Expand Down Expand Up @@ -69,15 +76,20 @@ svg.call(zoom);
var colours = ["#A9A9A9", "#FF8C00", "#8A2BE2", "#A52A2A", "#00008B", "#006400",
"#228B22", "#4B0082", "#DAA520", "#008B8B", "#000000", "#DC143C"]

// Shades of red in order of intensity, used for colouring nodes by time taken
var cols_by_time = ["#000000", "#1A0500", "#330A00", "#4C0F00", "#661400", "#801A00",
"#991F00", "#B22400", "#CC2900", "#E62E00", "#FF3300", "#FF4719"];

// Recursively build a list of edges and states that comprise the plan graph
function build(node, parent, edges, states, colour_idx) {
function build(node, parent, edges, states, colour_idx, max_node_time) {
states.push({ "name": node["label"],
"detail": node["label_detail"],
"num_instances": node["num_instances"],
"num_active": node["num_active"],
"max_time": node["max_time"],
"avg_time": node["avg_time"],
"is_broadcast": node["is_broadcast"],
"max_time_val": node["max_time_val"],
"style": "fill: " + colours[colour_idx]});
if (parent != null) {
var label_val = "" + node["output_card"];
Expand All @@ -92,12 +104,16 @@ function build(node, parent, edges, states, colour_idx) {
"style": { label: "" + node["output_card"],
style: "stroke: #f66; stroke-dasharray: 5, 5;"}});
}
max_node_time = Math.max(node["max_time_val"], max_node_time)
for (var i = 0; i < node["children"].length; ++i) {
build(node["children"][i], node["label"], edges, states, colour_idx);
max_node_time = build(
node["children"][i], node["label"], edges, states, colour_idx, max_node_time);
}
return max_node_time;
}

var is_first = true;

function renderGraph(ignored_arg) {
if (req.status != 200) return;
var json = JSON.parse(req.responseText);
Expand All @@ -107,8 +123,10 @@ function renderGraph(ignored_arg) {
var edges = []
var colour_idx = 0;

var max_node_time = 0;
plan["plan_nodes"].forEach(function(parent) {
build(parent, null, edges, states, colour_idx);
max_node_time = Math.max(
build(parent, null, edges, states, colour_idx, max_node_time));
// Pick a new colour for each plan fragment
colour_idx = (colour_idx + 1) % colours.length;
});
Expand All @@ -125,16 +143,28 @@ function renderGraph(ignored_arg) {
}
html += "</span><br/>";
html += "<span>Max: " + state.max_time + ", avg: " + state.avg_time + "</span>";

var style = state.style;

// If colouring nodes by total time taken, choose a shade in the cols_by_time list
// with idx proportional to the max time of the node divided by the max time over all
// nodes.
if (document.getElementById("colour_scheme").checked) {
var idx = (cols_by_time.length - 1) * (state.max_time_val / (1.0 * max_node_time));
style = "fill: " + cols_by_time[Math.floor(idx)];
}
g.setNode(state.name, { "label": html,
"labelType": "html",
"style": state.style });
"style": style });
states_by_name[state.name] = state;
});

edges.forEach(function(edge) {
// Impala marks 'broadcast' as a property of the receiver, not the sender. We use
// '(BCAST)' to denote that a node is duplicating its output to all receivers.
if (states_by_name[edge.end].is_broadcast) edge.style.label += " \n(BCAST)";
if (states_by_name[edge.end].is_broadcast) {
edge.style.label += " \n(BCAST * " + states_by_name[edge.end].num_instances + ")";
}
g.setEdge(edge.start, edge.end, edge.style);
});

Expand Down

0 comments on commit ac8b010

Please sign in to comment.