summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGiampaolo Rodola <g.rodola@gmail.com>2018-06-26 15:20:42 +0200
committerGiampaolo Rodola <g.rodola@gmail.com>2018-06-26 15:20:42 +0200
commitca202d8edce5f3744512bfa57708c8afd142dfe4 (patch)
treef40b253ad6ff60f7afbc67e9a77e2e6a1747955c
parent00c12b954749ce6d771f69ddf29cf4cb87f290ca (diff)
parent196802dd34b7134c1fff99ad4755d0bc0e04cc98 (diff)
downloadpsutil-ca202d8edce5f3744512bfa57708c8afd142dfe4.tar.gz
Merge branch 'master' of github.com:giampaolo/psutil
-rw-r--r--psutil/__init__.py4
-rw-r--r--psutil/_common.py2
-rw-r--r--psutil/_psosx.py146
-rw-r--r--psutil/_psutil_osx.c68
-rw-r--r--psutil/arch/osx/smc.c236
-rw-r--r--psutil/arch/osx/smc.h79
-rwxr-xr-xpsutil/tests/test_contracts.py4
-rwxr-xr-xsetup.py1
8 files changed, 535 insertions, 5 deletions
diff --git a/psutil/__init__.py b/psutil/__init__.py
index 5e9a7fb6..f3859279 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 @@ if hasattr(_psplatform, "sensors_temperatures"):
__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 fdc94b15..af1fb563 100644
--- a/psutil/_common.py
+++ b/psutil/_common.py
@@ -196,7 +196,7 @@ shwtemp = namedtuple(
'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 7cbf4032..90c8be43 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 @@ PROC_STATUSES = {
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 b8e3b85d..584df3b8 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)
@@ -896,6 +897,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
@@ -1828,6 +1891,7 @@ psutil_cpu_stats(PyObject *self, PyObject *args) {
}
+
/*
* Return battery information.
*/
@@ -1980,6 +2044,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 00000000..fb5668a1
--- /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 <stdio.h>
+#include <string.h>
+#include <IOKit/IOKitLib.h>
+
+#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 00000000..acd399c2
--- /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 0dfb3e2a..912e811f 100755
--- a/psutil/tests/test_contracts.py
+++ b/psutil/tests/test_contracts.py
@@ -116,10 +116,10 @@ class TestAvailability(unittest.TestCase):
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 fffefd2f..b0343212 100755
--- a/setup.py
+++ b/setup.py
@@ -152,6 +152,7 @@ elif OSX:
sources=sources + [
'psutil/_psutil_osx.c',
'psutil/arch/osx/process_info.c',
+ 'psutil/arch/osx/smc.c',
],
define_macros=macros,
extra_link_args=[