Back to home page

OSCL-LXR

 
 

    


0001 /*
0002  * Licensed to the Apache Software Foundation (ASF) under one or more
0003  * contributor license agreements.  See the NOTICE file distributed with
0004  * this work for additional information regarding copyright ownership.
0005  * The ASF licenses this file to You under the Apache License, Version 2.0
0006  * (the "License"); you may not use this file except in compliance with
0007  * the License.  You may obtain a copy of the License at
0008  *
0009  *    http://www.apache.org/licenses/LICENSE-2.0
0010  *
0011  * Unless required by applicable law or agreed to in writing, software
0012  * distributed under the License is distributed on an "AS IS" BASIS,
0013  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
0014  * See the License for the specific language governing permissions and
0015  * limitations under the License.
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   // Round corners on rectangles
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  * | Helper functions | *
0055  * -------------------- */
0056 
0057 function planVizContainer() { return d3.select("#plan-viz-graph"); }
0058 
0059 /*
0060  * Set up the tooltip for a SparkPlan node using metadata. When the user moves the mouse on the
0061  * node, it will display the details of this SparkPlan node in the right.
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 // labelSeparator should be a non-graphical character in order not to affect the width of boxes.
0080 var labelSeparator = "\x01";
0081 var stageAndTaskMetricsPattern = "^(.*)(\\(stage.*task[^)]*\\))(.*)$";
0082 
0083 /*
0084  * Helper function to pre-process the graph layout.
0085  * This step is necessary for certain styles that affect the positioning
0086  * and sizes of graph elements, e.g. padding, font style, shape.
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   // Curve the edges
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  * Helper function to size the SVG appropriately such that all elements are displayed.
0126  * This assumes that all outermost elements are clusters (rectangles).
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 /* Helper function to convert attributes to numeric values. */
0156 function toFloat(f) {
0157   if (f) {
0158     return parseFloat(f.toString().replace(/px$/, ""));
0159   } else {
0160     return f;
0161   }
0162 }
0163 
0164 /*
0165  * Helper function to compute the absolute position of the specified element in our graph.
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     // Climb upwards to find how our parents are translated
0182     obj = d3.select(obj.node().parentNode);
0183     // Stop when we've reached the graph container itself
0184     if (obj.node() === planVizContainer().node()) {
0185       break;
0186     }
0187   }
0188   return { x: _x, y: _y };
0189 }
0190 
0191 /*
0192  * Helper function for postprocess for additional metrics.
0193  */
0194 function postprocessForAdditionalMetrics() {
0195   // With dagre-d3, we can choose normal text (default) or HTML as a label type.
0196   // HTML label for node works well but not for cluster so we need to choose the default label type
0197   // and manipulate DOM.
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  * Helper function which defines the action on click the checkbox.
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 }