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

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

1251 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 

7from __future__ import division 

8 

9import base64 

10import collections 

11import errno 

12import functools 

13import glob 

14import os 

15import re 

16import socket 

17import struct 

18import sys 

19import warnings 

20from collections import defaultdict 

21from collections import namedtuple 

22 

23from . import _common 

24from . import _psposix 

25from . import _psutil_linux as cext 

26from . import _psutil_posix as cext_posix 

27from ._common import NIC_DUPLEX_FULL 

28from ._common import NIC_DUPLEX_HALF 

29from ._common import NIC_DUPLEX_UNKNOWN 

30from ._common import AccessDenied 

31from ._common import NoSuchProcess 

32from ._common import ZombieProcess 

33from ._common import bcat 

34from ._common import cat 

35from ._common import debug 

36from ._common import decode 

37from ._common import get_procfs_path 

38from ._common import isfile_strict 

39from ._common import memoize 

40from ._common import memoize_when_activated 

41from ._common import open_binary 

42from ._common import open_text 

43from ._common import parse_environ_block 

44from ._common import path_exists_strict 

45from ._common import supports_ipv6 

46from ._common import usage_percent 

47from ._compat import PY3 

48from ._compat import FileNotFoundError 

49from ._compat import PermissionError 

50from ._compat import ProcessLookupError 

51from ._compat import b 

52from ._compat import basestring 

53 

54 

55if PY3: 

56 import enum 

57else: 

58 enum = None 

59 

60 

61# fmt: off 

62__extra__all__ = [ 

63 # 

64 'PROCFS_PATH', 

65 # io prio constants 

66 "IOPRIO_CLASS_NONE", "IOPRIO_CLASS_RT", "IOPRIO_CLASS_BE", 

67 "IOPRIO_CLASS_IDLE", 

68 # connection status constants 

69 "CONN_ESTABLISHED", "CONN_SYN_SENT", "CONN_SYN_RECV", "CONN_FIN_WAIT1", 

70 "CONN_FIN_WAIT2", "CONN_TIME_WAIT", "CONN_CLOSE", "CONN_CLOSE_WAIT", 

71 "CONN_LAST_ACK", "CONN_LISTEN", "CONN_CLOSING", 

72] 

73# fmt: on 

74 

75 

76# ===================================================================== 

77# --- globals 

78# ===================================================================== 

79 

80 

81POWER_SUPPLY_PATH = "/sys/class/power_supply" 

82HAS_PROC_SMAPS = os.path.exists('/proc/%s/smaps' % os.getpid()) 

83HAS_PROC_SMAPS_ROLLUP = os.path.exists('/proc/%s/smaps_rollup' % os.getpid()) 

84HAS_PROC_IO_PRIORITY = hasattr(cext, "proc_ioprio_get") 

85HAS_CPU_AFFINITY = hasattr(cext, "proc_cpu_affinity_get") 

86 

87# Number of clock ticks per second 

88CLOCK_TICKS = os.sysconf("SC_CLK_TCK") 

89PAGESIZE = cext_posix.getpagesize() 

90BOOT_TIME = None # set later 

91LITTLE_ENDIAN = sys.byteorder == 'little' 

92 

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

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

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

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

97# throughout Linux source code: 

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

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

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

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

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

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

104DISK_SECTOR_SIZE = 512 

105 

106if enum is None: 

107 AF_LINK = socket.AF_PACKET 

108else: 

109 AddressFamily = enum.IntEnum( 

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

111 ) 

112 AF_LINK = AddressFamily.AF_LINK 

113 

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

115if enum is None: 

116 IOPRIO_CLASS_NONE = 0 

117 IOPRIO_CLASS_RT = 1 

118 IOPRIO_CLASS_BE = 2 

119 IOPRIO_CLASS_IDLE = 3 

120else: 

121 

122 class IOPriority(enum.IntEnum): 

123 IOPRIO_CLASS_NONE = 0 

124 IOPRIO_CLASS_RT = 1 

125 IOPRIO_CLASS_BE = 2 

126 IOPRIO_CLASS_IDLE = 3 

127 

128 globals().update(IOPriority.__members__) 

129 

130# See: 

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

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

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

134PROC_STATUSES = { 

135 "R": _common.STATUS_RUNNING, 

136 "S": _common.STATUS_SLEEPING, 

137 "D": _common.STATUS_DISK_SLEEP, 

138 "T": _common.STATUS_STOPPED, 

139 "t": _common.STATUS_TRACING_STOP, 

140 "Z": _common.STATUS_ZOMBIE, 

141 "X": _common.STATUS_DEAD, 

142 "x": _common.STATUS_DEAD, 

143 "K": _common.STATUS_WAKE_KILL, 

144 "W": _common.STATUS_WAKING, 

145 "I": _common.STATUS_IDLE, 

146 "P": _common.STATUS_PARKED, 

147} 

148 

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

150TCP_STATUSES = { 

151 "01": _common.CONN_ESTABLISHED, 

152 "02": _common.CONN_SYN_SENT, 

153 "03": _common.CONN_SYN_RECV, 

154 "04": _common.CONN_FIN_WAIT1, 

155 "05": _common.CONN_FIN_WAIT2, 

156 "06": _common.CONN_TIME_WAIT, 

157 "07": _common.CONN_CLOSE, 

158 "08": _common.CONN_CLOSE_WAIT, 

159 "09": _common.CONN_LAST_ACK, 

160 "0A": _common.CONN_LISTEN, 

161 "0B": _common.CONN_CLOSING, 

162} 

163 

164 

165# ===================================================================== 

166# --- named tuples 

167# ===================================================================== 

168 

169 

170# fmt: off 

171# psutil.virtual_memory() 

172svmem = namedtuple( 

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

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

175# psutil.disk_io_counters() 

176sdiskio = namedtuple( 

177 'sdiskio', ['read_count', 'write_count', 

178 'read_bytes', 'write_bytes', 

179 'read_time', 'write_time', 

180 'read_merged_count', 'write_merged_count', 

181 'busy_time']) 

182# psutil.Process().open_files() 

183popenfile = namedtuple( 

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

185# psutil.Process().memory_info() 

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

187# psutil.Process().memory_full_info() 

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

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

190pmmap_grouped = namedtuple( 

191 'pmmap_grouped', 

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

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

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

195pmmap_ext = namedtuple( 

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

197# psutil.Process.io_counters() 

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

199 'read_bytes', 'write_bytes', 

200 'read_chars', 'write_chars']) 

201# psutil.Process.cpu_times() 

202pcputimes = namedtuple('pcputimes', 

203 ['user', 'system', 'children_user', 'children_system', 

204 'iowait']) 

205# fmt: on 

206 

207 

208# ===================================================================== 

209# --- utils 

210# ===================================================================== 

211 

212 

213def readlink(path): 

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

215 assert isinstance(path, basestring), path 

216 path = os.readlink(path) 

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

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

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

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

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

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

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

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

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

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

227 # don't care. 

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

229 path = path[:-10] 

230 return path 

231 

232 

233def file_flags_to_mode(flags): 

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

235 Used by Process.open_files(). 

236 """ 

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

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

239 if flags & os.O_APPEND: 

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

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

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

243 return mode 

244 

245 

246def is_storage_device(name): 

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

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

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

250 return True. 

251 """ 

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

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

254 # 97912938cd476645b267280069e83b1c8dc0e1c7/common.c#L208 

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

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

257 including_virtual = True 

258 if including_virtual: 

259 path = "/sys/block/%s" % name 

260 else: 

261 path = "/sys/block/%s/device" % name 

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

263 

264 

265@memoize 

266def set_scputimes_ntuple(procfs_path): 

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

268 available on this Linux kernel version which may be: 

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

270 [guest_nice]]]) 

