Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/psutil/_pslinux.py: 21%

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

1227 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 

51# fmt: off 

52__extra__all__ = [ 

53 'PROCFS_PATH', 

54 # io prio constants 

55 "IOPRIO_CLASS_NONE", "IOPRIO_CLASS_RT", "IOPRIO_CLASS_BE", 

56 "IOPRIO_CLASS_IDLE", 

57 # connection status constants 

58 "CONN_ESTABLISHED", "CONN_SYN_SENT", "CONN_SYN_RECV", "CONN_FIN_WAIT1", 

59 "CONN_FIN_WAIT2", "CONN_TIME_WAIT", "CONN_CLOSE", "CONN_CLOSE_WAIT", 

60 "CONN_LAST_ACK", "CONN_LISTEN", "CONN_CLOSING", 

61] 

62 

63if hasattr(resource, "prlimit"): 

64 __extra__all__.extend( 

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

66 ) 

67# fmt: on 

68 

69 

70# ===================================================================== 

71# --- globals 

72# ===================================================================== 

73 

74 

75POWER_SUPPLY_PATH = "/sys/class/power_supply" 

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

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

78HAS_PROC_IO_PRIORITY = hasattr(cext, "proc_ioprio_get") 

79HAS_CPU_AFFINITY = hasattr(cext, "proc_cpu_affinity_get") 

80 

81# Number of clock ticks per second 

82CLOCK_TICKS = os.sysconf("SC_CLK_TCK") 

83PAGESIZE = cext_posix.getpagesize() 

84BOOT_TIME = None # set later 

85LITTLE_ENDIAN = sys.byteorder == 'little' 

86 

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

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

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

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

91# throughout Linux source code: 

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

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

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

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

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

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

98DISK_SECTOR_SIZE = 512 

99 

100AddressFamily = enum.IntEnum( 

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

102) 

103AF_LINK = AddressFamily.AF_LINK 

104 

105 

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

107class IOPriority(enum.IntEnum): 

108 IOPRIO_CLASS_NONE = 0 

109 IOPRIO_CLASS_RT = 1 

110 IOPRIO_CLASS_BE = 2 

111 IOPRIO_CLASS_IDLE = 3 

112 

113 

114globals().update(IOPriority.__members__) 

115 

116# See: 

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

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

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

120PROC_STATUSES = { 

121 "R": _common.STATUS_RUNNING, 

122 "S": _common.STATUS_SLEEPING, 

123 "D": _common.STATUS_DISK_SLEEP, 

124 "T": _common.STATUS_STOPPED, 

125 "t": _common.STATUS_TRACING_STOP, 

126 "Z": _common.STATUS_ZOMBIE, 

127 "X": _common.STATUS_DEAD, 

128 "x": _common.STATUS_DEAD, 

129 "K": _common.STATUS_WAKE_KILL, 

130 "W": _common.STATUS_WAKING, 

131 "I": _common.STATUS_IDLE, 

132 "P": _common.STATUS_PARKED, 

133} 

134 

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

136TCP_STATUSES = { 

137 "01": _common.CONN_ESTABLISHED, 

138 "02": _common.CONN_SYN_SENT, 

139 "03": _common.CONN_SYN_RECV, 

140 "04": _common.CONN_FIN_WAIT1, 

141 "05": _common.CONN_FIN_WAIT2, 

142 "06": _common.CONN_TIME_WAIT, 

143 "07": _common.CONN_CLOSE, 

144 "08": _common.CONN_CLOSE_WAIT, 

145 "09": _common.CONN_LAST_ACK, 

146 "0A": _common.CONN_LISTEN, 

147 "0B": _common.CONN_CLOSING, 

148} 

149 

150 

151# ===================================================================== 

152# --- named tuples 

153# ===================================================================== 

154 

155 

156# fmt: off 

157# psutil.virtual_memory() 

158svmem = namedtuple( 

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

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

161# psutil.disk_io_counters() 

162sdiskio = namedtuple( 

163 'sdiskio', ['read_count', 'write_count', 

164 'read_bytes', 'write_bytes', 

165 'read_time', 'write_time', 

166 'read_merged_count', 'write_merged_count', 

167 'busy_time']) 

168# psutil.Process().open_files() 

169popenfile = namedtuple( 

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

171# psutil.Process().memory_info() 

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

173# psutil.Process().memory_full_info() 

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

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

176pmmap_grouped = namedtuple( 

177 'pmmap_grouped', 

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

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

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

181pmmap_ext = namedtuple( 

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

183# psutil.Process.io_counters() 

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

185 'read_bytes', 'write_bytes', 

186 'read_chars', 'write_chars']) 

187# psutil.Process.cpu_times() 

188pcputimes = namedtuple('pcputimes', 

189 ['user', 'system', 'children_user', 'children_system', 

190 'iowait']) 

191# fmt: on 

192 

193 

194# ===================================================================== 

195# --- utils 

196# ===================================================================== 

197 

198 

199def readlink(path): 

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

201 assert isinstance(path, str), path 

202 path = os.readlink(path) 

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

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

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

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

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

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

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

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

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

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

213 # don't care. 

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

215 path = path[:-10] 

216 return path 

217 

218 

219def file_flags_to_mode(flags): 

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

221 Used by Process.open_files(). 

222 """ 

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

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

225 if flags & os.O_APPEND: 

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

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

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

229 return mode 

230 

231 

232def is_storage_device(name): 

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

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

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

236 return True. 

237 """ 

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

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

240 # 97912938cd476645b267280069e83b1c8dc0e1c7/common.c#L208 

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

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

243 including_virtual = True 

244 if including_virtual: 

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

246 else: 

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

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

249 

250 

251@memoize 

252def set_scputimes_ntuple(procfs_path): 

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

254 available on this Linux kernel version which may be: 

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

256 [guest_nice]]]) 

257 Used by cpu_times() function. 

