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