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 site_utils, 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 # pylint: disable=W0611
     15 from autotest_lib.client.cros import flimflam_test_path  # Needed for flimflam
     16 from autotest_lib.client.cros import httpd
     17 from autotest_lib.client.cros import power_rapl, power_status, power_utils
     18 from autotest_lib.client.cros import service_stopper
     19 from autotest_lib.client.cros.graphics import graphics_utils
     20 import flimflam  # Requires flimflam_test_path to be imported first.
     21 
     22 
     23 class power_Consumption(test.test):
     24     """Measure power consumption for different types of loads.
     25 
     26     This test runs a series of different tasks like media playback, flash
     27     animation, large file download etc. It measures and reports power
     28     consumptions during each of those tasks.
     29     """
     30 
     31     version = 2
     32 
     33 
     34     def initialize(self, ac_ok=False):
     35         """Initialize test.
     36 
     37         Args:
     38             ac_ok: boolean to allow running on AC
     39         """
     40         # Objects that need to be taken care of in cleanup() are initialized
     41         # here to None. Otherwise we run the risk of AttributeError raised in
     42         # cleanup() masking a real error that caused the test to fail during
     43         # initialize() before those variables were assigned.
     44         self._backlight = None
     45         self._tmp_keyvals = {}
     46 
     47         self._services = service_stopper.ServiceStopper(
     48             service_stopper.ServiceStopper.POWER_DRAW_SERVICES)
     49         self._services.stop_services()
     50 
     51 
     52         # Time to exclude from calculation after firing a task [seconds]
     53         self._stabilization_seconds = 5
     54         self._power_status = power_status.get_status()
     55         self._tmp_keyvals['b_on_ac'] = self._power_status.on_ac()
     56 
     57         if not ac_ok:
     58             # Verify that we are running on battery and the battery is
     59             # sufficiently charged
     60             self._power_status.assert_battery_state(30)
     61 
     62         # Find the battery capacity to report expected battery life in hours
     63         batinfo = self._power_status.battery[0]
     64         self.energy_full_design = batinfo.energy_full_design
     65         logging.info("energy_full_design = %0.3f Wh", self.energy_full_design)
     66 
     67         # Local data and web server settings. Tarballs with traditional names
     68         # like *.tgz don't get copied to the image by ebuilds (see
     69         # AUTOTEST_FILE_MASK in autotest-chrome ebuild).
     70         self._static_sub_dir = 'static_sites'
     71         utils.extract_tarball_to_dir(
     72                 'static_sites.tgz.keep',
     73                 os.path.join(self.bindir, self._static_sub_dir))
     74         self._media_dir = '/home/chronos/user/Downloads/'
     75         self._httpd_port = 8000
     76         self._url_base = 'http://localhost:%s/' % self._httpd_port
     77         self._test_server = httpd.HTTPListener(self._httpd_port,
     78                                                docroot=self.bindir)
     79 
     80         self._test_server.run()
     81 
     82         logging.info('initialize() finished')
     83 
     84 
     85     def _download_test_data(self):
     86         """Download audio and video files.
     87 
     88         This is also used as payload for download test.
     89 
     90         Note, can reach payload via browser at
     91           https://console.developers.google.com/storage/chromeos-test-public/big_buck_bunny
     92         Start with README
     93         """
     94 
     95         repo = 'http://commondatastorage.googleapis.com/chromeos-test-public/'
     96         file_list = [repo + 'big_buck_bunny/big_buck_bunny_trailer_400p.mp4', ]
     97         if not self.short:
     98             file_list += [
     99                 repo + 'big_buck_bunny/big_buck_bunny_trailer_400p.ogg',
    100                 repo + 'big_buck_bunny/big_buck_bunny_trailer_400p.vp8.webm',
    101                 repo + 'big_buck_bunny/big_buck_bunny_trailer_400p.vp9.webm',
    102                 repo + 'big_buck_bunny/big_buck_bunny_trailer_720p.mp4',
    103                 repo + 'big_buck_bunny/big_buck_bunny_trailer_720p.ogg',
    104                 repo + 'big_buck_bunny/big_buck_bunny_trailer_720p.vp8.webm',
    105                 repo + 'big_buck_bunny/big_buck_bunny_trailer_720p.vp9.webm',
    106                 repo + 'big_buck_bunny/big_buck_bunny_trailer_1080p.mp4',
    107                 repo + 'big_buck_bunny/big_buck_bunny_trailer_1080p.ogg',
    108                 repo + 'big_buck_bunny/big_buck_bunny_trailer_1080p.vp8.webm',
    109                 repo + 'big_buck_bunny/big_buck_bunny_trailer_1080p.vp9.webm',
    110                 repo + 'wikimedia/Greensleeves.ogg',
    111                 ]
    112 
    113         for url in file_list:
    114             logging.info('Downloading %s', url)
    115             utils.unmap_url('', url, self._media_dir)
    116 
    117 
    118     def _toggle_fullscreen(self):
    119         """Toggle full screen mode."""
    120         # Note: full screen mode toggled with F11 is different from clicking the
    121         # full screen icon on video player controls. This needs improvement.
    122         # Bug: http://crbug.com/248939
    123         graphics_utils.screen_toggle_fullscreen()
    124 
    125 
    126     # Below are a series of generic sub-test runners. They run a given task
    127     # and record the task name and start-end timestamps for future computation
    128     # of power consumption during the task.
    129     def _run_func(self, name, func, repeat=1, save_checkpoint=True):
    130         """Run a given python function as a sub-test."""
    131         start_time = time.time() + self._stabilization_seconds
    132         for _ in xrange(repeat):
    133             ret = func()
    134         if save_checkpoint:
    135             self._plog.checkpoint(name, start_time)
    136         return ret
    137 
    138 
    139     def _run_sleep(self, name, seconds=60):
    140         """Just sleep and record it as a named sub-test"""
    141         start_time = time.time() + self._stabilization_seconds
    142         time.sleep(seconds)
    143         self._plog.checkpoint(name, start_time)
    144 
    145 
    146     def _run_cmd(self, name, cmd, repeat=1):
    147         """Run command in a shell as a sub-test"""
    148         start_time = time.time() + self._stabilization_seconds
    149         for _ in xrange(repeat):
    150             logging.info('Executing command: %s', cmd)
    151             exit_status = utils.system(cmd, ignore_status=True)
    152             if exit_status != 0:
    153                 logging.error('run_cmd: the following command terminated with'
    154                                 'a non zero exit status: %s', cmd)
    155         self._plog.checkpoint(name, start_time)
    156         return exit_status
    157 
    158 
    159     def _run_until(self, name, predicate, timeout=60):
    160         """Probe the |predicate| function  and wait until it returns true.
    161         Record the waiting time as a sub-test
    162         """
    163         start_time = time.time() + self._stabilization_seconds
    164         utils.poll_for_condition(predicate, timeout=timeout)
    165         self._plog.checkpoint(name, start_time)
    166 
    167 
    168     def _run_url(self, name, url, duration):
    169         """Navigate to URL, sleep for some time and record it as a sub-test."""
    170         logging.info('Navigating to %s', url)
    171         self._tab.Activate()
    172         self._tab.Navigate(url)
    173         self._run_sleep(name, duration)
    174         tab_title = self._tab.EvaluateJavaScript('document.title')
    175         logging.info('Sub-test name: %s Tab title: %s.', name, tab_title)
    176 
    177 
    178     def _run_url_bg(self, name, url, duration):
    179         """Run a web site in background tab.
    180 
    181         Navigate to the given URL, open an empty tab to put the one with the
    182         URL in background, then sleep and record it as a sub-test.
    183 
    184         Args:
    185             name: sub-test name.
    186             url: url to open in background tab.
    187             duration: number of seconds to sleep while taking measurements.
    188         """
    189         bg_tab = self._tab
    190         bg_tab.Navigate(url)
    191         # Let it load and settle
    192         time.sleep(self._stabilization_seconds / 2.)
    193         tab_title = bg_tab.EvaluateJavaScript('document.title')
    194         logging.info('App name: %s Tab title: %s.', name, tab_title)
    195         # Open a new empty tab to cover the one with test payload.
    196         fg_tab = self._browser.tabs.New()
    197         fg_tab.Activate()
    198         self._run_sleep(name, duration)
    199         fg_tab.Close()
    200         bg_tab.Activate()
    201 
    202 
    203     def _run_group_download(self):
    204         """Download over ethernet. Using video test data as payload."""
    205 
    206         # For short run, the payload is too small to take measurement
    207         self._run_func('download_eth',
    208                        self._download_test_data ,
    209                        repeat=self._repeats,
    210                        save_checkpoint=not(self.short))
    211 
    212 
    213     def _run_group_webpages(self):
    214         """Runs a series of web pages as sub-tests."""
    215         data_url = self._url_base + self._static_sub_dir + '/'
    216 
    217         # URLs to be only tested in foreground tab.
    218         # Can't use about:blank here - crbug.com/248945
    219         # but chrome://version is just as good for our needs.
    220         urls = [('ChromeVer', 'chrome://version/')]
    221         # URLs to be tested in both, background and foreground modes.
    222         bg_urls = []
    223 
    224         more_urls = [('BallsDHTML',
    225                       data_url + 'balls/DHTMLBalls/dhtml.htm'),
    226                      # Disabling FlexBalls as experiment http://crbug.com/309403
    227                      # ('BallsFlex',
    228                      #  data_url + 'balls/FlexBalls/flexballs.html'),
    229                     ]
    230 
    231         if self.short:
    232             urls += more_urls
    233         else:
    234             bg_urls += more_urls
    235             bg_urls += [('Parapluesch',
    236                          'http://www.parapluesch.de/whiskystore/test.htm'),
    237                          ('PosterCircle',
    238                           'http://www.webkit.org'
    239                           '/blog-files/3d-transforms/poster-circle.html'), ]
    240 
    241         for name, url in urls + bg_urls:
    242             self._run_url(name, url, duration=self._duration_secs)
    243 
    244         for name, url in bg_urls:
    245             self._run_url_bg('bg_' + name, url, duration=self._duration_secs)
    246 
    247 
    248     def _run_group_v8(self):
    249         """Run the V8 benchmark suite as a sub-test.
    250 
    251         Fire it up and wait until it displays "Score".
    252         """
    253 
    254         url = 'http://v8.googlecode.com/svn/data/benchmarks/v7/run.html'
    255         js = "document.getElementById('status').textContent"
    256         tab = self._tab
    257 
    258         def v8_func():
    259             """To be passed as the callable to self._run_func()"""
    260             tab.Navigate(url)
    261             # V8 test will usually take 17-25 seconds. Need some sleep here
    262             # to let the V8 page load and create the 'status' div.
    263             is_done = lambda: tab.EvaluateJavaScript(js).startswith('Score')
    264             time.sleep(self._stabilization_seconds)
    265             utils.poll_for_condition(is_done, timeout=60, desc='V8 score found')
    266 
    267         self._run_func('V8', v8_func, repeat=self._repeats)
    268 
    269         # Write v8 score from the last run to log
    270         score = tab.EvaluateJavaScript(js)
    271         score = score.strip().split()[1]
    272         logging.info('V8 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 = site_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         wifi_ap = 'GoogleGuest'
    377         wifi_sec = 'none'
    378         wifi_pw = ''
    379 
    380         flim = flimflam.FlimFlam()
    381         conn = flim.ConnectService(retries=3,
    382                               retry=True,
    383                               service_type='wifi',
    384                               ssid=wifi_ap,
    385                               security=wifi_sec,
    386                               passphrase=wifi_pw,
    387                               mode='managed')
    388         if not conn[0]:
    389             logging.error("Could not connect to WiFi")
    390             return
    391 
    392         logging.info('Starting Backchannel')
    393         with backchannel.Backchannel():
    394             # Wifi needs some time to recover after backchanel is activated
    395             # TODO (kamrik) remove this sleep, once backchannel handles this
    396             time.sleep(15)
    397 
    398             cmd = 'ping -c %s www.google.com' % (self._duration_secs)
    399             self._run_cmd('ping_wifi', cmd)
    400 
    401             # This URL must be visible from WiFi network used for test
    402             big_file_url = ('http://googleappengine.googlecode.com'
    403                             '/files/GoogleAppEngine-1.6.2.msi')
    404             cmd = 'curl %s > /dev/null' % big_file_url
    405             self._run_cmd('download_wifi', cmd, repeat=self._repeats)
    406 
    407 
    408     def _run_group_backlight(self):
    409         """Vary backlight brightness and record power at each setting."""
    410         for i in [100, 50, 0]:
    411             self._backlight.set_percent(i)
    412             start_time = time.time() + self._stabilization_seconds
    413             time.sleep(30 * self._repeats)
    414             self._plog.checkpoint('backlight_%03d' % i, start_time)
    415         self._backlight.set_default()
    416 
    417 
    418     def _web_echo(self, msg):
    419         """ Displays a message in the browser."""
    420         url = self._url_base + 'echo.html?'
    421         url += urllib.quote(msg)
    422         self._tab.Navigate(url)
    423 
    424 
    425     def _run_test_groups(self, groups):
    426         """ Run all the test groups.
    427 
    428         Args:
    429             groups: list of sub-test groups to run. Each sub-test group refers
    430                 to a _run_group_...() function.
    431         """
    432 
    433         for group in groups:
    434             logging.info('Running group %s', group)
    435             # The _web_echo here is important for some tests (esp. non UI)
    436             # it gets the previous web page replaced with an almost empty one.
    437             self._tab.Activate()
    438             self._web_echo('Running test %s' % group)
    439             test_func = getattr(self, '_run_group_%s' % group)
    440             test_func()
    441 
    442 
    443     def run_once(self, short=False, test_groups=None, reps=1):
    444         # Some sub-tests have duration specified directly, _base_secs * reps
    445         # is used in this case. Others complete whenever the underlying task
    446         # completes, those are manually tuned to be roughly around
    447         # reps * 30 seconds. Don't change _base_secs unless you also
    448         # change the manual tuning in sub-tests
    449         self._base_secs = 30
    450         self._repeats = reps
    451         self._duration_secs = self._base_secs * reps
    452 
    453         # Lists of default tests to run
    454         UI_TESTS = ['backlight', 'download', 'webpages', 'video', 'v8']
    455         NONUI_TESTS = ['backchannel', 'sound', 'lowlevel']
    456         DEFAULT_TESTS = UI_TESTS + NONUI_TESTS
    457         DEFAULT_SHORT_TESTS = ['download', 'webpages', 'video']
    458 
    459         self.short = short
    460         if test_groups is None:
    461             if self.short:
    462                 test_groups = DEFAULT_SHORT_TESTS
    463             else:
    464                 test_groups = DEFAULT_TESTS
    465         logging.info('Test groups to run: %s', ', '.join(test_groups))
    466 
    467         self._backlight = power_utils.Backlight()
    468         self._backlight.set_default()
    469 
    470         measurements = \
    471             [power_status.SystemPower(self._power_status.battery_path)]
    472         if power_utils.has_rapl_support():
    473             measurements += power_rapl.create_rapl()
    474         self._plog = power_status.PowerLogger(measurements)
    475         self._plog.start()
    476 
    477         # Log in.
    478         with chrome.Chrome() as cr:
    479             self._browser = cr.browser
    480             graphics_utils.screen_disable_energy_saving()
    481             # Most of the tests will be running in this tab.
    482             self._tab = cr.browser.tabs[0]
    483 
    484             # Verify that we have a functioning browser and local web server.
    485             self._tab.Activate()
    486             self._web_echo("Sanity_test")
    487             self._tab.WaitForDocumentReadyStateToBeComplete()
    488 
    489             # Video test must have the data from download test
    490             if ('video' in test_groups):
    491                 iv = test_groups.index('video')
    492                 if 'download' not in test_groups[:iv]:
    493                     msg = '"download" test must run before "video".'
    494                     raise error.TestError(msg)
    495 
    496             # Run all the test groups
    497             self._run_test_groups(test_groups)
    498 
    499         # Wrap up
    500         keyvals = self._plog.calc()
    501         keyvals.update(self._tmp_keyvals)
    502 
    503         # Calculate expected battery life time with ChromeVer power draw
    504         idle_name = 'ChromeVer_system_pwr'
    505         if idle_name in keyvals:
    506             hours_life = self.energy_full_design / keyvals[idle_name]
    507             keyvals['hours_battery_ChromeVer'] = hours_life
    508 
    509         # Calculate a weighted power draw and battery life time. The weights
    510         # are intended to represent "typical" usage. Some video, some Flash ...
    511         # and most of the time idle.
    512         # see http://www.chromium.org/chromium-os/testing/power-testing
    513         weights = {'vid400p_h264_system_pwr':0.1,
    514                    # TODO(chromium:309403) re-enable BallsFlex once Flash in
    515                    # test-lab understood and re-distribute back to 60/20/10/10.
    516                    # 'BallsFlex_system_pwr':0.1,
    517                    'BallsDHTML_system_pwr':0.3,
    518                    }
    519         weights[idle_name] = 1 - sum(weights.values())
    520 
    521         if set(weights).issubset(set(keyvals)):
    522             p = sum(w * keyvals[k] for (k, w) in weights.items())
    523             keyvals['w_Weighted_system_pwr'] = p
    524             keyvals['hours_battery_Weighted'] = self.energy_full_design / p
    525 
    526         self.write_perf_keyval(keyvals)
    527         self._plog.save_results(self.resultsdir)
    528 
    529 
    530     def cleanup(self):
    531         # cleanup() is run by common_lib/test.py
    532         try:
    533             self._test_server.stop()
    534         except AttributeError:
    535             logging.debug('test_server could not be stopped in cleanup')
    536 
    537         if self._backlight:
    538             self._backlight.restore()
    539         if self._services:
    540             self._services.restore_services()
    541 
    542         super(power_Consumption, self).cleanup()
    543