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

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1300 statements  

1# Copyright (c) 2009, Giampaolo Rodola'. All rights reserved. 

2# Use of this source code is governed by a BSD-style license that can be 

3# found in the LICENSE file. 

4 

5"""Linux platform implementation.""" 

6 

7import base64 

8import collections 

9import enum 

10import errno 

11import functools 

12import glob 

13import os 

14import re 

15import resource 

16import socket 

17import stat 

18import struct 

19import sys 

20import warnings 

21from collections import defaultdict 

22 

23import psutil 

24 

25from . import _ntuples as ntp 

26from . import _psposix 

27from . import _psutil 

28from ._common import ENCODING 

29from ._common import AccessDenied 

30from ._common import NoSuchProcess 

31from ._common import ZombieProcess 

32from ._common import bcat 

33from ._common import cache_activate 

34from ._common import cache_deactivate 

35from ._common import cat 

36from ._common import debug 

37from ._common import decode 

38from ._common import isfile_strict 

39from ._common import memoize_when_activated 

40from ._common import open_binary 

41from ._common import open_text 

42from ._common import parse_environ_block 

43from ._common import path_exists_strict 

44from ._common import socktype_to_enum 

45from ._common import supports_ipv6 

46from ._common import usage_percent 

47from ._enums import BatteryTime 

48from ._enums import ConnectionStatus 

49from ._enums import NicDuplex 

50from ._enums import ProcessIOPriority 

51from ._enums import ProcessStatus 

52 

53__extra__all__ = [] 

54 

55 

56# ===================================================================== 

57# --- globals 

58# ===================================================================== 

59 

60 

61POWER_SUPPLY_PATH = "/sys/class/power_supply" 

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

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

64HAS_PROC_IO_PRIORITY = hasattr(_psutil, "proc_ioprio_get") 

65HAS_CPU_AFFINITY = hasattr(_psutil, "proc_cpu_affinity_get") 

66 

67# Number of clock ticks per second 

68CLOCK_TICKS = os.sysconf("SC_CLK_TCK") 

69PAGESIZE = _psutil.getpagesize() 

70LITTLE_ENDIAN = sys.byteorder == 'little' 

71UNSET = object() 

72 

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

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

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

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

77# throughout Linux source code: 

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

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

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

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

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

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

84DISK_SECTOR_SIZE = 512 

85 

86AddressFamily = enum.IntEnum( 

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

88) 

89AF_LINK = AddressFamily.AF_LINK 

90 

91 

92# See: 

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

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

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

96PROC_STATUSES = { 

97 "R": ProcessStatus.STATUS_RUNNING, 

98 "S": ProcessStatus.STATUS_SLEEPING, 

99 "D": ProcessStatus.STATUS_DISK_SLEEP, 

100 "T": ProcessStatus.STATUS_STOPPED, 

101 "t": ProcessStatus.STATUS_TRACING_STOP, 

102 "Z": ProcessStatus.STATUS_ZOMBIE, 

103 "X": ProcessStatus.STATUS_DEAD, 

104 "x": ProcessStatus.STATUS_DEAD, 

105 "K": ProcessStatus.STATUS_WAKE_KILL, 

106 "W": ProcessStatus.STATUS_WAKING, 

107 "I": ProcessStatus.STATUS_IDLE, 

108 "P": ProcessStatus.STATUS_PARKED, 

109} 

110 

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

112TCP_STATUSES = { 

113 "01": ConnectionStatus.CONN_ESTABLISHED, 

114 "02": ConnectionStatus.CONN_SYN_SENT, 

115 "03": ConnectionStatus.CONN_SYN_RECV, 

116 "04": ConnectionStatus.CONN_FIN_WAIT1, 

117 "05": ConnectionStatus.CONN_FIN_WAIT2, 

118 "06": ConnectionStatus.CONN_TIME_WAIT, 

119 "07": ConnectionStatus.CONN_CLOSE, 

120 "08": ConnectionStatus.CONN_CLOSE_WAIT, 

121 "09": ConnectionStatus.CONN_LAST_ACK, 

122 "0A": ConnectionStatus.CONN_LISTEN, 

123 "0B": ConnectionStatus.CONN_CLOSING, 

124} 

125 

126 

127# ===================================================================== 

128# --- utils 

129# ===================================================================== 

130 

131 

132def readlink(path): 

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

134 assert isinstance(path, str), path 

135 path = os.readlink(path) 

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

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

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

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

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

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

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

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

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

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

146 # don't care. 

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

148 path = path[:-10] 

149 return path 

150 

151 

152def file_flags_to_mode(flags): 

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

154 Used by Process.open_files(). 

155 """ 

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

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

158 if flags & os.O_APPEND: 

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

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

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

162 return mode 

163 

164 

165def is_storage_device(name): 

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

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

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

169 return True. 

170 """ 

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

172 # https://github.com/sysstat/sysstat/blob/97912938cd476/common.c#L208 

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

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

175 including_virtual = True 

176 if including_virtual: 

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

178 else: 

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

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

181 

182 

183# ===================================================================== 

184# --- system memory 

185# ===================================================================== 

186 

187 

188def calculate_avail_mem(mems): 

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

190 "MemAvailable", see: 

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

192 

193 This code reimplements the algorithm outlined here: 

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

195 commit/?id=34e431b0ae398fc54ea69ff85ec700722c9da773 

196 

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

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

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

200 column). 

201 

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

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

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

205 See: 

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

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

208 """ 

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

210 # https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=34e431b0ae398 

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

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

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

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

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

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

217 free = mems[b'MemFree:'] 

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

219 try: 

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

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

222 slab_reclaimable = mems[b'SReclaimable:'] 

223 except KeyError as err: 

224 debug( 

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

226 " approximation for calculating available memory" 

227 ) 

228 return fallback 

229 try: 

230 f = open_binary(f"{psutil.PROCFS_PATH}/zoneinfo") 

231 except OSError: 

232 return fallback # kernel 2.6.13 

233 

234 watermark_low = 0 

235 with f: 

236 for line in f: 

237 line = line.strip() 

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

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

240 watermark_low *= PAGESIZE 

241 

242 avail = free - watermark_low 

243 pagecache = lru_active_file + lru_inactive_file 

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

245 avail += pagecache 

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

247 return int(avail) 

248 

249 

250def memory_physical(): 

251 """Report physical memory stats. 

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

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

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

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

256 CLI tools. 

