Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/psutil-7.1.3-py3.11-linux-x86_64.egg/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

1228 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 ._common import ENCODING 

28from ._common import NIC_DUPLEX_FULL 

29from ._common import NIC_DUPLEX_HALF 

30from ._common import NIC_DUPLEX_UNKNOWN 

31from ._common import AccessDenied 

32from ._common import NoSuchProcess 

33from ._common import ZombieProcess 

34from ._common import bcat 

35from ._common import cat 

36from ._common import debug 

37from ._common import decode 

38from ._common import get_procfs_path 

39from ._common import isfile_strict 

40from ._common import memoize 

41from ._common import memoize_when_activated 

42from ._common import open_binary 

43from ._common import open_text 

44from ._common import parse_environ_block 

45from ._common import path_exists_strict 

46from ._common import supports_ipv6 

47from ._common import usage_percent 

48 

49# fmt: off 

50__extra__all__ = [ 

51 'PROCFS_PATH', 

52 # io prio constants 

53 "IOPRIO_CLASS_NONE", "IOPRIO_CLASS_RT", "IOPRIO_CLASS_BE", 

54 "IOPRIO_CLASS_IDLE", 

55 # connection status constants 

56 "CONN_ESTABLISHED", "CONN_SYN_SENT", "CONN_SYN_RECV", "CONN_FIN_WAIT1", 

57 "CONN_FIN_WAIT2", "CONN_TIME_WAIT", "CONN_CLOSE", "CONN_CLOSE_WAIT", 

58 "CONN_LAST_ACK", "CONN_LISTEN", "CONN_CLOSING", 

59] 

60# fmt: on 

61 

62 

63# ===================================================================== 

64# --- globals 

65# ===================================================================== 

66 

67 

68POWER_SUPPLY_PATH = "/sys/class/power_supply" 

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

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

71HAS_PROC_IO_PRIORITY = hasattr(cext, "proc_ioprio_get") 

72HAS_CPU_AFFINITY = hasattr(cext, "proc_cpu_affinity_get") 

73 

74# Number of clock ticks per second 

75CLOCK_TICKS = os.sysconf("SC_CLK_TCK") 

76PAGESIZE = cext.getpagesize() 

77LITTLE_ENDIAN = sys.byteorder == 'little' 

78UNSET = object() 

79 

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

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

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

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

84# throughout Linux source code: 

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

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

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

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

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

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

91DISK_SECTOR_SIZE = 512 

92 

93AddressFamily = enum.IntEnum( 

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

95) 

96AF_LINK = AddressFamily.AF_LINK 

97 

98 

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

100class IOPriority(enum.IntEnum): 

101 IOPRIO_CLASS_NONE = 0 

102 IOPRIO_CLASS_RT = 1 

103 IOPRIO_CLASS_BE = 2 

104 IOPRIO_CLASS_IDLE = 3 

105 

106 

107globals().update(IOPriority.__members__) 

108 

109# See: 

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

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

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

113PROC_STATUSES = { 

114 "R": _common.STATUS_RUNNING, 

115 "S": _common.STATUS_SLEEPING, 

116 "D": _common.STATUS_DISK_SLEEP, 

117 "T": _common.STATUS_STOPPED, 

118 "t": _common.STATUS_TRACING_STOP, 

119 "Z": _common.STATUS_ZOMBIE, 

120 "X": _common.STATUS_DEAD, 

121 "x": _common.STATUS_DEAD, 

122 "K": _common.STATUS_WAKE_KILL, 

123 "W": _common.STATUS_WAKING, 

124 "I": _common.STATUS_IDLE, 

125 "P": _common.STATUS_PARKED, 

126} 

127 

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

129TCP_STATUSES = { 

130 "01": _common.CONN_ESTABLISHED, 

131 "02": _common.CONN_SYN_SENT, 

132 "03": _common.CONN_SYN_RECV, 

133 "04": _common.CONN_FIN_WAIT1, 

134 "05": _common.CONN_FIN_WAIT2, 

135 "06": _common.CONN_TIME_WAIT, 

136 "07": _common.CONN_CLOSE, 

137 "08": _common.CONN_CLOSE_WAIT, 

138 "09": _common.CONN_LAST_ACK, 

139 "0A": _common.CONN_LISTEN, 

140 "0B": _common.CONN_CLOSING, 

141} 

142 

143 

144# ===================================================================== 

145# --- named tuples 

146# ===================================================================== 

147 

148 

149# fmt: off 

150# psutil.virtual_memory() 

151svmem = namedtuple( 

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

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

154# psutil.disk_io_counters() 

155sdiskio = namedtuple( 

156 'sdiskio', ['read_count', 'write_count', 

157 'read_bytes', 'write_bytes', 

158 'read_time', 'write_time', 

159 'read_merged_count', 'write_merged_count', 

160 'busy_time']) 

161# psutil.Process().open_files() 

162popenfile = namedtuple( 

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

164# psutil.Process().memory_info() 

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

166# psutil.Process().memory_full_info() 

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

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

169pmmap_grouped = namedtuple( 

170 'pmmap_grouped', 

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

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

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

174pmmap_ext = namedtuple( 

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

176# psutil.Process.io_counters() 

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

178 'read_bytes', 'write_bytes', 

179 'read_chars', 'write_chars']) 

180# psutil.Process.cpu_times() 

181pcputimes = namedtuple('pcputimes', 

182 ['user', 'system', 'children_user', 'children_system', 

183 'iowait']) 

184# fmt: on 

185 

186 

187# ===================================================================== 

188# --- utils 

189# ===================================================================== 

190 

191 

192def readlink(path): 

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

194 assert isinstance(path, str), path 

195 path = os.readlink(path) 

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

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

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

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

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

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

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

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

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

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

206 # don't care. 

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

208 path = path[:-10] 

209 return path 

210 

211 

212def file_flags_to_mode(flags): 

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

214 Used by Process.open_files(). 

215 """ 

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

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

218 if flags & os.O_APPEND: 

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

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

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

222 return mode 

223 

224 

225def is_storage_device(name): 

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

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

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

229 return True. 

230 """ 

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

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

233 # 97912938cd476645b267280069e83b1c8dc0e1c7/common.c#L208 

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

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

236 including_virtual = True 

237 if including_virtual: 

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

239 else: 

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

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

242 

243 

244@memoize 

245def set_scputimes_ntuple(procfs_path): 

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

247 available on this Linux kernel version which may be: 

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

249 [guest_nice]]]) 

