| #!/usr/bin/python3 | |
| # | |
| # Copyright 2021 The ANGLE Project Authors. All rights reserved. | |
| # Use of this source code is governed by a BSD-style license that can be | |
| # found in the LICENSE file. | |
| # | |
| # update_extension_data.py: | |
| # Downloads and updates auto-generated extension data. | |
| import argparse | |
| import logging | |
| import json | |
| import os | |
| import re | |
| import shutil | |
| import subprocess | |
| import sys | |
| import tempfile | |
| EXIT_SUCCESS = 0 | |
| EXIT_FAILURE = 1 | |
| TEST_SUITE = 'angle_end2end_tests' | |
| BUILDERS = [ | |
| 'angle/ci/android-arm64-test', | |
| # angle-linux-x64-amd-rx5500xt-rel currently omitted since it does not run | |
| # any tests. | |
| 'angle/ci/angle-linux-x64-intel-uhd630-rel', | |
| 'angle/ci/angle-linux-x64-nvidia-gtx1660-rel', | |
| 'angle/ci/angle-linux-x64-sws-rel', | |
| 'angle/ci/win-test', | |
| 'angle/ci/win-x86-test', | |
| ] | |
| SWARMING_SERVER = 'chromium-swarm.appspot.com' | |
| d = os.path.dirname | |
| THIS_DIR = d(os.path.abspath(__file__)) | |
| ANGLE_ROOT_DIR = d(THIS_DIR) | |
| # Host GPUs | |
| INTEL_UHD630 = '8086:9bc5' | |
| NVIDIA_GTX1660 = '10de:2184' | |
| SWIFTSHADER = 'none' | |
| GPUS = [INTEL_UHD630, NVIDIA_GTX1660, SWIFTSHADER] | |
| GPU_NAME_MAP = { | |
| INTEL_UHD630: 'intel_630', | |
| NVIDIA_GTX1660: 'nvidia_1660', | |
| SWIFTSHADER: 'swiftshader' | |
| } | |
| # OSes | |
| LINUX = 'Linux' | |
| WINDOWS_10 = 'Windows-10' | |
| BOT_OSES = [LINUX, WINDOWS_10] | |
| BOT_OS_NAME_MAP = {LINUX: 'linux', WINDOWS_10: 'win10'} | |
| # Devices | |
| PIXEL_4 = 'flame' | |
| PIXEL_6 = 'oriole' | |
| DEVICES_TYPES = [PIXEL_4, PIXEL_6] | |
| DEVICE_NAME_MAP = {PIXEL_4: 'pixel_4', PIXEL_6: 'pixel_6'} | |
| # Device OSes | |
| ANDROID_11 = 'R' | |
| ANDROID_13 = 'T' | |
| DEVICE_OSES = [ANDROID_11, ANDROID_13] | |
| DEVICE_OS_NAME_MAP = {ANDROID_11: 'android_11', ANDROID_13: 'android_13'} | |
| # Result names | |
| INFO_FILES = [ | |
| 'GLinfo_ES3_2_Vulkan.json', | |
| 'GLinfo_ES3_1_Vulkan_SwiftShader.json', | |
| ] | |
| LOG_LEVELS = ['WARNING', 'INFO', 'DEBUG'] | |
| def run_and_get_json(args): | |
| logging.debug(' '.join(args)) | |
| output = subprocess.check_output(args) | |
| return json.loads(output) | |
| def get_bb(): | |
| return 'bb.bat' if os.name == 'nt' else 'bb' | |
| def run_bb_and_get_output(*args): | |
| bb_args = [get_bb()] + list(args) | |
| return subprocess.check_output(bb_args, encoding='utf-8') | |
| def run_bb_and_get_json(*args): | |
| bb_args = [get_bb()] + list(args) + ['-json'] | |
| return run_and_get_json(bb_args) | |
| def get_swarming(): | |
| swarming_bin = 'swarming.exe' if os.name == 'nt' else 'swarming' | |
| return os.path.join(ANGLE_ROOT_DIR, 'tools', 'luci-go', swarming_bin) | |
| def run_swarming(*args): | |
| swarming_args = [get_swarming()] + list(args) | |
| logging.debug(' '.join(swarming_args)) | |
| subprocess.check_call(swarming_args) | |
| def run_swarming_and_get_json(*args): | |
| swarming_args = [get_swarming()] + list(args) | |
| return run_and_get_json(swarming_args) | |
| def name_device(gpu, device_type): | |
| if gpu: | |
| return GPU_NAME_MAP[gpu] | |
| else: | |
| assert device_type | |
| return DEVICE_NAME_MAP[device_type] | |
| def name_os(bot_os, device_os): | |
| if bot_os: | |
| return BOT_OS_NAME_MAP[bot_os] | |
| else: | |
| assert device_os | |
| return DEVICE_OS_NAME_MAP[device_os] | |
| def get_props_string(gpu, bot_os, device_os, device_type): | |
| d = {'gpu': gpu, 'os': bot_os, 'device os': device_os, 'device': device_type} | |
| return ', '.join('%s %s' % (k, v) for (k, v) in d.items() if v) | |
| def collect_task_and_update_json(task_id, found_dims): | |
| gpu = found_dims.get('gpu', None) | |
| bot_os = found_dims.get('os', None) | |
| device_os = found_dims.get('device_os', None) | |
| device_type = found_dims.get('device_type', None) | |
| logging.info('Found task with ID: %s, %s' % | |
| (task_id, get_props_string(gpu, bot_os, device_os, device_type))) | |
| target_file_name = '%s_%s.json' % (name_device(gpu, device_type), name_os(bot_os, device_os)) | |
| target_file = os.path.join(THIS_DIR, 'extension_data', target_file_name) | |
| with tempfile.TemporaryDirectory() as tempdirname: | |
| run_swarming('collect', '-S', SWARMING_SERVER, '-output-dir=%s' % tempdirname, task_id) | |
| task_dir = os.path.join(tempdirname, task_id) | |
| found = False | |
| for fname in os.listdir(task_dir): | |
| if fname in INFO_FILES: | |
| if found: | |
| logging.warning('Multiple candidates found for %s' % target_file_name) | |
| return | |
| else: | |
| logging.info('%s -> %s' % (fname, target_file)) | |
| found = True | |
| source_file = os.path.join(task_dir, fname) | |
| shutil.copy(source_file, target_file) | |
| def get_intersect_or_none(list_a, list_b): | |
| i = [v for v in list_a if v in list_b] | |
| assert not i or len(i) == 1 | |
| return i[0] if i else None | |
| def main(): | |
| parser = argparse.ArgumentParser(description='Pulls extension support data from ANGLE CI.') | |
| parser.add_argument( | |
| '-v', '--verbose', help='Print additional debugging into.', action='count', default=0) | |
| args = parser.parse_args() | |
| if args.verbose >= len(LOG_LEVELS): | |
| args.verbose = len(LOG_LEVELS) - 1 | |
| logging.basicConfig(level=LOG_LEVELS[args.verbose]) | |
| name_expr = re.compile(r'^' + TEST_SUITE + r' on (.*) on (.*)$') | |
| for builder in BUILDERS: | |
| # Step 1: Find the build ID. | |
| # We list two builds using 'bb ls' and take the second, to ensure the build is finished. | |
| ls_output = run_bb_and_get_output('ls', builder, '-n', '2', '-id') | |
| build_id = ls_output.splitlines()[1] | |
| logging.info('%s: build id %s' % (builder, build_id)) | |
| # Step 2: Get the test suite swarm hashes. | |
| # 'bb get' returns build properties, including cloud storage identifiers for this test suite. | |
| get_json = run_bb_and_get_json('get', build_id, '-p') | |
| # First check for the hash in the input properties (for child testers), | |
| # falling back to output properties (for legacy or compile/test | |
| # builders). | |
| test_suite_hash = get_json.get('input', {}).get('properties', {}).get('swarm_hashes', | |
| {}).get(TEST_SUITE) | |
| if not test_suite_hash: | |
| test_suite_hash = get_json.get('output', {}).get('properties', | |
| {}).get('swarm_hashes', | |
| {}).get(TEST_SUITE) | |
| assert test_suite_hash | |
| logging.info('Found swarm hash: %s' % test_suite_hash) | |
| # Step 3: Find all tasks using the swarm hashes. | |
| # 'swarming tasks' can find instances of the test suite that ran on specific systems. | |
| task_json = run_swarming_and_get_json('tasks', '-tag', 'data:%s' % test_suite_hash, '-S', | |
| SWARMING_SERVER) | |
| # Step 4: Download the extension data for each configuration we're monitoring. | |
| # 'swarming collect' downloads test artifacts to a temporary directory. | |
| dim_map = { | |
| 'gpu': GPUS, | |
| 'os': BOT_OSES, | |
| 'device_os': DEVICE_OSES, | |
| 'device_type': DEVICES_TYPES, | |
| } | |
| for task in task_json: | |
| found_dims = {} | |
| for bot_dim in task['bot_dimensions']: | |
| key, value = bot_dim['key'], bot_dim['value'] | |
| if key in dim_map: | |
| logging.debug('%s=%s' % (key, value)) | |
| mapped_values = dim_map[key] | |
| found_dim = get_intersect_or_none(mapped_values, value) | |
| if found_dim: | |
| found_dims[key] = found_dim | |
| found_gpu_or_device = ('gpu' in found_dims or 'device_type' in found_dims) | |
| found_os = ('os' in found_dims or 'device_os' in found_dims) | |
| if found_gpu_or_device and found_os: | |
| collect_task_and_update_json(task['task_id'], found_dims) | |
| return EXIT_SUCCESS | |
| if __name__ == '__main__': | |
| sys.exit(main()) |