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"""psutil is a cross-platform library for retrieving information on
6running processes and system utilization (CPU, memory, disks, network,
7sensors) in Python. Supported platforms:
8
9 - Linux
10 - Windows
11 - macOS
12 - FreeBSD
13 - OpenBSD
14 - NetBSD
15 - Sun Solaris
16 - AIX
17
18Supported Python versions are cPython 3.8+ and PyPy.
19"""
20
21from __future__ import annotations
22
23import collections
24import contextlib
25import datetime
26import functools
27import os
28import signal
29import socket
30import subprocess
31import sys
32import threading
33import time
34import warnings
35from typing import TYPE_CHECKING as _TYPE_CHECKING
36
37try:
38 import pwd
39except ImportError:
40 pwd = None
41
42from . import _common
43from . import _ntuples as _ntp
44from ._common import AIX
45from ._common import BSD
46from ._common import FREEBSD
47from ._common import LINUX
48from ._common import MACOS
49from ._common import NETBSD
50from ._common import OPENBSD
51from ._common import OSX # deprecated alias
52from ._common import POSIX
53from ._common import SUNOS
54from ._common import WINDOWS
55from ._common import AccessDenied
56from ._common import Error
57from ._common import NoSuchProcess
58from ._common import TimeoutExpired
59from ._common import ZombieProcess
60from ._common import bytes2human
61from ._common import debug
62from ._common import memoize_when_activated
63from ._common import warn
64from ._common import wrap_numbers as _wrap_numbers
65from ._enums import BatteryTime
66from ._enums import ConnectionStatus
67from ._enums import NicDuplex
68from ._enums import ProcessStatus
69
70if _TYPE_CHECKING:
71 from collections.abc import Collection
72 from typing import Any
73 from typing import Callable
74 from typing import Generator
75 from typing import Iterator
76
77 from ._ntuples import pconn
78 from ._ntuples import pcputimes
79 from ._ntuples import pctxsw
80 from ._ntuples import pfootprint
81 from ._ntuples import pfullmem
82 from ._ntuples import pgids
83 from ._ntuples import pheap
84 from ._ntuples import pio
85 from ._ntuples import pionice
86 from ._ntuples import pmem
87 from ._ntuples import pmem_extras
88 from ._ntuples import pmmap_ext
89 from ._ntuples import pmmap_grouped
90 from ._ntuples import popenfile
91 from ._ntuples import ppagefaults
92 from ._ntuples import pthread
93 from ._ntuples import puids
94 from ._ntuples import sbattery
95 from ._ntuples import sconn
96 from ._ntuples import scpufreq
97 from ._ntuples import scpustats
98 from ._ntuples import scputimes
99 from ._ntuples import sdiskio
100 from ._ntuples import sdiskpart
101 from ._ntuples import sdiskusage
102 from ._ntuples import sfan
103 from ._ntuples import shwtemp
104 from ._ntuples import snetio
105 from ._ntuples import snicaddr
106 from ._ntuples import snicstats
107 from ._ntuples import sswap
108 from ._ntuples import suser
109 from ._ntuples import svmem
110 from ._pswindows import WindowsService
111
112 # _export_enum() puts these in the module namespace at run time.
113 STATUS_DEAD: ProcessStatus
114 STATUS_DISK_SLEEP: ProcessStatus
115 STATUS_IDLE: ProcessStatus
116 STATUS_LOCKED: ProcessStatus
117 STATUS_PARKED: ProcessStatus
118 STATUS_RUNNING: ProcessStatus
119 STATUS_SLEEPING: ProcessStatus
120 STATUS_STOPPED: ProcessStatus
121 STATUS_SUSPENDED: ProcessStatus
122 STATUS_TRACING_STOP: ProcessStatus
123 STATUS_WAITING: ProcessStatus
124 STATUS_WAKE_KILL: ProcessStatus
125 STATUS_WAKING: ProcessStatus
126 STATUS_ZOMBIE: ProcessStatus
127
128 CONN_CLOSE: ConnectionStatus
129 CONN_CLOSE_WAIT: ConnectionStatus
130 CONN_CLOSING: ConnectionStatus
131 CONN_ESTABLISHED: ConnectionStatus
132 CONN_FIN_WAIT1: ConnectionStatus
133 CONN_FIN_WAIT2: ConnectionStatus
134 CONN_LAST_ACK: ConnectionStatus
135 CONN_LISTEN: ConnectionStatus
136 CONN_NONE: ConnectionStatus
137 CONN_SYN_RECV: ConnectionStatus
138 CONN_SYN_SENT: ConnectionStatus
139 CONN_TIME_WAIT: ConnectionStatus
140 CONN_BOUND: ConnectionStatus # SunOS
141 CONN_DELETE_TCB: ConnectionStatus # Windows
142 CONN_IDLE: ConnectionStatus # SunOS
143
144 NIC_DUPLEX_FULL: NicDuplex
145 NIC_DUPLEX_HALF: NicDuplex
146 NIC_DUPLEX_UNKNOWN: NicDuplex
147
148 POWER_TIME_UNKNOWN: BatteryTime
149 POWER_TIME_UNLIMITED: BatteryTime
150
151 IOPRIO_CLASS_BE: ProcessIOPriority # Linux
152 IOPRIO_CLASS_IDLE: ProcessIOPriority # Linux
153 IOPRIO_CLASS_NONE: ProcessIOPriority # Linux
154 IOPRIO_CLASS_RT: ProcessIOPriority # Linux
155 IOPRIO_HIGH: ProcessIOPriority # Windows
156 IOPRIO_LOW: ProcessIOPriority # Windows
157 IOPRIO_NORMAL: ProcessIOPriority # Windows
158 IOPRIO_VERYLOW: ProcessIOPriority # Windows
159
160 ABOVE_NORMAL_PRIORITY_CLASS: ProcessPriority # Windows
161 BELOW_NORMAL_PRIORITY_CLASS: ProcessPriority # Windows
162 HIGH_PRIORITY_CLASS: ProcessPriority # Windows
163 IDLE_PRIORITY_CLASS: ProcessPriority # Windows
164 NORMAL_PRIORITY_CLASS: ProcessPriority # Windows
165 REALTIME_PRIORITY_CLASS: ProcessPriority # Windows
166
167 RLIMIT_AS: ProcessRlimit # Linux, FreeBSD
168 RLIMIT_CORE: ProcessRlimit # Linux, FreeBSD
169 RLIMIT_CPU: ProcessRlimit # Linux, FreeBSD
170 RLIMIT_DATA: ProcessRlimit # Linux, FreeBSD
171 RLIMIT_FSIZE: ProcessRlimit # Linux, FreeBSD
172 RLIMIT_LOCKS: ProcessRlimit # Linux
173 RLIMIT_MEMLOCK: ProcessRlimit # Linux, FreeBSD
174 RLIMIT_MSGQUEUE: ProcessRlimit # Linux
175 RLIMIT_NICE: ProcessRlimit # Linux
176 RLIMIT_NOFILE: ProcessRlimit # Linux, FreeBSD
177 RLIMIT_NPROC: ProcessRlimit # Linux, FreeBSD
178 RLIMIT_NPTS: ProcessRlimit # FreeBSD
179 RLIMIT_RSS: ProcessRlimit # Linux, FreeBSD
180 RLIMIT_RTPRIO: ProcessRlimit # Linux
181 RLIMIT_RTTIME: ProcessRlimit # Linux
182 RLIMIT_SBSIZE: ProcessRlimit # FreeBSD
183 RLIMIT_SIGPENDING: ProcessRlimit # Linux
184 RLIMIT_STACK: ProcessRlimit # Linux, FreeBSD
185 RLIMIT_SWAP: ProcessRlimit # FreeBSD
186 RLIM_INFINITY: ProcessRlimit # Linux, FreeBSD
187
188
189if LINUX:
190 # This is public API and it will be retrieved from _pslinux.py
191 # via sys.modules.
192 PROCFS_PATH = "/proc"
193
194 from . import _pslinux as _psplatform
195 from ._enums import ProcessIOPriority
196 from ._enums import ProcessRlimit
197
198elif WINDOWS:
199 from . import _pswindows as _psplatform
200 from ._enums import ProcessIOPriority
201 from ._enums import ProcessPriority
202
203elif MACOS:
204 from . import _psosx as _psplatform
205
206elif BSD:
207 from . import _psbsd as _psplatform
208
209 if FREEBSD:
210 from ._enums import ProcessRlimit
211
212elif SUNOS:
213 from . import _pssunos as _psplatform
214
215 # This is public writable API which is read from _pslinux.py and
216 # _pssunos.py via sys.modules.
217 PROCFS_PATH = "/proc"
218
219elif AIX:
220 from . import _psaix as _psplatform
221
222 # This is public API and it will be retrieved from _pslinux.py
223 # via sys.modules.
224 PROCFS_PATH = "/proc"
225
226else: # pragma: no cover
227 msg = f"platform {sys.platform} is not supported"
228 raise NotImplementedError(msg)
229
230from . import _psutil
231
232# fmt: off
233__all__ = [
234 # exceptions
235 "Error", "NoSuchProcess", "ZombieProcess", "AccessDenied",
236 "TimeoutExpired",
237
238 # constants
239 "version_info", "__version__",
240
241 "AF_LINK",
242
243 "BSD", "FREEBSD", "LINUX", "NETBSD", "OPENBSD", "MACOS", "OSX", "POSIX",
244 "SUNOS", "WINDOWS", "AIX",
245
246 # classes
247 "Process", "Popen",
248
249 # functions
250 "pid_exists", "pids", "process_iter", "wait_procs", # proc
251 "virtual_memory", "swap_memory", # memory
252 "cpu_times", "cpu_percent", "cpu_times_percent", "cpu_count", # cpu
253 "cpu_stats", "getloadavg", # "cpu_freq",
254 "net_io_counters", "net_connections", "net_if_addrs", # network
255 "net_if_stats",
256 "disk_io_counters", "disk_partitions", "disk_usage", # disk
257 # "sensors_temperatures", "sensors_battery", "sensors_fans" # sensors
258 "users", "boot_time", # others
259 "bytes2human",
260]
261# fmt: on
262
263__all__.extend(_psplatform.__extra__all__)
264_globals = globals()
265
266
267def _export_enum(cls):
268 __all__.append(cls.__name__)
269 for name, member in cls.__members__.items():
270 _globals[name] = member # noqa: F821
271 __all__.append(name)
272
273
274# Populate global namespace with enums and CONSTANTs.
275_export_enum(ProcessStatus)
276_export_enum(ConnectionStatus)
277_export_enum(NicDuplex)
278_export_enum(BatteryTime)
279if LINUX or WINDOWS:
280 _export_enum(ProcessIOPriority)
281if WINDOWS:
282 _export_enum(ProcessPriority)
283if LINUX or FREEBSD:
284 _export_enum(ProcessRlimit)
285if LINUX or SUNOS or AIX:
286 __all__.append("PROCFS_PATH")
287
288del _globals, _export_enum
289
290AF_LINK = _psplatform.AF_LINK
291
292__author__ = "Giampaolo Rodola'"
293__version__ = "8.0.0"
294version_info = tuple(int(num) for num in __version__.split('.'))
295
296_timer = getattr(time, 'monotonic', time.time)
297_TOTAL_PHYMEM = None
298_LOWEST_PID = None
299_SENTINEL = object()
300
301# Sanity check in case the user messed up with psutil installation
302# or did something weird with sys.path. In this case we might end
303# up importing a python module using a C extension module which
304# was compiled for a different version of psutil.
305# We want to prevent that by failing sooner rather than later.
306# See: https://github.com/giampaolo/psutil/issues/564
307if int(__version__.replace('.', '')) != getattr(_psutil, 'version', None):
308 msg = f"version conflict: {_psutil.__file__!r} C extension "
309 msg += "module was built for another version of psutil"
310 if hasattr(_psutil, 'version'):
311 v = ".".join(list(str(_psutil.version)))
312 msg += f" ({v} instead of {__version__})"
313 else:
314 msg += f" (different than {__version__})"
315 what = getattr(
316 _psutil,
317 "__file__",
318 "the existing psutil install directory",
319 )
320 msg += f"; you may try to 'pip uninstall psutil', manually remove {what}"
321 msg += " or clean the virtual env somehow, then reinstall"
322 raise ImportError(msg)
323
324
325# =====================================================================
326# --- Utils
327# =====================================================================
328
329
330if hasattr(_psplatform, 'ppid_map'):
331 # Faster version (Windows and Linux).
332 _ppid_map = _psplatform.ppid_map
333else: # pragma: no cover
334
335 def _ppid_map():
336 """Return a `{pid: ppid, ...}` dict for all running processes in
337 one shot. Used to speed up `Process.children()`.
338 """
339 ret = {}
340 for pid in pids():
341 try:
342 ret[pid] = _psplatform.Process(pid).ppid()
343 except (NoSuchProcess, ZombieProcess):
344 pass
345 return ret
346
347
348def _pprint_secs(secs):
349 """Format seconds in a human readable form."""
350 now = time.time()
351 secs_ago = int(now - secs)
352 fmt = "%H:%M:%S" if secs_ago < 60 * 60 * 24 else "%Y-%m-%d %H:%M:%S"
353 return datetime.datetime.fromtimestamp(secs).strftime(fmt)
354
355
356def _check_conn_kind(kind):
357 """Check net_connections()'s `kind` parameter."""
358 kinds = tuple(_common.conn_tmap)
359 if kind not in kinds:
360 msg = f"invalid kind argument {kind!r}; valid ones are: {kinds}"
361 raise ValueError(msg)
362
363
364# =====================================================================
365# --- Process class
366# =====================================================================
367
368
369def _use_prefetch(method):
370 """Decorator returning cached values from `process_iter(attrs=...)`.
371
372 When `process_iter()` is called with an *attrs* argument, it
373 pre-fetches the requested attributes via `as_dict()` and stores
374 them in `Process._prefetch`. This decorator makes the decorated
375 method return the cached value (if present) instead of issuing
376 a new system call.
377 """
378
379 @functools.wraps(method)
380 def wrapper(self, *args, **kwargs):
381 if not args and not kwargs:
382 try:
383 return self._prefetch[method.__name__]
384 except KeyError:
385 pass
386 return method(self, *args, **kwargs)
387
388 return wrapper
389
390
391class Process:
392 """Represents an OS process identified by a PID.
393
394 If *pid* arg is omitted, the current process PID (`os.getpid()`) is
395 used. Raises `NoSuchProcess` if the PID does not exist.
396
397 The way this class is bound to a process is via its PID. Most
398 methods do not guarantee that the PID has not been reused, so you
399 may end up retrieving information for a different process.
400
401 Real process identity is checked (via PID + creation time) only for
402 methods that set attributes or send signals.
403
404 To avoid issues with PID reuse for other read-only methods, call
405 `is_running()` before querying the process.
406 """
407
408 attrs: frozenset[str] = frozenset() # dynamically set later
409
410 def __init__(self, pid: int | None = None) -> None:
411 self._init(pid)
412
413 def _init(self, pid, _ignore_nsp=False):
414 if pid is None:
415 pid = os.getpid()
416 else:
417 if pid < 0:
418 msg = f"pid must be a positive integer (got {pid})"
419 raise ValueError(msg)
420 try:
421 _psutil.check_pid_range(pid)
422 except OverflowError as err:
423 msg = "process PID out of range"
424 raise NoSuchProcess(pid, msg=msg) from err
425
426 self._pid = pid
427 self._name = None
428 self._exe = None
429 self._create_time = None
430 self._gone = False
431 self._pid_reused = False
432 self._hash = None
433 self._lock = threading.RLock()
434 # used for caching on Windows only (on POSIX ppid may change)
435 self._ppid = None
436 # platform-specific modules define an _psplatform.Process
437 # implementation class
438 self._proc = _psplatform.Process(pid)
439 self._last_sys_cpu_times = None
440 self._last_proc_cpu_times = None
441 self._exitcode = _SENTINEL
442 self._prefetch = {}
443 self._ad_value = _SENTINEL
444 self._ident = (self.pid, None)
445 try:
446 self._ident = self._get_ident()
447 except AccessDenied:
448 # This should happen on Windows only, since we use the fast
449 # create time method. AFAIK, on all other platforms we are
450 # able to get create time for all PIDs.
451 pass
452 except ZombieProcess:
453 # Zombies can still be queried by this class (although
454 # not always) and pids() return them so just go on.
455 pass
456 except NoSuchProcess:
457 if not _ignore_nsp:
458 msg = "process PID not found"
459 raise NoSuchProcess(pid, msg=msg) from None
460 self._gone = True
461
462 def _is_ad_value(self, value):
463 """Whether `value` is the `ad_value` that process_iter(attrs=...)
464 stored in place of a getter which raised AccessDenied.
465 """
466 return self._ad_value is not _SENTINEL and value is self._ad_value
467
468 def _get_ident(self):
469 """Return a `(pid, uid)` tuple which is supposed to identify a
470 Process instance univocally over time.
471
472 The PID alone is not enough, as it can be assigned to a new
473 process after this one terminates, so we add creation time to
474 the mix. We need this in order to prevent killing the wrong
475 process later on. This is also known as PID reuse or PID
476 recycling problem.
477
478 The reliability of this strategy mostly depends on
479 `create_time()` precision, which is 0.01 secs on Linux. The
480 assumption is that, after a process terminates, the kernel
481 won't reuse the same PID after such a short period of time
482 (0.01 secs). Technically this is inherently racy, but
483 practically it should be good enough.
484
485 NOTE: unreliable on FreeBSD and OpenBSD as ctime is subject to
486 system clock updates, so the PID-reuse check there is disabled.
487 Same goes for SunOS and AIX, where we don't know whether ctime
488 is stable across clock updates.
489
490 NOTE 2: it is also disabled on Windows in case `create_time()`
491 can't be fetched due to `AccessDenied`.
492 """
493
494 if WINDOWS:
495 # Use create_time() fast method in order to speedup
496 # `process_iter()`. This means we'll get AccessDenied for
497 # most ADMIN processes, but that's fine since it means
498 # we'll also get AccessDenied on kill().
499 # https://github.com/giampaolo/psutil/issues/2366#issuecomment-2381646555
500 self._create_time = self._proc.create_time(fast_only=True)
501 return (self.pid, self._create_time)
502 elif LINUX or NETBSD or OSX:
503 # Use 'monotonic' process starttime since boot to form unique
504 # process identity, since it is stable over changes to system
505 # time.
506 return (self.pid, self._proc.create_time(monotonic=True))
507 else:
508 # Still call create_time() to check PID existence (raise
509 # NSP at construction time), but don't use it for identity.
510 self.create_time()
511 return (self.pid, None)
512
513 def __str__(self):
514 info = {}
515 info["pid"] = self.pid
516 with self.oneshot():
517 if self._pid_reused:
518 info["status"] = "terminated + PID reused"
519 else:
520 try:
521 info["name"] = self._name or self.name()
522 info["status"] = str(self.status())
523 except ZombieProcess:
524 info["status"] = "zombie"
525 except NoSuchProcess:
526 info["status"] = "terminated"
527 except AccessDenied:
528 pass
529
530 if self._exitcode not in {_SENTINEL, None}:
531 info["exitcode"] = self._exitcode
532 if self._create_time is not None:
533 info['started'] = _pprint_secs(self._create_time)
534
535 return "{}.{}({})".format(
536 self.__class__.__module__,
537 self.__class__.__name__,
538 ", ".join([f"{k}={v!r}" for k, v in info.items()]),
539 )
540
541 __repr__ = __str__
542
543 @staticmethod
544 def _cmp_idents(ident1, ident2):
545 """Compare two `(pid, ctime)` identity tuples and return
546 "same", "different" or "unknown". "unknown" means ctime is
547 missing on either side (`AccessDenied` on Windows, zombies
548 resulting in ctime 0), which is not proof of a different
549 process.
550 """
551 pid1, ctime1 = ident1
552 pid2, ctime2 = ident2
553 if pid1 != pid2:
554 return "different"
555 if not ctime1 or not ctime2:
556 return "unknown"
557 return "same" if ctime1 == ctime2 else "different"
558
559 def __eq__(self, other):
560 # Test for equality with another Process object based
561 # on PID and creation time.
562 if not isinstance(other, Process):
563 return NotImplemented
564 return self._cmp_idents(self._ident, other._ident) != "different"
565
566 def __ne__(self, other):
567 return not self == other
568
569 def __hash__(self):
570 # PID only: __eq__ can match idents with different ctimes, and
571 # equal objects must hash the same.
572 if self._hash is None:
573 self._hash = hash(self._ident[0])
574 return self._hash
575
576 def _raise_if_pid_reused(self):
577 """Raise `NoSuchProcess` in case process PID has been reused."""
578 if self._pid_reused or (not self.is_running() and self._pid_reused):
579 # We may directly raise NSP in here already if PID is just
580 # not running, but I prefer NSP to be raised naturally by
581 # the actual Process API call. This way unit tests will tell
582 # us if the API is broken (aka don't raise NSP when it
583 # should). We also remain consistent with all other "get"
584 # APIs which don't use _raise_if_pid_reused().
585 msg = "process no longer exists and its PID has been reused"
586 raise NoSuchProcess(self.pid, self._name, msg=msg)
587
588 @property
589 def pid(self) -> int:
590 """The process PID."""
591 return self._pid
592
593 # DEPRECATED
594 @property
595 def info(self) -> dict:
596 """Return pre-fetched `process_iter()` info dict.
597
598 Deprecated: use method calls instead (e.g. `p.name()`).
599 """
600 msg = (
601 "Process.info is deprecated; use method calls instead"
602 " (e.g. p.name() instead of p.info['name'])"
603 )
604 warnings.warn(msg, DeprecationWarning, stacklevel=2)
605 # Return a copy to prevent the user from mutating the dict and
606 # corrupting the prefetch cache.
607 return self._prefetch.copy()
608
609 # --- utility methods
610
611 @contextlib.contextmanager
612 def oneshot(self) -> Generator[None, None, None]:
613 """Context manager which speeds up the retrieval of multiple
614 process attributes at the same time.
615
616 Internally, many attributes (e.g. `name()`, `ppid()`, `uids()`,
617 `create_time()`, ...) share the same system call. This context
618 manager executes each system call once, and caches the results,
619 so subsequent calls return cached values. The cache is cleared
620 when exiting the context manager block. Use this every time you
621 retrieve more than one attribute about the process.
622
623 >>> import psutil
624 >>> p = psutil.Process()
625 >>> with p.oneshot():
626 ... p.name() # collect multiple info
627 ... p.cpu_times() # return cached value
628 ... p.cpu_percent() # return cached value
629 ... p.create_time() # return cached value
630 ...
631 >>>
632 """
633 with self._lock:
634 if hasattr(self, "_cache"):
635 # NOOP: this covers the use case where the user enters the
636 # context twice:
637 #
638 # >>> with p.oneshot():
639 # ... with p.oneshot():
640 # ...
641 #
642 # Also, since as_dict() internally uses oneshot()
643 # I expect that the code below will be a pretty common
644 # "mistake" that the user will make, so let's guard
645 # against that:
646 #
647 # >>> with p.oneshot():
648 # ... p.as_dict()
649 # ...
650 yield
651 else:
652 try:
653 self.cpu_times.cache_activate(self)
654 # cached in case memory_percent() is used
655 self.memory_info.cache_activate(self)
656 # cached in case parent() is used
657 self.ppid.cache_activate(self)
658 # cached in case username() is used
659 if POSIX:
660 self.uids.cache_activate(self)
661 # specific implementation cache
662 self._proc.oneshot_enter()
663 yield
664 finally:
665 self.cpu_times.cache_deactivate(self)
666 self.memory_info.cache_deactivate(self)
667 self.ppid.cache_deactivate(self)
668 if POSIX:
669 self.uids.cache_deactivate(self)
670 self._proc.oneshot_exit()
671
672 def as_dict(
673 self, attrs: Collection[str] | None = None, ad_value: Any = None
674 ) -> dict[str, Any]:
675 """Utility method returning process information as a
676 hashable dictionary.
677
678 If *attrs* is specified it must be a collection of strings
679 reflecting available Process class' attribute names (e.g.
680 ['cpu_times', 'name']) else all public (read-only) attributes
681 are assumed. See `Process.attrs` for a full list.
682
683 *ad_value* is the value which gets assigned in case
684 `AccessDenied` or `ZombieProcess` exception is raised when
685 retrieving that particular process information.
686 """
687 valid_names = self.attrs
688 # Deprecated attrs: not returned by default but still accepted if
689 # explicitly requested.
690 deprecated_names = {"memory_full_info"}
691
692 if attrs is not None:
693 if not isinstance(attrs, (list, tuple, set, frozenset)):
694 msg = f"invalid attrs type {type(attrs)}"
695 raise TypeError(msg)
696 attrs = set(attrs)
697 invalid_names = attrs - valid_names - deprecated_names
698 if invalid_names:
699 msg = "invalid attr name{} {}".format(
700 "s" if len(invalid_names) > 1 else "",
701 ", ".join(map(repr, invalid_names)),
702 )
703 raise ValueError(msg)
704
705 retdict = {}
706 names = attrs or sorted(valid_names)
707 with self.oneshot():
708 for name in names:
709 try:
710 if name == 'pid':
711 ret = self.pid
712 else:
713 meth = getattr(self, name)
714 ret = meth()
715 except (AccessDenied, ZombieProcess):
716 ret = ad_value
717 except NotImplementedError:
718 # in case of not implemented functionality (may happen
719 # on old or exotic systems) we want to crash only if
720 # the user explicitly asked for that particular attr
721 if attrs:
722 raise
723 continue
724 retdict[name] = ret
725 return retdict
726
727 def parent(self) -> Process | None:
728 """Return the parent process as a `Process` object, preemptively
729 checking whether PID has been reused.
730
731 If no parent is known return None.
732 """
733 lowest_pid = _LOWEST_PID if _LOWEST_PID is not None else pids()[0]
734 if self.pid == lowest_pid:
735 return None
736 ppid = self.ppid()
737 if ppid is not None:
738 # Get a fresh (non-cached) ctime in case the system clock
739 # was updated. TODO: use a monotonic ctime on platforms
740 # where it's supported.
741 proc_ctime = Process(self.pid).create_time()
742 try:
743 parent = Process(ppid)
744 if parent.create_time() <= proc_ctime:
745 return parent
746 # ...else ppid has been reused by another process
747 except NoSuchProcess:
748 pass
749
750 def parents(self) -> list[Process]:
751 """Return the parents of this process as a list of `Process`
752 instances.
753
754 If no parents are known return an empty list.
755 """
756 parents = []
757 proc = self.parent()
758 while proc is not None:
759 parents.append(proc)
760 proc = proc.parent()
761 return parents
762
763 def is_running(self) -> bool:
764 """Return whether this process is running.
765
766 It also checks if PID has been reused by another process, in
767 which case it will remove the process from `process_iter()`
768 internal cache and return False.
769 """
770 if self._gone or self._pid_reused:
771 return False
772 try:
773 # Checking if PID is alive is not enough as the PID might
774 # have been reused by another process. Process identity is
775 # guaranteed by (PID + creation time), see __eq__.
776 other = Process(self.pid)
777 self._pid_reused = self != other
778 if self._pid_reused:
779 debug(f"PID reuse detected: {self._ident} vs. {other._ident}")
780 _pids_reused.add(self.pid)
781 raise NoSuchProcess(self.pid)
782 if self._cmp_idents(self._ident, other._ident) == "unknown":
783 debug(
784 "null create time, PID reuse check inconclusive:"
785 f" {self._ident} vs. {other._ident}"
786 )
787 return True
788 except ZombieProcess:
789 # We should never get here as it's already handled in
790 # Process.__init__; here just for extra safety.
791 return True
792 except NoSuchProcess:
793 self._gone = True
794 return False
795
796 # --- actual API
797
798 @_use_prefetch
799 @memoize_when_activated
800 def ppid(self) -> int:
801 """The process parent PID.
802 On Windows the return value is cached after first call.
803 """
804 # On POSIX we don't want to cache the ppid as it may unexpectedly
805 # change to 1 (init) in case this process turns into a zombie:
806 # https://github.com/giampaolo/psutil/issues/321
807 # http://stackoverflow.com/questions/356722/
808 self._raise_if_pid_reused()
809 if POSIX:
810 return self._proc.ppid()
811 else: # pragma: no cover
812 self._ppid = self._ppid or self._proc.ppid()
813 return self._ppid
814
815 @_use_prefetch
816 def name(self) -> str:
817 """The process name. The return value is cached after first call."""
818 # Process name is only cached on Windows as on POSIX it may
819 # change, see:
820 # https://github.com/giampaolo/psutil/issues/692
821 if WINDOWS and self._name is not None:
822 return self._name
823 name = self._proc.name()
824 if POSIX and len(name) >= 15:
825 # On UNIX the name gets truncated to the first 15 characters.
826 # If it matches the first part of the cmdline we return that
827 # one instead because it's usually more explicative.
828 # Examples are "gnome-keyring-d" vs. "gnome-keyring-daemon".
829 try:
830 cmdline = self.cmdline()
831 except (AccessDenied, ZombieProcess):
832 # Just pass and return the truncated name: it's better
833 # than nothing. Note: there are actual cases where a
834 # zombie process can return a name() but not a
835 # cmdline(), see:
836 # https://github.com/giampaolo/psutil/issues/2239
837 pass
838 else:
839 if cmdline:
840 extended_name = os.path.basename(cmdline[0])
841 if extended_name.startswith(name):
842 name = extended_name
843 self._name = name
844 self._proc._name = name
845 return name
846
847 @_use_prefetch
848 def exe(self) -> str:
849 """The process executable as an absolute path.
850
851 May also be an empty string. The return value is cached after
852 first call.
853 """
854
855 def guess_it(fallback):
856 # try to guess exe from cmdline[0] in absence of a native
857 # exe representation
858 cmdline = self.cmdline()
859 if cmdline and hasattr(os, 'access') and hasattr(os, 'X_OK'):
860 exe = cmdline[0] # the possible exe
861 # Attempt to guess only in case of an absolute path.
862 # It is not safe otherwise as the process might have
863 # changed cwd.
864 if (
865 os.path.isabs(exe)
866 and os.path.isfile(exe)
867 and os.access(exe, os.X_OK)
868 ):
869 return exe
870 if isinstance(fallback, AccessDenied):
871 raise fallback
872 return fallback
873
874 if self._exe is None:
875 try:
876 exe = self._proc.exe()
877 except AccessDenied as err:
878 return guess_it(fallback=err)
879 else:
880 if not exe:
881 # underlying implementation can legitimately return an
882 # empty string; if that's the case we don't want to
883 # raise AD while guessing from the cmdline
884 try:
885 exe = guess_it(fallback=exe)
886 except AccessDenied:
887 pass
888 self._exe = exe
889 return self._exe
890
891 @_use_prefetch
892 def cmdline(self) -> list[str]:
893 """The command line this process has been called with."""
894 return self._proc.cmdline()
895
896 @_use_prefetch
897 def status(self) -> ProcessStatus | str:
898 """The process current status as a `STATUS_` constant."""
899 try:
900 return self._proc.status()
901 except ZombieProcess:
902 return ProcessStatus.STATUS_ZOMBIE
903
904 @_use_prefetch
905 def username(self) -> str:
906 """The name of the user that owns the process.
907
908 On UNIX this is calculated by using the real process uid.
909 """
910 if POSIX:
911 if pwd is None:
912 # might happen if python was installed from sources
913 msg = "requires pwd module shipped with standard python"
914 raise ImportError(msg)
915 uids = self.uids()
916 if self._is_ad_value(uids):
917 return uids
918 real_uid = uids.real
919 try:
920 return pwd.getpwuid(real_uid).pw_name
921 except KeyError:
922 # the uid can't be resolved by the system
923 return str(real_uid)
924 else:
925 return self._proc.username()
926
927 @_use_prefetch
928 def create_time(self) -> float:
929 """The process creation time as a floating point number
930 expressed in seconds since the epoch (seconds since January 1,
931 1970, at midnight UTC).
932
933 The return value, which is cached after first call, is based on
934 the system clock, which means it may be affected by changes
935 such as manual adjustments or time synchronization (e.g. NTP).
936 """
937 if self._create_time is None:
938 self._create_time = self._proc.create_time()
939 return self._create_time
940
941 @_use_prefetch
942 def cwd(self) -> str:
943 """Process current working directory as an absolute path."""
944 return self._proc.cwd()
945
946 @_use_prefetch
947 def nice(self, value: int | None = None) -> int | None:
948 """Get or set process niceness (priority)."""
949 if value is None:
950 return self._proc.nice_get()
951 else:
952 self._raise_if_pid_reused()
953 self._proc.nice_set(value)
954
955 if POSIX:
956
957 @_use_prefetch
958 @memoize_when_activated
959 def uids(self) -> puids:
960 """Return process UIDs as a `(real, effective, saved)`
961 named tuple.
962 """
963 return self._proc.uids()
964
965 @_use_prefetch
966 def gids(self) -> pgids:
967 """Return process GIDs as a `(real, effective, saved)`
968 named tuple.
969 """
970 return self._proc.gids()
971
972 @_use_prefetch
973 def terminal(self) -> str | None:
974 """The terminal associated with this process, if any,
975 else None.
976 """
977 return self._proc.terminal()
978
979 @_use_prefetch
980 def num_fds(self) -> int:
981 """Return the number of file descriptors opened by this
982 process (POSIX only).
983 """
984 return self._proc.num_fds()
985
986 if hasattr(_psplatform.Process, "io_counters"):
987
988 @_use_prefetch
989 def io_counters(self) -> pio:
990 """Return process I/O statistics (primarily read and
991 written bytes).
992
993 Availability: Linux, Windows, BSD, AIX
994 """
995 return self._proc.io_counters()
996
997 if hasattr(_psplatform.Process, "ionice_get"):
998
999 @_use_prefetch
1000 def ionice(
1001 self, ioclass: int | None = None, value: int | None = None
1002 ) -> pionice | ProcessIOPriority | None:
1003 """Get or set process I/O niceness (priority).
1004
1005 On Linux *ioclass* is one of the `IOPRIO_CLASS_*` constants.
1006 *value* is a number which goes from 0 to 7. The higher the
1007 value, the lower the I/O priority of the process.
1008
1009 On Windows only *ioclass* is used and it can be set to
1010 one of the `IOPRIO_*` constants.
1011
1012 Availability: Linux, Windows
1013 """
1014 if ioclass is None:
1015 if value is not None:
1016 msg = "'ioclass' argument must be specified"
1017 raise ValueError(msg)
1018 return self._proc.ionice_get()
1019 else:
1020 self._raise_if_pid_reused()
1021 return self._proc.ionice_set(ioclass, value)
1022
1023 if hasattr(_psplatform.Process, "rlimit"):
1024
1025 def rlimit(
1026 self,
1027 resource: int,
1028 limits: tuple[int, int] | None = None,
1029 ) -> tuple[int, int] | None:
1030 """Get or set process resource limits as a `(soft, hard)`
1031 tuple.
1032
1033 - resource: one of the `RLIMIT_*` constants.
1034 - limits: a `(soft, hard)` tuple (set).
1035
1036 See "man prlimit" for further info.
1037
1038 Availability: Linux, FreeBSD
1039 """
1040 if limits is not None:
1041 self._raise_if_pid_reused()
1042 return self._proc.rlimit(resource, limits)
1043
1044 if hasattr(_psplatform.Process, "cpu_affinity_get"):
1045
1046 @_use_prefetch
1047 def cpu_affinity(
1048 self, cpus: list[int] | None = None
1049 ) -> list[int] | None:
1050 """Get or set process CPU affinity.
1051
1052 If specified, *cpus* must be a list of CPUs for which you
1053 want to set the affinity (e.g. `[0, 1]`). If an empty list is
1054 passed, all eligible CPUs are assumed (and set).
1055
1056 Availability: Linux, Windows, FreeBSD
1057 """
1058 if cpus is None:
1059 return sorted(set(self._proc.cpu_affinity_get()))
1060 else:
1061 self._raise_if_pid_reused()
1062 if not cpus:
1063 if hasattr(self._proc, "_get_eligible_cpus"):
1064 cpus = self._proc._get_eligible_cpus()
1065 else:
1066 cpus = tuple(range(len(cpu_times(percpu=True))))
1067 self._proc.cpu_affinity_set(list(set(cpus)))
1068
1069 # Linux, FreeBSD, SunOS
1070 if hasattr(_psplatform.Process, "cpu_num"):
1071
1072 @_use_prefetch
1073 def cpu_num(self) -> int:
1074 """Return what CPU this process is currently running on.
1075
1076 The returned number should be <= `psutil.cpu_count()`.
1077 """
1078 return self._proc.cpu_num()
1079
1080 # All platforms has it, but maybe not in the future.
1081 if hasattr(_psplatform.Process, "environ"):
1082
1083 @_use_prefetch
1084 def environ(self) -> dict[str, str]:
1085 """The environment variables of the process as a dict.
1086
1087 Note: this might not reflect changes made after the process
1088 started.
1089 """
1090 return self._proc.environ()
1091
1092 if WINDOWS:
1093
1094 @_use_prefetch
1095 def num_handles(self) -> int:
1096 """Return the number of handles opened by this process
1097
1098 Availability: Windows
1099 """
1100 return self._proc.num_handles()
1101
1102 @_use_prefetch
1103 def num_ctx_switches(self) -> pctxsw:
1104 """Return the number of voluntary and involuntary context
1105 switches performed by this process.
1106 """
1107 return self._proc.num_ctx_switches()
1108
1109 @_use_prefetch
1110 def num_threads(self) -> int:
1111 """Return the number of threads used by this process."""
1112 return self._proc.num_threads()
1113
1114 if hasattr(_psplatform.Process, "threads"):
1115
1116 @_use_prefetch
1117 def threads(self) -> list[pthread]:
1118 """Return threads opened by process as a list of
1119 `(id, user_time, system_time)` named tuples.
1120
1121 On OpenBSD this method requires root access.
1122 """
1123 return self._proc.threads()
1124
1125 def children(self, recursive: bool = False) -> list[Process]:
1126 """Return the children of this process as a list of Process
1127 instances, preemptively checking whether PID has been reused.
1128
1129 If *recursive* is True return all the parent descendants.
1130
1131 Example (A == this process):
1132
1133 A ─┐
1134 │
1135 ├─ B (child) ─┐
1136 │ └─ X (grandchild) ─┐
1137 │ └─ Y (great grandchild)
1138 ├─ C (child)
1139 └─ D (child)
1140
1141 >>> import psutil
1142 >>> p = psutil.Process()
1143 >>> p.children()
1144 B, C, D
1145 >>> p.children(recursive=True)
1146 B, X, Y, C, D
1147
1148 Note that in the example above if process X disappears
1149 process Y won't be listed as the reference to process A
1150 is lost.
1151 """
1152 self._raise_if_pid_reused()
1153 ppid_map = _ppid_map()
1154 # Get a fresh (non-cached) ctime in case the system clock was
1155 # updated. TODO: use a monotonic ctime on platforms where it's
1156 # supported.
1157 proc_ctime = Process(self.pid).create_time()
1158 ret = []
1159 if not recursive:
1160 for pid, ppid in ppid_map.items():
1161 if ppid == self.pid:
1162 try:
1163 child = Process(pid)
1164 # if child happens to be older than its parent
1165 # (self) it means child's PID has been reused
1166 if proc_ctime <= child.create_time():
1167 ret.append(child)
1168 except (NoSuchProcess, ZombieProcess):
1169 pass
1170 else:
1171 # Construct a {pid: [child pids]} dict
1172 reverse_ppid_map = collections.defaultdict(list)
1173 for pid, ppid in ppid_map.items():
1174 reverse_ppid_map[ppid].append(pid)
1175 # Recursively traverse that dict, starting from self.pid,
1176 # such that we only call Process() on actual children
1177 seen = set()
1178 stack = [self.pid]
1179 while stack:
1180 pid = stack.pop()
1181 if pid in seen:
1182 # Since pids can be reused while the ppid_map is
1183 # constructed, there may be rare instances where
1184 # there's a cycle in the recorded process "tree".
1185 continue
1186 seen.add(pid)
1187 for child_pid in reverse_ppid_map[pid]:
1188 try:
1189 child = Process(child_pid)
1190 # if child happens to be older than its parent
1191 # (self) it means child's PID has been reused
1192 intime = proc_ctime <= child.create_time()
1193 if intime:
1194 ret.append(child)
1195 stack.append(child_pid)
1196 except (NoSuchProcess, ZombieProcess):
1197 pass
1198 return ret
1199
1200 @_use_prefetch
1201 def cpu_percent(self, interval: float | None = None) -> float:
1202 """Return a float representing the current process CPU
1203 utilization as a percentage.
1204
1205 When *interval* is 0.0 or None (default) compares process times
1206 to system CPU times elapsed since last call, returning
1207 immediately (non-blocking). That means that the first time
1208 this is called it will return a meaningless 0.0 value.
1209
1210 When *interval* is > 0.0 compares process times to system CPU
1211 times elapsed before and after the interval (blocking).
1212
1213 In this case is recommended for accuracy that this function
1214 be called with at least 0.1 seconds between calls.
1215
1216 A value > 100.0 can be returned in case of processes running
1217 multiple threads on different CPU cores.
1218
1219 The returned value is explicitly NOT split evenly between
1220 all available logical CPUs. This means that a busy loop process
1221 running on a system with 2 logical CPUs will be reported as
1222 having 100% CPU utilization instead of 50%.
1223
1224 Examples:
1225
1226 >>> import psutil
1227 >>> p = psutil.Process(os.getpid())
1228 >>> # blocking
1229 >>> p.cpu_percent(interval=1)
1230 2.0
1231 >>> # non-blocking (percentage since last call)
1232 >>> p.cpu_percent(interval=None)
1233 2.9
1234 >>>
1235 """
1236 blocking = interval is not None and interval > 0.0
1237 if interval is not None and interval < 0:
1238 msg = f"interval is not positive (got {interval!r})"
1239 raise ValueError(msg)
1240 num_cpus = cpu_count() or 1
1241
1242 def timer():
1243 return _timer() * num_cpus
1244
1245 if blocking:
1246 st1 = timer()
1247 pt1 = self._proc.cpu_times()
1248 time.sleep(interval)
1249 st2 = timer()
1250 pt2 = self._proc.cpu_times()
1251 else:
1252 st1 = self._last_sys_cpu_times
1253 pt1 = self._last_proc_cpu_times
1254 st2 = timer()
1255 pt2 = self._proc.cpu_times()
1256 if st1 is None or pt1 is None:
1257 self._last_sys_cpu_times = st2
1258 self._last_proc_cpu_times = pt2
1259 return 0.0
1260
1261 delta_proc = (pt2.user - pt1.user) + (pt2.system - pt1.system)
1262 delta_time = st2 - st1
1263 # reset values for next call in case of interval == None
1264 self._last_sys_cpu_times = st2
1265 self._last_proc_cpu_times = pt2
1266
1267 try:
1268 # This is the utilization split evenly between all CPUs.
1269 # E.g. a busy loop process on a 2-CPU-cores system at this
1270 # point is reported as 50% instead of 100%.
1271 overall_cpus_percent = (delta_proc / delta_time) * 100
1272 except ZeroDivisionError:
1273 # interval was too low
1274 return 0.0
1275 else:
1276 # Note 1:
1277 # in order to emulate "top" we multiply the value for the num
1278 # of CPU cores. This way the busy process will be reported as
1279 # having 100% (or more) usage.
1280 #
1281 # Note 2:
1282 # taskmgr.exe on Windows differs in that it will show 50%
1283 # instead.
1284 #
1285 # Note 3:
1286 # a percentage > 100 is legitimate as it can result from a
1287 # process with multiple threads running on different CPU
1288 # cores (top does the same), see:
1289 # http://stackoverflow.com/questions/1032357
1290 # https://github.com/giampaolo/psutil/issues/474
1291 single_cpu_percent = overall_cpus_percent * num_cpus
1292 return round(single_cpu_percent, 1)
1293
1294 @_use_prefetch
1295 @memoize_when_activated
1296 def cpu_times(self) -> pcputimes:
1297 """Return a `(user, system, children_user, children_system)`
1298 named tuple representing the accumulated process time,
1299 expressed in seconds.
1300
1301 Linux includes an additional `iowait` field.
1302
1303 On macOS and Windows `children_user` and `children_system`
1304 fields are always set to 0.
1305 """
1306 return self._proc.cpu_times()
1307
1308 @_use_prefetch
1309 @memoize_when_activated
1310 def memory_info(self) -> pmem:
1311 """Return a named tuple with variable fields depending on the
1312 platform, representing memory information about the process.
1313
1314 The portable fields available on all platforms are `rss` and `vms`.
1315
1316 All numbers are expressed in bytes.
1317 """
1318 return self._proc.memory_info()
1319
1320 # Linux, macOS, Windows
1321 if hasattr(_psplatform.Process, "memory_extras"):
1322
1323 @_use_prefetch
1324 def memory_extras(self) -> pmem_extras:
1325 """Return a named tuple with extra platform-specific memory
1326 metrics, complementing `memory_info()`.
1327
1328 All numbers are expressed in bytes.
1329 """
1330 return self._proc.memory_extras()
1331
1332 # Linux, macOS, Windows
1333 if hasattr(_psplatform.Process, "memory_footprint"):
1334
1335 @_use_prefetch
1336 def memory_footprint(self) -> pfootprint:
1337 """Return a named tuple with USS and shared memory, and
1338 on Linux also PSS and swap.
1339
1340 These values provide a more accurate representation of
1341 actual process memory usage.
1342
1343 USS is the memory unique to a process and which would
1344 be freed if the process was terminated right now.
1345
1346 It does so by passing through the whole process address. As
1347 such it usually requires higher user privileges than
1348 `memory_info()` or `memory_extras()` and is considerably
1349 slower.
1350 """
1351 return self._proc.memory_footprint()
1352
1353 # DEPRECATED
1354 def memory_full_info(self) -> pfullmem:
1355 """Return the same information as `memory_info()` plus
1356 `memory_footprint()`'s `uss`, `pss` and `swap` fields in a
1357 single named tuple.
1358
1359 DEPRECATED in 8.0.0. Use `memory_footprint()` instead.
1360 """
1361 msg = (
1362 "memory_full_info() is deprecated; use memory_footprint() instead"
1363 )
1364 warnings.warn(msg, DeprecationWarning, stacklevel=2)
1365 basic_mem = self.memory_info()
1366 if self._is_ad_value(basic_mem):
1367 return basic_mem
1368 if hasattr(self, "memory_footprint"):
1369 fp = self.memory_footprint()
1370 if self._is_ad_value(fp):
1371 return fp
1372 names = _ntp.pfullmem._fields[len(basic_mem) :]
1373 return _ntp.pfullmem(*basic_mem, *[getattr(fp, x) for x in names])
1374 return _ntp.pfullmem(*basic_mem)
1375
1376 @_use_prefetch
1377 def memory_percent(self, memtype: str = "rss") -> float:
1378 """Compare process memory to total physical system memory and
1379 calculate process memory utilization as a percentage.
1380
1381 *memtype* argument is a string that dictates what type of
1382 process memory you want to compare against (defaults to
1383 "rss"). It can be any field of `memory_info()`,
1384 `memory_extras()` or `memory_footprint()`. The divisor is
1385 always total physical memory, regardless of *memtype*.
1386 """
1387 valid_types = list(_ntp.pmem._fields)
1388 if hasattr(_ntp, "pmem_extras"):
1389 valid_types += [
1390 f for f in _ntp.pmem_extras._fields if f not in valid_types
1391 ]
1392 if hasattr(_ntp, "pfootprint"):
1393 valid_types += [
1394 f for f in _ntp.pfootprint._fields if f not in valid_types
1395 ]
1396 if memtype not in valid_types:
1397 msg = (
1398 f"invalid memtype {memtype!r}; valid types are"
1399 f" {tuple(valid_types)!r}"
1400 )
1401 raise ValueError(msg)
1402
1403 if memtype in _ntp.pmem._fields:
1404 fun = self.memory_info
1405 elif (
1406 hasattr(_ntp, "pmem_extras")
1407 and memtype in _ntp.pmem_extras._fields
1408 ):
1409 fun = self.memory_extras
1410 else:
1411 fun = self.memory_footprint
1412
1413 metrics = fun()
1414 if self._is_ad_value(metrics):
1415 return metrics
1416 value = getattr(metrics, memtype)
1417
1418 # use cached value if available
1419 total_phymem = _TOTAL_PHYMEM or virtual_memory().total
1420 if not total_phymem > 0:
1421 # we should never get here
1422 msg = (
1423 "can't calculate process memory percent because total physical"
1424 f" system memory is not positive ({total_phymem!r})"
1425 )
1426 raise ValueError(msg)
1427 return round((value / float(total_phymem)) * 100, 2)
1428
1429 if hasattr(_psplatform.Process, "memory_maps"):
1430
1431 @_use_prefetch
1432 def memory_maps(
1433 self, grouped: bool = True
1434 ) -> list[pmmap_grouped] | list[pmmap_ext]:
1435 """Return process mapped memory regions as a list of named
1436 tuples whose fields are variable depending on the platform.
1437
1438 If *grouped* is True the mapped regions with the same 'path'
1439 are grouped together and the different memory fields are summed.
1440
1441 If *grouped* is False every mapped region is shown as a single
1442 entity and the named tuple will also include the mapped region's
1443 address space ('addr') and permission set ('perms').
1444 """
1445
1446 it = self._proc.memory_maps()
1447 if grouped:
1448 d = {}
1449 for tupl in it:
1450 path = tupl[2]
1451 nums = tupl[3:]
1452 try:
1453 d[path] = list(map(lambda x, y: x + y, d[path], nums))
1454 except KeyError:
1455 d[path] = nums
1456 return [_ntp.pmmap_grouped(path, *d[path]) for path in d]
1457 else:
1458 return [_ntp.pmmap_ext(*x) for x in it]
1459
1460 @_use_prefetch
1461 def page_faults(self) -> ppagefaults:
1462 """Return the number of page faults for this process as a
1463 `(minor, major)` named tuple.
1464
1465 - `minor` (a.k.a. *soft* faults): occur when a memory page is
1466 not currently mapped into the process address space, but is
1467 already present in physical RAM (e.g. a shared library page
1468 loaded by another process). The kernel resolves these without
1469 disk I/O.
1470
1471 - `major` (a.k.a. *hard* faults): occur when the page must be
1472 fetched from disk. These are expensive because they stall the
1473 process until I/O completes.
1474
1475 Both counters are cumulative since process creation.
1476 """
1477 return self._proc.page_faults()
1478
1479 @_use_prefetch
1480 def open_files(self) -> list[popenfile]:
1481 """Return files opened by process as a list of `(path, fd)`
1482 named tuples including the absolute file name and file
1483 descriptor number.
1484
1485 On Linux the named tuple also includes `position`, `mode` and
1486 `flags` fields.
1487 """
1488 return self._proc.open_files()
1489
1490 @_use_prefetch
1491 def net_connections(self, kind: str = "inet") -> list[pconn]:
1492 """Return socket connections opened by process as a list of
1493 `(fd, family, type, laddr, raddr, status)` named tuples.
1494
1495 The *kind* parameter filters for connections that match the
1496 following criteria:
1497
1498 +------------+----------------------------------------------------+
1499 | Kind Value | Connections using |
1500 +------------+----------------------------------------------------+
1501 | 'inet' | IPv4 and IPv6 |
1502 | 'inet4' | IPv4 |
1503 | 'inet6' | IPv6 |
1504 | 'tcp' | TCP |
1505 | 'tcp4' | TCP over IPv4 |
1506 | 'tcp6' | TCP over IPv6 |
1507 | 'udp' | UDP |
1508 | 'udp4' | UDP over IPv4 |
1509 | 'udp6' | UDP over IPv6 |
1510 | 'unix' | UNIX socket (both UDP and TCP protocols) |
1511 | 'all' | the sum of all the possible families and protocols |
1512 +------------+----------------------------------------------------+
1513 """
1514 _check_conn_kind(kind)
1515 return self._proc.net_connections(kind)
1516
1517 @_common.deprecated_method(replacement="net_connections")
1518 def connections(self, kind="inet") -> list[pconn]:
1519 return self.net_connections(kind=kind)
1520
1521 # --- signals
1522
1523 if POSIX:
1524
1525 def _send_signal(self, sig):
1526 assert not self.pid < 0, self.pid
1527 self._raise_if_pid_reused()
1528
1529 pid, ppid, name = self.pid, self._ppid, self._name
1530 if pid == 0:
1531 # see "man 2 kill"
1532 msg = (
1533 "preventing sending signal to process with PID 0 as it "
1534 "would affect every process in the process group of the "
1535 "calling process (os.getpid()) instead of PID 0"
1536 )
1537 raise ValueError(msg)
1538 try:
1539 os.kill(pid, sig)
1540 except ProcessLookupError as err:
1541 if OPENBSD and pid_exists(pid):
1542 # We do this because os.kill() lies in case of
1543 # zombie processes.
1544 raise ZombieProcess(pid, name, ppid) from err
1545 self._gone = True
1546 raise NoSuchProcess(pid, name) from err
1547 except PermissionError as err:
1548 raise AccessDenied(pid, name) from err
1549
1550 def send_signal(self, sig: int) -> None:
1551 """Send a signal *sig* to process, preemptively checking
1552 whether PID has been reused (see signal module constants).
1553
1554 On Windows only SIGTERM, CTRL_C_EVENT and CTRL_BREAK_EVENT
1555 are valid. SIGTERM is treated as an alias for `kill()`.
1556 """
1557 if POSIX:
1558 self._send_signal(sig)
1559 else: # pragma: no cover
1560 self._raise_if_pid_reused()
1561 if sig != signal.SIGTERM and not self.is_running():
1562 msg = "process no longer exists"
1563 raise NoSuchProcess(self.pid, self._name, msg=msg)
1564 self._proc.send_signal(sig)
1565
1566 def suspend(self) -> None:
1567 """Suspend process execution with SIGSTOP preemptively checking
1568 whether PID has been reused.
1569
1570 On Windows this has the effect of suspending all process threads.
1571 """
1572 if POSIX:
1573 self._send_signal(signal.SIGSTOP)
1574 else: # pragma: no cover
1575 self._raise_if_pid_reused()
1576 self._proc.suspend()
1577
1578 def resume(self) -> None:
1579 """Resume process execution with SIGCONT preemptively checking
1580 whether PID has been reused.
1581
1582 On Windows this has the effect of resuming all process threads.
1583 """
1584 if POSIX:
1585 self._send_signal(signal.SIGCONT)
1586 else: # pragma: no cover
1587 self._raise_if_pid_reused()
1588 self._proc.resume()
1589
1590 def terminate(self) -> None:
1591 """Terminate the process with SIGTERM preemptively checking
1592 whether PID has been reused.
1593
1594 On Windows this is an alias for `kill()`.
1595 """
1596 if POSIX:
1597 self._send_signal(signal.SIGTERM)
1598 else: # pragma: no cover
1599 self._raise_if_pid_reused()
1600 self._proc.kill()
1601
1602 def kill(self) -> None:
1603 """Kill the current process with SIGKILL preemptively checking
1604 whether PID has been reused.
1605 """
1606 if POSIX:
1607 self._send_signal(signal.SIGKILL)
1608 else: # pragma: no cover
1609 self._raise_if_pid_reused()
1610 self._proc.kill()
1611
1612 def wait(self, timeout: float | None = None) -> int | None:
1613 """Wait for process to terminate, and if process is a child
1614 of os.getpid(), also return its exit code, else None.
1615
1616 On Windows there's no such limitation (exit code is always
1617 returned).
1618
1619 If the process is already terminated, immediately return None
1620 instead of raising `NoSuchProcess`.
1621
1622 If *timeout* (in seconds) is specified and process is still
1623 alive, raise `TimeoutExpired`.
1624
1625 If *timeout=0* either return immediately or raise
1626 `TimeoutExpired` (non-blocking).
1627
1628 To wait for multiple Process objects use `psutil.wait_procs()`.
1629 """
1630 if self.pid == 0:
1631 msg = "can't wait for PID 0"
1632 raise ValueError(msg)
1633 if timeout is not None:
1634 if not isinstance(timeout, (int, float)):
1635 msg = f"timeout must be an int or float (got {type(timeout)})"
1636 raise TypeError(msg)
1637 if timeout < 0:
1638 msg = f"timeout must be positive or zero (got {timeout})"
1639 raise ValueError(msg)
1640
1641 if self._exitcode is not _SENTINEL:
1642 return self._exitcode
1643
1644 try:
1645 self._exitcode = self._proc.wait(timeout)
1646 except TimeoutExpired as err:
1647 exc = TimeoutExpired(timeout, pid=self.pid, name=self._name)
1648 raise exc from err
1649
1650 return self._exitcode
1651
1652
1653# The valid attr names which can be processed by Process.as_dict(attrs=...)
1654# and process_iter(attrs=...).
1655# fmt: off
1656Process.attrs = frozenset(
1657 x for x in dir(Process) if not x.startswith("_") and x not in
1658 {'send_signal', 'suspend', 'resume', 'terminate', 'kill', 'wait',
1659 'is_running', 'as_dict', 'parent', 'parents', 'children', 'rlimit',
1660 'connections', 'memory_full_info', 'oneshot', 'info', 'attrs'}
1661)
1662# fmt: on
1663
1664
1665# =====================================================================
1666# --- Popen class
1667# =====================================================================
1668
1669
1670class Popen(Process):
1671 """Same as `subprocess.Popen`, but in addition it provides all
1672 `Process` methods in a single class.
1673
1674 For the following methods which are common to both classes, psutil
1675 implementation takes precedence:
1676
1677 * `send_signal()`
1678 * `terminate()`
1679 * `kill()`
1680
1681 This is done in order to avoid killing another process in case its
1682 PID has been reused, fixing BPO-6973.
1683
1684 >>> import psutil
1685 >>> from subprocess import PIPE
1686 >>> p = psutil.Popen(["python", "-c", "print 'hi'"], stdout=PIPE)
1687 >>> p.name()
1688 'python3'
1689 >>> p.uids()
1690 user(real=1000, effective=1000, saved=1000)
1691 >>> p.username()
1692 'giampaolo'
1693 >>> p.communicate()
1694 ('hi', None)
1695 >>> p.terminate()
1696 >>> p.wait(timeout=2)
1697 0
1698 >>>
1699 """
1700
1701 def __init__(self, *args, **kwargs):
1702 # Explicitly avoid to raise NoSuchProcess in case the process
1703 # spawned by subprocess.Popen terminates too quickly, see:
1704 # https://github.com/giampaolo/psutil/issues/193
1705 self.__subproc = subprocess.Popen(*args, **kwargs)
1706 self._init(self.__subproc.pid, _ignore_nsp=True)
1707
1708 def __dir__(self):
1709 return sorted(set(dir(Popen) + dir(subprocess.Popen)))
1710
1711 def __enter__(self) -> Popen:
1712 if hasattr(self.__subproc, '__enter__'):
1713 self.__subproc.__enter__()
1714 return self
1715
1716 def __exit__(self, *args, **kwargs):
1717 if hasattr(self.__subproc, '__exit__'):
1718 return self.__subproc.__exit__(*args, **kwargs)
1719 else:
1720 if self.stdout:
1721 self.stdout.close()
1722 if self.stderr:
1723 self.stderr.close()
1724 try:
1725 # Flushing a BufferedWriter may raise an error.
1726 if self.stdin:
1727 self.stdin.close()
1728 finally:
1729 # Wait for the process to terminate, to avoid zombies.
1730 self.wait()
1731
1732 def __getattribute__(self, name):
1733 try:
1734 return object.__getattribute__(self, name)
1735 except AttributeError:
1736 try:
1737 return object.__getattribute__(self.__subproc, name)
1738 except AttributeError:
1739 msg = f"{self.__class__!r} has no attribute {name!r}"
1740 raise AttributeError(msg) from None
1741
1742 def wait(self, timeout: float | None = None) -> int | None:
1743 if self.__subproc.returncode is not None:
1744 return self.__subproc.returncode
1745 ret = super().wait(timeout)
1746 self.__subproc.returncode = ret
1747 return ret
1748
1749
1750# =====================================================================
1751# --- system processes related functions
1752# =====================================================================
1753
1754
1755def pids() -> list[int]:
1756 """Return a list of current running PIDs."""
1757 global _LOWEST_PID
1758 ret = sorted(_psplatform.pids())
1759 _LOWEST_PID = ret[0]
1760 return ret
1761
1762
1763def pid_exists(pid: int) -> bool:
1764 """Return True if *pid* exists in the current process list.
1765
1766 This is faster than doing `pid in psutil.pids()` and should be
1767 preferred.
1768 """
1769 if pid < 0:
1770 return False
1771 elif pid == 0 and POSIX:
1772 # On POSIX we use os.kill() to determine PID existence.
1773 # According to "man 2 kill" PID 0 has a special meaning
1774 # though: it refers to <<every process in the process
1775 # group of the calling process>> and that is not we want
1776 # to do here.
1777 return pid in pids()
1778 else:
1779 return _psplatform.pid_exists(pid)
1780
1781
1782_pmap = {}
1783_pids_reused = set()
1784
1785
1786def process_iter(
1787 attrs: Collection[str] | None = None, ad_value: Any = None
1788) -> Iterator[Process]:
1789 """Return a generator yielding a `Process` instance for all
1790 running processes.
1791
1792 Every new `Process` instance is only created once and then cached
1793 into an internal table which is updated every time this is used.
1794 Cache can optionally be cleared via `process_iter.cache_clear()`.
1795
1796 The sorting order in which processes are yielded is based on
1797 their PIDs.
1798
1799 *attrs* and *ad_value* have the same meaning as in
1800 `Process.as_dict()`.
1801
1802 If *attrs* is specified, `Process.as_dict()` is called and the
1803 results are cached, so that subsequent method calls (e.g.
1804 `p.name()`) return cached values. Use `attrs=Process.attrs` to
1805 retrieve all process info (slow).
1806
1807 If a method raises `AccessDenied` during pre-fetch, it will return
1808 *ad_value* (default None) instead of raising.
1809 """
1810 global _pmap
1811
1812 def add(pid):
1813 proc = Process(pid)
1814 pmap[proc.pid] = proc
1815 return proc
1816
1817 def remove(pid):
1818 pmap.pop(pid, None)
1819
1820 if attrs is not None:
1821 if attrs == []: # deprecated in 8.0.0
1822 msg = (
1823 "process_iter(attrs=[]) is deprecated; use "
1824 "process_iter(attrs=Process.attrs) to retrieve all attributes"
1825 )
1826 warnings.warn(msg, DeprecationWarning, stacklevel=2)
1827 elif not attrs:
1828 # as_dict() will resolve an empty list|tuple|set to "all
1829 # attribute names", but it's ambiguous and should be
1830 # signaled.
1831 msg = (
1832 f"process_iter(attrs={attrs}) is ambiguous; use "
1833 "process_iter(attrs=Process.attrs) to retrieve all attributes"
1834 )
1835 warnings.warn(msg, UserWarning, stacklevel=2)
1836
1837 pmap = _pmap.copy()
1838 a = set(pids())
1839 b = set(pmap)
1840 new_pids = a - b
1841 gone_pids = b - a
1842 for pid in gone_pids:
1843 remove(pid)
1844 while _pids_reused:
1845 pid = _pids_reused.pop()
1846 debug(f"refreshing Process instance for reused PID {pid}")
1847 remove(pid)
1848 try:
1849 ls = sorted(list(pmap.items()) + list(dict.fromkeys(new_pids).items()))
1850 for pid, proc in ls:
1851 try:
1852 if proc is None: # new process
1853 proc = add(pid)
1854 proc._prefetch = {} # clear cache
1855 proc._ad_value = _SENTINEL
1856 if attrs is not None:
1857 proc._prefetch = proc.as_dict(
1858 attrs=attrs, ad_value=ad_value
1859 )
1860 proc._ad_value = ad_value
1861 yield proc
1862 except ZombieProcess:
1863 if proc is not None:
1864 yield proc # zombie processes are still valid
1865 except NoSuchProcess:
1866 remove(pid)
1867 finally:
1868 _pmap = pmap
1869
1870
1871process_iter.cache_clear = lambda: _pmap.clear() # noqa: PLW0108
1872process_iter.cache_clear.__doc__ = "Clear process_iter() internal cache."
1873
1874
1875def wait_procs(
1876 procs: list[Process],
1877 timeout: float | None = None,
1878 callback: Callable[[Process], None] | None = None,
1879) -> tuple[list[Process], list[Process]]:
1880 """Convenience function which waits for a list of processes to
1881 terminate.
1882
1883 Return a `(gone, alive)` tuple indicating which processes
1884 are gone and which ones are still alive.
1885
1886 The gone ones will have a new `returncode` attribute indicating
1887 process exit status (may be None).
1888
1889 *callback* is a function which gets called every time a process
1890 terminates (a `Process` instance is passed as callback argument).
1891
1892 Function will return as soon as all processes terminate or when
1893 *timeout* occurs.
1894
1895 Differently from `Process.wait()` it will not raise `TimeoutExpired` if
1896 *timeout* occurs.
1897
1898 Typical use case is:
1899
1900 - send SIGTERM to a list of processes
1901 - give them some time to terminate
1902 - send SIGKILL to those ones which are still alive
1903
1904 Example:
1905
1906 >>> def on_terminate(proc):
1907 ... print("process {} terminated".format(proc))
1908 ...
1909 >>> for p in procs:
1910 ... p.terminate()
1911 ...
1912 >>> gone, alive = wait_procs(procs, timeout=3, callback=on_terminate)
1913 >>> for p in alive:
1914 ... p.kill()
1915 """
1916
1917 def check_gone(proc, timeout):
1918 try:
1919 returncode = proc.wait(timeout=timeout)
1920 except (TimeoutExpired, subprocess.TimeoutExpired):
1921 pass
1922 else:
1923 if returncode is not None or not proc.is_running():
1924 # Set new Process instance attribute.
1925 proc.returncode = returncode
1926 gone.add(proc)
1927 if callback is not None:
1928 callback(proc)
1929
1930 if timeout is not None and not timeout >= 0:
1931 msg = f"timeout must be a positive integer, got {timeout}"
1932 raise ValueError(msg)
1933 if callback is not None and not callable(callback):
1934 msg = f"callback {callback!r} is not a callable"
1935 raise TypeError(msg)
1936
1937 gone = set()
1938 alive = set(procs)
1939 if timeout is not None:
1940 deadline = _timer() + timeout
1941
1942 while alive:
1943 if timeout is not None and timeout <= 0:
1944 break
1945 for proc in alive:
1946 # Make sure that every complete iteration (all processes)
1947 # will last max 1 sec.
1948 # We do this because we don't want to wait too long on a
1949 # single process: in case it terminates too late other
1950 # processes may disappear in the meantime and their PID
1951 # reused.
1952 max_timeout = 1.0 / len(alive)
1953 if timeout is not None:
1954 timeout = min((deadline - _timer()), max_timeout)
1955 if timeout <= 0:
1956 break
1957 check_gone(proc, timeout)
1958 else:
1959 check_gone(proc, max_timeout)
1960 alive = alive - gone # noqa: PLR6104
1961
1962 if alive:
1963 # Last attempt over processes survived so far.
1964 # timeout == 0 won't make this function wait any further.
1965 for proc in alive:
1966 check_gone(proc, 0)
1967 alive = alive - gone # noqa: PLR6104
1968
1969 return (list(gone), list(alive))
1970
1971
1972# =====================================================================
1973# --- CPU related functions
1974# =====================================================================
1975
1976
1977def cpu_count(logical: bool = True) -> int | None:
1978 """Return the number of logical CPUs in the system (same as
1979 `os.cpu_count()`).
1980
1981 If *logical* is False return the number of physical cores only
1982 (e.g. hyper thread CPUs are excluded).
1983
1984 Return None if undetermined.
1985
1986 The return value is cached after first call.
1987 If desired cache can be cleared like this:
1988
1989 >>> psutil.cpu_count.cache_clear()
1990 """
1991 if logical:
1992 ret = _psplatform.cpu_count_logical()
1993 else:
1994 ret = _psplatform.cpu_count_cores()
1995 if ret is not None and ret < 1:
1996 ret = None
1997 return ret
1998
1999
2000def cpu_times(percpu: bool = False) -> scputimes | list[scputimes]:
2001 """Return system-wide CPU times as a named tuple.
2002
2003 Every CPU time represents the seconds the CPU has spent in the
2004 given mode:
2005
2006 - `user`
2007 - `system`
2008 - `idle`
2009 - `nice` (UNIX)
2010 - `iowait` (Linux)
2011 - `irq` (Linux, FreeBSD)
2012 - `softirq` (Linux)
2013 - `steal` (Linux)
2014 - `guest` (Linux)
2015 - `guest_nice` (Linux)
2016 - `dpc` (Windows)
2017
2018 When *percpu* is True return a list of named tuples for each
2019 logical CPU. First element of the list refers to first CPU, second
2020 element to second CPU and so on. The order of the list is
2021 consistent across calls.
2022 """
2023 if not percpu:
2024 return _psplatform.cpu_times()
2025 else:
2026 return _psplatform.per_cpu_times()
2027
2028
2029try:
2030 _last_cpu_times = {threading.current_thread().ident: cpu_times()}
2031except Exception: # noqa: BLE001
2032 # Don't want to crash at import time.
2033 _last_cpu_times = {}
2034
2035try:
2036 _last_per_cpu_times = {
2037 threading.current_thread().ident: cpu_times(percpu=True)
2038 }
2039except Exception: # noqa: BLE001
2040 # Don't want to crash at import time.
2041 _last_per_cpu_times = {}
2042
2043
2044def _cpu_tot_time(times):
2045 """Given a `cpu_time()` named tuple calculates the total CPU time
2046 (including idle time).
2047 """
2048 tot = sum(times)
2049 if LINUX:
2050 # On Linux guest times are already accounted in "user" or
2051 # "nice" times, so we subtract them from total.
2052 # Htop does the same. References:
2053 # https://github.com/giampaolo/psutil/pull/940
2054 # http://unix.stackexchange.com/questions/178045
2055 # https://github.com/torvalds/linux/blob/447976ef4/kernel/sched/cputime.c#L158
2056 tot -= times.guest
2057 tot -= times.guest_nice
2058 return tot
2059
2060
2061def _cpu_busy_time(times):
2062 """Given a `cpu_time()` named tuple calculates the busy CPU time by
2063 subtracting all idle CPU times.
2064 """
2065 busy = _cpu_tot_time(times)
2066 busy -= times.idle
2067 # Linux: "iowait" is time during which the CPU does not do anything
2068 # (waits for IO to complete). On Linux IO wait is *not* accounted
2069 # in "idle" time so we subtract it. Htop does the same.
2070 # References:
2071 # https://github.com/torvalds/linux/blob/447976ef4/kernel/sched/cputime.c#L244
2072 busy -= getattr(times, "iowait", 0)
2073 return busy
2074
2075
2076def _cpu_times_deltas(t1, t2):
2077 assert t1._fields == t2._fields, (t1, t2)
2078 field_deltas = []
2079 for field in _ntp.scputimes._fields:
2080 field_delta = getattr(t2, field) - getattr(t1, field)
2081 # CPU times are always supposed to increase over time
2082 # or at least remain the same and that's because time
2083 # cannot go backwards.
2084 # Surprisingly sometimes this might not be the case (at
2085 # least on Windows and Linux), see:
2086 # https://github.com/giampaolo/psutil/issues/392
2087 # https://github.com/giampaolo/psutil/issues/645
2088 # https://github.com/giampaolo/psutil/issues/1210
2089 # Trim negative deltas to zero to ignore decreasing fields.
2090 # top does the same. Reference:
2091 # https://gitlab.com/procps-ng/procps/blob/v3.3.12/top/top.c#L5063
2092 field_delta = max(0, field_delta)
2093 field_deltas.append(field_delta)
2094 return _ntp.scputimes(*field_deltas)
2095
2096
2097def cpu_percent(
2098 interval: float | None = None, percpu: bool = False
2099) -> float | list[float]:
2100 """Return a float representing the current system-wide CPU
2101 utilization as a percentage.
2102
2103 When *interval* is > 0.0 compares system CPU times elapsed before
2104 and after the interval (blocking).
2105
2106 When *interval* is 0.0 or None compares system CPU times elapsed
2107 since last call or module import, returning immediately (non
2108 blocking). That means the first time this is called it will
2109 return a meaningless 0.0 value which you should ignore.
2110 In this case is recommended for accuracy that this function be
2111 called with at least 0.1 seconds between calls.
2112
2113 When *percpu* is True returns a list of floats representing the
2114 utilization as a percentage for each CPU.
2115 First element of the list refers to first CPU, second element
2116 to second CPU and so on.
2117 The order of the list is consistent across calls.
2118
2119 Examples:
2120
2121 >>> # blocking, system-wide
2122 >>> psutil.cpu_percent(interval=1)
2123 2.0
2124 >>>
2125 >>> # blocking, per-cpu
2126 >>> psutil.cpu_percent(interval=1, percpu=True)
2127 [2.0, 1.0]
2128 >>>
2129 >>> # non-blocking (percentage since last call)
2130 >>> psutil.cpu_percent(interval=None)
2131 2.9
2132 >>>
2133 """
2134 tid = threading.current_thread().ident
2135 blocking = interval is not None and interval > 0.0
2136 if interval is not None and interval < 0:
2137 msg = f"interval is not positive (got {interval})"
2138 raise ValueError(msg)
2139
2140 def calculate(t1, t2):
2141 times_delta = _cpu_times_deltas(t1, t2)
2142 all_delta = _cpu_tot_time(times_delta)
2143 busy_delta = _cpu_busy_time(times_delta)
2144
2145 try:
2146 busy_perc = (busy_delta / all_delta) * 100
2147 except ZeroDivisionError:
2148 return 0.0
2149 else:
2150 return round(busy_perc, 1)
2151
2152 # system-wide usage
2153 if not percpu:
2154 if blocking:
2155 t1 = cpu_times()
2156 time.sleep(interval)
2157 else:
2158 t1 = _last_cpu_times.get(tid) or cpu_times()
2159 _last_cpu_times[tid] = cpu_times()
2160 return calculate(t1, _last_cpu_times[tid])
2161 # per-cpu usage
2162 else:
2163 ret = []
2164 if blocking:
2165 tot1 = cpu_times(percpu=True)
2166 time.sleep(interval)
2167 else:
2168 tot1 = _last_per_cpu_times.get(tid) or cpu_times(percpu=True)
2169 _last_per_cpu_times[tid] = cpu_times(percpu=True)
2170 for t1, t2 in zip(tot1, _last_per_cpu_times[tid]):
2171 ret.append(calculate(t1, t2))
2172 return ret
2173
2174
2175# Use a separate dict for cpu_times_percent(), so it's independent from
2176# cpu_percent() and they can both be used within the same program.
2177_last_cpu_times_2 = _last_cpu_times.copy()
2178_last_per_cpu_times_2 = _last_per_cpu_times.copy()
2179
2180
2181def cpu_times_percent(
2182 interval: float | None = None, percpu: bool = False
2183) -> scputimes | list[scputimes]:
2184 """Same as `cpu_percent()`, but provides utilization percentages
2185 for each specific CPU time as is returned by `cpu_times()`.
2186
2187 For instance, on Linux we'll get:
2188
2189 >>> cpu_times_percent()
2190 cpupercent(user=4.8, nice=0.0, system=4.8, idle=90.5, iowait=0.0,
2191 irq=0.0, softirq=0.0, steal=0.0, guest=0.0, guest_nice=0.0)
2192 >>>
2193
2194 *interval* and *percpu* arguments have the same meaning as in
2195 `cpu_percent()`.
2196 """
2197 tid = threading.current_thread().ident
2198 blocking = interval is not None and interval > 0.0
2199 if interval is not None and interval < 0:
2200 msg = f"interval is not positive (got {interval!r})"
2201 raise ValueError(msg)
2202
2203 def calculate(t1, t2):
2204 nums = []
2205 times_delta = _cpu_times_deltas(t1, t2)
2206 all_delta = _cpu_tot_time(times_delta)
2207 # "scale" is the value to multiply each delta with to get percentages.
2208 # We use "max" to avoid division by zero (if all_delta is 0, then all
2209 # fields are 0 so percentages will be 0 too. all_delta cannot be a
2210 # fraction because cpu times are integers)
2211 scale = 100.0 / max(1, all_delta)
2212 for field_delta in times_delta:
2213 field_perc = field_delta * scale
2214 field_perc = round(field_perc, 1)
2215 # make sure we don't return negative values or values over 100%
2216 field_perc = min(max(0.0, field_perc), 100.0)
2217 nums.append(field_perc)
2218 return _ntp.scputimes(*nums)
2219
2220 # system-wide usage
2221 if not percpu:
2222 if blocking:
2223 t1 = cpu_times()
2224 time.sleep(interval)
2225 else:
2226 t1 = _last_cpu_times_2.get(tid) or cpu_times()
2227 _last_cpu_times_2[tid] = cpu_times()
2228 return calculate(t1, _last_cpu_times_2[tid])
2229 # per-cpu usage
2230 else:
2231 ret = []
2232 if blocking:
2233 tot1 = cpu_times(percpu=True)
2234 time.sleep(interval)
2235 else:
2236 tot1 = _last_per_cpu_times_2.get(tid) or cpu_times(percpu=True)
2237 _last_per_cpu_times_2[tid] = cpu_times(percpu=True)
2238 for t1, t2 in zip(tot1, _last_per_cpu_times_2[tid]):
2239 ret.append(calculate(t1, t2))
2240 return ret
2241
2242
2243def cpu_stats() -> scpustats:
2244 """Return CPU statistics."""
2245 return _psplatform.cpu_stats()
2246
2247
2248if hasattr(_psplatform, "cpu_freq"):
2249
2250 def cpu_freq(percpu: bool = False) -> scpufreq | list[scpufreq] | None:
2251 """Return CPU frequency as a named tuple including current,
2252 min and max frequency expressed in Mhz.
2253
2254 If *percpu* is True and the system supports per-cpu frequency
2255 retrieval (Linux and FreeBSD), a list of frequencies is
2256 returned for each CPU. If not, a list with one element is
2257 returned.
2258 """
2259 ret = _psplatform.cpu_freq()
2260 if percpu:
2261 return ret
2262 else:
2263 num_cpus = float(len(ret))
2264 if num_cpus == 0:
2265 return None
2266 elif num_cpus == 1:
2267 return ret[0]
2268 else:
2269 currs, mins, maxs = 0.0, 0.0, 0.0
2270 set_none = False
2271 for cpu in ret:
2272 currs += cpu.current
2273 # On FreeBSD min/max are None if the sysctl value
2274 # can't be parsed.
2275 if cpu.min is None or cpu.max is None:
2276 set_none = True
2277 continue
2278 mins += cpu.min
2279 maxs += cpu.max
2280
2281 current = currs / num_cpus
2282
2283 if set_none:
2284 min_ = max_ = None
2285 else:
2286 min_ = mins / num_cpus
2287 max_ = maxs / num_cpus
2288
2289 return _ntp.scpufreq(current, min_, max_)
2290
2291 __all__.append("cpu_freq")
2292
2293
2294def getloadavg() -> tuple[float, float, float]:
2295 """Return the average system load over the last 1, 5 and 15 minutes
2296 as a tuple.
2297
2298 On Windows this is emulated by using a Windows API that spawns a
2299 thread which keeps running in background and updates results every
2300 5 seconds, mimicking the UNIX behavior.
2301 """
2302 if hasattr(os, "getloadavg"):
2303 return os.getloadavg()
2304 else:
2305 return _psplatform.getloadavg()
2306
2307
2308# =====================================================================
2309# --- system memory related functions
2310# =====================================================================
2311
2312
2313def virtual_memory() -> svmem:
2314 """Return statistics about system memory usage as a named tuple.
2315
2316 The fields vary by platform (see official doc), but the following
2317 are present on all platforms:
2318
2319 - total:
2320 total physical memory available
2321
2322 - available:
2323 the memory that can be given instantly to processes without the
2324 system going into swap.
2325 This is calculated by summing different memory values depending
2326 on the platform and it is supposed to be used to monitor actual
2327 memory usage in a cross platform fashion.
2328
2329 - percent:
2330 the percentage usage calculated as `(total - available) / total * 100`
2331
2332 - used:
2333 memory used, calculated differently depending on the platform and
2334 designed for informational purposes only
2335
2336 - free:
2337 memory not being used at all (zeroed) that is readily available;
2338 note that this doesn't reflect the actual memory available
2339 (use 'available' instead)
2340
2341 The sum of `used` and `available` does not necessarily equal `total`.
2342
2343 On Windows `available` and `free` are the same.
2344 """
2345 global _TOTAL_PHYMEM
2346 ret = _psplatform.virtual_memory()
2347 # cached for later use in Process.memory_percent()
2348 _TOTAL_PHYMEM = ret.total
2349 return ret
2350
2351
2352def swap_memory() -> sswap:
2353 """Return system swap memory statistics as a named tuple including
2354 the following fields:
2355
2356 - total: total swap memory in bytes
2357 - used: used swap memory in bytes
2358 - free: free swap memory in bytes
2359 - percent: the percentage usage
2360 - sin: no. of bytes the system has swapped in from disk (cumulative)
2361 - sout: no. of bytes the system has swapped out from disk (cumulative)
2362
2363 `sin` and `sout` on Windows are meaningless and always set to 0.
2364 """
2365 return _psplatform.swap_memory()
2366
2367
2368# =====================================================================
2369# --- disks/partitions related functions
2370# =====================================================================
2371
2372
2373def disk_usage(path: str) -> sdiskusage:
2374 """Return disk usage statistics about the given *path* as a
2375 named tuple including total, used and free space expressed in bytes
2376 plus the percentage usage.
2377 """
2378 return _psplatform.disk_usage(path)
2379
2380
2381def disk_partitions(all: bool = False) -> list[sdiskpart]:
2382 """Return mounted partitions as a list of
2383 (device, mountpoint, fstype, opts) named tuple.
2384
2385 `opts` field is a raw string separated by commas indicating mount
2386 options which may vary depending on the platform.
2387
2388 If *all* parameter is False return physical devices only and ignore
2389 all others.
2390 """
2391 return _psplatform.disk_partitions(all)
2392
2393
2394def disk_io_counters(
2395 perdisk: bool = False, nowrap: bool = True
2396) -> sdiskio | dict[str, sdiskio] | None:
2397 """Return system disk I/O statistics as a named tuple including
2398 the following fields:
2399
2400 - read_count: number of reads
2401 - write_count: number of writes
2402 - read_bytes: number of bytes read
2403 - write_bytes: number of bytes written
2404 - read_time: (not NetBSD, OpenBSD) time spent reading from
2405 disk (in ms)
2406 - write_time: (not NetBSD, OpenBSD) time spent writing to
2407 disk (in ms)
2408
2409 Platform specific:
2410
2411 - busy_time: (Linux, FreeBSD) time spent doing actual I/Os (in ms)
2412 - read_merged_count (Linux): number of merged reads
2413 - write_merged_count (Linux): number of merged writes
2414
2415 If *perdisk* is True return the same information for every
2416 physical disk as a dictionary with partition names as the keys.
2417
2418 If *nowrap* is True (default), counters that overflow and wrap to
2419 zero are automatically adjusted so they never decrease (this can
2420 happen on very busy or long-lived systems).
2421 `disk_io_counters.cache_clear()` can be used to invalidate the
2422 *nowrap* cache.
2423 """
2424 kwargs = dict(perdisk=perdisk) if LINUX else {}
2425 rawdict = _psplatform.disk_io_counters(**kwargs)
2426 if not rawdict:
2427 return {} if perdisk else None
2428 if nowrap:
2429 rawdict = _wrap_numbers(rawdict, 'psutil.disk_io_counters')
2430 if perdisk:
2431 for disk, fields in rawdict.items():
2432 rawdict[disk] = _ntp.sdiskio(*fields)
2433 return rawdict
2434 else:
2435 return _ntp.sdiskio(*(sum(x) for x in zip(*rawdict.values())))
2436
2437
2438disk_io_counters.cache_clear = functools.partial(
2439 _wrap_numbers.cache_clear, 'psutil.disk_io_counters'
2440)
2441disk_io_counters.cache_clear.__doc__ = "Clears nowrap argument cache"
2442
2443
2444# =====================================================================
2445# --- network related functions
2446# =====================================================================
2447
2448
2449def net_io_counters(
2450 pernic: bool = False, nowrap: bool = True
2451) -> snetio | dict[str, snetio] | None:
2452 """Return network I/O statistics as a named tuple including
2453 the following fields:
2454
2455 - bytes_sent: number of bytes sent
2456 - bytes_recv: number of bytes received
2457 - packets_sent: number of packets sent
2458 - packets_recv: number of packets received
2459 - errin: total number of errors while receiving
2460 - errout: total number of errors while sending
2461 - dropin: total number of incoming packets which were dropped
2462 - dropout: total number of outgoing packets which were dropped
2463 (always 0 on macOS and BSD)
2464
2465 If *pernic* is True return the same information for every
2466 network interface as a dictionary with interface names as the
2467 keys.
2468
2469 If *nowrap* is True (default), counters that overflow and wrap to
2470 zero are automatically adjusted so they never decrease (this can
2471 happen on very busy or long-lived systems).
2472 `net_io_counters.cache_clear()` can be used to invalidate the
2473 *nowrap* cache.
2474 """
2475 rawdict = _psplatform.net_io_counters()
2476 if not rawdict:
2477 return {} if pernic else None
2478 if nowrap:
2479 rawdict = _wrap_numbers(rawdict, 'psutil.net_io_counters')
2480 if pernic:
2481 for nic, fields in rawdict.items():
2482 rawdict[nic] = _ntp.snetio(*fields)
2483 return rawdict
2484 else:
2485 return _ntp.snetio(*[sum(x) for x in zip(*rawdict.values())])
2486
2487
2488net_io_counters.cache_clear = functools.partial(
2489 _wrap_numbers.cache_clear, 'psutil.net_io_counters'
2490)
2491net_io_counters.cache_clear.__doc__ = "Clears nowrap argument cache"
2492
2493
2494def net_connections(kind: str = 'inet') -> list[sconn]:
2495 """Return system-wide socket connections as a list of
2496 (fd, family, type, laddr, raddr, status, pid) named tuples.
2497
2498 In case of limited privileges `fd` and `pid` may be set to -1
2499 and None respectively.
2500
2501 The *kind* parameter filters for connections that fit the
2502 following criteria:
2503
2504 +------------+----------------------------------------------------+
2505 | Kind Value | Connections using |
2506 +------------+----------------------------------------------------+
2507 | 'inet' | IPv4 and IPv6 |
2508 | 'inet4' | IPv4 |
2509 | 'inet6' | IPv6 |
2510 | 'tcp' | TCP |
2511 | 'tcp4' | TCP over IPv4 |
2512 | 'tcp6' | TCP over IPv6 |
2513 | 'udp' | UDP |
2514 | 'udp4' | UDP over IPv4 |
2515 | 'udp6' | UDP over IPv6 |
2516 | 'unix' | UNIX socket (both UDP and TCP protocols) |
2517 | 'all' | the sum of all the possible families and protocols |
2518 +------------+----------------------------------------------------+
2519
2520 On macOS this function requires root privileges.
2521 """
2522 _check_conn_kind(kind)
2523 return _psplatform.net_connections(kind)
2524
2525
2526def net_if_addrs() -> dict[str, list[snicaddr]]:
2527 """Return a dictionary mapping each NIC (Network Interface Card) to
2528 a list of named tuples representing its addresses. Multiple
2529 addresses of the same family can exist per interface.
2530
2531 The named tuple includes 5 fields (addresses may be None):
2532
2533 - family: the address family, either `AF_INET`, `AF_INET6`,
2534 `psutil.AF_LINK` (a MAC address) or `AF_UNSPEC` (a virtual or
2535 unconfigured NIC).
2536 - address: the primary NIC address
2537 - netmask: the netmask address
2538 - broadcast: the broadcast address; always None on Windows
2539 - ptp: a "point to point" address (typically a VPN); always None on
2540 Windows
2541 """
2542 rawlist = _psplatform.net_if_addrs()
2543 rawlist.sort(key=lambda x: x[1]) # sort by family
2544 ret = collections.defaultdict(list)
2545 for name, fam, addr, mask, broadcast, ptp in rawlist:
2546 try:
2547 fam = socket.AddressFamily(fam)
2548 except ValueError:
2549 if WINDOWS and fam == -1:
2550 fam = _psplatform.AF_LINK
2551 elif (
2552 hasattr(_psplatform, "AF_LINK") and fam == _psplatform.AF_LINK
2553 ):
2554 # Linux defines AF_LINK as an alias for AF_PACKET.
2555 # We re-set the family here so that repr(family)
2556 # will show AF_LINK rather than AF_PACKET
2557 fam = _psplatform.AF_LINK
2558
2559 if fam == _psplatform.AF_LINK:
2560 # The underlying C function may return an incomplete MAC
2561 # address in which case we fill it with null bytes, see:
2562 # https://github.com/giampaolo/psutil/issues/786
2563 separator = ":" if POSIX else "-"
2564 while addr.count(separator) < 5:
2565 addr += f"{separator}00"
2566
2567 nt = _ntp.snicaddr(fam, addr, mask, broadcast, ptp)
2568
2569 # On Windows broadcast is None, so we determine it via
2570 # ipaddress module. On POSIX a /32 has no broadcast address,
2571 # but getifaddrs() hands back the local address.
2572 if nt.netmask and (
2573 fam == socket.AF_INET or (WINDOWS and fam == socket.AF_INET6)
2574 ):
2575 try:
2576 calculated = _common.broadcast_addr(nt)
2577 except Exception as err: # noqa: BLE001
2578 warn(f"broadcast_addr() failed: {err!r}")
2579 else:
2580 if calculated is None:
2581 nt = nt._replace(broadcast=None)
2582 elif WINDOWS:
2583 nt = nt._replace(broadcast=calculated)
2584
2585 ret[name].append(nt)
2586
2587 return dict(ret)
2588
2589
2590def net_if_stats() -> dict[str, snicstats]:
2591 """Return information about each NIC (network interface card)
2592 installed on the system as a dictionary whose keys are the
2593 NIC names and value is a named tuple with the following fields:
2594
2595 - isup: whether the interface is up (bool)
2596 - duplex: can be either `NIC_DUPLEX_FULL`, `NIC_DUPLEX_HALF` or
2597 `NIC_DUPLEX_UNKNOWN`
2598 - speed: the NIC speed expressed in mega bits (MB); if it can't
2599 be determined (e.g. 'localhost') it will be set to 0.
2600 - mtu: the maximum transmission unit expressed in bytes.
2601 - flags: a string of comma-separated flags on the interface.
2602 """
2603 return _psplatform.net_if_stats()
2604
2605
2606# =====================================================================
2607# --- sensors
2608# =====================================================================
2609
2610
2611# Linux, macOS
2612if hasattr(_psplatform, "sensors_temperatures"):
2613
2614 def sensors_temperatures(
2615 fahrenheit: bool = False,
2616 ) -> dict[str, list[shwtemp]]:
2617 """Return hardware temperatures.
2618
2619 Each entry is a named tuple representing a certain hardware
2620 sensor (it may be a CPU, an hard disk or something else,
2621 depending on the OS and its configuration).
2622
2623 All temperatures are expressed in celsius unless *fahrenheit*
2624 is set to True.
2625 """
2626
2627 def convert(n):
2628 if n is not None:
2629 return (float(n) * 9 / 5) + 32 if fahrenheit else n
2630
2631 ret = collections.defaultdict(list)
2632 rawdict = _psplatform.sensors_temperatures()
2633
2634 for name, values in rawdict.items():
2635 while values:
2636 label, current, high, critical = values.pop(0)
2637 current = convert(current)
2638 high = convert(high)
2639 critical = convert(critical)
2640
2641 if high and not critical:
2642 critical = high
2643 elif critical and not high:
2644 high = critical
2645
2646 ret[name].append(_ntp.shwtemp(label, current, high, critical))
2647
2648 return dict(ret)
2649
2650 __all__.append("sensors_temperatures")
2651
2652
2653# Linux
2654if hasattr(_psplatform, "sensors_fans"):
2655
2656 def sensors_fans() -> dict[str, list[sfan]]:
2657 """Return fans speed. Each entry is a named tuple
2658 representing a certain hardware sensor.
2659 All speed are expressed in RPM (rounds per minute).
2660 """
2661 return _psplatform.sensors_fans()
2662
2663 __all__.append("sensors_fans")
2664
2665
2666# Linux, Windows, FreeBSD, macOS
2667if hasattr(_psplatform, "sensors_battery"):
2668
2669 def sensors_battery() -> sbattery | None:
2670 """Return battery information. If no battery is installed
2671 returns None.
2672
2673 - percent: battery power left as a percentage.
2674 - secsleft: a rough approximation of how many seconds are left
2675 before the battery runs out of power. May be
2676 `POWER_TIME_UNLIMITED` or `POWER_TIME_UNKNOWN`.
2677 - power_plugged: True if the AC power cable is connected.
2678 """
2679 return _psplatform.sensors_battery()
2680
2681 __all__.append("sensors_battery")
2682
2683
2684# =====================================================================
2685# --- other system related functions
2686# =====================================================================
2687
2688
2689def boot_time() -> float:
2690 """Return the system boot time expressed in seconds since the epoch
2691 (seconds since January 1, 1970, at midnight UTC).
2692
2693 The returned value is based on the system clock, which means it may
2694 be affected by changes such as manual adjustments or time
2695 synchronization (e.g. NTP).
2696 """
2697 return _psplatform.boot_time()
2698
2699
2700def users() -> list[suser]:
2701 """Return users currently connected on the system as a list of
2702 named tuples including the following fields:
2703
2704 - user: the name of the user
2705 - terminal: the tty or pseudo-tty associated with the user, if any.
2706 - host: the host name associated with the entry, if any.
2707 - started: the creation time as a floating point number expressed in
2708 seconds since the epoch.
2709 - pid: the PID of the login process (None on Windows and OpenBSD).
2710 """
2711 return _psplatform.users()
2712
2713
2714# =====================================================================
2715# --- Windows services
2716# =====================================================================
2717
2718
2719if WINDOWS:
2720
2721 def win_service_iter() -> Iterator[WindowsService]:
2722 """Return a generator yielding a `WindowsService` instance for
2723 all Windows services installed.
2724 """
2725 return _psplatform.win_service_iter()
2726
2727 def win_service_get(name) -> WindowsService:
2728 """Get a Windows service by *name*.
2729
2730 Raise `NoSuchProcess` if no service with such name exists.
2731 """
2732 return _psplatform.win_service_get(name)
2733
2734
2735# =====================================================================
2736# --- malloc / heap
2737# =====================================================================
2738
2739
2740# Linux + glibc, Windows, macOS, FreeBSD, NetBSD
2741if hasattr(_psplatform, "heap_info"):
2742
2743 def heap_info() -> pheap:
2744 """Return low-level heap statistics from the C heap allocator
2745 (glibc).
2746
2747 - `heap_used`: the total number of bytes allocated via
2748 malloc/free. These are typically allocations smaller than
2749 MMAP_THRESHOLD.
2750
2751 - `mmap_used`: the total number of bytes allocated via `mmap()`
2752 or via large ``malloc()`` allocations.
2753
2754 - `heap_count` (Windows only): number of private heaps created
2755 via `HeapCreate()`.
2756 """
2757 return _ntp.pheap(*_psplatform.heap_info())
2758
2759 def heap_trim() -> None:
2760 """Request that the underlying allocator free any unused memory
2761 it's holding in the heap (typically small `malloc()`
2762 allocations).
2763
2764 In practice, modern allocators rarely comply, so this is not a
2765 general-purpose memory-reduction tool and won't meaningfully
2766 shrink RSS in real programs. Its primary value is in **leak
2767 detection tools**.
2768
2769 Calling `heap_trim()` before taking measurements helps reduce
2770 allocator noise, giving you a cleaner baseline so that changes
2771 in `heap_used` come from the code you're testing, not from
2772 internal allocator caching or fragmentation. Its effectiveness
2773 depends on allocator behavior and fragmentation patterns.
2774 """
2775 _psplatform.heap_trim()
2776
2777 __all__.append("heap_info")
2778 __all__.append("heap_trim")
2779
2780
2781# =====================================================================
2782
2783
2784def _set_debug(value):
2785 """Enable or disable `PSUTIL_DEBUG` option, which prints debugging
2786 messages to stderr.
2787 """
2788 import psutil._common
2789
2790 psutil._common.PSUTIL_DEBUG = bool(value)
2791 _psutil.set_debug(bool(value))
2792
2793
2794del memoize_when_activated