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

423 statements  

« prev     ^ index     » next       coverage.py v7.2.7, created at 2023-06-07 06:35 +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 

38if sys.version_info >= (3, 4): 

39 import enum 

40else: 

41 enum = None 

42 

43 

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

45PY3 = sys.version_info[0] == 3 

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

47_DEFAULT = object() 

48 

49__all__ = [ 

50 # OS constants 

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

52 'SUNOS', 'WINDOWS', 

53 # connection constants 

54 'CONN_CLOSE', 'CONN_CLOSE_WAIT', 'CONN_CLOSING', 'CONN_ESTABLISHED', 

55 'CONN_FIN_WAIT1', 'CONN_FIN_WAIT2', 'CONN_LAST_ACK', 'CONN_LISTEN', 

56 'CONN_NONE', 'CONN_SYN_RECV', 'CONN_SYN_SENT', 'CONN_TIME_WAIT', 

57 # net constants 

58 'NIC_DUPLEX_FULL', 'NIC_DUPLEX_HALF', 'NIC_DUPLEX_UNKNOWN', 

59 # process status constants 

60 'STATUS_DEAD', 'STATUS_DISK_SLEEP', 'STATUS_IDLE', 'STATUS_LOCKED', 

61 'STATUS_RUNNING', 'STATUS_SLEEPING', 'STATUS_STOPPED', 'STATUS_SUSPENDED', 

62 'STATUS_TRACING_STOP', 'STATUS_WAITING', 'STATUS_WAKE_KILL', 

63 'STATUS_WAKING', 'STATUS_ZOMBIE', 'STATUS_PARKED', 

64 # other constants 

65 'ENCODING', 'ENCODING_ERRS', 'AF_INET6', 

66 # named tuples 

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

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

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

70 # utility functions 

71 'conn_tmap', 'deprecated_method', 'isfile_strict', 'memoize', 

72 'parse_environ_block', 'path_exists_strict', 'usage_percent', 

73 'supports_ipv6', 'sockfam_to_enum', 'socktype_to_enum', "wrap_numbers", 

74 'open_text', 'open_binary', 'cat', 'bcat', 

75 'bytes2human', 'conn_to_ntuple', 'debug', 

76 # shell utils 

77 'hilite', 'term_supports_colors', 'print_color', 

78] 

79 

80 

81# =================================================================== 

82# --- OS constants 

83# =================================================================== 

84 

85 

86POSIX = os.name == "posix" 

87WINDOWS = os.name == "nt" 

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

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

90OSX = MACOS # deprecated alias 

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

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

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

94BSD = FREEBSD or OPENBSD or NETBSD 

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

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

97 

98 

99# =================================================================== 

100# --- API constants 

101# =================================================================== 

102 

103 

104# Process.status() 

105STATUS_RUNNING = "running" 

106STATUS_SLEEPING = "sleeping" 

107STATUS_DISK_SLEEP = "disk-sleep" 

108STATUS_STOPPED = "stopped" 

109STATUS_TRACING_STOP = "tracing-stop" 

110STATUS_ZOMBIE = "zombie" 

111STATUS_DEAD = "dead" 

112STATUS_WAKE_KILL = "wake-kill" 

113STATUS_WAKING = "waking" 

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

115STATUS_LOCKED = "locked" # FreeBSD 

116STATUS_WAITING = "waiting" # FreeBSD 

117STATUS_SUSPENDED = "suspended" # NetBSD 

118STATUS_PARKED = "parked" # Linux 

119 

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

121CONN_ESTABLISHED = "ESTABLISHED" 

122CONN_SYN_SENT = "SYN_SENT" 

123CONN_SYN_RECV = "SYN_RECV" 

124CONN_FIN_WAIT1 = "FIN_WAIT1" 

125CONN_FIN_WAIT2 = "FIN_WAIT2" 

126CONN_TIME_WAIT = "TIME_WAIT" 

127CONN_CLOSE = "CLOSE" 

128CONN_CLOSE_WAIT = "CLOSE_WAIT" 

129CONN_LAST_ACK = "LAST_ACK" 

130CONN_LISTEN = "LISTEN" 

131CONN_CLOSING = "CLOSING" 

132CONN_NONE = "NONE" 

