Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(297)

Side by Side Diff: infra/bots/common.py

Issue 1827413002: Fixes for Swarming recipes (Closed) Base URL: https://skia.googlesource.com/skia.git@master
Patch Set: Reinstate tmp_dir Created 4 years, 8 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « infra/bots/add_isolated_input.py ('k') | infra/bots/compile_skia.isolate » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
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
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()
OLDNEW
« no previous file with comments | « infra/bots/add_isolated_input.py ('k') | infra/bots/compile_skia.isolate » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698