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

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

423 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 

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

8# psutil or third-party modules. 

9 

10from __future__ import division 

11from __future__ import print_function 

12 

13import collections 

14import contextlib 

15import errno 

16import functools 

17import os 

18import socket 

19import stat 

20import sys 

21import threading 

22import warnings 

23from collections import namedtuple 

24from socket import AF_INET 

25from socket import SOCK_DGRAM 

26from socket import SOCK_STREAM 

27 

28 

29try: 

30 from socket import AF_INET6 

31except ImportError: 

32 AF_INET6 = None 

33try: 

34 from socket import AF_UNIX 

35except ImportError: 

36 AF_UNIX = None 

37 

38 

39# can't take it from _common.py as this script is imported by setup.py 

40PY3 = sys.version_info[0] >= 3 

41if PY3: 

42 import enum 

43else: 

44 enum = None 

45 

46 

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

48_DEFAULT = object() 

49 

50# fmt: off 

51__all__ = [ 

52 # OS constants 

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

54 'SUNOS', 'WINDOWS', 

55 # connection constants 

56 'CONN_CLOSE', 'CONN_CLOSE_WAIT', 'CONN_CLOSING', 'CONN_ESTABLISHED', 

57 'CONN_FIN_WAIT1', 'CONN_FIN_WAIT2', 'CONN_LAST_ACK', 'CONN_LISTEN', 

58 'CONN_NONE', 'CONN_SYN_RECV', 'CONN_SYN_SENT', 'CONN_TIME_WAIT', 

59 # net constants 

60 'NIC_DUPLEX_FULL', 'NIC_DUPLEX_HALF', 'NIC_DUPLEX_UNKNOWN', 

61 # process status constants 

62 'STATUS_DEAD', 'STATUS_DISK_SLEEP', 'STATUS_IDLE', 'STATUS_LOCKED', 

63 'STATUS_RUNNING', 'STATUS_SLEEPING', 'STATUS_STOPPED', 'STATUS_SUSPENDED', 

64 'STATUS_TRACING_STOP', 'STATUS_WAITING', 'STATUS_WAKE_KILL', 

65 'STATUS_WAKING', 'STATUS_ZOMBIE', 'STATUS_PARKED', 

66 # other constants 

67 'ENCODING', 'ENCODING_ERRS', 'AF_INET6', 

68 # named tuples 

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

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

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

72 # utility functions 

73 'conn_tmap', 'deprecated_method', 'isfile_strict', 'memoize', 

74 'parse_environ_block', 'path_exists_strict', 'usage_percent', 

75 'supports_ipv6', 'sockfam_to_enum', 'socktype_to_enum', "wrap_numbers", 

76 'open_text', 'open_binary', 'cat', 'bcat', 

77 'bytes2human', 'conn_to_ntuple', 'debug', 

78 # shell utils 

79 'hilite', 'term_supports_colors', 'print_color', 

80] 

81# fmt: on 

82 

83 

84# =================================================================== 

85# --- OS constants 

86# =================================================================== 

87 

88 

89POSIX = os.name == "posix" 

90WINDOWS = os.name == "nt" 

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

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

93OSX = MACOS # deprecated alias 

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

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

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

97BSD = FREEBSD or OPENBSD or NETBSD 

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

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

100 

101 

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

103# --- API constants 

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

105 

106 

107# Process.status() 

108STATUS_RUNNING = "running" 

109STATUS_SLEEPING = "sleeping" 

110STATUS_DISK_SLEEP = "disk-sleep" 

111STATUS_STOPPED = "stopped" 

112STATUS_TRACING_STOP = "tracing-stop" 

113STATUS_ZOMBIE = "zombie" 

114STATUS_DEAD = "dead" 

115STATUS_WAKE_KILL = "wake-kill" 

116STATUS_WAKING = "waking" 

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

118STATUS_LOCKED = "locked" # FreeBSD 

119STATUS_WAITING = "waiting" # FreeBSD 

120STATUS_SUSPENDED = "suspended" # NetBSD 

121STATUS_PARKED = "parked" # Linux 

122 

123# Process.connections() and psutil.net_connections() 

124CONN_ESTABLISHED = "ESTABLISHED" 

