1#!/usr/bin/python3 2# 3# Copyright 2021 The ANGLE Project Authors. All rights reserved. 4# Use of this source code is governed by a BSD-style license that can be 5# found in the LICENSE file. 6# 7# update_extension_data.py: 8# Downloads and updates auto-generated extension data. 9 10import argparse 11import logging 12import json 13import os 14import re 15import shutil 16import subprocess 17import sys 18import tempfile 19 20EXIT_SUCCESS = 0 21EXIT_FAILURE = 1 22 23TEST_SUITE = 'angle_end2end_tests' 24BUILDERS = [ 25 'angle/ci/android-arm64-test', 'angle/ci/linux-test', 'angle/ci/win-test', 26 'angle/ci/win-x86-test' 27] 28SWARMING_SERVER = 'chromium-swarm.appspot.com' 29 30d = os.path.dirname 31THIS_DIR = d(os.path.abspath(__file__)) 32ANGLE_ROOT_DIR = d(THIS_DIR) 33 34# Host GPUs 35INTEL_UHD630 = '8086:9bc5' 36NVIDIA_GTX1660 = '10de:2184' 37SWIFTSHADER = 'none' 38GPUS = [INTEL_UHD630, NVIDIA_GTX1660, SWIFTSHADER] 39GPU_NAME_MAP = { 40 INTEL_UHD630: 'intel_630', 41 NVIDIA_GTX1660: 'nvidia_1660', 42 SWIFTSHADER: 'swiftshader' 43} 44 45# OSes 46LINUX = 'Linux' 47WINDOWS_10 = 'Windows-10' 48BOT_OSES = [LINUX, WINDOWS_10] 49BOT_OS_NAME_MAP = {LINUX: 'linux', WINDOWS_10: 'win10'} 50 51# Devices 52PIXEL_4 = 'flame' 53PIXEL_6 = 'oriole' 54DEVICES_TYPES = [PIXEL_4, PIXEL_6] 55DEVICE_NAME_MAP = {PIXEL_4: 'pixel_4', PIXEL_6: 'pixel_6'} 56 57# Device OSes 58ANDROID_11 = 'R' 59ANDROID_13 = 'T' 60DEVICE_OSES = [ANDROID_11, ANDROID_13] 61DEVICE_OS_NAME_MAP = {ANDROID_11: 'android_11', ANDROID_13: 'android_13'} 62 63# Result names 64INFO_FILES = [ 65 'GLinfo_ES3_2_Vulkan.json', 66 'GLinfo_ES3_1_Vulkan_SwiftShader.json', 67] 68 69LOG_LEVELS = ['WARNING', 'INFO', 'DEBUG'] 70 71 72def run_and_get_json(args): 73 logging.debug(' '.join(args)) 74 output = subprocess.check_output(args) 75 return json.loads(output) 76 77 78def get_bb(): 79 return 'bb.bat' if os.name == 'nt' else 'bb' 80 81 82def run_bb_and_get_output(*args): 83 bb_args = [get_bb()] + list(args) 84 return subprocess.check_output(bb_args, encoding='utf-8') 85 86 87def run_bb_and_get_json(*args): 88 bb_args = [get_bb()] + list(args) + ['-json'] 89 return run_and_get_json(bb_args) 90 91 92def get_swarming(): 93 swarming_bin = 'swarming.exe' if os.name == 'nt' else 'swarming' 94 return os.path.join(ANGLE_ROOT_DIR, 'tools', 'luci-go', swarming_bin) 95 96 97def run_swarming(*args): 98 swarming_args = [get_swarming()] + list(args) 99 logging.debug(' '.join(swarming_args)) 100 subprocess.check_call(swarming_args) 101 102 103def run_swarming_and_get_json(*args): 104 swarming_args = [get_swarming()] + list(args) 105 return run_and_get_json(swarming_args) 106 107 108def name_device(gpu, device_type): 109 if gpu: 110 return GPU_NAME_MAP[gpu] 111 else: 112 assert device_type 113 return DEVICE_NAME_MAP[device_type] 114 115 116def name_os(bot_os, device_os): 117 if bot_os: 118 return BOT_OS_NAME_MAP[bot_os] 119 else: 120 assert device_os 121 return DEVICE_OS_NAME_MAP[device_os] 122 123 124def get_props_string(gpu, bot_os, device_os, device_type): 125 d = {'gpu': gpu, 'os': bot_os, 'device os': device_os, 'device': device_type} 126 return ', '.join('%s %s' % (k, v) for (k, v) in d.items() if v) 127 128 129def collect_task_and_update_json(task_id, found_dims): 130 gpu = found_dims.get('gpu', None) 131 bot_os = found_dims.get('os', None) 132 device_os = found_dims.get('device_os', None) 133 device_type = found_dims.get('device_type', None) 134 logging.info('Found task with ID: %s, %s' % 135 (task_id, get_props_string(gpu, bot_os, device_os, device_type))) 136 target_file_name = '%s_%s.json' % (name_device(gpu, device_type), name_os(bot_os, device_os)) 137 target_file = os.path.join(THIS_DIR, 'extension_data', target_file_name) 138 with tempfile.TemporaryDirectory() as tempdirname: 139 run_swarming('collect', '-S', SWARMING_SERVER, '-output-dir=%s' % tempdirname, task_id) 140 task_dir = os.path.join(tempdirname, task_id) 141 found = False 142 for fname in os.listdir(task_dir): 143 if fname in INFO_FILES: 144 if found: 145 logging.warning('Multiple candidates found for %s' % target_file_name) 146 return 147 else: 148 logging.info('%s -> %s' % (fname, target_file)) 149 found = True 150 source_file = os.path.join(task_dir, fname) 151 shutil.copy(source_file, target_file) 152 153 154def get_intersect_or_none(list_a, list_b): 155 i = [v for v in list_a if v in list_b] 156 assert not i or len(i) == 1 157 return i[0] if i else None 158 159 160def main(): 161 parser = argparse.ArgumentParser(description='Pulls extension support data from ANGLE CI.') 162 parser.add_argument( 163 '-v', '--verbose', help='Print additional debugging into.', action='count', default=0) 164 args = parser.parse_args() 165 166 if args.verbose >= len(LOG_LEVELS): 167 args.verbose = len(LOG_LEVELS) - 1 168 logging.basicConfig(level=LOG_LEVELS[args.verbose]) 169 170 name_expr = re.compile(r'^' + TEST_SUITE + r' on (.*) on (.*)$') 171 172 for builder in BUILDERS: 173 174 # Step 1: Find the build ID. 175 # We list two builds using 'bb ls' and take the second, to ensure the build is finished. 176 ls_output = run_bb_and_get_output('ls', builder, '-n', '2', '-id') 177 build_id = ls_output.splitlines()[1] 178 logging.info('%s: build id %s' % (builder, build_id)) 179 180 # Step 2: Get the test suite swarm hashes. 181 # 'bb get' returns build properties, including cloud storage identifiers for this test suite. 182 get_json = run_bb_and_get_json('get', build_id, '-p') 183 test_suite_hash = get_json['output']['properties']['swarm_hashes'][TEST_SUITE] 184 logging.info('Found swarm hash: %s' % test_suite_hash) 185 186 # Step 3: Find all tasks using the swarm hashes. 187 # 'swarming tasks' can find instances of the test suite that ran on specific systems. 188 task_json = run_swarming_and_get_json('tasks', '-tag', 'data:%s' % test_suite_hash, '-S', 189 SWARMING_SERVER) 190 191 # Step 4: Download the extension data for each configuration we're monitoring. 192 # 'swarming collect' downloads test artifacts to a temporary directory. 193 dim_map = { 194 'gpu': GPUS, 195 'os': BOT_OSES, 196 'device_os': DEVICE_OSES, 197 'device_type': DEVICES_TYPES, 198 } 199 200 for task in task_json: 201 found_dims = {} 202 for bot_dim in task['bot_dimensions']: 203 key, value = bot_dim['key'], bot_dim['value'] 204 if key in dim_map: 205 logging.debug('%s=%s' % (key, value)) 206 mapped_values = dim_map[key] 207 found_dim = get_intersect_or_none(mapped_values, value) 208 if found_dim: 209 found_dims[key] = found_dim 210 found_gpu_or_device = ('gpu' in found_dims or 'device_type' in found_dims) 211 found_os = ('os' in found_dims or 'device_os' in found_dims) 212 if found_gpu_or_device and found_os: 213 collect_task_and_update_json(task['task_id'], found_dims) 214 215 return EXIT_SUCCESS 216 217 218if __name__ == '__main__': 219 sys.exit(main()) 220