Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/psutil-7.1.0-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

1231 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 # - starting from 4.4.0 we match free's "available" column. 

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

425 # which matched htop. 

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

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

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

429 # - MemAvailable has been introduced in kernel 3.14 

430 try: 

431 avail = mems[b'MemAvailable:'] 

432 except KeyError: 

433 avail = calculate_avail_vmem(mems) 

434 else: 

435 if avail == 0: 

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

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

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

439 # and it matches "free" CLI tool. 

440 avail = calculate_avail_vmem(mems) 

441 

442 if avail < 0: 

443 avail = 0 

444 missing_fields.append('available') 

445 elif avail > total: 

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

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

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

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

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

451 avail = free 

452 

453 used = total - avail 

454 

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

456 

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

458 if missing_fields: 

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

460 ", ".join(missing_fields), 

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

462 ) 

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

464 

465 return svmem( 

466 total, 

467 avail, 

468 percent, 

469 used, 

470 free, 

471 active, 

472 inactive, 

473 buffers, 

474 cached, 

475 shared, 

476 slab, 

477 ) 

478 

479 

480def swap_memory(): 

481 """Return swap memory metrics.""" 

482 mems = {} 

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

484 for line in f: 

485 fields = line.split() 

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

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

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

489 # for linux containers, see: 

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

491 try: 

492 total = mems[b'SwapTotal:'] 

493 free = mems[b'SwapFree:'] 

494 except KeyError: 

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

496 total *= unit_multiplier 

497 free *= unit_multiplier 

498 

499 used = total - free 

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

501 # get pgin/pgouts 

502 try: 

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

504 except OSError as err: 

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

506 msg = ( 

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

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

509 ) 

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

511 sin = sout = 0 

512 else: 

513 with f: 

514 sin = sout = None 

515 for line in f: 

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

517 # bytes instead 

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

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

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

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

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

523 break 

524 else: 

525 # we might get here when dealing with exotic Linux 

526 # flavors, see: 

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

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

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

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

531 sin = sout = 0 

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

533 

534 

535# ===================================================================== 

536# --- CPU 

537# ===================================================================== 

538 

539 

540def cpu_times(): 

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

542 CPU times: 

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

544 [guest_nice]]]) 

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

546 """ 

547 procfs_path = get_procfs_path() 

548 set_scputimes_ntuple(procfs_path) 

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

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

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

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

553 return scputimes(*fields) 

554 

555 

556def per_cpu_times(): 

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

558 for every CPU available on the system. 

559 """ 

560 procfs_path = get_procfs_path() 

561 set_scputimes_ntuple(procfs_path) 

562 cpus = [] 

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

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

565 f.readline() 

566 for line in f: 

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

568 values = line.split() 

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

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

571 entry = scputimes(*fields) 

572 cpus.append(entry) 

573 return cpus 

574 

575 

576def cpu_count_logical(): 

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

578 try: 

579 return os.sysconf("SC_NPROCESSORS_ONLN") 

580 except ValueError: 

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

582 num = 0 

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

584 for line in f: 

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

586 num += 1 

587 

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

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

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

591 if num == 0: 

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

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

594 for line in f: 

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

596 if search.match(line): 

597 num += 1 

598 

599 if num == 0: 

600 # mimic os.cpu_count() 

601 return None 

602 return num 

603 

604 

605def cpu_count_cores(): 

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

607 # Method #1 

608 ls = set() 

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

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

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

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

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

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

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

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

617 with open_binary(path) as f: 

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

619 result = len(ls) 

620 if result != 0: 

621 return result 

622 

623 # Method #2 

624 mapping = {} 

625 current_info = {} 

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

627 for line in f: 

628 line = line.strip().lower() 

629 if not line: 

630 # new section 

631 try: 

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

633 b'cpu cores' 

634 ] 

635 except KeyError: 

636 pass 

637 current_info = {} 

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

639 # ongoing section 

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

641 current_info[key] = int(value) 

642 

643 result = sum(mapping.values()) 

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

645 

646 

647def cpu_stats(): 

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

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

650 ctx_switches = None 

651 interrupts = None 

652 soft_interrupts = None 

653 for line in f: 

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

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

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

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

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

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

660 if ( 

661 ctx_switches is not None 

662 and soft_interrupts is not None 

663 and interrupts is not None 

664 ): 

665 break 

666 syscalls = 0 

667 return _common.scpustats( 

668 ctx_switches, interrupts, soft_interrupts, syscalls 

669 ) 

670 

671 

672def _cpu_get_cpuinfo_freq(): 

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

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

675 return [ 

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

677 for line in f 

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

679 ] 

680 

681 

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

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

684): 

685 

686 def cpu_freq(): 

687 """Return frequency metrics for all CPUs. 

688 Contrarily to other OSes, Linux updates these values in 

689 real-time. 

690 """ 

691 cpuinfo_freqs = _cpu_get_cpuinfo_freq() 

