summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGiampaolo Rodola <g.rodola@gmail.com>2017-01-24 16:03:49 +0100
committerGiampaolo Rodola <g.rodola@gmail.com>2017-01-24 16:03:49 +0100
commit276c0bc05d8e45c9e80361011eed77654a438ee4 (patch)
treefdbf7ec2a16e37bf1f03f08175eb204a466c39a7
parent74583b34d0b2bf749153400ded22f0d1d618ed4d (diff)
parent232b6910b0c14ff2c91da44f39bc20158b68e999 (diff)
downloadpsutil-276c0bc05d8e45c9e80361011eed77654a438ee4.tar.gz
merge from master
-rw-r--r--CREDITS5
-rw-r--r--HISTORY.rst9
-rw-r--r--IDEAS2
-rw-r--r--README.rst5
-rw-r--r--docs/index.rst73
-rw-r--r--psutil/__init__.py56
-rw-r--r--psutil/_common.py3
-rw-r--r--psutil/_psbsd.py8
-rw-r--r--psutil/_pslinux.py39
-rw-r--r--psutil/_psosx.py10
-rw-r--r--psutil/_pssunos.py4
-rw-r--r--psutil/_psutil_bsd.c23
-rw-r--r--psutil/_psutil_osx.c31
-rw-r--r--psutil/_psutil_sunos.c82
-rw-r--r--psutil/_psutil_windows.c67
-rw-r--r--psutil/_pswindows.py9
-rwxr-xr-xpsutil/tests/test_memory_leaks.py11
-rwxr-xr-xpsutil/tests/test_osx.py15
-rwxr-xr-xpsutil/tests/test_process.py23
-rwxr-xr-xpsutil/tests/test_sunos.py4
-rwxr-xr-xpsutil/tests/test_system.py19
-rwxr-xr-xpsutil/tests/test_windows.py6
-rwxr-xr-xscripts/cpu_distribution.py102
-rwxr-xr-xscripts/internal/bench_oneshot.py7
-rwxr-xr-xscripts/internal/winmake.py4
-rwxr-xr-xscripts/procinfo.py2
-rwxr-xr-xsetup.py2
27 files changed, 587 insertions, 34 deletions
diff --git a/CREDITS b/CREDITS
index ccc4515f..031548ae 100644
--- a/CREDITS
+++ b/CREDITS
@@ -420,3 +420,8 @@ I: 919
N: Max BĂ©langer
W: https://github.com/maxbelanger
I: 936
+
+N: Pierre Fersing
+C: France
+E: pierre.fersing@bleemeo.com
+I: 950
diff --git a/HISTORY.rst b/HISTORY.rst
index 92413ec1..755a6d7f 100644
--- a/HISTORY.rst
+++ b/HISTORY.rst
@@ -1,15 +1,22 @@
*Bug tracker at https://github.com/giampaolo/psutil/issues*
-5.0.2
+5.1.0
=====
*XXXX-XX-XX*
+**Enhancements**
+
+- 357_: added psutil.Process.cpu_num() (what CPU a process is on).
+- 941_: added psutil.cpu_freq() (CPU frequency).
+
**Bug fixes**
- 687_: [Linux] pid_exists() no longer returns True if passed a process thread
ID.
- 948_: cannot install psutil with PYTHONOPTIMIZE=2.
+- 950_: [Windows] Process.cpu_percent() was calculated incorrectly and showed
+ higher number than real usage.
5.0.1
diff --git a/IDEAS b/IDEAS
index 015b5fff..247b8b38 100644
--- a/IDEAS
+++ b/IDEAS
@@ -79,8 +79,6 @@ FEATURES
- Number of system threads.
- Windows: http://msdn.microsoft.com/en-us/library/windows/desktop/ms684824(v=vs.85).aspx
-- #357: what CPU a process is on.
-
- Doc / wiki which compares similarities between UNIX cli tools and psutil.
Example:
```
diff --git a/README.rst b/README.rst
index d413f1e8..6d9a1d93 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
======
@@ -246,6 +249,8 @@ Process management
>>> p.cpu_affinity()
[0, 1, 2, 3]
>>> p.cpu_affinity([0]) # set
+ >>> p.cpu_num()
+ 2
>>>
>>> p.memory_info()
pmem(rss=10915840, vms=67608576, shared=3313664, text=2310144, lib=0, data=7262208, dirty=0)
diff --git a/docs/index.rst b/docs/index.rst
index 270124b7..0131b56a 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
------
@@ -823,39 +850,41 @@ Process class
+------------------------------+-------------------------------+------------------------------+------------------------------+--------------------------+
| Linux | Windows | OSX | BSD | SunOS |
+==============================+===============================+==============================+==============================+==========================+
- | :meth:`~Process.cpu_percent` | :meth:`~Process.cpu_percent` | :meth:`~Process.cpu_percent` | :meth:`~Process.cpu_percent` | :meth:`name` |
+ | :meth:`cpu_num` | :meth:`~Process.cpu_percent` | :meth:`~Process.cpu_percent` | :meth:`cpu_num` | :meth:`name` |
+------------------------------+-------------------------------+------------------------------+------------------------------+--------------------------+
- | :meth:`~Process.cpu_times` | :meth:`~Process.cpu_times` | :meth:`~Process.cpu_times` | :meth:`~Process.cpu_times` | :meth:`cmdline` |
+ | :meth:`~Process.cpu_percent` | :meth:`~Process.cpu_times` | :meth:`~Process.cpu_times` | :meth:`~Process.cpu_percent` | :meth:`cmdline` |
+------------------------------+-------------------------------+------------------------------+------------------------------+--------------------------+
- | :meth:`create_time` | :meth:`io_counters()` | :meth:`memory_info` | :meth:`create_time` | :meth:`create_time` |
+ | :meth:`~Process.cpu_times` | :meth:`io_counters()` | :meth:`memory_info` | :meth:`~Process.cpu_times` | :meth:`create_time` |
+------------------------------+-------------------------------+------------------------------+------------------------------+--------------------------+
- | :meth:`name` | :meth:`ionice` | :meth:`memory_percent` | :meth:`gids` | |
+ | :meth:`create_time` | :meth:`ionice` | :meth:`memory_percent` | :meth:`create_time` | |
+------------------------------+-------------------------------+------------------------------+------------------------------+--------------------------+
- | :meth:`ppid` | :meth:`memory_info` | :meth:`num_ctx_switches` | :meth:`io_counters` | :meth:`memory_info` |
+ | :meth:`name` | :meth:`memory_info` | :meth:`num_ctx_switches` | :meth:`gids` | :meth:`memory_info` |
+------------------------------+-------------------------------+------------------------------+------------------------------+--------------------------+
- | :meth:`status` | :meth:`nice` | :meth:`num_threads` | :meth:`name` | :meth:`memory_percent` |
+ | :meth:`ppid` | :meth:`nice` | :meth:`num_threads` | :meth:`io_counters` | :meth:`memory_percent` |
+------------------------------+-------------------------------+------------------------------+------------------------------+--------------------------+
- | :meth:`terminal` | :meth:`memory_maps` | | :meth:`memory_info` | :meth:`nice` |
+ | :meth:`status` | :meth:`memory_maps` | | :meth:`name` | :meth:`nice` |
+------------------------------+-------------------------------+------------------------------+------------------------------+--------------------------+
- | | :meth:`num_ctx_switches` | :meth:`create_time` | :meth:`memory_percent` | :meth:`num_threads` |
+ | :meth:`terminal` | :meth:`num_ctx_switches` | :meth:`create_time` | :meth:`memory_info` | :meth:`num_threads` |
+------------------------------+-------------------------------+------------------------------+------------------------------+--------------------------+
- | :meth:`gids` | :meth:`num_handles` | :meth:`gids` | :meth:`num_ctx_switches` | :meth:`ppid` |
+ | | :meth:`num_handles` | :meth:`gids` | :meth:`memory_percent` | :meth:`ppid` |
+------------------------------+-------------------------------+------------------------------+------------------------------+--------------------------+
- | :meth:`num_ctx_switches` | :meth:`num_threads` | :meth:`name` | :meth:`ppid` | :meth:`status` |
+ | :meth:`gids` | :meth:`num_threads` | :meth:`name` | :meth:`num_ctx_switches` | :meth:`status` |
+------------------------------+-------------------------------+------------------------------+------------------------------+--------------------------+
- | :meth:`num_threads` | :meth:`username` | :meth:`ppid` | :meth:`status` | :meth:`terminal` |
+ | :meth:`num_ctx_switches` | :meth:`username` | :meth:`ppid` | :meth:`ppid` | :meth:`terminal` |
+------------------------------+-------------------------------+------------------------------+------------------------------+--------------------------+
- | :meth:`uids` | | :meth:`status` | :meth:`terminal` | |
+ | :meth:`num_threads` | | :meth:`status` | :meth:`status` | |
+------------------------------+-------------------------------+------------------------------+------------------------------+--------------------------+
- | :meth:`username` | | :meth:`terminal` | :meth:`uids` | :meth:`gids` |
+ | :meth:`uids` | | :meth:`terminal` | :meth:`terminal` | :meth:`gids` |
+------------------------------+-------------------------------+------------------------------+------------------------------+--------------------------+
- | | | :meth:`uids` | :meth:`username` | :meth:`uids` |
+ | :meth:`username` | | :meth:`uids` | :meth:`uids` | :meth:`uids` |
+------------------------------+-------------------------------+------------------------------+------------------------------+--------------------------+
- | :meth:`memory_full_info` | | :meth:`username` | | :meth:`username` |
+ | | | :meth:`username` | :meth:`username` | :meth:`username` |
+ +------------------------------+-------------------------------+------------------------------+------------------------------+--------------------------+
+ | :meth:`memory_full_info` | | | | |
+------------------------------+-------------------------------+------------------------------+------------------------------+--------------------------+
| :meth:`memory_maps` | | | | |
+------------------------------+-------------------------------+------------------------------+------------------------------+--------------------------+
- | *speedup: +2.5x* | *speedup: +1.8x / +6.5x* | *speedup: +1.9x* | *speedup: +2.0x* | *speedup: +1.3x* |
+ | *speedup: +2.6x* | *speedup: +1.8x / +6.5x* | *speedup: +1.9x* | *speedup: +2.0x* | *speedup: +1.3x* |
+------------------------------+-------------------------------+------------------------------+------------------------------+--------------------------+
.. versionadded:: 5.0.0
@@ -1212,6 +1241,18 @@ Process class
.. versionchanged:: 2.2.0 added support for FreeBSD
+ .. method:: cpu_num()
+
+ Return what CPU this process is currently running on.
+ The returned number should be ``<=`` :func:`psutil.cpu_count()`.
+ It may be used in conjunction with ``psutil.cpu_percent(percpu=True)`` to
+ observe the system workload distributed across multiple CPUs as shown by
+ `cpu_workload.py <https://github.com/giampaolo/psutil/blob/master/scripts/cpu_workload.py>`__ example script.
+
+ Availability: Linux, FreeBSD, SunOS
+
+ .. versionadded:: 5.1.0
+
.. method:: memory_info()
Return a namedtuple with variable fields depending on the platform
diff --git a/psutil/__init__.py b/psutil/__init__.py
index 0f3960c5..7d6b660c 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
@@ -189,7 +189,7 @@ __all__ = [
]
__all__.extend(_psplatform.__extra__all__)
__author__ = "Giampaolo Rodola'"
-__version__ = "5.0.2"
+__version__ = "5.1.0"
version_info = tuple([int(num) for num in __version__.split('.')])
AF_LINK = _psplatform.AF_LINK
_TOTAL_PHYMEM = None
@@ -837,6 +837,19 @@ class Process(object):
else:
self._proc.cpu_affinity_set(list(set(cpus)))
+ # Linux, FreeBSD, SunOS
+ if hasattr(_psplatform.Process, "cpu_num"):
+
+ def cpu_num(self):
+ """Return what CPU this process is currently running on.
+ The returned number should be <= psutil.cpu_count()
+ and <= len(psutil.cpu_percent(percpu=True)).
+ It may be used in conjunction with
+ psutil.cpu_percent(percpu=True) to observe the system
+ workload distributed across CPUs.
+ """
+ return self._proc.cpu_num()
+
# Linux, OSX and Windows only
if hasattr(_psplatform.Process, "environ"):
@@ -1007,13 +1020,8 @@ class Process(object):
raise ValueError("interval is not positive (got %r)" % interval)
num_cpus = cpu_count() or 1
- if POSIX:
- def timer():
- return _timer() * num_cpus
- else:
- def timer():
- t = cpu_times()
- return sum((t.user, t.system))
+ def timer():
+ return _timer() * num_cpus
if blocking:
st1 = timer()
@@ -1857,6 +1865,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 3e5f07f5..fd8bc186 100644
--- a/psutil/_common.py
+++ b/psutil/_common.py
@@ -156,6 +156,9 @@ 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'])
+# psutil.temperatures()
shwtemp = namedtuple(
'shwtemp', ['name', 'label', 'current', 'high', 'critical'])
diff --git a/psutil/_psbsd.py b/psutil/_psbsd.py
index acf6c7c3..022f5758 100644
--- a/psutil/_psbsd.py
+++ b/psutil/_psbsd.py
@@ -122,7 +122,8 @@ kinfo_proc_map = dict(
memtext=20,
memdata=21,
memstack=22,
- name=23,
+ cpunum=23,
+ name=24,
)
@@ -609,6 +610,11 @@ class Process(object):
rawtuple[kinfo_proc_map['ch_user_time']],
rawtuple[kinfo_proc_map['ch_sys_time']])
+ if FREEBSD:
+ @wrap_exceptions
+ def cpu_num(self):
+ return self.oneshot()[kinfo_proc_map['cpunum']]
+
@wrap_exceptions
def memory_info(self):
rawtuple = self.oneshot()
diff --git a/psutil/_pslinux.py b/psutil/_pslinux.py
index 2dc5b4ef..bc72a502 100644
--- a/psutil/_pslinux.py
+++ b/psutil/_pslinux.py
@@ -135,6 +135,8 @@ TCP_STATUSES = {
"0B": _common.CONN_CLOSING
}
+_DEFAULT = object()
+
# =====================================================================
# -- exceptions
@@ -278,6 +280,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:
@@ -609,6 +623,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
# =====================================================================
@@ -1316,6 +1350,11 @@ class Process(object):
return _common.pcputimes(utime, stime, children_utime, children_stime)
@wrap_exceptions
+ def cpu_num(self):
+ """What CPU the process is on."""
+ return int(self._parse_stat_file()[37])
+
+ @wrap_exceptions
def wait(self, timeout=None):
try:
return _psposix.wait_pid(self.pid, timeout)
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/_pssunos.py b/psutil/_pssunos.py
index a62e0bf5..e6796bf9 100644
--- a/psutil/_pssunos.py
+++ b/psutil/_pssunos.py
@@ -469,6 +469,10 @@ class Process(object):
return _common.pcputimes(*times)
@wrap_exceptions
+ def cpu_num(self):
+ return cext.proc_cpu_num(self.pid, self._procfs_path)
+
+ @wrap_exceptions
def terminal(self):
procfs_path = self._procfs_path
hit_enoent = False
diff --git a/psutil/_psutil_bsd.c b/psutil/_psutil_bsd.c
index afe3834e..adcedf79 100644
--- a/psutil/_psutil_bsd.c
+++ b/psutil/_psutil_bsd.c
@@ -198,6 +198,7 @@ psutil_proc_oneshot_info(PyObject *self, PyObject *args) {
long memtext;
long memdata;
long memstack;
+ unsigned char oncpu;
kinfo_proc kp;
long pagesize = sysconf(_SC_PAGESIZE);
char str[1000];
@@ -252,9 +253,25 @@ psutil_proc_oneshot_info(PyObject *self, PyObject *args) {
memstack = (long)kp.p_vm_ssize * pagesize;
#endif
+#ifdef PSUTIL_FREEBSD
+ // what CPU we're on; top was used as an example:
+ // https://svnweb.freebsd.org/base/head/usr.bin/top/machine.c?
+ // view=markup&pathrev=273835
+ if (kp.ki_stat == SRUN && kp.ki_oncpu != NOCPU)
+ oncpu = kp.ki_oncpu;
+ else
+ oncpu = kp.ki_lastcpu;
+#else
+ // On Net/OpenBSD we have kp.p_cpuid but it appears it's always
+ // set to KI_NOCPU. Even if it's not, ki_lastcpu does not exist
+ // so there's no way to determine where "sleeping" processes
+ // were. Not supported.
+ oncpu = -1;
+#endif
+
// Return a single big tuple with all process info.
py_retlist = Py_BuildValue(
- "(lillllllidllllddddlllllO)",
+ "(lillllllidllllddddlllllbO)",
#ifdef PSUTIL_FREEBSD
//
(long)kp.ki_ppid, // (long) ppid
@@ -287,6 +304,8 @@ psutil_proc_oneshot_info(PyObject *self, PyObject *args) {
memtext, // (long) mem text
memdata, // (long) mem data
memstack, // (long) mem stack
+ // others
+ oncpu, // (unsigned char) the CPU we are on
#elif defined(PSUTIL_OPENBSD) || defined(PSUTIL_NETBSD)
//
(long)kp.p_ppid, // (long) ppid
@@ -321,6 +340,8 @@ psutil_proc_oneshot_info(PyObject *self, PyObject *args) {
memtext, // (long) mem text
memdata, // (long) mem data
memstack, // (long) mem stack
+ // others
+ oncpu, // (unsigned char) the CPU we are on
#endif
py_name // (pystr) name
);
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_sunos.c b/psutil/_psutil_sunos.c
index e98ff7f2..48767add 100644
--- a/psutil/_psutil_sunos.c
+++ b/psutil/_psutil_sunos.c
@@ -169,6 +169,86 @@ psutil_proc_cpu_times(PyObject *self, PyObject *args) {
/*
+ * Return what CPU the process is running on.
+ */
+static PyObject *
+psutil_proc_cpu_num(PyObject *self, PyObject *args) {
+ int fd = NULL;
+ int pid;
+ char path[1000];
+ struct prheader header;
+ struct lwpsinfo *lwp;
+ char *lpsinfo = NULL;
+ char *ptr = NULL;
+ int nent;
+ int size;
+ int proc_num;
+ size_t nbytes;
+ const char *procfs_path;
+
+ if (! PyArg_ParseTuple(args, "is", &pid, &procfs_path))
+ return NULL;
+
+ sprintf(path, "%s/%i/lpsinfo", procfs_path, pid);
+ fd = open(path, O_RDONLY);
+ if (fd == -1) {
+ PyErr_SetFromErrnoWithFilename(PyExc_OSError, path);
+ return NULL;
+ }
+
+ // read header
+ nbytes = pread(fd, &header, sizeof(header), 0);
+ if (nbytes == -1) {
+ PyErr_SetFromErrno(PyExc_OSError);
+ goto error;
+ }
+ if (nbytes != sizeof(header)) {
+ PyErr_SetString(
+ PyExc_RuntimeError, "read() file structure size mismatch");
+ goto error;
+ }
+
+ // malloc
+ nent = header.pr_nent;
+ size = header.pr_entsize * nent;
+ ptr = lpsinfo = malloc(size);
+ if (lpsinfo == NULL) {
+ PyErr_NoMemory();
+ goto error;
+ }
+
+ // read the rest
+ nbytes = pread(fd, lpsinfo, size, sizeof(header));
+ if (nbytes == -1) {
+ PyErr_SetFromErrno(PyExc_OSError);
+ goto error;
+ }
+ if (nbytes != size) {
+ PyErr_SetString(
+ PyExc_RuntimeError, "read() file structure size mismatch");
+ goto error;
+ }
+
+ // done
+ lwp = (lwpsinfo_t *)ptr;
+ proc_num = lwp->pr_onpro;
+ close(fd);
+ free(ptr);
+ free(lpsinfo);
+ return Py_BuildValue("i", proc_num);
+
+error:
+ if (fd != NULL)
+ close(fd);
+ if (ptr != NULL)
+ free(ptr);
+ if (lpsinfo != NULL)
+ free(lpsinfo);
+ return NULL;
+}
+
+
+/*
* Return process uids/gids as a Python tuple.
*/
static PyObject *
@@ -1340,6 +1420,8 @@ PsutilMethods[] = {
"Return process memory mappings"},
{"proc_num_ctx_switches", psutil_proc_num_ctx_switches, METH_VARARGS,
"Return the number of context switches performed by process"},
+ {"proc_cpu_num", psutil_proc_cpu_num, METH_VARARGS,
+ "Return what CPU the process is on"},
// --- system-related functions
{"swap_mem", psutil_swap_mem, 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..6f724339 100755
--- a/psutil/tests/test_memory_leaks.py
+++ b/psutil/tests/test_memory_leaks.py
@@ -267,6 +267,12 @@ class TestProcessObjectLeaks(TestMemLeak):
self.execute(self.proc.cpu_times)
@skip_if_linux()
+ @unittest.skipUnless(hasattr(psutil.Process, "cpu_num"),
+ "platform not supported")
+ def test_cpu_num(self):
+ self.execute(self.proc.cpu_num)
+
+ @skip_if_linux()
def test_memory_info(self):
self.execute(self.proc.memory_info)
@@ -458,6 +464,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_process.py b/psutil/tests/test_process.py
index d25f4474..74750473 100755
--- a/psutil/tests/test_process.py
+++ b/psutil/tests/test_process.py
@@ -281,6 +281,16 @@ class TestProcess(unittest.TestCase):
if (max([kernel_time, ktime]) - min([kernel_time, ktime])) > 0.1:
self.fail("expected: %s, found: %s" % (ktime, kernel_time))
+ @unittest.skipUnless(hasattr(psutil.Process, "cpu_num"),
+ "platform not supported")
+ def test_cpu_num(self):
+ p = psutil.Process()
+ num = p.cpu_num()
+ self.assertGreaterEqual(num, 0)
+ if psutil.cpu_count() == 1:
+ self.assertEqual(num, 0)
+ self.assertIn(p.cpu_num(), range(psutil.cpu_count()))
+
def test_create_time(self):
sproc = get_test_subprocess()
now = time.time()
@@ -853,6 +863,10 @@ class TestProcess(unittest.TestCase):
if hasattr(os, "sched_getaffinity"):
self.assertEqual(p.cpu_affinity(),
list(os.sched_getaffinity(p.pid)))
+ # also test num_cpu()
+ if hasattr(p, "num_cpu"):
+ self.assertEqual(p.cpu_affinity()[0], p.num_cpu())
+
#
p.cpu_affinity(all_cpus)
self.assertEqual(p.cpu_affinity(), all_cpus)
@@ -1723,6 +1737,12 @@ class TestFetchAllProcesses(unittest.TestCase):
self.assertTrue(ret.user >= 0)
self.assertTrue(ret.system >= 0)
+ def cpu_num(self, ret, proc):
+ self.assertGreaterEqual(ret, 0)
+ if psutil.cpu_count() == 1:
+ self.assertEqual(ret, 0)
+ self.assertIn(ret, range(psutil.cpu_count()))
+
def memory_info(self, ret, proc):
for name in ret._fields:
self.assertGreaterEqual(getattr(ret, name), 0)
@@ -1793,6 +1813,9 @@ class TestFetchAllProcesses(unittest.TestCase):
def cpu_affinity(self, ret, proc):
assert ret != [], ret
+ cpus = range(psutil.cpu_count())
+ for n in ret:
+ self.assertIn(n, cpus)
def terminal(self, ret, proc):
if ret is not None:
diff --git a/psutil/tests/test_sunos.py b/psutil/tests/test_sunos.py
index 9694b22b..0e770444 100755
--- a/psutil/tests/test_sunos.py
+++ b/psutil/tests/test_sunos.py
@@ -36,6 +36,10 @@ class SunOSSpecificTestCase(unittest.TestCase):
self.assertEqual(psutil_swap.used, used)
self.assertEqual(psutil_swap.free, free)
+ def test_cpu_count(self):
+ out = sh("/usr/sbin/psrinfo")
+ self.assertEqual(psutil.cpu_count(), len(out.split('\n')))
+
if __name__ == '__main__':
run_test_module_by_name(__file__)
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/scripts/cpu_distribution.py b/scripts/cpu_distribution.py
new file mode 100755
index 00000000..31cdbb86
--- /dev/null
+++ b/scripts/cpu_distribution.py
@@ -0,0 +1,102 @@
+#!/usr/bin/env python
+
+# 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.
+
+"""
+Shows CPU workload split across different CPUs.
+
+$ python scripts/cpu_workload.py
+CPU 0 CPU 1 CPU 2 CPU 3 CPU 4 CPU 5 CPU 6 CPU 7
+19.8 20.6 18.2 15.8 6.9 17.3 5.0 20.4
+gvfsd pytho kwork chrom unity kwork kwork kwork
+chrom chrom indic ibus- whoop nfsd (sd-p gvfsd
+ibus- cat at-sp chrom Modem nfsd4 light upsta
+ibus- iprt- ibus- nacl_ cfg80 kwork nfsd bluet
+chrom irqba gpg-a chrom ext4- biose nfsd dio/n
+chrom acpid bamfd nvidi kwork scsi_ sshd rpc.m
+upsta rsysl dbus- nfsd biose scsi_ ext4- polki
+rtkit avahi upowe Netwo scsi_ biose UVM T irq/9
+light rpcbi snapd cron ipv6_ biose kwork dbus-
+agett kvm-i avahi kwork biose biose scsi_ syste
+nfsd syste rpc.i biose biose kbloc kthro UVM g
+nfsd kwork kwork biose vmsta kwork crypt kaudi
+nfsd scsi_ charg biose md ksoft kwork kwork
+memca biose ksmd ecryp ksoft watch migra nvme
+therm biose kcomp kswap migra cpuhp watch biose
+syste biose kdevt khuge watch cpuhp biose
+led_w devfr kwork write cpuhp biose
+rpcio oom_r ksoft kwork syste biose
+kwork kwork watch migra acpi_
+biose ksoft cpuhp watch watch
+biose migra cpuhp kinte
+biose watch rcu_s netns
+biose cpuhp kthre kwork
+cpuhp ksoft
+watch migra
+rcu_b cpuhp
+kwork
+"""
+
+from __future__ import print_function
+import collections
+import os
+import sys
+import time
+
+import psutil
+
+
+if not hasattr(psutil.Process, "cpu_num"):
+ sys.exit("platform not supported")
+
+
+def clean_screen():
+ if psutil.POSIX:
+ os.system('clear')
+ else:
+ os.system('cls')
+
+
+def main():
+ total = psutil.cpu_count()
+ while True:
+ # header
+ clean_screen()
+ cpus_percent = psutil.cpu_percent(percpu=True)
+ for i in range(total):
+ print("CPU %-6i" % i, end="")
+ print()
+ for percent in cpus_percent:
+ print("%-10s" % percent, end="")
+ print()
+
+ # processes
+ procs = collections.defaultdict(list)
+ for p in psutil.process_iter():
+ try:
+ name = p.name()[:5]
+ cpunum = p.cpu_num()
+ except psutil.Error:
+ continue
+ else:
+ procs[cpunum].append(name)
+
+ end_marker = [[] for x in range(total)]
+ while True:
+ for num in range(total):
+ try:
+ pname = procs[num].pop()
+ except IndexError:
+ pname = ""
+ print("%-10s" % pname[:10], end="")
+ print()
+ if procs.values() == end_marker:
+ break
+
+ time.sleep(1)
+
+
+if __name__ == '__main__':
+ main()
diff --git a/scripts/internal/bench_oneshot.py b/scripts/internal/bench_oneshot.py
index cf8497f8..639e9ad7 100755
--- a/scripts/internal/bench_oneshot.py
+++ b/scripts/internal/bench_oneshot.py
@@ -37,10 +37,11 @@ if psutil.POSIX:
if psutil.LINUX:
names += [
- 'cpu_times',
- 'gids',
# 'memory_full_info',
# 'memory_maps',
+ 'cpu_num',
+ 'cpu_times',
+ 'gids',
'name',
'num_ctx_switches',
'num_threads',
@@ -63,6 +64,8 @@ elif psutil.BSD:
'terminal',
'uids',
]
+ if psutil.FREEBSD:
+ names.append('cpu_num')
elif psutil.SUNOS:
names += [
'cmdline',
diff --git a/scripts/internal/winmake.py b/scripts/internal/winmake.py
index e2c1f086..8ce51ed0 100755
--- a/scripts/internal/winmake.py
+++ b/scripts/internal/winmake.py
@@ -225,11 +225,12 @@ def install():
@cmd
def uninstall():
"""Uninstall psutil"""
- clean()
try:
import psutil
except ImportError:
+ clean()
return
+ clean()
install_pip()
sh("%s -m pip uninstall -y psutil" % PYTHON)
@@ -244,6 +245,7 @@ def uninstall():
try:
import psutil # NOQA
except ImportError:
+ clean()
return
sh("%s -m pip uninstall -y psutil" % PYTHON)
finally:
diff --git a/scripts/procinfo.py b/scripts/procinfo.py
index 8dc34c45..d8625560 100755
--- a/scripts/procinfo.py
+++ b/scripts/procinfo.py
@@ -190,6 +190,8 @@ def run(pid, verbose=False):
print_('cpu-times', str_ntuple(pinfo['cpu_times']))
if hasattr(proc, "cpu_affinity"):
print_("cpu-affinity", pinfo["cpu_affinity"])
+ if hasattr(proc, "cpu_num"):
+ print_("cpu-num", pinfo["cpu_num"])
print_('memory', str_ntuple(pinfo['memory_info'], bytes2human=True))
print_('memory %', round(pinfo['memory_percent'], 2))
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"]