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