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

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1233 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 

7 

8import base64 

9import collections 

10import enum 

11import errno 

12import functools 

13import glob 

14import os 

15import re 

16import resource 

17import socket 

18import struct 

19import sys 

20import warnings 

21from collections import defaultdict 

22from collections import namedtuple 

23 

24from . import _common 

25from . import _psposix 

26from . import _psutil_linux as cext 

27from . import _psutil_posix as cext_posix 

28from ._common import ENCODING 

29from ._common import NIC_DUPLEX_FULL 

30from ._common import NIC_DUPLEX_HALF 

31from ._common import NIC_DUPLEX_UNKNOWN 

32from ._common import AccessDenied 

33from ._common import NoSuchProcess 

34from ._common import ZombieProcess 

35from ._common import bcat 

36from ._common import cat 

37from ._common import debug 

38from ._common import decode 

39from ._common import get_procfs_path 

40from ._common import isfile_strict 

41from ._common import memoize 

42from ._common import memoize_when_activated 

43from ._common import open_binary 

44from ._common import open_text 

45from ._common import parse_environ_block 

46from ._common import path_exists_strict 

47from ._common import supports_ipv6 

48from ._common import usage_percent 

49 

50# fmt: off 

51__extra__all__ = [ 

52 'PROCFS_PATH', 

53 # io prio constants 

54 "IOPRIO_CLASS_NONE", "IOPRIO_CLASS_RT", "IOPRIO_CLASS_BE", 

55 "IOPRIO_CLASS_IDLE", 

56 # connection status constants 

57 "CONN_ESTABLISHED", "CONN_SYN_SENT", "CONN_SYN_RECV", "CONN_FIN_WAIT1", 

58 "CONN_FIN_WAIT2", "CONN_TIME_WAIT", "CONN_CLOSE", "CONN_CLOSE_WAIT", 

59 "CONN_LAST_ACK", "CONN_LISTEN", "CONN_CLOSING", 

60] 

61 

62if hasattr(resource, "prlimit"): 

63 __extra__all__.extend( 

64 [x for x in dir(cext) if x.startswith('RLIM') and x.isupper()] 

65 ) 

66# fmt: on 

67 

68 

69# ===================================================================== 

70# --- globals 

71# ===================================================================== 

72 

73 

74POWER_SUPPLY_PATH = "/sys/class/power_supply" 

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

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

77HAS_PROC_IO_PRIORITY = hasattr(cext, "proc_ioprio_get") 

78HAS_CPU_AFFINITY = hasattr(cext, "proc_cpu_affinity_get") 

79 

80# Number of clock ticks per second 

81CLOCK_TICKS = os.sysconf("SC_CLK_TCK") 

82PAGESIZE = cext_posix.getpagesize() 

83LITTLE_ENDIAN = sys.byteorder == 'little' 

84UNSET = object() 

85 

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

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

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

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

90# throughout Linux source code: 

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

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

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

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

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

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

97DISK_SECTOR_SIZE = 512 

98 

99AddressFamily = enum.IntEnum( 

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

101) 

102AF_LINK = AddressFamily.AF_LINK 

103 

104 

105# ioprio_* constants http://linux.die.net/man/2/ioprio_get 

106class IOPriority(enum.IntEnum): 

107 IOPRIO_CLASS_NONE = 0 

108 IOPRIO_CLASS_RT = 1 

109 IOPRIO_CLASS_BE = 2 

110 IOPRIO_CLASS_IDLE = 3 

111 

112 

113globals().update(IOPriority.__members__) 

114 

115# See: 

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

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

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

119PROC_STATUSES = { 

120 "R": _common.STATUS_RUNNING, 

121 "S": _common.STATUS_SLEEPING, 

122 "D": _common.STATUS_DISK_SLEEP, 

123 "T": _common.STATUS_STOPPED, 

124 "t": _common.STATUS_TRACING_STOP, 

125 "Z": _common.STATUS_ZOMBIE, 

126 "X": _common.STATUS_DEAD, 

127 "x": _common.STATUS_DEAD, 

128 "K": _common.STATUS_WAKE_KILL, 

129 "W": _common.STATUS_WAKING, 

130 "I": _common.STATUS_IDLE, 

131 "P": _common.STATUS_PARKED, 

132} 

133 

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

135TCP_STATUSES = { 

136 "01": _common.CONN_ESTABLISHED, 

137 "02": _common.CONN_SYN_SENT, 

138 "03": _common.CONN_SYN_RECV, 

139 "04": _common.CONN_FIN_WAIT1, 

140 "05": _common.CONN_FIN_WAIT2, 

141 "06": _common.CONN_TIME_WAIT, 

142 "07": _common.CONN_CLOSE, 

143 "08": _common.CONN_CLOSE_WAIT, 

144 "09": _common.CONN_LAST_ACK, 

145 "0A": _common.CONN_LISTEN, 

146 "0B": _common.CONN_CLOSING, 

147} 

148 

149 

150# ===================================================================== 

151# --- named tuples 

152# ===================================================================== 

153 

154 

155# fmt: off 

156# psutil.virtual_memory() 

157svmem = namedtuple( 

158 'svmem', ['total', 'available', 'percent', 'used', 'free', 

159 'active', 'inactive', 'buffers', 'cached', 'shared', 'slab']) 

160# psutil.disk_io_counters() 

161sdiskio = namedtuple( 

162 'sdiskio', ['read_count', 'write_count', 

163 'read_bytes', 'write_bytes', 

164 'read_time', 'write_time', 

165 'read_merged_count', 'write_merged_count', 

166 'busy_time']) 

167# psutil.Process().open_files() 

168popenfile = namedtuple( 

169 'popenfile', ['path', 'fd', 'position', 'mode', 'flags']) 

170# psutil.Process().memory_info() 

171pmem = namedtuple('pmem', 'rss vms shared text lib data dirty') 

172# psutil.Process().memory_full_info() 

173pfullmem = namedtuple('pfullmem', pmem._fields + ('uss', 'pss', 'swap')) 

174# psutil.Process().memory_maps(grouped=True) 

175pmmap_grouped = namedtuple( 

176 'pmmap_grouped', 

177 ['path', 'rss', 'size', 'pss', 'shared_clean', 'shared_dirty', 

178 'private_clean', 'private_dirty', 'referenced', 'anonymous', 'swap']) 

179# psutil.Process().memory_maps(grouped=False) 

180pmmap_ext = namedtuple( 

181 'pmmap_ext', 'addr perms ' + ' '.join(pmmap_grouped._fields)) 

182# psutil.Process.io_counters() 

183pio = namedtuple('pio', ['read_count', 'write_count', 

184 'read_bytes', 'write_bytes', 

185 'read_chars', 'write_chars']) 

186# psutil.Process.cpu_times() 

187pcputimes = namedtuple('pcputimes', 

188 ['user', 'system', 'children_user', 'children_system', 

189 'iowait']) 

190# fmt: on 

191 

192 

193# ===================================================================== 

194# --- utils 

195# ===================================================================== 

196 

197 

198def readlink(path): 

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

