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

372 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')) 

34_DEFAULT = object() 

35 

36# fmt: off 

37__all__ = [ 

38 # OS constants 

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

40 'SUNOS', 'WINDOWS', 

41 # other constants 

42 'ENCODING', 'ENCODING_ERRS', 'AF_INET6', 

43 # utility functions 

44 'conn_tmap', 'deprecated_method', 'isfile_strict', 

45 'parse_environ_block', 'path_exists_strict', 'usage_percent', 

46 'supports_ipv6', 'sockfam_to_enum', 'socktype_to_enum', "wrap_numbers", 

47 'open_text', 'open_binary', 'cat', 'bcat', 

48 'bytes2human', 'conn_to_ntuple', 'debug', 

49 # shell utils 

50 'hilite', 'term_supports_colors', 'print_color', 

51] 

52# fmt: on 

53 

54 

55# =================================================================== 

56# --- OS constants 

57# =================================================================== 

58 

59 

60POSIX = os.name == "posix" 

61WINDOWS = os.name == "nt" 

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

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

64OSX = MACOS # deprecated alias 

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

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

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

68BSD = FREEBSD or OPENBSD or NETBSD 

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

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

71 

72ENCODING = sys.getfilesystemencoding() 

73ENCODING_ERRS = sys.getfilesystemencodeerrors() 

74 

75 

76# =================================================================== 

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

78# =================================================================== 

79 

80 

81conn_tmap = { 

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

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

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

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

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

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

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

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

90} 

91 

92if AF_INET6 is not None: 

93 conn_tmap.update({ 

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

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

96 }) 

97 

98if AF_UNIX is not None and not SUNOS: 

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

100 

101 

102# ===================================================================== 

103# --- Exceptions 

104# ===================================================================== 

105 

106 

107class Error(Exception): 

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

109 from this one. 

110 """ 

111 

112 __module__ = 'psutil' 

113 

114 def _infodict(self, attrs): 

115 info = {} 

116 for name in attrs: 

117 value = getattr(self, name, None) 

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

119 info[name] = value 

120 return info 

121 

122 def __str__(self): 

123 # invoked on `raise Error` 

124 info = self._infodict(("pid", "ppid", "name")) 

125 if info: 

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. 

412 """ 

413 import ipaddress 

414 

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

416 return None 

417 if addr.family == socket.AF_INET: 

418 return str( 

419 ipaddress.IPv4Network( 

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

421 ).broadcast_address 

422 ) 

423 if addr.family == socket.AF_INET6: 

424 return str( 

425 ipaddress.IPv6Network( 

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

427 ).broadcast_address 

428 ) 

429 

430 

431def deprecated_method(replacement): 

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

433 'replcement' is the method name which will be called instead. 

434 """ 

435 

436 def outer(fun): 

437 msg = ( 

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

439 f" {replacement}() instead" 

440 ) 

441 if fun.__doc__ is None: 

442 fun.__doc__ = msg 

443 

444 @functools.wraps(fun) 

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

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

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

448 

449 return inner 

450 

451 return outer 

452 

453 

454class deprecated_property: 

455 """A descriptor which can be used to mark a property as deprecated. 

456 'replacement' is the attribute name to use instead. Usage:: 

457 

458 class Foo: 

459 bar = deprecated_property("baz") 

460 """ 

461 

462 def __init__(self, replacement): 

463 self.replacement = replacement 

464 self._msg = None 

465 

466 def __set_name__(self, owner, name): 

467 self._msg = ( 

468 f"{name} is deprecated and will be removed; use" 

469 f" {self.replacement} instead" 

470 ) 

471 

472 def __get__(self, obj, objtype=None): 

473 if obj is None: 

474 return self 

475 warnings.warn(self._msg, category=DeprecationWarning, stacklevel=2) 

476 return getattr(obj, self.replacement) 

477 

478 

479class _WrapNumbers: 

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

481 (reset to zero). 

482 """ 

483 

484 def __init__(self): 

485 self.lock = threading.Lock() 

486 self.cache = {} 

487 self.reminders = {} 

488 self.reminder_keys = {} 

489 

490 def _add_dict(self, input_dict, name): 

491 assert name not in self.cache 

492 assert name not in self.reminders 

493 assert name not in self.reminder_keys 

494 self.cache[name] = input_dict 

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

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

497 

498 def _remove_dead_reminders(self, input_dict, name): 

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

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

501 """ 

502 old_dict = self.cache[name] 

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

504 for gone_key in gone_keys: 

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

506 del self.reminders[name][remkey] 

507 del self.reminder_keys[name][gone_key] 

508 

509 def run(self, input_dict, name): 

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

511 Return an updated copy of `input_dict`. 

512 """ 

513 if name not in self.cache: 

514 # This was the first call. 

515 self._add_dict(input_dict, name) 

516 return input_dict 

517 

518 self._remove_dead_reminders(input_dict, name) 

