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.
17 from telemetry import decorators
18 from telemetry.core import util
19 from telemetry.core.platform import platform_backend
20 from telemetry.core.platform import power_monitor
21 from telemetry.util import cloud_storage
22 from telemetry.util import path
23 from telemetry.util import statistics
26 import win32con # pylint: disable=F0401
27 import win32event # pylint: disable=F0401
28 import win32process # pylint: disable=F0401
35 class IppetError(Exception):
41 # Look for pre-installed IPPET.
42 ippet_path = path.FindInstalledWindowsApplication(os.path.join(
43 'Intel', 'Intel(R) Platform Power Estimation Tool', 'ippet.exe'))
47 # Look for IPPET installed previously by this script.
48 ippet_path = os.path.join(
49 path.GetTelemetryDir(), 'bin', 'win', 'ippet', 'ippet.exe')
50 if path.IsExecutable(ippet_path):
54 zip_path = os.path.join(path.GetTelemetryDir(), 'bin', 'win', 'ippet.zip')
55 cloud_storage.GetIfChanged(zip_path, bucket=cloud_storage.PUBLIC_BUCKET)
56 with zipfile.ZipFile(zip_path, 'r') as zip_file:
57 zip_file.extractall(os.path.dirname(zip_path))
60 if path.IsExecutable(ippet_path):
66 class IppetPowerMonitor(power_monitor.PowerMonitor):
67 def __init__(self, backend):
68 super(IppetPowerMonitor, self).__init__()
69 self._backend = backend
70 self._ippet_handle = None
71 self._ippet_port = None
72 self._output_dir = None
74 def CanMonitorPower(self):
75 # IPPET disabled because of flakiness (crbug.com/336558).
82 windows_7_or_later = (
83 self._backend.GetOSName() == 'win' and
84 self._backend.GetOSVersionName() >= platform_backend.WIN7)
85 if not windows_7_or_later:
88 # This check works on Windows only.
89 family, model = map(int, re.match('.+ Family ([0-9]+) Model ([0-9]+)',
90 platform.processor()).groups())
92 # https://software.intel.com/en-us/articles/intel-architecture-and- \
93 # processor-identification-with-cpuid-model-and-family-numbers
94 # http://www.speedtraq.com
95 sandy_bridge_or_later = ('Intel' in platform.processor() and family == 6 and
96 (model in (0x2A, 0x2D) or model >= 0x30))
97 if not sandy_bridge_or_later:
105 def CanMeasurePerApplicationPower(self):
106 return self.CanMonitorPower()
108 def StartMonitoringPower(self, browser):
109 assert not self._ippet_handle, 'Called StartMonitoringPower() twice.'
110 self._output_dir = tempfile.mkdtemp()
111 self._ippet_port = util.GetUnreservedAvailableLocalPort()
112 parameters = ['-log_dir', self._output_dir,
113 '-web_port', str(self._ippet_port),
115 self._ippet_handle = self._backend.LaunchApplication(
116 IppetPath(), parameters, elevate_privilege=True)
118 def IppetServerIsUp():
120 urllib2.urlopen('http://127.0.0.1:%d/ippet' % self._ippet_port,
122 except urllib2.URLError:
125 util.WaitFor(IppetServerIsUp, timeout=5)
127 def StopMonitoringPower(self):
128 assert self._ippet_handle, (
129 'Called StopMonitoringPower() before StartMonitoringPower().')
132 ippet_quit_url = 'http://127.0.0.1:%d/ippet?cmd=quit' % self._ippet_port
133 with contextlib.closing(
134 urllib2.urlopen(ippet_quit_url, timeout=5)) as response:
135 quit_output = response.read()
136 if quit_output != 'quiting\r\n':
137 raise IppetError('Failed to quit IPPET: %s' % quit_output.strip())
138 wait_return_code = win32event.WaitForSingleObject(self._ippet_handle,
140 if wait_return_code != win32event.WAIT_OBJECT_0:
141 if wait_return_code == win32event.WAIT_TIMEOUT:
142 raise IppetError('Timed out waiting for IPPET to close.')
144 raise IppetError('Error code %d while waiting for IPPET to close.' %
148 ippet_exit_code = win32process.GetExitCodeProcess(self._ippet_handle)
149 if ippet_exit_code == win32con.STILL_ACTIVE:
150 win32process.TerminateProcess(self._ippet_handle)
151 raise IppetError('IPPET is still running but should have stopped.')
152 elif ippet_exit_code != 0:
153 raise IppetError('IPPET closed with exit code %d.' % ippet_exit_code)
155 self._ippet_handle.Close()
156 self._ippet_handle = None
157 self._ippet_port = None
159 # Read IPPET's log file.
160 log_file = os.path.join(self._output_dir, 'ippet_log_processes.xls')
162 with open(log_file, 'r') as f:
163 reader = csv.DictReader(f, dialect='excel-tab')
164 data = list(reader)[1:] # The first iteration only reports temperature.
166 logging.error('Output directory %s contains: %s',
167 self._output_dir, os.listdir(self._output_dir))
169 shutil.rmtree(self._output_dir)
170 self._output_dir = None
172 def get(*args, **kwargs):
173 """Pull all iterations of a field from the IPPET data as a list.
176 args: A list representing the field name.
177 mult: A cosntant to multiply the field's value by, for unit conversions.
178 default: The default value if the field is not found in the iteration.
181 A list containing the field's value across all iterations.
183 key = '\\\\.\\' + '\\'.join(args)
187 elif 'default' in kwargs:
188 return kwargs['default']
190 raise KeyError('Key "%s" not found in data and '
191 'no default was given.' % key)
192 return [float(value(line)) * kwargs.get('mult', 1) for line in data]
195 'identifier': 'ippet',
196 'power_samples_mw': get('Power(_Total)', 'Package W', mult=1000),
197 'energy_consumption_mwh':
198 sum(map(operator.mul,
199 get('Power(_Total)', 'Package W', mult=1000),
200 get('sys', 'Interval(secs)', mult=1./3600.))),
201 'component_utilization': {
203 'average_temperature_c':
204 statistics.ArithmeticMean(get(
205 'Temperature(Package)', 'Current C')),
208 'power_samples_mw': get('Power(_Total)', 'CPU W', mult=1000),
209 'energy_consumption_mwh':
210 sum(map(operator.mul,
211 get('Power(_Total)', 'CPU W', mult=1000),
212 get('sys', 'Interval(secs)', mult=1./3600.))),
215 'power_samples_mw': get('Power(_Total)', 'Disk W', mult=1000),
216 'energy_consumption_mwh':
217 sum(map(operator.mul,
218 get('Power(_Total)', 'Disk W', mult=1000),
219 get('sys', 'Interval(secs)', mult=1./3600.))),
222 'power_samples_mw': get('Power(_Total)', 'GPU W', mult=1000),
223 'energy_consumption_mwh':
224 sum(map(operator.mul,
225 get('Power(_Total)', 'GPU W', mult=1000),
226 get('sys', 'Interval(secs)', mult=1./3600.))),
231 # Find Chrome processes in data. Note that this won't work if there are
232 # extra Chrome processes lying around.
234 for iteration in data:
235 for key in iteration.iterkeys():
236 parts = key.split('\\')
237 if (len(parts) >= 4 and
238 re.match(r'Process\(Google Chrome [0-9]+\)', parts[3])):
239 chrome_keys.add(parts[3])
240 # Add Chrome process power usage to result.
241 # Note that this is only an estimate of Chrome's CPU power usage.
243 per_process_power_usage = [
244 get(key, 'CPU Power W', default=0, mult=1000) for key in chrome_keys]
245 result['application_energy_consumption_mwh'] = (
246 sum(map(operator.mul,
247 map(sum, zip(*per_process_power_usage)),
248 get('sys', 'Interval(secs)', mult=1./3600.))))