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

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

410 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 enum 

13import functools 

14import os 

15import socket 

16import stat 

17import sys 

18import threading 

19import warnings 

20from collections import namedtuple 

21from socket import AF_INET 

22from socket import SOCK_DGRAM 

23from socket import SOCK_STREAM 

24 

25try: 

26 from socket import AF_INET6 

27except ImportError: 

28 AF_INET6 = None 

29try: 

30 from socket import AF_UNIX 

31except ImportError: 

32 AF_UNIX = None 

33 

34 

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

36_DEFAULT = object() 

37 

38# fmt: off 

39__all__ = [ 

40 # OS constants 

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

42 'SUNOS', 'WINDOWS', 

43 # connection constants 

44 'CONN_CLOSE', 'CONN_CLOSE_WAIT', 'CONN_CLOSING', 'CONN_ESTABLISHED', 

45 'CONN_FIN_WAIT1', 'CONN_FIN_WAIT2', 'CONN_LAST_ACK', 'CONN_LISTEN', 

46 'CONN_NONE', 'CONN_SYN_RECV', 'CONN_SYN_SENT', 'CONN_TIME_WAIT', 

47 # net constants 

48 'NIC_DUPLEX_FULL', 'NIC_DUPLEX_HALF', 'NIC_DUPLEX_UNKNOWN', # noqa: F822 

49 # process status constants 

50 'STATUS_DEAD', 'STATUS_DISK_SLEEP', 'STATUS_IDLE', 'STATUS_LOCKED', 

51 'STATUS_RUNNING', 'STATUS_SLEEPING', 'STATUS_STOPPED', 'STATUS_SUSPENDED', 

52 'STATUS_TRACING_STOP', 'STATUS_WAITING', 'STATUS_WAKE_KILL', 

53 'STATUS_WAKING', 'STATUS_ZOMBIE', 'STATUS_PARKED', 

54 # other constants 

55 'ENCODING', 'ENCODING_ERRS', 'AF_INET6', 

56 # named tuples 

57 'pconn', 'pcputimes', 'pctxsw', 'pgids', 'pio', 'pionice', 'popenfile', 

58 'pthread', 'puids', 'sconn', 'scpustats', 'sdiskio', 'sdiskpart', 

59 'sdiskusage', 'snetio', 'snicaddr', 'snicstats', 'sswap', 'suser', 

60 # utility functions 

61 'conn_tmap', 'deprecated_method', 'isfile_strict', 'memoize', 

62 'parse_environ_block', 'path_exists_strict', 'usage_percent', 

63 'supports_ipv6', 'sockfam_to_enum', 'socktype_to_enum', "wrap_numbers", 

64 'open_text', 'open_binary', 'cat', 'bcat', 

65 'bytes2human', 'conn_to_ntuple', 'debug', 

66 # shell utils 

67 'hilite', 'term_supports_colors', 'print_color', 

68] 

69# fmt: on 

70 

71 

72# =================================================================== 

73# --- OS constants 

74# =================================================================== 

75 

76 

77POSIX = os.name == "posix" 

78WINDOWS = os.name == "nt" 

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

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

81OSX = MACOS # deprecated alias 

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

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

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

85BSD = FREEBSD or OPENBSD or NETBSD 

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

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

88 

89 

90# =================================================================== 

91# --- API constants 

92# =================================================================== 

93 

94 

95# Process.status() 

96STATUS_RUNNING = "running" 

97STATUS_SLEEPING = "sleeping" 

98STATUS_DISK_SLEEP = "disk-sleep" 

99STATUS_STOPPED = "stopped" 

100STATUS_TRACING_STOP = "tracing-stop" 

101STATUS_ZOMBIE = "zombie" 

102STATUS_DEAD = "dead" 

103STATUS_WAKE_KILL = "wake-kill" 

104STATUS_WAKING = "waking" 

105STATUS_IDLE = "idle" # Linux, macOS, FreeBSD 

106STATUS_LOCKED = "locked" # FreeBSD 

107STATUS_WAITING = "waiting" # FreeBSD 

108STATUS_SUSPENDED = "suspended" # NetBSD 

109STATUS_PARKED = "parked" # Linux 

110 

111# Process.net_connections() and psutil.net_connections() 

