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: 23%

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

1170 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 signal 

29import socket 

30import subprocess 

31import sys 

32import threading 

33import time 

34import warnings 

35from typing import TYPE_CHECKING as _TYPE_CHECKING 

36 

37try: 

38 import pwd 

39except ImportError: 

40 pwd = None 

41 

42from . import _common 

43from . import _ntuples as _ntp 

44from ._common import AIX 

45from ._common import BSD 

46from ._common import FREEBSD 

47from ._common import LINUX 

48from ._common import MACOS 

49from ._common import NETBSD 

50from ._common import OPENBSD 

51from ._common import OSX # deprecated alias 

52from ._common import POSIX 

53from ._common import SUNOS 

54from ._common import WINDOWS 

55from ._common import AccessDenied 

56from ._common import Error 

57from ._common import NoSuchProcess 

58from ._common import TimeoutExpired 

59from ._common import ZombieProcess 

60from ._common import bytes2human 

61from ._common import debug 

62from ._common import memoize_when_activated 

63from ._common import warn 

64from ._common import wrap_numbers as _wrap_numbers 

65from ._enums import BatteryTime 

66from ._enums import ConnectionStatus 

67from ._enums import NicDuplex 

68from ._enums import ProcessStatus 

69 

70if _TYPE_CHECKING: 

71 from collections.abc import Collection 

72 from typing import Any 

73 from typing import Callable 

74 from typing import Generator 

75 from typing import Iterator 

76 

77 from ._ntuples import pconn 

78 from ._ntuples import pcputimes 

79 from ._ntuples import pctxsw 

80 from ._ntuples import pfootprint 

81 from ._ntuples import pfullmem 

82 from ._ntuples import pgids 

83 from ._ntuples import pheap 

84 from ._ntuples import pio 

85 from ._ntuples import pionice 

86 from ._ntuples import pmem 

87 from ._ntuples import pmem_ex 

88 from ._ntuples import pmmap_ext 

89 from ._ntuples import pmmap_grouped 

90 from ._ntuples import popenfile 

91 from ._ntuples import ppagefaults 

92 from ._ntuples import pthread 

93 from ._ntuples import puids 

94 from ._ntuples import sbattery 

95 from ._ntuples import sconn 

96 from ._ntuples import scpufreq 

97 from ._ntuples import scpustats 

98 from ._ntuples import scputimes 

99 from ._ntuples import sdiskio 

100 from ._ntuples import sdiskpart 

101 from ._ntuples import sdiskusage 

102 from ._ntuples import sfan 

103 from ._ntuples import shwtemp 

104 from ._ntuples import snetio 

105 from ._ntuples import snicaddr 

106 from ._ntuples import snicstats 

107 from ._ntuples import sswap 

108 from ._ntuples import suser 

109 from ._ntuples import svmem 

110 from ._pswindows import WindowsService 

111 

112 # _export_enum() puts these in the module namespace at run time. 

113 STATUS_DEAD: ProcessStatus 

114 STATUS_DISK_SLEEP: ProcessStatus 

115 STATUS_IDLE: ProcessStatus 

116 STATUS_LOCKED: ProcessStatus 

117 STATUS_PARKED: ProcessStatus 

118 STATUS_RUNNING: ProcessStatus 

119 STATUS_SLEEPING: ProcessStatus 

120 STATUS_STOPPED: ProcessStatus 

121 STATUS_SUSPENDED: ProcessStatus 

122 STATUS_TRACING_STOP: ProcessStatus 

123 STATUS_WAITING: ProcessStatus 

124 STATUS_WAKE_KILL: ProcessStatus 

125 STATUS_WAKING: ProcessStatus 

126 STATUS_ZOMBIE: ProcessStatus 

127 

128 CONN_CLOSE: ConnectionStatus 

129 CONN_CLOSE_WAIT: ConnectionStatus 

130 CONN_CLOSING: ConnectionStatus 

131 CONN_ESTABLISHED: ConnectionStatus 

132 CONN_FIN_WAIT1: ConnectionStatus 

133 CONN_FIN_WAIT2: ConnectionStatus 

134 CONN_LAST_ACK: ConnectionStatus 

135 CONN_LISTEN: ConnectionStatus 

136 CONN_NONE: ConnectionStatus 

137 CONN_SYN_RECV: ConnectionStatus 

138 CONN_SYN_SENT: ConnectionStatus 

139 CONN_TIME_WAIT: ConnectionStatus 

140 CONN_BOUND: ConnectionStatus # SunOS 

141 CONN_DELETE_TCB: ConnectionStatus # Windows 

142 CONN_IDLE: ConnectionStatus # SunOS 

143 

144 NIC_DUPLEX_FULL: NicDuplex 

145 NIC_DUPLEX_HALF: NicDuplex 

146 NIC_DUPLEX_UNKNOWN: NicDuplex 

147 

148 POWER_TIME_UNKNOWN: BatteryTime 

149 POWER_TIME_UNLIMITED: BatteryTime 

150 

151 IOPRIO_CLASS_BE: ProcessIOPriority # Linux 

152 IOPRIO_CLASS_IDLE: ProcessIOPriority # Linux 

153 IOPRIO_CLASS_NONE: ProcessIOPriority # Linux 

154 IOPRIO_CLASS_RT: ProcessIOPriority # Linux 

155 IOPRIO_HIGH: ProcessIOPriority # Windows 

156 IOPRIO_LOW: ProcessIOPriority # Windows 

157 IOPRIO_NORMAL: ProcessIOPriority # Windows 

158 IOPRIO_VERYLOW: ProcessIOPriority # Windows 

159 

160 ABOVE_NORMAL_PRIORITY_CLASS: ProcessPriority # Windows 

161 BELOW_NORMAL_PRIORITY_CLASS: ProcessPriority # Windows 

162 HIGH_PRIORITY_CLASS: ProcessPriority # Windows 

163 IDLE_PRIORITY_CLASS: ProcessPriority # Windows 

164 NORMAL_PRIORITY_CLASS: ProcessPriority # Windows 

165 REALTIME_PRIORITY_CLASS: ProcessPriority # Windows 

166 

167 RLIMIT_AS: ProcessRlimit # Linux, FreeBSD 

168 RLIMIT_CORE: ProcessRlimit # Linux, FreeBSD 

169 RLIMIT_CPU: ProcessRlimit # Linux, FreeBSD 

170 RLIMIT_DATA: ProcessRlimit # Linux, FreeBSD 

171 RLIMIT_FSIZE: ProcessRlimit # Linux, FreeBSD 

172 RLIMIT_LOCKS: ProcessRlimit # Linux 

173 RLIMIT_MEMLOCK: ProcessRlimit # Linux, FreeBSD 

174 RLIMIT_MSGQUEUE: ProcessRlimit # Linux 

175 RLIMIT_NICE: ProcessRlimit # Linux 

176 RLIMIT_NOFILE: ProcessRlimit # Linux, FreeBSD 

177 RLIMIT_NPROC: ProcessRlimit # Linux, FreeBSD 

178 RLIMIT_NPTS: ProcessRlimit # FreeBSD 

179 RLIMIT_RSS: ProcessRlimit # Linux, FreeBSD 

180 RLIMIT_RTPRIO: ProcessRlimit # Linux 

181 RLIMIT_RTTIME: ProcessRlimit # Linux 

182 RLIMIT_SBSIZE: ProcessRlimit # FreeBSD 

183 RLIMIT_SIGPENDING: ProcessRlimit # Linux 

184 RLIMIT_STACK: ProcessRlimit # Linux, FreeBSD 

185 RLIMIT_SWAP: ProcessRlimit # FreeBSD 

186 RLIM_INFINITY: ProcessRlimit # Linux, FreeBSD 

187 

188 

189if LINUX: 

190 # This is public API and it will be retrieved from _pslinux.py 

191 # via sys.modules. 

192 PROCFS_PATH = "/proc" 

193 

194 from . import _pslinux as _psplatform 

195 from ._enums import ProcessIOPriority 

196 from ._enums import ProcessRlimit 

197 

198elif WINDOWS: 

199 from . import _pswindows as _psplatform 

200 from ._enums import ProcessIOPriority 

201 from ._enums import ProcessPriority 

202 

203elif MACOS: 

204 from . import _psosx as _psplatform 

205 

206elif BSD: 

207 from . import _psbsd as _psplatform 

208 

209 if FREEBSD: 

210 from ._enums import ProcessRlimit 

211 

212elif SUNOS: 

213 from . import _pssunos as _psplatform 

214 

215 # This is public writable API which is read from _pslinux.py and 

216 # _pssunos.py via sys.modules. 

217 PROCFS_PATH = "/proc" 

218 

219elif AIX: 

220 from . import _psaix as _psplatform 

221 

222 # This is public API and it will be retrieved from _pslinux.py 

223 # via sys.modules. 

224 PROCFS_PATH = "/proc" 

225 

226else: # pragma: no cover 

227 msg = f"platform {sys.platform} is not supported" 

228 raise NotImplementedError(msg) 

229 

230from . import _psutil 

231 

232# fmt: off 

233__all__ = [ 

234 # exceptions 

235 "Error", "NoSuchProcess", "ZombieProcess", "AccessDenied", 

236 "TimeoutExpired", 

237 

238 # constants 

239 "version_info", "__version__", 

240 

241 "AF_LINK", 

242 

243 "BSD", "FREEBSD", "LINUX", "NETBSD", "OPENBSD", "MACOS", "OSX", "POSIX", 

244 "SUNOS", "WINDOWS", "AIX", 

245 

246 # classes 

247 "Process", "Popen", 

248 

249 # functions 

250 "pid_exists", "pids", "process_iter", "wait_procs", # proc 

251 "virtual_memory", "swap_memory", # memory 

252 "cpu_times", "cpu_percent", "cpu_times_percent", "cpu_count", # cpu 

253 "cpu_stats", "getloadavg", # "cpu_freq", 

254 "net_io_counters", "net_connections", "net_if_addrs", # network 

255 "net_if_stats", 

256 "disk_io_counters", "disk_partitions", "disk_usage", # disk 

257 # "sensors_temperatures", "sensors_battery", "sensors_fans" # sensors 

258 "users", "boot_time", # others 

259 "bytes2human", 

260] 

261# fmt: on 

262 

263__all__.extend(_psplatform.__extra__all__) 

264_globals = globals() 

265 

266 

267def _export_enum(cls): 

268 __all__.append(cls.__name__) 

269 for name, member in cls.__members__.items(): 

270 _globals[name] = member # noqa: F821 

271 __all__.append(name) 

272 

273 

274# Populate global namespace with enums and CONSTANTs. 

275_export_enum(ProcessStatus) 

276_export_enum(ConnectionStatus) 

277_export_enum(NicDuplex) 

278_export_enum(BatteryTime) 

279if LINUX or WINDOWS: 

280 _export_enum(ProcessIOPriority) 

281if WINDOWS: 

282 _export_enum(ProcessPriority) 

283if LINUX or FREEBSD: 

284 _export_enum(ProcessRlimit) 

285if LINUX or SUNOS or AIX: 

286 __all__.append("PROCFS_PATH") 

287 

288del _globals, _export_enum 

289 

290AF_LINK = _psplatform.AF_LINK 

291 

292__author__ = "Giampaolo Rodola'" 

293__version__ = "8.0.0" 

294version_info = tuple(int(num) for num in __version__.split('.')) 

295 

296_timer = getattr(time, 'monotonic', time.time) 

297_TOTAL_PHYMEM = None 

298_LOWEST_PID = None 

299_SENTINEL = object() 

300 

301# Sanity check in case the user messed up with psutil installation 

302# or did something weird with sys.path. In this case we might end 

303# up importing a python module using a C extension module which 

304# was compiled for a different version of psutil. 

305# We want to prevent that by failing sooner rather than later. 

306# See: https://github.com/giampaolo/psutil/issues/564 

307if int(__version__.replace('.', '')) != getattr(_psutil, 'version', None): 

308 msg = f"version conflict: {_psutil.__file__!r} C extension " 

309 msg += "module was built for another version of psutil" 

310 if hasattr(_psutil, 'version'): 

311 v = ".".join(list(str(_psutil.version))) 

312 msg += f" ({v} instead of {__version__})" 

313 else: 

314 msg += f" (different than {__version__})" 

315 what = getattr( 

316 _psutil, 

317 "__file__", 

318 "the existing psutil install directory", 

319 ) 

320 msg += f"; you may try to 'pip uninstall psutil', manually remove {what}" 

321 msg += " or clean the virtual env somehow, then reinstall" 

322 raise ImportError(msg) 

323 

324 

325# ===================================================================== 

326# --- Utils 

327# ===================================================================== 

328 

329 

330if hasattr(_psplatform, 'ppid_map'): 

331 # Faster version (Windows and Linux). 

332 _ppid_map = _psplatform.ppid_map 

333else: # pragma: no cover 

334 

335 def _ppid_map(): 

336 """Return a `{pid: ppid, ...}` dict for all running processes in 

337 one shot. Used to speed up `Process.children()`. 

338 """ 

339 ret = {} 

340 for pid in pids(): 

341 try: 

342 ret[pid] = _psplatform.Process(pid).ppid() 

