koichi12 commited on
Commit
c7b7007
·
verified ·
1 Parent(s): ec4d14c

Add files using upload-large-folder tool

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. .venv/lib/python3.11/site-packages/ray/thirdparty_files/colorama-0.4.6.dist-info/INSTALLER +1 -0
  2. .venv/lib/python3.11/site-packages/ray/thirdparty_files/colorama/__init__.py +7 -0
  3. .venv/lib/python3.11/site-packages/ray/thirdparty_files/colorama/__pycache__/__init__.cpython-311.pyc +0 -0
  4. .venv/lib/python3.11/site-packages/ray/thirdparty_files/colorama/__pycache__/ansi.cpython-311.pyc +0 -0
  5. .venv/lib/python3.11/site-packages/ray/thirdparty_files/colorama/__pycache__/ansitowin32.cpython-311.pyc +0 -0
  6. .venv/lib/python3.11/site-packages/ray/thirdparty_files/colorama/__pycache__/initialise.cpython-311.pyc +0 -0
  7. .venv/lib/python3.11/site-packages/ray/thirdparty_files/colorama/__pycache__/win32.cpython-311.pyc +0 -0
  8. .venv/lib/python3.11/site-packages/ray/thirdparty_files/colorama/__pycache__/winterm.cpython-311.pyc +0 -0
  9. .venv/lib/python3.11/site-packages/ray/thirdparty_files/colorama/ansi.py +102 -0
  10. .venv/lib/python3.11/site-packages/ray/thirdparty_files/colorama/ansitowin32.py +277 -0
  11. .venv/lib/python3.11/site-packages/ray/thirdparty_files/colorama/initialise.py +121 -0
  12. .venv/lib/python3.11/site-packages/ray/thirdparty_files/colorama/win32.py +180 -0
  13. .venv/lib/python3.11/site-packages/ray/thirdparty_files/colorama/winterm.py +195 -0
  14. .venv/lib/python3.11/site-packages/ray/thirdparty_files/psutil-6.1.1.dist-info/INSTALLER +1 -0
  15. .venv/lib/python3.11/site-packages/ray/thirdparty_files/psutil-6.1.1.dist-info/LICENSE +29 -0
  16. .venv/lib/python3.11/site-packages/ray/thirdparty_files/psutil-6.1.1.dist-info/METADATA +548 -0
  17. .venv/lib/python3.11/site-packages/ray/thirdparty_files/psutil-6.1.1.dist-info/RECORD +65 -0
  18. .venv/lib/python3.11/site-packages/ray/thirdparty_files/psutil-6.1.1.dist-info/REQUESTED +0 -0
  19. .venv/lib/python3.11/site-packages/ray/thirdparty_files/psutil-6.1.1.dist-info/WHEEL +8 -0
  20. .venv/lib/python3.11/site-packages/ray/thirdparty_files/psutil-6.1.1.dist-info/top_level.txt +1 -0
  21. .venv/lib/python3.11/site-packages/ray/thirdparty_files/psutil/__init__.py +2486 -0
  22. .venv/lib/python3.11/site-packages/ray/thirdparty_files/psutil/__pycache__/__init__.cpython-311.pyc +0 -0
  23. .venv/lib/python3.11/site-packages/ray/thirdparty_files/psutil/__pycache__/_common.cpython-311.pyc +0 -0
  24. .venv/lib/python3.11/site-packages/ray/thirdparty_files/psutil/__pycache__/_compat.cpython-311.pyc +0 -0
  25. .venv/lib/python3.11/site-packages/ray/thirdparty_files/psutil/__pycache__/_psaix.cpython-311.pyc +0 -0
  26. .venv/lib/python3.11/site-packages/ray/thirdparty_files/psutil/__pycache__/_psbsd.cpython-311.pyc +0 -0
  27. .venv/lib/python3.11/site-packages/ray/thirdparty_files/psutil/__pycache__/_psosx.cpython-311.pyc +0 -0
  28. .venv/lib/python3.11/site-packages/ray/thirdparty_files/psutil/__pycache__/_psposix.cpython-311.pyc +0 -0
  29. .venv/lib/python3.11/site-packages/ray/thirdparty_files/psutil/__pycache__/_pssunos.cpython-311.pyc +0 -0
  30. .venv/lib/python3.11/site-packages/ray/thirdparty_files/psutil/__pycache__/_pswindows.cpython-311.pyc +0 -0
  31. .venv/lib/python3.11/site-packages/ray/thirdparty_files/psutil/_common.py +994 -0
  32. .venv/lib/python3.11/site-packages/ray/thirdparty_files/psutil/_compat.py +477 -0
  33. .venv/lib/python3.11/site-packages/ray/thirdparty_files/psutil/_psaix.py +579 -0
  34. .venv/lib/python3.11/site-packages/ray/thirdparty_files/psutil/_psbsd.py +985 -0
  35. .venv/lib/python3.11/site-packages/ray/thirdparty_files/psutil/_pslinux.py +2379 -0
  36. .venv/lib/python3.11/site-packages/ray/thirdparty_files/psutil/_psosx.py +552 -0
  37. .venv/lib/python3.11/site-packages/ray/thirdparty_files/psutil/_psposix.py +243 -0
  38. .venv/lib/python3.11/site-packages/ray/thirdparty_files/psutil/_pssunos.py +753 -0
  39. .venv/lib/python3.11/site-packages/ray/thirdparty_files/psutil/_psutil_posix.abi3.so +0 -0
  40. .venv/lib/python3.11/site-packages/ray/thirdparty_files/psutil/_pswindows.py +1173 -0
  41. .venv/lib/python3.11/site-packages/ray/thirdparty_files/setproctitle-1.2.2.dist-info/INSTALLER +1 -0
  42. .venv/lib/python3.11/site-packages/ray/thirdparty_files/setproctitle-1.2.2.dist-info/REQUESTED +0 -0
  43. .venv/lib/python3.11/site-packages/ray/thirdparty_files/setproctitle-1.2.2.dist-info/WHEEL +5 -0
  44. .venv/lib/python3.11/site-packages/ray/thirdparty_files/setproctitle-1.2.2.dist-info/top_level.txt +1 -0
  45. .venv/lib/python3.11/site-packages/ray/thirdparty_files/setproctitle.cpython-311-x86_64-linux-gnu.so +0 -0
  46. .venv/lib/python3.11/site-packages/ray/train/v2/_internal/__pycache__/constants.cpython-311.pyc +0 -0
  47. .venv/lib/python3.11/site-packages/ray/train/v2/_internal/callbacks/__pycache__/__init__.cpython-311.pyc +0 -0
  48. .venv/lib/python3.11/site-packages/ray/train/v2/_internal/execution/__pycache__/__init__.cpython-311.pyc +0 -0
  49. .venv/lib/python3.11/site-packages/ray/train/v2/_internal/execution/failure_handling/__init__.py +16 -0
  50. .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