1c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch# Copyright 2014 The Chromium Authors. All rights reserved.
2c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch# Use of this source code is governed by a BSD-style license that can be
3c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch# found in the LICENSE file.
4c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch
5c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdochimport csv
66e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)import logging
7c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdochfrom collections import defaultdict
8c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch
903b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)from telemetry.core.platform.power_monitor import sysfs_power_monitor
106e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)
11c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch
1203b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)class DumpsysPowerMonitor(sysfs_power_monitor.SysfsPowerMonitor):
13c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch  """PowerMonitor that relies on the dumpsys batterystats to monitor the power
14c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch  consumption of a single android application. This measure uses a heuristic
15c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch  and is the same information end-users see with the battery application.
16c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch  """
171320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci  def __init__(self, device, platform_backend):
18c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch    """Constructor.
19c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch
20c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch    Args:
211320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci        device: A DeviceUtil instance.
221320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci        platform_backend: A LinuxBasedPlatformBackend instance.
23c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch    """
241320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    super(DumpsysPowerMonitor, self).__init__(platform_backend)
255c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    self._device = device
26c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch
27c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch  def CanMonitorPower(self):
285c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    return self._device.old_interface.CanControlUsbCharging()
29c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch
30c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch  def StartMonitoringPower(self, browser):
3103b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)    super(DumpsysPowerMonitor, self).StartMonitoringPower(browser)
32c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch    # Disable the charging of the device over USB. This is necessary because the
33c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch    # device only collects information about power usage when the device is not
34c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch    # charging.
355c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    self._device.old_interface.DisableUsbCharging()
36c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch
37c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch  def StopMonitoringPower(self):
3803b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)    if self._browser:
39c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch      # pylint: disable=W0212
40c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch      package = self._browser._browser_backend.package
4103b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)    cpu_stats = super(DumpsysPowerMonitor, self).StopMonitoringPower()
4203b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)    self._device.old_interface.EnableUsbCharging()
4303b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)    # By default, 'dumpsys batterystats' measures power consumption during the
4403b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)    # last unplugged period.
451320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    result = self._platform.RunCommand('dumpsys batterystats -c %s' % package)
4603b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)    assert result, 'Dumpsys produced no output'
4703b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)    return super(DumpsysPowerMonitor, self).CombineResults(
4803b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)        cpu_stats, DumpsysPowerMonitor.ParseSamplingOutput(package, result))
49c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch
50c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch  @staticmethod
51c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch  def ParseSamplingOutput(package, dumpsys_output):
52c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch    """Parse output of 'dumpsys batterystats -c'
53c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch
545f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    See:
555f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    https://android.googlesource.com/platform/frameworks/base/+/master/core/java/android/os/BatteryStats.java
565f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
57c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch    Returns:
58c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch        Dictionary in the format returned by StopMonitoringPower().
59c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch    """
60c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch    # Raw power usage samples.
61c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch    out_dict = {}
62c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch    out_dict['identifier'] = 'dumpsys'
63c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch    out_dict['power_samples_mw'] = []
64c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch
655f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    # The list of useful CSV columns.
665f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    # Index of the column containing the format version.
67c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch    DUMP_VERSION_INDEX = 0
685f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    # Index of the column containing the type of the row.
695f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    ROW_TYPE_INDEX = 3
705f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
715f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    # Index for columns of type unique identifier ('uid')
725f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    # Index of the column containing the uid.
73c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch    PACKAGE_UID_INDEX = 4
745f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    # Index of the column containing the application package.
755f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    PACKAGE_NAME_INDEX = 5
765f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
775f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    # Index for columns of type power use ('pwi')
785f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    # The column containing the uid of the item.
79c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch    PWI_UID_INDEX = 1
805f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    # The column containing the type of consumption. Only consumtion since last
815f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    # charge are of interest here.
82c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch    PWI_AGGREGATION_INDEX = 2
835f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    # The column containing the amount of power used, in mah.
845f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    PWI_POWER_COMSUMPTION_INDEX = 5
85c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch    csvreader = csv.reader(dumpsys_output)
865f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    uid_entries = {}
875f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    pwi_entries = defaultdict(list)
88c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch    for entry in csvreader:
896e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)      if entry[DUMP_VERSION_INDEX] not in ['8', '9']:
905f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        # Wrong file version.
915f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        break
925f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)      if ROW_TYPE_INDEX >= len(entry):
93c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch        continue
945f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)      if entry[ROW_TYPE_INDEX] == 'uid':
955f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        current_package = entry[PACKAGE_NAME_INDEX]
965f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        assert current_package not in uid_entries
975f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        uid_entries[current_package] = entry[PACKAGE_UID_INDEX]
985f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)      elif (PWI_POWER_COMSUMPTION_INDEX < len(entry) and
995f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)            entry[ROW_TYPE_INDEX] == 'pwi' and
1005f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)            entry[PWI_AGGREGATION_INDEX] == 'l'):
1015f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        pwi_entries[entry[PWI_UID_INDEX]].append(
1025f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)            float(entry[PWI_POWER_COMSUMPTION_INDEX]))
1035f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
104c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch    # Find the uid of for the given package.
1055f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    if not package in uid_entries:
1065f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)      logging.warning('Unable to parse dumpsys output. ' +
1075f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)                      'Please upgrade the OS version of the device.')
108c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch      out_dict['energy_consumption_mwh'] = 0
109c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch      return out_dict
1105f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    uid = uid_entries[package]
1115f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    consumptions_mah = pwi_entries[uid]
112c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch    consumption_mah = sum(consumptions_mah)
113c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch    # Converting at a nominal voltage of 4.0V, as those values are obtained by a
114c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch    # heuristic, and 4.0V is the voltage we set when using a monsoon device.
115c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch    consumption_mwh = consumption_mah * 4.0
116c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch    out_dict['energy_consumption_mwh'] = consumption_mwh
117c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch    return out_dict
118