| 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
 | 
| 
 |