200 assert isinstance(path, str), path 

201 path = os.readlink(path) 

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

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

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

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

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

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

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

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

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

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

212 # don't care. 

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

214 path = path[:-10] 

215 return path 

216 

217 

218def file_flags_to_mode(flags): 

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

220 Used by Process.open_files(). 

221 """ 

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

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

224 if flags & os.O_APPEND: 

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

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

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

228 return mode 

229 

230 

231def is_storage_device(name): 

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

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

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

235 return True. 

236 """ 

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

238 # https://github.com/sysstat/sysstat/blob/ 

239 # 97912938cd476645b267280069e83b1c8dc0e1c7/common.c#L208 

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

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

242 including_virtual = True 

243 if including_virtual: 

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

245 else: 

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

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

248 

249 

250@memoize 

251def set_scputimes_ntuple(procfs_path): 

252 """Set a namedtuple of variable fields depending on the CPU times 

253 available on this Linux kernel version which may be: 

254 (user, nice, system, idle, iowait, irq, softirq, [steal, [guest, 

255 [guest_nice]]]) 

256 Used by cpu_times() function. 

257 """ 

258 global scputimes 

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

260 values = f.readline().split()[1:] 

261 fields = ['user', 'nice', 'system', 'idle', 'iowait', 'irq', 'softirq'] 

262 vlen = len(values) 

263 if vlen >= 8: 

264 # Linux >= 2.6.11 

265 fields.append('steal') 

266 if vlen >= 9: 

267 # Linux >= 2.6.24 

268 fields.append('guest') 

269 if vlen >= 10: 

270 # Linux >= 3.2.0 

271 fields.append('guest_nice') 

272 scputimes = namedtuple('scputimes', fields) 

273 

274 

275try: 

276 set_scputimes_ntuple("/proc") 

277except Exception as err: # noqa: BLE001 

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

279 debug(f"ignoring exception on import: {err!r}") 

280 scputimes = namedtuple('scputimes', 'user system idle')(0.0, 0.0, 0.0) 

281 

282 

283# ===================================================================== 

284# --- system memory 

285# ===================================================================== 

286 

287 

288def calculate_avail_vmem(mems): 

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

290 "MemAvailable", see: 

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

292 

293 This code reimplements the algorithm outlined here: 

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

295 commit/?id=34e431b0ae398fc54ea69ff85ec700722c9da773 

296 

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

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

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

300 column). 

301 

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

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

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

305 See: 

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

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

308 """ 

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

310 # https://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/ 

311 # commit/?id=34e431b0ae398fc54ea69ff85ec700722c9da773 

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

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

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

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

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

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

318 free = mems[b'MemFree:'] 

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

320 try: 

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

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

323 slab_reclaimable = mems[b'SReclaimable:'] 

324 except KeyError as err: 

325 debug( 

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

327 " approximation for calculating available memory" 

328 ) 

329 return fallback 

330 try: 

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

332 except OSError: 

333 return fallback # kernel 2.6.13 

334 

335 watermark_low = 0 

336 with f: 

337 for line in f: 

338 line = line.strip() 

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

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

341 watermark_low *= PAGESIZE 

342 

343 avail = free - watermark_low 

344 pagecache = lru_active_file + lru_inactive_file 

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

346 avail += pagecache 

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

348 return int(avail) 

349 

350 

351def virtual_memory(): 

352 """Report virtual memory stats. 

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

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

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

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

357 CLI tools. 

358 """ 

359 missing_fields = [] 

360 mems = {} 

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

362 for line in f: 

363 fields = line.split() 

364 mems[fields[0]] = int(fields[1]) * 1024 

365 

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

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

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

369 total = mems[b'MemTotal:'] 

370 free = mems[b'MemFree:'] 

371 try: 

372 buffers = mems[b'Buffers:'] 

373 except KeyError: 

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

375 buffers = 0 

376 missing_fields.append('buffers') 

377 try: 

378 cached = mems[b"Cached:"] 

379 except KeyError: 

380 cached = 0 

381 missing_fields.append('cached') 

382 else: 

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

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

385 # This got changed in: 

386 # https://gitlab.com/procps-ng/procps/commit/ 

387 # 05d751c4f076a2f0118b914c5e51cfbb4762ad8e 

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

389 

390 try: 

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

392 except KeyError: 

393 try: 

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

395 except KeyError: 

396 shared = 0 

397 missing_fields.append('shared') 

398 

399 try: 

400 active = mems[b"Active:"] 

401 except KeyError: 

402 active = 0 

403 missing_fields.append('active') 

404 

405 try: 

406 inactive = mems[b"Inactive:"] 

407 except KeyError: 

408 try: 

409 inactive = ( 

410 mems[b"Inact_dirty:"] 

411 + mems[b"Inact_clean:"] 

412 + mems[b"Inact_laundry:"] 

413 ) 

414 except KeyError: 

415 inactive = 0 

416 missing_fields.append('inactive') 

417 

418 try: 

419 slab = mems[b"Slab:"] 

420 except KeyError: 

421 slab = 0 

422 

423 used = total - free - cached - buffers 

424 if used < 0: 

425 # May be symptomatic of running within a LCX container where such 

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

427 used = total - free 

428 

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

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

431 # which matched htop. 

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

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

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

435 # - MemAvailable has been introduced in kernel 3.14 

436 try: 

437 avail = mems[b'MemAvailable:'] 

438 except KeyError: 

439 avail = calculate_avail_vmem(mems) 

440 else: 

441 if avail == 0: 

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

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

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

445 # and it matches "free" CLI tool. 

446 avail = calculate_avail_vmem(mems) 

447 

448 if avail < 0: 

449 avail = 0 

450 missing_fields.append('available') 

451 elif avail > total: 

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

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

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

455 # https://gitlab.com/procps-ng/procps/blob/ 

456 # 24fd2605c51fccc375ab0287cec33aa767f06718/proc/sysinfo.c#L764 

457 avail = free 

458 

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

460 

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

462 if missing_fields: 

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

464 ", ".join(missing_fields), 

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

466 ) 

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

468 

469 return svmem( 

470 total, 

471 avail, 

472 percent, 

473 used, 

474 free, 

475 active, 

476 inactive, 

477 buffers, 

478 cached, 

479 shared, 

480 slab, 

481 ) 

482 

483 

484def swap_memory(): 

485 """Return swap memory metrics.""" 

486 mems = {} 

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

488 for line in f: 

489 fields = line.split() 

490 mems[fields[0]] = int(fields[1]) * 1024 

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

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

493 # for linux containers, see: 

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

495 try: 

496 total = mems[b'SwapTotal:'] 

497 free = mems[b'SwapFree:'] 

498 except KeyError: 

499 _, _, _, _, total, free, unit_multiplier = cext.linux_sysinfo() 

500 total *= unit_multiplier 

501 free *= unit_multiplier 

502 

503 used = total - free 

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

505 # get pgin/pgouts 

506 try: 

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

508 except OSError as err: 

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

510 msg = ( 

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

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

513 ) 

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

515 sin = sout = 0 

516 else: 

517 with f: 

518 sin = sout = None 

519 for line in f: 

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

521 # bytes instead 

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

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

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

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

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

527 break 

528 else: 

529 # we might get here when dealing with exotic Linux 

530 # flavors, see: 

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

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

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

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

535 sin = sout = 0 

536 return _common.sswap(total, used, free, percent, sin, sout) 

537 

538 

539# ===================================================================== 

540# --- CPU 

541# ===================================================================== 

542 

543 

544def cpu_times(): 

545 """Return a named tuple representing the following system-wide 

