cygprofile: increase timeouts to allow showing web contents
[chromium-blink-merge.git] / tools / perf / metrics / power.py
blob77a20459d6bcabd49a108a8d033104fe9c3ad503
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.
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 __del__(self):
41 # TODO(jeremy): Remove once crbug.com/350841 is fixed.
42 # Don't leave power monitoring processes running on the system.
43 self._StopInternal()
44 parent = super(PowerMetric, self)
45 if hasattr(parent, '__del__'):
46 parent.__del__()
48 def _StopInternal(self):
49 """Stop monitoring power if measurement is running. This function is
50 idempotent."""
51 if not self._running:
52 return
53 self._running = False
54 self._results = self._platform.StopMonitoringPower()
55 if self._results: # StopMonitoringPower() can return None.
56 self._results['cpu_stats'] = (
57 _SubtractCpuStats(self._browser.cpu_stats, self._starting_cpu_stats))
59 def _MeasureQuiescentPower(self, measurement_time_s):
60 """Measure quiescent power draw for the system."""
61 if not self._platform.CanMonitorPower() or \
62 self._platform.CanMeasurePerApplicationPower() or \
63 not measurement_time_s:
64 return
66 # Only perform quiescent measurement once per run.
67 if PowerMetric._quiescent_power_draw_mwh:
68 return
70 self._platform.StartMonitoringPower(self._browser)
71 time.sleep(measurement_time_s)
72 power_results = self._platform.StopMonitoringPower()
73 PowerMetric._quiescent_power_draw_mwh = (
74 power_results.get(TOTAL_POWER_LABEL, 0))
76 def Start(self, _, tab):
77 self._browser = tab.browser
79 if not self._platform.CanMonitorPower():
80 return
82 if not self._browser.supports_power_metrics:
83 logging.warning('Power metrics not supported.')
84 return
86 self._results = None
87 self._StopInternal()
89 # This line invokes top a few times, call before starting power measurement.
90 self._starting_cpu_stats = self._browser.cpu_stats
91 self._platform.StartMonitoringPower(self._browser)
92 self._running = True
94 def Stop(self, _, tab):
95 if (not self._platform.CanMonitorPower() or
96 not self._browser.supports_power_metrics):
97 return
99 self._StopInternal()
101 def AddResults(self, _, results):
102 """Add the collected power data into the results object.
104 This function needs to be robust in the face of differing power data on
105 various platforms. Therefore data existence needs to be checked when
106 building up the results. Additionally 0 is a valid value for many of the
107 metrics here which is why there are plenty of checks for 'is not None'
108 below.
110 if not self._results:
111 return
113 application_energy_consumption_mwh = self._results.get(APP_POWER_LABEL)
114 total_energy_consumption_mwh = self._results.get(TOTAL_POWER_LABEL)
115 fuel_gauge_energy_consumption_mwh = self._results.get(FUELGAUGE_POWER_LABEL)
116 monsoon_energy_consumption_mwh = self._results.get(MONSOON_POWER_LABEL)
118 if (PowerMetric._quiescent_power_draw_mwh and
119 application_energy_consumption_mwh is None and
120 total_energy_consumption_mwh is not None):
121 application_energy_consumption_mwh = max(
122 total_energy_consumption_mwh - PowerMetric._quiescent_power_draw_mwh,
125 if fuel_gauge_energy_consumption_mwh is not None:
126 results.AddValue(scalar.ScalarValue(
127 results.current_page, FUELGAUGE_POWER_LABEL, 'mWh',
128 fuel_gauge_energy_consumption_mwh))
130 if monsoon_energy_consumption_mwh is not None:
131 results.AddValue(scalar.ScalarValue(
132 results.current_page, MONSOON_POWER_LABEL, 'mWh',
133 monsoon_energy_consumption_mwh))
135 if total_energy_consumption_mwh is not None:
136 results.AddValue(scalar.ScalarValue(
137 results.current_page, TOTAL_POWER_LABEL, 'mWh',
138 total_energy_consumption_mwh))
140 if application_energy_consumption_mwh is not None:
141 results.AddValue(scalar.ScalarValue(
142 results.current_page, APP_POWER_LABEL, 'mWh',
143 application_energy_consumption_mwh))
145 component_utilization = self._results.get('component_utilization', {})
146 # GPU Frequency.
147 gpu_power = component_utilization.get('gpu', {})
148 gpu_freq_hz = gpu_power.get('average_frequency_hz')
149 if gpu_freq_hz is not None:
150 results.AddValue(scalar.ScalarValue(
151 results.current_page, 'gpu_average_frequency_hz', 'hz', gpu_freq_hz,
152 important=False))
154 # Add idle wakeup numbers for all processes.
155 for (process_type, stats) in self._results.get('cpu_stats', {}).items():
156 trace_name_for_process = 'idle_wakeups_%s' % (process_type.lower())
157 results.AddValue(scalar.ScalarValue(
158 results.current_page, trace_name_for_process, 'count', stats,
159 important=False))
161 # Add temperature measurements.
162 platform_info_utilization = self._results.get('platform_info', {})
163 board_temperature_c = platform_info_utilization.get('average_temperature_c')
164 if board_temperature_c is not None:
165 results.AddValue(scalar.ScalarValue(
166 results.current_page, 'board_temperature', 'celsius',
167 board_temperature_c, important=False))
169 # Add CPU frequency measurements.
170 frequency_hz = platform_info_utilization.get('frequency_percent')
171 if frequency_hz is not None:
172 frequency_sum = 0.0
173 for freq, percent in frequency_hz.iteritems():
174 frequency_sum += freq * (percent / 100.0)
175 results.AddValue(scalar.ScalarValue(
176 results.current_page, 'cpu_average_frequency_hz', 'Hz',
177 frequency_sum, important=False))
179 # Add CPU c-state residency measurements.
180 cstate_percent = platform_info_utilization.get('cstate_residency_percent')
181 if cstate_percent is not None:
182 for state, percent in cstate_percent.iteritems():
183 results.AddValue(scalar.ScalarValue(
184 results.current_page, 'cpu_cstate_%s_residency_percent' % state,
185 '%', percent, important=False))
187 self._results = None
189 def _SubtractCpuStats(cpu_stats, start_cpu_stats):
190 """Computes number of idle wakeups that occurred over measurement period.
192 Each of the two cpu_stats arguments is a dict as returned by the
193 Browser.cpu_stats call.
195 Returns:
196 A dict of process type names (Browser, Renderer, etc.) to idle wakeup count
197 over the period recorded by the input.
199 cpu_delta = {}
200 total = 0
201 for process_type in cpu_stats:
202 assert process_type in start_cpu_stats, 'Mismatching process types'
203 # Skip any process_types that are empty.
204 if (not cpu_stats[process_type]) or (not start_cpu_stats[process_type]):
205 continue
206 # Skip if IdleWakeupCount is not present.
207 if (('IdleWakeupCount' not in cpu_stats[process_type]) or
208 ('IdleWakeupCount' not in start_cpu_stats[process_type])):
209 continue
211 assert isinstance(cpu_stats[process_type]['IdleWakeupCount'],
212 process_statistic_timeline_data.IdleWakeupTimelineData)
213 idle_wakeup_delta = (cpu_stats[process_type]['IdleWakeupCount'] -
214 start_cpu_stats[process_type]['IdleWakeupCount'])
215 cpu_delta[process_type] = idle_wakeup_delta.total_sum()
216 total = total + cpu_delta[process_type]
217 cpu_delta['Total'] = total
218 return cpu_delta