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

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

1219 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 _ntuples as ntp 

26from . import _psposix 

27from . import _psutil_linux as cext 

28from ._common import ENCODING 

29from ._common import NIC_DUPLEX_FULL 

30from ._common import NIC_DUPLEX_HALF 

31from ._common import NIC_DUPLEX_UNKNOWN 

32from ._common import AccessDenied 

33from ._common import NoSuchProcess 

34from ._common import ZombieProcess 

35from ._common import bcat 

36from ._common import cat 

37from ._common import debug 

38from ._common import decode 

39from ._common import get_procfs_path 

40from ._common import isfile_strict 

41from ._common import memoize 

42from ._common import memoize_when_activated 

43from ._common import open_binary 

44from ._common import open_text 

45from ._common import parse_environ_block 

46from ._common import path_exists_strict 

47from ._common import supports_ipv6 

48from ._common import usage_percent 

49 

50# fmt: off 

51__extra__all__ = [ 

52 'PROCFS_PATH', 

53 # io prio constants 

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

55 "IOPRIO_CLASS_IDLE", 

56 # connection status constants 

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

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

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

60] 

61# fmt: on 

62 

63 

64# ===================================================================== 

65# --- globals 

66# ===================================================================== 

67 

68 

69POWER_SUPPLY_PATH = "/sys/class/power_supply" 

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

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

72HAS_PROC_IO_PRIORITY = hasattr(cext, "proc_ioprio_get") 

73HAS_CPU_AFFINITY = hasattr(cext, "proc_cpu_affinity_get") 

74 

75# Number of clock ticks per second 

76CLOCK_TICKS = os.sysconf("SC_CLK_TCK") 

77PAGESIZE = cext.getpagesize() 

78LITTLE_ENDIAN = sys.byteorder == 'little' 

79UNSET = object() 

80 

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

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

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

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

85# throughout Linux source code: 

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

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

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

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

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

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

92DISK_SECTOR_SIZE = 512 

93 

94AddressFamily = enum.IntEnum( 

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

96) 

97AF_LINK = AddressFamily.AF_LINK 

98 

99 

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

101class IOPriority(enum.IntEnum): 

102 IOPRIO_CLASS_NONE = 0 

103 IOPRIO_CLASS_RT = 1 

104 IOPRIO_CLASS_BE = 2 

105 IOPRIO_CLASS_IDLE = 3 

106 

107 

108globals().update(IOPriority.__members__) 

109 

110# See: 

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

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

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

114PROC_STATUSES = { 

115 "R": _common.STATUS_RUNNING, 

116 "S": _common.STATUS_SLEEPING, 

117 "D": _common.STATUS_DISK_SLEEP, 

118 "T": _common.STATUS_STOPPED, 

119 "t": _common.STATUS_TRACING_STOP, 

120 "Z": _common.STATUS_ZOMBIE, 

121 "X": _common.STATUS_DEAD, 

122 "x": _common.STATUS_DEAD, 

123 "K": _common.STATUS_WAKE_KILL, 

124 "W": _common.STATUS_WAKING, 

125 "I": _common.STATUS_IDLE, 

126 "P": _common.STATUS_PARKED, 

127} 

128 

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

130TCP_STATUSES = { 

131 "01": _common.CONN_ESTABLISHED, 

132 "02": _common.CONN_SYN_SENT, 

133 "03": _common.CONN_SYN_RECV, 

134 "04": _common.CONN_FIN_WAIT1, 

135 "05": _common.CONN_FIN_WAIT2, 

136 "06": _common.CONN_TIME_WAIT, 

137 "07": _common.CONN_CLOSE, 

138 "08": _common.CONN_CLOSE_WAIT, 

139 "09": _common.CONN_LAST_ACK, 

140 "0A": _common.CONN_LISTEN, 

141 "0B": _common.CONN_CLOSING, 

142} 

143 

144 

145# ===================================================================== 

146# --- utils 

147# ===================================================================== 

148 

149 

150def readlink(path): 

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

152 assert isinstance(path, str), path 

153 path = os.readlink(path) 

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

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

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

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

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

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

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

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

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

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

164 # don't care. 

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

166 path = path[:-10] 

167 return path 

168 

169 

170def file_flags_to_mode(flags): 

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

172 Used by Process.open_files(). 

173 """ 

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

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

176 if flags & os.O_APPEND: 

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

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

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

180 return mode 

181 

182 

183def is_storage_device(name): 

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

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

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

187 return True. 

188 """ 

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

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

191 # 97912938cd476645b267280069e83b1c8dc0e1c7/common.c#L208 

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

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

194 including_virtual = True 

195 if including_virtual: 

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

197 else: 

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

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

200 

201 

202@memoize 

203def _scputimes_ntuple(procfs_path): 

204 """Return a namedtuple of variable fields depending on the CPU times 

205 available on this Linux kernel version which may be: 

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

207 [guest_nice]]]) 

208 Used by cpu_times() function. 

209 """ 

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

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

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

213 vlen = len(values) 

214 if vlen >= 8: 

215 # Linux >= 2.6.11 

216 fields.append('steal') 

217 if vlen >= 9: 

218 # Linux >= 2.6.24 

219 fields.append('guest') 

220 if vlen >= 10: 

221 # Linux >= 3.2.0 

222 fields.append('guest_nice') 

223 return namedtuple('scputimes', fields) 

224 

225 

226# Set it into _ntuples.py namespace. 

227try: 

228 ntp.scputimes = _scputimes_ntuple("/proc") 

229except Exception as err: # noqa: BLE001 

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

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

232 ntp.scputimes = namedtuple('scputimes', 'user system idle')(0.0, 0.0, 0.0) 

233 

234# XXX: must be available also at this module level in order to be 

235# serialized (tests/test_misc.py::TestMisc::test_serialization). 

236scputimes = ntp.scputimes 

237 

238 

239# ===================================================================== 

240# --- system memory 

241# ===================================================================== 

242 

243 

244def calculate_avail_vmem(mems): 

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

246 "MemAvailable", see: 

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

248 

249 This code reimplements the algorithm outlined here: 

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

251 commit/?id=34e431b0ae398fc54ea69ff85ec700722c9da773 

