OLD | NEW |
(Empty) | |
| 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 csv |
| 6 import logging |
| 7 import operator |
| 8 import os |
| 9 import platform |
| 10 import re |
| 11 import shutil |
| 12 import tempfile |
| 13 import urllib2 |
| 14 import zipfile |
| 15 |
| 16 from telemetry import decorators |
| 17 from telemetry.core import util |
| 18 from telemetry.core.platform import platform_backend |
| 19 from telemetry.core.platform import power_monitor |
| 20 from telemetry.util import cloud_storage |
| 21 from telemetry.util import path |
| 22 from telemetry.util import statistics |
| 23 |
| 24 try: |
| 25 import win32con # pylint: disable=F0401 |
| 26 import win32event # pylint: disable=F0401 |
| 27 import win32process # pylint: disable=F0401 |
| 28 except ImportError: |
| 29 win32con = None |
| 30 win32event = None |
| 31 win32process = None |
| 32 |
| 33 |
| 34 class IppetError(Exception): |
| 35 pass |
| 36 |
| 37 |
| 38 @decorators.Cache |
| 39 def IppetPath(): |
| 40 # Look for pre-installed IPPET. |
| 41 ippet_path = path.FindInstalledWindowsApplication(os.path.join( |
| 42 'Intel', 'Intel(R) Platform Power Estimation Tool', 'ippet.exe')) |
| 43 if ippet_path: |
| 44 return ippet_path |
| 45 |
| 46 # Look for IPPET installed previously by this script. |
| 47 ippet_path = os.path.join( |
| 48 path.GetTelemetryDir(), 'bin', 'win', 'ippet', 'ippet.exe') |
| 49 if path.IsExecutable(ippet_path): |
| 50 return ippet_path |
| 51 |
| 52 # Install IPPET. |
| 53 zip_path = os.path.join(path.GetTelemetryDir(), 'bin', 'win', 'ippet.zip') |
| 54 cloud_storage.GetIfChanged(zip_path, bucket=cloud_storage.PUBLIC_BUCKET) |
| 55 with zipfile.ZipFile(zip_path, 'r') as zip_file: |
| 56 zip_file.extractall(os.path.dirname(zip_path)) |
| 57 os.remove(zip_path) |
| 58 |
| 59 if path.IsExecutable(ippet_path): |
| 60 return ippet_path |
| 61 |
| 62 return None |
| 63 |
| 64 |
| 65 class IppetPowerMonitor(power_monitor.PowerMonitor): |
| 66 def __init__(self, backend): |
| 67 super(IppetPowerMonitor, self).__init__() |
| 68 self._backend = backend |
| 69 self._ippet_handle = None |
| 70 self._ippet_port = None |
| 71 self._output_dir = None |
| 72 |
| 73 def CanMonitorPower(self): |
| 74 if not win32event: |
| 75 return False |
| 76 |
| 77 windows_7_or_later = ( |
| 78 self._backend.GetOSName() == 'win' and |
| 79 self._backend.GetOSVersionName() >= platform_backend.WIN7) |
| 80 if not windows_7_or_later: |
| 81 return False |
| 82 |
| 83 # This check works on Windows only. |
| 84 family, model = map(int, re.match('.+ Family ([0-9]+) Model ([0-9]+)', |
| 85 platform.processor()).groups()) |
| 86 # Model numbers from: |
| 87 # https://software.intel.com/en-us/articles/intel-architecture-and- \ |
| 88 # processor-identification-with-cpuid-model-and-family-numbers |
| 89 # http://www.speedtraq.com |
| 90 sandy_bridge_or_later = ('Intel' in platform.processor() and family == 6 and |
| 91 (model in (0x2A, 0x2D) or model >= 0x30)) |
| 92 if not sandy_bridge_or_later: |
| 93 return False |
| 94 |
| 95 if not IppetPath(): |
| 96 return False |
| 97 |
| 98 return True |
| 99 |
| 100 def CanMeasurePerApplicationPower(self): |
| 101 return self.CanMonitorPower() |
| 102 |
| 103 def StartMonitoringPower(self, browser): |
| 104 assert not self._ippet_handle, 'Called StartMonitoringPower() twice.' |
| 105 self._output_dir = tempfile.mkdtemp() |
| 106 self._ippet_port = util.GetUnreservedAvailableLocalPort() |
| 107 parameters = ['-log_dir', self._output_dir, |
| 108 '-web_port', str(self._ippet_port), |
| 109 '-zip', 'n', '-all_processes', '-l', '0'] |
| 110 self._ippet_handle = self._backend.LaunchApplication( |
| 111 IppetPath(), parameters, elevate_privilege=True) |
| 112 |
| 113 def IppetServerIsUp(): |
| 114 try: |
| 115 urllib2.urlopen('http://127.0.0.1:%d/ippet' % self._ippet_port) |
| 116 except urllib2.URLError: |
| 117 return False |
| 118 return True |
| 119 util.WaitFor(IppetServerIsUp, timeout=5) |
| 120 |
| 121 def StopMonitoringPower(self): |
| 122 assert self._ippet_handle, ( |
| 123 'Called StopMonitoringPower() before StartMonitoringPower().') |
| 124 # Stop IPPET. |
| 125 ippet_quit_url = 'http://127.0.0.1:%d/ippet?cmd=quit' % self._ippet_port |
| 126 quit_output = urllib2.urlopen(ippet_quit_url).read() |
| 127 if quit_output != 'quiting\r\n': |
| 128 raise IppetError('Failed to quit IPPET: %s' % quit_output.strip()) |
| 129 wait_return_code = win32event.WaitForSingleObject(self._ippet_handle, 20000) |
| 130 if wait_return_code != win32event.WAIT_OBJECT_0: |
| 131 if wait_return_code == win32event.WAIT_TIMEOUT: |
| 132 raise IppetError('Timed out waiting for IPPET to close.') |
| 133 else: |
| 134 raise IppetError('Error code %d while waiting for IPPET to close.' % |
| 135 wait_return_code) |
| 136 ippet_exit_code = win32process.GetExitCodeProcess(self._ippet_handle) |
| 137 if ippet_exit_code == win32con.STILL_ACTIVE: |
| 138 raise IppetError('IPPET is still running but should have stopped.') |
| 139 elif ippet_exit_code != 0: |
| 140 raise IppetError('IPPET closed with exit code %d.' % ippet_exit_code) |
| 141 self._ippet_handle.Close() |
| 142 self._ippet_handle = None |
| 143 self._ippet_port = None |
| 144 |
| 145 # Read IPPET's log file. |
| 146 log_file = os.path.join(self._output_dir, 'ippet_log_processes.xls') |
| 147 try: |
| 148 with open(log_file, 'r') as f: |
| 149 reader = csv.DictReader(f, dialect='excel-tab') |
| 150 data = list(reader)[1:] # The first iteration only reports temperature. |
| 151 except IOError: |
| 152 logging.error('Output directory %s contains: %s', |
| 153 self._output_dir, os.listdir(self._output_dir)) |
| 154 raise |
| 155 shutil.rmtree(self._output_dir) |
| 156 self._output_dir = None |
| 157 |
| 158 def get(*args, **kwargs): |
| 159 """Pull all iterations of a field from the IPPET data as a list. |
| 160 |
| 161 Args: |
| 162 args: A list representing the field name. |
| 163 mult: A cosntant to multiply the field's value by, for unit conversions. |
| 164 default: The default value if the field is not found in the iteration. |
| 165 |
| 166 Returns: |
| 167 A list containing the field's value across all iterations. |
| 168 """ |
| 169 key = '\\\\.\\' + '\\'.join(args) |
| 170 def value(line): |
| 171 if key in line: |
| 172 return line[key] |
| 173 elif 'default' in kwargs: |
| 174 return kwargs['default'] |
| 175 else: |
| 176 raise KeyError('Key "%s" not found in data and ' |
| 177 'no default was given.' % key) |
| 178 return [float(value(line)) * kwargs.get('mult', 1) for line in data] |
| 179 |
| 180 result = { |
| 181 'identifier': 'ippet', |
| 182 'power_samples_mw': get('Power(_Total)', 'Package W', mult=1000), |
| 183 'energy_consumption_mwh': |
| 184 sum(map(operator.mul, |
| 185 get('Power(_Total)', 'Package W', mult=1000), |
| 186 get('sys', 'Interval(secs)', mult=1./3600.))), |
| 187 'component_utilization': { |
| 188 'whole_package': { |
| 189 'average_temperature_c': |
| 190 statistics.ArithmeticMean(get( |
| 191 'Temperature(Package)', 'Current C')), |
| 192 }, |
| 193 'cpu': { |
| 194 'power_samples_mw': get('Power(_Total)', 'CPU W', mult=1000), |
| 195 'energy_consumption_mwh': |
| 196 sum(map(operator.mul, |
| 197 get('Power(_Total)', 'CPU W', mult=1000), |
| 198 get('sys', 'Interval(secs)', mult=1./3600.))), |
| 199 }, |
| 200 'disk': { |
| 201 'power_samples_mw': get('Power(_Total)', 'Disk W', mult=1000), |
| 202 'energy_consumption_mwh': |
| 203 sum(map(operator.mul, |
| 204 get('Power(_Total)', 'Disk W', mult=1000), |
| 205 get('sys', 'Interval(secs)', mult=1./3600.))), |
| 206 }, |
| 207 'gpu': { |
| 208 'power_samples_mw': get('Power(_Total)', 'GPU W', mult=1000), |
| 209 'energy_consumption_mwh': |
| 210 sum(map(operator.mul, |
| 211 get('Power(_Total)', 'GPU W', mult=1000), |
| 212 get('sys', 'Interval(secs)', mult=1./3600.))), |
| 213 }, |
| 214 }, |
| 215 } |
| 216 |
| 217 # Find Chrome processes in data. Note that this won't work if there are |
| 218 # extra Chrome processes lying around. |
| 219 chrome_keys = set() |
| 220 for iteration in data: |
| 221 for key in iteration.iterkeys(): |
| 222 parts = key.split('\\') |
| 223 if (len(parts) >= 4 and |
| 224 re.match(r'Process\(Google Chrome [0-9]+\)', parts[3])): |
| 225 chrome_keys.add(parts[3]) |
| 226 # Add Chrome process power usage to result. |
| 227 # Note that this is only an estimate of Chrome's CPU power usage. |
| 228 if chrome_keys: |
| 229 per_process_power_usage = [ |
| 230 get(key, 'CPU Power W', default=0, mult=1000) for key in chrome_keys] |
| 231 result['application_energy_consumption_mwh'] = ( |
| 232 sum(map(operator.mul, |
| 233 map(sum, zip(*per_process_power_usage)), |
| 234 get('sys', 'Interval(secs)', mult=1./3600.)))) |
| 235 |
| 236 return result |
OLD | NEW |