546 CPU times: 

547 (user, nice, system, idle, iowait, irq, softirq [steal, [guest, 

548 [guest_nice]]]) 

549 Last 3 fields may not be available on all Linux kernel versions. 

550 """ 

551 procfs_path = get_procfs_path() 

552 set_scputimes_ntuple(procfs_path) 

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

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

555 fields = values[1 : len(scputimes._fields) + 1] 

556 fields = [float(x) / CLOCK_TICKS for x in fields] 

557 return scputimes(*fields) 

558 

559 

560def per_cpu_times(): 

561 """Return a list of namedtuple representing the CPU times 

562 for every CPU available on the system. 

563 """ 

564 procfs_path = get_procfs_path() 

565 set_scputimes_ntuple(procfs_path) 

566 cpus = [] 

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

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

569 f.readline() 

570 for line in f: 

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

572 values = line.split() 

573 fields = values[1 : len(scputimes._fields) + 1] 

574 fields = [float(x) / CLOCK_TICKS for x in fields] 

575 entry = scputimes(*fields) 

576 cpus.append(entry) 

577 return cpus 

578 

579 

580def cpu_count_logical(): 

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

582 try: 

583 return os.sysconf("SC_NPROCESSORS_ONLN") 

584 except ValueError: 

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

586 num = 0 

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

588 for line in f: 

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

590 num += 1 

591 

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

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

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

595 if num == 0: 

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

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

598 for line in f: 

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

600 if search.match(line): 

601 num += 1 

602 

603 if num == 0: 

604 # mimic os.cpu_count() 

605 return None 

606 return num 

607 

608 

609def cpu_count_cores(): 

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

611 # Method #1 

612 ls = set() 

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

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

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

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

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

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

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

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

621 with open_binary(path) as f: 

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

623 result = len(ls) 

624 if result != 0: 

625 return result 

626 

627 # Method #2 

628 mapping = {} 

629 current_info = {} 

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

631 for line in f: 

632 line = line.strip().lower() 

633 if not line: 

634 # new section 

635 try: 

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

637 b'cpu cores' 

638 ] 

639 except KeyError: 

640 pass 

641 current_info = {} 

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

643 # ongoing section 

644 key, value = line.split(b'\t:', 1) 

645 current_info[key] = int(value) 

646 

647 result = sum(mapping.values()) 

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

649 

650 

651def cpu_stats(): 

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

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

654 ctx_switches = None 

655 interrupts = None 

656 soft_interrupts = None 

657 for line in f: 

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

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

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

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

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

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

664 if ( 

665 ctx_switches is not None 

666 and soft_interrupts is not None 

667 and interrupts is not None 

668 ): 

669 break 

670 syscalls = 0 

671 return _common.scpustats( 

672 ctx_switches, interrupts, soft_interrupts, syscalls 

673 ) 

674 

675 

676def _cpu_get_cpuinfo_freq(): 

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

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

679 return [ 

680 float(line.split(b':', 1)[1]) 

681 for line in f 

682 if line.lower().startswith(b'cpu mhz') 

683 ] 

684 

685 

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

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

688): 

689 

690 def cpu_freq(): 

691 """Return frequency metrics for all CPUs. 

692 Contrarily to other OSes, Linux updates these values in 

693 real-time. 

694 """ 

695 cpuinfo_freqs = _cpu_get_cpuinfo_freq() 

696 paths = glob.glob( 

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

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

699 paths.sort(key=lambda x: int(re.search(r"[0-9]+", x).group())) 

700 ret = [] 

701 pjoin = os.path.join 

702 for i, path in enumerate(paths): 

703 if len(paths) == len(cpuinfo_freqs): 

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

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

706 curr = cpuinfo_freqs[i] * 1000 

707 else: 

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

709 if curr is None: 

710 # Likely an old RedHat, see: 

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

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

713 if curr is None: 

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

715 # if cpu core is offline, set to all zeroes 

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

717 ret.append(_common.scpufreq(0.0, 0.0, 0.0)) 

718 continue 

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

720 raise NotImplementedError(msg) 

721 curr = int(curr) / 1000 

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

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

724 ret.append(_common.scpufreq(curr, min_, max_)) 

725 return ret 

726 

727else: 

728 

729 def cpu_freq(): 

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

731 min and max frequencies are not available and are set to None. 

732 """ 

733 return [_common.scpufreq(x, 0.0, 0.0) for x in _cpu_get_cpuinfo_freq()] 

734 

735 

736# ===================================================================== 

737# --- network 

738# ===================================================================== 

739 

740 

741net_if_addrs = cext_posix.net_if_addrs 

742 

743 

744class _Ipv6UnsupportedError(Exception): 

745 pass 

746 

747 

748class NetConnections: 

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

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

751 "netstat -an". 

752 

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

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

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

756 

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

758 """ 

759 

760 def __init__(self): 

761 # The string represents the basename of the corresponding 

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

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

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

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

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

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

768 self.tmap = { 

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

770 "tcp": (tcp4, tcp6), 

771 "tcp4": (tcp4,), 

772 "tcp6": (tcp6,), 

773 "udp": (udp4, udp6), 

774 "udp4": (udp4,), 

775 "udp6": (udp6,), 

776 "unix": (unix,), 

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

778 "inet4": (tcp4, udp4), 

779 "inet6": (tcp6, udp6), 

780 } 

781 self._procfs_path = None 

782 

783 def get_proc_inodes(self, pid): 

784 inodes = defaultdict(list) 

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

786 try: 

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

788 except (FileNotFoundError, ProcessLookupError): 

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

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

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

792 continue 

793 except OSError as err: 

794 if err.errno == errno.EINVAL: 

795 # not a link 

796 continue 

797 if err.errno == errno.ENAMETOOLONG: 

798 # file name too long 

799 debug(err) 

800 continue 

801 raise 

802 else: 

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

804 # the process is using a socket 

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

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

807 return inodes 

808 

809 def get_all_inodes(self): 

810 inodes = {} 

811 for pid in pids(): 

812 try: 

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

814 except (FileNotFoundError, ProcessLookupError, PermissionError): 

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

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

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

818 # and fd set to None anyway. 

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

820 # unlikely we can do any better. 

821 # ENOENT just means a PID disappeared on us. 

822 continue 

823 return inodes 

824 

825 @staticmethod 

826 def decode_address(addr, family): 

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

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

829 

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

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

832 

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

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

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

836 to an IP address. 

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

838 

839 Reference: 

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

841 """ 

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

843 port = int(port, 16) 

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

845 # no end-points connected 

846 if not port: 

847 return () 

