Home | History | Annotate | Download | only in video_VEAPerf
      1 # Copyright 2015 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 errno
      6 import hashlib
      7 import logging
      8 import math
      9 import mmap
     10 import os
     11 import re
     12 
     13 from contextlib import closing
     14 
     15 from autotest_lib.client.bin import utils
     16 from autotest_lib.client.common_lib import error
     17 from autotest_lib.client.common_lib import file_utils
     18 from autotest_lib.client.cros import chrome_binary_test
     19 from autotest_lib.client.cros.video import helper_logger
     20 
     21 
     22 DOWNLOAD_BASE = ('http://commondatastorage.googleapis.com'
     23                  '/chromiumos-test-assets-public/')
     24 
     25 VEA_BINARY = 'video_encode_accelerator_unittest'
     26 TIME_BINARY = '/usr/local/bin/time'
     27 
     28 # The format used for 'time': <real time> <kernel time> <user time>
     29 TIME_OUTPUT_FORMAT = '%e %S %U'
     30 
     31 TEST_LOG_SUFFIX = 'test.log'
     32 TIME_LOG_SUFFIX = 'time.log'
     33 
     34 # Performance keys:
     35 # FPS (i.e. encoder throughput)
     36 KEY_FPS = 'fps'
     37 # Encode latencies at the 50th, 75th, and 95th percentiles.
     38 # Encode latency is the delay from input of a frame to output of the encoded
     39 # bitstream.
     40 KEY_ENCODE_LATENCY_50 = 'encode_latency.50_percentile'
     41 KEY_ENCODE_LATENCY_75 = 'encode_latency.75_percentile'
     42 KEY_ENCODE_LATENCY_95 = 'encode_latency.95_percentile'
     43 # CPU usage in kernel space
     44 KEY_CPU_KERNEL_USAGE = 'cpu_usage.kernel'
     45 # CPU usage in user space
     46 KEY_CPU_USER_USAGE = 'cpu_usage.user'
     47 
     48 # Units of performance values:
     49 # (These strings should match chromium/src/tools/perf/unit-info.json.)
     50 UNIT_MILLISECOND = 'milliseconds'
     51 UNIT_MICROSECOND = 'us'
     52 UNIT_PERCENT = 'percent'
     53 UNIT_FPS = 'fps'
     54 
     55 RE_FPS = re.compile(r'^Measured encoder FPS: ([+\-]?[0-9.]+)$', re.MULTILINE)
     56 RE_ENCODE_LATENCY_50 = re.compile(
     57     r'^Encode latency for the 50th percentile: (\d+) us$',
     58     re.MULTILINE)
     59 RE_ENCODE_LATENCY_75 = re.compile(
     60     r'^Encode latency for the 75th percentile: (\d+) us$',
     61     re.MULTILINE)
     62 RE_ENCODE_LATENCY_95 = re.compile(
     63     r'^Encode latency for the 95th percentile: (\d+) us$',
     64     re.MULTILINE)
     65 
     66 
     67 def _remove_if_exists(filepath):
     68     try:
     69         os.remove(filepath)
     70     except OSError, e:
     71         if e.errno != errno.ENOENT:  # no such file
     72             raise
     73 
     74 
     75 class video_VEAPerf(chrome_binary_test.ChromeBinaryTest):
     76     """
     77     This test monitors several performance metrics reported by Chrome test
     78     binary, video_encode_accelerator_unittest.
     79     """
     80 
     81     version = 1
     82 
     83     def _logperf(self, test_name, key, value, units, higher_is_better=False):
     84         description = '%s.%s' % (test_name, key)
     85         self.output_perf_value(
     86                 description=description, value=value, units=units,
     87                 higher_is_better=higher_is_better)
     88 
     89 
     90     def _analyze_fps(self, test_name, log_file):
     91         """
     92         Analyzes FPS info from result log file.
     93         """
     94         with open(log_file, 'r') as f:
     95             mm = mmap.mmap(f.fileno(), 0, access=mmap.ACCESS_READ)
     96             fps = [float(m.group(1)) for m in RE_FPS.finditer(mm)]
     97             mm.close()
     98         if len(fps) != 1:
     99             raise error.TestError('Parsing FPS failed w/ %d occurrence(s).' %
    100                                   len(fps))
    101         self._logperf(test_name, KEY_FPS, fps[0], UNIT_FPS, True)
    102 
    103 
    104     def _analyze_encode_latency(self, test_name, log_file):
    105         """
    106         Analyzes encode latency from result log file.
    107         """
    108         with open(log_file, 'r') as f:
    109             mm = mmap.mmap(f.fileno(), 0, access=mmap.ACCESS_READ)
    110             latency_50 = [int(m.group(1)) for m in
    111                           RE_ENCODE_LATENCY_50.finditer(mm)]
    112             latency_75 = [int(m.group(1)) for m in
    113                           RE_ENCODE_LATENCY_75.finditer(mm)]
    114             latency_95 = [int(m.group(1)) for m in
    115                           RE_ENCODE_LATENCY_95.finditer(mm)]
    116             mm.close()
    117         if any([len(l) != 1 for l in [latency_50, latency_75, latency_95]]):
    118             raise error.TestError('Parsing encode latency failed.')
    119         self._logperf(test_name, KEY_ENCODE_LATENCY_50, latency_50[0],
    120                       UNIT_MICROSECOND)
    121         self._logperf(test_name, KEY_ENCODE_LATENCY_75, latency_75[0],
    122                       UNIT_MICROSECOND)
    123         self._logperf(test_name, KEY_ENCODE_LATENCY_95, latency_95[0],
    124                       UNIT_MICROSECOND)
    125 
    126 
    127     def _analyze_cpu_usage(self, test_name, time_log_file):
    128         """
    129         Analyzes CPU usage from the output of 'time' command.
    130         """
    131         with open(time_log_file) as f:
    132             content = f.read()
    133         r, s, u = (float(x) for x in content.split())
    134         self._logperf(test_name, KEY_CPU_USER_USAGE, u / r, UNIT_PERCENT)
    135         self._logperf(test_name, KEY_CPU_KERNEL_USAGE, s / r, UNIT_PERCENT)
    136 
    137 
    138     def _get_profile_name(self, profile):
    139         """
    140         Gets profile name from a profile index.
    141         """
    142         if profile == 1:
    143             return 'h264'
    144         elif profile == 11:
    145             return 'vp8'
    146         else:
    147             raise error.TestError('Internal error.')
    148 
    149 
    150     def _convert_test_name(self, path, on_cloud, profile):
    151         """Converts source path to test name and output video file name.
    152 
    153         For example: for the path on cloud
    154             "tulip2/tulip2-1280x720-1b95123232922fe0067869c74e19cd09.yuv"
    155 
    156         We will derive the test case's name as "tulip2-1280x720.vp8" or
    157         "tulip2-1280x720.h264" depending on the profile. The MD5 checksum in
    158         path will be stripped.
    159 
    160         For the local file, we use the base name directly.
    161 
    162         @param path: The local path or download path.
    163         @param on_cloud: Whether the file is on cloud.
    164         @param profile: Profile index.
    165 
    166         @returns a pair of (test name, output video file name)
    167         """
    168         s = os.path.basename(path)
    169         name = s[:s.rfind('-' if on_cloud else '.')]
    170         profile_name = self._get_profile_name(profile)
    171         return (name + '_' + profile_name, name + '.' + profile_name)
    172 
    173 
    174     def _download_video(self, path_on_cloud, local_file):
    175         url = '%s%s' % (DOWNLOAD_BASE, path_on_cloud)
    176         logging.info('download "%s" to "%s"', url, local_file)
    177 
    178         file_utils.download_file(url, local_file)
    179 
    180         with open(local_file, 'r') as r:
    181             md5sum = hashlib.md5(r.read()).hexdigest()
    182             if md5sum not in path_on_cloud:
    183                 raise error.TestError('unmatched md5 sum: %s' % md5sum)
    184 
    185 
    186     def _get_result_filename(self, test_name, subtype, suffix):
    187         return os.path.join(self.resultsdir,
    188                             '%s_%s_%s' % (test_name, subtype, suffix))
    189 
    190 
    191     def _run_test_case(self, test_name, test_stream_data):
    192         """
    193         Runs a VEA unit test.
    194 
    195         @param test_name: Name of this test case.
    196         @param test_stream_data: Parameter to --test_stream_data in vea_unittest.
    197         """
    198         # Get FPS.
    199         test_log_file = self._get_result_filename(test_name, 'fullspeed',
    200                                                   TEST_LOG_SUFFIX)
    201         vea_args = [
    202             '--gtest_filter=EncoderPerf/*/0',
    203             '--test_stream_data=%s' % test_stream_data,
    204             '--output_log="%s"' % test_log_file,
    205             helper_logger.chrome_vmodule_flag(),
    206             '--ozone-platform=gbm']
    207         self.run_chrome_test_binary(VEA_BINARY, ' '.join(vea_args))
    208         self._analyze_fps(test_name, test_log_file)
    209 
    210         # Get CPU usage and encode latency under specified frame rate.
    211         test_log_file = self._get_result_filename(test_name, 'fixedspeed',
    212                                                   TEST_LOG_SUFFIX)
    213         time_log_file = self._get_result_filename(test_name, 'fixedspeed',
    214                                                   TIME_LOG_SUFFIX)
    215         vea_args = [
    216             '--gtest_filter=SimpleEncode/*/0',
    217             '--test_stream_data=%s' % test_stream_data,
    218             '--run_at_fps', '--measure_latency',
    219             '--output_log="%s"' % test_log_file,
    220             helper_logger.chrome_vmodule_flag(),
    221             '--ozone-platform=gbm']
    222         time_cmd = ('%s -f "%s" -o "%s" ' %
    223                     (TIME_BINARY, TIME_OUTPUT_FORMAT, time_log_file))
    224         self.run_chrome_test_binary(VEA_BINARY, ' '.join(vea_args),
    225                                     prefix=time_cmd)
    226         self._analyze_encode_latency(test_name, test_log_file)
    227         self._analyze_cpu_usage(test_name, time_log_file)
    228 
    229     @helper_logger.video_log_wrapper
    230     @chrome_binary_test.nuke_chrome
    231     def run_once(self, test_cases):
    232         last_error = None
    233         for (path, on_cloud, width, height, requested_bit_rate,
    234              profile, requested_frame_rate) in test_cases:
    235             try:
    236                 test_name, output_name = self._convert_test_name(
    237                     path, on_cloud, profile)
    238                 if on_cloud:
    239                     input_path = os.path.join(self.tmpdir,
    240                                               os.path.basename(path))
    241                     self._download_video(path, input_path)
    242                 else:
    243                     input_path = os.path.join(self.cr_source_dir, path)
    244                 output_path = os.path.join(self.tmpdir, output_name)
    245                 test_stream_data = '%s:%s:%s:%s:%s:%s:%s' % (
    246                     input_path, width, height, profile, output_path,
    247                     requested_bit_rate, requested_frame_rate)
    248                 self._run_test_case(test_name, test_stream_data)
    249             except Exception as last_error:
    250                 # Log the error and continue to the next test case.
    251                 logging.exception(last_error)
    252             finally:
    253                 if on_cloud:
    254                     _remove_if_exists(input_path)
    255                 _remove_if_exists(output_path)
    256 
    257         if last_error:
    258             raise last_error
    259