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_ex
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 # cached in case cpu_percent() is used
654 self.cpu_times.cache_activate(self)
655 # cached in case memory_percent() is used
656 self.memory_info.cache_activate(self)
657 # cached in case parent() is used
658 self.ppid.cache_activate(self)
659 # cached in case username() is used
660 if POSIX:
661 self.uids.cache_activate(self)
662 # specific implementation cache
663 self._proc.oneshot_enter()
664 yield
665 finally:
666 self.cpu_times.cache_deactivate(self)
667 self.memory_info.cache_deactivate(self)
668 self.ppid.cache_deactivate(self)
669 if POSIX:
670 self.uids.cache_deactivate(self)
671 self._proc.oneshot_exit()
672
673 def as_dict(
674 self, attrs: Collection[str] | None = None, ad_value: Any = None
675 ) -> dict[str, Any]:
676 """Utility method returning process information as a
677 hashable dictionary.
678
679 If *attrs* is specified it must be a collection of strings
680 reflecting available Process class' attribute names (e.g.
681 ['cpu_times', 'name']) else all public (read-only) attributes
682 are assumed. See `Process.attrs` for a full list.
683
684 *ad_value* is the value which gets assigned in case
685 `AccessDenied` or `ZombieProcess` exception is raised when
686 retrieving that particular process information.
687 """
688 valid_names = self.attrs
689 # Deprecated attrs: not returned by default but still accepted if
690 # explicitly requested.
691 deprecated_names = {"memory_full_info"}
692
693 if attrs is not None:
694 if not isinstance(attrs, (list, tuple, set, frozenset)):
695 msg = f"invalid attrs type {type(attrs)}"
696 raise TypeError(msg)
697 attrs = set(attrs)
698 invalid_names = attrs - valid_names - deprecated_names
699 if invalid_names:
700 msg = "invalid attr name{} {}".format(
701 "s" if len(invalid_names) > 1 else "",
702 ", ".join(map(repr, invalid_names)),
703 )
704 raise ValueError(msg)
705
706 retdict = {}
707 names = attrs or sorted(valid_names)
708 with self.oneshot():
709 for name in names:
710 try:
711 if name == 'pid':
712 ret = self.pid
713 else:
714 meth = getattr(self, name)
715 ret = meth()
716 except (AccessDenied, ZombieProcess):
717 ret = ad_value
718 except NotImplementedError:
719 # in case of not implemented functionality (may happen
720 # on old or exotic systems) we want to crash only if
721 # the user explicitly asked for that particular attr
722 if attrs:
723 raise
724 continue
725 retdict[name] = ret
726 return retdict
727
728 def parent(self) -> Process | None:
729 """Return the parent process as a `Process` object, preemptively
730 checking whether PID has been reused.
731
732 If no parent is known return None.
733 """
734 lowest_pid = _LOWEST_PID if _LOWEST_PID is not None else pids()[0]
735 if self.pid == lowest_pid:
736 return None
737 ppid = self.ppid()
738 if ppid is not None:
739 # Get a fresh (non-cached) ctime in case the system clock
740 # was updated. TODO: use a monotonic ctime on platforms
741 # where it's supported.
742 proc_ctime = Process(self.pid).create_time()
743 try:
744 parent = Process(ppid)
745 if parent.create_time() <= proc_ctime:
746 return parent
747 # ...else ppid has been reused by another process
748 except NoSuchProcess:
749 pass
750
751 def parents(self) -> list[Process]:
752 """Return the parents of this process as a list of `Process`
753 instances.
754
755 If no parents are known return an empty list.
756 """
757 parents = []
758 proc = self.parent()
759 while proc is not None:
760 parents.append(proc)
761 proc = proc.parent()
762 return parents
763
764 def is_running(self) -> bool:
765 """Return whether this process is running.
766
767 It also checks if PID has been reused by another process, in
768 which case it will remove the process from `process_iter()`
769 internal cache and return False.
770 """
771 if self._gone or self._pid_reused:
772 return False
773 try:
774 # Checking if PID is alive is not enough as the PID might
775 # have been reused by another process. Process identity is
776 # guaranteed by (PID + creation time), see __eq__.
777 other = Process(self.pid)
778 self._pid_reused = self != other
779 if self._pid_reused:
780 debug(f"PID reuse detected: {self._ident} vs. {other._ident}")
781 _pids_reused.add(self.pid)
782 raise NoSuchProcess(self.pid)
783 if self._cmp_idents(self._ident, other._ident) == "unknown":
784 debug(
785 "null create time, PID reuse check inconclusive:"
786 f" {self._ident} vs. {other._ident}"
787 )
788 return True
789 except ZombieProcess:
790 # We should never get here as it's already handled in
791 # Process.__init__; here just for extra safety.
792 return True
793 except NoSuchProcess:
794 self._gone = True
795 return False
796
797 # --- actual API
798
799 @_use_prefetch
800 @memoize_when_activated
801 def ppid(self) -> int:
802 """The process parent PID.
803 On Windows the return value is cached after first call.
804 """
805 # On POSIX we don't want to cache the ppid as it may unexpectedly
806 # change to 1 (init) in case this process turns into a zombie:
807 # https://github.com/giampaolo/psutil/issues/321
808 # http://stackoverflow.com/questions/356722/
809 self._raise_if_pid_reused()
810 if POSIX:
811 return self._proc.ppid()
812 else: # pragma: no cover
813 self._ppid = self._ppid or self._proc.ppid()
814 return self._ppid
815
816 @_use_prefetch
817 def name(self) -> str:
818 """The process name. The return value is cached after first call."""
819 # Process name is only cached on Windows as on POSIX it may
820 # change, see:
821 # https://github.com/giampaolo/psutil/issues/692
822 if WINDOWS and self._name is not None:
823 return self._name
824 name = self._proc.name()
825 if POSIX and len(name) >= 15:
826 # On UNIX the name gets truncated to the first 15 characters.
827 # If it matches the first part of the cmdline we return that
828 # one instead because it's usually more explicative.
829 # Examples are "gnome-keyring-d" vs. "gnome-keyring-daemon".
830 try:
831 cmdline = self.cmdline()
832 except (AccessDenied, ZombieProcess):
833 # Just pass and return the truncated name: it's better
834 # than nothing. Note: there are actual cases where a
835 # zombie process can return a name() but not a
836 # cmdline(), see:
837 # https://github.com/giampaolo/psutil/issues/2239
838 pass
839 else:
840 if cmdline:
841 extended_name = os.path.basename(cmdline[0])
842 if extended_name.startswith(name):
843 name = extended_name
844 self._name = name
845 self._proc._name = name
846 return name
847
848 @_use_prefetch
849 def exe(self) -> str:
850 """The process executable as an absolute path.
851
852 May also be an empty string. The return value is cached after
853 first call.
854 """
855
856 def guess_it(fallback):
857 # try to guess exe from cmdline[0] in absence of a native
858 # exe representation
859 cmdline = self.cmdline()
860 if cmdline and hasattr(os, 'access') and hasattr(os, 'X_OK'):
861 exe = cmdline[0] # the possible exe
862 # Attempt to guess only in case of an absolute path.
863 # It is not safe otherwise as the process might have
864 # changed cwd.
865 if (
866 os.path.isabs(exe)
867 and os.path.isfile(exe)
868 and os.access(exe, os.X_OK)
869 ):
870 return exe
871 if isinstance(fallback, AccessDenied):
872 raise fallback
873 return fallback
874
875 if self._exe is None:
876 try:
877 exe = self._proc.exe()
878 except AccessDenied as err:
879 return guess_it(fallback=err)
880 else:
881 if not exe:
882 # underlying implementation can legitimately return an
883 # empty string; if that's the case we don't want to
884 # raise AD while guessing from the cmdline
885 try:
886 exe = guess_it(fallback=exe)
887 except AccessDenied:
888 pass
889 self._exe = exe
890 return self._exe
891
892 @_use_prefetch
893 def cmdline(self) -> list[str]:
894 """The command line this process has been called with."""
895 return self._proc.cmdline()
896
897 @_use_prefetch
898 def status(self) -> ProcessStatus | str:
899 """The process current status as a `STATUS_` constant."""
900 try:
901 return self._proc.status()
902 except ZombieProcess:
903 return ProcessStatus.STATUS_ZOMBIE
904
905 @_use_prefetch
906 def username(self) -> str:
907 """The name of the user that owns the process.
908
909 On UNIX this is calculated by using the real process uid.
910 """
911 if POSIX:
912 if pwd is None:
913 # might happen if python was installed from sources
914 msg = "requires pwd module shipped with standard python"
915 raise ImportError(msg)
916 uids = self.uids()
917 if self._is_ad_value(uids):
918 return uids
919 real_uid = uids.real
920 try:
921 return pwd.getpwuid(real_uid).pw_name
922 except KeyError:
923 # the uid can't be resolved by the system
924 return str(real_uid)
925 else:
926 return self._proc.username()
927
928 @_use_prefetch
929 def create_time(self) -> float:
930 """The process creation time as a floating point number
931 expressed in seconds since the epoch (seconds since January 1,
932 1970, at midnight UTC).
933
934 The return value, which is cached after first call, is based on
935 the system clock, which means it may be affected by changes
936 such as manual adjustments or time synchronization (e.g. NTP).
937 """
938 if self._create_time is None:
939 self._create_time = self._proc.create_time()
940 return self._create_time
941
942 @_use_prefetch
943 def cwd(self) -> str:
944 """Process current working directory as an absolute path."""
945 return self._proc.cwd()
946
947 @_use_prefetch
948 def nice(self, value: int | None = None) -> int | None:
949 """Get or set process niceness (priority)."""
950 if value is None:
951 return self._proc.nice_get()
952 else:
953 self._raise_if_pid_reused()
954 self._proc.nice_set(value)
955
956 if POSIX:
957
958 @_use_prefetch
959 @memoize_when_activated
960 def uids(self) -> puids:
961 """Return process UIDs as a `(real, effective, saved)`
962 named tuple.
963 """
964 return self._proc.uids()
965
966 @_use_prefetch
967 def gids(self) -> pgids:
968 """Return process GIDs as a `(real, effective, saved)`
969 named tuple.
970 """
971 return self._proc.gids()
972
973 @_use_prefetch
974 def terminal(self) -> str | None:
975 """The terminal associated with this process, if any,
976 else None.
977 """
978 return self._proc.terminal()
979
980 @_use_prefetch
981 def num_fds(self) -> int:
982 """Return the number of file descriptors opened by this
983 process (POSIX only).
984 """
985 return self._proc.num_fds()
986
987 if hasattr(_psplatform.Process, "io_counters"):
988
989 @_use_prefetch
990 def io_counters(self) -> pio:
991 """Return process I/O statistics (primarily read and
992 written bytes).
993
994 Availability: Linux, Windows, BSD, AIX
995 """
996 return self._proc.io_counters()
997
998 if hasattr(_psplatform.Process, "ionice_get"):
999
1000 @_use_prefetch
1001 def ionice(
1002 self, ioclass: int | None = None, value: int | None = None
1003 ) -> pionice | ProcessIOPriority | None:
1004 """Get or set process I/O niceness (priority).
1005
1006 On Linux *ioclass* is one of the `IOPRIO_CLASS_*` constants.
1007 *value* is a number which goes from 0 to 7. The higher the
1008 value, the lower the I/O priority of the process.
1009
1010 On Windows only *ioclass* is used and it can be set to
1011 one of the `IOPRIO_*` constants.
1012
1013 Availability: Linux, Windows
1014 """
1015 if ioclass is None:
1016 if value is not None:
1017 msg = "'ioclass' argument must be specified"
1018 raise ValueError(msg)
1019 return self._proc.ionice_get()
1020 else:
1021 self._raise_if_pid_reused()
1022 return self._proc.ionice_set(ioclass, value)
1023
1024 if hasattr(_psplatform.Process, "rlimit"):
1025
1026 def rlimit(
1027 self,
1028 resource: int,
1029 limits: tuple[int, int] | None = None,
1030 ) -> tuple[int, int] | None:
1031 """Get or set process resource limits as a `(soft, hard)`
1032 tuple.
1033
1034 - resource: one of the `RLIMIT_*` constants.
1035 - limits: a `(soft, hard)` tuple (set).
1036
1037 See "man prlimit" for further info.
1038
1039 Availability: Linux, FreeBSD
1040 """
1041 if limits is not None:
1042 self._raise_if_pid_reused()
1043 return self._proc.rlimit(resource, limits)
1044
1045 if hasattr(_psplatform.Process, "cpu_affinity_get"):
1046
1047 @_use_prefetch
1048 def cpu_affinity(
1049 self, cpus: list[int] | None = None
1050 ) -> list[int] | None:
1051 """Get or set process CPU affinity.
1052
1053 If specified, *cpus* must be a list of CPUs for which you
1054 want to set the affinity (e.g. `[0, 1]`). If an empty list is
1055 passed, all eligible CPUs are assumed (and set).
1056
1057 Availability: Linux, Windows, FreeBSD
1058 """
1059 if cpus is None:
1060 return sorted(set(self._proc.cpu_affinity_get()))
1061 else:
1062 self._raise_if_pid_reused()
1063 if not cpus:
1064 if hasattr(self._proc, "_get_eligible_cpus"):
1065 cpus = self._proc._get_eligible_cpus()
1066 else:
1067 cpus = tuple(range(len(cpu_times(percpu=True))))
1068 self._proc.cpu_affinity_set(list(set(cpus)))
1069
1070 # Linux, FreeBSD, SunOS
1071 if hasattr(_psplatform.Process, "cpu_num"):
1072
1073 @_use_prefetch
1074 def cpu_num(self) -> int:
1075 """Return what CPU this process is currently running on.
1076
1077 The returned number should be <= `psutil.cpu_count()`.
1078 """
1079 return self._proc.cpu_num()
1080
1081 # All platforms has it, but maybe not in the future.
1082 if hasattr(_psplatform.Process, "environ"):
1083
1084 @_use_prefetch
1085 def environ(self) -> dict[str, str]:
1086 """The environment variables of the process as a dict.
1087
1088 Note: this might not reflect changes made after the process
1089 started.
1090 """
1091 return self._proc.environ()
1092
1093 if WINDOWS:
1094
1095 @_use_prefetch
1096 def num_handles(self) -> int:
1097 """Return the number of handles opened by this process
1098
1099 Availability: Windows
1100 """
1101 return self._proc.num_handles()
1102
1103 @_use_prefetch
1104 def num_ctx_switches(self) -> pctxsw:
1105 """Return the number of voluntary and involuntary context
1106 switches performed by this process.
1107 """
1108 return self._proc.num_ctx_switches()
1109
1110 @_use_prefetch
1111 def num_threads(self) -> int:
1112 """Return the number of threads used by this process."""
1113 return self._proc.num_threads()
1114
1115 if hasattr(_psplatform.Process, "threads"):
1116
1117 @_use_prefetch
1118 def threads(self) -> list[pthread]:
1119 """Return threads opened by process as a list of
1120 `(id, user_time, system_time)` named tuples.
1121
1122 On OpenBSD this method requires root access.
1123 """
1124 return self._proc.threads()
1125
1126 def children(self, recursive: bool = False) -> list[Process]:
1127 """Return the children of this process as a list of Process
1128 instances, preemptively checking whether PID has been reused.
1129
1130 If *recursive* is True return all the parent descendants.
1131
1132 Example (A == this process):
1133
1134 A ─┐
1135 │
1136 ├─ B (child) ─┐
1137 │ └─ X (grandchild) ─┐
1138 │ └─ Y (great grandchild)
1139 ├─ C (child)
1140 └─ D (child)
1141
1142 >>> import psutil
1143 >>> p = psutil.Process()
1144 >>> p.children()
1145 B, C, D
1146 >>> p.children(recursive=True)
1147 B, X, Y, C, D
1148
1149 Note that in the example above if process X disappears
1150 process Y won't be listed as the reference to process A
1151 is lost.
1152 """
1153 self._raise_if_pid_reused()
1154 ppid_map = _ppid_map()
1155 # Get a fresh (non-cached) ctime in case the system clock was
1156 # updated. TODO: use a monotonic ctime on platforms where it's
1157 # supported.
1158 proc_ctime = Process(self.pid).create_time()
1159 ret = []
1160 if not recursive:
1161 for pid, ppid in ppid_map.items():
1162 if ppid == self.pid:
1163 try:
1164 child = Process(pid)
1165 # if child happens to be older than its parent
1166 # (self) it means child's PID has been reused
1167 if proc_ctime <= child.create_time():
1168 ret.append(child)
1169 except (NoSuchProcess, ZombieProcess):
1170 pass
1171 else:
1172 # Construct a {pid: [child pids]} dict
1173 reverse_ppid_map = collections.defaultdict(list)
1174 for pid, ppid in ppid_map.items():
1175 reverse_ppid_map[ppid].append(pid)
1176 # Recursively traverse that dict, starting from self.pid,
1177 # such that we only call Process() on actual children
1178 seen = set()
1179 stack = [self.pid]
1180 while stack:
1181 pid = stack.pop()
1182 if pid in seen:
1183 # Since pids can be reused while the ppid_map is
1184 # constructed, there may be rare instances where
1185 # there's a cycle in the recorded process "tree".
1186 continue
1187 seen.add(pid)
1188 for child_pid in reverse_ppid_map[pid]:
1189 try:
1190 child = Process(child_pid)
1191 # if child happens to be older than its parent
1192 # (self) it means child's PID has been reused
1193 intime = proc_ctime <= child.create_time()
1194 if intime:
1195 ret.append(child)
1196 stack.append(child_pid)
1197 except (NoSuchProcess, ZombieProcess):
1198 pass
1199 return ret
1200
1201 @_use_prefetch
1202 def cpu_percent(self, interval: float | None = None) -> float:
1203 """Return a float representing the current process CPU
1204 utilization as a percentage.
1205
1206 When *interval* is 0.0 or None (default) compares process times
1207 to system CPU times elapsed since last call, returning
1208 immediately (non-blocking). That means that the first time
1209 this is called it will return a meaningless 0.0 value.
1210
1211 When *interval* is > 0.0 compares process times to system CPU
1212 times elapsed before and after the interval (blocking).
1213
1214 In this case is recommended for accuracy that this function
1215 be called with at least 0.1 seconds between calls.
1216
1217 A value > 100.0 can be returned in case of processes running
1218 multiple threads on different CPU cores.
1219
1220 The returned value is explicitly NOT split evenly between
1221 all available logical CPUs. This means that a busy loop process
1222 running on a system with 2 logical CPUs will be reported as
1223 having 100% CPU utilization instead of 50%.
1224
1225 Examples:
1226
1227 >>> import psutil
1228 >>> p = psutil.Process(os.getpid())
1229 >>> # blocking
1230 >>> p.cpu_percent(interval=1)
1231 2.0
1232 >>> # non-blocking (percentage since last call)
1233 >>> p.cpu_percent(interval=None)
1234 2.9
1235 >>>
1236 """
1237 blocking = interval is not None and interval > 0.0
1238 if interval is not None and interval < 0:
1239 msg = f"interval is not positive (got {interval!r})"
1240 raise ValueError(msg)
1241 num_cpus = cpu_count() or 1
1242
1243 def timer():
1244 return _timer() * num_cpus
1245
1246 if blocking:
1247 st1 = timer()
1248 pt1 = self._proc.cpu_times()
1249 time.sleep(interval)
1250 st2 = timer()
1251 pt2 = self._proc.cpu_times()
1252 else:
1253 st1 = self._last_sys_cpu_times
1254 pt1 = self._last_proc_cpu_times
1255 st2 = timer()
1256 pt2 = self._proc.cpu_times()
1257 if st1 is None or pt1 is None:
1258 self._last_sys_cpu_times = st2
1259 self._last_proc_cpu_times = pt2
1260 return 0.0
1261
1262 delta_proc = (pt2.user - pt1.user) + (pt2.system - pt1.system)
1263 delta_time = st2 - st1
1264 # reset values for next call in case of interval == None
1265 self._last_sys_cpu_times = st2
1266 self._last_proc_cpu_times = pt2
1267
1268 try:
1269 # This is the utilization split evenly between all CPUs.
1270 # E.g. a busy loop process on a 2-CPU-cores system at this
1271 # point is reported as 50% instead of 100%.
1272 overall_cpus_percent = (delta_proc / delta_time) * 100
1273 except ZeroDivisionError:
1274 # interval was too low
1275 return 0.0
1276 else:
1277 # Note 1:
1278 # in order to emulate "top" we multiply the value for the num
1279 # of CPU cores. This way the busy process will be reported as
1280 # having 100% (or more) usage.
1281 #
1282 # Note 2:
1283 # taskmgr.exe on Windows differs in that it will show 50%
1284 # instead.
1285 #
1286 # Note 3:
1287 # a percentage > 100 is legitimate as it can result from a
1288 # process with multiple threads running on different CPU
1289 # cores (top does the same), see:
1290 # http://stackoverflow.com/questions/1032357
1291 # https://github.com/giampaolo/psutil/issues/474
1292 single_cpu_percent = overall_cpus_percent * num_cpus
1293 return round(single_cpu_percent, 1)
1294
1295 @_use_prefetch
1296 @memoize_when_activated
1297 def cpu_times(self) -> pcputimes:
1298 """Return a `(user, system, children_user, children_system)`
1299 named tuple representing the accumulated process time,
1300 expressed in seconds.
1301
1302 Linux includes an additional `iowait` field.
1303
1304 On macOS and Windows `children_user` and `children_system`
1305 fields are always set to 0.
1306 """
1307 return self._proc.cpu_times()
1308
1309 @_use_prefetch
1310 @memoize_when_activated
1311 def memory_info(self) -> pmem:
1312 """Return a named tuple with variable fields depending on the
1313 platform, representing memory information about the process.
1314
1315 The portable fields available on all platforms are `rss` and `vms`.
1316
1317 All numbers are expressed in bytes.
1318 """
1319 return self._proc.memory_info()
1320
1321 @_use_prefetch
1322 @memoize_when_activated
1323 def memory_info_ex(self) -> pmem_ex:
1324 """Return a named tuple extending `memory_info()` with extra
1325 metrics.
1326
1327 All numbers are expressed in bytes.
1328 """
1329 base = self.memory_info()
1330 if self._is_ad_value(base):
1331 return base
1332 if hasattr(self._proc, "memory_info_ex"):
1333 extras = self._proc.memory_info_ex()
1334 return _ntp.pmem_ex(**base._asdict(), **extras)
1335 return base
1336
1337 # Linux, macOS, Windows
1338 if hasattr(_psplatform.Process, "memory_footprint"):
1339
1340 @_use_prefetch
1341 def memory_footprint(self) -> pfootprint:
1342 """Return a named tuple with USS memory, and on Linux also
1343 PSS and swap.
1344
1345 These values provide a more accurate representation of
1346 actual process memory usage.
1347
1348 USS is the memory unique to a process and which would
1349 be freed if the process was terminated right now.
1350
1351 It does so by passing through the whole process address. As
1352 such it usually requires higher user privileges than
1353 `memory_info()` or `memory_info_ex()` and is considerably
1354 slower.
1355 """
1356 return self._proc.memory_footprint()
1357
1358 # DEPRECATED
1359 def memory_full_info(self) -> pfullmem:
1360 """Return the same information as `memory_info()` plus
1361 `memory_footprint()` in a single named tuple.
1362
1363 DEPRECATED in 8.0.0. Use `memory_footprint()` instead.
1364 """
1365 msg = (
1366 "memory_full_info() is deprecated; use memory_footprint() instead"
1367 )
1368 warnings.warn(msg, DeprecationWarning, stacklevel=2)
1369 basic_mem = self.memory_info()
1370 if self._is_ad_value(basic_mem):
1371 return basic_mem
1372 if hasattr(self, "memory_footprint"):
1373 fp = self.memory_footprint()
1374 if self._is_ad_value(fp):
1375 return fp
1376 return _ntp.pfullmem(*basic_mem + fp)
1377 return _ntp.pfullmem(*basic_mem)
1378
1379 @_use_prefetch
1380 def memory_percent(self, memtype: str = "rss") -> float:
1381 """Compare process memory to total physical system memory and
1382 calculate process memory utilization as a percentage.
1383
1384 *memtype* argument is a string that dictates what type of
1385 process memory you want to compare against (defaults to "rss").
1386 The list of available strings can be obtained like this:
1387
1388 >>> psutil.Process().memory_info()._fields
1389 ('rss', 'vms', 'shared', 'text', 'lib', 'data', 'dirty', 'uss', 'pss')
1390 """
1391 valid_types = list(_ntp.pmem._fields)
1392 if hasattr(_ntp, "pmem_ex"):
1393 valid_types += [
1394 f for f in _ntp.pmem_ex._fields if f not in valid_types
1395 ]
1396 if hasattr(_ntp, "pfootprint"):
1397 valid_types += [
1398 f for f in _ntp.pfootprint._fields if f not in valid_types
1399 ]
1400 if memtype not in valid_types:
1401 msg = (
1402 f"invalid memtype {memtype!r}; valid types are"
1403 f" {tuple(valid_types)!r}"
1404 )
1405 raise ValueError(msg)
1406 if memtype in _ntp.pmem._fields:
1407 fun = self.memory_info
1408 elif (
1409 hasattr(_ntp, "pfootprint") and memtype in _ntp.pfootprint._fields
1410 ):
1411 fun = self.memory_footprint
1412 else:
1413 fun = self.memory_info_ex
1414 metrics = fun()
1415 if self._is_ad_value(metrics):
1416 return metrics
1417 value = getattr(metrics, memtype)
1418
1419 # use cached value if available
1420 total_phymem = _TOTAL_PHYMEM or virtual_memory().total
1421 if not total_phymem > 0:
1422 # we should never get here
1423 msg = (
1424 "can't calculate process memory percent because total physical"
1425 f" system memory is not positive ({total_phymem!r})"
1426 )
1427 raise ValueError(msg)
1428 return (value / float(total_phymem)) * 100
1429
1430 if hasattr(_psplatform.Process, "memory_maps"):
1431
1432 @_use_prefetch
1433 def memory_maps(
1434 self, grouped: bool = True
1435 ) -> list[pmmap_grouped] | list[pmmap_ext]:
1436 """Return process mapped memory regions as a list of named
1437 tuples whose fields are variable depending on the platform.
1438
1439 If *grouped* is True the mapped regions with the same 'path'
1440 are grouped together and the different memory fields are summed.
1441
1442 If *grouped* is False every mapped region is shown as a single
1443 entity and the named tuple will also include the mapped region's
1444 address space ('addr') and permission set ('perms').
1445 """
1446
1447 it = self._proc.memory_maps()
1448 if grouped:
1449 d = {}
1450 for tupl in it:
1451 path = tupl[2]
1452 nums = tupl[3:]
1453 try:
1454 d[path] = list(map(lambda x, y: x + y, d[path], nums))
1455 except KeyError:
1456 d[path] = nums
1457 return [_ntp.pmmap_grouped(path, *d[path]) for path in d]
1458 else:
1459 return [_ntp.pmmap_ext(*x) for x in it]
1460
1461 @_use_prefetch
1462 def page_faults(self) -> ppagefaults:
1463 """Return the number of page faults for this process as a
1464 `(minor, major)` named tuple.
1465
1466 - `minor` (a.k.a. *soft* faults): occur when a memory page is
1467 not currently mapped into the process address space, but is
1468 already present in physical RAM (e.g. a shared library page
1469 loaded by another process). The kernel resolves these without
1470 disk I/O.
1471
1472 - `major` (a.k.a. *hard* faults): occur when the page must be
1473 fetched from disk. These are expensive because they stall the
1474 process until I/O completes.
1475
1476 Both counters are cumulative since process creation.
1477 """
1478 return self._proc.page_faults()
1479
1480 @_use_prefetch
1481 def open_files(self) -> list[popenfile]:
1482 """Return files opened by process as a list of `(path, fd)`
1483 named tuples including the absolute file name and file
1484 descriptor number.
1485
1486 On Linux the named tuple also includes `position`, `mode` and
1487 `flags` fields.
1488 """
1489 return self._proc.open_files()
1490
1491 @_use_prefetch
1492 def net_connections(self, kind: str = "inet") -> list[pconn]:
1493 """Return socket connections opened by process as a list of
1494 `(fd, family, type, laddr, raddr, status)` named tuples.
1495
1496 The *kind* parameter filters for connections that match the
1497 following criteria:
1498
1499 +------------+----------------------------------------------------+
1500 | Kind Value | Connections using |
1501 +------------+----------------------------------------------------+
1502 | 'inet' | IPv4 and IPv6 |
1503 | 'inet4' | IPv4 |
1504 | 'inet6' | IPv6 |
1505 | 'tcp' | TCP |
1506 | 'tcp4' | TCP over IPv4 |
1507 | 'tcp6' | TCP over IPv6 |
1508 | 'udp' | UDP |
1509 | 'udp4' | UDP over IPv4 |
1510 | 'udp6' | UDP over IPv6 |
1511 | 'unix' | UNIX socket (both UDP and TCP protocols) |
1512 | 'all' | the sum of all the possible families and protocols |
1513 +------------+----------------------------------------------------+
1514 """
1515 _check_conn_kind(kind)
1516 return self._proc.net_connections(kind)
1517
1518 @_common.deprecated_method(replacement="net_connections")
1519 def connections(self, kind="inet") -> list[pconn]:
1520 return self.net_connections(kind=kind)
1521
1522 # --- signals
1523
1524 if POSIX:
1525
1526 def _send_signal(self, sig):
1527 assert not self.pid < 0, self.pid
1528 self._raise_if_pid_reused()
1529
1530 pid, ppid, name = self.pid, self._ppid, self._name
1531 if pid == 0:
1532 # see "man 2 kill"
1533 msg = (
1534 "preventing sending signal to process with PID 0 as it "
1535 "would affect every process in the process group of the "
1536 "calling process (os.getpid()) instead of PID 0"
1537 )
1538 raise ValueError(msg)
1539 try:
1540 os.kill(pid, sig)
1541 except ProcessLookupError as err:
1542 if OPENBSD and pid_exists(pid):
1543 # We do this because os.kill() lies in case of
1544 # zombie processes.
1545 raise ZombieProcess(pid, name, ppid) from err
1546 self._gone = True
1547 raise NoSuchProcess(pid, name) from err
1548 except PermissionError as err:
1549 raise AccessDenied(pid, name) from err
1550
1551 def send_signal(self, sig: int) -> None:
1552 """Send a signal *sig* to process, preemptively checking
1553 whether PID has been reused (see signal module constants).
1554
1555 On Windows only SIGTERM, CTRL_C_EVENT and CTRL_BREAK_EVENT
1556 are valid. SIGTERM is treated as an alias for `kill()`.
1557 """
1558 if POSIX:
1559 self._send_signal(sig)
1560 else: # pragma: no cover
1561 self._raise_if_pid_reused()
1562 if sig != signal.SIGTERM and not self.is_running():
1563 msg = "process no longer exists"
1564 raise NoSuchProcess(self.pid, self._name, msg=msg)
1565 self._proc.send_signal(sig)
1566
1567 def suspend(self) -> None:
1568 """Suspend process execution with SIGSTOP preemptively checking
1569 whether PID has been reused.
1570
1571 On Windows this has the effect of suspending all process threads.
1572 """
1573 if POSIX:
1574 self._send_signal(signal.SIGSTOP)
1575 else: # pragma: no cover
1576 self._raise_if_pid_reused()
1577 self._proc.suspend()
1578
1579 def resume(self) -> None:
1580 """Resume process execution with SIGCONT preemptively checking
1581 whether PID has been reused.
1582
1583 On Windows this has the effect of resuming all process threads.
1584 """
1585 if POSIX:
1586 self._send_signal(signal.SIGCONT)
1587 else: # pragma: no cover
1588 self._raise_if_pid_reused()
1589 self._proc.resume()
1590
1591 def terminate(self) -> None:
1592 """Terminate the process with SIGTERM preemptively checking
1593 whether PID has been reused.
1594
1595 On Windows this is an alias for `kill()`.
1596 """
1597 if POSIX:
1598 self._send_signal(signal.SIGTERM)
1599 else: # pragma: no cover
1600 self._raise_if_pid_reused()
1601 self._proc.kill()
1602
1603 def kill(self) -> None:
1604 """Kill the current process with SIGKILL preemptively checking
1605 whether PID has been reused.
1606 """
1607 if POSIX:
1608 self._send_signal(signal.SIGKILL)
1609 else: # pragma: no cover
1610 self._raise_if_pid_reused()
1611 self._proc.kill()
1612
1613 def wait(self, timeout: float | None = None) -> int | None:
1614 """Wait for process to terminate, and if process is a child
1615 of os.getpid(), also return its exit code, else None.
1616
1617 On Windows there's no such limitation (exit code is always
1618 returned).
1619
1620 If the process is already terminated, immediately return None
1621 instead of raising `NoSuchProcess`.
1622
1623 If *timeout* (in seconds) is specified and process is still
1624 alive, raise `TimeoutExpired`.
1625
1626 If *timeout=0* either return immediately or raise
1627 `TimeoutExpired` (non-blocking).
1628
1629 To wait for multiple Process objects use `psutil.wait_procs()`.
1630 """
1631 if self.pid == 0:
1632 msg = "can't wait for PID 0"
1633 raise ValueError(msg)
1634 if timeout is not None:
1635 if not isinstance(timeout, (int, float)):
1636 msg = f"timeout must be an int or float (got {type(timeout)})"
1637 raise TypeError(msg)
1638 if timeout < 0:
1639 msg = f"timeout must be positive or zero (got {timeout})"
1640 raise ValueError(msg)
1641
1642 if self._exitcode is not _SENTINEL:
1643 return self._exitcode
1644
1645 try:
1646 self._exitcode = self._proc.wait(timeout)
1647 except TimeoutExpired as err:
1648 exc = TimeoutExpired(timeout, pid=self.pid, name=self._name)
1649 raise exc from err
1650
1651 return self._exitcode
1652
1653
1654# The valid attr names which can be processed by Process.as_dict(attrs=...)
1655# and process_iter(attrs=...).
1656# fmt: off
1657Process.attrs = frozenset(
1658 x for x in dir(Process) if not x.startswith("_") and x not in
1659 {'send_signal', 'suspend', 'resume', 'terminate', 'kill', 'wait',
1660 'is_running', 'as_dict', 'parent', 'parents', 'children', 'rlimit',
1661 'connections', 'memory_full_info', 'oneshot', 'info', 'attrs'}
1662)
1663# fmt: on
1664
1665
1666# =====================================================================
1667# --- Popen class
1668# =====================================================================
1669
1670
1671class Popen(Process):
1672 """Same as `subprocess.Popen`, but in addition it provides all
1673 `Process` methods in a single class.
1674
1675 For the following methods which are common to both classes, psutil
1676 implementation takes precedence:
1677
1678 * `send_signal()`
1679 * `terminate()`
1680 * `kill()`
1681
1682 This is done in order to avoid killing another process in case its
1683 PID has been reused, fixing BPO-6973.
1684
1685 >>> import psutil
1686 >>> from subprocess import PIPE
1687 >>> p = psutil.Popen(["python", "-c", "print 'hi'"], stdout=PIPE)
1688 >>> p.name()
1689 'python3'
1690 >>> p.uids()
1691 user(real=1000, effective=1000, saved=1000)
1692 >>> p.username()
1693 'giampaolo'
1694 >>> p.communicate()
1695 ('hi', None)
1696 >>> p.terminate()
1697 >>> p.wait(timeout=2)
1698 0
1699 >>>
1700 """
1701
1702 def __init__(self, *args, **kwargs):
1703 # Explicitly avoid to raise NoSuchProcess in case the process
1704 # spawned by subprocess.Popen terminates too quickly, see:
1705 # https://github.com/giampaolo/psutil/issues/193
1706 self.__subproc = subprocess.Popen(*args, **kwargs)
1707 self._init(self.__subproc.pid, _ignore_nsp=True)
1708
1709 def __dir__(self):
1710 return sorted(set(dir(Popen) + dir(subprocess.Popen)))
1711
1712 def __enter__(self) -> Popen:
1713 if hasattr(self.__subproc, '__enter__'):
1714 self.__subproc.__enter__()
1715 return self
1716
1717 def __exit__(self, *args, **kwargs):
1718 if hasattr(self.__subproc, '__exit__'):
1719 return self.__subproc.__exit__(*args, **kwargs)
1720 else:
1721 if self.stdout:
1722 self.stdout.close()
1723 if self.stderr:
1724 self.stderr.close()
1725 try:
1726 # Flushing a BufferedWriter may raise an error.
1727 if self.stdin:
1728 self.stdin.close()
1729 finally:
1730 # Wait for the process to terminate, to avoid zombies.
1731 self.wait()
1732
1733 def __getattribute__(self, name):
1734 try:
1735 return object.__getattribute__(self, name)
1736 except AttributeError:
1737 try:
1738 return object.__getattribute__(self.__subproc, name)
1739 except AttributeError:
1740 msg = f"{self.__class__!r} has no attribute {name!r}"
1741 raise AttributeError(msg) from None
1742
1743 def wait(self, timeout: float | None = None) -> int | None:
1744 if self.__subproc.returncode is not None:
1745 return self.__subproc.returncode
1746 ret = super().wait(timeout)
1747 self.__subproc.returncode = ret
1748 return ret
1749
1750
1751# =====================================================================
1752# --- system processes related functions
1753# =====================================================================
1754
1755
1756def pids() -> list[int]:
1757 """Return a list of current running PIDs."""
1758 global _LOWEST_PID
1759 ret = sorted(_psplatform.pids())
1760 _LOWEST_PID = ret[0]
1761 return ret
1762
1763
1764def pid_exists(pid: int) -> bool:
1765 """Return True if *pid* exists in the current process list.
1766
1767 This is faster than doing `pid in psutil.pids()` and should be
1768 preferred.
1769 """
1770 if pid < 0:
1771 return False
1772 elif pid == 0 and POSIX:
1773 # On POSIX we use os.kill() to determine PID existence.
1774 # According to "man 2 kill" PID 0 has a special meaning
1775 # though: it refers to <<every process in the process
1776 # group of the calling process>> and that is not we want
1777 # to do here.
1778 return pid in pids()
1779 else:
1780 return _psplatform.pid_exists(pid)
1781
1782
1783_pmap = {}
1784_pids_reused = set()
1785
1786
1787def process_iter(
1788 attrs: Collection[str] | None = None, ad_value: Any = None
1789) -> Iterator[Process]:
1790 """Return a generator yielding a `Process` instance for all
1791 running processes.
1792
1793 Every new `Process` instance is only created once and then cached
1794 into an internal table which is updated every time this is used.
1795 Cache can optionally be cleared via `process_iter.cache_clear()`.
1796
1797 The sorting order in which processes are yielded is based on
1798 their PIDs.
1799
1800 *attrs* and *ad_value* have the same meaning as in
1801 `Process.as_dict()`.
1802
1803 If *attrs* is specified, `Process.as_dict()` is called and the
1804 results are cached, so that subsequent method calls (e.g.
1805 `p.name()`) return cached values. Use `attrs=Process.attrs` to
1806 retrieve all process info (slow).
1807
1808 If a method raises `AccessDenied` during pre-fetch, it will return
1809 *ad_value* (default None) instead of raising.
1810 """
1811 global _pmap
1812
1813 def add(pid):
1814 proc = Process(pid)
1815 pmap[proc.pid] = proc
1816 return proc
1817
1818 def remove(pid):
1819 pmap.pop(pid, None)
1820
1821 if attrs is not None:
1822 if attrs == []: # deprecated in 8.0.0
1823 msg = (
1824 "process_iter(attrs=[]) is deprecated; use "
1825 "process_iter(attrs=Process.attrs) to retrieve all attributes"
1826 )
1827 warnings.warn(msg, DeprecationWarning, stacklevel=2)
1828 elif not attrs:
1829 # as_dict() will resolve an empty list|tuple|set to "all
1830 # attribute names", but it's ambiguous and should be
1831 # signaled.
1832 msg = (
1833 f"process_iter(attrs={attrs}) is ambiguous; use "
1834 "process_iter(attrs=Process.attrs) to retrieve all attributes"
1835 )
1836 warnings.warn(msg, UserWarning, stacklevel=2)
1837
1838 pmap = _pmap.copy()
1839 a = set(pids())
1840 b = set(pmap)
1841 new_pids = a - b
1842 gone_pids = b - a
1843 for pid in gone_pids:
1844 remove(pid)
1845 while _pids_reused:
1846 pid = _pids_reused.pop()
1847 debug(f"refreshing Process instance for reused PID {pid}")
1848 remove(pid)
1849 try:
1850 ls = sorted(list(pmap.items()) + list(dict.fromkeys(new_pids).items()))
1851 for pid, proc in ls:
1852 try:
1853 if proc is None: # new process
1854 proc = add(pid)
1855 proc._prefetch = {} # clear cache
1856 proc._ad_value = _SENTINEL
1857 if attrs is not None:
1858 proc._prefetch = proc.as_dict(
1859 attrs=attrs, ad_value=ad_value
1860 )
1861 proc._ad_value = ad_value
1862 yield proc
1863 except ZombieProcess:
1864 if proc is not None:
1865 yield proc # zombie processes are still valid
1866 except NoSuchProcess:
1867 remove(pid)
1868 finally:
1869 _pmap = pmap
1870
1871
1872process_iter.cache_clear = lambda: _pmap.clear() # noqa: PLW0108
1873process_iter.cache_clear.__doc__ = "Clear process_iter() internal cache."
1874
1875
1876def wait_procs(
1877 procs: list[Process],
1878 timeout: float | None = None,
1879 callback: Callable[[Process], None] | None = None,
1880) -> tuple[list[Process], list[Process]]:
1881 """Convenience function which waits for a list of processes to
1882 terminate.
1883
1884 Return a `(gone, alive)` tuple indicating which processes
1885 are gone and which ones are still alive.
1886
1887 The gone ones will have a new `returncode` attribute indicating
1888 process exit status (may be None).
1889
1890 *callback* is a function which gets called every time a process
1891 terminates (a `Process` instance is passed as callback argument).
1892
1893 Function will return as soon as all processes terminate or when
1894 *timeout* occurs.
1895
1896 Differently from `Process.wait()` it will not raise `TimeoutExpired` if
1897 *timeout* occurs.
1898
1899 Typical use case is:
1900
1901 - send SIGTERM to a list of processes
1902 - give them some time to terminate
1903 - send SIGKILL to those ones which are still alive
1904
1905 Example:
1906
1907 >>> def on_terminate(proc):
1908 ... print("process {} terminated".format(proc))
1909 ...
1910 >>> for p in procs:
1911 ... p.terminate()
1912 ...
1913 >>> gone, alive = wait_procs(procs, timeout=3, callback=on_terminate)
1914 >>> for p in alive:
1915 ... p.kill()
1916 """
1917
1918 def check_gone(proc, timeout):
1919 try:
1920 returncode = proc.wait(timeout=timeout)
1921 except (TimeoutExpired, subprocess.TimeoutExpired):
1922 pass
1923 else:
1924 if returncode is not None or not proc.is_running():
1925 # Set new Process instance attribute.
1926 proc.returncode = returncode
1927 gone.add(proc)
1928 if callback is not None:
1929 callback(proc)
1930
1931 if timeout is not None and not timeout >= 0:
1932 msg = f"timeout must be a positive integer, got {timeout}"
1933 raise ValueError(msg)
1934 if callback is not None and not callable(callback):
1935 msg = f"callback {callback!r} is not a callable"
1936 raise TypeError(msg)
1937
1938 gone = set()
1939 alive = set(procs)
1940 if timeout is not None:
1941 deadline = _timer() + timeout
1942
1943 while alive:
1944 if timeout is not None and timeout <= 0:
1945 break
1946 for proc in alive:
1947 # Make sure that every complete iteration (all processes)
1948 # will last max 1 sec.
1949 # We do this because we don't want to wait too long on a
1950 # single process: in case it terminates too late other
1951 # processes may disappear in the meantime and their PID
1952 # reused.
1953 max_timeout = 1.0 / len(alive)
1954 if timeout is not None:
1955 timeout = min((deadline - _timer()), max_timeout)
1956 if timeout <= 0:
1957 break
1958 check_gone(proc, timeout)
1959 else:
1960 check_gone(proc, max_timeout)
1961 alive = alive - gone # noqa: PLR6104
1962
1963 if alive:
1964 # Last attempt over processes survived so far.
1965 # timeout == 0 won't make this function wait any further.
1966 for proc in alive:
1967 check_gone(proc, 0)
1968 alive = alive - gone # noqa: PLR6104
1969
1970 return (list(gone), list(alive))
1971
1972
1973# =====================================================================
1974# --- CPU related functions
1975# =====================================================================
1976
1977
1978def cpu_count(logical: bool = True) -> int | None:
1979 """Return the number of logical CPUs in the system (same as
1980 `os.cpu_count()`).
1981
1982 If *logical* is False return the number of physical cores only
1983 (e.g. hyper thread CPUs are excluded).
1984
1985 Return None if undetermined.
1986
1987 The return value is cached after first call.
1988 If desired cache can be cleared like this:
1989
1990 >>> psutil.cpu_count.cache_clear()
1991 """
1992 if logical:
1993 ret = _psplatform.cpu_count_logical()
1994 else:
1995 ret = _psplatform.cpu_count_cores()
1996 if ret is not None and ret < 1:
1997 ret = None
1998 return ret
1999
2000
2001def cpu_times(percpu: bool = False) -> scputimes | list[scputimes]:
2002 """Return system-wide CPU times as a named tuple.
2003
2004 Every CPU time represents the seconds the CPU has spent in the
2005 given mode:
2006
2007 - `user`
2008 - `system`
2009 - `idle`
2010 - `nice` (UNIX)
2011 - `iowait` (Linux)
2012 - `irq` (Linux, FreeBSD)
2013 - `softirq` (Linux)
2014 - `steal` (Linux)
2015 - `guest` (Linux)
2016 - `guest_nice` (Linux)
2017 - `dpc` (Windows)
2018
2019 When *percpu* is True return a list of named tuples for each
2020 logical CPU. First element of the list refers to first CPU, second
2021 element to second CPU and so on. The order of the list is
2022 consistent across calls.
2023 """
2024 if not percpu:
2025 return _psplatform.cpu_times()
2026 else:
2027 return _psplatform.per_cpu_times()
2028
2029
2030try:
2031 _last_cpu_times = {threading.current_thread().ident: cpu_times()}
2032except Exception: # noqa: BLE001
2033 # Don't want to crash at import time.
2034 _last_cpu_times = {}
2035
2036try:
2037 _last_per_cpu_times = {
2038 threading.current_thread().ident: cpu_times(percpu=True)
2039 }
2040except Exception: # noqa: BLE001
2041 # Don't want to crash at import time.
2042 _last_per_cpu_times = {}
2043
2044
2045def _cpu_tot_time(times):
2046 """Given a `cpu_time()` named tuple calculates the total CPU time
2047 (including idle time).
2048 """
2049 tot = sum(times)
2050 if LINUX:
2051 # On Linux guest times are already accounted in "user" or
2052 # "nice" times, so we subtract them from total.
2053 # Htop does the same. References:
2054 # https://github.com/giampaolo/psutil/pull/940
2055 # http://unix.stackexchange.com/questions/178045
2056 # https://github.com/torvalds/linux/blob/447976ef4/kernel/sched/cputime.c#L158
2057 tot -= times.guest
2058 tot -= times.guest_nice
2059 return tot
2060
2061
2062def _cpu_busy_time(times):
2063 """Given a `cpu_time()` named tuple calculates the busy CPU time by
2064 subtracting all idle CPU times.
2065 """
2066 busy = _cpu_tot_time(times)
2067 busy -= times.idle
2068 # Linux: "iowait" is time during which the CPU does not do anything
2069 # (waits for IO to complete). On Linux IO wait is *not* accounted
2070 # in "idle" time so we subtract it. Htop does the same.
2071 # References:
2072 # https://github.com/torvalds/linux/blob/447976ef4/kernel/sched/cputime.c#L244
2073 busy -= getattr(times, "iowait", 0)
2074 return busy
2075
2076
2077def _cpu_times_deltas(t1, t2):
2078 assert t1._fields == t2._fields, (t1, t2)
2079 field_deltas = []
2080 for field in _ntp.scputimes._fields:
2081 field_delta = getattr(t2, field) - getattr(t1, field)
2082 # CPU times are always supposed to increase over time
2083 # or at least remain the same and that's because time
2084 # cannot go backwards.
2085 # Surprisingly sometimes this might not be the case (at
2086 # least on Windows and Linux), see:
2087 # https://github.com/giampaolo/psutil/issues/392
2088 # https://github.com/giampaolo/psutil/issues/645
2089 # https://github.com/giampaolo/psutil/issues/1210
2090 # Trim negative deltas to zero to ignore decreasing fields.
2091 # top does the same. Reference:
2092 # https://gitlab.com/procps-ng/procps/blob/v3.3.12/top/top.c#L5063
2093 field_delta = max(0, field_delta)
2094 field_deltas.append(field_delta)
2095 return _ntp.scputimes(*field_deltas)
2096
2097
2098def cpu_percent(
2099 interval: float | None = None, percpu: bool = False
2100) -> float | list[float]:
2101 """Return a float representing the current system-wide CPU
2102 utilization as a percentage.
2103
2104 When *interval* is > 0.0 compares system CPU times elapsed before
2105 and after the interval (blocking).
2106
2107 When *interval* is 0.0 or None compares system CPU times elapsed
2108 since last call or module import, returning immediately (non
2109 blocking). That means the first time this is called it will
2110 return a meaningless 0.0 value which you should ignore.
2111 In this case is recommended for accuracy that this function be
2112 called with at least 0.1 seconds between calls.
2113
2114 When *percpu* is True returns a list of floats representing the
2115 utilization as a percentage for each CPU.
2116 First element of the list refers to first CPU, second element
2117 to second CPU and so on.
2118 The order of the list is consistent across calls.
2119
2120 Examples:
2121
2122 >>> # blocking, system-wide
2123 >>> psutil.cpu_percent(interval=1)
2124 2.0
2125 >>>
2126 >>> # blocking, per-cpu
2127 >>> psutil.cpu_percent(interval=1, percpu=True)
2128 [2.0, 1.0]
2129 >>>
2130 >>> # non-blocking (percentage since last call)
2131 >>> psutil.cpu_percent(interval=None)
2132 2.9
2133 >>>
2134 """
2135 tid = threading.current_thread().ident
2136 blocking = interval is not None and interval > 0.0
2137 if interval is not None and interval < 0:
2138 msg = f"interval is not positive (got {interval})"
2139 raise ValueError(msg)
2140
2141 def calculate(t1, t2):
2142 times_delta = _cpu_times_deltas(t1, t2)
2143 all_delta = _cpu_tot_time(times_delta)
2144 busy_delta = _cpu_busy_time(times_delta)
2145
2146 try:
2147 busy_perc = (busy_delta / all_delta) * 100
2148 except ZeroDivisionError:
2149 return 0.0
2150 else:
2151 return round(busy_perc, 1)
2152
2153 # system-wide usage
2154 if not percpu:
2155 if blocking:
2156 t1 = cpu_times()
2157 time.sleep(interval)
2158 else:
2159 t1 = _last_cpu_times.get(tid) or cpu_times()
2160 _last_cpu_times[tid] = cpu_times()
2161 return calculate(t1, _last_cpu_times[tid])
2162 # per-cpu usage
2163 else:
2164 ret = []
2165 if blocking:
2166 tot1 = cpu_times(percpu=True)
2167 time.sleep(interval)
2168 else:
2169 tot1 = _last_per_cpu_times.get(tid) or cpu_times(percpu=True)
2170 _last_per_cpu_times[tid] = cpu_times(percpu=True)
2171 for t1, t2 in zip(tot1, _last_per_cpu_times[tid]):
2172 ret.append(calculate(t1, t2))
2173 return ret
2174
2175
2176# Use a separate dict for cpu_times_percent(), so it's independent from
2177# cpu_percent() and they can both be used within the same program.
2178_last_cpu_times_2 = _last_cpu_times.copy()
2179_last_per_cpu_times_2 = _last_per_cpu_times.copy()
2180
2181
2182def cpu_times_percent(
2183 interval: float | None = None, percpu: bool = False
2184) -> scputimes | list[scputimes]:
2185 """Same as `cpu_percent()`, but provides utilization percentages
2186 for each specific CPU time as is returned by `cpu_times()`.
2187
2188 For instance, on Linux we'll get:
2189
2190 >>> cpu_times_percent()
2191 cpupercent(user=4.8, nice=0.0, system=4.8, idle=90.5, iowait=0.0,
2192 irq=0.0, softirq=0.0, steal=0.0, guest=0.0, guest_nice=0.0)
2193 >>>
2194
2195 *interval* and *percpu* arguments have the same meaning as in
2196 `cpu_percent()`.
2197 """
2198 tid = threading.current_thread().ident
2199 blocking = interval is not None and interval > 0.0
2200 if interval is not None and interval < 0:
2201 msg = f"interval is not positive (got {interval!r})"
2202 raise ValueError(msg)
2203
2204 def calculate(t1, t2):
2205 nums = []
2206 times_delta = _cpu_times_deltas(t1, t2)
2207 all_delta = _cpu_tot_time(times_delta)
2208 # "scale" is the value to multiply each delta with to get percentages.
2209 # We use "max" to avoid division by zero (if all_delta is 0, then all
2210 # fields are 0 so percentages will be 0 too. all_delta cannot be a
2211 # fraction because cpu times are integers)
2212 scale = 100.0 / max(1, all_delta)
2213 for field_delta in times_delta:
2214 field_perc = field_delta * scale
2215 field_perc = round(field_perc, 1)
2216 # make sure we don't return negative values or values over 100%
2217 field_perc = min(max(0.0, field_perc), 100.0)
2218 nums.append(field_perc)
2219 return _ntp.scputimes(*nums)
2220
2221 # system-wide usage
2222 if not percpu:
2223 if blocking:
2224 t1 = cpu_times()
2225 time.sleep(interval)
2226 else:
2227 t1 = _last_cpu_times_2.get(tid) or cpu_times()
2228 _last_cpu_times_2[tid] = cpu_times()
2229 return calculate(t1, _last_cpu_times_2[tid])
2230 # per-cpu usage
2231 else:
2232 ret = []
2233 if blocking:
2234 tot1 = cpu_times(percpu=True)
2235 time.sleep(interval)
2236 else:
2237 tot1 = _last_per_cpu_times_2.get(tid) or cpu_times(percpu=True)
2238 _last_per_cpu_times_2[tid] = cpu_times(percpu=True)
2239 for t1, t2 in zip(tot1, _last_per_cpu_times_2[tid]):
2240 ret.append(calculate(t1, t2))
2241 return ret
2242
2243
2244def cpu_stats() -> scpustats:
2245 """Return CPU statistics."""
2246 return _psplatform.cpu_stats()
2247
2248
2249if hasattr(_psplatform, "cpu_freq"):
2250
2251 def cpu_freq(percpu: bool = False) -> scpufreq | list[scpufreq] | None:
2252 """Return CPU frequency as a named tuple including current,
2253 min and max frequency expressed in Mhz.
2254
2255 If *percpu* is True and the system supports per-cpu frequency
2256 retrieval (Linux and FreeBSD), a list of frequencies is
2257 returned for each CPU. If not, a list with one element is
2258 returned.
2259 """
2260 ret = _psplatform.cpu_freq()
2261 if percpu:
2262 return ret
2263 else:
2264 num_cpus = float(len(ret))
2265 if num_cpus == 0:
2266 return None
2267 elif num_cpus == 1:
2268 return ret[0]
2269 else:
2270 currs, mins, maxs = 0.0, 0.0, 0.0
2271 set_none = False
2272 for cpu in ret:
2273 currs += cpu.current
2274 # On FreeBSD min/max are None if the sysctl value
2275 # can't be parsed.
2276 if cpu.min is None or cpu.max is None:
2277 set_none = True
2278 continue
2279 mins += cpu.min
2280 maxs += cpu.max
2281
2282 current = currs / num_cpus
2283
2284 if set_none:
2285 min_ = max_ = None
2286 else:
2287 min_ = mins / num_cpus
2288 max_ = maxs / num_cpus
2289
2290 return _ntp.scpufreq(current, min_, max_)
2291
2292 __all__.append("cpu_freq")
2293
2294
2295def getloadavg() -> tuple[float, float, float]:
2296 """Return the average system load over the last 1, 5 and 15 minutes
2297 as a tuple.
2298
2299 On Windows this is emulated by using a Windows API that spawns a
2300 thread which keeps running in background and updates results every
2301 5 seconds, mimicking the UNIX behavior.
2302 """
2303 if hasattr(os, "getloadavg"):
2304 return os.getloadavg()
2305 else:
2306 return _psplatform.getloadavg()
2307
2308
2309# =====================================================================
2310# --- system memory related functions
2311# =====================================================================
2312
2313
2314def virtual_memory() -> svmem:
2315 """Return statistics about system memory usage as a named tuple.
2316
2317 The fields vary by platform (see official doc), but the following
2318 are present on all platforms:
2319
2320 - total:
2321 total physical memory available
2322
2323 - available:
2324 the memory that can be given instantly to processes without the
2325 system going into swap.
2326 This is calculated by summing different memory values depending
2327 on the platform and it is supposed to be used to monitor actual
2328 memory usage in a cross platform fashion.
2329
2330 - percent:
2331 the percentage usage calculated as `(total - available) / total * 100`
2332
2333 - used:
2334 memory used, calculated differently depending on the platform and
2335 designed for informational purposes only
2336
2337 - free:
2338 memory not being used at all (zeroed) that is readily available;
2339 note that this doesn't reflect the actual memory available
2340 (use 'available' instead)
2341
2342 The sum of `used` and `available` does not necessarily equal `total`.
2343
2344 On Windows `available` and `free` are the same.
2345 """
2346 global _TOTAL_PHYMEM
2347 ret = _psplatform.virtual_memory()
2348 # cached for later use in Process.memory_percent()
2349 _TOTAL_PHYMEM = ret.total
2350 return ret
2351
2352
2353def swap_memory() -> sswap:
2354 """Return system swap memory statistics as a named tuple including
2355 the following fields:
2356
2357 - total: total swap memory in bytes
2358 - used: used swap memory in bytes
2359 - free: free swap memory in bytes
2360 - percent: the percentage usage
2361 - sin: no. of bytes the system has swapped in from disk (cumulative)
2362 - sout: no. of bytes the system has swapped out from disk (cumulative)
2363
2364 `sin` and `sout` on Windows are meaningless and always set to 0.
2365 """
2366 return _psplatform.swap_memory()
2367
2368
2369# =====================================================================
2370# --- disks/partitions related functions
2371# =====================================================================
2372
2373
2374def disk_usage(path: str) -> sdiskusage:
2375 """Return disk usage statistics about the given *path* as a
2376 named tuple including total, used and free space expressed in bytes
2377 plus the percentage usage.
2378 """
2379 return _psplatform.disk_usage(path)
2380
2381
2382def disk_partitions(all: bool = False) -> list[sdiskpart]:
2383 """Return mounted partitions as a list of
2384 (device, mountpoint, fstype, opts) named tuple.
2385
2386 `opts` field is a raw string separated by commas indicating mount
2387 options which may vary depending on the platform.
2388
2389 If *all* parameter is False return physical devices only and ignore
2390 all others.
2391 """
2392 return _psplatform.disk_partitions(all)
2393
2394
2395def disk_io_counters(
2396 perdisk: bool = False, nowrap: bool = True
2397) -> sdiskio | dict[str, sdiskio] | None:
2398 """Return system disk I/O statistics as a named tuple including
2399 the following fields:
2400
2401 - read_count: number of reads
2402 - write_count: number of writes
2403 - read_bytes: number of bytes read
2404 - write_bytes: number of bytes written
2405 - read_time: (not NetBSD, OpenBSD) time spent reading from
2406 disk (in ms)
2407 - write_time: (not NetBSD, OpenBSD) time spent writing to
2408 disk (in ms)
2409
2410 Platform specific:
2411
2412 - busy_time: (Linux, FreeBSD) time spent doing actual I/Os (in ms)
2413 - read_merged_count (Linux): number of merged reads
2414 - write_merged_count (Linux): number of merged writes
2415
2416 If *perdisk* is True return the same information for every
2417 physical disk as a dictionary with partition names as the keys.
2418
2419 If *nowrap* is True (default), counters that overflow and wrap to
2420 zero are automatically adjusted so they never decrease (this can
2421 happen on very busy or long-lived systems).
2422 `disk_io_counters.cache_clear()` can be used to invalidate the
2423 *nowrap* cache.
2424 """
2425 kwargs = dict(perdisk=perdisk) if LINUX else {}
2426 rawdict = _psplatform.disk_io_counters(**kwargs)
2427 if not rawdict:
2428 return {} if perdisk else None
2429 if nowrap:
2430 rawdict = _wrap_numbers(rawdict, 'psutil.disk_io_counters')
2431 if perdisk:
2432 for disk, fields in rawdict.items():
2433 rawdict[disk] = _ntp.sdiskio(*fields)
2434 return rawdict
2435 else:
2436 return _ntp.sdiskio(*(sum(x) for x in zip(*rawdict.values())))
2437
2438
2439disk_io_counters.cache_clear = functools.partial(
2440 _wrap_numbers.cache_clear, 'psutil.disk_io_counters'
2441)
2442disk_io_counters.cache_clear.__doc__ = "Clears nowrap argument cache"
2443
2444
2445# =====================================================================
2446# --- network related functions
2447# =====================================================================
2448
2449
2450def net_io_counters(
2451 pernic: bool = False, nowrap: bool = True
2452) -> snetio | dict[str, snetio] | None:
2453 """Return network I/O statistics as a named tuple including
2454 the following fields:
2455
2456 - bytes_sent: number of bytes sent
2457 - bytes_recv: number of bytes received
2458 - packets_sent: number of packets sent
2459 - packets_recv: number of packets received
2460 - errin: total number of errors while receiving
2461 - errout: total number of errors while sending
2462 - dropin: total number of incoming packets which were dropped
2463 - dropout: total number of outgoing packets which were dropped
2464 (always 0 on macOS and BSD)
2465
2466 If *pernic* is True return the same information for every
2467 network interface as a dictionary with interface names as the
2468 keys.
2469
2470 If *nowrap* is True (default), counters that overflow and wrap to
2471 zero are automatically adjusted so they never decrease (this can
2472 happen on very busy or long-lived systems).
2473 `net_io_counters.cache_clear()` can be used to invalidate the
2474 *nowrap* cache.
2475 """
2476 rawdict = _psplatform.net_io_counters()
2477 if not rawdict:
2478 return {} if pernic else None
2479 if nowrap:
2480 rawdict = _wrap_numbers(rawdict, 'psutil.net_io_counters')
2481 if pernic:
2482 for nic, fields in rawdict.items():
2483 rawdict[nic] = _ntp.snetio(*fields)
2484 return rawdict
2485 else:
2486 return _ntp.snetio(*[sum(x) for x in zip(*rawdict.values())])
2487
2488
2489net_io_counters.cache_clear = functools.partial(
2490 _wrap_numbers.cache_clear, 'psutil.net_io_counters'
2491)
2492net_io_counters.cache_clear.__doc__ = "Clears nowrap argument cache"
2493
2494
2495def net_connections(kind: str = 'inet') -> list[sconn]:
2496 """Return system-wide socket connections as a list of
2497 (fd, family, type, laddr, raddr, status, pid) named tuples.
2498
2499 In case of limited privileges `fd` and `pid` may be set to -1
2500 and None respectively.
2501
2502 The *kind* parameter filters for connections that fit the
2503 following criteria:
2504
2505 +------------+----------------------------------------------------+
2506 | Kind Value | Connections using |
2507 +------------+----------------------------------------------------+
2508 | 'inet' | IPv4 and IPv6 |
2509 | 'inet4' | IPv4 |
2510 | 'inet6' | IPv6 |
2511 | 'tcp' | TCP |
2512 | 'tcp4' | TCP over IPv4 |
2513 | 'tcp6' | TCP over IPv6 |
2514 | 'udp' | UDP |
2515 | 'udp4' | UDP over IPv4 |
2516 | 'udp6' | UDP over IPv6 |
2517 | 'unix' | UNIX socket (both UDP and TCP protocols) |
2518 | 'all' | the sum of all the possible families and protocols |
2519 +------------+----------------------------------------------------+
2520
2521 On macOS this function requires root privileges.
2522 """
2523 _check_conn_kind(kind)
2524 return _psplatform.net_connections(kind)
2525
2526
2527def net_if_addrs() -> dict[str, list[snicaddr]]:
2528 """Return a dictionary mapping each NIC (Network Interface Card) to
2529 a list of named tuples representing its addresses. Multiple
2530 addresses of the same family can exist per interface.
2531
2532 The named tuple includes 5 fields (addresses may be None):
2533
2534 - family: the address family, either `AF_INET`, `AF_INET6`,
2535 `psutil.AF_LINK` (a MAC address) or `AF_UNSPEC` (a virtual or
2536 unconfigured NIC).
2537 - address: the primary NIC address
2538 - netmask: the netmask address
2539 - broadcast: the broadcast address; always None on Windows
2540 - ptp: a "point to point" address (typically a VPN); always None on
2541 Windows
2542 """
2543 rawlist = _psplatform.net_if_addrs()
2544 rawlist.sort(key=lambda x: x[1]) # sort by family
2545 ret = collections.defaultdict(list)
2546 for name, fam, addr, mask, broadcast, ptp in rawlist:
2547 try:
2548 fam = socket.AddressFamily(fam)
2549 except ValueError:
2550 if WINDOWS and fam == -1:
2551 fam = _psplatform.AF_LINK
2552 elif (
2553 hasattr(_psplatform, "AF_LINK") and fam == _psplatform.AF_LINK
2554 ):
2555 # Linux defines AF_LINK as an alias for AF_PACKET.
2556 # We re-set the family here so that repr(family)
2557 # will show AF_LINK rather than AF_PACKET
2558 fam = _psplatform.AF_LINK
2559
2560 if fam == _psplatform.AF_LINK:
2561 # The underlying C function may return an incomplete MAC
2562 # address in which case we fill it with null bytes, see:
2563 # https://github.com/giampaolo/psutil/issues/786
2564 separator = ":" if POSIX else "-"
2565 while addr.count(separator) < 5:
2566 addr += f"{separator}00"
2567
2568 nt = _ntp.snicaddr(fam, addr, mask, broadcast, ptp)
2569
2570 # On Windows broadcast is None, so we determine it via
2571 # ipaddress module. On POSIX a /32 has no broadcast address,
2572 # but getifaddrs() hands back the local address.
2573 if nt.netmask and (
2574 fam == socket.AF_INET or (WINDOWS and fam == socket.AF_INET6)
2575 ):
2576 try:
2577 calculated = _common.broadcast_addr(nt)
2578 except Exception as err: # noqa: BLE001
2579 warn(f"broadcast_addr() failed: {err!r}")
2580 else:
2581 if calculated is None:
2582 nt = nt._replace(broadcast=None)
2583 elif WINDOWS:
2584 nt = nt._replace(broadcast=calculated)
2585
2586 ret[name].append(nt)
2587
2588 return dict(ret)
2589
2590
2591def net_if_stats() -> dict[str, snicstats]:
2592 """Return information about each NIC (network interface card)
2593 installed on the system as a dictionary whose keys are the
2594 NIC names and value is a named tuple with the following fields:
2595
2596 - isup: whether the interface is up (bool)
2597 - duplex: can be either `NIC_DUPLEX_FULL`, `NIC_DUPLEX_HALF` or
2598 `NIC_DUPLEX_UNKNOWN`
2599 - speed: the NIC speed expressed in mega bits (MB); if it can't
2600 be determined (e.g. 'localhost') it will be set to 0.
2601 - mtu: the maximum transmission unit expressed in bytes.
2602 - flags: a string of comma-separated flags on the interface.
2603 """
2604 return _psplatform.net_if_stats()
2605
2606
2607# =====================================================================
2608# --- sensors
2609# =====================================================================
2610
2611
2612# Linux, macOS
2613if hasattr(_psplatform, "sensors_temperatures"):
2614
2615 def sensors_temperatures(
2616 fahrenheit: bool = False,
2617 ) -> dict[str, list[shwtemp]]:
2618 """Return hardware temperatures.
2619
2620 Each entry is a named tuple representing a certain hardware
2621 sensor (it may be a CPU, an hard disk or something else,
2622 depending on the OS and its configuration).
2623
2624 All temperatures are expressed in celsius unless *fahrenheit*
2625 is set to True.
2626 """
2627
2628 def convert(n):
2629 if n is not None:
2630 return (float(n) * 9 / 5) + 32 if fahrenheit else n
2631
2632 ret = collections.defaultdict(list)
2633 rawdict = _psplatform.sensors_temperatures()
2634
2635 for name, values in rawdict.items():
2636 while values:
2637 label, current, high, critical = values.pop(0)
2638 current = convert(current)
2639 high = convert(high)
2640 critical = convert(critical)
2641
2642 if high and not critical:
2643 critical = high
2644 elif critical and not high:
2645 high = critical
2646
2647 ret[name].append(_ntp.shwtemp(label, current, high, critical))
2648
2649 return dict(ret)
2650
2651 __all__.append("sensors_temperatures")
2652
2653
2654# Linux
2655if hasattr(_psplatform, "sensors_fans"):
2656
2657 def sensors_fans() -> dict[str, list[sfan]]:
2658 """Return fans speed. Each entry is a named tuple
2659 representing a certain hardware sensor.
2660 All speed are expressed in RPM (rounds per minute).
2661 """
2662 return _psplatform.sensors_fans()
2663
2664 __all__.append("sensors_fans")
2665
2666
2667# Linux, Windows, FreeBSD, macOS
2668if hasattr(_psplatform, "sensors_battery"):
2669
2670 def sensors_battery() -> sbattery | None:
2671 """Return battery information. If no battery is installed
2672 returns None.
2673
2674 - percent: battery power left as a percentage.
2675 - secsleft: a rough approximation of how many seconds are left
2676 before the battery runs out of power. May be
2677 `POWER_TIME_UNLIMITED` or `POWER_TIME_UNKNOWN`.
2678 - power_plugged: True if the AC power cable is connected.
2679 """
2680 return _psplatform.sensors_battery()
2681
2682 __all__.append("sensors_battery")
2683
2684
2685# =====================================================================
2686# --- other system related functions
2687# =====================================================================
2688
2689
2690def boot_time() -> float:
2691 """Return the system boot time expressed in seconds since the epoch
2692 (seconds since January 1, 1970, at midnight UTC).
2693
2694 The returned value is based on the system clock, which means it may
2695 be affected by changes such as manual adjustments or time
2696 synchronization (e.g. NTP).
2697 """
2698 return _psplatform.boot_time()
2699
2700
2701def users() -> list[suser]:
2702 """Return users currently connected on the system as a list of
2703 named tuples including the following fields:
2704
2705 - user: the name of the user
2706 - terminal: the tty or pseudo-tty associated with the user, if any.
2707 - host: the host name associated with the entry, if any.
2708 - started: the creation time as a floating point number expressed in
2709 seconds since the epoch.
2710 - pid: the PID of the login process (None on Windows and OpenBSD).
2711 """
2712 return _psplatform.users()
2713
2714
2715# =====================================================================
2716# --- Windows services
2717# =====================================================================
2718
2719
2720if WINDOWS:
2721
2722 def win_service_iter() -> Iterator[WindowsService]:
2723 """Return a generator yielding a `WindowsService` instance for
2724 all Windows services installed.
2725 """
2726 return _psplatform.win_service_iter()
2727
2728 def win_service_get(name) -> WindowsService:
2729 """Get a Windows service by *name*.
2730
2731 Raise `NoSuchProcess` if no service with such name exists.
2732 """
2733 return _psplatform.win_service_get(name)
2734
2735
2736# =====================================================================
2737# --- malloc / heap
2738# =====================================================================
2739
2740
2741# Linux + glibc, Windows, macOS, FreeBSD, NetBSD
2742if hasattr(_psplatform, "heap_info"):
2743
2744 def heap_info() -> pheap:
2745 """Return low-level heap statistics from the C heap allocator
2746 (glibc).
2747
2748 - `heap_used`: the total number of bytes allocated via
2749 malloc/free. These are typically allocations smaller than
2750 MMAP_THRESHOLD.
2751
2752 - `mmap_used`: the total number of bytes allocated via `mmap()`
2753 or via large ``malloc()`` allocations.
2754
2755 - `heap_count` (Windows only): number of private heaps created
2756 via `HeapCreate()`.
2757 """
2758 return _ntp.pheap(*_psplatform.heap_info())
2759
2760 def heap_trim() -> None:
2761 """Request that the underlying allocator free any unused memory
2762 it's holding in the heap (typically small `malloc()`
2763 allocations).
2764
2765 In practice, modern allocators rarely comply, so this is not a
2766 general-purpose memory-reduction tool and won't meaningfully
2767 shrink RSS in real programs. Its primary value is in **leak
2768 detection tools**.
2769
2770 Calling `heap_trim()` before taking measurements helps reduce
2771 allocator noise, giving you a cleaner baseline so that changes
2772 in `heap_used` come from the code you're testing, not from
2773 internal allocator caching or fragmentation. Its effectiveness
2774 depends on allocator behavior and fragmentation patterns.
2775 """
2776 _psplatform.heap_trim()
2777
2778 __all__.append("heap_info")
2779 __all__.append("heap_trim")
2780
2781
2782# =====================================================================
2783
2784
2785def _set_debug(value):
2786 """Enable or disable `PSUTIL_DEBUG` option, which prints debugging
2787 messages to stderr.
2788 """
2789 import psutil._common
2790
2791 psutil._common.PSUTIL_DEBUG = bool(value)
2792 _psutil.set_debug(bool(value))
2793
2794
2795del memoize_when_activated