Commit e8f709b2 authored by dtu's avatar dtu Committed by Commit bot

[telemetry] Read MSRs from a separate privileged process.

This allows us to read MSRs on Windows Vista+.
Also move ReadMsr into PlatformBackend.

BUG=336558
TEST=tools/telemetry/run_tests msr

Committed: https://crrev.com/2156b0323c83d6f11e68344ecf7e7e0c833cfa14
Cr-Commit-Position: refs/heads/master@{#295634}

Review URL: https://codereview.chromium.org/578123002

Cr-Commit-Position: refs/heads/master@{#295737}
parent 3561b3ab
# 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.
"""A server that serves MSR values over TCP. Takes a port as its sole parameter.
The reference client for this server is msr_power_monitor.MsrPowerMonitor.
Must be run as Administrator. We use TCP instead of named pipes or another IPC
to avoid dealing with the pipe security mechanisms. We take the port as a
parameter instead of choosing one, because it's hard to communicate the port
number across integrity levels.
Requires WinRing0 to be installed in the Python directory.
msr_power_monitor.MsrPowerMonitor does this if needed.
"""
import argparse
import ctypes
import os
import SocketServer
import struct
import sys
WINRING0_STATUS_MESSAGES = (
'No error',
'Unsupported platform',
'Driver not loaded. You may need to run as Administrator',
'Driver not found',
'Driver unloaded by other process',
'Driver not loaded because of executing on Network Drive',
'Unknown error',
)
# The DLL initialization is global, so put it in a global variable.
_winring0 = None
class WinRing0Error(OSError):
pass
def _WinRing0Path():
python_is_64_bit = sys.maxsize > 2 ** 32
dll_file_name = 'WinRing0x64.dll' if python_is_64_bit else 'WinRing0.dll'
return os.path.join(os.path.dirname(sys.executable), dll_file_name)
def _Initialize():
global _winring0
if not _winring0:
winring0 = ctypes.WinDLL(_WinRing0Path())
if not winring0.InitializeOls():
winring0_status = winring0.GetDllStatus()
raise WinRing0Error(winring0_status,
'Unable to initialize WinRing0: %s' %
WINRING0_STATUS_MESSAGES[winring0_status])
_winring0 = winring0
def _Deinitialize():
global _winring0
if _winring0:
_winring0.DeinitializeOls()
_winring0 = None
def _ReadMsr(msr_number):
low = ctypes.c_uint()
high = ctypes.c_uint()
_winring0.Rdmsr(ctypes.c_uint(msr_number),
ctypes.byref(low), ctypes.byref(high))
return high.value << 32 | low.value
class MsrRequestHandler(SocketServer.StreamRequestHandler):
def handle(self):
msr_number = struct.unpack('I', self.rfile.read(4))[0]
self.wfile.write(struct.pack('Q', _ReadMsr(msr_number)))
def main():
parser = argparse.ArgumentParser()
parser.add_argument('port', type=int)
args = parser.parse_args()
_Initialize()
try:
SocketServer.TCPServer.allow_reuse_address = True
server_address = ('127.0.0.1', args.port)
server = SocketServer.TCPServer(server_address, MsrRequestHandler)
server.serve_forever()
finally:
_Deinitialize()
if __name__ == '__main__':
main()
...@@ -207,3 +207,11 @@ class PlatformBackend(object): ...@@ -207,3 +207,11 @@ class PlatformBackend(object):
def StopMonitoringPower(self): def StopMonitoringPower(self):
raise NotImplementedError() raise NotImplementedError()
def ReadMsr(self, msr_number):
"""Read a CPU model-specific register (MSR).
Which MSRs are available depends on the CPU model.
On systems with multiple CPUs, this function may run on any CPU.
"""
raise NotImplementedError()
...@@ -2,19 +2,12 @@ ...@@ -2,19 +2,12 @@
# Use of this source code is governed by a BSD-style license that can be # Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file. # found in the LICENSE file.
import atexit
import ctypes
import logging import logging
import os
import platform import platform
import re import re
import sys
import zipfile
from telemetry import decorators from telemetry import decorators
from telemetry.core.platform import power_monitor from telemetry.core.platform import power_monitor
from telemetry.util import cloud_storage
from telemetry.util import path
MSR_RAPL_POWER_UNIT = 0x606 MSR_RAPL_POWER_UNIT = 0x606
...@@ -26,100 +19,6 @@ IA32_PACKAGE_THERM_STATUS = 0x1b1 ...@@ -26,100 +19,6 @@ IA32_PACKAGE_THERM_STATUS = 0x1b1
IA32_TEMPERATURE_TARGET = 0x1a2 IA32_TEMPERATURE_TARGET = 0x1a2
WINRING0_STATUS_MESSAGES = (
'No error',
'Unsupported platform',
'Driver not loaded. You may need to run as Administrator',
'Driver not found',
'Driver unloaded by other process',
'Driver not loaded because of executing on Network Drive',
'Unknown error',
)
# The DLL initialization is global, so put it in a global variable.
_winring0 = None
class WinRing0Error(OSError):
pass
@decorators.Cache
def WinRing0Path():
python_is_64_bit = sys.maxsize > 2 ** 32
win_binary_dir = os.path.join(path.GetTelemetryDir(), 'bin', 'win')
dll_file_name = 'WinRing0x64.dll' if python_is_64_bit else 'WinRing0.dll'
dll_path = os.path.join(win_binary_dir, dll_file_name)
os_is_64_bit = 'PROGRAMFILES(X86)' in os.environ
executable_dir = os.path.dirname(sys.executable)
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)):
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:
# Install DLL.
if not os.path.exists(dll_path):
zip_file.extract(dll_file_name, win_binary_dir)
# Install kernel driver.
if not os.path.exists(driver_path):
zip_file.extract(driver_file_name, executable_dir)
finally:
os.remove(zip_path)
return dll_path
def _Initialize():
global _winring0
if not _winring0:
winring0 = ctypes.WinDLL(WinRing0Path())
if not winring0.InitializeOls():
winring0_status = winring0.GetDllStatus()
raise WinRing0Error(winring0_status,
'Unable to initialize WinRing0: %s' %
WINRING0_STATUS_MESSAGES[winring0_status])
_winring0 = winring0
atexit.register(_Deinitialize)
def _Deinitialize():
global _winring0
if _winring0:
_winring0.DeinitializeOls()
_winring0 = None
def _ReadMsr(msr_number):
low = ctypes.c_uint()
high = ctypes.c_uint()
_winring0.Rdmsr(ctypes.c_uint(msr_number),
ctypes.byref(low), ctypes.byref(high))
return high.value << 32 | low.value
@decorators.Cache
def _EnergyMultiplier():
return 0.5 ** ((_ReadMsr(MSR_RAPL_POWER_UNIT) >> 8) & 0x1f)
def _PackageEnergyJoules():
return _ReadMsr(MSR_PKG_ENERGY_STATUS) * _EnergyMultiplier()
def _TemperatureCelsius():
tcc_activation_temp = _ReadMsr(IA32_TEMPERATURE_TARGET) >> 16 & 0x7f
if tcc_activation_temp <= 0:
tcc_activation_temp = 105
package_temp_headroom = _ReadMsr(IA32_PACKAGE_THERM_STATUS) >> 16 & 0x7f
return tcc_activation_temp - package_temp_headroom
def _JoulesToMilliwattHours(value_joules): def _JoulesToMilliwattHours(value_joules):
return value_joules * 1000 / 3600. return value_joules * 1000 / 3600.
...@@ -149,16 +48,15 @@ class MsrPowerMonitor(power_monitor.PowerMonitor): ...@@ -149,16 +48,15 @@ class MsrPowerMonitor(power_monitor.PowerMonitor):
return False return False
try: try:
_Initialize() if self._PackageEnergyJoules() <= 0:
except OSError: logging.info('Cannot monitor power: no energy readings.')
return False return False
if _PackageEnergyJoules() <= 0: if self._TemperatureCelsius() <= 0:
logging.info('Cannot monitor power: no energy readings.') logging.info('Cannot monitor power: no temperature readings.')
return False return False
except OSError as e:
if _TemperatureCelsius() <= 0: logging.info('Cannot monitor power: %s' % e)
logging.info('Cannot monitor power: no temperature readings.')
return False return False
return True return True
...@@ -166,16 +64,15 @@ class MsrPowerMonitor(power_monitor.PowerMonitor): ...@@ -166,16 +64,15 @@ class MsrPowerMonitor(power_monitor.PowerMonitor):
def StartMonitoringPower(self, browser): def StartMonitoringPower(self, browser):
assert self._start_energy_j is None and self._start_temp_c is None, ( assert self._start_energy_j is None and self._start_temp_c is None, (
'Called StartMonitoringPower() twice.') 'Called StartMonitoringPower() twice.')
_Initialize() self._start_energy_j = self._PackageEnergyJoules()
self._start_energy_j = _PackageEnergyJoules() self._start_temp_c = self._TemperatureCelsius()
self._start_temp_c = _TemperatureCelsius()
def StopMonitoringPower(self): def StopMonitoringPower(self):
assert not(self._start_energy_j is None or self._start_temp_c is None), ( assert not(self._start_energy_j is None or self._start_temp_c is None), (
'Called StopMonitoringPower() before StartMonitoringPower().') 'Called StopMonitoringPower() before StartMonitoringPower().')
energy_consumption_j = _PackageEnergyJoules() - self._start_energy_j energy_consumption_j = self._PackageEnergyJoules() - self._start_energy_j
average_temp_c = (_TemperatureCelsius() + self._start_temp_c) / 2. average_temp_c = (self._TemperatureCelsius() + self._start_temp_c) / 2.
assert energy_consumption_j >= 0, ('Negative energy consumption. (Starting ' assert energy_consumption_j >= 0, ('Negative energy consumption. (Starting '
'energy was %s.)' % self._start_energy_j) 'energy was %s.)' % self._start_energy_j)
...@@ -191,3 +88,20 @@ class MsrPowerMonitor(power_monitor.PowerMonitor): ...@@ -191,3 +88,20 @@ class MsrPowerMonitor(power_monitor.PowerMonitor):
}, },
}, },
} }
@decorators.Cache
def _EnergyMultiplier(self):
return 0.5 ** ((self._backend.ReadMsr(MSR_RAPL_POWER_UNIT) >> 8) & 0x1f)
def _PackageEnergyJoules(self):
return (self._backend.ReadMsr(MSR_PKG_ENERGY_STATUS) *
self._EnergyMultiplier())
def _TemperatureCelsius(self):
tcc_activation_temp = (
self._backend.ReadMsr(IA32_TEMPERATURE_TARGET) >> 16 & 0x7f)
if tcc_activation_temp <= 0:
tcc_activation_temp = 105
package_temp_headroom = (
self._backend.ReadMsr(IA32_PACKAGE_THERM_STATUS) >> 16 & 0x7f)
return tcc_activation_temp - package_temp_headroom
...@@ -3,6 +3,7 @@ ...@@ -3,6 +3,7 @@
# found in the LICENSE file. # found in the LICENSE file.
import logging import logging
import time
import unittest import unittest
from telemetry import decorators from telemetry import decorators
...@@ -12,12 +13,7 @@ from telemetry.core.platform.power_monitor import msr_power_monitor ...@@ -12,12 +13,7 @@ from telemetry.core.platform.power_monitor import msr_power_monitor
class MsrPowerMonitorTest(unittest.TestCase): class MsrPowerMonitorTest(unittest.TestCase):
@decorators.Enabled('win') @decorators.Enabled('win')
def testFindOrInstallWinRing0(self): def testMsrRuns(self):
self.assertTrue(msr_power_monitor.WinRing0Path())
@decorators.Enabled('win')
def testMsrRunsWithoutErrors(self):
# Very basic test, doesn't validate any output data.
platform_backend = win_platform_backend.WinPlatformBackend() platform_backend = win_platform_backend.WinPlatformBackend()
power_monitor = msr_power_monitor.MsrPowerMonitor(platform_backend) power_monitor = msr_power_monitor.MsrPowerMonitor(platform_backend)
if not power_monitor.CanMonitorPower(): if not power_monitor.CanMonitorPower():
...@@ -25,7 +21,9 @@ class MsrPowerMonitorTest(unittest.TestCase): ...@@ -25,7 +21,9 @@ class MsrPowerMonitorTest(unittest.TestCase):
return return
power_monitor.StartMonitoringPower(None) power_monitor.StartMonitoringPower(None)
time.sleep(0.01)
statistics = power_monitor.StopMonitoringPower() statistics = power_monitor.StopMonitoringPower()
self.assertEqual(statistics['identifier'], 'msr') self.assertEqual(statistics['identifier'], 'msr')
self.assertIn('energy_consumption_mwh', statistics) self.assertIn('energy_consumption_mwh', statistics)
self.assertGreater(statistics['energy_consumption_mwh'], 0)
...@@ -2,19 +2,28 @@ ...@@ -2,19 +2,28 @@
# Use of this source code is governed by a BSD-style license that can be # Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file. # found in the LICENSE file.
import atexit
import collections import collections
import contextlib import contextlib
import ctypes import ctypes
import os
import platform import platform
import re import re
import socket
import struct
import subprocess import subprocess
import sys
import time import time
import zipfile
from telemetry import decorators from telemetry import decorators
from telemetry.core import exceptions from telemetry.core import exceptions
from telemetry.core import util
from telemetry.core.platform import desktop_platform_backend from telemetry.core.platform import desktop_platform_backend
from telemetry.core.platform import platform_backend from telemetry.core.platform import platform_backend
from telemetry.core.platform.power_monitor import msr_power_monitor from telemetry.core.platform.power_monitor import msr_power_monitor
from telemetry.util import cloud_storage
from telemetry.util import path
try: try:
import pywintypes # pylint: disable=F0401 import pywintypes # pylint: disable=F0401
...@@ -34,20 +43,64 @@ except ImportError: ...@@ -34,20 +43,64 @@ except ImportError:
win32security = None win32security = None
def _InstallWinRing0():
"""WinRing0 is used for reading MSRs."""
executable_dir = os.path.dirname(sys.executable)
def IsCurrentProcessElevated(): python_is_64_bit = sys.maxsize > 2 ** 32
handle = win32process.GetCurrentProcess() dll_file_name = 'WinRing0x64.dll' if python_is_64_bit else 'WinRing0.dll'
with contextlib.closing( dll_path = os.path.join(executable_dir, dll_file_name)
win32security.OpenProcessToken(handle, win32con.TOKEN_QUERY)) as token:
return bool( os_is_64_bit = 'PROGRAMFILES(X86)' in os.environ
win32security.GetTokenInformation(token, win32security.TokenElevation)) 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')
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:
# Install DLL.
if not os.path.exists(dll_path):
zip_file.extract(dll_file_name, executable_dir)
# Install kernel driver.
if not os.path.exists(driver_path):
zip_file.extract(driver_file_name, executable_dir)
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): class WinPlatformBackend(desktop_platform_backend.DesktopPlatformBackend):
def __init__(self): def __init__(self):
super(WinPlatformBackend, self).__init__() super(WinPlatformBackend, self).__init__()
self._msr_server_handle = None
self._msr_server_port = None
self._power_monitor = msr_power_monitor.MsrPowerMonitor(self) self._power_monitor = msr_power_monitor.MsrPowerMonitor(self)
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
# pylint: disable=W0613 # pylint: disable=W0613
def StartRawDisplayFrameRateMeasurement(self): def StartRawDisplayFrameRateMeasurement(self):
raise NotImplementedError() raise NotImplementedError()
...@@ -224,12 +277,23 @@ class WinPlatformBackend(desktop_platform_backend.DesktopPlatformBackend): ...@@ -224,12 +277,23 @@ class WinPlatformBackend(desktop_platform_backend.DesktopPlatformBackend):
ctypes.byref(performance_info), performance_info.size) ctypes.byref(performance_info), performance_info.size)
return performance_info return performance_info
def IsCurrentProcessElevated(self):
if self.GetOSVersionName() < platform_backend.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( def LaunchApplication(
self, application, parameters=None, elevate_privilege=False): self, application, parameters=None, elevate_privilege=False):
"""Launch an application. Returns a PyHANDLE object.""" """Launch an application. Returns a PyHANDLE object."""
parameters = ' '.join(parameters) if parameters else '' parameters = ' '.join(parameters) if parameters else ''
if elevate_privilege and not IsCurrentProcessElevated(): if elevate_privilege and not self.IsCurrentProcessElevated():
# Use ShellExecuteEx() instead of subprocess.Popen()/CreateProcess() to # Use ShellExecuteEx() instead of subprocess.Popen()/CreateProcess() to
# elevate privileges. A new console will be created if the new process has # elevate privileges. A new console will be created if the new process has
# different permissions than this process. # different permissions than this process.
...@@ -259,3 +323,38 @@ class WinPlatformBackend(desktop_platform_backend.DesktopPlatformBackend): ...@@ -259,3 +323,38 @@ class WinPlatformBackend(desktop_platform_backend.DesktopPlatformBackend):
def StopMonitoringPower(self): def StopMonitoringPower(self):
return self._power_monitor.StopMonitoringPower() 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):
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.1)
try:
sock.sendall(struct.pack('I', msr_number))
response = sock.recv(8)
finally:
sock.close()
return struct.unpack('Q', response)[0]
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment