浏览代码

HUE-8674 [jb] Add more metrics to query plan.

jdesjean 6 年之前
父节点
当前提交
5146aec

+ 1 - 1
apps/jobbrowser/src/jobbrowser/apis/query_api.py

@@ -178,7 +178,7 @@ class QueryApi(Api):
     query = self.api.get_query(query_id=appid)
     query['summary'] = query.get('summary').strip() if query.get('summary') else ''
     query['plan'] = query.get('plan').strip() if query.get('plan') else ''
-    if query['plan_json']:
+    if query.get('plan_json'):
       def get_exchange_icon (o):
         if re.search(r'broadcast', o['label_detail'], re.IGNORECASE):
           return { 'svg': 'hi-broadcast' }

文件差异内容过多而无法显示
+ 0 - 0
apps/jobbrowser/src/jobbrowser/static/jobbrowser/css/jobbrowser-embeddable.css


+ 81 - 59
apps/jobbrowser/src/jobbrowser/static/jobbrowser/js/impala_dagre.js

@@ -24,7 +24,6 @@ function impalaDagre(id) {
     },
     metrics: function(data) {
       _impalaDagree._metrics = data;
-      renderGraph()
     },
     update: function(plan) {
       _impalaDagree._plan = plan;
@@ -110,7 +109,7 @@ function impalaDagre(id) {
                       "#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, max_node_time) {
+  function build(node, parent, edges, states, colour_idx, max_node_time, index) {
     if (node["output_card"] === null || node["output_card"] === undefined) {
       return;
     }
@@ -125,23 +124,27 @@ function impalaDagre(id) {
                   "icon": node["icon"],
                   "is_broadcast": node["is_broadcast"],
                   "max_time_val": node["max_time_val"]});
+    var edgeCount;
     if (parent) {
-      var label_val = "" + ko.bindingHandlers.simplesize.humanSize(parseInt(node["output_card"], 10));
-      edges.push({ start: node["label"], end: parent,
-                   style: { label: label_val }});
+      edgeCount = parseInt(node["output_card"], 10);
+      var label_val = "" + ko.bindingHandlers.simplesize.humanSize(edgeCount);
+      edges.push({ start: node["label"], end: parent, style: { label: label_val, labelpos: index === 0 ? 'l' : 'r' }, val: edgeCount });
     }
     // Add an inter-fragment edge. We use a red dashed line to show that rows are crossing
     // the fragment boundary.
     if (node["data_stream_target"]) {
+      edgeCount = parseInt(node["output_card"], 10);
       edges.push({ "start": node["label"],
                    "end": node["data_stream_target"],
-                   "style": { label: ko.bindingHandlers.simplesize.humanSize(parseInt(node["output_card"], 10)),
-                              style: "stroke-dasharray: 5, 5;"}});
+                   "val": edgeCount,
+                   "style": { label: ko.bindingHandlers.simplesize.humanSize(edgeCount),
+                              style: "stroke-dasharray: 5, 5;",
+                              labelpos: index === 0 ? 'l' : 'r' }});
     }
     max_node_time = Math.max(node["max_time_val"], max_node_time)
     for (var i = 0; i < node["children"].length; ++i) {
       max_node_time = build(
-        node["children"][i], node["label"], edges, states, colour_idx, max_node_time);
+        node["children"][i], node["label"], edges, states, colour_idx, max_node_time, i);
     }
     return max_node_time;
   }
