| #!/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. |
| |
| """Linux platform implementation.""" |
| |
| from __future__ import division |
| |
| import base64 |
| import errno |
| import functools |
| import os |
| import re |
| import socket |
| import struct |
| import sys |
| import warnings |
| from collections import namedtuple, defaultdict |
| |
| from . import _common |
| from . import _psposix |
| 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, long |
| |
| if sys.version_info >= (3, 4): |
| import enum |
| else: |
| enum = None |
| |
| |
| __extra__all__ = [ |
| # io prio constants |
| "IOPRIO_CLASS_NONE", "IOPRIO_CLASS_RT", "IOPRIO_CLASS_BE", |
| "IOPRIO_CLASS_IDLE", |
| # 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", ] |
| |
| # --- constants |
| |
| HAS_PRLIMIT = hasattr(cext, "linux_prlimit") |
| |
| # RLIMIT_* constants, not guaranteed to be present on all kernels |
| if HAS_PRLIMIT: |
| for name in dir(cext): |
| if name.startswith('RLIM'): |
| __extra__all__.append(name) |
| |
| # Number of clock ticks per second |
| CLOCK_TICKS = os.sysconf("SC_CLK_TCK") |
| PAGESIZE = os.sysconf("SC_PAGE_SIZE") |
| BOOT_TIME = None # set later |
| DEFAULT_ENCODING = sys.getdefaultencoding() |
| if enum is None: |
| AF_LINK = socket.AF_PACKET |
| else: |
| AddressFamily = enum.IntEnum('AddressFamily', |
| {'AF_LINK': socket.AF_PACKET}) |
| AF_LINK = AddressFamily.AF_LINK |
| |
| # ioprio_* constants http://linux.die.net/man/2/ioprio_get |
| if enum is None: |
| IOPRIO_CLASS_NONE = 0 |
| IOPRIO_CLASS_RT = 1 |
| IOPRIO_CLASS_BE = 2 |
| IOPRIO_CLASS_IDLE = 3 |
| else: |
| class IOPriority(enum.IntEnum): |
| IOPRIO_CLASS_NONE = 0 |
| IOPRIO_CLASS_RT = 1 |
| IOPRIO_CLASS_BE = 2 |
| IOPRIO_CLASS_IDLE = 3 |
| |
| globals().update(IOPriority.__members__) |
| |
| # taken from /fs/proc/array.c |
| PROC_STATUSES = { |
| "R": _common.STATUS_RUNNING, |
| "S": _common.STATUS_SLEEPING, |
| "D": _common.STATUS_DISK_SLEEP, |
| "T": _common.STATUS_STOPPED, |
| "t": _common.STATUS_TRACING_STOP, |
| "Z": _common.STATUS_ZOMBIE, |
| "X": _common.STATUS_DEAD, |
| "x": _common.STATUS_DEAD, |
| "K": _common.STATUS_WAKE_KILL, |
| "W": _common.STATUS_WAKING |
| } |
| |
| # http://students.mimuw.edu.pl/lxr/source/include/net/tcp_states.h |
| TCP_STATUSES = { |
| "01": _common.CONN_ESTABLISHED, |
| "02": _common.CONN_SYN_SENT, |
| "03": _common.CONN_SYN_RECV, |
| "04": _common.CONN_FIN_WAIT1, |
| "05": _common.CONN_FIN_WAIT2, |
| "06": _common.CONN_TIME_WAIT, |
| "07": _common.CONN_CLOSE, |
| "08": _common.CONN_CLOSE_WAIT, |
| "09": _common.CONN_LAST_ACK, |
| "0A": _common.CONN_LISTEN, |
| "0B": _common.CONN_CLOSING |
| } |
| |
| # set later from __init__.py |
| NoSuchProcess = None |
| ZombieProcess = None |
| AccessDenied = None |
| TimeoutExpired = None |
| |
| |
| # --- named tuples |
| |
| def _get_cputimes_fields(): |
| """Return a namedtuple of variable fields depending on the |
| CPU times available on this Linux kernel version which may be: |
| (user, nice, system, idle, iowait, irq, softirq, [steal, [guest, |
| [guest_nice]]]) |
| """ |
| with open('/proc/stat', 'rb') as f: |
| values = f.readline().split()[1:] |
| fields = ['user', 'nice', 'system', 'idle', 'iowait', 'irq', 'softirq'] |
| vlen = len(values) |
| if vlen >= 8: |
| # Linux >= 2.6.11 |
| fields.append('steal') |
| if vlen >= 9: |
| # Linux >= 2.6.24 |
| fields.append('guest') |
| if vlen >= 10: |
| # Linux >= 3.2.0 |
| fields.append('guest_nice') |
| return fields |
| |
| |
| scputimes = namedtuple('scputimes', _get_cputimes_fields()) |
| |
| svmem = namedtuple( |
| 'svmem', ['total', 'available', 'percent', 'used', 'free', |
| 'active', 'inactive', 'buffers', 'cached']) |
| |
| pextmem = namedtuple('pextmem', 'rss vms shared text lib data dirty') |
| |
| pmmap_grouped = namedtuple( |
| 'pmmap_grouped', ['path', 'rss', 'size', 'pss', 'shared_clean', |
| 'shared_dirty', 'private_clean', 'private_dirty', |
| 'referenced', 'anonymous', 'swap']) |
| |
| pmmap_ext = namedtuple( |
| 'pmmap_ext', 'addr perms ' + ' '.join(pmmap_grouped._fields)) |
| |
| |
| # --- system memory |
| |
| def virtual_memory(): |
| total, free, buffers, shared, _, _ = cext.linux_sysinfo() |
| cached = active = inactive = None |
| with open('/proc/meminfo', 'rb') as f: |
| for line in f: |
| if line.startswith(b"Cached:"): |
| cached = int(line.split()[1]) * 1024 |
| elif line.startswith(b"Active:"): |
| active = int(line.split()[1]) * 1024 |
| elif line.startswith(b"Inactive:"): |
| inactive = int(line.split()[1]) * 1024 |
| if (cached is not None and |
| active is not None and |
| inactive is not None): |
| break |
| else: |
| # we might get here when dealing with exotic Linux flavors, see: |
| # https://github.com/giampaolo/psutil/issues/313 |
| msg = "'cached', 'active' and 'inactive' memory stats couldn't " \ |
| "be determined and were set to 0" |
| warnings.warn(msg, RuntimeWarning) |
| cached = active = inactive = 0 |
| avail = free + buffers + cached |
| used = total - free |
| percent = usage_percent((total - avail), total, _round=1) |
| return svmem(total, avail, percent, used, free, |
| active, inactive, buffers, cached) |
| |
| |
| def swap_memory(): |
| _, _, _, _, total, free = cext.linux_sysinfo() |
| used = total - free |
| percent = usage_percent(used, total, _round=1) |
| # get pgin/pgouts |
| with open("/proc/vmstat", "rb") as f: |
| sin = sout = None |
| for line in f: |
| # values are expressed in 4 kilo bytes, we want bytes instead |
| if line.startswith(b'pswpin'): |
| sin = int(line.split(b' ')[1]) * 4 * 1024 |
| elif line.startswith(b'pswpout'): |
| sout = int(line.split(b' ')[1]) * 4 * 1024 |
| if sin is not None and sout is not None: |
| break |
| else: |
| # we might get here when dealing with exotic Linux flavors, see: |
| # https://github.com/giampaolo/psutil/issues/313 |
| msg = "'sin' and 'sout' swap memory stats couldn't " \ |
| "be determined and were set to 0" |
| warnings.warn(msg, RuntimeWarning) |
| sin = sout = 0 |
| return _common.sswap(total, used, free, percent, sin, sout) |
| |
| |
| # --- CPUs |
| |
| def cpu_times(): |
| """Return a named tuple representing the following system-wide |
| CPU times: |
| (user, nice, system, idle, iowait, irq, softirq [steal, [guest, |
| [guest_nice]]]) |
| Last 3 fields may not be available on all Linux kernel versions. |
| """ |
| with open('/proc/stat', 'rb') as f: |
| values = f.readline().split() |
| fields = values[1:len(scputimes._fields) + 1] |
| fields = [float(x) / CLOCK_TICKS for x in fields] |
| return scputimes(*fields) |
| |
| |
| def per_cpu_times(): |
| """Return a list of namedtuple representing the CPU times |
| for every CPU available on the system. |
| """ |
| cpus = [] |
| with open('/proc/stat', 'rb') as f: |
| # get rid of the first line which refers to system wide CPU stats |
| f.readline() |
| for line in f: |
| if line.startswith(b'cpu'): |
| values = line.split() |
| fields = values[1:len(scputimes._fields) + 1] |
| fields = [float(x) / CLOCK_TICKS for x in fields] |
| entry = scputimes(*fields) |
| cpus.append(entry) |
| return cpus |
| |
| |
| def cpu_count_logical(): |
| """Return the number of logical CPUs in the system.""" |
| try: |
| return os.sysconf("SC_NPROCESSORS_ONLN") |
| except ValueError: |
| # as a second fallback we try to parse /proc/cpuinfo |
| num = 0 |
| with open('/proc/cpuinfo', 'rb') as f: |
| for line in f: |
| if line.lower().startswith(b'processor'): |
| num += 1 |
| |
| # unknown format (e.g. amrel/sparc architectures), see: |
| # https://github.com/giampaolo/psutil/issues/200 |
| # try to parse /proc/stat as a last resort |
| if num == 0: |
| search = re.compile('cpu\d') |
| with open('/proc/stat', 'rt') as f: |
| for line in f: |
| line = line.split(' ')[0] |
| if search.match(line): |
| num += 1 |
| |
| if num == 0: |
| # mimic os.cpu_count() |
| return None |
| return num |
| |
| |
| def cpu_count_physical(): |
| """Return the number of physical cores in the system.""" |
| mapping = {} |
| current_info = {} |
| with open('/proc/cpuinfo', 'rb') as f: |
| for line in f: |
| 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 sum(mapping.values()) or None |
| |
| |
| # --- other system functions |
| |
| def users(): |
| """Return currently connected users as a list of namedtuples.""" |
| retlist = [] |
| rawlist = cext.users() |
| for item in rawlist: |
| user, tty, hostname, tstamp, user_process = item |
| # note: the underlying C function includes entries about |
| # system boot, run level and others. We might want |
| # to use them in the future. |
| if not user_process: |
| continue |
| if hostname == ':0.0' or hostname == ':0': |
| hostname = 'localhost' |
| nt = _common.suser(user, tty or None, hostname, tstamp) |
| retlist.append(nt) |
| return retlist |
| |
| |
| def boot_time(): |
| """Return the system boot time expressed in seconds since the epoch.""" |
| global BOOT_TIME |
| with open('/proc/stat', 'rb') as f: |
| for line in f: |
| if line.startswith(b'btime'): |
| ret = float(line.strip().split()[1]) |
| BOOT_TIME = ret |
| return ret |
| raise RuntimeError("line 'btime' not found in /proc/stat") |
| |
| |
| # --- processes |
| |
| def pids(): |
| """Returns a list of PIDs currently running on the system.""" |
| return [int(x) for x in os.listdir(b'/proc') if x.isdigit()] |
| |
| |
| def pid_exists(pid): |
| """Check For the existence of a unix pid.""" |
| return _psposix.pid_exists(pid) |
| |
| |
| # --- network |
| |
| class Connections: |
| """A wrapper on top of /proc/net/* files, retrieving per-process |
| and system-wide open connections (TCP, UDP, UNIX) similarly to |
| "netstat -an". |
| |
| Note: in case of UNIX sockets we're only able to determine the |
| local endpoint/path, not the one it's connected to. |
| According to [1] it would be possible but not easily. |
| |
| [1] http://serverfault.com/a/417946 |
| """ |
| |
| def __init__(self): |
| tcp4 = ("tcp", socket.AF_INET, socket.SOCK_STREAM) |
| tcp6 = ("tcp6", socket.AF_INET6, socket.SOCK_STREAM) |
| udp4 = ("udp", socket.AF_INET, socket.SOCK_DGRAM) |
| udp6 = ("udp6", socket.AF_INET6, socket.SOCK_DGRAM) |
| unix = ("unix", socket.AF_UNIX, None) |
| self.tmap = { |
| "all": (tcp4, tcp6, udp4, udp6, unix), |
| "tcp": (tcp4, tcp6), |
| "tcp4": (tcp4,), |
| "tcp6": (tcp6,), |
| "udp": (udp4, udp6), |
| "udp4": (udp4,), |
| "udp6": (udp6,), |
| "unix": (unix,), |
| "inet": (tcp4, tcp6, udp4, udp6), |
| "inet4": (tcp4, udp4), |
| "inet6": (tcp6, udp6), |
| } |
| |
| def get_proc_inodes(self, pid): |
| inodes = defaultdict(list) |
| for fd in os.listdir("/proc/%s/fd" % pid): |
| try: |
| inode = os.readlink("/proc/%s/fd/%s" % (pid, fd)) |
| 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 |
| inode = inode[8:][:-1] |
| inodes[inode].append((pid, int(fd))) |
| return inodes |
| |
| def get_all_inodes(self): |
| inodes = {} |
| for pid in pids(): |
| try: |
| inodes.update(self.get_proc_inodes(pid)) |
| except OSError as err: |
| # os.listdir() is gonna raise a lot of access denied |
| # exceptions in case of unprivileged user; that's fine |
| # as we'll just end up returning a connection with PID |
| # and fd set to None anyway. |
| # Both netstat -an and lsof does the same so it's |
| # unlikely we can do any better. |
| # ENOENT just means a PID disappeared on us. |
| if err.errno not in ( |
| errno.ENOENT, errno.ESRCH, errno.EPERM, errno.EACCES): |
| raise |
| return inodes |
| |
| def decode_address(self, addr, family): |
| """Accept an "ip:port" address as displayed in /proc/net/* |
| and convert it into a human readable form, like: |
| |
| "0500000A:0016" -> ("10.0.0.5", 22) |
| "0000000000000000FFFF00000100007F:9E49" -> ("::ffff:127.0.0.1", 40521) |
| |
| The IP address portion is a little or big endian four-byte |
| hexadecimal number; that is, the least significant byte is listed |
| first, so we need to reverse the order of the bytes to convert it |
| to an IP address. |
| The port is represented as a two-byte hexadecimal number. |
| |
| Reference: |
| http://linuxdevcenter.com/pub/a/linux/2000/11/16/LinuxAdmin.html |
| """ |
| ip, port = addr.split(':') |
| port = int(port, 16) |
| # this usually refers to a local socket in listen mode with |
| # no end-points connected |
| if not port: |
| return () |
| if PY3: |
| ip = ip.encode('ascii') |
| if family == socket.AF_INET: |
| # see: https://github.com/giampaolo/psutil/issues/201 |
| if sys.byteorder == 'little': |
| ip = socket.inet_ntop(family, base64.b16decode(ip)[::-1]) |
| else: |
| ip = socket.inet_ntop(family, base64.b16decode(ip)) |
| else: # IPv6 |
| # old version - let's keep it, just in case... |
| # ip = ip.decode('hex') |
| # return socket.inet_ntop(socket.AF_INET6, |
| # ''.join(ip[i:i+4][::-1] for i in xrange(0, 16, 4))) |
| ip = base64.b16decode(ip) |
| # see: https://github.com/giampaolo/psutil/issues/201 |
| if sys.byteorder == 'little': |
| ip = socket.inet_ntop( |
| socket.AF_INET6, |
| struct.pack('>4I', *struct.unpack('<4I', ip))) |
| else: |
| ip = socket.inet_ntop( |
| socket.AF_INET6, |
| struct.pack('<4I', *struct.unpack('<4I', ip))) |
| return (ip, port) |
| |
| def process_inet(self, file, family, type_, inodes, filter_pid=None): |
| """Parse /proc/net/tcp* and /proc/net/udp* files.""" |
| if file.endswith('6') and not os.path.exists(file): |
| # IPv6 not supported |
| return |
| with open(file, 'rt') as f: |
| f.readline() # skip the first line |
| for line in f: |
| 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 |
| # # same inode. We won't do this for UNIX sockets. |
| # if len(inodes[inode]) > 1 and family != socket.AF_UNIX: |
| # raise ValueError("ambiguos inode with multiple " |
| # "PIDs references") |
| pid, fd = inodes[inode][0] |
| else: |
| pid, fd = None, -1 |
| if filter_pid is not None and filter_pid != pid: |
| continue |
| else: |
| if type_ == socket.SOCK_STREAM: |
| status = TCP_STATUSES[status] |
| else: |
| status = _common.CONN_NONE |
| laddr = self.decode_address(laddr, family) |
| raddr = self.decode_address(raddr, family) |
| yield (fd, family, type_, laddr, raddr, status, pid) |
| |
| def process_unix(self, file, family, inodes, filter_pid=None): |
| """Parse /proc/net/unix files.""" |
| with open(file, 'rt') as f: |
| f.readline() # skip the first line |
| for line in f: |
| tokens = line.split() |
| 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. |
| pairs = inodes[inode] |
| else: |
| pairs = [(None, -1)] |
| for pid, fd in pairs: |
| if filter_pid is not None and filter_pid != pid: |
| continue |
| else: |
| if len(tokens) == 8: |
| path = tokens[-1] |
| else: |
| path = "" |
| type_ = int(type_) |
| raddr = None |
| status = _common.CONN_NONE |
| yield (fd, family, type_, path, raddr, status, pid) |
| |
| def retrieve(self, kind, pid=None): |
| if kind not in self.tmap: |
| raise ValueError("invalid %r kind argument; choose between %s" |
| % (kind, ', '.join([repr(x) for x in self.tmap]))) |
| if pid is not None: |
| inodes = self.get_proc_inodes(pid) |
| if not inodes: |
| # no connections for this process |
| return [] |
| else: |
| inodes = self.get_all_inodes() |
| ret = set() |
| for f, family, type_ in self.tmap[kind]: |
| if family in (socket.AF_INET, socket.AF_INET6): |
| ls = self.process_inet( |
| "/proc/net/%s" % f, family, type_, inodes, filter_pid=pid) |
| else: |
| ls = self.process_unix( |
| "/proc/net/%s" % f, family, inodes, filter_pid=pid) |
| for fd, family, type_, laddr, raddr, status, bound_pid in ls: |
| if pid: |
| conn = _common.pconn(fd, family, type_, laddr, raddr, |
| status) |
| else: |
| conn = _common.sconn(fd, family, type_, laddr, raddr, |
| status, bound_pid) |
| ret.add(conn) |
| return list(ret) |
| |
| |
| _connections = Connections() |
| |
| |
| def net_connections(kind='inet'): |
| """Return system-wide open connections.""" |
| return _connections.retrieve(kind) |
| |
| |
| def net_io_counters(): |
| """Return network I/O statistics for every network interface |
| installed on the system as a dict of raw tuples. |
| """ |
| with open("/proc/net/dev", "rt") as f: |
| lines = f.readlines() |
| retdict = {} |
| for line in lines[2:]: |
| colon = line.rfind(':') |
| assert colon > 0, repr(line) |
| name = line[:colon].strip() |
| fields = line[colon + 1:].strip().split() |
| bytes_recv = int(fields[0]) |
| packets_recv = int(fields[1]) |
| errin = int(fields[2]) |
| dropin = int(fields[3]) |
| bytes_sent = int(fields[8]) |
| packets_sent = int(fields[9]) |
| errout = int(fields[10]) |
| dropout = int(fields[11]) |
| retdict[name] = (bytes_sent, bytes_recv, packets_sent, packets_recv, |
| errin, errout, dropin, dropout) |
| return retdict |
| |
| |
| def net_if_stats(): |
| """Get NIC stats (isup, duplex, speed, mtu).""" |
| duplex_map = {cext.DUPLEX_FULL: NIC_DUPLEX_FULL, |
| cext.DUPLEX_HALF: NIC_DUPLEX_HALF, |
| cext.DUPLEX_UNKNOWN: NIC_DUPLEX_UNKNOWN} |
| names = net_io_counters().keys() |
| ret = {} |
| for name in names: |
| isup, duplex, speed, mtu = cext.net_if_stats(name) |
| duplex = duplex_map[duplex] |
| ret[name] = _common.snicstats(isup, duplex, speed, mtu) |
| return ret |
| |
| |
| net_if_addrs = cext_posix.net_if_addrs |
| |
| |
| # --- disks |
| |
| def disk_io_counters(): |
| """Return disk I/O statistics for every disk installed on the |
| system as a dict of raw tuples. |
| """ |
| # man iostat states that sectors are equivalent with blocks and |
| # have a size of 512 bytes since 2.4 kernels. This value is |
| # needed to calculate the amount of disk I/O in bytes. |
| SECTOR_SIZE = 512 |
| |
| # determine partitions we want to look for |
| partitions = [] |
| with open("/proc/partitions", "rt") as f: |
| lines = f.readlines()[2:] |
| for line in reversed(lines): |
| _, _, _, name = line.split() |
| if name[-1].isdigit(): |
| # we're dealing with a partition (e.g. 'sda1'); 'sda' will |
| # also be around but we want to omit it |
| partitions.append(name) |
| else: |
| if not partitions or not partitions[-1].startswith(name): |
| # we're dealing with a disk entity for which no |
| # partitions have been defined (e.g. 'sda' but |
| # 'sda1' was not around), see: |
| # https://github.com/giampaolo/psutil/issues/338 |
| partitions.append(name) |
| # |
| retdict = {} |
| with open("/proc/diskstats", "rt") as f: |
| lines = f.readlines() |
| for line in lines: |
| # http://www.mjmwired.net/kernel/Documentation/iostats.txt |
| fields = line.split() |
| if len(fields) > 7: |
| _, _, name, reads, _, rbytes, rtime, writes, _, wbytes, wtime = \ |
| fields[:11] |
| else: |
| # from kernel 2.6.0 to 2.6.25 |
| _, _, name, reads, rbytes, writes, wbytes = fields |
| rtime, wtime = 0, 0 |
| if name in partitions: |
| rbytes = int(rbytes) * SECTOR_SIZE |
| wbytes = int(wbytes) * SECTOR_SIZE |
| reads = int(reads) |
| writes = int(writes) |
| rtime = int(rtime) |
| wtime = int(wtime) |
| retdict[name] = (reads, writes, rbytes, wbytes, rtime, wtime) |
| return retdict |
| |
| |
| def disk_partitions(all=False): |
| """Return mounted disk partitions as a list of namedtuples""" |
| fstypes = set() |
| with open("/proc/filesystems", "r") as f: |
| for line in f: |
| line = line.strip() |
| if not line.startswith("nodev"): |
| fstypes.add(line.strip()) |
| else: |
| # ignore all lines starting with "nodev" except "nodev zfs" |
| fstype = line.split("\t")[1] |
| if fstype == "zfs": |
| fstypes.add("zfs") |
| |
| retlist = [] |
| partitions = cext.disk_partitions() |
| for partition in partitions: |
| device, mountpoint, fstype, opts = partition |
| if device == 'none': |
| device = '' |
| if not all: |
| if device == '' or fstype not in fstypes: |
| continue |
| ntuple = _common.sdiskpart(device, mountpoint, fstype, opts) |
| retlist.append(ntuple) |
| return retlist |
| |
| |
| disk_usage = _psposix.disk_usage |
| |
| |
| # --- decorators |
| |
| def wrap_exceptions(fun): |
| """Decorator which translates bare OSError and IOError exceptions |
| into NoSuchProcess and AccessDenied. |
| """ |
| @functools.wraps(fun) |
| def wrapper(self, *args, **kwargs): |
| try: |
| return fun(self, *args, **kwargs) |
| except EnvironmentError as err: |
| # support for private module import |
| if NoSuchProcess is None or AccessDenied is None: |
| raise |
| # ENOENT (no such file or directory) gets raised on open(). |
| # ESRCH (no such process) can get raised on read() if |
| # process is gone in meantime. |
| if err.errno in (errno.ENOENT, errno.ESRCH): |
| raise NoSuchProcess(self.pid, self._name) |
| if err.errno in (errno.EPERM, errno.EACCES): |
| raise AccessDenied(self.pid, self._name) |
| raise |
| return wrapper |
| |
| |
| def wrap_exceptions_w_zombie(fun): |
| """Same as above but also handles zombies.""" |
| @functools.wraps(fun) |
| def wrapper(self, *args, **kwargs): |
| try: |
| return wrap_exceptions(fun)(self) |
| except NoSuchProcess: |
| if not pid_exists(self.pid): |
| raise |
| else: |
| raise ZombieProcess(self.pid, self._name, self._ppid) |
| return wrapper |
| |
| |
| class Process(object): |
| """Linux process implementation.""" |
| |
| __slots__ = ["pid", "_name", "_ppid"] |
| |
| def __init__(self, pid): |
| self.pid = pid |
| self._name = None |
| self._ppid = None |
| |
| @wrap_exceptions |
| def name(self): |
| fname = "/proc/%s/stat" % self.pid |
| kw = dict(encoding=DEFAULT_ENCODING) if PY3 else dict() |
| with open(fname, "rt", **kw) as f: |
| 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 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 |
| # low pids (about 0-20) |
| if os.path.lexists("/proc/%s" % self.pid): |
| return "" |
| else: |
| if not pid_exists(self.pid): |
| raise NoSuchProcess(self.pid, self._name) |
| else: |
| raise ZombieProcess(self.pid, self._name, self._ppid) |
| if err.errno in (errno.EPERM, errno.EACCES): |
| raise AccessDenied(self.pid, self._name) |
| raise |
| |
| # readlink() might return paths containing null bytes ('\x00'). |
| # Certain names have ' (deleted)' appended. Usually this is |
| # bogus as the file actually exists. Either way that's not |
| # important as we don't want to discriminate executables which |
| # have been deleted. |
| exe = exe.split('\x00')[0] |
| if exe.endswith(' (deleted)') and not os.path.exists(exe): |
| exe = exe[:-10] |
| return exe |
| |
| @wrap_exceptions |
| def cmdline(self): |
| fname = "/proc/%s/cmdline" % self.pid |
| kw = dict(encoding=DEFAULT_ENCODING) if PY3 else dict() |
| with open(fname, "rt", **kw) as f: |
| data = f.read() |
| if data.endswith('\x00'): |
| data = data[:-1] |
| return [x for x in data.split('\x00')] |
| |
| @wrap_exceptions |
| def terminal(self): |
| tmap = _psposix._get_terminal_map() |
| with open("/proc/%s/stat" % self.pid, 'rb') as f: |
| tty_nr = int(f.read().split(b' ')[6]) |
| try: |
| return tmap[tty_nr] |
| except KeyError: |
| return None |
| |
| if os.path.exists('/proc/%s/io' % os.getpid()): |
| @wrap_exceptions |
| def io_counters(self): |
| fname = "/proc/%s/io" % self.pid |
| with open(fname, 'rb') as f: |
| rcount = wcount = rbytes = wbytes = None |
| for line in f: |
| if rcount is None and line.startswith(b"syscr"): |
| rcount = int(line.split()[1]) |
| elif wcount is None and line.startswith(b"syscw"): |
| wcount = int(line.split()[1]) |
| elif rbytes is None and line.startswith(b"read_bytes"): |
| rbytes = int(line.split()[1]) |
| elif wbytes is None and line.startswith(b"write_bytes"): |
| wbytes = int(line.split()[1]) |
| for x in (rcount, wcount, rbytes, wbytes): |
| if x is None: |
| raise NotImplementedError( |
| "couldn't read all necessary info from %r" % fname) |
| return _common.pio(rcount, wcount, rbytes, wbytes) |
| else: |
| def io_counters(self): |
| raise NotImplementedError("couldn't find /proc/%s/io (kernel " |
| "too old?)" % self.pid) |
| |
| @wrap_exceptions |
| def cpu_times(self): |
| with open("/proc/%s/stat" % self.pid, 'rb') as f: |
| st = f.read().strip() |
| # ignore the first two values ("pid (exe)") |
| st = st[st.find(b')') + 2:] |
| values = st.split(b' ') |
| utime = float(values[11]) / CLOCK_TICKS |
| stime = float(values[12]) / CLOCK_TICKS |
| return _common.pcputimes(utime, stime) |
| |
| @wrap_exceptions |
| def wait(self, timeout=None): |
| try: |
| return _psposix.wait_pid(self.pid, timeout) |
| except _psposix.TimeoutExpired: |
| # support for private module import |
| if TimeoutExpired is None: |
| raise |
| raise TimeoutExpired(timeout, self.pid, self._name) |
| |
| @wrap_exceptions |
| def create_time(self): |
| with open("/proc/%s/stat" % self.pid, 'rb') as f: |
| st = f.read().strip() |
| # ignore the first two values ("pid (exe)") |
| st = st[st.rfind(b')') + 2:] |
| values = st.split(b' ') |
| # According to documentation, starttime is in field 21 and the |
| # unit is jiffies (clock ticks). |
| # We first divide it for clock ticks and then add uptime returning |
| # seconds since the epoch, in UTC. |
| # Also use cached value if available. |
| bt = BOOT_TIME or boot_time() |
| return (float(values[19]) / CLOCK_TICKS) + bt |
| |
| @wrap_exceptions |
| def memory_info(self): |
| with open("/proc/%s/statm" % self.pid, 'rb') as f: |
| vms, rss = f.readline().split()[:2] |
| return _common.pmem(int(rss) * PAGESIZE, |
| int(vms) * PAGESIZE) |
| |
| @wrap_exceptions |
| def memory_info_ex(self): |
| # ============================================================ |
| # | FIELD | DESCRIPTION | AKA | TOP | |
| # ============================================================ |
| # | rss | resident set size | | RES | |
| # | vms | total program size | size | VIRT | |
| # | shared | shared pages (from shared mappings) | | SHR | |
| # | text | text ('code') | trs | CODE | |
| # | lib | library (unused in Linux 2.6) | lrs | | |
| # | data | data + stack | drs | DATA | |
| # | dirty | dirty pages (unused in Linux 2.6) | dt | | |
| # ============================================================ |
| with open("/proc/%s/statm" % self.pid, "rb") as f: |
| vms, rss, shared, text, lib, data, dirty = \ |
| [int(x) * PAGESIZE for x in f.readline().split()[:7]] |
| return pextmem(rss, vms, shared, text, lib, data, dirty) |
| |
| if os.path.exists('/proc/%s/smaps' % os.getpid()): |
| |
| @wrap_exceptions |
| def memory_maps(self): |
| """Return process's mapped memory regions as a list of named tuples. |
| Fields are explained in 'man proc'; here is an updated (Apr 2012) |
| version: http://goo.gl/fmebo |
| """ |
| with open("/proc/%s/smaps" % self.pid, "rt") as f: |
| first_line = f.readline() |
| current_block = [first_line] |
| |
| def get_blocks(): |
| data = {} |
| for line in f: |
| fields = line.split(None, 5) |
| if not fields[0].endswith(':'): |
| # new block section |
| yield (current_block.pop(), data) |
| current_block.append(line) |
| else: |
| try: |
| data[fields[0]] = int(fields[1]) * 1024 |
| except ValueError: |
| if fields[0].startswith('VmFlags:'): |
| # see issue #369 |
| continue |
| else: |
| raise ValueError("don't know how to inte" |
| "rpret line %r" % line) |
| yield (current_block.pop(), data) |
| |
| ls = [] |
| if first_line: # smaps file can be empty |
| for header, data in get_blocks(): |
| hfields = header.split(None, 5) |
| try: |
| addr, perms, offset, dev, inode, path = hfields |
| except ValueError: |
| addr, perms, offset, dev, inode, path = \ |
| hfields + [''] |
| if not path: |
| path = '[anon]' |
| else: |
| path = path.strip() |
| ls.append(( |
| addr, perms, path, |
| data['Rss:'], |
| data.get('Size:', 0), |
| data.get('Pss:', 0), |
| data.get('Shared_Clean:', 0), |
| data.get('Shared_Dirty:', 0), |
| data.get('Private_Clean:', 0), |
| data.get('Private_Dirty:', 0), |
| data.get('Referenced:', 0), |
| data.get('Anonymous:', 0), |
| data.get('Swap:', 0) |
| )) |
| return ls |
| |
| else: |
| def memory_maps(self): |
| msg = "couldn't find /proc/%s/smaps; kernel < 2.6.14 or " \ |
| "CONFIG_MMU kernel configuration option is not enabled" \ |
| % self.pid |
| raise NotImplementedError(msg) |
| |
| @wrap_exceptions_w_zombie |
| def cwd(self): |
| # readlink() might return paths containing null bytes causing |
| # problems when used with other fs-related functions (os.*, |
| # open(), ...) |
| path = os.readlink("/proc/%s/cwd" % self.pid) |
| return path.replace('\x00', '') |
| |
| @wrap_exceptions |
| def num_ctx_switches(self): |
| vol = unvol = None |
| with open("/proc/%s/status" % self.pid, "rb") as f: |
| for line in f: |
| if line.startswith(b"voluntary_ctxt_switches"): |
| vol = int(line.split()[1]) |
| elif line.startswith(b"nonvoluntary_ctxt_switches"): |
| unvol = int(line.split()[1]) |
| if vol is not None and unvol is not None: |
| return _common.pctxsw(vol, unvol) |
| raise NotImplementedError( |
| "'voluntary_ctxt_switches' and 'nonvoluntary_ctxt_switches'" |
| "fields were not found in /proc/%s/status; the kernel is " |
| "probably older than 2.6.23" % self.pid) |
| |
| @wrap_exceptions |
| def num_threads(self): |
| with open("/proc/%s/status" % self.pid, "rb") as f: |
| for line in f: |
| if line.startswith(b"Threads:"): |
| return int(line.split()[1]) |
| raise NotImplementedError("line not found") |
| |
| @wrap_exceptions |
| def threads(self): |
| thread_ids = os.listdir("/proc/%s/task" % self.pid) |
| thread_ids.sort() |
| retlist = [] |
| hit_enoent = False |
| for thread_id in thread_ids: |
| fname = "/proc/%s/task/%s/stat" % (self.pid, thread_id) |
| try: |
| with open(fname, 'rb') as f: |
| st = f.read().strip() |
| except IOError as err: |
| if err.errno == errno.ENOENT: |
| # no such file or directory; it means thread |
| # disappeared on us |
| hit_enoent = True |
| continue |
| raise |
| # ignore the first two values ("pid (exe)") |
| st = st[st.find(b')') + 2:] |
| values = st.split(b' ') |
| utime = float(values[11]) / CLOCK_TICKS |
| stime = float(values[12]) / CLOCK_TICKS |
| ntuple = _common.pthread(int(thread_id), utime, stime) |
| retlist.append(ntuple) |
| if hit_enoent: |
| # raise NSP if the process disappeared on us |
| os.stat('/proc/%s' % self.pid) |
| return retlist |
| |
| @wrap_exceptions |
| def nice_get(self): |
| # with open('/proc/%s/stat' % self.pid, 'r') as f: |
| # data = f.read() |
| # return int(data.split()[18]) |
| |
| # Use C implementation |
| return cext_posix.getpriority(self.pid) |
| |
| @wrap_exceptions |
| def nice_set(self, value): |
| return cext_posix.setpriority(self.pid, value) |
| |
| @wrap_exceptions |
| def cpu_affinity_get(self): |
| return cext.proc_cpu_affinity_get(self.pid) |
| |
| @wrap_exceptions |
| def cpu_affinity_set(self, cpus): |
| try: |
| cext.proc_cpu_affinity_set(self.pid, cpus) |
| except OSError as err: |
| if err.errno == errno.EINVAL: |
| allcpus = tuple(range(len(per_cpu_times()))) |
| for cpu in cpus: |
| if cpu not in allcpus: |
| raise ValueError("invalid CPU #%i (choose between %s)" |
| % (cpu, allcpus)) |
| raise |
| |
| # only starting from kernel 2.6.13 |
| if hasattr(cext, "proc_ioprio_get"): |
| |
| @wrap_exceptions |
| def ionice_get(self): |
| ioclass, value = cext.proc_ioprio_get(self.pid) |
| if enum is not None: |
| ioclass = IOPriority(ioclass) |
| return _common.pionice(ioclass, value) |
| |
| @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 " \ |
| "(got %r)" % value |
| raise ValueError(msg) |
| ioclass = IOPRIO_CLASS_NONE |
| value = 0 |
| elif ioclass == IOPRIO_CLASS_IDLE: |
| if value: |
| 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: |
| # 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. 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: |
| if limits is None: |
| # get |
| return cext.linux_prlimit(self.pid, resource) |
| else: |
| # set |
| if len(limits) != 2: |
| raise ValueError( |
| "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: |
| if err.errno == errno.ENOSYS and pid_exists(self.pid): |
| # I saw this happening on Travis: |
| # https://travis-ci.org/giampaolo/psutil/jobs/51368273 |
| raise ZombieProcess(self.pid, self._name, self._ppid) |
| else: |
| raise |
| |
| @wrap_exceptions |
| def status(self): |
| with open("/proc/%s/status" % self.pid, 'rb') as f: |
| for line in f: |
| if line.startswith(b"State:"): |
| letter = line.split()[1] |
| if PY3: |
| letter = letter.decode() |
| # XXX is '?' legit? (we're not supposed to return |
| # it anyway) |
| return PROC_STATUSES.get(letter, '?') |
| |
| @wrap_exceptions |
| def open_files(self): |
| retlist = [] |
| files = os.listdir("/proc/%s/fd" % self.pid) |
| hit_enoent = False |
| for fd in files: |
| file = "/proc/%s/fd/%s" % (self.pid, fd) |
| try: |
| file = os.readlink(file) |
| except OSError as err: |
| # ENOENT == file which is gone in the meantime |
| if err.errno in (errno.ENOENT, errno.ESRCH): |
| hit_enoent = True |
| continue |
| elif err.errno == errno.EINVAL: |
| # not a link |
| continue |
| else: |
| raise |
| else: |
| # If file is not an absolute path there's no way |
| # to tell whether it's a regular file or not, |
| # so we skip it. A regular file is always supposed |
| # to be absolutized though. |
| if file.startswith('/') and isfile_strict(file): |
| ntuple = _common.popenfile(file, int(fd)) |
| retlist.append(ntuple) |
| if hit_enoent: |
| # raise NSP if the process disappeared on us |
| os.stat('/proc/%s' % self.pid) |
| return retlist |
| |
| @wrap_exceptions |
| def connections(self, kind='inet'): |
| ret = _connections.retrieve(kind, self.pid) |
| # raise NSP if the process disappeared on us |
| os.stat('/proc/%s' % self.pid) |
| return ret |
| |
| @wrap_exceptions |
| def num_fds(self): |
| return len(os.listdir("/proc/%s/fd" % self.pid)) |
| |
| @wrap_exceptions |
| def ppid(self): |
| 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 'PPid' not found in %s" % fpath) |
| |
| @wrap_exceptions |
| def uids(self): |
| 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 'Uid' not found in %s" % fpath) |
| |
| @wrap_exceptions |
| def gids(self): |
| 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 'Gid' not found in %s" % fpath) |