comparison env/lib/python3.9/site-packages/psutil/_common.py @ 0:4f3585e2f14b draft default tip

"planemo upload commit 60cee0fc7c0cda8592644e1aad72851dec82c959"
author shellac
date Mon, 22 Mar 2021 18:12:50 +0000 (2021-03-22)
parents
children
comparison
equal deleted inserted replaced
-1:000000000000 0:4f3585e2f14b
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.
4
5 """Common objects shared by __init__.py and _ps*.py modules."""
6
7 # Note: this module is imported by setup.py so it should not import
8 # psutil or third-party modules.
9
10 from __future__ import division, print_function
11
12 import contextlib
13 import errno
14 import functools
15 import os
16 import socket
17 import stat
18 import sys
19 import threading
20 import warnings
21 from collections import defaultdict
22 from collections import namedtuple
23 from socket import AF_INET
24 from socket import SOCK_DGRAM
25 from socket import SOCK_STREAM
26
27 try:
28 from socket import AF_INET6
29 except ImportError:
30 AF_INET6 = None
31 try:
32 from socket import AF_UNIX
33 except ImportError:
34 AF_UNIX = None
35
36 if sys.version_info >= (3, 4):
37 import enum
38 else:
39 enum = None
40
41
42 # can't take it from _common.py as this script is imported by setup.py
43 PY3 = sys.version_info[0] == 3
44
45 __all__ = [
46 # OS constants
47 'FREEBSD', 'BSD', 'LINUX', 'NETBSD', 'OPENBSD', 'MACOS', 'OSX', 'POSIX',
48 'SUNOS', 'WINDOWS',
49 # connection constants
50 'CONN_CLOSE', 'CONN_CLOSE_WAIT', 'CONN_CLOSING', 'CONN_ESTABLISHED',
51 'CONN_FIN_WAIT1', 'CONN_FIN_WAIT2', 'CONN_LAST_ACK', 'CONN_LISTEN',
52 'CONN_NONE', 'CONN_SYN_RECV', 'CONN_SYN_SENT', 'CONN_TIME_WAIT',
53 # net constants
54 'NIC_DUPLEX_FULL', 'NIC_DUPLEX_HALF', 'NIC_DUPLEX_UNKNOWN',
55 # process status constants
56 'STATUS_DEAD', 'STATUS_DISK_SLEEP', 'STATUS_IDLE', 'STATUS_LOCKED',
57 'STATUS_RUNNING', 'STATUS_SLEEPING', 'STATUS_STOPPED', 'STATUS_SUSPENDED',
58 'STATUS_TRACING_STOP', 'STATUS_WAITING', 'STATUS_WAKE_KILL',
59 'STATUS_WAKING', 'STATUS_ZOMBIE', 'STATUS_PARKED',
60 # other constants
61 'ENCODING', 'ENCODING_ERRS', 'AF_INET6',
62 # named tuples
63 'pconn', 'pcputimes', 'pctxsw', 'pgids', 'pio', 'pionice', 'popenfile',
64 'pthread', 'puids', 'sconn', 'scpustats', 'sdiskio', 'sdiskpart',
65 'sdiskusage', 'snetio', 'snicaddr', 'snicstats', 'sswap', 'suser',
66 # utility functions
67 'conn_tmap', 'deprecated_method', 'isfile_strict', 'memoize',
68 'parse_environ_block', 'path_exists_strict', 'usage_percent',
69 'supports_ipv6', 'sockfam_to_enum', 'socktype_to_enum', "wrap_numbers",
70 'bytes2human', 'conn_to_ntuple', 'debug',
71 # shell utils
72 'hilite', 'term_supports_colors', 'print_color',
73 ]
74
75
76 # ===================================================================
77 # --- OS constants
78 # ===================================================================
79
80
81 POSIX = os.name == "posix"
82 WINDOWS = os.name == "nt"
83 LINUX = sys.platform.startswith("linux")
84 MACOS = sys.platform.startswith("darwin")
85 OSX = MACOS # deprecated alias
86 FREEBSD = sys.platform.startswith("freebsd")
87 OPENBSD = sys.platform.startswith("openbsd")
88 NETBSD = sys.platform.startswith("netbsd")
89 BSD = FREEBSD or OPENBSD or NETBSD
90 SUNOS = sys.platform.startswith(("sunos", "solaris"))
91 AIX = sys.platform.startswith("aix")
92
93
94 # ===================================================================
95 # --- API constants
96 # ===================================================================
97
98
99 # Process.status()
100 STATUS_RUNNING = "running"
101 STATUS_SLEEPING = "sleeping"
102 STATUS_DISK_SLEEP = "disk-sleep"
103 STATUS_STOPPED = "stopped"
104 STATUS_TRACING_STOP = "tracing-stop"
105 STATUS_ZOMBIE = "zombie"
106 STATUS_DEAD = "dead"
107 STATUS_WAKE_KILL = "wake-kill"
108 STATUS_WAKING = "waking"
109 STATUS_IDLE = "idle" # Linux, macOS, FreeBSD
110 STATUS_LOCKED = "locked" # FreeBSD
111 STATUS_WAITING = "waiting" # FreeBSD
112 STATUS_SUSPENDED = "suspended" # NetBSD
113 STATUS_PARKED = "parked" # Linux
114
115 # Process.connections() and psutil.net_connections()
116 CONN_ESTABLISHED = "ESTABLISHED"
117 CONN_SYN_SENT = "SYN_SENT"
118 CONN_SYN_RECV = "SYN_RECV"
119 CONN_FIN_WAIT1 = "FIN_WAIT1"
120 CONN_FIN_WAIT2 = "FIN_WAIT2"
121 CONN_TIME_WAIT = "TIME_WAIT"
122 CONN_CLOSE = "CLOSE"
123 CONN_CLOSE_WAIT = "CLOSE_WAIT"
124 CONN_LAST_ACK = "LAST_ACK"
125 CONN_LISTEN = "LISTEN"
126 CONN_CLOSING = "CLOSING"
127 CONN_NONE = "NONE"
128
129 # net_if_stats()
130 if enum is None:
131 NIC_DUPLEX_FULL = 2
132 NIC_DUPLEX_HALF = 1
133 NIC_DUPLEX_UNKNOWN = 0
134 else:
135 class NicDuplex(enum.IntEnum):
136 NIC_DUPLEX_FULL = 2
137 NIC_DUPLEX_HALF = 1
138 NIC_DUPLEX_UNKNOWN = 0
139
140 globals().update(NicDuplex.__members__)
141
142 # sensors_battery()
143 if enum is None:
144 POWER_TIME_UNKNOWN = -1
145 POWER_TIME_UNLIMITED = -2
146 else:
147 class BatteryTime(enum.IntEnum):
148 POWER_TIME_UNKNOWN = -1
149 POWER_TIME_UNLIMITED = -2
150
151 globals().update(BatteryTime.__members__)
152
153 # --- others
154
155 ENCODING = sys.getfilesystemencoding()
156 if not PY3:
157 ENCODING_ERRS = "replace"
158 else:
159 try:
160 ENCODING_ERRS = sys.getfilesystemencodeerrors() # py 3.6
161 except AttributeError:
162 ENCODING_ERRS = "surrogateescape" if POSIX else "replace"
163
164
165 # ===================================================================
166 # --- namedtuples
167 # ===================================================================
168
169 # --- for system functions
170
171 # psutil.swap_memory()
172 sswap = namedtuple('sswap', ['total', 'used', 'free', 'percent', 'sin',
173 'sout'])
174 # psutil.disk_usage()
175 sdiskusage = namedtuple('sdiskusage', ['total', 'used', 'free', 'percent'])
176 # psutil.disk_io_counters()
177 sdiskio = namedtuple('sdiskio', ['read_count', 'write_count',
178 'read_bytes', 'write_bytes',
179 'read_time', 'write_time'])
180 # psutil.disk_partitions()
181 sdiskpart = namedtuple('sdiskpart', ['device', 'mountpoint', 'fstype', 'opts',
182 'maxfile', 'maxpath'])
183 # psutil.net_io_counters()
184 snetio = namedtuple('snetio', ['bytes_sent', 'bytes_recv',
185 'packets_sent', 'packets_recv',
186 'errin', 'errout',
187 'dropin', 'dropout'])
188 # psutil.users()
189 suser = namedtuple('suser', ['name', 'terminal', 'host', 'started', 'pid'])
190 # psutil.net_connections()
191 sconn = namedtuple('sconn', ['fd', 'family', 'type', 'laddr', 'raddr',
192 'status', 'pid'])
193 # psutil.net_if_addrs()
194 snicaddr = namedtuple('snicaddr',
195 ['family', 'address', 'netmask', 'broadcast', 'ptp'])
196 # psutil.net_if_stats()
197 snicstats = namedtuple('snicstats', ['isup', 'duplex', 'speed', 'mtu'])
198 # psutil.cpu_stats()
199 scpustats = namedtuple(
200 'scpustats', ['ctx_switches', 'interrupts', 'soft_interrupts', 'syscalls'])
201 # psutil.cpu_freq()
202 scpufreq = namedtuple('scpufreq', ['current', 'min', 'max'])
203 # psutil.sensors_temperatures()
204 shwtemp = namedtuple(
205 'shwtemp', ['label', 'current', 'high', 'critical'])
206 # psutil.sensors_battery()
207 sbattery = namedtuple('sbattery', ['percent', 'secsleft', 'power_plugged'])
208 # psutil.sensors_fans()
209 sfan = namedtuple('sfan', ['label', 'current'])
210
211 # --- for Process methods
212
213 # psutil.Process.cpu_times()
214 pcputimes = namedtuple('pcputimes',
215 ['user', 'system', 'children_user', 'children_system'])
216 # psutil.Process.open_files()
217 popenfile = namedtuple('popenfile', ['path', 'fd'])
218 # psutil.Process.threads()
219 pthread = namedtuple('pthread', ['id', 'user_time', 'system_time'])
220 # psutil.Process.uids()
221 puids = namedtuple('puids', ['real', 'effective', 'saved'])
222 # psutil.Process.gids()
223 pgids = namedtuple('pgids', ['real', 'effective', 'saved'])
224 # psutil.Process.io_counters()
225 pio = namedtuple('pio', ['read_count', 'write_count',
226 'read_bytes', 'write_bytes'])
227 # psutil.Process.ionice()
228 pionice = namedtuple('pionice', ['ioclass', 'value'])
229 # psutil.Process.ctx_switches()
230 pctxsw = namedtuple('pctxsw', ['voluntary', 'involuntary'])
231 # psutil.Process.connections()
232 pconn = namedtuple('pconn', ['fd', 'family', 'type', 'laddr', 'raddr',
233 'status'])
234
235 # psutil.connections() and psutil.Process.connections()
236 addr = namedtuple('addr', ['ip', 'port'])
237
238
239 # ===================================================================
240 # --- Process.connections() 'kind' parameter mapping
241 # ===================================================================
242
243
244 conn_tmap = {
245 "all": ([AF_INET, AF_INET6, AF_UNIX], [SOCK_STREAM, SOCK_DGRAM]),
246 "tcp": ([AF_INET, AF_INET6], [SOCK_STREAM]),
247 "tcp4": ([AF_INET], [SOCK_STREAM]),
248 "udp": ([AF_INET, AF_INET6], [SOCK_DGRAM]),
249 "udp4": ([AF_INET], [SOCK_DGRAM]),
250 "inet": ([AF_INET, AF_INET6], [SOCK_STREAM, SOCK_DGRAM]),
251 "inet4": ([AF_INET], [SOCK_STREAM, SOCK_DGRAM]),
252 "inet6": ([AF_INET6], [SOCK_STREAM, SOCK_DGRAM]),
253 }
254
255 if AF_INET6 is not None:
256 conn_tmap.update({
257 "tcp6": ([AF_INET6], [SOCK_STREAM]),
258 "udp6": ([AF_INET6], [SOCK_DGRAM]),
259 })
260
261 if AF_UNIX is not None:
262 conn_tmap.update({
263 "unix": ([AF_UNIX], [SOCK_STREAM, SOCK_DGRAM]),
264 })
265
266
267 # =====================================================================
268 # --- Exceptions
269 # =====================================================================
270
271
272 class Error(Exception):
273 """Base exception class. All other psutil exceptions inherit
274 from this one.
275 """
276 __module__ = 'psutil'
277
278 def __init__(self, msg=""):
279 Exception.__init__(self, msg)
280 self.msg = msg
281
282 def __repr__(self):
283 ret = "psutil.%s %s" % (self.__class__.__name__, self.msg)
284 return ret.strip()
285
286 __str__ = __repr__
287
288
289 class NoSuchProcess(Error):
290 """Exception raised when a process with a certain PID doesn't
291 or no longer exists.
292 """
293 __module__ = 'psutil'
294
295 def __init__(self, pid, name=None, msg=None):
296 Error.__init__(self, msg)
297 self.pid = pid
298 self.name = name
299 self.msg = msg
300 if msg is None:
301 if name:
302 details = "(pid=%s, name=%s)" % (self.pid, repr(self.name))
303 else:
304 details = "(pid=%s)" % self.pid
305 self.msg = "process no longer exists " + details
306
307
308 class ZombieProcess(NoSuchProcess):
309 """Exception raised when querying a zombie process. This is
310 raised on macOS, BSD and Solaris only, and not always: depending
311 on the query the OS may be able to succeed anyway.
312 On Linux all zombie processes are querable (hence this is never
313 raised). Windows doesn't have zombie processes.
314 """
315 __module__ = 'psutil'
316
317 def __init__(self, pid, name=None, ppid=None, msg=None):
318 NoSuchProcess.__init__(self, msg)
319 self.pid = pid
320 self.ppid = ppid
321 self.name = name
322 self.msg = msg
323 if msg is None:
324 args = ["pid=%s" % pid]
325 if name:
326 args.append("name=%s" % repr(self.name))
327 if ppid:
328 args.append("ppid=%s" % self.ppid)
329 details = "(%s)" % ", ".join(args)
330 self.msg = "process still exists but it's a zombie " + details
331
332
333 class AccessDenied(Error):
334 """Exception raised when permission to perform an action is denied."""
335 __module__ = 'psutil'
336
337 def __init__(self, pid=None, name=None, msg=None):
338 Error.__init__(self, msg)
339 self.pid = pid
340 self.name = name
341 self.msg = msg
342 if msg is None:
343 if (pid is not None) and (name is not None):
344 self.msg = "(pid=%s, name=%s)" % (pid, repr(name))
345 elif (pid is not None):
346 self.msg = "(pid=%s)" % self.pid
347 else:
348 self.msg = ""
349
350
351 class TimeoutExpired(Error):
352 """Raised on Process.wait(timeout) if timeout expires and process
353 is still alive.
354 """
355 __module__ = 'psutil'
356
357 def __init__(self, seconds, pid=None, name=None):
358 Error.__init__(self, "timeout after %s seconds" % seconds)
359 self.seconds = seconds
360 self.pid = pid
361 self.name = name
362 if (pid is not None) and (name is not None):
363 self.msg += " (pid=%s, name=%s)" % (pid, repr(name))
364 elif (pid is not None):
365 self.msg += " (pid=%s)" % self.pid
366
367
368 # ===================================================================
369 # --- utils
370 # ===================================================================
371
372
373 def usage_percent(used, total, round_=None):
374 """Calculate percentage usage of 'used' against 'total'."""
375 try:
376 ret = (float(used) / total) * 100
377 except ZeroDivisionError:
378 return 0.0
379 else:
380 if round_ is not None:
381 ret = round(ret, round_)
382 return ret
383
384
385 def memoize(fun):
386 """A simple memoize decorator for functions supporting (hashable)
387 positional arguments.
388 It also provides a cache_clear() function for clearing the cache:
389
390 >>> @memoize
391 ... def foo()
392 ... return 1
393 ...
394 >>> foo()
395 1
396 >>> foo.cache_clear()
397 >>>
398 """
399 @functools.wraps(fun)
400 def wrapper(*args, **kwargs):
401 key = (args, frozenset(sorted(kwargs.items())))
402 try:
403 return cache[key]
404 except KeyError:
405 ret = cache[key] = fun(*args, **kwargs)
406 return ret
407
408 def cache_clear():
409 """Clear cache."""
410 cache.clear()
411
412 cache = {}
413 wrapper.cache_clear = cache_clear
414 return wrapper
415
416
417 def memoize_when_activated(fun):
418 """A memoize decorator which is disabled by default. It can be
419 activated and deactivated on request.
420 For efficiency reasons it can be used only against class methods
421 accepting no arguments.
422
423 >>> class Foo:
424 ... @memoize
425 ... def foo()
426 ... print(1)
427 ...
428 >>> f = Foo()
429 >>> # deactivated (default)
430 >>> foo()
431 1
432 >>> foo()
433 1
434 >>>
435 >>> # activated
436 >>> foo.cache_activate(self)
437 >>> foo()
438 1
439 >>> foo()
440 >>> foo()
441 >>>
442 """
443 @functools.wraps(fun)
444 def wrapper(self):
445 try:
446 # case 1: we previously entered oneshot() ctx
447 ret = self._cache[fun]
448 except AttributeError:
449 # case 2: we never entered oneshot() ctx
450 return fun(self)
451 except KeyError:
452 # case 3: we entered oneshot() ctx but there's no cache
453 # for this entry yet
454 ret = self._cache[fun] = fun(self)
455 return ret
456
457 def cache_activate(proc):
458 """Activate cache. Expects a Process instance. Cache will be
459 stored as a "_cache" instance attribute."""
460 proc._cache = {}
461
462 def cache_deactivate(proc):
463 """Deactivate and clear cache."""
464 try:
465 del proc._cache
466 except AttributeError:
467 pass
468
469 wrapper.cache_activate = cache_activate
470 wrapper.cache_deactivate = cache_deactivate
471 return wrapper
472
473
474 def isfile_strict(path):
475 """Same as os.path.isfile() but does not swallow EACCES / EPERM
476 exceptions, see:
477 http://mail.python.org/pipermail/python-dev/2012-June/120787.html
478 """
479 try:
480 st = os.stat(path)
481 except OSError as err:
482 if err.errno in (errno.EPERM, errno.EACCES):
483 raise
484 return False
485 else:
486 return stat.S_ISREG(st.st_mode)
487
488
489 def path_exists_strict(path):
490 """Same as os.path.exists() but does not swallow EACCES / EPERM
491 exceptions, see:
492 http://mail.python.org/pipermail/python-dev/2012-June/120787.html
493 """
494 try:
495 os.stat(path)
496 except OSError as err:
497 if err.errno in (errno.EPERM, errno.EACCES):
498 raise
499 return False
500 else:
501 return True
502
503
504 @memoize
505 def supports_ipv6():
506 """Return True if IPv6 is supported on this platform."""
507 if not socket.has_ipv6 or AF_INET6 is None:
508 return False
509 try:
510 sock = socket.socket(AF_INET6, socket.SOCK_STREAM)
511 with contextlib.closing(sock):
512 sock.bind(("::1", 0))
513 return True
514 except socket.error:
515 return False
516
517
518 def parse_environ_block(data):
519 """Parse a C environ block of environment variables into a dictionary."""
520 # The block is usually raw data from the target process. It might contain
521 # trailing garbage and lines that do not look like assignments.
522 ret = {}
523 pos = 0
524
525 # localize global variable to speed up access.
526 WINDOWS_ = WINDOWS
527 while True:
528 next_pos = data.find("\0", pos)
529 # nul byte at the beginning or double nul byte means finish
530 if next_pos <= pos:
531 break
532 # there might not be an equals sign
533 equal_pos = data.find("=", pos, next_pos)
534 if equal_pos > pos:
535 key = data[pos:equal_pos]
536 value = data[equal_pos + 1:next_pos]
537 # Windows expects environment variables to be uppercase only
538 if WINDOWS_:
539 key = key.upper()
540 ret[key] = value
541 pos = next_pos + 1
542
543 return ret
544
545
546 def sockfam_to_enum(num):
547 """Convert a numeric socket family value to an IntEnum member.
548 If it's not a known member, return the numeric value itself.
549 """
550 if enum is None:
551 return num
552 else: # pragma: no cover
553 try:
554 return socket.AddressFamily(num)
555 except ValueError:
556 return num
557
558
559 def socktype_to_enum(num):
560 """Convert a numeric socket type value to an IntEnum member.
561 If it's not a known member, return the numeric value itself.
562 """
563 if enum is None:
564 return num
565 else: # pragma: no cover
566 try:
567 return socket.SocketKind(num)
568 except ValueError:
569 return num
570
571
572 def conn_to_ntuple(fd, fam, type_, laddr, raddr, status, status_map, pid=None):
573 """Convert a raw connection tuple to a proper ntuple."""
574 if fam in (socket.AF_INET, AF_INET6):
575 if laddr:
576 laddr = addr(*laddr)
577 if raddr:
578 raddr = addr(*raddr)
579 if type_ == socket.SOCK_STREAM and fam in (AF_INET, AF_INET6):
580 status = status_map.get(status, CONN_NONE)
581 else:
582 status = CONN_NONE # ignore whatever C returned to us
583 fam = sockfam_to_enum(fam)
584 type_ = socktype_to_enum(type_)
585 if pid is None:
586 return pconn(fd, fam, type_, laddr, raddr, status)
587 else:
588 return sconn(fd, fam, type_, laddr, raddr, status, pid)
589
590
591 def deprecated_method(replacement):
592 """A decorator which can be used to mark a method as deprecated
593 'replcement' is the method name which will be called instead.
594 """
595 def outer(fun):
596 msg = "%s() is deprecated and will be removed; use %s() instead" % (
597 fun.__name__, replacement)
598 if fun.__doc__ is None:
599 fun.__doc__ = msg
600
601 @functools.wraps(fun)
602 def inner(self, *args, **kwargs):
603 warnings.warn(msg, category=DeprecationWarning, stacklevel=2)
604 return getattr(self, replacement)(*args, **kwargs)
605 return inner
606 return outer
607
608
609 class _WrapNumbers:
610 """Watches numbers so that they don't overflow and wrap
611 (reset to zero).
612 """
613
614 def __init__(self):
615 self.lock = threading.Lock()
616 self.cache = {}
617 self.reminders = {}
618 self.reminder_keys = {}
619
620 def _add_dict(self, input_dict, name):
621 assert name not in self.cache
622 assert name not in self.reminders
623 assert name not in self.reminder_keys
624 self.cache[name] = input_dict
625 self.reminders[name] = defaultdict(int)
626 self.reminder_keys[name] = defaultdict(set)
627
628 def _remove_dead_reminders(self, input_dict, name):
629 """In case the number of keys changed between calls (e.g. a
630 disk disappears) this removes the entry from self.reminders.
631 """
632 old_dict = self.cache[name]
633 gone_keys = set(old_dict.keys()) - set(input_dict.keys())
634 for gone_key in gone_keys:
635 for remkey in self.reminder_keys[name][gone_key]:
636 del self.reminders[name][remkey]
637 del self.reminder_keys[name][gone_key]
638
639 def run(self, input_dict, name):
640 """Cache dict and sum numbers which overflow and wrap.
641 Return an updated copy of `input_dict`
642 """
643 if name not in self.cache:
644 # This was the first call.
645 self._add_dict(input_dict, name)
646 return input_dict
647
648 self._remove_dead_reminders(input_dict, name)
649
650 old_dict = self.cache[name]
651 new_dict = {}
652 for key in input_dict.keys():
653 input_tuple = input_dict[key]
654 try:
655 old_tuple = old_dict[key]
656 except KeyError:
657 # The input dict has a new key (e.g. a new disk or NIC)
658 # which didn't exist in the previous call.
659 new_dict[key] = input_tuple
660 continue
661
662 bits = []
663 for i in range(len(input_tuple)):
664 input_value = input_tuple[i]
665 old_value = old_tuple[i]
666 remkey = (key, i)
667 if input_value < old_value:
668 # it wrapped!
669 self.reminders[name][remkey] += old_value
670 self.reminder_keys[name][key].add(remkey)
671 bits.append(input_value + self.reminders[name][remkey])
672
673 new_dict[key] = tuple(bits)
674
675 self.cache[name] = input_dict
676 return new_dict
677
678 def cache_clear(self, name=None):
679 """Clear the internal cache, optionally only for function 'name'."""
680 with self.lock:
681 if name is None:
682 self.cache.clear()
683 self.reminders.clear()
684 self.reminder_keys.clear()
685 else:
686 self.cache.pop(name, None)
687 self.reminders.pop(name, None)
688 self.reminder_keys.pop(name, None)
689
690 def cache_info(self):
691 """Return internal cache dicts as a tuple of 3 elements."""
692 with self.lock:
693 return (self.cache, self.reminders, self.reminder_keys)
694
695
696 def wrap_numbers(input_dict, name):
697 """Given an `input_dict` and a function `name`, adjust the numbers
698 which "wrap" (restart from zero) across different calls by adding
699 "old value" to "new value" and return an updated dict.
700 """
701 with _wn.lock:
702 return _wn.run(input_dict, name)
703
704
705 _wn = _WrapNumbers()
706 wrap_numbers.cache_clear = _wn.cache_clear
707 wrap_numbers.cache_info = _wn.cache_info
708
709
710 def open_binary(fname, **kwargs):
711 return open(fname, "rb", **kwargs)
712
713
714 def open_text(fname, **kwargs):
715 """On Python 3 opens a file in text mode by using fs encoding and
716 a proper en/decoding errors handler.
717 On Python 2 this is just an alias for open(name, 'rt').
718 """
719 if PY3:
720 # See:
721 # https://github.com/giampaolo/psutil/issues/675
722 # https://github.com/giampaolo/psutil/pull/733
723 kwargs.setdefault('encoding', ENCODING)
724 kwargs.setdefault('errors', ENCODING_ERRS)
725 return open(fname, "rt", **kwargs)
726
727
728 def bytes2human(n, format="%(value).1f%(symbol)s"):
729 """Used by various scripts. See:
730 http://goo.gl/zeJZl
731
732 >>> bytes2human(10000)
733 '9.8K'
734 >>> bytes2human(100001221)
735 '95.4M'
736 """
737 symbols = ('B', 'K', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y')
738 prefix = {}
739 for i, s in enumerate(symbols[1:]):
740 prefix[s] = 1 << (i + 1) * 10
741 for symbol in reversed(symbols[1:]):
742 if n >= prefix[symbol]:
743 value = float(n) / prefix[symbol]
744 return format % locals()
745 return format % dict(symbol=symbols[0], value=n)
746
747
748 def get_procfs_path():
749 """Return updated psutil.PROCFS_PATH constant."""
750 return sys.modules['psutil'].PROCFS_PATH
751
752
753 if PY3:
754 def decode(s):
755 return s.decode(encoding=ENCODING, errors=ENCODING_ERRS)
756 else:
757 def decode(s):
758 return s
759
760
761 # =====================================================================
762 # --- shell utils
763 # =====================================================================
764
765
766 @memoize
767 def term_supports_colors(file=sys.stdout): # pragma: no cover
768 if os.name == 'nt':
769 return True
770 try:
771 import curses
772 assert file.isatty()
773 curses.setupterm()
774 assert curses.tigetnum("colors") > 0
775 except Exception:
776 return False
777 else:
778 return True
779
780
781 def hilite(s, color=None, bold=False): # pragma: no cover
782 """Return an highlighted version of 'string'."""
783 if not term_supports_colors():
784 return s
785 attr = []
786 colors = dict(green='32', red='91', brown='33', yellow='93', blue='34',
787 violet='35', lightblue='36', grey='37', darkgrey='30')
788 colors[None] = '29'
789 try:
790 color = colors[color]
791 except KeyError:
792 raise ValueError("invalid color %r; choose between %s" % (
793 list(colors.keys())))
794 attr.append(color)
795 if bold:
796 attr.append('1')
797 return '\x1b[%sm%s\x1b[0m' % (';'.join(attr), s)
798
799
800 def print_color(
801 s, color=None, bold=False, file=sys.stdout): # pragma: no cover
802 """Print a colorized version of string."""
803 if not term_supports_colors():
804 print(s, file=file) # NOQA
805 elif POSIX:
806 print(hilite(s, color, bold), file=file) # NOQA
807 else:
808 import ctypes
809
810 DEFAULT_COLOR = 7
811 GetStdHandle = ctypes.windll.Kernel32.GetStdHandle
812 SetConsoleTextAttribute = \
813 ctypes.windll.Kernel32.SetConsoleTextAttribute
814
815 colors = dict(green=2, red=4, brown=6, yellow=6)
816 colors[None] = DEFAULT_COLOR
817 try:
818 color = colors[color]
819 except KeyError:
820 raise ValueError("invalid color %r; choose between %r" % (
821 color, list(colors.keys())))
822 if bold and color <= 7:
823 color += 8
824
825 handle_id = -12 if file is sys.stderr else -11
826 GetStdHandle.restype = ctypes.c_ulong
827 handle = GetStdHandle(handle_id)
828 SetConsoleTextAttribute(handle, color)
829 try:
830 print(s, file=file) # NOQA
831 finally:
832 SetConsoleTextAttribute(handle, DEFAULT_COLOR)
833
834
835 if bool(os.getenv('PSUTIL_DEBUG', 0)):
836 import inspect
837
838 def debug(msg):
839 """If PSUTIL_DEBUG env var is set, print a debug message to stderr."""
840 fname, lineno, func_name, lines, index = inspect.getframeinfo(
841 inspect.currentframe().f_back)
842 print("psutil-debug [%s:%s]> %s" % (fname, lineno, msg), # NOQA
843 file=sys.stderr)
844 else:
845 def debug(msg):
846 pass