Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/psutil-8.0.0-py3.11-linux-x86_64.egg/psutil/_common.py: 31%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

376 statements  

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 

7Note: this module is imported by setup.py, so it should not import 

8psutil or third-party modules. 

9""" 

10 

11import collections 

12import functools 

13import os 

14import socket 

15import stat 

16import sys 

17import threading 

18import warnings 

19from socket import AF_INET 

20from socket import SOCK_DGRAM 

21from socket import SOCK_STREAM 

22 

23try: 

24 from socket import AF_INET6 

25except ImportError: 

26 AF_INET6 = None 

27try: 

28 from socket import AF_UNIX 

29except ImportError: 

30 AF_UNIX = None 

31 

32 

33PSUTIL_DEBUG = bool(os.getenv('PSUTIL_DEBUG')) 

34PSUTIL_TESTING = bool(os.getenv('PSUTIL_TESTING')) 

35_DEFAULT = object() 

36 

37# fmt: off 

38__all__ = [ 

39 # OS constants 

40 'FREEBSD', 'BSD', 'LINUX', 'NETBSD', 'OPENBSD', 'MACOS', 'OSX', 'POSIX', 

41 'SUNOS', 'WINDOWS', 

42 # other constants 

43 'ENCODING', 'ENCODING_ERRS', 'AF_INET6', 

44 # utility functions 

45 'conn_tmap', 'deprecated_method', 'isfile_strict', 

46 'parse_environ_block', 'path_exists_strict', 'usage_percent', 

47 'supports_ipv6', 'sockfam_to_enum', 'socktype_to_enum', "wrap_numbers", 

48 'open_text', 'open_binary', 'cat', 'bcat', 

49 'bytes2human', 'conn_to_ntuple', 'debug', 'warn', 

50 # shell utils 

51 'hilite', 'term_supports_colors', 'print_color', 

52] 

53# fmt: on 

54 

55 

56# =================================================================== 

57# --- OS constants 

58# =================================================================== 

59 

60 

61POSIX = os.name == "posix" 

62WINDOWS = os.name == "nt" 

63LINUX = sys.platform.startswith("linux") 

64MACOS = sys.platform.startswith("darwin") 

65OSX = MACOS # deprecated alias 

66FREEBSD = sys.platform.startswith(("freebsd", "midnightbsd")) 

67OPENBSD = sys.platform.startswith("openbsd") 

68NETBSD = sys.platform.startswith("netbsd") 

69BSD = FREEBSD or OPENBSD or NETBSD 

70SUNOS = sys.platform.startswith(("sunos", "solaris")) 

71AIX = sys.platform.startswith("aix") 

72 

73ENCODING = sys.getfilesystemencoding() 

74ENCODING_ERRS = sys.getfilesystemencodeerrors() 

75 

76 

77# =================================================================== 

78# --- Process.net_connections() 'kind' parameter mapping 

79# =================================================================== 

80 

81 

82conn_tmap = { 

83 "all": ([AF_INET, AF_INET6, AF_UNIX], [SOCK_STREAM, SOCK_DGRAM]), 

84 "tcp": ([AF_INET, AF_INET6], [SOCK_STREAM]), 

85 "tcp4": ([AF_INET], [SOCK_STREAM]), 

86 "udp": ([AF_INET, AF_INET6], [SOCK_DGRAM]), 

87 "udp4": ([AF_INET], [SOCK_DGRAM]), 

88 "inet": ([AF_INET, AF_INET6], [SOCK_STREAM, SOCK_DGRAM]), 

89 "inet4": ([AF_INET], [SOCK_STREAM, SOCK_DGRAM]), 

90 "inet6": ([AF_INET6], [SOCK_STREAM, SOCK_DGRAM]), 

91} 

92 

93if AF_INET6 is not None: 

94 conn_tmap.update({ 

95 "tcp6": ([AF_INET6], [SOCK_STREAM]), 

96 "udp6": ([AF_INET6], [SOCK_DGRAM]), 

97 }) 

98 

99if AF_UNIX is not None and not SUNOS: 

100 conn_tmap.update({"unix": ([AF_UNIX], [SOCK_STREAM, SOCK_DGRAM])}) 

101 

102 

103# ===================================================================== 

104# --- Exceptions 

105# ===================================================================== 

106 

107 

108class Error(Exception): 

109 """Base exception class. All other psutil exceptions inherit 

