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
)
40 def _StopInternal(self
):
41 """Stop monitoring power if measurement is running. This function is
46 self
._results
= self
._platform
.StopMonitoringPower()
47 if self
._results
: # StopMonitoringPower() can return None.
48 self
._results
['cpu_stats'] = (
49 _SubtractCpuStats(self
._browser
.cpu_stats
, self
._starting
_cpu
_stats
))
51 def _MeasureQuiescentPower(self
, measurement_time_s
):
52 """Measure quiescent power draw for the system."""
53 if not self
._platform
.CanMonitorPower() or \
54 self
._platform
.CanMeasurePerApplicationPower() or \
55 not measurement_time_s
:
58 # Only perform quiescent measurement once per run.
59 if PowerMetric
._quiescent
_power
_draw
_mwh
:
62 self
._platform
.StartMonitoringPower(self
._browser
)
63 time
.sleep(measurement_time_s
)
64 power_results
= self
._platform
.StopMonitoringPower()
65 PowerMetric
._quiescent
_power
_draw
_mwh
= (
66 power_results
.get(TOTAL_POWER_LABEL
, 0))
68 def Start(self
, _
, tab
):
69 self
._browser
= tab
.browser
71 if not self
._platform
.CanMonitorPower():
74 if not self
._browser
.supports_power_metrics
:
75 logging
.warning('Power metrics not supported.')
81 # This line invokes top a few times, call before starting power measurement.
82 self
._starting
_cpu
_stats
= self
._browser
.cpu_stats
83 self
._platform
.StartMonitoringPower(self
._browser
)
85 # Make sure that power monitoring is cleaned up when program exits.
86 platform
= self
._platform
88 platform
.StopMonitoringPower()
89 atexit
.register(CleanUp
)
93 def Stop(self
, _
, tab
):
94 if (not self
._platform
.CanMonitorPower() or
95 not self
._browser
.supports_power_metrics
):
100 def AddResults(self
, _
, results
):
101 """Add the collected power data into the results object.
103 This function needs to be robust in the face of differing power data on
104 various platforms. Therefore data existence needs to be checked when
105 building up the results. Additionally 0 is a valid value for many of the
106 metrics here which is why there are plenty of checks for 'is not None'
109 if not self
._results
:
112 application_energy_consumption_mwh
= self
._results
.get(APP_POWER_LABEL
)
113 total_energy_consumption_mwh
= self
._results
.get(TOTAL_POWER_LABEL
)
114 fuel_gauge_energy_consumption_mwh
= self
._results
.get(FUELGAUGE_POWER_LABEL
)
115 monsoon_energy_consumption_mwh
= self
._results
.get(MONSOON_POWER_LABEL
)
117 if (PowerMetric
._quiescent
_power
_draw
_mwh
and
118 application_energy_consumption_mwh
is None and
119 total_energy_consumption_mwh
is not None):
120 application_energy_consumption_mwh
= max(
121 total_energy_consumption_mwh
- PowerMetric
._quiescent
_power
_draw
_mwh
,
124 if fuel_gauge_energy_consumption_mwh
is not None:
125 results
.AddValue(scalar
.ScalarValue(
126 results
.current_page
, FUELGAUGE_POWER_LABEL
, 'mWh',
127 fuel_gauge_energy_consumption_mwh
))
129 if monsoon_energy_consumption_mwh
is not None:
130 results
.AddValue(scalar
.ScalarValue(
131 results
.current_page
, MONSOON_POWER_LABEL
, 'mWh',
132 monsoon_energy_consumption_mwh
))
134 if total_energy_consumption_mwh
is not None:
135 results
.AddValue(scalar
.ScalarValue(
136 results
.current_page
, TOTAL_POWER_LABEL
, 'mWh',
137 total_energy_consumption_mwh
))
139 if application_energy_consumption_mwh
is not None:
140 results
.AddValue(scalar
.ScalarValue(
141 results
.current_page
, APP_POWER_LABEL
, 'mWh',
142 application_energy_consumption_mwh
))
144 component_utilization
= self
._results
.get('component_utilization', {})
146 gpu_power
= component_utilization
.get('gpu', {})
147 gpu_freq_hz
= gpu_power
.get('average_frequency_hz')
148 if gpu_freq_hz
is not None:
149 results
.AddValue(scalar
.ScalarValue(
150 results
.current_page
, 'gpu_average_frequency_hz', 'hz', gpu_freq_hz
,
153 # Add idle wakeup numbers for all processes.
154 for (process_type
, stats
) in self
._results
.get('cpu_stats', {}).items():
155 trace_name_for_process
= 'idle_wakeups_%s' % (process_type
.lower())
156 results
.AddValue(scalar
.ScalarValue(
157 results
.current_page
, trace_name_for_process
, 'count', stats
,
160 # Add temperature measurements.
161 platform_info_utilization
= self
._results
.get('platform_info', {})
162 board_temperature_c
= platform_info_utilization
.get('average_temperature_c')
163 if board_temperature_c
is not None:
164 results
.AddValue(scalar
.ScalarValue(
165 results
.current_page
, 'board_temperature', 'celsius',
166 board_temperature_c
, important
=False))
168 # Add CPU frequency measurements.
169 frequency_hz
= platform_info_utilization
.get('frequency_percent')
170 if frequency_hz
is not None:
172 for freq
, percent
in frequency_hz
.iteritems():
173 frequency_sum
+= freq
* (percent
/ 100.0)
174 results
.AddValue(scalar
.ScalarValue(
175 results
.current_page
, 'cpu_average_frequency_hz', 'Hz',
176 frequency_sum
, important
=False))
178 # Add CPU c-state residency measurements.
179 cstate_percent
= platform_info_utilization
.get('cstate_residency_percent')
180 if cstate_percent
is not None:
181 for state
, percent
in cstate_percent
.iteritems():
182 results
.AddValue(scalar
.ScalarValue(
183 results
.current_page
, 'cpu_cstate_%s_residency_percent' % state
,
184 '%', percent
, important
=False))
188 def _SubtractCpuStats(cpu_stats
, start_cpu_stats
):
189 """Computes number of idle wakeups that occurred over measurement period.
191 Each of the two cpu_stats arguments is a dict as returned by the
192 Browser.cpu_stats call.
195 A dict of process type names (Browser, Renderer, etc.) to idle wakeup count
196 over the period recorded by the input.
200 for process_type
in cpu_stats
:
201 assert process_type
in start_cpu_stats
, 'Mismatching process types'
202 # Skip any process_types that are empty.
203 if (not cpu_stats
[process_type
]) or (not start_cpu_stats
[process_type
]):
205 # Skip if IdleWakeupCount is not present.
206 if (('IdleWakeupCount' not in cpu_stats
[process_type
]) or
207 ('IdleWakeupCount' not in start_cpu_stats
[process_type
])):
210 assert isinstance(cpu_stats
[process_type
]['IdleWakeupCount'],
211 process_statistic_timeline_data
.IdleWakeupTimelineData
)
212 idle_wakeup_delta
= (cpu_stats
[process_type
]['IdleWakeupCount'] -
213 start_cpu_stats
[process_type
]['IdleWakeupCount'])
214 cpu_delta
[process_type
] = idle_wakeup_delta
.total_sum()
215 total
= total
+ cpu_delta
[process_type
]
216 cpu_delta
['Total'] = total