summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGiampaolo Rodola <g.rodola@gmail.com>2017-01-23 22:56:08 +0100
committerGitHub <noreply@github.com>2017-01-23 22:56:08 +0100
commitf8c07ae1082ee07fd08c25fa163fc8edb31e5b7f (patch)
tree69f2796c4548866a719429694d5fda3ddff1c0bd
parent7ae1bb6341c99081f53e558e312d76a71cbb8abe (diff)
parent1266103171e75ff0e997c93980765259da285192 (diff)
downloadpsutil-f8c07ae1082ee07fd08c25fa163fc8edb31e5b7f.tar.gz
Merge pull request #952 from giampaolo/941-cpu-freq
941 cpu freq
-rw-r--r--README.rst3
-rw-r--r--docs/index.rst27
-rw-r--r--psutil/__init__.py32
-rw-r--r--psutil/_common.py2
-rw-r--r--psutil/_pslinux.py35
-rw-r--r--psutil/_psosx.py10
-rw-r--r--psutil/_psutil_osx.c31
-rw-r--r--psutil/_psutil_windows.c67
-rw-r--r--psutil/_pswindows.py9
-rwxr-xr-xpsutil/tests/test_memory_leaks.py5
-rwxr-xr-xpsutil/tests/test_osx.py15
-rwxr-xr-xpsutil/tests/test_system.py19
-rwxr-xr-xpsutil/tests/test_windows.py6
-rwxr-xr-xsetup.py2
14 files changed, 261 insertions, 2 deletions
diff --git a/README.rst b/README.rst
index d413f1e8..67e5bc92 100644
--- a/README.rst
+++ b/README.rst
@@ -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),
diff --git a/setup.py b/setup.py
index 80521a48..01543bee 100755
--- a/setup.py
+++ b/setup.py
@@ -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"]