Index: tools/telemetry/telemetry/core/platform/power_monitor/ippet_power_monitor.py |
diff --git a/tools/telemetry/telemetry/core/platform/power_monitor/ippet_power_monitor.py b/tools/telemetry/telemetry/core/platform/power_monitor/ippet_power_monitor.py |
new file mode 100644 |
index 0000000000000000000000000000000000000000..eaf031aa138b654fa3824492ef5a870376272f7f |
--- /dev/null |
+++ b/tools/telemetry/telemetry/core/platform/power_monitor/ippet_power_monitor.py |
@@ -0,0 +1,236 @@ |
+# Copyright 2014 The Chromium Authors. All rights reserved. |
+# Use of this source code is governed by a BSD-style license that can be |
+# found in the LICENSE file. |
+ |
+import csv |
+import logging |
+import operator |
+import os |
+import platform |
+import re |
+import shutil |
+import tempfile |
+import urllib2 |
+import zipfile |
+ |
+from telemetry import decorators |
+from telemetry.core import util |
+from telemetry.core.platform import platform_backend |
+from telemetry.core.platform import power_monitor |
+from telemetry.util import cloud_storage |
+from telemetry.util import path |
+from telemetry.util import statistics |
+ |
+try: |
+ import win32con # pylint: disable=F0401 |
+ import win32event # pylint: disable=F0401 |
+ import win32process # pylint: disable=F0401 |
+except ImportError: |
+ win32con = None |
+ win32event = None |
+ win32process = None |
+ |
+ |
+class IppetError(Exception): |
+ pass |
+ |
+ |
+@decorators.Cache |
+def IppetPath(): |
+ # Look for pre-installed IPPET. |
+ ippet_path = path.FindInstalledWindowsApplication(os.path.join( |
+ 'Intel', 'Intel(R) Platform Power Estimation Tool', 'ippet.exe')) |
+ if ippet_path: |
+ return ippet_path |
+ |
+ # Look for IPPET installed previously by this script. |
+ ippet_path = os.path.join( |
+ path.GetTelemetryDir(), 'bin', 'win', 'ippet', 'ippet.exe') |
+ if path.IsExecutable(ippet_path): |
+ return ippet_path |
+ |
+ # Install IPPET. |
+ zip_path = os.path.join(path.GetTelemetryDir(), 'bin', 'win', 'ippet.zip') |
+ cloud_storage.GetIfChanged(zip_path, bucket=cloud_storage.PUBLIC_BUCKET) |
+ with zipfile.ZipFile(zip_path, 'r') as zip_file: |
+ zip_file.extractall(os.path.dirname(zip_path)) |
+ os.remove(zip_path) |
+ |
+ if path.IsExecutable(ippet_path): |
+ return ippet_path |
+ |
+ return None |
+ |
+ |
+class IppetPowerMonitor(power_monitor.PowerMonitor): |
+ def __init__(self, backend): |
+ super(IppetPowerMonitor, self).__init__() |
+ self._backend = backend |
+ self._ippet_handle = None |
+ self._ippet_port = None |
+ self._output_dir = None |
+ |
+ def CanMonitorPower(self): |
+ if not win32event: |
+ return False |
+ |
+ windows_7_or_later = ( |
+ self._backend.GetOSName() == 'win' and |
+ self._backend.GetOSVersionName() >= platform_backend.WIN7) |
+ if not windows_7_or_later: |
+ return False |
+ |
+ # This check works on Windows only. |
+ family, model = map(int, re.match('.+ Family ([0-9]+) Model ([0-9]+)', |
+ platform.processor()).groups()) |
+ # Model numbers from: |
+ # https://software.intel.com/en-us/articles/intel-architecture-and- \ |
+ # processor-identification-with-cpuid-model-and-family-numbers |
+ # http://www.speedtraq.com |
+ sandy_bridge_or_later = ('Intel' in platform.processor() and family == 6 and |
+ (model in (0x2A, 0x2D) or model >= 0x30)) |
+ if not sandy_bridge_or_later: |
+ return False |
+ |
+ if not IppetPath(): |
+ return False |
+ |
+ return True |
+ |
+ def CanMeasurePerApplicationPower(self): |
+ return self.CanMonitorPower() |
+ |
+ def StartMonitoringPower(self, browser): |
+ assert not self._ippet_handle, 'Called StartMonitoringPower() twice.' |
+ self._output_dir = tempfile.mkdtemp() |
+ self._ippet_port = util.GetUnreservedAvailableLocalPort() |
+ parameters = ['-log_dir', self._output_dir, |
+ '-web_port', str(self._ippet_port), |
+ '-zip', 'n', '-all_processes', '-l', '0'] |
+ self._ippet_handle = self._backend.LaunchApplication( |
+ IppetPath(), parameters, elevate_privilege=True) |
+ |
+ def IppetServerIsUp(): |
+ try: |
+ urllib2.urlopen('http://127.0.0.1:%d/ippet' % self._ippet_port) |
+ except urllib2.URLError: |
+ return False |
+ return True |
+ util.WaitFor(IppetServerIsUp, timeout=5) |
+ |
+ def StopMonitoringPower(self): |
+ assert self._ippet_handle, ( |
+ 'Called StopMonitoringPower() before StartMonitoringPower().') |
+ # Stop IPPET. |
+ ippet_quit_url = 'http://127.0.0.1:%d/ippet?cmd=quit' % self._ippet_port |
+ quit_output = urllib2.urlopen(ippet_quit_url).read() |
+ if quit_output != 'quiting\r\n': |
+ raise IppetError('Failed to quit IPPET: %s' % quit_output.strip()) |
+ wait_return_code = win32event.WaitForSingleObject(self._ippet_handle, 20000) |
+ if wait_return_code != win32event.WAIT_OBJECT_0: |
+ if wait_return_code == win32event.WAIT_TIMEOUT: |
+ raise IppetError('Timed out waiting for IPPET to close.') |
+ else: |
+ raise IppetError('Error code %d while waiting for IPPET to close.' % |
+ wait_return_code) |
+ ippet_exit_code = win32process.GetExitCodeProcess(self._ippet_handle) |
+ if ippet_exit_code == win32con.STILL_ACTIVE: |
+ raise IppetError('IPPET is still running but should have stopped.') |
+ elif ippet_exit_code != 0: |
+ raise IppetError('IPPET closed with exit code %d.' % ippet_exit_code) |
+ self._ippet_handle.Close() |
+ self._ippet_handle = None |
+ self._ippet_port = None |
+ |
+ # Read IPPET's log file. |
+ log_file = os.path.join(self._output_dir, 'ippet_log_processes.xls') |
+ try: |
+ with open(log_file, 'r') as f: |
+ reader = csv.DictReader(f, dialect='excel-tab') |
+ data = list(reader)[1:] # The first iteration only reports temperature. |
+ except IOError: |
+ logging.error('Output directory %s contains: %s', |
+ self._output_dir, os.listdir(self._output_dir)) |
+ raise |
+ shutil.rmtree(self._output_dir) |
+ self._output_dir = None |
+ |
+ def get(*args, **kwargs): |
+ """Pull all iterations of a field from the IPPET data as a list. |
+ |
+ Args: |
+ args: A list representing the field name. |
+ mult: A cosntant to multiply the field's value by, for unit conversions. |
+ default: The default value if the field is not found in the iteration. |
+ |
+ Returns: |
+ A list containing the field's value across all iterations. |
+ """ |
+ key = '\\\\.\\' + '\\'.join(args) |
+ def value(line): |
+ if key in line: |
+ return line[key] |
+ elif 'default' in kwargs: |
+ return kwargs['default'] |
+ else: |
+ raise KeyError('Key "%s" not found in data and ' |
+ 'no default was given.' % key) |
+ return [float(value(line)) * kwargs.get('mult', 1) for line in data] |
+ |
+ result = { |
+ 'identifier': 'ippet', |
+ 'power_samples_mw': get('Power(_Total)', 'Package W', mult=1000), |
+ 'energy_consumption_mwh': |
+ sum(map(operator.mul, |
+ get('Power(_Total)', 'Package W', mult=1000), |
+ get('sys', 'Interval(secs)', mult=1./3600.))), |
+ 'component_utilization': { |
+ 'whole_package': { |
+ 'average_temperature_c': |
+ statistics.ArithmeticMean(get( |
+ 'Temperature(Package)', 'Current C')), |
+ }, |
+ 'cpu': { |
+ 'power_samples_mw': get('Power(_Total)', 'CPU W', mult=1000), |
+ 'energy_consumption_mwh': |
+ sum(map(operator.mul, |
+ get('Power(_Total)', 'CPU W', mult=1000), |
+ get('sys', 'Interval(secs)', mult=1./3600.))), |
+ }, |
+ 'disk': { |
+ 'power_samples_mw': get('Power(_Total)', 'Disk W', mult=1000), |
+ 'energy_consumption_mwh': |
+ sum(map(operator.mul, |
+ get('Power(_Total)', 'Disk W', mult=1000), |
+ get('sys', 'Interval(secs)', mult=1./3600.))), |
+ }, |
+ 'gpu': { |
+ 'power_samples_mw': get('Power(_Total)', 'GPU W', mult=1000), |
+ 'energy_consumption_mwh': |
+ sum(map(operator.mul, |
+ get('Power(_Total)', 'GPU W', mult=1000), |
+ get('sys', 'Interval(secs)', mult=1./3600.))), |
+ }, |
+ }, |
+ } |
+ |
+ # Find Chrome processes in data. Note that this won't work if there are |
+ # extra Chrome processes lying around. |
+ chrome_keys = set() |
+ for iteration in data: |
+ for key in iteration.iterkeys(): |
+ parts = key.split('\\') |
+ if (len(parts) >= 4 and |
+ re.match(r'Process\(Google Chrome [0-9]+\)', parts[3])): |
+ chrome_keys.add(parts[3]) |
+ # Add Chrome process power usage to result. |
+ # Note that this is only an estimate of Chrome's CPU power usage. |
+ if chrome_keys: |
+ per_process_power_usage = [ |
+ get(key, 'CPU Power W', default=0, mult=1000) for key in chrome_keys] |
+ result['application_energy_consumption_mwh'] = ( |
+ sum(map(operator.mul, |
+ map(sum, zip(*per_process_power_usage)), |
+ get('sys', 'Interval(secs)', mult=1./3600.)))) |
+ |
+ return result |