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

1165 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_extras 

88 from ._ntuples import pmmap_ext 

89 from ._ntuples import pmmap_grouped 

90 from ._ntuples import popenfile 

91 from ._ntuples import ppagefaults 

92 from ._ntuples import pthread 

93 from ._ntuples import puids 

94 from ._ntuples import sbattery 

95 from ._ntuples import sconn 

96 from ._ntuples import scpufreq 

97 from ._ntuples import scpustats 

98 from ._ntuples import scputimes 

99 from ._ntuples import sdiskio 

100 from ._ntuples import sdiskpart 

101 from ._ntuples import sdiskusage 

102 from ._ntuples import sfan 

103 from ._ntuples import shwtemp 

104 from ._ntuples import snetio 

105 from ._ntuples import snicaddr 

106 from ._ntuples import snicstats 

107 from ._ntuples import sswap 

108 from ._ntuples import suser 

109 from ._ntuples import svmem 

110 from ._pswindows import WindowsService 

111 

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

113 STATUS_DEAD: ProcessStatus 

114 STATUS_DISK_SLEEP: ProcessStatus 

115 STATUS_IDLE: ProcessStatus 

116 STATUS_LOCKED: ProcessStatus 

117 STATUS_PARKED: ProcessStatus 

118 STATUS_RUNNING: ProcessStatus 

119 STATUS_SLEEPING: ProcessStatus 

120 STATUS_STOPPED: ProcessStatus 

121 STATUS_SUSPENDED: ProcessStatus 

122 STATUS_TRACING_STOP: ProcessStatus 

123 STATUS_WAITING: ProcessStatus 

124 STATUS_WAKE_KILL: ProcessStatus 

125 STATUS_WAKING: ProcessStatus 

126 STATUS_ZOMBIE: ProcessStatus 

127 

128 CONN_CLOSE: ConnectionStatus 

129 CONN_CLOSE_WAIT: ConnectionStatus 

130 CONN_CLOSING: ConnectionStatus 

131 CONN_ESTABLISHED: ConnectionStatus 

132 CONN_FIN_WAIT1: ConnectionStatus 

133 CONN_FIN_WAIT2: ConnectionStatus 

134 CONN_LAST_ACK: ConnectionStatus 

135 CONN_LISTEN: ConnectionStatus 

136 CONN_NONE: ConnectionStatus 

137 CONN_SYN_RECV: ConnectionStatus 

138 CONN_SYN_SENT: ConnectionStatus 

139 CONN_TIME_WAIT: ConnectionStatus 

140 CONN_BOUND: ConnectionStatus # SunOS 

141 CONN_DELETE_TCB: ConnectionStatus # Windows 

142 CONN_IDLE: ConnectionStatus # SunOS 

143 

144 NIC_DUPLEX_FULL: NicDuplex 

145 NIC_DUPLEX_HALF: NicDuplex 

146 NIC_DUPLEX_UNKNOWN: NicDuplex 

147 

148 POWER_TIME_UNKNOWN: BatteryTime 

149 POWER_TIME_UNLIMITED: BatteryTime 

150 

151 IOPRIO_CLASS_BE: ProcessIOPriority # Linux 

152 IOPRIO_CLASS_IDLE: ProcessIOPriority # Linux 

153 IOPRIO_CLASS_NONE: ProcessIOPriority # Linux 

154 IOPRIO_CLASS_RT: ProcessIOPriority # Linux 

155 IOPRIO_HIGH: ProcessIOPriority # Windows 

156 IOPRIO_LOW: ProcessIOPriority # Windows 

157 IOPRIO_NORMAL: ProcessIOPriority # Windows 

158 IOPRIO_VERYLOW: ProcessIOPriority # Windows 

159 

160 ABOVE_NORMAL_PRIORITY_CLASS: ProcessPriority # Windows 

161 BELOW_NORMAL_PRIORITY_CLASS: ProcessPriority # Windows 

162 HIGH_PRIORITY_CLASS: ProcessPriority # Windows 

163 IDLE_PRIORITY_CLASS: ProcessPriority # Windows 

164 NORMAL_PRIORITY_CLASS: ProcessPriority # Windows 

165 REALTIME_PRIORITY_CLASS: ProcessPriority # Windows 

166 

167 RLIMIT_AS: ProcessRlimit # Linux, FreeBSD 

168 RLIMIT_CORE: ProcessRlimit # Linux, FreeBSD 

169 RLIMIT_CPU: ProcessRlimit # Linux, FreeBSD 

170 RLIMIT_DATA: ProcessRlimit # Linux, FreeBSD 

171 RLIMIT_FSIZE: ProcessRlimit # Linux, FreeBSD 

172 RLIMIT_LOCKS: ProcessRlimit # Linux 

173 RLIMIT_MEMLOCK: ProcessRlimit # Linux, FreeBSD 

174 RLIMIT_MSGQUEUE: ProcessRlimit # Linux 

175 RLIMIT_NICE: ProcessRlimit # Linux 

176 RLIMIT_NOFILE: ProcessRlimit # Linux, FreeBSD 

177 RLIMIT_NPROC: ProcessRlimit # Linux, FreeBSD 

178 RLIMIT_NPTS: ProcessRlimit # FreeBSD 

179 RLIMIT_RSS: ProcessRlimit # Linux, FreeBSD 

180 RLIMIT_RTPRIO: ProcessRlimit # Linux 

181 RLIMIT_RTTIME: ProcessRlimit # Linux 

182 RLIMIT_SBSIZE: ProcessRlimit # FreeBSD 

183 RLIMIT_SIGPENDING: ProcessRlimit # Linux 

184 RLIMIT_STACK: ProcessRlimit # Linux, FreeBSD 

185 RLIMIT_SWAP: ProcessRlimit # FreeBSD 

186 RLIM_INFINITY: ProcessRlimit # Linux, FreeBSD 

187 

188 

189if LINUX: 

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

191 # via sys.modules. 

192 PROCFS_PATH = "/proc" 

193 

194 from . import _pslinux as _psplatform 

195 from ._enums import ProcessIOPriority 

196 from ._enums import ProcessRlimit 

197 

198elif WINDOWS: 

199 from . import _pswindows as _psplatform 

200 from ._enums import ProcessIOPriority 

201 from ._enums import ProcessPriority 

202 

203elif MACOS: 

204 from . import _psosx as _psplatform 

205 

206elif BSD: 

207 from . import _psbsd as _psplatform 

208 

209 if FREEBSD: 

210 from ._enums import ProcessRlimit 

211 

212elif SUNOS: 

213 from . import _pssunos as _psplatform 

214 

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

216 # _pssunos.py via sys.modules. 

217 PROCFS_PATH = "/proc" 

218 

219elif AIX: 

220 from . import _psaix as _psplatform 

221 

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

223 # via sys.modules. 

224 PROCFS_PATH = "/proc" 

225 

226else: # pragma: no cover 

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

228 raise NotImplementedError(msg) 

229 

230from . import _psutil 

231 

232# fmt: off 

233__all__ = [ 

234 # exceptions 

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

236 "TimeoutExpired", 

237 

238 # constants 

239 "version_info", "__version__", 

240 

241 "AF_LINK", 

242 

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

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

245 

246 # classes 

247 "Process", "Popen", 

248 

249 # functions 

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

251 "virtual_memory", "swap_memory", # memory 

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

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

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

255 "net_if_stats", 

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

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

258 "users", "boot_time", # others 

259 "bytes2human", 

260] 

261# fmt: on 

262 

263__all__.extend(_psplatform.__extra__all__) 

264_globals = globals() 

265 

266 

267def _export_enum(cls): 

268 __all__.append(cls.__name__) 

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

270 _globals[name] = member # noqa: F821 

271 __all__.append(name) 

272 

273 

274# Populate global namespace with enums and CONSTANTs. 

275_export_enum(ProcessStatus) 

276_export_enum(ConnectionStatus) 

277_export_enum(NicDuplex) 

278_export_enum(BatteryTime) 

279if LINUX or WINDOWS: 

280 _export_enum(ProcessIOPriority) 

281if WINDOWS: 

282 _export_enum(ProcessPriority) 