@@ -205,67 +208,52 @@ function impalaDagre(id) {
 
   function getTimelineData(key) {
     if (!_impalaDagree._metrics) {
-      return [];
+      return;
     }
     var id = parseInt(key.split(':')[0], 10);
-    if (!_impalaDagree._metrics[id]) {
-      return [];
+    if (!_impalaDagree._metrics.nodes[id] || !_impalaDagree._metrics.nodes[id].timeline) {
+      return;
     }
-    var times = _impalaDagree._metrics[id];
-    var timesKeys = Object.keys(times);
-    var timesKey;
-    for (var i = 0; i < timesKeys.length; i++) {
-      if (times[timesKeys[i]]['timeline'] && times[timesKeys[i]]['timeline']['Node Lifecycle Event Timeline']) {
-        timesKey = timesKeys[i];
-        break;
+    var timeline = _impalaDagree._metrics.nodes[id].timeline;
+    var times = Object.keys(timeline.hosts);
+    for (var i = 0; i < times.length; i++) {
+      if (!timeline.hosts[times[i]]['Node Lifecycle Event Timeline']) {
+        continue;
       }
+      timeline.hosts[times[i]]['Node Lifecycle Event Timeline'].forEach(function (time, index, array) {
+        time.color = colors[index % colors.length];
+        return time;
+      });
     }
-    if (!timesKey) {
-      return [];
-    }
-    var time = times[timesKey]['timeline']['Node Lifecycle Event Timeline'];
-    return time.map(function (time, index, array) {
-      var startTime = index > 0 && array[index - 1].value || 0;
-      return { starting_time: startTime, ending_time : time.value, duration: time.value - startTime, color: colors[index % colors.length], name: time.name, unit: time.unit };
-    });
+    return timeline;
   }
 
   function renderTimeline(key) {
     var datum = getTimelineData(key);
-    if (!datum.length) {
+    if (!datum || !datum.hosts[datum.min] || !datum.hosts[datum.min]['Node Lifecycle Event Timeline']) {
       return '';
     }
     var end = _impalaDagree._metrics && _impalaDagree._metrics['max'] || 10;
     var divider = end > 33554428 ? 1000000 : 1; // values are in NS, scaling to MS as max pixel value is 33554428px ~9h in MS
     var html = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ' + (end / divider) + ' 10" class="timeline" preserveAspectRatio="none">';
-    html += datum.map(function(time, index) {
-      return '<rect x="' + (time.starting_time / divider) + '" width="' + (time.duration / divider)  + '" height="10" style="fill:' + time.color  +'"></rect>';
+    html += datum.hosts[datum.min]['Node Lifecycle Event Timeline'].map(function(time, index) {
+      return '<rect x="' + (time.start_time / divider) + '" width="' + (time.duration / divider)  + '" height="10" style="fill:' + time.color  +'"></rect>';
     }).join('');
     html += '</svg>';
     return html;
   }
 
-  function renderTimelineLegend(key) {
-    var datum = getTimelineData(key);
-    if (!datum.length) {
-      return '';
-    }
-    return datum.map(function(time, index) {
-      return '<li><div class="legend-icon" style="background-color:' + time.color +' "></div><div class="metric-name">' + time.name + '</div> <div class="metric-value">' + ko.bindingHandlers.numberFormat.human(time.duration, time.unit) + '</div></li>';
-    }).join('');
-  }
-
   function showDetail(id) {
     var data;
-    if (_impalaDagree._metrics[id] && _impalaDagree._metrics[id]['averaged']['metrics']) {
-      data = _impalaDagree._metrics[id]['averaged']['metrics'];
-    } else if (_impalaDagree._metrics[id] && _impalaDagree._metrics[id]['metrics']) {
-      data = _impalaDagree._metrics[id]['metrics'][Object.keys(_impalaDagree._metrics[id]['metrics'])[0]];
+    if (!_impalaDagree._metrics || !_impalaDagree._metrics.nodes[id]) {
+      return;
     }
+    var data = _impalaDagree._metrics.nodes[id];
+
     d3.select('.query-plan').classed('open', true);
     var details = d3.select('.query-plan .details');
     var key = getKey(id);
-    details.html('<header class="metric-title">' + getIcon(states_by_name[key].icon) + '<h3>' + states_by_name[key].label+ '</h3></div>')
+    details.html('<header class="metric-title">' + getIcon(states_by_name[key].icon) + '<h4>' + states_by_name[key].label+ '</h4></div>')
     var detailsContent = details.append('div').classed('details-content', true);
 
     var timeline = renderTimeline(key, '');
@@ -273,32 +261,57 @@ function impalaDagre(id) {
       var timelineSection = detailsContent.append('div').classed('details-section', true);
       var timelineTitle = timelineSection.append('header');
       timelineTitle.append('svg').classed('hi', true).append('use').attr('xlink:href', '#hi-access-time');
-      timelineTitle.append('h4').text(window.HUE_I18n.profile.timeline);
+      timelineTitle.append('h5').text(window.HUE_I18n.profile.timeline);
       timelineSection.node().appendChild($.parseXML(renderTimeline(key, '')).children[0]);
 
-      timelineSection.append('ol').classed('', true).html(renderTimelineLegend(key));
-      detailsContent.append('div').classed('divider', true);
+      var timelineSectionTable = timelineSection.append('table');
+      timelineSectionTable.append('thead').selectAll('tr').data(['\u00A0'].concat(Object.keys(_impalaDagree._metrics.nodes[id].timeline.hosts).sort())).enter().append('tr').append('td').text(function (host, i) { return i > 0 ? 'Host ' + i : host; }).attr('title', function (host) { return host; });
+      var timelineSectionTableBody = timelineSectionTable.append('tbody');
+      var timelineHosts = Object.keys(_impalaDagree._metrics.nodes[id].timeline.hosts).sort().map(function (host) { return _impalaDagree._metrics.nodes[id].timeline.hosts[host]; });
+      var timelineSectionTableCols = timelineSectionTableBody.selectAll('tr').data(timelineHosts);
+      var timelineSectionTableCol0 = timelineSectionTableBody.selectAll('tr').data(timelineHosts.slice(0,1));
+      timelineSectionTableCol0.enter().append('tr').selectAll('td').data(function (x) { return x['Node Lifecycle Event Timeline']; }).enter().append('td').html(function (time) { return '<div class="legend-icon" title="' + time.name + '" style="background-color:' + time.color +' "></div><div class="metric-name">' + time.name + '</div>'; });
+      timelineSectionTableCols.enter().append('tr').selectAll('td').data(function (x) { return x['Node Lifecycle Event Timeline']; }).enter().append('td').text(function (datum) { return ko.bindingHandlers.numberFormat.human(datum.duration, datum.unit); });
     }
 
     var metricsSection = detailsContent.append('div').classed('details-section', true);
+    var metricsChildSections = metricsSection.selectAll('div').data(Object.keys(data.children));
 
     var metricsTitle = metricsSection.append('header');
     metricsTitle.append('svg').classed('hi', true).append('use').attr('xlink:href', '#hi-bar-chart');
-    metricsTitle.append('h4').text(window.HUE_I18n.profile.metrics);
-
-    var metricsContent = metricsSection.append('ul').classed('metrics', true);
-
-    var metrics = metricsContent.selectAll('li')
-    .data(Object.keys(data).sort().map(function (key) { return data[key]; }));
-    metrics.exit().remove();
-    metrics.enter().append('li');
-    metrics.html(function (datum) { return '<div class="metric-name">' + datum.name + '</div> <div class="metric-value">' + ko.bindingHandlers.numberFormat.human(datum.value, datum.unit) + '</div>'; });
+    metricsTitle.append('h5').text(window.HUE_I18n.profile.metrics);
+
+    var metricsContent = metricsSection.append('table').classed('metrics', true);
+    var metricsHosts = Object.keys(data.properties.hosts).sort().map(function (key) { return data.properties.hosts[key]; });
+    var metricsCols = metricsContent.selectAll('tr').data(metricsHosts);
+    var metricsCols0 = metricsContent.selectAll('tr').data(metricsHosts.slice(0,1));
+    metricsCols0.enter().append('tr').selectAll('td').data(function (host) { return Object.keys(host).sort(); }).enter().append('td').text(function (x) { return x; }).attr('title', function (x) { return x; });
+    metricsCols.enter().append('tr').selectAll('td').data(function (x) { return Object.keys(x).sort().map(function (key) {return x[key]; }) }).enter().append('td').text(function (datum) { return ko.bindingHandlers.numberFormat.human(datum.value, datum.unit); });
+    metricsContent.append('thead').selectAll('tr').data(['\u00A0'].concat(Object.keys(data.properties.hosts).sort())).enter().append('tr').append('td').text(function (x, i) { return i > 0 ? x === 'averaged' ? x : 'Host ' + (i - 1) : x; }).attr('title', function (x) {return x;});
+
+    var metricsChildSectionsContent = metricsChildSections.enter().append('div');
+    metricsChildSectionsContent.append('header').append('h5').text(function (key) { return key; });
+    var metricsChildSectionsContentTable = metricsChildSectionsContent.append('table').classed('metrics', true);
+    var fChildrenHosts = function (key) { return Object.keys(data.children[key].hosts).sort().map(function (host) { return data.children[key].hosts[host]; }); };
+    var metricsChildSectionsContentCols = metricsChildSectionsContentTable.selectAll('tr').data(function (key) { return fChildrenHosts(key); });
+    var metricsChildSectionsContentCols0 = metricsChildSectionsContentTable.selectAll('tr').data(function (key) { return fChildrenHosts(key).slice(0,1); });
+    metricsChildSectionsContentCols0.enter().append('tr').selectAll('td').data(function (host) { return Object.keys(host).sort(); }).enter().append('td').text(function (x) { return x; }).attr('title', function (x) { return x; });
+    metricsChildSectionsContentCols.enter().append('tr').selectAll('td').data(function (x) { return Object.keys(x).sort().map(function (key) {return x[key]; }) }).enter().append('td').text(function(datum) { return ko.bindingHandlers.numberFormat.human(datum.value, datum.unit);});
+    metricsChildSectionsContentTable.append('thead').selectAll('tr').data(function (key) { return ['\u00A0'].concat(Object.keys(data.children[key].hosts).sort()); }).enter().append('tr').append('td').text(function (x, i) { return i > 0 ? x === 'averaged' ? x : 'Host ' + (i - 1) : x; }).attr('title', function (x) {return x;});
   }
 
   function hideDetail(id) {
     d3.select('.query-plan').classed('open', false);
   }
 
+  function average(states, metric) {
+    var sum = 0;
+    for (var i = 0; i < states.length; i++) {
+      sum += states[i][metric];
+    }
+    return sum / states.length;
+  }
+
   function renderGraph() {
     var plan = _impalaDagree._plan;
     if (!plan || !plan.plan_nodes || !plan.plan_nodes.length) return;
@@ -313,14 +326,16 @@ function impalaDagre(id) {
       // Pick a new colour for each plan fragment
       colour_idx = (colour_idx + 1) % colours.length;
     });
-
+    var avg = average(states, 'max_time_val');
     // Keep a map of names to states for use when processing edges.
     states.forEach(function(state) {
       // Build the label for the node from the name and the detail
       var html = "<div onclick=\"event.stopPropagation(); huePubSub.publish('impala.node.select', " + parseInt(state.name.split(':')[0], 10) + ");\">"; // TODO: Remove Hue dependency
       html += getIcon(state.icon)
       html += "<span class='name'>" + state.label + "</span><br/>";
-      html += "<span class='metric'>" + state.max_time + "</span>";
+      console.log(state.max_time_val + '-' + avg);
+      var aboveAverageClass = state.max_time_val > avg ? 'above-average' : '';
+      html += "<span class='metric " + aboveAverageClass + "'>" + state.max_time + "</span>";
       html += "<span class='detail'>" + state.detail + "</span><br/>";
       html += "<span class='id'>" + state.name + "</span>";
       html += renderTimeline(state.name);
@@ -341,11 +356,18 @@ function impalaDagre(id) {
       states_by_name[state.name] = state;
     });
 
+    var avgEdges = average(edges, 'val');
+
     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 += " * " + states_by_name[edge.end].num_instances;
+        if (states_by_name[edge.end].num_instances > 1) {
+          edge.style.label += " * " + states_by_name[edge.end].num_instances;
+        }
+      }
+      if (edge.val > avgEdges) {
+        edge.style.labelStyle = "font-weight: bold";
       }
       g.setEdge(edge.start, edge.end, edge.style);
     });

+ 52 - 21
apps/jobbrowser/src/jobbrowser/static/jobbrowser/less/jobbrowser-embeddable.less

@@ -96,24 +96,26 @@
     .badge {
       color: @cui-gray-800;
       text-shadow: none;
+      font-weight: normal;
     }
     .metric {
       position: absolute;
       top: 0px;
       right: 0px;
-      font-weight: normal;
     }
     .name {
       padding-right: 80px;
+      text-transform: capitalize;
+      font-size: 13px;
     }
     .detail {
-      font-weight: normal;
       overflow: hidden;
       text-overflow: ellipsis;
       width: calc(~"100% - 32px");
       max-width: 185px;
       display: inline-block;
       height: 14px;
+      text-transform: lowercase;
     }
     foreignObject {
       .fa {
@@ -153,7 +155,7 @@
       display: none;
     }
     .node.active {
-      rect {
+      > rect {
         filter: url(#dropshadow);
         stroke: @hue-primary-color-dark;
         fill: @hue-primary-color-light;
@@ -196,10 +198,15 @@
     }
     .edgeLabel text {
       font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
+      fill: @cui-gray-800;
+      font-size: inherit;
     }
     foreignObject > div {
       position: relative;
     }
+    .above-average {
+      font-weight: bold;
+    }
     .timeline {
       width: 175px;
       height: 10px;
@@ -207,7 +214,7 @@
       border-radius: 4px;
     }
     .details {
-      background-color: white;
+      background-color: @cui-white;
       box-shadow: 0px 0px 10px 0px;
       color: @cui-gray-600;
       position: absolute;
@@ -232,6 +239,10 @@
         margin: 0px;
         display: inline-block;
       }
+      h5 {
+        margin: 0px;
+        display: inline-block;
+      }
       ol {
         list-style-type: none;
         margin: 0px;
@@ -244,6 +255,9 @@
       }
       .details-section {
         padding: 4px;
+        header {
+          margin-top: 10px;
+        }
       }
       .metric-title {
         background-color: @hue-primary-color-light;
@@ -262,31 +276,48 @@
         overflow-y: scroll;
         max-height: calc(~"100% - 40px");
       }
+      .legend-icon {
+        width: 8px;
+        height: 8px;
+        border-radius: 5px;
+        border: 1px solid @cui-gray-300;
+        display: inline-block;
+      }
       .metric-name {
-        color: @cui-gray-800;
-        width: 107px;
-        overflow: hidden;
-        text-align: right;
         display: inline-block;
-        white-space: nowrap;
-        vertical-align: middle;
-        font-weight: bold;
         padding-left: 2px;
       }
-      .metric-value {
+      .details-section .timeline {
+        margin-bottom: 10px;
+      }
+      table {
+        display: table;
+      }
+      table tr {
+        display: table-cell;
+      }
+      table tr td {
         color: @cui-gray-800;
+        max-width: 107px;
         overflow: hidden;
-        width: 60px;
-        vertical-align: middle;
-        display: inline-block;
+        display: block;
         white-space: nowrap;
+        padding-right: 5px;
+        text-overflow: ellipsis;
       }
-      .legend-icon {
-        width: 8px;
-        height: 8px;
-        border-radius: 5px;
-        border: 1px solid @cui-gray-300;
-        display: inline-block;
+      table thead tr td {
+        background-color: #FFFFFF;
+        border-bottom: 1px solid @cui-gray-300;
+        text-transform: capitalize;
+      }
+      table tr:nth-child(1) td {
+        border-right: 1px solid @cui-gray-300;
+      }
+      table thead tr td:nth-child(odd) {
+        background-color:@cui-white;
+      }
+      table tr td:nth-child(odd) {
+        background-color: @cui-gray-050;
       }
     }
   }

+ 4 - 4
desktop/core/src/desktop/static/desktop/js/ko.hue-bindings.js

@@ -7510,9 +7510,9 @@
       },
       update: function (element, valueAccessor) {
         var props = ko.unwrap(valueAccessor());
-        this._impalaDagre.update(props.value);
         this._impalaDagre.metrics(props.metrics);
         this._impalaDagre.height(props.height);
+        this._impalaDagre.update(props.value);
       }
     };
   })();
@@ -7579,10 +7579,10 @@
           return value + " ns";
         } else if (value < Math.pow(10, 6)) {
           value = (value * 1.0) / Math.pow(10, 3);
-          return sprintf("%.2f us", value);
+          return sprintf("%.1f us", value);
         } else if (value < Math.pow(10, 9)) {
           value = (value * 1.0) / Math.pow(10, 6);
-          return sprintf("%.2f ms", value);
+          return sprintf("%.1f ms", value);
         } else {
           // get the ms value
           var SECOND = 1;
@@ -7602,7 +7602,7 @@
           }
 
           if (value > SECOND) {
-            buffer += sprintf("%.2f s", value * 1.0 / SECOND);
+            buffer += sprintf("%.1f s", value * 1.0 / SECOND);
           }
           return buffer;
         }

+ 55 - 18
desktop/libs/libanalyze/src/libanalyze/analyze.py

@@ -40,6 +40,7 @@ class Node(object):
     self.children = []
     self.fragment = None
     self.fragment_instance = None
+    self.plan_node = None
     self.pos = 0
 
   def add_child(self, c):
@@ -89,7 +90,11 @@ class Node(object):
     elif self.is_fragment():
       return re.search(r'(.*?Fragment) (F\d+)', self.val.name).group(1)
     else:
-      return self.val.name
+      matches = re.search(r'(.*?)(\s+\(.*?\))?$', self.val.name)
+      if matches.group(2):
+        return matches.group(1)
+      else:
+        return self.val.name
 
   def id(self):
     matches = re.search(r'(.*?)(\s+\(((dst_)?id)=(\d+)\))?$', self.val.name)
@@ -128,17 +133,20 @@ class Node(object):
 
     return results
 
-  def foreach_lambda(self, method, fragment=None, fragment_instance=None, pos=0):
+  def foreach_lambda(self, method, plan_node=None, fragment=None, fragment_instance=None, pos=0):
     self.fragment = fragment
     self.fragment_instance = fragment_instance
     self.pos = pos
+    self.plan_node = plan_node
     if self.is_fragment():
       fragment = self
     elif self.is_fragment_instance():
       fragment_instance = self
+    elif self.is_plan_node():
+      plan_node = self
 
     for idx, x in enumerate(self.children):
-      x.foreach_lambda(method, fragment=fragment, fragment_instance=fragment_instance, pos=idx)
+      x.foreach_lambda(method, plan_node=plan_node, fragment=fragment, fragment_instance=fragment_instance, pos=idx)
 
     method(self) # Post execution, because some results need child to have processed
 
@@ -242,11 +250,15 @@ class Node(object):
     event_list = {}
     if self.val.event_sequences:
       for s in self.val.event_sequences:
+        start_time = 0
         sequence_name = s.name
         event_list[sequence_name] = []
+        start_time = 0
         for i in range(len(s.labels)):
+          event_duration = s.timestamps[i] - start_time
           event_name = s.labels[i]
-          event_list[sequence_name].append({'name': event_name, 'value': s.timestamps[i], 'unit': 5})
+          event_list[sequence_name].append({'name': event_name, 'value': s.timestamps[i], 'unit': 5, 'start_time': start_time, 'duration': event_duration})
+          start_time = s.timestamps[i]
     return event_list
 
   def repr(self, indent):
@@ -296,23 +308,48 @@ def metrics(profile):
   execution_profile = profile.find_by_name('Execution Profile')
   if not execution_profile:
     return {}
-  counter_map = {'max': 0}
-  def get_metric(node, counter_map=counter_map):
-    if not node.is_plan_node():
-      return
-    nid = node.id()
-    if counter_map.get(nid) is None:
-      counter_map[nid] = {}
+  counter_map = {'nodes': {}, 'max': 0}
+  def flatten(node, counter_map=counter_map):
+    is_plan_node = node.is_plan_node()
+    if not is_plan_node:
+      if node.plan_node:
+        nid = node.plan_node.id()
+      else:
+        return
+    else:
+      nid = node.id()
+
     host = node.augmented_host()
+    metric_map = node.metric_map()
+    if counter_map['nodes'].get(nid) is None:
+      counter_map['nodes'][nid] = {'properties': { 'hosts': {} }, 'children': { }, 'timeline': {'hosts': {}}}
+
     event_list = node.event_list();
-    if event_list and event_list.get('Node Lifecycle Event Timeline'):
-      last_value = event_list['Node Lifecycle Event Timeline'][len(event_list['Node Lifecycle Event Timeline']) - 1]['value']
-      counter_map['max'] = max(last_value, counter_map['max'])
-    if host:
-      counter_map[nid][host] = {'metrics': node.metric_map(), 'timeline': event_list}
+
+    if is_plan_node:
+      counter_map['nodes'][nid]['properties']['hosts'][host] = metric_map
+      if event_list:
+        counter_map['nodes'][nid]['timeline']['hosts'][host] = event_list
     else:
-      counter_map[nid] = {'metrics': node.metric_map(), 'timeline': event_list}
-  execution_profile.foreach_lambda(get_metric)
+      name = node.name()
+      if counter_map['nodes'][nid]['children'].get(name) is None:
+        counter_map['nodes'][nid]['children'][name] = {'hosts': {}}
+      counter_map['nodes'][nid]['children'][name]['hosts'][host] = metric_map
+
+  execution_profile.foreach_lambda(flatten)
+
+  for nodeid, node in counter_map['nodes'].iteritems():
+    host_min = {'value': sys.maxint, 'host' : None}
+    for host_name, host_value in node['timeline']['hosts'].iteritems():
+      value = host_value['Node Lifecycle Event Timeline'][len(host_value['Node Lifecycle Event Timeline']) - 1]['value']
+      if value < host_min['value']:
+        host_min['value'] = value
+        host_min['host'] = host_name
+    node['timeline']['min'] = host_min.get('host', '')
+    if node['timeline']['min']:
+      node_min = node['timeline']['hosts'][node['timeline']['min']]['Node Lifecycle Event Timeline']
+      counter_map['max'] = max(node_min[len(node_min) - 1]['value'], counter_map['max'])
+
   counter_map['ImpalaServer'] = profile.find_by_name('ImpalaServer').metric_map()
   return counter_map
 

+ 5 - 3
desktop/libs/libanalyze/src/libanalyze/rules.py

@@ -618,9 +618,11 @@ class TopDownAnalysis:
             # Make sure to substract the wait time for the exchange node
             if is_plan_node and re.search(r'EXCHANGE_NODE', node.val.name) is not None:
                 async_time = counter_map.get('AsyncTotalTime', models.TCounter(value=0)).value
-                dequeue = node.find_by_name('Dequeue')
-                data_wait_time = dequeue.counter_map().get('DataWaitTime', models.TCounter(value=0)).value if dequeue else 0
-                local_time = counter_map['TotalTime'].value - counter_map['InactiveTotalTime'].value - async_time - data_wait_time
+                inactive_time = counter_map['InactiveTotalTime'].value
+                if inactive_time == 0:
+                  dequeue = node.find_by_name('Dequeue')
+                  inactive_time = dequeue.counter_map().get('DataWaitTime', models.TCounter(value=0)).value if dequeue else 0
+                local_time = counter_map['TotalTime'].value - inactive_time - async_time
 
             # For Hash Join, if the "LocalTime" metrics
             if is_plan_node and re.search(r'HASH_JOIN_NODE', node.val.name) is not None:

部分文件因为文件数量过多而无法显示