buildbot_test_toolchains.py revision e58198707a6524ad3f731c61c9d5ef140a342451
1#!/usr/bin/env python2
2#
3# Copyright 2016 The Chromium OS Authors. All rights reserved.
4# Use of this source code is governed by a BSD-style license that can be
5# found in the LICENSE file.
6
7"""Script for running nightly compiler tests on ChromeOS.
8
9This script launches a buildbot to build ChromeOS with the latest compiler on
10a particular board; then it finds and downloads the trybot image and the
11corresponding official image, and runs crosperf performance tests comparing
12the two.  It then generates a report, emails it to the c-compiler-chrome, as
13well as copying the images into the seven-day reports directory.
14"""
15
16# Script to test different toolchains against ChromeOS benchmarks.
17
18from __future__ import print_function
19
20import argparse
21import datetime
22import os
23import re
24import sys
25import time
26
27from cros_utils import command_executer
28from cros_utils import logger
29
30from cros_utils import buildbot_utils
31
32# CL that updated GCC ebuilds to use 'next_gcc'.
33USE_NEXT_GCC_PATCH = '230260'
34
35# CL that uses LLVM to build the peppy image.
36USE_LLVM_PATCH = '295217'
37
38CROSTC_ROOT = '/usr/local/google/crostc'
39ROLE_ACCOUNT = 'mobiletc-prebuild'
40TOOLCHAIN_DIR = os.path.dirname(os.path.realpath(__file__))
41MAIL_PROGRAM = '~/var/bin/mail-sheriff'
42PENDING_ARCHIVES_DIR = os.path.join(CROSTC_ROOT, 'pending_archives')
43NIGHTLY_TESTS_DIR = os.path.join(CROSTC_ROOT, 'nightly_test_reports')
44
45IMAGE_DIR = '{board}-{image_type}'
46IMAGE_VERSION_STR = r'{chrome_version}-{tip}\.{branch}\.{branch_branch}'
47IMAGE_FS = IMAGE_DIR + '/' + IMAGE_VERSION_STR
48TRYBOT_IMAGE_FS = 'trybot-' + IMAGE_FS + '-{build_id}'
49PFQ_IMAGE_FS = IMAGE_FS + '-rc1'
50IMAGE_RE_GROUPS = {
51    'board': r'(?P<board>\S+)',
52    'image_type': r'(?P<image_type>\S+)',
53    'chrome_version': r'(?P<chrome_version>R\d+)',
54    'tip': r'(?P<tip>\d+)',
55    'branch': r'(?P<branch>\d+)',
56    'branch_branch': r'(?P<branch_branch>\d+)',
57    'build_id': r'(?P<build_id>b\d+)'
58}
59TRYBOT_IMAGE_RE = TRYBOT_IMAGE_FS.format(**IMAGE_RE_GROUPS)
60
61
62class ToolchainComparator(object):
63  """Class for doing the nightly tests work."""
64
65  def __init__(self,
66               board,
67               remotes,
68               chromeos_root,
69               weekday,
70               patches,
71               noschedv2=False):
72    self._board = board
73    self._remotes = remotes
74    self._chromeos_root = chromeos_root
75    self._base_dir = os.getcwd()
76    self._ce = command_executer.GetCommandExecuter()
77    self._l = logger.GetLogger()
78    self._build = '%s-release' % board
79    self._patches = patches.split(',')
80    self._patches_string = '_'.join(str(p) for p in self._patches)
81    self._noschedv2 = noschedv2
82
83    if not weekday:
84      self._weekday = time.strftime('%a')
85    else:
86      self._weekday = weekday
87    timestamp = datetime.datetime.strftime(datetime.datetime.now(),
88                                           '%Y-%m-%d_%H:%M:%S')
89    self._reports_dir = os.path.join(
90        NIGHTLY_TESTS_DIR,
91        '%s.%s' % (timestamp, board),)
92
93  def _GetVanillaImageName(self, trybot_image):
94    """Given a trybot artifact name, get latest vanilla image name.
95
96    Args:
97      trybot_image: artifact name such as
98          'trybot-daisy-release/R40-6394.0.0-b1389'
99
100    Returns:
101      Latest official image name, e.g. 'daisy-release/R57-9089.0.0'.
102    """
103    mo = re.search(TRYBOT_IMAGE_RE, trybot_image)
104    assert mo
105    dirname = IMAGE_DIR.replace('\\', '').format(**mo.groupdict())
106    version = buildbot_utils.GetGSContent(self._chromeos_root,
107                                          dirname + '/LATEST-master')
108    return dirname + '/' + version
109
110  def _GetNonAFDOImageName(self, trybot_image):
111    """Given a trybot artifact name, get corresponding non-AFDO image name.
112
113    We get the non-AFDO image from the PFQ builders. This image
114    is not generated for all the boards and, the closest PFQ image
115    was the one build for the previous ChromeOS version (the chrome
116    used in the current version is the one validated in the previous
117    version).
118    The previous ChromeOS does not always exist either. So, we try
119    a couple of versions before.
120
121    Args:
122      trybot_image: artifact name such as
123          'trybot-daisy-release/R40-6394.0.0-b1389'
124
125    Returns:
126      Corresponding chrome PFQ image name, e.g.
127      'daisy-chrome-pfq/R40-6393.0.0-rc1'.
128    """
129    mo = re.search(TRYBOT_IMAGE_RE, trybot_image)
130    assert mo
131    image_dict = mo.groupdict()
132    image_dict['image_type'] = 'chrome-pfq'
133    for _ in xrange(2):
134      image_dict['tip'] = str(int(image_dict['tip']) - 1)
135      nonafdo_image = PFQ_IMAGE_FS.replace('\\', '').format(**image_dict)
136      if buildbot_utils.DoesImageExist(self._chromeos_root, nonafdo_image):
137        return nonafdo_image
138    return ''
139
140  def _FinishSetup(self):
141    """Make sure testing_rsa file is properly set up."""
142    # Fix protections on ssh key
143    command = ('chmod 600 /var/cache/chromeos-cache/distfiles/target'
144               '/chrome-src-internal/src/third_party/chromite/ssh_keys'
145               '/testing_rsa')
146    ret_val = self._ce.ChrootRunCommand(self._chromeos_root, command)
147    if ret_val != 0:
148      raise RuntimeError('chmod for testing_rsa failed')
149
150  def _TestImages(self, trybot_image, vanilla_image, nonafdo_image):
151    """Create crosperf experiment file.
152
153    Given the names of the trybot, vanilla and non-AFDO images, create the
154    appropriate crosperf experiment file and launch crosperf on it.
155    """
156    experiment_file_dir = os.path.join(self._chromeos_root, '..', self._weekday)
157    experiment_file_name = '%s_toolchain_experiment.txt' % self._board
158
159    compiler_string = 'gcc'
160    if USE_LLVM_PATCH in self._patches_string:
161      experiment_file_name = '%s_llvm_experiment.txt' % self._board
162      compiler_string = 'llvm'
163
164    experiment_file = os.path.join(experiment_file_dir, experiment_file_name)
165    experiment_header = """
166    board: %s
167    remote: %s
168    retries: 1
169    """ % (self._board, self._remotes)
170    experiment_tests = """
171    benchmark: all_toolchain_perf {
172      suite: telemetry_Crosperf
173      iterations: 3
174    }
175
176    benchmark: page_cycler_v2.typical_25 {
177      suite: telemetry_Crosperf
178      iterations: 2
179      run_local: False
180      retries: 0
181    }
182    """
183
184    with open(experiment_file, 'w') as f:
185      f.write(experiment_header)
186      f.write(experiment_tests)
187
188      # Now add vanilla to test file.
189      official_image = """
190          vanilla_image {
191            chromeos_root: %s
192            build: %s
193            compiler: gcc
194          }
195          """ % (self._chromeos_root, vanilla_image)
196      f.write(official_image)
197
198      # Now add non-AFDO image to test file.
199      if nonafdo_image:
200        official_nonafdo_image = """
201          nonafdo_image {
202            chromeos_root: %s
203            build: %s
204            compiler: gcc
205          }
206          """ % (self._chromeos_root, nonafdo_image)
207        f.write(official_nonafdo_image)
208
209      label_string = '%s_trybot_image' % compiler_string
210      if USE_NEXT_GCC_PATCH in self._patches:
211        label_string = 'gcc_next_trybot_image'
212
213      experiment_image = """
214          %s {
215            chromeos_root: %s
216            build: %s
217            compiler: %s
218          }
219          """ % (label_string, self._chromeos_root, trybot_image,
220                 compiler_string)
221      f.write(experiment_image)
222
223    crosperf = os.path.join(TOOLCHAIN_DIR, 'crosperf', 'crosperf')
224    noschedv2_opts = '--noschedv2' if self._noschedv2 else ''
225    command = ('{crosperf} --no_email=True --results_dir={r_dir} '
226               '--json_report=True {noschedv2_opts} {exp_file}').format(
227                   crosperf=crosperf,
228                   r_dir=self._reports_dir,
229                   noschedv2_opts=noschedv2_opts,
230                   exp_file=experiment_file)
231
232    ret = self._ce.RunCommand(command)
233    if ret != 0:
234      raise RuntimeError('Crosperf execution error!')
235    else:
236      # Copy json report to pending archives directory.
237      command = 'cp %s/*.json %s/.' % (self._reports_dir, PENDING_ARCHIVES_DIR)
238      ret = self._ce.RunCommand(command)
239    return
240
241  def _SendEmail(self):
242    """Find email message generated by crosperf and send it."""
243    filename = os.path.join(self._reports_dir, 'msg_body.html')
244    if (os.path.exists(filename) and
245        os.path.exists(os.path.expanduser(MAIL_PROGRAM))):
246      email_title = 'buildbot test results'
247      if self._patches_string == USE_LLVM_PATCH:
248        email_title = 'buildbot llvm test results'
249      command = ('cat %s | %s -s "%s, %s" -team -html' %
250                 (filename, MAIL_PROGRAM, email_title, self._board))
251      self._ce.RunCommand(command)
252
253  def DoAll(self):
254    """Main function inside ToolchainComparator class.
255
256    Launch trybot, get image names, create crosperf experiment file, run
257    crosperf, and copy images into seven-day report directories.
258    """
259    date_str = datetime.date.today()
260    description = 'master_%s_%s_%s' % (self._patches_string, self._build,
261                                       date_str)
262    build_id, trybot_image = buildbot_utils.GetTrybotImage(
263        self._chromeos_root,
264        self._build,
265        self._patches,
266        description,
267        other_flags=['--notests'],
268        build_toolchain=True)
269
270    print('trybot_url: \
271       https://uberchromegw.corp.google.com/i/chromiumos.tryserver/builders/release/builds/%s' \
272       % build_id)
273    if len(trybot_image) == 0:
274      self._l.LogError('Unable to find trybot_image for %s!' % description)
275      return 1
276
277    vanilla_image = self._GetVanillaImageName(trybot_image)
278    nonafdo_image = self._GetNonAFDOImageName(trybot_image)
279
280    print('trybot_image: %s' % trybot_image)
281    print('vanilla_image: %s' % vanilla_image)
282    print('nonafdo_image: %s' % nonafdo_image)
283
284    if os.getlogin() == ROLE_ACCOUNT:
285      self._FinishSetup()
286
287    self._TestImages(trybot_image, vanilla_image, nonafdo_image)
288    self._SendEmail()
289    return 0
290
291
292def Main(argv):
293  """The main function."""
294
295  # Common initializations
296  command_executer.InitCommandExecuter()
297  parser = argparse.ArgumentParser()
298  parser.add_argument(
299      '--remote', dest='remote', help='Remote machines to run tests on.')
300  parser.add_argument(
301      '--board', dest='board', default='x86-zgb', help='The target board.')
302  parser.add_argument(
303      '--chromeos_root',
304      dest='chromeos_root',
305      help='The chromeos root from which to run tests.')
306  parser.add_argument(
307      '--weekday',
308      default='',
309      dest='weekday',
310      help='The day of the week for which to run tests.')
311  parser.add_argument(
312      '--patch',
313      dest='patches',
314      help='The patches to use for the testing, '
315      "seprate the patch numbers with ',' "
316      'for more than one patches.')
317  parser.add_argument(
318      '--noschedv2',
319      dest='noschedv2',
320      action='store_true',
321      default=False,
322      help='Pass --noschedv2 to crosperf.')
323
324  options = parser.parse_args(argv[1:])
325  if not options.board:
326    print('Please give a board.')
327    return 1
328  if not options.remote:
329    print('Please give at least one remote machine.')
330    return 1
331  if not options.chromeos_root:
332    print('Please specify the ChromeOS root directory.')
333    return 1
334  if options.patches:
335    patches = options.patches
336  else:
337    patches = USE_NEXT_GCC_PATCH
338
339  fc = ToolchainComparator(options.board, options.remote, options.chromeos_root,
340                           options.weekday, patches, options.noschedv2)
341  return fc.DoAll()
342
343
344if __name__ == '__main__':
345  retval = Main(sys.argv)
346  sys.exit(retval)
347