848 ip = ip.encode('ascii') 

849 if family == socket.AF_INET: 

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

851 if LITTLE_ENDIAN: 

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

853 else: 

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

855 else: # IPv6 

856 ip = base64.b16decode(ip) 

857 try: 

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

859 if LITTLE_ENDIAN: 

860 ip = socket.inet_ntop( 

861 socket.AF_INET6, 

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

863 ) 

864 else: 

865 ip = socket.inet_ntop( 

866 socket.AF_INET6, 

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

868 ) 

869 except ValueError: 

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

871 if not supports_ipv6(): 

872 raise _Ipv6UnsupportedError from None 

873 raise 

874 return _common.addr(ip, port) 

875 

876 @staticmethod 

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

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

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

880 # IPv6 not supported 

881 return 

882 with open_text(file) as f: 

883 f.readline() # skip the first line 

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

885 try: 

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

887 line.split()[:10] 

888 ) 

889 except ValueError: 

890 msg = ( 

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

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

893 ) 

894 raise RuntimeError(msg) from None 

895 if inode in inodes: 

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

897 # # out if there are multiple references to the 

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

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

900 # raise ValueError("ambiguous inode with multiple " 

901 # "PIDs references") 

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

903 else: 

904 pid, fd = None, -1 

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

906 continue 

907 else: 

908 if type_ == socket.SOCK_STREAM: 

909 status = TCP_STATUSES[status] 

910 else: 

911 status = _common.CONN_NONE 

912 try: 

913 laddr = NetConnections.decode_address(laddr, family) 

914 raddr = NetConnections.decode_address(raddr, family) 

915 except _Ipv6UnsupportedError: 

916 continue 

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

918 

919 @staticmethod 

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

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

922 with open_text(file) as f: 

923 f.readline() # skip the first line 

924 for line in f: 

925 tokens = line.split() 

926 try: 

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

928 except ValueError: 

929 if ' ' not in line: 

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

931 continue 

932 msg = ( 

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

934 ) 

935 raise RuntimeError(msg) # noqa: B904 

936 if inode in inodes: # noqa: SIM108 

937 # With UNIX sockets we can have a single inode 

938 # referencing many file descriptors. 

939 pairs = inodes[inode] 

940 else: 

941 pairs = [(None, -1)] 

942 for pid, fd in pairs: 

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

944 continue 

945 else: 

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

947 type_ = _common.socktype_to_enum(int(type_)) 

948 # XXX: determining the remote endpoint of a 

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

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

951 raddr = "" 

952 status = _common.CONN_NONE 

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

954 

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

956 self._procfs_path = get_procfs_path() 

957 if pid is not None: 

958 inodes = self.get_proc_inodes(pid) 

959 if not inodes: 

960 # no connections for this process 

961 return [] 

962 else: 

963 inodes = self.get_all_inodes() 

964 ret = set() 

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

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

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

968 ls = self.process_inet( 

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

970 ) 

971 else: 

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

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

974 if pid: 

975 conn = _common.pconn( 

976 fd, family, type_, laddr, raddr, status 

977 ) 

978 else: 

979 conn = _common.sconn( 

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

981 ) 

982 ret.add(conn) 

983 return list(ret) 

984 

985 

986_net_connections = NetConnections() 

987 

988 

989def net_connections(kind='inet'): 

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

991 return _net_connections.retrieve(kind) 

992 

993 

994def net_io_counters(): 

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

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

997 """ 

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

999 lines = f.readlines() 

1000 retdict = {} 

1001 for line in lines[2:]: 

1002 colon = line.rfind(':') 

1003 assert colon > 0, repr(line) 

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

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

1006 

1007 ( 

1008 # in 

1009 bytes_recv, 

1010 packets_recv, 

1011 errin, 

1012 dropin, 

1013 _fifoin, # unused 

1014 _framein, # unused 

1015 _compressedin, # unused 

1016 _multicastin, # unused 

1017 # out 

1018 bytes_sent, 

1019 packets_sent, 

1020 errout, 

1021 dropout, 

1022 _fifoout, # unused 

1023 _collisionsout, # unused 

1024 _carrierout, # unused 

1025 _compressedout, # unused 

1026 ) = map(int, fields) 

1027 

1028 retdict[name] = ( 

1029 bytes_sent, 

1030 bytes_recv, 

1031 packets_sent, 

1032 packets_recv, 

1033 errin, 

1034 errout, 

1035 dropin, 

1036 dropout, 

1037 ) 

1038 return retdict 

1039 

1040 

1041def net_if_stats(): 

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

1043 duplex_map = { 

1044 cext.DUPLEX_FULL: NIC_DUPLEX_FULL, 

1045 cext.DUPLEX_HALF: NIC_DUPLEX_HALF, 

1046 cext.DUPLEX_UNKNOWN: NIC_DUPLEX_UNKNOWN, 

1047 } 

1048 names = net_io_counters().keys() 

1049 ret = {} 

1050 for name in names: 

1051 try: 

1052 mtu = cext_posix.net_if_mtu(name) 

1053 flags = cext_posix.net_if_flags(name) 

1054 duplex, speed = cext.net_if_duplex_speed(name) 

1055 except OSError as err: 

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

1057 if err.errno != errno.ENODEV: 

1058 raise 

1059 debug(err) 

1060 else: 

1061 output_flags = ','.join(flags) 

1062 isup = 'running' in flags 

1063 ret[name] = _common.snicstats( 

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

1065 ) 

1066 return ret 

1067 

1068 

1069# ===================================================================== 

1070# --- disks 

1071# ===================================================================== 

1072 

1073 

1074disk_usage = _psposix.disk_usage 

1075 

1076 

1077def disk_io_counters(perdisk=False): 

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

1079 system as a dict of raw tuples. 

1080 """ 

1081 

1082 def read_procfs(): 

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

1084 # have 3 variations. 

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

1086 # "3 0 8 hda 8 8 8 8 8 8 8 8 8 8 8" 

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

1088 # name is in another position, like this: 

1089 # "3 0 hda 8 8 8 8 8 8 8 8 8 8 8" 

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

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

1092 # "3 1 hda1 8 8 8 8" 

1093 # 4.18+ has 4 fields added: 

1094 # "3 0 hda 8 8 8 8 8 8 8 8 8 8 8 0 0 0 0" 

1095 # 5.5 has 2 more fields. 

1096 # See: 

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

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

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

1100 lines = f.readlines() 

1101 for line in lines: 

1102 fields = line.split() 

1103 flen = len(fields) 

1104 # fmt: off 

1105 if flen == 15: 

1106 # Linux 2.4 

1107 name = fields[3] 

1108 reads = int(fields[2]) 

1109 (reads_merged, rbytes, rtime, writes, writes_merged, 

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

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

1112 # Linux 2.6+, line referring to a disk 

1113 name = fields[2] 

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

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

1116 elif flen == 7: 

1117 # Linux 2.6+, line referring to a partition 

1118 name = fields[2] 

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

1120 rtime = wtime = reads_merged = writes_merged = busy_time = 0 

1121 else: 

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

1123 raise ValueError(msg) 

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

1125 reads_merged, writes_merged, busy_time) 

