diff options
author | Giampaolo Rodola <g.rodola@gmail.com> | 2017-01-24 16:03:49 +0100 |
---|---|---|
committer | Giampaolo Rodola <g.rodola@gmail.com> | 2017-01-24 16:03:49 +0100 |
commit | 276c0bc05d8e45c9e80361011eed77654a438ee4 (patch) | |
tree | fdbf7ec2a16e37bf1f03f08175eb204a466c39a7 | |
parent | 74583b34d0b2bf749153400ded22f0d1d618ed4d (diff) | |
parent | 232b6910b0c14ff2c91da44f39bc20158b68e999 (diff) | |
download | psutil-276c0bc05d8e45c9e80361011eed77654a438ee4.tar.gz |
merge from master
-rw-r--r-- | CREDITS | 5 | ||||
-rw-r--r-- | HISTORY.rst | 9 | ||||
-rw-r--r-- | IDEAS | 2 | ||||
-rw-r--r-- | README.rst | 5 | ||||
-rw-r--r-- | docs/index.rst | 73 | ||||
-rw-r--r-- | psutil/__init__.py | 56 | ||||
-rw-r--r-- | psutil/_common.py | 3 | ||||
-rw-r--r-- | psutil/_psbsd.py | 8 | ||||
-rw-r--r-- | psutil/_pslinux.py | 39 | ||||
-rw-r--r-- | psutil/_psosx.py | 10 | ||||
-rw-r--r-- | psutil/_pssunos.py | 4 | ||||
-rw-r--r-- | psutil/_psutil_bsd.c | 23 | ||||
-rw-r--r-- | psutil/_psutil_osx.c | 31 | ||||
-rw-r--r-- | psutil/_psutil_sunos.c | 82 | ||||
-rw-r--r-- | psutil/_psutil_windows.c | 67 | ||||
-rw-r--r-- | psutil/_pswindows.py | 9 | ||||
-rwxr-xr-x | psutil/tests/test_memory_leaks.py | 11 | ||||
-rwxr-xr-x | psutil/tests/test_osx.py | 15 | ||||
-rwxr-xr-x | psutil/tests/test_process.py | 23 | ||||
-rwxr-xr-x | psutil/tests/test_sunos.py | 4 | ||||
-rwxr-xr-x | psutil/tests/test_system.py | 19 | ||||
-rwxr-xr-x | psutil/tests/test_windows.py | 6 | ||||
-rwxr-xr-x | scripts/cpu_distribution.py | 102 | ||||
-rwxr-xr-x | scripts/internal/bench_oneshot.py | 7 | ||||
-rwxr-xr-x | scripts/internal/winmake.py | 4 | ||||
-rwxr-xr-x | scripts/procinfo.py | 2 | ||||
-rwxr-xr-x | setup.py | 2 |
27 files changed, 587 insertions, 34 deletions
@@ -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 @@ -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: ``` @@ -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)) @@ -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"] |