forked from mirrors/gecko-dev
Backed out changeset 19f707f5c097 (bug 1666347) Backed out changeset 3732ee259759 (bug 1666345) Backed out changeset 353d3c9e74b9 (bug 1661624) Backed out changeset a651515586a8 (bug 1667152)
903 lines
30 KiB
Python
903 lines
30 KiB
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.
|
|
|
|
"""FreeBSD, OpenBSD and NetBSD platforms implementation."""
|
|
|
|
import contextlib
|
|
import errno
|
|
import functools
|
|
import os
|
|
import xml.etree.ElementTree as ET
|
|
from collections import namedtuple
|
|
from collections import defaultdict
|
|
|
|
from . import _common
|
|
from . import _psposix
|
|
from . import _psutil_bsd as cext
|
|
from . import _psutil_posix as cext_posix
|
|
from ._common import AccessDenied
|
|
from ._common import conn_tmap
|
|
from ._common import conn_to_ntuple
|
|
from ._common import FREEBSD
|
|
from ._common import memoize
|
|
from ._common import memoize_when_activated
|
|
from ._common import NETBSD
|
|
from ._common import NoSuchProcess
|
|
from ._common import OPENBSD
|
|
from ._common import usage_percent
|
|
from ._common import ZombieProcess
|
|
from ._compat import FileNotFoundError
|
|
from ._compat import PermissionError
|
|
from ._compat import ProcessLookupError
|
|
from ._compat import which
|
|
|
|
|
|
__extra__all__ = []
|
|
|
|
|
|
# =====================================================================
|
|
# --- globals
|
|
# =====================================================================
|
|
|
|
|
|
if FREEBSD:
|
|
PROC_STATUSES = {
|
|
cext.SIDL: _common.STATUS_IDLE,
|
|
cext.SRUN: _common.STATUS_RUNNING,
|
|
cext.SSLEEP: _common.STATUS_SLEEPING,
|
|
cext.SSTOP: _common.STATUS_STOPPED,
|
|
cext.SZOMB: _common.STATUS_ZOMBIE,
|
|
cext.SWAIT: _common.STATUS_WAITING,
|
|
cext.SLOCK: _common.STATUS_LOCKED,
|
|
}
|
|
elif OPENBSD or NETBSD:
|
|
PROC_STATUSES = {
|
|
cext.SIDL: _common.STATUS_IDLE,
|
|
cext.SSLEEP: _common.STATUS_SLEEPING,
|
|
cext.SSTOP: _common.STATUS_STOPPED,
|
|
# According to /usr/include/sys/proc.h SZOMB is unused.
|
|
# test_zombie_process() shows that SDEAD is the right
|
|
# equivalent. Also it appears there's no equivalent of
|
|
# psutil.STATUS_DEAD. SDEAD really means STATUS_ZOMBIE.
|
|
# cext.SZOMB: _common.STATUS_ZOMBIE,
|
|
cext.SDEAD: _common.STATUS_ZOMBIE,
|
|
cext.SZOMB: _common.STATUS_ZOMBIE,
|
|
# From http://www.eecs.harvard.edu/~margo/cs161/videos/proc.h.txt
|
|
# OpenBSD has SRUN and SONPROC: SRUN indicates that a process
|
|
# is runnable but *not* yet running, i.e. is on a run queue.
|
|
# SONPROC indicates that the process is actually executing on
|
|
# a CPU, i.e. it is no longer on a run queue.
|
|
# As such we'll map SRUN to STATUS_WAKING and SONPROC to
|
|
# STATUS_RUNNING
|
|
cext.SRUN: _common.STATUS_WAKING,
|
|
cext.SONPROC: _common.STATUS_RUNNING,
|
|
}
|
|
elif NETBSD:
|
|
PROC_STATUSES = {
|
|
cext.SIDL: _common.STATUS_IDLE,
|
|
cext.SACTIVE: _common.STATUS_RUNNING,
|
|
cext.SDYING: _common.STATUS_ZOMBIE,
|
|
cext.SSTOP: _common.STATUS_STOPPED,
|
|
cext.SZOMB: _common.STATUS_ZOMBIE,
|
|
cext.SDEAD: _common.STATUS_DEAD,
|
|
cext.SSUSPENDED: _common.STATUS_SUSPENDED, # unique to NetBSD
|
|
}
|
|
|
|
TCP_STATUSES = {
|
|
cext.TCPS_ESTABLISHED: _common.CONN_ESTABLISHED,
|
|
cext.TCPS_SYN_SENT: _common.CONN_SYN_SENT,
|
|
cext.TCPS_SYN_RECEIVED: _common.CONN_SYN_RECV,
|
|
cext.TCPS_FIN_WAIT_1: _common.CONN_FIN_WAIT1,
|
|
cext.TCPS_FIN_WAIT_2: _common.CONN_FIN_WAIT2,
|
|
cext.TCPS_TIME_WAIT: _common.CONN_TIME_WAIT,
|
|
cext.TCPS_CLOSED: _common.CONN_CLOSE,
|
|
cext.TCPS_CLOSE_WAIT: _common.CONN_CLOSE_WAIT,
|
|
cext.TCPS_LAST_ACK: _common.CONN_LAST_ACK,
|
|
cext.TCPS_LISTEN: _common.CONN_LISTEN,
|
|
cext.TCPS_CLOSING: _common.CONN_CLOSING,
|
|
cext.PSUTIL_CONN_NONE: _common.CONN_NONE,
|
|
}
|
|
|
|
if NETBSD:
|
|
PAGESIZE = os.sysconf("SC_PAGESIZE")
|
|
else:
|
|
PAGESIZE = os.sysconf("SC_PAGE_SIZE")
|
|
AF_LINK = cext_posix.AF_LINK
|
|
|
|
HAS_PER_CPU_TIMES = hasattr(cext, "per_cpu_times")
|
|
HAS_PROC_NUM_THREADS = hasattr(cext, "proc_num_threads")
|
|
HAS_PROC_OPEN_FILES = hasattr(cext, 'proc_open_files')
|
|
HAS_PROC_NUM_FDS = hasattr(cext, 'proc_num_fds')
|
|
|
|
kinfo_proc_map = dict(
|
|
ppid=0,
|
|
status=1,
|
|
real_uid=2,
|
|
effective_uid=3,
|
|
saved_uid=4,
|
|
real_gid=5,
|
|
effective_gid=6,
|
|
saved_gid=7,
|
|
ttynr=8,
|
|
create_time=9,
|
|
ctx_switches_vol=10,
|
|
ctx_switches_unvol=11,
|
|
read_io_count=12,
|
|
write_io_count=13,
|
|
user_time=14,
|
|
sys_time=15,
|
|
ch_user_time=16,
|
|
ch_sys_time=17,
|
|
rss=18,
|
|
vms=19,
|
|
memtext=20,
|
|
memdata=21,
|
|
memstack=22,
|
|
cpunum=23,
|
|
name=24,
|
|
)
|
|
|
|
|
|
# =====================================================================
|
|
# --- named tuples
|
|
# =====================================================================
|
|
|
|
|
|
# psutil.virtual_memory()
|
|
svmem = namedtuple(
|
|
'svmem', ['total', 'available', 'percent', 'used', 'free',
|
|
'active', 'inactive', 'buffers', 'cached', 'shared', 'wired'])
|
|
# psutil.cpu_times()
|
|
scputimes = namedtuple(
|
|
'scputimes', ['user', 'nice', 'system', 'idle', 'irq'])
|
|
# psutil.Process.memory_info()
|
|
pmem = namedtuple('pmem', ['rss', 'vms', 'text', 'data', 'stack'])
|
|
# psutil.Process.memory_full_info()
|
|
pfullmem = pmem
|
|
# psutil.Process.cpu_times()
|
|
pcputimes = namedtuple('pcputimes',
|
|
['user', 'system', 'children_user', 'children_system'])
|
|
# psutil.Process.memory_maps(grouped=True)
|
|
pmmap_grouped = namedtuple(
|
|
'pmmap_grouped', 'path rss, private, ref_count, shadow_count')
|
|
# psutil.Process.memory_maps(grouped=False)
|
|
pmmap_ext = namedtuple(
|
|
'pmmap_ext', 'addr, perms path rss, private, ref_count, shadow_count')
|
|
# psutil.disk_io_counters()
|
|
if FREEBSD:
|
|
sdiskio = namedtuple('sdiskio', ['read_count', 'write_count',
|
|
'read_bytes', 'write_bytes',
|
|
'read_time', 'write_time',
|
|
'busy_time'])
|
|
else:
|
|
sdiskio = namedtuple('sdiskio', ['read_count', 'write_count',
|
|
'read_bytes', 'write_bytes'])
|
|
|
|
|
|
# =====================================================================
|
|
# --- memory
|
|
# =====================================================================
|
|
|
|
|
|
def virtual_memory():
|
|
"""System virtual memory as a namedtuple."""
|
|
mem = cext.virtual_mem()
|
|
total, free, active, inactive, wired, cached, buffers, shared = mem
|
|
if NETBSD:
|
|
# On NetBSD buffers and shared mem is determined via /proc.
|
|
# The C ext set them to 0.
|
|
with open('/proc/meminfo', 'rb') as f:
|
|
for line in f:
|
|
if line.startswith(b'Buffers:'):
|
|
buffers = int(line.split()[1]) * 1024
|
|
elif line.startswith(b'MemShared:'):
|
|
shared = int(line.split()[1]) * 1024
|
|
avail = inactive + cached + free
|
|
used = active + wired + cached
|
|
percent = usage_percent((total - avail), total, round_=1)
|
|
return svmem(total, avail, percent, used, free,
|
|
active, inactive, buffers, cached, shared, wired)
|
|
|
|
|
|
def swap_memory():
|
|
"""System swap memory as (total, used, free, sin, sout) namedtuple."""
|
|
total, used, free, sin, sout = cext.swap_mem()
|
|
percent = usage_percent(used, total, round_=1)
|
|
return _common.sswap(total, used, free, percent, sin, sout)
|
|
|
|
|
|
# =====================================================================
|
|
# --- CPU
|
|
# =====================================================================
|
|
|
|
|
|
def cpu_times():
|
|
"""Return system per-CPU times as a namedtuple"""
|
|
user, nice, system, idle, irq = cext.cpu_times()
|
|
return scputimes(user, nice, system, idle, irq)
|
|
|
|
|
|
if HAS_PER_CPU_TIMES:
|
|
def per_cpu_times():
|
|
"""Return system CPU times as a namedtuple"""
|
|
ret = []
|
|
for cpu_t in cext.per_cpu_times():
|
|
user, nice, system, idle, irq = cpu_t
|
|
item = scputimes(user, nice, system, idle, irq)
|
|
ret.append(item)
|
|
return ret
|
|
else:
|
|
# XXX
|
|
# Ok, this is very dirty.
|
|
# On FreeBSD < 8 we cannot gather per-cpu information, see:
|
|
# https://github.com/giampaolo/psutil/issues/226
|
|
# If num cpus > 1, on first call we return single cpu times to avoid a
|
|
# crash at psutil import time.
|
|
# Next calls will fail with NotImplementedError
|
|
def per_cpu_times():
|
|
"""Return system CPU times as a namedtuple"""
|
|
if cpu_count_logical() == 1:
|
|
return [cpu_times()]
|
|
if per_cpu_times.__called__:
|
|
raise NotImplementedError("supported only starting from FreeBSD 8")
|
|
per_cpu_times.__called__ = True
|
|
return [cpu_times()]
|
|
|
|
per_cpu_times.__called__ = False
|
|
|
|
|
|
def cpu_count_logical():
|
|
"""Return the number of logical CPUs in the system."""
|
|
return cext.cpu_count_logical()
|
|
|
|
|
|
if OPENBSD or NETBSD:
|
|
def cpu_count_physical():
|
|
# OpenBSD and NetBSD do not implement this.
|
|
return 1 if cpu_count_logical() == 1 else None
|
|
else:
|
|
def cpu_count_physical():
|
|
"""Return the number of physical CPUs in the system."""
|
|
# From the C module we'll get an XML string similar to this:
|
|
# http://manpages.ubuntu.com/manpages/precise/man4/smp.4freebsd.html
|
|
# We may get None in case "sysctl kern.sched.topology_spec"
|
|
# is not supported on this BSD version, in which case we'll mimic
|
|
# os.cpu_count() and return None.
|
|
ret = None
|
|
s = cext.cpu_count_phys()
|
|
if s is not None:
|
|
# get rid of padding chars appended at the end of the string
|
|
index = s.rfind("</groups>")
|
|
if index != -1:
|
|
s = s[:index + 9]
|
|
root = ET.fromstring(s)
|
|
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.
|
|
if cpu_count_logical() == 1:
|
|
return 1
|
|
return ret
|
|
|
|
|
|
def cpu_stats():
|
|
"""Return various CPU stats as a named tuple."""
|
|
if FREEBSD:
|
|
# Note: the C ext is returning some metrics we are not exposing:
|
|
# traps.
|
|
ctxsw, intrs, soft_intrs, syscalls, traps = cext.cpu_stats()
|
|
elif NETBSD:
|
|
# XXX
|
|
# Note about intrs: the C extension returns 0. intrs
|
|
# can be determined via /proc/stat; it has the same value as
|
|
# soft_intrs thought so the kernel is faking it (?).
|
|
#
|
|
# Note about syscalls: the C extension always sets it to 0 (?).
|
|
#
|
|
# Note: the C ext is returning some metrics we are not exposing:
|
|
# traps, faults and forks.
|
|
ctxsw, intrs, soft_intrs, syscalls, traps, faults, forks = \
|
|
cext.cpu_stats()
|
|
with open('/proc/stat', 'rb') as f:
|
|
for line in f:
|
|
if line.startswith(b'intr'):
|
|
intrs = int(line.split()[1])
|
|
elif OPENBSD:
|
|
# Note: the C ext is returning some metrics we are not exposing:
|
|
# traps, faults and forks.
|
|
ctxsw, intrs, soft_intrs, syscalls, traps, faults, forks = \
|
|
cext.cpu_stats()
|
|
return _common.scpustats(ctxsw, intrs, soft_intrs, syscalls)
|
|
|
|
|
|
# =====================================================================
|
|
# --- disks
|
|
# =====================================================================
|
|
|
|
|
|
def disk_partitions(all=False):
|
|
"""Return mounted disk partitions as a list of namedtuples.
|
|
'all' argument is ignored, see:
|
|
https://github.com/giampaolo/psutil/issues/906
|
|
"""
|
|
retlist = []
|
|
partitions = cext.disk_partitions()
|
|
for partition in partitions:
|
|
device, mountpoint, fstype, opts = partition
|
|
ntuple = _common.sdiskpart(device, mountpoint, fstype, opts)
|
|
retlist.append(ntuple)
|
|
return retlist
|
|
|
|
|
|
disk_usage = _psposix.disk_usage
|
|
disk_io_counters = cext.disk_io_counters
|
|
|
|
|
|
# =====================================================================
|
|
# --- network
|
|
# =====================================================================
|
|
|
|
|
|
net_io_counters = cext.net_io_counters
|
|
net_if_addrs = cext_posix.net_if_addrs
|
|
|
|
|
|
def net_if_stats():
|
|
"""Get NIC stats (isup, duplex, speed, mtu)."""
|
|
names = net_io_counters().keys()
|
|
ret = {}
|
|
for name in names:
|
|
try:
|
|
mtu = cext_posix.net_if_mtu(name)
|
|
isup = cext_posix.net_if_flags(name)
|
|
duplex, speed = cext_posix.net_if_duplex_speed(name)
|
|
except OSError as err:
|
|
# https://github.com/giampaolo/psutil/issues/1279
|
|
if err.errno != errno.ENODEV:
|
|
raise
|
|
else:
|
|
if hasattr(_common, 'NicDuplex'):
|
|
duplex = _common.NicDuplex(duplex)
|
|
ret[name] = _common.snicstats(isup, duplex, speed, mtu)
|
|
return ret
|
|
|
|
|
|
def net_connections(kind):
|
|
"""System-wide network connections."""
|
|
if OPENBSD:
|
|
ret = []
|
|
for pid in pids():
|
|
try:
|
|
cons = Process(pid).connections(kind)
|
|
except (NoSuchProcess, ZombieProcess):
|
|
continue
|
|
else:
|
|
for conn in cons:
|
|
conn = list(conn)
|
|
conn.append(pid)
|
|
ret.append(_common.sconn(*conn))
|
|
return ret
|
|
|
|
if kind not in _common.conn_tmap:
|
|
raise ValueError("invalid %r kind argument; choose between %s"
|
|
% (kind, ', '.join([repr(x) for x in conn_tmap])))
|
|
families, types = conn_tmap[kind]
|
|
ret = set()
|
|
if NETBSD:
|
|
rawlist = cext.net_connections(-1)
|
|
else:
|
|
rawlist = cext.net_connections()
|
|
for item in rawlist:
|
|
fd, fam, type, laddr, raddr, status, pid = item
|
|
# TODO: apply filter at C level
|
|
if fam in families and type in types:
|
|
nt = conn_to_ntuple(fd, fam, type, laddr, raddr, status,
|
|
TCP_STATUSES, pid)
|
|
ret.add(nt)
|
|
return list(ret)
|
|
|
|
|
|
# =====================================================================
|
|
# --- sensors
|
|
# =====================================================================
|
|
|
|
|
|
if FREEBSD:
|
|
|
|
def sensors_battery():
|
|
"""Return battery info."""
|
|
try:
|
|
percent, minsleft, power_plugged = cext.sensors_battery()
|
|
except NotImplementedError:
|
|
# See: https://github.com/giampaolo/psutil/issues/1074
|
|
return None
|
|
power_plugged = power_plugged == 1
|
|
if power_plugged:
|
|
secsleft = _common.POWER_TIME_UNLIMITED
|
|
elif minsleft == -1:
|
|
secsleft = _common.POWER_TIME_UNKNOWN
|
|
else:
|
|
secsleft = minsleft * 60
|
|
return _common.sbattery(percent, secsleft, power_plugged)
|
|
|
|
def sensors_temperatures():
|
|
"Return CPU cores temperatures if available, else an empty dict."
|
|
ret = defaultdict(list)
|
|
num_cpus = cpu_count_logical()
|
|
for cpu in range(num_cpus):
|
|
try:
|
|
current, high = cext.sensors_cpu_temperature(cpu)
|
|
if high <= 0:
|
|
high = None
|
|
name = "Core %s" % cpu
|
|
ret["coretemp"].append(
|
|
_common.shwtemp(name, current, high, high))
|
|
except NotImplementedError:
|
|
pass
|
|
|
|
return ret
|
|
|
|
def cpu_freq():
|
|
"""Return frequency metrics for CPUs. As of Dec 2018 only
|
|
CPU 0 appears to be supported by FreeBSD and all other cores
|
|
match the frequency of CPU 0.
|
|
"""
|
|
ret = []
|
|
num_cpus = cpu_count_logical()
|
|
for cpu in range(num_cpus):
|
|
try:
|
|
current, available_freq = cext.cpu_frequency(cpu)
|
|
except NotImplementedError:
|
|
continue
|
|
if available_freq:
|
|
try:
|
|
min_freq = int(available_freq.split(" ")[-1].split("/")[0])
|
|
except(IndexError, ValueError):
|
|
min_freq = None
|
|
try:
|
|
max_freq = int(available_freq.split(" ")[0].split("/")[0])
|
|
except(IndexError, ValueError):
|
|
max_freq = None
|
|
ret.append(_common.scpufreq(current, min_freq, max_freq))
|
|
return ret
|
|
|
|
|
|
# =====================================================================
|
|
# --- other system functions
|
|
# =====================================================================
|
|
|
|
|
|
def boot_time():
|
|
"""The system boot time expressed in seconds since the epoch."""
|
|
return cext.boot_time()
|
|
|
|
|
|
def users():
|
|
"""Return currently connected users as a list of namedtuples."""
|
|
retlist = []
|
|
rawlist = cext.users()
|
|
for item in rawlist:
|
|
user, tty, hostname, tstamp, pid = item
|
|
if pid == -1:
|
|
assert OPENBSD
|
|
pid = None
|
|
if tty == '~':
|
|
continue # reboot or shutdown
|
|
nt = _common.suser(user, tty or None, hostname, tstamp, pid)
|
|
retlist.append(nt)
|
|
return retlist
|
|
|
|
|
|
# =====================================================================
|
|
# --- processes
|
|
# =====================================================================
|
|
|
|
|
|
@memoize
|
|
def _pid_0_exists():
|
|
try:
|
|
Process(0).name()
|
|
except NoSuchProcess:
|
|
return False
|
|
except AccessDenied:
|
|
return True
|
|
else:
|
|
return True
|
|
|
|
|
|
def pids():
|
|
"""Returns a list of PIDs currently running on the system."""
|
|
ret = cext.pids()
|
|
if OPENBSD and (0 not in ret) and _pid_0_exists():
|
|
# On OpenBSD the kernel does not return PID 0 (neither does
|
|
# ps) but it's actually querable (Process(0) will succeed).
|
|
ret.insert(0, 0)
|
|
return ret
|
|
|
|
|
|
if OPENBSD or NETBSD:
|
|
def pid_exists(pid):
|
|
"""Return True if pid exists."""
|
|
exists = _psposix.pid_exists(pid)
|
|
if not exists:
|
|
# We do this because _psposix.pid_exists() lies in case of
|
|
# zombie processes.
|
|
return pid in pids()
|
|
else:
|
|
return True
|
|
else:
|
|
pid_exists = _psposix.pid_exists
|
|
|
|
|
|
def is_zombie(pid):
|
|
try:
|
|
st = cext.proc_oneshot_info(pid)[kinfo_proc_map['status']]
|
|
return st == cext.SZOMB
|
|
except Exception:
|
|
return False
|
|
|
|
|
|
def wrap_exceptions(fun):
|
|
"""Decorator which translates bare OSError exceptions into
|
|
NoSuchProcess and AccessDenied.
|
|
"""
|
|
@functools.wraps(fun)
|
|
def wrapper(self, *args, **kwargs):
|
|
try:
|
|
return fun(self, *args, **kwargs)
|
|
except ProcessLookupError:
|
|
if not pid_exists(self.pid):
|
|
raise NoSuchProcess(self.pid, self._name)
|
|
else:
|
|
raise ZombieProcess(self.pid, self._name, self._ppid)
|
|
except PermissionError:
|
|
raise AccessDenied(self.pid, self._name)
|
|
except OSError:
|
|
if self.pid == 0:
|
|
if 0 in pids():
|
|
raise AccessDenied(self.pid, self._name)
|
|
else:
|
|
raise
|
|
raise
|
|
return wrapper
|
|
|
|
|
|
@contextlib.contextmanager
|
|
def wrap_exceptions_procfs(inst):
|
|
"""Same as above, for routines relying on reading /proc fs."""
|
|
try:
|
|
yield
|
|
except (ProcessLookupError, FileNotFoundError):
|
|
# 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 not pid_exists(inst.pid):
|
|
raise NoSuchProcess(inst.pid, inst._name)
|
|
else:
|
|
raise ZombieProcess(inst.pid, inst._name, inst._ppid)
|
|
except PermissionError:
|
|
raise AccessDenied(inst.pid, inst._name)
|
|
|
|
|
|
class Process(object):
|
|
"""Wrapper class around underlying C implementation."""
|
|
|
|
__slots__ = ["pid", "_name", "_ppid", "_cache"]
|
|
|
|
def __init__(self, pid):
|
|
self.pid = pid
|
|
self._name = None
|
|
self._ppid = None
|
|
|
|
def _assert_alive(self):
|
|
"""Raise NSP if the process disappeared on us."""
|
|
# For those C function who do not raise NSP, possibly returning
|
|
# incorrect or incomplete result.
|
|
cext.proc_name(self.pid)
|
|
|
|
@wrap_exceptions
|
|
@memoize_when_activated
|
|
def oneshot(self):
|
|
"""Retrieves multiple process info in one shot as a raw tuple."""
|
|
ret = cext.proc_oneshot_info(self.pid)
|
|
assert len(ret) == len(kinfo_proc_map)
|
|
return ret
|
|
|
|
def oneshot_enter(self):
|
|
self.oneshot.cache_activate(self)
|
|
|
|
def oneshot_exit(self):
|
|
self.oneshot.cache_deactivate(self)
|
|
|
|
@wrap_exceptions
|
|
def name(self):
|
|
name = self.oneshot()[kinfo_proc_map['name']]
|
|
return name if name is not None else cext.proc_name(self.pid)
|
|
|
|
@wrap_exceptions
|
|
def exe(self):
|
|
if FREEBSD:
|
|
if self.pid == 0:
|
|
return '' # else NSP
|
|
return cext.proc_exe(self.pid)
|
|
elif NETBSD:
|
|
if self.pid == 0:
|
|
# /proc/0 dir exists but /proc/0/exe doesn't
|
|
return ""
|
|
with wrap_exceptions_procfs(self):
|
|
return os.readlink("/proc/%s/exe" % self.pid)
|
|
else:
|
|
# OpenBSD: exe cannot be determined; references:
|
|
# https://chromium.googlesource.com/chromium/src/base/+/
|
|
# master/base_paths_posix.cc
|
|
# We try our best guess by using which against the first
|
|
# cmdline arg (may return None).
|
|
cmdline = self.cmdline()
|
|
if cmdline:
|
|
return which(cmdline[0]) or ""
|
|
else:
|
|
return ""
|
|
|
|
@wrap_exceptions
|
|
def cmdline(self):
|
|
if OPENBSD and self.pid == 0:
|
|
return [] # ...else it crashes
|
|
elif NETBSD:
|
|
# XXX - most of the times the underlying sysctl() call on Net
|
|
# and Open BSD returns a truncated string.
|
|
# Also /proc/pid/cmdline behaves the same so it looks
|
|
# like this is a kernel bug.
|
|
try:
|
|
return cext.proc_cmdline(self.pid)
|
|
except OSError as err:
|
|
if err.errno == errno.EINVAL:
|
|
if is_zombie(self.pid):
|
|
raise ZombieProcess(self.pid, self._name, self._ppid)
|
|
elif not pid_exists(self.pid):
|
|
raise NoSuchProcess(self.pid, self._name, self._ppid)
|
|
else:
|
|
# XXX: this happens with unicode tests. It means the C
|
|
# routine is unable to decode invalid unicode chars.
|
|
return []
|
|
else:
|
|
raise
|
|
else:
|
|
return cext.proc_cmdline(self.pid)
|
|
|
|
@wrap_exceptions
|
|
def terminal(self):
|
|
tty_nr = self.oneshot()[kinfo_proc_map['ttynr']]
|
|
tmap = _psposix.get_terminal_map()
|
|
try:
|
|
return tmap[tty_nr]
|
|
except KeyError:
|
|
return None
|
|
|
|
@wrap_exceptions
|
|
def ppid(self):
|
|
self._ppid = self.oneshot()[kinfo_proc_map['ppid']]
|
|
return self._ppid
|
|
|
|
@wrap_exceptions
|
|
def uids(self):
|
|
rawtuple = self.oneshot()
|
|
return _common.puids(
|
|
rawtuple[kinfo_proc_map['real_uid']],
|
|
rawtuple[kinfo_proc_map['effective_uid']],
|
|
rawtuple[kinfo_proc_map['saved_uid']])
|
|
|
|
@wrap_exceptions
|
|
def gids(self):
|
|
rawtuple = self.oneshot()
|
|
return _common.pgids(
|
|
rawtuple[kinfo_proc_map['real_gid']],
|
|
rawtuple[kinfo_proc_map['effective_gid']],
|
|
rawtuple[kinfo_proc_map['saved_gid']])
|
|
|
|
@wrap_exceptions
|
|
def cpu_times(self):
|
|
rawtuple = self.oneshot()
|
|
return _common.pcputimes(
|
|
rawtuple[kinfo_proc_map['user_time']],
|
|
rawtuple[kinfo_proc_map['sys_time']],
|
|
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()
|
|
return pmem(
|
|
rawtuple[kinfo_proc_map['rss']],
|
|
rawtuple[kinfo_proc_map['vms']],
|
|
rawtuple[kinfo_proc_map['memtext']],
|
|
rawtuple[kinfo_proc_map['memdata']],
|
|
rawtuple[kinfo_proc_map['memstack']])
|
|
|
|
memory_full_info = memory_info
|
|
|
|
@wrap_exceptions
|
|
def create_time(self):
|
|
return self.oneshot()[kinfo_proc_map['create_time']]
|
|
|
|
@wrap_exceptions
|
|
def num_threads(self):
|
|
if HAS_PROC_NUM_THREADS:
|
|
# FreeBSD
|
|
return cext.proc_num_threads(self.pid)
|
|
else:
|
|
return len(self.threads())
|
|
|
|
@wrap_exceptions
|
|
def num_ctx_switches(self):
|
|
rawtuple = self.oneshot()
|
|
return _common.pctxsw(
|
|
rawtuple[kinfo_proc_map['ctx_switches_vol']],
|
|
rawtuple[kinfo_proc_map['ctx_switches_unvol']])
|
|
|
|
@wrap_exceptions
|
|
def threads(self):
|
|
# Note: on OpenSBD this (/dev/mem) requires root access.
|
|
rawlist = cext.proc_threads(self.pid)
|
|
retlist = []
|
|
for thread_id, utime, stime in rawlist:
|
|
ntuple = _common.pthread(thread_id, utime, stime)
|
|
retlist.append(ntuple)
|
|
if OPENBSD:
|
|
self._assert_alive()
|
|
return retlist
|
|
|
|
@wrap_exceptions
|
|
def connections(self, kind='inet'):
|
|
if kind not in conn_tmap:
|
|
raise ValueError("invalid %r kind argument; choose between %s"
|
|
% (kind, ', '.join([repr(x) for x in conn_tmap])))
|
|
|
|
if NETBSD:
|
|
families, types = conn_tmap[kind]
|
|
ret = []
|
|
rawlist = cext.net_connections(self.pid)
|
|
for item in rawlist:
|
|
fd, fam, type, laddr, raddr, status, pid = item
|
|
assert pid == self.pid
|
|
if fam in families and type in types:
|
|
nt = conn_to_ntuple(fd, fam, type, laddr, raddr, status,
|
|
TCP_STATUSES)
|
|
ret.append(nt)
|
|
self._assert_alive()
|
|
return list(ret)
|
|
|
|
families, types = conn_tmap[kind]
|
|
rawlist = cext.proc_connections(self.pid, families, types)
|
|
ret = []
|
|
for item in rawlist:
|
|
fd, fam, type, laddr, raddr, status = item
|
|
nt = conn_to_ntuple(fd, fam, type, laddr, raddr, status,
|
|
TCP_STATUSES)
|
|
ret.append(nt)
|
|
|
|
if OPENBSD:
|
|
self._assert_alive()
|
|
|
|
return ret
|
|
|
|
@wrap_exceptions
|
|
def wait(self, timeout=None):
|
|
return _psposix.wait_pid(self.pid, timeout, self._name)
|
|
|
|
@wrap_exceptions
|
|
def nice_get(self):
|
|
return cext_posix.getpriority(self.pid)
|
|
|
|
@wrap_exceptions
|
|
def nice_set(self, value):
|
|
return cext_posix.setpriority(self.pid, value)
|
|
|
|
@wrap_exceptions
|
|
def status(self):
|
|
code = self.oneshot()[kinfo_proc_map['status']]
|
|
# XXX is '?' legit? (we're not supposed to return it anyway)
|
|
return PROC_STATUSES.get(code, '?')
|
|
|
|
@wrap_exceptions
|
|
def io_counters(self):
|
|
rawtuple = self.oneshot()
|
|
return _common.pio(
|
|
rawtuple[kinfo_proc_map['read_io_count']],
|
|
rawtuple[kinfo_proc_map['write_io_count']],
|
|
-1,
|
|
-1)
|
|
|
|
@wrap_exceptions
|
|
def cwd(self):
|
|
"""Return process current working directory."""
|
|
# sometimes we get an empty string, in which case we turn
|
|
# it into None
|
|
if OPENBSD and self.pid == 0:
|
|
return None # ...else it would raise EINVAL
|
|
elif NETBSD or HAS_PROC_OPEN_FILES:
|
|
# FreeBSD < 8 does not support functions based on
|
|
# kinfo_getfile() and kinfo_getvmmap()
|
|
return cext.proc_cwd(self.pid) or None
|
|
else:
|
|
raise NotImplementedError(
|
|
"supported only starting from FreeBSD 8" if
|
|
FREEBSD else "")
|
|
|
|
nt_mmap_grouped = namedtuple(
|
|
'mmap', 'path rss, private, ref_count, shadow_count')
|
|
nt_mmap_ext = namedtuple(
|
|
'mmap', 'addr, perms path rss, private, ref_count, shadow_count')
|
|
|
|
def _not_implemented(self):
|
|
raise NotImplementedError
|
|
|
|
# FreeBSD < 8 does not support functions based on kinfo_getfile()
|
|
# and kinfo_getvmmap()
|
|
if HAS_PROC_OPEN_FILES:
|
|
@wrap_exceptions
|
|
def open_files(self):
|
|
"""Return files opened by process as a list of namedtuples."""
|
|
rawlist = cext.proc_open_files(self.pid)
|
|
return [_common.popenfile(path, fd) for path, fd in rawlist]
|
|
else:
|
|
open_files = _not_implemented
|
|
|
|
# FreeBSD < 8 does not support functions based on kinfo_getfile()
|
|
# and kinfo_getvmmap()
|
|
if HAS_PROC_NUM_FDS:
|
|
@wrap_exceptions
|
|
def num_fds(self):
|
|
"""Return the number of file descriptors opened by this process."""
|
|
ret = cext.proc_num_fds(self.pid)
|
|
if NETBSD:
|
|
self._assert_alive()
|
|
return ret
|
|
else:
|
|
num_fds = _not_implemented
|
|
|
|
# --- FreeBSD only APIs
|
|
|
|
if FREEBSD:
|
|
|
|
@wrap_exceptions
|
|
def cpu_affinity_get(self):
|
|
return cext.proc_cpu_affinity_get(self.pid)
|
|
|
|
@wrap_exceptions
|
|
def cpu_affinity_set(self, cpus):
|
|
# Pre-emptively check if CPUs are valid because the C
|
|
# function has a weird behavior in case of invalid CPUs,
|
|
# see: https://github.com/giampaolo/psutil/issues/586
|
|
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))
|
|
try:
|
|
cext.proc_cpu_affinity_set(self.pid, cpus)
|
|
except OSError as err:
|
|
# 'man cpuset_setaffinity' about EDEADLK:
|
|
# <<the call would leave a thread without a valid CPU to run
|
|
# on because the set does not overlap with the thread's
|
|
# anonymous mask>>
|
|
if err.errno in (errno.EINVAL, errno.EDEADLK):
|
|
for cpu in cpus:
|
|
if cpu not in allcpus:
|
|
raise ValueError(
|
|
"invalid CPU #%i (choose between %s)" % (
|
|
cpu, allcpus))
|
|
raise
|
|
|
|
@wrap_exceptions
|
|
def memory_maps(self):
|
|
return cext.proc_memory_maps(self.pid)
|