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

1275 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 

1222 def fstypes_from_filesystems(): 

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

1224 for line in f: 

1225 line = line.strip() 

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

1227 fstypes.add(line.strip()) 

1228 else: 

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

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

1231 if fstype == "zfs": 

1232 fstypes.add("zfs") 

1233 return fstypes 

1234 

1235 def fstypes_from_mountinfo(): 

1236 with open_text(f"{procfs_path}/self/mountinfo") as f: 

1237 for line in f: 

1238 line = line.strip().split() 

1239 if len(line) > 1: 

1240 for i in range(len(line) - 1): 

1241 if line[i] == "-": 

1242 fstypes.add(line[i + 1]) 

1243 return fstypes 

1244 

1245 if not all: 

1246 try: 

1247 fstypes = fstypes_from_filesystems() 

1248 except PermissionError: 

1249 fstypes = fstypes_from_mountinfo() 

1250 

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

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

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

1254 else: 

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

1256 

1257 retlist = [] 

1258 partitions = _psutil.disk_partitions(mounts_path) 

1259 for partition in partitions: 

1260 device, mountpoint, fstype, opts = partition 

1261 if device == 'none': 

1262 device = '' 

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

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

1265 if not all: 

1266 if not device or fstype not in fstypes: 

1267 continue 

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

1269 retlist.append(ntuple) 

1270 

1271 return retlist 

1272 

1273 

1274# ===================================================================== 

1275# --- sensors 

1276# ===================================================================== 

1277 

1278 

1279def sensors_temperatures(): 

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

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

1282 temperatures. 

1283 

1284 Implementation notes: 

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

1286 retrieve this info, and this implementation relies on it 

1287 only (old distros will probably use something else) 

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

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

1290 difficult to parse 

1291 """ 

1292 ret = collections.defaultdict(list) 

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

1294 # CentOS has an intermediate /device directory: 

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

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

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

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

1299 

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

1301 # /sys/class/hwmon/ 

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

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

1304 basenames2 = glob.glob( 

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

1306 ) 

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

1308 for name in basenames2: 

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

1310 if altname not in basenames: 

1311 basenames.append(name) 

1312 

1313 for base in basenames: 

1314 try: 

1315 path = base + '_input' 

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

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

1318 unit_name = cat(path).strip() 

1319 except (OSError, ValueError): 

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

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

1322 # is a stinky broken mess. 

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

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

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

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

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

1328 continue 

1329 

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

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

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

1333 

1334 if high is not None: 

1335 try: 

1336 high = float(high) / 1000.0 

1337 except ValueError: 

1338 high = None 

1339 if critical is not None: 

1340 try: 

1341 critical = float(critical) / 1000.0 

1342 except ValueError: 

1343 critical = None 

1344 

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

1346 

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

1348 if not basenames: 

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

1350 basenames = sorted(set(basenames)) 

1351 

1352 for base in basenames: 

1353 try: 

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

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

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

1357 unit_name = cat(path).strip() 

1358 except (OSError, ValueError) as err: 

1359 debug(err) 

1360 continue 

1361 

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

1363 trip_points = { 

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

1365 for p in trip_paths 

1366 } 

1367 critical = None 

1368 high = None 

1369 for trip_point in trip_points: 

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

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

1372 if trip_type == 'critical': 

1373 critical = bcat( 

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

1375 ) 

1376 elif trip_type == 'high': 

1377 high = bcat( 

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

1379 ) 

1380 

1381 if high is not None: 

1382 try: 

1383 high = float(high) / 1000.0 

1384 except ValueError: 

1385 high = None 

1386 if critical is not None: 

1387 try: 

1388 critical = float(critical) / 1000.0 

1389 except ValueError: 

1390 critical = None 

1391 

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

1393 

1394 return dict(ret) 

1395 

1396 

1397def sensors_fans(): 

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

1399 dict including hardware label and current speed. 

1400 

1401 Implementation notes: 

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

1403 retrieve this info, and this implementation relies on it 

1404 only (old distros will probably use something else) 

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

1406 """ 

1407 ret = collections.defaultdict(list) 

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

1409 if not basenames: 

1410 # CentOS has an intermediate /device directory: 

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

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

1413 

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

1415 for base in basenames: 

1416 try: 

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

1418 except OSError as err: 

1419 debug(err) 

1420 continue 

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

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

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

1424 

