Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/psutil/_common.py: 45%

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

411 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 

25 

26try: 

27 from socket import AF_INET6 

28except ImportError: 

29 AF_INET6 = None 

30try: 

31 from socket import AF_UNIX 

32except ImportError: 

33 AF_UNIX = None 

34 

35 

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

37_DEFAULT = object() 

38 

39# fmt: off 

40__all__ = [ 

41 # OS constants 

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

43 'SUNOS', 'WINDOWS', 

44 # connection constants 

45 'CONN_CLOSE', 'CONN_CLOSE_WAIT', 'CONN_CLOSING', 'CONN_ESTABLISHED', 

46 'CONN_FIN_WAIT1', 'CONN_FIN_WAIT2', 'CONN_LAST_ACK', 'CONN_LISTEN', 

47 'CONN_NONE', 'CONN_SYN_RECV', 'CONN_SYN_SENT', 'CONN_TIME_WAIT', 

48 # net constants 

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

50 # process status constants 

51 'STATUS_DEAD', 'STATUS_DISK_SLEEP', 'STATUS_IDLE', 'STATUS_LOCKED', 

52 'STATUS_RUNNING', 'STATUS_SLEEPING', 'STATUS_STOPPED', 'STATUS_SUSPENDED', 

53 'STATUS_TRACING_STOP', 'STATUS_WAITING', 'STATUS_WAKE_KILL', 

54 'STATUS_WAKING', 'STATUS_ZOMBIE', 'STATUS_PARKED', 

55 # other constants 

56 'ENCODING', 'ENCODING_ERRS', 'AF_INET6', 

57 # named tuples 

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

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

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

61 # utility functions 

62 'conn_tmap', 'deprecated_method', 'isfile_strict', 'memoize', 

63 'parse_environ_block', 'path_exists_strict', 'usage_percent', 

64 'supports_ipv6', 'sockfam_to_enum', 'socktype_to_enum', "wrap_numbers", 

65 'open_text', 'open_binary', 'cat', 'bcat', 

66 'bytes2human', 'conn_to_ntuple', 'debug', 

67 # shell utils 

68 'hilite', 'term_supports_colors', 'print_color', 

69] 

70# fmt: on 

71 

72 

73# =================================================================== 

74# --- OS constants 

75# =================================================================== 

76 

77 

78POSIX = os.name == "posix" 

79WINDOWS = os.name == "nt" 

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

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

82OSX = MACOS # deprecated alias 

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

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

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

86BSD = FREEBSD or OPENBSD or NETBSD 

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

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

89 

90 

91# =================================================================== 

92# --- API constants 

93# =================================================================== 

94 

95 

96# Process.status() 

97STATUS_RUNNING = "running" 

98STATUS_SLEEPING = "sleeping" 

99STATUS_DISK_SLEEP = "disk-sleep" 

100STATUS_STOPPED = "stopped" 

101STATUS_TRACING_STOP = "tracing-stop" 

102STATUS_ZOMBIE = "zombie" 

103STATUS_DEAD = "dead" 

104STATUS_WAKE_KILL = "wake-kill" 

105STATUS_WAKING = "waking" 

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

107STATUS_LOCKED = "locked" # FreeBSD 

108STATUS_WAITING = "waiting" # FreeBSD 

109STATUS_SUSPENDED = "suspended" # NetBSD 

110STATUS_PARKED = "parked" # Linux 

111 

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

113CONN_ESTABLISHED = "ESTABLISHED" 

114CONN_SYN_SENT = "SYN_SENT" 

115CONN_SYN_RECV = "SYN_RECV" 

116CONN_FIN_WAIT1 = "FIN_WAIT1" 

117CONN_FIN_WAIT2 = "FIN_WAIT2" 

118CONN_TIME_WAIT = "TIME_WAIT" 

119CONN_CLOSE = "CLOSE" 

120CONN_CLOSE_WAIT = "CLOSE_WAIT" 

121CONN_LAST_ACK = "LAST_ACK" 

122CONN_LISTEN = "LISTEN" 

123CONN_CLOSING = "CLOSING" 

124CONN_NONE = "NONE" 

125 

126 

127# net_if_stats() 

128class NicDuplex(enum.IntEnum): 

129 NIC_DUPLEX_FULL = 2 

