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

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

Issue 2987023002: swarming: report CPU temperature on OSX as state. (Closed)
Patch Set: Generalize 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
« no previous file with comments | « appengine/swarming/swarming_bot/api/os_utilities.py ('k') | no next file » | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
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..f950d83fad37aeed83ab9df37436c89a26881fe3 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
@@ -24,6 +24,280 @@ 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 _SMC_KeyDataVersion(ctypes.Structure):
+ _fields_ = [
+ ('major', ctypes.c_uint8),
+ ('minor', ctypes.c_uint8),
+ ('build', ctypes.c_uint8),
+ ('reserved', ctypes.c_uint8),
+ ('release', ctypes.c_uint16),
+ ]
+
+
+class _SMC_KeyDataLimits(ctypes.Structure):
+ _fields_ = [
+ ('version', ctypes.c_uint16),
+ ('length', ctypes.c_uint16),
+ ('cpu', ctypes.c_uint32),
+ ('gpu', ctypes.c_uint32),
+ ('mem', ctypes.c_uint32),
+ ]
+
+
+class _SMC_KeyDataInfo(ctypes.Structure):
+ _fields_ = [
+ ('size', ctypes.c_uint32),
+ ('type', ctypes.c_uint32),
+ ('attributes', ctypes.c_uint8),
+ ]
+
+
+class _SMC_KeyData(ctypes.Structure):
+ _fields_ = [
+ ('key', ctypes.c_uint32),
+ ('version', _SMC_KeyDataVersion),
+ ('pLimitData', _SMC_KeyDataLimits),
+ ('keyInfo', _SMC_KeyDataInfo),
+ ('result', ctypes.c_uint8),
+ ('status', ctypes.c_uint8),
+ ('data8', ctypes.c_uint8),
+ ('data32', ctypes.c_uint32),
+ ('bytes', ctypes.c_ubyte * 32),
+ ]
+
+
+class _SMC_Value(ctypes.Structure):
+ _fields_ = [
+ ('key', (ctypes.c_ubyte * 5)),
+ ('size', ctypes.c_uint32),
+ ('type', (ctypes.c_ubyte * 5)),
+ ('bytes', ctypes.c_ubyte * 32),
+ ]
+
+
+# http://bxr.su/OpenBSD/sys/dev/isa/asmc.c is a great list of sensors.
+#
+# The following other sensor were found on a MBP 2012 via brute forcing but
+# their signification is unknown:
+# TC0E, TC0F, TH0A, TH0B, TH0V, TP0P, TS0D, TS0P
+_sensor_names = {
Vadim Sh. 2017/07/28 20:54:55 do we really need all that stuff?...
M-A Ruel 2017/07/28 20:58:46 No, I want to make it live then trim based on what
M-A Ruel 2017/07/28 21:03:43 Actually, VM *do* report temperatures. That's so a
+ 'TA0P': u'ambient', # 'hdd bay 1',
+ 'TA0S': u'pci slot 1 pos 1',
+ 'TA1S': u'pci slot 1 pos 2',
+ 'TA3S': u'pci slot 2 pos 2',
+ 'TB0T': u'enclosure bottom',
+ 'TB2T': u'enclosure bottom 3',
+ 'TC0D': u'cpu0 die core',
+ 'TC0P': u'cpu0 proximity',
+ 'TC1D': u'cpu1',
+ 'TCAH': u'cpu0',
+ 'TCDH': u'cpu3',
+ 'TG0D': u'gpu0 diode',
+ 'TG0P': u'gpu0 proximity',
+ 'TG1H': u'gpu heatsink 2',
+ 'TH0P': u'hdd bay 1',
+ 'TH2P': u'hdd bay 3',
+ 'TL0P': u'lcd proximity',
+ 'TM0P': u'mem bank a1',
+ 'TM1P': u'mem bank a2',
+ 'TM2P': u'mem bank a3',
+ 'TM3P': u'mem bank a4',
+ 'TM4P': u'mem bank a5',
+ 'TM5P': u'mem bank a6',
+ 'TM6P': u'mem bank a7',
+ 'TM7P': u'mem bank a8',
+ 'TM8P': u'mem bank b1',
+ 'TM9P': u'mem bank b2',
+ 'TMA1': u'ram a1',
+ 'TMA3': u'ram a3',
+ 'TMAP': u'mem bank b3',
+ 'TMB1': u'ram b1',
+ 'TMB3': u'ram b3',
+ 'TMBP': u'mem bank b4',
+ 'TMCP': u'mem bank b5',
+ 'TMDP': u'mem bank b6',
+ 'TMEP': u'mem bank b7',
+ 'TMFP': u'mem bank b8',
+ 'TN0D': u'northbridge die core',
+ 'TN0P': u'northbridge proximity',
+ 'TO0P': u'optical drive',
+ 'TW0P': u'wireless airport card',
+ 'Th0H': u'main heatsink a',
+ 'Th2H': u'main heatsink c',
+ 'Tm0P': u'memory controller',
+ 'Tp0C': u'power supply 1',
+ 'Tp1C': u'power supply 2',
+ 'Tp2P': u'power supply 3',
+ 'Tp4P': u'power supply 5',
+ 'TA1P': u'ambient 2',
+ 'TA2S': u'pci slot 2 pos 1',
+ 'TB1T': u'enclosure bottom 2',
+ 'TB3T': u'enclosure bottom 4',
+ 'TC0H': u'cpu0 heatsink',
+ 'TC2D': u'cpu2',
+ 'TC3D': u'cpu3',
+ 'TCBH': u'cpu1',
+ 'TCCH': u'cpu2',
+ 'TG0H': u'gpu0 heatsink',
+ 'TH1P': u'hdd bay 2',
+ 'TH3P': u'hdd bay 4',
+ 'TM0S': u'mem module a1',
+ 'TM1S': u'mem module a2',
+ 'TM2S': u'mem module a3',
+ 'TM3S': u'mem module a4',
+ 'TM4S': u'mem module a5',
+ 'TM5S': u'mem module a6',
+ 'TM6S': u'mem module a7',
+ 'TM7S': u'mem module a8',
+ 'TM8S': u'mem module b1',
+ 'TM9S': u'mem module b2',
+ 'TMA2': u'ram a2',
+ 'TMA4': u'ram a4',
+ 'TMAS': u'mem module b3',
+ 'TMB2': u'ram b2',
+ 'TMB4': u'ram b4',
+ 'TMBS': u'mem module b4',
+ 'TMCS': u'mem module b5',
+ 'TMDS': u'mem module b6',
+ 'TMES': u'mem module b7',
+ 'TMFS': u'mem module b8',
+ 'TN0H': u'northbridge',
+ 'TN1P': u'northbridge 2',
+ 'TS0C': u'expansion slots',
+ 'Th1H': u'main heatsink b',
+ 'Tp0P': u'power supply 1',
+ 'Tp1P': u'power supply 2',
+ 'Tp3P': u'power supply 4',
+ 'Tp5P': u'power supply 6',
+}
+
+# _sensor_found_cache is set on the first call to _SMC_get_values.
+_sensor_found_cache = None
+
+
+@tools.cached
+def _SMC_open():
+ """Opens the default SMC driver and returns the first device.
+
+ It leaves the device handle open for the duration of the process.
+ """
+ # There should be only one.
+ 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)):
+ logging.error('failed to open AppleSMC (%d)', result)
+ return None
+ return conn
+
+
+def _SMC_call(conn, index, indata, outdata):
+ """Executes a call to the SMC subsystem."""
+ return iokit.IOConnectCallStructMethod(
+ conn,
+ ctypes.c_uint(index),
+ ctypes.cast(ctypes.pointer(indata), ctypes.c_void_p),
+ ctypes.c_ulonglong(ctypes.sizeof(_SMC_KeyData)),
+ ctypes.cast(ctypes.pointer(outdata), ctypes.c_void_p),
+ ctypes.pointer(ctypes.c_ulonglong(ctypes.sizeof(_SMC_KeyData))))
+
+
+def _SMC_read_key(conn, key):
+ """Retrieves an unprocessed key value."""
+ KERNEL_INDEX_SMC = 2
+
+ # Call with SMC_CMD_READ_KEYINFO.
+ # TODO(maruel): Keep cache of result size.
+ indata = _SMC_KeyData(key=struct.unpack('>i', key)[0], data8=9)
+ outdata = _SMC_KeyData()
+ 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.
+ val = _SMC_Value(size=outdata.keyInfo.size)
+ for i, x in enumerate(struct.pack('>i', outdata.keyInfo.type)):
+ val.type[i] = ord(x)
+ # pylint: disable=attribute-defined-outside-init
+ indata.data8 = 5
+ indata.keyInfo.size = val.size
+ 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_value(conn, key):
+ """Returns a processed measurement via AppleSMC for a specified key.
+
+ Returns None on failure.
+ """
+ val = _SMC_read_key(conn, key)
+ if not val or not val.size:
+ return None
+ t = ''.join(map(chr, val.type))
+ if t == 'sp78\0' and val.size == 2:
+ # Format is first byte signed int8, second byte uint8 fractional.
+ return float(ctypes.c_int8(val.bytes[0]).value) + (val.bytes[1] / 256.)
+ if t == 'fpe2\0' and val.size == 2:
+ # Format is unsigned 14 bits big endian, 2 bits fractional.
+ return (
+ float((val.bytes[0] << 6) + (val.bytes[1] >> 2)) +
+ (val.bytes[1] & 3) / 4.)
+ # TODO(maruel): Handler other formats like 64 bits long. This is used for fan
+ # speed.
+ logging.error(
+ '_SMC_get_value(%s) got unknown format: %s of %d bytes', key, t, val.size)
+ return None
+
+
@tools.cached
def _get_system_profiler(data_type):
"""Returns an XML about the system display properties."""
@@ -172,7 +446,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 +535,33 @@ def get_cpuinfo():
}
+def get_temperatures():
+ """Returns the temperatures in Celsius."""
+ global _sensor_found_cache
+ conn = _SMC_open()
+ if not conn:
+ return None
+
+ out = {}
+ if _sensor_found_cache is None:
+ _sensor_found_cache = set()
+ # Populate the cache of the sensors found on this system, so that the next
+ # call can only get the actual sensors.
+ # Note: It is relatively fast to brute force all the possible names.
+ for key, name in _sensor_names.iteritems():
+ value = _SMC_get_value(conn, key)
+ if value is not None:
+ _sensor_found_cache.add(key)
+ out[name] = value
+ return out
+
+ for key in _sensor_found_cache:
Vadim Sh. 2017/07/28 20:54:55 nit: remove extra space after 'in'
M-A Ruel 2017/07/28 20:58:46 Done.
+ value = _SMC_get_value(conn, key)
+ if value is not None:
+ out[_sensor_names[key]] = value
+ return out
+
+
@tools.cached
def get_monitor_hidpi():
"""Returns True if the monitor is hidpi.
@@ -284,12 +586,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
« no previous file with comments | « appengine/swarming/swarming_bot/api/os_utilities.py ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698