283if LINUX or FREEBSD: 

284 _export_enum(ProcessRlimit) 

285if LINUX or SUNOS or AIX: 

286 __all__.append("PROCFS_PATH") 

287 

288del _globals, _export_enum 

289 

290AF_LINK = _psplatform.AF_LINK 

291 

292__author__ = "Giampaolo Rodola'" 

293__version__ = "8.0.0" 

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

295 

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

297_TOTAL_PHYMEM = None 

298_LOWEST_PID = None 

299_SENTINEL = object() 

300 

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

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

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

304# was compiled for a different version of psutil. 

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

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

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

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

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

310 if hasattr(_psutil, 'version'): 

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

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

313 else: 

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

315 what = getattr( 

316 _psutil, 

317 "__file__", 

318 "the existing psutil install directory", 

319 ) 

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

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

322 raise ImportError(msg) 

323 

324 

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

326# --- Utils 

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

328 

329 

330if hasattr(_psplatform, 'ppid_map'): 

331 # Faster version (Windows and Linux). 

332 _ppid_map = _psplatform.ppid_map 

333else: # pragma: no cover 

334 

335 def _ppid_map(): 

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

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

338 """ 

339 ret = {} 

340 for pid in pids(): 

341 try: 

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

343 except (NoSuchProcess, ZombieProcess): 

344 pass 

345 return ret 

346 

347 

348def _pprint_secs(secs): 

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

350 now = time.time() 

351 secs_ago = int(now - secs) 

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

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

354 

355 

356def _check_conn_kind(kind): 

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

358 kinds = tuple(_common.conn_tmap) 

359 if kind not in kinds: 

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

361 raise ValueError(msg) 

362 

363 

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

365# --- Process class 

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

367 

368 

369def _use_prefetch(method): 

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

371 

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

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

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

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

376 a new system call. 

377 """ 

378 

379 @functools.wraps(method) 

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

381 if not args and not kwargs: 

382 try: 

383 return self._prefetch[method.__name__] 

384 except KeyError: 

385 pass 

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

387 

388 return wrapper 

389 

390 

391class Process: 

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

393 

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

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

396 

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

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

399 may end up retrieving information for a different process. 

400 

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

402 methods that set attributes or send signals. 

403 

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

405 `is_running()` before querying the process. 

406 """ 

407 

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

409 

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

411 self._init(pid) 

412 

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

414 if pid is None: 

415 pid = os.getpid() 

416 else: 

417 if pid < 0: 

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

419 raise ValueError(msg) 

420 try: 

421 _psutil.check_pid_range(pid) 

422 except OverflowError as err: 

423 msg = "process PID out of range" 

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

425 

426 self._pid = pid 

427 self._name = None 

428 self._exe = None 

429 self._create_time = None 

430 self._gone = False 

431 self._pid_reused = False 

432 self._hash = None 

433 self._lock = threading.RLock() 

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

435 self._ppid = None 

436 # platform-specific modules define an _psplatform.Process 

437 # implementation class 

438 self._proc = _psplatform.Process(pid) 

439 self._last_sys_cpu_times = None 

440 self._last_proc_cpu_times = None 

441 self._exitcode = _SENTINEL 

442 self._prefetch = {} 

443 self._ad_value = _SENTINEL 

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

445 try: 

446 self._ident = self._get_ident() 

447 except AccessDenied: 

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

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

450 # able to get create time for all PIDs. 

451 pass 

452 except ZombieProcess: 

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

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

455 pass 

456 except NoSuchProcess: 

457 if not _ignore_nsp: 

458 msg = "process PID not found" 

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

460 self._gone = True 

461 

462 def _is_ad_value(self, value): 

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

464 stored in place of a getter which raised AccessDenied. 

465 """ 

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

467 

468 def _get_ident(self): 

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

470 Process instance univocally over time. 

471 

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

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

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

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

476 recycling problem. 

477 

478 The reliability of this strategy mostly depends on 

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

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

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

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

483 practically it should be good enough. 

484 

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

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

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

488 is stable across clock updates. 

489 

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

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

492 """ 

493 

494 if WINDOWS: 

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

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

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

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

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

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

501 return (self.pid, self._create_time) 

502 elif LINUX or NETBSD or OSX: 

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

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

505 # time. 

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

507 else: 

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

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

510 self.create_time() 

511 return (self.pid, None) 

512 

513 def __str__(self): 

514 info = {} 

515 info["pid"] = self.pid 

516 with self.oneshot(): 

517 if self._pid_reused: 

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

519 else: 

520 try: 

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

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

523 except ZombieProcess: 

524 info["status"] = "zombie" 

525 except NoSuchProcess: 

526 info["status"] = "terminated" 

527 except AccessDenied: 

528 pass 

529 

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

531 info["exitcode"] = self._exitcode 

532 if self._create_time is not None: 

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

534 

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

536 self.__class__.__module__, 

537 self.__class__.__name__, 

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

539 ) 

540 

541 __repr__ = __str__ 

542 

543 @staticmethod 

544 def _cmp_idents(ident1, ident2): 

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

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

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

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

549 process. 

550 """ 

551 pid1, ctime1 = ident1 

552 pid2, ctime2 = ident2 

553 if pid1 != pid2: 

554 return "different" 

555 if not ctime1 or not ctime2: 

556 return "unknown" 

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

558 

559 def __eq__(self, other): 

560 # Test for equality with another Process object based 

561 # on PID and creation time. 

562 if not isinstance(other, Process): 

563 return NotImplemented 

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

565 

566 def __ne__(self, other): 

567 return not self == other 

568 

569 def __hash__(self): 

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

571 # equal objects must hash the same. 

572 if self._hash is None: 

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

574 return self._hash 

575 

576 def _raise_if_pid_reused(self): 

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

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

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

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

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

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

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

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

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

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

587 

588 @property 

589 def pid(self) -> int: 

590 """The process PID.""" 

591 return self._pid 

592 

593 # DEPRECATED 

594 @property 

595 def info(self) -> dict: 

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

597 

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

599 """ 

600 msg = ( 

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

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

603 ) 

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

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

606 # corrupting the prefetch cache. 

607 return self._prefetch.copy() 

608 

609 # --- utility methods 

610 

611 @contextlib.contextmanager 

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

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

614 process attributes at the same time. 

615 

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

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

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

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

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

621 retrieve more than one attribute about the process. 

622 

623 >>> import psutil 

624 >>> p = psutil.Process() 

625 >>> with p.oneshot(): 

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

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

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

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

630 ... 

631 >>> 

632 """ 

633 with self._lock: 

634 if hasattr(self, "_cache"): 

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

636 # context twice: 

637 # 

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

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

640 # ... 

641 # 

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

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

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

645 # against that: 

646 # 

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

648 # ... p.as_dict() 

649 # ... 

650 yield 

651 else: 

652 try: 

653 self.cpu_times.cache_activate(self) 

654 # cached in case memory_percent() is used 

655 self.memory_info.cache_activate(self) 

656 # cached in case parent() is used 

657 self.ppid.cache_activate(self) 

658 # cached in case username() is used 

659 if POSIX: 

660 self.uids.cache_activate(self) 

661 # specific implementation cache 

662 self._proc.oneshot_enter() 

663 yield 

664 finally: 

665 self.cpu_times.cache_deactivate(self) 

666 self.memory_info.cache_deactivate(self) 

667 self.ppid.cache_deactivate(self) 

668 if POSIX: 

669 self.uids.cache_deactivate(self) 

670 self._proc.oneshot_exit() 

671 

672 def as_dict( 

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

674 ) -> dict[str, Any]: 

675 """Utility method returning process information as a 

676 hashable dictionary. 

677 

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

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

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

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

682 

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

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

685 retrieving that particular process information. 

686 """ 

687 valid_names = self.attrs 

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

689 # explicitly requested. 

690 deprecated_names = {"memory_full_info"} 

691 

692 if attrs is not None: 

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

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

695 raise TypeError(msg) 

696 attrs = set(attrs) 

697 invalid_names = attrs - valid_names - deprecated_names 

698 if invalid_names: 

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

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

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

702 ) 

703 raise ValueError(msg) 

704 

