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