252 

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

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

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

256 column). 

257 

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

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

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

261 See: 

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

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

264 """ 

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

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

267 # commit/?id=34e431b0ae398fc54ea69ff85ec700722c9da773 

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

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

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

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

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

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

274 free = mems[b'MemFree:'] 

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

276 try: 

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

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

279 slab_reclaimable = mems[b'SReclaimable:'] 

280 except KeyError as err: 

281 debug( 

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

283 " approximation for calculating available memory" 

284 ) 

285 return fallback 

286 try: 

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

288 except OSError: 

289 return fallback # kernel 2.6.13 

290 

291 watermark_low = 0 

292 with f: 

293 for line in f: 

294 line = line.strip() 

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

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

297 watermark_low *= PAGESIZE 

298 

299 avail = free - watermark_low 

300 pagecache = lru_active_file + lru_inactive_file 

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

302 avail += pagecache 

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

304 return int(avail) 

305 

306 

307def virtual_memory(): 

308 """Report virtual memory stats. 

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

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

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

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

313 CLI tools. 

314 """ 

315 missing_fields = [] 

316 mems = {} 

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

318 for line in f: 

319 fields = line.split() 

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

321 

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

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

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

325 total = mems[b'MemTotal:'] 

326 free = mems[b'MemFree:'] 

327 try: 

328 buffers = mems[b'Buffers:'] 

329 except KeyError: 

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

331 buffers = 0 

332 missing_fields.append('buffers') 

333 try: 

334 cached = mems[b"Cached:"] 

335 except KeyError: 

336 cached = 0 

337 missing_fields.append('cached') 

338 else: 

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

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

341 # This got changed in: 

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

343 # 05d751c4f076a2f0118b914c5e51cfbb4762ad8e 

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

345 

346 try: 

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

348 except KeyError: 

349 try: 

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

351 except KeyError: 

352 shared = 0 

353 missing_fields.append('shared') 

354 

355 try: 

356 active = mems[b"Active:"] 

357 except KeyError: 

358 active = 0 

359 missing_fields.append('active') 

360 

361 try: 

362 inactive = mems[b"Inactive:"] 

363 except KeyError: 

364 try: 

365 inactive = ( 

366 mems[b"Inact_dirty:"] 

367 + mems[b"Inact_clean:"] 

368 + mems[b"Inact_laundry:"] 

369 ) 

370 except KeyError: 

371 inactive = 0 

372 missing_fields.append('inactive') 

373 

374 try: 

375 slab = mems[b"Slab:"] 

376 except KeyError: 

377 slab = 0 

378 

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

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

381 # which matched htop. 

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

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

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

385 # - MemAvailable has been introduced in kernel 3.14 

386 try: 

387 avail = mems[b'MemAvailable:'] 

388 except KeyError: 

389 avail = calculate_avail_vmem(mems) 

390 else: 

391 if avail == 0: 

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

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

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

395 # and it matches "free" CLI tool. 

396 avail = calculate_avail_vmem(mems) 

397 

398 if avail < 0: 

399 avail = 0 

400 missing_fields.append('available') 

401 elif avail > total: 

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

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

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

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

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

407 avail = free 

408 

409 used = total - avail 

410 

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

412 

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

414 if missing_fields: 

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

416 ", ".join(missing_fields), 

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

418 ) 

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

420 

421 return ntp.svmem( 

422 total, 

423 avail, 

424 percent, 

425 used, 

426 free, 

427 active, 

428 inactive, 

429 buffers, 

430 cached, 

431 shared, 

432 slab, 

433 ) 

434 

435 

436def swap_memory(): 

437 """Return swap memory metrics.""" 

438 mems = {} 

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

440 for line in f: 

441 fields = line.split() 

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

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

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

445 # for linux containers, see: 

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

447 try: 

448 total = mems[b'SwapTotal:'] 

449 free = mems[b'SwapFree:'] 

450 except KeyError: 

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

452 total *= unit_multiplier 

453 free *= unit_multiplier 

454 

455 used = total - free 

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

457 # get pgin/pgouts 

458 try: 

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

460 except OSError as err: 

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

462 msg = ( 

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

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

465 ) 

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

467 sin = sout = 0 

468 else: 

469 with f: 

470 sin = sout = None 

471 for line in f: 

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

473 # bytes instead 

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

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

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

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

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

479 break 

480 else: 

481 # we might get here when dealing with exotic Linux 

482 # flavors, see: 

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

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

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

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

487 sin = sout = 0 

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

489 

490 

491# ===================================================================== 

492# --- CPU 

493# ===================================================================== 

494 

495 

496def cpu_times(): 

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

498 CPU times: 

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

500 [guest_nice]]]) 

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

502 """ 

503 procfs_path = get_procfs_path() 

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

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

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

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

508 return ntp.scputimes(*fields) 

509 

510 

511def per_cpu_times(): 

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

513 for every CPU available on the system. 

514 """ 

515 procfs_path = get_procfs_path() 

516 cpus = [] 

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

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

519 f.readline() 

520 for line in f: 

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

522 values = line.split() 

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

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

525 entry = ntp.scputimes(*fields) 

526 cpus.append(entry) 

527 return cpus 

528 

529 

530def cpu_count_logical(): 

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

532 try: 

533 return os.sysconf("SC_NPROCESSORS_ONLN") 

534 except ValueError: 

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

536 num = 0 

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

538 for line in f: 

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

540 num += 1 

541 

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

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

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

545 if num == 0: 

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

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

548 for line in f: 

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

550 if search.match(line): 

551 num += 1 

552 

553 if num == 0: 

554 # mimic os.cpu_count() 

555 return None 

556 return num 

557 

558 

559def cpu_count_cores(): 

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

561 # Method #1 

562 ls = set() 

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

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

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

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

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

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

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

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

571 with open_binary(path) as f: 

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

573 result = len(ls) 

574 if result != 0: 

575 return result 

576 

577 # Method #2 

578 mapping = {} 

579 current_info = {} 

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

581 for line in f: 

582 line = line.strip().lower() 

583 if not line: 

584 # new section 

585 try: 

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

587 b'cpu cores' 

588 ] 

589 except KeyError: 

590 pass 

591 current_info = {} 

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

593 # ongoing section 

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

595 current_info[key] = int(value) 

596 

597 result = sum(mapping.values()) 

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

599 

600 

601def cpu_stats(): 

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

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

604 ctx_switches = None 

605 interrupts = None 

606 soft_interrupts = None 

607 for line in f: 

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

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

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

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

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

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

614 if ( 

615 ctx_switches is not None 

616 and soft_interrupts is not None 

617 and interrupts is not None 

618 ): 

619 break 

620 syscalls = 0 

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

622 

623 

624def _cpu_get_cpuinfo_freq(): 

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

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

627 return [ 

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

629 for line in f 

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

631 ] 

632 

633 

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

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

636): 

637 

638 def cpu_freq(): 

639 """Return frequency metrics for all CPUs. 

