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 21 matching lines...) Expand all Loading... |
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 Loading... |
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()) |
OLD | NEW |