Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(1007)

Side by Side Diff: build/android/pylib/device/battery_utils.py

Issue 1108173002: Roll //build, //native_client, and a few more targets of opportunity. Base URL: git@github.com:domokit/mojo.git@master
Patch Set: Test fix Created 5 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 # Copyright 2015 The Chromium 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 """Provides a variety of device interactions with power.
6 """
7 # pylint: disable=unused-argument
8
9 import collections
10 import contextlib
11 import csv
12 import logging
13
14 from pylib import constants
15 from pylib.device import decorators
16 from pylib.device import device_errors
17 from pylib.device import device_utils
18 from pylib.utils import timeout_retry
19
20 _DEFAULT_TIMEOUT = 30
21 _DEFAULT_RETRIES = 3
22
23 _CONTROL_CHARGING_COMMANDS = [
24 {
25 # Nexus 4
26 'witness_file': '/sys/module/pm8921_charger/parameters/disabled',
27 'enable_command': 'echo 0 > /sys/module/pm8921_charger/parameters/disabled',
28 'disable_command':
29 'echo 1 > /sys/module/pm8921_charger/parameters/disabled',
30 },
31 {
32 # Nexus 5
33 # Setting the HIZ bit of the bq24192 causes the charger to actually ignore
34 # energy coming from USB. Setting the power_supply offline just updates the
35 # Android system to reflect that.
36 'witness_file': '/sys/kernel/debug/bq24192/INPUT_SRC_CONT',
37 'enable_command': (
38 'echo 0x4A > /sys/kernel/debug/bq24192/INPUT_SRC_CONT && '
39 'echo 1 > /sys/class/power_supply/usb/online'),
40 'disable_command': (
41 'echo 0xCA > /sys/kernel/debug/bq24192/INPUT_SRC_CONT && '
42 'chmod 644 /sys/class/power_supply/usb/online && '
43 'echo 0 > /sys/class/power_supply/usb/online'),
44 },
45 ]
46
47 # The list of useful dumpsys columns.
48 # Index of the column containing the format version.
49 _DUMP_VERSION_INDEX = 0
50 # Index of the column containing the type of the row.
51 _ROW_TYPE_INDEX = 3
52 # Index of the column containing the uid.
53 _PACKAGE_UID_INDEX = 4
54 # Index of the column containing the application package.
55 _PACKAGE_NAME_INDEX = 5
56 # The column containing the uid of the power data.
57 _PWI_UID_INDEX = 1
58 # The column containing the type of consumption. Only consumtion since last
59 # charge are of interest here.
60 _PWI_AGGREGATION_INDEX = 2
61 # The column containing the amount of power used, in mah.
62 _PWI_POWER_CONSUMPTION_INDEX = 5
63
64
65 class BatteryUtils(object):
66
67 def __init__(self, device, default_timeout=_DEFAULT_TIMEOUT,
68 default_retries=_DEFAULT_RETRIES):
69 """BatteryUtils constructor.
70
71 Args:
72 device: A DeviceUtils instance.
73 default_timeout: An integer containing the default number of seconds to
74 wait for an operation to complete if no explicit value
75 is provided.
76 default_retries: An integer containing the default number or times an
77 operation should be retried on failure if no explicit
78 value is provided.
79
80 Raises:
81 TypeError: If it is not passed a DeviceUtils instance.
82 """
83 if not isinstance(device, device_utils.DeviceUtils):
84 raise TypeError('Must be initialized with DeviceUtils object.')
85 self._device = device
86 self._cache = device.GetClientCache(self.__class__.__name__)
87 self._default_timeout = default_timeout
88 self._default_retries = default_retries
89
90 @decorators.WithTimeoutAndRetriesFromInstance()
91 def GetNetworkData(self, package, timeout=None, retries=None):
92 """ Get network data for specific package.
93
94 Args:
95 package: package name you want network data for.
96 timeout: timeout in seconds
97 retries: number of retries
98
99 Returns:
100 Tuple of (sent_data, recieved_data)
101 None if no network data found
102 """
103 # If device_utils clears cache, cache['uids'] doesn't exist
104 if 'uids' not in self._cache:
105 self._cache['uids'] = {}
106 if package not in self._cache['uids']:
107 self.GetPowerData()
108 if package not in self._cache['uids']:
109 logging.warning('No UID found for %s. Can\'t get network data.',
110 package)
111 return None
112
113 network_data_path = '/proc/uid_stat/%s/' % self._cache['uids'][package]
114 try:
115 send_data = int(self._device.ReadFile(network_data_path + 'tcp_snd'))
116 # If ReadFile throws exception, it means no network data usage file for
117 # package has been recorded. Return 0 sent and 0 received.
118 except device_errors.AdbShellCommandFailedError:
119 logging.warning('No sent data found for package %s', package)
120 send_data = 0
121 try:
122 recv_data = int(self._device.ReadFile(network_data_path + 'tcp_rcv'))
123 except device_errors.AdbShellCommandFailedError:
124 logging.warning('No received data found for package %s', package)
125 recv_data = 0
126 return (send_data, recv_data)
127
128 @decorators.WithTimeoutAndRetriesFromInstance()
129 def GetPowerData(self, timeout=None, retries=None):
130 """ Get power data for device.
131 Args:
132 timeout: timeout in seconds
133 retries: number of retries
134
135 Returns:
136 Dict of power data, keyed on package names.
137 {
138 package_name: {
139 'uid': uid,
140 'data': [1,2,3]
141 },
142 }
143 """
144 if 'uids' not in self._cache:
145 self._cache['uids'] = {}
146 dumpsys_output = self._device.RunShellCommand(
147 ['dumpsys', 'batterystats', '-c'], check_return=True)
148 csvreader = csv.reader(dumpsys_output)
149 pwi_entries = collections.defaultdict(list)
150 for entry in csvreader:
151 if entry[_DUMP_VERSION_INDEX] not in ['8', '9']:
152 # Wrong dumpsys version.
153 raise device_errors.DeviceVersionError(
154 'Dumpsys version must be 8 or 9. %s found.'
155 % entry[_DUMP_VERSION_INDEX])
156 if _ROW_TYPE_INDEX < len(entry) and entry[_ROW_TYPE_INDEX] == 'uid':
157 current_package = entry[_PACKAGE_NAME_INDEX]
158 if (self._cache['uids'].get(current_package)
159 and self._cache['uids'].get(current_package)
160 != entry[_PACKAGE_UID_INDEX]):
161 raise device_errors.CommandFailedError(
162 'Package %s found multiple times with differnt UIDs %s and %s'
163 % (current_package, self._cache['uids'][current_package],
164 entry[_PACKAGE_UID_INDEX]))
165 self._cache['uids'][current_package] = entry[_PACKAGE_UID_INDEX]
166 elif (_PWI_POWER_CONSUMPTION_INDEX < len(entry)
167 and entry[_ROW_TYPE_INDEX] == 'pwi'
168 and entry[_PWI_AGGREGATION_INDEX] == 'l'):
169 pwi_entries[entry[_PWI_UID_INDEX]].append(
170 float(entry[_PWI_POWER_CONSUMPTION_INDEX]))
171
172 return {p: {'uid': uid, 'data': pwi_entries[uid]}
173 for p, uid in self._cache['uids'].iteritems()}
174
175 @decorators.WithTimeoutAndRetriesFromInstance()
176 def GetPackagePowerData(self, package, timeout=None, retries=None):
177 """ Get power data for particular package.
178
179 Args:
180 package: Package to get power data on.
181
182 returns:
183 Dict of UID and power data.
184 {
185 'uid': uid,
186 'data': [1,2,3]
187 }
188 None if the package is not found in the power data.
189 """
190 return self.GetPowerData().get(package)
191
192 @decorators.WithTimeoutAndRetriesFromInstance()
193 def GetBatteryInfo(self, timeout=None, retries=None):
194 """Gets battery info for the device.
195
196 Args:
197 timeout: timeout in seconds
198 retries: number of retries
199 Returns:
200 A dict containing various battery information as reported by dumpsys
201 battery.
202 """
203 result = {}
204 # Skip the first line, which is just a header.
205 for line in self._device.RunShellCommand(
206 ['dumpsys', 'battery'], check_return=True)[1:]:
207 # If usb charging has been disabled, an extra line of header exists.
208 if 'UPDATES STOPPED' in line:
209 logging.warning('Dumpsys battery not receiving updates. '
210 'Run dumpsys battery reset if this is in error.')
211 elif ':' not in line:
212 logging.warning('Unknown line found in dumpsys battery: "%s"', line)
213 else:
214 k, v = line.split(':', 1)
215 result[k.strip()] = v.strip()
216 return result
217
218 @decorators.WithTimeoutAndRetriesFromInstance()
219 def GetCharging(self, timeout=None, retries=None):
220 """Gets the charging state of the device.
221
222 Args:
223 timeout: timeout in seconds
224 retries: number of retries
225 Returns:
226 True if the device is charging, false otherwise.
227 """
228 battery_info = self.GetBatteryInfo()
229 for k in ('AC powered', 'USB powered', 'Wireless powered'):
230 if (k in battery_info and
231 battery_info[k].lower() in ('true', '1', 'yes')):
232 return True
233 return False
234
235 @decorators.WithTimeoutAndRetriesFromInstance()
236 def SetCharging(self, enabled, timeout=None, retries=None):
237 """Enables or disables charging on the device.
238
239 Args:
240 enabled: A boolean indicating whether charging should be enabled or
241 disabled.
242 timeout: timeout in seconds
243 retries: number of retries
244
245 Raises:
246 device_errors.CommandFailedError: If method of disabling charging cannot
247 be determined.
248 """
249 if 'charging_config' not in self._cache:
250 for c in _CONTROL_CHARGING_COMMANDS:
251 if self._device.FileExists(c['witness_file']):
252 self._cache['charging_config'] = c
253 break
254 else:
255 raise device_errors.CommandFailedError(
256 'Unable to find charging commands.')
257
258 if enabled:
259 command = self._cache['charging_config']['enable_command']
260 else:
261 command = self._cache['charging_config']['disable_command']
262
263 def set_and_verify_charging():
264 self._device.RunShellCommand(command, check_return=True)
265 return self.GetCharging() == enabled
266
267 timeout_retry.WaitFor(set_and_verify_charging, wait_period=1)
268
269 # TODO(rnephew): Make private when all use cases can use the context manager.
270 @decorators.WithTimeoutAndRetriesFromInstance()
271 def DisableBatteryUpdates(self, timeout=None, retries=None):
272 """ Resets battery data and makes device appear like it is not
273 charging so that it will collect power data since last charge.
274
275 Args:
276 timeout: timeout in seconds
277 retries: number of retries
278
279 Raises:
280 device_errors.CommandFailedError: When resetting batterystats fails to
281 reset power values.
282 device_errors.DeviceVersionError: If device is not L or higher.
283 """
284 def battery_updates_disabled():
285 return self.GetCharging() is False
286
287 if (self._device.build_version_sdk <
288 constants.ANDROID_SDK_VERSION_CODES.LOLLIPOP):
289 raise device_errors.DeviceVersionError('Device must be L or higher.')
290
291 self._device.RunShellCommand(
292 ['dumpsys', 'battery', 'reset'], check_return=True)
293 self._device.RunShellCommand(
294 ['dumpsys', 'batterystats', '--reset'], check_return=True)
295 battery_data = self._device.RunShellCommand(
296 ['dumpsys', 'batterystats', '--charged', '--checkin'],
297 check_return=True)
298 ROW_TYPE_INDEX = 3
299 PWI_POWER_INDEX = 5
300 for line in battery_data:
301 l = line.split(',')
302 if (len(l) > PWI_POWER_INDEX and l[ROW_TYPE_INDEX] == 'pwi'
303 and l[PWI_POWER_INDEX] != 0):
304 raise device_errors.CommandFailedError(
305 'Non-zero pmi value found after reset.')
306 self._device.RunShellCommand(['dumpsys', 'battery', 'set', 'ac', '0'],
307 check_return=True)
308 self._device.RunShellCommand(['dumpsys', 'battery', 'set', 'usb', '0'],
309 check_return=True)
310 timeout_retry.WaitFor(battery_updates_disabled, wait_period=1)
311
312 # TODO(rnephew): Make private when all use cases can use the context manager.
313 @decorators.WithTimeoutAndRetriesFromInstance()
314 def EnableBatteryUpdates(self, timeout=None, retries=None):
315 """ Restarts device charging so that dumpsys no longer collects power data.
316
317 Args:
318 timeout: timeout in seconds
319 retries: number of retries
320
321 Raises:
322 device_errors.DeviceVersionError: If device is not L or higher.
323 """
324 def battery_updates_enabled():
325 return self.GetCharging() is True
326
327 if (self._device.build_version_sdk <
328 constants.ANDROID_SDK_VERSION_CODES.LOLLIPOP):
329 raise device_errors.DeviceVersionError('Device must be L or higher.')
330
331 self._device.RunShellCommand(['dumpsys', 'battery', 'reset'],
332 check_return=True)
333 timeout_retry.WaitFor(battery_updates_enabled, wait_period=1)
334
335 @contextlib.contextmanager
336 def BatteryMeasurement(self, timeout=None, retries=None):
337 """Context manager that enables battery data collection. It makes
338 the device appear to stop charging so that dumpsys will start collecting
339 power data since last charge. Once the with block is exited, charging is
340 resumed and power data since last charge is no longer collected.
341
342 Only for devices L and higher.
343
344 Example usage:
345 with BatteryMeasurement():
346 browser_actions()
347 get_power_data() # report usage within this block
348 after_measurements() # Anything that runs after power
349 # measurements are collected
350
351 Args:
352 timeout: timeout in seconds
353 retries: number of retries
354
355 Raises:
356 device_errors.DeviceVersionError: If device is not L or higher.
357 """
358 if (self._device.build_version_sdk <
359 constants.ANDROID_SDK_VERSION_CODES.LOLLIPOP):
360 raise device_errors.DeviceVersionError('Device must be L or higher.')
361 try:
362 self.DisableBatteryUpdates(timeout=timeout, retries=retries)
363 yield
364 finally:
365 self.EnableBatteryUpdates(timeout=timeout, retries=retries)
366
367 def ChargeDeviceToLevel(self, level, wait_period=60):
368 """Enables charging and waits for device to be charged to given level.
369
370 Args:
371 level: level of charge to wait for.
372 wait_period: time in seconds to wait between checking.
373 """
374 self.SetCharging(True)
375
376 def device_charged():
377 battery_level = self.GetBatteryInfo().get('level')
378 if battery_level is None:
379 logging.warning('Unable to find current battery level.')
380 battery_level = 100
381 else:
382 logging.info('current battery level: %s', battery_level)
383 battery_level = int(battery_level)
384 return battery_level >= level
385
386 timeout_retry.WaitFor(device_charged, wait_period=wait_period)
OLDNEW
« no previous file with comments | « build/android/pylib/device/adb_wrapper_test.py ('k') | build/android/pylib/device/battery_utils_test.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698