1126 # fmt: on 

1127 

1128 def read_sysfs(): 

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

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

1131 if 'stat' not in files: 

1132 continue 

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

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

1135 name = os.path.basename(root) 

1136 # fmt: off 

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

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

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

1140 wtime, reads_merged, writes_merged, busy_time) 

1141 # fmt: on 

1142 

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

1144 gen = read_procfs() 

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

1146 gen = read_sysfs() 

1147 else: 

1148 msg = ( 

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

1150 " this system" 

1151 ) 

1152 raise NotImplementedError(msg) 

1153 

1154 retdict = {} 

1155 for entry in gen: 

1156 # fmt: off 

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

1158 writes_merged, busy_time) = entry 

1159 if not perdisk and not is_storage_device(name): 

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

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

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

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

1164 # of their own: 

1165 # $ cat /proc/diskstats 

1166 # 259 0 sda 10485760 ... 

1167 # 259 1 sda1 5186039 ... 

1168 # 259 1 sda2 5082039 ... 

1169 # See: 

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

1171 continue 

1172 

1173 rbytes *= DISK_SECTOR_SIZE 

1174 wbytes *= DISK_SECTOR_SIZE 

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

1176 reads_merged, writes_merged, busy_time) 

1177 # fmt: on 

1178 

1179 return retdict 

1180 

1181 

1182class RootFsDeviceFinder: 

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

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

1185 obtain the real device path. Resources: 

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

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

1188 """ 

1189 

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

1191 

1192 def __init__(self): 

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

1194 self.major = os.major(dev) 

1195 self.minor = os.minor(dev) 

1196 

1197 def ask_proc_partitions(self): 

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

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

1200 fields = line.split() 

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

1202 continue 

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

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

1205 name = fields[3] 

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

1207 if name: # just for extra safety 

1208 return f"/dev/{name}" 

1209 

1210 def ask_sys_dev_block(self): 

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

1212 with open_text(path) as f: 

1213 for line in f: 

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

1215 name = line.strip().rpartition("DEVNAME=")[2] 

1216 if name: # just for extra safety 

1217 return f"/dev/{name}" 

1218 

1219 def ask_sys_class_block(self): 

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

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

1222 for file in files: 

1223 try: 

1224 f = open_text(file) 

1225 except FileNotFoundError: # race condition 

1226 continue 

1227 else: 

1228 with f: 

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

1230 if data == needle: 

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

1232 return f"/dev/{name}" 

1233 

1234 def find(self): 

1235 path = None 

1236 if path is None: 

1237 try: 

1238 path = self.ask_proc_partitions() 

1239 except OSError as err: 

1240 debug(err) 

1241 if path is None: 

1242 try: 

1243 path = self.ask_sys_dev_block() 

1244 except OSError as err: 

1245 debug(err) 

1246 if path is None: 

1247 try: 

1248 path = self.ask_sys_class_block() 

1249 except OSError as err: 

1250 debug(err) 

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

1252 # coded, so we want to be sure. 

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

1254 return path 

1255 

1256 

1257def disk_partitions(all=False): 

1258 """Return mounted disk partitions as a list of namedtuples.""" 

1259 fstypes = set() 

1260 procfs_path = get_procfs_path() 

1261 if not all: 

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

1263 for line in f: 

1264 line = line.strip() 

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

1266 fstypes.add(line.strip()) 

1267 else: 

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

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

1270 if fstype == "zfs": 

1271 fstypes.add("zfs") 

1272 

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

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

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

1276 else: 

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

1278 

1279 retlist = [] 

1280 partitions = cext.disk_partitions(mounts_path) 

1281 for partition in partitions: 

1282 device, mountpoint, fstype, opts = partition 

1283 if device == 'none': 

1284 device = '' 

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

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

1287 if not all: 

1288 if not device or fstype not in fstypes: 

1289 continue 

1290 ntuple = _common.sdiskpart(device, mountpoint, fstype, opts) 

1291 retlist.append(ntuple) 

1292 

1293 return retlist 

1294 

1295 

1296# ===================================================================== 

1297# --- sensors 

1298# ===================================================================== 

1299 

1300 

1301def sensors_temperatures(): 

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

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

1304 temperatures. 

1305 

1306 Implementation notes: 

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

1308 retrieve this info, and this implementation relies on it 

1309 only (old distros will probably use something else) 

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

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

1312 difficult to parse 

1313 """ 

1314 ret = collections.defaultdict(list) 

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

1316 # CentOS has an intermediate /device directory: 

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

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

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

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

1321 

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

1323 # /sys/class/hwmon/ 

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

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

1326 basenames2 = glob.glob( 

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

1328 ) 

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

1330 for name in basenames2: 

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

1332 if altname not in basenames: 

1333 basenames.append(name) 

1334 

1335 for base in basenames: 

1336 try: 

1337 path = base + '_input' 

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

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

1340 unit_name = cat(path).strip() 

1341 except (OSError, ValueError): 

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

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

1344 # is a stinky broken mess. 

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

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

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

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

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

1350 continue 

1351 

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

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

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

1355 

1356 if high is not None: 

1357 try: 

1358 high = float(high) / 1000.0 

1359 except ValueError: 

1360 high = None 

1361 if critical is not None: 

1362 try: 

1363 critical = float(critical) / 1000.0 

1364 except ValueError: 

1365 critical = None 

1366 

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

1368 

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

1370 if not basenames: 

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

1372 basenames = sorted(set(basenames)) 

1373 

1374 for base in basenames: 

1375 try: 

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

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

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

1379 unit_name = cat(path).strip() 

1380 except (OSError, ValueError) as err: 

1381 debug(err) 

1382 continue 

1383 

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

1385 trip_points = { 

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

1387 for p in trip_paths 

1388 } 

1389 critical = None 

1390 high = None 

1391 for trip_point in trip_points: 

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

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

1394 if trip_type == 'critical': 

1395 critical = bcat( 

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

1397 ) 

1398 elif trip_type == 'high': 

1399 high = bcat( 

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

1401 ) 

1402 

1403 if high is not None: 

1404 try: 

1405 high = float(high) / 1000.0 

1406 except ValueError: 

1407 high = None 

1408 if critical is not None: 

1409 try: 

1410 critical = float(critical) / 1000.0 

1411 except ValueError: 

1412 critical = None 

1413 

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

1415 

1416 return dict(ret) 

1417 

1418 

1419def sensors_fans(): 

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

1421 dict including hardware label and current speed. 

1422 

1423 Implementation notes: 

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

1425 retrieve this info, and this implementation relies on it 

1426 only (old distros will probably use something else) 

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

1428 """ 

1429 ret = collections.defaultdict(list) 

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

1431 if not basenames: 

1432 # CentOS has an intermediate /device directory: 

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

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

1435 

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

1437 for base in basenames: 

1438 try: 

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

1440 except OSError as err: 

1441 debug(err) 

1442 continue 

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

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

1445 ret[unit_name].append(_common.sfan(label, current)) 

1446 

1447 return dict(ret) 

1448 

1449 

1450def sensors_battery(): 

1451 """Return battery information. 

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