692 paths = glob.glob( 

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

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

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

696 ret = [] 

697 pjoin = os.path.join 

698 for i, path in enumerate(paths): 

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

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

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

702 curr = cpuinfo_freqs[i] * 1000 

703 else: 

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

705 if curr is None: 

706 # Likely an old RedHat, see: 

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

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

709 if curr is None: 

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

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

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

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

714 continue 

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

716 raise NotImplementedError(msg) 

717 curr = int(curr) / 1000 

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

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

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

721 return ret 

722 

723else: 

724 

725 def cpu_freq(): 

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

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

728 """ 

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

730 

731 

732# ===================================================================== 

733# --- network 

734# ===================================================================== 

735 

736 

737net_if_addrs = cext_posix.net_if_addrs 

738 

739 

740class _Ipv6UnsupportedError(Exception): 

741 pass 

742 

743 

744class NetConnections: 

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

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

747 "netstat -an". 

748 

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

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

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

752 

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

754 """ 

755 

756 def __init__(self): 

757 # The string represents the basename of the corresponding 

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

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

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

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

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

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

764 self.tmap = { 

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

766 "tcp": (tcp4, tcp6), 

767 "tcp4": (tcp4,), 

768 "tcp6": (tcp6,), 

769 "udp": (udp4, udp6), 

770 "udp4": (udp4,), 

771 "udp6": (udp6,), 

772 "unix": (unix,), 

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

774 "inet4": (tcp4, udp4), 

775 "inet6": (tcp6, udp6), 

776 } 

777 self._procfs_path = None 

778 

779 def get_proc_inodes(self, pid): 

780 inodes = defaultdict(list) 

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

782 try: 

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

784 except (FileNotFoundError, ProcessLookupError): 

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

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

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

788 continue 

789 except OSError as err: 

790 if err.errno == errno.EINVAL: 

791 # not a link 

792 continue 

793 if err.errno == errno.ENAMETOOLONG: 

794 # file name too long 

795 debug(err) 

796 continue 

797 raise 

798 else: 

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

800 # the process is using a socket 

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

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

803 return inodes 

804 

805 def get_all_inodes(self): 

806 inodes = {} 

807 for pid in pids(): 

808 try: 

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

810 except (FileNotFoundError, ProcessLookupError, PermissionError): 

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

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

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

814 # and fd set to None anyway. 

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

816 # unlikely we can do any better. 

817 # ENOENT just means a PID disappeared on us. 

818 continue 

819 return inodes 

820 

821 @staticmethod 

822 def decode_address(addr, family): 

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

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

825 

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

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

828 

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

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

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

832 to an IP address. 

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

834 

835 Reference: 

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

837 """ 

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

839 port = int(port, 16) 

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

841 # no end-points connected 

842 if not port: 

843 return () 

844 ip = ip.encode('ascii') 

845 if family == socket.AF_INET: 

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

847 if LITTLE_ENDIAN: 

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

849 else: 

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

851 else: # IPv6 

852 ip = base64.b16decode(ip) 

853 try: 

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

855 if LITTLE_ENDIAN: 

856 ip = socket.inet_ntop( 

857 socket.AF_INET6, 

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

859 ) 

860 else: 

861 ip = socket.inet_ntop( 

862 socket.AF_INET6, 

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

864 ) 

865 except ValueError: 

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

867 if not supports_ipv6(): 

868 raise _Ipv6UnsupportedError from None 

869 raise 

870 return _common.addr(ip, port) 

871 

872 @staticmethod 

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

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

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

876 # IPv6 not supported 

877 return 

878 with open_text(file) as f: 

879 f.readline() # skip the first line 

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

881 try: 

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

883 line.split()[:10] 

884 ) 

885 except ValueError: 

886 msg = ( 

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

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

889 ) 

890 raise RuntimeError(msg) from None 

891 if inode in inodes: 

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

893 # # out if there are multiple references to the 

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

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

896 # raise ValueError("ambiguous inode with multiple " 

897 # "PIDs references") 

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

899 else: 

900 pid, fd = None, -1 

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

902 continue 

903 else: 

904 if type_ == socket.SOCK_STREAM: 

905 status = TCP_STATUSES[status] 

906 else: 

907 status = _common.CONN_NONE 

908 try: 

909 laddr = NetConnections.decode_address(laddr, family) 

910 raddr = NetConnections.decode_address(raddr, family) 

911 except _Ipv6UnsupportedError: 

912 continue 

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

914 

915 @staticmethod 

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

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

918 with open_text(file) as f: 

919 f.readline() # skip the first line 

920 for line in f: 

921 tokens = line.split() 

922 try: 

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

924 except ValueError: 

925 if ' ' not in line: 

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

927 continue 

928 msg = ( 

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

930 ) 

931 raise RuntimeError(msg) # noqa: B904 

932 if inode in inodes: # noqa: SIM108 

933 # With UNIX sockets we can have a single inode 

934 # referencing many file descriptors. 

935 pairs = inodes[inode] 

936 else: 

937 pairs = [(None, -1)] 

938 for pid, fd in pairs: 

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

940 continue 

941 else: 

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

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

944 # XXX: determining the remote endpoint of a 

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

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

947 raddr = "" 

948 status = _common.CONN_NONE 

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

950 

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

952 self._procfs_path = get_procfs_path() 

953 if pid is not None: 

954 inodes = self.get_proc_inodes(pid) 

955 if not inodes: 

956 # no connections for this process 

957 return [] 

958 else: 

959 inodes = self.get_all_inodes() 

960 ret = set() 

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

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

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

964 ls = self.process_inet( 

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

966 ) 

967 else: 

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

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

970 if pid: 

971 conn = _common.pconn( 

972 fd, family, type_, laddr, raddr, status 

973 ) 

974 else: 

975 conn = _common.sconn( 

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

977 ) 

978 ret.add(conn) 

979 return list(ret) 

980 

981 

982_net_connections = NetConnections() 

983 

984 

985def net_connections(kind='inet'): 

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

987 return _net_connections.retrieve(kind) 

988 

989 

990def net_io_counters(): 

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

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

993 """ 

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

995 lines = f.readlines() 

996 retdict = {} 

997 for line in lines[2:]: 

998 colon = line.rfind(':') 

999 assert colon > 0, repr(line) 

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

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

1002 

1003 ( 

1004 # in 

1005 bytes_recv, 

1006 packets_recv, 

1007 errin, 

1008 dropin, 

1009 _fifoin, # unused 

1010 _framein, # unused 

1011 _compressedin, # unused 

1012 _multicastin, # unused 

1013 # out 

1014 bytes_sent, 

1015 packets_sent, 

1016 errout, 

1017 dropout, 

1018 _fifoout, # unused 

1019 _collisionsout, # unused 

1020 _carrierout, # unused 

1021 _compressedout, # unused 

1022 ) = map(int, fields) 

1023 

1024 retdict[name] = ( 

1025 bytes_sent, 

1026 bytes_recv, 

1027 packets_sent, 

1028 packets_recv, 

1029 errin, 

1030 errout, 

1031 dropin, 

1032 dropout, 

1033 ) 

1034 return retdict 

1035 

1036 

1037def net_if_stats(): 

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

1039 duplex_map = { 

1040 cext.DUPLEX_FULL: NIC_DUPLEX_FULL, 

1041 cext.DUPLEX_HALF: NIC_DUPLEX_HALF, 

1042 cext.DUPLEX_UNKNOWN: NIC_DUPLEX_UNKNOWN, 

1043 } 

1044 names = net_io_counters().keys() 

1045 ret = {} 

1046 for name in names: 

1047 try: 

1048 mtu = cext_posix.net_if_mtu(name) 

1049 flags = cext_posix.net_if_flags(name) 

1050 duplex, speed = cext.net_if_duplex_speed(name) 

1051 except OSError as err: 

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

1053 if err.errno != errno.ENODEV: 

1054 raise 

1055 debug(err) 

1056 else: 

1057 output_flags = ','.join(flags) 

1058 isup = 'running' in flags 

1059 ret[name] = _common.snicstats( 

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

1061 ) 

1062 return ret 

1063 

1064 

1065# ===================================================================== 

1066# --- disks 

1067# ===================================================================== 

1068 

1069 

1070disk_usage = _psposix.disk_usage 

1071 

1072 

1073def disk_io_counters(perdisk=False): 

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

1075 system as a dict of raw tuples. 

1076 """ 

1077 

1078 def read_procfs(): 

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

1080 # have 3 variations. 

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

1082 # "3 0 8 hda 8 8 8 8 8 8 8 8 8 8 8" 

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

1084 # name is in another position, like this: 

1085 # "3 0 hda 8 8 8 8 8 8 8 8 8 8 8" 

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

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

1088 # "3 1 hda1 8 8 8 8" 

1089 # 4.18+ has 4 fields added: 

1090 # "3 0 hda 8 8 8 8 8 8 8 8 8 8 8 0 0 0 0" 

1091 # 5.5 has 2 more fields. 

1092 # See: 

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

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

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

1096 lines = f.readlines() 

1097 for line in lines: 

1098 fields = line.split() 

1099 flen = len(fields) 

1100 # fmt: off 

1101 if flen == 15: 

1102 # Linux 2.4 

1103 name = fields[3] 

1104 reads = int(fields[2]) 

1105 (reads_merged, rbytes, rtime, writes, writes_merged, 

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

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

1108 # Linux 2.6+, line referring to a disk 

1109 name = fields[2] 

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

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

1112 elif flen == 7: 

1113 # Linux 2.6+, line referring to a partition 

1114 name = fields[2] 

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

1116 rtime = wtime = reads_merged = writes_merged = busy_time = 0 

1117 else: 

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

1119 raise ValueError(msg) 

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

1121 reads_merged, writes_merged, busy_time) 

1122 # fmt: on 

1123 

1124 def read_sysfs(): 

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

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

1127 if 'stat' not in files: 

1128 continue 

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

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

1131 name = os.path.basename(root) 

1132 # fmt: off 

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

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

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

1136 wtime, reads_merged, writes_merged, busy_time) 

1137 # fmt: on 

1138 

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

1140 gen = read_procfs() 

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

1142 gen = read_sysfs() 

1143 else: 

1144 msg = ( 

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

1146 " this system" 

1147 ) 

1148 raise NotImplementedError(msg) 

1149 

1150 retdict = {} 

1151 for entry in gen: 

1152 # fmt: off 

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

1154 writes_merged, busy_time) = entry 

1155 if not perdisk and not is_storage_device(name): 

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

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

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

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

1160 # of their own: 

1161 # $ cat /proc/diskstats 

1162 # 259 0 sda 10485760 ... 

1163 # 259 1 sda1 5186039 ... 

1164 # 259 1 sda2 5082039 ... 

1165 # See: 

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

1167 continue 

1168 

1169 rbytes *= DISK_SECTOR_SIZE 

1170 wbytes *= DISK_SECTOR_SIZE 

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

1172 reads_merged, writes_merged, busy_time) 

1173 # fmt: on 

1174 

1175 return retdict 

1176 

1177 

1178class RootFsDeviceFinder: 

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

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

1181 obtain the real device path. Resources: 

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

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

1184 """ 

1185 

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

1187 

1188 def __init__(self): 

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

1190 self.major = os.major(dev) 

1191 self.minor = os.minor(dev) 

1192 

1193 def ask_proc_partitions(self): 

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

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

1196 fields = line.split() 

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

1198 continue 

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

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

1201 name = fields[3] 

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

1203 if name: # just for extra safety 

1204 return f"/dev/{name}" 

1205 

1206 def ask_sys_dev_block(self): 

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

1208 with open_text(path) as f: 

1209 for line in f: 

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

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

1212 if name: # just for extra safety 

1213 return f"/dev/{name}" 

1214 

1215 def ask_sys_class_block(self): 

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

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

1218 for file in files: 

1219 try: 

1220 f = open_text(file) 

1221 except FileNotFoundError: # race condition 

1222 continue 

1223 else: 

1224 with f: 

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

1226 if data == needle: 

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

1228 return f"/dev/{name}" 

1229 

1230 def find(self): 

1231 path = None 

1232 if path is None: 

1233 try: 

1234 path = self.ask_proc_partitions() 

1235 except OSError as err: 

1236 debug(err) 

1237 if path is None: 

1238 try: 

1239 path = self.ask_sys_dev_block() 

1240 except OSError as err: 

1241 debug(err) 

1242 if path is None: 

1243 try: 

1244 path = self.ask_sys_class_block() 

1245 except OSError as err: 

1246 debug(err) 

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

1248 # coded, so we want to be sure. 

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

1250 return path 

1251 

1252 

1253def disk_partitions(all=False): 

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

1255 fstypes = set() 

1256 procfs_path = get_procfs_path() 

1257 if not all: 

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

1259 for line in f: 

1260 line = line.strip() 

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

1262 fstypes.add(line.strip()) 

1263 else: 

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

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

1266 if fstype == "zfs": 

1267 fstypes.add("zfs") 

1268 

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

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

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

1272 else: 

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

1274 

1275 retlist = [] 

1276 partitions = cext.disk_partitions(mounts_path) 

1277 for partition in partitions: 

1278 device, mountpoint, fstype, opts = partition 

1279 if device == 'none': 

1280 device = '' 

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

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

1283 if not all: 

1284 if not device or fstype not in fstypes: 

1285 continue 

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

1287 retlist.append(ntuple) 

1288 

1289 return retlist 

1290 

1291 

1292# ===================================================================== 

1293# --- sensors 

1294# ===================================================================== 

1295 

1296 

1297def sensors_temperatures(): 

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

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

1300 temperatures. 

1301 

1302 Implementation notes: 

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

1304 retrieve this info, and this implementation relies on it 

1305 only (old distros will probably use something else) 

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

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

1308 difficult to parse 

1309 """ 

1310 ret = collections.defaultdict(list) 

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

1312 # CentOS has an intermediate /device directory: 

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

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

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

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

1317 

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

1319 # /sys/class/hwmon/ 

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

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

1322 basenames2 = glob.glob( 

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

1324 ) 

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

1326 for name in basenames2: 

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

1328 if altname not in basenames: 

1329 basenames.append(name) 

1330 

1331 for base in basenames: 

1332 try: 

1333 path = base + '_input' 

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

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

1336 unit_name = cat(path).strip() 

1337 except (OSError, ValueError): 

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

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

1340 # is a stinky broken mess. 

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

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

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

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

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

1346 continue 

1347 

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

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

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

1351 

1352 if high is not None: 

1353 try: 

1354 high = float(high) / 1000.0 

1355 except ValueError: 

1356 high = None 

1357 if critical is not None: 

1358 try: 

1359 critical = float(critical) / 1000.0 

1360 except ValueError: 

1361 critical = None 

1362 

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

1364 

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

1366 if not basenames: 

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

1368 basenames = sorted(set(basenames)) 

1369 

1370 for base in basenames: 

1371 try: 

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

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

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

1375 unit_name = cat(path).strip() 

1376 except (OSError, ValueError) as err: 

1377 debug(err) 

1378 continue 

1379 

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

1381 trip_points = { 

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

1383 for p in trip_paths 

1384 } 

1385 critical = None 

1386 high = None 

1387 for trip_point in trip_points: 

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

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

1390 if trip_type == 'critical': 

1391 critical = bcat( 

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

1393 ) 

1394 elif trip_type == 'high': 

1395 high = bcat( 

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

1397 ) 

1398 

1399 if high is not None: 

1400 try: 

1401 high = float(high) / 1000.0 

1402 except ValueError: 

1403 high = None 

1404 if critical is not None: 

1405 try: 

1406 critical = float(critical) / 1000.0 

1407 except ValueError: 

1408 critical = None 

1409 

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

1411 

1412 return dict(ret) 

1413 

1414 

1415def sensors_fans(): 

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

1417 dict including hardware label and current speed. 

1418 

1419 Implementation notes: 

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

1421 retrieve this info, and this implementation relies on it 

1422 only (old distros will probably use something else) 

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

1424 """ 

1425 ret = collections.defaultdict(list) 

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

1427 if not basenames: 

1428 # CentOS has an intermediate /device directory: 

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

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

1431 

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

1433 for base in basenames: 

1434 try: 

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

1436 except OSError as err: 

1437 debug(err) 

1438 continue 

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

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

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

1442 

1443 return dict(ret) 

1444 

1445 

1446def sensors_battery(): 

1447 """Return battery information. 

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

1449 directory structure may vary and provide files with the same 

1450 meaning but under different names, see: 

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

1452 """ 

1453 null = object() 

1454 

1455 def multi_bcat(*paths): 

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

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

1458 """ 

1459 for path in paths: 

1460 ret = bcat(path, fallback=null) 

1461 if ret != null: 

1462 try: 

1463 return int(ret) 

1464 except ValueError: 

1465 return ret.strip() 

1466 return None 

1467 

1468 bats = [ 

1469 x 

1470 for x in os.listdir(POWER_SUPPLY_PATH) 

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

1472 ] 

1473 if not bats: 

1474 return None 

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

1476 # some rare exceptions: 

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

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

1479 

1480 # Base metrics. 

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

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

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

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

1485 

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

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

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

1489 try: 

1490 percent = 100.0 * energy_now / energy_full 

1491 except ZeroDivisionError: 

1492 percent = 0.0 

1493 else: 

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

1495 if percent == -1: 

1496 return None 

1497 

1498 # Is AC power cable plugged in? 

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

1500 # it's called "AC". 

1501 power_plugged = None 

1502 online = multi_bcat( 

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

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

1505 ) 

1506 if online is not None: 

1507 power_plugged = online == 1 

1508 else: 

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

1510 if status == "discharging": 

1511 power_plugged = False 

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

1513 power_plugged = True 

1514 

1515 # Seconds left. 

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

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

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

1519 if power_plugged: 

1520 secsleft = _common.POWER_TIME_UNLIMITED 

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

1522 try: 

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

1524 except ZeroDivisionError: 

1525 secsleft = _common.POWER_TIME_UNKNOWN 

1526 elif time_to_empty is not None: 

1527 secsleft = int(time_to_empty * 60) 

1528 if secsleft < 0: 

1529 secsleft = _common.POWER_TIME_UNKNOWN 

1530 else: 

1531 secsleft = _common.POWER_TIME_UNKNOWN 

1532 

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

1534 

1535 

1536# ===================================================================== 

1537# --- other system functions 

1538# ===================================================================== 

1539 

1540 

1541def users(): 

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

1543 retlist = [] 

1544 rawlist = cext_posix.users() 

1545 for item in rawlist: 

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

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

1548 retlist.append(nt) 

1549 return retlist 

1550 

1551 

1552def boot_time(): 

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

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

1555 with open_binary(path) as f: 

1556 for line in f: 

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

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

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

1560 raise RuntimeError(msg) 

1561 

1562 

1563# ===================================================================== 

1564# --- processes 

1565# ===================================================================== 

1566 

1567 

1568def pids(): 

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

1570 path = get_procfs_path().encode(ENCODING) 

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

1572 

1573 

1574def pid_exists(pid): 

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

1576 supported (always return False). 

1577 """ 

1578 if not _psposix.pid_exists(pid): 

1579 return False 

1580 else: 

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

1582 # (thread IDs). 

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

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

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

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

1587 # only, see: 

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

1589 try: 

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

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

1592 # 'return pid in pids()' 

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

1594 with open_binary(path) as f: 

1595 for line in f: 

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

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

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

1599 # dealing with a process PID. 

1600 return tgid == pid 

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

1602 raise ValueError(msg) 

1603 except (OSError, ValueError): 

1604 return pid in pids() 

1605 

1606 

1607def ppid_map(): 

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

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

1610 """ 

1611 ret = {} 

1612 procfs_path = get_procfs_path() 

1613 for pid in pids(): 

1614 try: 

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

1616 data = f.read() 

1617 except (FileNotFoundError, ProcessLookupError): 

1618 pass 

1619 except PermissionError as err: 

1620 raise AccessDenied(pid) from err 

1621 else: 

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

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

1624 ppid = int(dset[1]) 

1625 ret[pid] = ppid 

1626 return ret 

1627 

1628 

1629def wrap_exceptions(fun): 

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

1631 into NoSuchProcess and AccessDenied. 

1632 """ 

1633 

1634 @functools.wraps(fun) 

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

1636 pid, name = self.pid, self._name 

1637 try: 

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

1639 except PermissionError as err: 

1640 raise AccessDenied(pid, name) from err 

1641 except ProcessLookupError as err: 

1642 self._raise_if_zombie() 

1643 raise NoSuchProcess(pid, name) from err 

1644 except FileNotFoundError as err: 

1645 self._raise_if_zombie() 

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

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

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

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

1650 raise NoSuchProcess(pid, name) from err 

1651 raise 

1652 

1653 return wrapper 

1654 

1655 

1656class Process: 

1657 """Linux process implementation.""" 

1658 

1659 __slots__ = [ 

1660 "_cache", 

1661 "_ctime", 

1662 "_name", 

1663 "_ppid", 

1664 "_procfs_path", 

1665 "pid", 

1666 ] 

1667 

1668 def __init__(self, pid): 

1669 self.pid = pid 

1670 self._name = None 

1671 self._ppid = None 

1672 self._ctime = None 

1673 self._procfs_path = get_procfs_path() 

1674 

1675 def _is_zombie(self): 

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

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

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

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

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

1681 # exception. 

1682 try: 

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

1684 except OSError: 

1685 return False 

1686 else: 

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

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

1689 return status == b"Z" 

1690 

1691 def _raise_if_zombie(self): 

1692 if self._is_zombie(): 

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

1694 

1695 def _raise_if_not_alive(self): 

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

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

1698 # incorrect or incomplete result. 

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

1700 

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

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

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

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

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

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

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

1708 try: 

1709 return readlink(path) 

1710 except (FileNotFoundError, ProcessLookupError): 

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

1712 self._raise_if_zombie() 

1713 if fallback is not UNSET: 

1714 return fallback 

1715 raise 

1716 

1717 @wrap_exceptions 

1718 @memoize_when_activated 

1719 def _parse_stat_file(self): 

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

1721 process info. 

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

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

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

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

1726 in use. 

1727 """ 

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

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

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

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

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

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

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

1735 

1736 ret = {} 

1737 ret['name'] = name 

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

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

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

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

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

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

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

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

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

1747 try: 

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

1749 except IndexError: 

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

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

1752 ret['blkio_ticks'] = 0 

1753 

1754 return ret 

1755 

1756 @wrap_exceptions 

1757 @memoize_when_activated 

1758 def _read_status_file(self): 

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

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

1761 in use. 

1762 """ 

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

1764 return f.read() 

1765 

1766 @wrap_exceptions 

1767 @memoize_when_activated 

1768 def _read_smaps_file(self): 

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

1770 return f.read().strip() 

1771 

1772 def oneshot_enter(self): 

1773 self._parse_stat_file.cache_activate(self) 

1774 self._read_status_file.cache_activate(self) 

1775 self._read_smaps_file.cache_activate(self) 

1776 

1777 def oneshot_exit(self): 

1778 self._parse_stat_file.cache_deactivate(self) 

1779 self._read_status_file.cache_deactivate(self) 

1780 self._read_smaps_file.cache_deactivate(self) 

1781 

1782 @wrap_exceptions 

1783 def name(self): 

1784 # XXX - gets changed later and probably needs refactoring 

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

1786 

1787 @wrap_exceptions 

1788 def exe(self): 

1789 return self._readlink( 

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

1791 ) 

1792 

1793 @wrap_exceptions 

1794 def cmdline(self): 

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

1796 data = f.read() 

1797 if not data: 

1798 # may happen in case of zombie process 

1799 self._raise_if_zombie() 

1800 return [] 

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

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

1803 # some processes may change their cmdline after being started 

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

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

1806 # Chrome process is an example. See: 

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

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

1809 if data.endswith(sep): 

1810 data = data[:-1] 

1811 cmdline = data.split(sep) 

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

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

1814 # issues/1179#issuecomment-552984549 

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

1816 cmdline = data.split(' ') 

1817 return cmdline 

1818 

1819 @wrap_exceptions 

1820 def environ(self): 

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

1822 data = f.read() 

1823 return parse_environ_block(data) 

1824 

1825 @wrap_exceptions 

1826 def terminal(self): 

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

1828 tmap = _psposix.get_terminal_map() 

1829 try: 

1830 return tmap[tty_nr] 

1831 except KeyError: 

1832 return None 

1833 

1834 # May not be available on old kernels. 

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

1836 

1837 @wrap_exceptions 

1838 def io_counters(self): 

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

1840 fields = {} 

1841 with open_binary(fname) as f: 

1842 for line in f: 

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

1844 line = line.strip() 

1845 if line: 

1846 try: 

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

1848 except ValueError: 

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

1850 continue 

1851 else: 

1852 fields[name] = int(value) 

1853 if not fields: 

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

1855 raise RuntimeError(msg) 

1856 try: 

1857 return pio( 

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

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

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

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

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

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

1864 ) 

1865 except KeyError as err: 

1866 msg = ( 

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

1868 f" fields are {fields!r}" 

1869 ) 

1870 raise ValueError(msg) from None 

1871 

1872 @wrap_exceptions 

1873 def cpu_times(self): 

1874 values = self._parse_stat_file() 

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

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

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

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

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

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

1881 

1882 @wrap_exceptions 

1883 def cpu_num(self): 

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

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

1886 

1887 @wrap_exceptions 

1888 def wait(self, timeout=None): 

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

1890 

1891 @wrap_exceptions 

1892 def create_time(self, monotonic=False): 

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

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

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

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

1897 # changes and is unaffected by system clock updates. 

1898 if self._ctime is None: 

1899 self._ctime = ( 

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

1901 ) 

1902 if monotonic: 

1903 return self._ctime 

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

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

1906 return self._ctime + boot_time() 

1907 

1908 @wrap_exceptions 

1909 def memory_info(self): 

1910 # ============================================================ 

1911 # | FIELD | DESCRIPTION | AKA | TOP | 

1912 # ============================================================ 

1913 # | rss | resident set size | | RES | 

1914 # | vms | total program size | size | VIRT | 

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

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

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

1918 # | data | data + stack | drs | DATA | 

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

1920 # ============================================================ 

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

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

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

1924 ) 

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

