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

423 statements  

« prev     ^ index     » next       coverage.py v7.3.1, created at 2023-09-25 07:00 +0000

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__all__ = [ 

51 # OS constants 

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

53 'SUNOS', 'WINDOWS', 

54 # connection constants 

55 'CONN_CLOSE', 'CONN_CLOSE_WAIT', 'CONN_CLOSING', 'CONN_ESTABLISHED', 

56 'CONN_FIN_WAIT1', 'CONN_FIN_WAIT2', 'CONN_LAST_ACK', 'CONN_LISTEN', 

57 'CONN_NONE', 'CONN_SYN_RECV', 'CONN_SYN_SENT', 'CONN_TIME_WAIT', 

58 # net constants 

59 'NIC_DUPLEX_FULL', 'NIC_DUPLEX_HALF', 'NIC_DUPLEX_UNKNOWN', 

60 # process status constants 

61 'STATUS_DEAD', 'STATUS_DISK_SLEEP', 'STATUS_IDLE', 'STATUS_LOCKED', 

62 'STATUS_RUNNING', 'STATUS_SLEEPING', 'STATUS_STOPPED', 'STATUS_SUSPENDED', 

63 'STATUS_TRACING_STOP', 'STATUS_WAITING', 'STATUS_WAKE_KILL', 

64 'STATUS_WAKING', 'STATUS_ZOMBIE', 'STATUS_PARKED', 

65 # other constants 

66 'ENCODING', 'ENCODING_ERRS', 'AF_INET6', 

67 # named tuples 

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

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

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

71 # utility functions 

72 'conn_tmap', 'deprecated_method', 'isfile_strict', 'memoize', 

73 'parse_environ_block', 'path_exists_strict', 'usage_percent', 

74 'supports_ipv6', 'sockfam_to_enum', 'socktype_to_enum', "wrap_numbers", 

75 'open_text', 'open_binary', 'cat', 'bcat', 

76 'bytes2human', 'conn_to_ntuple', 'debug', 

77 # shell utils 

78 'hilite', 'term_supports_colors', 'print_color', 

79] 

80 

81 

82# =================================================================== 

83# --- OS constants 

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

85 

86 

87POSIX = os.name == "posix" 

88WINDOWS = os.name == "nt" 

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

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

91OSX = MACOS # deprecated alias 

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

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

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

95BSD = FREEBSD or OPENBSD or NETBSD 

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

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

98 

99 

100# =================================================================== 

101# --- API constants 

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

103 

104 

105# Process.status() 

106STATUS_RUNNING = "running" 

107STATUS_SLEEPING = "sleeping" 

108STATUS_DISK_SLEEP = "disk-sleep" 

109STATUS_STOPPED = "stopped" 

110STATUS_TRACING_STOP = "tracing-stop" 

111STATUS_ZOMBIE = "zombie" 

112STATUS_DEAD = "dead" 

113STATUS_WAKE_KILL = "wake-kill" 

114STATUS_WAKING = "waking" 

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

116STATUS_LOCKED = "locked" # FreeBSD 

117STATUS_WAITING = "waiting" # FreeBSD 

118STATUS_SUSPENDED = "suspended" # NetBSD 

119STATUS_PARKED = "parked" # Linux 

120 

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

122CONN_ESTABLISHED = "ESTABLISHED" 

123CONN_SYN_SENT = "SYN_SENT" 

124CONN_SYN_RECV = "SYN_RECV" 

125CONN_FIN_WAIT1 = "FIN_WAIT1" 

126CONN_FIN_WAIT2 = "FIN_WAIT2" 

127CONN_TIME_WAIT = "TIME_WAIT" 

128CONN_CLOSE = "CLOSE" 

129CONN_CLOSE_WAIT = "CLOSE_WAIT" 

130CONN_LAST_ACK = "LAST_ACK" 

131CONN_LISTEN = "LISTEN" 

132CONN_CLOSING = "CLOSING" 

133CONN_NONE = "NONE" 

134 

135# net_if_stats() 

136if enum is None: 

137 NIC_DUPLEX_FULL = 2 

138 NIC_DUPLEX_HALF = 1 

139 NIC_DUPLEX_UNKNOWN = 0 

140else: 

141 class NicDuplex(enum.IntEnum): 

142 NIC_DUPLEX_FULL = 2 

143 NIC_DUPLEX_HALF = 1 

144 NIC_DUPLEX_UNKNOWN = 0 

145 

146 globals().update(NicDuplex.__members__) 

147 

148# sensors_battery() 

149if enum is None: 

150 POWER_TIME_UNKNOWN = -1 

151 POWER_TIME_UNLIMITED = -2 

152else: 