343 except (NoSuchProcess, ZombieProcess): 

344 pass 

345 return ret 

346 

347 

348def _pprint_secs(secs): 

349 """Format seconds in a human readable form.""" 

350 now = time.time() 

351 secs_ago = int(now - secs) 

352 fmt = "%H:%M:%S" if secs_ago < 60 * 60 * 24 else "%Y-%m-%d %H:%M:%S" 

353 return datetime.datetime.fromtimestamp(secs).strftime(fmt) 

354 

355 

356def _check_conn_kind(kind): 

357 """Check net_connections()'s `kind` parameter.""" 

358 kinds = tuple(_common.conn_tmap) 

359 if kind not in kinds: 

360 msg = f"invalid kind argument {kind!r}; valid ones are: {kinds}" 

361 raise ValueError(msg) 

362 

363 

364# ===================================================================== 

365# --- Process class 

366# ===================================================================== 

367 

368 

369def _use_prefetch(method): 

370 """Decorator returning cached values from `process_iter(attrs=...)`. 

371 

372 When `process_iter()` is called with an *attrs* argument, it 

373 pre-fetches the requested attributes via `as_dict()` and stores 

374 them in `Process._prefetch`. This decorator makes the decorated 

375 method return the cached value (if present) instead of issuing 

376 a new system call. 

377 """ 

378 

379 @functools.wraps(method) 

380 def wrapper(self, *args, **kwargs): 

381 if not args and not kwargs: 

382 try: 

383 return self._prefetch[method.__name__] 

384 except KeyError: 

385 pass 

386 return method(self, *args, **kwargs) 

387 

388 return wrapper 

389 

390 

391class Process: 

392 """Represents an OS process identified by a PID. 

393 

394 If *pid* arg is omitted, the current process PID (`os.getpid()`) is 

395 used. Raises `NoSuchProcess` if the PID does not exist. 

396 

397 The way this class is bound to a process is via its PID. Most 

398 methods do not guarantee that the PID has not been reused, so you 

399 may end up retrieving information for a different process. 

400 

401 Real process identity is checked (via PID + creation time) only for 

402 methods that set attributes or send signals. 

403 

404 To avoid issues with PID reuse for other read-only methods, call 

405 `is_running()` before querying the process. 

406 """ 

407 

408 attrs: frozenset[str] = frozenset() # dynamically set later 

409 

410 def __init__(self, pid: int | None = None) -> None: 

411 self._init(pid) 

412 

413 def _init(self, pid, _ignore_nsp=False): 

414 if pid is None: 

415 pid = os.getpid() 

416 else: 

417 if pid < 0: 

418 msg = f"pid must be a positive integer (got {pid})" 

419 raise ValueError(msg) 

420 try: 

421 _psutil.check_pid_range(pid) 

422 except OverflowError as err: 

423 msg = "process PID out of range" 

424 raise NoSuchProcess(pid, msg=msg) from err 

425 

426 self._pid = pid 

427 self._name = None 

428 self._exe = None 

429 self._create_time = None 

430 self._gone = False 

431 self._pid_reused = False 

432 self._hash = None 

433 self._lock = threading.RLock() 

434 # used for caching on Windows only (on POSIX ppid may change) 

435 self._ppid = None 

436 # platform-specific modules define an _psplatform.Process 

437 # implementation class 

438 self._proc = _psplatform.Process(pid) 

439 self._last_sys_cpu_times = None 

440 self._last_proc_cpu_times = None 

441 self._exitcode = _SENTINEL 

442 self._prefetch = {} 

443 self._ad_value = _SENTINEL 

444 self._ident = (self.pid, None) 

445 try: 

446 self._ident = self._get_ident() 

447 except AccessDenied: 

448 # This should happen on Windows only, since we use the fast 

449 # create time method. AFAIK, on all other platforms we are 

450 # able to get create time for all PIDs. 

451 pass 

452 except ZombieProcess: 

453 # Zombies can still be queried by this class (although 

454 # not always) and pids() return them so just go on. 

455 pass 

456 except NoSuchProcess: 

457 if not _ignore_nsp: 

458 msg = "process PID not found" 

459 raise NoSuchProcess(pid, msg=msg) from None 

460 self._gone = True 

461 

462 def _is_ad_value(self, value): 

463 """Whether `value` is the `ad_value` that process_iter(attrs=...) 

464 stored in place of a getter which raised AccessDenied. 

465 """ 

466 return self._ad_value is not _SENTINEL and value is self._ad_value 

467 

468 def _get_ident(self): 

469 """Return a `(pid, uid)` tuple which is supposed to identify a 

470 Process instance univocally over time. 

471 

472 The PID alone is not enough, as it can be assigned to a new 

473 process after this one terminates, so we add creation time to 

474 the mix. We need this in order to prevent killing the wrong 

475 process later on. This is also known as PID reuse or PID 

476 recycling problem. 

477 

478 The reliability of this strategy mostly depends on 

479 `create_time()` precision, which is 0.01 secs on Linux. The 

480 assumption is that, after a process terminates, the kernel 

481 won't reuse the same PID after such a short period of time 

482 (0.01 secs). Technically this is inherently racy, but 

483 practically it should be good enough. 

484 

485 NOTE: unreliable on FreeBSD and OpenBSD as ctime is subject to 

486 system clock updates, so the PID-reuse check there is disabled. 

487 Same goes for SunOS and AIX, where we don't know whether ctime 

488 is stable across clock updates. 

489 

490 NOTE 2: it is also disabled on Windows in case `create_time()` 

491 can't be fetched due to `AccessDenied`. 

492 """ 

493 

494 if WINDOWS: 

495 # Use create_time() fast method in order to speedup 

496 # `process_iter()`. This means we'll get AccessDenied for 

497 # most ADMIN processes, but that's fine since it means 

498 # we'll also get AccessDenied on kill(). 

499 # https://github.com/giampaolo/psutil/issues/2366#issuecomment-2381646555 

500 self._create_time = self._proc.create_time(fast_only=True) 

501 return (self.pid, self._create_time) 

502 elif LINUX or NETBSD or OSX: 

503 # Use 'monotonic' process starttime since boot to form unique 

504 # process identity, since it is stable over changes to system 

505 # time. 

506 return (self.pid, self._proc.create_time(monotonic=True)) 

507 else: 

508 # Still call create_time() to check PID existence (raise 

509 # NSP at construction time), but don't use it for identity. 

510 self.create_time() 

511 return (self.pid, None) 

512 

513 def __str__(self): 

514 info = {} 

515 info["pid"] = self.pid 

516 with self.oneshot(): 

517 if self._pid_reused: 

518 info["status"] = "terminated + PID reused" 

519 else: 

520 try: 

521 info["name"] = self._name or self.name() 

522 info["status"] = str(self.status()) 

523 except ZombieProcess: 

524 info["status"] = "zombie" 

525 except NoSuchProcess: 

526 info["status"] = "terminated" 

527 except AccessDenied: 

528 pass 

529 

530 if self._exitcode not in {_SENTINEL, None}: 

531 info["exitcode"] = self._exitcode 

532 if self._create_time is not None: 

533 info['started'] = _pprint_secs(self._create_time) 

534 

535 return "{}.{}({})".format( 

536 self.__class__.__module__, 

537 self.__class__.__name__, 

538 ", ".join([f"{k}={v!r}" for k, v in info.items()]), 

539 ) 

540 

541 __repr__ = __str__ 

542 

543 @staticmethod 

544 def _cmp_idents(ident1, ident2): 

545 """Compare two `(pid, ctime)` identity tuples and return 

546 "same", "different" or "unknown". "unknown" means ctime is 

547 missing on either side (`AccessDenied` on Windows, zombies 

548 resulting in ctime 0), which is not proof of a different 

549 process. 

550 """ 

551 pid1, ctime1 = ident1 

552 pid2, ctime2 = ident2 

553 if pid1 != pid2: 

554 return "different" 

555 if not ctime1 or not ctime2: 

556 return "unknown" 

557 return "same" if ctime1 == ctime2 else "different" 

558 

559 def __eq__(self, other): 

560 # Test for equality with another Process object based 

561 # on PID and creation time. 

562 if not isinstance(other, Process): 

563 return NotImplemented 

564 return self._cmp_idents(self._ident, other._ident) != "different" 

565 

566 def __ne__(self, other): 

567 return not self == other 

568 

569 def __hash__(self): 

570 # PID only: __eq__ can match idents with different ctimes, and 

571 # equal objects must hash the same. 

572 if self._hash is None: 

573 self._hash = hash(self._ident[0]) 

574 return self._hash 

575 

576 def _raise_if_pid_reused(self): 

577 """Raise `NoSuchProcess` in case process PID has been reused.""" 

578 if self._pid_reused or (not self.is_running() and self._pid_reused): 

579 # We may directly raise NSP in here already if PID is just 

580 # not running, but I prefer NSP to be raised naturally by 

581 # the actual Process API call. This way unit tests will tell 

582 # us if the API is broken (aka don't raise NSP when it 

583 # should). We also remain consistent with all other "get" 

584 # APIs which don't use _raise_if_pid_reused(). 

585 msg = "process no longer exists and its PID has been reused" 

586 raise NoSuchProcess(self.pid, self._name, msg=msg) 

587 

588 @property 

589 def pid(self) -> int: 

590 """The process PID.""" 

591 return self._pid 

592 

593 # DEPRECATED 

594 @property 

595 def info(self) -> dict: 

596 """Return pre-fetched `process_iter()` info dict. 

597 

598 Deprecated: use method calls instead (e.g. `p.name()`). 

599 """ 

600 msg = ( 

601 "Process.info is deprecated; use method calls instead" 

602 " (e.g. p.name() instead of p.info['name'])" 

603 ) 

604 warnings.warn(msg, DeprecationWarning, stacklevel=2) 

605 # Return a copy to prevent the user from mutating the dict and 

606 # corrupting the prefetch cache. 

607 return self._prefetch.copy() 

608 

609 # --- utility methods 

610 

611 @contextlib.contextmanager 

612 def oneshot(self) -> Generator[None, None, None]: 

613 """Context manager which speeds up the retrieval of multiple 

614 process attributes at the same time. 

615 

616 Internally, many attributes (e.g. `name()`, `ppid()`, `uids()`, 

617 `create_time()`, ...) share the same system call. This context 

618 manager executes each system call once, and caches the results, 

619 so subsequent calls return cached values. The cache is cleared 

620 when exiting the context manager block. Use this every time you 

621 retrieve more than one attribute about the process. 

622 

623 >>> import psutil 

624 >>> p = psutil.Process() 

625 >>> with p.oneshot(): 

626 ... p.name() # collect multiple info 

627 ... p.cpu_times() # return cached value 

628 ... p.cpu_percent() # return cached value 

629 ... p.create_time() # return cached value 

630 ... 

631 >>> 

632 """ 

633 with self._lock: 

634 if hasattr(self, "_cache"): 

635 # NOOP: this covers the use case where the user enters the 

636 # context twice: 

637 # 

638 # >>> with p.oneshot(): 

639 # ... with p.oneshot(): 

640 # ... 

641 # 

642 # Also, since as_dict() internally uses oneshot() 

643 # I expect that the code below will be a pretty common 

644 # "mistake" that the user will make, so let's guard 

645 # against that: 

646 # 

647 # >>> with p.oneshot(): 

648 # ... p.as_dict() 

649 # ... 

650 yield 

651 else: 

652 try: 

653 # cached in case cpu_percent() is used 

654 self.cpu_times.cache_activate(self) 

655 # cached in case memory_percent() is used 

656 self.memory_info.cache_activate(self) 

657 # cached in case parent() is used 

658 self.ppid.cache_activate(self) 

659 # cached in case username() is used 

660 if POSIX: 

661 self.uids.cache_activate(self) 

662 # specific implementation cache 

663 self._proc.oneshot_enter() 

664 yield 

665 finally: 

666 self.cpu_times.cache_deactivate(self) 

667 self.memory_info.cache_deactivate(self) 

668 self.ppid.cache_deactivate(self) 

669 if POSIX: 

670 self.uids.cache_deactivate(self) 

671 self._proc.oneshot_exit() 

672 

673 def as_dict( 

674 self, attrs: Collection[str] | None = None, ad_value: Any = None 

675 ) -> dict[str, Any]: 

676 """Utility method returning process information as a 

677 hashable dictionary. 

678 

679 If *attrs* is specified it must be a collection of strings 

680 reflecting available Process class' attribute names (e.g. 

681 ['cpu_times', 'name']) else all public (read-only) attributes 

682 are assumed. See `Process.attrs` for a full list. 

683 

684 *ad_value* is the value which gets assigned in case 

685 `AccessDenied` or `ZombieProcess` exception is raised when 

686 retrieving that particular process information. 

687 """ 

688 valid_names = self.attrs 

689 # Deprecated attrs: not returned by default but still accepted if 

690 # explicitly requested. 

691 deprecated_names = {"memory_full_info"} 

692 

693 if attrs is not None: 

694 if not isinstance(attrs, (list, tuple, set, frozenset)): 

695 msg = f"invalid attrs type {type(attrs)}" 

696 raise TypeError(msg) 

697 attrs = set(attrs) 