640 Contrarily to other OSes, Linux updates these values in 

641 real-time. 

642 """ 

643 cpuinfo_freqs = _cpu_get_cpuinfo_freq() 

644 paths = glob.glob( 

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

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

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

648 ret = [] 

649 pjoin = os.path.join 

650 for i, path in enumerate(paths): 

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

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

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

654 curr = cpuinfo_freqs[i] * 1000 

655 else: 

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

657 if curr is None: 

658 # Likely an old RedHat, see: 

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

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

661 if curr is None: 

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

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

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

665 ret.append(ntp.scpufreq(0.0, 0.0, 0.0)) 

666 continue 

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

668 raise NotImplementedError(msg) 

669 curr = int(curr) / 1000 

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

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

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

673 return ret 

674 

675else: 

676 

677 def cpu_freq(): 

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

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

680 """ 

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

682 

683 

684# ===================================================================== 

685# --- network 

686# ===================================================================== 

687 

688 

689net_if_addrs = cext.net_if_addrs 

690 

691 

692class _Ipv6UnsupportedError(Exception): 

693 pass 

694 

695 

696class NetConnections: 

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

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

699 "netstat -an". 

700 

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

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

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

704 

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

706 """ 

707 

708 def __init__(self): 

709 # The string represents the basename of the corresponding 

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

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

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

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

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

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

716 self.tmap = { 

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

718 "tcp": (tcp4, tcp6), 

719 "tcp4": (tcp4,), 

720 "tcp6": (tcp6,), 

721 "udp": (udp4, udp6), 

722 "udp4": (udp4,), 

723 "udp6": (udp6,), 

724 "unix": (unix,), 

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

726 "inet4": (tcp4, udp4), 

727 "inet6": (tcp6, udp6), 

728 } 

729 self._procfs_path = None 

730 

731 def get_proc_inodes(self, pid): 

732 inodes = defaultdict(list) 

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

734 try: 

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

736 except (FileNotFoundError, ProcessLookupError): 

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

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

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

740 continue 

741 except OSError as err: 

742 if err.errno == errno.EINVAL: 

743 # not a link 

744 continue 

745 if err.errno == errno.ENAMETOOLONG: 

746 # file name too long 

747 debug(err) 

748 continue 

749 raise 

750 else: 

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

752 # the process is using a socket 

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

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

755 return inodes 

756 

757 def get_all_inodes(self): 

758 inodes = {} 

759 for pid in pids(): 

760 try: 

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

762 except (FileNotFoundError, ProcessLookupError, PermissionError): 

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

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

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

766 # and fd set to None anyway. 

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

768 # unlikely we can do any better. 

769 # ENOENT just means a PID disappeared on us. 

770 continue 

771 return inodes 

772 

773 @staticmethod 

774 def decode_address(addr, family): 

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

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

777 

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

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

780 

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

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

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

784 to an IP address. 

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

786 

787 Reference: 

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

789 """ 

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

791 port = int(port, 16) 

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

793 # no end-points connected 

794 if not port: 

795 return () 

796 ip = ip.encode('ascii') 

797 if family == socket.AF_INET: 

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

799 if LITTLE_ENDIAN: 

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

801 else: 

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

803 else: # IPv6 

804 ip = base64.b16decode(ip) 

805 try: 

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

807 if LITTLE_ENDIAN: 

808 ip = socket.inet_ntop( 

809 socket.AF_INET6, 

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

811 ) 

812 else: 

813 ip = socket.inet_ntop( 

814 socket.AF_INET6, 

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

816 ) 

817 except ValueError: 

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

819 if not supports_ipv6(): 

820 raise _Ipv6UnsupportedError from None 

821 raise 

822 return ntp.addr(ip, port) 

823 

824 @staticmethod 

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

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

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

828 # IPv6 not supported 

829 return 

830 with open_text(file) as f: 

831 f.readline() # skip the first line 

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

833 try: 

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

835 line.split()[:10] 

836 ) 

837 except ValueError: 

838 msg = ( 

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

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

841 ) 

842 raise RuntimeError(msg) from None 

843 if inode in inodes: 

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

845 # # out if there are multiple references to the 

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

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

848 # raise ValueError("ambiguous inode with multiple " 

849 # "PIDs references") 

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

851 else: 

852 pid, fd = None, -1 

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

854 continue 

855 else: 

856 if type_ == socket.SOCK_STREAM: 

857 status = TCP_STATUSES[status] 

858 else: 

859 status = _common.CONN_NONE 

860 try: 

861 laddr = NetConnections.decode_address(laddr, family) 

862 raddr = NetConnections.decode_address(raddr, family) 

863 except _Ipv6UnsupportedError: 

864 continue 

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

866 

867 @staticmethod 

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

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

870 with open_text(file) as f: 

871 f.readline() # skip the first line 

872 for line in f: 

873 tokens = line.split() 

874 try: 

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

876 except ValueError: 

877 if ' ' not in line: 

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

879 continue 

880 msg = ( 

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

882 ) 

883 raise RuntimeError(msg) # noqa: B904 

884 if inode in inodes: # noqa: SIM108 

885 # With UNIX sockets we can have a single inode 

886 # referencing many file descriptors. 

887 pairs = inodes[inode] 

888 else: 

889 pairs = [(None, -1)] 

890 for pid, fd in pairs: 

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

892 continue 

893 else: 

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

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

896 # XXX: determining the remote endpoint of a 

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

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

899 raddr = "" 

