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.
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.
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
__()
34 self
._platform
= platform
36 self
._starting
_cpu
_stats
= None
38 self
._MeasureQuiescentPower
(quiescent_measurement_time_s
)
41 # TODO(jeremy): Remove once crbug.com/350841 is fixed.
42 # Don't leave power monitoring processes running on the system.
44 parent
= super(PowerMetric
, self
)
45 if hasattr(parent
, '__del__'):
48 def _StopInternal(self
):
49 """Stop monitoring power if measurement is running. This function is
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
:
66 # Only perform quiescent measurement once per run.
67 if PowerMetric
._quiescent
_power
_draw
_mwh
:
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():
82 if not self
._browser
.supports_power_metrics
:
83 logging
.warning('Power metrics not supported.')
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
)
94 def Stop(self
, _
, tab
):
95 if (not self
._platform
.CanMonitorPower() or
96 not self
._browser
.supports_power_metrics
):
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'
110 if not self
._results
:
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', {})
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
,
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
,
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:
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))
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.
196 A dict of process type names (Browser, Renderer, etc.) to idle wakeup count
197 over the period recorded by the input.
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
]):
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
])):
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