705 retdict = {} 

706 names = attrs or sorted(valid_names) 

707 with self.oneshot(): 

708 for name in names: 

709 try: 

710 if name == 'pid': 

711 ret = self.pid 

712 else: 

713 meth = getattr(self, name) 

714 ret = meth() 

715 except (AccessDenied, ZombieProcess): 

716 ret = ad_value 

717 except NotImplementedError: 

718 # in case of not implemented functionality (may happen 

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

720 # the user explicitly asked for that particular attr 

721 if attrs: 

722 raise 

723 continue 

724 retdict[name] = ret 

725 return retdict 

726 

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

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

729 checking whether PID has been reused. 

730 

731 If no parent is known return None. 

732 """ 

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

734 if self.pid == lowest_pid: 

735 return None 

736 ppid = self.ppid() 

737 if ppid is not None: 

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

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

740 # where it's supported. 

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

742 try: 

743 parent = Process(ppid) 

744 if parent.create_time() <= proc_ctime: 

745 return parent 

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

747 except NoSuchProcess: 

748 pass 

749 

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

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

752 instances. 

753 

754 If no parents are known return an empty list. 

755 """ 

756 parents = [] 

757 proc = self.parent() 

758 while proc is not None: 

759 parents.append(proc) 

760 proc = proc.parent() 

761 return parents 

762 

763 def is_running(self) -> bool: 

764 """Return whether this process is running. 

765 

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

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

768 internal cache and return False. 

769 """ 

770 if self._gone or self._pid_reused: 

771 return False 

772 try: 

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

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

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

776 other = Process(self.pid) 

777 self._pid_reused = self != other 

778 if self._pid_reused: 

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

780 _pids_reused.add(self.pid) 

781 raise NoSuchProcess(self.pid) 

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

783 debug( 

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

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

786 ) 

787 return True 

788 except ZombieProcess: 

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

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

791 return True 

792 except NoSuchProcess: 

793 self._gone = True 

794 return False 

795 

796 # --- actual API 

797 

798 @_use_prefetch 

799 @memoize_when_activated 

800 def ppid(self) -> int: 

801 """The process parent PID. 

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

803 """ 

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

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

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

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

808 self._raise_if_pid_reused() 

809 if POSIX: 

810 return self._proc.ppid() 

811 else: # pragma: no cover 

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

813 return self._ppid 

814 

815 @_use_prefetch 

816 def name(self) -> str: 

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

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

819 # change, see: 

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

821 if WINDOWS and self._name is not None: 

822 return self._name 

823 name = self._proc.name() 

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

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

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

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

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

829 try: 

830 cmdline = self.cmdline() 

831 except (AccessDenied, ZombieProcess): 

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

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

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

835 # cmdline(), see: 

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

837 pass 

838 else: 

839 if cmdline: 

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

841 if extended_name.startswith(name): 

842 name = extended_name 

843 self._name = name 

844 self._proc._name = name 

845 return name 

846 

847 @_use_prefetch 

848 def exe(self) -> str: 

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

850 

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

852 first call. 

853 """ 

854 

855 def guess_it(fallback): 

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

857 # exe representation 

858 cmdline = self.cmdline() 

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

860 exe = cmdline[0] # the possible exe 

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

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

863 # changed cwd. 

864 if ( 

865 os.path.isabs(exe) 

866 and os.path.isfile(exe) 

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

868 ): 

869 return exe 

870 if isinstance(fallback, AccessDenied): 

871 raise fallback 

872 return fallback 

873 

874 if self._exe is None: 

875 try: 

876 exe = self._proc.exe() 

877 except AccessDenied as err: 

878 return guess_it(fallback=err) 

879 else: 

880 if not exe: 

881 # underlying implementation can legitimately return an 

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

883 # raise AD while guessing from the cmdline 

884 try: 

885 exe = guess_it(fallback=exe) 

886 except AccessDenied: 

887 pass 

888 self._exe = exe 

889 return self._exe 

890 

891 @_use_prefetch 

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

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

894 return self._proc.cmdline() 

895 

896 @_use_prefetch 

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

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

899 try: 

900 return self._proc.status() 

901 except ZombieProcess: 

902 return ProcessStatus.STATUS_ZOMBIE 

903 

904 @_use_prefetch 

905 def username(self) -> str: 

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

907 

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

909 """ 

910 if POSIX: 

911 if pwd is None: 

912 # might happen if python was installed from sources 

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

914 raise ImportError(msg) 

915 uids = self.uids() 

916 if self._is_ad_value(uids): 

917 return uids 

918 real_uid = uids.real 

919 try: 

920 return pwd.getpwuid(real_uid).pw_name 

921 except KeyError: 

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

923 return str(real_uid) 

924 else: 

925 return self._proc.username() 

926 

927 @_use_prefetch 

928 def create_time(self) -> float: 

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

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

931 1970, at midnight UTC). 

932 

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

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

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

936 """ 

937 if self._create_time is None: 

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

939 return self._create_time 

940 

941 @_use_prefetch 

942 def cwd(self) -> str: 

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

944 return self._proc.cwd() 

945 

946 @_use_prefetch 

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

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

949 if value is None: 

950 return self._proc.nice_get() 

951 else: 

952 self._raise_if_pid_reused() 

953 self._proc.nice_set(value) 

954 

955 if POSIX: 

956 

957 @_use_prefetch 

958 @memoize_when_activated 

959 def uids(self) -> puids: 

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

961 named tuple. 

962 """ 

963 return self._proc.uids() 

964 

965 @_use_prefetch 

966 def gids(self) -> pgids: 

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

968 named tuple. 

969 """ 

970 return self._proc.gids() 

971 

972 @_use_prefetch 

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

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

975 else None. 

976 """ 

977 return self._proc.terminal() 

978 

979 @_use_prefetch 

980 def num_fds(self) -> int: 

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

982 process (POSIX only). 

983 """ 

984 return self._proc.num_fds() 

985 

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

987 

988 @_use_prefetch 

989 def io_counters(self) -> pio: 

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

991 written bytes). 

992 

993 Availability: Linux, Windows, BSD, AIX 

994 """ 

995 return self._proc.io_counters() 

996 

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

998 

999 @_use_prefetch 

1000 def ionice( 

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

1002 ) -> pionice | ProcessIOPriority | None: 

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

1004 

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

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

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

1008 

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

1010 one of the `IOPRIO_*` constants. 

1011 

1012 Availability: Linux, Windows 

1013 """ 

1014 if ioclass is None: 

1015 if value is not None: 

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

1017 raise ValueError(msg) 

1018 return self._proc.ionice_get() 

1019 else: 

1020 self._raise_if_pid_reused() 

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

1022 

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

1024 

1025 def rlimit( 

1026 self, 

1027 resource: int, 

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

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

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

1031 tuple. 

1032 

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

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

1035 

1036 See "man prlimit" for further info. 

1037 

1038 Availability: Linux, FreeBSD 

1039 """ 

1040 if limits is not None: 

1041 self._raise_if_pid_reused() 

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

1043 

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

1045 

1046 @_use_prefetch 

1047 def cpu_affinity( 

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

1049 ) -> list[int] | None: 

1050 """Get or set process CPU affinity. 

1051 

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

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

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

1055 

1056 Availability: Linux, Windows, FreeBSD 

1057 """ 

1058 if cpus is None: 

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

1060 else: 

1061 self._raise_if_pid_reused() 

1062 if not cpus: 

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

1064 cpus = self._proc._get_eligible_cpus() 

1065 else: 

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

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

1068 

1069 # Linux, FreeBSD, SunOS 

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

1071 

1072 @_use_prefetch 

1073 def cpu_num(self) -> int: 

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

1075 

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

1077 """ 

1078 return self._proc.cpu_num() 

1079 

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

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

1082 

1083 @_use_prefetch 

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

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

1086 

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

1088 started. 

1089 """ 

1090 return self._proc.environ() 

1091 

1092 if WINDOWS: 

1093 

1094 @_use_prefetch 

1095 def num_handles(self) -> int: 

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

1097 

1098 Availability: Windows 