258 """ 

259 global scputimes 

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

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

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

263 vlen = len(values) 

264 if vlen >= 8: 

265 # Linux >= 2.6.11 

266 fields.append('steal') 

267 if vlen >= 9: 

268 # Linux >= 2.6.24 

269 fields.append('guest') 

270 if vlen >= 10: 

271 # Linux >= 3.2.0 

272 fields.append('guest_nice') 

273 scputimes = namedtuple('scputimes', fields) 

274 

275 

276try: 

277 set_scputimes_ntuple("/proc") 

278except Exception as err: # noqa: BLE001 

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

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

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

282 

283 

284# ===================================================================== 

285# --- system memory 

286# ===================================================================== 

287 

288 

289def calculate_avail_vmem(mems): 

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

291 "MemAvailable", see: 

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

293 

294 This code reimplements the algorithm outlined here: 

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

296 commit/?id=34e431b0ae398fc54ea69ff85ec700722c9da773 

297 

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

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

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

301 column). 

302 

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

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

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

306 See: 

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

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

309 """ 

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

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

312 # commit/?id=34e431b0ae398fc54ea69ff85ec700722c9da773 

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

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

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

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

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

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

319 free = mems[b'MemFree:'] 

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

321 try: 

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

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

324 slab_reclaimable = mems[b'SReclaimable:'] 

325 except KeyError as err: 

326 debug( 

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

328 " approximation for calculating available memory" 

329 ) 

330 return fallback 

331 try: 

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

333 except OSError: 

334 return fallback # kernel 2.6.13 

335 

336 watermark_low = 0 

337 with f: 

338 for line in f: 

339 line = line.strip() 

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

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

342 watermark_low *= PAGESIZE 

343 

344 avail = free - watermark_low 

345 pagecache = lru_active_file + lru_inactive_file 

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

347 avail += pagecache 

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

349 return int(avail) 

350 

351 

352def virtual_memory(): 

353 """Report virtual memory stats. 

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

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

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

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

358 CLI tools. 

359 """ 

360 missing_fields = [] 

361 mems = {} 

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

363 for line in f: 

364 fields = line.split() 

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

366 

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

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

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

370 total = mems[b'MemTotal:'] 

371 free = mems[b'MemFree:'] 

372 try: 

373 buffers = mems[b'Buffers:'] 

374 except KeyError: 

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

376 buffers = 0 

377 missing_fields.append('buffers') 

378 try: 

379 cached = mems[b"Cached:"] 

380 except KeyError: 

381 cached = 0 

382 missing_fields.append('cached') 

383 else: 

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

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

386 # This got changed in: 

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

388 # 05d751c4f076a2f0118b914c5e51cfbb4762ad8e 

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

390 

391 try: 

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

393 except KeyError: 

394 try: 

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

396 except KeyError: 

397 shared = 0 

398 missing_fields.append('shared') 

399 

400 try: 

401 active = mems[b"Active:"] 

402 except KeyError: 

403 active = 0 

404 missing_fields.append('active') 

405 

406 try: 

407 inactive = mems[b"Inactive:"] 

408 except KeyError: 

409 try: 

410 inactive = ( 

411 mems[b"Inact_dirty:"] 

412 + mems[b"Inact_clean:"] 

413 + mems[b"Inact_laundry:"] 

414 ) 

415 except KeyError: 

416 inactive = 0 

417 missing_fields.append('inactive') 

418 

419 try: 

420 slab = mems[b"Slab:"] 

421 except KeyError: 

422 slab = 0 

423 

424 used = total - free - cached - buffers 

425 if used < 0: 

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

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

428 used = total - free 

429 

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

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

432 # which matched htop. 

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

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

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

436 # - MemAvailable has been introduced in kernel 3.14 

437 try: 

438 avail = mems[b'MemAvailable:'] 

439 except KeyError: 

440 avail = calculate_avail_vmem(mems) 

441 else: 

442 if avail == 0: 

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

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

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

446 # and it matches "free" CLI tool. 

447 avail = calculate_avail_vmem(mems) 

448 

449 if avail < 0: 

450 avail = 0 

451 missing_fields.append('available') 

452 elif avail > total: 

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

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

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

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

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

458 avail = free 

459 

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

461 

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

463 if missing_fields: 

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

465 ", ".join(missing_fields), 

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

467 ) 

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

469 

470 return svmem( 

471 total, 

472 avail, 

473 percent, 

474 used, 

475 free, 

476 active, 

477 inactive, 

478 buffers, 

479 cached, 

480 shared, 

481 slab, 

482 ) 

483 

484 

485def swap_memory(): 

486 """Return swap memory metrics.""" 

487 mems = {} 

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

489 for line in f: 

490 fields = line.split() 

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

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

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

494 # for linux containers, see: 

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

496 try: 

497 total = mems[b'SwapTotal:'] 

498 free = mems[b'SwapFree:'] 

499 except KeyError: 

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

501 total *= unit_multiplier 

502 free *= unit_multiplier 

503 

504 used = total - free 

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

506 # get pgin/pgouts 

507 try: 

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

509 except OSError as err: 

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

511 msg = ( 

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

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

514 ) 

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

516 sin = sout = 0 

517 else: 

518 with f: 

519 sin = sout = None 

520 for line in f: 

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

522 # bytes instead 

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

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

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

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

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

528 break 

529 else: 

530 # we might get here when dealing with exotic Linux 

531 # flavors, see: 

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

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

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

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

536 sin = sout = 0 

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

538 

539 

540# ===================================================================== 

541# --- CPU 

542# ===================================================================== 

543 

544 

545def cpu_times(): 

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

547 CPU times: 

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

549 [guest_nice]]]) 

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

551 """ 

552 procfs_path = get_procfs_path() 

553 set_scputimes_ntuple(procfs_path) 

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

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

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

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

558 return scputimes(*fields) 

559 

560 

561def per_cpu_times(): 

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

563 for every CPU available on the system. 

564 """ 

565 procfs_path = get_procfs_path() 

566 set_scputimes_ntuple(procfs_path) 

567 cpus = [] 

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

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

570 f.readline() 

571 for line in f: 

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

573 values = line.split() 

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

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

576 entry = scputimes(*fields) 

577 cpus.append(entry) 

578 return cpus 

579 

580 

581def cpu_count_logical(): 

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

583 try: 

584 return os.sysconf("SC_NPROCESSORS_ONLN") 

585 except ValueError: 

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

587 num = 0 

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

589 for line in f: 

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

591 num += 1 

592 

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

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

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

596 if num == 0: 

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

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

599 for line in f: 

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

601 if search.match(line): 

602 num += 1 

