diff options
author | Giampaolo Rodola <g.rodola@gmail.com> | 2017-01-23 22:56:08 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2017-01-23 22:56:08 +0100 |
commit | f8c07ae1082ee07fd08c25fa163fc8edb31e5b7f (patch) | |
tree | 69f2796c4548866a719429694d5fda3ddff1c0bd | |
parent | 7ae1bb6341c99081f53e558e312d76a71cbb8abe (diff) | |
parent | 1266103171e75ff0e997c93980765259da285192 (diff) | |
download | psutil-f8c07ae1082ee07fd08c25fa163fc8edb31e5b7f.tar.gz |
Merge pull request #952 from giampaolo/941-cpu-freq
941 cpu freq
-rw-r--r-- | README.rst | 3 | ||||
-rw-r--r-- | docs/index.rst | 27 | ||||
-rw-r--r-- | psutil/__init__.py | 32 | ||||
-rw-r--r-- | psutil/_common.py | 2 | ||||
-rw-r--r-- | psutil/_pslinux.py | 35 | ||||
-rw-r--r-- | psutil/_psosx.py | 10 | ||||
-rw-r--r-- | psutil/_psutil_osx.c | 31 | ||||
-rw-r--r-- | psutil/_psutil_windows.c | 67 | ||||
-rw-r--r-- | psutil/_pswindows.py | 9 | ||||
-rwxr-xr-x | psutil/tests/test_memory_leaks.py | 5 | ||||
-rwxr-xr-x | psutil/tests/test_osx.py | 15 | ||||
-rwxr-xr-x | psutil/tests/test_system.py | 19 | ||||
-rwxr-xr-x | psutil/tests/test_windows.py | 6 | ||||
-rwxr-xr-x | setup.py | 2 |
14 files changed, 261 insertions, 2 deletions
@@ -122,6 +122,9 @@ CPU >>> >>> psutil.cpu_stats() scpustats(ctx_switches=20455687, interrupts=6598984, soft_interrupts=2134212, syscalls=0) + >>> + >>> psutil.cpu_freq() + scpufreq(current=931.42925, min=800.0, max=3500.0) Memory ====== diff --git a/docs/index.rst b/docs/index.rst index 270124b7..906a1e16 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -191,6 +191,33 @@ CPU .. versionadded:: 4.1.0 +.. function:: cpu_freq(percpu=False) + + Return CPU frequency as a nameduple including *current*, *min* and *max* + frequencies expressed in Mhz. + If *percpu* is ``True`` and the system supports per-cpu frequency + retrieval (Linux only) a list of frequencies is returned for each CPU, + if not, a list with a single element is returned. + If *min* and *max* cannot be determined they are set to ``0``. + + Example (Linux): + + .. code-block:: python + + >>> import psutil + >>> psutil.cpu_freq() + scpufreq(current=931.42925, min=800.0, max=3500.0) + >>> psutil.cpu_freq(percpu=True) + [scpufreq(current=2394.945, min=800.0, max=3500.0), + scpufreq(current=2236.812, min=800.0, max=3500.0), + scpufreq(current=1703.609, min=800.0, max=3500.0), + scpufreq(current=1754.289, min=800.0, max=3500.0)] + + Availability: Linux, OSX, Windows + + .. versionadded:: 5.1.0 + + Memory ------ diff --git a/psutil/__init__.py b/psutil/__init__.py index 7472ca8e..77830230 100644 --- a/psutil/__init__.py +++ b/psutil/__init__.py @@ -181,7 +181,7 @@ __all__ = [ "pid_exists", "pids", "process_iter", "wait_procs", # proc "virtual_memory", "swap_memory", # memory "cpu_times", "cpu_percent", "cpu_times_percent", "cpu_count", # cpu - "cpu_stats", + "cpu_stats", # "cpu_freq", "net_io_counters", "net_connections", "net_if_addrs", # network "net_if_stats", "disk_io_counters", "disk_partitions", "disk_usage", # disk @@ -1852,6 +1852,36 @@ def cpu_stats(): return _psplatform.cpu_stats() +if hasattr(_psplatform, "cpu_freq"): + + def cpu_freq(percpu=False): + """Return CPU frequency as a nameduple including current, + min and max frequency expressed in Mhz. + + If percpu is True and the system supports per-cpu frequency + retrieval (Linux only) a list of frequencies is returned for + each CPU. If not a list with one element is returned. + """ + ret = _psplatform.cpu_freq() + if percpu: + return ret + else: + num_cpus = len(ret) + if num_cpus == 1: + return ret[0] + currs, mins, maxs = [], [], [] + for cpu in ret: + currs.append(cpu.current) + mins.append(cpu.min) + maxs.append(cpu.max) + return _common.scpufreq( + sum(currs) / num_cpus, + sum(mins) / num_cpus, + sum(maxs) / num_cpus) + + __all__.append("cpu_freq") + + # ===================================================================== # --- system memory related functions # ===================================================================== diff --git a/psutil/_common.py b/psutil/_common.py index 3879a1d7..68134820 100644 --- a/psutil/_common.py +++ b/psutil/_common.py @@ -156,6 +156,8 @@ snicstats = namedtuple('snicstats', ['isup', 'duplex', 'speed', 'mtu']) # psutil.cpu_stats() scpustats = namedtuple( 'scpustats', ['ctx_switches', 'interrupts', 'soft_interrupts', 'syscalls']) +# psutil.cpu_freq() +scpufreq = namedtuple('scpufreq', ['current', 'min', 'max']) # --- for Process methods diff --git a/psutil/_pslinux.py b/psutil/_pslinux.py index fe2f459d..9154896a 100644 --- a/psutil/_pslinux.py +++ b/psutil/_pslinux.py @@ -9,6 +9,7 @@ from __future__ import division import base64 import errno import functools +import glob import os import re import socket @@ -133,6 +134,8 @@ TCP_STATUSES = { "0B": _common.CONN_CLOSING } +_DEFAULT = object() + # ===================================================================== # -- exceptions @@ -276,6 +279,18 @@ def set_scputimes_ntuple(procfs_path): return scputimes +def cat(fname, fallback=_DEFAULT, binary=True): + """Return file content.""" + try: + with open_binary(fname) if binary else open_text(fname) as f: + return f.read() + except IOError: + if fallback != _DEFAULT: + return fallback + else: + raise + + try: scputimes = set_scputimes_ntuple("/proc") except Exception: @@ -607,6 +622,26 @@ def cpu_stats(): ctx_switches, interrupts, soft_interrupts, syscalls) +if os.path.exists("/sys/devices/system/cpu/cpufreq"): + + def cpu_freq(): + # scaling_* files seem preferable to cpuinfo_*, see: + # http://unix.stackexchange.com/a/87537/168884 + ret = [] + ls = glob.glob("/sys/devices/system/cpu/cpufreq/policy*") + # Sort the list so that '10' comes after '2'. This should + # ensure the CPU order is consistent with other CPU functions + # having a 'percpu' argument and returning results for multiple + # CPUs (cpu_times(), cpu_percent(), cpu_times_percent()). + ls.sort(key=lambda x: int(os.path.basename(x)[6:])) + for path in ls: + curr = int(cat(os.path.join(path, "scaling_cur_freq"))) / 1000 + max_ = int(cat(os.path.join(path, "scaling_max_freq"))) / 1000 + min_ = int(cat(os.path.join(path, "scaling_min_freq"))) / 1000 + ret.append(_common.scpufreq(curr, min_, max_)) + return ret + + # ===================================================================== # --- network # ===================================================================== diff --git a/psutil/_psosx.py b/psutil/_psosx.py index 2665080e..f7adb43a 100644 --- a/psutil/_psosx.py +++ b/psutil/_psosx.py @@ -165,6 +165,16 @@ def cpu_stats(): ctx_switches, interrupts, soft_interrupts, syscalls) +def cpu_freq(): + """Return CPU frequency. + On OSX per-cpu frequency is not supported. + Also, the returned frequency never changes, see: + https://arstechnica.com/civis/viewtopic.php?f=19&t=465002 + """ + curr, min_, max_ = cext.cpu_freq() + return [_common.scpufreq(curr, min_, max_)] + + # ===================================================================== # --- disks # ===================================================================== diff --git a/psutil/_psutil_osx.c b/psutil/_psutil_osx.c index a1168c29..fb26dc9b 100644 --- a/psutil/_psutil_osx.c +++ b/psutil/_psutil_osx.c @@ -809,6 +809,35 @@ error: /* + * Retrieve CPU frequency. + */ +static PyObject * +psutil_cpu_freq(PyObject *self, PyObject *args) { + int64_t curr; + int64_t min; + int64_t max; + size_t size = sizeof(int64_t); + + if (sysctlbyname("hw.cpufrequency", &curr, &size, NULL, 0)) + goto error; + if (sysctlbyname("hw.cpufrequency_min", &min, &size, NULL, 0)) + goto error; + if (sysctlbyname("hw.cpufrequency_max", &max, &size, NULL, 0)) + goto error; + + return Py_BuildValue( + "KKK", + curr / 1000 / 1000, + min / 1000 / 1000, + max / 1000 / 1000); + +error: + PyErr_SetFromErrno(PyExc_OSError); + return NULL; +} + + +/* * Return a Python float indicating the system boot time expressed in * seconds since the epoch. */ @@ -1778,6 +1807,8 @@ PsutilMethods[] = { "Return system cpu times as a tuple (user, system, nice, idle, irc)"}, {"per_cpu_times", psutil_per_cpu_times, METH_VARARGS, "Return system per-cpu times as a list of tuples"}, + {"cpu_freq", psutil_cpu_freq, METH_VARARGS, + "Return cpu current frequency"}, {"boot_time", psutil_boot_time, METH_VARARGS, "Return the system boot time expressed in seconds since the epoch."}, {"disk_partitions", psutil_disk_partitions, METH_VARARGS, diff --git a/psutil/_psutil_windows.c b/psutil/_psutil_windows.c index 4d939aff..4caace7d 100644 --- a/psutil/_psutil_windows.c +++ b/psutil/_psutil_windows.c @@ -24,6 +24,7 @@ #include <iphlpapi.h> #include <wtsapi32.h> #include <Winsvc.h> +#include <PowrProf.h> // Link with Iphlpapi.lib #pragma comment(lib, "IPHLPAPI.lib") @@ -145,6 +146,16 @@ typedef struct _MIB_UDP6TABLE_OWNER_PID { } MIB_UDP6TABLE_OWNER_PID, *PMIB_UDP6TABLE_OWNER_PID; #endif +typedef struct _PROCESSOR_POWER_INFORMATION { + ULONG Number; + ULONG MaxMhz; + ULONG CurrentMhz; + ULONG MhzLimit; + ULONG MaxIdleState; + ULONG CurrentIdleState; +} PROCESSOR_POWER_INFORMATION, *PPROCESSOR_POWER_INFORMATION; + + PIP_ADAPTER_ADDRESSES psutil_get_nic_addresses() { // allocate a 15 KB buffer to start with @@ -3391,6 +3402,60 @@ error: } +/* + * Return CPU frequency. + */ +static PyObject * +psutil_cpu_freq(PyObject *self, PyObject *args) { + PROCESSOR_POWER_INFORMATION *ppi; + NTSTATUS ret; + size_t size; + LPBYTE pBuffer = NULL; + ULONG current; + ULONG max; + unsigned int num_cpus; + SYSTEM_INFO system_info; + system_info.dwNumberOfProcessors = 0; + + // Get the number of CPUs. + GetSystemInfo(&system_info); + if (system_info.dwNumberOfProcessors == 0) + num_cpus = 1; + else + num_cpus = system_info.dwNumberOfProcessors; + + // Allocate size. + size = num_cpus * sizeof(PROCESSOR_POWER_INFORMATION); + pBuffer = (BYTE*)LocalAlloc(LPTR, size); + if (! pBuffer) { + PyErr_SetFromWindowsErr(0); + return NULL; + } + + // Syscall. + ret = CallNtPowerInformation( + ProcessorInformation, NULL, 0, pBuffer, size); + if (ret != 0) { + PyErr_SetString(PyExc_RuntimeError, + "CallNtPowerInformation syscall failed"); + goto error; + } + + // Results. + ppi = (PROCESSOR_POWER_INFORMATION *)pBuffer; + max = ppi->MaxMhz; + current = ppi->CurrentMhz; + LocalFree(pBuffer); + + return Py_BuildValue("kk", current, max); + +error: + if (pBuffer != NULL) + LocalFree(pBuffer); + return NULL; +} + + // ------------------------ Python init --------------------------- static PyMethodDef @@ -3495,6 +3560,8 @@ PsutilMethods[] = { "Return NICs stats."}, {"cpu_stats", psutil_cpu_stats, METH_VARARGS, "Return NICs stats."}, + {"cpu_freq", psutil_cpu_freq, METH_VARARGS, + "Return CPU frequency."}, // --- windows services {"winservice_enumerate", psutil_winservice_enumerate, METH_VARARGS, diff --git a/psutil/_pswindows.py b/psutil/_pswindows.py index cb816f73..da8552e1 100644 --- a/psutil/_pswindows.py +++ b/psutil/_pswindows.py @@ -299,6 +299,15 @@ def cpu_stats(): syscalls) +def cpu_freq(): + """Return CPU frequency. + On Windows per-cpu frequency is not supported. + """ + curr, max_ = cext.cpu_freq() + min_ = 0.0 + return [_common.scpufreq(float(curr), min_, float(max_))] + + # ===================================================================== # --- network # ===================================================================== diff --git a/psutil/tests/test_memory_leaks.py b/psutil/tests/test_memory_leaks.py index 46186e41..f1a951f0 100755 --- a/psutil/tests/test_memory_leaks.py +++ b/psutil/tests/test_memory_leaks.py @@ -458,6 +458,11 @@ class TestModuleFunctionsLeaks(TestMemLeak): def test_cpu_stats(self): self.execute(psutil.cpu_stats) + @skip_if_linux() + @unittest.skipUnless(hasattr(psutil, "cpu_freq"), "platform not supported") + def test_cpu_freq(self): + self.execute(psutil.cpu_freq) + # --- mem def test_virtual_memory(self): diff --git a/psutil/tests/test_osx.py b/psutil/tests/test_osx.py index 7b61bc74..6e7a5891 100755 --- a/psutil/tests/test_osx.py +++ b/psutil/tests/test_osx.py @@ -111,6 +111,8 @@ class TestProcess(unittest.TestCase): @unittest.skipUnless(OSX, "OSX only") class TestSystemAPIs(unittest.TestCase): + # --- disk + def test_disks(self): # test psutil.disk_usage() and psutil.disk_partitions() # against "df -a" @@ -138,6 +140,8 @@ class TestSystemAPIs(unittest.TestCase): if abs(usage.used - used) > 10 * 1024 * 1024: self.fail("psutil=%s, df=%s" % usage.used, used) + # --- cpu + def test_cpu_count_logical(self): num = sysctl("sysctl hw.logicalcpu") self.assertEqual(num, psutil.cpu_count(logical=True)) @@ -146,6 +150,15 @@ class TestSystemAPIs(unittest.TestCase): num = sysctl("sysctl hw.physicalcpu") self.assertEqual(num, psutil.cpu_count(logical=False)) + def test_cpu_freq(self): + freq = psutil.cpu_freq()[0] + self.assertEqual( + freq.current * 1000 * 1000, sysctl("sysctl hw.cpufrequency")) + self.assertEqual( + freq.min * 1000 * 1000, sysctl("sysctl hw.cpufrequency_min")) + self.assertEqual( + freq.max * 1000 * 1000, sysctl("sysctl hw.cpufrequency_max")) + # --- virtual mem def test_vmem_total(self): @@ -206,6 +219,8 @@ class TestSystemAPIs(unittest.TestCase): # self.assertEqual(psutil_smem.used, human2bytes(used)) # self.assertEqual(psutil_smem.free, human2bytes(free)) + # --- network + def test_net_if_stats(self): for name, stats in psutil.net_if_stats().items(): try: diff --git a/psutil/tests/test_system.py b/psutil/tests/test_system.py index e2220be4..4cbdb056 100755 --- a/psutil/tests/test_system.py +++ b/psutil/tests/test_system.py @@ -697,6 +697,25 @@ class TestSystemAPIs(unittest.TestCase): if name in ('ctx_switches', 'interrupts'): self.assertGreater(value, 0) + @unittest.skipUnless(hasattr(psutil, "cpu_freq"), + "platform not suported") + def test_cpu_freq(self): + def check_ls(ls): + for nt in ls: + self.assertLessEqual(nt.current, nt.max) + for name in nt._fields: + value = getattr(nt, name) + self.assertGreaterEqual(value, 0) + + ls = psutil.cpu_freq(percpu=True) + if not TRAVIS: + assert ls, ls + + check_ls([psutil.cpu_freq(percpu=False)]) + + if LINUX: + self.assertEqual(len(ls), psutil.cpu_count()) + def test_os_constants(self): names = ["POSIX", "WINDOWS", "LINUX", "OSX", "FREEBSD", "OPENBSD", "NETBSD", "BSD", "SUNOS"] diff --git a/psutil/tests/test_windows.py b/psutil/tests/test_windows.py index 802242b5..07f1d796 100755 --- a/psutil/tests/test_windows.py +++ b/psutil/tests/test_windows.py @@ -85,6 +85,12 @@ class TestSystemAPIs(unittest.TestCase): num_cpus = int(os.environ['NUMBER_OF_PROCESSORS']) self.assertEqual(num_cpus, psutil.cpu_count()) + def test_cpu_freq(self): + w = wmi.WMI() + proc = w.Win32_Processor()[0] + self.assertEqual(proc.CurrentClockSpeed, psutil.cpu_freq().current) + self.assertEqual(proc.MaxClockSpeed, psutil.cpu_freq().max) + def test_total_phymem(self): w = wmi.WMI().Win32_ComputerSystem()[0] self.assertEqual(int(w.TotalPhysicalMemory), @@ -122,7 +122,7 @@ if WINDOWS: define_macros=macros, libraries=[ "psapi", "kernel32", "advapi32", "shell32", "netapi32", - "iphlpapi", "wtsapi32", "ws2_32", + "iphlpapi", "wtsapi32", "ws2_32", "PowrProf", ], # extra_compile_args=["/Z7"], # extra_link_args=["/DEBUG"] |