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