Add files using upload-large-folder tool
Browse filesThis view is limited to 50 files because it contains too many changes.
See raw diff
- .venv/lib/python3.11/site-packages/ray/thirdparty_files/colorama-0.4.6.dist-info/INSTALLER +1 -0
- .venv/lib/python3.11/site-packages/ray/thirdparty_files/colorama/__init__.py +7 -0
- .venv/lib/python3.11/site-packages/ray/thirdparty_files/colorama/__pycache__/__init__.cpython-311.pyc +0 -0
- .venv/lib/python3.11/site-packages/ray/thirdparty_files/colorama/__pycache__/ansi.cpython-311.pyc +0 -0
- .venv/lib/python3.11/site-packages/ray/thirdparty_files/colorama/__pycache__/ansitowin32.cpython-311.pyc +0 -0
- .venv/lib/python3.11/site-packages/ray/thirdparty_files/colorama/__pycache__/initialise.cpython-311.pyc +0 -0
- .venv/lib/python3.11/site-packages/ray/thirdparty_files/colorama/__pycache__/win32.cpython-311.pyc +0 -0
- .venv/lib/python3.11/site-packages/ray/thirdparty_files/colorama/__pycache__/winterm.cpython-311.pyc +0 -0
- .venv/lib/python3.11/site-packages/ray/thirdparty_files/colorama/ansi.py +102 -0
- .venv/lib/python3.11/site-packages/ray/thirdparty_files/colorama/ansitowin32.py +277 -0
- .venv/lib/python3.11/site-packages/ray/thirdparty_files/colorama/initialise.py +121 -0
- .venv/lib/python3.11/site-packages/ray/thirdparty_files/colorama/win32.py +180 -0
- .venv/lib/python3.11/site-packages/ray/thirdparty_files/colorama/winterm.py +195 -0
- .venv/lib/python3.11/site-packages/ray/thirdparty_files/psutil-6.1.1.dist-info/INSTALLER +1 -0
- .venv/lib/python3.11/site-packages/ray/thirdparty_files/psutil-6.1.1.dist-info/LICENSE +29 -0
- .venv/lib/python3.11/site-packages/ray/thirdparty_files/psutil-6.1.1.dist-info/METADATA +548 -0
- .venv/lib/python3.11/site-packages/ray/thirdparty_files/psutil-6.1.1.dist-info/RECORD +65 -0
- .venv/lib/python3.11/site-packages/ray/thirdparty_files/psutil-6.1.1.dist-info/REQUESTED +0 -0
- .venv/lib/python3.11/site-packages/ray/thirdparty_files/psutil-6.1.1.dist-info/WHEEL +8 -0
- .venv/lib/python3.11/site-packages/ray/thirdparty_files/psutil-6.1.1.dist-info/top_level.txt +1 -0
- .venv/lib/python3.11/site-packages/ray/thirdparty_files/psutil/__init__.py +2486 -0
- .venv/lib/python3.11/site-packages/ray/thirdparty_files/psutil/__pycache__/__init__.cpython-311.pyc +0 -0
- .venv/lib/python3.11/site-packages/ray/thirdparty_files/psutil/__pycache__/_common.cpython-311.pyc +0 -0
- .venv/lib/python3.11/site-packages/ray/thirdparty_files/psutil/__pycache__/_compat.cpython-311.pyc +0 -0
- .venv/lib/python3.11/site-packages/ray/thirdparty_files/psutil/__pycache__/_psaix.cpython-311.pyc +0 -0
- .venv/lib/python3.11/site-packages/ray/thirdparty_files/psutil/__pycache__/_psbsd.cpython-311.pyc +0 -0
- .venv/lib/python3.11/site-packages/ray/thirdparty_files/psutil/__pycache__/_psosx.cpython-311.pyc +0 -0
- .venv/lib/python3.11/site-packages/ray/thirdparty_files/psutil/__pycache__/_psposix.cpython-311.pyc +0 -0
- .venv/lib/python3.11/site-packages/ray/thirdparty_files/psutil/__pycache__/_pssunos.cpython-311.pyc +0 -0
- .venv/lib/python3.11/site-packages/ray/thirdparty_files/psutil/__pycache__/_pswindows.cpython-311.pyc +0 -0
- .venv/lib/python3.11/site-packages/ray/thirdparty_files/psutil/_common.py +994 -0
- .venv/lib/python3.11/site-packages/ray/thirdparty_files/psutil/_compat.py +477 -0
- .venv/lib/python3.11/site-packages/ray/thirdparty_files/psutil/_psaix.py +579 -0
- .venv/lib/python3.11/site-packages/ray/thirdparty_files/psutil/_psbsd.py +985 -0
- .venv/lib/python3.11/site-packages/ray/thirdparty_files/psutil/_pslinux.py +2379 -0
- .venv/lib/python3.11/site-packages/ray/thirdparty_files/psutil/_psosx.py +552 -0
- .venv/lib/python3.11/site-packages/ray/thirdparty_files/psutil/_psposix.py +243 -0
- .venv/lib/python3.11/site-packages/ray/thirdparty_files/psutil/_pssunos.py +753 -0
- .venv/lib/python3.11/site-packages/ray/thirdparty_files/psutil/_psutil_posix.abi3.so +0 -0
- .venv/lib/python3.11/site-packages/ray/thirdparty_files/psutil/_pswindows.py +1173 -0
- .venv/lib/python3.11/site-packages/ray/thirdparty_files/setproctitle-1.2.2.dist-info/INSTALLER +1 -0
- .venv/lib/python3.11/site-packages/ray/thirdparty_files/setproctitle-1.2.2.dist-info/REQUESTED +0 -0
- .venv/lib/python3.11/site-packages/ray/thirdparty_files/setproctitle-1.2.2.dist-info/WHEEL +5 -0
- .venv/lib/python3.11/site-packages/ray/thirdparty_files/setproctitle-1.2.2.dist-info/top_level.txt +1 -0
- .venv/lib/python3.11/site-packages/ray/thirdparty_files/setproctitle.cpython-311-x86_64-linux-gnu.so +0 -0
- .venv/lib/python3.11/site-packages/ray/train/v2/_internal/__pycache__/constants.cpython-311.pyc +0 -0
- .venv/lib/python3.11/site-packages/ray/train/v2/_internal/callbacks/__pycache__/__init__.cpython-311.pyc +0 -0
- .venv/lib/python3.11/site-packages/ray/train/v2/_internal/execution/__pycache__/__init__.cpython-311.pyc +0 -0
- .venv/lib/python3.11/site-packages/ray/train/v2/_internal/execution/failure_handling/__init__.py +16 -0
- .venv/lib/python3.11/site-packages/ray/train/v2/_internal/execution/failure_handling/failure_policy.py +25 -0
.venv/lib/python3.11/site-packages/ray/thirdparty_files/colorama-0.4.6.dist-info/INSTALLER
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
pip
|
.venv/lib/python3.11/site-packages/ray/thirdparty_files/colorama/__init__.py
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright Jonathan Hartley 2013. BSD 3-Clause license, see LICENSE file.
|
| 2 |
+
from .initialise import init, deinit, reinit, colorama_text, just_fix_windows_console
|
| 3 |
+
from .ansi import Fore, Back, Style, Cursor
|
| 4 |
+
from .ansitowin32 import AnsiToWin32
|
| 5 |
+
|
| 6 |
+
__version__ = '0.4.6'
|
| 7 |
+
|
.venv/lib/python3.11/site-packages/ray/thirdparty_files/colorama/__pycache__/__init__.cpython-311.pyc
ADDED
|
Binary file (577 Bytes). View file
|
|
|
.venv/lib/python3.11/site-packages/ray/thirdparty_files/colorama/__pycache__/ansi.cpython-311.pyc
ADDED
|
Binary file (4.58 kB). View file
|
|
|
.venv/lib/python3.11/site-packages/ray/thirdparty_files/colorama/__pycache__/ansitowin32.cpython-311.pyc
ADDED
|
Binary file (16.2 kB). View file
|
|
|
.venv/lib/python3.11/site-packages/ray/thirdparty_files/colorama/__pycache__/initialise.cpython-311.pyc
ADDED
|
Binary file (3.94 kB). View file
|
|
|
.venv/lib/python3.11/site-packages/ray/thirdparty_files/colorama/__pycache__/win32.cpython-311.pyc
ADDED
|
Binary file (7.93 kB). View file
|
|
|
.venv/lib/python3.11/site-packages/ray/thirdparty_files/colorama/__pycache__/winterm.cpython-311.pyc
ADDED
|
Binary file (9.15 kB). View file
|
|
|
.venv/lib/python3.11/site-packages/ray/thirdparty_files/colorama/ansi.py
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright Jonathan Hartley 2013. BSD 3-Clause license, see LICENSE file.
|
| 2 |
+
'''
|
| 3 |
+
This module generates ANSI character codes to printing colors to terminals.
|
| 4 |
+
See: http://en.wikipedia.org/wiki/ANSI_escape_code
|
| 5 |
+
'''
|
| 6 |
+
|
| 7 |
+
CSI = '\033['
|
| 8 |
+
OSC = '\033]'
|
| 9 |
+
BEL = '\a'
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
def code_to_chars(code):
|
| 13 |
+
return CSI + str(code) + 'm'
|
| 14 |
+
|
| 15 |
+
def set_title(title):
|
| 16 |
+
return OSC + '2;' + title + BEL
|
| 17 |
+
|
| 18 |
+
def clear_screen(mode=2):
|
| 19 |
+
return CSI + str(mode) + 'J'
|
| 20 |
+
|
| 21 |
+
def clear_line(mode=2):
|
| 22 |
+
return CSI + str(mode) + 'K'
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
class AnsiCodes(object):
|
| 26 |
+
def __init__(self):
|
| 27 |
+
# the subclasses declare class attributes which are numbers.
|
| 28 |
+
# Upon instantiation we define instance attributes, which are the same
|
| 29 |
+
# as the class attributes but wrapped with the ANSI escape sequence
|
| 30 |
+
for name in dir(self):
|
| 31 |
+
if not name.startswith('_'):
|
| 32 |
+
value = getattr(self, name)
|
| 33 |
+
setattr(self, name, code_to_chars(value))
|
| 34 |
+
|
| 35 |
+
|
| 36 |
+
class AnsiCursor(object):
|
| 37 |
+
def UP(self, n=1):
|
| 38 |
+
return CSI + str(n) + 'A'
|
| 39 |
+
def DOWN(self, n=1):
|
| 40 |
+
return CSI + str(n) + 'B'
|
| 41 |
+
def FORWARD(self, n=1):
|
| 42 |
+
return CSI + str(n) + 'C'
|
| 43 |
+
def BACK(self, n=1):
|
| 44 |
+
return CSI + str(n) + 'D'
|
| 45 |
+
def POS(self, x=1, y=1):
|
| 46 |
+
return CSI + str(y) + ';' + str(x) + 'H'
|
| 47 |
+
|
| 48 |
+
|
| 49 |
+
class AnsiFore(AnsiCodes):
|
| 50 |
+
BLACK = 30
|
| 51 |
+
RED = 31
|
| 52 |
+
GREEN = 32
|
| 53 |
+
YELLOW = 33
|
| 54 |
+
BLUE = 34
|
| 55 |
+
MAGENTA = 35
|
| 56 |
+
CYAN = 36
|
| 57 |
+
WHITE = 37
|
| 58 |
+
RESET = 39
|
| 59 |
+
|
| 60 |
+
# These are fairly well supported, but not part of the standard.
|
| 61 |
+
LIGHTBLACK_EX = 90
|
| 62 |
+
LIGHTRED_EX = 91
|
| 63 |
+
LIGHTGREEN_EX = 92
|
| 64 |
+
LIGHTYELLOW_EX = 93
|
| 65 |
+
LIGHTBLUE_EX = 94
|
| 66 |
+
LIGHTMAGENTA_EX = 95
|
| 67 |
+
LIGHTCYAN_EX = 96
|
| 68 |
+
LIGHTWHITE_EX = 97
|
| 69 |
+
|
| 70 |
+
|
| 71 |
+
class AnsiBack(AnsiCodes):
|
| 72 |
+
BLACK = 40
|
| 73 |
+
RED = 41
|
| 74 |
+
GREEN = 42
|
| 75 |
+
YELLOW = 43
|
| 76 |
+
BLUE = 44
|
| 77 |
+
MAGENTA = 45
|
| 78 |
+
CYAN = 46
|
| 79 |
+
WHITE = 47
|
| 80 |
+
RESET = 49
|
| 81 |
+
|
| 82 |
+
# These are fairly well supported, but not part of the standard.
|
| 83 |
+
LIGHTBLACK_EX = 100
|
| 84 |
+
LIGHTRED_EX = 101
|
| 85 |
+
LIGHTGREEN_EX = 102
|
| 86 |
+
LIGHTYELLOW_EX = 103
|
| 87 |
+
LIGHTBLUE_EX = 104
|
| 88 |
+
LIGHTMAGENTA_EX = 105
|
| 89 |
+
LIGHTCYAN_EX = 106
|
| 90 |
+
LIGHTWHITE_EX = 107
|
| 91 |
+
|
| 92 |
+
|
| 93 |
+
class AnsiStyle(AnsiCodes):
|
| 94 |
+
BRIGHT = 1
|
| 95 |
+
DIM = 2
|
| 96 |
+
NORMAL = 22
|
| 97 |
+
RESET_ALL = 0
|
| 98 |
+
|
| 99 |
+
Fore = AnsiFore()
|
| 100 |
+
Back = AnsiBack()
|
| 101 |
+
Style = AnsiStyle()
|
| 102 |
+
Cursor = AnsiCursor()
|
.venv/lib/python3.11/site-packages/ray/thirdparty_files/colorama/ansitowin32.py
ADDED
|
@@ -0,0 +1,277 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright Jonathan Hartley 2013. BSD 3-Clause license, see LICENSE file.
|
| 2 |
+
import re
|
| 3 |
+
import sys
|
| 4 |
+
import os
|
| 5 |
+
|
| 6 |
+
from .ansi import AnsiFore, AnsiBack, AnsiStyle, Style, BEL
|
| 7 |
+
from .winterm import enable_vt_processing, WinTerm, WinColor, WinStyle
|
| 8 |
+
from .win32 import windll, winapi_test
|
| 9 |
+
|
| 10 |
+
|
| 11 |
+
winterm = None
|
| 12 |
+
if windll is not None:
|
| 13 |
+
winterm = WinTerm()
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
class StreamWrapper(object):
|
| 17 |
+
'''
|
| 18 |
+
Wraps a stream (such as stdout), acting as a transparent proxy for all
|
| 19 |
+
attribute access apart from method 'write()', which is delegated to our
|
| 20 |
+
Converter instance.
|
| 21 |
+
'''
|
| 22 |
+
def __init__(self, wrapped, converter):
|
| 23 |
+
# double-underscore everything to prevent clashes with names of
|
| 24 |
+
# attributes on the wrapped stream object.
|
| 25 |
+
self.__wrapped = wrapped
|
| 26 |
+
self.__convertor = converter
|
| 27 |
+
|
| 28 |
+
def __getattr__(self, name):
|
| 29 |
+
return getattr(self.__wrapped, name)
|
| 30 |
+
|
| 31 |
+
def __enter__(self, *args, **kwargs):
|
| 32 |
+
# special method lookup bypasses __getattr__/__getattribute__, see
|
| 33 |
+
# https://stackoverflow.com/questions/12632894/why-doesnt-getattr-work-with-exit
|
| 34 |
+
# thus, contextlib magic methods are not proxied via __getattr__
|
| 35 |
+
return self.__wrapped.__enter__(*args, **kwargs)
|
| 36 |
+
|
| 37 |
+
def __exit__(self, *args, **kwargs):
|
| 38 |
+
return self.__wrapped.__exit__(*args, **kwargs)
|
| 39 |
+
|
| 40 |
+
def __setstate__(self, state):
|
| 41 |
+
self.__dict__ = state
|
| 42 |
+
|
| 43 |
+
def __getstate__(self):
|
| 44 |
+
return self.__dict__
|
| 45 |
+
|
| 46 |
+
def write(self, text):
|
| 47 |
+
self.__convertor.write(text)
|
| 48 |
+
|
| 49 |
+
def isatty(self):
|
| 50 |
+
stream = self.__wrapped
|
| 51 |
+
if 'PYCHARM_HOSTED' in os.environ:
|
| 52 |
+
if stream is not None and (stream is sys.__stdout__ or stream is sys.__stderr__):
|
| 53 |
+
return True
|
| 54 |
+
try:
|
| 55 |
+
stream_isatty = stream.isatty
|
| 56 |
+
except AttributeError:
|
| 57 |
+
return False
|
| 58 |
+
else:
|
| 59 |
+
return stream_isatty()
|
| 60 |
+
|
| 61 |
+
@property
|
| 62 |
+
def closed(self):
|
| 63 |
+
stream = self.__wrapped
|
| 64 |
+
try:
|
| 65 |
+
return stream.closed
|
| 66 |
+
# AttributeError in the case that the stream doesn't support being closed
|
| 67 |
+
# ValueError for the case that the stream has already been detached when atexit runs
|
| 68 |
+
except (AttributeError, ValueError):
|
| 69 |
+
return True
|
| 70 |
+
|
| 71 |
+
|
| 72 |
+
class AnsiToWin32(object):
|
| 73 |
+
'''
|
| 74 |
+
Implements a 'write()' method which, on Windows, will strip ANSI character
|
| 75 |
+
sequences from the text, and if outputting to a tty, will convert them into
|
| 76 |
+
win32 function calls.
|
| 77 |
+
'''
|
| 78 |
+
ANSI_CSI_RE = re.compile('\001?\033\\[((?:\\d|;)*)([a-zA-Z])\002?') # Control Sequence Introducer
|
| 79 |
+
ANSI_OSC_RE = re.compile('\001?\033\\]([^\a]*)(\a)\002?') # Operating System Command
|
| 80 |
+
|
| 81 |
+
def __init__(self, wrapped, convert=None, strip=None, autoreset=False):
|
| 82 |
+
# The wrapped stream (normally sys.stdout or sys.stderr)
|
| 83 |
+
self.wrapped = wrapped
|
| 84 |
+
|
| 85 |
+
# should we reset colors to defaults after every .write()
|
| 86 |
+
self.autoreset = autoreset
|
| 87 |
+
|
| 88 |
+
# create the proxy wrapping our output stream
|
| 89 |
+
self.stream = StreamWrapper(wrapped, self)
|
| 90 |
+
|
| 91 |
+
on_windows = os.name == 'nt'
|
| 92 |
+
# We test if the WinAPI works, because even if we are on Windows
|
| 93 |
+
# we may be using a terminal that doesn't support the WinAPI
|
| 94 |
+
# (e.g. Cygwin Terminal). In this case it's up to the terminal
|
| 95 |
+
# to support the ANSI codes.
|
| 96 |
+
conversion_supported = on_windows and winapi_test()
|
| 97 |
+
try:
|
| 98 |
+
fd = wrapped.fileno()
|
| 99 |
+
except Exception:
|
| 100 |
+
fd = -1
|
| 101 |
+
system_has_native_ansi = not on_windows or enable_vt_processing(fd)
|
| 102 |
+
have_tty = not self.stream.closed and self.stream.isatty()
|
| 103 |
+
need_conversion = conversion_supported and not system_has_native_ansi
|
| 104 |
+
|
| 105 |
+
# should we strip ANSI sequences from our output?
|
| 106 |
+
if strip is None:
|
| 107 |
+
strip = need_conversion or not have_tty
|
| 108 |
+
self.strip = strip
|
| 109 |
+
|
| 110 |
+
# should we should convert ANSI sequences into win32 calls?
|
| 111 |
+
if convert is None:
|
| 112 |
+
convert = need_conversion and have_tty
|
| 113 |
+
self.convert = convert
|
| 114 |
+
|
| 115 |
+
# dict of ansi codes to win32 functions and parameters
|
| 116 |
+
self.win32_calls = self.get_win32_calls()
|
| 117 |
+
|
| 118 |
+
# are we wrapping stderr?
|
| 119 |
+
self.on_stderr = self.wrapped is sys.stderr
|
| 120 |
+
|
| 121 |
+
def should_wrap(self):
|
| 122 |
+
'''
|
| 123 |
+
True if this class is actually needed. If false, then the output
|
| 124 |
+
stream will not be affected, nor will win32 calls be issued, so
|
| 125 |
+
wrapping stdout is not actually required. This will generally be
|
| 126 |
+
False on non-Windows platforms, unless optional functionality like
|
| 127 |
+
autoreset has been requested using kwargs to init()
|
| 128 |
+
'''
|
| 129 |
+
return self.convert or self.strip or self.autoreset
|
| 130 |
+
|
| 131 |
+
def get_win32_calls(self):
|
| 132 |
+
if self.convert and winterm:
|
| 133 |
+
return {
|
| 134 |
+
AnsiStyle.RESET_ALL: (winterm.reset_all, ),
|
| 135 |
+
AnsiStyle.BRIGHT: (winterm.style, WinStyle.BRIGHT),
|
| 136 |
+
AnsiStyle.DIM: (winterm.style, WinStyle.NORMAL),
|
| 137 |
+
AnsiStyle.NORMAL: (winterm.style, WinStyle.NORMAL),
|
| 138 |
+
AnsiFore.BLACK: (winterm.fore, WinColor.BLACK),
|
| 139 |
+
AnsiFore.RED: (winterm.fore, WinColor.RED),
|
| 140 |
+
AnsiFore.GREEN: (winterm.fore, WinColor.GREEN),
|
| 141 |
+
AnsiFore.YELLOW: (winterm.fore, WinColor.YELLOW),
|
| 142 |
+
AnsiFore.BLUE: (winterm.fore, WinColor.BLUE),
|
| 143 |
+
AnsiFore.MAGENTA: (winterm.fore, WinColor.MAGENTA),
|
| 144 |
+
AnsiFore.CYAN: (winterm.fore, WinColor.CYAN),
|
| 145 |
+
AnsiFore.WHITE: (winterm.fore, WinColor.GREY),
|
| 146 |
+
AnsiFore.RESET: (winterm.fore, ),
|
| 147 |
+
AnsiFore.LIGHTBLACK_EX: (winterm.fore, WinColor.BLACK, True),
|
| 148 |
+
AnsiFore.LIGHTRED_EX: (winterm.fore, WinColor.RED, True),
|
| 149 |
+
AnsiFore.LIGHTGREEN_EX: (winterm.fore, WinColor.GREEN, True),
|
| 150 |
+
AnsiFore.LIGHTYELLOW_EX: (winterm.fore, WinColor.YELLOW, True),
|
| 151 |
+
AnsiFore.LIGHTBLUE_EX: (winterm.fore, WinColor.BLUE, True),
|
| 152 |
+
AnsiFore.LIGHTMAGENTA_EX: (winterm.fore, WinColor.MAGENTA, True),
|
| 153 |
+
AnsiFore.LIGHTCYAN_EX: (winterm.fore, WinColor.CYAN, True),
|
| 154 |
+
AnsiFore.LIGHTWHITE_EX: (winterm.fore, WinColor.GREY, True),
|
| 155 |
+
AnsiBack.BLACK: (winterm.back, WinColor.BLACK),
|
| 156 |
+
AnsiBack.RED: (winterm.back, WinColor.RED),
|
| 157 |
+
AnsiBack.GREEN: (winterm.back, WinColor.GREEN),
|
| 158 |
+
AnsiBack.YELLOW: (winterm.back, WinColor.YELLOW),
|
| 159 |
+
AnsiBack.BLUE: (winterm.back, WinColor.BLUE),
|
| 160 |
+
AnsiBack.MAGENTA: (winterm.back, WinColor.MAGENTA),
|
| 161 |
+
AnsiBack.CYAN: (winterm.back, WinColor.CYAN),
|
| 162 |
+
AnsiBack.WHITE: (winterm.back, WinColor.GREY),
|
| 163 |
+
AnsiBack.RESET: (winterm.back, ),
|
| 164 |
+
AnsiBack.LIGHTBLACK_EX: (winterm.back, WinColor.BLACK, True),
|
| 165 |
+
AnsiBack.LIGHTRED_EX: (winterm.back, WinColor.RED, True),
|
| 166 |
+
AnsiBack.LIGHTGREEN_EX: (winterm.back, WinColor.GREEN, True),
|
| 167 |
+
AnsiBack.LIGHTYELLOW_EX: (winterm.back, WinColor.YELLOW, True),
|
| 168 |
+
AnsiBack.LIGHTBLUE_EX: (winterm.back, WinColor.BLUE, True),
|
| 169 |
+
AnsiBack.LIGHTMAGENTA_EX: (winterm.back, WinColor.MAGENTA, True),
|
| 170 |
+
AnsiBack.LIGHTCYAN_EX: (winterm.back, WinColor.CYAN, True),
|
| 171 |
+
AnsiBack.LIGHTWHITE_EX: (winterm.back, WinColor.GREY, True),
|
| 172 |
+
}
|
| 173 |
+
return dict()
|
| 174 |
+
|
| 175 |
+
def write(self, text):
|
| 176 |
+
if self.strip or self.convert:
|
| 177 |
+
self.write_and_convert(text)
|
| 178 |
+
else:
|
| 179 |
+
self.wrapped.write(text)
|
| 180 |
+
self.wrapped.flush()
|
| 181 |
+
if self.autoreset:
|
| 182 |
+
self.reset_all()
|
| 183 |
+
|
| 184 |
+
|
| 185 |
+
def reset_all(self):
|
| 186 |
+
if self.convert:
|
| 187 |
+
self.call_win32('m', (0,))
|
| 188 |
+
elif not self.strip and not self.stream.closed:
|
| 189 |
+
self.wrapped.write(Style.RESET_ALL)
|
| 190 |
+
|
| 191 |
+
|
| 192 |
+
def write_and_convert(self, text):
|
| 193 |
+
'''
|
| 194 |
+
Write the given text to our wrapped stream, stripping any ANSI
|
| 195 |
+
sequences from the text, and optionally converting them into win32
|
| 196 |
+
calls.
|
| 197 |
+
'''
|
| 198 |
+
cursor = 0
|
| 199 |
+
text = self.convert_osc(text)
|
| 200 |
+
for match in self.ANSI_CSI_RE.finditer(text):
|
| 201 |
+
start, end = match.span()
|
| 202 |
+
self.write_plain_text(text, cursor, start)
|
| 203 |
+
self.convert_ansi(*match.groups())
|
| 204 |
+
cursor = end
|
| 205 |
+
self.write_plain_text(text, cursor, len(text))
|
| 206 |
+
|
| 207 |
+
|
| 208 |
+
def write_plain_text(self, text, start, end):
|
| 209 |
+
if start < end:
|
| 210 |
+
self.wrapped.write(text[start:end])
|
| 211 |
+
self.wrapped.flush()
|
| 212 |
+
|
| 213 |
+
|
| 214 |
+
def convert_ansi(self, paramstring, command):
|
| 215 |
+
if self.convert:
|
| 216 |
+
params = self.extract_params(command, paramstring)
|
| 217 |
+
self.call_win32(command, params)
|
| 218 |
+
|
| 219 |
+
|
| 220 |
+
def extract_params(self, command, paramstring):
|
| 221 |
+
if command in 'Hf':
|
| 222 |
+
params = tuple(int(p) if len(p) != 0 else 1 for p in paramstring.split(';'))
|
| 223 |
+
while len(params) < 2:
|
| 224 |
+
# defaults:
|
| 225 |
+
params = params + (1,)
|
| 226 |
+
else:
|
| 227 |
+
params = tuple(int(p) for p in paramstring.split(';') if len(p) != 0)
|
| 228 |
+
if len(params) == 0:
|
| 229 |
+
# defaults:
|
| 230 |
+
if command in 'JKm':
|
| 231 |
+
params = (0,)
|
| 232 |
+
elif command in 'ABCD':
|
| 233 |
+
params = (1,)
|
| 234 |
+
|
| 235 |
+
return params
|
| 236 |
+
|
| 237 |
+
|
| 238 |
+
def call_win32(self, command, params):
|
| 239 |
+
if command == 'm':
|
| 240 |
+
for param in params:
|
| 241 |
+
if param in self.win32_calls:
|
| 242 |
+
func_args = self.win32_calls[param]
|
| 243 |
+
func = func_args[0]
|
| 244 |
+
args = func_args[1:]
|
| 245 |
+
kwargs = dict(on_stderr=self.on_stderr)
|
| 246 |
+
func(*args, **kwargs)
|
| 247 |
+
elif command in 'J':
|
| 248 |
+
winterm.erase_screen(params[0], on_stderr=self.on_stderr)
|
| 249 |
+
elif command in 'K':
|
| 250 |
+
winterm.erase_line(params[0], on_stderr=self.on_stderr)
|
| 251 |
+
elif command in 'Hf': # cursor position - absolute
|
| 252 |
+
winterm.set_cursor_position(params, on_stderr=self.on_stderr)
|
| 253 |
+
elif command in 'ABCD': # cursor position - relative
|
| 254 |
+
n = params[0]
|
| 255 |
+
# A - up, B - down, C - forward, D - back
|
| 256 |
+
x, y = {'A': (0, -n), 'B': (0, n), 'C': (n, 0), 'D': (-n, 0)}[command]
|
| 257 |
+
winterm.cursor_adjust(x, y, on_stderr=self.on_stderr)
|
| 258 |
+
|
| 259 |
+
|
| 260 |
+
def convert_osc(self, text):
|
| 261 |
+
for match in self.ANSI_OSC_RE.finditer(text):
|
| 262 |
+
start, end = match.span()
|
| 263 |
+
text = text[:start] + text[end:]
|
| 264 |
+
paramstring, command = match.groups()
|
| 265 |
+
if command == BEL:
|
| 266 |
+
if paramstring.count(";") == 1:
|
| 267 |
+
params = paramstring.split(";")
|
| 268 |
+
# 0 - change title and icon (we will only change title)
|
| 269 |
+
# 1 - change icon (we don't support this)
|
| 270 |
+
# 2 - change title
|
| 271 |
+
if params[0] in '02':
|
| 272 |
+
winterm.set_title(params[1])
|
| 273 |
+
return text
|
| 274 |
+
|
| 275 |
+
|
| 276 |
+
def flush(self):
|
| 277 |
+
self.wrapped.flush()
|
.venv/lib/python3.11/site-packages/ray/thirdparty_files/colorama/initialise.py
ADDED
|
@@ -0,0 +1,121 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright Jonathan Hartley 2013. BSD 3-Clause license, see LICENSE file.
|
| 2 |
+
import atexit
|
| 3 |
+
import contextlib
|
| 4 |
+
import sys
|
| 5 |
+
|
| 6 |
+
from .ansitowin32 import AnsiToWin32
|
| 7 |
+
|
| 8 |
+
|
| 9 |
+
def _wipe_internal_state_for_tests():
|
| 10 |
+
global orig_stdout, orig_stderr
|
| 11 |
+
orig_stdout = None
|
| 12 |
+
orig_stderr = None
|
| 13 |
+
|
| 14 |
+
global wrapped_stdout, wrapped_stderr
|
| 15 |
+
wrapped_stdout = None
|
| 16 |
+
wrapped_stderr = None
|
| 17 |
+
|
| 18 |
+
global atexit_done
|
| 19 |
+
atexit_done = False
|
| 20 |
+
|
| 21 |
+
global fixed_windows_console
|
| 22 |
+
fixed_windows_console = False
|
| 23 |
+
|
| 24 |
+
try:
|
| 25 |
+
# no-op if it wasn't registered
|
| 26 |
+
atexit.unregister(reset_all)
|
| 27 |
+
except AttributeError:
|
| 28 |
+
# python 2: no atexit.unregister. Oh well, we did our best.
|
| 29 |
+
pass
|
| 30 |
+
|
| 31 |
+
|
| 32 |
+
def reset_all():
|
| 33 |
+
if AnsiToWin32 is not None: # Issue #74: objects might become None at exit
|
| 34 |
+
AnsiToWin32(orig_stdout).reset_all()
|
| 35 |
+
|
| 36 |
+
|
| 37 |
+
def init(autoreset=False, convert=None, strip=None, wrap=True):
|
| 38 |
+
|
| 39 |
+
if not wrap and any([autoreset, convert, strip]):
|
| 40 |
+
raise ValueError('wrap=False conflicts with any other arg=True')
|
| 41 |
+
|
| 42 |
+
global wrapped_stdout, wrapped_stderr
|
| 43 |
+
global orig_stdout, orig_stderr
|
| 44 |
+
|
| 45 |
+
orig_stdout = sys.stdout
|
| 46 |
+
orig_stderr = sys.stderr
|
| 47 |
+
|
| 48 |
+
if sys.stdout is None:
|
| 49 |
+
wrapped_stdout = None
|
| 50 |
+
else:
|
| 51 |
+
sys.stdout = wrapped_stdout = \
|
| 52 |
+
wrap_stream(orig_stdout, convert, strip, autoreset, wrap)
|
| 53 |
+
if sys.stderr is None:
|
| 54 |
+
wrapped_stderr = None
|
| 55 |
+
else:
|
| 56 |
+
sys.stderr = wrapped_stderr = \
|
| 57 |
+
wrap_stream(orig_stderr, convert, strip, autoreset, wrap)
|
| 58 |
+
|
| 59 |
+
global atexit_done
|
| 60 |
+
if not atexit_done:
|
| 61 |
+
atexit.register(reset_all)
|
| 62 |
+
atexit_done = True
|
| 63 |
+
|
| 64 |
+
|
| 65 |
+
def deinit():
|
| 66 |
+
if orig_stdout is not None:
|
| 67 |
+
sys.stdout = orig_stdout
|
| 68 |
+
if orig_stderr is not None:
|
| 69 |
+
sys.stderr = orig_stderr
|
| 70 |
+
|
| 71 |
+
|
| 72 |
+
def just_fix_windows_console():
|
| 73 |
+
global fixed_windows_console
|
| 74 |
+
|
| 75 |
+
if sys.platform != "win32":
|
| 76 |
+
return
|
| 77 |
+
if fixed_windows_console:
|
| 78 |
+
return
|
| 79 |
+
if wrapped_stdout is not None or wrapped_stderr is not None:
|
| 80 |
+
# Someone already ran init() and it did stuff, so we won't second-guess them
|
| 81 |
+
return
|
| 82 |
+
|
| 83 |
+
# On newer versions of Windows, AnsiToWin32.__init__ will implicitly enable the
|
| 84 |
+
# native ANSI support in the console as a side-effect. We only need to actually
|
| 85 |
+
# replace sys.stdout/stderr if we're in the old-style conversion mode.
|
| 86 |
+
new_stdout = AnsiToWin32(sys.stdout, convert=None, strip=None, autoreset=False)
|
| 87 |
+
if new_stdout.convert:
|
| 88 |
+
sys.stdout = new_stdout
|
| 89 |
+
new_stderr = AnsiToWin32(sys.stderr, convert=None, strip=None, autoreset=False)
|
| 90 |
+
if new_stderr.convert:
|
| 91 |
+
sys.stderr = new_stderr
|
| 92 |
+
|
| 93 |
+
fixed_windows_console = True
|
| 94 |
+
|
| 95 |
+
@contextlib.contextmanager
|
| 96 |
+
def colorama_text(*args, **kwargs):
|
| 97 |
+
init(*args, **kwargs)
|
| 98 |
+
try:
|
| 99 |
+
yield
|
| 100 |
+
finally:
|
| 101 |
+
deinit()
|
| 102 |
+
|
| 103 |
+
|
| 104 |
+
def reinit():
|
| 105 |
+
if wrapped_stdout is not None:
|
| 106 |
+
sys.stdout = wrapped_stdout
|
| 107 |
+
if wrapped_stderr is not None:
|
| 108 |
+
sys.stderr = wrapped_stderr
|
| 109 |
+
|
| 110 |
+
|
| 111 |
+
def wrap_stream(stream, convert, strip, autoreset, wrap):
|
| 112 |
+
if wrap:
|
| 113 |
+
wrapper = AnsiToWin32(stream,
|
| 114 |
+
convert=convert, strip=strip, autoreset=autoreset)
|
| 115 |
+
if wrapper.should_wrap():
|
| 116 |
+
stream = wrapper.stream
|
| 117 |
+
return stream
|
| 118 |
+
|
| 119 |
+
|
| 120 |
+
# Use this for initial setup as well, to reduce code duplication
|
| 121 |
+
_wipe_internal_state_for_tests()
|
.venv/lib/python3.11/site-packages/ray/thirdparty_files/colorama/win32.py
ADDED
|
@@ -0,0 +1,180 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright Jonathan Hartley 2013. BSD 3-Clause license, see LICENSE file.
|
| 2 |
+
|
| 3 |
+
# from winbase.h
|
| 4 |
+
STDOUT = -11
|
| 5 |
+
STDERR = -12
|
| 6 |
+
|
| 7 |
+
ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x0004
|
| 8 |
+
|
| 9 |
+
try:
|
| 10 |
+
import ctypes
|
| 11 |
+
from ctypes import LibraryLoader
|
| 12 |
+
windll = LibraryLoader(ctypes.WinDLL)
|
| 13 |
+
from ctypes import wintypes
|
| 14 |
+
except (AttributeError, ImportError):
|
| 15 |
+
windll = None
|
| 16 |
+
SetConsoleTextAttribute = lambda *_: None
|
| 17 |
+
winapi_test = lambda *_: None
|
| 18 |
+
else:
|
| 19 |
+
from ctypes import byref, Structure, c_char, POINTER
|
| 20 |
+
|
| 21 |
+
COORD = wintypes._COORD
|
| 22 |
+
|
| 23 |
+
class CONSOLE_SCREEN_BUFFER_INFO(Structure):
|
| 24 |
+
"""struct in wincon.h."""
|
| 25 |
+
_fields_ = [
|
| 26 |
+
("dwSize", COORD),
|
| 27 |
+
("dwCursorPosition", COORD),
|
| 28 |
+
("wAttributes", wintypes.WORD),
|
| 29 |
+
("srWindow", wintypes.SMALL_RECT),
|
| 30 |
+
("dwMaximumWindowSize", COORD),
|
| 31 |
+
]
|
| 32 |
+
def __str__(self):
|
| 33 |
+
return '(%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d)' % (
|
| 34 |
+
self.dwSize.Y, self.dwSize.X
|
| 35 |
+
, self.dwCursorPosition.Y, self.dwCursorPosition.X
|
| 36 |
+
, self.wAttributes
|
| 37 |
+
, self.srWindow.Top, self.srWindow.Left, self.srWindow.Bottom, self.srWindow.Right
|
| 38 |
+
, self.dwMaximumWindowSize.Y, self.dwMaximumWindowSize.X
|
| 39 |
+
)
|
| 40 |
+
|
| 41 |
+
_GetStdHandle = windll.kernel32.GetStdHandle
|
| 42 |
+
_GetStdHandle.argtypes = [
|
| 43 |
+
wintypes.DWORD,
|
| 44 |
+
]
|
| 45 |
+
_GetStdHandle.restype = wintypes.HANDLE
|
| 46 |
+
|
| 47 |
+
_GetConsoleScreenBufferInfo = windll.kernel32.GetConsoleScreenBufferInfo
|
| 48 |
+
_GetConsoleScreenBufferInfo.argtypes = [
|
| 49 |
+
wintypes.HANDLE,
|
| 50 |
+
POINTER(CONSOLE_SCREEN_BUFFER_INFO),
|
| 51 |
+
]
|
| 52 |
+
_GetConsoleScreenBufferInfo.restype = wintypes.BOOL
|
| 53 |
+
|
| 54 |
+
_SetConsoleTextAttribute = windll.kernel32.SetConsoleTextAttribute
|
| 55 |
+
_SetConsoleTextAttribute.argtypes = [
|
| 56 |
+
wintypes.HANDLE,
|
| 57 |
+
wintypes.WORD,
|
| 58 |
+
]
|
| 59 |
+
_SetConsoleTextAttribute.restype = wintypes.BOOL
|
| 60 |
+
|
| 61 |
+
_SetConsoleCursorPosition = windll.kernel32.SetConsoleCursorPosition
|
| 62 |
+
_SetConsoleCursorPosition.argtypes = [
|
| 63 |
+
wintypes.HANDLE,
|
| 64 |
+
COORD,
|
| 65 |
+
]
|
| 66 |
+
_SetConsoleCursorPosition.restype = wintypes.BOOL
|
| 67 |
+
|
| 68 |
+
_FillConsoleOutputCharacterA = windll.kernel32.FillConsoleOutputCharacterA
|
| 69 |
+
_FillConsoleOutputCharacterA.argtypes = [
|
| 70 |
+
wintypes.HANDLE,
|
| 71 |
+
c_char,
|
| 72 |
+
wintypes.DWORD,
|
| 73 |
+
COORD,
|
| 74 |
+
POINTER(wintypes.DWORD),
|
| 75 |
+
]
|
| 76 |
+
_FillConsoleOutputCharacterA.restype = wintypes.BOOL
|
| 77 |
+
|
| 78 |
+
_FillConsoleOutputAttribute = windll.kernel32.FillConsoleOutputAttribute
|
| 79 |
+
_FillConsoleOutputAttribute.argtypes = [
|
| 80 |
+
wintypes.HANDLE,
|
| 81 |
+
wintypes.WORD,
|
| 82 |
+
wintypes.DWORD,
|
| 83 |
+
COORD,
|
| 84 |
+
POINTER(wintypes.DWORD),
|
| 85 |
+
]
|
| 86 |
+
_FillConsoleOutputAttribute.restype = wintypes.BOOL
|
| 87 |
+
|
| 88 |
+
_SetConsoleTitleW = windll.kernel32.SetConsoleTitleW
|
| 89 |
+
_SetConsoleTitleW.argtypes = [
|
| 90 |
+
wintypes.LPCWSTR
|
| 91 |
+
]
|
| 92 |
+
_SetConsoleTitleW.restype = wintypes.BOOL
|
| 93 |
+
|
| 94 |
+
_GetConsoleMode = windll.kernel32.GetConsoleMode
|
| 95 |
+
_GetConsoleMode.argtypes = [
|
| 96 |
+
wintypes.HANDLE,
|
| 97 |
+
POINTER(wintypes.DWORD)
|
| 98 |
+
]
|
| 99 |
+
_GetConsoleMode.restype = wintypes.BOOL
|
| 100 |
+
|
| 101 |
+
_SetConsoleMode = windll.kernel32.SetConsoleMode
|
| 102 |
+
_SetConsoleMode.argtypes = [
|
| 103 |
+
wintypes.HANDLE,
|
| 104 |
+
wintypes.DWORD
|
| 105 |
+
]
|
| 106 |
+
_SetConsoleMode.restype = wintypes.BOOL
|
| 107 |
+
|
| 108 |
+
def _winapi_test(handle):
|
| 109 |
+
csbi = CONSOLE_SCREEN_BUFFER_INFO()
|
| 110 |
+
success = _GetConsoleScreenBufferInfo(
|
| 111 |
+
handle, byref(csbi))
|
| 112 |
+
return bool(success)
|
| 113 |
+
|
| 114 |
+
def winapi_test():
|
| 115 |
+
return any(_winapi_test(h) for h in
|
| 116 |
+
(_GetStdHandle(STDOUT), _GetStdHandle(STDERR)))
|
| 117 |
+
|
| 118 |
+
def GetConsoleScreenBufferInfo(stream_id=STDOUT):
|
| 119 |
+
handle = _GetStdHandle(stream_id)
|
| 120 |
+
csbi = CONSOLE_SCREEN_BUFFER_INFO()
|
| 121 |
+
success = _GetConsoleScreenBufferInfo(
|
| 122 |
+
handle, byref(csbi))
|
| 123 |
+
return csbi
|
| 124 |
+
|
| 125 |
+
def SetConsoleTextAttribute(stream_id, attrs):
|
| 126 |
+
handle = _GetStdHandle(stream_id)
|
| 127 |
+
return _SetConsoleTextAttribute(handle, attrs)
|
| 128 |
+
|
| 129 |
+
def SetConsoleCursorPosition(stream_id, position, adjust=True):
|
| 130 |
+
position = COORD(*position)
|
| 131 |
+
# If the position is out of range, do nothing.
|
| 132 |
+
if position.Y <= 0 or position.X <= 0:
|
| 133 |
+
return
|
| 134 |
+
# Adjust for Windows' SetConsoleCursorPosition:
|
| 135 |
+
# 1. being 0-based, while ANSI is 1-based.
|
| 136 |
+
# 2. expecting (x,y), while ANSI uses (y,x).
|
| 137 |
+
adjusted_position = COORD(position.Y - 1, position.X - 1)
|
| 138 |
+
if adjust:
|
| 139 |
+
# Adjust for viewport's scroll position
|
| 140 |
+
sr = GetConsoleScreenBufferInfo(STDOUT).srWindow
|
| 141 |
+
adjusted_position.Y += sr.Top
|
| 142 |
+
adjusted_position.X += sr.Left
|
| 143 |
+
# Resume normal processing
|
| 144 |
+
handle = _GetStdHandle(stream_id)
|
| 145 |
+
return _SetConsoleCursorPosition(handle, adjusted_position)
|
| 146 |
+
|
| 147 |
+
def FillConsoleOutputCharacter(stream_id, char, length, start):
|
| 148 |
+
handle = _GetStdHandle(stream_id)
|
| 149 |
+
char = c_char(char.encode())
|
| 150 |
+
length = wintypes.DWORD(length)
|
| 151 |
+
num_written = wintypes.DWORD(0)
|
| 152 |
+
# Note that this is hard-coded for ANSI (vs wide) bytes.
|
| 153 |
+
success = _FillConsoleOutputCharacterA(
|
| 154 |
+
handle, char, length, start, byref(num_written))
|
| 155 |
+
return num_written.value
|
| 156 |
+
|
| 157 |
+
def FillConsoleOutputAttribute(stream_id, attr, length, start):
|
| 158 |
+
''' FillConsoleOutputAttribute( hConsole, csbi.wAttributes, dwConSize, coordScreen, &cCharsWritten )'''
|
| 159 |
+
handle = _GetStdHandle(stream_id)
|
| 160 |
+
attribute = wintypes.WORD(attr)
|
| 161 |
+
length = wintypes.DWORD(length)
|
| 162 |
+
num_written = wintypes.DWORD(0)
|
| 163 |
+
# Note that this is hard-coded for ANSI (vs wide) bytes.
|
| 164 |
+
return _FillConsoleOutputAttribute(
|
| 165 |
+
handle, attribute, length, start, byref(num_written))
|
| 166 |
+
|
| 167 |
+
def SetConsoleTitle(title):
|
| 168 |
+
return _SetConsoleTitleW(title)
|
| 169 |
+
|
| 170 |
+
def GetConsoleMode(handle):
|
| 171 |
+
mode = wintypes.DWORD()
|
| 172 |
+
success = _GetConsoleMode(handle, byref(mode))
|
| 173 |
+
if not success:
|
| 174 |
+
raise ctypes.WinError()
|
| 175 |
+
return mode.value
|
| 176 |
+
|
| 177 |
+
def SetConsoleMode(handle, mode):
|
| 178 |
+
success = _SetConsoleMode(handle, mode)
|
| 179 |
+
if not success:
|
| 180 |
+
raise ctypes.WinError()
|
.venv/lib/python3.11/site-packages/ray/thirdparty_files/colorama/winterm.py
ADDED
|
@@ -0,0 +1,195 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright Jonathan Hartley 2013. BSD 3-Clause license, see LICENSE file.
|
| 2 |
+
try:
|
| 3 |
+
from msvcrt import get_osfhandle
|
| 4 |
+
except ImportError:
|
| 5 |
+
def get_osfhandle(_):
|
| 6 |
+
raise OSError("This isn't windows!")
|
| 7 |
+
|
| 8 |
+
|
| 9 |
+
from . import win32
|
| 10 |
+
|
| 11 |
+
# from wincon.h
|
| 12 |
+
class WinColor(object):
|
| 13 |
+
BLACK = 0
|
| 14 |
+
BLUE = 1
|
| 15 |
+
GREEN = 2
|
| 16 |
+
CYAN = 3
|
| 17 |
+
RED = 4
|
| 18 |
+
MAGENTA = 5
|
| 19 |
+
YELLOW = 6
|
| 20 |
+
GREY = 7
|
| 21 |
+
|
| 22 |
+
# from wincon.h
|
| 23 |
+
class WinStyle(object):
|
| 24 |
+
NORMAL = 0x00 # dim text, dim background
|
| 25 |
+
BRIGHT = 0x08 # bright text, dim background
|
| 26 |
+
BRIGHT_BACKGROUND = 0x80 # dim text, bright background
|
| 27 |
+
|
| 28 |
+
class WinTerm(object):
|
| 29 |
+
|
| 30 |
+
def __init__(self):
|
| 31 |
+
self._default = win32.GetConsoleScreenBufferInfo(win32.STDOUT).wAttributes
|
| 32 |
+
self.set_attrs(self._default)
|
| 33 |
+
self._default_fore = self._fore
|
| 34 |
+
self._default_back = self._back
|
| 35 |
+
self._default_style = self._style
|
| 36 |
+
# In order to emulate LIGHT_EX in windows, we borrow the BRIGHT style.
|
| 37 |
+
# So that LIGHT_EX colors and BRIGHT style do not clobber each other,
|
| 38 |
+
# we track them separately, since LIGHT_EX is overwritten by Fore/Back
|
| 39 |
+
# and BRIGHT is overwritten by Style codes.
|
| 40 |
+
self._light = 0
|
| 41 |
+
|
| 42 |
+
def get_attrs(self):
|
| 43 |
+
return self._fore + self._back * 16 + (self._style | self._light)
|
| 44 |
+
|
| 45 |
+
def set_attrs(self, value):
|
| 46 |
+
self._fore = value & 7
|
| 47 |
+
self._back = (value >> 4) & 7
|
| 48 |
+
self._style = value & (WinStyle.BRIGHT | WinStyle.BRIGHT_BACKGROUND)
|
| 49 |
+
|
| 50 |
+
def reset_all(self, on_stderr=None):
|
| 51 |
+
self.set_attrs(self._default)
|
| 52 |
+
self.set_console(attrs=self._default)
|
| 53 |
+
self._light = 0
|
| 54 |
+
|
| 55 |
+
def fore(self, fore=None, light=False, on_stderr=False):
|
| 56 |
+
if fore is None:
|
| 57 |
+
fore = self._default_fore
|
| 58 |
+
self._fore = fore
|
| 59 |
+
# Emulate LIGHT_EX with BRIGHT Style
|
| 60 |
+
if light:
|
| 61 |
+
self._light |= WinStyle.BRIGHT
|
| 62 |
+
else:
|
| 63 |
+
self._light &= ~WinStyle.BRIGHT
|
| 64 |
+
self.set_console(on_stderr=on_stderr)
|
| 65 |
+
|
| 66 |
+
def back(self, back=None, light=False, on_stderr=False):
|
| 67 |
+
if back is None:
|
| 68 |
+
back = self._default_back
|
| 69 |
+
self._back = back
|
| 70 |
+
# Emulate LIGHT_EX with BRIGHT_BACKGROUND Style
|
| 71 |
+
if light:
|
| 72 |
+
self._light |= WinStyle.BRIGHT_BACKGROUND
|
| 73 |
+
else:
|
| 74 |
+
self._light &= ~WinStyle.BRIGHT_BACKGROUND
|
| 75 |
+
self.set_console(on_stderr=on_stderr)
|
| 76 |
+
|
| 77 |
+
def style(self, style=None, on_stderr=False):
|
| 78 |
+
if style is None:
|
| 79 |
+
style = self._default_style
|
| 80 |
+
self._style = style
|
| 81 |
+
self.set_console(on_stderr=on_stderr)
|
| 82 |
+
|
| 83 |
+
def set_console(self, attrs=None, on_stderr=False):
|
| 84 |
+
if attrs is None:
|
| 85 |
+
attrs = self.get_attrs()
|
| 86 |
+
handle = win32.STDOUT
|
| 87 |
+
if on_stderr:
|
| 88 |
+
handle = win32.STDERR
|
| 89 |
+
win32.SetConsoleTextAttribute(handle, attrs)
|
| 90 |
+
|
| 91 |
+
def get_position(self, handle):
|
| 92 |
+
position = win32.GetConsoleScreenBufferInfo(handle).dwCursorPosition
|
| 93 |
+
# Because Windows coordinates are 0-based,
|
| 94 |
+
# and win32.SetConsoleCursorPosition expects 1-based.
|
| 95 |
+
position.X += 1
|
| 96 |
+
position.Y += 1
|
| 97 |
+
return position
|
| 98 |
+
|
| 99 |
+
def set_cursor_position(self, position=None, on_stderr=False):
|
| 100 |
+
if position is None:
|
| 101 |
+
# I'm not currently tracking the position, so there is no default.
|
| 102 |
+
# position = self.get_position()
|
| 103 |
+
return
|
| 104 |
+
handle = win32.STDOUT
|
| 105 |
+
if on_stderr:
|
| 106 |
+
handle = win32.STDERR
|
| 107 |
+
win32.SetConsoleCursorPosition(handle, position)
|
| 108 |
+
|
| 109 |
+
def cursor_adjust(self, x, y, on_stderr=False):
|
| 110 |
+
handle = win32.STDOUT
|
| 111 |
+
if on_stderr:
|
| 112 |
+
handle = win32.STDERR
|
| 113 |
+
position = self.get_position(handle)
|
| 114 |
+
adjusted_position = (position.Y + y, position.X + x)
|
| 115 |
+
win32.SetConsoleCursorPosition(handle, adjusted_position, adjust=False)
|
| 116 |
+
|
| 117 |
+
def erase_screen(self, mode=0, on_stderr=False):
|
| 118 |
+
# 0 should clear from the cursor to the end of the screen.
|
| 119 |
+
# 1 should clear from the cursor to the beginning of the screen.
|
| 120 |
+
# 2 should clear the entire screen, and move cursor to (1,1)
|
| 121 |
+
handle = win32.STDOUT
|
| 122 |
+
if on_stderr:
|
| 123 |
+
handle = win32.STDERR
|
| 124 |
+
csbi = win32.GetConsoleScreenBufferInfo(handle)
|
| 125 |
+
# get the number of character cells in the current buffer
|
| 126 |
+
cells_in_screen = csbi.dwSize.X * csbi.dwSize.Y
|
| 127 |
+
# get number of character cells before current cursor position
|
| 128 |
+
cells_before_cursor = csbi.dwSize.X * csbi.dwCursorPosition.Y + csbi.dwCursorPosition.X
|
| 129 |
+
if mode == 0:
|
| 130 |
+
from_coord = csbi.dwCursorPosition
|
| 131 |
+
cells_to_erase = cells_in_screen - cells_before_cursor
|
| 132 |
+
elif mode == 1:
|
| 133 |
+
from_coord = win32.COORD(0, 0)
|
| 134 |
+
cells_to_erase = cells_before_cursor
|
| 135 |
+
elif mode == 2:
|
| 136 |
+
from_coord = win32.COORD(0, 0)
|
| 137 |
+
cells_to_erase = cells_in_screen
|
| 138 |
+
else:
|
| 139 |
+
# invalid mode
|
| 140 |
+
return
|
| 141 |
+
# fill the entire screen with blanks
|
| 142 |
+
win32.FillConsoleOutputCharacter(handle, ' ', cells_to_erase, from_coord)
|
| 143 |
+
# now set the buffer's attributes accordingly
|
| 144 |
+
win32.FillConsoleOutputAttribute(handle, self.get_attrs(), cells_to_erase, from_coord)
|
| 145 |
+
if mode == 2:
|
| 146 |
+
# put the cursor where needed
|
| 147 |
+
win32.SetConsoleCursorPosition(handle, (1, 1))
|
| 148 |
+
|
| 149 |
+
def erase_line(self, mode=0, on_stderr=False):
|
| 150 |
+
# 0 should clear from the cursor to the end of the line.
|
| 151 |
+
# 1 should clear from the cursor to the beginning of the line.
|
| 152 |
+
# 2 should clear the entire line.
|
| 153 |
+
handle = win32.STDOUT
|
| 154 |
+
if on_stderr:
|
| 155 |
+
handle = win32.STDERR
|
| 156 |
+
csbi = win32.GetConsoleScreenBufferInfo(handle)
|
| 157 |
+
if mode == 0:
|
| 158 |
+
from_coord = csbi.dwCursorPosition
|
| 159 |
+
cells_to_erase = csbi.dwSize.X - csbi.dwCursorPosition.X
|
| 160 |
+
elif mode == 1:
|
| 161 |
+
from_coord = win32.COORD(0, csbi.dwCursorPosition.Y)
|
| 162 |
+
cells_to_erase = csbi.dwCursorPosition.X
|
| 163 |
+
elif mode == 2:
|
| 164 |
+
from_coord = win32.COORD(0, csbi.dwCursorPosition.Y)
|
| 165 |
+
cells_to_erase = csbi.dwSize.X
|
| 166 |
+
else:
|
| 167 |
+
# invalid mode
|
| 168 |
+
return
|
| 169 |
+
# fill the entire screen with blanks
|
| 170 |
+
win32.FillConsoleOutputCharacter(handle, ' ', cells_to_erase, from_coord)
|
| 171 |
+
# now set the buffer's attributes accordingly
|
| 172 |
+
win32.FillConsoleOutputAttribute(handle, self.get_attrs(), cells_to_erase, from_coord)
|
| 173 |
+
|
| 174 |
+
def set_title(self, title):
|
| 175 |
+
win32.SetConsoleTitle(title)
|
| 176 |
+
|
| 177 |
+
|
| 178 |
+
def enable_vt_processing(fd):
|
| 179 |
+
if win32.windll is None or not win32.winapi_test():
|
| 180 |
+
return False
|
| 181 |
+
|
| 182 |
+
try:
|
| 183 |
+
handle = get_osfhandle(fd)
|
| 184 |
+
mode = win32.GetConsoleMode(handle)
|
| 185 |
+
win32.SetConsoleMode(
|
| 186 |
+
handle,
|
| 187 |
+
mode | win32.ENABLE_VIRTUAL_TERMINAL_PROCESSING,
|
| 188 |
+
)
|
| 189 |
+
|
| 190 |
+
mode = win32.GetConsoleMode(handle)
|
| 191 |
+
if mode & win32.ENABLE_VIRTUAL_TERMINAL_PROCESSING:
|
| 192 |
+
return True
|
| 193 |
+
# Can get TypeError in testsuite where 'fd' is a Mock()
|
| 194 |
+
except (OSError, TypeError):
|
| 195 |
+
return False
|
.venv/lib/python3.11/site-packages/ray/thirdparty_files/psutil-6.1.1.dist-info/INSTALLER
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
pip
|
.venv/lib/python3.11/site-packages/ray/thirdparty_files/psutil-6.1.1.dist-info/LICENSE
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
BSD 3-Clause License
|
| 2 |
+
|
| 3 |
+
Copyright (c) 2009, Jay Loden, Dave Daeschler, Giampaolo Rodola
|
| 4 |
+
All rights reserved.
|
| 5 |
+
|
| 6 |
+
Redistribution and use in source and binary forms, with or without modification,
|
| 7 |
+
are permitted provided that the following conditions are met:
|
| 8 |
+
|
| 9 |
+
* Redistributions of source code must retain the above copyright notice, this
|
| 10 |
+
list of conditions and the following disclaimer.
|
| 11 |
+
|
| 12 |
+
* Redistributions in binary form must reproduce the above copyright notice,
|
| 13 |
+
this list of conditions and the following disclaimer in the documentation
|
| 14 |
+
and/or other materials provided with the distribution.
|
| 15 |
+
|
| 16 |
+
* Neither the name of the psutil authors nor the names of its contributors
|
| 17 |
+
may be used to endorse or promote products derived from this software without
|
| 18 |
+
specific prior written permission.
|
| 19 |
+
|
| 20 |
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
| 21 |
+
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
| 22 |
+
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
| 23 |
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
| 24 |
+
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
| 25 |
+
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
| 26 |
+
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
| 27 |
+
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
| 28 |
+
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
| 29 |
+
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
.venv/lib/python3.11/site-packages/ray/thirdparty_files/psutil-6.1.1.dist-info/METADATA
ADDED
|
@@ -0,0 +1,548 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
Metadata-Version: 2.1
|
| 2 |
+
Name: psutil
|
| 3 |
+
Version: 6.1.1
|
| 4 |
+
Summary: Cross-platform lib for process and system monitoring in Python.
|
| 5 |
+
Home-page: https://github.com/giampaolo/psutil
|
| 6 |
+
Author: Giampaolo Rodola
|
| 7 |
+
Author-email: g.rodola@gmail.com
|
| 8 |
+
License: BSD-3-Clause
|
| 9 |
+
Keywords: ps,top,kill,free,lsof,netstat,nice,tty,ionice,uptime,taskmgr,process,df,iotop,iostat,ifconfig,taskset,who,pidof,pmap,smem,pstree,monitoring,ulimit,prlimit,smem,performance,metrics,agent,observability
|
| 10 |
+
Platform: Platform Independent
|
| 11 |
+
Classifier: Development Status :: 5 - Production/Stable
|
| 12 |
+
Classifier: Environment :: Console
|
| 13 |
+
Classifier: Environment :: Win32 (MS Windows)
|
| 14 |
+
Classifier: Intended Audience :: Developers
|
| 15 |
+
Classifier: Intended Audience :: Information Technology
|
| 16 |
+
Classifier: Intended Audience :: System Administrators
|
| 17 |
+
Classifier: License :: OSI Approved :: BSD License
|
| 18 |
+
Classifier: Operating System :: MacOS :: MacOS X
|
| 19 |
+
Classifier: Operating System :: Microsoft :: Windows :: Windows 10
|
| 20 |
+
Classifier: Operating System :: Microsoft :: Windows :: Windows 7
|
| 21 |
+
Classifier: Operating System :: Microsoft :: Windows :: Windows 8
|
| 22 |
+
Classifier: Operating System :: Microsoft :: Windows :: Windows 8.1
|
| 23 |
+
Classifier: Operating System :: Microsoft :: Windows :: Windows Server 2003
|
| 24 |
+
Classifier: Operating System :: Microsoft :: Windows :: Windows Server 2008
|
| 25 |
+
Classifier: Operating System :: Microsoft :: Windows :: Windows Vista
|
| 26 |
+
Classifier: Operating System :: Microsoft
|
| 27 |
+
Classifier: Operating System :: OS Independent
|
| 28 |
+
Classifier: Operating System :: POSIX :: AIX
|
| 29 |
+
Classifier: Operating System :: POSIX :: BSD :: FreeBSD
|
| 30 |
+
Classifier: Operating System :: POSIX :: BSD :: NetBSD
|
| 31 |
+
Classifier: Operating System :: POSIX :: BSD :: OpenBSD
|
| 32 |
+
Classifier: Operating System :: POSIX :: BSD
|
| 33 |
+
Classifier: Operating System :: POSIX :: Linux
|
| 34 |
+
Classifier: Operating System :: POSIX :: SunOS/Solaris
|
| 35 |
+
Classifier: Operating System :: POSIX
|
| 36 |
+
Classifier: Programming Language :: C
|
| 37 |
+
Classifier: Programming Language :: Python :: 2
|
| 38 |
+
Classifier: Programming Language :: Python :: 2.7
|
| 39 |
+
Classifier: Programming Language :: Python :: 3
|
| 40 |
+
Classifier: Programming Language :: Python :: Implementation :: CPython
|
| 41 |
+
Classifier: Programming Language :: Python :: Implementation :: PyPy
|
| 42 |
+
Classifier: Programming Language :: Python
|
| 43 |
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
| 44 |
+
Classifier: Topic :: Software Development :: Libraries
|
| 45 |
+
Classifier: Topic :: System :: Benchmark
|
| 46 |
+
Classifier: Topic :: System :: Hardware :: Hardware Drivers
|
| 47 |
+
Classifier: Topic :: System :: Hardware
|
| 48 |
+
Classifier: Topic :: System :: Monitoring
|
| 49 |
+
Classifier: Topic :: System :: Networking :: Monitoring :: Hardware Watchdog
|
| 50 |
+
Classifier: Topic :: System :: Networking :: Monitoring
|
| 51 |
+
Classifier: Topic :: System :: Networking
|
| 52 |
+
Classifier: Topic :: System :: Operating System
|
| 53 |
+
Classifier: Topic :: System :: Systems Administration
|
| 54 |
+
Classifier: Topic :: Utilities
|
| 55 |
+
Requires-Python: >=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*
|
| 56 |
+
Description-Content-Type: text/x-rst
|
| 57 |
+
License-File: LICENSE
|
| 58 |
+
Provides-Extra: dev
|
| 59 |
+
Requires-Dist: abi3audit ; extra == 'dev'
|
| 60 |
+
Requires-Dist: black ; extra == 'dev'
|
| 61 |
+
Requires-Dist: check-manifest ; extra == 'dev'
|
| 62 |
+
Requires-Dist: coverage ; extra == 'dev'
|
| 63 |
+
Requires-Dist: packaging ; extra == 'dev'
|
| 64 |
+
Requires-Dist: pylint ; extra == 'dev'
|
| 65 |
+
Requires-Dist: pyperf ; extra == 'dev'
|
| 66 |
+
Requires-Dist: pypinfo ; extra == 'dev'
|
| 67 |
+
Requires-Dist: pytest-cov ; extra == 'dev'
|
| 68 |
+
Requires-Dist: requests ; extra == 'dev'
|
| 69 |
+
Requires-Dist: rstcheck ; extra == 'dev'
|
| 70 |
+
Requires-Dist: ruff ; extra == 'dev'
|
| 71 |
+
Requires-Dist: sphinx ; extra == 'dev'
|
| 72 |
+
Requires-Dist: sphinx-rtd-theme ; extra == 'dev'
|
| 73 |
+
Requires-Dist: toml-sort ; extra == 'dev'
|
| 74 |
+
Requires-Dist: twine ; extra == 'dev'
|
| 75 |
+
Requires-Dist: virtualenv ; extra == 'dev'
|
| 76 |
+
Requires-Dist: vulture ; extra == 'dev'
|
| 77 |
+
Requires-Dist: wheel ; extra == 'dev'
|
| 78 |
+
Provides-Extra: test
|
| 79 |
+
Requires-Dist: pytest ; extra == 'test'
|
| 80 |
+
Requires-Dist: pytest-xdist ; extra == 'test'
|
| 81 |
+
Requires-Dist: setuptools ; extra == 'test'
|
| 82 |
+
|
| 83 |
+
| |downloads| |stars| |forks| |contributors| |coverage|
|
| 84 |
+
| |version| |py-versions| |packages| |license|
|
| 85 |
+
| |github-actions-wheels| |github-actions-bsd| |appveyor| |doc| |twitter| |tidelift|
|
| 86 |
+
|
| 87 |
+
.. |downloads| image:: https://img.shields.io/pypi/dm/psutil.svg
|
| 88 |
+
:target: https://pepy.tech/project/psutil
|
| 89 |
+
:alt: Downloads
|
| 90 |
+
|
| 91 |
+
.. |stars| image:: https://img.shields.io/github/stars/giampaolo/psutil.svg
|
| 92 |
+
:target: https://github.com/giampaolo/psutil/stargazers
|
| 93 |
+
:alt: Github stars
|
| 94 |
+
|
| 95 |
+
.. |forks| image:: https://img.shields.io/github/forks/giampaolo/psutil.svg
|
| 96 |
+
:target: https://github.com/giampaolo/psutil/network/members
|
| 97 |
+
:alt: Github forks
|
| 98 |
+
|
| 99 |
+
.. |contributors| image:: https://img.shields.io/github/contributors/giampaolo/psutil.svg
|
| 100 |
+
:target: https://github.com/giampaolo/psutil/graphs/contributors
|
| 101 |
+
:alt: Contributors
|
| 102 |
+
|
| 103 |
+
.. |github-actions-wheels| image:: https://img.shields.io/github/actions/workflow/status/giampaolo/psutil/.github/workflows/build.yml.svg?label=Linux%2C%20macOS%2C%20Windows
|
| 104 |
+
:target: https://github.com/giampaolo/psutil/actions?query=workflow%3Abuild
|
| 105 |
+
:alt: Linux, macOS, Windows
|
| 106 |
+
|
| 107 |
+
.. |github-actions-bsd| image:: https://img.shields.io/github/actions/workflow/status/giampaolo/psutil/.github/workflows/bsd.yml.svg?label=FreeBSD,%20NetBSD,%20OpenBSD
|
| 108 |
+
:target: https://github.com/giampaolo/psutil/actions?query=workflow%3Absd-tests
|
| 109 |
+
:alt: FreeBSD, NetBSD, OpenBSD
|
| 110 |
+
|
| 111 |
+
.. |appveyor| image:: https://img.shields.io/appveyor/build/giampaolo/psutil/master.svg?maxAge=3600&label=Windows%20(py2)
|
| 112 |
+
:target: https://ci.appveyor.com/project/giampaolo/psutil
|
| 113 |
+
:alt: Windows (Appveyor)
|
| 114 |
+
|
| 115 |
+
.. |coverage| image:: https://coveralls.io/repos/github/giampaolo/psutil/badge.svg?branch=master
|
| 116 |
+
:target: https://coveralls.io/github/giampaolo/psutil?branch=master
|
| 117 |
+
:alt: Test coverage (coverall.io)
|
| 118 |
+
|
| 119 |
+
.. |doc| image:: https://readthedocs.org/projects/psutil/badge/?version=latest
|
| 120 |
+
:target: https://psutil.readthedocs.io/en/latest/
|
| 121 |
+
:alt: Documentation Status
|
| 122 |
+
|
| 123 |
+
.. |version| image:: https://img.shields.io/pypi/v/psutil.svg?label=pypi
|
| 124 |
+
:target: https://pypi.org/project/psutil
|
| 125 |
+
:alt: Latest version
|
| 126 |
+
|
| 127 |
+
.. |py-versions| image:: https://img.shields.io/pypi/pyversions/psutil.svg
|
| 128 |
+
:alt: Supported Python versions
|
| 129 |
+
|
| 130 |
+
.. |packages| image:: https://repology.org/badge/tiny-repos/python:psutil.svg
|
| 131 |
+
:target: https://repology.org/metapackage/python:psutil/versions
|
| 132 |
+
:alt: Binary packages
|
| 133 |
+
|
| 134 |
+
.. |license| image:: https://img.shields.io/pypi/l/psutil.svg
|
| 135 |
+
:target: https://github.com/giampaolo/psutil/blob/master/LICENSE
|
| 136 |
+
:alt: License
|
| 137 |
+
|
| 138 |
+
.. |twitter| image:: https://img.shields.io/twitter/follow/grodola.svg?label=follow&style=flat&logo=twitter&logoColor=4FADFF
|
| 139 |
+
:target: https://twitter.com/grodola
|
| 140 |
+
:alt: Twitter Follow
|
| 141 |
+
|
| 142 |
+
.. |tidelift| image:: https://tidelift.com/badges/github/giampaolo/psutil?style=flat
|
| 143 |
+
:target: https://tidelift.com/subscription/pkg/pypi-psutil?utm_source=pypi-psutil&utm_medium=referral&utm_campaign=readme
|
| 144 |
+
:alt: Tidelift
|
| 145 |
+
|
| 146 |
+
-----
|
| 147 |
+
|
| 148 |
+
Quick links
|
| 149 |
+
===========
|
| 150 |
+
|
| 151 |
+
- `Home page <https://github.com/giampaolo/psutil>`_
|
| 152 |
+
- `Install <https://github.com/giampaolo/psutil/blob/master/INSTALL.rst>`_
|
| 153 |
+
- `Documentation <http://psutil.readthedocs.io>`_
|
| 154 |
+
- `Download <https://pypi.org/project/psutil/#files>`_
|
| 155 |
+
- `Forum <http://groups.google.com/group/psutil/topics>`_
|
| 156 |
+
- `StackOverflow <https://stackoverflow.com/questions/tagged/psutil>`_
|
| 157 |
+
- `Blog <https://gmpy.dev/tags/psutil>`_
|
| 158 |
+
- `What's new <https://github.com/giampaolo/psutil/blob/master/HISTORY.rst>`_
|
| 159 |
+
|
| 160 |
+
|
| 161 |
+
Summary
|
| 162 |
+
=======
|
| 163 |
+
|
| 164 |
+
psutil (process and system utilities) is a cross-platform library for
|
| 165 |
+
retrieving information on **running processes** and **system utilization**
|
| 166 |
+
(CPU, memory, disks, network, sensors) in Python.
|
| 167 |
+
It is useful mainly for **system monitoring**, **profiling and limiting process
|
| 168 |
+
resources** and **management of running processes**.
|
| 169 |
+
It implements many functionalities offered by classic UNIX command line tools
|
| 170 |
+
such as *ps, top, iotop, lsof, netstat, ifconfig, free* and others.
|
| 171 |
+
psutil currently supports the following platforms:
|
| 172 |
+
|
| 173 |
+
- **Linux**
|
| 174 |
+
- **Windows**
|
| 175 |
+
- **macOS**
|
| 176 |
+
- **FreeBSD, OpenBSD**, **NetBSD**
|
| 177 |
+
- **Sun Solaris**
|
| 178 |
+
- **AIX**
|
| 179 |
+
|
| 180 |
+
Supported Python versions are **2.7**, **3.6+** and
|
| 181 |
+
`PyPy <http://pypy.org/>`__.
|
| 182 |
+
|
| 183 |
+
Funding
|
| 184 |
+
=======
|
| 185 |
+
|
| 186 |
+
While psutil is free software and will always be, the project would benefit
|
| 187 |
+
immensely from some funding.
|
| 188 |
+
Keeping up with bug reports and maintenance has become hardly sustainable for
|
| 189 |
+
me alone in terms of time.
|
| 190 |
+
If you're a company that's making significant use of psutil you can consider
|
| 191 |
+
becoming a sponsor via `GitHub Sponsors <https://github.com/sponsors/giampaolo>`__,
|
| 192 |
+
`Open Collective <https://opencollective.com/psutil>`__ or
|
| 193 |
+
`PayPal <https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=A9ZS7PKKRM3S8>`__
|
| 194 |
+
and have your logo displayed in here and psutil `doc <https://psutil.readthedocs.io>`__.
|
| 195 |
+
|
| 196 |
+
Sponsors
|
| 197 |
+
========
|
| 198 |
+
|
| 199 |
+
.. image:: https://github.com/giampaolo/psutil/raw/master/docs/_static/tidelift-logo.png
|
| 200 |
+
:width: 200
|
| 201 |
+
:alt: Alternative text
|
| 202 |
+
|
| 203 |
+
`Add your logo <https://github.com/sponsors/giampaolo>`__.
|
| 204 |
+
|
| 205 |
+
Example usages
|
| 206 |
+
==============
|
| 207 |
+
|
| 208 |
+
This represents pretty much the whole psutil API.
|
| 209 |
+
|
| 210 |
+
CPU
|
| 211 |
+
---
|
| 212 |
+
|
| 213 |
+
.. code-block:: python
|
| 214 |
+
|
| 215 |
+
>>> import psutil
|
| 216 |
+
>>>
|
| 217 |
+
>>> psutil.cpu_times()
|
| 218 |
+
scputimes(user=3961.46, nice=169.729, system=2150.659, idle=16900.540, iowait=629.59, irq=0.0, softirq=19.42, steal=0.0, guest=0, guest_nice=0.0)
|
| 219 |
+
>>>
|
| 220 |
+
>>> for x in range(3):
|
| 221 |
+
... psutil.cpu_percent(interval=1)
|
| 222 |
+
...
|
| 223 |
+
4.0
|
| 224 |
+
5.9
|
| 225 |
+
3.8
|
| 226 |
+
>>>
|
| 227 |
+
>>> for x in range(3):
|
| 228 |
+
... psutil.cpu_percent(interval=1, percpu=True)
|
| 229 |
+
...
|
| 230 |
+
[4.0, 6.9, 3.7, 9.2]
|
| 231 |
+
[7.0, 8.5, 2.4, 2.1]
|
| 232 |
+
[1.2, 9.0, 9.9, 7.2]
|
| 233 |
+
>>>
|
| 234 |
+
>>> for x in range(3):
|
| 235 |
+
... psutil.cpu_times_percent(interval=1, percpu=False)
|
| 236 |
+
...
|
| 237 |
+
scputimes(user=1.5, nice=0.0, system=0.5, idle=96.5, iowait=1.5, irq=0.0, softirq=0.0, steal=0.0, guest=0.0, guest_nice=0.0)
|
| 238 |
+
scputimes(user=1.0, nice=0.0, system=0.0, idle=99.0, iowait=0.0, irq=0.0, softirq=0.0, steal=0.0, guest=0.0, guest_nice=0.0)
|
| 239 |
+
scputimes(user=2.0, nice=0.0, system=0.0, idle=98.0, iowait=0.0, irq=0.0, softirq=0.0, steal=0.0, guest=0.0, guest_nice=0.0)
|
| 240 |
+
>>>
|
| 241 |
+
>>> psutil.cpu_count()
|
| 242 |
+
4
|
| 243 |
+
>>> psutil.cpu_count(logical=False)
|
| 244 |
+
2
|
| 245 |
+
>>>
|
| 246 |
+
>>> psutil.cpu_stats()
|
| 247 |
+
scpustats(ctx_switches=20455687, interrupts=6598984, soft_interrupts=2134212, syscalls=0)
|
| 248 |
+
>>>
|
| 249 |
+
>>> psutil.cpu_freq()
|
| 250 |
+
scpufreq(current=931.42925, min=800.0, max=3500.0)
|
| 251 |
+
>>>
|
| 252 |
+
>>> psutil.getloadavg() # also on Windows (emulated)
|
| 253 |
+
(3.14, 3.89, 4.67)
|
| 254 |
+
|
| 255 |
+
Memory
|
| 256 |
+
------
|
| 257 |
+
|
| 258 |
+
.. code-block:: python
|
| 259 |
+
|
| 260 |
+
>>> psutil.virtual_memory()
|
| 261 |
+
svmem(total=10367352832, available=6472179712, percent=37.6, used=8186245120, free=2181107712, active=4748992512, inactive=2758115328, buffers=790724608, cached=3500347392, shared=787554304)
|
| 262 |
+
>>> psutil.swap_memory()
|
| 263 |
+
sswap(total=2097147904, used=296128512, free=1801019392, percent=14.1, sin=304193536, sout=677842944)
|
| 264 |
+
>>>
|
| 265 |
+
|
| 266 |
+
Disks
|
| 267 |
+
-----
|
| 268 |
+
|
| 269 |
+
.. code-block:: python
|
| 270 |
+
|
| 271 |
+
>>> psutil.disk_partitions()
|
| 272 |
+
[sdiskpart(device='/dev/sda1', mountpoint='/', fstype='ext4', opts='rw,nosuid'),
|
| 273 |
+
sdiskpart(device='/dev/sda2', mountpoint='/home', fstype='ext', opts='rw')]
|
| 274 |
+
>>>
|
| 275 |
+
>>> psutil.disk_usage('/')
|
| 276 |
+
sdiskusage(total=21378641920, used=4809781248, free=15482871808, percent=22.5)
|
| 277 |
+
>>>
|
| 278 |
+
>>> psutil.disk_io_counters(perdisk=False)
|
| 279 |
+
sdiskio(read_count=719566, write_count=1082197, read_bytes=18626220032, write_bytes=24081764352, read_time=5023392, write_time=63199568, read_merged_count=619166, write_merged_count=812396, busy_time=4523412)
|
| 280 |
+
>>>
|
| 281 |
+
|
| 282 |
+
Network
|
| 283 |
+
-------
|
| 284 |
+
|
| 285 |
+
.. code-block:: python
|
| 286 |
+
|
| 287 |
+
>>> psutil.net_io_counters(pernic=True)
|
| 288 |
+
{'eth0': netio(bytes_sent=485291293, bytes_recv=6004858642, packets_sent=3251564, packets_recv=4787798, errin=0, errout=0, dropin=0, dropout=0),
|
| 289 |
+
'lo': netio(bytes_sent=2838627, bytes_recv=2838627, packets_sent=30567, packets_recv=30567, errin=0, errout=0, dropin=0, dropout=0)}
|
| 290 |
+
>>>
|
| 291 |
+
>>> psutil.net_connections(kind='tcp')
|
| 292 |
+
[sconn(fd=115, family=<AddressFamily.AF_INET: 2>, type=<SocketType.SOCK_STREAM: 1>, laddr=addr(ip='10.0.0.1', port=48776), raddr=addr(ip='93.186.135.91', port=80), status='ESTABLISHED', pid=1254),
|
| 293 |
+
sconn(fd=117, family=<AddressFamily.AF_INET: 2>, type=<SocketType.SOCK_STREAM: 1>, laddr=addr(ip='10.0.0.1', port=43761), raddr=addr(ip='72.14.234.100', port=80), status='CLOSING', pid=2987),
|
| 294 |
+
...]
|
| 295 |
+
>>>
|
| 296 |
+
>>> psutil.net_if_addrs()
|
| 297 |
+
{'lo': [snicaddr(family=<AddressFamily.AF_INET: 2>, address='127.0.0.1', netmask='255.0.0.0', broadcast='127.0.0.1', ptp=None),
|
| 298 |
+
snicaddr(family=<AddressFamily.AF_INET6: 10>, address='::1', netmask='ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff', broadcast=None, ptp=None),
|
| 299 |
+
snicaddr(family=<AddressFamily.AF_LINK: 17>, address='00:00:00:00:00:00', netmask=None, broadcast='00:00:00:00:00:00', ptp=None)],
|
| 300 |
+
'wlan0': [snicaddr(family=<AddressFamily.AF_INET: 2>, address='192.168.1.3', netmask='255.255.255.0', broadcast='192.168.1.255', ptp=None),
|
| 301 |
+
snicaddr(family=<AddressFamily.AF_INET6: 10>, address='fe80::c685:8ff:fe45:641%wlan0', netmask='ffff:ffff:ffff:ffff::', broadcast=None, ptp=None),
|
| 302 |
+
snicaddr(family=<AddressFamily.AF_LINK: 17>, address='c4:85:08:45:06:41', netmask=None, broadcast='ff:ff:ff:ff:ff:ff', ptp=None)]}
|
| 303 |
+
>>>
|
| 304 |
+
>>> psutil.net_if_stats()
|
| 305 |
+
{'lo': snicstats(isup=True, duplex=<NicDuplex.NIC_DUPLEX_UNKNOWN: 0>, speed=0, mtu=65536, flags='up,loopback,running'),
|
| 306 |
+
'wlan0': snicstats(isup=True, duplex=<NicDuplex.NIC_DUPLEX_FULL: 2>, speed=100, mtu=1500, flags='up,broadcast,running,multicast')}
|
| 307 |
+
>>>
|
| 308 |
+
|
| 309 |
+
Sensors
|
| 310 |
+
-------
|
| 311 |
+
|
| 312 |
+
.. code-block:: python
|
| 313 |
+
|
| 314 |
+
>>> import psutil
|
| 315 |
+
>>> psutil.sensors_temperatures()
|
| 316 |
+
{'acpitz': [shwtemp(label='', current=47.0, high=103.0, critical=103.0)],
|
| 317 |
+
'asus': [shwtemp(label='', current=47.0, high=None, critical=None)],
|
| 318 |
+
'coretemp': [shwtemp(label='Physical id 0', current=52.0, high=100.0, critical=100.0),
|
| 319 |
+
shwtemp(label='Core 0', current=45.0, high=100.0, critical=100.0)]}
|
| 320 |
+
>>>
|
| 321 |
+
>>> psutil.sensors_fans()
|
| 322 |
+
{'asus': [sfan(label='cpu_fan', current=3200)]}
|
| 323 |
+
>>>
|
| 324 |
+
>>> psutil.sensors_battery()
|
| 325 |
+
sbattery(percent=93, secsleft=16628, power_plugged=False)
|
| 326 |
+
>>>
|
| 327 |
+
|
| 328 |
+
Other system info
|
| 329 |
+
-----------------
|
| 330 |
+
|
| 331 |
+
.. code-block:: python
|
| 332 |
+
|
| 333 |
+
>>> import psutil
|
| 334 |
+
>>> psutil.users()
|
| 335 |
+
[suser(name='giampaolo', terminal='pts/2', host='localhost', started=1340737536.0, pid=1352),
|
| 336 |
+
suser(name='giampaolo', terminal='pts/3', host='localhost', started=1340737792.0, pid=1788)]
|
| 337 |
+
>>>
|
| 338 |
+
>>> psutil.boot_time()
|
| 339 |
+
1365519115.0
|
| 340 |
+
>>>
|
| 341 |
+
|
| 342 |
+
Process management
|
| 343 |
+
------------------
|
| 344 |
+
|
| 345 |
+
.. code-block:: python
|
| 346 |
+
|
| 347 |
+
>>> import psutil
|
| 348 |
+
>>> psutil.pids()
|
| 349 |
+
[1, 2, 3, 4, 5, 6, 7, 46, 48, 50, 51, 178, 182, 222, 223, 224, 268, 1215,
|
| 350 |
+
1216, 1220, 1221, 1243, 1244, 1301, 1601, 2237, 2355, 2637, 2774, 3932,
|
| 351 |
+
4176, 4177, 4185, 4187, 4189, 4225, 4243, 4245, 4263, 4282, 4306, 4311,
|
| 352 |
+
4312, 4313, 4314, 4337, 4339, 4357, 4358, 4363, 4383, 4395, 4408, 4433,
|
| 353 |
+
4443, 4445, 4446, 5167, 5234, 5235, 5252, 5318, 5424, 5644, 6987, 7054,
|
| 354 |
+
7055, 7071]
|
| 355 |
+
>>>
|
| 356 |
+
>>> p = psutil.Process(7055)
|
| 357 |
+
>>> p
|
| 358 |
+
psutil.Process(pid=7055, name='python3', status='running', started='09:04:44')
|
| 359 |
+
>>> p.pid
|
| 360 |
+
7055
|
| 361 |
+
>>> p.name()
|
| 362 |
+
'python3'
|
| 363 |
+
>>> p.exe()
|
| 364 |
+
'/usr/bin/python3'
|
| 365 |
+
>>> p.cwd()
|
| 366 |
+
'/home/giampaolo'
|
| 367 |
+
>>> p.cmdline()
|
| 368 |
+
['/usr/bin/python3', 'main.py']
|
| 369 |
+
>>>
|
| 370 |
+
>>> p.ppid()
|
| 371 |
+
7054
|
| 372 |
+
>>> p.parent()
|
| 373 |
+
psutil.Process(pid=4699, name='bash', status='sleeping', started='09:06:44')
|
| 374 |
+
>>> p.parents()
|
| 375 |
+
[psutil.Process(pid=4699, name='bash', started='09:06:44'),
|
| 376 |
+
psutil.Process(pid=4689, name='gnome-terminal-server', status='sleeping', started='0:06:44'),
|
| 377 |
+
psutil.Process(pid=1, name='systemd', status='sleeping', started='05:56:55')]
|
| 378 |
+
>>> p.children(recursive=True)
|
| 379 |
+
[psutil.Process(pid=29835, name='python3', status='sleeping', started='11:45:38'),
|
| 380 |
+
psutil.Process(pid=29836, name='python3', status='waking', started='11:43:39')]
|
| 381 |
+
>>>
|
| 382 |
+
>>> p.status()
|
| 383 |
+
'running'
|
| 384 |
+
>>> p.create_time()
|
| 385 |
+
1267551141.5019531
|
| 386 |
+
>>> p.terminal()
|
| 387 |
+
'/dev/pts/0'
|
| 388 |
+
>>>
|
| 389 |
+
>>> p.username()
|
| 390 |
+
'giampaolo'
|
| 391 |
+
>>> p.uids()
|
| 392 |
+
puids(real=1000, effective=1000, saved=1000)
|
| 393 |
+
>>> p.gids()
|
| 394 |
+
pgids(real=1000, effective=1000, saved=1000)
|
| 395 |
+
>>>
|
| 396 |
+
>>> p.cpu_times()
|
| 397 |
+
pcputimes(user=1.02, system=0.31, children_user=0.32, children_system=0.1, iowait=0.0)
|
| 398 |
+
>>> p.cpu_percent(interval=1.0)
|
| 399 |
+
12.1
|
| 400 |
+
>>> p.cpu_affinity()
|
| 401 |
+
[0, 1, 2, 3]
|
| 402 |
+
>>> p.cpu_affinity([0, 1]) # set
|
| 403 |
+
>>> p.cpu_num()
|
| 404 |
+
1
|
| 405 |
+
>>>
|
| 406 |
+
>>> p.memory_info()
|
| 407 |
+
pmem(rss=10915840, vms=67608576, shared=3313664, text=2310144, lib=0, data=7262208, dirty=0)
|
| 408 |
+
>>> p.memory_full_info() # "real" USS memory usage (Linux, macOS, Win only)
|
| 409 |
+
pfullmem(rss=10199040, vms=52133888, shared=3887104, text=2867200, lib=0, data=5967872, dirty=0, uss=6545408, pss=6872064, swap=0)
|
| 410 |
+
>>> p.memory_percent()
|
| 411 |
+
0.7823
|
| 412 |
+
>>> p.memory_maps()
|
| 413 |
+
[pmmap_grouped(path='/lib/x8664-linux-gnu/libutil-2.15.so', rss=32768, size=2125824, pss=32768, shared_clean=0, shared_dirty=0, private_clean=20480, private_dirty=12288, referenced=32768, anonymous=12288, swap=0),
|
| 414 |
+
pmmap_grouped(path='/lib/x8664-linux-gnu/libc-2.15.so', rss=3821568, size=3842048, pss=3821568, shared_clean=0, shared_dirty=0, private_clean=0, private_dirty=3821568, referenced=3575808, anonymous=3821568, swap=0),
|
| 415 |
+
pmmap_grouped(path='[heap]', rss=32768, size=139264, pss=32768, shared_clean=0, shared_dirty=0, private_clean=0, private_dirty=32768, referenced=32768, anonymous=32768, swap=0),
|
| 416 |
+
pmmap_grouped(path='[stack]', rss=2465792, size=2494464, pss=2465792, shared_clean=0, shared_dirty=0, private_clean=0, private_dirty=2465792, referenced=2277376, anonymous=2465792, swap=0),
|
| 417 |
+
...]
|
| 418 |
+
>>>
|
| 419 |
+
>>> p.io_counters()
|
| 420 |
+
pio(read_count=478001, write_count=59371, read_bytes=700416, write_bytes=69632, read_chars=456232, write_chars=517543)
|
| 421 |
+
>>>
|
| 422 |
+
>>> p.open_files()
|
| 423 |
+
[popenfile(path='/home/giampaolo/monit.py', fd=3, position=0, mode='r', flags=32768),
|
| 424 |
+
popenfile(path='/var/log/monit.log', fd=4, position=235542, mode='a', flags=33793)]
|
| 425 |
+
>>>
|
| 426 |
+
>>> p.net_connections(kind='tcp')
|
| 427 |
+
[pconn(fd=115, family=<AddressFamily.AF_INET: 2>, type=<SocketType.SOCK_STREAM: 1>, laddr=addr(ip='10.0.0.1', port=48776), raddr=addr(ip='93.186.135.91', port=80), status='ESTABLISHED'),
|
| 428 |
+
pconn(fd=117, family=<AddressFamily.AF_INET: 2>, type=<SocketType.SOCK_STREAM: 1>, laddr=addr(ip='10.0.0.1', port=43761), raddr=addr(ip='72.14.234.100', port=80), status='CLOSING')]
|
| 429 |
+
>>>
|
| 430 |
+
>>> p.threads()
|
| 431 |
+
[pthread(id=5234, user_time=22.5, system_time=9.2891),
|
| 432 |
+
pthread(id=5237, user_time=0.0707, system_time=1.1)]
|
| 433 |
+
>>>
|
| 434 |
+
>>> p.num_threads()
|
| 435 |
+
4
|
| 436 |
+
>>> p.num_fds()
|
| 437 |
+
8
|
| 438 |
+
>>> p.num_ctx_switches()
|
| 439 |
+
pctxsw(voluntary=78, involuntary=19)
|
| 440 |
+
>>>
|
| 441 |
+
>>> p.nice()
|
| 442 |
+
0
|
| 443 |
+
>>> p.nice(10) # set
|
| 444 |
+
>>>
|
| 445 |
+
>>> p.ionice(psutil.IOPRIO_CLASS_IDLE) # IO priority (Win and Linux only)
|
| 446 |
+
>>> p.ionice()
|
| 447 |
+
pionice(ioclass=<IOPriority.IOPRIO_CLASS_IDLE: 3>, value=0)
|
| 448 |
+
>>>
|
| 449 |
+
>>> p.rlimit(psutil.RLIMIT_NOFILE, (5, 5)) # set resource limits (Linux only)
|
| 450 |
+
>>> p.rlimit(psutil.RLIMIT_NOFILE)
|
| 451 |
+
(5, 5)
|
| 452 |
+
>>>
|
| 453 |
+
>>> p.environ()
|
| 454 |
+
{'LC_PAPER': 'it_IT.UTF-8', 'SHELL': '/bin/bash', 'GREP_OPTIONS': '--color=auto',
|
| 455 |
+
'XDG_CONFIG_DIRS': '/etc/xdg/xdg-ubuntu:/usr/share/upstart/xdg:/etc/xdg',
|
| 456 |
+
...}
|
| 457 |
+
>>>
|
| 458 |
+
>>> p.as_dict()
|
| 459 |
+
{'status': 'running', 'num_ctx_switches': pctxsw(voluntary=63, involuntary=1), 'pid': 5457, ...}
|
| 460 |
+
>>> p.is_running()
|
| 461 |
+
True
|
| 462 |
+
>>> p.suspend()
|
| 463 |
+
>>> p.resume()
|
| 464 |
+
>>>
|
| 465 |
+
>>> p.terminate()
|
| 466 |
+
>>> p.kill()
|
| 467 |
+
>>> p.wait(timeout=3)
|
| 468 |
+
<Exitcode.EX_OK: 0>
|
| 469 |
+
>>>
|
| 470 |
+
>>> psutil.test()
|
| 471 |
+
USER PID %CPU %MEM VSZ RSS TTY START TIME COMMAND
|
| 472 |
+
root 1 0.0 0.0 24584 2240 Jun17 00:00 init
|
| 473 |
+
root 2 0.0 0.0 0 0 Jun17 00:00 kthreadd
|
| 474 |
+
...
|
| 475 |
+
giampaolo 31475 0.0 0.0 20760 3024 /dev/pts/0 Jun19 00:00 python2.4
|
| 476 |
+
giampaolo 31721 0.0 2.2 773060 181896 00:04 10:30 chrome
|
| 477 |
+
root 31763 0.0 0.0 0 0 00:05 00:00 kworker/0:1
|
| 478 |
+
>>>
|
| 479 |
+
|
| 480 |
+
Further process APIs
|
| 481 |
+
--------------------
|
| 482 |
+
|
| 483 |
+
.. code-block:: python
|
| 484 |
+
|
| 485 |
+
>>> import psutil
|
| 486 |
+
>>> for proc in psutil.process_iter(['pid', 'name']):
|
| 487 |
+
... print(proc.info)
|
| 488 |
+
...
|
| 489 |
+
{'pid': 1, 'name': 'systemd'}
|
| 490 |
+
{'pid': 2, 'name': 'kthreadd'}
|
| 491 |
+
{'pid': 3, 'name': 'ksoftirqd/0'}
|
| 492 |
+
...
|
| 493 |
+
>>>
|
| 494 |
+
>>> psutil.pid_exists(3)
|
| 495 |
+
True
|
| 496 |
+
>>>
|
| 497 |
+
>>> def on_terminate(proc):
|
| 498 |
+
... print("process {} terminated".format(proc))
|
| 499 |
+
...
|
| 500 |
+
>>> # waits for multiple processes to terminate
|
| 501 |
+
>>> gone, alive = psutil.wait_procs(procs_list, timeout=3, callback=on_terminate)
|
| 502 |
+
>>>
|
| 503 |
+
|
| 504 |
+
Windows services
|
| 505 |
+
----------------
|
| 506 |
+
|
| 507 |
+
.. code-block:: python
|
| 508 |
+
|
| 509 |
+
>>> list(psutil.win_service_iter())
|
| 510 |
+
[<WindowsService(name='AeLookupSvc', display_name='Application Experience') at 38850096>,
|
| 511 |
+
<WindowsService(name='ALG', display_name='Application Layer Gateway Service') at 38850128>,
|
| 512 |
+
<WindowsService(name='APNMCP', display_name='Ask Update Service') at 38850160>,
|
| 513 |
+
<WindowsService(name='AppIDSvc', display_name='Application Identity') at 38850192>,
|
| 514 |
+
...]
|
| 515 |
+
>>> s = psutil.win_service_get('alg')
|
| 516 |
+
>>> s.as_dict()
|
| 517 |
+
{'binpath': 'C:\\Windows\\System32\\alg.exe',
|
| 518 |
+
'description': 'Provides support for 3rd party protocol plug-ins for Internet Connection Sharing',
|
| 519 |
+
'display_name': 'Application Layer Gateway Service',
|
| 520 |
+
'name': 'alg',
|
| 521 |
+
'pid': None,
|
| 522 |
+
'start_type': 'manual',
|
| 523 |
+
'status': 'stopped',
|
| 524 |
+
'username': 'NT AUTHORITY\\LocalService'}
|
| 525 |
+
|
| 526 |
+
Projects using psutil
|
| 527 |
+
=====================
|
| 528 |
+
|
| 529 |
+
Here's some I find particularly interesting:
|
| 530 |
+
|
| 531 |
+
- https://github.com/google/grr
|
| 532 |
+
- https://github.com/facebook/osquery/
|
| 533 |
+
- https://github.com/nicolargo/glances
|
| 534 |
+
- https://github.com/aristocratos/bpytop
|
| 535 |
+
- https://github.com/Jahaja/psdash
|
| 536 |
+
- https://github.com/ajenti/ajenti
|
| 537 |
+
- https://github.com/home-assistant/home-assistant/
|
| 538 |
+
|
| 539 |
+
Portings
|
| 540 |
+
========
|
| 541 |
+
|
| 542 |
+
- Go: https://github.com/shirou/gopsutil
|
| 543 |
+
- C: https://github.com/hamon-in/cpslib
|
| 544 |
+
- Rust: https://github.com/rust-psutil/rust-psutil
|
| 545 |
+
- Nim: https://github.com/johnscillieri/psutil-nim
|
| 546 |
+
|
| 547 |
+
|
| 548 |
+
|
.venv/lib/python3.11/site-packages/ray/thirdparty_files/psutil-6.1.1.dist-info/RECORD
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
psutil-6.1.1.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
|
| 2 |
+
psutil-6.1.1.dist-info/LICENSE,sha256=uJwGOzeG4o4MCjjxkx22H-015p3SopZvvs_-4PRsjRA,1548
|
| 3 |
+
psutil-6.1.1.dist-info/METADATA,sha256=BmJ_eRw5cHEIkb3lwUXfm6xLZDoVKeIdJajBxnmspAQ,22377
|
| 4 |
+
psutil-6.1.1.dist-info/RECORD,,
|
| 5 |
+
psutil-6.1.1.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
| 6 |
+
psutil-6.1.1.dist-info/WHEEL,sha256=rgpVBmjjvbINeGKCkWEGd3f40VHMTsDkQj1Lgil82zE,221
|
| 7 |
+
psutil-6.1.1.dist-info/top_level.txt,sha256=gCNhn57wzksDjSAISmgMJ0aiXzQulk0GJhb2-BAyYgw,7
|
| 8 |
+
psutil/__init__.py,sha256=6iEjSGFQplxGDfmFcEjjYtoFSfiSuiCW1VV-KBdUPOQ,89154
|
| 9 |
+
psutil/__pycache__/__init__.cpython-311.pyc,,
|
| 10 |
+
psutil/__pycache__/_common.cpython-311.pyc,,
|
| 11 |
+
psutil/__pycache__/_compat.cpython-311.pyc,,
|
| 12 |
+
psutil/__pycache__/_psaix.cpython-311.pyc,,
|
| 13 |
+
psutil/__pycache__/_psbsd.cpython-311.pyc,,
|
| 14 |
+
psutil/__pycache__/_pslinux.cpython-311.pyc,,
|
| 15 |
+
psutil/__pycache__/_psosx.cpython-311.pyc,,
|
| 16 |
+
psutil/__pycache__/_psposix.cpython-311.pyc,,
|
| 17 |
+
psutil/__pycache__/_pssunos.cpython-311.pyc,,
|
| 18 |
+
psutil/__pycache__/_pswindows.cpython-311.pyc,,
|
| 19 |
+
psutil/_common.py,sha256=VNozj9CPxOdvWL-ZQlFK11y45Vr-Pc6BmvfPIj8lpkI,29739
|
| 20 |
+
psutil/_compat.py,sha256=sZrxGYsKyvsPJBNeKhBsrmpH8M7B6DpS9Xowc8Mx3-I,15270
|
| 21 |
+
psutil/_psaix.py,sha256=auBiK5gCD4fOjqrjTwckg7wfOHw6vv3f0hIkGvNcBC4,18663
|
| 22 |
+
psutil/_psbsd.py,sha256=g9FLvFyX-ApMZrKn1EnYY1q4gz3K9EvEnR0vux_K0Ls,32205
|
| 23 |
+
psutil/_pslinux.py,sha256=b4g8D-e8s2g5yMuLwy16wpS-n1SVsZov_hULRnGKP3w,88798
|
| 24 |
+
psutil/_psosx.py,sha256=js281YWrza5x0_EeYhjLLypDqzmiehZASGpUkxNhKqw,16136
|
| 25 |
+
psutil/_psposix.py,sha256=X9rd7WHKQ6mUAn2ihb03MCnzrBtQsrPRkCouExmuagQ,8235
|
| 26 |
+
psutil/_pssunos.py,sha256=KO9YQena9rX7vOBoBq8lgFUzl7aqDdRhI5s6QHaJVV0,25479
|
| 27 |
+
psutil/_psutil_linux.abi3.so,sha256=LXawFmZUbygGCJ1vTvCPAF2wu0ENiTpU1GOnsPi3J-I,115336
|
| 28 |
+
psutil/_psutil_posix.abi3.so,sha256=bLoKDfoWp8Pmo_QGNDssv7kIOLk2hnF6HZHYlRWatOI,71640
|
| 29 |
+
psutil/_pswindows.py,sha256=SZdCQ22YYWs8CWatwcYQJ6gwFJzx4NGn9JlR9l3Z6m8,38098
|
| 30 |
+
psutil/tests/__init__.py,sha256=2M9hFltTN-sPzKCW9kiHqjlMZ9Wz8iP2Xujyobw56Ko,66696
|
| 31 |
+
psutil/tests/__main__.py,sha256=GYT-hlMnWDtybkJ76DqQcjXPr0jnLeZDTe0lVVeDb7o,309
|
| 32 |
+
psutil/tests/__pycache__/__init__.cpython-311.pyc,,
|
| 33 |
+
psutil/tests/__pycache__/__main__.cpython-311.pyc,,
|
| 34 |
+
psutil/tests/__pycache__/test_aix.cpython-311.pyc,,
|
| 35 |
+
psutil/tests/__pycache__/test_bsd.cpython-311.pyc,,
|
| 36 |
+
psutil/tests/__pycache__/test_connections.cpython-311.pyc,,
|
| 37 |
+
psutil/tests/__pycache__/test_contracts.cpython-311.pyc,,
|
| 38 |
+
psutil/tests/__pycache__/test_linux.cpython-311.pyc,,
|
| 39 |
+
psutil/tests/__pycache__/test_memleaks.cpython-311.pyc,,
|
| 40 |
+
psutil/tests/__pycache__/test_misc.cpython-311.pyc,,
|
| 41 |
+
psutil/tests/__pycache__/test_osx.cpython-311.pyc,,
|
| 42 |
+
psutil/tests/__pycache__/test_posix.cpython-311.pyc,,
|
| 43 |
+
psutil/tests/__pycache__/test_process.cpython-311.pyc,,
|
| 44 |
+
psutil/tests/__pycache__/test_process_all.cpython-311.pyc,,
|
| 45 |
+
psutil/tests/__pycache__/test_sunos.cpython-311.pyc,,
|
| 46 |
+
psutil/tests/__pycache__/test_system.cpython-311.pyc,,
|
| 47 |
+
psutil/tests/__pycache__/test_testutils.cpython-311.pyc,,
|
| 48 |
+
psutil/tests/__pycache__/test_unicode.cpython-311.pyc,,
|
| 49 |
+
psutil/tests/__pycache__/test_windows.cpython-311.pyc,,
|
| 50 |
+
psutil/tests/test_aix.py,sha256=m1r5OjB1BXohEI158lQtMfxjGLkiXsNjmRUCSV0IW-U,4418
|
| 51 |
+
psutil/tests/test_bsd.py,sha256=94Y34BUn0sBw-87NF0IIeU_NanOEwWDImRhkptQ4VYM,20222
|
| 52 |
+
psutil/tests/test_connections.py,sha256=t79nsafeHRdgfzMe8D_-YmKzeJ8KKL8jJ-3wY3jQPt4,21252
|
| 53 |
+
psutil/tests/test_contracts.py,sha256=FSFZpNeJumY9Lgerih3079pg4F80TwR7ftNS5nhFhqg,12577
|
| 54 |
+
psutil/tests/test_linux.py,sha256=MgdG_JG3ejEpla0pxqMXp8krMwqv_Lt8GYo7xmltYUM,91306
|
| 55 |
+
psutil/tests/test_memleaks.py,sha256=q6mV6Mtl_GH6HjQs33hrwaX78oSHZ9oojPyDTYLSBRQ,15411
|
| 56 |
+
psutil/tests/test_misc.py,sha256=2GXmf_dQzD3IoMVAjM41pLqKkQ1UJmxqONSIYepx6c0,35975
|
| 57 |
+
psutil/tests/test_osx.py,sha256=HeEXXZP3wsWdGZKu870nRtw5ndprn3zuqtvr-o9RpRg,6133
|
| 58 |
+
psutil/tests/test_posix.py,sha256=_IDaC_JaOq21HLbAoXvFVYCvgqT9xgXT1KxbxvITz-g,17384
|
| 59 |
+
psutil/tests/test_process.py,sha256=ABYJphyFJPNi7D6hpILfHbDACZTH4A2D0wX6l6FTf8c,63230
|
| 60 |
+
psutil/tests/test_process_all.py,sha256=xeLI-dTrgEVmP7PKd8yr67uWaMwQJaKNypx5yLRx_9g,18616
|
| 61 |
+
psutil/tests/test_sunos.py,sha256=tf9OOQyidTFA4WAp2eZoewvwXy95MmTD06JURgnH7ig,1192
|
| 62 |
+
psutil/tests/test_system.py,sha256=0QAYuOddXjSisvxnaqwII6ufqtoGmZ5QAg955fQhGzM,36372
|
| 63 |
+
psutil/tests/test_testutils.py,sha256=r8wyH_dII82NPE2k8Kp5k59tB7Of3a5j75wTF5b1dHY,18586
|
| 64 |
+
psutil/tests/test_unicode.py,sha256=U_CpmnYAVSoShiCsD1bnUfP9bAh9Nvhve1O7aU0aXqk,12772
|
| 65 |
+
psutil/tests/test_windows.py,sha256=LBjSNHGn5SyX-B3k5tLuC2hIfzXIP8bn6rT5QBwQDfE,34008
|
.venv/lib/python3.11/site-packages/ray/thirdparty_files/psutil-6.1.1.dist-info/REQUESTED
ADDED
|
File without changes
|
.venv/lib/python3.11/site-packages/ray/thirdparty_files/psutil-6.1.1.dist-info/WHEEL
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
Wheel-Version: 1.0
|
| 2 |
+
Generator: bdist_wheel (0.37.1)
|
| 3 |
+
Root-Is-Purelib: false
|
| 4 |
+
Tag: cp36-abi3-manylinux_2_12_x86_64
|
| 5 |
+
Tag: cp36-abi3-manylinux2010_x86_64
|
| 6 |
+
Tag: cp36-abi3-manylinux_2_17_x86_64
|
| 7 |
+
Tag: cp36-abi3-manylinux2014_x86_64
|
| 8 |
+
|
.venv/lib/python3.11/site-packages/ray/thirdparty_files/psutil-6.1.1.dist-info/top_level.txt
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
psutil
|
.venv/lib/python3.11/site-packages/ray/thirdparty_files/psutil/__init__.py
ADDED
|
@@ -0,0 +1,2486 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# -*- coding: utf-8 -*-
|
| 2 |
+
|
| 3 |
+
# Copyright (c) 2009, Giampaolo Rodola'. All rights reserved.
|
| 4 |
+
# Use of this source code is governed by a BSD-style license that can be
|
| 5 |
+
# found in the LICENSE file.
|
| 6 |
+
|
| 7 |
+
"""psutil is a cross-platform library for retrieving information on
|
| 8 |
+
running processes and system utilization (CPU, memory, disks, network,
|
| 9 |
+
sensors) in Python. Supported platforms:
|
| 10 |
+
|
| 11 |
+
- Linux
|
| 12 |
+
- Windows
|
| 13 |
+
- macOS
|
| 14 |
+
- FreeBSD
|
| 15 |
+
- OpenBSD
|
| 16 |
+
- NetBSD
|
| 17 |
+
- Sun Solaris
|
| 18 |
+
- AIX
|
| 19 |
+
|
| 20 |
+
Works with Python versions 2.7 and 3.6+.
|
| 21 |
+
"""
|
| 22 |
+
|
| 23 |
+
from __future__ import division
|
| 24 |
+
|
| 25 |
+
import collections
|
| 26 |
+
import contextlib
|
| 27 |
+
import datetime
|
| 28 |
+
import functools
|
| 29 |
+
import os
|
| 30 |
+
import signal
|
| 31 |
+
import subprocess
|
| 32 |
+
import sys
|
| 33 |
+
import threading
|
| 34 |
+
import time
|
| 35 |
+
|
| 36 |
+
|
| 37 |
+
try:
|
| 38 |
+
import pwd
|
| 39 |
+
except ImportError:
|
| 40 |
+
pwd = None
|
| 41 |
+
|
| 42 |
+
from . import _common
|
| 43 |
+
from ._common import AIX
|
| 44 |
+
from ._common import BSD
|
| 45 |
+
from ._common import CONN_CLOSE
|
| 46 |
+
from ._common import CONN_CLOSE_WAIT
|
| 47 |
+
from ._common import CONN_CLOSING
|
| 48 |
+
from ._common import CONN_ESTABLISHED
|
| 49 |
+
from ._common import CONN_FIN_WAIT1
|
| 50 |
+
from ._common import CONN_FIN_WAIT2
|
| 51 |
+
from ._common import CONN_LAST_ACK
|
| 52 |
+
from ._common import CONN_LISTEN
|
| 53 |
+
from ._common import CONN_NONE
|
| 54 |
+
from ._common import CONN_SYN_RECV
|
| 55 |
+
from ._common import CONN_SYN_SENT
|
| 56 |
+
from ._common import CONN_TIME_WAIT
|
| 57 |
+
from ._common import FREEBSD # NOQA
|
| 58 |
+
from ._common import LINUX
|
| 59 |
+
from ._common import MACOS
|
| 60 |
+
from ._common import NETBSD # NOQA
|
| 61 |
+
from ._common import NIC_DUPLEX_FULL
|
| 62 |
+
from ._common import NIC_DUPLEX_HALF
|
| 63 |
+
from ._common import NIC_DUPLEX_UNKNOWN
|
| 64 |
+
from ._common import OPENBSD # NOQA
|
| 65 |
+
from ._common import OSX # deprecated alias
|
| 66 |
+
from ._common import POSIX # NOQA
|
| 67 |
+
from ._common import POWER_TIME_UNKNOWN
|
| 68 |
+
from ._common import POWER_TIME_UNLIMITED
|
| 69 |
+
from ._common import STATUS_DEAD
|
| 70 |
+
from ._common import STATUS_DISK_SLEEP
|
| 71 |
+
from ._common import STATUS_IDLE
|
| 72 |
+
from ._common import STATUS_LOCKED
|
| 73 |
+
from ._common import STATUS_PARKED
|
| 74 |
+
from ._common import STATUS_RUNNING
|
| 75 |
+
from ._common import STATUS_SLEEPING
|
| 76 |
+
from ._common import STATUS_STOPPED
|
| 77 |
+
from ._common import STATUS_TRACING_STOP
|
| 78 |
+
from ._common import STATUS_WAITING
|
| 79 |
+
from ._common import STATUS_WAKING
|
| 80 |
+
from ._common import STATUS_ZOMBIE
|
| 81 |
+
from ._common import SUNOS
|
| 82 |
+
from ._common import WINDOWS
|
| 83 |
+
from ._common import AccessDenied
|
| 84 |
+
from ._common import Error
|
| 85 |
+
from ._common import NoSuchProcess
|
| 86 |
+
from ._common import TimeoutExpired
|
| 87 |
+
from ._common import ZombieProcess
|
| 88 |
+
from ._common import debug
|
| 89 |
+
from ._common import memoize_when_activated
|
| 90 |
+
from ._common import wrap_numbers as _wrap_numbers
|
| 91 |
+
from ._compat import PY3 as _PY3
|
| 92 |
+
from ._compat import PermissionError
|
| 93 |
+
from ._compat import ProcessLookupError
|
| 94 |
+
from ._compat import SubprocessTimeoutExpired as _SubprocessTimeoutExpired
|
| 95 |
+
from ._compat import long
|
| 96 |
+
|
| 97 |
+
|
| 98 |
+
if LINUX:
|
| 99 |
+
# This is public API and it will be retrieved from _pslinux.py
|
| 100 |
+
# via sys.modules.
|
| 101 |
+
PROCFS_PATH = "/proc"
|
| 102 |
+
|
| 103 |
+
from . import _pslinux as _psplatform
|
| 104 |
+
from ._pslinux import IOPRIO_CLASS_BE # NOQA
|
| 105 |
+
from ._pslinux import IOPRIO_CLASS_IDLE # NOQA
|
| 106 |
+
from ._pslinux import IOPRIO_CLASS_NONE # NOQA
|
| 107 |
+
from ._pslinux import IOPRIO_CLASS_RT # NOQA
|
| 108 |
+
|
| 109 |
+
elif WINDOWS:
|
| 110 |
+
from . import _pswindows as _psplatform
|
| 111 |
+
from ._psutil_windows import ABOVE_NORMAL_PRIORITY_CLASS # NOQA
|
| 112 |
+
from ._psutil_windows import BELOW_NORMAL_PRIORITY_CLASS # NOQA
|
| 113 |
+
from ._psutil_windows import HIGH_PRIORITY_CLASS # NOQA
|
| 114 |
+
from ._psutil_windows import IDLE_PRIORITY_CLASS # NOQA
|
| 115 |
+
from ._psutil_windows import NORMAL_PRIORITY_CLASS # NOQA
|
| 116 |
+
from ._psutil_windows import REALTIME_PRIORITY_CLASS # NOQA
|
| 117 |
+
from ._pswindows import CONN_DELETE_TCB # NOQA
|
| 118 |
+
from ._pswindows import IOPRIO_HIGH # NOQA
|
| 119 |
+
from ._pswindows import IOPRIO_LOW # NOQA
|
| 120 |
+
from ._pswindows import IOPRIO_NORMAL # NOQA
|
| 121 |
+
from ._pswindows import IOPRIO_VERYLOW # NOQA
|
| 122 |
+
|
| 123 |
+
elif MACOS:
|
| 124 |
+
from . import _psosx as _psplatform
|
| 125 |
+
|
| 126 |
+
elif BSD:
|
| 127 |
+
from . import _psbsd as _psplatform
|
| 128 |
+
|
| 129 |
+
elif SUNOS:
|
| 130 |
+
from . import _pssunos as _psplatform
|
| 131 |
+
from ._pssunos import CONN_BOUND # NOQA
|
| 132 |
+
from ._pssunos import CONN_IDLE # NOQA
|
| 133 |
+
|
| 134 |
+
# This is public writable API which is read from _pslinux.py and
|
| 135 |
+
# _pssunos.py via sys.modules.
|
| 136 |
+
PROCFS_PATH = "/proc"
|
| 137 |
+
|
| 138 |
+
elif AIX:
|
| 139 |
+
from . import _psaix as _psplatform
|
| 140 |
+
|
| 141 |
+
# This is public API and it will be retrieved from _pslinux.py
|
| 142 |
+
# via sys.modules.
|
| 143 |
+
PROCFS_PATH = "/proc"
|
| 144 |
+
|
| 145 |
+
else: # pragma: no cover
|
| 146 |
+
raise NotImplementedError('platform %s is not supported' % sys.platform)
|
| 147 |
+
|
| 148 |
+
|
| 149 |
+
# fmt: off
|
| 150 |
+
__all__ = [
|
| 151 |
+
# exceptions
|
| 152 |
+
"Error", "NoSuchProcess", "ZombieProcess", "AccessDenied",
|
| 153 |
+
"TimeoutExpired",
|
| 154 |
+
|
| 155 |
+
# constants
|
| 156 |
+
"version_info", "__version__",
|
| 157 |
+
|
| 158 |
+
"STATUS_RUNNING", "STATUS_IDLE", "STATUS_SLEEPING", "STATUS_DISK_SLEEP",
|
| 159 |
+
"STATUS_STOPPED", "STATUS_TRACING_STOP", "STATUS_ZOMBIE", "STATUS_DEAD",
|
| 160 |
+
"STATUS_WAKING", "STATUS_LOCKED", "STATUS_WAITING", "STATUS_LOCKED",
|
| 161 |
+
"STATUS_PARKED",
|
| 162 |
+
|
| 163 |
+
"CONN_ESTABLISHED", "CONN_SYN_SENT", "CONN_SYN_RECV", "CONN_FIN_WAIT1",
|
| 164 |
+
"CONN_FIN_WAIT2", "CONN_TIME_WAIT", "CONN_CLOSE", "CONN_CLOSE_WAIT",
|
| 165 |
+
"CONN_LAST_ACK", "CONN_LISTEN", "CONN_CLOSING", "CONN_NONE",
|
| 166 |
+
# "CONN_IDLE", "CONN_BOUND",
|
| 167 |
+
|
| 168 |
+
"AF_LINK",
|
| 169 |
+
|
| 170 |
+
"NIC_DUPLEX_FULL", "NIC_DUPLEX_HALF", "NIC_DUPLEX_UNKNOWN",
|
| 171 |
+
|
| 172 |
+
"POWER_TIME_UNKNOWN", "POWER_TIME_UNLIMITED",
|
| 173 |
+
|
| 174 |
+
"BSD", "FREEBSD", "LINUX", "NETBSD", "OPENBSD", "MACOS", "OSX", "POSIX",
|
| 175 |
+
"SUNOS", "WINDOWS", "AIX",
|
| 176 |
+
|
| 177 |
+
# "RLIM_INFINITY", "RLIMIT_AS", "RLIMIT_CORE", "RLIMIT_CPU", "RLIMIT_DATA",
|
| 178 |
+
# "RLIMIT_FSIZE", "RLIMIT_LOCKS", "RLIMIT_MEMLOCK", "RLIMIT_NOFILE",
|
| 179 |
+
# "RLIMIT_NPROC", "RLIMIT_RSS", "RLIMIT_STACK", "RLIMIT_MSGQUEUE",
|
| 180 |
+
# "RLIMIT_NICE", "RLIMIT_RTPRIO", "RLIMIT_RTTIME", "RLIMIT_SIGPENDING",
|
| 181 |
+
|
| 182 |
+
# classes
|
| 183 |
+
"Process", "Popen",
|
| 184 |
+
|
| 185 |
+
# functions
|
| 186 |
+
"pid_exists", "pids", "process_iter", "wait_procs", # proc
|
| 187 |
+
"virtual_memory", "swap_memory", # memory
|
| 188 |
+
"cpu_times", "cpu_percent", "cpu_times_percent", "cpu_count", # cpu
|
| 189 |
+
"cpu_stats", # "cpu_freq", "getloadavg"
|
| 190 |
+
"net_io_counters", "net_connections", "net_if_addrs", # network
|
| 191 |
+
"net_if_stats",
|
| 192 |
+
"disk_io_counters", "disk_partitions", "disk_usage", # disk
|
| 193 |
+
# "sensors_temperatures", "sensors_battery", "sensors_fans" # sensors
|
| 194 |
+
"users", "boot_time", # others
|
| 195 |
+
]
|
| 196 |
+
# fmt: on
|
| 197 |
+
|
| 198 |
+
|
| 199 |
+
__all__.extend(_psplatform.__extra__all__)
|
| 200 |
+
|
| 201 |
+
# Linux, FreeBSD
|
| 202 |
+
if hasattr(_psplatform.Process, "rlimit"):
|
| 203 |
+
# Populate global namespace with RLIM* constants.
|
| 204 |
+
from . import _psutil_posix
|
| 205 |
+
|
| 206 |
+
_globals = globals()
|
| 207 |
+
_name = None
|
| 208 |
+
for _name in dir(_psutil_posix):
|
| 209 |
+
if _name.startswith('RLIM') and _name.isupper():
|
| 210 |
+
_globals[_name] = getattr(_psutil_posix, _name)
|
| 211 |
+
__all__.append(_name)
|
| 212 |
+
del _globals, _name
|
| 213 |
+
|
| 214 |
+
AF_LINK = _psplatform.AF_LINK
|
| 215 |
+
|
| 216 |
+
__author__ = "Giampaolo Rodola'"
|
| 217 |
+
__version__ = "6.1.1"
|
| 218 |
+
version_info = tuple([int(num) for num in __version__.split('.')])
|
| 219 |
+
|
| 220 |
+
_timer = getattr(time, 'monotonic', time.time)
|
| 221 |
+
_TOTAL_PHYMEM = None
|
| 222 |
+
_LOWEST_PID = None
|
| 223 |
+
_SENTINEL = object()
|
| 224 |
+
|
| 225 |
+
# Sanity check in case the user messed up with psutil installation
|
| 226 |
+
# or did something weird with sys.path. In this case we might end
|
| 227 |
+
# up importing a python module using a C extension module which
|
| 228 |
+
# was compiled for a different version of psutil.
|
| 229 |
+
# We want to prevent that by failing sooner rather than later.
|
| 230 |
+
# See: https://github.com/giampaolo/psutil/issues/564
|
| 231 |
+
if int(__version__.replace('.', '')) != getattr(
|
| 232 |
+
_psplatform.cext, 'version', None
|
| 233 |
+
):
|
| 234 |
+
msg = "version conflict: %r C extension " % _psplatform.cext.__file__
|
| 235 |
+
msg += "module was built for another version of psutil"
|
| 236 |
+
if hasattr(_psplatform.cext, 'version'):
|
| 237 |
+
msg += " (%s instead of %s)" % (
|
| 238 |
+
'.'.join([x for x in str(_psplatform.cext.version)]),
|
| 239 |
+
__version__,
|
| 240 |
+
)
|
| 241 |
+
else:
|
| 242 |
+
msg += " (different than %s)" % __version__
|
| 243 |
+
msg += "; you may try to 'pip uninstall psutil', manually remove %s" % (
|
| 244 |
+
getattr(
|
| 245 |
+
_psplatform.cext,
|
| 246 |
+
"__file__",
|
| 247 |
+
"the existing psutil install directory",
|
| 248 |
+
)
|
| 249 |
+
)
|
| 250 |
+
msg += " or clean the virtual env somehow, then reinstall"
|
| 251 |
+
raise ImportError(msg)
|
| 252 |
+
|
| 253 |
+
|
| 254 |
+
# =====================================================================
|
| 255 |
+
# --- Utils
|
| 256 |
+
# =====================================================================
|
| 257 |
+
|
| 258 |
+
|
| 259 |
+
if hasattr(_psplatform, 'ppid_map'):
|
| 260 |
+
# Faster version (Windows and Linux).
|
| 261 |
+
_ppid_map = _psplatform.ppid_map
|
| 262 |
+
else: # pragma: no cover
|
| 263 |
+
|
| 264 |
+
def _ppid_map():
|
| 265 |
+
"""Return a {pid: ppid, ...} dict for all running processes in
|
| 266 |
+
one shot. Used to speed up Process.children().
|
| 267 |
+
"""
|
| 268 |
+
ret = {}
|
| 269 |
+
for pid in pids():
|
| 270 |
+
try:
|
| 271 |
+
ret[pid] = _psplatform.Process(pid).ppid()
|
| 272 |
+
except (NoSuchProcess, ZombieProcess):
|
| 273 |
+
pass
|
| 274 |
+
return ret
|
| 275 |
+
|
| 276 |
+
|
| 277 |
+
def _pprint_secs(secs):
|
| 278 |
+
"""Format seconds in a human readable form."""
|
| 279 |
+
now = time.time()
|
| 280 |
+
secs_ago = int(now - secs)
|
| 281 |
+
fmt = "%H:%M:%S" if secs_ago < 60 * 60 * 24 else "%Y-%m-%d %H:%M:%S"
|
| 282 |
+
return datetime.datetime.fromtimestamp(secs).strftime(fmt)
|
| 283 |
+
|
| 284 |
+
|
| 285 |
+
# =====================================================================
|
| 286 |
+
# --- Process class
|
| 287 |
+
# =====================================================================
|
| 288 |
+
|
| 289 |
+
|
| 290 |
+
class Process(object): # noqa: UP004
|
| 291 |
+
"""Represents an OS process with the given PID.
|
| 292 |
+
If PID is omitted current process PID (os.getpid()) is used.
|
| 293 |
+
Raise NoSuchProcess if PID does not exist.
|
| 294 |
+
|
| 295 |
+
Note that most of the methods of this class do not make sure that
|
| 296 |
+
the PID of the process being queried has been reused. That means
|
| 297 |
+
that you may end up retrieving information for another process.
|
| 298 |
+
|
| 299 |
+
The only exceptions for which process identity is pre-emptively
|
| 300 |
+
checked and guaranteed are:
|
| 301 |
+
|
| 302 |
+
- parent()
|
| 303 |
+
- children()
|
| 304 |
+
- nice() (set)
|
| 305 |
+
- ionice() (set)
|
| 306 |
+
- rlimit() (set)
|
| 307 |
+
- cpu_affinity (set)
|
| 308 |
+
- suspend()
|
| 309 |
+
- resume()
|
| 310 |
+
- send_signal()
|
| 311 |
+
- terminate()
|
| 312 |
+
- kill()
|
| 313 |
+
|
| 314 |
+
To prevent this problem for all other methods you can use
|
| 315 |
+
is_running() before querying the process.
|
| 316 |
+
"""
|
| 317 |
+
|
| 318 |
+
def __init__(self, pid=None):
|
| 319 |
+
self._init(pid)
|
| 320 |
+
|
| 321 |
+
def _init(self, pid, _ignore_nsp=False):
|
| 322 |
+
if pid is None:
|
| 323 |
+
pid = os.getpid()
|
| 324 |
+
else:
|
| 325 |
+
if not _PY3 and not isinstance(pid, (int, long)):
|
| 326 |
+
msg = "pid must be an integer (got %r)" % pid
|
| 327 |
+
raise TypeError(msg)
|
| 328 |
+
if pid < 0:
|
| 329 |
+
msg = "pid must be a positive integer (got %s)" % pid
|
| 330 |
+
raise ValueError(msg)
|
| 331 |
+
try:
|
| 332 |
+
_psplatform.cext.check_pid_range(pid)
|
| 333 |
+
except OverflowError:
|
| 334 |
+
msg = "process PID out of range (got %s)" % pid
|
| 335 |
+
raise NoSuchProcess(pid, msg=msg)
|
| 336 |
+
|
| 337 |
+
self._pid = pid
|
| 338 |
+
self._name = None
|
| 339 |
+
self._exe = None
|
| 340 |
+
self._create_time = None
|
| 341 |
+
self._gone = False
|
| 342 |
+
self._pid_reused = False
|
| 343 |
+
self._hash = None
|
| 344 |
+
self._lock = threading.RLock()
|
| 345 |
+
# used for caching on Windows only (on POSIX ppid may change)
|
| 346 |
+
self._ppid = None
|
| 347 |
+
# platform-specific modules define an _psplatform.Process
|
| 348 |
+
# implementation class
|
| 349 |
+
self._proc = _psplatform.Process(pid)
|
| 350 |
+
self._last_sys_cpu_times = None
|
| 351 |
+
self._last_proc_cpu_times = None
|
| 352 |
+
self._exitcode = _SENTINEL
|
| 353 |
+
self._ident = (self.pid, None)
|
| 354 |
+
try:
|
| 355 |
+
self._ident = self._get_ident()
|
| 356 |
+
except AccessDenied:
|
| 357 |
+
# This should happen on Windows only, since we use the fast
|
| 358 |
+
# create time method. AFAIK, on all other platforms we are
|
| 359 |
+
# able to get create time for all PIDs.
|
| 360 |
+
pass
|
| 361 |
+
except ZombieProcess:
|
| 362 |
+
# Zombies can still be queried by this class (although
|
| 363 |
+
# not always) and pids() return them so just go on.
|
| 364 |
+
pass
|
| 365 |
+
except NoSuchProcess:
|
| 366 |
+
if not _ignore_nsp:
|
| 367 |
+
msg = "process PID not found"
|
| 368 |
+
raise NoSuchProcess(pid, msg=msg)
|
| 369 |
+
else:
|
| 370 |
+
self._gone = True
|
| 371 |
+
|
| 372 |
+
def _get_ident(self):
|
| 373 |
+
"""Return a (pid, uid) tuple which is supposed to identify a
|
| 374 |
+
Process instance univocally over time. The PID alone is not
|
| 375 |
+
enough, as it can be assigned to a new process after this one
|
| 376 |
+
terminates, so we add process creation time to the mix. We need
|
| 377 |
+
this in order to prevent killing the wrong process later on.
|
| 378 |
+
This is also known as PID reuse or PID recycling problem.
|
| 379 |
+
|
| 380 |
+
The reliability of this strategy mostly depends on
|
| 381 |
+
create_time() precision, which is 0.01 secs on Linux. The
|
| 382 |
+
assumption is that, after a process terminates, the kernel
|
| 383 |
+
won't reuse the same PID after such a short period of time
|
| 384 |
+
(0.01 secs). Technically this is inherently racy, but
|
| 385 |
+
practically it should be good enough.
|
| 386 |
+
"""
|
| 387 |
+
if WINDOWS:
|
| 388 |
+
# Use create_time() fast method in order to speedup
|
| 389 |
+
# `process_iter()`. This means we'll get AccessDenied for
|
| 390 |
+
# most ADMIN processes, but that's fine since it means
|
| 391 |
+
# we'll also get AccessDenied on kill().
|
| 392 |
+
# https://github.com/giampaolo/psutil/issues/2366#issuecomment-2381646555
|
| 393 |
+
self._create_time = self._proc.create_time(fast_only=True)
|
| 394 |
+
return (self.pid, self._create_time)
|
| 395 |
+
else:
|
| 396 |
+
return (self.pid, self.create_time())
|
| 397 |
+
|
| 398 |
+
def __str__(self):
|
| 399 |
+
info = collections.OrderedDict()
|
| 400 |
+
info["pid"] = self.pid
|
| 401 |
+
if self._name:
|
| 402 |
+
info['name'] = self._name
|
| 403 |
+
with self.oneshot():
|
| 404 |
+
if self._pid_reused:
|
| 405 |
+
info["status"] = "terminated + PID reused"
|
| 406 |
+
else:
|
| 407 |
+
try:
|
| 408 |
+
info["name"] = self.name()
|
| 409 |
+
info["status"] = self.status()
|
| 410 |
+
except ZombieProcess:
|
| 411 |
+
info["status"] = "zombie"
|
| 412 |
+
except NoSuchProcess:
|
| 413 |
+
info["status"] = "terminated"
|
| 414 |
+
except AccessDenied:
|
| 415 |
+
pass
|
| 416 |
+
|
| 417 |
+
if self._exitcode not in {_SENTINEL, None}:
|
| 418 |
+
info["exitcode"] = self._exitcode
|
| 419 |
+
if self._create_time is not None:
|
| 420 |
+
info['started'] = _pprint_secs(self._create_time)
|
| 421 |
+
|
| 422 |
+
return "%s.%s(%s)" % (
|
| 423 |
+
self.__class__.__module__,
|
| 424 |
+
self.__class__.__name__,
|
| 425 |
+
", ".join(["%s=%r" % (k, v) for k, v in info.items()]),
|
| 426 |
+
)
|
| 427 |
+
|
| 428 |
+
__repr__ = __str__
|
| 429 |
+
|
| 430 |
+
def __eq__(self, other):
|
| 431 |
+
# Test for equality with another Process object based
|
| 432 |
+
# on PID and creation time.
|
| 433 |
+
if not isinstance(other, Process):
|
| 434 |
+
return NotImplemented
|
| 435 |
+
if OPENBSD or NETBSD: # pragma: no cover
|
| 436 |
+
# Zombie processes on Open/NetBSD have a creation time of
|
| 437 |
+
# 0.0. This covers the case when a process started normally
|
| 438 |
+
# (so it has a ctime), then it turned into a zombie. It's
|
| 439 |
+
# important to do this because is_running() depends on
|
| 440 |
+
# __eq__.
|
| 441 |
+
pid1, ident1 = self._ident
|
| 442 |
+
pid2, ident2 = other._ident
|
| 443 |
+
if pid1 == pid2:
|
| 444 |
+
if ident1 and not ident2:
|
| 445 |
+
try:
|
| 446 |
+
return self.status() == STATUS_ZOMBIE
|
| 447 |
+
except Error:
|
| 448 |
+
pass
|
| 449 |
+
return self._ident == other._ident
|
| 450 |
+
|
| 451 |
+
def __ne__(self, other):
|
| 452 |
+
return not self == other
|
| 453 |
+
|
| 454 |
+
def __hash__(self):
|
| 455 |
+
if self._hash is None:
|
| 456 |
+
self._hash = hash(self._ident)
|
| 457 |
+
return self._hash
|
| 458 |
+
|
| 459 |
+
def _raise_if_pid_reused(self):
|
| 460 |
+
"""Raises NoSuchProcess in case process PID has been reused."""
|
| 461 |
+
if self._pid_reused or (not self.is_running() and self._pid_reused):
|
| 462 |
+
# We may directly raise NSP in here already if PID is just
|
| 463 |
+
# not running, but I prefer NSP to be raised naturally by
|
| 464 |
+
# the actual Process API call. This way unit tests will tell
|
| 465 |
+
# us if the API is broken (aka don't raise NSP when it
|
| 466 |
+
# should). We also remain consistent with all other "get"
|
| 467 |
+
# APIs which don't use _raise_if_pid_reused().
|
| 468 |
+
msg = "process no longer exists and its PID has been reused"
|
| 469 |
+
raise NoSuchProcess(self.pid, self._name, msg=msg)
|
| 470 |
+
|
| 471 |
+
@property
|
| 472 |
+
def pid(self):
|
| 473 |
+
"""The process PID."""
|
| 474 |
+
return self._pid
|
| 475 |
+
|
| 476 |
+
# --- utility methods
|
| 477 |
+
|
| 478 |
+
@contextlib.contextmanager
|
| 479 |
+
def oneshot(self):
|
| 480 |
+
"""Utility context manager which considerably speeds up the
|
| 481 |
+
retrieval of multiple process information at the same time.
|
| 482 |
+
|
| 483 |
+
Internally different process info (e.g. name, ppid, uids,
|
| 484 |
+
gids, ...) may be fetched by using the same routine, but
|
| 485 |
+
only one information is returned and the others are discarded.
|
| 486 |
+
When using this context manager the internal routine is
|
| 487 |
+
executed once (in the example below on name()) and the
|
| 488 |
+
other info are cached.
|
| 489 |
+
|
| 490 |
+
The cache is cleared when exiting the context manager block.
|
| 491 |
+
The advice is to use this every time you retrieve more than
|
| 492 |
+
one information about the process. If you're lucky, you'll
|
| 493 |
+
get a hell of a speedup.
|
| 494 |
+
|
| 495 |
+
>>> import psutil
|
| 496 |
+
>>> p = psutil.Process()
|
| 497 |
+
>>> with p.oneshot():
|
| 498 |
+
... p.name() # collect multiple info
|
| 499 |
+
... p.cpu_times() # return cached value
|
| 500 |
+
... p.cpu_percent() # return cached value
|
| 501 |
+
... p.create_time() # return cached value
|
| 502 |
+
...
|
| 503 |
+
>>>
|
| 504 |
+
"""
|
| 505 |
+
with self._lock:
|
| 506 |
+
if hasattr(self, "_cache"):
|
| 507 |
+
# NOOP: this covers the use case where the user enters the
|
| 508 |
+
# context twice:
|
| 509 |
+
#
|
| 510 |
+
# >>> with p.oneshot():
|
| 511 |
+
# ... with p.oneshot():
|
| 512 |
+
# ...
|
| 513 |
+
#
|
| 514 |
+
# Also, since as_dict() internally uses oneshot()
|
| 515 |
+
# I expect that the code below will be a pretty common
|
| 516 |
+
# "mistake" that the user will make, so let's guard
|
| 517 |
+
# against that:
|
| 518 |
+
#
|
| 519 |
+
# >>> with p.oneshot():
|
| 520 |
+
# ... p.as_dict()
|
| 521 |
+
# ...
|
| 522 |
+
yield
|
| 523 |
+
else:
|
| 524 |
+
try:
|
| 525 |
+
# cached in case cpu_percent() is used
|
| 526 |
+
self.cpu_times.cache_activate(self)
|
| 527 |
+
# cached in case memory_percent() is used
|
| 528 |
+
self.memory_info.cache_activate(self)
|
| 529 |
+
# cached in case parent() is used
|
| 530 |
+
self.ppid.cache_activate(self)
|
| 531 |
+
# cached in case username() is used
|
| 532 |
+
if POSIX:
|
| 533 |
+
self.uids.cache_activate(self)
|
| 534 |
+
# specific implementation cache
|
| 535 |
+
self._proc.oneshot_enter()
|
| 536 |
+
yield
|
| 537 |
+
finally:
|
| 538 |
+
self.cpu_times.cache_deactivate(self)
|
| 539 |
+
self.memory_info.cache_deactivate(self)
|
| 540 |
+
self.ppid.cache_deactivate(self)
|
| 541 |
+
if POSIX:
|
| 542 |
+
self.uids.cache_deactivate(self)
|
| 543 |
+
self._proc.oneshot_exit()
|
| 544 |
+
|
| 545 |
+
def as_dict(self, attrs=None, ad_value=None):
|
| 546 |
+
"""Utility method returning process information as a
|
| 547 |
+
hashable dictionary.
|
| 548 |
+
If *attrs* is specified it must be a list of strings
|
| 549 |
+
reflecting available Process class' attribute names
|
| 550 |
+
(e.g. ['cpu_times', 'name']) else all public (read
|
| 551 |
+
only) attributes are assumed.
|
| 552 |
+
*ad_value* is the value which gets assigned in case
|
| 553 |
+
AccessDenied or ZombieProcess exception is raised when
|
| 554 |
+
retrieving that particular process information.
|
| 555 |
+
"""
|
| 556 |
+
valid_names = _as_dict_attrnames
|
| 557 |
+
if attrs is not None:
|
| 558 |
+
if not isinstance(attrs, (list, tuple, set, frozenset)):
|
| 559 |
+
msg = "invalid attrs type %s" % type(attrs)
|
| 560 |
+
raise TypeError(msg)
|
| 561 |
+
attrs = set(attrs)
|
| 562 |
+
invalid_names = attrs - valid_names
|
| 563 |
+
if invalid_names:
|
| 564 |
+
msg = "invalid attr name%s %s" % (
|
| 565 |
+
"s" if len(invalid_names) > 1 else "",
|
| 566 |
+
", ".join(map(repr, invalid_names)),
|
| 567 |
+
)
|
| 568 |
+
raise ValueError(msg)
|
| 569 |
+
|
| 570 |
+
retdict = {}
|
| 571 |
+
ls = attrs or valid_names
|
| 572 |
+
with self.oneshot():
|
| 573 |
+
for name in ls:
|
| 574 |
+
try:
|
| 575 |
+
if name == 'pid':
|
| 576 |
+
ret = self.pid
|
| 577 |
+
else:
|
| 578 |
+
meth = getattr(self, name)
|
| 579 |
+
ret = meth()
|
| 580 |
+
except (AccessDenied, ZombieProcess):
|
| 581 |
+
ret = ad_value
|
| 582 |
+
except NotImplementedError:
|
| 583 |
+
# in case of not implemented functionality (may happen
|
| 584 |
+
# on old or exotic systems) we want to crash only if
|
| 585 |
+
# the user explicitly asked for that particular attr
|
| 586 |
+
if attrs:
|
| 587 |
+
raise
|
| 588 |
+
continue
|
| 589 |
+
retdict[name] = ret
|
| 590 |
+
return retdict
|
| 591 |
+
|
| 592 |
+
def parent(self):
|
| 593 |
+
"""Return the parent process as a Process object pre-emptively
|
| 594 |
+
checking whether PID has been reused.
|
| 595 |
+
If no parent is known return None.
|
| 596 |
+
"""
|
| 597 |
+
lowest_pid = _LOWEST_PID if _LOWEST_PID is not None else pids()[0]
|
| 598 |
+
if self.pid == lowest_pid:
|
| 599 |
+
return None
|
| 600 |
+
ppid = self.ppid()
|
| 601 |
+
if ppid is not None:
|
| 602 |
+
ctime = self.create_time()
|
| 603 |
+
try:
|
| 604 |
+
parent = Process(ppid)
|
| 605 |
+
if parent.create_time() <= ctime:
|
| 606 |
+
return parent
|
| 607 |
+
# ...else ppid has been reused by another process
|
| 608 |
+
except NoSuchProcess:
|
| 609 |
+
pass
|
| 610 |
+
|
| 611 |
+
def parents(self):
|
| 612 |
+
"""Return the parents of this process as a list of Process
|
| 613 |
+
instances. If no parents are known return an empty list.
|
| 614 |
+
"""
|
| 615 |
+
parents = []
|
| 616 |
+
proc = self.parent()
|
| 617 |
+
while proc is not None:
|
| 618 |
+
parents.append(proc)
|
| 619 |
+
proc = proc.parent()
|
| 620 |
+
return parents
|
| 621 |
+
|
| 622 |
+
def is_running(self):
|
| 623 |
+
"""Return whether this process is running.
|
| 624 |
+
|
| 625 |
+
It also checks if PID has been reused by another process, in
|
| 626 |
+
which case it will remove the process from `process_iter()`
|
| 627 |
+
internal cache and return False.
|
| 628 |
+
"""
|
| 629 |
+
if self._gone or self._pid_reused:
|
| 630 |
+
return False
|
| 631 |
+
try:
|
| 632 |
+
# Checking if PID is alive is not enough as the PID might
|
| 633 |
+
# have been reused by another process. Process identity /
|
| 634 |
+
# uniqueness over time is guaranteed by (PID + creation
|
| 635 |
+
# time) and that is verified in __eq__.
|
| 636 |
+
self._pid_reused = self != Process(self.pid)
|
| 637 |
+
if self._pid_reused:
|
| 638 |
+
_pids_reused.add(self.pid)
|
| 639 |
+
raise NoSuchProcess(self.pid)
|
| 640 |
+
return True
|
| 641 |
+
except ZombieProcess:
|
| 642 |
+
# We should never get here as it's already handled in
|
| 643 |
+
# Process.__init__; here just for extra safety.
|
| 644 |
+
return True
|
| 645 |
+
except NoSuchProcess:
|
| 646 |
+
self._gone = True
|
| 647 |
+
return False
|
| 648 |
+
|
| 649 |
+
# --- actual API
|
| 650 |
+
|
| 651 |
+
@memoize_when_activated
|
| 652 |
+
def ppid(self):
|
| 653 |
+
"""The process parent PID.
|
| 654 |
+
On Windows the return value is cached after first call.
|
| 655 |
+
"""
|
| 656 |
+
# On POSIX we don't want to cache the ppid as it may unexpectedly
|
| 657 |
+
# change to 1 (init) in case this process turns into a zombie:
|
| 658 |
+
# https://github.com/giampaolo/psutil/issues/321
|
| 659 |
+
# http://stackoverflow.com/questions/356722/
|
| 660 |
+
|
| 661 |
+
# XXX should we check creation time here rather than in
|
| 662 |
+
# Process.parent()?
|
| 663 |
+
self._raise_if_pid_reused()
|
| 664 |
+
if POSIX:
|
| 665 |
+
return self._proc.ppid()
|
| 666 |
+
else: # pragma: no cover
|
| 667 |
+
self._ppid = self._ppid or self._proc.ppid()
|
| 668 |
+
return self._ppid
|
| 669 |
+
|
| 670 |
+
def name(self):
|
| 671 |
+
"""The process name. The return value is cached after first call."""
|
| 672 |
+
# Process name is only cached on Windows as on POSIX it may
|
| 673 |
+
# change, see:
|
| 674 |
+
# https://github.com/giampaolo/psutil/issues/692
|
| 675 |
+
if WINDOWS and self._name is not None:
|
| 676 |
+
return self._name
|
| 677 |
+
name = self._proc.name()
|
| 678 |
+
if POSIX and len(name) >= 15:
|
| 679 |
+
# On UNIX the name gets truncated to the first 15 characters.
|
| 680 |
+
# If it matches the first part of the cmdline we return that
|
| 681 |
+
# one instead because it's usually more explicative.
|
| 682 |
+
# Examples are "gnome-keyring-d" vs. "gnome-keyring-daemon".
|
| 683 |
+
try:
|
| 684 |
+
cmdline = self.cmdline()
|
| 685 |
+
except (AccessDenied, ZombieProcess):
|
| 686 |
+
# Just pass and return the truncated name: it's better
|
| 687 |
+
# than nothing. Note: there are actual cases where a
|
| 688 |
+
# zombie process can return a name() but not a
|
| 689 |
+
# cmdline(), see:
|
| 690 |
+
# https://github.com/giampaolo/psutil/issues/2239
|
| 691 |
+
pass
|
| 692 |
+
else:
|
| 693 |
+
if cmdline:
|
| 694 |
+
extended_name = os.path.basename(cmdline[0])
|
| 695 |
+
if extended_name.startswith(name):
|
| 696 |
+
name = extended_name
|
| 697 |
+
self._name = name
|
| 698 |
+
self._proc._name = name
|
| 699 |
+
return name
|
| 700 |
+
|
| 701 |
+
def exe(self):
|
| 702 |
+
"""The process executable as an absolute path.
|
| 703 |
+
May also be an empty string.
|
| 704 |
+
The return value is cached after first call.
|
| 705 |
+
"""
|
| 706 |
+
|
| 707 |
+
def guess_it(fallback):
|
| 708 |
+
# try to guess exe from cmdline[0] in absence of a native
|
| 709 |
+
# exe representation
|
| 710 |
+
cmdline = self.cmdline()
|
| 711 |
+
if cmdline and hasattr(os, 'access') and hasattr(os, 'X_OK'):
|
| 712 |
+
exe = cmdline[0] # the possible exe
|
| 713 |
+
# Attempt to guess only in case of an absolute path.
|
| 714 |
+
# It is not safe otherwise as the process might have
|
| 715 |
+
# changed cwd.
|
| 716 |
+
if (
|
| 717 |
+
os.path.isabs(exe)
|
| 718 |
+
and os.path.isfile(exe)
|
| 719 |
+
and os.access(exe, os.X_OK)
|
| 720 |
+
):
|
| 721 |
+
return exe
|
| 722 |
+
if isinstance(fallback, AccessDenied):
|
| 723 |
+
raise fallback
|
| 724 |
+
return fallback
|
| 725 |
+
|
| 726 |
+
if self._exe is None:
|
| 727 |
+
try:
|
| 728 |
+
exe = self._proc.exe()
|
| 729 |
+
except AccessDenied as err:
|
| 730 |
+
return guess_it(fallback=err)
|
| 731 |
+
else:
|
| 732 |
+
if not exe:
|
| 733 |
+
# underlying implementation can legitimately return an
|
| 734 |
+
# empty string; if that's the case we don't want to
|
| 735 |
+
# raise AD while guessing from the cmdline
|
| 736 |
+
try:
|
| 737 |
+
exe = guess_it(fallback=exe)
|
| 738 |
+
except AccessDenied:
|
| 739 |
+
pass
|
| 740 |
+
self._exe = exe
|
| 741 |
+
return self._exe
|
| 742 |
+
|
| 743 |
+
def cmdline(self):
|
| 744 |
+
"""The command line this process has been called with."""
|
| 745 |
+
return self._proc.cmdline()
|
| 746 |
+
|
| 747 |
+
def status(self):
|
| 748 |
+
"""The process current status as a STATUS_* constant."""
|
| 749 |
+
try:
|
| 750 |
+
return self._proc.status()
|
| 751 |
+
except ZombieProcess:
|
| 752 |
+
return STATUS_ZOMBIE
|
| 753 |
+
|
| 754 |
+
def username(self):
|
| 755 |
+
"""The name of the user that owns the process.
|
| 756 |
+
On UNIX this is calculated by using *real* process uid.
|
| 757 |
+
"""
|
| 758 |
+
if POSIX:
|
| 759 |
+
if pwd is None:
|
| 760 |
+
# might happen if python was installed from sources
|
| 761 |
+
msg = "requires pwd module shipped with standard python"
|
| 762 |
+
raise ImportError(msg)
|
| 763 |
+
real_uid = self.uids().real
|
| 764 |
+
try:
|
| 765 |
+
return pwd.getpwuid(real_uid).pw_name
|
| 766 |
+
except KeyError:
|
| 767 |
+
# the uid can't be resolved by the system
|
| 768 |
+
return str(real_uid)
|
| 769 |
+
else:
|
| 770 |
+
return self._proc.username()
|
| 771 |
+
|
| 772 |
+
def create_time(self):
|
| 773 |
+
"""The process creation time as a floating point number
|
| 774 |
+
expressed in seconds since the epoch.
|
| 775 |
+
The return value is cached after first call.
|
| 776 |
+
"""
|
| 777 |
+
if self._create_time is None:
|
| 778 |
+
self._create_time = self._proc.create_time()
|
| 779 |
+
return self._create_time
|
| 780 |
+
|
| 781 |
+
def cwd(self):
|
| 782 |
+
"""Process current working directory as an absolute path."""
|
| 783 |
+
return self._proc.cwd()
|
| 784 |
+
|
| 785 |
+
def nice(self, value=None):
|
| 786 |
+
"""Get or set process niceness (priority)."""
|
| 787 |
+
if value is None:
|
| 788 |
+
return self._proc.nice_get()
|
| 789 |
+
else:
|
| 790 |
+
self._raise_if_pid_reused()
|
| 791 |
+
self._proc.nice_set(value)
|
| 792 |
+
|
| 793 |
+
if POSIX:
|
| 794 |
+
|
| 795 |
+
@memoize_when_activated
|
| 796 |
+
def uids(self):
|
| 797 |
+
"""Return process UIDs as a (real, effective, saved)
|
| 798 |
+
namedtuple.
|
| 799 |
+
"""
|
| 800 |
+
return self._proc.uids()
|
| 801 |
+
|
| 802 |
+
def gids(self):
|
| 803 |
+
"""Return process GIDs as a (real, effective, saved)
|
| 804 |
+
namedtuple.
|
| 805 |
+
"""
|
| 806 |
+
return self._proc.gids()
|
| 807 |
+
|
| 808 |
+
def terminal(self):
|
| 809 |
+
"""The terminal associated with this process, if any,
|
| 810 |
+
else None.
|
| 811 |
+
"""
|
| 812 |
+
return self._proc.terminal()
|
| 813 |
+
|
| 814 |
+
def num_fds(self):
|
| 815 |
+
"""Return the number of file descriptors opened by this
|
| 816 |
+
process (POSIX only).
|
| 817 |
+
"""
|
| 818 |
+
return self._proc.num_fds()
|
| 819 |
+
|
| 820 |
+
# Linux, BSD, AIX and Windows only
|
| 821 |
+
if hasattr(_psplatform.Process, "io_counters"):
|
| 822 |
+
|
| 823 |
+
def io_counters(self):
|
| 824 |
+
"""Return process I/O statistics as a
|
| 825 |
+
(read_count, write_count, read_bytes, write_bytes)
|
| 826 |
+
namedtuple.
|
| 827 |
+
Those are the number of read/write calls performed and the
|
| 828 |
+
amount of bytes read and written by the process.
|
| 829 |
+
"""
|
| 830 |
+
return self._proc.io_counters()
|
| 831 |
+
|
| 832 |
+
# Linux and Windows
|
| 833 |
+
if hasattr(_psplatform.Process, "ionice_get"):
|
| 834 |
+
|
| 835 |
+
def ionice(self, ioclass=None, value=None):
|
| 836 |
+
"""Get or set process I/O niceness (priority).
|
| 837 |
+
|
| 838 |
+
On Linux *ioclass* is one of the IOPRIO_CLASS_* constants.
|
| 839 |
+
*value* is a number which goes from 0 to 7. The higher the
|
| 840 |
+
value, the lower the I/O priority of the process.
|
| 841 |
+
|
| 842 |
+
On Windows only *ioclass* is used and it can be set to 2
|
| 843 |
+
(normal), 1 (low) or 0 (very low).
|
| 844 |
+
|
| 845 |
+
Available on Linux and Windows > Vista only.
|
| 846 |
+
"""
|
| 847 |
+
if ioclass is None:
|
| 848 |
+
if value is not None:
|
| 849 |
+
msg = "'ioclass' argument must be specified"
|
| 850 |
+
raise ValueError(msg)
|
| 851 |
+
return self._proc.ionice_get()
|
| 852 |
+
else:
|
| 853 |
+
self._raise_if_pid_reused()
|
| 854 |
+
return self._proc.ionice_set(ioclass, value)
|
| 855 |
+
|
| 856 |
+
# Linux / FreeBSD only
|
| 857 |
+
if hasattr(_psplatform.Process, "rlimit"):
|
| 858 |
+
|
| 859 |
+
def rlimit(self, resource, limits=None):
|
| 860 |
+
"""Get or set process resource limits as a (soft, hard)
|
| 861 |
+
tuple.
|
| 862 |
+
|
| 863 |
+
*resource* is one of the RLIMIT_* constants.
|
| 864 |
+
*limits* is supposed to be a (soft, hard) tuple.
|
| 865 |
+
|
| 866 |
+
See "man prlimit" for further info.
|
| 867 |
+
Available on Linux and FreeBSD only.
|
| 868 |
+
"""
|
| 869 |
+
if limits is not None:
|
| 870 |
+
self._raise_if_pid_reused()
|
| 871 |
+
return self._proc.rlimit(resource, limits)
|
| 872 |
+
|
| 873 |
+
# Windows, Linux and FreeBSD only
|
| 874 |
+
if hasattr(_psplatform.Process, "cpu_affinity_get"):
|
| 875 |
+
|
| 876 |
+
def cpu_affinity(self, cpus=None):
|
| 877 |
+
"""Get or set process CPU affinity.
|
| 878 |
+
If specified, *cpus* must be a list of CPUs for which you
|
| 879 |
+
want to set the affinity (e.g. [0, 1]).
|
| 880 |
+
If an empty list is passed, all egible CPUs are assumed
|
| 881 |
+
(and set).
|
| 882 |
+
(Windows, Linux and BSD only).
|
| 883 |
+
"""
|
| 884 |
+
if cpus is None:
|
| 885 |
+
return sorted(set(self._proc.cpu_affinity_get()))
|
| 886 |
+
else:
|
| 887 |
+
self._raise_if_pid_reused()
|
| 888 |
+
if not cpus:
|
| 889 |
+
if hasattr(self._proc, "_get_eligible_cpus"):
|
| 890 |
+
cpus = self._proc._get_eligible_cpus()
|
| 891 |
+
else:
|
| 892 |
+
cpus = tuple(range(len(cpu_times(percpu=True))))
|
| 893 |
+
self._proc.cpu_affinity_set(list(set(cpus)))
|
| 894 |
+
|
| 895 |
+
# Linux, FreeBSD, SunOS
|
| 896 |
+
if hasattr(_psplatform.Process, "cpu_num"):
|
| 897 |
+
|
| 898 |
+
def cpu_num(self):
|
| 899 |
+
"""Return what CPU this process is currently running on.
|
| 900 |
+
The returned number should be <= psutil.cpu_count()
|
| 901 |
+
and <= len(psutil.cpu_percent(percpu=True)).
|
| 902 |
+
It may be used in conjunction with
|
| 903 |
+
psutil.cpu_percent(percpu=True) to observe the system
|
| 904 |
+
workload distributed across CPUs.
|
| 905 |
+
"""
|
| 906 |
+
return self._proc.cpu_num()
|
| 907 |
+
|
| 908 |
+
# All platforms has it, but maybe not in the future.
|
| 909 |
+
if hasattr(_psplatform.Process, "environ"):
|
| 910 |
+
|
| 911 |
+
def environ(self):
|
| 912 |
+
"""The environment variables of the process as a dict. Note: this
|
| 913 |
+
might not reflect changes made after the process started.
|
| 914 |
+
"""
|
| 915 |
+
return self._proc.environ()
|
| 916 |
+
|
| 917 |
+
if WINDOWS:
|
| 918 |
+
|
| 919 |
+
def num_handles(self):
|
| 920 |
+
"""Return the number of handles opened by this process
|
| 921 |
+
(Windows only).
|
| 922 |
+
"""
|
| 923 |
+
return self._proc.num_handles()
|
| 924 |
+
|
| 925 |
+
def num_ctx_switches(self):
|
| 926 |
+
"""Return the number of voluntary and involuntary context
|
| 927 |
+
switches performed by this process.
|
| 928 |
+
"""
|
| 929 |
+
return self._proc.num_ctx_switches()
|
| 930 |
+
|
| 931 |
+
def num_threads(self):
|
| 932 |
+
"""Return the number of threads used by this process."""
|
| 933 |
+
return self._proc.num_threads()
|
| 934 |
+
|
| 935 |
+
if hasattr(_psplatform.Process, "threads"):
|
| 936 |
+
|
| 937 |
+
def threads(self):
|
| 938 |
+
"""Return threads opened by process as a list of
|
| 939 |
+
(id, user_time, system_time) namedtuples representing
|
| 940 |
+
thread id and thread CPU times (user/system).
|
| 941 |
+
On OpenBSD this method requires root access.
|
| 942 |
+
"""
|
| 943 |
+
return self._proc.threads()
|
| 944 |
+
|
| 945 |
+
def children(self, recursive=False):
|
| 946 |
+
"""Return the children of this process as a list of Process
|
| 947 |
+
instances, pre-emptively checking whether PID has been reused.
|
| 948 |
+
If *recursive* is True return all the parent descendants.
|
| 949 |
+
|
| 950 |
+
Example (A == this process):
|
| 951 |
+
|
| 952 |
+
A ─┐
|
| 953 |
+
│
|
| 954 |
+
├─ B (child) ─┐
|
| 955 |
+
│ └─ X (grandchild) ─┐
|
| 956 |
+
│ └─ Y (great grandchild)
|
| 957 |
+
├─ C (child)
|
| 958 |
+
└─ D (child)
|
| 959 |
+
|
| 960 |
+
>>> import psutil
|
| 961 |
+
>>> p = psutil.Process()
|
| 962 |
+
>>> p.children()
|
| 963 |
+
B, C, D
|
| 964 |
+
>>> p.children(recursive=True)
|
| 965 |
+
B, X, Y, C, D
|
| 966 |
+
|
| 967 |
+
Note that in the example above if process X disappears
|
| 968 |
+
process Y won't be listed as the reference to process A
|
| 969 |
+
is lost.
|
| 970 |
+
"""
|
| 971 |
+
self._raise_if_pid_reused()
|
| 972 |
+
ppid_map = _ppid_map()
|
| 973 |
+
ret = []
|
| 974 |
+
if not recursive:
|
| 975 |
+
for pid, ppid in ppid_map.items():
|
| 976 |
+
if ppid == self.pid:
|
| 977 |
+
try:
|
| 978 |
+
child = Process(pid)
|
| 979 |
+
# if child happens to be older than its parent
|
| 980 |
+
# (self) it means child's PID has been reused
|
| 981 |
+
if self.create_time() <= child.create_time():
|
| 982 |
+
ret.append(child)
|
| 983 |
+
except (NoSuchProcess, ZombieProcess):
|
| 984 |
+
pass
|
| 985 |
+
else:
|
| 986 |
+
# Construct a {pid: [child pids]} dict
|
| 987 |
+
reverse_ppid_map = collections.defaultdict(list)
|
| 988 |
+
for pid, ppid in ppid_map.items():
|
| 989 |
+
reverse_ppid_map[ppid].append(pid)
|
| 990 |
+
# Recursively traverse that dict, starting from self.pid,
|
| 991 |
+
# such that we only call Process() on actual children
|
| 992 |
+
seen = set()
|
| 993 |
+
stack = [self.pid]
|
| 994 |
+
while stack:
|
| 995 |
+
pid = stack.pop()
|
| 996 |
+
if pid in seen:
|
| 997 |
+
# Since pids can be reused while the ppid_map is
|
| 998 |
+
# constructed, there may be rare instances where
|
| 999 |
+
# there's a cycle in the recorded process "tree".
|
| 1000 |
+
continue
|
| 1001 |
+
seen.add(pid)
|
| 1002 |
+
for child_pid in reverse_ppid_map[pid]:
|
| 1003 |
+
try:
|
| 1004 |
+
child = Process(child_pid)
|
| 1005 |
+
# if child happens to be older than its parent
|
| 1006 |
+
# (self) it means child's PID has been reused
|
| 1007 |
+
intime = self.create_time() <= child.create_time()
|
| 1008 |
+
if intime:
|
| 1009 |
+
ret.append(child)
|
| 1010 |
+
stack.append(child_pid)
|
| 1011 |
+
except (NoSuchProcess, ZombieProcess):
|
| 1012 |
+
pass
|
| 1013 |
+
return ret
|
| 1014 |
+
|
| 1015 |
+
def cpu_percent(self, interval=None):
|
| 1016 |
+
"""Return a float representing the current process CPU
|
| 1017 |
+
utilization as a percentage.
|
| 1018 |
+
|
| 1019 |
+
When *interval* is 0.0 or None (default) compares process times
|
| 1020 |
+
to system CPU times elapsed since last call, returning
|
| 1021 |
+
immediately (non-blocking). That means that the first time
|
| 1022 |
+
this is called it will return a meaningful 0.0 value.
|
| 1023 |
+
|
| 1024 |
+
When *interval* is > 0.0 compares process times to system CPU
|
| 1025 |
+
times elapsed before and after the interval (blocking).
|
| 1026 |
+
|
| 1027 |
+
In this case is recommended for accuracy that this function
|
| 1028 |
+
be called with at least 0.1 seconds between calls.
|
| 1029 |
+
|
| 1030 |
+
A value > 100.0 can be returned in case of processes running
|
| 1031 |
+
multiple threads on different CPU cores.
|
| 1032 |
+
|
| 1033 |
+
The returned value is explicitly NOT split evenly between
|
| 1034 |
+
all available logical CPUs. This means that a busy loop process
|
| 1035 |
+
running on a system with 2 logical CPUs will be reported as
|
| 1036 |
+
having 100% CPU utilization instead of 50%.
|
| 1037 |
+
|
| 1038 |
+
Examples:
|
| 1039 |
+
|
| 1040 |
+
>>> import psutil
|
| 1041 |
+
>>> p = psutil.Process(os.getpid())
|
| 1042 |
+
>>> # blocking
|
| 1043 |
+
>>> p.cpu_percent(interval=1)
|
| 1044 |
+
2.0
|
| 1045 |
+
>>> # non-blocking (percentage since last call)
|
| 1046 |
+
>>> p.cpu_percent(interval=None)
|
| 1047 |
+
2.9
|
| 1048 |
+
>>>
|
| 1049 |
+
"""
|
| 1050 |
+
blocking = interval is not None and interval > 0.0
|
| 1051 |
+
if interval is not None and interval < 0:
|
| 1052 |
+
msg = "interval is not positive (got %r)" % interval
|
| 1053 |
+
raise ValueError(msg)
|
| 1054 |
+
num_cpus = cpu_count() or 1
|
| 1055 |
+
|
| 1056 |
+
def timer():
|
| 1057 |
+
return _timer() * num_cpus
|
| 1058 |
+
|
| 1059 |
+
if blocking:
|
| 1060 |
+
st1 = timer()
|
| 1061 |
+
pt1 = self._proc.cpu_times()
|
| 1062 |
+
time.sleep(interval)
|
| 1063 |
+
st2 = timer()
|
| 1064 |
+
pt2 = self._proc.cpu_times()
|
| 1065 |
+
else:
|
| 1066 |
+
st1 = self._last_sys_cpu_times
|
| 1067 |
+
pt1 = self._last_proc_cpu_times
|
| 1068 |
+
st2 = timer()
|
| 1069 |
+
pt2 = self._proc.cpu_times()
|
| 1070 |
+
if st1 is None or pt1 is None:
|
| 1071 |
+
self._last_sys_cpu_times = st2
|
| 1072 |
+
self._last_proc_cpu_times = pt2
|
| 1073 |
+
return 0.0
|
| 1074 |
+
|
| 1075 |
+
delta_proc = (pt2.user - pt1.user) + (pt2.system - pt1.system)
|
| 1076 |
+
delta_time = st2 - st1
|
| 1077 |
+
# reset values for next call in case of interval == None
|
| 1078 |
+
self._last_sys_cpu_times = st2
|
| 1079 |
+
self._last_proc_cpu_times = pt2
|
| 1080 |
+
|
| 1081 |
+
try:
|
| 1082 |
+
# This is the utilization split evenly between all CPUs.
|
| 1083 |
+
# E.g. a busy loop process on a 2-CPU-cores system at this
|
| 1084 |
+
# point is reported as 50% instead of 100%.
|
| 1085 |
+
overall_cpus_percent = (delta_proc / delta_time) * 100
|
| 1086 |
+
except ZeroDivisionError:
|
| 1087 |
+
# interval was too low
|
| 1088 |
+
return 0.0
|
| 1089 |
+
else:
|
| 1090 |
+
# Note 1:
|
| 1091 |
+
# in order to emulate "top" we multiply the value for the num
|
| 1092 |
+
# of CPU cores. This way the busy process will be reported as
|
| 1093 |
+
# having 100% (or more) usage.
|
| 1094 |
+
#
|
| 1095 |
+
# Note 2:
|
| 1096 |
+
# taskmgr.exe on Windows differs in that it will show 50%
|
| 1097 |
+
# instead.
|
| 1098 |
+
#
|
| 1099 |
+
# Note 3:
|
| 1100 |
+
# a percentage > 100 is legitimate as it can result from a
|
| 1101 |
+
# process with multiple threads running on different CPU
|
| 1102 |
+
# cores (top does the same), see:
|
| 1103 |
+
# http://stackoverflow.com/questions/1032357
|
| 1104 |
+
# https://github.com/giampaolo/psutil/issues/474
|
| 1105 |
+
single_cpu_percent = overall_cpus_percent * num_cpus
|
| 1106 |
+
return round(single_cpu_percent, 1)
|
| 1107 |
+
|
| 1108 |
+
@memoize_when_activated
|
| 1109 |
+
def cpu_times(self):
|
| 1110 |
+
"""Return a (user, system, children_user, children_system)
|
| 1111 |
+
namedtuple representing the accumulated process time, in
|
| 1112 |
+
seconds.
|
| 1113 |
+
This is similar to os.times() but per-process.
|
| 1114 |
+
On macOS and Windows children_user and children_system are
|
| 1115 |
+
always set to 0.
|
| 1116 |
+
"""
|
| 1117 |
+
return self._proc.cpu_times()
|
| 1118 |
+
|
| 1119 |
+
@memoize_when_activated
|
| 1120 |
+
def memory_info(self):
|
| 1121 |
+
"""Return a namedtuple with variable fields depending on the
|
| 1122 |
+
platform, representing memory information about the process.
|
| 1123 |
+
|
| 1124 |
+
The "portable" fields available on all platforms are `rss` and `vms`.
|
| 1125 |
+
|
| 1126 |
+
All numbers are expressed in bytes.
|
| 1127 |
+
"""
|
| 1128 |
+
return self._proc.memory_info()
|
| 1129 |
+
|
| 1130 |
+
@_common.deprecated_method(replacement="memory_info")
|
| 1131 |
+
def memory_info_ex(self):
|
| 1132 |
+
return self.memory_info()
|
| 1133 |
+
|
| 1134 |
+
def memory_full_info(self):
|
| 1135 |
+
"""This method returns the same information as memory_info(),
|
| 1136 |
+
plus, on some platform (Linux, macOS, Windows), also provides
|
| 1137 |
+
additional metrics (USS, PSS and swap).
|
| 1138 |
+
The additional metrics provide a better representation of actual
|
| 1139 |
+
process memory usage.
|
| 1140 |
+
|
| 1141 |
+
Namely USS is the memory which is unique to a process and which
|
| 1142 |
+
would be freed if the process was terminated right now.
|
| 1143 |
+
|
| 1144 |
+
It does so by passing through the whole process address.
|
| 1145 |
+
As such it usually requires higher user privileges than
|
| 1146 |
+
memory_info() and is considerably slower.
|
| 1147 |
+
"""
|
| 1148 |
+
return self._proc.memory_full_info()
|
| 1149 |
+
|
| 1150 |
+
def memory_percent(self, memtype="rss"):
|
| 1151 |
+
"""Compare process memory to total physical system memory and
|
| 1152 |
+
calculate process memory utilization as a percentage.
|
| 1153 |
+
*memtype* argument is a string that dictates what type of
|
| 1154 |
+
process memory you want to compare against (defaults to "rss").
|
| 1155 |
+
The list of available strings can be obtained like this:
|
| 1156 |
+
|
| 1157 |
+
>>> psutil.Process().memory_info()._fields
|
| 1158 |
+
('rss', 'vms', 'shared', 'text', 'lib', 'data', 'dirty', 'uss', 'pss')
|
| 1159 |
+
"""
|
| 1160 |
+
valid_types = list(_psplatform.pfullmem._fields)
|
| 1161 |
+
if memtype not in valid_types:
|
| 1162 |
+
msg = "invalid memtype %r; valid types are %r" % (
|
| 1163 |
+
memtype,
|
| 1164 |
+
tuple(valid_types),
|
| 1165 |
+
)
|
| 1166 |
+
raise ValueError(msg)
|
| 1167 |
+
fun = (
|
| 1168 |
+
self.memory_info
|
| 1169 |
+
if memtype in _psplatform.pmem._fields
|
| 1170 |
+
else self.memory_full_info
|
| 1171 |
+
)
|
| 1172 |
+
metrics = fun()
|
| 1173 |
+
value = getattr(metrics, memtype)
|
| 1174 |
+
|
| 1175 |
+
# use cached value if available
|
| 1176 |
+
total_phymem = _TOTAL_PHYMEM or virtual_memory().total
|
| 1177 |
+
if not total_phymem > 0:
|
| 1178 |
+
# we should never get here
|
| 1179 |
+
msg = (
|
| 1180 |
+
"can't calculate process memory percent because total physical"
|
| 1181 |
+
" system memory is not positive (%r)" % (total_phymem)
|
| 1182 |
+
)
|
| 1183 |
+
raise ValueError(msg)
|
| 1184 |
+
return (value / float(total_phymem)) * 100
|
| 1185 |
+
|
| 1186 |
+
if hasattr(_psplatform.Process, "memory_maps"):
|
| 1187 |
+
|
| 1188 |
+
def memory_maps(self, grouped=True):
|
| 1189 |
+
"""Return process' mapped memory regions as a list of namedtuples
|
| 1190 |
+
whose fields are variable depending on the platform.
|
| 1191 |
+
|
| 1192 |
+
If *grouped* is True the mapped regions with the same 'path'
|
| 1193 |
+
are grouped together and the different memory fields are summed.
|
| 1194 |
+
|
| 1195 |
+
If *grouped* is False every mapped region is shown as a single
|
| 1196 |
+
entity and the namedtuple will also include the mapped region's
|
| 1197 |
+
address space ('addr') and permission set ('perms').
|
| 1198 |
+
"""
|
| 1199 |
+
it = self._proc.memory_maps()
|
| 1200 |
+
if grouped:
|
| 1201 |
+
d = {}
|
| 1202 |
+
for tupl in it:
|
| 1203 |
+
path = tupl[2]
|
| 1204 |
+
nums = tupl[3:]
|
| 1205 |
+
try:
|
| 1206 |
+
d[path] = map(lambda x, y: x + y, d[path], nums)
|
| 1207 |
+
except KeyError:
|
| 1208 |
+
d[path] = nums
|
| 1209 |
+
nt = _psplatform.pmmap_grouped
|
| 1210 |
+
return [nt(path, *d[path]) for path in d] # NOQA
|
| 1211 |
+
else:
|
| 1212 |
+
nt = _psplatform.pmmap_ext
|
| 1213 |
+
return [nt(*x) for x in it]
|
| 1214 |
+
|
| 1215 |
+
def open_files(self):
|
| 1216 |
+
"""Return files opened by process as a list of
|
| 1217 |
+
(path, fd) namedtuples including the absolute file name
|
| 1218 |
+
and file descriptor number.
|
| 1219 |
+
"""
|
| 1220 |
+
return self._proc.open_files()
|
| 1221 |
+
|
| 1222 |
+
def net_connections(self, kind='inet'):
|
| 1223 |
+
"""Return socket connections opened by process as a list of
|
| 1224 |
+
(fd, family, type, laddr, raddr, status) namedtuples.
|
| 1225 |
+
The *kind* parameter filters for connections that match the
|
| 1226 |
+
following criteria:
|
| 1227 |
+
|
| 1228 |
+
+------------+----------------------------------------------------+
|
| 1229 |
+
| Kind Value | Connections using |
|
| 1230 |
+
+------------+----------------------------------------------------+
|
| 1231 |
+
| inet | IPv4 and IPv6 |
|
| 1232 |
+
| inet4 | IPv4 |
|
| 1233 |
+
| inet6 | IPv6 |
|
| 1234 |
+
| tcp | TCP |
|
| 1235 |
+
| tcp4 | TCP over IPv4 |
|
| 1236 |
+
| tcp6 | TCP over IPv6 |
|
| 1237 |
+
| udp | UDP |
|
| 1238 |
+
| udp4 | UDP over IPv4 |
|
| 1239 |
+
| udp6 | UDP over IPv6 |
|
| 1240 |
+
| unix | UNIX socket (both UDP and TCP protocols) |
|
| 1241 |
+
| all | the sum of all the possible families and protocols |
|
| 1242 |
+
+------------+----------------------------------------------------+
|
| 1243 |
+
"""
|
| 1244 |
+
return self._proc.net_connections(kind)
|
| 1245 |
+
|
| 1246 |
+
@_common.deprecated_method(replacement="net_connections")
|
| 1247 |
+
def connections(self, kind="inet"):
|
| 1248 |
+
return self.net_connections(kind=kind)
|
| 1249 |
+
|
| 1250 |
+
# --- signals
|
| 1251 |
+
|
| 1252 |
+
if POSIX:
|
| 1253 |
+
|
| 1254 |
+
def _send_signal(self, sig):
|
| 1255 |
+
assert not self.pid < 0, self.pid
|
| 1256 |
+
self._raise_if_pid_reused()
|
| 1257 |
+
if self.pid == 0:
|
| 1258 |
+
# see "man 2 kill"
|
| 1259 |
+
msg = (
|
| 1260 |
+
"preventing sending signal to process with PID 0 as it "
|
| 1261 |
+
"would affect every process in the process group of the "
|
| 1262 |
+
"calling process (os.getpid()) instead of PID 0"
|
| 1263 |
+
)
|
| 1264 |
+
raise ValueError(msg)
|
| 1265 |
+
try:
|
| 1266 |
+
os.kill(self.pid, sig)
|
| 1267 |
+
except ProcessLookupError:
|
| 1268 |
+
if OPENBSD and pid_exists(self.pid):
|
| 1269 |
+
# We do this because os.kill() lies in case of
|
| 1270 |
+
# zombie processes.
|
| 1271 |
+
raise ZombieProcess(self.pid, self._name, self._ppid)
|
| 1272 |
+
else:
|
| 1273 |
+
self._gone = True
|
| 1274 |
+
raise NoSuchProcess(self.pid, self._name)
|
| 1275 |
+
except PermissionError:
|
| 1276 |
+
raise AccessDenied(self.pid, self._name)
|
| 1277 |
+
|
| 1278 |
+
def send_signal(self, sig):
|
| 1279 |
+
"""Send a signal *sig* to process pre-emptively checking
|
| 1280 |
+
whether PID has been reused (see signal module constants) .
|
| 1281 |
+
On Windows only SIGTERM is valid and is treated as an alias
|
| 1282 |
+
for kill().
|
| 1283 |
+
"""
|
| 1284 |
+
if POSIX:
|
| 1285 |
+
self._send_signal(sig)
|
| 1286 |
+
else: # pragma: no cover
|
| 1287 |
+
self._raise_if_pid_reused()
|
| 1288 |
+
if sig != signal.SIGTERM and not self.is_running():
|
| 1289 |
+
msg = "process no longer exists"
|
| 1290 |
+
raise NoSuchProcess(self.pid, self._name, msg=msg)
|
| 1291 |
+
self._proc.send_signal(sig)
|
| 1292 |
+
|
| 1293 |
+
def suspend(self):
|
| 1294 |
+
"""Suspend process execution with SIGSTOP pre-emptively checking
|
| 1295 |
+
whether PID has been reused.
|
| 1296 |
+
On Windows this has the effect of suspending all process threads.
|
| 1297 |
+
"""
|
| 1298 |
+
if POSIX:
|
| 1299 |
+
self._send_signal(signal.SIGSTOP)
|
| 1300 |
+
else: # pragma: no cover
|
| 1301 |
+
self._raise_if_pid_reused()
|
| 1302 |
+
self._proc.suspend()
|
| 1303 |
+
|
| 1304 |
+
def resume(self):
|
| 1305 |
+
"""Resume process execution with SIGCONT pre-emptively checking
|
| 1306 |
+
whether PID has been reused.
|
| 1307 |
+
On Windows this has the effect of resuming all process threads.
|
| 1308 |
+
"""
|
| 1309 |
+
if POSIX:
|
| 1310 |
+
self._send_signal(signal.SIGCONT)
|
| 1311 |
+
else: # pragma: no cover
|
| 1312 |
+
self._raise_if_pid_reused()
|
| 1313 |
+
self._proc.resume()
|
| 1314 |
+
|
| 1315 |
+
def terminate(self):
|
| 1316 |
+
"""Terminate the process with SIGTERM pre-emptively checking
|
| 1317 |
+
whether PID has been reused.
|
| 1318 |
+
On Windows this is an alias for kill().
|
| 1319 |
+
"""
|
| 1320 |
+
if POSIX:
|
| 1321 |
+
self._send_signal(signal.SIGTERM)
|
| 1322 |
+
else: # pragma: no cover
|
| 1323 |
+
self._raise_if_pid_reused()
|
| 1324 |
+
self._proc.kill()
|
| 1325 |
+
|
| 1326 |
+
def kill(self):
|
| 1327 |
+
"""Kill the current process with SIGKILL pre-emptively checking
|
| 1328 |
+
whether PID has been reused.
|
| 1329 |
+
"""
|
| 1330 |
+
if POSIX:
|
| 1331 |
+
self._send_signal(signal.SIGKILL)
|
| 1332 |
+
else: # pragma: no cover
|
| 1333 |
+
self._raise_if_pid_reused()
|
| 1334 |
+
self._proc.kill()
|
| 1335 |
+
|
| 1336 |
+
def wait(self, timeout=None):
|
| 1337 |
+
"""Wait for process to terminate and, if process is a children
|
| 1338 |
+
of os.getpid(), also return its exit code, else None.
|
| 1339 |
+
On Windows there's no such limitation (exit code is always
|
| 1340 |
+
returned).
|
| 1341 |
+
|
| 1342 |
+
If the process is already terminated immediately return None
|
| 1343 |
+
instead of raising NoSuchProcess.
|
| 1344 |
+
|
| 1345 |
+
If *timeout* (in seconds) is specified and process is still
|
| 1346 |
+
alive raise TimeoutExpired.
|
| 1347 |
+
|
| 1348 |
+
To wait for multiple Process(es) use psutil.wait_procs().
|
| 1349 |
+
"""
|
| 1350 |
+
if timeout is not None and not timeout >= 0:
|
| 1351 |
+
msg = "timeout must be a positive integer"
|
| 1352 |
+
raise ValueError(msg)
|
| 1353 |
+
if self._exitcode is not _SENTINEL:
|
| 1354 |
+
return self._exitcode
|
| 1355 |
+
self._exitcode = self._proc.wait(timeout)
|
| 1356 |
+
return self._exitcode
|
| 1357 |
+
|
| 1358 |
+
|
| 1359 |
+
# The valid attr names which can be processed by Process.as_dict().
|
| 1360 |
+
# fmt: off
|
| 1361 |
+
_as_dict_attrnames = set(
|
| 1362 |
+
[x for x in dir(Process) if not x.startswith('_') and x not in
|
| 1363 |
+
{'send_signal', 'suspend', 'resume', 'terminate', 'kill', 'wait',
|
| 1364 |
+
'is_running', 'as_dict', 'parent', 'parents', 'children', 'rlimit',
|
| 1365 |
+
'memory_info_ex', 'connections', 'oneshot'}])
|
| 1366 |
+
# fmt: on
|
| 1367 |
+
|
| 1368 |
+
|
| 1369 |
+
# =====================================================================
|
| 1370 |
+
# --- Popen class
|
| 1371 |
+
# =====================================================================
|
| 1372 |
+
|
| 1373 |
+
|
| 1374 |
+
class Popen(Process):
|
| 1375 |
+
"""Same as subprocess.Popen, but in addition it provides all
|
| 1376 |
+
psutil.Process methods in a single class.
|
| 1377 |
+
For the following methods which are common to both classes, psutil
|
| 1378 |
+
implementation takes precedence:
|
| 1379 |
+
|
| 1380 |
+
* send_signal()
|
| 1381 |
+
* terminate()
|
| 1382 |
+
* kill()
|
| 1383 |
+
|
| 1384 |
+
This is done in order to avoid killing another process in case its
|
| 1385 |
+
PID has been reused, fixing BPO-6973.
|
| 1386 |
+
|
| 1387 |
+
>>> import psutil
|
| 1388 |
+
>>> from subprocess import PIPE
|
| 1389 |
+
>>> p = psutil.Popen(["python", "-c", "print 'hi'"], stdout=PIPE)
|
| 1390 |
+
>>> p.name()
|
| 1391 |
+
'python'
|
| 1392 |
+
>>> p.uids()
|
| 1393 |
+
user(real=1000, effective=1000, saved=1000)
|
| 1394 |
+
>>> p.username()
|
| 1395 |
+
'giampaolo'
|
| 1396 |
+
>>> p.communicate()
|
| 1397 |
+
('hi', None)
|
| 1398 |
+
>>> p.terminate()
|
| 1399 |
+
>>> p.wait(timeout=2)
|
| 1400 |
+
0
|
| 1401 |
+
>>>
|
| 1402 |
+
"""
|
| 1403 |
+
|
| 1404 |
+
def __init__(self, *args, **kwargs):
|
| 1405 |
+
# Explicitly avoid to raise NoSuchProcess in case the process
|
| 1406 |
+
# spawned by subprocess.Popen terminates too quickly, see:
|
| 1407 |
+
# https://github.com/giampaolo/psutil/issues/193
|
| 1408 |
+
self.__subproc = subprocess.Popen(*args, **kwargs)
|
| 1409 |
+
self._init(self.__subproc.pid, _ignore_nsp=True)
|
| 1410 |
+
|
| 1411 |
+
def __dir__(self):
|
| 1412 |
+
return sorted(set(dir(Popen) + dir(subprocess.Popen)))
|
| 1413 |
+
|
| 1414 |
+
def __enter__(self):
|
| 1415 |
+
if hasattr(self.__subproc, '__enter__'):
|
| 1416 |
+
self.__subproc.__enter__()
|
| 1417 |
+
return self
|
| 1418 |
+
|
| 1419 |
+
def __exit__(self, *args, **kwargs):
|
| 1420 |
+
if hasattr(self.__subproc, '__exit__'):
|
| 1421 |
+
return self.__subproc.__exit__(*args, **kwargs)
|
| 1422 |
+
else:
|
| 1423 |
+
if self.stdout:
|
| 1424 |
+
self.stdout.close()
|
| 1425 |
+
if self.stderr:
|
| 1426 |
+
self.stderr.close()
|
| 1427 |
+
try:
|
| 1428 |
+
# Flushing a BufferedWriter may raise an error.
|
| 1429 |
+
if self.stdin:
|
| 1430 |
+
self.stdin.close()
|
| 1431 |
+
finally:
|
| 1432 |
+
# Wait for the process to terminate, to avoid zombies.
|
| 1433 |
+
self.wait()
|
| 1434 |
+
|
| 1435 |
+
def __getattribute__(self, name):
|
| 1436 |
+
try:
|
| 1437 |
+
return object.__getattribute__(self, name)
|
| 1438 |
+
except AttributeError:
|
| 1439 |
+
try:
|
| 1440 |
+
return object.__getattribute__(self.__subproc, name)
|
| 1441 |
+
except AttributeError:
|
| 1442 |
+
msg = "%s instance has no attribute '%s'" % (
|
| 1443 |
+
self.__class__.__name__,
|
| 1444 |
+
name,
|
| 1445 |
+
)
|
| 1446 |
+
raise AttributeError(msg)
|
| 1447 |
+
|
| 1448 |
+
def wait(self, timeout=None):
|
| 1449 |
+
if self.__subproc.returncode is not None:
|
| 1450 |
+
return self.__subproc.returncode
|
| 1451 |
+
ret = super(Popen, self).wait(timeout) # noqa
|
| 1452 |
+
self.__subproc.returncode = ret
|
| 1453 |
+
return ret
|
| 1454 |
+
|
| 1455 |
+
|
| 1456 |
+
# =====================================================================
|
| 1457 |
+
# --- system processes related functions
|
| 1458 |
+
# =====================================================================
|
| 1459 |
+
|
| 1460 |
+
|
| 1461 |
+
def pids():
|
| 1462 |
+
"""Return a list of current running PIDs."""
|
| 1463 |
+
global _LOWEST_PID
|
| 1464 |
+
ret = sorted(_psplatform.pids())
|
| 1465 |
+
_LOWEST_PID = ret[0]
|
| 1466 |
+
return ret
|
| 1467 |
+
|
| 1468 |
+
|
| 1469 |
+
def pid_exists(pid):
|
| 1470 |
+
"""Return True if given PID exists in the current process list.
|
| 1471 |
+
This is faster than doing "pid in psutil.pids()" and
|
| 1472 |
+
should be preferred.
|
| 1473 |
+
"""
|
| 1474 |
+
if pid < 0:
|
| 1475 |
+
return False
|
| 1476 |
+
elif pid == 0 and POSIX:
|
| 1477 |
+
# On POSIX we use os.kill() to determine PID existence.
|
| 1478 |
+
# According to "man 2 kill" PID 0 has a special meaning
|
| 1479 |
+
# though: it refers to <<every process in the process
|
| 1480 |
+
# group of the calling process>> and that is not we want
|
| 1481 |
+
# to do here.
|
| 1482 |
+
return pid in pids()
|
| 1483 |
+
else:
|
| 1484 |
+
return _psplatform.pid_exists(pid)
|
| 1485 |
+
|
| 1486 |
+
|
| 1487 |
+
_pmap = {}
|
| 1488 |
+
_pids_reused = set()
|
| 1489 |
+
|
| 1490 |
+
|
| 1491 |
+
def process_iter(attrs=None, ad_value=None):
|
| 1492 |
+
"""Return a generator yielding a Process instance for all
|
| 1493 |
+
running processes.
|
| 1494 |
+
|
| 1495 |
+
Every new Process instance is only created once and then cached
|
| 1496 |
+
into an internal table which is updated every time this is used.
|
| 1497 |
+
Cache can optionally be cleared via `process_iter.clear_cache()`.
|
| 1498 |
+
|
| 1499 |
+
The sorting order in which processes are yielded is based on
|
| 1500 |
+
their PIDs.
|
| 1501 |
+
|
| 1502 |
+
*attrs* and *ad_value* have the same meaning as in
|
| 1503 |
+
Process.as_dict(). If *attrs* is specified as_dict() is called
|
| 1504 |
+
and the resulting dict is stored as a 'info' attribute attached
|
| 1505 |
+
to returned Process instance.
|
| 1506 |
+
If *attrs* is an empty list it will retrieve all process info
|
| 1507 |
+
(slow).
|
| 1508 |
+
"""
|
| 1509 |
+
global _pmap
|
| 1510 |
+
|
| 1511 |
+
def add(pid):
|
| 1512 |
+
proc = Process(pid)
|
| 1513 |
+
pmap[proc.pid] = proc
|
| 1514 |
+
return proc
|
| 1515 |
+
|
| 1516 |
+
def remove(pid):
|
| 1517 |
+
pmap.pop(pid, None)
|
| 1518 |
+
|
| 1519 |
+
pmap = _pmap.copy()
|
| 1520 |
+
a = set(pids())
|
| 1521 |
+
b = set(pmap.keys())
|
| 1522 |
+
new_pids = a - b
|
| 1523 |
+
gone_pids = b - a
|
| 1524 |
+
for pid in gone_pids:
|
| 1525 |
+
remove(pid)
|
| 1526 |
+
while _pids_reused:
|
| 1527 |
+
pid = _pids_reused.pop()
|
| 1528 |
+
debug("refreshing Process instance for reused PID %s" % pid)
|
| 1529 |
+
remove(pid)
|
| 1530 |
+
try:
|
| 1531 |
+
ls = sorted(list(pmap.items()) + list(dict.fromkeys(new_pids).items()))
|
| 1532 |
+
for pid, proc in ls:
|
| 1533 |
+
try:
|
| 1534 |
+
if proc is None: # new process
|
| 1535 |
+
proc = add(pid)
|
| 1536 |
+
if attrs is not None:
|
| 1537 |
+
proc.info = proc.as_dict(attrs=attrs, ad_value=ad_value)
|
| 1538 |
+
yield proc
|
| 1539 |
+
except NoSuchProcess:
|
| 1540 |
+
remove(pid)
|
| 1541 |
+
finally:
|
| 1542 |
+
_pmap = pmap
|
| 1543 |
+
|
| 1544 |
+
|
| 1545 |
+
process_iter.cache_clear = lambda: _pmap.clear() # noqa
|
| 1546 |
+
process_iter.cache_clear.__doc__ = "Clear process_iter() internal cache."
|
| 1547 |
+
|
| 1548 |
+
|
| 1549 |
+
def wait_procs(procs, timeout=None, callback=None):
|
| 1550 |
+
"""Convenience function which waits for a list of processes to
|
| 1551 |
+
terminate.
|
| 1552 |
+
|
| 1553 |
+
Return a (gone, alive) tuple indicating which processes
|
| 1554 |
+
are gone and which ones are still alive.
|
| 1555 |
+
|
| 1556 |
+
The gone ones will have a new *returncode* attribute indicating
|
| 1557 |
+
process exit status (may be None).
|
| 1558 |
+
|
| 1559 |
+
*callback* is a function which gets called every time a process
|
| 1560 |
+
terminates (a Process instance is passed as callback argument).
|
| 1561 |
+
|
| 1562 |
+
Function will return as soon as all processes terminate or when
|
| 1563 |
+
*timeout* occurs.
|
| 1564 |
+
Differently from Process.wait() it will not raise TimeoutExpired if
|
| 1565 |
+
*timeout* occurs.
|
| 1566 |
+
|
| 1567 |
+
Typical use case is:
|
| 1568 |
+
|
| 1569 |
+
- send SIGTERM to a list of processes
|
| 1570 |
+
- give them some time to terminate
|
| 1571 |
+
- send SIGKILL to those ones which are still alive
|
| 1572 |
+
|
| 1573 |
+
Example:
|
| 1574 |
+
|
| 1575 |
+
>>> def on_terminate(proc):
|
| 1576 |
+
... print("process {} terminated".format(proc))
|
| 1577 |
+
...
|
| 1578 |
+
>>> for p in procs:
|
| 1579 |
+
... p.terminate()
|
| 1580 |
+
...
|
| 1581 |
+
>>> gone, alive = wait_procs(procs, timeout=3, callback=on_terminate)
|
| 1582 |
+
>>> for p in alive:
|
| 1583 |
+
... p.kill()
|
| 1584 |
+
"""
|
| 1585 |
+
|
| 1586 |
+
def check_gone(proc, timeout):
|
| 1587 |
+
try:
|
| 1588 |
+
returncode = proc.wait(timeout=timeout)
|
| 1589 |
+
except TimeoutExpired:
|
| 1590 |
+
pass
|
| 1591 |
+
except _SubprocessTimeoutExpired:
|
| 1592 |
+
pass
|
| 1593 |
+
else:
|
| 1594 |
+
if returncode is not None or not proc.is_running():
|
| 1595 |
+
# Set new Process instance attribute.
|
| 1596 |
+
proc.returncode = returncode
|
| 1597 |
+
gone.add(proc)
|
| 1598 |
+
if callback is not None:
|
| 1599 |
+
callback(proc)
|
| 1600 |
+
|
| 1601 |
+
if timeout is not None and not timeout >= 0:
|
| 1602 |
+
msg = "timeout must be a positive integer, got %s" % timeout
|
| 1603 |
+
raise ValueError(msg)
|
| 1604 |
+
gone = set()
|
| 1605 |
+
alive = set(procs)
|
| 1606 |
+
if callback is not None and not callable(callback):
|
| 1607 |
+
msg = "callback %r is not a callable" % callback
|
| 1608 |
+
raise TypeError(msg)
|
| 1609 |
+
if timeout is not None:
|
| 1610 |
+
deadline = _timer() + timeout
|
| 1611 |
+
|
| 1612 |
+
while alive:
|
| 1613 |
+
if timeout is not None and timeout <= 0:
|
| 1614 |
+
break
|
| 1615 |
+
for proc in alive:
|
| 1616 |
+
# Make sure that every complete iteration (all processes)
|
| 1617 |
+
# will last max 1 sec.
|
| 1618 |
+
# We do this because we don't want to wait too long on a
|
| 1619 |
+
# single process: in case it terminates too late other
|
| 1620 |
+
# processes may disappear in the meantime and their PID
|
| 1621 |
+
# reused.
|
| 1622 |
+
max_timeout = 1.0 / len(alive)
|
| 1623 |
+
if timeout is not None:
|
| 1624 |
+
timeout = min((deadline - _timer()), max_timeout)
|
| 1625 |
+
if timeout <= 0:
|
| 1626 |
+
break
|
| 1627 |
+
check_gone(proc, timeout)
|
| 1628 |
+
else:
|
| 1629 |
+
check_gone(proc, max_timeout)
|
| 1630 |
+
alive = alive - gone # noqa PLR6104
|
| 1631 |
+
|
| 1632 |
+
if alive:
|
| 1633 |
+
# Last attempt over processes survived so far.
|
| 1634 |
+
# timeout == 0 won't make this function wait any further.
|
| 1635 |
+
for proc in alive:
|
| 1636 |
+
check_gone(proc, 0)
|
| 1637 |
+
alive = alive - gone # noqa: PLR6104
|
| 1638 |
+
|
| 1639 |
+
return (list(gone), list(alive))
|
| 1640 |
+
|
| 1641 |
+
|
| 1642 |
+
# =====================================================================
|
| 1643 |
+
# --- CPU related functions
|
| 1644 |
+
# =====================================================================
|
| 1645 |
+
|
| 1646 |
+
|
| 1647 |
+
def cpu_count(logical=True):
|
| 1648 |
+
"""Return the number of logical CPUs in the system (same as
|
| 1649 |
+
os.cpu_count() in Python 3.4).
|
| 1650 |
+
|
| 1651 |
+
If *logical* is False return the number of physical cores only
|
| 1652 |
+
(e.g. hyper thread CPUs are excluded).
|
| 1653 |
+
|
| 1654 |
+
Return None if undetermined.
|
| 1655 |
+
|
| 1656 |
+
The return value is cached after first call.
|
| 1657 |
+
If desired cache can be cleared like this:
|
| 1658 |
+
|
| 1659 |
+
>>> psutil.cpu_count.cache_clear()
|
| 1660 |
+
"""
|
| 1661 |
+
if logical:
|
| 1662 |
+
ret = _psplatform.cpu_count_logical()
|
| 1663 |
+
else:
|
| 1664 |
+
ret = _psplatform.cpu_count_cores()
|
| 1665 |
+
if ret is not None and ret < 1:
|
| 1666 |
+
ret = None
|
| 1667 |
+
return ret
|
| 1668 |
+
|
| 1669 |
+
|
| 1670 |
+
def cpu_times(percpu=False):
|
| 1671 |
+
"""Return system-wide CPU times as a namedtuple.
|
| 1672 |
+
Every CPU time represents the seconds the CPU has spent in the
|
| 1673 |
+
given mode. The namedtuple's fields availability varies depending on the
|
| 1674 |
+
platform:
|
| 1675 |
+
|
| 1676 |
+
- user
|
| 1677 |
+
- system
|
| 1678 |
+
- idle
|
| 1679 |
+
- nice (UNIX)
|
| 1680 |
+
- iowait (Linux)
|
| 1681 |
+
- irq (Linux, FreeBSD)
|
| 1682 |
+
- softirq (Linux)
|
| 1683 |
+
- steal (Linux >= 2.6.11)
|
| 1684 |
+
- guest (Linux >= 2.6.24)
|
| 1685 |
+
- guest_nice (Linux >= 3.2.0)
|
| 1686 |
+
|
| 1687 |
+
When *percpu* is True return a list of namedtuples for each CPU.
|
| 1688 |
+
First element of the list refers to first CPU, second element
|
| 1689 |
+
to second CPU and so on.
|
| 1690 |
+
The order of the list is consistent across calls.
|
| 1691 |
+
"""
|
| 1692 |
+
if not percpu:
|
| 1693 |
+
return _psplatform.cpu_times()
|
| 1694 |
+
else:
|
| 1695 |
+
return _psplatform.per_cpu_times()
|
| 1696 |
+
|
| 1697 |
+
|
| 1698 |
+
try:
|
| 1699 |
+
_last_cpu_times = {threading.current_thread().ident: cpu_times()}
|
| 1700 |
+
except Exception: # noqa: BLE001
|
| 1701 |
+
# Don't want to crash at import time.
|
| 1702 |
+
_last_cpu_times = {}
|
| 1703 |
+
|
| 1704 |
+
try:
|
| 1705 |
+
_last_per_cpu_times = {
|
| 1706 |
+
threading.current_thread().ident: cpu_times(percpu=True)
|
| 1707 |
+
}
|
| 1708 |
+
except Exception: # noqa: BLE001
|
| 1709 |
+
# Don't want to crash at import time.
|
| 1710 |
+
_last_per_cpu_times = {}
|
| 1711 |
+
|
| 1712 |
+
|
| 1713 |
+
def _cpu_tot_time(times):
|
| 1714 |
+
"""Given a cpu_time() ntuple calculates the total CPU time
|
| 1715 |
+
(including idle time).
|
| 1716 |
+
"""
|
| 1717 |
+
tot = sum(times)
|
| 1718 |
+
if LINUX:
|
| 1719 |
+
# On Linux guest times are already accounted in "user" or
|
| 1720 |
+
# "nice" times, so we subtract them from total.
|
| 1721 |
+
# Htop does the same. References:
|
| 1722 |
+
# https://github.com/giampaolo/psutil/pull/940
|
| 1723 |
+
# http://unix.stackexchange.com/questions/178045
|
| 1724 |
+
# https://github.com/torvalds/linux/blob/
|
| 1725 |
+
# 447976ef4fd09b1be88b316d1a81553f1aa7cd07/kernel/sched/
|
| 1726 |
+
# cputime.c#L158
|
| 1727 |
+
tot -= getattr(times, "guest", 0) # Linux 2.6.24+
|
| 1728 |
+
tot -= getattr(times, "guest_nice", 0) # Linux 3.2.0+
|
| 1729 |
+
return tot
|
| 1730 |
+
|
| 1731 |
+
|
| 1732 |
+
def _cpu_busy_time(times):
|
| 1733 |
+
"""Given a cpu_time() ntuple calculates the busy CPU time.
|
| 1734 |
+
We do so by subtracting all idle CPU times.
|
| 1735 |
+
"""
|
| 1736 |
+
busy = _cpu_tot_time(times)
|
| 1737 |
+
busy -= times.idle
|
| 1738 |
+
# Linux: "iowait" is time during which the CPU does not do anything
|
| 1739 |
+
# (waits for IO to complete). On Linux IO wait is *not* accounted
|
| 1740 |
+
# in "idle" time so we subtract it. Htop does the same.
|
| 1741 |
+
# References:
|
| 1742 |
+
# https://github.com/torvalds/linux/blob/
|
| 1743 |
+
# 447976ef4fd09b1be88b316d1a81553f1aa7cd07/kernel/sched/cputime.c#L244
|
| 1744 |
+
busy -= getattr(times, "iowait", 0)
|
| 1745 |
+
return busy
|
| 1746 |
+
|
| 1747 |
+
|
| 1748 |
+
def _cpu_times_deltas(t1, t2):
|
| 1749 |
+
assert t1._fields == t2._fields, (t1, t2)
|
| 1750 |
+
field_deltas = []
|
| 1751 |
+
for field in _psplatform.scputimes._fields:
|
| 1752 |
+
field_delta = getattr(t2, field) - getattr(t1, field)
|
| 1753 |
+
# CPU times are always supposed to increase over time
|
| 1754 |
+
# or at least remain the same and that's because time
|
| 1755 |
+
# cannot go backwards.
|
| 1756 |
+
# Surprisingly sometimes this might not be the case (at
|
| 1757 |
+
# least on Windows and Linux), see:
|
| 1758 |
+
# https://github.com/giampaolo/psutil/issues/392
|
| 1759 |
+
# https://github.com/giampaolo/psutil/issues/645
|
| 1760 |
+
# https://github.com/giampaolo/psutil/issues/1210
|
| 1761 |
+
# Trim negative deltas to zero to ignore decreasing fields.
|
| 1762 |
+
# top does the same. Reference:
|
| 1763 |
+
# https://gitlab.com/procps-ng/procps/blob/v3.3.12/top/top.c#L5063
|
| 1764 |
+
field_delta = max(0, field_delta)
|
| 1765 |
+
field_deltas.append(field_delta)
|
| 1766 |
+
return _psplatform.scputimes(*field_deltas)
|
| 1767 |
+
|
| 1768 |
+
|
| 1769 |
+
def cpu_percent(interval=None, percpu=False):
|
| 1770 |
+
"""Return a float representing the current system-wide CPU
|
| 1771 |
+
utilization as a percentage.
|
| 1772 |
+
|
| 1773 |
+
When *interval* is > 0.0 compares system CPU times elapsed before
|
| 1774 |
+
and after the interval (blocking).
|
| 1775 |
+
|
| 1776 |
+
When *interval* is 0.0 or None compares system CPU times elapsed
|
| 1777 |
+
since last call or module import, returning immediately (non
|
| 1778 |
+
blocking). That means the first time this is called it will
|
| 1779 |
+
return a meaningless 0.0 value which you should ignore.
|
| 1780 |
+
In this case is recommended for accuracy that this function be
|
| 1781 |
+
called with at least 0.1 seconds between calls.
|
| 1782 |
+
|
| 1783 |
+
When *percpu* is True returns a list of floats representing the
|
| 1784 |
+
utilization as a percentage for each CPU.
|
| 1785 |
+
First element of the list refers to first CPU, second element
|
| 1786 |
+
to second CPU and so on.
|
| 1787 |
+
The order of the list is consistent across calls.
|
| 1788 |
+
|
| 1789 |
+
Examples:
|
| 1790 |
+
|
| 1791 |
+
>>> # blocking, system-wide
|
| 1792 |
+
>>> psutil.cpu_percent(interval=1)
|
| 1793 |
+
2.0
|
| 1794 |
+
>>>
|
| 1795 |
+
>>> # blocking, per-cpu
|
| 1796 |
+
>>> psutil.cpu_percent(interval=1, percpu=True)
|
| 1797 |
+
[2.0, 1.0]
|
| 1798 |
+
>>>
|
| 1799 |
+
>>> # non-blocking (percentage since last call)
|
| 1800 |
+
>>> psutil.cpu_percent(interval=None)
|
| 1801 |
+
2.9
|
| 1802 |
+
>>>
|
| 1803 |
+
"""
|
| 1804 |
+
tid = threading.current_thread().ident
|
| 1805 |
+
blocking = interval is not None and interval > 0.0
|
| 1806 |
+
if interval is not None and interval < 0:
|
| 1807 |
+
msg = "interval is not positive (got %r)" % interval
|
| 1808 |
+
raise ValueError(msg)
|
| 1809 |
+
|
| 1810 |
+
def calculate(t1, t2):
|
| 1811 |
+
times_delta = _cpu_times_deltas(t1, t2)
|
| 1812 |
+
all_delta = _cpu_tot_time(times_delta)
|
| 1813 |
+
busy_delta = _cpu_busy_time(times_delta)
|
| 1814 |
+
|
| 1815 |
+
try:
|
| 1816 |
+
busy_perc = (busy_delta / all_delta) * 100
|
| 1817 |
+
except ZeroDivisionError:
|
| 1818 |
+
return 0.0
|
| 1819 |
+
else:
|
| 1820 |
+
return round(busy_perc, 1)
|
| 1821 |
+
|
| 1822 |
+
# system-wide usage
|
| 1823 |
+
if not percpu:
|
| 1824 |
+
if blocking:
|
| 1825 |
+
t1 = cpu_times()
|
| 1826 |
+
time.sleep(interval)
|
| 1827 |
+
else:
|
| 1828 |
+
t1 = _last_cpu_times.get(tid) or cpu_times()
|
| 1829 |
+
_last_cpu_times[tid] = cpu_times()
|
| 1830 |
+
return calculate(t1, _last_cpu_times[tid])
|
| 1831 |
+
# per-cpu usage
|
| 1832 |
+
else:
|
| 1833 |
+
ret = []
|
| 1834 |
+
if blocking:
|
| 1835 |
+
tot1 = cpu_times(percpu=True)
|
| 1836 |
+
time.sleep(interval)
|
| 1837 |
+
else:
|
| 1838 |
+
tot1 = _last_per_cpu_times.get(tid) or cpu_times(percpu=True)
|
| 1839 |
+
_last_per_cpu_times[tid] = cpu_times(percpu=True)
|
| 1840 |
+
for t1, t2 in zip(tot1, _last_per_cpu_times[tid]):
|
| 1841 |
+
ret.append(calculate(t1, t2))
|
| 1842 |
+
return ret
|
| 1843 |
+
|
| 1844 |
+
|
| 1845 |
+
# Use a separate dict for cpu_times_percent(), so it's independent from
|
| 1846 |
+
# cpu_percent() and they can both be used within the same program.
|
| 1847 |
+
_last_cpu_times_2 = _last_cpu_times.copy()
|
| 1848 |
+
_last_per_cpu_times_2 = _last_per_cpu_times.copy()
|
| 1849 |
+
|
| 1850 |
+
|
| 1851 |
+
def cpu_times_percent(interval=None, percpu=False):
|
| 1852 |
+
"""Same as cpu_percent() but provides utilization percentages
|
| 1853 |
+
for each specific CPU time as is returned by cpu_times().
|
| 1854 |
+
For instance, on Linux we'll get:
|
| 1855 |
+
|
| 1856 |
+
>>> cpu_times_percent()
|
| 1857 |
+
cpupercent(user=4.8, nice=0.0, system=4.8, idle=90.5, iowait=0.0,
|
| 1858 |
+
irq=0.0, softirq=0.0, steal=0.0, guest=0.0, guest_nice=0.0)
|
| 1859 |
+
>>>
|
| 1860 |
+
|
| 1861 |
+
*interval* and *percpu* arguments have the same meaning as in
|
| 1862 |
+
cpu_percent().
|
| 1863 |
+
"""
|
| 1864 |
+
tid = threading.current_thread().ident
|
| 1865 |
+
blocking = interval is not None and interval > 0.0
|
| 1866 |
+
if interval is not None and interval < 0:
|
| 1867 |
+
msg = "interval is not positive (got %r)" % interval
|
| 1868 |
+
raise ValueError(msg)
|
| 1869 |
+
|
| 1870 |
+
def calculate(t1, t2):
|
| 1871 |
+
nums = []
|
| 1872 |
+
times_delta = _cpu_times_deltas(t1, t2)
|
| 1873 |
+
all_delta = _cpu_tot_time(times_delta)
|
| 1874 |
+
# "scale" is the value to multiply each delta with to get percentages.
|
| 1875 |
+
# We use "max" to avoid division by zero (if all_delta is 0, then all
|
| 1876 |
+
# fields are 0 so percentages will be 0 too. all_delta cannot be a
|
| 1877 |
+
# fraction because cpu times are integers)
|
| 1878 |
+
scale = 100.0 / max(1, all_delta)
|
| 1879 |
+
for field_delta in times_delta:
|
| 1880 |
+
field_perc = field_delta * scale
|
| 1881 |
+
field_perc = round(field_perc, 1)
|
| 1882 |
+
# make sure we don't return negative values or values over 100%
|
| 1883 |
+
field_perc = min(max(0.0, field_perc), 100.0)
|
| 1884 |
+
nums.append(field_perc)
|
| 1885 |
+
return _psplatform.scputimes(*nums)
|
| 1886 |
+
|
| 1887 |
+
# system-wide usage
|
| 1888 |
+
if not percpu:
|
| 1889 |
+
if blocking:
|
| 1890 |
+
t1 = cpu_times()
|
| 1891 |
+
time.sleep(interval)
|
| 1892 |
+
else:
|
| 1893 |
+
t1 = _last_cpu_times_2.get(tid) or cpu_times()
|
| 1894 |
+
_last_cpu_times_2[tid] = cpu_times()
|
| 1895 |
+
return calculate(t1, _last_cpu_times_2[tid])
|
| 1896 |
+
# per-cpu usage
|
| 1897 |
+
else:
|
| 1898 |
+
ret = []
|
| 1899 |
+
if blocking:
|
| 1900 |
+
tot1 = cpu_times(percpu=True)
|
| 1901 |
+
time.sleep(interval)
|
| 1902 |
+
else:
|
| 1903 |
+
tot1 = _last_per_cpu_times_2.get(tid) or cpu_times(percpu=True)
|
| 1904 |
+
_last_per_cpu_times_2[tid] = cpu_times(percpu=True)
|
| 1905 |
+
for t1, t2 in zip(tot1, _last_per_cpu_times_2[tid]):
|
| 1906 |
+
ret.append(calculate(t1, t2))
|
| 1907 |
+
return ret
|
| 1908 |
+
|
| 1909 |
+
|
| 1910 |
+
def cpu_stats():
|
| 1911 |
+
"""Return CPU statistics."""
|
| 1912 |
+
return _psplatform.cpu_stats()
|
| 1913 |
+
|
| 1914 |
+
|
| 1915 |
+
if hasattr(_psplatform, "cpu_freq"):
|
| 1916 |
+
|
| 1917 |
+
def cpu_freq(percpu=False):
|
| 1918 |
+
"""Return CPU frequency as a namedtuple including current,
|
| 1919 |
+
min and max frequency expressed in Mhz.
|
| 1920 |
+
|
| 1921 |
+
If *percpu* is True and the system supports per-cpu frequency
|
| 1922 |
+
retrieval (Linux only) a list of frequencies is returned for
|
| 1923 |
+
each CPU. If not a list with one element is returned.
|
| 1924 |
+
"""
|
| 1925 |
+
ret = _psplatform.cpu_freq()
|
| 1926 |
+
if percpu:
|
| 1927 |
+
return ret
|
| 1928 |
+
else:
|
| 1929 |
+
num_cpus = float(len(ret))
|
| 1930 |
+
if num_cpus == 0:
|
| 1931 |
+
return None
|
| 1932 |
+
elif num_cpus == 1:
|
| 1933 |
+
return ret[0]
|
| 1934 |
+
else:
|
| 1935 |
+
currs, mins, maxs = 0.0, 0.0, 0.0
|
| 1936 |
+
set_none = False
|
| 1937 |
+
for cpu in ret:
|
| 1938 |
+
currs += cpu.current
|
| 1939 |
+
# On Linux if /proc/cpuinfo is used min/max are set
|
| 1940 |
+
# to None.
|
| 1941 |
+
if LINUX and cpu.min is None:
|
| 1942 |
+
set_none = True
|
| 1943 |
+
continue
|
| 1944 |
+
mins += cpu.min
|
| 1945 |
+
maxs += cpu.max
|
| 1946 |
+
|
| 1947 |
+
current = currs / num_cpus
|
| 1948 |
+
|
| 1949 |
+
if set_none:
|
| 1950 |
+
min_ = max_ = None
|
| 1951 |
+
else:
|
| 1952 |
+
min_ = mins / num_cpus
|
| 1953 |
+
max_ = maxs / num_cpus
|
| 1954 |
+
|
| 1955 |
+
return _common.scpufreq(current, min_, max_)
|
| 1956 |
+
|
| 1957 |
+
__all__.append("cpu_freq")
|
| 1958 |
+
|
| 1959 |
+
|
| 1960 |
+
if hasattr(os, "getloadavg") or hasattr(_psplatform, "getloadavg"):
|
| 1961 |
+
# Perform this hasattr check once on import time to either use the
|
| 1962 |
+
# platform based code or proxy straight from the os module.
|
| 1963 |
+
if hasattr(os, "getloadavg"):
|
| 1964 |
+
getloadavg = os.getloadavg
|
| 1965 |
+
else:
|
| 1966 |
+
getloadavg = _psplatform.getloadavg
|
| 1967 |
+
|
| 1968 |
+
__all__.append("getloadavg")
|
| 1969 |
+
|
| 1970 |
+
|
| 1971 |
+
# =====================================================================
|
| 1972 |
+
# --- system memory related functions
|
| 1973 |
+
# =====================================================================
|
| 1974 |
+
|
| 1975 |
+
|
| 1976 |
+
def virtual_memory():
|
| 1977 |
+
"""Return statistics about system memory usage as a namedtuple
|
| 1978 |
+
including the following fields, expressed in bytes:
|
| 1979 |
+
|
| 1980 |
+
- total:
|
| 1981 |
+
total physical memory available.
|
| 1982 |
+
|
| 1983 |
+
- available:
|
| 1984 |
+
the memory that can be given instantly to processes without the
|
| 1985 |
+
system going into swap.
|
| 1986 |
+
This is calculated by summing different memory values depending
|
| 1987 |
+
on the platform and it is supposed to be used to monitor actual
|
| 1988 |
+
memory usage in a cross platform fashion.
|
| 1989 |
+
|
| 1990 |
+
- percent:
|
| 1991 |
+
the percentage usage calculated as (total - available) / total * 100
|
| 1992 |
+
|
| 1993 |
+
- used:
|
| 1994 |
+
memory used, calculated differently depending on the platform and
|
| 1995 |
+
designed for informational purposes only:
|
| 1996 |
+
macOS: active + wired
|
| 1997 |
+
BSD: active + wired + cached
|
| 1998 |
+
Linux: total - free
|
| 1999 |
+
|
| 2000 |
+
- free:
|
| 2001 |
+
memory not being used at all (zeroed) that is readily available;
|
| 2002 |
+
note that this doesn't reflect the actual memory available
|
| 2003 |
+
(use 'available' instead)
|
| 2004 |
+
|
| 2005 |
+
Platform-specific fields:
|
| 2006 |
+
|
| 2007 |
+
- active (UNIX):
|
| 2008 |
+
memory currently in use or very recently used, and so it is in RAM.
|
| 2009 |
+
|
| 2010 |
+
- inactive (UNIX):
|
| 2011 |
+
memory that is marked as not used.
|
| 2012 |
+
|
| 2013 |
+
- buffers (BSD, Linux):
|
| 2014 |
+
cache for things like file system metadata.
|
| 2015 |
+
|
| 2016 |
+
- cached (BSD, macOS):
|
| 2017 |
+
cache for various things.
|
| 2018 |
+
|
| 2019 |
+
- wired (macOS, BSD):
|
| 2020 |
+
memory that is marked to always stay in RAM. It is never moved to disk.
|
| 2021 |
+
|
| 2022 |
+
- shared (BSD):
|
| 2023 |
+
memory that may be simultaneously accessed by multiple processes.
|
| 2024 |
+
|
| 2025 |
+
The sum of 'used' and 'available' does not necessarily equal total.
|
| 2026 |
+
On Windows 'available' and 'free' are the same.
|
| 2027 |
+
"""
|
| 2028 |
+
global _TOTAL_PHYMEM
|
| 2029 |
+
ret = _psplatform.virtual_memory()
|
| 2030 |
+
# cached for later use in Process.memory_percent()
|
| 2031 |
+
_TOTAL_PHYMEM = ret.total
|
| 2032 |
+
return ret
|
| 2033 |
+
|
| 2034 |
+
|
| 2035 |
+
def swap_memory():
|
| 2036 |
+
"""Return system swap memory statistics as a namedtuple including
|
| 2037 |
+
the following fields:
|
| 2038 |
+
|
| 2039 |
+
- total: total swap memory in bytes
|
| 2040 |
+
- used: used swap memory in bytes
|
| 2041 |
+
- free: free swap memory in bytes
|
| 2042 |
+
- percent: the percentage usage
|
| 2043 |
+
- sin: no. of bytes the system has swapped in from disk (cumulative)
|
| 2044 |
+
- sout: no. of bytes the system has swapped out from disk (cumulative)
|
| 2045 |
+
|
| 2046 |
+
'sin' and 'sout' on Windows are meaningless and always set to 0.
|
| 2047 |
+
"""
|
| 2048 |
+
return _psplatform.swap_memory()
|
| 2049 |
+
|
| 2050 |
+
|
| 2051 |
+
# =====================================================================
|
| 2052 |
+
# --- disks/partitions related functions
|
| 2053 |
+
# =====================================================================
|
| 2054 |
+
|
| 2055 |
+
|
| 2056 |
+
def disk_usage(path):
|
| 2057 |
+
"""Return disk usage statistics about the given *path* as a
|
| 2058 |
+
namedtuple including total, used and free space expressed in bytes
|
| 2059 |
+
plus the percentage usage.
|
| 2060 |
+
"""
|
| 2061 |
+
return _psplatform.disk_usage(path)
|
| 2062 |
+
|
| 2063 |
+
|
| 2064 |
+
def disk_partitions(all=False):
|
| 2065 |
+
"""Return mounted partitions as a list of
|
| 2066 |
+
(device, mountpoint, fstype, opts) namedtuple.
|
| 2067 |
+
'opts' field is a raw string separated by commas indicating mount
|
| 2068 |
+
options which may vary depending on the platform.
|
| 2069 |
+
|
| 2070 |
+
If *all* parameter is False return physical devices only and ignore
|
| 2071 |
+
all others.
|
| 2072 |
+
"""
|
| 2073 |
+
return _psplatform.disk_partitions(all)
|
| 2074 |
+
|
| 2075 |
+
|
| 2076 |
+
def disk_io_counters(perdisk=False, nowrap=True):
|
| 2077 |
+
"""Return system disk I/O statistics as a namedtuple including
|
| 2078 |
+
the following fields:
|
| 2079 |
+
|
| 2080 |
+
- read_count: number of reads
|
| 2081 |
+
- write_count: number of writes
|
| 2082 |
+
- read_bytes: number of bytes read
|
| 2083 |
+
- write_bytes: number of bytes written
|
| 2084 |
+
- read_time: time spent reading from disk (in ms)
|
| 2085 |
+
- write_time: time spent writing to disk (in ms)
|
| 2086 |
+
|
| 2087 |
+
Platform specific:
|
| 2088 |
+
|
| 2089 |
+
- busy_time: (Linux, FreeBSD) time spent doing actual I/Os (in ms)
|
| 2090 |
+
- read_merged_count (Linux): number of merged reads
|
| 2091 |
+
- write_merged_count (Linux): number of merged writes
|
| 2092 |
+
|
| 2093 |
+
If *perdisk* is True return the same information for every
|
| 2094 |
+
physical disk installed on the system as a dictionary
|
| 2095 |
+
with partition names as the keys and the namedtuple
|
| 2096 |
+
described above as the values.
|
| 2097 |
+
|
| 2098 |
+
If *nowrap* is True it detects and adjust the numbers which overflow
|
| 2099 |
+
and wrap (restart from 0) and add "old value" to "new value" so that
|
| 2100 |
+
the returned numbers will always be increasing or remain the same,
|
| 2101 |
+
but never decrease.
|
| 2102 |
+
"disk_io_counters.cache_clear()" can be used to invalidate the
|
| 2103 |
+
cache.
|
| 2104 |
+
|
| 2105 |
+
On recent Windows versions 'diskperf -y' command may need to be
|
| 2106 |
+
executed first otherwise this function won't find any disk.
|
| 2107 |
+
"""
|
| 2108 |
+
kwargs = dict(perdisk=perdisk) if LINUX else {}
|
| 2109 |
+
rawdict = _psplatform.disk_io_counters(**kwargs)
|
| 2110 |
+
if not rawdict:
|
| 2111 |
+
return {} if perdisk else None
|
| 2112 |
+
if nowrap:
|
| 2113 |
+
rawdict = _wrap_numbers(rawdict, 'psutil.disk_io_counters')
|
| 2114 |
+
nt = getattr(_psplatform, "sdiskio", _common.sdiskio)
|
| 2115 |
+
if perdisk:
|
| 2116 |
+
for disk, fields in rawdict.items():
|
| 2117 |
+
rawdict[disk] = nt(*fields)
|
| 2118 |
+
return rawdict
|
| 2119 |
+
else:
|
| 2120 |
+
return nt(*(sum(x) for x in zip(*rawdict.values())))
|
| 2121 |
+
|
| 2122 |
+
|
| 2123 |
+
disk_io_counters.cache_clear = functools.partial(
|
| 2124 |
+
_wrap_numbers.cache_clear, 'psutil.disk_io_counters'
|
| 2125 |
+
)
|
| 2126 |
+
disk_io_counters.cache_clear.__doc__ = "Clears nowrap argument cache"
|
| 2127 |
+
|
| 2128 |
+
|
| 2129 |
+
# =====================================================================
|
| 2130 |
+
# --- network related functions
|
| 2131 |
+
# =====================================================================
|
| 2132 |
+
|
| 2133 |
+
|
| 2134 |
+
def net_io_counters(pernic=False, nowrap=True):
|
| 2135 |
+
"""Return network I/O statistics as a namedtuple including
|
| 2136 |
+
the following fields:
|
| 2137 |
+
|
| 2138 |
+
- bytes_sent: number of bytes sent
|
| 2139 |
+
- bytes_recv: number of bytes received
|
| 2140 |
+
- packets_sent: number of packets sent
|
| 2141 |
+
- packets_recv: number of packets received
|
| 2142 |
+
- errin: total number of errors while receiving
|
| 2143 |
+
- errout: total number of errors while sending
|
| 2144 |
+
- dropin: total number of incoming packets which were dropped
|
| 2145 |
+
- dropout: total number of outgoing packets which were dropped
|
| 2146 |
+
(always 0 on macOS and BSD)
|
| 2147 |
+
|
| 2148 |
+
If *pernic* is True return the same information for every
|
| 2149 |
+
network interface installed on the system as a dictionary
|
| 2150 |
+
with network interface names as the keys and the namedtuple
|
| 2151 |
+
described above as the values.
|
| 2152 |
+
|
| 2153 |
+
If *nowrap* is True it detects and adjust the numbers which overflow
|
| 2154 |
+
and wrap (restart from 0) and add "old value" to "new value" so that
|
| 2155 |
+
the returned numbers will always be increasing or remain the same,
|
| 2156 |
+
but never decrease.
|
| 2157 |
+
"net_io_counters.cache_clear()" can be used to invalidate the
|
| 2158 |
+
cache.
|
| 2159 |
+
"""
|
| 2160 |
+
rawdict = _psplatform.net_io_counters()
|
| 2161 |
+
if not rawdict:
|
| 2162 |
+
return {} if pernic else None
|
| 2163 |
+
if nowrap:
|
| 2164 |
+
rawdict = _wrap_numbers(rawdict, 'psutil.net_io_counters')
|
| 2165 |
+
if pernic:
|
| 2166 |
+
for nic, fields in rawdict.items():
|
| 2167 |
+
rawdict[nic] = _common.snetio(*fields)
|
| 2168 |
+
return rawdict
|
| 2169 |
+
else:
|
| 2170 |
+
return _common.snetio(*[sum(x) for x in zip(*rawdict.values())])
|
| 2171 |
+
|
| 2172 |
+
|
| 2173 |
+
net_io_counters.cache_clear = functools.partial(
|
| 2174 |
+
_wrap_numbers.cache_clear, 'psutil.net_io_counters'
|
| 2175 |
+
)
|
| 2176 |
+
net_io_counters.cache_clear.__doc__ = "Clears nowrap argument cache"
|
| 2177 |
+
|
| 2178 |
+
|
| 2179 |
+
def net_connections(kind='inet'):
|
| 2180 |
+
"""Return system-wide socket connections as a list of
|
| 2181 |
+
(fd, family, type, laddr, raddr, status, pid) namedtuples.
|
| 2182 |
+
In case of limited privileges 'fd' and 'pid' may be set to -1
|
| 2183 |
+
and None respectively.
|
| 2184 |
+
The *kind* parameter filters for connections that fit the
|
| 2185 |
+
following criteria:
|
| 2186 |
+
|
| 2187 |
+
+------------+----------------------------------------------------+
|
| 2188 |
+
| Kind Value | Connections using |
|
| 2189 |
+
+------------+----------------------------------------------------+
|
| 2190 |
+
| inet | IPv4 and IPv6 |
|
| 2191 |
+
| inet4 | IPv4 |
|
| 2192 |
+
| inet6 | IPv6 |
|
| 2193 |
+
| tcp | TCP |
|
| 2194 |
+
| tcp4 | TCP over IPv4 |
|
| 2195 |
+
| tcp6 | TCP over IPv6 |
|
| 2196 |
+
| udp | UDP |
|
| 2197 |
+
| udp4 | UDP over IPv4 |
|
| 2198 |
+
| udp6 | UDP over IPv6 |
|
| 2199 |
+
| unix | UNIX socket (both UDP and TCP protocols) |
|
| 2200 |
+
| all | the sum of all the possible families and protocols |
|
| 2201 |
+
+------------+----------------------------------------------------+
|
| 2202 |
+
|
| 2203 |
+
On macOS this function requires root privileges.
|
| 2204 |
+
"""
|
| 2205 |
+
return _psplatform.net_connections(kind)
|
| 2206 |
+
|
| 2207 |
+
|
| 2208 |
+
def net_if_addrs():
|
| 2209 |
+
"""Return the addresses associated to each NIC (network interface
|
| 2210 |
+
card) installed on the system as a dictionary whose keys are the
|
| 2211 |
+
NIC names and value is a list of namedtuples for each address
|
| 2212 |
+
assigned to the NIC. Each namedtuple includes 5 fields:
|
| 2213 |
+
|
| 2214 |
+
- family: can be either socket.AF_INET, socket.AF_INET6 or
|
| 2215 |
+
psutil.AF_LINK, which refers to a MAC address.
|
| 2216 |
+
- address: is the primary address and it is always set.
|
| 2217 |
+
- netmask: and 'broadcast' and 'ptp' may be None.
|
| 2218 |
+
- ptp: stands for "point to point" and references the
|
| 2219 |
+
destination address on a point to point interface
|
| 2220 |
+
(typically a VPN).
|
| 2221 |
+
- broadcast: and *ptp* are mutually exclusive.
|
| 2222 |
+
|
| 2223 |
+
Note: you can have more than one address of the same family
|
| 2224 |
+
associated with each interface.
|
| 2225 |
+
"""
|
| 2226 |
+
has_enums = _PY3
|
| 2227 |
+
if has_enums:
|
| 2228 |
+
import socket
|
| 2229 |
+
rawlist = _psplatform.net_if_addrs()
|
| 2230 |
+
rawlist.sort(key=lambda x: x[1]) # sort by family
|
| 2231 |
+
ret = collections.defaultdict(list)
|
| 2232 |
+
for name, fam, addr, mask, broadcast, ptp in rawlist:
|
| 2233 |
+
if has_enums:
|
| 2234 |
+
try:
|
| 2235 |
+
fam = socket.AddressFamily(fam)
|
| 2236 |
+
except ValueError:
|
| 2237 |
+
if WINDOWS and fam == -1:
|
| 2238 |
+
fam = _psplatform.AF_LINK
|
| 2239 |
+
elif (
|
| 2240 |
+
hasattr(_psplatform, "AF_LINK")
|
| 2241 |
+
and fam == _psplatform.AF_LINK
|
| 2242 |
+
):
|
| 2243 |
+
# Linux defines AF_LINK as an alias for AF_PACKET.
|
| 2244 |
+
# We re-set the family here so that repr(family)
|
| 2245 |
+
# will show AF_LINK rather than AF_PACKET
|
| 2246 |
+
fam = _psplatform.AF_LINK
|
| 2247 |
+
if fam == _psplatform.AF_LINK:
|
| 2248 |
+
# The underlying C function may return an incomplete MAC
|
| 2249 |
+
# address in which case we fill it with null bytes, see:
|
| 2250 |
+
# https://github.com/giampaolo/psutil/issues/786
|
| 2251 |
+
separator = ":" if POSIX else "-"
|
| 2252 |
+
while addr.count(separator) < 5:
|
| 2253 |
+
addr += "%s00" % separator
|
| 2254 |
+
ret[name].append(_common.snicaddr(fam, addr, mask, broadcast, ptp))
|
| 2255 |
+
return dict(ret)
|
| 2256 |
+
|
| 2257 |
+
|
| 2258 |
+
def net_if_stats():
|
| 2259 |
+
"""Return information about each NIC (network interface card)
|
| 2260 |
+
installed on the system as a dictionary whose keys are the
|
| 2261 |
+
NIC names and value is a namedtuple with the following fields:
|
| 2262 |
+
|
| 2263 |
+
- isup: whether the interface is up (bool)
|
| 2264 |
+
- duplex: can be either NIC_DUPLEX_FULL, NIC_DUPLEX_HALF or
|
| 2265 |
+
NIC_DUPLEX_UNKNOWN
|
| 2266 |
+
- speed: the NIC speed expressed in mega bits (MB); if it can't
|
| 2267 |
+
be determined (e.g. 'localhost') it will be set to 0.
|
| 2268 |
+
- mtu: the maximum transmission unit expressed in bytes.
|
| 2269 |
+
"""
|
| 2270 |
+
return _psplatform.net_if_stats()
|
| 2271 |
+
|
| 2272 |
+
|
| 2273 |
+
# =====================================================================
|
| 2274 |
+
# --- sensors
|
| 2275 |
+
# =====================================================================
|
| 2276 |
+
|
| 2277 |
+
|
| 2278 |
+
# Linux, macOS
|
| 2279 |
+
if hasattr(_psplatform, "sensors_temperatures"):
|
| 2280 |
+
|
| 2281 |
+
def sensors_temperatures(fahrenheit=False):
|
| 2282 |
+
"""Return hardware temperatures. Each entry is a namedtuple
|
| 2283 |
+
representing a certain hardware sensor (it may be a CPU, an
|
| 2284 |
+
hard disk or something else, depending on the OS and its
|
| 2285 |
+
configuration).
|
| 2286 |
+
All temperatures are expressed in celsius unless *fahrenheit*
|
| 2287 |
+
is set to True.
|
| 2288 |
+
"""
|
| 2289 |
+
|
| 2290 |
+
def convert(n):
|
| 2291 |
+
if n is not None:
|
| 2292 |
+
return (float(n) * 9 / 5) + 32 if fahrenheit else n
|
| 2293 |
+
|
| 2294 |
+
ret = collections.defaultdict(list)
|
| 2295 |
+
rawdict = _psplatform.sensors_temperatures()
|
| 2296 |
+
|
| 2297 |
+
for name, values in rawdict.items():
|
| 2298 |
+
while values:
|
| 2299 |
+
label, current, high, critical = values.pop(0)
|
| 2300 |
+
current = convert(current)
|
| 2301 |
+
high = convert(high)
|
| 2302 |
+
critical = convert(critical)
|
| 2303 |
+
|
| 2304 |
+
if high and not critical:
|
| 2305 |
+
critical = high
|
| 2306 |
+
elif critical and not high:
|
| 2307 |
+
high = critical
|
| 2308 |
+
|
| 2309 |
+
ret[name].append(
|
| 2310 |
+
_common.shwtemp(label, current, high, critical)
|
| 2311 |
+
)
|
| 2312 |
+
|
| 2313 |
+
return dict(ret)
|
| 2314 |
+
|
| 2315 |
+
__all__.append("sensors_temperatures")
|
| 2316 |
+
|
| 2317 |
+
|
| 2318 |
+
# Linux
|
| 2319 |
+
if hasattr(_psplatform, "sensors_fans"):
|
| 2320 |
+
|
| 2321 |
+
def sensors_fans():
|
| 2322 |
+
"""Return fans speed. Each entry is a namedtuple
|
| 2323 |
+
representing a certain hardware sensor.
|
| 2324 |
+
All speed are expressed in RPM (rounds per minute).
|
| 2325 |
+
"""
|
| 2326 |
+
return _psplatform.sensors_fans()
|
| 2327 |
+
|
| 2328 |
+
__all__.append("sensors_fans")
|
| 2329 |
+
|
| 2330 |
+
|
| 2331 |
+
# Linux, Windows, FreeBSD, macOS
|
| 2332 |
+
if hasattr(_psplatform, "sensors_battery"):
|
| 2333 |
+
|
| 2334 |
+
def sensors_battery():
|
| 2335 |
+
"""Return battery information. If no battery is installed
|
| 2336 |
+
returns None.
|
| 2337 |
+
|
| 2338 |
+
- percent: battery power left as a percentage.
|
| 2339 |
+
- secsleft: a rough approximation of how many seconds are left
|
| 2340 |
+
before the battery runs out of power. May be
|
| 2341 |
+
POWER_TIME_UNLIMITED or POWER_TIME_UNLIMITED.
|
| 2342 |
+
- power_plugged: True if the AC power cable is connected.
|
| 2343 |
+
"""
|
| 2344 |
+
return _psplatform.sensors_battery()
|
| 2345 |
+
|
| 2346 |
+
__all__.append("sensors_battery")
|
| 2347 |
+
|
| 2348 |
+
|
| 2349 |
+
# =====================================================================
|
| 2350 |
+
# --- other system related functions
|
| 2351 |
+
# =====================================================================
|
| 2352 |
+
|
| 2353 |
+
|
| 2354 |
+
def boot_time():
|
| 2355 |
+
"""Return the system boot time expressed in seconds since the epoch."""
|
| 2356 |
+
# Note: we are not caching this because it is subject to
|
| 2357 |
+
# system clock updates.
|
| 2358 |
+
return _psplatform.boot_time()
|
| 2359 |
+
|
| 2360 |
+
|
| 2361 |
+
def users():
|
| 2362 |
+
"""Return users currently connected on the system as a list of
|
| 2363 |
+
namedtuples including the following fields.
|
| 2364 |
+
|
| 2365 |
+
- user: the name of the user
|
| 2366 |
+
- terminal: the tty or pseudo-tty associated with the user, if any.
|
| 2367 |
+
- host: the host name associated with the entry, if any.
|
| 2368 |
+
- started: the creation time as a floating point number expressed in
|
| 2369 |
+
seconds since the epoch.
|
| 2370 |
+
"""
|
| 2371 |
+
return _psplatform.users()
|
| 2372 |
+
|
| 2373 |
+
|
| 2374 |
+
# =====================================================================
|
| 2375 |
+
# --- Windows services
|
| 2376 |
+
# =====================================================================
|
| 2377 |
+
|
| 2378 |
+
|
| 2379 |
+
if WINDOWS:
|
| 2380 |
+
|
| 2381 |
+
def win_service_iter():
|
| 2382 |
+
"""Return a generator yielding a WindowsService instance for all
|
| 2383 |
+
Windows services installed.
|
| 2384 |
+
"""
|
| 2385 |
+
return _psplatform.win_service_iter()
|
| 2386 |
+
|
| 2387 |
+
def win_service_get(name):
|
| 2388 |
+
"""Get a Windows service by *name*.
|
| 2389 |
+
Raise NoSuchProcess if no service with such name exists.
|
| 2390 |
+
"""
|
| 2391 |
+
return _psplatform.win_service_get(name)
|
| 2392 |
+
|
| 2393 |
+
|
| 2394 |
+
# =====================================================================
|
| 2395 |
+
|
| 2396 |
+
|
| 2397 |
+
def _set_debug(value):
|
| 2398 |
+
"""Enable or disable PSUTIL_DEBUG option, which prints debugging
|
| 2399 |
+
messages to stderr.
|
| 2400 |
+
"""
|
| 2401 |
+
import psutil._common
|
| 2402 |
+
|
| 2403 |
+
psutil._common.PSUTIL_DEBUG = bool(value)
|
| 2404 |
+
_psplatform.cext.set_debug(bool(value))
|
| 2405 |
+
|
| 2406 |
+
|
| 2407 |
+
def test(): # pragma: no cover
|
| 2408 |
+
from ._common import bytes2human
|
| 2409 |
+
from ._compat import get_terminal_size
|
| 2410 |
+
|
| 2411 |
+
today_day = datetime.date.today()
|
| 2412 |
+
# fmt: off
|
| 2413 |
+
templ = "%-10s %5s %5s %7s %7s %5s %6s %6s %6s %s"
|
| 2414 |
+
attrs = ['pid', 'memory_percent', 'name', 'cmdline', 'cpu_times',
|
| 2415 |
+
'create_time', 'memory_info', 'status', 'nice', 'username']
|
| 2416 |
+
print(templ % ("USER", "PID", "%MEM", "VSZ", "RSS", "NICE", # NOQA
|
| 2417 |
+
"STATUS", "START", "TIME", "CMDLINE"))
|
| 2418 |
+
# fmt: on
|
| 2419 |
+
for p in process_iter(attrs, ad_value=None):
|
| 2420 |
+
if p.info['create_time']:
|
| 2421 |
+
ctime = datetime.datetime.fromtimestamp(p.info['create_time'])
|
| 2422 |
+
if ctime.date() == today_day:
|
| 2423 |
+
ctime = ctime.strftime("%H:%M")
|
| 2424 |
+
else:
|
| 2425 |
+
ctime = ctime.strftime("%b%d")
|
| 2426 |
+
else:
|
| 2427 |
+
ctime = ''
|
| 2428 |
+
if p.info['cpu_times']:
|
| 2429 |
+
cputime = time.strftime(
|
| 2430 |
+
"%M:%S", time.localtime(sum(p.info['cpu_times']))
|
| 2431 |
+
)
|
| 2432 |
+
else:
|
| 2433 |
+
cputime = ''
|
| 2434 |
+
|
| 2435 |
+
user = p.info['username'] or ''
|
| 2436 |
+
if not user and POSIX:
|
| 2437 |
+
try:
|
| 2438 |
+
user = p.uids()[0]
|
| 2439 |
+
except Error:
|
| 2440 |
+
pass
|
| 2441 |
+
if user and WINDOWS and '\\' in user:
|
| 2442 |
+
user = user.split('\\')[1]
|
| 2443 |
+
user = user[:9]
|
| 2444 |
+
vms = (
|
| 2445 |
+
bytes2human(p.info['memory_info'].vms)
|
| 2446 |
+
if p.info['memory_info'] is not None
|
| 2447 |
+
else ''
|
| 2448 |
+
)
|
| 2449 |
+
rss = (
|
| 2450 |
+
bytes2human(p.info['memory_info'].rss)
|
| 2451 |
+
if p.info['memory_info'] is not None
|
| 2452 |
+
else ''
|
| 2453 |
+
)
|
| 2454 |
+
memp = (
|
| 2455 |
+
round(p.info['memory_percent'], 1)
|
| 2456 |
+
if p.info['memory_percent'] is not None
|
| 2457 |
+
else ''
|
| 2458 |
+
)
|
| 2459 |
+
nice = int(p.info['nice']) if p.info['nice'] else ''
|
| 2460 |
+
if p.info['cmdline']:
|
| 2461 |
+
cmdline = ' '.join(p.info['cmdline'])
|
| 2462 |
+
else:
|
| 2463 |
+
cmdline = p.info['name']
|
| 2464 |
+
status = p.info['status'][:5] if p.info['status'] else ''
|
| 2465 |
+
|
| 2466 |
+
line = templ % (
|
| 2467 |
+
user[:10],
|
| 2468 |
+
p.info['pid'],
|
| 2469 |
+
memp,
|
| 2470 |
+
vms,
|
| 2471 |
+
rss,
|
| 2472 |
+
nice,
|
| 2473 |
+
status,
|
| 2474 |
+
ctime,
|
| 2475 |
+
cputime,
|
| 2476 |
+
cmdline,
|
| 2477 |
+
)
|
| 2478 |
+
print(line[: get_terminal_size()[0]]) # NOQA
|
| 2479 |
+
|
| 2480 |
+
|
| 2481 |
+
del memoize_when_activated, division
|
| 2482 |
+
if sys.version_info[0] < 3:
|
| 2483 |
+
del num, x # noqa
|
| 2484 |
+
|
| 2485 |
+
if __name__ == "__main__":
|
| 2486 |
+
test()
|
.venv/lib/python3.11/site-packages/ray/thirdparty_files/psutil/__pycache__/__init__.cpython-311.pyc
ADDED
|
Binary file (98.4 kB). View file
|
|
|
.venv/lib/python3.11/site-packages/ray/thirdparty_files/psutil/__pycache__/_common.cpython-311.pyc
ADDED
|
Binary file (38.2 kB). View file
|
|
|
.venv/lib/python3.11/site-packages/ray/thirdparty_files/psutil/__pycache__/_compat.cpython-311.pyc
ADDED
|
Binary file (20.9 kB). View file
|
|
|
.venv/lib/python3.11/site-packages/ray/thirdparty_files/psutil/__pycache__/_psaix.cpython-311.pyc
ADDED
|
Binary file (26.9 kB). View file
|
|
|
.venv/lib/python3.11/site-packages/ray/thirdparty_files/psutil/__pycache__/_psbsd.cpython-311.pyc
ADDED
|
Binary file (38.2 kB). View file
|
|
|
.venv/lib/python3.11/site-packages/ray/thirdparty_files/psutil/__pycache__/_psosx.cpython-311.pyc
ADDED
|
Binary file (23.3 kB). View file
|
|
|
.venv/lib/python3.11/site-packages/ray/thirdparty_files/psutil/__pycache__/_psposix.cpython-311.pyc
ADDED
|
Binary file (7.45 kB). View file
|
|
|
.venv/lib/python3.11/site-packages/ray/thirdparty_files/psutil/__pycache__/_pssunos.cpython-311.pyc
ADDED
|
Binary file (33.1 kB). View file
|
|
|
.venv/lib/python3.11/site-packages/ray/thirdparty_files/psutil/__pycache__/_pswindows.cpython-311.pyc
ADDED
|
Binary file (50 kB). View file
|
|
|
.venv/lib/python3.11/site-packages/ray/thirdparty_files/psutil/_common.py
ADDED
|
@@ -0,0 +1,994 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) 2009, Giampaolo Rodola'. All rights reserved.
|
| 2 |
+
# Use of this source code is governed by a BSD-style license that can be
|
| 3 |
+
# found in the LICENSE file.
|
| 4 |
+
|
| 5 |
+
"""Common objects shared by __init__.py and _ps*.py modules."""
|
| 6 |
+
|
| 7 |
+
# Note: this module is imported by setup.py so it should not import
|
| 8 |
+
# psutil or third-party modules.
|
| 9 |
+
|
| 10 |
+
from __future__ import division
|
| 11 |
+
from __future__ import print_function
|
| 12 |
+
|
| 13 |
+
import collections
|
| 14 |
+
import contextlib
|
| 15 |
+
import errno
|
| 16 |
+
import functools
|
| 17 |
+
import os
|
| 18 |
+
import socket
|
| 19 |
+
import stat
|
| 20 |
+
import sys
|
| 21 |
+
import threading
|
| 22 |
+
import warnings
|
| 23 |
+
from collections import namedtuple
|
| 24 |
+
from socket import AF_INET
|
| 25 |
+
from socket import SOCK_DGRAM
|
| 26 |
+
from socket import SOCK_STREAM
|
| 27 |
+
|
| 28 |
+
|
| 29 |
+
try:
|
| 30 |
+
from socket import AF_INET6
|
| 31 |
+
except ImportError:
|
| 32 |
+
AF_INET6 = None
|
| 33 |
+
try:
|
| 34 |
+
from socket import AF_UNIX
|
| 35 |
+
except ImportError:
|
| 36 |
+
AF_UNIX = None
|
| 37 |
+
|
| 38 |
+
|
| 39 |
+
# can't take it from _common.py as this script is imported by setup.py
|
| 40 |
+
PY3 = sys.version_info[0] >= 3
|
| 41 |
+
if PY3:
|
| 42 |
+
import enum
|
| 43 |
+
else:
|
| 44 |
+
enum = None
|
| 45 |
+
|
| 46 |
+
|
| 47 |
+
PSUTIL_DEBUG = bool(os.getenv('PSUTIL_DEBUG'))
|
| 48 |
+
_DEFAULT = object()
|
| 49 |
+
|
| 50 |
+
# fmt: off
|
| 51 |
+
__all__ = [
|
| 52 |
+
# OS constants
|
| 53 |
+
'FREEBSD', 'BSD', 'LINUX', 'NETBSD', 'OPENBSD', 'MACOS', 'OSX', 'POSIX',
|
| 54 |
+
'SUNOS', 'WINDOWS',
|
| 55 |
+
# connection constants
|
| 56 |
+
'CONN_CLOSE', 'CONN_CLOSE_WAIT', 'CONN_CLOSING', 'CONN_ESTABLISHED',
|
| 57 |
+
'CONN_FIN_WAIT1', 'CONN_FIN_WAIT2', 'CONN_LAST_ACK', 'CONN_LISTEN',
|
| 58 |
+
'CONN_NONE', 'CONN_SYN_RECV', 'CONN_SYN_SENT', 'CONN_TIME_WAIT',
|
| 59 |
+
# net constants
|
| 60 |
+
'NIC_DUPLEX_FULL', 'NIC_DUPLEX_HALF', 'NIC_DUPLEX_UNKNOWN',
|
| 61 |
+
# process status constants
|
| 62 |
+
'STATUS_DEAD', 'STATUS_DISK_SLEEP', 'STATUS_IDLE', 'STATUS_LOCKED',
|
| 63 |
+
'STATUS_RUNNING', 'STATUS_SLEEPING', 'STATUS_STOPPED', 'STATUS_SUSPENDED',
|
| 64 |
+
'STATUS_TRACING_STOP', 'STATUS_WAITING', 'STATUS_WAKE_KILL',
|
| 65 |
+
'STATUS_WAKING', 'STATUS_ZOMBIE', 'STATUS_PARKED',
|
| 66 |
+
# other constants
|
| 67 |
+
'ENCODING', 'ENCODING_ERRS', 'AF_INET6',
|
| 68 |
+
# named tuples
|
| 69 |
+
'pconn', 'pcputimes', 'pctxsw', 'pgids', 'pio', 'pionice', 'popenfile',
|
| 70 |
+
'pthread', 'puids', 'sconn', 'scpustats', 'sdiskio', 'sdiskpart',
|
| 71 |
+
'sdiskusage', 'snetio', 'snicaddr', 'snicstats', 'sswap', 'suser',
|
| 72 |
+
# utility functions
|
| 73 |
+
'conn_tmap', 'deprecated_method', 'isfile_strict', 'memoize',
|
| 74 |
+
'parse_environ_block', 'path_exists_strict', 'usage_percent',
|
| 75 |
+
'supports_ipv6', 'sockfam_to_enum', 'socktype_to_enum', "wrap_numbers",
|
| 76 |
+
'open_text', 'open_binary', 'cat', 'bcat',
|
| 77 |
+
'bytes2human', 'conn_to_ntuple', 'debug',
|
| 78 |
+
# shell utils
|
| 79 |
+
'hilite', 'term_supports_colors', 'print_color',
|
| 80 |
+
]
|
| 81 |
+
# fmt: on
|
| 82 |
+
|
| 83 |
+
|
| 84 |
+
# ===================================================================
|
| 85 |
+
# --- OS constants
|
| 86 |
+
# ===================================================================
|
| 87 |
+
|
| 88 |
+
|
| 89 |
+
POSIX = os.name == "posix"
|
| 90 |
+
WINDOWS = os.name == "nt"
|
| 91 |
+
LINUX = sys.platform.startswith("linux")
|
| 92 |
+
MACOS = sys.platform.startswith("darwin")
|
| 93 |
+
OSX = MACOS # deprecated alias
|
| 94 |
+
FREEBSD = sys.platform.startswith(("freebsd", "midnightbsd"))
|
| 95 |
+
OPENBSD = sys.platform.startswith("openbsd")
|
| 96 |
+
NETBSD = sys.platform.startswith("netbsd")
|
| 97 |
+
BSD = FREEBSD or OPENBSD or NETBSD
|
| 98 |
+
SUNOS = sys.platform.startswith(("sunos", "solaris"))
|
| 99 |
+
AIX = sys.platform.startswith("aix")
|
| 100 |
+
|
| 101 |
+
|
| 102 |
+
# ===================================================================
|
| 103 |
+
# --- API constants
|
| 104 |
+
# ===================================================================
|
| 105 |
+
|
| 106 |
+
|
| 107 |
+
# Process.status()
|
| 108 |
+
STATUS_RUNNING = "running"
|
| 109 |
+
STATUS_SLEEPING = "sleeping"
|
| 110 |
+
STATUS_DISK_SLEEP = "disk-sleep"
|
| 111 |
+
STATUS_STOPPED = "stopped"
|
| 112 |
+
STATUS_TRACING_STOP = "tracing-stop"
|
| 113 |
+
STATUS_ZOMBIE = "zombie"
|
| 114 |
+
STATUS_DEAD = "dead"
|
| 115 |
+
STATUS_WAKE_KILL = "wake-kill"
|
| 116 |
+
STATUS_WAKING = "waking"
|
| 117 |
+
STATUS_IDLE = "idle" # Linux, macOS, FreeBSD
|
| 118 |
+
STATUS_LOCKED = "locked" # FreeBSD
|
| 119 |
+
STATUS_WAITING = "waiting" # FreeBSD
|
| 120 |
+
STATUS_SUSPENDED = "suspended" # NetBSD
|
| 121 |
+
STATUS_PARKED = "parked" # Linux
|
| 122 |
+
|
| 123 |
+
# Process.net_connections() and psutil.net_connections()
|
| 124 |
+
CONN_ESTABLISHED = "ESTABLISHED"
|
| 125 |
+
CONN_SYN_SENT = "SYN_SENT"
|
| 126 |
+
CONN_SYN_RECV = "SYN_RECV"
|
| 127 |
+
CONN_FIN_WAIT1 = "FIN_WAIT1"
|
| 128 |
+
CONN_FIN_WAIT2 = "FIN_WAIT2"
|
| 129 |
+
CONN_TIME_WAIT = "TIME_WAIT"
|
| 130 |
+
CONN_CLOSE = "CLOSE"
|
| 131 |
+
CONN_CLOSE_WAIT = "CLOSE_WAIT"
|
| 132 |
+
CONN_LAST_ACK = "LAST_ACK"
|
| 133 |
+
CONN_LISTEN = "LISTEN"
|
| 134 |
+
CONN_CLOSING = "CLOSING"
|
| 135 |
+
CONN_NONE = "NONE"
|
| 136 |
+
|
| 137 |
+
# net_if_stats()
|
| 138 |
+
if enum is None:
|
| 139 |
+
NIC_DUPLEX_FULL = 2
|
| 140 |
+
NIC_DUPLEX_HALF = 1
|
| 141 |
+
NIC_DUPLEX_UNKNOWN = 0
|
| 142 |
+
else:
|
| 143 |
+
|
| 144 |
+
class NicDuplex(enum.IntEnum):
|
| 145 |
+
NIC_DUPLEX_FULL = 2
|
| 146 |
+
NIC_DUPLEX_HALF = 1
|
| 147 |
+
NIC_DUPLEX_UNKNOWN = 0
|
| 148 |
+
|
| 149 |
+
globals().update(NicDuplex.__members__)
|
| 150 |
+
|
| 151 |
+
# sensors_battery()
|
| 152 |
+
if enum is None:
|
| 153 |
+
POWER_TIME_UNKNOWN = -1
|
| 154 |
+
POWER_TIME_UNLIMITED = -2
|
| 155 |
+
else:
|
| 156 |
+
|
| 157 |
+
class BatteryTime(enum.IntEnum):
|
| 158 |
+
POWER_TIME_UNKNOWN = -1
|
| 159 |
+
POWER_TIME_UNLIMITED = -2
|
| 160 |
+
|
| 161 |
+
globals().update(BatteryTime.__members__)
|
| 162 |
+
|
| 163 |
+
# --- others
|
| 164 |
+
|
| 165 |
+
ENCODING = sys.getfilesystemencoding()
|
| 166 |
+
if not PY3:
|
| 167 |
+
ENCODING_ERRS = "replace"
|
| 168 |
+
else:
|
| 169 |
+
try:
|
| 170 |
+
ENCODING_ERRS = sys.getfilesystemencodeerrors() # py 3.6
|
| 171 |
+
except AttributeError:
|
| 172 |
+
ENCODING_ERRS = "surrogateescape" if POSIX else "replace"
|
| 173 |
+
|
| 174 |
+
|
| 175 |
+
# ===================================================================
|
| 176 |
+
# --- namedtuples
|
| 177 |
+
# ===================================================================
|
| 178 |
+
|
| 179 |
+
# --- for system functions
|
| 180 |
+
|
| 181 |
+
# fmt: off
|
| 182 |
+
# psutil.swap_memory()
|
| 183 |
+
sswap = namedtuple('sswap', ['total', 'used', 'free', 'percent', 'sin',
|
| 184 |
+
'sout'])
|
| 185 |
+
# psutil.disk_usage()
|
| 186 |
+
sdiskusage = namedtuple('sdiskusage', ['total', 'used', 'free', 'percent'])
|
| 187 |
+
# psutil.disk_io_counters()
|
| 188 |
+
sdiskio = namedtuple('sdiskio', ['read_count', 'write_count',
|
| 189 |
+
'read_bytes', 'write_bytes',
|
| 190 |
+
'read_time', 'write_time'])
|
| 191 |
+
# psutil.disk_partitions()
|
| 192 |
+
sdiskpart = namedtuple('sdiskpart', ['device', 'mountpoint', 'fstype', 'opts'])
|
| 193 |
+
# psutil.net_io_counters()
|
| 194 |
+
snetio = namedtuple('snetio', ['bytes_sent', 'bytes_recv',
|
| 195 |
+
'packets_sent', 'packets_recv',
|
| 196 |
+
'errin', 'errout',
|
| 197 |
+
'dropin', 'dropout'])
|
| 198 |
+
# psutil.users()
|
| 199 |
+
suser = namedtuple('suser', ['name', 'terminal', 'host', 'started', 'pid'])
|
| 200 |
+
# psutil.net_connections()
|
| 201 |
+
sconn = namedtuple('sconn', ['fd', 'family', 'type', 'laddr', 'raddr',
|
| 202 |
+
'status', 'pid'])
|
| 203 |
+
# psutil.net_if_addrs()
|
| 204 |
+
snicaddr = namedtuple('snicaddr',
|
| 205 |
+
['family', 'address', 'netmask', 'broadcast', 'ptp'])
|
| 206 |
+
# psutil.net_if_stats()
|
| 207 |
+
snicstats = namedtuple('snicstats',
|
| 208 |
+
['isup', 'duplex', 'speed', 'mtu', 'flags'])
|
| 209 |
+
# psutil.cpu_stats()
|
| 210 |
+
scpustats = namedtuple(
|
| 211 |
+
'scpustats', ['ctx_switches', 'interrupts', 'soft_interrupts', 'syscalls'])
|
| 212 |
+
# psutil.cpu_freq()
|
| 213 |
+
scpufreq = namedtuple('scpufreq', ['current', 'min', 'max'])
|
| 214 |
+
# psutil.sensors_temperatures()
|
| 215 |
+
shwtemp = namedtuple(
|
| 216 |
+
'shwtemp', ['label', 'current', 'high', 'critical'])
|
| 217 |
+
# psutil.sensors_battery()
|
| 218 |
+
sbattery = namedtuple('sbattery', ['percent', 'secsleft', 'power_plugged'])
|
| 219 |
+
# psutil.sensors_fans()
|
| 220 |
+
sfan = namedtuple('sfan', ['label', 'current'])
|
| 221 |
+
# fmt: on
|
| 222 |
+
|
| 223 |
+
# --- for Process methods
|
| 224 |
+
|
| 225 |
+
# psutil.Process.cpu_times()
|
| 226 |
+
pcputimes = namedtuple(
|
| 227 |
+
'pcputimes', ['user', 'system', 'children_user', 'children_system']
|
| 228 |
+
)
|
| 229 |
+
# psutil.Process.open_files()
|
| 230 |
+
popenfile = namedtuple('popenfile', ['path', 'fd'])
|
| 231 |
+
# psutil.Process.threads()
|
| 232 |
+
pthread = namedtuple('pthread', ['id', 'user_time', 'system_time'])
|
| 233 |
+
# psutil.Process.uids()
|
| 234 |
+
puids = namedtuple('puids', ['real', 'effective', 'saved'])
|
| 235 |
+
# psutil.Process.gids()
|
| 236 |
+
pgids = namedtuple('pgids', ['real', 'effective', 'saved'])
|
| 237 |
+
# psutil.Process.io_counters()
|
| 238 |
+
pio = namedtuple(
|
| 239 |
+
'pio', ['read_count', 'write_count', 'read_bytes', 'write_bytes']
|
| 240 |
+
)
|
| 241 |
+
# psutil.Process.ionice()
|
| 242 |
+
pionice = namedtuple('pionice', ['ioclass', 'value'])
|
| 243 |
+
# psutil.Process.ctx_switches()
|
| 244 |
+
pctxsw = namedtuple('pctxsw', ['voluntary', 'involuntary'])
|
| 245 |
+
# psutil.Process.net_connections()
|
| 246 |
+
pconn = namedtuple(
|
| 247 |
+
'pconn', ['fd', 'family', 'type', 'laddr', 'raddr', 'status']
|
| 248 |
+
)
|
| 249 |
+
|
| 250 |
+
# psutil.net_connections() and psutil.Process.net_connections()
|
| 251 |
+
addr = namedtuple('addr', ['ip', 'port'])
|
| 252 |
+
|
| 253 |
+
|
| 254 |
+
# ===================================================================
|
| 255 |
+
# --- Process.net_connections() 'kind' parameter mapping
|
| 256 |
+
# ===================================================================
|
| 257 |
+
|
| 258 |
+
|
| 259 |
+
conn_tmap = {
|
| 260 |
+
"all": ([AF_INET, AF_INET6, AF_UNIX], [SOCK_STREAM, SOCK_DGRAM]),
|
| 261 |
+
"tcp": ([AF_INET, AF_INET6], [SOCK_STREAM]),
|
| 262 |
+
"tcp4": ([AF_INET], [SOCK_STREAM]),
|
| 263 |
+
"udp": ([AF_INET, AF_INET6], [SOCK_DGRAM]),
|
| 264 |
+
"udp4": ([AF_INET], [SOCK_DGRAM]),
|
| 265 |
+
"inet": ([AF_INET, AF_INET6], [SOCK_STREAM, SOCK_DGRAM]),
|
| 266 |
+
"inet4": ([AF_INET], [SOCK_STREAM, SOCK_DGRAM]),
|
| 267 |
+
"inet6": ([AF_INET6], [SOCK_STREAM, SOCK_DGRAM]),
|
| 268 |
+
}
|
| 269 |
+
|
| 270 |
+
if AF_INET6 is not None:
|
| 271 |
+
conn_tmap.update({
|
| 272 |
+
"tcp6": ([AF_INET6], [SOCK_STREAM]),
|
| 273 |
+
"udp6": ([AF_INET6], [SOCK_DGRAM]),
|
| 274 |
+
})
|
| 275 |
+
|
| 276 |
+
if AF_UNIX is not None:
|
| 277 |
+
conn_tmap.update({"unix": ([AF_UNIX], [SOCK_STREAM, SOCK_DGRAM])})
|
| 278 |
+
|
| 279 |
+
|
| 280 |
+
# =====================================================================
|
| 281 |
+
# --- Exceptions
|
| 282 |
+
# =====================================================================
|
| 283 |
+
|
| 284 |
+
|
| 285 |
+
class Error(Exception):
|
| 286 |
+
"""Base exception class. All other psutil exceptions inherit
|
| 287 |
+
from this one.
|
| 288 |
+
"""
|
| 289 |
+
|
| 290 |
+
__module__ = 'psutil'
|
| 291 |
+
|
| 292 |
+
def _infodict(self, attrs):
|
| 293 |
+
info = collections.OrderedDict()
|
| 294 |
+
for name in attrs:
|
| 295 |
+
value = getattr(self, name, None)
|
| 296 |
+
if value: # noqa
|
| 297 |
+
info[name] = value
|
| 298 |
+
elif name == "pid" and value == 0:
|
| 299 |
+
info[name] = value
|
| 300 |
+
return info
|
| 301 |
+
|
| 302 |
+
def __str__(self):
|
| 303 |
+
# invoked on `raise Error`
|
| 304 |
+
info = self._infodict(("pid", "ppid", "name"))
|
| 305 |
+
if info:
|
| 306 |
+
details = "(%s)" % ", ".join(
|
| 307 |
+
["%s=%r" % (k, v) for k, v in info.items()]
|
| 308 |
+
)
|
| 309 |
+
else:
|
| 310 |
+
details = None
|
| 311 |
+
return " ".join([x for x in (getattr(self, "msg", ""), details) if x])
|
| 312 |
+
|
| 313 |
+
def __repr__(self):
|
| 314 |
+
# invoked on `repr(Error)`
|
| 315 |
+
info = self._infodict(("pid", "ppid", "name", "seconds", "msg"))
|
| 316 |
+
details = ", ".join(["%s=%r" % (k, v) for k, v in info.items()])
|
| 317 |
+
return "psutil.%s(%s)" % (self.__class__.__name__, details)
|
| 318 |
+
|
| 319 |
+
|
| 320 |
+
class NoSuchProcess(Error):
|
| 321 |
+
"""Exception raised when a process with a certain PID doesn't
|
| 322 |
+
or no longer exists.
|
| 323 |
+
"""
|
| 324 |
+
|
| 325 |
+
__module__ = 'psutil'
|
| 326 |
+
|
| 327 |
+
def __init__(self, pid, name=None, msg=None):
|
| 328 |
+
Error.__init__(self)
|
| 329 |
+
self.pid = pid
|
| 330 |
+
self.name = name
|
| 331 |
+
self.msg = msg or "process no longer exists"
|
| 332 |
+
|
| 333 |
+
def __reduce__(self):
|
| 334 |
+
return (self.__class__, (self.pid, self.name, self.msg))
|
| 335 |
+
|
| 336 |
+
|
| 337 |
+
class ZombieProcess(NoSuchProcess):
|
| 338 |
+
"""Exception raised when querying a zombie process. This is
|
| 339 |
+
raised on macOS, BSD and Solaris only, and not always: depending
|
| 340 |
+
on the query the OS may be able to succeed anyway.
|
| 341 |
+
On Linux all zombie processes are querable (hence this is never
|
| 342 |
+
raised). Windows doesn't have zombie processes.
|
| 343 |
+
"""
|
| 344 |
+
|
| 345 |
+
__module__ = 'psutil'
|
| 346 |
+
|
| 347 |
+
def __init__(self, pid, name=None, ppid=None, msg=None):
|
| 348 |
+
NoSuchProcess.__init__(self, pid, name, msg)
|
| 349 |
+
self.ppid = ppid
|
| 350 |
+
self.msg = msg or "PID still exists but it's a zombie"
|
| 351 |
+
|
| 352 |
+
def __reduce__(self):
|
| 353 |
+
return (self.__class__, (self.pid, self.name, self.ppid, self.msg))
|
| 354 |
+
|
| 355 |
+
|
| 356 |
+
class AccessDenied(Error):
|
| 357 |
+
"""Exception raised when permission to perform an action is denied."""
|
| 358 |
+
|
| 359 |
+
__module__ = 'psutil'
|
| 360 |
+
|
| 361 |
+
def __init__(self, pid=None, name=None, msg=None):
|
| 362 |
+
Error.__init__(self)
|
| 363 |
+
self.pid = pid
|
| 364 |
+
self.name = name
|
| 365 |
+
self.msg = msg or ""
|
| 366 |
+
|
| 367 |
+
def __reduce__(self):
|
| 368 |
+
return (self.__class__, (self.pid, self.name, self.msg))
|
| 369 |
+
|
| 370 |
+
|
| 371 |
+
class TimeoutExpired(Error):
|
| 372 |
+
"""Raised on Process.wait(timeout) if timeout expires and process
|
| 373 |
+
is still alive.
|
| 374 |
+
"""
|
| 375 |
+
|
| 376 |
+
__module__ = 'psutil'
|
| 377 |
+
|
| 378 |
+
def __init__(self, seconds, pid=None, name=None):
|
| 379 |
+
Error.__init__(self)
|
| 380 |
+
self.seconds = seconds
|
| 381 |
+
self.pid = pid
|
| 382 |
+
self.name = name
|
| 383 |
+
self.msg = "timeout after %s seconds" % seconds
|
| 384 |
+
|
| 385 |
+
def __reduce__(self):
|
| 386 |
+
return (self.__class__, (self.seconds, self.pid, self.name))
|
| 387 |
+
|
| 388 |
+
|
| 389 |
+
# ===================================================================
|
| 390 |
+
# --- utils
|
| 391 |
+
# ===================================================================
|
| 392 |
+
|
| 393 |
+
|
| 394 |
+
# This should be in _compat.py rather than here, but does not work well
|
| 395 |
+
# with setup.py importing this module via a sys.path trick.
|
| 396 |
+
if PY3:
|
| 397 |
+
if isinstance(__builtins__, dict): # cpython
|
| 398 |
+
exec_ = __builtins__["exec"]
|
| 399 |
+
else: # pypy
|
| 400 |
+
exec_ = getattr(__builtins__, "exec") # noqa
|
| 401 |
+
|
| 402 |
+
exec_("""def raise_from(value, from_value):
|
| 403 |
+
try:
|
| 404 |
+
raise value from from_value
|
| 405 |
+
finally:
|
| 406 |
+
value = None
|
| 407 |
+
""")
|
| 408 |
+
else:
|
| 409 |
+
|
| 410 |
+
def raise_from(value, from_value):
|
| 411 |
+
raise value
|
| 412 |
+
|
| 413 |
+
|
| 414 |
+
def usage_percent(used, total, round_=None):
|
| 415 |
+
"""Calculate percentage usage of 'used' against 'total'."""
|
| 416 |
+
try:
|
| 417 |
+
ret = (float(used) / total) * 100
|
| 418 |
+
except ZeroDivisionError:
|
| 419 |
+
return 0.0
|
| 420 |
+
else:
|
| 421 |
+
if round_ is not None:
|
| 422 |
+
ret = round(ret, round_)
|
| 423 |
+
return ret
|
| 424 |
+
|
| 425 |
+
|
| 426 |
+
def memoize(fun):
|
| 427 |
+
"""A simple memoize decorator for functions supporting (hashable)
|
| 428 |
+
positional arguments.
|
| 429 |
+
It also provides a cache_clear() function for clearing the cache:
|
| 430 |
+
|
| 431 |
+
>>> @memoize
|
| 432 |
+
... def foo()
|
| 433 |
+
... return 1
|
| 434 |
+
...
|
| 435 |
+
>>> foo()
|
| 436 |
+
1
|
| 437 |
+
>>> foo.cache_clear()
|
| 438 |
+
>>>
|
| 439 |
+
|
| 440 |
+
It supports:
|
| 441 |
+
- functions
|
| 442 |
+
- classes (acts as a @singleton)
|
| 443 |
+
- staticmethods
|
| 444 |
+
- classmethods
|
| 445 |
+
|
| 446 |
+
It does NOT support:
|
| 447 |
+
- methods
|
| 448 |
+
"""
|
| 449 |
+
|
| 450 |
+
@functools.wraps(fun)
|
| 451 |
+
def wrapper(*args, **kwargs):
|
| 452 |
+
key = (args, frozenset(sorted(kwargs.items())))
|
| 453 |
+
try:
|
| 454 |
+
return cache[key]
|
| 455 |
+
except KeyError:
|
| 456 |
+
try:
|
| 457 |
+
ret = cache[key] = fun(*args, **kwargs)
|
| 458 |
+
except Exception as err: # noqa: BLE001
|
| 459 |
+
raise raise_from(err, None)
|
| 460 |
+
return ret
|
| 461 |
+
|
| 462 |
+
def cache_clear():
|
| 463 |
+
"""Clear cache."""
|
| 464 |
+
cache.clear()
|
| 465 |
+
|
| 466 |
+
cache = {}
|
| 467 |
+
wrapper.cache_clear = cache_clear
|
| 468 |
+
return wrapper
|
| 469 |
+
|
| 470 |
+
|
| 471 |
+
def memoize_when_activated(fun):
|
| 472 |
+
"""A memoize decorator which is disabled by default. It can be
|
| 473 |
+
activated and deactivated on request.
|
| 474 |
+
For efficiency reasons it can be used only against class methods
|
| 475 |
+
accepting no arguments.
|
| 476 |
+
|
| 477 |
+
>>> class Foo:
|
| 478 |
+
... @memoize
|
| 479 |
+
... def foo()
|
| 480 |
+
... print(1)
|
| 481 |
+
...
|
| 482 |
+
>>> f = Foo()
|
| 483 |
+
>>> # deactivated (default)
|
| 484 |
+
>>> foo()
|
| 485 |
+
1
|
| 486 |
+
>>> foo()
|
| 487 |
+
1
|
| 488 |
+
>>>
|
| 489 |
+
>>> # activated
|
| 490 |
+
>>> foo.cache_activate(self)
|
| 491 |
+
>>> foo()
|
| 492 |
+
1
|
| 493 |
+
>>> foo()
|
| 494 |
+
>>> foo()
|
| 495 |
+
>>>
|
| 496 |
+
"""
|
| 497 |
+
|
| 498 |
+
@functools.wraps(fun)
|
| 499 |
+
def wrapper(self):
|
| 500 |
+
try:
|
| 501 |
+
# case 1: we previously entered oneshot() ctx
|
| 502 |
+
ret = self._cache[fun]
|
| 503 |
+
except AttributeError:
|
| 504 |
+
# case 2: we never entered oneshot() ctx
|
| 505 |
+
try:
|
| 506 |
+
return fun(self)
|
| 507 |
+
except Exception as err: # noqa: BLE001
|
| 508 |
+
raise raise_from(err, None)
|
| 509 |
+
except KeyError:
|
| 510 |
+
# case 3: we entered oneshot() ctx but there's no cache
|
| 511 |
+
# for this entry yet
|
| 512 |
+
try:
|
| 513 |
+
ret = fun(self)
|
| 514 |
+
except Exception as err: # noqa: BLE001
|
| 515 |
+
raise raise_from(err, None)
|
| 516 |
+
try:
|
| 517 |
+
self._cache[fun] = ret
|
| 518 |
+
except AttributeError:
|
| 519 |
+
# multi-threading race condition, see:
|
| 520 |
+
# https://github.com/giampaolo/psutil/issues/1948
|
| 521 |
+
pass
|
| 522 |
+
return ret
|
| 523 |
+
|
| 524 |
+
def cache_activate(proc):
|
| 525 |
+
"""Activate cache. Expects a Process instance. Cache will be
|
| 526 |
+
stored as a "_cache" instance attribute.
|
| 527 |
+
"""
|
| 528 |
+
proc._cache = {}
|
| 529 |
+
|
| 530 |
+
def cache_deactivate(proc):
|
| 531 |
+
"""Deactivate and clear cache."""
|
| 532 |
+
try:
|
| 533 |
+
del proc._cache
|
| 534 |
+
except AttributeError:
|
| 535 |
+
pass
|
| 536 |
+
|
| 537 |
+
wrapper.cache_activate = cache_activate
|
| 538 |
+
wrapper.cache_deactivate = cache_deactivate
|
| 539 |
+
return wrapper
|
| 540 |
+
|
| 541 |
+
|
| 542 |
+
def isfile_strict(path):
|
| 543 |
+
"""Same as os.path.isfile() but does not swallow EACCES / EPERM
|
| 544 |
+
exceptions, see:
|
| 545 |
+
http://mail.python.org/pipermail/python-dev/2012-June/120787.html.
|
| 546 |
+
"""
|
| 547 |
+
try:
|
| 548 |
+
st = os.stat(path)
|
| 549 |
+
except OSError as err:
|
| 550 |
+
if err.errno in {errno.EPERM, errno.EACCES}:
|
| 551 |
+
raise
|
| 552 |
+
return False
|
| 553 |
+
else:
|
| 554 |
+
return stat.S_ISREG(st.st_mode)
|
| 555 |
+
|
| 556 |
+
|
| 557 |
+
def path_exists_strict(path):
|
| 558 |
+
"""Same as os.path.exists() but does not swallow EACCES / EPERM
|
| 559 |
+
exceptions. See:
|
| 560 |
+
http://mail.python.org/pipermail/python-dev/2012-June/120787.html.
|
| 561 |
+
"""
|
| 562 |
+
try:
|
| 563 |
+
os.stat(path)
|
| 564 |
+
except OSError as err:
|
| 565 |
+
if err.errno in {errno.EPERM, errno.EACCES}:
|
| 566 |
+
raise
|
| 567 |
+
return False
|
| 568 |
+
else:
|
| 569 |
+
return True
|
| 570 |
+
|
| 571 |
+
|
| 572 |
+
@memoize
|
| 573 |
+
def supports_ipv6():
|
| 574 |
+
"""Return True if IPv6 is supported on this platform."""
|
| 575 |
+
if not socket.has_ipv6 or AF_INET6 is None:
|
| 576 |
+
return False
|
| 577 |
+
try:
|
| 578 |
+
sock = socket.socket(AF_INET6, socket.SOCK_STREAM)
|
| 579 |
+
with contextlib.closing(sock):
|
| 580 |
+
sock.bind(("::1", 0))
|
| 581 |
+
return True
|
| 582 |
+
except socket.error:
|
| 583 |
+
return False
|
| 584 |
+
|
| 585 |
+
|
| 586 |
+
def parse_environ_block(data):
|
| 587 |
+
"""Parse a C environ block of environment variables into a dictionary."""
|
| 588 |
+
# The block is usually raw data from the target process. It might contain
|
| 589 |
+
# trailing garbage and lines that do not look like assignments.
|
| 590 |
+
ret = {}
|
| 591 |
+
pos = 0
|
| 592 |
+
|
| 593 |
+
# localize global variable to speed up access.
|
| 594 |
+
WINDOWS_ = WINDOWS
|
| 595 |
+
while True:
|
| 596 |
+
next_pos = data.find("\0", pos)
|
| 597 |
+
# nul byte at the beginning or double nul byte means finish
|
| 598 |
+
if next_pos <= pos:
|
| 599 |
+
break
|
| 600 |
+
# there might not be an equals sign
|
| 601 |
+
equal_pos = data.find("=", pos, next_pos)
|
| 602 |
+
if equal_pos > pos:
|
| 603 |
+
key = data[pos:equal_pos]
|
| 604 |
+
value = data[equal_pos + 1 : next_pos]
|
| 605 |
+
# Windows expects environment variables to be uppercase only
|
| 606 |
+
if WINDOWS_:
|
| 607 |
+
key = key.upper()
|
| 608 |
+
ret[key] = value
|
| 609 |
+
pos = next_pos + 1
|
| 610 |
+
|
| 611 |
+
return ret
|
| 612 |
+
|
| 613 |
+
|
| 614 |
+
def sockfam_to_enum(num):
|
| 615 |
+
"""Convert a numeric socket family value to an IntEnum member.
|
| 616 |
+
If it's not a known member, return the numeric value itself.
|
| 617 |
+
"""
|
| 618 |
+
if enum is None:
|
| 619 |
+
return num
|
| 620 |
+
else: # pragma: no cover
|
| 621 |
+
try:
|
| 622 |
+
return socket.AddressFamily(num)
|
| 623 |
+
except ValueError:
|
| 624 |
+
return num
|
| 625 |
+
|
| 626 |
+
|
| 627 |
+
def socktype_to_enum(num):
|
| 628 |
+
"""Convert a numeric socket type value to an IntEnum member.
|
| 629 |
+
If it's not a known member, return the numeric value itself.
|
| 630 |
+
"""
|
| 631 |
+
if enum is None:
|
| 632 |
+
return num
|
| 633 |
+
else: # pragma: no cover
|
| 634 |
+
try:
|
| 635 |
+
return socket.SocketKind(num)
|
| 636 |
+
except ValueError:
|
| 637 |
+
return num
|
| 638 |
+
|
| 639 |
+
|
| 640 |
+
def conn_to_ntuple(fd, fam, type_, laddr, raddr, status, status_map, pid=None):
|
| 641 |
+
"""Convert a raw connection tuple to a proper ntuple."""
|
| 642 |
+
if fam in {socket.AF_INET, AF_INET6}:
|
| 643 |
+
if laddr:
|
| 644 |
+
laddr = addr(*laddr)
|
| 645 |
+
if raddr:
|
| 646 |
+
raddr = addr(*raddr)
|
| 647 |
+
if type_ == socket.SOCK_STREAM and fam in {AF_INET, AF_INET6}:
|
| 648 |
+
status = status_map.get(status, CONN_NONE)
|
| 649 |
+
else:
|
| 650 |
+
status = CONN_NONE # ignore whatever C returned to us
|
| 651 |
+
fam = sockfam_to_enum(fam)
|
| 652 |
+
type_ = socktype_to_enum(type_)
|
| 653 |
+
if pid is None:
|
| 654 |
+
return pconn(fd, fam, type_, laddr, raddr, status)
|
| 655 |
+
else:
|
| 656 |
+
return sconn(fd, fam, type_, laddr, raddr, status, pid)
|
| 657 |
+
|
| 658 |
+
|
| 659 |
+
def deprecated_method(replacement):
|
| 660 |
+
"""A decorator which can be used to mark a method as deprecated
|
| 661 |
+
'replcement' is the method name which will be called instead.
|
| 662 |
+
"""
|
| 663 |
+
|
| 664 |
+
def outer(fun):
|
| 665 |
+
msg = "%s() is deprecated and will be removed; use %s() instead" % (
|
| 666 |
+
fun.__name__,
|
| 667 |
+
replacement,
|
| 668 |
+
)
|
| 669 |
+
if fun.__doc__ is None:
|
| 670 |
+
fun.__doc__ = msg
|
| 671 |
+
|
| 672 |
+
@functools.wraps(fun)
|
| 673 |
+
def inner(self, *args, **kwargs):
|
| 674 |
+
warnings.warn(msg, category=DeprecationWarning, stacklevel=2)
|
| 675 |
+
return getattr(self, replacement)(*args, **kwargs)
|
| 676 |
+
|
| 677 |
+
return inner
|
| 678 |
+
|
| 679 |
+
return outer
|
| 680 |
+
|
| 681 |
+
|
| 682 |
+
class _WrapNumbers:
|
| 683 |
+
"""Watches numbers so that they don't overflow and wrap
|
| 684 |
+
(reset to zero).
|
| 685 |
+
"""
|
| 686 |
+
|
| 687 |
+
def __init__(self):
|
| 688 |
+
self.lock = threading.Lock()
|
| 689 |
+
self.cache = {}
|
| 690 |
+
self.reminders = {}
|
| 691 |
+
self.reminder_keys = {}
|
| 692 |
+
|
| 693 |
+
def _add_dict(self, input_dict, name):
|
| 694 |
+
assert name not in self.cache
|
| 695 |
+
assert name not in self.reminders
|
| 696 |
+
assert name not in self.reminder_keys
|
| 697 |
+
self.cache[name] = input_dict
|
| 698 |
+
self.reminders[name] = collections.defaultdict(int)
|
| 699 |
+
self.reminder_keys[name] = collections.defaultdict(set)
|
| 700 |
+
|
| 701 |
+
def _remove_dead_reminders(self, input_dict, name):
|
| 702 |
+
"""In case the number of keys changed between calls (e.g. a
|
| 703 |
+
disk disappears) this removes the entry from self.reminders.
|
| 704 |
+
"""
|
| 705 |
+
old_dict = self.cache[name]
|
| 706 |
+
gone_keys = set(old_dict.keys()) - set(input_dict.keys())
|
| 707 |
+
for gone_key in gone_keys:
|
| 708 |
+
for remkey in self.reminder_keys[name][gone_key]:
|
| 709 |
+
del self.reminders[name][remkey]
|
| 710 |
+
del self.reminder_keys[name][gone_key]
|
| 711 |
+
|
| 712 |
+
def run(self, input_dict, name):
|
| 713 |
+
"""Cache dict and sum numbers which overflow and wrap.
|
| 714 |
+
Return an updated copy of `input_dict`.
|
| 715 |
+
"""
|
| 716 |
+
if name not in self.cache:
|
| 717 |
+
# This was the first call.
|
| 718 |
+
self._add_dict(input_dict, name)
|
| 719 |
+
return input_dict
|
| 720 |
+
|
| 721 |
+
self._remove_dead_reminders(input_dict, name)
|
| 722 |
+
|
| 723 |
+
old_dict = self.cache[name]
|
| 724 |
+
new_dict = {}
|
| 725 |
+
for key in input_dict:
|
| 726 |
+
input_tuple = input_dict[key]
|
| 727 |
+
try:
|
| 728 |
+
old_tuple = old_dict[key]
|
| 729 |
+
except KeyError:
|
| 730 |
+
# The input dict has a new key (e.g. a new disk or NIC)
|
| 731 |
+
# which didn't exist in the previous call.
|
| 732 |
+
new_dict[key] = input_tuple
|
| 733 |
+
continue
|
| 734 |
+
|
| 735 |
+
bits = []
|
| 736 |
+
for i in range(len(input_tuple)):
|
| 737 |
+
input_value = input_tuple[i]
|
| 738 |
+
old_value = old_tuple[i]
|
| 739 |
+
remkey = (key, i)
|
| 740 |
+
if input_value < old_value:
|
| 741 |
+
# it wrapped!
|
| 742 |
+
self.reminders[name][remkey] += old_value
|
| 743 |
+
self.reminder_keys[name][key].add(remkey)
|
| 744 |
+
bits.append(input_value + self.reminders[name][remkey])
|
| 745 |
+
|
| 746 |
+
new_dict[key] = tuple(bits)
|
| 747 |
+
|
| 748 |
+
self.cache[name] = input_dict
|
| 749 |
+
return new_dict
|
| 750 |
+
|
| 751 |
+
def cache_clear(self, name=None):
|
| 752 |
+
"""Clear the internal cache, optionally only for function 'name'."""
|
| 753 |
+
with self.lock:
|
| 754 |
+
if name is None:
|
| 755 |
+
self.cache.clear()
|
| 756 |
+
self.reminders.clear()
|
| 757 |
+
self.reminder_keys.clear()
|
| 758 |
+
else:
|
| 759 |
+
self.cache.pop(name, None)
|
| 760 |
+
self.reminders.pop(name, None)
|
| 761 |
+
self.reminder_keys.pop(name, None)
|
| 762 |
+
|
| 763 |
+
def cache_info(self):
|
| 764 |
+
"""Return internal cache dicts as a tuple of 3 elements."""
|
| 765 |
+
with self.lock:
|
| 766 |
+
return (self.cache, self.reminders, self.reminder_keys)
|
| 767 |
+
|
| 768 |
+
|
| 769 |
+
def wrap_numbers(input_dict, name):
|
| 770 |
+
"""Given an `input_dict` and a function `name`, adjust the numbers
|
| 771 |
+
which "wrap" (restart from zero) across different calls by adding
|
| 772 |
+
"old value" to "new value" and return an updated dict.
|
| 773 |
+
"""
|
| 774 |
+
with _wn.lock:
|
| 775 |
+
return _wn.run(input_dict, name)
|
| 776 |
+
|
| 777 |
+
|
| 778 |
+
_wn = _WrapNumbers()
|
| 779 |
+
wrap_numbers.cache_clear = _wn.cache_clear
|
| 780 |
+
wrap_numbers.cache_info = _wn.cache_info
|
| 781 |
+
|
| 782 |
+
|
| 783 |
+
# The read buffer size for open() builtin. This (also) dictates how
|
| 784 |
+
# much data we read(2) when iterating over file lines as in:
|
| 785 |
+
# >>> with open(file) as f:
|
| 786 |
+
# ... for line in f:
|
| 787 |
+
# ... ...
|
| 788 |
+
# Default per-line buffer size for binary files is 1K. For text files
|
| 789 |
+
# is 8K. We use a bigger buffer (32K) in order to have more consistent
|
| 790 |
+
# results when reading /proc pseudo files on Linux, see:
|
| 791 |
+
# https://github.com/giampaolo/psutil/issues/2050
|
| 792 |
+
# On Python 2 this also speeds up the reading of big files:
|
| 793 |
+
# (namely /proc/{pid}/smaps and /proc/net/*):
|
| 794 |
+
# https://github.com/giampaolo/psutil/issues/708
|
| 795 |
+
FILE_READ_BUFFER_SIZE = 32 * 1024
|
| 796 |
+
|
| 797 |
+
|
| 798 |
+
def open_binary(fname):
|
| 799 |
+
return open(fname, "rb", buffering=FILE_READ_BUFFER_SIZE)
|
| 800 |
+
|
| 801 |
+
|
| 802 |
+
def open_text(fname):
|
| 803 |
+
"""On Python 3 opens a file in text mode by using fs encoding and
|
| 804 |
+
a proper en/decoding errors handler.
|
| 805 |
+
On Python 2 this is just an alias for open(name, 'rt').
|
| 806 |
+
"""
|
| 807 |
+
if not PY3:
|
| 808 |
+
return open(fname, buffering=FILE_READ_BUFFER_SIZE)
|
| 809 |
+
|
| 810 |
+
# See:
|
| 811 |
+
# https://github.com/giampaolo/psutil/issues/675
|
| 812 |
+
# https://github.com/giampaolo/psutil/pull/733
|
| 813 |
+
fobj = open(
|
| 814 |
+
fname,
|
| 815 |
+
buffering=FILE_READ_BUFFER_SIZE,
|
| 816 |
+
encoding=ENCODING,
|
| 817 |
+
errors=ENCODING_ERRS,
|
| 818 |
+
)
|
| 819 |
+
try:
|
| 820 |
+
# Dictates per-line read(2) buffer size. Defaults is 8k. See:
|
| 821 |
+
# https://github.com/giampaolo/psutil/issues/2050#issuecomment-1013387546
|
| 822 |
+
fobj._CHUNK_SIZE = FILE_READ_BUFFER_SIZE
|
| 823 |
+
except AttributeError:
|
| 824 |
+
pass
|
| 825 |
+
except Exception:
|
| 826 |
+
fobj.close()
|
| 827 |
+
raise
|
| 828 |
+
|
| 829 |
+
return fobj
|
| 830 |
+
|
| 831 |
+
|
| 832 |
+
def cat(fname, fallback=_DEFAULT, _open=open_text):
|
| 833 |
+
"""Read entire file content and return it as a string. File is
|
| 834 |
+
opened in text mode. If specified, `fallback` is the value
|
| 835 |
+
returned in case of error, either if the file does not exist or
|
| 836 |
+
it can't be read().
|
| 837 |
+
"""
|
| 838 |
+
if fallback is _DEFAULT:
|
| 839 |
+
with _open(fname) as f:
|
| 840 |
+
return f.read()
|
| 841 |
+
else:
|
| 842 |
+
try:
|
| 843 |
+
with _open(fname) as f:
|
| 844 |
+
return f.read()
|
| 845 |
+
except (IOError, OSError):
|
| 846 |
+
return fallback
|
| 847 |
+
|
| 848 |
+
|
| 849 |
+
def bcat(fname, fallback=_DEFAULT):
|
| 850 |
+
"""Same as above but opens file in binary mode."""
|
| 851 |
+
return cat(fname, fallback=fallback, _open=open_binary)
|
| 852 |
+
|
| 853 |
+
|
| 854 |
+
def bytes2human(n, format="%(value).1f%(symbol)s"):
|
| 855 |
+
"""Used by various scripts. See: http://goo.gl/zeJZl.
|
| 856 |
+
|
| 857 |
+
>>> bytes2human(10000)
|
| 858 |
+
'9.8K'
|
| 859 |
+
>>> bytes2human(100001221)
|
| 860 |
+
'95.4M'
|
| 861 |
+
"""
|
| 862 |
+
symbols = ('B', 'K', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y')
|
| 863 |
+
prefix = {}
|
| 864 |
+
for i, s in enumerate(symbols[1:]):
|
| 865 |
+
prefix[s] = 1 << (i + 1) * 10
|
| 866 |
+
for symbol in reversed(symbols[1:]):
|
| 867 |
+
if abs(n) >= prefix[symbol]:
|
| 868 |
+
value = float(n) / prefix[symbol]
|
| 869 |
+
return format % locals()
|
| 870 |
+
return format % dict(symbol=symbols[0], value=n)
|
| 871 |
+
|
| 872 |
+
|
| 873 |
+
def get_procfs_path():
|
| 874 |
+
"""Return updated psutil.PROCFS_PATH constant."""
|
| 875 |
+
return sys.modules['psutil'].PROCFS_PATH
|
| 876 |
+
|
| 877 |
+
|
| 878 |
+
if PY3:
|
| 879 |
+
|
| 880 |
+
def decode(s):
|
| 881 |
+
return s.decode(encoding=ENCODING, errors=ENCODING_ERRS)
|
| 882 |
+
|
| 883 |
+
else:
|
| 884 |
+
|
| 885 |
+
def decode(s):
|
| 886 |
+
return s
|
| 887 |
+
|
| 888 |
+
|
| 889 |
+
# =====================================================================
|
| 890 |
+
# --- shell utils
|
| 891 |
+
# =====================================================================
|
| 892 |
+
|
| 893 |
+
|
| 894 |
+
@memoize
|
| 895 |
+
def term_supports_colors(file=sys.stdout): # pragma: no cover
|
| 896 |
+
if os.name == 'nt':
|
| 897 |
+
return True
|
| 898 |
+
try:
|
| 899 |
+
import curses
|
| 900 |
+
|
| 901 |
+
assert file.isatty()
|
| 902 |
+
curses.setupterm()
|
| 903 |
+
assert curses.tigetnum("colors") > 0
|
| 904 |
+
except Exception: # noqa: BLE001
|
| 905 |
+
return False
|
| 906 |
+
else:
|
| 907 |
+
return True
|
| 908 |
+
|
| 909 |
+
|
| 910 |
+
def hilite(s, color=None, bold=False): # pragma: no cover
|
| 911 |
+
"""Return an highlighted version of 'string'."""
|
| 912 |
+
if not term_supports_colors():
|
| 913 |
+
return s
|
| 914 |
+
attr = []
|
| 915 |
+
colors = dict(
|
| 916 |
+
blue='34',
|
| 917 |
+
brown='33',
|
| 918 |
+
darkgrey='30',
|
| 919 |
+
green='32',
|
| 920 |
+
grey='37',
|
| 921 |
+
lightblue='36',
|
| 922 |
+
red='91',
|
| 923 |
+
violet='35',
|
| 924 |
+
yellow='93',
|
| 925 |
+
)
|
| 926 |
+
colors[None] = '29'
|
| 927 |
+
try:
|
| 928 |
+
color = colors[color]
|
| 929 |
+
except KeyError:
|
| 930 |
+
raise ValueError(
|
| 931 |
+
"invalid color %r; choose between %s" % (list(colors.keys()))
|
| 932 |
+
)
|
| 933 |
+
attr.append(color)
|
| 934 |
+
if bold:
|
| 935 |
+
attr.append('1')
|
| 936 |
+
return '\x1b[%sm%s\x1b[0m' % (';'.join(attr), s)
|
| 937 |
+
|
| 938 |
+
|
| 939 |
+
def print_color(
|
| 940 |
+
s, color=None, bold=False, file=sys.stdout
|
| 941 |
+
): # pragma: no cover
|
| 942 |
+
"""Print a colorized version of string."""
|
| 943 |
+
if not term_supports_colors():
|
| 944 |
+
print(s, file=file) # NOQA
|
| 945 |
+
elif POSIX:
|
| 946 |
+
print(hilite(s, color, bold), file=file) # NOQA
|
| 947 |
+
else:
|
| 948 |
+
import ctypes
|
| 949 |
+
|
| 950 |
+
DEFAULT_COLOR = 7
|
| 951 |
+
GetStdHandle = ctypes.windll.Kernel32.GetStdHandle
|
| 952 |
+
SetConsoleTextAttribute = (
|
| 953 |
+
ctypes.windll.Kernel32.SetConsoleTextAttribute
|
| 954 |
+
)
|
| 955 |
+
|
| 956 |
+
colors = dict(green=2, red=4, brown=6, yellow=6)
|
| 957 |
+
colors[None] = DEFAULT_COLOR
|
| 958 |
+
try:
|
| 959 |
+
color = colors[color]
|
| 960 |
+
except KeyError:
|
| 961 |
+
raise ValueError(
|
| 962 |
+
"invalid color %r; choose between %r"
|
| 963 |
+
% (color, list(colors.keys()))
|
| 964 |
+
)
|
| 965 |
+
if bold and color <= 7:
|
| 966 |
+
color += 8
|
| 967 |
+
|
| 968 |
+
handle_id = -12 if file is sys.stderr else -11
|
| 969 |
+
GetStdHandle.restype = ctypes.c_ulong
|
| 970 |
+
handle = GetStdHandle(handle_id)
|
| 971 |
+
SetConsoleTextAttribute(handle, color)
|
| 972 |
+
try:
|
| 973 |
+
print(s, file=file) # NOQA
|
| 974 |
+
finally:
|
| 975 |
+
SetConsoleTextAttribute(handle, DEFAULT_COLOR)
|
| 976 |
+
|
| 977 |
+
|
| 978 |
+
def debug(msg):
|
| 979 |
+
"""If PSUTIL_DEBUG env var is set, print a debug message to stderr."""
|
| 980 |
+
if PSUTIL_DEBUG:
|
| 981 |
+
import inspect
|
| 982 |
+
|
| 983 |
+
fname, lineno, _, _lines, _index = inspect.getframeinfo(
|
| 984 |
+
inspect.currentframe().f_back
|
| 985 |
+
)
|
| 986 |
+
if isinstance(msg, Exception):
|
| 987 |
+
if isinstance(msg, (OSError, IOError, EnvironmentError)):
|
| 988 |
+
# ...because str(exc) may contain info about the file name
|
| 989 |
+
msg = "ignoring %s" % msg
|
| 990 |
+
else:
|
| 991 |
+
msg = "ignoring %r" % msg
|
| 992 |
+
print( # noqa
|
| 993 |
+
"psutil-debug [%s:%s]> %s" % (fname, lineno, msg), file=sys.stderr
|
| 994 |
+
)
|
.venv/lib/python3.11/site-packages/ray/thirdparty_files/psutil/_compat.py
ADDED
|
@@ -0,0 +1,477 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) 2009, Giampaolo Rodola'. All rights reserved.
|
| 2 |
+
# Use of this source code is governed by a BSD-style license that can be
|
| 3 |
+
# found in the LICENSE file.
|
| 4 |
+
|
| 5 |
+
"""Module which provides compatibility with older Python versions.
|
| 6 |
+
This is more future-compatible rather than the opposite (prefer latest
|
| 7 |
+
Python 3 way of doing things).
|
| 8 |
+
"""
|
| 9 |
+
|
| 10 |
+
import collections
|
| 11 |
+
import contextlib
|
| 12 |
+
import errno
|
| 13 |
+
import functools
|
| 14 |
+
import os
|
| 15 |
+
import sys
|
| 16 |
+
import types
|
| 17 |
+
|
| 18 |
+
|
| 19 |
+
# fmt: off
|
| 20 |
+
__all__ = [
|
| 21 |
+
# constants
|
| 22 |
+
"PY3",
|
| 23 |
+
# builtins
|
| 24 |
+
"long", "range", "super", "unicode", "basestring",
|
| 25 |
+
# literals
|
| 26 |
+
"b",
|
| 27 |
+
# collections module
|
| 28 |
+
"lru_cache",
|
| 29 |
+
# shutil module
|
| 30 |
+
"which", "get_terminal_size",
|
| 31 |
+
# contextlib module
|
| 32 |
+
"redirect_stderr",
|
| 33 |
+
# python 3 exceptions
|
| 34 |
+
"FileNotFoundError", "PermissionError", "ProcessLookupError",
|
| 35 |
+
"InterruptedError", "ChildProcessError", "FileExistsError",
|
| 36 |
+
]
|
| 37 |
+
# fmt: on
|
| 38 |
+
|
| 39 |
+
|
| 40 |
+
PY3 = sys.version_info[0] >= 3
|
| 41 |
+
_SENTINEL = object()
|
| 42 |
+
|
| 43 |
+
if PY3:
|
| 44 |
+
long = int
|
| 45 |
+
xrange = range
|
| 46 |
+
unicode = str
|
| 47 |
+
basestring = str
|
| 48 |
+
range = range
|
| 49 |
+
|
| 50 |
+
def b(s):
|
| 51 |
+
return s.encode("latin-1")
|
| 52 |
+
|
| 53 |
+
else:
|
| 54 |
+
long = long
|
| 55 |
+
range = xrange
|
| 56 |
+
unicode = unicode
|
| 57 |
+
basestring = basestring
|
| 58 |
+
|
| 59 |
+
def b(s):
|
| 60 |
+
return s
|
| 61 |
+
|
| 62 |
+
|
| 63 |
+
# --- builtins
|
| 64 |
+
|
| 65 |
+
|
| 66 |
+
# Python 3 super().
|
| 67 |
+
# Taken from "future" package.
|
| 68 |
+
# Credit: Ryan Kelly
|
| 69 |
+
if PY3:
|
| 70 |
+
super = super
|
| 71 |
+
else:
|
| 72 |
+
_builtin_super = super
|
| 73 |
+
|
| 74 |
+
def super(type_=_SENTINEL, type_or_obj=_SENTINEL, framedepth=1):
|
| 75 |
+
"""Like Python 3 builtin super(). If called without any arguments
|
| 76 |
+
it attempts to infer them at runtime.
|
| 77 |
+
"""
|
| 78 |
+
if type_ is _SENTINEL:
|
| 79 |
+
f = sys._getframe(framedepth)
|
| 80 |
+
try:
|
| 81 |
+
# Get the function's first positional argument.
|
| 82 |
+
type_or_obj = f.f_locals[f.f_code.co_varnames[0]]
|
| 83 |
+
except (IndexError, KeyError):
|
| 84 |
+
msg = 'super() used in a function with no args'
|
| 85 |
+
raise RuntimeError(msg)
|
| 86 |
+
try:
|
| 87 |
+
# Get the MRO so we can crawl it.
|
| 88 |
+
mro = type_or_obj.__mro__
|
| 89 |
+
except (AttributeError, RuntimeError):
|
| 90 |
+
try:
|
| 91 |
+
mro = type_or_obj.__class__.__mro__
|
| 92 |
+
except AttributeError:
|
| 93 |
+
msg = 'super() used in a non-newstyle class'
|
| 94 |
+
raise RuntimeError(msg)
|
| 95 |
+
for type_ in mro:
|
| 96 |
+
# Find the class that owns the currently-executing method.
|
| 97 |
+
for meth in type_.__dict__.values():
|
| 98 |
+
# Drill down through any wrappers to the underlying func.
|
| 99 |
+
# This handles e.g. classmethod() and staticmethod().
|
| 100 |
+
try:
|
| 101 |
+
while not isinstance(meth, types.FunctionType):
|
| 102 |
+
if isinstance(meth, property):
|
| 103 |
+
# Calling __get__ on the property will invoke
|
| 104 |
+
# user code which might throw exceptions or
|
| 105 |
+
# have side effects
|
| 106 |
+
meth = meth.fget
|
| 107 |
+
else:
|
| 108 |
+
try:
|
| 109 |
+
meth = meth.__func__
|
| 110 |
+
except AttributeError:
|
| 111 |
+
meth = meth.__get__(type_or_obj, type_)
|
| 112 |
+
except (AttributeError, TypeError):
|
| 113 |
+
continue
|
| 114 |
+
if meth.func_code is f.f_code:
|
| 115 |
+
break # found
|
| 116 |
+
else:
|
| 117 |
+
# Not found. Move onto the next class in MRO.
|
| 118 |
+
continue
|
| 119 |
+
break # found
|
| 120 |
+
else:
|
| 121 |
+
msg = 'super() called outside a method'
|
| 122 |
+
raise RuntimeError(msg)
|
| 123 |
+
|
| 124 |
+
# Dispatch to builtin super().
|
| 125 |
+
if type_or_obj is not _SENTINEL:
|
| 126 |
+
return _builtin_super(type_, type_or_obj)
|
| 127 |
+
return _builtin_super(type_)
|
| 128 |
+
|
| 129 |
+
|
| 130 |
+
# --- exceptions
|
| 131 |
+
|
| 132 |
+
|
| 133 |
+
if PY3:
|
| 134 |
+
FileNotFoundError = FileNotFoundError # NOQA
|
| 135 |
+
PermissionError = PermissionError # NOQA
|
| 136 |
+
ProcessLookupError = ProcessLookupError # NOQA
|
| 137 |
+
InterruptedError = InterruptedError # NOQA
|
| 138 |
+
ChildProcessError = ChildProcessError # NOQA
|
| 139 |
+
FileExistsError = FileExistsError # NOQA
|
| 140 |
+
else:
|
| 141 |
+
# https://github.com/PythonCharmers/python-future/blob/exceptions/
|
| 142 |
+
# src/future/types/exceptions/pep3151.py
|
| 143 |
+
import platform
|
| 144 |
+
|
| 145 |
+
def _instance_checking_exception(base_exception=Exception):
|
| 146 |
+
def wrapped(instance_checker):
|
| 147 |
+
class TemporaryClass(base_exception):
|
| 148 |
+
def __init__(self, *args, **kwargs):
|
| 149 |
+
if len(args) == 1 and isinstance(args[0], TemporaryClass):
|
| 150 |
+
unwrap_me = args[0]
|
| 151 |
+
for attr in dir(unwrap_me):
|
| 152 |
+
if not attr.startswith('__'):
|
| 153 |
+
setattr(self, attr, getattr(unwrap_me, attr))
|
| 154 |
+
else:
|
| 155 |
+
super(TemporaryClass, self).__init__( # noqa
|
| 156 |
+
*args, **kwargs
|
| 157 |
+
)
|
| 158 |
+
|
| 159 |
+
class __metaclass__(type):
|
| 160 |
+
def __instancecheck__(cls, inst):
|
| 161 |
+
return instance_checker(inst)
|
| 162 |
+
|
| 163 |
+
def __subclasscheck__(cls, classinfo):
|
| 164 |
+
value = sys.exc_info()[1]
|
| 165 |
+
return isinstance(value, cls)
|
| 166 |
+
|
| 167 |
+
TemporaryClass.__name__ = instance_checker.__name__
|
| 168 |
+
TemporaryClass.__doc__ = instance_checker.__doc__
|
| 169 |
+
return TemporaryClass
|
| 170 |
+
|
| 171 |
+
return wrapped
|
| 172 |
+
|
| 173 |
+
@_instance_checking_exception(EnvironmentError)
|
| 174 |
+
def FileNotFoundError(inst):
|
| 175 |
+
return getattr(inst, 'errno', _SENTINEL) == errno.ENOENT
|
| 176 |
+
|
| 177 |
+
@_instance_checking_exception(EnvironmentError)
|
| 178 |
+
def ProcessLookupError(inst):
|
| 179 |
+
return getattr(inst, 'errno', _SENTINEL) == errno.ESRCH
|
| 180 |
+
|
| 181 |
+
@_instance_checking_exception(EnvironmentError)
|
| 182 |
+
def PermissionError(inst):
|
| 183 |
+
return getattr(inst, 'errno', _SENTINEL) in {errno.EACCES, errno.EPERM}
|
| 184 |
+
|
| 185 |
+
@_instance_checking_exception(EnvironmentError)
|
| 186 |
+
def InterruptedError(inst):
|
| 187 |
+
return getattr(inst, 'errno', _SENTINEL) == errno.EINTR
|
| 188 |
+
|
| 189 |
+
@_instance_checking_exception(EnvironmentError)
|
| 190 |
+
def ChildProcessError(inst):
|
| 191 |
+
return getattr(inst, 'errno', _SENTINEL) == errno.ECHILD
|
| 192 |
+
|
| 193 |
+
@_instance_checking_exception(EnvironmentError)
|
| 194 |
+
def FileExistsError(inst):
|
| 195 |
+
return getattr(inst, 'errno', _SENTINEL) == errno.EEXIST
|
| 196 |
+
|
| 197 |
+
if platform.python_implementation() != "CPython":
|
| 198 |
+
try:
|
| 199 |
+
raise OSError(errno.EEXIST, "perm")
|
| 200 |
+
except FileExistsError:
|
| 201 |
+
pass
|
| 202 |
+
except OSError:
|
| 203 |
+
msg = (
|
| 204 |
+
"broken or incompatible Python implementation, see: "
|
| 205 |
+
"https://github.com/giampaolo/psutil/issues/1659"
|
| 206 |
+
)
|
| 207 |
+
raise RuntimeError(msg)
|
| 208 |
+
|
| 209 |
+
|
| 210 |
+
# --- stdlib additions
|
| 211 |
+
|
| 212 |
+
|
| 213 |
+
# py 3.2 functools.lru_cache
|
| 214 |
+
# Taken from: http://code.activestate.com/recipes/578078
|
| 215 |
+
# Credit: Raymond Hettinger
|
| 216 |
+
try:
|
| 217 |
+
from functools import lru_cache
|
| 218 |
+
except ImportError:
|
| 219 |
+
try:
|
| 220 |
+
from threading import RLock
|
| 221 |
+
except ImportError:
|
| 222 |
+
from dummy_threading import RLock
|
| 223 |
+
|
| 224 |
+
_CacheInfo = collections.namedtuple(
|
| 225 |
+
"CacheInfo", ["hits", "misses", "maxsize", "currsize"]
|
| 226 |
+
)
|
| 227 |
+
|
| 228 |
+
class _HashedSeq(list): # noqa: FURB189
|
| 229 |
+
__slots__ = ('hashvalue',)
|
| 230 |
+
|
| 231 |
+
def __init__(self, tup, hash=hash):
|
| 232 |
+
self[:] = tup
|
| 233 |
+
self.hashvalue = hash(tup)
|
| 234 |
+
|
| 235 |
+
def __hash__(self):
|
| 236 |
+
return self.hashvalue
|
| 237 |
+
|
| 238 |
+
def _make_key(
|
| 239 |
+
args,
|
| 240 |
+
kwds,
|
| 241 |
+
typed,
|
| 242 |
+
kwd_mark=(_SENTINEL,),
|
| 243 |
+
fasttypes=set((int, str, frozenset, type(None))), # noqa
|
| 244 |
+
sorted=sorted,
|
| 245 |
+
tuple=tuple,
|
| 246 |
+
type=type,
|
| 247 |
+
len=len,
|
| 248 |
+
):
|
| 249 |
+
key = args
|
| 250 |
+
if kwds:
|
| 251 |
+
sorted_items = sorted(kwds.items())
|
| 252 |
+
key += kwd_mark
|
| 253 |
+
for item in sorted_items:
|
| 254 |
+
key += item
|
| 255 |
+
if typed:
|
| 256 |
+
key += tuple(type(v) for v in args)
|
| 257 |
+
if kwds:
|
| 258 |
+
key += tuple(type(v) for k, v in sorted_items)
|
| 259 |
+
elif len(key) == 1 and type(key[0]) in fasttypes:
|
| 260 |
+
return key[0]
|
| 261 |
+
return _HashedSeq(key)
|
| 262 |
+
|
| 263 |
+
def lru_cache(maxsize=100, typed=False):
|
| 264 |
+
"""Least-recently-used cache decorator, see:
|
| 265 |
+
http://docs.python.org/3/library/functools.html#functools.lru_cache.
|
| 266 |
+
"""
|
| 267 |
+
|
| 268 |
+
def decorating_function(user_function):
|
| 269 |
+
cache = {}
|
| 270 |
+
stats = [0, 0]
|
| 271 |
+
HITS, MISSES = 0, 1
|
| 272 |
+
make_key = _make_key
|
| 273 |
+
cache_get = cache.get
|
| 274 |
+
_len = len
|
| 275 |
+
lock = RLock()
|
| 276 |
+
root = []
|
| 277 |
+
root[:] = [root, root, None, None]
|
| 278 |
+
nonlocal_root = [root]
|
| 279 |
+
PREV, NEXT, KEY, RESULT = 0, 1, 2, 3
|
| 280 |
+
if maxsize == 0:
|
| 281 |
+
|
| 282 |
+
def wrapper(*args, **kwds):
|
| 283 |
+
result = user_function(*args, **kwds)
|
| 284 |
+
stats[MISSES] += 1
|
| 285 |
+
return result
|
| 286 |
+
|
| 287 |
+
elif maxsize is None:
|
| 288 |
+
|
| 289 |
+
def wrapper(*args, **kwds):
|
| 290 |
+
key = make_key(args, kwds, typed)
|
| 291 |
+
result = cache_get(key, root)
|
| 292 |
+
if result is not root:
|
| 293 |
+
stats[HITS] += 1
|
| 294 |
+
return result
|
| 295 |
+
result = user_function(*args, **kwds)
|
| 296 |
+
cache[key] = result
|
| 297 |
+
stats[MISSES] += 1
|
| 298 |
+
return result
|
| 299 |
+
|
| 300 |
+
else:
|
| 301 |
+
|
| 302 |
+
def wrapper(*args, **kwds):
|
| 303 |
+
if kwds or typed:
|
| 304 |
+
key = make_key(args, kwds, typed)
|
| 305 |
+
else:
|
| 306 |
+
key = args
|
| 307 |
+
lock.acquire()
|
| 308 |
+
try:
|
| 309 |
+
link = cache_get(key)
|
| 310 |
+
if link is not None:
|
| 311 |
+
(root,) = nonlocal_root
|
| 312 |
+
link_prev, link_next, key, result = link
|
| 313 |
+
link_prev[NEXT] = link_next
|
| 314 |
+
link_next[PREV] = link_prev
|
| 315 |
+
last = root[PREV]
|
| 316 |
+
last[NEXT] = root[PREV] = link
|
| 317 |
+
link[PREV] = last
|
| 318 |
+
link[NEXT] = root
|
| 319 |
+
stats[HITS] += 1
|
| 320 |
+
return result
|
| 321 |
+
finally:
|
| 322 |
+
lock.release()
|
| 323 |
+
result = user_function(*args, **kwds)
|
| 324 |
+
lock.acquire()
|
| 325 |
+
try:
|
| 326 |
+
(root,) = nonlocal_root
|
| 327 |
+
if key in cache:
|
| 328 |
+
pass
|
| 329 |
+
elif _len(cache) >= maxsize:
|
| 330 |
+
oldroot = root
|
| 331 |
+
oldroot[KEY] = key
|
| 332 |
+
oldroot[RESULT] = result
|
| 333 |
+
root = nonlocal_root[0] = oldroot[NEXT]
|
| 334 |
+
oldkey = root[KEY]
|
| 335 |
+
root[KEY] = root[RESULT] = None
|
| 336 |
+
del cache[oldkey]
|
| 337 |
+
cache[key] = oldroot
|
| 338 |
+
else:
|
| 339 |
+
last = root[PREV]
|
| 340 |
+
link = [last, root, key, result]
|
| 341 |
+
last[NEXT] = root[PREV] = cache[key] = link
|
| 342 |
+
stats[MISSES] += 1
|
| 343 |
+
finally:
|
| 344 |
+
lock.release()
|
| 345 |
+
return result
|
| 346 |
+
|
| 347 |
+
def cache_info():
|
| 348 |
+
"""Report cache statistics."""
|
| 349 |
+
lock.acquire()
|
| 350 |
+
try:
|
| 351 |
+
return _CacheInfo(
|
| 352 |
+
stats[HITS], stats[MISSES], maxsize, len(cache)
|
| 353 |
+
)
|
| 354 |
+
finally:
|
| 355 |
+
lock.release()
|
| 356 |
+
|
| 357 |
+
def cache_clear():
|
| 358 |
+
"""Clear the cache and cache statistics."""
|
| 359 |
+
lock.acquire()
|
| 360 |
+
try:
|
| 361 |
+
cache.clear()
|
| 362 |
+
root = nonlocal_root[0]
|
| 363 |
+
root[:] = [root, root, None, None]
|
| 364 |
+
stats[:] = [0, 0]
|
| 365 |
+
finally:
|
| 366 |
+
lock.release()
|
| 367 |
+
|
| 368 |
+
wrapper.__wrapped__ = user_function
|
| 369 |
+
wrapper.cache_info = cache_info
|
| 370 |
+
wrapper.cache_clear = cache_clear
|
| 371 |
+
return functools.update_wrapper(wrapper, user_function)
|
| 372 |
+
|
| 373 |
+
return decorating_function
|
| 374 |
+
|
| 375 |
+
|
| 376 |
+
# python 3.3
|
| 377 |
+
try:
|
| 378 |
+
from shutil import which
|
| 379 |
+
except ImportError:
|
| 380 |
+
|
| 381 |
+
def which(cmd, mode=os.F_OK | os.X_OK, path=None):
|
| 382 |
+
"""Given a command, mode, and a PATH string, return the path which
|
| 383 |
+
conforms to the given mode on the PATH, or None if there is no such
|
| 384 |
+
file.
|
| 385 |
+
|
| 386 |
+
`mode` defaults to os.F_OK | os.X_OK. `path` defaults to the result
|
| 387 |
+
of os.environ.get("PATH"), or can be overridden with a custom search
|
| 388 |
+
path.
|
| 389 |
+
"""
|
| 390 |
+
|
| 391 |
+
def _access_check(fn, mode):
|
| 392 |
+
return (
|
| 393 |
+
os.path.exists(fn)
|
| 394 |
+
and os.access(fn, mode)
|
| 395 |
+
and not os.path.isdir(fn)
|
| 396 |
+
)
|
| 397 |
+
|
| 398 |
+
if os.path.dirname(cmd):
|
| 399 |
+
if _access_check(cmd, mode):
|
| 400 |
+
return cmd
|
| 401 |
+
return None
|
| 402 |
+
|
| 403 |
+
if path is None:
|
| 404 |
+
path = os.environ.get("PATH", os.defpath)
|
| 405 |
+
if not path:
|
| 406 |
+
return None
|
| 407 |
+
path = path.split(os.pathsep)
|
| 408 |
+
|
| 409 |
+
if sys.platform == "win32":
|
| 410 |
+
if os.curdir not in path:
|
| 411 |
+
path.insert(0, os.curdir)
|
| 412 |
+
|
| 413 |
+
pathext = os.environ.get("PATHEXT", "").split(os.pathsep)
|
| 414 |
+
if any(cmd.lower().endswith(ext.lower()) for ext in pathext):
|
| 415 |
+
files = [cmd]
|
| 416 |
+
else:
|
| 417 |
+
files = [cmd + ext for ext in pathext]
|
| 418 |
+
else:
|
| 419 |
+
files = [cmd]
|
| 420 |
+
|
| 421 |
+
seen = set()
|
| 422 |
+
for dir in path:
|
| 423 |
+
normdir = os.path.normcase(dir)
|
| 424 |
+
if normdir not in seen:
|
| 425 |
+
seen.add(normdir)
|
| 426 |
+
for thefile in files:
|
| 427 |
+
name = os.path.join(dir, thefile)
|
| 428 |
+
if _access_check(name, mode):
|
| 429 |
+
return name
|
| 430 |
+
return None
|
| 431 |
+
|
| 432 |
+
|
| 433 |
+
# python 3.3
|
| 434 |
+
try:
|
| 435 |
+
from shutil import get_terminal_size
|
| 436 |
+
except ImportError:
|
| 437 |
+
|
| 438 |
+
def get_terminal_size(fallback=(80, 24)):
|
| 439 |
+
try:
|
| 440 |
+
import fcntl
|
| 441 |
+
import struct
|
| 442 |
+
import termios
|
| 443 |
+
except ImportError:
|
| 444 |
+
return fallback
|
| 445 |
+
else:
|
| 446 |
+
try:
|
| 447 |
+
# This should work on Linux.
|
| 448 |
+
res = struct.unpack(
|
| 449 |
+
'hh', fcntl.ioctl(1, termios.TIOCGWINSZ, '1234')
|
| 450 |
+
)
|
| 451 |
+
return (res[1], res[0])
|
| 452 |
+
except Exception: # noqa: BLE001
|
| 453 |
+
return fallback
|
| 454 |
+
|
| 455 |
+
|
| 456 |
+
# python 3.3
|
| 457 |
+
try:
|
| 458 |
+
from subprocess import TimeoutExpired as SubprocessTimeoutExpired
|
| 459 |
+
except ImportError:
|
| 460 |
+
|
| 461 |
+
class SubprocessTimeoutExpired(Exception):
|
| 462 |
+
pass
|
| 463 |
+
|
| 464 |
+
|
| 465 |
+
# python 3.5
|
| 466 |
+
try:
|
| 467 |
+
from contextlib import redirect_stderr
|
| 468 |
+
except ImportError:
|
| 469 |
+
|
| 470 |
+
@contextlib.contextmanager
|
| 471 |
+
def redirect_stderr(new_target):
|
| 472 |
+
original = sys.stderr
|
| 473 |
+
try:
|
| 474 |
+
sys.stderr = new_target
|
| 475 |
+
yield new_target
|
| 476 |
+
finally:
|
| 477 |
+
sys.stderr = original
|
.venv/lib/python3.11/site-packages/ray/thirdparty_files/psutil/_psaix.py
ADDED
|
@@ -0,0 +1,579 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) 2009, Giampaolo Rodola'
|
| 2 |
+
# Copyright (c) 2017, Arnon Yaari
|
| 3 |
+
# All rights reserved.
|
| 4 |
+
# Use of this source code is governed by a BSD-style license that can be
|
| 5 |
+
# found in the LICENSE file.
|
| 6 |
+
|
| 7 |
+
"""AIX platform implementation."""
|
| 8 |
+
|
| 9 |
+
import functools
|
| 10 |
+
import glob
|
| 11 |
+
import os
|
| 12 |
+
import re
|
| 13 |
+
import subprocess
|
| 14 |
+
import sys
|
| 15 |
+
from collections import namedtuple
|
| 16 |
+
|
| 17 |
+
from . import _common
|
| 18 |
+
from . import _psposix
|
| 19 |
+
from . import _psutil_aix as cext
|
| 20 |
+
from . import _psutil_posix as cext_posix
|
| 21 |
+
from ._common import NIC_DUPLEX_FULL
|
| 22 |
+
from ._common import NIC_DUPLEX_HALF
|
| 23 |
+
from ._common import NIC_DUPLEX_UNKNOWN
|
| 24 |
+
from ._common import AccessDenied
|
| 25 |
+
from ._common import NoSuchProcess
|
| 26 |
+
from ._common import ZombieProcess
|
| 27 |
+
from ._common import conn_to_ntuple
|
| 28 |
+
from ._common import get_procfs_path
|
| 29 |
+
from ._common import memoize_when_activated
|
| 30 |
+
from ._common import usage_percent
|
| 31 |
+
from ._compat import PY3
|
| 32 |
+
from ._compat import FileNotFoundError
|
| 33 |
+
from ._compat import PermissionError
|
| 34 |
+
from ._compat import ProcessLookupError
|
| 35 |
+
|
| 36 |
+
|
| 37 |
+
__extra__all__ = ["PROCFS_PATH"]
|
| 38 |
+
|
| 39 |
+
|
| 40 |
+
# =====================================================================
|
| 41 |
+
# --- globals
|
| 42 |
+
# =====================================================================
|
| 43 |
+
|
| 44 |
+
|
| 45 |
+
HAS_THREADS = hasattr(cext, "proc_threads")
|
| 46 |
+
HAS_NET_IO_COUNTERS = hasattr(cext, "net_io_counters")
|
| 47 |
+
HAS_PROC_IO_COUNTERS = hasattr(cext, "proc_io_counters")
|
| 48 |
+
|
| 49 |
+
PAGE_SIZE = cext_posix.getpagesize()
|
| 50 |
+
AF_LINK = cext_posix.AF_LINK
|
| 51 |
+
|
| 52 |
+
PROC_STATUSES = {
|
| 53 |
+
cext.SIDL: _common.STATUS_IDLE,
|
| 54 |
+
cext.SZOMB: _common.STATUS_ZOMBIE,
|
| 55 |
+
cext.SACTIVE: _common.STATUS_RUNNING,
|
| 56 |
+
cext.SSWAP: _common.STATUS_RUNNING, # TODO what status is this?
|
| 57 |
+
cext.SSTOP: _common.STATUS_STOPPED,
|
| 58 |
+
}
|
| 59 |
+
|
| 60 |
+
TCP_STATUSES = {
|
| 61 |
+
cext.TCPS_ESTABLISHED: _common.CONN_ESTABLISHED,
|
| 62 |
+
cext.TCPS_SYN_SENT: _common.CONN_SYN_SENT,
|
| 63 |
+
cext.TCPS_SYN_RCVD: _common.CONN_SYN_RECV,
|
| 64 |
+
cext.TCPS_FIN_WAIT_1: _common.CONN_FIN_WAIT1,
|
| 65 |
+
cext.TCPS_FIN_WAIT_2: _common.CONN_FIN_WAIT2,
|
| 66 |
+
cext.TCPS_TIME_WAIT: _common.CONN_TIME_WAIT,
|
| 67 |
+
cext.TCPS_CLOSED: _common.CONN_CLOSE,
|
| 68 |
+
cext.TCPS_CLOSE_WAIT: _common.CONN_CLOSE_WAIT,
|
| 69 |
+
cext.TCPS_LAST_ACK: _common.CONN_LAST_ACK,
|
| 70 |
+
cext.TCPS_LISTEN: _common.CONN_LISTEN,
|
| 71 |
+
cext.TCPS_CLOSING: _common.CONN_CLOSING,
|
| 72 |
+
cext.PSUTIL_CONN_NONE: _common.CONN_NONE,
|
| 73 |
+
}
|
| 74 |
+
|
| 75 |
+
proc_info_map = dict(
|
| 76 |
+
ppid=0,
|
| 77 |
+
rss=1,
|
| 78 |
+
vms=2,
|
| 79 |
+
create_time=3,
|
| 80 |
+
nice=4,
|
| 81 |
+
num_threads=5,
|
| 82 |
+
status=6,
|
| 83 |
+
ttynr=7,
|
| 84 |
+
)
|
| 85 |
+
|
| 86 |
+
|
| 87 |
+
# =====================================================================
|
| 88 |
+
# --- named tuples
|
| 89 |
+
# =====================================================================
|
| 90 |
+
|
| 91 |
+
|
| 92 |
+
# psutil.Process.memory_info()
|
| 93 |
+
pmem = namedtuple('pmem', ['rss', 'vms'])
|
| 94 |
+
# psutil.Process.memory_full_info()
|
| 95 |
+
pfullmem = pmem
|
| 96 |
+
# psutil.Process.cpu_times()
|
| 97 |
+
scputimes = namedtuple('scputimes', ['user', 'system', 'idle', 'iowait'])
|
| 98 |
+
# psutil.virtual_memory()
|
| 99 |
+
svmem = namedtuple('svmem', ['total', 'available', 'percent', 'used', 'free'])
|
| 100 |
+
|
| 101 |
+
|
| 102 |
+
# =====================================================================
|
| 103 |
+
# --- memory
|
| 104 |
+
# =====================================================================
|
| 105 |
+
|
| 106 |
+
|
| 107 |
+
def virtual_memory():
|
| 108 |
+
total, avail, free, _pinned, inuse = cext.virtual_mem()
|
| 109 |
+
percent = usage_percent((total - avail), total, round_=1)
|
| 110 |
+
return svmem(total, avail, percent, inuse, free)
|
| 111 |
+
|
| 112 |
+
|
| 113 |
+
def swap_memory():
|
| 114 |
+
"""Swap system memory as a (total, used, free, sin, sout) tuple."""
|
| 115 |
+
total, free, sin, sout = cext.swap_mem()
|
| 116 |
+
used = total - free
|
| 117 |
+
percent = usage_percent(used, total, round_=1)
|
| 118 |
+
return _common.sswap(total, used, free, percent, sin, sout)
|
| 119 |
+
|
| 120 |
+
|
| 121 |
+
# =====================================================================
|
| 122 |
+
# --- CPU
|
| 123 |
+
# =====================================================================
|
| 124 |
+
|
| 125 |
+
|
| 126 |
+
def cpu_times():
|
| 127 |
+
"""Return system-wide CPU times as a named tuple."""
|
| 128 |
+
ret = cext.per_cpu_times()
|
| 129 |
+
return scputimes(*[sum(x) for x in zip(*ret)])
|
| 130 |
+
|
| 131 |
+
|
| 132 |
+
def per_cpu_times():
|
| 133 |
+
"""Return system per-CPU times as a list of named tuples."""
|
| 134 |
+
ret = cext.per_cpu_times()
|
| 135 |
+
return [scputimes(*x) for x in ret]
|
| 136 |
+
|
| 137 |
+
|
| 138 |
+
def cpu_count_logical():
|
| 139 |
+
"""Return the number of logical CPUs in the system."""
|
| 140 |
+
try:
|
| 141 |
+
return os.sysconf("SC_NPROCESSORS_ONLN")
|
| 142 |
+
except ValueError:
|
| 143 |
+
# mimic os.cpu_count() behavior
|
| 144 |
+
return None
|
| 145 |
+
|
| 146 |
+
|
| 147 |
+
def cpu_count_cores():
|
| 148 |
+
cmd = ["lsdev", "-Cc", "processor"]
|
| 149 |
+
p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
| 150 |
+
stdout, stderr = p.communicate()
|
| 151 |
+
if PY3:
|
| 152 |
+
stdout, stderr = (
|
| 153 |
+
x.decode(sys.stdout.encoding) for x in (stdout, stderr)
|
| 154 |
+
)
|
| 155 |
+
if p.returncode != 0:
|
| 156 |
+
raise RuntimeError("%r command error\n%s" % (cmd, stderr))
|
| 157 |
+
processors = stdout.strip().splitlines()
|
| 158 |
+
return len(processors) or None
|
| 159 |
+
|
| 160 |
+
|
| 161 |
+
def cpu_stats():
|
| 162 |
+
"""Return various CPU stats as a named tuple."""
|
| 163 |
+
ctx_switches, interrupts, soft_interrupts, syscalls = cext.cpu_stats()
|
| 164 |
+
return _common.scpustats(
|
| 165 |
+
ctx_switches, interrupts, soft_interrupts, syscalls
|
| 166 |
+
)
|
| 167 |
+
|
| 168 |
+
|
| 169 |
+
# =====================================================================
|
| 170 |
+
# --- disks
|
| 171 |
+
# =====================================================================
|
| 172 |
+
|
| 173 |
+
|
| 174 |
+
disk_io_counters = cext.disk_io_counters
|
| 175 |
+
disk_usage = _psposix.disk_usage
|
| 176 |
+
|
| 177 |
+
|
| 178 |
+
def disk_partitions(all=False):
|
| 179 |
+
"""Return system disk partitions."""
|
| 180 |
+
# TODO - the filtering logic should be better checked so that
|
| 181 |
+
# it tries to reflect 'df' as much as possible
|
| 182 |
+
retlist = []
|
| 183 |
+
partitions = cext.disk_partitions()
|
| 184 |
+
for partition in partitions:
|
| 185 |
+
device, mountpoint, fstype, opts = partition
|
| 186 |
+
if device == 'none':
|
| 187 |
+
device = ''
|
| 188 |
+
if not all:
|
| 189 |
+
# Differently from, say, Linux, we don't have a list of
|
| 190 |
+
# common fs types so the best we can do, AFAIK, is to
|
| 191 |
+
# filter by filesystem having a total size > 0.
|
| 192 |
+
if not disk_usage(mountpoint).total:
|
| 193 |
+
continue
|
| 194 |
+
ntuple = _common.sdiskpart(device, mountpoint, fstype, opts)
|
| 195 |
+
retlist.append(ntuple)
|
| 196 |
+
return retlist
|
| 197 |
+
|
| 198 |
+
|
| 199 |
+
# =====================================================================
|
| 200 |
+
# --- network
|
| 201 |
+
# =====================================================================
|
| 202 |
+
|
| 203 |
+
|
| 204 |
+
net_if_addrs = cext_posix.net_if_addrs
|
| 205 |
+
|
| 206 |
+
if HAS_NET_IO_COUNTERS:
|
| 207 |
+
net_io_counters = cext.net_io_counters
|
| 208 |
+
|
| 209 |
+
|
| 210 |
+
def net_connections(kind, _pid=-1):
|
| 211 |
+
"""Return socket connections. If pid == -1 return system-wide
|
| 212 |
+
connections (as opposed to connections opened by one process only).
|
| 213 |
+
"""
|
| 214 |
+
cmap = _common.conn_tmap
|
| 215 |
+
if kind not in cmap:
|
| 216 |
+
raise ValueError(
|
| 217 |
+
"invalid %r kind argument; choose between %s"
|
| 218 |
+
% (kind, ', '.join([repr(x) for x in cmap]))
|
| 219 |
+
)
|
| 220 |
+
families, types = _common.conn_tmap[kind]
|
| 221 |
+
rawlist = cext.net_connections(_pid)
|
| 222 |
+
ret = []
|
| 223 |
+
for item in rawlist:
|
| 224 |
+
fd, fam, type_, laddr, raddr, status, pid = item
|
| 225 |
+
if fam not in families:
|
| 226 |
+
continue
|
| 227 |
+
if type_ not in types:
|
| 228 |
+
continue
|
| 229 |
+
nt = conn_to_ntuple(
|
| 230 |
+
fd,
|
| 231 |
+
fam,
|
| 232 |
+
type_,
|
| 233 |
+
laddr,
|
| 234 |
+
raddr,
|
| 235 |
+
status,
|
| 236 |
+
TCP_STATUSES,
|
| 237 |
+
pid=pid if _pid == -1 else None,
|
| 238 |
+
)
|
| 239 |
+
ret.append(nt)
|
| 240 |
+
return ret
|
| 241 |
+
|
| 242 |
+
|
| 243 |
+
def net_if_stats():
|
| 244 |
+
"""Get NIC stats (isup, duplex, speed, mtu)."""
|
| 245 |
+
duplex_map = {"Full": NIC_DUPLEX_FULL, "Half": NIC_DUPLEX_HALF}
|
| 246 |
+
names = set([x[0] for x in net_if_addrs()])
|
| 247 |
+
ret = {}
|
| 248 |
+
for name in names:
|
| 249 |
+
mtu = cext_posix.net_if_mtu(name)
|
| 250 |
+
flags = cext_posix.net_if_flags(name)
|
| 251 |
+
|
| 252 |
+
# try to get speed and duplex
|
| 253 |
+
# TODO: rewrite this in C (entstat forks, so use truss -f to follow.
|
| 254 |
+
# looks like it is using an undocumented ioctl?)
|
| 255 |
+
duplex = ""
|
| 256 |
+
speed = 0
|
| 257 |
+
p = subprocess.Popen(
|
| 258 |
+
["/usr/bin/entstat", "-d", name],
|
| 259 |
+
stdout=subprocess.PIPE,
|
| 260 |
+
stderr=subprocess.PIPE,
|
| 261 |
+
)
|
| 262 |
+
stdout, stderr = p.communicate()
|
| 263 |
+
if PY3:
|
| 264 |
+
stdout, stderr = (
|
| 265 |
+
x.decode(sys.stdout.encoding) for x in (stdout, stderr)
|
| 266 |
+
)
|
| 267 |
+
if p.returncode == 0:
|
| 268 |
+
re_result = re.search(
|
| 269 |
+
r"Running: (\d+) Mbps.*?(\w+) Duplex", stdout
|
| 270 |
+
)
|
| 271 |
+
if re_result is not None:
|
| 272 |
+
speed = int(re_result.group(1))
|
| 273 |
+
duplex = re_result.group(2)
|
| 274 |
+
|
| 275 |
+
output_flags = ','.join(flags)
|
| 276 |
+
isup = 'running' in flags
|
| 277 |
+
duplex = duplex_map.get(duplex, NIC_DUPLEX_UNKNOWN)
|
| 278 |
+
ret[name] = _common.snicstats(isup, duplex, speed, mtu, output_flags)
|
| 279 |
+
return ret
|
| 280 |
+
|
| 281 |
+
|
| 282 |
+
# =====================================================================
|
| 283 |
+
# --- other system functions
|
| 284 |
+
# =====================================================================
|
| 285 |
+
|
| 286 |
+
|
| 287 |
+
def boot_time():
|
| 288 |
+
"""The system boot time expressed in seconds since the epoch."""
|
| 289 |
+
return cext.boot_time()
|
| 290 |
+
|
| 291 |
+
|
| 292 |
+
def users():
|
| 293 |
+
"""Return currently connected users as a list of namedtuples."""
|
| 294 |
+
retlist = []
|
| 295 |
+
rawlist = cext.users()
|
| 296 |
+
localhost = (':0.0', ':0')
|
| 297 |
+
for item in rawlist:
|
| 298 |
+
user, tty, hostname, tstamp, user_process, pid = item
|
| 299 |
+
# note: the underlying C function includes entries about
|
| 300 |
+
# system boot, run level and others. We might want
|
| 301 |
+
# to use them in the future.
|
| 302 |
+
if not user_process:
|
| 303 |
+
continue
|
| 304 |
+
if hostname in localhost:
|
| 305 |
+
hostname = 'localhost'
|
| 306 |
+
nt = _common.suser(user, tty, hostname, tstamp, pid)
|
| 307 |
+
retlist.append(nt)
|
| 308 |
+
return retlist
|
| 309 |
+
|
| 310 |
+
|
| 311 |
+
# =====================================================================
|
| 312 |
+
# --- processes
|
| 313 |
+
# =====================================================================
|
| 314 |
+
|
| 315 |
+
|
| 316 |
+
def pids():
|
| 317 |
+
"""Returns a list of PIDs currently running on the system."""
|
| 318 |
+
return [int(x) for x in os.listdir(get_procfs_path()) if x.isdigit()]
|
| 319 |
+
|
| 320 |
+
|
| 321 |
+
def pid_exists(pid):
|
| 322 |
+
"""Check for the existence of a unix pid."""
|
| 323 |
+
return os.path.exists(os.path.join(get_procfs_path(), str(pid), "psinfo"))
|
| 324 |
+
|
| 325 |
+
|
| 326 |
+
def wrap_exceptions(fun):
|
| 327 |
+
"""Call callable into a try/except clause and translate ENOENT,
|
| 328 |
+
EACCES and EPERM in NoSuchProcess or AccessDenied exceptions.
|
| 329 |
+
"""
|
| 330 |
+
|
| 331 |
+
@functools.wraps(fun)
|
| 332 |
+
def wrapper(self, *args, **kwargs):
|
| 333 |
+
try:
|
| 334 |
+
return fun(self, *args, **kwargs)
|
| 335 |
+
except (FileNotFoundError, ProcessLookupError):
|
| 336 |
+
# ENOENT (no such file or directory) gets raised on open().
|
| 337 |
+
# ESRCH (no such process) can get raised on read() if
|
| 338 |
+
# process is gone in meantime.
|
| 339 |
+
if not pid_exists(self.pid):
|
| 340 |
+
raise NoSuchProcess(self.pid, self._name)
|
| 341 |
+
else:
|
| 342 |
+
raise ZombieProcess(self.pid, self._name, self._ppid)
|
| 343 |
+
except PermissionError:
|
| 344 |
+
raise AccessDenied(self.pid, self._name)
|
| 345 |
+
|
| 346 |
+
return wrapper
|
| 347 |
+
|
| 348 |
+
|
| 349 |
+
class Process:
|
| 350 |
+
"""Wrapper class around underlying C implementation."""
|
| 351 |
+
|
| 352 |
+
__slots__ = ["_cache", "_name", "_ppid", "_procfs_path", "pid"]
|
| 353 |
+
|
| 354 |
+
def __init__(self, pid):
|
| 355 |
+
self.pid = pid
|
| 356 |
+
self._name = None
|
| 357 |
+
self._ppid = None
|
| 358 |
+
self._procfs_path = get_procfs_path()
|
| 359 |
+
|
| 360 |
+
def oneshot_enter(self):
|
| 361 |
+
self._proc_basic_info.cache_activate(self)
|
| 362 |
+
self._proc_cred.cache_activate(self)
|
| 363 |
+
|
| 364 |
+
def oneshot_exit(self):
|
| 365 |
+
self._proc_basic_info.cache_deactivate(self)
|
| 366 |
+
self._proc_cred.cache_deactivate(self)
|
| 367 |
+
|
| 368 |
+
@wrap_exceptions
|
| 369 |
+
@memoize_when_activated
|
| 370 |
+
def _proc_basic_info(self):
|
| 371 |
+
return cext.proc_basic_info(self.pid, self._procfs_path)
|
| 372 |
+
|
| 373 |
+
@wrap_exceptions
|
| 374 |
+
@memoize_when_activated
|
| 375 |
+
def _proc_cred(self):
|
| 376 |
+
return cext.proc_cred(self.pid, self._procfs_path)
|
| 377 |
+
|
| 378 |
+
@wrap_exceptions
|
| 379 |
+
def name(self):
|
| 380 |
+
if self.pid == 0:
|
| 381 |
+
return "swapper"
|
| 382 |
+
# note: max 16 characters
|
| 383 |
+
return cext.proc_name(self.pid, self._procfs_path).rstrip("\x00")
|
| 384 |
+
|
| 385 |
+
@wrap_exceptions
|
| 386 |
+
def exe(self):
|
| 387 |
+
# there is no way to get executable path in AIX other than to guess,
|
| 388 |
+
# and guessing is more complex than what's in the wrapping class
|
| 389 |
+
cmdline = self.cmdline()
|
| 390 |
+
if not cmdline:
|
| 391 |
+
return ''
|
| 392 |
+
exe = cmdline[0]
|
| 393 |
+
if os.path.sep in exe:
|
| 394 |
+
# relative or absolute path
|
| 395 |
+
if not os.path.isabs(exe):
|
| 396 |
+
# if cwd has changed, we're out of luck - this may be wrong!
|
| 397 |
+
exe = os.path.abspath(os.path.join(self.cwd(), exe))
|
| 398 |
+
if (
|
| 399 |
+
os.path.isabs(exe)
|
| 400 |
+
and os.path.isfile(exe)
|
| 401 |
+
and os.access(exe, os.X_OK)
|
| 402 |
+
):
|
| 403 |
+
return exe
|
| 404 |
+
# not found, move to search in PATH using basename only
|
| 405 |
+
exe = os.path.basename(exe)
|
| 406 |
+
# search for exe name PATH
|
| 407 |
+
for path in os.environ["PATH"].split(":"):
|
| 408 |
+
possible_exe = os.path.abspath(os.path.join(path, exe))
|
| 409 |
+
if os.path.isfile(possible_exe) and os.access(
|
| 410 |
+
possible_exe, os.X_OK
|
| 411 |
+
):
|
| 412 |
+
return possible_exe
|
| 413 |
+
return ''
|
| 414 |
+
|
| 415 |
+
@wrap_exceptions
|
| 416 |
+
def cmdline(self):
|
| 417 |
+
return cext.proc_args(self.pid)
|
| 418 |
+
|
| 419 |
+
@wrap_exceptions
|
| 420 |
+
def environ(self):
|
| 421 |
+
return cext.proc_environ(self.pid)
|
| 422 |
+
|
| 423 |
+
@wrap_exceptions
|
| 424 |
+
def create_time(self):
|
| 425 |
+
return self._proc_basic_info()[proc_info_map['create_time']]
|
| 426 |
+
|
| 427 |
+
@wrap_exceptions
|
| 428 |
+
def num_threads(self):
|
| 429 |
+
return self._proc_basic_info()[proc_info_map['num_threads']]
|
| 430 |
+
|
| 431 |
+
if HAS_THREADS:
|
| 432 |
+
|
| 433 |
+
@wrap_exceptions
|
| 434 |
+
def threads(self):
|
| 435 |
+
rawlist = cext.proc_threads(self.pid)
|
| 436 |
+
retlist = []
|
| 437 |
+
for thread_id, utime, stime in rawlist:
|
| 438 |
+
ntuple = _common.pthread(thread_id, utime, stime)
|
| 439 |
+
retlist.append(ntuple)
|
| 440 |
+
# The underlying C implementation retrieves all OS threads
|
| 441 |
+
# and filters them by PID. At this point we can't tell whether
|
| 442 |
+
# an empty list means there were no connections for process or
|
| 443 |
+
# process is no longer active so we force NSP in case the PID
|
| 444 |
+
# is no longer there.
|
| 445 |
+
if not retlist:
|
| 446 |
+
# will raise NSP if process is gone
|
| 447 |
+
os.stat('%s/%s' % (self._procfs_path, self.pid))
|
| 448 |
+
return retlist
|
| 449 |
+
|
| 450 |
+
@wrap_exceptions
|
| 451 |
+
def net_connections(self, kind='inet'):
|
| 452 |
+
ret = net_connections(kind, _pid=self.pid)
|
| 453 |
+
# The underlying C implementation retrieves all OS connections
|
| 454 |
+
# and filters them by PID. At this point we can't tell whether
|
| 455 |
+
# an empty list means there were no connections for process or
|
| 456 |
+
# process is no longer active so we force NSP in case the PID
|
| 457 |
+
# is no longer there.
|
| 458 |
+
if not ret:
|
| 459 |
+
# will raise NSP if process is gone
|
| 460 |
+
os.stat('%s/%s' % (self._procfs_path, self.pid))
|
| 461 |
+
return ret
|
| 462 |
+
|
| 463 |
+
@wrap_exceptions
|
| 464 |
+
def nice_get(self):
|
| 465 |
+
return cext_posix.getpriority(self.pid)
|
| 466 |
+
|
| 467 |
+
@wrap_exceptions
|
| 468 |
+
def nice_set(self, value):
|
| 469 |
+
return cext_posix.setpriority(self.pid, value)
|
| 470 |
+
|
| 471 |
+
@wrap_exceptions
|
| 472 |
+
def ppid(self):
|
| 473 |
+
self._ppid = self._proc_basic_info()[proc_info_map['ppid']]
|
| 474 |
+
return self._ppid
|
| 475 |
+
|
| 476 |
+
@wrap_exceptions
|
| 477 |
+
def uids(self):
|
| 478 |
+
real, effective, saved, _, _, _ = self._proc_cred()
|
| 479 |
+
return _common.puids(real, effective, saved)
|
| 480 |
+
|
| 481 |
+
@wrap_exceptions
|
| 482 |
+
def gids(self):
|
| 483 |
+
_, _, _, real, effective, saved = self._proc_cred()
|
| 484 |
+
return _common.puids(real, effective, saved)
|
| 485 |
+
|
| 486 |
+
@wrap_exceptions
|
| 487 |
+
def cpu_times(self):
|
| 488 |
+
t = cext.proc_cpu_times(self.pid, self._procfs_path)
|
| 489 |
+
return _common.pcputimes(*t)
|
| 490 |
+
|
| 491 |
+
@wrap_exceptions
|
| 492 |
+
def terminal(self):
|
| 493 |
+
ttydev = self._proc_basic_info()[proc_info_map['ttynr']]
|
| 494 |
+
# convert from 64-bit dev_t to 32-bit dev_t and then map the device
|
| 495 |
+
ttydev = ((ttydev & 0x0000FFFF00000000) >> 16) | (ttydev & 0xFFFF)
|
| 496 |
+
# try to match rdev of /dev/pts/* files ttydev
|
| 497 |
+
for dev in glob.glob("/dev/**/*"):
|
| 498 |
+
if os.stat(dev).st_rdev == ttydev:
|
| 499 |
+
return dev
|
| 500 |
+
return None
|
| 501 |
+
|
| 502 |
+
@wrap_exceptions
|
| 503 |
+
def cwd(self):
|
| 504 |
+
procfs_path = self._procfs_path
|
| 505 |
+
try:
|
| 506 |
+
result = os.readlink("%s/%s/cwd" % (procfs_path, self.pid))
|
| 507 |
+
return result.rstrip('/')
|
| 508 |
+
except FileNotFoundError:
|
| 509 |
+
os.stat("%s/%s" % (procfs_path, self.pid)) # raise NSP or AD
|
| 510 |
+
return ""
|
| 511 |
+
|
| 512 |
+
@wrap_exceptions
|
| 513 |
+
def memory_info(self):
|
| 514 |
+
ret = self._proc_basic_info()
|
| 515 |
+
rss = ret[proc_info_map['rss']] * 1024
|
| 516 |
+
vms = ret[proc_info_map['vms']] * 1024
|
| 517 |
+
return pmem(rss, vms)
|
| 518 |
+
|
| 519 |
+
memory_full_info = memory_info
|
| 520 |
+
|
| 521 |
+
@wrap_exceptions
|
| 522 |
+
def status(self):
|
| 523 |
+
code = self._proc_basic_info()[proc_info_map['status']]
|
| 524 |
+
# XXX is '?' legit? (we're not supposed to return it anyway)
|
| 525 |
+
return PROC_STATUSES.get(code, '?')
|
| 526 |
+
|
| 527 |
+
def open_files(self):
|
| 528 |
+
# TODO rewrite without using procfiles (stat /proc/pid/fd/* and then
|
| 529 |
+
# find matching name of the inode)
|
| 530 |
+
p = subprocess.Popen(
|
| 531 |
+
["/usr/bin/procfiles", "-n", str(self.pid)],
|
| 532 |
+
stdout=subprocess.PIPE,
|
| 533 |
+
stderr=subprocess.PIPE,
|
| 534 |
+
)
|
| 535 |
+
stdout, stderr = p.communicate()
|
| 536 |
+
if PY3:
|
| 537 |
+
stdout, stderr = (
|
| 538 |
+
x.decode(sys.stdout.encoding) for x in (stdout, stderr)
|
| 539 |
+
)
|
| 540 |
+
if "no such process" in stderr.lower():
|
| 541 |
+
raise NoSuchProcess(self.pid, self._name)
|
| 542 |
+
procfiles = re.findall(r"(\d+): S_IFREG.*name:(.*)\n", stdout)
|
| 543 |
+
retlist = []
|
| 544 |
+
for fd, path in procfiles:
|
| 545 |
+
path = path.strip()
|
| 546 |
+
if path.startswith("//"):
|
| 547 |
+
path = path[1:]
|
| 548 |
+
if path.lower() == "cannot be retrieved":
|
| 549 |
+
continue
|
| 550 |
+
retlist.append(_common.popenfile(path, int(fd)))
|
| 551 |
+
return retlist
|
| 552 |
+
|
| 553 |
+
@wrap_exceptions
|
| 554 |
+
def num_fds(self):
|
| 555 |
+
if self.pid == 0: # no /proc/0/fd
|
| 556 |
+
return 0
|
| 557 |
+
return len(os.listdir("%s/%s/fd" % (self._procfs_path, self.pid)))
|
| 558 |
+
|
| 559 |
+
@wrap_exceptions
|
| 560 |
+
def num_ctx_switches(self):
|
| 561 |
+
return _common.pctxsw(*cext.proc_num_ctx_switches(self.pid))
|
| 562 |
+
|
| 563 |
+
@wrap_exceptions
|
| 564 |
+
def wait(self, timeout=None):
|
| 565 |
+
return _psposix.wait_pid(self.pid, timeout, self._name)
|
| 566 |
+
|
| 567 |
+
if HAS_PROC_IO_COUNTERS:
|
| 568 |
+
|
| 569 |
+
@wrap_exceptions
|
| 570 |
+
def io_counters(self):
|
| 571 |
+
try:
|
| 572 |
+
rc, wc, rb, wb = cext.proc_io_counters(self.pid)
|
| 573 |
+
except OSError:
|
| 574 |
+
# if process is terminated, proc_io_counters returns OSError
|
| 575 |
+
# instead of NSP
|
| 576 |
+
if not pid_exists(self.pid):
|
| 577 |
+
raise NoSuchProcess(self.pid, self._name)
|
| 578 |
+
raise
|
| 579 |
+
return _common.pio(rc, wc, rb, wb)
|
.venv/lib/python3.11/site-packages/ray/thirdparty_files/psutil/_psbsd.py
ADDED
|
@@ -0,0 +1,985 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) 2009, Giampaolo Rodola'. All rights reserved.
|
| 2 |
+
# Use of this source code is governed by a BSD-style license that can be
|
| 3 |
+
# found in the LICENSE file.
|
| 4 |
+
|
| 5 |
+
"""FreeBSD, OpenBSD and NetBSD platforms implementation."""
|
| 6 |
+
|
| 7 |
+
import contextlib
|
| 8 |
+
import errno
|
| 9 |
+
import functools
|
| 10 |
+
import os
|
| 11 |
+
from collections import defaultdict
|
| 12 |
+
from collections import namedtuple
|
| 13 |
+
from xml.etree import ElementTree # noqa ICN001
|
| 14 |
+
|
| 15 |
+
from . import _common
|
| 16 |
+
from . import _psposix
|
| 17 |
+
from . import _psutil_bsd as cext
|
| 18 |
+
from . import _psutil_posix as cext_posix
|
| 19 |
+
from ._common import FREEBSD
|
| 20 |
+
from ._common import NETBSD
|
| 21 |
+
from ._common import OPENBSD
|
| 22 |
+
from ._common import AccessDenied
|
| 23 |
+
from ._common import NoSuchProcess
|
| 24 |
+
from ._common import ZombieProcess
|
| 25 |
+
from ._common import conn_tmap
|
| 26 |
+
from ._common import conn_to_ntuple
|
| 27 |
+
from ._common import debug
|
| 28 |
+
from ._common import memoize
|
| 29 |
+
from ._common import memoize_when_activated
|
| 30 |
+
from ._common import usage_percent
|
| 31 |
+
from ._compat import FileNotFoundError
|
| 32 |
+
from ._compat import PermissionError
|
| 33 |
+
from ._compat import ProcessLookupError
|
| 34 |
+
from ._compat import which
|
| 35 |
+
|
| 36 |
+
|
| 37 |
+
__extra__all__ = []
|
| 38 |
+
|
| 39 |
+
|
| 40 |
+
# =====================================================================
|
| 41 |
+
# --- globals
|
| 42 |
+
# =====================================================================
|
| 43 |
+
|
| 44 |
+
|
| 45 |
+
if FREEBSD:
|
| 46 |
+
PROC_STATUSES = {
|
| 47 |
+
cext.SIDL: _common.STATUS_IDLE,
|
| 48 |
+
cext.SRUN: _common.STATUS_RUNNING,
|
| 49 |
+
cext.SSLEEP: _common.STATUS_SLEEPING,
|
| 50 |
+
cext.SSTOP: _common.STATUS_STOPPED,
|
| 51 |
+
cext.SZOMB: _common.STATUS_ZOMBIE,
|
| 52 |
+
cext.SWAIT: _common.STATUS_WAITING,
|
| 53 |
+
cext.SLOCK: _common.STATUS_LOCKED,
|
| 54 |
+
}
|
| 55 |
+
elif OPENBSD:
|
| 56 |
+
PROC_STATUSES = {
|
| 57 |
+
cext.SIDL: _common.STATUS_IDLE,
|
| 58 |
+
cext.SSLEEP: _common.STATUS_SLEEPING,
|
| 59 |
+
cext.SSTOP: _common.STATUS_STOPPED,
|
| 60 |
+
# According to /usr/include/sys/proc.h SZOMB is unused.
|
| 61 |
+
# test_zombie_process() shows that SDEAD is the right
|
| 62 |
+
# equivalent. Also it appears there's no equivalent of
|
| 63 |
+
# psutil.STATUS_DEAD. SDEAD really means STATUS_ZOMBIE.
|
| 64 |
+
# cext.SZOMB: _common.STATUS_ZOMBIE,
|
| 65 |
+
cext.SDEAD: _common.STATUS_ZOMBIE,
|
| 66 |
+
cext.SZOMB: _common.STATUS_ZOMBIE,
|
| 67 |
+
# From http://www.eecs.harvard.edu/~margo/cs161/videos/proc.h.txt
|
| 68 |
+
# OpenBSD has SRUN and SONPROC: SRUN indicates that a process
|
| 69 |
+
# is runnable but *not* yet running, i.e. is on a run queue.
|
| 70 |
+
# SONPROC indicates that the process is actually executing on
|
| 71 |
+
# a CPU, i.e. it is no longer on a run queue.
|
| 72 |
+
# As such we'll map SRUN to STATUS_WAKING and SONPROC to
|
| 73 |
+
# STATUS_RUNNING
|
| 74 |
+
cext.SRUN: _common.STATUS_WAKING,
|
| 75 |
+
cext.SONPROC: _common.STATUS_RUNNING,
|
| 76 |
+
}
|
| 77 |
+
elif NETBSD:
|
| 78 |
+
PROC_STATUSES = {
|
| 79 |
+
cext.SIDL: _common.STATUS_IDLE,
|
| 80 |
+
cext.SSLEEP: _common.STATUS_SLEEPING,
|
| 81 |
+
cext.SSTOP: _common.STATUS_STOPPED,
|
| 82 |
+
cext.SZOMB: _common.STATUS_ZOMBIE,
|
| 83 |
+
cext.SRUN: _common.STATUS_WAKING,
|
| 84 |
+
cext.SONPROC: _common.STATUS_RUNNING,
|
| 85 |
+
}
|
| 86 |
+
|
| 87 |
+
TCP_STATUSES = {
|
| 88 |
+
cext.TCPS_ESTABLISHED: _common.CONN_ESTABLISHED,
|
| 89 |
+
cext.TCPS_SYN_SENT: _common.CONN_SYN_SENT,
|
| 90 |
+
cext.TCPS_SYN_RECEIVED: _common.CONN_SYN_RECV,
|
| 91 |
+
cext.TCPS_FIN_WAIT_1: _common.CONN_FIN_WAIT1,
|
| 92 |
+
cext.TCPS_FIN_WAIT_2: _common.CONN_FIN_WAIT2,
|
| 93 |
+
cext.TCPS_TIME_WAIT: _common.CONN_TIME_WAIT,
|
| 94 |
+
cext.TCPS_CLOSED: _common.CONN_CLOSE,
|
| 95 |
+
cext.TCPS_CLOSE_WAIT: _common.CONN_CLOSE_WAIT,
|
| 96 |
+
cext.TCPS_LAST_ACK: _common.CONN_LAST_ACK,
|
| 97 |
+
cext.TCPS_LISTEN: _common.CONN_LISTEN,
|
| 98 |
+
cext.TCPS_CLOSING: _common.CONN_CLOSING,
|
| 99 |
+
cext.PSUTIL_CONN_NONE: _common.CONN_NONE,
|
| 100 |
+
}
|
| 101 |
+
|
| 102 |
+
PAGESIZE = cext_posix.getpagesize()
|
| 103 |
+
AF_LINK = cext_posix.AF_LINK
|
| 104 |
+
|
| 105 |
+
HAS_PER_CPU_TIMES = hasattr(cext, "per_cpu_times")
|
| 106 |
+
HAS_PROC_NUM_THREADS = hasattr(cext, "proc_num_threads")
|
| 107 |
+
HAS_PROC_OPEN_FILES = hasattr(cext, 'proc_open_files')
|
| 108 |
+
HAS_PROC_NUM_FDS = hasattr(cext, 'proc_num_fds')
|
| 109 |
+
|
| 110 |
+
kinfo_proc_map = dict(
|
| 111 |
+
ppid=0,
|
| 112 |
+
status=1,
|
| 113 |
+
real_uid=2,
|
| 114 |
+
effective_uid=3,
|
| 115 |
+
saved_uid=4,
|
| 116 |
+
real_gid=5,
|
| 117 |
+
effective_gid=6,
|
| 118 |
+
saved_gid=7,
|
| 119 |
+
ttynr=8,
|
| 120 |
+
create_time=9,
|
| 121 |
+
ctx_switches_vol=10,
|
| 122 |
+
ctx_switches_unvol=11,
|
| 123 |
+
read_io_count=12,
|
| 124 |
+
write_io_count=13,
|
| 125 |
+
user_time=14,
|
| 126 |
+
sys_time=15,
|
| 127 |
+
ch_user_time=16,
|
| 128 |
+
ch_sys_time=17,
|
| 129 |
+
rss=18,
|
| 130 |
+
vms=19,
|
| 131 |
+
memtext=20,
|
| 132 |
+
memdata=21,
|
| 133 |
+
memstack=22,
|
| 134 |
+
cpunum=23,
|
| 135 |
+
name=24,
|
| 136 |
+
)
|
| 137 |
+
|
| 138 |
+
|
| 139 |
+
# =====================================================================
|
| 140 |
+
# --- named tuples
|
| 141 |
+
# =====================================================================
|
| 142 |
+
|
| 143 |
+
|
| 144 |
+
# fmt: off
|
| 145 |
+
# psutil.virtual_memory()
|
| 146 |
+
svmem = namedtuple(
|
| 147 |
+
'svmem', ['total', 'available', 'percent', 'used', 'free',
|
| 148 |
+
'active', 'inactive', 'buffers', 'cached', 'shared', 'wired'])
|
| 149 |
+
# psutil.cpu_times()
|
| 150 |
+
scputimes = namedtuple(
|
| 151 |
+
'scputimes', ['user', 'nice', 'system', 'idle', 'irq'])
|
| 152 |
+
# psutil.Process.memory_info()
|
| 153 |
+
pmem = namedtuple('pmem', ['rss', 'vms', 'text', 'data', 'stack'])
|
| 154 |
+
# psutil.Process.memory_full_info()
|
| 155 |
+
pfullmem = pmem
|
| 156 |
+
# psutil.Process.cpu_times()
|
| 157 |
+
pcputimes = namedtuple('pcputimes',
|
| 158 |
+
['user', 'system', 'children_user', 'children_system'])
|
| 159 |
+
# psutil.Process.memory_maps(grouped=True)
|
| 160 |
+
pmmap_grouped = namedtuple(
|
| 161 |
+
'pmmap_grouped', 'path rss, private, ref_count, shadow_count')
|
| 162 |
+
# psutil.Process.memory_maps(grouped=False)
|
| 163 |
+
pmmap_ext = namedtuple(
|
| 164 |
+
'pmmap_ext', 'addr, perms path rss, private, ref_count, shadow_count')
|
| 165 |
+
# psutil.disk_io_counters()
|
| 166 |
+
if FREEBSD:
|
| 167 |
+
sdiskio = namedtuple('sdiskio', ['read_count', 'write_count',
|
| 168 |
+
'read_bytes', 'write_bytes',
|
| 169 |
+
'read_time', 'write_time',
|
| 170 |
+
'busy_time'])
|
| 171 |
+
else:
|
| 172 |
+
sdiskio = namedtuple('sdiskio', ['read_count', 'write_count',
|
| 173 |
+
'read_bytes', 'write_bytes'])
|
| 174 |
+
# fmt: on
|
| 175 |
+
|
| 176 |
+
|
| 177 |
+
# =====================================================================
|
| 178 |
+
# --- memory
|
| 179 |
+
# =====================================================================
|
| 180 |
+
|
| 181 |
+
|
| 182 |
+
def virtual_memory():
|
| 183 |
+
mem = cext.virtual_mem()
|
| 184 |
+
if NETBSD:
|
| 185 |
+
total, free, active, inactive, wired, cached = mem
|
| 186 |
+
# On NetBSD buffers and shared mem is determined via /proc.
|
| 187 |
+
# The C ext set them to 0.
|
| 188 |
+
with open('/proc/meminfo', 'rb') as f:
|
| 189 |
+
for line in f:
|
| 190 |
+
if line.startswith(b'Buffers:'):
|
| 191 |
+
buffers = int(line.split()[1]) * 1024
|
| 192 |
+
elif line.startswith(b'MemShared:'):
|
| 193 |
+
shared = int(line.split()[1]) * 1024
|
| 194 |
+
# Before avail was calculated as (inactive + cached + free),
|
| 195 |
+
# same as zabbix, but it turned out it could exceed total (see
|
| 196 |
+
# #2233), so zabbix seems to be wrong. Htop calculates it
|
| 197 |
+
# differently, and the used value seem more realistic, so let's
|
| 198 |
+
# match htop.
|
| 199 |
+
# https://github.com/htop-dev/htop/blob/e7f447b/netbsd/NetBSDProcessList.c#L162 # noqa
|
| 200 |
+
# https://github.com/zabbix/zabbix/blob/af5e0f8/src/libs/zbxsysinfo/netbsd/memory.c#L135 # noqa
|
| 201 |
+
used = active + wired
|
| 202 |
+
avail = total - used
|
| 203 |
+
else:
|
| 204 |
+
total, free, active, inactive, wired, cached, buffers, shared = mem
|
| 205 |
+
# matches freebsd-memory CLI:
|
| 206 |
+
# * https://people.freebsd.org/~rse/dist/freebsd-memory
|
| 207 |
+
# * https://www.cyberciti.biz/files/scripts/freebsd-memory.pl.txt
|
| 208 |
+
# matches zabbix:
|
| 209 |
+
# * https://github.com/zabbix/zabbix/blob/af5e0f8/src/libs/zbxsysinfo/freebsd/memory.c#L143 # noqa
|
| 210 |
+
avail = inactive + cached + free
|
| 211 |
+
used = active + wired + cached
|
| 212 |
+
|
| 213 |
+
percent = usage_percent((total - avail), total, round_=1)
|
| 214 |
+
return svmem(
|
| 215 |
+
total,
|
| 216 |
+
avail,
|
| 217 |
+
percent,
|
| 218 |
+
used,
|
| 219 |
+
free,
|
| 220 |
+
active,
|
| 221 |
+
inactive,
|
| 222 |
+
buffers,
|
| 223 |
+
cached,
|
| 224 |
+
shared,
|
| 225 |
+
wired,
|
| 226 |
+
)
|
| 227 |
+
|
| 228 |
+
|
| 229 |
+
def swap_memory():
|
| 230 |
+
"""System swap memory as (total, used, free, sin, sout) namedtuple."""
|
| 231 |
+
total, used, free, sin, sout = cext.swap_mem()
|
| 232 |
+
percent = usage_percent(used, total, round_=1)
|
| 233 |
+
return _common.sswap(total, used, free, percent, sin, sout)
|
| 234 |
+
|
| 235 |
+
|
| 236 |
+
# =====================================================================
|
| 237 |
+
# --- CPU
|
| 238 |
+
# =====================================================================
|
| 239 |
+
|
| 240 |
+
|
| 241 |
+
def cpu_times():
|
| 242 |
+
"""Return system per-CPU times as a namedtuple."""
|
| 243 |
+
user, nice, system, idle, irq = cext.cpu_times()
|
| 244 |
+
return scputimes(user, nice, system, idle, irq)
|
| 245 |
+
|
| 246 |
+
|
| 247 |
+
if HAS_PER_CPU_TIMES:
|
| 248 |
+
|
| 249 |
+
def per_cpu_times():
|
| 250 |
+
"""Return system CPU times as a namedtuple."""
|
| 251 |
+
ret = []
|
| 252 |
+
for cpu_t in cext.per_cpu_times():
|
| 253 |
+
user, nice, system, idle, irq = cpu_t
|
| 254 |
+
item = scputimes(user, nice, system, idle, irq)
|
| 255 |
+
ret.append(item)
|
| 256 |
+
return ret
|
| 257 |
+
|
| 258 |
+
else:
|
| 259 |
+
# XXX
|
| 260 |
+
# Ok, this is very dirty.
|
| 261 |
+
# On FreeBSD < 8 we cannot gather per-cpu information, see:
|
| 262 |
+
# https://github.com/giampaolo/psutil/issues/226
|
| 263 |
+
# If num cpus > 1, on first call we return single cpu times to avoid a
|
| 264 |
+
# crash at psutil import time.
|
| 265 |
+
# Next calls will fail with NotImplementedError
|
| 266 |
+
def per_cpu_times():
|
| 267 |
+
"""Return system CPU times as a namedtuple."""
|
| 268 |
+
if cpu_count_logical() == 1:
|
| 269 |
+
return [cpu_times()]
|
| 270 |
+
if per_cpu_times.__called__:
|
| 271 |
+
msg = "supported only starting from FreeBSD 8"
|
| 272 |
+
raise NotImplementedError(msg)
|
| 273 |
+
per_cpu_times.__called__ = True
|
| 274 |
+
return [cpu_times()]
|
| 275 |
+
|
| 276 |
+
per_cpu_times.__called__ = False
|
| 277 |
+
|
| 278 |
+
|
| 279 |
+
def cpu_count_logical():
|
| 280 |
+
"""Return the number of logical CPUs in the system."""
|
| 281 |
+
return cext.cpu_count_logical()
|
| 282 |
+
|
| 283 |
+
|
| 284 |
+
if OPENBSD or NETBSD:
|
| 285 |
+
|
| 286 |
+
def cpu_count_cores():
|
| 287 |
+
# OpenBSD and NetBSD do not implement this.
|
| 288 |
+
return 1 if cpu_count_logical() == 1 else None
|
| 289 |
+
|
| 290 |
+
else:
|
| 291 |
+
|
| 292 |
+
def cpu_count_cores():
|
| 293 |
+
"""Return the number of CPU cores in the system."""
|
| 294 |
+
# From the C module we'll get an XML string similar to this:
|
| 295 |
+
# http://manpages.ubuntu.com/manpages/precise/man4/smp.4freebsd.html
|
| 296 |
+
# We may get None in case "sysctl kern.sched.topology_spec"
|
| 297 |
+
# is not supported on this BSD version, in which case we'll mimic
|
| 298 |
+
# os.cpu_count() and return None.
|
| 299 |
+
ret = None
|
| 300 |
+
s = cext.cpu_topology()
|
| 301 |
+
if s is not None:
|
| 302 |
+
# get rid of padding chars appended at the end of the string
|
| 303 |
+
index = s.rfind("</groups>")
|
| 304 |
+
if index != -1:
|
| 305 |
+
s = s[: index + 9]
|
| 306 |
+
root = ElementTree.fromstring(s)
|
| 307 |
+
try:
|
| 308 |
+
ret = len(root.findall('group/children/group/cpu')) or None
|
| 309 |
+
finally:
|
| 310 |
+
# needed otherwise it will memleak
|
| 311 |
+
root.clear()
|
| 312 |
+
if not ret:
|
| 313 |
+
# If logical CPUs == 1 it's obvious we' have only 1 core.
|
| 314 |
+
if cpu_count_logical() == 1:
|
| 315 |
+
return 1
|
| 316 |
+
return ret
|
| 317 |
+
|
| 318 |
+
|
| 319 |
+
def cpu_stats():
|
| 320 |
+
"""Return various CPU stats as a named tuple."""
|
| 321 |
+
if FREEBSD:
|
| 322 |
+
# Note: the C ext is returning some metrics we are not exposing:
|
| 323 |
+
# traps.
|
| 324 |
+
ctxsw, intrs, soft_intrs, syscalls, _traps = cext.cpu_stats()
|
| 325 |
+
elif NETBSD:
|
| 326 |
+
# XXX
|
| 327 |
+
# Note about intrs: the C extension returns 0. intrs
|
| 328 |
+
# can be determined via /proc/stat; it has the same value as
|
| 329 |
+
# soft_intrs thought so the kernel is faking it (?).
|
| 330 |
+
#
|
| 331 |
+
# Note about syscalls: the C extension always sets it to 0 (?).
|
| 332 |
+
#
|
| 333 |
+
# Note: the C ext is returning some metrics we are not exposing:
|
| 334 |
+
# traps, faults and forks.
|
| 335 |
+
ctxsw, intrs, soft_intrs, syscalls, _traps, _faults, _forks = (
|
| 336 |
+
cext.cpu_stats()
|
| 337 |
+
)
|
| 338 |
+
with open('/proc/stat', 'rb') as f:
|
| 339 |
+
for line in f:
|
| 340 |
+
if line.startswith(b'intr'):
|
| 341 |
+
intrs = int(line.split()[1])
|
| 342 |
+
elif OPENBSD:
|
| 343 |
+
# Note: the C ext is returning some metrics we are not exposing:
|
| 344 |
+
# traps, faults and forks.
|
| 345 |
+
ctxsw, intrs, soft_intrs, syscalls, _traps, _faults, _forks = (
|
| 346 |
+
cext.cpu_stats()
|
| 347 |
+
)
|
| 348 |
+
return _common.scpustats(ctxsw, intrs, soft_intrs, syscalls)
|
| 349 |
+
|
| 350 |
+
|
| 351 |
+
if FREEBSD:
|
| 352 |
+
|
| 353 |
+
def cpu_freq():
|
| 354 |
+
"""Return frequency metrics for CPUs. As of Dec 2018 only
|
| 355 |
+
CPU 0 appears to be supported by FreeBSD and all other cores
|
| 356 |
+
match the frequency of CPU 0.
|
| 357 |
+
"""
|
| 358 |
+
ret = []
|
| 359 |
+
num_cpus = cpu_count_logical()
|
| 360 |
+
for cpu in range(num_cpus):
|
| 361 |
+
try:
|
| 362 |
+
current, available_freq = cext.cpu_freq(cpu)
|
| 363 |
+
except NotImplementedError:
|
| 364 |
+
continue
|
| 365 |
+
if available_freq:
|
| 366 |
+
try:
|
| 367 |
+
min_freq = int(available_freq.split(" ")[-1].split("/")[0])
|
| 368 |
+
except (IndexError, ValueError):
|
| 369 |
+
min_freq = None
|
| 370 |
+
try:
|
| 371 |
+
max_freq = int(available_freq.split(" ")[0].split("/")[0])
|
| 372 |
+
except (IndexError, ValueError):
|
| 373 |
+
max_freq = None
|
| 374 |
+
ret.append(_common.scpufreq(current, min_freq, max_freq))
|
| 375 |
+
return ret
|
| 376 |
+
|
| 377 |
+
elif OPENBSD:
|
| 378 |
+
|
| 379 |
+
def cpu_freq():
|
| 380 |
+
curr = float(cext.cpu_freq())
|
| 381 |
+
return [_common.scpufreq(curr, 0.0, 0.0)]
|
| 382 |
+
|
| 383 |
+
|
| 384 |
+
# =====================================================================
|
| 385 |
+
# --- disks
|
| 386 |
+
# =====================================================================
|
| 387 |
+
|
| 388 |
+
|
| 389 |
+
def disk_partitions(all=False):
|
| 390 |
+
"""Return mounted disk partitions as a list of namedtuples.
|
| 391 |
+
'all' argument is ignored, see:
|
| 392 |
+
https://github.com/giampaolo/psutil/issues/906.
|
| 393 |
+
"""
|
| 394 |
+
retlist = []
|
| 395 |
+
partitions = cext.disk_partitions()
|
| 396 |
+
for partition in partitions:
|
| 397 |
+
device, mountpoint, fstype, opts = partition
|
| 398 |
+
ntuple = _common.sdiskpart(device, mountpoint, fstype, opts)
|
| 399 |
+
retlist.append(ntuple)
|
| 400 |
+
return retlist
|
| 401 |
+
|
| 402 |
+
|
| 403 |
+
disk_usage = _psposix.disk_usage
|
| 404 |
+
disk_io_counters = cext.disk_io_counters
|
| 405 |
+
|
| 406 |
+
|
| 407 |
+
# =====================================================================
|
| 408 |
+
# --- network
|
| 409 |
+
# =====================================================================
|
| 410 |
+
|
| 411 |
+
|
| 412 |
+
net_io_counters = cext.net_io_counters
|
| 413 |
+
net_if_addrs = cext_posix.net_if_addrs
|
| 414 |
+
|
| 415 |
+
|
| 416 |
+
def net_if_stats():
|
| 417 |
+
"""Get NIC stats (isup, duplex, speed, mtu)."""
|
| 418 |
+
names = net_io_counters().keys()
|
| 419 |
+
ret = {}
|
| 420 |
+
for name in names:
|
| 421 |
+
try:
|
| 422 |
+
mtu = cext_posix.net_if_mtu(name)
|
| 423 |
+
flags = cext_posix.net_if_flags(name)
|
| 424 |
+
duplex, speed = cext_posix.net_if_duplex_speed(name)
|
| 425 |
+
except OSError as err:
|
| 426 |
+
# https://github.com/giampaolo/psutil/issues/1279
|
| 427 |
+
if err.errno != errno.ENODEV:
|
| 428 |
+
raise
|
| 429 |
+
else:
|
| 430 |
+
if hasattr(_common, 'NicDuplex'):
|
| 431 |
+
duplex = _common.NicDuplex(duplex)
|
| 432 |
+
output_flags = ','.join(flags)
|
| 433 |
+
isup = 'running' in flags
|
| 434 |
+
ret[name] = _common.snicstats(
|
| 435 |
+
isup, duplex, speed, mtu, output_flags
|
| 436 |
+
)
|
| 437 |
+
return ret
|
| 438 |
+
|
| 439 |
+
|
| 440 |
+
def net_connections(kind):
|
| 441 |
+
"""System-wide network connections."""
|
| 442 |
+
if kind not in _common.conn_tmap:
|
| 443 |
+
raise ValueError(
|
| 444 |
+
"invalid %r kind argument; choose between %s"
|
| 445 |
+
% (kind, ', '.join([repr(x) for x in conn_tmap]))
|
| 446 |
+
)
|
| 447 |
+
families, types = conn_tmap[kind]
|
| 448 |
+
ret = set()
|
| 449 |
+
|
| 450 |
+
if OPENBSD:
|
| 451 |
+
rawlist = cext.net_connections(-1, families, types)
|
| 452 |
+
elif NETBSD:
|
| 453 |
+
rawlist = cext.net_connections(-1, kind)
|
| 454 |
+
else: # FreeBSD
|
| 455 |
+
rawlist = cext.net_connections(families, types)
|
| 456 |
+
|
| 457 |
+
for item in rawlist:
|
| 458 |
+
fd, fam, type, laddr, raddr, status, pid = item
|
| 459 |
+
nt = conn_to_ntuple(
|
| 460 |
+
fd, fam, type, laddr, raddr, status, TCP_STATUSES, pid
|
| 461 |
+
)
|
| 462 |
+
ret.add(nt)
|
| 463 |
+
return list(ret)
|
| 464 |
+
|
| 465 |
+
|
| 466 |
+
# =====================================================================
|
| 467 |
+
# --- sensors
|
| 468 |
+
# =====================================================================
|
| 469 |
+
|
| 470 |
+
|
| 471 |
+
if FREEBSD:
|
| 472 |
+
|
| 473 |
+
def sensors_battery():
|
| 474 |
+
"""Return battery info."""
|
| 475 |
+
try:
|
| 476 |
+
percent, minsleft, power_plugged = cext.sensors_battery()
|
| 477 |
+
except NotImplementedError:
|
| 478 |
+
# See: https://github.com/giampaolo/psutil/issues/1074
|
| 479 |
+
return None
|
| 480 |
+
power_plugged = power_plugged == 1
|
| 481 |
+
if power_plugged:
|
| 482 |
+
secsleft = _common.POWER_TIME_UNLIMITED
|
| 483 |
+
elif minsleft == -1:
|
| 484 |
+
secsleft = _common.POWER_TIME_UNKNOWN
|
| 485 |
+
else:
|
| 486 |
+
secsleft = minsleft * 60
|
| 487 |
+
return _common.sbattery(percent, secsleft, power_plugged)
|
| 488 |
+
|
| 489 |
+
def sensors_temperatures():
|
| 490 |
+
"""Return CPU cores temperatures if available, else an empty dict."""
|
| 491 |
+
ret = defaultdict(list)
|
| 492 |
+
num_cpus = cpu_count_logical()
|
| 493 |
+
for cpu in range(num_cpus):
|
| 494 |
+
try:
|
| 495 |
+
current, high = cext.sensors_cpu_temperature(cpu)
|
| 496 |
+
if high <= 0:
|
| 497 |
+
high = None
|
| 498 |
+
name = "Core %s" % cpu
|
| 499 |
+
ret["coretemp"].append(
|
| 500 |
+
_common.shwtemp(name, current, high, high)
|
| 501 |
+
)
|
| 502 |
+
except NotImplementedError:
|
| 503 |
+
pass
|
| 504 |
+
|
| 505 |
+
return ret
|
| 506 |
+
|
| 507 |
+
|
| 508 |
+
# =====================================================================
|
| 509 |
+
# --- other system functions
|
| 510 |
+
# =====================================================================
|
| 511 |
+
|
| 512 |
+
|
| 513 |
+
def boot_time():
|
| 514 |
+
"""The system boot time expressed in seconds since the epoch."""
|
| 515 |
+
return cext.boot_time()
|
| 516 |
+
|
| 517 |
+
|
| 518 |
+
def users():
|
| 519 |
+
"""Return currently connected users as a list of namedtuples."""
|
| 520 |
+
retlist = []
|
| 521 |
+
rawlist = cext.users()
|
| 522 |
+
for item in rawlist:
|
| 523 |
+
user, tty, hostname, tstamp, pid = item
|
| 524 |
+
if pid == -1:
|
| 525 |
+
assert OPENBSD
|
| 526 |
+
pid = None
|
| 527 |
+
if tty == '~':
|
| 528 |
+
continue # reboot or shutdown
|
| 529 |
+
nt = _common.suser(user, tty or None, hostname, tstamp, pid)
|
| 530 |
+
retlist.append(nt)
|
| 531 |
+
return retlist
|
| 532 |
+
|
| 533 |
+
|
| 534 |
+
# =====================================================================
|
| 535 |
+
# --- processes
|
| 536 |
+
# =====================================================================
|
| 537 |
+
|
| 538 |
+
|
| 539 |
+
@memoize
|
| 540 |
+
def _pid_0_exists():
|
| 541 |
+
try:
|
| 542 |
+
Process(0).name()
|
| 543 |
+
except NoSuchProcess:
|
| 544 |
+
return False
|
| 545 |
+
except AccessDenied:
|
| 546 |
+
return True
|
| 547 |
+
else:
|
| 548 |
+
return True
|
| 549 |
+
|
| 550 |
+
|
| 551 |
+
def pids():
|
| 552 |
+
"""Returns a list of PIDs currently running on the system."""
|
| 553 |
+
ret = cext.pids()
|
| 554 |
+
if OPENBSD and (0 not in ret) and _pid_0_exists():
|
| 555 |
+
# On OpenBSD the kernel does not return PID 0 (neither does
|
| 556 |
+
# ps) but it's actually querable (Process(0) will succeed).
|
| 557 |
+
ret.insert(0, 0)
|
| 558 |
+
return ret
|
| 559 |
+
|
| 560 |
+
|
| 561 |
+
if NETBSD:
|
| 562 |
+
|
| 563 |
+
def pid_exists(pid):
|
| 564 |
+
exists = _psposix.pid_exists(pid)
|
| 565 |
+
if not exists:
|
| 566 |
+
# We do this because _psposix.pid_exists() lies in case of
|
| 567 |
+
# zombie processes.
|
| 568 |
+
return pid in pids()
|
| 569 |
+
else:
|
| 570 |
+
return True
|
| 571 |
+
|
| 572 |
+
elif OPENBSD:
|
| 573 |
+
|
| 574 |
+
def pid_exists(pid):
|
| 575 |
+
exists = _psposix.pid_exists(pid)
|
| 576 |
+
if not exists:
|
| 577 |
+
return False
|
| 578 |
+
else:
|
| 579 |
+
# OpenBSD seems to be the only BSD platform where
|
| 580 |
+
# _psposix.pid_exists() returns True for thread IDs (tids),
|
| 581 |
+
# so we can't use it.
|
| 582 |
+
return pid in pids()
|
| 583 |
+
|
| 584 |
+
else: # FreeBSD
|
| 585 |
+
pid_exists = _psposix.pid_exists
|
| 586 |
+
|
| 587 |
+
|
| 588 |
+
def is_zombie(pid):
|
| 589 |
+
try:
|
| 590 |
+
st = cext.proc_oneshot_info(pid)[kinfo_proc_map['status']]
|
| 591 |
+
return PROC_STATUSES.get(st) == _common.STATUS_ZOMBIE
|
| 592 |
+
except OSError:
|
| 593 |
+
return False
|
| 594 |
+
|
| 595 |
+
|
| 596 |
+
def wrap_exceptions(fun):
|
| 597 |
+
"""Decorator which translates bare OSError exceptions into
|
| 598 |
+
NoSuchProcess and AccessDenied.
|
| 599 |
+
"""
|
| 600 |
+
|
| 601 |
+
@functools.wraps(fun)
|
| 602 |
+
def wrapper(self, *args, **kwargs):
|
| 603 |
+
try:
|
| 604 |
+
return fun(self, *args, **kwargs)
|
| 605 |
+
except ProcessLookupError:
|
| 606 |
+
if is_zombie(self.pid):
|
| 607 |
+
raise ZombieProcess(self.pid, self._name, self._ppid)
|
| 608 |
+
else:
|
| 609 |
+
raise NoSuchProcess(self.pid, self._name)
|
| 610 |
+
except PermissionError:
|
| 611 |
+
raise AccessDenied(self.pid, self._name)
|
| 612 |
+
except OSError:
|
| 613 |
+
if self.pid == 0:
|
| 614 |
+
if 0 in pids():
|
| 615 |
+
raise AccessDenied(self.pid, self._name)
|
| 616 |
+
else:
|
| 617 |
+
raise
|
| 618 |
+
raise
|
| 619 |
+
|
| 620 |
+
return wrapper
|
| 621 |
+
|
| 622 |
+
|
| 623 |
+
@contextlib.contextmanager
|
| 624 |
+
def wrap_exceptions_procfs(inst):
|
| 625 |
+
"""Same as above, for routines relying on reading /proc fs."""
|
| 626 |
+
try:
|
| 627 |
+
yield
|
| 628 |
+
except (ProcessLookupError, FileNotFoundError):
|
| 629 |
+
# ENOENT (no such file or directory) gets raised on open().
|
| 630 |
+
# ESRCH (no such process) can get raised on read() if
|
| 631 |
+
# process is gone in meantime.
|
| 632 |
+
if is_zombie(inst.pid):
|
| 633 |
+
raise ZombieProcess(inst.pid, inst._name, inst._ppid)
|
| 634 |
+
else:
|
| 635 |
+
raise NoSuchProcess(inst.pid, inst._name)
|
| 636 |
+
except PermissionError:
|
| 637 |
+
raise AccessDenied(inst.pid, inst._name)
|
| 638 |
+
|
| 639 |
+
|
| 640 |
+
class Process:
|
| 641 |
+
"""Wrapper class around underlying C implementation."""
|
| 642 |
+
|
| 643 |
+
__slots__ = ["_cache", "_name", "_ppid", "pid"]
|
| 644 |
+
|
| 645 |
+
def __init__(self, pid):
|
| 646 |
+
self.pid = pid
|
| 647 |
+
self._name = None
|
| 648 |
+
self._ppid = None
|
| 649 |
+
|
| 650 |
+
def _assert_alive(self):
|
| 651 |
+
"""Raise NSP if the process disappeared on us."""
|
| 652 |
+
# For those C function who do not raise NSP, possibly returning
|
| 653 |
+
# incorrect or incomplete result.
|
| 654 |
+
cext.proc_name(self.pid)
|
| 655 |
+
|
| 656 |
+
@wrap_exceptions
|
| 657 |
+
@memoize_when_activated
|
| 658 |
+
def oneshot(self):
|
| 659 |
+
"""Retrieves multiple process info in one shot as a raw tuple."""
|
| 660 |
+
ret = cext.proc_oneshot_info(self.pid)
|
| 661 |
+
assert len(ret) == len(kinfo_proc_map)
|
| 662 |
+
return ret
|
| 663 |
+
|
| 664 |
+
def oneshot_enter(self):
|
| 665 |
+
self.oneshot.cache_activate(self)
|
| 666 |
+
|
| 667 |
+
def oneshot_exit(self):
|
| 668 |
+
self.oneshot.cache_deactivate(self)
|
| 669 |
+
|
| 670 |
+
@wrap_exceptions
|
| 671 |
+
def name(self):
|
| 672 |
+
name = self.oneshot()[kinfo_proc_map['name']]
|
| 673 |
+
return name if name is not None else cext.proc_name(self.pid)
|
| 674 |
+
|
| 675 |
+
@wrap_exceptions
|
| 676 |
+
def exe(self):
|
| 677 |
+
if FREEBSD:
|
| 678 |
+
if self.pid == 0:
|
| 679 |
+
return '' # else NSP
|
| 680 |
+
return cext.proc_exe(self.pid)
|
| 681 |
+
elif NETBSD:
|
| 682 |
+
if self.pid == 0:
|
| 683 |
+
# /proc/0 dir exists but /proc/0/exe doesn't
|
| 684 |
+
return ""
|
| 685 |
+
with wrap_exceptions_procfs(self):
|
| 686 |
+
return os.readlink("/proc/%s/exe" % self.pid)
|
| 687 |
+
else:
|
| 688 |
+
# OpenBSD: exe cannot be determined; references:
|
| 689 |
+
# https://chromium.googlesource.com/chromium/src/base/+/
|
| 690 |
+
# master/base_paths_posix.cc
|
| 691 |
+
# We try our best guess by using which against the first
|
| 692 |
+
# cmdline arg (may return None).
|
| 693 |
+
cmdline = self.cmdline()
|
| 694 |
+
if cmdline:
|
| 695 |
+
return which(cmdline[0]) or ""
|
| 696 |
+
else:
|
| 697 |
+
return ""
|
| 698 |
+
|
| 699 |
+
@wrap_exceptions
|
| 700 |
+
def cmdline(self):
|
| 701 |
+
if OPENBSD and self.pid == 0:
|
| 702 |
+
return [] # ...else it crashes
|
| 703 |
+
elif NETBSD:
|
| 704 |
+
# XXX - most of the times the underlying sysctl() call on
|
| 705 |
+
# NetBSD and OpenBSD returns a truncated string. Also
|
| 706 |
+
# /proc/pid/cmdline behaves the same so it looks like this
|
| 707 |
+
# is a kernel bug.
|
| 708 |
+
try:
|
| 709 |
+
return cext.proc_cmdline(self.pid)
|
| 710 |
+
except OSError as err:
|
| 711 |
+
if err.errno == errno.EINVAL:
|
| 712 |
+
if is_zombie(self.pid):
|
| 713 |
+
raise ZombieProcess(self.pid, self._name, self._ppid)
|
| 714 |
+
elif not pid_exists(self.pid):
|
| 715 |
+
raise NoSuchProcess(self.pid, self._name, self._ppid)
|
| 716 |
+
else:
|
| 717 |
+
# XXX: this happens with unicode tests. It means the C
|
| 718 |
+
# routine is unable to decode invalid unicode chars.
|
| 719 |
+
debug("ignoring %r and returning an empty list" % err)
|
| 720 |
+
return []
|
| 721 |
+
else:
|
| 722 |
+
raise
|
| 723 |
+
else:
|
| 724 |
+
return cext.proc_cmdline(self.pid)
|
| 725 |
+
|
| 726 |
+
@wrap_exceptions
|
| 727 |
+
def environ(self):
|
| 728 |
+
return cext.proc_environ(self.pid)
|
| 729 |
+
|
| 730 |
+
@wrap_exceptions
|
| 731 |
+
def terminal(self):
|
| 732 |
+
tty_nr = self.oneshot()[kinfo_proc_map['ttynr']]
|
| 733 |
+
tmap = _psposix.get_terminal_map()
|
| 734 |
+
try:
|
| 735 |
+
return tmap[tty_nr]
|
| 736 |
+
except KeyError:
|
| 737 |
+
return None
|
| 738 |
+
|
| 739 |
+
@wrap_exceptions
|
| 740 |
+
def ppid(self):
|
| 741 |
+
self._ppid = self.oneshot()[kinfo_proc_map['ppid']]
|
| 742 |
+
return self._ppid
|
| 743 |
+
|
| 744 |
+
@wrap_exceptions
|
| 745 |
+
def uids(self):
|
| 746 |
+
rawtuple = self.oneshot()
|
| 747 |
+
return _common.puids(
|
| 748 |
+
rawtuple[kinfo_proc_map['real_uid']],
|
| 749 |
+
rawtuple[kinfo_proc_map['effective_uid']],
|
| 750 |
+
rawtuple[kinfo_proc_map['saved_uid']],
|
| 751 |
+
)
|
| 752 |
+
|
| 753 |
+
@wrap_exceptions
|
| 754 |
+
def gids(self):
|
| 755 |
+
rawtuple = self.oneshot()
|
| 756 |
+
return _common.pgids(
|
| 757 |
+
rawtuple[kinfo_proc_map['real_gid']],
|
| 758 |
+
rawtuple[kinfo_proc_map['effective_gid']],
|
| 759 |
+
rawtuple[kinfo_proc_map['saved_gid']],
|
| 760 |
+
)
|
| 761 |
+
|
| 762 |
+
@wrap_exceptions
|
| 763 |
+
def cpu_times(self):
|
| 764 |
+
rawtuple = self.oneshot()
|
| 765 |
+
return _common.pcputimes(
|
| 766 |
+
rawtuple[kinfo_proc_map['user_time']],
|
| 767 |
+
rawtuple[kinfo_proc_map['sys_time']],
|
| 768 |
+
rawtuple[kinfo_proc_map['ch_user_time']],
|
| 769 |
+
rawtuple[kinfo_proc_map['ch_sys_time']],
|
| 770 |
+
)
|
| 771 |
+
|
| 772 |
+
if FREEBSD:
|
| 773 |
+
|
| 774 |
+
@wrap_exceptions
|
| 775 |
+
def cpu_num(self):
|
| 776 |
+
return self.oneshot()[kinfo_proc_map['cpunum']]
|
| 777 |
+
|
| 778 |
+
@wrap_exceptions
|
| 779 |
+
def memory_info(self):
|
| 780 |
+
rawtuple = self.oneshot()
|
| 781 |
+
return pmem(
|
| 782 |
+
rawtuple[kinfo_proc_map['rss']],
|
| 783 |
+
rawtuple[kinfo_proc_map['vms']],
|
| 784 |
+
rawtuple[kinfo_proc_map['memtext']],
|
| 785 |
+
rawtuple[kinfo_proc_map['memdata']],
|
| 786 |
+
rawtuple[kinfo_proc_map['memstack']],
|
| 787 |
+
)
|
| 788 |
+
|
| 789 |
+
memory_full_info = memory_info
|
| 790 |
+
|
| 791 |
+
@wrap_exceptions
|
| 792 |
+
def create_time(self):
|
| 793 |
+
return self.oneshot()[kinfo_proc_map['create_time']]
|
| 794 |
+
|
| 795 |
+
@wrap_exceptions
|
| 796 |
+
def num_threads(self):
|
| 797 |
+
if HAS_PROC_NUM_THREADS:
|
| 798 |
+
# FreeBSD
|
| 799 |
+
return cext.proc_num_threads(self.pid)
|
| 800 |
+
else:
|
| 801 |
+
return len(self.threads())
|
| 802 |
+
|
| 803 |
+
@wrap_exceptions
|
| 804 |
+
def num_ctx_switches(self):
|
| 805 |
+
rawtuple = self.oneshot()
|
| 806 |
+
return _common.pctxsw(
|
| 807 |
+
rawtuple[kinfo_proc_map['ctx_switches_vol']],
|
| 808 |
+
rawtuple[kinfo_proc_map['ctx_switches_unvol']],
|
| 809 |
+
)
|
| 810 |
+
|
| 811 |
+
@wrap_exceptions
|
| 812 |
+
def threads(self):
|
| 813 |
+
# Note: on OpenSBD this (/dev/mem) requires root access.
|
| 814 |
+
rawlist = cext.proc_threads(self.pid)
|
| 815 |
+
retlist = []
|
| 816 |
+
for thread_id, utime, stime in rawlist:
|
| 817 |
+
ntuple = _common.pthread(thread_id, utime, stime)
|
| 818 |
+
retlist.append(ntuple)
|
| 819 |
+
if OPENBSD:
|
| 820 |
+
self._assert_alive()
|
| 821 |
+
return retlist
|
| 822 |
+
|
| 823 |
+
@wrap_exceptions
|
| 824 |
+
def net_connections(self, kind='inet'):
|
| 825 |
+
if kind not in conn_tmap:
|
| 826 |
+
raise ValueError(
|
| 827 |
+
"invalid %r kind argument; choose between %s"
|
| 828 |
+
% (kind, ', '.join([repr(x) for x in conn_tmap]))
|
| 829 |
+
)
|
| 830 |
+
families, types = conn_tmap[kind]
|
| 831 |
+
ret = []
|
| 832 |
+
|
| 833 |
+
if NETBSD:
|
| 834 |
+
rawlist = cext.net_connections(self.pid, kind)
|
| 835 |
+
elif OPENBSD:
|
| 836 |
+
rawlist = cext.net_connections(self.pid, families, types)
|
| 837 |
+
else:
|
| 838 |
+
rawlist = cext.proc_net_connections(self.pid, families, types)
|
| 839 |
+
|
| 840 |
+
for item in rawlist:
|
| 841 |
+
fd, fam, type, laddr, raddr, status = item[:6]
|
| 842 |
+
if FREEBSD:
|
| 843 |
+
if (fam not in families) or (type not in types):
|
| 844 |
+
continue
|
| 845 |
+
nt = conn_to_ntuple(
|
| 846 |
+
fd, fam, type, laddr, raddr, status, TCP_STATUSES
|
| 847 |
+
)
|
| 848 |
+
ret.append(nt)
|
| 849 |
+
|
| 850 |
+
self._assert_alive()
|
| 851 |
+
return ret
|
| 852 |
+
|
| 853 |
+
@wrap_exceptions
|
| 854 |
+
def wait(self, timeout=None):
|
| 855 |
+
return _psposix.wait_pid(self.pid, timeout, self._name)
|
| 856 |
+
|
| 857 |
+
@wrap_exceptions
|
| 858 |
+
def nice_get(self):
|
| 859 |
+
return cext_posix.getpriority(self.pid)
|
| 860 |
+
|
| 861 |
+
@wrap_exceptions
|
| 862 |
+
def nice_set(self, value):
|
| 863 |
+
return cext_posix.setpriority(self.pid, value)
|
| 864 |
+
|
| 865 |
+
@wrap_exceptions
|
| 866 |
+
def status(self):
|
| 867 |
+
code = self.oneshot()[kinfo_proc_map['status']]
|
| 868 |
+
# XXX is '?' legit? (we're not supposed to return it anyway)
|
| 869 |
+
return PROC_STATUSES.get(code, '?')
|
| 870 |
+
|
| 871 |
+
@wrap_exceptions
|
| 872 |
+
def io_counters(self):
|
| 873 |
+
rawtuple = self.oneshot()
|
| 874 |
+
return _common.pio(
|
| 875 |
+
rawtuple[kinfo_proc_map['read_io_count']],
|
| 876 |
+
rawtuple[kinfo_proc_map['write_io_count']],
|
| 877 |
+
-1,
|
| 878 |
+
-1,
|
| 879 |
+
)
|
| 880 |
+
|
| 881 |
+
@wrap_exceptions
|
| 882 |
+
def cwd(self):
|
| 883 |
+
"""Return process current working directory."""
|
| 884 |
+
# sometimes we get an empty string, in which case we turn
|
| 885 |
+
# it into None
|
| 886 |
+
if OPENBSD and self.pid == 0:
|
| 887 |
+
return "" # ...else it would raise EINVAL
|
| 888 |
+
elif NETBSD or HAS_PROC_OPEN_FILES:
|
| 889 |
+
# FreeBSD < 8 does not support functions based on
|
| 890 |
+
# kinfo_getfile() and kinfo_getvmmap()
|
| 891 |
+
return cext.proc_cwd(self.pid)
|
| 892 |
+
else:
|
| 893 |
+
raise NotImplementedError(
|
| 894 |
+
"supported only starting from FreeBSD 8" if FREEBSD else ""
|
| 895 |
+
)
|
| 896 |
+
|
| 897 |
+
nt_mmap_grouped = namedtuple(
|
| 898 |
+
'mmap', 'path rss, private, ref_count, shadow_count'
|
| 899 |
+
)
|
| 900 |
+
nt_mmap_ext = namedtuple(
|
| 901 |
+
'mmap', 'addr, perms path rss, private, ref_count, shadow_count'
|
| 902 |
+
)
|
| 903 |
+
|
| 904 |
+
def _not_implemented(self):
|
| 905 |
+
raise NotImplementedError
|
| 906 |
+
|
| 907 |
+
# FreeBSD < 8 does not support functions based on kinfo_getfile()
|
| 908 |
+
# and kinfo_getvmmap()
|
| 909 |
+
if HAS_PROC_OPEN_FILES:
|
| 910 |
+
|
| 911 |
+
@wrap_exceptions
|
| 912 |
+
def open_files(self):
|
| 913 |
+
"""Return files opened by process as a list of namedtuples."""
|
| 914 |
+
rawlist = cext.proc_open_files(self.pid)
|
| 915 |
+
return [_common.popenfile(path, fd) for path, fd in rawlist]
|
| 916 |
+
|
| 917 |
+
else:
|
| 918 |
+
open_files = _not_implemented
|
| 919 |
+
|
| 920 |
+
# FreeBSD < 8 does not support functions based on kinfo_getfile()
|
| 921 |
+
# and kinfo_getvmmap()
|
| 922 |
+
if HAS_PROC_NUM_FDS:
|
| 923 |
+
|
| 924 |
+
@wrap_exceptions
|
| 925 |
+
def num_fds(self):
|
| 926 |
+
"""Return the number of file descriptors opened by this process."""
|
| 927 |
+
ret = cext.proc_num_fds(self.pid)
|
| 928 |
+
if NETBSD:
|
| 929 |
+
self._assert_alive()
|
| 930 |
+
return ret
|
| 931 |
+
|
| 932 |
+
else:
|
| 933 |
+
num_fds = _not_implemented
|
| 934 |
+
|
| 935 |
+
# --- FreeBSD only APIs
|
| 936 |
+
|
| 937 |
+
if FREEBSD:
|
| 938 |
+
|
| 939 |
+
@wrap_exceptions
|
| 940 |
+
def cpu_affinity_get(self):
|
| 941 |
+
return cext.proc_cpu_affinity_get(self.pid)
|
| 942 |
+
|
| 943 |
+
@wrap_exceptions
|
| 944 |
+
def cpu_affinity_set(self, cpus):
|
| 945 |
+
# Pre-emptively check if CPUs are valid because the C
|
| 946 |
+
# function has a weird behavior in case of invalid CPUs,
|
| 947 |
+
# see: https://github.com/giampaolo/psutil/issues/586
|
| 948 |
+
allcpus = tuple(range(len(per_cpu_times())))
|
| 949 |
+
for cpu in cpus:
|
| 950 |
+
if cpu not in allcpus:
|
| 951 |
+
raise ValueError(
|
| 952 |
+
"invalid CPU #%i (choose between %s)" % (cpu, allcpus)
|
| 953 |
+
)
|
| 954 |
+
try:
|
| 955 |
+
cext.proc_cpu_affinity_set(self.pid, cpus)
|
| 956 |
+
except OSError as err:
|
| 957 |
+
# 'man cpuset_setaffinity' about EDEADLK:
|
| 958 |
+
# <<the call would leave a thread without a valid CPU to run
|
| 959 |
+
# on because the set does not overlap with the thread's
|
| 960 |
+
# anonymous mask>>
|
| 961 |
+
if err.errno in {errno.EINVAL, errno.EDEADLK}:
|
| 962 |
+
for cpu in cpus:
|
| 963 |
+
if cpu not in allcpus:
|
| 964 |
+
raise ValueError(
|
| 965 |
+
"invalid CPU #%i (choose between %s)"
|
| 966 |
+
% (cpu, allcpus)
|
| 967 |
+
)
|
| 968 |
+
raise
|
| 969 |
+
|
| 970 |
+
@wrap_exceptions
|
| 971 |
+
def memory_maps(self):
|
| 972 |
+
return cext.proc_memory_maps(self.pid)
|
| 973 |
+
|
| 974 |
+
@wrap_exceptions
|
| 975 |
+
def rlimit(self, resource, limits=None):
|
| 976 |
+
if limits is None:
|
| 977 |
+
return cext.proc_getrlimit(self.pid, resource)
|
| 978 |
+
else:
|
| 979 |
+
if len(limits) != 2:
|
| 980 |
+
raise ValueError(
|
| 981 |
+
"second argument must be a (soft, hard) tuple, got %s"
|
| 982 |
+
% repr(limits)
|
| 983 |
+
)
|
| 984 |
+
soft, hard = limits
|
| 985 |
+
return cext.proc_setrlimit(self.pid, resource, soft, hard)
|
.venv/lib/python3.11/site-packages/ray/thirdparty_files/psutil/_pslinux.py
ADDED
|
@@ -0,0 +1,2379 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) 2009, Giampaolo Rodola'. All rights reserved.
|
| 2 |
+
# Use of this source code is governed by a BSD-style license that can be
|
| 3 |
+
# found in the LICENSE file.
|
| 4 |
+
|
| 5 |
+
"""Linux platform implementation."""
|
| 6 |
+
|
| 7 |
+
from __future__ import division
|
| 8 |
+
|
| 9 |
+
import base64
|
| 10 |
+
import collections
|
| 11 |
+
import errno
|
| 12 |
+
import functools
|
| 13 |
+
import glob
|
| 14 |
+
import os
|
| 15 |
+
import re
|
| 16 |
+
import socket
|
| 17 |
+
import struct
|
| 18 |
+
import sys
|
| 19 |
+
import warnings
|
| 20 |
+
from collections import defaultdict
|
| 21 |
+
from collections import namedtuple
|
| 22 |
+
|
| 23 |
+
from . import _common
|
| 24 |
+
from . import _psposix
|
| 25 |
+
from . import _psutil_linux as cext
|
| 26 |
+
from . import _psutil_posix as cext_posix
|
| 27 |
+
from ._common import NIC_DUPLEX_FULL
|
| 28 |
+
from ._common import NIC_DUPLEX_HALF
|
| 29 |
+
from ._common import NIC_DUPLEX_UNKNOWN
|
| 30 |
+
from ._common import AccessDenied
|
| 31 |
+
from ._common import NoSuchProcess
|
| 32 |
+
from ._common import ZombieProcess
|
| 33 |
+
from ._common import bcat
|
| 34 |
+
from ._common import cat
|
| 35 |
+
from ._common import debug
|
| 36 |
+
from ._common import decode
|
| 37 |
+
from ._common import get_procfs_path
|
| 38 |
+
from ._common import isfile_strict
|
| 39 |
+
from ._common import memoize
|
| 40 |
+
from ._common import memoize_when_activated
|
| 41 |
+
from ._common import open_binary
|
| 42 |
+
from ._common import open_text
|
| 43 |
+
from ._common import parse_environ_block
|
| 44 |
+
from ._common import path_exists_strict
|
| 45 |
+
from ._common import supports_ipv6
|
| 46 |
+
from ._common import usage_percent
|
| 47 |
+
from ._compat import PY3
|
| 48 |
+
from ._compat import FileNotFoundError
|
| 49 |
+
from ._compat import PermissionError
|
| 50 |
+
from ._compat import ProcessLookupError
|
| 51 |
+
from ._compat import b
|
| 52 |
+
from ._compat import basestring
|
| 53 |
+
|
| 54 |
+
|
| 55 |
+
if PY3:
|
| 56 |
+
import enum
|
| 57 |
+
else:
|
| 58 |
+
enum = None
|
| 59 |
+
|
| 60 |
+
|
| 61 |
+
# fmt: off
|
| 62 |
+
__extra__all__ = [
|
| 63 |
+
'PROCFS_PATH',
|
| 64 |
+
# io prio constants
|
| 65 |
+
"IOPRIO_CLASS_NONE", "IOPRIO_CLASS_RT", "IOPRIO_CLASS_BE",
|
| 66 |
+
"IOPRIO_CLASS_IDLE",
|
| 67 |
+
# connection status constants
|
| 68 |
+
"CONN_ESTABLISHED", "CONN_SYN_SENT", "CONN_SYN_RECV", "CONN_FIN_WAIT1",
|
| 69 |
+
"CONN_FIN_WAIT2", "CONN_TIME_WAIT", "CONN_CLOSE", "CONN_CLOSE_WAIT",
|
| 70 |
+
"CONN_LAST_ACK", "CONN_LISTEN", "CONN_CLOSING",
|
| 71 |
+
]
|
| 72 |
+
# fmt: on
|
| 73 |
+
|
| 74 |
+
|
| 75 |
+
# =====================================================================
|
| 76 |
+
# --- globals
|
| 77 |
+
# =====================================================================
|
| 78 |
+
|
| 79 |
+
|
| 80 |
+
POWER_SUPPLY_PATH = "/sys/class/power_supply"
|
| 81 |
+
HAS_PROC_SMAPS = os.path.exists('/proc/%s/smaps' % os.getpid())
|
| 82 |
+
HAS_PROC_SMAPS_ROLLUP = os.path.exists('/proc/%s/smaps_rollup' % os.getpid())
|
| 83 |
+
HAS_PROC_IO_PRIORITY = hasattr(cext, "proc_ioprio_get")
|
| 84 |
+
HAS_CPU_AFFINITY = hasattr(cext, "proc_cpu_affinity_get")
|
| 85 |
+
|
| 86 |
+
# Number of clock ticks per second
|
| 87 |
+
CLOCK_TICKS = os.sysconf("SC_CLK_TCK")
|
| 88 |
+
PAGESIZE = cext_posix.getpagesize()
|
| 89 |
+
BOOT_TIME = None # set later
|
| 90 |
+
LITTLE_ENDIAN = sys.byteorder == 'little'
|
| 91 |
+
|
| 92 |
+
# "man iostat" states that sectors are equivalent with blocks and have
|
| 93 |
+
# a size of 512 bytes. Despite this value can be queried at runtime
|
| 94 |
+
# via /sys/block/{DISK}/queue/hw_sector_size and results may vary
|
| 95 |
+
# between 1k, 2k, or 4k... 512 appears to be a magic constant used
|
| 96 |
+
# throughout Linux source code:
|
| 97 |
+
# * https://stackoverflow.com/a/38136179/376587
|
| 98 |
+
# * https://lists.gt.net/linux/kernel/2241060
|
| 99 |
+
# * https://github.com/giampaolo/psutil/issues/1305
|
| 100 |
+
# * https://github.com/torvalds/linux/blob/
|
| 101 |
+
# 4f671fe2f9523a1ea206f63fe60a7c7b3a56d5c7/include/linux/bio.h#L99
|
| 102 |
+
# * https://lkml.org/lkml/2015/8/17/234
|
| 103 |
+
DISK_SECTOR_SIZE = 512
|
| 104 |
+
|
| 105 |
+
if enum is None:
|
| 106 |
+
AF_LINK = socket.AF_PACKET
|
| 107 |
+
else:
|
| 108 |
+
AddressFamily = enum.IntEnum(
|
| 109 |
+
'AddressFamily', {'AF_LINK': int(socket.AF_PACKET)}
|
| 110 |
+
)
|
| 111 |
+
AF_LINK = AddressFamily.AF_LINK
|
| 112 |
+
|
| 113 |
+
# ioprio_* constants http://linux.die.net/man/2/ioprio_get
|
| 114 |
+
if enum is None:
|
| 115 |
+
IOPRIO_CLASS_NONE = 0
|
| 116 |
+
IOPRIO_CLASS_RT = 1
|
| 117 |
+
IOPRIO_CLASS_BE = 2
|
| 118 |
+
IOPRIO_CLASS_IDLE = 3
|
| 119 |
+
else:
|
| 120 |
+
|
| 121 |
+
class IOPriority(enum.IntEnum):
|
| 122 |
+
IOPRIO_CLASS_NONE = 0
|
| 123 |
+
IOPRIO_CLASS_RT = 1
|
| 124 |
+
IOPRIO_CLASS_BE = 2
|
| 125 |
+
IOPRIO_CLASS_IDLE = 3
|
| 126 |
+
|
| 127 |
+
globals().update(IOPriority.__members__)
|
| 128 |
+
|
| 129 |
+
# See:
|
| 130 |
+
# https://github.com/torvalds/linux/blame/master/fs/proc/array.c
|
| 131 |
+
# ...and (TASK_* constants):
|
| 132 |
+
# https://github.com/torvalds/linux/blob/master/include/linux/sched.h
|
| 133 |
+
PROC_STATUSES = {
|
| 134 |
+
"R": _common.STATUS_RUNNING,
|
| 135 |
+
"S": _common.STATUS_SLEEPING,
|
| 136 |
+
"D": _common.STATUS_DISK_SLEEP,
|
| 137 |
+
"T": _common.STATUS_STOPPED,
|
| 138 |
+
"t": _common.STATUS_TRACING_STOP,
|
| 139 |
+
"Z": _common.STATUS_ZOMBIE,
|
| 140 |
+
"X": _common.STATUS_DEAD,
|
| 141 |
+
"x": _common.STATUS_DEAD,
|
| 142 |
+
"K": _common.STATUS_WAKE_KILL,
|
| 143 |
+
"W": _common.STATUS_WAKING,
|
| 144 |
+
"I": _common.STATUS_IDLE,
|
| 145 |
+
"P": _common.STATUS_PARKED,
|
| 146 |
+
}
|
| 147 |
+
|
| 148 |
+
# https://github.com/torvalds/linux/blob/master/include/net/tcp_states.h
|
| 149 |
+
TCP_STATUSES = {
|
| 150 |
+
"01": _common.CONN_ESTABLISHED,
|
| 151 |
+
"02": _common.CONN_SYN_SENT,
|
| 152 |
+
"03": _common.CONN_SYN_RECV,
|
| 153 |
+
"04": _common.CONN_FIN_WAIT1,
|
| 154 |
+
"05": _common.CONN_FIN_WAIT2,
|
| 155 |
+
"06": _common.CONN_TIME_WAIT,
|
| 156 |
+
"07": _common.CONN_CLOSE,
|
| 157 |
+
"08": _common.CONN_CLOSE_WAIT,
|
| 158 |
+
"09": _common.CONN_LAST_ACK,
|
| 159 |
+
"0A": _common.CONN_LISTEN,
|
| 160 |
+
"0B": _common.CONN_CLOSING,
|
| 161 |
+
}
|
| 162 |
+
|
| 163 |
+
|
| 164 |
+
# =====================================================================
|
| 165 |
+
# --- named tuples
|
| 166 |
+
# =====================================================================
|
| 167 |
+
|
| 168 |
+
|
| 169 |
+
# fmt: off
|
| 170 |
+
# psutil.virtual_memory()
|
| 171 |
+
svmem = namedtuple(
|
| 172 |
+
'svmem', ['total', 'available', 'percent', 'used', 'free',
|
| 173 |
+
'active', 'inactive', 'buffers', 'cached', 'shared', 'slab'])
|
| 174 |
+
# psutil.disk_io_counters()
|
| 175 |
+
sdiskio = namedtuple(
|
| 176 |
+
'sdiskio', ['read_count', 'write_count',
|
| 177 |
+
'read_bytes', 'write_bytes',
|
| 178 |
+
'read_time', 'write_time',
|
| 179 |
+
'read_merged_count', 'write_merged_count',
|
| 180 |
+
'busy_time'])
|
| 181 |
+
# psutil.Process().open_files()
|
| 182 |
+
popenfile = namedtuple(
|
| 183 |
+
'popenfile', ['path', 'fd', 'position', 'mode', 'flags'])
|
| 184 |
+
# psutil.Process().memory_info()
|
| 185 |
+
pmem = namedtuple('pmem', 'rss vms shared text lib data dirty')
|
| 186 |
+
# psutil.Process().memory_full_info()
|
| 187 |
+
pfullmem = namedtuple('pfullmem', pmem._fields + ('uss', 'pss', 'swap'))
|
| 188 |
+
# psutil.Process().memory_maps(grouped=True)
|
| 189 |
+
pmmap_grouped = namedtuple(
|
| 190 |
+
'pmmap_grouped',
|
| 191 |
+
['path', 'rss', 'size', 'pss', 'shared_clean', 'shared_dirty',
|
| 192 |
+
'private_clean', 'private_dirty', 'referenced', 'anonymous', 'swap'])
|
| 193 |
+
# psutil.Process().memory_maps(grouped=False)
|
| 194 |
+
pmmap_ext = namedtuple(
|
| 195 |
+
'pmmap_ext', 'addr perms ' + ' '.join(pmmap_grouped._fields))
|
| 196 |
+
# psutil.Process.io_counters()
|
| 197 |
+
pio = namedtuple('pio', ['read_count', 'write_count',
|
| 198 |
+
'read_bytes', 'write_bytes',
|
| 199 |
+
'read_chars', 'write_chars'])
|
| 200 |
+
# psutil.Process.cpu_times()
|
| 201 |
+
pcputimes = namedtuple('pcputimes',
|
| 202 |
+
['user', 'system', 'children_user', 'children_system',
|
| 203 |
+
'iowait'])
|
| 204 |
+
# fmt: on
|
| 205 |
+
|
| 206 |
+
|
| 207 |
+
# =====================================================================
|
| 208 |
+
# --- utils
|
| 209 |
+
# =====================================================================
|
| 210 |
+
|
| 211 |
+
|
| 212 |
+
def readlink(path):
|
| 213 |
+
"""Wrapper around os.readlink()."""
|
| 214 |
+
assert isinstance(path, basestring), path
|
| 215 |
+
path = os.readlink(path)
|
| 216 |
+
# readlink() might return paths containing null bytes ('\x00')
|
| 217 |
+
# resulting in "TypeError: must be encoded string without NULL
|
| 218 |
+
# bytes, not str" errors when the string is passed to other
|
| 219 |
+
# fs-related functions (os.*, open(), ...).
|
| 220 |
+
# Apparently everything after '\x00' is garbage (we can have
|
| 221 |
+
# ' (deleted)', 'new' and possibly others), see:
|
| 222 |
+
# https://github.com/giampaolo/psutil/issues/717
|
| 223 |
+
path = path.split('\x00')[0]
|
| 224 |
+
# Certain paths have ' (deleted)' appended. Usually this is
|
| 225 |
+
# bogus as the file actually exists. Even if it doesn't we
|
| 226 |
+
# don't care.
|
| 227 |
+
if path.endswith(' (deleted)') and not path_exists_strict(path):
|
| 228 |
+
path = path[:-10]
|
| 229 |
+
return path
|
| 230 |
+
|
| 231 |
+
|
| 232 |
+
def file_flags_to_mode(flags):
|
| 233 |
+
"""Convert file's open() flags into a readable string.
|
| 234 |
+
Used by Process.open_files().
|
| 235 |
+
"""
|
| 236 |
+
modes_map = {os.O_RDONLY: 'r', os.O_WRONLY: 'w', os.O_RDWR: 'w+'}
|
| 237 |
+
mode = modes_map[flags & (os.O_RDONLY | os.O_WRONLY | os.O_RDWR)]
|
| 238 |
+
if flags & os.O_APPEND:
|
| 239 |
+
mode = mode.replace('w', 'a', 1)
|
| 240 |
+
mode = mode.replace('w+', 'r+')
|
| 241 |
+
# possible values: r, w, a, r+, a+
|
| 242 |
+
return mode
|
| 243 |
+
|
| 244 |
+
|
| 245 |
+
def is_storage_device(name):
|
| 246 |
+
"""Return True if the given name refers to a root device (e.g.
|
| 247 |
+
"sda", "nvme0n1") as opposed to a logical partition (e.g. "sda1",
|
| 248 |
+
"nvme0n1p1"). If name is a virtual device (e.g. "loop1", "ram")
|
| 249 |
+
return True.
|
| 250 |
+
"""
|
| 251 |
+
# Re-adapted from iostat source code, see:
|
| 252 |
+
# https://github.com/sysstat/sysstat/blob/
|
| 253 |
+
# 97912938cd476645b267280069e83b1c8dc0e1c7/common.c#L208
|
| 254 |
+
# Some devices may have a slash in their name (e.g. cciss/c0d0...).
|
| 255 |
+
name = name.replace('/', '!')
|
| 256 |
+
including_virtual = True
|
| 257 |
+
if including_virtual:
|
| 258 |
+
path = "/sys/block/%s" % name
|
| 259 |
+
else:
|
| 260 |
+
path = "/sys/block/%s/device" % name
|
| 261 |
+
return os.access(path, os.F_OK)
|
| 262 |
+
|
| 263 |
+
|
| 264 |
+
@memoize
|
| 265 |
+
def set_scputimes_ntuple(procfs_path):
|
| 266 |
+
"""Set a namedtuple of variable fields depending on the CPU times
|
| 267 |
+
available on this Linux kernel version which may be:
|
| 268 |
+
(user, nice, system, idle, iowait, irq, softirq, [steal, [guest,
|
| 269 |
+
[guest_nice]]])
|
| 270 |
+
Used by cpu_times() function.
|
| 271 |
+
"""
|
| 272 |
+
global scputimes
|
| 273 |
+
with open_binary('%s/stat' % procfs_path) as f:
|
| 274 |
+
values = f.readline().split()[1:]
|
| 275 |
+
fields = ['user', 'nice', 'system', 'idle', 'iowait', 'irq', 'softirq']
|
| 276 |
+
vlen = len(values)
|
| 277 |
+
if vlen >= 8:
|
| 278 |
+
# Linux >= 2.6.11
|
| 279 |
+
fields.append('steal')
|
| 280 |
+
if vlen >= 9:
|
| 281 |
+
# Linux >= 2.6.24
|
| 282 |
+
fields.append('guest')
|
| 283 |
+
if vlen >= 10:
|
| 284 |
+
# Linux >= 3.2.0
|
| 285 |
+
fields.append('guest_nice')
|
| 286 |
+
scputimes = namedtuple('scputimes', fields)
|
| 287 |
+
|
| 288 |
+
|
| 289 |
+
try:
|
| 290 |
+
set_scputimes_ntuple("/proc")
|
| 291 |
+
except Exception as err: # noqa: BLE001
|
| 292 |
+
# Don't want to crash at import time.
|
| 293 |
+
debug("ignoring exception on import: %r" % err)
|
| 294 |
+
scputimes = namedtuple('scputimes', 'user system idle')(0.0, 0.0, 0.0)
|
| 295 |
+
|
| 296 |
+
|
| 297 |
+
# =====================================================================
|
| 298 |
+
# --- prlimit
|
| 299 |
+
# =====================================================================
|
| 300 |
+
|
| 301 |
+
# Backport of resource.prlimit() for Python 2. Originally this was done
|
| 302 |
+
# in C, but CentOS-6 which we use to create manylinux wheels is too old
|
| 303 |
+
# and does not support prlimit() syscall. As such the resulting wheel
|
| 304 |
+
# would not include prlimit(), even when installed on newer systems.
|
| 305 |
+
# This is the only part of psutil using ctypes.
|
| 306 |
+
|
| 307 |
+
prlimit = None
|
| 308 |
+
try:
|
| 309 |
+
from resource import prlimit # python >= 3.4
|
| 310 |
+
except ImportError:
|
| 311 |
+
import ctypes
|
| 312 |
+
|
| 313 |
+
libc = ctypes.CDLL(None, use_errno=True)
|
| 314 |
+
|
| 315 |
+
if hasattr(libc, "prlimit"):
|
| 316 |
+
|
| 317 |
+
def prlimit(pid, resource_, limits=None):
|
| 318 |
+
class StructRlimit(ctypes.Structure):
|
| 319 |
+
_fields_ = [
|
| 320 |
+
('rlim_cur', ctypes.c_longlong),
|
| 321 |
+
('rlim_max', ctypes.c_longlong),
|
| 322 |
+
]
|
| 323 |
+
|
| 324 |
+
current = StructRlimit()
|
| 325 |
+
if limits is None:
|
| 326 |
+
# get
|
| 327 |
+
ret = libc.prlimit(pid, resource_, None, ctypes.byref(current))
|
| 328 |
+
else:
|
| 329 |
+
# set
|
| 330 |
+
new = StructRlimit()
|
| 331 |
+
new.rlim_cur = limits[0]
|
| 332 |
+
new.rlim_max = limits[1]
|
| 333 |
+
ret = libc.prlimit(
|
| 334 |
+
pid, resource_, ctypes.byref(new), ctypes.byref(current)
|
| 335 |
+
)
|
| 336 |
+
|
| 337 |
+
if ret != 0:
|
| 338 |
+
errno_ = ctypes.get_errno()
|
| 339 |
+
raise OSError(errno_, os.strerror(errno_))
|
| 340 |
+
return (current.rlim_cur, current.rlim_max)
|
| 341 |
+
|
| 342 |
+
|
| 343 |
+
if prlimit is not None:
|
| 344 |
+
__extra__all__.extend(
|
| 345 |
+
[x for x in dir(cext) if x.startswith('RLIM') and x.isupper()]
|
| 346 |
+
)
|
| 347 |
+
|
| 348 |
+
|
| 349 |
+
# =====================================================================
|
| 350 |
+
# --- system memory
|
| 351 |
+
# =====================================================================
|
| 352 |
+
|
| 353 |
+
|
| 354 |
+
def calculate_avail_vmem(mems):
|
| 355 |
+
"""Fallback for kernels < 3.14 where /proc/meminfo does not provide
|
| 356 |
+
"MemAvailable", see:
|
| 357 |
+
https://blog.famzah.net/2014/09/24/.
|
| 358 |
+
|
| 359 |
+
This code reimplements the algorithm outlined here:
|
| 360 |
+
https://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/
|
| 361 |
+
commit/?id=34e431b0ae398fc54ea69ff85ec700722c9da773
|
| 362 |
+
|
| 363 |
+
We use this function also when "MemAvailable" returns 0 (possibly a
|
| 364 |
+
kernel bug, see: https://github.com/giampaolo/psutil/issues/1915).
|
| 365 |
+
In that case this routine matches "free" CLI tool result ("available"
|
| 366 |
+
column).
|
| 367 |
+
|
| 368 |
+
XXX: on recent kernels this calculation may differ by ~1.5% compared
|
| 369 |
+
to "MemAvailable:", as it's calculated slightly differently.
|
| 370 |
+
It is still way more realistic than doing (free + cached) though.
|
| 371 |
+
See:
|
| 372 |
+
* https://gitlab.com/procps-ng/procps/issues/42
|
| 373 |
+
* https://github.com/famzah/linux-memavailable-procfs/issues/2
|
| 374 |
+
"""
|
| 375 |
+
# Note about "fallback" value. According to:
|
| 376 |
+
# https://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/
|
| 377 |
+
# commit/?id=34e431b0ae398fc54ea69ff85ec700722c9da773
|
| 378 |
+
# ...long ago "available" memory was calculated as (free + cached),
|
| 379 |
+
# We use fallback when one of these is missing from /proc/meminfo:
|
| 380 |
+
# "Active(file)": introduced in 2.6.28 / Dec 2008
|
| 381 |
+
# "Inactive(file)": introduced in 2.6.28 / Dec 2008
|
| 382 |
+
# "SReclaimable": introduced in 2.6.19 / Nov 2006
|
| 383 |
+
# /proc/zoneinfo: introduced in 2.6.13 / Aug 2005
|
| 384 |
+
free = mems[b'MemFree:']
|
| 385 |
+
fallback = free + mems.get(b"Cached:", 0)
|
| 386 |
+
try:
|
| 387 |
+
lru_active_file = mems[b'Active(file):']
|
| 388 |
+
lru_inactive_file = mems[b'Inactive(file):']
|
| 389 |
+
slab_reclaimable = mems[b'SReclaimable:']
|
| 390 |
+
except KeyError as err:
|
| 391 |
+
debug(
|
| 392 |
+
"%s is missing from /proc/meminfo; using an approximation for "
|
| 393 |
+
"calculating available memory"
|
| 394 |
+
% err.args[0]
|
| 395 |
+
)
|
| 396 |
+
return fallback
|
| 397 |
+
try:
|
| 398 |
+
f = open_binary('%s/zoneinfo' % get_procfs_path())
|
| 399 |
+
except IOError:
|
| 400 |
+
return fallback # kernel 2.6.13
|
| 401 |
+
|
| 402 |
+
watermark_low = 0
|
| 403 |
+
with f:
|
| 404 |
+
for line in f:
|
| 405 |
+
line = line.strip()
|
| 406 |
+
if line.startswith(b'low'):
|
| 407 |
+
watermark_low += int(line.split()[1])
|
| 408 |
+
watermark_low *= PAGESIZE
|
| 409 |
+
|
| 410 |
+
avail = free - watermark_low
|
| 411 |
+
pagecache = lru_active_file + lru_inactive_file
|
| 412 |
+
pagecache -= min(pagecache / 2, watermark_low)
|
| 413 |
+
avail += pagecache
|
| 414 |
+
avail += slab_reclaimable - min(slab_reclaimable / 2.0, watermark_low)
|
| 415 |
+
return int(avail)
|
| 416 |
+
|
| 417 |
+
|
| 418 |
+
def virtual_memory():
|
| 419 |
+
"""Report virtual memory stats.
|
| 420 |
+
This implementation mimics procps-ng-3.3.12, aka "free" CLI tool:
|
| 421 |
+
https://gitlab.com/procps-ng/procps/blob/
|
| 422 |
+
24fd2605c51fccc375ab0287cec33aa767f06718/proc/sysinfo.c#L778-791
|
| 423 |
+
The returned values are supposed to match both "free" and "vmstat -s"
|
| 424 |
+
CLI tools.
|
| 425 |
+
"""
|
| 426 |
+
missing_fields = []
|
| 427 |
+
mems = {}
|
| 428 |
+
with open_binary('%s/meminfo' % get_procfs_path()) as f:
|
| 429 |
+
for line in f:
|
| 430 |
+
fields = line.split()
|
| 431 |
+
mems[fields[0]] = int(fields[1]) * 1024
|
| 432 |
+
|
| 433 |
+
# /proc doc states that the available fields in /proc/meminfo vary
|
| 434 |
+
# by architecture and compile options, but these 3 values are also
|
| 435 |
+
# returned by sysinfo(2); as such we assume they are always there.
|
| 436 |
+
total = mems[b'MemTotal:']
|
| 437 |
+
free = mems[b'MemFree:']
|
| 438 |
+
try:
|
| 439 |
+
buffers = mems[b'Buffers:']
|
| 440 |
+
except KeyError:
|
| 441 |
+
# https://github.com/giampaolo/psutil/issues/1010
|
| 442 |
+
buffers = 0
|
| 443 |
+
missing_fields.append('buffers')
|
| 444 |
+
try:
|
| 445 |
+
cached = mems[b"Cached:"]
|
| 446 |
+
except KeyError:
|
| 447 |
+
cached = 0
|
| 448 |
+
missing_fields.append('cached')
|
| 449 |
+
else:
|
| 450 |
+
# "free" cmdline utility sums reclaimable to cached.
|
| 451 |
+
# Older versions of procps used to add slab memory instead.
|
| 452 |
+
# This got changed in:
|
| 453 |
+
# https://gitlab.com/procps-ng/procps/commit/
|
| 454 |
+
# 05d751c4f076a2f0118b914c5e51cfbb4762ad8e
|
| 455 |
+
cached += mems.get(b"SReclaimable:", 0) # since kernel 2.6.19
|
| 456 |
+
|
| 457 |
+
try:
|
| 458 |
+
shared = mems[b'Shmem:'] # since kernel 2.6.32
|
| 459 |
+
except KeyError:
|
| 460 |
+
try:
|
| 461 |
+
shared = mems[b'MemShared:'] # kernels 2.4
|
| 462 |
+
except KeyError:
|
| 463 |
+
shared = 0
|
| 464 |
+
missing_fields.append('shared')
|
| 465 |
+
|
| 466 |
+
try:
|
| 467 |
+
active = mems[b"Active:"]
|
| 468 |
+
except KeyError:
|
| 469 |
+
active = 0
|
| 470 |
+
missing_fields.append('active')
|
| 471 |
+
|
| 472 |
+
try:
|
| 473 |
+
inactive = mems[b"Inactive:"]
|
| 474 |
+
except KeyError:
|
| 475 |
+
try:
|
| 476 |
+
inactive = (
|
| 477 |
+
mems[b"Inact_dirty:"]
|
| 478 |
+
+ mems[b"Inact_clean:"]
|
| 479 |
+
+ mems[b"Inact_laundry:"]
|
| 480 |
+
)
|
| 481 |
+
except KeyError:
|
| 482 |
+
inactive = 0
|
| 483 |
+
missing_fields.append('inactive')
|
| 484 |
+
|
| 485 |
+
try:
|
| 486 |
+
slab = mems[b"Slab:"]
|
| 487 |
+
except KeyError:
|
| 488 |
+
slab = 0
|
| 489 |
+
|
| 490 |
+
used = total - free - cached - buffers
|
| 491 |
+
if used < 0:
|
| 492 |
+
# May be symptomatic of running within a LCX container where such
|
| 493 |
+
# values will be dramatically distorted over those of the host.
|
| 494 |
+
used = total - free
|
| 495 |
+
|
| 496 |
+
# - starting from 4.4.0 we match free's "available" column.
|
| 497 |
+
# Before 4.4.0 we calculated it as (free + buffers + cached)
|
| 498 |
+
# which matched htop.
|
| 499 |
+
# - free and htop available memory differs as per:
|
| 500 |
+
# http://askubuntu.com/a/369589
|
| 501 |
+
# http://unix.stackexchange.com/a/65852/168884
|
| 502 |
+
# - MemAvailable has been introduced in kernel 3.14
|
| 503 |
+
try:
|
| 504 |
+
avail = mems[b'MemAvailable:']
|
| 505 |
+
except KeyError:
|
| 506 |
+
avail = calculate_avail_vmem(mems)
|
| 507 |
+
else:
|
| 508 |
+
if avail == 0:
|
| 509 |
+
# Yes, it can happen (probably a kernel bug):
|
| 510 |
+
# https://github.com/giampaolo/psutil/issues/1915
|
| 511 |
+
# In this case "free" CLI tool makes an estimate. We do the same,
|
| 512 |
+
# and it matches "free" CLI tool.
|
| 513 |
+
avail = calculate_avail_vmem(mems)
|
| 514 |
+
|
| 515 |
+
if avail < 0:
|
| 516 |
+
avail = 0
|
| 517 |
+
missing_fields.append('available')
|
| 518 |
+
elif avail > total:
|
| 519 |
+
# If avail is greater than total or our calculation overflows,
|
| 520 |
+
# that's symptomatic of running within a LCX container where such
|
| 521 |
+
# values will be dramatically distorted over those of the host.
|
| 522 |
+
# https://gitlab.com/procps-ng/procps/blob/
|
| 523 |
+
# 24fd2605c51fccc375ab0287cec33aa767f06718/proc/sysinfo.c#L764
|
| 524 |
+
avail = free
|
| 525 |
+
|
| 526 |
+
percent = usage_percent((total - avail), total, round_=1)
|
| 527 |
+
|
| 528 |
+
# Warn about missing metrics which are set to 0.
|
| 529 |
+
if missing_fields:
|
| 530 |
+
msg = "%s memory stats couldn't be determined and %s set to 0" % (
|
| 531 |
+
", ".join(missing_fields),
|
| 532 |
+
"was" if len(missing_fields) == 1 else "were",
|
| 533 |
+
)
|
| 534 |
+
warnings.warn(msg, RuntimeWarning, stacklevel=2)
|
| 535 |
+
|
| 536 |
+
return svmem(
|
| 537 |
+
total,
|
| 538 |
+
avail,
|
| 539 |
+
percent,
|
| 540 |
+
used,
|
| 541 |
+
free,
|
| 542 |
+
active,
|
| 543 |
+
inactive,
|
| 544 |
+
buffers,
|
| 545 |
+
cached,
|
| 546 |
+
shared,
|
| 547 |
+
slab,
|
| 548 |
+
)
|
| 549 |
+
|
| 550 |
+
|
| 551 |
+
def swap_memory():
|
| 552 |
+
"""Return swap memory metrics."""
|
| 553 |
+
mems = {}
|
| 554 |
+
with open_binary('%s/meminfo' % get_procfs_path()) as f:
|
| 555 |
+
for line in f:
|
| 556 |
+
fields = line.split()
|
| 557 |
+
mems[fields[0]] = int(fields[1]) * 1024
|
| 558 |
+
# We prefer /proc/meminfo over sysinfo() syscall so that
|
| 559 |
+
# psutil.PROCFS_PATH can be used in order to allow retrieval
|
| 560 |
+
# for linux containers, see:
|
| 561 |
+
# https://github.com/giampaolo/psutil/issues/1015
|
| 562 |
+
try:
|
| 563 |
+
total = mems[b'SwapTotal:']
|
| 564 |
+
free = mems[b'SwapFree:']
|
| 565 |
+
except KeyError:
|
| 566 |
+
_, _, _, _, total, free, unit_multiplier = cext.linux_sysinfo()
|
| 567 |
+
total *= unit_multiplier
|
| 568 |
+
free *= unit_multiplier
|
| 569 |
+
|
| 570 |
+
used = total - free
|
| 571 |
+
percent = usage_percent(used, total, round_=1)
|
| 572 |
+
# get pgin/pgouts
|
| 573 |
+
try:
|
| 574 |
+
f = open_binary("%s/vmstat" % get_procfs_path())
|
| 575 |
+
except IOError as err:
|
| 576 |
+
# see https://github.com/giampaolo/psutil/issues/722
|
| 577 |
+
msg = (
|
| 578 |
+
"'sin' and 'sout' swap memory stats couldn't "
|
| 579 |
+
+ "be determined and were set to 0 (%s)" % str(err)
|
| 580 |
+
)
|
| 581 |
+
warnings.warn(msg, RuntimeWarning, stacklevel=2)
|
| 582 |
+
sin = sout = 0
|
| 583 |
+
else:
|
| 584 |
+
with f:
|
| 585 |
+
sin = sout = None
|
| 586 |
+
for line in f:
|
| 587 |
+
# values are expressed in 4 kilo bytes, we want
|
| 588 |
+
# bytes instead
|
| 589 |
+
if line.startswith(b'pswpin'):
|
| 590 |
+
sin = int(line.split(b' ')[1]) * 4 * 1024
|
| 591 |
+
elif line.startswith(b'pswpout'):
|
| 592 |
+
sout = int(line.split(b' ')[1]) * 4 * 1024
|
| 593 |
+
if sin is not None and sout is not None:
|
| 594 |
+
break
|
| 595 |
+
else:
|
| 596 |
+
# we might get here when dealing with exotic Linux
|
| 597 |
+
# flavors, see:
|
| 598 |
+
# https://github.com/giampaolo/psutil/issues/313
|
| 599 |
+
msg = "'sin' and 'sout' swap memory stats couldn't "
|
| 600 |
+
msg += "be determined and were set to 0"
|
| 601 |
+
warnings.warn(msg, RuntimeWarning, stacklevel=2)
|
| 602 |
+
sin = sout = 0
|
| 603 |
+
return _common.sswap(total, used, free, percent, sin, sout)
|
| 604 |
+
|
| 605 |
+
|
| 606 |
+
# =====================================================================
|
| 607 |
+
# --- CPU
|
| 608 |
+
# =====================================================================
|
| 609 |
+
|
| 610 |
+
|
| 611 |
+
def cpu_times():
|
| 612 |
+
"""Return a named tuple representing the following system-wide
|
| 613 |
+
CPU times:
|
| 614 |
+
(user, nice, system, idle, iowait, irq, softirq [steal, [guest,
|
| 615 |
+
[guest_nice]]])
|
| 616 |
+
Last 3 fields may not be available on all Linux kernel versions.
|
| 617 |
+
"""
|
| 618 |
+
procfs_path = get_procfs_path()
|
| 619 |
+
set_scputimes_ntuple(procfs_path)
|
| 620 |
+
with open_binary('%s/stat' % procfs_path) as f:
|
| 621 |
+
values = f.readline().split()
|
| 622 |
+
fields = values[1 : len(scputimes._fields) + 1]
|
| 623 |
+
fields = [float(x) / CLOCK_TICKS for x in fields]
|
| 624 |
+
return scputimes(*fields)
|
| 625 |
+
|
| 626 |
+
|
| 627 |
+
def per_cpu_times():
|
| 628 |
+
"""Return a list of namedtuple representing the CPU times
|
| 629 |
+
for every CPU available on the system.
|
| 630 |
+
"""
|
| 631 |
+
procfs_path = get_procfs_path()
|
| 632 |
+
set_scputimes_ntuple(procfs_path)
|
| 633 |
+
cpus = []
|
| 634 |
+
with open_binary('%s/stat' % procfs_path) as f:
|
| 635 |
+
# get rid of the first line which refers to system wide CPU stats
|
| 636 |
+
f.readline()
|
| 637 |
+
for line in f:
|
| 638 |
+
if line.startswith(b'cpu'):
|
| 639 |
+
values = line.split()
|
| 640 |
+
fields = values[1 : len(scputimes._fields) + 1]
|
| 641 |
+
fields = [float(x) / CLOCK_TICKS for x in fields]
|
| 642 |
+
entry = scputimes(*fields)
|
| 643 |
+
cpus.append(entry)
|
| 644 |
+
return cpus
|
| 645 |
+
|
| 646 |
+
|
| 647 |
+
def cpu_count_logical():
|
| 648 |
+
"""Return the number of logical CPUs in the system."""
|
| 649 |
+
try:
|
| 650 |
+
return os.sysconf("SC_NPROCESSORS_ONLN")
|
| 651 |
+
except ValueError:
|
| 652 |
+
# as a second fallback we try to parse /proc/cpuinfo
|
| 653 |
+
num = 0
|
| 654 |
+
with open_binary('%s/cpuinfo' % get_procfs_path()) as f:
|
| 655 |
+
for line in f:
|
| 656 |
+
if line.lower().startswith(b'processor'):
|
| 657 |
+
num += 1
|
| 658 |
+
|
| 659 |
+
# unknown format (e.g. amrel/sparc architectures), see:
|
| 660 |
+
# https://github.com/giampaolo/psutil/issues/200
|
| 661 |
+
# try to parse /proc/stat as a last resort
|
| 662 |
+
if num == 0:
|
| 663 |
+
search = re.compile(r'cpu\d')
|
| 664 |
+
with open_text('%s/stat' % get_procfs_path()) as f:
|
| 665 |
+
for line in f:
|
| 666 |
+
line = line.split(' ')[0]
|
| 667 |
+
if search.match(line):
|
| 668 |
+
num += 1
|
| 669 |
+
|
| 670 |
+
if num == 0:
|
| 671 |
+
# mimic os.cpu_count()
|
| 672 |
+
return None
|
| 673 |
+
return num
|
| 674 |
+
|
| 675 |
+
|
| 676 |
+
def cpu_count_cores():
|
| 677 |
+
"""Return the number of CPU cores in the system."""
|
| 678 |
+
# Method #1
|
| 679 |
+
ls = set()
|
| 680 |
+
# These 2 files are the same but */core_cpus_list is newer while
|
| 681 |
+
# */thread_siblings_list is deprecated and may disappear in the future.
|
| 682 |
+
# https://www.kernel.org/doc/Documentation/admin-guide/cputopology.rst
|
| 683 |
+
# https://github.com/giampaolo/psutil/pull/1727#issuecomment-707624964
|
| 684 |
+
# https://lkml.org/lkml/2019/2/26/41
|
| 685 |
+
p1 = "/sys/devices/system/cpu/cpu[0-9]*/topology/core_cpus_list"
|
| 686 |
+
p2 = "/sys/devices/system/cpu/cpu[0-9]*/topology/thread_siblings_list"
|
| 687 |
+
for path in glob.glob(p1) or glob.glob(p2):
|
| 688 |
+
with open_binary(path) as f:
|
| 689 |
+
ls.add(f.read().strip())
|
| 690 |
+
result = len(ls)
|
| 691 |
+
if result != 0:
|
| 692 |
+
return result
|
| 693 |
+
|
| 694 |
+
# Method #2
|
| 695 |
+
mapping = {}
|
| 696 |
+
current_info = {}
|
| 697 |
+
with open_binary('%s/cpuinfo' % get_procfs_path()) as f:
|
| 698 |
+
for line in f:
|
| 699 |
+
line = line.strip().lower()
|
| 700 |
+
if not line:
|
| 701 |
+
# new section
|
| 702 |
+
try:
|
| 703 |
+
mapping[current_info[b'physical id']] = current_info[
|
| 704 |
+
b'cpu cores'
|
| 705 |
+
]
|
| 706 |
+
except KeyError:
|
| 707 |
+
pass
|
| 708 |
+
current_info = {}
|
| 709 |
+
elif line.startswith((b'physical id', b'cpu cores')):
|
| 710 |
+
# ongoing section
|
| 711 |
+
key, value = line.split(b'\t:', 1)
|
| 712 |
+
current_info[key] = int(value)
|
| 713 |
+
|
| 714 |
+
result = sum(mapping.values())
|
| 715 |
+
return result or None # mimic os.cpu_count()
|
| 716 |
+
|
| 717 |
+
|
| 718 |
+
def cpu_stats():
|
| 719 |
+
"""Return various CPU stats as a named tuple."""
|
| 720 |
+
with open_binary('%s/stat' % get_procfs_path()) as f:
|
| 721 |
+
ctx_switches = None
|
| 722 |
+
interrupts = None
|
| 723 |
+
soft_interrupts = None
|
| 724 |
+
for line in f:
|
| 725 |
+
if line.startswith(b'ctxt'):
|
| 726 |
+
ctx_switches = int(line.split()[1])
|
| 727 |
+
elif line.startswith(b'intr'):
|
| 728 |
+
interrupts = int(line.split()[1])
|
| 729 |
+
elif line.startswith(b'softirq'):
|
| 730 |
+
soft_interrupts = int(line.split()[1])
|
| 731 |
+
if (
|
| 732 |
+
ctx_switches is not None
|
| 733 |
+
and soft_interrupts is not None
|
| 734 |
+
and interrupts is not None
|
| 735 |
+
):
|
| 736 |
+
break
|
| 737 |
+
syscalls = 0
|
| 738 |
+
return _common.scpustats(
|
| 739 |
+
ctx_switches, interrupts, soft_interrupts, syscalls
|
| 740 |
+
)
|
| 741 |
+
|
| 742 |
+
|
| 743 |
+
def _cpu_get_cpuinfo_freq():
|
| 744 |
+
"""Return current CPU frequency from cpuinfo if available."""
|
| 745 |
+
ret = []
|
| 746 |
+
with open_binary('%s/cpuinfo' % get_procfs_path()) as f:
|
| 747 |
+
for line in f:
|
| 748 |
+
if line.lower().startswith(b'cpu mhz'):
|
| 749 |
+
ret.append(float(line.split(b':', 1)[1]))
|
| 750 |
+
return ret
|
| 751 |
+
|
| 752 |
+
|
| 753 |
+
if os.path.exists("/sys/devices/system/cpu/cpufreq/policy0") or os.path.exists(
|
| 754 |
+
"/sys/devices/system/cpu/cpu0/cpufreq"
|
| 755 |
+
):
|
| 756 |
+
|
| 757 |
+
def cpu_freq():
|
| 758 |
+
"""Return frequency metrics for all CPUs.
|
| 759 |
+
Contrarily to other OSes, Linux updates these values in
|
| 760 |
+
real-time.
|
| 761 |
+
"""
|
| 762 |
+
cpuinfo_freqs = _cpu_get_cpuinfo_freq()
|
| 763 |
+
paths = glob.glob(
|
| 764 |
+
"/sys/devices/system/cpu/cpufreq/policy[0-9]*"
|
| 765 |
+
) or glob.glob("/sys/devices/system/cpu/cpu[0-9]*/cpufreq")
|
| 766 |
+
paths.sort(key=lambda x: int(re.search(r"[0-9]+", x).group()))
|
| 767 |
+
ret = []
|
| 768 |
+
pjoin = os.path.join
|
| 769 |
+
for i, path in enumerate(paths):
|
| 770 |
+
if len(paths) == len(cpuinfo_freqs):
|
| 771 |
+
# take cached value from cpuinfo if available, see:
|
| 772 |
+
# https://github.com/giampaolo/psutil/issues/1851
|
| 773 |
+
curr = cpuinfo_freqs[i] * 1000
|
| 774 |
+
else:
|
| 775 |
+
curr = bcat(pjoin(path, "scaling_cur_freq"), fallback=None)
|
| 776 |
+
if curr is None:
|
| 777 |
+
# Likely an old RedHat, see:
|
| 778 |
+
# https://github.com/giampaolo/psutil/issues/1071
|
| 779 |
+
curr = bcat(pjoin(path, "cpuinfo_cur_freq"), fallback=None)
|
| 780 |
+
if curr is None:
|
| 781 |
+
online_path = (
|
| 782 |
+
"/sys/devices/system/cpu/cpu{}/online".format(i)
|
| 783 |
+
)
|
| 784 |
+
# if cpu core is offline, set to all zeroes
|
| 785 |
+
if cat(online_path, fallback=None) == "0\n":
|
| 786 |
+
ret.append(_common.scpufreq(0.0, 0.0, 0.0))
|
| 787 |
+
continue
|
| 788 |
+
msg = "can't find current frequency file"
|
| 789 |
+
raise NotImplementedError(msg)
|
| 790 |
+
curr = int(curr) / 1000
|
| 791 |
+
max_ = int(bcat(pjoin(path, "scaling_max_freq"))) / 1000
|
| 792 |
+
min_ = int(bcat(pjoin(path, "scaling_min_freq"))) / 1000
|
| 793 |
+
ret.append(_common.scpufreq(curr, min_, max_))
|
| 794 |
+
return ret
|
| 795 |
+
|
| 796 |
+
else:
|
| 797 |
+
|
| 798 |
+
def cpu_freq():
|
| 799 |
+
"""Alternate implementation using /proc/cpuinfo.
|
| 800 |
+
min and max frequencies are not available and are set to None.
|
| 801 |
+
"""
|
| 802 |
+
return [_common.scpufreq(x, 0.0, 0.0) for x in _cpu_get_cpuinfo_freq()]
|
| 803 |
+
|
| 804 |
+
|
| 805 |
+
# =====================================================================
|
| 806 |
+
# --- network
|
| 807 |
+
# =====================================================================
|
| 808 |
+
|
| 809 |
+
|
| 810 |
+
net_if_addrs = cext_posix.net_if_addrs
|
| 811 |
+
|
| 812 |
+
|
| 813 |
+
class _Ipv6UnsupportedError(Exception):
|
| 814 |
+
pass
|
| 815 |
+
|
| 816 |
+
|
| 817 |
+
class NetConnections:
|
| 818 |
+
"""A wrapper on top of /proc/net/* files, retrieving per-process
|
| 819 |
+
and system-wide open connections (TCP, UDP, UNIX) similarly to
|
| 820 |
+
"netstat -an".
|
| 821 |
+
|
| 822 |
+
Note: in case of UNIX sockets we're only able to determine the
|
| 823 |
+
local endpoint/path, not the one it's connected to.
|
| 824 |
+
According to [1] it would be possible but not easily.
|
| 825 |
+
|
| 826 |
+
[1] http://serverfault.com/a/417946
|
| 827 |
+
"""
|
| 828 |
+
|
| 829 |
+
def __init__(self):
|
| 830 |
+
# The string represents the basename of the corresponding
|
| 831 |
+
# /proc/net/{proto_name} file.
|
| 832 |
+
tcp4 = ("tcp", socket.AF_INET, socket.SOCK_STREAM)
|
| 833 |
+
tcp6 = ("tcp6", socket.AF_INET6, socket.SOCK_STREAM)
|
| 834 |
+
udp4 = ("udp", socket.AF_INET, socket.SOCK_DGRAM)
|
| 835 |
+
udp6 = ("udp6", socket.AF_INET6, socket.SOCK_DGRAM)
|
| 836 |
+
unix = ("unix", socket.AF_UNIX, None)
|
| 837 |
+
self.tmap = {
|
| 838 |
+
"all": (tcp4, tcp6, udp4, udp6, unix),
|
| 839 |
+
"tcp": (tcp4, tcp6),
|
| 840 |
+
"tcp4": (tcp4,),
|
| 841 |
+
"tcp6": (tcp6,),
|
| 842 |
+
"udp": (udp4, udp6),
|
| 843 |
+
"udp4": (udp4,),
|
| 844 |
+
"udp6": (udp6,),
|
| 845 |
+
"unix": (unix,),
|
| 846 |
+
"inet": (tcp4, tcp6, udp4, udp6),
|
| 847 |
+
"inet4": (tcp4, udp4),
|
| 848 |
+
"inet6": (tcp6, udp6),
|
| 849 |
+
}
|
| 850 |
+
self._procfs_path = None
|
| 851 |
+
|
| 852 |
+
def get_proc_inodes(self, pid):
|
| 853 |
+
inodes = defaultdict(list)
|
| 854 |
+
for fd in os.listdir("%s/%s/fd" % (self._procfs_path, pid)):
|
| 855 |
+
try:
|
| 856 |
+
inode = readlink("%s/%s/fd/%s" % (self._procfs_path, pid, fd))
|
| 857 |
+
except (FileNotFoundError, ProcessLookupError):
|
| 858 |
+
# ENOENT == file which is gone in the meantime;
|
| 859 |
+
# os.stat('/proc/%s' % self.pid) will be done later
|
| 860 |
+
# to force NSP (if it's the case)
|
| 861 |
+
continue
|
| 862 |
+
except OSError as err:
|
| 863 |
+
if err.errno == errno.EINVAL:
|
| 864 |
+
# not a link
|
| 865 |
+
continue
|
| 866 |
+
if err.errno == errno.ENAMETOOLONG:
|
| 867 |
+
# file name too long
|
| 868 |
+
debug(err)
|
| 869 |
+
continue
|
| 870 |
+
raise
|
| 871 |
+
else:
|
| 872 |
+
if inode.startswith('socket:['):
|
| 873 |
+
# the process is using a socket
|
| 874 |
+
inode = inode[8:][:-1]
|
| 875 |
+
inodes[inode].append((pid, int(fd)))
|
| 876 |
+
return inodes
|
| 877 |
+
|
| 878 |
+
def get_all_inodes(self):
|
| 879 |
+
inodes = {}
|
| 880 |
+
for pid in pids():
|
| 881 |
+
try:
|
| 882 |
+
inodes.update(self.get_proc_inodes(pid))
|
| 883 |
+
except (FileNotFoundError, ProcessLookupError, PermissionError):
|
| 884 |
+
# os.listdir() is gonna raise a lot of access denied
|
| 885 |
+
# exceptions in case of unprivileged user; that's fine
|
| 886 |
+
# as we'll just end up returning a connection with PID
|
| 887 |
+
# and fd set to None anyway.
|
| 888 |
+
# Both netstat -an and lsof does the same so it's
|
| 889 |
+
# unlikely we can do any better.
|
| 890 |
+
# ENOENT just means a PID disappeared on us.
|
| 891 |
+
continue
|
| 892 |
+
return inodes
|
| 893 |
+
|
| 894 |
+
@staticmethod
|
| 895 |
+
def decode_address(addr, family):
|
| 896 |
+
"""Accept an "ip:port" address as displayed in /proc/net/*
|
| 897 |
+
and convert it into a human readable form, like:
|
| 898 |
+
|
| 899 |
+
"0500000A:0016" -> ("10.0.0.5", 22)
|
| 900 |
+
"0000000000000000FFFF00000100007F:9E49" -> ("::ffff:127.0.0.1", 40521)
|
| 901 |
+
|
| 902 |
+
The IP address portion is a little or big endian four-byte
|
| 903 |
+
hexadecimal number; that is, the least significant byte is listed
|
| 904 |
+
first, so we need to reverse the order of the bytes to convert it
|
| 905 |
+
to an IP address.
|
| 906 |
+
The port is represented as a two-byte hexadecimal number.
|
| 907 |
+
|
| 908 |
+
Reference:
|
| 909 |
+
http://linuxdevcenter.com/pub/a/linux/2000/11/16/LinuxAdmin.html
|
| 910 |
+
"""
|
| 911 |
+
ip, port = addr.split(':')
|
| 912 |
+
port = int(port, 16)
|
| 913 |
+
# this usually refers to a local socket in listen mode with
|
| 914 |
+
# no end-points connected
|
| 915 |
+
if not port:
|
| 916 |
+
return ()
|
| 917 |
+
if PY3:
|
| 918 |
+
ip = ip.encode('ascii')
|
| 919 |
+
if family == socket.AF_INET:
|
| 920 |
+
# see: https://github.com/giampaolo/psutil/issues/201
|
| 921 |
+
if LITTLE_ENDIAN:
|
| 922 |
+
ip = socket.inet_ntop(family, base64.b16decode(ip)[::-1])
|
| 923 |
+
else:
|
| 924 |
+
ip = socket.inet_ntop(family, base64.b16decode(ip))
|
| 925 |
+
else: # IPv6
|
| 926 |
+
ip = base64.b16decode(ip)
|
| 927 |
+
try:
|
| 928 |
+
# see: https://github.com/giampaolo/psutil/issues/201
|
| 929 |
+
if LITTLE_ENDIAN:
|
| 930 |
+
ip = socket.inet_ntop(
|
| 931 |
+
socket.AF_INET6,
|
| 932 |
+
struct.pack('>4I', *struct.unpack('<4I', ip)),
|
| 933 |
+
)
|
| 934 |
+
else:
|
| 935 |
+
ip = socket.inet_ntop(
|
| 936 |
+
socket.AF_INET6,
|
| 937 |
+
struct.pack('<4I', *struct.unpack('<4I', ip)),
|
| 938 |
+
)
|
| 939 |
+
except ValueError:
|
| 940 |
+
# see: https://github.com/giampaolo/psutil/issues/623
|
| 941 |
+
if not supports_ipv6():
|
| 942 |
+
raise _Ipv6UnsupportedError
|
| 943 |
+
else:
|
| 944 |
+
raise
|
| 945 |
+
return _common.addr(ip, port)
|
| 946 |
+
|
| 947 |
+
@staticmethod
|
| 948 |
+
def process_inet(file, family, type_, inodes, filter_pid=None):
|
| 949 |
+
"""Parse /proc/net/tcp* and /proc/net/udp* files."""
|
| 950 |
+
if file.endswith('6') and not os.path.exists(file):
|
| 951 |
+
# IPv6 not supported
|
| 952 |
+
return
|
| 953 |
+
with open_text(file) as f:
|
| 954 |
+
f.readline() # skip the first line
|
| 955 |
+
for lineno, line in enumerate(f, 1):
|
| 956 |
+
try:
|
| 957 |
+
_, laddr, raddr, status, _, _, _, _, _, inode = (
|
| 958 |
+
line.split()[:10]
|
| 959 |
+
)
|
| 960 |
+
except ValueError:
|
| 961 |
+
raise RuntimeError(
|
| 962 |
+
"error while parsing %s; malformed line %s %r"
|
| 963 |
+
% (file, lineno, line)
|
| 964 |
+
)
|
| 965 |
+
if inode in inodes:
|
| 966 |
+
# # We assume inet sockets are unique, so we error
|
| 967 |
+
# # out if there are multiple references to the
|
| 968 |
+
# # same inode. We won't do this for UNIX sockets.
|
| 969 |
+
# if len(inodes[inode]) > 1 and family != socket.AF_UNIX:
|
| 970 |
+
# raise ValueError("ambiguous inode with multiple "
|
| 971 |
+
# "PIDs references")
|
| 972 |
+
pid, fd = inodes[inode][0]
|
| 973 |
+
else:
|
| 974 |
+
pid, fd = None, -1
|
| 975 |
+
if filter_pid is not None and filter_pid != pid:
|
| 976 |
+
continue
|
| 977 |
+
else:
|
| 978 |
+
if type_ == socket.SOCK_STREAM:
|
| 979 |
+
status = TCP_STATUSES[status]
|
| 980 |
+
else:
|
| 981 |
+
status = _common.CONN_NONE
|
| 982 |
+
try:
|
| 983 |
+
laddr = NetConnections.decode_address(laddr, family)
|
| 984 |
+
raddr = NetConnections.decode_address(raddr, family)
|
| 985 |
+
except _Ipv6UnsupportedError:
|
| 986 |
+
continue
|
| 987 |
+
yield (fd, family, type_, laddr, raddr, status, pid)
|
| 988 |
+
|
| 989 |
+
@staticmethod
|
| 990 |
+
def process_unix(file, family, inodes, filter_pid=None):
|
| 991 |
+
"""Parse /proc/net/unix files."""
|
| 992 |
+
with open_text(file) as f:
|
| 993 |
+
f.readline() # skip the first line
|
| 994 |
+
for line in f:
|
| 995 |
+
tokens = line.split()
|
| 996 |
+
try:
|
| 997 |
+
_, _, _, _, type_, _, inode = tokens[0:7]
|
| 998 |
+
except ValueError:
|
| 999 |
+
if ' ' not in line:
|
| 1000 |
+
# see: https://github.com/giampaolo/psutil/issues/766
|
| 1001 |
+
continue
|
| 1002 |
+
raise RuntimeError(
|
| 1003 |
+
"error while parsing %s; malformed line %r"
|
| 1004 |
+
% (file, line)
|
| 1005 |
+
)
|
| 1006 |
+
if inode in inodes: # noqa
|
| 1007 |
+
# With UNIX sockets we can have a single inode
|
| 1008 |
+
# referencing many file descriptors.
|
| 1009 |
+
pairs = inodes[inode]
|
| 1010 |
+
else:
|
| 1011 |
+
pairs = [(None, -1)]
|
| 1012 |
+
for pid, fd in pairs:
|
| 1013 |
+
if filter_pid is not None and filter_pid != pid:
|
| 1014 |
+
continue
|
| 1015 |
+
else:
|
| 1016 |
+
path = tokens[-1] if len(tokens) == 8 else ''
|
| 1017 |
+
type_ = _common.socktype_to_enum(int(type_))
|
| 1018 |
+
# XXX: determining the remote endpoint of a
|
| 1019 |
+
# UNIX socket on Linux is not possible, see:
|
| 1020 |
+
# https://serverfault.com/questions/252723/
|
| 1021 |
+
raddr = ""
|
| 1022 |
+
status = _common.CONN_NONE
|
| 1023 |
+
yield (fd, family, type_, path, raddr, status, pid)
|
| 1024 |
+
|
| 1025 |
+
def retrieve(self, kind, pid=None):
|
| 1026 |
+
if kind not in self.tmap:
|
| 1027 |
+
raise ValueError(
|
| 1028 |
+
"invalid %r kind argument; choose between %s"
|
| 1029 |
+
% (kind, ', '.join([repr(x) for x in self.tmap]))
|
| 1030 |
+
)
|
| 1031 |
+
self._procfs_path = get_procfs_path()
|
| 1032 |
+
if pid is not None:
|
| 1033 |
+
inodes = self.get_proc_inodes(pid)
|
| 1034 |
+
if not inodes:
|
| 1035 |
+
# no connections for this process
|
| 1036 |
+
return []
|
| 1037 |
+
else:
|
| 1038 |
+
inodes = self.get_all_inodes()
|
| 1039 |
+
ret = set()
|
| 1040 |
+
for proto_name, family, type_ in self.tmap[kind]:
|
| 1041 |
+
path = "%s/net/%s" % (self._procfs_path, proto_name)
|
| 1042 |
+
if family in {socket.AF_INET, socket.AF_INET6}:
|
| 1043 |
+
ls = self.process_inet(
|
| 1044 |
+
path, family, type_, inodes, filter_pid=pid
|
| 1045 |
+
)
|
| 1046 |
+
else:
|
| 1047 |
+
ls = self.process_unix(path, family, inodes, filter_pid=pid)
|
| 1048 |
+
for fd, family, type_, laddr, raddr, status, bound_pid in ls:
|
| 1049 |
+
if pid:
|
| 1050 |
+
conn = _common.pconn(
|
| 1051 |
+
fd, family, type_, laddr, raddr, status
|
| 1052 |
+
)
|
| 1053 |
+
else:
|
| 1054 |
+
conn = _common.sconn(
|
| 1055 |
+
fd, family, type_, laddr, raddr, status, bound_pid
|
| 1056 |
+
)
|
| 1057 |
+
ret.add(conn)
|
| 1058 |
+
return list(ret)
|
| 1059 |
+
|
| 1060 |
+
|
| 1061 |
+
_net_connections = NetConnections()
|
| 1062 |
+
|
| 1063 |
+
|
| 1064 |
+
def net_connections(kind='inet'):
|
| 1065 |
+
"""Return system-wide open connections."""
|
| 1066 |
+
return _net_connections.retrieve(kind)
|
| 1067 |
+
|
| 1068 |
+
|
| 1069 |
+
def net_io_counters():
|
| 1070 |
+
"""Return network I/O statistics for every network interface
|
| 1071 |
+
installed on the system as a dict of raw tuples.
|
| 1072 |
+
"""
|
| 1073 |
+
with open_text("%s/net/dev" % get_procfs_path()) as f:
|
| 1074 |
+
lines = f.readlines()
|
| 1075 |
+
retdict = {}
|
| 1076 |
+
for line in lines[2:]:
|
| 1077 |
+
colon = line.rfind(':')
|
| 1078 |
+
assert colon > 0, repr(line)
|
| 1079 |
+
name = line[:colon].strip()
|
| 1080 |
+
fields = line[colon + 1 :].strip().split()
|
| 1081 |
+
|
| 1082 |
+
(
|
| 1083 |
+
# in
|
| 1084 |
+
bytes_recv,
|
| 1085 |
+
packets_recv,
|
| 1086 |
+
errin,
|
| 1087 |
+
dropin,
|
| 1088 |
+
_fifoin, # unused
|
| 1089 |
+
_framein, # unused
|
| 1090 |
+
_compressedin, # unused
|
| 1091 |
+
_multicastin, # unused
|
| 1092 |
+
# out
|
| 1093 |
+
bytes_sent,
|
| 1094 |
+
packets_sent,
|
| 1095 |
+
errout,
|
| 1096 |
+
dropout,
|
| 1097 |
+
_fifoout, # unused
|
| 1098 |
+
_collisionsout, # unused
|
| 1099 |
+
_carrierout, # unused
|
| 1100 |
+
_compressedout, # unused
|
| 1101 |
+
) = map(int, fields)
|
| 1102 |
+
|
| 1103 |
+
retdict[name] = (
|
| 1104 |
+
bytes_sent,
|
| 1105 |
+
bytes_recv,
|
| 1106 |
+
packets_sent,
|
| 1107 |
+
packets_recv,
|
| 1108 |
+
errin,
|
| 1109 |
+
errout,
|
| 1110 |
+
dropin,
|
| 1111 |
+
dropout,
|
| 1112 |
+
)
|
| 1113 |
+
return retdict
|
| 1114 |
+
|
| 1115 |
+
|
| 1116 |
+
def net_if_stats():
|
| 1117 |
+
"""Get NIC stats (isup, duplex, speed, mtu)."""
|
| 1118 |
+
duplex_map = {
|
| 1119 |
+
cext.DUPLEX_FULL: NIC_DUPLEX_FULL,
|
| 1120 |
+
cext.DUPLEX_HALF: NIC_DUPLEX_HALF,
|
| 1121 |
+
cext.DUPLEX_UNKNOWN: NIC_DUPLEX_UNKNOWN,
|
| 1122 |
+
}
|
| 1123 |
+
names = net_io_counters().keys()
|
| 1124 |
+
ret = {}
|
| 1125 |
+
for name in names:
|
| 1126 |
+
try:
|
| 1127 |
+
mtu = cext_posix.net_if_mtu(name)
|
| 1128 |
+
flags = cext_posix.net_if_flags(name)
|
| 1129 |
+
duplex, speed = cext.net_if_duplex_speed(name)
|
| 1130 |
+
except OSError as err:
|
| 1131 |
+
# https://github.com/giampaolo/psutil/issues/1279
|
| 1132 |
+
if err.errno != errno.ENODEV:
|
| 1133 |
+
raise
|
| 1134 |
+
else:
|
| 1135 |
+
debug(err)
|
| 1136 |
+
else:
|
| 1137 |
+
output_flags = ','.join(flags)
|
| 1138 |
+
isup = 'running' in flags
|
| 1139 |
+
ret[name] = _common.snicstats(
|
| 1140 |
+
isup, duplex_map[duplex], speed, mtu, output_flags
|
| 1141 |
+
)
|
| 1142 |
+
return ret
|
| 1143 |
+
|
| 1144 |
+
|
| 1145 |
+
# =====================================================================
|
| 1146 |
+
# --- disks
|
| 1147 |
+
# =====================================================================
|
| 1148 |
+
|
| 1149 |
+
|
| 1150 |
+
disk_usage = _psposix.disk_usage
|
| 1151 |
+
|
| 1152 |
+
|
| 1153 |
+
def disk_io_counters(perdisk=False):
|
| 1154 |
+
"""Return disk I/O statistics for every disk installed on the
|
| 1155 |
+
system as a dict of raw tuples.
|
| 1156 |
+
"""
|
| 1157 |
+
|
| 1158 |
+
def read_procfs():
|
| 1159 |
+
# OK, this is a bit confusing. The format of /proc/diskstats can
|
| 1160 |
+
# have 3 variations.
|
| 1161 |
+
# On Linux 2.4 each line has always 15 fields, e.g.:
|
| 1162 |
+
# "3 0 8 hda 8 8 8 8 8 8 8 8 8 8 8"
|
| 1163 |
+
# On Linux 2.6+ each line *usually* has 14 fields, and the disk
|
| 1164 |
+
# name is in another position, like this:
|
| 1165 |
+
# "3 0 hda 8 8 8 8 8 8 8 8 8 8 8"
|
| 1166 |
+
# ...unless (Linux 2.6) the line refers to a partition instead
|
| 1167 |
+
# of a disk, in which case the line has less fields (7):
|
| 1168 |
+
# "3 1 hda1 8 8 8 8"
|
| 1169 |
+
# 4.18+ has 4 fields added:
|
| 1170 |
+
# "3 0 hda 8 8 8 8 8 8 8 8 8 8 8 0 0 0 0"
|
| 1171 |
+
# 5.5 has 2 more fields.
|
| 1172 |
+
# See:
|
| 1173 |
+
# https://www.kernel.org/doc/Documentation/iostats.txt
|
| 1174 |
+
# https://www.kernel.org/doc/Documentation/ABI/testing/procfs-diskstats
|
| 1175 |
+
with open_text("%s/diskstats" % get_procfs_path()) as f:
|
| 1176 |
+
lines = f.readlines()
|
| 1177 |
+
for line in lines:
|
| 1178 |
+
fields = line.split()
|
| 1179 |
+
flen = len(fields)
|
| 1180 |
+
# fmt: off
|
| 1181 |
+
if flen == 15:
|
| 1182 |
+
# Linux 2.4
|
| 1183 |
+
name = fields[3]
|
| 1184 |
+
reads = int(fields[2])
|
| 1185 |
+
(reads_merged, rbytes, rtime, writes, writes_merged,
|
| 1186 |
+
wbytes, wtime, _, busy_time, _) = map(int, fields[4:14])
|
| 1187 |
+
elif flen == 14 or flen >= 18:
|
| 1188 |
+
# Linux 2.6+, line referring to a disk
|
| 1189 |
+
name = fields[2]
|
| 1190 |
+
(reads, reads_merged, rbytes, rtime, writes, writes_merged,
|
| 1191 |
+
wbytes, wtime, _, busy_time, _) = map(int, fields[3:14])
|
| 1192 |
+
elif flen == 7:
|
| 1193 |
+
# Linux 2.6+, line referring to a partition
|
| 1194 |
+
name = fields[2]
|
| 1195 |
+
reads, rbytes, writes, wbytes = map(int, fields[3:])
|
| 1196 |
+
rtime = wtime = reads_merged = writes_merged = busy_time = 0
|
| 1197 |
+
else:
|
| 1198 |
+
raise ValueError("not sure how to interpret line %r" % line)
|
| 1199 |
+
yield (name, reads, writes, rbytes, wbytes, rtime, wtime,
|
| 1200 |
+
reads_merged, writes_merged, busy_time)
|
| 1201 |
+
# fmt: on
|
| 1202 |
+
|
| 1203 |
+
def read_sysfs():
|
| 1204 |
+
for block in os.listdir('/sys/block'):
|
| 1205 |
+
for root, _, files in os.walk(os.path.join('/sys/block', block)):
|
| 1206 |
+
if 'stat' not in files:
|
| 1207 |
+
continue
|
| 1208 |
+
with open_text(os.path.join(root, 'stat')) as f:
|
| 1209 |
+
fields = f.read().strip().split()
|
| 1210 |
+
name = os.path.basename(root)
|
| 1211 |
+
# fmt: off
|
| 1212 |
+
(reads, reads_merged, rbytes, rtime, writes, writes_merged,
|
| 1213 |
+
wbytes, wtime, _, busy_time) = map(int, fields[:10])
|
| 1214 |
+
yield (name, reads, writes, rbytes, wbytes, rtime,
|
| 1215 |
+
wtime, reads_merged, writes_merged, busy_time)
|
| 1216 |
+
# fmt: on
|
| 1217 |
+
|
| 1218 |
+
if os.path.exists('%s/diskstats' % get_procfs_path()):
|
| 1219 |
+
gen = read_procfs()
|
| 1220 |
+
elif os.path.exists('/sys/block'):
|
| 1221 |
+
gen = read_sysfs()
|
| 1222 |
+
else:
|
| 1223 |
+
raise NotImplementedError(
|
| 1224 |
+
"%s/diskstats nor /sys/block filesystem are available on this "
|
| 1225 |
+
"system"
|
| 1226 |
+
% get_procfs_path()
|
| 1227 |
+
)
|
| 1228 |
+
|
| 1229 |
+
retdict = {}
|
| 1230 |
+
for entry in gen:
|
| 1231 |
+
# fmt: off
|
| 1232 |
+
(name, reads, writes, rbytes, wbytes, rtime, wtime, reads_merged,
|
| 1233 |
+
writes_merged, busy_time) = entry
|
| 1234 |
+
if not perdisk and not is_storage_device(name):
|
| 1235 |
+
# perdisk=False means we want to calculate totals so we skip
|
| 1236 |
+
# partitions (e.g. 'sda1', 'nvme0n1p1') and only include
|
| 1237 |
+
# base disk devices (e.g. 'sda', 'nvme0n1'). Base disks
|
| 1238 |
+
# include a total of all their partitions + some extra size
|
| 1239 |
+
# of their own:
|
| 1240 |
+
# $ cat /proc/diskstats
|
| 1241 |
+
# 259 0 sda 10485760 ...
|
| 1242 |
+
# 259 1 sda1 5186039 ...
|
| 1243 |
+
# 259 1 sda2 5082039 ...
|
| 1244 |
+
# See:
|
| 1245 |
+
# https://github.com/giampaolo/psutil/pull/1313
|
| 1246 |
+
continue
|
| 1247 |
+
|
| 1248 |
+
rbytes *= DISK_SECTOR_SIZE
|
| 1249 |
+
wbytes *= DISK_SECTOR_SIZE
|
| 1250 |
+
retdict[name] = (reads, writes, rbytes, wbytes, rtime, wtime,
|
| 1251 |
+
reads_merged, writes_merged, busy_time)
|
| 1252 |
+
# fmt: on
|
| 1253 |
+
|
| 1254 |
+
return retdict
|
| 1255 |
+
|
| 1256 |
+
|
| 1257 |
+
class RootFsDeviceFinder:
|
| 1258 |
+
"""disk_partitions() may return partitions with device == "/dev/root"
|
| 1259 |
+
or "rootfs". This container class uses different strategies to try to
|
| 1260 |
+
obtain the real device path. Resources:
|
| 1261 |
+
https://bootlin.com/blog/find-root-device/
|
| 1262 |
+
https://www.systutorials.com/how-to-find-the-disk-where-root-is-on-in-bash-on-linux/.
|
| 1263 |
+
"""
|
| 1264 |
+
|
| 1265 |
+
__slots__ = ['major', 'minor']
|
| 1266 |
+
|
| 1267 |
+
def __init__(self):
|
| 1268 |
+
dev = os.stat("/").st_dev
|
| 1269 |
+
self.major = os.major(dev)
|
| 1270 |
+
self.minor = os.minor(dev)
|
| 1271 |
+
|
| 1272 |
+
def ask_proc_partitions(self):
|
| 1273 |
+
with open_text("%s/partitions" % get_procfs_path()) as f:
|
| 1274 |
+
for line in f.readlines()[2:]:
|
| 1275 |
+
fields = line.split()
|
| 1276 |
+
if len(fields) < 4: # just for extra safety
|
| 1277 |
+
continue
|
| 1278 |
+
major = int(fields[0]) if fields[0].isdigit() else None
|
| 1279 |
+
minor = int(fields[1]) if fields[1].isdigit() else None
|
| 1280 |
+
name = fields[3]
|
| 1281 |
+
if major == self.major and minor == self.minor:
|
| 1282 |
+
if name: # just for extra safety
|
| 1283 |
+
return "/dev/%s" % name
|
| 1284 |
+
|
| 1285 |
+
def ask_sys_dev_block(self):
|
| 1286 |
+
path = "/sys/dev/block/%s:%s/uevent" % (self.major, self.minor)
|
| 1287 |
+
with open_text(path) as f:
|
| 1288 |
+
for line in f:
|
| 1289 |
+
if line.startswith("DEVNAME="):
|
| 1290 |
+
name = line.strip().rpartition("DEVNAME=")[2]
|
| 1291 |
+
if name: # just for extra safety
|
| 1292 |
+
return "/dev/%s" % name
|
| 1293 |
+
|
| 1294 |
+
def ask_sys_class_block(self):
|
| 1295 |
+
needle = "%s:%s" % (self.major, self.minor)
|
| 1296 |
+
files = glob.iglob("/sys/class/block/*/dev")
|
| 1297 |
+
for file in files:
|
| 1298 |
+
try:
|
| 1299 |
+
f = open_text(file)
|
| 1300 |
+
except FileNotFoundError: # race condition
|
| 1301 |
+
continue
|
| 1302 |
+
else:
|
| 1303 |
+
with f:
|
| 1304 |
+
data = f.read().strip()
|
| 1305 |
+
if data == needle:
|
| 1306 |
+
name = os.path.basename(os.path.dirname(file))
|
| 1307 |
+
return "/dev/%s" % name
|
| 1308 |
+
|
| 1309 |
+
def find(self):
|
| 1310 |
+
path = None
|
| 1311 |
+
if path is None:
|
| 1312 |
+
try:
|
| 1313 |
+
path = self.ask_proc_partitions()
|
| 1314 |
+
except (IOError, OSError) as err:
|
| 1315 |
+
debug(err)
|
| 1316 |
+
if path is None:
|
| 1317 |
+
try:
|
| 1318 |
+
path = self.ask_sys_dev_block()
|
| 1319 |
+
except (IOError, OSError) as err:
|
| 1320 |
+
debug(err)
|
| 1321 |
+
if path is None:
|
| 1322 |
+
try:
|
| 1323 |
+
path = self.ask_sys_class_block()
|
| 1324 |
+
except (IOError, OSError) as err:
|
| 1325 |
+
debug(err)
|
| 1326 |
+
# We use exists() because the "/dev/*" part of the path is hard
|
| 1327 |
+
# coded, so we want to be sure.
|
| 1328 |
+
if path is not None and os.path.exists(path):
|
| 1329 |
+
return path
|
| 1330 |
+
|
| 1331 |
+
|
| 1332 |
+
def disk_partitions(all=False):
|
| 1333 |
+
"""Return mounted disk partitions as a list of namedtuples."""
|
| 1334 |
+
fstypes = set()
|
| 1335 |
+
procfs_path = get_procfs_path()
|
| 1336 |
+
if not all:
|
| 1337 |
+
with open_text("%s/filesystems" % procfs_path) as f:
|
| 1338 |
+
for line in f:
|
| 1339 |
+
line = line.strip()
|
| 1340 |
+
if not line.startswith("nodev"):
|
| 1341 |
+
fstypes.add(line.strip())
|
| 1342 |
+
else:
|
| 1343 |
+
# ignore all lines starting with "nodev" except "nodev zfs"
|
| 1344 |
+
fstype = line.split("\t")[1]
|
| 1345 |
+
if fstype == "zfs":
|
| 1346 |
+
fstypes.add("zfs")
|
| 1347 |
+
|
| 1348 |
+
# See: https://github.com/giampaolo/psutil/issues/1307
|
| 1349 |
+
if procfs_path == "/proc" and os.path.isfile('/etc/mtab'):
|
| 1350 |
+
mounts_path = os.path.realpath("/etc/mtab")
|
| 1351 |
+
else:
|
| 1352 |
+
mounts_path = os.path.realpath("%s/self/mounts" % procfs_path)
|
| 1353 |
+
|
| 1354 |
+
retlist = []
|
| 1355 |
+
partitions = cext.disk_partitions(mounts_path)
|
| 1356 |
+
for partition in partitions:
|
| 1357 |
+
device, mountpoint, fstype, opts = partition
|
| 1358 |
+
if device == 'none':
|
| 1359 |
+
device = ''
|
| 1360 |
+
if device in {"/dev/root", "rootfs"}:
|
| 1361 |
+
device = RootFsDeviceFinder().find() or device
|
| 1362 |
+
if not all:
|
| 1363 |
+
if not device or fstype not in fstypes:
|
| 1364 |
+
continue
|
| 1365 |
+
ntuple = _common.sdiskpart(device, mountpoint, fstype, opts)
|
| 1366 |
+
retlist.append(ntuple)
|
| 1367 |
+
|
| 1368 |
+
return retlist
|
| 1369 |
+
|
| 1370 |
+
|
| 1371 |
+
# =====================================================================
|
| 1372 |
+
# --- sensors
|
| 1373 |
+
# =====================================================================
|
| 1374 |
+
|
| 1375 |
+
|
| 1376 |
+
def sensors_temperatures():
|
| 1377 |
+
"""Return hardware (CPU and others) temperatures as a dict
|
| 1378 |
+
including hardware name, label, current, max and critical
|
| 1379 |
+
temperatures.
|
| 1380 |
+
|
| 1381 |
+
Implementation notes:
|
| 1382 |
+
- /sys/class/hwmon looks like the most recent interface to
|
| 1383 |
+
retrieve this info, and this implementation relies on it
|
| 1384 |
+
only (old distros will probably use something else)
|
| 1385 |
+
- lm-sensors on Ubuntu 16.04 relies on /sys/class/hwmon
|
| 1386 |
+
- /sys/class/thermal/thermal_zone* is another one but it's more
|
| 1387 |
+
difficult to parse
|
| 1388 |
+
"""
|
| 1389 |
+
ret = collections.defaultdict(list)
|
| 1390 |
+
basenames = glob.glob('/sys/class/hwmon/hwmon*/temp*_*')
|
| 1391 |
+
# CentOS has an intermediate /device directory:
|
| 1392 |
+
# https://github.com/giampaolo/psutil/issues/971
|
| 1393 |
+
# https://github.com/nicolargo/glances/issues/1060
|
| 1394 |
+
basenames.extend(glob.glob('/sys/class/hwmon/hwmon*/device/temp*_*'))
|
| 1395 |
+
basenames = sorted(set([x.split('_')[0] for x in basenames]))
|
| 1396 |
+
|
| 1397 |
+
# Only add the coretemp hwmon entries if they're not already in
|
| 1398 |
+
# /sys/class/hwmon/
|
| 1399 |
+
# https://github.com/giampaolo/psutil/issues/1708
|
| 1400 |
+
# https://github.com/giampaolo/psutil/pull/1648
|
| 1401 |
+
basenames2 = glob.glob(
|
| 1402 |
+
'/sys/devices/platform/coretemp.*/hwmon/hwmon*/temp*_*'
|
| 1403 |
+
)
|
| 1404 |
+
repl = re.compile('/sys/devices/platform/coretemp.*/hwmon/')
|
| 1405 |
+
for name in basenames2:
|
| 1406 |
+
altname = repl.sub('/sys/class/hwmon/', name)
|
| 1407 |
+
if altname not in basenames:
|
| 1408 |
+
basenames.append(name)
|
| 1409 |
+
|
| 1410 |
+
for base in basenames:
|
| 1411 |
+
try:
|
| 1412 |
+
path = base + '_input'
|
| 1413 |
+
current = float(bcat(path)) / 1000.0
|
| 1414 |
+
path = os.path.join(os.path.dirname(base), 'name')
|
| 1415 |
+
unit_name = cat(path).strip()
|
| 1416 |
+
except (IOError, OSError, ValueError):
|
| 1417 |
+
# A lot of things can go wrong here, so let's just skip the
|
| 1418 |
+
# whole entry. Sure thing is Linux's /sys/class/hwmon really
|
| 1419 |
+
# is a stinky broken mess.
|
| 1420 |
+
# https://github.com/giampaolo/psutil/issues/1009
|
| 1421 |
+
# https://github.com/giampaolo/psutil/issues/1101
|
| 1422 |
+
# https://github.com/giampaolo/psutil/issues/1129
|
| 1423 |
+
# https://github.com/giampaolo/psutil/issues/1245
|
| 1424 |
+
# https://github.com/giampaolo/psutil/issues/1323
|
| 1425 |
+
continue
|
| 1426 |
+
|
| 1427 |
+
high = bcat(base + '_max', fallback=None)
|
| 1428 |
+
critical = bcat(base + '_crit', fallback=None)
|
| 1429 |
+
label = cat(base + '_label', fallback='').strip()
|
| 1430 |
+
|
| 1431 |
+
if high is not None:
|
| 1432 |
+
try:
|
| 1433 |
+
high = float(high) / 1000.0
|
| 1434 |
+
except ValueError:
|
| 1435 |
+
high = None
|
| 1436 |
+
if critical is not None:
|
| 1437 |
+
try:
|
| 1438 |
+
critical = float(critical) / 1000.0
|
| 1439 |
+
except ValueError:
|
| 1440 |
+
critical = None
|
| 1441 |
+
|
| 1442 |
+
ret[unit_name].append((label, current, high, critical))
|
| 1443 |
+
|
| 1444 |
+
# Indication that no sensors were detected in /sys/class/hwmon/
|
| 1445 |
+
if not basenames:
|
| 1446 |
+
basenames = glob.glob('/sys/class/thermal/thermal_zone*')
|
| 1447 |
+
basenames = sorted(set(basenames))
|
| 1448 |
+
|
| 1449 |
+
for base in basenames:
|
| 1450 |
+
try:
|
| 1451 |
+
path = os.path.join(base, 'temp')
|
| 1452 |
+
current = float(bcat(path)) / 1000.0
|
| 1453 |
+
path = os.path.join(base, 'type')
|
| 1454 |
+
unit_name = cat(path).strip()
|
| 1455 |
+
except (IOError, OSError, ValueError) as err:
|
| 1456 |
+
debug(err)
|
| 1457 |
+
continue
|
| 1458 |
+
|
| 1459 |
+
trip_paths = glob.glob(base + '/trip_point*')
|
| 1460 |
+
trip_points = set([
|
| 1461 |
+
'_'.join(os.path.basename(p).split('_')[0:3])
|
| 1462 |
+
for p in trip_paths
|
| 1463 |
+
])
|
| 1464 |
+
critical = None
|
| 1465 |
+
high = None
|
| 1466 |
+
for trip_point in trip_points:
|
| 1467 |
+
path = os.path.join(base, trip_point + "_type")
|
| 1468 |
+
trip_type = cat(path, fallback='').strip()
|
| 1469 |
+
if trip_type == 'critical':
|
| 1470 |
+
critical = bcat(
|
| 1471 |
+
os.path.join(base, trip_point + "_temp"), fallback=None
|
| 1472 |
+
)
|
| 1473 |
+
elif trip_type == 'high':
|
| 1474 |
+
high = bcat(
|
| 1475 |
+
os.path.join(base, trip_point + "_temp"), fallback=None
|
| 1476 |
+
)
|
| 1477 |
+
|
| 1478 |
+
if high is not None:
|
| 1479 |
+
try:
|
| 1480 |
+
high = float(high) / 1000.0
|
| 1481 |
+
except ValueError:
|
| 1482 |
+
high = None
|
| 1483 |
+
if critical is not None:
|
| 1484 |
+
try:
|
| 1485 |
+
critical = float(critical) / 1000.0
|
| 1486 |
+
except ValueError:
|
| 1487 |
+
critical = None
|
| 1488 |
+
|
| 1489 |
+
ret[unit_name].append(('', current, high, critical))
|
| 1490 |
+
|
| 1491 |
+
return dict(ret)
|
| 1492 |
+
|
| 1493 |
+
|
| 1494 |
+
def sensors_fans():
|
| 1495 |
+
"""Return hardware fans info (for CPU and other peripherals) as a
|
| 1496 |
+
dict including hardware label and current speed.
|
| 1497 |
+
|
| 1498 |
+
Implementation notes:
|
| 1499 |
+
- /sys/class/hwmon looks like the most recent interface to
|
| 1500 |
+
retrieve this info, and this implementation relies on it
|
| 1501 |
+
only (old distros will probably use something else)
|
| 1502 |
+
- lm-sensors on Ubuntu 16.04 relies on /sys/class/hwmon
|
| 1503 |
+
"""
|
| 1504 |
+
ret = collections.defaultdict(list)
|
| 1505 |
+
basenames = glob.glob('/sys/class/hwmon/hwmon*/fan*_*')
|
| 1506 |
+
if not basenames:
|
| 1507 |
+
# CentOS has an intermediate /device directory:
|
| 1508 |
+
# https://github.com/giampaolo/psutil/issues/971
|
| 1509 |
+
basenames = glob.glob('/sys/class/hwmon/hwmon*/device/fan*_*')
|
| 1510 |
+
|
| 1511 |
+
basenames = sorted(set([x.split('_')[0] for x in basenames]))
|
| 1512 |
+
for base in basenames:
|
| 1513 |
+
try:
|
| 1514 |
+
current = int(bcat(base + '_input'))
|
| 1515 |
+
except (IOError, OSError) as err:
|
| 1516 |
+
debug(err)
|
| 1517 |
+
continue
|
| 1518 |
+
unit_name = cat(os.path.join(os.path.dirname(base), 'name')).strip()
|
| 1519 |
+
label = cat(base + '_label', fallback='').strip()
|
| 1520 |
+
ret[unit_name].append(_common.sfan(label, current))
|
| 1521 |
+
|
| 1522 |
+
return dict(ret)
|
| 1523 |
+
|
| 1524 |
+
|
| 1525 |
+
def sensors_battery():
|
| 1526 |
+
"""Return battery information.
|
| 1527 |
+
Implementation note: it appears /sys/class/power_supply/BAT0/
|
| 1528 |
+
directory structure may vary and provide files with the same
|
| 1529 |
+
meaning but under different names, see:
|
| 1530 |
+
https://github.com/giampaolo/psutil/issues/966.
|
| 1531 |
+
"""
|
| 1532 |
+
null = object()
|
| 1533 |
+
|
| 1534 |
+
def multi_bcat(*paths):
|
| 1535 |
+
"""Attempt to read the content of multiple files which may
|
| 1536 |
+
not exist. If none of them exist return None.
|
| 1537 |
+
"""
|
| 1538 |
+
for path in paths:
|
| 1539 |
+
ret = bcat(path, fallback=null)
|
| 1540 |
+
if ret != null:
|
| 1541 |
+
try:
|
| 1542 |
+
return int(ret)
|
| 1543 |
+
except ValueError:
|
| 1544 |
+
return ret.strip()
|
| 1545 |
+
return None
|
| 1546 |
+
|
| 1547 |
+
bats = [
|
| 1548 |
+
x
|
| 1549 |
+
for x in os.listdir(POWER_SUPPLY_PATH)
|
| 1550 |
+
if x.startswith('BAT') or 'battery' in x.lower()
|
| 1551 |
+
]
|
| 1552 |
+
if not bats:
|
| 1553 |
+
return None
|
| 1554 |
+
# Get the first available battery. Usually this is "BAT0", except
|
| 1555 |
+
# some rare exceptions:
|
| 1556 |
+
# https://github.com/giampaolo/psutil/issues/1238
|
| 1557 |
+
root = os.path.join(POWER_SUPPLY_PATH, sorted(bats)[0])
|
| 1558 |
+
|
| 1559 |
+
# Base metrics.
|
| 1560 |
+
energy_now = multi_bcat(root + "/energy_now", root + "/charge_now")
|
| 1561 |
+
power_now = multi_bcat(root + "/power_now", root + "/current_now")
|
| 1562 |
+
energy_full = multi_bcat(root + "/energy_full", root + "/charge_full")
|
| 1563 |
+
time_to_empty = multi_bcat(root + "/time_to_empty_now")
|
| 1564 |
+
|
| 1565 |
+
# Percent. If we have energy_full the percentage will be more
|
| 1566 |
+
# accurate compared to reading /capacity file (float vs. int).
|
| 1567 |
+
if energy_full is not None and energy_now is not None:
|
| 1568 |
+
try:
|
| 1569 |
+
percent = 100.0 * energy_now / energy_full
|
| 1570 |
+
except ZeroDivisionError:
|
| 1571 |
+
percent = 0.0
|
| 1572 |
+
else:
|
| 1573 |
+
percent = int(cat(root + "/capacity", fallback=-1))
|
| 1574 |
+
if percent == -1:
|
| 1575 |
+
return None
|
| 1576 |
+
|
| 1577 |
+
# Is AC power cable plugged in?
|
| 1578 |
+
# Note: AC0 is not always available and sometimes (e.g. CentOS7)
|
| 1579 |
+
# it's called "AC".
|
| 1580 |
+
power_plugged = None
|
| 1581 |
+
online = multi_bcat(
|
| 1582 |
+
os.path.join(POWER_SUPPLY_PATH, "AC0/online"),
|
| 1583 |
+
os.path.join(POWER_SUPPLY_PATH, "AC/online"),
|
| 1584 |
+
)
|
| 1585 |
+
if online is not None:
|
| 1586 |
+
power_plugged = online == 1
|
| 1587 |
+
else:
|
| 1588 |
+
status = cat(root + "/status", fallback="").strip().lower()
|
| 1589 |
+
if status == "discharging":
|
| 1590 |
+
power_plugged = False
|
| 1591 |
+
elif status in {"charging", "full"}:
|
| 1592 |
+
power_plugged = True
|
| 1593 |
+
|
| 1594 |
+
# Seconds left.
|
| 1595 |
+
# Note to self: we may also calculate the charging ETA as per:
|
| 1596 |
+
# https://github.com/thialfihar/dotfiles/blob/
|
| 1597 |
+
# 013937745fd9050c30146290e8f963d65c0179e6/bin/battery.py#L55
|
| 1598 |
+
if power_plugged:
|
| 1599 |
+
secsleft = _common.POWER_TIME_UNLIMITED
|
| 1600 |
+
elif energy_now is not None and power_now is not None:
|
| 1601 |
+
try:
|
| 1602 |
+
secsleft = int(energy_now / power_now * 3600)
|
| 1603 |
+
except ZeroDivisionError:
|
| 1604 |
+
secsleft = _common.POWER_TIME_UNKNOWN
|
| 1605 |
+
elif time_to_empty is not None:
|
| 1606 |
+
secsleft = int(time_to_empty * 60)
|
| 1607 |
+
if secsleft < 0:
|
| 1608 |
+
secsleft = _common.POWER_TIME_UNKNOWN
|
| 1609 |
+
else:
|
| 1610 |
+
secsleft = _common.POWER_TIME_UNKNOWN
|
| 1611 |
+
|
| 1612 |
+
return _common.sbattery(percent, secsleft, power_plugged)
|
| 1613 |
+
|
| 1614 |
+
|
| 1615 |
+
# =====================================================================
|
| 1616 |
+
# --- other system functions
|
| 1617 |
+
# =====================================================================
|
| 1618 |
+
|
| 1619 |
+
|
| 1620 |
+
def users():
|
| 1621 |
+
"""Return currently connected users as a list of namedtuples."""
|
| 1622 |
+
retlist = []
|
| 1623 |
+
rawlist = cext.users()
|
| 1624 |
+
for item in rawlist:
|
| 1625 |
+
user, tty, hostname, tstamp, pid = item
|
| 1626 |
+
nt = _common.suser(user, tty or None, hostname, tstamp, pid)
|
| 1627 |
+
retlist.append(nt)
|
| 1628 |
+
return retlist
|
| 1629 |
+
|
| 1630 |
+
|
| 1631 |
+
def boot_time():
|
| 1632 |
+
"""Return the system boot time expressed in seconds since the epoch."""
|
| 1633 |
+
global BOOT_TIME
|
| 1634 |
+
path = '%s/stat' % get_procfs_path()
|
| 1635 |
+
with open_binary(path) as f:
|
| 1636 |
+
for line in f:
|
| 1637 |
+
if line.startswith(b'btime'):
|
| 1638 |
+
ret = float(line.strip().split()[1])
|
| 1639 |
+
BOOT_TIME = ret
|
| 1640 |
+
return ret
|
| 1641 |
+
raise RuntimeError("line 'btime' not found in %s" % path)
|
| 1642 |
+
|
| 1643 |
+
|
| 1644 |
+
# =====================================================================
|
| 1645 |
+
# --- processes
|
| 1646 |
+
# =====================================================================
|
| 1647 |
+
|
| 1648 |
+
|
| 1649 |
+
def pids():
|
| 1650 |
+
"""Returns a list of PIDs currently running on the system."""
|
| 1651 |
+
return [int(x) for x in os.listdir(b(get_procfs_path())) if x.isdigit()]
|
| 1652 |
+
|
| 1653 |
+
|
| 1654 |
+
def pid_exists(pid):
|
| 1655 |
+
"""Check for the existence of a unix PID. Linux TIDs are not
|
| 1656 |
+
supported (always return False).
|
| 1657 |
+
"""
|
| 1658 |
+
if not _psposix.pid_exists(pid):
|
| 1659 |
+
return False
|
| 1660 |
+
else:
|
| 1661 |
+
# Linux's apparently does not distinguish between PIDs and TIDs
|
| 1662 |
+
# (thread IDs).
|
| 1663 |
+
# listdir("/proc") won't show any TID (only PIDs) but
|
| 1664 |
+
# os.stat("/proc/{tid}") will succeed if {tid} exists.
|
| 1665 |
+
# os.kill() can also be passed a TID. This is quite confusing.
|
| 1666 |
+
# In here we want to enforce this distinction and support PIDs
|
| 1667 |
+
# only, see:
|
| 1668 |
+
# https://github.com/giampaolo/psutil/issues/687
|
| 1669 |
+
try:
|
| 1670 |
+
# Note: already checked that this is faster than using a
|
| 1671 |
+
# regular expr. Also (a lot) faster than doing
|
| 1672 |
+
# 'return pid in pids()'
|
| 1673 |
+
path = "%s/%s/status" % (get_procfs_path(), pid)
|
| 1674 |
+
with open_binary(path) as f:
|
| 1675 |
+
for line in f:
|
| 1676 |
+
if line.startswith(b"Tgid:"):
|
| 1677 |
+
tgid = int(line.split()[1])
|
| 1678 |
+
# If tgid and pid are the same then we're
|
| 1679 |
+
# dealing with a process PID.
|
| 1680 |
+
return tgid == pid
|
| 1681 |
+
raise ValueError("'Tgid' line not found in %s" % path)
|
| 1682 |
+
except (EnvironmentError, ValueError):
|
| 1683 |
+
return pid in pids()
|
| 1684 |
+
|
| 1685 |
+
|
| 1686 |
+
def ppid_map():
|
| 1687 |
+
"""Obtain a {pid: ppid, ...} dict for all running processes in
|
| 1688 |
+
one shot. Used to speed up Process.children().
|
| 1689 |
+
"""
|
| 1690 |
+
ret = {}
|
| 1691 |
+
procfs_path = get_procfs_path()
|
| 1692 |
+
for pid in pids():
|
| 1693 |
+
try:
|
| 1694 |
+
with open_binary("%s/%s/stat" % (procfs_path, pid)) as f:
|
| 1695 |
+
data = f.read()
|
| 1696 |
+
except (FileNotFoundError, ProcessLookupError):
|
| 1697 |
+
# Note: we should be able to access /stat for all processes
|
| 1698 |
+
# aka it's unlikely we'll bump into EPERM, which is good.
|
| 1699 |
+
pass
|
| 1700 |
+
else:
|
| 1701 |
+
rpar = data.rfind(b')')
|
| 1702 |
+
dset = data[rpar + 2 :].split()
|
| 1703 |
+
ppid = int(dset[1])
|
| 1704 |
+
ret[pid] = ppid
|
| 1705 |
+
return ret
|
| 1706 |
+
|
| 1707 |
+
|
| 1708 |
+
def wrap_exceptions(fun):
|
| 1709 |
+
"""Decorator which translates bare OSError and IOError exceptions
|
| 1710 |
+
into NoSuchProcess and AccessDenied.
|
| 1711 |
+
"""
|
| 1712 |
+
|
| 1713 |
+
@functools.wraps(fun)
|
| 1714 |
+
def wrapper(self, *args, **kwargs):
|
| 1715 |
+
try:
|
| 1716 |
+
return fun(self, *args, **kwargs)
|
| 1717 |
+
except PermissionError:
|
| 1718 |
+
raise AccessDenied(self.pid, self._name)
|
| 1719 |
+
except ProcessLookupError:
|
| 1720 |
+
self._raise_if_zombie()
|
| 1721 |
+
raise NoSuchProcess(self.pid, self._name)
|
| 1722 |
+
except FileNotFoundError:
|
| 1723 |
+
self._raise_if_zombie()
|
| 1724 |
+
# /proc/PID directory may still exist, but the files within
|
| 1725 |
+
# it may not, indicating the process is gone, see:
|
| 1726 |
+
# https://github.com/giampaolo/psutil/issues/2418
|
| 1727 |
+
if not os.path.exists(
|
| 1728 |
+
"%s/%s/stat" % (self._procfs_path, self.pid)
|
| 1729 |
+
):
|
| 1730 |
+
raise NoSuchProcess(self.pid, self._name)
|
| 1731 |
+
raise
|
| 1732 |
+
|
| 1733 |
+
return wrapper
|
| 1734 |
+
|
| 1735 |
+
|
| 1736 |
+
class Process:
|
| 1737 |
+
"""Linux process implementation."""
|
| 1738 |
+
|
| 1739 |
+
__slots__ = ["_cache", "_name", "_ppid", "_procfs_path", "pid"]
|
| 1740 |
+
|
| 1741 |
+
def __init__(self, pid):
|
| 1742 |
+
self.pid = pid
|
| 1743 |
+
self._name = None
|
| 1744 |
+
self._ppid = None
|
| 1745 |
+
self._procfs_path = get_procfs_path()
|
| 1746 |
+
|
| 1747 |
+
def _is_zombie(self):
|
| 1748 |
+
# Note: most of the times Linux is able to return info about the
|
| 1749 |
+
# process even if it's a zombie, and /proc/{pid} will exist.
|
| 1750 |
+
# There are some exceptions though, like exe(), cmdline() and
|
| 1751 |
+
# memory_maps(). In these cases /proc/{pid}/{file} exists but
|
| 1752 |
+
# it's empty. Instead of returning a "null" value we'll raise an
|
| 1753 |
+
# exception.
|
| 1754 |
+
try:
|
| 1755 |
+
data = bcat("%s/%s/stat" % (self._procfs_path, self.pid))
|
| 1756 |
+
except (IOError, OSError):
|
| 1757 |
+
return False
|
| 1758 |
+
else:
|
| 1759 |
+
rpar = data.rfind(b')')
|
| 1760 |
+
status = data[rpar + 2 : rpar + 3]
|
| 1761 |
+
return status == b"Z"
|
| 1762 |
+
|
| 1763 |
+
def _raise_if_zombie(self):
|
| 1764 |
+
if self._is_zombie():
|
| 1765 |
+
raise ZombieProcess(self.pid, self._name, self._ppid)
|
| 1766 |
+
|
| 1767 |
+
def _raise_if_not_alive(self):
|
| 1768 |
+
"""Raise NSP if the process disappeared on us."""
|
| 1769 |
+
# For those C function who do not raise NSP, possibly returning
|
| 1770 |
+
# incorrect or incomplete result.
|
| 1771 |
+
os.stat('%s/%s' % (self._procfs_path, self.pid))
|
| 1772 |
+
|
| 1773 |
+
@wrap_exceptions
|
| 1774 |
+
@memoize_when_activated
|
| 1775 |
+
def _parse_stat_file(self):
|
| 1776 |
+
"""Parse /proc/{pid}/stat file and return a dict with various
|
| 1777 |
+
process info.
|
| 1778 |
+
Using "man proc" as a reference: where "man proc" refers to
|
| 1779 |
+
position N always subtract 3 (e.g ppid position 4 in
|
| 1780 |
+
'man proc' == position 1 in here).
|
| 1781 |
+
The return value is cached in case oneshot() ctx manager is
|
| 1782 |
+
in use.
|
| 1783 |
+
"""
|
| 1784 |
+
data = bcat("%s/%s/stat" % (self._procfs_path, self.pid))
|
| 1785 |
+
# Process name is between parentheses. It can contain spaces and
|
| 1786 |
+
# other parentheses. This is taken into account by looking for
|
| 1787 |
+
# the first occurrence of "(" and the last occurrence of ")".
|
| 1788 |
+
rpar = data.rfind(b')')
|
| 1789 |
+
name = data[data.find(b'(') + 1 : rpar]
|
| 1790 |
+
fields = data[rpar + 2 :].split()
|
| 1791 |
+
|
| 1792 |
+
ret = {}
|
| 1793 |
+
ret['name'] = name
|
| 1794 |
+
ret['status'] = fields[0]
|
| 1795 |
+
ret['ppid'] = fields[1]
|
| 1796 |
+
ret['ttynr'] = fields[4]
|
| 1797 |
+
ret['utime'] = fields[11]
|
| 1798 |
+
ret['stime'] = fields[12]
|
| 1799 |
+
ret['children_utime'] = fields[13]
|
| 1800 |
+
ret['children_stime'] = fields[14]
|
| 1801 |
+
ret['create_time'] = fields[19]
|
| 1802 |
+
ret['cpu_num'] = fields[36]
|
| 1803 |
+
try:
|
| 1804 |
+
ret['blkio_ticks'] = fields[39] # aka 'delayacct_blkio_ticks'
|
| 1805 |
+
except IndexError:
|
| 1806 |
+
# https://github.com/giampaolo/psutil/issues/2455
|
| 1807 |
+
debug("can't get blkio_ticks, set iowait to 0")
|
| 1808 |
+
ret['blkio_ticks'] = 0
|
| 1809 |
+
|
| 1810 |
+
return ret
|
| 1811 |
+
|
| 1812 |
+
@wrap_exceptions
|
| 1813 |
+
@memoize_when_activated
|
| 1814 |
+
def _read_status_file(self):
|
| 1815 |
+
"""Read /proc/{pid}/stat file and return its content.
|
| 1816 |
+
The return value is cached in case oneshot() ctx manager is
|
| 1817 |
+
in use.
|
| 1818 |
+
"""
|
| 1819 |
+
with open_binary("%s/%s/status" % (self._procfs_path, self.pid)) as f:
|
| 1820 |
+
return f.read()
|
| 1821 |
+
|
| 1822 |
+
@wrap_exceptions
|
| 1823 |
+
@memoize_when_activated
|
| 1824 |
+
def _read_smaps_file(self):
|
| 1825 |
+
with open_binary("%s/%s/smaps" % (self._procfs_path, self.pid)) as f:
|
| 1826 |
+
return f.read().strip()
|
| 1827 |
+
|
| 1828 |
+
def oneshot_enter(self):
|
| 1829 |
+
self._parse_stat_file.cache_activate(self)
|
| 1830 |
+
self._read_status_file.cache_activate(self)
|
| 1831 |
+
self._read_smaps_file.cache_activate(self)
|
| 1832 |
+
|
| 1833 |
+
def oneshot_exit(self):
|
| 1834 |
+
self._parse_stat_file.cache_deactivate(self)
|
| 1835 |
+
self._read_status_file.cache_deactivate(self)
|
| 1836 |
+
self._read_smaps_file.cache_deactivate(self)
|
| 1837 |
+
|
| 1838 |
+
@wrap_exceptions
|
| 1839 |
+
def name(self):
|
| 1840 |
+
name = self._parse_stat_file()['name']
|
| 1841 |
+
if PY3:
|
| 1842 |
+
name = decode(name)
|
| 1843 |
+
# XXX - gets changed later and probably needs refactoring
|
| 1844 |
+
return name
|
| 1845 |
+
|
| 1846 |
+
@wrap_exceptions
|
| 1847 |
+
def exe(self):
|
| 1848 |
+
try:
|
| 1849 |
+
return readlink("%s/%s/exe" % (self._procfs_path, self.pid))
|
| 1850 |
+
except (FileNotFoundError, ProcessLookupError):
|
| 1851 |
+
self._raise_if_zombie()
|
| 1852 |
+
# no such file error; might be raised also if the
|
| 1853 |
+
# path actually exists for system processes with
|
| 1854 |
+
# low pids (about 0-20)
|
| 1855 |
+
if os.path.lexists("%s/%s" % (self._procfs_path, self.pid)):
|
| 1856 |
+
return ""
|
| 1857 |
+
raise
|
| 1858 |
+
|
| 1859 |
+
@wrap_exceptions
|
| 1860 |
+
def cmdline(self):
|
| 1861 |
+
with open_text("%s/%s/cmdline" % (self._procfs_path, self.pid)) as f:
|
| 1862 |
+
data = f.read()
|
| 1863 |
+
if not data:
|
| 1864 |
+
# may happen in case of zombie process
|
| 1865 |
+
self._raise_if_zombie()
|
| 1866 |
+
return []
|
| 1867 |
+
# 'man proc' states that args are separated by null bytes '\0'
|
| 1868 |
+
# and last char is supposed to be a null byte. Nevertheless
|
| 1869 |
+
# some processes may change their cmdline after being started
|
| 1870 |
+
# (via setproctitle() or similar), they are usually not
|
| 1871 |
+
# compliant with this rule and use spaces instead. Google
|
| 1872 |
+
# Chrome process is an example. See:
|
| 1873 |
+
# https://github.com/giampaolo/psutil/issues/1179
|
| 1874 |
+
sep = '\x00' if data.endswith('\x00') else ' '
|
| 1875 |
+
if data.endswith(sep):
|
| 1876 |
+
data = data[:-1]
|
| 1877 |
+
cmdline = data.split(sep)
|
| 1878 |
+
# Sometimes last char is a null byte '\0' but the args are
|
| 1879 |
+
# separated by spaces, see: https://github.com/giampaolo/psutil/
|
| 1880 |
+
# issues/1179#issuecomment-552984549
|
| 1881 |
+
if sep == '\x00' and len(cmdline) == 1 and ' ' in data:
|
| 1882 |
+
cmdline = data.split(' ')
|
| 1883 |
+
return cmdline
|
| 1884 |
+
|
| 1885 |
+
@wrap_exceptions
|
| 1886 |
+
def environ(self):
|
| 1887 |
+
with open_text("%s/%s/environ" % (self._procfs_path, self.pid)) as f:
|
| 1888 |
+
data = f.read()
|
| 1889 |
+
return parse_environ_block(data)
|
| 1890 |
+
|
| 1891 |
+
@wrap_exceptions
|
| 1892 |
+
def terminal(self):
|
| 1893 |
+
tty_nr = int(self._parse_stat_file()['ttynr'])
|
| 1894 |
+
tmap = _psposix.get_terminal_map()
|
| 1895 |
+
try:
|
| 1896 |
+
return tmap[tty_nr]
|
| 1897 |
+
except KeyError:
|
| 1898 |
+
return None
|
| 1899 |
+
|
| 1900 |
+
# May not be available on old kernels.
|
| 1901 |
+
if os.path.exists('/proc/%s/io' % os.getpid()):
|
| 1902 |
+
|
| 1903 |
+
@wrap_exceptions
|
| 1904 |
+
def io_counters(self):
|
| 1905 |
+
fname = "%s/%s/io" % (self._procfs_path, self.pid)
|
| 1906 |
+
fields = {}
|
| 1907 |
+
with open_binary(fname) as f:
|
| 1908 |
+
for line in f:
|
| 1909 |
+
# https://github.com/giampaolo/psutil/issues/1004
|
| 1910 |
+
line = line.strip()
|
| 1911 |
+
if line:
|
| 1912 |
+
try:
|
| 1913 |
+
name, value = line.split(b': ')
|
| 1914 |
+
except ValueError:
|
| 1915 |
+
# https://github.com/giampaolo/psutil/issues/1004
|
| 1916 |
+
continue
|
| 1917 |
+
else:
|
| 1918 |
+
fields[name] = int(value)
|
| 1919 |
+
if not fields:
|
| 1920 |
+
raise RuntimeError("%s file was empty" % fname)
|
| 1921 |
+
try:
|
| 1922 |
+
return pio(
|
| 1923 |
+
fields[b'syscr'], # read syscalls
|
| 1924 |
+
fields[b'syscw'], # write syscalls
|
| 1925 |
+
fields[b'read_bytes'], # read bytes
|
| 1926 |
+
fields[b'write_bytes'], # write bytes
|
| 1927 |
+
fields[b'rchar'], # read chars
|
| 1928 |
+
fields[b'wchar'], # write chars
|
| 1929 |
+
)
|
| 1930 |
+
except KeyError as err:
|
| 1931 |
+
raise ValueError(
|
| 1932 |
+
"%r field was not found in %s; found fields are %r"
|
| 1933 |
+
% (err.args[0], fname, fields)
|
| 1934 |
+
)
|
| 1935 |
+
|
| 1936 |
+
@wrap_exceptions
|
| 1937 |
+
def cpu_times(self):
|
| 1938 |
+
values = self._parse_stat_file()
|
| 1939 |
+
utime = float(values['utime']) / CLOCK_TICKS
|
| 1940 |
+
stime = float(values['stime']) / CLOCK_TICKS
|
| 1941 |
+
children_utime = float(values['children_utime']) / CLOCK_TICKS
|
| 1942 |
+
children_stime = float(values['children_stime']) / CLOCK_TICKS
|
| 1943 |
+
iowait = float(values['blkio_ticks']) / CLOCK_TICKS
|
| 1944 |
+
return pcputimes(utime, stime, children_utime, children_stime, iowait)
|
| 1945 |
+
|
| 1946 |
+
@wrap_exceptions
|
| 1947 |
+
def cpu_num(self):
|
| 1948 |
+
"""What CPU the process is on."""
|
| 1949 |
+
return int(self._parse_stat_file()['cpu_num'])
|
| 1950 |
+
|
| 1951 |
+
@wrap_exceptions
|
| 1952 |
+
def wait(self, timeout=None):
|
| 1953 |
+
return _psposix.wait_pid(self.pid, timeout, self._name)
|
| 1954 |
+
|
| 1955 |
+
@wrap_exceptions
|
| 1956 |
+
def create_time(self):
|
| 1957 |
+
ctime = float(self._parse_stat_file()['create_time'])
|
| 1958 |
+
# According to documentation, starttime is in field 21 and the
|
| 1959 |
+
# unit is jiffies (clock ticks).
|
| 1960 |
+
# We first divide it for clock ticks and then add uptime returning
|
| 1961 |
+
# seconds since the epoch.
|
| 1962 |
+
# Also use cached value if available.
|
| 1963 |
+
bt = BOOT_TIME or boot_time()
|
| 1964 |
+
return (ctime / CLOCK_TICKS) + bt
|
| 1965 |
+
|
| 1966 |
+
@wrap_exceptions
|
| 1967 |
+
def memory_info(self):
|
| 1968 |
+
# ============================================================
|
| 1969 |
+
# | FIELD | DESCRIPTION | AKA | TOP |
|
| 1970 |
+
# ============================================================
|
| 1971 |
+
# | rss | resident set size | | RES |
|
| 1972 |
+
# | vms | total program size | size | VIRT |
|
| 1973 |
+
# | shared | shared pages (from shared mappings) | | SHR |
|
| 1974 |
+
# | text | text ('code') | trs | CODE |
|
| 1975 |
+
# | lib | library (unused in Linux 2.6) | lrs | |
|
| 1976 |
+
# | data | data + stack | drs | DATA |
|
| 1977 |
+
# | dirty | dirty pages (unused in Linux 2.6) | dt | |
|
| 1978 |
+
# ============================================================
|
| 1979 |
+
with open_binary("%s/%s/statm" % (self._procfs_path, self.pid)) as f:
|
| 1980 |
+
vms, rss, shared, text, lib, data, dirty = (
|
| 1981 |
+
int(x) * PAGESIZE for x in f.readline().split()[:7]
|
| 1982 |
+
)
|
| 1983 |
+
return pmem(rss, vms, shared, text, lib, data, dirty)
|
| 1984 |
+
|
| 1985 |
+
if HAS_PROC_SMAPS_ROLLUP or HAS_PROC_SMAPS:
|
| 1986 |
+
|
| 1987 |
+
def _parse_smaps_rollup(self):
|
| 1988 |
+
# /proc/pid/smaps_rollup was added to Linux in 2017. Faster
|
| 1989 |
+
# than /proc/pid/smaps. It reports higher PSS than */smaps
|
| 1990 |
+
# (from 1k up to 200k higher; tested against all processes).
|
| 1991 |
+
# IMPORTANT: /proc/pid/smaps_rollup is weird, because it
|
| 1992 |
+
# raises ESRCH / ENOENT for many PIDs, even if they're alive
|
| 1993 |
+
# (also as root). In that case we'll use /proc/pid/smaps as
|
| 1994 |
+
# fallback, which is slower but has a +50% success rate
|
| 1995 |
+
# compared to /proc/pid/smaps_rollup.
|
| 1996 |
+
uss = pss = swap = 0
|
| 1997 |
+
with open_binary(
|
| 1998 |
+
"{}/{}/smaps_rollup".format(self._procfs_path, self.pid)
|
| 1999 |
+
) as f:
|
| 2000 |
+
for line in f:
|
| 2001 |
+
if line.startswith(b"Private_"):
|
| 2002 |
+
# Private_Clean, Private_Dirty, Private_Hugetlb
|
| 2003 |
+
uss += int(line.split()[1]) * 1024
|
| 2004 |
+
elif line.startswith(b"Pss:"):
|
| 2005 |
+
pss = int(line.split()[1]) * 1024
|
| 2006 |
+
elif line.startswith(b"Swap:"):
|
| 2007 |
+
swap = int(line.split()[1]) * 1024
|
| 2008 |
+
return (uss, pss, swap)
|
| 2009 |
+
|
| 2010 |
+
@wrap_exceptions
|
| 2011 |
+
def _parse_smaps(
|
| 2012 |
+
self,
|
| 2013 |
+
# Gets Private_Clean, Private_Dirty, Private_Hugetlb.
|
| 2014 |
+
_private_re=re.compile(br"\nPrivate.*:\s+(\d+)"),
|
| 2015 |
+
_pss_re=re.compile(br"\nPss\:\s+(\d+)"),
|
| 2016 |
+
_swap_re=re.compile(br"\nSwap\:\s+(\d+)"),
|
| 2017 |
+
):
|
| 2018 |
+
# /proc/pid/smaps does not exist on kernels < 2.6.14 or if
|
| 2019 |
+
# CONFIG_MMU kernel configuration option is not enabled.
|
| 2020 |
+
|
| 2021 |
+
# Note: using 3 regexes is faster than reading the file
|
| 2022 |
+
# line by line.
|
| 2023 |
+
# XXX: on Python 3 the 2 regexes are 30% slower than on
|
| 2024 |
+
# Python 2 though. Figure out why.
|
| 2025 |
+
#
|
| 2026 |
+
# You might be tempted to calculate USS by subtracting
|
| 2027 |
+
# the "shared" value from the "resident" value in
|
| 2028 |
+
# /proc/<pid>/statm. But at least on Linux, statm's "shared"
|
| 2029 |
+
# value actually counts pages backed by files, which has
|
| 2030 |
+
# little to do with whether the pages are actually shared.
|
| 2031 |
+
# /proc/self/smaps on the other hand appears to give us the
|
| 2032 |
+
# correct information.
|
| 2033 |
+
smaps_data = self._read_smaps_file()
|
| 2034 |
+
# Note: smaps file can be empty for certain processes.
|
| 2035 |
+
# The code below will not crash though and will result to 0.
|
| 2036 |
+
uss = sum(map(int, _private_re.findall(smaps_data))) * 1024
|
| 2037 |
+
pss = sum(map(int, _pss_re.findall(smaps_data))) * 1024
|
| 2038 |
+
swap = sum(map(int, _swap_re.findall(smaps_data))) * 1024
|
| 2039 |
+
return (uss, pss, swap)
|
| 2040 |
+
|
| 2041 |
+
@wrap_exceptions
|
| 2042 |
+
def memory_full_info(self):
|
| 2043 |
+
if HAS_PROC_SMAPS_ROLLUP: # faster
|
| 2044 |
+
try:
|
| 2045 |
+
uss, pss, swap = self._parse_smaps_rollup()
|
| 2046 |
+
except (ProcessLookupError, FileNotFoundError):
|
| 2047 |
+
uss, pss, swap = self._parse_smaps()
|
| 2048 |
+
else:
|
| 2049 |
+
uss, pss, swap = self._parse_smaps()
|
| 2050 |
+
basic_mem = self.memory_info()
|
| 2051 |
+
return pfullmem(*basic_mem + (uss, pss, swap))
|
| 2052 |
+
|
| 2053 |
+
else:
|
| 2054 |
+
memory_full_info = memory_info
|
| 2055 |
+
|
| 2056 |
+
if HAS_PROC_SMAPS:
|
| 2057 |
+
|
| 2058 |
+
@wrap_exceptions
|
| 2059 |
+
def memory_maps(self):
|
| 2060 |
+
"""Return process's mapped memory regions as a list of named
|
| 2061 |
+
tuples. Fields are explained in 'man proc'; here is an updated
|
| 2062 |
+
(Apr 2012) version: http://goo.gl/fmebo.
|
| 2063 |
+
|
| 2064 |
+
/proc/{PID}/smaps does not exist on kernels < 2.6.14 or if
|
| 2065 |
+
CONFIG_MMU kernel configuration option is not enabled.
|
| 2066 |
+
"""
|
| 2067 |
+
|
| 2068 |
+
def get_blocks(lines, current_block):
|
| 2069 |
+
data = {}
|
| 2070 |
+
for line in lines:
|
| 2071 |
+
fields = line.split(None, 5)
|
| 2072 |
+
if not fields[0].endswith(b':'):
|
| 2073 |
+
# new block section
|
| 2074 |
+
yield (current_block.pop(), data)
|
| 2075 |
+
current_block.append(line)
|
| 2076 |
+
else:
|
| 2077 |
+
try:
|
| 2078 |
+
data[fields[0]] = int(fields[1]) * 1024
|
| 2079 |
+
except ValueError:
|
| 2080 |
+
if fields[0].startswith(b'VmFlags:'):
|
| 2081 |
+
# see issue #369
|
| 2082 |
+
continue
|
| 2083 |
+
else:
|
| 2084 |
+
raise ValueError(
|
| 2085 |
+
"don't know how to interpret line %r"
|
| 2086 |
+
% line
|
| 2087 |
+
)
|
| 2088 |
+
yield (current_block.pop(), data)
|
| 2089 |
+
|
| 2090 |
+
data = self._read_smaps_file()
|
| 2091 |
+
# Note: smaps file can be empty for certain processes or for
|
| 2092 |
+
# zombies.
|
| 2093 |
+
if not data:
|
| 2094 |
+
self._raise_if_zombie()
|
| 2095 |
+
return []
|
| 2096 |
+
lines = data.split(b'\n')
|
| 2097 |
+
ls = []
|
| 2098 |
+
first_line = lines.pop(0)
|
| 2099 |
+
current_block = [first_line]
|
| 2100 |
+
for header, data in get_blocks(lines, current_block):
|
| 2101 |
+
hfields = header.split(None, 5)
|
| 2102 |
+
try:
|
| 2103 |
+
addr, perms, _offset, _dev, _inode, path = hfields
|
| 2104 |
+
except ValueError:
|
| 2105 |
+
addr, perms, _offset, _dev, _inode, path = hfields + ['']
|
| 2106 |
+
if not path:
|
| 2107 |
+
path = '[anon]'
|
| 2108 |
+
else:
|
| 2109 |
+
if PY3:
|
| 2110 |
+
path = decode(path)
|
| 2111 |
+
path = path.strip()
|
| 2112 |
+
if path.endswith(' (deleted)') and not path_exists_strict(
|
| 2113 |
+
path
|
| 2114 |
+
):
|
| 2115 |
+
path = path[:-10]
|
| 2116 |
+
item = (
|
| 2117 |
+
decode(addr),
|
| 2118 |
+
decode(perms),
|
| 2119 |
+
path,
|
| 2120 |
+
data.get(b'Rss:', 0),
|
| 2121 |
+
data.get(b'Size:', 0),
|
| 2122 |
+
data.get(b'Pss:', 0),
|
| 2123 |
+
data.get(b'Shared_Clean:', 0),
|
| 2124 |
+
data.get(b'Shared_Dirty:', 0),
|
| 2125 |
+
data.get(b'Private_Clean:', 0),
|
| 2126 |
+
data.get(b'Private_Dirty:', 0),
|
| 2127 |
+
data.get(b'Referenced:', 0),
|
| 2128 |
+
data.get(b'Anonymous:', 0),
|
| 2129 |
+
data.get(b'Swap:', 0),
|
| 2130 |
+
)
|
| 2131 |
+
ls.append(item)
|
| 2132 |
+
return ls
|
| 2133 |
+
|
| 2134 |
+
@wrap_exceptions
|
| 2135 |
+
def cwd(self):
|
| 2136 |
+
return readlink("%s/%s/cwd" % (self._procfs_path, self.pid))
|
| 2137 |
+
|
| 2138 |
+
@wrap_exceptions
|
| 2139 |
+
def num_ctx_switches(
|
| 2140 |
+
self, _ctxsw_re=re.compile(br'ctxt_switches:\t(\d+)')
|
| 2141 |
+
):
|
| 2142 |
+
data = self._read_status_file()
|
| 2143 |
+
ctxsw = _ctxsw_re.findall(data)
|
| 2144 |
+
if not ctxsw:
|
| 2145 |
+
raise NotImplementedError(
|
| 2146 |
+
"'voluntary_ctxt_switches' and 'nonvoluntary_ctxt_switches'"
|
| 2147 |
+
"lines were not found in %s/%s/status; the kernel is "
|
| 2148 |
+
"probably older than 2.6.23" % (self._procfs_path, self.pid)
|
| 2149 |
+
)
|
| 2150 |
+
else:
|
| 2151 |
+
return _common.pctxsw(int(ctxsw[0]), int(ctxsw[1]))
|
| 2152 |
+
|
| 2153 |
+
@wrap_exceptions
|
| 2154 |
+
def num_threads(self, _num_threads_re=re.compile(br'Threads:\t(\d+)')):
|
| 2155 |
+
# Note: on Python 3 using a re is faster than iterating over file
|
| 2156 |
+
# line by line. On Python 2 is the exact opposite, and iterating
|
| 2157 |
+
# over a file on Python 3 is slower than on Python 2.
|
| 2158 |
+
data = self._read_status_file()
|
| 2159 |
+
return int(_num_threads_re.findall(data)[0])
|
| 2160 |
+
|
| 2161 |
+
@wrap_exceptions
|
| 2162 |
+
def threads(self):
|
| 2163 |
+
thread_ids = os.listdir("%s/%s/task" % (self._procfs_path, self.pid))
|
| 2164 |
+
thread_ids.sort()
|
| 2165 |
+
retlist = []
|
| 2166 |
+
hit_enoent = False
|
| 2167 |
+
for thread_id in thread_ids:
|
| 2168 |
+
fname = "%s/%s/task/%s/stat" % (
|
| 2169 |
+
self._procfs_path,
|
| 2170 |
+
self.pid,
|
| 2171 |
+
thread_id,
|
| 2172 |
+
)
|
| 2173 |
+
try:
|
| 2174 |
+
with open_binary(fname) as f:
|
| 2175 |
+
st = f.read().strip()
|
| 2176 |
+
except (FileNotFoundError, ProcessLookupError):
|
| 2177 |
+
# no such file or directory or no such process;
|
| 2178 |
+
# it means thread disappeared on us
|
| 2179 |
+
hit_enoent = True
|
| 2180 |
+
continue
|
| 2181 |
+
# ignore the first two values ("pid (exe)")
|
| 2182 |
+
st = st[st.find(b')') + 2 :]
|
| 2183 |
+
values = st.split(b' ')
|
| 2184 |
+
utime = float(values[11]) / CLOCK_TICKS
|
| 2185 |
+
stime = float(values[12]) / CLOCK_TICKS
|
| 2186 |
+
ntuple = _common.pthread(int(thread_id), utime, stime)
|
| 2187 |
+
retlist.append(ntuple)
|
| 2188 |
+
if hit_enoent:
|
| 2189 |
+
self._raise_if_not_alive()
|
| 2190 |
+
return retlist
|
| 2191 |
+
|
| 2192 |
+
@wrap_exceptions
|
| 2193 |
+
def nice_get(self):
|
| 2194 |
+
# with open_text('%s/%s/stat' % (self._procfs_path, self.pid)) as f:
|
| 2195 |
+
# data = f.read()
|
| 2196 |
+
# return int(data.split()[18])
|
| 2197 |
+
|
| 2198 |
+
# Use C implementation
|
| 2199 |
+
return cext_posix.getpriority(self.pid)
|
| 2200 |
+
|
| 2201 |
+
@wrap_exceptions
|
| 2202 |
+
def nice_set(self, value):
|
| 2203 |
+
return cext_posix.setpriority(self.pid, value)
|
| 2204 |
+
|
| 2205 |
+
# starting from CentOS 6.
|
| 2206 |
+
if HAS_CPU_AFFINITY:
|
| 2207 |
+
|
| 2208 |
+
@wrap_exceptions
|
| 2209 |
+
def cpu_affinity_get(self):
|
| 2210 |
+
return cext.proc_cpu_affinity_get(self.pid)
|
| 2211 |
+
|
| 2212 |
+
def _get_eligible_cpus(
|
| 2213 |
+
self, _re=re.compile(br"Cpus_allowed_list:\t(\d+)-(\d+)")
|
| 2214 |
+
):
|
| 2215 |
+
# See: https://github.com/giampaolo/psutil/issues/956
|
| 2216 |
+
data = self._read_status_file()
|
| 2217 |
+
match = _re.findall(data)
|
| 2218 |
+
if match:
|
| 2219 |
+
return list(range(int(match[0][0]), int(match[0][1]) + 1))
|
| 2220 |
+
else:
|
| 2221 |
+
return list(range(len(per_cpu_times())))
|
| 2222 |
+
|
| 2223 |
+
@wrap_exceptions
|
| 2224 |
+
def cpu_affinity_set(self, cpus):
|
| 2225 |
+
try:
|
| 2226 |
+
cext.proc_cpu_affinity_set(self.pid, cpus)
|
| 2227 |
+
except (OSError, ValueError) as err:
|
| 2228 |
+
if isinstance(err, ValueError) or err.errno == errno.EINVAL:
|
| 2229 |
+
eligible_cpus = self._get_eligible_cpus()
|
| 2230 |
+
all_cpus = tuple(range(len(per_cpu_times())))
|
| 2231 |
+
for cpu in cpus:
|
| 2232 |
+
if cpu not in all_cpus:
|
| 2233 |
+
raise ValueError(
|
| 2234 |
+
"invalid CPU number %r; choose between %s"
|
| 2235 |
+
% (cpu, eligible_cpus)
|
| 2236 |
+
)
|
| 2237 |
+
if cpu not in eligible_cpus:
|
| 2238 |
+
raise ValueError(
|
| 2239 |
+
"CPU number %r is not eligible; choose "
|
| 2240 |
+
"between %s" % (cpu, eligible_cpus)
|
| 2241 |
+
)
|
| 2242 |
+
raise
|
| 2243 |
+
|
| 2244 |
+
# only starting from kernel 2.6.13
|
| 2245 |
+
if HAS_PROC_IO_PRIORITY:
|
| 2246 |
+
|
| 2247 |
+
@wrap_exceptions
|
| 2248 |
+
def ionice_get(self):
|
| 2249 |
+
ioclass, value = cext.proc_ioprio_get(self.pid)
|
| 2250 |
+
if enum is not None:
|
| 2251 |
+
ioclass = IOPriority(ioclass)
|
| 2252 |
+
return _common.pionice(ioclass, value)
|
| 2253 |
+
|
| 2254 |
+
@wrap_exceptions
|
| 2255 |
+
def ionice_set(self, ioclass, value):
|
| 2256 |
+
if value is None:
|
| 2257 |
+
value = 0
|
| 2258 |
+
if value and ioclass in {IOPRIO_CLASS_IDLE, IOPRIO_CLASS_NONE}:
|
| 2259 |
+
raise ValueError("%r ioclass accepts no value" % ioclass)
|
| 2260 |
+
if value < 0 or value > 7:
|
| 2261 |
+
msg = "value not in 0-7 range"
|
| 2262 |
+
raise ValueError(msg)
|
| 2263 |
+
return cext.proc_ioprio_set(self.pid, ioclass, value)
|
| 2264 |
+
|
| 2265 |
+
if prlimit is not None:
|
| 2266 |
+
|
| 2267 |
+
@wrap_exceptions
|
| 2268 |
+
def rlimit(self, resource_, limits=None):
|
| 2269 |
+
# If pid is 0 prlimit() applies to the calling process and
|
| 2270 |
+
# we don't want that. We should never get here though as
|
| 2271 |
+
# PID 0 is not supported on Linux.
|
| 2272 |
+
if self.pid == 0:
|
| 2273 |
+
msg = "can't use prlimit() against PID 0 process"
|
| 2274 |
+
raise ValueError(msg)
|
| 2275 |
+
try:
|
| 2276 |
+
if limits is None:
|
| 2277 |
+
# get
|
| 2278 |
+
return prlimit(self.pid, resource_)
|
| 2279 |
+
else:
|
| 2280 |
+
# set
|
| 2281 |
+
if len(limits) != 2:
|
| 2282 |
+
msg = (
|
| 2283 |
+
"second argument must be a (soft, hard) "
|
| 2284 |
+
+ "tuple, got %s" % repr(limits)
|
| 2285 |
+
)
|
| 2286 |
+
raise ValueError(msg)
|
| 2287 |
+
prlimit(self.pid, resource_, limits)
|
| 2288 |
+
except OSError as err:
|
| 2289 |
+
if err.errno == errno.ENOSYS:
|
| 2290 |
+
# I saw this happening on Travis:
|
| 2291 |
+
# https://travis-ci.org/giampaolo/psutil/jobs/51368273
|
| 2292 |
+
self._raise_if_zombie()
|
| 2293 |
+
raise
|
| 2294 |
+
|
| 2295 |
+
@wrap_exceptions
|
| 2296 |
+
def status(self):
|
| 2297 |
+
letter = self._parse_stat_file()['status']
|
| 2298 |
+
if PY3:
|
| 2299 |
+
letter = letter.decode()
|
| 2300 |
+
# XXX is '?' legit? (we're not supposed to return it anyway)
|
| 2301 |
+
return PROC_STATUSES.get(letter, '?')
|
| 2302 |
+
|
| 2303 |
+
@wrap_exceptions
|
| 2304 |
+
def open_files(self):
|
| 2305 |
+
retlist = []
|
| 2306 |
+
files = os.listdir("%s/%s/fd" % (self._procfs_path, self.pid))
|
| 2307 |
+
hit_enoent = False
|
| 2308 |
+
for fd in files:
|
| 2309 |
+
file = "%s/%s/fd/%s" % (self._procfs_path, self.pid, fd)
|
| 2310 |
+
try:
|
| 2311 |
+
path = readlink(file)
|
| 2312 |
+
except (FileNotFoundError, ProcessLookupError):
|
| 2313 |
+
# ENOENT == file which is gone in the meantime
|
| 2314 |
+
hit_enoent = True
|
| 2315 |
+
continue
|
| 2316 |
+
except OSError as err:
|
| 2317 |
+
if err.errno == errno.EINVAL:
|
| 2318 |
+
# not a link
|
| 2319 |
+
continue
|
| 2320 |
+
if err.errno == errno.ENAMETOOLONG:
|
| 2321 |
+
# file name too long
|
| 2322 |
+
debug(err)
|
| 2323 |
+
continue
|
| 2324 |
+
raise
|
| 2325 |
+
else:
|
| 2326 |
+
# If path is not an absolute there's no way to tell
|
| 2327 |
+
# whether it's a regular file or not, so we skip it.
|
| 2328 |
+
# A regular file is always supposed to be have an
|
| 2329 |
+
# absolute path though.
|
| 2330 |
+
if path.startswith('/') and isfile_strict(path):
|
| 2331 |
+
# Get file position and flags.
|
| 2332 |
+
file = "%s/%s/fdinfo/%s" % (
|
| 2333 |
+
self._procfs_path,
|
| 2334 |
+
self.pid,
|
| 2335 |
+
fd,
|
| 2336 |
+
)
|
| 2337 |
+
try:
|
| 2338 |
+
with open_binary(file) as f:
|
| 2339 |
+
pos = int(f.readline().split()[1])
|
| 2340 |
+
flags = int(f.readline().split()[1], 8)
|
| 2341 |
+
except (FileNotFoundError, ProcessLookupError):
|
| 2342 |
+
# fd gone in the meantime; process may
|
| 2343 |
+
# still be alive
|
| 2344 |
+
hit_enoent = True
|
| 2345 |
+
else:
|
| 2346 |
+
mode = file_flags_to_mode(flags)
|
| 2347 |
+
ntuple = popenfile(
|
| 2348 |
+
path, int(fd), int(pos), mode, flags
|
| 2349 |
+
)
|
| 2350 |
+
retlist.append(ntuple)
|
| 2351 |
+
if hit_enoent:
|
| 2352 |
+
self._raise_if_not_alive()
|
| 2353 |
+
return retlist
|
| 2354 |
+
|
| 2355 |
+
@wrap_exceptions
|
| 2356 |
+
def net_connections(self, kind='inet'):
|
| 2357 |
+
ret = _net_connections.retrieve(kind, self.pid)
|
| 2358 |
+
self._raise_if_not_alive()
|
| 2359 |
+
return ret
|
| 2360 |
+
|
| 2361 |
+
@wrap_exceptions
|
| 2362 |
+
def num_fds(self):
|
| 2363 |
+
return len(os.listdir("%s/%s/fd" % (self._procfs_path, self.pid)))
|
| 2364 |
+
|
| 2365 |
+
@wrap_exceptions
|
| 2366 |
+
def ppid(self):
|
| 2367 |
+
return int(self._parse_stat_file()['ppid'])
|
| 2368 |
+
|
| 2369 |
+
@wrap_exceptions
|
| 2370 |
+
def uids(self, _uids_re=re.compile(br'Uid:\t(\d+)\t(\d+)\t(\d+)')):
|
| 2371 |
+
data = self._read_status_file()
|
| 2372 |
+
real, effective, saved = _uids_re.findall(data)[0]
|
| 2373 |
+
return _common.puids(int(real), int(effective), int(saved))
|
| 2374 |
+
|
| 2375 |
+
@wrap_exceptions
|
| 2376 |
+
def gids(self, _gids_re=re.compile(br'Gid:\t(\d+)\t(\d+)\t(\d+)')):
|
| 2377 |
+
data = self._read_status_file()
|
| 2378 |
+
real, effective, saved = _gids_re.findall(data)[0]
|
| 2379 |
+
return _common.pgids(int(real), int(effective), int(saved))
|
.venv/lib/python3.11/site-packages/ray/thirdparty_files/psutil/_psosx.py
ADDED
|
@@ -0,0 +1,552 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) 2009, Giampaolo Rodola'. All rights reserved.
|
| 2 |
+
# Use of this source code is governed by a BSD-style license that can be
|
| 3 |
+
# found in the LICENSE file.
|
| 4 |
+
|
| 5 |
+
"""macOS platform implementation."""
|
| 6 |
+
|
| 7 |
+
import errno
|
| 8 |
+
import functools
|
| 9 |
+
import os
|
| 10 |
+
from collections import namedtuple
|
| 11 |
+
|
| 12 |
+
from . import _common
|
| 13 |
+
from . import _psposix
|
| 14 |
+
from . import _psutil_osx as cext
|
| 15 |
+
from . import _psutil_posix as cext_posix
|
| 16 |
+
from ._common import AccessDenied
|
| 17 |
+
from ._common import NoSuchProcess
|
| 18 |
+
from ._common import ZombieProcess
|
| 19 |
+
from ._common import conn_tmap
|
| 20 |
+
from ._common import conn_to_ntuple
|
| 21 |
+
from ._common import isfile_strict
|
| 22 |
+
from ._common import memoize_when_activated
|
| 23 |
+
from ._common import parse_environ_block
|
| 24 |
+
from ._common import usage_percent
|
| 25 |
+
from ._compat import PermissionError
|
| 26 |
+
from ._compat import ProcessLookupError
|
| 27 |
+
|
| 28 |
+
|
| 29 |
+
__extra__all__ = []
|
| 30 |
+
|
| 31 |
+
|
| 32 |
+
# =====================================================================
|
| 33 |
+
# --- globals
|
| 34 |
+
# =====================================================================
|
| 35 |
+
|
| 36 |
+
|
| 37 |
+
PAGESIZE = cext_posix.getpagesize()
|
| 38 |
+
AF_LINK = cext_posix.AF_LINK
|
| 39 |
+
|
| 40 |
+
TCP_STATUSES = {
|
| 41 |
+
cext.TCPS_ESTABLISHED: _common.CONN_ESTABLISHED,
|
| 42 |
+
cext.TCPS_SYN_SENT: _common.CONN_SYN_SENT,
|
| 43 |
+
cext.TCPS_SYN_RECEIVED: _common.CONN_SYN_RECV,
|
| 44 |
+
cext.TCPS_FIN_WAIT_1: _common.CONN_FIN_WAIT1,
|
| 45 |
+
cext.TCPS_FIN_WAIT_2: _common.CONN_FIN_WAIT2,
|
| 46 |
+
cext.TCPS_TIME_WAIT: _common.CONN_TIME_WAIT,
|
| 47 |
+
cext.TCPS_CLOSED: _common.CONN_CLOSE,
|
| 48 |
+
cext.TCPS_CLOSE_WAIT: _common.CONN_CLOSE_WAIT,
|
| 49 |
+
cext.TCPS_LAST_ACK: _common.CONN_LAST_ACK,
|
| 50 |
+
cext.TCPS_LISTEN: _common.CONN_LISTEN,
|
| 51 |
+
cext.TCPS_CLOSING: _common.CONN_CLOSING,
|
| 52 |
+
cext.PSUTIL_CONN_NONE: _common.CONN_NONE,
|
| 53 |
+
}
|
| 54 |
+
|
| 55 |
+
PROC_STATUSES = {
|
| 56 |
+
cext.SIDL: _common.STATUS_IDLE,
|
| 57 |
+
cext.SRUN: _common.STATUS_RUNNING,
|
| 58 |
+
cext.SSLEEP: _common.STATUS_SLEEPING,
|
| 59 |
+
cext.SSTOP: _common.STATUS_STOPPED,
|
| 60 |
+
cext.SZOMB: _common.STATUS_ZOMBIE,
|
| 61 |
+
}
|
| 62 |
+
|
| 63 |
+
kinfo_proc_map = dict(
|
| 64 |
+
ppid=0,
|
| 65 |
+
ruid=1,
|
| 66 |
+
euid=2,
|
| 67 |
+
suid=3,
|
| 68 |
+
rgid=4,
|
| 69 |
+
egid=5,
|
| 70 |
+
sgid=6,
|
| 71 |
+
ttynr=7,
|
| 72 |
+
ctime=8,
|
| 73 |
+
status=9,
|
| 74 |
+
name=10,
|
| 75 |
+
)
|
| 76 |
+
|
| 77 |
+
pidtaskinfo_map = dict(
|
| 78 |
+
cpuutime=0,
|
| 79 |
+
cpustime=1,
|
| 80 |
+
rss=2,
|
| 81 |
+
vms=3,
|
| 82 |
+
pfaults=4,
|
| 83 |
+
pageins=5,
|
| 84 |
+
numthreads=6,
|
| 85 |
+
volctxsw=7,
|
| 86 |
+
)
|
| 87 |
+
|
| 88 |
+
|
| 89 |
+
# =====================================================================
|
| 90 |
+
# --- named tuples
|
| 91 |
+
# =====================================================================
|
| 92 |
+
|
| 93 |
+
|
| 94 |
+
# fmt: off
|
| 95 |
+
# psutil.cpu_times()
|
| 96 |
+
scputimes = namedtuple('scputimes', ['user', 'nice', 'system', 'idle'])
|
| 97 |
+
# psutil.virtual_memory()
|
| 98 |
+
svmem = namedtuple(
|
| 99 |
+
'svmem', ['total', 'available', 'percent', 'used', 'free',
|
| 100 |
+
'active', 'inactive', 'wired'])
|
| 101 |
+
# psutil.Process.memory_info()
|
| 102 |
+
pmem = namedtuple('pmem', ['rss', 'vms', 'pfaults', 'pageins'])
|
| 103 |
+
# psutil.Process.memory_full_info()
|
| 104 |
+
pfullmem = namedtuple('pfullmem', pmem._fields + ('uss', ))
|
| 105 |
+
# fmt: on
|
| 106 |
+
|
| 107 |
+
|
| 108 |
+
# =====================================================================
|
| 109 |
+
# --- memory
|
| 110 |
+
# =====================================================================
|
| 111 |
+
|
| 112 |
+
|
| 113 |
+
def virtual_memory():
|
| 114 |
+
"""System virtual memory as a namedtuple."""
|
| 115 |
+
total, active, inactive, wired, free, speculative = cext.virtual_mem()
|
| 116 |
+
# This is how Zabbix calculate avail and used mem:
|
| 117 |
+
# https://github.com/zabbix/zabbix/blob/trunk/src/libs/zbxsysinfo/
|
| 118 |
+
# osx/memory.c
|
| 119 |
+
# Also see: https://github.com/giampaolo/psutil/issues/1277
|
| 120 |
+
avail = inactive + free
|
| 121 |
+
used = active + wired
|
| 122 |
+
# This is NOT how Zabbix calculates free mem but it matches "free"
|
| 123 |
+
# cmdline utility.
|
| 124 |
+
free -= speculative
|
| 125 |
+
percent = usage_percent((total - avail), total, round_=1)
|
| 126 |
+
return svmem(total, avail, percent, used, free, active, inactive, wired)
|
| 127 |
+
|
| 128 |
+
|
| 129 |
+
def swap_memory():
|
| 130 |
+
"""Swap system memory as a (total, used, free, sin, sout) tuple."""
|
| 131 |
+
total, used, free, sin, sout = cext.swap_mem()
|
| 132 |
+
percent = usage_percent(used, total, round_=1)
|
| 133 |
+
return _common.sswap(total, used, free, percent, sin, sout)
|
| 134 |
+
|
| 135 |
+
|
| 136 |
+
# =====================================================================
|
| 137 |
+
# --- CPU
|
| 138 |
+
# =====================================================================
|
| 139 |
+
|
| 140 |
+
|
| 141 |
+
def cpu_times():
|
| 142 |
+
"""Return system CPU times as a namedtuple."""
|
| 143 |
+
user, nice, system, idle = cext.cpu_times()
|
| 144 |
+
return scputimes(user, nice, system, idle)
|
| 145 |
+
|
| 146 |
+
|
| 147 |
+
def per_cpu_times():
|
| 148 |
+
"""Return system CPU times as a named tuple."""
|
| 149 |
+
ret = []
|
| 150 |
+
for cpu_t in cext.per_cpu_times():
|
| 151 |
+
user, nice, system, idle = cpu_t
|
| 152 |
+
item = scputimes(user, nice, system, idle)
|
| 153 |
+
ret.append(item)
|
| 154 |
+
return ret
|
| 155 |
+
|
| 156 |
+
|
| 157 |
+
def cpu_count_logical():
|
| 158 |
+
"""Return the number of logical CPUs in the system."""
|
| 159 |
+
return cext.cpu_count_logical()
|
| 160 |
+
|
| 161 |
+
|
| 162 |
+
def cpu_count_cores():
|
| 163 |
+
"""Return the number of CPU cores in the system."""
|
| 164 |
+
return cext.cpu_count_cores()
|
| 165 |
+
|
| 166 |
+
|
| 167 |
+
def cpu_stats():
|
| 168 |
+
ctx_switches, interrupts, soft_interrupts, syscalls, _traps = (
|
| 169 |
+
cext.cpu_stats()
|
| 170 |
+
)
|
| 171 |
+
return _common.scpustats(
|
| 172 |
+
ctx_switches, interrupts, soft_interrupts, syscalls
|
| 173 |
+
)
|
| 174 |
+
|
| 175 |
+
|
| 176 |
+
def cpu_freq():
|
| 177 |
+
"""Return CPU frequency.
|
| 178 |
+
On macOS per-cpu frequency is not supported.
|
| 179 |
+
Also, the returned frequency never changes, see:
|
| 180 |
+
https://arstechnica.com/civis/viewtopic.php?f=19&t=465002.
|
| 181 |
+
"""
|
| 182 |
+
curr, min_, max_ = cext.cpu_freq()
|
| 183 |
+
return [_common.scpufreq(curr, min_, max_)]
|
| 184 |
+
|
| 185 |
+
|
| 186 |
+
# =====================================================================
|
| 187 |
+
# --- disks
|
| 188 |
+
# =====================================================================
|
| 189 |
+
|
| 190 |
+
|
| 191 |
+
disk_usage = _psposix.disk_usage
|
| 192 |
+
disk_io_counters = cext.disk_io_counters
|
| 193 |
+
|
| 194 |
+
|
| 195 |
+
def disk_partitions(all=False):
|
| 196 |
+
"""Return mounted disk partitions as a list of namedtuples."""
|
| 197 |
+
retlist = []
|
| 198 |
+
partitions = cext.disk_partitions()
|
| 199 |
+
for partition in partitions:
|
| 200 |
+
device, mountpoint, fstype, opts = partition
|
| 201 |
+
if device == 'none':
|
| 202 |
+
device = ''
|
| 203 |
+
if not all:
|
| 204 |
+
if not os.path.isabs(device) or not os.path.exists(device):
|
| 205 |
+
continue
|
| 206 |
+
ntuple = _common.sdiskpart(device, mountpoint, fstype, opts)
|
| 207 |
+
retlist.append(ntuple)
|
| 208 |
+
return retlist
|
| 209 |
+
|
| 210 |
+
|
| 211 |
+
# =====================================================================
|
| 212 |
+
# --- sensors
|
| 213 |
+
# =====================================================================
|
| 214 |
+
|
| 215 |
+
|
| 216 |
+
def sensors_battery():
|
| 217 |
+
"""Return battery information."""
|
| 218 |
+
try:
|
| 219 |
+
percent, minsleft, power_plugged = cext.sensors_battery()
|
| 220 |
+
except NotImplementedError:
|
| 221 |
+
# no power source - return None according to interface
|
| 222 |
+
return None
|
| 223 |
+
power_plugged = power_plugged == 1
|
| 224 |
+
if power_plugged:
|
| 225 |
+
secsleft = _common.POWER_TIME_UNLIMITED
|
| 226 |
+
elif minsleft == -1:
|
| 227 |
+
secsleft = _common.POWER_TIME_UNKNOWN
|
| 228 |
+
else:
|
| 229 |
+
secsleft = minsleft * 60
|
| 230 |
+
return _common.sbattery(percent, secsleft, power_plugged)
|
| 231 |
+
|
| 232 |
+
|
| 233 |
+
# =====================================================================
|
| 234 |
+
# --- network
|
| 235 |
+
# =====================================================================
|
| 236 |
+
|
| 237 |
+
|
| 238 |
+
net_io_counters = cext.net_io_counters
|
| 239 |
+
net_if_addrs = cext_posix.net_if_addrs
|
| 240 |
+
|
| 241 |
+
|
| 242 |
+
def net_connections(kind='inet'):
|
| 243 |
+
"""System-wide network connections."""
|
| 244 |
+
# Note: on macOS this will fail with AccessDenied unless
|
| 245 |
+
# the process is owned by root.
|
| 246 |
+
ret = []
|
| 247 |
+
for pid in pids():
|
| 248 |
+
try:
|
| 249 |
+
cons = Process(pid).net_connections(kind)
|
| 250 |
+
except NoSuchProcess:
|
| 251 |
+
continue
|
| 252 |
+
else:
|
| 253 |
+
if cons:
|
| 254 |
+
for c in cons:
|
| 255 |
+
c = list(c) + [pid]
|
| 256 |
+
ret.append(_common.sconn(*c))
|
| 257 |
+
return ret
|
| 258 |
+
|
| 259 |
+
|
| 260 |
+
def net_if_stats():
|
| 261 |
+
"""Get NIC stats (isup, duplex, speed, mtu)."""
|
| 262 |
+
names = net_io_counters().keys()
|
| 263 |
+
ret = {}
|
| 264 |
+
for name in names:
|
| 265 |
+
try:
|
| 266 |
+
mtu = cext_posix.net_if_mtu(name)
|
| 267 |
+
flags = cext_posix.net_if_flags(name)
|
| 268 |
+
duplex, speed = cext_posix.net_if_duplex_speed(name)
|
| 269 |
+
except OSError as err:
|
| 270 |
+
# https://github.com/giampaolo/psutil/issues/1279
|
| 271 |
+
if err.errno != errno.ENODEV:
|
| 272 |
+
raise
|
| 273 |
+
else:
|
| 274 |
+
if hasattr(_common, 'NicDuplex'):
|
| 275 |
+
duplex = _common.NicDuplex(duplex)
|
| 276 |
+
output_flags = ','.join(flags)
|
| 277 |
+
isup = 'running' in flags
|
| 278 |
+
ret[name] = _common.snicstats(
|
| 279 |
+
isup, duplex, speed, mtu, output_flags
|
| 280 |
+
)
|
| 281 |
+
return ret
|
| 282 |
+
|
| 283 |
+
|
| 284 |
+
# =====================================================================
|
| 285 |
+
# --- other system functions
|
| 286 |
+
# =====================================================================
|
| 287 |
+
|
| 288 |
+
|
| 289 |
+
def boot_time():
|
| 290 |
+
"""The system boot time expressed in seconds since the epoch."""
|
| 291 |
+
return cext.boot_time()
|
| 292 |
+
|
| 293 |
+
|
| 294 |
+
def users():
|
| 295 |
+
"""Return currently connected users as a list of namedtuples."""
|
| 296 |
+
retlist = []
|
| 297 |
+
rawlist = cext.users()
|
| 298 |
+
for item in rawlist:
|
| 299 |
+
user, tty, hostname, tstamp, pid = item
|
| 300 |
+
if tty == '~':
|
| 301 |
+
continue # reboot or shutdown
|
| 302 |
+
if not tstamp:
|
| 303 |
+
continue
|
| 304 |
+
nt = _common.suser(user, tty or None, hostname or None, tstamp, pid)
|
| 305 |
+
retlist.append(nt)
|
| 306 |
+
return retlist
|
| 307 |
+
|
| 308 |
+
|
| 309 |
+
# =====================================================================
|
| 310 |
+
# --- processes
|
| 311 |
+
# =====================================================================
|
| 312 |
+
|
| 313 |
+
|
| 314 |
+
def pids():
|
| 315 |
+
ls = cext.pids()
|
| 316 |
+
if 0 not in ls:
|
| 317 |
+
# On certain macOS versions pids() C doesn't return PID 0 but
|
| 318 |
+
# "ps" does and the process is querable via sysctl():
|
| 319 |
+
# https://travis-ci.org/giampaolo/psutil/jobs/309619941
|
| 320 |
+
try:
|
| 321 |
+
Process(0).create_time()
|
| 322 |
+
ls.insert(0, 0)
|
| 323 |
+
except NoSuchProcess:
|
| 324 |
+
pass
|
| 325 |
+
except AccessDenied:
|
| 326 |
+
ls.insert(0, 0)
|
| 327 |
+
return ls
|
| 328 |
+
|
| 329 |
+
|
| 330 |
+
pid_exists = _psposix.pid_exists
|
| 331 |
+
|
| 332 |
+
|
| 333 |
+
def is_zombie(pid):
|
| 334 |
+
try:
|
| 335 |
+
st = cext.proc_kinfo_oneshot(pid)[kinfo_proc_map['status']]
|
| 336 |
+
return st == cext.SZOMB
|
| 337 |
+
except OSError:
|
| 338 |
+
return False
|
| 339 |
+
|
| 340 |
+
|
| 341 |
+
def wrap_exceptions(fun):
|
| 342 |
+
"""Decorator which translates bare OSError exceptions into
|
| 343 |
+
NoSuchProcess and AccessDenied.
|
| 344 |
+
"""
|
| 345 |
+
|
| 346 |
+
@functools.wraps(fun)
|
| 347 |
+
def wrapper(self, *args, **kwargs):
|
| 348 |
+
try:
|
| 349 |
+
return fun(self, *args, **kwargs)
|
| 350 |
+
except ProcessLookupError:
|
| 351 |
+
if is_zombie(self.pid):
|
| 352 |
+
raise ZombieProcess(self.pid, self._name, self._ppid)
|
| 353 |
+
else:
|
| 354 |
+
raise NoSuchProcess(self.pid, self._name)
|
| 355 |
+
except PermissionError:
|
| 356 |
+
raise AccessDenied(self.pid, self._name)
|
| 357 |
+
|
| 358 |
+
return wrapper
|
| 359 |
+
|
| 360 |
+
|
| 361 |
+
class Process:
|
| 362 |
+
"""Wrapper class around underlying C implementation."""
|
| 363 |
+
|
| 364 |
+
__slots__ = ["_cache", "_name", "_ppid", "pid"]
|
| 365 |
+
|
| 366 |
+
def __init__(self, pid):
|
| 367 |
+
self.pid = pid
|
| 368 |
+
self._name = None
|
| 369 |
+
self._ppid = None
|
| 370 |
+
|
| 371 |
+
@wrap_exceptions
|
| 372 |
+
@memoize_when_activated
|
| 373 |
+
def _get_kinfo_proc(self):
|
| 374 |
+
# Note: should work with all PIDs without permission issues.
|
| 375 |
+
ret = cext.proc_kinfo_oneshot(self.pid)
|
| 376 |
+
assert len(ret) == len(kinfo_proc_map)
|
| 377 |
+
return ret
|
| 378 |
+
|
| 379 |
+
@wrap_exceptions
|
| 380 |
+
@memoize_when_activated
|
| 381 |
+
def _get_pidtaskinfo(self):
|
| 382 |
+
# Note: should work for PIDs owned by user only.
|
| 383 |
+
ret = cext.proc_pidtaskinfo_oneshot(self.pid)
|
| 384 |
+
assert len(ret) == len(pidtaskinfo_map)
|
| 385 |
+
return ret
|
| 386 |
+
|
| 387 |
+
def oneshot_enter(self):
|
| 388 |
+
self._get_kinfo_proc.cache_activate(self)
|
| 389 |
+
self._get_pidtaskinfo.cache_activate(self)
|
| 390 |
+
|
| 391 |
+
def oneshot_exit(self):
|
| 392 |
+
self._get_kinfo_proc.cache_deactivate(self)
|
| 393 |
+
self._get_pidtaskinfo.cache_deactivate(self)
|
| 394 |
+
|
| 395 |
+
@wrap_exceptions
|
| 396 |
+
def name(self):
|
| 397 |
+
name = self._get_kinfo_proc()[kinfo_proc_map['name']]
|
| 398 |
+
return name if name is not None else cext.proc_name(self.pid)
|
| 399 |
+
|
| 400 |
+
@wrap_exceptions
|
| 401 |
+
def exe(self):
|
| 402 |
+
return cext.proc_exe(self.pid)
|
| 403 |
+
|
| 404 |
+
@wrap_exceptions
|
| 405 |
+
def cmdline(self):
|
| 406 |
+
return cext.proc_cmdline(self.pid)
|
| 407 |
+
|
| 408 |
+
@wrap_exceptions
|
| 409 |
+
def environ(self):
|
| 410 |
+
return parse_environ_block(cext.proc_environ(self.pid))
|
| 411 |
+
|
| 412 |
+
@wrap_exceptions
|
| 413 |
+
def ppid(self):
|
| 414 |
+
self._ppid = self._get_kinfo_proc()[kinfo_proc_map['ppid']]
|
| 415 |
+
return self._ppid
|
| 416 |
+
|
| 417 |
+
@wrap_exceptions
|
| 418 |
+
def cwd(self):
|
| 419 |
+
return cext.proc_cwd(self.pid)
|
| 420 |
+
|
| 421 |
+
@wrap_exceptions
|
| 422 |
+
def uids(self):
|
| 423 |
+
rawtuple = self._get_kinfo_proc()
|
| 424 |
+
return _common.puids(
|
| 425 |
+
rawtuple[kinfo_proc_map['ruid']],
|
| 426 |
+
rawtuple[kinfo_proc_map['euid']],
|
| 427 |
+
rawtuple[kinfo_proc_map['suid']],
|
| 428 |
+
)
|
| 429 |
+
|
| 430 |
+
@wrap_exceptions
|
| 431 |
+
def gids(self):
|
| 432 |
+
rawtuple = self._get_kinfo_proc()
|
| 433 |
+
return _common.puids(
|
| 434 |
+
rawtuple[kinfo_proc_map['rgid']],
|
| 435 |
+
rawtuple[kinfo_proc_map['egid']],
|
| 436 |
+
rawtuple[kinfo_proc_map['sgid']],
|
| 437 |
+
)
|
| 438 |
+
|
| 439 |
+
@wrap_exceptions
|
| 440 |
+
def terminal(self):
|
| 441 |
+
tty_nr = self._get_kinfo_proc()[kinfo_proc_map['ttynr']]
|
| 442 |
+
tmap = _psposix.get_terminal_map()
|
| 443 |
+
try:
|
| 444 |
+
return tmap[tty_nr]
|
| 445 |
+
except KeyError:
|
| 446 |
+
return None
|
| 447 |
+
|
| 448 |
+
@wrap_exceptions
|
| 449 |
+
def memory_info(self):
|
| 450 |
+
rawtuple = self._get_pidtaskinfo()
|
| 451 |
+
return pmem(
|
| 452 |
+
rawtuple[pidtaskinfo_map['rss']],
|
| 453 |
+
rawtuple[pidtaskinfo_map['vms']],
|
| 454 |
+
rawtuple[pidtaskinfo_map['pfaults']],
|
| 455 |
+
rawtuple[pidtaskinfo_map['pageins']],
|
| 456 |
+
)
|
| 457 |
+
|
| 458 |
+
@wrap_exceptions
|
| 459 |
+
def memory_full_info(self):
|
| 460 |
+
basic_mem = self.memory_info()
|
| 461 |
+
uss = cext.proc_memory_uss(self.pid)
|
| 462 |
+
return pfullmem(*basic_mem + (uss,))
|
| 463 |
+
|
| 464 |
+
@wrap_exceptions
|
| 465 |
+
def cpu_times(self):
|
| 466 |
+
rawtuple = self._get_pidtaskinfo()
|
| 467 |
+
return _common.pcputimes(
|
| 468 |
+
rawtuple[pidtaskinfo_map['cpuutime']],
|
| 469 |
+
rawtuple[pidtaskinfo_map['cpustime']],
|
| 470 |
+
# children user / system times are not retrievable (set to 0)
|
| 471 |
+
0.0,
|
| 472 |
+
0.0,
|
| 473 |
+
)
|
| 474 |
+
|
| 475 |
+
@wrap_exceptions
|
| 476 |
+
def create_time(self):
|
| 477 |
+
return self._get_kinfo_proc()[kinfo_proc_map['ctime']]
|
| 478 |
+
|
| 479 |
+
@wrap_exceptions
|
| 480 |
+
def num_ctx_switches(self):
|
| 481 |
+
# Unvoluntary value seems not to be available;
|
| 482 |
+
# getrusage() numbers seems to confirm this theory.
|
| 483 |
+
# We set it to 0.
|
| 484 |
+
vol = self._get_pidtaskinfo()[pidtaskinfo_map['volctxsw']]
|
| 485 |
+
return _common.pctxsw(vol, 0)
|
| 486 |
+
|
| 487 |
+
@wrap_exceptions
|
| 488 |
+
def num_threads(self):
|
| 489 |
+
return self._get_pidtaskinfo()[pidtaskinfo_map['numthreads']]
|
| 490 |
+
|
| 491 |
+
@wrap_exceptions
|
| 492 |
+
def open_files(self):
|
| 493 |
+
if self.pid == 0:
|
| 494 |
+
return []
|
| 495 |
+
files = []
|
| 496 |
+
rawlist = cext.proc_open_files(self.pid)
|
| 497 |
+
for path, fd in rawlist:
|
| 498 |
+
if isfile_strict(path):
|
| 499 |
+
ntuple = _common.popenfile(path, fd)
|
| 500 |
+
files.append(ntuple)
|
| 501 |
+
return files
|
| 502 |
+
|
| 503 |
+
@wrap_exceptions
|
| 504 |
+
def net_connections(self, kind='inet'):
|
| 505 |
+
if kind not in conn_tmap:
|
| 506 |
+
raise ValueError(
|
| 507 |
+
"invalid %r kind argument; choose between %s"
|
| 508 |
+
% (kind, ', '.join([repr(x) for x in conn_tmap]))
|
| 509 |
+
)
|
| 510 |
+
families, types = conn_tmap[kind]
|
| 511 |
+
rawlist = cext.proc_net_connections(self.pid, families, types)
|
| 512 |
+
ret = []
|
| 513 |
+
for item in rawlist:
|
| 514 |
+
fd, fam, type, laddr, raddr, status = item
|
| 515 |
+
nt = conn_to_ntuple(
|
| 516 |
+
fd, fam, type, laddr, raddr, status, TCP_STATUSES
|
| 517 |
+
)
|
| 518 |
+
ret.append(nt)
|
| 519 |
+
return ret
|
| 520 |
+
|
| 521 |
+
@wrap_exceptions
|
| 522 |
+
def num_fds(self):
|
| 523 |
+
if self.pid == 0:
|
| 524 |
+
return 0
|
| 525 |
+
return cext.proc_num_fds(self.pid)
|
| 526 |
+
|
| 527 |
+
@wrap_exceptions
|
| 528 |
+
def wait(self, timeout=None):
|
| 529 |
+
return _psposix.wait_pid(self.pid, timeout, self._name)
|
| 530 |
+
|
| 531 |
+
@wrap_exceptions
|
| 532 |
+
def nice_get(self):
|
| 533 |
+
return cext_posix.getpriority(self.pid)
|
| 534 |
+
|
| 535 |
+
@wrap_exceptions
|
| 536 |
+
def nice_set(self, value):
|
| 537 |
+
return cext_posix.setpriority(self.pid, value)
|
| 538 |
+
|
| 539 |
+
@wrap_exceptions
|
| 540 |
+
def status(self):
|
| 541 |
+
code = self._get_kinfo_proc()[kinfo_proc_map['status']]
|
| 542 |
+
# XXX is '?' legit? (we're not supposed to return it anyway)
|
| 543 |
+
return PROC_STATUSES.get(code, '?')
|
| 544 |
+
|
| 545 |
+
@wrap_exceptions
|
| 546 |
+
def threads(self):
|
| 547 |
+
rawlist = cext.proc_threads(self.pid)
|
| 548 |
+
retlist = []
|
| 549 |
+
for thread_id, utime, stime in rawlist:
|
| 550 |
+
ntuple = _common.pthread(thread_id, utime, stime)
|
| 551 |
+
retlist.append(ntuple)
|
| 552 |
+
return retlist
|
.venv/lib/python3.11/site-packages/ray/thirdparty_files/psutil/_psposix.py
ADDED
|
@@ -0,0 +1,243 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) 2009, Giampaolo Rodola'. All rights reserved.
|
| 2 |
+
# Use of this source code is governed by a BSD-style license that can be
|
| 3 |
+
# found in the LICENSE file.
|
| 4 |
+
|
| 5 |
+
"""Routines common to all posix systems."""
|
| 6 |
+
|
| 7 |
+
import glob
|
| 8 |
+
import os
|
| 9 |
+
import signal
|
| 10 |
+
import sys
|
| 11 |
+
import time
|
| 12 |
+
|
| 13 |
+
from ._common import MACOS
|
| 14 |
+
from ._common import TimeoutExpired
|
| 15 |
+
from ._common import memoize
|
| 16 |
+
from ._common import sdiskusage
|
| 17 |
+
from ._common import usage_percent
|
| 18 |
+
from ._compat import PY3
|
| 19 |
+
from ._compat import ChildProcessError
|
| 20 |
+
from ._compat import FileNotFoundError
|
| 21 |
+
from ._compat import InterruptedError
|
| 22 |
+
from ._compat import PermissionError
|
| 23 |
+
from ._compat import ProcessLookupError
|
| 24 |
+
from ._compat import unicode
|
| 25 |
+
|
| 26 |
+
|
| 27 |
+
if MACOS:
|
| 28 |
+
from . import _psutil_osx
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
if PY3:
|
| 32 |
+
import enum
|
| 33 |
+
else:
|
| 34 |
+
enum = None
|
| 35 |
+
|
| 36 |
+
|
| 37 |
+
__all__ = ['pid_exists', 'wait_pid', 'disk_usage', 'get_terminal_map']
|
| 38 |
+
|
| 39 |
+
|
| 40 |
+
def pid_exists(pid):
|
| 41 |
+
"""Check whether pid exists in the current process table."""
|
| 42 |
+
if pid == 0:
|
| 43 |
+
# According to "man 2 kill" PID 0 has a special meaning:
|
| 44 |
+
# it refers to <<every process in the process group of the
|
| 45 |
+
# calling process>> so we don't want to go any further.
|
| 46 |
+
# If we get here it means this UNIX platform *does* have
|
| 47 |
+
# a process with id 0.
|
| 48 |
+
return True
|
| 49 |
+
try:
|
| 50 |
+
os.kill(pid, 0)
|
| 51 |
+
except ProcessLookupError:
|
| 52 |
+
return False
|
| 53 |
+
except PermissionError:
|
| 54 |
+
# EPERM clearly means there's a process to deny access to
|
| 55 |
+
return True
|
| 56 |
+
# According to "man 2 kill" possible error values are
|
| 57 |
+
# (EINVAL, EPERM, ESRCH)
|
| 58 |
+
else:
|
| 59 |
+
return True
|
| 60 |
+
|
| 61 |
+
|
| 62 |
+
# Python 3.5 signals enum (contributed by me ^^):
|
| 63 |
+
# https://bugs.python.org/issue21076
|
| 64 |
+
if enum is not None and hasattr(signal, "Signals"):
|
| 65 |
+
Negsignal = enum.IntEnum(
|
| 66 |
+
'Negsignal', dict([(x.name, -x.value) for x in signal.Signals])
|
| 67 |
+
)
|
| 68 |
+
|
| 69 |
+
def negsig_to_enum(num):
|
| 70 |
+
"""Convert a negative signal value to an enum."""
|
| 71 |
+
try:
|
| 72 |
+
return Negsignal(num)
|
| 73 |
+
except ValueError:
|
| 74 |
+
return num
|
| 75 |
+
|
| 76 |
+
else: # pragma: no cover
|
| 77 |
+
|
| 78 |
+
def negsig_to_enum(num):
|
| 79 |
+
return num
|
| 80 |
+
|
| 81 |
+
|
| 82 |
+
def wait_pid(
|
| 83 |
+
pid,
|
| 84 |
+
timeout=None,
|
| 85 |
+
proc_name=None,
|
| 86 |
+
_waitpid=os.waitpid,
|
| 87 |
+
_timer=getattr(time, 'monotonic', time.time), # noqa: B008
|
| 88 |
+
_min=min,
|
| 89 |
+
_sleep=time.sleep,
|
| 90 |
+
_pid_exists=pid_exists,
|
| 91 |
+
):
|
| 92 |
+
"""Wait for a process PID to terminate.
|
| 93 |
+
|
| 94 |
+
If the process terminated normally by calling exit(3) or _exit(2),
|
| 95 |
+
or by returning from main(), the return value is the positive integer
|
| 96 |
+
passed to *exit().
|
| 97 |
+
|
| 98 |
+
If it was terminated by a signal it returns the negated value of the
|
| 99 |
+
signal which caused the termination (e.g. -SIGTERM).
|
| 100 |
+
|
| 101 |
+
If PID is not a children of os.getpid() (current process) just
|
| 102 |
+
wait until the process disappears and return None.
|
| 103 |
+
|
| 104 |
+
If PID does not exist at all return None immediately.
|
| 105 |
+
|
| 106 |
+
If *timeout* != None and process is still alive raise TimeoutExpired.
|
| 107 |
+
timeout=0 is also possible (either return immediately or raise).
|
| 108 |
+
"""
|
| 109 |
+
if pid <= 0:
|
| 110 |
+
# see "man waitpid"
|
| 111 |
+
msg = "can't wait for PID 0"
|
| 112 |
+
raise ValueError(msg)
|
| 113 |
+
interval = 0.0001
|
| 114 |
+
flags = 0
|
| 115 |
+
if timeout is not None:
|
| 116 |
+
flags |= os.WNOHANG
|
| 117 |
+
stop_at = _timer() + timeout
|
| 118 |
+
|
| 119 |
+
def sleep(interval):
|
| 120 |
+
# Sleep for some time and return a new increased interval.
|
| 121 |
+
if timeout is not None:
|
| 122 |
+
if _timer() >= stop_at:
|
| 123 |
+
raise TimeoutExpired(timeout, pid=pid, name=proc_name)
|
| 124 |
+
_sleep(interval)
|
| 125 |
+
return _min(interval * 2, 0.04)
|
| 126 |
+
|
| 127 |
+
# See: https://linux.die.net/man/2/waitpid
|
| 128 |
+
while True:
|
| 129 |
+
try:
|
| 130 |
+
retpid, status = os.waitpid(pid, flags)
|
| 131 |
+
except InterruptedError:
|
| 132 |
+
interval = sleep(interval)
|
| 133 |
+
except ChildProcessError:
|
| 134 |
+
# This has two meanings:
|
| 135 |
+
# - PID is not a child of os.getpid() in which case
|
| 136 |
+
# we keep polling until it's gone
|
| 137 |
+
# - PID never existed in the first place
|
| 138 |
+
# In both cases we'll eventually return None as we
|
| 139 |
+
# can't determine its exit status code.
|
| 140 |
+
while _pid_exists(pid):
|
| 141 |
+
interval = sleep(interval)
|
| 142 |
+
return
|
| 143 |
+
else:
|
| 144 |
+
if retpid == 0:
|
| 145 |
+
# WNOHANG flag was used and PID is still running.
|
| 146 |
+
interval = sleep(interval)
|
| 147 |
+
continue
|
| 148 |
+
|
| 149 |
+
if os.WIFEXITED(status):
|
| 150 |
+
# Process terminated normally by calling exit(3) or _exit(2),
|
| 151 |
+
# or by returning from main(). The return value is the
|
| 152 |
+
# positive integer passed to *exit().
|
| 153 |
+
return os.WEXITSTATUS(status)
|
| 154 |
+
elif os.WIFSIGNALED(status):
|
| 155 |
+
# Process exited due to a signal. Return the negative value
|
| 156 |
+
# of that signal.
|
| 157 |
+
return negsig_to_enum(-os.WTERMSIG(status))
|
| 158 |
+
# elif os.WIFSTOPPED(status):
|
| 159 |
+
# # Process was stopped via SIGSTOP or is being traced, and
|
| 160 |
+
# # waitpid() was called with WUNTRACED flag. PID is still
|
| 161 |
+
# # alive. From now on waitpid() will keep returning (0, 0)
|
| 162 |
+
# # until the process state doesn't change.
|
| 163 |
+
# # It may make sense to catch/enable this since stopped PIDs
|
| 164 |
+
# # ignore SIGTERM.
|
| 165 |
+
# interval = sleep(interval)
|
| 166 |
+
# continue
|
| 167 |
+
# elif os.WIFCONTINUED(status):
|
| 168 |
+
# # Process was resumed via SIGCONT and waitpid() was called
|
| 169 |
+
# # with WCONTINUED flag.
|
| 170 |
+
# interval = sleep(interval)
|
| 171 |
+
# continue
|
| 172 |
+
else:
|
| 173 |
+
# Should never happen.
|
| 174 |
+
raise ValueError("unknown process exit status %r" % status)
|
| 175 |
+
|
| 176 |
+
|
| 177 |
+
def disk_usage(path):
|
| 178 |
+
"""Return disk usage associated with path.
|
| 179 |
+
Note: UNIX usually reserves 5% disk space which is not accessible
|
| 180 |
+
by user. In this function "total" and "used" values reflect the
|
| 181 |
+
total and used disk space whereas "free" and "percent" represent
|
| 182 |
+
the "free" and "used percent" user disk space.
|
| 183 |
+
"""
|
| 184 |
+
if PY3:
|
| 185 |
+
st = os.statvfs(path)
|
| 186 |
+
else: # pragma: no cover
|
| 187 |
+
# os.statvfs() does not support unicode on Python 2:
|
| 188 |
+
# - https://github.com/giampaolo/psutil/issues/416
|
| 189 |
+
# - http://bugs.python.org/issue18695
|
| 190 |
+
try:
|
| 191 |
+
st = os.statvfs(path)
|
| 192 |
+
except UnicodeEncodeError:
|
| 193 |
+
if isinstance(path, unicode):
|
| 194 |
+
try:
|
| 195 |
+
path = path.encode(sys.getfilesystemencoding())
|
| 196 |
+
except UnicodeEncodeError:
|
| 197 |
+
pass
|
| 198 |
+
st = os.statvfs(path)
|
| 199 |
+
else:
|
| 200 |
+
raise
|
| 201 |
+
|
| 202 |
+
# Total space which is only available to root (unless changed
|
| 203 |
+
# at system level).
|
| 204 |
+
total = st.f_blocks * st.f_frsize
|
| 205 |
+
# Remaining free space usable by root.
|
| 206 |
+
avail_to_root = st.f_bfree * st.f_frsize
|
| 207 |
+
# Remaining free space usable by user.
|
| 208 |
+
avail_to_user = st.f_bavail * st.f_frsize
|
| 209 |
+
# Total space being used in general.
|
| 210 |
+
used = total - avail_to_root
|
| 211 |
+
if MACOS:
|
| 212 |
+
# see: https://github.com/giampaolo/psutil/pull/2152
|
| 213 |
+
used = _psutil_osx.disk_usage_used(path, used)
|
| 214 |
+
# Total space which is available to user (same as 'total' but
|
| 215 |
+
# for the user).
|
| 216 |
+
total_user = used + avail_to_user
|
| 217 |
+
# User usage percent compared to the total amount of space
|
| 218 |
+
# the user can use. This number would be higher if compared
|
| 219 |
+
# to root's because the user has less space (usually -5%).
|
| 220 |
+
usage_percent_user = usage_percent(used, total_user, round_=1)
|
| 221 |
+
|
| 222 |
+
# NB: the percentage is -5% than what shown by df due to
|
| 223 |
+
# reserved blocks that we are currently not considering:
|
| 224 |
+
# https://github.com/giampaolo/psutil/issues/829#issuecomment-223750462
|
| 225 |
+
return sdiskusage(
|
| 226 |
+
total=total, used=used, free=avail_to_user, percent=usage_percent_user
|
| 227 |
+
)
|
| 228 |
+
|
| 229 |
+
|
| 230 |
+
@memoize
|
| 231 |
+
def get_terminal_map():
|
| 232 |
+
"""Get a map of device-id -> path as a dict.
|
| 233 |
+
Used by Process.terminal().
|
| 234 |
+
"""
|
| 235 |
+
ret = {}
|
| 236 |
+
ls = glob.glob('/dev/tty*') + glob.glob('/dev/pts/*')
|
| 237 |
+
for name in ls:
|
| 238 |
+
assert name not in ret, name
|
| 239 |
+
try:
|
| 240 |
+
ret[os.stat(name).st_rdev] = name
|
| 241 |
+
except FileNotFoundError:
|
| 242 |
+
pass
|
| 243 |
+
return ret
|
.venv/lib/python3.11/site-packages/ray/thirdparty_files/psutil/_pssunos.py
ADDED
|
@@ -0,0 +1,753 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) 2009, Giampaolo Rodola'. All rights reserved.
|
| 2 |
+
# Use of this source code is governed by a BSD-style license that can be
|
| 3 |
+
# found in the LICENSE file.
|
| 4 |
+
|
| 5 |
+
"""Sun OS Solaris platform implementation."""
|
| 6 |
+
|
| 7 |
+
import errno
|
| 8 |
+
import functools
|
| 9 |
+
import os
|
| 10 |
+
import socket
|
| 11 |
+
import subprocess
|
| 12 |
+
import sys
|
| 13 |
+
from collections import namedtuple
|
| 14 |
+
from socket import AF_INET
|
| 15 |
+
|
| 16 |
+
from . import _common
|
| 17 |
+
from . import _psposix
|
| 18 |
+
from . import _psutil_posix as cext_posix
|
| 19 |
+
from . import _psutil_sunos as cext
|
| 20 |
+
from ._common import AF_INET6
|
| 21 |
+
from ._common import AccessDenied
|
| 22 |
+
from ._common import NoSuchProcess
|
| 23 |
+
from ._common import ZombieProcess
|
| 24 |
+
from ._common import debug
|
| 25 |
+
from ._common import get_procfs_path
|
| 26 |
+
from ._common import isfile_strict
|
| 27 |
+
from ._common import memoize_when_activated
|
| 28 |
+
from ._common import sockfam_to_enum
|
| 29 |
+
from ._common import socktype_to_enum
|
| 30 |
+
from ._common import usage_percent
|
| 31 |
+
from ._compat import PY3
|
| 32 |
+
from ._compat import FileNotFoundError
|
| 33 |
+
from ._compat import PermissionError
|
| 34 |
+
from ._compat import ProcessLookupError
|
| 35 |
+
from ._compat import b
|
| 36 |
+
|
| 37 |
+
|
| 38 |
+
__extra__all__ = ["CONN_IDLE", "CONN_BOUND", "PROCFS_PATH"]
|
| 39 |
+
|
| 40 |
+
|
| 41 |
+
# =====================================================================
|
| 42 |
+
# --- globals
|
| 43 |
+
# =====================================================================
|
| 44 |
+
|
| 45 |
+
|
| 46 |
+
PAGE_SIZE = cext_posix.getpagesize()
|
| 47 |
+
AF_LINK = cext_posix.AF_LINK
|
| 48 |
+
IS_64_BIT = sys.maxsize > 2**32
|
| 49 |
+
|
| 50 |
+
CONN_IDLE = "IDLE"
|
| 51 |
+
CONN_BOUND = "BOUND"
|
| 52 |
+
|
| 53 |
+
PROC_STATUSES = {
|
| 54 |
+
cext.SSLEEP: _common.STATUS_SLEEPING,
|
| 55 |
+
cext.SRUN: _common.STATUS_RUNNING,
|
| 56 |
+
cext.SZOMB: _common.STATUS_ZOMBIE,
|
| 57 |
+
cext.SSTOP: _common.STATUS_STOPPED,
|
| 58 |
+
cext.SIDL: _common.STATUS_IDLE,
|
| 59 |
+
cext.SONPROC: _common.STATUS_RUNNING, # same as run
|
| 60 |
+
cext.SWAIT: _common.STATUS_WAITING,
|
| 61 |
+
}
|
| 62 |
+
|
| 63 |
+
TCP_STATUSES = {
|
| 64 |
+
cext.TCPS_ESTABLISHED: _common.CONN_ESTABLISHED,
|
| 65 |
+
cext.TCPS_SYN_SENT: _common.CONN_SYN_SENT,
|
| 66 |
+
cext.TCPS_SYN_RCVD: _common.CONN_SYN_RECV,
|
| 67 |
+
cext.TCPS_FIN_WAIT_1: _common.CONN_FIN_WAIT1,
|
| 68 |
+
cext.TCPS_FIN_WAIT_2: _common.CONN_FIN_WAIT2,
|
| 69 |
+
cext.TCPS_TIME_WAIT: _common.CONN_TIME_WAIT,
|
| 70 |
+
cext.TCPS_CLOSED: _common.CONN_CLOSE,
|
| 71 |
+
cext.TCPS_CLOSE_WAIT: _common.CONN_CLOSE_WAIT,
|
| 72 |
+
cext.TCPS_LAST_ACK: _common.CONN_LAST_ACK,
|
| 73 |
+
cext.TCPS_LISTEN: _common.CONN_LISTEN,
|
| 74 |
+
cext.TCPS_CLOSING: _common.CONN_CLOSING,
|
| 75 |
+
cext.PSUTIL_CONN_NONE: _common.CONN_NONE,
|
| 76 |
+
cext.TCPS_IDLE: CONN_IDLE, # sunos specific
|
| 77 |
+
cext.TCPS_BOUND: CONN_BOUND, # sunos specific
|
| 78 |
+
}
|
| 79 |
+
|
| 80 |
+
proc_info_map = dict(
|
| 81 |
+
ppid=0,
|
| 82 |
+
rss=1,
|
| 83 |
+
vms=2,
|
| 84 |
+
create_time=3,
|
| 85 |
+
nice=4,
|
| 86 |
+
num_threads=5,
|
| 87 |
+
status=6,
|
| 88 |
+
ttynr=7,
|
| 89 |
+
uid=8,
|
| 90 |
+
euid=9,
|
| 91 |
+
gid=10,
|
| 92 |
+
egid=11,
|
| 93 |
+
)
|
| 94 |
+
|
| 95 |
+
|
| 96 |
+
# =====================================================================
|
| 97 |
+
# --- named tuples
|
| 98 |
+
# =====================================================================
|
| 99 |
+
|
| 100 |
+
|
| 101 |
+
# psutil.cpu_times()
|
| 102 |
+
scputimes = namedtuple('scputimes', ['user', 'system', 'idle', 'iowait'])
|
| 103 |
+
# psutil.cpu_times(percpu=True)
|
| 104 |
+
pcputimes = namedtuple(
|
| 105 |
+
'pcputimes', ['user', 'system', 'children_user', 'children_system']
|
| 106 |
+
)
|
| 107 |
+
# psutil.virtual_memory()
|
| 108 |
+
svmem = namedtuple('svmem', ['total', 'available', 'percent', 'used', 'free'])
|
| 109 |
+
# psutil.Process.memory_info()
|
| 110 |
+
pmem = namedtuple('pmem', ['rss', 'vms'])
|
| 111 |
+
pfullmem = pmem
|
| 112 |
+
# psutil.Process.memory_maps(grouped=True)
|
| 113 |
+
pmmap_grouped = namedtuple(
|
| 114 |
+
'pmmap_grouped', ['path', 'rss', 'anonymous', 'locked']
|
| 115 |
+
)
|
| 116 |
+
# psutil.Process.memory_maps(grouped=False)
|
| 117 |
+
pmmap_ext = namedtuple(
|
| 118 |
+
'pmmap_ext', 'addr perms ' + ' '.join(pmmap_grouped._fields)
|
| 119 |
+
)
|
| 120 |
+
|
| 121 |
+
|
| 122 |
+
# =====================================================================
|
| 123 |
+
# --- memory
|
| 124 |
+
# =====================================================================
|
| 125 |
+
|
| 126 |
+
|
| 127 |
+
def virtual_memory():
|
| 128 |
+
"""Report virtual memory metrics."""
|
| 129 |
+
# we could have done this with kstat, but IMHO this is good enough
|
| 130 |
+
total = os.sysconf('SC_PHYS_PAGES') * PAGE_SIZE
|
| 131 |
+
# note: there's no difference on Solaris
|
| 132 |
+
free = avail = os.sysconf('SC_AVPHYS_PAGES') * PAGE_SIZE
|
| 133 |
+
used = total - free
|
| 134 |
+
percent = usage_percent(used, total, round_=1)
|
| 135 |
+
return svmem(total, avail, percent, used, free)
|
| 136 |
+
|
| 137 |
+
|
| 138 |
+
def swap_memory():
|
| 139 |
+
"""Report swap memory metrics."""
|
| 140 |
+
sin, sout = cext.swap_mem()
|
| 141 |
+
# XXX
|
| 142 |
+
# we are supposed to get total/free by doing so:
|
| 143 |
+
# http://cvs.opensolaris.org/source/xref/onnv/onnv-gate/
|
| 144 |
+
# usr/src/cmd/swap/swap.c
|
| 145 |
+
# ...nevertheless I can't manage to obtain the same numbers as 'swap'
|
| 146 |
+
# cmdline utility, so let's parse its output (sigh!)
|
| 147 |
+
p = subprocess.Popen(
|
| 148 |
+
[
|
| 149 |
+
'/usr/bin/env',
|
| 150 |
+
'PATH=/usr/sbin:/sbin:%s' % os.environ['PATH'],
|
| 151 |
+
'swap',
|
| 152 |
+
'-l',
|
| 153 |
+
],
|
| 154 |
+
stdout=subprocess.PIPE,
|
| 155 |
+
)
|
| 156 |
+
stdout, _ = p.communicate()
|
| 157 |
+
if PY3:
|
| 158 |
+
stdout = stdout.decode(sys.stdout.encoding)
|
| 159 |
+
if p.returncode != 0:
|
| 160 |
+
raise RuntimeError("'swap -l' failed (retcode=%s)" % p.returncode)
|
| 161 |
+
|
| 162 |
+
lines = stdout.strip().split('\n')[1:]
|
| 163 |
+
if not lines:
|
| 164 |
+
msg = 'no swap device(s) configured'
|
| 165 |
+
raise RuntimeError(msg)
|
| 166 |
+
total = free = 0
|
| 167 |
+
for line in lines:
|
| 168 |
+
line = line.split()
|
| 169 |
+
t, f = line[3:5]
|
| 170 |
+
total += int(int(t) * 512)
|
| 171 |
+
free += int(int(f) * 512)
|
| 172 |
+
used = total - free
|
| 173 |
+
percent = usage_percent(used, total, round_=1)
|
| 174 |
+
return _common.sswap(
|
| 175 |
+
total, used, free, percent, sin * PAGE_SIZE, sout * PAGE_SIZE
|
| 176 |
+
)
|
| 177 |
+
|
| 178 |
+
|
| 179 |
+
# =====================================================================
|
| 180 |
+
# --- CPU
|
| 181 |
+
# =====================================================================
|
| 182 |
+
|
| 183 |
+
|
| 184 |
+
def cpu_times():
|
| 185 |
+
"""Return system-wide CPU times as a named tuple."""
|
| 186 |
+
ret = cext.per_cpu_times()
|
| 187 |
+
return scputimes(*[sum(x) for x in zip(*ret)])
|
| 188 |
+
|
| 189 |
+
|
| 190 |
+
def per_cpu_times():
|
| 191 |
+
"""Return system per-CPU times as a list of named tuples."""
|
| 192 |
+
ret = cext.per_cpu_times()
|
| 193 |
+
return [scputimes(*x) for x in ret]
|
| 194 |
+
|
| 195 |
+
|
| 196 |
+
def cpu_count_logical():
|
| 197 |
+
"""Return the number of logical CPUs in the system."""
|
| 198 |
+
try:
|
| 199 |
+
return os.sysconf("SC_NPROCESSORS_ONLN")
|
| 200 |
+
except ValueError:
|
| 201 |
+
# mimic os.cpu_count() behavior
|
| 202 |
+
return None
|
| 203 |
+
|
| 204 |
+
|
| 205 |
+
def cpu_count_cores():
|
| 206 |
+
"""Return the number of CPU cores in the system."""
|
| 207 |
+
return cext.cpu_count_cores()
|
| 208 |
+
|
| 209 |
+
|
| 210 |
+
def cpu_stats():
|
| 211 |
+
"""Return various CPU stats as a named tuple."""
|
| 212 |
+
ctx_switches, interrupts, syscalls, _traps = cext.cpu_stats()
|
| 213 |
+
soft_interrupts = 0
|
| 214 |
+
return _common.scpustats(
|
| 215 |
+
ctx_switches, interrupts, soft_interrupts, syscalls
|
| 216 |
+
)
|
| 217 |
+
|
| 218 |
+
|
| 219 |
+
# =====================================================================
|
| 220 |
+
# --- disks
|
| 221 |
+
# =====================================================================
|
| 222 |
+
|
| 223 |
+
|
| 224 |
+
disk_io_counters = cext.disk_io_counters
|
| 225 |
+
disk_usage = _psposix.disk_usage
|
| 226 |
+
|
| 227 |
+
|
| 228 |
+
def disk_partitions(all=False):
|
| 229 |
+
"""Return system disk partitions."""
|
| 230 |
+
# TODO - the filtering logic should be better checked so that
|
| 231 |
+
# it tries to reflect 'df' as much as possible
|
| 232 |
+
retlist = []
|
| 233 |
+
partitions = cext.disk_partitions()
|
| 234 |
+
for partition in partitions:
|
| 235 |
+
device, mountpoint, fstype, opts = partition
|
| 236 |
+
if device == 'none':
|
| 237 |
+
device = ''
|
| 238 |
+
if not all:
|
| 239 |
+
# Differently from, say, Linux, we don't have a list of
|
| 240 |
+
# common fs types so the best we can do, AFAIK, is to
|
| 241 |
+
# filter by filesystem having a total size > 0.
|
| 242 |
+
try:
|
| 243 |
+
if not disk_usage(mountpoint).total:
|
| 244 |
+
continue
|
| 245 |
+
except OSError as err:
|
| 246 |
+
# https://github.com/giampaolo/psutil/issues/1674
|
| 247 |
+
debug("skipping %r: %s" % (mountpoint, err))
|
| 248 |
+
continue
|
| 249 |
+
ntuple = _common.sdiskpart(device, mountpoint, fstype, opts)
|
| 250 |
+
retlist.append(ntuple)
|
| 251 |
+
return retlist
|
| 252 |
+
|
| 253 |
+
|
| 254 |
+
# =====================================================================
|
| 255 |
+
# --- network
|
| 256 |
+
# =====================================================================
|
| 257 |
+
|
| 258 |
+
|
| 259 |
+
net_io_counters = cext.net_io_counters
|
| 260 |
+
net_if_addrs = cext_posix.net_if_addrs
|
| 261 |
+
|
| 262 |
+
|
| 263 |
+
def net_connections(kind, _pid=-1):
|
| 264 |
+
"""Return socket connections. If pid == -1 return system-wide
|
| 265 |
+
connections (as opposed to connections opened by one process only).
|
| 266 |
+
Only INET sockets are returned (UNIX are not).
|
| 267 |
+
"""
|
| 268 |
+
cmap = _common.conn_tmap.copy()
|
| 269 |
+
if _pid == -1:
|
| 270 |
+
cmap.pop('unix', 0)
|
| 271 |
+
if kind not in cmap:
|
| 272 |
+
raise ValueError(
|
| 273 |
+
"invalid %r kind argument; choose between %s"
|
| 274 |
+
% (kind, ', '.join([repr(x) for x in cmap]))
|
| 275 |
+
)
|
| 276 |
+
families, types = _common.conn_tmap[kind]
|
| 277 |
+
rawlist = cext.net_connections(_pid)
|
| 278 |
+
ret = set()
|
| 279 |
+
for item in rawlist:
|
| 280 |
+
fd, fam, type_, laddr, raddr, status, pid = item
|
| 281 |
+
if fam not in families:
|
| 282 |
+
continue
|
| 283 |
+
if type_ not in types:
|
| 284 |
+
continue
|
| 285 |
+
# TODO: refactor and use _common.conn_to_ntuple.
|
| 286 |
+
if fam in {AF_INET, AF_INET6}:
|
| 287 |
+
if laddr:
|
| 288 |
+
laddr = _common.addr(*laddr)
|
| 289 |
+
if raddr:
|
| 290 |
+
raddr = _common.addr(*raddr)
|
| 291 |
+
status = TCP_STATUSES[status]
|
| 292 |
+
fam = sockfam_to_enum(fam)
|
| 293 |
+
type_ = socktype_to_enum(type_)
|
| 294 |
+
if _pid == -1:
|
| 295 |
+
nt = _common.sconn(fd, fam, type_, laddr, raddr, status, pid)
|
| 296 |
+
else:
|
| 297 |
+
nt = _common.pconn(fd, fam, type_, laddr, raddr, status)
|
| 298 |
+
ret.add(nt)
|
| 299 |
+
return list(ret)
|
| 300 |
+
|
| 301 |
+
|
| 302 |
+
def net_if_stats():
|
| 303 |
+
"""Get NIC stats (isup, duplex, speed, mtu)."""
|
| 304 |
+
ret = cext.net_if_stats()
|
| 305 |
+
for name, items in ret.items():
|
| 306 |
+
isup, duplex, speed, mtu = items
|
| 307 |
+
if hasattr(_common, 'NicDuplex'):
|
| 308 |
+
duplex = _common.NicDuplex(duplex)
|
| 309 |
+
ret[name] = _common.snicstats(isup, duplex, speed, mtu, '')
|
| 310 |
+
return ret
|
| 311 |
+
|
| 312 |
+
|
| 313 |
+
# =====================================================================
|
| 314 |
+
# --- other system functions
|
| 315 |
+
# =====================================================================
|
| 316 |
+
|
| 317 |
+
|
| 318 |
+
def boot_time():
|
| 319 |
+
"""The system boot time expressed in seconds since the epoch."""
|
| 320 |
+
return cext.boot_time()
|
| 321 |
+
|
| 322 |
+
|
| 323 |
+
def users():
|
| 324 |
+
"""Return currently connected users as a list of namedtuples."""
|
| 325 |
+
retlist = []
|
| 326 |
+
rawlist = cext.users()
|
| 327 |
+
localhost = (':0.0', ':0')
|
| 328 |
+
for item in rawlist:
|
| 329 |
+
user, tty, hostname, tstamp, user_process, pid = item
|
| 330 |
+
# note: the underlying C function includes entries about
|
| 331 |
+
# system boot, run level and others. We might want
|
| 332 |
+
# to use them in the future.
|
| 333 |
+
if not user_process:
|
| 334 |
+
continue
|
| 335 |
+
if hostname in localhost:
|
| 336 |
+
hostname = 'localhost'
|
| 337 |
+
nt = _common.suser(user, tty, hostname, tstamp, pid)
|
| 338 |
+
retlist.append(nt)
|
| 339 |
+
return retlist
|
| 340 |
+
|
| 341 |
+
|
| 342 |
+
# =====================================================================
|
| 343 |
+
# --- processes
|
| 344 |
+
# =====================================================================
|
| 345 |
+
|
| 346 |
+
|
| 347 |
+
def pids():
|
| 348 |
+
"""Returns a list of PIDs currently running on the system."""
|
| 349 |
+
return [int(x) for x in os.listdir(b(get_procfs_path())) if x.isdigit()]
|
| 350 |
+
|
| 351 |
+
|
| 352 |
+
def pid_exists(pid):
|
| 353 |
+
"""Check for the existence of a unix pid."""
|
| 354 |
+
return _psposix.pid_exists(pid)
|
| 355 |
+
|
| 356 |
+
|
| 357 |
+
def wrap_exceptions(fun):
|
| 358 |
+
"""Call callable into a try/except clause and translate ENOENT,
|
| 359 |
+
EACCES and EPERM in NoSuchProcess or AccessDenied exceptions.
|
| 360 |
+
"""
|
| 361 |
+
|
| 362 |
+
@functools.wraps(fun)
|
| 363 |
+
def wrapper(self, *args, **kwargs):
|
| 364 |
+
try:
|
| 365 |
+
return fun(self, *args, **kwargs)
|
| 366 |
+
except (FileNotFoundError, ProcessLookupError):
|
| 367 |
+
# ENOENT (no such file or directory) gets raised on open().
|
| 368 |
+
# ESRCH (no such process) can get raised on read() if
|
| 369 |
+
# process is gone in meantime.
|
| 370 |
+
if not pid_exists(self.pid):
|
| 371 |
+
raise NoSuchProcess(self.pid, self._name)
|
| 372 |
+
else:
|
| 373 |
+
raise ZombieProcess(self.pid, self._name, self._ppid)
|
| 374 |
+
except PermissionError:
|
| 375 |
+
raise AccessDenied(self.pid, self._name)
|
| 376 |
+
except OSError:
|
| 377 |
+
if self.pid == 0:
|
| 378 |
+
if 0 in pids():
|
| 379 |
+
raise AccessDenied(self.pid, self._name)
|
| 380 |
+
else:
|
| 381 |
+
raise
|
| 382 |
+
raise
|
| 383 |
+
|
| 384 |
+
return wrapper
|
| 385 |
+
|
| 386 |
+
|
| 387 |
+
class Process:
|
| 388 |
+
"""Wrapper class around underlying C implementation."""
|
| 389 |
+
|
| 390 |
+
__slots__ = ["_cache", "_name", "_ppid", "_procfs_path", "pid"]
|
| 391 |
+
|
| 392 |
+
def __init__(self, pid):
|
| 393 |
+
self.pid = pid
|
| 394 |
+
self._name = None
|
| 395 |
+
self._ppid = None
|
| 396 |
+
self._procfs_path = get_procfs_path()
|
| 397 |
+
|
| 398 |
+
def _assert_alive(self):
|
| 399 |
+
"""Raise NSP if the process disappeared on us."""
|
| 400 |
+
# For those C function who do not raise NSP, possibly returning
|
| 401 |
+
# incorrect or incomplete result.
|
| 402 |
+
os.stat('%s/%s' % (self._procfs_path, self.pid))
|
| 403 |
+
|
| 404 |
+
def oneshot_enter(self):
|
| 405 |
+
self._proc_name_and_args.cache_activate(self)
|
| 406 |
+
self._proc_basic_info.cache_activate(self)
|
| 407 |
+
self._proc_cred.cache_activate(self)
|
| 408 |
+
|
| 409 |
+
def oneshot_exit(self):
|
| 410 |
+
self._proc_name_and_args.cache_deactivate(self)
|
| 411 |
+
self._proc_basic_info.cache_deactivate(self)
|
| 412 |
+
self._proc_cred.cache_deactivate(self)
|
| 413 |
+
|
| 414 |
+
@wrap_exceptions
|
| 415 |
+
@memoize_when_activated
|
| 416 |
+
def _proc_name_and_args(self):
|
| 417 |
+
return cext.proc_name_and_args(self.pid, self._procfs_path)
|
| 418 |
+
|
| 419 |
+
@wrap_exceptions
|
| 420 |
+
@memoize_when_activated
|
| 421 |
+
def _proc_basic_info(self):
|
| 422 |
+
if self.pid == 0 and not os.path.exists(
|
| 423 |
+
'%s/%s/psinfo' % (self._procfs_path, self.pid)
|
| 424 |
+
):
|
| 425 |
+
raise AccessDenied(self.pid)
|
| 426 |
+
ret = cext.proc_basic_info(self.pid, self._procfs_path)
|
| 427 |
+
assert len(ret) == len(proc_info_map)
|
| 428 |
+
return ret
|
| 429 |
+
|
| 430 |
+
@wrap_exceptions
|
| 431 |
+
@memoize_when_activated
|
| 432 |
+
def _proc_cred(self):
|
| 433 |
+
return cext.proc_cred(self.pid, self._procfs_path)
|
| 434 |
+
|
| 435 |
+
@wrap_exceptions
|
| 436 |
+
def name(self):
|
| 437 |
+
# note: max len == 15
|
| 438 |
+
return self._proc_name_and_args()[0]
|
| 439 |
+
|
| 440 |
+
@wrap_exceptions
|
| 441 |
+
def exe(self):
|
| 442 |
+
try:
|
| 443 |
+
return os.readlink(
|
| 444 |
+
"%s/%s/path/a.out" % (self._procfs_path, self.pid)
|
| 445 |
+
)
|
| 446 |
+
except OSError:
|
| 447 |
+
pass # continue and guess the exe name from the cmdline
|
| 448 |
+
# Will be guessed later from cmdline but we want to explicitly
|
| 449 |
+
# invoke cmdline here in order to get an AccessDenied
|
| 450 |
+
# exception if the user has not enough privileges.
|
| 451 |
+
self.cmdline()
|
| 452 |
+
return ""
|
| 453 |
+
|
| 454 |
+
@wrap_exceptions
|
| 455 |
+
def cmdline(self):
|
| 456 |
+
return self._proc_name_and_args()[1].split(' ')
|
| 457 |
+
|
| 458 |
+
@wrap_exceptions
|
| 459 |
+
def environ(self):
|
| 460 |
+
return cext.proc_environ(self.pid, self._procfs_path)
|
| 461 |
+
|
| 462 |
+
@wrap_exceptions
|
| 463 |
+
def create_time(self):
|
| 464 |
+
return self._proc_basic_info()[proc_info_map['create_time']]
|
| 465 |
+
|
| 466 |
+
@wrap_exceptions
|
| 467 |
+
def num_threads(self):
|
| 468 |
+
return self._proc_basic_info()[proc_info_map['num_threads']]
|
| 469 |
+
|
| 470 |
+
@wrap_exceptions
|
| 471 |
+
def nice_get(self):
|
| 472 |
+
# Note #1: getpriority(3) doesn't work for realtime processes.
|
| 473 |
+
# Psinfo is what ps uses, see:
|
| 474 |
+
# https://github.com/giampaolo/psutil/issues/1194
|
| 475 |
+
return self._proc_basic_info()[proc_info_map['nice']]
|
| 476 |
+
|
| 477 |
+
@wrap_exceptions
|
| 478 |
+
def nice_set(self, value):
|
| 479 |
+
if self.pid in {2, 3}:
|
| 480 |
+
# Special case PIDs: internally setpriority(3) return ESRCH
|
| 481 |
+
# (no such process), no matter what.
|
| 482 |
+
# The process actually exists though, as it has a name,
|
| 483 |
+
# creation time, etc.
|
| 484 |
+
raise AccessDenied(self.pid, self._name)
|
| 485 |
+
return cext_posix.setpriority(self.pid, value)
|
| 486 |
+
|
| 487 |
+
@wrap_exceptions
|
| 488 |
+
def ppid(self):
|
| 489 |
+
self._ppid = self._proc_basic_info()[proc_info_map['ppid']]
|
| 490 |
+
return self._ppid
|
| 491 |
+
|
| 492 |
+
@wrap_exceptions
|
| 493 |
+
def uids(self):
|
| 494 |
+
try:
|
| 495 |
+
real, effective, saved, _, _, _ = self._proc_cred()
|
| 496 |
+
except AccessDenied:
|
| 497 |
+
real = self._proc_basic_info()[proc_info_map['uid']]
|
| 498 |
+
effective = self._proc_basic_info()[proc_info_map['euid']]
|
| 499 |
+
saved = None
|
| 500 |
+
return _common.puids(real, effective, saved)
|
| 501 |
+
|
| 502 |
+
@wrap_exceptions
|
| 503 |
+
def gids(self):
|
| 504 |
+
try:
|
| 505 |
+
_, _, _, real, effective, saved = self._proc_cred()
|
| 506 |
+
except AccessDenied:
|
| 507 |
+
real = self._proc_basic_info()[proc_info_map['gid']]
|
| 508 |
+
effective = self._proc_basic_info()[proc_info_map['egid']]
|
| 509 |
+
saved = None
|
| 510 |
+
return _common.puids(real, effective, saved)
|
| 511 |
+
|
| 512 |
+
@wrap_exceptions
|
| 513 |
+
def cpu_times(self):
|
| 514 |
+
try:
|
| 515 |
+
times = cext.proc_cpu_times(self.pid, self._procfs_path)
|
| 516 |
+
except OSError as err:
|
| 517 |
+
if err.errno == errno.EOVERFLOW and not IS_64_BIT:
|
| 518 |
+
# We may get here if we attempt to query a 64bit process
|
| 519 |
+
# with a 32bit python.
|
| 520 |
+
# Error originates from read() and also tools like "cat"
|
| 521 |
+
# fail in the same way (!).
|
| 522 |
+
# Since there simply is no way to determine CPU times we
|
| 523 |
+
# return 0.0 as a fallback. See:
|
| 524 |
+
# https://github.com/giampaolo/psutil/issues/857
|
| 525 |
+
times = (0.0, 0.0, 0.0, 0.0)
|
| 526 |
+
else:
|
| 527 |
+
raise
|
| 528 |
+
return _common.pcputimes(*times)
|
| 529 |
+
|
| 530 |
+
@wrap_exceptions
|
| 531 |
+
def cpu_num(self):
|
| 532 |
+
return cext.proc_cpu_num(self.pid, self._procfs_path)
|
| 533 |
+
|
| 534 |
+
@wrap_exceptions
|
| 535 |
+
def terminal(self):
|
| 536 |
+
procfs_path = self._procfs_path
|
| 537 |
+
hit_enoent = False
|
| 538 |
+
tty = wrap_exceptions(self._proc_basic_info()[proc_info_map['ttynr']])
|
| 539 |
+
if tty != cext.PRNODEV:
|
| 540 |
+
for x in (0, 1, 2, 255):
|
| 541 |
+
try:
|
| 542 |
+
return os.readlink(
|
| 543 |
+
'%s/%d/path/%d' % (procfs_path, self.pid, x)
|
| 544 |
+
)
|
| 545 |
+
except FileNotFoundError:
|
| 546 |
+
hit_enoent = True
|
| 547 |
+
continue
|
| 548 |
+
if hit_enoent:
|
| 549 |
+
self._assert_alive()
|
| 550 |
+
|
| 551 |
+
@wrap_exceptions
|
| 552 |
+
def cwd(self):
|
| 553 |
+
# /proc/PID/path/cwd may not be resolved by readlink() even if
|
| 554 |
+
# it exists (ls shows it). If that's the case and the process
|
| 555 |
+
# is still alive return None (we can return None also on BSD).
|
| 556 |
+
# Reference: http://goo.gl/55XgO
|
| 557 |
+
procfs_path = self._procfs_path
|
| 558 |
+
try:
|
| 559 |
+
return os.readlink("%s/%s/path/cwd" % (procfs_path, self.pid))
|
| 560 |
+
except FileNotFoundError:
|
| 561 |
+
os.stat("%s/%s" % (procfs_path, self.pid)) # raise NSP or AD
|
| 562 |
+
return ""
|
| 563 |
+
|
| 564 |
+
@wrap_exceptions
|
| 565 |
+
def memory_info(self):
|
| 566 |
+
ret = self._proc_basic_info()
|
| 567 |
+
rss = ret[proc_info_map['rss']] * 1024
|
| 568 |
+
vms = ret[proc_info_map['vms']] * 1024
|
| 569 |
+
return pmem(rss, vms)
|
| 570 |
+
|
| 571 |
+
memory_full_info = memory_info
|
| 572 |
+
|
| 573 |
+
@wrap_exceptions
|
| 574 |
+
def status(self):
|
| 575 |
+
code = self._proc_basic_info()[proc_info_map['status']]
|
| 576 |
+
# XXX is '?' legit? (we're not supposed to return it anyway)
|
| 577 |
+
return PROC_STATUSES.get(code, '?')
|
| 578 |
+
|
| 579 |
+
@wrap_exceptions
|
| 580 |
+
def threads(self):
|
| 581 |
+
procfs_path = self._procfs_path
|
| 582 |
+
ret = []
|
| 583 |
+
tids = os.listdir('%s/%d/lwp' % (procfs_path, self.pid))
|
| 584 |
+
hit_enoent = False
|
| 585 |
+
for tid in tids:
|
| 586 |
+
tid = int(tid)
|
| 587 |
+
try:
|
| 588 |
+
utime, stime = cext.query_process_thread(
|
| 589 |
+
self.pid, tid, procfs_path
|
| 590 |
+
)
|
| 591 |
+
except EnvironmentError as err:
|
| 592 |
+
if err.errno == errno.EOVERFLOW and not IS_64_BIT:
|
| 593 |
+
# We may get here if we attempt to query a 64bit process
|
| 594 |
+
# with a 32bit python.
|
| 595 |
+
# Error originates from read() and also tools like "cat"
|
| 596 |
+
# fail in the same way (!).
|
| 597 |
+
# Since there simply is no way to determine CPU times we
|
| 598 |
+
# return 0.0 as a fallback. See:
|
| 599 |
+
# https://github.com/giampaolo/psutil/issues/857
|
| 600 |
+
continue
|
| 601 |
+
# ENOENT == thread gone in meantime
|
| 602 |
+
if err.errno == errno.ENOENT:
|
| 603 |
+
hit_enoent = True
|
| 604 |
+
continue
|
| 605 |
+
raise
|
| 606 |
+
else:
|
| 607 |
+
nt = _common.pthread(tid, utime, stime)
|
| 608 |
+
ret.append(nt)
|
| 609 |
+
if hit_enoent:
|
| 610 |
+
self._assert_alive()
|
| 611 |
+
return ret
|
| 612 |
+
|
| 613 |
+
@wrap_exceptions
|
| 614 |
+
def open_files(self):
|
| 615 |
+
retlist = []
|
| 616 |
+
hit_enoent = False
|
| 617 |
+
procfs_path = self._procfs_path
|
| 618 |
+
pathdir = '%s/%d/path' % (procfs_path, self.pid)
|
| 619 |
+
for fd in os.listdir('%s/%d/fd' % (procfs_path, self.pid)):
|
| 620 |
+
path = os.path.join(pathdir, fd)
|
| 621 |
+
if os.path.islink(path):
|
| 622 |
+
try:
|
| 623 |
+
file = os.readlink(path)
|
| 624 |
+
except FileNotFoundError:
|
| 625 |
+
hit_enoent = True
|
| 626 |
+
continue
|
| 627 |
+
else:
|
| 628 |
+
if isfile_strict(file):
|
| 629 |
+
retlist.append(_common.popenfile(file, int(fd)))
|
| 630 |
+
if hit_enoent:
|
| 631 |
+
self._assert_alive()
|
| 632 |
+
return retlist
|
| 633 |
+
|
| 634 |
+
def _get_unix_sockets(self, pid):
|
| 635 |
+
"""Get UNIX sockets used by process by parsing 'pfiles' output."""
|
| 636 |
+
# TODO: rewrite this in C (...but the damn netstat source code
|
| 637 |
+
# does not include this part! Argh!!)
|
| 638 |
+
cmd = ["pfiles", str(pid)]
|
| 639 |
+
p = subprocess.Popen(
|
| 640 |
+
cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE
|
| 641 |
+
)
|
| 642 |
+
stdout, stderr = p.communicate()
|
| 643 |
+
if PY3:
|
| 644 |
+
stdout, stderr = (
|
| 645 |
+
x.decode(sys.stdout.encoding) for x in (stdout, stderr)
|
| 646 |
+
)
|
| 647 |
+
if p.returncode != 0:
|
| 648 |
+
if 'permission denied' in stderr.lower():
|
| 649 |
+
raise AccessDenied(self.pid, self._name)
|
| 650 |
+
if 'no such process' in stderr.lower():
|
| 651 |
+
raise NoSuchProcess(self.pid, self._name)
|
| 652 |
+
raise RuntimeError("%r command error\n%s" % (cmd, stderr))
|
| 653 |
+
|
| 654 |
+
lines = stdout.split('\n')[2:]
|
| 655 |
+
for i, line in enumerate(lines):
|
| 656 |
+
line = line.lstrip()
|
| 657 |
+
if line.startswith('sockname: AF_UNIX'):
|
| 658 |
+
path = line.split(' ', 2)[2]
|
| 659 |
+
type = lines[i - 2].strip()
|
| 660 |
+
if type == 'SOCK_STREAM':
|
| 661 |
+
type = socket.SOCK_STREAM
|
| 662 |
+
elif type == 'SOCK_DGRAM':
|
| 663 |
+
type = socket.SOCK_DGRAM
|
| 664 |
+
else:
|
| 665 |
+
type = -1
|
| 666 |
+
yield (-1, socket.AF_UNIX, type, path, "", _common.CONN_NONE)
|
| 667 |
+
|
| 668 |
+
@wrap_exceptions
|
| 669 |
+
def net_connections(self, kind='inet'):
|
| 670 |
+
ret = net_connections(kind, _pid=self.pid)
|
| 671 |
+
# The underlying C implementation retrieves all OS connections
|
| 672 |
+
# and filters them by PID. At this point we can't tell whether
|
| 673 |
+
# an empty list means there were no connections for process or
|
| 674 |
+
# process is no longer active so we force NSP in case the PID
|
| 675 |
+
# is no longer there.
|
| 676 |
+
if not ret:
|
| 677 |
+
# will raise NSP if process is gone
|
| 678 |
+
os.stat('%s/%s' % (self._procfs_path, self.pid))
|
| 679 |
+
|
| 680 |
+
# UNIX sockets
|
| 681 |
+
if kind in {'all', 'unix'}:
|
| 682 |
+
ret.extend([
|
| 683 |
+
_common.pconn(*conn)
|
| 684 |
+
for conn in self._get_unix_sockets(self.pid)
|
| 685 |
+
])
|
| 686 |
+
return ret
|
| 687 |
+
|
| 688 |
+
nt_mmap_grouped = namedtuple('mmap', 'path rss anon locked')
|
| 689 |
+
nt_mmap_ext = namedtuple('mmap', 'addr perms path rss anon locked')
|
| 690 |
+
|
| 691 |
+
@wrap_exceptions
|
| 692 |
+
def memory_maps(self):
|
| 693 |
+
def toaddr(start, end):
|
| 694 |
+
return '%s-%s' % (
|
| 695 |
+
hex(start)[2:].strip('L'),
|
| 696 |
+
hex(end)[2:].strip('L'),
|
| 697 |
+
)
|
| 698 |
+
|
| 699 |
+
procfs_path = self._procfs_path
|
| 700 |
+
retlist = []
|
| 701 |
+
try:
|
| 702 |
+
rawlist = cext.proc_memory_maps(self.pid, procfs_path)
|
| 703 |
+
except OSError as err:
|
| 704 |
+
if err.errno == errno.EOVERFLOW and not IS_64_BIT:
|
| 705 |
+
# We may get here if we attempt to query a 64bit process
|
| 706 |
+
# with a 32bit python.
|
| 707 |
+
# Error originates from read() and also tools like "cat"
|
| 708 |
+
# fail in the same way (!).
|
| 709 |
+
# Since there simply is no way to determine CPU times we
|
| 710 |
+
# return 0.0 as a fallback. See:
|
| 711 |
+
# https://github.com/giampaolo/psutil/issues/857
|
| 712 |
+
return []
|
| 713 |
+
else:
|
| 714 |
+
raise
|
| 715 |
+
hit_enoent = False
|
| 716 |
+
for item in rawlist:
|
| 717 |
+
addr, addrsize, perm, name, rss, anon, locked = item
|
| 718 |
+
addr = toaddr(addr, addrsize)
|
| 719 |
+
if not name.startswith('['):
|
| 720 |
+
try:
|
| 721 |
+
name = os.readlink(
|
| 722 |
+
'%s/%s/path/%s' % (procfs_path, self.pid, name)
|
| 723 |
+
)
|
| 724 |
+
except OSError as err:
|
| 725 |
+
if err.errno == errno.ENOENT:
|
| 726 |
+
# sometimes the link may not be resolved by
|
| 727 |
+
# readlink() even if it exists (ls shows it).
|
| 728 |
+
# If that's the case we just return the
|
| 729 |
+
# unresolved link path.
|
| 730 |
+
# This seems an inconsistency with /proc similar
|
| 731 |
+
# to: http://goo.gl/55XgO
|
| 732 |
+
name = '%s/%s/path/%s' % (procfs_path, self.pid, name)
|
| 733 |
+
hit_enoent = True
|
| 734 |
+
else:
|
| 735 |
+
raise
|
| 736 |
+
retlist.append((addr, perm, name, rss, anon, locked))
|
| 737 |
+
if hit_enoent:
|
| 738 |
+
self._assert_alive()
|
| 739 |
+
return retlist
|
| 740 |
+
|
| 741 |
+
@wrap_exceptions
|
| 742 |
+
def num_fds(self):
|
| 743 |
+
return len(os.listdir("%s/%s/fd" % (self._procfs_path, self.pid)))
|
| 744 |
+
|
| 745 |
+
@wrap_exceptions
|
| 746 |
+
def num_ctx_switches(self):
|
| 747 |
+
return _common.pctxsw(
|
| 748 |
+
*cext.proc_num_ctx_switches(self.pid, self._procfs_path)
|
| 749 |
+
)
|
| 750 |
+
|
| 751 |
+
@wrap_exceptions
|
| 752 |
+
def wait(self, timeout=None):
|
| 753 |
+
return _psposix.wait_pid(self.pid, timeout, self._name)
|
.venv/lib/python3.11/site-packages/ray/thirdparty_files/psutil/_psutil_posix.abi3.so
ADDED
|
Binary file (71.6 kB). View file
|
|
|
.venv/lib/python3.11/site-packages/ray/thirdparty_files/psutil/_pswindows.py
ADDED
|
@@ -0,0 +1,1173 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) 2009, Giampaolo Rodola'. All rights reserved.
|
| 2 |
+
# Use of this source code is governed by a BSD-style license that can be
|
| 3 |
+
# found in the LICENSE file.
|
| 4 |
+
|
| 5 |
+
"""Windows platform implementation."""
|
| 6 |
+
|
| 7 |
+
import contextlib
|
| 8 |
+
import errno
|
| 9 |
+
import functools
|
| 10 |
+
import os
|
| 11 |
+
import signal
|
| 12 |
+
import sys
|
| 13 |
+
import time
|
| 14 |
+
from collections import namedtuple
|
| 15 |
+
|
| 16 |
+
from . import _common
|
| 17 |
+
from ._common import ENCODING
|
| 18 |
+
from ._common import ENCODING_ERRS
|
| 19 |
+
from ._common import AccessDenied
|
| 20 |
+
from ._common import NoSuchProcess
|
| 21 |
+
from ._common import TimeoutExpired
|
| 22 |
+
from ._common import conn_tmap
|
| 23 |
+
from ._common import conn_to_ntuple
|
| 24 |
+
from ._common import debug
|
| 25 |
+
from ._common import isfile_strict
|
| 26 |
+
from ._common import memoize
|
| 27 |
+
from ._common import memoize_when_activated
|
| 28 |
+
from ._common import parse_environ_block
|
| 29 |
+
from ._common import usage_percent
|
| 30 |
+
from ._compat import PY3
|
| 31 |
+
from ._compat import long
|
| 32 |
+
from ._compat import lru_cache
|
| 33 |
+
from ._compat import range
|
| 34 |
+
from ._compat import unicode
|
| 35 |
+
from ._psutil_windows import ABOVE_NORMAL_PRIORITY_CLASS
|
| 36 |
+
from ._psutil_windows import BELOW_NORMAL_PRIORITY_CLASS
|
| 37 |
+
from ._psutil_windows import HIGH_PRIORITY_CLASS
|
| 38 |
+
from ._psutil_windows import IDLE_PRIORITY_CLASS
|
| 39 |
+
from ._psutil_windows import NORMAL_PRIORITY_CLASS
|
| 40 |
+
from ._psutil_windows import REALTIME_PRIORITY_CLASS
|
| 41 |
+
|
| 42 |
+
|
| 43 |
+
try:
|
| 44 |
+
from . import _psutil_windows as cext
|
| 45 |
+
except ImportError as err:
|
| 46 |
+
if (
|
| 47 |
+
str(err).lower().startswith("dll load failed")
|
| 48 |
+
and sys.getwindowsversion()[0] < 6
|
| 49 |
+
):
|
| 50 |
+
# We may get here if:
|
| 51 |
+
# 1) we are on an old Windows version
|
| 52 |
+
# 2) psutil was installed via pip + wheel
|
| 53 |
+
# See: https://github.com/giampaolo/psutil/issues/811
|
| 54 |
+
msg = "this Windows version is too old (< Windows Vista); "
|
| 55 |
+
msg += "psutil 3.4.2 is the latest version which supports Windows "
|
| 56 |
+
msg += "2000, XP and 2003 server"
|
| 57 |
+
raise RuntimeError(msg)
|
| 58 |
+
else:
|
| 59 |
+
raise
|
| 60 |
+
|
| 61 |
+
if PY3:
|
| 62 |
+
import enum
|
| 63 |
+
else:
|
| 64 |
+
enum = None
|
| 65 |
+
|
| 66 |
+
# process priority constants, import from __init__.py:
|
| 67 |
+
# http://msdn.microsoft.com/en-us/library/ms686219(v=vs.85).aspx
|
| 68 |
+
# fmt: off
|
| 69 |
+
__extra__all__ = [
|
| 70 |
+
"win_service_iter", "win_service_get",
|
| 71 |
+
# Process priority
|
| 72 |
+
"ABOVE_NORMAL_PRIORITY_CLASS", "BELOW_NORMAL_PRIORITY_CLASS",
|
| 73 |
+
"HIGH_PRIORITY_CLASS", "IDLE_PRIORITY_CLASS", "NORMAL_PRIORITY_CLASS",
|
| 74 |
+
"REALTIME_PRIORITY_CLASS",
|
| 75 |
+
# IO priority
|
| 76 |
+
"IOPRIO_VERYLOW", "IOPRIO_LOW", "IOPRIO_NORMAL", "IOPRIO_HIGH",
|
| 77 |
+
# others
|
| 78 |
+
"CONN_DELETE_TCB", "AF_LINK",
|
| 79 |
+
]
|
| 80 |
+
# fmt: on
|
| 81 |
+
|
| 82 |
+
|
| 83 |
+
# =====================================================================
|
| 84 |
+
# --- globals
|
| 85 |
+
# =====================================================================
|
| 86 |
+
|
| 87 |
+
CONN_DELETE_TCB = "DELETE_TCB"
|
| 88 |
+
ERROR_PARTIAL_COPY = 299
|
| 89 |
+
PYPY = '__pypy__' in sys.builtin_module_names
|
| 90 |
+
|
| 91 |
+
if enum is None:
|
| 92 |
+
AF_LINK = -1
|
| 93 |
+
else:
|
| 94 |
+
AddressFamily = enum.IntEnum('AddressFamily', {'AF_LINK': -1})
|
| 95 |
+
AF_LINK = AddressFamily.AF_LINK
|
| 96 |
+
|
| 97 |
+
TCP_STATUSES = {
|
| 98 |
+
cext.MIB_TCP_STATE_ESTAB: _common.CONN_ESTABLISHED,
|
| 99 |
+
cext.MIB_TCP_STATE_SYN_SENT: _common.CONN_SYN_SENT,
|
| 100 |
+
cext.MIB_TCP_STATE_SYN_RCVD: _common.CONN_SYN_RECV,
|
| 101 |
+
cext.MIB_TCP_STATE_FIN_WAIT1: _common.CONN_FIN_WAIT1,
|
| 102 |
+
cext.MIB_TCP_STATE_FIN_WAIT2: _common.CONN_FIN_WAIT2,
|
| 103 |
+
cext.MIB_TCP_STATE_TIME_WAIT: _common.CONN_TIME_WAIT,
|
| 104 |
+
cext.MIB_TCP_STATE_CLOSED: _common.CONN_CLOSE,
|
| 105 |
+
cext.MIB_TCP_STATE_CLOSE_WAIT: _common.CONN_CLOSE_WAIT,
|
| 106 |
+
cext.MIB_TCP_STATE_LAST_ACK: _common.CONN_LAST_ACK,
|
| 107 |
+
cext.MIB_TCP_STATE_LISTEN: _common.CONN_LISTEN,
|
| 108 |
+
cext.MIB_TCP_STATE_CLOSING: _common.CONN_CLOSING,
|
| 109 |
+
cext.MIB_TCP_STATE_DELETE_TCB: CONN_DELETE_TCB,
|
| 110 |
+
cext.PSUTIL_CONN_NONE: _common.CONN_NONE,
|
| 111 |
+
}
|
| 112 |
+
|
| 113 |
+
if enum is not None:
|
| 114 |
+
|
| 115 |
+
class Priority(enum.IntEnum):
|
| 116 |
+
ABOVE_NORMAL_PRIORITY_CLASS = ABOVE_NORMAL_PRIORITY_CLASS
|
| 117 |
+
BELOW_NORMAL_PRIORITY_CLASS = BELOW_NORMAL_PRIORITY_CLASS
|
| 118 |
+
HIGH_PRIORITY_CLASS = HIGH_PRIORITY_CLASS
|
| 119 |
+
IDLE_PRIORITY_CLASS = IDLE_PRIORITY_CLASS
|
| 120 |
+
NORMAL_PRIORITY_CLASS = NORMAL_PRIORITY_CLASS
|
| 121 |
+
REALTIME_PRIORITY_CLASS = REALTIME_PRIORITY_CLASS
|
| 122 |
+
|
| 123 |
+
globals().update(Priority.__members__)
|
| 124 |
+
|
| 125 |
+
if enum is None:
|
| 126 |
+
IOPRIO_VERYLOW = 0
|
| 127 |
+
IOPRIO_LOW = 1
|
| 128 |
+
IOPRIO_NORMAL = 2
|
| 129 |
+
IOPRIO_HIGH = 3
|
| 130 |
+
else:
|
| 131 |
+
|
| 132 |
+
class IOPriority(enum.IntEnum):
|
| 133 |
+
IOPRIO_VERYLOW = 0
|
| 134 |
+
IOPRIO_LOW = 1
|
| 135 |
+
IOPRIO_NORMAL = 2
|
| 136 |
+
IOPRIO_HIGH = 3
|
| 137 |
+
|
| 138 |
+
globals().update(IOPriority.__members__)
|
| 139 |
+
|
| 140 |
+
pinfo_map = dict(
|
| 141 |
+
num_handles=0,
|
| 142 |
+
ctx_switches=1,
|
| 143 |
+
user_time=2,
|
| 144 |
+
kernel_time=3,
|
| 145 |
+
create_time=4,
|
| 146 |
+
num_threads=5,
|
| 147 |
+
io_rcount=6,
|
| 148 |
+
io_wcount=7,
|
| 149 |
+
io_rbytes=8,
|
| 150 |
+
io_wbytes=9,
|
| 151 |
+
io_count_others=10,
|
| 152 |
+
io_bytes_others=11,
|
| 153 |
+
num_page_faults=12,
|
| 154 |
+
peak_wset=13,
|
| 155 |
+
wset=14,
|
| 156 |
+
peak_paged_pool=15,
|
| 157 |
+
paged_pool=16,
|
| 158 |
+
peak_non_paged_pool=17,
|
| 159 |
+
non_paged_pool=18,
|
| 160 |
+
pagefile=19,
|
| 161 |
+
peak_pagefile=20,
|
| 162 |
+
mem_private=21,
|
| 163 |
+
)
|
| 164 |
+
|
| 165 |
+
|
| 166 |
+
# =====================================================================
|
| 167 |
+
# --- named tuples
|
| 168 |
+
# =====================================================================
|
| 169 |
+
|
| 170 |
+
|
| 171 |
+
# fmt: off
|
| 172 |
+
# psutil.cpu_times()
|
| 173 |
+
scputimes = namedtuple('scputimes',
|
| 174 |
+
['user', 'system', 'idle', 'interrupt', 'dpc'])
|
| 175 |
+
# psutil.virtual_memory()
|
| 176 |
+
svmem = namedtuple('svmem', ['total', 'available', 'percent', 'used', 'free'])
|
| 177 |
+
# psutil.Process.memory_info()
|
| 178 |
+
pmem = namedtuple(
|
| 179 |
+
'pmem', ['rss', 'vms',
|
| 180 |
+
'num_page_faults', 'peak_wset', 'wset', 'peak_paged_pool',
|
| 181 |
+
'paged_pool', 'peak_nonpaged_pool', 'nonpaged_pool',
|
| 182 |
+
'pagefile', 'peak_pagefile', 'private'])
|
| 183 |
+
# psutil.Process.memory_full_info()
|
| 184 |
+
pfullmem = namedtuple('pfullmem', pmem._fields + ('uss', ))
|
| 185 |
+
# psutil.Process.memory_maps(grouped=True)
|
| 186 |
+
pmmap_grouped = namedtuple('pmmap_grouped', ['path', 'rss'])
|
| 187 |
+
# psutil.Process.memory_maps(grouped=False)
|
| 188 |
+
pmmap_ext = namedtuple(
|
| 189 |
+
'pmmap_ext', 'addr perms ' + ' '.join(pmmap_grouped._fields))
|
| 190 |
+
# psutil.Process.io_counters()
|
| 191 |
+
pio = namedtuple('pio', ['read_count', 'write_count',
|
| 192 |
+
'read_bytes', 'write_bytes',
|
| 193 |
+
'other_count', 'other_bytes'])
|
| 194 |
+
# fmt: on
|
| 195 |
+
|
| 196 |
+
|
| 197 |
+
# =====================================================================
|
| 198 |
+
# --- utils
|
| 199 |
+
# =====================================================================
|
| 200 |
+
|
| 201 |
+
|
| 202 |
+
@lru_cache(maxsize=512)
|
| 203 |
+
def convert_dos_path(s):
|
| 204 |
+
r"""Convert paths using native DOS format like:
|
| 205 |
+
"\Device\HarddiskVolume1\Windows\systemew\file.txt"
|
| 206 |
+
into:
|
| 207 |
+
"C:\Windows\systemew\file.txt".
|
| 208 |
+
"""
|
| 209 |
+
rawdrive = '\\'.join(s.split('\\')[:3])
|
| 210 |
+
driveletter = cext.QueryDosDevice(rawdrive)
|
| 211 |
+
remainder = s[len(rawdrive) :]
|
| 212 |
+
return os.path.join(driveletter, remainder)
|
| 213 |
+
|
| 214 |
+
|
| 215 |
+
def py2_strencode(s):
|
| 216 |
+
"""Encode a unicode string to a byte string by using the default fs
|
| 217 |
+
encoding + "replace" error handler.
|
| 218 |
+
"""
|
| 219 |
+
if PY3:
|
| 220 |
+
return s
|
| 221 |
+
if isinstance(s, str):
|
| 222 |
+
return s
|
| 223 |
+
else:
|
| 224 |
+
return s.encode(ENCODING, ENCODING_ERRS)
|
| 225 |
+
|
| 226 |
+
|
| 227 |
+
@memoize
|
| 228 |
+
def getpagesize():
|
| 229 |
+
return cext.getpagesize()
|
| 230 |
+
|
| 231 |
+
|
| 232 |
+
# =====================================================================
|
| 233 |
+
# --- memory
|
| 234 |
+
# =====================================================================
|
| 235 |
+
|
| 236 |
+
|
| 237 |
+
def virtual_memory():
|
| 238 |
+
"""System virtual memory as a namedtuple."""
|
| 239 |
+
mem = cext.virtual_mem()
|
| 240 |
+
totphys, availphys, _totsys, _availsys = mem
|
| 241 |
+
total = totphys
|
| 242 |
+
avail = availphys
|
| 243 |
+
free = availphys
|
| 244 |
+
used = total - avail
|
| 245 |
+
percent = usage_percent((total - avail), total, round_=1)
|
| 246 |
+
return svmem(total, avail, percent, used, free)
|
| 247 |
+
|
| 248 |
+
|
| 249 |
+
def swap_memory():
|
| 250 |
+
"""Swap system memory as a (total, used, free, sin, sout) tuple."""
|
| 251 |
+
mem = cext.virtual_mem()
|
| 252 |
+
|
| 253 |
+
total_phys = mem[0]
|
| 254 |
+
total_system = mem[2]
|
| 255 |
+
|
| 256 |
+
# system memory (commit total/limit) is the sum of physical and swap
|
| 257 |
+
# thus physical memory values need to be subtracted to get swap values
|
| 258 |
+
total = total_system - total_phys
|
| 259 |
+
# commit total is incremented immediately (decrementing free_system)
|
| 260 |
+
# while the corresponding free physical value is not decremented until
|
| 261 |
+
# pages are accessed, so we can't use free system memory for swap.
|
| 262 |
+
# instead, we calculate page file usage based on performance counter
|
| 263 |
+
if total > 0:
|
| 264 |
+
percentswap = cext.swap_percent()
|
| 265 |
+
used = int(0.01 * percentswap * total)
|
| 266 |
+
else:
|
| 267 |
+
percentswap = 0.0
|
| 268 |
+
used = 0
|
| 269 |
+
|
| 270 |
+
free = total - used
|
| 271 |
+
percent = round(percentswap, 1)
|
| 272 |
+
return _common.sswap(total, used, free, percent, 0, 0)
|
| 273 |
+
|
| 274 |
+
|
| 275 |
+
# =====================================================================
|
| 276 |
+
# --- disk
|
| 277 |
+
# =====================================================================
|
| 278 |
+
|
| 279 |
+
|
| 280 |
+
disk_io_counters = cext.disk_io_counters
|
| 281 |
+
|
| 282 |
+
|
| 283 |
+
def disk_usage(path):
|
| 284 |
+
"""Return disk usage associated with path."""
|
| 285 |
+
if PY3 and isinstance(path, bytes):
|
| 286 |
+
# XXX: do we want to use "strict"? Probably yes, in order
|
| 287 |
+
# to fail immediately. After all we are accepting input here...
|
| 288 |
+
path = path.decode(ENCODING, errors="strict")
|
| 289 |
+
total, free = cext.disk_usage(path)
|
| 290 |
+
used = total - free
|
| 291 |
+
percent = usage_percent(used, total, round_=1)
|
| 292 |
+
return _common.sdiskusage(total, used, free, percent)
|
| 293 |
+
|
| 294 |
+
|
| 295 |
+
def disk_partitions(all):
|
| 296 |
+
"""Return disk partitions."""
|
| 297 |
+
rawlist = cext.disk_partitions(all)
|
| 298 |
+
return [_common.sdiskpart(*x) for x in rawlist]
|
| 299 |
+
|
| 300 |
+
|
| 301 |
+
# =====================================================================
|
| 302 |
+
# --- CPU
|
| 303 |
+
# =====================================================================
|
| 304 |
+
|
| 305 |
+
|
| 306 |
+
def cpu_times():
|
| 307 |
+
"""Return system CPU times as a named tuple."""
|
| 308 |
+
user, system, idle = cext.cpu_times()
|
| 309 |
+
# Internally, GetSystemTimes() is used, and it doesn't return
|
| 310 |
+
# interrupt and dpc times. cext.per_cpu_times() does, so we
|
| 311 |
+
# rely on it to get those only.
|
| 312 |
+
percpu_summed = scputimes(*[sum(n) for n in zip(*cext.per_cpu_times())])
|
| 313 |
+
return scputimes(
|
| 314 |
+
user, system, idle, percpu_summed.interrupt, percpu_summed.dpc
|
| 315 |
+
)
|
| 316 |
+
|
| 317 |
+
|
| 318 |
+
def per_cpu_times():
|
| 319 |
+
"""Return system per-CPU times as a list of named tuples."""
|
| 320 |
+
ret = []
|
| 321 |
+
for user, system, idle, interrupt, dpc in cext.per_cpu_times():
|
| 322 |
+
item = scputimes(user, system, idle, interrupt, dpc)
|
| 323 |
+
ret.append(item)
|
| 324 |
+
return ret
|
| 325 |
+
|
| 326 |
+
|
| 327 |
+
def cpu_count_logical():
|
| 328 |
+
"""Return the number of logical CPUs in the system."""
|
| 329 |
+
return cext.cpu_count_logical()
|
| 330 |
+
|
| 331 |
+
|
| 332 |
+
def cpu_count_cores():
|
| 333 |
+
"""Return the number of CPU cores in the system."""
|
| 334 |
+
return cext.cpu_count_cores()
|
| 335 |
+
|
| 336 |
+
|
| 337 |
+
def cpu_stats():
|
| 338 |
+
"""Return CPU statistics."""
|
| 339 |
+
ctx_switches, interrupts, _dpcs, syscalls = cext.cpu_stats()
|
| 340 |
+
soft_interrupts = 0
|
| 341 |
+
return _common.scpustats(
|
| 342 |
+
ctx_switches, interrupts, soft_interrupts, syscalls
|
| 343 |
+
)
|
| 344 |
+
|
| 345 |
+
|
| 346 |
+
def cpu_freq():
|
| 347 |
+
"""Return CPU frequency.
|
| 348 |
+
On Windows per-cpu frequency is not supported.
|
| 349 |
+
"""
|
| 350 |
+
curr, max_ = cext.cpu_freq()
|
| 351 |
+
min_ = 0.0
|
| 352 |
+
return [_common.scpufreq(float(curr), min_, float(max_))]
|
| 353 |
+
|
| 354 |
+
|
| 355 |
+
_loadavg_inititialized = False
|
| 356 |
+
|
| 357 |
+
|
| 358 |
+
def getloadavg():
|
| 359 |
+
"""Return the number of processes in the system run queue averaged
|
| 360 |
+
over the last 1, 5, and 15 minutes respectively as a tuple.
|
| 361 |
+
"""
|
| 362 |
+
global _loadavg_inititialized
|
| 363 |
+
|
| 364 |
+
if not _loadavg_inititialized:
|
| 365 |
+
cext.init_loadavg_counter()
|
| 366 |
+
_loadavg_inititialized = True
|
| 367 |
+
|
| 368 |
+
# Drop to 2 decimal points which is what Linux does
|
| 369 |
+
raw_loads = cext.getloadavg()
|
| 370 |
+
return tuple([round(load, 2) for load in raw_loads])
|
| 371 |
+
|
| 372 |
+
|
| 373 |
+
# =====================================================================
|
| 374 |
+
# --- network
|
| 375 |
+
# =====================================================================
|
| 376 |
+
|
| 377 |
+
|
| 378 |
+
def net_connections(kind, _pid=-1):
|
| 379 |
+
"""Return socket connections. If pid == -1 return system-wide
|
| 380 |
+
connections (as opposed to connections opened by one process only).
|
| 381 |
+
"""
|
| 382 |
+
if kind not in conn_tmap:
|
| 383 |
+
raise ValueError(
|
| 384 |
+
"invalid %r kind argument; choose between %s"
|
| 385 |
+
% (kind, ', '.join([repr(x) for x in conn_tmap]))
|
| 386 |
+
)
|
| 387 |
+
families, types = conn_tmap[kind]
|
| 388 |
+
rawlist = cext.net_connections(_pid, families, types)
|
| 389 |
+
ret = set()
|
| 390 |
+
for item in rawlist:
|
| 391 |
+
fd, fam, type, laddr, raddr, status, pid = item
|
| 392 |
+
nt = conn_to_ntuple(
|
| 393 |
+
fd,
|
| 394 |
+
fam,
|
| 395 |
+
type,
|
| 396 |
+
laddr,
|
| 397 |
+
raddr,
|
| 398 |
+
status,
|
| 399 |
+
TCP_STATUSES,
|
| 400 |
+
pid=pid if _pid == -1 else None,
|
| 401 |
+
)
|
| 402 |
+
ret.add(nt)
|
| 403 |
+
return list(ret)
|
| 404 |
+
|
| 405 |
+
|
| 406 |
+
def net_if_stats():
|
| 407 |
+
"""Get NIC stats (isup, duplex, speed, mtu)."""
|
| 408 |
+
ret = {}
|
| 409 |
+
rawdict = cext.net_if_stats()
|
| 410 |
+
for name, items in rawdict.items():
|
| 411 |
+
if not PY3:
|
| 412 |
+
assert isinstance(name, unicode), type(name)
|
| 413 |
+
name = py2_strencode(name)
|
| 414 |
+
isup, duplex, speed, mtu = items
|
| 415 |
+
if hasattr(_common, 'NicDuplex'):
|
| 416 |
+
duplex = _common.NicDuplex(duplex)
|
| 417 |
+
ret[name] = _common.snicstats(isup, duplex, speed, mtu, '')
|
| 418 |
+
return ret
|
| 419 |
+
|
| 420 |
+
|
| 421 |
+
def net_io_counters():
|
| 422 |
+
"""Return network I/O statistics for every network interface
|
| 423 |
+
installed on the system as a dict of raw tuples.
|
| 424 |
+
"""
|
| 425 |
+
ret = cext.net_io_counters()
|
| 426 |
+
return dict([(py2_strencode(k), v) for k, v in ret.items()])
|
| 427 |
+
|
| 428 |
+
|
| 429 |
+
def net_if_addrs():
|
| 430 |
+
"""Return the addresses associated to each NIC."""
|
| 431 |
+
ret = []
|
| 432 |
+
for items in cext.net_if_addrs():
|
| 433 |
+
items = list(items)
|
| 434 |
+
items[0] = py2_strencode(items[0])
|
| 435 |
+
ret.append(items)
|
| 436 |
+
return ret
|
| 437 |
+
|
| 438 |
+
|
| 439 |
+
# =====================================================================
|
| 440 |
+
# --- sensors
|
| 441 |
+
# =====================================================================
|
| 442 |
+
|
| 443 |
+
|
| 444 |
+
def sensors_battery():
|
| 445 |
+
"""Return battery information."""
|
| 446 |
+
# For constants meaning see:
|
| 447 |
+
# https://msdn.microsoft.com/en-us/library/windows/desktop/
|
| 448 |
+
# aa373232(v=vs.85).aspx
|
| 449 |
+
acline_status, flags, percent, secsleft = cext.sensors_battery()
|
| 450 |
+
power_plugged = acline_status == 1
|
| 451 |
+
no_battery = bool(flags & 128)
|
| 452 |
+
charging = bool(flags & 8)
|
| 453 |
+
|
| 454 |
+
if no_battery:
|
| 455 |
+
return None
|
| 456 |
+
if power_plugged or charging:
|
| 457 |
+
secsleft = _common.POWER_TIME_UNLIMITED
|
| 458 |
+
elif secsleft == -1:
|
| 459 |
+
secsleft = _common.POWER_TIME_UNKNOWN
|
| 460 |
+
|
| 461 |
+
return _common.sbattery(percent, secsleft, power_plugged)
|
| 462 |
+
|
| 463 |
+
|
| 464 |
+
# =====================================================================
|
| 465 |
+
# --- other system functions
|
| 466 |
+
# =====================================================================
|
| 467 |
+
|
| 468 |
+
|
| 469 |
+
_last_btime = 0
|
| 470 |
+
|
| 471 |
+
|
| 472 |
+
def boot_time():
|
| 473 |
+
"""The system boot time expressed in seconds since the epoch."""
|
| 474 |
+
# This dirty hack is to adjust the precision of the returned
|
| 475 |
+
# value which may have a 1 second fluctuation, see:
|
| 476 |
+
# https://github.com/giampaolo/psutil/issues/1007
|
| 477 |
+
global _last_btime
|
| 478 |
+
ret = float(cext.boot_time())
|
| 479 |
+
if abs(ret - _last_btime) <= 1:
|
| 480 |
+
return _last_btime
|
| 481 |
+
else:
|
| 482 |
+
_last_btime = ret
|
| 483 |
+
return ret
|
| 484 |
+
|
| 485 |
+
|
| 486 |
+
def users():
|
| 487 |
+
"""Return currently connected users as a list of namedtuples."""
|
| 488 |
+
retlist = []
|
| 489 |
+
rawlist = cext.users()
|
| 490 |
+
for item in rawlist:
|
| 491 |
+
user, hostname, tstamp = item
|
| 492 |
+
user = py2_strencode(user)
|
| 493 |
+
nt = _common.suser(user, None, hostname, tstamp, None)
|
| 494 |
+
retlist.append(nt)
|
| 495 |
+
return retlist
|
| 496 |
+
|
| 497 |
+
|
| 498 |
+
# =====================================================================
|
| 499 |
+
# --- Windows services
|
| 500 |
+
# =====================================================================
|
| 501 |
+
|
| 502 |
+
|
| 503 |
+
def win_service_iter():
|
| 504 |
+
"""Yields a list of WindowsService instances."""
|
| 505 |
+
for name, display_name in cext.winservice_enumerate():
|
| 506 |
+
yield WindowsService(py2_strencode(name), py2_strencode(display_name))
|
| 507 |
+
|
| 508 |
+
|
| 509 |
+
def win_service_get(name):
|
| 510 |
+
"""Open a Windows service and return it as a WindowsService instance."""
|
| 511 |
+
service = WindowsService(name, None)
|
| 512 |
+
service._display_name = service._query_config()['display_name']
|
| 513 |
+
return service
|
| 514 |
+
|
| 515 |
+
|
| 516 |
+
class WindowsService: # noqa: PLW1641
|
| 517 |
+
"""Represents an installed Windows service."""
|
| 518 |
+
|
| 519 |
+
def __init__(self, name, display_name):
|
| 520 |
+
self._name = name
|
| 521 |
+
self._display_name = display_name
|
| 522 |
+
|
| 523 |
+
def __str__(self):
|
| 524 |
+
details = "(name=%r, display_name=%r)" % (
|
| 525 |
+
self._name,
|
| 526 |
+
self._display_name,
|
| 527 |
+
)
|
| 528 |
+
return "%s%s" % (self.__class__.__name__, details)
|
| 529 |
+
|
| 530 |
+
def __repr__(self):
|
| 531 |
+
return "<%s at %s>" % (self.__str__(), id(self))
|
| 532 |
+
|
| 533 |
+
def __eq__(self, other):
|
| 534 |
+
# Test for equality with another WindosService object based
|
| 535 |
+
# on name.
|
| 536 |
+
if not isinstance(other, WindowsService):
|
| 537 |
+
return NotImplemented
|
| 538 |
+
return self._name == other._name
|
| 539 |
+
|
| 540 |
+
def __ne__(self, other):
|
| 541 |
+
return not self == other
|
| 542 |
+
|
| 543 |
+
def _query_config(self):
|
| 544 |
+
with self._wrap_exceptions():
|
| 545 |
+
display_name, binpath, username, start_type = (
|
| 546 |
+
cext.winservice_query_config(self._name)
|
| 547 |
+
)
|
| 548 |
+
# XXX - update _self.display_name?
|
| 549 |
+
return dict(
|
| 550 |
+
display_name=py2_strencode(display_name),
|
| 551 |
+
binpath=py2_strencode(binpath),
|
| 552 |
+
username=py2_strencode(username),
|
| 553 |
+
start_type=py2_strencode(start_type),
|
| 554 |
+
)
|
| 555 |
+
|
| 556 |
+
def _query_status(self):
|
| 557 |
+
with self._wrap_exceptions():
|
| 558 |
+
status, pid = cext.winservice_query_status(self._name)
|
| 559 |
+
if pid == 0:
|
| 560 |
+
pid = None
|
| 561 |
+
return dict(status=status, pid=pid)
|
| 562 |
+
|
| 563 |
+
@contextlib.contextmanager
|
| 564 |
+
def _wrap_exceptions(self):
|
| 565 |
+
"""Ctx manager which translates bare OSError and WindowsError
|
| 566 |
+
exceptions into NoSuchProcess and AccessDenied.
|
| 567 |
+
"""
|
| 568 |
+
try:
|
| 569 |
+
yield
|
| 570 |
+
except OSError as err:
|
| 571 |
+
if is_permission_err(err):
|
| 572 |
+
msg = (
|
| 573 |
+
"service %r is not querable (not enough privileges)"
|
| 574 |
+
% self._name
|
| 575 |
+
)
|
| 576 |
+
raise AccessDenied(pid=None, name=self._name, msg=msg)
|
| 577 |
+
elif err.winerror in {
|
| 578 |
+
cext.ERROR_INVALID_NAME,
|
| 579 |
+
cext.ERROR_SERVICE_DOES_NOT_EXIST,
|
| 580 |
+
}:
|
| 581 |
+
msg = "service %r does not exist" % self._name
|
| 582 |
+
raise NoSuchProcess(pid=None, name=self._name, msg=msg)
|
| 583 |
+
else:
|
| 584 |
+
raise
|
| 585 |
+
|
| 586 |
+
# config query
|
| 587 |
+
|
| 588 |
+
def name(self):
|
| 589 |
+
"""The service name. This string is how a service is referenced
|
| 590 |
+
and can be passed to win_service_get() to get a new
|
| 591 |
+
WindowsService instance.
|
| 592 |
+
"""
|
| 593 |
+
return self._name
|
| 594 |
+
|
| 595 |
+
def display_name(self):
|
| 596 |
+
"""The service display name. The value is cached when this class
|
| 597 |
+
is instantiated.
|
| 598 |
+
"""
|
| 599 |
+
return self._display_name
|
| 600 |
+
|
| 601 |
+
def binpath(self):
|
| 602 |
+
"""The fully qualified path to the service binary/exe file as
|
| 603 |
+
a string, including command line arguments.
|
| 604 |
+
"""
|
| 605 |
+
return self._query_config()['binpath']
|
| 606 |
+
|
| 607 |
+
def username(self):
|
| 608 |
+
"""The name of the user that owns this service."""
|
| 609 |
+
return self._query_config()['username']
|
| 610 |
+
|
| 611 |
+
def start_type(self):
|
| 612 |
+
"""A string which can either be "automatic", "manual" or
|
| 613 |
+
"disabled".
|
| 614 |
+
"""
|
| 615 |
+
return self._query_config()['start_type']
|
| 616 |
+
|
| 617 |
+
# status query
|
| 618 |
+
|
| 619 |
+
def pid(self):
|
| 620 |
+
"""The process PID, if any, else None. This can be passed
|
| 621 |
+
to Process class to control the service's process.
|
| 622 |
+
"""
|
| 623 |
+
return self._query_status()['pid']
|
| 624 |
+
|
| 625 |
+
def status(self):
|
| 626 |
+
"""Service status as a string."""
|
| 627 |
+
return self._query_status()['status']
|
| 628 |
+
|
| 629 |
+
def description(self):
|
| 630 |
+
"""Service long description."""
|
| 631 |
+
return py2_strencode(cext.winservice_query_descr(self.name()))
|
| 632 |
+
|
| 633 |
+
# utils
|
| 634 |
+
|
| 635 |
+
def as_dict(self):
|
| 636 |
+
"""Utility method retrieving all the information above as a
|
| 637 |
+
dictionary.
|
| 638 |
+
"""
|
| 639 |
+
d = self._query_config()
|
| 640 |
+
d.update(self._query_status())
|
| 641 |
+
d['name'] = self.name()
|
| 642 |
+
d['display_name'] = self.display_name()
|
| 643 |
+
d['description'] = self.description()
|
| 644 |
+
return d
|
| 645 |
+
|
| 646 |
+
# actions
|
| 647 |
+
# XXX: the necessary C bindings for start() and stop() are
|
| 648 |
+
# implemented but for now I prefer not to expose them.
|
| 649 |
+
# I may change my mind in the future. Reasons:
|
| 650 |
+
# - they require Administrator privileges
|
| 651 |
+
# - can't implement a timeout for stop() (unless by using a thread,
|
| 652 |
+
# which sucks)
|
| 653 |
+
# - would require adding ServiceAlreadyStarted and
|
| 654 |
+
# ServiceAlreadyStopped exceptions, adding two new APIs.
|
| 655 |
+
# - we might also want to have modify(), which would basically mean
|
| 656 |
+
# rewriting win32serviceutil.ChangeServiceConfig, which involves a
|
| 657 |
+
# lot of stuff (and API constants which would pollute the API), see:
|
| 658 |
+
# http://pyxr.sourceforge.net/PyXR/c/python24/lib/site-packages/
|
| 659 |
+
# win32/lib/win32serviceutil.py.html#0175
|
| 660 |
+
# - psutil is typically about "read only" monitoring stuff;
|
| 661 |
+
# win_service_* APIs should only be used to retrieve a service and
|
| 662 |
+
# check whether it's running
|
| 663 |
+
|
| 664 |
+
# def start(self, timeout=None):
|
| 665 |
+
# with self._wrap_exceptions():
|
| 666 |
+
# cext.winservice_start(self.name())
|
| 667 |
+
# if timeout:
|
| 668 |
+
# giveup_at = time.time() + timeout
|
| 669 |
+
# while True:
|
| 670 |
+
# if self.status() == "running":
|
| 671 |
+
# return
|
| 672 |
+
# else:
|
| 673 |
+
# if time.time() > giveup_at:
|
| 674 |
+
# raise TimeoutExpired(timeout)
|
| 675 |
+
# else:
|
| 676 |
+
# time.sleep(.1)
|
| 677 |
+
|
| 678 |
+
# def stop(self):
|
| 679 |
+
# # Note: timeout is not implemented because it's just not
|
| 680 |
+
# # possible, see:
|
| 681 |
+
# # http://stackoverflow.com/questions/11973228/
|
| 682 |
+
# with self._wrap_exceptions():
|
| 683 |
+
# return cext.winservice_stop(self.name())
|
| 684 |
+
|
| 685 |
+
|
| 686 |
+
# =====================================================================
|
| 687 |
+
# --- processes
|
| 688 |
+
# =====================================================================
|
| 689 |
+
|
| 690 |
+
|
| 691 |
+
pids = cext.pids
|
| 692 |
+
pid_exists = cext.pid_exists
|
| 693 |
+
ppid_map = cext.ppid_map # used internally by Process.children()
|
| 694 |
+
|
| 695 |
+
|
| 696 |
+
def is_permission_err(exc):
|
| 697 |
+
"""Return True if this is a permission error."""
|
| 698 |
+
assert isinstance(exc, OSError), exc
|
| 699 |
+
if exc.errno in {errno.EPERM, errno.EACCES}:
|
| 700 |
+
return True
|
| 701 |
+
# On Python 2 OSError doesn't always have 'winerror'. Sometimes
|
| 702 |
+
# it does, in which case the original exception was WindowsError
|
| 703 |
+
# (which is a subclass of OSError).
|
| 704 |
+
return getattr(exc, "winerror", -1) in {
|
| 705 |
+
cext.ERROR_ACCESS_DENIED,
|
| 706 |
+
cext.ERROR_PRIVILEGE_NOT_HELD,
|
| 707 |
+
}
|
| 708 |
+
|
| 709 |
+
|
| 710 |
+
def convert_oserror(exc, pid=None, name=None):
|
| 711 |
+
"""Convert OSError into NoSuchProcess or AccessDenied."""
|
| 712 |
+
assert isinstance(exc, OSError), exc
|
| 713 |
+
if is_permission_err(exc):
|
| 714 |
+
return AccessDenied(pid=pid, name=name)
|
| 715 |
+
if exc.errno == errno.ESRCH:
|
| 716 |
+
return NoSuchProcess(pid=pid, name=name)
|
| 717 |
+
raise exc
|
| 718 |
+
|
| 719 |
+
|
| 720 |
+
def wrap_exceptions(fun):
|
| 721 |
+
"""Decorator which converts OSError into NoSuchProcess or AccessDenied."""
|
| 722 |
+
|
| 723 |
+
@functools.wraps(fun)
|
| 724 |
+
def wrapper(self, *args, **kwargs):
|
| 725 |
+
try:
|
| 726 |
+
return fun(self, *args, **kwargs)
|
| 727 |
+
except OSError as err:
|
| 728 |
+
raise convert_oserror(err, pid=self.pid, name=self._name)
|
| 729 |
+
|
| 730 |
+
return wrapper
|
| 731 |
+
|
| 732 |
+
|
| 733 |
+
def retry_error_partial_copy(fun):
|
| 734 |
+
"""Workaround for https://github.com/giampaolo/psutil/issues/875.
|
| 735 |
+
See: https://stackoverflow.com/questions/4457745#4457745.
|
| 736 |
+
"""
|
| 737 |
+
|
| 738 |
+
@functools.wraps(fun)
|
| 739 |
+
def wrapper(self, *args, **kwargs):
|
| 740 |
+
delay = 0.0001
|
| 741 |
+
times = 33
|
| 742 |
+
for _ in range(times): # retries for roughly 1 second
|
| 743 |
+
try:
|
| 744 |
+
return fun(self, *args, **kwargs)
|
| 745 |
+
except WindowsError as _:
|
| 746 |
+
err = _
|
| 747 |
+
if err.winerror == ERROR_PARTIAL_COPY:
|
| 748 |
+
time.sleep(delay)
|
| 749 |
+
delay = min(delay * 2, 0.04)
|
| 750 |
+
continue
|
| 751 |
+
raise
|
| 752 |
+
msg = (
|
| 753 |
+
"{} retried {} times, converted to AccessDenied as it's still"
|
| 754 |
+
"returning {}".format(fun, times, err)
|
| 755 |
+
)
|
| 756 |
+
raise AccessDenied(pid=self.pid, name=self._name, msg=msg)
|
| 757 |
+
|
| 758 |
+
return wrapper
|
| 759 |
+
|
| 760 |
+
|
| 761 |
+
class Process:
|
| 762 |
+
"""Wrapper class around underlying C implementation."""
|
| 763 |
+
|
| 764 |
+
__slots__ = ["_cache", "_name", "_ppid", "pid"]
|
| 765 |
+
|
| 766 |
+
def __init__(self, pid):
|
| 767 |
+
self.pid = pid
|
| 768 |
+
self._name = None
|
| 769 |
+
self._ppid = None
|
| 770 |
+
|
| 771 |
+
# --- oneshot() stuff
|
| 772 |
+
|
| 773 |
+
def oneshot_enter(self):
|
| 774 |
+
self._proc_info.cache_activate(self)
|
| 775 |
+
self.exe.cache_activate(self)
|
| 776 |
+
|
| 777 |
+
def oneshot_exit(self):
|
| 778 |
+
self._proc_info.cache_deactivate(self)
|
| 779 |
+
self.exe.cache_deactivate(self)
|
| 780 |
+
|
| 781 |
+
@memoize_when_activated
|
| 782 |
+
def _proc_info(self):
|
| 783 |
+
"""Return multiple information about this process as a
|
| 784 |
+
raw tuple.
|
| 785 |
+
"""
|
| 786 |
+
ret = cext.proc_info(self.pid)
|
| 787 |
+
assert len(ret) == len(pinfo_map)
|
| 788 |
+
return ret
|
| 789 |
+
|
| 790 |
+
def name(self):
|
| 791 |
+
"""Return process name, which on Windows is always the final
|
| 792 |
+
part of the executable.
|
| 793 |
+
"""
|
| 794 |
+
# This is how PIDs 0 and 4 are always represented in taskmgr
|
| 795 |
+
# and process-hacker.
|
| 796 |
+
if self.pid == 0:
|
| 797 |
+
return "System Idle Process"
|
| 798 |
+
if self.pid == 4:
|
| 799 |
+
return "System"
|
| 800 |
+
return os.path.basename(self.exe())
|
| 801 |
+
|
| 802 |
+
@wrap_exceptions
|
| 803 |
+
@memoize_when_activated
|
| 804 |
+
def exe(self):
|
| 805 |
+
if PYPY:
|
| 806 |
+
try:
|
| 807 |
+
exe = cext.proc_exe(self.pid)
|
| 808 |
+
except WindowsError as err:
|
| 809 |
+
# 24 = ERROR_TOO_MANY_OPEN_FILES. Not sure why this happens
|
| 810 |
+
# (perhaps PyPy's JIT delaying garbage collection of files?).
|
| 811 |
+
if err.errno == 24:
|
| 812 |
+
debug("%r translated into AccessDenied" % err)
|
| 813 |
+
raise AccessDenied(self.pid, self._name)
|
| 814 |
+
raise
|
| 815 |
+
else:
|
| 816 |
+
exe = cext.proc_exe(self.pid)
|
| 817 |
+
if not PY3:
|
| 818 |
+
exe = py2_strencode(exe)
|
| 819 |
+
if exe.startswith('\\'):
|
| 820 |
+
return convert_dos_path(exe)
|
| 821 |
+
return exe # May be "Registry", "MemCompression", ...
|
| 822 |
+
|
| 823 |
+
@wrap_exceptions
|
| 824 |
+
@retry_error_partial_copy
|
| 825 |
+
def cmdline(self):
|
| 826 |
+
if cext.WINVER >= cext.WINDOWS_8_1:
|
| 827 |
+
# PEB method detects cmdline changes but requires more
|
| 828 |
+
# privileges: https://github.com/giampaolo/psutil/pull/1398
|
| 829 |
+
try:
|
| 830 |
+
ret = cext.proc_cmdline(self.pid, use_peb=True)
|
| 831 |
+
except OSError as err:
|
| 832 |
+
if is_permission_err(err):
|
| 833 |
+
ret = cext.proc_cmdline(self.pid, use_peb=False)
|
| 834 |
+
else:
|
| 835 |
+
raise
|
| 836 |
+
else:
|
| 837 |
+
ret = cext.proc_cmdline(self.pid, use_peb=True)
|
| 838 |
+
if PY3:
|
| 839 |
+
return ret
|
| 840 |
+
else:
|
| 841 |
+
return [py2_strencode(s) for s in ret]
|
| 842 |
+
|
| 843 |
+
@wrap_exceptions
|
| 844 |
+
@retry_error_partial_copy
|
| 845 |
+
def environ(self):
|
| 846 |
+
ustr = cext.proc_environ(self.pid)
|
| 847 |
+
if ustr and not PY3:
|
| 848 |
+
assert isinstance(ustr, unicode), type(ustr)
|
| 849 |
+
return parse_environ_block(py2_strencode(ustr))
|
| 850 |
+
|
| 851 |
+
def ppid(self):
|
| 852 |
+
try:
|
| 853 |
+
return ppid_map()[self.pid]
|
| 854 |
+
except KeyError:
|
| 855 |
+
raise NoSuchProcess(self.pid, self._name)
|
| 856 |
+
|
| 857 |
+
def _get_raw_meminfo(self):
|
| 858 |
+
try:
|
| 859 |
+
return cext.proc_memory_info(self.pid)
|
| 860 |
+
except OSError as err:
|
| 861 |
+
if is_permission_err(err):
|
| 862 |
+
# TODO: the C ext can probably be refactored in order
|
| 863 |
+
# to get this from cext.proc_info()
|
| 864 |
+
debug("attempting memory_info() fallback (slower)")
|
| 865 |
+
info = self._proc_info()
|
| 866 |
+
return (
|
| 867 |
+
info[pinfo_map['num_page_faults']],
|
| 868 |
+
info[pinfo_map['peak_wset']],
|
| 869 |
+
info[pinfo_map['wset']],
|
| 870 |
+
info[pinfo_map['peak_paged_pool']],
|
| 871 |
+
info[pinfo_map['paged_pool']],
|
| 872 |
+
info[pinfo_map['peak_non_paged_pool']],
|
| 873 |
+
info[pinfo_map['non_paged_pool']],
|
| 874 |
+
info[pinfo_map['pagefile']],
|
| 875 |
+
info[pinfo_map['peak_pagefile']],
|
| 876 |
+
info[pinfo_map['mem_private']],
|
| 877 |
+
)
|
| 878 |
+
raise
|
| 879 |
+
|
| 880 |
+
@wrap_exceptions
|
| 881 |
+
def memory_info(self):
|
| 882 |
+
# on Windows RSS == WorkingSetSize and VSM == PagefileUsage.
|
| 883 |
+
# Underlying C function returns fields of PROCESS_MEMORY_COUNTERS
|
| 884 |
+
# struct.
|
| 885 |
+
t = self._get_raw_meminfo()
|
| 886 |
+
rss = t[2] # wset
|
| 887 |
+
vms = t[7] # pagefile
|
| 888 |
+
return pmem(*(rss, vms) + t)
|
| 889 |
+
|
| 890 |
+
@wrap_exceptions
|
| 891 |
+
def memory_full_info(self):
|
| 892 |
+
basic_mem = self.memory_info()
|
| 893 |
+
uss = cext.proc_memory_uss(self.pid)
|
| 894 |
+
uss *= getpagesize()
|
| 895 |
+
return pfullmem(*basic_mem + (uss,))
|
| 896 |
+
|
| 897 |
+
def memory_maps(self):
|
| 898 |
+
try:
|
| 899 |
+
raw = cext.proc_memory_maps(self.pid)
|
| 900 |
+
except OSError as err:
|
| 901 |
+
# XXX - can't use wrap_exceptions decorator as we're
|
| 902 |
+
# returning a generator; probably needs refactoring.
|
| 903 |
+
raise convert_oserror(err, self.pid, self._name)
|
| 904 |
+
else:
|
| 905 |
+
for addr, perm, path, rss in raw:
|
| 906 |
+
path = convert_dos_path(path)
|
| 907 |
+
if not PY3:
|
| 908 |
+
path = py2_strencode(path)
|
| 909 |
+
addr = hex(addr)
|
| 910 |
+
yield (addr, perm, path, rss)
|
| 911 |
+
|
| 912 |
+
@wrap_exceptions
|
| 913 |
+
def kill(self):
|
| 914 |
+
return cext.proc_kill(self.pid)
|
| 915 |
+
|
| 916 |
+
@wrap_exceptions
|
| 917 |
+
def send_signal(self, sig):
|
| 918 |
+
if sig == signal.SIGTERM:
|
| 919 |
+
cext.proc_kill(self.pid)
|
| 920 |
+
# py >= 2.7
|
| 921 |
+
elif sig in {
|
| 922 |
+
getattr(signal, "CTRL_C_EVENT", object()),
|
| 923 |
+
getattr(signal, "CTRL_BREAK_EVENT", object()),
|
| 924 |
+
}:
|
| 925 |
+
os.kill(self.pid, sig)
|
| 926 |
+
else:
|
| 927 |
+
msg = (
|
| 928 |
+
"only SIGTERM, CTRL_C_EVENT and CTRL_BREAK_EVENT signals "
|
| 929 |
+
"are supported on Windows"
|
| 930 |
+
)
|
| 931 |
+
raise ValueError(msg)
|
| 932 |
+
|
| 933 |
+
@wrap_exceptions
|
| 934 |
+
def wait(self, timeout=None):
|
| 935 |
+
if timeout is None:
|
| 936 |
+
cext_timeout = cext.INFINITE
|
| 937 |
+
else:
|
| 938 |
+
# WaitForSingleObject() expects time in milliseconds.
|
| 939 |
+
cext_timeout = int(timeout * 1000)
|
| 940 |
+
|
| 941 |
+
timer = getattr(time, 'monotonic', time.time)
|
| 942 |
+
stop_at = timer() + timeout if timeout is not None else None
|
| 943 |
+
|
| 944 |
+
try:
|
| 945 |
+
# Exit code is supposed to come from GetExitCodeProcess().
|
| 946 |
+
# May also be None if OpenProcess() failed with
|
| 947 |
+
# ERROR_INVALID_PARAMETER, meaning PID is already gone.
|
| 948 |
+
exit_code = cext.proc_wait(self.pid, cext_timeout)
|
| 949 |
+
except cext.TimeoutExpired:
|
| 950 |
+
# WaitForSingleObject() returned WAIT_TIMEOUT. Just raise.
|
| 951 |
+
raise TimeoutExpired(timeout, self.pid, self._name)
|
| 952 |
+
except cext.TimeoutAbandoned:
|
| 953 |
+
# WaitForSingleObject() returned WAIT_ABANDONED, see:
|
| 954 |
+
# https://github.com/giampaolo/psutil/issues/1224
|
| 955 |
+
# We'll just rely on the internal polling and return None
|
| 956 |
+
# when the PID disappears. Subprocess module does the same
|
| 957 |
+
# (return None):
|
| 958 |
+
# https://github.com/python/cpython/blob/
|
| 959 |
+
# be50a7b627d0aa37e08fa8e2d5568891f19903ce/
|
| 960 |
+
# Lib/subprocess.py#L1193-L1194
|
| 961 |
+
exit_code = None
|
| 962 |
+
|
| 963 |
+
# At this point WaitForSingleObject() returned WAIT_OBJECT_0,
|
| 964 |
+
# meaning the process is gone. Stupidly there are cases where
|
| 965 |
+
# its PID may still stick around so we do a further internal
|
| 966 |
+
# polling.
|
| 967 |
+
delay = 0.0001
|
| 968 |
+
while True:
|
| 969 |
+
if not pid_exists(self.pid):
|
| 970 |
+
return exit_code
|
| 971 |
+
if stop_at and timer() >= stop_at:
|
| 972 |
+
raise TimeoutExpired(timeout, pid=self.pid, name=self._name)
|
| 973 |
+
time.sleep(delay)
|
| 974 |
+
delay = min(delay * 2, 0.04) # incremental delay
|
| 975 |
+
|
| 976 |
+
@wrap_exceptions
|
| 977 |
+
def username(self):
|
| 978 |
+
if self.pid in {0, 4}:
|
| 979 |
+
return 'NT AUTHORITY\\SYSTEM'
|
| 980 |
+
domain, user = cext.proc_username(self.pid)
|
| 981 |
+
return py2_strencode(domain) + '\\' + py2_strencode(user)
|
| 982 |
+
|
| 983 |
+
@wrap_exceptions
|
| 984 |
+
def create_time(self, fast_only=False):
|
| 985 |
+
# Note: proc_times() not put under oneshot() 'cause create_time()
|
| 986 |
+
# is already cached by the main Process class.
|
| 987 |
+
try:
|
| 988 |
+
_user, _system, created = cext.proc_times(self.pid)
|
| 989 |
+
return created
|
| 990 |
+
except OSError as err:
|
| 991 |
+
if is_permission_err(err):
|
| 992 |
+
if fast_only:
|
| 993 |
+
raise
|
| 994 |
+
debug("attempting create_time() fallback (slower)")
|
| 995 |
+
return self._proc_info()[pinfo_map['create_time']]
|
| 996 |
+
raise
|
| 997 |
+
|
| 998 |
+
@wrap_exceptions
|
| 999 |
+
def num_threads(self):
|
| 1000 |
+
return self._proc_info()[pinfo_map['num_threads']]
|
| 1001 |
+
|
| 1002 |
+
@wrap_exceptions
|
| 1003 |
+
def threads(self):
|
| 1004 |
+
rawlist = cext.proc_threads(self.pid)
|
| 1005 |
+
retlist = []
|
| 1006 |
+
for thread_id, utime, stime in rawlist:
|
| 1007 |
+
ntuple = _common.pthread(thread_id, utime, stime)
|
| 1008 |
+
retlist.append(ntuple)
|
| 1009 |
+
return retlist
|
| 1010 |
+
|
| 1011 |
+
@wrap_exceptions
|
| 1012 |
+
def cpu_times(self):
|
| 1013 |
+
try:
|
| 1014 |
+
user, system, _created = cext.proc_times(self.pid)
|
| 1015 |
+
except OSError as err:
|
| 1016 |
+
if not is_permission_err(err):
|
| 1017 |
+
raise
|
| 1018 |
+
debug("attempting cpu_times() fallback (slower)")
|
| 1019 |
+
info = self._proc_info()
|
| 1020 |
+
user = info[pinfo_map['user_time']]
|
| 1021 |
+
system = info[pinfo_map['kernel_time']]
|
| 1022 |
+
# Children user/system times are not retrievable (set to 0).
|
| 1023 |
+
return _common.pcputimes(user, system, 0.0, 0.0)
|
| 1024 |
+
|
| 1025 |
+
@wrap_exceptions
|
| 1026 |
+
def suspend(self):
|
| 1027 |
+
cext.proc_suspend_or_resume(self.pid, True)
|
| 1028 |
+
|
| 1029 |
+
@wrap_exceptions
|
| 1030 |
+
def resume(self):
|
| 1031 |
+
cext.proc_suspend_or_resume(self.pid, False)
|
| 1032 |
+
|
| 1033 |
+
@wrap_exceptions
|
| 1034 |
+
@retry_error_partial_copy
|
| 1035 |
+
def cwd(self):
|
| 1036 |
+
if self.pid in {0, 4}:
|
| 1037 |
+
raise AccessDenied(self.pid, self._name)
|
| 1038 |
+
# return a normalized pathname since the native C function appends
|
| 1039 |
+
# "\\" at the and of the path
|
| 1040 |
+
path = cext.proc_cwd(self.pid)
|
| 1041 |
+
return py2_strencode(os.path.normpath(path))
|
| 1042 |
+
|
| 1043 |
+
@wrap_exceptions
|
| 1044 |
+
def open_files(self):
|
| 1045 |
+
if self.pid in {0, 4}:
|
| 1046 |
+
return []
|
| 1047 |
+
ret = set()
|
| 1048 |
+
# Filenames come in in native format like:
|
| 1049 |
+
# "\Device\HarddiskVolume1\Windows\systemew\file.txt"
|
| 1050 |
+
# Convert the first part in the corresponding drive letter
|
| 1051 |
+
# (e.g. "C:\") by using Windows's QueryDosDevice()
|
| 1052 |
+
raw_file_names = cext.proc_open_files(self.pid)
|
| 1053 |
+
for _file in raw_file_names:
|
| 1054 |
+
_file = convert_dos_path(_file)
|
| 1055 |
+
if isfile_strict(_file):
|
| 1056 |
+
if not PY3:
|
| 1057 |
+
_file = py2_strencode(_file)
|
| 1058 |
+
ntuple = _common.popenfile(_file, -1)
|
| 1059 |
+
ret.add(ntuple)
|
| 1060 |
+
return list(ret)
|
| 1061 |
+
|
| 1062 |
+
@wrap_exceptions
|
| 1063 |
+
def net_connections(self, kind='inet'):
|
| 1064 |
+
return net_connections(kind, _pid=self.pid)
|
| 1065 |
+
|
| 1066 |
+
@wrap_exceptions
|
| 1067 |
+
def nice_get(self):
|
| 1068 |
+
value = cext.proc_priority_get(self.pid)
|
| 1069 |
+
if enum is not None:
|
| 1070 |
+
value = Priority(value)
|
| 1071 |
+
return value
|
| 1072 |
+
|
| 1073 |
+
@wrap_exceptions
|
| 1074 |
+
def nice_set(self, value):
|
| 1075 |
+
return cext.proc_priority_set(self.pid, value)
|
| 1076 |
+
|
| 1077 |
+
@wrap_exceptions
|
| 1078 |
+
def ionice_get(self):
|
| 1079 |
+
ret = cext.proc_io_priority_get(self.pid)
|
| 1080 |
+
if enum is not None:
|
| 1081 |
+
ret = IOPriority(ret)
|
| 1082 |
+
return ret
|
| 1083 |
+
|
| 1084 |
+
@wrap_exceptions
|
| 1085 |
+
def ionice_set(self, ioclass, value):
|
| 1086 |
+
if value:
|
| 1087 |
+
msg = "value argument not accepted on Windows"
|
| 1088 |
+
raise TypeError(msg)
|
| 1089 |
+
if ioclass not in {
|
| 1090 |
+
IOPRIO_VERYLOW,
|
| 1091 |
+
IOPRIO_LOW,
|
| 1092 |
+
IOPRIO_NORMAL,
|
| 1093 |
+
IOPRIO_HIGH,
|
| 1094 |
+
}:
|
| 1095 |
+
raise ValueError("%s is not a valid priority" % ioclass)
|
| 1096 |
+
cext.proc_io_priority_set(self.pid, ioclass)
|
| 1097 |
+
|
| 1098 |
+
@wrap_exceptions
|
| 1099 |
+
def io_counters(self):
|
| 1100 |
+
try:
|
| 1101 |
+
ret = cext.proc_io_counters(self.pid)
|
| 1102 |
+
except OSError as err:
|
| 1103 |
+
if not is_permission_err(err):
|
| 1104 |
+
raise
|
| 1105 |
+
debug("attempting io_counters() fallback (slower)")
|
| 1106 |
+
info = self._proc_info()
|
| 1107 |
+
ret = (
|
| 1108 |
+
info[pinfo_map['io_rcount']],
|
| 1109 |
+
info[pinfo_map['io_wcount']],
|
| 1110 |
+
info[pinfo_map['io_rbytes']],
|
| 1111 |
+
info[pinfo_map['io_wbytes']],
|
| 1112 |
+
info[pinfo_map['io_count_others']],
|
| 1113 |
+
info[pinfo_map['io_bytes_others']],
|
| 1114 |
+
)
|
| 1115 |
+
return pio(*ret)
|
| 1116 |
+
|
| 1117 |
+
@wrap_exceptions
|
| 1118 |
+
def status(self):
|
| 1119 |
+
suspended = cext.proc_is_suspended(self.pid)
|
| 1120 |
+
if suspended:
|
| 1121 |
+
return _common.STATUS_STOPPED
|
| 1122 |
+
else:
|
| 1123 |
+
return _common.STATUS_RUNNING
|
| 1124 |
+
|
| 1125 |
+
@wrap_exceptions
|
| 1126 |
+
def cpu_affinity_get(self):
|
| 1127 |
+
def from_bitmask(x):
|
| 1128 |
+
return [i for i in range(64) if (1 << i) & x]
|
| 1129 |
+
|
| 1130 |
+
bitmask = cext.proc_cpu_affinity_get(self.pid)
|
| 1131 |
+
return from_bitmask(bitmask)
|
| 1132 |
+
|
| 1133 |
+
@wrap_exceptions
|
| 1134 |
+
def cpu_affinity_set(self, value):
|
| 1135 |
+
def to_bitmask(ls):
|
| 1136 |
+
if not ls:
|
| 1137 |
+
raise ValueError("invalid argument %r" % ls)
|
| 1138 |
+
out = 0
|
| 1139 |
+
for b in ls:
|
| 1140 |
+
out |= 2**b
|
| 1141 |
+
return out
|
| 1142 |
+
|
| 1143 |
+
# SetProcessAffinityMask() states that ERROR_INVALID_PARAMETER
|
| 1144 |
+
# is returned for an invalid CPU but this seems not to be true,
|
| 1145 |
+
# therefore we check CPUs validy beforehand.
|
| 1146 |
+
allcpus = list(range(len(per_cpu_times())))
|
| 1147 |
+
for cpu in value:
|
| 1148 |
+
if cpu not in allcpus:
|
| 1149 |
+
if not isinstance(cpu, (int, long)):
|
| 1150 |
+
raise TypeError(
|
| 1151 |
+
"invalid CPU %r; an integer is required" % cpu
|
| 1152 |
+
)
|
| 1153 |
+
else:
|
| 1154 |
+
raise ValueError("invalid CPU %r" % cpu)
|
| 1155 |
+
|
| 1156 |
+
bitmask = to_bitmask(value)
|
| 1157 |
+
cext.proc_cpu_affinity_set(self.pid, bitmask)
|
| 1158 |
+
|
| 1159 |
+
@wrap_exceptions
|
| 1160 |
+
def num_handles(self):
|
| 1161 |
+
try:
|
| 1162 |
+
return cext.proc_num_handles(self.pid)
|
| 1163 |
+
except OSError as err:
|
| 1164 |
+
if is_permission_err(err):
|
| 1165 |
+
debug("attempting num_handles() fallback (slower)")
|
| 1166 |
+
return self._proc_info()[pinfo_map['num_handles']]
|
| 1167 |
+
raise
|
| 1168 |
+
|
| 1169 |
+
@wrap_exceptions
|
| 1170 |
+
def num_ctx_switches(self):
|
| 1171 |
+
ctx_switches = self._proc_info()[pinfo_map['ctx_switches']]
|
| 1172 |
+
# only voluntary ctx switches are supported
|
| 1173 |
+
return _common.pctxsw(ctx_switches, 0)
|
.venv/lib/python3.11/site-packages/ray/thirdparty_files/setproctitle-1.2.2.dist-info/INSTALLER
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
pip
|
.venv/lib/python3.11/site-packages/ray/thirdparty_files/setproctitle-1.2.2.dist-info/REQUESTED
ADDED
|
File without changes
|
.venv/lib/python3.11/site-packages/ray/thirdparty_files/setproctitle-1.2.2.dist-info/WHEEL
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
Wheel-Version: 1.0
|
| 2 |
+
Generator: setuptools (75.8.0)
|
| 3 |
+
Root-Is-Purelib: false
|
| 4 |
+
Tag: cp311-cp311-linux_x86_64
|
| 5 |
+
|
.venv/lib/python3.11/site-packages/ray/thirdparty_files/setproctitle-1.2.2.dist-info/top_level.txt
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
setproctitle
|
.venv/lib/python3.11/site-packages/ray/thirdparty_files/setproctitle.cpython-311-x86_64-linux-gnu.so
ADDED
|
Binary file (69.1 kB). View file
|
|
|
.venv/lib/python3.11/site-packages/ray/train/v2/_internal/__pycache__/constants.cpython-311.pyc
ADDED
|
Binary file (2.81 kB). View file
|
|
|
.venv/lib/python3.11/site-packages/ray/train/v2/_internal/callbacks/__pycache__/__init__.cpython-311.pyc
ADDED
|
Binary file (565 Bytes). View file
|
|
|
.venv/lib/python3.11/site-packages/ray/train/v2/_internal/execution/__pycache__/__init__.cpython-311.pyc
ADDED
|
Binary file (205 Bytes). View file
|
|
|
.venv/lib/python3.11/site-packages/ray/train/v2/_internal/execution/failure_handling/__init__.py
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# isort: off
|
| 2 |
+
from .failure_policy import FailureDecision, FailurePolicy
|
| 3 |
+
from .default import DefaultFailurePolicy
|
| 4 |
+
from .factory import create_failure_policy
|
| 5 |
+
|
| 6 |
+
# isort: on
|
| 7 |
+
|
| 8 |
+
__all__ = [
|
| 9 |
+
"DefaultFailurePolicy",
|
| 10 |
+
"FailureDecision",
|
| 11 |
+
"FailurePolicy",
|
| 12 |
+
"create_failure_policy",
|
| 13 |
+
]
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
# DO NOT ADD ANYTHING AFTER THIS LINE.
|
.venv/lib/python3.11/site-packages/ray/train/v2/_internal/execution/failure_handling/failure_policy.py
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import abc
|
| 2 |
+
from enum import Enum
|
| 3 |
+
|
| 4 |
+
from ray.train.v2._internal.execution.worker_group import WorkerGroupStatus
|
| 5 |
+
from ray.train.v2.api.config import FailureConfig
|
| 6 |
+
|
| 7 |
+
|
| 8 |
+
class FailureDecision(Enum):
|
| 9 |
+
RESTART = "RESTART"
|
| 10 |
+
RAISE = "RAISE"
|
| 11 |
+
NOOP = "NOOP"
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
class FailurePolicy(abc.ABC):
|
| 15 |
+
"""A policy that determines how to handle user and system failures.
|
| 16 |
+
|
| 17 |
+
This can be used to implement fault tolerance and error recovery.
|
| 18 |
+
"""
|
| 19 |
+
|
| 20 |
+
def __init__(self, failure_config: FailureConfig):
|
| 21 |
+
self.failure_config = failure_config
|
| 22 |
+
|
| 23 |
+
@abc.abstractmethod
|
| 24 |
+
def make_decision(self, worker_group_status: WorkerGroupStatus) -> FailureDecision:
|
| 25 |
+
raise NotImplementedError
|