250 Used by cpu_times() function. 

251 """ 

252 global scputimes 

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

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

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

256 vlen = len(values) 

257 if vlen >= 8: 

258 # Linux >= 2.6.11 

259 fields.append('steal') 

260 if vlen >= 9: 

261 # Linux >= 2.6.24 

262 fields.append('guest') 

263 if vlen >= 10: 

264 # Linux >= 3.2.0 

265 fields.append('guest_nice') 

266 scputimes = namedtuple('scputimes', fields) 

267 

268 

269try: 

270 set_scputimes_ntuple("/proc") 

271except Exception as err: # noqa: BLE001 

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

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

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

275 

276 

277# ===================================================================== 

278# --- system memory 

279# ===================================================================== 

280 

281 

282def calculate_avail_vmem(mems): 

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

284 "MemAvailable", see: 

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

286 

287 This code reimplements the algorithm outlined here: 

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

289 commit/?id=34e431b0ae398fc54ea69ff85ec700722c9da773 

290 

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

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

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

294 column). 

295 

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

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

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

299 See: 

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

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

302 """ 

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

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

305 # commit/?id=34e431b0ae398fc54ea69ff85ec700722c9da773 

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

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

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

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

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

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

312 free = mems[b'MemFree:'] 

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

314 try: 

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

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

317 slab_reclaimable = mems[b'SReclaimable:'] 

318 except KeyError as err: 

319 debug( 

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

321 " approximation for calculating available memory" 

322 ) 

323 return fallback 

324 try: 

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

326 except OSError: 

327 return fallback # kernel 2.6.13 

328 

329 watermark_low = 0 

330 with f: 

331 for line in f: 

332 line = line.strip() 

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

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

335 watermark_low *= PAGESIZE 

336 

337 avail = free - watermark_low 

338 pagecache = lru_active_file + lru_inactive_file 

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

340 avail += pagecache 

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

342 return int(avail) 

343 

344 

345def virtual_memory(): 

346 """Report virtual memory stats. 

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

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

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

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

351 CLI tools. 

352 """ 

353 missing_fields = [] 

354 mems = {} 

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

356 for line in f: 

357 fields = line.split() 

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

359 

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

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

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

363 total = mems[b'MemTotal:'] 

364 free = mems[b'MemFree:'] 

365 try: 

366 buffers = mems[b'Buffers:'] 

367 except KeyError: 

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

369 buffers = 0 

370 missing_fields.append('buffers') 

371 try: 

372 cached = mems[b"Cached:"] 

373 except KeyError: 

374 cached = 0 

375 missing_fields.append('cached') 

376 else: 

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

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

379 # This got changed in: 

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

381 # 05d751c4f076a2f0118b914c5e51cfbb4762ad8e 

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

383 

384 try: 

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

386 except KeyError: 

387 try: 

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

389 except KeyError: 

390 shared = 0 

391 missing_fields.append('shared') 

392 

393 try: 

394 active = mems[b"Active:"] 

395 except KeyError: 

396 active = 0 

397 missing_fields.append('active') 

398 

399 try: 

400 inactive = mems[b"Inactive:"] 

401 except KeyError: 

402 try: 

403 inactive = ( 

404 mems[b"Inact_dirty:"] 

405 + mems[b"Inact_clean:"] 

406 + mems[b"Inact_laundry:"] 

407 ) 

408 except KeyError: 

409 inactive = 0 

410 missing_fields.append('inactive') 

411 

412 try: 

413 slab = mems[b"Slab:"] 

414 except KeyError: 

415 slab = 0 

416 

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

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

419 # which matched htop. 

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

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

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

423 # - MemAvailable has been introduced in kernel 3.14 

424 try: 

425 avail = mems[b'MemAvailable:'] 

426 except KeyError: 

427 avail = calculate_avail_vmem(mems) 

428 else: 

429 if avail == 0: 

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

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

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

433 # and it matches "free" CLI tool. 

434 avail = calculate_avail_vmem(mems) 

435 

436 if avail < 0: 

437 avail = 0 

438 missing_fields.append('available') 

439 elif avail > total: 

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

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

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

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

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

445 avail = free 

446 

447 used = total - avail 

448 

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

450 

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

452 if missing_fields: 

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

454 ", ".join(missing_fields), 

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

456 ) 

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

458 

459 return svmem( 

460 total, 

461 avail, 

462 percent, 

463 used, 

464 free, 

465 active, 

466 inactive, 

467 buffers, 

468 cached, 

469 shared, 

470 slab, 

471 ) 

472 

473 

474def swap_memory(): 

475 """Return swap memory metrics.""" 

476 mems = {} 

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

478 for line in f: 

479 fields = line.split() 

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

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

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

483 # for linux containers, see: 

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

485 try: 

486 total = mems[b'SwapTotal:'] 

487 free = mems[b'SwapFree:'] 

488 except KeyError: 

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

490 total *= unit_multiplier 

491 free *= unit_multiplier 

492 

493 used = total - free 

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

495 # get pgin/pgouts 

496 try: 

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

498 except OSError as err: 

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

500 msg = ( 

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

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

503 ) 

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

505 sin = sout = 0 

506 else: 

507 with f: 

508 sin = sout = None 

509 for line in f: 

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

511 # bytes instead 

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

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

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

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

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

517 break 

518 else: 

519 # we might get here when dealing with exotic Linux 

520 # flavors, see: 

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

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

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

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

525 sin = sout = 0 

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

527 

528 

529# ===================================================================== 

530# --- CPU 

531# ===================================================================== 

532 

533 

534def cpu_times(): 

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

536 CPU times: 

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

538 [guest_nice]]]) 

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

540 """ 

541 procfs_path = get_procfs_path() 

542 set_scputimes_ntuple(procfs_path) 

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

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

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

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

547 return scputimes(*fields) 

548 

549 

550def per_cpu_times(): 

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

552 for every CPU available on the system. 

553 """ 

554 procfs_path = get_procfs_path() 

555 set_scputimes_ntuple(procfs_path) 

556 cpus = [] 

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

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

559 f.readline() 

560 for line in f: 

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

562 values = line.split() 

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

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

565 entry = scputimes(*fields) 

566 cpus.append(entry) 

567 return cpus 

568 

569 