257 """ 

258 missing_fields = [] 

259 mems = {} 

260 with open_binary(f"{psutil.PROCFS_PATH}/meminfo") as f: 

261 for line in f: 

262 key, value = line.split(b':', 1) 

263 mems[key + b':'] = int(value.split()[0]) * 1024 

264 

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

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

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

268 total = mems[b'MemTotal:'] 

269 free = mems[b'MemFree:'] 

270 try: 

271 buffers = mems[b'Buffers:'] 

272 except KeyError: 

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

274 buffers = 0 

275 missing_fields.append('buffers') 

276 try: 

277 cached = mems[b"Cached:"] 

278 except KeyError: 

279 cached = 0 

280 missing_fields.append('cached') 

281 else: 

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

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

284 # This got changed in: 

285 # https://gitlab.com/procps-ng/procps/-/commit/05d751c4f 

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

287 

288 try: 

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

290 except KeyError: 

291 try: 

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

293 except KeyError: 

294 shared = 0 

295 missing_fields.append('shared') 

296 

297 try: 

298 active = mems[b"Active:"] 

299 except KeyError: 

300 active = 0 

301 missing_fields.append('active') 

302 

303 try: 

304 inactive = mems[b"Inactive:"] 

305 except KeyError: 

306 try: 

307 inactive = ( 

308 mems[b"Inact_dirty:"] 

309 + mems[b"Inact_clean:"] 

310 + mems[b"Inact_laundry:"] 

311 ) 

312 except KeyError: 

313 inactive = 0 

314 missing_fields.append('inactive') 

315 

316 try: 

317 slab = mems[b"Slab:"] 

318 except KeyError: 

319 slab = 0 

320 

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

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

323 # which matched htop. 

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

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

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

327 # - MemAvailable has been introduced in kernel 3.14 

328 try: 

329 avail = mems[b'MemAvailable:'] 

330 except KeyError: 

331 avail = calculate_avail_mem(mems) 

332 else: 

333 if avail == 0: 

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

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

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

337 # and it matches "free" CLI tool. 

338 avail = calculate_avail_mem(mems) 

339 

340 if avail < 0: 

341 avail = 0 

342 missing_fields.append('available') 

343 elif avail > total: 

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

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

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

347 # https://gitlab.com/procps-ng/procps/blob/24fd2605c51fcc/proc/sysinfo.c#L764 

348 avail = free 

349 

350 used = total - avail 

351 

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

353 

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

355 if missing_fields: 

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

357 ", ".join(missing_fields), 

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

359 ) 

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

361 

362 return ntp.smem( 

363 total, 

364 avail, 

365 percent, 

366 used, 

367 free, 

368 active, 

369 inactive, 

370 buffers, 

371 cached, 

372 shared, 

373 slab, 

374 ) 

375 

376 

377def memory_swap(): 

378 """Return swap memory metrics.""" 

379 mems = {} 

380 with open_binary(f"{psutil.PROCFS_PATH}/meminfo") as f: 

381 for line in f: 

382 # Note: some fields (e.g. "ShadowCallStack:10373888 kB") 

383 # may not have a space after the colon, see: 

384 # https://github.com/giampaolo/psutil/issues/2809 

385 key, value = line.split(b':', 1) 

386 mems[key + b':'] = int(value.split()[0]) * 1024 

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

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

389 # for linux containers, see: 

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

391 try: 

392 total = mems[b'SwapTotal:'] 

393 free = mems[b'SwapFree:'] 

394 except KeyError: 

395 _, _, _, _, total, free, unit_multiplier = _psutil.linux_sysinfo() 

396 total *= unit_multiplier 

397 free *= unit_multiplier 

398 

399 used = total - free 

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

401 # get pgin/pgouts 

402 try: 

403 f = open_binary(f"{psutil.PROCFS_PATH}/vmstat") 

404 except OSError as err: 

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

406 msg = ( 

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

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

409 ) 

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

411 sin = sout = 0 

412 else: 

413 with f: 

414 sin = sout = None 

415 for line in f: 

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

417 # bytes instead 

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

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

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

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

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

423 break 

424 else: 

425 # we might get here when dealing with exotic Linux 

426 # flavors, see: 

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

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

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

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

431 sin = sout = 0 

432 return ntp.sswap(total, used, free, percent, sin, sout) 

433 

434 

435# malloc / heap functions; require glibc 

436if hasattr(_psutil, "heap_info"): 

437 heap_info = _psutil.heap_info 

438 heap_trim = _psutil.heap_trim 

439 

440 

441# ===================================================================== 

442# --- CPU 

443# ===================================================================== 

444 

445 

446def cpu_times(): 

447 """Return a named tuple representing system-wide CPU times.""" 

448 

449 def lsget(lst, idx, field_name): 

450 try: 

451 return lst[idx] 

452 except IndexError: 

453 debug(f"can't get {field_name} CPU time; set it to 0") 

454 return 0.0 

455 

456 procfs_path = psutil.PROCFS_PATH 

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

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

459 nfields = len(ntp.scputimes._fields) 

460 raw = [float(x) / CLOCK_TICKS for x in values[1 : nfields + 1]] 

461 user, nice, system, idle = raw[:4] 

462 return ntp.scputimes( 

463 user, 

464 system, 

465 idle, 

466 nice, 

467 lsget(raw, 4, "iowait"), # Linux >= 2.5.41 

468 lsget(raw, 5, "irq"), # Linux >= 2.6.0 

469 lsget(raw, 6, "softirq"), # Linux >= 2.6.0 

470 lsget(raw, 7, "steal"), # Linux >= 2.6.11 

471 lsget(raw, 8, "guest"), # Linux >= 2.6.24 

472 lsget(raw, 9, "guest_nice"), # Linux >= 2.6.33 

473 ) 

474 

475 

476def per_cpu_times(): 

477 """Return a list of named tuples representing the CPU times 

478 for every CPU available on the system. 

479 """ 

480 procfs_path = psutil.PROCFS_PATH 

481 cpus = [] 

482 nfields = len(ntp.scputimes._fields) 

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

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

485 f.readline() 

486 for line in f: 

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

488 values = line.split() 

489 raw = [float(x) / CLOCK_TICKS for x in values[1 : nfields + 1]] 

490 user, nice, system, idle = raw[0], raw[1], raw[2], raw[3] 

491 entry = ntp.scputimes(user, system, idle, nice, *raw[4:]) 

492 cpus.append(entry) 

493 return cpus 

494 

495 

496def _parse_cpulist(cpulist): 

497 """Parse Linux CPU list string (e.g "0-3,8,10-11")""" 

498 cpulist = cpulist.strip() 

499 if not cpulist: 

500 return [] 

501 cpus = [] 

502 for chunk in cpulist.split(','): 

503 chunk = chunk.strip() 

504 if not chunk: 

505 continue 

506 if '-' in chunk: 

507 start, _, end = chunk.partition('-') 

508 start = int(start) 

509 end = int(end) 

510 if start > end: 

511 msg = f"invalid CPU range {chunk!r}" 

512 raise ValueError(msg) 

513 cpus.extend(range(start, end + 1)) 

514 else: 

515 cpus.append(int(chunk)) 

516 return cpus 

517 

518 

519def cpu_count_logical(): 

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

521 try: 

522 return os.sysconf("SC_NPROCESSORS_ONLN") 

523 except ValueError: 

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

525 num = 0 

526 with open_binary(f"{psutil.PROCFS_PATH}/cpuinfo") as f: 

527 for line in f: 

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

529 num += 1 

530 

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

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

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

534 if num == 0: 

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

536 with open_text(f"{psutil.PROCFS_PATH}/stat") as f: 

537 for line in f: 

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

539 if search.match(line): 

540 num += 1 

541 

542 if num == 0: 

543 # mimic os.cpu_count() 

544 return None 

545 return num 

546 

547 

548def cpu_count_cores(): 

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

550 # Method #1 

551 ls = set() 

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

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

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

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

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

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

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

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

560 with open_binary(path) as f: 

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

562 result = len(ls) 

563 if result != 0: 

564 return result 

565 

566 # Method #2 

567 mapping = {} 

568 current_info = {} 

569 with open_binary(f"{psutil.PROCFS_PATH}/cpuinfo") as f: 

570 for line in f: 

571 line = line.strip().lower() 

572 if not line: 

573 # new section 

574 try: 

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

576 b'cpu cores' 

577 ] 

578 except KeyError: 

579 pass 

580 current_info = {} 

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

582 # ongoing section 

583 key, value = line.split(b':', 1) 

584 current_info[key.strip()] = int(value) 

585 

586 result = sum(mapping.values()) 

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

588 

589 

590def cpu_stats(): 

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

592 with open_binary(f"{psutil.PROCFS_PATH}/stat") as f: 

593 ctx_switches = None 

594 interrupts = None 

595 soft_interrupts = None 

596 for line in f: 

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

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

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

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

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

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

603 if ( 

604 ctx_switches is not None 

605 and soft_interrupts is not None 

606 and interrupts is not None 

607 ): 

608 break 

609 syscalls = 0 

610 return ntp.scpustats(ctx_switches, interrupts, soft_interrupts, syscalls) 

611 

612 

613def _cpu_get_cpuinfo_freq(): 

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

615 ret = [] 

616 with open_binary(f"{psutil.PROCFS_PATH}/cpuinfo") as f: 

617 for line in f: 

618 key, _, value = line.partition(b':') 

619 key = key.strip().lower() 

620 # x86 says "cpu MHz", ppc "clock" (with a MHz suffix), 

621 # s390x "cpu MHz dynamic" plus a "static" one we skip. 

622 # https://github.com/torvalds/linux/blob/master/arch/powerpc/kernel/setup-common.c 

623 # https://github.com/torvalds/linux/blob/master/arch/s390/kernel/processor.c 

624 if key in {b'cpu mhz', b'clock', b'cpu mhz dynamic'}: 

625 value = value.strip() 

626 if value.endswith(b"MHz"): 

627 value = value[:-3] 

628 ret.append(float(value)) 

629 return ret 

630 

631 

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

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

634): 

635 

636 def cpu_freq(): 

637 """Return frequency metrics for all CPUs. 

