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