900 status = _common.CONN_NONE 

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

902 

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

904 self._procfs_path = get_procfs_path() 

905 if pid is not None: 

906 inodes = self.get_proc_inodes(pid) 

907 if not inodes: 

908 # no connections for this process 

909 return [] 

910 else: 

911 inodes = self.get_all_inodes() 

912 ret = set() 

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

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

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

916 ls = self.process_inet( 

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

918 ) 

919 else: 

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

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

922 if pid: 

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

924 else: 

925 conn = ntp.sconn( 

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

927 ) 

928 ret.add(conn) 

929 return list(ret) 

930 

931 

932_net_connections = NetConnections() 

933 

934 

935def net_connections(kind='inet'): 

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

937 return _net_connections.retrieve(kind) 

938 

939 

940def net_io_counters(): 

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

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

943 """ 

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

945 lines = f.readlines() 

946 retdict = {} 

947 for line in lines[2:]: 

948 colon = line.rfind(':') 

949 assert colon > 0, repr(line) 

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

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

952 

953 ( 

954 # in 

955 bytes_recv, 

956 packets_recv, 

957 errin, 

958 dropin, 

959 _fifoin, # unused 

960 _framein, # unused 

961 _compressedin, # unused 

962 _multicastin, # unused 

963 # out 

964 bytes_sent, 

965 packets_sent, 

966 errout, 

967 dropout, 

968 _fifoout, # unused 

969 _collisionsout, # unused 

970 _carrierout, # unused 

971 _compressedout, # unused 

972 ) = map(int, fields) 

973 

974 retdict[name] = ( 

975 bytes_sent, 

976 bytes_recv, 

977 packets_sent, 

978 packets_recv, 

979 errin, 

980 errout, 

981 dropin, 

982 dropout, 

983 ) 

984 return retdict 

985 

986 

987def net_if_stats(): 

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

989 duplex_map = { 

990 cext.DUPLEX_FULL: NIC_DUPLEX_FULL, 

991 cext.DUPLEX_HALF: NIC_DUPLEX_HALF, 

992 cext.DUPLEX_UNKNOWN: NIC_DUPLEX_UNKNOWN, 

993 } 

994 names = net_io_counters().keys() 

995 ret = {} 

996 for name in names: 

997 try: 

998 mtu = cext.net_if_mtu(name) 

999 flags = cext.net_if_flags(name) 

1000 duplex, speed = cext.net_if_duplex_speed(name) 

1001 except OSError as err: 

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

1003 if err.errno != errno.ENODEV: 

1004 raise 

1005 debug(err) 

1006 else: 

1007 output_flags = ','.join(flags) 

1008 isup = 'running' in flags 

1009 ret[name] = ntp.snicstats( 

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

1011 ) 

1012 return ret 

1013 

1014 

1015# ===================================================================== 

1016# --- disks 

1017# ===================================================================== 

1018 

1019 

1020disk_usage = _psposix.disk_usage 

1021 

1022 

1023def disk_io_counters(perdisk=False): 

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

1025 system as a dict of raw tuples. 

1026 """ 

1027 

1028 def read_procfs(): 

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

1030 # have 3 variations. 

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

1032 # "3 0 8 hda 8 8 8 8 8 8 8 8 8 8 8" 

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

1034 # name is in another position, like this: 

1035 # "3 0 hda 8 8 8 8 8 8 8 8 8 8 8" 

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

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

1038 # "3 1 hda1 8 8 8 8" 

1039 # 4.18+ has 4 fields added: 

1040 # "3 0 hda 8 8 8 8 8 8 8 8 8 8 8 0 0 0 0" 

1041 # 5.5 has 2 more fields. 

1042 # See: 

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

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

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

1046 lines = f.readlines() 

1047 for line in lines: 

1048 fields = line.split() 

1049 flen = len(fields) 

1050 # fmt: off 

1051 if flen == 15: 

1052 # Linux 2.4 

1053 name = fields[3] 

1054 reads = int(fields[2]) 

1055 (reads_merged, rbytes, rtime, writes, writes_merged, 

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

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

1058 # Linux 2.6+, line referring to a disk 

1059 name = fields[2] 

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

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

1062 elif flen == 7: 

1063 # Linux 2.6+, line referring to a partition 

1064 name = fields[2] 

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

1066 rtime = wtime = reads_merged = writes_merged = busy_time = 0 

1067 else: 

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

1069 raise ValueError(msg) 

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

1071 reads_merged, writes_merged, busy_time) 

1072 # fmt: on 

1073 

1074 def read_sysfs(): 

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

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

1077 if 'stat' not in files: 

1078 continue 

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

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

1081 name = os.path.basename(root) 

1082 # fmt: off 

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

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

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

1086 wtime, reads_merged, writes_merged, busy_time) 

1087 # fmt: on 

1088 

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

1090 gen = read_procfs() 

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

1092 gen = read_sysfs() 

1093 else: 

1094 msg = ( 

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

1096 " this system" 

1097 ) 

1098 raise NotImplementedError(msg) 

1099 

1100 retdict = {} 

1101 for entry in gen: 

1102 # fmt: off 

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

1104 writes_merged, busy_time) = entry 

1105 if not perdisk and not is_storage_device(name): 

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

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

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

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

1110 # of their own: 

1111 # $ cat /proc/diskstats 

1112 # 259 0 sda 10485760 ... 

1113 # 259 1 sda1 5186039 ... 

1114 # 259 1 sda2 5082039 ... 

1115 # See: 

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

1117 continue 

1118 

1119 rbytes *= DISK_SECTOR_SIZE 

1120 wbytes *= DISK_SECTOR_SIZE 

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

1122 reads_merged, writes_merged, busy_time) 

1123 # fmt: on 

1124 

1125 return retdict 

1126 

1127 

1128class RootFsDeviceFinder: 

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

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

1131 obtain the real device path. Resources: 

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

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

