| Index: infra/bots/common.py
|
| diff --git a/infra/bots/common.py b/infra/bots/common.py
|
| index 8d91f743bd949fa5ec93d7236e3c51681edee4a9..e4314dd78fa100ff2e606b890a41f27f8d32f16b 100644
|
| --- a/infra/bots/common.py
|
| +++ b/infra/bots/common.py
|
| @@ -6,91 +6,20 @@
|
| # found in the LICENSE file.
|
|
|
|
|
| -import contextlib
|
| -import glob
|
| -import math
|
| import os
|
| -import psutil
|
| import shutil
|
| -import socket
|
| import subprocess
|
| -import sys
|
| -import time
|
| -import urllib2
|
|
|
| -from flavor import android_flavor
|
| -from flavor import chromeos_flavor
|
| -from flavor import cmake_flavor
|
| -from flavor import coverage_flavor
|
| -from flavor import default_flavor
|
| -from flavor import ios_flavor
|
| -from flavor import valgrind_flavor
|
| -from flavor import xsan_flavor
|
| -
|
| -
|
| -CONFIG_COVERAGE = 'Coverage'
|
| -CONFIG_DEBUG = 'Debug'
|
| -CONFIG_RELEASE = 'Release'
|
| -VALID_CONFIGS = (CONFIG_COVERAGE, CONFIG_DEBUG, CONFIG_RELEASE)
|
| -
|
| -BUILD_PRODUCTS_WHITELIST = [
|
| - 'dm',
|
| - 'dm.exe',
|
| - 'nanobench',
|
| - 'nanobench.exe',
|
| - '*.so',
|
| - '*.dll',
|
| -]
|
| -
|
| -GM_ACTUAL_FILENAME = 'actual-results.json'
|
| -GM_EXPECTATIONS_FILENAME = 'expected-results.json'
|
| -GM_IGNORE_TESTS_FILENAME = 'ignored-tests.txt'
|
| -
|
| -GOLD_UNINTERESTING_HASHES_URL = 'https://gold.skia.org/_/hashes'
|
|
|
| GS_GM_BUCKET = 'chromium-skia-gm'
|
| -GS_SUMMARIES_BUCKET = 'chromium-skia-gm-summaries'
|
|
|
| GS_SUBDIR_TMPL_SK_IMAGE = 'skimage/v%s'
|
| GS_SUBDIR_TMPL_SKP = 'playback_%s/skps'
|
|
|
| -SKIA_REPO = 'https://skia.googlesource.com/skia.git'
|
| -INFRA_REPO = 'https://skia.googlesource.com/buildbot.git'
|
| -
|
| -SERVICE_ACCOUNT_FILE = 'service-account-skia.json'
|
| -SERVICE_ACCOUNT_INTERNAL_FILE = 'service-account-skia-internal.json'
|
| -
|
| VERSION_FILE_SK_IMAGE = 'SK_IMAGE_VERSION'
|
| VERSION_FILE_SKP = 'SKP_VERSION'
|
|
|
|
|
| -def is_android(bot_cfg):
|
| - """Determine whether the given bot is an Android bot."""
|
| - return ('Android' in bot_cfg.get('extra_config', '') or
|
| - bot_cfg.get('os') == 'Android')
|
| -
|
| -def is_chromeos(bot_cfg):
|
| - return ('CrOS' in bot_cfg.get('extra_config', '') or
|
| - bot_cfg.get('os') == 'ChromeOS')
|
| -
|
| -def is_cmake(bot_cfg):
|
| - return 'CMake' in bot_cfg.get('extra_config', '')
|
| -
|
| -def is_ios(bot_cfg):
|
| - return ('iOS' in bot_cfg.get('extra_config', '') or
|
| - bot_cfg.get('os') == 'iOS')
|
| -
|
| -
|
| -def is_valgrind(bot_cfg):
|
| - return 'Valgrind' in bot_cfg.get('extra_config', '')
|
| -
|
| -
|
| -def is_xsan(bot_cfg):
|
| - return (bot_cfg.get('extra_config') == 'ASAN' or
|
| - bot_cfg.get('extra_config') == 'MSAN' or
|
| - bot_cfg.get('extra_config') == 'TSAN')
|
| -
|
| -
|
| def download_dir(skia_dir, tmp_dir, version_file, gs_path_tmpl, dst_dir):
|
| # Ensure that the tmp_dir exists.
|
| if not os.path.isdir(tmp_dir):
|
| @@ -124,368 +53,3 @@ def download_dir(skia_dir, tmp_dir, version_file, gs_path_tmpl, dst_dir):
|
| subprocess.check_call(['gsutil', 'cp', '-R', gs_path, dst_dir])
|
| with open(actual_version_file, 'w') as f:
|
| f.write(expected_version)
|
| -
|
| -
|
| -def get_uninteresting_hashes(hashes_file):
|
| - retries = 5
|
| - timeout = 60
|
| - wait_base = 15
|
| -
|
| - socket.setdefaulttimeout(timeout)
|
| - for retry in range(retries):
|
| - try:
|
| - with contextlib.closing(
|
| - urllib2.urlopen(GOLD_UNINTERESTING_HASHES_URL, timeout=timeout)) as w:
|
| - hashes = w.read()
|
| - with open(hashes_file, 'w') as f:
|
| - f.write(hashes)
|
| - break
|
| - except Exception as e:
|
| - print >> sys.stderr, 'Failed to get uninteresting hashes from %s:\n%s' % (
|
| - GOLD_UNINTERESTING_HASHES_URL, e)
|
| - if retry == retries:
|
| - raise
|
| - waittime = wait_base * math.pow(2, retry)
|
| - print 'Retry in %d seconds.' % waittime
|
| - time.sleep(waittime)
|
| -
|
| -
|
| -class BotInfo(object):
|
| - def __init__(self, bot_name, swarm_out_dir):
|
| - """Initialize the bot, given its name.
|
| -
|
| - Assumes that CWD is the directory containing this file.
|
| - """
|
| - self.name = bot_name
|
| - self.skia_dir = os.path.abspath(os.path.join(
|
| - os.path.dirname(os.path.realpath(__file__)),
|
| - os.pardir, os.pardir))
|
| - self.swarm_out_dir = swarm_out_dir
|
| - os.chdir(self.skia_dir)
|
| - self.build_dir = os.path.abspath(os.path.join(self.skia_dir, os.pardir))
|
| - self.infrabots_dir = os.path.join(self.skia_dir, 'infra', 'bots')
|
| - self.home_dir = os.path.expanduser('~')
|
| -
|
| - self.spec = self.get_bot_spec(bot_name)
|
| - self.bot_cfg = self.spec['builder_cfg']
|
| - self.out_dir = os.path.join(os.pardir, 'out')
|
| - self.configuration = self.spec['configuration']
|
| - self.default_env = {
|
| - 'CHROME_HEADLESS': '1',
|
| - 'SKIA_OUT': self.out_dir,
|
| - 'BUILDTYPE': self.configuration,
|
| - 'PATH': os.environ['PATH'],
|
| - }
|
| - if 'Win' in self.bot_cfg['os']:
|
| - self.default_env['SystemRoot'] = 'C:\\Windows'
|
| - self.default_env['TEMP'] = os.path.join(
|
| - self.home_dir, 'AppData', 'Local', 'Temp')
|
| - self.default_env['TMP'] = self.default_env['TEMP']
|
| - self.default_env.update(self.spec['env'])
|
| - self.build_targets = [str(t) for t in self.spec['build_targets']]
|
| - self.is_trybot = self.bot_cfg['is_trybot']
|
| - self.upload_dm_results = self.spec['upload_dm_results']
|
| - self.upload_perf_results = self.spec['upload_perf_results']
|
| - self.perf_data_dir = os.path.join(self.swarm_out_dir, 'perfdata',
|
| - self.name, 'data')
|
| - self.resource_dir = os.path.join(self.skia_dir, 'resources')
|
| - self.images_dir = os.path.join(self.build_dir, 'images')
|
| - self.local_skp_dir = os.path.join(self.build_dir, 'playback', 'skps')
|
| - self.dm_flags = self.spec['dm_flags']
|
| - self.nanobench_flags = self.spec['nanobench_flags']
|
| - self._ccache = None
|
| - self._checked_for_ccache = False
|
| - self._already_ran = {}
|
| - self.tmp_dir = os.path.join(self.build_dir, 'tmp')
|
| - self.flavor = self.get_flavor(self.bot_cfg)
|
| -
|
| - # These get filled in during subsequent steps.
|
| - self.device_dirs = None
|
| - self.build_number = None
|
| - self.got_revision = None
|
| - self.master_name = None
|
| - self.slave_name = None
|
| -
|
| - @property
|
| - def ccache(self):
|
| - if not self._checked_for_ccache:
|
| - self._checked_for_ccache = True
|
| - if sys.platform != 'win32':
|
| - try:
|
| - result = subprocess.check_output(['which', 'ccache'])
|
| - self._ccache = result.rstrip()
|
| - except subprocess.CalledProcessError:
|
| - pass
|
| -
|
| - return self._ccache
|
| -
|
| - def get_bot_spec(self, bot_name):
|
| - """Retrieve the bot spec for this bot."""
|
| - sys.path.append(self.skia_dir)
|
| - from tools import buildbot_spec
|
| - return buildbot_spec.get_builder_spec(bot_name)
|
| -
|
| - def get_flavor(self, bot_cfg):
|
| - """Return a flavor utils object specific to the given bot."""
|
| - if is_android(bot_cfg):
|
| - return android_flavor.AndroidFlavorUtils(self)
|
| - elif is_chromeos(bot_cfg):
|
| - return chromeos_flavor.ChromeOSFlavorUtils(self)
|
| - elif is_cmake(bot_cfg):
|
| - return cmake_flavor.CMakeFlavorUtils(self)
|
| - elif is_ios(bot_cfg):
|
| - return ios_flavor.iOSFlavorUtils(self)
|
| - elif is_valgrind(bot_cfg):
|
| - return valgrind_flavor.ValgrindFlavorUtils(self)
|
| - elif is_xsan(bot_cfg):
|
| - return xsan_flavor.XSanFlavorUtils(self)
|
| - elif bot_cfg.get('configuration') == CONFIG_COVERAGE:
|
| - return coverage_flavor.CoverageFlavorUtils(self)
|
| - else:
|
| - return default_flavor.DefaultFlavorUtils(self)
|
| -
|
| - def run(self, cmd, env=None, cwd=None):
|
| - _env = {}
|
| - _env.update(self.default_env)
|
| - _env.update(env or {})
|
| - cwd = cwd or self.skia_dir
|
| - print '============'
|
| - print 'CMD: %s' % cmd
|
| - print 'CWD: %s' % cwd
|
| - print 'ENV: %s' % _env
|
| - print '============'
|
| - subprocess.check_call(cmd, env=_env, cwd=cwd)
|
| -
|
| - def compile_steps(self):
|
| - for t in self.build_targets:
|
| - self.flavor.compile(t)
|
| - dst = os.path.join(self.swarm_out_dir, 'out', self.configuration)
|
| - os.makedirs(dst)
|
| - for pattern in BUILD_PRODUCTS_WHITELIST:
|
| - path = os.path.join(self.out_dir, self.configuration, pattern)
|
| - for f in glob.glob(path):
|
| - print 'Copying build product %s' % f
|
| - shutil.copy(f, dst)
|
| - self.cleanup()
|
| -
|
| - def _run_once(self, fn, *args, **kwargs):
|
| - if not fn.__name__ in self._already_ran:
|
| - self._already_ran[fn.__name__] = True
|
| - fn(*args, **kwargs)
|
| -
|
| - def install(self):
|
| - """Copy the required executables and files to the device."""
|
| - self.device_dirs = self.flavor.get_device_dirs()
|
| -
|
| - # Run any device-specific installation.
|
| - self.flavor.install()
|
| -
|
| - # TODO(borenet): Only copy files which have changed.
|
| - # Resources
|
| - self.flavor.copy_directory_contents_to_device(self.resource_dir,
|
| - self.device_dirs.resource_dir)
|
| -
|
| - def _key_params(self):
|
| - """Build a unique key from the builder name (as a list).
|
| -
|
| - E.g. arch x86 gpu GeForce320M mode MacMini4.1 os Mac10.6
|
| - """
|
| - # Don't bother to include role, which is always Test.
|
| - # TryBots are uploaded elsewhere so they can use the same key.
|
| - blacklist = ['role', 'is_trybot']
|
| -
|
| - flat = []
|
| - for k in sorted(self.bot_cfg.keys()):
|
| - if k not in blacklist:
|
| - flat.append(k)
|
| - flat.append(self.bot_cfg[k])
|
| - return flat
|
| -
|
| - def test_steps(self, got_revision, master_name, slave_name, build_number,
|
| - issue=None, patchset=None):
|
| - """Run the DM test."""
|
| - self.build_number = build_number
|
| - self.got_revision = got_revision
|
| - self.master_name = master_name
|
| - self.slave_name = slave_name
|
| - self._run_once(self.install)
|
| -
|
| - use_hash_file = False
|
| - if self.upload_dm_results:
|
| - # This must run before we write anything into self.device_dirs.dm_dir
|
| - # or we may end up deleting our output on machines where they're the same.
|
| - host_dm_dir = os.path.join(self.swarm_out_dir, 'dm')
|
| - print 'host dm dir: %s' % host_dm_dir
|
| - self.flavor.create_clean_host_dir(host_dm_dir)
|
| - if str(host_dm_dir) != str(self.device_dirs.dm_dir):
|
| - self.flavor.create_clean_device_dir(self.device_dirs.dm_dir)
|
| -
|
| - # Obtain the list of already-generated hashes.
|
| - if not os.path.isdir(self.tmp_dir):
|
| - os.makedirs(self.tmp_dir)
|
| - hash_filename = 'uninteresting_hashes.txt'
|
| - host_hashes_file = os.path.join(self.tmp_dir, hash_filename)
|
| - hashes_file = self.flavor.device_path_join(
|
| - self.device_dirs.tmp_dir, hash_filename)
|
| -
|
| - try:
|
| - get_uninteresting_hashes(host_hashes_file)
|
| - except Exception:
|
| - pass
|
| -
|
| - if os.path.exists(host_hashes_file):
|
| - self.flavor.copy_file_to_device(host_hashes_file, hashes_file)
|
| - use_hash_file = True
|
| -
|
| - # Run DM.
|
| - properties = [
|
| - 'gitHash', self.got_revision,
|
| - 'master', self.master_name,
|
| - 'builder', self.name,
|
| - 'build_number', self.build_number,
|
| - ]
|
| - if self.is_trybot:
|
| - if not issue:
|
| - raise Exception('issue is required for trybots.')
|
| - if not patchset:
|
| - raise Exception('patchset is required for trybots.')
|
| - properties.extend([
|
| - 'issue', issue,
|
| - 'patchset', patchset,
|
| - ])
|
| -
|
| - args = [
|
| - 'dm',
|
| - '--undefok', # This helps branches that may not know new flags.
|
| - '--resourcePath', self.device_dirs.resource_dir,
|
| - '--skps', self.device_dirs.skp_dir,
|
| - '--images', self.flavor.device_path_join(
|
| - self.device_dirs.images_dir, 'dm'),
|
| - '--nameByHash',
|
| - '--properties'
|
| - ] + properties
|
| -
|
| - args.append('--key')
|
| - args.extend(self._key_params())
|
| - if use_hash_file:
|
| - args.extend(['--uninterestingHashesFile', hashes_file])
|
| - if self.upload_dm_results:
|
| - args.extend(['--writePath', self.device_dirs.dm_dir])
|
| -
|
| - skip_flag = None
|
| - if self.bot_cfg.get('cpu_or_gpu') == 'CPU':
|
| - skip_flag = '--nogpu'
|
| - elif self.bot_cfg.get('cpu_or_gpu') == 'GPU':
|
| - skip_flag = '--nocpu'
|
| - if skip_flag:
|
| - args.append(skip_flag)
|
| - args.extend(self.dm_flags)
|
| -
|
| - self.flavor.run(args, env=self.default_env)
|
| -
|
| - if self.upload_dm_results:
|
| - # Copy images and JSON to host machine if needed.
|
| - self.flavor.copy_directory_contents_to_host(self.device_dirs.dm_dir,
|
| - host_dm_dir)
|
| -
|
| - # See skia:2789.
|
| - if ('Valgrind' in self.name and
|
| - self.bot_cfg.get('cpu_or_gpu') == 'GPU'):
|
| - abandonGpuContext = list(args)
|
| - abandonGpuContext.append('--abandonGpuContext')
|
| - self.flavor.run(abandonGpuContext)
|
| - preAbandonGpuContext = list(args)
|
| - preAbandonGpuContext.append('--preAbandonGpuContext')
|
| - self.flavor.run(preAbandonGpuContext)
|
| -
|
| - self.cleanup()
|
| -
|
| - def perf_steps(self, got_revision, master_name, slave_name, build_number,
|
| - issue=None, patchset=None):
|
| - """Run Skia benchmarks."""
|
| - self.build_number = build_number
|
| - self.got_revision = got_revision
|
| - self.master_name = master_name
|
| - self.slave_name = slave_name
|
| - self._run_once(self.install)
|
| - if self.upload_perf_results:
|
| - self.flavor.create_clean_device_dir(self.device_dirs.perf_data_dir)
|
| -
|
| - # Run nanobench.
|
| - properties = [
|
| - '--properties',
|
| - 'gitHash', self.got_revision,
|
| - 'build_number', self.build_number,
|
| - ]
|
| - if self.is_trybot:
|
| - if not issue:
|
| - raise Exception('issue is required for trybots.')
|
| - if not patchset:
|
| - raise Exception('patchset is required for trybots.')
|
| - properties.extend([
|
| - 'issue', issue,
|
| - 'patchset', patchset,
|
| - ])
|
| -
|
| - target = 'nanobench'
|
| - if 'VisualBench' in self.name:
|
| - target = 'visualbench'
|
| - args = [
|
| - target,
|
| - '--undefok', # This helps branches that may not know new flags.
|
| - '-i', self.device_dirs.resource_dir,
|
| - '--skps', self.device_dirs.skp_dir,
|
| - '--images', self.flavor.device_path_join(
|
| - self.device_dirs.images_dir, 'dm'), # Using DM images for now.
|
| - ]
|
| -
|
| - skip_flag = None
|
| - if self.bot_cfg.get('cpu_or_gpu') == 'CPU':
|
| - skip_flag = '--nogpu'
|
| - elif self.bot_cfg.get('cpu_or_gpu') == 'GPU':
|
| - skip_flag = '--nocpu'
|
| - if skip_flag:
|
| - args.append(skip_flag)
|
| - args.extend(self.nanobench_flags)
|
| -
|
| - if self.upload_perf_results:
|
| - json_path = self.flavor.device_path_join(
|
| - self.device_dirs.perf_data_dir,
|
| - 'nanobench_%s.json' % self.got_revision)
|
| - args.extend(['--outResultsFile', json_path])
|
| - args.extend(properties)
|
| -
|
| - keys_blacklist = ['configuration', 'role', 'is_trybot']
|
| - args.append('--key')
|
| - for k in sorted(self.bot_cfg.keys()):
|
| - if not k in keys_blacklist:
|
| - args.extend([k, self.bot_cfg[k]])
|
| -
|
| - self.flavor.run(args, env=self.default_env)
|
| -
|
| - # See skia:2789.
|
| - if ('Valgrind' in self.name and
|
| - self.bot_cfg.get('cpu_or_gpu') == 'GPU'):
|
| - abandonGpuContext = list(args)
|
| - abandonGpuContext.extend(['--abandonGpuContext', '--nocpu'])
|
| - self.flavor.run(abandonGpuContext, env=self.default_env)
|
| -
|
| - # Copy results to host.
|
| - if self.upload_perf_results:
|
| - if not os.path.exists(self.perf_data_dir):
|
| - os.makedirs(self.perf_data_dir)
|
| - self.flavor.copy_directory_contents_to_host(
|
| - self.device_dirs.perf_data_dir, self.perf_data_dir)
|
| -
|
| - self.cleanup()
|
| -
|
| - def cleanup(self):
|
| - if sys.platform == 'win32':
|
| - # Kill mspdbsrv.exe, which tends to hang around after the build finishes.
|
| - for p in psutil.process_iter():
|
| - try:
|
| - if p.name == 'mspdbsrv.exe':
|
| - p.kill()
|
| - except psutil._error.AccessDenied:
|
| - pass
|
| - self.flavor.cleanup_steps()
|
|
|