125CONN_SYN_SENT = "SYN_SENT" 

126CONN_SYN_RECV = "SYN_RECV" 

127CONN_FIN_WAIT1 = "FIN_WAIT1" 

128CONN_FIN_WAIT2 = "FIN_WAIT2" 

129CONN_TIME_WAIT = "TIME_WAIT" 

130CONN_CLOSE = "CLOSE" 

131CONN_CLOSE_WAIT = "CLOSE_WAIT" 

132CONN_LAST_ACK = "LAST_ACK" 

133CONN_LISTEN = "LISTEN" 

134CONN_CLOSING = "CLOSING" 

135CONN_NONE = "NONE" 

136 

137# net_if_stats() 

138if enum is None: 

139 NIC_DUPLEX_FULL = 2 

140 NIC_DUPLEX_HALF = 1 

141 NIC_DUPLEX_UNKNOWN = 0 

142else: 

143 

144 class NicDuplex(enum.IntEnum): 

145 NIC_DUPLEX_FULL = 2 

146 NIC_DUPLEX_HALF = 1 

147 NIC_DUPLEX_UNKNOWN = 0 

148 

149 globals().update(NicDuplex.__members__) 

150 

151# sensors_battery() 

152if enum is None: 

153 POWER_TIME_UNKNOWN = -1 

154 POWER_TIME_UNLIMITED = -2 

155else: 

156 

157 class BatteryTime(enum.IntEnum): 

158 POWER_TIME_UNKNOWN = -1 

159 POWER_TIME_UNLIMITED = -2 

160 

161 globals().update(BatteryTime.__members__) 

162 

163# --- others 

164 

165ENCODING = sys.getfilesystemencoding() 

166if not PY3: 

167 ENCODING_ERRS = "replace" 

168else: 

169 try: 

170 ENCODING_ERRS = sys.getfilesystemencodeerrors() # py 3.6 

171 except AttributeError: 

172 ENCODING_ERRS = "surrogateescape" if POSIX else "replace" 

173 

174 

175# =================================================================== 

176# --- namedtuples 

177# =================================================================== 

178 

179# --- for system functions 

180 

181# fmt: off 

182# psutil.swap_memory() 

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

184 'sout']) 

185# psutil.disk_usage() 

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

187# psutil.disk_io_counters() 

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

189 'read_bytes', 'write_bytes', 

190 'read_time', 'write_time']) 

191# psutil.disk_partitions() 

192sdiskpart = namedtuple('sdiskpart', ['device', 'mountpoint', 'fstype', 'opts', 

193 'maxfile', 'maxpath']) 

194# psutil.net_io_counters() 

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

196 'packets_sent', 'packets_recv', 

197 'errin', 'errout', 

198 'dropin', 'dropout']) 

199# psutil.users() 

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

201# psutil.net_connections() 

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

203 'status', 'pid']) 

204# psutil.net_if_addrs() 

