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 collections 6import logging 7import os 8import time 9 10from autotest_lib.client.bin import test, utils 11from autotest_lib.client.common_lib import error 12from autotest_lib.client.common_lib import file_utils 13from autotest_lib.client.common_lib.cros import chrome 14from autotest_lib.client.cros import service_stopper 15from autotest_lib.client.cros.power import power_rapl 16from autotest_lib.client.cros.power import power_status 17from autotest_lib.client.cros.power import power_utils 18from autotest_lib.client.cros.video import histogram_verifier 19from autotest_lib.client.cros.video import constants 20from autotest_lib.client.cros.video import helper_logger 21 22 23DISABLE_ACCELERATED_VIDEO_DECODE_BROWSER_ARGS = [ 24 '--disable-accelerated-video-decode'] 25DOWNLOAD_BASE = 'http://commondatastorage.googleapis.com/chromiumos-test-assets-public/' 26 27PLAYBACK_WITH_HW_ACCELERATION = 'playback_with_hw_acceleration' 28PLAYBACK_WITHOUT_HW_ACCELERATION = 'playback_without_hw_acceleration' 29 30# Measurement duration in seconds. 31MEASUREMENT_DURATION = 30 32# Time to exclude from calculation after playing a video [seconds]. 33STABILIZATION_DURATION = 10 34 35# List of thermal throttling services that should be disabled. 36# - temp_metrics for link. 37# - thermal for daisy, snow, pit etc. 38THERMAL_SERVICES = ['temp_metrics', 'thermal'] 39 40# Time in seconds to wait for cpu idle until giveup. 41WAIT_FOR_IDLE_CPU_TIMEOUT = 60.0 42# Maximum percent of cpu usage considered as idle. 43CPU_IDLE_USAGE = 0.1 44 45CPU_USAGE_DESCRIPTION = 'video_cpu_usage_' 46DROPPED_FRAMES_DESCRIPTION = 'video_dropped_frames_' 47DROPPED_FRAMES_PERCENT_DESCRIPTION = 'video_dropped_frames_percent_' 48POWER_DESCRIPTION = 'video_mean_energy_rate_' 49RAPL_GRAPH_NAME = 'rapl_power_consumption' 50 51# Minimum battery charge percentage to run the test 52BATTERY_INITIAL_CHARGED_MIN = 10 53 54 55class video_PlaybackPerf(test.test): 56 """ 57 The test outputs the cpu usage, the dropped frame count and the power 58 consumption for video playback to performance dashboard. 59 """ 60 version = 1 61 arc_mode = None 62 63 64 def initialize(self): 65 self._service_stopper = None 66 self._original_governors = None 67 self._backlight = None 68 69 70 def start_playback(self, cr, local_path): 71 """ 72 Opens the video and plays it. 73 74 @param cr: Autotest Chrome instance. 75 @param local_path: path to the local video file to play. 76 """ 77 cr.browser.platform.SetHTTPServerDirectories(self.bindir) 78 79 tab = cr.browser.tabs[0] 80 tab.Navigate(cr.browser.platform.http_server.UrlOf(local_path)) 81 tab.WaitForDocumentReadyStateToBeComplete() 82 tab.EvaluateJavaScript("document.getElementsByTagName('video')[0]." 83 "loop=true") 84 85 86 @helper_logger.video_log_wrapper 87 def run_once(self, video_name, video_description, power_test=False, 88 arc_mode=None): 89 """ 90 Runs the video_PlaybackPerf test. 91 92 @param video_name: the name of video to play in the DOWNLOAD_BASE 93 @param video_description: a string describes the video to play which 94 will be part of entry name in dashboard. 95 @param power_test: True if this is a power test and it would only run 96 the power test. If False, it would run the cpu usage test and 97 the dropped frame count test. 98 @param arc_mode: if 'enabled', run the test with Android enabled. 99 """ 100 # Download test video. 101 url = DOWNLOAD_BASE + video_name 102 local_path = os.path.join(self.bindir, os.path.basename(video_name)) 103 logging.info("Downloading %s to %s", url, local_path); 104 file_utils.download_file(url, local_path) 105 self.arc_mode = arc_mode 106 107 if not power_test: 108 # Run the video playback dropped frame tests. 109 keyvals = self.test_dropped_frames(local_path) 110 111 # Every dictionary value is a tuple. The first element of the tuple 112 # is dropped frames. The second is dropped frames percent. 113 keyvals_dropped_frames = {k: v[0] for k, v in keyvals.iteritems()} 114 keyvals_dropped_frames_percent = { 115 k: v[1] for k, v in keyvals.iteritems()} 116 117 self.log_result(keyvals_dropped_frames, DROPPED_FRAMES_DESCRIPTION + 118 video_description, 'frames') 119 self.log_result(keyvals_dropped_frames_percent, 120 DROPPED_FRAMES_PERCENT_DESCRIPTION + 121 video_description, 'percent') 122 123 # Run the video playback cpu usage tests. 124 keyvals = self.test_cpu_usage(local_path) 125 self.log_result(keyvals, CPU_USAGE_DESCRIPTION + video_description, 126 'percent') 127 else: 128 tmp = self.test_power(local_path) 129 # Power measurement with rapl(4 domains) and system drain are 130 # reported. Reformat the data to align with the log_result. 131 keyvals = collections.defaultdict(dict) 132 for top_key in tmp.keys(): 133 for key in tmp[top_key].keys(): 134 keyvals[key][top_key] = tmp[top_key][key] 135 136 for key in keyvals: 137 rapl_type = [keyword for keyword in power_rapl.VALID_DOMAINS 138 if keyword in key] 139 if rapl_type: 140 description = "%s_%s_pwr" % (video_description, 141 rapl_type[0]) 142 self.log_result(keyvals[key], description, 'W', 143 graph=RAPL_GRAPH_NAME) 144 else: 145 self.log_result(keyvals[key], 146 POWER_DESCRIPTION + video_description, 'W') 147 148 149 def test_dropped_frames(self, local_path): 150 """ 151 Runs the video dropped frame test. 152 153 @param local_path: the path to the video file. 154 155 @return a dictionary that contains the test result. 156 """ 157 def get_dropped_frames(cr): 158 time.sleep(MEASUREMENT_DURATION) 159 tab = cr.browser.tabs[0] 160 decoded_frame_count = tab.EvaluateJavaScript( 161 "document.getElementsByTagName" 162 "('video')[0].webkitDecodedFrameCount") 163 dropped_frame_count = tab.EvaluateJavaScript( 164 "document.getElementsByTagName" 165 "('video')[0].webkitDroppedFrameCount") 166 if decoded_frame_count != 0: 167 dropped_frame_percent = \ 168 100.0 * dropped_frame_count / decoded_frame_count 169 else: 170 logging.error("No frame is decoded. Set drop percent to 100.") 171 dropped_frame_percent = 100.0 172 logging.info("Decoded frames=%d, dropped frames=%d, percent=%f", 173 decoded_frame_count, 174 dropped_frame_count, 175 dropped_frame_percent) 176 return (dropped_frame_count, dropped_frame_percent) 177 return self.test_playback(local_path, get_dropped_frames) 178 179 180 def test_cpu_usage(self, local_path): 181 """ 182 Runs the video cpu usage test. 183 184 @param local_path: the path to the video file. 185 186 @return a dictionary that contains the test result. 187 """ 188 def get_cpu_usage(cr): 189 time.sleep(STABILIZATION_DURATION) 190 cpu_usage_start = utils.get_cpu_usage() 191 time.sleep(MEASUREMENT_DURATION) 192 cpu_usage_end = utils.get_cpu_usage() 193 return utils.compute_active_cpu_time(cpu_usage_start, 194 cpu_usage_end) * 100 195 196 # crbug/753292 - APNG login pictures increase CPU usage. Move the more 197 # strict idle checks after the login phase. 198 if not utils.wait_for_idle_cpu(WAIT_FOR_IDLE_CPU_TIMEOUT, 199 CPU_IDLE_USAGE): 200 logging.warning('Could not get idle CPU pre login.') 201 if not utils.wait_for_cool_machine(): 202 logging.warning('Could not get cold machine pre login.') 203 204 # Stop the thermal service that may change the cpu frequency. 205 self._service_stopper = service_stopper.ServiceStopper(THERMAL_SERVICES) 206 self._service_stopper.stop_services() 207 # Set the scaling governor to performance mode to set the cpu to the 208 # highest frequency available. 209 self._original_governors = utils.set_high_performance_mode() 210 return self.test_playback(local_path, get_cpu_usage) 211 212 213 def test_power(self, local_path): 214 """ 215 Runs the video power consumption test. 216 217 @param local_path: the path to the video file. 218 219 @return a dictionary that contains the test result. 220 """ 221 222 self._backlight = power_utils.Backlight() 223 self._backlight.set_default() 224 225 self._service_stopper = service_stopper.ServiceStopper( 226 service_stopper.ServiceStopper.POWER_DRAW_SERVICES) 227 self._service_stopper.stop_services() 228 229 self._power_status = power_status.get_status() 230 # We expect the DUT is powered by battery now. But this is not always 231 # true due to other bugs. Disable this test temporarily as workaround. 232 # TODO(kcwu): remove this workaround after AC control is stable 233 # crbug.com/723968 234 if self._power_status.on_ac(): 235 logging.warning('Still powered by AC. Skip this test') 236 return {} 237 # Verify that the battery is sufficiently charged. 238 self._power_status.assert_battery_state(BATTERY_INITIAL_CHARGED_MIN) 239 240 measurements = [power_status.SystemPower( 241 self._power_status.battery_path)] 242 if power_utils.has_rapl_support(): 243 measurements += power_rapl.create_rapl() 244 245 def get_power(cr): 246 power_logger = power_status.PowerLogger(measurements) 247 power_logger.start() 248 time.sleep(STABILIZATION_DURATION) 249 start_time = time.time() 250 time.sleep(MEASUREMENT_DURATION) 251 power_logger.checkpoint('result', start_time) 252 keyval = power_logger.calc() 253 keyval = {key: keyval[key] 254 for key in keyval if key.endswith('_pwr')} 255 return keyval 256 257 return self.test_playback(local_path, get_power) 258 259 260 def test_playback(self, local_path, gather_result): 261 """ 262 Runs the video playback test with and without hardware acceleration. 263 264 @param local_path: the path to the video file. 265 @param gather_result: a function to run and return the test result 266 after chrome opens. The input parameter of the funciton is 267 Autotest chrome instance. 268 269 @return a dictionary that contains test the result. 270 """ 271 keyvals = {} 272 273 with chrome.Chrome( 274 extra_browser_args=helper_logger.chrome_vmodule_flag(), 275 arc_mode=self.arc_mode, 276 init_network_controller=True) as cr: 277 278 # crbug/753292 - enforce the idle checks after login 279 if not utils.wait_for_idle_cpu(WAIT_FOR_IDLE_CPU_TIMEOUT, 280 CPU_IDLE_USAGE): 281 logging.warning('Could not get idle CPU post login.') 282 if not utils.wait_for_cool_machine(): 283 logging.warning('Could not get cold machine post login.') 284 285 # Open the video playback page and start playing. 286 self.start_playback(cr, local_path) 287 result = gather_result(cr) 288 289 # Check if decode is hardware accelerated. 290 if histogram_verifier.is_bucket_present( 291 cr, 292 constants.MEDIA_GVD_INIT_STATUS, 293 constants.MEDIA_GVD_BUCKET): 294 keyvals[PLAYBACK_WITH_HW_ACCELERATION] = result 295 else: 296 logging.info("Can not use hardware decoding.") 297 keyvals[PLAYBACK_WITHOUT_HW_ACCELERATION] = result 298 return keyvals 299 300 # Start chrome with disabled video hardware decode flag. 301 with chrome.Chrome(extra_browser_args= 302 DISABLE_ACCELERATED_VIDEO_DECODE_BROWSER_ARGS, 303 arc_mode=self.arc_mode, init_network_controller=True) as cr: 304 # Open the video playback page and start playing. 305 self.start_playback(cr, local_path) 306 result = gather_result(cr) 307 308 # Make sure decode is not hardware accelerated. 309 if histogram_verifier.is_bucket_present( 310 cr, 311 constants.MEDIA_GVD_INIT_STATUS, 312 constants.MEDIA_GVD_BUCKET): 313 raise error.TestError( 314 'Video decode acceleration should not be working.') 315 keyvals[PLAYBACK_WITHOUT_HW_ACCELERATION] = result 316 317 return keyvals 318 319 320 def log_result(self, keyvals, description, units, graph=None): 321 """ 322 Logs the test result output to the performance dashboard. 323 324 @param keyvals: a dictionary that contains results returned by 325 test_playback. 326 @param description: a string that describes the video and test result 327 and it will be part of the entry name in the dashboard. 328 @param units: the units of test result. 329 @param graph: a string that indicates which graph should the result 330 belongs to. 331 """ 332 result_with_hw = keyvals.get(PLAYBACK_WITH_HW_ACCELERATION) 333 if result_with_hw is not None: 334 self.output_perf_value( 335 description= 'hw_' + description, value=result_with_hw, 336 units=units, higher_is_better=False, graph=graph) 337 338 result_without_hw = keyvals.get(PLAYBACK_WITHOUT_HW_ACCELERATION) 339 if result_without_hw is not None: 340 self.output_perf_value( 341 description= 'sw_' + description, value=result_without_hw, 342 units=units, higher_is_better=False, graph=graph) 343 344 345 def cleanup(self): 346 # cleanup() is run by common_lib/test.py. 347 if self._backlight: 348 self._backlight.restore() 349 if self._service_stopper: 350 self._service_stopper.restore_services() 351 if self._original_governors: 352 utils.restore_scaling_governor_states(self._original_governors) 353 354 super(video_PlaybackPerf, self).cleanup() 355