271 Used by cpu_times() function. 

272 """ 

273 global scputimes 

274 with open_binary('%s/stat' % procfs_path) as f: 

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

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

277 vlen = len(values) 

278 if vlen >= 8: 

279 # Linux >= 2.6.11 

280 fields.append('steal') 

281 if vlen >= 9: 

282 # Linux >= 2.6.24 

283 fields.append('guest') 

284 if vlen >= 10: 

285 # Linux >= 3.2.0 

286 fields.append('guest_nice') 

287 scputimes = namedtuple('scputimes', fields) 

288 

289 

290try: 

291 set_scputimes_ntuple("/proc") 

292except Exception as err: # noqa: BLE001 

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

294 debug("ignoring exception on import: %r" % err) 

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

296 

297 

298# ===================================================================== 

299# --- prlimit 

300# ===================================================================== 

301 

302# Backport of resource.prlimit() for Python 2. Originally this was done 

303# in C, but CentOS-6 which we use to create manylinux wheels is too old 

304# and does not support prlimit() syscall. As such the resulting wheel 

305# would not include prlimit(), even when installed on newer systems. 

306# This is the only part of psutil using ctypes. 

307 

308prlimit = None 

309try: 

310 from resource import prlimit # python >= 3.4 

311except ImportError: 

312 import ctypes 

313 

314 libc = ctypes.CDLL(None, use_errno=True) 

315 

316 if hasattr(libc, "prlimit"): 

317 

318 def prlimit(pid, resource_, limits=None): 

319 class StructRlimit(ctypes.Structure): 

320 _fields_ = [ 

321 ('rlim_cur', ctypes.c_longlong), 

322 ('rlim_max', ctypes.c_longlong), 

323 ] 

324 

325 current = StructRlimit() 

326 if limits is None: 

327 # get 

328 ret = libc.prlimit(pid, resource_, None, ctypes.byref(current)) 

329 else: 

330 # set 

331 new = StructRlimit() 

332 new.rlim_cur = limits[0] 

333 new.rlim_max = limits[1] 

334 ret = libc.prlimit( 

335 pid, resource_, ctypes.byref(new), ctypes.byref(current) 

336 ) 

337 

338 if ret != 0: 

339 errno_ = ctypes.get_errno() 

340 raise OSError(errno_, os.strerror(errno_)) 

341 return (current.rlim_cur, current.rlim_max) 

342 

343 

344if prlimit is not None: 

345 __extra__all__.extend( 

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

347 ) 

348 

349 

350# ===================================================================== 

351# --- system memory 

352# ===================================================================== 

353 

354 

355def calculate_avail_vmem(mems): 

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

357 "MemAvailable", see: 

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

359 

360 This code reimplements the algorithm outlined here: 

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

362 commit/?id=34e431b0ae398fc54ea69ff85ec700722c9da773 

363 

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

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

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

367 column). 

368 

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

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

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

372 See: 

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

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

375 """ 

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

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

378 # commit/?id=34e431b0ae398fc54ea69ff85ec700722c9da773 

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

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

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

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

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

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

385 free = mems[b'MemFree:'] 

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

387 try: 

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

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

390 slab_reclaimable = mems[b'SReclaimable:'] 

391 except KeyError as err: 

392 debug( 

393 "%s is missing from /proc/meminfo; using an approximation for " 

394 "calculating available memory" 

395 % err.args[0] 

396 ) 

397 return fallback 

398 try: 

399 f = open_binary('%s/zoneinfo' % get_procfs_path()) 

400 except IOError: 

401 return fallback # kernel 2.6.13 

402 

403 watermark_low = 0 

404 with f: 

405 for line in f: 

406 line = line.strip() 

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

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

409 watermark_low *= PAGESIZE 

410 

411 avail = free - watermark_low 

412 pagecache = lru_active_file + lru_inactive_file 

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

414 avail += pagecache 

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

416 return int(avail) 

417 

418 

419def virtual_memory(): 

420 """Report virtual memory stats. 

421 This implementation mimicks procps-ng-3.3.12, aka "free" CLI tool: 

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

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

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

425 CLI tools. 

426 """ 

427 missing_fields = [] 

428 mems = {} 

429 with open_binary('%s/meminfo' % get_procfs_path()) as f: 

430 for line in f: 

431 fields = line.split() 

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

433 

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

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

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

437 total = mems[b'MemTotal:'] 

438 free = mems[b'MemFree:'] 

439 try: 

440 buffers = mems[b'Buffers:'] 

441 except KeyError: 

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

443 buffers = 0 

444 missing_fields.append('buffers') 

445 try: 

446 cached = mems[b"Cached:"] 

447 except KeyError: 

448 cached = 0 

449 missing_fields.append('cached') 

450 else: 

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

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

453 # This got changed in: 

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

455 # 05d751c4f076a2f0118b914c5e51cfbb4762ad8e 

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

457 

458 try: 

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

460 except KeyError: 

461 try: 

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

463 except KeyError: 

464 shared = 0 

465 missing_fields.append('shared') 

466 

467 try: 

468 active = mems[b"Active:"] 

469 except KeyError: 

470 active = 0 

471 missing_fields.append('active') 

472 

473 try: 

474 inactive = mems[b"Inactive:"] 

475 except KeyError: 

476 try: 

477 inactive = ( 

478 mems[b"Inact_dirty:"] 

479 + mems[b"Inact_clean:"] 

480 + mems[b"Inact_laundry:"] 

481 ) 

482 except KeyError: 

483 inactive = 0 

484 missing_fields.append('inactive') 

485 

486 try: 

487 slab = mems[b"Slab:"] 

488 except KeyError: 

489 slab = 0 

490 

491 used = total - free - cached - buffers 

492 if used < 0: 

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

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

495 used = total - free 

496 

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

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

499 # which matched htop. 

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

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

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

503 # - MemAvailable has been introduced in kernel 3.14 

504 try: 

505 avail = mems[b'MemAvailable:'] 

506 except KeyError: 

507 avail = calculate_avail_vmem(mems) 

508 else: 

509 if avail == 0: 

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

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

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

513 # and it matches "free" CLI tool. 

514 avail = calculate_avail_vmem(mems) 

515 

516 if avail < 0: 

517 avail = 0 

518 missing_fields.append('available') 

519 elif avail > total: 

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

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

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

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

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

525 avail = free 

526 

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

528 

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

530 if missing_fields: 

531 msg = "%s memory stats couldn't be determined and %s set to 0" % ( 

532 ", ".join(missing_fields), 

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

534 ) 

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

536 

537 return svmem( 

538 total, 

539 avail, 

540 percent, 

541 used, 

542 free, 

543 active, 

544 inactive, 

545 buffers, 

546 cached, 

547 shared, 

548 slab, 

549 ) 

550 

551 

552def swap_memory(): 

553 """Return swap memory metrics.""" 

554 mems = {} 

555 with open_binary('%s/meminfo' % get_procfs_path()) as f: 

556 for line in f: 

557 fields = line.split() 

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

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

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

561 # for linux containers, see: 

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

563 try: 

564 total = mems[b'SwapTotal:'] 

565 free = mems[b'SwapFree:'] 

566 except KeyError: 

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

568 total *= unit_multiplier 

