_common.py 26 KB

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