603 

604 if num == 0: 

605 # mimic os.cpu_count() 

606 return None 

607 return num 

608 

609 

610def cpu_count_cores(): 

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

612 # Method #1 

613 ls = set() 

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

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

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

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

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

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

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

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

622 with open_binary(path) as f: 

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

624 result = len(ls) 

625 if result != 0: 

626 return result 

627 

628 # Method #2 

629 mapping = {} 

630 current_info = {} 

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

632 for line in f: 

633 line = line.strip().lower() 

634 if not line: 

635 # new section 

636 try: 

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

638 b'cpu cores' 

639 ] 

640 except KeyError: 

641 pass 

642 current_info = {} 

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

644 # ongoing section 

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

646 current_info[key] = int(value) 

647 

648 result = sum(mapping.values()) 

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

650 

651 

652def cpu_stats(): 

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

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

655 ctx_switches = None 

656 interrupts = None 

657 soft_interrupts = None 

658 for line in f: 

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

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

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

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

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

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

665 if ( 

666 ctx_switches is not None 

667 and soft_interrupts is not None 

668 and interrupts is not None 

669 ): 

670 break 

671 syscalls = 0 

672 return _common.scpustats( 

673 ctx_switches, interrupts, soft_interrupts, syscalls 

674 ) 

675 

676 

677def _cpu_get_cpuinfo_freq(): 

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

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

680 return [ 

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

682 for line in f 

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

684 ] 

685 

686 

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

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

689): 

690 

691 def cpu_freq(): 

692 """Return frequency metrics for all CPUs. 

693 Contrarily to other OSes, Linux updates these values in 

694 real-time. 

695 """ 

696 cpuinfo_freqs = _cpu_get_cpuinfo_freq() 