133 

134# net_if_stats() 

135if enum is None: 

136 NIC_DUPLEX_FULL = 2 

137 NIC_DUPLEX_HALF = 1 

138 NIC_DUPLEX_UNKNOWN = 0 

139else: 

140 class NicDuplex(enum.IntEnum): 

141 NIC_DUPLEX_FULL = 2 

142 NIC_DUPLEX_HALF = 1 

143 NIC_DUPLEX_UNKNOWN = 0 

144 

145 globals().update(NicDuplex.__members__) 

146 

147# sensors_battery() 

148if enum is None: 

149 POWER_TIME_UNKNOWN = -1 

150 POWER_TIME_UNLIMITED = -2 

151else: 

152 class BatteryTime(enum.IntEnum): 

153 POWER_TIME_UNKNOWN = -1 

154 POWER_TIME_UNLIMITED = -2 

155 

156 globals().update(BatteryTime.__members__) 

157 

158# --- others 

159 

160ENCODING = sys.getfilesystemencoding() 

161if not PY3: 

162 ENCODING_ERRS = "replace" 

163else: 

164 try: 

165 ENCODING_ERRS = sys.getfilesystemencodeerrors() # py 3.6 

166 except AttributeError: 

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

168 

169 

170# =================================================================== 

171# --- namedtuples 

172# =================================================================== 

173 

174# --- for system functions 

175 

176# psutil.swap_memory() 

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

178 'sout']) 

179# psutil.disk_usage() 

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

181# psutil.disk_io_counters() 

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

183 'read_bytes', 'write_bytes', 

184 'read_time', 'write_time']) 

185# psutil.disk_partitions() 

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

187 'maxfile', 'maxpath']) 

188# psutil.net_io_counters() 

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

190 'packets_sent', 'packets_recv', 

191 'errin', 'errout', 

192 'dropin', 'dropout']) 

193# psutil.users() 

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

195# psutil.net_connections() 

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

197 'status', 'pid']) 

198# psutil.net_if_addrs() 