112CONN_ESTABLISHED = "ESTABLISHED" 

113CONN_SYN_SENT = "SYN_SENT" 

114CONN_SYN_RECV = "SYN_RECV" 

115CONN_FIN_WAIT1 = "FIN_WAIT1" 

116CONN_FIN_WAIT2 = "FIN_WAIT2" 

117CONN_TIME_WAIT = "TIME_WAIT" 

118CONN_CLOSE = "CLOSE" 

119CONN_CLOSE_WAIT = "CLOSE_WAIT" 

120CONN_LAST_ACK = "LAST_ACK" 

121CONN_LISTEN = "LISTEN" 

122CONN_CLOSING = "CLOSING" 

123CONN_NONE = "NONE" 

124 

125 

126# net_if_stats() 

127class NicDuplex(enum.IntEnum): 

128 NIC_DUPLEX_FULL = 2 

129 NIC_DUPLEX_HALF = 1 

130 NIC_DUPLEX_UNKNOWN = 0 

131 

132 

133globals().update(NicDuplex.__members__) 

134 

135 

136# sensors_battery() 

137class BatteryTime(enum.IntEnum): 

138 POWER_TIME_UNKNOWN = -1 

139 POWER_TIME_UNLIMITED = -2 

140 

141 

142globals().update(BatteryTime.__members__) 

143 

144# --- others 

145 

146ENCODING = sys.getfilesystemencoding() 

147ENCODING_ERRS = sys.getfilesystemencodeerrors() 

148 

149 

150# =================================================================== 

151# --- namedtuples 

152# =================================================================== 

153 

154# --- for system functions 

155 

156# fmt: off 

157# psutil.swap_memory() 

158sswap = namedtuple('sswap', ['total', 'used', 'free', 'percent', 'sin', 

159 'sout']) 

160# psutil.disk_usage() 

161sdiskusage = namedtuple('sdiskusage', ['total', 'used', 'free', 'percent']) 

162# psutil.disk_io_counters() 

163sdiskio = namedtuple('sdiskio', ['read_count', 'write_count', 

164 'read_bytes', 'write_bytes', 

165 'read_time', 'write_time']) 

166# psutil.disk_partitions() 

167sdiskpart = namedtuple('sdiskpart', ['device', 'mountpoint', 'fstype', 'opts']) 

168# psutil.net_io_counters() 

169snetio = namedtuple('snetio', ['bytes_sent', 'bytes_recv', 

170 'packets_sent', 'packets_recv', 

171 'errin', 'errout', 

172 'dropin', 'dropout']) 

173# psutil.users() 

174suser = namedtuple('suser', ['name', 'terminal', 'host', 'started', 'pid']) 

175# psutil.net_connections() 

176sconn = namedtuple('sconn', ['fd', 'family', 'type', 'laddr', 'raddr', 

177 'status', 'pid']) 

178# psutil.net_if_addrs() 

179snicaddr = namedtuple('snicaddr', 

180 ['family', 'address', 'netmask', 'broadcast', 'ptp']) 

181# psutil.net_if_stats() 

182snicstats = namedtuple('snicstats', 

183 ['isup', 'duplex', 'speed', 'mtu', 'flags']) 

184# psutil.cpu_stats() 

185scpustats = namedtuple( 

186 'scpustats', ['ctx_switches', 'interrupts', 'soft_interrupts', 'syscalls']) 

187# psutil.cpu_freq() 

188scpufreq = namedtuple('scpufreq', ['current', 'min', 'max']) 

189# psutil.sensors_temperatures() 

190shwtemp = namedtuple( 

191 'shwtemp', ['label', 'current', 'high', 'critical']) 

192# psutil.sensors_battery() 

193sbattery = namedtuple('sbattery', ['percent', 'secsleft', 'power_plugged']) 

194# psutil.sensors_fans() 

195sfan = namedtuple('sfan', ['label', 'current']) 

196# fmt: on 

197 

198# --- for Process methods 

199 

200# psutil.Process.cpu_times() 

201pcputimes = namedtuple( 

202 'pcputimes', ['user', 'system', 'children_user', 'children_system'] 

203) 

204# psutil.Process.open_files() 

