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

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

1257 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"""Linux platform implementation.""" 

6 

7import base64 

8import collections 

9import enum 

10import errno 

11import functools 

12import glob 

13import os 

14import re 

15import resource 

16import socket 

17import struct 

18import sys 

19import warnings 

20from collections import defaultdict 

21 

22from . import _ntuples as ntp 

23from . import _psposix 

24from . import _psutil 

25from ._common import ENCODING 

26from ._common import AccessDenied 

27from ._common import NoSuchProcess 

28from ._common import ZombieProcess 

29from ._common import bcat 

30from ._common import cat 

31from ._common import debug 

32from ._common import decode 

33from ._common import get_procfs_path 

34from ._common import isfile_strict 

35from ._common import memoize_when_activated 

36from ._common import open_binary 

37from ._common import open_text 

38from ._common import parse_environ_block 

39from ._common import path_exists_strict 

40from ._common import socktype_to_enum 

41from ._common import supports_ipv6 

42from ._common import usage_percent 

43from ._enums import BatteryTime 

44from ._enums import ConnectionStatus 

45from ._enums import NicDuplex 

46from ._enums import ProcessIOPriority 

47from ._enums import ProcessStatus 

48 

49__extra__all__ = ['PROCFS_PATH'] 

50 

51 

52# ===================================================================== 

53# --- globals 

54# ===================================================================== 

55 

56 

57POWER_SUPPLY_PATH = "/sys/class/power_supply" 

58HAS_PROC_SMAPS = os.path.exists(f"/proc/{os.getpid()}/smaps") 

59HAS_PROC_SMAPS_ROLLUP = os.path.exists(f"/proc/{os.getpid()}/smaps_rollup") 

60HAS_PROC_IO_PRIORITY = hasattr(_psutil, "proc_ioprio_get") 

61HAS_CPU_AFFINITY = hasattr(_psutil, "proc_cpu_affinity_get") 

62 

63# Number of clock ticks per second 

64CLOCK_TICKS = os.sysconf("SC_CLK_TCK") 

65PAGESIZE = _psutil.getpagesize() 

66LITTLE_ENDIAN = sys.byteorder == 'little' 

67UNSET = object() 

68 

69# Python 3.15 changed resource.prlimit() to return RLIM_INFINITY as the 

70# unsigned 2**64-1 instead of -1; used to map it back to -1. 

71RLIM_INFINITY_UNSIGNED = _psutil.RLIM_INFINITY & 0xFFFFFFFFFFFFFFFF 

72 

73# "man iostat" states that sectors are equivalent with blocks and have 

74# a size of 512 bytes. Despite this value can be queried at runtime 

75# via /sys/block/{DISK}/queue/hw_sector_size and results may vary 

76# between 1k, 2k, or 4k... 512 appears to be a magic constant used 

77# throughout Linux source code: 

78# * https://stackoverflow.com/a/38136179/376587 

79# * https://lists.gt.net/linux/kernel/2241060 

80# * https://github.com/giampaolo/psutil/issues/1305 

81# * https://github.com/torvalds/linux/blob/ 

82# 4f671fe2f9523a1ea206f63fe60a7c7b3a56d5c7/include/linux/bio.h#L99 

83# * https://lkml.org/lkml/2015/8/17/234 

84DISK_SECTOR_SIZE = 512 

85 

86AddressFamily = enum.IntEnum( 

87 'AddressFamily', {'AF_LINK': int(socket.AF_PACKET)} 

88) 

89AF_LINK = AddressFamily.AF_LINK 

90 

91 

92# See: 

93# https://github.com/torvalds/linux/blame/master/fs/proc/array.c 

94# ...and (TASK_* constants): 

95# https://github.com/torvalds/linux/blob/master/include/linux/sched.h 

96PROC_STATUSES = { 

97 "R": ProcessStatus.STATUS_RUNNING, 

98 "S": ProcessStatus.STATUS_SLEEPING, 

99 "D": ProcessStatus.STATUS_DISK_SLEEP, 

100 "T": ProcessStatus.STATUS_STOPPED, 

101 "t": ProcessStatus.STATUS_TRACING_STOP, 

102 "Z": ProcessStatus.STATUS_ZOMBIE, 

103 "X": ProcessStatus.STATUS_DEAD, 

104 "x": ProcessStatus.STATUS_DEAD, 

105 "K": ProcessStatus.STATUS_WAKE_KILL, 

106 "W": ProcessStatus.STATUS_WAKING, 

107 "I": ProcessStatus.STATUS_IDLE, 

108 "P": ProcessStatus.STATUS_PARKED, 

109} 

110 

111# https://github.com/torvalds/linux/blob/master/include/net/tcp_states.h 

112TCP_STATUSES = { 

113 "01": ConnectionStatus.CONN_ESTABLISHED, 

114 "02": ConnectionStatus.CONN_SYN_SENT, 

115 "03": ConnectionStatus.CONN_SYN_RECV, 

116 "04": ConnectionStatus.CONN_FIN_WAIT1, 

117 "05": ConnectionStatus.CONN_FIN_WAIT2, 

118 "06": ConnectionStatus.CONN_TIME_WAIT, 

119 "07": ConnectionStatus.CONN_CLOSE, 

120 "08": ConnectionStatus.CONN_CLOSE_WAIT, 

121 "09": ConnectionStatus.CONN_LAST_ACK, 

122 "0A": ConnectionStatus.CONN_LISTEN, 

123 "0B": ConnectionStatus.CONN_CLOSING, 

124} 

125 

126 

127# ===================================================================== 

128# --- utils 

129# ===================================================================== 

130 

131 

132def readlink(path): 

133 """Wrapper around os.readlink().""" 

134 assert isinstance(path, str), path 

135 path = os.readlink(path) 

136 # readlink() might return paths containing null bytes ('\x00') 

137 # resulting in "TypeError: must be encoded string without NULL 

138 # bytes, not str" errors when the string is passed to other 

139 # fs-related functions (os.*, open(), ...). 

140 # Apparently everything after '\x00' is garbage (we can have 

141 # ' (deleted)', 'new' and possibly others), see: 

142 # https://github.com/giampaolo/psutil/issues/717 

143 path = path.split('\x00')[0] 

144 # Certain paths have ' (deleted)' appended. Usually this is 

145 # bogus as the file actually exists. Even if it doesn't we 

146 # don't care. 

147 if path.endswith(' (deleted)') and not path_exists_strict(path): 

148 path = path[:-10] 

149 return path 

150 

151 

152def file_flags_to_mode(flags): 

153 """Convert file's open() flags into a readable string. 

154 Used by Process.open_files(). 

155 """ 

156 modes_map = {os.O_RDONLY: 'r', os.O_WRONLY: 'w', os.O_RDWR: 'w+'} 

157 mode = modes_map[flags & (os.O_RDONLY | os.O_WRONLY | os.O_RDWR)] 

158 if flags & os.O_APPEND: 

159 mode = mode.replace('w', 'a', 1) 

160 mode = mode.replace('w+', 'r+') 

161 # possible values: r, w, a, r+, a+ 

162 return mode 

163 

164 

165def is_storage_device(name): 

166 """Return True if the given name refers to a root device (e.g. 

167 "sda", "nvme0n1") as opposed to a logical partition (e.g. "sda1", 

168 "nvme0n1p1"). If name is a virtual device (e.g. "loop1", "ram") 

169 return True. 

170 """ 

171 # Re-adapted from iostat source code, see: 

172 # https://github.com/sysstat/sysstat/blob/97912938cd476/common.c#L208 

173 # Some devices may have a slash in their name (e.g. cciss/c0d0...). 

174 name = name.replace('/', '!') 

175 including_virtual = True 

176 if including_virtual: 

177 path = f"/sys/block/{name}" 

178 else: 

179 path = f"/sys/block/{name}/device" 

180 return os.access(path, os.F_OK) 

181 

182 

183# ===================================================================== 

184# --- system memory 

185# ===================================================================== 

186 

187 

188def calculate_avail_vmem(mems): 

189 """Fallback for kernels < 3.14 where /proc/meminfo does not provide 

190 "MemAvailable", see: 

191 https://blog.famzah.net/2014/09/24/. 

192 

193 This code reimplements the algorithm outlined here: 

194 https://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/ 

195 commit/?id=34e431b0ae398fc54ea69ff85ec700722c9da773 

196 

197 We use this function also when "MemAvailable" returns 0 (possibly a 

198 kernel bug, see: https://github.com/giampaolo/psutil/issues/1915). 

199 In that case this routine matches "free" CLI tool result ("available" 

200 column). 

201 

202 XXX: on recent kernels this calculation may differ by ~1.5% compared 

203 to "MemAvailable:", as it's calculated slightly differently. 

204 It is still way more realistic than doing (free + cached) though. 

205 See: 

206 * https://gitlab.com/procps-ng/procps/issues/42 

207 * https://github.com/famzah/linux-memavailable-procfs/issues/2 

208 """ 

209 # Note about "fallback" value. According to: 

210 # https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=34e431b0ae398 

211 # ...long ago "available" memory was calculated as (free + cached), 

212 # We use fallback when one of these is missing from /proc/meminfo: 

213 # "Active(file)": introduced in 2.6.28 / Dec 2008 

214 # "Inactive(file)": introduced in 2.6.28 / Dec 2008 

215 # "SReclaimable": introduced in 2.6.19 / Nov 2006 

216 # /proc/zoneinfo: introduced in 2.6.13 / Aug 2005 

217 free = mems[b'MemFree:'] 

218 fallback = free + mems.get(b"Cached:", 0) 

219 try: 

220 lru_active_file = mems[b'Active(file):'] 

221 lru_inactive_file = mems[b'Inactive(file):'] 

222 slab_reclaimable = mems[b'SReclaimable:'] 

223 except KeyError as err: 

224 debug( 

225 f"{err.args[0]} is missing from /proc/meminfo; using an" 

226 " approximation for calculating available memory" 

227 ) 

228 return fallback 

229 try: 

230 f = open_binary(f"{get_procfs_path()}/zoneinfo") 

231 except OSError: 

232 return fallback # kernel 2.6.13 

233 

234 watermark_low = 0 

235 with f: 

236 for line in f: 

237 line = line.strip() 

238 if line.startswith(b'low'): 

239 watermark_low += int(line.split()[1]) 

240 watermark_low *= PAGESIZE 

241 

242 avail = free - watermark_low 

243 pagecache = lru_active_file + lru_inactive_file 

244 pagecache -= min(pagecache / 2, watermark_low) 

245 avail += pagecache 

246 avail += slab_reclaimable - min(slab_reclaimable / 2.0, watermark_low) 

247 return int(avail) 

248 

249 

250def virtual_memory(): 

251 """Report virtual memory stats. 

252 This implementation mimics procps-ng-3.3.12, aka "free" CLI tool: 

253 https://gitlab.com/procps-ng/procps/blob/ 

254 24fd2605c51fccc375ab0287cec33aa767f06718/proc/sysinfo.c#L778-791 

255 The returned values are supposed to match both "free" and "vmstat -s" 

256 CLI tools. 

257 """ 

258 missing_fields = [] 

259 mems = {} 

260 with open_binary(f"{get_procfs_path()}/meminfo") as f: 

261 for line in f: 

262 key, value = line.split(b':', 1) 

263 mems[key + b':'] = int(value.split()[0]) * 1024 

264 

265 # /proc doc states that the available fields in /proc/meminfo vary 

266 # by architecture and compile options, but these 3 values are also 

267 # returned by sysinfo(2); as such we assume they are always there. 

268 total = mems[b'MemTotal:'] 

269 free = mems[b'MemFree:'] 

270 try: 

271 buffers = mems[b'Buffers:'] 

272 except KeyError: 

273 # https://github.com/giampaolo/psutil/issues/1010 

274 buffers = 0 

275 missing_fields.append('buffers') 

276 try: 

277 cached = mems[b"Cached:"] 

278 except KeyError: 

