| # Copyright 2016 The Chromium Authors. All rights reserved. |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| import json |
| import logging |
| import os |
| import pickle |
| import re |
| |
| from devil import base_error |
| from devil.utils import cmd_helper |
| from pylib import constants |
| from pylib.base import base_test_result |
| from pylib.base import test_instance |
| from pylib.constants import host_paths |
| from pylib.utils import test_filter |
| |
| |
| _GIT_CR_POS_RE = re.compile(r'^Cr-Commit-Position: refs/heads/master@{#(\d+)}$') |
| |
| |
| def _GetPersistedResult(test_name): |
| file_name = os.path.join(constants.PERF_OUTPUT_DIR, test_name) |
| if not os.path.exists(file_name): |
| logging.error('File not found %s', file_name) |
| return None |
| |
| with file(file_name, 'r') as f: |
| return pickle.load(f) |
| |
| |
| def _GetChromiumRevision(): |
| # pylint: disable=line-too-long |
| """Get the git hash and commit position of the chromium master branch. |
| |
| See: |
| https://chromium.googlesource.com/chromium/tools/build/+/387e3cf3/scripts/slave/runtest.py#211 |
| |
| Returns: |
| A dictionary with 'revision' and 'commit_pos' keys. |
| """ |
| # pylint: enable=line-too-long |
| status, output = cmd_helper.GetCmdStatusAndOutput( |
| ['git', 'log', '-n', '1', '--pretty=format:%H%n%B', 'HEAD'], |
| cwd=host_paths.DIR_SOURCE_ROOT) |
| revision = None |
| commit_pos = None |
| if not status: |
| lines = output.splitlines() |
| revision = lines[0] |
| for line in reversed(lines): |
| m = _GIT_CR_POS_RE.match(line.strip()) |
| if m: |
| commit_pos = int(m.group(1)) |
| break |
| return {'revision': revision, 'commit_pos': commit_pos} |
| |
| |
| class PerfTestInstance(test_instance.TestInstance): |
| def __init__(self, args, _): |
| super(PerfTestInstance, self).__init__() |
| |
| self._collect_chartjson_data = args.collect_chartjson_data |
| self._dry_run = args.dry_run |
| self._output_dir_archive_path = args.output_dir_archive_path |
| # TODO(rnephew): Get rid of this when everything uses |
| # --output-dir-archive-path |
| if self._output_dir_archive_path is None and args.get_output_dir_archive: |
| self._output_dir_archive_path = args.get_output_dir_archive |
| self._known_devices_file = args.known_devices_file |
| self._max_battery_temp = args.max_battery_temp |
| self._min_battery_level = args.min_battery_level |
| self._no_timeout = args.no_timeout |
| self._output_chartjson_data = args.output_chartjson_data |
| self._output_json_list = args.output_json_list |
| self._print_step = args.print_step |
| self._single_step = ( |
| ' '.join(args.single_step_command) if args.single_step else None) |
| self._steps = args.steps |
| self._test_filter = test_filter.InitializeFilterFromArgs(args) |
| self._write_buildbot_json = args.write_buildbot_json |
| |
| #override |
| def SetUp(self): |
| pass |
| |
| #override |
| def TearDown(self): |
| pass |
| |
| def OutputJsonList(self): |
| try: |
| with file(self._steps, 'r') as i: |
| all_steps = json.load(i) |
| |
| step_values = [] |
| for k, v in all_steps['steps'].iteritems(): |
| data = {'test': k, 'device_affinity': v['device_affinity']} |
| |
| persisted_result = _GetPersistedResult(k) |
| if persisted_result: |
| data['start_time'] = persisted_result['start_time'] |
| data['end_time'] = persisted_result['end_time'] |
| data['total_time'] = persisted_result['total_time'] |
| data['has_archive'] = persisted_result['archive_bytes'] is not None |
| step_values.append(data) |
| |
| with file(self.output_json_list, 'w') as o: |
| o.write(json.dumps(step_values)) |
| return base_test_result.ResultType.PASS |
| except KeyError: |
| logging.exception('Persistent results file missing key.') |
| return base_test_result.ResultType.FAIL |
| |
| def PrintTestOutput(self): |
| """Helper method to print the output of previously executed test_name. |
| |
| Test_name is passed from the command line as print_step |
| |
| Returns: |
| exit code generated by the test step. |
| """ |
| persisted_result = _GetPersistedResult(self._print_step) |
| if not persisted_result: |
| raise PersistentDataError('No data for test %s found.' % self._print_step) |
| logging.info('*' * 80) |
| logging.info('Output from:') |
| logging.info(persisted_result['cmd']) |
| logging.info('*' * 80) |
| |
| output_formatted = '' |
| persisted_outputs = persisted_result['output'] |
| for i in xrange(len(persisted_outputs)): |
| output_formatted += '\n\nOutput from run #%d:\n\n%s' % ( |
| i, persisted_outputs[i]) |
| print output_formatted |
| |
| if self.output_chartjson_data: |
| with file(self.output_chartjson_data, 'w') as f: |
| f.write(persisted_result['chartjson']) |
| |
| if self.output_dir_archive_path: |
| if persisted_result['archive_bytes'] is not None: |
| with file(self.output_dir_archive_path, 'wb') as f: |
| f.write(persisted_result['archive_bytes']) |
| else: |
| logging.error('The output dir was not archived.') |
| if persisted_result['exit_code'] == 0: |
| return base_test_result.ResultType.PASS |
| return base_test_result.ResultType.FAIL |
| |
| #override |
| def TestType(self): |
| return 'perf' |
| |
| @staticmethod |
| def ReadChartjsonOutput(output_dir): |
| if not output_dir: |
| return '' |
| json_output_path = os.path.join(output_dir, 'results-chart.json') |
| try: |
| with open(json_output_path) as f: |
| return f.read() |
| except IOError: |
| logging.exception('Exception when reading chartjson.') |
| logging.error('This usually means that telemetry did not run, so it could' |
| ' not generate the file. Please check the device running' |
| ' the test.') |
| return '' |
| |
| def WriteBuildBotJson(self, output_dir): |
| """Write metadata about the buildbot environment to the output dir.""" |
| if not output_dir or not self._write_buildbot_json: |
| return |
| data = { |
| 'chromium': _GetChromiumRevision(), |
| 'environment': dict(os.environ) |
| } |
| with open(os.path.join(output_dir, 'buildbot.json'), 'w') as f: |
| json.dump(data, f, sort_keys=True, separators=(',', ': ')) |
| |
| @property |
| def collect_chartjson_data(self): |
| return self._collect_chartjson_data |
| |
| @property |
| def dry_run(self): |
| return self._dry_run |
| |
| @property |
| def known_devices_file(self): |
| return self._known_devices_file |
| |
| @property |
| def max_battery_temp(self): |
| return self._max_battery_temp |
| |
| @property |
| def min_battery_level(self): |
| return self._min_battery_level |
| |
| @property |
| def no_timeout(self): |
| return self._no_timeout |
| |
| @property |
| def output_chartjson_data(self): |
| return self._output_chartjson_data |
| |
| @property |
| def output_dir_archive_path(self): |
| return self._output_dir_archive_path |
| |
| @property |
| def output_json_list(self): |
| return self._output_json_list |
| |
| @property |
| def print_step(self): |
| return self._print_step |
| |
| @property |
| def single_step(self): |
| return self._single_step |
| |
| @property |
| def steps(self): |
| return self._steps |
| |
| @property |
| def test_filter(self): |
| return self._test_filter |
| |
| |
| class PersistentDataError(base_error.BaseError): |
| def __init__(self, message): |
| super(PersistentDataError, self).__init__(message) |
| self._is_infra_error = True |