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.
8 from telemetry import decorators
9 from telemetry.core.platform.power_monitor import sysfs_power_monitor
12 class CrosPowerMonitor(sysfs_power_monitor.SysfsPowerMonitor):
13 """PowerMonitor that relies on 'power_supply_info' to monitor power
14 consumption of a single ChromeOS application.
16 def __init__(self, platform_backend):
20 platform_backend: A LinuxBasedPlatformBackend object.
23 _initial_power: The result of 'power_supply_info' before the test.
24 _start_time: The epoch time at which the test starts executing.
26 super(CrosPowerMonitor, self).__init__(platform_backend)
27 self._initial_power = None
28 self._start_time = None
31 def CanMonitorPower(self):
32 return super(CrosPowerMonitor, self).CanMonitorPower()
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(
41 def StopMonitoringPower(self):
42 cpu_stats = super(CrosPowerMonitor, self).StopMonitoringPower()
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)
54 def SplitSample(sample):
55 """Splits a power and time sample into the two separate values.
58 sample: The result of calling 'power_supply_info; date +%s' on the
62 A tuple of power sample and epoch time of the sample.
64 sample = sample.strip()
65 index = sample.rfind('\n')
66 power = sample[:index]
67 time = sample[index + 1:]
68 return power, int(time)
71 def IsOnBatteryPower(status, board):
72 """Determines if the devices is being charged.
75 status: The parsed result of 'power_supply_info'
76 board: The name of the board running the test.
79 True if the device is on battery power; False otherwise.
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'
88 def _IsOnBatteryPower(self):
89 """Determines if the device is being charged.
92 True if the device is on battery power; False otherwise.
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)
101 def ParsePowerSupplyInfo(sample):
102 """Parses 'power_supply_info' command output.
105 sample: The output of 'power_supply_info'
108 Dictionary containing all fields from 'power_supply_info'
110 rv = collections.defaultdict(dict)
112 for ln in sample.splitlines():
113 result = re.findall(r'^Device:\s+(.*)', ln)
117 result = re.findall(r'\s+(.+):\s+(.+)', ln)
119 kname = re.findall(r'(.*)\s+\(\w+\)', result[0][0])
121 rv[dev][kname[0]] = result[0][1]
123 rv[dev][result[0][0]] = result[0][1]
127 def ParsePower(initial_stats, final_stats, length_h):
128 """Parse output of 'power_supply_info'
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.
136 Dictionary in the format returned by StopMonitoringPower().
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.
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