698 invalid_names = attrs - valid_names - deprecated_names 

699 if invalid_names: 

700 msg = "invalid attr name{} {}".format( 

701 "s" if len(invalid_names) > 1 else "", 

702 ", ".join(map(repr, invalid_names)), 

703 ) 

704 raise ValueError(msg) 

705 

706 retdict = {} 

707 names = attrs or sorted(valid_names) 

708 with self.oneshot(): 

709 for name in names: 

710 try: 

711 if name == 'pid': 

712 ret = self.pid 

713 else: 

714 meth = getattr(self, name) 

715 ret = meth() 

716 except (AccessDenied, ZombieProcess): 

717 ret = ad_value 

718 except NotImplementedError: 

719 # in case of not implemented functionality (may happen 

720 # on old or exotic systems) we want to crash only if 

721 # the user explicitly asked for that particular attr 

722 if attrs: 

723 raise 

724 continue 

725 retdict[name] = ret 

726 return retdict 

727 

728 def parent(self) -> Process | None: 

729 """Return the parent process as a `Process` object, preemptively 

730 checking whether PID has been reused. 

731 

732 If no parent is known return None. 

733 """ 

734 lowest_pid = _LOWEST_PID if _LOWEST_PID is not None else pids()[0] 

735 if self.pid == lowest_pid: 

736 return None 

737 ppid = self.ppid() 

738 if ppid is not None: 

739 # Get a fresh (non-cached) ctime in case the system clock 

740 # was updated. TODO: use a monotonic ctime on platforms 

741 # where it's supported. 

742 proc_ctime = Process(self.pid).create_time() 

743 try: 

744 parent = Process(ppid) 

745 if parent.create_time() <= proc_ctime: 

746 return parent 

747 # ...else ppid has been reused by another process 

748 except NoSuchProcess: 

749 pass 

750 

751 def parents(self) -> list[Process]: 

752 """Return the parents of this process as a list of `Process` 

753 instances. 

754 

755 If no parents are known return an empty list. 

756 """ 

757 parents = [] 

758 proc = self.parent() 

759 while proc is not None: 

760 parents.append(proc) 

761 proc = proc.parent() 

762 return parents 

763 

764 def is_running(self) -> bool: 

765 """Return whether this process is running. 

766 

767 It also checks if PID has been reused by another process, in 

768 which case it will remove the process from `process_iter()` 

769 internal cache and return False. 

770 """ 

771 if self._gone or self._pid_reused: 

772 return False 

773 try: 

774 # Checking if PID is alive is not enough as the PID might 

775 # have been reused by another process. Process identity is 

776 # guaranteed by (PID + creation time), see __eq__. 

777 other = Process(self.pid) 

778 self._pid_reused = self != other 

779 if self._pid_reused: 

780 debug(f"PID reuse detected: {self._ident} vs. {other._ident}") 

781 _pids_reused.add(self.pid) 

782 raise NoSuchProcess(self.pid) 

783 if self._cmp_idents(self._ident, other._ident) == "unknown": 

784 debug( 

785 "null create time, PID reuse check inconclusive:" 

786 f" {self._ident} vs. {other._ident}" 

787 ) 

788 return True 

789 except ZombieProcess: 

790 # We should never get here as it's already handled in 

791 # Process.__init__; here just for extra safety. 

792 return True 

793 except NoSuchProcess: 

794 self._gone = True 

795 return False 

796 

797 # --- actual API 

798 

799 @_use_prefetch 

800 @memoize_when_activated 

801 def ppid(self) -> int: 

802 """The process parent PID. 

803 On Windows the return value is cached after first call. 

804 """ 

805 # On POSIX we don't want to cache the ppid as it may unexpectedly 

806 # change to 1 (init) in case this process turns into a zombie: 

807 # https://github.com/giampaolo/psutil/issues/321 

808 # http://stackoverflow.com/questions/356722/ 

809 self._raise_if_pid_reused() 

810 if POSIX: 

811 return self._proc.ppid() 

812 else: # pragma: no cover 

813 self._ppid = self._ppid or self._proc.ppid() 

814 return self._ppid 

815 

816 @_use_prefetch 

817 def name(self) -> str: 

818 """The process name. The return value is cached after first call.""" 

819 # Process name is only cached on Windows as on POSIX it may 

820 # change, see: 

821 # https://github.com/giampaolo/psutil/issues/692 

822 if WINDOWS and self._name is not None: 

823 return self._name 

824 name = self._proc.name() 

825 if POSIX and len(name) >= 15: 

826 # On UNIX the name gets truncated to the first 15 characters. 

827 # If it matches the first part of the cmdline we return that 

828 # one instead because it's usually more explicative. 

829 # Examples are "gnome-keyring-d" vs. "gnome-keyring-daemon". 

830 try: 

831 cmdline = self.cmdline() 

832 except (AccessDenied, ZombieProcess): 

833 # Just pass and return the truncated name: it's better 

834 # than nothing. Note: there are actual cases where a 

835 # zombie process can return a name() but not a 

836 # cmdline(), see: 

837 # https://github.com/giampaolo/psutil/issues/2239 

838 pass 

839 else: 

840 if cmdline: 

841 extended_name = os.path.basename(cmdline[0]) 

842 if extended_name.startswith(name): 

843 name = extended_name 

844 self._name = name 

845 self._proc._name = name 

846 return name 

847 

848 @_use_prefetch 

849 def exe(self) -> str: 

850 """The process executable as an absolute path. 

851 

852 May also be an empty string. The return value is cached after 

853 first call. 

854 """ 

855 

856 def guess_it(fallback): 

857 # try to guess exe from cmdline[0] in absence of a native 

858 # exe representation 

859 cmdline = self.cmdline() 

860 if cmdline and hasattr(os, 'access') and hasattr(os, 'X_OK'): 

861 exe = cmdline[0] # the possible exe 

862 # Attempt to guess only in case of an absolute path. 

863 # It is not safe otherwise as the process might have 

864 # changed cwd. 

865 if ( 

866 os.path.isabs(exe) 

867 and os.path.isfile(exe) 

868 and os.access(exe, os.X_OK) 

869 ): 

870 return exe 

871 if isinstance(fallback, AccessDenied): 

872 raise fallback 

873 return fallback 

874 

875 if self._exe is None: 

876 try: 

877 exe = self._proc.exe() 

878 except AccessDenied as err: 

879 return guess_it(fallback=err) 

880 else: 

881 if not exe: 

882 # underlying implementation can legitimately return an 

883 # empty string; if that's the case we don't want to 

884 # raise AD while guessing from the cmdline 

885 try: 

886 exe = guess_it(fallback=exe) 

887 except AccessDenied: 

888 pass 

889 self._exe = exe 

890 return self._exe 

891 

892 @_use_prefetch 

893 def cmdline(self) -> list[str]: 

894 """The command line this process has been called with.""" 

895 return self._proc.cmdline() 

896 

897 @_use_prefetch 

898 def status(self) -> ProcessStatus | str: 

899 """The process current status as a `STATUS_` constant.""" 

900 try: 

901 return self._proc.status() 

902 except ZombieProcess: 

903 return ProcessStatus.STATUS_ZOMBIE 

904 

905 @_use_prefetch 

906 def username(self) -> str: 

907 """The name of the user that owns the process. 

908 

909 On UNIX this is calculated by using the real process uid. 

910 """ 

911 if POSIX: 

912 if pwd is None: 

913 # might happen if python was installed from sources 

914 msg = "requires pwd module shipped with standard python" 

915 raise ImportError(msg) 

916 uids = self.uids() 

917 if self._is_ad_value(uids): 

918 return uids 

919 real_uid = uids.real 

920 try: 

921 return pwd.getpwuid(real_uid).pw_name 

922 except KeyError: 

923 # the uid can't be resolved by the system 

924 return str(real_uid) 

925 else: 

926 return self._proc.username() 

927 

928 @_use_prefetch 

929 def create_time(self) -> float: 

930 """The process creation time as a floating point number 

931 expressed in seconds since the epoch (seconds since January 1, 

932 1970, at midnight UTC). 

933 

934 The return value, which is cached after first call, is based on 

935 the system clock, which means it may be affected by changes 

936 such as manual adjustments or time synchronization (e.g. NTP). 

937 """ 

938 if self._create_time is None: 

939 self._create_time = self._proc.create_time() 

940 return self._create_time 

941 

942 @_use_prefetch 

943 def cwd(self) -> str: 

944 """Process current working directory as an absolute path.""" 

945 return self._proc.cwd() 

946 

947 @_use_prefetch 

948 def nice(self, value: int | None = None) -> int | None: 

949 """Get or set process niceness (priority).""" 

950 if value is None: 

951 return self._proc.nice_get() 

952 else: 

953 self._raise_if_pid_reused() 

954 self._proc.nice_set(value) 

955 

956 if POSIX: 

957 

958 @_use_prefetch 

959 @memoize_when_activated 

960 def uids(self) -> puids: 

961 """Return process UIDs as a `(real, effective, saved)` 

962 named tuple. 

963 """ 

964 return self._proc.uids() 

965 

966 @_use_prefetch 

967 def gids(self) -> pgids: 

968 """Return process GIDs as a `(real, effective, saved)` 

969 named tuple. 

970 """ 

971 return self._proc.gids() 

972 

973 @_use_prefetch 

974 def terminal(self) -> str | None: 

975 """The terminal associated with this process, if any, 

976 else None. 

977 """ 

978 return self._proc.terminal() 

979 

980 @_use_prefetch 

981 def num_fds(self) -> int: 

982 """Return the number of file descriptors opened by this 

983 process (POSIX only). 

984 """ 

985 return self._proc.num_fds() 

986 

987 if hasattr(_psplatform.Process, "io_counters"): 

988 

989 @_use_prefetch 

990 def io_counters(self) -> pio: 

991 """Return process I/O statistics (primarily read and 

992 written bytes). 

993 

994 Availability: Linux, Windows, BSD, AIX 

995 """ 

996 return self._proc.io_counters() 

997 

998 if hasattr(_psplatform.Process, "ionice_get"): 

999 

1000 @_use_prefetch 

1001 def ionice( 

1002 self, ioclass: int | None = None, value: int | None = None 

1003 ) -> pionice | ProcessIOPriority | None: 

1004 """Get or set process I/O niceness (priority). 

1005 

1006 On Linux *ioclass* is one of the `IOPRIO_CLASS_*` constants. 

1007 *value* is a number which goes from 0 to 7. The higher the 

1008 value, the lower the I/O priority of the process. 

1009 

1010 On Windows only *ioclass* is used and it can be set to 

1011 one of the `IOPRIO_*` constants. 

1012 

1013 Availability: Linux, Windows 

1014 """ 

1015 if ioclass is None: 

1016 if value is not None: 

1017 msg = "'ioclass' argument must be specified" 

1018 raise ValueError(msg) 

1019 return self._proc.ionice_get() 

1020 else: 

1021 self._raise_if_pid_reused() 

1022 return self._proc.ionice_set(ioclass, value) 

1023 

1024 if hasattr(_psplatform.Process, "rlimit"): 

1025 

1026 def rlimit( 

1027 self, 

1028 resource: int, 

1029 limits: tuple[int, int] | None = None, 

1030 ) -> tuple[int, int] | None: 

1031 """Get or set process resource limits as a `(soft, hard)` 

1032 tuple. 

1033 

1034 - resource: one of the `RLIMIT_*` constants. 

1035 - limits: a `(soft, hard)` tuple (set). 

1036 

1037 See "man prlimit" for further info. 

1038 

1039 Availability: Linux, FreeBSD 

1040 """ 

1041 if limits is not None: 

1042 self._raise_if_pid_reused() 

1043 return self._proc.rlimit(resource, limits) 

1044 

1045 if hasattr(_psplatform.Process, "cpu_affinity_get"): 

1046 

1047 @_use_prefetch 

1048 def cpu_affinity( 

1049 self, cpus: list[int] | None = None 

1050 ) -> list[int] | None: 

1051 """Get or set process CPU affinity. 

1052 

1053 If specified, *cpus* must be a list of CPUs for which you 

1054 want to set the affinity (e.g. `[0, 1]`). If an empty list is 

1055 passed, all eligible CPUs are assumed (and set). 

1056 

1057 Availability: Linux, Windows, FreeBSD 

1058 """ 

1059 if cpus is None: 

1060 return sorted(set(self._proc.cpu_affinity_get())) 

1061 else: 

1062 self._raise_if_pid_reused() 

1063 if not cpus: 

1064 if hasattr(self._proc, "_get_eligible_cpus"): 

1065 cpus = self._proc._get_eligible_cpus() 

1066 else: 

1067 cpus = tuple(range(len(cpu_times(percpu=True)))) 

1068 self._proc.cpu_affinity_set(list(set(cpus))) 

1069 

1070 # Linux, FreeBSD, SunOS 

1071 if hasattr(_psplatform.Process, "cpu_num"): 

1072 

1073 @_use_prefetch 

1074 def cpu_num(self) -> int: 

1075 """Return what CPU this process is currently running on. 

1076 

1077 The returned number should be <= `psutil.cpu_count()`. 

1078 """ 

1079 return self._proc.cpu_num() 

