Home | History | Annotate | Download | only in power_Consumption
      1 # Copyright (c) 2012 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 import urllib
      9 
     10 from autotest_lib.client.bin import test, utils
     11 from autotest_lib.client.common_lib import error
     12 from autotest_lib.client.common_lib.cros import chrome
     13 from autotest_lib.client.cros import backchannel
     14 from autotest_lib.client.cros import httpd
     15 from autotest_lib.client.cros import service_stopper
     16 from autotest_lib.client.cros.graphics import graphics_utils
     17 from autotest_lib.client.cros.networking import wifi_proxy
     18 from autotest_lib.client.cros.power import power_rapl, power_status, power_utils
     19 
     20 
     21 class power_Consumption(test.test):
     22     """Measure power consumption for different types of loads.
     23 
     24     This test runs a series of different tasks like media playback, flash
     25     animation, large file download etc. It measures and reports power
     26     consumptions during each of those tasks.
     27     """
     28 
     29     version = 2
     30 
     31 
     32     def initialize(self, ac_ok=False):
     33         """Initialize test.
     34 
     35         Args:
     36             ac_ok: boolean to allow running on AC
     37         """
     38         # Objects that need to be taken care of in cleanup() are initialized
     39         # here to None. Otherwise we run the risk of AttributeError raised in
     40         # cleanup() masking a real error that caused the test to fail during
     41         # initialize() before those variables were assigned.
     42         self._backlight = None
     43         self._tmp_keyvals = {}
     44 
     45         self._services = service_stopper.ServiceStopper(
     46             service_stopper.ServiceStopper.POWER_DRAW_SERVICES)
     47         self._services.stop_services()
     48 
     49 
     50         # Time to exclude from calculation after firing a task [seconds]
     51         self._stabilization_seconds = 5
     52         self._power_status = power_status.get_status()
     53         self._tmp_keyvals['b_on_ac'] = self._power_status.on_ac()
     54 
     55         if not ac_ok:
     56             # Verify that we are running on battery and the battery is
     57             # sufficiently charged
     58             self._power_status.assert_battery_state(30)
     59 
     60         # Local data and web server settings. Tarballs with traditional names
     61         # like *.tgz don't get copied to the image by ebuilds (see
     62         # AUTOTEST_FILE_MASK in autotest-chrome ebuild).
     63         self._static_sub_dir = 'static_sites'
     64         utils.extract_tarball_to_dir(
     65                 'static_sites.tgz.keep',
     66                 os.path.join(self.bindir, self._static_sub_dir))
     67         self._media_dir = '/home/chronos/user/Downloads/'
     68         self._httpd_port = 8000
     69         self._url_base = 'http://localhost:%s/' % self._httpd_port
     70         self._test_server = httpd.HTTPListener(self._httpd_port,
     71                                                docroot=self.bindir)
     72 
     73         # initialize various interesting power related stats
     74         self._statomatic = power_status.StatoMatic()
     75         self._test_server.run()
     76 
     77 
     78         logging.info('initialize() finished')
     79 
     80 
     81     def _download_test_data(self):
     82         """Download audio and video files.
     83 
     84         This is also used as payload for download test.
     85 
     86         Note, can reach payload via browser at
     87           https://console.developers.google.com/storage/chromeos-test-public/big_buck_bunny
     88         Start with README
     89         """
     90 
     91         repo = 'http://commondatastorage.googleapis.com/chromeos-test-public/'
     92         file_list = [repo + 'big_buck_bunny/big_buck_bunny_trailer_400p.mp4', ]
     93         if not self.short:
     94             file_list += [
     95                 repo + 'big_buck_bunny/big_buck_bunny_trailer_400p.ogg',
     96                 repo + 'big_buck_bunny/big_buck_bunny_trailer_400p.vp8.webm',
     97                 repo + 'big_buck_bunny/big_buck_bunny_trailer_400p.vp9.webm',
     98                 repo + 'big_buck_bunny/big_buck_bunny_trailer_720p.mp4',
     99                 repo + 'big_buck_bunny/big_buck_bunny_trailer_720p.ogg',
    100                 repo + 'big_buck_bunny/big_buck_bunny_trailer_720p.vp8.webm',
    101                 repo + 'big_buck_bunny/big_buck_bunny_trailer_720p.vp9.webm',
    102                 repo + 'big_buck_bunny/big_buck_bunny_trailer_1080p.mp4',
    103                 repo + 'big_buck_bunny/big_buck_bunny_trailer_1080p.ogg',
    104                 repo + 'big_buck_bunny/big_buck_bunny_trailer_1080p.vp8.webm',
    105                 repo + 'big_buck_bunny/big_buck_bunny_trailer_1080p.vp9.webm',
    106                 repo + 'wikimedia/Greensleeves.ogg',
    107                 ]
    108 
    109         for url in file_list:
    110             logging.info('Downloading %s', url)
    111             utils.unmap_url('', url, self._media_dir)
    112 
    113 
    114     def _toggle_fullscreen(self):
    115         """Toggle full screen mode."""
    116         # Note: full screen mode toggled with F11 is different from clicking the
    117         # full screen icon on video player controls. This needs improvement.
    118         # Bug: http://crbug.com/248939
    119         graphics_utils.screen_toggle_fullscreen()
    120 
    121 
    122     # Below are a series of generic sub-test runners. They run a given task
    123     # and record the task name and start-end timestamps for future computation
    124     # of power consumption during the task.
    125     def _run_func(self, name, func, repeat=1, save_checkpoint=True):
    126         """Run a given python function as a sub-test."""
    127         start_time = time.time() + self._stabilization_seconds
    128         for _ in xrange(repeat):
    129             ret = func()
    130         if save_checkpoint:
    131             self._plog.checkpoint(name, start_time)
    132         return ret
    133 
    134 
    135     def _run_sleep(self, name, seconds=60):
    136         """Just sleep and record it as a named sub-test"""
    137         start_time = time.time() + self._stabilization_seconds
    138         time.sleep(seconds)
    139         self._plog.checkpoint(name, start_time)
    140 
    141 
    142     def _run_cmd(self, name, cmd, repeat=1):
    143         """Run command in a shell as a sub-test"""
    144         start_time = time.time() + self._stabilization_seconds
    145         for _ in xrange(repeat):
    146             logging.info('Executing command: %s', cmd)
    147             exit_status = utils.system(cmd, ignore_status=True)
    148             if exit_status != 0:
    149                 logging.error('run_cmd: the following command terminated with'
    150                                 'a non zero exit status: %s', cmd)
    151         self._plog.checkpoint(name, start_time)
    152         return exit_status
    153 
    154 
    155     def _run_until(self, name, predicate, timeout=60):
    156         """Probe the |predicate| function  and wait until it returns true.
    157         Record the waiting time as a sub-test
    158         """
    159         start_time = time.time() + self._stabilization_seconds
    160         utils.poll_for_condition(predicate, timeout=timeout)
    161         self._plog.checkpoint(name, start_time)
    162 
    163 
    164     def _run_url(self, name, url, duration):
    165         """Navigate to URL, sleep for some time and record it as a sub-test."""
    166         logging.info('Navigating to %s', url)
    167         self._tab.Activate()
    168         self._tab.Navigate(url)
    169         self._run_sleep(name, duration)
    170         tab_title = self._tab.EvaluateJavaScript('document.title')
    171         logging.info('Sub-test name: %s Tab title: %s.', name, tab_title)
    172 
    173 
    174     def _run_url_bg(self, name, url, duration):
    175         """Run a web site in background tab.
    176 
    177         Navigate to the given URL, open an empty tab to put the one with the
    178         URL in background, then sleep and record it as a sub-test.
    179 
    180         Args:
    181             name: sub-test name.
    182             url: url to open in background tab.
    183             duration: number of seconds to sleep while taking measurements.
    184         """
    185         bg_tab = self._tab
    186         bg_tab.Navigate(url)
    187         # Let it load and settle
    188         time.sleep(self._stabilization_seconds / 2.)
    189         tab_title = bg_tab.EvaluateJavaScript('document.title')
    190         logging.info('App name: %s Tab title: %s.', name, tab_title)
    191         # Open a new empty tab to cover the one with test payload.
    192         fg_tab = self._browser.tabs.New()
    193         fg_tab.Activate()
    194         self._run_sleep(name, duration)
    195         fg_tab.Close()
    196         bg_tab.Activate()
    197 
    198 
    199     def _run_group_download(self):
    200         """Download over ethernet. Using video test data as payload."""
    201 
    202         # For short run, the payload is too small to take measurement
    203         self._run_func('download_eth',
    204                        self._download_test_data ,
    205                        repeat=self._repeats,
    206                        save_checkpoint=not(self.short))
    207 
    208 
    209     def _run_group_webpages(self):
    210         """Runs a series of web pages as sub-tests."""
    211         data_url = self._url_base + self._static_sub_dir + '/'
    212 
    213         # URLs to be only tested in foreground tab.
    214         # Can't use about:blank here - crbug.com/248945
    215         # but chrome://version is just as good for our needs.
    216         urls = [('ChromeVer', 'chrome://version/')]
    217         # URLs to be tested in both, background and foreground modes.
    218         bg_urls = []
    219 
    220         more_urls = [('BallsDHTML',
    221                       data_url + 'balls/DHTMLBalls/dhtml.htm'),
    222                      ('BallsFlex',
    223                       data_url + 'balls/FlexBalls/flexballs.html'),
    224                     ]
    225 
    226         if self.short:
    227             urls += more_urls
    228         else:
    229             bg_urls += more_urls
    230             bg_urls += [('Parapluesch',
    231                          'http://www.parapluesch.de/whiskystore/test.htm'),
    232                          ('PosterCircle',
    233                           'http://www.webkit.org'
    234                           '/blog-files/3d-transforms/poster-circle.html'), ]
    235 
    236         for name, url in urls + bg_urls:
    237             self._run_url(name, url, duration=self._duration_secs)
    238 
    239         for name, url in bg_urls:
    240             self._run_url_bg('bg_' + name, url, duration=self._duration_secs)
    241 
    242 
    243     def _run_group_speedometer(self):
    244         """Run the Speedometer benchmark suite as a sub-test.
    245 
    246         Fire it up and wait until it displays "Score".
    247         """
    248 
    249         # TODO: check in a local copy of the test if we can get permission if
    250         # the network causes problems.
    251         url = 'http://browserbench.org/Speedometer/'
    252         start_js = 'startTest()'
    253         score_js = "document.getElementById('result-number').innerText"
    254         tab = self._tab
    255 
    256         def speedometer_func():
    257             """To be passed as the callable to self._run_func()"""
    258             tab.Navigate(url)
    259             tab.WaitForDocumentReadyStateToBeComplete()
    260             tab.EvaluateJavaScript(start_js)
    261             # Speedometer test should be done in less than 15 minutes (actual
    262             # runs are closer to 5).
    263             is_done = lambda: tab.EvaluateJavaScript(score_js) != ""
    264             time.sleep(self._stabilization_seconds)
    265             utils.poll_for_condition(is_done, timeout=900,
    266                                      desc='Speedometer score found')
    267 
    268         self._run_func('Speedometer', speedometer_func, repeat=self._repeats)
    269 
    270         # Write speedometer score from the last run to log
    271         score = tab.EvaluateJavaScript(score_js)
    272         logging.info('Speedometer Score: %s', score)
    273 
    274 
    275     def _run_group_video(self):
    276         """Run video and audio playback in the browser."""
    277 
    278         # Note: for perf keyvals, key names are defined as VARCHAR(30) in the
    279         # results DB. Chars above 30 are truncated when saved to DB.
    280         urls = [('vid400p_h264', 'big_buck_bunny_trailer_400p.mp4'), ]
    281         fullscreen_urls = []
    282         bg_urls = []
    283 
    284         if not self.short:
    285             urls += [
    286                 ('vid400p_ogg', 'big_buck_bunny_trailer_400p.ogg'),
    287                 ('vid400p_vp8', 'big_buck_bunny_trailer_400p.vp8.webm'),
    288                 ('vid400p_vp9', 'big_buck_bunny_trailer_400p.vp9.webm'),
    289                 ('vid720_h264', 'big_buck_bunny_trailer_720p.mp4'),
    290                 ('vid720_ogg', 'big_buck_bunny_trailer_720p.ogg'),
    291                 ('vid720_vp8', 'big_buck_bunny_trailer_720p.vp8.webm'),
    292                 ('vid720_vp9', 'big_buck_bunny_trailer_720p.vp9.webm'),
    293                 ('vid1080_h264', 'big_buck_bunny_trailer_1080p.mp4'),
    294                 ('vid1080_ogg', 'big_buck_bunny_trailer_1080p.ogg'),
    295                 ('vid1080_vp8', 'big_buck_bunny_trailer_1080p.vp8.webm'),
    296                 ('vid1080_vp9', 'big_buck_bunny_trailer_1080p.vp9.webm'),
    297                 ('audio', 'Greensleeves.ogg'),
    298                 ]
    299 
    300             fullscreen_urls += [
    301                 ('vid720_h264_fs', 'big_buck_bunny_trailer_720p.mp4'),
    302                 ('vid720_vp8_fs', 'big_buck_bunny_trailer_720p.vp8.webm'),
    303                 ('vid720_vp9_fs', 'big_buck_bunny_trailer_720p.vp9.webm'),
    304                 ('vid1080_h264_fs', 'big_buck_bunny_trailer_1080p.mp4'),
    305                 ('vid1080_vp8_fs', 'big_buck_bunny_trailer_1080p.vp8.webm'),
    306                 ('vid1080_vp9_fs', 'big_buck_bunny_trailer_1080p.vp9.webm'),
    307                 ]
    308 
    309             bg_urls += [
    310                 ('bg_vid400p', 'big_buck_bunny_trailer_400p.vp8.webm'),
    311                 ]
    312 
    313         # The video files are run from a file:// url. In order to work properly
    314         # from an http:// url, some careful web server configuration is needed
    315         def full_url(filename):
    316             """Create a file:// url for the media file and verify it exists.
    317 
    318             @param filename: string
    319             """
    320             p = os.path.join(self._media_dir, filename)
    321             if not os.path.isfile(p):
    322                 raise error.TestError('Media file %s is missing.', p)
    323             return 'file://' + p
    324 
    325         js_loop_enable = """ve = document.getElementsByTagName('video')[0];
    326                          ve.loop = true;
    327                          ve.play();
    328                          """
    329 
    330         for name, url in urls:
    331             logging.info('Playing video %s', url)
    332             self._tab.Navigate(full_url(url))
    333             self._tab.ExecuteJavaScript(js_loop_enable)
    334             self._run_sleep(name, self._duration_secs)
    335 
    336         for name, url in fullscreen_urls:
    337             self._toggle_fullscreen()
    338             self._tab.Navigate(full_url(url))
    339             self._tab.ExecuteJavaScript(js_loop_enable)
    340             self._run_sleep(name, self._duration_secs)
    341             self._toggle_fullscreen()
    342 
    343         for name, url in bg_urls:
    344             logging.info('Playing video in background tab %s', url)
    345             self._tab.Navigate(full_url(url))
    346             self._tab.ExecuteJavaScript(js_loop_enable)
    347             fg_tab = self._browser.tabs.New()
    348             self._run_sleep(name, self._duration_secs)
    349             fg_tab.Close()
    350             self._tab.Activate()
    351 
    352 
    353     def _run_group_sound(self):
    354         """Run non-UI sound test using 'speaker-test'."""
    355         # For some reason speaker-test won't work on CrOS without a reasonable
    356         # buffer size specified with -b.
    357         # http://crbug.com/248955
    358         cmd = 'speaker-test -l %s -t sine -c 2 -b 16384' % (self._repeats * 6)
    359         self._run_cmd('speaker_test', cmd)
    360 
    361 
    362     def _run_group_lowlevel(self):
    363         """Low level system stuff"""
    364         mb = min(1024, 32 * self._repeats)
    365         self._run_cmd('memtester', '/usr/local/sbin/memtester %s 1' % mb)
    366 
    367         # one rep of dd takes about 15 seconds
    368         root_dev = utils.get_root_partition()
    369         cmd = 'dd if=%s of=/dev/null' % root_dev
    370         self._run_cmd('dd', cmd, repeat=2 * self._repeats)
    371 
    372 
    373     def _run_group_backchannel(self):
    374         """WiFi sub-tests."""
    375 
    376         shill = wifi_proxy.WifiProxy()
    377         for _ in xrange(3):
    378             succeeded, _, _, _, _ = shill.connect_to_wifi_network(
    379                     ssid='GoogleGuest',
    380                     security='none',
    381                     security_parameters={},
    382                     save_credentials=False)
    383             if succeeded:
    384                 break
    385 
    386         if not succeeded:
    387             logging.error("Could not connect to WiFi")
    388             return
    389 
    390         logging.info('Starting Backchannel')
    391         with backchannel.Backchannel():
    392             # Wifi needs some time to recover after backchanel is activated
    393             # TODO (kamrik) remove this sleep, once backchannel handles this
    394             time.sleep(15)
    395 
    396             cmd = 'ping -c %s www.google.com' % (self._duration_secs)
    397             self._run_cmd('ping_wifi', cmd)
    398 
    399             # This URL must be visible from WiFi network used for test
    400             big_file_url = ('http://googleappengine.googlecode.com'
    401                             '/files/GoogleAppEngine-1.6.2.msi')
    402             cmd = 'curl %s > /dev/null' % big_file_url
    403             self._run_cmd('download_wifi', cmd, repeat=self._repeats)
    404 
    405 
    406     def _run_group_backlight(self):
    407         """Vary backlight brightness and record power at each setting."""
    408         for i in [100, 50, 0]:
    409             self._backlight.set_percent(i)
    410             start_time = time.time() + self._stabilization_seconds
    411             time.sleep(30 * self._repeats)
    412             self._plog.checkpoint('backlight_%03d' % i, start_time)
    413         self._backlight.set_default()
    414 
    415 
    416     def _web_echo(self, msg):
    417         """ Displays a message in the browser."""
    418         url = self._url_base + 'echo.html?'
    419         url += urllib.quote(msg)
    420         self._tab.Navigate(url)
    421 
    422 
    423     def _run_test_groups(self, groups):
    424         """ Run all the test groups.
    425 
    426         Args:
    427             groups: list of sub-test groups to run. Each sub-test group refers
    428                 to a _run_group_...() function.
    429         """
    430 
    431         for group in groups:
    432             logging.info('Running group %s', group)
    433             # The _web_echo here is important for some tests (esp. non UI)
    434             # it gets the previous web page replaced with an almost empty one.
    435             self._tab.Activate()
    436             self._web_echo('Running test %s' % group)
    437             test_func = getattr(self, '_run_group_%s' % group)
    438             test_func()
    439 
    440 
    441     def run_once(self, short=False, test_groups=None, reps=1):
    442         # Some sub-tests have duration specified directly, _base_secs * reps
    443         # is used in this case. Others complete whenever the underlying task
    444         # completes, those are manually tuned to be roughly around
    445         # reps * 30 seconds. Don't change _base_secs unless you also
    446         # change the manual tuning in sub-tests
    447         self._base_secs = 30
    448         self._repeats = reps
    449         self._duration_secs = self._base_secs * reps
    450 
    451         # Lists of default tests to run
    452         UI_TESTS = ['backlight', 'download', 'webpages', 'video', 'speedometer']
    453         NONUI_TESTS = ['backchannel', 'sound', 'lowlevel']
    454         DEFAULT_TESTS = UI_TESTS + NONUI_TESTS
    455         DEFAULT_SHORT_TESTS = ['download', 'webpages', 'video']
    456 
    457         self.short = short
    458         if test_groups is None:
    459             if self.short:
    460                 test_groups = DEFAULT_SHORT_TESTS
    461             else:
    462                 test_groups = DEFAULT_TESTS
    463         logging.info('Test groups to run: %s', ', '.join(test_groups))
    464 
    465         self._backlight = power_utils.Backlight()
    466         self._backlight.set_default()
    467 
    468         measure = []
    469         if not self._power_status.on_ac():
    470             measure += \
    471                 [power_status.SystemPower(self._power_status.battery_path)]
    472         if power_utils.has_powercap_support():
    473             measure += power_rapl.create_powercap()
    474         elif power_utils.has_rapl_support():
    475             measure += power_rapl.create_rapl()
    476         self._plog = power_status.PowerLogger(measure)
    477         self._plog.start()
    478 
    479         # Log in.
    480         with chrome.Chrome() as cr:
    481             self._browser = cr.browser
    482             graphics_utils.screen_disable_energy_saving()
    483             # Most of the tests will be running in this tab.
    484             self._tab = cr.browser.tabs[0]
    485 
    486             # Verify that we have a functioning browser and local web server.
    487             self._tab.Activate()
    488             self._web_echo("Sanity_test")
    489             self._tab.WaitForDocumentReadyStateToBeComplete()
    490 
    491             # Video test must have the data from download test
    492             if ('video' in test_groups):
    493                 iv = test_groups.index('video')
    494                 if 'download' not in test_groups[:iv]:
    495                     msg = '"download" test must run before "video".'
    496                     raise error.TestError(msg)
    497 
    498             # Run all the test groups
    499             self._run_test_groups(test_groups)
    500 
    501         # Wrap up
    502         keyvals = self._plog.calc()
    503         keyvals.update(self._tmp_keyvals)
    504         keyvals.update(self._statomatic.publish())
    505 
    506         # check AC status is still the same as init
    507         self._power_status.refresh()
    508         on_ac = self._power_status.on_ac()
    509         if keyvals['b_on_ac'] != on_ac:
    510             raise error.TestError('on AC changed between start & stop of test')
    511 
    512         if not on_ac:
    513             whrs = self._power_status.battery[0].energy_full_design
    514             logging.info("energy_full_design = %0.3f Wh", whrs)
    515 
    516             # Calculate expected battery life time with ChromeVer power draw
    517             idle_name = 'ChromeVer_system_pwr_avg'
    518             if idle_name in keyvals:
    519                 hours_life = whrs / keyvals[idle_name]
    520                 keyvals['hours_battery_ChromeVer'] = hours_life
    521 
    522             # Calculate a weighted power draw and battery life time. The weights
    523             # are intended to represent "typical" usage. Some video, some Flash
    524             # ... and most of the time idle. see,
    525             # http://www.chromium.org/chromium-os/testing/power-testing
    526             weights = {'vid400p_h264_system_pwr_avg':0.1,
    527                        'BallsFlex_system_pwr_avg':0.1,
    528                        'BallsDHTML_system_pwr_avg':0.3,
    529                       }
    530             weights[idle_name] = 1 - sum(weights.values())
    531 
    532             if set(weights).issubset(set(keyvals)):
    533                 p = sum(w * keyvals[k] for (k, w) in weights.items())
    534                 keyvals['w_Weighted_system_pwr_avg'] = p
    535                 keyvals['hours_battery_Weighted'] = whrs / p
    536 
    537         self.write_perf_keyval(keyvals)
    538         self._plog.save_results(self.resultsdir)
    539 
    540 
    541     def cleanup(self):
    542         # cleanup() is run by common_lib/test.py
    543         try:
    544             self._test_server.stop()
    545         except AttributeError:
    546             logging.debug('test_server could not be stopped in cleanup')
    547 
    548         if self._backlight:
    549             self._backlight.restore()
    550         if self._services:
    551             self._services.restore_services()
    552 
    553         super(power_Consumption, self).cleanup()
    554