279 cached = 0 

280 missing_fields.append('cached') 

281 else: 

282 # "free" cmdline utility sums reclaimable to cached. 

283 # Older versions of procps used to add slab memory instead. 

284 # This got changed in: 

285 # https://gitlab.com/procps-ng/procps/-/commit/05d751c4f 

286 cached += mems.get(b"SReclaimable:", 0) # since kernel 2.6.19 

287 

288 try: 

289 shared = mems[b'Shmem:'] # since kernel 2.6.32 

290 except KeyError: 

291 try: 

292 shared = mems[b'MemShared:'] # kernels 2.4 

293 except KeyError: 

294 shared = 0 

295 missing_fields.append('shared') 

296 

297 try: 

298 active = mems[b"Active:"] 

299 except KeyError: 

300 active = 0 

301 missing_fields.append('active') 

302 

303 try: 

304 inactive = mems[b"Inactive:"] 

305 except KeyError: 

306 try: 

307 inactive = ( 

308 mems[b"Inact_dirty:"] 

309 + mems[b"Inact_clean:"] 

310 + mems[b"Inact_laundry:"] 

311 ) 

312 except KeyError: 

313 inactive = 0 

314 missing_fields.append('inactive') 

315 

316 try: 

317 slab = mems[b"Slab:"] 

318 except KeyError: 

319 slab = 0 

320 

321 # - starting from 4.4.0 we match free's "available" column. 

322 # Before 4.4.0 we calculated it as (free + buffers + cached) 

323 # which matched htop. 

324 # - free and htop available memory differs as per: 

325 # http://askubuntu.com/a/369589 

326 # http://unix.stackexchange.com/a/65852/168884 

327 # - MemAvailable has been introduced in kernel 3.14 

328 try: 

329 avail = mems[b'MemAvailable:'] 

330 except KeyError: 

331 avail = calculate_avail_vmem(mems) 

332 else: 

333 if avail == 0: 

334 # Yes, it can happen (probably a kernel bug): 

335 # https://github.com/giampaolo/psutil/issues/1915 

336 # In this case "free" CLI tool makes an estimate. We do the same, 

337 # and it matches "free" CLI tool. 

338 avail = calculate_avail_vmem(mems) 

339 

340 if avail < 0: 

341 avail = 0 

342 missing_fields.append('available') 

343 elif avail > total: 

344 # If avail is greater than total or our calculation overflows, 

345 # that's symptomatic of running within a LCX container where such 

346 # values will be dramatically distorted over those of the host. 

347 # https://gitlab.com/procps-ng/procps/blob/24fd2605c51fcc/proc/sysinfo.c#L764 

348 avail = free 

349 

350 used = total - avail 

351 

352 percent = usage_percent((total - avail), total, round_=1) 

353 

354 # Warn about missing metrics which are set to 0. 

355 if missing_fields: 

356 msg = "{} memory stats couldn't be determined and {} set to 0".format( 

357 ", ".join(missing_fields), 

358 "was" if len(missing_fields) == 1 else "were", 

359 ) 

360 warnings.warn(msg, RuntimeWarning, stacklevel=2) 

361 

362 return ntp.svmem( 

363 total, 

364 avail, 

365 percent, 

366 used, 

367 free, 

368 active, 

369 inactive, 

370 buffers, 

371 cached, 

372 shared, 

373 slab, 

374 ) 

375 

376 

377def swap_memory(): 

378 """Return swap memory metrics.""" 

379 mems = {} 

380 with open_binary(f"{get_procfs_path()}/meminfo") as f: 

381 for line in f: 

382 # Note: some fields (e.g. "ShadowCallStack:10373888 kB") 

383 # may not have a space after the colon, see: 

384 # https://github.com/giampaolo/psutil/issues/2809 

385 key, value = line.split(b':', 1) 

386 mems[key + b':'] = int(value.split()[0]) * 1024 

387 # We prefer /proc/meminfo over sysinfo() syscall so that 

388 # psutil.PROCFS_PATH can be used in order to allow retrieval 

389 # for linux containers, see: 

390 # https://github.com/giampaolo/psutil/issues/1015 

391 try: 

392 total = mems[b'SwapTotal:'] 

393 free = mems[b'SwapFree:'] 

394 except KeyError: 

395 _, _, _, _, total, free, unit_multiplier = _psutil.linux_sysinfo() 

396 total *= unit_multiplier 

397 free *= unit_multiplier 

398 

399 used = total - free 

400 percent = usage_percent(used, total, round_=1) 

401 # get pgin/pgouts 

402 try: 

403 f = open_binary(f"{get_procfs_path()}/vmstat") 

404 except OSError as err: 

405 # see https://github.com/giampaolo/psutil/issues/722 

406 msg = ( 

407 "'sin' and 'sout' swap memory stats couldn't " 

408 f"be determined and were set to 0 ({err})" 

409 ) 

410 warnings.warn(msg, RuntimeWarning, stacklevel=2) 

411 sin = sout = 0 

412 else: 

413 with f: 

414 sin = sout = None 

415 for line in f: 

416 # values are expressed in 4 kilo bytes, we want 

417 # bytes instead 

418 if line.startswith(b'pswpin'): 

419 sin = int(line.split(b' ')[1]) * 4 * 1024 

420 elif line.startswith(b'pswpout'): 

421 sout = int(line.split(b' ')[1]) * 4 * 1024 

422 if sin is not None and sout is not None: 

423 break 

424 else: 

425 # we might get here when dealing with exotic Linux 

426 # flavors, see: 

427 # https://github.com/giampaolo/psutil/issues/313 

428 msg = "'sin' and 'sout' swap memory stats couldn't " 

429 msg += "be determined and were set to 0" 

430 warnings.warn(msg, RuntimeWarning, stacklevel=2) 

431 sin = sout = 0 

432 return ntp.sswap(total, used, free, percent, sin, sout) 

433 

434 

435# malloc / heap functions; require glibc 

436if hasattr(_psutil, "heap_info"): 

437 heap_info = _psutil.heap_info 

438 heap_trim = _psutil.heap_trim 

439 

440 

441# ===================================================================== 

442# --- CPU 

443# ===================================================================== 

444 

445 

446def cpu_times(): 

447 """Return a named tuple representing system-wide CPU times.""" 

448 

449 def lsget(lst, idx, field_name): 

450 try: 

451 return lst[idx] 

452 except IndexError: 

453 debug(f"can't get {field_name} CPU time; set it to 0") 

454 return 0 

455 

456 procfs_path = get_procfs_path() 

457 with open_binary(f"{procfs_path}/stat") as f: 

458 values = f.readline().split() 

459 nfields = len(ntp.scputimes._fields) 

460 raw = [float(x) / CLOCK_TICKS for x in values[1 : nfields + 1]] 

461 user, nice, system, idle = raw[:4] 

462 return ntp.scputimes( 

463 user, 

464 system, 

465 idle, 

466 nice, 

467 lsget(raw, 4, "iowait"), # Linux >= 2.5.41 

468 lsget(raw, 5, "irq"), # Linux >= 2.6.0 

469 lsget(raw, 6, "softirq"), # Linux >= 2.6.0 

470 lsget(raw, 7, "steal"), # Linux >= 2.6.11 

471 lsget(raw, 8, "guest"), # Linux >= 2.6.24 

472 lsget(raw, 9, "guest_nice"), # Linux >= 2.6.33 

473 ) 

474 

475 

476def per_cpu_times(): 

477 """Return a list of named tuples representing the CPU times 

478 for every CPU available on the system. 

479 """ 

480 procfs_path = get_procfs_path() 

481 cpus = [] 

482 nfields = len(ntp.scputimes._fields) 

483 with open_binary(f"{procfs_path}/stat") as f: 

484 # get rid of the first line which refers to system wide CPU stats 

485 f.readline() 

486 for line in f: 

487 if line.startswith(b'cpu'): 

488 values = line.split() 

489 raw = [float(x) / CLOCK_TICKS for x in values[1 : nfields + 1]] 

490 user, nice, system, idle = raw[0], raw[1], raw[2], raw[3] 

491 entry = ntp.scputimes(user, system, idle, nice, *raw[4:]) 

492 cpus.append(entry) 

493 return cpus 

494 

495 

496def _parse_cpulist(cpulist): 

497 """Parse Linux CPU list string (e.g "0-3,8,10-11")""" 

498 cpulist = cpulist.strip() 

499 if not cpulist: 

500 return [] 

501 cpus = [] 

502 for chunk in cpulist.split(','): 

503 chunk = chunk.strip() 

504 if not chunk: 

505 continue 

506 if '-' in chunk: 

507 start, _, end = chunk.partition('-') 

508 start = int(start) 

509 end = int(end) 

510 if start > end: 

511 msg = f"invalid CPU range {chunk!r}" 

512 raise ValueError(msg) 

513 cpus.extend(range(start, end + 1)) 

514 else: 

515 cpus.append(int(chunk)) 

516 return cpus 

517 

518 

519def cpu_count_logical(): 

520 """Return the number of logical CPUs in the system.""" 

521 try: 

522 return os.sysconf("SC_NPROCESSORS_ONLN") 

523 except ValueError: 

524 # as a second fallback we try to parse /proc/cpuinfo 

525 num = 0 

526 with open_binary(f"{get_procfs_path()}/cpuinfo") as f: 

527 for line in f: 

528 if line.lower().startswith(b'processor'): 

529 num += 1 

530 

531 # unknown format (e.g. amrel/sparc architectures), see: 

532 # https://github.com/giampaolo/psutil/issues/200 

533 # try to parse /proc/stat as a last resort 

534 if num == 0: 

535 search = re.compile(r'cpu\d') 

536 with open_text(f"{get_procfs_path()}/stat") as f: 

537 for line in f: 

538 line = line.split(' ')[0] 

539 if search.match(line): 

540 num += 1 

541 

542 if num == 0: 

543 # mimic os.cpu_count() 

544 return None 

545 return num 

546 

547 

548def cpu_count_cores(): 

549 """Return the number of CPU cores in the system.""" 

550 # Method #1 

551 ls = set() 

552 # These 2 files are the same but */core_cpus_list is newer while 

553 # */thread_siblings_list is deprecated and may disappear in the future. 

554 # https://www.kernel.org/doc/Documentation/admin-guide/cputopology.rst 

555 # https://github.com/giampaolo/psutil/pull/1727#issuecomment-707624964 

556 # https://lkml.org/lkml/2019/2/26/41 

557 p1 = "/sys/devices/system/cpu/cpu[0-9]*/topology/core_cpus_list" 

558 p2 = "/sys/devices/system/cpu/cpu[0-9]*/topology/thread_siblings_list" 

559 for path in glob.glob(p1) or glob.glob(p2): 

560 with open_binary(path) as f: 

561 ls.add(f.read().strip()) 

562 result = len(ls) 

563 if result != 0: 

564 return result 

565 

566 # Method #2 

567 mapping = {} 

568 current_info = {} 

569 with open_binary(f"{get_procfs_path()}/cpuinfo") as f: 

570 for line in f: 

571 line = line.strip().lower() 

572 if not line: 

573 # new section 

574 try: 

575 mapping[current_info[b'physical id']] = current_info[ 

576 b'cpu cores' 

577 ] 

578 except KeyError: 

579 pass 

580 current_info = {} 

581 elif line.startswith((b'physical id', b'cpu cores')): 

582 # ongoing section 

583 key, value = line.split(b':', 1) 

584 current_info[key.strip()] = int(value) 

585 

586 result = sum(mapping.values()) 

587 return result or None # mimic os.cpu_count() 

588 

589 

590def cpu_stats(): 

591 """Return various CPU stats as a named tuple.""" 

592 with open_binary(f"{get_procfs_path()}/stat") as f: 

593 ctx_switches = None 

594 interrupts = None 

595 soft_interrupts = None 

596 for line in f: 

597 if line.startswith(b'ctxt'): 

598 ctx_switches = int(line.split()[1]) 