1425 return dict(ret) 

1426 

1427 

1428def sensors_battery(): 

1429 """Return battery information. 

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

1431 directory structure may vary and provide files with the same 

1432 meaning but under different names, see: 

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

1434 """ 

1435 null = object() 

1436 

1437 def multi_bcat(*paths): 

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

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

1440 """ 

1441 for path in paths: 

1442 ret = bcat(path, fallback=null) 

1443 if ret != null: 

1444 try: 

1445 return int(ret) 

1446 except ValueError: 

1447 return ret.strip() 

1448 return None 

1449 

1450 bats = [ 

1451 x 

1452 for x in os.listdir(POWER_SUPPLY_PATH) 

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

1454 ] 

1455 if not bats: 

1456 return None 

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

1458 # some rare exceptions: 

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

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

1461 

1462 # Base metrics. 

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

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

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

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

1467 

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

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

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

1471 try: 

1472 percent = 100.0 * energy_now / energy_full 

1473 except ZeroDivisionError: 

1474 percent = 0.0 

1475 else: 

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

1477 if percent == -1: 

1478 return None 

1479 

1480 # Is AC power cable plugged in? 

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

1482 # it's called "AC". 

1483 power_plugged = None 

1484 online = multi_bcat( 

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

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

1487 ) 

1488 if online is not None: 

1489 power_plugged = online == 1 

1490 else: 

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

1492 if status == "discharging": 

1493 power_plugged = False 

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

1495 power_plugged = True 

1496 

1497 # Seconds left. 

1498 if power_plugged: 

1499 secsleft = BatteryTime.POWER_TIME_UNLIMITED 

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

1501 try: 

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

1503 except ZeroDivisionError: 

1504 secsleft = BatteryTime.POWER_TIME_UNKNOWN 

1505 elif time_to_empty is not None: 

1506 secsleft = int(time_to_empty * 60) 

1507 if secsleft < 0: 

1508 secsleft = BatteryTime.POWER_TIME_UNKNOWN 

1509 else: 

1510 secsleft = BatteryTime.POWER_TIME_UNKNOWN 

1511 

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

1513 

1514 

1515# ===================================================================== 

1516# --- other system functions 

1517# ===================================================================== 

1518 

1519 

1520def users(): 

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

1522 retlist = [] 

1523 rawlist = _psutil.users() 

1524 for item in rawlist: 

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

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

1527 retlist.append(nt) 

1528 return retlist 

1529 

1530 

1531def boot_time(): 

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

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

1534 with open_binary(path) as f: 

1535 for line in f: 

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

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

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

1539 raise RuntimeError(msg) 

1540 

1541 

1542# ===================================================================== 

1543# --- processes 

1544# ===================================================================== 

1545 

1546 

1547def pids(): 

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

1549 path = get_procfs_path().encode(ENCODING) 

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

1551 

1552 

1553def pid_exists(pid): 

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

1555 supported (always return False). 

1556 """ 

1557 if not _psposix.pid_exists(pid): 

1558 return False 

1559 else: 

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

1561 # (thread IDs). 

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

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

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

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

1566 # only, see: 

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

1568 try: 

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

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

1571 # 'return pid in pids()' 

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

1573 with open_binary(path) as f: 

1574 for line in f: 

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

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

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

1578 # dealing with a process PID. 

1579 return tgid == pid 

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

1581 raise ValueError(msg) 

1582 except (OSError, ValueError): 

1583 return pid in pids() 

1584 

1585 

1586def ppid_map(): 

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

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

1589 """ 

1590 ret = {} 

1591 procfs_path = get_procfs_path() 

1592 for pid in pids(): 

1593 try: 

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

1595 data = f.read() 

1596 except (FileNotFoundError, ProcessLookupError): 

1597 pass 

1598 except PermissionError as err: 

1599 raise AccessDenied(pid) from err 

1600 else: 

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

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

1603 ppid = int(dset[1]) 

1604 ret[pid] = ppid 

1605 return ret 

1606 

1607 

1608def wrap_exceptions(fun): 

1609 """Decorator which translates bare OSError exceptions into 

1610 NoSuchProcess and AccessDenied. 

