| Index: tools/telemetry/telemetry/internal/platform/win_platform_backend.py
|
| diff --git a/tools/telemetry/telemetry/internal/platform/win_platform_backend.py b/tools/telemetry/telemetry/internal/platform/win_platform_backend.py
|
| deleted file mode 100644
|
| index ec7e8ae45b7a89a5cd7bfd375aeb40a7d89ac37f..0000000000000000000000000000000000000000
|
| --- a/tools/telemetry/telemetry/internal/platform/win_platform_backend.py
|
| +++ /dev/null
|
| @@ -1,409 +0,0 @@
|
| -# Copyright 2013 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 atexit
|
| -import collections
|
| -import contextlib
|
| -import ctypes
|
| -import logging
|
| -import os
|
| -import platform
|
| -import re
|
| -import socket
|
| -import struct
|
| -import subprocess
|
| -import sys
|
| -import time
|
| -import zipfile
|
| -
|
| -from catapult_base import cloud_storage # pylint: disable=import-error
|
| -
|
| -from telemetry.core import exceptions
|
| -from telemetry.core import os_version as os_version_module
|
| -from telemetry.core import util
|
| -from telemetry import decorators
|
| -from telemetry.internal.platform import desktop_platform_backend
|
| -from telemetry.internal.platform.power_monitor import msr_power_monitor
|
| -from telemetry.internal.util import path
|
| -
|
| -try:
|
| - import pywintypes # pylint: disable=import-error
|
| - import win32api # pylint: disable=import-error
|
| - from win32com.shell import shell # pylint: disable=no-name-in-module
|
| - from win32com.shell import shellcon # pylint: disable=no-name-in-module
|
| - import win32con # pylint: disable=import-error
|
| - import win32gui # pylint: disable=import-error
|
| - import win32process # pylint: disable=import-error
|
| - import win32security # pylint: disable=import-error
|
| -except ImportError:
|
| - pywintypes = None
|
| - shell = None
|
| - shellcon = None
|
| - win32api = None
|
| - win32con = None
|
| - win32gui = None
|
| - win32process = None
|
| - win32security = None
|
| -
|
| -
|
| -def _InstallWinRing0():
|
| - """WinRing0 is used for reading MSRs."""
|
| - executable_dir = os.path.dirname(sys.executable)
|
| -
|
| - python_is_64_bit = sys.maxsize > 2 ** 32
|
| - dll_file_name = 'WinRing0x64.dll' if python_is_64_bit else 'WinRing0.dll'
|
| - dll_path = os.path.join(executable_dir, dll_file_name)
|
| -
|
| - os_is_64_bit = platform.machine().endswith('64')
|
| - driver_file_name = 'WinRing0x64.sys' if os_is_64_bit else 'WinRing0.sys'
|
| - driver_path = os.path.join(executable_dir, driver_file_name)
|
| -
|
| - # Check for WinRing0 and download if needed.
|
| - if not (os.path.exists(dll_path) and os.path.exists(driver_path)):
|
| - win_binary_dir = os.path.join(
|
| - path.GetTelemetryDir(), 'bin', 'win', 'AMD64')
|
| - zip_path = os.path.join(win_binary_dir, 'winring0.zip')
|
| - cloud_storage.GetIfChanged(zip_path, bucket=cloud_storage.PUBLIC_BUCKET)
|
| - try:
|
| - with zipfile.ZipFile(zip_path, 'r') as zip_file:
|
| - error_message = (
|
| - 'Failed to extract %s into %s. If python claims that '
|
| - 'the zip file is locked, this may be a lie. The problem may be '
|
| - 'that python does not have write permissions to the destination '
|
| - 'directory.'
|
| - )
|
| - # Install DLL.
|
| - if not os.path.exists(dll_path):
|
| - try:
|
| - zip_file.extract(dll_file_name, executable_dir)
|
| - except:
|
| - logging.error(error_message % (dll_file_name, executable_dir))
|
| - raise
|
| -
|
| - # Install kernel driver.
|
| - if not os.path.exists(driver_path):
|
| - try:
|
| - zip_file.extract(driver_file_name, executable_dir)
|
| - except:
|
| - logging.error(error_message % (driver_file_name, executable_dir))
|
| - raise
|
| - finally:
|
| - os.remove(zip_path)
|
| -
|
| -
|
| -def TerminateProcess(process_handle):
|
| - if not process_handle:
|
| - return
|
| - if win32process.GetExitCodeProcess(process_handle) == win32con.STILL_ACTIVE:
|
| - win32process.TerminateProcess(process_handle, 0)
|
| - process_handle.close()
|
| -
|
| -
|
| -class WinPlatformBackend(desktop_platform_backend.DesktopPlatformBackend):
|
| - def __init__(self):
|
| - super(WinPlatformBackend, self).__init__()
|
| - self._msr_server_handle = None
|
| - self._msr_server_port = None
|
| - self._power_monitor = msr_power_monitor.MsrPowerMonitorWin(self)
|
| -
|
| - @classmethod
|
| - def IsPlatformBackendForHost(cls):
|
| - return sys.platform == 'win32'
|
| -
|
| - def __del__(self):
|
| - self.close()
|
| -
|
| - def close(self):
|
| - self.CloseMsrServer()
|
| -
|
| - def CloseMsrServer(self):
|
| - if not self._msr_server_handle:
|
| - return
|
| -
|
| - TerminateProcess(self._msr_server_handle)
|
| - self._msr_server_handle = None
|
| - self._msr_server_port = None
|
| -
|
| - def IsThermallyThrottled(self):
|
| - raise NotImplementedError()
|
| -
|
| - def HasBeenThermallyThrottled(self):
|
| - raise NotImplementedError()
|
| -
|
| - def GetSystemCommitCharge(self):
|
| - performance_info = self._GetPerformanceInfo()
|
| - return performance_info.CommitTotal * performance_info.PageSize / 1024
|
| -
|
| - @decorators.Cache
|
| - def GetSystemTotalPhysicalMemory(self):
|
| - performance_info = self._GetPerformanceInfo()
|
| - return performance_info.PhysicalTotal * performance_info.PageSize / 1024
|
| -
|
| - def GetCpuStats(self, pid):
|
| - cpu_info = self._GetWin32ProcessInfo(win32process.GetProcessTimes, pid)
|
| - # Convert 100 nanosecond units to seconds
|
| - cpu_time = (cpu_info['UserTime'] / 1e7 +
|
| - cpu_info['KernelTime'] / 1e7)
|
| - return {'CpuProcessTime': cpu_time}
|
| -
|
| - def GetCpuTimestamp(self):
|
| - """Return current timestamp in seconds."""
|
| - return {'TotalTime': time.time()}
|
| -
|
| - def GetMemoryStats(self, pid):
|
| - memory_info = self._GetWin32ProcessInfo(
|
| - win32process.GetProcessMemoryInfo, pid)
|
| - return {'VM': memory_info['PagefileUsage'],
|
| - 'VMPeak': memory_info['PeakPagefileUsage'],
|
| - 'WorkingSetSize': memory_info['WorkingSetSize'],
|
| - 'WorkingSetSizePeak': memory_info['PeakWorkingSetSize']}
|
| -
|
| - def KillProcess(self, pid, kill_process_tree=False):
|
| - # os.kill for Windows is Python 2.7.
|
| - cmd = ['taskkill', '/F', '/PID', str(pid)]
|
| - if kill_process_tree:
|
| - cmd.append('/T')
|
| - subprocess.Popen(cmd, stdout=subprocess.PIPE,
|
| - stderr=subprocess.STDOUT).communicate()
|
| -
|
| - def GetSystemProcessInfo(self):
|
| - # [3:] To skip 2 blank lines and header.
|
| - lines = subprocess.Popen(
|
| - ['wmic', 'process', 'get',
|
| - 'CommandLine,CreationDate,Name,ParentProcessId,ProcessId',
|
| - '/format:csv'],
|
| - stdout=subprocess.PIPE).communicate()[0].splitlines()[3:]
|
| - process_info = []
|
| - for line in lines:
|
| - if not line:
|
| - continue
|
| - parts = line.split(',')
|
| - pi = {}
|
| - pi['ProcessId'] = int(parts[-1])
|
| - pi['ParentProcessId'] = int(parts[-2])
|
| - pi['Name'] = parts[-3]
|
| - creation_date = None
|
| - if parts[-4]:
|
| - creation_date = float(re.split('[+-]', parts[-4])[0])
|
| - pi['CreationDate'] = creation_date
|
| - pi['CommandLine'] = ','.join(parts[1:-4])
|
| - process_info.append(pi)
|
| - return process_info
|
| -
|
| - def GetChildPids(self, pid):
|
| - """Retunds a list of child pids of |pid|."""
|
| - ppid_map = collections.defaultdict(list)
|
| - creation_map = {}
|
| - for pi in self.GetSystemProcessInfo():
|
| - ppid_map[pi['ParentProcessId']].append(pi['ProcessId'])
|
| - if pi['CreationDate']:
|
| - creation_map[pi['ProcessId']] = pi['CreationDate']
|
| -
|
| - def _InnerGetChildPids(pid):
|
| - if not pid or pid not in ppid_map:
|
| - return []
|
| - ret = [p for p in ppid_map[pid] if creation_map[p] >= creation_map[pid]]
|
| - for child in ret:
|
| - if child == pid:
|
| - continue
|
| - ret.extend(_InnerGetChildPids(child))
|
| - return ret
|
| -
|
| - return _InnerGetChildPids(pid)
|
| -
|
| - def GetCommandLine(self, pid):
|
| - for pi in self.GetSystemProcessInfo():
|
| - if pid == pi['ProcessId']:
|
| - return pi['CommandLine']
|
| - raise exceptions.ProcessGoneException()
|
| -
|
| - @decorators.Cache
|
| - def GetArchName(self):
|
| - return platform.machine()
|
| -
|
| - def GetOSName(self):
|
| - return 'win'
|
| -
|
| - @decorators.Cache
|
| - def GetOSVersionName(self):
|
| - os_version = platform.uname()[3]
|
| -
|
| - if os_version.startswith('5.1.'):
|
| - return os_version_module.XP
|
| - if os_version.startswith('6.0.'):
|
| - return os_version_module.VISTA
|
| - if os_version.startswith('6.1.'):
|
| - return os_version_module.WIN7
|
| - if os_version.startswith('6.2.'):
|
| - return os_version_module.WIN8
|
| - if os_version.startswith('10.'):
|
| - return os_version_module.WIN10
|
| -
|
| - raise NotImplementedError('Unknown win version %s.' % os_version)
|
| -
|
| - def CanFlushIndividualFilesFromSystemCache(self):
|
| - return True
|
| -
|
| - def _GetWin32ProcessInfo(self, func, pid):
|
| - mask = (win32con.PROCESS_QUERY_INFORMATION |
|
| - win32con.PROCESS_VM_READ)
|
| - handle = None
|
| - try:
|
| - handle = win32api.OpenProcess(mask, False, pid)
|
| - return func(handle)
|
| - except pywintypes.error, e:
|
| - errcode = e[0]
|
| - if errcode == 87:
|
| - raise exceptions.ProcessGoneException()
|
| - raise
|
| - finally:
|
| - if handle:
|
| - win32api.CloseHandle(handle)
|
| -
|
| - def _GetPerformanceInfo(self):
|
| - class PerformanceInfo(ctypes.Structure):
|
| - """Struct for GetPerformanceInfo() call
|
| - http://msdn.microsoft.com/en-us/library/ms683210
|
| - """
|
| - _fields_ = [('size', ctypes.c_ulong),
|
| - ('CommitTotal', ctypes.c_size_t),
|
| - ('CommitLimit', ctypes.c_size_t),
|
| - ('CommitPeak', ctypes.c_size_t),
|
| - ('PhysicalTotal', ctypes.c_size_t),
|
| - ('PhysicalAvailable', ctypes.c_size_t),
|
| - ('SystemCache', ctypes.c_size_t),
|
| - ('KernelTotal', ctypes.c_size_t),
|
| - ('KernelPaged', ctypes.c_size_t),
|
| - ('KernelNonpaged', ctypes.c_size_t),
|
| - ('PageSize', ctypes.c_size_t),
|
| - ('HandleCount', ctypes.c_ulong),
|
| - ('ProcessCount', ctypes.c_ulong),
|
| - ('ThreadCount', ctypes.c_ulong)]
|
| -
|
| - def __init__(self):
|
| - self.size = ctypes.sizeof(self)
|
| - # pylint: disable=bad-super-call
|
| - super(PerformanceInfo, self).__init__()
|
| -
|
| - performance_info = PerformanceInfo()
|
| - ctypes.windll.psapi.GetPerformanceInfo(
|
| - ctypes.byref(performance_info), performance_info.size)
|
| - return performance_info
|
| -
|
| - def IsCurrentProcessElevated(self):
|
| - if self.GetOSVersionName() < os_version_module.VISTA:
|
| - # TOKEN_QUERY is not defined before Vista. All processes are elevated.
|
| - return True
|
| -
|
| - handle = win32process.GetCurrentProcess()
|
| - with contextlib.closing(
|
| - win32security.OpenProcessToken(handle, win32con.TOKEN_QUERY)) as token:
|
| - return bool(win32security.GetTokenInformation(
|
| - token, win32security.TokenElevation))
|
| -
|
| - def LaunchApplication(
|
| - self, application, parameters=None, elevate_privilege=False):
|
| - """Launch an application. Returns a PyHANDLE object."""
|
| -
|
| - parameters = ' '.join(parameters) if parameters else ''
|
| - if elevate_privilege and not self.IsCurrentProcessElevated():
|
| - # Use ShellExecuteEx() instead of subprocess.Popen()/CreateProcess() to
|
| - # elevate privileges. A new console will be created if the new process has
|
| - # different permissions than this process.
|
| - proc_info = shell.ShellExecuteEx(
|
| - fMask=shellcon.SEE_MASK_NOCLOSEPROCESS | shellcon.SEE_MASK_NO_CONSOLE,
|
| - lpVerb='runas' if elevate_privilege else '',
|
| - lpFile=application,
|
| - lpParameters=parameters,
|
| - nShow=win32con.SW_HIDE)
|
| - if proc_info['hInstApp'] <= 32:
|
| - raise Exception('Unable to launch %s' % application)
|
| - return proc_info['hProcess']
|
| - else:
|
| - handle, _, _, _ = win32process.CreateProcess(
|
| - None, application + ' ' + parameters, None, None, False,
|
| - win32process.CREATE_NO_WINDOW, None, None, win32process.STARTUPINFO())
|
| - return handle
|
| -
|
| - def CanMonitorPower(self):
|
| - return self._power_monitor.CanMonitorPower()
|
| -
|
| - def CanMeasurePerApplicationPower(self):
|
| - return self._power_monitor.CanMeasurePerApplicationPower()
|
| -
|
| - def StartMonitoringPower(self, browser):
|
| - self._power_monitor.StartMonitoringPower(browser)
|
| -
|
| - def StopMonitoringPower(self):
|
| - return self._power_monitor.StopMonitoringPower()
|
| -
|
| - def _StartMsrServerIfNeeded(self):
|
| - if self._msr_server_handle:
|
| - return
|
| -
|
| - _InstallWinRing0()
|
| - self._msr_server_port = util.GetUnreservedAvailableLocalPort()
|
| - # It might be flaky to get a port number without reserving it atomically,
|
| - # but if the server process chooses a port, we have no way of getting it.
|
| - # The stdout of the elevated process isn't accessible.
|
| - parameters = (
|
| - os.path.join(os.path.dirname(__file__), 'msr_server_win.py'),
|
| - str(self._msr_server_port),
|
| - )
|
| - self._msr_server_handle = self.LaunchApplication(
|
| - sys.executable, parameters, elevate_privilege=True)
|
| - # Wait for server to start.
|
| - try:
|
| - socket.create_connection(('127.0.0.1', self._msr_server_port), 5).close()
|
| - except socket.error:
|
| - self.CloseMsrServer()
|
| - atexit.register(TerminateProcess, self._msr_server_handle)
|
| -
|
| - def ReadMsr(self, msr_number, start=0, length=64):
|
| - self._StartMsrServerIfNeeded()
|
| - if not self._msr_server_handle:
|
| - raise OSError('Unable to start MSR server.')
|
| -
|
| - sock = socket.create_connection(('127.0.0.1', self._msr_server_port), 0.2)
|
| - try:
|
| - sock.sendall(struct.pack('I', msr_number))
|
| - response = sock.recv(8)
|
| - finally:
|
| - sock.close()
|
| - return struct.unpack('Q', response)[0] >> start & ((1 << length) - 1)
|
| -
|
| - def IsCooperativeShutdownSupported(self):
|
| - return True
|
| -
|
| - def CooperativelyShutdown(self, proc, app_name):
|
| - pid = proc.pid
|
| -
|
| - # http://timgolden.me.uk/python/win32_how_do_i/
|
| - # find-the-window-for-my-subprocess.html
|
| - #
|
| - # It seems that intermittently this code manages to find windows
|
| - # that don't belong to Chrome -- for example, the cmd.exe window
|
| - # running slave.bat on the tryservers. Try to be careful about
|
| - # finding only Chrome's windows. This works for both the browser
|
| - # and content_shell.
|
| - #
|
| - # It seems safest to send the WM_CLOSE messages after discovering
|
| - # all of the sub-process's windows.
|
| - def find_chrome_windows(hwnd, hwnds):
|
| - _, win_pid = win32process.GetWindowThreadProcessId(hwnd)
|
| - if (pid == win_pid and
|
| - win32gui.IsWindowVisible(hwnd) and
|
| - win32gui.IsWindowEnabled(hwnd) and
|
| - win32gui.GetClassName(hwnd).lower().startswith(app_name)):
|
| - hwnds.append(hwnd)
|
| - return True
|
| - hwnds = []
|
| - win32gui.EnumWindows(find_chrome_windows, hwnds)
|
| - if hwnds:
|
| - for hwnd in hwnds:
|
| - win32gui.SendMessage(hwnd, win32con.WM_CLOSE, 0, 0)
|
| - return True
|
| - else:
|
| - logging.info('Did not find any windows owned by target process')
|
| - return False
|
|
|