638 Contrarily to other OSes, Linux updates these values in 

639 real-time. 

640 """ 

641 pjoin = os.path.join 

642 cpuinfo_freqs = _cpu_get_cpuinfo_freq() 

643 paths = glob.glob( 

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

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

646 

647 # One policy may govern more than one CPU, so ask each policy 

648 # which CPUs it affects instead of assuming one per CPU. Offline 

649 # CPUs are listed by no policy, and are therefore left out. 

650 # https://github.com/giampaolo/psutil/issues/2512 

651 cpu_to_path = {} 

652 for path in paths: 

653 affected = bcat(pjoin(path, "affected_cpus"), fallback=None) 

654 if affected is None: 

655 cpu_to_path[int(re.search(r"[0-9]+", path).group())] = path 

656 else: 

657 for cpu in affected.split(): 

658 cpu_to_path[int(cpu)] = path 

659 

660 ret = [] 

661 for i, cpu in enumerate(sorted(cpu_to_path)): 

662 path = cpu_to_path[cpu] 

663 if len(cpu_to_path) == len(cpuinfo_freqs): 

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

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

666 curr = cpuinfo_freqs[i] * 1000 

667 else: 

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

669 if curr is None: 

670 # Likely an old RedHat, see: 

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

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

673 if curr is None: 

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

675 # If the CPU core is offline skip it instead of 

676 # reporting it as all zeroes, otherwise it drags 

677 # down the average frequency. See: 

678 # https://github.com/giampaolo/psutil/issues/2628 

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

680 continue 

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

682 raise NotImplementedError(msg) 

683 curr = int(curr) / 1000 

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

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

686 ret.append(ntp.scpufreq(curr, min_, max_)) 

687 return ret 

688 

689else: 

690 

691 def cpu_freq(): 

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

693 min and max frequencies are not available and are set to 0. 

694 """ 

695 return [ntp.scpufreq(x, 0.0, 0.0) for x in _cpu_get_cpuinfo_freq()] 

696 

697 

698# ===================================================================== 

699# --- network 

700# ===================================================================== 

701 

702 

703net_if_addrs = _psutil.net_if_addrs 

704 

705 

706class _Ipv6UnsupportedError(Exception): 

707 pass 

708 

709 

710class NetConnections: 

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

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

713 "netstat -an". 

714 

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

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

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

718 

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

720 """ 

721 

722 def __init__(self): 

723 # The string represents the basename of the corresponding 

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

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

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

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

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

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

730 self.tmap = { 

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

732 "tcp": (tcp4, tcp6), 

733 "tcp4": (tcp4,), 

734 "tcp6": (tcp6,), 

735 "udp": (udp4, udp6), 

736 "udp4": (udp4,), 

737 "udp6": (udp6,), 

738 "unix": (unix,), 

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

740 "inet4": (tcp4, udp4), 

741 "inet6": (tcp6, udp6), 

742 } 

743 self._procfs_path = None 

744 

745 def get_proc_inodes(self, pid): 

746 inodes = defaultdict(list) 

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

748 try: 

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

750 except (FileNotFoundError, ProcessLookupError): 

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

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

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

754 continue 

755 except OSError as err: 

756 if err.errno == errno.EINVAL: 

757 # not a link 

758 continue 

759 if err.errno == errno.ENAMETOOLONG: 

760 # file name too long 

761 debug(err) 

762 continue 

763 raise 

764 else: 

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

766 # the process is using a socket 

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

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

769 return inodes 

770 

771 def get_all_inodes(self): 

772 inodes = {} 

773 for pid in pids(): 

774 try: 

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

776 except (FileNotFoundError, ProcessLookupError, PermissionError): 

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

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

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

780 # and fd set to None anyway. 

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

782 # unlikely we can do any better. 

783 # ENOENT just means a PID disappeared on us. 

784 continue 

785 return inodes 

786 

787 @staticmethod 

788 def decode_address(addr, family): 

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

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

791 

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

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

794 

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

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

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

798 to an IP address. 

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

800 

801 Reference: 

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

803 """ 

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

805 port = int(port, 16) 

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

807 # no end-points connected 

808 if not port: 

809 return () 

810 ip = ip.encode('ascii') 

811 if family == socket.AF_INET: 

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

813 if LITTLE_ENDIAN: 

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

815 else: 

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

817 else: # IPv6 

818 ip = base64.b16decode(ip) 

819 try: 

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

821 if LITTLE_ENDIAN: 

822 ip = socket.inet_ntop( 

823 socket.AF_INET6, 

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

825 ) 

826 else: 

827 ip = socket.inet_ntop( 

828 socket.AF_INET6, 

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

830 ) 

831 except ValueError: 

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

833 if not supports_ipv6(): 

834 raise _Ipv6UnsupportedError from None 

835 raise 

836 return ntp.addr(ip, port) 

837 

838 @staticmethod 

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

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

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

842 # IPv6 not supported 

843 return 

844 with open_text(file) as f: 

845 f.readline() # skip the first line 

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

847 try: 

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

849 line.split()[:10] 

850 ) 

851 except ValueError: 

852 msg = ( 

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

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

855 ) 

856 raise RuntimeError(msg) from None 

857 if inode in inodes: 

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

859 # # out if there are multiple references to the 

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

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

862 # raise ValueError("ambiguous inode with multiple " 

