_common.py 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942
  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. """Common objects shared by __init__.py and _ps*.py modules."""
  5. # Note: this module is imported by setup.py so it should not import
  6. # psutil or third-party modules.
  7. from __future__ import division
  8. from __future__ import print_function
  9. import collections
  10. import contextlib
  11. import errno
  12. import functools
  13. import os
  14. import socket
  15. import stat
  16. import sys
  17. import threading
  18. import warnings
  19. from collections import namedtuple
  20. from socket import AF_INET
  21. from socket import SOCK_DGRAM
  22. from socket import SOCK_STREAM
  23. try:
  24. from socket import AF_INET6
  25. except ImportError:
  26. AF_INET6 = None
  27. try:
  28. from socket import AF_UNIX
  29. except ImportError:
  30. AF_UNIX = None
  31. # can't take it from _common.py as this script is imported by setup.py
  32. PY3 = sys.version_info[0] >= 3
  33. if PY3:
  34. import enum
  35. else:
  36. enum = None
  37. PSUTIL_DEBUG = bool(os.getenv('PSUTIL_DEBUG'))
  38. _DEFAULT = object()
  39. __all__ = [
  40. # OS constants
  41. 'FREEBSD', 'BSD', 'LINUX', 'NETBSD', 'OPENBSD', 'MACOS', 'OSX', 'POSIX',
  42. 'SUNOS', 'WINDOWS',
  43. # connection constants
  44. 'CONN_CLOSE', 'CONN_CLOSE_WAIT', 'CONN_CLOSING', 'CONN_ESTABLISHED',
  45. 'CONN_FIN_WAIT1', 'CONN_FIN_WAIT2', 'CONN_LAST_ACK', 'CONN_LISTEN',
  46. 'CONN_NONE', 'CONN_SYN_RECV', 'CONN_SYN_SENT', 'CONN_TIME_WAIT',
  47. # net constants
  48. 'NIC_DUPLEX_FULL', 'NIC_DUPLEX_HALF', 'NIC_DUPLEX_UNKNOWN',
  49. # process status constants
  50. 'STATUS_DEAD', 'STATUS_DISK_SLEEP', 'STATUS_IDLE', 'STATUS_LOCKED',
  51. 'STATUS_RUNNING', 'STATUS_SLEEPING', 'STATUS_STOPPED', 'STATUS_SUSPENDED',
  52. 'STATUS_TRACING_STOP', 'STATUS_WAITING', 'STATUS_WAKE_KILL',
  53. 'STATUS_WAKING', 'STATUS_ZOMBIE', 'STATUS_PARKED',
  54. # other constants
  55. 'ENCODING', 'ENCODING_ERRS', 'AF_INET6',
  56. # named tuples
  57. 'pconn', 'pcputimes', 'pctxsw', 'pgids', 'pio', 'pionice', 'popenfile',
  58. 'pthread', 'puids', 'sconn', 'scpustats', 'sdiskio', 'sdiskpart',
  59. 'sdiskusage', 'snetio', 'snicaddr', 'snicstats', 'sswap', 'suser',
  60. # utility functions
  61. 'conn_tmap', 'deprecated_method', 'isfile_strict', 'memoize',
  62. 'parse_environ_block', 'path_exists_strict', 'usage_percent',
  63. 'supports_ipv6', 'sockfam_to_enum', 'socktype_to_enum', "wrap_numbers",
  64. 'open_text', 'open_binary', 'cat', 'bcat',
  65. 'bytes2human', 'conn_to_ntuple', 'debug',
  66. # shell utils
  67. 'hilite', 'term_supports_colors', 'print_color',
  68. ]
  69. # ===================================================================
  70. # --- OS constants
  71. # ===================================================================
  72. POSIX = os.name == "posix"
  73. WINDOWS = os.name == "nt"
  74. LINUX = sys.platform.startswith("linux")
  75. MACOS = sys.platform.startswith("darwin")
  76. OSX = MACOS # deprecated alias
  77. FREEBSD = sys.platform.startswith(("freebsd", "midnightbsd"))
  78. OPENBSD = sys.platform.startswith("openbsd")
  79. NETBSD = sys.platform.startswith("netbsd")
  80. BSD = FREEBSD or OPENBSD or NETBSD
  81. SUNOS = sys.platform.startswith(("sunos", "solaris"))
  82. AIX = sys.platform.startswith("aix")
  83. # ===================================================================
  84. # --- API constants
  85. # ===================================================================
  86. # Process.status()
  87. STATUS_RUNNING = "running"
  88. STATUS_SLEEPING = "sleeping"
  89. STATUS_DISK_SLEEP = "disk-sleep"
  90. STATUS_STOPPED = "stopped"
  91. STATUS_TRACING_STOP = "tracing-stop"
  92. STATUS_ZOMBIE = "zombie"
  93. STATUS_DEAD = "dead"
  94. STATUS_WAKE_KILL = "wake-kill"
  95. STATUS_WAKING = "waking"
  96. STATUS_IDLE = "idle" # Linux, macOS, FreeBSD
  97. STATUS_LOCKED = "locked" # FreeBSD
  98. STATUS_WAITING = "waiting" # FreeBSD
  99. STATUS_SUSPENDED = "suspended" # NetBSD
  100. STATUS_PARKED = "parked" # Linux
  101. # Process.connections() and psutil.net_connections()
  102. CONN_ESTABLISHED = "ESTABLISHED"
  103. CONN_SYN_SENT = "SYN_SENT"
  104. CONN_SYN_RECV = "SYN_RECV"
  105. CONN_FIN_WAIT1 = "FIN_WAIT1"
  106. CONN_FIN_WAIT2 = "FIN_WAIT2"
  107. CONN_TIME_WAIT = "TIME_WAIT"
  108. CONN_CLOSE = "CLOSE"
  109. CONN_CLOSE_WAIT = "CLOSE_WAIT"
  110. CONN_LAST_ACK = "LAST_ACK"
  111. CONN_LISTEN = "LISTEN"
  112. CONN_CLOSING = "CLOSING"
  113. CONN_NONE = "NONE"
  114. # net_if_stats()
  115. if enum is None:
  116. NIC_DUPLEX_FULL = 2
  117. NIC_DUPLEX_HALF = 1
  118. NIC_DUPLEX_UNKNOWN = 0
  119. else:
  120. class NicDuplex(enum.IntEnum):
  121. NIC_DUPLEX_FULL = 2
  122. NIC_DUPLEX_HALF = 1
  123. NIC_DUPLEX_UNKNOWN = 0
  124. globals().update(NicDuplex.__members__)
  125. # sensors_battery()
  126. if enum is None:
  127. POWER_TIME_UNKNOWN = -1
  128. POWER_TIME_UNLIMITED = -2
  129. else:
  130. class BatteryTime(enum.IntEnum):
  131. POWER_TIME_UNKNOWN = -1
  132. POWER_TIME_UNLIMITED = -2
  133. globals().update(BatteryTime.__members__)
  134. # --- others
  135. ENCODING = sys.getfilesystemencoding()
  136. if not PY3:
  137. ENCODING_ERRS = "replace"
  138. else:
  139. try:
  140. ENCODING_ERRS = sys.getfilesystemencodeerrors() # py 3.6
  141. except AttributeError:
  142. ENCODING_ERRS = "surrogateescape" if POSIX else "replace"
  143. # ===================================================================
  144. # --- namedtuples
  145. # ===================================================================
  146. # --- for system functions
  147. # psutil.swap_memory()
  148. sswap = namedtuple('sswap', ['total', 'used', 'free', 'percent', 'sin',
  149. 'sout'])
  150. # psutil.disk_usage()
  151. sdiskusage = namedtuple('sdiskusage', ['total', 'used', 'free', 'percent'])
  152. # psutil.disk_io_counters()
  153. sdiskio = namedtuple('sdiskio', ['read_count', 'write_count',
  154. 'read_bytes', 'write_bytes',
  155. 'read_time', 'write_time'])
  156. # psutil.disk_partitions()
  157. sdiskpart = namedtuple('sdiskpart', ['device', 'mountpoint', 'fstype', 'opts',
  158. 'maxfile', 'maxpath'])
  159. # psutil.net_io_counters()
  160. snetio = namedtuple('snetio', ['bytes_sent', 'bytes_recv',
  161. 'packets_sent', 'packets_recv',
  162. 'errin', 'errout',
  163. 'dropin', 'dropout'])
  164. # psutil.users()
  165. suser = namedtuple('suser', ['name', 'terminal', 'host', 'started', 'pid'])
  166. # psutil.net_connections()
  167. sconn = namedtuple('sconn', ['fd', 'family', 'type', 'laddr', 'raddr',
  168. 'status', 'pid'])
  169. # psutil.net_if_addrs()
  170. snicaddr = namedtuple('snicaddr',
  171. ['family', 'address', 'netmask', 'broadcast', 'ptp'])
  172. # psutil.net_if_stats()
  173. snicstats = namedtuple('snicstats',
  174. ['isup', 'duplex', 'speed', 'mtu', 'flags'])
  175. # psutil.cpu_stats()
  176. scpustats = namedtuple(
  177. 'scpustats', ['ctx_switches', 'interrupts', 'soft_interrupts', 'syscalls'])
  178. # psutil.cpu_freq()
  179. scpufreq = namedtuple('scpufreq', ['current', 'min', 'max'])
  180. # psutil.sensors_temperatures()
  181. shwtemp = namedtuple(
  182. 'shwtemp', ['label', 'current', 'high', 'critical'])
  183. # psutil.sensors_battery()
  184. sbattery = namedtuple('sbattery', ['percent', 'secsleft', 'power_plugged'])
  185. # psutil.sensors_fans()
  186. sfan = namedtuple('sfan', ['label', 'current'])
  187. # --- for Process methods
  188. # psutil.Process.cpu_times()
  189. pcputimes = namedtuple('pcputimes',
  190. ['user', 'system', 'children_user', 'children_system'])
  191. # psutil.Process.open_files()
  192. popenfile = namedtuple('popenfile', ['path', 'fd'])
  193. # psutil.Process.threads()
  194. pthread = namedtuple('pthread', ['id', 'user_time', 'system_time'])
  195. # psutil.Process.uids()
  196. puids = namedtuple('puids', ['real', 'effective', 'saved'])
  197. # psutil.Process.gids()
  198. pgids = namedtuple('pgids', ['real', 'effective', 'saved'])
  199. # psutil.Process.io_counters()
  200. pio = namedtuple('pio', ['read_count', 'write_count',
  201. 'read_bytes', 'write_bytes'])
  202. # psutil.Process.ionice()
  203. pionice = namedtuple('pionice', ['ioclass', 'value'])
  204. # psutil.Process.ctx_switches()
  205. pctxsw = namedtuple('pctxsw', ['voluntary', 'involuntary'])
  206. # psutil.Process.connections()
  207. pconn = namedtuple('pconn', ['fd', 'family', 'type', 'laddr', 'raddr',
  208. 'status'])
  209. # psutil.connections() and psutil.Process.connections()
  210. addr = namedtuple('addr', ['ip', 'port'])
  211. # ===================================================================
  212. # --- Process.connections() 'kind' parameter mapping
  213. # ===================================================================
  214. conn_tmap = {
  215. "all": ([AF_INET, AF_INET6, AF_UNIX], [SOCK_STREAM, SOCK_DGRAM]),
  216. "tcp": ([AF_INET, AF_INET6], [SOCK_STREAM]),
  217. "tcp4": ([AF_INET], [SOCK_STREAM]),
  218. "udp": ([AF_INET, AF_INET6], [SOCK_DGRAM]),
  219. "udp4": ([AF_INET], [SOCK_DGRAM]),
  220. "inet": ([AF_INET, AF_INET6], [SOCK_STREAM, SOCK_DGRAM]),
  221. "inet4": ([AF_INET], [SOCK_STREAM, SOCK_DGRAM]),
  222. "inet6": ([AF_INET6], [SOCK_STREAM, SOCK_DGRAM]),
  223. }
  224. if AF_INET6 is not None:
  225. conn_tmap.update({
  226. "tcp6": ([AF_INET6], [SOCK_STREAM]),
  227. "udp6": ([AF_INET6], [SOCK_DGRAM]),
  228. })
  229. if AF_UNIX is not None:
  230. conn_tmap.update({
  231. "unix": ([AF_UNIX], [SOCK_STREAM, SOCK_DGRAM]),
  232. })
  233. # =====================================================================
  234. # --- Exceptions
  235. # =====================================================================
  236. class Error(Exception):
  237. """Base exception class. All other psutil exceptions inherit
  238. from this one.
  239. """
  240. __module__ = 'psutil'
  241. def _infodict(self, attrs):
  242. info = collections.OrderedDict()
  243. for name in attrs:
  244. value = getattr(self, name, None)
  245. if value: # noqa
  246. info[name] = value
  247. elif name == "pid" and value == 0:
  248. info[name] = value
  249. return info
  250. def __str__(self):
  251. # invoked on `raise Error`
  252. info = self._infodict(("pid", "ppid", "name"))
  253. if info:
  254. details = "(%s)" % ", ".join(
  255. ["%s=%r" % (k, v) for k, v in info.items()])
  256. else:
  257. details = None
  258. return " ".join([x for x in (getattr(self, "msg", ""), details) if x])
  259. def __repr__(self):
  260. # invoked on `repr(Error)`
  261. info = self._infodict(("pid", "ppid", "name", "seconds", "msg"))
  262. details = ", ".join(["%s=%r" % (k, v) for k, v in info.items()])
  263. return "psutil.%s(%s)" % (self.__class__.__name__, details)
  264. class NoSuchProcess(Error):
  265. """Exception raised when a process with a certain PID doesn't
  266. or no longer exists.
  267. """
  268. __module__ = 'psutil'
  269. def __init__(self, pid, name=None, msg=None):
  270. Error.__init__(self)
  271. self.pid = pid
  272. self.name = name
  273. self.msg = msg or "process no longer exists"
  274. class ZombieProcess(NoSuchProcess):
  275. """Exception raised when querying a zombie process. This is
  276. raised on macOS, BSD and Solaris only, and not always: depending
  277. on the query the OS may be able to succeed anyway.
  278. On Linux all zombie processes are querable (hence this is never
  279. raised). Windows doesn't have zombie processes.
  280. """
  281. __module__ = 'psutil'
  282. def __init__(self, pid, name=None, ppid=None, msg=None):
  283. NoSuchProcess.__init__(self, pid, name, msg)
  284. self.ppid = ppid
  285. self.msg = msg or "PID still exists but it's a zombie"
  286. class AccessDenied(Error):
  287. """Exception raised when permission to perform an action is denied."""
  288. __module__ = 'psutil'
  289. def __init__(self, pid=None, name=None, msg=None):
  290. Error.__init__(self)
  291. self.pid = pid
  292. self.name = name
  293. self.msg = msg or ""
  294. class TimeoutExpired(Error):
  295. """Raised on Process.wait(timeout) if timeout expires and process
  296. is still alive.
  297. """
  298. __module__ = 'psutil'
  299. def __init__(self, seconds, pid=None, name=None):
  300. Error.__init__(self)
  301. self.seconds = seconds
  302. self.pid = pid
  303. self.name = name
  304. self.msg = "timeout after %s seconds" % seconds
  305. # ===================================================================
  306. # --- utils
  307. # ===================================================================
  308. # This should be in _compat.py rather than here, but does not work well
  309. # with setup.py importing this module via a sys.path trick.
  310. if PY3:
  311. if isinstance(__builtins__, dict): # cpython
  312. exec_ = __builtins__["exec"]
  313. else: # pypy
  314. exec_ = getattr(__builtins__, "exec") # noqa
  315. exec_("""def raise_from(value, from_value):
  316. try:
  317. raise value from from_value
  318. finally:
  319. value = None
  320. """)
  321. else:
  322. def raise_from(value, from_value):
  323. raise value
  324. def usage_percent(used, total, round_=None):
  325. """Calculate percentage usage of 'used' against 'total'."""
  326. try:
  327. ret = (float(used) / total) * 100
  328. except ZeroDivisionError:
  329. return 0.0
  330. else:
  331. if round_ is not None:
  332. ret = round(ret, round_)
  333. return ret
  334. def memoize(fun):
  335. """A simple memoize decorator for functions supporting (hashable)
  336. positional arguments.
  337. It also provides a cache_clear() function for clearing the cache:
  338. >>> @memoize
  339. ... def foo()
  340. ... return 1
  341. ...
  342. >>> foo()
  343. 1
  344. >>> foo.cache_clear()
  345. >>>
  346. It supports:
  347. - functions
  348. - classes (acts as a @singleton)
  349. - staticmethods
  350. - classmethods
  351. It does NOT support:
  352. - methods
  353. """
  354. @functools.wraps(fun)
  355. def wrapper(*args, **kwargs):
  356. key = (args, frozenset(sorted(kwargs.items())))
  357. try:
  358. return cache[key]
  359. except KeyError:
  360. try:
  361. ret = cache[key] = fun(*args, **kwargs)
  362. except Exception as err:
  363. raise raise_from(err, None)
  364. return ret
  365. def cache_clear():
  366. """Clear cache."""
  367. cache.clear()
  368. cache = {}
  369. wrapper.cache_clear = cache_clear
  370. return wrapper
  371. def memoize_when_activated(fun):
  372. """A memoize decorator which is disabled by default. It can be
  373. activated and deactivated on request.
  374. For efficiency reasons it can be used only against class methods
  375. accepting no arguments.
  376. >>> class Foo:
  377. ... @memoize
  378. ... def foo()
  379. ... print(1)
  380. ...
  381. >>> f = Foo()
  382. >>> # deactivated (default)
  383. >>> foo()
  384. 1
  385. >>> foo()
  386. 1
  387. >>>
  388. >>> # activated
  389. >>> foo.cache_activate(self)
  390. >>> foo()
  391. 1
  392. >>> foo()
  393. >>> foo()
  394. >>>
  395. """
  396. @functools.wraps(fun)
  397. def wrapper(self):
  398. try:
  399. # case 1: we previously entered oneshot() ctx
  400. ret = self._cache[fun]
  401. except AttributeError:
  402. # case 2: we never entered oneshot() ctx
  403. try:
  404. return fun(self)
  405. except Exception as err:
  406. raise raise_from(err, None)
  407. except KeyError:
  408. # case 3: we entered oneshot() ctx but there's no cache
  409. # for this entry yet
  410. try:
  411. ret = fun(self)
  412. except Exception as err:
  413. raise raise_from(err, None)
  414. try:
  415. self._cache[fun] = ret
  416. except AttributeError:
  417. # multi-threading race condition, see:
  418. # https://github.com/giampaolo/psutil/issues/1948
  419. pass
  420. return ret
  421. def cache_activate(proc):
  422. """Activate cache. Expects a Process instance. Cache will be
  423. stored as a "_cache" instance attribute.
  424. """
  425. proc._cache = {}
  426. def cache_deactivate(proc):
  427. """Deactivate and clear cache."""
  428. try:
  429. del proc._cache
  430. except AttributeError:
  431. pass
  432. wrapper.cache_activate = cache_activate
  433. wrapper.cache_deactivate = cache_deactivate
  434. return wrapper
  435. def isfile_strict(path):
  436. """Same as os.path.isfile() but does not swallow EACCES / EPERM
  437. exceptions, see:
  438. http://mail.python.org/pipermail/python-dev/2012-June/120787.html.
  439. """
  440. try:
  441. st = os.stat(path)
  442. except OSError as err:
  443. if err.errno in (errno.EPERM, errno.EACCES):
  444. raise
  445. return False
  446. else:
  447. return stat.S_ISREG(st.st_mode)
  448. def path_exists_strict(path):
  449. """Same as os.path.exists() but does not swallow EACCES / EPERM
  450. exceptions. See:
  451. http://mail.python.org/pipermail/python-dev/2012-June/120787.html.
  452. """
  453. try:
  454. os.stat(path)
  455. except OSError as err:
  456. if err.errno in (errno.EPERM, errno.EACCES):
  457. raise
  458. return False
  459. else:
  460. return True
  461. @memoize
  462. def supports_ipv6():
  463. """Return True if IPv6 is supported on this platform."""
  464. if not socket.has_ipv6 or AF_INET6 is None:
  465. return False
  466. try:
  467. sock = socket.socket(AF_INET6, socket.SOCK_STREAM)
  468. with contextlib.closing(sock):
  469. sock.bind(("::1", 0))
  470. return True
  471. except socket.error:
  472. return False
  473. def parse_environ_block(data):
  474. """Parse a C environ block of environment variables into a dictionary."""
  475. # The block is usually raw data from the target process. It might contain
  476. # trailing garbage and lines that do not look like assignments.
  477. ret = {}
  478. pos = 0
  479. # localize global variable to speed up access.
  480. WINDOWS_ = WINDOWS
  481. while True:
  482. next_pos = data.find("\0", pos)
  483. # nul byte at the beginning or double nul byte means finish
  484. if next_pos <= pos:
  485. break
  486. # there might not be an equals sign
  487. equal_pos = data.find("=", pos, next_pos)
  488. if equal_pos > pos:
  489. key = data[pos:equal_pos]
  490. value = data[equal_pos + 1:next_pos]
  491. # Windows expects environment variables to be uppercase only
  492. if WINDOWS_:
  493. key = key.upper()
  494. ret[key] = value
  495. pos = next_pos + 1
  496. return ret
  497. def sockfam_to_enum(num):
  498. """Convert a numeric socket family value to an IntEnum member.
  499. If it's not a known member, return the numeric value itself.
  500. """
  501. if enum is None:
  502. return num
  503. else: # pragma: no cover
  504. try:
  505. return socket.AddressFamily(num)
  506. except ValueError:
  507. return num
  508. def socktype_to_enum(num):
  509. """Convert a numeric socket type value to an IntEnum member.
  510. If it's not a known member, return the numeric value itself.
  511. """
  512. if enum is None:
  513. return num
  514. else: # pragma: no cover
  515. try:
  516. return socket.SocketKind(num)
  517. except ValueError:
  518. return num
  519. def conn_to_ntuple(fd, fam, type_, laddr, raddr, status, status_map, pid=None):
  520. """Convert a raw connection tuple to a proper ntuple."""
  521. if fam in (socket.AF_INET, AF_INET6):
  522. if laddr:
  523. laddr = addr(*laddr)
  524. if raddr:
  525. raddr = addr(*raddr)
  526. if type_ == socket.SOCK_STREAM and fam in (AF_INET, AF_INET6):
  527. status = status_map.get(status, CONN_NONE)
  528. else:
  529. status = CONN_NONE # ignore whatever C returned to us
  530. fam = sockfam_to_enum(fam)
  531. type_ = socktype_to_enum(type_)
  532. if pid is None:
  533. return pconn(fd, fam, type_, laddr, raddr, status)
  534. else:
  535. return sconn(fd, fam, type_, laddr, raddr, status, pid)
  536. def deprecated_method(replacement):
  537. """A decorator which can be used to mark a method as deprecated
  538. 'replcement' is the method name which will be called instead.
  539. """
  540. def outer(fun):
  541. msg = "%s() is deprecated and will be removed; use %s() instead" % (
  542. fun.__name__, replacement)
  543. if fun.__doc__ is None:
  544. fun.__doc__ = msg
  545. @functools.wraps(fun)
  546. def inner(self, *args, **kwargs):
  547. warnings.warn(msg, category=DeprecationWarning, stacklevel=2)
  548. return getattr(self, replacement)(*args, **kwargs)
  549. return inner
  550. return outer
  551. class _WrapNumbers:
  552. """Watches numbers so that they don't overflow and wrap
  553. (reset to zero).
  554. """
  555. def __init__(self):
  556. self.lock = threading.Lock()
  557. self.cache = {}
  558. self.reminders = {}
  559. self.reminder_keys = {}
  560. def _add_dict(self, input_dict, name):
  561. assert name not in self.cache
  562. assert name not in self.reminders
  563. assert name not in self.reminder_keys
  564. self.cache[name] = input_dict
  565. self.reminders[name] = collections.defaultdict(int)
  566. self.reminder_keys[name] = collections.defaultdict(set)
  567. def _remove_dead_reminders(self, input_dict, name):
  568. """In case the number of keys changed between calls (e.g. a
  569. disk disappears) this removes the entry from self.reminders.
  570. """
  571. old_dict = self.cache[name]
  572. gone_keys = set(old_dict.keys()) - set(input_dict.keys())
  573. for gone_key in gone_keys:
  574. for remkey in self.reminder_keys[name][gone_key]:
  575. del self.reminders[name][remkey]
  576. del self.reminder_keys[name][gone_key]
  577. def run(self, input_dict, name):
  578. """Cache dict and sum numbers which overflow and wrap.
  579. Return an updated copy of `input_dict`.
  580. """
  581. if name not in self.cache:
  582. # This was the first call.
  583. self._add_dict(input_dict, name)
  584. return input_dict
  585. self._remove_dead_reminders(input_dict, name)
  586. old_dict = self.cache[name]
  587. new_dict = {}
  588. for key in input_dict:
  589. input_tuple = input_dict[key]
  590. try:
  591. old_tuple = old_dict[key]
  592. except KeyError:
  593. # The input dict has a new key (e.g. a new disk or NIC)
  594. # which didn't exist in the previous call.
  595. new_dict[key] = input_tuple
  596. continue
  597. bits = []
  598. for i in range(len(input_tuple)):
  599. input_value = input_tuple[i]
  600. old_value = old_tuple[i]
  601. remkey = (key, i)
  602. if input_value < old_value:
  603. # it wrapped!
  604. self.reminders[name][remkey] += old_value
  605. self.reminder_keys[name][key].add(remkey)
  606. bits.append(input_value + self.reminders[name][remkey])
  607. new_dict[key] = tuple(bits)
  608. self.cache[name] = input_dict
  609. return new_dict
  610. def cache_clear(self, name=None):
  611. """Clear the internal cache, optionally only for function 'name'."""
  612. with self.lock:
  613. if name is None:
  614. self.cache.clear()
  615. self.reminders.clear()
  616. self.reminder_keys.clear()
  617. else:
  618. self.cache.pop(name, None)
  619. self.reminders.pop(name, None)
  620. self.reminder_keys.pop(name, None)
  621. def cache_info(self):
  622. """Return internal cache dicts as a tuple of 3 elements."""
  623. with self.lock:
  624. return (self.cache, self.reminders, self.reminder_keys)
  625. def wrap_numbers(input_dict, name):
  626. """Given an `input_dict` and a function `name`, adjust the numbers
  627. which "wrap" (restart from zero) across different calls by adding
  628. "old value" to "new value" and return an updated dict.
  629. """
  630. with _wn.lock:
  631. return _wn.run(input_dict, name)
  632. _wn = _WrapNumbers()
  633. wrap_numbers.cache_clear = _wn.cache_clear
  634. wrap_numbers.cache_info = _wn.cache_info
  635. # The read buffer size for open() builtin. This (also) dictates how
  636. # much data we read(2) when iterating over file lines as in:
  637. # >>> with open(file) as f:
  638. # ... for line in f:
  639. # ... ...
  640. # Default per-line buffer size for binary files is 1K. For text files
  641. # is 8K. We use a bigger buffer (32K) in order to have more consistent
  642. # results when reading /proc pseudo files on Linux, see:
  643. # https://github.com/giampaolo/psutil/issues/2050
  644. # On Python 2 this also speeds up the reading of big files:
  645. # (namely /proc/{pid}/smaps and /proc/net/*):
  646. # https://github.com/giampaolo/psutil/issues/708
  647. FILE_READ_BUFFER_SIZE = 32 * 1024
  648. def open_binary(fname):
  649. return open(fname, "rb", buffering=FILE_READ_BUFFER_SIZE)
  650. def open_text(fname):
  651. """On Python 3 opens a file in text mode by using fs encoding and
  652. a proper en/decoding errors handler.
  653. On Python 2 this is just an alias for open(name, 'rt').
  654. """
  655. if not PY3:
  656. return open(fname, buffering=FILE_READ_BUFFER_SIZE)
  657. # See:
  658. # https://github.com/giampaolo/psutil/issues/675
  659. # https://github.com/giampaolo/psutil/pull/733
  660. fobj = open(fname, buffering=FILE_READ_BUFFER_SIZE,
  661. encoding=ENCODING, errors=ENCODING_ERRS)
  662. try:
  663. # Dictates per-line read(2) buffer size. Defaults is 8k. See:
  664. # https://github.com/giampaolo/psutil/issues/2050#issuecomment-1013387546
  665. fobj._CHUNK_SIZE = FILE_READ_BUFFER_SIZE
  666. except AttributeError:
  667. pass
  668. except Exception:
  669. fobj.close()
  670. raise
  671. return fobj
  672. def cat(fname, fallback=_DEFAULT, _open=open_text):
  673. """Read entire file content and return it as a string. File is
  674. opened in text mode. If specified, `fallback` is the value
  675. returned in case of error, either if the file does not exist or
  676. it can't be read().
  677. """
  678. if fallback is _DEFAULT:
  679. with _open(fname) as f:
  680. return f.read()
  681. else:
  682. try:
  683. with _open(fname) as f:
  684. return f.read()
  685. except (IOError, OSError):
  686. return fallback
  687. def bcat(fname, fallback=_DEFAULT):
  688. """Same as above but opens file in binary mode."""
  689. return cat(fname, fallback=fallback, _open=open_binary)
  690. def bytes2human(n, format="%(value).1f%(symbol)s"):
  691. """Used by various scripts. See: http://goo.gl/zeJZl.
  692. >>> bytes2human(10000)
  693. '9.8K'
  694. >>> bytes2human(100001221)
  695. '95.4M'
  696. """
  697. symbols = ('B', 'K', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y')
  698. prefix = {}
  699. for i, s in enumerate(symbols[1:]):
  700. prefix[s] = 1 << (i + 1) * 10
  701. for symbol in reversed(symbols[1:]):
  702. if abs(n) >= prefix[symbol]:
  703. value = float(n) / prefix[symbol]
  704. return format % locals()
  705. return format % dict(symbol=symbols[0], value=n)
  706. def get_procfs_path():
  707. """Return updated psutil.PROCFS_PATH constant."""
  708. return sys.modules['psutil'].PROCFS_PATH
  709. if PY3:
  710. def decode(s):
  711. return s.decode(encoding=ENCODING, errors=ENCODING_ERRS)
  712. else:
  713. def decode(s):
  714. return s
  715. # =====================================================================
  716. # --- shell utils
  717. # =====================================================================
  718. @memoize
  719. def term_supports_colors(file=sys.stdout): # pragma: no cover
  720. if os.name == 'nt':
  721. return True
  722. try:
  723. import curses
  724. assert file.isatty()
  725. curses.setupterm()
  726. assert curses.tigetnum("colors") > 0
  727. except Exception:
  728. return False
  729. else:
  730. return True
  731. def hilite(s, color=None, bold=False): # pragma: no cover
  732. """Return an highlighted version of 'string'."""
  733. if not term_supports_colors():
  734. return s
  735. attr = []
  736. colors = dict(green='32', red='91', brown='33', yellow='93', blue='34',
  737. violet='35', lightblue='36', grey='37', darkgrey='30')
  738. colors[None] = '29'
  739. try:
  740. color = colors[color]
  741. except KeyError:
  742. raise ValueError("invalid color %r; choose between %s" % (
  743. list(colors.keys())))
  744. attr.append(color)
  745. if bold:
  746. attr.append('1')
  747. return '\x1b[%sm%s\x1b[0m' % (';'.join(attr), s)
  748. def print_color(
  749. s, color=None, bold=False, file=sys.stdout): # pragma: no cover
  750. """Print a colorized version of string."""
  751. if not term_supports_colors():
  752. print(s, file=file) # NOQA
  753. elif POSIX:
  754. print(hilite(s, color, bold), file=file) # NOQA
  755. else:
  756. import ctypes
  757. DEFAULT_COLOR = 7
  758. GetStdHandle = ctypes.windll.Kernel32.GetStdHandle
  759. SetConsoleTextAttribute = \
  760. ctypes.windll.Kernel32.SetConsoleTextAttribute
  761. colors = dict(green=2, red=4, brown=6, yellow=6)
  762. colors[None] = DEFAULT_COLOR
  763. try:
  764. color = colors[color]
  765. except KeyError:
  766. raise ValueError("invalid color %r; choose between %r" % (
  767. color, list(colors.keys())))
  768. if bold and color <= 7:
  769. color += 8
  770. handle_id = -12 if file is sys.stderr else -11
  771. GetStdHandle.restype = ctypes.c_ulong
  772. handle = GetStdHandle(handle_id)
  773. SetConsoleTextAttribute(handle, color)
  774. try:
  775. print(s, file=file) # NOQA
  776. finally:
  777. SetConsoleTextAttribute(handle, DEFAULT_COLOR)
  778. def debug(msg):
  779. """If PSUTIL_DEBUG env var is set, print a debug message to stderr."""
  780. if PSUTIL_DEBUG:
  781. import inspect
  782. fname, lineno, _, lines, index = inspect.getframeinfo(
  783. inspect.currentframe().f_back)
  784. if isinstance(msg, Exception):
  785. if isinstance(msg, (OSError, IOError, EnvironmentError)):
  786. # ...because str(exc) may contain info about the file name
  787. msg = "ignoring %s" % msg
  788. else:
  789. msg = "ignoring %r" % msg
  790. print("psutil-debug [%s:%s]> %s" % (fname, lineno, msg), # NOQA
  791. file=sys.stderr)