1080 

1081 # All platforms has it, but maybe not in the future. 

1082 if hasattr(_psplatform.Process, "environ"): 

1083 

1084 @_use_prefetch 

1085 def environ(self) -> dict[str, str]: 

1086 """The environment variables of the process as a dict. 

1087 

1088 Note: this might not reflect changes made after the process 

1089 started. 

1090 """ 

1091 return self._proc.environ() 

1092 

1093 if WINDOWS: 

1094 

1095 @_use_prefetch 

1096 def num_handles(self) -> int: 

1097 """Return the number of handles opened by this process 

1098 

1099 Availability: Windows 

1100 """ 

1101 return self._proc.num_handles() 

1102 

1103 @_use_prefetch 

1104 def num_ctx_switches(self) -> pctxsw: 

1105 """Return the number of voluntary and involuntary context 

1106 switches performed by this process. 

1107 """ 

1108 return self._proc.num_ctx_switches() 

1109 

1110 @_use_prefetch 

1111 def num_threads(self) -> int: 

1112 """Return the number of threads used by this process.""" 

1113 return self._proc.num_threads() 

1114 

1115 if hasattr(_psplatform.Process, "threads"): 

1116 

1117 @_use_prefetch 

1118 def threads(self) -> list[pthread]: 

1119 """Return threads opened by process as a list of 

1120 `(id, user_time, system_time)` named tuples. 

1121 

1122 On OpenBSD this method requires root access. 

1123 """ 

1124 return self._proc.threads() 

1125 

1126 def children(self, recursive: bool = False) -> list[Process]: 

1127 """Return the children of this process as a list of Process 

1128 instances, preemptively checking whether PID has been reused. 

1129 

1130 If *recursive* is True return all the parent descendants. 

1131 

1132 Example (A == this process): 

1133 

1134 A ─┐ 

1135 

1136 ├─ B (child) ─┐ 

1137 │ └─ X (grandchild) ─┐ 

1138 │ └─ Y (great grandchild) 

1139 ├─ C (child) 

1140 └─ D (child) 

1141 

1142 >>> import psutil 

1143 >>> p = psutil.Process() 

1144 >>> p.children() 

1145 B, C, D 

1146 >>> p.children(recursive=True) 

1147 B, X, Y, C, D 

1148 

1149 Note that in the example above if process X disappears 

1150 process Y won't be listed as the reference to process A 

1151 is lost. 

1152 """ 

1153 self._raise_if_pid_reused() 

1154 ppid_map = _ppid_map() 

1155 # Get a fresh (non-cached) ctime in case the system clock was 

1156 # updated. TODO: use a monotonic ctime on platforms where it's 

1157 # supported. 

1158 proc_ctime = Process(self.pid).create_time() 

1159 ret = [] 

1160 if not recursive: 

1161 for pid, ppid in ppid_map.items(): 

1162 if ppid == self.pid: 

1163 try: 

1164 child = Process(pid) 

1165 # if child happens to be older than its parent 

1166 # (self) it means child's PID has been reused 

1167 if proc_ctime <= child.create_time(): 

1168 ret.append(child) 

1169 except (NoSuchProcess, ZombieProcess): 

1170 pass 

1171 else: 

1172 # Construct a {pid: [child pids]} dict 

1173 reverse_ppid_map = collections.defaultdict(list) 

1174 for pid, ppid in ppid_map.items(): 

1175 reverse_ppid_map[ppid].append(pid) 

1176 # Recursively traverse that dict, starting from self.pid, 

1177 # such that we only call Process() on actual children 

1178 seen = set() 

1179 stack = [self.pid] 

1180 while stack: 

1181 pid = stack.pop() 

1182 if pid in seen: 

1183 # Since pids can be reused while the ppid_map is 

1184 # constructed, there may be rare instances where 

1185 # there's a cycle in the recorded process "tree". 

1186 continue 

1187 seen.add(pid) 

1188 for child_pid in reverse_ppid_map[pid]: 

1189 try: 

1190 child = Process(child_pid) 

1191 # if child happens to be older than its parent 

1192 # (self) it means child's PID has been reused 

1193 intime = proc_ctime <= child.create_time() 

1194 if intime: 

1195 ret.append(child) 

1196 stack.append(child_pid) 

1197 except (NoSuchProcess, ZombieProcess): 

1198 pass 

1199 return ret 

1200 

1201 @_use_prefetch 

1202 def cpu_percent(self, interval: float | None = None) -> float: 

1203 """Return a float representing the current process CPU 

1204 utilization as a percentage. 

1205 

1206 When *interval* is 0.0 or None (default) compares process times 

1207 to system CPU times elapsed since last call, returning 

1208 immediately (non-blocking). That means that the first time 

1209 this is called it will return a meaningless 0.0 value. 

1210 

1211 When *interval* is > 0.0 compares process times to system CPU 

1212 times elapsed before and after the interval (blocking). 

1213 

1214 In this case is recommended for accuracy that this function 

1215 be called with at least 0.1 seconds between calls. 

1216 

1217 A value > 100.0 can be returned in case of processes running 

1218 multiple threads on different CPU cores. 

1219 

1220 The returned value is explicitly NOT split evenly between 

1221 all available logical CPUs. This means that a busy loop process 

1222 running on a system with 2 logical CPUs will be reported as 

1223 having 100% CPU utilization instead of 50%. 

1224 

1225 Examples: 

1226 

1227 >>> import psutil 

1228 >>> p = psutil.Process(os.getpid()) 

1229 >>> # blocking 

1230 >>> p.cpu_percent(interval=1) 

1231 2.0 

1232 >>> # non-blocking (percentage since last call) 

1233 >>> p.cpu_percent(interval=None) 

1234 2.9 

1235 >>> 

1236 """ 

1237 blocking = interval is not None and interval > 0.0 

1238 if interval is not None and interval < 0: 

1239 msg = f"interval is not positive (got {interval!r})" 

1240 raise ValueError(msg) 

1241 num_cpus = cpu_count() or 1 

1242 

1243 def timer(): 

1244 return _timer() * num_cpus 

1245 

1246 if blocking: 

1247 st1 = timer() 

1248 pt1 = self._proc.cpu_times() 

1249 time.sleep(interval) 

1250 st2 = timer() 

1251 pt2 = self._proc.cpu_times() 

1252 else: 

1253 st1 = self._last_sys_cpu_times 

1254 pt1 = self._last_proc_cpu_times 

1255 st2 = timer() 

1256 pt2 = self._proc.cpu_times() 

1257 if st1 is None or pt1 is None: 

1258 self._last_sys_cpu_times = st2 

1259 self._last_proc_cpu_times = pt2 

1260 return 0.0 

1261 

1262 delta_proc = (pt2.user - pt1.user) + (pt2.system - pt1.system) 

1263 delta_time = st2 - st1 

1264 # reset values for next call in case of interval == None 

1265 self._last_sys_cpu_times = st2 

1266 self._last_proc_cpu_times = pt2 

1267 

1268 try: 

1269 # This is the utilization split evenly between all CPUs. 

1270 # E.g. a busy loop process on a 2-CPU-cores system at this 

1271 # point is reported as 50% instead of 100%. 

1272 overall_cpus_percent = (delta_proc / delta_time) * 100 

1273 except ZeroDivisionError: 

1274 # interval was too low 

1275 return 0.0 

1276 else: 

1277 # Note 1: 

1278 # in order to emulate "top" we multiply the value for the num 

1279 # of CPU cores. This way the busy process will be reported as 

1280 # having 100% (or more) usage. 

1281 # 

1282 # Note 2: 

1283 # taskmgr.exe on Windows differs in that it will show 50% 

1284 # instead. 

1285 # 

1286 # Note 3: 

1287 # a percentage > 100 is legitimate as it can result from a 

1288 # process with multiple threads running on different CPU 

1289 # cores (top does the same), see: 

1290 # http://stackoverflow.com/questions/1032357 

1291 # https://github.com/giampaolo/psutil/issues/474 

1292 single_cpu_percent = overall_cpus_percent * num_cpus 

1293 return round(single_cpu_percent, 1) 

1294 

1295 @_use_prefetch 

1296 @memoize_when_activated 

1297 def cpu_times(self) -> pcputimes: 

1298 """Return a `(user, system, children_user, children_system)` 

1299 named tuple representing the accumulated process time, 

1300 expressed in seconds. 

1301 

1302 Linux includes an additional `iowait` field. 

1303 

1304 On macOS and Windows `children_user` and `children_system` 

1305 fields are always set to 0. 

1306 """ 

1307 return self._proc.cpu_times() 

1308 

1309 @_use_prefetch 

1310 @memoize_when_activated 

1311 def memory_info(self) -> pmem: 

1312 """Return a named tuple with variable fields depending on the 

1313 platform, representing memory information about the process. 

1314 

1315 The portable fields available on all platforms are `rss` and `vms`. 

1316 

1317 All numbers are expressed in bytes. 

1318 """ 

1319 return self._proc.memory_info() 

1320 

1321 @_use_prefetch 

1322 @memoize_when_activated 

1323 def memory_info_ex(self) -> pmem_ex: 

1324 """Return a named tuple extending `memory_info()` with extra 

1325 metrics. 

1326 

1327 All numbers are expressed in bytes. 

1328 """ 

1329 base = self.memory_info() 

1330 if self._is_ad_value(base): 

1331 return base 

1332 if hasattr(self._proc, "memory_info_ex"): 

1333 extras = self._proc.memory_info_ex() 

1334 return _ntp.pmem_ex(**base._asdict(), **extras) 

1335 return base 

1336 

1337 # Linux, macOS, Windows 

1338 if hasattr(_psplatform.Process, "memory_footprint"): 

1339 

1340 @_use_prefetch 

1341 def memory_footprint(self) -> pfootprint: 

1342 """Return a named tuple with USS memory, and on Linux also 

1343 PSS and swap. 

1344 

1345 These values provide a more accurate representation of 

1346 actual process memory usage. 

1347 

1348 USS is the memory unique to a process and which would 

1349 be freed if the process was terminated right now. 

1350 

1351 It does so by passing through the whole process address. As 

1352 such it usually requires higher user privileges than 

1353 `memory_info()` or `memory_info_ex()` and is considerably 

1354 slower. 

1355 """ 

1356 return self._proc.memory_footprint() 

1357 

1358 # DEPRECATED 

1359 def memory_full_info(self) -> pfullmem: 

1360 """Return the same information as `memory_info()` plus 

1361 `memory_footprint()` in a single named tuple. 

1362 

1363 DEPRECATED in 8.0.0. Use `memory_footprint()` instead. 

1364 """ 

1365 msg = ( 

1366 "memory_full_info() is deprecated; use memory_footprint() instead" 

1367 ) 

1368 warnings.warn(msg, DeprecationWarning, stacklevel=2) 

1369 basic_mem = self.memory_info() 

1370 if self._is_ad_value(basic_mem): 

1371 return basic_mem 

1372 if hasattr(self, "memory_footprint"): 

1373 fp = self.memory_footprint() 

1374 if self._is_ad_value(fp): 

1375 return fp 

1376 return _ntp.pfullmem(*basic_mem + fp) 

1377 return _ntp.pfullmem(*basic_mem) 

1378 

1379 @_use_prefetch 

1380 def memory_percent(self, memtype: str = "rss") -> float: 

1381 """Compare process memory to total physical system memory and 

1382 calculate process memory utilization as a percentage. 

1383 

1384 *memtype* argument is a string that dictates what type of 

1385 process memory you want to compare against (defaults to "rss"). 

1386 The list of available strings can be obtained like this: 

1387 

1388 >>> psutil.Process().memory_info()._fields 

1389 ('rss', 'vms', 'shared', 'text', 'lib', 'data', 'dirty', 'uss', 'pss') 

1390 """ 

1391 valid_types = list(_ntp.pmem._fields) 

1392 if hasattr(_ntp, "pmem_ex"): 

1393 valid_types += [ 

1394 f for f in _ntp.pmem_ex._fields if f not in valid_types 

1395 ] 

1396 if hasattr(_ntp, "pfootprint"): 

1397 valid_types += [ 

1398 f for f in _ntp.pfootprint._fields if f not in valid_types 

1399 ] 

1400 if memtype not in valid_types: 

1401 msg = ( 

1402 f"invalid memtype {memtype!r}; valid types are" 

1403 f" {tuple(valid_types)!r}" 

1404 ) 

1405 raise ValueError(msg) 

1406 if memtype in _ntp.pmem._fields: 

1407 fun = self.memory_info 

1408 elif ( 

1409 hasattr(_ntp, "pfootprint") and memtype in _ntp.pfootprint._fields 

1410 ): 

1411 fun = self.memory_footprint 

1412 else: 

1413 fun = self.memory_info_ex 

1414 metrics = fun() 

1415 if self._is_ad_value(metrics): 

1416 return metrics 

1417 value = getattr(metrics, memtype) 

1418 

1419 # use cached value if available 

1420 total_phymem = _TOTAL_PHYMEM or virtual_memory().total 

1421 if not total_phymem > 0: 

1422 # we should never get here 

1423 msg = ( 

1424 "can't calculate process memory percent because total physical" 

1425 f" system memory is not positive ({total_phymem!r})" 

1426 ) 