599 elif line.startswith(b'intr'): 

600 interrupts = int(line.split()[1]) 

601 elif line.startswith(b'softirq'): 

602 soft_interrupts = int(line.split()[1]) 

603 if ( 

604 ctx_switches is not None 

605 and soft_interrupts is not None 

606 and interrupts is not None 

607 ): 

608 break 

609 syscalls = 0 

610 return ntp.scpustats(ctx_switches, interrupts, soft_interrupts, syscalls) 

611 

612 

613def _cpu_get_cpuinfo_freq(): 

614 """Return current CPU frequency from cpuinfo if available.""" 

615 ret = [] 

616 with open_binary(f"{get_procfs_path()}/cpuinfo") as f: 

617 for line in f: 

618 key, _, value = line.partition(b':') 

619 key = key.strip().lower() 

620 # x86 says "cpu MHz", ppc "clock" (with a MHz suffix), 

621 # s390x "cpu MHz dynamic" plus a "static" one we skip. 

622 # https://github.com/torvalds/linux/blob/master/arch/powerpc/kernel/setup-common.c 

623 # https://github.com/torvalds/linux/blob/master/arch/s390/kernel/processor.c 

624 if key in {b'cpu mhz', b'clock', b'cpu mhz dynamic'}: 

625 value = value.strip() 

626 if value.endswith(b"MHz"): 

627 value = value[:-3] 

628 ret.append(float(value)) 

629 return ret 

630 

631 

632if os.path.exists("/sys/devices/system/cpu/cpufreq/policy0") or os.path.exists( 

633 "/sys/devices/system/cpu/cpu0/cpufreq" 

634): 

635 

636 def cpu_freq(): 

637 """Return frequency metrics for all CPUs. 

638 Contrarily to other OSes, Linux updates these values in 

639 real-time. 

640 """ 

641 pjoin = os.path.join 

642 cpuinfo_freqs = _cpu_get_cpuinfo_freq() 

643 paths = glob.glob( 

644 "/sys/devices/system/cpu/cpufreq/policy[0-9]*" 

645 ) or glob.glob("/sys/devices/system/cpu/cpu[0-9]*/cpufreq") 

646 

647 # One policy may govern more than one CPU, so ask each policy 

648 # which CPUs it affects instead of assuming one per CPU. Offline 

649 # CPUs are listed by no policy, and are therefore left out. 

650 # https://github.com/giampaolo/psutil/issues/2512 

651 cpu_to_path = {} 

652 for path in paths: 

653 affected = bcat(pjoin(path, "affected_cpus"), fallback=None) 

654 if affected is None: 

655 cpu_to_path[int(re.search(r"[0-9]+", path).group())] = path 

656 else: 

657 for cpu in affected.split(): 

658 cpu_to_path[int(cpu)] = path 

659 

660 ret = [] 

661 for i, cpu in enumerate(sorted(cpu_to_path)): 

662 path = cpu_to_path[cpu] 

663 if len(cpu_to_path) == len(cpuinfo_freqs): 

664 # take cached value from cpuinfo if available, see: 

665 # https://github.com/giampaolo/psutil/issues/1851 

666 curr = cpuinfo_freqs[i] * 1000 

667 else: 

668 curr = bcat(pjoin(path, "scaling_cur_freq"), fallback=None) 

669 if curr is None: 

670 # Likely an old RedHat, see: 

671 # https://github.com/giampaolo/psutil/issues/1071 

672 curr = bcat(pjoin(path, "cpuinfo_cur_freq"), fallback=None) 

673 if curr is None: 

674 online_path = f"/sys/devices/system/cpu/cpu{cpu}/online" 

675 # If the CPU core is offline skip it instead of 

676 # reporting it as all zeroes, otherwise it drags 

677 # down the average frequency. See: 

678 # https://github.com/giampaolo/psutil/issues/2628 

679 if cat(online_path, fallback=None) == "0\n": 

680 continue 

681 msg = "can't find current frequency file" 

682 raise NotImplementedError(msg) 

683 curr = int(curr) / 1000 

684 max_ = int(bcat(pjoin(path, "scaling_max_freq"))) / 1000 

685 min_ = int(bcat(pjoin(path, "scaling_min_freq"))) / 1000 

686 ret.append(ntp.scpufreq(curr, min_, max_)) 

687 return ret 

688 

689else: 

690 

691 def cpu_freq(): 

692 """Alternate implementation using /proc/cpuinfo. 

693 min and max frequencies are not available and are set to 0. 

694 """ 

695 return [ntp.scpufreq(x, 0.0, 0.0) for x in _cpu_get_cpuinfo_freq()] 

696 

697 

698# ===================================================================== 

699# --- network 

700# ===================================================================== 

701 

702 

703net_if_addrs = _psutil.net_if_addrs 

704 

705 

706class _Ipv6UnsupportedError(Exception): 

707 pass 

708 

709 

710class NetConnections: 

711 """A wrapper on top of /proc/net/* files, retrieving per-process 

712 and system-wide open connections (TCP, UDP, UNIX) similarly to 

713 "netstat -an". 

714 

715 Note: in case of UNIX sockets we're only able to determine the 

716 local endpoint/path, not the one it's connected to. 

717 According to [1] it would be possible but not easily. 

718 

719 [1] http://serverfault.com/a/417946 

720 """ 

721 

722 def __init__(self): 

723 # The string represents the basename of the corresponding 

724 # /proc/net/{proto_name} file. 

725 tcp4 = ("tcp", socket.AF_INET, socket.SOCK_STREAM) 

726 tcp6 = ("tcp6", socket.AF_INET6, socket.SOCK_STREAM) 

727 udp4 = ("udp", socket.AF_INET, socket.SOCK_DGRAM) 

728 udp6 = ("udp6", socket.AF_INET6, socket.SOCK_DGRAM) 

729 unix = ("unix", socket.AF_UNIX, None) 

730 self.tmap = { 

731 "all": (tcp4, tcp6, udp4, udp6, unix), 

732 "tcp": (tcp4, tcp6), 

733 "tcp4": (tcp4,), 

734 "tcp6": (tcp6,), 

735 "udp": (udp4, udp6), 

736 "udp4": (udp4,), 

737 "udp6": (udp6,), 

738 "unix": (unix,), 

739 "inet": (tcp4, tcp6, udp4, udp6), 

740 "inet4": (tcp4, udp4), 

741 "inet6": (tcp6, udp6), 

742 } 

743 self._procfs_path = None 

744 

745 def get_proc_inodes(self, pid): 

746 inodes = defaultdict(list) 

747 for fd in os.listdir(f"{self._procfs_path}/{pid}/fd"): 

748 try: 

749 inode = readlink(f"{self._procfs_path}/{pid}/fd/{fd}") 

750 except (FileNotFoundError, ProcessLookupError): 

751 # ENOENT == file which is gone in the meantime; 

752 # os.stat(f"/proc/{self.pid}") will be done later 

753 # to force NSP (if it's the case) 

754 continue 

755 except OSError as err: 

756 if err.errno == errno.EINVAL: 

757 # not a link 

758 continue 

759 if err.errno == errno.ENAMETOOLONG: 

760 # file name too long 

761 debug(err) 

762 continue 

763 raise 

764 else: 

765 if inode.startswith('socket:['): 

766 # the process is using a socket 

767 inode = inode[8:][:-1] 

768 inodes[inode].append((pid, int(fd))) 

769 return inodes 

770 

771 def get_all_inodes(self): 

772 inodes = {} 

773 for pid in pids(): 

774 try: 

775 inodes.update(self.get_proc_inodes(pid)) 

776 except (FileNotFoundError, ProcessLookupError, PermissionError): 

777 # os.listdir() is gonna raise a lot of access denied 

778 # exceptions in case of unprivileged user; that's fine 

779 # as we'll just end up returning a connection with PID 

780 # and fd set to None anyway. 

781 # Both netstat -an and lsof does the same so it's 

782 # unlikely we can do any better. 

783 # ENOENT just means a PID disappeared on us. 

784 continue 

785 return inodes 

786 

787 @staticmethod 

788 def decode_address(addr, family): 

789 """Accept an "ip:port" address as displayed in /proc/net/* 

790 and convert it into a human readable form, like: 

791 

792 "0500000A:0016" -> ("10.0.0.5", 22) 

793 "0000000000000000FFFF00000100007F:9E49" -> ("::ffff:127.0.0.1", 40521) 

794 

795 The IP address portion is a little or big endian four-byte 

796 hexadecimal number; that is, the least significant byte is listed 

797 first, so we need to reverse the order of the bytes to convert it 

798 to an IP address. 

799 The port is represented as a two-byte hexadecimal number. 

800 

801 Reference: 

802 http://linuxdevcenter.com/pub/a/linux/2000/11/16/LinuxAdmin.html 

803 """ 

804 ip, port = addr.split(':') 

805 port = int(port, 16) 

806 # this usually refers to a local socket in listen mode with 

807 # no end-points connected 

808 if not port: 

809 return () 

810 ip = ip.encode('ascii') 

811 if family == socket.AF_INET: 

812 # see: https://github.com/giampaolo/psutil/issues/201 

813 if LITTLE_ENDIAN: 

814 ip = socket.inet_ntop(family, base64.b16decode(ip)[::-1]) 

815 else: 

816 ip = socket.inet_ntop(family, base64.b16decode(ip)) 

817 else: # IPv6 

818 ip = base64.b16decode(ip) 

819 try: 

820 # see: https://github.com/giampaolo/psutil/issues/201 

821 if LITTLE_ENDIAN: 

822 ip = socket.inet_ntop( 

823 socket.AF_INET6, 

824 struct.pack('>4I', *struct.unpack('<4I', ip)), 

825 ) 

826 else: 

827 ip = socket.inet_ntop( 

828 socket.AF_INET6, 

829 struct.pack('<4I', *struct.unpack('<4I', ip)), 

830 ) 

831 except ValueError: 

832 # see: https://github.com/giampaolo/psutil/issues/623 

833 if not supports_ipv6(): 

834 raise _Ipv6UnsupportedError from None 

835 raise 

836 return ntp.addr(ip, port) 

837 

838 @staticmethod 

839 def process_inet(file, family, type_, inodes, filter_pid=None): 

840 """Parse /proc/net/tcp* and /proc/net/udp* files.""" 

841 if file.endswith('6') and not os.path.exists(file): 

842 # IPv6 not supported 

843 return 

844 with open_text(file) as f: 

845 f.readline() # skip the first line 

846 for lineno, line in enumerate(f, 1): 

847 try: 

848 _, laddr, raddr, status, _, _, _, _, _, inode = ( 

849 line.split()[:10] 

850 ) 

851 except ValueError: 

852 msg = ( 

853 f"error while parsing {file}; malformed line" 

854 f" {lineno} {line!r}" 

855 ) 

856 raise RuntimeError(msg) from None 

857 if inode in inodes: 

858 # # We assume inet sockets are unique, so we error 

859 # # out if there are multiple references to the 

860 # # same inode. We won't do this for UNIX sockets. 

861 # if len(inodes[inode]) > 1 and family != socket.AF_UNIX: 

862 # raise ValueError("ambiguous inode with multiple " 

863 # "PIDs references") 

864 pid, fd = inodes[inode][0] 

865 else: 

866 pid, fd = None, -1 

867 if filter_pid is not None and filter_pid != pid: 

868 continue 

869 else: 

870 if type_ == socket.SOCK_STREAM: 

871 status = TCP_STATUSES[status] 

872 else: 

873 status = ConnectionStatus.CONN_NONE 

874 try: 

875 laddr = NetConnections.decode_address(laddr, family) 

876 raddr = NetConnections.decode_address(raddr, family) 

877 except _Ipv6UnsupportedError: 

878 continue 

879 yield (fd, family, type_, laddr, raddr, status, pid) 

880 

881 @staticmethod 

882 def process_unix(file, family, inodes, filter_pid=None): 

