blob: 49d75e48e32573947d5b4b61820656f1193e5e02 [file] [log] [blame]
# 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