205popenfile = namedtuple('popenfile', ['path', 'fd']) 

206# psutil.Process.threads() 

207pthread = namedtuple('pthread', ['id', 'user_time', 'system_time']) 

208# psutil.Process.uids() 

209puids = namedtuple('puids', ['real', 'effective', 'saved']) 

210# psutil.Process.gids() 

211pgids = namedtuple('pgids', ['real', 'effective', 'saved']) 

212# psutil.Process.io_counters() 

213pio = namedtuple( 

214 'pio', ['read_count', 'write_count', 'read_bytes', 'write_bytes'] 

215) 

216# psutil.Process.ionice() 

217pionice = namedtuple('pionice', ['ioclass', 'value']) 

218# psutil.Process.ctx_switches() 

219pctxsw = namedtuple('pctxsw', ['voluntary', 'involuntary']) 

220# psutil.Process.net_connections() 

221pconn = namedtuple( 

222 'pconn', ['fd', 'family', 'type', 'laddr', 'raddr', 'status'] 

223) 

224 

225# psutil.net_connections() and psutil.Process.net_connections() 

226addr = namedtuple('addr', ['ip', 'port']) 

227 

228 

229# =================================================================== 

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

231# =================================================================== 

232 

233 

234conn_tmap = { 

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

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

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

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

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

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

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

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

243} 

244 

245if AF_INET6 is not None: 

246 conn_tmap.update({ 

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

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

249 }) 

250 

251if AF_UNIX is not None and not SUNOS: 

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

253 

254 

255# ===================================================================== 

256# --- Exceptions 

257# ===================================================================== 

258 

259 

260class Error(Exception): 

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

262 from this one. 

263 """ 

264 

265 __module__ = 'psutil' 

266 

267 def _infodict(self, attrs): 

268 info = collections.OrderedDict() 

269 for name in attrs: 

270 value = getattr(self, name, None) 

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

272 info[name] = value 

273 return info 

274 

275 def __str__(self): 

276 # invoked on `raise Error` 

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

278 if info: 

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

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

281 ) 

282 else: 

283 details = None 

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

285 

286 def __repr__(self): 

287 # invoked on `repr(Error)` 

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

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

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

291 

292 

293class NoSuchProcess(Error): 

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

295 or no longer exists. 

296 """ 

297 

298 __module__ = 'psutil' 

299 

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

301 Error.__init__(self) 

302 self.pid = pid 

303 self.name = name 

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

305 

306 def __reduce__(self): 

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

308 

309 

310class ZombieProcess(NoSuchProcess): 

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

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

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

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

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

316 """ 

317 

318 __module__ = 'psutil' 

319 

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

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

322 self.ppid = ppid 

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

324 

325 def __reduce__(self): 

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

327 

328 

329class AccessDenied(Error): 

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

331 

332 __module__ = 'psutil' 

333 

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

335 Error.__init__(self) 

336 self.pid = pid 

337 self.name = name 

338 self.msg = msg or "" 

339 

340 def __reduce__(self): 

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

342 

343 

344class TimeoutExpired(Error): 

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

346 is still alive. 

347 """ 

348 

349 __module__ = 'psutil' 

350 

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

352 Error.__init__(self) 

353 self.seconds = seconds 

354 self.pid = pid 

355 self.name = name 

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

357 

358 def __reduce__(self): 

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

360 

361 

362# =================================================================== 

363# --- utils 

364# =================================================================== 

365 

366 

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

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

369 try: 

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

371 except ZeroDivisionError: 

372 return 0.0 

373 else: 

374 if round_ is not None: 

375 ret = round(ret, round_) 

376 return ret 

377 

378 

379def memoize(fun): 

380 """A simple memoize decorator for functions supporting (hashable) 

381 positional arguments. 

382 It also provides a cache_clear() function for clearing the cache: 

383 

384 >>> @memoize 

385 ... def foo() 

386 ... return 1 

387 ... 

388 >>> foo() 

389 1 

390 >>> foo.cache_clear() 

391 >>> 

392 

393 It supports: 

394 - functions 

395 - classes (acts as a @singleton) 

396 - staticmethods 

397 - classmethods 

398 

399 It does NOT support: 

400 - methods 

401 """ 

402 