883 """Parse /proc/net/unix files.""" 

884 with open_text(file) as f: 

885 f.readline() # skip the first line 

886 for line in f: 

887 tokens = line.split() 

888 try: 

889 _, _, _, _, type_, _, inode = tokens[0:7] 

890 except ValueError: 

891 if ' ' not in line: 

892 # see: https://github.com/giampaolo/psutil/issues/766 

893 continue 

894 msg = ( 

895 f"error while parsing {file}; malformed line {line!r}" 

896 ) 

897 raise RuntimeError(msg) # noqa: B904 

898 if inode in inodes: # noqa: SIM108 

899 # With UNIX sockets we can have a single inode 

900 # referencing many file descriptors. 

901 pairs = inodes[inode] 

902 else: 

903 pairs = [(None, -1)] 

904 for pid, fd in pairs: 

905 if filter_pid is not None and filter_pid != pid: 

906 continue 

907 else: 

908 path = tokens[-1] if len(tokens) == 8 else '' 

909 type_ = socktype_to_enum(int(type_)) 

910 # XXX: determining the remote endpoint of a 

911 # UNIX socket on Linux is not possible, see: 

912 # https://serverfault.com/questions/252723/ 

913 raddr = "" 

914 status = ConnectionStatus.CONN_NONE 

915 yield (fd, family, type_, path, raddr, status, pid) 

916 

917 def retrieve(self, kind, pid=None): 

918 self._procfs_path = get_procfs_path() 

919 if pid is not None: 

920 inodes = self.get_proc_inodes(pid) 

921 if not inodes: 

922 # no connections for this process 

923 return [] 

924 else: 

925 inodes = self.get_all_inodes() 

926 ret = set() 

927 for proto_name, family, type_ in self.tmap[kind]: 

928 path = f"{self._procfs_path}/net/{proto_name}" 

929 if family in {socket.AF_INET, socket.AF_INET6}: 

930 ls = self.process_inet( 

931 path, family, type_, inodes, filter_pid=pid 

932 ) 

933 else: 

934 ls = self.process_unix(path, family, inodes, filter_pid=pid) 

935 for fd, family, type_, laddr, raddr, status, bound_pid in ls: 

936 if pid: 

937 conn = ntp.pconn(fd, family, type_, laddr, raddr, status) 

938 else: 

939 conn = ntp.sconn( 

940 fd, family, type_, laddr, raddr, status, bound_pid 

941 ) 

942 ret.add(conn) 

943 return list(ret) 

944 

945 

946_net_connections = NetConnections() 

947 

948 

949def net_connections(kind='inet'): 

950 """Return system-wide open connections.""" 

951 return _net_connections.retrieve(kind) 

952 

953 

954def net_io_counters(): 

955 """Return network I/O statistics for every network interface 

956 installed on the system as a dict of raw tuples. 

957 """ 

958 with open_text(f"{get_procfs_path()}/net/dev") as f: 

959 lines = f.readlines() 

960 retdict = {} 

961 for line in lines[2:]: 

962 colon = line.rfind(':') 

963 assert colon > 0, repr(line) 

964 name = line[:colon].strip() 

965 fields = line[colon + 1 :].strip().split() 

966 

967 ( 

968 # in 

969 bytes_recv, 

970 packets_recv, 

971 errin, 

972 dropin, 

973 _fifoin, # unused 

974 _framein, # unused 

975 _compressedin, # unused 

976 _multicastin, # unused 

977 # out 

978 bytes_sent, 

979 packets_sent, 

980 errout, 

981 dropout, 

982 _fifoout, # unused 

983 _collisionsout, # unused 

984 _carrierout, # unused 

985 _compressedout, # unused 

986 ) = map(int, fields) 

987 

988 retdict[name] = ( 

989 bytes_sent, 

990 bytes_recv, 

991 packets_sent, 

992 packets_recv, 

993 errin, 

994 errout, 

995 dropin, 

996 dropout, 

997 ) 

998 return retdict 

999 

1000 

1001def net_if_stats(): 

1002 """Get NIC stats (isup, duplex, speed, mtu).""" 

1003 duplex_map = { 

1004 _psutil.DUPLEX_FULL: NicDuplex.NIC_DUPLEX_FULL, 

1005 _psutil.DUPLEX_HALF: NicDuplex.NIC_DUPLEX_HALF, 

1006 _psutil.DUPLEX_UNKNOWN: NicDuplex.NIC_DUPLEX_UNKNOWN, 

1007 } 

1008 names = net_io_counters().keys() 

1009 ret = {} 

1010 for name in names: 

1011 try: 

1012 mtu = _psutil.net_if_mtu(name) 

1013 flags = _psutil.net_if_flags(name) 

1014 duplex, speed = _psutil.net_if_duplex_speed(name) 

1015 except OSError as err: 

1016 # https://github.com/giampaolo/psutil/issues/1279 

1017 if err.errno != errno.ENODEV: 

1018 raise 

1019 debug(err) 

1020 else: 

1021 output_flags = ','.join(flags) 

1022 isup = 'running' in flags 

1023 ret[name] = ntp.snicstats( 

1024 isup, duplex_map[duplex], speed, mtu, output_flags 

1025 ) 

1026 return ret 

1027 

1028 

1029# ===================================================================== 

1030# --- disks 

1031# ===================================================================== 

1032 

1033 

1034disk_usage = _psposix.disk_usage 

1035 

1036 

1037def disk_io_counters(perdisk=False): 

1038 """Return disk I/O statistics for every disk installed on the 

1039 system as a dict of raw tuples. 

1040 """ 

1041 

1042 def read_procfs(): 

1043 # OK, this is a bit confusing. The format of /proc/diskstats can 

1044 # have 3 variations. 

1045 # On Linux 2.4 each line has always 15 fields, e.g.: 

1046 # "3 0 8 hda 8 8 8 8 8 8 8 8 8 8 8" 

1047 # On Linux 2.6+ each line *usually* has 14 fields, and the disk 

1048 # name is in another position, like this: 

1049 # "3 0 hda 8 8 8 8 8 8 8 8 8 8 8" 

1050 # ...unless (Linux 2.6) the line refers to a partition instead 

1051 # of a disk, in which case the line has less fields (7): 

1052 # "3 1 hda1 8 8 8 8" 

1053 # 4.18+ has 4 fields added: 

1054 # "3 0 hda 8 8 8 8 8 8 8 8 8 8 8 0 0 0 0" 

1055 # 5.5 has 2 more fields. 

1056 # See: 

1057 # https://www.kernel.org/doc/Documentation/iostats.txt 

1058 # https://www.kernel.org/doc/Documentation/ABI/testing/procfs-diskstats 

1059 with open_text(f"{get_procfs_path()}/diskstats") as f: 

1060 lines = f.readlines() 

1061 for line in lines: 

1062 fields = line.split() 

1063 flen = len(fields) 

1064 # fmt: off 

1065 if flen == 15: 

1066 # Linux 2.4 

1067 name = fields[3] 

1068 reads = int(fields[2]) 

1069 (reads_merged, rbytes, rtime, writes, writes_merged, 

1070 wbytes, wtime, _, busy_time, _) = map(int, fields[4:14]) 

1071 elif flen == 14 or flen >= 18: 

1072 # Linux 2.6+, line referring to a disk 

1073 name = fields[2] 

1074 (reads, reads_merged, rbytes, rtime, writes, writes_merged, 

1075 wbytes, wtime, _, busy_time, _) = map(int, fields[3:14]) 

1076 elif flen == 7: 

1077 # Linux 2.6+, line referring to a partition 

1078 name = fields[2] 

1079 reads, rbytes, writes, wbytes = map(int, fields[3:]) 

1080 rtime = wtime = reads_merged = writes_merged = busy_time = 0 

1081 else: 

1082 msg = f"not sure how to interpret line {line!r}" 

1083 raise ValueError(msg) 

1084 yield (name, reads, writes, rbytes, wbytes, rtime, wtime, 

1085 reads_merged, writes_merged, busy_time) 

1086 # fmt: on 

1087 

1088 def read_sysfs(): 

1089 for block in os.listdir('/sys/block'): 

1090 for root, _, files in os.walk(os.path.join('/sys/block', block)): 

1091 if 'stat' not in files: 

1092 continue 

1093 with open_text(os.path.join(root, 'stat')) as f: 

1094 fields = f.read().strip().split() 

1095 name = os.path.basename(root) 

1096 # fmt: off 

1097 (reads, reads_merged, rbytes, rtime, writes, writes_merged, 

1098 wbytes, wtime, _, busy_time) = map(int, fields[:10]) 

1099 yield (name, reads, writes, rbytes, wbytes, rtime, 

1100 wtime, reads_merged, writes_merged, busy_time) 

1101 # fmt: on 

1102 

1103 if os.path.exists(f"{get_procfs_path()}/diskstats"): 

1104 gen = read_procfs() 

1105 elif os.path.exists('/sys/block'): 

1106 gen = read_sysfs() 

1107 else: 

1108 msg = ( 

1109 f"{get_procfs_path()}/diskstats nor /sys/block are available on" 

1110 " this system" 

1111 ) 

1112 raise NotImplementedError(msg) 

1113 

1114 retdict = {} 

1115 for entry in gen: 

1116 # fmt: off 

1117 (name, reads, writes, rbytes, wbytes, rtime, wtime, reads_merged, 

1118 writes_merged, busy_time) = entry 

1119 if not perdisk and not is_storage_device(name): 

1120 # perdisk=False means we want to calculate totals so we skip 

1121 # partitions (e.g. 'sda1', 'nvme0n1p1') and only include 

1122 # base disk devices (e.g. 'sda', 'nvme0n1'). Base disks 

1123 # include a total of all their partitions + some extra size 

1124 # of their own: 

1125 # $ cat /proc/diskstats 

1126 # 259 0 sda 10485760 ... 

1127 # 259 1 sda1 5186039 ... 

1128 # 259 1 sda2 5082039 ... 

1129 # See: 

1130 # https://github.com/giampaolo/psutil/pull/1313 

1131 continue 

1132 

1133 rbytes *= DISK_SECTOR_SIZE 

1134 wbytes *= DISK_SECTOR_SIZE 

1135 retdict[name] = (reads, writes, rbytes, wbytes, rtime, wtime, 

1136 reads_merged, writes_merged, busy_time) 

1137 # fmt: on 

1138 

1139 return retdict 

1140 

1141 

1142class RootFsDeviceFinder: 

1143 """disk_partitions() may return partitions with device == "/dev/root" 

1144 or "rootfs". This container class uses different strategies to try to 

1145 obtain the real device path. Resources: 

1146 https://bootlin.com/blog/find-root-device/ 

1147 https://www.systutorials.com/how-to-find-the-disk-where-root-is-on-in-bash-on-linux/. 

1148 """ 

1149 

1150 __slots__ = ['major', 'minor'] 

1151 

1152 def __init__(self): 

1153 dev = os.stat("/").st_dev 

1154 self.major = os.major(dev) 

1155 self.minor = os.minor(dev) 

1156 

1157 def ask_proc_partitions(self): 

1158 with open_text(f"{get_procfs_path()}/partitions") as f: 

1159 for line in f.readlines()[2:]: 

1160 fields = line.split() 

1161 if len(fields) < 4: # just for extra safety 

1162 continue 

1163 major = int(fields[0]) if fields[0].isdigit() else None 

1164 minor = int(fields[1]) if fields[1].isdigit() else None 

1165 name = fields[3] 

1166 if major == self.major and minor == self.minor: 

1167 if name: # just for extra safety 

1168 return f"/dev/{name}" 

1169 

1170 def ask_sys_dev_block(self): 

1171 path = f"/sys/dev/block/{self.major}:{self.minor}/uevent" 

1172 with open_text(path) as f: 

1173 for line in f: 

1174 if line.startswith("DEVNAME="): 

1175 # just for extra safety 

1176 if name := line.strip().rpartition("DEVNAME=")[2]: 