1453 directory structure may vary and provide files with the same 

1454 meaning but under different names, see: 

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

1456 """ 

1457 null = object() 

1458 

1459 def multi_bcat(*paths): 

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

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

1462 """ 

1463 for path in paths: 

1464 ret = bcat(path, fallback=null) 

1465 if ret != null: 

1466 try: 

1467 return int(ret) 

1468 except ValueError: 

1469 return ret.strip() 

1470 return None 

1471 

1472 bats = [ 

1473 x 

1474 for x in os.listdir(POWER_SUPPLY_PATH) 

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

1476 ] 

1477 if not bats: 

1478 return None 

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

1480 # some rare exceptions: 

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

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

1483 

1484 # Base metrics. 

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

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

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

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

1489 

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

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

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

1493 try: 

1494 percent = 100.0 * energy_now / energy_full 

1495 except ZeroDivisionError: 

1496 percent = 0.0 

1497 else: 

1498 percent = int(cat(root + "/capacity", fallback=-1)) 

1499 if percent == -1: 

1500 return None 

1501 

1502 # Is AC power cable plugged in? 

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

1504 # it's called "AC". 

1505 power_plugged = None 

1506 online = multi_bcat( 

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

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

1509 ) 

1510 if online is not None: 

1511 power_plugged = online == 1 

1512 else: 

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

1514 if status == "discharging": 

1515 power_plugged = False 

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

1517 power_plugged = True 

1518 

1519 # Seconds left. 

1520 # Note to self: we may also calculate the charging ETA as per: 

1521 # https://github.com/thialfihar/dotfiles/blob/ 

1522 # 013937745fd9050c30146290e8f963d65c0179e6/bin/battery.py#L55 

1523 if power_plugged: 

1524 secsleft = _common.POWER_TIME_UNLIMITED 

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

1526 try: 

1527 secsleft = int(energy_now / power_now * 3600) 

1528 except ZeroDivisionError: 

1529 secsleft = _common.POWER_TIME_UNKNOWN 

1530 elif time_to_empty is not None: 

1531 secsleft = int(time_to_empty * 60) 

1532 if secsleft < 0: 

1533 secsleft = _common.POWER_TIME_UNKNOWN 

1534 else: 

1535 secsleft = _common.POWER_TIME_UNKNOWN 

1536 

1537 return _common.sbattery(percent, secsleft, power_plugged) 

1538 

1539 

1540# ===================================================================== 

1541# --- other system functions 

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

1543 

1544 

1545def users(): 

1546 """Return currently connected users as a list of namedtuples.""" 

1547 retlist = [] 

1548 rawlist = cext.users() 

1549 for item in rawlist: 

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

1551 nt = _common.suser(user, tty or None, hostname, tstamp, pid) 

1552 retlist.append(nt) 

1553 return retlist 

1554 

1555 

1556def boot_time(): 

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

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

1559 with open_binary(path) as f: 

1560 for line in f: 

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

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

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

1564 raise RuntimeError(msg) 

1565 

1566 

1567# ===================================================================== 

1568# --- processes 

1569# ===================================================================== 

1570 

1571 

1572def pids(): 

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

1574 path = get_procfs_path().encode(ENCODING) 

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

1576 

1577 

1578def pid_exists(pid): 

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

1580 supported (always return False). 

1581 """ 

1582 if not _psposix.pid_exists(pid): 

1583 return False 

1584 else: 

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

1586 # (thread IDs). 

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

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

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

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

1591 # only, see: 

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

1593 try: 

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

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

1596 # 'return pid in pids()' 

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

1598 with open_binary(path) as f: 

1599 for line in f: 

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

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

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

1603 # dealing with a process PID. 

1604 return tgid == pid 

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

1606 raise ValueError(msg) 

1607 except (OSError, ValueError): 

1608 return pid in pids() 

1609 

1610 

1611def ppid_map(): 

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

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

1614 """ 

1615 ret = {} 

1616 procfs_path = get_procfs_path() 

1617 for pid in pids(): 

1618 try: 

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

1620 data = f.read() 

1621 except (FileNotFoundError, ProcessLookupError): 

1622 pass 

1623 except PermissionError as err: 

1624 raise AccessDenied(pid) from err 

1625 else: 

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

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

1628 ppid = int(dset[1]) 

1629 ret[pid] = ppid 

1630 return ret 

1631 

1632 

1633def wrap_exceptions(fun): 

1634 """Decorator which translates bare OSError and OSError exceptions 

1635 into NoSuchProcess and AccessDenied. 

1636 """ 

1637 

1638 @functools.wraps(fun) 

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

1640 pid, name = self.pid, self._name 

1641 try: 

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

1643 except PermissionError as err: 

1644 raise AccessDenied(pid, name) from err 

1645 except ProcessLookupError as err: 

1646 self._raise_if_zombie() 

1647 raise NoSuchProcess(pid, name) from err 

1648 except FileNotFoundError as err: 

1649 self._raise_if_zombie() 

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

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

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

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

1654 raise NoSuchProcess(pid, name) from err 

1655 raise 

1656 

1657 return wrapper 

1658 

1659 

1660class Process: 

1661 """Linux process implementation.""" 

1662 

1663 __slots__ = [ 

1664 "_cache", 

1665 "_ctime", 

1666 "_name", 

1667 "_ppid", 

1668 "_procfs_path", 

1669 "pid", 

1670 ] 

1671 

1672 def __init__(self, pid): 

1673 self.pid = pid 

1674 self._name = None 

1675 self._ppid = None 

1676 self._ctime = None 

1677 self._procfs_path = get_procfs_path() 

1678 

1679 def _is_zombie(self): 

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

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

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

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

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

1685 # exception. 

1686 try: 

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

1688 except OSError: 

1689 return False 

1690 else: 

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

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

1693 return status == b"Z" 

1694 

1695 def _raise_if_zombie(self): 

1696 if self._is_zombie(): 

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

1698 

1699 def _raise_if_not_alive(self): 

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

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

1702 # incorrect or incomplete result. 

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

1704 

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

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

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

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

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

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

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

1712 try: 

1713 return readlink(path) 

1714 except (FileNotFoundError, ProcessLookupError): 

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

1716 self._raise_if_zombie() 

1717 if fallback is not UNSET: 

1718 return fallback 

1719 raise 

1720 

1721 @wrap_exceptions 

1722 @memoize_when_activated 

1723 def _parse_stat_file(self): 

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

1725 process info. 

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

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

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

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

1730 in use. 

1731 """ 

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

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

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

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

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

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

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

1739 

1740 ret = {} 

1741 ret['name'] = name 

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

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

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

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

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

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

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

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

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

1751 try: 

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

1753 except IndexError: 

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

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

1756 ret['blkio_ticks'] = 0 

1757 

1758 return ret 

1759 

1760 @wrap_exceptions 

1761 @memoize_when_activated 

1762 def _read_status_file(self): 

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

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

1765 in use. 

1766 """ 

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