403 @functools.wraps(fun) 

404 def wrapper(*args, **kwargs): 

405 key = (args, frozenset(sorted(kwargs.items()))) 

406 try: 

407 return cache[key] 

408 except KeyError: 

409 try: 

410 ret = cache[key] = fun(*args, **kwargs) 

411 except Exception as err: # noqa: BLE001 

412 raise err from None 

413 return ret 

414 

415 def cache_clear(): 

416 """Clear cache.""" 

417 cache.clear() 

418 

419 cache = {} 

420 wrapper.cache_clear = cache_clear 

421 return wrapper 

422 

423 

424def memoize_when_activated(fun): 

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

426 activated and deactivated on request. 

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

428 accepting no arguments. 

429 

430 >>> class Foo: 

431 ... @memoize 

432 ... def foo() 

433 ... print(1) 

434 ... 

435 >>> f = Foo() 

436 >>> # deactivated (default) 

437 >>> foo() 

438 1 

439 >>> foo() 

440 1 

441 >>> 

442 >>> # activated 

443 >>> foo.cache_activate(self) 

444 >>> foo() 

445 1 

446 >>> foo() 

447 >>> foo() 

448 >>> 

449 """ 

450 

451 @functools.wraps(fun) 

452 def wrapper(self): 

453 try: 

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

455 ret = self._cache[fun] 

456 except AttributeError: 

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

458 try: 

459 return fun(self) 

460 except Exception as err: # noqa: BLE001 

461 raise err from None 

462 except KeyError: 

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

464 # for this entry yet 

465 try: 

466 ret = fun(self) 

467 except Exception as err: # noqa: BLE001 

468 raise err from None 

469 try: 

470 self._cache[fun] = ret 

471 except AttributeError: 

472 # multi-threading race condition, see: 

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

474 pass 

475 return ret 

476 

477 def cache_activate(proc): 

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

479 stored as a "_cache" instance attribute. 

480 """ 

481 proc._cache = {} 

482 

483 def cache_deactivate(proc): 

484 """Deactivate and clear cache.""" 

485 try: 

486 del proc._cache 

487 except AttributeError: 

488 pass 

489 

490 wrapper.cache_activate = cache_activate 

491 wrapper.cache_deactivate = cache_deactivate 

492 return wrapper 

493 

494 

495def isfile_strict(path): 

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

497 exceptions, see: 

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

499 """ 

500 try: 

501 st = os.stat(path) 

502 except PermissionError: 

503 raise 

504 except OSError: 

505 return False 

506 else: 

507 return stat.S_ISREG(st.st_mode) 

508 

509 

510def path_exists_strict(path): 

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

512 exceptions. See: 

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

514 """ 

515 try: 

516 os.stat(path) 

517 except PermissionError: 

518 raise 

519 except OSError: 

520 return False 

521 else: 

522 return True 

523 

524 

525def supports_ipv6(): 

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

527 if not socket.has_ipv6 or AF_INET6 is None: 

528 return False 

529 try: 

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

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

532 return True 

533 except OSError: 

534 return False 

535 

536 

537def parse_environ_block(data): 

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

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

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

541 ret = {} 

542 pos = 0 

543 

544 # localize global variable to speed up access. 

545 WINDOWS_ = WINDOWS 

546 while True: 

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

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

549 if next_pos <= pos: 

550 break 

551 # there might not be an equals sign 

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

553 if equal_pos > pos: 

554 key = data[pos:equal_pos] 

555 value = data[equal_pos + 1 : next_pos] 

556 # Windows expects environment variables to be uppercase only 

557 if WINDOWS_: 

558 key = key.upper() 

559 ret[key] = value 

560 pos = next_pos + 1 

561 

562 return ret 

563 

564 

565def sockfam_to_enum(num): 

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

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

568 """ 

569 try: 

570 return socket.AddressFamily(num) 

571 except ValueError: 

572 return num 

573 

574 

575def socktype_to_enum(num): 

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

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

578 """ 

579 try: 

580 return socket.SocketKind(num) 

581 except ValueError: 

582 return num 

583 

584 

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

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

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

588 if laddr: 

589 laddr = addr(*laddr) 

590 if raddr: 

591 raddr = addr(*raddr) 

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