863 # "PIDs references") 

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

865 else: 

866 pid, fd = None, -1 

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

868 continue 

869 else: 

870 if type_ == socket.SOCK_STREAM: 

871 status = TCP_STATUSES[status] 

872 else: 

873 status = ConnectionStatus.CONN_NONE 

874 try: 

875 laddr = NetConnections.decode_address(laddr, family) 

876 raddr = NetConnections.decode_address(raddr, family) 

877 except _Ipv6UnsupportedError: 

878 continue 

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

880 

881 @staticmethod 

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

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

884 with open_text(file) as f: 

885 f.readline() # skip the first line 

886 for line in f: 

887 tokens = line.split() 

888 try: 

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

890 except ValueError: 

891 if ' ' not in line: 

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

893 continue 

894 msg = ( 

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

896 ) 

897 raise RuntimeError(msg) # noqa: B904 

898 if inode in inodes: # noqa: SIM108 

899 # With UNIX sockets we can have a single inode 

900 # referencing many file descriptors. 

901 pairs = inodes[inode] 

902 else: 

903 pairs = [(None, -1)] 

904 for pid, fd in pairs: 

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

906 continue 

907 else: 

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

909 type_ = socktype_to_enum(int(type_)) 

910 # XXX: determining the remote endpoint of a 

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

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

913 raddr = "" 

914 status = ConnectionStatus.CONN_NONE 

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

916 

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

918 self._procfs_path = psutil.PROCFS_PATH 

919 if pid is not None: 

920 inodes = self.get_proc_inodes(pid) 

921 if not inodes: 

922 # no connections for this process 

923 return [] 

924 else: 

925 inodes = self.get_all_inodes() 

926 ret = set() 

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

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

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

930 ls = self.process_inet( 

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

932 ) 

933 else: 

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

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

936 if pid: 

937 conn = ntp.pconn(fd, family, type_, laddr, raddr, status) 

938 else: 

939 conn = ntp.sconn( 

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

941 ) 

942 ret.add(conn) 

943 return list(ret) 

944 

945 

946_net_connections = NetConnections() 

947 

948 

949def net_connections(kind='inet'): 

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

951 return _net_connections.retrieve(kind) 

952 

953 

954def net_io_counters(): 

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

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

957 """ 

958 with open_text(f"{psutil.PROCFS_PATH}/net/dev") as f: 

959 lines = f.readlines() 

960 retdict = {} 

961 for line in lines[2:]: 

962 colon = line.rfind(':') 

963 assert colon > 0, repr(line) 

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

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

966 

967 ( 

968 # in 

969 bytes_recv, 

970 packets_recv, 

971 errin, 

972 dropin, 

973 _fifoin, # unused 

974 _framein, # unused 

975 _compressedin, # unused 

976 _multicastin, # unused 

977 # out 

978 bytes_sent, 

979 packets_sent, 

980 errout, 

981 dropout, 

982 _fifoout, # unused 

983 _collisionsout, # unused 

984 _carrierout, # unused 

985 _compressedout, # unused 

986 ) = map(int, fields) 

987 

988 retdict[name] = ( 

989 bytes_sent, 

990 bytes_recv, 

991 packets_sent, 

992 packets_recv, 

993 errin, 

994 errout, 

995 dropin, 

996 dropout, 

997 ) 

998 return retdict 

999 

1000 

1001def net_if_stats(): 

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

1003 duplex_map = { 

1004 _psutil.DUPLEX_FULL: NicDuplex.NIC_DUPLEX_FULL, 

1005 _psutil.DUPLEX_HALF: NicDuplex.NIC_DUPLEX_HALF, 

1006 _psutil.DUPLEX_UNKNOWN: NicDuplex.NIC_DUPLEX_UNKNOWN, 

1007 } 

1008 names = net_io_counters().keys() 

1009 ret = {} 

1010 for name in names: 

1011 try: 

1012 mtu = _psutil.net_if_mtu(name) 

1013 flags = _psutil.net_if_flags(name) 

1014 duplex, speed = _psutil.net_if_duplex_speed(name) 

1015 except OSError as err: 

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

1017 if err.errno != errno.ENODEV: 

1018 raise 

1019 debug(err) 

1020 else: 

1021 output_flags = ','.join(flags) 

1022 isup = 'running' in flags 

1023 ret[name] = ntp.snicstats( 

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

1025 ) 

1026 return ret 

1027 

1028 

1029# ===================================================================== 

1030# --- disks 

1031# ===================================================================== 

1032 

1033 

1034disk_usage = _psposix.disk_usage 

1035 

1036 

1037def disk_io_counters(perdisk=False): 

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

1039 system as a dict of raw tuples. 

1040 """ 

1041 

1042 def read_procfs(): 

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

1044 # have 3 variations. 

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

1046 # "3 0 8 hda 8 8 8 8 8 8 8 8 8 8 8" 

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

1048 # name is in another position, like this: 

1049 # "3 0 hda 8 8 8 8 8 8 8 8 8 8 8" 

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

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

1052 # "3 1 hda1 8 8 8 8" 

1053 # 4.18+ has 4 fields added: 

1054 # "3 0 hda 8 8 8 8 8 8 8 8 8 8 8 0 0 0 0" 

1055 # 5.5 has 2 more fields. 

1056 # See: 

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

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

1059 with open_text(f"{psutil.PROCFS_PATH}/diskstats") as f: 

1060 lines = f.readlines() 

1061 for line in lines: 

1062 fields = line.split() 

1063 flen = len(fields) 

1064 # fmt: off 

1065 if flen == 15: 

1066 # Linux 2.4 

1067 name = fields[3] 

1068 reads = int(fields[2]) 

1069 (reads_merged, rbytes, rtime, writes, writes_merged, 

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

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

1072 # Linux 2.6+, line referring to a disk 

1073 name = fields[2] 

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

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

1076 elif flen == 7: 

1077 # Linux 2.6+, line referring to a partition 

1078 name = fields[2] 

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

1080 rtime = wtime = reads_merged = writes_merged = busy_time = 0 

1081 else: 

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

1083 raise ValueError(msg) 

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

1085 reads_merged, writes_merged, busy_time) 

1086 # fmt: on 

1087 

1088 def read_sysfs(): 

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

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

1091 if 'stat' not in files: 

1092 continue 

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

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

1095 name = os.path.basename(root) 

1096 # fmt: off 

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

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

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

1100 wtime, reads_merged, writes_merged, busy_time) 

1101 # fmt: on 

1102 

1103 if os.path.exists(f"{psutil.PROCFS_PATH}/diskstats"): 

1104 gen = read_procfs() 

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

1106 gen = read_sysfs() 

1107 else: 

1108 msg = ( 

1109 f"{psutil.PROCFS_PATH}/diskstats nor /sys/block are available on" 

1110 " this system" 

1111 ) 

1112 raise NotImplementedError(msg) 

1113 

1114 retdict = {} 

1115 for entry in gen: 

1116 # fmt: off 

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

1118 writes_merged, busy_time) = entry 

1119 if not perdisk and not is_storage_device(name): 

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

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

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

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

1124 # of their own: 

1125 # $ cat /proc/diskstats 

1126 # 259 0 sda 10485760 ... 

1127 # 259 1 sda1 5186039 ... 

1128 # 259 1 sda2 5082039 ... 