697 paths = glob.glob( 

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

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

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

701 ret = [] 

702 pjoin = os.path.join 

703 for i, path in enumerate(paths): 

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

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

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

707 curr = cpuinfo_freqs[i] * 1000 

708 else: 

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

710 if curr is None: 

711 # Likely an old RedHat, see: 

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

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

714 if curr is None: 

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

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

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

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

719 continue 

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

721 raise NotImplementedError(msg) 

722 curr = int(curr) / 1000 

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

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

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

726 return ret 

727 

728else: 

729 

730 def cpu_freq(): 

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

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

733 """ 

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

735 

736 

737# ===================================================================== 

738# --- network 

739# ===================================================================== 

740 

741 

742net_if_addrs = cext_posix.net_if_addrs 

743 

744 

745class _Ipv6UnsupportedError(Exception): 

746 pass 

747 

748 

749class NetConnections: 

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

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

752 "netstat -an". 

753 

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

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

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

757 

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

759 """ 

760 

761 def __init__(self): 

762 # The string represents the basename of the corresponding 

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

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

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

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

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

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

769 self.tmap = { 

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

771 "tcp": (tcp4, tcp6), 

772 "tcp4": (tcp4,), 

773 "tcp6": (tcp6,), 

774 "udp": (udp4, udp6), 

775 "udp4": (udp4,), 

776 "udp6": (udp6,), 

777 "unix": (unix,), 

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

779 "inet4": (tcp4, udp4), 

780 "inet6": (tcp6, udp6), 

781 } 

782 self._procfs_path = None 

783 

784 def get_proc_inodes(self, pid): 

785 inodes = defaultdict(list) 

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

787 try: 

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

789 except (FileNotFoundError, ProcessLookupError): 

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

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

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

793 continue 

794 except OSError as err: 

795 if err.errno == errno.EINVAL: 

796 # not a link 

797 continue 

798 if err.errno == errno.ENAMETOOLONG: 

799 # file name too long 

800 debug(err) 

801 continue 

802 raise 

803 else: 

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

805 # the process is using a socket 

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

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

808 return inodes 

809 

810 def get_all_inodes(self): 

811 inodes = {} 

812 for pid in pids(): 

813 try: 

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

815 except (FileNotFoundError, ProcessLookupError, PermissionError): 

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

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

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

819 # and fd set to None anyway. 

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

821 # unlikely we can do any better. 

822 # ENOENT just means a PID disappeared on us. 

823 continue 

824 return inodes 

825 

826 @staticmethod 

827 def decode_address(addr, family): 

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

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

830 

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

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

833 

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

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

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

837 to an IP address. 

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

839 

840 Reference: 

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

842 """ 

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

844 port = int(port, 16) 

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

846 # no end-points connected 

847 if not port: 

848 return () 

849 ip = ip.encode('ascii') 

850 if family == socket.AF_INET: 

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

852 if LITTLE_ENDIAN: 

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

854 else: 

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

856 else: # IPv6 

857 ip = base64.b16decode(ip) 

858 try: 

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

860 if LITTLE_ENDIAN: 

861 ip = socket.inet_ntop( 

862 socket.AF_INET6, 

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

864 ) 

865 else: 

866 ip = socket.inet_ntop( 

867 socket.AF_INET6, 

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

869 ) 

870 except ValueError: 

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

872 if not supports_ipv6(): 

873 raise _Ipv6UnsupportedError from None 

874 raise 

875 return _common.addr(ip, port) 

876 

877 @staticmethod 

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

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

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

881 # IPv6 not supported 

882 return 

883 with open_text(file) as f: 

884 f.readline() # skip the first line 

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

886 try: 

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

888 line.split()[:10] 

889 ) 

890 except ValueError: 

891 msg = ( 

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

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

894 ) 

895 raise RuntimeError(msg) from None 

896 if inode in inodes: 

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

898 # # out if there are multiple references to the 

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

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

901 # raise ValueError("ambiguous inode with multiple " 

902 # "PIDs references") 

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

904 else: 

905 pid, fd = None, -1 

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

907 continue 

908 else: 

909 if type_ == socket.SOCK_STREAM: 

910 status = TCP_STATUSES[status] 

911 else: 

912 status = _common.CONN_NONE 

913 try: 

914 laddr = NetConnections.decode_address(laddr, family) 

915 raddr = NetConnections.decode_address(raddr, family) 

916 except _Ipv6UnsupportedError: 

917 continue 

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

919 

920 @staticmethod 

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

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

923 with open_text(file) as f: 

924 f.readline() # skip the first line 

925 for line in f: 

926 tokens = line.split() 

927 try: 

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

929 except ValueError: 

930 if ' ' not in line: 

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

932 continue 

933 msg = ( 

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

935 ) 

936 raise RuntimeError(msg) # noqa: B904 

937 if inode in inodes: # noqa: SIM108 

938 # With UNIX sockets we can have a single inode 

939 # referencing many file descriptors. 

940 pairs = inodes[inode] 

941 else: 

942 pairs = [(None, -1)] 

943 for pid, fd in pairs: 

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

945 continue 

946 else: 

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

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

949 # XXX: determining the remote endpoint of a 

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

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

952 raddr = "" 

953 status = _common.CONN_NONE 

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

955 

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

957 self._procfs_path = get_procfs_path() 

958 if pid is not None: 

959 inodes = self.get_proc_inodes(pid) 

960 if not inodes: 

961 # no connections for this process 

962 return [] 

963 else: 

964 inodes = self.get_all_inodes() 

965 ret = set() 

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

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

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

969 ls = self.process_inet( 

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

971 ) 

972 else: 

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

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

975 if pid: 

976 conn = _common.pconn( 

977 fd, family, type_, laddr, raddr, status 

978 ) 

979 else: 

980 conn = _common.sconn( 

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

982 ) 

983 ret.add(conn) 

984 return list(ret) 

985 

986 

987_net_connections = NetConnections() 

988 

989 

990def net_connections(kind='inet'): 

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

992 return _net_connections.retrieve(kind) 

993 

994 

995def net_io_counters(): 

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

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

998 """ 

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

1000 lines = f.readlines() 

1001 retdict = {} 

1002 for line in lines[2:]: 

1003 colon = line.rfind(':') 

1004 assert colon > 0, repr(line) 

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

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

1007 

1008 ( 

1009 # in 

1010 bytes_recv, 

1011 packets_recv, 

1012 errin, 

1013 dropin, 

1014 _fifoin, # unused 

1015 _framein, # unused 

1016 _compressedin, # unused 

1017 _multicastin, # unused 

1018 # out 

1019 bytes_sent, 

1020 packets_sent, 

1021 errout, 

1022 dropout, 

1023 _fifoout, # unused 

1024 _collisionsout, # unused 

1025 _carrierout, # unused 

1026 _compressedout, # unused 

1027 ) = map(int, fields) 

1028 

1029 retdict[name] = ( 

1030 bytes_sent, 

1031 bytes_recv, 

1032 packets_sent, 

1033 packets_recv, 

1034 errin, 

1035 errout, 

1036 dropin, 

1037 dropout, 

1038 ) 

1039 return retdict 

1040 

1041 

1042def net_if_stats(): 

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

1044 duplex_map = { 

1045 cext.DUPLEX_FULL: NIC_DUPLEX_FULL, 

1046 cext.DUPLEX_HALF: NIC_DUPLEX_HALF, 

1047 cext.DUPLEX_UNKNOWN: NIC_DUPLEX_UNKNOWN, 

1048 } 

1049 names = net_io_counters().keys() 

1050 ret = {} 

1051 for name in names: 

1052 try: 

1053 mtu = cext_posix.net_if_mtu(name) 

1054 flags = cext_posix.net_if_flags(name) 

1055 duplex, speed = cext.net_if_duplex_speed(name) 

1056 except OSError as err: 

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

1058 if err.errno != errno.ENODEV: 

1059 raise 

1060 debug(err) 

1061 else: 

1062 output_flags = ','.join(flags) 

1063 isup = 'running' in flags 

1064 ret[name] = _common.snicstats( 

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

1066 ) 

1067 return ret 

1068 

1069 

1070# ===================================================================== 

1071# --- disks 

1072# ===================================================================== 

1073 

1074 

1075disk_usage = _psposix.disk_usage 

1076 

1077 

1078def disk_io_counters(perdisk=False): 

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

1080 system as a dict of raw tuples. 

1081 """ 

1082 

1083 def read_procfs(): 

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

1085 # have 3 variations. 

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

1087 # "3 0 8 hda 8 8 8 8 8 8 8 8 8 8 8" 

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

1089 # name is in another position, like this: 

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

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

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

1093 # "3 1 hda1 8 8 8 8" 

1094 # 4.18+ has 4 fields added: 

1095 # "3 0 hda 8 8 8 8 8 8 8 8 8 8 8 0 0 0 0" 

1096 # 5.5 has 2 more fields. 

1097 # See: 

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

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

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

1101 lines = f.readlines() 

1102 for line in lines: 

1103 fields = line.split() 

1104 flen = len(fields) 

1105 # fmt: off 

1106 if flen == 15: 

1107 # Linux 2.4 

1108 name = fields[3] 

1109 reads = int(fields[2]) 

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

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

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

1113 # Linux 2.6+, line referring to a disk 

1114 name = fields[2] 

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

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

1117 elif flen == 7: 

1118 # Linux 2.6+, line referring to a partition 

1119 name = fields[2] 

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

1121 rtime = wtime = reads_merged = writes_merged = busy_time = 0 

1122 else: 

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

1124 raise ValueError(msg) 

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

1126 reads_merged, writes_merged, busy_time) 

1127 # fmt: on 

1128 

1129 def read_sysfs(): 

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

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

1132 if 'stat' not in files: 

1133 continue 

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

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

1136 name = os.path.basename(root) 

1137 # fmt: off 

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

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

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

1141 wtime, reads_merged, writes_merged, busy_time) 

1142 # fmt: on 

1143 

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

1145 gen = read_procfs() 

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

1147 gen = read_sysfs() 

1148 else: 

1149 msg = ( 

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

1151 " this system" 

1152 ) 

1153 raise NotImplementedError(msg) 

1154 

1155 retdict = {} 

1156 for entry in gen: 

1157 # fmt: off 

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

1159 writes_merged, busy_time) = entry 

1160 if not perdisk and not is_storage_device(name): 

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

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

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

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

1165 # of their own: 

1166 # $ cat /proc/diskstats 

1167 # 259 0 sda 10485760 ... 

1168 # 259 1 sda1 5186039 ... 

1169 # 259 1 sda2 5082039 ... 

1170 # See: 

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

1172 continue 

1173 

1174 rbytes *= DISK_SECTOR_SIZE 

1175 wbytes *= DISK_SECTOR_SIZE 

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

1177 reads_merged, writes_merged, busy_time) 

1178 # fmt: on 

1179 

1180 return retdict 

1181 

1182 

1183class RootFsDeviceFinder: 

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

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

1186 obtain the real device path. Resources: 

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

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

1189 """ 

1190 

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

1192 

1193 def __init__(self): 

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

1195 self.major = os.major(dev) 

1196 self.minor = os.minor(dev) 

1197 

1198 def ask_proc_partitions(self): 

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

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

1201 fields = line.split() 

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

1203 continue 

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

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

1206 name = fields[3] 

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

1208 if name: # just for extra safety 

1209 return f"/dev/{name}" 

1210 

1211 def ask_sys_dev_block(self): 

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

1213 with open_text(path) as f: 

1214 for line in f: 

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

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

1217 if name: # just for extra safety 

1218 return f"/dev/{name}" 

1219 

1220 def ask_sys_class_block(self): 

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

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

1223 for file in files: 

1224 try: 

1225 f = open_text(file) 

1226 except FileNotFoundError: # race condition 

1227 continue 

1228 else: 

1229 with f: 

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

1231 if data == needle: 

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

1233 return f"/dev/{name}" 

1234 

1235 def find(self): 

1236 path = None 

1237 if path is None: 

1238 try: 

1239 path = self.ask_proc_partitions() 

1240 except OSError as err: 

1241 debug(err) 

1242 if path is None: 

1243 try: 

1244 path = self.ask_sys_dev_block() 

1245 except OSError as err: 

1246 debug(err) 

1247 if path is None: 

1248 try: 

1249 path = self.ask_sys_class_block() 

1250 except OSError as err: 

1251 debug(err) 

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

1253 # coded, so we want to be sure. 

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

1255 return path 

1256 

1257 

1258def disk_partitions(all=False): 

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

1260 fstypes = set() 

1261 procfs_path = get_procfs_path() 

1262 if not all: 

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

1264 for line in f: 

1265 line = line.strip() 

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

1267 fstypes.add(line.strip()) 

1268 else: 

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

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

1271 if fstype == "zfs": 

1272 fstypes.add("zfs") 

1273 

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

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

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

1277 else: 

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

1279 

1280 retlist = [] 

1281 partitions = cext.disk_partitions(mounts_path) 

1282 for partition in partitions: 

1283 device, mountpoint, fstype, opts = partition 

1284 if device == 'none': 

1285 device = '' 

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

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

1288 if not all: 

1289 if not device or fstype not in fstypes: 

1290 continue 

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

1292 retlist.append(ntuple) 

1293 

1294 return retlist 

1295 

1296 

1297# ===================================================================== 

1298# --- sensors 

1299# ===================================================================== 

1300 

1301 

1302def sensors_temperatures(): 

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

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

1305 temperatures. 

1306 

1307 Implementation notes: 

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

1309 retrieve this info, and this implementation relies on it 

1310 only (old distros will probably use something else) 

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

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

1313 difficult to parse 

1314 """ 

1315 ret = collections.defaultdict(list) 

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

1317 # CentOS has an intermediate /device directory: 

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

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

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

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

1322 

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

1324 # /sys/class/hwmon/ 

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

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

1327 basenames2 = glob.glob( 

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

1329 ) 

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

1331 for name in basenames2: 

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

1333 if altname not in basenames: 

1334 basenames.append(name) 

1335 

1336 for base in basenames: 

1337 try: 

1338 path = base + '_input' 

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

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

1341 unit_name = cat(path).strip() 

1342 except (OSError, ValueError): 

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

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

1345 # is a stinky broken mess. 

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

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

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

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

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

1351 continue 

1352 

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

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

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

1356 

1357 if high is not None: 

1358 try: 

1359 high = float(high) / 1000.0 

1360 except ValueError: 

1361 high = None 

1362 if critical is not None: 

1363 try: 

1364 critical = float(critical) / 1000.0 

1365 except ValueError: 

1366 critical = None 

1367 

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

1369 

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

1371 if not basenames: 

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

1373 basenames = sorted(set(basenames)) 

1374 

1375 for base in basenames: 

1376 try: 

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

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

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

1380 unit_name = cat(path).strip() 

1381 except (OSError, ValueError) as err: 

1382 debug(err) 

1383 continue 

1384 

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

1386 trip_points = { 

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

1388 for p in trip_paths 

1389 } 

1390 critical = None 

1391 high = None 

1392 for trip_point in trip_points: 

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

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

1395 if trip_type == 'critical': 

1396 critical = bcat( 

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

1398 ) 

1399 elif trip_type == 'high': 

1400 high = bcat( 

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

1402 ) 

1403 

1404 if high is not None: 

1405 try: 

1406 high = float(high) / 1000.0 

1407 except ValueError: 

1408 high = None 

1409 if critical is not None: 

1410 try: 

1411 critical = float(critical) / 1000.0 

1412 except ValueError: 

1413 critical = None 

1414 

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

1416 

1417 return dict(ret) 

1418 

1419 

1420def sensors_fans(): 

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

1422 dict including hardware label and current speed. 

1423 

1424 Implementation notes: 

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

1426 retrieve this info, and this implementation relies on it 

1427 only (old distros will probably use something else) 

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

1429 """ 

1430 ret = collections.defaultdict(list) 

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

1432 if not basenames: 

1433 # CentOS has an intermediate /device directory: 

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

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

1436 

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

1438 for base in basenames: 

1439 try: 

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

1441 except OSError as err: 

1442 debug(err) 

1443 continue 

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

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

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

1447 

1448 return dict(ret) 

1449 

1450 

1451def sensors_battery(): 

1452 """Return battery information. 

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

1454 directory structure may vary and provide files with the same 

1455 meaning but under different names, see: 

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

1457 """ 

1458 null = object() 

1459 

1460 def multi_bcat(*paths): 

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

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

1463 """ 

1464 for path in paths: 

1465 ret = bcat(path, fallback=null) 

1466 if ret != null: 

1467 try: 

1468 return int(ret) 

1469 except ValueError: 

1470 return ret.strip() 

1471 return None 

1472 

1473 bats = [ 

1474 x 

1475 for x in os.listdir(POWER_SUPPLY_PATH) 

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

1477 ] 

1478 if not bats: 

1479 return None 

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

1481 # some rare exceptions: 

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

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

1484 

1485 # Base metrics. 

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

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

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

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

1490 

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

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

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

1494 try: 

1495 percent = 100.0 * energy_now / energy_full 

1496 except ZeroDivisionError: 

1497 percent = 0.0 

1498 else: 

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

1500 if percent == -1: 

1501 return None 

1502 

1503 # Is AC power cable plugged in? 

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

1505 # it's called "AC". 

1506 power_plugged = None 

1507 online = multi_bcat( 

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

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

1510 ) 

1511 if online is not None: 

1512 power_plugged = online == 1 

1513 else: 

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

1515 if status == "discharging": 

1516 power_plugged = False 

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

1518 power_plugged = True 

1519 

1520 # Seconds left. 

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

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

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

1524 if power_plugged: 

1525 secsleft = _common.POWER_TIME_UNLIMITED 

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

1527 try: 

1528 secsleft = int(energy_now / power_now * 3600) 

1529 except ZeroDivisionError: 

1530 secsleft = _common.POWER_TIME_UNKNOWN 

1531 elif time_to_empty is not None: 

1532 secsleft = int(time_to_empty * 60) 

1533 if secsleft < 0: 

1534 secsleft = _common.POWER_TIME_UNKNOWN 

1535 else: 

1536 secsleft = _common.POWER_TIME_UNKNOWN 

1537 

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

1539 

1540 

1541# ===================================================================== 

1542# --- other system functions 

1543# ===================================================================== 

1544 

1545 

1546def users(): 

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

1548 retlist = [] 

1549 rawlist = cext.users() 

1550 for item in rawlist: 

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

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

1553 retlist.append(nt) 

1554 return retlist 

1555 

1556 

1557def boot_time(): 

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

1559 global BOOT_TIME 

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

1561 with open_binary(path) as f: 

1562 for line in f: 

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

1564 ret = float(line.strip().split()[1]) 

1565 BOOT_TIME = ret 

1566 return ret 

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

1568 raise RuntimeError(msg) 

1569 

1570 

1571# ===================================================================== 

1572# --- processes 

1573# ===================================================================== 

1574 

1575 

1576def pids(): 

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

1578 path = get_procfs_path().encode(ENCODING) 

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

1580 

1581 

1582def pid_exists(pid): 

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

1584 supported (always return False). 

1585 """ 

1586 if not _psposix.pid_exists(pid): 

1587 return False 

1588 else: 

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

1590 # (thread IDs). 

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

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

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

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

1595 # only, see: 

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

1597 try: 

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

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

1600 # 'return pid in pids()' 

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

1602 with open_binary(path) as f: 

1603 for line in f: 

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

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

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

1607 # dealing with a process PID. 

1608 return tgid == pid 

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

1610 raise ValueError(msg) 

1611 except (OSError, ValueError): 

1612 return pid in pids() 

1613 

1614 

1615def ppid_map(): 

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

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

1618 """ 

1619 ret = {} 

1620 procfs_path = get_procfs_path() 

1621 for pid in pids(): 

1622 try: 

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

1624 data = f.read() 

1625 except (FileNotFoundError, ProcessLookupError): 

1626 # Note: we should be able to access /stat for all processes 

1627 # aka it's unlikely we'll bump into EPERM, which is good. 

1628 pass 

1629 else: 

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

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

1632 ppid = int(dset[1]) 

1633 ret[pid] = ppid 

1634 return ret 

1635 

1636 

1637def wrap_exceptions(fun): 

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

1639 into NoSuchProcess and AccessDenied. 

1640 """ 

1641 

1642 @functools.wraps(fun) 

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

1644 pid, name = self.pid, self._name 

1645 try: 

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

1647 except PermissionError as err: 

1648 raise AccessDenied(pid, name) from err 

1649 except ProcessLookupError as err: 

1650 self._raise_if_zombie() 

1651 raise NoSuchProcess(pid, name) from err 

1652 except FileNotFoundError as err: 

1653 self._raise_if_zombie() 

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

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

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

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

1658 raise NoSuchProcess(pid, name) from err 

1659 raise 

1660 

1661 return wrapper 

1662 

1663 

1664class Process: 

1665 """Linux process implementation.""" 

1666 

1667 __slots__ = ["_cache", "_name", "_ppid", "_procfs_path", "pid"] 

1668 

1669 def __init__(self, pid): 

1670 self.pid = pid 

1671 self._name = None 

1672 self._ppid = 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 @wrap_exceptions 

1702 @memoize_when_activated 

1703 def _parse_stat_file(self): 

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

1705 process info. 

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

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

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

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

1710 in use. 

1711 """ 

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

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

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

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

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

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

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

1719 

1720 ret = {} 

1721 ret['name'] = name 

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

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

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

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

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

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

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

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

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

1731 try: 

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

1733 except IndexError: 

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

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

1736 ret['blkio_ticks'] = 0 

1737 

1738 return ret 

1739 

1740 @wrap_exceptions 

1741 @memoize_when_activated 

1742 def _read_status_file(self): 

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

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

1745 in use. 

1746 """ 

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

1748 return f.read() 

1749 

1750 @wrap_exceptions 

1751 @memoize_when_activated 

1752 def _read_smaps_file(self): 

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

1754 return f.read().strip() 

1755 

1756 def oneshot_enter(self): 

1757 self._parse_stat_file.cache_activate(self) 

1758 self._read_status_file.cache_activate(self) 

1759 self._read_smaps_file.cache_activate(self) 

1760 

1761 def oneshot_exit(self): 

1762 self._parse_stat_file.cache_deactivate(self) 

1763 self._read_status_file.cache_deactivate(self) 

1764 self._read_smaps_file.cache_deactivate(self) 

1765 

1766 @wrap_exceptions 

1767 def name(self): 

1768 # XXX - gets changed later and probably needs refactoring 

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

1770 

1771 @wrap_exceptions 

1772 def exe(self): 

1773 try: 

1774 return readlink(f"{self._procfs_path}/{self.pid}/exe") 

1775 except (FileNotFoundError, ProcessLookupError): 

1776 self._raise_if_zombie() 

1777 # no such file error; might be raised also if the 

1778 # path actually exists for system processes with 

1779 # low pids (about 0-20) 

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

1781 return "" 

1782 raise 

1783 

1784 @wrap_exceptions 

1785 def cmdline(self): 

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

1787 data = f.read() 

1788 if not data: 

1789 # may happen in case of zombie process 

1790 self._raise_if_zombie() 

1791 return [] 

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

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

1794 # some processes may change their cmdline after being started 

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

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

1797 # Chrome process is an example. See: 

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

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

1800 if data.endswith(sep): 

1801 data = data[:-1] 

1802 cmdline = data.split(sep) 

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

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

1805 # issues/1179#issuecomment-552984549 

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

1807 cmdline = data.split(' ') 

1808 return cmdline 

1809 

1810 @wrap_exceptions 

1811 def environ(self): 

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

1813 data = f.read() 

1814 return parse_environ_block(data) 

1815 

1816 @wrap_exceptions 

1817 def terminal(self): 

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

1819 tmap = _psposix.get_terminal_map() 

1820 try: 

1821 return tmap[tty_nr] 

1822 except KeyError: 

1823 return None 

1824 

1825 # May not be available on old kernels. 

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

1827 

1828 @wrap_exceptions 

1829 def io_counters(self): 

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

1831 fields = {} 

1832 with open_binary(fname) as f: 

1833 for line in f: 

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

1835 line = line.strip() 

1836 if line: 

1837 try: 

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

1839 except ValueError: 

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

1841 continue 

1842 else: 

1843 fields[name] = int(value) 

1844 if not fields: 

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

1846 raise RuntimeError(msg) 

1847 try: 

1848 return pio( 

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

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

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

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

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

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

1855 ) 

1856 except KeyError as err: 

1857 msg = ( 

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

1859 f" fields are {fields!r}" 

1860 ) 

1861 raise ValueError(msg) from None 

1862 

1863 @wrap_exceptions 

1864 def cpu_times(self): 

1865 values = self._parse_stat_file() 

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

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

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

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

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

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

1872 

1873 @wrap_exceptions 

1874 def cpu_num(self): 

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

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

1877 

1878 @wrap_exceptions 

1879 def wait(self, timeout=None): 

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

1881 

1882 @wrap_exceptions 

1883 def create_time(self): 

1884 ctime = float(self._parse_stat_file()['create_time']) 

1885 # According to documentation, starttime is in field 21 and the 

1886 # unit is jiffies (clock ticks). 

1887 # We first divide it for clock ticks and then add uptime returning 

1888 # seconds since the epoch. 

1889 # Also use cached value if available. 

1890 bt = BOOT_TIME or boot_time() 

1891 return (ctime / CLOCK_TICKS) + bt 

1892 

1893 @wrap_exceptions 

1894 def memory_info(self): 

1895 # ============================================================ 

1896 # | FIELD | DESCRIPTION | AKA | TOP | 

1897 # ============================================================ 

1898 # | rss | resident set size | | RES | 

1899 # | vms | total program size | size | VIRT | 

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

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

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

1903 # | data | data + stack | drs | DATA | 

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

1905 # ============================================================ 

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

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

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

1909 ) 

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

