Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(65)

Side by Side Diff: tools/binary_size/run_binary_size_analysis.py

Issue 302443006: binary_size: Avoid creating nodes with many thousand children. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Split (no path) Rebased to newer master Created 6 years, 6 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « no previous file | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 #!/usr/bin/env python 1 #!/usr/bin/env python
2 # Copyright 2014 The Chromium Authors. All rights reserved. 2 # Copyright 2014 The Chromium Authors. All rights reserved.
3 # Use of this source code is governed by a BSD-style license that can be 3 # Use of this source code is governed by a BSD-style license that can be
4 # found in the LICENSE file. 4 # found in the LICENSE file.
5 5
6 """Generate a spatial analysis against an arbitrary library. 6 """Generate a spatial analysis against an arbitrary library.
7 7
8 To use, build the 'binary_size_tool' target. Then run this tool, passing 8 To use, build the 'binary_size_tool' target. Then run this tool, passing
9 in the location of the library to be analyzed along with any other options 9 in the location of the library to be analyzed along with any other options
10 you desire. 10 you desire.
(...skipping 21 matching lines...) Expand all
32 os.path.dirname(__file__), 32 os.path.dirname(__file__),
33 '..', 33 '..',
34 '..', 34 '..',
35 'build', 35 'build',
36 'android', 36 'android',
37 'pylib')) 37 'pylib'))
38 sys.path.append(elf_symbolizer_path) 38 sys.path.append(elf_symbolizer_path)
39 import symbols.elf_symbolizer as elf_symbolizer # pylint: disable=F0401 39 import symbols.elf_symbolizer as elf_symbolizer # pylint: disable=F0401
40 40
41 41
42 # Node dictionary keys. These are output in json read by the webapp so
43 # keep them short to save file size.
44 # Note: If these change, the webapp must also change.
45 NODE_TYPE_KEY = 'k'
46 NODE_NAME_KEY = 'n'
47 NODE_CHILDREN_KEY = 'children'
48 NODE_SYMBOL_TYPE_KEY = 't'
49 NODE_SYMBOL_SIZE_KEY = 'value'
50 NODE_MAX_DEPTH_KEY = 'maxDepth'
51 NODE_LAST_PATH_ELEMENT_KEY = 'lastPathElement'
52
53 # The display name of the bucket where we put symbols without path.
54 NAME_NO_PATH_BUCKET = '(No Path)'
55
56 # Try to keep data buckets smaller than this to avoid killing the
57 # graphing lib.
58 BIG_BUCKET_LIMIT = 3000
59
60
42 # TODO(andrewhayden): Only used for legacy reports. Delete. 61 # TODO(andrewhayden): Only used for legacy reports. Delete.
43 def FormatBytes(byte_count): 62 def FormatBytes(byte_count):
44 """Pretty-print a number of bytes.""" 63 """Pretty-print a number of bytes."""
45 if byte_count > 1e6: 64 if byte_count > 1e6:
46 byte_count = byte_count / 1.0e6 65 byte_count = byte_count / 1.0e6
47 return '%.1fm' % byte_count 66 return '%.1fm' % byte_count
48 if byte_count > 1e3: 67 if byte_count > 1e3:
49 byte_count = byte_count / 1.0e3 68 byte_count = byte_count / 1.0e3
50 return '%.1fk' % byte_count 69 return '%.1fk' % byte_count
51 return str(byte_count) 70 return str(byte_count)
52 71
53 72
54 # TODO(andrewhayden): Only used for legacy reports. Delete. 73 # TODO(andrewhayden): Only used for legacy reports. Delete.
55 def SymbolTypeToHuman(symbol_type): 74 def SymbolTypeToHuman(symbol_type):
56 """Convert a symbol type as printed by nm into a human-readable name.""" 75 """Convert a symbol type as printed by nm into a human-readable name."""
57 return {'b': 'bss', 76 return {'b': 'bss',
58 'd': 'data', 77 'd': 'data',
59 'r': 'read-only data', 78 'r': 'read-only data',
60 't': 'code', 79 't': 'code',
61 'w': 'weak symbol', 80 'w': 'weak symbol',
62 'v': 'weak symbol'}[symbol_type] 81 'v': 'weak symbol'}[symbol_type]
63 82
64 83
65 def _MkChild(node, name): 84 def _MkChild(node, name):
66 child = node['children'].get(name) 85 child = node[NODE_CHILDREN_KEY].get(name)
67 if child is None: 86 if child is None:
68 child = {'n': name, 'children': {}} 87 child = {NODE_NAME_KEY: name,
69 node['children'][name] = child 88 NODE_CHILDREN_KEY: {}}
89 node[NODE_CHILDREN_KEY][name] = child
70 return child 90 return child
71 91
72 92
93
94 def SplitNoPathBucket(node):
95 """NAME_NO_PATH_BUCKET can be too large for the graphing lib to
96 handle. Split it into sub-buckets in that case."""
97 root_children = node[NODE_CHILDREN_KEY]
98 if NAME_NO_PATH_BUCKET in root_children:
99 no_path_bucket = root_children[NAME_NO_PATH_BUCKET]
100 old_children = no_path_bucket[NODE_CHILDREN_KEY]
101 count = 0
102 for symbol_type, symbol_bucket in old_children.iteritems():
103 count += len(symbol_bucket[NODE_CHILDREN_KEY])
104 if count > BIG_BUCKET_LIMIT:
105 new_children = {}
106 no_path_bucket[NODE_CHILDREN_KEY] = new_children
107 current_bucket = None
108 index = 0
109 for symbol_type, symbol_bucket in old_children.iteritems():
110 for symbol_name, value in symbol_bucket[NODE_CHILDREN_KEY].iteritems():
111 if index % BIG_BUCKET_LIMIT == 0:
112 group_no = (index / BIG_BUCKET_LIMIT) + 1
113 current_bucket = _MkChild(no_path_bucket,
114 '%s subgroup %d' % (NAME_NO_PATH_BUCKET,
115 group_no))
116 assert not NODE_TYPE_KEY in node or node[NODE_TYPE_KEY] == 'p'
117 node[NODE_TYPE_KEY] = 'p' # p for path
118 index += 1
119 symbol_size = value[NODE_SYMBOL_SIZE_KEY]
120 AddSymbolIntoFileNode(current_bucket, symbol_type,
121 symbol_name, symbol_size)
122
123
73 def MakeChildrenDictsIntoLists(node): 124 def MakeChildrenDictsIntoLists(node):
74 largest_list_len = 0 125 largest_list_len = 0
75 if 'children' in node: 126 if NODE_CHILDREN_KEY in node:
76 largest_list_len = len(node['children']) 127 largest_list_len = len(node[NODE_CHILDREN_KEY])
77 child_list = [] 128 child_list = []
78 for child in node['children'].itervalues(): 129 for child in node[NODE_CHILDREN_KEY].itervalues():
79 child_largest_list_len = MakeChildrenDictsIntoLists(child) 130 child_largest_list_len = MakeChildrenDictsIntoLists(child)
80 if child_largest_list_len > largest_list_len: 131 if child_largest_list_len > largest_list_len:
81 largest_list_len = child_largest_list_len 132 largest_list_len = child_largest_list_len
82 child_list.append(child) 133 child_list.append(child)
83 node['children'] = child_list 134 node[NODE_CHILDREN_KEY] = child_list
84 135
85 return largest_list_len 136 return largest_list_len
86 137
87 138
139 def AddSymbolIntoFileNode(node, symbol_type, symbol_name, symbol_size):
140 """Puts symbol into the file path node |node|.
141 Returns the number of added levels in tree. I.e. returns 2."""
142
143 # 'node' is the file node and first step is to find its symbol-type bucket.
144 node[NODE_LAST_PATH_ELEMENT_KEY] = True
145 node = _MkChild(node, symbol_type)
146 assert not NODE_TYPE_KEY in node or node[NODE_TYPE_KEY] == 'b'
147 node[NODE_SYMBOL_TYPE_KEY] = symbol_type
148 node[NODE_TYPE_KEY] = 'b' # b for bucket
149
150 # 'node' is now the symbol-type bucket. Make the child entry.
151 node = _MkChild(node, symbol_name)
152 if NODE_CHILDREN_KEY in node:
153 if node[NODE_CHILDREN_KEY]:
154 logging.warning('A container node used as symbol for %s.' % symbol_name)
155 # This is going to be used as a leaf so no use for child list.
156 del node[NODE_CHILDREN_KEY]
157 node[NODE_SYMBOL_SIZE_KEY] = symbol_size
158 node[NODE_SYMBOL_TYPE_KEY] = symbol_type
159 node[NODE_TYPE_KEY] = 's' # s for symbol
160
161 return 2 # Depth of the added subtree.
162
163
88 def MakeCompactTree(symbols): 164 def MakeCompactTree(symbols):
89 result = {'n': '/', 'children': {}, 'k': 'p', 'maxDepth': 0} 165 result = {NODE_NAME_KEY: '/',
166 NODE_CHILDREN_KEY: {},
167 NODE_TYPE_KEY: 'p',
168 NODE_MAX_DEPTH_KEY: 0}
90 seen_symbol_with_path = False 169 seen_symbol_with_path = False
91 for symbol_name, symbol_type, symbol_size, file_path in symbols: 170 for symbol_name, symbol_type, symbol_size, file_path in symbols:
92 171
93 if 'vtable for ' in symbol_name: 172 if 'vtable for ' in symbol_name:
94 symbol_type = '@' # hack to categorize these separately 173 symbol_type = '@' # hack to categorize these separately
95 # Take path like '/foo/bar/baz', convert to ['foo', 'bar', 'baz'] 174 # Take path like '/foo/bar/baz', convert to ['foo', 'bar', 'baz']
96 if file_path: 175 if file_path:
97 file_path = os.path.normpath(file_path) 176 file_path = os.path.normpath(file_path)
98 seen_symbol_with_path = True 177 seen_symbol_with_path = True
99 else: 178 else:
100 file_path = '(No Path)' 179 file_path = NAME_NO_PATH_BUCKET
101 180
102 if file_path.startswith('/'): 181 if file_path.startswith('/'):
103 file_path = file_path[1:] 182 file_path = file_path[1:]
104 path_parts = file_path.split('/') 183 path_parts = file_path.split('/')
105 184
106 # Find pre-existing node in tree, or update if it already exists 185 # Find pre-existing node in tree, or update if it already exists
107 node = result 186 node = result
108 depth = 0 187 depth = 0
109 while len(path_parts) > 0: 188 while len(path_parts) > 0:
110 path_part = path_parts.pop(0) 189 path_part = path_parts.pop(0)
111 if len(path_part) == 0: 190 if len(path_part) == 0:
112 continue 191 continue
113 depth += 1 192 depth += 1
114 node = _MkChild(node, path_part) 193 node = _MkChild(node, path_part)
115 assert not 'k' in node or node['k'] == 'p' 194 assert not NODE_TYPE_KEY in node or node[NODE_TYPE_KEY] == 'p'
116 node['k'] = 'p' # p for path 195 node[NODE_TYPE_KEY] = 'p' # p for path
117 196
118 # 'node' is now the file node. Find the symbol-type bucket. 197 depth += AddSymbolIntoFileNode(node, symbol_type, symbol_name, symbol_size)
119 node['lastPathElement'] = True 198 result[NODE_MAX_DEPTH_KEY] = max(result[NODE_MAX_DEPTH_KEY], depth)
120 node = _MkChild(node, symbol_type)
121 assert not 'k' in node or node['k'] == 'b'
122 node['t'] = symbol_type
123 node['k'] = 'b' # b for bucket
124 depth += 1
125
126 # 'node' is now the symbol-type bucket. Make the child entry.
127 node = _MkChild(node, symbol_name)
128 if 'children' in node:
129 if node['children']:
130 logging.warning('A container node used as symbol for %s.' % symbol_name)
131 # This is going to be used as a leaf so no use for child list.
132 del node['children']
133 node['value'] = symbol_size
134 node['t'] = symbol_type
135 node['k'] = 's' # s for symbol
136 depth += 1
137 result['maxDepth'] = max(result['maxDepth'], depth)
138 199
139 if not seen_symbol_with_path: 200 if not seen_symbol_with_path:
140 logging.warning('Symbols lack paths. Data will not be structured.') 201 logging.warning('Symbols lack paths. Data will not be structured.')
141 202
203 # The (no path) bucket can be extremely large if we failed to get
204 # path information. Split it into subgroups if needed.
205 SplitNoPathBucket(result)
206
142 largest_list_len = MakeChildrenDictsIntoLists(result) 207 largest_list_len = MakeChildrenDictsIntoLists(result)
143 208
144 if largest_list_len > 1000: 209 if largest_list_len > BIG_BUCKET_LIMIT:
145 logging.warning('There are sections with %d nodes. ' 210 logging.warning('There are sections with %d nodes. '
146 'Results might be unusable.' % largest_list_len) 211 'Results might be unusable.' % largest_list_len)
147 return result 212 return result
148 213
149 214
150 # TODO(andrewhayden): Only used for legacy reports. Delete. 215 # TODO(andrewhayden): Only used for legacy reports. Delete.
151 def TreeifySymbols(symbols): 216 def TreeifySymbols(symbols):
152 """Convert symbols into a path-based tree, calculating size information 217 """Convert symbols into a path-based tree, calculating size information
153 along the way. 218 along the way.
154 219
(...skipping 572 matching lines...) Expand 10 before | Expand all | Expand 10 after
727 shutil.copy(os.path.join(d3_src, 'LICENSE'), d3_out) 792 shutil.copy(os.path.join(d3_src, 'LICENSE'), d3_out)
728 shutil.copy(os.path.join(d3_src, 'd3.js'), d3_out) 793 shutil.copy(os.path.join(d3_src, 'd3.js'), d3_out)
729 shutil.copy(os.path.join(template_src, 'index.html'), opts.destdir) 794 shutil.copy(os.path.join(template_src, 'index.html'), opts.destdir)
730 shutil.copy(os.path.join(template_src, 'D3SymbolTreeMap.js'), opts.destdir) 795 shutil.copy(os.path.join(template_src, 'D3SymbolTreeMap.js'), opts.destdir)
731 796
732 print 'Report saved to ' + opts.destdir + '/index.html' 797 print 'Report saved to ' + opts.destdir + '/index.html'
733 798
734 799
735 if __name__ == '__main__': 800 if __name__ == '__main__':
736 sys.exit(main()) 801 sys.exit(main())
OLDNEW
« no previous file with comments | « no previous file | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698