1099 """ 

1100 return self._proc.num_handles() 

1101 

1102 @_use_prefetch 

1103 def num_ctx_switches(self) -> pctxsw: 

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

1105 switches performed by this process. 

1106 """ 

1107 return self._proc.num_ctx_switches() 

1108 

1109 @_use_prefetch 

1110 def num_threads(self) -> int: 

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

1112 return self._proc.num_threads() 

1113 

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

1115 

1116 @_use_prefetch 

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

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

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

1120 

1121 On OpenBSD this method requires root access. 

1122 """ 

1123 return self._proc.threads() 

1124 

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

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

1127 instances, preemptively checking whether PID has been reused. 

1128 

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

1130 

1131 Example (A == this process): 

1132 

1133 A ─┐ 

1134 │ 

1135 ├─ B (child) ─┐ 

1136 │ └─ X (grandchild) ─┐ 

1137 │ └─ Y (great grandchild) 

1138 ├─ C (child) 

1139 └─ D (child) 

1140 

1141 >>> import psutil 

1142 >>> p = psutil.Process() 

1143 >>> p.children() 

1144 B, C, D 

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

1146 B, X, Y, C, D 

1147 

1148 Note that in the example above if process X disappears 

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

1150 is lost. 

1151 """ 

1152 self._raise_if_pid_reused() 

1153 ppid_map = _ppid_map() 

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

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

1156 # supported. 

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

1158 ret = [] 

1159 if not recursive: 

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

1161 if ppid == self.pid: 

1162 try: 

1163 child = Process(pid) 

1164 # if child happens to be older than its parent 

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

1166 if proc_ctime <= child.create_time(): 

1167 ret.append(child) 

1168 except (NoSuchProcess, ZombieProcess): 

1169 pass 

1170 else: 

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

1172 reverse_ppid_map = collections.defaultdict(list) 

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

1174 reverse_ppid_map[ppid].append(pid) 

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

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

1177 seen = set() 

1178 stack = [self.pid] 

1179 while stack: 

1180 pid = stack.pop() 

1181 if pid in seen: 

1182 # Since pids can be reused while the ppid_map is 

1183 # constructed, there may be rare instances where 

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

1185 continue 

1186 seen.add(pid) 

1187 for child_pid in reverse_ppid_map[pid]: 

1188 try: 

1189 child = Process(child_pid) 

1190 # if child happens to be older than its parent 

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

1192 intime = proc_ctime <= child.create_time() 

1193 if intime: 

1194 ret.append(child) 

1195 stack.append(child_pid) 

1196 except (NoSuchProcess, ZombieProcess): 

1197 pass 

1198 return ret 

1199 

1200 @_use_prefetch 

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

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

1203 utilization as a percentage. 

1204 

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

1206 to system CPU times elapsed since last call, returning 

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

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

1209 

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

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

1212 

1213 In this case is recommended for accuracy that this function 

1214 be called with at least 0.1 seconds between calls. 

1215 

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

1217 multiple threads on different CPU cores. 

1218 

1219 The returned value is explicitly NOT split evenly between 

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

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

1222 having 100% CPU utilization instead of 50%. 

1223 

1224 Examples: 

1225 

1226 >>> import psutil 

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

1228 >>> # blocking 

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

1230 2.0 

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

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

1233 2.9 

1234 >>> 

1235 """ 

1236 blocking = interval is not None and interval > 0.0 

1237 if interval is not None and interval < 0: 

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

1239 raise ValueError(msg) 

1240 num_cpus = cpu_count() or 1 

1241 

1242 def timer(): 

1243 return _timer() * num_cpus 

1244 

1245 if blocking: 

1246 st1 = timer() 

1247 pt1 = self._proc.cpu_times() 

1248 time.sleep(interval) 

1249 st2 = timer() 

1250 pt2 = self._proc.cpu_times() 

1251 else: 

1252 st1 = self._last_sys_cpu_times 

1253 pt1 = self._last_proc_cpu_times 

1254 st2 = timer() 

1255 pt2 = self._proc.cpu_times() 

1256 if st1 is None or pt1 is None: 

1257 self._last_sys_cpu_times = st2 

1258 self._last_proc_cpu_times = pt2 

1259 return 0.0 

1260 

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

1262 delta_time = st2 - st1 

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

1264 self._last_sys_cpu_times = st2 

1265 self._last_proc_cpu_times = pt2 

1266 

1267 try: 

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

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

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

1271 overall_cpus_percent = (delta_proc / delta_time) * 100 

1272 except ZeroDivisionError: 

1273 # interval was too low 

1274 return 0.0 

1275 else: 

1276 # Note 1: 

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

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

1279 # having 100% (or more) usage. 

1280 # 

1281 # Note 2: 

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

1283 # instead. 

1284 # 

1285 # Note 3: 

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

1287 # process with multiple threads running on different CPU 

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

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

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

1291 single_cpu_percent = overall_cpus_percent * num_cpus 

1292 return round(single_cpu_percent, 1) 

1293 

1294 @_use_prefetch 

1295 @memoize_when_activated 

1296 def cpu_times(self) -> pcputimes: 

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

1298 named tuple representing the accumulated process time, 

1299 expressed in seconds. 

1300 

1301 Linux includes an additional `iowait` field. 

1302 

1303 On macOS and Windows `children_user` and `children_system` 

1304 fields are always set to 0. 

1305 """ 

1306 return self._proc.cpu_times() 

1307 

1308 @_use_prefetch 

1309 @memoize_when_activated 

1310 def memory_info(self) -> pmem: 

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

1312 platform, representing memory information about the process. 

1313 

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

1315 

1316 All numbers are expressed in bytes. 

1317 """ 

1318 return self._proc.memory_info() 

1319 

1320 # Linux, macOS, Windows 

1321 if hasattr(_psplatform.Process, "memory_extras"): 

1322 

1323 @_use_prefetch 

1324 def memory_extras(self) -> pmem_extras: 

1325 """Return a named tuple with extra platform-specific memory 

1326 metrics, complementing `memory_info()`. 

1327 

1328 All numbers are expressed in bytes. 

1329 """ 

1330 return self._proc.memory_extras() 

1331 

1332 # Linux, macOS, Windows 

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

1334 

1335 @_use_prefetch 

1336 def memory_footprint(self) -> pfootprint: 

1337 """Return a named tuple with USS and shared memory, and 

1338 on Linux also PSS and swap. 

1339 

1340 These values provide a more accurate representation of 

1341 actual process memory usage. 

1342 

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

1344 be freed if the process was terminated right now. 

1345 

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

1347 such it usually requires higher user privileges than 

1348 `memory_info()` or `memory_extras()` and is considerably 

1349 slower. 

1350 """ 

1351 return self._proc.memory_footprint() 

1352 

1353 # DEPRECATED 

1354 def memory_full_info(self) -> pfullmem: 

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

1356 `memory_footprint()`'s `uss`, `pss` and `swap` fields in a 

1357 single named tuple. 

1358 

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

1360 """ 

1361 msg = ( 

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

1363 ) 

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

1365 basic_mem = self.memory_info() 

1366 if self._is_ad_value(basic_mem): 

1367 return basic_mem 

1368 if hasattr(self, "memory_footprint"): 

1369 fp = self.memory_footprint() 

1370 if self._is_ad_value(fp): 

1371 return fp 

1372 names = _ntp.pfullmem._fields[len(basic_mem) :] 

1373 return _ntp.pfullmem(*basic_mem, *[getattr(fp, x) for x in names]) 

1374 return _ntp.pfullmem(*basic_mem) 

1375 

1376 @_use_prefetch 

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

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

1379 calculate process memory utilization as a percentage. 

1380 

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

1382 process memory you want to compare against (defaults to 

1383 "rss"). It can be any field of `memory_info()`, 

1384 `memory_extras()` or `memory_footprint()`. The divisor is 

1385 always total physical memory, regardless of *memtype*. 

