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.
7 from metrics import Metric
8 from telemetry.value import scalar
11 class PowerMetric(Metric):
12 """A metric for measuring power usage."""
14 # System power draw while idle.
15 _quiescent_power_draw_mwh = 0
17 def __init__(self, browser, quiescent_measurement_time_s=0):
18 """PowerMetric Constructor.
21 browser: browser object to use.
22 quiescent_measurement_time_s: time to measure quiescent power,
23 in seconds. 0 means don't measure quiescent power."""
24 super(PowerMetric, self).__init__()
25 self._browser = browser
27 self._starting_cpu_stats = None
29 self._MeasureQuiescentPower(quiescent_measurement_time_s)
32 # TODO(jeremy): Remove once crbug.com/350841 is fixed.
33 # Don't leave power monitoring processes running on the system.
35 parent = super(PowerMetric, self)
36 if hasattr(parent, '__del__'):
39 def _StopInternal(self):
40 """Stop monitoring power if measurement is running. This function is
45 self._results = self._browser.platform.StopMonitoringPower()
46 if self._results: # StopMonitoringPower() can return None.
47 self._results['cpu_stats'] = (
48 _SubtractCpuStats(self._browser.cpu_stats, self._starting_cpu_stats))
50 def _MeasureQuiescentPower(self, measurement_time_s):
51 """Measure quiescent power draw for the system."""
52 platform = self._browser.platform
53 if not platform.CanMonitorPower() or \
54 platform.CanMeasurePerApplicationPower() or \
55 not measurement_time_s:
58 # Only perform quiescent measurement once per run.
59 if PowerMetric._quiescent_power_draw_mwh:
62 platform.StartMonitoringPower(self._browser)
63 time.sleep(measurement_time_s)
64 power_results = platform.StopMonitoringPower()
65 PowerMetric._quiescent_power_draw_mwh = (
66 power_results.get('energy_consumption_mwh', 0))
68 def Start(self, _, tab):
69 if not tab.browser.platform.CanMonitorPower():
75 # This line invokes top a few times, call before starting power measurement.
76 self._starting_cpu_stats = self._browser.cpu_stats
77 self._browser.platform.StartMonitoringPower(self._browser)
80 def Stop(self, _, tab):
81 if not tab.browser.platform.CanMonitorPower():
86 def AddResults(self, _, results):
87 """Add the collected power data into the results object.
89 This function needs to be robust in the face of differing power data on
90 various platforms. Therefore data existence needs to be checked when
91 building up the results. Additionally 0 is a valid value for many of the
92 metrics here which is why there are plenty of checks for 'is not None'
98 application_energy_consumption_mwh = (
99 self._results.get('application_energy_consumption_mwh'))
100 total_energy_consumption_mwh = self._results.get('energy_consumption_mwh')
102 if not application_energy_consumption_mwh and total_energy_consumption_mwh:
103 application_energy_consumption_mwh = max(
104 total_energy_consumption_mwh - PowerMetric._quiescent_power_draw_mwh,
107 if total_energy_consumption_mwh is not None:
108 results.AddValue(scalar.ScalarValue(
109 results.current_page, 'energy_consumption_mwh', 'mWh',
110 total_energy_consumption_mwh))
112 if application_energy_consumption_mwh is not None:
113 results.AddValue(scalar.ScalarValue(
114 results.current_page, 'application_energy_consumption_mwh', 'mWh',
115 application_energy_consumption_mwh))
117 component_utilization = self._results.get('component_utilization', {})
119 gpu_power = component_utilization.get('gpu', {})
120 gpu_freq_hz = gpu_power.get('average_frequency_hz')
121 if gpu_freq_hz is not None:
122 results.AddValue(scalar.ScalarValue(
123 results.current_page, 'gpu_average_frequency_hz', 'hz', gpu_freq_hz,
126 # Add idle wakeup numbers for all processes.
127 for (process_type, stats) in self._results.get('cpu_stats', {}).items():
128 trace_name_for_process = 'idle_wakeups_%s' % (process_type.lower())
129 results.AddValue(scalar.ScalarValue(
130 results.current_page, trace_name_for_process, 'count', stats,
133 # Add temperature measurements.
134 whole_package_utilization = component_utilization.get('whole_package', {})
135 board_temperature_c = whole_package_utilization.get('average_temperature_c')
136 if board_temperature_c is not None:
137 results.AddValue(scalar.ScalarValue(
138 results.current_page, 'board_temperature', 'celsius',
139 board_temperature_c, important=False))
143 def _SubtractCpuStats(cpu_stats, start_cpu_stats):
144 """Computes number of idle wakeups that occurred over measurement period.
146 Each of the two cpu_stats arguments is a dict as returned by the
147 Browser.cpu_stats call.
150 A dict of process type names (Browser, Renderer, etc.) to idle wakeup count
151 over the period recorded by the input.
154 for process_type in cpu_stats:
155 assert process_type in start_cpu_stats, 'Mismatching process types'
156 # Skip any process_types that are empty.
157 if (not cpu_stats[process_type]) or (not start_cpu_stats[process_type]):
159 # Skip if IdleWakeupCount is not present.
160 if (('IdleWakeupCount' not in cpu_stats[process_type]) or
161 ('IdleWakeupCount' not in start_cpu_stats[process_type])):
163 idle_wakeup_delta = (cpu_stats[process_type]['IdleWakeupCount'] -
164 start_cpu_stats[process_type]['IdleWakeupCount'])
165 cpu_delta[process_type] = idle_wakeup_delta