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

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

commit 970c6009600dd7b47fac52d391923aded0403bfc
Author: Surya Hebbar <[email protected]>
AuthorDate: Tue Apr 11 00:23:28 2023 +0530

    IMPALA-11970: Optimized rendering for the query timeline display
    
    Decoupled the retrieval of attributes and rendering
    to optimize window resize events for large JSON
    query profiles in the timeline display.
    
    A reload will be triggered after every second,
    in case the query execution has not completed,
    or due to errors in parsing the profile;
    only valid profiles are rendered.
    
    Replaces 'maximum()' and 'minimum()' functions with
    'Math.max.apply()' and 'Math.min.apply()'.
    
    Change-Id: Iec458602266863b6e313f52b26a2dc35cff0db0f
    Reviewed-on: http://gerrit.cloudera.org:8080/19744
    Reviewed-by: Impala Public Jenkins <[email protected]>
    Tested-by: Impala Public Jenkins <[email protected]>
---
 www/query_timeline.tmpl | 414 ++++++++++++++++++++++++------------------------
 1 file changed, 210 insertions(+), 204 deletions(-)

diff --git a/www/query_timeline.tmpl b/www/query_timeline.tmpl
index f32a1bda0..18e84e5c7 100644
--- a/www/query_timeline.tmpl
+++ b/www/query_timeline.tmpl
@@ -62,6 +62,7 @@ under the License.
 
 $("#plan-timing-tab").addClass("active");
 
+var profile = {};
 var phases = [
   { color: "#C0C0FF", label: "Prepare" }
 , { color: "#E0E0E0", label: "Open" }
@@ -69,29 +70,18 @@ var phases = [
 , { color: "#C0FFFF", label: "Send First Batch" }
 , { color: "#C0FFC0", label: "Process Remaining Batches" }
 , { color: "#FFC0C0", label: "Close" }
-]
-
+];
 var fragment_colors = ["#A9A9A9", "#FF8C00", "#8A2BE2", "#A52A2A", "#00008B", 
"#006400",
-                       "#228B22", "#4B0082", "#DAA520", "#008B8B", "#000000", 
"#DC143C"]
-
-var query_finished = false;
-var intervalId = 0;
-
-function minimum(numlist) {
-  var minval = numlist[0];
-  for (var i = 1; i < numlist.length; ++i) {
-    minval=Math.min(minval, numlist[i]);
-  }
-  return minval;
-}
+                       "#228B22", "#4B0082", "#DAA520", "#008B8B", "#000000", 
"#DC143C"];
+var fragments = [];
+var rownum = 0;
+var char_width = 6;
+var max_namelen = 0;
+var all_nodes = [];
+var receiver_nodes = [];
+var profile_available = false;
+var maxts = 0;
 
-function maximum(numlist) {
-  var maxval = numlist[0];
-  for (var i = 1; i < numlist.length; ++i) {
-    maxval=Math.max(maxval, numlist[i]);
-  }
-  return maxval;
-}
 
 function DrawBars(ctx, rownum, height, events, xoffset, px_per_ns) {
   var color_idx = 0;
@@ -103,7 +93,7 @@ function DrawBars(ctx, rownum, height, events, xoffset, 
px_per_ns) {
       var x = last_end;
       var y = rownum * height;
 
-      var endts = maximum(ev.tslist);
+      var endts = Math.max.apply(null, ev.tslist);
       var width = xoffset + endts * px_per_ns - last_end;
       last_end = x + width;
 
@@ -133,186 +123,186 @@ function DrawBars(ctx, rownum, height, events, xoffset, 
px_per_ns) {
   });
 }
 
