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

431 statements  

« prev     ^ index     » next       coverage.py v7.4.4, created at 2024-04-02 06:13 +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# 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 def __reduce__(self): 

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

336 

337 

338class ZombieProcess(NoSuchProcess): 

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

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

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

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

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

344 """ 

345 

346 __module__ = 'psutil' 

347 

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

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

350 self.ppid = ppid 

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

352 

353 def __reduce__(self): 

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

355 

356 

357class AccessDenied(Error): 

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

359 

360 __module__ = 'psutil' 

361 

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

363 Error.__init__(self) 

364 self.pid = pid 

365 self.name = name 

366 self.msg = msg or "" 

367 

368 def __reduce__(self): 

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

370 

371 

372class TimeoutExpired(Error): 

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

374 is still alive. 

375 """ 

376 

377 __module__ = 'psutil' 

378 

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

380 Error.__init__(self) 

381 self.seconds = seconds 

382 self.pid = pid 

383 self.name = name 

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

385 

386 def __reduce__(self): 

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

388 

389 

390# =================================================================== 

391# --- utils 

392# =================================================================== 

393 

394 

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

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

397if PY3: 

398 if isinstance(__builtins__, dict): # cpython 

399 exec_ = __builtins__["exec"] 

400 else: # pypy 

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

402 

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

404 try: 

405 raise value from from_value 

406 finally: 

407 value = None 

408 """) 

409else: 

410 

411 def raise_from(value, from_value): 

412 raise value 

413 

414 

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

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

417 try: 

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

419 except ZeroDivisionError: 

420 return 0.0 

421 else: 

422 if round_ is not None: 

423 ret = round(ret, round_) 

424 return ret 

425 

426 

427def memoize(fun): 

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

429 positional arguments. 

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

431 

432 >>> @memoize 

433 ... def foo() 

434 ... return 1 

435 ... 

436 >>> foo() 

437 1 

438 >>> foo.cache_clear() 

439 >>> 

440 

441 It supports: 

442 - functions 

443 - classes (acts as a @singleton) 

444 - staticmethods 

445 - classmethods 

446 

447 It does NOT support: 

448 - methods 

449 """ 

450 

451 @functools.wraps(fun) 

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

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

454 try: 

455 return cache[key] 

456 except KeyError: 

457 try: 

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

459 except Exception as err: # noqa: BLE001 

460 raise raise_from(err, None) 

461 return ret 

462 

463 def cache_clear(): 

464 """Clear cache.""" 

465 cache.clear() 

466 

467 cache = {} 

468 wrapper.cache_clear = cache_clear 

469 return wrapper 

470 

471 

472def memoize_when_activated(fun): 

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

474 activated and deactivated on request. 

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

476 accepting no arguments. 

477 

478 >>> class Foo: 

479 ... @memoize 

480 ... def foo() 

481 ... print(1) 

482 ... 

483 >>> f = Foo() 

484 >>> # deactivated (default) 

485 >>> foo() 

486 1 

487 >>> foo() 

488 1 

489 >>> 

490 >>> # activated 

491 >>> foo.cache_activate(self) 

492 >>> foo() 

493 1 

494 >>> foo() 

495 >>> foo() 

496 >>> 

497 """ 

498 

499 @functools.wraps(fun) 

500 def wrapper(self): 

501 try: 

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

503 ret = self._cache[fun] 

504 except AttributeError: 

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

506 try: 

507 return fun(self) 

508 except Exception as err: # noqa: BLE001 

509 raise raise_from(err, None) 

510 except KeyError: 

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

512 # for this entry yet 

513 try: 

514 ret = fun(self) 

515 except Exception as err: # noqa: BLE001 

516 raise raise_from(err, None) 

517 try: 

518 self._cache[fun] = ret 

519 except AttributeError: 

520 # multi-threading race condition, see: 

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

522 pass 

523 return ret 

524 

525 def cache_activate(proc): 

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

527 stored as a "_cache" instance attribute. 

528 """ 

529 proc._cache = {} 

530 

531 def cache_deactivate(proc): 

532 """Deactivate and clear cache.""" 

533 try: 

534 del proc._cache 

535 except AttributeError: 

536 pass 

537 

538 wrapper.cache_activate = cache_activate 

539 wrapper.cache_deactivate = cache_deactivate 

540 return wrapper 

541 

542 

543def isfile_strict(path): 

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

545 exceptions, see: 

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

547 """ 

548 try: 

549 st = os.stat(path) 

550 except OSError as err: 

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

552 raise 

553 return False 

554 else: 

555 return stat.S_ISREG(st.st_mode) 

556 

557 

558def path_exists_strict(path): 

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

560 exceptions. See: 

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

562 """ 