1611 """ 

1612 

1613 @functools.wraps(fun) 

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

1615 pid, name = self.pid, self._name 

1616 try: 

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

1618 except PermissionError as err: 

1619 raise AccessDenied(pid, name) from err 

1620 except ProcessLookupError as err: 

1621 self._raise_if_zombie() 

1622 raise NoSuchProcess(pid, name) from err 

1623 except FileNotFoundError as err: 

1624 self._raise_if_zombie() 

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

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

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

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

1629 raise NoSuchProcess(pid, name) from err 

1630 raise 

1631 

1632 return wrapper 

1633 

1634 

1635class Process: 

1636 """Linux process implementation.""" 

1637 

1638 __slots__ = [ 

1639 "_cache", 

1640 "_ctime", 

1641 "_name", 

1642 "_ppid", 

1643 "_procfs_path", 

1644 "pid", 

1645 ] 

1646 

1647 def __init__(self, pid): 

1648 self.pid = pid 

1649 self._name = None 

1650 self._ppid = None 

1651 self._ctime = None 

1652 self._procfs_path = get_procfs_path() 

1653 

1654 def _is_zombie(self): 

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

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

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

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

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

1660 # exception. 

1661 try: 

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

1663 except OSError: 

1664 return False 

1665 else: 

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

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

1668 return status == b"Z" 

1669 

1670 def _raise_if_zombie(self): 

1671 if self._is_zombie(): 

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

1673 

1674 def _raise_if_not_alive(self): 

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

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

1677 # incorrect or incomplete result. 

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

1679 

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

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

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

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

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

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

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

1687 try: 

1688 return readlink(path) 

1689 except (FileNotFoundError, ProcessLookupError): 

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

1691 self._raise_if_zombie() 

1692 if fallback is not UNSET: 

1693 return fallback 

1694 raise 

1695 

1696 @wrap_exceptions 

1697 @memoize_when_activated 

1698 def _parse_stat_file(self): 

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

1700 process info. 

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

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

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

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

1705 in use. 

1706 """ 

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

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

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

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

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

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

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

1714 

1715 ret = {} 

1716 ret['name'] = name 

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

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

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

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

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

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

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

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

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

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

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

1728 try: 

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

1730 except IndexError: 

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

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

1733 ret['blkio_ticks'] = 0 

1734 

1735 return ret 

1736 

1737 @wrap_exceptions 

1738 @memoize_when_activated 

1739 def _read_status_file(self): 

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

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

1742 in use. 

