buildbot_test_toolchains.py revision 43494297a1745c084d8a2f3beb52ff1c70a2007c
1#!/usr/bin/python
2"""
3Script for running nightly compiler tests on ChromeOS.
4
5This script launches a buildbot to build ChromeOS with the latest compiler on
6a particular board; then it finds and downloads the trybot image and the
7corresponding official image, and runs crosperf performance tests comparing
8the two.  It then generates a report, emails it to the c-compiler-chrome, as
9well as copying the images into the seven-day reports directory.
10"""
11
12# Script to test different toolchains against ChromeOS benchmarks.
13import datetime
14import optparse
15import os
16import sys
17import time
18import urllib2
19
20from utils import command_executer
21from utils import logger
22
23from utils import buildbot_utils
24
25# CL that updated GCC ebuilds to use 'next_gcc'.
26USE_NEXT_GCC_PATCH = "230260"
27
28# CL that uses LLVM to build the peppy image.
29USE_LLVM_PATCH = "295217"
30
31# The boards on which we run weekly reports
32WEEKLY_REPORT_BOARDS = ["lumpy"]
33
34CROSTC_ROOT = "/usr/local/google/crostc"
35ROLE_ACCOUNT = "mobiletc-prebuild"
36TOOLCHAIN_DIR = os.path.dirname(os.path.realpath(__file__))
37MAIL_PROGRAM = "~/var/bin/mail-sheriff"
38WEEKLY_REPORTS_ROOT = os.path.join(CROSTC_ROOT, "weekly_test_data")
39PENDING_ARCHIVES_DIR = os.path.join(CROSTC_ROOT, "pending_archives")
40NIGHTLY_TESTS_DIR = os.path.join(CROSTC_ROOT, "nightly_test_reports")
41
42class ToolchainComparator():
43  """
44  Class for doing the nightly tests work.
45  """
46
47  def __init__(self, board, remotes, chromeos_root, weekday, patches, noschedv2=False):
48    self._board = board
49    self._remotes = remotes
50    self._chromeos_root = chromeos_root
51    self._base_dir = os.getcwd()
52    self._ce = command_executer.GetCommandExecuter()
53    self._l = logger.GetLogger()
54    self._build = "%s-release" % board
55    self._patches = patches.split(',')
56    self._patches_string = '_'.join(str(p) for p in self._patches)
57    self._noschedv2 = noschedv2
58
59    if not weekday:
60      self._weekday = time.strftime("%a")
61    else:
62      self._weekday = weekday
63    timestamp = datetime.datetime.strftime(datetime.datetime.now(),
64                                           "%Y-%m-%d_%H:%M:%S")
65    self._reports_dir = os.path.join(NIGHTLY_TESTS_DIR,
66        "%s.%s" % (timestamp, board),
67        )
68
69  def _ParseVanillaImage(self, trybot_image):
70    """
71    Parse a trybot artifact name to get corresponding vanilla image.
72
73    This function takes an artifact name, such as
74    'trybot-daisy-release/R40-6394.0.0-b1389', and returns the
75    corresponding official build name, e.g. 'daisy-release/R40-6394.0.0'.
76    """
77    start_pos = trybot_image.find(self._build)
78    end_pos = trybot_image.rfind("-b")
79    vanilla_image = trybot_image[start_pos:end_pos]
80    return vanilla_image
81
82  def _FinishSetup(self):
83    """
84    Make sure testing_rsa file is properly set up.
85    """
86    # Fix protections on ssh key
87    command = ("chmod 600 /var/cache/chromeos-cache/distfiles/target"
88               "/chrome-src-internal/src/third_party/chromite/ssh_keys"
89               "/testing_rsa")
90    ret_val = self._ce.ChrootRunCommand(self._chromeos_root, command)
91    if ret_val != 0:
92      raise RuntimeError("chmod for testing_rsa failed")
93
94  def _TestImages(self, trybot_image, vanilla_image):
95    """
96    Create crosperf experiment file.
97
98    Given the names of the trybot and vanilla images, create the
99    appropriate crosperf experiment file and launch crosperf on it.
100    """
101    experiment_file_dir = os.path.join (self._chromeos_root, "..",
102                                        self._weekday)
103    experiment_file_name = "%s_toolchain_experiment.txt" % self._board
104
105    if self._patches_string == USE_LLVM_PATCH:
106      experiment_file_name = "%s_llvm_experiment.txt" % self._board
107
108    experiment_file = os.path.join (experiment_file_dir,
109                                    experiment_file_name)
110    experiment_header = """
111    board: %s
112    remote: %s
113    retries: 1
114    """ % (self._board, self._remotes)
115    experiment_tests = """
116    benchmark: all_toolchain_perf {
117      suite: telemetry_Crosperf
118      iterations: 3
119    }
120    """
121    with open(experiment_file, "w") as f:
122      print >> f, experiment_header
123      print >> f, experiment_tests
124
125      # Now add vanilla to test file.
126      official_image = """
127          vanilla_image {
128            chromeos_root: %s
129            build: %s
130          }
131          """ % (self._chromeos_root, vanilla_image)
132      print >> f, official_image
133
134      experiment_image = """
135          test_image {
136            chromeos_root: %s
137            build: %s
138          }
139          """ % (self._chromeos_root, trybot_image)
140      print >> f, experiment_image
141
142    crosperf = os.path.join(TOOLCHAIN_DIR,
143                            "crosperf",
144                            "crosperf")
145    noschedv2_opts = '--noschedv2' if self._noschedv2 else ''
146    command = ("{crosperf} --no_email=True --results_dir={r_dir} "
147               "--json_report=True {noschedv2_opts} {exp_file}").format(
148                crosperf=crosperf,
149                r_dir=self._reports_dir,
150                noschedv2_opts=noschedv2_opts,
151                exp_file=experiment_file)
152
153    ret = self._ce.RunCommand(command)
154    if ret != 0:
155      raise RuntimeError("Couldn't run crosperf!")
156    else:
157      # Copy json report to pending archives directory.
158      command = "cp %s/*.json %s/." % (self._reports_dir, PENDING_ARCHIVES_DIR)
159      ret = self._ce.RunCommand(command)
160    return
161
162  def _CopyWeeklyReportFiles(self, trybot_image, vanilla_image):
163    """
164    Put files in place for running seven-day reports.
165
166    Create tar files of the custom and official images and copy them
167    to the weekly reports directory, so they exist when the weekly report
168    gets generated.  IMPORTANT NOTE: This function must run *after*
169    crosperf has been run; otherwise the vanilla images will not be there.
170    """
171
172    dry_run = False
173    if (os.getlogin() != ROLE_ACCOUNT):
174      self._l.LogOutput("Running this from non-role account; not copying "
175                        "tar files for weekly reports.")
176      dry_run = True
177
178    images_path = os.path.join(os.path.realpath(self._chromeos_root),
179                               "chroot/tmp")
180
181    data_dir = os.path.join(WEEKLY_REPORTS_ROOT, self._board)
182    dest_dir = os.path.join (data_dir, self._weekday)
183    if not os.path.exists(dest_dir):
184      os.makedirs(dest_dir)
185
186    # Make sure dest_dir is empty (clean out last week's data).
187    cmd = "cd %s; rm -Rf %s_*_image*" % (dest_dir, self._weekday)
188    if dry_run:
189      print "CMD: %s" % cmd
190    else:
191      self._ce.RunCommand(cmd)
192
193    # Now create new tar files and copy them over.
194    labels = [ "test", "vanilla" ]
195    for label_name in labels:
196      if label_name == "test":
197        test_path = trybot_image
198      else:
199        test_path = vanilla_image
200      tar_file_name = "%s_%s_image.tar" % (self._weekday, label_name)
201      cmd = ("cd %s; tar -cvf %s %s/chromiumos_test_image.bin; "
202             "cp %s %s/.") % (images_path,
203                              tar_file_name,
204                              test_path,
205                              tar_file_name,
206                              dest_dir)
207      if dry_run:
208        print "CMD: %s" % cmd
209        tar_ret = 0
210      else:
211        tar_ret = self._ce.RunCommand(cmd)
212      if tar_ret:
213        self._l.LogOutput("Error while creating/copying test tar file(%s)."
214                          % tar_file_name)
215
216  def _SendEmail(self):
217    """Find email message generated by crosperf and send it."""
218    filename = os.path.join(self._reports_dir,
219                            "msg_body.html")
220    if (os.path.exists(filename) and
221        os.path.exists(os.path.expanduser(MAIL_PROGRAM))):
222      email_title = "buildbot test results"
223      if self._patches_string == USE_LLVM_PATCH:
224        email_title = "buildbot llvm test results"
225      command = ('cat %s | %s -s "%s, %s" -team -html'
226                 % (filename, MAIL_PROGRAM, email_title, self._board))
227      self._ce.RunCommand(command)
228
229  def DoAll(self):
230    """
231    Main function inside ToolchainComparator class.
232
233    Launch trybot, get image names, create crosperf experiment file, run
234    crosperf, and copy images into seven-day report directories.
235    """
236    date_str = datetime.date.today()
237    description = "master_%s_%s_%s" % (self._patches_string,
238                                       self._build,
239                                       date_str)
240    trybot_image = buildbot_utils.GetTrybotImage(self._chromeos_root,
241                                                 self._build,
242                                                 self._patches,
243                                                 description,
244                                                 build_toolchain=True)
245
246    vanilla_image = self._ParseVanillaImage(trybot_image)
247
248    print ("trybot_image: %s" % trybot_image)
249    print ("vanilla_image: %s" % vanilla_image)
250    if len(trybot_image) == 0:
251        self._l.LogError("Unable to find trybot_image for %s!" % description)
252        return 1
253    if len(vanilla_image) == 0:
254        self._l.LogError("Unable to find vanilla image for %s!" % description)
255        return 1
256    if os.getlogin() == ROLE_ACCOUNT:
257      self._FinishSetup()
258
259    self._TestImages(trybot_image, vanilla_image)
260    self._SendEmail()
261    if (self._patches_string == USE_NEXT_GCC_PATCH and
262        self._board in WEEKLY_REPORT_BOARDS):
263        # Only try to copy the image files if the test runs ran successfully.
264        self._CopyWeeklyReportFiles(trybot_image, vanilla_image)
265    return 0
266
267
268def Main(argv):
269  """The main function."""
270
271  # Common initializations
272  command_executer.InitCommandExecuter()
273  parser = optparse.OptionParser()
274  parser.add_option("--remote",
275                    dest="remote",
276                    help="Remote machines to run tests on.")
277  parser.add_option("--board",
278                    dest="board",
279                    default="x86-zgb",
280                    help="The target board.")
281  parser.add_option("--chromeos_root",
282                    dest="chromeos_root",
283                    help="The chromeos root from which to run tests.")
284  parser.add_option("--weekday", default="",
285                    dest="weekday",
286                    help="The day of the week for which to run tests.")
287  parser.add_option("--patch",
288                    dest="patches",
289                    help="The patches to use for the testing, "
290                    "seprate the patch numbers with ',' "
291                    "for more than one patches.")
292  parser.add_option("--noschedv2",
293                    dest="noschedv2",
294                    action="store_true",
295                    default=False,
296                    help="Pass --noschedv2 to crosperf.")
297
298  options, _ = parser.parse_args(argv)
299  if not options.board:
300    print "Please give a board."
301    return 1
302  if not options.remote:
303    print "Please give at least one remote machine."
304    return 1
305  if not options.chromeos_root:
306    print "Please specify the ChromeOS root directory."
307    return 1
308  if options.patches:
309    patches = options.patches
310  else:
311    patches = USE_NEXT_GCC_PATCH
312
313  fc = ToolchainComparator(options.board, options.remote,
314                           options.chromeos_root, options.weekday, patches,
315                           options.noschedv2)
316  return fc.DoAll()
317
318
319if __name__ == "__main__":
320  retval = Main(sys.argv)
321  sys.exit(retval)
322