-function renderTiming(ignored_arg) {
-  if (req.status != 200) {
-    return;
-  }
-  var json = JSON.parse(req.responseText);
-  var profile = json["profile_json"];
-  var qs = profile.child_profiles[0].info_strings.find(({ key }) => key === 
"Query State").value;
-  if (qs == "FINISHED" ) {
-    query_finished = true;
-  }
-
-
-  var header_canvas = document.getElementById('header_canvas');
-  var timing_canvas = document.getElementById('timing_canvas');
-  var footer_canvas = document.getElementById('footer_canvas');
-
-  timing_canvas.width = window.innerWidth - 50;
-  header_canvas.width = footer_canvas.width = timing_canvas.clientWidth;
-
-  header_canvas.height = 15; // Height gets resized later
-  timing_canvas.height = 200; // Height gets resized later
-  footer_canvas.height = 15; // Height gets resized later
-
-  var header_ctx = header_canvas.getContext('2d');
-  var timing_ctx = timing_canvas.getContext('2d');
-  var footer_ctx = footer_canvas.getContext('2d');
-
-  var plan_order = document.getElementById("plan_order").checked;
-
-  var rownum = 0;
-  var maxts = 0;
-  var max_namelen = 0;
-  var char_width = 6;
+function collectFromProfile(ignored_arg) {
+  rownum = 0;
+  maxts = 0;
+  max_namelen = 0;
+  fragments = [];
   var color_idx = 0;
-  var fragments = [];
-
-  var all_nodes = [];
-  var receiver_nodes = [];
-  // First pass: compute sizes
-  profile.child_profiles[2].child_profiles.forEach(function(fp) {
-
-    var cp = fp.child_profiles[0];
-
-    if (cp.event_sequences != undefined) {
-      var fevents = fp.child_profiles[0].event_sequences[0].events;
-
-      // Build list of timestamps that spans instances for each event
-      for (var en = 0; en < fevents.length; ++en) {
-        fevents[en].tslist = [ fevents[en].timestamp ];
-        for (var instance = 1; instance < fp.child_profiles.length; 
++instance) {
-          if (fp.child_profiles[instance].event_sequences != undefined) {
-            
fevents[en].tslist.push(fp.child_profiles[instance].event_sequences[0].events[en].timestamp);
+  all_nodes = [];
+  receiver_nodes = [];
+  try {
+    // First pass: compute sizes
+    profile.child_profiles[2].child_profiles.forEach(function(fp) {
+
+      var cp = fp.child_profiles[0];
+
+      if (cp.event_sequences != undefined) {
+        var fevents = fp.child_profiles[0].event_sequences[0].events;
+
+        // Build list of timestamps that spans instances for each event
+        for (var en = 0; en < fevents.length; ++en) {
+          fevents[en].tslist = [ fevents[en].timestamp ];
+          for (var instance = 1; instance < fp.child_profiles.length; 
++instance) {
+            if (fp.child_profiles[instance].event_sequences != undefined) {
+              fevents[en].tslist.push(
+                  
fp.child_profiles[instance].event_sequences[0].events[en].timestamp);
+            }
           }
         }
-      }
 
-      fragment = {
-        name: fp.profile_name,
-        events: fevents,
-        nodes: [ ],
-        color: fragment_colors[color_idx]
-      }
-
-      // Pick a new color for each plan fragment
-      color_idx = (color_idx + 1) % fragment_colors.length;
-      maxts = Math.max(maxts, fevents[fevents.length - 1].timestamp);
-      max_namelen = Math.max(max_namelen, fp.profile_name.length);
-      var node_path = [];
-      var node_stack = [];
-      cp.child_profiles.forEach(function get_plan_nodes(pp, index) {
-        if (pp.node_metadata != undefined) {
-          node_path.push(index);
-          var name_flds = pp.profile_name.split(/[()]/);
-          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("join node_","")
-          if (node_type.indexOf("SCAN_NODE") >= 0) {
-            var table_name = pp.info_strings.find(({ key }) => key === "Table 
Name").value.split(/[.]/);
-            node_name = node_name.replace("SCAN", "SCAN [" + 
table_name[table_name.length - 1] + "]");
-          }
+        fragment = {
+          name: fp.profile_name,
+          events: fevents,
+          nodes: [ ],
+          color: fragment_colors[color_idx]
+        }
+        // Pick a new color for each plan fragment
+        color_idx = (color_idx + 1) % fragment_colors.length;
+        maxts = Math.max(maxts, fevents[fevents.length - 1].timestamp);
+        max_namelen = Math.max(max_namelen, fp.profile_name.length);
+        var node_path = [];
+        var node_stack = [];
+        cp.child_profiles.forEach(function get_plan_nodes(pp, index) {
+          if (pp.node_metadata != undefined) {
+            node_path.push(index);
+            var name_flds = pp.profile_name.split(/[()]/);
+            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("join node_", "");
+            if (node_type.indexOf("SCAN_NODE") >= 0) {
+              var table_name = pp.info_strings.find(({ key }) => key === 
"Table Name")
+                  .value.split(/[.]/);
+              node_name = node_name.replace("SCAN",
+                  "SCAN [" + table_name[table_name.length - 1] + "]");
+            }
 
-          var is_receiver = node_type == "EXCHANGE_NODE" ||
-              (node_type == "HASH_JOIN_NODE" && pp.num_children < 3);
-
-          var is_sender = (node_type == "Hash Join Builder" ||
-                           node_type == "KrpcDataStreamSender");
-          var parent_node;
-          if (node_type == "PLAN_ROOT_SINK") {
-            parent_node = undefined;
-          } else if (pp.node_metadata.data_sink_id != undefined) {
-            parent_node = receiver_nodes[node_id]; // Exchange sender dst
-          } else if (pp.node_metadata.join_build_id != undefined) {
-            parent_node = receiver_nodes[node_id]; // Join sender dst
-          } else if (node_stack.length > 0) {
-            parent_node = node_stack[node_stack.length - 1];
-          } else if (all_nodes.length) {
-            parent_node = all_nodes[all_nodes.length - 1];
-          }
+            var is_receiver = node_type == "EXCHANGE_NODE" ||
+                (node_type == "HASH_JOIN_NODE" && pp.num_children < 3);
+
+            var is_sender = (node_type == "Hash Join Builder" ||
+                             node_type == "KrpcDataStreamSender");
+            var parent_node;
+            if (node_type == "PLAN_ROOT_SINK") {
+              parent_node = undefined;
+            } else if (pp.node_metadata.data_sink_id != undefined) {
+              parent_node = receiver_nodes[node_id]; // Exchange sender dst
+            } else if (pp.node_metadata.join_build_id != undefined) {
+              parent_node = receiver_nodes[node_id]; // Join sender dst
+            } else if (node_stack.length > 0) {
+              parent_node = node_stack[node_stack.length - 1];
+            } else if (all_nodes.length) {
+              parent_node = all_nodes[all_nodes.length - 1];
+            }
 
-          max_namelen = Math.max(max_namelen, node_name.length + 
node_stack.length + 1);
+            max_namelen = Math.max(max_namelen, node_name.length + 
node_stack.length + 1);
 
-          if (pp.event_sequences != undefined) {
-            var node_events = pp.event_sequences[0].events;
+            if (pp.event_sequences != undefined) {
+              var node_events = pp.event_sequences[0].events;
 
-            // Start the instance event list for each event with timestamps 
from this instance
-            for (var en = 0; en < node_events.length; ++en) {
-              node_events[en].tslist = [ node_events[en].timestamp ];
-              if (node_type == "HASH_JOIN_NODE" && (en == 1 || en == 2)) {
-                node_events[en].no_bar = true;
+              // Start the instance event list for each event with timestamps 
from this instance
+              for (var en = 0; en < node_events.length; ++en) {
+                node_events[en].tslist = [ node_events[en].timestamp ];
+                if (node_type == "HASH_JOIN_NODE" && (en == 1 || en == 2)) {
+                  node_events[en].no_bar = true;
+                }
               }
             }
-          }
-          var node = {
-              name: node_name,
-              type: node_type,
-              node_id: node_id,
-              num_children: 0,
-              child_index: 0,
-              metadata: pp.node_metadata,
-              parent_node: parent_node,
-              events: node_events,
-              path: node_path.slice(0),
-              is_receiver: is_receiver,
-              is_sender: is_sender
-          }
+            var node = {
+                name: node_name,
+                type: node_type,
+                node_id: node_id,
+                num_children: 0,
+                child_index: 0,
+                metadata: pp.node_metadata,
+                parent_node: parent_node,
+                events: node_events,
+                path: node_path.slice(0),
+                is_receiver: is_receiver,
+                is_sender: is_sender
+            }
 
-          if (is_sender) {
-            node.parent_node.sender_frag_index = fragments.length;
-          }
+            if (is_sender) {
+              node.parent_node.sender_frag_index = fragments.length;
+            }
 
-          if (is_receiver) {
-            receiver_nodes[node_id] = node;
-          }
+            if (is_receiver) {
+              receiver_nodes[node_id] = node;
+            }
 
-          if (parent_node != undefined) {
-            node.child_index = parent_node.num_children++;
-          }
+            if (parent_node != undefined) {
+              node.child_index = parent_node.num_children++;
+            }
 
-          all_nodes.push(node);
+            all_nodes.push(node);
 
-          fragment.nodes.push(node);
+            fragment.nodes.push(node);
 
-          if (pp.child_profiles != undefined) {
-            node_stack.push(node);
-            pp.child_profiles.forEach(get_plan_nodes);
-            node = node_stack.pop();
+            if (pp.child_profiles != undefined) {
+              node_stack.push(node);
+              pp.child_profiles.forEach(get_plan_nodes);
+              node = node_stack.pop();
+            }
+            rownum++;
+            node_path.pop();
           }
-          rownum++;
-          node_path.pop();
-        }
-      });
+        });
 
 
-      // For each node, retrieve the instance timestamps for the remaining 
instances
-      for (var ni = 0; ni < fragment.nodes.length; ++ni) {
-        var node = fragment.nodes[ni];
-        for (var cpn = 1; cpn < fp.child_profiles.length; ++cpn) {
-          var cp = fp.child_profiles[cpn];
+        // For each node, retrieve the instance timestamps for the remaining 
instances
+        for (var ni = 0; ni < fragment.nodes.length; ++ni) {
+          var node = fragment.nodes[ni];
+          for (var cpn = 1; cpn < fp.child_profiles.length; ++cpn) {
+            var cp = fp.child_profiles[cpn];
 
-          // Use the saved node path to traverse to the same position in this 
instance
-          for (var pi = 0; pi < node.path.length; ++pi) {
-            cp = cp.child_profiles[node.path[pi]];
-          }
-          console.assert(cp.node_metadata.data_sink_id == undefined ||
-                         cp.profile_name.indexOf("(dst_id=" + node.node_id + 
")"));
-          console.assert(cp.node_metadata.plan_node_id == undefined ||
-                         cp.node_metadata.plan_node_id == node.node_id);
-
-          // Add instance events to this node
-          if (cp.event_sequences != undefined) {
-            for (var en = 0; en < node.events.length; ++en) {
-              
node.events[en].tslist.push(cp.event_sequences[0].events[en].timestamp);
+            // Use the saved node path to traverse to the same position in 
this instance
+            for (var pi = 0; pi < node.path.length; ++pi) {
+              cp = cp.child_profiles[node.path[pi]];
+            }
+            console.assert(cp.node_metadata.data_sink_id == undefined ||
+                           cp.profile_name.indexOf("(dst_id=" + node.node_id + 
")"));
+            console.assert(cp.node_metadata.plan_node_id == undefined ||
+                           cp.node_metadata.plan_node_id == node.node_id);
+
+            // Add instance events to this node
+            if (cp.event_sequences != undefined) {
+              for (var en = 0; en < node.events.length; ++en) {
+                
node.events[en].tslist.push(cp.event_sequences[0].events[en].timestamp);
+              }
             }
           }
         }
+
+        fragments.push(fragment);
       }
+    });
+    profile_available = true;
+  } catch(e) {
+    console.log(e);
+    profile_available = false;
+  }
+}
+
+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');
+
+  timing_canvas.width = window.innerWidth - 50;
+  header_canvas.width = footer_canvas.width = timing_canvas.clientWidth;
+
+  header_canvas.height = 15; // Height gets resized later
+  timing_canvas.height = 200; // Height gets resized later
+  footer_canvas.height = 15; // Height gets resized later
+
+  var header_ctx = header_canvas.getContext('2d');
+  var timing_ctx = timing_canvas.getContext('2d');
+  var footer_ctx = footer_canvas.getContext('2d');
 
-      fragments.push(fragment);
-    }
-  });
 
   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);
@@ -320,7 +310,8 @@ function renderTiming(ignored_arg) {
   var height = 15;
 
   timing_canvas.height = rownum * height;
-  var screen_height = Math.min(timing_canvas.height + 10, window.innerHeight - 
timing_canvas.offsetTop - 30);
+  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;");
 
@@ -342,7 +333,7 @@ function renderTiming(ignored_arg) {
     header_ctx.fillText(p.label, x + width / 3, text_y, Math.min(text_width, 
width / 2));
   });
 
-  rownum = 0;
+  var rownum_l = 0;
   var max_indent = 0;
   var pending_children = 0;
   var pending_senders = 0;
@@ -359,18 +350,22 @@ function renderTiming(ignored_arg) {
       timing_ctx.fillText(frag_name, 1, text_y, frag_name_width);
 
       // Fragment/sender timing row
-      DrawBars(timing_ctx, rownum, height, fevents, name_width, px_per_ns);
+      DrawBars(timing_ctx, rownum_l, height, fevents, name_width, px_per_ns);
 
-      for(var i = 0; i < fragment.nodes.length; i++) {
+      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, height, node.events, name_width, 
px_per_ns);
+          DrawBars(timing_ctx, rownum_l, height, node.events, name_width, 
px_per_ns);
           if (node.type == "HASH_JOIN_NODE") {
             timing_ctx.fillStyle = "#000000";
-            timing_ctx.fillText("X", name_width + 
minimum(node.events[2].tslist) * px_per_ns, text_y, char_width);
-            timing_ctx.fillText("O", name_width + 
minimum(node.events[2].tslist) * px_per_ns, text_y, char_width);
+            timing_ctx.fillText("X",
+                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);
           }
         }
 
@@ -393,8 +388,7 @@ function renderTiming(ignored_arg) {
         if (node.parent_node != undefined) {
           var y = height * node.parent_node.rendering.rownum;
           if (node.is_sender) {
-            var x = name_width + minimum(fevents[3].tslist) * px_per_ns;
-
+            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]);
@@ -404,7 +398,7 @@ function renderTiming(ignored_arg) {
 
             // Dotted rectangle for received rows
             timing_ctx.beginPath();
-            var x2 = name_width + maximum(fevents[4].tslist) * px_per_ns;
+            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([]);
           }
@@ -426,11 +420,11 @@ function renderTiming(ignored_arg) {
           timing_ctx.stroke();
 
         }
-        node.rendering = { rownum: rownum, label_end: label_x + label_width };
+        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;
-        rownum++;
+        rownum_l++;
 
         if (node.is_receiver) {
           if (plan_order) {
@@ -447,9 +441,11 @@ function renderTiming(ignored_arg) {
 
     }
   });
-
-  rownum = 0;
-  var text_y = (rownum + 1) * height - 4;
+  fragments.forEach(function(fragment) {
+    fragment.printed = false;
+  });
+  rownum_l = 0;
+  var text_y = (rownum_l + 1) * height - 4;
 
   // Time scale below timing diagram
   var ntics = 10;
@@ -458,7 +454,7 @@ function renderTiming(ignored_arg) {
   var x = name_width;
   for (var i = 0; i < ntics; ++i) {
     footer_ctx.fillStyle = "#000000";
-    var y = rownum * height;
+    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);
@@ -466,27 +462,37 @@ function renderTiming(ignored_arg) {
     footer_ctx.fillText((i * sec_per_tic).toFixed(2), x + px_per_tic - 25, 
text_y, chart_width / ntics);
     x += px_per_tic;
   }
-
-  if (query_finished && intervalId) {
-    clearInterval(intervalId);
-    intervalId = 0;
-  }
 }
 
-
 function refresh() {
-  req = new XMLHttpRequest();
-  req.onload = renderTiming;
+  var req = new XMLHttpRequest();
+  req.onload = function() {
+    if (req.status == 200) {
+      profile = JSON.parse(req.responseText)["profile_json"];
+      collectFromProfile();
+      if (!profile_available) {
+        setTimeout(refresh, 1000);
+      } else {
+        if (profile.child_profiles[0].info_strings.find(({key}) => key === 
"Query State").value
+            != "FINISHED") {
+          setTimeout(refresh, 1000);
+        }
+        renderTiming();
+      }
+    }
+  }
   req.open("GET", make_url("/query_timeline?query_id={{query_id}}&json"), 
true);
   req.send();
 }
 
-// Force one refresh before starting the timer.
-refresh();
+
 
 window.addEventListener('resize', function(event) {
-  renderTiming();
+  if (profile_available) {
+    renderTiming();
+  }
 }, true);
 
+window.onload = refresh;
 
 </script>

Reply via email to