199snicaddr = namedtuple('snicaddr', 

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

201# psutil.net_if_stats() 

202snicstats = namedtuple('snicstats', 

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

204# psutil.cpu_stats() 

205scpustats = namedtuple( 

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

207# psutil.cpu_freq() 

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

209# psutil.sensors_temperatures() 

210shwtemp = namedtuple( 

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

212# psutil.sensors_battery() 

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

214# psutil.sensors_fans() 

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

216 

217# --- for Process methods 

218 

219# psutil.Process.cpu_times() 

220pcputimes = namedtuple('pcputimes', 

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

222# psutil.Process.open_files() 

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

224# psutil.Process.threads() 

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

226# psutil.Process.uids() 

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

228# psutil.Process.gids() 

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

230# psutil.Process.io_counters() 

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

232 'read_bytes', 'write_bytes']) 

233# psutil.Process.ionice() 

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

235# psutil.Process.ctx_switches() 

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

237# psutil.Process.connections() 

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

239 'status']) 

240 

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

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

243 

244 

245# =================================================================== 

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

247# =================================================================== 

248 

249 

250conn_tmap = { 

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

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

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

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

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

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

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

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

259} 

260 

261if AF_INET6 is not None: 

262 conn_tmap.update({ 

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

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

265 }) 

266 

267if AF_UNIX is not None: 

268 conn_tmap.update({ 

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

270 }) 

271 

272 

273# ===================================================================== 

274# --- Exceptions 

275# ===================================================================== 

276 

277 

278class Error(Exception): 

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

280 from this one. 

281 """ 

282 __module__ = 'psutil' 

283 

284 def _infodict(self, attrs): 

285 info = collections.OrderedDict() 

286 for name in attrs: 

287 value = getattr(self, name, None) 

288 if value: 

289 info[name] = value 

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

291 info[name] = value 

292 return info 

293 

294 def __str__(self): 

295 # invoked on `raise Error` 

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

297 if info: 

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

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

300 else: 

301 details = None 

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

303 

304 def __repr__(self): 

305 # invoked on `repr(Error)` 

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

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

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

309 

310 

311class NoSuchProcess(Error): 

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

313 or no longer exists. 

314 """ 

315 __module__ = 'psutil' 

316 

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

318 Error.__init__(self) 

319 self.pid = pid 

320 self.name = name 

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

322 

323 

324class ZombieProcess(NoSuchProcess): 

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

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

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

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

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

330 """ 

331 __module__ = 'psutil' 

332 

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

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

335 self.ppid = ppid 

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

337 

338 

339class AccessDenied(Error): 

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

341 __module__ = 'psutil' 

342 

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

344 Error.__init__(self) 

345 self.pid = pid 

346 self.name = name 

347 self.msg = msg or "" 

348 

349 

350class TimeoutExpired(Error): 

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

352 is still alive. 

353 """ 

354 __module__ = 'psutil' 

355 

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

357 Error.__init__(self) 

358 self.seconds = seconds 

359 self.pid = pid 

360 self.name = name 

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

362 

363 

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

365# --- utils 

366# =================================================================== 

367 

368 

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

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

371if PY3: 

372 if isinstance(__builtins__, dict): # cpython 

373 exec_ = __builtins__["exec"] 

374 else: # pypy 

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

376 

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

378 try: 

379 raise value from from_value 

380 finally: 

381 value = None 

382 """) 

383else: 

384 def raise_from(value, from_value): 

385 raise value 

386 

387 

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

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

390 try: 

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

392 except ZeroDivisionError: 

393 return 0.0 

394 else: 

395 if round_ is not None: 

396 ret = round(ret, round_) 

397 return ret 

398 

399 

400def memoize(fun): 

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

402 positional arguments. 

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

404 

405 >>> @memoize 

406 ... def foo() 

407 ... return 1 

408 ... 

409 >>> foo() 

410 1 

411 >>> foo.cache_clear() 

412 >>> 

413 

414 It supports: 

415 - functions 

416 - classes (acts as a @singleton) 

417 - staticmethods 

418 - classmethods 

419 

420 It does NOT support: 

421 - methods 

422 """ 

423 @functools.wraps(fun) 

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

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

426 try: 

427 return cache[key] 

428 except KeyError: 

429 try: 

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

431 except Exception as err: 

432 raise raise_from(err, None) 

433 return ret 

434 

435 def cache_clear(): 

436 """Clear cache.""" 

437 cache.clear() 

438 

439 cache = {} 

440 wrapper.cache_clear = cache_clear 

441 return wrapper 

442 

443 

444def memoize_when_activated(fun): 

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

446 activated and deactivated on request. 

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

448 accepting no arguments. 

449 

450 >>> class Foo: 

451 ... @memoize 

452 ... def foo() 

453 ... print(1) 

454 ... 

455 >>> f = Foo() 

456 >>> # deactivated (default) 

457 >>> foo() 

458 1 

459 >>> foo() 

460 1 

461 >>> 

462 >>> # activated 

463 >>> foo.cache_activate(self) 

464 >>> foo() 

465 1 

466 >>> foo() 

467 >>> foo() 

468 >>> 

469 """ 

470 @functools.wraps(fun) 

471 def wrapper(self): 

472 try: 

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

474 ret = self._cache[fun] 

475 except AttributeError: 

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

477 try: 

478 return fun(self) 

479 except Exception as err: 

480 raise raise_from(err, None) 

481 except KeyError: 

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

483 # for this entry yet 

484 try: 

485 ret = fun(self) 

486 except Exception as err: 

487 raise raise_from(err, None) 

488 try: 

489 self._cache[fun] = ret 

490 except AttributeError: 

491 # multi-threading race condition, see: 

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

493 pass 

494 return ret 

495 

496 def cache_activate(proc): 

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

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

499 proc._cache = {} 

500 

501 def cache_deactivate(proc): 

502 """Deactivate and clear cache.""" 

503 try: 

504 del proc._cache 

505 except AttributeError: 

506 pass 

507 

508 wrapper.cache_activate = cache_activate 

509 wrapper.cache_deactivate = cache_deactivate 

510 return wrapper 

511 

512 

513def isfile_strict(path): 

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

515 exceptions, see: 

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

517 """ 

518 try: 

519 st = os.stat(path) 

520 except OSError as err: 

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

522 raise 

523 return False 

524 else: 

525 return stat.S_ISREG(st.st_mode) 

526 

527 

528def path_exists_strict(path): 

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

530 exceptions, see: 

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

532 """ 

533 try: 

534 os.stat(path) 

535 except OSError as err: 

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

537 raise 

538 return False 

539 else: 

540 return True 

541 

542 

543@memoize 

544def supports_ipv6(): 

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

546 if not socket.has_ipv6 or AF_INET6 is None: 

547 return False 

548 try: 

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

550 with contextlib.closing(sock): 

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

552 return True 

553 except socket.error: 

554 return False 

555 

556 

557def parse_environ_block(data): 

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

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

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

561 ret = {} 

562 pos = 0 

563 

564 # localize global variable to speed up access. 

565 WINDOWS_ = WINDOWS 

566 while True: 

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

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

569 if next_pos <= pos: 

570 break 

571 # there might not be an equals sign 

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

573 if equal_pos > pos: 

574 key = data[pos:equal_pos] 

575 value = data[equal_pos + 1:next_pos] 

576 # Windows expects environment variables to be uppercase only 

577 if WINDOWS_: 

578 key = key.upper() 

579 ret[key] = value 

580 pos = next_pos + 1 

581 

582 return ret 

583 

584 

585def sockfam_to_enum(num): 

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

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

588 """ 

589 if enum is None: 

590 return num 

591 else: # pragma: no cover 

592 try: 

593 return socket.AddressFamily(num) 

594 except ValueError: 

595 return num 

596 

597 

598def socktype_to_enum(num): 

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

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

601 """ 

602 if enum is None: 

603 return num 

604 else: # pragma: no cover 

605 try: 

606 return socket.SocketKind(num) 

607 except ValueError: 

608 return num 

609 

610 

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

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

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

614 if laddr: 

615 laddr = addr(*laddr) 

616 if raddr: 

617 raddr = addr(*raddr) 

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

619 status = status_map.get(status, CONN_NONE) 

620 else: 

621 status = CONN_NONE # ignore whatever C returned to us 

622 fam = sockfam_to_enum(fam) 

623 type_ = socktype_to_enum(type_) 

624 if pid is None: 

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

626 else: 

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

628 

629 

630def deprecated_method(replacement): 

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

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

633 """ 

634 def outer(fun): 

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

636 fun.__name__, replacement) 

