test_toolchains.py revision fe054f1eac0d0d739b74dee6bd45805af86568a2
1#!/usr/bin/python
2
3# Script to test different toolchains against ChromeOS benchmarks.
4import optparse
5import os
6import sys
7import build_chromeos
8import setup_chromeos
9import time
10from utils import command_executer
11from utils import misc
12from utils import logger
13
14
15WEEKLY_REPORTS_ROOT="/usr/local/google/crostc/weekly_test_data"
16AFDO_BOARDS = ["butterfly", "lumpy", "stumpy", "stout", "parrot", "parrot_ivb"]
17
18class GCCConfig(object):
19  def __init__(self, githash):
20    self.githash = githash
21
22
23class ToolchainConfig:
24  def __init__(self, gcc_config=None, binutils_config=None):
25    self.gcc_config = gcc_config
26
27
28class ChromeOSCheckout(object):
29  def __init__(self, board, chromeos_root):
30    self._board = board
31    self._chromeos_root = chromeos_root
32    self._ce = command_executer.GetCommandExecuter()
33    self._l = logger.GetLogger()
34    self._build_num = None
35
36  def _DeleteChroot(self):
37    command = "cd %s; cros_sdk --delete" % self._chromeos_root
38    return self._ce.RunCommand(command)
39
40  def _DeleteCcahe(self):
41    # crosbug.com/34956
42    command = "sudo rm -rf %s" % os.path.join(self._chromeos_root, ".cache")
43    return self._ce.RunCommand(command)
44
45  def _GetBuildNumber(self):
46    """ This function assumes a ChromeOS image has been built in the chroot.
47    It translates the 'latest' symlink in the
48    <chroot>/src/build/images/<board> directory, to find the actual
49    ChromeOS build number for the image that was built.  For example, if
50    src/build/image/lumpy/latest ->  R37-5982.0.2014_06_23_0454-a1, then
51    This function would parse it out and assign 'R37-5982' to self._build_num.
52    This is used to determine the official, vanilla build to use for
53    comparison tests.
54    """
55    # Get the path to 'latest'
56    sym_path = os.path.join (misc.GetImageDir(self._chromeos_root,
57                                              self._board),
58                             "latest")
59    # Translate the symlink to its 'real' path.
60    real_path = os.path.realpath(sym_path)
61    # Break up the path and get the last piece
62    # (e.g. 'R37-5982.0.2014_06_23_0454-a1"
63    path_pieces = real_path.split("/")
64    last_piece = path_pieces[-1]
65    # Break this piece into the image number + other pieces, and get the
66    # image number [ 'R37-5982', '0', '2014_06_23_0454-a1']
67    image_parts = last_piece.split(".")
68    self._build_num = image_parts[0]
69
70  def _BuildAndImage(self, label=""):
71    if (not label or
72        not misc.DoesLabelExist(self._chromeos_root, self._board, label)):
73      build_chromeos_args = [build_chromeos.__file__,
74                             "--chromeos_root=%s" % self._chromeos_root,
75                             "--board=%s" % self._board,
76                             "--rebuild"]
77      if self._public:
78        build_chromeos_args.append("--env=USE=-chrome_internal")
79
80      if self._board in AFDO_BOARDS:
81        build_chromeos_args.append("--env=USE=afdo_use")
82
83      ret = build_chromeos.Main(build_chromeos_args)
84      if ret:
85        raise Exception("Couldn't build ChromeOS!")
86
87      if not  self._build_num:
88        self._GetBuildNumber()
89      # Check to see if we need to create the symbolic link for the vanilla
90      # image, and do so if appropriate.
91      if not misc.DoesLabelExist(self._chromeos_root, self._board, "vanilla"):
92        build_name = "%s-release/%s.0.0" % (self._board, self._build_num)
93        full_vanilla_path = os.path.join (os.getcwd(), self._chromeos_root,
94                                          'chroot/tmp', build_name)
95        misc.LabelLatestImage(self._chromeos_root, self._board, label,
96                              full_vanilla_path)
97      else:
98        misc.LabelLatestImage(self._chromeos_root, self._board, label)
99    return label
100
101  def _SetupBoard(self, env_dict, usepkg_flag, clobber_flag):
102    env_string = misc.GetEnvStringFromDict(env_dict)
103    command = ("%s %s" %
104               (env_string,
105                misc.GetSetupBoardCommand(self._board,
106                                          usepkg=usepkg_flag,
107                                          force=clobber_flag)))
108    ret = self._ce.ChrootRunCommand(self._chromeos_root,
109                                    command)
110    error_str = "Could not setup board: '%s'" % command
111    assert ret == 0, error_str
112
113  def _UnInstallToolchain(self):
114    command = ("sudo CLEAN_DELAY=0 emerge -C cross-%s/gcc" %
115               misc.GetCtargetFromBoard(self._board,
116                                   self._chromeos_root))
117    ret = self._ce.ChrootRunCommand(self._chromeos_root,
118                                    command)
119    if ret:
120      raise Exception("Couldn't uninstall the toolchain!")
121
122  def _CheckoutChromeOS(self):
123    # TODO(asharif): Setup a fixed ChromeOS version (quarterly snapshot).
124    if not os.path.exists(self._chromeos_root):
125      setup_chromeos_args = [setup_chromeos.__file__,
126                             "--dir=%s" % self._chromeos_root]
127      if self._public:
128        setup_chromeos_args.append("--public")
129      ret = setup_chromeos.Main(setup_chromeos_args)
130      if ret:
131        raise Exception("Couldn't run setup_chromeos!")
132
133
134  def _BuildToolchain(self, config):
135    # Call setup_board for basic, vanilla setup.
136    self._SetupBoard({}, usepkg_flag=True, clobber_flag=False)
137    # Now uninstall the vanilla compiler and setup/build our custom
138    # compiler.
139    self._UnInstallToolchain()
140    envdict = {"USE": "git_gcc",
141               "GCC_GITHASH": config.gcc_config.githash,
142               "EMERGE_DEFAULT_OPTS": "--exclude=gcc"}
143    self._SetupBoard(envdict, usepkg_flag=False, clobber_flag=False)
144
145
146class ToolchainComparator(ChromeOSCheckout):
147  def __init__(self, board, remotes, configs, clean, public, force_mismatch):
148    self._board = board
149    self._remotes = remotes
150    self._chromeos_root = "chromeos"
151    self._configs = configs
152    self._clean = clean
153    self._public = public
154    self._force_mismatch = force_mismatch
155    self._ce = command_executer.GetCommandExecuter()
156    self._l = logger.GetLogger()
157    ChromeOSCheckout.__init__(self, board, self._chromeos_root)
158
159
160  def _FinishSetup(self):
161    # Get correct .boto file
162    current_dir = os.getcwd()
163    src = "/home/mobiletc-prebuild/.boto"
164    dest = os.path.join(current_dir, self._chromeos_root,
165                        "src/private-overlays/chromeos-overlay/"
166                        "googlestorage_account.boto")
167    # Copy the file to the correct place
168    copy_cmd = "cp %s %s" % (src, dest)
169    retval = self._ce.RunCommand(copy_cmd)
170    if retval:
171      raise Exception("Couldn't copy .boto file for google storage.")
172
173    # Fix protections on ssh key
174    command = ("chmod 600 /var/cache/chromeos-cache/distfiles/target"
175               "/chrome-src-internal/src/third_party/chromite/ssh_keys"
176               "/testing_rsa")
177    retval = self._ce.ChrootRunCommand(self._chromeos_root, command)
178    if retval:
179      raise Exception("chmod for testing_rsa failed")
180
181  def _TestLabels(self, labels):
182    experiment_file = "toolchain_experiment.txt"
183    image_args = ""
184    if self._force_mismatch:
185      image_args = "--force-mismatch"
186    experiment_header = """
187    board: %s
188    remote: %s
189    """ % (self._board, self._remotes)
190    experiment_tests = """
191    benchmark: all_perfv2 {
192      suite: telemetry_Crosperf
193      iterations: 3
194    }
195    """
196    with open(experiment_file, "w") as f:
197      print >>f, experiment_header
198      print >>f, experiment_tests
199      for label in labels:
200        # TODO(asharif): Fix crosperf so it accepts labels with symbols
201        crosperf_label = label
202        crosperf_label = crosperf_label.replace("-", "minus")
203        crosperf_label = crosperf_label.replace("+", "plus")
204        crosperf_label = crosperf_label.replace(".", "")
205
206        # Use the official build instead of building vanilla ourselves.
207        if label == "vanilla":
208          build_name = '%s-release/%s.0.0' % (self._board, self._build_num)
209
210          # Now add 'official build' to test file.
211          official_image = """
212          official_image {
213            chromeos_root: %s
214            build: %s
215          }
216          """ % (self._chromeos_root, build_name)
217          print >>f, official_image
218
219        else:
220          experiment_image = """
221          %s {
222            chromeos_image: %s
223            image_args: %s
224          }
225          """ % (crosperf_label,
226                 os.path.join(misc.GetImageDir(self._chromeos_root,
227                                               self._board),
228                              label, "chromiumos_test_image.bin"),
229                 image_args)
230          print >>f, experiment_image
231
232    crosperf = os.path.join(os.path.dirname(__file__),
233                            "crosperf",
234                            "crosperf")
235    command = "%s --email=c-compiler-chrome %s" % (crosperf, experiment_file)
236    ret = self._ce.RunCommand(command)
237    if ret:
238      raise Exception("Couldn't run crosperf!")
239    return ret
240
241
242  def _CopyWeeklyReportFiles(self, labels):
243    """Create tar files of the custom and official images and copy them
244    to the weekly reports directory, so they exist when the weekly report
245    gets generated.  IMPORTANT NOTE: This function must run *after*
246    crosperf has been run; otherwise the vanilla images will not be there.
247    """
248    images_path = os.path.join(os.path.realpath(self._chromeos_root),
249                               "src/build/images", self._board)
250    weekday = time.strftime("%a")
251    data_dir = os.path.join(WEEKLY_REPORTS_ROOT, self._board)
252    dest_dir = os.path.join (data_dir, weekday)
253    if not os.path.exists(dest_dir):
254      os.makedirs(dest_dir)
255    # Make sure dest_dir is empty (clean out last week's data).
256    cmd = "cd %s; rm -Rf %s_*_image*" % (dest_dir, weekday)
257    self._ce.RunCommand(cmd)
258    # Now create new tar files and copy them over.
259    for l in labels:
260      test_path = os.path.join(images_path, l)
261      if os.path.exists(test_path):
262        if l != "vanilla":
263          label_name = "test"
264        else:
265          label_name = "vanilla"
266        tar_file_name = "%s_%s_image.tar" % (weekday, label_name)
267        cmd = ("cd %s; tar -cvf %s %s/chromiumos_test_image.bin; "
268               "cp %s %s/.") % (images_path,
269                                tar_file_name,
270                                l, tar_file_name,
271                                dest_dir)
272        tar_ret = self._ce.RunCommand(cmd)
273        if tar_ret:
274          self._l.LogOutput("Error while creating/copying test tar file(%s)."
275                            % tar_file_name)
276
277
278  def DoAll(self):
279    self._CheckoutChromeOS()
280    labels = []
281    labels.append("vanilla")
282    for config in self._configs:
283      label = misc.GetFilenameFromString(config.gcc_config.githash)
284      if (not misc.DoesLabelExist(self._chromeos_root,
285                                  self._board,
286                                  label)):
287        self._BuildToolchain(config)
288        label = self._BuildAndImage(label)
289      labels.append(label)
290    self._FinishSetup()
291    if not self._TestLabels(labels):
292      # Only try to copy the image files if the test runs ran successfully.
293      self._CopyWeeklyReportFiles(labels)
294    if self._clean:
295      ret = self._DeleteChroot()
296      if ret: return ret
297      ret = self._DeleteCcahe()
298      if ret: return ret
299    return 0
300
301
302def Main(argv):
303  """The main function."""
304  # Common initializations
305###  command_executer.InitCommandExecuter(True)
306  command_executer.InitCommandExecuter()
307  parser = optparse.OptionParser()
308  parser.add_option("--remote",
309                    dest="remote",
310                    help="Remote machines to run tests on.")
311  parser.add_option("--board",
312                    dest="board",
313                    default="x86-zgb",
314                    help="The target board.")
315  parser.add_option("--githashes",
316                    dest="githashes",
317                    default="master",
318                    help="The gcc githashes to test.")
319  parser.add_option("--clean",
320                    dest="clean",
321                    default=False,
322                    action="store_true",
323                    help="Clean the chroot after testing.")
324  parser.add_option("--public",
325                    dest="public",
326                    default=False,
327                    action="store_true",
328                    help="Use the public checkout/build.")
329  parser.add_option("--force-mismatch",
330                    dest="force_mismatch",
331                    default="",
332                    help="Force the image regardless of board mismatch")
333  options, _ = parser.parse_args(argv)
334  if not options.board:
335    print "Please give a board."
336    return 1
337  if not options.remote:
338    print "Please give at least one remote machine."
339    return 1
340  toolchain_configs = []
341  for githash in options.githashes.split(","):
342    gcc_config = GCCConfig(githash=githash)
343    toolchain_config = ToolchainConfig(gcc_config=gcc_config)
344    toolchain_configs.append(toolchain_config)
345  fc = ToolchainComparator(options.board, options.remote, toolchain_configs,
346                           options.clean, options.public,
347                           options.force_mismatch)
348  return fc.DoAll()
349
350
351if __name__ == "__main__":
352  retval = Main(sys.argv)
353  sys.exit(retval)
354