110 from this one. 

111 """ 

112 

113 __module__ = 'psutil' 

114 

115 def _infodict(self, attrs): 

116 info = {} 

117 for name in attrs: 

118 value = getattr(self, name, None) 

119 if value or (name == "pid" and value == 0): 

120 info[name] = value 

121 return info 

122 

123 def __str__(self): 

124 # invoked on `raise Error` 

125 if info := self._infodict(("pid", "ppid", "name")): 

126 details = "({})".format( 

127 ", ".join([f"{k}={v!r}" for k, v in info.items()]) 

128 ) 

129 else: 

130 details = None 

131 return " ".join([x for x in (getattr(self, "msg", ""), details) if x]) 

132 

133 def __repr__(self): 

134 # invoked on `repr(Error)` 

135 info = self._infodict(("pid", "ppid", "name", "seconds", "msg")) 

136 details = ", ".join([f"{k}={v!r}" for k, v in info.items()]) 

137 return f"psutil.{self.__class__.__name__}({details})" 

138 

139 

140class NoSuchProcess(Error): 

141 """Exception raised when a process with a certain PID doesn't 

142 or no longer exists. 

143 """ 

144 

145 __module__ = 'psutil' 

146 

147 def __init__(self, pid, name=None, msg=None): 

148 Error.__init__(self) 

149 self.pid = pid 

150 self.name = name 

151 self.msg = msg or "process no longer exists" 

152 

153 def __reduce__(self): 

154 return (self.__class__, (self.pid, self.name, self.msg)) 

155 

156 

157class ZombieProcess(NoSuchProcess): 

158 """Exception raised when querying a zombie process. This is 

159 raised on macOS, BSD and Solaris only, and not always: depending 

160 on the query the OS may be able to succeed anyway. 

161 On Linux all zombie processes are querable (hence this is never 

162 raised). Windows doesn't have zombie processes. 

163 """ 

164 

165 __module__ = 'psutil' 

166 

167 def __init__(self, pid, name=None, ppid=None, msg=None): 

168 NoSuchProcess.__init__(self, pid, name, msg) 

169 self.ppid = ppid 

170 self.msg = msg or "PID still exists but it's a zombie" 

171 

172 def __reduce__(self): 

173 return (self.__class__, (self.pid, self.name, self.ppid, self.msg)) 

174 

175 

176class AccessDenied(Error): 

177 """Exception raised when permission to perform an action is denied.""" 

178 

179 __module__ = 'psutil' 

180 

181 def __init__(self, pid=None, name=None, msg=None): 

182 Error.__init__(self) 

183 self.pid = pid 

184 self.name = name 

185 self.msg = msg or "" 

186 

187 def __reduce__(self): 

188 return (self.__class__, (self.pid, self.name, self.msg)) 

189 

190 

191class TimeoutExpired(Error): 

192 """Raised on Process.wait(timeout) if timeout expires and process 

193 is still alive. 

194 """ 

195 

196 __module__ = 'psutil' 

197 

198 def __init__(self, seconds, pid=None, name=None): 

199 Error.__init__(self) 

200 self.seconds = seconds 

201 self.pid = pid 

202 self.name = name 

203 self.msg = f"timeout after {seconds} seconds" 

204 

205 def __reduce__(self): 

206 return (self.__class__, (self.seconds, self.pid, self.name)) 

207 

208 

209# =================================================================== 

210# --- utils 

211# =================================================================== 

212 

213 

214def usage_percent(used, total, round_=None): 

215 """Calculate percentage usage of 'used' against 'total'.""" 

216 try: 

217 ret = (float(used) / total) * 100 

218 except ZeroDivisionError: 

219 return 0.0 

220 else: 

221 if round_ is not None: 

222 ret = round(ret, round_) 

223 return ret 

224 

225 

226def memoize_when_activated(fun): 

227 """A memoize decorator which is disabled by default. It can be 

228 activated and deactivated on request. 

229 For efficiency reasons it can be used only against class methods 

230 accepting no arguments. 

231 

232 >>> class Foo: 

233 ... @memoize_when_activated 

234 ... def foo() 

235 ... print(1) 

236 ... 

237 >>> f = Foo() 

238 >>> # deactivated (default) 

