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

Side by Side 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, 4 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 unified diff | Download patch
OLDNEW
1 # Copyright 2015 The LUCI Authors. All rights reserved. 1 # Copyright 2015 The LUCI Authors. All rights reserved.
2 # Use of this source code is governed under the Apache License, Version 2.0 2 # Use of this source code is governed under the Apache License, Version 2.0
3 # that can be found in the LICENSE file. 3 # that can be found in the LICENSE file.
4 4
5 """OSX specific utility functions.""" 5 """OSX specific utility functions."""
6 6
7 import cgi 7 import cgi
8 import ctypes 8 import ctypes
9 import logging 9 import logging
10 import os 10 import os
11 import platform 11 import platform
12 import plistlib
12 import re 13 import re
14 import struct
13 import subprocess 15 import subprocess
14 import time 16 import time
15 17
16 import plistlib
17
18 from utils import tools 18 from utils import tools
19 19
20 import common 20 import common
21 import gpu 21 import gpu
22 22
23 23
24 ## Private stuff. 24 ## Private stuff.
25 25
26 iokit = ctypes.CDLL(
27 '/System/Library/Frameworks/IOKit.framework/Versions/A/IOKit')
28 # https://developer.apple.com/documentation/iokit/1514274-ioconnectcallstructmet hod
29 iokit.IOConnectCallStructMethod.argtypes = [
30 ctypes.c_uint, ctypes.c_uint, ctypes.c_void_p, ctypes.c_ulonglong,
31 ctypes.c_void_p, ctypes.POINTER(ctypes.c_ulonglong),
32 ]
33 iokit.IOConnectCallStructMethod.restype = ctypes.c_int
34
35 # https://developer.apple.com/documentation/iokit/1514515-ioserviceopen
36 iokit.IOServiceOpen.argtypes = [
37 ctypes.c_uint, ctypes.c_uint, ctypes.c_uint, ctypes.POINTER(ctypes.c_uint),
38 ]
39 iokit.IOServiceOpen.restype = ctypes.c_int
40
41 # https://developer.apple.com/documentation/iokit/1514687-ioservicematching
42 iokit.IOServiceMatching.restype = ctypes.c_void_p
43
44 # https://developer.apple.com/documentation/iokit/1514494-ioservicegetmatchingse rvices
45 iokit.IOServiceGetMatchingServices.argtypes = [
46 ctypes.c_uint, ctypes.c_void_p, ctypes.POINTER(ctypes.c_uint),
47 ]
48 iokit.IOServiceGetMatchingServices.restype = ctypes.c_int
49
50 # https://developer.apple.com/documentation/iokit/1514741-ioiteratornext
51 iokit.IOIteratorNext.argtypes = [ctypes.c_uint]
52 iokit.IOIteratorNext.restype = ctypes.c_uint
53
54 # https://developer.apple.com/documentation/iokit/1514627-ioobjectrelease
55 iokit.IOObjectRelease.argtypes = [ctypes.c_uint]
56 iokit.IOObjectRelease.restype = ctypes.c_int
57
58
59 libkern = ctypes.CDLL('/usr/lib/system/libsystem_kernel.dylib')
60 libkern.mach_task_self.restype = ctypes.c_uint
61
62
63 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
64 _fields_ = [
65 ('major', ctypes.c_char),
66 ('minor', ctypes.c_char),
67 ('build', ctypes.c_char),
68 ('reserved', ctypes.c_char),
69 ('release', ctypes.c_ushort),
70 ]
71
72
73 class SMCKeyData_pLimitData_t(ctypes.Structure):
74 _fields_ = [
75 ('version', ctypes.c_ushort),
76 ('length', ctypes.c_ushort),
77 ('cpuPLimit', ctypes.c_uint),
78 ('gpuPLimit', ctypes.c_uint),
79 ('memPLimit', ctypes.c_uint),
80 ]
81
82
83 class SMCKeyData_keyInfo_t(ctypes.Structure):
84 _fields_ = [
85 ('dataSize', ctypes.c_uint),
86 ('dataType', ctypes.c_uint),
87 ('dataAttributes', ctypes.c_char),
88 ]
89
90
91 class SMCKeyData_t(ctypes.Structure):
92 _fields_ = [
93 ('key', ctypes.c_uint),
94 ('vers', SMCKeyData_vers_t),
95 ('pLimitData', SMCKeyData_pLimitData_t),
96 ('keyInfo', SMCKeyData_keyInfo_t),
97 ('result', ctypes.c_char),
98 ('status', ctypes.c_char),
99 ('data8', ctypes.c_char),
100 ('data32', ctypes.c_uint),
101 ('bytes', ctypes.c_ubyte * 32),
102 ]
103
104
105 class SMCVal_t(ctypes.Structure):
106 _fields_ = [
107 ('key', (ctypes.c_ubyte * 5)),
108 ('dataSize', ctypes.c_uint),
109 ('dataType', (ctypes.c_ubyte * 5)),
110 ('bytes', ctypes.c_ubyte * 32),
111 ]
112
113
114 @tools.cached
115 def SMC_open():
116 itr = ctypes.c_uint()
117 result = iokit.IOServiceGetMatchingServices(
118 0, iokit.IOServiceMatching('AppleSMC'), ctypes.byref(itr))
119 if result:
120 logging.error('failed to get AppleSMC (%d)', result)
121 return None
122 dev = iokit.IOIteratorNext(itr)
123 iokit.IOObjectRelease(itr)
124 if not dev:
125 logging.error('no SMC found')
126 return None
127 conn = ctypes.c_uint()
128 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
129 logging.error('failed to open AppleSMC (%d)', result)
130 return None
131 return conn
132
133
134 def SMC_call(conn, index, indata, outdata):
135 return iokit.IOConnectCallStructMethod(
136 conn,
137 ctypes.c_uint(index),
138 ctypes.cast(ctypes.pointer(indata), ctypes.c_void_p),
139 ctypes.c_ulonglong(ctypes.sizeof(SMCKeyData_t)),
140 ctypes.cast(ctypes.pointer(outdata), ctypes.c_void_p),
141 ctypes.pointer(ctypes.c_ulonglong(ctypes.sizeof(SMCKeyData_t))))
142
143
144 def SMC_read_key(conn, key):
145 KERNEL_INDEX_SMC = 2
146
147 # Call with SMC_CMD_READ_KEYINFO.
148 indata = SMCKeyData_t(key=struct.unpack('>i', key)[0], data8='\x09')
149 outdata = SMCKeyData_t()
150 if SMC_call(conn, KERNEL_INDEX_SMC, indata, outdata):
151 logging.error('SMC call to get key info failed')
152 return None
153
154 # 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. 💣
155 val = SMCVal_t(dataSize=outdata.keyInfo.dataSize)
156 for i, x in enumerate(struct.pack('>i', outdata.keyInfo.dataType)):
157 val.dataType[i] = ord(x)
158 # pylint: disable=attribute-defined-outside-init
159 indata.data8 = '\x05'
160 indata.keyInfo.dataSize = val.dataSize
161 if SMC_call(conn, KERNEL_INDEX_SMC, indata, outdata):
162 logging.error('SMC call to get data info failed')
163 return None
164 val.bytes = outdata.bytes
165 return val
166
167
168 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.
169 val = SMC_read_key(conn, key)
170 if not val:
171 return None
172 if val.dataSize != 2 or 'sp78\0' != ''.join(map(chr, val.dataType)):
173 return None
174 return (val.bytes[0] * 256 + val.bytes[1]) / 256.
175
26 176
27 @tools.cached 177 @tools.cached
28 def _get_system_profiler(data_type): 178 def _get_system_profiler(data_type):
29 """Returns an XML about the system display properties.""" 179 """Returns an XML about the system display properties."""
30 sp = subprocess.check_output( 180 sp = subprocess.check_output(
31 ['system_profiler', data_type, '-xml']) 181 ['system_profiler', data_type, '-xml'])
32 return plistlib.readPlistFromString(sp)[0]['_items'] 182 return plistlib.readPlistFromString(sp)[0]['_items']
33 183
34 184
35 @tools.cached 185 @tools.cached
(...skipping 129 matching lines...) Expand 10 before | Expand all | Expand 10 after
165 315
166 316
167 @tools.cached 317 @tools.cached
168 def get_hardware_model_string(): 318 def get_hardware_model_string():
169 """Returns the Mac model string. 319 """Returns the Mac model string.
170 320
171 Returns: 321 Returns:
172 A string like Macmini5,3 or MacPro6,1. 322 A string like Macmini5,3 or MacPro6,1.
173 """ 323 """
174 try: 324 try:
175 return subprocess.check_output(['sysctl', '-n', 'hw.model']).rstrip() 325 return unicode(
326 subprocess.check_output(['sysctl', '-n', 'hw.model']).rstrip())
176 except (OSError, subprocess.CalledProcessError): 327 except (OSError, subprocess.CalledProcessError):
177 return None 328 return None
178 329
179 330
180 @tools.cached 331 @tools.cached
181 def get_os_version_number(): 332 def get_os_version_number():
182 """Returns the normalized OS version number as a string. 333 """Returns the normalized OS version number as a string.
183 334
184 Returns: 335 Returns:
185 Version as a string like '10.12.4' 336 Version as a string like '10.12.4'
(...skipping 67 matching lines...) Expand 10 before | Expand all | Expand 10 after
253 u'model': [ 404 u'model': [
254 int(values['machdep.cpu.family']), int(values['machdep.cpu.model']), 405 int(values['machdep.cpu.family']), int(values['machdep.cpu.model']),
255 int(values['machdep.cpu.stepping']), 406 int(values['machdep.cpu.stepping']),
256 int(values['machdep.cpu.microcode_version']), 407 int(values['machdep.cpu.microcode_version']),
257 ], 408 ],
258 u'name': values[u'machdep.cpu.brand_string'], 409 u'name': values[u'machdep.cpu.brand_string'],
259 u'vendor': values[u'machdep.cpu.vendor'], 410 u'vendor': values[u'machdep.cpu.vendor'],
260 } 411 }
261 412
262 413
414 def get_cpu_temperature():
415 """Returns the CPU temperature via AppleSMC."""
416 conn = SMC_open()
417 if not conn:
418 return None
419 return SMC_get_temperature(conn, 'TC0P')
420
421
263 @tools.cached 422 @tools.cached
264 def get_monitor_hidpi(): 423 def get_monitor_hidpi():
265 """Returns True if the monitor is hidpi. 424 """Returns True if the monitor is hidpi.
266 425
267 On 10.12.3 and earlier, the following could be used to detect an hidpi 426 On 10.12.3 and earlier, the following could be used to detect an hidpi
268 display: 427 display:
269 <key>spdisplays_retina</key> 428 <key>spdisplays_retina</key>
270 <string>spdisplays_yes</string> 429 <string>spdisplays_yes</string>
271 430
272 On 10.12.4 and later, the key above doesn't exist anymore. Fall back to search 431 On 10.12.4 and later, the key above doesn't exist anymore. Fall back to search
273 for: 432 for:
274 <key>spdisplays_display_type</key> 433 <key>spdisplays_display_type</key>
275 <string>spdisplays_built-in_retinaLCD</string> 434 <string>spdisplays_built-in_retinaLCD</string>
276 """ 435 """
277 def is_hidpi(displays): 436 def is_hidpi(displays):
278 return any( 437 return any(
279 d.get('spdisplays_retina') == 'spdisplays_yes' or 438 d.get('spdisplays_retina') == 'spdisplays_yes' or
280 'retina' in d.get('spdisplays_display_type', '').lower() 439 'retina' in d.get('spdisplays_display_type', '').lower()
281 for d in displays) 440 for d in displays)
282 441
283 hidpi = any( 442 hidpi = any(
284 is_hidpi(card['spdisplays_ndrvs']) 443 is_hidpi(card['spdisplays_ndrvs'])
285 for card in _get_system_profiler('SPDisplaysDataType') 444 for card in _get_system_profiler('SPDisplaysDataType')
286 if 'spdisplays_ndrvs' in card) 445 if 'spdisplays_ndrvs' in card)
287 return str(int(hidpi)) 446 return unicode(int(hidpi))
288 447
289 448
290 def get_xcode_versions(): 449 def get_xcode_versions():
291 """Returns all Xcode versions installed.""" 450 """Returns all Xcode versions installed."""
292 return sorted(xcode['version'] for xcode in get_xcode_state().itervalues()) 451 return sorted(
452 unicode(xcode['version']) for xcode in get_xcode_state().itervalues())
293 453
294 454
295 @tools.cached 455 @tools.cached
296 def get_physical_ram(): 456 def get_physical_ram():
297 """Returns the amount of installed RAM in Mb, rounded to the nearest number. 457 """Returns the amount of installed RAM in Mb, rounded to the nearest number.
298 """ 458 """
299 CTL_HW = 6 459 CTL_HW = 6
300 HW_MEMSIZE = 24 460 HW_MEMSIZE = 24
301 result = ctypes.c_uint64(0) 461 result = ctypes.c_uint64(0)
302 _sysctl(CTL_HW, HW_MEMSIZE, result) 462 _sysctl(CTL_HW, HW_MEMSIZE, result)
(...skipping 86 matching lines...) Expand 10 before | Expand all | Expand 10 after
389 header = ( 549 header = (
390 '<?xml version="1.0" encoding="UTF-8"?>\n' 550 '<?xml version="1.0" encoding="UTF-8"?>\n'
391 '<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" ' 551 '<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" '
392 '"http://www.apple.com/DTDs/PropertyList-1.0.dtd">\n' 552 '"http://www.apple.com/DTDs/PropertyList-1.0.dtd">\n'
393 '<plist version="1.0">\n' 553 '<plist version="1.0">\n'
394 ' <dict>\n' 554 ' <dict>\n'
395 + ''.join(' %s\n' % l for l in entries) + 555 + ''.join(' %s\n' % l for l in entries) +
396 ' </dict>\n' 556 ' </dict>\n'
397 '</plist>\n') 557 '</plist>\n')
398 return header 558 return header
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698