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