239 >>> foo() 

240 1 

241 >>> foo() 

242 1 

243 >>> 

244 >>> # activated 

245 >>> foo.cache_activate(self) 

246 >>> foo() 

247 1 

248 >>> foo() 

249 >>> foo() 

250 >>> 

251 """ 

252 

253 @functools.wraps(fun) 

254 def wrapper(self): 

255 try: 

256 # case 1: we previously entered oneshot() ctx 

257 ret = self._cache[fun] 

258 except AttributeError: 

259 # case 2: we never entered oneshot() ctx 

260 try: 

261 return fun(self) 

262 except Exception as err: 

263 raise err from None 

264 except KeyError: 

265 # case 3: we entered oneshot() ctx but there's no cache 

266 # for this entry yet 

267 try: 

268 ret = fun(self) 

269 except Exception as err: 

270 raise err from None 

271 try: 

272 self._cache[fun] = ret 

273 except AttributeError: 

274 # multi-threading race condition, see: 

275 # https://github.com/giampaolo/psutil/issues/1948 

276 pass 

277 return ret 

278 

279 def cache_activate(proc): 

280 """Activate cache. Expects a Process instance. Cache will be 

281 stored as a "_cache" instance attribute. 

282 """ 

283 proc._cache = {} 

284 

285 def cache_deactivate(proc): 

286 """Deactivate and clear cache.""" 

287 try: 

288 del proc._cache 

289 except AttributeError: 

290 pass 

291 

292 wrapper.cache_activate = cache_activate 

293 wrapper.cache_deactivate = cache_deactivate 

294 return wrapper 

295 

296 

297def isfile_strict(path): 

298 """Same as os.path.isfile() but does not swallow EACCES / EPERM 

299 exceptions, see: 

300 http://mail.python.org/pipermail/python-dev/2012-June/120787.html. 

301 """ 

302 try: 

303 st = os.stat(path) 

304 except PermissionError: 

305 raise 

306 except OSError: 

307 return False 

308 else: 

309 return stat.S_ISREG(st.st_mode) 

310 

311 

312def path_exists_strict(path): 

313 """Same as os.path.exists() but does not swallow EACCES / EPERM 

314 exceptions. See: 

315 http://mail.python.org/pipermail/python-dev/2012-June/120787.html. 

316 """ 

317 try: 

318 os.stat(path) 

319 except PermissionError: 

320 raise 

321 except OSError: 

322 return False 

323 else: 

324 return True 

325 

326 

327def supports_ipv6(): 

328 """Return True if IPv6 is supported on this platform.""" 

329 if not socket.has_ipv6 or AF_INET6 is None: 

330 return False 

331 try: 

332 with socket.socket(AF_INET6, socket.SOCK_STREAM) as sock: 

333 sock.bind(("::1", 0)) 

334 return True 

335 except OSError: 

336 return False 

337 

338 

339def parse_environ_block(data): 

340 """Parse a C environ block of environment variables into a dictionary.""" 

341 # The block is usually raw data from the target process. It might contain 

342 # trailing garbage and lines that do not look like assignments. 

343 ret = {} 

344 pos = 0 

345 

346 # localize global variable to speed up access. 

347 WINDOWS_ = WINDOWS 

348 while True: 

349 next_pos = data.find("\0", pos) 

350 # nul byte at the beginning or double nul byte means finish 

351 if next_pos <= pos: 

352 break 

353 # there might not be an equals sign 

354 equal_pos = data.find("=", pos, next_pos) 

355 if equal_pos > pos: 

356 key = data[pos:equal_pos] 

357 value = data[equal_pos + 1 : next_pos] 

358 # Windows expects environment variables to be uppercase only 

359 if WINDOWS_: 

360 key = key.upper() 

361 ret[key] = value 

362 pos = next_pos + 1 

363 

364 return ret 

365 

366 

367def sockfam_to_enum(num): 

368 """Convert a numeric socket family value to an IntEnum member. 

369 If it's not a known member, return the numeric value itself. 

370 """ 

371 try: 

372 return socket.AddressFamily(num) 

373 except ValueError: 

374 return num 

375 

376 

377def socktype_to_enum(num): 

378 """Convert a numeric socket type value to an IntEnum member. 

379 If it's not a known member, return the numeric value itself. 

