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