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