569 free *= unit_multiplier 

570 

571 used = total - free 

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

573 # get pgin/pgouts 

574 try: 

575 f = open_binary("%s/vmstat" % get_procfs_path()) 

576 except IOError as err: 

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

578 msg = ( 

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

580 + "be determined and were set to 0 (%s)" % str(err) 

581 ) 

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

583 sin = sout = 0 

584 else: 

585 with f: 

586 sin = sout = None 

587 for line in f: 

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

589 # bytes instead 

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

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

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

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

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

595 break 

596 else: 

597 # we might get here when dealing with exotic Linux 

598 # flavors, see: 

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

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

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

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

603 sin = sout = 0 

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

605 

606 

607# ===================================================================== 

608# --- CPU 

609# ===================================================================== 

610 

611 

612def cpu_times(): 

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

614 CPU times: 

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

616 [guest_nice]]]) 

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

618 """ 

619 procfs_path = get_procfs_path() 

620 set_scputimes_ntuple(procfs_path) 

621 with open_binary('%s/stat' % procfs_path) as f: 

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

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

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

625 return scputimes(*fields) 

626 

627 

628def per_cpu_times(): 

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

630 for every CPU available on the system. 

631 """ 

632 procfs_path = get_procfs_path() 

633 set_scputimes_ntuple(procfs_path) 

634 cpus = [] 

635 with open_binary('%s/stat' % procfs_path) as f: 

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

637 f.readline() 

638 for line in f: 

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

640 values = line.split() 

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

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

643 entry = scputimes(*fields) 

644 cpus.append(entry) 

645 return cpus 

646 

647 

648def cpu_count_logical(): 

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

650 try: 

651 return os.sysconf("SC_NPROCESSORS_ONLN") 

652 except ValueError: 

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

654 num = 0 

655 with open_binary('%s/cpuinfo' % get_procfs_path()) as f: 

656 for line in f: 

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

658 num += 1 

659 

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

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

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

663 if num == 0: 

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

665 with open_text('%s/stat' % get_procfs_path()) as f: 

666 for line in f: 

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

668 if search.match(line): 

669 num += 1 

670 

671 if num == 0: 

672 # mimic os.cpu_count() 

673 return None 

674 return num 

675 

676 

677def cpu_count_cores(): 

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

679 # Method #1 

680 ls = set() 

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

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

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

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

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

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

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

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

689 with open_binary(path) as f: 

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

691 result = len(ls) 

692 if result != 0: 

693 return result 

694 

695 # Method #2 

696 mapping = {} 

697 current_info = {} 

698 with open_binary('%s/cpuinfo' % get_procfs_path()) as f: 

699 for line in f: 

700 line = line.strip().lower() 

701 if not line: 

702 # new section 

703 try: 

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

705 b'cpu cores' 

706 ] 

707 except KeyError: 

708 pass 

709 current_info = {} 

710 else: 

711 # ongoing section 

712 if line.startswith((b'physical id', b'cpu cores')): 

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

714 current_info[key] = int(value) 

715 

716 result = sum(mapping.values()) 

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

718 

719 

720def cpu_stats(): 

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

722 with open_binary('%s/stat' % get_procfs_path()) as f: 

723 ctx_switches = None 

724 interrupts = None 

725 soft_interrupts = None 

726 for line in f: 

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

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

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

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

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

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

733 if ( 

734 ctx_switches is not None 

735 and soft_interrupts is not None 

736 and interrupts is not None 

737 ): 

738 break 

739 syscalls = 0 

740 return _common.scpustats( 

741 ctx_switches, interrupts, soft_interrupts, syscalls 

742 ) 

743 

744 

745def _cpu_get_cpuinfo_freq(): 

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

747 ret = [] 

748 with open_binary('%s/cpuinfo' % get_procfs_path()) as f: 

749 for line in f: 

750 if line.lower().startswith(b'cpu mhz'): 

751 ret.append(float(line.split(b':', 1)[1])) 

752 return ret 

753 

754 

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

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

757): 

758 

759 def cpu_freq(): 

760 """Return frequency metrics for all CPUs. 

761 Contrarily to other OSes, Linux updates these values in 

762 real-time. 

763 """ 

764 cpuinfo_freqs = _cpu_get_cpuinfo_freq() 

