Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/psutil/_common.py: 47%
407 statements
« prev ^ index » next coverage.py v7.0.1, created at 2022-12-25 06:11 +0000
« prev ^ index » next coverage.py v7.0.1, created at 2022-12-25 06:11 +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', 0))
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# ===================================================================
369def usage_percent(used, total, round_=None):
370 """Calculate percentage usage of 'used' against 'total'."""
371 try:
372 ret = (float(used) / total) * 100
373 except ZeroDivisionError:
374 return 0.0
375 else:
376 if round_ is not None:
377 ret = round(ret, round_)
378 return ret
381def memoize(fun):
382 """A simple memoize decorator for functions supporting (hashable)
383 positional arguments.
384 It also provides a cache_clear() function for clearing the cache:
386 >>> @memoize
387 ... def foo()
388 ... return 1
389 ...
390 >>> foo()
391 1
392 >>> foo.cache_clear()
393 >>>
394 """
395 @functools.wraps(fun)
396 def wrapper(*args, **kwargs):
397 key = (args, frozenset(sorted(kwargs.items())))
398 try:
399 return cache[key]
400 except KeyError:
401 ret = cache[key] = fun(*args, **kwargs)
402 return ret
404 def cache_clear():
405 """Clear cache."""
406 cache.clear()
408 cache = {}
409 wrapper.cache_clear = cache_clear
410 return wrapper
413def memoize_when_activated(fun):
414 """A memoize decorator which is disabled by default. It can be
415 activated and deactivated on request.
416 For efficiency reasons it can be used only against class methods
417 accepting no arguments.
419 >>> class Foo:
420 ... @memoize
421 ... def foo()
422 ... print(1)
423 ...
424 >>> f = Foo()
425 >>> # deactivated (default)
426 >>> foo()
427 1
428 >>> foo()
429 1
430 >>>
431 >>> # activated
432 >>> foo.cache_activate(self)
433 >>> foo()
434 1
435 >>> foo()
436 >>> foo()
437 >>>
438 """
439 @functools.wraps(fun)
440 def wrapper(self):
441 try:
442 # case 1: we previously entered oneshot() ctx
443 ret = self._cache[fun]
444 except AttributeError:
445 # case 2: we never entered oneshot() ctx
446 return fun(self)
447 except KeyError:
448 # case 3: we entered oneshot() ctx but there's no cache
449 # for this entry yet
450 ret = fun(self)
451 try:
452 self._cache[fun] = ret
453 except AttributeError:
454 # multi-threading race condition, see:
455 # https://github.com/giampaolo/psutil/issues/1948
456 pass
457 return ret
459 def cache_activate(proc):
460 """Activate cache. Expects a Process instance. Cache will be
461 stored as a "_cache" instance attribute."""
462 proc._cache = {}
464 def cache_deactivate(proc):
465 """Deactivate and clear cache."""
466 try:
467 del proc._cache
468 except AttributeError:
469 pass
471 wrapper.cache_activate = cache_activate
472 wrapper.cache_deactivate = cache_deactivate
473 return wrapper
476def isfile_strict(path):
477 """Same as os.path.isfile() but does not swallow EACCES / EPERM
478 exceptions, see:
479 http://mail.python.org/pipermail/python-dev/2012-June/120787.html
480 """
481 try:
482 st = os.stat(path)
483 except OSError as err:
484 if err.errno in (errno.EPERM, errno.EACCES):
485 raise
486 return False
487 else:
488 return stat.S_ISREG(st.st_mode)
491def path_exists_strict(path):
492 """Same as os.path.exists() but does not swallow EACCES / EPERM
493 exceptions, see:
494 http://mail.python.org/pipermail/python-dev/2012-June/120787.html
495 """
496 try:
497 os.stat(path)
498 except OSError as err:
499 if err.errno in (errno.EPERM, errno.EACCES):
500 raise
501 return False
502 else:
503 return True
506@memoize
507def supports_ipv6():
508 """Return True if IPv6 is supported on this platform."""
509 if not socket.has_ipv6 or AF_INET6 is None:
510 return False
511 try:
512 sock = socket.socket(AF_INET6, socket.SOCK_STREAM)
513 with contextlib.closing(sock):
514 sock.bind(("::1", 0))
515 return True
516 except socket.error:
517 return False
520def parse_environ_block(data):
521 """Parse a C environ block of environment variables into a dictionary."""
522 # The block is usually raw data from the target process. It might contain
523 # trailing garbage and lines that do not look like assignments.
524 ret = {}
525 pos = 0
527 # localize global variable to speed up access.
528 WINDOWS_ = WINDOWS
529 while True:
530 next_pos = data.find("\0", pos)
531 # nul byte at the beginning or double nul byte means finish
532 if next_pos <= pos:
533 break
534 # there might not be an equals sign
535 equal_pos = data.find("=", pos, next_pos)
536 if equal_pos > pos:
537 key = data[pos:equal_pos]
538 value = data[equal_pos + 1:next_pos]
539 # Windows expects environment variables to be uppercase only
540 if WINDOWS_:
541 key = key.upper()
542 ret[key] = value
543 pos = next_pos + 1
545 return ret
548def sockfam_to_enum(num):
549 """Convert a numeric socket family value to an IntEnum member.
550 If it's not a known member, return the numeric value itself.
551 """
552 if enum is None:
553 return num
554 else: # pragma: no cover
555 try:
556 return socket.AddressFamily(num)
557 except ValueError:
558 return num
561def socktype_to_enum(num):
562 """Convert a numeric socket type value to an IntEnum member.
563 If it's not a known member, return the numeric value itself.
564 """
565 if enum is None:
566 return num
567 else: # pragma: no cover
568 try:
569 return socket.SocketKind(num)
570 except ValueError:
571 return num
574def conn_to_ntuple(fd, fam, type_, laddr, raddr, status, status_map, pid=None):
575 """Convert a raw connection tuple to a proper ntuple."""
576 if fam in (socket.AF_INET, AF_INET6):
577 if laddr:
578 laddr = addr(*laddr)
579 if raddr:
580 raddr = addr(*raddr)
581 if type_ == socket.SOCK_STREAM and fam in (AF_INET, AF_INET6):
582 status = status_map.get(status, CONN_NONE)
583 else:
584 status = CONN_NONE # ignore whatever C returned to us
585 fam = sockfam_to_enum(fam)
586 type_ = socktype_to_enum(type_)
587 if pid is None:
588 return pconn(fd, fam, type_, laddr, raddr, status)
589 else:
590 return sconn(fd, fam, type_, laddr, raddr, status, pid)
593def deprecated_method(replacement):
594 """A decorator which can be used to mark a method as deprecated
595 'replcement' is the method name which will be called instead.
596 """
597 def outer(fun):
598 msg = "%s() is deprecated and will be removed; use %s() instead" % (
599 fun.__name__, replacement)
600 if fun.__doc__ is None:
601 fun.__doc__ = msg
603 @functools.wraps(fun)
604 def inner(self, *args, **kwargs):
605 warnings.warn(msg, category=DeprecationWarning, stacklevel=2)
606 return getattr(self, replacement)(*args, **kwargs)
607 return inner
608 return outer
611class _WrapNumbers:
612 """Watches numbers so that they don't overflow and wrap
613 (reset to zero).
614 """
616 def __init__(self):
617 self.lock = threading.Lock()
618 self.cache = {}
619 self.reminders = {}
620 self.reminder_keys = {}
622 def _add_dict(self, input_dict, name):
623 assert name not in self.cache
624 assert name not in self.reminders
625 assert name not in self.reminder_keys
626 self.cache[name] = input_dict
627 self.reminders[name] = collections.defaultdict(int)
628 self.reminder_keys[name] = collections.defaultdict(set)
630 def _remove_dead_reminders(self, input_dict, name):
631 """In case the number of keys changed between calls (e.g. a
632 disk disappears) this removes the entry from self.reminders.
633 """
634 old_dict = self.cache[name]
635 gone_keys = set(old_dict.keys()) - set(input_dict.keys())
636 for gone_key in gone_keys:
637 for remkey in self.reminder_keys[name][gone_key]:
638 del self.reminders[name][remkey]
639 del self.reminder_keys[name][gone_key]
641 def run(self, input_dict, name):
642 """Cache dict and sum numbers which overflow and wrap.
643 Return an updated copy of `input_dict`
644 """
645 if name not in self.cache:
646 # This was the first call.
647 self._add_dict(input_dict, name)
648 return input_dict
650 self._remove_dead_reminders(input_dict, name)
652 old_dict = self.cache[name]
653 new_dict = {}
654 for key in input_dict.keys():
655 input_tuple = input_dict[key]
656 try:
657 old_tuple = old_dict[key]
658 except KeyError:
659 # The input dict has a new key (e.g. a new disk or NIC)
660 # which didn't exist in the previous call.
661 new_dict[key] = input_tuple
662 continue
664 bits = []
665 for i in range(len(input_tuple)):
666 input_value = input_tuple[i]
667 old_value = old_tuple[i]
668 remkey = (key, i)
669 if input_value < old_value:
670 # it wrapped!
671 self.reminders[name][remkey] += old_value
672 self.reminder_keys[name][key].add(remkey)
673 bits.append(input_value + self.reminders[name][remkey])
675 new_dict[key] = tuple(bits)
677 self.cache[name] = input_dict
678 return new_dict
680 def cache_clear(self, name=None):
681 """Clear the internal cache, optionally only for function 'name'."""
682 with self.lock:
683 if name is None:
684 self.cache.clear()
685 self.reminders.clear()
686 self.reminder_keys.clear()
687 else:
688 self.cache.pop(name, None)
689 self.reminders.pop(name, None)
690 self.reminder_keys.pop(name, None)
692 def cache_info(self):
693 """Return internal cache dicts as a tuple of 3 elements."""
694 with self.lock:
695 return (self.cache, self.reminders, self.reminder_keys)
698def wrap_numbers(input_dict, name):
699 """Given an `input_dict` and a function `name`, adjust the numbers
700 which "wrap" (restart from zero) across different calls by adding
701 "old value" to "new value" and return an updated dict.
702 """
703 with _wn.lock:
704 return _wn.run(input_dict, name)
707_wn = _WrapNumbers()
708wrap_numbers.cache_clear = _wn.cache_clear
709wrap_numbers.cache_info = _wn.cache_info
712# The read buffer size for open() builtin. This (also) dictates how
713# much data we read(2) when iterating over file lines as in:
714# >>> with open(file) as f:
715# ... for line in f:
716# ... ...
717# Default per-line buffer size for binary files is 1K. For text files
718# is 8K. We use a bigger buffer (32K) in order to have more consistent
719# results when reading /proc pseudo files on Linux, see:
720# https://github.com/giampaolo/psutil/issues/2050
721# On Python 2 this also speeds up the reading of big files:
722# (namely /proc/{pid}/smaps and /proc/net/*):
723# https://github.com/giampaolo/psutil/issues/708
724FILE_READ_BUFFER_SIZE = 32 * 1024
727def open_binary(fname):
728 return open(fname, "rb", buffering=FILE_READ_BUFFER_SIZE)
731def open_text(fname):
732 """On Python 3 opens a file in text mode by using fs encoding and
733 a proper en/decoding errors handler.
734 On Python 2 this is just an alias for open(name, 'rt').
735 """
736 if not PY3:
737 return open(fname, "rt", buffering=FILE_READ_BUFFER_SIZE)
739 # See:
740 # https://github.com/giampaolo/psutil/issues/675
741 # https://github.com/giampaolo/psutil/pull/733
742 fobj = open(fname, "rt", buffering=FILE_READ_BUFFER_SIZE,
743 encoding=ENCODING, errors=ENCODING_ERRS)
744 try:
745 # Dictates per-line read(2) buffer size. Defaults is 8k. See:
746 # https://github.com/giampaolo/psutil/issues/2050#issuecomment-1013387546
747 fobj._CHUNK_SIZE = FILE_READ_BUFFER_SIZE
748 except AttributeError:
749 pass
750 except Exception:
751 fobj.close()
752 raise
754 return fobj
757def cat(fname, fallback=_DEFAULT, _open=open_text):
758 """Read entire file content and return it as a string. File is
759 opened in text mode. If specified, `fallback` is the value
760 returned in case of error, either if the file does not exist or
761 it can't be read().
762 """
763 if fallback is _DEFAULT:
764 with _open(fname) as f:
765 return f.read()
766 else:
767 try:
768 with _open(fname) as f:
769 return f.read()
770 except (IOError, OSError):
771 return fallback
774def bcat(fname, fallback=_DEFAULT):
775 """Same as above but opens file in binary mode."""
776 return cat(fname, fallback=fallback, _open=open_binary)
779def bytes2human(n, format="%(value).1f%(symbol)s"):
780 """Used by various scripts. See:
781 http://goo.gl/zeJZl
783 >>> bytes2human(10000)
784 '9.8K'
785 >>> bytes2human(100001221)
786 '95.4M'
787 """
788 symbols = ('B', 'K', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y')
789 prefix = {}
790 for i, s in enumerate(symbols[1:]):
791 prefix[s] = 1 << (i + 1) * 10
792 for symbol in reversed(symbols[1:]):
793 if n >= prefix[symbol]:
794 value = float(n) / prefix[symbol]
795 return format % locals()
796 return format % dict(symbol=symbols[0], value=n)
799def get_procfs_path():
800 """Return updated psutil.PROCFS_PATH constant."""
801 return sys.modules['psutil'].PROCFS_PATH
804if PY3:
805 def decode(s):
806 return s.decode(encoding=ENCODING, errors=ENCODING_ERRS)
807else:
808 def decode(s):
809 return s
812# =====================================================================
813# --- shell utils
814# =====================================================================
817@memoize
818def term_supports_colors(file=sys.stdout): # pragma: no cover
819 if os.name == 'nt':
820 return True
821 try:
822 import curses
823 assert file.isatty()
824 curses.setupterm()
825 assert curses.tigetnum("colors") > 0
826 except Exception:
827 return False
828 else:
829 return True
832def hilite(s, color=None, bold=False): # pragma: no cover
833 """Return an highlighted version of 'string'."""
834 if not term_supports_colors():
835 return s
836 attr = []
837 colors = dict(green='32', red='91', brown='33', yellow='93', blue='34',
838 violet='35', lightblue='36', grey='37', darkgrey='30')
839 colors[None] = '29'
840 try:
841 color = colors[color]
842 except KeyError:
843 raise ValueError("invalid color %r; choose between %s" % (
844 list(colors.keys())))
845 attr.append(color)
846 if bold:
847 attr.append('1')
848 return '\x1b[%sm%s\x1b[0m' % (';'.join(attr), s)
851def print_color(
852 s, color=None, bold=False, file=sys.stdout): # pragma: no cover
853 """Print a colorized version of string."""
854 if not term_supports_colors():
855 print(s, file=file) # NOQA
856 elif POSIX:
857 print(hilite(s, color, bold), file=file) # NOQA
858 else:
859 import ctypes
861 DEFAULT_COLOR = 7
862 GetStdHandle = ctypes.windll.Kernel32.GetStdHandle
863 SetConsoleTextAttribute = \
864 ctypes.windll.Kernel32.SetConsoleTextAttribute
866 colors = dict(green=2, red=4, brown=6, yellow=6)
867 colors[None] = DEFAULT_COLOR
868 try:
869 color = colors[color]
870 except KeyError:
871 raise ValueError("invalid color %r; choose between %r" % (
872 color, list(colors.keys())))
873 if bold and color <= 7:
874 color += 8
876 handle_id = -12 if file is sys.stderr else -11
877 GetStdHandle.restype = ctypes.c_ulong
878 handle = GetStdHandle(handle_id)
879 SetConsoleTextAttribute(handle, color)
880 try:
881 print(s, file=file) # NOQA
882 finally:
883 SetConsoleTextAttribute(handle, DEFAULT_COLOR)
886def debug(msg):
887 """If PSUTIL_DEBUG env var is set, print a debug message to stderr."""
888 if PSUTIL_DEBUG:
889 import inspect
890 fname, lineno, func_name, lines, index = inspect.getframeinfo(
891 inspect.currentframe().f_back)
892 if isinstance(msg, Exception):
893 if isinstance(msg, (OSError, IOError, EnvironmentError)):
894 # ...because str(exc) may contain info about the file name
895 msg = "ignoring %s" % msg
896 else:
897 msg = "ignoring %r" % msg
898 print("psutil-debug [%s:%s]> %s" % (fname, lineno, msg), # NOQA
899 file=sys.stderr)