OLD | NEW |
---|---|
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 Loading... | |
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 Loading... | |
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 Loading... | |
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 |
OLD | NEW |