1386 """ 

1387 valid_types = list(_ntp.pmem._fields) 

1388 if hasattr(_ntp, "pmem_extras"): 

1389 valid_types += [ 

1390 f for f in _ntp.pmem_extras._fields if f not in valid_types 

1391 ] 

1392 if hasattr(_ntp, "pfootprint"): 

1393 valid_types += [ 

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

1395 ] 

1396 if memtype not in valid_types: 

1397 msg = ( 

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

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

1400 ) 

1401 raise ValueError(msg) 

1402 

1403 if memtype in _ntp.pmem._fields: 

1404 fun = self.memory_info 

1405 elif ( 

1406 hasattr(_ntp, "pmem_extras") 

1407 and memtype in _ntp.pmem_extras._fields 

1408 ): 

1409 fun = self.memory_extras 

1410 else: 

1411 fun = self.memory_footprint 

1412 

1413 metrics = fun() 

1414 if self._is_ad_value(metrics): 

1415 return metrics 

1416 value = getattr(metrics, memtype) 

1417 

1418 # use cached value if available 

1419 total_phymem = _TOTAL_PHYMEM or virtual_memory().total 

1420 if not total_phymem > 0: 

1421 # we should never get here 

1422 msg = ( 

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

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

1425 ) 

1426 raise ValueError(msg) 

1427 return round((value / float(total_phymem)) * 100, 2) 

1428 

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

1430 

1431 @_use_prefetch 

1432 def memory_maps( 

1433 self, grouped: bool = True 

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

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

1436 tuples whose fields are variable depending on the platform. 

1437 

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

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

1440 

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

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

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

1444 """ 

1445 

1446 it = self._proc.memory_maps() 

1447 if grouped: 

1448 d = {} 

1449 for tupl in it: 

1450 path = tupl[2] 

1451 nums = tupl[3:] 

1452 try: 

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

1454 except KeyError: 

1455 d[path] = nums 

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

1457 else: 

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

1459 

1460 @_use_prefetch 

1461 def page_faults(self) -> ppagefaults: 

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

1463 `(minor, major)` named tuple. 

1464 

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

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

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

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

1469 disk I/O. 

1470 

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

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

1473 process until I/O completes. 

1474 

1475 Both counters are cumulative since process creation. 

1476 """ 

1477 return self._proc.page_faults() 

1478 

1479 @_use_prefetch 

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

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

1482 named tuples including the absolute file name and file 

1483 descriptor number. 

1484 

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

1486 `flags` fields. 

1487 """ 

1488 return self._proc.open_files() 

1489 

1490 @_use_prefetch 

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

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

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

1494 

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

1496 following criteria: 

1497 

1498 +------------+----------------------------------------------------+ 

1499 | Kind Value | Connections using | 

1500 +------------+----------------------------------------------------+ 

1501 | 'inet' | IPv4 and IPv6 | 

1502 | 'inet4' | IPv4 | 

1503 | 'inet6' | IPv6 | 

1504 | 'tcp' | TCP | 

1505 | 'tcp4' | TCP over IPv4 | 

1506 | 'tcp6' | TCP over IPv6 | 

1507 | 'udp' | UDP | 

1508 | 'udp4' | UDP over IPv4 | 

1509 | 'udp6' | UDP over IPv6 | 

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

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

1512 +------------+----------------------------------------------------+ 

1513 """ 

1514 _check_conn_kind(kind) 

1515 return self._proc.net_connections(kind) 

1516 

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

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

1519 return self.net_connections(kind=kind) 

1520 

1521 # --- signals 

1522 

1523 if POSIX: 

1524 

1525 def _send_signal(self, sig): 

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

1527 self._raise_if_pid_reused() 

1528 

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

1530 if pid == 0: 

1531 # see "man 2 kill" 

1532 msg = ( 

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

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

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

1536 ) 

1537 raise ValueError(msg) 

1538 try: 

1539 os.kill(pid, sig) 

1540 except ProcessLookupError as err: 

1541 if OPENBSD and pid_exists(pid): 

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

1543 # zombie processes. 

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

1545 self._gone = True 

1546 raise NoSuchProcess(pid, name) from err 

1547 except PermissionError as err: 

1548 raise AccessDenied(pid, name) from err 

1549 

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

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

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

1553 

1554 On Windows only SIGTERM, CTRL_C_EVENT and CTRL_BREAK_EVENT 

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

1556 """ 

1557 if POSIX: 

1558 self._send_signal(sig) 

1559 else: # pragma: no cover 

1560 self._raise_if_pid_reused() 

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

1562 msg = "process no longer exists" 

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

1564 self._proc.send_signal(sig) 

1565 

1566 def suspend(self) -> None: 

1567 """Suspend process execution with SIGSTOP preemptively checking 

1568 whether PID has been reused. 

1569 

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

1571 """ 

1572 if POSIX: 

1573 self._send_signal(signal.SIGSTOP) 

1574 else: # pragma: no cover 

1575 self._raise_if_pid_reused() 

1576 self._proc.suspend() 

1577 

1578 def resume(self) -> None: 

1579 """Resume process execution with SIGCONT preemptively checking 

1580 whether PID has been reused. 

1581 

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

1583 """ 

1584 if POSIX: 

1585 self._send_signal(signal.SIGCONT) 

1586 else: # pragma: no cover 

1587 self._raise_if_pid_reused() 

1588 self._proc.resume() 

1589 

1590 def terminate(self) -> None: 

1591 """Terminate the process with SIGTERM preemptively checking 

1592 whether PID has been reused. 

1593 

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

1595 """ 

1596 if POSIX: 

1597 self._send_signal(signal.SIGTERM) 

1598 else: # pragma: no cover 

1599 self._raise_if_pid_reused() 

1600 self._proc.kill() 

1601 

1602 def kill(self) -> None: 

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

1604 whether PID has been reused. 

1605 """ 

1606 if POSIX: 

1607 self._send_signal(signal.SIGKILL) 

1608 else: # pragma: no cover 

1609 self._raise_if_pid_reused() 

1610 self._proc.kill() 

1611 

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

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

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

1615 

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

1617 returned). 

1618 

1619 If the process is already terminated, immediately return None 

1620 instead of raising `NoSuchProcess`. 

1621 

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

1623 alive, raise `TimeoutExpired`. 

1624 

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

1626 `TimeoutExpired` (non-blocking). 

1627 

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

1629 """ 

1630 if self.pid == 0: 

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

1632 raise ValueError(msg) 

1633 if timeout is not None: 

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

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

1636 raise TypeError(msg) 

1637 if timeout < 0: 

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

1639 raise ValueError(msg) 

1640 

1641 if self._exitcode is not _SENTINEL: 

1642 return self._exitcode 

1643 

1644 try: 

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

1646 except TimeoutExpired as err: 

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

1648 raise exc from err 

1649 

1650 return self._exitcode 

1651 

1652 

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

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

1655# fmt: off 

1656Process.attrs = frozenset( 

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

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

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

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

1661) 

1662# fmt: on 

1663 

1664 

1665# ===================================================================== 

1666# --- Popen class 

1667# ===================================================================== 

1668 

1669 

1670class Popen(Process): 

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

1672 `Process` methods in a single class. 

1673 

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

1675 implementation takes precedence: 

1676 

1677 * `send_signal()` 

1678 * `terminate()` 

1679 * `kill()` 

1680 

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

1682 PID has been reused, fixing BPO-6973. 

1683 

1684 >>> import psutil 

1685 >>> from subprocess import PIPE 

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

1687 >>> p.name() 

1688 'python3' 

1689 >>> p.uids() 

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

1691 >>> p.username() 

1692 'giampaolo' 

1693 >>> p.communicate() 

1694 ('hi', None) 

1695 >>> p.terminate() 

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

1697 0 

1698 >>> 

