blob: a274cc5c9eacfc7deb8c859f597d9b4c51cd828f [file] [log] [blame]
Yunlian Jiange84ea3d2016-12-12 11:07:40 -08001#!/usr/bin/env python2
Ting-Yuan Huange5819872016-12-15 14:22:26 -08002#
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.
Yunlian Jiang14cf5962015-12-11 15:50:14 -08006"""Script for running nightly compiler tests on ChromeOS.
cmtice46093e52014-12-09 14:59:16 -08007
8This script launches a buildbot to build ChromeOS with the latest compiler on
9a particular board; then it finds and downloads the trybot image and the
10corresponding official image, and runs crosperf performance tests comparing
11the two. It then generates a report, emails it to the c-compiler-chrome, as
12well as copying the images into the seven-day reports directory.
13"""
14
15# Script to test different toolchains against ChromeOS benchmarks.
Yunlian Jiang14cf5962015-12-11 15:50:14 -080016
17from __future__ import print_function
18
Caroline Ticeeddb0632016-04-14 09:19:02 -070019import argparse
cmticece5ffa42015-02-12 15:18:43 -080020import datetime
cmtice46093e52014-12-09 14:59:16 -080021import os
Luis Lozanoc75fd052016-02-19 17:37:01 -080022import re
cmtice46093e52014-12-09 14:59:16 -080023import sys
24import time
cmtice46093e52014-12-09 14:59:16 -080025
Caroline Ticea8af9a72016-07-20 12:52:59 -070026from cros_utils import command_executer
27from cros_utils import logger
cmtice46093e52014-12-09 14:59:16 -080028
Caroline Ticea8af9a72016-07-20 12:52:59 -070029from cros_utils import buildbot_utils
cmtice46093e52014-12-09 14:59:16 -080030
Manoj Guptac4110352016-12-28 13:47:12 -080031# CL that uses LLVM-Next to build the images (includes chrome).
Manoj Gupta66682c72017-05-24 12:19:57 -070032USE_LLVM_NEXT_PATCH = '513590'
Manoj Guptac4110352016-12-28 13:47:12 -080033
Luis Lozanof2a3ef42015-12-15 13:49:30 -080034CROSTC_ROOT = '/usr/local/google/crostc'
35ROLE_ACCOUNT = 'mobiletc-prebuild'
cmtice46093e52014-12-09 14:59:16 -080036TOOLCHAIN_DIR = os.path.dirname(os.path.realpath(__file__))
Luis Lozanof2a3ef42015-12-15 13:49:30 -080037MAIL_PROGRAM = '~/var/bin/mail-sheriff'
Luis Lozanof2a3ef42015-12-15 13:49:30 -080038PENDING_ARCHIVES_DIR = os.path.join(CROSTC_ROOT, 'pending_archives')
39NIGHTLY_TESTS_DIR = os.path.join(CROSTC_ROOT, 'nightly_test_reports')
40
Ting-Yuan Huange5819872016-12-15 14:22:26 -080041IMAGE_DIR = '{board}-{image_type}'
42IMAGE_VERSION_STR = r'{chrome_version}-{tip}\.{branch}\.{branch_branch}'
43IMAGE_FS = IMAGE_DIR + '/' + IMAGE_VERSION_STR
Luis Lozanoc75fd052016-02-19 17:37:01 -080044TRYBOT_IMAGE_FS = 'trybot-' + IMAGE_FS + '-{build_id}'
45PFQ_IMAGE_FS = IMAGE_FS + '-rc1'
Manoj Guptaaee96b72016-10-24 13:43:28 -070046IMAGE_RE_GROUPS = {
47 'board': r'(?P<board>\S+)',
48 'image_type': r'(?P<image_type>\S+)',
49 'chrome_version': r'(?P<chrome_version>R\d+)',
50 'tip': r'(?P<tip>\d+)',
51 'branch': r'(?P<branch>\d+)',
52 'branch_branch': r'(?P<branch_branch>\d+)',
53 'build_id': r'(?P<build_id>b\d+)'
54}
Luis Lozanoc75fd052016-02-19 17:37:01 -080055TRYBOT_IMAGE_RE = TRYBOT_IMAGE_FS.format(**IMAGE_RE_GROUPS)
56
cmtice46093e52014-12-09 14:59:16 -080057
Yunlian Jiang14cf5962015-12-11 15:50:14 -080058class ToolchainComparator(object):
59 """Class for doing the nightly tests work."""
cmtice46093e52014-12-09 14:59:16 -080060
Luis Lozanof2a3ef42015-12-15 13:49:30 -080061 def __init__(self,
62 board,
63 remotes,
64 chromeos_root,
65 weekday,
66 patches,
67 noschedv2=False):
cmtice46093e52014-12-09 14:59:16 -080068 self._board = board
69 self._remotes = remotes
70 self._chromeos_root = chromeos_root
71 self._base_dir = os.getcwd()
72 self._ce = command_executer.GetCommandExecuter()
73 self._l = logger.GetLogger()
Yunlian Jiang73580dd2017-11-21 04:48:40 -080074 self._build = '%s-release-tryjob' % board
Manoj Guptad575b8a2017-03-08 10:51:28 -080075 self._patches = patches.split(',') if patches else []
Yunlian Jiang3c6e4672015-08-24 15:58:22 -070076 self._patches_string = '_'.join(str(p) for p in self._patches)
Han Shen43494292015-09-14 10:26:40 -070077 self._noschedv2 = noschedv2
Yunlian Jiang3c6e4672015-08-24 15:58:22 -070078
cmtice46093e52014-12-09 14:59:16 -080079 if not weekday:
Luis Lozanof2a3ef42015-12-15 13:49:30 -080080 self._weekday = time.strftime('%a')
cmtice46093e52014-12-09 14:59:16 -080081 else:
82 self._weekday = weekday
cmtice7f3190b2015-05-22 14:14:51 -070083 timestamp = datetime.datetime.strftime(datetime.datetime.now(),
Luis Lozanof2a3ef42015-12-15 13:49:30 -080084 '%Y-%m-%d_%H:%M:%S')
Manoj Guptaaee96b72016-10-24 13:43:28 -070085 self._reports_dir = os.path.join(
86 NIGHTLY_TESTS_DIR,
Caroline Tice9c4003a2017-11-07 16:37:33 -080087 '%s.%s' % (timestamp, board),
88 )
cmtice46093e52014-12-09 14:59:16 -080089
Luis Lozanoc75fd052016-02-19 17:37:01 -080090 def _GetVanillaImageName(self, trybot_image):
Ting-Yuan Huange5819872016-12-15 14:22:26 -080091 """Given a trybot artifact name, get latest vanilla image name.
cmtice46093e52014-12-09 14:59:16 -080092
Luis Lozano783954f2015-12-21 18:06:29 -080093 Args:
94 trybot_image: artifact name such as
Yunlian Jiang73580dd2017-11-21 04:48:40 -080095 'trybot-daisy-release-tryjob/R40-6394.0.0-b1389'
Luis Lozano783954f2015-12-21 18:06:29 -080096
97 Returns:
Ting-Yuan Huange5819872016-12-15 14:22:26 -080098 Latest official image name, e.g. 'daisy-release/R57-9089.0.0'.
cmtice46093e52014-12-09 14:59:16 -080099 """
Luis Lozanoc75fd052016-02-19 17:37:01 -0800100 mo = re.search(TRYBOT_IMAGE_RE, trybot_image)
101 assert mo
Ting-Yuan Huange5819872016-12-15 14:22:26 -0800102 dirname = IMAGE_DIR.replace('\\', '').format(**mo.groupdict())
Ting-Yuan Huang99d32c42017-04-24 20:34:43 -0700103 return buildbot_utils.GetLatestImage(self._chromeos_root, dirname)
cmtice46093e52014-12-09 14:59:16 -0800104
Luis Lozanoc75fd052016-02-19 17:37:01 -0800105 def _GetNonAFDOImageName(self, trybot_image):
106 """Given a trybot artifact name, get corresponding non-AFDO image name.
107
108 We get the non-AFDO image from the PFQ builders. This image
109 is not generated for all the boards and, the closest PFQ image
110 was the one build for the previous ChromeOS version (the chrome
111 used in the current version is the one validated in the previous
112 version).
113 The previous ChromeOS does not always exist either. So, we try
114 a couple of versions before.
Luis Lozano783954f2015-12-21 18:06:29 -0800115
116 Args:
117 trybot_image: artifact name such as
Yunlian Jiang73580dd2017-11-21 04:48:40 -0800118 'trybot-daisy-release-tryjob/R40-6394.0.0-b1389'
Luis Lozano783954f2015-12-21 18:06:29 -0800119
120 Returns:
121 Corresponding chrome PFQ image name, e.g.
Luis Lozanoc75fd052016-02-19 17:37:01 -0800122 'daisy-chrome-pfq/R40-6393.0.0-rc1'.
Luis Lozano783954f2015-12-21 18:06:29 -0800123 """
Luis Lozanoc75fd052016-02-19 17:37:01 -0800124 mo = re.search(TRYBOT_IMAGE_RE, trybot_image)
125 assert mo
126 image_dict = mo.groupdict()
127 image_dict['image_type'] = 'chrome-pfq'
128 for _ in xrange(2):
129 image_dict['tip'] = str(int(image_dict['tip']) - 1)
130 nonafdo_image = PFQ_IMAGE_FS.replace('\\', '').format(**image_dict)
131 if buildbot_utils.DoesImageExist(self._chromeos_root, nonafdo_image):
132 return nonafdo_image
133 return ''
Luis Lozano783954f2015-12-21 18:06:29 -0800134
cmtice46093e52014-12-09 14:59:16 -0800135 def _FinishSetup(self):
Yunlian Jiang14cf5962015-12-11 15:50:14 -0800136 """Make sure testing_rsa file is properly set up."""
cmtice46093e52014-12-09 14:59:16 -0800137 # Fix protections on ssh key
Luis Lozanof2a3ef42015-12-15 13:49:30 -0800138 command = ('chmod 600 /var/cache/chromeos-cache/distfiles/target'
139 '/chrome-src-internal/src/third_party/chromite/ssh_keys'
140 '/testing_rsa')
cmtice46093e52014-12-09 14:59:16 -0800141 ret_val = self._ce.ChrootRunCommand(self._chromeos_root, command)
cmtice7f3190b2015-05-22 14:14:51 -0700142 if ret_val != 0:
Luis Lozanof2a3ef42015-12-15 13:49:30 -0800143 raise RuntimeError('chmod for testing_rsa failed')
cmtice46093e52014-12-09 14:59:16 -0800144
Luis Lozano783954f2015-12-21 18:06:29 -0800145 def _TestImages(self, trybot_image, vanilla_image, nonafdo_image):
Yunlian Jiang14cf5962015-12-11 15:50:14 -0800146 """Create crosperf experiment file.
cmtice46093e52014-12-09 14:59:16 -0800147
Luis Lozano783954f2015-12-21 18:06:29 -0800148 Given the names of the trybot, vanilla and non-AFDO images, create the
cmtice46093e52014-12-09 14:59:16 -0800149 appropriate crosperf experiment file and launch crosperf on it.
150 """
Luis Lozanof2a3ef42015-12-15 13:49:30 -0800151 experiment_file_dir = os.path.join(self._chromeos_root, '..', self._weekday)
152 experiment_file_name = '%s_toolchain_experiment.txt' % self._board
Yunlian Jiang2f563562015-08-28 13:54:04 -0700153
Manoj Guptad575b8a2017-03-08 10:51:28 -0800154 compiler_string = 'llvm'
Manoj Guptac4110352016-12-28 13:47:12 -0800155 if USE_LLVM_NEXT_PATCH in self._patches_string:
156 experiment_file_name = '%s_llvm_next_experiment.txt' % self._board
157 compiler_string = 'llvm_next'
Yunlian Jiang2f563562015-08-28 13:54:04 -0700158
Luis Lozanof2a3ef42015-12-15 13:49:30 -0800159 experiment_file = os.path.join(experiment_file_dir, experiment_file_name)
cmtice46093e52014-12-09 14:59:16 -0800160 experiment_header = """
161 board: %s
162 remote: %s
Luis Lozanoe1efeb82015-06-16 16:35:44 -0700163 retries: 1
cmtice46093e52014-12-09 14:59:16 -0800164 """ % (self._board, self._remotes)
165 experiment_tests = """
Luis Lozano1489d642015-12-08 10:08:19 -0800166 benchmark: all_toolchain_perf {
cmtice46093e52014-12-09 14:59:16 -0800167 suite: telemetry_Crosperf
Ting-Yuan Huang83323642017-02-17 12:20:02 -0800168 iterations: 0
Manoj Gupta5a516382017-08-23 12:27:54 -0700169 run_local: False
cmtice46093e52014-12-09 14:59:16 -0800170 }
Caroline Ticee82513b2016-10-27 12:45:15 -0700171
172 benchmark: page_cycler_v2.typical_25 {
173 suite: telemetry_Crosperf
Ting-Yuan Huang83323642017-02-17 12:20:02 -0800174 iterations: 0
Caroline Ticee82513b2016-10-27 12:45:15 -0700175 run_local: False
176 retries: 0
177 }
cmtice46093e52014-12-09 14:59:16 -0800178 """
Luis Lozanof2a3ef42015-12-15 13:49:30 -0800179
180 with open(experiment_file, 'w') as f:
Yunlian Jiang14cf5962015-12-11 15:50:14 -0800181 f.write(experiment_header)
182 f.write(experiment_tests)
cmtice46093e52014-12-09 14:59:16 -0800183
184 # Now add vanilla to test file.
185 official_image = """
186 vanilla_image {
187 chromeos_root: %s
188 build: %s
Yunlian Jiang8c18be12017-03-20 16:52:33 -0700189 compiler: llvm
cmtice46093e52014-12-09 14:59:16 -0800190 }
191 """ % (self._chromeos_root, vanilla_image)
Yunlian Jiang14cf5962015-12-11 15:50:14 -0800192 f.write(official_image)
cmtice46093e52014-12-09 14:59:16 -0800193
Luis Lozano783954f2015-12-21 18:06:29 -0800194 # Now add non-AFDO image to test file.
Luis Lozano439f2b72016-01-08 11:56:02 -0800195 if nonafdo_image:
196 official_nonafdo_image = """
Luis Lozano783954f2015-12-21 18:06:29 -0800197 nonafdo_image {
198 chromeos_root: %s
199 build: %s
Yunlian Jiang8c18be12017-03-20 16:52:33 -0700200 compiler: llvm
Luis Lozano783954f2015-12-21 18:06:29 -0800201 }
202 """ % (self._chromeos_root, nonafdo_image)
Luis Lozano439f2b72016-01-08 11:56:02 -0800203 f.write(official_nonafdo_image)
Luis Lozano783954f2015-12-21 18:06:29 -0800204
Luis Lozanof2a3ef42015-12-15 13:49:30 -0800205 label_string = '%s_trybot_image' % compiler_string
Caroline Tice80eab982015-11-04 14:03:14 -0800206
Manoj Gupta3594db82017-01-31 11:48:57 -0800207 # Reuse autotest files from vanilla image for trybot images
208 autotest_files = os.path.join('/tmp', vanilla_image, 'autotest_files')
cmtice46093e52014-12-09 14:59:16 -0800209 experiment_image = """
Caroline Tice80eab982015-11-04 14:03:14 -0800210 %s {
cmtice46093e52014-12-09 14:59:16 -0800211 chromeos_root: %s
212 build: %s
Manoj Gupta3594db82017-01-31 11:48:57 -0800213 autotest_path: %s
Caroline Ticeddde5052015-09-23 09:43:35 -0700214 compiler: %s
cmtice46093e52014-12-09 14:59:16 -0800215 }
Caroline Tice80eab982015-11-04 14:03:14 -0800216 """ % (label_string, self._chromeos_root, trybot_image,
Manoj Gupta3594db82017-01-31 11:48:57 -0800217 autotest_files, compiler_string)
Yunlian Jiang14cf5962015-12-11 15:50:14 -0800218 f.write(experiment_image)
cmtice46093e52014-12-09 14:59:16 -0800219
Luis Lozanof2a3ef42015-12-15 13:49:30 -0800220 crosperf = os.path.join(TOOLCHAIN_DIR, 'crosperf', 'crosperf')
Han Shen43494292015-09-14 10:26:40 -0700221 noschedv2_opts = '--noschedv2' if self._noschedv2 else ''
Luis Lozanof2a3ef42015-12-15 13:49:30 -0800222 command = ('{crosperf} --no_email=True --results_dir={r_dir} '
Yunlian Jiang4260aae2017-09-06 11:49:15 -0700223 '--json_report=True {noschedv2_opts} {exp_file} '
224 '--locks_dir=/usr/local/google/home/mobiletc-prebuild/locks '
225 '--use_file_locks=True').format(
Yunlian Jiang14cf5962015-12-11 15:50:14 -0800226 crosperf=crosperf,
227 r_dir=self._reports_dir,
228 noschedv2_opts=noschedv2_opts,
229 exp_file=experiment_file)
cmticeaa700b02015-06-12 13:26:47 -0700230
cmtice46093e52014-12-09 14:59:16 -0800231 ret = self._ce.RunCommand(command)
cmtice7f3190b2015-05-22 14:14:51 -0700232 if ret != 0:
Manoj Guptaaee96b72016-10-24 13:43:28 -0700233 raise RuntimeError('Crosperf execution error!')
Caroline Ticeebbc3da2015-09-03 10:27:20 -0700234 else:
235 # Copy json report to pending archives directory.
Luis Lozanof2a3ef42015-12-15 13:49:30 -0800236 command = 'cp %s/*.json %s/.' % (self._reports_dir, PENDING_ARCHIVES_DIR)
Caroline Ticeebbc3da2015-09-03 10:27:20 -0700237 ret = self._ce.RunCommand(command)
cmtice7f3190b2015-05-22 14:14:51 -0700238 return
cmtice46093e52014-12-09 14:59:16 -0800239
cmtice7f3190b2015-05-22 14:14:51 -0700240 def _SendEmail(self):
241 """Find email message generated by crosperf and send it."""
Luis Lozanof2a3ef42015-12-15 13:49:30 -0800242 filename = os.path.join(self._reports_dir, 'msg_body.html')
cmtice7f3190b2015-05-22 14:14:51 -0700243 if (os.path.exists(filename) and
244 os.path.exists(os.path.expanduser(MAIL_PROGRAM))):
Manoj Guptad575b8a2017-03-08 10:51:28 -0800245 email_title = 'buildbot llvm test results'
Manoj Guptac4110352016-12-28 13:47:12 -0800246 if USE_LLVM_NEXT_PATCH in self._patches_string:
247 email_title = 'buildbot llvm_next test results'
Luis Lozanof2a3ef42015-12-15 13:49:30 -0800248 command = ('cat %s | %s -s "%s, %s" -team -html' %
249 (filename, MAIL_PROGRAM, email_title, self._board))
cmtice7f3190b2015-05-22 14:14:51 -0700250 self._ce.RunCommand(command)
cmtice46093e52014-12-09 14:59:16 -0800251
Caroline Tice9c4003a2017-11-07 16:37:33 -0800252 def DoAll(self, crostc_dir):
Yunlian Jiang14cf5962015-12-11 15:50:14 -0800253 """Main function inside ToolchainComparator class.
cmtice46093e52014-12-09 14:59:16 -0800254
255 Launch trybot, get image names, create crosperf experiment file, run
256 crosperf, and copy images into seven-day report directories.
257 """
cmticece5ffa42015-02-12 15:18:43 -0800258 date_str = datetime.date.today()
Luis Lozanof2a3ef42015-12-15 13:49:30 -0800259 description = 'master_%s_%s_%s' % (self._patches_string, self._build,
Han Shenfe054f12015-02-18 15:00:13 -0800260 date_str)
Caroline Tice9c4003a2017-11-07 16:37:33 -0800261 if crostc_dir:
262 build_id, trybot_image = buildbot_utils.GetTrybotImage(
263 self._chromeos_root,
264 self._build,
265 self._patches,
266 description,
267 tryjob_flags=['--notests'],
268 credentials_dir=crostc_dir,
269 build_toolchain=True)
270 else:
271 build_id, trybot_image = buildbot_utils.GetTrybotImage(
272 self._chromeos_root,
273 self._build,
274 self._patches,
275 description,
276 tryjob_flags=['--notests'],
277 build_toolchain=True)
cmtice46093e52014-12-09 14:59:16 -0800278
Yunlian Jiange84ea3d2016-12-12 11:07:40 -0800279 print('trybot_url: \
Manoj Guptac4110352016-12-28 13:47:12 -0800280 https://uberchromegw.corp.google.com/i/chromiumos.tryserver/builders/release/builds/%s'
281 % build_id)
cmticed54f9802015-02-05 11:04:11 -0800282 if len(trybot_image) == 0:
Luis Lozanof2a3ef42015-12-15 13:49:30 -0800283 self._l.LogError('Unable to find trybot_image for %s!' % description)
Yunlian Jiangcdd19072017-09-29 10:15:56 -0700284 return 2
Luis Lozano783954f2015-12-21 18:06:29 -0800285
Luis Lozanoc75fd052016-02-19 17:37:01 -0800286 vanilla_image = self._GetVanillaImageName(trybot_image)
287 nonafdo_image = self._GetNonAFDOImageName(trybot_image)
Luis Lozano783954f2015-12-21 18:06:29 -0800288
289 print('trybot_image: %s' % trybot_image)
290 print('vanilla_image: %s' % vanilla_image)
291 print('nonafdo_image: %s' % nonafdo_image)
Luis Lozanoc75fd052016-02-19 17:37:01 -0800292
cmtice46093e52014-12-09 14:59:16 -0800293 if os.getlogin() == ROLE_ACCOUNT:
294 self._FinishSetup()
295
Luis Lozano783954f2015-12-21 18:06:29 -0800296 self._TestImages(trybot_image, vanilla_image, nonafdo_image)
cmtice7f3190b2015-05-22 14:14:51 -0700297 self._SendEmail()
cmtice46093e52014-12-09 14:59:16 -0800298 return 0
299
300
301def Main(argv):
302 """The main function."""
303
304 # Common initializations
305 command_executer.InitCommandExecuter()
Caroline Ticeeddb0632016-04-14 09:19:02 -0700306 parser = argparse.ArgumentParser()
Manoj Guptaaee96b72016-10-24 13:43:28 -0700307 parser.add_argument(
308 '--remote', dest='remote', help='Remote machines to run tests on.')
309 parser.add_argument(
310 '--board', dest='board', default='x86-zgb', help='The target board.')
311 parser.add_argument(
312 '--chromeos_root',
313 dest='chromeos_root',
314 help='The chromeos root from which to run tests.')
315 parser.add_argument(
316 '--weekday',
317 default='',
318 dest='weekday',
319 help='The day of the week for which to run tests.')
320 parser.add_argument(
321 '--patch',
322 dest='patches',
323 help='The patches to use for the testing, '
324 "seprate the patch numbers with ',' "
325 'for more than one patches.')
326 parser.add_argument(
327 '--noschedv2',
328 dest='noschedv2',
329 action='store_true',
330 default=False,
331 help='Pass --noschedv2 to crosperf.')
Caroline Tice9c4003a2017-11-07 16:37:33 -0800332 parser.add_argument(
333 '--crostc_dir',
334 dest='crostc_dir',
335 help='Path to the directory containing the '
336 'chromeos-toolchain-credentials.json file; normally in the '
337 'crostc repo.')
Han Shen36413122015-08-28 11:05:40 -0700338
Caroline Ticeeddb0632016-04-14 09:19:02 -0700339 options = parser.parse_args(argv[1:])
cmtice46093e52014-12-09 14:59:16 -0800340 if not options.board:
Luis Lozanof2a3ef42015-12-15 13:49:30 -0800341 print('Please give a board.')
cmtice46093e52014-12-09 14:59:16 -0800342 return 1
343 if not options.remote:
Luis Lozanof2a3ef42015-12-15 13:49:30 -0800344 print('Please give at least one remote machine.')
cmtice46093e52014-12-09 14:59:16 -0800345 return 1
346 if not options.chromeos_root:
Luis Lozanof2a3ef42015-12-15 13:49:30 -0800347 print('Please specify the ChromeOS root directory.')
cmtice46093e52014-12-09 14:59:16 -0800348 return 1
Yunlian Jiange52838c2015-08-20 14:32:37 -0700349
Luis Lozanof2a3ef42015-12-15 13:49:30 -0800350 fc = ToolchainComparator(options.board, options.remote, options.chromeos_root,
Manoj Guptad575b8a2017-03-08 10:51:28 -0800351 options.weekday, options.patches, options.noschedv2)
Caroline Tice9c4003a2017-11-07 16:37:33 -0800352 return fc.DoAll(options.crostc_dir)
cmtice46093e52014-12-09 14:59:16 -0800353
354
Luis Lozanof2a3ef42015-12-15 13:49:30 -0800355if __name__ == '__main__':
cmtice46093e52014-12-09 14:59:16 -0800356 retval = Main(sys.argv)
357 sys.exit(retval)