llvm.org GIT mirror llvm / cf77b92
Add a utility script to stress test the demangler. git-svn-id: https://llvm.org/svn/llvm-project/llvm/trunk@341120 91177308-0d34-0410-b5e6-96231b3b80d8 Zachary Turner 1 year, 19 days ago
1 changed file(s) with 226 addition(s) and 0 deletion(s). Raw diff Collapse all Expand all
0 # Given a path to llvm-objdump and a directory tree, spider the directory tree
1 # dumping every object file encountered with correct options needed to demangle
2 # symbols in the object file, and collect statistics about failed / crashed
3 # demanglings. Useful for stress testing the demangler against a large corpus
4 # of inputs.
5
6 import argparse
7 import functools
8 import os
9 import re
10 import sys
11 import subprocess
12 import traceback
13 from multiprocessing import Pool
14 import multiprocessing
15
16 args = None
17
18 def parse_line(line):
19 question = line.find('?')
20 if question == -1:
21 return None, None
22
23 open_paren = line.find('(', question)
24 if open_paren == -1:
25 return None, None
26 close_paren = line.rfind(')', open_paren)
27 if open_paren == -1:
28 return None, None
29 mangled = line[question : open_paren]
30 demangled = line[open_paren+1 : close_paren]
31 return mangled.strip(), demangled.strip()
32
33 class Result(object):
34 def __init__(self):
35 self.crashed = []
36 self.file = None
37 self.nsymbols = 0
38 self.errors = set()
39 self.nfiles = 0
40
41 class MapContext(object):
42 def __init__(self):
43 self.rincomplete = None
44 self.rcumulative = Result()
45 self.pending_objs = []
46 self.npending = 0
47
48 def process_file(path, objdump):
49 r = Result()
50 r.file = path
51
52 popen_args = [objdump, '-t', '-demangle', path]
53 p = subprocess.Popen(popen_args, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
54 stdout, stderr = p.communicate()
55 if p.returncode != 0:
56 r.crashed = [r.file]
57 return r
58
59 output = stdout.decode('utf-8')
60
61 for line in output.splitlines():
62 mangled, demangled = parse_line(line)
63 if mangled is None:
64 continue
65 r.nsymbols += 1
66 if "invalid mangled name" in demangled:
67 r.errors.add(mangled)
68 return r
69
70 def add_results(r1, r2):
71 r1.crashed.extend(r2.crashed)
72 r1.errors.update(r2.errors)
73 r1.nsymbols += r2.nsymbols
74 r1.nfiles += r2.nfiles
75
76 def print_result_row(directory, result):
77 print("[{0} files, {1} crashes, {2} errors, {3} symbols]: '{4}'".format(
78 result.nfiles, len(result.crashed), len(result.errors), result.nsymbols, directory))
79
80 def process_one_chunk(pool, chunk_size, objdump, context):
81 objs = []
82
83 incomplete = False
84 dir_results = {}
85 ordered_dirs = []
86 while context.npending > 0 and len(objs) < chunk_size:
87 this_dir = context.pending_objs[0][0]
88 ordered_dirs.append(this_dir)
89 re = Result()
90 if context.rincomplete is not None:
91 re = context.rincomplete
92 context.rincomplete = None
93
94 dir_results[this_dir] = re
95 re.file = this_dir
96
97 nneeded = chunk_size - len(objs)
98 objs_this_dir = context.pending_objs[0][1]
99 navail = len(objs_this_dir)
100 ntaken = min(nneeded, navail)
101 objs.extend(objs_this_dir[0:ntaken])
102 remaining_objs_this_dir = objs_this_dir[ntaken:]
103 context.pending_objs[0] = (context.pending_objs[0][0], remaining_objs_this_dir)
104 context.npending -= ntaken
105 if ntaken == navail:
106 context.pending_objs.pop(0)
107 else:
108 incomplete = True
109
110 re.nfiles += ntaken
111
112 assert(len(objs) == chunk_size or context.npending == 0)
113
114 copier = functools.partial(process_file, objdump=objdump)
115 mapped_results = list(pool.map(copier, objs))
116
117 for mr in mapped_results:
118 result_dir = os.path.dirname(mr.file)
119 result_entry = dir_results[result_dir]
120 add_results(result_entry, mr)
121
122 # It's only possible that a single item is incomplete, and it has to be the
123 # last item.
124 if incomplete:
125 context.rincomplete = dir_results[ordered_dirs[-1]]
126 ordered_dirs.pop()
127
128 # Now ordered_dirs contains a list of all directories which *did* complete.
129 for c in ordered_dirs:
130 re = dir_results[c]
131 add_results(context.rcumulative, re)
132 print_result_row(c, re)
133
134 def process_pending_files(pool, chunk_size, objdump, context):
135 while context.npending >= chunk_size:
136 process_one_chunk(pool, chunk_size, objdump, context)
137
138 def go():
139 global args
140
141 obj_dir = args.dir
142 extensions = args.extensions.split(',')
143 extensions = [x if x[0] == '.' else '.' + x for x in extensions]
144
145
146 pool_size = 48
147 pool = Pool(processes=pool_size)
148
149 try:
150 nfiles = 0
151 context = MapContext()
152
153 for root, dirs, files in os.walk(obj_dir):
154 root = os.path.normpath(root)
155 pending = []
156 for f in files:
157 file, ext = os.path.splitext(f)
158 if not ext in extensions:
159 continue
160
161 nfiles += 1
162 full_path = os.path.join(root, f)
163 full_path = os.path.normpath(full_path)
164 pending.append(full_path)
165
166 # If this directory had no object files, just print a default
167 # status line and continue with the next dir
168 if len(pending) == 0:
169 print_result_row(root, Result())
170 continue
171
172 context.npending += len(pending)
173 context.pending_objs.append((root, pending))
174 # Drain the tasks, `pool_size` at a time, until we have less than
175 # `pool_size` tasks remaining.
176 process_pending_files(pool, pool_size, args.objdump, context)
177
178 assert(context.npending < pool_size);
179 process_one_chunk(pool, pool_size, args.objdump, context)
180
181 total = context.rcumulative
182 nfailed = len(total.errors)
183 nsuccess = total.nsymbols - nfailed
184 ncrashed = len(total.crashed)
185
186 if (nfailed > 0):
187 print("Failures:")
188 for m in sorted(total.errors):
189 print(" " + m)
190 if (ncrashed > 0):
191 print("Crashes:")
192 for f in sorted(total.crashed):
193 print(" " + f)
194 print("Summary:")
195 spct = float(nsuccess)/float(total.nsymbols)
196 fpct = float(nfailed)/float(total.nsymbols)
197 cpct = float(ncrashed)/float(nfiles)
198 print("Processed {0} object files.".format(nfiles))
199 print("{0}/{1} symbols successfully demangled ({2:.4%})".format(nsuccess, total.nsymbols, spct))
200 print("{0} symbols could not be demangled ({1:.4%})".format(nfailed, fpct))
201 print("{0} files crashed while demangling ({1:.4%})".format(ncrashed, cpct))
202
203 except:
204 traceback.print_exc()
205
206 pool.close()
207 pool.join()
208
209 if __name__ == "__main__":
210 def_obj = 'obj' if sys.platform == 'win32' else 'o'
211
212 parser = argparse.ArgumentParser(description='Demangle all symbols in a tree of object files, looking for failures.')
213 parser.add_argument('dir', type=str, help='the root directory at which to start crawling')
214 parser.add_argument('--objdump', type=str, help='path to llvm-objdump. If not specified ' +
215 'the tool is located as if by `which llvm-objdump`.')
216 parser.add_argument('--extensions', type=str, default=def_obj,
217 help='comma separated list of extensions to demangle (e.g. `o,obj`). ' +
218 'By default this will be `obj` on Windows and `o` otherwise.')
219
220 args = parser.parse_args()
221
222
223 multiprocessing.freeze_support()
224 go()
225