1743 """ 

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

1745 return f.read() 

1746 

1747 @wrap_exceptions 

1748 @memoize_when_activated 

1749 def _read_smaps_file(self): 

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

1751 return f.read().strip() 

1752 

1753 def oneshot_enter(self): 

1754 self._parse_stat_file.cache_activate(self) 

1755 self._read_status_file.cache_activate(self) 

1756 self._read_smaps_file.cache_activate(self) 

1757 

1758 def oneshot_exit(self): 

1759 self._parse_stat_file.cache_deactivate(self) 

1760 self._read_status_file.cache_deactivate(self) 

1761 self._read_smaps_file.cache_deactivate(self) 

1762 

1763 @wrap_exceptions 

1764 def name(self): 

1765 # XXX - gets changed later and probably needs refactoring 

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

1767 

1768 @wrap_exceptions 

1769 def exe(self): 

1770 return self._readlink( 

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

1772 ) 

1773 

1774 @wrap_exceptions 

1775 def cmdline(self): 

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

1777 data = f.read() 

1778 if not data: 

1779 # may happen in case of zombie process 

1780 self._raise_if_zombie() 

1781 return [] 

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

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

1784 # some processes may change their cmdline after being started 

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

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

1787 # Chrome process is an example. See: 

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

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

1790 if data.endswith(sep): 

1791 data = data[:-1] 

1792 cmdline = data.split(sep) 

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

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

1795 # issues/1179#issuecomment-552984549 

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

1797 cmdline = data.split(' ') 

1798 return cmdline 

1799 

1800 @wrap_exceptions 

1801 def environ(self): 

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

1803 data = f.read() 

1804 return parse_environ_block(data) 

1805 

1806 @wrap_exceptions 

1807 def terminal(self): 

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

1809 if tty_nr == 0: 

1810 return None 

1811 return _psposix.get_terminal(tty_nr) 

1812 

1813 # May not be available on old kernels. 

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

1815 

1816 @wrap_exceptions 

1817 def io_counters(self): 

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

1819 fields = {} 

1820 with open_binary(fname) as f: 

1821 for line in f: 

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

1823 line = line.strip() 

1824 if line: 

1825 try: 

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

1827 except ValueError: 

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

1829 continue 

1830 else: 

1831 fields[name] = int(value) 

1832 if not fields: 

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

1834 raise RuntimeError(msg) 

1835 try: 

1836 return ntp.pio( 

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

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

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

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

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

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

1843 ) 

1844 except KeyError as err: 

1845 msg = ( 

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

1847 f" fields are {fields!r}" 

1848 ) 

1849 raise ValueError(msg) from None 

1850 

1851 @wrap_exceptions 

1852 def cpu_times(self): 

1853 values = self._parse_stat_file() 

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

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

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

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

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

1859 return ntp.pcputimes( 

1860 utime, stime, children_utime, children_stime, iowait 

1861 ) 

1862 

1863 @wrap_exceptions 

1864 def cpu_num(self): 

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

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

1867 

1868 @wrap_exceptions 

1869 def wait(self, timeout=None): 

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

1871 

1872 @wrap_exceptions 

1873 def create_time(self, monotonic=False): 

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

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

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

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

1878 # changes and is unaffected by system clock updates. 

1879 if self._ctime is None: 

1880 self._ctime = ( 

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

1882 ) 

1883 if monotonic: 

1884 return self._ctime 

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

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

1887 return self._ctime + boot_time() 

1888 

1889 @wrap_exceptions 

1890 def memory_info(self): 

1891 # ============================================================ 

1892 # | FIELD | DESCRIPTION | AKA | TOP | 

1893 # ============================================================ 

1894 # | rss | resident set size | | RES | 

1895 # | vms | total program size | size | VIRT | 

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

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

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

1899 # | data | data + stack | drs | DATA | 

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

1901 # ============================================================ 

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

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

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

1905 ) 

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

1907 

1908 @wrap_exceptions 

1909 def memory_extras( 

1910 self, 

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

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

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

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

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

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

1917 _vmlck_re=re.compile(br"VmLck:\s+(\d+)"), 

1918 ): 

1919 # Read /proc/{pid}/status. 

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

1921 # VmSwap in 2.6.34. 

1922 data = self._read_status_file() 

1923 

1924 def parse(regex): 

1925 m = regex.search(data) 

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

1927 

1928 return ntp.pmem_extras( 

1929 peak_rss=parse(_vmhwm_re), 

1930 peak_vms=parse(_vmpeak_re), 

1931 rss_anon=parse(_rssanon_re), 

1932 rss_file=parse(_rssfile_re), 

1933 rss_shmem=parse(_rssshmem_re), 

1934 swap_anon=parse(_vmswap_re), 

1935 locked=parse(_vmlck_re), 

1936 ) 

1937 

1938 if HAS_PROC_SMAPS_ROLLUP or HAS_PROC_SMAPS: 

1939 

1940 def _parse_smaps_rollup(self): 

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

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

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

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

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

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

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

1948 # compared to /proc/pid/smaps_rollup. 

1949 uss = pss = swap = shared = 0 

1950 with open_binary( 

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

1952 ) as f: 

1953 for line in f: 

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

1955 # Private_Clean, Private_Dirty, Private_Hugetlb 

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

1957 elif line.startswith(b"Shared_"): 

1958 # Shared_Clean, Shared_Dirty, Shared_Hugetlb 

1959 shared += int(line.split()[1]) * 1024 

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

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

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

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

1964 return (uss, pss, swap, shared) 

1965 

1966 @wrap_exceptions 

1967 def _parse_smaps( 

1968 self, 

1969 # Gets Private_Clean, Private_Dirty, Private_Hugetlb. 

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

1971 _shared_re=re.compile(br"\nShared.*:\s+(\d+)"), 

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

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

1974 ): 

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

1976 # CONFIG_MMU kernel configuration option is not enabled. 

1977 

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

1979 # line by line. 

1980 # 

1981 # You might be tempted to calculate USS by subtracting 

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

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

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

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

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

1987 # correct information. 

1988 smaps_data = self._read_smaps_file() 

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

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

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

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

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

1994 shared = sum(map(int, _shared_re.findall(smaps_data))) * 1024 

1995 return (uss, pss, swap, shared) 

1996 

1997 @wrap_exceptions 

1998 def memory_footprint(self): 

1999 def fetch(): 

2000 if HAS_PROC_SMAPS_ROLLUP: # faster 

2001 try: 

2002 return self._parse_smaps_rollup() 

2003 except (ProcessLookupError, FileNotFoundError): 

2004 pass 

2005 return self._parse_smaps() 

2006 

2007 uss, pss, swap, shared = fetch() 

2008 return ntp.pfootprint(uss, pss, swap, shared) 

2009 

2010 if HAS_PROC_SMAPS: 

2011 

2012 @wrap_exceptions 

2013 def memory_maps(self): 

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

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

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

2017 

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

2019 CONFIG_MMU kernel configuration option is not enabled. 

2020 """ 