637 if fun.__doc__ is None: 

638 fun.__doc__ = msg 

639 

640 @functools.wraps(fun) 

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

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

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

644 return inner 

645 return outer 

646 

647 

648class _WrapNumbers: 

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

650 (reset to zero). 

651 """ 

652 

653 def __init__(self): 

654 self.lock = threading.Lock() 

655 self.cache = {} 

656 self.reminders = {} 

657 self.reminder_keys = {} 

658 

659 def _add_dict(self, input_dict, name): 

660 assert name not in self.cache 

661 assert name not in self.reminders 

662 assert name not in self.reminder_keys 

663 self.cache[name] = input_dict 

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

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

666 

667 def _remove_dead_reminders(self, input_dict, name): 

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

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

670 """ 

671 old_dict = self.cache[name] 

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

673 for gone_key in gone_keys: 

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

675 del self.reminders[name][remkey] 

676 del self.reminder_keys[name][gone_key] 

677 

678 def run(self, input_dict, name): 

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

680 Return an updated copy of `input_dict` 

681 """ 

682 if name not in self.cache: 

683 # This was the first call. 

684 self._add_dict(input_dict, name) 

685 return input_dict 

686 

687 self._remove_dead_reminders(input_dict, name) 

688 

689 old_dict = self.cache[name] 

690 new_dict = {} 

691 for key in input_dict.keys(): 

692 input_tuple = input_dict[key] 

693 try: 

694 old_tuple = old_dict[key] 

695 except KeyError: 

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

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

698 new_dict[key] = input_tuple 

699 continue 

700 

701 bits = [] 

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

703 input_value = input_tuple[i] 

704 old_value = old_tuple[i] 

705 remkey = (key, i) 

706 if input_value < old_value: 

707 # it wrapped! 

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

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

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

711 

712 new_dict[key] = tuple(bits) 

713 

714 self.cache[name] = input_dict 

715 return new_dict 

716 

717 def cache_clear(self, name=None): 

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

719 with self.lock: 

720 if name is None: 

721 self.cache.clear() 

722 self.reminders.clear() 

723 self.reminder_keys.clear() 

724 else: 

725 self.cache.pop(name, None) 

726 self.reminders.pop(name, None) 

727 self.reminder_keys.pop(name, None) 

728 

729 def cache_info(self): 

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

731 with self.lock: 

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

733 

734 

735def wrap_numbers(input_dict, name): 

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

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

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

739 """ 

