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