class TfNodeInfo extends LegacyElementMixin()

in tensorboard/plugins/graph/tf_graph_info/tf-node-info.ts [32:746]


class TfNodeInfo extends LegacyElementMixin(PolymerElement) {
  static readonly template = html`
    <style>
      .sub-list-group {
        font-weight: 500;
        font-size: 12pt;
        padding-bottom: 8px;
        width: 100%;
      }

      .sub-list {
        max-height: 300px;
        overflow-y: scroll;
      }

      .attr-left {
        float: left;
        width: 30%;
        word-wrap: break-word;
        color: var(--secondary-text-color);
        font-size: 11pt;
        font-weight: 400;
      }

      .attr-right {
        margin-left: 30%;
        word-wrap: break-word;
        color: var(--secondary-text-color);
        font-weight: 400;
      }

      .sub-list-table {
        display: table;
        width: 100%;
      }

      .sub-list-table-row {
        display: table-row;
      }

      .sub-list-table-row .sub-list-table-cell:last-child {
        text-align: right;
      }

      .sub-list-table-cell {
        color: var(--secondary-text-color);
        display: table-cell;
        font-size: 11pt;
        font-weight: 400;
        max-width: 200px;
        padding: 0 4px;
      }

      paper-item {
        padding: 0;
        background: var(--primary-background-color);
      }

      paper-item-body[two-line] {
        min-height: 0;
        padding: 8px 12px 4px;
      }

      .expandedInfo {
        padding: 8px 12px;
      }

      .controlDeps {
        padding: 0 0 0 8px;
      }

      .node-name {
        white-space: normal;
        word-wrap: break-word;
        font-size: 14pt;
        font-weight: 500;
      }

      .node-icon {
        float: right;
      }

      .subtitle {
        color: var(--secondary-text-color);
        font-size: 12pt;
      }

      .controlLine {
        font-size: 11pt;
        font-weight: 400;
      }

      .toggle-button {
        float: right;
        max-height: 20px;
        max-width: 20px;
        padding: 0;
      }

      .control-toggle-button {
        float: left;
        max-height: 20px;
        max-width: 20px;
        padding: 0;
      }

      .toggle-include-group {
        padding-top: 4px;
      }

      .toggle-include {
        margin: 5px 6px;
        text-transform: none;
        padding: 4px 6px;
        font-size: 10pt;
        background-color: #fafafa;
        color: #666;
      }

      .toggle-include:hover {
        background-color: var(--google-yellow-100);
      }

      .non-control-list-item {
        padding-left: 10px;
      }
    </style>
    <paper-item>
      <paper-item-body two-line>
        <div>
          <paper-icon-button
            icon="{{_getToggleIcon(_expanded)}}"
            on-click="_toggleExpanded"
            class="toggle-button"
          >
          </paper-icon-button>
          <div class="node-name">
            <tf-wbr-string value="[[_node.name]]" delimiter-pattern="/">
            </tf-wbr-string>
          </div>
        </div>
        <div secondary>
          <tf-node-icon
            class="node-icon"
            node="[[_node]]"
            render-info="[[_getRenderInfo(graphNodeName, renderHierarchy)]]"
            color-by="[[colorBy]]"
            template-index="[[_templateIndex]]"
          ></tf-node-icon>
          <template is="dom-if" if="{{_node.op}}">
            <div class="subtitle">
              Operation:
              <span>[[_node.op]]</span>
            </div>
          </template>
          <template is="dom-if" if="{{_node.metagraph}}">
            <div class="subtitle">
              Subgraph:
              <span>[[_node.cardinality]]</span> nodes
            </div>
          </template>
        </div>
      </paper-item-body>
    </paper-item>
    <iron-collapse opened="{{_expanded}}">
      <template is="dom-if" if="{{_expanded}}" restamp="true">
        <div class="expandedInfo">
          <div class="sub-list-group attributes">
            Attributes (<span>[[_attributes.length]]</span>)
            <iron-list
              class="sub-list"
              id="attributesList"
              items="[[_attributes]]"
            >
              <template>
                <div>
                  <div class="attr-left">[[item.key]]</div>
                  <div class="attr-right">[[item.value]]</div>
                </div>
              </template>
            </iron-list>
          </div>

          <template is="dom-if" if="{{_device}}">
            <div class="sub-list-group device">
              <div class="attr-left">Device</div>
              <div class="attr-right">[[_device]]</div>
            </div>
          </template>

          <div class="sub-list-group predecessors">
            Inputs (<span>[[_totalPredecessors]]</span>)
            <iron-list
              class="sub-list"
              id="inputsList"
              items="[[_predecessors.regular]]"
            >
              <template>
                <tf-node-list-item
                  class="non-control-list-item"
                  card-node="[[_node]]"
                  item-node="[[item.node]]"
                  edge-label="[[item.edgeLabel]]"
                  item-render-info="[[item.renderInfo]]"
                  name="[[item.name]]"
                  item-type="predecessors"
                  color-by="[[colorBy]]"
                  template-index="[[_templateIndex]]"
                >
                </tf-node-list-item>
              </template>
            </iron-list>
            <template is="dom-if" if="[[_predecessors.control.length]]">
              <div class="controlDeps">
                <div class="controlLine">
                  <paper-icon-button
                    icon="{{_getToggleIcon(_openedControlPred)}}"
                    on-click="_toggleControlPred"
                    class="control-toggle-button"
                  >
                  </paper-icon-button>
                  Control dependencies
                </div>
                <iron-collapse opened="{{_openedControlPred}}" no-animation>
                  <template
                    is="dom-if"
                    if="{{_openedControlPred}}"
                    restamp="true"
                  >
                    <iron-list
                      class="sub-list"
                      items="[[_predecessors.control]]"
                    >
                      <template>
                        <tf-node-list-item
                          card-node="[[_node]]"
                          item-node="[[item.node]]"
                          item-render-info="[[item.renderInfo]]"
                          name="[[item.name]]"
                          item-type="predecessors"
                          color-by="[[colorBy]]"
                          template-index="[[_templateIndex]]"
                        >
                        </tf-node-list-item>
                      </template>
                    </iron-list>
                  </template>
                </iron-collapse>
              </div>
            </template>
          </div>

          <div class="sub-list-group successors">
            Outputs (<span>[[_totalSuccessors]]</span>)
            <iron-list
              class="sub-list"
              id="outputsList"
              items="[[_successors.regular]]"
            >
              <template>
                <tf-node-list-item
                  class="non-control-list-item"
                  card-node="[[_node]]"
                  item-node="[[item.node]]"
                  edge-label="[[item.edgeLabel]]"
                  item-render-info="[[item.renderInfo]]"
                  name="[[item.name]]"
                  item-type="successor"
                  color-by="[[colorBy]]"
                  template-index="[[_templateIndex]]"
                >
                </tf-node-list-item>
              </template>
            </iron-list>
            <template is="dom-if" if="[[_successors.control.length]]">
              <div class="controlDeps">
                <div class="controlLine">
                  <paper-icon-button
                    icon="{{_getToggleIcon(_openedControlSucc)}}"
                    on-click="_toggleControlSucc"
                    class="control-toggle-button"
                  >
                  </paper-icon-button>
                  Control dependencies
                </div>
                <iron-collapse opened="{{_openedControlSucc}}" no-animation>
                  <template
                    is="dom-if"
                    if="{{_openedControlSucc}}"
                    restamp="true"
                  >
                    <iron-list class="sub-list" items="[[_successors.control]]">
                      <template>
                        <tf-node-list-item
                          card-node="[[_node]]"
                          item-node="[[item.node]]"
                          item-render-info="[[item.renderInfo]]"
                          name="[[item.name]]"
                          item-type="successors"
                          color-by="[[colorBy]]"
                          template-index="[[_templateIndex]]"
                        >
                        </tf-node-list-item>
                      </template>
                    </iron-list>
                  </template>
                </iron-collapse>
              </div>
            </template>
          </div>
          <template is="dom-if" if="{{_hasDisplayableNodeStats}}">
            <div class="sub-list-group node-stats">
              Node Stats
              <div class="sub-list-table">
                <template is="dom-if" if="{{_nodeStats.totalBytes}}">
                  <div class="sub-list-table-row">
                    <div class="sub-list-table-cell">Memory</div>
                    <div class="sub-list-table-cell">
                      [[_nodeStatsFormattedBytes]]
                    </div>
                  </div>
                </template>
                <template is="dom-if" if="{{_getTotalMicros(_nodeStats)}}">
                  <div class="sub-list-table-row">
                    <div class="sub-list-table-cell">Compute Time</div>
                    <div class="sub-list-table-cell">
                      [[_nodeStatsFormattedComputeTime]]
                    </div>
                  </div>
                </template>
                <template is="dom-if" if="{{_nodeStats.outputSize}}">
                  <div class="sub-list-table-row">
                    <div class="sub-list-table-cell">Tensor Output Sizes</div>
                    <div class="sub-list-table-cell">
                      <template
                        is="dom-repeat"
                        items="{{_nodeStatsFormattedOutputSizes}}"
                      >
                        [[item]] <br />
                      </template>
                    </div>
                  </div>
                </template>
              </div>
            </div>
          </template>

          <template is="dom-if" if="[[_functionUsages.length]]">
            <div class="sub-list-group predecessors">
              Usages of the Function (<span>[[_functionUsages.length]]</span>)
              <iron-list
                class="sub-list"
                id="functionUsagesList"
                items="[[_functionUsages]]"
              >
                <template>
                  <tf-node-list-item
                    class="non-control-list-item"
                    card-node="[[_node]]"
                    item-node="[[item]]"
                    name="[[item.name]]"
                    item-type="functionUsages"
                    color-by="[[colorBy]]"
                    template-index="[[_templateIndex]]"
                  >
                  </tf-node-list-item>
                </template>
              </iron-list>
            </div>
          </template>

          <template is="dom-if" if="[[!_isLibraryFunction(_node)]]">
            <div class="toggle-include-group">
              <paper-button
                raised
                class="toggle-include"
                on-click="_toggleInclude"
              >
                <span>[[_auxButtonText]]</span>
              </paper-button>
            </div>
          </template>

          <template is="dom-if" if="{{_isInSeries(_node)}}">
            <div class="toggle-include-group">
              <paper-button
                raised
                class="toggle-include"
                on-click="_toggleGroup"
              >
                <span>[[_groupButtonText]]</span>
              </paper-button>
            </div>
          </template>
        </div>
      </template>
    </iron-collapse>
  `;
  /**
   * Note: we intentionally avoid the property name 'nodeName', because
   * Polymer Resin does not support it. Resin's contract system prevents
   * using native HTMLElement property names unless they have an
   * explicit security contract (e.g. 'title' is allowed).
   * https://github.com/Polymer/polymer-resin/blob/master/lib/contracts/contracts.js
   */
  @property({type: String})
  graphNodeName: string;
  @property({type: Object})
  graphHierarchy: tf_graph_hierarchy.Hierarchy;
  @property({type: Object})
  renderHierarchy: any;
  /** What to color the nodes by (compute time, memory, device etc.) */
  @property({type: String})
  colorBy: ColorBy;
  @property({
    type: Object,
    computed: '_getNode(graphNodeName, graphHierarchy)',
    observer: '_resetState',
  })
  _node: any;
  @property({
    type: Object,
    computed: '_getNodeStats(graphNodeName, graphHierarchy)',
    observer: '_resetState',
  })
  _nodeStats: any;
  @property({
    type: Number,
    observer: '_nodeIncludeStateChanged',
  })
  // The enum value of the include property of the selected node.
  nodeInclude: number;
  @property({
    type: Boolean,
  })
  _expanded: boolean = true;
  @property({
    type: Boolean,
  })
  _openedControlPred: boolean = false;
  @property({
    type: Boolean,
  })
  _openedControlSucc: boolean = false;
  @property({type: String})
  _auxButtonText: string;
  @property({type: String})
  _groupButtonText: string;
  @property({type: Object})
  _templateIndex: (name: string) => number | null = null;
  expandNode() {
    this.fire('_node.expand', (this as any).node);
  }
  _getNode(graphNodeName, graphHierarchy) {
    return graphHierarchy.node(graphNodeName);
  }
  _getNodeStats(graphNodeName, graphHierarchy) {
    var node = this._getNode(graphNodeName, graphHierarchy);
    if (node) {
      return node.stats;
    }
    return null;
  }
  _getTotalMicros(stats) {
    return stats ? stats.getTotalMicros() : 0;
  }
  @computed('_nodeStats')
  get _hasDisplayableNodeStats(): boolean {
    var stats = this._nodeStats;
    return tf_graph_util.hasDisplayableNodeStats(stats);
  }
  @computed('_nodeStats')
  get _nodeStatsFormattedBytes(): string {
    var stats = this._nodeStats;
    if (!stats || !stats.totalBytes) {
      return;
    }
    return tf_graph_util.convertUnitsToHumanReadable(
      stats.totalBytes,
      tf_graph_util.MEMORY_UNITS
    );
  }
  @computed('_nodeStats')
  get _nodeStatsFormattedComputeTime(): string {
    var stats = this._nodeStats;
    if (!stats || !stats.getTotalMicros()) {
      return;
    }
    return tf_graph_util.convertUnitsToHumanReadable(
      stats.getTotalMicros(),
      tf_graph_util.TIME_UNITS
    );
  }
  @computed('_nodeStats')
  get _nodeStatsFormattedOutputSizes(): unknown[] {
    var stats = this._nodeStats;
    if (!stats || !stats.outputSize || !stats.outputSize.length) {
      return;
    }
    return _.map(stats.outputSize, function (shape) {
      if (shape.length === 0) {
        return 'scalar';
      }
      return '[' + shape.join(', ') + ']';
    });
  }
  _getRenderInfo(graphNodeName, renderHierarchy) {
    return this.renderHierarchy.getOrCreateRenderNodeByName(graphNodeName);
  }
  @computed('_node')
  get _attributes(): unknown[] {
    var node = this._node;
    this.async(this._resizeList.bind(this, '#attributesList'));
    if (!node || !node.attr) {
      return [];
    }
    var attrs = [];
    _.each(node.attr, function (entry) {
      // Unpack the "too large" attributes into separate attributes
      // in the info card, with values "too large to show".
      if (entry.key === tf_graph.LARGE_ATTRS_KEY) {
        attrs = attrs.concat(
          entry.value.list.s.map(function (key) {
            return {key: key, value: 'Too large to show...'};
          })
        );
      } else {
        attrs.push({
          key: entry.key,
          value: JSON.stringify(entry.value),
        });
      }
    });
    return attrs;
  }
  @computed('_node')
  get _device(): string {
    var node = this._node;
    return node ? node.device : null;
  }
  @computed('_node', 'graphHierarchy')
  get _successors(): any {
    var node = this._node;
    var hierarchy = this.graphHierarchy;
    this._refreshNodeItemList('inputsList');
    if (!node) {
      return {regular: [], control: []};
    }
    return this._convertEdgeListToEdgeInfoList(
      hierarchy.getSuccessors(node.name),
      false,
      node.isGroupNode
    );
  }
  @computed('_node', 'graphHierarchy')
  get _predecessors(): any {
    var node = this._node;
    var hierarchy = this.graphHierarchy;
    this._refreshNodeItemList('outputsList');
    if (!node) {
      return {regular: [], control: []};
    }
    return this._convertEdgeListToEdgeInfoList(
      hierarchy.getPredecessors(node.name),
      true,
      node.isGroupNode
    );
  }
  // Only relevant if this is a library function. A list of nodes that
  // represent where the function is used.
  @computed('_node', 'graphHierarchy')
  get _functionUsages(): unknown[] {
    var node = this._node;
    var hierarchy = this.graphHierarchy;
    this._refreshNodeItemList('functionUsagesList');
    if (!node || node.type !== tf_graph.NodeType.META) {
      // Functions must be represented by metanodes.
      return [];
    }
    const libraryFunctionData =
      hierarchy.libraryFunctions[node.associatedFunction];
    if (!libraryFunctionData) {
      // This is no function.
      return [];
    }
    // Return where the function is used.
    return libraryFunctionData.usages;
  }
  // The iron lists that enumerate ops must be asynchronously updated when
  // the data they render changes. This function triggers that update.
  _refreshNodeItemList(nodeListId) {
    this.async(this._resizeList.bind(this, `#${nodeListId}`));
  }
  _convertEdgeListToEdgeInfoList(list, isPredecessor, isGroupNode) {
    /**
     * Unpacks the metaedge into a list of base edge information
     * that can be rendered.
     */
    var unpackMetaedge = (metaedge) => {
      return _.map(metaedge.baseEdgeList, (baseEdge) => {
        var name = isPredecessor ? baseEdge.v : baseEdge.w;
        return {
          name: name,
          node: this._getNode(name, this.graphHierarchy),
          edgeLabel: tf_graph_scene_edge.getLabelForBaseEdge(
            baseEdge,
            this.renderHierarchy
          ),
          renderInfo: this._getRenderInfo(name, this.renderHierarchy),
        };
      });
    };
    /**
     * Converts a list of metaedges to a list of edge information
     * that can be rendered.
     */
    var toEdgeInfoList = function (edges) {
      var edgeInfoList = [];
      _.each(edges, (metaedge) => {
        var name = isPredecessor ? metaedge.v : metaedge.w;
        // Enumerate all the base edges if the node is an OpNode, or the
        // metaedge has only 1 edge in it.
        if (!isGroupNode || metaedge.baseEdgeList.length == 1) {
          edgeInfoList = edgeInfoList.concat(unpackMetaedge(metaedge));
        } else {
          edgeInfoList.push({
            name: name,
            node: this._getNode(name, this.graphHierarchy),
            edgeLabel: tf_graph_scene_edge.getLabelForEdge(
              metaedge,
              this.renderHierarchy
            ),
            renderInfo: this._getRenderInfo(name, this.renderHierarchy),
          });
        }
      });
      return edgeInfoList;
    }.bind(this);
    return {
      regular: toEdgeInfoList(list.regular),
      control: toEdgeInfoList(list.control),
    };
  }
  @computed('_node')
  get _subnodes(): unknown[] {
    var node = this._node;
    return node && node.metagraph ? node.metagraph.nodes() : null;
  }
  @computed('_predecessors')
  get _totalPredecessors(): number {
    var predecessors = this._predecessors;
    return predecessors.regular.length + predecessors.control.length;
  }
  @computed('_successors')
  get _totalSuccessors(): number {
    var successors = this._successors;
    return successors.regular.length + successors.control.length;
  }
  _toggleControlPred() {
    this._openedControlPred = !this._openedControlPred;
  }
  _toggleControlSucc() {
    this._openedControlSucc = !this._openedControlSucc;
  }
  _toggleExpanded() {
    this._expanded = !this._expanded;
  }
  _getToggleIcon(expanded) {
    return expanded ? 'expand-less' : 'expand-more';
  }
  _resetState() {
    this._openedControlPred = false;
    this._openedControlSucc = false;
    this.set(
      '_groupButtonText',
      tf_graph_scene_node.getGroupSettingLabel(this._node)
    );
  }
  _resizeList(selector) {
    var list = document.querySelector(selector);
    if (list) {
      list.fire('iron-resize');
    }
  }
  _toggleInclude() {
    this.fire('node-toggle-inclusion', {name: this.graphNodeName});
  }
  _nodeIncludeStateChanged(include, oldInclude) {
    this.set('_auxButtonText', tf_graph.getIncludeNodeButtonString(include));
  }
  _toggleGroup() {
    var seriesName = tf_graph_scene_node.getSeriesName(this._node);
    this.fire('node-toggle-seriesgroup', {name: seriesName});
  }
  _isLibraryFunction(node) {
    // If the node name starts with this string, the node is either a
    // library function or a node within it. Those nodes should never be
    // extracted into the auxiliary scene group because they represent
    // templates for function call nodes, not ops in the graph themselves.
    return node && node.name.startsWith(tf_graph.FUNCTION_LIBRARY_NODE_PREFIX);
  }
  _isInSeries(node) {
    return tf_graph_scene_node.canBeInSeries(node);
  }
  @observe('graphHierarchy')
  _graphHierarchyChanged() {
    this._templateIndex = this.graphHierarchy.getTemplateIndex();
    this.graphHierarchy.addListener(
      tf_graph_hierarchy.HierarchyEvent.TEMPLATES_UPDATED,
      () => {
        this._templateIndex = this.graphHierarchy.getTemplateIndex();
      }
    );
  }
}