Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(4928)

Unified Diff: appengine/swarming/swarming_bot/api/platforms/osx.py

Issue 2987023002: swarming: report CPU temperature on OSX as state. (Closed)
Patch Set: Created 3 years, 5 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
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

Powered by Google App Engine
This is Rietveld 408576698