diff --git a/psutil/__init__.py b/psutil/__init__.py index 5e9a7fb6c..f38592792 100644 --- a/psutil/__init__.py +++ b/psutil/__init__.py @@ -2175,7 +2175,7 @@ def net_if_stats(): # ===================================================================== -# Linux +# Linux, OSX if hasattr(_psplatform, "sensors_temperatures"): def sensors_temperatures(fahrenheit=False): @@ -2213,7 +2213,7 @@ def convert(n): __all__.append("sensors_temperatures") -# Linux +# Linux, OSX if hasattr(_psplatform, "sensors_fans"): def sensors_fans(): diff --git a/psutil/_common.py b/psutil/_common.py index e65d39e3b..b72139396 100644 --- a/psutil/_common.py +++ b/psutil/_common.py @@ -196,7 +196,7 @@ class BatteryTime(enum.IntEnum): 'shwtemp', ['label', 'current', 'high', 'critical']) # psutil.sensors_battery() sbattery = namedtuple('sbattery', ['percent', 'secsleft', 'power_plugged']) -# psutil.sensors_battery() +# psutil.sensors_fans() sfan = namedtuple('sfan', ['label', 'current']) # --- for Process methods diff --git a/psutil/_psosx.py b/psutil/_psosx.py index 193f63e00..fc8553f54 100644 --- a/psutil/_psosx.py +++ b/psutil/_psosx.py @@ -6,6 +6,7 @@ import contextlib import errno +import collections import functools import os from socket import AF_INET @@ -62,6 +63,110 @@ cext.SZOMB: _common.STATUS_ZOMBIE, } +temperatures = ( + # group, key, label + + # --- CPU + ("CPU", "TCXC", "PECI CPU"), + ("CPU", "TCXc", "PECI CPU"), + ("CPU", "TC0P", "CPU 1 Proximity"), + ("CPU", "TC0H", "CPU 1 Heatsink"), + ("CPU", "TC0D", "CPU 1 Package"), + ("CPU", "TC0E", "CPU 1"), + ("CPU", "TC1C", "CPU Core 1"), + ("CPU", "TC2C", "CPU Core 2"), + ("CPU", "TC3C", "CPU Core 3"), + ("CPU", "TC4C", "CPU Core 4"), + ("CPU", "TC5C", "CPU Core 5"), + ("CPU", "TC6C", "CPU Core 6"), + ("CPU", "TC7C", "CPU Core 7"), + ("CPU", "TC8C", "CPU Core 8"), + ("CPU", "TCAH", "CPU 1 Heatsink Alt."), + ("CPU", "TCAD", "CPU 1 Package Alt."), + ("CPU", "TC1P", "CPU 2 Proximity"), + ("CPU", "TC1H", "CPU 2 Heatsink"), + ("CPU", "TC1D", "CPU 2 Package"), + ("CPU", "TC1E", "CPU 2"), + ("CPU", "TCBH", "CPU 2 Heatsink Alt."), + ("CPU", "TCBD", "CPU 2 Package Alt."), + + ("CPU", "TCSC", "PECI SA"), + ("CPU", "TCSc", "PECI SA"), + ("CPU", "TCSA", "PECI SA"), + + # --- GPU + ("GPU", "TCGC", "PECI GPU"), + ("GPU", "TCGc", "PECI GPU"), + ("GPU", "TG0P", "GPU Proximity"), + ("GPU", "TG0D", "GPU Die"), + ("GPU", "TG1D", "GPU Die"), + ("GPU", "TG0H", "GPU Heatsink"), + ("GPU", "TG1H", "GPU Heatsink"), + + # --- Memory + ("Memory", "Ts0S", "Memory Proximity"), + ("Memory", "TM0P", "Mem Bank A1"), + ("Memory", "TM1P", "Mem Bank A2"), + ("Memory", "TM8P", "Mem Bank B1"), + ("Memory", "TM9P", "Mem Bank B2"), + ("Memory", "TM0S", "Mem Module A1"), + ("Memory", "TM1S", "Mem Module A2"), + ("Memory", "TM8S", "Mem Module B1"), + ("Memory", "TM9S", "Mem Module B2"), + + # --- HDD + ("HDD", "TH0P", "HDD Bay 1"), + ("HDD", "TH1P", "HDD Bay 2"), + ("HDD", "TH2P", "HDD Bay 3"), + ("HDD", "TH3P", "HDD Bay 4"), + + # --- Battery + ("Battery", "TB0T", "Battery TS_MAX"), + ("Battery", "TB1T", "Battery 1"), + ("Battery", "TB2T", "Battery 2"), + ("Battery", "TB3T", "Battery"), + + # --- Others + ("Others", "TN0D", "Northbridge Die"), + ("Others", "TN0P", "Northbridge Proximity 1"), + ("Others", "TN1P", "Northbridge Proximity 2"), + ("Others", "TN0C", "MCH Die"), + ("Others", "TN0H", "MCH Heatsink"), + ("Others", "TP0D", "PCH Die"), + ("Others", "TPCD", "PCH Die"), + ("Others", "TP0P", "PCH Proximity"), + + ("Others", "TA0P", "Airflow 1"), + ("Others", "TA1P", "Airflow 2"), + ("Others", "Th0H", "Heatpipe 1"), + ("Others", "Th1H", "Heatpipe 2"), + ("Others", "Th2H", "Heatpipe 3"), + + ("Others", "Tm0P", "Mainboard Proximity"), + ("Others", "Tp0P", "Powerboard Proximity"), + ("Others", "Ts0P", "Palm Rest"), + ("Others", "Tb0P", "BLC Proximity"), + + ("Others", "TL0P", "LCD Proximity"), + ("Others", "TW0P", "Airport Proximity"), + ("Others", "TO0P", "Optical Drive"), + + ("Others", "Tp0P", "Power Supply 1"), + ("Others", "Tp0C", "Power Supply 1 Alt."), + ("Others", "Tp1P", "Power Supply 2"), + ("Others", "Tp1C", "Power Supply 2 Alt."), + ("Others", "Tp2P", "Power Supply 3"), + ("Others", "Tp3P", "Power Supply 4"), + ("Others", "Tp4P", "Power Supply 5"), + ("Others", "Tp5P", "Power Supply 6"), + + ("Others", "TS0C", "Expansion Slots"), + ("Others", "TA0S", "PCI Slot 1 Pos 1"), + ("Others", "TA1S", "PCI Slot 1 Pos 2"), + ("Others", "TA2S", "PCI Slot 2 Pos 1"), + ("Others", "TA3S", "PCI Slot 2 Pos 2"), +) + kinfo_proc_map = dict( ppid=0, ruid=1, @@ -212,6 +317,35 @@ def disk_partitions(all=False): # ===================================================================== +def sensors_temperatures(): + """Returns a dictionary of regions of temperature sensors: + CPU/GPU/Memory/Others + Each entry contains a list of results of all the successfully polled + SMC keys from the system. + + References for SMC keys and meaning: + + https://stackoverflow.com/questions/28568775/ + description-for-apples-smc-keys/31033665#31033665 + + https://github.com/Chris911/iStats/blob/ + 09b159f85a9481b59f347a37259f6d272f65cc05/lib/iStats/smc.rb + + http://web.archive.org/web/20140714090133/http://www.parhelia.ch:80/ + blog/statics/k3_keys.html + """ + ret = collections.defaultdict(list) + + for group, key, label in temperatures: + result = cext.smc_get_temperature(key) + result = round(result, 1) + if result <= 0: + continue + ret[group].append((label, result, None, None)) + + return dict(ret) + + def sensors_battery(): """Return battery information. """ @@ -230,6 +364,18 @@ def sensors_battery(): return _common.sbattery(percent, secsleft, power_plugged) +def sensors_fans(): + """Return fans speed information. + """ + ret = collections.defaultdict(list) + rawlist = cext.sensors_fans() + if rawlist is not None: + for fan in rawlist: + ret["Fans"].append(_common.sfan(fan[0], fan[1])) + + return dict(ret) + + # ===================================================================== # --- network # ===================================================================== diff --git a/psutil/_psutil_osx.c b/psutil/_psutil_osx.c index 55dd64ca5..d9e2ada68 100644 --- a/psutil/_psutil_osx.c +++ b/psutil/_psutil_osx.c @@ -44,6 +44,7 @@ #include "_psutil_common.h" #include "_psutil_posix.h" #include "arch/osx/process_info.h" +#include "arch/osx/smc.h" #define PSUTIL_TV2DOUBLE(t) ((t).tv_sec + (t).tv_usec / 1000000.0) @@ -860,6 +861,68 @@ psutil_boot_time(PyObject *self, PyObject *args) { return Py_BuildValue("f", (float)boot_time); } +/* + * Return a Python float indicating the value of the temperature + * measured by an SMC key + */ +static PyObject * +psutil_smc_get_temperature(PyObject *self, PyObject *args) { + char* key; + float temp; + + if (! PyArg_ParseTuple(args, "s", &key)) { + return NULL; + } + temp = SMCGetTemperature(key); + return Py_BuildValue("d", temp); +} + + +/* + * Return a Python list of tuples of fan label and speed + */ +static PyObject * +psutil_sensors_fans(PyObject *self, PyObject *args) { + int key; + int speed; + char fan[7]; + int fan_count; + PyObject *py_tuple = NULL; + PyObject *py_retlist = PyList_New(0); + + if (py_retlist == NULL) + return NULL; + + fan_count = SMCGetFanNumber(SMC_KEY_FAN_NUM); + if (fan_count < 0) { + fan_count = 0; + } + for (key =0; key < fan_count; key++) { + sprintf(fan, "Fan %d", key); + speed = SMCGetFanSpeed(key); + if (speed < 0) { + continue; + } + py_tuple = Py_BuildValue( + "(si)", + fan, // label + speed // value + ); + if (!py_tuple) + goto error; + + if (PyList_Append(py_retlist, py_tuple)) { + goto error; + } + Py_XDECREF(py_tuple); + } + + return py_retlist; +error: + Py_XDECREF(py_tuple); + Py_XDECREF(py_retlist); + return NULL; +} /* * Return a list of tuples including device, mount point and fs type @@ -1798,6 +1861,7 @@ psutil_cpu_stats(PyObject *self, PyObject *args) { } + /* * Return battery information. */ @@ -1950,6 +2014,10 @@ PsutilMethods[] = { "Return currently connected users as a list of tuples"}, {"cpu_stats", psutil_cpu_stats, METH_VARARGS, "Return CPU statistics"}, + {"smc_get_temperature", psutil_smc_get_temperature, METH_VARARGS, + "Temperature of SMC key as float"}, + {"sensors_fans", psutil_sensors_fans, METH_VARARGS, + "Return the RPM of the fan with SMC key"}, {"sensors_battery", psutil_sensors_battery, METH_VARARGS, "Return battery information."}, diff --git a/psutil/arch/osx/smc.c b/psutil/arch/osx/smc.c new file mode 100644 index 000000000..fb5668a1e --- /dev/null +++ b/psutil/arch/osx/smc.c @@ -0,0 +1,236 @@ +/* + * Copyright (c) 2009, Giampaolo Rodola'. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + * + * Interface to SMC API, needed in order to collect sensors stats. + */ + +#include +#include +#include + +#include "smc.h" + +UInt32 _strtoul(char *str, int size, int base) +{ + UInt32 total = 0; + int i; + + for (i = 0; i < size; i++) { + if (base == 16) + total += str[i] << (size - 1 - i) * 8; + else + total += (unsigned char) (str[i] << (size - 1 - i) * 8); + } + return total; +} + + +float _strtof(unsigned char *str, int size, int e) +{ + float total = 0; + int i; + + for (i = 0; i < size; i++) { + if (i == (size - 1)) + total += (str[i] & 0xff) >> e; + else + total += str[i] << (size - 1 - i) * (8 - e); + } + + total += (str[size-1] & 0x03) * 0.25; + + return total; +} + + +void _ultostr(char *str, UInt32 val) +{ + str[0] = '\0'; + sprintf(str, "%c%c%c%c", + (unsigned int) val >> 24, + (unsigned int) val >> 16, + (unsigned int) val >> 8, + (unsigned int) val); +} + + +kern_return_t SMCOpen(io_connect_t * pconn) +{ + kern_return_t result; + io_iterator_t iterator; + io_object_t device; + + CFMutableDictionaryRef matchingDictionary = IOServiceMatching("AppleSMC"); + result = IOServiceGetMatchingServices( + kIOMasterPortDefault, + matchingDictionary, + &iterator + ); + if (result != kIOReturnSuccess) { + return 1; + } + + device = IOIteratorNext(iterator); + IOObjectRelease(iterator); + if (device == 0) { + return 1; + } + + result = IOServiceOpen(device, mach_task_self(), 0, pconn); + IOObjectRelease(device); + if (result != kIOReturnSuccess) { + return 1; + } + + return kIOReturnSuccess; +} + + +kern_return_t SMCClose(io_connect_t conn) +{ + return IOServiceClose(conn); +} + + +kern_return_t SMCCall(io_connect_t conn, + int index, SMCKeyData_t *inputStructure, SMCKeyData_t *outputStructure) +{ + size_t structureInputSize; + size_t structureOutputSize; + + structureInputSize = sizeof(SMCKeyData_t); + structureOutputSize = sizeof(SMCKeyData_t); + +#if MAC_OS_X_VERSION_10_5 + return IOConnectCallStructMethod( + conn, index, + inputStructure, + structureInputSize, + outputStructure, + &structureOutputSize); +#else + return IOConnectMethodStructureIStructureO( + conn, index, + structureInputSize, + &structureOutputSize, + inputStructure, + outputStructure); +#endif +} + + +kern_return_t SMCReadKey(io_connect_t conn, UInt32Char_t key, SMCVal_t *val) { + kern_return_t result; + SMCKeyData_t inputStructure; + SMCKeyData_t outputStructure; + + memset(&inputStructure, 0, sizeof(SMCKeyData_t)); + memset(&outputStructure, 0, sizeof(SMCKeyData_t)); + memset(val, 0, sizeof(SMCVal_t)); + + inputStructure.key = _strtoul(key, 4, 16); + inputStructure.data8 = SMC_CMD_READ_KEYINFO; + + result = SMCCall( + conn, + KERNEL_INDEX_SMC, + &inputStructure, + &outputStructure + ); + if (result != kIOReturnSuccess) + return result; + + val->dataSize = outputStructure.keyInfo.dataSize; + _ultostr(val->dataType, outputStructure.keyInfo.dataType); + inputStructure.keyInfo.dataSize = val->dataSize; + inputStructure.data8 = SMC_CMD_READ_BYTES; + + result = SMCCall( + conn, + KERNEL_INDEX_SMC, + &inputStructure, + &outputStructure + ); + if (result != kIOReturnSuccess) + return result; + + memcpy(val->bytes, outputStructure.bytes, sizeof(outputStructure.bytes)); + + return kIOReturnSuccess; +} + + +double SMCGetTemperature(char *key) { + io_connect_t conn; + SMCVal_t val; + kern_return_t result; + int intValue; + + result = SMCOpen(&conn); + if (result != kIOReturnSuccess) { + return 0.0; + } + + result = SMCReadKey(conn, key, &val); + if (result == kIOReturnSuccess) { + // read succeeded - check returned value + if (val.dataSize > 0) { + if (strcmp(val.dataType, DATATYPE_SP78) == 0) { + // convert sp78 value to temperature + intValue = val.bytes[0] * 256 + (unsigned char)val.bytes[1]; + SMCClose(conn); + return intValue / 256.0; + } + } + } + // read failed + SMCClose(conn); + return 0.0; +} + + +float SMCGetFanSpeed(int fanNum) +{ + SMCVal_t val; + kern_return_t result; + UInt32Char_t key; + io_connect_t conn; + + result = SMCOpen(&conn); + if (result != kIOReturnSuccess) { + return -1; + } + + sprintf(key, SMC_KEY_FAN_SPEED, fanNum); + result = SMCReadKey(conn, key, &val); + if (result != kIOReturnSuccess) { + SMCClose(conn); + return -1; + } + SMCClose(conn); + return _strtof((unsigned char *)val.bytes, val.dataSize, 2); +} + + + +int SMCGetFanNumber(char *key) +{ + SMCVal_t val; + kern_return_t result; + io_connect_t conn; + + result = SMCOpen(&conn); + if (result != kIOReturnSuccess) { + return 0; + } + + result = SMCReadKey(conn, key, &val); + if (result != kIOReturnSuccess) { + SMCClose(conn); + return 0; + } + SMCClose(conn); + return _strtoul((char *)val.bytes, val.dataSize, 10); +} diff --git a/psutil/arch/osx/smc.h b/psutil/arch/osx/smc.h new file mode 100644 index 000000000..acd399c2e --- /dev/null +++ b/psutil/arch/osx/smc.h @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2009, Giampaolo Rodola'. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef __SMC_H__ +#define __SMC_H__ + +#define KERNEL_INDEX_SMC 2 + +#define SMC_CMD_READ_BYTES 5 +#define SMC_CMD_WRITE_BYTES 6 +#define SMC_CMD_READ_INDEX 8 +#define SMC_CMD_READ_KEYINFO 9 +#define SMC_CMD_READ_PLIMIT 11 +#define SMC_CMD_READ_VERS 12 + + +#define DATATYPE_FPE2 "fpe2" +#define DATATYPE_UINT8 "ui8 " +#define DATATYPE_UINT16 "ui16" +#define DATATYPE_UINT32 "ui32" +#define DATATYPE_SP78 "sp78" + +// Fans SMC key values +#define SMC_KEY_FAN_SPEED "F%dAc" +#define SMC_KEY_FAN_NUM "FNum" + +typedef struct { + char major; + char minor; + char build; + char reserved[1]; + UInt16 release; +} SMCKeyData_vers_t; + +typedef struct { + UInt16 version; + UInt16 length; + UInt32 cpuPLimit; + UInt32 gpuPLimit; + UInt32 memPLimit; +} SMCKeyData_pLimitData_t; + +typedef struct { + UInt32 dataSize; + UInt32 dataType; + char dataAttributes; +} SMCKeyData_keyInfo_t; + +typedef char SMCBytes_t[32]; + +typedef struct { + UInt32 key; + SMCKeyData_vers_t vers; + SMCKeyData_pLimitData_t pLimitData; + SMCKeyData_keyInfo_t keyInfo; + char result; + char status; + char data8; + UInt32 data32; + SMCBytes_t bytes; +} SMCKeyData_t; + +typedef char UInt32Char_t[5]; + +typedef struct { + UInt32Char_t key; + UInt32 dataSize; + UInt32Char_t dataType; + SMCBytes_t bytes; +} SMCVal_t; + +double SMCGetTemperature(char *key); +int SMCGetFanNumber(char *key); +float SMCGetFanSpeed(int fanNum); + +#endif diff --git a/psutil/tests/test_contracts.py b/psutil/tests/test_contracts.py index 0dfb3e2a8..912e811f3 100755 --- a/psutil/tests/test_contracts.py +++ b/psutil/tests/test_contracts.py @@ -116,10 +116,10 @@ def test_cpu_freq(self): self.assertEqual(hasattr(psutil, "cpu_freq"), linux or OSX or WINDOWS) def test_sensors_temperatures(self): - self.assertEqual(hasattr(psutil, "sensors_temperatures"), LINUX) + self.assertEqual(hasattr(psutil, "sensors_temperatures"), LINUX or OSX) def test_sensors_fans(self): - self.assertEqual(hasattr(psutil, "sensors_fans"), LINUX) + self.assertEqual(hasattr(psutil, "sensors_fans"), LINUX or OSX) def test_battery(self): self.assertEqual(hasattr(psutil, "sensors_battery"), diff --git a/setup.py b/setup.py index fffefd2f7..b0343212b 100755 --- a/setup.py +++ b/setup.py @@ -152,6 +152,7 @@ def get_winver(): sources=sources + [ 'psutil/_psutil_osx.c', 'psutil/arch/osx/process_info.c', + 'psutil/arch/osx/smc.c', ], define_macros=macros, extra_link_args=[