1134 """ 

1135 

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

1137 

1138 def __init__(self): 

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

1140 self.major = os.major(dev) 

1141 self.minor = os.minor(dev) 

1142 

1143 def ask_proc_partitions(self): 

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

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

1146 fields = line.split() 

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

1148 continue 

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

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

1151 name = fields[3] 

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

1153 if name: # just for extra safety 

1154 return f"/dev/{name}" 

1155 

1156 def ask_sys_dev_block(self): 

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

1158 with open_text(path) as f: 

1159 for line in f: 

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

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

1162 if name: # just for extra safety 

1163 return f"/dev/{name}" 

1164 

1165 def ask_sys_class_block(self): 

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

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

1168 for file in files: 

1169 try: 

1170 f = open_text(file) 

1171 except FileNotFoundError: # race condition 

1172 continue 

1173 else: 

1174 with f: 

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

1176 if data == needle: 

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

1178 return f"/dev/{name}" 

1179 

1180 def find(self): 

1181 path = None 

1182 if path is None: 

1183 try: 

1184 path = self.ask_proc_partitions() 

1185 except OSError as err: 

1186 debug(err) 

1187 if path is None: 

1188 try: 

1189 path = self.ask_sys_dev_block() 

1190 except OSError as err: 

1191 debug(err) 

1192 if path is None: 

1193 try: 

1194 path = self.ask_sys_class_block() 

1195 except OSError as err: 

1196 debug(err) 

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

1198 # coded, so we want to be sure. 

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

1200 return path 

1201 

1202 

1203def disk_partitions(all=False): 

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

1205 fstypes = set() 

1206 procfs_path = get_procfs_path() 

1207 if not all: 

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

1209 for line in f: 

1210 line = line.strip() 

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

1212 fstypes.add(line.strip()) 

1213 else: 

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

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

1216 if fstype == "zfs": 

1217 fstypes.add("zfs") 

1218 

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

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

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

1222 else: 

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

1224 

1225 retlist = [] 

1226 partitions = cext.disk_partitions(mounts_path) 

1227 for partition in partitions: 

1228 device, mountpoint, fstype, opts = partition 

1229 if device == 'none': 

1230 device = '' 

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

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

1233 if not all: 

1234 if not device or fstype not in fstypes: 

1235 continue 

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

1237 retlist.append(ntuple) 

1238 

1239 return retlist 

1240 

1241 

1242# ===================================================================== 

1243# --- sensors 

1244# ===================================================================== 

1245 

1246 

1247def sensors_temperatures(): 

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

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

1250 temperatures. 

1251 

1252 Implementation notes: 

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

1254 retrieve this info, and this implementation relies on it 

1255 only (old distros will probably use something else) 

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

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

1258 difficult to parse 

1259 """ 

1260 ret = collections.defaultdict(list) 

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

1262 # CentOS has an intermediate /device directory: 

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

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

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

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

1267 

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

1269 # /sys/class/hwmon/ 

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

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

1272 basenames2 = glob.glob( 

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

1274 ) 

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

1276 for name in basenames2: 

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

1278 if altname not in basenames: 

1279 basenames.append(name) 

1280 

1281 for base in basenames: 

1282 try: 

1283 path = base + '_input' 

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

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

1286 unit_name = cat(path).strip() 

1287 except (OSError, ValueError): 

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

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

1290 # is a stinky broken mess. 

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

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

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

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

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

1296 continue 

1297 

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

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

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

1301 

1302 if high is not None: 

1303 try: 

1304 high = float(high) / 1000.0 

1305 except ValueError: 

1306 high = None 

1307 if critical is not None: 

1308 try: 

1309 critical = float(critical) / 1000.0 

1310 except ValueError: 

1311 critical = None 

1312 

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

1314 

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

1316 if not basenames: 

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

1318 basenames = sorted(set(basenames)) 

1319 

1320 for base in basenames: 

1321 try: 

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

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

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

1325 unit_name = cat(path).strip() 

1326 except (OSError, ValueError) as err: 

1327 debug(err) 

1328 continue 

1329 

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

1331 trip_points = { 

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

1333 for p in trip_paths 

1334 } 

1335 critical = None 

1336 high = None 

1337 for trip_point in trip_points: 

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

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

1340 if trip_type == 'critical': 

1341 critical = bcat( 

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

1343 ) 

1344 elif trip_type == 'high': 

1345 high = bcat( 

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

1347 ) 

1348 

1349 if high is not None: 

1350 try: 

1351 high = float(high) / 1000.0 

1352 except ValueError: 

1353 high = None 

1354 if critical is not None: 

1355 try: 

1356 critical = float(critical) / 1000.0 

1357 except ValueError: 

1358 critical = None 

1359 

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

1361 

1362 return dict(ret) 

1363 

1364 

1365def sensors_fans(): 

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

1367 dict including hardware label and current speed. 

1368 

1369 Implementation notes: 

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

1371 retrieve this info, and this implementation relies on it 

1372 only (old distros will probably use something else) 

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

1374 """ 

1375 ret = collections.defaultdict(list) 

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

1377 if not basenames: 

1378 # CentOS has an intermediate /device directory: 

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

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

1381 

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

1383 for base in basenames: 

1384 try: 

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

1386 except OSError as err: 

1387 debug(err) 

1388 continue 

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

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

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

1392 

1393 return dict(ret) 

1394 

1395 

1396def sensors_battery(): 

1397 """Return battery information. 

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

1399 directory structure may vary and provide files with the same 

1400 meaning but under different names, see: 

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

1402 """ 

1403 null = object() 

1404 

1405 def multi_bcat(*paths): 

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

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

1408 """ 

1409 for path in paths: 

1410 ret = bcat(path, fallback=null) 

1411 if ret != null: 

1412 try: 

1413 return int(ret) 

1414 except ValueError: 

1415 return ret.strip() 

1416 return None 

1417 

1418 bats = [ 

1419 x 

1420 for x in os.listdir(POWER_SUPPLY_PATH) 

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

1422 ] 

1423 if not bats: 

1424 return None 

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

1426 # some rare exceptions: 

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

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

1429 

1430 # Base metrics. 

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

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

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

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

1435 

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

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

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

1439 try: 

1440 percent = 100.0 * energy_now / energy_full 

1441 except ZeroDivisionError: 

1442 percent = 0.0 

1443 else: 

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

1445 if percent == -1: 

1446 return None 

1447 

1448 # Is AC power cable plugged in? 

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

1450 # it's called "AC". 

