1# Copyright 2014 The Chromium Authors. All rights reserved. 2# Use of this source code is governed by a BSD-style license that can be 3# found in the LICENSE file. 4 5import hashlib, logging, os, re, time, tempfile 6 7from autotest_lib.client.bin import utils 8from autotest_lib.client.common_lib import error 9from autotest_lib.client.common_lib import file_utils 10from autotest_lib.client.cros import chrome_binary_test 11from autotest_lib.client.cros import service_stopper 12from autotest_lib.client.cros.audio import cmd_utils 13from autotest_lib.client.cros.power import power_status, power_utils 14from autotest_lib.client.cros.video import device_capability 15from autotest_lib.client.cros.video import helper_logger 16 17# The download base for test assets. 18DOWNLOAD_BASE = ('http://commondatastorage.googleapis.com' 19 '/chromiumos-test-assets-public/') 20 21# The executable name of the vda unittest 22VDA_BINARY = 'video_decode_accelerator_unittest' 23 24# The executable name of the vea unittest 25VEA_BINARY = 'video_encode_accelerator_unittest' 26 27# The input frame rate for the vea_unittest. 28INPUT_FPS = 30 29 30# The rendering fps in the vda_unittest. 31RENDERING_FPS = 30 32 33UNIT_PERCENT = '%' 34UNIT_WATT = 'W' 35 36# The regex of the versioning file. 37# e.g., crowd720-3cfe7b096f765742b4aa79e55fe7c994.yuv 38RE_VERSIONING_FILE = re.compile(r'(.+)-([0-9a-fA-F]{32})(\..+)?') 39 40# Time in seconds to wait for cpu idle until giveup. 41WAIT_FOR_IDLE_CPU_TIMEOUT = 60 42 43# Maximum percent of cpu usage considered as idle. 44CPU_IDLE_USAGE = 0.1 45 46# Measurement duration in seconds. 47MEASUREMENT_DURATION = 30 48 49# Time to exclude from calculation after playing a video [seconds]. 50STABILIZATION_DURATION = 10 51 52# A big number, used to keep the [vda|vea]_unittest running during the 53# measurement. 54MAX_INT = 2 ** 31 - 1 55 56# Minimum battery charge percentage to run the test 57BATTERY_INITIAL_CHARGED_MIN = 10 58 59 60class CpuUsageMeasurer(object): 61 """ Class used to measure the CPU usage.""" 62 63 def __init__(self): 64 self._service_stopper = None 65 self._original_governors = None 66 67 def __enter__(self): 68 # Stop the thermal service that may change the cpu frequency. 69 self._service_stopper = service_stopper.get_thermal_service_stopper() 70 self._service_stopper.stop_services() 71 72 if not utils.wait_for_idle_cpu( 73 WAIT_FOR_IDLE_CPU_TIMEOUT, CPU_IDLE_USAGE): 74 raise error.TestError('Could not get idle CPU.') 75 if not utils.wait_for_cool_machine(): 76 raise error.TestError('Could not get cold machine.') 77 78 # Set the scaling governor to performance mode to set the cpu to the 79 # highest frequency available. 80 self._original_governors = utils.set_high_performance_mode() 81 return self 82 83 def start(self): 84 self.start_cpu_usage_ = utils.get_cpu_usage() 85 86 def stop(self): 87 return utils.compute_active_cpu_time( 88 self.start_cpu_usage_, utils.get_cpu_usage()) 89 90 def __exit__(self, type, value, tb): 91 if self._service_stopper: 92 self._service_stopper.restore_services() 93 self._service_stopper = None 94 if self._original_governors: 95 utils.restore_scaling_governor_states(self._original_governors) 96 self._original_governors = None 97 98 99class PowerMeasurer(object): 100 """ Class used to measure the power consumption.""" 101 102 def __init__(self): 103 self._backlight = None 104 self._service_stopper = None 105 106 def __enter__(self): 107 status = power_status.get_status() 108 109 # We expect the DUT is powered by battery now. But this is not always 110 # true due to other bugs. Disable this test temporarily as workaround. 111 # TODO(johnylin): remove this workaround after AC control is stable 112 # crbug.com/914211 113 if status.on_ac(): 114 logging.warning('Still powered by AC. Skip this test') 115 raise error.TestNAError('Unable to disable AC.') 116 # Verify that we are running on battery and the battery is sufficiently 117 # charged. 118 status.assert_battery_state(BATTERY_INITIAL_CHARGED_MIN) 119 120 self._backlight = power_utils.Backlight() 121 self._backlight.set_default() 122 123 self._service_stopper = service_stopper.ServiceStopper( 124 service_stopper.ServiceStopper.POWER_DRAW_SERVICES) 125 self._service_stopper.stop_services() 126 127 self._system_power = power_status.SystemPower(status.battery_path) 128 self._power_logger = power_status.PowerLogger([self._system_power]) 129 return self 130 131 def start(self): 132 self._power_logger.start() 133 134 def stop(self): 135 self._power_logger.checkpoint('result') 136 keyval = self._power_logger.calc() 137 logging.info(keyval) 138 return keyval['result_' + self._system_power.domain + '_pwr_avg'] 139 140 def __exit__(self, type, value, tb): 141 if self._backlight: 142 self._backlight.restore() 143 if self._service_stopper: 144 self._service_stopper.restore_services() 145 146 147class DownloadManager(object): 148 """Use this class to download and manage the resources for testing.""" 149 150 def __init__(self, tmpdir=None): 151 self._download_map = {} 152 self._tmpdir = tmpdir 153 154 def get_path(self, name): 155 return self._download_map[name] 156 157 def clear(self): 158 map(os.unlink, self._download_map.values()) 159 self._download_map.clear() 160 161 def _download_single_file(self, remote_path): 162 url = DOWNLOAD_BASE + remote_path 163 tmp = tempfile.NamedTemporaryFile(delete=False, dir=self._tmpdir) 164 logging.info('download "%s" to "%s"', url, tmp.name) 165 166 file_utils.download_file(url, tmp.name) 167 md5 = hashlib.md5() 168 with open(tmp.name, 'r') as r: 169 while True: 170 block = r.read(128 * 1024) 171 if not block: 172 break 173 md5.update(block) 174 175 filename = os.path.basename(remote_path) 176 m = RE_VERSIONING_FILE.match(filename) 177 if m: 178 prefix, md5sum, suffix = m.groups() 179 if md5.hexdigest() != md5sum: 180 raise error.TestError( 181 'unmatched md5 sum: %s' % md5.hexdigest()) 182 filename = prefix + (suffix or '') 183 self._download_map[filename] = tmp.name 184 185 def download_all(self, resources): 186 for r in resources: 187 self._download_single_file(r) 188 189 190class video_HangoutHardwarePerf(chrome_binary_test.ChromeBinaryTest): 191 """ 192 The test outputs the cpu usage when doing video encoding and video 193 decoding concurrently. 194 """ 195 196 version = 1 197 198 def get_vda_unittest_cmd_line(self, decode_videos): 199 test_video_data = [] 200 for v in decode_videos: 201 assert len(v) == 6 202 # Convert to strings, also make a copy of the list. 203 v = map(str, v) 204 v[0] = self._downloads.get_path(v[0]) 205 v[-1:-1] = ['0', '0'] # no fps requirements 206 test_video_data.append(':'.join(v)) 207 cmd_line = [ 208 self.get_chrome_binary_path(VDA_BINARY), 209 '--gtest_filter=DecodeVariations/*/0', 210 '--test_video_data=%s' % ';'.join(test_video_data), 211 '--rendering_fps=%f' % RENDERING_FPS, 212 '--num_play_throughs=%d' % MAX_INT, 213 helper_logger.chrome_vmodule_flag(), 214 ] 215 cmd_line.append('--ozone-platform=gbm') 216 return cmd_line 217 218 219 def get_vea_unittest_cmd_line(self, encode_videos): 220 test_stream_data = [] 221 for v in encode_videos: 222 assert len(v) == 5 223 # Convert to strings, also make a copy of the list. 224 v = map(str, v) 225 v[0] = self._downloads.get_path(v[0]) 226 # The output destination, ignore the output. 227 v.insert(4, '/dev/null') 228 # Insert the FPS requirement 229 v.append(str(INPUT_FPS)) 230 test_stream_data.append(':'.join(v)) 231 cmd_line = [ 232 self.get_chrome_binary_path(VEA_BINARY), 233 '--gtest_filter=SimpleEncode/*/0', 234 '--test_stream_data=%s' % ';'.join(test_stream_data), 235 '--run_at_fps', 236 '--num_frames_to_encode=%d' % MAX_INT, 237 helper_logger.chrome_vmodule_flag(), 238 ] 239 cmd_line.append('--ozone-platform=gbm') 240 return cmd_line 241 242 def run_in_parallel(self, *commands): 243 env = os.environ.copy() 244 245 # To clear the temparory files created by vea_unittest. 246 env['TMPDIR'] = self.tmpdir 247 return map(lambda c: cmd_utils.popen(c, env=env), commands) 248 249 def simulate_hangout(self, decode_videos, encode_videos, measurer, 250 decode_threads, encode_threads): 251 decode_cmds = [self.get_vda_unittest_cmd_line(decode_videos) 252 for i in range(decode_threads)] 253 encode_cmds = [self.get_vea_unittest_cmd_line(encode_videos) 254 for i in range(encode_threads)] 255 popens = self.run_in_parallel(*(decode_cmds + encode_cmds)) 256 try: 257 time.sleep(STABILIZATION_DURATION) 258 measurer.start() 259 time.sleep(MEASUREMENT_DURATION) 260 measurement = measurer.stop() 261 262 # Ensure both encoding and decoding are still alive 263 if any(p.poll() is not None for p in popens): 264 raise error.TestError('vea/vda_unittest failed') 265 266 return measurement 267 finally: 268 cmd_utils.kill_or_log_returncode(*popens) 269 270 @helper_logger.video_log_wrapper 271 @chrome_binary_test.nuke_chrome 272 def run_once(self, resources, decode_videos, encode_videos, measurement, 273 capabilities, decode_threads=1, encode_threads=1): 274 dc = device_capability.DeviceCapability() 275 for cap in capabilities: 276 dc.ensure_capability(cap) 277 278 self._downloads = DownloadManager(tmpdir = self.tmpdir) 279 try: 280 self._downloads.download_all(resources) 281 if measurement == 'cpu': 282 with CpuUsageMeasurer() as measurer: 283 value = self.simulate_hangout( 284 decode_videos, encode_videos, measurer, 285 decode_threads, encode_threads) 286 self.output_perf_value( 287 description='cpu_usage', value=value * 100, 288 units=UNIT_PERCENT, higher_is_better=False) 289 elif measurement == 'power': 290 with PowerMeasurer() as measurer: 291 value = self.simulate_hangout( 292 decode_videos, encode_videos, measurer, 293 decode_threads, encode_threads) 294 self.output_perf_value( 295 description='power_usage', value=value, 296 units=UNIT_WATT, higher_is_better=False) 297 else: 298 raise error.TestError('Unknown measurement: ' + measurement) 299 finally: 300 self._downloads.clear() 301