| #!/usr/bin/env vpython3 |
| # Copyright 2018 The Chromium Authors |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| from __future__ import print_function |
| |
| from __future__ import absolute_import |
| import argparse |
| import collections |
| import json |
| import logging |
| import multiprocessing |
| import os |
| import shutil |
| import sys |
| import tempfile |
| import time |
| import uuid |
| import six |
| |
| logging.basicConfig( |
| level=logging.INFO, |
| format='(%(levelname)s) %(asctime)s pid=%(process)d' |
| ' %(module)s.%(funcName)s:%(lineno)d %(message)s') |
| |
| import cross_device_test_config |
| |
| from core import path_util |
| path_util.AddTelemetryToPath() |
| |
| from core import upload_results_to_perf_dashboard |
| from core import results_merger |
| from core import bot_platforms |
| |
| path_util.AddAndroidPylibToPath() |
| |
| try: |
| from pylib.utils import logdog_helper |
| except ImportError: |
| pass |
| |
| |
| RESULTS_URL = 'https://chromeperf.appspot.com' |
| |
| # Until we are migrated to LUCI, we will be utilizing a hard |
| # coded master name based on what is passed in in the build properties. |
| # See crbug.com/801289 for more details. |
| MACHINE_GROUP_JSON_FILE = os.path.join( |
| path_util.GetChromiumSrcDir(), 'tools', 'perf', 'core', |
| 'perf_dashboard_machine_group_mapping.json') |
| |
| JSON_CONTENT_TYPE = 'application/json' |
| |
| # Cache of what data format (ChartJSON, Histograms, etc.) each results file is |
| # in so that only one disk read is required when checking the format multiple |
| # times. |
| _data_format_cache = {} |
| DATA_FORMAT_GTEST = 'gtest' |
| DATA_FORMAT_CHARTJSON = 'chartjson' |
| DATA_FORMAT_HISTOGRAMS = 'histograms' |
| DATA_FORMAT_UNKNOWN = 'unknown' |
| |
| def _GetMachineGroup(build_properties): |
| machine_group = None |
| if build_properties.get('perf_dashboard_machine_group', False): |
| # Once luci migration is complete this will exist as a property |
| # in the build properties |
| machine_group = build_properties['perf_dashboard_machine_group'] |
| else: |
| builder_group_mapping = {} |
| with open(MACHINE_GROUP_JSON_FILE) as fp: |
| builder_group_mapping = json.load(fp) |
| if build_properties.get('builder_group', False): |
| legacy_builder_group = build_properties['builder_group'] |
| else: |
| # TODO(crbug.com/1153958): remove reference to mastername. |
| legacy_builder_group = build_properties['mastername'] |
| if builder_group_mapping.get(legacy_builder_group): |
| machine_group = builder_group_mapping[legacy_builder_group] |
| if not machine_group: |
| raise ValueError( |
| 'Must set perf_dashboard_machine_group or have a valid ' |
| 'mapping in ' |
| 'src/tools/perf/core/perf_dashboard_machine_group_mapping.json' |
| 'See bit.ly/perf-dashboard-machine-group for more details') |
| return machine_group |
| |
| |
| def _upload_perf_results(json_to_upload, name, configuration_name, |
| build_properties, output_json_file): |
| """Upload the contents of result JSON(s) to the perf dashboard.""" |
| args= [ |
| '--buildername', build_properties['buildername'], |
| '--buildnumber', build_properties['buildnumber'], |
| '--name', name, |
| '--configuration-name', configuration_name, |
| '--results-file', json_to_upload, |
| '--results-url', RESULTS_URL, |
| '--got-revision-cp', build_properties['got_revision_cp'], |
| '--got-v8-revision', build_properties['got_v8_revision'], |
| '--got-webrtc-revision', build_properties['got_webrtc_revision'], |
| '--output-json-file', output_json_file, |
| '--perf-dashboard-machine-group', _GetMachineGroup(build_properties) |
| ] |
| buildbucket = build_properties.get('buildbucket', {}) |
| if isinstance(buildbucket, six.string_types): |
| buildbucket = json.loads(buildbucket) |
| |
| if 'build' in buildbucket: |
| args += [ |
| '--project', buildbucket['build'].get('project'), |
| '--buildbucket', buildbucket['build'].get('bucket'), |
| ] |
| |
| if build_properties.get('got_revision'): |
| args.append('--git-revision') |
| args.append(build_properties['got_revision']) |
| if _is_histogram(json_to_upload): |
| args.append('--send-as-histograms') |
| |
| #TODO(crbug.com/1072729): log this in top level |
| logging.info('upload_results_to_perf_dashboard: %s.' % args) |
| |
| # Duplicate part of the results upload to staging. |
| if configuration_name == 'linux-perf-fyi' and name == 'system_health.common_desktop': |
| try: |
| RESULTS_URL_STAGE = 'https://chromeperf-stage.uc.r.appspot.com' |
| staging_args = [(s if s != RESULTS_URL else RESULTS_URL_STAGE) |
| for s in args] |
| result = upload_results_to_perf_dashboard.main(staging_args) |
| logging.info('Uploaded results to staging. Return value: %d', result) |
| except Exception as e: |
| logging.info('Failed to upload results to staging: %s', str(e)) |
| |
| return upload_results_to_perf_dashboard.main(args) |
| |
| def _is_histogram(json_file): |
| return _determine_data_format(json_file) == DATA_FORMAT_HISTOGRAMS |
| |
| |
| def _is_gtest(json_file): |
| return _determine_data_format(json_file) == DATA_FORMAT_GTEST |
| |
| |
| def _determine_data_format(json_file): |
| if json_file not in _data_format_cache: |
| with open(json_file, 'rb') as f: |
| data = json.load(f) |
| if isinstance(data, list): |
| _data_format_cache[json_file] = DATA_FORMAT_HISTOGRAMS |
| elif isinstance(data, dict): |
| if 'charts' in data: |
| _data_format_cache[json_file] = DATA_FORMAT_CHARTJSON |
| else: |
| _data_format_cache[json_file] = DATA_FORMAT_GTEST |
| else: |
| _data_format_cache[json_file] = DATA_FORMAT_UNKNOWN |
| return _data_format_cache[json_file] |
| _data_format_cache[json_file] = DATA_FORMAT_UNKNOWN |
| return _data_format_cache[json_file] |
| |
| |
| def _merge_json_output(output_json, |
| jsons_to_merge, |
| extra_links, |
| test_cross_device=False): |
| """Merges the contents of one or more results JSONs. |
| |
| Args: |
| output_json: A path to a JSON file to which the merged results should be |
| written. |
| jsons_to_merge: A list of JSON files that should be merged. |
| extra_links: a (key, value) map in which keys are the human-readable strings |
| which describe the data, and value is logdog url that contain the data. |
| """ |
| begin_time = time.time() |
| merged_results = results_merger.merge_test_results(jsons_to_merge, |
| test_cross_device) |
| |
| # Only append the perf results links if present |
| if extra_links: |
| merged_results['links'] = extra_links |
| |
| with open(output_json, 'w') as f: |
| json.dump(merged_results, f) |
| |
| end_time = time.time() |
| print_duration('Merging json test results', begin_time, end_time) |
| return 0 |
| |
| |
| def _handle_perf_json_test_results( |
| benchmark_directory_map, test_results_list): |
| """Checks the test_results.json under each folder: |
| |
| 1. mark the benchmark 'enabled' if tests results are found |
| 2. add the json content to a list for non-ref. |
| """ |
| begin_time = time.time() |
| benchmark_enabled_map = {} |
| for benchmark_name, directories in benchmark_directory_map.items(): |
| for directory in directories: |
| # Obtain the test name we are running |
| is_ref = '.reference' in benchmark_name |
| enabled = True |
| try: |
| with open(os.path.join(directory, 'test_results.json')) as json_data: |
| json_results = json.load(json_data) |
| if not json_results: |
| # Output is null meaning the test didn't produce any results. |
| # Want to output an error and continue loading the rest of the |
| # test results. |
| logging.warning( |
| 'No results produced for %s, skipping upload' % directory) |
| continue |
| if json_results.get('version') == 3: |
| # Non-telemetry tests don't have written json results but |
| # if they are executing then they are enabled and will generate |
| # chartjson results. |
| if not bool(json_results.get('tests')): |
| enabled = False |
| if not is_ref: |
| # We don't need to upload reference build data to the |
| # flakiness dashboard since we don't monitor the ref build |
| test_results_list.append(json_results) |
| except IOError as e: |
| # TODO(crbug.com/936602): Figure out how to surface these errors. Should |
| # we have a non-zero exit code if we error out? |
| logging.error('Failed to obtain test results for %s: %s', |
| benchmark_name, e) |
| continue |
| if not enabled: |
| # We don't upload disabled benchmarks or tests that are run |
| # as a smoke test |
| logging.info( |
| 'Benchmark %s ran no tests on at least one shard' % benchmark_name) |
| continue |
| benchmark_enabled_map[benchmark_name] = True |
| |
| end_time = time.time() |
| print_duration('Analyzing perf json test results', begin_time, end_time) |
| return benchmark_enabled_map |
| |
| |
| def _generate_unique_logdog_filename(name_prefix): |
| return name_prefix + '_' + str(uuid.uuid4()) |
| |
| |
| def _handle_perf_logs(benchmark_directory_map, extra_links): |
| """ Upload benchmark logs to logdog and add a page entry for them. """ |
| begin_time = time.time() |
| benchmark_logs_links = collections.defaultdict(list) |
| |
| for benchmark_name, directories in benchmark_directory_map.items(): |
| for directory in directories: |
| benchmark_log_file = os.path.join(directory, 'benchmark_log.txt') |
| if os.path.exists(benchmark_log_file): |
| with open(benchmark_log_file) as f: |
| uploaded_link = logdog_helper.text( |
| name=_generate_unique_logdog_filename(benchmark_name), |
| data=f.read()) |
| benchmark_logs_links[benchmark_name].append(uploaded_link) |
| |
| logdog_file_name = _generate_unique_logdog_filename('Benchmarks_Logs') |
| logdog_stream = logdog_helper.text( |
| logdog_file_name, json.dumps(benchmark_logs_links, sort_keys=True, |
| indent=4, separators=(',', ': ')), |
| content_type=JSON_CONTENT_TYPE) |
| extra_links['Benchmarks logs'] = logdog_stream |
| end_time = time.time() |
| print_duration('Generating perf log streams', begin_time, end_time) |
| |
| |
| def _handle_benchmarks_shard_map(benchmarks_shard_map_file, extra_links): |
| begin_time = time.time() |
| with open(benchmarks_shard_map_file) as f: |
| benchmarks_shard_data = f.read() |
| logdog_file_name = _generate_unique_logdog_filename('Benchmarks_Shard_Map') |
| logdog_stream = logdog_helper.text(logdog_file_name, |
| benchmarks_shard_data, |
| content_type=JSON_CONTENT_TYPE) |
| extra_links['Benchmarks shard map'] = logdog_stream |
| end_time = time.time() |
| print_duration('Generating benchmark shard map stream', begin_time, end_time) |
| |
| |
| def _get_benchmark_name(directory): |
| return os.path.basename(directory).replace(" benchmark", "") |
| |
| |
| def _scan_output_dir(task_output_dir): |
| benchmark_directory_map = {} |
| benchmarks_shard_map_file = None |
| |
| directory_list = [ |
| f for f in os.listdir(task_output_dir) |
| if not os.path.isfile(os.path.join(task_output_dir, f)) |
| ] |
| benchmark_directory_list = [] |
| for directory in directory_list: |
| for f in os.listdir(os.path.join(task_output_dir, directory)): |
| path = os.path.join(task_output_dir, directory, f) |
| if os.path.isdir(path): |
| benchmark_directory_list.append(path) |
| elif path.endswith('benchmarks_shard_map.json'): |
| benchmarks_shard_map_file = path |
| # Now create a map of benchmark name to the list of directories |
| # the lists were written to. |
| for directory in benchmark_directory_list: |
| benchmark_name = _get_benchmark_name(directory) |
| if benchmark_name in benchmark_directory_map: |
| benchmark_directory_map[benchmark_name].append(directory) |
| else: |
| benchmark_directory_map[benchmark_name] = [directory] |
| |
| return benchmark_directory_map, benchmarks_shard_map_file |
| |
| |
| def process_perf_results(output_json, |
| configuration_name, |
| build_properties, |
| task_output_dir, |
| smoke_test_mode, |
| output_results_dir, |
| lightweight=False, |
| skip_perf=False): |
| """Process perf results. |
| |
| Consists of merging the json-test-format output, uploading the perf test |
| output (chartjson and histogram), and store the benchmark logs in logdog. |
| |
| Each directory in the task_output_dir represents one benchmark |
| that was run. Within this directory, there is a subdirectory with the name |
| of the benchmark that was run. In that subdirectory, there is a |
| perftest-output.json file containing the performance results in histogram |
| or dashboard json format and an output.json file containing the json test |
| results for the benchmark. |
| |
| Returns: |
| (return_code, upload_results_map): |
| return_code is 0 if the whole operation is successful, non zero otherwise. |
| benchmark_upload_result_map: the dictionary that describe which benchmarks |
| were successfully uploaded. |
| """ |
| handle_perf = not lightweight or not skip_perf |
| handle_non_perf = not lightweight or skip_perf |
| logging.info('lightweight mode: %r; handle_perf: %r; handle_non_perf: %r' % |
| (lightweight, handle_perf, handle_non_perf)) |
| |
| begin_time = time.time() |
| return_code = 0 |
| benchmark_upload_result_map = {} |
| |
| benchmark_directory_map, benchmarks_shard_map_file = _scan_output_dir( |
| task_output_dir) |
| |
| test_results_list = [] |
| extra_links = {} |
| |
| if handle_non_perf: |
| # First, upload benchmarks shard map to logdog and add a page |
| # entry for it in extra_links. |
| if benchmarks_shard_map_file: |
| _handle_benchmarks_shard_map(benchmarks_shard_map_file, extra_links) |
| |
| # Second, upload all the benchmark logs to logdog and add a page entry for |
| # those links in extra_links. |
| _handle_perf_logs(benchmark_directory_map, extra_links) |
| |
| # Then try to obtain the list of json test results to merge |
| # and determine the status of each benchmark. |
| benchmark_enabled_map = _handle_perf_json_test_results( |
| benchmark_directory_map, test_results_list) |
| |
| build_properties_map = json.loads(build_properties) |
| if not configuration_name: |
| # we are deprecating perf-id crbug.com/817823 |
| configuration_name = build_properties_map['buildername'] |
| |
| # The calibration project is paused and the experiments of adding device id, |
| # which currently broken, is removed for now. |
| # _update_perf_results_for_calibration(benchmarks_shard_map_file, |
| # benchmark_enabled_map, |
| # benchmark_directory_map, |
| # configuration_name) |
| if not smoke_test_mode and handle_perf: |
| try: |
| return_code, benchmark_upload_result_map = _handle_perf_results( |
| benchmark_enabled_map, benchmark_directory_map, configuration_name, |
| build_properties_map, extra_links, output_results_dir) |
| except Exception: |
| logging.exception('Error handling perf results jsons') |
| return_code = 1 |
| |
| if handle_non_perf: |
| # Finally, merge all test results json, add the extra links and write out to |
| # output location |
| try: |
| _merge_json_output( |
| output_json, test_results_list, extra_links, |
| configuration_name in cross_device_test_config.TARGET_DEVICES) |
| except Exception: |
| logging.exception('Error handling test results jsons.') |
| |
| end_time = time.time() |
| print_duration('Total process_perf_results', begin_time, end_time) |
| return return_code, benchmark_upload_result_map |
| |
| def _merge_chartjson_results(chartjson_dicts): |
| merged_results = chartjson_dicts[0] |
| for chartjson_dict in chartjson_dicts[1:]: |
| for key in chartjson_dict: |
| if key == 'charts': |
| for add_key in chartjson_dict[key]: |
| merged_results[key][add_key] = chartjson_dict[key][add_key] |
| return merged_results |
| |
| def _merge_histogram_results(histogram_lists): |
| merged_results = [] |
| for histogram_list in histogram_lists: |
| merged_results += histogram_list |
| |
| return merged_results |
| |
| def _merge_perf_results(benchmark_name, results_filename, directories): |
| begin_time = time.time() |
| collected_results = [] |
| for directory in directories: |
| filename = os.path.join(directory, 'perf_results.json') |
| try: |
| with open(filename) as pf: |
| collected_results.append(json.load(pf)) |
| except IOError as e: |
| # TODO(crbug.com/936602): Figure out how to surface these errors. Should |
| # we have a non-zero exit code if we error out? |
| logging.error('Failed to obtain perf results from %s: %s', |
| directory, e) |
| if not collected_results: |
| logging.error('Failed to obtain any perf results from %s.', |
| benchmark_name) |
| return |
| |
| # Assuming that multiple shards will only be chartjson or histogram set |
| # Non-telemetry benchmarks only ever run on one shard |
| merged_results = [] |
| if isinstance(collected_results[0], dict): |
| merged_results = _merge_chartjson_results(collected_results) |
| elif isinstance(collected_results[0], list): |
| merged_results =_merge_histogram_results(collected_results) |
| |
| with open(results_filename, 'w') as rf: |
| json.dump(merged_results, rf) |
| |
| end_time = time.time() |
| print_duration(('%s results merging' % (benchmark_name)), |
| begin_time, end_time) |
| |
| |
| def _upload_individual( |
| benchmark_name, directories, configuration_name, build_properties, |
| output_json_file): |
| tmpfile_dir = tempfile.mkdtemp() |
| try: |
| upload_begin_time = time.time() |
| # There are potentially multiple directores with results, re-write and |
| # merge them if necessary |
| results_filename = None |
| if len(directories) > 1: |
| merge_perf_dir = os.path.join( |
| os.path.abspath(tmpfile_dir), benchmark_name) |
| if not os.path.exists(merge_perf_dir): |
| os.makedirs(merge_perf_dir) |
| results_filename = os.path.join( |
| merge_perf_dir, 'merged_perf_results.json') |
| _merge_perf_results(benchmark_name, results_filename, directories) |
| else: |
| # It was only written to one shard, use that shards data |
| results_filename = os.path.join(directories[0], 'perf_results.json') |
| |
| results_size_in_mib = os.path.getsize(results_filename) / (2 ** 20) |
| logging.info('Uploading perf results from %s benchmark (size %s Mib)' % |
| (benchmark_name, results_size_in_mib)) |
| with open(output_json_file, 'w') as oj: |
| upload_return_code = _upload_perf_results( |
| results_filename, |
| benchmark_name, configuration_name, build_properties, oj) |
| upload_end_time = time.time() |
| print_duration(('%s upload time' % (benchmark_name)), |
| upload_begin_time, upload_end_time) |
| return (benchmark_name, upload_return_code == 0) |
| finally: |
| shutil.rmtree(tmpfile_dir) |
| |
| |
| def _upload_individual_benchmark(params): |
| try: |
| return _upload_individual(*params) |
| except Exception: |
| benchmark_name = params[0] |
| upload_succeed = False |
| logging.exception('Error uploading perf result of %s' % benchmark_name) |
| return benchmark_name, upload_succeed |
| |
| |
| def _GetCpuCount(log=True): |
| try: |
| cpu_count = multiprocessing.cpu_count() |
| if sys.platform == 'win32': |
| # TODO(crbug.com/1190269) - we can't use more than 56 |
| # cores on Windows or Python3 may hang. |
| cpu_count = min(cpu_count, 56) |
| return cpu_count |
| except NotImplementedError: |
| if log: |
| logging.warning( |
| 'Failed to get a CPU count for this bot. See crbug.com/947035.') |
| # TODO(crbug.com/948281): This is currently set to 4 since the mac masters |
| # only have 4 cores. Once we move to all-linux, this can be increased or |
| # we can even delete this whole function and use multiprocessing.cpu_count() |
| # directly. |
| return 4 |
| |
| |
| def _load_shard_id_from_test_results(directory): |
| shard_id = None |
| test_json_path = os.path.join(directory, 'test_results.json') |
| try: |
| with open(test_json_path) as f: |
| test_json = json.load(f) |
| all_results = test_json['tests'] |
| for _, benchmark_results in all_results.items(): |
| for _, measurement_result in benchmark_results.items(): |
| shard_id = measurement_result['shard'] |
| break |
| except IOError as e: |
| logging.error('Failed to open test_results.json from %s: %s', |
| test_json_path, e) |
| except KeyError as e: |
| logging.error('Failed to locate results in test_results.json: %s', e) |
| return shard_id |
| |
| |
| def _find_device_id_by_shard_id(benchmarks_shard_map_file, shard_id): |
| try: |
| with open(benchmarks_shard_map_file) as f: |
| shard_map_json = json.load(f) |
| device_id = shard_map_json['extra_infos']['bot #%s' % shard_id] |
| except KeyError as e: |
| logging.error('Failed to locate device name in shard map: %s', e) |
| return device_id |
| |
| |
| def _update_perf_json_with_summary_on_device_id(directory, device_id): |
| perf_json_path = os.path.join(directory, 'perf_results.json') |
| try: |
| with open(perf_json_path, 'r') as f: |
| perf_json = json.load(f) |
| except IOError as e: |
| logging.error('Failed to open perf_results.json from %s: %s', |
| perf_json_path, e) |
| summary_key_guid = str(uuid.uuid4()) |
| summary_key_generic_set = { |
| 'values': ['device_id'], |
| 'guid': summary_key_guid, |
| 'type': 'GenericSet' |
| } |
| perf_json.insert(0, summary_key_generic_set) |
| logging.info('Inserted summary key generic set for perf result in %s: %s', |
| directory, summary_key_generic_set) |
| stories_guids = set() |
| for entry in perf_json: |
| if 'diagnostics' in entry: |
| entry['diagnostics']['summaryKeys'] = summary_key_guid |
| stories_guids.add(entry['diagnostics']['stories']) |
| for entry in perf_json: |
| if 'guid' in entry and entry['guid'] in stories_guids: |
| entry['values'].append(device_id) |
| try: |
| with open(perf_json_path, 'w') as f: |
| json.dump(perf_json, f) |
| except IOError as e: |
| logging.error('Failed to writing perf_results.json to %s: %s', |
| perf_json_path, e) |
| logging.info('Finished adding device id %s in perf result.', device_id) |
| |
| |
| def _should_add_device_id_in_perf_result(builder_name): |
| # We should always add device id in calibration builders. |
| # For testing purpose, adding fyi as well for faster turnaround, because |
| # calibration builders run every 24 hours. |
| return any(builder_name == p.name |
| for p in bot_platforms.CALIBRATION_PLATFORMS) or ( |
| builder_name == 'android-pixel2-perf-fyi') |
| |
| |
| def _update_perf_results_for_calibration(benchmarks_shard_map_file, |
| benchmark_enabled_map, |
| benchmark_directory_map, |
| configuration_name): |
| if not _should_add_device_id_in_perf_result(configuration_name): |
| return |
| logging.info('Updating perf results for %s.', configuration_name) |
| for benchmark_name, directories in benchmark_directory_map.items(): |
| if not benchmark_enabled_map.get(benchmark_name, False): |
| continue |
| for directory in directories: |
| shard_id = _load_shard_id_from_test_results(directory) |
| device_id = _find_device_id_by_shard_id(benchmarks_shard_map_file, |
| shard_id) |
| _update_perf_json_with_summary_on_device_id(directory, device_id) |
| |
| |
| def _handle_perf_results( |
| benchmark_enabled_map, benchmark_directory_map, configuration_name, |
| build_properties, extra_links, output_results_dir): |
| """ |
| Upload perf results to the perf dashboard. |
| |
| This method also upload the perf results to logdog and augment it to |
| |extra_links|. |
| |
| Returns: |
| (return_code, benchmark_upload_result_map) |
| return_code is 0 if this upload to perf dashboard successfully, 1 |
| otherwise. |
| benchmark_upload_result_map is a dictionary describes which benchmark |
| was successfully uploaded. |
| """ |
| begin_time = time.time() |
| # Upload all eligible benchmarks to the perf dashboard |
| results_dict = {} |
| |
| invocations = [] |
| for benchmark_name, directories in benchmark_directory_map.items(): |
| if not benchmark_enabled_map.get(benchmark_name, False): |
| continue |
| # Create a place to write the perf results that you will write out to |
| # logdog. |
| output_json_file = os.path.join( |
| output_results_dir, (str(uuid.uuid4()) + benchmark_name)) |
| results_dict[benchmark_name] = output_json_file |
| #TODO(crbug.com/1072729): pass final arguments instead of build properties |
| # and configuration_name |
| invocations.append(( |
| benchmark_name, directories, configuration_name, |
| build_properties, output_json_file)) |
| |
| # Kick off the uploads in multiple processes |
| # crbug.com/1035930: We are hitting HTTP Response 429. Limit ourselves |
| # to 2 processes to avoid this error. Uncomment the following code once |
| # the problem is fixed on the dashboard side. |
| # pool = multiprocessing.Pool(_GetCpuCount()) |
| pool = multiprocessing.Pool(2) |
| upload_result_timeout = False |
| try: |
| async_result = pool.map_async( |
| _upload_individual_benchmark, invocations) |
| # TODO(crbug.com/947035): What timeout is reasonable? |
| results = async_result.get(timeout=4000) |
| except multiprocessing.TimeoutError: |
| upload_result_timeout = True |
| logging.error('Timeout uploading benchmarks to perf dashboard in parallel') |
| results = [] |
| for benchmark_name in benchmark_directory_map: |
| results.append((benchmark_name, False)) |
| finally: |
| pool.terminate() |
| |
| # Keep a mapping of benchmarks to their upload results |
| benchmark_upload_result_map = {} |
| for r in results: |
| benchmark_upload_result_map[r[0]] = r[1] |
| |
| logdog_dict = {} |
| upload_failures_counter = 0 |
| logdog_stream = None |
| logdog_label = 'Results Dashboard' |
| for benchmark_name, output_file in results_dict.items(): |
| upload_succeed = benchmark_upload_result_map[benchmark_name] |
| if not upload_succeed: |
| upload_failures_counter += 1 |
| is_reference = '.reference' in benchmark_name |
| _write_perf_data_to_logfile( |
| benchmark_name, output_file, |
| configuration_name, build_properties, logdog_dict, |
| is_reference, upload_failure=not upload_succeed) |
| |
| logdog_file_name = _generate_unique_logdog_filename('Results_Dashboard_') |
| logdog_stream = logdog_helper.text(logdog_file_name, |
| json.dumps(dict(logdog_dict), sort_keys=True, |
| indent=4, separators=(',', ': ')), |
| content_type=JSON_CONTENT_TYPE) |
| if upload_failures_counter > 0: |
| logdog_label += (' %s merge script perf data upload failures' % |
| upload_failures_counter) |
| extra_links[logdog_label] = logdog_stream |
| end_time = time.time() |
| print_duration('Uploading results to perf dashboard', begin_time, end_time) |
| if upload_result_timeout or upload_failures_counter > 0: |
| return 1, benchmark_upload_result_map |
| return 0, benchmark_upload_result_map |
| |
| |
| def _write_perf_data_to_logfile(benchmark_name, output_file, |
| configuration_name, build_properties, |
| logdog_dict, is_ref, upload_failure): |
| viewer_url = None |
| # logdog file to write perf results to |
| if os.path.exists(output_file): |
| results = None |
| with open(output_file) as f: |
| try: |
| results = json.load(f) |
| except ValueError: |
| logging.error('Error parsing perf results JSON for benchmark %s' % |
| benchmark_name) |
| if results: |
| output_json_file = logdog_helper.open_text(benchmark_name) |
| if output_json_file: |
| viewer_url = output_json_file.get_viewer_url() |
| try: |
| json.dump(results, output_json_file, indent=4, separators=(',', ': ')) |
| except ValueError as e: |
| logging.error('ValueError: "%s" while dumping output to logdog' % e) |
| finally: |
| output_json_file.close() |
| else: |
| logging.warning('Could not open output JSON file for benchmark %s' % |
| benchmark_name) |
| else: |
| logging.warning("Perf results JSON file doesn't exist for benchmark %s" % |
| benchmark_name) |
| |
| base_benchmark_name = benchmark_name.replace('.reference', '') |
| |
| if base_benchmark_name not in logdog_dict: |
| logdog_dict[base_benchmark_name] = {} |
| |
| # add links for the perf results and the dashboard url to |
| # the logs section of buildbot |
| if is_ref: |
| if viewer_url: |
| logdog_dict[base_benchmark_name]['perf_results_ref'] = viewer_url |
| if upload_failure: |
| logdog_dict[base_benchmark_name]['ref_upload_failed'] = 'True' |
| else: |
| logdog_dict[base_benchmark_name]['dashboard_url'] = ( |
| upload_results_to_perf_dashboard.GetDashboardUrl( |
| benchmark_name, |
| configuration_name, RESULTS_URL, |
| build_properties['got_revision_cp'], |
| _GetMachineGroup(build_properties))) |
| if viewer_url: |
| logdog_dict[base_benchmark_name]['perf_results'] = viewer_url |
| if upload_failure: |
| logdog_dict[base_benchmark_name]['upload_failed'] = 'True' |
| |
| |
| def print_duration(step, start, end): |
| logging.info('Duration of %s: %d seconds' % (step, end - start)) |
| |
| |
| def main(): |
| """ See collect_task.collect_task for more on the merge script API. """ |
| logging.info(sys.argv) |
| parser = argparse.ArgumentParser() |
| # configuration-name (previously perf-id) is the name of bot the tests run on |
| # For example, buildbot-test is the name of the android-go-perf bot |
| # configuration-name and results-url are set in the json file which is going |
| # away tools/perf/core/chromium.perf.fyi.extras.json |
| parser.add_argument('--configuration-name', help=argparse.SUPPRESS) |
| |
| parser.add_argument('--build-properties', help=argparse.SUPPRESS) |
| parser.add_argument('--summary-json', help=argparse.SUPPRESS) |
| parser.add_argument('--task-output-dir', help=argparse.SUPPRESS) |
| parser.add_argument('-o', '--output-json', required=True, |
| help=argparse.SUPPRESS) |
| parser.add_argument( |
| '--skip-perf', |
| action='store_true', |
| help='In lightweight mode, using --skip-perf will skip the performance' |
| ' data handling.') |
| parser.add_argument( |
| '--lightweight', |
| action='store_true', |
| help='Choose the lightweight mode in which the perf result handling' |
| ' is performed on a separate VM.') |
| parser.add_argument('json_files', nargs='*', help=argparse.SUPPRESS) |
| parser.add_argument('--smoke-test-mode', action='store_true', |
| help='This test should be run in smoke test mode' |
| ' meaning it does not upload to the perf dashboard') |
| |
| args = parser.parse_args() |
| |
| output_results_dir = tempfile.mkdtemp('outputresults') |
| try: |
| return_code, _ = process_perf_results( |
| args.output_json, args.configuration_name, args.build_properties, |
| args.task_output_dir, args.smoke_test_mode, output_results_dir, |
| args.lightweight, args.skip_perf) |
| return return_code |
| finally: |
| # crbug/1378275. In some cases, the temp dir could be deleted. Add a |
| # check to avoid FileNotFoundError. |
| if os.path.exists(output_results_dir): |
| shutil.rmtree(output_results_dir) |
| |
| |
| if __name__ == '__main__': |
| sys.exit(main()) |