1451 power_plugged = None 

1452 online = multi_bcat( 

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

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

1455 ) 

1456 if online is not None: 

1457 power_plugged = online == 1 

1458 else: 

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

1460 if status == "discharging": 

1461 power_plugged = False 

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

1463 power_plugged = True 

1464 

1465 # Seconds left. 

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

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

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

1469 if power_plugged: 

1470 secsleft = _common.POWER_TIME_UNLIMITED 

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

1472 try: 

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

1474 except ZeroDivisionError: 

1475 secsleft = _common.POWER_TIME_UNKNOWN 

1476 elif time_to_empty is not None: 

1477 secsleft = int(time_to_empty * 60) 

1478 if secsleft < 0: 

1479 secsleft = _common.POWER_TIME_UNKNOWN 

1480 else: 

1481 secsleft = _common.POWER_TIME_UNKNOWN 

1482 

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

1484 

1485 

1486# ===================================================================== 

1487# --- other system functions 

1488# ===================================================================== 

1489 

1490 

1491def users(): 

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

1493 retlist = [] 

1494 rawlist = cext.users() 

1495 for item in rawlist: 

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

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

1498 retlist.append(nt) 

1499 return retlist 

1500 

1501 

1502def boot_time(): 

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

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

1505 with open_binary(path) as f: 

1506 for line in f: 

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

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

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

1510 raise RuntimeError(msg) 

1511 

1512 

1513# ===================================================================== 

1514# --- processes 

1515# ===================================================================== 

1516 

1517 

1518def pids(): 

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

1520 path = get_procfs_path().encode(ENCODING) 

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

1522 

1523 

1524def pid_exists(pid): 

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

1526 supported (always return False). 

1527 """ 

1528 if not _psposix.pid_exists(pid): 

1529 return False 

1530 else: 

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

1532 # (thread IDs). 

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

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

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

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

1537 # only, see: 

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

1539 try: 

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

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

1542 # 'return pid in pids()' 

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

1544 with open_binary(path) as f: 

1545 for line in f: 

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

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

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

1549 # dealing with a process PID. 

1550 return tgid == pid 

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

1552 raise ValueError(msg) 

1553 except (OSError, ValueError): 

1554 return pid in pids() 

1555 

1556 

1557def ppid_map(): 

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

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

1560 """ 

1561 ret = {} 

1562 procfs_path = get_procfs_path() 

1563 for pid in pids(): 

1564 try: 

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

1566 data = f.read() 

1567 except (FileNotFoundError, ProcessLookupError): 

1568 pass 

1569 except PermissionError as err: 

1570 raise AccessDenied(pid) from err 

1571 else: 

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

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

1574 ppid = int(dset[1]) 

1575 ret[pid] = ppid 

1576 return ret 

1577 

1578 

1579def wrap_exceptions(fun): 

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

1581 into NoSuchProcess and AccessDenied. 

1582 """ 

1583 

1584 @functools.wraps(fun) 

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

1586 pid, name = self.pid, self._name 

1587 try: 

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

1589 except PermissionError as err: 

1590 raise AccessDenied(pid, name) from err 

1591 except ProcessLookupError as err: 

1592 self._raise_if_zombie() 

1593 raise NoSuchProcess(pid, name) from err 

1594 except FileNotFoundError as err: 

1595 self._raise_if_zombie() 

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

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

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

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

1600 raise NoSuchProcess(pid, name) from err 

1601 raise 

1602 

1603 return wrapper 

1604 

1605 

1606class Process: 

1607 """Linux process implementation.""" 

1608 

1609 __slots__ = [ 

1610 "_cache", 

1611 "_ctime", 

1612 "_name", 

1613 "_ppid", 

1614 "_procfs_path", 

1615 "pid", 

1616 ] 

1617 

1618 def __init__(self, pid): 

1619 self.pid = pid 

1620 self._name = None 

1621 self._ppid = None 

1622 self._ctime = None 

1623 self._procfs_path = get_procfs_path() 

1624 

1625 def _is_zombie(self): 

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

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

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

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

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

1631 # exception. 

1632 try: 

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

1634 except OSError: 

1635 return False 

1636 else: 

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

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

1639 return status == b"Z" 

1640 

1641 def _raise_if_zombie(self): 

1642 if self._is_zombie(): 

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

1644 

1645 def _raise_if_not_alive(self): 

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

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

1648 # incorrect or incomplete result. 

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

1650 

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

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

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

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

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

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

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

1658 try: 

1659 return readlink(path) 

1660 except (FileNotFoundError, ProcessLookupError): 

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

1662 self._raise_if_zombie() 

1663 if fallback is not UNSET: 

1664 return fallback 

1665 raise 

1666 

1667 @wrap_exceptions 

1668 @memoize_when_activated 

1669 def _parse_stat_file(self): 

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

1671 process info. 

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

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

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

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

1676 in use. 

1677 """ 

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

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

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

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

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

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

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

1685 

1686 ret = {} 

1687 ret['name'] = name 

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

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

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

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

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

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

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

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

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

1697 try: 

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

1699 except IndexError: 

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

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

1702 ret['blkio_ticks'] = 0 

1703 

1704 return ret 

1705 

1706 @wrap_exceptions 

1707 @memoize_when_activated 

1708 def _read_status_file(self): 

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

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

1711 in use. 