153 class BatteryTime(enum.IntEnum): 

154 POWER_TIME_UNKNOWN = -1 

155 POWER_TIME_UNLIMITED = -2 

156 

157 globals().update(BatteryTime.__members__) 

158 

159# --- others 

160 

161ENCODING = sys.getfilesystemencoding() 

162if not PY3: 

163 ENCODING_ERRS = "replace" 

164else: 

165 try: 

166 ENCODING_ERRS = sys.getfilesystemencodeerrors() # py 3.6 

167 except AttributeError: 

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

169 

170 

171# =================================================================== 

172# --- namedtuples 

173# =================================================================== 

174 

175# --- for system functions 

176 

177# psutil.swap_memory() 

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

179 'sout']) 

180# psutil.disk_usage() 

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

182# psutil.disk_io_counters() 

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

184 'read_bytes', 'write_bytes', 

185 'read_time', 'write_time']) 

186# psutil.disk_partitions() 

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

188 'maxfile', 'maxpath']) 

189# psutil.net_io_counters() 

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

191 'packets_sent', 'packets_recv', 

192 'errin', 'errout', 

193 'dropin', 'dropout']) 

194# psutil.users() 

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

196# psutil.net_connections() 

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

198 'status', 'pid']) 

199# psutil.net_if_addrs() 

