普通文本  |  360行  |  13.56 KB

# Copyright (c) 2014 The Chromium OS Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

import logging
import os
import time

from autotest_lib.client.bin import site_utils, test, utils
from autotest_lib.client.common_lib import error
from autotest_lib.client.common_lib import file_utils
from autotest_lib.client.common_lib.cros import chrome
from autotest_lib.client.cros import power_status, power_utils
from autotest_lib.client.cros import service_stopper
from autotest_lib.client.cros.video import histogram_verifier
from autotest_lib.client.cros.video import constants


EXTRA_BROWSER_ARGS = ['--use-fake-device-for-media-stream',
                      '--use-fake-ui-for-media-stream']
FAKE_FILE_ARG = '--use-file-for-fake-video-capture="%s"'
DISABLE_ACCELERATED_VIDEO_DECODE_BROWSER_ARGS = [
        '--disable-accelerated-video-decode']

DOWNLOAD_BASE = ('http://commondatastorage.googleapis.com/'
                 'chromiumos-test-assets-public/crowd/')
VIDEO_NAME = 'crowd720_25frames.y4m'

WEBRTC_INTERNALS_URL = 'chrome://webrtc-internals'

WEBRTC_WITH_HW_ACCELERATION = 'webrtc_with_hw_acceleration'
WEBRTC_WITHOUT_HW_ACCELERATION = 'webrtc_without_hw_acceleration'

# Measurement duration in seconds.
MEASUREMENT_DURATION = 30
# Time to exclude from calculation after start the loopback [seconds].
STABILIZATION_DURATION = 10

# List of thermal throttling services that should be disabled.
# - temp_metrics for link.
# - thermal for daisy, snow, pit etc.
THERMAL_SERVICES = ['temp_metrics', 'thermal']

# Time in seconds to wait for cpu idle until giving up.
WAIT_FOR_IDLE_CPU_TIMEOUT = 60.0
# Maximum percent of cpu usage considered as idle.
CPU_IDLE_USAGE = 0.1

MAX_DECODE_TIME_DESCRIPTION = 'decode_time.max'
MEDIAN_DECODE_TIME_DESCRIPTION = 'decode_time.percentile_0.50'
CPU_USAGE_DESCRIPTION = 'video_cpu_usage'
POWER_DESCRIPTION = 'video_mean_energy_rate'

# Minimum battery charge percentage to run the power test.
BATTERY_INITIAL_CHARGED_MIN = 10

# These are the variable names in WebRTC internals.
# Maximum decode time of the frames of the last 10 seconds.
GOOG_MAX_DECODE_MS = 'googMaxDecodeMs'
# The decode time of the last frame.
GOOG_DECODE_MS = 'googDecodeMs'

# Javascript function to get the decode time. addStats is a function called by
# Chrome to pass WebRTC statistics every second.
ADD_STATS_JAVASCRIPT = (
        'var googMaxDecodeMs = new Array();'
        'var googDecodeMs = new Array();'
        'addStats = function(data) {'
        '  reports = data.reports;'
        '  for (var i=0; i < reports.length; i++) {'
        '    if (reports[i].type == "ssrc") {'
        '      values = reports[i].stats.values;'
        '      for (var j=0; j < values.length; j++) {'
        '        if (values[j] == "googMaxDecodeMs")'
        '          googMaxDecodeMs[googMaxDecodeMs.length] = values[j+1];'
        '        else if (values[j] == "googDecodeMs")'
        '          googDecodeMs[googDecodeMs.length] = values[j+1];'
        '      }'
        '    }'
        '  }'
        '}')

# Measure the stats until getting 10 samples or exceeding 15 seconds. addStats
# is called once per second for now.
NUM_DECODE_TIME_SAMPLES = 10
TIMEOUT = 15


