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