Home | History | Annotate | Download | only in fdo_scripts
      1 # Copyright 2011 Google Inc. All Rights Reserved.
      2 """Summarize hottest basic blocks found while doing a ChromeOS FDO build.
      3 
      4 Here is an example execution:
      5 
      6   summarize_hot_blocks.py
      7    --data_dir=~/chromeos/chroot/var/cache/chromeos-chrome/ --cutoff=10000
      8    --output_dir=/home/x/y
      9 
     10 With the cutoff, it will ignore any basic blocks that have a count less
     11 than what is specified (in this example 10000)
     12 The script looks inside the directory (this is typically a directory where
     13 the object files are generated) for files with *.profile and *.optimized
     14 suffixes. To get these, the following flags were added to the compiler
     15 invokation within vanilla_vs_fdo.py in the profile-use phase.
     16 
     17               "-fdump-tree-optimized-blocks-lineno "
     18               "-fdump-ipa-profile-blocks-lineno "
     19 
     20 Here is an example of the *.profile and *.optimized files contents:
     21 
     22 # BLOCK 7 freq:3901 count:60342, starting at line 92
     23 # PRED: 6 [39.0%]  count:60342 (true,exec)
     24   [url_canon_internal.cc : 92:28] MEM[(const char * *)source_6(D) + 16B] =
     25   D.28080_17;
     26   [url_canon_internal.cc : 93:41] MEM[(struct Component *)parsed_4(D) + 16B] =
     27   MEM[(const struct Component &)repl_1(D) + 80];
     28 # SUCC: 8 [100.0%]  count:60342 (fallthru,exec)
     29 # BLOCK 8 freq:10000 count:154667, starting at line 321
     30 # PRED: 7 [100.0%]  count:60342 (fallthru,exec) 6 [61.0%]  count:94325
     31 (false,exec)
     32   [url_canon_internal.cc : 321:51] # DEBUG D#10 =>
     33   [googleurl/src/url_canon_internal.cc : 321] &parsed_4(D)->host
     34 
     35 this script finds the blocks with highest count and shows the first line
     36 of each block so that it is easy to identify the origin of the basic block.
     37 
     38 """
     39 
     40 __author__ = 'llozano (at] google.com (Luis Lozano)'
     41 
     42 import optparse
     43 import os
     44 import re
     45 import shutil
     46 import sys
     47 import tempfile
     48 
     49 from cros_utils import command_executer
     50 
     51 
     52 # Given a line, check if it has a block count and return it.
     53 # Return -1 if there is no match
     54 def GetBlockCount(line):
     55   match_obj = re.match('.*# BLOCK \d+ .*count:(\d+)', line)
     56   if match_obj:
     57     return int(match_obj.group(1))
     58   else:
     59     return -1
     60 
     61 
     62 class Collector(object):
     63 
     64   def __init__(self, data_dir, cutoff, output_dir, tempdir):
     65     self._data_dir = data_dir
     66     self._cutoff = cutoff
     67     self._output_dir = output_dir
     68     self._tempdir = tempdir
     69     self._ce = command_executer.GetCommandExecuter()
     70 
     71   def CollectFileList(self, file_exp, list_file):
     72     command = ("find %s -type f -name '%s' > %s" %
     73                (self._data_dir, file_exp,
     74                 os.path.join(self._tempdir, list_file)))
     75     ret = self._ce.RunCommand(command)
     76     if ret:
     77       raise RuntimeError('Failed: %s' % command)
     78 
     79   def SummarizeLines(self, data_file):
     80     sum_lines = []
     81     search_lno = False
     82     for line in data_file:
     83       count = GetBlockCount(line)
     84       if count != -1:
     85         if count >= self._cutoff:
     86           search_lno = True
     87           sum_line = line.strip()
     88           sum_count = count
     89       # look for a line that starts with line number information
     90       elif search_lno and re.match('^\s*\[.*: \d*:\d*]', line):
     91         search_lno = False
     92         sum_lines.append('%d:%s: %s %s' %
     93                          (sum_count, data_file.name, sum_line, line))
     94     return sum_lines
     95 
     96   # Look for blocks in the data file that have a count larger than the cutoff
     97   # and generate a sorted summary file of the hottest blocks.
     98   def SummarizeFile(self, data_file, sum_file):
     99     with open(data_file, 'r') as f:
    100       sum_lines = self.SummarizeLines(f)
    101 
    102     # sort reverse the list in place by the block count number
    103     sum_lines.sort(key=GetBlockCount, reverse=True)
    104 
    105     with open(sum_file, 'w') as sf:
    106       sf.write(''.join(sum_lines))
    107 
    108     print 'Generated file Summary: ', sum_file
    109 
    110   # Find hottest blocks in the list of files, generate a sorted summary for
    111   # each file and then do a sorted merge of all the summaries.
    112   def SummarizeList(self, list_file, summary_file):
    113     with open(os.path.join(self._tempdir, list_file)) as f:
    114       sort_list = []
    115       for file_name in f:
    116         file_name = file_name.strip()
    117         sum_file = '%s.sum' % file_name
    118         sort_list.append('%s%s' % (sum_file, chr(0)))
    119         self.SummarizeFile(file_name, sum_file)
    120 
    121     tmp_list_file = os.path.join(self._tempdir, 'file_list.dat')
    122     with open(tmp_list_file, 'w') as file_list_file:
    123       for x in sort_list:
    124         file_list_file.write(x)
    125 
    126     merge_command = ('sort -nr -t: -k1 --merge --files0-from=%s > %s ' %
    127                      (tmp_list_file, summary_file))
    128 
    129     ret = self._ce.RunCommand(merge_command)
    130     if ret:
    131       raise RuntimeError('Failed: %s' % merge_command)
    132     print 'Generated general summary: ', summary_file
    133 
    134   def SummarizePreOptimized(self, summary_file):
    135     self.CollectFileList('*.profile', 'chrome.profile.list')
    136     self.SummarizeList('chrome.profile.list',
    137                        os.path.join(self._output_dir, summary_file))
    138 
    139   def SummarizeOptimized(self, summary_file):
    140     self.CollectFileList('*.optimized', 'chrome.optimized.list')
    141     self.SummarizeList('chrome.optimized.list',
    142                        os.path.join(self._output_dir, summary_file))
    143 
    144 
    145 def Main(argv):
    146   command_executer.InitCommandExecuter()
    147   usage = ('usage: %prog --data_dir=<dir> --cutoff=<value> '
    148            '--output_dir=<dir> [--keep_tmp]')
    149   parser = optparse.OptionParser(usage=usage)
    150   parser.add_option('--data_dir',
    151                     dest='data_dir',
    152                     help=('directory where the FDO (*.profile and '
    153                           '*.optimized) files are located'))
    154   parser.add_option('--cutoff',
    155                     dest='cutoff',
    156                     help='Minimum count to consider for each basic block')
    157   parser.add_option('--output_dir',
    158                     dest='output_dir',
    159                     help=('directory where summary data will be generated'
    160                           '(pre_optimized.txt, optimized.txt)'))
    161   parser.add_option('--keep_tmp',
    162                     action='store_true',
    163                     dest='keep_tmp',
    164                     default=False,
    165                     help=('Keep directory with temporary files'
    166                           '(for debugging purposes)'))
    167   options = parser.parse_args(argv)[0]
    168   if not all((options.data_dir, options.cutoff, options.output_dir)):
    169     parser.print_help()
    170     sys.exit(1)
    171 
    172   tempdir = tempfile.mkdtemp()
    173 
    174   co = Collector(options.data_dir, int(options.cutoff), options.output_dir,
    175                  tempdir)
    176   co.SummarizePreOptimized('pre_optimized.txt')
    177   co.SummarizeOptimized('optimized.txt')
    178 
    179   if not options.keep_tmp:
    180     shutil.rmtree(tempdir, ignore_errors=True)
    181 
    182   return 0
    183 
    184 
    185 if __name__ == '__main__':
    186   retval = Main(sys.argv)
    187   sys.exit(retval)
    188