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