130 NIC_DUPLEX_HALF = 1 

131 NIC_DUPLEX_UNKNOWN = 0 

132 

133 

134globals().update(NicDuplex.__members__) 

135 

136 

137# sensors_battery() 

138class BatteryTime(enum.IntEnum): 

139 POWER_TIME_UNKNOWN = -1 

140 POWER_TIME_UNLIMITED = -2 

141 

142 

143globals().update(BatteryTime.__members__) 

144 

145# --- others 

146 

147ENCODING = sys.getfilesystemencoding() 

148ENCODING_ERRS = sys.getfilesystemencodeerrors() 

149 

150 

151# =================================================================== 

152# --- namedtuples 

153# =================================================================== 

154 

155# --- for system functions 

156 

157# fmt: off 

158# psutil.swap_memory() 

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

160 'sout']) 

161# psutil.disk_usage() 

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

163# psutil.disk_io_counters() 

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

165 'read_bytes', 'write_bytes', 

166 'read_time', 'write_time']) 

167# psutil.disk_partitions() 

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

169# psutil.net_io_counters() 

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

171 'packets_sent', 'packets_recv', 

172 'errin', 'errout', 

173 'dropin', 'dropout']) 

174# psutil.users() 

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

176# psutil.net_connections() 

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

178 'status', 'pid']) 

179# psutil.net_if_addrs() 