1768 return f.read() 

1769 

1770 @wrap_exceptions 

1771 @memoize_when_activated 

1772 def _read_smaps_file(self): 

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

1774 return f.read().strip() 

1775 

1776 def oneshot_enter(self): 

1777 self._parse_stat_file.cache_activate(self) 

1778 self._read_status_file.cache_activate(self) 

1779 self._read_smaps_file.cache_activate(self) 

1780 

1781 def oneshot_exit(self): 

1782 self._parse_stat_file.cache_deactivate(self) 

1783 self._read_status_file.cache_deactivate(self) 

1784 self._read_smaps_file.cache_deactivate(self) 

1785 

1786 @wrap_exceptions 

1787 def name(self): 

1788 # XXX - gets changed later and probably needs refactoring 

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

1790 

1791 @wrap_exceptions 

1792 def exe(self): 

1793 return self._readlink( 

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

1795 ) 

1796 

1797 @wrap_exceptions 

1798 def cmdline(self): 

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

1800 data = f.read() 

1801 if not data: 

1802 # may happen in case of zombie process 

1803 self._raise_if_zombie() 

1804 return [] 

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

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

1807 # some processes may change their cmdline after being started 

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

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

1810 # Chrome process is an example. See: 

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

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

1813 if data.endswith(sep): 

1814 data = data[:-1] 

1815 cmdline = data.split(sep) 

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

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

1818 # issues/1179#issuecomment-552984549 

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

1820 cmdline = data.split(' ') 

1821 return cmdline 

1822 

1823 @wrap_exceptions 

1824 def environ(self): 

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

1826 data = f.read() 

1827 return parse_environ_block(data) 

1828 

1829 @wrap_exceptions 

1830 def terminal(self): 

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

1832 tmap = _psposix.get_terminal_map() 

1833 try: 

1834 return tmap[tty_nr] 

1835 except KeyError: 

1836 return None 

1837 

1838 # May not be available on old kernels. 

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

1840 

1841 @wrap_exceptions 

1842 def io_counters(self): 

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

1844 fields = {} 

1845 with open_binary(fname) as f: 

1846 for line in f: 

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

1848 line = line.strip() 

1849 if line: 

1850 try: 

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

1852 except ValueError: 

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

1854 continue 

1855 else: 

1856 fields[name] = int(value) 

1857 if not fields: 

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

1859 raise RuntimeError(msg) 

1860 try: 

1861 return pio( 

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

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

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

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

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

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

1868 ) 

1869 except KeyError as err: 

1870 msg = ( 

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

1872 f" fields are {fields!r}" 

1873 ) 

1874 raise ValueError(msg) from None 

1875 

1876 @wrap_exceptions 

1877 def cpu_times(self): 

1878 values = self._parse_stat_file() 

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

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

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

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

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

1884 return pcputimes(utime, stime, children_utime, children_stime, iowait) 

1885 

1886 @wrap_exceptions 

1887 def cpu_num(self): 

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

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

1890 

1891 @wrap_exceptions 

1892 def wait(self, timeout=None): 

1893 return _psposix.wait_pid(self.pid, timeout, self._name) 

1894 

1895 @wrap_exceptions 

1896 def create_time(self, monotonic=False): 

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

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

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

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

1901 # changes and is unaffected by system clock updates. 

1902 if self._ctime is None: 

1903 self._ctime = ( 

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

1905 ) 

1906 if monotonic: 

1907 return self._ctime 

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

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

1910 return self._ctime + boot_time() 

1911 

1912 @wrap_exceptions 

1913 def memory_info(self): 

1914 # ============================================================ 

1915 # | FIELD | DESCRIPTION | AKA | TOP | 

1916 # ============================================================ 

1917 # | rss | resident set size | | RES | 

1918 # | vms | total program size | size | VIRT | 

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

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

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

1922 # | data | data + stack | drs | DATA | 

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

1924 # ============================================================ 

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

1926 vms, rss, shared, text, lib, data, dirty = ( 

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

1928 ) 

1929 return pmem(rss, vms, shared, text, lib, data, dirty) 

1930 

1931 if HAS_PROC_SMAPS_ROLLUP or HAS_PROC_SMAPS: 

1932 

1933 def _parse_smaps_rollup(self): 

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

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

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

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

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

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

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

1941 # compared to /proc/pid/smaps_rollup. 

1942 uss = pss = swap = 0 

1943 with open_binary( 

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

1945 ) as f: 

1946 for line in f: 

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

1948 # Private_Clean, Private_Dirty, Private_Hugetlb 

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

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

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

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

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

1954 return (uss, pss, swap) 

1955 

1956 @wrap_exceptions 

1957 def _parse_smaps( 

1958 self, 

1959 # Gets Private_Clean, Private_Dirty, Private_Hugetlb. 

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

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

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

1963 ): 

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

1965 # CONFIG_MMU kernel configuration option is not enabled. 

1966 

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

1968 # line by line. 

1969 # 

1970 # You might be tempted to calculate USS by subtracting 

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

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

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

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

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

1976 # correct information. 

1977 smaps_data = self._read_smaps_file() 

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

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

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

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

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

1983 return (uss, pss, swap) 

1984 

1985 @wrap_exceptions 

1986 def memory_full_info(self): 

1987 if HAS_PROC_SMAPS_ROLLUP: # faster 

1988 try: 

1989 uss, pss, swap = self._parse_smaps_rollup() 

1990 except (ProcessLookupError, FileNotFoundError): 

1991 uss, pss, swap = self._parse_smaps() 

1992 else: 

1993 uss, pss, swap = self._parse_smaps() 

1994 basic_mem = self.memory_info() 

1995 return pfullmem(*basic_mem + (uss, pss, swap)) 

1996 

1997 else: 

1998 memory_full_info = memory_info 

1999 

2000 if HAS_PROC_SMAPS: 

2001 

2002 @wrap_exceptions 

2003 def memory_maps(self): 

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

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

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

2007 

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

2009 CONFIG_MMU kernel configuration option is not enabled. 