1911 

1912 if HAS_PROC_SMAPS_ROLLUP or HAS_PROC_SMAPS: 

1913 

1914 def _parse_smaps_rollup(self): 

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

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

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

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

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

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

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

1922 # compared to /proc/pid/smaps_rollup. 

1923 uss = pss = swap = 0 

1924 with open_binary( 

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

1926 ) as f: 

1927 for line in f: 

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

1929 # Private_Clean, Private_Dirty, Private_Hugetlb 

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

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

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

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

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

1935 return (uss, pss, swap) 

1936 

1937 @wrap_exceptions 

1938 def _parse_smaps( 

1939 self, 

1940 # Gets Private_Clean, Private_Dirty, Private_Hugetlb. 

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

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

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

1944 ): 

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

1946 # CONFIG_MMU kernel configuration option is not enabled. 

1947 

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

1949 # line by line. 

1950 # 

1951 # You might be tempted to calculate USS by subtracting 

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

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

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

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

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

1957 # correct information. 

1958 smaps_data = self._read_smaps_file() 

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

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

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

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

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

1964 return (uss, pss, swap) 

1965 

1966 @wrap_exceptions 

1967 def memory_full_info(self): 

1968 if HAS_PROC_SMAPS_ROLLUP: # faster 

1969 try: 

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