1699 """ 

1700 

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

1702 # Explicitly avoid to raise NoSuchProcess in case the process 

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

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

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

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

1707 

1708 def __dir__(self): 

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

1710 

1711 def __enter__(self) -> Popen: 

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

1713 self.__subproc.__enter__() 

1714 return self 

1715 

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

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

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

1719 else: 

1720 if self.stdout: 

1721 self.stdout.close() 

1722 if self.stderr: 

1723 self.stderr.close() 

1724 try: 

1725 # Flushing a BufferedWriter may raise an error. 

1726 if self.stdin: 

1727 self.stdin.close() 

1728 finally: 

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

1730 self.wait() 

1731 

1732 def __getattribute__(self, name): 

1733 try: 

1734 return object.__getattribute__(self, name) 

1735 except AttributeError: 

1736 try: 

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

1738 except AttributeError: 

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

1740 raise AttributeError(msg) from None 

1741 

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

1743 if self.__subproc.returncode is not None: 

1744 return self.__subproc.returncode 

1745 ret = super().wait(timeout) 

1746 self.__subproc.returncode = ret 

1747 return ret 

1748 

1749 

1750# ===================================================================== 

1751# --- system processes related functions 

1752# ===================================================================== 

1753 

1754 

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

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

1757 global _LOWEST_PID 

1758 ret = sorted(_psplatform.pids()) 

1759 _LOWEST_PID = ret[0] 

1760 return ret 

1761 

1762 

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

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

1765 

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

1767 preferred. 

1768 """ 

1769 if pid < 0: 

1770 return False 

1771 elif pid == 0 and POSIX: 

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

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

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

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

1776 # to do here. 

1777 return pid in pids() 

1778 else: 

1779 return _psplatform.pid_exists(pid) 

1780 

1781 

1782_pmap = {} 

1783_pids_reused = set() 

1784 

1785 

1786def process_iter( 

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

1788) -> Iterator[Process]: 

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

1790 running processes. 

1791 

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

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

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

1795 

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

1797 their PIDs. 

1798 

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

1800 `Process.as_dict()`. 

1801 

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

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

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

1805 retrieve all process info (slow). 

1806 

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

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

1809 """ 

1810 global _pmap 

1811 

1812 def add(pid): 

1813 proc = Process(pid) 

1814 pmap[proc.pid] = proc 

1815 return proc 

1816 

1817 def remove(pid): 

1818 pmap.pop(pid, None) 

1819 

1820 if attrs is not None: 

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

1822 msg = ( 

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

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

1825 ) 

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

1827 elif not attrs: 

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

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

1830 # signaled. 

1831 msg = ( 

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

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

1834 ) 

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

1836 

1837 pmap = _pmap.copy() 

1838 a = set(pids()) 

1839 b = set(pmap) 

1840 new_pids = a - b 

1841 gone_pids = b - a 

1842 for pid in gone_pids: 

1843 remove(pid) 

1844 while _pids_reused: 

1845 pid = _pids_reused.pop() 

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

1847 remove(pid) 

1848 try: 

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

1850 for pid, proc in ls: 

1851 try: 

1852 if proc is None: # new process 

1853 proc = add(pid) 

1854 proc._prefetch = {} # clear cache 

1855 proc._ad_value = _SENTINEL 

1856 if attrs is not None: 

1857 proc._prefetch = proc.as_dict( 

1858 attrs=attrs, ad_value=ad_value 

1859 ) 

1860 proc._ad_value = ad_value 

1861 yield proc 

1862 except ZombieProcess: 

1863 if proc is not None: 

1864 yield proc # zombie processes are still valid 

1865 except NoSuchProcess: 

1866 remove(pid) 

1867 finally: 

1868 _pmap = pmap 

1869 

1870 

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

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

1873 

1874 

1875def wait_procs( 

1876 procs: list[Process], 

1877 timeout: float | None = None, 

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

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

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

1881 terminate. 

1882 

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

1884 are gone and which ones are still alive. 

1885 

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

1887 process exit status (may be None). 

1888 

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

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

1891 

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

1893 *timeout* occurs. 

1894 

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

1896 *timeout* occurs. 

1897 

1898 Typical use case is: 

1899 

1900 - send SIGTERM to a list of processes 

1901 - give them some time to terminate 

1902 - send SIGKILL to those ones which are still alive 

1903 

1904 Example: 

1905 

1906 >>> def on_terminate(proc): 

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

1908 ... 

1909 >>> for p in procs: 

1910 ... p.terminate() 

1911 ... 

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

1913 >>> for p in alive: 

1914 ... p.kill() 

1915 """ 

1916 

1917 def check_gone(proc, timeout): 

1918 try: 

1919 returncode = proc.wait(timeout=timeout) 

1920 except (TimeoutExpired, subprocess.TimeoutExpired): 

1921 pass 

1922 else: 

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

1924 # Set new Process instance attribute. 

1925 proc.returncode = returncode 

1926 gone.add(proc) 

1927 if callback is not None: 

1928 callback(proc) 

1929 

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

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

1932 raise ValueError(msg) 

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

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

1935 raise TypeError(msg) 

1936 

1937 gone = set() 

1938 alive = set(procs) 

1939 if timeout is not None: 

1940 deadline = _timer() + timeout 

1941 

1942 while alive: 

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

1944 break 

1945 for proc in alive: 

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

1947 # will last max 1 sec. 

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

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

1950 # processes may disappear in the meantime and their PID 

1951 # reused. 

1952 max_timeout = 1.0 / len(alive) 

1953 if timeout is not None: 

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

1955 if timeout <= 0: 

1956 break 

1957 check_gone(proc, timeout) 

1958 else: 

1959 check_gone(proc, max_timeout) 

1960 alive = alive - gone # noqa: PLR6104 

1961 

1962 if alive: 

1963 # Last attempt over processes survived so far. 

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

1965 for proc in alive: 

1966 check_gone(proc, 0) 

1967 alive = alive - gone # noqa: PLR6104 

1968 

1969 return (list(gone), list(alive)) 

1970 

1971 

1972# ===================================================================== 

1973# --- CPU related functions 

1974# ===================================================================== 

1975 

1976 

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

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

1979 `os.cpu_count()`). 

1980 

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

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

1983 

1984 Return None if undetermined. 

1985 

1986 The return value is cached after first call. 

1987 If desired cache can be cleared like this: 

1988 

1989 >>> psutil.cpu_count.cache_clear() 

1990 """ 

1991 if logical: 

1992 ret = _psplatform.cpu_count_logical() 

1993 else: 

1994 ret = _psplatform.cpu_count_cores() 

1995 if ret is not None and ret < 1: 

1996 ret = None 

1997 return ret 

1998 

1999 

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

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

2002 

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

2004 given mode: 

2005 

2006 - `user` 

2007 - `system` 

2008 - `idle` 

2009 - `nice` (UNIX) 

2010 - `iowait` (Linux) 

2011 - `irq` (Linux, FreeBSD) 

2012 - `softirq` (Linux) 

2013 - `steal` (Linux) 

2014 - `guest` (Linux) 

2015 - `guest_nice` (Linux) 

2016 - `dpc` (Windows) 

2017 

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

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

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

2021 consistent across calls. 

2022 """ 

2023 if not percpu: 

2024 return _psplatform.cpu_times() 

2025 else: 

2026 return _psplatform.per_cpu_times() 

2027 

2028 

2029try: 

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

2031except Exception: # noqa: BLE001 

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

2033 _last_cpu_times = {} 

2034 

2035try: 

2036 _last_per_cpu_times = { 

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

2038 } 

2039except Exception: # noqa: BLE001 

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

2041 _last_per_cpu_times = {} 

2042 

2043 

2044def _cpu_tot_time(times): 

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

2046 (including idle time). 

2047 """ 

2048 tot = sum(times) 

2049 if LINUX: 

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

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

2052 # Htop does the same. References: 

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

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

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

2056 tot -= times.guest 

2057 tot -= times.guest_nice 

2058 return tot 

2059 

2060 

2061def _cpu_busy_time(times): 

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

2063 subtracting all idle CPU times. 

2064 """ 

2065 busy = _cpu_tot_time(times) 

2066 busy -= times.idle 

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

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

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

2070 # References: 

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

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

2073 return busy 

2074 

2075 

2076def _cpu_times_deltas(t1, t2): 

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

2078 field_deltas = [] 

2079 for field in _ntp.scputimes._fields: 

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

2081 # CPU times are always supposed to increase over time 

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

2083 # cannot go backwards. 

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

2085 # least on Windows and Linux), see: 

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

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

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

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

2090 # top does the same. Reference: 

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

2092 field_delta = max(0, field_delta) 

2093 field_deltas.append(field_delta) 

2094 return _ntp.scputimes(*field_deltas) 

2095 

2096 

2097def cpu_percent( 

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

2099) -> float | list[float]: 

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

