Home | History | Annotate | Download | only in opt-viewer
      1 #!/usr/bin/env python2.7
      2 
      3 from __future__ import print_function
      4 
      5 import yaml
      6 # Try to use the C parser.
      7 try:
      8     from yaml import CLoader as Loader
      9 except ImportError:
     10     print("For faster parsing, you may want to install libYAML for PyYAML")
     11     from yaml import Loader
     12 
     13 import cgi
     14 from collections import defaultdict
     15 import fnmatch
     16 import functools
     17 from multiprocessing import Lock
     18 import os, os.path
     19 import subprocess
     20 try:
     21     # The previously builtin function `intern()` was moved
     22     # to the `sys` module in Python 3.
     23     from sys import intern
     24 except:
     25     pass
     26 
     27 import optpmap
     28 
     29 try:
     30     dict.iteritems
     31 except AttributeError:
     32     # Python 3
     33     def itervalues(d):
     34         return iter(d.values())
     35     def iteritems(d):
     36         return iter(d.items())
     37 else:
     38     # Python 2
     39     def itervalues(d):
     40         return d.itervalues()
     41     def iteritems(d):
     42         return d.iteritems()
     43 
     44 
     45 def html_file_name(filename):
     46     return filename.replace('/', '_').replace('#', '_') + ".html"
     47 
     48 
     49 def make_link(File, Line):
     50     return "\"{}#L{}\"".format(html_file_name(File), Line)
     51 
     52 
     53 class 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 
    234 class Analysis(Remark):
    235     yaml_tag = '!Analysis'
    236 
    237     @property
    238     def color(self):
    239         return "white"
    240 
    241 
    242 class AnalysisFPCommute(Analysis):
    243     yaml_tag = '!AnalysisFPCommute'
    244 
    245 
    246 class AnalysisAliasing(Analysis):
    247     yaml_tag = '!AnalysisAliasing'
    248 
    249 
    250 class Passed(Remark):
    251     yaml_tag = '!Passed'
    252 
    253     @property
    254     def color(self):
    255         return "green"
    256 
    257 
    258 class Missed(Remark):
    259     yaml_tag = '!Missed'
    260 
    261     @property
    262     def color(self):
    263         return "red"
    264 
    265 
    266 def 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 
    292 def 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 
    320 def 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