1177 return f"/dev/{name}" 

1178 

1179 def ask_sys_class_block(self): 

1180 needle = f"{self.major}:{self.minor}" 

1181 files = glob.iglob("/sys/class/block/*/dev") 

1182 for file in files: 

1183 try: 

1184 f = open_text(file) 

1185 except FileNotFoundError: # race condition 

1186 continue 

1187 else: 

1188 with f: 

1189 data = f.read().strip() 

1190 if data == needle: 

1191 name = os.path.basename(os.path.dirname(file)) 

1192 return f"/dev/{name}" 

1193 

1194 def find(self): 

1195 path = None 

1196 if path is None: 

1197 try: 

1198 path = self.ask_proc_partitions() 

1199 except OSError as err: 

1200 debug(err) 

1201 if path is None: 

1202 try: 

1203 path = self.ask_sys_dev_block() 

1204 except OSError as err: 

1205 debug(err) 

1206 if path is None: 

1207 try: 

1208 path = self.ask_sys_class_block() 

1209 except OSError as err: 

1210 debug(err) 

1211 # We use exists() because the "/dev/*" part of the path is hard 

1212 # coded, so we want to be sure. 

1213 if path is not None and os.path.exists(path): 

1214 return path 

1215 

1216 

1217def disk_partitions(all=False): 

1218 """Return mounted disk partitions as a list of named tuples.""" 

1219 fstypes = set() 

1220 procfs_path = get_procfs_path() 

1221 if not all: 

1222 with open_text(f"{procfs_path}/filesystems") as f: 

1223 for line in f: 

1224 line = line.strip() 

1225 if not line.startswith("nodev"): 

1226 fstypes.add(line.strip()) 

1227 else: 

1228 # ignore all lines starting with "nodev" except "nodev zfs" 

1229 fstype = line.split("\t")[1] 

1230 if fstype == "zfs": 

1231 fstypes.add("zfs") 

1232 

1233 # See: https://github.com/giampaolo/psutil/issues/1307 

1234 if procfs_path == "/proc" and os.path.isfile('/etc/mtab'): 

1235 mounts_path = os.path.realpath("/etc/mtab") 

1236 else: 

1237 mounts_path = os.path.realpath(f"{procfs_path}/self/mounts") 

1238 

1239 retlist = [] 

1240 partitions = _psutil.disk_partitions(mounts_path) 

1241 for partition in partitions: 

1242 device, mountpoint, fstype, opts = partition 

1243 if device == 'none': 

1244 device = '' 

1245 if device in {"/dev/root", "rootfs"}: 

1246 device = RootFsDeviceFinder().find() or device 

1247 if not all: 

1248 if not device or fstype not in fstypes: 

1249 continue 

1250 ntuple = ntp.sdiskpart(device, mountpoint, fstype, opts) 

1251 retlist.append(ntuple) 

1252 

1253 return retlist 

1254 

1255 

1256# ===================================================================== 

1257# --- sensors 

1258# ===================================================================== 

1259 

1260 

1261def sensors_temperatures(): 

1262 """Return hardware (CPU and others) temperatures as a dict 

1263 including hardware name, label, current, max and critical 

1264 temperatures. 

1265 

1266 Implementation notes: 

1267 - /sys/class/hwmon looks like the most recent interface to 

1268 retrieve this info, and this implementation relies on it 

1269 only (old distros will probably use something else) 

1270 - lm-sensors on Ubuntu 16.04 relies on /sys/class/hwmon 

1271 - /sys/class/thermal/thermal_zone* is another one but it's more 

1272 difficult to parse 

1273 """ 

1274 ret = collections.defaultdict(list) 

1275 basenames = glob.glob('/sys/class/hwmon/hwmon*/temp*_*') 

1276 # CentOS has an intermediate /device directory: 

1277 # https://github.com/giampaolo/psutil/issues/971 

1278 # https://github.com/nicolargo/glances/issues/1060 

1279 basenames.extend(glob.glob('/sys/class/hwmon/hwmon*/device/temp*_*')) 

1280 basenames = sorted({x.split('_')[0] for x in basenames}) 

1281 

1282 # Only add the coretemp hwmon entries if they're not already in 

1283 # /sys/class/hwmon/ 

1284 # https://github.com/giampaolo/psutil/issues/1708 

1285 # https://github.com/giampaolo/psutil/pull/1648 

1286 basenames2 = glob.glob( 

1287 '/sys/devices/platform/coretemp.*/hwmon/hwmon*/temp*_*' 

1288 ) 

1289 repl = re.compile(r"/sys/devices/platform/coretemp.*/hwmon/") 

1290 for name in basenames2: 

1291 altname = repl.sub('/sys/class/hwmon/', name) 

1292 if altname not in basenames: 

1293 basenames.append(name) 

1294 

1295 for base in basenames: 

1296 try: 

1297 path = base + '_input' 

1298 current = float(bcat(path)) / 1000.0 

1299 path = os.path.join(os.path.dirname(base), 'name') 

1300 unit_name = cat(path).strip() 

1301 except (OSError, ValueError): 

1302 # A lot of things can go wrong here, so let's just skip the 

1303 # whole entry. Sure thing is Linux's /sys/class/hwmon really 

1304 # is a stinky broken mess. 

1305 # https://github.com/giampaolo/psutil/issues/1009 

1306 # https://github.com/giampaolo/psutil/issues/1101 

1307 # https://github.com/giampaolo/psutil/issues/1129 

1308 # https://github.com/giampaolo/psutil/issues/1245 

1309 # https://github.com/giampaolo/psutil/issues/1323 

1310 continue 

1311 

1312 high = bcat(base + '_max', fallback=None) 

1313 critical = bcat(base + '_crit', fallback=None) 

1314 label = cat(base + '_label', fallback='').strip() 

1315 

1316 if high is not None: 

1317 try: 

1318 high = float(high) / 1000.0 

1319 except ValueError: 

1320 high = None 

1321 if critical is not None: 

1322 try: 

1323 critical = float(critical) / 1000.0 

1324 except ValueError: 

1325 critical = None 

1326 

1327 ret[unit_name].append((label, current, high, critical)) 

1328 

1329 # Indication that no sensors were detected in /sys/class/hwmon/ 

1330 if not basenames: 

1331 basenames = glob.glob('/sys/class/thermal/thermal_zone*') 

1332 basenames = sorted(set(basenames)) 

1333 

1334 for base in basenames: 

1335 try: 

1336 path = os.path.join(base, 'temp') 

1337 current = float(bcat(path)) / 1000.0 

1338 path = os.path.join(base, 'type') 

1339 unit_name = cat(path).strip() 

1340 except (OSError, ValueError) as err: 

1341 debug(err) 

1342 continue 

1343 

1344 trip_paths = glob.glob(base + '/trip_point*') 

1345 trip_points = { 

1346 '_'.join(os.path.basename(p).split('_')[0:3]) 

1347 for p in trip_paths 

1348 } 

1349 critical = None 

1350 high = None 

1351 for trip_point in trip_points: 

1352 path = os.path.join(base, trip_point + "_type") 

1353 trip_type = cat(path, fallback='').strip() 

1354 if trip_type == 'critical': 

1355 critical = bcat( 

1356 os.path.join(base, trip_point + "_temp"), fallback=None 

1357 ) 

1358 elif trip_type == 'high': 

1359 high = bcat( 

1360 os.path.join(base, trip_point + "_temp"), fallback=None 

1361 ) 

1362 

1363 if high is not None: 

1364 try: 

1365 high = float(high) / 1000.0 

1366 except ValueError: 

1367 high = None 

1368 if critical is not None: 

1369 try: 

1370 critical = float(critical) / 1000.0 

1371 except ValueError: 

1372 critical = None 

1373 

1374 ret[unit_name].append(('', current, high, critical)) 

1375 

1376 return dict(ret) 

1377 

1378 

1379def sensors_fans(): 

1380 """Return hardware fans info (for CPU and other peripherals) as a 

1381 dict including hardware label and current speed. 

1382 

1383 Implementation notes: 

1384 - /sys/class/hwmon looks like the most recent interface to 

1385 retrieve this info, and this implementation relies on it 

1386 only (old distros will probably use something else) 

1387 - lm-sensors on Ubuntu 16.04 relies on /sys/class/hwmon 

1388 """ 

1389 ret = collections.defaultdict(list) 

1390 basenames = glob.glob('/sys/class/hwmon/hwmon*/fan*_*') 

1391 if not basenames: 

1392 # CentOS has an intermediate /device directory: 

1393 # https://github.com/giampaolo/psutil/issues/971 

1394 basenames = glob.glob('/sys/class/hwmon/hwmon*/device/fan*_*') 

1395 

1396 basenames = sorted({x.split("_")[0] for x in basenames}) 

1397 for base in basenames: 

1398 try: 

1399 current = int(bcat(base + '_input')) 

1400 except OSError as err: 

1401 debug(err) 

1402 continue 

1403 unit_name = cat(os.path.join(os.path.dirname(base), 'name')).strip() 

1404 label = cat(base + '_label', fallback='').strip() 

1405 ret[unit_name].append(ntp.sfan(label, current)) 

1406 

1407 return dict(ret) 

1408 

1409 

1410def sensors_battery(): 

1411 """Return battery information. 

1412 Implementation note: it appears /sys/class/power_supply/BAT0/ 

1413 directory structure may vary and provide files with the same 

1414 meaning but under different names, see: 

1415 https://github.com/giampaolo/psutil/issues/966. 

1416 """ 

1417 null = object() 

1418 

1419 def multi_bcat(*paths): 

1420 """Attempt to read the content of multiple files which may 

1421 not exist. If none of them exist return None. 

1422 """ 

1423 for path in paths: 

1424 ret = bcat(path, fallback=null) 

1425 if ret != null: 

1426 try: 

1427 return int(ret) 

1428 except ValueError: 

1429 return ret.strip() 

1430 return None 

1431 

1432 bats = [ 

1433 x 

1434 for x in os.listdir(POWER_SUPPLY_PATH) 

1435 if x.startswith('BAT') or 'battery' in x.lower() 

1436 ] 

1437 if not bats: 

1438 return None 

1439 # Get the first available battery. Usually this is "BAT0", except 

1440 # some rare exceptions: 

1441 # https://github.com/giampaolo/psutil/issues/1238 

1442 root = os.path.join(POWER_SUPPLY_PATH, min(bats)) 

1443 

1444 # Base metrics. 

1445 energy_now = multi_bcat(root + "/energy_now", root + "/charge_now") 

1446 power_now = multi_bcat(root + "/power_now", root + "/current_now") 

1447 energy_full = multi_bcat(root + "/energy_full", root + "/charge_full") 

1448 time_to_empty = multi_bcat(root + "/time_to_empty_now") 

1449 

1450 # Percent. If we have energy_full the percentage will be more 

1451 # accurate compared to reading /capacity file (float vs. int). 

1452 if energy_full is not None and energy_now is not None: 

1453 try: 

1454 percent = 100.0 * energy_now / energy_full 

1455 except ZeroDivisionError: 

1456 percent = 0.0 

1457 else: 

1458 percent = float(cat(root + "/capacity", fallback=-1)) 

1459 if percent == -1: 

1460 return None 

1461 

1462 # Is AC power cable plugged in? 

1463 # Note: AC0 is not always available and sometimes (e.g. CentOS7) 

1464 # it's called "AC". 

1465 power_plugged = None 

1466 online = multi_bcat( 

1467 os.path.join(POWER_SUPPLY_PATH, "AC0/online"), 

1468 os.path.join(POWER_SUPPLY_PATH, "AC/online"), 

1469 ) 

1470 if online is not None: 

1471 power_plugged = online == 1 

1472 else: 

1473 status = cat(root + "/status", fallback="").strip().lower() 

1474 if status == "discharging": 

1475 power_plugged = False 

1476 elif status in {"charging", "full"}: 

1477 power_plugged = True 

1478 