740 with _wn.lock: 

741 return _wn.run(input_dict, name) 

742 

743 

744_wn = _WrapNumbers() 

745wrap_numbers.cache_clear = _wn.cache_clear 

746wrap_numbers.cache_info = _wn.cache_info 

747 

748 

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

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

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

752# ... for line in f: 

753# ... ... 

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

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

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

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

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

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

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

761FILE_READ_BUFFER_SIZE = 32 * 1024 

762 

763 

764def open_binary(fname): 

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

766 

767 

768def open_text(fname): 

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

770 a proper en/decoding errors handler. 

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

772 """ 

773 if not PY3: 

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

775 

776 # See: 

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

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

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

780 encoding=ENCODING, errors=ENCODING_ERRS) 

781 try: 

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

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

784 fobj._CHUNK_SIZE = FILE_READ_BUFFER_SIZE 

785 except AttributeError: 

786 pass 

787 except Exception: 

788 fobj.close() 

789 raise 

790 

791 return fobj 

792 

793 

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

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

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

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

798 it can't be read(). 

799 """ 

800 if fallback is _DEFAULT: 

801 with _open(fname) as f: 

802 return f.read() 

803 else: 

804 try: 

805 with _open(fname) as f: 

806 return f.read() 

807 except (IOError, OSError): 

808 return fallback 

809 

810 

811def bcat(fname, fallback=_DEFAULT): 

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

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

814 

815 

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

817 """Used by various scripts. See: 

818 http://goo.gl/zeJZl 

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

841if PY3: 

842 def decode(s): 

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

844else: 

845 def decode(s): 

846 return s 

847 

848 

849# ===================================================================== 

850# --- shell utils 

851# ===================================================================== 

852 

853 

854@memoize 

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

856 if os.name == 'nt': 

857 return True 

858 try: 

859 import curses 

860 assert file.isatty() 

861 curses.setupterm() 

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

863 except Exception: 

864 return False 

865 else: 

866 return True 

867 

868 

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

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

871 if not term_supports_colors(): 

872 return s 

873 attr = [] 

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

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

876 colors[None] = '29' 

877 try: 

878 color = colors[color] 

879 except KeyError: 

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

881 list(colors.keys()))) 

882 attr.append(color) 

883 if bold: 

884 attr.append('1') 

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

886 

887 

888def print_color( 

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

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

891 if not term_supports_colors(): 

892 print(s, file=file) # NOQA 

893 elif POSIX: 

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

895 else: 

896 import ctypes 

897 

898 DEFAULT_COLOR = 7 

899 GetStdHandle = ctypes.windll.Kernel32.GetStdHandle 

900 SetConsoleTextAttribute = \ 

901 ctypes.windll.Kernel32.SetConsoleTextAttribute 

902 

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

904 colors[None] = DEFAULT_COLOR 

905 try: 

906 color = colors[color] 

907 except KeyError: 

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

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

910 if bold and color <= 7: 

911 color += 8 

912 

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

914 GetStdHandle.restype = ctypes.c_ulong 

915 handle = GetStdHandle(handle_id) 

916 SetConsoleTextAttribute(handle, color) 

917 try: 

918 print(s, file=file) # NOQA 

919 finally: 

920 SetConsoleTextAttribute(handle, DEFAULT_COLOR) 

921 

922 

923def debug(msg): 

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

925 if PSUTIL_DEBUG: 

926 import inspect 

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

928 inspect.currentframe().f_back) 

929 if isinstance(msg, Exception): 

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

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

932 msg = "ignoring %s" % msg 

933 else: 

934 msg = "ignoring %r" % msg 

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

936 file=sys.stderr)