1926 

1927 if HAS_PROC_SMAPS_ROLLUP or HAS_PROC_SMAPS: 

1928 

1929 def _parse_smaps_rollup(self): 

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

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

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

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

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

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

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

1937 # compared to /proc/pid/smaps_rollup. 

1938 uss = pss = swap = 0 

1939 with open_binary( 

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

1941 ) as f: 

1942 for line in f: 

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

1944 # Private_Clean, Private_Dirty, Private_Hugetlb 

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

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

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

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

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

1950 return (uss, pss, swap) 

1951 

1952 @wrap_exceptions 

1953 def _parse_smaps( 

1954 self, 

1955 # Gets Private_Clean, Private_Dirty, Private_Hugetlb. 

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

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

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

1959 ): 

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

1961 # CONFIG_MMU kernel configuration option is not enabled. 

1962 

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

1964 # line by line. 

1965 # 

1966 # You might be tempted to calculate USS by subtracting 

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

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

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

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

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

1972 # correct information. 

1973 smaps_data = self._read_smaps_file() 

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

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

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

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

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

1979 return (uss, pss, swap) 

1980 

1981 @wrap_exceptions 

1982 def memory_full_info(self): 

1983 if HAS_PROC_SMAPS_ROLLUP: # faster 

1984 try: 

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

