blob: 4c0f5b5ddd7b673862a6f68b8ec4d33947e82e02 [file] [log] [blame]
# Copyright 2014 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 contextlib, hashlib, logging, os, pipes, re, sys, time, tempfile, urllib2
from autotest_lib.client.bin import utils
from autotest_lib.client.common_lib import error
from autotest_lib.client.cros import chrome_binary_test
from autotest_lib.client.cros import power_status, power_utils
from autotest_lib.client.cros import service_stopper
from autotest_lib.client.cros.audio import cmd_utils
# The download base for test assets.
DOWNLOAD_BASE = ('http://commondatastorage.googleapis.com'
'/chromiumos-test-assets-public/')
# The executable name of the vda unittest
VDA_BINARY = 'video_decode_accelerator_unittest'
# The executable name of the vea unittest
VEA_BINARY = 'video_encode_accelerator_unittest'
# The input frame rate for the vea_unittest.
INPUT_FPS = 30
# The rendering fps in the vda_unittest.
RENDERING_FPS = 30
# The unit(s) should match chromium/src/tools/perf/unit-info.json.
UNIT_PERCENT = '%'
UNIT_WATT = 'W'
# The regex of the versioning file.
# e.g., crowd720-3cfe7b096f765742b4aa79e55fe7c994.yuv
RE_VERSIONING_FILE = re.compile(r'(.+)-([0-9a-fA-F]{32})(\..+)?')
# Time in seconds to wait for cpu idle until giveup.
WAIT_FOR_IDLE_CPU_TIMEOUT = 60
# Maximum percent of cpu usage considered as idle.
CPU_IDLE_USAGE = 0.1
# List of thermal throttling services that should be disabled.
# - temp_metrics for link.
# - thermal for daisy, snow, pit etc.
THERMAL_SERVICES = ['temp_metrics', 'thermal']
# Measurement duration in seconds.
MEASUREMENT_DURATION = 30
# Time to exclude from calculation after playing a video [seconds].
STABILIZATION_DURATION = 10
# The number of frames used to warm up the rendering.
RENDERING_WARM_UP = 15
# A big number, used to keep the [vda|vea]_unittest running during the
# measurement.
MAX_INT = 2 ** 31 - 1
# Minimum battery charge percentage to run the test
BATTERY_INITIAL_CHARGED_MIN = 10
class CpuUsageMeasurer(object):
""" Class used to measure the CPU usage."""
def __init__(self):
self._service_stopper = None
self._original_governors = None
def __enter__(self):
# Stop the thermal service that may change the cpu frequency.
self._service_stopper = service_stopper.ServiceStopper(THERMAL_SERVICES)
self._service_stopper.stop_services()
if not utils.wait_for_idle_cpu(
WAIT_FOR_IDLE_CPU_TIMEOUT, CPU_IDLE_USAGE):
raise error.TestError('Could not get idle CPU.')
if not utils.wait_for_cool_machine():
raise error.TestError('Could not get cold machine.')
# Set the scaling governor to performance mode to set the cpu to the
# highest frequency available.
self._original_governors = utils.set_high_performance_mode()
return self
def start(self):
self.start_cpu_usage_ = utils.get_cpu_usage()
def stop(self):
return utils.compute_active_cpu_time(
self.start_cpu_usage_, utils.get_cpu_usage())
def __exit__(self, type, value, tb):
if self._service_stopper:
self._service_stopper.restore_services()
self._service_stopper = None
if self._original_governors:
utils.restore_scaling_governor_states(self._original_governors)
self._original_governors = None
class PowerMeasurer(object):
""" Class used to measure the power consumption."""
def __init__(self):
self._backlight = None
self._service_stopper = None
def __enter__(self):
self._backlight = power_utils.Backlight()
self._backlight.set_default()
self._service_stopper = service_stopper.ServiceStopper(
service_stopper.ServiceStopper.POWER_DRAW_SERVICES)
self._service_stopper.stop_services()
status = power_status.get_status()
# Verify that we are running on battery and the battery is sufficiently
# charged.
status.assert_battery_state(BATTERY_INITIAL_CHARGED_MIN)
self._system_power = power_status.SystemPower(status.battery_path)
self._power_logger = power_status.PowerLogger([self._system_power])
return self
def start(self):
self._power_logger.start()
def stop(self):
self._power_logger.checkpoint('result')
keyval = self._power_logger.calc()
logging.info(keyval)
return keyval['result_' + self._system_power.domain + '_pwr']
def __exit__(self, type, value, tb):
if self._backlight:
self._backlight.restore()
if self._service_stopper:
self._service_stopper.restore_services()
class DownloadManager(object):
"""Use this class to download and manage the resources for testing."""
def __init__(self, tmpdir=None):
self._download_map = {}
self._tmpdir = tmpdir
def get_path(self, name):
return self._download_map[name]
def clear(self):
map(os.unlink, self._download_map.values())
self._download_map.clear()
def _download_single_file(self, remote_path):
url = DOWNLOAD_BASE + remote_path
tmp = tempfile.NamedTemporaryFile(delete=False, dir=self._tmpdir)
logging.info('download "%s" to "%s"', url, tmp.name)
md5 = hashlib.md5()
with contextlib.closing(urllib2.urlopen(url)) as r:
while True:
content = r.read(128 * 1024)
if not content: break
md5.update(content)
tmp.write(content)
tmp.close()
filename = os.path.basename(remote_path)
m = RE_VERSIONING_FILE.match(filename)
if m:
prefix, md5sum, suffix = m.groups()
if md5.hexdigest() != md5sum:
raise error.TestError(
'unmatched md5 sum: %s' % md5.hexdigest())
filename = prefix + (suffix or '')
self._download_map[filename] = tmp.name
def download_all(self, resources):
for r in resources:
self._download_single_file(r)
class video_HangoutHardwarePerf(chrome_binary_test.ChromeBinaryTest):
"""
The test outputs the cpu usage when doing video encoding and video
decoding concurrently.
"""
version = 1
def get_vda_unittest_cmd_line(self, decode_videos):
test_video_data = []
for v in decode_videos:
assert len(v) == 6
# Convert to strings, also make a copy of the list.
v = map(str, v)
v[0] = self._downloads.get_path(v[0])
v[-1:-1] = ['0', '0'] # no fps requirements
test_video_data.append(':'.join(v))
cmd_line = [
self.get_chrome_binary_path(VDA_BINARY),
'--gtest_filter=DecodeVariations/*/0',
'--test_video_data=%s' % ';'.join(test_video_data),
'--rendering_warm_up=%d' % RENDERING_WARM_UP,
'--rendering_fps=%f' % RENDERING_FPS,
'--num_play_throughs=%d' % MAX_INT]
if utils.is_freon():
cmd_line.append('--ozone-platform=gbm')
return cmd_line
def get_vea_unittest_cmd_line(self, encode_videos):
test_stream_data = []
for v in encode_videos:
assert len(v) == 5
# Convert to strings, also make a copy of the list.
v = map(str, v)
v[0] = self._downloads.get_path(v[0])
# The output destination, ignore the output.
v.insert(4, '/dev/null')
# Insert the FPS requirement
v.append(str(INPUT_FPS))
test_stream_data.append(':'.join(v))
return [self.get_chrome_binary_path(VEA_BINARY),
'--gtest_filter=SimpleEncode/*/0',
'--test_stream_data=%s' % ';'.join(test_stream_data),
'--run_at_fps',
'--num_frames_to_encode=%d' % MAX_INT]
def run_in_parallel(self, *commands):
env = os.environ.copy()
# To clear the temparory files created by vea_unittest.
env['TMPDIR'] = self.tmpdir
if not utils.is_freon():
env['DISPLAY'] = ':0'
env['XAUTHORITY'] = '/home/chronos/.Xauthority'
return map(lambda c: cmd_utils.popen(c, env=env), commands)
def simulate_hangout(self, decode_videos, encode_videos, measurer):
popens = self.run_in_parallel(
self.get_vda_unittest_cmd_line(decode_videos),
self.get_vea_unittest_cmd_line(encode_videos))
try:
time.sleep(STABILIZATION_DURATION)
measurer.start()
time.sleep(MEASUREMENT_DURATION)
measurement = measurer.stop()
# Ensure both encoding and decoding are still alive
if any(p.poll() is not None for p in popens):
raise error.TestError('vea/vda_unittest failed')
return measurement
finally:
cmd_utils.kill_or_log_returncode(*popens)
@chrome_binary_test.nuke_chrome
def run_once(self, resources, decode_videos, encode_videos, measurement):
self._downloads = DownloadManager(tmpdir = self.tmpdir)
try:
self._downloads.download_all(resources)
if measurement == 'cpu':
with CpuUsageMeasurer() as measurer:
value = self.simulate_hangout(
decode_videos, encode_videos, measurer)
self.output_perf_value(
description='cpu_usage', value=value * 100,
units=UNIT_PERCENT, higher_is_better=False)
elif measurement == 'power':
with PowerMeasurer() as measurer:
value = self.simulate_hangout(
decode_videos, encode_videos, measurer)
self.output_perf_value(
description='power_usage', value=value,
units=UNIT_WATT, higher_is_better=False)
else:
raise error.TestError('Unknown measurement: ' + measurement)
finally:
self._downloads.clear()