1479 # Seconds left. 

1480 if power_plugged: 

1481 secsleft = BatteryTime.POWER_TIME_UNLIMITED 

1482 elif energy_now is not None and power_now is not None: 

1483 try: 

1484 secsleft = int(energy_now / abs(power_now) * 3600) 

1485 except ZeroDivisionError: 

1486 secsleft = BatteryTime.POWER_TIME_UNKNOWN 

1487 elif time_to_empty is not None: 

1488 secsleft = int(time_to_empty * 60) 

1489 if secsleft < 0: 

1490 secsleft = BatteryTime.POWER_TIME_UNKNOWN 

1491 else: 

1492 secsleft = BatteryTime.POWER_TIME_UNKNOWN 

1493 

1494 return ntp.sbattery(percent, secsleft, power_plugged) 

1495 

1496 

1497# ===================================================================== 

1498# --- other system functions 

1499# ===================================================================== 

1500 

1501 

1502def users(): 

1503 """Return currently connected users as a list of named tuples.""" 

1504 retlist = [] 

1505 rawlist = _psutil.users() 

1506 for item in rawlist: 

1507 user, tty, hostname, tstamp, pid = item 

1508 nt = ntp.suser(user, tty or None, hostname, tstamp, pid) 

1509 retlist.append(nt) 

1510 return retlist 

1511 

1512 

1513def boot_time(): 

1514 """Return the system boot time expressed in seconds since the epoch.""" 

1515 path = f"{get_procfs_path()}/stat" 

1516 with open_binary(path) as f: 

1517 for line in f: 

1518 if line.startswith(b'btime'): 

1519 return float(line.strip().split()[1]) 

1520 msg = f"line 'btime' not found in {path}" 

1521 raise RuntimeError(msg) 

1522 

1523 

1524# ===================================================================== 

1525# --- processes 

1526# ===================================================================== 

1527 

1528 

1529def pids(): 

1530 """Returns a list of PIDs currently running on the system.""" 

1531 path = get_procfs_path().encode(ENCODING) 

1532 return [int(x) for x in os.listdir(path) if x.isdigit()] 

1533 

1534 

1535def pid_exists(pid): 

1536 """Check for the existence of a unix PID. Linux TIDs are not 

1537 supported (always return False). 

1538 """ 

1539 if not _psposix.pid_exists(pid): 

1540 return False 

1541 else: 

1542 # Linux's apparently does not distinguish between PIDs and TIDs 

1543 # (thread IDs). 

1544 # listdir("/proc") won't show any TID (only PIDs) but 

1545 # os.stat("/proc/{tid}") will succeed if {tid} exists. 

1546 # os.kill() can also be passed a TID. This is quite confusing. 

1547 # In here we want to enforce this distinction and support PIDs 

1548 # only, see: 

1549 # https://github.com/giampaolo/psutil/issues/687 

1550 try: 

1551 # Note: already checked that this is faster than using a 

1552 # regular expr. Also (a lot) faster than doing 

1553 # 'return pid in pids()' 

1554 path = f"{get_procfs_path()}/{pid}/status" 

1555 with open_binary(path) as f: 

1556 for line in f: 

1557 if line.startswith(b"Tgid:"): 

1558 tgid = int(line.split()[1]) 

1559 # If tgid and pid are the same then we're 

1560 # dealing with a process PID. 

1561 return tgid == pid 

1562 msg = f"'Tgid' line not found in {path}" 

1563 raise ValueError(msg) 

1564 except (OSError, ValueError): 

1565 return pid in pids() 

1566 

1567 

1568def ppid_map(): 

1569 """Obtain a {pid: ppid, ...} dict for all running processes in 

1570 one shot. Used to speed up Process.children(). 

1571 """ 

1572 ret = {} 

1573 procfs_path = get_procfs_path() 

1574 for pid in pids(): 

1575 try: 

1576 with open_binary(f"{procfs_path}/{pid}/stat") as f: 

1577 data = f.read() 

1578 except (FileNotFoundError, ProcessLookupError): 

1579 pass 

1580 except PermissionError as err: 

1581 raise AccessDenied(pid) from err 

1582 else: 

1583 rpar = data.rfind(b')') 

1584 dset = data[rpar + 2 :].split() 

1585 ppid = int(dset[1]) 

1586 ret[pid] = ppid 

1587 return ret 

1588 

1589 

1590def wrap_exceptions(fun): 

1591 """Decorator which translates bare OSError exceptions into 

1592 NoSuchProcess and AccessDenied. 

1593 """ 

1594 

1595 @functools.wraps(fun) 

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

1597 pid, name = self.pid, self._name 

1598 try: 

1599 return fun(self, *args, **kwargs) 

1600 except PermissionError as err: 

1601 raise AccessDenied(pid, name) from err 

1602 except ProcessLookupError as err: 

1603 self._raise_if_zombie() 

1604 raise NoSuchProcess(pid, name) from err 

1605 except FileNotFoundError as err: 

1606 self._raise_if_zombie() 

1607 # /proc/PID directory may still exist, but the files within 

1608 # it may not, indicating the process is gone, see: 

1609 # https://github.com/giampaolo/psutil/issues/2418 

1610 if not os.path.exists(f"{self._procfs_path}/{pid}/stat"): 

1611 raise NoSuchProcess(pid, name) from err 

1612 raise 

1613 

1614 return wrapper 

1615 

1616 

1617class Process: 

1618 """Linux process implementation.""" 

1619 

1620 __slots__ = [ 

1621 "_cache", 

1622 "_ctime", 

1623 "_name", 

1624 "_ppid", 

1625 "_procfs_path", 

1626 "pid", 

1627 ] 

1628 

1629 def __init__(self, pid): 

1630 self.pid = pid 

1631 self._name = None 

1632 self._ppid = None 

1633 self._ctime = None 

1634 self._procfs_path = get_procfs_path() 

1635 

1636 def _is_zombie(self): 

1637 # Note: most of the times Linux is able to return info about the 

1638 # process even if it's a zombie, and /proc/{pid} will exist. 

1639 # There are some exceptions though, like exe(), cmdline() and 

1640 # memory_maps(). In these cases /proc/{pid}/{file} exists but 

1641 # it's empty. Instead of returning a "null" value we'll raise an 

1642 # exception. 

1643 try: 

1644 data = bcat(f"{self._procfs_path}/{self.pid}/stat") 

1645 except OSError: 

1646 return False 

1647 else: 

1648 rpar = data.rfind(b')') 

1649 status = data[rpar + 2 : rpar + 3] 

1650 return status == b"Z" 

1651 

1652 def _raise_if_zombie(self): 

1653 if self._is_zombie(): 

1654 raise ZombieProcess(self.pid, self._name, self._ppid) 

1655 

1656 def _raise_if_not_alive(self): 

1657 """Raise NSP if the process disappeared on us.""" 

1658 # For those C function who do not raise NSP, possibly returning 

1659 # incorrect or incomplete result. 

1660 os.stat(f"{self._procfs_path}/{self.pid}") 

1661 

1662 def _readlink(self, path, fallback=UNSET): 

1663 # * https://github.com/giampaolo/psutil/issues/503 

1664 # os.readlink('/proc/pid/exe') may raise ESRCH (ProcessLookupError) 

1665 # instead of ENOENT (FileNotFoundError) when it races. 

1666 # * ENOENT may occur also if the path actually exists if PID is 

1667 # a low PID (~0-20 range). 

1668 # * https://github.com/giampaolo/psutil/issues/2514 

1669 try: 

1670 return readlink(path) 

1671 except (FileNotFoundError, ProcessLookupError): 

1672 if os.path.lexists(f"{self._procfs_path}/{self.pid}"): 

1673 self._raise_if_zombie() 

1674 if fallback is not UNSET: 

1675 return fallback 

1676 raise 

1677 

1678 @wrap_exceptions 

1679 @memoize_when_activated 

1680 def _parse_stat_file(self): 

1681 """Parse /proc/{pid}/stat file and return a dict with various 

1682 process info. 

1683 Using "man proc" as a reference: where "man proc" refers to 

1684 position N always subtract 3 (e.g ppid position 4 in 

1685 'man proc' == position 1 in here). 

1686 The return value is cached in case oneshot() ctx manager is 

1687 in use. 

1688 """ 

1689 data = bcat(f"{self._procfs_path}/{self.pid}/stat") 

1690 # Process name is between parentheses. It can contain spaces and 

1691 # other parentheses. This is taken into account by looking for 

1692 # the first occurrence of "(" and the last occurrence of ")". 

1693 rpar = data.rfind(b')') 

1694 name = data[data.find(b'(') + 1 : rpar] 

1695 fields = data[rpar + 2 :].split() 

1696 

1697 ret = {} 

1698 ret['name'] = name 

1699 ret['status'] = fields[0] 

1700 ret['ppid'] = fields[1] 

1701 ret['ttynr'] = fields[4] 

1702 ret['minflt'] = fields[7] 

1703 ret['majflt'] = fields[9] 

1704 ret['utime'] = fields[11] 

1705 ret['stime'] = fields[12] 

1706 ret['children_utime'] = fields[13] 

1707 ret['children_stime'] = fields[14] 

1708 ret['create_time'] = fields[19] 

1709 ret['cpu_num'] = fields[36] 

1710 try: 

1711 ret['blkio_ticks'] = fields[39] # aka 'delayacct_blkio_ticks' 

1712 except IndexError: 

1713 # https://github.com/giampaolo/psutil/issues/2455 

1714 debug("can't get blkio_ticks, set iowait to 0") 

1715 ret['blkio_ticks'] = 0 

1716 

1717 return ret 

1718 

1719 @wrap_exceptions 

1720 @memoize_when_activated 

1721 def _read_status_file(self): 

1722 """Read /proc/{pid}/stat file and return its content. 

1723 The return value is cached in case oneshot() ctx manager is 

1724 in use. 

1725 """ 

1726 with open_binary(f"{self._procfs_path}/{self.pid}/status") as f: 

1727 return f.read() 

1728 

1729 @wrap_exceptions 

1730 @memoize_when_activated 

1731 def _read_smaps_file(self): 

1732 with open_binary(f"{self._procfs_path}/{self.pid}/smaps") as f: 

1733 return f.read().strip() 

1734 

1735 def oneshot_enter(self): 

1736 self._parse_stat_file.cache_activate(self) 

1737 self._read_status_file.cache_activate(self) 

1738 self._read_smaps_file.cache_activate(self) 

1739 

1740 def oneshot_exit(self): 

1741 self._parse_stat_file.cache_deactivate(self) 

1742 self._read_status_file.cache_deactivate(self) 

1743 self._read_smaps_file.cache_deactivate(self) 

1744 

1745 @wrap_exceptions 

1746 def name(self): 

1747 # XXX - gets changed later and probably needs refactoring 

1748 return decode(self._parse_stat_file()['name']) 

1749 

1750 @wrap_exceptions 

1751 def exe(self): 

1752 return self._readlink( 

1753 f"{self._procfs_path}/{self.pid}/exe", fallback="" 

1754 ) 

1755 

1756 @wrap_exceptions 

1757 def cmdline(self): 

1758 with open_text(f"{self._procfs_path}/{self.pid}/cmdline") as f: 

1759 data = f.read() 

1760 if not data: 

1761 # may happen in case of zombie process 

1762 self._raise_if_zombie() 

1763 return [] 

1764 # 'man proc' states that args are separated by null bytes '\0' 

1765 # and last char is supposed to be a null byte. Nevertheless 

1766 # some processes may change their cmdline after being started 

1767 # (via setproctitle() or similar), they are usually not 

1768 # compliant with this rule and use spaces instead. Google 

1769 # Chrome process is an example. See: 

1770 # https://github.com/giampaolo/psutil/issues/1179 

1771 sep = '\x00' if data.endswith('\x00') else ' ' 

1772 if data.endswith(sep): 

1773 data = data[:-1] 

1774 cmdline = data.split(sep) 

