You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
900 lines
28 KiB
Python
900 lines
28 KiB
Python
2 years ago
|
# 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.
|
||
|
|
||
|
"""Common objects shared by __init__.py and _ps*.py modules."""
|
||
|
|
||
|
# Note: this module is imported by setup.py so it should not import
|
||
|
# psutil or third-party modules.
|
||
|
|
||
|
from __future__ import division
|
||
|
from __future__ import print_function
|
||
|
|
||
|
import collections
|
||
|
import contextlib
|
||
|
import errno
|
||
|
import functools
|
||
|
import os
|
||
|
import socket
|
||
|
import stat
|
||
|
import sys
|
||
|
import threading
|
||
|
import warnings
|
||
|
from collections import namedtuple
|
||
|
from socket import AF_INET
|
||
|
from socket import SOCK_DGRAM
|
||
|
from socket import SOCK_STREAM
|
||
|
|
||
|
|
||
|
try:
|
||
|
from socket import AF_INET6
|
||
|
except ImportError:
|
||
|
AF_INET6 = None
|
||
|
try:
|
||
|
from socket import AF_UNIX
|
||
|
except ImportError:
|
||
|
AF_UNIX = None
|
||
|
|
||
|
if sys.version_info >= (3, 4):
|
||
|
import enum
|
||
|
else:
|
||
|
enum = None
|
||
|
|
||
|
|
||
|
# can't take it from _common.py as this script is imported by setup.py
|
||
|
PY3 = sys.version_info[0] == 3
|
||
|
PSUTIL_DEBUG = bool(os.getenv('PSUTIL_DEBUG', 0))
|
||
|
_DEFAULT = object()
|
||
|
|
||
|
__all__ = [
|
||
|
# OS constants
|
||
|
'FREEBSD', 'BSD', 'LINUX', 'NETBSD', 'OPENBSD', 'MACOS', 'OSX', 'POSIX',
|
||
|
'SUNOS', 'WINDOWS',
|
||
|
# connection constants
|
||
|
'CONN_CLOSE', 'CONN_CLOSE_WAIT', 'CONN_CLOSING', 'CONN_ESTABLISHED',
|
||
|
'CONN_FIN_WAIT1', 'CONN_FIN_WAIT2', 'CONN_LAST_ACK', 'CONN_LISTEN',
|
||
|
'CONN_NONE', 'CONN_SYN_RECV', 'CONN_SYN_SENT', 'CONN_TIME_WAIT',
|
||
|
# net constants
|
||
|
'NIC_DUPLEX_FULL', 'NIC_DUPLEX_HALF', 'NIC_DUPLEX_UNKNOWN',
|
||
|
# process status constants
|
||
|
'STATUS_DEAD', 'STATUS_DISK_SLEEP', 'STATUS_IDLE', 'STATUS_LOCKED',
|
||
|
'STATUS_RUNNING', 'STATUS_SLEEPING', 'STATUS_STOPPED', 'STATUS_SUSPENDED',
|
||
|
'STATUS_TRACING_STOP', 'STATUS_WAITING', 'STATUS_WAKE_KILL',
|
||
|
'STATUS_WAKING', 'STATUS_ZOMBIE', 'STATUS_PARKED',
|
||
|
# other constants
|
||
|
'ENCODING', 'ENCODING_ERRS', 'AF_INET6',
|
||
|
# named tuples
|
||
|
'pconn', 'pcputimes', 'pctxsw', 'pgids', 'pio', 'pionice', 'popenfile',
|
||
|
'pthread', 'puids', 'sconn', 'scpustats', 'sdiskio', 'sdiskpart',
|
||
|
'sdiskusage', 'snetio', 'snicaddr', 'snicstats', 'sswap', 'suser',
|
||
|
# utility functions
|
||
|
'conn_tmap', 'deprecated_method', 'isfile_strict', 'memoize',
|
||
|
'parse_environ_block', 'path_exists_strict', 'usage_percent',
|
||
|
'supports_ipv6', 'sockfam_to_enum', 'socktype_to_enum', "wrap_numbers",
|
||
|
'open_text', 'open_binary', 'cat', 'bcat',
|
||
|
'bytes2human', 'conn_to_ntuple', 'debug',
|
||
|
# shell utils
|
||
|
'hilite', 'term_supports_colors', 'print_color',
|
||
|
]
|
||
|
|
||
|
|
||
|
# ===================================================================
|
||
|
# --- OS constants
|
||
|
# ===================================================================
|
||
|
|
||
|
|
||
|
POSIX = os.name == "posix"
|
||
|
WINDOWS = os.name == "nt"
|
||
|
LINUX = sys.platform.startswith("linux")
|
||
|
MACOS = sys.platform.startswith("darwin")
|
||
|
OSX = MACOS # deprecated alias
|
||
|
FREEBSD = sys.platform.startswith(("freebsd", "midnightbsd"))
|
||
|
OPENBSD = sys.platform.startswith("openbsd")
|
||
|
NETBSD = sys.platform.startswith("netbsd")
|
||
|
BSD = FREEBSD or OPENBSD or NETBSD
|
||
|
SUNOS = sys.platform.startswith(("sunos", "solaris"))
|
||
|
AIX = sys.platform.startswith("aix")
|
||
|
|
||
|
|
||
|
# ===================================================================
|
||
|
# --- API constants
|
||
|
# ===================================================================
|
||
|
|
||
|
|
||
|
# Process.status()
|
||
|
STATUS_RUNNING = "running"
|
||
|
STATUS_SLEEPING = "sleeping"
|
||
|
STATUS_DISK_SLEEP = "disk-sleep"
|
||
|
STATUS_STOPPED = "stopped"
|
||
|
STATUS_TRACING_STOP = "tracing-stop"
|
||
|
STATUS_ZOMBIE = "zombie"
|
||
|
STATUS_DEAD = "dead"
|
||
|
STATUS_WAKE_KILL = "wake-kill"
|
||
|
STATUS_WAKING = "waking"
|
||
|
STATUS_IDLE = "idle" # Linux, macOS, FreeBSD
|
||
|
STATUS_LOCKED = "locked" # FreeBSD
|
||
|
STATUS_WAITING = "waiting" # FreeBSD
|
||
|
STATUS_SUSPENDED = "suspended" # NetBSD
|
||
|
STATUS_PARKED = "parked" # Linux
|
||
|
|
||
|
# Process.connections() and psutil.net_connections()
|
||
|
CONN_ESTABLISHED = "ESTABLISHED"
|
||
|
CONN_SYN_SENT = "SYN_SENT"
|
||
|
CONN_SYN_RECV = "SYN_RECV"
|
||
|
CONN_FIN_WAIT1 = "FIN_WAIT1"
|
||
|
CONN_FIN_WAIT2 = "FIN_WAIT2"
|
||
|
CONN_TIME_WAIT = "TIME_WAIT"
|
||
|
CONN_CLOSE = "CLOSE"
|
||
|
CONN_CLOSE_WAIT = "CLOSE_WAIT"
|
||
|
CONN_LAST_ACK = "LAST_ACK"
|
||
|
CONN_LISTEN = "LISTEN"
|
||
|
CONN_CLOSING = "CLOSING"
|
||
|
CONN_NONE = "NONE"
|
||
|
|
||
|
# net_if_stats()
|
||
|
if enum is None:
|
||
|
NIC_DUPLEX_FULL = 2
|
||
|
NIC_DUPLEX_HALF = 1
|
||
|
NIC_DUPLEX_UNKNOWN = 0
|
||
|
else:
|
||
|
class NicDuplex(enum.IntEnum):
|
||
|
NIC_DUPLEX_FULL = 2
|
||
|
NIC_DUPLEX_HALF = 1
|
||
|
NIC_DUPLEX_UNKNOWN = 0
|
||
|
|
||
|
globals().update(NicDuplex.__members__)
|
||
|
|
||
|
# sensors_battery()
|
||
|
if enum is None:
|
||
|
POWER_TIME_UNKNOWN = -1
|
||
|
POWER_TIME_UNLIMITED = -2
|
||
|
else:
|
||
|
class BatteryTime(enum.IntEnum):
|
||
|
POWER_TIME_UNKNOWN = -1
|
||
|
POWER_TIME_UNLIMITED = -2
|
||
|
|
||
|
globals().update(BatteryTime.__members__)
|
||
|
|
||
|
# --- others
|
||
|
|
||
|
ENCODING = sys.getfilesystemencoding()
|
||
|
if not PY3:
|
||
|
ENCODING_ERRS = "replace"
|
||
|
else:
|
||
|
try:
|
||
|
ENCODING_ERRS = sys.getfilesystemencodeerrors() # py 3.6
|
||
|
except AttributeError:
|
||
|
ENCODING_ERRS = "surrogateescape" if POSIX else "replace"
|
||
|
|
||
|
|
||
|
# ===================================================================
|
||
|
# --- namedtuples
|
||
|
# ===================================================================
|
||
|
|
||
|
# --- for system functions
|
||
|
|
||
|
# psutil.swap_memory()
|
||
|
sswap = namedtuple('sswap', ['total', 'used', 'free', 'percent', 'sin',
|
||
|
'sout'])
|
||
|
# psutil.disk_usage()
|
||
|
sdiskusage = namedtuple('sdiskusage', ['total', 'used', 'free', 'percent'])
|
||
|
# psutil.disk_io_counters()
|
||
|
sdiskio = namedtuple('sdiskio', ['read_count', 'write_count',
|
||
|
'read_bytes', 'write_bytes',
|
||
|
'read_time', 'write_time'])
|
||
|
# psutil.disk_partitions()
|
||
|
sdiskpart = namedtuple('sdiskpart', ['device', 'mountpoint', 'fstype', 'opts',
|
||
|
'maxfile', 'maxpath'])
|
||
|
# psutil.net_io_counters()
|
||
|
snetio = namedtuple('snetio', ['bytes_sent', 'bytes_recv',
|
||
|
'packets_sent', 'packets_recv',
|
||
|
'errin', 'errout',
|
||
|
'dropin', 'dropout'])
|
||
|
# psutil.users()
|
||
|
suser = namedtuple('suser', ['name', 'terminal', 'host', 'started', 'pid'])
|
||
|
# psutil.net_connections()
|
||
|
sconn = namedtuple('sconn', ['fd', 'family', 'type', 'laddr', 'raddr',
|
||
|
'status', 'pid'])
|
||
|
# psutil.net_if_addrs()
|
||
|
snicaddr = namedtuple('snicaddr',
|
||
|
['family', 'address', 'netmask', 'broadcast', 'ptp'])
|
||
|
# psutil.net_if_stats()
|
||
|
snicstats = namedtuple('snicstats',
|
||
|
['isup', 'duplex', 'speed', 'mtu', 'flags'])
|
||
|
# psutil.cpu_stats()
|
||
|
scpustats = namedtuple(
|
||
|
'scpustats', ['ctx_switches', 'interrupts', 'soft_interrupts', 'syscalls'])
|
||
|
# psutil.cpu_freq()
|
||
|
scpufreq = namedtuple('scpufreq', ['current', 'min', 'max'])
|
||
|
# psutil.sensors_temperatures()
|
||
|
shwtemp = namedtuple(
|
||
|
'shwtemp', ['label', 'current', 'high', 'critical'])
|
||
|
# psutil.sensors_battery()
|
||
|
sbattery = namedtuple('sbattery', ['percent', 'secsleft', 'power_plugged'])
|
||
|
# psutil.sensors_fans()
|
||
|
sfan = namedtuple('sfan', ['label', 'current'])
|
||
|
|
||
|
# --- for Process methods
|
||
|
|
||
|
# psutil.Process.cpu_times()
|
||
|
pcputimes = namedtuple('pcputimes',
|
||
|
['user', 'system', 'children_user', 'children_system'])
|
||
|
# psutil.Process.open_files()
|
||
|
popenfile = namedtuple('popenfile', ['path', 'fd'])
|
||
|
# psutil.Process.threads()
|
||
|
pthread = namedtuple('pthread', ['id', 'user_time', 'system_time'])
|
||
|
# psutil.Process.uids()
|
||
|
puids = namedtuple('puids', ['real', 'effective', 'saved'])
|
||
|
# psutil.Process.gids()
|
||
|
pgids = namedtuple('pgids', ['real', 'effective', 'saved'])
|
||
|
# psutil.Process.io_counters()
|
||
|
pio = namedtuple('pio', ['read_count', 'write_count',
|
||
|
'read_bytes', 'write_bytes'])
|
||
|
# psutil.Process.ionice()
|
||
|
pionice = namedtuple('pionice', ['ioclass', 'value'])
|
||
|
# psutil.Process.ctx_switches()
|
||
|
pctxsw = namedtuple('pctxsw', ['voluntary', 'involuntary'])
|
||
|
# psutil.Process.connections()
|
||
|
pconn = namedtuple('pconn', ['fd', 'family', 'type', 'laddr', 'raddr',
|
||
|
'status'])
|
||
|
|
||
|
# psutil.connections() and psutil.Process.connections()
|
||
|
addr = namedtuple('addr', ['ip', 'port'])
|
||
|
|
||
|
|
||
|
# ===================================================================
|
||
|
# --- Process.connections() 'kind' parameter mapping
|
||
|
# ===================================================================
|
||
|
|
||
|
|
||
|
conn_tmap = {
|
||
|
"all": ([AF_INET, AF_INET6, AF_UNIX], [SOCK_STREAM, SOCK_DGRAM]),
|
||
|
"tcp": ([AF_INET, AF_INET6], [SOCK_STREAM]),
|
||
|
"tcp4": ([AF_INET], [SOCK_STREAM]),
|
||
|
"udp": ([AF_INET, AF_INET6], [SOCK_DGRAM]),
|
||
|
"udp4": ([AF_INET], [SOCK_DGRAM]),
|
||
|
"inet": ([AF_INET, AF_INET6], [SOCK_STREAM, SOCK_DGRAM]),
|
||
|
"inet4": ([AF_INET], [SOCK_STREAM, SOCK_DGRAM]),
|
||
|
"inet6": ([AF_INET6], [SOCK_STREAM, SOCK_DGRAM]),
|
||
|
}
|
||
|
|
||
|
if AF_INET6 is not None:
|
||
|
conn_tmap.update({
|
||
|
"tcp6": ([AF_INET6], [SOCK_STREAM]),
|
||
|
"udp6": ([AF_INET6], [SOCK_DGRAM]),
|
||
|
})
|
||
|
|
||
|
if AF_UNIX is not None:
|
||
|
conn_tmap.update({
|
||
|
"unix": ([AF_UNIX], [SOCK_STREAM, SOCK_DGRAM]),
|
||
|
})
|
||
|
|
||
|
|
||
|
# =====================================================================
|
||
|
# --- Exceptions
|
||
|
# =====================================================================
|
||
|
|
||
|
|
||
|
class Error(Exception):
|
||
|
"""Base exception class. All other psutil exceptions inherit
|
||
|
from this one.
|
||
|
"""
|
||
|
__module__ = 'psutil'
|
||
|
|
||
|
def _infodict(self, attrs):
|
||
|
info = collections.OrderedDict()
|
||
|
for name in attrs:
|
||
|
value = getattr(self, name, None)
|
||
|
if value:
|
||
|
info[name] = value
|
||
|
elif name == "pid" and value == 0:
|
||
|
info[name] = value
|
||
|
return info
|
||
|
|
||
|
def __str__(self):
|
||
|
# invoked on `raise Error`
|
||
|
info = self._infodict(("pid", "ppid", "name"))
|
||
|
if info:
|
||
|
details = "(%s)" % ", ".join(
|
||
|
["%s=%r" % (k, v) for k, v in info.items()])
|
||
|
else:
|
||
|
details = None
|
||
|
return " ".join([x for x in (getattr(self, "msg", ""), details) if x])
|
||
|
|
||
|
def __repr__(self):
|
||
|
# invoked on `repr(Error)`
|
||
|
info = self._infodict(("pid", "ppid", "name", "seconds", "msg"))
|
||
|
details = ", ".join(["%s=%r" % (k, v) for k, v in info.items()])
|
||
|
return "psutil.%s(%s)" % (self.__class__.__name__, details)
|
||
|
|
||
|
|
||
|
class NoSuchProcess(Error):
|
||
|
"""Exception raised when a process with a certain PID doesn't
|
||
|
or no longer exists.
|
||
|
"""
|
||
|
__module__ = 'psutil'
|
||
|
|
||
|
def __init__(self, pid, name=None, msg=None):
|
||
|
Error.__init__(self)
|
||
|
self.pid = pid
|
||
|
self.name = name
|
||
|
self.msg = msg or "process no longer exists"
|
||
|
|
||
|
|
||
|
class ZombieProcess(NoSuchProcess):
|
||
|
"""Exception raised when querying a zombie process. This is
|
||
|
raised on macOS, BSD and Solaris only, and not always: depending
|
||
|
on the query the OS may be able to succeed anyway.
|
||
|
On Linux all zombie processes are querable (hence this is never
|
||
|
raised). Windows doesn't have zombie processes.
|
||
|
"""
|
||
|
__module__ = 'psutil'
|
||
|
|
||
|
def __init__(self, pid, name=None, ppid=None, msg=None):
|
||
|
NoSuchProcess.__init__(self, pid, name, msg)
|
||
|
self.ppid = ppid
|
||
|
self.msg = msg or "PID still exists but it's a zombie"
|
||
|
|
||
|
|
||
|
class AccessDenied(Error):
|
||
|
"""Exception raised when permission to perform an action is denied."""
|
||
|
__module__ = 'psutil'
|
||
|
|
||
|
def __init__(self, pid=None, name=None, msg=None):
|
||
|
Error.__init__(self)
|
||
|
self.pid = pid
|
||
|
self.name = name
|
||
|
self.msg = msg or ""
|
||
|
|
||
|
|
||
|
class TimeoutExpired(Error):
|
||
|
"""Raised on Process.wait(timeout) if timeout expires and process
|
||
|
is still alive.
|
||
|
"""
|
||
|
__module__ = 'psutil'
|
||
|
|
||
|
def __init__(self, seconds, pid=None, name=None):
|
||
|
Error.__init__(self)
|
||
|
self.seconds = seconds
|
||
|
self.pid = pid
|
||
|
self.name = name
|
||
|
self.msg = "timeout after %s seconds" % seconds
|
||
|
|
||
|
|
||
|
# ===================================================================
|
||
|
# --- utils
|
||
|
# ===================================================================
|
||
|
|
||
|
|
||
|
def usage_percent(used, total, round_=None):
|
||
|
"""Calculate percentage usage of 'used' against 'total'."""
|
||
|
try:
|
||
|
ret = (float(used) / total) * 100
|
||
|
except ZeroDivisionError:
|
||
|
return 0.0
|
||
|
else:
|
||
|
if round_ is not None:
|
||
|
ret = round(ret, round_)
|
||
|
return ret
|
||
|
|
||
|
|
||
|
def memoize(fun):
|
||
|
"""A simple memoize decorator for functions supporting (hashable)
|
||
|
positional arguments.
|
||
|
It also provides a cache_clear() function for clearing the cache:
|
||
|
|
||
|
>>> @memoize
|
||
|
... def foo()
|
||
|
... return 1
|
||
|
...
|
||
|
>>> foo()
|
||
|
1
|
||
|
>>> foo.cache_clear()
|
||
|
>>>
|
||
|
"""
|
||
|
@functools.wraps(fun)
|
||
|
def wrapper(*args, **kwargs):
|
||
|
key = (args, frozenset(sorted(kwargs.items())))
|
||
|
try:
|
||
|
return cache[key]
|
||
|
except KeyError:
|
||
|
ret = cache[key] = fun(*args, **kwargs)
|
||
|
return ret
|
||
|
|
||
|
def cache_clear():
|
||
|
"""Clear cache."""
|
||
|
cache.clear()
|
||
|
|
||
|
cache = {}
|
||
|
wrapper.cache_clear = cache_clear
|
||
|
return wrapper
|
||
|
|
||
|
|
||
|
def memoize_when_activated(fun):
|
||
|
"""A memoize decorator which is disabled by default. It can be
|
||
|
activated and deactivated on request.
|
||
|
For efficiency reasons it can be used only against class methods
|
||
|
accepting no arguments.
|
||
|
|
||
|
>>> class Foo:
|
||
|
... @memoize
|
||
|
... def foo()
|
||
|
... print(1)
|
||
|
...
|
||
|
>>> f = Foo()
|
||
|
>>> # deactivated (default)
|
||
|
>>> foo()
|
||
|
1
|
||
|
>>> foo()
|
||
|
1
|
||
|
>>>
|
||
|
>>> # activated
|
||
|
>>> foo.cache_activate(self)
|
||
|
>>> foo()
|
||
|
1
|
||
|
>>> foo()
|
||
|
>>> foo()
|
||
|
>>>
|
||
|
"""
|
||
|
@functools.wraps(fun)
|
||
|
def wrapper(self):
|
||
|
try:
|
||
|
# case 1: we previously entered oneshot() ctx
|
||
|
ret = self._cache[fun]
|
||
|
except AttributeError:
|
||
|
# case 2: we never entered oneshot() ctx
|
||
|
return fun(self)
|
||
|
except KeyError:
|
||
|
# case 3: we entered oneshot() ctx but there's no cache
|
||
|
# for this entry yet
|
||
|
ret = fun(self)
|
||
|
try:
|
||
|
self._cache[fun] = ret
|
||
|
except AttributeError:
|
||
|
# multi-threading race condition, see:
|
||
|
# https://github.com/giampaolo/psutil/issues/1948
|
||
|
pass
|
||
|
return ret
|
||
|
|
||
|
def cache_activate(proc):
|
||
|
"""Activate cache. Expects a Process instance. Cache will be
|
||
|
stored as a "_cache" instance attribute."""
|
||
|
proc._cache = {}
|
||
|
|
||
|
def cache_deactivate(proc):
|
||
|
"""Deactivate and clear cache."""
|
||
|
try:
|
||
|
del proc._cache
|
||
|
except AttributeError:
|
||
|
pass
|
||
|
|
||
|
wrapper.cache_activate = cache_activate
|
||
|
wrapper.cache_deactivate = cache_deactivate
|
||
|
return wrapper
|
||
|
|
||
|
|
||
|
def isfile_strict(path):
|
||
|
"""Same as os.path.isfile() but does not swallow EACCES / EPERM
|
||
|
exceptions, see:
|
||
|
http://mail.python.org/pipermail/python-dev/2012-June/120787.html
|
||
|
"""
|
||
|
try:
|
||
|
st = os.stat(path)
|
||
|
except OSError as err:
|
||
|
if err.errno in (errno.EPERM, errno.EACCES):
|
||
|
raise
|
||
|
return False
|
||
|
else:
|
||
|
return stat.S_ISREG(st.st_mode)
|
||
|
|
||
|
|
||
|
def path_exists_strict(path):
|
||
|
"""Same as os.path.exists() but does not swallow EACCES / EPERM
|
||
|
exceptions, see:
|
||
|
http://mail.python.org/pipermail/python-dev/2012-June/120787.html
|
||
|
"""
|
||
|
try:
|
||
|
os.stat(path)
|
||
|
except OSError as err:
|
||
|
if err.errno in (errno.EPERM, errno.EACCES):
|
||
|
raise
|
||
|
return False
|
||
|
else:
|
||
|
return True
|
||
|
|
||
|
|
||
|
@memoize
|
||
|
def supports_ipv6():
|
||
|
"""Return True if IPv6 is supported on this platform."""
|
||
|
if not socket.has_ipv6 or AF_INET6 is None:
|
||
|
return False
|
||
|
try:
|
||
|
sock = socket.socket(AF_INET6, socket.SOCK_STREAM)
|
||
|
with contextlib.closing(sock):
|
||
|
sock.bind(("::1", 0))
|
||
|
return True
|
||
|
except socket.error:
|
||
|
return False
|
||
|
|
||
|
|
||
|
def parse_environ_block(data):
|
||
|
"""Parse a C environ block of environment variables into a dictionary."""
|
||
|
# The block is usually raw data from the target process. It might contain
|
||
|
# trailing garbage and lines that do not look like assignments.
|
||
|
ret = {}
|
||
|
pos = 0
|
||
|
|
||
|
# localize global variable to speed up access.
|
||
|
WINDOWS_ = WINDOWS
|
||
|
while True:
|
||
|
next_pos = data.find("\0", pos)
|
||
|
# nul byte at the beginning or double nul byte means finish
|
||
|
if next_pos <= pos:
|
||
|
break
|
||
|
# there might not be an equals sign
|
||
|
equal_pos = data.find("=", pos, next_pos)
|
||
|
if equal_pos > pos:
|
||
|
key = data[pos:equal_pos]
|
||
|
value = data[equal_pos + 1:next_pos]
|
||
|
# Windows expects environment variables to be uppercase only
|
||
|
if WINDOWS_:
|
||
|
key = key.upper()
|
||
|
ret[key] = value
|
||
|
pos = next_pos + 1
|
||
|
|
||
|
return ret
|
||
|
|
||
|
|
||
|
def sockfam_to_enum(num):
|
||
|
"""Convert a numeric socket family value to an IntEnum member.
|
||
|
If it's not a known member, return the numeric value itself.
|
||
|
"""
|
||
|
if enum is None:
|
||
|
return num
|
||
|
else: # pragma: no cover
|
||
|
try:
|
||
|
return socket.AddressFamily(num)
|
||
|
except ValueError:
|
||
|
return num
|
||
|
|
||
|
|
||
|
def socktype_to_enum(num):
|
||
|
"""Convert a numeric socket type value to an IntEnum member.
|
||
|
If it's not a known member, return the numeric value itself.
|
||
|
"""
|
||
|
if enum is None:
|
||
|
return num
|
||
|
else: # pragma: no cover
|
||
|
try:
|
||
|
return socket.SocketKind(num)
|
||
|
except ValueError:
|
||
|
return num
|
||
|
|
||
|
|
||
|
def conn_to_ntuple(fd, fam, type_, laddr, raddr, status, status_map, pid=None):
|
||
|
"""Convert a raw connection tuple to a proper ntuple."""
|
||
|
if fam in (socket.AF_INET, AF_INET6):
|
||
|
if laddr:
|
||
|
laddr = addr(*laddr)
|
||
|
if raddr:
|
||
|
raddr = addr(*raddr)
|
||
|
if type_ == socket.SOCK_STREAM and fam in (AF_INET, AF_INET6):
|
||
|
status = status_map.get(status, CONN_NONE)
|
||
|
else:
|
||
|
status = CONN_NONE # ignore whatever C returned to us
|
||
|
fam = sockfam_to_enum(fam)
|
||
|
type_ = socktype_to_enum(type_)
|
||
|
if pid is None:
|
||
|
return pconn(fd, fam, type_, laddr, raddr, status)
|
||
|
else:
|
||
|
return sconn(fd, fam, type_, laddr, raddr, status, pid)
|
||
|
|
||
|
|
||
|
def deprecated_method(replacement):
|
||
|
"""A decorator which can be used to mark a method as deprecated
|
||
|
'replcement' is the method name which will be called instead.
|
||
|
"""
|
||
|
def outer(fun):
|
||
|
msg = "%s() is deprecated and will be removed; use %s() instead" % (
|
||
|
fun.__name__, replacement)
|
||
|
if fun.__doc__ is None:
|
||
|
fun.__doc__ = msg
|
||
|
|
||
|
@functools.wraps(fun)
|
||
|
def inner(self, *args, **kwargs):
|
||
|
warnings.warn(msg, category=DeprecationWarning, stacklevel=2)
|
||
|
return getattr(self, replacement)(*args, **kwargs)
|
||
|
return inner
|
||
|
return outer
|
||
|
|
||
|
|
||
|
class _WrapNumbers:
|
||
|
"""Watches numbers so that they don't overflow and wrap
|
||
|
(reset to zero).
|
||
|
"""
|
||
|
|
||
|
def __init__(self):
|
||
|
self.lock = threading.Lock()
|
||
|
self.cache = {}
|
||
|
self.reminders = {}
|
||
|
self.reminder_keys = {}
|
||
|
|
||
|
def _add_dict(self, input_dict, name):
|
||
|
assert name not in self.cache
|
||
|
assert name not in self.reminders
|
||
|
assert name not in self.reminder_keys
|
||
|
self.cache[name] = input_dict
|
||
|
self.reminders[name] = collections.defaultdict(int)
|
||
|
self.reminder_keys[name] = collections.defaultdict(set)
|
||
|
|
||
|
def _remove_dead_reminders(self, input_dict, name):
|
||
|
"""In case the number of keys changed between calls (e.g. a
|
||
|
disk disappears) this removes the entry from self.reminders.
|
||
|
"""
|
||
|
old_dict = self.cache[name]
|
||
|
gone_keys = set(old_dict.keys()) - set(input_dict.keys())
|
||
|
for gone_key in gone_keys:
|
||
|
for remkey in self.reminder_keys[name][gone_key]:
|
||
|
del self.reminders[name][remkey]
|
||
|
del self.reminder_keys[name][gone_key]
|
||
|
|
||
|
def run(self, input_dict, name):
|
||
|
"""Cache dict and sum numbers which overflow and wrap.
|
||
|
Return an updated copy of `input_dict`
|
||
|
"""
|
||
|
if name not in self.cache:
|
||
|
# This was the first call.
|
||
|
self._add_dict(input_dict, name)
|
||
|
return input_dict
|
||
|
|
||
|
self._remove_dead_reminders(input_dict, name)
|
||
|
|
||
|
old_dict = self.cache[name]
|
||
|
new_dict = {}
|
||
|
for key in input_dict.keys():
|
||
|
input_tuple = input_dict[key]
|
||
|
try:
|
||
|
old_tuple = old_dict[key]
|
||
|
except KeyError:
|
||
|
# The input dict has a new key (e.g. a new disk or NIC)
|
||
|
# which didn't exist in the previous call.
|
||
|
new_dict[key] = input_tuple
|
||
|
continue
|
||
|
|
||
|
bits = []
|
||
|
for i in range(len(input_tuple)):
|
||
|
input_value = input_tuple[i]
|
||
|
old_value = old_tuple[i]
|
||
|
remkey = (key, i)
|
||
|
if input_value < old_value:
|
||
|
# it wrapped!
|
||
|
self.reminders[name][remkey] += old_value
|
||
|
self.reminder_keys[name][key].add(remkey)
|
||
|
bits.append(input_value + self.reminders[name][remkey])
|
||
|
|
||
|
new_dict[key] = tuple(bits)
|
||
|
|
||
|
self.cache[name] = input_dict
|
||
|
return new_dict
|
||
|
|
||
|
def cache_clear(self, name=None):
|
||
|
"""Clear the internal cache, optionally only for function 'name'."""
|
||
|
with self.lock:
|
||
|
if name is None:
|
||
|
self.cache.clear()
|
||
|
self.reminders.clear()
|
||
|
self.reminder_keys.clear()
|
||
|
else:
|
||
|
self.cache.pop(name, None)
|
||
|
self.reminders.pop(name, None)
|
||
|
self.reminder_keys.pop(name, None)
|
||
|
|
||
|
def cache_info(self):
|
||
|
"""Return internal cache dicts as a tuple of 3 elements."""
|
||
|
with self.lock:
|
||
|
return (self.cache, self.reminders, self.reminder_keys)
|
||
|
|
||
|
|
||
|
def wrap_numbers(input_dict, name):
|
||
|
"""Given an `input_dict` and a function `name`, adjust the numbers
|
||
|
which "wrap" (restart from zero) across different calls by adding
|
||
|
"old value" to "new value" and return an updated dict.
|
||
|
"""
|
||
|
with _wn.lock:
|
||
|
return _wn.run(input_dict, name)
|
||
|
|
||
|
|
||
|
_wn = _WrapNumbers()
|
||
|
wrap_numbers.cache_clear = _wn.cache_clear
|
||
|
wrap_numbers.cache_info = _wn.cache_info
|
||
|
|
||
|
|
||
|
# The read buffer size for open() builtin. This (also) dictates how
|
||
|
# much data we read(2) when iterating over file lines as in:
|
||
|
# >>> with open(file) as f:
|
||
|
# ... for line in f:
|
||
|
# ... ...
|
||
|
# Default per-line buffer size for binary files is 1K. For text files
|
||
|
# is 8K. We use a bigger buffer (32K) in order to have more consistent
|
||
|
# results when reading /proc pseudo files on Linux, see:
|
||
|
# https://github.com/giampaolo/psutil/issues/2050
|
||
|
# On Python 2 this also speeds up the reading of big files:
|
||
|
# (namely /proc/{pid}/smaps and /proc/net/*):
|
||
|
# https://github.com/giampaolo/psutil/issues/708
|
||
|
FILE_READ_BUFFER_SIZE = 32 * 1024
|
||
|
|
||
|
|
||
|
def open_binary(fname):
|
||
|
return open(fname, "rb", buffering=FILE_READ_BUFFER_SIZE)
|
||
|
|
||
|
|
||
|
def open_text(fname):
|
||
|
"""On Python 3 opens a file in text mode by using fs encoding and
|
||
|
a proper en/decoding errors handler.
|
||
|
On Python 2 this is just an alias for open(name, 'rt').
|
||
|
"""
|
||
|
if not PY3:
|
||
|
return open(fname, "rt", buffering=FILE_READ_BUFFER_SIZE)
|
||
|
|
||
|
# See:
|
||
|
# https://github.com/giampaolo/psutil/issues/675
|
||
|
# https://github.com/giampaolo/psutil/pull/733
|
||
|
fobj = open(fname, "rt", buffering=FILE_READ_BUFFER_SIZE,
|
||
|
encoding=ENCODING, errors=ENCODING_ERRS)
|
||
|
try:
|
||
|
# Dictates per-line read(2) buffer size. Defaults is 8k. See:
|
||
|
# https://github.com/giampaolo/psutil/issues/2050#issuecomment-1013387546
|
||
|
fobj._CHUNK_SIZE = FILE_READ_BUFFER_SIZE
|
||
|
except AttributeError:
|
||
|
pass
|
||
|
except Exception:
|
||
|
fobj.close()
|
||
|
raise
|
||
|
|
||
|
return fobj
|
||
|
|
||
|
|
||
|
def cat(fname, fallback=_DEFAULT, _open=open_text):
|
||
|
"""Read entire file content and return it as a string. File is
|
||
|
opened in text mode. If specified, `fallback` is the value
|
||
|
returned in case of error, either if the file does not exist or
|
||
|
it can't be read().
|
||
|
"""
|
||
|
if fallback is _DEFAULT:
|
||
|
with _open(fname) as f:
|
||
|
return f.read()
|
||
|
else:
|
||
|
try:
|
||
|
with _open(fname) as f:
|
||
|
return f.read()
|
||
|
except (IOError, OSError):
|
||
|
return fallback
|
||
|
|
||
|
|
||
|
def bcat(fname, fallback=_DEFAULT):
|
||
|
"""Same as above but opens file in binary mode."""
|
||
|
return cat(fname, fallback=fallback, _open=open_binary)
|
||
|
|
||
|
|
||
|
def bytes2human(n, format="%(value).1f%(symbol)s"):
|
||
|
"""Used by various scripts. See:
|
||
|
http://goo.gl/zeJZl
|
||
|
|
||
|
>>> bytes2human(10000)
|
||
|
'9.8K'
|
||
|
>>> bytes2human(100001221)
|
||
|
'95.4M'
|
||
|
"""
|
||
|
symbols = ('B', 'K', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y')
|
||
|
prefix = {}
|
||
|
for i, s in enumerate(symbols[1:]):
|
||
|
prefix[s] = 1 << (i + 1) * 10
|
||
|
for symbol in reversed(symbols[1:]):
|
||
|
if n >= prefix[symbol]:
|
||
|
value = float(n) / prefix[symbol]
|
||
|
return format % locals()
|
||
|
return format % dict(symbol=symbols[0], value=n)
|
||
|
|
||
|
|
||
|
def get_procfs_path():
|
||
|
"""Return updated psutil.PROCFS_PATH constant."""
|
||
|
return sys.modules['psutil'].PROCFS_PATH
|
||
|
|
||
|
|
||
|
if PY3:
|
||
|
def decode(s):
|
||
|
return s.decode(encoding=ENCODING, errors=ENCODING_ERRS)
|
||
|
else:
|
||
|
def decode(s):
|
||
|
return s
|
||
|
|
||
|
|
||
|
# =====================================================================
|
||
|
# --- shell utils
|
||
|
# =====================================================================
|
||
|
|
||
|
|
||
|
@memoize
|
||
|
def term_supports_colors(file=sys.stdout): # pragma: no cover
|
||
|
if os.name == 'nt':
|
||
|
return True
|
||
|
try:
|
||
|
import curses
|
||
|
assert file.isatty()
|
||
|
curses.setupterm()
|
||
|
assert curses.tigetnum("colors") > 0
|
||
|
except Exception:
|
||
|
return False
|
||
|
else:
|
||
|
return True
|
||
|
|
||
|
|
||
|
def hilite(s, color=None, bold=False): # pragma: no cover
|
||
|
"""Return an highlighted version of 'string'."""
|
||
|
if not term_supports_colors():
|
||
|
return s
|
||
|
attr = []
|
||
|
colors = dict(green='32', red='91', brown='33', yellow='93', blue='34',
|
||
|
violet='35', lightblue='36', grey='37', darkgrey='30')
|
||
|
colors[None] = '29'
|
||
|
try:
|
||
|
color = colors[color]
|
||
|
except KeyError:
|
||
|
raise ValueError("invalid color %r; choose between %s" % (
|
||
|
list(colors.keys())))
|
||
|
attr.append(color)
|
||
|
if bold:
|
||
|
attr.append('1')
|
||
|
return '\x1b[%sm%s\x1b[0m' % (';'.join(attr), s)
|
||
|
|
||
|
|
||
|
def print_color(
|
||
|
s, color=None, bold=False, file=sys.stdout): # pragma: no cover
|
||
|
"""Print a colorized version of string."""
|
||
|
if not term_supports_colors():
|
||
|
print(s, file=file) # NOQA
|
||
|
elif POSIX:
|
||
|
print(hilite(s, color, bold), file=file) # NOQA
|
||
|
else:
|
||
|
import ctypes
|
||
|
|
||
|
DEFAULT_COLOR = 7
|
||
|
GetStdHandle = ctypes.windll.Kernel32.GetStdHandle
|
||
|
SetConsoleTextAttribute = \
|
||
|
ctypes.windll.Kernel32.SetConsoleTextAttribute
|
||
|
|
||
|
colors = dict(green=2, red=4, brown=6, yellow=6)
|
||
|
colors[None] = DEFAULT_COLOR
|
||
|
try:
|
||
|
color = colors[color]
|
||
|
except KeyError:
|
||
|
raise ValueError("invalid color %r; choose between %r" % (
|
||
|
color, list(colors.keys())))
|
||
|
if bold and color <= 7:
|
||
|
color += 8
|
||
|
|
||
|
handle_id = -12 if file is sys.stderr else -11
|
||
|
GetStdHandle.restype = ctypes.c_ulong
|
||
|
handle = GetStdHandle(handle_id)
|
||
|
SetConsoleTextAttribute(handle, color)
|
||
|
try:
|
||
|
print(s, file=file) # NOQA
|
||
|
finally:
|
||
|
SetConsoleTextAttribute(handle, DEFAULT_COLOR)
|
||
|
|
||
|
|
||
|
def debug(msg):
|
||
|
"""If PSUTIL_DEBUG env var is set, print a debug message to stderr."""
|
||
|
if PSUTIL_DEBUG:
|
||
|
import inspect
|
||
|
fname, lineno, func_name, lines, index = inspect.getframeinfo(
|
||
|
inspect.currentframe().f_back)
|
||
|
if isinstance(msg, Exception):
|
||
|
if isinstance(msg, (OSError, IOError, EnvironmentError)):
|
||
|
# ...because str(exc) may contain info about the file name
|
||
|
msg = "ignoring %s" % msg
|
||
|
else:
|
||
|
msg = "ignoring %r" % msg
|
||
|
print("psutil-debug [%s:%s]> %s" % (fname, lineno, msg), # NOQA
|
||
|
file=sys.stderr)
|