summaryrefslogtreecommitdiff
path: root/psutil
diff options
context:
space:
mode:
Diffstat (limited to 'psutil')
-rw-r--r--psutil/__init__.py57
-rw-r--r--psutil/_common.py12
-rw-r--r--psutil/_psbsd.py6
-rw-r--r--psutil/_pslinux.py128
-rw-r--r--psutil/_pssunos.py4
-rw-r--r--psutil/_psutil_sunos.c8
-rw-r--r--psutil/_psutil_windows.c17
-rw-r--r--psutil/arch/windows/process_info.c5
8 files changed, 152 insertions, 85 deletions
diff --git a/psutil/__init__.py b/psutil/__init__.py
index 608db167..7a3fb066 100644
--- a/psutil/__init__.py
+++ b/psutil/__init__.py
@@ -158,7 +158,7 @@ __all__ = [
]
__all__.extend(_psplatform.__extra__all__)
__author__ = "Giampaolo Rodola'"
-__version__ = "3.0.0"
+__version__ = "3.0.2"
version_info = tuple([int(num) for num in __version__.split('.')])
AF_LINK = _psplatform.AF_LINK
_TOTAL_PHYMEM = None
@@ -190,6 +190,12 @@ class Error(Exception):
from this one.
"""
+ def __init__(self, msg=""):
+ self.msg = msg
+
+ def __str__(self):
+ return self.msg
+
class NoSuchProcess(Error):
"""Exception raised when a process with a certain PID doesn't
@@ -197,7 +203,7 @@ class NoSuchProcess(Error):
"""
def __init__(self, pid, name=None, msg=None):
- Error.__init__(self)
+ Error.__init__(self, msg)
self.pid = pid
self.name = name
self.msg = msg
@@ -208,9 +214,6 @@ class NoSuchProcess(Error):
details = "(pid=%s)" % self.pid
self.msg = "process no longer exists " + details
- def __str__(self):
- return self.msg
-
class ZombieProcess(NoSuchProcess):
"""Exception raised when querying a zombie process. This is
@@ -221,7 +224,7 @@ class ZombieProcess(NoSuchProcess):
"""
def __init__(self, pid, name=None, ppid=None, msg=None):
- Error.__init__(self)
+ Error.__init__(self, msg)
self.pid = pid
self.ppid = ppid
self.name = name
@@ -236,15 +239,12 @@ class ZombieProcess(NoSuchProcess):
details = "(pid=%s)" % self.pid
self.msg = "process still exists but it's a zombie " + details
- def __str__(self):
- return self.msg
-
class AccessDenied(Error):
"""Exception raised when permission to perform an action is denied."""
def __init__(self, pid=None, name=None, msg=None):
- Error.__init__(self)
+ Error.__init__(self, msg)
self.pid = pid
self.name = name
self.msg = msg
@@ -256,9 +256,6 @@ class AccessDenied(Error):
else:
self.msg = ""
- def __str__(self):
- return self.msg
-
class TimeoutExpired(Error):
"""Raised on Process.wait(timeout) if timeout expires and process
@@ -266,18 +263,15 @@ class TimeoutExpired(Error):
"""
def __init__(self, seconds, pid=None, name=None):
- Error.__init__(self)
+ Error.__init__(self, "timeout after %s seconds" % seconds)
self.seconds = seconds
self.pid = pid
self.name = name
- self.msg = "timeout after %s seconds" % seconds
if (pid is not None) and (name is not None):
self.msg += " (pid=%s, name=%s)" % (pid, repr(name))
elif (pid is not None):
self.msg += " (pid=%s)" % self.pid
- def __str__(self):
- return self.msg
# push exception classes into platform specific module namespace
_psplatform.NoSuchProcess = NoSuchProcess
@@ -290,6 +284,7 @@ _psplatform.TimeoutExpired = TimeoutExpired
# --- Process class
# =====================================================================
+
def _assert_pid_not_reused(fun):
"""Decorator which raises NoSuchProcess in case a process is no
longer running or its PID has been reused.
@@ -686,7 +681,7 @@ class Process(object):
"""
if ioclass is None:
if value is not None:
- raise ValueError("'ioclass' must be specified")
+ raise ValueError("'ioclass' argument must be specified")
return self._proc.ionice_get()
else:
return self._proc.ionice_set(ioclass, value)
@@ -1012,10 +1007,12 @@ class Process(object):
if _POSIX:
def _send_signal(self, sig):
- # XXX: according to "man 2 kill" PID 0 has a special
- # meaning as it refers to <<every process in the process
- # group of the calling process>>, so should we prevent
- # it here?
+ if self.pid == 0:
+ # see "man 2 kill"
+ raise ValueError(
+ "preventing sending signal to process with PID 0 as it "
+ "would affect every process in the process group of the "
+ "calling process (os.getpid()) instead of PID 0")
try:
os.kill(self.pid, sig)
except OSError as err:
@@ -1105,6 +1102,7 @@ class Process(object):
# --- Popen class
# =====================================================================
+
class Popen(Process):
"""A more convenient interface to stdlib subprocess module.
It starts a sub process and deals with it exactly as when using
@@ -1172,6 +1170,7 @@ class Popen(Process):
# --- system processes related functions
# =====================================================================
+
def pids():
"""Return a list of current running PIDs."""
return _psplatform.pids()
@@ -1338,13 +1337,14 @@ def wait_procs(procs, timeout=None, callback=None):
# --- CPU related functions
# =====================================================================
+
@memoize
def cpu_count(logical=True):
"""Return the number of logical CPUs in the system (same as
os.cpu_count() in Python 3.4).
If logical is False return the number of physical cores only
- (hyper thread CPUs are excluded).
+ (e.g. hyper thread CPUs are excluded).
Return None if undetermined.
@@ -1548,6 +1548,7 @@ def cpu_times_percent(interval=None, percpu=False):
# --- system memory related functions
# =====================================================================
+
def virtual_memory():
"""Return statistics about system memory usage as a namedtuple
including the following fields, expressed in bytes:
@@ -1628,6 +1629,7 @@ def swap_memory():
# --- disks/paritions related functions
# =====================================================================
+
def disk_usage(path):
"""Return disk usage statistics about the given path as a namedtuple
including total, used and free space expressed in bytes plus the
@@ -1682,6 +1684,7 @@ def disk_io_counters(perdisk=False):
# --- network related functions
# =====================================================================
+
def net_io_counters(pernic=False):
"""Return network I/O statistics as a namedtuple including
the following fields:
@@ -1852,14 +1855,6 @@ def test():
time.localtime(sum(pinfo['cpu_times'])))
try:
user = p.username()
- except KeyError:
- if _POSIX:
- if pinfo['uids']:
- user = str(pinfo['uids'].real)
- else:
- user = ''
- else:
- raise
except Error:
user = ''
if _WINDOWS and '\\' in user:
diff --git a/psutil/_common.py b/psutil/_common.py
index 132d9d59..e9acf595 100644
--- a/psutil/_common.py
+++ b/psutil/_common.py
@@ -12,17 +12,19 @@ import functools
import os
import socket
import stat
+import sys
+from collections import namedtuple
+from socket import AF_INET, SOCK_STREAM, SOCK_DGRAM
try:
import threading
except ImportError:
import dummy_threading as threading
-try:
- import enum # py >= 3.4
-except ImportError:
+
+if sys.version_info >= (3, 4):
+ import enum
+else:
enum = None
-from collections import namedtuple
-from socket import AF_INET, SOCK_STREAM, SOCK_DGRAM
# --- constants
diff --git a/psutil/_psbsd.py b/psutil/_psbsd.py
index 3ce5fd1c..db54a02e 100644
--- a/psutil/_psbsd.py
+++ b/psutil/_psbsd.py
@@ -143,7 +143,11 @@ def cpu_count_physical():
if index != -1:
s = s[:index + 9]
root = ET.fromstring(s)
- ret = len(root.findall('group/children/group/cpu')) or None
+ try:
+ ret = len(root.findall('group/children/group/cpu')) or None
+ finally:
+ # needed otherwise it will memleak
+ root.clear()
if not ret:
# If logical CPUs are 1 it's obvious we'll have only 1
# physical CPU.
diff --git a/psutil/_pslinux.py b/psutil/_pslinux.py
index 40132794..be443eff 100644
--- a/psutil/_pslinux.py
+++ b/psutil/_pslinux.py
@@ -25,7 +25,7 @@ from . import _psutil_linux as cext
from . import _psutil_posix as cext_posix
from ._common import isfile_strict, usage_percent
from ._common import NIC_DUPLEX_FULL, NIC_DUPLEX_HALF, NIC_DUPLEX_UNKNOWN
-from ._compat import PY3
+from ._compat import PY3, long
if sys.version_info >= (3, 4):
import enum
@@ -40,10 +40,7 @@ __extra__all__ = [
# connection status constants
"CONN_ESTABLISHED", "CONN_SYN_SENT", "CONN_SYN_RECV", "CONN_FIN_WAIT1",
"CONN_FIN_WAIT2", "CONN_TIME_WAIT", "CONN_CLOSE", "CONN_CLOSE_WAIT",
- "CONN_LAST_ACK", "CONN_LISTEN", "CONN_CLOSING",
- # other
- "phymem_buffers", "cached_phymem"]
-
+ "CONN_LAST_ACK", "CONN_LISTEN", "CONN_CLOSING", ]
# --- constants
@@ -279,14 +276,28 @@ def cpu_count_logical():
def cpu_count_physical():
- """Return the number of physical CPUs in the system."""
+ """Return the number of physical cores in the system."""
+ mapping = {}
+ current_info = {}
with open('/proc/cpuinfo', 'rb') as f:
- found = set()
for line in f:
- if line.lower().startswith(b'physical id'):
- found.add(line.strip())
+ line = line.strip().lower()
+ if not line:
+ # new section
+ if (b'physical id' in current_info and
+ b'cpu cores' in current_info):
+ mapping[current_info[b'physical id']] = \
+ current_info[b'cpu cores']
+ current_info = {}
+ else:
+ # ongoing section
+ if (line.startswith(b'physical id') or
+ line.startswith(b'cpu cores')):
+ key, value = line.split(b'\t:', 1)
+ current_info[key] = int(value)
+
# mimic os.cpu_count()
- return len(found) if found else None
+ return sum(mapping.values()) or None
# --- other system functions
@@ -318,7 +329,7 @@ def boot_time():
ret = float(line.strip().split()[1])
BOOT_TIME = ret
return ret
- raise RuntimeError("line 'btime' not found")
+ raise RuntimeError("line 'btime' not found in /proc/stat")
# --- processes
@@ -372,9 +383,17 @@ class Connections:
for fd in os.listdir("/proc/%s/fd" % pid):
try:
inode = os.readlink("/proc/%s/fd/%s" % (pid, fd))
- except OSError:
- # TODO: need comment here
- continue
+ except OSError as err:
+ # ENOENT == file which is gone in the meantime;
+ # os.stat('/proc/%s' % self.pid) will be done later
+ # to force NSP (if it's the case)
+ if err.errno in (errno.ENOENT, errno.ESRCH):
+ continue
+ elif err.errno == errno.EINVAL:
+ # not a link
+ continue
+ else:
+ raise
else:
if inode.startswith('socket:['):
# the process is using a socket
@@ -455,8 +474,13 @@ class Connections:
with open(file, 'rt') as f:
f.readline() # skip the first line
for line in f:
- _, laddr, raddr, status, _, _, _, _, _, inode = \
- line.split()[:10]
+ try:
+ _, laddr, raddr, status, _, _, _, _, _, inode = \
+ line.split()[:10]
+ except ValueError:
+ raise RuntimeError(
+ "error while parsing %s; malformed line %r" % (
+ file, line))
if inode in inodes:
# # We assume inet sockets are unique, so we error
# # out if there are multiple references to the
@@ -484,7 +508,12 @@ class Connections:
f.readline() # skip the first line
for line in f:
tokens = line.split()
- _, _, _, _, type_, _, inode = tokens[0:7]
+ try:
+ _, _, _, _, type_, _, inode = tokens[0:7]
+ except ValueError:
+ raise RuntimeError(
+ "error while parsing %s; malformed line %r" % (
+ file, line))
if inode in inodes:
# With UNIX sockets we can have a single inode
# referencing many file descriptors.
@@ -716,13 +745,14 @@ class Process(object):
fname = "/proc/%s/stat" % self.pid
kw = dict(encoding=DEFAULT_ENCODING) if PY3 else dict()
with open(fname, "rt", **kw) as f:
- # XXX - gets changed later and probably needs refactoring
- return f.read().split(' ')[1].replace('(', '').replace(')', '')
+ data = f.read()
+ # XXX - gets changed later and probably needs refactoring
+ return data[data.find('(') + 1:data.rfind(')')]
def exe(self):
try:
exe = os.readlink("/proc/%s/exe" % self.pid)
- except (OSError, IOError) as err:
+ except OSError as err:
if err.errno in (errno.ENOENT, errno.ESRCH):
# no such file error; might be raised also if the
# path actually exists for system processes with
@@ -753,7 +783,10 @@ class Process(object):
fname = "/proc/%s/cmdline" % self.pid
kw = dict(encoding=DEFAULT_ENCODING) if PY3 else dict()
with open(fname, "rt", **kw) as f:
- return [x for x in f.read().split('\x00') if x]
+ data = f.read()
+ if data.endswith('\x00'):
+ data = data[:-1]
+ return [x for x in data.split('\x00')]
@wrap_exceptions
def terminal(self):
@@ -961,7 +994,7 @@ class Process(object):
try:
with open(fname, 'rb') as f:
st = f.read().strip()
- except EnvironmentError as err:
+ except IOError as err:
if err.errno == errno.ENOENT:
# no such file or directory; it means thread
# disappeared on us
@@ -1022,32 +1055,43 @@ class Process(object):
@wrap_exceptions
def ionice_set(self, ioclass, value):
+ if value is not None:
+ if not PY3 and not isinstance(value, (int, long)):
+ msg = "value argument is not an integer (gor %r)" % value
+ raise TypeError(msg)
+ if not 0 <= value <= 8:
+ raise ValueError(
+ "value argument range expected is between 0 and 8")
+
if ioclass in (IOPRIO_CLASS_NONE, None):
if value:
- msg = "can't specify value with IOPRIO_CLASS_NONE"
+ msg = "can't specify value with IOPRIO_CLASS_NONE " \
+ "(got %r)" % value
raise ValueError(msg)
ioclass = IOPRIO_CLASS_NONE
value = 0
- if ioclass in (IOPRIO_CLASS_RT, IOPRIO_CLASS_BE):
- if value is None:
- value = 4
elif ioclass == IOPRIO_CLASS_IDLE:
if value:
- msg = "can't specify value with IOPRIO_CLASS_IDLE"
+ msg = "can't specify value with IOPRIO_CLASS_IDLE " \
+ "(got %r)" % value
raise ValueError(msg)
value = 0
+ elif ioclass in (IOPRIO_CLASS_RT, IOPRIO_CLASS_BE):
+ if value is None:
+ # TODO: add comment explaining why this is 4 (?)
+ value = 4
else:
- value = 0
- if not 0 <= value <= 8:
- raise ValueError(
- "value argument range expected is between 0 and 8")
+ # otherwise we would get OSError(EVINAL)
+ raise ValueError("invalid ioclass argument %r" % ioclass)
+
return cext.proc_ioprio_set(self.pid, ioclass, value)
if HAS_PRLIMIT:
@wrap_exceptions
def rlimit(self, resource, limits=None):
- # if pid is 0 prlimit() applies to the calling process and
- # we don't want that
+ # If pid is 0 prlimit() applies to the calling process and
+ # we don't want that. We should never get here though as
+ # PID 0 is not supported on Linux.
if self.pid == 0:
raise ValueError("can't use prlimit() against PID 0 process")
try:
@@ -1058,7 +1102,8 @@ class Process(object):
# set
if len(limits) != 2:
raise ValueError(
- "second argument must be a (soft, hard) tuple")
+ "second argument must be a (soft, hard) tuple, "
+ "got %s" % repr(limits))
soft, hard = limits
cext.linux_prlimit(self.pid, resource, soft, hard)
except OSError as err:
@@ -1126,27 +1171,30 @@ class Process(object):
@wrap_exceptions
def ppid(self):
- with open("/proc/%s/status" % self.pid, 'rb') as f:
+ fpath = "/proc/%s/status" % self.pid
+ with open(fpath, 'rb') as f:
for line in f:
if line.startswith(b"PPid:"):
# PPid: nnnn
return int(line.split()[1])
- raise NotImplementedError("line not found")
+ raise NotImplementedError("line 'PPid' not found in %s" % fpath)
@wrap_exceptions
def uids(self):
- with open("/proc/%s/status" % self.pid, 'rb') as f:
+ fpath = "/proc/%s/status" % self.pid
+ with open(fpath, 'rb') as f:
for line in f:
if line.startswith(b'Uid:'):
_, real, effective, saved, fs = line.split()
return _common.puids(int(real), int(effective), int(saved))
- raise NotImplementedError("line not found")
+ raise NotImplementedError("line 'Uid' not found in %s" % fpath)
@wrap_exceptions
def gids(self):
- with open("/proc/%s/status" % self.pid, 'rb') as f:
+ fpath = "/proc/%s/status" % self.pid
+ with open(fpath, 'rb') as f:
for line in f:
if line.startswith(b'Gid:'):
_, real, effective, saved, fs = line.split()
return _common.pgids(int(real), int(effective), int(saved))
- raise NotImplementedError("line not found")
+ raise NotImplementedError("line 'Gid' not found in %s" % fpath)
diff --git a/psutil/_pssunos.py b/psutil/_pssunos.py
index bb47fd29..bc35a718 100644
--- a/psutil/_pssunos.py
+++ b/psutil/_pssunos.py
@@ -96,7 +96,9 @@ def swap_memory():
# usr/src/cmd/swap/swap.c
# ...nevertheless I can't manage to obtain the same numbers as 'swap'
# cmdline utility, so let's parse its output (sigh!)
- p = subprocess.Popen(['swap', '-l', '-k'], stdout=subprocess.PIPE)
+ p = subprocess.Popen(['/usr/bin/env', 'PATH=/usr/sbin:/sbin:%s' %
+ os.environ['PATH'], 'swap', '-l', '-k'],
+ stdout=subprocess.PIPE)
stdout, stderr = p.communicate()
if PY3:
stdout = stdout.decode(sys.stdout.encoding)
diff --git a/psutil/_psutil_sunos.c b/psutil/_psutil_sunos.c
index fb80e3ef..0cb6978f 100644
--- a/psutil/_psutil_sunos.c
+++ b/psutil/_psutil_sunos.c
@@ -736,18 +736,18 @@ psutil_net_io_counters(PyObject *self, PyObject *args)
#if defined(_INT64_TYPE)
py_ifc_info = Py_BuildValue("(KKKKkkii)",
- rbytes->value.ui64,
wbytes->value.ui64,
- rpkts->value.ui64,
+ rbytes->value.ui64,
wpkts->value.ui64,
+ rpkts->value.ui64,
ierrs->value.ui32,
oerrs->value.ui32,
#else
py_ifc_info = Py_BuildValue("(kkkkkkii)",
- rbytes->value.ui32,
wbytes->value.ui32,
- rpkts->value.ui32,
+ rbytes->value.ui32,
wpkts->value.ui32,
+ rpkts->value.ui32,
ierrs->value.ui32,
oerrs->value.ui32,
#endif
diff --git a/psutil/_psutil_windows.c b/psutil/_psutil_windows.c
index 9a4c0944..b05db820 100644
--- a/psutil/_psutil_windows.c
+++ b/psutil/_psutil_windows.c
@@ -2683,6 +2683,19 @@ psutil_proc_num_handles(PyObject *self, PyObject *args)
}
+/*
+ * Get various process information by using NtQuerySystemInformation.
+ * We use this as a fallback when faster functions fail with access
+ * denied. This is slower because it iterates over all processes.
+ * Returned tuple includes the following process info:
+ *
+ * - num_threads
+ * - ctx_switches
+ * - num_handles (fallback)
+ * - user/kernel times (fallback)
+ * - create time (fallback)
+ * - io counters (fallback)
+ */
static PyObject *
psutil_proc_info(PyObject *self, PyObject *args)
{
@@ -3180,7 +3193,7 @@ PsutilMethods[] =
"seconds since the epoch"},
{"proc_memory_info", psutil_proc_memory_info, METH_VARARGS,
"Return a tuple of process memory information"},
- {"proc_memory_info_2", psutil_proc_memory_info, METH_VARARGS,
+ {"proc_memory_info_2", psutil_proc_memory_info_2, METH_VARARGS,
"Alternate implementation"},
{"proc_cwd", psutil_proc_cwd, METH_VARARGS,
"Return process current working directory"},
@@ -3387,4 +3400,4 @@ void init_psutil_windows(void)
#if PY_MAJOR_VERSION >= 3
return module;
#endif
-} \ No newline at end of file
+}
diff --git a/psutil/arch/windows/process_info.c b/psutil/arch/windows/process_info.c
index ad272bff..a59cce47 100644
--- a/psutil/arch/windows/process_info.c
+++ b/psutil/arch/windows/process_info.c
@@ -357,7 +357,10 @@ const int STATUS_BUFFER_TOO_SMALL = 0xC0000023L;
/*
* Given a process PID and a PSYSTEM_PROCESS_INFORMATION structure
- * fills the structure with process information.
+ * fills the structure with various process information by using
+ * NtQuerySystemInformation.
+ * We use this as a fallback when faster functions fail with access
+ * denied. This is slower because it iterates over all processes.
* On success return 1, else 0 with Python exception already set.
*/
int