570def cpu_count_logical(): 

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

572 try: 

573 return os.sysconf("SC_NPROCESSORS_ONLN") 

574 except ValueError: 

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

576 num = 0 

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

578 for line in f: 

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

580 num += 1 

581 

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

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

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

585 if num == 0: 

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

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

588 for line in f: 

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

590 if search.match(line): 

591 num += 1 

592 

593 if num == 0: 

594 # mimic os.cpu_count() 

595 return None 

596 return num 

597 

598 

599def cpu_count_cores(): 

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

601 # Method #1 

602 ls = set() 

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

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

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

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

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

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

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

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

611 with open_binary(path) as f: 

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

613 result = len(ls) 

614 if result != 0: 

615 return result 

616 

617 # Method #2 

618 mapping = {} 

619 current_info = {} 

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

621 for line in f: 

622 line = line.strip().lower() 

623 if not line: 

624 # new section 

625 try: 

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

627 b'cpu cores' 

628 ] 

629 except KeyError: 

630 pass 

631 current_info = {} 

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

633 # ongoing section 

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

635 current_info[key] = int(value) 

636 

637 result = sum(mapping.values()) 

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

639 

640 

641def cpu_stats(): 

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

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

644 ctx_switches = None 

645 interrupts = None 

646 soft_interrupts = None 

647 for line in f: 

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

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

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

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

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

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

654 if ( 

655 ctx_switches is not None 

656 and soft_interrupts is not None 

657 and interrupts is not None 

658 ): 

659 break 

660 syscalls = 0 

661 return _common.scpustats( 

662 ctx_switches, interrupts, soft_interrupts, syscalls 

663 ) 

664 

665 

666def _cpu_get_cpuinfo_freq(): 

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

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

669 return [ 

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

671 for line in f 

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

673 ] 

674 

675 

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

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

678): 

679 

680 def cpu_freq(): 

681 """Return frequency metrics for all CPUs. 

682 Contrarily to other OSes, Linux updates these values in 

683 real-time. 

684 """ 

685 cpuinfo_freqs = _cpu_get_cpuinfo_freq() 

