diff options
-rw-r--r-- | README.rst | 15 | ||||
-rw-r--r-- | docs/index.rst | 35 | ||||
-rw-r--r-- | psutil/__init__.py | 45 | ||||
-rw-r--r-- | psutil/_common.py | 3 | ||||
-rw-r--r-- | psutil/_pslinux.py | 43 | ||||
-rw-r--r-- | psutil/arch/windows/services.c | 6 | ||||
-rwxr-xr-x | psutil/tests/test_misc.py | 8 | ||||
-rwxr-xr-x | psutil/tests/test_process.py | 2 | ||||
-rwxr-xr-x | psutil/tests/test_system.py | 16 | ||||
-rwxr-xr-x | scripts/sensors.py | 46 |
10 files changed, 217 insertions, 2 deletions
@@ -181,6 +181,21 @@ Network {'eth0': snicstats(isup=True, duplex=<NicDuplex.NIC_DUPLEX_FULL: 2>, speed=100, mtu=1500), 'lo': snicstats(isup=True, duplex=<NicDuplex.NIC_DUPLEX_UNKNOWN: 0>, speed=0, mtu=65536)} +Sensors (Linux only) +==================== + +.. code-block:: python + + >>> import psutil + >>> psutil.sensors_temperatures() + {'acpitz': [shwtemp(label='', current=47.0, high=103.0, critical=103.0)], + 'asus': [shwtemp(label='', current=47.0, high=None, critical=None)], + 'coretemp': [shwtemp(label='Physical id 0', current=52.0, high=100.0, critical=100.0), + shwtemp(label='Core 0', current=45.0, high=100.0, critical=100.0), + shwtemp(label='Core 1', current=52.0, high=100.0, critical=100.0), + shwtemp(label='Core 2', current=45.0, high=100.0, critical=100.0), + shwtemp(label='Core 3', current=47.0, high=100.0, critical=100.0)]} + Other system info ================= diff --git a/docs/index.rst b/docs/index.rst index 645df8df..87ebf734 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -617,6 +617,41 @@ Network .. versionadded:: 3.0.0 +Sensors +------- + +.. function:: sensors_temperatures(fahrenheit=False) + + Return hardware temperatures. Each entry is a namedtuple representing a + certain hardware sensor (it may be a CPU, an hard disk or something + else, depending on the OS and its configuration). + All temperatures are expressed in celsius unless *fahrenheit* is set to + ``True``. Example:: + + >>> import psutil + >>> psutil.sensors_temperatures() + {'acpitz': [shwtemp(label='', current=47.0, high=103.0, critical=103.0)], + 'asus': [shwtemp(label='', current=47.0, high=None, critical=None)], + 'coretemp': [shwtemp(label='Physical id 0', current=52.0, high=100.0, critical=100.0), + shwtemp(label='Core 0', current=45.0, high=100.0, critical=100.0), + shwtemp(label='Core 1', current=52.0, high=100.0, critical=100.0), + shwtemp(label='Core 2', current=45.0, high=100.0, critical=100.0), + shwtemp(label='Core 3', current=47.0, high=100.0, critical=100.0)]} + + See also `sensors.py <https://github.com/giampaolo/psutil/blob/master/scripts/sensors.py>`__ + for an example application. + + .. warning:: + + This API is experimental. Backwards incompatible changes may occur if + deemed necessary. + + Availability: Linux + + .. versionadded:: 5.1.0 + + + Other system info ----------------- diff --git a/psutil/__init__.py b/psutil/__init__.py index 647af0a6..e13866a3 100644 --- a/psutil/__init__.py +++ b/psutil/__init__.py @@ -185,6 +185,7 @@ __all__ = [ "net_io_counters", "net_connections", "net_if_addrs", # network "net_if_stats", "disk_io_counters", "disk_partitions", "disk_usage", # disk + # "sensors_temperatures", # sensors "users", "boot_time", # others ] __all__.extend(_psplatform.__extra__all__) @@ -2181,6 +2182,50 @@ def net_if_stats(): # ===================================================================== +# --- sensors +# ===================================================================== + + +if hasattr(_psplatform, "sensors_temperatures"): + + def sensors_temperatures(fahrenheit=False): + """Return hardware temperatures. Each entry is a namedtuple + representing a certain hardware sensor (it may be a CPU, an + hard disk or something else, depending on the OS and its + configuration). + All temperatures are expressed in celsius unless *fahrenheit* + is set to True. + """ + def to_fahrenheit(n): + return (float(n) * 9 / 5) + 32 + + ret = collections.defaultdict(list) + rawdict = _psplatform.sensors_temperatures() + + for name, values in rawdict.items(): + while values: + label, current, high, critical = values.pop(0) + if fahrenheit: + current = to_fahrenheit(current) + if high is not None: + high = to_fahrenheit(high) + if critical is not None: + critical = to_fahrenheit(critical) + + if high and not critical: + critical = high + elif critical and not high: + high = critical + + ret[name].append( + _common.shwtemp(label, current, high, critical)) + + return dict(ret) + + __all__.append("sensors_temperatures") + + +# ===================================================================== # --- other system related functions # ===================================================================== diff --git a/psutil/_common.py b/psutil/_common.py index 68134820..0d965ac1 100644 --- a/psutil/_common.py +++ b/psutil/_common.py @@ -158,6 +158,9 @@ scpustats = namedtuple( 'scpustats', ['ctx_switches', 'interrupts', 'soft_interrupts', 'syscalls']) # psutil.cpu_freq() scpufreq = namedtuple('scpufreq', ['current', 'min', 'max']) +# psutil.sensors_temperatures() +shwtemp = namedtuple( + 'shwtemp', ['label', 'current', 'high', 'critical']) # --- for Process methods diff --git a/psutil/_pslinux.py b/psutil/_pslinux.py index a9fbd8bc..16de79e5 100644 --- a/psutil/_pslinux.py +++ b/psutil/_pslinux.py @@ -7,6 +7,7 @@ from __future__ import division import base64 +import collections import errno import functools import glob @@ -64,6 +65,7 @@ __extra__all__ = [ HAS_SMAPS = os.path.exists('/proc/%s/smaps' % os.getpid()) HAS_PRLIMIT = hasattr(cext, "linux_prlimit") +_DEFAULT = object() # RLIMIT_* constants, not guaranteed to be present on all kernels if HAS_PRLIMIT: @@ -1060,6 +1062,47 @@ def disk_partitions(all=False): # ===================================================================== +# --- sensors +# ===================================================================== + + +if os.path.exists('/sys/class/hwmon'): + + def sensors_temperatures(): + """Return hardware (CPU and others) temperatures as a dict + including hardware name, label, current, max and critical + temperatures. + + Implementation notes: + - /sys/class/hwmon looks like the most recent interface to + retrieve this info, and this implementation relies on it + only (old distros will probably use something else) + - lm-sensors on Ubuntu 16.04 relies on /sys/class/hwmon + - /sys/class/thermal/thermal_zone* is another one but it's more + difficult to parse + """ + ret = collections.defaultdict(list) + basenames = sorted(set( + [x.split('_')[0] for x in + glob.glob('/sys/class/hwmon/hwmon*/temp*_*')])) + for base in basenames: + unit_name = cat(os.path.join(os.path.dirname(base), 'name')) + label = cat(base + '_label', fallback='') + current = float(cat(base + '_input')) / 1000.0 + high = cat(base + '_max', fallback=None) + critical = cat(base + '_crit', fallback=None) + + if high is not None: + high = float(high) / 1000.0 + if critical is not None: + critical = float(critical) / 1000.0 + + ret[unit_name].append((label, current, high, critical)) + + return ret + + +# ===================================================================== # --- other system functions # ===================================================================== diff --git a/psutil/arch/windows/services.c b/psutil/arch/windows/services.c index cb85afb5..26e58225 100644 --- a/psutil/arch/windows/services.c +++ b/psutil/arch/windows/services.c @@ -382,6 +382,12 @@ psutil_winservice_query_descr(PyObject *self, PyObject *args) { bytesNeeded = 0; QueryServiceConfig2(hService, SERVICE_CONFIG_DESCRIPTION, NULL, 0, &bytesNeeded); + if (GetLastError() == ERROR_MUI_FILE_NOT_FOUND) { + // Also services.msc fails in the same manner, so we return an + // empty string. + CloseServiceHandle(hService); + return Py_BuildValue("s", ""); + } if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) { PyErr_SetFromWindowsErr(0); goto error; diff --git a/psutil/tests/test_misc.py b/psutil/tests/test_misc.py index 0b696f8c..8697bcff 100755 --- a/psutil/tests/test_misc.py +++ b/psutil/tests/test_misc.py @@ -388,7 +388,7 @@ class TestScripts(unittest.TestCase): src = f.read() ast.parse(src) - def test_check_presence(self): + def test_coverage(self): # make sure all example scripts have a test method defined meths = dir(self) for name in os.listdir(SCRIPTS_DIR): @@ -469,6 +469,12 @@ class TestScripts(unittest.TestCase): def test_cpu_distribution(self): self.assert_syntax('cpu_distribution.py') + def test_sensors(self): + if hasattr(psutil, "sensors_temperatures"): + self.assert_stdout('sensors.py') + else: + self.assert_syntax('sensors.py') + # =================================================================== # --- Unit tests for test utilities. diff --git a/psutil/tests/test_process.py b/psutil/tests/test_process.py index db86290b..74ae9330 100755 --- a/psutil/tests/test_process.py +++ b/psutil/tests/test_process.py @@ -1808,10 +1808,10 @@ class TestFetchAllProcesses(unittest.TestCase): try: st = os.stat(ret) except OSError as err: - # directory has been removed in mean time if WINDOWS and err.errno in \ psutil._psplatform.ACCESS_DENIED_SET: pass + # directory has been removed in mean time elif err.errno != errno.ENOENT: raise else: diff --git a/psutil/tests/test_system.py b/psutil/tests/test_system.py index 18a5ae2d..79ae6bba 100755 --- a/psutil/tests/test_system.py +++ b/psutil/tests/test_system.py @@ -29,6 +29,7 @@ from psutil import POSIX from psutil import SUNOS from psutil import WINDOWS from psutil._compat import long +from psutil._compat import unicode from psutil.tests import AF_INET6 from psutil.tests import APPVEYOR from psutil.tests import check_net_address @@ -755,6 +756,21 @@ class TestSystemAPIs(unittest.TestCase): for name in names: self.assertIs(getattr(psutil, name), False, msg=name) + @unittest.skipUnless(hasattr(psutil, "sensors_temperatures"), + "platform not suported") + def test_sensors_temperatures(self): + temps = psutil.sensors_temperatures() + for name, entries in temps.items(): + self.assertIsInstance(name, (str, unicode)) + for entry in entries: + self.assertIsInstance(entry.label, (str, unicode)) + if entry.current is not None: + self.assertGreaterEqual(entry.current, 0) + if entry.high is not None: + self.assertGreaterEqual(entry.high, 0) + if entry.critical is not None: + self.assertGreaterEqual(entry.critical, 0) + if __name__ == '__main__': run_test_module_by_name(__file__) diff --git a/scripts/sensors.py b/scripts/sensors.py new file mode 100755 index 00000000..4b14180e --- /dev/null +++ b/scripts/sensors.py @@ -0,0 +1,46 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +# 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. + +""" +A clone of 'sensors' utility on Linux printing hardware temperatures. + +$ python scripts/sensors.py +asus + asus 47.0 °C (high = None °C, critical = None °C) + +acpitz + acpitz 47.0 °C (high = 103.0 °C, critical = 103.0 °C) + +coretemp + Physical id 0 54.0 °C (high = 100.0 °C, critical = 100.0 °C) + Core 0 47.0 °C (high = 100.0 °C, critical = 100.0 °C) + Core 1 48.0 °C (high = 100.0 °C, critical = 100.0 °C) + Core 2 47.0 °C (high = 100.0 °C, critical = 100.0 °C) + Core 3 54.0 °C (high = 100.0 °C, critical = 100.0 °C) +""" + +from __future__ import print_function +import sys + +import psutil + + +def main(): + if not hasattr(psutil, "sensors_temperatures"): + sys.exit("platform not supported") + temps = psutil.sensors_temperatures() + for name, entries in temps.items(): + print(name) + for entry in entries: + print(" %-20s %s °C (high = %s °C, critical = %s °C)" % ( + entry.label or name, entry.current, entry.high, + entry.critical)) + print() + + +if __name__ == '__main__': + main() |