| Index: tools/binary_size/run_binary_size_analysis.py
 | 
| diff --git a/tools/binary_size/run_binary_size_analysis.py b/tools/binary_size/run_binary_size_analysis.py
 | 
| index f4dad625868b64b765115205ac8c36a1543fa69a..f06884f8dafe1f7ba54c8586d18882c0e0ec5227 100755
 | 
| --- a/tools/binary_size/run_binary_size_analysis.py
 | 
| +++ b/tools/binary_size/run_binary_size_analysis.py
 | 
| @@ -39,6 +39,25 @@ sys.path.append(elf_symbolizer_path)
 | 
|  import symbols.elf_symbolizer as elf_symbolizer  # pylint: disable=F0401
 | 
|  
 | 
|  
 | 
| +# Node dictionary keys. These are output in json read by the webapp so
 | 
| +# keep them short to save file size.
 | 
| +# Note: If these change, the webapp must also change.
 | 
| +NODE_TYPE_KEY = 'k'
 | 
| +NODE_NAME_KEY = 'n'
 | 
| +NODE_CHILDREN_KEY = 'children'
 | 
| +NODE_SYMBOL_TYPE_KEY = 't'
 | 
| +NODE_SYMBOL_SIZE_KEY = 'value'
 | 
| +NODE_MAX_DEPTH_KEY = 'maxDepth'
 | 
| +NODE_LAST_PATH_ELEMENT_KEY = 'lastPathElement'
 | 
| +
 | 
| +# The display name of the bucket where we put symbols without path.
 | 
| +NAME_NO_PATH_BUCKET = '(No Path)'
 | 
| +
 | 
| +# Try to keep data buckets smaller than this to avoid killing the
 | 
| +# graphing lib.
 | 
| +BIG_BUCKET_LIMIT = 3000
 | 
| +
 | 
| +
 | 
|  # TODO(andrewhayden): Only used for legacy reports. Delete.
 | 
|  def FormatBytes(byte_count):
 | 
|    """Pretty-print a number of bytes."""
 | 
| @@ -63,41 +82,101 @@ def SymbolTypeToHuman(symbol_type):
 | 
|  
 | 
|  
 | 
|  def _MkChild(node, name):
 | 
| -  child = node['children'].get(name)
 | 
| +  child = node[NODE_CHILDREN_KEY].get(name)
 | 
|    if child is None:
 | 
| -    child = {'n': name, 'children': {}}
 | 
| -    node['children'][name] = child
 | 
| +    child = {NODE_NAME_KEY: name,
 | 
| +             NODE_CHILDREN_KEY: {}}
 | 
| +    node[NODE_CHILDREN_KEY][name] = child
 | 
|    return child
 | 
|  
 | 
|  
 | 
| +
 | 
| +def SplitNoPathBucket(node):
 | 
| +  """NAME_NO_PATH_BUCKET can be too large for the graphing lib to
 | 
| +  handle. Split it into sub-buckets in that case."""
 | 
| +  root_children = node[NODE_CHILDREN_KEY]
 | 
| +  if NAME_NO_PATH_BUCKET in root_children:
 | 
| +    no_path_bucket = root_children[NAME_NO_PATH_BUCKET]
 | 
| +    old_children = no_path_bucket[NODE_CHILDREN_KEY]
 | 
| +    count = 0
 | 
| +    for symbol_type, symbol_bucket in old_children.iteritems():
 | 
| +      count += len(symbol_bucket[NODE_CHILDREN_KEY])
 | 
| +    if count > BIG_BUCKET_LIMIT:
 | 
| +      new_children = {}
 | 
| +      no_path_bucket[NODE_CHILDREN_KEY] = new_children
 | 
| +      current_bucket = None
 | 
| +      index = 0
 | 
| +      for symbol_type, symbol_bucket in old_children.iteritems():
 | 
| +        for symbol_name, value in symbol_bucket[NODE_CHILDREN_KEY].iteritems():
 | 
| +          if index % BIG_BUCKET_LIMIT == 0:
 | 
| +            group_no = (index / BIG_BUCKET_LIMIT) + 1
 | 
| +            current_bucket = _MkChild(no_path_bucket,
 | 
| +                                      '%s subgroup %d' % (NAME_NO_PATH_BUCKET,
 | 
| +                                                          group_no))
 | 
| +            assert not NODE_TYPE_KEY in node or node[NODE_TYPE_KEY] == 'p'
 | 
| +            node[NODE_TYPE_KEY] = 'p'  # p for path
 | 
| +          index += 1
 | 
| +          symbol_size = value[NODE_SYMBOL_SIZE_KEY]
 | 
| +          AddSymbolIntoFileNode(current_bucket, symbol_type,
 | 
| +                                symbol_name, symbol_size)
 | 
| +
 | 
| +
 | 
|  def MakeChildrenDictsIntoLists(node):
 | 
|    largest_list_len = 0
 | 
| -  if 'children' in node:
 | 
| -    largest_list_len = len(node['children'])
 | 
| +  if NODE_CHILDREN_KEY in node:
 | 
| +    largest_list_len = len(node[NODE_CHILDREN_KEY])
 | 
|      child_list = []
 | 
| -    for child in node['children'].itervalues():
 | 
| +    for child in node[NODE_CHILDREN_KEY].itervalues():
 | 
|        child_largest_list_len = MakeChildrenDictsIntoLists(child)
 | 
|        if child_largest_list_len > largest_list_len:
 | 
|          largest_list_len = child_largest_list_len
 | 
|        child_list.append(child)
 | 
| -    node['children'] = child_list
 | 
| +    node[NODE_CHILDREN_KEY] = child_list
 | 
