xref: /aosp_15_r20/external/angle/scripts/process_angle_perf_results.py (revision 8975f5c5ed3d1c378011245431ada316dfb6f244)
1*8975f5c5SAndroid Build Coastguard Worker#!/usr/bin/env vpython
2*8975f5c5SAndroid Build Coastguard Worker#
3*8975f5c5SAndroid Build Coastguard Worker# Copyright 2021 The ANGLE Project Authors. All rights reserved.
4*8975f5c5SAndroid Build Coastguard Worker# Use of this source code is governed by a BSD-style license that can be
5*8975f5c5SAndroid Build Coastguard Worker# found in the LICENSE file.
6*8975f5c5SAndroid Build Coastguard Worker#
7*8975f5c5SAndroid Build Coastguard Worker# process_angle_perf_results.py:
8*8975f5c5SAndroid Build Coastguard Worker#   Perf result merging and upload. Adapted from the Chromium script:
9*8975f5c5SAndroid Build Coastguard Worker#   https://chromium.googlesource.com/chromium/src/+/main/tools/perf/process_perf_results.py
10*8975f5c5SAndroid Build Coastguard Worker
11*8975f5c5SAndroid Build Coastguard Workerfrom __future__ import print_function
12*8975f5c5SAndroid Build Coastguard Worker
13*8975f5c5SAndroid Build Coastguard Workerimport argparse
14*8975f5c5SAndroid Build Coastguard Workerimport collections
15*8975f5c5SAndroid Build Coastguard Workerimport datetime
16*8975f5c5SAndroid Build Coastguard Workerimport json
17*8975f5c5SAndroid Build Coastguard Workerimport logging
18*8975f5c5SAndroid Build Coastguard Workerimport multiprocessing
19*8975f5c5SAndroid Build Coastguard Workerimport os
20*8975f5c5SAndroid Build Coastguard Workerimport pathlib
21*8975f5c5SAndroid Build Coastguard Workerimport shutil
22*8975f5c5SAndroid Build Coastguard Workerimport subprocess
23*8975f5c5SAndroid Build Coastguard Workerimport sys
24*8975f5c5SAndroid Build Coastguard Workerimport tempfile
25*8975f5c5SAndroid Build Coastguard Workerimport time
26*8975f5c5SAndroid Build Coastguard Workerimport uuid
27*8975f5c5SAndroid Build Coastguard Worker
28*8975f5c5SAndroid Build Coastguard Workerlogging.basicConfig(
29*8975f5c5SAndroid Build Coastguard Worker    level=logging.INFO,
30*8975f5c5SAndroid Build Coastguard Worker    format='(%(levelname)s) %(asctime)s pid=%(process)d'
31*8975f5c5SAndroid Build Coastguard Worker    '  %(module)s.%(funcName)s:%(lineno)d  %(message)s')
32*8975f5c5SAndroid Build Coastguard Worker
33*8975f5c5SAndroid Build Coastguard WorkerPY_UTILS = str(pathlib.Path(__file__).resolve().parents[1] / 'src' / 'tests' / 'py_utils')
34*8975f5c5SAndroid Build Coastguard Workerif PY_UTILS not in sys.path:
35*8975f5c5SAndroid Build Coastguard Worker    os.stat(PY_UTILS) and sys.path.insert(0, PY_UTILS)
36*8975f5c5SAndroid Build Coastguard Workerimport angle_metrics
37*8975f5c5SAndroid Build Coastguard Workerimport angle_path_util
38*8975f5c5SAndroid Build Coastguard Worker
39*8975f5c5SAndroid Build Coastguard Workerangle_path_util.AddDepsDirToPath('tools/perf')
40*8975f5c5SAndroid Build Coastguard Workerfrom core import path_util
41*8975f5c5SAndroid Build Coastguard Worker
42*8975f5c5SAndroid Build Coastguard Workerpath_util.AddTelemetryToPath()
43*8975f5c5SAndroid Build Coastguard Workerfrom core import upload_results_to_perf_dashboard
44*8975f5c5SAndroid Build Coastguard Workerfrom core import results_merger
45*8975f5c5SAndroid Build Coastguard Worker
46*8975f5c5SAndroid Build Coastguard Workerpath_util.AddAndroidPylibToPath()
47*8975f5c5SAndroid Build Coastguard Workertry:
48*8975f5c5SAndroid Build Coastguard Worker    from pylib.utils import logdog_helper
49*8975f5c5SAndroid Build Coastguard Workerexcept ImportError:
50*8975f5c5SAndroid Build Coastguard Worker    pass
51*8975f5c5SAndroid Build Coastguard Worker
52*8975f5c5SAndroid Build Coastguard Workerpath_util.AddTracingToPath()
53*8975f5c5SAndroid Build Coastguard Workerfrom tracing.value import histogram
54*8975f5c5SAndroid Build Coastguard Workerfrom tracing.value import histogram_set
55*8975f5c5SAndroid Build Coastguard Workerfrom tracing.value.diagnostics import generic_set
56*8975f5c5SAndroid Build Coastguard Workerfrom tracing.value.diagnostics import reserved_infos
57*8975f5c5SAndroid Build Coastguard Worker
58*8975f5c5SAndroid Build Coastguard WorkerRESULTS_URL = 'https://chromeperf.appspot.com'
59*8975f5c5SAndroid Build Coastguard WorkerJSON_CONTENT_TYPE = 'application/json'
60*8975f5c5SAndroid Build Coastguard WorkerMACHINE_GROUP = 'ANGLE'
61*8975f5c5SAndroid Build Coastguard WorkerBUILD_URL = 'https://ci.chromium.org/ui/p/angle/builders/ci/%s/%d'
62*8975f5c5SAndroid Build Coastguard Worker
63*8975f5c5SAndroid Build Coastguard WorkerGSUTIL_PY_PATH = str(
64*8975f5c5SAndroid Build Coastguard Worker    pathlib.Path(__file__).resolve().parents[1] / 'third_party' / 'depot_tools' / 'gsutil.py')
65*8975f5c5SAndroid Build Coastguard Worker
66*8975f5c5SAndroid Build Coastguard Worker
67*8975f5c5SAndroid Build Coastguard Workerdef _upload_perf_results(json_to_upload, name, configuration_name, build_properties,
68*8975f5c5SAndroid Build Coastguard Worker                         output_json_file):
69*8975f5c5SAndroid Build Coastguard Worker    """Upload the contents of result JSON(s) to the perf dashboard."""
70*8975f5c5SAndroid Build Coastguard Worker    args = [
71*8975f5c5SAndroid Build Coastguard Worker        '--buildername',
72*8975f5c5SAndroid Build Coastguard Worker        build_properties['buildername'],
73*8975f5c5SAndroid Build Coastguard Worker        '--buildnumber',
74*8975f5c5SAndroid Build Coastguard Worker        str(build_properties['buildnumber']),
75*8975f5c5SAndroid Build Coastguard Worker        '--name',
76*8975f5c5SAndroid Build Coastguard Worker        name,
77*8975f5c5SAndroid Build Coastguard Worker        '--configuration-name',
78*8975f5c5SAndroid Build Coastguard Worker        configuration_name,
79*8975f5c5SAndroid Build Coastguard Worker        '--results-file',
80*8975f5c5SAndroid Build Coastguard Worker        json_to_upload,
81*8975f5c5SAndroid Build Coastguard Worker        '--results-url',
82*8975f5c5SAndroid Build Coastguard Worker        RESULTS_URL,
83*8975f5c5SAndroid Build Coastguard Worker        '--output-json-file',
84*8975f5c5SAndroid Build Coastguard Worker        output_json_file,
85*8975f5c5SAndroid Build Coastguard Worker        '--perf-dashboard-machine-group',
86*8975f5c5SAndroid Build Coastguard Worker        MACHINE_GROUP,
87*8975f5c5SAndroid Build Coastguard Worker        '--got-angle-revision',
88*8975f5c5SAndroid Build Coastguard Worker        build_properties['got_angle_revision'],
89*8975f5c5SAndroid Build Coastguard Worker        '--send-as-histograms',
90*8975f5c5SAndroid Build Coastguard Worker        '--project',
91*8975f5c5SAndroid Build Coastguard Worker        'angle',
92*8975f5c5SAndroid Build Coastguard Worker    ]
93*8975f5c5SAndroid Build Coastguard Worker
94*8975f5c5SAndroid Build Coastguard Worker    if build_properties.get('git_revision'):
95*8975f5c5SAndroid Build Coastguard Worker        args.append('--git-revision')
96*8975f5c5SAndroid Build Coastguard Worker        args.append(build_properties['git_revision'])
97*8975f5c5SAndroid Build Coastguard Worker
98*8975f5c5SAndroid Build Coastguard Worker    #TODO(crbug.com/1072729): log this in top level
99*8975f5c5SAndroid Build Coastguard Worker    logging.info('upload_results_to_perf_dashboard: %s.' % args)
100*8975f5c5SAndroid Build Coastguard Worker
101*8975f5c5SAndroid Build Coastguard Worker    return upload_results_to_perf_dashboard.main(args)
102*8975f5c5SAndroid Build Coastguard Worker
103*8975f5c5SAndroid Build Coastguard Worker
104*8975f5c5SAndroid Build Coastguard Workerdef _merge_json_output(output_json, jsons_to_merge, extra_links, test_cross_device=False):
105*8975f5c5SAndroid Build Coastguard Worker    """Merges the contents of one or more results JSONs.
106*8975f5c5SAndroid Build Coastguard Worker
107*8975f5c5SAndroid Build Coastguard Worker  Args:
108*8975f5c5SAndroid Build Coastguard Worker    output_json: A path to a JSON file to which the merged results should be
109*8975f5c5SAndroid Build Coastguard Worker      written.
110*8975f5c5SAndroid Build Coastguard Worker    jsons_to_merge: A list of JSON files that should be merged.
111*8975f5c5SAndroid Build Coastguard Worker    extra_links: a (key, value) map in which keys are the human-readable strings
112*8975f5c5SAndroid Build Coastguard Worker      which describe the data, and value is logdog url that contain the data.
113*8975f5c5SAndroid Build Coastguard Worker  """
114*8975f5c5SAndroid Build Coastguard Worker    begin_time = time.time()
115*8975f5c5SAndroid Build Coastguard Worker    merged_results = results_merger.merge_test_results(jsons_to_merge, test_cross_device)
116*8975f5c5SAndroid Build Coastguard Worker
117*8975f5c5SAndroid Build Coastguard Worker    # Only append the perf results links if present
118*8975f5c5SAndroid Build Coastguard Worker    if extra_links:
119*8975f5c5SAndroid Build Coastguard Worker        merged_results['links'] = extra_links
120*8975f5c5SAndroid Build Coastguard Worker
121*8975f5c5SAndroid Build Coastguard Worker    with open(output_json, 'w') as f:
122*8975f5c5SAndroid Build Coastguard Worker        json.dump(merged_results, f)
123*8975f5c5SAndroid Build Coastguard Worker
124*8975f5c5SAndroid Build Coastguard Worker    end_time = time.time()
125*8975f5c5SAndroid Build Coastguard Worker    print_duration('Merging json test results', begin_time, end_time)
126*8975f5c5SAndroid Build Coastguard Worker    return 0
127*8975f5c5SAndroid Build Coastguard Worker
128*8975f5c5SAndroid Build Coastguard Worker
129*8975f5c5SAndroid Build Coastguard Workerdef _handle_perf_json_test_results(benchmark_directory_map, test_results_list):
130*8975f5c5SAndroid Build Coastguard Worker    """Checks the test_results.json under each folder:
131*8975f5c5SAndroid Build Coastguard Worker
132*8975f5c5SAndroid Build Coastguard Worker  1. mark the benchmark 'enabled' if tests results are found
133*8975f5c5SAndroid Build Coastguard Worker  2. add the json content to a list for non-ref.
134*8975f5c5SAndroid Build Coastguard Worker  """
135*8975f5c5SAndroid Build Coastguard Worker    begin_time = time.time()
136*8975f5c5SAndroid Build Coastguard Worker    benchmark_enabled_map = {}
137*8975f5c5SAndroid Build Coastguard Worker    for benchmark_name, directories in benchmark_directory_map.items():
138*8975f5c5SAndroid Build Coastguard Worker        for directory in directories:
139*8975f5c5SAndroid Build Coastguard Worker            # Obtain the test name we are running
140*8975f5c5SAndroid Build Coastguard Worker            is_ref = '.reference' in benchmark_name
141*8975f5c5SAndroid Build Coastguard Worker            enabled = True
142*8975f5c5SAndroid Build Coastguard Worker            try:
143*8975f5c5SAndroid Build Coastguard Worker                with open(os.path.join(directory, 'test_results.json')) as json_data:
144*8975f5c5SAndroid Build Coastguard Worker                    json_results = json.load(json_data)
145*8975f5c5SAndroid Build Coastguard Worker                    if not json_results:
146*8975f5c5SAndroid Build Coastguard Worker                        # Output is null meaning the test didn't produce any results.
147*8975f5c5SAndroid Build Coastguard Worker                        # Want to output an error and continue loading the rest of the
148*8975f5c5SAndroid Build Coastguard Worker                        # test results.
149*8975f5c5SAndroid Build Coastguard Worker                        logging.warning('No results produced for %s, skipping upload' % directory)
150*8975f5c5SAndroid Build Coastguard Worker                        continue
151*8975f5c5SAndroid Build Coastguard Worker                    if json_results.get('version') == 3:
152*8975f5c5SAndroid Build Coastguard Worker                        # Non-telemetry tests don't have written json results but
153*8975f5c5SAndroid Build Coastguard Worker                        # if they are executing then they are enabled and will generate
154*8975f5c5SAndroid Build Coastguard Worker                        # chartjson results.
155*8975f5c5SAndroid Build Coastguard Worker                        if not bool(json_results.get('tests')):
156*8975f5c5SAndroid Build Coastguard Worker                            enabled = False
157*8975f5c5SAndroid Build Coastguard Worker                    if not is_ref:
158*8975f5c5SAndroid Build Coastguard Worker                        # We don't need to upload reference build data to the
159*8975f5c5SAndroid Build Coastguard Worker                        # flakiness dashboard since we don't monitor the ref build
160*8975f5c5SAndroid Build Coastguard Worker                        test_results_list.append(json_results)
161*8975f5c5SAndroid Build Coastguard Worker            except IOError as e:
162*8975f5c5SAndroid Build Coastguard Worker                # TODO(crbug.com/936602): Figure out how to surface these errors. Should
163*8975f5c5SAndroid Build Coastguard Worker                # we have a non-zero exit code if we error out?
164*8975f5c5SAndroid Build Coastguard Worker                logging.error('Failed to obtain test results for %s: %s', benchmark_name, e)
165*8975f5c5SAndroid Build Coastguard Worker                continue
166*8975f5c5SAndroid Build Coastguard Worker            if not enabled:
167*8975f5c5SAndroid Build Coastguard Worker                # We don't upload disabled benchmarks or tests that are run
168*8975f5c5SAndroid Build Coastguard Worker                # as a smoke test
169*8975f5c5SAndroid Build Coastguard Worker                logging.info('Benchmark %s ran no tests on at least one shard' % benchmark_name)
170*8975f5c5SAndroid Build Coastguard Worker                continue
171*8975f5c5SAndroid Build Coastguard Worker            benchmark_enabled_map[benchmark_name] = True
172*8975f5c5SAndroid Build Coastguard Worker
173*8975f5c5SAndroid Build Coastguard Worker    end_time = time.time()
174*8975f5c5SAndroid Build Coastguard Worker    print_duration('Analyzing perf json test results', begin_time, end_time)
175*8975f5c5SAndroid Build Coastguard Worker    return benchmark_enabled_map
176*8975f5c5SAndroid Build Coastguard Worker
177*8975f5c5SAndroid Build Coastguard Worker
178*8975f5c5SAndroid Build Coastguard Workerdef _generate_unique_logdog_filename(name_prefix):
179*8975f5c5SAndroid Build Coastguard Worker    return name_prefix + '_' + str(uuid.uuid4())
180*8975f5c5SAndroid Build Coastguard Worker
181*8975f5c5SAndroid Build Coastguard Worker
182*8975f5c5SAndroid Build Coastguard Workerdef _handle_perf_logs(benchmark_directory_map, extra_links):
183*8975f5c5SAndroid Build Coastguard Worker    """ Upload benchmark logs to logdog and add a page entry for them. """
184*8975f5c5SAndroid Build Coastguard Worker    begin_time = time.time()
185*8975f5c5SAndroid Build Coastguard Worker    benchmark_logs_links = collections.defaultdict(list)
186*8975f5c5SAndroid Build Coastguard Worker
187*8975f5c5SAndroid Build Coastguard Worker    for benchmark_name, directories in benchmark_directory_map.items():
188*8975f5c5SAndroid Build Coastguard Worker        for directory in directories:
189*8975f5c5SAndroid Build Coastguard Worker            benchmark_log_file = os.path.join(directory, 'benchmark_log.txt')
190*8975f5c5SAndroid Build Coastguard Worker            if os.path.exists(benchmark_log_file):
191*8975f5c5SAndroid Build Coastguard Worker                with open(benchmark_log_file) as f:
192*8975f5c5SAndroid Build Coastguard Worker                    uploaded_link = logdog_helper.text(
193*8975f5c5SAndroid Build Coastguard Worker                        name=_generate_unique_logdog_filename(benchmark_name), data=f.read())
194*8975f5c5SAndroid Build Coastguard Worker                    benchmark_logs_links[benchmark_name].append(uploaded_link)
195*8975f5c5SAndroid Build Coastguard Worker
196*8975f5c5SAndroid Build Coastguard Worker    logdog_file_name = _generate_unique_logdog_filename('Benchmarks_Logs')
197*8975f5c5SAndroid Build Coastguard Worker    logdog_stream = logdog_helper.text(
198*8975f5c5SAndroid Build Coastguard Worker        logdog_file_name,
199*8975f5c5SAndroid Build Coastguard Worker        json.dumps(benchmark_logs_links, sort_keys=True, indent=4, separators=(',', ': ')),
200*8975f5c5SAndroid Build Coastguard Worker        content_type=JSON_CONTENT_TYPE)
201*8975f5c5SAndroid Build Coastguard Worker    extra_links['Benchmarks logs'] = logdog_stream
202*8975f5c5SAndroid Build Coastguard Worker    end_time = time.time()
203*8975f5c5SAndroid Build Coastguard Worker    print_duration('Generating perf log streams', begin_time, end_time)
204*8975f5c5SAndroid Build Coastguard Worker
205*8975f5c5SAndroid Build Coastguard Worker
206*8975f5c5SAndroid Build Coastguard Workerdef _handle_benchmarks_shard_map(benchmarks_shard_map_file, extra_links):
207*8975f5c5SAndroid Build Coastguard Worker    begin_time = time.time()
208*8975f5c5SAndroid Build Coastguard Worker    with open(benchmarks_shard_map_file) as f:
209*8975f5c5SAndroid Build Coastguard Worker        benchmarks_shard_data = f.read()
210*8975f5c5SAndroid Build Coastguard Worker        logdog_file_name = _generate_unique_logdog_filename('Benchmarks_Shard_Map')
211*8975f5c5SAndroid Build Coastguard Worker        logdog_stream = logdog_helper.text(
212*8975f5c5SAndroid Build Coastguard Worker            logdog_file_name, benchmarks_shard_data, content_type=JSON_CONTENT_TYPE)
213*8975f5c5SAndroid Build Coastguard Worker        extra_links['Benchmarks shard map'] = logdog_stream
214*8975f5c5SAndroid Build Coastguard Worker    end_time = time.time()
215*8975f5c5SAndroid Build Coastguard Worker    print_duration('Generating benchmark shard map stream', begin_time, end_time)
216*8975f5c5SAndroid Build Coastguard Worker
217*8975f5c5SAndroid Build Coastguard Worker
218*8975f5c5SAndroid Build Coastguard Workerdef _get_benchmark_name(directory):
219*8975f5c5SAndroid Build Coastguard Worker    return os.path.basename(directory).replace(" benchmark", "")
220*8975f5c5SAndroid Build Coastguard Worker
221*8975f5c5SAndroid Build Coastguard Worker
222*8975f5c5SAndroid Build Coastguard Workerdef _scan_output_dir(task_output_dir):
223*8975f5c5SAndroid Build Coastguard Worker    benchmark_directory_map = {}
224*8975f5c5SAndroid Build Coastguard Worker    benchmarks_shard_map_file = None
225*8975f5c5SAndroid Build Coastguard Worker
226*8975f5c5SAndroid Build Coastguard Worker    directory_list = [
227*8975f5c5SAndroid Build Coastguard Worker        f for f in os.listdir(task_output_dir)
228*8975f5c5SAndroid Build Coastguard Worker        if not os.path.isfile(os.path.join(task_output_dir, f))
229*8975f5c5SAndroid Build Coastguard Worker    ]
230*8975f5c5SAndroid Build Coastguard Worker    benchmark_directory_list = []
231*8975f5c5SAndroid Build Coastguard Worker    for directory in directory_list:
232*8975f5c5SAndroid Build Coastguard Worker        for f in os.listdir(os.path.join(task_output_dir, directory)):
233*8975f5c5SAndroid Build Coastguard Worker            path = os.path.join(task_output_dir, directory, f)
234*8975f5c5SAndroid Build Coastguard Worker            if os.path.isdir(path):
235*8975f5c5SAndroid Build Coastguard Worker                benchmark_directory_list.append(path)
236*8975f5c5SAndroid Build Coastguard Worker            elif path.endswith('benchmarks_shard_map.json'):
237*8975f5c5SAndroid Build Coastguard Worker                benchmarks_shard_map_file = path
238*8975f5c5SAndroid Build Coastguard Worker    # Now create a map of benchmark name to the list of directories
239*8975f5c5SAndroid Build Coastguard Worker    # the lists were written to.
240*8975f5c5SAndroid Build Coastguard Worker    for directory in benchmark_directory_list:
241*8975f5c5SAndroid Build Coastguard Worker        benchmark_name = _get_benchmark_name(directory)
242*8975f5c5SAndroid Build Coastguard Worker        logging.debug('Found benchmark %s directory %s' % (benchmark_name, directory))
243*8975f5c5SAndroid Build Coastguard Worker        if benchmark_name in benchmark_directory_map.keys():
244*8975f5c5SAndroid Build Coastguard Worker            benchmark_directory_map[benchmark_name].append(directory)
245*8975f5c5SAndroid Build Coastguard Worker        else:
246*8975f5c5SAndroid Build Coastguard Worker            benchmark_directory_map[benchmark_name] = [directory]
247*8975f5c5SAndroid Build Coastguard Worker
248*8975f5c5SAndroid Build Coastguard Worker    return benchmark_directory_map, benchmarks_shard_map_file
249*8975f5c5SAndroid Build Coastguard Worker
250*8975f5c5SAndroid Build Coastguard Worker
251*8975f5c5SAndroid Build Coastguard Workerdef _upload_to_skia_perf(benchmark_directory_map, benchmark_enabled_map, build_properties_map):
252*8975f5c5SAndroid Build Coastguard Worker    metric_filenames = []
253*8975f5c5SAndroid Build Coastguard Worker
254*8975f5c5SAndroid Build Coastguard Worker    for benchmark_name, directories in benchmark_directory_map.items():
255*8975f5c5SAndroid Build Coastguard Worker        if not benchmark_enabled_map.get(benchmark_name, False):
256*8975f5c5SAndroid Build Coastguard Worker            continue
257*8975f5c5SAndroid Build Coastguard Worker
258*8975f5c5SAndroid Build Coastguard Worker        for directory in directories:
259*8975f5c5SAndroid Build Coastguard Worker            metric_filenames.append(os.path.join(directory, 'angle_metrics.json'))
260*8975f5c5SAndroid Build Coastguard Worker
261*8975f5c5SAndroid Build Coastguard Worker    assert metric_filenames
262*8975f5c5SAndroid Build Coastguard Worker
263*8975f5c5SAndroid Build Coastguard Worker    buildername = build_properties_map['buildername']  # e.g. win10-nvidia-gtx1660-perf
264*8975f5c5SAndroid Build Coastguard Worker    skia_data = {
265*8975f5c5SAndroid Build Coastguard Worker        'version': 1,
266*8975f5c5SAndroid Build Coastguard Worker        'git_hash': build_properties_map['got_angle_revision'],
267*8975f5c5SAndroid Build Coastguard Worker        'key': {
268*8975f5c5SAndroid Build Coastguard Worker            'buildername': buildername,
269*8975f5c5SAndroid Build Coastguard Worker        },
270*8975f5c5SAndroid Build Coastguard Worker        'results': angle_metrics.ConvertToSkiaPerf(metric_filenames),
271*8975f5c5SAndroid Build Coastguard Worker    }
272*8975f5c5SAndroid Build Coastguard Worker
273*8975f5c5SAndroid Build Coastguard Worker    skia_perf_dir = tempfile.mkdtemp('skia_perf')
274*8975f5c5SAndroid Build Coastguard Worker    try:
275*8975f5c5SAndroid Build Coastguard Worker        local_file = os.path.join(skia_perf_dir, '%s.%s.json' % (buildername, time.time()))
276*8975f5c5SAndroid Build Coastguard Worker        with open(local_file, 'w') as f:
277*8975f5c5SAndroid Build Coastguard Worker            json.dump(skia_data, f, indent=2)
278*8975f5c5SAndroid Build Coastguard Worker        gs_dir = 'gs://angle-perf-skia/angle_perftests/%s/' % (
279*8975f5c5SAndroid Build Coastguard Worker            datetime.datetime.now().strftime('%Y/%m/%d/%H'))
280*8975f5c5SAndroid Build Coastguard Worker        upload_cmd = ['vpython3', GSUTIL_PY_PATH, 'cp', local_file, gs_dir]
281*8975f5c5SAndroid Build Coastguard Worker        logging.info('Skia upload: %s', ' '.join(upload_cmd))
282*8975f5c5SAndroid Build Coastguard Worker        subprocess.check_call(upload_cmd)
283*8975f5c5SAndroid Build Coastguard Worker    finally:
284*8975f5c5SAndroid Build Coastguard Worker        shutil.rmtree(skia_perf_dir)
285*8975f5c5SAndroid Build Coastguard Worker
286*8975f5c5SAndroid Build Coastguard Worker
287*8975f5c5SAndroid Build Coastguard Workerdef process_perf_results(output_json,
288*8975f5c5SAndroid Build Coastguard Worker                         configuration_name,
289*8975f5c5SAndroid Build Coastguard Worker                         build_properties,
290*8975f5c5SAndroid Build Coastguard Worker                         task_output_dir,
291*8975f5c5SAndroid Build Coastguard Worker                         smoke_test_mode,
292*8975f5c5SAndroid Build Coastguard Worker                         output_results_dir,
293*8975f5c5SAndroid Build Coastguard Worker                         lightweight=False,
294*8975f5c5SAndroid Build Coastguard Worker                         skip_perf=False):
295*8975f5c5SAndroid Build Coastguard Worker    """Process perf results.
296*8975f5c5SAndroid Build Coastguard Worker
297*8975f5c5SAndroid Build Coastguard Worker  Consists of merging the json-test-format output, uploading the perf test
298*8975f5c5SAndroid Build Coastguard Worker  output (histogram), and store the benchmark logs in logdog.
299*8975f5c5SAndroid Build Coastguard Worker
300*8975f5c5SAndroid Build Coastguard Worker  Each directory in the task_output_dir represents one benchmark
301*8975f5c5SAndroid Build Coastguard Worker  that was run. Within this directory, there is a subdirectory with the name
302*8975f5c5SAndroid Build Coastguard Worker  of the benchmark that was run. In that subdirectory, there is a
303*8975f5c5SAndroid Build Coastguard Worker  perftest-output.json file containing the performance results in histogram
304*8975f5c5SAndroid Build Coastguard Worker  format and an output.json file containing the json test results for the
305*8975f5c5SAndroid Build Coastguard Worker  benchmark.
306*8975f5c5SAndroid Build Coastguard Worker
307*8975f5c5SAndroid Build Coastguard Worker  Returns:
308*8975f5c5SAndroid Build Coastguard Worker    (return_code, upload_results_map):
309*8975f5c5SAndroid Build Coastguard Worker      return_code is 0 if the whole operation is successful, non zero otherwise.
310*8975f5c5SAndroid Build Coastguard Worker      benchmark_upload_result_map: the dictionary that describe which benchmarks
311*8975f5c5SAndroid Build Coastguard Worker        were successfully uploaded.
312*8975f5c5SAndroid Build Coastguard Worker  """
313*8975f5c5SAndroid Build Coastguard Worker    handle_perf = not lightweight or not skip_perf
314*8975f5c5SAndroid Build Coastguard Worker    handle_non_perf = not lightweight or skip_perf
315*8975f5c5SAndroid Build Coastguard Worker    logging.info('lightweight mode: %r; handle_perf: %r; handle_non_perf: %r' %
316*8975f5c5SAndroid Build Coastguard Worker                 (lightweight, handle_perf, handle_non_perf))
317*8975f5c5SAndroid Build Coastguard Worker
318*8975f5c5SAndroid Build Coastguard Worker    begin_time = time.time()
319*8975f5c5SAndroid Build Coastguard Worker    return_code = 0
320*8975f5c5SAndroid Build Coastguard Worker    benchmark_upload_result_map = {}
321*8975f5c5SAndroid Build Coastguard Worker
322*8975f5c5SAndroid Build Coastguard Worker    benchmark_directory_map, benchmarks_shard_map_file = _scan_output_dir(task_output_dir)
323*8975f5c5SAndroid Build Coastguard Worker
324*8975f5c5SAndroid Build Coastguard Worker    test_results_list = []
325*8975f5c5SAndroid Build Coastguard Worker    extra_links = {}
326*8975f5c5SAndroid Build Coastguard Worker
327*8975f5c5SAndroid Build Coastguard Worker    if handle_non_perf:
328*8975f5c5SAndroid Build Coastguard Worker        # First, upload benchmarks shard map to logdog and add a page
329*8975f5c5SAndroid Build Coastguard Worker        # entry for it in extra_links.
330*8975f5c5SAndroid Build Coastguard Worker        if benchmarks_shard_map_file:
331*8975f5c5SAndroid Build Coastguard Worker            _handle_benchmarks_shard_map(benchmarks_shard_map_file, extra_links)
332*8975f5c5SAndroid Build Coastguard Worker
333*8975f5c5SAndroid Build Coastguard Worker        # Second, upload all the benchmark logs to logdog and add a page entry for
334*8975f5c5SAndroid Build Coastguard Worker        # those links in extra_links.
335*8975f5c5SAndroid Build Coastguard Worker        _handle_perf_logs(benchmark_directory_map, extra_links)
336*8975f5c5SAndroid Build Coastguard Worker
337*8975f5c5SAndroid Build Coastguard Worker    # Then try to obtain the list of json test results to merge
338*8975f5c5SAndroid Build Coastguard Worker    # and determine the status of each benchmark.
339*8975f5c5SAndroid Build Coastguard Worker    benchmark_enabled_map = _handle_perf_json_test_results(benchmark_directory_map,
340*8975f5c5SAndroid Build Coastguard Worker                                                           test_results_list)
341*8975f5c5SAndroid Build Coastguard Worker
342*8975f5c5SAndroid Build Coastguard Worker    if not smoke_test_mode and handle_perf:
343*8975f5c5SAndroid Build Coastguard Worker        build_properties_map = json.loads(build_properties)
344*8975f5c5SAndroid Build Coastguard Worker        if not configuration_name:
345*8975f5c5SAndroid Build Coastguard Worker            # we are deprecating perf-id crbug.com/817823
346*8975f5c5SAndroid Build Coastguard Worker            configuration_name = build_properties_map['buildername']
347*8975f5c5SAndroid Build Coastguard Worker
348*8975f5c5SAndroid Build Coastguard Worker        try:
349*8975f5c5SAndroid Build Coastguard Worker            return_code, benchmark_upload_result_map = _handle_perf_results(
350*8975f5c5SAndroid Build Coastguard Worker                benchmark_enabled_map, benchmark_directory_map, configuration_name,
351*8975f5c5SAndroid Build Coastguard Worker                build_properties_map, extra_links, output_results_dir)
352*8975f5c5SAndroid Build Coastguard Worker        except Exception:
353*8975f5c5SAndroid Build Coastguard Worker            logging.exception('Error handling perf results jsons')
354*8975f5c5SAndroid Build Coastguard Worker            return_code = 1
355*8975f5c5SAndroid Build Coastguard Worker
356*8975f5c5SAndroid Build Coastguard Worker        try:
357*8975f5c5SAndroid Build Coastguard Worker            _upload_to_skia_perf(benchmark_directory_map, benchmark_enabled_map,
358*8975f5c5SAndroid Build Coastguard Worker                                 build_properties_map)
359*8975f5c5SAndroid Build Coastguard Worker        except Exception:
360*8975f5c5SAndroid Build Coastguard Worker            logging.exception('Error uploading to skia perf')
361*8975f5c5SAndroid Build Coastguard Worker            return_code = 1
362*8975f5c5SAndroid Build Coastguard Worker
363*8975f5c5SAndroid Build Coastguard Worker    if handle_non_perf:
364*8975f5c5SAndroid Build Coastguard Worker        # Finally, merge all test results json, add the extra links and write out to
365*8975f5c5SAndroid Build Coastguard Worker        # output location
366*8975f5c5SAndroid Build Coastguard Worker        try:
367*8975f5c5SAndroid Build Coastguard Worker            _merge_json_output(output_json, test_results_list, extra_links)
368*8975f5c5SAndroid Build Coastguard Worker        except Exception:
369*8975f5c5SAndroid Build Coastguard Worker            logging.exception('Error handling test results jsons.')
370*8975f5c5SAndroid Build Coastguard Worker
371*8975f5c5SAndroid Build Coastguard Worker    end_time = time.time()
372*8975f5c5SAndroid Build Coastguard Worker    print_duration('Total process_perf_results', begin_time, end_time)
373*8975f5c5SAndroid Build Coastguard Worker    return return_code, benchmark_upload_result_map
374*8975f5c5SAndroid Build Coastguard Worker
375*8975f5c5SAndroid Build Coastguard Worker
376*8975f5c5SAndroid Build Coastguard Workerdef _merge_histogram_results(histogram_lists):
377*8975f5c5SAndroid Build Coastguard Worker    merged_results = []
378*8975f5c5SAndroid Build Coastguard Worker    for histogram_list in histogram_lists:
379*8975f5c5SAndroid Build Coastguard Worker        merged_results += histogram_list
380*8975f5c5SAndroid Build Coastguard Worker
381*8975f5c5SAndroid Build Coastguard Worker    return merged_results
382*8975f5c5SAndroid Build Coastguard Worker
383*8975f5c5SAndroid Build Coastguard Worker
384*8975f5c5SAndroid Build Coastguard Workerdef _load_histogram_set_from_dict(data):
385*8975f5c5SAndroid Build Coastguard Worker    histograms = histogram_set.HistogramSet()
386*8975f5c5SAndroid Build Coastguard Worker    histograms.ImportDicts(data)
387*8975f5c5SAndroid Build Coastguard Worker    return histograms
388*8975f5c5SAndroid Build Coastguard Worker
389*8975f5c5SAndroid Build Coastguard Worker
390*8975f5c5SAndroid Build Coastguard Workerdef _add_build_info(results, benchmark_name, build_properties):
391*8975f5c5SAndroid Build Coastguard Worker    histograms = _load_histogram_set_from_dict(results)
392*8975f5c5SAndroid Build Coastguard Worker
393*8975f5c5SAndroid Build Coastguard Worker    common_diagnostics = {
394*8975f5c5SAndroid Build Coastguard Worker        reserved_infos.MASTERS:
395*8975f5c5SAndroid Build Coastguard Worker            build_properties['builder_group'],
396*8975f5c5SAndroid Build Coastguard Worker        reserved_infos.BOTS:
397*8975f5c5SAndroid Build Coastguard Worker            build_properties['buildername'],
398*8975f5c5SAndroid Build Coastguard Worker        reserved_infos.POINT_ID:
399*8975f5c5SAndroid Build Coastguard Worker            build_properties['angle_commit_pos'],
400*8975f5c5SAndroid Build Coastguard Worker        reserved_infos.BENCHMARKS:
401*8975f5c5SAndroid Build Coastguard Worker            benchmark_name,
402*8975f5c5SAndroid Build Coastguard Worker        reserved_infos.ANGLE_REVISIONS:
403*8975f5c5SAndroid Build Coastguard Worker            build_properties['got_angle_revision'],
404*8975f5c5SAndroid Build Coastguard Worker        reserved_infos.BUILD_URLS:
405*8975f5c5SAndroid Build Coastguard Worker            BUILD_URL % (build_properties['buildername'], build_properties['buildnumber']),
406*8975f5c5SAndroid Build Coastguard Worker    }
407*8975f5c5SAndroid Build Coastguard Worker
408*8975f5c5SAndroid Build Coastguard Worker    for k, v in common_diagnostics.items():
409*8975f5c5SAndroid Build Coastguard Worker        histograms.AddSharedDiagnosticToAllHistograms(k.name, generic_set.GenericSet([v]))
410*8975f5c5SAndroid Build Coastguard Worker
411*8975f5c5SAndroid Build Coastguard Worker    return histograms.AsDicts()
412*8975f5c5SAndroid Build Coastguard Worker
413*8975f5c5SAndroid Build Coastguard Worker
414*8975f5c5SAndroid Build Coastguard Workerdef _merge_perf_results(benchmark_name, results_filename, directories, build_properties):
415*8975f5c5SAndroid Build Coastguard Worker    begin_time = time.time()
416*8975f5c5SAndroid Build Coastguard Worker    collected_results = []
417*8975f5c5SAndroid Build Coastguard Worker    for directory in directories:
418*8975f5c5SAndroid Build Coastguard Worker        filename = os.path.join(directory, 'perf_results.json')
419*8975f5c5SAndroid Build Coastguard Worker        try:
420*8975f5c5SAndroid Build Coastguard Worker            with open(filename) as pf:
421*8975f5c5SAndroid Build Coastguard Worker                collected_results.append(json.load(pf))
422*8975f5c5SAndroid Build Coastguard Worker        except IOError as e:
423*8975f5c5SAndroid Build Coastguard Worker            # TODO(crbug.com/936602): Figure out how to surface these errors. Should
424*8975f5c5SAndroid Build Coastguard Worker            # we have a non-zero exit code if we error out?
425*8975f5c5SAndroid Build Coastguard Worker            logging.error('Failed to obtain perf results from %s: %s', directory, e)
426*8975f5c5SAndroid Build Coastguard Worker    if not collected_results:
427*8975f5c5SAndroid Build Coastguard Worker        logging.error('Failed to obtain any perf results from %s.', benchmark_name)
428*8975f5c5SAndroid Build Coastguard Worker        return
429*8975f5c5SAndroid Build Coastguard Worker
430*8975f5c5SAndroid Build Coastguard Worker    # Assuming that multiple shards will be histogram set
431*8975f5c5SAndroid Build Coastguard Worker    # Non-telemetry benchmarks only ever run on one shard
432*8975f5c5SAndroid Build Coastguard Worker    merged_results = []
433*8975f5c5SAndroid Build Coastguard Worker    assert (isinstance(collected_results[0], list))
434*8975f5c5SAndroid Build Coastguard Worker    merged_results = _merge_histogram_results(collected_results)
435*8975f5c5SAndroid Build Coastguard Worker
436*8975f5c5SAndroid Build Coastguard Worker    # Write additional histogram build info.
437*8975f5c5SAndroid Build Coastguard Worker    merged_results = _add_build_info(merged_results, benchmark_name, build_properties)
438*8975f5c5SAndroid Build Coastguard Worker
439*8975f5c5SAndroid Build Coastguard Worker    with open(results_filename, 'w') as rf:
440*8975f5c5SAndroid Build Coastguard Worker        json.dump(merged_results, rf)
441*8975f5c5SAndroid Build Coastguard Worker
442*8975f5c5SAndroid Build Coastguard Worker    end_time = time.time()
443*8975f5c5SAndroid Build Coastguard Worker    print_duration(('%s results merging' % (benchmark_name)), begin_time, end_time)
444*8975f5c5SAndroid Build Coastguard Worker
445*8975f5c5SAndroid Build Coastguard Worker
446*8975f5c5SAndroid Build Coastguard Workerdef _upload_individual(benchmark_name, directories, configuration_name, build_properties,
447*8975f5c5SAndroid Build Coastguard Worker                       output_json_file):
448*8975f5c5SAndroid Build Coastguard Worker    tmpfile_dir = tempfile.mkdtemp()
449*8975f5c5SAndroid Build Coastguard Worker    try:
450*8975f5c5SAndroid Build Coastguard Worker        upload_begin_time = time.time()
451*8975f5c5SAndroid Build Coastguard Worker        # There are potentially multiple directores with results, re-write and
452*8975f5c5SAndroid Build Coastguard Worker        # merge them if necessary
453*8975f5c5SAndroid Build Coastguard Worker        results_filename = None
454*8975f5c5SAndroid Build Coastguard Worker        if len(directories) > 1:
455*8975f5c5SAndroid Build Coastguard Worker            merge_perf_dir = os.path.join(os.path.abspath(tmpfile_dir), benchmark_name)
456*8975f5c5SAndroid Build Coastguard Worker            if not os.path.exists(merge_perf_dir):
457*8975f5c5SAndroid Build Coastguard Worker                os.makedirs(merge_perf_dir)
458*8975f5c5SAndroid Build Coastguard Worker            results_filename = os.path.join(merge_perf_dir, 'merged_perf_results.json')
459*8975f5c5SAndroid Build Coastguard Worker            _merge_perf_results(benchmark_name, results_filename, directories, build_properties)
460*8975f5c5SAndroid Build Coastguard Worker        else:
461*8975f5c5SAndroid Build Coastguard Worker            # It was only written to one shard, use that shards data
462*8975f5c5SAndroid Build Coastguard Worker            results_filename = os.path.join(directories[0], 'perf_results.json')
463*8975f5c5SAndroid Build Coastguard Worker
464*8975f5c5SAndroid Build Coastguard Worker        results_size_in_mib = os.path.getsize(results_filename) / (2**20)
465*8975f5c5SAndroid Build Coastguard Worker        logging.info('Uploading perf results from %s benchmark (size %s Mib)' %
466*8975f5c5SAndroid Build Coastguard Worker                     (benchmark_name, results_size_in_mib))
467*8975f5c5SAndroid Build Coastguard Worker        upload_return_code = _upload_perf_results(results_filename, benchmark_name,
468*8975f5c5SAndroid Build Coastguard Worker                                                  configuration_name, build_properties,
469*8975f5c5SAndroid Build Coastguard Worker                                                  output_json_file)
470*8975f5c5SAndroid Build Coastguard Worker        upload_end_time = time.time()
471*8975f5c5SAndroid Build Coastguard Worker        print_duration(('%s upload time' % (benchmark_name)), upload_begin_time, upload_end_time)
472*8975f5c5SAndroid Build Coastguard Worker        return (benchmark_name, upload_return_code == 0)
473*8975f5c5SAndroid Build Coastguard Worker    finally:
474*8975f5c5SAndroid Build Coastguard Worker        shutil.rmtree(tmpfile_dir)
475*8975f5c5SAndroid Build Coastguard Worker
476*8975f5c5SAndroid Build Coastguard Worker
477*8975f5c5SAndroid Build Coastguard Workerdef _upload_individual_benchmark(params):
478*8975f5c5SAndroid Build Coastguard Worker    try:
479*8975f5c5SAndroid Build Coastguard Worker        return _upload_individual(*params)
480*8975f5c5SAndroid Build Coastguard Worker    except Exception:
481*8975f5c5SAndroid Build Coastguard Worker        benchmark_name = params[0]
482*8975f5c5SAndroid Build Coastguard Worker        upload_succeed = False
483*8975f5c5SAndroid Build Coastguard Worker        logging.exception('Error uploading perf result of %s' % benchmark_name)
484*8975f5c5SAndroid Build Coastguard Worker        return benchmark_name, upload_succeed
485*8975f5c5SAndroid Build Coastguard Worker
486*8975f5c5SAndroid Build Coastguard Worker
487*8975f5c5SAndroid Build Coastguard Workerdef _GetCpuCount(log=True):
488*8975f5c5SAndroid Build Coastguard Worker    try:
489*8975f5c5SAndroid Build Coastguard Worker        cpu_count = multiprocessing.cpu_count()
490*8975f5c5SAndroid Build Coastguard Worker        if sys.platform == 'win32':
491*8975f5c5SAndroid Build Coastguard Worker            # TODO(crbug.com/1190269) - we can't use more than 56
492*8975f5c5SAndroid Build Coastguard Worker            # cores on Windows or Python3 may hang.
493*8975f5c5SAndroid Build Coastguard Worker            cpu_count = min(cpu_count, 56)
494*8975f5c5SAndroid Build Coastguard Worker        return cpu_count
495*8975f5c5SAndroid Build Coastguard Worker    except NotImplementedError:
496*8975f5c5SAndroid Build Coastguard Worker        if log:
497*8975f5c5SAndroid Build Coastguard Worker            logging.warn('Failed to get a CPU count for this bot. See crbug.com/947035.')
498*8975f5c5SAndroid Build Coastguard Worker        # TODO(crbug.com/948281): This is currently set to 4 since the mac masters
499*8975f5c5SAndroid Build Coastguard Worker        # only have 4 cores. Once we move to all-linux, this can be increased or
500*8975f5c5SAndroid Build Coastguard Worker        # we can even delete this whole function and use multiprocessing.cpu_count()
501*8975f5c5SAndroid Build Coastguard Worker        # directly.
502*8975f5c5SAndroid Build Coastguard Worker        return 4
503*8975f5c5SAndroid Build Coastguard Worker
504*8975f5c5SAndroid Build Coastguard Worker
505*8975f5c5SAndroid Build Coastguard Workerdef _load_shard_id_from_test_results(directory):
506*8975f5c5SAndroid Build Coastguard Worker    shard_id = None
507*8975f5c5SAndroid Build Coastguard Worker    test_json_path = os.path.join(directory, 'test_results.json')
508*8975f5c5SAndroid Build Coastguard Worker    try:
509*8975f5c5SAndroid Build Coastguard Worker        with open(test_json_path) as f:
510*8975f5c5SAndroid Build Coastguard Worker            test_json = json.load(f)
511*8975f5c5SAndroid Build Coastguard Worker            all_results = test_json['tests']
512*8975f5c5SAndroid Build Coastguard Worker            for _, benchmark_results in all_results.items():
513*8975f5c5SAndroid Build Coastguard Worker                for _, measurement_result in benchmark_results.items():
514*8975f5c5SAndroid Build Coastguard Worker                    shard_id = measurement_result['shard']
515*8975f5c5SAndroid Build Coastguard Worker                    break
516*8975f5c5SAndroid Build Coastguard Worker    except IOError as e:
517*8975f5c5SAndroid Build Coastguard Worker        logging.error('Failed to open test_results.json from %s: %s', test_json_path, e)
518*8975f5c5SAndroid Build Coastguard Worker    except KeyError as e:
519*8975f5c5SAndroid Build Coastguard Worker        logging.error('Failed to locate results in test_results.json: %s', e)
520*8975f5c5SAndroid Build Coastguard Worker    return shard_id
521*8975f5c5SAndroid Build Coastguard Worker
522*8975f5c5SAndroid Build Coastguard Worker
523*8975f5c5SAndroid Build Coastguard Workerdef _find_device_id_by_shard_id(benchmarks_shard_map_file, shard_id):
524*8975f5c5SAndroid Build Coastguard Worker    try:
525*8975f5c5SAndroid Build Coastguard Worker        with open(benchmarks_shard_map_file) as f:
526*8975f5c5SAndroid Build Coastguard Worker            shard_map_json = json.load(f)
527*8975f5c5SAndroid Build Coastguard Worker            device_id = shard_map_json['extra_infos']['bot #%s' % shard_id]
528*8975f5c5SAndroid Build Coastguard Worker    except KeyError as e:
529*8975f5c5SAndroid Build Coastguard Worker        logging.error('Failed to locate device name in shard map: %s', e)
530*8975f5c5SAndroid Build Coastguard Worker    return device_id
531*8975f5c5SAndroid Build Coastguard Worker
532*8975f5c5SAndroid Build Coastguard Worker
533*8975f5c5SAndroid Build Coastguard Workerdef _update_perf_json_with_summary_on_device_id(directory, device_id):
534*8975f5c5SAndroid Build Coastguard Worker    perf_json_path = os.path.join(directory, 'perf_results.json')
535*8975f5c5SAndroid Build Coastguard Worker    try:
536*8975f5c5SAndroid Build Coastguard Worker        with open(perf_json_path, 'r') as f:
537*8975f5c5SAndroid Build Coastguard Worker            perf_json = json.load(f)
538*8975f5c5SAndroid Build Coastguard Worker    except IOError as e:
539*8975f5c5SAndroid Build Coastguard Worker        logging.error('Failed to open perf_results.json from %s: %s', perf_json_path, e)
540*8975f5c5SAndroid Build Coastguard Worker    summary_key_guid = str(uuid.uuid4())
541*8975f5c5SAndroid Build Coastguard Worker    summary_key_generic_set = {
542*8975f5c5SAndroid Build Coastguard Worker        'values': ['device_id'],
543*8975f5c5SAndroid Build Coastguard Worker        'guid': summary_key_guid,
544*8975f5c5SAndroid Build Coastguard Worker        'type': 'GenericSet'
545*8975f5c5SAndroid Build Coastguard Worker    }
546*8975f5c5SAndroid Build Coastguard Worker    perf_json.insert(0, summary_key_generic_set)
547*8975f5c5SAndroid Build Coastguard Worker    logging.info('Inserted summary key generic set for perf result in %s: %s', directory,
548*8975f5c5SAndroid Build Coastguard Worker                 summary_key_generic_set)
549*8975f5c5SAndroid Build Coastguard Worker    stories_guids = set()
550*8975f5c5SAndroid Build Coastguard Worker    for entry in perf_json:
551*8975f5c5SAndroid Build Coastguard Worker        if 'diagnostics' in entry:
552*8975f5c5SAndroid Build Coastguard Worker            entry['diagnostics']['summaryKeys'] = summary_key_guid
553*8975f5c5SAndroid Build Coastguard Worker            stories_guids.add(entry['diagnostics']['stories'])
554*8975f5c5SAndroid Build Coastguard Worker    for entry in perf_json:
555*8975f5c5SAndroid Build Coastguard Worker        if 'guid' in entry and entry['guid'] in stories_guids:
556*8975f5c5SAndroid Build Coastguard Worker            entry['values'].append(device_id)
557*8975f5c5SAndroid Build Coastguard Worker    try:
558*8975f5c5SAndroid Build Coastguard Worker        with open(perf_json_path, 'w') as f:
559*8975f5c5SAndroid Build Coastguard Worker            json.dump(perf_json, f)
560*8975f5c5SAndroid Build Coastguard Worker    except IOError as e:
561*8975f5c5SAndroid Build Coastguard Worker        logging.error('Failed to writing perf_results.json to %s: %s', perf_json_path, e)
562*8975f5c5SAndroid Build Coastguard Worker    logging.info('Finished adding device id %s in perf result.', device_id)
563*8975f5c5SAndroid Build Coastguard Worker
564*8975f5c5SAndroid Build Coastguard Worker
565*8975f5c5SAndroid Build Coastguard Workerdef _handle_perf_results(benchmark_enabled_map, benchmark_directory_map, configuration_name,
566*8975f5c5SAndroid Build Coastguard Worker                         build_properties, extra_links, output_results_dir):
567*8975f5c5SAndroid Build Coastguard Worker    """
568*8975f5c5SAndroid Build Coastguard Worker    Upload perf results to the perf dashboard.
569*8975f5c5SAndroid Build Coastguard Worker
570*8975f5c5SAndroid Build Coastguard Worker    This method also upload the perf results to logdog and augment it to
571*8975f5c5SAndroid Build Coastguard Worker    |extra_links|.
572*8975f5c5SAndroid Build Coastguard Worker
573*8975f5c5SAndroid Build Coastguard Worker    Returns:
574*8975f5c5SAndroid Build Coastguard Worker      (return_code, benchmark_upload_result_map)
575*8975f5c5SAndroid Build Coastguard Worker      return_code is 0 if this upload to perf dashboard successfully, 1
576*8975f5c5SAndroid Build Coastguard Worker        otherwise.
577*8975f5c5SAndroid Build Coastguard Worker       benchmark_upload_result_map is a dictionary describes which benchmark
578*8975f5c5SAndroid Build Coastguard Worker        was successfully uploaded.
579*8975f5c5SAndroid Build Coastguard Worker  """
580*8975f5c5SAndroid Build Coastguard Worker    begin_time = time.time()
581*8975f5c5SAndroid Build Coastguard Worker    # Upload all eligible benchmarks to the perf dashboard
582*8975f5c5SAndroid Build Coastguard Worker    results_dict = {}
583*8975f5c5SAndroid Build Coastguard Worker
584*8975f5c5SAndroid Build Coastguard Worker    invocations = []
585*8975f5c5SAndroid Build Coastguard Worker    for benchmark_name, directories in benchmark_directory_map.items():
586*8975f5c5SAndroid Build Coastguard Worker        if not benchmark_enabled_map.get(benchmark_name, False):
587*8975f5c5SAndroid Build Coastguard Worker            continue
588*8975f5c5SAndroid Build Coastguard Worker        # Create a place to write the perf results that you will write out to
589*8975f5c5SAndroid Build Coastguard Worker        # logdog.
590*8975f5c5SAndroid Build Coastguard Worker        output_json_file = os.path.join(output_results_dir, (str(uuid.uuid4()) + benchmark_name))
591*8975f5c5SAndroid Build Coastguard Worker        results_dict[benchmark_name] = output_json_file
592*8975f5c5SAndroid Build Coastguard Worker        #TODO(crbug.com/1072729): pass final arguments instead of build properties
593*8975f5c5SAndroid Build Coastguard Worker        # and configuration_name
594*8975f5c5SAndroid Build Coastguard Worker        invocations.append(
595*8975f5c5SAndroid Build Coastguard Worker            (benchmark_name, directories, configuration_name, build_properties, output_json_file))
596*8975f5c5SAndroid Build Coastguard Worker
597*8975f5c5SAndroid Build Coastguard Worker    # Kick off the uploads in multiple processes
598*8975f5c5SAndroid Build Coastguard Worker    # crbug.com/1035930: We are hitting HTTP Response 429. Limit ourselves
599*8975f5c5SAndroid Build Coastguard Worker    # to 2 processes to avoid this error. Uncomment the following code once
600*8975f5c5SAndroid Build Coastguard Worker    # the problem is fixed on the dashboard side.
601*8975f5c5SAndroid Build Coastguard Worker    # pool = multiprocessing.Pool(_GetCpuCount())
602*8975f5c5SAndroid Build Coastguard Worker    pool = multiprocessing.Pool(2)
603*8975f5c5SAndroid Build Coastguard Worker    upload_result_timeout = False
604*8975f5c5SAndroid Build Coastguard Worker    try:
605*8975f5c5SAndroid Build Coastguard Worker        async_result = pool.map_async(_upload_individual_benchmark, invocations)
606*8975f5c5SAndroid Build Coastguard Worker        # TODO(crbug.com/947035): What timeout is reasonable?
607*8975f5c5SAndroid Build Coastguard Worker        results = async_result.get(timeout=4000)
608*8975f5c5SAndroid Build Coastguard Worker    except multiprocessing.TimeoutError:
609*8975f5c5SAndroid Build Coastguard Worker        upload_result_timeout = True
610*8975f5c5SAndroid Build Coastguard Worker        logging.error('Timeout uploading benchmarks to perf dashboard in parallel')
611*8975f5c5SAndroid Build Coastguard Worker        results = []
612*8975f5c5SAndroid Build Coastguard Worker        for benchmark_name in benchmark_directory_map:
613*8975f5c5SAndroid Build Coastguard Worker            results.append((benchmark_name, False))
614*8975f5c5SAndroid Build Coastguard Worker    finally:
615*8975f5c5SAndroid Build Coastguard Worker        pool.terminate()
616*8975f5c5SAndroid Build Coastguard Worker
617*8975f5c5SAndroid Build Coastguard Worker    # Keep a mapping of benchmarks to their upload results
618*8975f5c5SAndroid Build Coastguard Worker    benchmark_upload_result_map = {}
619*8975f5c5SAndroid Build Coastguard Worker    for r in results:
620*8975f5c5SAndroid Build Coastguard Worker        benchmark_upload_result_map[r[0]] = r[1]
621*8975f5c5SAndroid Build Coastguard Worker
622*8975f5c5SAndroid Build Coastguard Worker    logdog_dict = {}
623*8975f5c5SAndroid Build Coastguard Worker    upload_failures_counter = 0
624*8975f5c5SAndroid Build Coastguard Worker    logdog_stream = None
625*8975f5c5SAndroid Build Coastguard Worker    logdog_label = 'Results Dashboard'
626*8975f5c5SAndroid Build Coastguard Worker    for benchmark_name, output_file in results_dict.items():
627*8975f5c5SAndroid Build Coastguard Worker        upload_succeed = benchmark_upload_result_map[benchmark_name]
628*8975f5c5SAndroid Build Coastguard Worker        if not upload_succeed:
629*8975f5c5SAndroid Build Coastguard Worker            upload_failures_counter += 1
630*8975f5c5SAndroid Build Coastguard Worker        is_reference = '.reference' in benchmark_name
631*8975f5c5SAndroid Build Coastguard Worker        _write_perf_data_to_logfile(
632*8975f5c5SAndroid Build Coastguard Worker            benchmark_name,
633*8975f5c5SAndroid Build Coastguard Worker            output_file,
634*8975f5c5SAndroid Build Coastguard Worker            configuration_name,
635*8975f5c5SAndroid Build Coastguard Worker            build_properties,
636*8975f5c5SAndroid Build Coastguard Worker            logdog_dict,
637*8975f5c5SAndroid Build Coastguard Worker            is_reference,
638*8975f5c5SAndroid Build Coastguard Worker            upload_failure=not upload_succeed)
639*8975f5c5SAndroid Build Coastguard Worker
640*8975f5c5SAndroid Build Coastguard Worker    logdog_file_name = _generate_unique_logdog_filename('Results_Dashboard_')
641*8975f5c5SAndroid Build Coastguard Worker    logdog_stream = logdog_helper.text(
642*8975f5c5SAndroid Build Coastguard Worker        logdog_file_name,
643*8975f5c5SAndroid Build Coastguard Worker        json.dumps(dict(logdog_dict), sort_keys=True, indent=4, separators=(',', ': ')),
644*8975f5c5SAndroid Build Coastguard Worker        content_type=JSON_CONTENT_TYPE)
645*8975f5c5SAndroid Build Coastguard Worker    if upload_failures_counter > 0:
646*8975f5c5SAndroid Build Coastguard Worker        logdog_label += (' %s merge script perf data upload failures' % upload_failures_counter)
647*8975f5c5SAndroid Build Coastguard Worker    extra_links[logdog_label] = logdog_stream
648*8975f5c5SAndroid Build Coastguard Worker    end_time = time.time()
649*8975f5c5SAndroid Build Coastguard Worker    print_duration('Uploading results to perf dashboard', begin_time, end_time)
650*8975f5c5SAndroid Build Coastguard Worker    if upload_result_timeout or upload_failures_counter > 0:
651*8975f5c5SAndroid Build Coastguard Worker        return 1, benchmark_upload_result_map
652*8975f5c5SAndroid Build Coastguard Worker    return 0, benchmark_upload_result_map
653*8975f5c5SAndroid Build Coastguard Worker
654*8975f5c5SAndroid Build Coastguard Worker
655*8975f5c5SAndroid Build Coastguard Workerdef _write_perf_data_to_logfile(benchmark_name, output_file, configuration_name, build_properties,
656*8975f5c5SAndroid Build Coastguard Worker                                logdog_dict, is_ref, upload_failure):
657*8975f5c5SAndroid Build Coastguard Worker    viewer_url = None
658*8975f5c5SAndroid Build Coastguard Worker    # logdog file to write perf results to
659*8975f5c5SAndroid Build Coastguard Worker    if os.path.exists(output_file):
660*8975f5c5SAndroid Build Coastguard Worker        results = None
661*8975f5c5SAndroid Build Coastguard Worker        with open(output_file) as f:
662*8975f5c5SAndroid Build Coastguard Worker            try:
663*8975f5c5SAndroid Build Coastguard Worker                results = json.load(f)
664*8975f5c5SAndroid Build Coastguard Worker            except ValueError:
665*8975f5c5SAndroid Build Coastguard Worker                logging.error('Error parsing perf results JSON for benchmark  %s' % benchmark_name)
666*8975f5c5SAndroid Build Coastguard Worker        if results:
667*8975f5c5SAndroid Build Coastguard Worker            try:
668*8975f5c5SAndroid Build Coastguard Worker                json_fname = _generate_unique_logdog_filename(benchmark_name)
669*8975f5c5SAndroid Build Coastguard Worker                output_json_file = logdog_helper.open_text(json_fname)
670*8975f5c5SAndroid Build Coastguard Worker                json.dump(results, output_json_file, indent=4, separators=(',', ': '))
671*8975f5c5SAndroid Build Coastguard Worker            except ValueError as e:
672*8975f5c5SAndroid Build Coastguard Worker                logging.error('ValueError: "%s" while dumping output to logdog' % e)
673*8975f5c5SAndroid Build Coastguard Worker            finally:
674*8975f5c5SAndroid Build Coastguard Worker                output_json_file.close()
675*8975f5c5SAndroid Build Coastguard Worker            viewer_url = output_json_file.get_viewer_url()
676*8975f5c5SAndroid Build Coastguard Worker    else:
677*8975f5c5SAndroid Build Coastguard Worker        logging.warning("Perf results JSON file doesn't exist for benchmark %s" % benchmark_name)
678*8975f5c5SAndroid Build Coastguard Worker
679*8975f5c5SAndroid Build Coastguard Worker    base_benchmark_name = benchmark_name.replace('.reference', '')
680*8975f5c5SAndroid Build Coastguard Worker
681*8975f5c5SAndroid Build Coastguard Worker    if base_benchmark_name not in logdog_dict:
682*8975f5c5SAndroid Build Coastguard Worker        logdog_dict[base_benchmark_name] = {}
683*8975f5c5SAndroid Build Coastguard Worker
684*8975f5c5SAndroid Build Coastguard Worker    # add links for the perf results and the dashboard url to
685*8975f5c5SAndroid Build Coastguard Worker    # the logs section of buildbot
686*8975f5c5SAndroid Build Coastguard Worker    if is_ref:
687*8975f5c5SAndroid Build Coastguard Worker        if viewer_url:
688*8975f5c5SAndroid Build Coastguard Worker            logdog_dict[base_benchmark_name]['perf_results_ref'] = viewer_url
689*8975f5c5SAndroid Build Coastguard Worker        if upload_failure:
690*8975f5c5SAndroid Build Coastguard Worker            logdog_dict[base_benchmark_name]['ref_upload_failed'] = 'True'
691*8975f5c5SAndroid Build Coastguard Worker    else:
692*8975f5c5SAndroid Build Coastguard Worker        # TODO(jmadill): Figure out if we can get a dashboard URL here. http://anglebug.com/40096778
693*8975f5c5SAndroid Build Coastguard Worker        # logdog_dict[base_benchmark_name]['dashboard_url'] = (
694*8975f5c5SAndroid Build Coastguard Worker        #     upload_results_to_perf_dashboard.GetDashboardUrl(benchmark_name, configuration_name,
695*8975f5c5SAndroid Build Coastguard Worker        #                                                      RESULTS_URL,
696*8975f5c5SAndroid Build Coastguard Worker        #                                                      build_properties['got_revision_cp'],
697*8975f5c5SAndroid Build Coastguard Worker        #                                                      _GetMachineGroup(build_properties)))
698*8975f5c5SAndroid Build Coastguard Worker        if viewer_url:
699*8975f5c5SAndroid Build Coastguard Worker            logdog_dict[base_benchmark_name]['perf_results'] = viewer_url
700*8975f5c5SAndroid Build Coastguard Worker        if upload_failure:
701*8975f5c5SAndroid Build Coastguard Worker            logdog_dict[base_benchmark_name]['upload_failed'] = 'True'
702*8975f5c5SAndroid Build Coastguard Worker
703*8975f5c5SAndroid Build Coastguard Worker
704*8975f5c5SAndroid Build Coastguard Workerdef print_duration(step, start, end):
705*8975f5c5SAndroid Build Coastguard Worker    logging.info('Duration of %s: %d seconds' % (step, end - start))
706*8975f5c5SAndroid Build Coastguard Worker
707*8975f5c5SAndroid Build Coastguard Worker
708*8975f5c5SAndroid Build Coastguard Workerdef main():
709*8975f5c5SAndroid Build Coastguard Worker    """ See collect_task.collect_task for more on the merge script API. """
710*8975f5c5SAndroid Build Coastguard Worker    logging.info(sys.argv)
711*8975f5c5SAndroid Build Coastguard Worker    parser = argparse.ArgumentParser()
712*8975f5c5SAndroid Build Coastguard Worker    # configuration-name (previously perf-id) is the name of bot the tests run on
713*8975f5c5SAndroid Build Coastguard Worker    # For example, buildbot-test is the name of the android-go-perf bot
714*8975f5c5SAndroid Build Coastguard Worker    # configuration-name and results-url are set in the json file which is going
715*8975f5c5SAndroid Build Coastguard Worker    # away tools/perf/core/chromium.perf.fyi.extras.json
716*8975f5c5SAndroid Build Coastguard Worker    parser.add_argument('--configuration-name', help=argparse.SUPPRESS)
717*8975f5c5SAndroid Build Coastguard Worker    parser.add_argument('--build-properties', help=argparse.SUPPRESS)
718*8975f5c5SAndroid Build Coastguard Worker    parser.add_argument('--summary-json', required=True, help=argparse.SUPPRESS)
719*8975f5c5SAndroid Build Coastguard Worker    parser.add_argument('--task-output-dir', required=True, help=argparse.SUPPRESS)
720*8975f5c5SAndroid Build Coastguard Worker    parser.add_argument('-o', '--output-json', required=True, help=argparse.SUPPRESS)
721*8975f5c5SAndroid Build Coastguard Worker    parser.add_argument(
722*8975f5c5SAndroid Build Coastguard Worker        '--skip-perf',
723*8975f5c5SAndroid Build Coastguard Worker        action='store_true',
724*8975f5c5SAndroid Build Coastguard Worker        help='In lightweight mode, using --skip-perf will skip the performance'
725*8975f5c5SAndroid Build Coastguard Worker        ' data handling.')
726*8975f5c5SAndroid Build Coastguard Worker    parser.add_argument(
727*8975f5c5SAndroid Build Coastguard Worker        '--lightweight',
728*8975f5c5SAndroid Build Coastguard Worker        action='store_true',
729*8975f5c5SAndroid Build Coastguard Worker        help='Choose the lightweight mode in which the perf result handling'
730*8975f5c5SAndroid Build Coastguard Worker        ' is performed on a separate VM.')
731*8975f5c5SAndroid Build Coastguard Worker    parser.add_argument('json_files', nargs='*', help=argparse.SUPPRESS)
732*8975f5c5SAndroid Build Coastguard Worker    parser.add_argument(
733*8975f5c5SAndroid Build Coastguard Worker        '--smoke-test-mode',
734*8975f5c5SAndroid Build Coastguard Worker        action='store_true',
735*8975f5c5SAndroid Build Coastguard Worker        help='This test should be run in smoke test mode'
736*8975f5c5SAndroid Build Coastguard Worker        ' meaning it does not upload to the perf dashboard')
737*8975f5c5SAndroid Build Coastguard Worker
738*8975f5c5SAndroid Build Coastguard Worker    args = parser.parse_args()
739*8975f5c5SAndroid Build Coastguard Worker
740*8975f5c5SAndroid Build Coastguard Worker    with open(args.summary_json) as f:
741*8975f5c5SAndroid Build Coastguard Worker        shard_summary = json.load(f)
742*8975f5c5SAndroid Build Coastguard Worker    shard_failed = any(int(shard.get('exit_code', 1)) != 0 for shard in shard_summary['shards'])
743*8975f5c5SAndroid Build Coastguard Worker
744*8975f5c5SAndroid Build Coastguard Worker    output_results_dir = tempfile.mkdtemp('outputresults')
745*8975f5c5SAndroid Build Coastguard Worker    try:
746*8975f5c5SAndroid Build Coastguard Worker        return_code, _ = process_perf_results(args.output_json, args.configuration_name,
747*8975f5c5SAndroid Build Coastguard Worker                                              args.build_properties, args.task_output_dir,
748*8975f5c5SAndroid Build Coastguard Worker                                              args.smoke_test_mode, output_results_dir,
749*8975f5c5SAndroid Build Coastguard Worker                                              args.lightweight, args.skip_perf)
750*8975f5c5SAndroid Build Coastguard Worker    except Exception:
751*8975f5c5SAndroid Build Coastguard Worker        logging.exception('process_perf_results raised an exception')
752*8975f5c5SAndroid Build Coastguard Worker        return_code = 1
753*8975f5c5SAndroid Build Coastguard Worker    finally:
754*8975f5c5SAndroid Build Coastguard Worker        shutil.rmtree(output_results_dir)
755*8975f5c5SAndroid Build Coastguard Worker
756*8975f5c5SAndroid Build Coastguard Worker    if return_code != 0 and shard_failed:
757*8975f5c5SAndroid Build Coastguard Worker        logging.warning('Perf processing failed but one or more shards failed earlier')
758*8975f5c5SAndroid Build Coastguard Worker        return_code = 0  # Enables the failed build info to be rendered normally
759*8975f5c5SAndroid Build Coastguard Worker
760*8975f5c5SAndroid Build Coastguard Worker    return return_code
761*8975f5c5SAndroid Build Coastguard Worker
762*8975f5c5SAndroid Build Coastguard Worker
763*8975f5c5SAndroid Build Coastguard Workerif __name__ == '__main__':
764*8975f5c5SAndroid Build Coastguard Worker    sys.exit(main())
765