#!/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/ci/linux-test', '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_HD630 = '8086:5912' NVIDIA_GTX1660 = '10de:2184' SWIFTSHADER = 'none' GPUS = [INTEL_HD630, NVIDIA_GTX1660, SWIFTSHADER] GPU_NAME_MAP = { INTEL_HD630: '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' DEVICES_TYPES = [PIXEL_4] DEVICE_NAME_MAP = {PIXEL_4: 'pixel_4'} # Device OSes ANDROID_11 = 'R' DEVICE_OSES = [ANDROID_11] DEVICE_OS_NAME_MAP = {ANDROID_11: 'android_11'} # 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') test_suite_hash = get_json['output']['properties']['swarm_hashes'][TEST_SUITE] 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())