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
« 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.
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"
334 def __reduce__(self):
335 return (self.__class__, (self.pid, self.name, self.msg))
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 """
346 __module__ = 'psutil'
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"
353 def __reduce__(self):
354 return (self.__class__, (self.pid, self.name, self.ppid, self.msg))
357class AccessDenied(Error):
358 """Exception raised when permission to perform an action is denied."""
360 __module__ = 'psutil'
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 ""
368 def __reduce__(self):
369 return (self.__class__, (self.pid, self.name, self.msg))
372class TimeoutExpired(Error):
373 """Raised on Process.wait(timeout) if timeout expires and process
374 is still alive.
375 """
377 __module__ = 'psutil'
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
386 def __reduce__(self):
387 return (self.__class__, (self.seconds, self.pid, self.name))
390# ===================================================================
391# --- utils
392# ===================================================================
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
403 exec_("""def raise_from(value, from_value):
404 try:
405 raise value from from_value
406 finally:
407 value = None
408 """)
409else:
411 def raise_from(value, from_value):
412 raise value
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
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:
432 >>> @memoize
433 ... def foo()
434 ... return 1
435 ...
436 >>> foo()
437 1
438 >>> foo.cache_clear()
439 >>>
441 It supports:
442 - functions
443 - classes (acts as a @singleton)
444 - staticmethods
445 - classmethods
447 It does NOT support:
448 - methods
449 """
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
463 def cache_clear():
464 """Clear cache."""
465 cache.clear()
467 cache = {}
468 wrapper.cache_clear = cache_clear
469 return wrapper
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.
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 """
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
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 = {}
531 def cache_deactivate(proc):
532 """Deactivate and clear cache."""
533 try:
534 del proc._cache
535 except AttributeError:
536 pass
538 wrapper.cache_activate = cache_activate
539 wrapper.cache_deactivate = cache_deactivate
540 return wrapper
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)
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
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
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
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
612 return ret
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
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
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)
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 """
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
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)
678 return inner
680 return outer
683class _WrapNumbers:
684 """Watches numbers so that they don't overflow and wrap
685 (reset to zero).
686 """
688 def __init__(self):
689 self.lock = threading.Lock()
690 self.cache = {}
691 self.reminders = {}
692 self.reminder_keys = {}
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)
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]
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
722 self._remove_dead_reminders(input_dict, name)
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
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])
747 new_dict[key] = tuple(bits)
749 self.cache[name] = input_dict
750 return new_dict
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)
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)
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)
779_wn = _WrapNumbers()
780wrap_numbers.cache_clear = _wn.cache_clear
781wrap_numbers.cache_info = _wn.cache_info
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
799def open_binary(fname):
800 return open(fname, "rb", buffering=FILE_READ_BUFFER_SIZE)
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)
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
830 return fobj
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
850def bcat(fname, fallback=_DEFAULT):
851 """Same as above but opens file in binary mode."""
852 return cat(fname, fallback=fallback, _open=open_binary)
855def bytes2human(n, format="%(value).1f%(symbol)s"):
856 """Used by various scripts. See: http://goo.gl/zeJZl.
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)
874def get_procfs_path():
875 """Return updated psutil.PROCFS_PATH constant."""
876 return sys.modules['psutil'].PROCFS_PATH
879if PY3:
881 def decode(s):
882 return s.decode(encoding=ENCODING, errors=ENCODING_ERRS)
884else:
886 def decode(s):
887 return s
890# =====================================================================
891# --- shell utils
892# =====================================================================
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
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
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)
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
951 DEFAULT_COLOR = 7
952 GetStdHandle = ctypes.windll.Kernel32.GetStdHandle
953 SetConsoleTextAttribute = (
954 ctypes.windll.Kernel32.SetConsoleTextAttribute
955 )
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
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)
979def debug(msg):
980 """If PSUTIL_DEBUG env var is set, print a debug message to stderr."""
981 if PSUTIL_DEBUG:
982 import inspect
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 )