1# Copyright 2011 Google Inc. All Rights Reserved.
2"""Script to build chrome with FDO and compare performance against no FDO."""
3
4import getpass
5import optparse
6import os
7import sys
8
9import image_chromeos
10import setup_chromeos
11from cros_utils import command_executer
12from cros_utils import misc
13from cros_utils import logger
14
15
16class Patcher(object):
17
18  def __init__(self, dir_to_patch, patch_file):
19    self._dir_to_patch = dir_to_patch
20    self._patch_file = patch_file
21    self._base_patch_command = 'patch -p0 %%s < %s' % patch_file
22    self._ce = command_executer.GetCommandExecuter()
23
24  def _RunPatchCommand(self, args):
25    patch_command = self._base_patch_command % args
26    command = ('cd %s && %s' % (self._dir_to_patch, patch_command))
27    return self._ce.RunCommand(command)
28
29  def _ApplyPatch(self, args):
30    full_args = '%s --dry-run' % args
31    ret = self._RunPatchCommand(full_args)
32    if ret:
33      raise RuntimeError('Patch dry run failed!')
34    ret = self._RunPatchCommand(args)
35    if ret:
36      raise RuntimeError('Patch application failed!')
37
38  def __enter__(self):
39    self._ApplyPatch('')
40
41  def __exit__(self, type, value, traceback):
42    self._ApplyPatch('-R')
43
44
45class FDOComparator(object):
46
47  def __init__(self, board, remotes, ebuild_version, plus_pgo, minus_pgo,
48               update_pgo, chromeos_root):
49    self._board = board
50    self._remotes = remotes
51    self._ebuild_version = ebuild_version
52    self._remote = remotes.split(',')[0]
53    self._chromeos_root = chromeos_root
54    self._profile_dir = 'profile_dir'
55    self._profile_path = os.path.join(self._chromeos_root, 'src', 'scripts',
56                                      os.path.basename(self._profile_dir))
57    self._plus_pgo = plus_pgo
58    self._minus_pgo = minus_pgo
59    self._update_pgo = update_pgo
60
61    self._ce = command_executer.GetCommandExecuter()
62    self._l = logger.GetLogger()
63
64  def _CheckoutChromeOS(self):
65    if not os.path.exists(self._chromeos_root):
66      setup_chromeos_args = [setup_chromeos.__file__,
67                             '--dir=%s' % self._chromeos_root, '--minilayout']
68      setup_chromeos.Main(setup_chromeos_args)
69
70  def _BuildChromeOSUsingBinaries(self):
71    image_dir = misc.GetImageDir(self._chromeos_root, self._board)
72    command = 'equery-%s l chromeos' % self._board
73    ret = self._ce.ChrootRunCommand(self._chromeos_root, command)
74    if ret:
75      command = misc.GetSetupBoardCommand(self._board, usepkg=True)
76      ret = self._ce.ChrootRunCommand(self._chromeos_root, command)
77      if ret:
78        raise RuntimeError("Couldn't run setup_board!")
79      command = misc.GetBuildPackagesCommand(self._board, True)
80      ret = self._ce.ChrootRunCommand(self._chromeos_root, command)
81      if ret:
82        raise RuntimeError("Couldn't run build_packages!")
83
84  def _ReportMismatches(self, build_log):
85    mismatch_signature = '-Wcoverage-mismatch'
86    mismatches = build_log.count(mismatch_signature)
87    self._l.LogOutput('Total mismatches: %s' % mismatches)
88    stale_files = set([])
89    for line in build_log.splitlines():
90      if mismatch_signature in line:
91        filename = line.split(':')[0]
92        stale_files.add(filename)
93    self._l.LogOutput('Total stale files: %s' % len(stale_files))
94
95  def _BuildChromeAndImage(self,
96                           ebuild_version='',
97                           env_dict={},
98                           cflags='',
99                           cxxflags='',
100                           ldflags='',
101                           label='',
102                           build_image_args=''):
103    env_string = misc.GetEnvStringFromDict(env_dict)
104    if not label:
105      label = ' '.join([env_string, cflags, cxxflags, ldflags, ebuild_version])
106      label = label.strip()
107      label = misc.GetFilenameFromString(label)
108    if not misc.DoesLabelExist(self._chromeos_root, self._board, label):
109      build_chrome_browser_args = ['--clean', '--chromeos_root=%s' %
110                                   self._chromeos_root, '--board=%s' %
111                                   self._board, '--env=%r' % env_string,
112                                   '--cflags=%r' % cflags, '--cxxflags=%r' %
113                                   cxxflags, '--ldflags=%r' % ldflags,
114                                   '--ebuild_version=%s' % ebuild_version,
115                                   '--build_image_args=%s' % build_image_args]
116
117      build_chrome_browser = os.path.join(
118          os.path.dirname(__file__), '..', 'build_chrome_browser.py')
119      command = 'python %s %s' % (build_chrome_browser,
120                                  ' '.join(build_chrome_browser_args))
121      ret, out, err = self._ce.RunCommandWOutput(command)
122      if '-fprofile-use' in cxxflags:
123        self._ReportMismatches(out)
124
125      if ret:
126        raise RuntimeError("Couldn't build chrome browser!")
127      misc.LabelLatestImage(self._chromeos_root, self._board, label)
128    return label
129
130  def _TestLabels(self, labels):
131    experiment_file = 'pgo_experiment.txt'
132    experiment_header = """
133    board: %s
134    remote: %s
135    """ % (self._board, self._remotes)
136    experiment_tests = """
137    benchmark: desktopui_PyAutoPerfTests {
138      iterations: 1
139    }
140    """
141
142    with open(experiment_file, 'w') as f:
143      print >> f, experiment_header
144      print >> f, experiment_tests
145      for label in labels:
146        # TODO(asharif): Fix crosperf so it accepts labels with symbols
147        crosperf_label = label
148        crosperf_label = crosperf_label.replace('-', 'minus')
149        crosperf_label = crosperf_label.replace('+', 'plus')
150        experiment_image = """
151        %s {
152          chromeos_image: %s
153        }
154        """ % (crosperf_label, os.path.join(
155            misc.GetImageDir(self._chromeos_root, self._board), label,
156            'chromiumos_test_image.bin'))
157        print >> f, experiment_image
158    crosperf = os.path.join(
159        os.path.dirname(__file__), '..', 'crosperf', 'crosperf')
160    command = '%s %s' % (crosperf, experiment_file)
161    ret = self._ce.RunCommand(command)
162    if ret:
163      raise RuntimeError("Couldn't run crosperf!")
164
165  def _ImageRemote(self, label):
166    image_path = os.path.join(
167        misc.GetImageDir(self._chromeos_root,
168                         self._board), label, 'chromiumos_test_image.bin')
169    image_chromeos_args = [image_chromeos.__file__, '--chromeos_root=%s' %
170                           self._chromeos_root, '--image=%s' % image_path,
171                           '--remote=%s' % self._remote,
172                           '--board=%s' % self._board]
173    image_chromeos.Main(image_chromeos_args)
174
175  def _ProfileRemote(self):
176    profile_cycler = os.path.join(
177        os.path.dirname(__file__), 'profile_cycler.py')
178    profile_cycler_args = ['--chromeos_root=%s' % self._chromeos_root,
179                           '--cycler=all', '--board=%s' % self._board,
180                           '--profile_dir=%s' % self._profile_path,
181                           '--remote=%s' % self._remote]
182    command = 'python %s %s' % (profile_cycler, ' '.join(profile_cycler_args))
183    ret = self._ce.RunCommand(command)
184    if ret:
185      raise RuntimeError("Couldn't profile cycler!")
186
187  def _BuildGenerateImage(self):
188    # TODO(asharif): add cflags as well.
189    labels_list = ['fprofile-generate', self._ebuild_version]
190    label = '_'.join(labels_list)
191    generate_label = self._BuildChromeAndImage(
192        env_dict={'USE': 'chrome_internal -pgo pgo_generate'},
193        label=label,
194        ebuild_version=self._ebuild_version,
195        build_image_args='--rootfs_boost_size=400')
196    return generate_label
197
198  def _BuildUseImage(self):
199    ctarget = misc.GetCtargetFromBoard(self._board, self._chromeos_root)
200    chroot_profile_dir = os.path.join('/home/%s/trunk' % getpass.getuser(),
201                                      'src', 'scripts', self._profile_dir,
202                                      ctarget)
203    cflags = ('-fprofile-use '
204              '-fprofile-correction '
205              '-Wno-error '
206              '-fdump-tree-optimized-blocks-lineno '
207              '-fdump-ipa-profile-blocks-lineno '
208              '-fno-vpt '
209              '-fprofile-dir=%s' % chroot_profile_dir)
210    labels_list = ['updated_pgo', self._ebuild_version]
211    label = '_'.join(labels_list)
212    pgo_use_label = self._BuildChromeAndImage(
213        env_dict={'USE': 'chrome_internal -pgo'},
214        cflags=cflags,
215        cxxflags=cflags,
216        ldflags=cflags,
217        label=label,
218        ebuild_version=self._ebuild_version)
219    return pgo_use_label
220
221  def DoAll(self):
222    self._CheckoutChromeOS()
223    self._BuildChromeOSUsingBinaries()
224    labels = []
225
226    if self._minus_pgo:
227      minus_pgo = self._BuildChromeAndImage(
228          env_dict={'USE': 'chrome_internal -pgo'},
229          ebuild_version=self._ebuild_version)
230      labels.append(minus_pgo)
231    if self._plus_pgo:
232      plus_pgo = self._BuildChromeAndImage(
233          env_dict={'USE': 'chrome_internal pgo'},
234          ebuild_version=self._ebuild_version)
235      labels.append(plus_pgo)
236
237    if self._update_pgo:
238      if not os.path.exists(self._profile_path):
239        # Build Chrome with -fprofile-generate
240        generate_label = self._BuildGenerateImage()
241        # Image to the remote box.
242        self._ImageRemote(generate_label)
243        # Profile it using all page cyclers.
244        self._ProfileRemote()
245
246      # Use the profile directory to rebuild it.
247      updated_pgo_label = self._BuildUseImage()
248      labels.append(updated_pgo_label)
249
250    # Run crosperf on all images now.
251    self._TestLabels(labels)
252    return 0
253
254
255def Main(argv):
256  """The main function."""
257  # Common initializations
258  ###  command_executer.InitCommandExecuter(True)
259  command_executer.InitCommandExecuter()
260  parser = optparse.OptionParser()
261  parser.add_option('--remote',
262                    dest='remote',
263                    help='Remote machines to run tests on.')
264  parser.add_option('--board',
265                    dest='board',
266                    default='x86-zgb',
267                    help='The target board.')
268  parser.add_option('--ebuild_version',
269                    dest='ebuild_version',
270                    default='',
271                    help='The Chrome ebuild version to use.')
272  parser.add_option('--plus_pgo',
273                    dest='plus_pgo',
274                    action='store_true',
275                    default=False,
276                    help='Build USE=+pgo.')
277  parser.add_option('--minus_pgo',
278                    dest='minus_pgo',
279                    action='store_true',
280                    default=False,
281                    help='Build USE=-pgo.')
282  parser.add_option('--update_pgo',
283                    dest='update_pgo',
284                    action='store_true',
285                    default=False,
286                    help='Update pgo and build Chrome with the update.')
287  parser.add_option('--chromeos_root',
288                    dest='chromeos_root',
289                    default=False,
290                    help='The chromeos root directory')
291  options, _ = parser.parse_args(argv)
292  if not options.board:
293    print 'Please give a board.'
294    return 1
295  if not options.remote:
296    print 'Please give at least one remote machine.'
297    return 1
298  if not options.chromeos_root:
299    print 'Please provide the chromeos root directory.'
300    return 1
301  if not any((options.minus_pgo, options.plus_pgo, options.update_pgo)):
302    print 'Please provide at least one build option.'
303    return 1
304  fc = FDOComparator(options.board, options.remote, options.ebuild_version,
305                     options.plus_pgo, options.minus_pgo, options.update_pgo,
306                     os.path.expanduser(options.chromeos_root))
307  return fc.DoAll()
308
309
310if __name__ == '__main__':
311  retval = Main(sys.argv)
312  sys.exit(retval)
313