1427 raise ValueError(msg) 

1428 return (value / float(total_phymem)) * 100 

1429 

1430 if hasattr(_psplatform.Process, "memory_maps"): 

1431 

1432 @_use_prefetch 

1433 def memory_maps( 

1434 self, grouped: bool = True 

1435 ) -> list[pmmap_grouped] | list[pmmap_ext]: 

1436 """Return process mapped memory regions as a list of named 

1437 tuples whose fields are variable depending on the platform. 

1438 

1439 If *grouped* is True the mapped regions with the same 'path' 

1440 are grouped together and the different memory fields are summed. 

1441 

1442 If *grouped* is False every mapped region is shown as a single 

1443 entity and the named tuple will also include the mapped region's 

1444 address space ('addr') and permission set ('perms'). 

1445 """ 

1446 

1447 it = self._proc.memory_maps() 

1448 if grouped: 

1449 d = {} 

1450 for tupl in it: 

1451 path = tupl[2] 

1452 nums = tupl[3:] 

1453 try: 

1454 d[path] = list(map(lambda x, y: x + y, d[path], nums)) 

1455 except KeyError: 

1456 d[path] = nums 

1457 return [_ntp.pmmap_grouped(path, *d[path]) for path in d] 

1458 else: 

1459 return [_ntp.pmmap_ext(*x) for x in it] 

1460 

1461 @_use_prefetch 

1462 def page_faults(self) -> ppagefaults: 

1463 """Return the number of page faults for this process as a 

1464 `(minor, major)` named tuple. 

1465 

1466 - `minor` (a.k.a. *soft* faults): occur when a memory page is 

1467 not currently mapped into the process address space, but is 

1468 already present in physical RAM (e.g. a shared library page 

1469 loaded by another process). The kernel resolves these without 

1470 disk I/O. 

1471 

1472 - `major` (a.k.a. *hard* faults): occur when the page must be 

1473 fetched from disk. These are expensive because they stall the 

1474 process until I/O completes. 

1475 

1476 Both counters are cumulative since process creation. 

1477 """ 

1478 return self._proc.page_faults() 

1479 

1480 @_use_prefetch 

1481 def open_files(self) -> list[popenfile]: 

1482 """Return files opened by process as a list of `(path, fd)` 

1483 named tuples including the absolute file name and file 

1484 descriptor number. 

1485 

1486 On Linux the named tuple also includes `position`, `mode` and 

1487 `flags` fields. 

1488 """ 

1489 return self._proc.open_files() 

1490 

1491 @_use_prefetch 

1492 def net_connections(self, kind: str = "inet") -> list[pconn]: 

1493 """Return socket connections opened by process as a list of 

1494 `(fd, family, type, laddr, raddr, status)` named tuples. 

1495 

1496 The *kind* parameter filters for connections that match the 

1497 following criteria: 

1498 

1499 +------------+----------------------------------------------------+ 

1500 | Kind Value | Connections using | 

1501 +------------+----------------------------------------------------+ 

1502 | 'inet' | IPv4 and IPv6 | 

1503 | 'inet4' | IPv4 | 

1504 | 'inet6' | IPv6 | 

1505 | 'tcp' | TCP | 

1506 | 'tcp4' | TCP over IPv4 | 

1507 | 'tcp6' | TCP over IPv6 | 

1508 | 'udp' | UDP | 

1509 | 'udp4' | UDP over IPv4 | 

1510 | 'udp6' | UDP over IPv6 | 

1511 | 'unix' | UNIX socket (both UDP and TCP protocols) | 

1512 | 'all' | the sum of all the possible families and protocols | 

1513 +------------+----------------------------------------------------+ 

1514 """ 

1515 _check_conn_kind(kind) 

1516 return self._proc.net_connections(kind) 

1517 

1518 @_common.deprecated_method(replacement="net_connections") 

1519 def connections(self, kind="inet") -> list[pconn]: 

1520 return self.net_connections(kind=kind) 

1521 

1522 # --- signals 

1523 

1524 if POSIX: 

1525 

1526 def _send_signal(self, sig): 

1527 assert not self.pid < 0, self.pid 

1528 self._raise_if_pid_reused() 

1529 

1530 pid, ppid, name = self.pid, self._ppid, self._name 

1531 if pid == 0: 

1532 # see "man 2 kill" 

1533 msg = ( 

1534 "preventing sending signal to process with PID 0 as it " 

1535 "would affect every process in the process group of the " 

1536 "calling process (os.getpid()) instead of PID 0" 

1537 ) 

1538 raise ValueError(msg) 

1539 try: 

1540 os.kill(pid, sig) 

1541 except ProcessLookupError as err: 

1542 if OPENBSD and pid_exists(pid): 

1543 # We do this because os.kill() lies in case of 

1544 # zombie processes. 

1545 raise ZombieProcess(pid, name, ppid) from err 

1546 self._gone = True 

1547 raise NoSuchProcess(pid, name) from err 

1548 except PermissionError as err: 

1549 raise AccessDenied(pid, name) from err 

1550 

1551 def send_signal(self, sig: int) -> None: 

1552 """Send a signal *sig* to process, preemptively checking 

1553 whether PID has been reused (see signal module constants). 

1554 

1555 On Windows only SIGTERM, CTRL_C_EVENT and CTRL_BREAK_EVENT 

1556 are valid. SIGTERM is treated as an alias for `kill()`. 

1557 """ 

1558 if POSIX: 

1559 self._send_signal(sig) 

1560 else: # pragma: no cover 

1561 self._raise_if_pid_reused() 

1562 if sig != signal.SIGTERM and not self.is_running(): 

1563 msg = "process no longer exists" 

1564 raise NoSuchProcess(self.pid, self._name, msg=msg) 

1565 self._proc.send_signal(sig) 

1566 

1567 def suspend(self) -> None: 

1568 """Suspend process execution with SIGSTOP preemptively checking 

1569 whether PID has been reused. 

1570 

1571 On Windows this has the effect of suspending all process threads. 

1572 """ 

1573 if POSIX: 

1574 self._send_signal(signal.SIGSTOP) 

1575 else: # pragma: no cover 

1576 self._raise_if_pid_reused() 

1577 self._proc.suspend() 

1578 

1579 def resume(self) -> None: 

1580 """Resume process execution with SIGCONT preemptively checking 

1581 whether PID has been reused. 

1582 

1583 On Windows this has the effect of resuming all process threads. 

1584 """ 

1585 if POSIX: 

1586 self._send_signal(signal.SIGCONT) 

1587 else: # pragma: no cover 

1588 self._raise_if_pid_reused() 

1589 self._proc.resume() 

1590 

1591 def terminate(self) -> None: 

1592 """Terminate the process with SIGTERM preemptively checking 

1593 whether PID has been reused. 

1594 

1595 On Windows this is an alias for `kill()`. 

1596 """ 

1597 if POSIX: 

1598 self._send_signal(signal.SIGTERM) 

1599 else: # pragma: no cover 

1600 self._raise_if_pid_reused() 

1601 self._proc.kill() 

1602 

1603 def kill(self) -> None: 

1604 """Kill the current process with SIGKILL preemptively checking 

1605 whether PID has been reused. 

1606 """ 

1607 if POSIX: 

1608 self._send_signal(signal.SIGKILL) 

1609 else: # pragma: no cover 

1610 self._raise_if_pid_reused() 

1611 self._proc.kill() 

1612 

1613 def wait(self, timeout: float | None = None) -> int | None: 

1614 """Wait for process to terminate, and if process is a child 

1615 of os.getpid(), also return its exit code, else None. 

1616 

1617 On Windows there's no such limitation (exit code is always 

1618 returned). 

1619 

1620 If the process is already terminated, immediately return None 

1621 instead of raising `NoSuchProcess`. 

1622 

1623 If *timeout* (in seconds) is specified and process is still 

1624 alive, raise `TimeoutExpired`. 

1625 

1626 If *timeout=0* either return immediately or raise 

1627 `TimeoutExpired` (non-blocking). 

1628 

1629 To wait for multiple Process objects use `psutil.wait_procs()`. 

1630 """ 

1631 if self.pid == 0: 

1632 msg = "can't wait for PID 0" 

1633 raise ValueError(msg) 

1634 if timeout is not None: 

1635 if not isinstance(timeout, (int, float)): 

1636 msg = f"timeout must be an int or float (got {type(timeout)})" 

1637 raise TypeError(msg) 

1638 if timeout < 0: 

1639 msg = f"timeout must be positive or zero (got {timeout})" 

1640 raise ValueError(msg) 

1641 

1642 if self._exitcode is not _SENTINEL: 

1643 return self._exitcode 

1644 

1645 try: 

1646 self._exitcode = self._proc.wait(timeout) 

1647 except TimeoutExpired as err: 

1648 exc = TimeoutExpired(timeout, pid=self.pid, name=self._name) 

1649 raise exc from err 

1650 

1651 return self._exitcode 

1652 

1653 

1654# The valid attr names which can be processed by Process.as_dict(attrs=...) 

1655# and process_iter(attrs=...). 

1656# fmt: off 

1657Process.attrs = frozenset( 

1658 x for x in dir(Process) if not x.startswith("_") and x not in 

1659 {'send_signal', 'suspend', 'resume', 'terminate', 'kill', 'wait', 

1660 'is_running', 'as_dict', 'parent', 'parents', 'children', 'rlimit', 

1661 'connections', 'memory_full_info', 'oneshot', 'info', 'attrs'} 

1662) 

1663# fmt: on 

1664 

1665 

1666# ===================================================================== 

1667# --- Popen class 

1668# ===================================================================== 

1669 

1670 

1671class Popen(Process): 

1672 """Same as `subprocess.Popen`, but in addition it provides all 

1673 `Process` methods in a single class. 

1674 

1675 For the following methods which are common to both classes, psutil 

1676 implementation takes precedence: 

1677 

1678 * `send_signal()` 

1679 * `terminate()` 

1680 * `kill()` 

1681 

1682 This is done in order to avoid killing another process in case its 

1683 PID has been reused, fixing BPO-6973. 

1684 

1685 >>> import psutil 

1686 >>> from subprocess import PIPE 

1687 >>> p = psutil.Popen(["python", "-c", "print 'hi'"], stdout=PIPE) 

1688 >>> p.name() 

1689 'python3' 

1690 >>> p.uids() 

1691 user(real=1000, effective=1000, saved=1000) 

1692 >>> p.username() 

1693 'giampaolo' 

1694 >>> p.communicate() 

1695 ('hi', None) 

1696 >>> p.terminate() 

1697 >>> p.wait(timeout=2) 

1698 0 

1699 >>> 

1700 """ 

1701 

1702 def __init__(self, *args, **kwargs): 

1703 # Explicitly avoid to raise NoSuchProcess in case the process 

1704 # spawned by subprocess.Popen terminates too quickly, see: 

1705 # https://github.com/giampaolo/psutil/issues/193 

1706 self.__subproc = subprocess.Popen(*args, **kwargs) 

1707 self._init(self.__subproc.pid, _ignore_nsp=True) 

1708 

1709 def __dir__(self): 

1710 return sorted(set(dir(Popen) + dir(subprocess.Popen))) 

1711 

1712 def __enter__(self) -> Popen: 

1713 if hasattr(self.__subproc, '__enter__'): 

1714 self.__subproc.__enter__() 

1715 return self 

1716 

1717 def __exit__(self, *args, **kwargs): 

1718 if hasattr(self.__subproc, '__exit__'): 

1719 return self.__subproc.__exit__(*args, **kwargs) 

1720 else: 

1721 if self.stdout: 

1722 self.stdout.close() 

1723 if self.stderr: 

1724 self.stderr.close() 

1725 try: 

1726 # Flushing a BufferedWriter may raise an error. 

1727 if self.stdin: 

1728 self.stdin.close() 

1729 finally: 

1730 # Wait for the process to terminate, to avoid zombies. 

1731 self.wait() 

1732 

1733 def __getattribute__(self, name): 

1734 try: 

1735 return object.__getattribute__(self, name) 

1736 except AttributeError: 

1737 try: 

1738 return object.__getattribute__(self.__subproc, name) 

1739 except AttributeError: 

1740 msg = f"{self.__class__!r} has no attribute {name!r}" 

1741 raise AttributeError(msg) from None 

1742 

1743 def wait(self, timeout: float | None = None) -> int | None: 

1744 if self.__subproc.returncode is not None: 

1745 return self.__subproc.returncode 

1746 ret = super().wait(timeout) 

1747 self.__subproc.returncode = ret 

1748 return ret 

1749 

1750 

1751# ===================================================================== 

1752# --- system processes related functions 

1753# ===================================================================== 

1754 

1755 

1756def pids() -> list[int]: 

1757 """Return a list of current running PIDs.""" 

1758 global _LOWEST_PID 

1759 ret = sorted(_psplatform.pids()) 

1760 _LOWEST_PID = ret[0] 

1761 return ret 

1762 

1763 

1764def pid_exists(pid: int) -> bool: 

1765 """Return True if *pid* exists in the current process list. 

1766 

1767 This is faster than doing `pid in psutil.pids()` and should be 

1768 preferred. 

1769 """ 