1712 """ 

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

1714 return f.read() 

1715 

1716 @wrap_exceptions 

1717 @memoize_when_activated 

1718 def _read_smaps_file(self): 

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

1720 return f.read().strip() 

1721 

1722 def oneshot_enter(self): 

1723 self._parse_stat_file.cache_activate(self) 

1724 self._read_status_file.cache_activate(self) 

1725 self._read_smaps_file.cache_activate(self) 

1726 

1727 def oneshot_exit(self): 

1728 self._parse_stat_file.cache_deactivate(self) 

1729 self._read_status_file.cache_deactivate(self) 

1730 self._read_smaps_file.cache_deactivate(self) 

1731 

1732 @wrap_exceptions 

1733 def name(self): 

1734 # XXX - gets changed later and probably needs refactoring 

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

1736 

1737 @wrap_exceptions 

1738 def exe(self): 

1739 return self._readlink( 

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

1741 ) 

1742 

1743 @wrap_exceptions 

1744 def cmdline(self): 

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

1746 data = f.read() 

1747 if not data: 

1748 # may happen in case of zombie process 

1749 self._raise_if_zombie() 

1750 return [] 

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

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

1753 # some processes may change their cmdline after being started 

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

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

1756 # Chrome process is an example. See: 

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

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

1759 if data.endswith(sep): 

1760 data = data[:-1] 

1761 cmdline = data.split(sep) 

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

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

1764 # issues/1179#issuecomment-552984549 

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

1766 cmdline = data.split(' ') 

1767 return cmdline 

1768 

1769 @wrap_exceptions 

1770 def environ(self): 

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

1772 data = f.read() 

1773 return parse_environ_block(data) 

1774 

1775 @wrap_exceptions 

1776 def terminal(self): 

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

1778 tmap = _psposix.get_terminal_map() 

1779 try: 

1780 return tmap[tty_nr] 

1781 except KeyError: 

1782 return None 

1783 

1784 # May not be available on old kernels. 

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

1786 

1787 @wrap_exceptions 

1788 def io_counters(self): 

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

1790 fields = {} 

1791 with open_binary(fname) as f: 

1792 for line in f: 

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

1794 line = line.strip() 

1795 if line: 

1796 try: 

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

1798 except ValueError: 

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

1800 continue 

1801 else: 

1802 fields[name] = int(value) 

1803 if not fields: 

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

1805 raise RuntimeError(msg) 

1806 try: 

1807 return ntp.pio( 

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

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

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

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

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

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

1814 ) 

1815 except KeyError as err: 

1816 msg = ( 

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

1818 f" fields are {fields!r}" 

1819 ) 

1820 raise ValueError(msg) from None 

1821 

1822 @wrap_exceptions 

1823 def cpu_times(self): 

1824 values = self._parse_stat_file() 

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

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

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

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

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

1830 return ntp.pcputimes( 

1831 utime, stime, children_utime, children_stime, iowait 

1832 ) 

1833 

1834 @wrap_exceptions 

1835 def cpu_num(self): 

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

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

1838 

1839 @wrap_exceptions 

1840 def wait(self, timeout=None): 

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

1842 

1843 @wrap_exceptions 

1844 def create_time(self, monotonic=False): 

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

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

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

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

1849 # changes and is unaffected by system clock updates. 

1850 if self._ctime is None: 

1851 self._ctime = ( 

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

1853 ) 

1854 if monotonic: 

1855 return self._ctime 

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

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

1858 return self._ctime + boot_time() 

1859 

1860 @wrap_exceptions 

1861 def memory_info(self): 

1862 # ============================================================ 

1863 # | FIELD | DESCRIPTION | AKA | TOP | 

1864 # ============================================================ 

1865 # | rss | resident set size | | RES | 

1866 # | vms | total program size | size | VIRT | 

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

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

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

1870 # | data | data + stack | drs | DATA | 

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

1872 # ============================================================ 

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

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

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

1876 ) 

1877 return ntp.pmem(rss, vms, shared, text, lib, data, dirty) 

1878 

1879 if HAS_PROC_SMAPS_ROLLUP or HAS_PROC_SMAPS: 

1880 

1881 def _parse_smaps_rollup(self): 

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

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

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

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

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

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

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

1889 # compared to /proc/pid/smaps_rollup. 

1890 uss = pss = swap = 0 

1891 with open_binary( 

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

1893 ) as f: 

1894 for line in f: 

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

1896 # Private_Clean, Private_Dirty, Private_Hugetlb 

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

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

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

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

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

1902 return (uss, pss, swap) 

1903 

1904 @wrap_exceptions 

1905 def _parse_smaps( 

1906 self, 

1907 # Gets Private_Clean, Private_Dirty, Private_Hugetlb. 

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

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

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

1911 ): 

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

1913 # CONFIG_MMU kernel configuration option is not enabled. 

1914 

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

1916 # line by line. 

1917 # 

1918 # You might be tempted to calculate USS by subtracting 

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

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

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

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

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

1924 # correct information. 

1925 smaps_data = self._read_smaps_file() 

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

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

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

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

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

1931 return (uss, pss, swap) 

1932 

1933 @wrap_exceptions 

1934 def memory_full_info(self): 

1935 if HAS_PROC_SMAPS_ROLLUP: # faster 

1936 try: 

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

1938 except (ProcessLookupError, FileNotFoundError): 

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

1940 else: 

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

1942 basic_mem = self.memory_info() 

1943 return ntp.pfullmem(*basic_mem + (uss, pss, swap)) 

1944 

1945 else: 

1946 memory_full_info = memory_info 

1947 

1948 if HAS_PROC_SMAPS: 

1949 

1950 @wrap_exceptions 

1951 def memory_maps(self): 

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

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

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

1955 

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

1957 CONFIG_MMU kernel configuration option is not enabled. 

1958 """ 

1959 

1960 def get_blocks(lines, current_block): 

1961 data = {} 

1962 for line in lines: 

1963 fields = line.split(None, 5) 

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

1965 # new block section 

1966 yield (current_block.pop(), data) 

1967 current_block.append(line) 

1968 else: 

1969 try: 

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

1971 except (ValueError, IndexError): 

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

1973 # see issue #369 

1974 continue 

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

1976 raise ValueError(msg) from None 

1977 yield (current_block.pop(), data) 

1978 

1979 data = self._read_smaps_file() 

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

1981 # zombies. 

1982 if not data: 

1983 self._raise_if_zombie() 

1984 return [] 

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

1986 ls = [] 

1987 first_line = lines.pop(0) 

1988 current_block = [first_line] 

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

1990 hfields = header.split(None, 5) 

1991 try: 

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

1993 except ValueError: 

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

1995 if not path: 

1996 path = '[anon]' 

1997 else: 

1998 path = decode(path) 

1999 path = path.strip() 

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

2001 path 

2002 ): 

2003 path = path[:-10] 

2004 item = ( 

2005 decode(addr), 

2006 decode(perms), 

2007 path, 

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

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

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

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

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

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

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

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

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

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

2018 ) 

2019 ls.append(item) 

2020 return ls 