1129 # See: 

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

1131 continue 

1132 

1133 rbytes *= DISK_SECTOR_SIZE 

1134 wbytes *= DISK_SECTOR_SIZE 

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

1136 reads_merged, writes_merged, busy_time) 

1137 # fmt: on 

1138 

1139 return retdict 

1140 

1141 

1142class RootFsDeviceFinder: 

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

1144 This container class uses different strategies to try to obtain the 

1145 real device path. Resources: 

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

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

1148 """ 

1149 

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

1151 

1152 def __init__(self): 

1153 self.major, self.minor = self.find_major_minor() 

1154 

1155 def find_major_minor(self): 

1156 # Every "/dev/root" mount is on the boot device, so take the 

1157 # first one, like libmount. os.stat("/") is wrong in a chroot. 

1158 try: 

1159 f = open_text(f"{psutil.PROCFS_PATH}/self/mountinfo") 

1160 except OSError as err: 

1161 debug(err) 

1162 return None, None 

1163 with f: 

1164 for line in f: 

1165 fields = line.split() 

1166 try: 

1167 i = fields.index("-") 

1168 source = fields[i + 2] 

1169 major, minor = map(int, fields[2].split(":")) 

1170 except (ValueError, IndexError): 

1171 continue 

1172 if source == "/dev/root": 

1173 return major, minor 

1174 return None, None 

1175 

1176 def ask_proc_partitions(self): 

1177 with open_text(f"{psutil.PROCFS_PATH}/partitions") as f: 

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

1179 fields = line.split() 

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

1181 continue 

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

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

1184 name = fields[3] 

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

1186 if name: # just for extra safety 

1187 return f"/dev/{name}" 

1188 

1189 def ask_sys_dev_block(self): 

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

1191 with open_text(path) as f: 

1192 for line in f: 

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

1194 # just for extra safety 

1195 if name := line.strip().rpartition("DEVNAME=")[2]: 

1196 return f"/dev/{name}" 

1197 

1198 def ask_sys_class_block(self): 

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

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

1201 for file in files: 

1202 try: 

1203 f = open_text(file) 

1204 except FileNotFoundError: # race condition 

1205 continue 

1206 else: 

1207 with f: 

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

1209 if data == needle: 

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

1211 return f"/dev/{name}" 

1212 

1213 def find(self): 

1214 if self.major is None: # no "/dev/root" mount 

1215 return None 

1216 path = None 

1217 if path is None: 

1218 try: 

1219 path = self.ask_proc_partitions() 

1220 except OSError as err: 

1221 debug(err) 

1222 if path is None: 

1223 try: 

1224 path = self.ask_sys_dev_block() 

1225 except OSError as err: 

1226 debug(err) 

1227 if path is None: 

1228 try: 

1229 path = self.ask_sys_class_block() 

1230 except OSError as err: 

1231 debug(err) 

1232 

1233 # The "/dev/*" part of the path is hard coded, so make sure it's 

1234 # the same block device, like libmount does. 

1235 if path is not None: 

1236 try: 

1237 st = os.stat(path) 

1238 except OSError as err: 

1239 debug(err) 

1240 else: 

1241 devno = os.makedev(self.major, self.minor) 

1242 if stat.S_ISBLK(st.st_mode) and st.st_rdev == devno: 

1243 return path 

1244 

1245 

1246# NOTE: this function should follow libmount implementation as much 

1247# as possible. 

1248def disk_partitions(all=False): 

1249 """Return mounted disk partitions as a list of named tuples.""" 

1250 fstypes = set() 

1251 procfs_path = psutil.PROCFS_PATH 

1252 

1253 def fstypes_from_filesystems(): 

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

1255 for line in f: 

1256 line = line.strip() 

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

1258 fstypes.add(line.strip()) 

1259 else: 

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

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

1262 if fstype == "zfs": 

1263 fstypes.add("zfs") 

1264 return fstypes 

1265 

1266 def fstypes_from_mountinfo(): 

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

1268 for line in f: 

1269 fields = line.split() 

1270 try: 

1271 i = fields.index("-") 

1272 fstype, source = fields[i + 1], fields[i + 2] 

1273 except (ValueError, IndexError): 

1274 continue 

1275 # A /dev/ source is the equivalent of a non "nodev" 

1276 # entry in /proc/filesystems. 

1277 if source.startswith("/dev/") or fstype == "zfs": 

1278 fstypes.add(fstype) 

1279 return fstypes 

1280 

1281 if not all: 

1282 try: 

1283 fstypes = fstypes_from_filesystems() 

1284 except PermissionError: 

1285 fstypes = fstypes_from_mountinfo() 

1286 

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

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

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

1290 else: 

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

1292 

1293 retlist = [] 

1294 root_device = None 

1295 partitions = _psutil.disk_partitions(mounts_path) 

1296 for partition in partitions: 

1297 device, mountpoint, fstype, opts = partition 

1298 if device == 'none': 

1299 device = '' 

1300 if device == "/dev/root": 

1301 if root_device is None: 

1302 root_device = RootFsDeviceFinder().find() or device 

1303 device = root_device 

1304 if not all: 

1305 if not device or fstype not in fstypes: 

1306 continue 

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

1308 retlist.append(ntuple) 

1309 

1310 return retlist 

1311 

1312 

1313# ===================================================================== 

1314# --- sensors 

1315# ===================================================================== 

1316 

1317 

1318def sensors_temperatures(): 

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

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

1321 temperatures. 

1322 

1323 Implementation notes: 

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

1325 retrieve this info, and this implementation relies on it 

1326 only (old distros will probably use something else) 

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

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

1329 difficult to parse 

1330 """ 

1331 ret = collections.defaultdict(list) 

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

1333 # CentOS has an intermediate /device directory: 

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

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

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

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

1338 

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

1340 # /sys/class/hwmon/ 

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

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

1343 basenames2 = glob.glob( 

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

1345 ) 

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

1347 for name in basenames2: 

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

1349 if altname not in basenames: 

1350 basenames.append(name) 

1351 

1352 for base in basenames: 

1353 try: 

1354 path = base + '_input' 

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

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

1357 unit_name = cat(path).strip() 

1358 except (OSError, ValueError): 

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

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

1361 # is a stinky broken mess. 

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

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

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

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

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

1367 continue 

1368 

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

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

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

1372 

1373 if high is not None: 

1374 try: 

1375 high = float(high) / 1000.0 

1376 except ValueError: 

1377 high = None 

1378 if critical is not None: 

1379 try: 

1380 critical = float(critical) / 1000.0 

1381 except ValueError: 

1382 critical = None 

1383 

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

1385 

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

1387 if not basenames: 

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

1389 basenames = sorted(set(basenames)) 

1390 

1391 for base in basenames: 

1392 try: 

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

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

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

1396 unit_name = cat(path).strip() 

1397 except (OSError, ValueError) as err: 

1398 debug(err) 

1399 continue 

1400 

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

1402 trip_points = { 

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

1404 for p in trip_paths 

1405 } 

1406 critical = None 

1407 high = None 

1408 for trip_point in trip_points: 

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

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

1411 if trip_type == 'critical': 

1412 critical = bcat( 

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

1414 ) 

1415 elif trip_type == 'high': 

1416 high = bcat( 

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

1418 ) 

1419 

1420 if high is not None: 

