power_LoadTest.py revision 01d3c6edb4c352e83cd491bfdaf1aed5595727be
1# Copyright (c) 2010 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
5import logging, os, shutil, sys, time
6from autotest_lib.client.bin import site_backchannel, site_ui_test, site_login
7from autotest_lib.client.common_lib import error, site_httpd, \
8                            site_power_status, site_ui, utils
9
10sys.path.append(os.environ.get('SYSROOT', '') + '/usr/local/lib/flimflam/test')
11import flimflam
12
13
14params_dict = {
15    'test_time_ms': '_mseconds',
16    'should_scroll': '_should_scroll',
17    'should_scroll_up': '_should_scroll_up',
18    'scroll_loop': '_scroll_loop',
19    'scroll_interval_ms': '_scroll_interval_ms',
20    'scroll_by_pixels': '_scroll_by_pixels',
21}
22
23
24class power_LoadTest(site_ui_test.UITest):
25    version = 2
26
27    def ensure_login_complete(self):
28        """
29        Override site_ui_test.UITest's ensure_login_complete.
30        Do not use auth server and local dns for our test. We need to be
31        able to reach the web.
32        """
33        pass
34
35    def initialize(self, creds='$default', percent_initial_charge_min=None,
36                 check_network=True, loop_time=3600, loop_count=1,
37                 should_scroll='true', should_scroll_up='true',
38                 scroll_loop='false', scroll_interval_ms='10000',
39                 scroll_by_pixels='600', low_battery_threshold=3,
40                 verbose=True, force_wifi=False, wifi_ap='', wifi_sec='none',
41                 wifi_pw=''):
42
43        """
44        percent_initial_charge_min: min battery charge at start of test
45        check_network: check that Ethernet interface is not running
46        loop_count: number of times to loop the test for
47        loop_time: length of time to run the test for in each loop
48        should_scroll: should the extension scroll pages
49        should_scroll_up: should scroll in up direction
50        scroll_loop: continue scrolling indefinitely
51        scroll_interval_ms: how often to scoll
52        scroll_by_pixels: number of pixels to scroll each time
53        """
54        self._loop_time = loop_time
55        self._loop_count = loop_count
56        self._mseconds = self._loop_time * 1000
57        self._verbose = verbose
58        self._low_battery_threshold = low_battery_threshold
59        self._should_scroll = should_scroll
60        self._should_scroll_up = should_scroll_up
61        self._scroll_loop = scroll_loop
62        self._scroll_interval_ms = scroll_interval_ms
63        self._scroll_by_pixels = scroll_by_pixels
64        self._tmp_keyvals = {}
65        self._power_status = site_power_status.get_status()
66        self._json_path = None
67
68        # verify that initial conditions are met:
69        if self._power_status.linepower[0].online:
70            raise error.TestError(
71                'Running on AC power. Please remove AC power cable')
72
73        percent_initial_charge = self._percent_current_charge()
74        if percent_initial_charge_min and percent_initial_charge < \
75                                          percent_initial_charge_min:
76            raise error.TestError('Initial charge (%f) less than min (%f)'
77                      % (percent_initial_charge, percent_initial_charge_min))
78
79        # If force wifi enabled, convert eth0 to backchannel and connect to the
80        # specified WiFi AP.
81        if force_wifi:
82            # If backchannel is already running, don't run it again.
83            if not site_backchannel.setup():
84                raise error.TestError('Could not setup Backchannel network.')
85
86            # Note: FlimFlam is flaky after Backchannel setup sometimes. It may
87            # take several tries for WiFi to connect. More experimentation with
88            # the retry settings here may be necessary if this becomes a source
89            # of test flakiness in the future.
90            if not flimflam.FlimFlam().ConnectService(retry=True,
91                                                      service_type='wifi',
92                                                      ssid=wifi_ap,
93                                                      security=wifi_sec,
94                                                      passphrase=wifi_pw,
95                                                      mode='managed')[0]:
96                raise error.TestError('Could not connect to WiFi network.')
97
98        if check_network and site_backchannel.is_network_iface_running('eth0'):
99            raise error.TestError(
100                'Ethernet interface is active. Please remove Ethernet cable')
101
102        # record the max backlight level
103        cmd = 'backlight-tool --get_max_brightness'
104        self._max_backlight = int(utils.system_output(cmd).rstrip())
105        self._tmp_keyvals['level_backlight_max'] = self._max_backlight
106
107        # fix up file perms for the power test extension so that chrome
108        # can access it
109        os.system('chmod -R 755 %s' % self.bindir)
110
111        # TODO (bleung) :
112        # The new external extension packed crx means we can't pass params by
113        # modifying params.js
114        # Possible solution :
115        # - modify extension to not start until we poke it from the browser.
116        #       then pass through URL.
117
118        # write test parameters to the power extension's params.js file
119        # self._ext_path = os.path.join(self.bindir, 'extension')
120        # self._write_ext_params()
121
122        # copy external_extensions.json to known location
123        self._json_path = os.path.join(self.bindir, '..')
124        shutil.copy(os.path.join(self.bindir, 'external_extensions.json'),
125                                 self._json_path)
126
127        # setup a HTTP Server to listen for status updates from the power
128        # test extension
129        self._testServer = site_httpd.HTTPListener(8001, docroot=self.bindir)
130        self._testServer.run()
131
132        # initialize various interesting power related stats
133        self._usb_stats = site_power_status.USBSuspendStats()
134        self._cpufreq_stats = site_power_status.CPUFreqStats()
135        self._cpuidle_stats = site_power_status.CPUIdleStats()
136
137
138        self._usb_stats.refresh()
139        self._cpufreq_stats.refresh()
140        self._cpuidle_stats.refresh()
141        self._power_status.refresh()
142
143        self._ah_charge_start = self._power_status.battery[0].charge_now
144        self._wh_energy_start = self._power_status.battery[0].energy
145
146        # from site_ui_test.UITest.initialize, sans authserver & local dns.
147        (self.username, self.password) = self._UITest__resolve_creds(creds)
148
149    def run_once(self):
150
151        t0 = time.time()
152
153        for i in range(self._loop_count):
154            # the power test extension will report its status here
155            latch = self._testServer.add_wait_url('/status')
156
157            if site_login.logged_in():
158                site_login.attempt_logout()
159            # the act of logging in will launch chrome with external extension.
160            self.login(self.username, self.password)
161
162            # stop powerd
163            os.system('stop powerd')
164
165            # reset X settings since X gets restarted upon login
166            self._do_xset()
167
168            # reset backlight level since powerd might've modified it
169            # based on ambient light
170            self._set_backlight_level()
171
172            low_battery = self._do_wait(self._verbose, self._loop_time,
173                                        latch)
174
175            if self._verbose:
176                logging.debug('loop %d completed' % i)
177
178            if low_battery:
179                logging.info('Exiting due to low battery')
180                break
181
182        t1 = time.time()
183        self._tmp_keyvals['minutes_battery_life'] = (t1 - t0) / 60
184
185
186    def postprocess_iteration(self):
187        keyvals = {}
188
189        # refresh power related statistics
190        usb_stats = self._usb_stats.refresh()
191        cpufreq_stats = self._cpufreq_stats.refresh()
192        cpuidle_stats = self._cpuidle_stats.refresh()
193
194        # record percent time USB devices were not in suspended state
195        keyvals['percent_usb_active'] = usb_stats
196
197        # record percent time spent in each CPU C-state
198        for state in cpuidle_stats:
199            keyvals['percent_cpuidle_%s_time' % state] = cpuidle_stats[state]
200
201        # record percent time spent at each CPU frequency
202        for freq in cpufreq_stats:
203            keyvals['percent_cpufreq_%s_time' % freq] = cpufreq_stats[freq]
204
205        # record battery stats
206        keyvals['a_current_now'] = self._power_status.battery[0].current_now
207        keyvals['ah_charge_full'] = self._power_status.battery[0].charge_full
208        keyvals['ah_charge_full_design'] = \
209                             self._power_status.battery[0].charge_full_design
210        keyvals['ah_charge_start'] = self._ah_charge_start
211        keyvals['ah_charge_now'] = self._power_status.battery[0].charge_now
212        keyvals['ah_charge_used'] = keyvals['ah_charge_start'] - \
213                                    keyvals['ah_charge_now']
214        keyvals['wh_energy_start'] = self._wh_energy_start
215        keyvals['wh_energy_now'] = self._power_status.battery[0].energy
216        keyvals['wh_energy_used'] = keyvals['wh_energy_start'] - \
217                                    keyvals['wh_energy_now']
218        keyvals['v_voltage_min_design'] = \
219                             self._power_status.battery[0].voltage_min_design
220        keyvals['v_voltage_now'] = self._power_status.battery[0].voltage_now
221
222        keyvals.update(self._tmp_keyvals)
223
224        keyvals['a_current_rate'] = keyvals['ah_charge_used'] * 60 / \
225                                    keyvals['minutes_battery_life']
226        keyvals['w_energy_rate'] = keyvals['wh_energy_used'] * 60 / \
227                                   keyvals['minutes_battery_life']
228
229        self.write_perf_keyval(keyvals)
230
231
232    def cleanup(self):
233        # remove json file after test to stop external extension launch.
234        if self._json_path:
235            jsonfile = os.path.join(self._json_path, 'external_extensions.json')
236            if os.path.exists(jsonfile):
237                os.system('rm -f %s' % jsonfile)
238        # re-enable powerd
239        os.system('start powerd')
240        if site_login.logged_in():
241            site_login.attempt_logout()
242
243
244    def _percent_current_charge(self):
245        return self._power_status.battery[0].charge_now * 100 / \
246               self._power_status.battery[0].charge_full_design
247
248
249    def _write_ext_params(self):
250        data = ''
251        template= 'var %s = %s;\n'
252        for k in params_dict:
253            data += template % (k, getattr(self, params_dict[k]))
254
255        filename = os.path.join(self._ext_path, 'params.js')
256        utils.open_write_close(filename, data)
257
258        logging.debug('filename ' + filename)
259        logging.debug(data)
260
261
262    def _do_wait(self, verbose, seconds, latch):
263        latched = False
264        low_battery = False
265        total_time = seconds + 60
266        elapsed_time = 0
267        wait_time = 60
268
269        while elapsed_time < total_time:
270            time.sleep(wait_time)
271            elapsed_time += wait_time
272
273            self._power_status.refresh()
274            if verbose:
275                logging.debug('ah_charge_now %f' \
276                    % self._power_status.battery[0].charge_now)
277                logging.debug('w_energy_rate %f' \
278                    % self._power_status.battery[0].energy_rate)
279
280            low_battery = (self._percent_current_charge() <
281                           self._low_battery_threshold)
282
283            latched = latch.is_set()
284
285            if latched or low_battery:
286                break
287
288        if latched:
289            # record chrome power extension stats
290            form_data = self._testServer.get_form_entries()
291            logging.debug(form_data)
292            for e in form_data:
293                key = 'ext_' + e
294                if key in self._tmp_keyvals:
295                    self._tmp_keyvals[key] += form_data[e]
296                else:
297                    self._tmp_keyvals[key] = form_data[e]
298        else:
299            logging.debug("Didn't get status back from power extension")
300
301        return low_battery
302
303
304    def _do_xset(self):
305        XSET = 'LD_LIBRARY_PATH=/usr/local/lib xset'
306        # Disable X screen saver
307        site_ui.xsystem('%s s 0 0' % XSET)
308        # Disable DPMS Standby/Suspend/Off
309        site_ui.xsystem('%s dpms 0 0 0' % XSET)
310        # Force monitor on
311        site_ui.xsystem('%s dpms force on' % XSET)
312        # Save off X settings
313        site_ui.xsystem('%s q' % XSET)
314
315
316    def _set_backlight_level(self):
317        # set backlight level to 40% of max
318        cmd = 'backlight-tool --set_brightness %d ' % (
319              int(self._max_backlight * 0.4))
320        os.system(cmd)
321
322        # record brightness level
323        cmd = 'backlight-tool --get_brightness'
324        level = int(utils.system_output(cmd).rstrip())
325        logging.info('backlight level is %d' % level)
326        self._tmp_keyvals['level_backlight_current'] = level
327