diff options
Diffstat (limited to 'psutil')
-rw-r--r-- | psutil/__init__.py | 57 | ||||
-rw-r--r-- | psutil/_common.py | 12 | ||||
-rw-r--r-- | psutil/_psbsd.py | 6 | ||||
-rw-r--r-- | psutil/_pslinux.py | 128 | ||||
-rw-r--r-- | psutil/_pssunos.py | 4 | ||||
-rw-r--r-- | psutil/_psutil_sunos.c | 8 | ||||
-rw-r--r-- | psutil/_psutil_windows.c | 17 | ||||
-rw-r--r-- | psutil/arch/windows/process_info.c | 5 |
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 |