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 helper_logger 15 16# The download base for test assets. 17DOWNLOAD_BASE = ('http://commondatastorage.googleapis.com' 18 '/chromiumos-test-assets-public/') 19 20# The executable name of the vda unittest 21VDA_BINARY = 'video_decode_accelerator_unittest' 22 23# The executable name of the vea unittest 24VEA_BINARY = 'video_encode_accelerator_unittest' 25 26# The input frame rate for the vea_unittest. 27INPUT_FPS = 30 28 29# The rendering fps in the vda_unittest. 30RENDERING_FPS = 30 31 32UNIT_PERCENT = '%' 33UNIT_WATT = 'W' 34 35# The regex of the versioning file. 36# e.g., crowd720-3cfe7b096f765742b4aa79e55fe7c994.yuv 37RE_VERSIONING_FILE = re.compile(r'(.+)-([0-9a-fA-F]{32})(\..+)?') 38 39# Time in seconds to wait for cpu idle until giveup. 40WAIT_FOR_IDLE_CPU_TIMEOUT = 60 41 42# Maximum percent of cpu usage considered as idle. 43CPU_IDLE_USAGE = 0.1 44 45# List of thermal throttling services that should be disabled. 46# - temp_metrics for link. 47# - thermal for daisy, snow, pit etc. 48THERMAL_SERVICES = ['temp_metrics', 'thermal'] 49 50# Measurement duration in seconds. 51MEASUREMENT_DURATION = 30 52 53# Time to exclude from calculation after playing a video [seconds]. 54STABILIZATION_DURATION = 10 55 56# The number of frames used to warm up the rendering. 57RENDERING_WARM_UP = 15 58 59# A big number, used to keep the [vda|vea]_unittest running during the 60# measurement. 61MAX_INT = 2 ** 31 - 1 62 63# Minimum battery charge percentage to run the test 64BATTERY_INITIAL_CHARGED_MIN = 10 65 66 67class CpuUsageMeasurer(object): 68 """ Class used to measure the CPU usage.""" 69 70 def __init__(self): 71 self._service_stopper = None 72 self._original_governors = None 73 74 def __enter__(self): 75 # Stop the thermal service that may change the cpu frequency. 76 self._service_stopper = service_stopper.ServiceStopper(THERMAL_SERVICES) 77 self._service_stopper.stop_services() 78 79 if not utils.wait_for_idle_cpu( 80 WAIT_FOR_IDLE_CPU_TIMEOUT, CPU_IDLE_USAGE): 81 raise error.TestError('Could not get idle CPU.') 82 if not utils.wait_for_cool_machine(): 83 raise error.TestError('Could not get cold machine.') 84 85 # Set the scaling governor to performance mode to set the cpu to the 86 # highest frequency available. 87 self._original_governors = utils.set_high_performance_mode() 88 return self 89 90 def start(self): 91 self.start_cpu_usage_ = utils.get_cpu_usage() 92 93 def stop(self): 94 return utils.compute_active_cpu_time( 95 self.start_cpu_usage_, utils.get_cpu_usage()) 96 97 def __exit__(self, type, value, tb): 98 if self._service_stopper: 99 self._service_stopper.restore_services() 100 self._service_stopper = None 101 if self._original_governors: 102 utils.restore_scaling_governor_states(self._original_governors) 103 self._original_governors = None 104 105 106class PowerMeasurer(object): 107 """ Class used to measure the power consumption.""" 108 109 def __init__(self): 110 self._backlight = None 111 self._service_stopper = None 112 113 def __enter__(self): 114 self._backlight = power_utils.Backlight() 115 self._backlight.set_default() 116 117 self._service_stopper = service_stopper.ServiceStopper( 118 service_stopper.ServiceStopper.POWER_DRAW_SERVICES) 119 self._service_stopper.stop_services() 120 121 status = power_status.get_status() 122 123 # Verify that we are running on battery and the battery is sufficiently 124 # charged. 125 status.assert_battery_state(BATTERY_INITIAL_CHARGED_MIN) 126 self._system_power = power_status.SystemPower(status.battery_path) 127 self._power_logger = power_status.PowerLogger([self._system_power]) 128 return self 129 130 def start(self): 131 self._power_logger.start() 132 133 def stop(self): 134 self._power_logger.checkpoint('result') 135 keyval = self._power_logger.calc() 136 logging.info(keyval) 137 return keyval['result_' + self._system_power.domain + '_pwr'] 138 139 def __exit__(self, type, value, tb): 140 if self._backlight: 141 self._backlight.restore() 142 if self._service_stopper: 143 self._service_stopper.restore_services() 144 145 146class DownloadManager(object): 147 """Use this class to download and manage the resources for testing.""" 148 149 def __init__(self, tmpdir=None): 150 self._download_map = {} 151 self._tmpdir = tmpdir 152 153 def get_path(self, name): 154 return self._download_map[name] 155 156 def clear(self): 157 map(os.unlink, self._download_map.values()) 158 self._download_map.clear() 159 160 def _download_single_file(self, remote_path): 161 url = DOWNLOAD_BASE + remote_path 162 tmp = tempfile.NamedTemporaryFile(delete=False, dir=self._tmpdir) 163 logging.info('download "%s" to "%s"', url, tmp.name) 164 165 file_utils.download_file(url, tmp.name) 166 md5 = hashlib.md5() 167 with open(tmp.name, 'r') as r: 168 while True: 169 block = r.read(128 * 1024) 170 if not block: 171 break 172 md5.update(block) 173 174 filename = os.path.basename(remote_path) 175 m = RE_VERSIONING_FILE.match(filename) 176 if m: 177 prefix, md5sum, suffix = m.groups() 178 if md5.hexdigest() != md5sum: 179 raise error.TestError( 180 'unmatched md5 sum: %s' % md5.hexdigest()) 181 filename = prefix + (suffix or '') 182 self._download_map[filename] = tmp.name 183 184 def download_all(self, resources): 185 for r in resources: 186 self._download_single_file(r) 187 188 189class video_HangoutHardwarePerf(chrome_binary_test.ChromeBinaryTest): 190 """ 191 The test outputs the cpu usage when doing video encoding and video 192 decoding concurrently. 193 """ 194 195 version = 1 196 197 def get_vda_unittest_cmd_line(self, decode_videos): 198 test_video_data = [] 199 for v in decode_videos: 200 assert len(v) == 6 201 # Convert to strings, also make a copy of the list. 202 v = map(str, v) 203 v[0] = self._downloads.get_path(v[0]) 204 v[-1:-1] = ['0', '0'] # no fps requirements 205 test_video_data.append(':'.join(v)) 206 cmd_line = [ 207 self.get_chrome_binary_path(VDA_BINARY), 208 '--gtest_filter=DecodeVariations/*/0', 209 '--test_video_data=%s' % ';'.join(test_video_data), 210 '--rendering_warm_up=%d' % RENDERING_WARM_UP, 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 popens = self.run_in_parallel( 251 self.get_vda_unittest_cmd_line(decode_videos), 252 self.get_vea_unittest_cmd_line(encode_videos)) 253 try: 254 time.sleep(STABILIZATION_DURATION) 255 measurer.start() 256 time.sleep(MEASUREMENT_DURATION) 257 measurement = measurer.stop() 258 259 # Ensure both encoding and decoding are still alive 260 if any(p.poll() is not None for p in popens): 261 raise error.TestError('vea/vda_unittest failed') 262 263 return measurement 264 finally: 265 cmd_utils.kill_or_log_returncode(*popens) 266 267 @helper_logger.video_log_wrapper 268 @chrome_binary_test.nuke_chrome 269 def run_once(self, resources, decode_videos, encode_videos, measurement): 270 self._downloads = DownloadManager(tmpdir = self.tmpdir) 271 try: 272 self._downloads.download_all(resources) 273 if measurement == 'cpu': 274 with CpuUsageMeasurer() as measurer: 275 value = self.simulate_hangout( 276 decode_videos, encode_videos, measurer) 277 self.output_perf_value( 278 description='cpu_usage', value=value * 100, 279 units=UNIT_PERCENT, higher_is_better=False) 280 elif measurement == 'power': 281 with PowerMeasurer() as measurer: 282 value = self.simulate_hangout( 283 decode_videos, encode_videos, measurer) 284 self.output_perf_value( 285 description='power_usage', value=value, 286 units=UNIT_WATT, higher_is_better=False) 287 else: 288 raise error.TestError('Unknown measurement: ' + measurement) 289 finally: 290 self._downloads.clear() 291