Upstream version 11.40.277.0
[platform/framework/web/crosswalk.git] / src / tools / perf / metrics / power.py
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
5 import time
6
7 from metrics import Metric
8 from telemetry.core.platform import process_statistic_timeline_data
9 from telemetry.value import scalar
10
11
12 class PowerMetric(Metric):
13   """A metric for measuring power usage."""
14
15   # System power draw while idle.
16   _quiescent_power_draw_mwh = 0
17
18   def __init__(self, platform, quiescent_measurement_time_s=0):
19     """PowerMetric Constructor.
20
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)
32
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__()
40
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))
51
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
58
59     # Only perform quiescent measurement once per run.
60     if PowerMetric._quiescent_power_draw_mwh:
61       return
62
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))
68
69   def Start(self, _, tab):
70     self._browser = tab.browser
71
72     if not self._platform.CanMonitorPower():
73       return
74
75     self._results = None
76     self._StopInternal()
77
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
82
83   def Stop(self, _, tab):
84     if not self._platform.CanMonitorPower():
85       return
86
87     self._StopInternal()
88
89   def AddResults(self, _, results):
90     """Add the collected power data into the results object.
91
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
100
101     application_energy_consumption_mwh = (
102         self._results.get('application_energy_consumption_mwh'))
103     total_energy_consumption_mwh = self._results.get('energy_consumption_mwh')
104
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,
110           0)
111
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))
116
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))
121
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))
130
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))
137
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))
145
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))
155
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))
163
164     self._results = None
165
166 def _SubtractCpuStats(cpu_stats, start_cpu_stats):
167   """Computes number of idle wakeups that occurred over measurement period.
168
169   Each of the two cpu_stats arguments is a dict as returned by the
170   Browser.cpu_stats call.
171
172   Returns:
173     A dict of process type names (Browser, Renderer, etc.) to idle wakeup count
174     over the period recorded by the input.
175   """
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
187
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