0001
0002
0003
0004
0005
0006
0007
0008
0009
0010
0011
0012
0013
0014
0015
0016
0017
0018 var PlanVizConstants = {
0019 svgMarginX: 16,
0020 svgMarginY: 16
0021 };
0022
0023 function shouldRenderPlanViz() {
0024 return planVizContainer().selectAll("svg").empty();
0025 }
0026
0027 function renderPlanViz() {
0028 var svg = planVizContainer().append("svg");
0029 var metadata = d3.select("#plan-viz-metadata");
0030 var dot = metadata.select(".dot-file").text().trim();
0031 var graph = svg.append("g");
0032
0033 var g = graphlibDot.read(dot);
0034 preprocessGraphLayout(g);
0035 var renderer = new dagreD3.render();
0036 renderer(graph, g);
0037
0038
0039 svg
0040 .selectAll("rect")
0041 .attr("rx", "5")
0042 .attr("ry", "5");
0043
0044 var nodeSize = parseInt($("#plan-viz-metadata-size").text());
0045 for (var i = 0; i < nodeSize; i++) {
0046 setupTooltipForSparkPlanNode(i);
0047 }
0048
0049 resizeSvg(svg);
0050 postprocessForAdditionalMetrics();
0051 }
0052
0053
0054
0055
0056
0057 function planVizContainer() { return d3.select("#plan-viz-graph"); }
0058
0059
0060
0061
0062
0063 function setupTooltipForSparkPlanNode(nodeId) {
0064 var nodeTooltip = d3.select("#plan-meta-data-" + nodeId).text();
0065 d3.select("svg g .node_" + nodeId)
0066 .on('mouseover', function(d) {
0067 var domNode = d3.select(this).node();
0068 $(domNode).tooltip({
0069 title: nodeTooltip, trigger: "manual", container: "body", placement: "top"
0070 });
0071 $(domNode).tooltip("show");
0072 })
0073 .on('mouseout', function(d) {
0074 var domNode = d3.select(this).node();
0075 $(domNode).tooltip("destroy");
0076 })
0077 }
0078
0079
0080 var labelSeparator = "\x01";
0081 var stageAndTaskMetricsPattern = "^(.*)(\\(stage.*task[^)]*\\))(.*)$";
0082
0083
0084
0085
0086
0087
0088 function preprocessGraphLayout(g) {
0089 g.graph().ranksep = "70";
0090 var nodes = g.nodes();
0091 for (var i = 0; i < nodes.length; i++) {
0092 var node = g.node(nodes[i]);
0093 node.padding = "5";
0094
0095 var firstSearator;
0096 var secondSeparator;
0097 var splitter;
0098 if (node.isCluster) {
0099 firstSearator = secondSeparator = labelSeparator;
0100 splitter = "\\n";
0101 } else {
0102 firstSearator = "<span class='stageId-and-taskId-metrics'>";
0103 secondSeparator = "</span>";
0104 splitter = "<br>";
0105 }
0106
0107 node.label.split(splitter).forEach(function(text, i) {
0108 var newTexts = text.match(stageAndTaskMetricsPattern);
0109 if (newTexts) {
0110 node.label = node.label.replace(
0111 newTexts[0],
0112 newTexts[1] + firstSearator + newTexts[2] + secondSeparator + newTexts[3]);
0113 }
0114 });
0115 }
0116
0117 var edges = g.edges();
0118 for (var j = 0; j < edges.length; j++) {
0119 var edge = g.edge(edges[j]);
0120 edge.lineInterpolate = "basis";
0121 }
0122 }
0123
0124
0125
0126
0127
0128 function resizeSvg(svg) {
0129 var allClusters = svg.selectAll("g rect")[0];
0130 var startX = -PlanVizConstants.svgMarginX +
0131 toFloat(d3.min(allClusters, function(e) {
0132 return getAbsolutePosition(d3.select(e)).x;
0133 }));
0134 var startY = -PlanVizConstants.svgMarginY +
0135 toFloat(d3.min(allClusters, function(e) {
0136 return getAbsolutePosition(d3.select(e)).y;
0137 }));
0138 var endX = PlanVizConstants.svgMarginX +
0139 toFloat(d3.max(allClusters, function(e) {
0140 var t = d3.select(e);
0141 return getAbsolutePosition(t).x + toFloat(t.attr("width"));
0142 }));
0143 var endY = PlanVizConstants.svgMarginY +
0144 toFloat(d3.max(allClusters, function(e) {
0145 var t = d3.select(e);
0146 return getAbsolutePosition(t).y + toFloat(t.attr("height"));
0147 }));
0148 var width = endX - startX;
0149 var height = endY - startY;
0150 svg.attr("viewBox", startX + " " + startY + " " + width + " " + height)
0151 .attr("width", width)
0152 .attr("height", height);
0153 }
0154
0155
0156 function toFloat(f) {
0157 if (f) {
0158 return parseFloat(f.toString().replace(/px$/, ""));
0159 } else {
0160 return f;
0161 }
0162 }
0163
0164
0165
0166
0167 function getAbsolutePosition(d3selection) {
0168 if (d3selection.empty()) {
0169 throw "Attempted to get absolute position of an empty selection.";
0170 }
0171 var obj = d3selection;
0172 var _x = toFloat(obj.attr("x")) || 0;
0173 var _y = toFloat(obj.attr("y")) || 0;
0174 while (!obj.empty()) {
0175 var transformText = obj.attr("transform");
0176 if (transformText) {
0177 var translate = d3.transform(transformText).translate;
0178 _x += toFloat(translate[0]);
0179 _y += toFloat(translate[1]);
0180 }
0181
0182 obj = d3.select(obj.node().parentNode);
0183
0184 if (obj.node() === planVizContainer().node()) {
0185 break;
0186 }
0187 }
0188 return { x: _x, y: _y };
0189 }
0190
0191
0192
0193
0194 function postprocessForAdditionalMetrics() {
0195
0196
0197
0198 $("g.cluster text tspan")
0199 .each(function() {
0200 var originalText = $(this).text();
0201 if (originalText.indexOf(labelSeparator) > 0) {
0202 var newTexts = originalText.split(labelSeparator);
0203 var thisD3Node = d3.selectAll($(this));
0204 thisD3Node.text(newTexts[0]);
0205 thisD3Node.append("tspan").attr("class", "stageId-and-taskId-metrics").text(newTexts[1]);
0206 $(this).append(newTexts[2]);
0207 } else {
0208 return originalText;
0209 }
0210 });
0211
0212 var checkboxNode = $("#stageId-and-taskId-checkbox");
0213 checkboxNode.click(function() {
0214 onClickAdditionalMetricsCheckbox($(this));
0215 });
0216 var isChecked = window.localStorage.getItem("stageId-and-taskId-checked") === "true";
0217 checkboxNode.prop("checked", isChecked);
0218 onClickAdditionalMetricsCheckbox(checkboxNode);
0219 }
0220
0221
0222
0223
0224 function onClickAdditionalMetricsCheckbox(checkboxNode) {
0225 var additionalMetrics = $(".stageId-and-taskId-metrics");
0226 var isChecked = checkboxNode.prop("checked");
0227 if (isChecked) {
0228 additionalMetrics.show();
0229 } else {
0230 additionalMetrics.hide();
0231 }
0232 window.localStorage.setItem("stageId-and-taskId-checked", isChecked);
0233 }