| # Copyright 2014 The Chromium Authors. All rights reserved. |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| import time |
| |
| from metrics import Metric |
| from telemetry.value import scalar |
| |
| |
| class PowerMetric(Metric): |
| """A metric for measuring power usage.""" |
| |
| # System power draw while idle. |
| _quiescent_power_draw_mwh = 0 |
| |
| def __init__(self, browser, quiescent_measurement_time_s=0): |
| """PowerMetric Constructor. |
| |
| Args: |
| browser: browser object to use. |
| quiescent_measurement_time_s: time to measure quiescent power, |
| in seconds. 0 means don't measure quiescent power.""" |
| super(PowerMetric, self).__init__() |
| self._browser = browser |
| self._running = False |
| self._starting_cpu_stats = None |
| self._results = None |
| self._MeasureQuiescentPower(quiescent_measurement_time_s) |
| |
| def __del__(self): |
| # TODO(jeremy): Remove once crbug.com/350841 is fixed. |
| # Don't leave power monitoring processes running on the system. |
| self._StopInternal() |
| parent = super(PowerMetric, self) |
| if hasattr(parent, '__del__'): |
| parent.__del__() |
| |
| def _StopInternal(self): |
| """Stop monitoring power if measurement is running. This function is |
| idempotent.""" |
| if not self._running: |
| return |
| self._running = False |
| self._results = self._browser.platform.StopMonitoringPower() |
| if self._results: # StopMonitoringPower() can return None. |
| self._results['cpu_stats'] = ( |
| _SubtractCpuStats(self._browser.cpu_stats, self._starting_cpu_stats)) |
| |
| def _MeasureQuiescentPower(self, measurement_time_s): |
| """Measure quiescent power draw for the system.""" |
| platform = self._browser.platform |
| if not platform.CanMonitorPower() or \ |
| platform.CanMeasurePerApplicationPower() or \ |
| not measurement_time_s: |
| return |
| |
| # Only perform quiescent measurement once per run. |
| if PowerMetric._quiescent_power_draw_mwh: |
| return |
| |
| platform.StartMonitoringPower(self._browser) |
| time.sleep(measurement_time_s) |
| power_results = platform.StopMonitoringPower() |
| PowerMetric._quiescent_power_draw_mwh = ( |
| power_results.get('energy_consumption_mwh', 0)) |
| |
| def Start(self, _, tab): |
| if not tab.browser.platform.CanMonitorPower(): |
| return |
| |
| self._results = None |
| self._StopInternal() |
| |
| # This line invokes top a few times, call before starting power measurement. |
| self._starting_cpu_stats = self._browser.cpu_stats |
| self._browser.platform.StartMonitoringPower(self._browser) |
| self._running = True |
| |
| def Stop(self, _, tab): |
| if not tab.browser.platform.CanMonitorPower(): |
| return |
| |
| self._StopInternal() |
| |
| def AddResults(self, _, results): |
| """Add the collected power data into the results object. |
| |
| This function needs to be robust in the face of differing power data on |
| various platforms. Therefore data existence needs to be checked when |
| building up the results. Additionally 0 is a valid value for many of the |
| metrics here which is why there are plenty of checks for 'is not None' |
| below. |
| """ |
| if not self._results: |
| return |
| |
| application_energy_consumption_mwh = ( |
| self._results.get('application_energy_consumption_mwh')) |
| total_energy_consumption_mwh = self._results.get('energy_consumption_mwh') |
| |
| if not application_energy_consumption_mwh and total_energy_consumption_mwh: |
| application_energy_consumption_mwh = max( |
| total_energy_consumption_mwh - PowerMetric._quiescent_power_draw_mwh, |
| 0) |
| |
| if total_energy_consumption_mwh is not None: |
| results.AddValue(scalar.ScalarValue( |
| results.current_page, 'energy_consumption_mwh', 'mWh', |
| total_energy_consumption_mwh)) |
| |
| if application_energy_consumption_mwh is not None: |
| results.AddValue(scalar.ScalarValue( |
| results.current_page, 'application_energy_consumption_mwh', 'mWh', |
| application_energy_consumption_mwh)) |
| |
| component_utilization = self._results.get('component_utilization', {}) |
| # GPU Frequency. |
| gpu_power = component_utilization.get('gpu', {}) |
| gpu_freq_hz = gpu_power.get('average_frequency_hz') |
| if gpu_freq_hz is not None: |
| results.AddValue(scalar.ScalarValue( |
| results.current_page, 'gpu_average_frequency_hz', 'hz', gpu_freq_hz, |
| important=False)) |
| |
| # Add idle wakeup numbers for all processes. |
| for (process_type, stats) in self._results.get('cpu_stats', {}).items(): |
| trace_name_for_process = 'idle_wakeups_%s' % (process_type.lower()) |
| results.AddValue(scalar.ScalarValue( |
| results.current_page, trace_name_for_process, 'count', stats, |
| important=False)) |
| |
| # Add temperature measurements. |
| whole_package_utilization = component_utilization.get('whole_package', {}) |
| board_temperature_c = whole_package_utilization.get('average_temperature_c') |
| if board_temperature_c is not None: |
| results.AddValue(scalar.ScalarValue( |
| results.current_page, 'board_temperature', 'celsius', |
| board_temperature_c, important=False)) |
| |
| self._results = None |
| |
| def _SubtractCpuStats(cpu_stats, start_cpu_stats): |
| """Computes number of idle wakeups that occurred over measurement period. |
| |
| Each of the two cpu_stats arguments is a dict as returned by the |
| Browser.cpu_stats call. |
| |
| Returns: |
| A dict of process type names (Browser, Renderer, etc.) to idle wakeup count |
| over the period recorded by the input. |
| """ |
| cpu_delta = {} |
| for process_type in cpu_stats: |
| assert process_type in start_cpu_stats, 'Mismatching process types' |
| # Skip any process_types that are empty. |
| if (not cpu_stats[process_type]) or (not start_cpu_stats[process_type]): |
| continue |
| # Skip if IdleWakeupCount is not present. |
| if (('IdleWakeupCount' not in cpu_stats[process_type]) or |
| ('IdleWakeupCount' not in start_cpu_stats[process_type])): |
| continue |
| idle_wakeup_delta = (cpu_stats[process_type]['IdleWakeupCount'] - |
| start_cpu_stats[process_type]['IdleWakeupCount']) |
| cpu_delta[process_type] = idle_wakeup_delta |
| return cpu_delta |