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