|  
 | 
|    return largest_list_len
 | 
|  
 | 
|  
 | 
| +def AddSymbolIntoFileNode(node, symbol_type, symbol_name, symbol_size):
 | 
| +  """Puts symbol into the file path node |node|.
 | 
| +  Returns the number of added levels in tree. I.e. returns 2."""
 | 
| +
 | 
| +  # 'node' is the file node and first step is to find its symbol-type bucket.
 | 
| +  node[NODE_LAST_PATH_ELEMENT_KEY] = True
 | 
| +  node = _MkChild(node, symbol_type)
 | 
| +  assert not NODE_TYPE_KEY in node or node[NODE_TYPE_KEY] == 'b'
 | 
| +  node[NODE_SYMBOL_TYPE_KEY] = symbol_type
 | 
| +  node[NODE_TYPE_KEY] = 'b'  # b for bucket
 | 
| +
 | 
| +  # 'node' is now the symbol-type bucket. Make the child entry.
 | 
| +  node = _MkChild(node, symbol_name)
 | 
| +  if NODE_CHILDREN_KEY in node:
 | 
| +    if node[NODE_CHILDREN_KEY]:
 | 
| +      logging.warning('A container node used as symbol for %s.' % symbol_name)
 | 
| +    # This is going to be used as a leaf so no use for child list.
 | 
| +    del node[NODE_CHILDREN_KEY]
 | 
| +  node[NODE_SYMBOL_SIZE_KEY] = symbol_size
 | 
| +  node[NODE_SYMBOL_TYPE_KEY] = symbol_type
 | 
| +  node[NODE_TYPE_KEY] = 's'  # s for symbol
 | 
| +
 | 
| +  return 2  # Depth of the added subtree.
 | 
| +
 | 
| +
 | 
|  def MakeCompactTree(symbols):
 | 
| -  result = {'n': '/', 'children': {}, 'k': 'p', 'maxDepth': 0}
 | 
| +  result = {NODE_NAME_KEY: '/',
 | 
| +            NODE_CHILDREN_KEY: {},
 | 
| +            NODE_TYPE_KEY: 'p',
 | 
| +            NODE_MAX_DEPTH_KEY: 0}
 | 
|    seen_symbol_with_path = False
 | 
|    for symbol_name, symbol_type, symbol_size, file_path in symbols:
 | 
|  
 | 
|      if 'vtable for ' in symbol_name:
 | 
| -      symbol_type = '@' # hack to categorize these separately
 | 
| +      symbol_type = '@'  # hack to categorize these separately
 | 
|      # Take path like '/foo/bar/baz', convert to ['foo', 'bar', 'baz']
 | 
|      if file_path:
 | 
|        file_path = os.path.normpath(file_path)
 | 
|        seen_symbol_with_path = True
 | 
|      else:
 | 
| -      file_path = '(No Path)'
 | 
| +      file_path = NAME_NO_PATH_BUCKET
 | 
|  
 | 
|      if file_path.startswith('/'):
 | 
|        file_path = file_path[1:]
 | 
| @@ -112,36 +191,22 @@ def MakeCompactTree(symbols):
 | 
|          continue
 | 
|        depth += 1
 | 
|        node = _MkChild(node, path_part)
 | 
| -      assert not 'k' in node or node['k'] == 'p'
 | 
| -      node['k'] = 'p' # p for path
 | 
| -
 | 
| -    # 'node' is now the file node. Find the symbol-type bucket.
 | 
| -    node['lastPathElement'] = True
 | 
| -    node = _MkChild(node, symbol_type)
 | 
| -    assert not 'k' in node or node['k'] == 'b'
 | 
| -    node['t'] = symbol_type
 | 
| -    node['k'] = 'b' # b for bucket
 | 
| -    depth += 1
 | 
| -
 | 
| -    # 'node' is now the symbol-type bucket. Make the child entry.
 | 
| -    node = _MkChild(node, symbol_name)
 | 
| -    if 'children' in node:
 | 
| -      if node['children']:
 | 
| -        logging.warning('A container node used as symbol for %s.' % symbol_name)
 | 
| -      # This is going to be used as a leaf so no use for child list.
 | 
| -      del node['children']
 | 
| -    node['value'] = symbol_size
 | 
| -    node['t'] = symbol_type
 | 
| -    node['k'] = 's' # s for symbol
 | 
| -    depth += 1
 | 
| -    result['maxDepth'] = max(result['maxDepth'], depth)
 | 
| +      assert not NODE_TYPE_KEY in node or node[NODE_TYPE_KEY] == 'p'
 | 
| +      node[NODE_TYPE_KEY] = 'p'  # p for path
 | 
| +
 | 
| +    depth += AddSymbolIntoFileNode(node, symbol_type, symbol_name, symbol_size)
 | 
| +    result[NODE_MAX_DEPTH_KEY] = max(result[NODE_MAX_DEPTH_KEY], depth)
 | 
|  
 | 
|    if not seen_symbol_with_path:
 | 
|      logging.warning('Symbols lack paths. Data will not be structured.')
 | 
|  
 | 
| +  # The (no path) bucket can be extremely large if we failed to get
 | 
| +  # path information. Split it into subgroups if needed.
 | 
| +  SplitNoPathBucket(result)
 | 
| +
 | 
|    largest_list_len = MakeChildrenDictsIntoLists(result)
 | 
|  
 | 
| -  if largest_list_len > 1000:
 | 
| +  if largest_list_len > BIG_BUCKET_LIMIT:
 | 
|      logging.warning('There are sections with %d nodes. '
 | 
|                      'Results might be unusable.' % largest_list_len)
 | 
|    return result
 | 
| 
 |