Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/psutil/_common.py: 49%
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
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.
5"""Common objects shared by __init__.py and _ps*.py modules."""
7# Note: this module is imported by setup.py so it should not import
8# psutil or third-party modules.
10from __future__ import division
11from __future__ import print_function
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
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
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
47PSUTIL_DEBUG = bool(os.getenv('PSUTIL_DEBUG'))
48_DEFAULT = object()
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
84# ===================================================================
85# --- OS constants
86# ===================================================================
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")
102# ===================================================================
103# --- API constants
104# ===================================================================
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
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"
137# net_if_stats()
138if enum is None:
139 NIC_DUPLEX_FULL = 2
140 NIC_DUPLEX_HALF = 1
141 NIC_DUPLEX_UNKNOWN = 0
142else:
144 class NicDuplex(enum.IntEnum):
145 NIC_DUPLEX_FULL = 2
146 NIC_DUPLEX_HALF = 1
147 NIC_DUPLEX_UNKNOWN = 0
149 globals().update(NicDuplex.__members__)
151# sensors_battery()
152if enum is None:
153 POWER_TIME_UNKNOWN = -1
154 POWER_TIME_UNLIMITED = -2
155else:
157 class BatteryTime(enum.IntEnum):
158 POWER_TIME_UNKNOWN = -1
159 POWER_TIME_UNLIMITED = -2
161 globals().update(BatteryTime.__members__)
163# --- others
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"
175# ===================================================================
176# --- namedtuples
177# ===================================================================
179# --- for system functions
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
224# --- for Process methods
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)
251# psutil.connections() and psutil.Process.connections()
252addr = namedtuple('addr', ['ip', 'port'])
255# ===================================================================
256# --- Process.connections() 'kind' parameter mapping
257# ===================================================================
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}
271if AF_INET6 is not None:
272 conn_tmap.update({
273 "tcp6": ([AF_INET6], [SOCK_STREAM]),
274 "udp6": ([AF_INET6], [SOCK_DGRAM]),
275 })
277if AF_UNIX is not None:
278 conn_tmap.update({"unix": ([AF_UNIX], [SOCK_STREAM, SOCK_DGRAM])})
281# =====================================================================
282# --- Exceptions
283# =====================================================================
286class Error(Exception):
287 """Base exception class. All other psutil exceptions inherit
288 from this one.
289 """
291 __module__ = 'psutil'
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
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])
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)
321class NoSuchProcess(Error):
322 """Exception raised when a process with a certain PID doesn't
323 or no longer exists.
324 """
326 __module__ = 'psutil'
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"
335class ZombieProcess(NoSuchProcess):
336 """Exception raised when querying a zombie process. This is
337 raised on macOS, BSD and Solaris only, and not always: depending
338 on the query the OS may be able to succeed anyway.
339 On Linux all zombie processes are querable (hence this is never
340 raised). Windows doesn't have zombie processes.
341 """
343 __module__ = 'psutil'
345 def __init__(self, pid, name=None, ppid=None, msg=None):
346 NoSuchProcess.__init__(self, pid, name, msg)
347 self.ppid = ppid
348 self.msg = msg or "PID still exists but it's a zombie"
351class AccessDenied(Error):
352 """Exception raised when permission to perform an action is denied."""
354 __module__ = 'psutil'
356 def __init__(self, pid=None, name=None, msg=None):
357 Error.__init__(self)
358 self.pid = pid
359 self.name = name
360 self.msg = msg or ""
363class TimeoutExpired(Error):
364 """Raised on Process.wait(timeout) if timeout expires and process
365 is still alive.
366 """
368 __module__ = 'psutil'
370 def __init__(self, seconds, pid=None, name=None):
371 Error.__init__(self)
372 self.seconds = seconds
373 self.pid = pid
374 self.name = name
375 self.msg = "timeout after %s seconds" % seconds
378# ===================================================================
379# --- utils
380# ===================================================================
383# This should be in _compat.py rather than here, but does not work well
384# with setup.py importing this module via a sys.path trick.
385if PY3:
386 if isinstance(__builtins__, dict): # cpython
387 exec_ = __builtins__["exec"]
388 else: # pypy
389 exec_ = getattr(__builtins__, "exec") # noqa
391 exec_("""def raise_from(value, from_value):
392 try:
393 raise value from from_value
394 finally:
395 value = None
396 """)
397else:
399 def raise_from(value, from_value):
400 raise value
403def usage_percent(used, total, round_=None):
404 """Calculate percentage usage of 'used' against 'total'."""
405 try:
406 ret = (float(used) / total) * 100
407 except ZeroDivisionError:
408 return 0.0
409 else:
410 if round_ is not None:
411 ret = round(ret, round_)
412 return ret
415def memoize(fun):
416 """A simple memoize decorator for functions supporting (hashable)
417 positional arguments.
418 It also provides a cache_clear() function for clearing the cache:
420 >>> @memoize
421 ... def foo()
422 ... return 1
423 ...
424 >>> foo()
425 1
426 >>> foo.cache_clear()
427 >>>
429 It supports:
430 - functions
431 - classes (acts as a @singleton)
432 - staticmethods
433 - classmethods
435 It does NOT support:
436 - methods
437 """
439 @functools.wraps(fun)
440 def wrapper(*args, **kwargs):
441 key = (args, frozenset(sorted(kwargs.items())))
442 try:
443 return cache[key]
444 except KeyError:
445 try:
446 ret = cache[key] = fun(*args, **kwargs)
447 except Exception as err: # noqa: BLE001
448 raise raise_from(err, None)
449 return ret
451 def cache_clear():
452 """Clear cache."""
453 cache.clear()
455 cache = {}
456 wrapper.cache_clear = cache_clear
457 return wrapper
460def memoize_when_activated(fun):
461 """A memoize decorator which is disabled by default. It can be
462 activated and deactivated on request.
463 For efficiency reasons it can be used only against class methods
464 accepting no arguments.
466 >>> class Foo:
467 ... @memoize
468 ... def foo()
469 ... print(1)
470 ...
471 >>> f = Foo()
472 >>> # deactivated (default)
473 >>> foo()
474 1
475 >>> foo()
476 1
477 >>>
478 >>> # activated
479 >>> foo.cache_activate(self)
480 >>> foo()
481 1
482 >>> foo()
483 >>> foo()
484 >>>
485 """
487 @functools.wraps(fun)
488 def wrapper(self):
489 try:
490 # case 1: we previously entered oneshot() ctx
491 ret = self._cache[fun]
492 except AttributeError:
493 # case 2: we never entered oneshot() ctx
494 try:
495 return fun(self)
496 except Exception as err: # noqa: BLE001
497 raise raise_from(err, None)
498 except KeyError:
499 # case 3: we entered oneshot() ctx but there's no cache
500 # for this entry yet
501 try:
502 ret = fun(self)
503 except Exception as err: # noqa: BLE001
504 raise raise_from(err, None)
505 try:
506 self._cache[fun] = ret
507 except AttributeError:
508 # multi-threading race condition, see:
509 # https://github.com/giampaolo/psutil/issues/1948
510 pass
511 return ret
513 def cache_activate(proc):
514 """Activate cache. Expects a Process instance. Cache will be
515 stored as a "_cache" instance attribute.
516 """
517 proc._cache = {}
519 def cache_deactivate(proc):
520 """Deactivate and clear cache."""
521 try:
522 del proc._cache
523 except AttributeError:
524 pass
526 wrapper.cache_activate = cache_activate
527 wrapper.cache_deactivate = cache_deactivate
528 return wrapper
531def isfile_strict(path):
532 """Same as os.path.isfile() but does not swallow EACCES / EPERM
533 exceptions, see:
534 http://mail.python.org/pipermail/python-dev/2012-June/120787.html.
535 """
536 try:
537 st = os.stat(path)
538 except OSError as err:
539 if err.errno in (errno.EPERM, errno.EACCES):
540 raise
541 return False
542 else:
543 return stat.S_ISREG(st.st_mode)
546def path_exists_strict(path):
547 """Same as os.path.exists() but does not swallow EACCES / EPERM
548 exceptions. See:
549 http://mail.python.org/pipermail/python-dev/2012-June/120787.html.
550 """
551 try:
552 os.stat(path)
553 except OSError as err:
554 if err.errno in (errno.EPERM, errno.EACCES):
555 raise
556 return False
557 else:
558 return True
561@memoize
562def supports_ipv6():
563 """Return True if IPv6 is supported on this platform."""
564 if not socket.has_ipv6 or AF_INET6 is None:
565 return False
566 try:
567 sock = socket.socket(AF_INET6, socket.SOCK_STREAM)
568 with contextlib.closing(sock):
569 sock.bind(("::1", 0))
570 return True
571 except socket.error:
572 return False
575def parse_environ_block(data):
576 """Parse a C environ block of environment variables into a dictionary."""
577 # The block is usually raw data from the target process. It might contain
578 # trailing garbage and lines that do not look like assignments.
579 ret = {}
580 pos = 0
582 # localize global variable to speed up access.
583 WINDOWS_ = WINDOWS
584 while True:
585 next_pos = data.find("\0", pos)
586 # nul byte at the beginning or double nul byte means finish
587 if next_pos <= pos:
588 break
589 # there might not be an equals sign
590 equal_pos = data.find("=", pos, next_pos)
591 if equal_pos > pos:
592 key = data[pos:equal_pos]
593 value = data[equal_pos + 1 : next_pos]
594 # Windows expects environment variables to be uppercase only
595 if WINDOWS_:
596 key = key.upper()
597 ret[key] = value
598 pos = next_pos + 1
600 return ret
603def sockfam_to_enum(num):
604 """Convert a numeric socket family value to an IntEnum member.
605 If it's not a known member, return the numeric value itself.
606 """
607 if enum is None:
608 return num
609 else: # pragma: no cover
610 try:
611 return socket.AddressFamily(num)
612 except ValueError:
613 return num
616def socktype_to_enum(num):
617 """Convert a numeric socket type value to an IntEnum member.
618 If it's not a known member, return the numeric value itself.
619 """
620 if enum is None:
621 return num
622 else: # pragma: no cover
623 try:
624 return socket.SocketKind(num)
625 except ValueError:
626 return num
629def conn_to_ntuple(fd, fam, type_, laddr, raddr, status, status_map, pid=None):
630 """Convert a raw connection tuple to a proper ntuple."""
631 if fam in (socket.AF_INET, AF_INET6):
632 if laddr:
633 laddr = addr(*laddr)
634 if raddr:
635 raddr = addr(*raddr)
636 if type_ == socket.SOCK_STREAM and fam in (AF_INET, AF_INET6):
637 status = status_map.get(status, CONN_NONE)
638 else:
639 status = CONN_NONE # ignore whatever C returned to us
640 fam = sockfam_to_enum(fam)
641 type_ = socktype_to_enum(type_)
642 if pid is None:
643 return pconn(fd, fam, type_, laddr, raddr, status)
644 else:
645 return sconn(fd, fam, type_, laddr, raddr, status, pid)
648def deprecated_method(replacement):
649 """A decorator which can be used to mark a method as deprecated
650 'replcement' is the method name which will be called instead.
651 """
653 def outer(fun):
654 msg = "%s() is deprecated and will be removed; use %s() instead" % (
655 fun.__name__,
656 replacement,
657 )
658 if fun.__doc__ is None:
659 fun.__doc__ = msg
661 @functools.wraps(fun)
662 def inner(self, *args, **kwargs):
663 warnings.warn(msg, category=DeprecationWarning, stacklevel=2)
664 return getattr(self, replacement)(*args, **kwargs)
666 return inner
668 return outer
671class _WrapNumbers:
672 """Watches numbers so that they don't overflow and wrap
673 (reset to zero).
674 """
676 def __init__(self):
677 self.lock = threading.Lock()
678 self.cache = {}
679 self.reminders = {}
680 self.reminder_keys = {}
682 def _add_dict(self, input_dict, name):
683 assert name not in self.cache
684 assert name not in self.reminders
685 assert name not in self.reminder_keys
686 self.cache[name] = input_dict
687 self.reminders[name] = collections.defaultdict(int)
688 self.reminder_keys[name] = collections.defaultdict(set)
690 def _remove_dead_reminders(self, input_dict, name):
691 """In case the number of keys changed between calls (e.g. a
692 disk disappears) this removes the entry from self.reminders.
693 """
694 old_dict = self.cache[name]
695 gone_keys = set(old_dict.keys()) - set(input_dict.keys())
696 for gone_key in gone_keys:
697 for remkey in self.reminder_keys[name][gone_key]:
698 del self.reminders[name][remkey]
699 del self.reminder_keys[name][gone_key]
701 def run(self, input_dict, name):
702 """Cache dict and sum numbers which overflow and wrap.
703 Return an updated copy of `input_dict`.
704 """
705 if name not in self.cache:
706 # This was the first call.
707 self._add_dict(input_dict, name)
708 return input_dict
710 self._remove_dead_reminders(input_dict, name)
712 old_dict = self.cache[name]
713 new_dict = {}
714 for key in input_dict:
715 input_tuple = input_dict[key]
716 try:
717 old_tuple = old_dict[key]
718 except KeyError:
719 # The input dict has a new key (e.g. a new disk or NIC)
720 # which didn't exist in the previous call.
721 new_dict[key] = input_tuple
722 continue
724 bits = []
725 for i in range(len(input_tuple)):
726 input_value = input_tuple[i]
727 old_value = old_tuple[i]
728 remkey = (key, i)
729 if input_value < old_value:
730 # it wrapped!
731 self.reminders[name][remkey] += old_value
732 self.reminder_keys[name][key].add(remkey)
733 bits.append(input_value + self.reminders[name][remkey])
735 new_dict[key] = tuple(bits)
737 self.cache[name] = input_dict
738 return new_dict
740 def cache_clear(self, name=None):
741 """Clear the internal cache, optionally only for function 'name'."""
742 with self.lock:
743 if name is None:
744 self.cache.clear()
745 self.reminders.clear()
746 self.reminder_keys.clear()
747 else:
748 self.cache.pop(name, None)
749 self.reminders.pop(name, None)
750 self.reminder_keys.pop(name, None)
752 def cache_info(self):
753 """Return internal cache dicts as a tuple of 3 elements."""
754 with self.lock:
755 return (self.cache, self.reminders, self.reminder_keys)
758def wrap_numbers(input_dict, name):
759 """Given an `input_dict` and a function `name`, adjust the numbers
760 which "wrap" (restart from zero) across different calls by adding
761 "old value" to "new value" and return an updated dict.
762 """
763 with _wn.lock:
764 return _wn.run(input_dict, name)
767_wn = _WrapNumbers()
768wrap_numbers.cache_clear = _wn.cache_clear
769wrap_numbers.cache_info = _wn.cache_info
772# The read buffer size for open() builtin. This (also) dictates how
773# much data we read(2) when iterating over file lines as in:
774# >>> with open(file) as f:
775# ... for line in f:
776# ... ...
777# Default per-line buffer size for binary files is 1K. For text files
778# is 8K. We use a bigger buffer (32K) in order to have more consistent
779# results when reading /proc pseudo files on Linux, see:
780# https://github.com/giampaolo/psutil/issues/2050
781# On Python 2 this also speeds up the reading of big files:
782# (namely /proc/{pid}/smaps and /proc/net/*):
783# https://github.com/giampaolo/psutil/issues/708
784FILE_READ_BUFFER_SIZE = 32 * 1024
787def open_binary(fname):
788 return open(fname, "rb", buffering=FILE_READ_BUFFER_SIZE)
791def open_text(fname):
792 """On Python 3 opens a file in text mode by using fs encoding and
793 a proper en/decoding errors handler.
794 On Python 2 this is just an alias for open(name, 'rt').
795 """
796 if not PY3:
797 return open(fname, buffering=FILE_READ_BUFFER_SIZE)
799 # See:
800 # https://github.com/giampaolo/psutil/issues/675
801 # https://github.com/giampaolo/psutil/pull/733
802 fobj = open(
803 fname,
804 buffering=FILE_READ_BUFFER_SIZE,
805 encoding=ENCODING,
806 errors=ENCODING_ERRS,
807 )
808 try:
809 # Dictates per-line read(2) buffer size. Defaults is 8k. See:
810 # https://github.com/giampaolo/psutil/issues/2050#issuecomment-1013387546
811 fobj._CHUNK_SIZE = FILE_READ_BUFFER_SIZE
812 except AttributeError:
813 pass
814 except Exception:
815 fobj.close()
816 raise
818 return fobj
821def cat(fname, fallback=_DEFAULT, _open=open_text):
822 """Read entire file content and return it as a string. File is
823 opened in text mode. If specified, `fallback` is the value
824 returned in case of error, either if the file does not exist or
825 it can't be read().
826 """
827 if fallback is _DEFAULT:
828 with _open(fname) as f:
829 return f.read()
830 else:
831 try:
832 with _open(fname) as f:
833 return f.read()
834 except (IOError, OSError):
835 return fallback
838def bcat(fname, fallback=_DEFAULT):
839 """Same as above but opens file in binary mode."""
840 return cat(fname, fallback=fallback, _open=open_binary)
843def bytes2human(n, format="%(value).1f%(symbol)s"):
844 """Used by various scripts. See: http://goo.gl/zeJZl.
846 >>> bytes2human(10000)
847 '9.8K'
848 >>> bytes2human(100001221)
849 '95.4M'
850 """
851 symbols = ('B', 'K', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y')
852 prefix = {}
853 for i, s in enumerate(symbols[1:]):
854 prefix[s] = 1 << (i + 1) * 10
855 for symbol in reversed(symbols[1:]):
856 if abs(n) >= prefix[symbol]:
857 value = float(n) / prefix[symbol]
858 return format % locals()
859 return format % dict(symbol=symbols[0], value=n)
862def get_procfs_path():
863 """Return updated psutil.PROCFS_PATH constant."""
864 return sys.modules['psutil'].PROCFS_PATH
867if PY3:
869 def decode(s):
870 return s.decode(encoding=ENCODING, errors=ENCODING_ERRS)
872else:
874 def decode(s):
875 return s
878# =====================================================================
879# --- shell utils
880# =====================================================================
883@memoize
884def term_supports_colors(file=sys.stdout): # pragma: no cover
885 if os.name == 'nt':
886 return True
887 try:
888 import curses
890 assert file.isatty()
891 curses.setupterm()
892 assert curses.tigetnum("colors") > 0
893 except Exception: # noqa: BLE001
894 return False
895 else:
896 return True
899def hilite(s, color=None, bold=False): # pragma: no cover
900 """Return an highlighted version of 'string'."""
901 if not term_supports_colors():
902 return s
903 attr = []
904 colors = dict(
905 blue='34',
906 brown='33',
907 darkgrey='30',
908 green='32',
909 grey='37',
910 lightblue='36',
911 red='91',
912 violet='35',
913 yellow='93',
914 )
915 colors[None] = '29'
916 try:
917 color = colors[color]
918 except KeyError:
919 raise ValueError(
920 "invalid color %r; choose between %s" % (list(colors.keys()))
921 )
922 attr.append(color)
923 if bold:
924 attr.append('1')
925 return '\x1b[%sm%s\x1b[0m' % (';'.join(attr), s)
928def print_color(
929 s, color=None, bold=False, file=sys.stdout
930): # pragma: no cover
931 """Print a colorized version of string."""
932 if not term_supports_colors():
933 print(s, file=file) # NOQA
934 elif POSIX:
935 print(hilite(s, color, bold), file=file) # NOQA
936 else:
937 import ctypes
939 DEFAULT_COLOR = 7
940 GetStdHandle = ctypes.windll.Kernel32.GetStdHandle
941 SetConsoleTextAttribute = (
942 ctypes.windll.Kernel32.SetConsoleTextAttribute
943 )
945 colors = dict(green=2, red=4, brown=6, yellow=6)
946 colors[None] = DEFAULT_COLOR
947 try:
948 color = colors[color]
949 except KeyError:
950 raise ValueError(
951 "invalid color %r; choose between %r"
952 % (color, list(colors.keys()))
953 )
954 if bold and color <= 7:
955 color += 8
957 handle_id = -12 if file is sys.stderr else -11
958 GetStdHandle.restype = ctypes.c_ulong
959 handle = GetStdHandle(handle_id)
960 SetConsoleTextAttribute(handle, color)
961 try:
962 print(s, file=file) # NOQA
963 finally:
964 SetConsoleTextAttribute(handle, DEFAULT_COLOR)
967def debug(msg):
968 """If PSUTIL_DEBUG env var is set, print a debug message to stderr."""
969 if PSUTIL_DEBUG:
970 import inspect
972 fname, lineno, _, lines, index = inspect.getframeinfo(
973 inspect.currentframe().f_back
974 )
975 if isinstance(msg, Exception):
976 if isinstance(msg, (OSError, IOError, EnvironmentError)):
977 # ...because str(exc) may contain info about the file name
978 msg = "ignoring %s" % msg
979 else:
980 msg = "ignoring %r" % msg
981 print( # noqa
982 "psutil-debug [%s:%s]> %s" % (fname, lineno, msg), file=sys.stderr
983 )