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 5 import contextlib, hashlib, logging, os, pipes, re, sys, time, tempfile 6 7 from autotest_lib.client.bin import utils 8 from autotest_lib.client.common_lib import error 9 from autotest_lib.client.common_lib import file_utils 10 from autotest_lib.client.cros import chrome_binary_test 11 from autotest_lib.client.cros import power_status, power_utils 12 from autotest_lib.client.cros import service_stopper 13 from autotest_lib.client.cros.audio import cmd_utils 14 15 # The download base for test assets. 16 DOWNLOAD_BASE = ('http://commondatastorage.googleapis.com' 17 '/chromiumos-test-assets-public/') 18 19 # The executable name of the vda unittest 20 VDA_BINARY = 'video_decode_accelerator_unittest' 21 22 # The executable name of the vea unittest 23 VEA_BINARY = 'video_encode_accelerator_unittest' 24 25 # The input frame rate for the vea_unittest. 26 INPUT_FPS = 30 27 28 # The rendering fps in the vda_unittest. 29 RENDERING_FPS = 30 30 31 # The unit(s) should match chromium/src/tools/perf/unit-info.json. 32 UNIT_PERCENT = '%' 33 UNIT_WATT = 'W' 34 35 # The regex of the versioning file. 36 # e.g., crowd720-3cfe7b096f765742b4aa79e55fe7c994.yuv 37 RE_VERSIONING_FILE = re.compile(r'(.+)-([0-9a-fA-F]{32})(\..+)?') 38 39 # Time in seconds to wait for cpu idle until giveup. 40 WAIT_FOR_IDLE_CPU_TIMEOUT = 60 41 42 # Maximum percent of cpu usage considered as idle. 43 CPU_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. 48 THERMAL_SERVICES = ['temp_metrics', 'thermal'] 49 50 # Measurement duration in seconds. 51 MEASUREMENT_DURATION = 30 52 53 # Time to exclude from calculation after playing a video [seconds]. 54 STABILIZATION_DURATION = 10 55 56 # The number of frames used to warm up the rendering. 57 RENDERING_WARM_UP = 15 58 59 # A big number, used to keep the [vda|vea]_unittest running during the 60 # measurement. 61 MAX_INT = 2 ** 31 - 1 62 63 # Minimum battery charge percentage to run the test 64 BATTERY_INITIAL_CHARGED_MIN = 10 65 66 67 class 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 106 class 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 146 class 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 md5.update(r.read()) 169 170 filename = os.path.basename(remote_path) 171 m = RE_VERSIONING_FILE.match(filename) 172 if m: 173 prefix, md5sum, suffix = m.groups() 174 if md5.hexdigest() != md5sum: 175 raise error.TestError( 176 'unmatched md5 sum: %s' % md5.hexdigest()) 177 filename = prefix + (suffix or '') 178 self._download_map[filename] = tmp.name 179 180 def download_all(self, resources): 181 for r in resources: 182 self._download_single_file(r) 183 184 185 class video_HangoutHardwarePerf(chrome_binary_test.ChromeBinaryTest): 186 """ 187 The test outputs the cpu usage when doing video encoding and video 188 decoding concurrently. 189 """ 190 191 version = 1 192 193 def get_vda_unittest_cmd_line(self, decode_videos): 194 test_video_data = [] 195 for v in decode_videos: 196 assert len(v) == 6 197 # Convert to strings, also make a copy of the list. 198 v = map(str, v) 199 v[0] = self._downloads.get_path(v[0]) 200 v[-1:-1] = ['0', '0'] # no fps requirements 201 test_video_data.append(':'.join(v)) 202 cmd_line = [ 203 self.get_chrome_binary_path(VDA_BINARY), 204 '--gtest_filter=DecodeVariations/*/0', 205 '--test_video_data=%s' % ';'.join(test_video_data), 206 '--rendering_warm_up=%d' % RENDERING_WARM_UP, 207 '--rendering_fps=%f' % RENDERING_FPS, 208 '--num_play_throughs=%d' % MAX_INT] 209 if utils.is_freon(): 210 cmd_line.append('--ozone-platform=gbm') 211 return cmd_line 212 213 214 def get_vea_unittest_cmd_line(self, encode_videos): 215 test_stream_data = [] 216 for v in encode_videos: 217 assert len(v) == 5 218 # Convert to strings, also make a copy of the list. 219 v = map(str, v) 220 v[0] = self._downloads.get_path(v[0]) 221 # The output destination, ignore the output. 222 v.insert(4, '/dev/null') 223 # Insert the FPS requirement 224 v.append(str(INPUT_FPS)) 225 test_stream_data.append(':'.join(v)) 226 cmd_line = [ 227 self.get_chrome_binary_path(VEA_BINARY), 228 '--gtest_filter=SimpleEncode/*/0', 229 '--test_stream_data=%s' % ';'.join(test_stream_data), 230 '--run_at_fps', 231 '--num_frames_to_encode=%d' % MAX_INT] 232 if utils.is_freon(): 233 cmd_line.append('--ozone-platform=gbm') 234 return cmd_line 235 236 def run_in_parallel(self, *commands): 237 env = os.environ.copy() 238 239 # To clear the temparory files created by vea_unittest. 240 env['TMPDIR'] = self.tmpdir 241 if not utils.is_freon(): 242 env['DISPLAY'] = ':0' 243 env['XAUTHORITY'] = '/home/chronos/.Xauthority' 244 return map(lambda c: cmd_utils.popen(c, env=env), commands) 245 246 def simulate_hangout(self, decode_videos, encode_videos, measurer): 247 popens = self.run_in_parallel( 248 self.get_vda_unittest_cmd_line(decode_videos), 249 self.get_vea_unittest_cmd_line(encode_videos)) 250 try: 251 time.sleep(STABILIZATION_DURATION) 252 measurer.start() 253 time.sleep(MEASUREMENT_DURATION) 254 measurement = measurer.stop() 255 256 # Ensure both encoding and decoding are still alive 257 if any(p.poll() is not None for p in popens): 258 raise error.TestError('vea/vda_unittest failed') 259 260 return measurement 261 finally: 262 cmd_utils.kill_or_log_returncode(*popens) 263 264 @chrome_binary_test.nuke_chrome 265 def run_once(self, resources, decode_videos, encode_videos, measurement): 266 self._downloads = DownloadManager(tmpdir = self.tmpdir) 267 try: 268 self._downloads.download_all(resources) 269 if measurement == 'cpu': 270 with CpuUsageMeasurer() as measurer: 271 value = self.simulate_hangout( 272 decode_videos, encode_videos, measurer) 273 self.output_perf_value( 274 description='cpu_usage', value=value * 100, 275 units=UNIT_PERCENT, higher_is_better=False) 276 elif measurement == 'power': 277 with PowerMeasurer() as measurer: 278 value = self.simulate_hangout( 279 decode_videos, encode_videos, measurer) 280 self.output_perf_value( 281 description='power_usage', value=value, 282 units=UNIT_WATT, higher_is_better=False) 283 else: 284 raise error.TestError('Unknown measurement: ' + measurement) 285 finally: 286 self._downloads.clear() 287