765 paths = glob.glob( 

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

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

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

769 ret = [] 

770 pjoin = os.path.join 

771 for i, path in enumerate(paths): 

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

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

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

775 curr = cpuinfo_freqs[i] * 1000 

776 else: 

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

778 if curr is None: 

779 # Likely an old RedHat, see: 

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

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

782 if curr is None: 

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

784 raise NotImplementedError(msg) 

785 curr = int(curr) / 1000 

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

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

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

789 return ret 

790 

791else: 

792 

793 def cpu_freq(): 

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

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

796 """ 

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

798 

799 

800# ===================================================================== 

801# --- network 

802# ===================================================================== 

803 

804 

805net_if_addrs = cext_posix.net_if_addrs 

806 

807 

808class _Ipv6UnsupportedError(Exception): 

809 pass 

810 

811 

812class Connections: 

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

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

815 "netstat -an". 

816 

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

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

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

820 

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

822 """ 

823 

824 def __init__(self): 

825 # The string represents the basename of the corresponding 

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

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

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

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

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

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

832 self.tmap = { 

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

834 "tcp": (tcp4, tcp6), 

835 "tcp4": (tcp4,), 

836 "tcp6": (tcp6,), 

837 "udp": (udp4, udp6), 

838 "udp4": (udp4,), 

839 "udp6": (udp6,), 

840 "unix": (unix,), 

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

842 "inet4": (tcp4, udp4), 

843 "inet6": (tcp6, udp6), 

844 } 

845 self._procfs_path = None 

846 

847 def get_proc_inodes(self, pid): 

848 inodes = defaultdict(list) 

849 for fd in os.listdir("%s/%s/fd" % (self._procfs_path, pid)): 

850 try: 

851 inode = readlink("%s/%s/fd/%s" % (self._procfs_path, pid, fd)) 

852 except (FileNotFoundError, ProcessLookupError): 

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

854 # os.stat('/proc/%s' % self.pid) will be done later 

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

856 continue 

857 except OSError as err: 

858 if err.errno == errno.EINVAL: 

859 # not a link 

860 continue 

861 if err.errno == errno.ENAMETOOLONG: 

862 # file name too long 

863 debug(err) 

864 continue 

865 raise 

866 else: 

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

868 # the process is using a socket 

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

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

871 return inodes 

872 

873 def get_all_inodes(self): 

874 inodes = {} 

875 for pid in pids(): 

876 try: 

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

878 except (FileNotFoundError, ProcessLookupError, PermissionError): 

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

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

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

882 # and fd set to None anyway. 

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

884 # unlikely we can do any better. 

885 # ENOENT just means a PID disappeared on us. 

886 continue 

887 return inodes 

888 

889 @staticmethod 

890 def decode_address(addr, family): 

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

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

893 

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

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

896 

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

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

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

900 to an IP address. 

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

902 

903 Reference: 

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

905 """ 

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

907 port = int(port, 16) 

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

909 # no end-points connected 

910 if not port: 

911 return () 

912 if PY3: 

913 ip = ip.encode('ascii') 

914 if family == socket.AF_INET: 

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

916 if LITTLE_ENDIAN: 

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

918 else: 

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

920 else: # IPv6 

921 ip = base64.b16decode(ip) 

922 try: 

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

924 if LITTLE_ENDIAN: 

925 ip = socket.inet_ntop( 

926 socket.AF_INET6, 

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

928 ) 

929 else: 

930 ip = socket.inet_ntop( 

931 socket.AF_INET6, 

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

933 ) 

934 except ValueError: 

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

936 if not supports_ipv6(): 

937 raise _Ipv6UnsupportedError 

938 else: 

939 raise 

940 return _common.addr(ip, port) 

941 

942 @staticmethod 

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

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

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

946 # IPv6 not supported 

947 return 

948 with open_text(file) as f: 

949 f.readline() # skip the first line 

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

951 try: 

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

953 line.split()[:10] 

954 ) 

955 except ValueError: 

956 raise RuntimeError( 

957 "error while parsing %s; malformed line %s %r" 

958 % (file, lineno, line) 

959 ) 

960 if inode in inodes: 

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

962 # # out if there are multiple references to the 

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

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

965 # raise ValueError("ambiguous inode with multiple " 

966 # "PIDs references") 

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

968 else: 

969 pid, fd = None, -1 

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

971 continue 

972 else: 

973 if type_ == socket.SOCK_STREAM: 

974 status = TCP_STATUSES[status] 

975 else: 

976 status = _common.CONN_NONE 

977 try: 

978 laddr = Connections.decode_address(laddr, family) 

979 raddr = Connections.decode_address(raddr, family) 

980 except _Ipv6UnsupportedError: 

981 continue 

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

983 

984 @staticmethod 

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

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

987 with open_text(file) as f: 

988 f.readline() # skip the first line 

989 for line in f: 

990 tokens = line.split() 

991 try: 

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

993 except ValueError: 

994 if ' ' not in line: 

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

996 continue 

997 raise RuntimeError( 

998 "error while parsing %s; malformed line %r" 

999 % (file, line) 

1000 ) 

1001 if inode in inodes: # noqa 

1002 # With UNIX sockets we can have a single inode 

1003 # referencing many file descriptors. 

1004 pairs = inodes[inode] 

1005 else: 

1006 pairs = [(None, -1)] 

1007 for pid, fd in pairs: 

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

1009 continue 

1010 else: 

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

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

1013 # XXX: determining the remote endpoint of a 

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

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

1016 raddr = "" 

1017 status = _common.CONN_NONE 

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

1019 

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

1021 if kind not in self.tmap: 

1022 raise ValueError( 

1023 "invalid %r kind argument; choose between %s" 

1024 % (kind, ', '.join([repr(x) for x in self.tmap])) 

1025 ) 

1026 self._procfs_path = get_procfs_path() 

1027 if pid is not None: 

1028 inodes = self.get_proc_inodes(pid) 

1029 if not inodes: 

1030 # no connections for this process 

1031 return [] 

1032 else: 

1033 inodes = self.get_all_inodes() 

1034 ret = set() 

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

1036 path = "%s/net/%s" % (self._procfs_path, proto_name) 

1037 if family in (socket.AF_INET, socket.AF_INET6): 

1038 ls = self.process_inet( 

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

1040 ) 

1041 else: 

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

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

1044 if pid: 

1045 conn = _common.pconn( 

1046 fd, family, type_, laddr, raddr, status 

1047 ) 

1048 else: 

1049 conn = _common.sconn( 

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

1051 ) 

1052 ret.add(conn) 

1053 return list(ret) 

1054 

1055 

1056_connections = Connections() 

1057 

1058 

1059def net_connections(kind='inet'): 

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

1061 return _connections.retrieve(kind) 

1062 

1063 

1064def net_io_counters(): 

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

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

1067 """ 

1068 with open_text("%s/net/dev" % get_procfs_path()) as f: 

1069 lines = f.readlines() 

1070 retdict = {} 

1071 for line in lines[2:]: 

1072 colon = line.rfind(':') 

1073 assert colon > 0, repr(line) 

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

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

1076 

1077 # in 

1078 ( 

1079 bytes_recv, 

1080 packets_recv, 

1081 errin, 

1082 dropin, 

1083 fifoin, # unused 

1084 framein, # unused 

1085 compressedin, # unused 

1086 multicastin, # unused 

1087 # out 

1088 bytes_sent, 

1089 packets_sent, 

1090 errout, 

1091 dropout, 

1092 fifoout, # unused 

1093 collisionsout, # unused 

1094 carrierout, # unused 

1095 compressedout, 

1096 ) = map(int, fields) 

1097 

1098 retdict[name] = ( 

1099 bytes_sent, 

1100 bytes_recv, 

1101 packets_sent, 

1102 packets_recv, 

1103 errin, 

1104 errout, 

1105 dropin, 

1106 dropout, 

1107 ) 

1108 return retdict 

1109 

1110 

1111def net_if_stats(): 

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

1113 duplex_map = { 

1114 cext.DUPLEX_FULL: NIC_DUPLEX_FULL, 

1115 cext.DUPLEX_HALF: NIC_DUPLEX_HALF, 

1116 cext.DUPLEX_UNKNOWN: NIC_DUPLEX_UNKNOWN, 

1117 } 

1118 names = net_io_counters().keys() 

1119 ret = {} 

1120 for name in names: 

1121 try: 

1122 mtu = cext_posix.net_if_mtu(name) 

1123 flags = cext_posix.net_if_flags(name) 

1124 duplex, speed = cext.net_if_duplex_speed(name) 

1125 except OSError as err: 

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

1127 if err.errno != errno.ENODEV: 

1128 raise 

1129 else: 

1130 debug(err) 

1131 else: 

1132 output_flags = ','.join(flags) 

1133 isup = 'running' in flags 

1134 ret[name] = _common.snicstats( 

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

1136 ) 

1137 return ret 

1138 

1139 

1140# ===================================================================== 

1141# --- disks 

1142# ===================================================================== 

1143 

1144 

1145disk_usage = _psposix.disk_usage 

1146 

1147 

1148def disk_io_counters(perdisk=False): 

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

1150 system as a dict of raw tuples. 

1151 """ 

1152 

1153 def read_procfs(): 

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

1155 # have 3 variations. 

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

1157 # "3 0 8 hda 8 8 8 8 8 8 8 8 8 8 8" 

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

1159 # name is in another position, like this: 

1160 # "3 0 hda 8 8 8 8 8 8 8 8 8 8 8" 

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

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

1163 # "3 1 hda1 8 8 8 8" 

1164 # 4.18+ has 4 fields added: 

1165 # "3 0 hda 8 8 8 8 8 8 8 8 8 8 8 0 0 0 0" 

1166 # 5.5 has 2 more fields. 

1167 # See: 

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

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

1170 with open_text("%s/diskstats" % get_procfs_path()) as f: 

1171 lines = f.readlines() 

1172 for line in lines: 

1173 fields = line.split() 

1174 flen = len(fields) 

1175 # fmt: off 

1176 if flen == 15: 

1177 # Linux 2.4 

1178 name = fields[3] 

1179 reads = int(fields[2]) 

1180 (reads_merged, rbytes, rtime, writes, writes_merged, 

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

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

1183 # Linux 2.6+, line referring to a disk 

1184 name = fields[2] 

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

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

1187 elif flen == 7: 

1188 # Linux 2.6+, line referring to a partition 

1189 name = fields[2] 

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

1191 rtime = wtime = reads_merged = writes_merged = busy_time = 0 

1192 else: 

1193 raise ValueError("not sure how to interpret line %r" % line) 

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

1195 reads_merged, writes_merged, busy_time) 

1196 # fmt: on 

1197 

1198 def read_sysfs(): 

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

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

1201 if 'stat' not in files: 

1202 continue 

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

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

1205 name = os.path.basename(root) 

1206 # fmt: off 

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

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

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

1210 wtime, reads_merged, writes_merged, busy_time) 

1211 # fmt: on 

1212 

1213 if os.path.exists('%s/diskstats' % get_procfs_path()): 

1214 gen = read_procfs() 

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

1216 gen = read_sysfs() 

1217 else: 

1218 raise NotImplementedError( 

1219 "%s/diskstats nor /sys/block filesystem are available on this " 

1220 "system" 

1221 % get_procfs_path() 

1222 ) 

1223 

1224 retdict = {} 

1225 for entry in gen: 

1226 # fmt: off 

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

1228 writes_merged, busy_time) = entry 

1229 if not perdisk and not is_storage_device(name): 

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

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

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

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

1234 # of their own: 

1235 # $ cat /proc/diskstats 

1236 # 259 0 sda 10485760 ... 

1237 # 259 1 sda1 5186039 ... 

1238 # 259 1 sda2 5082039 ... 

1239 # See: 

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

1241 continue 

1242 

1243 rbytes *= DISK_SECTOR_SIZE 

1244 wbytes *= DISK_SECTOR_SIZE 

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

1246 reads_merged, writes_merged, busy_time) 

1247 # fmt: on 

1248 

1249 return retdict 

1250 

1251 

1252class RootFsDeviceFinder: 

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

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

1255 obtain the real device path. Resources: 

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

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

1258 """ 

1259 

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

1261 

1262 def __init__(self): 

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

1264 self.major = os.major(dev) 

1265 self.minor = os.minor(dev) 

1266 

1267 def ask_proc_partitions(self): 

1268 with open_text("%s/partitions" % get_procfs_path()) as f: 

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

1270 fields = line.split() 

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

1272 continue 

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

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

1275 name = fields[3] 

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

1277 if name: # just for extra safety 

1278 return "/dev/%s" % name 

1279 

1280 def ask_sys_dev_block(self): 

1281 path = "/sys/dev/block/%s:%s/uevent" % (self.major, self.minor) 

1282 with open_text(path) as f: 

1283 for line in f: 

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

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

1286 if name: # just for extra safety 

1287 return "/dev/%s" % name 

1288 

1289 def ask_sys_class_block(self): 

1290 needle = "%s:%s" % (self.major, self.minor) 

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

1292 for file in files: 

1293 try: 

1294 f = open_text(file) 

1295 except FileNotFoundError: # race condition 

1296 continue 

1297 else: 

1298 with f: 

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

1300 if data == needle: 

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

1302 return "/dev/%s" % name 

1303 

1304 def find(self): 

1305 path = None 

1306 if path is None: 

1307 try: 

1308 path = self.ask_proc_partitions() 

1309 except (IOError, OSError) as err: 

1310 debug(err) 

1311 if path is None: 

1312 try: 

1313 path = self.ask_sys_dev_block() 

1314 except (IOError, OSError) as err: 

1315 debug(err) 

1316 if path is None: 

1317 try: 

1318 path = self.ask_sys_class_block() 

1319 except (IOError, OSError) as err: 

1320 debug(err) 

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

1322 # coded, so we want to be sure. 

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

1324 return path 

1325 

1326 

1327def disk_partitions(all=False): 

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

1329 fstypes = set() 

1330 procfs_path = get_procfs_path() 

1331 if not all: 

1332 with open_text("%s/filesystems" % procfs_path) as f: 

1333 for line in f: 

1334 line = line.strip() 

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

1336 fstypes.add(line.strip()) 

1337 else: 

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

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

1340 if fstype == "zfs": 

1341 fstypes.add("zfs") 

1342 

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

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

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

1346 else: 

1347 mounts_path = os.path.realpath("%s/self/mounts" % procfs_path) 

1348 

1349 retlist = [] 

1350 partitions = cext.disk_partitions(mounts_path) 

1351 for partition in partitions: 

1352 device, mountpoint, fstype, opts = partition 

1353 if device == 'none': 

1354 device = '' 

1355 if device in ("/dev/root", "rootfs"): 

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

1357 if not all: 

1358 if device == '' or fstype not in fstypes: 

1359 continue 

1360 maxfile = maxpath = None # set later 

1361 ntuple = _common.sdiskpart( 

1362 device, mountpoint, fstype, opts, maxfile, maxpath 

1363 ) 

1364 retlist.append(ntuple) 

1365 

1366 return retlist 

1367 

1368 

1369# ===================================================================== 

1370# --- sensors 

1371# ===================================================================== 

1372 

1373 

1374def sensors_temperatures(): 

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

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

1377 temperatures. 

1378 

1379 Implementation notes: 

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

1381 retrieve this info, and this implementation relies on it 

1382 only (old distros will probably use something else) 

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

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

1385 difficult to parse 

1386 """ 

1387 ret = collections.defaultdict(list) 

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

1389 # CentOS has an intermediate /device directory: 

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

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

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

1393 basenames = sorted(set([x.split('_')[0] for x in basenames])) 

1394 

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

1396 # /sys/class/hwmon/ 

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

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

1399 basenames2 = glob.glob( 

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

1401 ) 

1402 repl = re.compile('/sys/devices/platform/coretemp.*/hwmon/') 

1403 for name in basenames2: 

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

1405 if altname not in basenames: 

1406 basenames.append(name) 

1407 

1408 for base in basenames: 

1409 try: 

1410 path = base + '_input' 

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

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

1413 unit_name = cat(path).strip() 

1414 except (IOError, OSError, ValueError): 

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

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

1417 # is a stinky broken mess. 

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

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

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

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

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

1423 continue 

1424 

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

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

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

1428 

1429 if high is not None: 

1430 try: 

1431 high = float(high) / 1000.0 

1432 except ValueError: 

1433 high = None 

1434 if critical is not None: 

1435 try: 

1436 critical = float(critical) / 1000.0 

1437 except ValueError: 

1438 critical = None 

1439 

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

1441 

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

1443 if not basenames: 

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

1445 basenames = sorted(set(basenames)) 

1446 

1447 for base in basenames: 

1448 try: 

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

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

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

1452 unit_name = cat(path).strip() 

1453 except (IOError, OSError, ValueError) as err: 

1454 debug(err) 

1455 continue 

1456 

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

1458 trip_points = set([ 

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

1460 for p in trip_paths 

1461 ]) 

1462 critical = None 

1463 high = None 

1464 for trip_point in trip_points: 

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

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

1467 if trip_type == 'critical': 

1468 critical = bcat( 

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

1470 ) 

1471 elif trip_type == 'high': 

1472 high = bcat( 

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

1474 ) 

1475 

1476 if high is not None: 

1477 try: 

1478 high = float(high) / 1000.0 

1479 except ValueError: 

1480 high = None 

1481 if critical is not None: 

1482 try: 

1483 critical = float(critical) / 1000.0 

1484 except ValueError: 

1485 critical = None 

1486 

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

1488 

1489 return dict(ret) 

1490 

1491 

1492def sensors_fans(): 

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

1494 dict including hardware label and current speed. 

1495 

1496 Implementation notes: 

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

1498 retrieve this info, and this implementation relies on it 

1499 only (old distros will probably use something else) 

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

1501 """ 

1502 ret = collections.defaultdict(list) 

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

1504 if not basenames: 

1505 # CentOS has an intermediate /device directory: 

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

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

1508 

1509 basenames = sorted(set([x.split('_')[0] for x in basenames])) 

1510 for base in basenames: 

1511 try: 

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

1513 except (IOError, OSError) as err: 

1514 debug(err) 

1515 continue 

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

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

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

1519 

1520 return dict(ret) 

1521 

1522 

1523def sensors_battery(): 

1524 """Return battery information. 

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

1526 directory structure may vary and provide files with the same 

1527 meaning but under different names, see: 

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

1529 """ 

1530 null = object() 

1531 

1532 def multi_bcat(*paths): 

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

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

1535 """ 

1536 for path in paths: 

1537 ret = bcat(path, fallback=null) 

1538 if ret != null: 

1539 try: 

1540 return int(ret) 

1541 except ValueError: 

1542 return ret.strip() 

1543 return None 

1544 

1545 bats = [ 

1546 x 

1547 for x in os.listdir(POWER_SUPPLY_PATH) 

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

1549 ] 

1550 if not bats: 

1551 return None 

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

1553 # some rare exceptions: 

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

1555 root = os.path.join(POWER_SUPPLY_PATH, sorted(bats)[0]) 

1556 

1557 # Base metrics. 

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

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

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

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

1562 

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

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

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

1566 try: 

1567 percent = 100.0 * energy_now / energy_full 

1568 except ZeroDivisionError: 

1569 percent = 0.0 

1570 else: 

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

1572 if percent == -1: 

1573 return None 

1574 

1575 # Is AC power cable plugged in? 

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

1577 # it's called "AC". 

1578 power_plugged = None 

1579 online = multi_bcat( 

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

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

1582 ) 

1583 if online is not None: 

1584 power_plugged = online == 1 

1585 else: 

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

1587 if status == "discharging": 

1588 power_plugged = False 

1589 elif status in ("charging", "full"): 

1590 power_plugged = True 

1591 

1592 # Seconds left. 

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

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

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

1596 if power_plugged: 

1597 secsleft = _common.POWER_TIME_UNLIMITED 

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

1599 try: 

1600 secsleft = int(energy_now / power_now * 3600) 

1601 except ZeroDivisionError: 

1602 secsleft = _common.POWER_TIME_UNKNOWN 

1603 elif time_to_empty is not None: 

1604 secsleft = int(time_to_empty * 60) 

1605 if secsleft < 0: 

1606 secsleft = _common.POWER_TIME_UNKNOWN 

1607 else: 

1608 secsleft = _common.POWER_TIME_UNKNOWN 

1609 

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

1611 

1612 

1613# ===================================================================== 

1614# --- other system functions 

1615# ===================================================================== 

1616 

1617 

1618def users(): 

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

1620 retlist = [] 

1621 rawlist = cext.users() 

1622 for item in rawlist: 

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

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

1625 retlist.append(nt) 

1626 return retlist 

1627 

1628 

1629def boot_time(): 

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

1631 global BOOT_TIME 

1632 path = '%s/stat' % get_procfs_path() 

1633 with open_binary(path) as f: 

1634 for line in f: 

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

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

1637 BOOT_TIME = ret 

1638 return ret 

1639 raise RuntimeError("line 'btime' not found in %s" % path) 

1640 

1641 

1642# ===================================================================== 

1643# --- processes 

1644# ===================================================================== 

1645 

1646 

1647def pids(): 

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

1649 return [int(x) for x in os.listdir(b(get_procfs_path())) if x.isdigit()] 

1650 

1651 

1652def pid_exists(pid): 

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

1654 supported (always return False). 

1655 """ 

1656 if not _psposix.pid_exists(pid): 

1657 return False 

1658 else: 

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

1660 # (thread IDs). 

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

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

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

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

1665 # only, see: 

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

1667 try: 

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

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

1670 # 'return pid in pids()' 

1671 path = "%s/%s/status" % (get_procfs_path(), pid) 

1672 with open_binary(path) as f: 

1673 for line in f: 

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

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

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

1677 # dealing with a process PID. 

1678 return tgid == pid 

1679 raise ValueError("'Tgid' line not found in %s" % path) 

1680 except (EnvironmentError, ValueError): 

1681 return pid in pids() 

1682 

1683 

1684def ppid_map(): 

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

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

1687 """ 

1688 ret = {} 

1689 procfs_path = get_procfs_path() 

1690 for pid in pids(): 

1691 try: 

1692 with open_binary("%s/%s/stat" % (procfs_path, pid)) as f: 

1693 data = f.read() 

1694 except (FileNotFoundError, ProcessLookupError): 

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

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

1697 pass 

1698 else: 

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

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

1701 ppid = int(dset[1]) 

1702 ret[pid] = ppid 

1703 return ret 

1704 

1705 

1706def wrap_exceptions(fun): 

1707 """Decorator which translates bare OSError and IOError exceptions 

1708 into NoSuchProcess and AccessDenied. 

1709 """ 

1710 

1711 @functools.wraps(fun) 

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

1713 try: 

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

1715 except PermissionError: 

1716 raise AccessDenied(self.pid, self._name) 

1717 except ProcessLookupError: 

1718 self._raise_if_zombie() 

1719 raise NoSuchProcess(self.pid, self._name) 

1720 except FileNotFoundError: 

1721 self._raise_if_zombie() 

1722 if not os.path.exists("%s/%s" % (self._procfs_path, self.pid)): 

1723 raise NoSuchProcess(self.pid, self._name) 

1724 raise 

1725 

1726 return wrapper 

1727 

1728 

1729class Process: 

1730 """Linux process implementation.""" 

1731 

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

1733 

1734 def __init__(self, pid): 

1735 self.pid = pid 

1736 self._name = None 

1737 self._ppid = None 

1738 self._procfs_path = get_procfs_path() 

1739 

1740 def _is_zombie(self): 

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

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

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

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

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

1746 # exception. 

1747 try: 

1748 data = bcat("%s/%s/stat" % (self._procfs_path, self.pid)) 

1749 except (IOError, OSError): 

1750 return False 

1751 else: 

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

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

1754 return status == b"Z" 

1755 

1756 def _raise_if_zombie(self): 

1757 if self._is_zombie(): 

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

1759 

1760 def _raise_if_not_alive(self): 

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

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

1763 # incorrect or incomplete result. 

1764 os.stat('%s/%s' % (self._procfs_path, self.pid)) 

1765 

1766 @wrap_exceptions 

1767 @memoize_when_activated 

1768 def _parse_stat_file(self): 

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

1770 process info. 

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

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

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

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

1775 in use. 

1776 """ 

1777 data = bcat("%s/%s/stat" % (self._procfs_path, self.pid)) 

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

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

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

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

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

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

1784 

1785 ret = {} 

1786 ret['name'] = name 

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

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

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

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

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

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

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

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

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

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

1797 

1798 return ret 

1799 

1800 @wrap_exceptions 

1801 @memoize_when_activated 

1802 def _read_status_file(self): 

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

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

1805 in use. 

1806 """ 

1807 with open_binary("%s/%s/status" % (self._procfs_path, self.pid)) as f: 

1808 return f.read() 

1809 

1810 @wrap_exceptions 

1811 @memoize_when_activated 

1812 def _read_smaps_file(self): 

1813 with open_binary("%s/%s/smaps" % (self._procfs_path, self.pid)) as f: 

1814 return f.read().strip() 

1815 

1816 def oneshot_enter(self): 

1817 self._parse_stat_file.cache_activate(self) 

1818 self._read_status_file.cache_activate(self) 

1819 self._read_smaps_file.cache_activate(self) 

1820 

1821 def oneshot_exit(self): 

1822 self._parse_stat_file.cache_deactivate(self) 

1823 self._read_status_file.cache_deactivate(self) 

1824 self._read_smaps_file.cache_deactivate(self) 

1825 

1826 @wrap_exceptions 

1827 def name(self): 

1828 name = self._parse_stat_file()['name'] 

1829 if PY3: 

1830 name = decode(name) 

1831 # XXX - gets changed later and probably needs refactoring 

1832 return name 

1833 

1834 @wrap_exceptions 

1835 def exe(self): 

1836 try: 

1837 return readlink("%s/%s/exe" % (self._procfs_path, self.pid)) 

1838 except (FileNotFoundError, ProcessLookupError): 

1839 self._raise_if_zombie() 

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

1841 # path actually exists for system processes with 

1842 # low pids (about 0-20) 

1843 if os.path.lexists("%s/%s" % (self._procfs_path, self.pid)): 

1844 return "" 

1845 raise 

1846 

1847 @wrap_exceptions 

1848 def cmdline(self): 

1849 with open_text("%s/%s/cmdline" % (self._procfs_path, self.pid)) as f: 

1850 data = f.read() 

1851 if not data: 

1852 # may happen in case of zombie process 

1853 self._raise_if_zombie() 

1854 return [] 

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

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

1857 # some processes may change their cmdline after being started 

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

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

1860 # Chrome process is an example. See: 

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

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

1863 if data.endswith(sep): 

1864 data = data[:-1] 

1865 cmdline = data.split(sep) 

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

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

1868 # issues/1179#issuecomment-552984549 

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

1870 cmdline = data.split(' ') 

1871 return cmdline 

1872 

1873 @wrap_exceptions 

1874 def environ(self): 

1875 with open_text("%s/%s/environ" % (self._procfs_path, self.pid)) as f: 

1876 data = f.read() 

1877 return parse_environ_block(data) 

1878 

1879 @wrap_exceptions 

1880 def terminal(self): 

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

1882 tmap = _psposix.get_terminal_map() 

1883 try: 

1884 return tmap[tty_nr] 

1885 except KeyError: 

1886 return None 

1887 

1888 # May not be available on old kernels. 

1889 if os.path.exists('/proc/%s/io' % os.getpid()): 

1890 

1891 @wrap_exceptions 

1892 def io_counters(self): 

1893 fname = "%s/%s/io" % (self._procfs_path, self.pid) 

1894 fields = {} 

1895 with open_binary(fname) as f: 

1896 for line in f: 

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

1898 line = line.strip() 

1899 if line: 

1900 try: 

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

1902 except ValueError: 

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

1904 continue 

1905 else: 

1906 fields[name] = int(value) 

1907 if not fields: 

1908 raise RuntimeError("%s file was empty" % fname) 

1909 try: 

1910 return pio( 

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

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

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

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

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

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

1917 ) 

1918 except KeyError as err: 

1919 raise ValueError( 

1920 "%r field was not found in %s; found fields are %r" 

1921 % (err.args[0], fname, fields) 

1922 ) 

1923 

1924 @wrap_exceptions 

1925 def cpu_times(self): 

1926 values = self._parse_stat_file() 

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

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

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

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

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

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

1933 

1934 @wrap_exceptions 

1935 def cpu_num(self): 

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

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

1938 

1939 @wrap_exceptions 

1940 def wait(self, timeout=None): 

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

1942 

1943 @wrap_exceptions 

1944 def create_time(self): 

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

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

1947 # unit is jiffies (clock ticks). 

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

1949 # seconds since the epoch. 

1950 # Also use cached value if available. 

1951 bt = BOOT_TIME or boot_time() 

1952 return (ctime / CLOCK_TICKS) + bt 

1953 

1954 @wrap_exceptions 

1955 def memory_info(self): 

1956 # ============================================================ 

1957 # | FIELD | DESCRIPTION | AKA | TOP | 

1958 # ============================================================ 

1959 # | rss | resident set size | | RES | 

1960 # | vms | total program size | size | VIRT | 

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

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

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

1964 # | data | data + stack | drs | DATA | 

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

1966 # ============================================================ 

1967 with open_binary("%s/%s/statm" % (self._procfs_path, self.pid)) as f: 

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

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

1970 ) 

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

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 = 0 

1985 with open_binary( 

1986 "{}/{}/smaps_rollup".format(self._procfs_path, self.pid) 

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"Pss:"): 

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

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

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

1996 return (uss, pss, swap) 

1997 

1998 @wrap_exceptions 

1999 def _parse_smaps( 

2000 self, 

2001 # Gets Private_Clean, Private_Dirty, Private_Hugetlb. 

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

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

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

2005 ): 

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

2007 # CONFIG_MMU kernel configuration option is not enabled. 

2008 

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

2010 # line by line. 

2011 # XXX: on Python 3 the 2 regexes are 30% slower than on 

2012 # Python 2 though. Figure out why. 

2013 # 

2014 # You might be tempted to calculate USS by subtracting 

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

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

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

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

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

2020 # correct information. 

2021 smaps_data = self._read_smaps_file() 

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

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

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

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

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

2027 return (uss, pss, swap) 

2028 

2029 @wrap_exceptions 

2030 def memory_full_info(self): 

2031 if HAS_PROC_SMAPS_ROLLUP: # faster 

2032 try: 

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

2034 except (ProcessLookupError, FileNotFoundError): 

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

2036 else: 

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

2038 basic_mem = self.memory_info() 

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

2040 

2041 else: 

2042 memory_full_info = memory_info 

2043 

2044 if HAS_PROC_SMAPS: 

2045 

2046 @wrap_exceptions 

2047 def memory_maps(self): 

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

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

2050 (Apr 2012) version: http://goo.gl/fmebo. 

2051 

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

2053 CONFIG_MMU kernel configuration option is not enabled. 

2054 """ 

2055 

2056 def get_blocks(lines, current_block): 

2057 data = {} 

2058 for line in lines: 

2059 fields = line.split(None, 5) 

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

2061 # new block section 

2062 yield (current_block.pop(), data) 

2063 current_block.append(line) 

2064 else: 

2065 try: 

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

2067 except ValueError: 

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

2069 # see issue #369 

2070 continue 

2071 else: 

2072 raise ValueError( 

2073 "don't know how to interpret line %r" 

2074 % line 

2075 ) 

2076 yield (current_block.pop(), data) 

2077 

2078 data = self._read_smaps_file() 

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

2080 # zombies. 

2081 if not data: 

2082 self._raise_if_zombie() 

2083 return [] 

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

2085 ls = [] 

2086 first_line = lines.pop(0) 

2087 current_block = [first_line] 

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

2089 hfields = header.split(None, 5) 

2090 try: 

2091 addr, perms, offset, dev, inode, path = hfields 

2092 except ValueError: 

2093 addr, perms, offset, dev, inode, path = hfields + [''] 

2094 if not path: 

2095 path = '[anon]' 

2096 else: 

2097 if PY3: 

2098 path = decode(path) 

2099 path = path.strip() 

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

2101 path 

2102 ): 

