1#!/usr/bin/env python2.7 2 3from __future__ import print_function 4 5import yaml 6# Try to use the C parser. 7try: 8 from yaml import CLoader as Loader 9except ImportError: 10 print("For faster parsing, you may want to install libYAML for PyYAML") 11 from yaml import Loader 12 13import cgi 14from collections import defaultdict 15import fnmatch 16import functools 17from multiprocessing import Lock 18import os, os.path 19import subprocess 20try: 21 # The previously builtin function `intern()` was moved 22 # to the `sys` module in Python 3. 23 from sys import intern 24except: 25 pass 26 27import optpmap 28 29try: 30 dict.iteritems 31except AttributeError: 32 # Python 3 33 def itervalues(d): 34 return iter(d.values()) 35 def iteritems(d): 36 return iter(d.items()) 37else: 38 # Python 2 39 def itervalues(d): 40 return d.itervalues() 41 def iteritems(d): 42 return d.iteritems() 43 44 45def html_file_name(filename): 46 return filename.replace('/', '_').replace('#', '_') + ".html" 47 48 49def make_link(File, Line): 50 return "\"{}#L{}\"".format(html_file_name(File), Line) 51 52 53class Remark(yaml.YAMLObject): 54 # Work-around for http://pyyaml.org/ticket/154. 55 yaml_loader = Loader 56 57 default_demangler = 'c++filt -n' 58 demangler_proc = None 59 60 @classmethod 61 def set_demangler(cls, demangler): 62 cls.demangler_proc = subprocess.Popen(demangler.split(), stdin=subprocess.PIPE, stdout=subprocess.PIPE) 63 cls.demangler_lock = Lock() 64 65 @classmethod 66 def demangle(cls, name): 67 with cls.demangler_lock: 68 cls.demangler_proc.stdin.write((name + '\n').encode('utf-8')) 69 cls.demangler_proc.stdin.flush() 70 return cls.demangler_proc.stdout.readline().rstrip().decode('utf-8') 71 72 # Intern all strings since we have lot of duplication across filenames, 73 # remark text. 74 # 75 # Change Args from a list of dicts to a tuple of tuples. This saves 76 # memory in two ways. One, a small tuple is significantly smaller than a 77 # small dict. Two, using tuple instead of list allows Args to be directly 78 # used as part of the key (in Python only immutable types are hashable). 79 def _reduce_memory(self): 80 self.Pass = intern(self.Pass) 81 self.Name = intern(self.Name) 82 try: 83 # Can't intern unicode strings. 84 self.Function = intern(self.Function) 85 except: 86 pass 87 88 def _reduce_memory_dict(old_dict): 89 new_dict = dict() 90 for (k, v) in iteritems(old_dict): 91 if type(k) is str: 92 k = intern(k) 93 94 if type(v) is str: 95 v = intern(v) 96 elif type(v) is dict: 97 # This handles [{'Caller': ..., 'DebugLoc': { 'File': ... }}] 98 v = _reduce_memory_dict(v) 99 new_dict[k] = v 100 return tuple(new_dict.items()) 101 102 self.Args = tuple([_reduce_memory_dict(arg_dict) for arg_dict in self.Args]) 103 104 # The inverse operation of the dictonary-related memory optimization in 105 # _reduce_memory_dict. E.g. 106 # (('DebugLoc', (('File', ...) ... ))) -> [{'DebugLoc': {'File': ...} ....}] 107 def recover_yaml_structure(self): 108 def tuple_to_dict(t): 109 d = dict() 110 for (k, v) in t: 111 if type(v) is tuple: 112 v = tuple_to_dict(v) 113 d[k] = v 114 return d 115 116 self.Args = [tuple_to_dict(arg_tuple) for arg_tuple in self.Args] 117 118 def canonicalize(self): 119 if not hasattr(self, 'Hotness'): 120 self.Hotness = 0 121 if not hasattr(self, 'Args'): 122 self.Args = [] 123 self._reduce_memory() 124 125 @property 126 def File(self): 127 return self.DebugLoc['File'] 128 129 @property 130 def Line(self): 131 return int(self.DebugLoc['Line']) 132 133 @property 134 def Column(self): 135 return self.DebugLoc['Column'] 136 137 @property 138 def DebugLocString(self): 139 return "{}:{}:{}".format(self.File, self.Line, self.Column) 140 141 @property 142 def DemangledFunctionName(self): 143 return self.demangle(self.Function) 144 145 @property 146 def Link(self): 147 return make_link(self.File, self.Line) 148 149 def getArgString(self, mapping): 150 mapping = dict(list(mapping)) 151 dl = mapping.get('DebugLoc') 152 if dl: 153 del mapping['DebugLoc'] 154 155 assert(len(mapping) == 1) 156 (key, value) = list(mapping.items())[0] 157 158 if key == 'Caller' or key == 'Callee' or key == 'DirectCallee': 159 value = cgi.escape(self.demangle(value)) 160 161 if dl and key != 'Caller': 162 dl_dict = dict(list(dl)) 163 return u"<a href={}>{}</a>".format( 164 make_link(dl_dict['File'], dl_dict['Line']), value) 165 else: 166 return value 167 168 # Return a cached dictionary for the arguments. The key for each entry is 169 # the argument key (e.g. 'Callee' for inlining remarks. The value is a 170 # list containing the value (e.g. for 'Callee' the function) and 171 # optionally a DebugLoc. 172 def getArgDict(self): 173 if hasattr(self, 'ArgDict'): 174 return self.ArgDict 175 self.ArgDict = {} 176 for arg in self.Args: 177 if len(arg) == 2: 178 if arg[0][0] == 'DebugLoc': 179 dbgidx = 0 180 else: 181 assert(arg[1][0] == 'DebugLoc') 182 dbgidx = 1 183 184 key = arg[1 - dbgidx][0] 185 entry = (arg[1 - dbgidx][1], arg[dbgidx][1]) 186 else: 187 arg = arg[0] 188 key = arg[0] 189 entry = (arg[1], ) 190 191 self.ArgDict[key] = entry 192 return self.ArgDict 193 194 def getDiffPrefix(self): 195 if hasattr(self, 'Added'): 196 if self.Added: 197 return '+' 198 else: 199 return '-' 200 return '' 201 202 @property 203 def PassWithDiffPrefix(self): 204 return self.getDiffPrefix() + self.Pass 205 206 @property 207 def message(self): 208 # Args is a list of mappings (dictionaries) 209 values = [self.getArgString(mapping) for mapping in self.Args] 210 return "".join(values) 211 212 @property 213 def RelativeHotness(self): 214 if self.max_hotness: 215 return "{0:.2f}%".format(self.Hotness * 100. / self.max_hotness) 216 else: 217 return '' 218 219 @property 220 def key(self): 221 return (self.__class__, self.PassWithDiffPrefix, self.Name, self.File, 222 self.Line, self.Column, self.Function, self.Args) 223 224 def __hash__(self): 225 return hash(self.key) 226 227 def __eq__(self, other): 228 return self.key == other.key 229 230 def __repr__(self): 231 return str(self.key) 232 233 234class Analysis(Remark): 235 yaml_tag = '!Analysis' 236 237 @property 238 def color(self): 239 return "white" 240 241 242class AnalysisFPCommute(Analysis): 243 yaml_tag = '!AnalysisFPCommute' 244 245 246class AnalysisAliasing(Analysis): 247 yaml_tag = '!AnalysisAliasing' 248 249 250class Passed(Remark): 251 yaml_tag = '!Passed' 252 253 @property 254 def color(self): 255 return "green" 256 257 258class Missed(Remark): 259 yaml_tag = '!Missed' 260 261 @property 262 def color(self): 263 return "red" 264 265 266def get_remarks(input_file): 267 max_hotness = 0 268 all_remarks = dict() 269 file_remarks = defaultdict(functools.partial(defaultdict, list)) 270 271 with open(input_file) as f: 272 docs = yaml.load_all(f, Loader=Loader) 273 for remark in docs: 274 remark.canonicalize() 275 # Avoid remarks withoug debug location or if they are duplicated 276 if not hasattr(remark, 'DebugLoc') or remark.key in all_remarks: 277 continue 278 all_remarks[remark.key] = remark 279 280 file_remarks[remark.File][remark.Line].append(remark) 281 282 # If we're reading a back a diff yaml file, max_hotness is already 283 # captured which may actually be less than the max hotness found 284 # in the file. 285 if hasattr(remark, 'max_hotness'): 286 max_hotness = remark.max_hotness 287 max_hotness = max(max_hotness, remark.Hotness) 288 289 return max_hotness, all_remarks, file_remarks 290 291 292def gather_results(filenames, num_jobs, should_print_progress): 293 if should_print_progress: 294 print('Reading YAML files...') 295 if not Remark.demangler_proc: 296 Remark.set_demangler(Remark.default_demangler) 297 remarks = optpmap.pmap( 298 get_remarks, filenames, num_jobs, should_print_progress) 299 max_hotness = max(entry[0] for entry in remarks) 300 301 def merge_file_remarks(file_remarks_job, all_remarks, merged): 302 for filename, d in iteritems(file_remarks_job): 303 for line, remarks in iteritems(d): 304 for remark in remarks: 305 # Bring max_hotness into the remarks so that 306 # RelativeHotness does not depend on an external global. 307 remark.max_hotness = max_hotness 308 if remark.key not in all_remarks: 309 merged[filename][line].append(remark) 310 311 all_remarks = dict() 312 file_remarks = defaultdict(functools.partial(defaultdict, list)) 313 for _, all_remarks_job, file_remarks_job in remarks: 314 merge_file_remarks(file_remarks_job, all_remarks, file_remarks) 315 all_remarks.update(all_remarks_job) 316 317 return all_remarks, file_remarks, max_hotness != 0 318 319 320def find_opt_files(*dirs_or_files): 321 all = [] 322 for dir_or_file in dirs_or_files: 323 if os.path.isfile(dir_or_file): 324 all.append(dir_or_file) 325 else: 326 for dir, subdirs, files in os.walk(dir_or_file): 327 # Exclude mounted directories and symlinks (os.walk default). 328 subdirs[:] = [d for d in subdirs 329 if not os.path.ismount(os.path.join(dir, d))] 330 for file in files: 331 if fnmatch.fnmatch(file, "*.opt.yaml*"): 332 all.append(os.path.join(dir, file)) 333 return all 334