comparison env/lib/python3.7/site-packages/psutil/_common.py @ 0:26e78fe6e8c4 draft

"planemo upload commit c699937486c35866861690329de38ec1a5d9f783"
author shellac
date Sat, 02 May 2020 07:14:21 -0400
parents
children
comparison
equal deleted inserted replaced
-1:000000000000 0:26e78fe6e8c4
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 # psutil.net_io_counters()
183 snetio = namedtuple('snetio', ['bytes_sent', 'bytes_recv',
184 'packets_sent', 'packets_recv',
185 'errin', 'errout',
186 'dropin', 'dropout'])
187 # psutil.users()
188 suser = namedtuple('suser', ['name', 'terminal', 'host', 'started', 'pid'])
189 # psutil.net_connections()
190 sconn = namedtuple('sconn', ['fd', 'family', 'type', 'laddr', 'raddr',
191 'status', 'pid'])
192 # psutil.net_if_addrs()
193 snicaddr = namedtuple('snicaddr',
194 ['family', 'address', 'netmask', 'broadcast', 'ptp'])
195 # psutil.net_if_stats()
196 snicstats = namedtuple('snicstats', ['isup', 'duplex', 'speed', 'mtu'])
197 # psutil.cpu_stats()
198 scpustats = namedtuple(
199 'scpustats', ['ctx_switches', 'interrupts', 'soft_interrupts', 'syscalls'])
200 # psutil.cpu_freq()
201 scpufreq = namedtuple('scpufreq', ['current', 'min', 'max'])
202 # psutil.sensors_temperatures()
203 shwtemp = namedtuple(
204 'shwtemp', ['label', 'current', 'high', 'critical'])
205 # psutil.sensors_battery()
206 sbattery = namedtuple('sbattery', ['percent', 'secsleft', 'power_plugged'])
207 # psutil.sensors_fans()
208 sfan = namedtuple('sfan', ['label', 'current'])
209
210 # --- for Process methods
211
212 # psutil.Process.cpu_times()
213 pcputimes = namedtuple('pcputimes',
214 ['user', 'system', 'children_user', 'children_system'])
215 # psutil.Process.open_files()
216 popenfile = namedtuple('popenfile', ['path', 'fd'])
217 # psutil.Process.threads()
218 pthread = namedtuple('pthread', ['id', 'user_time', 'system_time'])
219 # psutil.Process.uids()
220 puids = namedtuple('puids', ['real', 'effective', 'saved'])
221 # psutil.Process.gids()
222 pgids = namedtuple('pgids', ['real', 'effective', 'saved'])
223 # psutil.Process.io_counters()
224 pio = namedtuple('pio', ['read_count', 'write_count',
225 'read_bytes', 'write_bytes'])
226 # psutil.Process.ionice()
227 pionice = namedtuple('pionice', ['ioclass', 'value'])
228 # psutil.Process.ctx_switches()
229 pctxsw = namedtuple('pctxsw', ['voluntary', 'involuntary'])
230 # psutil.Process.connections()
231 pconn = namedtuple('pconn', ['fd', 'family', 'type', 'laddr', 'raddr',
232 'status'])
233
234 # psutil.connections() and psutil.Process.connections()
235 addr = namedtuple('addr', ['ip', 'port'])
236
237
238 # ===================================================================
239 # --- Process.connections() 'kind' parameter mapping
240 # ===================================================================
241
242
243 conn_tmap = {
244 "all": ([AF_INET, AF_INET6, AF_UNIX], [SOCK_STREAM, SOCK_DGRAM]),
245 "tcp": ([AF_INET, AF_INET6], [SOCK_STREAM]),
246 "tcp4": ([AF_INET], [SOCK_STREAM]),
247 "udp": ([AF_INET, AF_INET6], [SOCK_DGRAM]),
248 "udp4": ([AF_INET], [SOCK_DGRAM]),
249 "inet": ([AF_INET, AF_INET6], [SOCK_STREAM, SOCK_DGRAM]),
250 "inet4": ([AF_INET], [SOCK_STREAM, SOCK_DGRAM]),
251 "inet6": ([AF_INET6], [SOCK_STREAM, SOCK_DGRAM]),
252 }
253
254 if AF_INET6 is not None:
255 conn_tmap.update({
256 "tcp6": ([AF_INET6], [SOCK_STREAM]),
257 "udp6": ([AF_INET6], [SOCK_DGRAM]),
258 })
259
260 if AF_UNIX is not None:
261 conn_tmap.update({
262 "unix": ([AF_UNIX], [SOCK_STREAM, SOCK_DGRAM]),
263 })
264
265
266 # =====================================================================
267 # --- Exceptions
268 # =====================================================================
269
270
271 class Error(Exception):
272 """Base exception class. All other psutil exceptions inherit
273 from this one.
274 """
275 __module__ = 'psutil'
276
277 def __init__(self, msg=""):
278 Exception.__init__(self, msg)
279 self.msg = msg
280
281 def __repr__(self):
282 ret = "psutil.%s %s" % (self.__class__.__name__, self.msg)
283 return ret.strip()
284
285 __str__ = __repr__
286
287
288 class NoSuchProcess(Error):
289 """Exception raised when a process with a certain PID doesn't
290 or no longer exists.
291 """
292 __module__ = 'psutil'
293
294 def __init__(self, pid, name=None, msg=None):
295 Error.__init__(self, msg)
296 self.pid = pid
297 self.name = name
298 self.msg = msg
299 if msg is None:
300 if name:
301 details = "(pid=%s, name=%s)" % (self.pid, repr(self.name))
302 else:
303 details = "(pid=%s)" % self.pid
304 self.msg = "process no longer exists " + details
305
306 def __path__(self):
307 return 'xxx'
308
309
310 class ZombieProcess(NoSuchProcess):
311 """Exception raised when querying a zombie process. This is
312 raised on macOS, BSD and Solaris only, and not always: depending
313 on the query the OS may be able to succeed anyway.
314 On Linux all zombie processes are querable (hence this is never
315 raised). Windows doesn't have zombie processes.
316 """
317 __module__ = 'psutil'
318
319 def __init__(self, pid, name=None, ppid=None, msg=None):
320 NoSuchProcess.__init__(self, msg)
321 self.pid = pid
322 self.ppid = ppid
323 self.name = name
324 self.msg = msg
325 if msg is None:
326 args = ["pid=%s" % pid]
327 if name:
328 args.append("name=%s" % repr(self.name))
329 if ppid:
330 args.append("ppid=%s" % self.ppid)
331 details = "(%s)" % ", ".join(args)
332 self.msg = "process still exists but it's a zombie " + details
333
334
335 class AccessDenied(Error):
336 """Exception raised when permission to perform an action is denied."""
337 __module__ = 'psutil'
338
339 def __init__(self, pid=None, name=None, msg=None):
340 Error.__init__(self, msg)
341 self.pid = pid
342 self.name = name
343 self.msg = msg
344 if msg is None:
345 if (pid is not None) and (name is not None):
346 self.msg = "(pid=%s, name=%s)" % (pid, repr(name))
347 elif (pid is not None):
348 self.msg = "(pid=%s)" % self.pid
349 else:
350 self.msg = ""
351
352
353 class TimeoutExpired(Error):
354 """Raised on Process.wait(timeout) if timeout expires and process
355 is still alive.
356 """
357 __module__ = 'psutil'
358
359 def __init__(self, seconds, pid=None, name=None):
360 Error.__init__(self, "timeout after %s seconds" % seconds)
361 self.seconds = seconds
362 self.pid = pid
363 self.name = name
364 if (pid is not None) and (name is not None):
365 self.msg += " (pid=%s, name=%s)" % (pid, repr(name))
366 elif (pid is not None):
367 self.msg += " (pid=%s)" % self.pid
368
369
370 # ===================================================================
371 # --- utils
372 # ===================================================================
373
374
375 def usage_percent(used, total, round_=None):
376 """Calculate percentage usage of 'used' against 'total'."""
377 try:
378 ret = (float(used) / total) * 100
379 except ZeroDivisionError:
380 return 0.0
381 else:
382 if round_ is not None:
383 ret = round(ret, round_)
384 return ret
385
386
387 def memoize(fun):
388 """A simple memoize decorator for functions supporting (hashable)
389 positional arguments.
390 It also provides a cache_clear() function for clearing the cache:
391
392 >>> @memoize
393 ... def foo()
394 ... return 1
395 ...
396 >>> foo()
397 1
398 >>> foo.cache_clear()
399 >>>
400 """
401 @functools.wraps(fun)
402 def wrapper(*args, **kwargs):
403 key = (args, frozenset(sorted(kwargs.items())))
404 try:
405 return cache[key]
406 except KeyError:
407 ret = cache[key] = fun(*args, **kwargs)
408 return ret
409
410 def cache_clear():
411 """Clear cache."""
412 cache.clear()
413
414 cache = {}
415 wrapper.cache_clear = cache_clear
416 return wrapper
417
418
419 def memoize_when_activated(fun):
420 """A memoize decorator which is disabled by default. It can be
421 activated and deactivated on request.
422 For efficiency reasons it can be used only against class methods
423 accepting no arguments.
424
425 >>> class Foo:
426 ... @memoize
427 ... def foo()
428 ... print(1)
429 ...
430 >>> f = Foo()
431 >>> # deactivated (default)
432 >>> foo()
433 1
434 >>> foo()
435 1
436 >>>
437 >>> # activated
438 >>> foo.cache_activate(self)
439 >>> foo()
440 1
441 >>> foo()
442 >>> foo()
443 >>>
444 """
445 @functools.wraps(fun)
446 def wrapper(self):
447 try:
448 # case 1: we previously entered oneshot() ctx
449 ret = self._cache[fun]
450 except AttributeError:
451 # case 2: we never entered oneshot() ctx
452 return fun(self)
453 except KeyError:
454 # case 3: we entered oneshot() ctx but there's no cache
455 # for this entry yet
456 ret = self._cache[fun] = fun(self)
457 return ret
458
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 = {}
463
464 def cache_deactivate(proc):
465 """Deactivate and clear cache."""
466 try:
467 del proc._cache
468 except AttributeError:
469 pass
470
471 wrapper.cache_activate = cache_activate
472 wrapper.cache_deactivate = cache_deactivate
473 return wrapper
474
475
476 def 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)
489
490
491 def 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
504
505
506 @memoize
507 def 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
518
519
520 def 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
526
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
544
545 return ret
546
547
548 def 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
559
560
561 def 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
572
573
574 def 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)
591
592
593 def 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
602
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
609
610
611 class _WrapNumbers:
612 """Watches numbers so that they don't overflow and wrap
613 (reset to zero).
614 """
615
616 def __init__(self):
617 self.lock = threading.Lock()
618 self.cache = {}
619 self.reminders = {}
620 self.reminder_keys = {}
621
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] = defaultdict(int)
628 self.reminder_keys[name] = defaultdict(set)
629
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]
640
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
649
650 self._remove_dead_reminders(input_dict, name)
651
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
663
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])
674
675 new_dict[key] = tuple(bits)
676
677 self.cache[name] = input_dict
678 return new_dict
679
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)
691
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)
696
697
698 def 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)
705
706
707 _wn = _WrapNumbers()
708 wrap_numbers.cache_clear = _wn.cache_clear
709 wrap_numbers.cache_info = _wn.cache_info
710
711
712 def open_binary(fname, **kwargs):
713 return open(fname, "rb", **kwargs)
714
715
716 def open_text(fname, **kwargs):
717 """On Python 3 opens a file in text mode by using fs encoding and
718 a proper en/decoding errors handler.
719 On Python 2 this is just an alias for open(name, 'rt').
720 """
721 if PY3:
722 # See:
723 # https://github.com/giampaolo/psutil/issues/675
724 # https://github.com/giampaolo/psutil/pull/733
725 kwargs.setdefault('encoding', ENCODING)
726 kwargs.setdefault('errors', ENCODING_ERRS)
727 return open(fname, "rt", **kwargs)
728
729
730 def bytes2human(n, format="%(value).1f%(symbol)s"):
731 """Used by various scripts. See:
732 http://goo.gl/zeJZl
733
734 >>> bytes2human(10000)
735 '9.8K'
736 >>> bytes2human(100001221)
737 '95.4M'
738 """
739 symbols = ('B', 'K', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y')
740 prefix = {}
741 for i, s in enumerate(symbols[1:]):
742 prefix[s] = 1 << (i + 1) * 10
743 for symbol in reversed(symbols[1:]):
744 if n >= prefix[symbol]:
745 value = float(n) / prefix[symbol]
746 return format % locals()
747 return format % dict(symbol=symbols[0], value=n)
748
749
750 def get_procfs_path():
751 """Return updated psutil.PROCFS_PATH constant."""
752 return sys.modules['psutil'].PROCFS_PATH
753
754
755 if PY3:
756 def decode(s):
757 return s.decode(encoding=ENCODING, errors=ENCODING_ERRS)
758 else:
759 def decode(s):
760 return s
761
762
763 # =====================================================================
764 # --- shell utils
765 # =====================================================================
766
767
768 @memoize
769 def term_supports_colors(file=sys.stdout):
770 if os.name == 'nt':
771 return True
772 try:
773 import curses
774 assert file.isatty()
775 curses.setupterm()
776 assert curses.tigetnum("colors") > 0
777 except Exception:
778 return False
779 else:
780 return True
781
782
783 def hilite(s, color="green", bold=False):
784 """Return an highlighted version of 'string'."""
785 if not term_supports_colors():
786 return s
787 attr = []
788 colors = dict(green='32', red='91', brown='33')
789 colors[None] = '29'
790 try:
791 color = colors[color]
792 except KeyError:
793 raise ValueError("invalid color %r; choose between %s" % (
794 list(colors.keys())))
795 attr.append(color)
796 if bold:
797 attr.append('1')
798 return '\x1b[%sm%s\x1b[0m' % (';'.join(attr), s)
799
800
801 def print_color(s, color="green", bold=False, file=sys.stdout):
802 """Print a colorized version of string."""
803 if not term_supports_colors():
804 print(s, file=file)
805 elif POSIX:
806 print(hilite(s, color, bold), file=file)
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)
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)
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),
843 file=sys.stderr)
844 else:
845 def debug(msg):
846 pass