| # -*- coding: utf-8 -*- |
| # |
| # Licensed to the Apache Software Foundation (ASF) under one |
| # or more contributor license agreements. See the NOTICE file |
| # distributed with this work for additional information |
| # regarding copyright ownership. The ASF licenses this file |
| # to you under the Apache License, Version 2.0 (the |
| # "License"); you may not use this file except in compliance |
| # with the License. You may obtain a copy of the License at |
| # |
| # http://www.apache.org/licenses/LICENSE-2.0 |
| # |
| # Unless required by applicable law or agreed to in writing, |
| # software distributed under the License is distributed on an |
| # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| # KIND, either express or implied. See the License for the |
| # specific language governing permissions and limitations |
| # under the License. |
| |
| from __future__ import absolute_import, print_function, unicode_literals |
| |
| from impala_thrift_gen.ExecStats.ttypes import TExecStats |
| |
| |
| def build_exec_summary_table(summary, idx, indent_level, new_indent_level, output, |
| is_prettyprint=True, separate_prefix_column=False): |
| """Direct translation of Coordinator::PrintExecSummary() to recursively build a list |
| of rows of summary statistics, one per exec node |
| |
| summary: the TExecSummary object that contains all the summary data |
| |
| idx: the index of the node to print |
| |
| indent_level: the number of spaces to print before writing the node's label, to give |
| the appearance of a tree. The 0th child of a node has the same indent_level as its |
| parent. All other children have an indent_level of one greater than their parent. |
| |
| new_indent_level: If true, this indent level is different from the previous row's. |
| |
| output: the list of rows into which to append the rows produced for this node and its |
| children. |
| |
| is_prettyprint: Optional. If True, print time, units, and bytes columns in pretty |
| printed format. |
| |
| separate_prefix_column: Optional. If True, the prefix and operator name will be |
| returned as separate column. Otherwise, prefix and operater name will be concatenated |
| into single column. |
| |
| Returns the index of the next exec node in summary.exec_nodes that should be |
| processed, used internally to this method only. |
| """ |
| if not summary.nodes: |
| # Summary nodes is empty or None. Nothing to build. |
| return |
| assert idx < len(summary.nodes), ( |
| "Index ({0}) must be less than exec summary count ({1})").format( |
| idx, len(summary.nodes)) |
| |
| attrs = ["latency_ns", "cpu_time_ns", "cardinality", "memory_used"] |
| |
| # Initialise aggregate and maximum stats |
| agg_stats, max_stats = TExecStats(), TExecStats() |
| for attr in attrs: |
| setattr(agg_stats, attr, 0) |
| setattr(max_stats, attr, 0) |
| |
| node = summary.nodes[idx] |
| instances = 0 |
| if node.exec_stats: |
| # exec_stats is not None or an empty list. |
| instances = len(node.exec_stats) |
| for stats in node.exec_stats: |
| for attr in attrs: |
| val = getattr(stats, attr) |
| if val is not None: |
| setattr(agg_stats, attr, getattr(agg_stats, attr) + val) |
| setattr(max_stats, attr, max(getattr(max_stats, attr), val)) |
| avg_time = agg_stats.latency_ns / instances |
| else: |
| avg_time = 0 |
| |
| is_sink = node.node_id == -1 |
| # If the node is a broadcast-receiving exchange node, the cardinality of rows produced |
| # is the max over all instances (which should all have received the same number of |
| # rows). Otherwise, the cardinality is the sum over all instances which process |
| # disjoint partitions. |
| if is_sink: |
| cardinality = -1 |
| elif node.is_broadcast: |
| cardinality = max_stats.cardinality |
| else: |
| cardinality = agg_stats.cardinality |
| |
| est_stats = node.estimated_stats |
| label_prefix = "" |
| if indent_level > 0: |
| label_prefix = "|" |
| label_prefix += " |" * (indent_level - 1) |
| if new_indent_level: |
| label_prefix += "--" |
| else: |
| label_prefix += " " |
| |
| def prettyprint(val, units, divisor): |
| for unit in units: |
| if val < divisor: |
| if unit == units[0]: |
| return "%d%s" % (val, unit) |
| else: |
| return "%3.2f%s" % (val, unit) |
| val /= divisor |
| |
| def prettyprint_bytes(byte_val): |
| return prettyprint(byte_val, [' B', ' KB', ' MB', ' GB', ' TB'], 1024.0) |
| |
| def prettyprint_units(unit_val): |
| return prettyprint(unit_val, ["", "K", "M", "B"], 1000.0) |
| |
| def prettyprint_time(time_val): |
| return prettyprint(time_val, ["ns", "us", "ms", "s"], 1000.0) |
| |
| latency = max_stats.latency_ns |
| cardinality_est = est_stats.cardinality |
| memory_used = max_stats.memory_used |
| memory_est = est_stats.memory_used |
| if (is_prettyprint): |
| avg_time = prettyprint_time(avg_time) |
| latency = prettyprint_time(latency) |
| cardinality = "" if is_sink else prettyprint_units(cardinality) |
| cardinality_est = "" if is_sink else prettyprint_units(cardinality_est) |
| memory_used = prettyprint_bytes(memory_used) |
| memory_est = prettyprint_bytes(memory_est) |
| |
| row = list() |
| if separate_prefix_column: |
| row.append(label_prefix) |
| row.append(node.label) |
| else: |
| row.append(label_prefix + node.label) |
| |
| row.extend([ |
| node.num_hosts, |
| instances, |
| avg_time, |
| latency, |
| cardinality, |
| cardinality_est, |
| memory_used, |
| memory_est, |
| node.label_detail]) |
| |
| output.append(row) |
| try: |
| sender_idx = summary.exch_to_sender_map[idx] |
| # This is an exchange node or a join node with a separate builder, so the source |
| # is a fragment root, and should be printed next. |
| sender_indent_level = indent_level + node.num_children |
| sender_new_indent_level = node.num_children > 0 |
| build_exec_summary_table(summary, sender_idx, sender_indent_level, |
| sender_new_indent_level, output, is_prettyprint, |
| separate_prefix_column) |
| except (KeyError, TypeError): |
| # Fall through if idx not in map, or if exch_to_sender_map itself is not set |
| pass |
| |
| idx += 1 |
| if node.num_children > 0: |
| first_child_output = [] |
| idx = build_exec_summary_table(summary, idx, indent_level, False, first_child_output, |
| is_prettyprint, separate_prefix_column) |
| for _ in range(1, node.num_children): |
| # All other children are indented |
| idx = build_exec_summary_table(summary, idx, indent_level + 1, True, output, |
| is_prettyprint, separate_prefix_column) |
| output += first_child_output |
| return idx |