This is an automated email from the ASF dual-hosted git repository.

joemcdonnell pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/impala.git

commit 6e4bd5ad3057a7baa0545c8417785d056ddcd575
Author: Surya Hebbar <[email protected]>
AuthorDate: Fri Apr 14 17:58:42 2023 +0530

    IMPALA-11970: Shifting the timeline display to SVG
    
    The usage of SVG allows for better scaling and
    further manipulation of elements through the DOM,
    facilitating text selection and parsing of such
    elements from the diagram.
    
    'text-anchor' and 'dominant-baseline' properties of
    the SVG text element have been used to preserve the
    same canvas coordinates, while shifting the center.
    
    The maximum width behaviour of canvas text has been
    implemented using 'textLength' and 'lengthAdjust'.
    
    Change-Id: I083c2ec12e1743b89092fc23281ee576d66fa81b
    Reviewed-on: http://gerrit.cloudera.org:8080/19745
    Reviewed-by: Impala Public Jenkins <[email protected]>
    Tested-by: Impala Public Jenkins <[email protected]>
---
 www/query_timeline.tmpl | 235 +++++++++++++++++++++++++++---------------------
 1 file changed, 133 insertions(+), 102 deletions(-)

diff --git a/www/query_timeline.tmpl b/www/query_timeline.tmpl
index bef1c5bbb..5a46dc41c 100644
--- a/www/query_timeline.tmpl
+++ b/www/query_timeline.tmpl
@@ -21,7 +21,7 @@ under the License.
 
 </div>
 
-<div class="container" style="width:1200px;margin:0 auto;">
+<div class="container">
 
 <style id="css">
 </style>
@@ -42,16 +42,17 @@ under the License.
 
 </div>
 
-<div style="height:20px; margin-bottom:5px; border:1px solid #c3c3c3;">
-<canvas id="header_canvas" style="height: 15px;"></canvas>
+<div id="timing_diagram" style="border:1px solid #c3c3c3; overflow:hidden;">
+  <div style="margin-top:5px; border:1px solid #c3c3c3;">
+    <svg id="phases_header" height="15px"></svg>
+  </div>
+  <div style="margin-top:5px; border:1px solid #c3c3c3; overflow-y:auto;">
+    <svg id="fragment_diagram"></svg>
+  </div>
+  <div style="margin-top:5px; border:1px solid #c3c3c3;">
+    <svg id="timeticks_footer" height="15px"></svg>
+  </div>
 </div>
-<div style="height:auto; overflow-y:auto; border:1px solid #c3c3c3;">
-<canvas id="timing_canvas"></canvas>
-</div>
-<div style="height:20px; margin-top:5px; border:1px solid #c3c3c3;">
-<canvas id="footer_canvas" style="height: 15px;"></canvas>
-</div>
-
 
 
 {{/plan_metadata_unavailable}}
@@ -82,41 +83,81 @@ var receiver_nodes = [];
 var profile_available = false;
 var maxts = 0;
 
