1# Copyright (c) 2014 The Chromium OS 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 logging 6import os 7import time 8 9from autotest_lib.client.bin import site_utils, test, utils 10from autotest_lib.client.common_lib import error 11from autotest_lib.client.common_lib import file_utils 12from autotest_lib.client.common_lib.cros import chrome 13from autotest_lib.client.cros import power_status, power_utils 14from autotest_lib.client.cros import service_stopper 15from autotest_lib.client.cros.video import histogram_verifier 16from autotest_lib.client.cros.video import constants 17 18 19EXTRA_BROWSER_ARGS = ['--use-fake-device-for-media-stream', 20 '--use-fake-ui-for-media-stream'] 21FAKE_FILE_ARG = '--use-file-for-fake-video-capture="%s"' 22DISABLE_ACCELERATED_VIDEO_DECODE_BROWSER_ARGS = [ 23 '--disable-accelerated-video-decode'] 24 25DOWNLOAD_BASE = ('http://commondatastorage.googleapis.com/' 26 'chromiumos-test-assets-public/crowd/') 27VIDEO_NAME = 'crowd720_25frames.y4m' 28 29WEBRTC_INTERNALS_URL = 'chrome://webrtc-internals' 30 31WEBRTC_WITH_HW_ACCELERATION = 'webrtc_with_hw_acceleration' 32WEBRTC_WITHOUT_HW_ACCELERATION = 'webrtc_without_hw_acceleration' 33 34# Measurement duration in seconds. 35MEASUREMENT_DURATION = 30 36# Time to exclude from calculation after start the loopback [seconds]. 37STABILIZATION_DURATION = 10 38 39# List of thermal throttling services that should be disabled. 40# - temp_metrics for link. 41# - thermal for daisy, snow, pit etc. 42THERMAL_SERVICES = ['temp_metrics', 'thermal'] 43 44# Time in seconds to wait for cpu idle until giving up. 45WAIT_FOR_IDLE_CPU_TIMEOUT = 60.0 46# Maximum percent of cpu usage considered as idle. 47CPU_IDLE_USAGE = 0.1 48 49MAX_DECODE_TIME_DESCRIPTION = 'decode_time.max' 50MEDIAN_DECODE_TIME_DESCRIPTION = 'decode_time.percentile_0.50' 51CPU_USAGE_DESCRIPTION = 'video_cpu_usage' 52POWER_DESCRIPTION = 'video_mean_energy_rate' 53 54# Minimum battery charge percentage to run the power test. 55BATTERY_INITIAL_CHARGED_MIN = 10 56 57# These are the variable names in WebRTC internals. 58# Maximum decode time of the frames of the last 10 seconds. 59GOOG_MAX_DECODE_MS = 'googMaxDecodeMs' 60# The decode time of the last frame. 61GOOG_DECODE_MS = 'googDecodeMs' 62 63# Javascript function to get the decode time. addStats is a function called by 64# Chrome to pass WebRTC statistics every second. 65ADD_STATS_JAVASCRIPT = ( 66 'var googMaxDecodeMs = new Array();' 67 'var googDecodeMs = new Array();' 68 'addStats = function(data) {' 69 ' reports = data.reports;' 70 ' for (var i=0; i < reports.length; i++) {' 71 ' if (reports[i].type == "ssrc") {' 72 ' values = reports[i].stats.values;' 73 ' for (var j=0; j < values.length; j++) {' 74 ' if (values[j] == "googMaxDecodeMs")' 75 ' googMaxDecodeMs[googMaxDecodeMs.length] = values[j+1];' 76 ' else if (values[j] == "googDecodeMs")' 77 ' googDecodeMs[googDecodeMs.length] = values[j+1];' 78 ' }' 79 ' }' 80 ' }' 81 '}') 82 83# Measure the stats until getting 10 samples or exceeding 15 seconds. addStats 84# is called once per second for now. 85NUM_DECODE_TIME_SAMPLES = 10 86TIMEOUT = 15 87 88 89class video_WebRtcPerf(test.test): 90 """ 91 The test outputs the decode time, cpu usage and the power consumption for 92 WebRTC to performance dashboard. 93 """ 94 version = 1 95 arc_mode = None 96 97 98 def start_loopback(self, cr): 99 """ 100 Opens WebRTC loopback page. 101 102 @param cr: Autotest Chrome instance. 103 """ 104 cr.browser.platform.SetHTTPServerDirectories(self.bindir) 105 106 tab = cr.browser.tabs[0] 107 tab.Navigate(cr.browser.platform.http_server.UrlOf( 108 os.path.join(self.bindir, 'loopback.html'))) 109 tab.WaitForDocumentReadyStateToBeComplete() 110 111 112 def open_stats_page(self, cr): 113 """ 114 Opens WebRTC internal statistics page. 115 116 @param cr: Autotest Chrome instance. 117 118 @returns the tab of the stats page. 119 """ 120 tab = cr.browser.tabs.New() 121 tab.Navigate(WEBRTC_INTERNALS_URL) 122 tab.WaitForDocumentReadyStateToBeComplete() 123 # Inject stats callback function. 124 tab.EvaluateJavaScript(ADD_STATS_JAVASCRIPT) 125 return tab 126 127 128 def run_once(self, decode_time_test=False, cpu_test=False, 129 power_test=False, arc_mode=None): 130 """ 131 Runs the video_WebRtcPerf test. 132 133 @param decode_time_test: Pass True to run decode time test. 134 @param cpu_test: Pass True to run CPU usage test. 135 @param power_test: Pass True to run power consumption test. 136 @param arc_mode: if 'enabled', run the test with Android enabled. 137 """ 138 # Download test video. 139 url = DOWNLOAD_BASE + VIDEO_NAME 140 local_path = os.path.join(self.bindir, VIDEO_NAME) 141 file_utils.download_file(url, local_path) 142 self.arc_mode = arc_mode 143 144 if decode_time_test: 145 keyvals = self.test_decode_time(local_path) 146 # The first value is max decode time. The second value is median 147 # decode time. Construct a dictionary containing one value to log 148 # the result. 149 max_decode_time = { 150 key:value[0] for (key, value) in keyvals.items()} 151 self.log_result(max_decode_time, MAX_DECODE_TIME_DESCRIPTION, 152 'milliseconds') 153 median_decode_time = { 154 key:value[1] for (key, value) in keyvals.items()} 155 self.log_result(median_decode_time, MEDIAN_DECODE_TIME_DESCRIPTION, 156 'milliseconds') 157 158 if cpu_test: 159 keyvals = self.test_cpu_usage(local_path) 160 self.log_result(keyvals, CPU_USAGE_DESCRIPTION, 'percent') 161 162 if power_test: 163 keyvals = self.test_power(local_path) 164 self.log_result(keyvals, POWER_DESCRIPTION , 'W') 165 166 167 def test_webrtc(self, local_path, gather_result): 168 """ 169 Runs the webrtc test with and without hardware acceleration. 170 171 @param local_path: the path to the video file. 172 @param gather_result: a function to run and return the test result 173 after chrome opens. The input parameter of the funciton is 174 Autotest chrome instance. 175 176 @return a dictionary that contains test the result. 177 """ 178 keyvals = {} 179 EXTRA_BROWSER_ARGS.append(FAKE_FILE_ARG % local_path) 180 181 with chrome.Chrome(extra_browser_args=EXTRA_BROWSER_ARGS, 182 arc_mode=self.arc_mode, 183 init_network_controller=True) as cr: 184 # On daisy, Chrome freezes about 30 seconds after login because of 185 # TPM error. See http://crbug.com/588579. 186 if utils.get_board() == 'daisy': 187 logging.warning('Delay 30s for issue 588579 on daisy') 188 time.sleep(30) 189 # Open WebRTC loopback page and start the loopback. 190 self.start_loopback(cr) 191 result = gather_result(cr) 192 193 # Check if decode is hardware accelerated. 194 if histogram_verifier.is_bucket_present( 195 cr, 196 constants.RTC_INIT_HISTOGRAM, 197 constants.RTC_VIDEO_INIT_BUCKET): 198 keyvals[WEBRTC_WITH_HW_ACCELERATION] = result 199 else: 200 logging.info("Can not use hardware decoding.") 201 keyvals[WEBRTC_WITHOUT_HW_ACCELERATION] = result 202 return keyvals 203 204 # Start chrome with disabled video hardware decode flag. 205 with chrome.Chrome(extra_browser_args= 206 DISABLE_ACCELERATED_VIDEO_DECODE_BROWSER_ARGS + 207 EXTRA_BROWSER_ARGS, arc_mode=self.arc_mode) as cr: 208 if utils.get_board() == 'daisy': 209 logging.warning('Delay 30s for issue 588579 on daisy') 210 time.sleep(30) 211 # Open the webrtc loopback page and start the loopback. 212 self.start_loopback(cr) 213 result = gather_result(cr) 214 215 # Make sure decode is not hardware accelerated. 216 if histogram_verifier.is_bucket_present( 217 cr, 218 constants.RTC_INIT_HISTOGRAM, 219 constants.RTC_VIDEO_INIT_BUCKET): 220 raise error.TestError('HW decode should not be used.') 221 keyvals[WEBRTC_WITHOUT_HW_ACCELERATION] = result 222 223 return keyvals 224 225 226 def test_cpu_usage(self, local_path): 227 """ 228 Runs the video cpu usage test. 229 230 @param local_path: the path to the video file. 231 232 @return a dictionary that contains the test result. 233 """ 234 def get_cpu_usage(cr): 235 time.sleep(STABILIZATION_DURATION) 236 cpu_usage_start = site_utils.get_cpu_usage() 237 time.sleep(MEASUREMENT_DURATION) 238 cpu_usage_end = site_utils.get_cpu_usage() 239 return site_utils.compute_active_cpu_time(cpu_usage_start, 240 cpu_usage_end) * 100 241 if not utils.wait_for_idle_cpu(WAIT_FOR_IDLE_CPU_TIMEOUT, 242 CPU_IDLE_USAGE): 243 raise error.TestError('Could not get idle CPU.') 244 if not utils.wait_for_cool_machine(): 245 raise error.TestError('Could not get cool machine.') 246 # Stop the thermal service that may change the cpu frequency. 247 services = service_stopper.ServiceStopper(THERMAL_SERVICES) 248 services.stop_services() 249 # Set the scaling governor to performance mode to set the cpu to the 250 # highest frequency available. 251 original_governors = utils.set_high_performance_mode() 252 try: 253 return self.test_webrtc(local_path, get_cpu_usage) 254 finally: 255 services.restore_services() 256 utils.restore_scaling_governor_states(original_governors) 257 258 259 def test_power(self, local_path): 260 """ 261 Runs the video power consumption test. 262 263 @param local_path: the path to the video file. 264 265 @return a dictionary that contains the test result. 266 """ 267 # Verify that we are running on battery and the battery is sufficiently 268 # charged. 269 current_power_status = power_status.get_status() 270 current_power_status.assert_battery_state(BATTERY_INITIAL_CHARGED_MIN) 271 272 measurements = [power_status.SystemPower( 273 current_power_status.battery_path)] 274 275 def get_power(cr): 276 power_logger = power_status.PowerLogger(measurements) 277 power_logger.start() 278 time.sleep(STABILIZATION_DURATION) 279 start_time = time.time() 280 time.sleep(MEASUREMENT_DURATION) 281 power_logger.checkpoint('result', start_time) 282 keyval = power_logger.calc() 283 return keyval['result_' + measurements[0].domain + '_pwr'] 284 285 backlight = power_utils.Backlight() 286 backlight.set_default() 287 services = service_stopper.ServiceStopper( 288 service_stopper.ServiceStopper.POWER_DRAW_SERVICES) 289 services.stop_services() 290 try: 291 return self.test_webrtc(local_path, get_power) 292 finally: 293 backlight.restore() 294 services.restore_services() 295 296 297 def test_decode_time(self, local_path): 298 """ 299 Runs the decode time test. 300 301 @param local_path: the path to the video file. 302 303 @return a dictionary that contains the test result. 304 """ 305 def get_decode_time(cr): 306 tab = self.open_stats_page(cr) 307 # Collect the decode time until there are enough samples. 308 start_time = time.time() 309 max_decode_time_list = [] 310 decode_time_list = [] 311 while (time.time() - start_time < TIMEOUT and 312 len(decode_time_list) < NUM_DECODE_TIME_SAMPLES): 313 time.sleep(1) 314 max_decode_time_list = [] 315 decode_time_list = [] 316 try: 317 max_decode_time_list = [int(x) for x in 318 tab.EvaluateJavaScript(GOOG_MAX_DECODE_MS)] 319 decode_time_list = [int(x) for x in 320 tab.EvaluateJavaScript(GOOG_DECODE_MS)] 321 except: 322 pass 323 # Output the values if they are valid. 324 if len(max_decode_time_list) < NUM_DECODE_TIME_SAMPLES: 325 raise error.TestError('Not enough ' + GOOG_MAX_DECODE_MS) 326 if len(decode_time_list) < NUM_DECODE_TIME_SAMPLES: 327 raise error.TestError('Not enough ' + GOOG_DECODE_MS) 328 max_decode_time = max(max_decode_time_list) 329 decode_time_median = self.get_median(decode_time_list) 330 logging.info("Max decode time list=%s", str(max_decode_time_list)) 331 logging.info("Decode time list=%s", str(decode_time_list)) 332 logging.info("Maximum decode time=%d, median=%d", max_decode_time, 333 decode_time_median) 334 return (max_decode_time, decode_time_median) 335 336 return self.test_webrtc(local_path, get_decode_time) 337 338 339 def get_median(self, seq): 340 """ 341 Calculates the median of a sequence of numbers. 342 343 @param seq: a list with numbers. 344 345 @returns the median of the numbers. 346 """ 347 seq.sort() 348 size = len(seq) 349 if size % 2 != 0: 350 return seq[size / 2] 351 return (seq[size / 2] + seq[size / 2 - 1]) / 2.0 352 353 def log_result(self, keyvals, description, units): 354 """ 355 Logs the test result output to the performance dashboard. 356 357 @param keyvals: a dictionary that contains results returned by 358 test_webrtc. 359 @param description: a string that describes the video and test result 360 and it will be part of the entry name in the dashboard. 361 @param units: the units of test result. 362 """ 363 result_with_hw = keyvals.get(WEBRTC_WITH_HW_ACCELERATION) 364 if result_with_hw: 365 self.output_perf_value( 366 description= 'hw_' + description, value=result_with_hw, 367 units=units, higher_is_better=False) 368 369 result_without_hw = keyvals.get(WEBRTC_WITHOUT_HW_ACCELERATION) 370 if result_without_hw: 371 self.output_perf_value( 372 description= 'sw_' + description, value=result_without_hw, 373 units=units, higher_is_better=False) 374