Index: appengine/swarming/swarming_bot/api/platforms/osx.py |
diff --git a/appengine/swarming/swarming_bot/api/platforms/osx.py b/appengine/swarming/swarming_bot/api/platforms/osx.py |
index 27b51cbb107c359a7a6fc3b6ea7424e475a4aa83..1700e6b588547c931966ccbda6430053cd591d1e 100644 |
--- a/appengine/swarming/swarming_bot/api/platforms/osx.py |
+++ b/appengine/swarming/swarming_bot/api/platforms/osx.py |
@@ -9,12 +9,12 @@ import ctypes |
import logging |
import os |
import platform |
+import plistlib |
import re |
+import struct |
import subprocess |
import time |
-import plistlib |
- |
from utils import tools |
import common |
@@ -23,6 +23,156 @@ import gpu |
## Private stuff. |
+iokit = ctypes.CDLL( |
+ '/System/Library/Frameworks/IOKit.framework/Versions/A/IOKit') |
+# https://developer.apple.com/documentation/iokit/1514274-ioconnectcallstructmethod |
+iokit.IOConnectCallStructMethod.argtypes = [ |
+ ctypes.c_uint, ctypes.c_uint, ctypes.c_void_p, ctypes.c_ulonglong, |
+ ctypes.c_void_p, ctypes.POINTER(ctypes.c_ulonglong), |
+] |
+iokit.IOConnectCallStructMethod.restype = ctypes.c_int |
+ |
+# https://developer.apple.com/documentation/iokit/1514515-ioserviceopen |
+iokit.IOServiceOpen.argtypes = [ |
+ ctypes.c_uint, ctypes.c_uint, ctypes.c_uint, ctypes.POINTER(ctypes.c_uint), |
+] |
+iokit.IOServiceOpen.restype = ctypes.c_int |
+ |
+# https://developer.apple.com/documentation/iokit/1514687-ioservicematching |
+iokit.IOServiceMatching.restype = ctypes.c_void_p |
+ |
+# https://developer.apple.com/documentation/iokit/1514494-ioservicegetmatchingservices |
+iokit.IOServiceGetMatchingServices.argtypes = [ |
+ ctypes.c_uint, ctypes.c_void_p, ctypes.POINTER(ctypes.c_uint), |
+] |
+iokit.IOServiceGetMatchingServices.restype = ctypes.c_int |
+ |
+# https://developer.apple.com/documentation/iokit/1514741-ioiteratornext |
+iokit.IOIteratorNext.argtypes = [ctypes.c_uint] |
+iokit.IOIteratorNext.restype = ctypes.c_uint |
+ |
+# https://developer.apple.com/documentation/iokit/1514627-ioobjectrelease |
+iokit.IOObjectRelease.argtypes = [ctypes.c_uint] |
+iokit.IOObjectRelease.restype = ctypes.c_int |
+ |
+ |
+libkern = ctypes.CDLL('/usr/lib/system/libsystem_kernel.dylib') |
+libkern.mach_task_self.restype = ctypes.c_uint |
+ |
+ |
+class SMCKeyData_vers_t(ctypes.Structure): |
Ken Russell (switch to Gerrit)
2017/07/28 18:22:01
How about a pointer to https://github.com/lavoiesl
M-A Ruel
2017/07/28 20:41:57
It is not the original source either. I found sour
|
+ _fields_ = [ |
+ ('major', ctypes.c_char), |
+ ('minor', ctypes.c_char), |
+ ('build', ctypes.c_char), |
+ ('reserved', ctypes.c_char), |
+ ('release', ctypes.c_ushort), |
+ ] |
+ |
+ |
+class SMCKeyData_pLimitData_t(ctypes.Structure): |
+ _fields_ = [ |
+ ('version', ctypes.c_ushort), |
+ ('length', ctypes.c_ushort), |
+ ('cpuPLimit', ctypes.c_uint), |
+ ('gpuPLimit', ctypes.c_uint), |
+ ('memPLimit', ctypes.c_uint), |
+ ] |
+ |
+ |
+class SMCKeyData_keyInfo_t(ctypes.Structure): |
+ _fields_ = [ |
+ ('dataSize', ctypes.c_uint), |
+ ('dataType', ctypes.c_uint), |
+ ('dataAttributes', ctypes.c_char), |
+ ] |
+ |
+ |
+class SMCKeyData_t(ctypes.Structure): |
+ _fields_ = [ |
+ ('key', ctypes.c_uint), |
+ ('vers', SMCKeyData_vers_t), |
+ ('pLimitData', SMCKeyData_pLimitData_t), |
+ ('keyInfo', SMCKeyData_keyInfo_t), |
+ ('result', ctypes.c_char), |
+ ('status', ctypes.c_char), |
+ ('data8', ctypes.c_char), |
+ ('data32', ctypes.c_uint), |
+ ('bytes', ctypes.c_ubyte * 32), |
+ ] |
+ |
+ |
+class SMCVal_t(ctypes.Structure): |
+ _fields_ = [ |
+ ('key', (ctypes.c_ubyte * 5)), |
+ ('dataSize', ctypes.c_uint), |
+ ('dataType', (ctypes.c_ubyte * 5)), |
+ ('bytes', ctypes.c_ubyte * 32), |
+ ] |
+ |
+ |
+@tools.cached |
+def SMC_open(): |
+ itr = ctypes.c_uint() |
+ result = iokit.IOServiceGetMatchingServices( |
+ 0, iokit.IOServiceMatching('AppleSMC'), ctypes.byref(itr)) |
+ if result: |
+ logging.error('failed to get AppleSMC (%d)', result) |
+ return None |
+ dev = iokit.IOIteratorNext(itr) |
+ iokit.IOObjectRelease(itr) |
+ if not dev: |
+ logging.error('no SMC found') |
+ return None |
+ conn = ctypes.c_uint() |
+ if iokit.IOServiceOpen(dev, libkern.mach_task_self(), 0, ctypes.byref(conn)): |
Vadim Sh.
2017/07/28 18:15:56
I assume all such connections are automatically re
Vadim Sh.
2017/07/28 18:25:32
The code kbr@ linked to releases the device after
M-A Ruel
2017/07/28 20:41:57
Yes, I've did not implement code to close the hand
|
+ logging.error('failed to open AppleSMC (%d)', result) |
+ return None |
+ return conn |
+ |
+ |
+def SMC_call(conn, index, indata, outdata): |
+ return iokit.IOConnectCallStructMethod( |
+ conn, |
+ ctypes.c_uint(index), |
+ ctypes.cast(ctypes.pointer(indata), ctypes.c_void_p), |
+ ctypes.c_ulonglong(ctypes.sizeof(SMCKeyData_t)), |
+ ctypes.cast(ctypes.pointer(outdata), ctypes.c_void_p), |
+ ctypes.pointer(ctypes.c_ulonglong(ctypes.sizeof(SMCKeyData_t)))) |
+ |
+ |
+def SMC_read_key(conn, key): |
+ KERNEL_INDEX_SMC = 2 |
+ |
+ # Call with SMC_CMD_READ_KEYINFO. |
+ indata = SMCKeyData_t(key=struct.unpack('>i', key)[0], data8='\x09') |
+ outdata = SMCKeyData_t() |
+ if SMC_call(conn, KERNEL_INDEX_SMC, indata, outdata): |
+ logging.error('SMC call to get key info failed') |
+ return None |
+ |
+ # Call with SMC_CMD_READ_BYTES. |
Vadim Sh.
2017/07/28 18:15:56
this looks horrifying :(
M-A Ruel
2017/07/28 20:41:57
Na, I've done much worse recently. 💣
|
+ val = SMCVal_t(dataSize=outdata.keyInfo.dataSize) |
+ for i, x in enumerate(struct.pack('>i', outdata.keyInfo.dataType)): |
+ val.dataType[i] = ord(x) |
+ # pylint: disable=attribute-defined-outside-init |
+ indata.data8 = '\x05' |
+ indata.keyInfo.dataSize = val.dataSize |
+ if SMC_call(conn, KERNEL_INDEX_SMC, indata, outdata): |
+ logging.error('SMC call to get data info failed') |
+ return None |
+ val.bytes = outdata.bytes |
+ return val |
+ |
+ |
+def SMC_get_temperature(conn, key): |
Ken Russell (switch to Gerrit)
2017/07/28 18:22:01
Comment that this is in Celsius.
M-A Ruel
2017/07/28 20:41:57
Celsius or Death. Well it could have been Kelvin.
|
+ val = SMC_read_key(conn, key) |
+ if not val: |
+ return None |
+ if val.dataSize != 2 or 'sp78\0' != ''.join(map(chr, val.dataType)): |
+ return None |
+ return (val.bytes[0] * 256 + val.bytes[1]) / 256. |
+ |
@tools.cached |
def _get_system_profiler(data_type): |
@@ -172,7 +322,8 @@ def get_hardware_model_string(): |
A string like Macmini5,3 or MacPro6,1. |
""" |
try: |
- return subprocess.check_output(['sysctl', '-n', 'hw.model']).rstrip() |
+ return unicode( |
+ subprocess.check_output(['sysctl', '-n', 'hw.model']).rstrip()) |
except (OSError, subprocess.CalledProcessError): |
return None |
@@ -260,6 +411,14 @@ def get_cpuinfo(): |
} |
+def get_cpu_temperature(): |
+ """Returns the CPU temperature via AppleSMC.""" |
+ conn = SMC_open() |
+ if not conn: |
+ return None |
+ return SMC_get_temperature(conn, 'TC0P') |
+ |
+ |
@tools.cached |
def get_monitor_hidpi(): |
"""Returns True if the monitor is hidpi. |
@@ -284,12 +443,13 @@ def get_monitor_hidpi(): |
is_hidpi(card['spdisplays_ndrvs']) |
for card in _get_system_profiler('SPDisplaysDataType') |
if 'spdisplays_ndrvs' in card) |
- return str(int(hidpi)) |
+ return unicode(int(hidpi)) |
def get_xcode_versions(): |
"""Returns all Xcode versions installed.""" |
- return sorted(xcode['version'] for xcode in get_xcode_state().itervalues()) |
+ return sorted( |
+ unicode(xcode['version']) for xcode in get_xcode_state().itervalues()) |
@tools.cached |