普通文本  |  543行  |  21.36 KB

# Copyright (c) 2012 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
import urllib

from autotest_lib.client.bin import site_utils, test, utils
from autotest_lib.client.common_lib import error
from autotest_lib.client.common_lib.cros import chrome
from autotest_lib.client.cros import backchannel
# pylint: disable=W0611
from autotest_lib.client.cros import flimflam_test_path  # Needed for flimflam
from autotest_lib.client.cros import httpd
from autotest_lib.client.cros import power_rapl, power_status, power_utils
from autotest_lib.client.cros import service_stopper
from autotest_lib.client.cros.graphics import graphics_utils
import flimflam  # Requires flimflam_test_path to be imported first.


class power_Consumption(test.test):
    """Measure power consumption for different types of loads.

    This test runs a series of different tasks like media playback, flash
    animation, large file download etc. It measures and reports power
    consumptions during each of those tasks.
    """

    version = 2


    def initialize(self, ac_ok=False):
        """Initialize test.

        Args:
            ac_ok: boolean to allow running on AC
        """
        # Objects that need to be taken care of in cleanup() are initialized
        # here to None. Otherwise we run the risk of AttributeError raised in
        # cleanup() masking a real error that caused the test to fail during
        # initialize() before those variables were assigned.
        self._backlight = None
        self._tmp_keyvals = {}

        self._services = service_stopper.ServiceStopper(
            service_stopper.ServiceStopper.POWER_DRAW_SERVICES)
        self._services.stop_services()


        # Time to exclude from calculation after firing a task [seconds]
        self._stabilization_seconds = 5
        self._power_status = power_status.get_status()
        self._tmp_keyvals['b_on_ac'] = self._power_status.on_ac()

        if not ac_ok:
            # Verify that we are running on battery and the battery is
            # sufficiently charged
            self._power_status.assert_battery_state(30)

        # Find the battery capacity to report expected battery life in hours
        batinfo = self._power_status.battery[0]
        self.energy_full_design = batinfo.energy_full_design
        logging.info("energy_full_design = %0.3f Wh", self.energy_full_design)

        # Local data and web server settings. Tarballs with traditional names
        # like *.tgz don't get copied to the image by ebuilds (see
        # AUTOTEST_FILE_MASK in autotest-chrome ebuild).
        self._static_sub_dir = 'static_sites'
        utils.extract_tarball_to_dir(
                'static_sites.tgz.keep',
                os.path.join(self.bindir, self._static_sub_dir))
        self._media_dir = '/home/chronos/user/Downloads/'
        self._httpd_port = 8000
        self._url_base = 'http://localhost:%s/' % self._httpd_port
        self._test_server = httpd.HTTPListener(self._httpd_port,
                                               docroot=self.bindir)

        self._test_server.run()

        logging.info('initialize() finished')


    def _download_test_data(self):
        """Download audio and video files.

        This is also used as payload for download test.

        Note, can reach payload via browser at
          https://console.developers.google.com/storage/chromeos-test-public/big_buck_bunny
        Start with README
        """

        repo = 'http://commondatastorage.googleapis.com/chromeos-test-public/'
        file_list = [repo + 'big_buck_bunny/big_buck_bunny_trailer_400p.mp4', ]
        if not self.short:
            file_list += [
                repo + 'big_buck_bunny/big_buck_bunny_trailer_400p.ogg',
                repo + 'big_buck_bunny/big_buck_bunny_trailer_400p.vp8.webm',
                repo + 'big_buck_bunny/big_buck_bunny_trailer_400p.vp9.webm',
                repo + 'big_buck_bunny/big_buck_bunny_trailer_720p.mp4',
                repo + 'big_buck_bunny/big_buck_bunny_trailer_720p.ogg',
                repo + 'big_buck_bunny/big_buck_bunny_trailer_720p.vp8.webm',
                repo + 'big_buck_bunny/big_buck_bunny_trailer_720p.vp9.webm',
                repo + 'big_buck_bunny/big_buck_bunny_trailer_1080p.mp4',
                repo + 'big_buck_bunny/big_buck_bunny_trailer_1080p.ogg',
                repo + 'big_buck_bunny/big_buck_bunny_trailer_1080p.vp8.webm',
                repo + 'big_buck_bunny/big_buck_bunny_trailer_1080p.vp9.webm',
                repo + 'wikimedia/Greensleeves.ogg',
                ]

        for url in file_list:
            logging.info('Downloading %s', url)
            utils.unmap_url('', url, self._media_dir)


    def _toggle_fullscreen(self):
        """Toggle full screen mode."""
        # Note: full screen mode toggled with F11 is different from clicking the
        # full screen icon on video player controls. This needs improvement.
        # Bug: http://crbug.com/248939
        graphics_utils.screen_toggle_fullscreen()


    # Below are a series of generic sub-test runners. They run a given task
    # and record the task name and start-end timestamps for future computation
    # of power consumption during the task.
    def _run_func(self, name, func, repeat=1, save_checkpoint=True):
        """Run a given python function as a sub-test."""
        start_time = time.time() + self._stabilization_seconds
        for _ in xrange(repeat):
            ret = func()
        if save_checkpoint:
            self._plog.checkpoint(name, start_time)
        return ret


    def _run_sleep(self, name, seconds=60):
        """Just sleep and record it as a named sub-test"""
        start_time = time.time() + self._stabilization_seconds
        time.sleep(seconds)
        self._plog.checkpoint(name, start_time)


    def _run_cmd(self, name, cmd, repeat=1):
        """Run command in a shell as a sub-test"""
        start_time = time.time() + self._stabilization_seconds
        for _ in xrange(repeat):
            logging.info('Executing command: %s', cmd)
            exit_status = utils.system(cmd, ignore_status=True)
            if exit_status != 0:
                logging.error('run_cmd: the following command terminated with'
                                'a non zero exit status: %s', cmd)
        self._plog.checkpoint(name, start_time)
        return exit_status


    def _run_until(self, name, predicate, timeout=60):
        """Probe the |predicate| function  and wait until it returns true.
        Record the waiting time as a sub-test
        """
        start_time = time.time() + self._stabilization_seconds
        utils.poll_for_condition(predicate, timeout=timeout)
        self._plog.checkpoint(name, start_time)


    def _run_url(self, name, url, duration):
        """Navigate to URL, sleep for some time and record it as a sub-test."""
        logging.info('Navigating to %s', url)
        self._tab.Activate()
        self._tab.Navigate(url)
        self._run_sleep(name, duration)
        tab_title = self._tab.EvaluateJavaScript('document.title')
        logging.info('Sub-test name: %s Tab title: %s.', name, tab_title)


    def _run_url_bg(self, name, url, duration):
        """Run a web site in background tab.

        Navigate to the given URL, open an empty tab to put the one with the
        URL in background, then sleep and record it as a sub-test.

        Args:
            name: sub-test name.
            url: url to open in background tab.
            duration: number of seconds to sleep while taking measurements.
        """
        bg_tab = self._tab
        bg_tab.Navigate(url)
        # Let it load and settle
        time.sleep(self._stabilization_seconds / 2.)
        tab_title = bg_tab.EvaluateJavaScript('document.title')
        logging.info('App name: %s Tab title: %s.', name, tab_title)
        # Open a new empty tab to cover the one with test payload.
        fg_tab = self._browser.tabs.New()
        fg_tab.Activate()
        self._run_sleep(name, duration)
        fg_tab.Close()
        bg_tab.Activate()


    def _run_group_download(self):
        """Download over ethernet. Using video test data as payload."""

        # For short run, the payload is too small to take measurement
        self._run_func('download_eth',
                       self._download_test_data ,
                       repeat=self._repeats,
                       save_checkpoint=not(self.short))


    def _run_group_webpages(self):
        """Runs a series of web pages as sub-tests."""
        data_url = self._url_base + self._static_sub_dir + '/'

        # URLs to be only tested in foreground tab.
        # Can't use about:blank here - crbug.com/248945
        # but chrome://version is just as good for our needs.
        urls = [('ChromeVer', 'chrome://version/')]
        # URLs to be tested in both, background and foreground modes.
        bg_urls = []

        more_urls = [('BallsDHTML',
                      data_url + 'balls/DHTMLBalls/dhtml.htm'),
                     # Disabling FlexBalls as experiment http://crbug.com/309403
                     # ('BallsFlex',
                     #  data_url + 'balls/FlexBalls/flexballs.html'),
                    ]

        if self.short:
            urls += more_urls
        else:
            bg_urls += more_urls
            bg_urls += [('Parapluesch',
                         'http://www.parapluesch.de/whiskystore/test.htm'),
                         ('PosterCircle',
                          'http://www.webkit.org'
                          '/blog-files/3d-transforms/poster-circle.html'), ]

        for name, url in urls + bg_urls:
            self._run_url(name, url, duration=self._duration_secs)

        for name, url in bg_urls:
            self._run_url_bg('bg_' + name, url, duration=self._duration_secs)


    def _run_group_v8(self):
        """Run the V8 benchmark suite as a sub-test.

        Fire it up and wait until it displays "Score".
        """

        url = 'http://v8.googlecode.com/svn/data/benchmarks/v7/run.html'
        js = "document.getElementById('status').textContent"
        tab = self._tab

        def v8_func():
            """To be passed as the callable to self._run_func()"""
            tab.Navigate(url)
            # V8 test will usually take 17-25 seconds. Need some sleep here
            # to let the V8 page load and create the 'status' div.
            is_done = lambda: tab.EvaluateJavaScript(js).startswith('Score')
            time.sleep(self._stabilization_seconds)
            utils.poll_for_condition(is_done, timeout=60, desc='V8 score found')

        self._run_func('V8', v8_func, repeat=self._repeats)

        # Write v8 score from the last run to log
        score = tab.EvaluateJavaScript(js)
        score = score.strip().split()[1]
        logging.info('V8 Score: %s', score)


    def _run_group_video(self):
        """Run video and audio playback in the browser."""

        # Note: for perf keyvals, key names are defined as VARCHAR(30) in the
        # results DB. Chars above 30 are truncated when saved to DB.
        urls = [('vid400p_h264', 'big_buck_bunny_trailer_400p.mp4'), ]
        fullscreen_urls = []
        bg_urls = []

        if not self.short:
            urls += [
                ('vid400p_ogg', 'big_buck_bunny_trailer_400p.ogg'),
                ('vid400p_vp8', 'big_buck_bunny_trailer_400p.vp8.webm'),
                ('vid400p_vp9', 'big_buck_bunny_trailer_400p.vp9.webm'),
                ('vid720_h264', 'big_buck_bunny_trailer_720p.mp4'),
                ('vid720_ogg', 'big_buck_bunny_trailer_720p.ogg'),
                ('vid720_vp8', 'big_buck_bunny_trailer_720p.vp8.webm'),
                ('vid720_vp9', 'big_buck_bunny_trailer_720p.vp9.webm'),
                ('vid1080_h264', 'big_buck_bunny_trailer_1080p.mp4'),
                ('vid1080_ogg', 'big_buck_bunny_trailer_1080p.ogg'),
                ('vid1080_vp8', 'big_buck_bunny_trailer_1080p.vp8.webm'),
                ('vid1080_vp9', 'big_buck_bunny_trailer_1080p.vp9.webm'),
                ('audio', 'Greensleeves.ogg'),
                ]

            fullscreen_urls += [
                ('vid720_h264_fs', 'big_buck_bunny_trailer_720p.mp4'),
                ('vid720_vp8_fs', 'big_buck_bunny_trailer_720p.vp8.webm'),
                ('vid720_vp9_fs', 'big_buck_bunny_trailer_720p.vp9.webm'),
                ('vid1080_h264_fs', 'big_buck_bunny_trailer_1080p.mp4'),
                ('vid1080_vp8_fs', 'big_buck_bunny_trailer_1080p.vp8.webm'),
                ('vid1080_vp9_fs', 'big_buck_bunny_trailer_1080p.vp9.webm'),
                ]

            bg_urls += [
                ('bg_vid400p', 'big_buck_bunny_trailer_400p.vp8.webm'),
                ]

        # The video files are run from a file:// url. In order to work properly
        # from an http:// url, some careful web server configuration is needed
        def full_url(filename):
            """Create a file:// url for the media file and verify it exists.

            @param filename: string
            """
            p = os.path.join(self._media_dir, filename)
            if not os.path.isfile(p):
                raise error.TestError('Media file %s is missing.', p)
            return 'file://' + p

        js_loop_enable = """ve = document.getElementsByTagName('video')[0];
                         ve.loop = true;
                         ve.play();
                         """

        for name, url in urls:
            logging.info('Playing video %s', url)
            self._tab.Navigate(full_url(url))
            self._tab.ExecuteJavaScript(js_loop_enable)
            self._run_sleep(name, self._duration_secs)

        for name, url in fullscreen_urls:
            self._toggle_fullscreen()
            self._tab.Navigate(full_url(url))
            self._tab.ExecuteJavaScript(js_loop_enable)
            self._run_sleep(name, self._duration_secs)
            self._toggle_fullscreen()

        for name, url in bg_urls:
            logging.info('Playing video in background tab %s', url)
            self._tab.Navigate(full_url(url))
            self._tab.ExecuteJavaScript(js_loop_enable)
            fg_tab = self._browser.tabs.New()
            self._run_sleep(name, self._duration_secs)
            fg_tab.Close()
            self._tab.Activate()


    def _run_group_sound(self):
        """Run non-UI sound test using 'speaker-test'."""
        # For some reason speaker-test won't work on CrOS without a reasonable
        # buffer size specified with -b.
        # http://crbug.com/248955
        cmd = 'speaker-test -l %s -t sine -c 2 -b 16384' % (self._repeats * 6)
        self._run_cmd('speaker_test', cmd)


    def _run_group_lowlevel(self):
        """Low level system stuff"""
        mb = min(1024, 32 * self._repeats)
        self._run_cmd('memtester', '/usr/local/sbin/memtester %s 1' % mb)

        # one rep of dd takes about 15 seconds
        root_dev = site_utils.get_root_partition()
        cmd = 'dd if=%s of=/dev/null' % root_dev
        self._run_cmd('dd', cmd, repeat=2 * self._repeats)


    def _run_group_backchannel(self):
        """WiFi sub-tests."""

        wifi_ap = 'GoogleGuest'
        wifi_sec = 'none'
        wifi_pw = ''

        flim = flimflam.FlimFlam()
        conn = flim.ConnectService(retries=3,
                              retry=True,
                              service_type='wifi',
                              ssid=wifi_ap,
                              security=wifi_sec,
                              passphrase=wifi_pw,
                              mode='managed')
        if not conn[0]:
            logging.error("Could not connect to WiFi")
            return

        logging.info('Starting Backchannel')
        with backchannel.Backchannel():
            # Wifi needs some time to recover after backchanel is activated
            # TODO (kamrik) remove this sleep, once backchannel handles this
            time.sleep(15)

            cmd = 'ping -c %s www.google.com' % (self._duration_secs)
            self._run_cmd('ping_wifi', cmd)

            # This URL must be visible from WiFi network used for test
            big_file_url = ('http://googleappengine.googlecode.com'
                            '/files/GoogleAppEngine-1.6.2.msi')
            cmd = 'curl %s > /dev/null' % big_file_url
            self._run_cmd('download_wifi', cmd, repeat=self._repeats)


    def _run_group_backlight(self):
        """Vary backlight brightness and record power at each setting."""
        for i in [100, 50, 0]:
            self._backlight.set_percent(i)
            start_time = time.time() + self._stabilization_seconds
            time.sleep(30 * self._repeats)
            self._plog.checkpoint('backlight_%03d' % i, start_time)
        self._backlight.set_default()


    def _web_echo(self, msg):
        """ Displays a message in the browser."""
        url = self._url_base + 'echo.html?'
        url += urllib.quote(msg)
        self._tab.Navigate(url)


    def _run_test_groups(self, groups):
        """ Run all the test groups.

        Args:
            groups: list of sub-test groups to run. Each sub-test group refers
                to a _run_group_...() function.
        """

        for group in groups:
            logging.info('Running group %s', group)
            # The _web_echo here is important for some tests (esp. non UI)
            # it gets the previous web page replaced with an almost empty one.
            self._tab.Activate()
            self._web_echo('Running test %s' % group)
            test_func = getattr(self, '_run_group_%s' % group)
            test_func()


    def run_once(self, short=False, test_groups=None, reps=1):
        # Some sub-tests have duration specified directly, _base_secs * reps
        # is used in this case. Others complete whenever the underlying task
        # completes, those are manually tuned to be roughly around
        # reps * 30 seconds. Don't change _base_secs unless you also
        # change the manual tuning in sub-tests
        self._base_secs = 30
        self._repeats = reps
        self._duration_secs = self._base_secs * reps

        # Lists of default tests to run
        UI_TESTS = ['backlight', 'download', 'webpages', 'video', 'v8']
        NONUI_TESTS = ['backchannel', 'sound', 'lowlevel']
        DEFAULT_TESTS = UI_TESTS + NONUI_TESTS
        DEFAULT_SHORT_TESTS = ['download', 'webpages', 'video']

        self.short = short
        if test_groups is None:
            if self.short:
                test_groups = DEFAULT_SHORT_TESTS
            else:
                test_groups = DEFAULT_TESTS
        logging.info('Test groups to run: %s', ', '.join(test_groups))

        self._backlight = power_utils.Backlight()
        self._backlight.set_default()

        measurements = \
            [power_status.SystemPower(self._power_status.battery_path)]
        if power_utils.has_rapl_support():
            measurements += power_rapl.create_rapl()
        self._plog = power_status.PowerLogger(measurements)
        self._plog.start()

        # Log in.
        with chrome.Chrome() as cr:
            self._browser = cr.browser
            graphics_utils.screen_disable_energy_saving()
            # Most of the tests will be running in this tab.
            self._tab = cr.browser.tabs[0]

            # Verify that we have a functioning browser and local web server.
            self._tab.Activate()
            self._web_echo("Sanity_test")
            self._tab.WaitForDocumentReadyStateToBeComplete()

            # Video test must have the data from download test
            if ('video' in test_groups):
                iv = test_groups.index('video')
                if 'download' not in test_groups[:iv]:
                    msg = '"download" test must run before "video".'
                    raise error.TestError(msg)

            # Run all the test groups
            self._run_test_groups(test_groups)

        # Wrap up
        keyvals = self._plog.calc()
        keyvals.update(self._tmp_keyvals)

        # Calculate expected battery life time with ChromeVer power draw
        idle_name = 'ChromeVer_system_pwr'
        if idle_name in keyvals:
            hours_life = self.energy_full_design / keyvals[idle_name]
            keyvals['hours_battery_ChromeVer'] = hours_life

        # Calculate a weighted power draw and battery life time. The weights
        # are intended to represent "typical" usage. Some video, some Flash ...
        # and most of the time idle.
        # see http://www.chromium.org/chromium-os/testing/power-testing
        weights = {'vid400p_h264_system_pwr':0.1,
                   # TODO(chromium:309403) re-enable BallsFlex once Flash in
                   # test-lab understood and re-distribute back to 60/20/10/10.
                   # 'BallsFlex_system_pwr':0.1,
                   'BallsDHTML_system_pwr':0.3,
                   }
        weights[idle_name] = 1 - sum(weights.values())

        if set(weights).issubset(set(keyvals)):
            p = sum(w * keyvals[k] for (k, w) in weights.items())
            keyvals['w_Weighted_system_pwr'] = p
            keyvals['hours_battery_Weighted'] = self.energy_full_design / p

        self.write_perf_keyval(keyvals)
        self._plog.save_results(self.resultsdir)


    def cleanup(self):
        # cleanup() is run by common_lib/test.py
        try:
            self._test_server.stop()
        except AttributeError:
            logging.debug('test_server could not be stopped in cleanup')

        if self._backlight:
            self._backlight.restore()
        if self._services:
            self._services.restore_services()

        super(power_Consumption, self).cleanup()