2103 path = path[:-10] 

2104 ls.append(( 

2105 decode(addr), 

2106 decode(perms), 

2107 path, 

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

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

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

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

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

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

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

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

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

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

2118 )) 

2119 return ls 

2120 

2121 @wrap_exceptions 

2122 def cwd(self): 

2123 return readlink("%s/%s/cwd" % (self._procfs_path, self.pid)) 

2124 

2125 @wrap_exceptions 

2126 def num_ctx_switches( 

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

2128 ): 

2129 data = self._read_status_file() 

2130 ctxsw = _ctxsw_re.findall(data) 

2131 if not ctxsw: 

2132 raise NotImplementedError( 

2133 "'voluntary_ctxt_switches' and 'nonvoluntary_ctxt_switches'" 

2134 "lines were not found in %s/%s/status; the kernel is " 

2135 "probably older than 2.6.23" % (self._procfs_path, self.pid) 

2136 ) 

2137 else: 

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

2139 

2140 @wrap_exceptions 

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

2142 # Note: on Python 3 using a re is faster than iterating over file 

2143 # line by line. On Python 2 is the exact opposite, and iterating 

2144 # over a file on Python 3 is slower than on Python 2. 

2145 data = self._read_status_file() 

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

2147 

2148 @wrap_exceptions 