1770 if pid < 0: 

1771 return False 

1772 elif pid == 0 and POSIX: 

1773 # On POSIX we use os.kill() to determine PID existence. 

1774 # According to "man 2 kill" PID 0 has a special meaning 

1775 # though: it refers to <<every process in the process 

1776 # group of the calling process>> and that is not we want 

1777 # to do here. 

1778 return pid in pids() 

1779 else: 

1780 return _psplatform.pid_exists(pid) 

1781 

1782 

1783_pmap = {} 

1784_pids_reused = set() 

1785 

1786 

1787def process_iter( 

1788 attrs: Collection[str] | None = None, ad_value: Any = None 

1789) -> Iterator[Process]: 

1790 """Return a generator yielding a `Process` instance for all 

1791 running processes. 

1792 

1793 Every new `Process` instance is only created once and then cached 

1794 into an internal table which is updated every time this is used. 

1795 Cache can optionally be cleared via `process_iter.cache_clear()`. 

1796 

1797 The sorting order in which processes are yielded is based on 

1798 their PIDs. 

1799 

1800 *attrs* and *ad_value* have the same meaning as in 

1801 `Process.as_dict()`. 

1802 

1803 If *attrs* is specified, `Process.as_dict()` is called and the 

1804 results are cached, so that subsequent method calls (e.g. 

1805 `p.name()`) return cached values. Use `attrs=Process.attrs` to 

1806 retrieve all process info (slow). 

1807 

1808 If a method raises `AccessDenied` during pre-fetch, it will return 

1809 *ad_value* (default None) instead of raising. 

1810 """ 

1811 global _pmap 

1812 

1813 def add(pid): 

1814 proc = Process(pid) 

1815 pmap[proc.pid] = proc 

1816 return proc 

1817 

1818 def remove(pid): 

1819 pmap.pop(pid, None) 

1820 

1821 if attrs is not None: 

1822 if attrs == []: # deprecated in 8.0.0 

1823 msg = ( 

1824 "process_iter(attrs=[]) is deprecated; use " 

1825 "process_iter(attrs=Process.attrs) to retrieve all attributes" 

1826 ) 

1827 warnings.warn(msg, DeprecationWarning, stacklevel=2) 

1828 elif not attrs: 

1829 # as_dict() will resolve an empty list|tuple|set to "all 

1830 # attribute names", but it's ambiguous and should be 

1831 # signaled. 

1832 msg = ( 

1833 f"process_iter(attrs={attrs}) is ambiguous; use " 

1834 "process_iter(attrs=Process.attrs) to retrieve all attributes" 

1835 ) 

1836 warnings.warn(msg, UserWarning, stacklevel=2) 

1837 

1838 pmap = _pmap.copy() 

1839 a = set(pids()) 

1840 b = set(pmap) 

1841 new_pids = a - b 

1842 gone_pids = b - a 

1843 for pid in gone_pids: 

1844 remove(pid) 

1845 while _pids_reused: 

1846 pid = _pids_reused.pop() 

1847 debug(f"refreshing Process instance for reused PID {pid}") 

1848 remove(pid) 

1849 try: 

1850 ls = sorted(list(pmap.items()) + list(dict.fromkeys(new_pids).items())) 

1851 for pid, proc in ls: 

1852 try: 

1853 if proc is None: # new process 

1854 proc = add(pid) 

1855 proc._prefetch = {} # clear cache 

1856 proc._ad_value = _SENTINEL 

1857 if attrs is not None: 

1858 proc._prefetch = proc.as_dict( 

1859 attrs=attrs, ad_value=ad_value 

1860 ) 

1861 proc._ad_value = ad_value 

1862 yield proc 

1863 except ZombieProcess: 

1864 if proc is not None: 

1865 yield proc # zombie processes are still valid 

1866 except NoSuchProcess: 

1867 remove(pid) 

1868 finally: 

1869 _pmap = pmap 

1870 

1871 

1872process_iter.cache_clear = lambda: _pmap.clear() # noqa: PLW0108 

1873process_iter.cache_clear.__doc__ = "Clear process_iter() internal cache." 

1874 

1875 

1876def wait_procs( 

1877 procs: list[Process], 

1878 timeout: float | None = None, 

1879 callback: Callable[[Process], None] | None = None, 

1880) -> tuple[list[Process], list[Process]]: 

1881 """Convenience function which waits for a list of processes to 

1882 terminate. 

1883 

1884 Return a `(gone, alive)` tuple indicating which processes 

1885 are gone and which ones are still alive. 

1886 

1887 The gone ones will have a new `returncode` attribute indicating 

1888 process exit status (may be None). 

1889 

1890 *callback* is a function which gets called every time a process 

1891 terminates (a `Process` instance is passed as callback argument). 

1892 

1893 Function will return as soon as all processes terminate or when 

1894 *timeout* occurs. 

1895 

1896 Differently from `Process.wait()` it will not raise `TimeoutExpired` if 

1897 *timeout* occurs. 

1898 

1899 Typical use case is: 

1900 

1901 - send SIGTERM to a list of processes 

1902 - give them some time to terminate 

1903 - send SIGKILL to those ones which are still alive 

1904 

1905 Example: 

1906 

1907 >>> def on_terminate(proc): 

1908 ... print("process {} terminated".format(proc)) 

1909 ... 

1910 >>> for p in procs: 

1911 ... p.terminate() 

1912 ... 

1913 >>> gone, alive = wait_procs(procs, timeout=3, callback=on_terminate) 

1914 >>> for p in alive: 

1915 ... p.kill() 

1916 """ 

1917 

1918 def check_gone(proc, timeout): 

1919 try: 

1920 returncode = proc.wait(timeout=timeout) 

1921 except (TimeoutExpired, subprocess.TimeoutExpired): 

1922 pass 

1923 else: 

1924 if returncode is not None or not proc.is_running(): 

1925 # Set new Process instance attribute. 

1926 proc.returncode = returncode 

1927 gone.add(proc) 

1928 if callback is not None: 

1929 callback(proc) 

1930 

1931 if timeout is not None and not timeout >= 0: 

1932 msg = f"timeout must be a positive integer, got {timeout}" 

1933 raise ValueError(msg) 

1934 if callback is not None and not callable(callback): 

1935 msg = f"callback {callback!r} is not a callable" 

1936 raise TypeError(msg) 

1937 

1938 gone = set() 

1939 alive = set(procs) 

1940 if timeout is not None: 

1941 deadline = _timer() + timeout 

1942 

1943 while alive: 

1944 if timeout is not None and timeout <= 0: 

1945 break 

1946 for proc in alive: 

1947 # Make sure that every complete iteration (all processes) 

1948 # will last max 1 sec. 

1949 # We do this because we don't want to wait too long on a 

1950 # single process: in case it terminates too late other 

1951 # processes may disappear in the meantime and their PID 

1952 # reused. 

1953 max_timeout = 1.0 / len(alive) 

1954 if timeout is not None: 

1955 timeout = min((deadline - _timer()), max_timeout) 

1956 if timeout <= 0: 

1957 break 

1958 check_gone(proc, timeout) 

1959 else: 

1960 check_gone(proc, max_timeout) 

1961 alive = alive - gone # noqa: PLR6104 

1962 

1963 if alive: 

1964 # Last attempt over processes survived so far. 

1965 # timeout == 0 won't make this function wait any further. 

1966 for proc in alive: 

1967 check_gone(proc, 0) 

1968 alive = alive - gone # noqa: PLR6104 

1969 

1970 return (list(gone), list(alive)) 

1971 

1972 

1973# ===================================================================== 

1974# --- CPU related functions 

1975# ===================================================================== 

1976 

1977 

1978def cpu_count(logical: bool = True) -> int | None: 

1979 """Return the number of logical CPUs in the system (same as 

1980 `os.cpu_count()`). 

1981 

1982 If *logical* is False return the number of physical cores only 

1983 (e.g. hyper thread CPUs are excluded). 

1984 

1985 Return None if undetermined. 

1986 

1987 The return value is cached after first call. 

1988 If desired cache can be cleared like this: 

1989 

1990 >>> psutil.cpu_count.cache_clear() 

1991 """ 

1992 if logical: 

1993 ret = _psplatform.cpu_count_logical() 

1994 else: 

1995 ret = _psplatform.cpu_count_cores() 

1996 if ret is not None and ret < 1: 

1997 ret = None 

1998 return ret 

1999 

2000 

2001def cpu_times(percpu: bool = False) -> scputimes | list[scputimes]: 

2002 """Return system-wide CPU times as a named tuple. 

2003 

2004 Every CPU time represents the seconds the CPU has spent in the 

2005 given mode: 

2006 

2007 - `user` 

2008 - `system` 

2009 - `idle` 

2010 - `nice` (UNIX) 

2011 - `iowait` (Linux) 

2012 - `irq` (Linux, FreeBSD) 

2013 - `softirq` (Linux) 

2014 - `steal` (Linux) 

2015 - `guest` (Linux) 

2016 - `guest_nice` (Linux) 

2017 - `dpc` (Windows) 

2018 

2019 When *percpu* is True return a list of named tuples for each 

2020 logical CPU. First element of the list refers to first CPU, second 

2021 element to second CPU and so on. The order of the list is 

2022 consistent across calls. 

2023 """ 

2024 if not percpu: 

2025 return _psplatform.cpu_times() 

2026 else: 

2027 return _psplatform.per_cpu_times() 

2028 

2029 

2030try: 

2031 _last_cpu_times = {threading.current_thread().ident: cpu_times()} 

2032except Exception: # noqa: BLE001 

2033 # Don't want to crash at import time. 

2034 _last_cpu_times = {} 

2035 

2036try: 

2037 _last_per_cpu_times = { 

2038 threading.current_thread().ident: cpu_times(percpu=True) 

2039 } 

2040except Exception: # noqa: BLE001 

2041 # Don't want to crash at import time. 

2042 _last_per_cpu_times = {} 

2043 

2044 

2045def _cpu_tot_time(times): 

2046 """Given a `cpu_time()` named tuple calculates the total CPU time 

2047 (including idle time). 

2048 """ 

2049 tot = sum(times) 

2050 if LINUX: 

2051 # On Linux guest times are already accounted in "user" or 

2052 # "nice" times, so we subtract them from total. 

2053 # Htop does the same. References: 

2054 # https://github.com/giampaolo/psutil/pull/940 

2055 # http://unix.stackexchange.com/questions/178045 

2056 # https://github.com/torvalds/linux/blob/447976ef4/kernel/sched/cputime.c#L158 

2057 tot -= times.guest 

2058 tot -= times.guest_nice 

2059 return tot 

2060 

2061 

2062def _cpu_busy_time(times): 

2063 """Given a `cpu_time()` named tuple calculates the busy CPU time by 

2064 subtracting all idle CPU times. 

2065 """ 

2066 busy = _cpu_tot_time(times) 

2067 busy -= times.idle 

2068 # Linux: "iowait" is time during which the CPU does not do anything 

2069 # (waits for IO to complete). On Linux IO wait is *not* accounted 

2070 # in "idle" time so we subtract it. Htop does the same. 

2071 # References: 

2072 # https://github.com/torvalds/linux/blob/447976ef4/kernel/sched/cputime.c#L244 

2073 busy -= getattr(times, "iowait", 0) 

2074 return busy 

2075 

2076 

2077def _cpu_times_deltas(t1, t2): 

2078 assert t1._fields == t2._fields, (t1, t2) 

2079 field_deltas = [] 

2080 for field in _ntp.scputimes._fields: 

2081 field_delta = getattr(t2, field) - getattr(t1, field) 

2082 # CPU times are always supposed to increase over time 

2083 # or at least remain the same and that's because time 

2084 # cannot go backwards. 

2085 # Surprisingly sometimes this might not be the case (at 

2086 # least on Windows and Linux), see: 

2087 # https://github.com/giampaolo/psutil/issues/392 

2088 # https://github.com/giampaolo/psutil/issues/645 

2089 # https://github.com/giampaolo/psutil/issues/1210 

2090 # Trim negative deltas to zero to ignore decreasing fields. 

2091 # top does the same. Reference: 

2092 # https://gitlab.com/procps-ng/procps/blob/v3.3.12/top/top.c#L5063 

2093 field_delta = max(0, field_delta) 

2094 field_deltas.append(field_delta) 

2095 return _ntp.scputimes(*field_deltas) 

2096 

2097 

2098def cpu_percent( 

2099 interval: float | None = None, percpu: bool = False 

2100) -> float | list[float]: 

2101 """Return a float representing the current system-wide CPU 

2102 utilization as a percentage. 

2103 

2104 When *interval* is > 0.0 compares system CPU times elapsed before 

2105 and after the interval (blocking). 

2106 

2107 When *interval* is 0.0 or None compares system CPU times elapsed 

2108 since last call or module import, returning immediately (non 

2109 blocking). That means the first time this is called it will 

2110 return a meaningless 0.0 value which you should ignore. 

2111 In this case is recommended for accuracy that this function be 

2112 called with at least 0.1 seconds between calls. 

2113 

2114 When *percpu* is True returns a list of floats representing the 

2115 utilization as a percentage for each CPU. 

2116 First element of the list refers to first CPU, second element 

2117 to second CPU and so on. 

2118 The order of the list is consistent across calls. 

2119 

2120 Examples: 

2121 

2122 >>> # blocking, system-wide 

2123 >>> psutil.cpu_percent(interval=1) 

2124 2.0 

2125 >>> 

2126 >>> # blocking, per-cpu 

2127 >>> psutil.cpu_percent(interval=1, percpu=True) 

2128 [2.0, 1.0] 

2129 >>> 

2130 >>> # non-blocking (percentage since last call) 

2131 >>> psutil.cpu_percent(interval=None) 

2132 2.9 

2133 >>> 

2134 """ 

