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