+function get_svg_rect(fill_color, x, y, width, height, dash, stroke_color){
+  var rect = document.createElementNS("http://www.w3.org/2000/svg";, "rect");
+  rect.setAttribute("x", x + "px");
+  rect.setAttribute("y", y + "px");
+  rect.setAttribute("width", width + "px");
+  rect.setAttribute("height", height + "px");
+  rect.setAttribute("fill", fill_color);
+  if (dash) {
+    rect.setAttribute("stroke", stroke_color);
+    rect.setAttribute("stroke-dasharray", "2 2");
+  }
+  return rect;
+}
+
+function get_svg_text(text, fill_color, x, y, height, container_center, 
max_width = 0){
+  var text_el = document.createElementNS("http://www.w3.org/2000/svg";, "text");
+  text_el.appendChild(document.createTextNode(text));
+  text_el.setAttribute("x", x + "px");
+  text_el.setAttribute("y", y + "px");
+  text_el.style.fontSize = height / 1.5 + "px";
+  if (container_center) {
+    text_el.setAttribute("dominant-baseline", "middle");
+    text_el.setAttribute("text-anchor", "middle");
+  }
+  text_el.setAttribute("fill", fill_color);
+  if (max_width != 0) {
+    text_el.setAttribute("textLength", max_width);
+    text_el.setAttribute("lengthAdjust", "spacingAndGlyphs");
+  }
+  return text_el;
+}
+
+function get_svg_line(stroke_color, x1, y1, x2, y2, dash){
+  var line = document.createElementNS("http://www.w3.org/2000/svg";, "line");
+  line.setAttribute("x1", x1 + "px");
+  line.setAttribute("y1", y1 + "px");
+  line.setAttribute("x2", x2 + "px");
+  line.setAttribute("y2", y2 + "px");
+  line.setAttribute("stroke", stroke_color);
+  if (dash) {
+    line.setAttribute("stroke-dasharray", "2 2");
+  }
+  return line;
+}
 