686 paths = glob.glob( 

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

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

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

690 ret = [] 

691 pjoin = os.path.join 

692 for i, path in enumerate(paths): 

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

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

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

696 curr = cpuinfo_freqs[i] * 1000 

697 else: 

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

699 if curr is None: 

700 # Likely an old RedHat, see: 

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

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

703 if curr is None: 

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

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

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

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

708 continue 

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

710 raise NotImplementedError(msg) 

711 curr = int(curr) / 1000 

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

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

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

715 return ret 

716 

717else: 

718 

719 def cpu_freq(): 

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

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

722 """ 

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

724 

725 

726# ===================================================================== 

727# --- network 

728# ===================================================================== 

729 

730 

731net_if_addrs = cext.net_if_addrs 

732 

733 

734class _Ipv6UnsupportedError(Exception): 

735 pass 

736 

737 

738class NetConnections: 

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

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

741 "netstat -an". 

742 

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

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

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

746 

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

748 """ 

749 

750 def __init__(self): 

751 # The string represents the basename of the corresponding 

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

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

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

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

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

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

758 self.tmap = { 

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

760 "tcp": (tcp4, tcp6), 

761 "tcp4": (tcp4,), 

762 "tcp6": (tcp6,), 

763 "udp": (udp4, udp6), 

764 "udp4": (udp4,), 

765 "udp6": (udp6,), 

766 "unix": (unix,), 

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

768 "inet4": (tcp4, udp4), 

769 "inet6": (tcp6, udp6), 

770 } 

771 self._procfs_path = None 

772 

773 def get_proc_inodes(self, pid): 

774 inodes = defaultdict(list) 

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

776 try: 

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

778 except (FileNotFoundError, ProcessLookupError): 

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

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

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

782 continue 

783 except OSError as err: 

784 if err.errno == errno.EINVAL: 

785 # not a link 

786 continue 

787 if err.errno == errno.ENAMETOOLONG: 

788 # file name too long 

789 debug(err) 

790 continue 

791 raise 

792 else: 

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

794 # the process is using a socket 

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

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

797 return inodes 

798 

799 def get_all_inodes(self): 

800 inodes = {} 

801 for pid in pids(): 

802 try: 

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

804 except (FileNotFoundError, ProcessLookupError, PermissionError): 

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

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

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

808 # and fd set to None anyway. 

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

810 # unlikely we can do any better. 

811 # ENOENT just means a PID disappeared on us. 

812 continue 

813 return inodes 

814 

815 @staticmethod 

816 def decode_address(addr, family): 

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

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

819 

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

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

822 

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

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

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

826 to an IP address. 

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

828 

829 Reference: 

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

831 """ 

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

833 port = int(port, 16) 

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

835 # no end-points connected 

836 if not port: 

837 return () 

838 ip = ip.encode('ascii') 

839 if family == socket.AF_INET: 

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

841 if LITTLE_ENDIAN: 

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

843 else: 

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

845 else: # IPv6 

846 ip = base64.b16decode(ip) 

847 try: 

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

849 if LITTLE_ENDIAN: 

850 ip = socket.inet_ntop( 

851 socket.AF_INET6, 

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

853 ) 

854 else: 

855 ip = socket.inet_ntop( 

856 socket.AF_INET6, 

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

858 ) 

859 except ValueError: 

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

861 if not supports_ipv6(): 

862 raise _Ipv6UnsupportedError from None 

863 raise 

864 return _common.addr(ip, port) 

865 

866 @staticmethod 

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

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

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

870 # IPv6 not supported 

871 return 

872 with open_text(file) as f: 

873 f.readline() # skip the first line 

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

875 try: 

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

877 line.split()[:10] 

878 ) 

879 except ValueError: 

880 msg = ( 

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

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

883 ) 

884 raise RuntimeError(msg) from None 

885 if inode in inodes: 

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

887 # # out if there are multiple references to the 

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

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

890 # raise ValueError("ambiguous inode with multiple " 

891 # "PIDs references") 

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

893 else: 

894 pid, fd = None, -1 

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

896 continue 

897 else: 

898 if type_ == socket.SOCK_STREAM: 

899 status = TCP_STATUSES[status] 

900 else: 

901 status = _common.CONN_NONE 

902 try: 

903 laddr = NetConnections.decode_address(laddr, family) 

904 raddr = NetConnections.decode_address(raddr, family) 

905 except _Ipv6UnsupportedError: 

906 continue 

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

908 

909 @staticmethod 

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

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

912 with open_text(file) as f: 

913 f.readline() # skip the first line 

914 for line in f: 

915 tokens = line.split() 

916 try: 

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

918 except ValueError: 

919 if ' ' not in line: 

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

921 continue 

922 msg = ( 

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

924 ) 

925 raise RuntimeError(msg) # noqa: B904 

926 if inode in inodes: # noqa: SIM108 

927 # With UNIX sockets we can have a single inode 

928 # referencing many file descriptors. 

929 pairs = inodes[inode] 

930 else: 

931 pairs = [(None, -1)] 

932 for pid, fd in pairs: 

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

934 continue 

935 else: 

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

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

938 # XXX: determining the remote endpoint of a 

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

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

941 raddr = "" 

942 status = _common.CONN_NONE 

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

944 

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

946 self._procfs_path = get_procfs_path() 

947 if pid is not None: 

948 inodes = self.get_proc_inodes(pid) 

949 if not inodes: 

950 # no connections for this process 

951 return [] 

952 else: 

953 inodes = self.get_all_inodes() 

954 ret = set() 

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

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

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

958 ls = self.process_inet( 

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

960 ) 

961 else: 

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

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

964 if pid: 

965 conn = _common.pconn( 

966 fd, family, type_, laddr, raddr, status 

967 ) 

968 else: 

969 conn = _common.sconn( 

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

971 ) 

972 ret.add(conn) 

973 return list(ret) 

974 

975 

976_net_connections = NetConnections() 

977 

978 

979def net_connections(kind='inet'): 

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

981 return _net_connections.retrieve(kind) 

982 

983 

984def net_io_counters(): 

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

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

987 """ 

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

989 lines = f.readlines() 

990 retdict = {} 

991 for line in lines[2:]: 

992 colon = line.rfind(':') 

993 assert colon > 0, repr(line) 

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

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

996 

997 ( 

998 # in 

999 bytes_recv, 

1000 packets_recv, 

1001 errin, 

1002 dropin, 

1003 _fifoin, # unused 

1004 _framein, # unused 

1005 _compressedin, # unused 

1006 _multicastin, # unused 

1007 # out 

1008 bytes_sent, 

1009 packets_sent, 

1010 errout, 

1011 dropout, 

1012 _fifoout, # unused 

1013 _collisionsout, # unused 

1014 _carrierout, # unused 

1015 _compressedout, # unused 

1016 ) = map(int, fields) 

1017 

1018 retdict[name] = ( 

1019 bytes_sent, 

1020 bytes_recv, 

1021 packets_sent, 

1022 packets_recv, 

1023 errin, 

1024 errout, 

1025 dropin, 

1026 dropout, 

1027 ) 

1028 return retdict 

1029 

1030 

1031def net_if_stats(): 

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

1033 duplex_map = { 

1034 cext.DUPLEX_FULL: NIC_DUPLEX_FULL, 

1035 cext.DUPLEX_HALF: NIC_DUPLEX_HALF, 

1036 cext.DUPLEX_UNKNOWN: NIC_DUPLEX_UNKNOWN, 

1037 } 

1038 names = net_io_counters().keys() 

1039 ret = {} 

1040 for name in names: 

1041 try: 

1042 mtu = cext.net_if_mtu(name) 

1043 flags = cext.net_if_flags(name) 

1044 duplex, speed = cext.net_if_duplex_speed(name) 

1045 except OSError as err: 

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

1047 if err.errno != errno.ENODEV: 

1048 raise 

1049 debug(err) 

1050 else: 

1051 output_flags = ','.join(flags) 

1052 isup = 'running' in flags 

1053 ret[name] = _common.snicstats( 

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

1055 ) 

1056 return ret 

1057 

1058 

1059# ===================================================================== 

1060# --- disks 

1061# ===================================================================== 

1062 

1063 

1064disk_usage = _psposix.disk_usage 

1065 

1066 

1067def disk_io_counters(perdisk=False): 

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

1069 system as a dict of raw tuples. 

1070 """ 

1071 

1072 def read_procfs(): 

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

1074 # have 3 variations. 

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

1076 # "3 0 8 hda 8 8 8 8 8 8 8 8 8 8 8" 

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

1078 # name is in another position, like this: 

1079 # "3 0 hda 8 8 8 8 8 8 8 8 8 8 8" 

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

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

1082 # "3 1 hda1 8 8 8 8" 

1083 # 4.18+ has 4 fields added: 

1084 # "3 0 hda 8 8 8 8 8 8 8 8 8 8 8 0 0 0 0" 

1085 # 5.5 has 2 more fields. 

1086 # See: 

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

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

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

1090 lines = f.readlines() 

1091 for line in lines: 

1092 fields = line.split() 

1093 flen = len(fields) 

1094 # fmt: off 

1095 if flen == 15: 

1096 # Linux 2.4 

1097 name = fields[3] 

1098 reads = int(fields[2]) 

1099 (reads_merged, rbytes, rtime, writes, writes_merged, 

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

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

1102 # Linux 2.6+, line referring to a disk 

1103 name = fields[2] 

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

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

1106 elif flen == 7: 

1107 # Linux 2.6+, line referring to a partition 

1108 name = fields[2] 

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

1110 rtime = wtime = reads_merged = writes_merged = busy_time = 0 

1111 else: 

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

1113 raise ValueError(msg) 

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

1115 reads_merged, writes_merged, busy_time) 

1116 # fmt: on 

1117 

1118 def read_sysfs(): 

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

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

1121 if 'stat' not in files: 

1122 continue 

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

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

1125 name = os.path.basename(root) 

1126 # fmt: off 

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

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

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

1130 wtime, reads_merged, writes_merged, busy_time) 

1131 # fmt: on 

1132 

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

1134 gen = read_procfs() 

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

1136 gen = read_sysfs() 

1137 else: 

1138 msg = ( 

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

1140 " this system" 

1141 ) 

1142 raise NotImplementedError(msg) 

1143 

1144 retdict = {} 

1145 for entry in gen: 

1146 # fmt: off 

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

1148 writes_merged, busy_time) = entry 

1149 if not perdisk and not is_storage_device(name): 

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

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

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

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

1154 # of their own: 

1155 # $ cat /proc/diskstats 

1156 # 259 0 sda 10485760 ... 

1157 # 259 1 sda1 5186039 ... 

1158 # 259 1 sda2 5082039 ... 

1159 # See: 

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

1161 continue 

1162 

1163 rbytes *= DISK_SECTOR_SIZE 

1164 wbytes *= DISK_SECTOR_SIZE 

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

1166 reads_merged, writes_merged, busy_time) 

1167 # fmt: on 

1168 

1169 return retdict 

1170 

1171 

1172class RootFsDeviceFinder: 

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

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

1175 obtain the real device path. Resources: 

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

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

1178 """ 

1179 

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

1181 

1182 def __init__(self): 

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

1184 self.major = os.major(dev) 

1185 self.minor = os.minor(dev) 

1186 

1187 def ask_proc_partitions(self): 

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

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

1190 fields = line.split() 

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

1192 continue 

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

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

1195 name = fields[3] 

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

1197 if name: # just for extra safety 

1198 return f"/dev/{name}" 

1199 

1200 def ask_sys_dev_block(self): 

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

1202 with open_text(path) as f: 

1203 for line in f: 

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

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

1206 if name: # just for extra safety 

1207 return f"/dev/{name}" 

1208 

1209 def ask_sys_class_block(self): 

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

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

1212 for file in files: 

1213 try: 

1214 f = open_text(file) 

1215 except FileNotFoundError: # race condition 

1216 continue 

1217 else: 

1218 with f: 

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

1220 if data == needle: 

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

1222 return f"/dev/{name}" 

1223 

1224 def find(self): 

1225 path = None 

1226 if path is None: 

1227 try: 

1228 path = self.ask_proc_partitions() 

1229 except OSError as err: 

1230 debug(err) 

1231 if path is None: 

1232 try: 

1233 path = self.ask_sys_dev_block() 

1234 except OSError as err: 

1235 debug(err) 

1236 if path is None: 

1237 try: 

1238 path = self.ask_sys_class_block() 

1239 except OSError as err: 

1240 debug(err) 

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

1242 # coded, so we want to be sure. 

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

1244 return path 

1245 

1246 

1247def disk_partitions(all=False): 

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

1249 fstypes = set() 

1250 procfs_path = get_procfs_path() 

1251 if not all: 

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

1253 for line in f: 

1254 line = line.strip() 

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

1256 fstypes.add(line.strip()) 

1257 else: 

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

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

1260 if fstype == "zfs": 

1261 fstypes.add("zfs") 

1262 

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

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

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

1266 else: 

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

1268 

1269 retlist = [] 

1270 partitions = cext.disk_partitions(mounts_path) 

1271 for partition in partitions: 

1272 device, mountpoint, fstype, opts = partition 

1273 if device == 'none': 

1274 device = '' 

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

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

1277 if not all: 

1278 if not device or fstype not in fstypes: 

1279 continue 

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

1281 retlist.append(ntuple) 

1282 

1283 return retlist 

1284 

1285 

1286# ===================================================================== 

1287# --- sensors 

1288# ===================================================================== 

1289 

1290 

1291def sensors_temperatures(): 

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

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

1294 temperatures. 

1295 

1296 Implementation notes: 

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

1298 retrieve this info, and this implementation relies on it 

1299 only (old distros will probably use something else) 

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

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

1302 difficult to parse 

1303 """ 

1304 ret = collections.defaultdict(list) 

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

1306 # CentOS has an intermediate /device directory: 

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

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

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

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

1311 

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

1313 # /sys/class/hwmon/ 

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

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

1316 basenames2 = glob.glob( 

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

1318 ) 

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

1320 for name in basenames2: 

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

1322 if altname not in basenames: 

1323 basenames.append(name) 

1324 

1325 for base in basenames: 

1326 try: 

1327 path = base + '_input' 

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

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

1330 unit_name = cat(path).strip() 

1331 except (OSError, ValueError): 

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

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

1334 # is a stinky broken mess. 

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

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

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

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

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

1340 continue 

1341 

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

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

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

1345 

1346 if high is not None: 

1347 try: 

1348 high = float(high) / 1000.0 

1349 except ValueError: 

1350 high = None 

1351 if critical is not None: 

1352 try: 

1353 critical = float(critical) / 1000.0 

1354 except ValueError: 

1355 critical = None 

1356 

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

1358 

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

1360 if not basenames: 

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

1362 basenames = sorted(set(basenames)) 

1363 

1364 for base in basenames: 

1365 try: 

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

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

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

1369 unit_name = cat(path).strip() 

1370 except (OSError, ValueError) as err: 

1371 debug(err) 

1372 continue 

1373 

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

1375 trip_points = { 

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

1377 for p in trip_paths 

1378 } 

1379 critical = None 

1380 high = None 

1381 for trip_point in trip_points: 

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

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

1384 if trip_type == 'critical': 

1385 critical = bcat( 

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

1387 ) 

1388 elif trip_type == 'high': 

1389 high = bcat( 

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

1391 ) 

1392 

1393 if high is not None: 

1394 try: 

1395 high = float(high) / 1000.0 

1396 except ValueError: 

1397 high = None 

1398 if critical is not None: 

1399 try: 

1400 critical = float(critical) / 1000.0 

1401 except ValueError: 

1402 critical = None 

1403 

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

1405 

1406 return dict(ret) 

1407 

1408 

1409def sensors_fans(): 

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

1411 dict including hardware label and current speed. 

1412 

1413 Implementation notes: 

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

1415 retrieve this info, and this implementation relies on it 

1416 only (old distros will probably use something else) 

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

1418 """ 

1419 ret = collections.defaultdict(list) 

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

1421 if not basenames: 

1422 # CentOS has an intermediate /device directory: 

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

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

1425 

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

1427 for base in basenames: 

1428 try: 

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

1430 except OSError as err: 

1431 debug(err) 

1432 continue 

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

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

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

1436 

1437 return dict(ret) 

1438 

1439 

1440def sensors_battery(): 

1441 """Return battery information. 

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

1443 directory structure may vary and provide files with the same 

1444 meaning but under different names, see: 

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

1446 """ 

1447 null = object() 

1448 

1449 def multi_bcat(*paths): 

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

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

1452 """ 

1453 for path in paths: 

1454 ret = bcat(path, fallback=null) 

1455 if ret != null: 

1456 try: 

1457 return int(ret) 

1458 except ValueError: 

1459 return ret.strip() 

1460 return None 

1461 

1462 bats = [ 

1463 x 

1464 for x in os.listdir(POWER_SUPPLY_PATH) 

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

1466 ] 

1467 if not bats: 

1468 return None 

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

1470 # some rare exceptions: 

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

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

1473 

1474 # Base metrics. 

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

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

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

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

1479 

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

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

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

1483 try: 

1484 percent = 100.0 * energy_now / energy_full 

1485 except ZeroDivisionError: 

1486 percent = 0.0 

1487 else: 

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

1489 if percent == -1: 

1490 return None 

1491 

1492 # Is AC power cable plugged in? 

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

1494 # it's called "AC". 

1495 power_plugged = None 

1496 online = multi_bcat( 

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

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

1499 ) 

1500 if online is not None: 

1501 power_plugged = online == 1 

1502 else: 

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

1504 if status == "discharging": 

1505 power_plugged = False 

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

1507 power_plugged = True 

1508 

1509 # Seconds left. 

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

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

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

1513 if power_plugged: 

1514 secsleft = _common.POWER_TIME_UNLIMITED 

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

1516 try: 

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

1518 except ZeroDivisionError: 

1519 secsleft = _common.POWER_TIME_UNKNOWN 

1520 elif time_to_empty is not None: 

1521 secsleft = int(time_to_empty * 60) 

1522 if secsleft < 0: 

1523 secsleft = _common.POWER_TIME_UNKNOWN 

1524 else: 

1525 secsleft = _common.POWER_TIME_UNKNOWN 

1526 

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

1528 

1529 

1530# ===================================================================== 

1531# --- other system functions 

1532# ===================================================================== 

1533 

1534 

1535def users(): 

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

1537 retlist = [] 

1538 rawlist = cext.users() 

1539 for item in rawlist: 

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

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

1542 retlist.append(nt) 

1543 return retlist 

1544 

1545 

1546def boot_time(): 

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

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

1549 with open_binary(path) as f: 

1550 for line in f: 

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

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

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

1554 raise RuntimeError(msg) 

1555 

1556 

1557# ===================================================================== 

1558# --- processes 

1559# ===================================================================== 

1560 

1561 

1562def pids(): 

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

1564 path = get_procfs_path().encode(ENCODING) 

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

1566 

1567 

1568def pid_exists(pid): 

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

1570 supported (always return False). 

1571 """ 

1572 if not _psposix.pid_exists(pid): 

1573 return False 

1574 else: 

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

1576 # (thread IDs). 

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

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

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

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

1581 # only, see: 

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

1583 try: 

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

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

1586 # 'return pid in pids()' 

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

1588 with open_binary(path) as f: 

1589 for line in f: 

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

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

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

1593 # dealing with a process PID. 

1594 return tgid == pid 

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

1596 raise ValueError(msg) 

1597 except (OSError, ValueError): 

1598 return pid in pids() 

1599 

1600 

1601def ppid_map(): 

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

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

1604 """ 

1605 ret = {} 

1606 procfs_path = get_procfs_path() 

1607 for pid in pids(): 

1608 try: 

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

1610 data = f.read() 

1611 except (FileNotFoundError, ProcessLookupError): 

1612 pass 

1613 except PermissionError as err: 

1614 raise AccessDenied(pid) from err 

1615 else: 

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

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

1618 ppid = int(dset[1]) 

1619 ret[pid] = ppid 

1620 return ret 

1621 

1622 

1623def wrap_exceptions(fun): 

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

1625 into NoSuchProcess and AccessDenied. 

1626 """ 

1627 

1628 @functools.wraps(fun) 

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

1630 pid, name = self.pid, self._name 

1631 try: 

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

1633 except PermissionError as err: 

1634 raise AccessDenied(pid, name) from err 

1635 except ProcessLookupError as err: 

1636 self._raise_if_zombie() 

1637 raise NoSuchProcess(pid, name) from err 

1638 except FileNotFoundError as err: 

1639 self._raise_if_zombie() 

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

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

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

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

1644 raise NoSuchProcess(pid, name) from err 

1645 raise 

1646 

1647 return wrapper 

1648 

1649 

1650class Process: 

1651 """Linux process implementation.""" 

1652 

1653 __slots__ = [ 

1654 "_cache", 

1655 "_ctime", 

1656 "_name", 

1657 "_ppid", 

1658 "_procfs_path", 

1659 "pid", 

1660 ] 

1661 

1662 def __init__(self, pid): 

1663 self.pid = pid 

1664 self._name = None 

1665 self._ppid = None 

1666 self._ctime = None 

1667 self._procfs_path = get_procfs_path() 

1668 

1669 def _is_zombie(self): 

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

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

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

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

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

1675 # exception. 

1676 try: 

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

1678 except OSError: 

1679 return False 

1680 else: 

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

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

1683 return status == b"Z" 

1684 

1685 def _raise_if_zombie(self): 

1686 if self._is_zombie(): 

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

1688 

1689 def _raise_if_not_alive(self): 

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

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

1692 # incorrect or incomplete result. 

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

1694 

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

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

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

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

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

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

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

1702 try: 

1703 return readlink(path) 

1704 except (FileNotFoundError, ProcessLookupError): 

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

1706 self._raise_if_zombie() 

1707 if fallback is not UNSET: 

1708 return fallback 

1709 raise 

1710 

1711 @wrap_exceptions 

1712 @memoize_when_activated 

1713 def _parse_stat_file(self): 

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

1715 process info. 

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

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

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

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

1720 in use. 

1721 """ 

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

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

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

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

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

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

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

1729 

1730 ret = {} 

1731 ret['name'] = name 

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

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

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

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

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

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

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

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

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

1741 try: 

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

1743 except IndexError: 

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

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

1746 ret['blkio_ticks'] = 0 

1747 

1748 return ret 

1749 

1750 @wrap_exceptions 

1751 @memoize_when_activated 

1752 def _read_status_file(self): 

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

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

1755 in use. 

1756 """ 

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

1758 return f.read() 

1759 

1760 @wrap_exceptions 

1761 @memoize_when_activated 

1762 def _read_smaps_file(self): 

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

1764 return f.read().strip() 

1765 

1766 def oneshot_enter(self): 

1767 self._parse_stat_file.cache_activate(self) 

1768 self._read_status_file.cache_activate(self) 

1769 self._read_smaps_file.cache_activate(self) 

1770 

1771 def oneshot_exit(self): 

1772 self._parse_stat_file.cache_deactivate(self) 

1773 self._read_status_file.cache_deactivate(self) 

1774 self._read_smaps_file.cache_deactivate(self) 

1775 

1776 @wrap_exceptions 

1777 def name(self): 

1778 # XXX - gets changed later and probably needs refactoring 

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

1780 

1781 @wrap_exceptions 

1782 def exe(self): 

1783 return self._readlink( 

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

1785 ) 

1786 

1787 @wrap_exceptions 

1788 def cmdline(self): 

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

1790 data = f.read() 

1791 if not data: 

1792 # may happen in case of zombie process 

1793 self._raise_if_zombie() 

1794 return [] 

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

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

1797 # some processes may change their cmdline after being started 

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

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

1800 # Chrome process is an example. See: 

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

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

1803 if data.endswith(sep): 

1804 data = data[:-1] 

1805 cmdline = data.split(sep) 

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

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

1808 # issues/1179#issuecomment-552984549 

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

1810 cmdline = data.split(' ') 

1811 return cmdline 

1812 

1813 @wrap_exceptions 

1814 def environ(self): 

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

1816 data = f.read() 

1817 return parse_environ_block(data) 

1818 

1819 @wrap_exceptions 

1820 def terminal(self): 

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

1822 tmap = _psposix.get_terminal_map() 

1823 try: 

1824 return tmap[tty_nr] 

1825 except KeyError: 

1826 return None 

1827 

1828 # May not be available on old kernels. 

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

1830 

1831 @wrap_exceptions 

1832 def io_counters(self): 

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

1834 fields = {} 

1835 with open_binary(fname) as f: 

1836 for line in f: 

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

1838 line = line.strip() 

1839 if line: 

1840 try: 

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

1842 except ValueError: 

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

1844 continue 

1845 else: 

1846 fields[name] = int(value) 

1847 if not fields: 

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

1849 raise RuntimeError(msg) 

1850 try: 

1851 return pio( 

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

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

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

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

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

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

1858 ) 

1859 except KeyError as err: 

1860 msg = ( 

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

1862 f" fields are {fields!r}" 

1863 ) 

1864 raise ValueError(msg) from None 

1865 

1866 @wrap_exceptions 

1867 def cpu_times(self): 

1868 values = self._parse_stat_file() 

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

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

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

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

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

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

1875 

1876 @wrap_exceptions 

1877 def cpu_num(self): 

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

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

1880 

1881 @wrap_exceptions 

1882 def wait(self, timeout=None): 

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

1884 

1885 @wrap_exceptions 

1886 def create_time(self, monotonic=False): 

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

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

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

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

1891 # changes and is unaffected by system clock updates. 

1892 if self._ctime is None: 

1893 self._ctime = ( 

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

1895 ) 

1896 if monotonic: 

1897 return self._ctime 

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

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

1900 return self._ctime + boot_time() 

1901 

1902 @wrap_exceptions 

1903 def memory_info(self): 

1904 # ============================================================ 

1905 # | FIELD | DESCRIPTION | AKA | TOP | 

1906 # ============================================================ 

1907 # | rss | resident set size | | RES | 

1908 # | vms | total program size | size | VIRT | 

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

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

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

1912 # | data | data + stack | drs | DATA | 

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

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

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

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

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

1918 ) 

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

