OLD | NEW |
---|---|
(Empty) | |
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 csv | |
6 import operator | |
7 import os | |
8 import platform | |
9 import re | |
10 import shutil | |
11 import tempfile | |
12 import urllib2 | |
13 | |
14 from telemetry.core.platform import platform_backend | |
15 from telemetry.core.platform import power_monitor | |
16 from telemetry.util import statistics | |
17 | |
18 try: | |
19 import win32event # pylint: disable=F0401 | |
20 except ImportError: | |
21 win32event = None | |
22 | |
23 | |
24 IPPET_PATH = os.path.join( | |
25 '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.
| |
26 'Intel(R) Platform Power Estimation Tool', 'ippet.exe') | |
27 | |
28 | |
29 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
| |
30 def __init__(self, backend): | |
31 super(IppetPowerMonitor, self).__init__() | |
32 self._backend = backend | |
33 self._ippet_handle = None | |
34 self._output_dir = None | |
35 | |
36 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.
| |
37 windows_7_or_later = ( | |
38 self._backend.GetOSName() == 'win' and | |
39 self._backend.GetOSVersionName() >= platform_backend.WIN7) | |
40 | |
41 # This check works on Windows only. | |
42 family, model = map(int, re.match('.+ Family ([0-9]+) Model ([0-9]+)', | |
43 platform.processor()).groups()) | |
44 # Model numbers from: | |
45 # https://software.intel.com/en-us/articles/intel-architecture-and- \ | |
46 # processor-identification-with-cpuid-model-and-family-numbers | |
47 # http://www.speedtraq.com | |
48 sandy_bridge_or_later = ('Intel' in platform.processor() and family == 6 and | |
49 (model in (0x2A, 0x2D) or model >= 0x30)) | |
50 | |
51 ippet_installed = (os.path.isfile(IPPET_PATH) and | |
52 os.access(IPPET_PATH, os.X_OK)) | |
53 | |
54 return (windows_7_or_later and sandy_bridge_or_later and | |
55 ippet_installed and win32event) | |
tonyg
2014/07/21 21:26:04
Maybe this method should shortcut?
dtu
2014/07/24 01:13:53
Done.
| |
56 | |
57 def StartMonitoringPower(self, browser): | |
58 assert not self._ippet_handle, 'Called StartMonitoringPower() twice.' | |
59 self._output_dir = tempfile.mkdtemp() | |
60 parameters = ['-log_dir', self._output_dir, '-zip', 'n', | |
61 '-all_processes', '-l', '0'] | |
62 self._ippet_handle = self._backend.LaunchApplication(IPPET_PATH, parameters, | |
63 elevate_privilege=True) | |
64 | |
65 def StopMonitoringPower(self): | |
66 assert self._ippet_handle, ( | |
67 'Called StopMonitoringPower() before StartMonitoringPower().') | |
68 # Stop IPPET. | |
69 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.
| |
70 win32event.WaitForSingleObject(self._ippet_handle, 5000) | |
71 self._ippet_handle = None | |
72 | |
73 # Read IPPET's log file. | |
74 log_file = os.path.join(self._output_dir, 'ippet_log_processes.xls') | |
75 with open(log_file, 'r') as f: | |
76 reader = csv.DictReader(f, dialect='excel-tab') | |
77 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
| |
78 shutil.rmtree(self._output_dir) | |
79 self._output_dir = None | |
80 | |
81 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
| |
82 """Pull all iterations of a field from the IPPET data as a list. | |
83 | |
84 Args: | |
85 args: A list representing the field name. | |
86 mult: A cosntant to multiply the field's value by, for unit conversions. | |
87 default: The default value if the field is not found in the iteration. | |
88 | |
89 Returns: | |
90 A list containing the field's value across all iterations. | |
91 """ | |
92 key = '\\\\.\\' + '\\'.join(args) | |
93 def value(line): | |
94 if key in line: | |
95 return line[key] | |
96 elif 'default' in kwargs: | |
97 return kwargs['default'] | |
98 else: | |
99 raise KeyError('Key "%s" not found in data and ' | |
100 'no default was given.' % key) | |
101 return [float(value(line)) * kwargs.get('mult', 1) for line in data] | |
102 | |
103 result = { | |
104 'identifier': 'ippet', | |
105 'power_samples_mw': get('Power(_Total)', 'Package W', mult=1000), | |
106 'energy_consumption_mwh': | |
107 sum(map(operator.mul, | |
108 get('Power(_Total)', 'Package W', mult=1000), | |
109 get('sys', 'Interval(secs)', mult=1./3600.))), | |
110 'component_utilization': { | |
111 'whole_package': { | |
112 'average_temperature_c': | |
113 statistics.ArithmeticMean(get( | |
114 'Temperature(Package)', 'Current C')), | |
115 }, | |
116 'cpu': { | |
117 'power_samples_mw': get('Power(_Total)', 'CPU W', mult=1000), | |
118 'energy_consumption_mwh': | |
119 sum(map(operator.mul, | |
120 get('Power(_Total)', 'CPU W', mult=1000), | |
121 get('sys', 'Interval(secs)', mult=1./3600.))), | |
122 }, | |
123 '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.
| |
124 'power_samples_mw': get('Power(_Total)', 'Disk W', mult=1000), | |
125 'energy_consumption_mwh': | |
126 sum(map(operator.mul, | |
127 get('Power(_Total)', 'Disk W', mult=1000), | |
128 get('sys', 'Interval(secs)', mult=1./3600.))), | |
129 }, | |
130 'gpu': { | |
131 'power_samples_mw': get('Power(_Total)', 'GPU W', mult=1000), | |
132 'energy_consumption_mwh': | |
133 sum(map(operator.mul, | |
134 get('Power(_Total)', 'GPU W', mult=1000), | |
135 get('sys', 'Interval(secs)', mult=1./3600.))), | |
136 }, | |
137 }, | |
138 } | |
139 | |
140 # Find Chrome processes in data. | |
141 chrome_keys = set() | |
142 for iteration in data: | |
143 for key in iteration.iterkeys(): | |
144 parts = key.split('\\') | |
145 if (len(parts) >= 4 and | |
146 re.match(r'Process\(Google Chrome [0-9]+\)', parts[3])): | |
147 chrome_keys.add(parts[3]) | |
148 # Add Chrome process power usage to result. | |
149 if chrome_keys: | |
150 per_process_power_usage = [ | |
151 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
| |
152 result['application_energy_consumption_mwh'] = ( | |
153 sum(map(operator.mul, | |
154 map(sum, zip(*per_process_power_usage)), | |
155 get('sys', 'Interval(secs)', mult=1./3600.)))) | |
156 | |
157 return result | |
OLD | NEW |