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..5ffc3bcfbb867ef6686aff7dee47dc58e174c564 |
--- /dev/null |
+++ b/tools/telemetry/telemetry/core/platform/power_monitor/ippet_power_monitor.py |
@@ -0,0 +1,157 @@ |
+# 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 operator |
+import os |
+import platform |
+import re |
+import shutil |
+import tempfile |
+import urllib2 |
+ |
+from telemetry.core.platform import platform_backend |
+from telemetry.core.platform import power_monitor |
+from telemetry.util import statistics |
+ |
+try: |
+ import win32event # pylint: disable=F0401 |
+except ImportError: |
+ win32event = None |
+ |
+ |
+IPPET_PATH = os.path.join( |
+ 'C:\\', 'Program Files (x86)', 'Intel', |
tonyg
2014/07/21 21:26:03
I think we want to reuse the os.getenv() code in d
dtu
2014/07/24 01:13:53
Done.
|
+ 'Intel(R) Platform Power Estimation Tool', 'ippet.exe') |
+ |
+ |
+class IppetPowerMonitor(power_monitor.PowerMonitor): |
tonyg
2014/07/21 21:26:04
Doesn't this need to be registered somewhere or do
dtu
2014/07/24 01:13:53
Done. It's really ad-hoc right now, could use a re
|
+ def __init__(self, backend): |
+ super(IppetPowerMonitor, self).__init__() |
+ self._backend = backend |
+ self._ippet_handle = None |
+ self._output_dir = None |
+ |
+ def CanMonitorPower(self): |
tonyg
2014/07/21 21:26:04
Is this method worth caching?
dtu
2014/07/24 01:13:53
Eh, none of these checks are expensive.
|
+ windows_7_or_later = ( |
+ self._backend.GetOSName() == 'win' and |
+ self._backend.GetOSVersionName() >= platform_backend.WIN7) |
+ |
+ # 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)) |
+ |
+ ippet_installed = (os.path.isfile(IPPET_PATH) and |
+ os.access(IPPET_PATH, os.X_OK)) |
+ |
+ return (windows_7_or_later and sandy_bridge_or_later and |
+ ippet_installed and win32event) |
tonyg
2014/07/21 21:26:04
Maybe this method should shortcut?
dtu
2014/07/24 01:13:53
Done.
|
+ |
+ def StartMonitoringPower(self, browser): |
+ assert not self._ippet_handle, 'Called StartMonitoringPower() twice.' |
+ self._output_dir = tempfile.mkdtemp() |
+ parameters = ['-log_dir', self._output_dir, '-zip', 'n', |
+ '-all_processes', '-l', '0'] |
+ self._ippet_handle = self._backend.LaunchApplication(IPPET_PATH, parameters, |
+ elevate_privilege=True) |
+ |
+ def StopMonitoringPower(self): |
+ assert self._ippet_handle, ( |
+ 'Called StopMonitoringPower() before StartMonitoringPower().') |
+ # Stop IPPET. |
+ urllib2.urlopen('http://127.0.0.1:8080/ippet?cmd=quit').read() |
tonyg
2014/07/21 21:26:03
Can the command line take an explicit port? This'd
dtu
2014/07/24 01:13:53
Done.
|
+ win32event.WaitForSingleObject(self._ippet_handle, 5000) |
+ self._ippet_handle = None |
+ |
+ # Read IPPET's log file. |
+ log_file = os.path.join(self._output_dir, 'ippet_log_processes.xls') |
+ with open(log_file, 'r') as f: |
+ reader = csv.DictReader(f, dialect='excel-tab') |
+ data = list(reader)[1:] # There's not much data in the initial iteration. |
tonyg
2014/07/21 21:26:04
Is this always true? Maybe the amount of data is t
dtu
2014/07/24 01:13:53
Yes it's always true. The "zeroth" iteration repor
|
+ shutil.rmtree(self._output_dir) |
+ self._output_dir = None |
+ |
+ def get(*args, **kwargs): |
tonyg
2014/07/21 21:26:04
Worth unittesting?
dtu
2014/07/24 01:13:53
This internal function, no, because it's not a pub
|
+ """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': { |
tonyg
2014/07/21 21:26:03
Oh cool, I didn't know it was capable of disk.
dtu
2014/07/24 01:13:53
Acknowledged.
|
+ '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. |
+ 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. |
+ if chrome_keys: |
+ per_process_power_usage = [ |
+ get(key, 'CPU Power W', default=0, mult=1000) for key in chrome_keys] |
dtu
2014/07/24 01:13:53
I'd also note that "application_energy_consumption
|
+ 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 |