180snicaddr = namedtuple('snicaddr', 

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

182# psutil.net_if_stats() 

183snicstats = namedtuple('snicstats', 

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

185# psutil.cpu_stats() 

186scpustats = namedtuple( 

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

188# psutil.cpu_freq() 

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

190# psutil.sensors_temperatures() 

191shwtemp = namedtuple( 

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

193# psutil.sensors_battery() 

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

195# psutil.sensors_fans() 

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

197# fmt: on 

198 

199# --- for Process methods 

200 

201# psutil.Process.cpu_times() 

202pcputimes = namedtuple( 

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

204) 

205# psutil.Process.open_files() 

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

207# psutil.Process.threads() 

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

209# psutil.Process.uids() 

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

211# psutil.Process.gids() 

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

213# psutil.Process.io_counters() 

214pio = namedtuple( 

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

216) 

217# psutil.Process.ionice() 

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

219# psutil.Process.ctx_switches() 

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

221# psutil.Process.net_connections() 

222pconn = namedtuple( 

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

224) 

225 

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

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

228 

229 

230# =================================================================== 

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

232# =================================================================== 

233 

234 

235conn_tmap = { 

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

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

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

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

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

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

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

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

244} 

245 

246if AF_INET6 is not None: 

247 conn_tmap.update({ 

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

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

250 }) 

251 

252if AF_UNIX is not None and not SUNOS: 

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

254 

255 

256# ===================================================================== 

257# --- Exceptions 

258# ===================================================================== 

259 

260 

261class Error(Exception): 

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

263 from this one. 

264 """ 

265 

266 __module__ = 'psutil' 

267 

268 def _infodict(self, attrs): 

269 info = collections.OrderedDict() 

270 for name in attrs: 

271 value = getattr(self, name, None) 

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

273 info[name] = value 

274 return info 

275 

276 def __str__(self): 

277 # invoked on `raise Error` 

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

279 if info: 

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

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

282 ) 

283 else: 

284 details = None 

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

286 

287 def __repr__(self): 

288 # invoked on `repr(Error)` 

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

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

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

292 

293 

294class NoSuchProcess(Error): 

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

296 or no longer exists. 

297 """ 

298 

299 __module__ = 'psutil' 

300 

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

302 Error.__init__(self) 

303 self.pid = pid 

304 self.name = name 

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

306 

307 def __reduce__(self): 

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

309 

310 

311class ZombieProcess(NoSuchProcess): 

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

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

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

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

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

317 """ 

318 

319 __module__ = 'psutil' 

320 

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

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

323 self.ppid = ppid 

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

325 

326 def __reduce__(self): 

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

328 

329 

330class AccessDenied(Error): 

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

332 

333 __module__ = 'psutil' 

334 

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

336 Error.__init__(self) 

337 self.pid = pid 

338 self.name = name 

339 self.msg = msg or "" 

340 

341 def __reduce__(self): 

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

343 

344 

345class TimeoutExpired(Error): 

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

347 is still alive. 

348 """ 

349 

350 __module__ = 'psutil' 

351 

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

353 Error.__init__(self) 

354 self.seconds = seconds 

355 self.pid = pid 

356 self.name = name 

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

358 

359 def __reduce__(self): 

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

361 

362 

363# =================================================================== 

364# --- utils 

365# =================================================================== 

366 

367 

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

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

370 try: 

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

372 except ZeroDivisionError: 

373 return 0.0 

374 else: 

375 if round_ is not None: 

376 ret = round(ret, round_) 

377 return ret 

378 

379 

380def memoize(fun): 

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

382 positional arguments. 

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

384 

385 >>> @memoize 

386 ... def foo() 

387 ... return 1 

388 ... 

389 >>> foo() 

390 1 

391 >>> foo.cache_clear() 

392 >>> 

393 

394 It supports: 

395 - functions 

396 - classes (acts as a @singleton) 

397 - staticmethods 

398 - classmethods 

399 

400 It does NOT support: 

401 - methods 

402 """ 

403 

404 @functools.wraps(fun) 

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

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

407 try: 

408 return cache[key] 

409 except KeyError: 

410 try: 

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

412 except Exception as err: # noqa: BLE001 

413 raise err from None 

414 return ret 

415 

416 def cache_clear(): 

417 """Clear cache.""" 

418 cache.clear() 

419 

420 cache = {} 

421 wrapper.cache_clear = cache_clear 

422 return wrapper 

423 

424 

425def memoize_when_activated(fun): 

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

427 activated and deactivated on request. 

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

429 accepting no arguments. 

430 

431 >>> class Foo: 

432 ... @memoize 

433 ... def foo() 

434 ... print(1) 

435 ... 

436 >>> f = Foo() 

437 >>> # deactivated (default) 

438 >>> foo() 

439 1 

440 >>> foo() 

441 1 

442 >>> 

443 >>> # activated 

444 >>> foo.cache_activate(self) 

445 >>> foo() 

446 1 

447 >>> foo() 

448 >>> foo() 

449 >>> 

450 """ 

451 

452 @functools.wraps(fun) 

453 def wrapper(self): 

454 try: 

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

456 ret = self._cache[fun] 

457 except AttributeError: 

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

459 try: 

460 return fun(self) 

461 except Exception as err: # noqa: BLE001 

462 raise err from None 

463 except KeyError: 

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

465 # for this entry yet 

466 try: 

467 ret = fun(self) 

468 except Exception as err: # noqa: BLE001 

469 raise err from None 

470 try: 

471 self._cache[fun] = ret 

472 except AttributeError: 

473 # multi-threading race condition, see: 

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

475 pass 

476 return ret 

477 

478 def cache_activate(proc): 

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

480 stored as a "_cache" instance attribute. 

481 """ 

482 proc._cache = {} 

483 

484 def cache_deactivate(proc): 

485 """Deactivate and clear cache.""" 

486 try: 

487 del proc._cache 

488 except AttributeError: 

489 pass 

490 

491 wrapper.cache_activate = cache_activate 

492 wrapper.cache_deactivate = cache_deactivate 

493 return wrapper 

494 

495 

496def isfile_strict(path): 

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

498 exceptions, see: 

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

500 """ 

501 try: 

502 st = os.stat(path) 

503 except PermissionError: 

504 raise 

505 except OSError: 

506 return False 

507 else: 

508 return stat.S_ISREG(st.st_mode) 

509 

510 

511def path_exists_strict(path): 

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

513 exceptions. See: 

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

515 """ 

516 try: 

517 os.stat(path) 

518 except PermissionError: 

519 raise 

520 except OSError: 

521 return False 

522 else: 

523 return True 

524 

525 

526@memoize 

527def supports_ipv6(): 

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

529 if not socket.has_ipv6 or AF_INET6 is None: 

530 return False 

531 try: 

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

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

534 return True 

535 except OSError: 

536 return False 

537 

538 

539def parse_environ_block(data): 

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

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

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

543 ret = {} 

544 pos = 0 

545 

546 # localize global variable to speed up access. 

547 WINDOWS_ = WINDOWS 

548 while True: 

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

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

551 if next_pos <= pos: 

552 break 

553 # there might not be an equals sign 

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

555 if equal_pos > pos: 

556 key = data[pos:equal_pos] 

557 value = data[equal_pos + 1 : next_pos] 

558 # Windows expects environment variables to be uppercase only 

559 if WINDOWS_: 

560 key = key.upper() 

561 ret[key] = value 

562 pos = next_pos + 1 

563 

564 return ret 

565 

566 

567def sockfam_to_enum(num): 

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

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

570 """ 

571 try: 

572 return socket.AddressFamily(num) 

573 except ValueError: 

574 return num 

575 

576 

577def socktype_to_enum(num): 

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

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

580 """ 

581 try: 

582 return socket.SocketKind(num) 

583 except ValueError: 

584 return num 

585 

586 

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

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

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

590 if laddr: 

591 laddr = addr(*laddr) 

592 if raddr: 

593 raddr = addr(*raddr) 

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

595 status = status_map.get(status, CONN_NONE) 

596 else: 

597 status = CONN_NONE # ignore whatever C returned to us 

598 fam = sockfam_to_enum(fam) 

599 type_ = socktype_to_enum(type_) 

600 if pid is None: 

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

602 else: 

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

604 

605 

606def broadcast_addr(addr): 

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

608 calculates the broadcast address. 

609 """ 

610 import ipaddress 

611 

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

613 return None 

614 if addr.family == socket.AF_INET: 

615 return str( 

616 ipaddress.IPv4Network( 

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

618 ).broadcast_address 

619 ) 

620 if addr.family == socket.AF_INET6: 

621 return str( 

622 ipaddress.IPv6Network( 

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

624 ).broadcast_address 

625 ) 

626 

627 

628def deprecated_method(replacement): 

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

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

631 """ 

632 

633 def outer(fun): 

634 msg = ( 

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

636 f" {replacement}() instead" 

637 ) 

638 if fun.__doc__ is None: 

639 fun.__doc__ = msg 

640 

641 @functools.wraps(fun) 

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

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

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

645 

646 return inner 

647 

648 return outer 

649 

650 

651class _WrapNumbers: 

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

653 (reset to zero). 

654 """ 

655 

656 def __init__(self): 

657 self.lock = threading.Lock() 

658 self.cache = {} 

659 self.reminders = {} 

660 self.reminder_keys = {} 

661 

662 def _add_dict(self, input_dict, name): 

663 assert name not in self.cache 

664 assert name not in self.reminders 

665 assert name not in self.reminder_keys 

666 self.cache[name] = input_dict 

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

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

669 

670 def _remove_dead_reminders(self, input_dict, name): 

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

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

673 """ 

674 old_dict = self.cache[name] 

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

676 for gone_key in gone_keys: 

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

678 del self.reminders[name][remkey] 

679 del self.reminder_keys[name][gone_key] 

680 

681 def run(self, input_dict, name): 

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

683 Return an updated copy of `input_dict`. 

684 """ 

685 if name not in self.cache: 

686 # This was the first call. 

687 self._add_dict(input_dict, name) 

688 return input_dict 

689 

690 self._remove_dead_reminders(input_dict, name) 

691 

692 old_dict = self.cache[name] 

693 new_dict = {} 

694 for key in input_dict: 

695 input_tuple = input_dict[key] 

696 try: 

697 old_tuple = old_dict[key] 

698 except KeyError: 

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

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

701 new_dict[key] = input_tuple 

702 continue 

703 

704 bits = [] 

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

706 input_value = input_tuple[i] 

707 old_value = old_tuple[i] 

708 remkey = (key, i) 

709 if input_value < old_value: 

710 # it wrapped! 

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

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

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

714 

715 new_dict[key] = tuple(bits) 

716 

717 self.cache[name] = input_dict 

718 return new_dict 

719 

720 def cache_clear(self, name=None): 

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

722 with self.lock: 

723 if name is None: 

724 self.cache.clear() 

725 self.reminders.clear() 

726 self.reminder_keys.clear() 

727 else: 

728 self.cache.pop(name, None) 

729 self.reminders.pop(name, None) 

730 self.reminder_keys.pop(name, None) 

731 

732 def cache_info(self): 

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

734 with self.lock: 

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

736 

737 

738def wrap_numbers(input_dict, name): 

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

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

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

742 """ 

743 with _wn.lock: 

744 return _wn.run(input_dict, name) 

745 

746 

747_wn = _WrapNumbers() 

748wrap_numbers.cache_clear = _wn.cache_clear 

749wrap_numbers.cache_info = _wn.cache_info 

750 

751 

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

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

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

755# ... for line in f: 

756# ... ... 

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

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

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

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

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

762FILE_READ_BUFFER_SIZE = 32 * 1024 

763 

764 

765def open_binary(fname): 

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

767 

768 

769def open_text(fname): 

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

771 en/decoding error handlers. 

772 """ 

773 # See: 

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

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

776 fobj = open( # noqa: SIM115 

777 fname, 

778 buffering=FILE_READ_BUFFER_SIZE, 

779 encoding=ENCODING, 

780 errors=ENCODING_ERRS, 

781 ) 

782 try: 

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

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

785 fobj._CHUNK_SIZE = FILE_READ_BUFFER_SIZE 

786 except AttributeError: 

787 pass 

788 except Exception: 

789 fobj.close() 

790 raise 

791 

792 return fobj 

793 

794 

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

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

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

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

799 it can't be read(). 

800 """ 

801 if fallback is _DEFAULT: 

802 with _open(fname) as f: 

803 return f.read() 

804 else: 

805 try: 

806 with _open(fname) as f: 

807 return f.read() 

808 except OSError: 

809 return fallback 

810 

811 

812def bcat(fname, fallback=_DEFAULT): 

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

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

815 

816 

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

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

819 

820 >>> bytes2human(10000) 

821 '9.8K' 

822 >>> bytes2human(100001221) 

823 '95.4M' 

824 """ 

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

826 prefix = {} 

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

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

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

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

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

832 return format % locals() 

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

834 

835 

836def get_procfs_path(): 

837 """Return updated psutil.PROCFS_PATH constant.""" 

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

839 

840 

841def decode(s): 

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

843 

844 

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

846# --- shell utils 

847# ===================================================================== 

848 

849 

850@memoize 

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

852 if os.name == 'nt': 

853 return True 

854 try: 

855 import curses 

856 

857 assert file.isatty() 

858 curses.setupterm() 

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

860 except Exception: # noqa: BLE001 

861 return False 

862 else: 

863 return True 

864 

865 

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

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

868 if not term_supports_colors(): 

869 return s 

870 attr = [] 

871 colors = dict( 

872 blue='34', 

873 brown='33', 

874 darkgrey='30', 

875 green='32', 

876 grey='37', 

877 lightblue='36', 

878 red='91', 

879 violet='35', 

880 yellow='93', 

881 ) 

882 colors[None] = '29' 

883 try: 

884 color = colors[color] 

885 except KeyError: 

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

887 raise ValueError(msg) from None 

888 attr.append(color) 

889 if bold: 

890 attr.append('1') 

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

892 

893 

894def print_color( 

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

896): # pragma: no cover 

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

898 if not term_supports_colors(): 

899 print(s, file=file) 

900 elif POSIX: 

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

902 else: 

903 import ctypes 

904 

905 DEFAULT_COLOR = 7 

906 GetStdHandle = ctypes.windll.Kernel32.GetStdHandle 

907 SetConsoleTextAttribute = ( 

908 ctypes.windll.Kernel32.SetConsoleTextAttribute 

909 ) 

910 

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

912 colors[None] = DEFAULT_COLOR 

913 try: 

914 color = colors[color] 

915 except KeyError: 

916 msg = ( 

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

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

919 ) 

920 raise ValueError(msg) from None 

921 if bold and color <= 7: 

922 color += 8 

923 

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

925 GetStdHandle.restype = ctypes.c_ulong 

926 handle = GetStdHandle(handle_id) 

927 SetConsoleTextAttribute(handle, color) 

928 try: 

929 print(s, file=file) 

930 finally: 

931 SetConsoleTextAttribute(handle, DEFAULT_COLOR) 

932 

933 

934def debug(msg): 

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

936 if PSUTIL_DEBUG: 

937 import inspect 

938 

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

940 inspect.currentframe().f_back 

941 ) 

942 if isinstance(msg, Exception): 

943 if isinstance(msg, OSError): 

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

945 msg = f"ignoring {msg}" 

946 else: 

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

948 print( # noqa: T201 

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

950 )