2101 utilization as a percentage. 

2102 

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

2104 and after the interval (blocking). 

2105 

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

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

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

2109 return a meaningless 0.0 value which you should ignore. 

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

2111 called with at least 0.1 seconds between calls. 

2112 

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

2114 utilization as a percentage for each CPU. 

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

2116 to second CPU and so on. 

2117 The order of the list is consistent across calls. 

2118 

2119 Examples: 

2120 

2121 >>> # blocking, system-wide 

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

2123 2.0 

2124 >>> 

2125 >>> # blocking, per-cpu 

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

2127 [2.0, 1.0] 

2128 >>> 

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

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

2131 2.9 

2132 >>> 

2133 """ 

2134 tid = threading.current_thread().ident 

2135 blocking = interval is not None and interval > 0.0 

2136 if interval is not None and interval < 0: 

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

2138 raise ValueError(msg) 

2139 

2140 def calculate(t1, t2): 

2141 times_delta = _cpu_times_deltas(t1, t2) 

2142 all_delta = _cpu_tot_time(times_delta) 

2143 busy_delta = _cpu_busy_time(times_delta) 

2144 

2145 try: 

2146 busy_perc = (busy_delta / all_delta) * 100 

2147 except ZeroDivisionError: 

2148 return 0.0 

2149 else: 

2150 return round(busy_perc, 1) 

2151 

2152 # system-wide usage 

2153 if not percpu: 

2154 if blocking: 

2155 t1 = cpu_times() 

2156 time.sleep(interval) 

2157 else: 

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

2159 _last_cpu_times[tid] = cpu_times() 

2160 return calculate(t1, _last_cpu_times[tid]) 

2161 # per-cpu usage 

2162 else: 

2163 ret = [] 

2164 if blocking: 

2165 tot1 = cpu_times(percpu=True) 

2166 time.sleep(interval) 

2167 else: 

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

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

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

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

2172 return ret 

2173 

2174 

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

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

2177_last_cpu_times_2 = _last_cpu_times.copy() 

2178_last_per_cpu_times_2 = _last_per_cpu_times.copy() 

2179 

2180 

2181def cpu_times_percent( 

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

2183) -> scputimes | list[scputimes]: 

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

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

2186 

2187 For instance, on Linux we'll get: 

2188 

2189 >>> cpu_times_percent() 

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

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

2192 >>> 

2193 

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

2195 `cpu_percent()`. 

2196 """ 

2197 tid = threading.current_thread().ident 

2198 blocking = interval is not None and interval > 0.0 

2199 if interval is not None and interval < 0: 

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

2201 raise ValueError(msg) 

2202 

2203 def calculate(t1, t2): 

2204 nums = [] 

2205 times_delta = _cpu_times_deltas(t1, t2) 

2206 all_delta = _cpu_tot_time(times_delta) 

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

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

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

2210 # fraction because cpu times are integers) 

2211 scale = 100.0 / max(1, all_delta) 

2212 for field_delta in times_delta: 

2213 field_perc = field_delta * scale 

2214 field_perc = round(field_perc, 1) 

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

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

2217 nums.append(field_perc) 

2218 return _ntp.scputimes(*nums) 

2219 

2220 # system-wide usage 

2221 if not percpu: 

2222 if blocking: 

2223 t1 = cpu_times() 

2224 time.sleep(interval) 

2225 else: 

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

2227 _last_cpu_times_2[tid] = cpu_times() 

2228 return calculate(t1, _last_cpu_times_2[tid]) 

2229 # per-cpu usage 

2230 else: 

2231 ret = [] 

2232 if blocking: 

2233 tot1 = cpu_times(percpu=True) 

2234 time.sleep(interval) 

2235 else: 

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

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

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

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

2240 return ret 

2241 

2242 

2243def cpu_stats() -> scpustats: 

2244 """Return CPU statistics.""" 

2245 return _psplatform.cpu_stats() 

2246 

2247 

2248if hasattr(_psplatform, "cpu_freq"): 

2249 

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

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

2252 min and max frequency expressed in Mhz. 

2253 

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

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

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

2257 returned. 

2258 """ 

2259 ret = _psplatform.cpu_freq() 

2260 if percpu: 

2261 return ret 

2262 else: 

2263 num_cpus = float(len(ret)) 

2264 if num_cpus == 0: 

2265 return None 

2266 elif num_cpus == 1: 

2267 return ret[0] 

2268 else: 

2269 currs, mins, maxs = 0.0, 0.0, 0.0 

2270 set_none = False 

2271 for cpu in ret: 

2272 currs += cpu.current 

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

2274 # can't be parsed. 

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

2276 set_none = True 

2277 continue 

2278 mins += cpu.min 

2279 maxs += cpu.max 

2280 

2281 current = currs / num_cpus 

2282 

2283 if set_none: 

2284 min_ = max_ = None 

2285 else: 

2286 min_ = mins / num_cpus 

2287 max_ = maxs / num_cpus 

2288 

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

2290 

2291 __all__.append("cpu_freq") 

2292 

2293 

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

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

2296 as a tuple. 

2297 

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

2299 thread which keeps running in background and updates results every 

2300 5 seconds, mimicking the UNIX behavior. 

2301 """ 

2302 if hasattr(os, "getloadavg"): 

2303 return os.getloadavg() 

2304 else: 

2305 return _psplatform.getloadavg() 

2306 

2307 

2308# ===================================================================== 

2309# --- system memory related functions 

2310# ===================================================================== 

2311 

2312 

2313def virtual_memory() -> svmem: 

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

2315 

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

2317 are present on all platforms: 

2318 

2319 - total: 

2320 total physical memory available 

2321 

2322 - available: 

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

2324 system going into swap. 

2325 This is calculated by summing different memory values depending 

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

2327 memory usage in a cross platform fashion. 

2328 

2329 - percent: 

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

2331 

2332 - used: 

2333 memory used, calculated differently depending on the platform and 

2334 designed for informational purposes only 

2335 

2336 - free: 

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

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

2339 (use 'available' instead) 

2340 

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

2342 

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

2344 """ 

2345 global _TOTAL_PHYMEM 

2346 ret = _psplatform.virtual_memory() 

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

2348 _TOTAL_PHYMEM = ret.total 

2349 return ret 

2350 

2351 

2352def swap_memory() -> sswap: 

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

2354 the following fields: 

2355 

2356 - total: total swap memory in bytes 

2357 - used: used swap memory in bytes 

2358 - free: free swap memory in bytes 

2359 - percent: the percentage usage 

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

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

2362 

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

2364 """ 

2365 return _psplatform.swap_memory() 

2366 

2367 

2368# ===================================================================== 

2369# --- disks/partitions related functions 

2370# ===================================================================== 

2371 

2372 

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

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

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

2376 plus the percentage usage. 

2377 """ 

2378 return _psplatform.disk_usage(path) 

2379 

2380 

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

2382 """Return mounted partitions as a list of 

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

2384 

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

2386 options which may vary depending on the platform. 

2387 

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

2389 all others. 

2390 """ 

2391 return _psplatform.disk_partitions(all) 

2392 

2393 

2394def disk_io_counters( 

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

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

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

2398 the following fields: 

2399 

2400 - read_count: number of reads 

2401 - write_count: number of writes 

2402 - read_bytes: number of bytes read 

2403 - write_bytes: number of bytes written 

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

2405 disk (in ms) 

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

2407 disk (in ms) 

2408 

2409 Platform specific: 

2410 

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

2412 - read_merged_count (Linux): number of merged reads 

2413 - write_merged_count (Linux): number of merged writes 

2414 

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

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

2417 

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

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

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

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

2422 *nowrap* cache. 

2423 """ 

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

2425 rawdict = _psplatform.disk_io_counters(**kwargs) 

2426 if not rawdict: 

2427 return {} if perdisk else None 

2428 if nowrap: 

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

2430 if perdisk: 

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

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

2433 return rawdict 

2434 else: 

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

2436 

2437 

2438disk_io_counters.cache_clear = functools.partial( 

2439 _wrap_numbers.cache_clear, 'psutil.disk_io_counters' 

2440) 

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

2442 