1775 # Sometimes last char is a null byte '\0' but the args are 

1776 # separated by spaces, see: https://github.com/giampaolo/psutil/ 

1777 # issues/1179#issuecomment-552984549 

1778 if sep == '\x00' and len(cmdline) == 1 and ' ' in data: 

1779 cmdline = data.split(' ') 

1780 return cmdline 

1781 

1782 @wrap_exceptions 

1783 def environ(self): 

1784 with open_text(f"{self._procfs_path}/{self.pid}/environ") as f: 

1785 data = f.read() 

1786 return parse_environ_block(data) 

1787 

1788 @wrap_exceptions 

1789 def terminal(self): 

1790 tty_nr = int(self._parse_stat_file()['ttynr']) 

1791 if tty_nr == 0: 

1792 return None 

1793 return _psposix.get_terminal(tty_nr) 

1794 

1795 # May not be available on old kernels. 

1796 if os.path.exists(f"/proc/{os.getpid()}/io"): 

1797 

1798 @wrap_exceptions 

1799 def io_counters(self): 

1800 fname = f"{self._procfs_path}/{self.pid}/io" 

1801 fields = {} 

1802 with open_binary(fname) as f: 

1803 for line in f: 

1804 # https://github.com/giampaolo/psutil/issues/1004 

1805 line = line.strip() 

1806 if line: 

1807 try: 

1808 name, value = line.split(b': ') 

1809 except ValueError: 

1810 # https://github.com/giampaolo/psutil/issues/1004 

1811 continue 

1812 else: 

1813 fields[name] = int(value) 

1814 if not fields: 

1815 msg = f"{fname} file was empty" 

1816 raise RuntimeError(msg) 

1817 try: 

1818 return ntp.pio( 

1819 fields[b'syscr'], # read syscalls 

1820 fields[b'syscw'], # write syscalls 

1821 fields[b'read_bytes'], # read bytes 

1822 fields[b'write_bytes'], # write bytes 

1823 fields[b'rchar'], # read chars 

1824 fields[b'wchar'], # write chars 

1825 ) 

1826 except KeyError as err: 

1827 msg = ( 

1828 f"{err.args[0]!r} field was not found in {fname}; found" 

1829 f" fields are {fields!r}" 

1830 ) 

1831 raise ValueError(msg) from None 

1832 

1833 @wrap_exceptions 

1834 def cpu_times(self): 

1835 values = self._parse_stat_file() 

1836 utime = float(values['utime']) / CLOCK_TICKS 

1837 stime = float(values['stime']) / CLOCK_TICKS 

1838 children_utime = float(values['children_utime']) / CLOCK_TICKS 

1839 children_stime = float(values['children_stime']) / CLOCK_TICKS 

1840 iowait = float(values['blkio_ticks']) / CLOCK_TICKS 

1841 return ntp.pcputimes( 

1842 utime, stime, children_utime, children_stime, iowait 

1843 ) 

1844 

1845 @wrap_exceptions 

1846 def cpu_num(self): 

1847 """What CPU the process is on.""" 

1848 return int(self._parse_stat_file()['cpu_num']) 

1849 

1850 @wrap_exceptions 

1851 def wait(self, timeout=None): 

1852 return _psposix.wait_pid(self.pid, timeout) 

1853 

1854 @wrap_exceptions 

1855 def create_time(self, monotonic=False): 

1856 # The 'starttime' field in /proc/[pid]/stat is expressed in 

1857 # jiffies (clock ticks per second), a relative value which 

1858 # represents the number of clock ticks that have passed since 

1859 # the system booted until the process was created. It never 

1860 # changes and is unaffected by system clock updates. 

1861 if self._ctime is None: 

1862 self._ctime = ( 

1863 float(self._parse_stat_file()['create_time']) / CLOCK_TICKS 

1864 ) 

1865 if monotonic: 

1866 return self._ctime 

1867 # Add the boot time, returning time expressed in seconds since 

1868 # the epoch. This is subject to system clock updates. 

1869 return self._ctime + boot_time() 

1870 

1871 @wrap_exceptions 

1872 def memory_info(self): 

1873 # ============================================================ 

1874 # | FIELD | DESCRIPTION | AKA | TOP | 

1875 # ============================================================ 

1876 # | rss | resident set size | | RES | 

1877 # | vms | total program size | size | VIRT | 

1878 # | shared | shared pages (from shared mappings) | | SHR | 

1879 # | text | text ('code') | trs | CODE | 

1880 # | lib | library (unused in Linux 2.6) | lrs | | 

1881 # | data | data + stack | drs | DATA | 

1882 # | dirty | dirty pages (unused in Linux 2.6) | dt | | 

1883 # ============================================================ 

1884 with open_binary(f"{self._procfs_path}/{self.pid}/statm") as f: 

1885 vms, rss, shared, text, _lib, data, _dirty = ( 

1886 int(x) * PAGESIZE for x in f.readline().split()[:7] 

1887 ) 

1888 return ntp.pmem(rss, vms, shared, text, data) 

1889 

1890 @wrap_exceptions 

1891 def memory_info_ex( 

1892 self, 

1893 _vmpeak_re=re.compile(br"VmPeak:\s+(\d+)"), 

1894 _vmhwm_re=re.compile(br"VmHWM:\s+(\d+)"), 

1895 _rssanon_re=re.compile(br"RssAnon:\s+(\d+)"), 

1896 _rssfile_re=re.compile(br"RssFile:\s+(\d+)"), 

1897 _rssshmem_re=re.compile(br"RssShmem:\s+(\d+)"), 

1898 _vmswap_re=re.compile(br"VmSwap:\s+(\d+)"), 

1899 _hugetlb_re=re.compile(br"HugetlbPages:\s+(\d+)"), 

1900 ): 

1901 # Read /proc/{pid}/status which provides peak RSS/VMS and a 

1902 # cheaper way to get swap (no smaps parsing needed). 

1903 # RssAnon/RssFile/RssShmem were added in Linux 4.5; 

1904 # VmSwap in 2.6.34; HugetlbPages in 4.4. 

1905 data = self._read_status_file() 

1906 

1907 def parse(regex): 

1908 m = regex.search(data) 

1909 return int(m.group(1)) * 1024 if m else 0 

1910 

1911 return { 

1912 "peak_rss": parse(_vmhwm_re), 

1913 "peak_vms": parse(_vmpeak_re), 

1914 "rss_anon": parse(_rssanon_re), 

1915 "rss_file": parse(_rssfile_re), 

1916 "rss_shmem": parse(_rssshmem_re), 

1917 "swap": parse(_vmswap_re), 

1918 "hugetlb": parse(_hugetlb_re), 

1919 } 

1920 

1921 if HAS_PROC_SMAPS_ROLLUP or HAS_PROC_SMAPS: 

1922 

1923 def _parse_smaps_rollup(self): 

1924 # /proc/pid/smaps_rollup was added to Linux in 2017. Faster 

1925 # than /proc/pid/smaps. It reports higher PSS than */smaps 

1926 # (from 1k up to 200k higher; tested against all processes). 

1927 # IMPORTANT: /proc/pid/smaps_rollup is weird, because it 

1928 # raises ESRCH / ENOENT for many PIDs, even if they're alive 

1929 # (also as root). In that case we'll use /proc/pid/smaps as 

1930 # fallback, which is slower but has a +50% success rate 

1931 # compared to /proc/pid/smaps_rollup. 

1932 uss = pss = swap = 0 

1933 with open_binary( 

1934 f"{self._procfs_path}/{self.pid}/smaps_rollup" 

1935 ) as f: 

1936 for line in f: 

1937 if line.startswith(b"Private_"): 

1938 # Private_Clean, Private_Dirty, Private_Hugetlb 

1939 uss += int(line.split()[1]) * 1024 

1940 elif line.startswith(b"Pss:"): 

1941 pss = int(line.split()[1]) * 1024 

1942 elif line.startswith(b"Swap:"): 

1943 swap = int(line.split()[1]) * 1024 

1944 return (uss, pss, swap) 

1945 

1946 @wrap_exceptions 

1947 def _parse_smaps( 

1948 self, 

1949 # Gets Private_Clean, Private_Dirty, Private_Hugetlb. 

1950 _private_re=re.compile(br"\nPrivate.*:\s+(\d+)"), 

1951 _pss_re=re.compile(br"\nPss\:\s+(\d+)"), 

1952 _swap_re=re.compile(br"\nSwap\:\s+(\d+)"), 

1953 ): 

1954 # /proc/pid/smaps does not exist on kernels < 2.6.14 or if 

1955 # CONFIG_MMU kernel configuration option is not enabled. 

1956 

1957 # Note: using 3 regexes is faster than reading the file 

1958 # line by line. 

1959 # 

1960 # You might be tempted to calculate USS by subtracting 

1961 # the "shared" value from the "resident" value in 

1962 # /proc/<pid>/statm. But at least on Linux, statm's "shared" 

1963 # value actually counts pages backed by files, which has 

1964 # little to do with whether the pages are actually shared. 

1965 # /proc/self/smaps on the other hand appears to give us the 

1966 # correct information. 

1967 smaps_data = self._read_smaps_file() 

1968 # Note: smaps file can be empty for certain processes. 

1969 # The code below will not crash though and will result to 0. 

1970 uss = sum(map(int, _private_re.findall(smaps_data))) * 1024 

1971 pss = sum(map(int, _pss_re.findall(smaps_data))) * 1024 

1972 swap = sum(map(int, _swap_re.findall(smaps_data))) * 1024 

1973 return (uss, pss, swap) 

1974 

1975 @wrap_exceptions 

1976 def memory_footprint(self): 

1977 def fetch(): 

1978 if HAS_PROC_SMAPS_ROLLUP: # faster 

1979 try: 

1980 return self._parse_smaps_rollup() 

1981 except (ProcessLookupError, FileNotFoundError): 

1982 pass 

1983 return self._parse_smaps() 

1984 

1985 uss, pss, swap = fetch() 

1986 return ntp.pfootprint(uss, pss, swap) 

1987 

1988 if HAS_PROC_SMAPS: 

1989 

1990 @wrap_exceptions 

1991 def memory_maps(self): 

1992 """Return process's mapped memory regions as a list of named 

1993 tuples. Fields are explained in 'man proc'; here is an updated 

1994 (Apr 2012) version: https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/Documentation/filesystems/proc.txt?id=b76437579d1344b612cf1851ae610c636cec7db0. 

1995 

1996 /proc/{PID}/smaps does not exist on kernels < 2.6.14 or if 

1997 CONFIG_MMU kernel configuration option is not enabled. 

1998 """ 

1999 

2000 def get_blocks(lines, current_block): 

2001 data = {} 

2002 for line in lines: 

2003 fields = line.split(None, 5) 

2004 if not fields[0].endswith(b':'): 

2005 # new block section 

2006 yield (current_block.pop(), data) 

2007 current_block.append(line) 

2008 else: 

2009 try: 

2010 data[fields[0]] = int(fields[1]) * 1024 

2011 except (ValueError, IndexError): 

2012 if fields[0].startswith(b'VmFlags:'): 

2013 # see issue #369 

2014 continue 

2015 msg = f"don't know how to interpret line {line!r}" 

2016 raise ValueError(msg) from None 

2017 yield (current_block.pop(), data) 

2018 

2019 data = self._read_smaps_file() 

2020 # Note: smaps file can be empty for certain processes or for 

2021 # zombies. 

2022 if not data: 

2023 self._raise_if_zombie() 

2024 return [] 

2025 lines = data.split(b'\n') 

2026 ls = [] 

2027 first_line = lines.pop(0) 

2028 current_block = [first_line] 

2029 for header, data in get_blocks(lines, current_block): 

2030 hfields = header.split(None, 5) 

2031 try: 

2032 addr, perms, _offset, _dev, _inode, path = hfields 

2033 except ValueError: 

2034 addr, perms, _offset, _dev, _inode, path = hfields + [''] 