519 

520 old_dict = self.cache[name] 

521 new_dict = {} 

522 for key in input_dict: 

523 input_tuple = input_dict[key] 

524 try: 

525 old_tuple = old_dict[key] 

526 except KeyError: 

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

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

529 new_dict[key] = input_tuple 

530 continue 

531 

532 bits = [] 

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

534 input_value = input_tuple[i] 

535 old_value = old_tuple[i] 

536 remkey = (key, i) 

537 if input_value < old_value: 

538 # it wrapped! 

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

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

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

542 

543 new_dict[key] = tuple(bits) 

544 

545 self.cache[name] = input_dict 

546 return new_dict 

547 

548 def cache_clear(self, name=None): 

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

550 with self.lock: 

551 if name is None: 

552 self.cache.clear() 

553 self.reminders.clear() 

554 self.reminder_keys.clear() 

555 else: 

556 self.cache.pop(name, None) 

557 self.reminders.pop(name, None) 

558 self.reminder_keys.pop(name, None) 

559 

560 def cache_info(self): 

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

562 with self.lock: 

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

564 

565 

566def wrap_numbers(input_dict, name): 

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

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

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

570 """ 

571 with _wn.lock: 

572 return _wn.run(input_dict, name) 

573 

574 

575_wn = _WrapNumbers() 

576wrap_numbers.cache_clear = _wn.cache_clear 

577wrap_numbers.cache_info = _wn.cache_info 

578 

579 

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

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

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

583# ... for line in f: 

584# ... ... 

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

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

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

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

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

590FILE_READ_BUFFER_SIZE = 32 * 1024 

591 

592 

593def open_binary(fname): 

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

595 

596 

597def open_text(fname): 

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

599 en/decoding error handlers. 

600 """ 

601 # See: 

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

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

604 fobj = open( # noqa: SIM115 

605 fname, 

606 buffering=FILE_READ_BUFFER_SIZE, 

607 encoding=ENCODING, 

608 errors=ENCODING_ERRS, 

609 ) 

610 try: 

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

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

613 fobj._CHUNK_SIZE = FILE_READ_BUFFER_SIZE 

614 except AttributeError: 

615 pass 

616 except Exception: 

617 fobj.close() 

618 raise 

619 

620 return fobj 

621 

622 

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

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

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

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

627 it can't be read(). 

628 """ 

629 if fallback is _DEFAULT: 

630 with _open(fname) as f: 

631 return f.read() 

632 else: 

633 try: 

634 with _open(fname) as f: 

635 return f.read() 

636 except OSError: 

637 return fallback 

638 

639 

640def bcat(fname, fallback=_DEFAULT): 

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

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

643 

644 

645def bytes2human(n, format="%(value).1f%(symbol)s"): 

646 """Used by various scripts. See: https://code.activestate.com/recipes/578019-bytes-to-human-human-to-bytes-converter/?in=user-4178764. 

647 

648 >>> bytes2human(10000) 

649 '9.8K' 

650 >>> bytes2human(100001221) 

651 '95.4M' 

652 """ 

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

654 prefix = {} 

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

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

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

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

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

660 return format % locals() 

661 return format % dict(symbol=symbols[0], value=n) 

662 

663 

664def get_procfs_path(): 

665 """Return updated psutil.PROCFS_PATH constant.""" 

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

667 

668 

669def decode(s): 

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

671 

672 

673# ===================================================================== 

674# --- shell utils 

675# ===================================================================== 

676 

677 

678@functools.lru_cache 

679def term_supports_colors(force_color=False): 

680 if WINDOWS: 

681 return False 

682 if force_color: 

683 return True 

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

685 return False 

686 try: 

687 sys.stdout.fileno() 

688 except Exception: # noqa: BLE001 

689 return False 

690 return True 

691 

692 

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

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

695 if not term_supports_colors(force_color=force_color): 

696 return s 

697 attr = [] 

698 colors = dict( 

699 blue='34', 

700 brown='33', 

701 darkgrey='30', 

702 green='32', 

703 grey='37', 

704 lightblue='36', 

705 red='31', 

706 violet='35', 

707 yellow='93', 

708 ) 

709 colors[None] = '29' 

710 try: 

711 color = colors[color] 

712 except KeyError: 

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

714 raise ValueError(msg) from None 

715 attr.append(color) 

716 if bold: 

717 attr.append('1') 

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

719 

720 

721def print_color( 

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

723): # pragma: no cover 

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

725 if term_supports_colors(): 

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

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

728 

729 

730def debug(msg): 

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

732 if PSUTIL_DEBUG: 

733 import inspect 

734 

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

736 inspect.currentframe().f_back 

737 ) 

738 if isinstance(msg, Exception): 

739 if isinstance(msg, OSError): 

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

741 msg = f"ignoring {msg}" 

742 else: 

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

744 print( # noqa: T201 

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

746 )