-function DrawBars(ctx, rownum, height, events, xoffset, px_per_ns) {
+function DrawBars(svg, rownum, row_height, events, xoffset, px_per_ns) {
   var color_idx = 0;
   var last_end = xoffset;
-  bar_height = height - 2;
+  bar_height = row_height - 2;
 
   events.forEach(function(ev) {
     if (ev.no_bar == undefined) {
       var x = last_end;
-      var y = rownum * height;
+      var y = rownum * row_height;
 
       var endts = Math.max.apply(null, ev.tslist);
       var width = xoffset + endts * px_per_ns - last_end;
       last_end = x + width;
 
       // Block phase outline
-      ctx.fillStyle = "#000000";
-      ctx.fillRect(x, y, width, bar_height);
-      ctx.fillStyle = phases[color_idx++].color;
-
-      // Colored phase box if duration long enough
+      svg.appendChild(get_svg_rect("#000000", x, y, width, bar_height, false));
       if (width > 2) {
-        ctx.fillRect(x + 1, y + 1, width - 2, bar_height - 2);
+        svg.appendChild(get_svg_rect(phases[color_idx].color, x + 1, y + 1,
+            width - 2, bar_height - 2, false));
       }
 
+      color_idx++;
+
       // Grey dividers for other instances that finished earlier
-      ctx.beginPath()
       ev.tslist.forEach(function(ts) {
         var dx = (endts - ts) * px_per_ns;
-        ctx.strokeStyle = "#505050";
         var ignore_px = 2; // Don't print tiny skews
         if (Math.abs(dx) > ignore_px) {
-          ctx.moveTo(last_end - dx, y);
-          ctx.lineTo(last_end - dx, y + bar_height);
-          ctx.stroke();
+          svg.appendChild(get_svg_line("#505050", last_end - dx, y , last_end 
- dx,
+              y + bar_height, false));
         }
       });
     }
@@ -170,7 +211,8 @@ function collectFromProfile(ignored_arg) {
             var node_type = name_flds[0].trim();
             var node_id = name_flds.length > 1 ? name_flds[1].split(/[=]/)[1] 
: 0;
             node_name = pp.profile_name.replace("_NODE", "").replace("_", " ")
-                .replace("KrpcDataStreamSender", "SENDER").replace("Hash Join 
Builder", "JOIN BUILD")
+                .replace("KrpcDataStreamSender", "SENDER")
+                .replace("Hash Join Builder", "JOIN BUILD")
                 .replace("join node_", "");
             if (node_type.indexOf("SCAN_NODE") >= 0) {
               var table_name = pp.info_strings.find(({ key }) => key === 
"Table Name")
@@ -286,52 +328,45 @@ function collectFromProfile(ignored_arg) {
 }
 
 function renderTiming(ignored_arg) {
-  var plan_order = document.getElementById("plan_order").checked;
-
-  var header_canvas = document.getElementById('header_canvas');
-  var timing_canvas = document.getElementById('timing_canvas');
-  var footer_canvas = document.getElementById('footer_canvas');
+  var plan_order = document.getElementById('plan_order').checked;
 
-  timing_canvas.width = window.innerWidth - 50;
-  header_canvas.width = footer_canvas.width = timing_canvas.clientWidth;
+  var timing_diagram = document.getElementById('timing_diagram');
+  var phases_header = document.getElementById('phases_header');
+  var fragment_diagram = document.getElementById('fragment_diagram');
+  var timeticks_footer = document.getElementById('timeticks_footer');
 
-  header_canvas.height = 15; // Height gets resized later
-  timing_canvas.height = 200; // Height gets resized later
-  footer_canvas.height = 15; // Height gets resized later
+  phases_header.innerHTML = "";
+  fragment_diagram.innerHTML = "";
+  timeticks_footer.innerHTML = "";
 
-  var header_ctx = header_canvas.getContext('2d');
-  var timing_ctx = timing_canvas.getContext('2d');
-  var footer_ctx = footer_canvas.getContext('2d');
+  var row_height = 15;
+  var display_height = window.innerHeight - timing_diagram.offsetTop - 70;
+  fragment_diagram.parentNode.style.height = display_height * 1 + "px";
+  fragment_diagram.setAttribute("height", rownum * row_height + "px");
 
+  fragment_diagram.setAttribute("width", timing_diagram.clientWidth - 50);
+  phases_header.setAttribute("width", fragment_diagram.clientWidth);
+  timeticks_footer.setAttribute("width", fragment_diagram.clientWidth);
 
   var frag_name_width = (Math.max(2, (fragments.length - 1).toString().length) 
+ 3) * char_width;
   var name_width = max_namelen * char_width + (frag_name_width + 2);
-  var chart_width = timing_canvas.width - name_width;
-  var height = 15;
-
-  timing_canvas.height = rownum * height;
-  var screen_height = Math.min(timing_canvas.height + 10,
-      window.innerHeight - timing_canvas.offsetTop - 30);
-  timing_canvas.parentNode.setAttribute("style",
-      "height:" + screen_height + "px; overflow-y:auto; border:1px solid 
#c3c3c3;");
+  var chart_width = fragment_diagram.clientWidth - name_width;
 
   var px_per_ns = chart_width / maxts;
 
-  var text_y = height - 4;
+  var text_y = row_height - 4;
   var color_idx = 0;
   var width = Math.ceil(chart_width / phases.length);
   phases.forEach(function(p) {
     var x = name_width + Math.ceil(chart_width * color_idx / phases.length);
-    x = Math.min(x, header_canvas.width - width);
-
-    header_ctx.fillStyle = "#000000";
-    header_ctx.fillRect(x, 0, width, height);
-    header_ctx.fillStyle = phases[color_idx++].color;
-    header_ctx.fillRect(x + 1, 1, width - 2, height - 2);
-    header_ctx.fillStyle = "#000000";
-    var text_width = p.label.length * char_width;
-    header_ctx.fillText(p.label, x + width / 3, text_y, Math.min(text_width, 
width / 2));
-  });
+    x = Math.min(x, phases_header.clientWidth - width);
+
+    phases_header.appendChild(get_svg_rect("#000000", x, 0, width, row_height, 
false));
+    phases_header.appendChild(get_svg_rect(phases[color_idx++].color, x + 1, 1,
+        width - 2, row_height - 2, false));
+    phases_header.appendChild(get_svg_text(p.label, "#000000", x + width / 2,
+        text_y - 2, row_height, true, Math.min(p.label.length * char_width, 
width / 1.5)));
+   });
 
   var rownum_l = 0;
   var max_indent = 0;
@@ -346,31 +381,28 @@ function renderTiming(ignored_arg) {
 
       frag_name = fragment.name.replace("Coordinator ", "").replace("Fragment 
", "");
 
-      timing_ctx.fillStyle = fragment.color;
-      timing_ctx.fillText(frag_name, 1, text_y, frag_name_width);
+      fragment_diagram.appendChild(get_svg_text(frag_name, fragment.color, 1, 
text_y,
+          row_height, false));
 
       // Fragment/sender timing row
-      DrawBars(timing_ctx, rownum_l, height, fevents, name_width, px_per_ns);
+      DrawBars(fragment_diagram, rownum_l, row_height, fevents, name_width, 
px_per_ns);
 
       for (var i = 0; i < fragment.nodes.length; ++i) {
         var node = fragment.nodes[i];
 
         if (node.events != undefined) {
           // Plan node timing row
-          DrawBars(timing_ctx, rownum_l, height, node.events, name_width, 
px_per_ns);
+          DrawBars(fragment_diagram, rownum_l, row_height, node.events, 
name_width, px_per_ns);
           if (node.type == "HASH_JOIN_NODE") {
-            timing_ctx.fillStyle = "#000000";
-            timing_ctx.fillText("X",
+            fragment_diagram.appendChild(get_svg_text("X", "#000000",
                 name_width + Math.min.apply(null, node.events[2].tslist) * 
px_per_ns,
-                text_y, char_width);
-            timing_ctx.fillText("O",
-                name_width + Math.min.apply(null,node.events[2].tslist) * 
px_per_ns,
-                text_y, char_width);
+                text_y, row_height, false));
+            fragment_diagram.appendChild(get_svg_text("O", "#000000",
+                name_width + Math.min.apply(null, node.events[2].tslist) * 
px_per_ns,
+                text_y, row_height, false));
           }
         }
 
-        timing_ctx.fillStyle = fragment.color;
-
         if (node.is_receiver) {
           pending_senders++;
         } else if (node.is_sender) {
@@ -381,49 +413,49 @@ function renderTiming(ignored_arg) {
         }
 
         var label_x = frag_name_width + char_width * pending_children;
-        var label_width = Math.min(char_width * node.name.length, name_width - 
label_x - 2);
-        timing_ctx.fillText(node.name, label_x, text_y, label_width);
-        timing_ctx.strokeStyle = fragment.color;
+        var label_width = Math.min(char_width * node.name.length,
+            name_width - label_x - 2);
+        fragment_diagram.appendChild(get_svg_text(node.name, fragment.color,
+            label_x, text_y, row_height, false));
 
         if (node.parent_node != undefined) {
-          var y = height * node.parent_node.rendering.rownum;
+          var y = row_height * node.parent_node.rendering.rownum;
           if (node.is_sender) {
             var x = name_width + Math.min.apply(null, fevents[3].tslist) * 
px_per_ns;
             // Dotted horizontal connector to received rows
-            timing_ctx.beginPath();
-            timing_ctx.setLineDash([2, 2]);
-            timing_ctx.moveTo(name_width, y + height / 2 - 1);
-            timing_ctx.lineTo(x, y + height / 2 - 1);
-            timing_ctx.stroke();
+            fragment_diagram.appendChild(get_svg_line(fragment.color, 
name_width,
+                y + row_height / 2 - 1, x, y + row_height / 2 - 1, true));
 
             // Dotted rectangle for received rows
-            timing_ctx.beginPath();
-            var x2 = name_width + Math.max.apply(null, fevents[4].tslist) * 
px_per_ns;
-            timing_ctx.strokeRect(x, y + 4, x2 - x, height - 10);
-            timing_ctx.setLineDash([]);
+            var x2 = name_width + Math.max.apply(null,fevents[4].tslist) * 
px_per_ns;
+            fragment_diagram.appendChild(get_svg_rect("rgba(0,0,0,0)", x, y + 
4, x2 - x,
+                row_height - 10, true, fragment.color));
           }
 
-          timing_ctx.beginPath();
-          if (node.is_sender && node.parent_node.rendering.rownum != rownum - 
1) {
+          if (node.is_sender && node.parent_node.rendering.rownum != rownum_l 
- 1) {
             // DAG edge on right side to distant sender
             var x = name_width - (pending_senders) * char_width - char_width / 
2;
-            timing_ctx.moveTo(node.parent_node.rendering.label_end, y + height 
/ 2);
-            timing_ctx.lineTo(x, y + height / 2);
-            timing_ctx.lineTo(x, text_y - height / 2 + 3);
-            timing_ctx.lineTo(label_x + label_width, text_y - height / 2 + 3);
+            fragment_diagram.appendChild(get_svg_line(fragment.color,
+                node.parent_node.rendering.label_end,
+                y + row_height / 2, x , y + row_height / 2, false));
+            fragment_diagram.appendChild(get_svg_line(fragment.color, x, y + 
row_height / 2,
+                x, text_y - row_height / 2 + 3, false));
+            fragment_diagram.appendChild(get_svg_line(fragment.color, x,
+                text_y - row_height / 2 + 3, label_x + label_width,
+                text_y - row_height / 2 + 3, false));
+
           } else {
             // DAG edge from parent to immediate child
             var x = frag_name_width + (pending_children + 1) * char_width - 
char_width / 2;
-            timing_ctx.moveTo(x, y + height - 3);
-            timing_ctx.lineTo(x, text_y - height + 6);
+            fragment_diagram.appendChild(get_svg_line(fragment.color, x, y + 
row_height - 3,
+                x, text_y - row_height + 6, false));
           }
-          timing_ctx.stroke();
 
         }
         node.rendering = { rownum: rownum_l, label_end: label_x + label_width 
};
         if (node.num_children) // Scan (leaf) node
           pending_children += (node.num_children - node.is_receiver);
-        text_y += height;
+        text_y += row_height;
         rownum_l++;
 
         if (node.is_receiver) {
@@ -445,21 +477,22 @@ function renderTiming(ignored_arg) {
     fragment.printed = false;
   });
   rownum_l = 0;
-  var text_y = (rownum_l + 1) * height - 4;
+  var text_y = (rownum_l + 1) * row_height - 4;
 
   // Time scale below timing diagram
   var ntics = 10;
   var sec_per_tic = maxts / ntics / 1000000000;
   var px_per_tic = chart_width / ntics;
   var x = name_width;
+  var decimals = 2;
   for (var i = 1; i <= ntics; ++i) {
-    footer_ctx.fillStyle = "#000000";
-    var y = rownum_l * height;
-    footer_ctx.fillRect(x, y, px_per_tic, height);
-    footer_ctx.fillStyle = "#F0F0F0";
-    footer_ctx.fillRect(x + 1, y + 1, px_per_tic - 2, height - 2);
-    footer_ctx.fillStyle = "#000000";
-    footer_ctx.fillText((i * sec_per_tic).toFixed(2), x + px_per_tic - 25, 
text_y, chart_width / ntics);
+    var y = rownum_l * row_height;
+    timeticks_footer.appendChild(get_svg_rect("#000000", x, y, px_per_tic,
+        row_height, false));
+    timeticks_footer.appendChild(get_svg_rect("#F0F0F0", x + 1, y + 1, 
px_per_tic - 2,
+        row_height - 2, false));
+    timeticks_footer.appendChild(get_svg_text((i * 
sec_per_tic).toFixed(decimals),
+        "#000000", x + px_per_tic - decimals * char_width, text_y - 2, 
row_height, true));
     x += px_per_tic;
   }
 }
@@ -485,8 +518,6 @@ function refresh() {
   req.send();
 }
 
-
-
 window.addEventListener('resize', function(event) {
   if (profile_available) {
     renderTiming();

Reply via email to