blob: e289b1a59c14f66eb36ada5570a529e6b562833e [file] [log] [blame]
Yunlian Jiange84ea3d2016-12-12 11:07:40 -08001#!/usr/bin/env python2
Tiancong Wangd0348132019-03-07 11:22:48 -08002# -*- coding: utf-8 -*-
Ting-Yuan Huange5819872016-12-15 14:22:26 -08003#
4# Copyright 2016 The Chromium OS Authors. All rights reserved.
5# Use of this source code is governed by a BSD-style license that can be
6# found in the LICENSE file.
Denis Nikitin265c2962019-08-01 19:52:49 -07007
Yunlian Jiang14cf5962015-12-11 15:50:14 -08008"""Script for running nightly compiler tests on ChromeOS.
cmtice46093e52014-12-09 14:59:16 -08009
10This script launches a buildbot to build ChromeOS with the latest compiler on
11a particular board; then it finds and downloads the trybot image and the
12corresponding official image, and runs crosperf performance tests comparing
13the two. It then generates a report, emails it to the c-compiler-chrome, as
14well as copying the images into the seven-day reports directory.
15"""
16
17# Script to test different toolchains against ChromeOS benchmarks.
Yunlian Jiang14cf5962015-12-11 15:50:14 -080018
19from __future__ import print_function
20
Caroline Ticeeddb0632016-04-14 09:19:02 -070021import argparse
cmticece5ffa42015-02-12 15:18:43 -080022import datetime
cmtice46093e52014-12-09 14:59:16 -080023import os
Luis Lozanoc75fd052016-02-19 17:37:01 -080024import re
cmtice46093e52014-12-09 14:59:16 -080025import sys
26import time
cmtice46093e52014-12-09 14:59:16 -080027
Caroline Ticea8af9a72016-07-20 12:52:59 -070028from cros_utils import command_executer
29from cros_utils import logger
cmtice46093e52014-12-09 14:59:16 -080030
Caroline Ticea8af9a72016-07-20 12:52:59 -070031from cros_utils import buildbot_utils
cmtice46093e52014-12-09 14:59:16 -080032
Manoj Guptac4110352016-12-28 13:47:12 -080033# CL that uses LLVM-Next to build the images (includes chrome).
Manoj Gupta66682c72017-05-24 12:19:57 -070034USE_LLVM_NEXT_PATCH = '513590'
Manoj Guptac4110352016-12-28 13:47:12 -080035
Luis Lozanof2a3ef42015-12-15 13:49:30 -080036CROSTC_ROOT = '/usr/local/google/crostc'
37ROLE_ACCOUNT = 'mobiletc-prebuild'
cmtice46093e52014-12-09 14:59:16 -080038TOOLCHAIN_DIR = os.path.dirname(os.path.realpath(__file__))
Luis Lozanof2a3ef42015-12-15 13:49:30 -080039MAIL_PROGRAM = '~/var/bin/mail-sheriff'
Luis Lozanof2a3ef42015-12-15 13:49:30 -080040PENDING_ARCHIVES_DIR = os.path.join(CROSTC_ROOT, 'pending_archives')
41NIGHTLY_TESTS_DIR = os.path.join(CROSTC_ROOT, 'nightly_test_reports')
42
Ting-Yuan Huange5819872016-12-15 14:22:26 -080043IMAGE_DIR = '{board}-{image_type}'
44IMAGE_VERSION_STR = r'{chrome_version}-{tip}\.{branch}\.{branch_branch}'
45IMAGE_FS = IMAGE_DIR + '/' + IMAGE_VERSION_STR
Ting-Yuan Huang112d5622018-03-26 13:49:42 -070046TRYBOT_IMAGE_FS = IMAGE_FS + '-{build_id}'
Luis Lozanoc75fd052016-02-19 17:37:01 -080047PFQ_IMAGE_FS = IMAGE_FS + '-rc1'
Manoj Guptaaee96b72016-10-24 13:43:28 -070048IMAGE_RE_GROUPS = {
49 'board': r'(?P<board>\S+)',
50 'image_type': r'(?P<image_type>\S+)',
51 'chrome_version': r'(?P<chrome_version>R\d+)',
52 'tip': r'(?P<tip>\d+)',
53 'branch': r'(?P<branch>\d+)',
54 'branch_branch': r'(?P<branch_branch>\d+)',
55 'build_id': r'(?P<build_id>b\d+)'
56}
Luis Lozanoc75fd052016-02-19 17:37:01 -080057TRYBOT_IMAGE_RE = TRYBOT_IMAGE_FS.format(**IMAGE_RE_GROUPS)
58
cmtice46093e52014-12-09 14:59:16 -080059
Yunlian Jiang14cf5962015-12-11 15:50:14 -080060class ToolchainComparator(object):
61 """Class for doing the nightly tests work."""
cmtice46093e52014-12-09 14:59:16 -080062
Luis Lozanof2a3ef42015-12-15 13:49:30 -080063 def __init__(self,
64 board,
65 remotes,
66 chromeos_root,
67 weekday,
68 patches,
69 noschedv2=False):
cmtice46093e52014-12-09 14:59:16 -080070 self._board = board
71 self._remotes = remotes
72 self._chromeos_root = chromeos_root
73 self._base_dir = os.getcwd()
74 self._ce = command_executer.GetCommandExecuter()
75 self._l = logger.GetLogger()
Yunlian Jiang73580dd2017-11-21 04:48:40 -080076 self._build = '%s-release-tryjob' % board
Manoj Guptad575b8a2017-03-08 10:51:28 -080077 self._patches = patches.split(',') if patches else []
Yunlian Jiang3c6e4672015-08-24 15:58:22 -070078 self._patches_string = '_'.join(str(p) for p in self._patches)
Han Shen43494292015-09-14 10:26:40 -070079 self._noschedv2 = noschedv2
Yunlian Jiang3c6e4672015-08-24 15:58:22 -070080
cmtice46093e52014-12-09 14:59:16 -080081 if not weekday:
Luis Lozanof2a3ef42015-12-15 13:49:30 -080082 self._weekday = time.strftime('%a')
cmtice46093e52014-12-09 14:59:16 -080083 else:
84 self._weekday = weekday
cmtice7f3190b2015-05-22 14:14:51 -070085 timestamp = datetime.datetime.strftime(datetime.datetime.now(),
Luis Lozanof2a3ef42015-12-15 13:49:30 -080086 '%Y-%m-%d_%H:%M:%S')
Manoj Guptaaee96b72016-10-24 13:43:28 -070087 self._reports_dir = os.path.join(
88 NIGHTLY_TESTS_DIR,
Caroline Tice9c4003a2017-11-07 16:37:33 -080089 '%s.%s' % (timestamp, board),
90 )
cmtice46093e52014-12-09 14:59:16 -080091
Luis Lozanoc75fd052016-02-19 17:37:01 -080092 def _GetVanillaImageName(self, trybot_image):
Ting-Yuan Huange5819872016-12-15 14:22:26 -080093 """Given a trybot artifact name, get latest vanilla image name.
cmtice46093e52014-12-09 14:59:16 -080094
Luis Lozano783954f2015-12-21 18:06:29 -080095 Args:
96 trybot_image: artifact name such as
Caroline Tice219e3b72018-12-18 15:54:49 -080097 'daisy-release-tryjob/R40-6394.0.0-b1389'
Luis Lozano783954f2015-12-21 18:06:29 -080098
99 Returns:
Ting-Yuan Huange5819872016-12-15 14:22:26 -0800100 Latest official image name, e.g. 'daisy-release/R57-9089.0.0'.
cmtice46093e52014-12-09 14:59:16 -0800101 """
Yunlian Jiang2e0ad052017-11-22 14:06:45 -0800102 # We need to filter out -tryjob in the trybot_image.
103 trybot = re.sub('-tryjob', '', trybot_image)
104 mo = re.search(TRYBOT_IMAGE_RE, trybot)
Luis Lozanoc75fd052016-02-19 17:37:01 -0800105 assert mo
Ting-Yuan Huange5819872016-12-15 14:22:26 -0800106 dirname = IMAGE_DIR.replace('\\', '').format(**mo.groupdict())
Ting-Yuan Huang99d32c42017-04-24 20:34:43 -0700107 return buildbot_utils.GetLatestImage(self._chromeos_root, dirname)
cmtice46093e52014-12-09 14:59:16 -0800108
Luis Lozanoc75fd052016-02-19 17:37:01 -0800109 def _GetNonAFDOImageName(self, trybot_image):
110 """Given a trybot artifact name, get corresponding non-AFDO image name.
111
112 We get the non-AFDO image from the PFQ builders. This image
113 is not generated for all the boards and, the closest PFQ image
114 was the one build for the previous ChromeOS version (the chrome
115 used in the current version is the one validated in the previous
116 version).
117 The previous ChromeOS does not always exist either. So, we try
118 a couple of versions before.
Luis Lozano783954f2015-12-21 18:06:29 -0800119
120 Args:
121 trybot_image: artifact name such as
Caroline Tice219e3b72018-12-18 15:54:49 -0800122 'daisy-release-tryjob/R40-6394.0.0-b1389'
Luis Lozano783954f2015-12-21 18:06:29 -0800123
124 Returns:
125 Corresponding chrome PFQ image name, e.g.
Luis Lozanoc75fd052016-02-19 17:37:01 -0800126 'daisy-chrome-pfq/R40-6393.0.0-rc1'.
Luis Lozano783954f2015-12-21 18:06:29 -0800127 """
Yunlian Jiang2e0ad052017-11-22 14:06:45 -0800128 trybot = re.sub('-tryjob', '', trybot_image)
129 mo = re.search(TRYBOT_IMAGE_RE, trybot)
Luis Lozanoc75fd052016-02-19 17:37:01 -0800130 assert mo
131 image_dict = mo.groupdict()
132 image_dict['image_type'] = 'chrome-pfq'
George Burgess IV9a6dae82019-07-24 23:20:57 -0700133 for _ in range(2):
Luis Lozanoc75fd052016-02-19 17:37:01 -0800134 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 ''
Luis Lozano783954f2015-12-21 18:06:29 -0800139
Luis Lozano783954f2015-12-21 18:06:29 -0800140 def _TestImages(self, trybot_image, vanilla_image, nonafdo_image):
Yunlian Jiang14cf5962015-12-11 15:50:14 -0800141 """Create crosperf experiment file.
cmtice46093e52014-12-09 14:59:16 -0800142
Luis Lozano783954f2015-12-21 18:06:29 -0800143 Given the names of the trybot, vanilla and non-AFDO images, create the
cmtice46093e52014-12-09 14:59:16 -0800144 appropriate crosperf experiment file and launch crosperf on it.
145 """
Caroline Ticead16df92019-08-02 11:09:25 -0700146 experiment_file_dir = os.path.join(CROSTC_ROOT, self._weekday)
Luis Lozanof2a3ef42015-12-15 13:49:30 -0800147 experiment_file_name = '%s_toolchain_experiment.txt' % self._board
Yunlian Jiang2f563562015-08-28 13:54:04 -0700148
Manoj Guptad575b8a2017-03-08 10:51:28 -0800149 compiler_string = 'llvm'
Manoj Guptac4110352016-12-28 13:47:12 -0800150 if USE_LLVM_NEXT_PATCH in self._patches_string:
151 experiment_file_name = '%s_llvm_next_experiment.txt' % self._board
152 compiler_string = 'llvm_next'
Yunlian Jiang2f563562015-08-28 13:54:04 -0700153
Luis Lozanof2a3ef42015-12-15 13:49:30 -0800154 experiment_file = os.path.join(experiment_file_dir, experiment_file_name)
cmtice46093e52014-12-09 14:59:16 -0800155 experiment_header = """
156 board: %s
157 remote: %s
Luis Lozanoe1efeb82015-06-16 16:35:44 -0700158 retries: 1
Denis Nikitind7cded22019-09-12 13:38:50 -0700159 cooldown_temp: 40
160 cooldown_time: 10
Denis Nikitin858e1452019-09-16 15:30:05 -0700161 cpu_freq_pct: 95
Denis Nikitin30a061f2019-09-27 09:04:31 -0700162 top_interval: 1
cmtice46093e52014-12-09 14:59:16 -0800163 """ % (self._board, self._remotes)
164 experiment_tests = """
Luis Lozano1489d642015-12-08 10:08:19 -0800165 benchmark: all_toolchain_perf {
cmtice46093e52014-12-09 14:59:16 -0800166 suite: telemetry_Crosperf
Tiancong Wangd0348132019-03-07 11:22:48 -0800167 iterations: 5
Manoj Gupta5a516382017-08-23 12:27:54 -0700168 run_local: False
cmtice46093e52014-12-09 14:59:16 -0800169 }
Caroline Ticee82513b2016-10-27 12:45:15 -0700170
Caroline Tice219e3b72018-12-18 15:54:49 -0800171 benchmark: loading.desktop {
Caroline Ticee82513b2016-10-27 12:45:15 -0700172 suite: telemetry_Crosperf
Caroline Tice219e3b72018-12-18 15:54:49 -0800173 test_args: --story-tag-filter=typical
174 iterations: 3
Caroline Ticee82513b2016-10-27 12:45:15 -0700175 run_local: False
176 retries: 0
177 }
Tiancong Wangd8bf2812019-08-30 11:34:05 -0700178
179 benchmark: rendering.desktop {
180 run_local: False
181 suite: telemetry_Crosperf
182 test_args: --story-filter=aquarium$
183 iterations: 5
184 }
185
186 benchmark: rendering.desktop {
187 run_local: False
188 suite: telemetry_Crosperf
189 test_args: --story-filter=aquarium_20k$
190 iterations: 3
191 }
cmtice46093e52014-12-09 14:59:16 -0800192 """
Luis Lozanof2a3ef42015-12-15 13:49:30 -0800193
194 with open(experiment_file, 'w') as f:
Yunlian Jiang14cf5962015-12-11 15:50:14 -0800195 f.write(experiment_header)
196 f.write(experiment_tests)
cmtice46093e52014-12-09 14:59:16 -0800197
198 # Now add vanilla to test file.
Yunlian Jiangfceaba32018-06-06 09:08:44 -0700199 official_image = """
cmtice46093e52014-12-09 14:59:16 -0800200 vanilla_image {
201 chromeos_root: %s
202 build: %s
Yunlian Jiang8c18be12017-03-20 16:52:33 -0700203 compiler: llvm
cmtice46093e52014-12-09 14:59:16 -0800204 }
205 """ % (self._chromeos_root, vanilla_image)
Yunlian Jiang14cf5962015-12-11 15:50:14 -0800206 f.write(official_image)
cmtice46093e52014-12-09 14:59:16 -0800207
Luis Lozano783954f2015-12-21 18:06:29 -0800208 # Now add non-AFDO image to test file.
Luis Lozano439f2b72016-01-08 11:56:02 -0800209 if nonafdo_image:
Yunlian Jiangfceaba32018-06-06 09:08:44 -0700210 official_nonafdo_image = """
Luis Lozano783954f2015-12-21 18:06:29 -0800211 nonafdo_image {
212 chromeos_root: %s
213 build: %s
Yunlian Jiang8c18be12017-03-20 16:52:33 -0700214 compiler: llvm
Luis Lozano783954f2015-12-21 18:06:29 -0800215 }
216 """ % (self._chromeos_root, nonafdo_image)
Luis Lozano439f2b72016-01-08 11:56:02 -0800217 f.write(official_nonafdo_image)
Luis Lozano783954f2015-12-21 18:06:29 -0800218
Luis Lozanof2a3ef42015-12-15 13:49:30 -0800219 label_string = '%s_trybot_image' % compiler_string
Caroline Tice80eab982015-11-04 14:03:14 -0800220
Manoj Gupta3594db82017-01-31 11:48:57 -0800221 # Reuse autotest files from vanilla image for trybot images
222 autotest_files = os.path.join('/tmp', vanilla_image, 'autotest_files')
Yunlian Jiangfceaba32018-06-06 09:08:44 -0700223 experiment_image = """
Caroline Tice80eab982015-11-04 14:03:14 -0800224 %s {
cmtice46093e52014-12-09 14:59:16 -0800225 chromeos_root: %s
226 build: %s
Manoj Gupta3594db82017-01-31 11:48:57 -0800227 autotest_path: %s
Caroline Ticeddde5052015-09-23 09:43:35 -0700228 compiler: %s
cmtice46093e52014-12-09 14:59:16 -0800229 }
Caroline Tice80eab982015-11-04 14:03:14 -0800230 """ % (label_string, self._chromeos_root, trybot_image,
Manoj Gupta3594db82017-01-31 11:48:57 -0800231 autotest_files, compiler_string)
Yunlian Jiang14cf5962015-12-11 15:50:14 -0800232 f.write(experiment_image)
cmtice46093e52014-12-09 14:59:16 -0800233
Luis Lozanof2a3ef42015-12-15 13:49:30 -0800234 crosperf = os.path.join(TOOLCHAIN_DIR, 'crosperf', 'crosperf')
Han Shen43494292015-09-14 10:26:40 -0700235 noschedv2_opts = '--noschedv2' if self._noschedv2 else ''
Denis Nikitin265c2962019-08-01 19:52:49 -0700236 command = ('{crosperf} --no_email=True --results_dir={r_dir} --no_hwp '
Ting-Yuan Huangb1afe3f2018-08-16 21:03:50 +0000237 '--json_report=True {noschedv2_opts} {exp_file}').format(
Yunlian Jiang14cf5962015-12-11 15:50:14 -0800238 crosperf=crosperf,
239 r_dir=self._reports_dir,
240 noschedv2_opts=noschedv2_opts,
241 exp_file=experiment_file)
cmticeaa700b02015-06-12 13:26:47 -0700242
cmtice46093e52014-12-09 14:59:16 -0800243 ret = self._ce.RunCommand(command)
cmtice7f3190b2015-05-22 14:14:51 -0700244 if ret != 0:
Manoj Guptaaee96b72016-10-24 13:43:28 -0700245 raise RuntimeError('Crosperf execution error!')
Caroline Ticeebbc3da2015-09-03 10:27:20 -0700246 else:
247 # Copy json report to pending archives directory.
Luis Lozanof2a3ef42015-12-15 13:49:30 -0800248 command = 'cp %s/*.json %s/.' % (self._reports_dir, PENDING_ARCHIVES_DIR)
Caroline Ticeebbc3da2015-09-03 10:27:20 -0700249 ret = self._ce.RunCommand(command)
cmtice7f3190b2015-05-22 14:14:51 -0700250 return
cmtice46093e52014-12-09 14:59:16 -0800251
cmtice7f3190b2015-05-22 14:14:51 -0700252 def _SendEmail(self):
253 """Find email message generated by crosperf and send it."""
Luis Lozanof2a3ef42015-12-15 13:49:30 -0800254 filename = os.path.join(self._reports_dir, 'msg_body.html')
cmtice7f3190b2015-05-22 14:14:51 -0700255 if (os.path.exists(filename) and
256 os.path.exists(os.path.expanduser(MAIL_PROGRAM))):
Manoj Guptad575b8a2017-03-08 10:51:28 -0800257 email_title = 'buildbot llvm test results'
Manoj Guptac4110352016-12-28 13:47:12 -0800258 if USE_LLVM_NEXT_PATCH in self._patches_string:
259 email_title = 'buildbot llvm_next test results'
Luis Lozanof2a3ef42015-12-15 13:49:30 -0800260 command = ('cat %s | %s -s "%s, %s" -team -html' %
261 (filename, MAIL_PROGRAM, email_title, self._board))
cmtice7f3190b2015-05-22 14:14:51 -0700262 self._ce.RunCommand(command)
cmtice46093e52014-12-09 14:59:16 -0800263
Ting-Yuan Huang6a9a98a2018-03-07 17:35:13 -0800264 def DoAll(self):
Yunlian Jiang14cf5962015-12-11 15:50:14 -0800265 """Main function inside ToolchainComparator class.
cmtice46093e52014-12-09 14:59:16 -0800266
267 Launch trybot, get image names, create crosperf experiment file, run
268 crosperf, and copy images into seven-day report directories.
269 """
Ting-Yuan Huang6a9a98a2018-03-07 17:35:13 -0800270 buildbucket_id, trybot_image = buildbot_utils.GetTrybotImage(
271 self._chromeos_root,
272 self._build,
273 self._patches,
Jian Cai9d576402019-06-17 20:49:35 +0000274 tryjob_flags=['--notests'],
Ting-Yuan Huang6a9a98a2018-03-07 17:35:13 -0800275 build_toolchain=True)
cmtice46093e52014-12-09 14:59:16 -0800276
Yunlian Jiange84ea3d2016-12-12 11:07:40 -0800277 print('trybot_url: \
Ting-Yuan Huang6a9a98a2018-03-07 17:35:13 -0800278 http://cros-goldeneye/chromeos/healthmonitoring/buildDetails?buildbucketId=%s'
279 % buildbucket_id)
cmticed54f9802015-02-05 11:04:11 -0800280 if len(trybot_image) == 0:
Caroline Ticeefb79902018-04-18 11:23:01 -0700281 self._l.LogError('Unable to find trybot_image!')
Yunlian Jiangcdd19072017-09-29 10:15:56 -0700282 return 2
Luis Lozano783954f2015-12-21 18:06:29 -0800283
Luis Lozanoc75fd052016-02-19 17:37:01 -0800284 vanilla_image = self._GetVanillaImageName(trybot_image)
285 nonafdo_image = self._GetNonAFDOImageName(trybot_image)
Luis Lozano783954f2015-12-21 18:06:29 -0800286
287 print('trybot_image: %s' % trybot_image)
288 print('vanilla_image: %s' % vanilla_image)
289 print('nonafdo_image: %s' % nonafdo_image)
Luis Lozanoc75fd052016-02-19 17:37:01 -0800290
Luis Lozano783954f2015-12-21 18:06:29 -0800291 self._TestImages(trybot_image, vanilla_image, nonafdo_image)
cmtice7f3190b2015-05-22 14:14:51 -0700292 self._SendEmail()
cmtice46093e52014-12-09 14:59:16 -0800293 return 0
294
295
296def Main(argv):
297 """The main function."""
298
299 # Common initializations
300 command_executer.InitCommandExecuter()
Caroline Ticeeddb0632016-04-14 09:19:02 -0700301 parser = argparse.ArgumentParser()
Manoj Guptaaee96b72016-10-24 13:43:28 -0700302 parser.add_argument(
303 '--remote', dest='remote', help='Remote machines to run tests on.')
304 parser.add_argument(
305 '--board', dest='board', default='x86-zgb', help='The target board.')
306 parser.add_argument(
307 '--chromeos_root',
308 dest='chromeos_root',
309 help='The chromeos root from which to run tests.')
310 parser.add_argument(
311 '--weekday',
312 default='',
313 dest='weekday',
314 help='The day of the week for which to run tests.')
315 parser.add_argument(
316 '--patch',
317 dest='patches',
318 help='The patches to use for the testing, '
319 "seprate the patch numbers with ',' "
320 'for more than one patches.')
321 parser.add_argument(
322 '--noschedv2',
323 dest='noschedv2',
324 action='store_true',
325 default=False,
326 help='Pass --noschedv2 to crosperf.')
Han Shen36413122015-08-28 11:05:40 -0700327
Caroline Ticeeddb0632016-04-14 09:19:02 -0700328 options = parser.parse_args(argv[1:])
cmtice46093e52014-12-09 14:59:16 -0800329 if not options.board:
Luis Lozanof2a3ef42015-12-15 13:49:30 -0800330 print('Please give a board.')
cmtice46093e52014-12-09 14:59:16 -0800331 return 1
332 if not options.remote:
Luis Lozanof2a3ef42015-12-15 13:49:30 -0800333 print('Please give at least one remote machine.')
cmtice46093e52014-12-09 14:59:16 -0800334 return 1
335 if not options.chromeos_root:
Luis Lozanof2a3ef42015-12-15 13:49:30 -0800336 print('Please specify the ChromeOS root directory.')
cmtice46093e52014-12-09 14:59:16 -0800337 return 1
Yunlian Jiange52838c2015-08-20 14:32:37 -0700338
Luis Lozanof2a3ef42015-12-15 13:49:30 -0800339 fc = ToolchainComparator(options.board, options.remote, options.chromeos_root,
Manoj Guptad575b8a2017-03-08 10:51:28 -0800340 options.weekday, options.patches, options.noschedv2)
Ting-Yuan Huang6a9a98a2018-03-07 17:35:13 -0800341 return fc.DoAll()
cmtice46093e52014-12-09 14:59:16 -0800342
343
Luis Lozanof2a3ef42015-12-15 13:49:30 -0800344if __name__ == '__main__':
cmtice46093e52014-12-09 14:59:16 -0800345 retval = Main(sys.argv)
346 sys.exit(retval)