2135 tid = threading.current_thread().ident 

2136 blocking = interval is not None and interval > 0.0 

2137 if interval is not None and interval < 0: 

2138 msg = f"interval is not positive (got {interval})" 

2139 raise ValueError(msg) 

2140 

2141 def calculate(t1, t2): 

2142 times_delta = _cpu_times_deltas(t1, t2) 

2143 all_delta = _cpu_tot_time(times_delta) 

2144 busy_delta = _cpu_busy_time(times_delta) 

2145 

2146 try: 

2147 busy_perc = (busy_delta / all_delta) * 100 

2148 except ZeroDivisionError: 

2149 return 0.0 

2150 else: 

2151 return round(busy_perc, 1) 

2152 

2153 # system-wide usage 

2154 if not percpu: 

2155 if blocking: 

2156 t1 = cpu_times() 

2157 time.sleep(interval) 

2158 else: 

2159 t1 = _last_cpu_times.get(tid) or cpu_times() 

2160 _last_cpu_times[tid] = cpu_times() 

2161 return calculate(t1, _last_cpu_times[tid]) 

2162 # per-cpu usage 

2163 else: 

2164 ret = [] 

2165 if blocking: 

2166 tot1 = cpu_times(percpu=True) 

2167 time.sleep(interval) 

2168 else: 

2169 tot1 = _last_per_cpu_times.get(tid) or cpu_times(percpu=True) 

2170 _last_per_cpu_times[tid] = cpu_times(percpu=True) 

2171 for t1, t2 in zip(tot1, _last_per_cpu_times[tid]): 

2172 ret.append(calculate(t1, t2)) 

2173 return ret 

2174 

2175 

2176# Use a separate dict for cpu_times_percent(), so it's independent from 

2177# cpu_percent() and they can both be used within the same program. 

2178_last_cpu_times_2 = _last_cpu_times.copy() 

2179_last_per_cpu_times_2 = _last_per_cpu_times.copy() 

2180 

2181 

2182def cpu_times_percent( 

2183 interval: float | None = None, percpu: bool = False 

2184) -> scputimes | list[scputimes]: 

2185 """Same as `cpu_percent()`, but provides utilization percentages 

2186 for each specific CPU time as is returned by `cpu_times()`. 

2187 

2188 For instance, on Linux we'll get: 

2189 

2190 >>> cpu_times_percent() 

2191 cpupercent(user=4.8, nice=0.0, system=4.8, idle=90.5, iowait=0.0, 

2192 irq=0.0, softirq=0.0, steal=0.0, guest=0.0, guest_nice=0.0) 

2193 >>> 

2194 

2195 *interval* and *percpu* arguments have the same meaning as in 

2196 `cpu_percent()`. 

2197 """ 

2198 tid = threading.current_thread().ident 

2199 blocking = interval is not None and interval > 0.0 

2200 if interval is not None and interval < 0: 

2201 msg = f"interval is not positive (got {interval!r})" 

2202 raise ValueError(msg) 

2203 

2204 def calculate(t1, t2): 

2205 nums = [] 

2206 times_delta = _cpu_times_deltas(t1, t2) 

2207 all_delta = _cpu_tot_time(times_delta) 

2208 # "scale" is the value to multiply each delta with to get percentages. 

2209 # We use "max" to avoid division by zero (if all_delta is 0, then all 

2210 # fields are 0 so percentages will be 0 too. all_delta cannot be a 

2211 # fraction because cpu times are integers) 

2212 scale = 100.0 / max(1, all_delta) 

2213 for field_delta in times_delta: 

2214 field_perc = field_delta * scale 

2215 field_perc = round(field_perc, 1) 

2216 # make sure we don't return negative values or values over 100% 

2217 field_perc = min(max(0.0, field_perc), 100.0) 

2218 nums.append(field_perc) 

2219 return _ntp.scputimes(*nums) 

2220 

2221 # system-wide usage 

2222 if not percpu: 

2223 if blocking: 

2224 t1 = cpu_times() 

2225 time.sleep(interval) 

2226 else: 

2227 t1 = _last_cpu_times_2.get(tid) or cpu_times() 

2228 _last_cpu_times_2[tid] = cpu_times() 

2229 return calculate(t1, _last_cpu_times_2[tid]) 

2230 # per-cpu usage 

2231 else: 

2232 ret = [] 

2233 if blocking: 

2234 tot1 = cpu_times(percpu=True) 

2235 time.sleep(interval) 

2236 else: 

2237 tot1 = _last_per_cpu_times_2.get(tid) or cpu_times(percpu=True) 

2238 _last_per_cpu_times_2[tid] = cpu_times(percpu=True) 

2239 for t1, t2 in zip(tot1, _last_per_cpu_times_2[tid]): 

2240 ret.append(calculate(t1, t2)) 

2241 return ret 

2242 

2243 

2244def cpu_stats() -> scpustats: 

2245 """Return CPU statistics.""" 

2246 return _psplatform.cpu_stats() 

2247 

2248 

2249if hasattr(_psplatform, "cpu_freq"): 

2250 

2251 def cpu_freq(percpu: bool = False) -> scpufreq | list[scpufreq] | None: 

2252 """Return CPU frequency as a named tuple including current, 

2253 min and max frequency expressed in Mhz. 

2254 

2255 If *percpu* is True and the system supports per-cpu frequency 

2256 retrieval (Linux and FreeBSD), a list of frequencies is 

2257 returned for each CPU. If not, a list with one element is 

2258 returned. 

2259 """ 

2260 ret = _psplatform.cpu_freq() 

2261 if percpu: 

2262 return ret 

2263 else: 

2264 num_cpus = float(len(ret)) 

2265 if num_cpus == 0: 

2266 return None 

2267 elif num_cpus == 1: 

2268 return ret[0] 

2269 else: 

2270 currs, mins, maxs = 0.0, 0.0, 0.0 

2271 set_none = False 

2272 for cpu in ret: 

2273 currs += cpu.current 

2274 # On FreeBSD min/max are None if the sysctl value 

2275 # can't be parsed. 

2276 if cpu.min is None or cpu.max is None: 

2277 set_none = True 

2278 continue 

2279 mins += cpu.min 

2280 maxs += cpu.max 

2281 

2282 current = currs / num_cpus 

2283 

2284 if set_none: 

2285 min_ = max_ = None 

2286 else: 

2287 min_ = mins / num_cpus 

2288 max_ = maxs / num_cpus 

2289 

2290 return _ntp.scpufreq(current, min_, max_) 

2291 

2292 __all__.append("cpu_freq") 

2293 

2294 

2295def getloadavg() -> tuple[float, float, float]: 

2296 """Return the average system load over the last 1, 5 and 15 minutes 

2297 as a tuple. 

2298 

2299 On Windows this is emulated by using a Windows API that spawns a 

2300 thread which keeps running in background and updates results every 

2301 5 seconds, mimicking the UNIX behavior. 

2302 """ 

2303 if hasattr(os, "getloadavg"): 

2304 return os.getloadavg() 

2305 else: 

2306 return _psplatform.getloadavg() 

2307 

2308 

2309# ===================================================================== 

2310# --- system memory related functions 

2311# ===================================================================== 

2312 

2313 

2314def virtual_memory() -> svmem: 

2315 """Return statistics about system memory usage as a named tuple. 

2316 

2317 The fields vary by platform (see official doc), but the following 

2318 are present on all platforms: 

2319 

2320 - total: 

2321 total physical memory available 

2322 

2323 - available: 

2324 the memory that can be given instantly to processes without the 

2325 system going into swap. 

2326 This is calculated by summing different memory values depending 

2327 on the platform and it is supposed to be used to monitor actual 

2328 memory usage in a cross platform fashion. 

2329 

2330 - percent: 

2331 the percentage usage calculated as `(total - available) / total * 100` 

2332 

2333 - used: 

2334 memory used, calculated differently depending on the platform and 

2335 designed for informational purposes only 

2336 

2337 - free: 

2338 memory not being used at all (zeroed) that is readily available; 

2339 note that this doesn't reflect the actual memory available 

2340 (use 'available' instead) 

2341 

2342 The sum of `used` and `available` does not necessarily equal `total`. 

2343 

2344 On Windows `available` and `free` are the same. 

2345 """ 

2346 global _TOTAL_PHYMEM 

2347 ret = _psplatform.virtual_memory() 

2348 # cached for later use in Process.memory_percent() 

2349 _TOTAL_PHYMEM = ret.total 

2350 return ret 

2351 

2352 

2353def swap_memory() -> sswap: 

2354 """Return system swap memory statistics as a named tuple including 

2355 the following fields: 

2356 

2357 - total: total swap memory in bytes 

2358 - used: used swap memory in bytes 

2359 - free: free swap memory in bytes 

2360 - percent: the percentage usage 

2361 - sin: no. of bytes the system has swapped in from disk (cumulative) 

2362 - sout: no. of bytes the system has swapped out from disk (cumulative) 

2363 

2364 `sin` and `sout` on Windows are meaningless and always set to 0. 

2365 """ 

2366 return _psplatform.swap_memory() 

2367 

2368 

2369# ===================================================================== 

2370# --- disks/partitions related functions 

2371# ===================================================================== 

2372 

2373 

2374def disk_usage(path: str) -> sdiskusage: 

2375 """Return disk usage statistics about the given *path* as a 

2376 named tuple including total, used and free space expressed in bytes 

2377 plus the percentage usage. 

2378 """ 

2379 return _psplatform.disk_usage(path) 

2380 

2381 

2382def disk_partitions(all: bool = False) -> list[sdiskpart]: 

2383 """Return mounted partitions as a list of 

2384 (device, mountpoint, fstype, opts) named tuple. 

2385 

2386 `opts` field is a raw string separated by commas indicating mount 

2387 options which may vary depending on the platform. 

2388 

2389 If *all* parameter is False return physical devices only and ignore 

2390 all others. 

2391 """ 

2392 return _psplatform.disk_partitions(all) 

2393 

2394 

2395def disk_io_counters( 

2396 perdisk: bool = False, nowrap: bool = True 

2397) -> sdiskio | dict[str, sdiskio] | None: 

2398 """Return system disk I/O statistics as a named tuple including 

2399 the following fields: 

2400 

2401 - read_count: number of reads 

2402 - write_count: number of writes 

2403 - read_bytes: number of bytes read 

2404 - write_bytes: number of bytes written 

2405 - read_time: (not NetBSD, OpenBSD) time spent reading from 

2406 disk (in ms) 

2407 - write_time: (not NetBSD, OpenBSD) time spent writing to 

2408 disk (in ms) 

2409 

2410 Platform specific: 

2411 

2412 - busy_time: (Linux, FreeBSD) time spent doing actual I/Os (in ms) 

2413 - read_merged_count (Linux): number of merged reads 

2414 - write_merged_count (Linux): number of merged writes 

2415 

2416 If *perdisk* is True return the same information for every 

2417 physical disk as a dictionary with partition names as the keys. 

2418 

2419 If *nowrap* is True (default), counters that overflow and wrap to 

2420 zero are automatically adjusted so they never decrease (this can 

2421 happen on very busy or long-lived systems). 

2422 `disk_io_counters.cache_clear()` can be used to invalidate the 

2423 *nowrap* cache. 

2424 """ 

2425 kwargs = dict(perdisk=perdisk) if LINUX else {} 

2426 rawdict = _psplatform.disk_io_counters(**kwargs) 

2427 if not rawdict: 

2428 return {} if perdisk else None 

2429 if nowrap: 

2430 rawdict = _wrap_numbers(rawdict, 'psutil.disk_io_counters') 

2431 if perdisk: 

2432 for disk, fields in rawdict.items(): 

2433 rawdict[disk] = _ntp.sdiskio(*fields) 

2434 return rawdict 

2435 else: 

2436 return _ntp.sdiskio(*(sum(x) for x in zip(*rawdict.values()))) 

2437 

2438 

2439disk_io_counters.cache_clear = functools.partial( 

2440 _wrap_numbers.cache_clear, 'psutil.disk_io_counters' 

2441) 

2442disk_io_counters.cache_clear.__doc__ = "Clears nowrap argument cache" 

2443 

2444 

2445# ===================================================================== 

2446# --- network related functions 

2447# ===================================================================== 

2448 

2449 

2450def net_io_counters( 

2451 pernic: bool = False, nowrap: bool = True 

2452) -> snetio | dict[str, snetio] | None: 

