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 logging 6import os 7 8from telemetry import decorators 9from telemetry.core.platform.power_monitor import sysfs_power_monitor 10from telemetry.core.platform.profiler import android_prebuilt_profiler_helper 11 12 13SAMPLE_RATE_HZ = 2 # The data is collected from the ds2784 fuel gauge chip 14 # that only updates its data every 3.5s. 15FUEL_GAUGE_PATH = '/sys/class/power_supply/ds2784-fuelgauge' 16CHARGE_COUNTER = os.path.join(FUEL_GAUGE_PATH, 'charge_counter_ext') 17CURRENT = os.path.join(FUEL_GAUGE_PATH, 'current_now') 18VOLTAGE = os.path.join(FUEL_GAUGE_PATH, 'voltage_now') 19 20 21class DS2784PowerMonitor(sysfs_power_monitor.SysfsPowerMonitor): 22 def __init__(self, device, platform_backend): 23 super(DS2784PowerMonitor, self).__init__(platform_backend) 24 self._device = device 25 self._powermonitor_process_port = None 26 self._file_poller_binary = android_prebuilt_profiler_helper.GetDevicePath( 27 'file_poller') 28 29 @decorators.Cache 30 def _HasFuelGauge(self): 31 return self._device.FileExists(CHARGE_COUNTER) 32 33 def CanMonitorPower(self): 34 if not self._HasFuelGauge(): 35 return False 36 if self._device.old_interface.IsDeviceCharging(): 37 logging.warning('Can\'t monitor power usage since device is charging.') 38 return False 39 return True 40 41 def StartMonitoringPower(self, browser): 42 assert not self._powermonitor_process_port, ( 43 'Must call StopMonitoringPower().') 44 super(DS2784PowerMonitor, self).StartMonitoringPower(browser) 45 android_prebuilt_profiler_helper.InstallOnDevice( 46 self._device, 'file_poller') 47 self._powermonitor_process_port = int( 48 self._device.RunShellCommand( 49 '%s %d %s %s %s' % (self._file_poller_binary, SAMPLE_RATE_HZ, 50 CHARGE_COUNTER, CURRENT, VOLTAGE))[0]) 51 52 def StopMonitoringPower(self): 53 assert self._powermonitor_process_port, ( 54 'StartMonitoringPower() not called.') 55 try: 56 cpu_stats = super(DS2784PowerMonitor, self).StopMonitoringPower() 57 result = '\n'.join(self._device.RunShellCommand( 58 '%s %d' % (self._file_poller_binary, 59 self._powermonitor_process_port))) 60 assert result, 'PowerMonitor produced no output' 61 return super(DS2784PowerMonitor, self).CombineResults( 62 cpu_stats, DS2784PowerMonitor.ParseSamplingOutput(result)) 63 finally: 64 self._powermonitor_process_port = None 65 66 @staticmethod 67 def ParseSamplingOutput(powermonitor_output): 68 """Parse output of powermonitor command line utility. 69 70 Returns: 71 Dictionary in the format returned by StopMonitoringPower(). 72 """ 73 power_samples = [] 74 total_energy_consumption_mwh = 0 75 def ParseSample(sample): 76 values = [float(x) for x in sample.split(' ')] 77 res = {} 78 (res['timestamp_s'], 79 res['charge_nah'], 80 res['current_ua'], 81 res['voltage_uv']) = values 82 return res 83 # The output contains a sample per line. 84 samples = map(ParseSample, powermonitor_output.split('\n')[:-1]) 85 # Keep track of the last sample that found an updated reading. 86 last_updated_sample = samples[0] 87 # Compute average voltage. 88 voltage_sum_uv = 0 89 voltage_count = 0 90 for sample in samples: 91 if sample['charge_nah'] != last_updated_sample['charge_nah']: 92 charge_difference_nah = (sample['charge_nah'] - 93 last_updated_sample['charge_nah']) 94 # Use average voltage for the energy consumption. 95 voltage_sum_uv += sample['voltage_uv'] 96 voltage_count += 1 97 average_voltage_uv = voltage_sum_uv / voltage_count 98 total_energy_consumption_mwh += (-charge_difference_nah * 99 average_voltage_uv / 10 ** 12) 100 last_updated_sample = sample 101 voltage_sum_uv = 0 102 voltage_count = 0 103 # Update average voltage. 104 voltage_sum_uv += sample['voltage_uv'] 105 voltage_count += 1 106 # Compute energy of the sample. 107 energy_consumption_mw = (-sample['current_ua'] * sample['voltage_uv'] / 108 10 ** 9) 109 110 power_samples.append(energy_consumption_mw) 111 # Because the data is stalled for a few seconds, compute the remaining 112 # energy consumption using the last available current reading. 113 last_sample = samples[-1] 114 remaining_time_h = ( 115 last_sample['timestamp_s'] - last_updated_sample['timestamp_s']) / 3600 116 average_voltage_uv = voltage_sum_uv / voltage_count 117 118 remaining_energy_consumption_mwh = (-last_updated_sample['current_ua'] * 119 average_voltage_uv * 120 remaining_time_h / 10 ** 9) 121 total_energy_consumption_mwh += remaining_energy_consumption_mwh 122 123 # -------- Collect and Process Data ------------- 124 out_dict = {} 125 # Raw power usage samples. 126 out_dict['identifier'] = 'ds2784' 127 out_dict['power_samples_mw'] = power_samples 128 out_dict['energy_consumption_mwh'] = total_energy_consumption_mwh 129 130 return out_dict 131