380 """ 

381 try: 

382 return socket.SocketKind(num) 

383 except ValueError: 

384 return num 

385 

386 

387def conn_to_ntuple(fd, fam, type_, laddr, raddr, status, status_map, pid=None): 

388 """Convert a raw connection tuple to a proper ntuple.""" 

389 from . import _ntuples as ntp 

390 from ._enums import ConnectionStatus 

391 

392 if fam in {socket.AF_INET, AF_INET6}: 

393 if laddr: 

394 laddr = ntp.addr(*laddr) 

395 if raddr: 

396 raddr = ntp.addr(*raddr) 

397 if type_ == socket.SOCK_STREAM and fam in {AF_INET, AF_INET6}: 

398 status = status_map.get(status, ConnectionStatus.CONN_NONE) 

399 else: 

400 status = ConnectionStatus.CONN_NONE # ignore whatever C returned to us 

401 fam = sockfam_to_enum(fam) 

402 type_ = socktype_to_enum(type_) 

403 if pid is None: 

404 return ntp.pconn(fd, fam, type_, laddr, raddr, status) 

405 else: 

406 return ntp.sconn(fd, fam, type_, laddr, raddr, status, pid) 

407 

408 

409def broadcast_addr(addr): 

410 """Given the address ntuple returned by ``net_if_addrs()`` 

411 calculates the broadcast address. Returns None for a single-host 

412 network (/32 or /128), which has no broadcast address. 

413 """ 

414 import ipaddress 

415 

416 if not addr.address or not addr.netmask: 

417 return None 

418 if addr.family == socket.AF_INET: 

419 net = ipaddress.IPv4Network( 

420 f"{addr.address}/{addr.netmask}", strict=False 

421 ) 

422 elif addr.family == socket.AF_INET6: 

423 net = ipaddress.IPv6Network( 

424 f"{addr.address}/{addr.netmask}", strict=False 

425 ) 

426 else: 

427 return None 

428 if net.prefixlen == net.max_prefixlen: 

429 return None 

430 return str(net.broadcast_address) 

431 

432 

433def deprecated_method(replacement): 

434 """A decorator which can be used to mark a method as deprecated 

435 'replacement' is the method name which will be called instead. 

436 """ 

437 

438 def outer(fun): 

439 msg = ( 

440 f"{fun.__name__}() is deprecated and will be removed; use" 

441 f" {replacement}() instead" 

442 ) 

443 if fun.__doc__ is None: 

444 fun.__doc__ = msg 

445 

446 @functools.wraps(fun) 

447 def inner(self, *args, **kwargs): 

448 warnings.warn(msg, category=DeprecationWarning, stacklevel=2) 

449 return getattr(self, replacement)(*args, **kwargs) 

450 

451 return inner 

452 

453 return outer 

454 

455 

456class _WrapNumbers: 

457 """Watches numbers so that they don't overflow and wrap 

458 (reset to zero). 

459 """ 

460 

461 def __init__(self): 

462 self.lock = threading.Lock() 

463 self.cache = {} 

464 self.reminders = {} 

465 self.reminder_keys = {} 

466 

467 def _add_dict(self, input_dict, name): 

468 assert name not in self.cache 

469 assert name not in self.reminders 

470 assert name not in self.reminder_keys 

471 self.cache[name] = input_dict 

472 self.reminders[name] = collections.defaultdict(int) 

473 self.reminder_keys[name] = collections.defaultdict(set) 

474 

475 def _remove_dead_reminders(self, input_dict, name): 

476 """In case the number of keys changed between calls (e.g. a 

477 disk disappears) this removes the entry from self.reminders. 

478 """ 

479 old_dict = self.cache[name] 

480 gone_keys = set(old_dict) - set(input_dict) 

481 for gone_key in gone_keys: 

482 for remkey in self.reminder_keys[name][gone_key]: 

483 del self.reminders[name][remkey] 

484 del self.reminder_keys[name][gone_key] 

485 

486 def run(self, input_dict, name): 

487 """Cache dict and sum numbers which overflow and wrap. 

488 Return an updated copy of `input_dict`. 

