[refactor] More post-NSS WebCrypto cleanups (utility functions).
[chromium-blink-merge.git] / tools / perf / metrics / power.py
blobab709648a48d56105f03daa59feb6545bd253a07
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 import atexit
5 import logging
6 import time
8 from telemetry.util import process_statistic_timeline_data
9 from telemetry.value import scalar
11 from metrics import Metric
14 MONSOON_POWER_LABEL = 'monsoon_energy_consumption_mwh'
15 FUELGAUGE_POWER_LABEL = 'fuel_gauge_energy_consumption_mwh'
16 APP_POWER_LABEL = 'application_energy_consumption_mwh'
17 TOTAL_POWER_LABEL = 'energy_consumption_mwh'
19 class PowerMetric(Metric):
20 """A metric for measuring power usage."""
22 # System power draw while idle.
23 _quiescent_power_draw_mwh = 0
25 def __init__(self, platform, quiescent_measurement_time_s=0):
26 """PowerMetric Constructor.
28 Args:
29 platform: platform object to use.
30 quiescent_measurement_time_s: time to measure quiescent power,
31 in seconds. 0 means don't measure quiescent power."""
32 super(PowerMetric, self).__init__()
33 self._browser = None
34 self._platform = platform
35 self._running = False
36 self._starting_cpu_stats = None
37 self._results = None
38 self._MeasureQuiescentPower(quiescent_measurement_time_s)
40 def _StopInternal(self):
41 """Stop monitoring power if measurement is running. This function is
42 idempotent."""
43 if not self._running:
44 return
45 self._running = False
46 self._results = self._platform.StopMonitoringPower()
47 if self._results: # StopMonitoringPower() can return None.
48 self._results['cpu_stats'] = (
49 _SubtractCpuStats(self._browser.cpu_stats, self._starting_cpu_stats))
51 def _MeasureQuiescentPower(self, measurement_time_s):
52 """Measure quiescent power draw for the system."""
53 if not self._platform.CanMonitorPower() or \
54 self._platform.CanMeasurePerApplicationPower() or \
55 not measurement_time_s:
56 return
58 # Only perform quiescent measurement once per run.
59 if PowerMetric._quiescent_power_draw_mwh:
60 return
62 self._platform.StartMonitoringPower(self._browser)
63 time.sleep(measurement_time_s)
64 power_results = self._platform.StopMonitoringPower()
65 PowerMetric._quiescent_power_draw_mwh = (
66 power_results.get(TOTAL_POWER_LABEL, 0))
68 def Start(self, _, tab):
69 self._browser = tab.browser
71 if not self._platform.CanMonitorPower():
72 return
74 if not self._browser.supports_power_metrics:
75 logging.warning('Power metrics not supported.')
76 return
78 self._results = None
79 self._StopInternal()
81 # This line invokes top a few times, call before starting power measurement.
82 self._starting_cpu_stats = self._browser.cpu_stats
83 self._platform.StartMonitoringPower(self._browser)
85 # Make sure that power monitoring is cleaned up when program exits.
86 platform = self._platform
87 def CleanUp():
88 platform.StopMonitoringPower()
89 atexit.register(CleanUp)
91 self._running = True
93 def Stop(self, _, tab):
94 if (not self._platform.CanMonitorPower() or
95 not self._browser.supports_power_metrics):
96 return
98 self._StopInternal()
100 def AddResults(self, _, results):
101 """Add the collected power data into the results object.
103 This function needs to be robust in the face of differing power data on
104 various platforms. Therefore data existence needs to be checked when
105 building up the results. Additionally 0 is a valid value for many of the
106 metrics here which is why there are plenty of checks for 'is not None'
107 below.
109 if not self._results:
110 return
112 application_energy_consumption_mwh = self._results.get(APP_POWER_LABEL)
113 total_energy_consumption_mwh = self._results.get(TOTAL_POWER_LABEL)
114 fuel_gauge_energy_consumption_mwh = self._results.get(FUELGAUGE_POWER_LABEL)
115 monsoon_energy_consumption_mwh = self._results.get(MONSOON_POWER_LABEL)
117 if (PowerMetric._quiescent_power_draw_mwh and
118 application_energy_consumption_mwh is None and
119 total_energy_consumption_mwh is not None):
120 application_energy_consumption_mwh = max(
121 total_energy_consumption_mwh - PowerMetric._quiescent_power_draw_mwh,
124 if fuel_gauge_energy_consumption_mwh is not None:
125 results.AddValue(scalar.ScalarValue(
126 results.current_page, FUELGAUGE_POWER_LABEL, 'mWh',
127 fuel_gauge_energy_consumption_mwh))
129 if monsoon_energy_consumption_mwh is not None:
130 results.AddValue(scalar.ScalarValue(
131 results.current_page, MONSOON_POWER_LABEL, 'mWh',
132 monsoon_energy_consumption_mwh))
134 if total_energy_consumption_mwh is not None:
135 results.AddValue(scalar.ScalarValue(
136 results.current_page, TOTAL_POWER_LABEL, 'mWh',
137 total_energy_consumption_mwh))
139 if application_energy_consumption_mwh is not None:
140 results.AddValue(scalar.ScalarValue(
141 results.current_page, APP_POWER_LABEL, 'mWh',
142 application_energy_consumption_mwh))
144 component_utilization = self._results.get('component_utilization', {})
145 # GPU Frequency.
146 gpu_power = component_utilization.get('gpu', {})
147 gpu_freq_hz = gpu_power.get('average_frequency_hz')
148 if gpu_freq_hz is not None:
149 results.AddValue(scalar.ScalarValue(
150 results.current_page, 'gpu_average_frequency_hz', 'hz', gpu_freq_hz,
151 important=False))
153 # Add idle wakeup numbers for all processes.
154 for (process_type, stats) in self._results.get('cpu_stats', {}).items():
155 trace_name_for_process = 'idle_wakeups_%s' % (process_type.lower())
156 results.AddValue(scalar.ScalarValue(
157 results.current_page, trace_name_for_process, 'count', stats,
158 important=False))
160 # Add temperature measurements.
161 platform_info_utilization = self._results.get('platform_info', {})
162 board_temperature_c = platform_info_utilization.get('average_temperature_c')
163 if board_temperature_c is not None:
164 results.AddValue(scalar.ScalarValue(
165 results.current_page, 'board_temperature', 'celsius',
166 board_temperature_c, important=False))
168 # Add CPU frequency measurements.
169 frequency_hz = platform_info_utilization.get('frequency_percent')
170 if frequency_hz is not None:
171 frequency_sum = 0.0
172 for freq, percent in frequency_hz.iteritems():
173 frequency_sum += freq * (percent / 100.0)
174 results.AddValue(scalar.ScalarValue(
175 results.current_page, 'cpu_average_frequency_hz', 'Hz',
176 frequency_sum, important=False))
178 # Add CPU c-state residency measurements.
179 cstate_percent = platform_info_utilization.get('cstate_residency_percent')
180 if cstate_percent is not None:
181 for state, percent in cstate_percent.iteritems():
182 results.AddValue(scalar.ScalarValue(
183 results.current_page, 'cpu_cstate_%s_residency_percent' % state,
184 '%', percent, important=False))
186 self._results = None
188 def _SubtractCpuStats(cpu_stats, start_cpu_stats):
189 """Computes number of idle wakeups that occurred over measurement period.
191 Each of the two cpu_stats arguments is a dict as returned by the
192 Browser.cpu_stats call.
194 Returns:
195 A dict of process type names (Browser, Renderer, etc.) to idle wakeup count
196 over the period recorded by the input.
198 cpu_delta = {}
199 total = 0
200 for process_type in cpu_stats:
201 assert process_type in start_cpu_stats, 'Mismatching process types'
202 # Skip any process_types that are empty.
203 if (not cpu_stats[process_type]) or (not start_cpu_stats[process_type]):
204 continue
205 # Skip if IdleWakeupCount is not present.
206 if (('IdleWakeupCount' not in cpu_stats[process_type]) or
207 ('IdleWakeupCount' not in start_cpu_stats[process_type])):
208 continue
210 assert isinstance(cpu_stats[process_type]['IdleWakeupCount'],
211 process_statistic_timeline_data.IdleWakeupTimelineData)
212 idle_wakeup_delta = (cpu_stats[process_type]['IdleWakeupCount'] -
213 start_cpu_stats[process_type]['IdleWakeupCount'])
214 cpu_delta[process_type] = idle_wakeup_delta.total_sum()
215 total = total + cpu_delta[process_type]
216 cpu_delta['Total'] = total
217 return cpu_delta