2035 if not path: 

2036 path = '[anon]' 

2037 else: 

2038 path = decode(path) 

2039 path = path.strip() 

2040 if path.endswith(' (deleted)') and not path_exists_strict( 

2041 path 

2042 ): 

2043 path = path[:-10] 

2044 item = ( 

2045 decode(addr), 

2046 decode(perms), 

2047 path, 

2048 data.get(b'Rss:', 0), 

2049 data.get(b'Size:', 0), 

2050 data.get(b'Pss:', 0), 

2051 data.get(b'Shared_Clean:', 0), 

2052 data.get(b'Shared_Dirty:', 0), 

2053 data.get(b'Private_Clean:', 0), 

2054 data.get(b'Private_Dirty:', 0), 

2055 data.get(b'Referenced:', 0), 

2056 data.get(b'Anonymous:', 0), 

2057 data.get(b'Swap:', 0), 

2058 ) 

2059 ls.append(item) 

2060 return ls 

2061 

2062 @wrap_exceptions 

2063 def page_faults(self): 

2064 values = self._parse_stat_file() 

2065 return ntp.ppagefaults(int(values['minflt']), int(values['majflt'])) 

2066 

2067 @wrap_exceptions 

2068 def cwd(self): 

2069 return self._readlink( 

2070 f"{self._procfs_path}/{self.pid}/cwd", fallback="" 

2071 ) 

2072 

2073 @wrap_exceptions 

2074 def num_ctx_switches( 

2075 self, _ctxsw_re=re.compile(br'ctxt_switches:\t(\d+)') 

2076 ): 

2077 data = self._read_status_file() 

2078 ctxsw = _ctxsw_re.findall(data) 

2079 if not ctxsw: 

2080 msg = ( 

2081 "'voluntary_ctxt_switches' and" 

2082 " 'nonvoluntary_ctxt_switches'lines were not found in" 

2083 f" {self._procfs_path}/{self.pid}/status; the kernel is" 

2084 " probably older than 2.6.23" 

2085 ) 

2086 raise NotImplementedError(msg) 

2087 return ntp.pctxsw(int(ctxsw[0]), int(ctxsw[1])) 

2088 

2089 @wrap_exceptions 

2090 def num_threads(self, _num_threads_re=re.compile(br'Threads:\t(\d+)')): 

2091 # Using a re is faster than iterating over file line by line. 

2092 data = self._read_status_file() 

2093 return int(_num_threads_re.findall(data)[0]) 

2094 

2095 @wrap_exceptions 

2096 def threads(self): 

2097 thread_ids = os.listdir(f"{self._procfs_path}/{self.pid}/task") 

2098 thread_ids.sort() 

2099 retlist = [] 

2100 hit_enoent = False 

2101 for thread_id in thread_ids: 

2102 fname = f"{self._procfs_path}/{self.pid}/task/{thread_id}/stat" 

2103 try: 

2104 with open_binary(fname) as f: 

2105 st = f.read().strip() 

2106 except (FileNotFoundError, ProcessLookupError): 

2107 # no such file or directory or no such process; 

2108 # it means thread disappeared on us 

2109 hit_enoent = True 

2110 continue 

2111 # ignore the first two values ("pid (exe)") 

2112 st = st[st.find(b')') + 2 :] 

2113 values = st.split(b' ') 

2114 utime = float(values[11]) / CLOCK_TICKS 

2115 stime = float(values[12]) / CLOCK_TICKS 

2116 ntuple = ntp.pthread(int(thread_id), utime, stime) 

2117 retlist.append(ntuple) 

2118 if hit_enoent: 

2119 self._raise_if_not_alive() 

2120 return retlist 

2121 

2122 @wrap_exceptions 

2123 def nice_get(self): 

2124 # with open_text(f"{self._procfs_path}/{self.pid}/stat") as f: 

2125 # data = f.read() 

2126 # return int(data.split()[18]) 

2127 

2128 # Use C implementation 

2129 return _psutil.proc_priority_get(self.pid) 

2130 

2131 @wrap_exceptions 

2132 def nice_set(self, value): 

2133 return _psutil.proc_priority_set(self.pid, value) 

2134 

2135 # starting from CentOS 6. 

2136 if HAS_CPU_AFFINITY: 

2137 

2138 @wrap_exceptions 

2139 def cpu_affinity_get(self): 

2140 return _psutil.proc_cpu_affinity_get(self.pid) 

2141 

2142 def _get_eligible_cpus( 

2143 self, 

2144 _re=re.compile( 

2145 br"^Cpus_allowed_list:[ \t]*([^\r\n]*)", re.MULTILINE 

2146 ), 

2147 ): 

2148 # See: https://github.com/giampaolo/psutil/issues/956 

2149 data = self._read_status_file() 

2150 if match := _re.search(data): 

2151 try: 

2152 return _parse_cpulist(decode(match.group(1))) 

2153 except ValueError as err: 

2154 debug( 

2155 f"can't parse Cpus_allowed_list ({err}); falling back" 

2156 ) 

2157 return list(range(len(per_cpu_times()))) 

2158 

2159 @wrap_exceptions 

2160 def cpu_affinity_set(self, cpus): 

2161 try: 

2162 _psutil.proc_cpu_affinity_set(self.pid, cpus) 

2163 except (OSError, ValueError) as err: 

2164 if isinstance(err, ValueError) or err.errno == errno.EINVAL: 

2165 eligible_cpus = self._get_eligible_cpus() 

2166 all_cpus = tuple(range(len(per_cpu_times()))) 

2167 for cpu in cpus: 

2168 if cpu not in all_cpus: 

2169 msg = ( 

2170 f"invalid CPU {cpu!r}; choose between" 

2171 f" {eligible_cpus!r}" 

2172 ) 

2173 raise ValueError(msg) from None 

2174 if cpu not in eligible_cpus: 

2175 msg = ( 

2176 f"CPU number {cpu} is not eligible; choose" 

2177 f" between {eligible_cpus}" 

2178 ) 

2179 raise ValueError(msg) from err 

2180 raise 

2181 

2182 # only starting from kernel 2.6.13 

2183 if HAS_PROC_IO_PRIORITY: 

2184 

2185 @wrap_exceptions 

2186 def ionice_get(self): 

2187 ioclass, value = _psutil.proc_ioprio_get(self.pid) 

2188 ioclass = ProcessIOPriority(ioclass) 

2189 return ntp.pionice(ioclass, value) 

2190 

2191 @wrap_exceptions 

2192 def ionice_set(self, ioclass, value): 

2193 if value is None: 

2194 value = 0 

2195 if value and ioclass in { 

2196 ProcessIOPriority.IOPRIO_CLASS_IDLE, 

2197 ProcessIOPriority.IOPRIO_CLASS_NONE, 

2198 }: 

2199 msg = f"{ioclass!r} ioclass accepts no value" 

2200 raise ValueError(msg) 

2201 if value < 0 or value > 7: 

2202 msg = "value not in 0-7 range" 

2203 raise ValueError(msg) 

2204 return _psutil.proc_ioprio_set(self.pid, ioclass, value) 

2205 

2206 if hasattr(resource, "prlimit"): 

2207 

2208 @wrap_exceptions 

2209 def rlimit(self, resource_, limits=None): 

2210 # If pid is 0 prlimit() applies to the calling process and 

2211 # we don't want that. We should never get here though as 

2212 # PID 0 is not supported on Linux. 

2213 if self.pid == 0: 

2214 msg = "can't use prlimit() against PID 0 process" 

2215 raise ValueError(msg) 

2216 try: 

2217 if limits is None: 

2218 # get 

2219 soft, hard = resource.prlimit(self.pid, resource_) 

2220 # Python 3.15 returns RLIM_INFINITY as the unsigned 

2221 # 2**64-1 instead of -1; map it back for consistency. 

2222 if soft == RLIM_INFINITY_UNSIGNED: 

2223 soft = _psutil.RLIM_INFINITY 

2224 if hard == RLIM_INFINITY_UNSIGNED: 

2225 hard = _psutil.RLIM_INFINITY 

2226 return soft, hard 

2227 else: 

2228 # set 

2229 if len(limits) != 2: 

2230 msg = ( 

2231 "second argument must be a (soft, hard) " 

2232 f"tuple, got {limits!r}" 

2233 ) 

2234 raise ValueError(msg) 

2235 resource.prlimit(self.pid, resource_, limits) 

2236 except OSError as err: 

2237 if err.errno == errno.ENOSYS: 

2238 # I saw this happening on Travis: 

2239 # https://travis-ci.org/giampaolo/psutil/jobs/51368273 

2240 self._raise_if_zombie() 

2241 raise 

2242 

2243 @wrap_exceptions 

2244 def status(self): 

2245 letter = self._parse_stat_file()['status'] 

2246 letter = letter.decode() 

2247 # XXX is '?' legit? (we're not supposed to return it anyway) 

2248 return PROC_STATUSES.get(letter, '?') 

2249 

2250 @wrap_exceptions 

2251 def open_files(self): 

2252 retlist = [] 

2253 files = os.listdir(f"{self._procfs_path}/{self.pid}/fd") 

2254 hit_enoent = False 

2255 for fd in files: 

2256 file = f"{self._procfs_path}/{self.pid}/fd/{fd}" 

2257 try: 

2258 path = readlink(file) 

2259 except (FileNotFoundError, ProcessLookupError): 

2260 # ENOENT == file which is gone in the meantime 

2261 hit_enoent = True 

2262 continue 

2263 except OSError as err: 

2264 if err.errno == errno.EINVAL: 

2265 # not a link 

2266 continue 

2267 if err.errno == errno.ENAMETOOLONG: 

2268 # file name too long 

2269 debug(err) 

2270 continue 

2271 raise 

2272 else: 

2273 # If path is not an absolute there's no way to tell 

2274 # whether it's a regular file or not, so we skip it. 

2275 # A regular file is always supposed to be have an 

2276 # absolute path though. 

2277 if path.startswith('/') and isfile_strict(path): 

2278 # Get file position and flags. 

2279 file = f"{self._procfs_path}/{self.pid}/fdinfo/{fd}" 

2280 try: 

2281 with open_binary(file) as f: 

2282 pos = int(f.readline().split()[1]) 

2283 flags = int(f.readline().split()[1], 8) 

2284 except (FileNotFoundError, ProcessLookupError): 

2285 # fd gone in the meantime; process may 

2286 # still be alive 

2287 hit_enoent = True 

2288 else: 

2289 mode = file_flags_to_mode(flags) 

2290 ntuple = ntp.popenfile( 

2291 path, int(fd), int(pos), mode, flags 

2292 ) 

2293 retlist.append(ntuple) 

2294 if hit_enoent: 

2295 self._raise_if_not_alive() 

2296 return retlist 

2297 

2298 @wrap_exceptions 

2299 def net_connections(self, kind='inet'): 

2300 ret = _net_connections.retrieve(kind, self.pid) 

2301 self._raise_if_not_alive() 

2302 return ret 

2303 

2304 @wrap_exceptions 

2305 def num_fds(self): 

2306 return len(os.listdir(f"{self._procfs_path}/{self.pid}/fd")) 

2307 

2308 @wrap_exceptions 

2309 def ppid(self): 

2310 return int(self._parse_stat_file()['ppid']) 

2311 

2312 @wrap_exceptions 

2313 def uids(self, _uids_re=re.compile(br'Uid:\t(\d+)\t(\d+)\t(\d+)')): 

2314 data = self._read_status_file() 

2315 real, effective, saved = _uids_re.findall(data)[0] 

2316 return ntp.puids(int(real), int(effective), int(saved)) 

2317 

2318 @wrap_exceptions 

2319 def gids(self, _gids_re=re.compile(br'Gid:\t(\d+)\t(\d+)\t(\d+)')): 

2320 data = self._read_status_file() 

2321 real, effective, saved = _gids_re.findall(data)[0] 

2322 return ntp.pgids(int(real), int(effective), int(saved))