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 contextlib, hashlib, logging, os, pipes, re, sys, 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 power_status, power_utils 12from autotest_lib.client.cros import service_stopper 13from autotest_lib.client.cros.audio import cmd_utils 14 15# The download base for test assets. 16DOWNLOAD_BASE = ('http://commondatastorage.googleapis.com' 17 '/chromiumos-test-assets-public/') 18 19# The executable name of the vda unittest 20VDA_BINARY = 'video_decode_accelerator_unittest' 21 22# The executable name of the vea unittest 23VEA_BINARY = 'video_encode_accelerator_unittest' 24 25# The input frame rate for the vea_unittest. 26INPUT_FPS = 30 27 28# The rendering fps in the vda_unittest. 29RENDERING_FPS = 30 30 31# The unit(s) should match chromium/src/tools/perf/unit-info.json. 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 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 185class 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