593 status = status_map.get(status, CONN_NONE) 

594 else: 

595 status = CONN_NONE # ignore whatever C returned to us 

596 fam = sockfam_to_enum(fam) 

597 type_ = socktype_to_enum(type_) 

598 if pid is None: 

599 return pconn(fd, fam, type_, laddr, raddr, status) 

600 else: 

601 return sconn(fd, fam, type_, laddr, raddr, status, pid) 

602 

603 

604def broadcast_addr(addr): 

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

606 calculates the broadcast address. 

607 """ 

608 import ipaddress 

609 

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

611 return None 

612 if addr.family == socket.AF_INET: 

613 return str( 

614 ipaddress.IPv4Network( 

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

616 ).broadcast_address 

617 ) 

618 if addr.family == socket.AF_INET6: 

619 return str( 

620 ipaddress.IPv6Network( 

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

622 ).broadcast_address 

623 ) 

624 

625 

626def deprecated_method(replacement): 

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

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

629 """ 

630 

631 def outer(fun): 

632 msg = ( 

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

634 f" {replacement}() instead" 

635 ) 

636 if fun.__doc__ is None: 

637 fun.__doc__ = msg 

638 

639 @functools.wraps(fun) 

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

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

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

643 

644 return inner 

645 

646 return outer 

647 

648 

649class _WrapNumbers: 

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

651 (reset to zero). 

652 """ 

653 

654 def __init__(self): 

655 self.lock = threading.Lock() 

656 self.cache = {} 

657 self.reminders = {} 

658 self.reminder_keys = {} 

659 

660 def _add_dict(self, input_dict, name): 

661 assert name not in self.cache 

662 assert name not in self.reminders 

663 assert name not in self.reminder_keys 

664 self.cache[name] = input_dict 

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

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

667 

668 def _remove_dead_reminders(self, input_dict, name): 

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

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

671 """ 

672 old_dict = self.cache[name] 

673 gone_keys = set(old_dict.keys()) - set(input_dict.keys()) 

674 for gone_key in gone_keys: 

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

676 del self.reminders[name][remkey] 

677 del self.reminder_keys[name][gone_key] 

678 

679 def run(self, input_dict, name): 

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

681 Return an updated copy of `input_dict`. 

682 """ 

683 if name not in self.cache: 

684 # This was the first call. 

685 self._add_dict(input_dict, name) 

686 return input_dict 

687 

688 self._remove_dead_reminders(input_dict, name) 

689 

690 old_dict = self.cache[name] 

691 new_dict = {} 

692 for key in input_dict: 

693 input_tuple = input_dict[key] 

694 try: 

695 old_tuple = old_dict[key] 

696 except KeyError: 

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

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

699 new_dict[key] = input_tuple 

700 continue 

701 

702 bits = [] 

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

704 input_value = input_tuple[i] 

705 old_value = old_tuple[i] 

706 remkey = (key, i) 

707 if input_value < old_value: 

708 # it wrapped! 

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

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

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

712 

713 new_dict[key] = tuple(bits) 

714 

715 self.cache[name] = input_dict 

716 return new_dict 

717 

718 def cache_clear(self, name=None): 

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

720 with self.lock: 

721 if name is None: 

722 self.cache.clear() 

723 self.reminders.clear() 

724 self.reminder_keys.clear() 

725 else: 

726 self.cache.pop(name, None) 

727 self.reminders.pop(name, None) 

728 self.reminder_keys.pop(name, None) 

729 

730 def cache_info(self): 

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

732 with self.lock: 

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

734 

735 

736def wrap_numbers(input_dict, name): 

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

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

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

740 """ 

741 with _wn.lock: 

742 return _wn.run(input_dict, name) 

743 

744 

745_wn = _WrapNumbers() 

746wrap_numbers.cache_clear = _wn.cache_clear 

747wrap_numbers.cache_info = _wn.cache_info 

748 

749 

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

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

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

753# ... for line in f: 

754# ... ... 

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

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

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

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

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

760FILE_READ_BUFFER_SIZE = 32 * 1024 

761 

762 

763def open_binary(fname): 

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

765 

766 

767def open_text(fname): 

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

769 en/decoding error handlers. 