489 """ 

490 if name not in self.cache: 

491 # This was the first call. 

492 self._add_dict(input_dict, name) 

493 return input_dict 

494 

495 self._remove_dead_reminders(input_dict, name) 

496 

497 old_dict = self.cache[name] 

498 new_dict = {} 

499 for key in input_dict: 

500 input_tuple = input_dict[key] 

501 try: 

502 old_tuple = old_dict[key] 

503 except KeyError: 

504 # The input dict has a new key (e.g. a new disk or NIC) 

505 # which didn't exist in the previous call. 

506 new_dict[key] = input_tuple 

507 continue 

508 

509 bits = [] 

510 for i in range(len(input_tuple)): 

511 input_value = input_tuple[i] 

512 old_value = old_tuple[i] 

513 remkey = (key, i) 

514 if input_value < old_value: 

515 # it wrapped! 

516 self.reminders[name][remkey] += old_value 

517 self.reminder_keys[name][key].add(remkey) 

518 bits.append(input_value + self.reminders[name][remkey]) 

519 

520 new_dict[key] = tuple(bits) 

521 

522 self.cache[name] = input_dict 

523 return new_dict 

524 

525 def cache_clear(self, name=None): 

526 """Clear the internal cache, optionally only for function 'name'.""" 

527 with self.lock: 

528 if name is None: 

529 self.cache.clear() 

530 self.reminders.clear() 

531 self.reminder_keys.clear() 

532 else: 

533 self.cache.pop(name, None) 

534 self.reminders.pop(name, None) 

535 self.reminder_keys.pop(name, None) 

536 

537 def cache_info(self): 

538 """Return internal cache dicts as a tuple of 3 elements.""" 

539 with self.lock: 

540 return (self.cache, self.reminders, self.reminder_keys) 

541 

542 

543def wrap_numbers(input_dict, name): 

544 """Given an `input_dict` and a function `name`, adjust the numbers 

545 which "wrap" (restart from zero) across different calls by adding 

546 "old value" to "new value" and return an updated dict. 

547 """ 

548 with _wn.lock: 

549 return _wn.run(input_dict, name) 

550 

551 

552_wn = _WrapNumbers() 

553wrap_numbers.cache_clear = _wn.cache_clear 

554wrap_numbers.cache_info = _wn.cache_info 

555 

556 

557# The read buffer size for open() builtin. This (also) dictates how 

558# much data we read(2) when iterating over file lines as in: 

559# >>> with open(file) as f: 

560# ... for line in f: 

561# ... ... 

562# Default per-line buffer size for binary files is 1K. For text files 

563# is 8K. We use a bigger buffer (32K) in order to have more consistent 

564# results when reading /proc pseudo files on Linux, see: 

565# https://github.com/giampaolo/psutil/issues/2050 

566# https://github.com/giampaolo/psutil/issues/708 

567FILE_READ_BUFFER_SIZE = 32 * 1024 

568 

569 

570def open_binary(fname): 

571 return open(fname, "rb", buffering=FILE_READ_BUFFER_SIZE) 

572 

573 

574def open_text(fname): 

575 """Open a file in text mode by using the proper FS encoding and 

576 en/decoding error handlers. 

577 """ 

578 # See: 

579 # https://github.com/giampaolo/psutil/issues/675 

580 # https://github.com/giampaolo/psutil/pull/733 

581 fobj = open( # noqa: SIM115 

582 fname, 

583 buffering=FILE_READ_BUFFER_SIZE, 

584 encoding=ENCODING, 

585 errors=ENCODING_ERRS, 

586 ) 

587 try: 

588 # Dictates per-line read(2) buffer size. Defaults is 8k. See: 

589 # https://github.com/giampaolo/psutil/issues/2050#issuecomment-1013387546 

590 fobj._CHUNK_SIZE = FILE_READ_BUFFER_SIZE 

591 except AttributeError: 

592 pass 

593 except Exception: 

594 fobj.close() 

595 raise 

596 

597 return fobj 

598 

599 

600def cat(fname, fallback=_DEFAULT, _open=open_text): 

601 """Read entire file content and return it as a string. File is 

602 opened in text mode. If specified, `fallback` is the value 

603 returned in case of error, either if the file does not exist or 

604 it can't be read(). 

605 """ 

606 if fallback is _DEFAULT: 

607 with _open(fname) as f: 

608 return f.read() 

609 else: 

610 try: 

611 with _open(fname) as f: 

612 return f.read() 

613 except OSError: 

614 return fallback 

615 

616 

617def bcat(fname, fallback=_DEFAULT): 

618 """Same as above but opens file in binary mode.""" 

619 return cat(fname, fallback=fallback, _open=open_binary) 

620 

621 

622def bytes2human(n): 

623 """Convert n bytes to a human-readable string. 

