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 5 import time 6 7 from metrics import Metric 8 from telemetry.core.platform import process_statistic_timeline_data 9 from telemetry.value import scalar 10 11 12 class PowerMetric(Metric): 13 """A metric for measuring power usage.""" 14 15 # System power draw while idle. 16 _quiescent_power_draw_mwh = 0 17 18 def __init__(self, platform, quiescent_measurement_time_s=0): 19 """PowerMetric Constructor. 20 21 Args: 22 platform: platform object to use. 23 quiescent_measurement_time_s: time to measure quiescent power, 24 in seconds. 0 means don't measure quiescent power.""" 25 super(PowerMetric, self).__init__() 26 self._browser = None 27 self._platform = platform 28 self._running = False 29 self._starting_cpu_stats = None 30 self._results = None 31 self._MeasureQuiescentPower(quiescent_measurement_time_s) 32 33 def __del__(self): 34 # TODO(jeremy): Remove once crbug.com/350841 is fixed. 35 # Don't leave power monitoring processes running on the system. 36 self._StopInternal() 37 parent = super(PowerMetric, self) 38 if hasattr(parent, '__del__'): 39 parent.__del__() 40 41 def _StopInternal(self): 42 """Stop monitoring power if measurement is running. This function is 43 idempotent.""" 44 if not self._running: 45 return 46 self._running = False 47 self._results = self._platform.StopMonitoringPower() 48 if self._results: # StopMonitoringPower() can return None. 49 self._results['cpu_stats'] = ( 50 _SubtractCpuStats(self._browser.cpu_stats, self._starting_cpu_stats)) 51 52 def _MeasureQuiescentPower(self, measurement_time_s): 53 """Measure quiescent power draw for the system.""" 54 if not self._platform.CanMonitorPower() or \ 55 self._platform.CanMeasurePerApplicationPower() or \ 56 not measurement_time_s: 57 return 58 59 # Only perform quiescent measurement once per run. 60 if PowerMetric._quiescent_power_draw_mwh: 61 return 62 63 self._platform.StartMonitoringPower(self._browser) 64 time.sleep(measurement_time_s) 65 power_results = self._platform.StopMonitoringPower() 66 PowerMetric._quiescent_power_draw_mwh = ( 67 power_results.get('energy_consumption_mwh', 0)) 68 69 def Start(self, _, tab): 70 self._browser = tab.browser 71 72 if not self._platform.CanMonitorPower(): 73 return 74 75 self._results = None 76 self._StopInternal() 77 78 # This line invokes top a few times, call before starting power measurement. 79 self._starting_cpu_stats = self._browser.cpu_stats 80 self._platform.StartMonitoringPower(self._browser) 81 self._running = True 82 83 def Stop(self, _, tab): 84 if not self._platform.CanMonitorPower(): 85 return 86 87 self._StopInternal() 88 89 def AddResults(self, _, results): 90 """Add the collected power data into the results object. 91 92 This function needs to be robust in the face of differing power data on 93 various platforms. Therefore data existence needs to be checked when 94 building up the results. Additionally 0 is a valid value for many of the 95 metrics here which is why there are plenty of checks for 'is not None' 96 below. 97 """ 98 if not self._results: 99 return 100 101 application_energy_consumption_mwh = ( 102 self._results.get('application_energy_consumption_mwh')) 103 total_energy_consumption_mwh = self._results.get('energy_consumption_mwh') 104 105 if not application_energy_consumption_mwh and total_energy_consumption_mwh: 106 application_energy_consumption_mwh = max( 107 total_energy_consumption_mwh - PowerMetric._quiescent_power_draw_mwh, 108 0) 109 110 if total_energy_consumption_mwh is not None: 111 results.AddValue(scalar.ScalarValue( 112 results.current_page, 'energy_consumption_mwh', 'mWh', 113 total_energy_consumption_mwh)) 114 115 if application_energy_consumption_mwh is not None: 116 results.AddValue(scalar.ScalarValue( 117 results.current_page, 'application_energy_consumption_mwh', 'mWh', 118 application_energy_consumption_mwh)) 119 120 component_utilization = self._results.get('component_utilization', {}) 121 # GPU Frequency. 122 gpu_power = component_utilization.get('gpu', {}) 123 gpu_freq_hz = gpu_power.get('average_frequency_hz') 124 if gpu_freq_hz is not None: 125 results.AddValue(scalar.ScalarValue( 126 results.current_page, 'gpu_average_frequency_hz', 'hz', gpu_freq_hz, 127 important=False)) 128 129 # Add idle wakeup numbers for all processes. 130 for (process_type, stats) in self._results.get('cpu_stats', {}).items(): 131 trace_name_for_process = 'idle_wakeups_%s' % (process_type.lower()) 132 results.AddValue(scalar.ScalarValue( 133 results.current_page, trace_name_for_process, 'count', stats, 134 important=False)) 135 136 # Add temperature measurements. 137 whole_package_utilization = component_utilization.get('whole_package', {}) 138 board_temperature_c = whole_package_utilization.get('average_temperature_c') 139 if board_temperature_c is not None: 140 results.AddValue(scalar.ScalarValue( 141 results.current_page, 'board_temperature', 'celsius', 142 board_temperature_c, important=False)) 143 144 # Add CPU frequency measurements. 145 frequency_hz = whole_package_utilization.get('frequency_percent') 146 if frequency_hz is not None: 147 frequency_sum = 0.0 148 for freq, percent in frequency_hz.iteritems(): 149 frequency_sum += freq * (percent / 100.0) 150 results.AddValue(scalar.ScalarValue( 151 results.current_page, 'cpu_average_frequency_hz', 'Hz', 152 frequency_sum, important=False)) 153 154 # Add CPU c-state residency measurements. 155 cstate_percent = whole_package_utilization.get('cstate_residency_percent') 156 if cstate_percent is not None: 157 for state, percent in cstate_percent.iteritems(): 158 results.AddValue(scalar.ScalarValue( 159 results.current_page, 'cpu_cstate_%s_residency_percent' % state, 160 '%', percent, important=False)) 161 162 self._results = None 163 164 def _SubtractCpuStats(cpu_stats, start_cpu_stats): 165 """Computes number of idle wakeups that occurred over measurement period. 166 167 Each of the two cpu_stats arguments is a dict as returned by the 168 Browser.cpu_stats call. 169 170 Returns: 171 A dict of process type names (Browser, Renderer, etc.) to idle wakeup count 172 over the period recorded by the input. 173 """ 174 cpu_delta = {} 175 total = 0 176 for process_type in cpu_stats: 177 assert process_type in start_cpu_stats, 'Mismatching process types' 178 # Skip any process_types that are empty. 179 if (not cpu_stats[process_type]) or (not start_cpu_stats[process_type]): 180 continue 181 # Skip if IdleWakeupCount is not present. 182 if (('IdleWakeupCount' not in cpu_stats[process_type]) or 183 ('IdleWakeupCount' not in start_cpu_stats[process_type])): 184 continue 185 186 assert isinstance(cpu_stats[process_type]['IdleWakeupCount'], 187 process_statistic_timeline_data.IdleWakeupTimelineData) 188 idle_wakeup_delta = (cpu_stats[process_type]['IdleWakeupCount'] - 189 start_cpu_stats[process_type]['IdleWakeupCount']) 190 cpu_delta[process_type] = idle_wakeup_delta.total_sum() 191 total = total + cpu_delta[process_type] 192 cpu_delta['Total'] = total 193 return cpu_delta 194