• 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 = ['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