1421 try: 

1422 high = float(high) / 1000.0 

1423 except ValueError: 

1424 high = None 

1425 if critical is not None: 

1426 try: 

1427 critical = float(critical) / 1000.0 

1428 except ValueError: 

1429 critical = None 

1430 

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

1432 

1433 return dict(ret) 

1434 

1435 

1436def sensors_fans(): 

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

1438 dict including hardware label and current speed. 

1439 

1440 Implementation notes: 

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

1442 retrieve this info, and this implementation relies on it 

1443 only (old distros will probably use something else) 

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

1445 """ 

1446 ret = collections.defaultdict(list) 

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

1448 if not basenames: 

1449 # CentOS has an intermediate /device directory: 

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

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

1452 

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

1454 for base in basenames: 

1455 try: 

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

1457 except OSError as err: 

1458 debug(err) 

1459 continue 

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

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

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

1463 

1464 return dict(ret) 

1465 

1466 

1467def sensors_battery(): 

1468 """Return battery information. 

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

1470 directory structure may vary and provide files with the same 

1471 meaning but under different names, see: 

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

1473 """ 

1474 null = object() 

1475 

1476 def multi_bcat(*paths): 

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

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

1479 """ 

1480 for path in paths: 

1481 ret = bcat(path, fallback=null) 

1482 if ret != null: 

1483 try: 

1484 return int(ret) 

1485 except ValueError: 

1486 return ret.strip() 

1487 return None 

1488 

1489 bats = [ 

1490 x 

1491 for x in os.listdir(POWER_SUPPLY_PATH) 

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

1493 ] 

1494 if not bats: 

1495 return None 

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

1497 # some rare exceptions: 

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

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

1500 

1501 # Base metrics. 

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

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

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

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

1506 

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

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

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

1510 try: 

1511 percent = 100.0 * energy_now / energy_full 

1512 except ZeroDivisionError: 

1513 percent = 0.0 

1514 else: 

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

1516 if percent == -1: 

1517 return None 

1518 

1519 # Is AC power cable plugged in? 

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

1521 # it's called "AC". 

1522 power_plugged = None 

1523 online = multi_bcat( 

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

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

1526 ) 

1527 if online is not None: 

1528 power_plugged = online == 1 

1529 else: 

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

1531 if status == "discharging": 

1532 power_plugged = False 

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

1534 power_plugged = True 

1535 

1536 # Seconds left. 

1537 if power_plugged: 

1538 secsleft = BatteryTime.POWER_TIME_UNLIMITED 

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

1540 try: 

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

1542 except ZeroDivisionError: 

1543 secsleft = BatteryTime.POWER_TIME_UNKNOWN 

1544 elif time_to_empty is not None: 

1545 secsleft = int(time_to_empty * 60) 

1546 if secsleft < 0: 

1547 secsleft = BatteryTime.POWER_TIME_UNKNOWN 

1548 else: 

1549 secsleft = BatteryTime.POWER_TIME_UNKNOWN 

1550 

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

1552 

1553 

1554# ===================================================================== 

1555# --- other system functions 

1556# ===================================================================== 

1557 

1558 

1559def users(): 

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

1561 retlist = [] 

1562 rawlist = _psutil.users() 

1563 for item in rawlist: 

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

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

1566 retlist.append(nt) 

1567 return retlist 

1568 

1569 

1570def boot_time(): 

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

1572 path = f"{psutil.PROCFS_PATH}/stat" 

1573 with open_binary(path) as f: 

1574 for line in f: 

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

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

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

1578 raise RuntimeError(msg) 

1579 

1580 

1581# ===================================================================== 

1582# --- processes 

1583# ===================================================================== 

1584 

1585 

1586def pids(): 

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

1588 path = psutil.PROCFS_PATH.encode(ENCODING) 

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

1590 

1591 

1592def pid_exists(pid): 

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

1594 supported (always return False). 

1595 """ 

1596 if not _psposix.pid_exists(pid): 

1597 return False 

1598 else: 

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

1600 # (thread IDs). 

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

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

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

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

1605 # only, see: 

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

1607 try: 

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

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

1610 # 'return pid in pids()' 

1611 path = f"{psutil.PROCFS_PATH}/{pid}/status" 

1612 with open_binary(path) as f: 

1613 for line in f: 

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

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

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

1617 # dealing with a process PID. 

1618 return tgid == pid 

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

1620 raise ValueError(msg) 

1621 except (OSError, ValueError): 

1622 return pid in pids() 

1623 

1624 

1625def ppid_map(): 

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

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

1628 """ 

1629 ret = {} 

1630 procfs_path = psutil.PROCFS_PATH 

1631 for pid in pids(): 

1632 try: 

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

1634 data = f.read() 

1635 except (FileNotFoundError, ProcessLookupError): 

1636 pass 

1637 except PermissionError as err: 

1638 raise AccessDenied(pid) from err 

1639 else: 

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

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

1642 ppid = int(dset[1]) 

1643 ret[pid] = ppid 

1644 return ret 

1645 

1646 

1647def wrap_exceptions(fun): 

1648 """Decorator which translates bare OSError exceptions into 

1649 NoSuchProcess and AccessDenied. 

1650 """ 

1651 

1652 @functools.wraps(fun) 

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

1654 pid, name = self.pid, self._name 

1655 try: 

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

1657 except PermissionError as err: 

1658 raise AccessDenied(pid, name) from err 

1659 except ProcessLookupError as err: 

1660 self._raise_if_zombie() 

1661 raise NoSuchProcess(pid, name) from err 

1662 except FileNotFoundError as err: 

1663 self._raise_if_zombie() 

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

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

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

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

1668 raise NoSuchProcess(pid, name) from err 

1669 raise 

1670 

1671 return wrapper 

1672 

1673 

1674class Process: 

1675 """Linux process implementation.""" 

1676 

1677 __slots__ = [ 

1678 "_cache", 

1679 "_ctime", 

1680 "_name", 

1681 "_ppid", 

1682 "_procfs_path", 

1683 "pid", 

1684 ] 

1685 

1686 def __init__(self, pid): 

1687 self.pid = pid 

1688 self._name = None 

1689 self._ppid = None 

1690 self._ctime = None 

1691 self._procfs_path = psutil.PROCFS_PATH 

1692 

1693 def _is_zombie(self): 

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

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

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

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

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

1699 # exception. 

1700 try: 

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

1702 except OSError: 

1703 return False 

1704 else: 

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

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

1707 return status == b"Z" 

1708 

1709 def _raise_if_zombie(self): 

1710 if self._is_zombie(): 

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

1712 

1713 def _raise_if_not_alive(self): 

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

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

1716 # incorrect or incomplete result. 

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

1718 

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

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

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

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

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

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

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

1726 try: 

1727 return readlink(path) 

1728 except (FileNotFoundError, ProcessLookupError): 

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

1730 self._raise_if_zombie() 

1731 if fallback is not UNSET: 

1732 return fallback 

1733 raise 

1734 

1735 @wrap_exceptions 

1736 @memoize_when_activated 

1737 def _parse_stat_file(self): 

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

1739 process info. 

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

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

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

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

1744 in use. 

