Chromium Code Reviews| 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 |