OLD | NEW |
1 # Copyright 2013 The Chromium Authors. All rights reserved. | 1 # Copyright 2013 The Chromium Authors. All rights reserved. |
2 # Use of this source code is governed by a BSD-style license that can be | 2 # Use of this source code is governed by a BSD-style license that can be |
3 # found in the LICENSE file. | 3 # found in the LICENSE file. |
4 | 4 |
| 5 import base64 |
| 6 import codecs |
5 import json | 7 import json |
6 import os | 8 import os |
7 import string | 9 import string |
8 import subprocess | 10 import subprocess |
9 import sys | 11 import sys |
10 | 12 |
11 | 13 |
12 BASE_DIR = os.path.dirname(os.path.abspath(__file__)) | 14 BASE_DIR = os.path.dirname(os.path.abspath(__file__)) |
13 | 15 |
14 | 16 |
15 def Run(*args): | 17 def Run(*args): |
16 with open(os.devnull, 'w') as null: | 18 with open(os.devnull, 'w') as null: |
17 subprocess.check_call(args, stdout=null, stderr=null) | 19 subprocess.check_call(args, stdout=null, stderr=null) |
18 | 20 |
19 | 21 |
20 def FindNode(node, component): | 22 def FindNode(node, component): |
21 for child in node['children']: | 23 for child in node['children']: |
22 if child['name'] == component: | 24 if child['name'] == component: |
23 return child | 25 return child |
24 return None | 26 return None |
25 | 27 |
26 | 28 |
27 def InsertIntoTree(tree, source_name, size): | 29 def InsertIntoTree(tree, source_name, size): |
28 components = source_name.replace(':', '').split('\\') | 30 components = source_name[3:].split('\\') |
29 node = tree | 31 node = tree |
30 for index, component in enumerate(components): | 32 for index, component in enumerate(components): |
31 data = FindNode(node, component) | 33 data = FindNode(node, component) |
32 if not data: | 34 if not data: |
33 data = { 'name': component } | 35 data = { 'name': source_name, 'name': component } |
34 if index == len(components) - 1: | 36 if index == len(components) - 1: |
35 data['size'] = size | 37 data['size'] = size |
36 else: | 38 else: |
37 data['children'] = [] | 39 data['children'] = [] |
38 node['children'].append(data) | 40 node['children'].append(data) |
39 node = data | 41 node = data |
40 | 42 |
41 | 43 |
| 44 def FlattenTree(tree): |
| 45 result = [['Path', 'Parent', 'Size', 'Value']] |
| 46 def Flatten(node, parent): |
| 47 name = node['name'] |
| 48 if parent and parent != '/': |
| 49 name = parent + '/' + name |
| 50 if 'children' in node: |
| 51 result.append([name, parent, -1, -1]) |
| 52 for c in node['children']: |
| 53 Flatten(c, name) |
| 54 else: |
| 55 result.append([name, parent, node['size'], node['size']]) |
| 56 Flatten(tree, '') |
| 57 return result |
| 58 |
| 59 |
| 60 def GetAsset(filename): |
| 61 with open(os.path.join(BASE_DIR, filename), 'rb') as f: |
| 62 return f.read() |
| 63 |
| 64 |
| 65 def AppendAsScriptBlock(f, value, var=None): |
| 66 f.write('<script type="text/javascript">\n') |
| 67 if var: |
| 68 f.write('var ' + var + ' = ') |
| 69 f.write(value) |
| 70 if var: |
| 71 f.write(';\n') |
| 72 f.write('</script>\n') |
| 73 |
| 74 |
42 def main(): | 75 def main(): |
43 out_dir = os.path.join(BASE_DIR, '..', '..', '..', 'out', 'Release') | 76 out_dir = os.path.join(BASE_DIR, '..', '..', '..', 'out', 'Release') |
44 jsons = [] | 77 jsons = [] |
45 for dll in ('chrome.dll', 'chrome_child.dll'): | 78 for dll in ('chrome.dll', 'chrome_child.dll'): |
46 dll_path = os.path.normpath(os.path.join(out_dir, dll)) | 79 dll_path = os.path.normpath(os.path.join(out_dir, dll)) |
47 if os.path.exists(dll_path): | 80 if os.path.exists(dll_path): |
48 print 'Tallying %s...' % dll_path | 81 print 'Tallying %s...' % dll_path |
49 json_path = dll_path + '.json' | 82 json_path = dll_path + '.json' |
50 Run(os.path.join(BASE_DIR, 'code_tally.exe'), | 83 Run(os.path.join(BASE_DIR, 'code_tally.exe'), |
51 '--input-image=' + dll_path, | 84 '--input-image=' + dll_path, |
52 '--input-pdb=' + dll_path + '.pdb', | 85 '--input-pdb=' + dll_path + '.pdb', |
53 '--output-file=' + json_path) | 86 '--output-file=' + json_path) |
54 jsons.append(json_path) | 87 jsons.append(json_path) |
55 if not jsons: | 88 if not jsons: |
56 print 'Couldn\'t find binaries, looking in', out_dir | 89 print 'Couldn\'t find binaries, looking in', out_dir |
57 return 1 | 90 return 1 |
58 | 91 |
| 92 # Munge the code_tally json format into an easier-to-view format. |
59 for json_name in jsons: | 93 for json_name in jsons: |
60 with open(json_name, 'r') as jsonf: | 94 with open(json_name, 'r') as jsonf: |
61 all_data = json.load(jsonf) | 95 all_data = json.load(jsonf) |
62 html_path = os.path.splitext(json_name)[0] + '.html' | 96 html_path = os.path.splitext(json_name)[0] + '.html' |
63 print 'Generating %s...' % html_path | 97 print 'Generating %s... (standlone)' % html_path |
64 by_source = {} | 98 by_source = {} |
| 99 symbols_index = {} |
| 100 symbols = [] |
65 for obj_name, obj_data in all_data['objects'].iteritems(): | 101 for obj_name, obj_data in all_data['objects'].iteritems(): |
66 for symbol, symbol_data in obj_data.iteritems(): | 102 for symbol, symbol_data in obj_data.iteritems(): |
67 size = int(symbol_data['size']) | 103 size = int(symbol_data['size']) |
68 # Sometimes there's symbols with no source file, we just ignore those. | 104 # Sometimes there's symbols with no source file, we just ignore those. |
69 if 'contribs' in symbol_data: | 105 if 'contribs' in symbol_data: |
70 # There may be more than one file in the list, we just assign to the | 106 i = 0 |
71 # first source file that contains the symbol, rather than try to | 107 while i < len(symbol_data['contribs']): |
72 # split or duplicate info. | 108 src_index = symbol_data['contribs'][i] |
73 src_index = symbol_data['contribs'][0] | 109 i += 1 |
74 source = all_data['sources'][int(src_index)] | 110 per_line = symbol_data['contribs'][i] |
75 if source not in by_source: | 111 i += 1 |
76 by_source[source] = [] | 112 source = all_data['sources'][int(src_index)] |
77 by_source[source].append(size) | 113 if source not in by_source: |
| 114 by_source[source] = {'lines': {}, 'total_size': 0} |
| 115 size = 0 |
| 116 # per_line is [line, size, line, size, line, size, ...] |
| 117 for j in range(0, len(per_line), 2): |
| 118 line_number = per_line[j] |
| 119 size += per_line[j + 1] |
| 120 # Save some time/space in JS by using an array here. 0 == size, |
| 121 # 1 == symbol list. |
| 122 by_source[source]['lines'].setdefault(line_number, [0, []]) |
| 123 by_source[source]['lines'][line_number][0] += per_line[j + 1] |
| 124 if symbol in symbols_index: |
| 125 symindex = symbols_index[symbol] |
| 126 else: |
| 127 symbols.append(symbol) |
| 128 symbols_index[symbol] = symindex = len(symbols) - 1 |
| 129 by_source[source]['lines'][line_number][1].append( |
| 130 symindex) |
| 131 by_source[source]['total_size'] += size |
78 binary_name = all_data['executable']['name'] | 132 binary_name = all_data['executable']['name'] |
79 data = {} | 133 data = {} |
80 data['name'] = binary_name | 134 data['name'] = '/' |
81 data['children'] = [] | 135 data['children'] = [] |
82 for source, sizes in by_source.iteritems(): | 136 file_contents = {} |
83 InsertIntoTree(data, source, sum(sizes)) | 137 line_data = {} |
| 138 for source, file_data in by_source.iteritems(): |
| 139 InsertIntoTree(data, source, file_data['total_size']) |
| 140 |
| 141 store_as = source[3:].replace('\\', '/') |
| 142 try: |
| 143 with codecs.open(source, 'rb', encoding='latin1') as f: |
| 144 file_contents[store_as] = f.read() |
| 145 except IOError: |
| 146 file_contents[store_as] = '// Unable to load source.' |
| 147 |
| 148 line_data[store_as] = file_data['lines'] |
| 149 # code_tally attempts to assign fractional bytes when code is shared |
| 150 # across multiple symbols. Round off here for display after summing above. |
| 151 for per_line in line_data[store_as].values(): |
| 152 per_line[0] = round(per_line[0]) |
| 153 |
| 154 flattened = FlattenTree(data) |
| 155 maxval = 0 |
| 156 for i in flattened[1:]: |
| 157 maxval = max(i[2], maxval) |
| 158 flattened_str = json.dumps(flattened) |
| 159 |
| 160 to_write = GetAsset('template.html') |
| 161 # Save all data and what would normally be external resources into the |
| 162 # one html so that it's a standalone report. |
84 with open(html_path, 'w') as f: | 163 with open(html_path, 'w') as f: |
85 with open(os.path.join(BASE_DIR, 'template.html'), 'r') as templatef: | 164 f.write(to_write) |
86 template = templatef.read() | 165 # These aren't subbed in as a silly workaround for 32-bit python. |
87 f.write(string.Template(template).substitute( | 166 # The end result is only ~100M, but while substituting these into a |
88 {'data': json.dumps(data, indent=2), | 167 # template, it otherwise raises a MemoryError, I guess due to |
89 'dllname': binary_name + ' ' + all_data['executable']['version']})) | 168 # fragmentation. So instead, we just append them as variables to the file |
| 169 # and then refer to the variables in the main script. |
| 170 filedata_str = json.dumps(file_contents).replace( |
| 171 '</script>', '</scr"+"ipt>') |
| 172 AppendAsScriptBlock(f, filedata_str, var='g_file_contents') |
| 173 AppendAsScriptBlock(f, json.dumps(line_data), var='g_line_data') |
| 174 AppendAsScriptBlock(f, json.dumps(symbols), var='g_symbol_list') |
| 175 favicon_str = json.dumps(base64.b64encode(GetAsset('favicon.png'))) |
| 176 AppendAsScriptBlock(f, favicon_str, var='g_favicon') |
| 177 AppendAsScriptBlock(f, flattened_str, var='g_raw_data') |
| 178 AppendAsScriptBlock(f, str(maxval), var='g_maxval') |
| 179 dllname_str = binary_name + ' ' + all_data['executable']['version'] |
| 180 AppendAsScriptBlock(f, json.dumps(dllname_str), var='g_dllname') |
| 181 AppendAsScriptBlock(f, GetAsset('codemirror.js')) |
| 182 AppendAsScriptBlock(f, GetAsset('clike.js')) |
| 183 AppendAsScriptBlock(f, GetAsset('main.js')) |
| 184 f.write('</html>') |
90 | 185 |
91 return 0 | 186 return 0 |
92 | 187 |
93 | 188 |
94 if __name__ == '__main__': | 189 if __name__ == '__main__': |
95 sys.exit(main()) | 190 sys.exit(main()) |
OLD | NEW |