1920 

1921 if HAS_PROC_SMAPS_ROLLUP or HAS_PROC_SMAPS: 

1922 

1923 def _parse_smaps_rollup(self): 

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

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

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

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

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

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

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

1931 # compared to /proc/pid/smaps_rollup. 

1932 uss = pss = swap = 0 

1933 with open_binary( 

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

1935 ) as f: 

1936 for line in f: 

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

1938 # Private_Clean, Private_Dirty, Private_Hugetlb 

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

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

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

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

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

1944 return (uss, pss, swap) 

1945 

1946 @wrap_exceptions 

1947 def _parse_smaps( 

1948 self, 

1949 # Gets Private_Clean, Private_Dirty, Private_Hugetlb. 

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

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

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

1953 ): 

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

1955 # CONFIG_MMU kernel configuration option is not enabled. 

1956 

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

1958 # line by line. 

1959 # 

1960 # You might be tempted to calculate USS by subtracting 

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

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

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

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

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

1966 # correct information. 

1967 smaps_data = self._read_smaps_file() 

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

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

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

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

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

1973 return (uss, pss, swap) 

1974 

1975 @wrap_exceptions 

1976 def memory_full_info(self): 

1977 if HAS_PROC_SMAPS_ROLLUP: # faster 

1978 try: 

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

