Home | History | Annotate | Download | only in video_HangoutHardwarePerf
      1 # Copyright 2014 The Chromium 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 contextlib, hashlib, logging, os, pipes, re, sys, time, tempfile
      6 
      7 from autotest_lib.client.bin import utils
      8 from autotest_lib.client.common_lib import error
      9 from autotest_lib.client.common_lib import file_utils
     10 from autotest_lib.client.cros import chrome_binary_test
     11 from autotest_lib.client.cros import power_status, power_utils
     12 from autotest_lib.client.cros import service_stopper
     13 from autotest_lib.client.cros.audio import cmd_utils
     14 
     15 # The download base for test assets.
     16 DOWNLOAD_BASE = ('http://commondatastorage.googleapis.com'
     17                  '/chromiumos-test-assets-public/')
     18 
     19 # The executable name of the vda unittest
     20 VDA_BINARY = 'video_decode_accelerator_unittest'
     21 
     22 # The executable name of the vea unittest
     23 VEA_BINARY = 'video_encode_accelerator_unittest'
     24 
     25 # The input frame rate for the vea_unittest.
     26 INPUT_FPS = 30
     27 
     28 # The rendering fps in the vda_unittest.
     29 RENDERING_FPS = 30
     30 
     31 # The unit(s) should match chromium/src/tools/perf/unit-info.json.
     32 UNIT_PERCENT = '%'
     33 UNIT_WATT = 'W'
     34 
     35 # The regex of the versioning file.
     36 # e.g., crowd720-3cfe7b096f765742b4aa79e55fe7c994.yuv
     37 RE_VERSIONING_FILE = re.compile(r'(.+)-([0-9a-fA-F]{32})(\..+)?')
     38 
     39 # Time in seconds to wait for cpu idle until giveup.
     40 WAIT_FOR_IDLE_CPU_TIMEOUT = 60
     41 
     42 # Maximum percent of cpu usage considered as idle.
     43 CPU_IDLE_USAGE = 0.1
     44 
     45 # List of thermal throttling services that should be disabled.
     46 # - temp_metrics for link.
     47 # - thermal for daisy, snow, pit etc.
     48 THERMAL_SERVICES = ['temp_metrics', 'thermal']
     49 
     50 # Measurement duration in seconds.
     51 MEASUREMENT_DURATION = 30
     52 
     53 # Time to exclude from calculation after playing a video [seconds].
     54 STABILIZATION_DURATION = 10
     55 
     56 # The number of frames used to warm up the rendering.
     57 RENDERING_WARM_UP = 15
     58 
     59 # A big number, used to keep the [vda|vea]_unittest running during the
     60 # measurement.
     61 MAX_INT = 2 ** 31 - 1
     62 
     63 # Minimum battery charge percentage to run the test
     64 BATTERY_INITIAL_CHARGED_MIN = 10
     65 
     66 
     67 class CpuUsageMeasurer(object):
     68     """ Class used to measure the CPU usage."""
     69 
     70     def __init__(self):
     71         self._service_stopper = None
     72         self._original_governors = None
     73 
     74     def __enter__(self):
     75         # Stop the thermal service that may change the cpu frequency.
     76         self._service_stopper = service_stopper.ServiceStopper(THERMAL_SERVICES)
     77         self._service_stopper.stop_services()
     78 
     79         if not utils.wait_for_idle_cpu(
     80                 WAIT_FOR_IDLE_CPU_TIMEOUT, CPU_IDLE_USAGE):
     81             raise error.TestError('Could not get idle CPU.')
     82         if not utils.wait_for_cool_machine():
     83             raise error.TestError('Could not get cold machine.')
     84 
     85         # Set the scaling governor to performance mode to set the cpu to the
     86         # highest frequency available.
     87         self._original_governors = utils.set_high_performance_mode()
     88         return self
     89 
     90     def start(self):
     91         self.start_cpu_usage_ = utils.get_cpu_usage()
     92 
     93     def stop(self):
     94         return utils.compute_active_cpu_time(
     95                 self.start_cpu_usage_, utils.get_cpu_usage())
     96 
     97     def __exit__(self, type, value, tb):
     98         if self._service_stopper:
     99             self._service_stopper.restore_services()
    100             self._service_stopper = None
    101         if self._original_governors:
    102             utils.restore_scaling_governor_states(self._original_governors)
    103             self._original_governors = None
    104 
    105 
    106 class PowerMeasurer(object):
    107     """ Class used to measure the power consumption."""
    108 
    109     def __init__(self):
    110         self._backlight = None
    111         self._service_stopper = None
    112 
    113     def __enter__(self):
    114         self._backlight = power_utils.Backlight()
    115         self._backlight.set_default()
    116 
    117         self._service_stopper = service_stopper.ServiceStopper(
    118                 service_stopper.ServiceStopper.POWER_DRAW_SERVICES)
    119         self._service_stopper.stop_services()
    120 
    121         status = power_status.get_status()
    122 
    123         # Verify that we are running on battery and the battery is sufficiently
    124         # charged.
    125         status.assert_battery_state(BATTERY_INITIAL_CHARGED_MIN)
    126         self._system_power = power_status.SystemPower(status.battery_path)
    127         self._power_logger = power_status.PowerLogger([self._system_power])
    128         return self
    129 
    130     def start(self):
    131         self._power_logger.start()
    132 
    133     def stop(self):
    134         self._power_logger.checkpoint('result')
    135         keyval = self._power_logger.calc()
    136         logging.info(keyval)
    137         return keyval['result_' + self._system_power.domain + '_pwr']
    138 
    139     def __exit__(self, type, value, tb):
    140         if self._backlight:
    141             self._backlight.restore()
    142         if self._service_stopper:
    143             self._service_stopper.restore_services()
    144 
    145 
    146 class DownloadManager(object):
    147     """Use this class to download and manage the resources for testing."""
    148 
    149     def __init__(self, tmpdir=None):
    150         self._download_map = {}
    151         self._tmpdir = tmpdir
    152 
    153     def get_path(self, name):
    154         return self._download_map[name]
    155 
    156     def clear(self):
    157         map(os.unlink, self._download_map.values())
    158         self._download_map.clear()
    159 
    160     def _download_single_file(self, remote_path):
    161         url = DOWNLOAD_BASE + remote_path
    162         tmp = tempfile.NamedTemporaryFile(delete=False, dir=self._tmpdir)
    163         logging.info('download "%s" to "%s"', url, tmp.name)
    164 
    165         file_utils.download_file(url, tmp.name)
    166         md5 = hashlib.md5()
    167         with open(tmp.name, 'r') as r:
    168             md5.update(r.read())
    169 
    170         filename = os.path.basename(remote_path)
    171         m = RE_VERSIONING_FILE.match(filename)
    172         if m:
    173             prefix, md5sum, suffix = m.groups()
    174             if md5.hexdigest() != md5sum:
    175                 raise error.TestError(
    176                         'unmatched md5 sum: %s' % md5.hexdigest())
    177             filename = prefix + (suffix or '')
    178         self._download_map[filename] = tmp.name
    179 
    180     def download_all(self, resources):
    181         for r in resources:
    182             self._download_single_file(r)
    183 
    184 
    185 class video_HangoutHardwarePerf(chrome_binary_test.ChromeBinaryTest):
    186     """
    187     The test outputs the cpu usage when doing video encoding and video
    188     decoding concurrently.
    189     """
    190 
    191     version = 1
    192 
    193     def get_vda_unittest_cmd_line(self, decode_videos):
    194         test_video_data = []
    195         for v in decode_videos:
    196             assert len(v) == 6
    197             # Convert to strings, also make a copy of the list.
    198             v = map(str, v)
    199             v[0] = self._downloads.get_path(v[0])
    200             v[-1:-1] = ['0', '0'] # no fps requirements
    201             test_video_data.append(':'.join(v))
    202         cmd_line = [
    203             self.get_chrome_binary_path(VDA_BINARY),
    204             '--gtest_filter=DecodeVariations/*/0',
    205             '--test_video_data=%s' % ';'.join(test_video_data),
    206             '--rendering_warm_up=%d' % RENDERING_WARM_UP,
    207             '--rendering_fps=%f' % RENDERING_FPS,
    208             '--num_play_throughs=%d' % MAX_INT]
    209         if utils.is_freon():
    210             cmd_line.append('--ozone-platform=gbm')
    211         return cmd_line
    212 
    213 
    214     def get_vea_unittest_cmd_line(self, encode_videos):
    215         test_stream_data = []
    216         for v in encode_videos:
    217             assert len(v) == 5
    218             # Convert to strings, also make a copy of the list.
    219             v = map(str, v)
    220             v[0] = self._downloads.get_path(v[0])
    221             # The output destination, ignore the output.
    222             v.insert(4, '/dev/null')
    223             # Insert the FPS requirement
    224             v.append(str(INPUT_FPS))
    225             test_stream_data.append(':'.join(v))
    226         cmd_line = [
    227             self.get_chrome_binary_path(VEA_BINARY),
    228             '--gtest_filter=SimpleEncode/*/0',
    229             '--test_stream_data=%s' % ';'.join(test_stream_data),
    230             '--run_at_fps',
    231             '--num_frames_to_encode=%d' % MAX_INT]
    232         if utils.is_freon():
    233             cmd_line.append('--ozone-platform=gbm')
    234         return cmd_line
    235 
    236     def run_in_parallel(self, *commands):
    237         env = os.environ.copy()
    238 
    239         # To clear the temparory files created by vea_unittest.
    240         env['TMPDIR'] = self.tmpdir
    241         if not utils.is_freon():
    242             env['DISPLAY'] = ':0'
    243             env['XAUTHORITY'] = '/home/chronos/.Xauthority'
    244         return map(lambda c: cmd_utils.popen(c, env=env), commands)
    245 
    246     def simulate_hangout(self, decode_videos, encode_videos, measurer):
    247         popens = self.run_in_parallel(
    248             self.get_vda_unittest_cmd_line(decode_videos),
    249             self.get_vea_unittest_cmd_line(encode_videos))
    250         try:
    251             time.sleep(STABILIZATION_DURATION)
    252             measurer.start()
    253             time.sleep(MEASUREMENT_DURATION)
    254             measurement = measurer.stop()
    255 
    256             # Ensure both encoding and decoding are still alive
    257             if any(p.poll() is not None for p in popens):
    258                 raise error.TestError('vea/vda_unittest failed')
    259 
    260             return measurement
    261         finally:
    262             cmd_utils.kill_or_log_returncode(*popens)
    263 
    264     @chrome_binary_test.nuke_chrome
    265     def run_once(self, resources, decode_videos, encode_videos, measurement):
    266         self._downloads = DownloadManager(tmpdir = self.tmpdir)
    267         try:
    268             self._downloads.download_all(resources)
    269             if measurement == 'cpu':
    270                 with CpuUsageMeasurer() as measurer:
    271                     value = self.simulate_hangout(
    272                             decode_videos, encode_videos, measurer)
    273                     self.output_perf_value(
    274                             description='cpu_usage', value=value * 100,
    275                             units=UNIT_PERCENT, higher_is_better=False)
    276             elif measurement == 'power':
    277                 with PowerMeasurer() as measurer:
    278                     value = self.simulate_hangout(
    279                             decode_videos, encode_videos, measurer)
    280                     self.output_perf_value(
    281                             description='power_usage', value=value,
    282                             units=UNIT_WATT, higher_is_better=False)
    283             else:
    284                 raise error.TestError('Unknown measurement: ' + measurement)
    285         finally:
    286             self._downloads.clear()
    287