2021 

2022 def get_blocks(lines, current_block): 

2023 data = {} 

2024 for line in lines: 

2025 fields = line.split(None, 5) 

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

2027 # new block section 

2028 yield (current_block.pop(), data) 

2029 current_block.append(line) 

2030 else: 

2031 try: 

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

2033 except (ValueError, IndexError): 

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

2035 # see issue #369 

2036 continue 

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

2038 raise ValueError(msg) from None 

2039 yield (current_block.pop(), data) 

2040 

2041 data = self._read_smaps_file() 

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

2043 # zombies. 

2044 if not data: 

2045 self._raise_if_zombie() 

2046 return [] 

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

2048 ls = [] 

2049 first_line = lines.pop(0) 

2050 current_block = [first_line] 

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

2052 hfields = header.split(None, 5) 

2053 try: 

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

2055 except ValueError: 

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

2057 if not path: 

2058 path = '[anon]' 

2059 else: 

2060 path = decode(path) 

2061 path = path.strip() 

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

2063 path 

2064 ): 

2065 path = path[:-10] 

2066 item = ( 

2067 decode(addr), 

2068 decode(perms), 

2069 path, 

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

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

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

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

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

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

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

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

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

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

2080 ) 

2081 ls.append(item) 

2082 return ls 

2083 

2084 @wrap_exceptions 

2085 def page_faults(self): 

2086 values = self._parse_stat_file() 

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

2088 

2089 @wrap_exceptions 

2090 def cwd(self): 

2091 return self._readlink( 

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

2093 ) 

2094 

2095 @wrap_exceptions 

2096 def num_ctx_switches( 

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

2098 ): 

2099 data = self._read_status_file() 

2100 ctxsw = _ctxsw_re.findall(data) 

2101 if not ctxsw: 

2102 msg = ( 

2103 "'voluntary_ctxt_switches' and" 

2104 " 'nonvoluntary_ctxt_switches'lines were not found in" 

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

2106 " probably older than 2.6.23" 

2107 ) 

2108 raise NotImplementedError(msg) 

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

2110 

2111 @wrap_exceptions 

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

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

2114 data = self._read_status_file() 

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

2116 

2117 @wrap_exceptions 

2118 def threads(self): 

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

2120 thread_ids.sort() 

2121 retlist = [] 

2122 hit_enoent = False 

2123 for thread_id in thread_ids: 

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

2125 try: 

2126 with open_binary(fname) as f: 

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

2128 except (FileNotFoundError, ProcessLookupError): 

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

2130 # it means thread disappeared on us 

2131 hit_enoent = True 

2132 continue 

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

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

2135 values = st.split(b' ') 

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

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

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

2139 retlist.append(ntuple) 

2140 if hit_enoent: 

2141 self._raise_if_not_alive() 

2142 return retlist 

2143 

2144 @wrap_exceptions 

2145 def nice_get(self): 

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

2147 # data = f.read() 

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

2149 

2150 # Use C implementation 

2151 return _psutil.proc_priority_get(self.pid) 

2152 

2153 @wrap_exceptions 

2154 def nice_set(self, value): 

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

2156 

2157 # starting from CentOS 6. 

2158 if HAS_CPU_AFFINITY: 

2159 

2160 @wrap_exceptions 

2161 def cpu_affinity_get(self): 

2162 return _psutil.proc_cpu_affinity_get(self.pid) 

2163 

2164 def _get_eligible_cpus( 

2165 self, 

2166 _re=re.compile( 

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

2168 ), 

2169 ): 

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

2171 data = self._read_status_file() 

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

2173 try: 

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

2175 except ValueError as err: 

2176 debug( 

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

2178 ) 

2179 return list(range(len(per_cpu_times()))) 

2180 

2181 @wrap_exceptions 

