OLD | NEW |
---|---|
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 43 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
54 # TODO(andrewhayden): Only used for legacy reports. Delete. | 54 # TODO(andrewhayden): Only used for legacy reports. Delete. |
55 def SymbolTypeToHuman(symbol_type): | 55 def SymbolTypeToHuman(symbol_type): |
56 """Convert a symbol type as printed by nm into a human-readable name.""" | 56 """Convert a symbol type as printed by nm into a human-readable name.""" |
57 return {'b': 'bss', | 57 return {'b': 'bss', |
58 'd': 'data', | 58 'd': 'data', |
59 'r': 'read-only data', | 59 'r': 'read-only data', |
60 't': 'code', | 60 't': 'code', |
61 'w': 'weak symbol', | 61 'w': 'weak symbol', |
62 'v': 'weak symbol'}[symbol_type] | 62 'v': 'weak symbol'}[symbol_type] |
63 | 63 |
64 # Node dictionary keys. These are output in json read by the webapp so | |
65 # keep them short to save file size. | |
66 # Note: If these change, the webapp must also change. | |
67 NODE_TYPE_KEY = 'k' | |
Primiano Tucci (use gerrit)
2014/05/27 09:24:17
Globals and constatns should go between imports an
Daniel Bratell
2014/06/04 13:04:45
Done.
| |
68 NODE_NAME_KEY = 'n' | |
69 NODE_CHILDREN_KEY = 'children' | |
70 NODE_SYMBOL_TYPE_KEY = 't' | |
71 NODE_SYMBOL_SIZE_KEY = 'value' | |
72 NODE_MAX_DEPTH_KEY = 'maxDepth' | |
73 NODE_LAST_PATH_ELEMENT_KEY = 'lastPathElement' | |
64 | 74 |
65 def _MkChild(node, name): | 75 def _MkChild(node, name): |
66 child = node['children'].get(name) | 76 child = node[NODE_CHILDREN_KEY].get(name) |
Primiano Tucci (use gerrit)
2014/05/27 09:24:17
To be honest, sounds like your nodes should start
Daniel Bratell
2014/06/04 13:04:45
I agree, but I'd prefer to postpone that change to
| |
67 if child is None: | 77 if child is None: |
68 child = {'n': name, 'children': {}} | 78 child = {NODE_NAME_KEY: name, |
69 node['children'][name] = child | 79 NODE_CHILDREN_KEY: {}} |
80 node[NODE_CHILDREN_KEY][name] = child | |
70 return child | 81 return child |
71 | 82 |
72 | 83 |
84 BIG_BUCKET_LIMIT = 2000 | |
Primiano Tucci (use gerrit)
2014/05/27 09:24:17
Ditto. Globals should live all together happily in
Andrew Hayden (chromium.org)
2014/05/27 10:04:11
Minor nit, the review says 3k and this constant is
Daniel Bratell
2014/06/04 13:04:45
Done.
Daniel Bratell
2014/06/04 13:04:45
Done.
| |
85 | |
86 | |
87 def SplitNoPathBucket(node): | |
88 """(No Path) can be too large for the graphing lib to handle. Split | |
89 it into sub-buckets in that case.""" | |
90 root_children = node[NODE_CHILDREN_KEY] | |
91 if '(No Path)' in root_children: | |
Andrew Hayden (chromium.org)
2014/05/27 10:04:11
We should probably extract the string "(No Path)"
Daniel Bratell
2014/06/04 13:04:45
Done.
| |
92 no_path_bucket = root_children['(No Path)'] | |
93 old_children = no_path_bucket[NODE_CHILDREN_KEY] | |
94 count = 0 | |
95 for symbol_type, symbol_bucket in old_children.iteritems(): | |
96 count += len(symbol_bucket[NODE_CHILDREN_KEY]) | |
97 if count > BIG_BUCKET_LIMIT: | |
98 new_children = {} | |
99 no_path_bucket[NODE_CHILDREN_KEY] = new_children | |
100 current_bucket = None | |
101 index = 0 | |
102 for symbol_type, symbol_bucket in old_children.iteritems(): | |
103 for symbol_name, value in symbol_bucket[NODE_CHILDREN_KEY].iteritems(): | |
104 if index % BIG_BUCKET_LIMIT == 0: | |
105 group_no = (index / BIG_BUCKET_LIMIT) + 1 | |
106 current_bucket = _MkChild(no_path_bucket, | |
107 '(No Path) subgroup %d' % group_no) | |
108 assert not NODE_TYPE_KEY in node or node[NODE_TYPE_KEY] == 'p' | |
109 node[NODE_TYPE_KEY] = 'p' # p for path | |
110 index += 1 | |
111 symbol_size = value[NODE_SYMBOL_SIZE_KEY] | |
112 AddSymbolIntoFileNode(current_bucket, symbol_type, | |
113 symbol_name, symbol_size) | |
114 no_path_bucket[NODE_CHILDREN_KEY] = new_children | |
Primiano Tucci (use gerrit)
2014/05/27 09:24:17
Uh? Looks like you already did this on line 99? Or
Daniel Bratell
2014/06/04 13:04:45
Yes, duplicate assignments. Fixed.
| |
115 | |
116 | |
73 def MakeChildrenDictsIntoLists(node): | 117 def MakeChildrenDictsIntoLists(node): |
74 largest_list_len = 0 | 118 largest_list_len = 0 |
75 if 'children' in node: | 119 if NODE_CHILDREN_KEY in node: |
76 largest_list_len = len(node['children']) | 120 largest_list_len = len(node[NODE_CHILDREN_KEY]) |
77 child_list = [] | 121 child_list = [] |
78 for child in node['children'].itervalues(): | 122 for child in node[NODE_CHILDREN_KEY].itervalues(): |
79 child_largest_list_len = MakeChildrenDictsIntoLists(child) | 123 child_largest_list_len = MakeChildrenDictsIntoLists(child) |
80 if child_largest_list_len > largest_list_len: | 124 if child_largest_list_len > largest_list_len: |
81 largest_list_len = child_largest_list_len | 125 largest_list_len = child_largest_list_len |
82 child_list.append(child) | 126 child_list.append(child) |
83 node['children'] = child_list | 127 node[NODE_CHILDREN_KEY] = child_list |
84 | 128 |
85 return largest_list_len | 129 return largest_list_len |
86 | 130 |
87 | 131 |
132 def AddSymbolIntoFileNode(node, symbol_type, symbol_name, symbol_size): | |
133 """Puts symbol into the file path node |node|. Returns the number of | |
Primiano Tucci (use gerrit)
2014/05/27 09:24:17
Nit: the first line of a docstring should fit in o
Daniel Bratell
2014/06/04 13:04:45
They are always of height 2. :-)
This is extracte
| |
134 added levels in tree. I.e. returns 2.""" | |
135 | |
136 depth = 0 | |
Andrew Hayden (chromium.org)
2014/05/27 10:04:11
From a clarity standpoint I'd prefer if we avoid t
Daniel Bratell
2014/06/04 13:04:45
Done.
| |
137 | |
138 # 'node' is now the file node. Find the symbol-type bucket. | |
Andrew Hayden (chromium.org)
2014/05/27 10:04:11
The first part of this comment made sense in-situ,
Daniel Bratell
2014/06/04 13:04:45
Done.
| |
139 node[NODE_LAST_PATH_ELEMENT_KEY] = True | |
140 node = _MkChild(node, symbol_type) | |
141 assert not NODE_TYPE_KEY in node or node[NODE_TYPE_KEY] == 'b' | |
142 node[NODE_SYMBOL_TYPE_KEY] = symbol_type | |
143 node[NODE_TYPE_KEY] = 'b' # b for bucket | |
144 depth += 1 | |
145 | |
146 # 'node' is now the symbol-type bucket. Make the child entry. | |
147 node = _MkChild(node, symbol_name) | |
148 if NODE_CHILDREN_KEY in node: | |
149 if node[NODE_CHILDREN_KEY]: | |
150 logging.warning('A container node used as symbol for %s.' % symbol_name) | |
151 # This is going to be used as a leaf so no use for child list. | |
152 del node[NODE_CHILDREN_KEY] | |
153 node[NODE_SYMBOL_SIZE_KEY] = symbol_size | |
154 node[NODE_SYMBOL_TYPE_KEY] = symbol_type | |
155 node[NODE_TYPE_KEY] = 's' # s for symbol | |
156 depth += 1 | |
157 return depth | |
158 | |
159 | |
88 def MakeCompactTree(symbols): | 160 def MakeCompactTree(symbols): |
89 result = {'n': '/', 'children': {}, 'k': 'p', 'maxDepth': 0} | 161 result = {NODE_NAME_KEY: '/', |
162 NODE_CHILDREN_KEY: {}, | |
163 NODE_TYPE_KEY: 'p', | |
164 NODE_MAX_DEPTH_KEY: 0} | |
90 seen_symbol_with_path = False | 165 seen_symbol_with_path = False |
91 for symbol_name, symbol_type, symbol_size, file_path in symbols: | 166 for symbol_name, symbol_type, symbol_size, file_path in symbols: |
92 | 167 |
93 if 'vtable for ' in symbol_name: | 168 if 'vtable for ' in symbol_name: |
94 symbol_type = '@' # hack to categorize these separately | 169 symbol_type = '@' # hack to categorize these separately |
95 # Take path like '/foo/bar/baz', convert to ['foo', 'bar', 'baz'] | 170 # Take path like '/foo/bar/baz', convert to ['foo', 'bar', 'baz'] |
96 if file_path: | 171 if file_path: |
97 file_path = os.path.normpath(file_path) | 172 file_path = os.path.normpath(file_path) |
98 seen_symbol_with_path = True | 173 seen_symbol_with_path = True |
99 else: | 174 else: |
100 file_path = '(No Path)' | 175 file_path = '(No Path)' |
101 | 176 |
102 if file_path.startswith('/'): | 177 if file_path.startswith('/'): |
103 file_path = file_path[1:] | 178 file_path = file_path[1:] |
104 path_parts = file_path.split('/') | 179 path_parts = file_path.split('/') |
105 | 180 |
106 # Find pre-existing node in tree, or update if it already exists | 181 # Find pre-existing node in tree, or update if it already exists |
107 node = result | 182 node = result |
108 depth = 0 | 183 depth = 0 |
109 while len(path_parts) > 0: | 184 while len(path_parts) > 0: |
110 path_part = path_parts.pop(0) | 185 path_part = path_parts.pop(0) |
111 if len(path_part) == 0: | 186 if len(path_part) == 0: |
112 continue | 187 continue |
113 depth += 1 | 188 depth += 1 |
114 node = _MkChild(node, path_part) | 189 node = _MkChild(node, path_part) |
115 assert not 'k' in node or node['k'] == 'p' | 190 assert not NODE_TYPE_KEY in node or node[NODE_TYPE_KEY] == 'p' |
116 node['k'] = 'p' # p for path | 191 node[NODE_TYPE_KEY] = 'p' # p for path |
Primiano Tucci (use gerrit)
2014/05/27 09:24:17
Nit: add an extra space before #
| |
117 | 192 |
118 # 'node' is now the file node. Find the symbol-type bucket. | 193 depth += AddSymbolIntoFileNode(node, symbol_type, symbol_name, symbol_size) |
119 node['lastPathElement'] = True | 194 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 | 195 |
139 if not seen_symbol_with_path: | 196 if not seen_symbol_with_path: |
140 logging.warning('Symbols lack paths. Data will not be structured.') | 197 logging.warning('Symbols lack paths. Data will not be structured.') |
141 | 198 |
199 # The (no path) bucket can be extremely large if we failed to get | |
200 # path information. Split it into subgroups if needed. | |
201 SplitNoPathBucket(result) | |
202 | |
142 largest_list_len = MakeChildrenDictsIntoLists(result) | 203 largest_list_len = MakeChildrenDictsIntoLists(result) |
143 | 204 |
144 if largest_list_len > 1000: | 205 if largest_list_len > BIG_BUCKET_LIMIT: |
145 logging.warning('There are sections with %d nodes. ' | 206 logging.warning('There are sections with %d nodes. ' |
146 'Results might be unusable.' % largest_list_len) | 207 'Results might be unusable.' % largest_list_len) |
147 return result | 208 return result |
148 | 209 |
149 | 210 |
150 # TODO(andrewhayden): Only used for legacy reports. Delete. | 211 # TODO(andrewhayden): Only used for legacy reports. Delete. |
151 def TreeifySymbols(symbols): | 212 def TreeifySymbols(symbols): |
152 """Convert symbols into a path-based tree, calculating size information | 213 """Convert symbols into a path-based tree, calculating size information |
153 along the way. | 214 along the way. |
154 | 215 |
(...skipping 526 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
681 print('Copying index.html') | 742 print('Copying index.html') |
682 shutil.copy(os.path.join(template_src, 'index.html'), opts.destdir) | 743 shutil.copy(os.path.join(template_src, 'index.html'), opts.destdir) |
683 shutil.copy(os.path.join(template_src, 'D3SymbolTreeMap.js'), opts.destdir) | 744 shutil.copy(os.path.join(template_src, 'D3SymbolTreeMap.js'), opts.destdir) |
684 | 745 |
685 if opts.verbose: | 746 if opts.verbose: |
686 print 'Report saved to ' + opts.destdir + '/index.html' | 747 print 'Report saved to ' + opts.destdir + '/index.html' |
687 | 748 |
688 | 749 |
689 if __name__ == '__main__': | 750 if __name__ == '__main__': |
690 sys.exit(main()) | 751 sys.exit(main()) |
OLD | NEW |