2021 

2022 @wrap_exceptions 

2023 def cwd(self): 

2024 return self._readlink( 

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

2026 ) 

2027 

2028 @wrap_exceptions 

2029 def num_ctx_switches( 

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

2031 ): 

2032 data = self._read_status_file() 

2033 ctxsw = _ctxsw_re.findall(data) 

2034 if not ctxsw: 

2035 msg = ( 

2036 "'voluntary_ctxt_switches' and" 

2037 " 'nonvoluntary_ctxt_switches'lines were not found in" 

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

2039 " probably older than 2.6.23" 

2040 ) 

2041 raise NotImplementedError(msg) 

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

2043 

2044 @wrap_exceptions 

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

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

2047 data = self._read_status_file() 

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

2049 

2050 @wrap_exceptions 

2051 def threads(self): 

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

2053 thread_ids.sort() 

2054 retlist = [] 

2055 hit_enoent = False 

2056 for thread_id in thread_ids: 

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

2058 try: 

2059 with open_binary(fname) as f: 

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

2061 except (FileNotFoundError, ProcessLookupError): 

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

2063 # it means thread disappeared on us 

2064 hit_enoent = True 

2065 continue 

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

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

2068 values = st.split(b' ') 

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

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

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

2072 retlist.append(ntuple) 

2073 if hit_enoent: 

2074 self._raise_if_not_alive() 

2075 return retlist 

2076 

2077 @wrap_exceptions 

2078 def nice_get(self): 

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

2080 # data = f.read() 

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

2082 

2083 # Use C implementation 

2084 return cext.proc_priority_get(self.pid) 

2085 

2086 @wrap_exceptions 

2087 def nice_set(self, value): 

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

2089 

2090 # starting from CentOS 6. 

2091 if HAS_CPU_AFFINITY: 

2092 

2093 @wrap_exceptions 

2094 def cpu_affinity_get(self): 

2095 return cext.proc_cpu_affinity_get(self.pid) 

2096 

2097 def _get_eligible_cpus( 

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

2099 ): 

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

2101 data = self._read_status_file() 

2102 match = _re.findall(data) 

2103 if match: 

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

2105 else: 

2106 return list(range(len(per_cpu_times()))) 

2107 

2108 @wrap_exceptions 

2109 def cpu_affinity_set(self, cpus): 

2110 try: 

2111 cext.proc_cpu_affinity_set(self.pid, cpus) 

2112 except (OSError, ValueError) as err: 

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

2114 eligible_cpus = self._get_eligible_cpus() 

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

2116 for cpu in cpus: 

2117 if cpu not in all_cpus: 

2118 msg = ( 

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

2120 f" {eligible_cpus!r}" 

2121 ) 

2122 raise ValueError(msg) from None 

2123 if cpu not in eligible_cpus: 

2124 msg = ( 

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

2126 f" between {eligible_cpus}" 

2127 ) 

2128 raise ValueError(msg) from err 

2129 raise 

2130 

2131 # only starting from kernel 2.6.13 

2132 if HAS_PROC_IO_PRIORITY: 

2133 

2134 @wrap_exceptions 

2135 def ionice_get(self): 

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

2137 ioclass = IOPriority(ioclass) 

2138 return ntp.pionice(ioclass, value) 

2139 

2140 @wrap_exceptions 

2141 def ionice_set(self, ioclass, value): 

2142 if value is None: 

2143 value = 0 

2144 if value and ioclass in { 

2145 IOPriority.IOPRIO_CLASS_IDLE, 

2146 IOPriority.IOPRIO_CLASS_NONE, 

2147 }: 

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

2149 raise ValueError(msg) 

2150 if value < 0 or value > 7: 

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

2152 raise ValueError(msg) 

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

2154 

2155 if hasattr(resource, "prlimit"): 

2156 

2157 @wrap_exceptions 

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

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

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

2161 # PID 0 is not supported on Linux. 

2162 if self.pid == 0: 

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

2164 raise ValueError(msg) 

2165 try: 

2166 if limits is None: 

2167 # get 

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

2169 else: 

2170 # set 

2171 if len(limits) != 2: 

2172 msg = ( 

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

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

2175 ) 

2176 raise ValueError(msg) 

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

2178 except OSError as err: 

2179 if err.errno == errno.ENOSYS: 

2180 # I saw this happening on Travis: 

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

2182 self._raise_if_zombie() 

2183 raise 

2184 

2185 @wrap_exceptions 

2186 def status(self): 

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

2188 letter = letter.decode() 

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

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

2191 

2192 @wrap_exceptions 

2193 def open_files(self): 

2194 retlist = [] 

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

2196 hit_enoent = False 

2197 for fd in files: 

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

2199 try: 

2200 path = readlink(file) 

2201 except (FileNotFoundError, ProcessLookupError): 

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

2203 hit_enoent = True 

2204 continue 

2205 except OSError as err: 

2206 if err.errno == errno.EINVAL: 

2207 # not a link 

2208 continue 

2209 if err.errno == errno.ENAMETOOLONG: 

2210 # file name too long 

2211 debug(err) 

2212 continue 

2213 raise 

2214 else: 

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

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

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

2218 # absolute path though. 

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

2220 # Get file position and flags. 

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

2222 try: 

2223 with open_binary(file) as f: 

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

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

2226 except (FileNotFoundError, ProcessLookupError): 

2227 # fd gone in the meantime; process may 

2228 # still be alive 

2229 hit_enoent = True 

2230 else: 

2231 mode = file_flags_to_mode(flags) 

2232 ntuple = ntp.popenfile( 

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

2234 ) 

2235 retlist.append(ntuple) 

2236 if hit_enoent: 

2237 self._raise_if_not_alive() 

2238 return retlist 

2239 

2240 @wrap_exceptions 

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

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

2243 self._raise_if_not_alive() 

2244 return ret 

2245 

2246 @wrap_exceptions 

2247 def num_fds(self): 

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

2249 

2250 @wrap_exceptions 

2251 def ppid(self): 

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

2253 

2254 @wrap_exceptions 

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

2256 data = self._read_status_file() 

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

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

2259 

2260 @wrap_exceptions 

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

2262 data = self._read_status_file() 

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

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