563 try: 

564 os.stat(path) 

565 except OSError as err: 

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

567 raise 

568 return False 

569 else: 

570 return True 

571 

572 

573@memoize 

574def supports_ipv6(): 

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

576 if not socket.has_ipv6 or AF_INET6 is None: 

577 return False 

578 try: 

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

580 with contextlib.closing(sock): 

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

582 return True 

583 except socket.error: 

584 return False 

585 

586 

587def parse_environ_block(data): 

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

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

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

591 ret = {} 

592 pos = 0 

593 

594 # localize global variable to speed up access. 

595 WINDOWS_ = WINDOWS 

596 while True: 

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

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

599 if next_pos <= pos: 

600 break 

601 # there might not be an equals sign 

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

603 if equal_pos > pos: 

604 key = data[pos:equal_pos] 

605 value = data[equal_pos + 1 : next_pos] 

606 # Windows expects environment variables to be uppercase only 

607 if WINDOWS_: 

608 key = key.upper() 

609 ret[key] = value 

610 pos = next_pos + 1 

611 

612 return ret 

613 

614 

615def sockfam_to_enum(num): 

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

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

618 """ 

619 if enum is None: 

620 return num 

621 else: # pragma: no cover 

622 try: 

623 return socket.AddressFamily(num) 

624 except ValueError: 

625 return num 

626 

627 

628def socktype_to_enum(num): 

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

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

631 """ 

632 if enum is None: 

633 return num 

634 else: # pragma: no cover 

635 try: 

636 return socket.SocketKind(num) 

637 except ValueError: 

638 return num 

639 

640 

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

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

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

644 if laddr: 

645 laddr = addr(*laddr) 

646 if raddr: 

647 raddr = addr(*raddr) 

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

649 status = status_map.get(status, CONN_NONE) 

650 else: 

651 status = CONN_NONE # ignore whatever C returned to us 

652 fam = sockfam_to_enum(fam) 

653 type_ = socktype_to_enum(type_) 

654 if pid is None: 

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

656 else: 

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

658 

659 

660def deprecated_method(replacement): 

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

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

663 """ 

664 

665 def outer(fun): 

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

667 fun.__name__, 

668 replacement, 

669 ) 

670 if fun.__doc__ is None: 

671 fun.__doc__ = msg 

672 

673 @functools.wraps(fun) 

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

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

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

677 

678 return inner 

679 

680 return outer 

681 

682 

683class _WrapNumbers: 

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

685 (reset to zero). 

686 """ 

687 

688 def __init__(self): 

689 self.lock = threading.Lock() 

690 self.cache = {} 

691 self.reminders = {} 

692 self.reminder_keys = {} 

693 

694 def _add_dict(self, input_dict, name): 

695 assert name not in self.cache 

696 assert name not in self.reminders 

697 assert name not in self.reminder_keys 

698 self.cache[name] = input_dict 

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

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

701 

702 def _remove_dead_reminders(self, input_dict, name): 

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

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

705 """ 

706 old_dict = self.cache[name] 

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

708 for gone_key in gone_keys: 

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

710 del self.reminders[name][remkey] 

711 del self.reminder_keys[name][gone_key] 

712 

713 def run(self, input_dict, name): 

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

715 Return an updated copy of `input_dict`. 

716 """ 

717 if name not in self.cache: 

718 # This was the first call. 

719 self._add_dict(input_dict, name) 

720 return input_dict 

721 

722 self._remove_dead_reminders(input_dict, name) 

723 

724 old_dict = self.cache[name] 

725 new_dict = {} 

726 for key in input_dict: 

727 input_tuple = input_dict[key] 

728 try: 

729 old_tuple = old_dict[key] 

730 except KeyError: 

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

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

733 new_dict[key] = input_tuple 

734 continue 

735 

736 bits = [] 

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

738 input_value = input_tuple[i] 

739 old_value = old_tuple[i] 

740 remkey = (key, i) 

741 if input_value < old_value: 

742 # it wrapped! 

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

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

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

746 

747 new_dict[key] = tuple(bits) 

748 

749 self.cache[name] = input_dict 

750 return new_dict 

751 

752 def cache_clear(self, name=None): 

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

754 with self.lock: 

755 if name is None: 

756 self.cache.clear() 

757 self.reminders.clear() 

758 self.reminder_keys.clear() 

759 else: 

760 self.cache.pop(name, None) 

761 self.reminders.pop(name, None) 

762 self.reminder_keys.pop(name, None) 

763 

764 def cache_info(self): 

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

766 with self.lock: 

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

768 

769 