1980 except (ProcessLookupError, FileNotFoundError): 

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

1982 else: 

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

1984 basic_mem = self.memory_info() 

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

1986 

1987 else: 

1988 memory_full_info = memory_info 

1989 

1990 if HAS_PROC_SMAPS: 

1991 

1992 @wrap_exceptions 

1993 def memory_maps(self): 

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

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

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

1997 

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

1999 CONFIG_MMU kernel configuration option is not enabled. 

2000 """ 

2001 

2002 def get_blocks(lines, current_block): 

2003 data = {} 

2004 for line in lines: 

2005 fields = line.split(None, 5) 

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

2007 # new block section 

2008 yield (current_block.pop(), data) 

2009 current_block.append(line) 

2010 else: 

2011 try: 

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

2013 except (ValueError, IndexError): 

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

2015 # see issue #369 

2016 continue 

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

2018 raise ValueError(msg) from None 

2019 yield (current_block.pop(), data) 

2020 

2021 data = self._read_smaps_file() 

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

2023 # zombies. 

2024 if not data: 

2025 self._raise_if_zombie() 

2026 return [] 

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

2028 ls = [] 

2029 first_line = lines.pop(0) 

2030 current_block = [first_line] 

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

2032 hfields = header.split(None, 5) 

2033 try: 

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

2035 except ValueError: 

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

2037 if not path: 

2038 path = '[anon]' 

2039 else: 

2040 path = decode(path) 

2041 path = path.strip() 

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

2043 path 

2044 ): 

2045 path = path[:-10] 

2046 item = ( 

2047 decode(addr), 

2048 decode(perms), 

2049 path, 

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

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

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

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

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

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

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

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

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

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

2060 ) 

2061 ls.append(item) 

2062 return ls 

2063 

2064 @wrap_exceptions 

2065 def cwd(self): 

2066 return self._readlink( 

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

2068 ) 

2069 

2070 @wrap_exceptions 

2071 def num_ctx_switches( 

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

2073 ): 

2074 data = self._read_status_file() 

2075 ctxsw = _ctxsw_re.findall(data) 

2076 if not ctxsw: 

2077 msg = ( 

2078 "'voluntary_ctxt_switches' and" 

2079 " 'nonvoluntary_ctxt_switches'lines were not found in" 

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

2081 " probably older than 2.6.23" 

2082 ) 

2083 raise NotImplementedError(msg) 

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

2085 

2086 @wrap_exceptions 

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

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

2089 data = self._read_status_file() 

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

2091 

2092 @wrap_exceptions 

2093 def threads(self): 

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

2095 thread_ids.sort() 

2096 retlist = [] 

2097 hit_enoent = False 

2098 for thread_id in thread_ids: 

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

2100 try: 

2101 with open_binary(fname) as f: 

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

2103 except (FileNotFoundError, ProcessLookupError): 

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

2105 # it means thread disappeared on us 

2106 hit_enoent = True 

2107 continue 

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

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

2110 values = st.split(b' ') 

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

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

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

2114 retlist.append(ntuple) 

2115 if hit_enoent: 

2116 self._raise_if_not_alive() 

2117 return retlist 

2118 

2119 @wrap_exceptions 

2120 def nice_get(self): 

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

2122 # data = f.read() 

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

2124 

2125 # Use C implementation 

2126 return cext.proc_priority_get(self.pid) 

2127 

2128 @wrap_exceptions 

2129 def nice_set(self, value): 

2130 return cext.proc_priority_set(self.pid, value) 

2131 

2132 # starting from CentOS 6. 

2133 if HAS_CPU_AFFINITY: 

2134 

2135 @wrap_exceptions 

2136 def cpu_affinity_get(self): 

2137 return cext.proc_cpu_affinity_get(self.pid) 

2138 

2139 def _get_eligible_cpus( 

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

2141 ): 

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

2143 data = self._read_status_file() 

2144 match = _re.findall(data) 

2145 if match: 

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

2147 else: 

2148 return list(range(len(per_cpu_times()))) 

2149 

2150 @wrap_exceptions 

2151 def cpu_affinity_set(self, cpus): 

2152 try: 

2153 cext.proc_cpu_affinity_set(self.pid, cpus) 

2154 except (OSError, ValueError) as err: 

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

2156 eligible_cpus = self._get_eligible_cpus() 

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

2158 for cpu in cpus: 

2159 if cpu not in all_cpus: 

2160 msg = ( 

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

2162 f" {eligible_cpus!r}" 

2163 ) 

2164 raise ValueError(msg) from None 

2165 if cpu not in eligible_cpus: 

2166 msg = ( 

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

2168 f" between {eligible_cpus}" 

2169 ) 

2170 raise ValueError(msg) from err 

2171 raise 

2172 

2173 # only starting from kernel 2.6.13 

2174 if HAS_PROC_IO_PRIORITY: 

2175 

2176 @wrap_exceptions 

2177 def ionice_get(self): 

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

2179 ioclass = IOPriority(ioclass) 

2180 return _common.pionice(ioclass, value) 

2181 

2182 @wrap_exceptions 

2183 def ionice_set(self, ioclass, value): 

2184 if value is None: 

2185 value = 0 

2186 if value and ioclass in { 

2187 IOPriority.IOPRIO_CLASS_IDLE, 

2188 IOPriority.IOPRIO_CLASS_NONE, 

2189 }: 

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

2191 raise ValueError(msg) 

2192 if value < 0 or value > 7: 

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

2194 raise ValueError(msg) 

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

2196 

2197 if hasattr(resource, "prlimit"): 

2198 

2199 @wrap_exceptions 

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

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

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

2203 # PID 0 is not supported on Linux. 

2204 if self.pid == 0: 

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

2206 raise ValueError(msg) 

2207 try: 

2208 if limits is None: 

2209 # get 

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

2211 else: 

2212 # set 

2213 if len(limits) != 2: 

2214 msg = ( 

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

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

2217 ) 

2218 raise ValueError(msg) 

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

2220 except OSError as err: 

2221 if err.errno == errno.ENOSYS: 

2222 # I saw this happening on Travis: 

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

2224 self._raise_if_zombie() 

2225 raise 

2226 

2227 @wrap_exceptions 

2228 def status(self): 

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

2230 letter = letter.decode() 

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

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

2233 

2234 @wrap_exceptions 

2235 def open_files(self): 

2236 retlist = [] 

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

2238 hit_enoent = False 

2239 for fd in files: 

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

2241 try: 

2242 path = readlink(file) 

2243 except (FileNotFoundError, ProcessLookupError): 

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

2245 hit_enoent = True 

2246 continue 

2247 except OSError as err: 

2248 if err.errno == errno.EINVAL: 

2249 # not a link 

2250 continue 

2251 if err.errno == errno.ENAMETOOLONG: 

2252 # file name too long 

2253 debug(err) 

2254 continue 

2255 raise 

2256 else: 

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

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

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

2260 # absolute path though. 

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

2262 # Get file position and flags. 

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

2264 try: 

2265 with open_binary(file) as f: 

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

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

2268 except (FileNotFoundError, ProcessLookupError): 

2269 # fd gone in the meantime; process may 

2270 # still be alive 

2271 hit_enoent = True 

2272 else: 

2273 mode = file_flags_to_mode(flags) 

2274 ntuple = popenfile( 

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

2276 ) 

2277 retlist.append(ntuple) 

2278 if hit_enoent: 

2279 self._raise_if_not_alive() 

2280 return retlist 

2281 

2282 @wrap_exceptions 

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

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

2285 self._raise_if_not_alive() 

2286 return ret 

2287 

2288 @wrap_exceptions 

2289 def num_fds(self): 

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

2291 

2292 @wrap_exceptions 

2293 def ppid(self): 

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

2295 

2296 @wrap_exceptions 

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

2298 data = self._read_status_file() 

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

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

2301 

2302 @wrap_exceptions 

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

2304 data = self._read_status_file() 

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

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