2453 """Return network I/O statistics as a named tuple including 

2454 the following fields: 

2455 

2456 - bytes_sent: number of bytes sent 

2457 - bytes_recv: number of bytes received 

2458 - packets_sent: number of packets sent 

2459 - packets_recv: number of packets received 

2460 - errin: total number of errors while receiving 

2461 - errout: total number of errors while sending 

2462 - dropin: total number of incoming packets which were dropped 

2463 - dropout: total number of outgoing packets which were dropped 

2464 (always 0 on macOS and BSD) 

2465 

2466 If *pernic* is True return the same information for every 

2467 network interface as a dictionary with interface names as the 

2468 keys. 

2469 

2470 If *nowrap* is True (default), counters that overflow and wrap to 

2471 zero are automatically adjusted so they never decrease (this can 

2472 happen on very busy or long-lived systems). 

2473 `net_io_counters.cache_clear()` can be used to invalidate the 

2474 *nowrap* cache. 

2475 """ 

2476 rawdict = _psplatform.net_io_counters() 

2477 if not rawdict: 

2478 return {} if pernic else None 

2479 if nowrap: 

2480 rawdict = _wrap_numbers(rawdict, 'psutil.net_io_counters') 

2481 if pernic: 

2482 for nic, fields in rawdict.items(): 

2483 rawdict[nic] = _ntp.snetio(*fields) 

2484 return rawdict 

2485 else: 

2486 return _ntp.snetio(*[sum(x) for x in zip(*rawdict.values())]) 

2487 

2488 

2489net_io_counters.cache_clear = functools.partial( 

2490 _wrap_numbers.cache_clear, 'psutil.net_io_counters' 

2491) 

2492net_io_counters.cache_clear.__doc__ = "Clears nowrap argument cache" 

2493 

2494 

2495def net_connections(kind: str = 'inet') -> list[sconn]: 

2496 """Return system-wide socket connections as a list of 

2497 (fd, family, type, laddr, raddr, status, pid) named tuples. 

2498 

2499 In case of limited privileges `fd` and `pid` may be set to -1 

2500 and None respectively. 

2501 

2502 The *kind* parameter filters for connections that fit the 

2503 following criteria: 

2504 

2505 +------------+----------------------------------------------------+ 

2506 | Kind Value | Connections using | 

2507 +------------+----------------------------------------------------+ 

2508 | 'inet' | IPv4 and IPv6 | 

2509 | 'inet4' | IPv4 | 

2510 | 'inet6' | IPv6 | 

2511 | 'tcp' | TCP | 

2512 | 'tcp4' | TCP over IPv4 | 

2513 | 'tcp6' | TCP over IPv6 | 

2514 | 'udp' | UDP | 

2515 | 'udp4' | UDP over IPv4 | 

2516 | 'udp6' | UDP over IPv6 | 

2517 | 'unix' | UNIX socket (both UDP and TCP protocols) | 

2518 | 'all' | the sum of all the possible families and protocols | 

2519 +------------+----------------------------------------------------+ 

2520 

2521 On macOS this function requires root privileges. 

2522 """ 

2523 _check_conn_kind(kind) 

2524 return _psplatform.net_connections(kind) 

2525 

2526 

2527def net_if_addrs() -> dict[str, list[snicaddr]]: 

2528 """Return a dictionary mapping each NIC (Network Interface Card) to 

2529 a list of named tuples representing its addresses. Multiple 

2530 addresses of the same family can exist per interface. 

2531 

2532 The named tuple includes 5 fields (addresses may be None): 

2533 

2534 - family: the address family, either `AF_INET`, `AF_INET6`, 

2535 `psutil.AF_LINK` (a MAC address) or `AF_UNSPEC` (a virtual or 

2536 unconfigured NIC). 

2537 - address: the primary NIC address 

2538 - netmask: the netmask address 

2539 - broadcast: the broadcast address; always None on Windows 

2540 - ptp: a "point to point" address (typically a VPN); always None on 

2541 Windows 

2542 """ 

2543 rawlist = _psplatform.net_if_addrs() 

2544 rawlist.sort(key=lambda x: x[1]) # sort by family 

2545 ret = collections.defaultdict(list) 

2546 for name, fam, addr, mask, broadcast, ptp in rawlist: 

2547 try: 

2548 fam = socket.AddressFamily(fam) 

2549 except ValueError: 

2550 if WINDOWS and fam == -1: 

2551 fam = _psplatform.AF_LINK 

2552 elif ( 

2553 hasattr(_psplatform, "AF_LINK") and fam == _psplatform.AF_LINK 

2554 ): 

2555 # Linux defines AF_LINK as an alias for AF_PACKET. 

2556 # We re-set the family here so that repr(family) 

2557 # will show AF_LINK rather than AF_PACKET 

2558 fam = _psplatform.AF_LINK 

2559 

2560 if fam == _psplatform.AF_LINK: 

2561 # The underlying C function may return an incomplete MAC 

2562 # address in which case we fill it with null bytes, see: 

2563 # https://github.com/giampaolo/psutil/issues/786 

2564 separator = ":" if POSIX else "-" 

2565 while addr.count(separator) < 5: 

2566 addr += f"{separator}00" 

2567 

2568 nt = _ntp.snicaddr(fam, addr, mask, broadcast, ptp) 

2569 

2570 # On Windows broadcast is None, so we determine it via 

2571 # ipaddress module. On POSIX a /32 has no broadcast address, 

2572 # but getifaddrs() hands back the local address. 

2573 if nt.netmask and ( 

2574 fam == socket.AF_INET or (WINDOWS and fam == socket.AF_INET6) 

2575 ): 

2576 try: 

2577 calculated = _common.broadcast_addr(nt) 

2578 except Exception as err: # noqa: BLE001 

2579 warn(f"broadcast_addr() failed: {err!r}") 

2580 else: 

2581 if calculated is None: 

2582 nt = nt._replace(broadcast=None) 

2583 elif WINDOWS: 

2584 nt = nt._replace(broadcast=calculated) 

2585 

2586 ret[name].append(nt) 

2587 

2588 return dict(ret) 

2589 

2590 

2591def net_if_stats() -> dict[str, snicstats]: 

2592 """Return information about each NIC (network interface card) 

2593 installed on the system as a dictionary whose keys are the 

2594 NIC names and value is a named tuple with the following fields: 

2595 

2596 - isup: whether the interface is up (bool) 

2597 - duplex: can be either `NIC_DUPLEX_FULL`, `NIC_DUPLEX_HALF` or 

2598 `NIC_DUPLEX_UNKNOWN` 

2599 - speed: the NIC speed expressed in mega bits (MB); if it can't 

2600 be determined (e.g. 'localhost') it will be set to 0. 

2601 - mtu: the maximum transmission unit expressed in bytes. 

2602 - flags: a string of comma-separated flags on the interface. 

2603 """ 

2604 return _psplatform.net_if_stats() 

2605 

2606 

2607# ===================================================================== 

2608# --- sensors 

2609# ===================================================================== 

2610 

2611 

2612# Linux, macOS 

2613if hasattr(_psplatform, "sensors_temperatures"): 

2614 

2615 def sensors_temperatures( 

2616 fahrenheit: bool = False, 

2617 ) -> dict[str, list[shwtemp]]: 

2618 """Return hardware temperatures. 

2619 

2620 Each entry is a named tuple representing a certain hardware 

2621 sensor (it may be a CPU, an hard disk or something else, 

2622 depending on the OS and its configuration). 

2623 

2624 All temperatures are expressed in celsius unless *fahrenheit* 

2625 is set to True. 

2626 """ 

2627 

2628 def convert(n): 

2629 if n is not None: 

2630 return (float(n) * 9 / 5) + 32 if fahrenheit else n 

2631 

2632 ret = collections.defaultdict(list) 

2633 rawdict = _psplatform.sensors_temperatures() 

2634 

2635 for name, values in rawdict.items(): 

2636 while values: 

2637 label, current, high, critical = values.pop(0) 

2638 current = convert(current) 

2639 high = convert(high) 

2640 critical = convert(critical) 

2641 

2642 if high and not critical: 

2643 critical = high 

2644 elif critical and not high: 

2645 high = critical 

2646 

2647 ret[name].append(_ntp.shwtemp(label, current, high, critical)) 

2648 

2649 return dict(ret) 

2650 

2651 __all__.append("sensors_temperatures") 

2652 

2653 

2654# Linux 

2655if hasattr(_psplatform, "sensors_fans"): 

2656 

2657 def sensors_fans() -> dict[str, list[sfan]]: 

2658 """Return fans speed. Each entry is a named tuple 

2659 representing a certain hardware sensor. 

2660 All speed are expressed in RPM (rounds per minute). 

2661 """ 

2662 return _psplatform.sensors_fans() 

2663 

2664 __all__.append("sensors_fans") 

2665 

2666 

2667# Linux, Windows, FreeBSD, macOS 

2668if hasattr(_psplatform, "sensors_battery"): 

2669 

2670 def sensors_battery() -> sbattery | None: 

2671 """Return battery information. If no battery is installed 

2672 returns None. 

2673 

2674 - percent: battery power left as a percentage. 

2675 - secsleft: a rough approximation of how many seconds are left 

2676 before the battery runs out of power. May be 

2677 `POWER_TIME_UNLIMITED` or `POWER_TIME_UNKNOWN`. 

2678 - power_plugged: True if the AC power cable is connected. 

2679 """ 

2680 return _psplatform.sensors_battery() 

2681 

2682 __all__.append("sensors_battery") 

2683 

2684 

2685# ===================================================================== 

2686# --- other system related functions 

2687# ===================================================================== 

2688 

2689 

2690def boot_time() -> float: 

2691 """Return the system boot time expressed in seconds since the epoch 

2692 (seconds since January 1, 1970, at midnight UTC). 

2693 

2694 The returned value is based on the system clock, which means it may 

2695 be affected by changes such as manual adjustments or time 

2696 synchronization (e.g. NTP). 

2697 """ 

2698 return _psplatform.boot_time() 

2699 

2700 

2701def users() -> list[suser]: 

2702 """Return users currently connected on the system as a list of 

2703 named tuples including the following fields: 

2704 

2705 - user: the name of the user 

2706 - terminal: the tty or pseudo-tty associated with the user, if any. 

2707 - host: the host name associated with the entry, if any. 

2708 - started: the creation time as a floating point number expressed in 

2709 seconds since the epoch. 

2710 - pid: the PID of the login process (None on Windows and OpenBSD). 

2711 """ 

2712 return _psplatform.users() 

2713 

2714 

2715# ===================================================================== 

2716# --- Windows services 

2717# ===================================================================== 

2718 

2719 

2720if WINDOWS: 

2721 

2722 def win_service_iter() -> Iterator[WindowsService]: 

2723 """Return a generator yielding a `WindowsService` instance for 

2724 all Windows services installed. 

2725 """ 

2726 return _psplatform.win_service_iter() 

2727 

2728 def win_service_get(name) -> WindowsService: 

2729 """Get a Windows service by *name*. 

2730 

2731 Raise `NoSuchProcess` if no service with such name exists. 

2732 """ 

2733 return _psplatform.win_service_get(name) 

2734 

2735 

2736# ===================================================================== 

2737# --- malloc / heap 

2738# ===================================================================== 

2739 

2740 

2741# Linux + glibc, Windows, macOS, FreeBSD, NetBSD 

2742if hasattr(_psplatform, "heap_info"): 

2743 

2744 def heap_info() -> pheap: 

2745 """Return low-level heap statistics from the C heap allocator 

2746 (glibc). 

2747 

2748 - `heap_used`: the total number of bytes allocated via 

2749 malloc/free. These are typically allocations smaller than 

2750 MMAP_THRESHOLD. 

2751 

2752 - `mmap_used`: the total number of bytes allocated via `mmap()` 

2753 or via large ``malloc()`` allocations. 

2754 

2755 - `heap_count` (Windows only): number of private heaps created 

2756 via `HeapCreate()`. 

2757 """ 

2758 return _ntp.pheap(*_psplatform.heap_info()) 

2759 

2760 def heap_trim() -> None: 

2761 """Request that the underlying allocator free any unused memory 

2762 it's holding in the heap (typically small `malloc()` 

2763 allocations). 

2764 

2765 In practice, modern allocators rarely comply, so this is not a 

2766 general-purpose memory-reduction tool and won't meaningfully 

2767 shrink RSS in real programs. Its primary value is in **leak 

2768 detection tools**. 

2769 

2770 Calling `heap_trim()` before taking measurements helps reduce 

2771 allocator noise, giving you a cleaner baseline so that changes 

2772 in `heap_used` come from the code you're testing, not from 

2773 internal allocator caching or fragmentation. Its effectiveness 

2774 depends on allocator behavior and fragmentation patterns. 

2775 """ 

2776 _psplatform.heap_trim() 

2777 

2778 __all__.append("heap_info") 

2779 __all__.append("heap_trim") 

2780 

2781 

2782# ===================================================================== 

2783 

2784 

2785def _set_debug(value): 

2786 """Enable or disable `PSUTIL_DEBUG` option, which prints debugging 

2787 messages to stderr. 

2788 """ 

2789 import psutil._common 

2790 

2791 psutil._common.PSUTIL_DEBUG = bool(value) 

2792 _psutil.set_debug(bool(value)) 

2793 

2794 

2795del memoize_when_activated