2182 def cpu_affinity_set(self, cpus): 

2183 try: 

2184 _psutil.proc_cpu_affinity_set(self.pid, cpus) 

2185 except (OSError, ValueError) as err: 

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

2187 eligible_cpus = self._get_eligible_cpus() 

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

2189 for cpu in cpus: 

2190 if cpu not in all_cpus: 

2191 msg = ( 

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

2193 f" {eligible_cpus!r}" 

2194 ) 

2195 raise ValueError(msg) from None 

2196 if cpu not in eligible_cpus: 

2197 msg = ( 

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

2199 f" between {eligible_cpus}" 

2200 ) 

2201 raise ValueError(msg) from err 

2202 raise 

2203 

2204 # only starting from kernel 2.6.13 

2205 if HAS_PROC_IO_PRIORITY: 

2206 

2207 @wrap_exceptions 

2208 def ionice_get(self): 

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

2210 ioclass = ProcessIOPriority(ioclass) 

2211 return ntp.pionice(ioclass, value) 

2212 

2213 @wrap_exceptions 

2214 def ionice_set(self, ioclass, value): 

2215 if value is None: 

2216 value = 0 

2217 if value and ioclass in { 

2218 ProcessIOPriority.IOPRIO_CLASS_IDLE, 

2219 ProcessIOPriority.IOPRIO_CLASS_NONE, 

2220 }: 

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

2222 raise ValueError(msg) 

2223 if value < 0 or value > 7: 

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

2225 raise ValueError(msg) 

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

2227 

2228 if hasattr(resource, "prlimit"): 

2229 

2230 @wrap_exceptions 

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

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

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

2234 # PID 0 is not supported on Linux. 

2235 if self.pid == 0: 

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

2237 raise ValueError(msg) 

2238 try: 

2239 if limits is None: 

2240 # get 

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

2242 # Python 3.15 returns RLIM_INFINITY as the unsigned 

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

2244 if soft == RLIM_INFINITY_UNSIGNED: 

2245 soft = _psutil.RLIM_INFINITY 

2246 if hard == RLIM_INFINITY_UNSIGNED: 

2247 hard = _psutil.RLIM_INFINITY 

2248 return soft, hard 

2249 else: 

2250 # set 

2251 if len(limits) != 2: 

2252 msg = ( 

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

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

2255 ) 

2256 raise ValueError(msg) 

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

2258 except OSError as err: 

2259 if err.errno == errno.ENOSYS: 

2260 # I saw this happening on Travis: 

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

2262 self._raise_if_zombie() 

2263 raise 

2264 

2265 @wrap_exceptions 

2266 def status(self): 

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

2268 letter = letter.decode() 

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

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

2271 

2272 @wrap_exceptions 

2273 def open_files(self): 

2274 retlist = [] 

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

2276 hit_enoent = False 

2277 for fd in files: 

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

2279 try: 

2280 path = readlink(file) 

2281 except (FileNotFoundError, ProcessLookupError): 

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

2283 hit_enoent = True 

2284 continue 

2285 except OSError as err: 

2286 if err.errno == errno.EINVAL: 

2287 # not a link 

2288 continue 

2289 if err.errno == errno.ENAMETOOLONG: 

2290 # file name too long 

2291 debug(err) 

2292 continue 

2293 raise 

2294 else: 

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

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

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

2298 # absolute path though. 

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

2300 # Get file position and flags. 

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

2302 try: 

2303 with open_binary(file) as f: 

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

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

2306 except (FileNotFoundError, ProcessLookupError): 

2307 # fd gone in the meantime; process may 

2308 # still be alive 

2309 hit_enoent = True 

2310 else: 

2311 mode = file_flags_to_mode(flags) 

2312 ntuple = ntp.popenfile( 

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

2314 ) 

2315 retlist.append(ntuple) 

2316 if hit_enoent: 

2317 self._raise_if_not_alive() 

2318 return retlist 

2319 

2320 @wrap_exceptions 

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

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

2323 self._raise_if_not_alive() 

2324 return ret 

2325 

2326 @wrap_exceptions 

2327 def num_fds(self): 

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

2329 

2330 @wrap_exceptions 

2331 def ppid(self): 

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

2333 

2334 @wrap_exceptions 

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

2336 data = self._read_status_file() 

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

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

2339 

2340 @wrap_exceptions 

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

2342 data = self._read_status_file() 

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

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