function computeScopeGraph(db, zoneIndex) {
  // Edges in the graph, keyed by 'eventName|eventName', value is sum user time.
  var edges = {};
  var nodeList = [];
  var nodeIndices = {};

  nodeList.push({
    name: '<root>',
    eventType: null
  });
  nodeIndices['<root>'] = 0;

  zoneIndex.forEach(-Number.MAX_VALUE, Number.MAX_VALUE, function(e) {
    var eventType = e.eventType;
    if (eventType.eventClass == wtf.data.EventClass.SCOPE &&
        !(eventType.flags & wtf.data.EventFlag.INTERNAL)) {
      var scope = e.scope;
      if (!nodeIndices[eventType.name]) {
        nodeIndices[eventType.name] = nodeList.length;
        nodeList.push({
          name: eventType.name,
          eventType: eventType
        });
      }

      var key;
      var parentScope = scope.getParent();
      if (parentScope) {
        // Child.
        var parentEventType = parentScope.getEnterEvent().eventType;
        if (!nodeIndices[parentEventType.name]) {
          nodeIndices[parentEventType.name] = nodeList.length;
          nodeList.push({
            name: parentEventType.name,
            eventType: parentEventType
          });
        }
        key = parentEventType.name + '|' + eventType.name;
      } else {
        // Root scope.
        key = '<root>|' + eventType.name;
      }

      var value = edges[key] || 0;
      value += scope.getUserDuration();
      edges[key] = value;
    }
  });

  var edgeList = [];
  for (var key in edges) {
    var value = edges[key];
    if (!value) {
      continue;
    }
    var keyParts = key.split('|');
    var parentName = keyParts[0];
    var childName = keyParts[1];
    if (parentName == childName) {
      continue;
    }
    if (parentName == '<root>') {
      continue;
    }
    edgeList.push({
      source: nodeIndices[parentName],
      target: nodeIndices[childName],
      value: edges[key]
    });
  }

  return {
    nodes: nodeList,
    edges: edgeList
  };
}
@fileoverview Diagrams addon app code.

@author benvanik@google.com (Ben Vanik)