770def wrap_numbers(input_dict, name): 

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

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

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

774 """ 

775 with _wn.lock: 

776 return _wn.run(input_dict, name) 

777 

778 

779_wn = _WrapNumbers() 

780wrap_numbers.cache_clear = _wn.cache_clear 

781wrap_numbers.cache_info = _wn.cache_info 

782 

783 

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

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

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

787# ... for line in f: 

788# ... ... 

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

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

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

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

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

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

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

796FILE_READ_BUFFER_SIZE = 32 * 1024 

797 

798 

799def open_binary(fname): 

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

801 

802 

803def open_text(fname): 

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

805 a proper en/decoding errors handler. 

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

807 """ 

808 if not PY3: 

809 return open(fname, buffering=FILE_READ_BUFFER_SIZE) 

810 

811 # See: 

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

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

814 fobj = open( 

815 fname, 

816 buffering=FILE_READ_BUFFER_SIZE, 

817 encoding=ENCODING, 

818 errors=ENCODING_ERRS, 

819 ) 

820 try: 

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

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

823 fobj._CHUNK_SIZE = FILE_READ_BUFFER_SIZE 

824 except AttributeError: 

825 pass 

826 except Exception: 

827 fobj.close() 

828 raise 

829 

830 return fobj 

831 

832 

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

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

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

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

837 it can't be read(). 

838 """ 

839 if fallback is _DEFAULT: 

840 with _open(fname) as f: 

841 return f.read() 

842 else: 

843 try: 

844 with _open(fname) as f: 

845 return f.read() 

846 except (IOError, OSError): 

847 return fallback 

848 

849 

850def bcat(fname, fallback=_DEFAULT): 

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

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

853 

854 

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

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

857 

858 >>> bytes2human(10000) 

859 '9.8K' 

860 >>> bytes2human(100001221) 

861 '95.4M' 

862 """ 

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

864 prefix = {} 

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

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

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

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

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

870 return format % locals() 

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

872 

873 

874def get_procfs_path(): 

875 """Return updated psutil.PROCFS_PATH constant.""" 

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

877 

878 

879if PY3: 

880 

881 def decode(s): 

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

883 

884else: 

885 

886 def decode(s): 

887 return s 

888 

889 

890# ===================================================================== 

891# --- shell utils 

892# ===================================================================== 

893 

894 

895@memoize 

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

897 if os.name == 'nt': 

898 return True 

899 try: 

900 import curses 

901 

902 assert file.isatty() 

903 curses.setupterm() 

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

905 except Exception: # noqa: BLE001 

906 return False 

907 else: 

908 return True 

909 

910 

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

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

913 if not term_supports_colors(): 

914 return s 

915 attr = [] 

916 colors = dict( 

917 blue='34', 

918 brown='33', 

919 darkgrey='30', 

920 green='32', 

921 grey='37', 

922 lightblue='36', 

923 red='91', 

924 violet='35', 

925 yellow='93', 

926 ) 

927 colors[None] = '29' 

928 try: 

929 color = colors[color] 

930 except KeyError: 

931 raise ValueError( 

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

933 ) 

934 attr.append(color) 

935 if bold: 

936 attr.append('1') 

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

938 

939 

940def print_color( 

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

942): # pragma: no cover 

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

944 if not term_supports_colors(): 

945 print(s, file=file) # NOQA 

946 elif POSIX: 

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

948 else: 

949 import ctypes 

950 

951 DEFAULT_COLOR = 7 

952 GetStdHandle = ctypes.windll.Kernel32.GetStdHandle 

953 SetConsoleTextAttribute = ( 

954 ctypes.windll.Kernel32.SetConsoleTextAttribute 

955 ) 

956 

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

958 colors[None] = DEFAULT_COLOR 

959 try: 

960 color = colors[color] 

961 except KeyError: 

962 raise ValueError( 

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

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

965 ) 

966 if bold and color <= 7: 

967 color += 8 

968 

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

970 GetStdHandle.restype = ctypes.c_ulong 

971 handle = GetStdHandle(handle_id) 

972 SetConsoleTextAttribute(handle, color) 

973 try: 

974 print(s, file=file) # NOQA 

975 finally: 

976 SetConsoleTextAttribute(handle, DEFAULT_COLOR) 

977 

978 

979def debug(msg): 

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

981 if PSUTIL_DEBUG: 

982 import inspect 

983 

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

985 inspect.currentframe().f_back 

986 ) 

987 if isinstance(msg, Exception): 

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

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

990 msg = "ignoring %s" % msg 

991 else: 

992 msg = "ignoring %r" % msg 

993 print( # noqa 

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

995 )