2443 

2444# ===================================================================== 

2445# --- network related functions 

2446# ===================================================================== 

2447 

2448 

2449def net_io_counters( 

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

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

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

2453 the following fields: 

2454 

2455 - bytes_sent: number of bytes sent 

2456 - bytes_recv: number of bytes received 

2457 - packets_sent: number of packets sent 

2458 - packets_recv: number of packets received 

2459 - errin: total number of errors while receiving 

2460 - errout: total number of errors while sending 

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

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

2463 (always 0 on macOS and BSD) 

2464 

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

2466 network interface as a dictionary with interface names as the 

2467 keys. 

2468 

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

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

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

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

2473 *nowrap* cache. 

2474 """ 

2475 rawdict = _psplatform.net_io_counters() 

2476 if not rawdict: 

2477 return {} if pernic else None 

2478 if nowrap: 

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

2480 if pernic: 

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

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

2483 return rawdict 

2484 else: 

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

2486 

2487 

2488net_io_counters.cache_clear = functools.partial( 

2489 _wrap_numbers.cache_clear, 'psutil.net_io_counters' 

2490) 

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

2492 

2493 

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

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

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

2497 

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

2499 and None respectively. 

2500 

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

2502 following criteria: 

2503 

2504 +------------+----------------------------------------------------+ 

2505 | Kind Value | Connections using | 

2506 +------------+----------------------------------------------------+ 

2507 | 'inet' | IPv4 and IPv6 | 

2508 | 'inet4' | IPv4 | 

2509 | 'inet6' | IPv6 | 

2510 | 'tcp' | TCP | 

2511 | 'tcp4' | TCP over IPv4 | 

2512 | 'tcp6' | TCP over IPv6 | 

2513 | 'udp' | UDP | 

2514 | 'udp4' | UDP over IPv4 | 

2515 | 'udp6' | UDP over IPv6 | 

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

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

2518 +------------+----------------------------------------------------+ 

2519 

2520 On macOS this function requires root privileges. 

2521 """ 

2522 _check_conn_kind(kind) 

2523 return _psplatform.net_connections(kind) 

2524 

2525 

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

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

2528 a list of named tuples representing its addresses. Multiple 

2529 addresses of the same family can exist per interface. 

2530 

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

2532 

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

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

2535 unconfigured NIC). 

2536 - address: the primary NIC address 

2537 - netmask: the netmask address 

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

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

2540 Windows 

2541 """ 

2542 rawlist = _psplatform.net_if_addrs() 

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

2544 ret = collections.defaultdict(list) 

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

2546 try: 

2547 fam = socket.AddressFamily(fam) 

2548 except ValueError: 

2549 if WINDOWS and fam == -1: 

2550 fam = _psplatform.AF_LINK 

2551 elif ( 

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

2553 ): 

2554 # Linux defines AF_LINK as an alias for AF_PACKET. 

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

2556 # will show AF_LINK rather than AF_PACKET 

2557 fam = _psplatform.AF_LINK 

2558 

2559 if fam == _psplatform.AF_LINK: 

2560 # The underlying C function may return an incomplete MAC 

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

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

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

2564 while addr.count(separator) < 5: 

2565 addr += f"{separator}00" 

2566 

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

2568 

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

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

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

2572 if nt.netmask and ( 

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

2574 ): 

2575 try: 

2576 calculated = _common.broadcast_addr(nt) 

2577 except Exception as err: # noqa: BLE001 

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

2579 else: 

2580 if calculated is None: 

2581 nt = nt._replace(broadcast=None) 

2582 elif WINDOWS: 

2583 nt = nt._replace(broadcast=calculated) 

2584 

2585 ret[name].append(nt) 

2586 

2587 return dict(ret) 

2588 

2589 

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

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

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

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

2594 

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

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

2597 `NIC_DUPLEX_UNKNOWN` 

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

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

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

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

2602 """ 

2603 return _psplatform.net_if_stats() 

2604 

2605 

2606# ===================================================================== 

2607# --- sensors 

2608# ===================================================================== 

2609 

2610 

2611# Linux, macOS 

2612if hasattr(_psplatform, "sensors_temperatures"): 

2613 

2614 def sensors_temperatures( 

2615 fahrenheit: bool = False, 

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

2617 """Return hardware temperatures. 

2618 

2619 Each entry is a named tuple representing a certain hardware 

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

2621 depending on the OS and its configuration). 

2622 

2623 All temperatures are expressed in celsius unless *fahrenheit* 

2624 is set to True. 

2625 """ 

2626 

2627 def convert(n): 

2628 if n is not None: 

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

2630 

2631 ret = collections.defaultdict(list) 

2632 rawdict = _psplatform.sensors_temperatures() 

2633 

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

2635 while values: 

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

2637 current = convert(current) 

2638 high = convert(high) 

2639 critical = convert(critical) 

2640 

2641 if high and not critical: 

2642 critical = high 

2643 elif critical and not high: 

2644 high = critical 

2645 

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

2647 

2648 return dict(ret) 

2649 

2650 __all__.append("sensors_temperatures") 

2651 

2652 

2653# Linux 

2654if hasattr(_psplatform, "sensors_fans"): 

2655 

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

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

2658 representing a certain hardware sensor. 

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

2660 """ 

2661 return _psplatform.sensors_fans() 

2662 

2663 __all__.append("sensors_fans") 

2664 

2665 

2666# Linux, Windows, FreeBSD, macOS 

2667if hasattr(_psplatform, "sensors_battery"): 

2668 

2669 def sensors_battery() -> sbattery | None: 

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

2671 returns None. 

2672 

2673 - percent: battery power left as a percentage. 

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

2675 before the battery runs out of power. May be 

2676 `POWER_TIME_UNLIMITED` or `POWER_TIME_UNKNOWN`. 

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

2678 """ 

2679 return _psplatform.sensors_battery() 

2680 

2681 __all__.append("sensors_battery") 

2682 

2683 

2684# ===================================================================== 

2685# --- other system related functions 

2686# ===================================================================== 

2687 

2688 

2689def boot_time() -> float: 

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

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

2692 

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

2694 be affected by changes such as manual adjustments or time 

2695 synchronization (e.g. NTP). 

2696 """ 

2697 return _psplatform.boot_time() 

2698 

2699 

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

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

2702 named tuples including the following fields: 

2703 

2704 - user: the name of the user 

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

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

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

2708 seconds since the epoch. 

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

2710 """ 

2711 return _psplatform.users() 

2712 

2713 

2714# ===================================================================== 

2715# --- Windows services 

2716# ===================================================================== 

2717 

2718 

2719if WINDOWS: 

2720 

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

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

2723 all Windows services installed. 

2724 """ 

2725 return _psplatform.win_service_iter() 

2726 

2727 def win_service_get(name) -> WindowsService: 

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

2729 

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

2731 """ 

2732 return _psplatform.win_service_get(name) 

2733 

2734 

2735# ===================================================================== 

2736# --- malloc / heap 

2737# ===================================================================== 

2738 

2739 

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

2741if hasattr(_psplatform, "heap_info"): 

2742 

2743 def heap_info() -> pheap: 

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

2745 (glibc). 

2746 

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

2748 malloc/free. These are typically allocations smaller than 

2749 MMAP_THRESHOLD. 

2750 

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

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

2753 

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

2755 via `HeapCreate()`. 

2756 """ 

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

2758 

2759 def heap_trim() -> None: 

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

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

2762 allocations). 

2763 

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

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

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

2767 detection tools**. 

2768 

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

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

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

2772 internal allocator caching or fragmentation. Its effectiveness 

2773 depends on allocator behavior and fragmentation patterns. 

2774 """ 

2775 _psplatform.heap_trim() 

2776 

2777 __all__.append("heap_info") 

2778 __all__.append("heap_trim") 

2779 

2780 

2781# ===================================================================== 

2782 

2783 

2784def _set_debug(value): 

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

2786 messages to stderr. 

2787 """ 

2788 import psutil._common 

2789 

2790 psutil._common.PSUTIL_DEBUG = bool(value) 

2791 _psutil.set_debug(bool(value)) 

2792 

2793 

2794del memoize_when_activated