Home | History | Annotate | Download | only in video_PlaybackPerf
      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