624 

625 >>> bytes2human(10000) 

626 '9.8K' 

627 >>> bytes2human(100001221) 

628 '95.4M' 

629 """ 

630 symbols = ('B', 'K', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y') 

631 prefix = {} 

632 for i, s in enumerate(symbols[1:]): 

633 prefix[s] = 1 << (i + 1) * 10 

634 for symbol in reversed(symbols[1:]): 

635 if abs(n) >= prefix[symbol]: 

636 value = float(n) / prefix[symbol] 

637 return f"{value:.1f}{symbol}" 

638 return f"{float(n):.1f}{symbols[0]}" 

639 

640 

641def get_procfs_path(): 

642 """Return updated psutil.PROCFS_PATH constant.""" 

643 return sys.modules['psutil'].PROCFS_PATH 

644 

645 

646def decode(s): 

647 return s.decode(encoding=ENCODING, errors=ENCODING_ERRS) 

648 

649 

650# ===================================================================== 

651# --- shell utils 

652# ===================================================================== 

653 

654 

655@functools.lru_cache 

656def term_supports_colors(force_color=False): 

657 if WINDOWS: 

658 return False 

659 if force_color: 

660 return True 

661 if not hasattr(sys.stdout, "isatty") or not sys.stdout.isatty(): 

662 return False 

663 try: 

664 sys.stdout.fileno() 

665 except Exception: # noqa: BLE001 

666 return False 

667 return True 

668 

669 

670def hilite(s, color=None, bold=False, force_color=False): 

671 """Return an highlighted version of 'string'.""" 

672 if not term_supports_colors(force_color=force_color): 

673 return s 

674 attr = [] 

675 colors = dict( 

676 blue='34', 

677 brown='33', 

678 darkgrey='30', 

679 green='32', 

680 grey='37', 

681 lightblue='36', 

682 red='31', 

683 violet='35', 

684 yellow='93', 

685 ) 

686 colors[None] = '29' 

687 try: 

688 color = colors[color] 

689 except KeyError: 

690 msg = f"invalid color {color!r}; choose amongst {list(colors)}" 

691 raise ValueError(msg) from None 

692 attr.append(color) 

693 if bold: 

694 attr.append('1') 

695 return f"\x1b[{';'.join(attr)}m{s}\x1b[0m" 

696 

697 

698def print_color( 

699 s, color=None, bold=False, file=sys.stdout 

700): # pragma: no cover 

701 """Print a colorized version of string.""" 

702 if term_supports_colors(): 

703 s = hilite(s, color=color, bold=bold) 

704 print(s, file=file, flush=True) 

705 

706 

707def debug(msg): 

708 """If PSUTIL_DEBUG env var is set, print a debug message to stderr.""" 

709 if PSUTIL_DEBUG: 

710 import inspect 

711 

712 fname, lineno, _, _lines, _index = inspect.getframeinfo( 

713 inspect.currentframe().f_back 

714 ) 

715 if isinstance(msg, Exception): 

716 if isinstance(msg, OSError): 

717 # ...because str(exc) may contain info about the file name 

718 msg = f"ignoring {msg}" 

719 else: 

720 msg = f"ignoring {msg!r}" 

721 print( # noqa: T201 

722 f"psutil-debug [{fname}:{lineno}]> {msg}", file=sys.stderr 

723 ) 

724 

725 

726def warn(msg): 

727 """Emit a RuntimeWarning. Use it for events which are never 

728 supposed to happen, and imply a psutil bug. 

729 """ 

730 import inspect 

731 

732 fname, lineno, _, _lines, _index = inspect.getframeinfo( 

733 inspect.currentframe().f_back 

734 ) 

735 msg = f"{msg} (originated from {fname}:{lineno})" 

736 if PSUTIL_TESTING: 

737 msg = f"CRITICAL: {msg}" 

738 raise RuntimeError(msg) 

739 try: 

740 warnings.warn(msg, RuntimeWarning, stacklevel=2) 

741 except RuntimeWarning: 

742 pass # -W error: we never want to fail because of a warning