class video_WebRtcPerf(test.test):
    """
    The test outputs the decode time, cpu usage and the power consumption for
    WebRTC to performance dashboard.
    """
    version = 1


    def start_loopback(self, cr):
        """
        Opens WebRTC loopback page.

        @param cr: Autotest Chrome instance.
        """
        cr.browser.platform.SetHTTPServerDirectories(self.bindir)

        tab = cr.browser.tabs[0]
        tab.Navigate(cr.browser.platform.http_server.UrlOf(
                os.path.join(self.bindir, 'loopback.html')))
        tab.WaitForDocumentReadyStateToBeComplete()


    def open_stats_page(self, cr):
        """
        Opens WebRTC internal statistics page.

        @param cr: Autotest Chrome instance.

        @returns the tab of the stats page.
        """
        tab = cr.browser.tabs.New()
        tab.Navigate(WEBRTC_INTERNALS_URL)
        tab.WaitForDocumentReadyStateToBeComplete()
        # Inject stats callback function.
        tab.EvaluateJavaScript(ADD_STATS_JAVASCRIPT)
        return tab


    def run_once(self, decode_time_test=False, cpu_test=False, power_test=False):
        """
        Runs the video_WebRtcPerf test.

        @param decode_time_test: Pass True to run decode time test.
        @param cpu_test: Pass True to run CPU usage test.
        @param power_test: Pass True to run power consumption test.
        """
        # Download test video.
        url = DOWNLOAD_BASE + VIDEO_NAME
        local_path = os.path.join(self.bindir, VIDEO_NAME)
        file_utils.download_file(url, local_path)

        if decode_time_test:
            keyvals = self.test_decode_time(local_path)
            # The first value is max decode time. The second value is median
            # decode time. Construct a dictionary containing one value to log
            # the result.
            max_decode_time = {
                    key:value[0] for (key, value) in keyvals.items()}
            self.log_result(max_decode_time, MAX_DECODE_TIME_DESCRIPTION,
                            'milliseconds')
            median_decode_time = {
                    key:value[1] for (key, value) in keyvals.items()}
            self.log_result(median_decode_time, MEDIAN_DECODE_TIME_DESCRIPTION,
                            'milliseconds')

        if cpu_test:
            keyvals = self.test_cpu_usage(local_path)
            self.log_result(keyvals, CPU_USAGE_DESCRIPTION, 'percent')

        if power_test:
            keyvals = self.test_power(local_path)
            self.log_result(keyvals, POWER_DESCRIPTION , 'W')


    def test_webrtc(self, local_path, gather_result):
        """
        Runs the webrtc test with and without hardware acceleration.

        @param local_path: the path to the video file.
        @param gather_result: a function to run and return the test result
                after chrome opens. The input parameter of the funciton is
                Autotest chrome instance.

        @return a dictionary that contains test the result.
        """
        keyvals = {}
        EXTRA_BROWSER_ARGS.append(FAKE_FILE_ARG % local_path)

        with chrome.Chrome(extra_browser_args=EXTRA_BROWSER_ARGS) as cr:
            # Open WebRTC loopback page and start the loopback.
            self.start_loopback(cr)
            result = gather_result(cr)

            # Check if decode is hardware accelerated.
            if histogram_verifier.is_bucket_present(
                    cr,
                    constants.RTC_INIT_HISTOGRAM,
                    constants.RTC_VIDEO_INIT_BUCKET):
                keyvals[WEBRTC_WITH_HW_ACCELERATION] = result
            else:
                logging.info("Can not use hardware decoding.")
                keyvals[WEBRTC_WITHOUT_HW_ACCELERATION] = result
                return keyvals

        # Start chrome with disabled video hardware decode flag.
        with chrome.Chrome(extra_browser_args=
                DISABLE_ACCELERATED_VIDEO_DECODE_BROWSER_ARGS +
                EXTRA_BROWSER_ARGS) as cr:
            # Open the webrtc loopback page and start the loopback.
            self.start_loopback(cr)
            result = gather_result(cr)

            # Make sure decode is not hardware accelerated.
            if histogram_verifier.is_bucket_present(
                    cr,
                    constants.RTC_INIT_HISTOGRAM,
                    constants.RTC_VIDEO_INIT_BUCKET):
                raise error.TestError('HW decode should not be used.')
            keyvals[WEBRTC_WITHOUT_HW_ACCELERATION] = result

        return keyvals


    def test_cpu_usage(self, local_path):
        """
        Runs the video cpu usage test.

        @param local_path: the path to the video file.

        @return a dictionary that contains the test result.
        """
        def get_cpu_usage(cr):
            time.sleep(STABILIZATION_DURATION)
            cpu_usage_start = site_utils.get_cpu_usage()
            time.sleep(MEASUREMENT_DURATION)
            cpu_usage_end = site_utils.get_cpu_usage()
            return site_utils.compute_active_cpu_time(cpu_usage_start,
                                                      cpu_usage_end) * 100
        if not utils.wait_for_idle_cpu(WAIT_FOR_IDLE_CPU_TIMEOUT,
                                       CPU_IDLE_USAGE):
            raise error.TestError('Could not get idle CPU.')
        if not utils.wait_for_cool_machine():
            raise error.TestError('Could not get cool machine.')
        # Stop the thermal service that may change the cpu frequency.
        services = service_stopper.ServiceStopper(THERMAL_SERVICES)
        services.stop_services()
        # Set the scaling governor to performance mode to set the cpu to the
        # highest frequency available.
        original_governors = utils.set_high_performance_mode()
        try:
            return self.test_webrtc(local_path, get_cpu_usage)
        finally:
            services.restore_services()
            utils.restore_scaling_governor_states(original_governors)


    def test_power(self, local_path):
        """
        Runs the video power consumption test.

        @param local_path: the path to the video file.

        @return a dictionary that contains the test result.
        """
        # Verify that we are running on battery and the battery is sufficiently
        # charged.
        current_power_status = power_status.get_status()
        current_power_status.assert_battery_state(BATTERY_INITIAL_CHARGED_MIN)

        measurements = [power_status.SystemPower(
                current_power_status.battery_path)]

        def get_power(cr):
            power_logger = power_status.PowerLogger(measurements)
            power_logger.start()
            time.sleep(STABILIZATION_DURATION)
            start_time = time.time()
            time.sleep(MEASUREMENT_DURATION)
            power_logger.checkpoint('result', start_time)
            keyval = power_logger.calc()
            return keyval['result_' + measurements[0].domain + '_pwr']

        backlight = power_utils.Backlight()
        backlight.set_default()
        services = service_stopper.ServiceStopper(
                service_stopper.ServiceStopper.POWER_DRAW_SERVICES)
        services.stop_services()
        try:
            return self.test_webrtc(local_path, get_power)
        finally:
            backlight.restore()
            services.restore_services()


    def test_decode_time(self, local_path):
        """
        Runs the decode time test.

        @param local_path: the path to the video file.

        @return a dictionary that contains the test result.
        """
        def get_decode_time(cr):
            tab = self.open_stats_page(cr)
            # Collect the decode time until there are enough samples.
            start_time = time.time()
            max_decode_time_list = []
            decode_time_list = []
            while (time.time() - start_time < TIMEOUT and
                   len(decode_time_list) < NUM_DECODE_TIME_SAMPLES):
                time.sleep(1)
                max_decode_time_list = []
                decode_time_list = []
                try:
                    max_decode_time_list = [int(x) for x in
                            tab.EvaluateJavaScript(GOOG_MAX_DECODE_MS)]
                    decode_time_list = [int(x) for x in
                            tab.EvaluateJavaScript(GOOG_DECODE_MS)]
                except:
                    pass
            # Output the values if they are valid.
            if len(max_decode_time_list) < NUM_DECODE_TIME_SAMPLES:
                raise error.TestError('Not enough ' + GOOG_MAX_DECODE_MS)
            if len(decode_time_list) < NUM_DECODE_TIME_SAMPLES:
                raise error.TestError('Not enough ' + GOOG_DECODE_MS)
            max_decode_time = max(max_decode_time_list)
            decode_time_median = self.get_median(decode_time_list)
            logging.info("Max decode time list=%s", str(max_decode_time_list))
            logging.info("Decode time list=%s", str(decode_time_list))
            logging.info("Maximum decode time=%d, median=%d", max_decode_time,
                         decode_time_median)
            return (max_decode_time, decode_time_median)

        return self.test_webrtc(local_path, get_decode_time)


    def get_median(self, seq):
        """
        Calculates the median of a sequence of numbers.

        @param seq: a list with numbers.

        @returns the median of the numbers.
        """
        seq.sort()
        size = len(seq)
        if size % 2 != 0:
            return seq[size / 2]
        return (seq[size / 2] + seq[size / 2 - 1]) / 2.0

    def log_result(self, keyvals, description, units):
        """
        Logs the test result output to the performance dashboard.

        @param keyvals: a dictionary that contains results returned by
                test_webrtc.
        @param description: a string that describes the video and test result
                and it will be part of the entry name in the dashboard.
        @param units: the units of test result.
        """
        result_with_hw = keyvals.get(WEBRTC_WITH_HW_ACCELERATION)
        if result_with_hw:
            self.output_perf_value(
                    description= 'hw_' + description, value=result_with_hw,
                    units=units, higher_is_better=False)

        result_without_hw = keyvals.get(WEBRTC_WITHOUT_HW_ACCELERATION)
        if result_without_hw:
            self.output_perf_value(
                    description= 'sw_' + description, value=result_without_hw,
                    units=units, higher_is_better=False)