Chromium Code Reviews| OLD | NEW |
|---|---|
| (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 self._device = device | |
| 84 if not isinstance(device, device_utils.DeviceUtils): | |
| 85 raise TypeError('Must be initialized with DeviceUtils object.') | |
| 86 self._default_timeout = default_timeout | |
| 87 self._default_retries = default_retries | |
| 88 | |
| 89 @decorators.WithTimeoutAndRetriesFromInstance() | |
| 90 def GetPowerData(self, timeout=None, retries=None): | |
| 91 """ Get power data for device. | |
| 92 Args: | |
| 93 timeout: timeout in seconds | |
| 94 retries: number of retries | |
| 95 | |
| 96 Returns: | |
| 97 Dict of power data, keyed on package names. | |
| 98 { | |
| 99 package_name: { | |
| 100 'uid': uid, | |
| 101 'data': [1,2,3] | |
| 102 }, | |
| 103 } | |
| 104 """ | |
| 105 dumpsys_output = self._device.RunShellCommand( | |
| 106 ['dumpsys', 'batterystats', '-c'], check_return=True) | |
| 107 csvreader = csv.reader(dumpsys_output) | |
| 108 uid_entries = {} | |
| 109 pwi_entries = collections.defaultdict(list) | |
| 110 for entry in csvreader: | |
| 111 if entry[_DUMP_VERSION_INDEX] not in ['8', '9']: | |
| 112 # Wrong dumpsys version. | |
| 113 raise device_errors.DeviceVersionError( | |
| 114 'Dumpsys version must be 8 or 9. %s found.' | |
| 115 % entry[_DUMP_VERSION_INDEX]) | |
| 116 if _ROW_TYPE_INDEX >= len(entry): | |
| 117 continue | |
| 118 if entry[_ROW_TYPE_INDEX] == 'uid': | |
| 119 current_package = entry[_PACKAGE_NAME_INDEX] | |
| 120 if current_package in uid_entries: | |
| 121 raise device_errors.CommandFailedError( | |
| 122 'Package %s found multiple times' % (current_package)) | |
| 123 uid_entries[current_package] = entry[_PACKAGE_UID_INDEX] | |
| 124 elif (_PWI_POWER_CONSUMPTION_INDEX < len(entry) | |
| 125 and entry[_ROW_TYPE_INDEX] == 'pwi' | |
| 126 and entry[_PWI_AGGREGATION_INDEX] == 'l'): | |
| 127 pwi_entries[entry[_PWI_UID_INDEX]].append( | |
| 128 float(entry[_PWI_POWER_CONSUMPTION_INDEX])) | |
| 129 | |
| 130 out_dict = {} | |
|
perezju
2015/03/30 11:05:23
maybe something like this would be more concise an
rnephew (Wrong account)
2015/03/30 17:50:28
Done.
| |
| 131 for p in uid_entries: | |
| 132 out_dict[p] = {} | |
| 133 out_dict[p]['uid'] = uid_entries[p] | |
| 134 out_dict[p]['data'] = pwi_entries[uid_entries[p]] | |
| 135 return out_dict | |
| 136 | |
| 137 def GetPackagePowerData(self, package, timeout=None, retries=None): | |
| 138 """ Get power data for particular package. | |
| 139 | |
| 140 Args: | |
| 141 package: Package to get power data on. | |
| 142 | |
| 143 returns: | |
| 144 Dict of UID and power data. | |
| 145 { | |
| 146 'uid': uid, | |
| 147 'data': [1,2,3] | |
| 148 } | |
| 149 None if the package is not found in the power data. | |
| 150 """ | |
| 151 return self.GetPowerData().get(package) | |
| 152 | |
| 153 # TODO(rnephew): Move implementation from device_utils when this is used. | |
| 154 def GetBatteryInfo(self, timeout=None, retries=None): | |
| 155 """Gets battery info for the device. | |
| 156 | |
| 157 Args: | |
| 158 timeout: timeout in seconds | |
| 159 retries: number of retries | |
| 160 Returns: | |
| 161 A dict containing various battery information as reported by dumpsys | |
| 162 battery. | |
| 163 """ | |
| 164 return self._device.GetBatteryInfo(timeout=None, retries=None) | |
| 165 | |
| 166 # TODO(rnephew): Move implementation from device_utils when this is used. | |
| 167 def GetCharging(self, timeout=None, retries=None): | |
| 168 """Gets the charging state of the device. | |
| 169 | |
| 170 Args: | |
| 171 timeout: timeout in seconds | |
| 172 retries: number of retries | |
| 173 Returns: | |
| 174 True if the device is charging, false otherwise. | |
| 175 """ | |
| 176 return self._device.GetCharging(timeout=None, retries=None) | |
| 177 | |
| 178 # TODO(rnephew): Move implementation from device_utils when this is used. | |
| 179 def SetCharging(self, enabled, timeout=None, retries=None): | |
| 180 """Enables or disables charging on the device. | |
| 181 | |
| 182 Args: | |
| 183 enabled: A boolean indicating whether charging should be enabled or | |
| 184 disabled. | |
| 185 timeout: timeout in seconds | |
| 186 retries: number of retries | |
| 187 | |
| 188 Raises: | |
| 189 device_errors.CommandFailedError: If method of disabling charging cannot | |
| 190 be determined. | |
| 191 """ | |
| 192 self._device.SetCharging(enabled, timeout=None, retries=None) | |
| 193 | |
| 194 # TODO(rnephew): Move implementation from device_utils when this is used. | |
| 195 # TODO(rnephew): Make private when all use cases can use the context manager. | |
| 196 def DisableBatteryUpdates(self, timeout=None, retries=None): | |
| 197 """ Resets battery data and makes device appear like it is not | |
| 198 charging so that it will collect power data since last charge. | |
| 199 | |
| 200 Args: | |
| 201 timeout: timeout in seconds | |
| 202 retries: number of retries | |
| 203 | |
| 204 Raises: | |
| 205 device_errors.CommandFailedError: When resetting batterystats fails to | |
| 206 reset power values. | |
| 207 """ | |
| 208 self._device.DisableBatteryUpdates(timeout=None, retries=None) | |
| 209 | |
| 210 # TODO(rnephew): Move implementation from device_utils when this is used. | |
| 211 # TODO(rnephew): Make private when all use cases can use the context manager. | |
| 212 def EnableBatteryUpdates(self, timeout=None, retries=None): | |
| 213 """ Restarts device charging so that dumpsys no longer collects power data. | |
| 214 | |
| 215 Args: | |
| 216 timeout: timeout in seconds | |
| 217 retries: number of retries | |
| 218 """ | |
| 219 self._device.EnableBatteryUpdates(timeout=None, retries=None) | |
| 220 | |
| 221 @contextlib.contextmanager | |
| 222 def BatteryMeasurement(self, timeout=None, retries=None): | |
| 223 """Context manager that enables battery data collection. It makes | |
| 224 the device appear to stop charging so that dumpsys will start collecting | |
| 225 power data since last charge. Once the with block is exited, charging is | |
| 226 resumed and power data since last charge is no longer collected. | |
| 227 | |
| 228 Only for devices L and higher. | |
| 229 | |
| 230 Example usage: | |
| 231 with BatteryMeasurement(): | |
| 232 browser_actions() | |
| 233 get_power_data() # report usage within this block | |
| 234 after_measurements() # Anything that runs after power | |
| 235 # measurements are collected | |
| 236 | |
| 237 Args: | |
| 238 timeout: timeout in seconds | |
| 239 retries: number of retries | |
| 240 | |
| 241 Raises: | |
| 242 device_errors.CommandFailedError: If device is not L or higher. | |
| 243 """ | |
| 244 if (self._device.build_version_sdk < | |
| 245 constants.ANDROID_SDK_VERSION_CODES.LOLLIPOP): | |
| 246 raise device_errors.CommandFailedError('Device must be L or higher.') | |
| 247 try: | |
| 248 self.DisableBatteryUpdates(timeout=timeout, retries=retries) | |
| 249 yield | |
| 250 finally: | |
| 251 self.EnableBatteryUpdates(timeout=timeout, retries=retries) | |
| OLD | NEW |