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 logging 7import re 8 9from telemetry import decorators 10from telemetry.internal.platform.power_monitor import sysfs_power_monitor 11 12 13class 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