1986 except (ProcessLookupError, FileNotFoundError): 

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

1988 else: 

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

1990 basic_mem = self.memory_info() 

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

1992 

1993 else: 

1994 memory_full_info = memory_info 

1995 

1996 if HAS_PROC_SMAPS: 

1997 

1998 @wrap_exceptions 

1999 def memory_maps(self): 

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

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

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

2003 

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

2005 CONFIG_MMU kernel configuration option is not enabled. 

2006 """ 

2007 

2008 def get_blocks(lines, current_block): 

2009 data = {} 

2010 for line in lines: 

2011 fields = line.split(None, 5) 

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

2013 # new block section 

2014 yield (current_block.pop(), data) 

2015 current_block.append(line) 

2016 else: 

2017 try: 

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

2019 except (ValueError, IndexError): 

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

2021 # see issue #369 

2022 continue 

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

2024 raise ValueError(msg) from None 

2025 yield (current_block.pop(), data) 

2026 

2027 data = self._read_smaps_file() 

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

2029 # zombies. 

2030 if not data: 

2031 self._raise_if_zombie() 

2032 return [] 

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

2034 ls = [] 

2035 first_line = lines.pop(0) 

2036 current_block = [first_line] 

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

2038 hfields = header.split(None, 5) 

2039 try: 

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

2041 except ValueError: 

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

2043 if not path: 

2044 path = '[anon]' 

2045 else: 

2046 path = decode(path) 

2047 path = path.strip() 

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

2049 path 

2050 ): 

2051 path = path[:-10] 

2052 item = ( 

2053 decode(addr), 

2054 decode(perms), 

2055 path, 

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

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

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

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

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

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

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

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

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

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

2066 ) 

2067 ls.append(item) 

2068 return ls 

2069 

2070 @wrap_exceptions 

2071 def cwd(self): 

2072 return self._readlink( 

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

2074 ) 

2075 

2076 @wrap_exceptions 

2077 def num_ctx_switches( 

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

2079 ): 

2080 data = self._read_status_file() 

2081 ctxsw = _ctxsw_re.findall(data) 

2082 if not ctxsw: 

2083 msg = ( 

2084 "'voluntary_ctxt_switches' and" 

2085 " 'nonvoluntary_ctxt_switches'lines were not found in" 

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

2087 " probably older than 2.6.23" 

2088 ) 

2089 raise NotImplementedError(msg) 

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

2091 

2092 @wrap_exceptions 

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

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

2095 data = self._read_status_file() 

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

2097 

2098 @wrap_exceptions 

2099 def threads(self): 

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

2101 thread_ids.sort() 

2102 retlist = [] 

2103 hit_enoent = False 

2104 for thread_id in thread_ids: 

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

2106 try: 

2107 with open_binary(fname) as f: 

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

2109 except (FileNotFoundError, ProcessLookupError): 

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

2111 # it means thread disappeared on us 

2112 hit_enoent = True 

2113 continue 

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

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

2116 values = st.split(b' ') 

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

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

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

2120 retlist.append(ntuple) 

2121 if hit_enoent: 

2122 self._raise_if_not_alive() 

2123 return retlist 

2124 

2125 @wrap_exceptions 

2126 def nice_get(self): 

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

2128 # data = f.read() 

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

2130 

2131 # Use C implementation 

2132 return cext_posix.getpriority(self.pid) 

2133 

2134 @wrap_exceptions 

2135 def nice_set(self, value): 

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

2137 

2138 # starting from CentOS 6. 

2139 if HAS_CPU_AFFINITY: 

2140 

2141 @wrap_exceptions 

2142 def cpu_affinity_get(self): 

2143 return cext.proc_cpu_affinity_get(self.pid) 

2144 

2145 def _get_eligible_cpus( 

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

2147 ): 

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

2149 data = self._read_status_file() 

2150 match = _re.findall(data) 

2151 if match: 

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

2153 else: 

2154 return list(range(len(per_cpu_times()))) 

2155 

2156 @wrap_exceptions 

2157 def cpu_affinity_set(self, cpus): 

2158 try: 

2159 cext.proc_cpu_affinity_set(self.pid, cpus) 

2160 except (OSError, ValueError) as err: 

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

2162 eligible_cpus = self._get_eligible_cpus() 

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

2164 for cpu in cpus: 

2165 if cpu not in all_cpus: 

2166 msg = ( 

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

2168 f" {eligible_cpus!r}" 

2169 ) 

2170 raise ValueError(msg) from None 

2171 if cpu not in eligible_cpus: 

2172 msg = ( 

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

2174 f" between {eligible_cpus}" 

2175 ) 

2176 raise ValueError(msg) from err 

2177 raise 

2178 

2179 # only starting from kernel 2.6.13 

2180 if HAS_PROC_IO_PRIORITY: 

2181 

2182 @wrap_exceptions 

2183 def ionice_get(self): 

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

2185 ioclass = IOPriority(ioclass) 

2186 return _common.pionice(ioclass, value) 

2187 

2188 @wrap_exceptions 

2189 def ionice_set(self, ioclass, value): 

2190 if value is None: 

2191 value = 0 

2192 if value and ioclass in { 

2193 IOPriority.IOPRIO_CLASS_IDLE, 

2194 IOPriority.IOPRIO_CLASS_NONE, 

2195 }: 

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

2197 raise ValueError(msg) 

2198 if value < 0 or value > 7: 

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

2200 raise ValueError(msg) 

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

2202 

2203 if hasattr(resource, "prlimit"): 

2204 

2205 @wrap_exceptions 

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

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

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

2209 # PID 0 is not supported on Linux. 

2210 if self.pid == 0: 

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

2212 raise ValueError(msg) 

2213 try: 

2214 if limits is None: 

2215 # get 

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

2217 else: 

2218 # set 

2219 if len(limits) != 2: 

2220 msg = ( 

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

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

2223 ) 

2224 raise ValueError(msg) 

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

2226 except OSError as err: 

2227 if err.errno == errno.ENOSYS: 

2228 # I saw this happening on Travis: 

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

2230 self._raise_if_zombie() 

2231 raise 

2232 

2233 @wrap_exceptions 

2234 def status(self): 

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

2236 letter = letter.decode() 

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

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

2239 

2240 @wrap_exceptions 

2241 def open_files(self): 

2242 retlist = [] 

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

2244 hit_enoent = False 

2245 for fd in files: 

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

2247 try: 

2248 path = readlink(file) 

2249 except (FileNotFoundError, ProcessLookupError): 

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

2251 hit_enoent = True 

2252 continue 

2253 except OSError as err: 

2254 if err.errno == errno.EINVAL: 

2255 # not a link 

2256 continue 

2257 if err.errno == errno.ENAMETOOLONG: 

2258 # file name too long 

2259 debug(err) 

2260 continue 

2261 raise 

2262 else: 

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

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

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

2266 # absolute path though. 

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

2268 # Get file position and flags. 

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

2270 try: 

2271 with open_binary(file) as f: 

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

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

2274 except (FileNotFoundError, ProcessLookupError): 

2275 # fd gone in the meantime; process may 

2276 # still be alive 

2277 hit_enoent = True 

2278 else: 

2279 mode = file_flags_to_mode(flags) 

2280 ntuple = popenfile( 

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

2282 ) 

2283 retlist.append(ntuple) 

2284 if hit_enoent: 

2285 self._raise_if_not_alive() 

2286 return retlist 

2287 

2288 @wrap_exceptions 

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

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

2291 self._raise_if_not_alive() 

2292 return ret 

2293 

2294 @wrap_exceptions 

2295 def num_fds(self): 

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

2297 

2298 @wrap_exceptions 

2299 def ppid(self): 

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

2301 

2302 @wrap_exceptions 

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

2304 data = self._read_status_file() 

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

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

2307 

2308 @wrap_exceptions 

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

2310 data = self._read_status_file() 

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

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