2149 def threads(self): 

2150 thread_ids = os.listdir("%s/%s/task" % (self._procfs_path, self.pid)) 

2151 thread_ids.sort() 

2152 retlist = [] 

2153 hit_enoent = False 

2154 for thread_id in thread_ids: 

2155 fname = "%s/%s/task/%s/stat" % ( 

2156 self._procfs_path, 

2157 self.pid, 

2158 thread_id, 

2159 ) 

2160 try: 

2161 with open_binary(fname) as f: 

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

2163 except (FileNotFoundError, ProcessLookupError): 

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

2165 # it means thread disappeared on us 

2166 hit_enoent = True 

2167 continue 

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

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

2170 values = st.split(b' ') 

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

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

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

2174 retlist.append(ntuple) 

2175 if hit_enoent: 

2176 self._raise_if_not_alive() 

2177 return retlist 

2178 

2179 @wrap_exceptions 

2180 def nice_get(self): 

2181 # with open_text('%s/%s/stat' % (self._procfs_path, self.pid)) as f: 

2182 # data = f.read() 

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

2184 

2185 # Use C implementation 

2186 return cext_posix.getpriority(self.pid) 

2187 

2188 @wrap_exceptions 

2189 def nice_set(self, value): 

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

2191 

2192 # starting from CentOS 6. 

