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