770 """ 

771 # See: 

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

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

774 fobj = open( # noqa: SIM115 

775 fname, 

776 buffering=FILE_READ_BUFFER_SIZE, 

777 encoding=ENCODING, 

778 errors=ENCODING_ERRS, 

779 ) 

780 try: 

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

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

783 fobj._CHUNK_SIZE = FILE_READ_BUFFER_SIZE 

784 except AttributeError: 

785 pass 

786 except Exception: 

787 fobj.close() 

788 raise 

789 

790 return fobj 

791 

792 

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

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

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

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

797 it can't be read(). 

798 """ 

799 if fallback is _DEFAULT: 

800 with _open(fname) as f: 

801 return f.read() 

802 else: 

803 try: 

804 with _open(fname) as f: 

805 return f.read() 

806 except OSError: 

807 return fallback 

808 

809 

810def bcat(fname, fallback=_DEFAULT): 

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

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

813 

814 

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

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

817 

818 >>> bytes2human(10000) 

819 '9.8K' 

820 >>> bytes2human(100001221) 

821 '95.4M' 

822 """ 

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

824 prefix = {} 

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

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

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

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

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

830 return format % locals() 

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

832 

833 

834def get_procfs_path(): 

835 """Return updated psutil.PROCFS_PATH constant.""" 

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

837 

838 

839def decode(s): 

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

841 

842 

843# ===================================================================== 

844# --- shell utils 

845# ===================================================================== 

846 

847 

848@memoize 

849def term_supports_colors(file=sys.stdout): # pragma: no cover 

850 if os.name == 'nt': 

851 return True 

852 try: 

853 import curses 

854 

855 assert file.isatty() 

856 curses.setupterm() 

857 assert curses.tigetnum("colors") > 0 

858 except Exception: # noqa: BLE001 

859 return False 

860 else: 

861 return True 

862 

863 

864def hilite(s, color=None, bold=False): # pragma: no cover 

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

866 if not term_supports_colors(): 

867 return s 

868 attr = [] 

869 colors = dict( 

870 blue='34', 

871 brown='33', 

872 darkgrey='30', 

873 green='32', 

874 grey='37', 

875 lightblue='36', 

876 red='91', 

877 violet='35', 

878 yellow='93', 

879 ) 

880 colors[None] = '29' 

881 try: 

882 color = colors[color] 

883 except KeyError: 

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

885 raise ValueError(msg) from None 

886 attr.append(color) 

887 if bold: 

888 attr.append('1') 

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

890 

891 

892def print_color( 

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

894): # pragma: no cover 

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

896 if not term_supports_colors(): 

897 print(s, file=file) 

898 elif POSIX: 

899 print(hilite(s, color, bold), file=file) 

900 else: 

901 import ctypes 

902 

903 DEFAULT_COLOR = 7 

904 GetStdHandle = ctypes.windll.Kernel32.GetStdHandle 

905 SetConsoleTextAttribute = ( 

906 ctypes.windll.Kernel32.SetConsoleTextAttribute 

907 ) 

908 

909 colors = dict(green=2, red=4, brown=6, yellow=6) 

910 colors[None] = DEFAULT_COLOR 

911 try: 

912 color = colors[color] 

913 except KeyError: 

914 msg = ( 

915 f"invalid color {color!r}; choose between" 

916 f" {list(colors.keys())!r}" 

917 ) 

918 raise ValueError(msg) from None 

919 if bold and color <= 7: 

920 color += 8 

921 

922 handle_id = -12 if file is sys.stderr else -11 

923 GetStdHandle.restype = ctypes.c_ulong 

924 handle = GetStdHandle(handle_id) 

925 SetConsoleTextAttribute(handle, color) 

926 try: 

927 print(s, file=file) 

928 finally: 

929 SetConsoleTextAttribute(handle, DEFAULT_COLOR) 

930 

931 

932def debug(msg): 

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

934 if PSUTIL_DEBUG: 

935 import inspect 

936 

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

938 inspect.currentframe().f_back 

939 ) 

940 if isinstance(msg, Exception): 

941 if isinstance(msg, OSError): 

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

943 msg = f"ignoring {msg}" 

944 else: 

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

946 print( # noqa: T201 

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

948 )