1745 """ 

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

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

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

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

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

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

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

1753 

1754 ret = {} 

1755 ret['name'] = name 

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

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

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

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

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

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

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

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

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

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

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

1767 try: 

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

1769 except IndexError: 

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

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

1772 ret['blkio_ticks'] = 0 

1773 

1774 return ret 

1775 

1776 @wrap_exceptions 

1777 @memoize_when_activated 

1778 def _read_status_file(self): 

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

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

1781 in use. 

1782 """ 

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

1784 return f.read() 

1785 

1786 @wrap_exceptions 

1787 @memoize_when_activated 

1788 def _read_smaps_file(self): 

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

1790 return f.read().strip() 

1791 

1792 def oneshot_enter(self): 

1793 cache_activate(self) 

1794 

1795 def oneshot_exit(self): 

1796 cache_deactivate(self) 

1797 

1798 @wrap_exceptions 

1799 def name(self): 

1800 # XXX - gets changed later and probably needs refactoring 

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

1802 

1803 @wrap_exceptions 

1804 def exe(self): 

1805 return self._readlink( 

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

1807 ) 

1808 

1809 @wrap_exceptions 

1810 def cmdline(self): 

1811 # not open_text(): it turns \r and \r\n into \n 

1812 data = decode(bcat(f"{self._procfs_path}/{self.pid}/cmdline")) 

1813 if not data: 

1814 # may happen in case of zombie process 

1815 self._raise_if_zombie() 

1816 return [] 

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

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

1819 # some processes may change their cmdline after being started 

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

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

1822 # Chrome process is an example. See: 

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

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

1825 if data.endswith(sep): 

1826 data = data[:-1] 

1827 cmdline = data.split(sep) 

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

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

1830 # issues/1179#issuecomment-552984549 

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

1832 cmdline = data.split(' ') 

1833 return cmdline 

1834 

1835 @wrap_exceptions 

1836 def environ(self): 

1837 # not open_text(): it turns \r and \r\n into \n 

1838 data = decode(bcat(f"{self._procfs_path}/{self.pid}/environ")) 

1839 return parse_environ_block(data) 

1840 

1841 @wrap_exceptions 

1842 def terminal(self): 

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

1844 if tty_nr == 0: 

1845 return None 

1846 return _psposix.get_terminal(tty_nr) 

1847 

1848 # May not be available on old kernels. 

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

1850 

1851 @wrap_exceptions 

1852 def io_counters(self): 

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

1854 fields = {} 

1855 with open_binary(fname) as f: 

1856 for line in f: 

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

1858 line = line.strip() 

1859 if line: 

1860 try: 

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

1862 except ValueError: 

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

1864 continue 

1865 else: 

1866 fields[name] = int(value) 

1867 if not fields: 

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

1869 raise RuntimeError(msg) 

1870 try: 

1871 return ntp.pio( 

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

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

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

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

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

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

1878 ) 

1879 except KeyError as err: 

1880 msg = ( 

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

1882 f" fields are {fields!r}" 

1883 ) 

1884 raise ValueError(msg) from None 

1885 

1886 @wrap_exceptions 

1887 def cpu_times(self): 

1888 values = self._parse_stat_file() 

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

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

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

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

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

1894 return ntp.pcputimes( 

1895 utime, stime, children_utime, children_stime, iowait 

1896 ) 

1897 

1898 @wrap_exceptions 

1899 def cpu_num(self): 

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

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

1902 

1903 @wrap_exceptions 

1904 def wait(self, timeout=None): 

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

1906 

1907 @wrap_exceptions 

1908 def create_time(self, monotonic=False): 

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

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

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

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

1913 # changes and is unaffected by system clock updates. 

1914 if self._ctime is None: 

1915 self._ctime = ( 

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

1917 ) 

1918 if monotonic: 

1919 return self._ctime 

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

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

1922 return self._ctime + boot_time() 

1923 

1924 @wrap_exceptions 

1925 def memory_info(self): 

1926 # ============================================================ 

1927 # | FIELD | DESCRIPTION | AKA | TOP | 

1928 # ============================================================ 

1929 # | rss | resident set size | | RES | 

1930 # | vms | total program size | size | VIRT | 

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

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

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

1934 # | data | data + stack | drs | DATA | 

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

1936 # ============================================================ 

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

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

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

1940 ) 

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

1942 

1943 @wrap_exceptions 

1944 def memory_extras( 

1945 self, 

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

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

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

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

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

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

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

1953 ): 

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

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

1956 # VmSwap in 2.6.34. 

1957 data = self._read_status_file() 

1958 

1959 def parse(regex): 

1960 m = regex.search(data) 

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

1962 

1963 return ntp.pmem_extras( 

1964 peak_rss=parse(_vmhwm_re), 

1965 peak_vms=parse(_vmpeak_re), 

1966 rss_anon=parse(_rssanon_re), 

1967 rss_file=parse(_rssfile_re), 

1968 rss_shmem=parse(_rssshmem_re), 

1969 swap_anon=parse(_vmswap_re), 

1970 locked=parse(_vmlck_re), 

1971 ) 

1972 

1973 if HAS_PROC_SMAPS_ROLLUP or HAS_PROC_SMAPS: 

1974 

1975 def _parse_smaps_rollup(self): 

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

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

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

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

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

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

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

1983 # compared to /proc/pid/smaps_rollup. 

1984 uss = pss = swap = shared = 0 

1985 with open_binary( 

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

1987 ) as f: 

1988 for line in f: 

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

1990 # Private_Clean, Private_Dirty, Private_Hugetlb 

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

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

1993 # Shared_Clean, Shared_Dirty, Shared_Hugetlb 

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

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

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

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

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

1999 return (uss, pss, swap, shared) 

2000 

2001 @wrap_exceptions 

2002 def _parse_smaps( 

2003 self, 

2004 # Gets Private_Clean, Private_Dirty, Private_Hugetlb. 

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

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

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

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

2009 ): 

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

2011 # CONFIG_MMU kernel configuration option is not enabled. 

2012 

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

2014 # line by line. 

2015 # 

2016 # You might be tempted to calculate USS by subtracting 

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

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

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

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

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

2022 # correct information. 

2023 smaps_data = self._read_smaps_file() 

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

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

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

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

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

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

2030 return (uss, pss, swap, shared) 

2031 

2032 @wrap_exceptions 

2033 def memory_footprint(self): 

2034 def fetch(): 

2035 if HAS_PROC_SMAPS_ROLLUP: # faster 

2036 try: 

2037 return self._parse_smaps_rollup() 

2038 except (ProcessLookupError, FileNotFoundError): 

2039 pass 

2040 return self._parse_smaps() 

2041 

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

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

2044 

2045 if HAS_PROC_SMAPS: 

2046 

2047 @wrap_exceptions 

2048 def memory_maps(self): 

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

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

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

2052 

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

2054 CONFIG_MMU kernel configuration option is not enabled. 