2010 """ 

2011 

2012 def get_blocks(lines, current_block): 

2013 data = {} 

2014 for line in lines: 

2015 fields = line.split(None, 5) 

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

2017 # new block section 

2018 yield (current_block.pop(), data) 

2019 current_block.append(line) 

2020 else: 

2021 try: 

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

2023 except (ValueError, IndexError): 

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

2025 # see issue #369 

2026 continue 

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

2028 raise ValueError(msg) from None 

2029 yield (current_block.pop(), data) 

2030 

2031 data = self._read_smaps_file() 

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

2033 # zombies. 

2034 if not data: 

2035 self._raise_if_zombie() 

2036 return [] 

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

2038 ls = [] 

2039 first_line = lines.pop(0) 

2040 current_block = [first_line] 

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

2042 hfields = header.split(None, 5) 

2043 try: 

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

2045 except ValueError: 

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

2047 if not path: 

2048 path = '[anon]' 

2049 else: 

2050 path = decode(path) 

2051 path = path.strip() 

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

2053 path 

2054 ): 

2055 path = path[:-10] 

2056 item = ( 

2057 decode(addr), 

2058 decode(perms), 

2059 path, 

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

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

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

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

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

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

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

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

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

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

2070 ) 

2071 ls.append(item) 

2072 return ls 

2073 

2074 @wrap_exceptions 

2075 def cwd(self): 

2076 return self._readlink( 

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

2078 ) 

2079 

2080 @wrap_exceptions 

2081 def num_ctx_switches( 

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

2083 ): 

2084 data = self._read_status_file() 

2085 ctxsw = _ctxsw_re.findall(data) 

2086 if not ctxsw: 

2087 msg = ( 

2088 "'voluntary_ctxt_switches' and" 

2089 " 'nonvoluntary_ctxt_switches'lines were not found in" 

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

2091 " probably older than 2.6.23" 

2092 ) 

2093 raise NotImplementedError(msg) 

2094 return _common.pctxsw(int(ctxsw[0]), int(ctxsw[1])) 

2095 

2096 @wrap_exceptions 

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

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

2099 data = self._read_status_file() 

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

2101 

2102 @wrap_exceptions 

2103 def threads(self): 

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

2105 thread_ids.sort() 

2106 retlist = [] 

2107 hit_enoent = False 

2108 for thread_id in thread_ids: 

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

2110 try: 

2111 with open_binary(fname) as f: 

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

2113 except (FileNotFoundError, ProcessLookupError): 

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

2115 # it means thread disappeared on us 

2116 hit_enoent = True 

2117 continue 

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

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

2120 values = st.split(b' ') 

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

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

2123 ntuple = _common.pthread(int(thread_id), utime, stime) 

2124 retlist.append(ntuple) 

2125 if hit_enoent: 

2126 self._raise_if_not_alive() 

2127 return retlist 

2128 

2129 @wrap_exceptions 

2130 def nice_get(self): 

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

2132 # data = f.read() 

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

2134 

2135 # Use C implementation 

2136 return cext_posix.getpriority(self.pid) 

2137 

2138 @wrap_exceptions 

2139 def nice_set(self, value): 

2140 return cext_posix.setpriority(self.pid, value) 

2141 

2142 # starting from CentOS 6. 

2143 if HAS_CPU_AFFINITY: 

2144 

2145 @wrap_exceptions 

2146 def cpu_affinity_get(self): 

2147 return cext.proc_cpu_affinity_get(self.pid) 

2148 

2149 def _get_eligible_cpus( 

2150 self, _re=re.compile(br"Cpus_allowed_list:\t(\d+)-(\d+)") 

2151 ): 

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

2153 data = self._read_status_file() 

2154 match = _re.findall(data) 

2155 if match: 

2156 return list(range(int(match[0][0]), int(match[0][1]) + 1)) 

2157 else: 

2158 return list(range(len(per_cpu_times()))) 

2159 

2160 @wrap_exceptions 

2161 def cpu_affinity_set(self, cpus): 

2162 try: 

2163 cext.proc_cpu_affinity_set(self.pid, cpus) 

2164 except (OSError, ValueError) as err: 

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

2166 eligible_cpus = self._get_eligible_cpus() 

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

2168 for cpu in cpus: 

2169 if cpu not in all_cpus: 

2170 msg = ( 

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

2172 f" {eligible_cpus!r}" 

2173 ) 

2174 raise ValueError(msg) from None 

2175 if cpu not in eligible_cpus: 

2176 msg = ( 

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

2178 f" between {eligible_cpus}" 

2179 ) 

2180 raise ValueError(msg) from err 

2181 raise 

2182 

2183 # only starting from kernel 2.6.13 

2184 if HAS_PROC_IO_PRIORITY: 

2185 

2186 @wrap_exceptions 

2187 def ionice_get(self): 

2188 ioclass, value = cext.proc_ioprio_get(self.pid) 

2189 ioclass = IOPriority(ioclass) 

2190 return _common.pionice(ioclass, value) 

2191 

2192 @wrap_exceptions 

2193 def ionice_set(self, ioclass, value): 

2194 if value is None: 

2195 value = 0 

2196 if value and ioclass in { 

2197 IOPriority.IOPRIO_CLASS_IDLE, 

2198 IOPriority.IOPRIO_CLASS_NONE, 

2199 }: 

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

2201 raise ValueError(msg) 

2202 if value < 0 or value > 7: 

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

2204 raise ValueError(msg) 

2205 return cext.proc_ioprio_set(self.pid, ioclass, value) 

2206 

2207 if hasattr(resource, "prlimit"): 

2208 

2209 @wrap_exceptions 

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

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

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

2213 # PID 0 is not supported on Linux. 

2214 if self.pid == 0: 

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

2216 raise ValueError(msg) 

2217 try: 

2218 if limits is None: 

2219 # get 

2220 return resource.prlimit(self.pid, resource_) 

2221 else: 

2222 # set 

2223 if len(limits) != 2: 

2224 msg = ( 

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

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

2227 ) 

2228 raise ValueError(msg) 

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

2230 except OSError as err: 

2231 if err.errno == errno.ENOSYS: 

2232 # I saw this happening on Travis: 

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

2234 self._raise_if_zombie() 

2235 raise 

2236 

2237 @wrap_exceptions 

2238 def status(self): 

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

2240 letter = letter.decode() 

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

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

2243 

2244 @wrap_exceptions 

2245 def open_files(self): 

2246 retlist = [] 

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

2248 hit_enoent = False 

2249 for fd in files: 

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

2251 try: 

2252 path = readlink(file) 

2253 except (FileNotFoundError, ProcessLookupError): 

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

2255 hit_enoent = True 

2256 continue 

2257 except OSError as err: 

2258 if err.errno == errno.EINVAL: 

2259 # not a link 

2260 continue 

2261 if err.errno == errno.ENAMETOOLONG: 

2262 # file name too long 

2263 debug(err) 

2264 continue 

2265 raise 

2266 else: 

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

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

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

2270 # absolute path though. 

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

2272 # Get file position and flags. 

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

2274 try: 

2275 with open_binary(file) as f: 

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

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

2278 except (FileNotFoundError, ProcessLookupError): 

2279 # fd gone in the meantime; process may 

2280 # still be alive 

2281 hit_enoent = True 

2282 else: 

2283 mode = file_flags_to_mode(flags) 

2284 ntuple = popenfile( 

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

2286 ) 

2287 retlist.append(ntuple) 

2288 if hit_enoent: 

2289 self._raise_if_not_alive() 

2290 return retlist 

2291 

2292 @wrap_exceptions 

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

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

2295 self._raise_if_not_alive() 

2296 return ret 

2297 

2298 @wrap_exceptions 

2299 def num_fds(self): 

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

2301 

2302 @wrap_exceptions 

2303 def ppid(self): 

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

2305 

2306 @wrap_exceptions 

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

2308 data = self._read_status_file() 

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

2310 return _common.puids(int(real), int(effective), int(saved)) 

2311 

2312 @wrap_exceptions 

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

2314 data = self._read_status_file() 

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

2316 return _common.pgids(int(real), int(effective), int(saved))