205snicaddr = namedtuple('snicaddr', 

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

207# psutil.net_if_stats() 

208snicstats = namedtuple('snicstats', 

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

210# psutil.cpu_stats() 

211scpustats = namedtuple( 

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

213# psutil.cpu_freq() 

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

215# psutil.sensors_temperatures() 

216shwtemp = namedtuple( 

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

218# psutil.sensors_battery() 

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

220# psutil.sensors_fans() 

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

222# fmt: on 

223 

224# --- for Process methods 

225 

226# psutil.Process.cpu_times() 

227pcputimes = namedtuple( 

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

229) 

230# psutil.Process.open_files() 

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

232# psutil.Process.threads() 

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

234# psutil.Process.uids() 

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

236# psutil.Process.gids() 

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

238# psutil.Process.io_counters() 

239pio = namedtuple( 

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

241) 

242# psutil.Process.ionice() 

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

244# psutil.Process.ctx_switches() 

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

246# psutil.Process.connections() 

247pconn = namedtuple( 

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

249) 

250 

251# psutil.connections() and psutil.Process.connections() 

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

253 

254 

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

256# --- Process.connections() 'kind' parameter mapping 

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

258 

259 

260conn_tmap = { 

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

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

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

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

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

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

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

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

269} 

270 

271if AF_INET6 is not None: 

272 conn_tmap.update({ 

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

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

275 }) 

276 

277if AF_UNIX is not None: 

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

279 

280 

281# ===================================================================== 

282# --- Exceptions 

283# ===================================================================== 

284 

285 

286class Error(Exception): 

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

288 from this one. 

289 """ 

290 

291 __module__ = 'psutil' 

292 

293 def _infodict(self, attrs): 

294 info = collections.OrderedDict() 

295 for name in attrs: 

296 value = getattr(self, name, None) 

297 if value: # noqa 

298 info[name] = value 

299 elif name == "pid" and value == 0: 

300 info[name] = value 

301 return info 

302 

303 def __str__(self): 

304 # invoked on `raise Error` 

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

306 if info: 

307 details = "(%s)" % ", ".join( 

308 ["%s=%r" % (k, v) for k, v in info.items()] 

309 ) 

310 else: 

311 details = None 

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

313 

314 def __repr__(self): 

315 # invoked on `repr(Error)` 

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

317 details = ", ".join(["%s=%r" % (k, v) for k, v in info.items()]) 

318 return "psutil.%s(%s)" % (self.__class__.__name__, details) 

319 

320 

321class NoSuchProcess(Error): 

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

323 or no longer exists. 

324 """ 

325 

326 __module__ = 'psutil' 

327 

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

329 Error.__init__(self) 

330 self.pid = pid 

331 self.name = name 

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

333 

334 

335class ZombieProcess(NoSuchProcess): 

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

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

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

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

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

341 """ 

342 

343 __module__ = 'psutil' 

344 

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

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

347 self.ppid = ppid 

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

349 

350 

351class AccessDenied(Error): 

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

353 

354 __module__ = 'psutil' 

355 

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

357 Error.__init__(self) 

358 self.pid = pid 

359 self.name = name 

360 self.msg = msg or "" 

361 

362 

363class TimeoutExpired(Error): 

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

365 is still alive. 

366 """ 

367 

368 __module__ = 'psutil' 

369 

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

371 Error.__init__(self) 

372 self.seconds = seconds 

373 self.pid = pid 

374 self.name = name 

375 self.msg = "timeout after %s seconds" % seconds 

376 

377 

378# =================================================================== 

379# --- utils 

380# =================================================================== 

381 

382 

383# This should be in _compat.py rather than here, but does not work well 

384# with setup.py importing this module via a sys.path trick. 

385if PY3: 

386 if isinstance(__builtins__, dict): # cpython 

387 exec_ = __builtins__["exec"] 

388 else: # pypy 

389 exec_ = getattr(__builtins__, "exec") # noqa 

390 

391 exec_("""def raise_from(value, from_value): 

392 try: 

393 raise value from from_value 

394 finally: 

395 value = None 

396 """) 

397else: 

398 

399 def raise_from(value, from_value): 

400 raise value 

401 

402 

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

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

405 try: 

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

407 except ZeroDivisionError: 

408 return 0.0 

409 else: 

410 if round_ is not None: 

411 ret = round(ret, round_) 

412 return ret 

413 

414 

415def memoize(fun): 

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

417 positional arguments. 

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

419 

420 >>> @memoize 

421 ... def foo() 

422 ... return 1 

423 ... 

424 >>> foo() 

425 1 

426 >>> foo.cache_clear() 

427 >>> 

428 

429 It supports: 

430 - functions 

431 - classes (acts as a @singleton) 

432 - staticmethods 

433 - classmethods 

434 

435 It does NOT support: 

436 - methods 

437 """ 

438 

439 @functools.wraps(fun) 

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

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

442 try: 

443 return cache[key] 

444 except KeyError: 

445 try: 

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

447 except Exception as err: # noqa: BLE001 

448 raise raise_from(err, None) 

449 return ret 

450 

451 def cache_clear(): 

452 """Clear cache.""" 

453 cache.clear() 

454 

455 cache = {} 

456 wrapper.cache_clear = cache_clear 

457 return wrapper 

458 

459 

460def memoize_when_activated(fun): 

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

462 activated and deactivated on request. 

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

464 accepting no arguments. 

465 

466 >>> class Foo: 

467 ... @memoize 

468 ... def foo() 

469 ... print(1) 

470 ... 

471 >>> f = Foo() 

472 >>> # deactivated (default) 

473 >>> foo() 

474 1 

475 >>> foo() 

476 1 

477 >>> 

478 >>> # activated 

479 >>> foo.cache_activate(self) 

480 >>> foo() 

481 1 

482 >>> foo() 

483 >>> foo() 

484 >>> 

485 """ 

486 

487 @functools.wraps(fun) 

488 def wrapper(self): 

489 try: 

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

491 ret = self._cache[fun] 

492 except AttributeError: 

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

494 try: 

495 return fun(self) 

496 except Exception as err: # noqa: BLE001 

497 raise raise_from(err, None) 

498 except KeyError: 

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

500 # for this entry yet 

501 try: 

502 ret = fun(self) 

503 except Exception as err: # noqa: BLE001 

504 raise raise_from(err, None) 

505 try: 

506 self._cache[fun] = ret 

507 except AttributeError: 

508 # multi-threading race condition, see: 

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

510 pass 

511 return ret 

512 

513 def cache_activate(proc): 

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

515 stored as a "_cache" instance attribute. 

516 """ 

517 proc._cache = {} 

518 

519 def cache_deactivate(proc): 

520 """Deactivate and clear cache.""" 

521 try: 

522 del proc._cache 

523 except AttributeError: 

524 pass 

525 

526 wrapper.cache_activate = cache_activate 

527 wrapper.cache_deactivate = cache_deactivate 

528 return wrapper 

529 

530 

531def isfile_strict(path): 

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

533 exceptions, see: 

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

535 """ 

536 try: 

537 st = os.stat(path) 

538 except OSError as err: 

539 if err.errno in (errno.EPERM, errno.EACCES): 

540 raise 

541 return False 

542 else: 

543 return stat.S_ISREG(st.st_mode) 

544 

545 

546def path_exists_strict(path): 

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

548 exceptions. See: 

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

550 """ 

551 try: 

552 os.stat(path) 

553 except OSError as err: 

554 if err.errno in (errno.EPERM, errno.EACCES): 

555 raise 

556 return False 

557 else: 

558 return True 

559 

560 

561@memoize 

562def supports_ipv6(): 

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

564 if not socket.has_ipv6 or AF_INET6 is None: 

565 return False 

566 try: 

567 sock = socket.socket(AF_INET6, socket.SOCK_STREAM) 

568 with contextlib.closing(sock): 

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

570 return True 

571 except socket.error: 

572 return False 

573 

574 

575def parse_environ_block(data): 

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

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

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

579 ret = {} 

580 pos = 0 

581 

582 # localize global variable to speed up access. 

583 WINDOWS_ = WINDOWS 

584 while True: 

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

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

587 if next_pos <= pos: 

588 break 

589 # there might not be an equals sign 

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

591 if equal_pos > pos: 

592 key = data[pos:equal_pos] 

593 value = data[equal_pos + 1 : next_pos] 

594 # Windows expects environment variables to be uppercase only 

595 if WINDOWS_: 

596 key = key.upper() 

597 ret[key] = value 

598 pos = next_pos + 1 

599 

600 return ret 

601 

602 

603def sockfam_to_enum(num): 

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

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

606 """ 

607 if enum is None: 

608 return num 

609 else: # pragma: no cover 

610 try: 

611 return socket.AddressFamily(num) 

612 except ValueError: 

613 return num 

614 

615 

616def socktype_to_enum(num): 

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

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

619 """ 

620 if enum is None: 

621 return num 

622 else: # pragma: no cover 

623 try: 

624 return socket.SocketKind(num) 

625 except ValueError: 

626 return num 

627 

628 

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

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

631 if fam in (socket.AF_INET, AF_INET6): 

632 if laddr: 

633 laddr = addr(*laddr) 

634 if raddr: 

635 raddr = addr(*raddr) 

636 if type_ == socket.SOCK_STREAM and fam in (AF_INET, AF_INET6): 

637 status = status_map.get(status, CONN_NONE) 

638 else: 

639 status = CONN_NONE # ignore whatever C returned to us 

640 fam = sockfam_to_enum(fam) 

641 type_ = socktype_to_enum(type_) 

642 if pid is None: 

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

644 else: 

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

646 

647 

648def deprecated_method(replacement): 

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

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

651 """ 

652 

653 def outer(fun): 

654 msg = "%s() is deprecated and will be removed; use %s() instead" % ( 

655 fun.__name__, 

656 replacement, 

657 ) 

658 if fun.__doc__ is None: 

659 fun.__doc__ = msg 

660 

661 @functools.wraps(fun) 

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

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

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

665 

666 return inner 

667 

668 return outer 

669 

670 

671class _WrapNumbers: 

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

673 (reset to zero). 

674 """ 

675 

676 def __init__(self): 

677 self.lock = threading.Lock() 

678 self.cache = {} 

679 self.reminders = {} 

680 self.reminder_keys = {} 

681 

682 def _add_dict(self, input_dict, name): 

683 assert name not in self.cache 

684 assert name not in self.reminders 

685 assert name not in self.reminder_keys 

686 self.cache[name] = input_dict 

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

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

689 

690 def _remove_dead_reminders(self, input_dict, name): 

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

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

693 """ 

694 old_dict = self.cache[name] 

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

696 for gone_key in gone_keys: 

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

698 del self.reminders[name][remkey] 

699 del self.reminder_keys[name][gone_key] 

700 

701 def run(self, input_dict, name): 

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

703 Return an updated copy of `input_dict`. 

704 """ 

705 if name not in self.cache: 

706 # This was the first call. 

707 self._add_dict(input_dict, name) 

708 return input_dict 

709 

710 self._remove_dead_reminders(input_dict, name) 

711 

712 old_dict = self.cache[name] 

713 new_dict = {} 

714 for key in input_dict: 

715 input_tuple = input_dict[key] 

716 try: 

717 old_tuple = old_dict[key] 

718 except KeyError: 

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

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

721 new_dict[key] = input_tuple 

722 continue 

723 

724 bits = [] 

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

726 input_value = input_tuple[i] 

727 old_value = old_tuple[i] 

728 remkey = (key, i) 

729 if input_value < old_value: 

730 # it wrapped! 

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

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

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

734 

735 new_dict[key] = tuple(bits) 

736 

737 self.cache[name] = input_dict 

738 return new_dict 

739 

740 def cache_clear(self, name=None): 

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

742 with self.lock: 

743 if name is None: 

744 self.cache.clear() 

745 self.reminders.clear() 

746 self.reminder_keys.clear() 

747 else: 

748 self.cache.pop(name, None) 

749 self.reminders.pop(name, None) 

750 self.reminder_keys.pop(name, None) 

751 

752 def cache_info(self): 

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

754 with self.lock: 

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

756 

757 

758def wrap_numbers(input_dict, name): 

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

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

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

762 """ 

763 with _wn.lock: 

764 return _wn.run(input_dict, name) 

765 

766 

767_wn = _WrapNumbers() 

768wrap_numbers.cache_clear = _wn.cache_clear 

769wrap_numbers.cache_info = _wn.cache_info 

770 

771 

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

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

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

775# ... for line in f: 

776# ... ... 

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

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

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

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

781# On Python 2 this also speeds up the reading of big files: 

782# (namely /proc/{pid}/smaps and /proc/net/*): 

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

784FILE_READ_BUFFER_SIZE = 32 * 1024 

785 

786 

787def open_binary(fname): 

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

789 

790 

791def open_text(fname): 

792 """On Python 3 opens a file in text mode by using fs encoding and 

793 a proper en/decoding errors handler. 

794 On Python 2 this is just an alias for open(name, 'rt'). 

795 """ 

796 if not PY3: 

797 return open(fname, buffering=FILE_READ_BUFFER_SIZE) 

798 

799 # See: 

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

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

802 fobj = open( 

803 fname, 

804 buffering=FILE_READ_BUFFER_SIZE, 

805 encoding=ENCODING, 

806 errors=ENCODING_ERRS, 

807 ) 

808 try: 

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

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

811 fobj._CHUNK_SIZE = FILE_READ_BUFFER_SIZE 

812 except AttributeError: 

813 pass 

814 except Exception: 

815 fobj.close() 

816 raise 

817 

818 return fobj 

819 

820 

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

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

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

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

825 it can't be read(). 

826 """ 

827 if fallback is _DEFAULT: 

828 with _open(fname) as f: 

829 return f.read() 

830 else: 

831 try: 

832 with _open(fname) as f: 

833 return f.read() 

834 except (IOError, OSError): 

835 return fallback 

836 

837 

838def bcat(fname, fallback=_DEFAULT): 

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

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

841 

842 

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

844 """Used by various scripts. See: http://goo.gl/zeJZl. 

845 

846 >>> bytes2human(10000) 

847 '9.8K' 

848 >>> bytes2human(100001221) 

849 '95.4M' 

850 """ 

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

852 prefix = {} 

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

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

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

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

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

858 return format % locals() 

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

860 

861 

862def get_procfs_path(): 

863 """Return updated psutil.PROCFS_PATH constant.""" 

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

865 

866 

867if PY3: 

868 

869 def decode(s): 

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

871 

872else: 

873 

874 def decode(s): 

875 return s 

876 

877 

878# ===================================================================== 

879# --- shell utils 

880# ===================================================================== 

881 

882 

883@memoize 

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

885 if os.name == 'nt': 

886 return True 

887 try: 

888 import curses 

889 

890 assert file.isatty() 

891 curses.setupterm() 

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

893 except Exception: # noqa: BLE001 

894 return False 

895 else: 

896 return True 

897 

898 

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

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

901 if not term_supports_colors(): 

902 return s 

903 attr = [] 

904 colors = dict( 

905 blue='34', 

906 brown='33', 

907 darkgrey='30', 

908 green='32', 

909 grey='37', 

910 lightblue='36', 

911 red='91', 

912 violet='35', 

913 yellow='93', 

914 ) 

915 colors[None] = '29' 

916 try: 

917 color = colors[color] 

918 except KeyError: 

919 raise ValueError( 

920 "invalid color %r; choose between %s" % (list(colors.keys())) 

921 ) 

922 attr.append(color) 

923 if bold: 

924 attr.append('1') 

925 return '\x1b[%sm%s\x1b[0m' % (';'.join(attr), s) 

926 

927 

928def print_color( 

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

930): # pragma: no cover 

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

932 if not term_supports_colors(): 

933 print(s, file=file) # NOQA 

934 elif POSIX: 

935 print(hilite(s, color, bold), file=file) # NOQA 

936 else: 

937 import ctypes 

938 

939 DEFAULT_COLOR = 7 

940 GetStdHandle = ctypes.windll.Kernel32.GetStdHandle 

941 SetConsoleTextAttribute = ( 

942 ctypes.windll.Kernel32.SetConsoleTextAttribute 

943 ) 

944 

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

946 colors[None] = DEFAULT_COLOR 

947 try: 

948 color = colors[color] 

949 except KeyError: 

950 raise ValueError( 

951 "invalid color %r; choose between %r" 

952 % (color, list(colors.keys())) 

953 ) 

954 if bold and color <= 7: 

955 color += 8 

956 

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

958 GetStdHandle.restype = ctypes.c_ulong 

959 handle = GetStdHandle(handle_id) 

960 SetConsoleTextAttribute(handle, color) 

961 try: 

962 print(s, file=file) # NOQA 

963 finally: 

964 SetConsoleTextAttribute(handle, DEFAULT_COLOR) 

965 

966 

967def debug(msg): 

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

969 if PSUTIL_DEBUG: 

970 import inspect 

971 

972 fname, lineno, _, lines, index = inspect.getframeinfo( 

973 inspect.currentframe().f_back 

974 ) 

975 if isinstance(msg, Exception): 

976 if isinstance(msg, (OSError, IOError, EnvironmentError)): 

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

978 msg = "ignoring %s" % msg 

979 else: 

980 msg = "ignoring %r" % msg 

981 print( # noqa 

982 "psutil-debug [%s:%s]> %s" % (fname, lineno, msg), file=sys.stderr 

983 )