Home | History | Annotate | Download | only in graphics_WebGLAquarium
      1 # Copyright (c) 2013 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 """This is a client side WebGL aquarium test."""
      5 
      6 import logging
      7 import math
      8 import os
      9 import sampler
     10 import threading
     11 import time
     12 
     13 from autotest_lib.client.bin import test, utils
     14 from autotest_lib.client.common_lib import error
     15 from autotest_lib.client.common_lib.cros import chrome
     16 from autotest_lib.client.cros.graphics import graphics_utils
     17 from autotest_lib.client.cros import power_status, power_utils
     18 from autotest_lib.client.cros import service_stopper
     19 
     20 # Minimum battery charge percentage to run the test
     21 BATTERY_INITIAL_CHARGED_MIN = 10
     22 
     23 # Measurement duration in seconds.
     24 MEASUREMENT_DURATION = 30
     25 
     26 POWER_DESCRIPTION = 'avg_energy_rate_1000_fishes'
     27 
     28 # Time to exclude from calculation after playing a webgl demo [seconds].
     29 STABILIZATION_DURATION = 10
     30 
     31 
     32 class graphics_WebGLAquarium(test.test):
     33     """WebGL aquarium graphics test."""
     34     version = 1
     35 
     36     _backlight = None
     37     _power_status = None
     38     _service_stopper = None
     39     _test_power = False
     40     active_tab = None
     41     flip_stats = {}
     42     GSC = None
     43     kernel_sampler = None
     44     perf_keyval = {}
     45     sampler_lock = None
     46     test_duration_secs = 30
     47     test_setting_num_fishes = 50
     48     test_settings = {50: ('setSetting2', 2), 1000: ('setSetting6', 6),}
     49 
     50     def setup(self):
     51         tarball_path = os.path.join(self.bindir,
     52                                     'webgl_aquarium_static.tar.bz2')
     53         utils.extract_tarball_to_dir(tarball_path, self.srcdir)
     54 
     55     def initialize(self):
     56         self.GSC = graphics_utils.GraphicsStateChecker()
     57         self.sampler_lock = threading.Lock()
     58         # TODO: Create samplers for other platforms (e.g. x86).
     59         if utils.get_board().lower() in ['daisy', 'daisy_spring']:
     60             # Enable ExynosSampler on Exynos platforms.  The sampler looks for
     61             # exynos-drm page flip states: 'wait_kds', 'rendered', 'prepared',
     62             # and 'flipped' in kernel debugfs.
     63 
     64             # Sample 3-second durtaion for every 5 seconds.
     65             self.kernel_sampler = sampler.ExynosSampler(period=5, duration=3)
     66             self.kernel_sampler.sampler_callback = self.exynos_sampler_callback
     67             self.kernel_sampler.output_flip_stats = (
     68                 self.exynos_output_flip_stats)
     69 
     70     def cleanup(self):
     71         if self._backlight:
     72             self._backlight.restore()
     73         if self._service_stopper:
     74             self._service_stopper.restore_services()
     75         if self.GSC:
     76             keyvals = self.GSC.get_memory_keyvals()
     77             if not self._test_power:
     78                 for key, val in keyvals.iteritems():
     79                     self.output_perf_value(description=key,
     80                                            value=val,
     81                                            units='bytes',
     82                                            higher_is_better=False)
     83             self.GSC.finalize()
     84             self.write_perf_keyval(keyvals)
     85 
     86     def run_fish_test(self, browser, test_url, num_fishes, perf_log=True):
     87         """Run the test with the given number of fishes.
     88 
     89         @param browser: The Browser object to run the test with.
     90         @param test_url: The URL to the aquarium test site.
     91         @param num_fishes: The number of fishes to run the test with.
     92         @param perf_log: Report perf data only if it's set to True.
     93         """
     94         # Create tab and load page. Set the number of fishes when page is fully
     95         # loaded.
     96         tab = browser.tabs.New()
     97         tab.Navigate(test_url)
     98         tab.Activate()
     99         self.active_tab = tab
    100         tab.WaitForDocumentReadyStateToBeComplete()
    101 
    102         # Set the number of fishes when document finishes loading.  Also reset
    103         # our own FPS counter and start recording FPS and rendering time.
    104         utils.wait_for_value(
    105             lambda: tab.EvaluateJavaScript(
    106                 'if (document.readyState === "complete") {'
    107                 '  setSetting(document.getElementById("%s"), %d);'
    108                 '  g_crosFpsCounter.reset();'
    109                 '  true;'
    110                 '} else {'
    111                 '  false;'
    112                 '}' % self.test_settings[num_fishes]),
    113             expected_value=True,
    114             timeout_sec=30)
    115 
    116         if self.kernel_sampler:
    117             self.kernel_sampler.start_sampling_thread()
    118         time.sleep(self.test_duration_secs)
    119         if self.kernel_sampler:
    120             self.kernel_sampler.stop_sampling_thread()
    121             self.kernel_sampler.output_flip_stats('flip_stats_%d' % num_fishes)
    122             self.flip_stats = {}
    123 
    124         if perf_log:
    125             # Get average FPS and rendering time, then close the tab.
    126             avg_fps = tab.EvaluateJavaScript('g_crosFpsCounter.getAvgFps();')
    127             if math.isnan(float(avg_fps)):
    128                 raise error.TestFail('Could not get FPS count.')
    129             avg_render_time = tab.EvaluateJavaScript(
    130                 'g_crosFpsCounter.getAvgRenderTime();')
    131             self.perf_keyval['avg_fps_%04d_fishes' % num_fishes] = avg_fps
    132             self.perf_keyval['avg_render_time_%04d_fishes' % num_fishes] = (
    133                 avg_render_time)
    134             self.output_perf_value(
    135                 description='avg_fps_%04d_fishes' % num_fishes,
    136                 value=avg_fps,
    137                 units='fps',
    138                 higher_is_better=True)
    139             logging.info('%d fish(es): Average FPS = %f, '
    140                          'average render time = %f', num_fishes, avg_fps,
    141                          avg_render_time)
    142 
    143     def run_power_test(self, browser, test_url):
    144         """Runs the webgl power consumption test and reports the perf results.
    145 
    146         @param browser: The Browser object to run the test with.
    147         @param test_url: The URL to the aquarium test site.
    148         """
    149 
    150         self._backlight = power_utils.Backlight()
    151         self._backlight.set_default()
    152 
    153         self._service_stopper = service_stopper.ServiceStopper(
    154             service_stopper.ServiceStopper.POWER_DRAW_SERVICES)
    155         self._service_stopper.stop_services()
    156 
    157         self._power_status = power_status.get_status()
    158         # Verify that we are running on battery and the battery is sufficiently
    159         # charged.
    160         self._power_status.assert_battery_state(BATTERY_INITIAL_CHARGED_MIN)
    161 
    162         measurements = [
    163             power_status.SystemPower(self._power_status.battery_path)
    164         ]
    165 
    166         def get_power():
    167             power_logger = power_status.PowerLogger(measurements)
    168             power_logger.start()
    169             time.sleep(STABILIZATION_DURATION)
    170             start_time = time.time()
    171             time.sleep(MEASUREMENT_DURATION)
    172             power_logger.checkpoint('result', start_time)
    173             keyval = power_logger.calc()
    174             logging.info('Power output %s', keyval)
    175             return keyval['result_' + measurements[0].domain + '_pwr']
    176 
    177         self.run_fish_test(browser, test_url, 1000, perf_log=False)
    178         energy_rate = get_power()
    179         # This is a power specific test so we are not capturing
    180         # avg_fps and avg_render_time in this test.
    181         self.perf_keyval[POWER_DESCRIPTION] = energy_rate
    182         self.output_perf_value(description=POWER_DESCRIPTION,
    183                                value=energy_rate,
    184                                units='W',
    185                                higher_is_better=False)
    186 
    187     def exynos_sampler_callback(self, sampler_obj):
    188         """Sampler callback function for ExynosSampler.
    189 
    190         @param sampler_obj: The ExynosSampler object that invokes this callback
    191                 function.
    192         """
    193         if sampler_obj.stopped:
    194             return
    195 
    196         with self.sampler_lock:
    197             now = time.time()
    198             results = {}
    199             info_str = ['\nfb_id wait_kds flipped']
    200             for value in sampler_obj.frame_buffers.itervalues():
    201                 results[value.fb] = {}
    202                 for state, stats in value.states.iteritems():
    203                     results[value.fb][state] = (stats.avg, stats.stdev)
    204                 info_str.append('%s: %s %s' %
    205                                 (value.fb, results[value.fb]['wait_kds'][0],
    206                                  results[value.fb]['flipped'][0]))
    207             results['avg_fps'] = self.active_tab.EvaluateJavaScript(
    208                 'g_crosFpsCounter.getAvgFps();')
    209             results['avg_render_time'] = self.active_tab.EvaluateJavaScript(
    210                 'g_crosFpsCounter.getAvgRenderTime();')
    211             self.active_tab.ExecuteJavaScript('g_crosFpsCounter.reset();')
    212             info_str.append('avg_fps: %s, avg_render_time: %s' %
    213                             (results['avg_fps'], results['avg_render_time']))
    214             self.flip_stats[now] = results
    215             logging.info('\n'.join(info_str))
    216 
    217     def exynos_output_flip_stats(self, file_name):
    218         """Pageflip statistics output function for ExynosSampler.
    219 
    220         @param file_name: The output file name.
    221         """
    222         # output format:
    223         # time fb_id avg_rendered avg_prepared avg_wait_kds avg_flipped
    224         # std_rendered std_prepared std_wait_kds std_flipped
    225         with open(file_name, 'w') as f:
    226             for t in sorted(self.flip_stats.keys()):
    227                 if ('avg_fps' in self.flip_stats[t] and
    228                         'avg_render_time' in self.flip_stats[t]):
    229                     f.write('%s %s %s\n' %
    230                             (t, self.flip_stats[t]['avg_fps'],
    231                              self.flip_stats[t]['avg_render_time']))
    232                 for fb, stats in self.flip_stats[t].iteritems():
    233                     if not isinstance(fb, int):
    234                         continue
    235                     f.write('%s %s ' % (t, fb))
    236                     f.write('%s %s %s %s ' % (stats['rendered'][0],
    237                                               stats['prepared'][0],
    238                                               stats['wait_kds'][0],
    239                                               stats['flipped'][0]))
    240                     f.write('%s %s %s %s\n' % (stats['rendered'][1],
    241                                                stats['prepared'][1],
    242                                                stats['wait_kds'][1],
    243                                                stats['flipped'][1]))
    244     def run_once(self,
    245                  test_duration_secs=30,
    246                  test_setting_num_fishes=(50, 1000),
    247                  power_test=False):
    248         """Find a brower with telemetry, and run the test.
    249 
    250         @param test_duration_secs: The duration in seconds to run each scenario
    251                 for.
    252         @param test_setting_num_fishes: A list of the numbers of fishes to
    253                 enable in the test.
    254         """
    255         self.test_duration_secs = test_duration_secs
    256         self.test_setting_num_fishes = test_setting_num_fishes
    257 
    258         with chrome.Chrome(logged_in=False) as cr:
    259             cr.browser.platform.SetHTTPServerDirectories(self.srcdir)
    260             test_url = cr.browser.platform.http_server.UrlOf(os.path.join(
    261                 self.srcdir, 'aquarium.html'))
    262 
    263             if not utils.wait_for_idle_cpu(60.0, 0.1):
    264                 if not utils.wait_for_idle_cpu(20.0, 0.2):
    265                     raise error.TestFail('Could not get idle CPU.')
    266             if not utils.wait_for_cool_machine():
    267                 raise error.TestFail('Could not get cold machine.')
    268             if power_test:
    269                 self._test_power = True
    270                 self.run_power_test(cr.browser, test_url)
    271                 with self.sampler_lock:
    272                     self.active_tab.Close()
    273                     self.active_tab = None
    274             else:
    275                 for n in self.test_setting_num_fishes:
    276                     self.run_fish_test(cr.browser, test_url, n)
    277                     # Do not close the tab when the sampler_callback is doing
    278                     # his work.
    279                     with self.sampler_lock:
    280                         self.active_tab.Close()
    281                         self.active_tab = None
    282         self.write_perf_keyval(self.perf_keyval)
    283