Chromium Code Reviews| 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 |