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