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