1971 except (ProcessLookupError, FileNotFoundError): 

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

1973 else: 

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

1975 basic_mem = self.memory_info() 

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

1977 

1978 else: 

1979 memory_full_info = memory_info 

1980 

1981 if HAS_PROC_SMAPS: 

1982 

1983 @wrap_exceptions 

1984 def memory_maps(self): 

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

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

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

1988 

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

1990 CONFIG_MMU kernel configuration option is not enabled. 

1991 """ 

1992 

1993 def get_blocks(lines, current_block): 

1994 data = {} 

1995 for line in lines: 

1996 fields = line.split(None, 5) 

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

1998 # new block section 

1999 yield (current_block.pop(), data) 

2000 current_block.append(line) 

2001 else: 

2002 try: 

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

2004 except ValueError: 

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

2006 # see issue #369 

2007 continue 

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

2009 raise ValueError(msg) from None 

2010 yield (current_block.pop(), data) 

2011 

2012 data = self._read_smaps_file() 

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

2014 # zombies. 

2015 if not data: 

2016 self._raise_if_zombie() 

2017 return [] 

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

2019 ls = [] 

2020 first_line = lines.pop(0) 

2021 current_block = [first_line] 

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

2023 hfields = header.split(None, 5) 

2024 try: 

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

2026 except ValueError: 

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

2028 if not path: 

2029 path = '[anon]' 

2030 else: 

2031 path = decode(path) 

2032 path = path.strip() 

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

2034 path 

2035 ): 

2036 path = path[:-10] 

2037 item = ( 

2038 decode(addr), 

2039 decode(perms), 

2040 path, 

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

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

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

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

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

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

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

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

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

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

2051 ) 

2052 ls.append(item) 

2053 return ls 

2054 

2055 @wrap_exceptions 

2056 def cwd(self): 

2057 return readlink(f"{self._procfs_path}/{self.pid}/cwd") 

2058 

2059 @wrap_exceptions 

2060 def num_ctx_switches( 

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

2062 ): 

2063 data = self._read_status_file() 

2064 ctxsw = _ctxsw_re.findall(data) 

2065 if not ctxsw: 

2066 msg = ( 

2067 "'voluntary_ctxt_switches' and" 

2068 " 'nonvoluntary_ctxt_switches'lines were not found in" 

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

2070 " probably older than 2.6.23" 

2071 ) 

2072 raise NotImplementedError(msg) 

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

2074 

2075 @wrap_exceptions 

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

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

2078 data = self._read_status_file() 

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

2080 

2081 @wrap_exceptions 

2082 def threads(self): 

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

2084 thread_ids.sort() 

2085 retlist = [] 

2086 hit_enoent = False 

2087 for thread_id in thread_ids: 

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

2089 try: 

2090 with open_binary(fname) as f: 

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

2092 except (FileNotFoundError, ProcessLookupError): 

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

2094 # it means thread disappeared on us 

2095 hit_enoent = True 

2096 continue 

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

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

2099 values = st.split(b' ') 

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

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

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

2103 retlist.append(ntuple) 

2104 if hit_enoent: 

2105 self._raise_if_not_alive() 

2106 return retlist 

2107 

2108 @wrap_exceptions 

2109 def nice_get(self): 

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

2111 # data = f.read() 

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

2113 

2114 # Use C implementation 

2115 return cext_posix.getpriority(self.pid) 

2116 

2117 @wrap_exceptions 

2118 def nice_set(self, value): 

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

2120 

2121 # starting from CentOS 6. 

2122 if HAS_CPU_AFFINITY: 

2123 

2124 @wrap_exceptions 

2125 def cpu_affinity_get(self): 

2126 return cext.proc_cpu_affinity_get(self.pid) 

2127 

2128 def _get_eligible_cpus( 

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

2130 ): 

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

2132 data = self._read_status_file() 

2133 match = _re.findall(data) 

2134 if match: 

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

2136 else: 

2137 return list(range(len(per_cpu_times()))) 

2138 

2139 @wrap_exceptions 

2140 def cpu_affinity_set(self, cpus): 

2141 try: 

2142 cext.proc_cpu_affinity_set(self.pid, cpus) 

2143 except (OSError, ValueError) as err: 

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

2145 eligible_cpus = self._get_eligible_cpus() 

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

2147 for cpu in cpus: 

2148 if cpu not in all_cpus: 

2149 msg = ( 

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

2151 f" {eligible_cpus!r}" 

2152 ) 

2153 raise ValueError(msg) from None 

2154 if cpu not in eligible_cpus: 

2155 msg = ( 

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

2157 f" between {eligible_cpus}" 

2158 ) 

2159 raise ValueError(msg) from err 

2160 raise 

2161 

2162 # only starting from kernel 2.6.13 

2163 if HAS_PROC_IO_PRIORITY: 

2164 

2165 @wrap_exceptions 

2166 def ionice_get(self): 

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

2168 ioclass = IOPriority(ioclass) 

2169 return _common.pionice(ioclass, value) 

2170 

2171 @wrap_exceptions 

2172 def ionice_set(self, ioclass, value): 

2173 if value is None: 

2174 value = 0 

2175 if value and ioclass in { 

2176 IOPriority.IOPRIO_CLASS_IDLE, 

2177 IOPriority.IOPRIO_CLASS_NONE, 

2178 }: 

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

2180 raise ValueError(msg) 

2181 if value < 0 or value > 7: 

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

2183 raise ValueError(msg) 

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

2185 

2186 if hasattr(resource, "prlimit"): 

2187 

2188 @wrap_exceptions 

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

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

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

2192 # PID 0 is not supported on Linux. 

2193 if self.pid == 0: 

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

2195 raise ValueError(msg) 

2196 try: 

2197 if limits is None: 

2198 # get 

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

2200 else: 

2201 # set 

2202 if len(limits) != 2: 

2203 msg = ( 

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

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

2206 ) 

2207 raise ValueError(msg) 

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

2209 except OSError as err: 

2210 if err.errno == errno.ENOSYS: 

2211 # I saw this happening on Travis: 

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

2213 self._raise_if_zombie() 

2214 raise 

2215 

2216 @wrap_exceptions 

2217 def status(self): 

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

2219 letter = letter.decode() 

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

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

2222 

2223 @wrap_exceptions 

2224 def open_files(self): 

2225 retlist = [] 

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

2227 hit_enoent = False 

2228 for fd in files: 

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

2230 try: 

2231 path = readlink(file) 

2232 except (FileNotFoundError, ProcessLookupError): 

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

2234 hit_enoent = True 

2235 continue 

2236 except OSError as err: 

2237 if err.errno == errno.EINVAL: 

2238 # not a link 

2239 continue 

2240 if err.errno == errno.ENAMETOOLONG: 

2241 # file name too long 

2242 debug(err) 

2243 continue 

2244 raise 

2245 else: 

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

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

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

2249 # absolute path though. 

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

2251 # Get file position and flags. 

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

2253 try: 

2254 with open_binary(file) as f: 

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

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

2257 except (FileNotFoundError, ProcessLookupError): 

2258 # fd gone in the meantime; process may 

2259 # still be alive 

2260 hit_enoent = True 

2261 else: 

2262 mode = file_flags_to_mode(flags) 

2263 ntuple = popenfile( 

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

2265 ) 

2266 retlist.append(ntuple) 

2267 if hit_enoent: 

2268 self._raise_if_not_alive() 

2269 return retlist 

2270 

2271 @wrap_exceptions 

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

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

2274 self._raise_if_not_alive() 

2275 return ret 

2276 

2277 @wrap_exceptions 

2278 def num_fds(self): 

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

2280 

2281 @wrap_exceptions 

2282 def ppid(self): 

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

2284 

2285 @wrap_exceptions 

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

2287 data = self._read_status_file() 

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

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

2290 

2291 @wrap_exceptions 

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

2293 data = self._read_status_file() 

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

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