Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/psutil-8.0.0-py3.11-linux-x86_64.egg/psutil/__init__.py: 24%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1236 statements  

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