200snicaddr = namedtuple('snicaddr', 

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

202# psutil.net_if_stats() 

203snicstats = namedtuple('snicstats', 

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

205# psutil.cpu_stats() 

206scpustats = namedtuple( 

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

208# psutil.cpu_freq() 

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

210# psutil.sensors_temperatures() 

211shwtemp = namedtuple( 

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

213# psutil.sensors_battery() 

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

215# psutil.sensors_fans() 

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

217 

218# --- for Process methods 

219 

220# psutil.Process.cpu_times() 

221pcputimes = namedtuple('pcputimes', 

222 ['user', 'system', 'children_user', 'children_system']) 

223# psutil.Process.open_files() 

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

225# psutil.Process.threads() 

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

227# psutil.Process.uids() 

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

229# psutil.Process.gids() 

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

231# psutil.Process.io_counters() 

232pio = namedtuple('pio', ['read_count', 'write_count', 

233 'read_bytes', 'write_bytes']) 

234# psutil.Process.ionice() 

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

236# psutil.Process.ctx_switches() 

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

238# psutil.Process.connections() 

239pconn = namedtuple('pconn', ['fd', 'family', 'type', 'laddr', 'raddr', 

240 'status']) 

241 

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

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

244 

245 

246# =================================================================== 

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

248# =================================================================== 

249 

250 

251conn_tmap = { 

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

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

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

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

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

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

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

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

260} 

261 

262if AF_INET6 is not None: 

263 conn_tmap.update({ 

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

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

266 }) 

267 

268if AF_UNIX is not None: 

269 conn_tmap.update({ 

270 "unix": ([AF_UNIX], [SOCK_STREAM, SOCK_DGRAM]), 

271 }) 

272 

273 

274# ===================================================================== 

275# --- Exceptions 

276# ===================================================================== 

277 

278 

279class Error(Exception): 

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

281 from this one. 

282 """ 

283 __module__ = 'psutil' 

284 

285 def _infodict(self, attrs): 

286 info = collections.OrderedDict() 

287 for name in attrs: 

288 value = getattr(self, name, None) 

289 if value: 

290 info[name] = value 

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

292 info[name] = value 

293 return info 

294 

295 def __str__(self): 

296 # invoked on `raise Error` 

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

298 if info: 

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

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

301 else: 

302 details = None 

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

304 

305 def __repr__(self): 

306 # invoked on `repr(Error)` 

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

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

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

310 

311 

312class NoSuchProcess(Error): 

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

314 or no longer exists. 

315 """ 

316 __module__ = 'psutil' 

317 

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

319 Error.__init__(self) 

320 self.pid = pid 

321 self.name = name 

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

323 

324 

325class ZombieProcess(NoSuchProcess): 

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

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

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

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

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

331 """ 

332 __module__ = 'psutil' 

333 

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

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

336 self.ppid = ppid 

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

338 

339 

340class AccessDenied(Error): 

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

342 __module__ = 'psutil' 

343 

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

345 Error.__init__(self) 

346 self.pid = pid 

347 self.name = name 

348 self.msg = msg or "" 

349 

350 

351class TimeoutExpired(Error): 

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

353 is still alive. 

354 """ 

355 __module__ = 'psutil' 

356 

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

358 Error.__init__(self) 

359 self.seconds = seconds 

360 self.pid = pid 

361 self.name = name 

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

363 

364 

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

366# --- utils 

367# =================================================================== 

368 

369 

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

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

372if PY3: 

373 if isinstance(__builtins__, dict): # cpython 

374 exec_ = __builtins__["exec"] 

375 else: # pypy 

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

377 

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

379 try: 

380 raise value from from_value 

381 finally: 

382 value = None 

383 """) 

384else: 

385 def raise_from(value, from_value): 

386 raise value 

387 

388 

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

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

391 try: 

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

393 except ZeroDivisionError: 

394 return 0.0 

395 else: 

396 if round_ is not None: 

397 ret = round(ret, round_) 

398 return ret 

399 

400 

401def memoize(fun): 

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

403 positional arguments. 

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

405 

406 >>> @memoize 

407 ... def foo() 

408 ... return 1 

409 ... 

410 >>> foo() 

411 1 

412 >>> foo.cache_clear() 

413 >>> 

414 

415 It supports: 

416 - functions 

417 - classes (acts as a @singleton) 

418 - staticmethods 

419 - classmethods 

420 

421 It does NOT support: 

422 - methods 

423 """ 

424 @functools.wraps(fun) 

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

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

427 try: 

428 return cache[key] 

429 except KeyError: 

430 try: 

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

432 except Exception as err: 

433 raise raise_from(err, None) 

434 return ret 

435 

436 def cache_clear(): 

437 """Clear cache.""" 

438 cache.clear() 

439 

440 cache = {} 

441 wrapper.cache_clear = cache_clear 

442 return wrapper 

443 

444 

445def memoize_when_activated(fun): 

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

447 activated and deactivated on request. 

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

449 accepting no arguments. 

450 

451 >>> class Foo: 

452 ... @memoize 

453 ... def foo() 

454 ... print(1) 

455 ... 

456 >>> f = Foo() 

457 >>> # deactivated (default) 

458 >>> foo() 

459 1 

460 >>> foo() 

461 1 

462 >>> 

463 >>> # activated 

464 >>> foo.cache_activate(self) 

465 >>> foo() 

466 1 

467 >>> foo() 

468 >>> foo() 

469 >>> 

470 """ 

471 @functools.wraps(fun) 

472 def wrapper(self): 

473 try: 

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

475 ret = self._cache[fun] 

476 except AttributeError: 

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

478 try: 

479 return fun(self) 

480 except Exception as err: 

481 raise raise_from(err, None) 

482 except KeyError: 

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

484 # for this entry yet 

485 try: 

486 ret = fun(self) 

487 except Exception as err: 

488 raise raise_from(err, None) 

489 try: 

490 self._cache[fun] = ret 

491 except AttributeError: 

492 # multi-threading race condition, see: 

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

494 pass 

495 return ret 

496 

497 def cache_activate(proc): 

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

499 stored as a "_cache" instance attribute.""" 

500 proc._cache = {} 

501 

502 def cache_deactivate(proc): 

503 """Deactivate and clear cache.""" 

504 try: 

505 del proc._cache 

506 except AttributeError: 

507 pass 

508 

509 wrapper.cache_activate = cache_activate 

510 wrapper.cache_deactivate = cache_deactivate 

511 return wrapper 

512 

513 

514def isfile_strict(path): 

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

516 exceptions, see: 

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

518 """ 

519 try: 

520 st = os.stat(path) 

521 except OSError as err: 

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

523 raise 

524 return False 

525 else: 

526 return stat.S_ISREG(st.st_mode) 

527 

528 

529def path_exists_strict(path): 

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

531 exceptions, see: 

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

533 """ 

534 try: 

535 os.stat(path) 

536 except OSError as err: 

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

538 raise 

539 return False 

540 else: 

541 return True 

542 

543 

544@memoize 

545def supports_ipv6(): 

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

547 if not socket.has_ipv6 or AF_INET6 is None: 

548 return False 

549 try: 

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

551 with contextlib.closing(sock): 

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

553 return True 

554 except socket.error: 

555 return False 

556 

557 

558def parse_environ_block(data): 

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

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

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

562 ret = {} 

563 pos = 0 

564 

565 # localize global variable to speed up access. 

566 WINDOWS_ = WINDOWS 

567 while True: 

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

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

570 if next_pos <= pos: 

571 break 

572 # there might not be an equals sign 

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

574 if equal_pos > pos: 

575 key = data[pos:equal_pos] 

576 value = data[equal_pos + 1:next_pos] 

577 # Windows expects environment variables to be uppercase only 

578 if WINDOWS_: 

579 key = key.upper() 

580 ret[key] = value 

581 pos = next_pos + 1 

582 

583 return ret 

584 

585 

586def sockfam_to_enum(num): 

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

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

589 """ 

590 if enum is None: 

591 return num 

592 else: # pragma: no cover 

593 try: 

594 return socket.AddressFamily(num) 

595 except ValueError: 

596 return num 

597 

598 

599def socktype_to_enum(num): 

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

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

602 """ 

603 if enum is None: 

604 return num 

605 else: # pragma: no cover 

606 try: 

607 return socket.SocketKind(num) 

608 except ValueError: 

609 return num 

610 

611 

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

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

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

615 if laddr: 

616 laddr = addr(*laddr) 

617 if raddr: 

618 raddr = addr(*raddr) 

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

620 status = status_map.get(status, CONN_NONE) 

621 else: 

622 status = CONN_NONE # ignore whatever C returned to us 

623 fam = sockfam_to_enum(fam) 

624 type_ = socktype_to_enum(type_) 

625 if pid is None: 

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

627 else: 

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

629 

630 

631def deprecated_method(replacement): 

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

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

634 """ 

635 def outer(fun): 

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

637 fun.__name__, replacement) 

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 return inner 

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.keys(): 

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# On Python 2 this also speeds up the reading of big files: 

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

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 """On Python 3 opens a file in text mode by using fs encoding and 

771 a proper en/decoding errors handler. 

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

773 """ 

774 if not PY3: 

775 return open(fname, "rt", buffering=FILE_READ_BUFFER_SIZE) 

776 

777 # See: 

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

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

780 fobj = open(fname, "rt", buffering=FILE_READ_BUFFER_SIZE, 

781 encoding=ENCODING, errors=ENCODING_ERRS) 

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 (IOError, 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: 

819 http://goo.gl/zeJZl 

820 

821 >>> bytes2human(10000) 

822 '9.8K' 

823 >>> bytes2human(100001221) 

824 '95.4M' 

825 """ 

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

827 prefix = {} 

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

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

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

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

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

833 return format % locals() 

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

835 

836 

837def get_procfs_path(): 

838 """Return updated psutil.PROCFS_PATH constant.""" 

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

840 

841 

842if PY3: 

843 def decode(s): 

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

845else: 

846 def decode(s): 

847 return s 

848 

849 

850# ===================================================================== 

851# --- shell utils 

852# ===================================================================== 

853 

854 

855@memoize 

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

857 if os.name == 'nt': 

858 return True 

859 try: 

860 import curses 

861 assert file.isatty() 

862 curses.setupterm() 

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

864 except Exception: 

865 return False 

866 else: 

867 return True 

868 

869 

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

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

872 if not term_supports_colors(): 

873 return s 

874 attr = [] 

875 colors = dict(green='32', red='91', brown='33', yellow='93', blue='34', 

876 violet='35', lightblue='36', grey='37', darkgrey='30') 

877 colors[None] = '29' 

878 try: 

879 color = colors[color] 

880 except KeyError: 

881 raise ValueError("invalid color %r; choose between %s" % ( 

882 list(colors.keys()))) 

883 attr.append(color) 

884 if bold: 

885 attr.append('1') 

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

887 

888 

889def print_color( 

890 s, color=None, bold=False, file=sys.stdout): # pragma: no cover 

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

892 if not term_supports_colors(): 

893 print(s, file=file) # NOQA 

894 elif POSIX: 

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

896 else: 

897 import ctypes 

898 

899 DEFAULT_COLOR = 7 

900 GetStdHandle = ctypes.windll.Kernel32.GetStdHandle 

901 SetConsoleTextAttribute = \ 

902 ctypes.windll.Kernel32.SetConsoleTextAttribute 

903 

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

905 colors[None] = DEFAULT_COLOR 

906 try: 

907 color = colors[color] 

908 except KeyError: 

909 raise ValueError("invalid color %r; choose between %r" % ( 

910 color, list(colors.keys()))) 

911 if bold and color <= 7: 

912 color += 8 

913 

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

915 GetStdHandle.restype = ctypes.c_ulong 

916 handle = GetStdHandle(handle_id) 

917 SetConsoleTextAttribute(handle, color) 

918 try: 

919 print(s, file=file) # NOQA 

920 finally: 

921 SetConsoleTextAttribute(handle, DEFAULT_COLOR) 

922 

923 

924def debug(msg): 

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

926 if PSUTIL_DEBUG: 

927 import inspect 

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

929 inspect.currentframe().f_back) 

930 if isinstance(msg, Exception): 

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

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

933 msg = "ignoring %s" % msg 

934 else: 

935 msg = "ignoring %r" % msg 

936 print("psutil-debug [%s:%s]> %s" % (fname, lineno, msg), # NOQA 

937 file=sys.stderr)