2055 """ 

2056 

2057 def get_blocks(lines, current_block): 

2058 data = {} 

2059 for line in lines: 

2060 fields = line.split(None, 5) 

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

2062 # new block section 

2063 yield (current_block.pop(), data) 

2064 current_block.append(line) 

2065 else: 

2066 try: 

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

2068 except (ValueError, IndexError): 

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

2070 # see issue #369 

2071 continue 

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

2073 raise ValueError(msg) from None 

2074 yield (current_block.pop(), data) 

2075 

2076 data = self._read_smaps_file() 

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

2078 # zombies. 

2079 if not data: 

2080 self._raise_if_zombie() 

2081 return [] 

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

2083 ls = [] 

2084 first_line = lines.pop(0) 

2085 current_block = [first_line] 

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

2087 hfields = header.split(None, 5) 

2088 try: 

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

2090 except ValueError: 

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

2092 if not path: 

2093 path = '[anon]' 

2094 else: 

2095 path = decode(path) 

2096 path = path.strip() 

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

2098 path 

2099 ): 

2100 path = path[:-10] 

2101 item = ( 

2102 decode(addr), 

2103 decode(perms), 

2104 path, 

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

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

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

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

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

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

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

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

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

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

2115 ) 

2116 ls.append(item) 

2117 return ls 

2118 

2119 @wrap_exceptions 

2120 def page_faults(self): 

2121 values = self._parse_stat_file() 

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

2123 

2124 @wrap_exceptions 

2125 def cwd(self): 

2126 return self._readlink( 

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

2128 ) 

2129 

2130 @wrap_exceptions 

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

2132 data = self._read_status_file() 

2133 ctxsw = _ctxsw_re.findall(data) 

2134 if not ctxsw: 

2135 msg = ( 

2136 "'voluntary_ctxt_switches' and" 

2137 " 'nonvoluntary_ctxt_switches'lines were not found in" 

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

2139 " probably older than 2.6.23" 

2140 ) 

2141 raise NotImplementedError(msg) 

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

2143 

2144 @wrap_exceptions 

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

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

2147 data = self._read_status_file() 

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

2149 

2150 @wrap_exceptions 

2151 def threads(self): 

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

2153 thread_ids.sort() 

2154 retlist = [] 

2155 hit_enoent = False 

2156 for thread_id in thread_ids: 

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

2158 try: 

2159 with open_binary(fname) as f: 

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

2161 except (FileNotFoundError, ProcessLookupError): 

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

2163 # it means thread disappeared on us 

2164 hit_enoent = True 

2165 continue 

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

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

2168 values = st.split(b' ') 

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

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

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

2172 retlist.append(ntuple) 

2173 if hit_enoent: 

2174 self._raise_if_not_alive() 

2175 return retlist 

2176 

2177 @wrap_exceptions 

2178 def nice_get(self): 

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

2180 # data = f.read() 

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

2182 

2183 # Use C implementation 

2184 return _psutil.proc_priority_get(self.pid) 

2185 

2186 @wrap_exceptions 

2187 def nice_set(self, value): 

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

2189 

2190 # starting from CentOS 6. 

2191 if HAS_CPU_AFFINITY: 

2192 

2193 @wrap_exceptions 

2194 def cpu_affinity_get(self): 

2195 return _psutil.proc_cpu_affinity_get(self.pid) 

2196 

2197 def _get_eligible_cpus( 

2198 self, 

2199 _re=re.compile( 

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

2201 ), 

2202 ): 

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

2204 data = self._read_status_file() 

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

2206 try: 

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

2208 except ValueError as err: 

2209 debug( 

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

2211 ) 

2212 return list(range(len(per_cpu_times()))) 

2213 

2214 @wrap_exceptions 

2215 def cpu_affinity_set(self, cpus): 

2216 try: 

2217 _psutil.proc_cpu_affinity_set(self.pid, cpus) 

2218 except (OSError, ValueError) as err: 

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

2220 eligible_cpus = self._get_eligible_cpus() 

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

2222 for cpu in cpus: 

2223 if cpu not in all_cpus: 

2224 msg = ( 

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

2226 f" {eligible_cpus!r}" 

2227 ) 

2228 raise ValueError(msg) from None 

2229 if cpu not in eligible_cpus: 

2230 msg = ( 

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

2232 f" between {eligible_cpus}" 

2233 ) 

2234 raise ValueError(msg) from err 

2235 raise 

2236 

2237 # only starting from kernel 2.6.13 

2238 if HAS_PROC_IO_PRIORITY: 

2239 

2240 @wrap_exceptions 

2241 def ionice_get(self): 

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

2243 ioclass = ProcessIOPriority(ioclass) 

2244 return ntp.pionice(ioclass, value) 

2245 

2246 @wrap_exceptions 

2247 def ionice_set(self, ioclass, value): 

2248 if value is None: 

2249 value = 0 

2250 if value and ioclass in { 

2251 ProcessIOPriority.IOPRIO_CLASS_IDLE, 

2252 ProcessIOPriority.IOPRIO_CLASS_NONE, 

2253 }: 

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

2255 raise ValueError(msg) 

2256 if value < 0 or value > 7: 

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

2258 raise ValueError(msg) 

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

2260 

2261 if hasattr(resource, "prlimit"): 

2262 

2263 @wrap_exceptions 

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

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

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

2267 # PID 0 is not supported on Linux. 

2268 if self.pid == 0: 

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

2270 raise ValueError(msg) 

2271 try: 

2272 if limits is None: 

2273 # get 

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

2275 else: 

2276 # set 

2277 if len(limits) != 2: 

2278 msg = ( 

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

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

2281 ) 

2282 raise ValueError(msg) 

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

2284 except OSError as err: 

2285 if err.errno == errno.ENOSYS: 

2286 # I saw this happening on Travis: 

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

2288 self._raise_if_zombie() 

2289 raise 

2290 

2291 @wrap_exceptions 

2292 def status(self): 

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

2294 letter = letter.decode() 

2295 try: 

2296 return PROC_STATUSES[letter] 

2297 except KeyError: 

2298 debug(f"unknown process status {letter!r}") 

2299 return ProcessStatus.STATUS_UNKNOWN 

2300 

2301 @wrap_exceptions 

2302 def open_files(self): 

2303 retlist = [] 

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

2305 hit_enoent = False 

2306 for fd in files: 

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

2308 try: 

2309 path = readlink(file) 

2310 except (FileNotFoundError, ProcessLookupError): 

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

2312 hit_enoent = True 

2313 continue 

2314 except OSError as err: 

2315 if err.errno == errno.EINVAL: 

2316 # not a link 

2317 continue 

2318 if err.errno == errno.ENAMETOOLONG: 

2319 # file name too long 

2320 debug(err) 

2321 continue 

2322 raise 

2323 else: 

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

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

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

2327 # absolute path though. 

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

2329 # Get file position and flags. 

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

2331 try: 

2332 with open_binary(file) as f: 

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

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

2335 except (FileNotFoundError, ProcessLookupError): 

2336 # fd gone in the meantime; process may 

2337 # still be alive 

2338 hit_enoent = True 

2339 else: 

2340 mode = file_flags_to_mode(flags) 

2341 ntuple = ntp.popenfile( 

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

2343 ) 

2344 retlist.append(ntuple) 

2345 if hit_enoent: 

2346 self._raise_if_not_alive() 

2347 return retlist 

2348 

2349 @wrap_exceptions 

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

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

2352 self._raise_if_not_alive() 

2353 return ret 

2354 

2355 @wrap_exceptions 

2356 def num_fds(self): 

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

2358 

2359 @wrap_exceptions 

2360 def ppid(self): 

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

2362 

2363 @wrap_exceptions 

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

2365 data = self._read_status_file() 

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

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

2368 

2369 @wrap_exceptions 

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

2371 data = self._read_status_file() 

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

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