2193 if HAS_CPU_AFFINITY: 

2194 

2195 @wrap_exceptions 

2196 def cpu_affinity_get(self): 

2197 return cext.proc_cpu_affinity_get(self.pid) 

2198 

2199 def _get_eligible_cpus( 

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

2201 ): 

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

2203 data = self._read_status_file() 

2204 match = _re.findall(data) 

2205 if match: 

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

2207 else: 

2208 return list(range(len(per_cpu_times()))) 

2209 

2210 @wrap_exceptions 

2211 def cpu_affinity_set(self, cpus): 

2212 try: 

2213 cext.proc_cpu_affinity_set(self.pid, cpus) 

2214 except (OSError, ValueError) as err: 

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

2216 eligible_cpus = self._get_eligible_cpus() 

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

2218 for cpu in cpus: 

2219 if cpu not in all_cpus: 

2220 raise ValueError( 

2221 "invalid CPU number %r; choose between %s" 

2222 % (cpu, eligible_cpus) 

2223 ) 

2224 if cpu not in eligible_cpus: 

2225 raise ValueError( 

2226 "CPU number %r is not eligible; choose " 

2227 "between %s" % (cpu, eligible_cpus) 

2228 ) 

2229 raise 

2230 

2231 # only starting from kernel 2.6.13 

