Update To 11.40.268.0
[platform/framework/web/crosswalk.git] / src / tools / telemetry / telemetry / core / platform / win_platform_backend.py
1 # Copyright 2013 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 atexit
6 import collections
7 import contextlib
8 import ctypes
9 import os
10 import platform
11 import re
12 import socket
13 import struct
14 import subprocess
15 import sys
16 import time
17 import zipfile
18
19 from telemetry import decorators
20 from telemetry.core import exceptions
21 from telemetry.core import util
22 from telemetry.core.platform import desktop_platform_backend
23 from telemetry.core.platform import platform_backend
24 from telemetry.core.platform.power_monitor import msr_power_monitor
25 from telemetry.util import cloud_storage
26 from telemetry.util import path
27
28 try:
29   import pywintypes  # pylint: disable=F0401
30   import win32api  # pylint: disable=F0401
31   from win32com.shell import shell  # pylint: disable=F0401
32   from win32com.shell import shellcon  # pylint: disable=F0401
33   import win32con  # pylint: disable=F0401
34   import win32process  # pylint: disable=F0401
35   import win32security  # pylint: disable=F0401
36 except ImportError:
37   pywintypes = None
38   shell = None
39   shellcon = None
40   win32api = None
41   win32con = None
42   win32process = None
43   win32security = None
44
45
46 def _InstallWinRing0():
47   """WinRing0 is used for reading MSRs."""
48   executable_dir = os.path.dirname(sys.executable)
49
50   python_is_64_bit = sys.maxsize > 2 ** 32
51   dll_file_name = 'WinRing0x64.dll' if python_is_64_bit else 'WinRing0.dll'
52   dll_path = os.path.join(executable_dir, dll_file_name)
53
54   os_is_64_bit = platform.machine().endswith('64')
55   driver_file_name = 'WinRing0x64.sys' if os_is_64_bit else 'WinRing0.sys'
56   driver_path = os.path.join(executable_dir, driver_file_name)
57
58   # Check for WinRing0 and download if needed.
59   if not (os.path.exists(dll_path) and os.path.exists(driver_path)):
60     win_binary_dir = os.path.join(path.GetTelemetryDir(), 'bin', 'win')
61     zip_path = os.path.join(win_binary_dir, 'winring0.zip')
62     cloud_storage.GetIfChanged(zip_path, bucket=cloud_storage.PUBLIC_BUCKET)
63     try:
64       with zipfile.ZipFile(zip_path, 'r') as zip_file:
65         # Install DLL.
66         if not os.path.exists(dll_path):
67           zip_file.extract(dll_file_name, executable_dir)
68         # Install kernel driver.
69         if not os.path.exists(driver_path):
70           zip_file.extract(driver_file_name, executable_dir)
71     finally:
72       os.remove(zip_path)
73
74
75 def TerminateProcess(process_handle):
76   if not process_handle:
77     return
78   if win32process.GetExitCodeProcess(process_handle) == win32con.STILL_ACTIVE:
79     win32process.TerminateProcess(process_handle, 0)
80   process_handle.close()
81
82
83 class WinPlatformBackend(desktop_platform_backend.DesktopPlatformBackend):
84   def __init__(self):
85     super(WinPlatformBackend, self).__init__()
86     self._msr_server_handle = None
87     self._msr_server_port = None
88     self._power_monitor = msr_power_monitor.MsrPowerMonitor(self)
89
90   def __del__(self):
91     self.close()
92
93   def close(self):
94     self.CloseMsrServer()
95
96   def CloseMsrServer(self):
97     if not self._msr_server_handle:
98       return
99
100     TerminateProcess(self._msr_server_handle)
101     self._msr_server_handle = None
102     self._msr_server_port = None
103
104   # pylint: disable=W0613
105   def StartRawDisplayFrameRateMeasurement(self):
106     raise NotImplementedError()
107
108   def StopRawDisplayFrameRateMeasurement(self):
109     raise NotImplementedError()
110
111   def GetRawDisplayFrameRateMeasurements(self):
112     raise NotImplementedError()
113
114   def IsThermallyThrottled(self):
115     raise NotImplementedError()
116
117   def HasBeenThermallyThrottled(self):
118     raise NotImplementedError()
119
120   def GetSystemCommitCharge(self):
121     performance_info = self._GetPerformanceInfo()
122     return performance_info.CommitTotal * performance_info.PageSize / 1024
123
124   @decorators.Cache
125   def GetSystemTotalPhysicalMemory(self):
126     performance_info = self._GetPerformanceInfo()
127     return performance_info.PhysicalTotal * performance_info.PageSize / 1024
128
129   def GetCpuStats(self, pid):
130     cpu_info = self._GetWin32ProcessInfo(win32process.GetProcessTimes, pid)
131     # Convert 100 nanosecond units to seconds
132     cpu_time = (cpu_info['UserTime'] / 1e7 +
133                 cpu_info['KernelTime'] / 1e7)
134     return {'CpuProcessTime': cpu_time}
135
136   def GetCpuTimestamp(self):
137     """Return current timestamp in seconds."""
138     return {'TotalTime': time.time()}
139
140   def GetMemoryStats(self, pid):
141     memory_info = self._GetWin32ProcessInfo(
142         win32process.GetProcessMemoryInfo, pid)
143     return {'VM': memory_info['PagefileUsage'],
144             'VMPeak': memory_info['PeakPagefileUsage'],
145             'WorkingSetSize': memory_info['WorkingSetSize'],
146             'WorkingSetSizePeak': memory_info['PeakWorkingSetSize']}
147
148   def GetIOStats(self, pid):
149     io_stats = self._GetWin32ProcessInfo(win32process.GetProcessIoCounters, pid)
150     return {'ReadOperationCount': io_stats['ReadOperationCount'],
151             'WriteOperationCount': io_stats['WriteOperationCount'],
152             'ReadTransferCount': io_stats['ReadTransferCount'],
153             'WriteTransferCount': io_stats['WriteTransferCount']}
154
155   def KillProcess(self, pid, kill_process_tree=False):
156     # os.kill for Windows is Python 2.7.
157     cmd = ['taskkill', '/F', '/PID', str(pid)]
158     if kill_process_tree:
159       cmd.append('/T')
160     subprocess.Popen(cmd, stdout=subprocess.PIPE,
161                      stderr=subprocess.STDOUT).communicate()
162
163   def GetSystemProcessInfo(self):
164     # [3:] To skip 2 blank lines and header.
165     lines = subprocess.Popen(
166         ['wmic', 'process', 'get',
167          'CommandLine,CreationDate,Name,ParentProcessId,ProcessId',
168          '/format:csv'],
169         stdout=subprocess.PIPE).communicate()[0].splitlines()[3:]
170     process_info = []
171     for line in lines:
172       if not line:
173         continue
174       parts = line.split(',')
175       pi = {}
176       pi['ProcessId'] = int(parts[-1])
177       pi['ParentProcessId'] = int(parts[-2])
178       pi['Name'] = parts[-3]
179       creation_date = None
180       if parts[-4]:
181         creation_date = float(re.split('[+-]', parts[-4])[0])
182       pi['CreationDate'] = creation_date
183       pi['CommandLine'] = ','.join(parts[1:-4])
184       process_info.append(pi)
185     return process_info
186
187   def GetChildPids(self, pid):
188     """Retunds a list of child pids of |pid|."""
189     ppid_map = collections.defaultdict(list)
190     creation_map = {}
191     for pi in self.GetSystemProcessInfo():
192       ppid_map[pi['ParentProcessId']].append(pi['ProcessId'])
193       if pi['CreationDate']:
194         creation_map[pi['ProcessId']] = pi['CreationDate']
195
196     def _InnerGetChildPids(pid):
197       if not pid or pid not in ppid_map:
198         return []
199       ret = [p for p in ppid_map[pid] if creation_map[p] >= creation_map[pid]]
200       for child in ret:
201         if child == pid:
202           continue
203         ret.extend(_InnerGetChildPids(child))
204       return ret
205
206     return _InnerGetChildPids(pid)
207
208   def GetCommandLine(self, pid):
209     for pi in self.GetSystemProcessInfo():
210       if pid == pi['ProcessId']:
211         return pi['CommandLine']
212     raise exceptions.ProcessGoneException()
213
214   @decorators.Cache
215   def GetArchName(self):
216     return platform.machine()
217
218   def GetOSName(self):
219     return 'win'
220
221   @decorators.Cache
222   def GetOSVersionName(self):
223     os_version = platform.uname()[3]
224
225     if os_version.startswith('5.1.'):
226       return platform_backend.XP
227     if os_version.startswith('6.0.'):
228       return platform_backend.VISTA
229     if os_version.startswith('6.1.'):
230       return platform_backend.WIN7
231     if os_version.startswith('6.2.'):
232       return platform_backend.WIN8
233
234     raise NotImplementedError('Unknown win version %s.' % os_version)
235
236   def CanFlushIndividualFilesFromSystemCache(self):
237     return True
238
239   def _GetWin32ProcessInfo(self, func, pid):
240     mask = (win32con.PROCESS_QUERY_INFORMATION |
241             win32con.PROCESS_VM_READ)
242     handle = None
243     try:
244       handle = win32api.OpenProcess(mask, False, pid)
245       return func(handle)
246     except pywintypes.error, e:
247       errcode = e[0]
248       if errcode == 87:
249         raise exceptions.ProcessGoneException()
250       raise
251     finally:
252       if handle:
253         win32api.CloseHandle(handle)
254
255   def _GetPerformanceInfo(self):
256     class PerformanceInfo(ctypes.Structure):
257       """Struct for GetPerformanceInfo() call
258       http://msdn.microsoft.com/en-us/library/ms683210
259       """
260       _fields_ = [('size', ctypes.c_ulong),
261                   ('CommitTotal', ctypes.c_size_t),
262                   ('CommitLimit', ctypes.c_size_t),
263                   ('CommitPeak', ctypes.c_size_t),
264                   ('PhysicalTotal', ctypes.c_size_t),
265                   ('PhysicalAvailable', ctypes.c_size_t),
266                   ('SystemCache', ctypes.c_size_t),
267                   ('KernelTotal', ctypes.c_size_t),
268                   ('KernelPaged', ctypes.c_size_t),
269                   ('KernelNonpaged', ctypes.c_size_t),
270                   ('PageSize', ctypes.c_size_t),
271                   ('HandleCount', ctypes.c_ulong),
272                   ('ProcessCount', ctypes.c_ulong),
273                   ('ThreadCount', ctypes.c_ulong)]
274
275       def __init__(self):
276         self.size = ctypes.sizeof(self)
277         super(PerformanceInfo, self).__init__()
278
279     performance_info = PerformanceInfo()
280     ctypes.windll.psapi.GetPerformanceInfo(
281         ctypes.byref(performance_info), performance_info.size)
282     return performance_info
283
284   def IsCurrentProcessElevated(self):
285     if self.GetOSVersionName() < platform_backend.VISTA:
286       # TOKEN_QUERY is not defined before Vista. All processes are elevated.
287       return True
288
289     handle = win32process.GetCurrentProcess()
290     with contextlib.closing(
291         win32security.OpenProcessToken(handle, win32con.TOKEN_QUERY)) as token:
292       return bool(win32security.GetTokenInformation(
293           token, win32security.TokenElevation))
294
295   def LaunchApplication(
296       self, application, parameters=None, elevate_privilege=False):
297     """Launch an application. Returns a PyHANDLE object."""
298
299     parameters = ' '.join(parameters) if parameters else ''
300     if elevate_privilege and not self.IsCurrentProcessElevated():
301       # Use ShellExecuteEx() instead of subprocess.Popen()/CreateProcess() to
302       # elevate privileges. A new console will be created if the new process has
303       # different permissions than this process.
304       proc_info = shell.ShellExecuteEx(
305           fMask=shellcon.SEE_MASK_NOCLOSEPROCESS | shellcon.SEE_MASK_NO_CONSOLE,
306           lpVerb='runas' if elevate_privilege else '',
307           lpFile=application,
308           lpParameters=parameters,
309           nShow=win32con.SW_HIDE)
310       if proc_info['hInstApp'] <= 32:
311         raise Exception('Unable to launch %s' % application)
312       return proc_info['hProcess']
313     else:
314       handle, _, _, _ = win32process.CreateProcess(
315           None, application + ' ' + parameters, None, None, False,
316           win32process.CREATE_NO_WINDOW, None, None, win32process.STARTUPINFO())
317       return handle
318
319   def CanMonitorPower(self):
320     return self._power_monitor.CanMonitorPower()
321
322   def CanMeasurePerApplicationPower(self):
323     return self._power_monitor.CanMeasurePerApplicationPower()
324
325   def StartMonitoringPower(self, browser):
326     self._power_monitor.StartMonitoringPower(browser)
327
328   def StopMonitoringPower(self):
329     return self._power_monitor.StopMonitoringPower()
330
331   def _StartMsrServerIfNeeded(self):
332     if self._msr_server_handle:
333       return
334
335     _InstallWinRing0()
336     self._msr_server_port = util.GetUnreservedAvailableLocalPort()
337     # It might be flaky to get a port number without reserving it atomically,
338     # but if the server process chooses a port, we have no way of getting it.
339     # The stdout of the elevated process isn't accessible.
340     parameters = (
341         os.path.join(os.path.dirname(__file__), 'msr_server_win.py'),
342         str(self._msr_server_port),
343     )
344     self._msr_server_handle = self.LaunchApplication(
345         sys.executable, parameters, elevate_privilege=True)
346     # Wait for server to start.
347     try:
348       socket.create_connection(('127.0.0.1', self._msr_server_port), 5).close()
349     except socket.error:
350       self.CloseMsrServer()
351     atexit.register(TerminateProcess, self._msr_server_handle)
352
353   def ReadMsr(self, msr_number, start=0, length=64):
354     self._StartMsrServerIfNeeded()
355     if not self._msr_server_handle:
356       raise OSError('Unable to start MSR server.')
357
358     sock = socket.create_connection(('127.0.0.1', self._msr_server_port), 0.1)
359     try:
360       sock.sendall(struct.pack('I', msr_number))
361       response = sock.recv(8)
362     finally:
363       sock.close()
364     return struct.unpack('Q', response)[0] >> start & ((1 << length) - 1)