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