Home | History | Annotate | Download | only in power_monitor
      1 # Copyright 2014 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 import collections
      6 import logging
      7 import re
      8 
      9 from telemetry import decorators
     10 from telemetry.internal.platform.power_monitor import sysfs_power_monitor
     11 
     12 
     13 class CrosPowerMonitor(sysfs_power_monitor.SysfsPowerMonitor):
     14   """PowerMonitor that relies on 'dump_power_status' to monitor power
     15   consumption of a single ChromeOS application.
     16   """
     17   def __init__(self, platform_backend):
     18     """Constructor.
     19 
     20     Args:
     21         platform_backend: A LinuxBasedPlatformBackend object.
     22 
     23     Attributes:
     24         _initial_power: The result of 'dump_power_status' before the test.
     25         _start_time: The epoch time at which the test starts executing.
     26     """
     27     super(CrosPowerMonitor, self).__init__(platform_backend)
     28     self._initial_power = None
     29     self._start_time = None
     30 
     31   @decorators.Cache
     32   def CanMonitorPower(self):
     33     return super(CrosPowerMonitor, self).CanMonitorPower()
     34 
     35   def StartMonitoringPower(self, browser):
     36     super(CrosPowerMonitor, self).StartMonitoringPower(browser)
     37     if self._IsOnBatteryPower():
     38       sample = self._platform.RunCommand(['dump_power_status;', 'date', '+%s'])
     39       self._initial_power, self._start_time = CrosPowerMonitor.SplitSample(
     40           sample)
     41     else:
     42       logging.warning('Device not on battery power during power monitoring. '
     43                       'Results may be incorrect.')
     44 
     45   def StopMonitoringPower(self):
     46     # Don't need to call self._CheckStop here; it's called by the superclass
     47     cpu_stats = super(CrosPowerMonitor, self).StopMonitoringPower()
     48     power_stats = {}
     49     if self._IsOnBatteryPower():
     50       sample = self._platform.RunCommand(['dump_power_status;', 'date', '+%s'])
     51       final_power, end_time = CrosPowerMonitor.SplitSample(sample)
     52       # The length of the test is used to measure energy consumption.
     53       length_h = (end_time - self._start_time) / 3600.0
     54       power_stats = CrosPowerMonitor.ParsePower(self._initial_power,
     55                                                 final_power, length_h)
     56     else:
     57       logging.warning('Device not on battery power during power monitoring. '
     58                       'Results may be incorrect.')
     59     return CrosPowerMonitor.CombineResults(cpu_stats, power_stats)
     60 
     61   @staticmethod
     62   def SplitSample(sample):
     63     """Splits a power and time sample into the two separate values.
     64 
     65     Args:
     66         sample: The result of calling 'dump_power_status; date +%s' on the
     67             device.
     68 
     69     Returns:
     70         A tuple of power sample and epoch time of the sample.
     71     """
     72     sample = sample.strip()
     73     index = sample.rfind('\n')
     74     power = sample[:index]
     75     time = sample[index + 1:]
     76     return power, int(time)
     77 
     78   @staticmethod
     79   def IsOnBatteryPower(status, board):
     80     """Determines if the devices is being charged.
     81 
     82     Args:
     83         status: The parsed result of 'dump_power_status'
     84         board: The name of the board running the test.
     85 
     86     Returns:
     87         True if the device is on battery power; False otherwise.
     88     """
     89     on_battery = status['line_power_connected'] == '0'
     90     # Butterfly can incorrectly report AC online for some time after unplug.
     91     # Check battery discharge state to confirm.
     92     if board == 'butterfly':
     93       on_battery |= status['battery_discharging'] == '1'
     94     return on_battery
     95 
     96   def _IsOnBatteryPower(self):
     97     """Determines if the device is being charged.
     98 
     99     Returns:
    100         True if the device is on battery power; False otherwise.
    101     """
    102     status = CrosPowerMonitor.ParsePowerStatus(
    103         self._platform.RunCommand(['dump_power_status']))
    104     board_data = self._platform.RunCommand(['cat', '/etc/lsb-release'])
    105     board = re.search('BOARD=(.*)', board_data).group(1)
    106     return CrosPowerMonitor.IsOnBatteryPower(status, board)
    107 
    108   @staticmethod
    109   def ParsePowerStatus(sample):
    110     """Parses 'dump_power_status' command output.
    111 
    112     Args:
    113         sample: The output of 'dump_power_status'
    114 
    115     Returns:
    116         Dictionary containing all fields from 'dump_power_status'
    117     """
    118     rv = collections.defaultdict(dict)
    119     for ln in sample.splitlines():
    120       words = ln.split()
    121       assert len(words) == 2
    122       rv[words[0]] = words[1]
    123     return dict(rv)
    124 
    125   @staticmethod
    126   def ParsePower(initial_stats, final_stats, length_h):
    127     """Parse output of 'dump_power_status'
    128 
    129     Args:
    130         initial_stats: The output of 'dump_power_status' before the test.
    131         final_stats: The output of 'dump_power_status' after the test.
    132         length_h: The length of the test in hours.
    133 
    134     Returns:
    135         Dictionary in the format returned by StopMonitoringPower().
    136     """
    137     initial = CrosPowerMonitor.ParsePowerStatus(initial_stats)
    138     final = CrosPowerMonitor.ParsePowerStatus(final_stats)
    139     # The charge value reported by 'dump_power_status' is not precise enough to
    140     # give meaningful results across shorter tests, so average energy rate and
    141     # the length of the test are used.
    142     initial_power_mw = float(initial['battery_energy_rate']) * 10 ** 3
    143     final_power_mw = float(final['battery_energy_rate']) * 10 ** 3
    144     average_power_mw = (initial_power_mw + final_power_mw) / 2.0
    145 
    146     # Duplicating CrOS battery fields where applicable.
    147     def CopyFinalState(field, key):
    148       """Copy fields from battery final state."""
    149       if field in final:
    150         battery[key] = float(final[field])
    151 
    152     battery = {}
    153     CopyFinalState('battery_charge_full', 'charge_full')
    154     CopyFinalState('battery_charge_full_design', 'charge_full_design')
    155     CopyFinalState('battery_charge', 'charge_now')
    156     CopyFinalState('battery_current', 'current_now')
    157     CopyFinalState('battery_energy', 'energy')
    158     CopyFinalState('battery_energy_rate', 'energy_rate')
    159     CopyFinalState('battery_voltage', 'voltage_now')
    160 
    161     return {'identifier': 'dump_power_status',
    162             'power_samples_mw': [initial_power_mw, final_power_mw],
    163             'energy_consumption_mwh': average_power_mw * length_h,
    164             'component_utilization': {'battery': battery}}
    165