OLD | NEW |
---|---|
1 #!/usr/bin/env python | 1 #!/usr/bin/env python |
borenet
2016/03/30 14:02:09
This file can be deleted entirely once download_sk
| |
2 # | 2 # |
3 # Copyright 2016 Google Inc. | 3 # Copyright 2016 Google Inc. |
4 # | 4 # |
5 # Use of this source code is governed by a BSD-style license that can be | 5 # Use of this source code is governed by a BSD-style license that can be |
6 # found in the LICENSE file. | 6 # found in the LICENSE file. |
7 | 7 |
8 | 8 |
9 import contextlib | |
10 import glob | |
11 import math | |
12 import os | 9 import os |
13 import psutil | |
14 import shutil | 10 import shutil |
15 import socket | |
16 import subprocess | 11 import subprocess |
17 import sys | |
18 import time | |
19 import urllib2 | |
20 | |
21 from flavor import android_flavor | |
22 from flavor import chromeos_flavor | |
23 from flavor import cmake_flavor | |
24 from flavor import coverage_flavor | |
25 from flavor import default_flavor | |
26 from flavor import ios_flavor | |
27 from flavor import valgrind_flavor | |
28 from flavor import xsan_flavor | |
29 | 12 |
30 | 13 |
31 CONFIG_COVERAGE = 'Coverage' | |
32 CONFIG_DEBUG = 'Debug' | |
33 CONFIG_RELEASE = 'Release' | |
34 VALID_CONFIGS = (CONFIG_COVERAGE, CONFIG_DEBUG, CONFIG_RELEASE) | |
35 | |
36 BUILD_PRODUCTS_WHITELIST = [ | |
37 'dm', | |
38 'dm.exe', | |
39 'nanobench', | |
40 'nanobench.exe', | |
41 '*.so', | |
42 '*.dll', | |
43 ] | |
44 | |
45 GM_ACTUAL_FILENAME = 'actual-results.json' | |
46 GM_EXPECTATIONS_FILENAME = 'expected-results.json' | |
47 GM_IGNORE_TESTS_FILENAME = 'ignored-tests.txt' | |
48 | |
49 GOLD_UNINTERESTING_HASHES_URL = 'https://gold.skia.org/_/hashes' | |
50 | |
51 GS_GM_BUCKET = 'chromium-skia-gm' | 14 GS_GM_BUCKET = 'chromium-skia-gm' |
52 GS_SUMMARIES_BUCKET = 'chromium-skia-gm-summaries' | |
53 | 15 |
54 GS_SUBDIR_TMPL_SK_IMAGE = 'skimage/v%s' | 16 GS_SUBDIR_TMPL_SK_IMAGE = 'skimage/v%s' |
55 GS_SUBDIR_TMPL_SKP = 'playback_%s/skps' | 17 GS_SUBDIR_TMPL_SKP = 'playback_%s/skps' |
56 | 18 |
57 SKIA_REPO = 'https://skia.googlesource.com/skia.git' | |
58 INFRA_REPO = 'https://skia.googlesource.com/buildbot.git' | |
59 | |
60 SERVICE_ACCOUNT_FILE = 'service-account-skia.json' | |
61 SERVICE_ACCOUNT_INTERNAL_FILE = 'service-account-skia-internal.json' | |
62 | |
63 VERSION_FILE_SK_IMAGE = 'SK_IMAGE_VERSION' | 19 VERSION_FILE_SK_IMAGE = 'SK_IMAGE_VERSION' |
64 VERSION_FILE_SKP = 'SKP_VERSION' | 20 VERSION_FILE_SKP = 'SKP_VERSION' |
65 | 21 |
66 | 22 |
67 def is_android(bot_cfg): | |
68 """Determine whether the given bot is an Android bot.""" | |
69 return ('Android' in bot_cfg.get('extra_config', '') or | |
70 bot_cfg.get('os') == 'Android') | |
71 | |
72 def is_chromeos(bot_cfg): | |
73 return ('CrOS' in bot_cfg.get('extra_config', '') or | |
74 bot_cfg.get('os') == 'ChromeOS') | |
75 | |
76 def is_cmake(bot_cfg): | |
77 return 'CMake' in bot_cfg.get('extra_config', '') | |
78 | |
79 def is_ios(bot_cfg): | |
80 return ('iOS' in bot_cfg.get('extra_config', '') or | |
81 bot_cfg.get('os') == 'iOS') | |
82 | |
83 | |
84 def is_valgrind(bot_cfg): | |
85 return 'Valgrind' in bot_cfg.get('extra_config', '') | |
86 | |
87 | |
88 def is_xsan(bot_cfg): | |
89 return (bot_cfg.get('extra_config') == 'ASAN' or | |
90 bot_cfg.get('extra_config') == 'MSAN' or | |
91 bot_cfg.get('extra_config') == 'TSAN') | |
92 | |
93 | |
94 def download_dir(skia_dir, tmp_dir, version_file, gs_path_tmpl, dst_dir): | 23 def download_dir(skia_dir, tmp_dir, version_file, gs_path_tmpl, dst_dir): |
95 # Ensure that the tmp_dir exists. | 24 # Ensure that the tmp_dir exists. |
96 if not os.path.isdir(tmp_dir): | 25 if not os.path.isdir(tmp_dir): |
97 os.makedirs(tmp_dir) | 26 os.makedirs(tmp_dir) |
98 | 27 |
99 # Get the expected version. | 28 # Get the expected version. |
100 with open(os.path.join(skia_dir, version_file)) as f: | 29 with open(os.path.join(skia_dir, version_file)) as f: |
101 expected_version = f.read().rstrip() | 30 expected_version = f.read().rstrip() |
102 | 31 |
103 print 'Expected %s = %s' % (version_file, expected_version) | 32 print 'Expected %s = %s' % (version_file, expected_version) |
(...skipping 13 matching lines...) Expand all Loading... | |
117 if actual_version != -1: | 46 if actual_version != -1: |
118 os.remove(actual_version_file) | 47 os.remove(actual_version_file) |
119 if os.path.isdir(dst_dir): | 48 if os.path.isdir(dst_dir): |
120 shutil.rmtree(dst_dir) | 49 shutil.rmtree(dst_dir) |
121 os.makedirs(dst_dir) | 50 os.makedirs(dst_dir) |
122 gs_path = 'gs://%s/%s/*' % (GS_GM_BUCKET, gs_path_tmpl % expected_version) | 51 gs_path = 'gs://%s/%s/*' % (GS_GM_BUCKET, gs_path_tmpl % expected_version) |
123 print 'Downloading from %s' % gs_path | 52 print 'Downloading from %s' % gs_path |
124 subprocess.check_call(['gsutil', 'cp', '-R', gs_path, dst_dir]) | 53 subprocess.check_call(['gsutil', 'cp', '-R', gs_path, dst_dir]) |
125 with open(actual_version_file, 'w') as f: | 54 with open(actual_version_file, 'w') as f: |
126 f.write(expected_version) | 55 f.write(expected_version) |
127 | |
128 | |
129 def get_uninteresting_hashes(hashes_file): | |
130 retries = 5 | |
131 timeout = 60 | |
132 wait_base = 15 | |
133 | |
134 socket.setdefaulttimeout(timeout) | |
135 for retry in range(retries): | |
136 try: | |
137 with contextlib.closing( | |
138 urllib2.urlopen(GOLD_UNINTERESTING_HASHES_URL, timeout=timeout)) as w: | |
139 hashes = w.read() | |
140 with open(hashes_file, 'w') as f: | |
141 f.write(hashes) | |
142 break | |
143 except Exception as e: | |
144 print >> sys.stderr, 'Failed to get uninteresting hashes from %s:\n%s' % ( | |
145 GOLD_UNINTERESTING_HASHES_URL, e) | |
146 if retry == retries: | |
147 raise | |
148 waittime = wait_base * math.pow(2, retry) | |
149 print 'Retry in %d seconds.' % waittime | |
150 time.sleep(waittime) | |
151 | |
152 | |
153 class BotInfo(object): | |
154 def __init__(self, bot_name, swarm_out_dir): | |
155 """Initialize the bot, given its name. | |
156 | |
157 Assumes that CWD is the directory containing this file. | |
158 """ | |
159 self.name = bot_name | |
160 self.skia_dir = os.path.abspath(os.path.join( | |
161 os.path.dirname(os.path.realpath(__file__)), | |
162 os.pardir, os.pardir)) | |
163 self.swarm_out_dir = swarm_out_dir | |
164 os.chdir(self.skia_dir) | |
165 self.build_dir = os.path.abspath(os.path.join(self.skia_dir, os.pardir)) | |
166 self.infrabots_dir = os.path.join(self.skia_dir, 'infra', 'bots') | |
167 self.home_dir = os.path.expanduser('~') | |
168 | |
169 self.spec = self.get_bot_spec(bot_name) | |
170 self.bot_cfg = self.spec['builder_cfg'] | |
171 self.out_dir = os.path.join(os.pardir, 'out') | |
172 self.configuration = self.spec['configuration'] | |
173 self.default_env = { | |
174 'CHROME_HEADLESS': '1', | |
175 'SKIA_OUT': self.out_dir, | |
176 'BUILDTYPE': self.configuration, | |
177 'PATH': os.environ['PATH'], | |
178 } | |
179 if 'Win' in self.bot_cfg['os']: | |
180 self.default_env['SystemRoot'] = 'C:\\Windows' | |
181 self.default_env['TEMP'] = os.path.join( | |
182 self.home_dir, 'AppData', 'Local', 'Temp') | |
183 self.default_env['TMP'] = self.default_env['TEMP'] | |
184 self.default_env.update(self.spec['env']) | |
185 self.build_targets = [str(t) for t in self.spec['build_targets']] | |
186 self.is_trybot = self.bot_cfg['is_trybot'] | |
187 self.upload_dm_results = self.spec['upload_dm_results'] | |
188 self.upload_perf_results = self.spec['upload_perf_results'] | |
189 self.perf_data_dir = os.path.join(self.swarm_out_dir, 'perfdata', | |
190 self.name, 'data') | |
191 self.resource_dir = os.path.join(self.skia_dir, 'resources') | |
192 self.images_dir = os.path.join(self.build_dir, 'images') | |
193 self.local_skp_dir = os.path.join(self.build_dir, 'playback', 'skps') | |
194 self.dm_flags = self.spec['dm_flags'] | |
195 self.nanobench_flags = self.spec['nanobench_flags'] | |
196 self._ccache = None | |
197 self._checked_for_ccache = False | |
198 self._already_ran = {} | |
199 self.tmp_dir = os.path.join(self.build_dir, 'tmp') | |
200 self.flavor = self.get_flavor(self.bot_cfg) | |
201 | |
202 # These get filled in during subsequent steps. | |
203 self.device_dirs = None | |
204 self.build_number = None | |
205 self.got_revision = None | |
206 self.master_name = None | |
207 self.slave_name = None | |
208 | |
209 @property | |
210 def ccache(self): | |
211 if not self._checked_for_ccache: | |
212 self._checked_for_ccache = True | |
213 if sys.platform != 'win32': | |
214 try: | |
215 result = subprocess.check_output(['which', 'ccache']) | |
216 self._ccache = result.rstrip() | |
217 except subprocess.CalledProcessError: | |
218 pass | |
219 | |
220 return self._ccache | |
221 | |
222 def get_bot_spec(self, bot_name): | |
223 """Retrieve the bot spec for this bot.""" | |
224 sys.path.append(self.skia_dir) | |
225 from tools import buildbot_spec | |
226 return buildbot_spec.get_builder_spec(bot_name) | |
227 | |
228 def get_flavor(self, bot_cfg): | |
229 """Return a flavor utils object specific to the given bot.""" | |
230 if is_android(bot_cfg): | |
231 return android_flavor.AndroidFlavorUtils(self) | |
232 elif is_chromeos(bot_cfg): | |
233 return chromeos_flavor.ChromeOSFlavorUtils(self) | |
234 elif is_cmake(bot_cfg): | |
235 return cmake_flavor.CMakeFlavorUtils(self) | |
236 elif is_ios(bot_cfg): | |
237 return ios_flavor.iOSFlavorUtils(self) | |
238 elif is_valgrind(bot_cfg): | |
239 return valgrind_flavor.ValgrindFlavorUtils(self) | |
240 elif is_xsan(bot_cfg): | |
241 return xsan_flavor.XSanFlavorUtils(self) | |
242 elif bot_cfg.get('configuration') == CONFIG_COVERAGE: | |
243 return coverage_flavor.CoverageFlavorUtils(self) | |
244 else: | |
245 return default_flavor.DefaultFlavorUtils(self) | |
246 | |
247 def run(self, cmd, env=None, cwd=None): | |
248 _env = {} | |
249 _env.update(self.default_env) | |
250 _env.update(env or {}) | |
251 cwd = cwd or self.skia_dir | |
252 print '============' | |
253 print 'CMD: %s' % cmd | |
254 print 'CWD: %s' % cwd | |
255 print 'ENV: %s' % _env | |
256 print '============' | |
257 subprocess.check_call(cmd, env=_env, cwd=cwd) | |
258 | |
259 def compile_steps(self): | |
260 for t in self.build_targets: | |
261 self.flavor.compile(t) | |
262 dst = os.path.join(self.swarm_out_dir, 'out', self.configuration) | |
263 os.makedirs(dst) | |
264 for pattern in BUILD_PRODUCTS_WHITELIST: | |
265 path = os.path.join(self.out_dir, self.configuration, pattern) | |
266 for f in glob.glob(path): | |
267 print 'Copying build product %s' % f | |
268 shutil.copy(f, dst) | |
269 self.cleanup() | |
270 | |
271 def _run_once(self, fn, *args, **kwargs): | |
272 if not fn.__name__ in self._already_ran: | |
273 self._already_ran[fn.__name__] = True | |
274 fn(*args, **kwargs) | |
275 | |
276 def install(self): | |
277 """Copy the required executables and files to the device.""" | |
278 self.device_dirs = self.flavor.get_device_dirs() | |
279 | |
280 # Run any device-specific installation. | |
281 self.flavor.install() | |
282 | |
283 # TODO(borenet): Only copy files which have changed. | |
284 # Resources | |
285 self.flavor.copy_directory_contents_to_device(self.resource_dir, | |
286 self.device_dirs.resource_dir) | |
287 | |
288 def _key_params(self): | |
289 """Build a unique key from the builder name (as a list). | |
290 | |
291 E.g. arch x86 gpu GeForce320M mode MacMini4.1 os Mac10.6 | |
292 """ | |
293 # Don't bother to include role, which is always Test. | |
294 # TryBots are uploaded elsewhere so they can use the same key. | |
295 blacklist = ['role', 'is_trybot'] | |
296 | |
297 flat = [] | |
298 for k in sorted(self.bot_cfg.keys()): | |
299 if k not in blacklist: | |
300 flat.append(k) | |
301 flat.append(self.bot_cfg[k]) | |
302 return flat | |
303 | |
304 def test_steps(self, got_revision, master_name, slave_name, build_number, | |
305 issue=None, patchset=None): | |
306 """Run the DM test.""" | |
307 self.build_number = build_number | |
308 self.got_revision = got_revision | |
309 self.master_name = master_name | |
310 self.slave_name = slave_name | |
311 self._run_once(self.install) | |
312 | |
313 use_hash_file = False | |
314 if self.upload_dm_results: | |
315 # This must run before we write anything into self.device_dirs.dm_dir | |
316 # or we may end up deleting our output on machines where they're the same. | |
317 host_dm_dir = os.path.join(self.swarm_out_dir, 'dm') | |
318 print 'host dm dir: %s' % host_dm_dir | |
319 self.flavor.create_clean_host_dir(host_dm_dir) | |
320 if str(host_dm_dir) != str(self.device_dirs.dm_dir): | |
321 self.flavor.create_clean_device_dir(self.device_dirs.dm_dir) | |
322 | |
323 # Obtain the list of already-generated hashes. | |
324 if not os.path.isdir(self.tmp_dir): | |
325 os.makedirs(self.tmp_dir) | |
326 hash_filename = 'uninteresting_hashes.txt' | |
327 host_hashes_file = os.path.join(self.tmp_dir, hash_filename) | |
328 hashes_file = self.flavor.device_path_join( | |
329 self.device_dirs.tmp_dir, hash_filename) | |
330 | |
331 try: | |
332 get_uninteresting_hashes(host_hashes_file) | |
333 except Exception: | |
334 pass | |
335 | |
336 if os.path.exists(host_hashes_file): | |
337 self.flavor.copy_file_to_device(host_hashes_file, hashes_file) | |
338 use_hash_file = True | |
339 | |
340 # Run DM. | |
341 properties = [ | |
342 'gitHash', self.got_revision, | |
343 'master', self.master_name, | |
344 'builder', self.name, | |
345 'build_number', self.build_number, | |
346 ] | |
347 if self.is_trybot: | |
348 if not issue: | |
349 raise Exception('issue is required for trybots.') | |
350 if not patchset: | |
351 raise Exception('patchset is required for trybots.') | |
352 properties.extend([ | |
353 'issue', issue, | |
354 'patchset', patchset, | |
355 ]) | |
356 | |
357 args = [ | |
358 'dm', | |
359 '--undefok', # This helps branches that may not know new flags. | |
360 '--resourcePath', self.device_dirs.resource_dir, | |
361 '--skps', self.device_dirs.skp_dir, | |
362 '--images', self.flavor.device_path_join( | |
363 self.device_dirs.images_dir, 'dm'), | |
364 '--nameByHash', | |
365 '--properties' | |
366 ] + properties | |
367 | |
368 args.append('--key') | |
369 args.extend(self._key_params()) | |
370 if use_hash_file: | |
371 args.extend(['--uninterestingHashesFile', hashes_file]) | |
372 if self.upload_dm_results: | |
373 args.extend(['--writePath', self.device_dirs.dm_dir]) | |
374 | |
375 skip_flag = None | |
376 if self.bot_cfg.get('cpu_or_gpu') == 'CPU': | |
377 skip_flag = '--nogpu' | |
378 elif self.bot_cfg.get('cpu_or_gpu') == 'GPU': | |
379 skip_flag = '--nocpu' | |
380 if skip_flag: | |
381 args.append(skip_flag) | |
382 args.extend(self.dm_flags) | |
383 | |
384 self.flavor.run(args, env=self.default_env) | |
385 | |
386 if self.upload_dm_results: | |
387 # Copy images and JSON to host machine if needed. | |
388 self.flavor.copy_directory_contents_to_host(self.device_dirs.dm_dir, | |
389 host_dm_dir) | |
390 | |
391 # See skia:2789. | |
392 if ('Valgrind' in self.name and | |
393 self.bot_cfg.get('cpu_or_gpu') == 'GPU'): | |
394 abandonGpuContext = list(args) | |
395 abandonGpuContext.append('--abandonGpuContext') | |
396 self.flavor.run(abandonGpuContext) | |
397 preAbandonGpuContext = list(args) | |
398 preAbandonGpuContext.append('--preAbandonGpuContext') | |
399 self.flavor.run(preAbandonGpuContext) | |
400 | |
401 self.cleanup() | |
402 | |
403 def perf_steps(self, got_revision, master_name, slave_name, build_number, | |
404 issue=None, patchset=None): | |
405 """Run Skia benchmarks.""" | |
406 self.build_number = build_number | |
407 self.got_revision = got_revision | |
408 self.master_name = master_name | |
409 self.slave_name = slave_name | |
410 self._run_once(self.install) | |
411 if self.upload_perf_results: | |
412 self.flavor.create_clean_device_dir(self.device_dirs.perf_data_dir) | |
413 | |
414 # Run nanobench. | |
415 properties = [ | |
416 '--properties', | |
417 'gitHash', self.got_revision, | |
418 'build_number', self.build_number, | |
419 ] | |
420 if self.is_trybot: | |
421 if not issue: | |
422 raise Exception('issue is required for trybots.') | |
423 if not patchset: | |
424 raise Exception('patchset is required for trybots.') | |
425 properties.extend([ | |
426 'issue', issue, | |
427 'patchset', patchset, | |
428 ]) | |
429 | |
430 target = 'nanobench' | |
431 if 'VisualBench' in self.name: | |
432 target = 'visualbench' | |
433 args = [ | |
434 target, | |
435 '--undefok', # This helps branches that may not know new flags. | |
436 '-i', self.device_dirs.resource_dir, | |
437 '--skps', self.device_dirs.skp_dir, | |
438 '--images', self.flavor.device_path_join( | |
439 self.device_dirs.images_dir, 'dm'), # Using DM images for now. | |
440 ] | |
441 | |
442 skip_flag = None | |
443 if self.bot_cfg.get('cpu_or_gpu') == 'CPU': | |
444 skip_flag = '--nogpu' | |
445 elif self.bot_cfg.get('cpu_or_gpu') == 'GPU': | |
446 skip_flag = '--nocpu' | |
447 if skip_flag: | |
448 args.append(skip_flag) | |
449 args.extend(self.nanobench_flags) | |
450 | |
451 if self.upload_perf_results: | |
452 json_path = self.flavor.device_path_join( | |
453 self.device_dirs.perf_data_dir, | |
454 'nanobench_%s.json' % self.got_revision) | |
455 args.extend(['--outResultsFile', json_path]) | |
456 args.extend(properties) | |
457 | |
458 keys_blacklist = ['configuration', 'role', 'is_trybot'] | |
459 args.append('--key') | |
460 for k in sorted(self.bot_cfg.keys()): | |
461 if not k in keys_blacklist: | |
462 args.extend([k, self.bot_cfg[k]]) | |
463 | |
464 self.flavor.run(args, env=self.default_env) | |
465 | |
466 # See skia:2789. | |
467 if ('Valgrind' in self.name and | |
468 self.bot_cfg.get('cpu_or_gpu') == 'GPU'): | |
469 abandonGpuContext = list(args) | |
470 abandonGpuContext.extend(['--abandonGpuContext', '--nocpu']) | |
471 self.flavor.run(abandonGpuContext, env=self.default_env) | |
472 | |
473 # Copy results to host. | |
474 if self.upload_perf_results: | |
475 if not os.path.exists(self.perf_data_dir): | |
476 os.makedirs(self.perf_data_dir) | |
477 self.flavor.copy_directory_contents_to_host( | |
478 self.device_dirs.perf_data_dir, self.perf_data_dir) | |
479 | |
480 self.cleanup() | |
481 | |
482 def cleanup(self): | |
483 if sys.platform == 'win32': | |
484 # Kill mspdbsrv.exe, which tends to hang around after the build finishes. | |
485 for p in psutil.process_iter(): | |
486 try: | |
487 if p.name == 'mspdbsrv.exe': | |
488 p.kill() | |
489 except psutil._error.AccessDenied: | |
490 pass | |
491 self.flavor.cleanup_steps() | |
OLD | NEW |