2232 if HAS_PROC_IO_PRIORITY: 

2233 

2234 @wrap_exceptions 

2235 def ionice_get(self): 

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

2237 if enum is not None: 

2238 ioclass = IOPriority(ioclass) 

2239 return _common.pionice(ioclass, value) 

2240 

2241 @wrap_exceptions 

2242 def ionice_set(self, ioclass, value): 

2243 if value is None: 

2244 value = 0 

2245 if value and ioclass in (IOPRIO_CLASS_IDLE, IOPRIO_CLASS_NONE): 

2246 raise ValueError("%r ioclass accepts no value" % ioclass) 

2247 if value < 0 or value > 7: 

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

2249 raise ValueError(msg) 

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

2251 

2252 if prlimit is not None: 

2253 

2254 @wrap_exceptions 

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

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

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

2258 # PID 0 is not supported on Linux. 

2259 if self.pid == 0: 

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

2261 raise ValueError(msg) 

2262 try: 

2263 if limits is None: 

2264 # get 

2265 return prlimit(self.pid, resource_) 

2266 else: 

2267 # set 

2268 if len(limits) != 2: 

2269 msg = ( 

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

2271 + "tuple, got %s" % repr(limits) 

2272 ) 

2273 raise ValueError(msg) 

2274 prlimit(self.pid, resource_, limits) 

2275 except OSError as err: 

2276 if err.errno == errno.ENOSYS: 

2277 # I saw this happening on Travis: 

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

2279 self._raise_if_zombie() 

2280 raise 

2281 

2282 @wrap_exceptions 

2283 def status(self): 

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

2285 if PY3: 

2286 letter = letter.decode() 

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

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

2289 

2290 @wrap_exceptions 

2291 def open_files(self): 

2292 retlist = [] 

2293 files = os.listdir("%s/%s/fd" % (self._procfs_path, self.pid)) 

2294 hit_enoent = False 

2295 for fd in files: 

2296 file = "%s/%s/fd/%s" % (self._procfs_path, self.pid, fd) 

2297 try: 

2298 path = readlink(file) 

2299 except (FileNotFoundError, ProcessLookupError): 

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

2301 hit_enoent = True 

2302 continue 

2303 except OSError as err: 

2304 if err.errno == errno.EINVAL: 

2305 # not a link 

2306 continue 

2307 if err.errno == errno.ENAMETOOLONG: 

2308 # file name too long 

2309 debug(err) 

2310 continue 

2311 raise 

2312 else: 

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

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

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

2316 # absolute path though. 

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

2318 # Get file position and flags. 

2319 file = "%s/%s/fdinfo/%s" % ( 

2320 self._procfs_path, 

2321 self.pid, 

2322 fd, 

2323 ) 

2324 try: 

2325 with open_binary(file) as f: 

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

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

2328 except (FileNotFoundError, ProcessLookupError): 

2329 # fd gone in the meantime; process may 

2330 # still be alive 

2331 hit_enoent = True 

2332 else: 

2333 mode = file_flags_to_mode(flags) 

2334 ntuple = popenfile( 

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

2336 ) 

2337 retlist.append(ntuple) 

2338 if hit_enoent: 

2339 self._raise_if_not_alive() 

2340 return retlist 

2341 

2342 @wrap_exceptions 

2343 def connections(self, kind='inet'): 

2344 ret = _connections.retrieve(kind, self.pid) 

2345 self._raise_if_not_alive() 

2346 return ret 

2347 

2348 @wrap_exceptions 

2349 def num_fds(self): 

2350 return len(os.listdir("%s/%s/fd" % (self._procfs_path, self.pid))) 

2351 

2352 @wrap_exceptions 

2353 def ppid(self): 

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

2355 

2356 @wrap_exceptions 

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

2358 data = self._read_status_file() 

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

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

2361 

2362 @wrap_exceptions 

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

2364 data = self._read_status_file() 

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

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