Mercurial > repos > shellac > sam_consensus_v3
comparison env/lib/python3.9/site-packages/psutil/_pslinux.py @ 0:4f3585e2f14b draft default tip
"planemo upload commit 60cee0fc7c0cda8592644e1aad72851dec82c959"
| author | shellac |
|---|---|
| date | Mon, 22 Mar 2021 18:12:50 +0000 |
| parents | |
| children |
comparison
equal
deleted
inserted
replaced
| -1:000000000000 | 0:4f3585e2f14b |
|---|---|
| 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 from __future__ import division | |
| 8 | |
| 9 import base64 | |
| 10 import collections | |
| 11 import errno | |
| 12 import functools | |
| 13 import glob | |
| 14 import os | |
| 15 import re | |
| 16 import socket | |
| 17 import struct | |
| 18 import sys | |
| 19 import traceback | |
| 20 import warnings | |
| 21 from collections import defaultdict | |
| 22 from collections import namedtuple | |
| 23 | |
| 24 from . import _common | |
| 25 from . import _psposix | |
| 26 from . import _psutil_linux as cext | |
| 27 from . import _psutil_posix as cext_posix | |
| 28 from ._common import AccessDenied | |
| 29 from ._common import debug | |
| 30 from ._common import decode | |
| 31 from ._common import get_procfs_path | |
| 32 from ._common import isfile_strict | |
| 33 from ._common import memoize | |
| 34 from ._common import memoize_when_activated | |
| 35 from ._common import NIC_DUPLEX_FULL | |
| 36 from ._common import NIC_DUPLEX_HALF | |
| 37 from ._common import NIC_DUPLEX_UNKNOWN | |
| 38 from ._common import NoSuchProcess | |
| 39 from ._common import open_binary | |
| 40 from ._common import open_text | |
| 41 from ._common import parse_environ_block | |
| 42 from ._common import path_exists_strict | |
| 43 from ._common import supports_ipv6 | |
| 44 from ._common import usage_percent | |
| 45 from ._common import ZombieProcess | |
| 46 from ._compat import b | |
| 47 from ._compat import basestring | |
| 48 from ._compat import FileNotFoundError | |
| 49 from ._compat import PermissionError | |
| 50 from ._compat import ProcessLookupError | |
| 51 from ._compat import PY3 | |
| 52 | |
| 53 if sys.version_info >= (3, 4): | |
| 54 import enum | |
| 55 else: | |
| 56 enum = None | |
| 57 | |
| 58 | |
| 59 __extra__all__ = [ | |
| 60 # | |
| 61 'PROCFS_PATH', | |
| 62 # io prio constants | |
| 63 "IOPRIO_CLASS_NONE", "IOPRIO_CLASS_RT", "IOPRIO_CLASS_BE", | |
| 64 "IOPRIO_CLASS_IDLE", | |
| 65 # connection status constants | |
| 66 "CONN_ESTABLISHED", "CONN_SYN_SENT", "CONN_SYN_RECV", "CONN_FIN_WAIT1", | |
| 67 "CONN_FIN_WAIT2", "CONN_TIME_WAIT", "CONN_CLOSE", "CONN_CLOSE_WAIT", | |
| 68 "CONN_LAST_ACK", "CONN_LISTEN", "CONN_CLOSING", ] | |
| 69 | |
| 70 | |
| 71 # ===================================================================== | |
| 72 # --- globals | |
| 73 # ===================================================================== | |
| 74 | |
| 75 | |
| 76 POWER_SUPPLY_PATH = "/sys/class/power_supply" | |
| 77 HAS_SMAPS = os.path.exists('/proc/%s/smaps' % os.getpid()) | |
| 78 HAS_PROC_IO_PRIORITY = hasattr(cext, "proc_ioprio_get") | |
| 79 HAS_CPU_AFFINITY = hasattr(cext, "proc_cpu_affinity_get") | |
| 80 _DEFAULT = object() | |
| 81 | |
| 82 # Number of clock ticks per second | |
| 83 CLOCK_TICKS = os.sysconf("SC_CLK_TCK") | |
| 84 PAGESIZE = cext_posix.getpagesize() | |
| 85 BOOT_TIME = None # set later | |
| 86 # Used when reading "big" files, namely /proc/{pid}/smaps and /proc/net/*. | |
| 87 # On Python 2, using a buffer with open() for such files may result in a | |
| 88 # speedup, see: https://github.com/giampaolo/psutil/issues/708 | |
| 89 BIGFILE_BUFFERING = -1 if PY3 else 8192 | |
| 90 LITTLE_ENDIAN = sys.byteorder == 'little' | |
| 91 | |
| 92 # "man iostat" states that sectors are equivalent with blocks and have | |
| 93 # a size of 512 bytes. Despite this value can be queried at runtime | |
| 94 # via /sys/block/{DISK}/queue/hw_sector_size and results may vary | |
| 95 # between 1k, 2k, or 4k... 512 appears to be a magic constant used | |
| 96 # throughout Linux source code: | |
| 97 # * https://stackoverflow.com/a/38136179/376587 | |
| 98 # * https://lists.gt.net/linux/kernel/2241060 | |
| 99 # * https://github.com/giampaolo/psutil/issues/1305 | |
| 100 # * https://github.com/torvalds/linux/blob/ | |
| 101 # 4f671fe2f9523a1ea206f63fe60a7c7b3a56d5c7/include/linux/bio.h#L99 | |
| 102 # * https://lkml.org/lkml/2015/8/17/234 | |
| 103 DISK_SECTOR_SIZE = 512 | |
| 104 | |
| 105 if enum is None: | |
| 106 AF_LINK = socket.AF_PACKET | |
| 107 else: | |
| 108 AddressFamily = enum.IntEnum('AddressFamily', | |
| 109 {'AF_LINK': int(socket.AF_PACKET)}) | |
| 110 AF_LINK = AddressFamily.AF_LINK | |
| 111 | |
| 112 # ioprio_* constants http://linux.die.net/man/2/ioprio_get | |
| 113 if enum is None: | |
| 114 IOPRIO_CLASS_NONE = 0 | |
| 115 IOPRIO_CLASS_RT = 1 | |
| 116 IOPRIO_CLASS_BE = 2 | |
| 117 IOPRIO_CLASS_IDLE = 3 | |
| 118 else: | |
| 119 class IOPriority(enum.IntEnum): | |
| 120 IOPRIO_CLASS_NONE = 0 | |
| 121 IOPRIO_CLASS_RT = 1 | |
| 122 IOPRIO_CLASS_BE = 2 | |
| 123 IOPRIO_CLASS_IDLE = 3 | |
| 124 | |
| 125 globals().update(IOPriority.__members__) | |
| 126 | |
| 127 # See: | |
| 128 # https://github.com/torvalds/linux/blame/master/fs/proc/array.c | |
| 129 # ...and (TASK_* constants): | |
| 130 # https://github.com/torvalds/linux/blob/master/include/linux/sched.h | |
| 131 PROC_STATUSES = { | |
| 132 "R": _common.STATUS_RUNNING, | |
| 133 "S": _common.STATUS_SLEEPING, | |
| 134 "D": _common.STATUS_DISK_SLEEP, | |
| 135 "T": _common.STATUS_STOPPED, | |
| 136 "t": _common.STATUS_TRACING_STOP, | |
| 137 "Z": _common.STATUS_ZOMBIE, | |
| 138 "X": _common.STATUS_DEAD, | |
| 139 "x": _common.STATUS_DEAD, | |
| 140 "K": _common.STATUS_WAKE_KILL, | |
| 141 "W": _common.STATUS_WAKING, | |
| 142 "I": _common.STATUS_IDLE, | |
| 143 "P": _common.STATUS_PARKED, | |
| 144 } | |
| 145 | |
| 146 # https://github.com/torvalds/linux/blob/master/include/net/tcp_states.h | |
| 147 TCP_STATUSES = { | |
| 148 "01": _common.CONN_ESTABLISHED, | |
| 149 "02": _common.CONN_SYN_SENT, | |
| 150 "03": _common.CONN_SYN_RECV, | |
| 151 "04": _common.CONN_FIN_WAIT1, | |
| 152 "05": _common.CONN_FIN_WAIT2, | |
| 153 "06": _common.CONN_TIME_WAIT, | |
| 154 "07": _common.CONN_CLOSE, | |
| 155 "08": _common.CONN_CLOSE_WAIT, | |
| 156 "09": _common.CONN_LAST_ACK, | |
| 157 "0A": _common.CONN_LISTEN, | |
| 158 "0B": _common.CONN_CLOSING | |
| 159 } | |
| 160 | |
| 161 | |
| 162 # ===================================================================== | |
| 163 # --- named tuples | |
| 164 # ===================================================================== | |
| 165 | |
| 166 | |
| 167 # psutil.virtual_memory() | |
| 168 svmem = namedtuple( | |
| 169 'svmem', ['total', 'available', 'percent', 'used', 'free', | |
| 170 'active', 'inactive', 'buffers', 'cached', 'shared', 'slab']) | |
| 171 # psutil.disk_io_counters() | |
| 172 sdiskio = namedtuple( | |
| 173 'sdiskio', ['read_count', 'write_count', | |
| 174 'read_bytes', 'write_bytes', | |
| 175 'read_time', 'write_time', | |
| 176 'read_merged_count', 'write_merged_count', | |
| 177 'busy_time']) | |
| 178 # psutil.Process().open_files() | |
| 179 popenfile = namedtuple( | |
| 180 'popenfile', ['path', 'fd', 'position', 'mode', 'flags']) | |
| 181 # psutil.Process().memory_info() | |
| 182 pmem = namedtuple('pmem', 'rss vms shared text lib data dirty') | |
| 183 # psutil.Process().memory_full_info() | |
| 184 pfullmem = namedtuple('pfullmem', pmem._fields + ('uss', 'pss', 'swap')) | |
| 185 # psutil.Process().memory_maps(grouped=True) | |
| 186 pmmap_grouped = namedtuple( | |
| 187 'pmmap_grouped', | |
| 188 ['path', 'rss', 'size', 'pss', 'shared_clean', 'shared_dirty', | |
| 189 'private_clean', 'private_dirty', 'referenced', 'anonymous', 'swap']) | |
| 190 # psutil.Process().memory_maps(grouped=False) | |
| 191 pmmap_ext = namedtuple( | |
| 192 'pmmap_ext', 'addr perms ' + ' '.join(pmmap_grouped._fields)) | |
| 193 # psutil.Process.io_counters() | |
| 194 pio = namedtuple('pio', ['read_count', 'write_count', | |
| 195 'read_bytes', 'write_bytes', | |
| 196 'read_chars', 'write_chars']) | |
| 197 # psutil.Process.cpu_times() | |
| 198 pcputimes = namedtuple('pcputimes', | |
| 199 ['user', 'system', 'children_user', 'children_system', | |
| 200 'iowait']) | |
| 201 | |
| 202 | |
| 203 # ===================================================================== | |
| 204 # --- utils | |
| 205 # ===================================================================== | |
| 206 | |
| 207 | |
| 208 def readlink(path): | |
| 209 """Wrapper around os.readlink().""" | |
| 210 assert isinstance(path, basestring), path | |
| 211 path = os.readlink(path) | |
| 212 # readlink() might return paths containing null bytes ('\x00') | |
| 213 # resulting in "TypeError: must be encoded string without NULL | |
| 214 # bytes, not str" errors when the string is passed to other | |
| 215 # fs-related functions (os.*, open(), ...). | |
| 216 # Apparently everything after '\x00' is garbage (we can have | |
| 217 # ' (deleted)', 'new' and possibly others), see: | |
| 218 # https://github.com/giampaolo/psutil/issues/717 | |
| 219 path = path.split('\x00')[0] | |
| 220 # Certain paths have ' (deleted)' appended. Usually this is | |
| 221 # bogus as the file actually exists. Even if it doesn't we | |
| 222 # don't care. | |
| 223 if path.endswith(' (deleted)') and not path_exists_strict(path): | |
| 224 path = path[:-10] | |
| 225 return path | |
| 226 | |
| 227 | |
| 228 def file_flags_to_mode(flags): | |
| 229 """Convert file's open() flags into a readable string. | |
| 230 Used by Process.open_files(). | |
| 231 """ | |
| 232 modes_map = {os.O_RDONLY: 'r', os.O_WRONLY: 'w', os.O_RDWR: 'w+'} | |
| 233 mode = modes_map[flags & (os.O_RDONLY | os.O_WRONLY | os.O_RDWR)] | |
| 234 if flags & os.O_APPEND: | |
| 235 mode = mode.replace('w', 'a', 1) | |
| 236 mode = mode.replace('w+', 'r+') | |
| 237 # possible values: r, w, a, r+, a+ | |
| 238 return mode | |
| 239 | |
| 240 | |
| 241 def is_storage_device(name): | |
| 242 """Return True if the given name refers to a root device (e.g. | |
| 243 "sda", "nvme0n1") as opposed to a logical partition (e.g. "sda1", | |
| 244 "nvme0n1p1"). If name is a virtual device (e.g. "loop1", "ram") | |
| 245 return True. | |
| 246 """ | |
| 247 # Readapted from iostat source code, see: | |
| 248 # https://github.com/sysstat/sysstat/blob/ | |
| 249 # 97912938cd476645b267280069e83b1c8dc0e1c7/common.c#L208 | |
| 250 # Some devices may have a slash in their name (e.g. cciss/c0d0...). | |
| 251 name = name.replace('/', '!') | |
| 252 including_virtual = True | |
| 253 if including_virtual: | |
| 254 path = "/sys/block/%s" % name | |
| 255 else: | |
| 256 path = "/sys/block/%s/device" % name | |
| 257 return os.access(path, os.F_OK) | |
| 258 | |
| 259 | |
| 260 @memoize | |
| 261 def set_scputimes_ntuple(procfs_path): | |
| 262 """Set a namedtuple of variable fields depending on the CPU times | |
| 263 available on this Linux kernel version which may be: | |
| 264 (user, nice, system, idle, iowait, irq, softirq, [steal, [guest, | |
| 265 [guest_nice]]]) | |
| 266 Used by cpu_times() function. | |
| 267 """ | |
| 268 global scputimes | |
| 269 with open_binary('%s/stat' % procfs_path) as f: | |
| 270 values = f.readline().split()[1:] | |
| 271 fields = ['user', 'nice', 'system', 'idle', 'iowait', 'irq', 'softirq'] | |
| 272 vlen = len(values) | |
| 273 if vlen >= 8: | |
| 274 # Linux >= 2.6.11 | |
| 275 fields.append('steal') | |
| 276 if vlen >= 9: | |
| 277 # Linux >= 2.6.24 | |
| 278 fields.append('guest') | |
| 279 if vlen >= 10: | |
| 280 # Linux >= 3.2.0 | |
| 281 fields.append('guest_nice') | |
| 282 scputimes = namedtuple('scputimes', fields) | |
| 283 | |
| 284 | |
| 285 def cat(fname, fallback=_DEFAULT, binary=True): | |
| 286 """Return file content. | |
| 287 fallback: the value returned in case the file does not exist or | |
| 288 cannot be read | |
| 289 binary: whether to open the file in binary or text mode. | |
| 290 """ | |
| 291 try: | |
| 292 with open_binary(fname) if binary else open_text(fname) as f: | |
| 293 return f.read().strip() | |
| 294 except (IOError, OSError): | |
| 295 if fallback is not _DEFAULT: | |
| 296 return fallback | |
| 297 else: | |
| 298 raise | |
| 299 | |
| 300 | |
| 301 try: | |
| 302 set_scputimes_ntuple("/proc") | |
| 303 except Exception: # pragma: no cover | |
| 304 # Don't want to crash at import time. | |
| 305 traceback.print_exc() | |
| 306 scputimes = namedtuple('scputimes', 'user system idle')(0.0, 0.0, 0.0) | |
| 307 | |
| 308 | |
| 309 # ===================================================================== | |
| 310 # --- prlimit | |
| 311 # ===================================================================== | |
| 312 | |
| 313 # Backport of resource.prlimit() for Python 2. Originally this was done | |
| 314 # in C, but CentOS-6 which we use to create manylinux wheels is too old | |
| 315 # and does not support prlimit() syscall. As such the resulting wheel | |
| 316 # would not include prlimit(), even when installed on newer systems. | |
| 317 # This is the only part of psutil using ctypes. | |
| 318 | |
| 319 prlimit = None | |
| 320 try: | |
| 321 from resource import prlimit # python >= 3.4 | |
| 322 except ImportError: | |
| 323 import ctypes | |
| 324 | |
| 325 libc = ctypes.CDLL(None, use_errno=True) | |
| 326 | |
| 327 if hasattr(libc, "prlimit"): | |
| 328 | |
| 329 def prlimit(pid, resource_, limits=None): | |
| 330 class StructRlimit(ctypes.Structure): | |
| 331 _fields_ = [('rlim_cur', ctypes.c_longlong), | |
| 332 ('rlim_max', ctypes.c_longlong)] | |
| 333 | |
| 334 current = StructRlimit() | |
| 335 if limits is None: | |
| 336 # get | |
| 337 ret = libc.prlimit(pid, resource_, None, ctypes.byref(current)) | |
| 338 else: | |
| 339 # set | |
| 340 new = StructRlimit() | |
| 341 new.rlim_cur = limits[0] | |
| 342 new.rlim_max = limits[1] | |
| 343 ret = libc.prlimit( | |
| 344 pid, resource_, ctypes.byref(new), ctypes.byref(current)) | |
| 345 | |
| 346 if ret != 0: | |
| 347 errno = ctypes.get_errno() | |
| 348 raise OSError(errno, os.strerror(errno)) | |
| 349 return (current.rlim_cur, current.rlim_max) | |
| 350 | |
| 351 | |
| 352 if prlimit is not None: | |
| 353 __extra__all__.extend( | |
| 354 [x for x in dir(cext) if x.startswith('RLIM') and x.isupper()]) | |
| 355 | |
| 356 | |
| 357 # ===================================================================== | |
| 358 # --- system memory | |
| 359 # ===================================================================== | |
| 360 | |
| 361 | |
| 362 def calculate_avail_vmem(mems): | |
| 363 """Fallback for kernels < 3.14 where /proc/meminfo does not provide | |
| 364 "MemAvailable:" column, see: | |
| 365 https://blog.famzah.net/2014/09/24/ | |
| 366 This code reimplements the algorithm outlined here: | |
| 367 https://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/ | |
| 368 commit/?id=34e431b0ae398fc54ea69ff85ec700722c9da773 | |
| 369 | |
| 370 XXX: on recent kernels this calculation differs by ~1.5% than | |
| 371 "MemAvailable:" as it's calculated slightly differently, see: | |
| 372 https://gitlab.com/procps-ng/procps/issues/42 | |
| 373 https://github.com/famzah/linux-memavailable-procfs/issues/2 | |
| 374 It is still way more realistic than doing (free + cached) though. | |
| 375 """ | |
| 376 # Fallback for very old distros. According to | |
| 377 # https://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/ | |
| 378 # commit/?id=34e431b0ae398fc54ea69ff85ec700722c9da773 | |
| 379 # ...long ago "avail" was calculated as (free + cached). | |
| 380 # We might fallback in such cases: | |
| 381 # "Active(file)" not available: 2.6.28 / Dec 2008 | |
| 382 # "Inactive(file)" not available: 2.6.28 / Dec 2008 | |
| 383 # "SReclaimable:" not available: 2.6.19 / Nov 2006 | |
| 384 # /proc/zoneinfo not available: 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: | |
| 392 return fallback | |
| 393 try: | |
| 394 f = open_binary('%s/zoneinfo' % get_procfs_path()) | |
| 395 except IOError: | |
| 396 return fallback # kernel 2.6.13 | |
| 397 | |
| 398 watermark_low = 0 | |
| 399 with f: | |
| 400 for line in f: | |
| 401 line = line.strip() | |
| 402 if line.startswith(b'low'): | |
| 403 watermark_low += int(line.split()[1]) | |
| 404 watermark_low *= PAGESIZE | |
| 405 | |
| 406 avail = free - watermark_low | |
| 407 pagecache = lru_active_file + lru_inactive_file | |
| 408 pagecache -= min(pagecache / 2, watermark_low) | |
| 409 avail += pagecache | |
| 410 avail += slab_reclaimable - min(slab_reclaimable / 2.0, watermark_low) | |
| 411 return int(avail) | |
| 412 | |
| 413 | |
| 414 def virtual_memory(): | |
| 415 """Report virtual memory stats. | |
| 416 This implementation matches "free" and "vmstat -s" cmdline | |
| 417 utility values and procps-ng-3.3.12 source was used as a reference | |
| 418 (2016-09-18): | |
| 419 https://gitlab.com/procps-ng/procps/blob/ | |
| 420 24fd2605c51fccc375ab0287cec33aa767f06718/proc/sysinfo.c | |
| 421 For reference, procps-ng-3.3.10 is the version available on Ubuntu | |
| 422 16.04. | |
| 423 | |
| 424 Note about "available" memory: up until psutil 4.3 it was | |
| 425 calculated as "avail = (free + buffers + cached)". Now | |
| 426 "MemAvailable:" column (kernel 3.14) from /proc/meminfo is used as | |
| 427 it's more accurate. | |
| 428 That matches "available" column in newer versions of "free". | |
| 429 """ | |
| 430 missing_fields = [] | |
| 431 mems = {} | |
| 432 with open_binary('%s/meminfo' % get_procfs_path()) as f: | |
| 433 for line in f: | |
| 434 fields = line.split() | |
| 435 mems[fields[0]] = int(fields[1]) * 1024 | |
| 436 | |
| 437 # /proc doc states that the available fields in /proc/meminfo vary | |
| 438 # by architecture and compile options, but these 3 values are also | |
| 439 # returned by sysinfo(2); as such we assume they are always there. | |
| 440 total = mems[b'MemTotal:'] | |
| 441 free = mems[b'MemFree:'] | |
| 442 try: | |
| 443 buffers = mems[b'Buffers:'] | |
| 444 except KeyError: | |
| 445 # https://github.com/giampaolo/psutil/issues/1010 | |
| 446 buffers = 0 | |
| 447 missing_fields.append('buffers') | |
| 448 try: | |
| 449 cached = mems[b"Cached:"] | |
| 450 except KeyError: | |
| 451 cached = 0 | |
| 452 missing_fields.append('cached') | |
| 453 else: | |
| 454 # "free" cmdline utility sums reclaimable to cached. | |
| 455 # Older versions of procps used to add slab memory instead. | |
| 456 # This got changed in: | |
| 457 # https://gitlab.com/procps-ng/procps/commit/ | |
| 458 # 05d751c4f076a2f0118b914c5e51cfbb4762ad8e | |
| 459 cached += mems.get(b"SReclaimable:", 0) # since kernel 2.6.19 | |
| 460 | |
| 461 try: | |
| 462 shared = mems[b'Shmem:'] # since kernel 2.6.32 | |
| 463 except KeyError: | |
| 464 try: | |
| 465 shared = mems[b'MemShared:'] # kernels 2.4 | |
| 466 except KeyError: | |
| 467 shared = 0 | |
| 468 missing_fields.append('shared') | |
| 469 | |
| 470 try: | |
| 471 active = mems[b"Active:"] | |
| 472 except KeyError: | |
| 473 active = 0 | |
| 474 missing_fields.append('active') | |
| 475 | |
| 476 try: | |
| 477 inactive = mems[b"Inactive:"] | |
| 478 except KeyError: | |
| 479 try: | |
| 480 inactive = \ | |
| 481 mems[b"Inact_dirty:"] + \ | |
| 482 mems[b"Inact_clean:"] + \ | |
| 483 mems[b"Inact_laundry:"] | |
| 484 except KeyError: | |
| 485 inactive = 0 | |
| 486 missing_fields.append('inactive') | |
| 487 | |
| 488 try: | |
| 489 slab = mems[b"Slab:"] | |
| 490 except KeyError: | |
| 491 slab = 0 | |
| 492 | |
| 493 used = total - free - cached - buffers | |
| 494 if used < 0: | |
| 495 # May be symptomatic of running within a LCX container where such | |
| 496 # values will be dramatically distorted over those of the host. | |
| 497 used = total - free | |
| 498 | |
| 499 # - starting from 4.4.0 we match free's "available" column. | |
| 500 # Before 4.4.0 we calculated it as (free + buffers + cached) | |
| 501 # which matched htop. | |
| 502 # - free and htop available memory differs as per: | |
| 503 # http://askubuntu.com/a/369589 | |
| 504 # http://unix.stackexchange.com/a/65852/168884 | |
| 505 # - MemAvailable has been introduced in kernel 3.14 | |
| 506 try: | |
| 507 avail = mems[b'MemAvailable:'] | |
| 508 except KeyError: | |
| 509 avail = calculate_avail_vmem(mems) | |
| 510 | |
| 511 if avail < 0: | |
| 512 avail = 0 | |
| 513 missing_fields.append('available') | |
| 514 | |
| 515 # If avail is greater than total or our calculation overflows, | |
| 516 # that's symptomatic of running within a LCX container where such | |
| 517 # values will be dramatically distorted over those of the host. | |
| 518 # https://gitlab.com/procps-ng/procps/blob/ | |
| 519 # 24fd2605c51fccc375ab0287cec33aa767f06718/proc/sysinfo.c#L764 | |
| 520 if avail > total: | |
| 521 avail = free | |
| 522 | |
| 523 percent = usage_percent((total - avail), total, round_=1) | |
| 524 | |
| 525 # Warn about missing metrics which are set to 0. | |
| 526 if missing_fields: | |
| 527 msg = "%s memory stats couldn't be determined and %s set to 0" % ( | |
| 528 ", ".join(missing_fields), | |
| 529 "was" if len(missing_fields) == 1 else "were") | |
| 530 warnings.warn(msg, RuntimeWarning) | |
| 531 | |
| 532 return svmem(total, avail, percent, used, free, | |
| 533 active, inactive, buffers, cached, shared, slab) | |
| 534 | |
| 535 | |
| 536 def swap_memory(): | |
| 537 """Return swap memory metrics.""" | |
| 538 mems = {} | |
| 539 with open_binary('%s/meminfo' % get_procfs_path()) as f: | |
| 540 for line in f: | |
| 541 fields = line.split() | |
| 542 mems[fields[0]] = int(fields[1]) * 1024 | |
| 543 # We prefer /proc/meminfo over sysinfo() syscall so that | |
| 544 # psutil.PROCFS_PATH can be used in order to allow retrieval | |
| 545 # for linux containers, see: | |
| 546 # https://github.com/giampaolo/psutil/issues/1015 | |
| 547 try: | |
| 548 total = mems[b'SwapTotal:'] | |
| 549 free = mems[b'SwapFree:'] | |
| 550 except KeyError: | |
| 551 _, _, _, _, total, free, unit_multiplier = cext.linux_sysinfo() | |
| 552 total *= unit_multiplier | |
| 553 free *= unit_multiplier | |
| 554 | |
| 555 used = total - free | |
| 556 percent = usage_percent(used, total, round_=1) | |
| 557 # get pgin/pgouts | |
| 558 try: | |
| 559 f = open_binary("%s/vmstat" % get_procfs_path()) | |
| 560 except IOError as err: | |
| 561 # see https://github.com/giampaolo/psutil/issues/722 | |
| 562 msg = "'sin' and 'sout' swap memory stats couldn't " \ | |
| 563 "be determined and were set to 0 (%s)" % str(err) | |
| 564 warnings.warn(msg, RuntimeWarning) | |
| 565 sin = sout = 0 | |
| 566 else: | |
| 567 with f: | |
| 568 sin = sout = None | |
| 569 for line in f: | |
| 570 # values are expressed in 4 kilo bytes, we want | |
| 571 # bytes instead | |
| 572 if line.startswith(b'pswpin'): | |
| 573 sin = int(line.split(b' ')[1]) * 4 * 1024 | |
| 574 elif line.startswith(b'pswpout'): | |
| 575 sout = int(line.split(b' ')[1]) * 4 * 1024 | |
| 576 if sin is not None and sout is not None: | |
| 577 break | |
| 578 else: | |
| 579 # we might get here when dealing with exotic Linux | |
| 580 # flavors, see: | |
| 581 # https://github.com/giampaolo/psutil/issues/313 | |
| 582 msg = "'sin' and 'sout' swap memory stats couldn't " \ | |
| 583 "be determined and were set to 0" | |
| 584 warnings.warn(msg, RuntimeWarning) | |
| 585 sin = sout = 0 | |
| 586 return _common.sswap(total, used, free, percent, sin, sout) | |
| 587 | |
| 588 | |
| 589 # ===================================================================== | |
| 590 # --- CPU | |
| 591 # ===================================================================== | |
| 592 | |
| 593 | |
| 594 def cpu_times(): | |
| 595 """Return a named tuple representing the following system-wide | |
| 596 CPU times: | |
| 597 (user, nice, system, idle, iowait, irq, softirq [steal, [guest, | |
| 598 [guest_nice]]]) | |
| 599 Last 3 fields may not be available on all Linux kernel versions. | |
| 600 """ | |
| 601 procfs_path = get_procfs_path() | |
| 602 set_scputimes_ntuple(procfs_path) | |
| 603 with open_binary('%s/stat' % procfs_path) as f: | |
| 604 values = f.readline().split() | |
| 605 fields = values[1:len(scputimes._fields) + 1] | |
| 606 fields = [float(x) / CLOCK_TICKS for x in fields] | |
| 607 return scputimes(*fields) | |
| 608 | |
| 609 | |
| 610 def per_cpu_times(): | |
| 611 """Return a list of namedtuple representing the CPU times | |
| 612 for every CPU available on the system. | |
| 613 """ | |
| 614 procfs_path = get_procfs_path() | |
| 615 set_scputimes_ntuple(procfs_path) | |
| 616 cpus = [] | |
| 617 with open_binary('%s/stat' % procfs_path) as f: | |
| 618 # get rid of the first line which refers to system wide CPU stats | |
| 619 f.readline() | |
| 620 for line in f: | |
| 621 if line.startswith(b'cpu'): | |
| 622 values = line.split() | |
| 623 fields = values[1:len(scputimes._fields) + 1] | |
| 624 fields = [float(x) / CLOCK_TICKS for x in fields] | |
| 625 entry = scputimes(*fields) | |
| 626 cpus.append(entry) | |
| 627 return cpus | |
| 628 | |
| 629 | |
| 630 def cpu_count_logical(): | |
| 631 """Return the number of logical CPUs in the system.""" | |
| 632 try: | |
| 633 return os.sysconf("SC_NPROCESSORS_ONLN") | |
| 634 except ValueError: | |
| 635 # as a second fallback we try to parse /proc/cpuinfo | |
| 636 num = 0 | |
| 637 with open_binary('%s/cpuinfo' % get_procfs_path()) as f: | |
| 638 for line in f: | |
| 639 if line.lower().startswith(b'processor'): | |
| 640 num += 1 | |
| 641 | |
| 642 # unknown format (e.g. amrel/sparc architectures), see: | |
| 643 # https://github.com/giampaolo/psutil/issues/200 | |
| 644 # try to parse /proc/stat as a last resort | |
| 645 if num == 0: | |
| 646 search = re.compile(r'cpu\d') | |
| 647 with open_text('%s/stat' % get_procfs_path()) as f: | |
| 648 for line in f: | |
| 649 line = line.split(' ')[0] | |
| 650 if search.match(line): | |
| 651 num += 1 | |
| 652 | |
| 653 if num == 0: | |
| 654 # mimic os.cpu_count() | |
| 655 return None | |
| 656 return num | |
| 657 | |
| 658 | |
| 659 def cpu_count_physical(): | |
| 660 """Return the number of physical cores in the system.""" | |
| 661 # Method #1 | |
| 662 ls = set() | |
| 663 # These 2 files are the same but */core_cpus_list is newer while | |
| 664 # */thread_siblings_list is deprecated and may disappear in the future. | |
| 665 # https://www.kernel.org/doc/Documentation/admin-guide/cputopology.rst | |
| 666 # https://github.com/giampaolo/psutil/pull/1727#issuecomment-707624964 | |
| 667 # https://lkml.org/lkml/2019/2/26/41 | |
| 668 p1 = "/sys/devices/system/cpu/cpu[0-9]*/topology/core_cpus_list" | |
| 669 p2 = "/sys/devices/system/cpu/cpu[0-9]*/topology/thread_siblings_list" | |
| 670 for path in glob.glob(p1) or glob.glob(p2): | |
| 671 with open_binary(path) as f: | |
| 672 ls.add(f.read().strip()) | |
| 673 result = len(ls) | |
| 674 if result != 0: | |
| 675 return result | |
| 676 | |
| 677 # Method #2 | |
| 678 mapping = {} | |
| 679 current_info = {} | |
| 680 with open_binary('%s/cpuinfo' % get_procfs_path()) as f: | |
| 681 for line in f: | |
| 682 line = line.strip().lower() | |
| 683 if not line: | |
| 684 # new section | |
| 685 try: | |
| 686 mapping[current_info[b'physical id']] = \ | |
| 687 current_info[b'cpu cores'] | |
| 688 except KeyError: | |
| 689 pass | |
| 690 current_info = {} | |
| 691 else: | |
| 692 # ongoing section | |
| 693 if line.startswith((b'physical id', b'cpu cores')): | |
| 694 key, value = line.split(b'\t:', 1) | |
| 695 current_info[key] = int(value) | |
| 696 | |
| 697 result = sum(mapping.values()) | |
| 698 return result or None # mimic os.cpu_count() | |
| 699 | |
| 700 | |
| 701 def cpu_stats(): | |
| 702 """Return various CPU stats as a named tuple.""" | |
| 703 with open_binary('%s/stat' % get_procfs_path()) as f: | |
| 704 ctx_switches = None | |
| 705 interrupts = None | |
| 706 soft_interrupts = None | |
| 707 for line in f: | |
| 708 if line.startswith(b'ctxt'): | |
| 709 ctx_switches = int(line.split()[1]) | |
| 710 elif line.startswith(b'intr'): | |
| 711 interrupts = int(line.split()[1]) | |
| 712 elif line.startswith(b'softirq'): | |
| 713 soft_interrupts = int(line.split()[1]) | |
| 714 if ctx_switches is not None and soft_interrupts is not None \ | |
| 715 and interrupts is not None: | |
| 716 break | |
| 717 syscalls = 0 | |
| 718 return _common.scpustats( | |
| 719 ctx_switches, interrupts, soft_interrupts, syscalls) | |
| 720 | |
| 721 | |
| 722 if os.path.exists("/sys/devices/system/cpu/cpufreq/policy0") or \ | |
| 723 os.path.exists("/sys/devices/system/cpu/cpu0/cpufreq"): | |
| 724 def cpu_freq(): | |
| 725 """Return frequency metrics for all CPUs. | |
| 726 Contrarily to other OSes, Linux updates these values in | |
| 727 real-time. | |
| 728 """ | |
| 729 def get_path(num): | |
| 730 for p in ("/sys/devices/system/cpu/cpufreq/policy%s" % num, | |
| 731 "/sys/devices/system/cpu/cpu%s/cpufreq" % num): | |
| 732 if os.path.exists(p): | |
| 733 return p | |
| 734 | |
| 735 ret = [] | |
| 736 for n in range(cpu_count_logical()): | |
| 737 path = get_path(n) | |
| 738 if not path: | |
| 739 continue | |
| 740 | |
| 741 pjoin = os.path.join | |
| 742 curr = cat(pjoin(path, "scaling_cur_freq"), fallback=None) | |
| 743 if curr is None: | |
| 744 # Likely an old RedHat, see: | |
| 745 # https://github.com/giampaolo/psutil/issues/1071 | |
| 746 curr = cat(pjoin(path, "cpuinfo_cur_freq"), fallback=None) | |
| 747 if curr is None: | |
| 748 raise NotImplementedError( | |
| 749 "can't find current frequency file") | |
| 750 curr = int(curr) / 1000 | |
| 751 max_ = int(cat(pjoin(path, "scaling_max_freq"))) / 1000 | |
| 752 min_ = int(cat(pjoin(path, "scaling_min_freq"))) / 1000 | |
| 753 ret.append(_common.scpufreq(curr, min_, max_)) | |
| 754 return ret | |
| 755 | |
| 756 elif os.path.exists("/proc/cpuinfo"): | |
| 757 def cpu_freq(): | |
| 758 """Alternate implementation using /proc/cpuinfo. | |
| 759 min and max frequencies are not available and are set to None. | |
| 760 """ | |
| 761 ret = [] | |
| 762 with open_binary('%s/cpuinfo' % get_procfs_path()) as f: | |
| 763 for line in f: | |
| 764 if line.lower().startswith(b'cpu mhz'): | |
| 765 key, value = line.split(b':', 1) | |
| 766 ret.append(_common.scpufreq(float(value), 0., 0.)) | |
| 767 return ret | |
| 768 | |
| 769 else: | |
| 770 def cpu_freq(): | |
| 771 """Dummy implementation when none of the above files are present. | |
| 772 """ | |
| 773 return [] | |
| 774 | |
| 775 | |
| 776 # ===================================================================== | |
| 777 # --- network | |
| 778 # ===================================================================== | |
| 779 | |
| 780 | |
| 781 net_if_addrs = cext_posix.net_if_addrs | |
| 782 | |
| 783 | |
| 784 class _Ipv6UnsupportedError(Exception): | |
| 785 pass | |
| 786 | |
| 787 | |
| 788 class Connections: | |
| 789 """A wrapper on top of /proc/net/* files, retrieving per-process | |
| 790 and system-wide open connections (TCP, UDP, UNIX) similarly to | |
| 791 "netstat -an". | |
| 792 | |
| 793 Note: in case of UNIX sockets we're only able to determine the | |
| 794 local endpoint/path, not the one it's connected to. | |
| 795 According to [1] it would be possible but not easily. | |
| 796 | |
| 797 [1] http://serverfault.com/a/417946 | |
| 798 """ | |
| 799 | |
| 800 def __init__(self): | |
| 801 # The string represents the basename of the corresponding | |
| 802 # /proc/net/{proto_name} file. | |
| 803 tcp4 = ("tcp", socket.AF_INET, socket.SOCK_STREAM) | |
| 804 tcp6 = ("tcp6", socket.AF_INET6, socket.SOCK_STREAM) | |
| 805 udp4 = ("udp", socket.AF_INET, socket.SOCK_DGRAM) | |
| 806 udp6 = ("udp6", socket.AF_INET6, socket.SOCK_DGRAM) | |
| 807 unix = ("unix", socket.AF_UNIX, None) | |
| 808 self.tmap = { | |
| 809 "all": (tcp4, tcp6, udp4, udp6, unix), | |
| 810 "tcp": (tcp4, tcp6), | |
| 811 "tcp4": (tcp4,), | |
| 812 "tcp6": (tcp6,), | |
| 813 "udp": (udp4, udp6), | |
| 814 "udp4": (udp4,), | |
| 815 "udp6": (udp6,), | |
| 816 "unix": (unix,), | |
| 817 "inet": (tcp4, tcp6, udp4, udp6), | |
| 818 "inet4": (tcp4, udp4), | |
| 819 "inet6": (tcp6, udp6), | |
| 820 } | |
| 821 self._procfs_path = None | |
| 822 | |
| 823 def get_proc_inodes(self, pid): | |
| 824 inodes = defaultdict(list) | |
| 825 for fd in os.listdir("%s/%s/fd" % (self._procfs_path, pid)): | |
| 826 try: | |
| 827 inode = readlink("%s/%s/fd/%s" % (self._procfs_path, pid, fd)) | |
| 828 except (FileNotFoundError, ProcessLookupError): | |
| 829 # ENOENT == file which is gone in the meantime; | |
| 830 # os.stat('/proc/%s' % self.pid) will be done later | |
| 831 # to force NSP (if it's the case) | |
| 832 continue | |
| 833 except OSError as err: | |
| 834 if err.errno == errno.EINVAL: | |
| 835 # not a link | |
| 836 continue | |
| 837 raise | |
| 838 else: | |
| 839 if inode.startswith('socket:['): | |
| 840 # the process is using a socket | |
| 841 inode = inode[8:][:-1] | |
| 842 inodes[inode].append((pid, int(fd))) | |
| 843 return inodes | |
| 844 | |
| 845 def get_all_inodes(self): | |
| 846 inodes = {} | |
| 847 for pid in pids(): | |
| 848 try: | |
| 849 inodes.update(self.get_proc_inodes(pid)) | |
| 850 except (FileNotFoundError, ProcessLookupError, PermissionError): | |
| 851 # os.listdir() is gonna raise a lot of access denied | |
| 852 # exceptions in case of unprivileged user; that's fine | |
| 853 # as we'll just end up returning a connection with PID | |
| 854 # and fd set to None anyway. | |
| 855 # Both netstat -an and lsof does the same so it's | |
| 856 # unlikely we can do any better. | |
| 857 # ENOENT just means a PID disappeared on us. | |
| 858 continue | |
| 859 return inodes | |
| 860 | |
| 861 @staticmethod | |
| 862 def decode_address(addr, family): | |
| 863 """Accept an "ip:port" address as displayed in /proc/net/* | |
| 864 and convert it into a human readable form, like: | |
| 865 | |
| 866 "0500000A:0016" -> ("10.0.0.5", 22) | |
| 867 "0000000000000000FFFF00000100007F:9E49" -> ("::ffff:127.0.0.1", 40521) | |
| 868 | |
| 869 The IP address portion is a little or big endian four-byte | |
| 870 hexadecimal number; that is, the least significant byte is listed | |
| 871 first, so we need to reverse the order of the bytes to convert it | |
| 872 to an IP address. | |
| 873 The port is represented as a two-byte hexadecimal number. | |
| 874 | |
| 875 Reference: | |
| 876 http://linuxdevcenter.com/pub/a/linux/2000/11/16/LinuxAdmin.html | |
| 877 """ | |
| 878 ip, port = addr.split(':') | |
| 879 port = int(port, 16) | |
| 880 # this usually refers to a local socket in listen mode with | |
| 881 # no end-points connected | |
| 882 if not port: | |
| 883 return () | |
| 884 if PY3: | |
| 885 ip = ip.encode('ascii') | |
| 886 if family == socket.AF_INET: | |
| 887 # see: https://github.com/giampaolo/psutil/issues/201 | |
| 888 if LITTLE_ENDIAN: | |
| 889 ip = socket.inet_ntop(family, base64.b16decode(ip)[::-1]) | |
| 890 else: | |
| 891 ip = socket.inet_ntop(family, base64.b16decode(ip)) | |
| 892 else: # IPv6 | |
| 893 ip = base64.b16decode(ip) | |
| 894 try: | |
| 895 # see: https://github.com/giampaolo/psutil/issues/201 | |
| 896 if LITTLE_ENDIAN: | |
| 897 ip = socket.inet_ntop( | |
| 898 socket.AF_INET6, | |
| 899 struct.pack('>4I', *struct.unpack('<4I', ip))) | |
| 900 else: | |
| 901 ip = socket.inet_ntop( | |
| 902 socket.AF_INET6, | |
| 903 struct.pack('<4I', *struct.unpack('<4I', ip))) | |
| 904 except ValueError: | |
| 905 # see: https://github.com/giampaolo/psutil/issues/623 | |
| 906 if not supports_ipv6(): | |
| 907 raise _Ipv6UnsupportedError | |
| 908 else: | |
| 909 raise | |
| 910 return _common.addr(ip, port) | |
| 911 | |
| 912 @staticmethod | |
| 913 def process_inet(file, family, type_, inodes, filter_pid=None): | |
| 914 """Parse /proc/net/tcp* and /proc/net/udp* files.""" | |
| 915 if file.endswith('6') and not os.path.exists(file): | |
| 916 # IPv6 not supported | |
| 917 return | |
| 918 with open_text(file, buffering=BIGFILE_BUFFERING) as f: | |
| 919 f.readline() # skip the first line | |
| 920 for lineno, line in enumerate(f, 1): | |
| 921 try: | |
| 922 _, laddr, raddr, status, _, _, _, _, _, inode = \ | |
| 923 line.split()[:10] | |
| 924 except ValueError: | |
| 925 raise RuntimeError( | |
| 926 "error while parsing %s; malformed line %s %r" % ( | |
| 927 file, lineno, line)) | |
| 928 if inode in inodes: | |
| 929 # # We assume inet sockets are unique, so we error | |
| 930 # # out if there are multiple references to the | |
| 931 # # same inode. We won't do this for UNIX sockets. | |
| 932 # if len(inodes[inode]) > 1 and family != socket.AF_UNIX: | |
| 933 # raise ValueError("ambiguos inode with multiple " | |
| 934 # "PIDs references") | |
| 935 pid, fd = inodes[inode][0] | |
| 936 else: | |
| 937 pid, fd = None, -1 | |
| 938 if filter_pid is not None and filter_pid != pid: | |
| 939 continue | |
| 940 else: | |
| 941 if type_ == socket.SOCK_STREAM: | |
| 942 status = TCP_STATUSES[status] | |
| 943 else: | |
| 944 status = _common.CONN_NONE | |
| 945 try: | |
| 946 laddr = Connections.decode_address(laddr, family) | |
| 947 raddr = Connections.decode_address(raddr, family) | |
| 948 except _Ipv6UnsupportedError: | |
| 949 continue | |
| 950 yield (fd, family, type_, laddr, raddr, status, pid) | |
| 951 | |
| 952 @staticmethod | |
| 953 def process_unix(file, family, inodes, filter_pid=None): | |
| 954 """Parse /proc/net/unix files.""" | |
| 955 with open_text(file, buffering=BIGFILE_BUFFERING) as f: | |
| 956 f.readline() # skip the first line | |
| 957 for line in f: | |
| 958 tokens = line.split() | |
| 959 try: | |
| 960 _, _, _, _, type_, _, inode = tokens[0:7] | |
| 961 except ValueError: | |
| 962 if ' ' not in line: | |
| 963 # see: https://github.com/giampaolo/psutil/issues/766 | |
| 964 continue | |
| 965 raise RuntimeError( | |
| 966 "error while parsing %s; malformed line %r" % ( | |
| 967 file, line)) | |
| 968 if inode in inodes: | |
| 969 # With UNIX sockets we can have a single inode | |
| 970 # referencing many file descriptors. | |
| 971 pairs = inodes[inode] | |
| 972 else: | |
| 973 pairs = [(None, -1)] | |
| 974 for pid, fd in pairs: | |
| 975 if filter_pid is not None and filter_pid != pid: | |
| 976 continue | |
| 977 else: | |
| 978 if len(tokens) == 8: | |
| 979 path = tokens[-1] | |
| 980 else: | |
| 981 path = "" | |
| 982 type_ = _common.socktype_to_enum(int(type_)) | |
| 983 # XXX: determining the remote endpoint of a | |
| 984 # UNIX socket on Linux is not possible, see: | |
| 985 # https://serverfault.com/questions/252723/ | |
| 986 raddr = "" | |
| 987 status = _common.CONN_NONE | |
| 988 yield (fd, family, type_, path, raddr, status, pid) | |
| 989 | |
| 990 def retrieve(self, kind, pid=None): | |
| 991 if kind not in self.tmap: | |
| 992 raise ValueError("invalid %r kind argument; choose between %s" | |
| 993 % (kind, ', '.join([repr(x) for x in self.tmap]))) | |
| 994 self._procfs_path = get_procfs_path() | |
| 995 if pid is not None: | |
| 996 inodes = self.get_proc_inodes(pid) | |
| 997 if not inodes: | |
| 998 # no connections for this process | |
| 999 return [] | |
| 1000 else: | |
| 1001 inodes = self.get_all_inodes() | |
| 1002 ret = set() | |
| 1003 for proto_name, family, type_ in self.tmap[kind]: | |
| 1004 path = "%s/net/%s" % (self._procfs_path, proto_name) | |
| 1005 if family in (socket.AF_INET, socket.AF_INET6): | |
| 1006 ls = self.process_inet( | |
| 1007 path, family, type_, inodes, filter_pid=pid) | |
| 1008 else: | |
| 1009 ls = self.process_unix( | |
| 1010 path, family, inodes, filter_pid=pid) | |
| 1011 for fd, family, type_, laddr, raddr, status, bound_pid in ls: | |
| 1012 if pid: | |
| 1013 conn = _common.pconn(fd, family, type_, laddr, raddr, | |
| 1014 status) | |
| 1015 else: | |
| 1016 conn = _common.sconn(fd, family, type_, laddr, raddr, | |
| 1017 status, bound_pid) | |
| 1018 ret.add(conn) | |
| 1019 return list(ret) | |
| 1020 | |
| 1021 | |
| 1022 _connections = Connections() | |
| 1023 | |
| 1024 | |
| 1025 def net_connections(kind='inet'): | |
| 1026 """Return system-wide open connections.""" | |
| 1027 return _connections.retrieve(kind) | |
| 1028 | |
| 1029 | |
| 1030 def net_io_counters(): | |
| 1031 """Return network I/O statistics for every network interface | |
| 1032 installed on the system as a dict of raw tuples. | |
| 1033 """ | |
| 1034 with open_text("%s/net/dev" % get_procfs_path()) as f: | |
| 1035 lines = f.readlines() | |
| 1036 retdict = {} | |
| 1037 for line in lines[2:]: | |
| 1038 colon = line.rfind(':') | |
| 1039 assert colon > 0, repr(line) | |
| 1040 name = line[:colon].strip() | |
| 1041 fields = line[colon + 1:].strip().split() | |
| 1042 | |
| 1043 # in | |
| 1044 (bytes_recv, | |
| 1045 packets_recv, | |
| 1046 errin, | |
| 1047 dropin, | |
| 1048 fifoin, # unused | |
| 1049 framein, # unused | |
| 1050 compressedin, # unused | |
| 1051 multicastin, # unused | |
| 1052 # out | |
| 1053 bytes_sent, | |
| 1054 packets_sent, | |
| 1055 errout, | |
| 1056 dropout, | |
| 1057 fifoout, # unused | |
| 1058 collisionsout, # unused | |
| 1059 carrierout, # unused | |
| 1060 compressedout) = map(int, fields) | |
| 1061 | |
| 1062 retdict[name] = (bytes_sent, bytes_recv, packets_sent, packets_recv, | |
| 1063 errin, errout, dropin, dropout) | |
| 1064 return retdict | |
| 1065 | |
| 1066 | |
| 1067 def net_if_stats(): | |
| 1068 """Get NIC stats (isup, duplex, speed, mtu).""" | |
| 1069 duplex_map = {cext.DUPLEX_FULL: NIC_DUPLEX_FULL, | |
| 1070 cext.DUPLEX_HALF: NIC_DUPLEX_HALF, | |
| 1071 cext.DUPLEX_UNKNOWN: NIC_DUPLEX_UNKNOWN} | |
| 1072 names = net_io_counters().keys() | |
| 1073 ret = {} | |
| 1074 for name in names: | |
| 1075 try: | |
| 1076 mtu = cext_posix.net_if_mtu(name) | |
| 1077 isup = cext_posix.net_if_is_running(name) | |
| 1078 duplex, speed = cext.net_if_duplex_speed(name) | |
| 1079 except OSError as err: | |
| 1080 # https://github.com/giampaolo/psutil/issues/1279 | |
| 1081 if err.errno != errno.ENODEV: | |
| 1082 raise | |
| 1083 else: | |
| 1084 ret[name] = _common.snicstats(isup, duplex_map[duplex], speed, mtu) | |
| 1085 return ret | |
| 1086 | |
| 1087 | |
| 1088 # ===================================================================== | |
| 1089 # --- disks | |
| 1090 # ===================================================================== | |
| 1091 | |
| 1092 | |
| 1093 disk_usage = _psposix.disk_usage | |
| 1094 | |
| 1095 | |
| 1096 def disk_io_counters(perdisk=False): | |
| 1097 """Return disk I/O statistics for every disk installed on the | |
| 1098 system as a dict of raw tuples. | |
| 1099 """ | |
| 1100 def read_procfs(): | |
| 1101 # OK, this is a bit confusing. The format of /proc/diskstats can | |
| 1102 # have 3 variations. | |
| 1103 # On Linux 2.4 each line has always 15 fields, e.g.: | |
| 1104 # "3 0 8 hda 8 8 8 8 8 8 8 8 8 8 8" | |
| 1105 # On Linux 2.6+ each line *usually* has 14 fields, and the disk | |
| 1106 # name is in another position, like this: | |
| 1107 # "3 0 hda 8 8 8 8 8 8 8 8 8 8 8" | |
| 1108 # ...unless (Linux 2.6) the line refers to a partition instead | |
| 1109 # of a disk, in which case the line has less fields (7): | |
| 1110 # "3 1 hda1 8 8 8 8" | |
| 1111 # 4.18+ has 4 fields added: | |
| 1112 # "3 0 hda 8 8 8 8 8 8 8 8 8 8 8 0 0 0 0" | |
| 1113 # 5.5 has 2 more fields. | |
| 1114 # See: | |
| 1115 # https://www.kernel.org/doc/Documentation/iostats.txt | |
| 1116 # https://www.kernel.org/doc/Documentation/ABI/testing/procfs-diskstats | |
| 1117 with open_text("%s/diskstats" % get_procfs_path()) as f: | |
| 1118 lines = f.readlines() | |
| 1119 for line in lines: | |
| 1120 fields = line.split() | |
| 1121 flen = len(fields) | |
| 1122 if flen == 15: | |
| 1123 # Linux 2.4 | |
| 1124 name = fields[3] | |
| 1125 reads = int(fields[2]) | |
| 1126 (reads_merged, rbytes, rtime, writes, writes_merged, | |
| 1127 wbytes, wtime, _, busy_time, _) = map(int, fields[4:14]) | |
| 1128 elif flen == 14 or flen >= 18: | |
| 1129 # Linux 2.6+, line referring to a disk | |
| 1130 name = fields[2] | |
| 1131 (reads, reads_merged, rbytes, rtime, writes, writes_merged, | |
| 1132 wbytes, wtime, _, busy_time, _) = map(int, fields[3:14]) | |
| 1133 elif flen == 7: | |
| 1134 # Linux 2.6+, line referring to a partition | |
| 1135 name = fields[2] | |
| 1136 reads, rbytes, writes, wbytes = map(int, fields[3:]) | |
| 1137 rtime = wtime = reads_merged = writes_merged = busy_time = 0 | |
| 1138 else: | |
| 1139 raise ValueError("not sure how to interpret line %r" % line) | |
| 1140 yield (name, reads, writes, rbytes, wbytes, rtime, wtime, | |
| 1141 reads_merged, writes_merged, busy_time) | |
| 1142 | |
| 1143 def read_sysfs(): | |
| 1144 for block in os.listdir('/sys/block'): | |
| 1145 for root, _, files in os.walk(os.path.join('/sys/block', block)): | |
| 1146 if 'stat' not in files: | |
| 1147 continue | |
| 1148 with open_text(os.path.join(root, 'stat')) as f: | |
| 1149 fields = f.read().strip().split() | |
| 1150 name = os.path.basename(root) | |
| 1151 (reads, reads_merged, rbytes, rtime, writes, writes_merged, | |
| 1152 wbytes, wtime, _, busy_time) = map(int, fields[:10]) | |
| 1153 yield (name, reads, writes, rbytes, wbytes, rtime, | |
| 1154 wtime, reads_merged, writes_merged, busy_time) | |
| 1155 | |
| 1156 if os.path.exists('%s/diskstats' % get_procfs_path()): | |
| 1157 gen = read_procfs() | |
| 1158 elif os.path.exists('/sys/block'): | |
| 1159 gen = read_sysfs() | |
| 1160 else: | |
| 1161 raise NotImplementedError( | |
| 1162 "%s/diskstats nor /sys/block filesystem are available on this " | |
| 1163 "system" % get_procfs_path()) | |
| 1164 | |
| 1165 retdict = {} | |
| 1166 for entry in gen: | |
| 1167 (name, reads, writes, rbytes, wbytes, rtime, wtime, reads_merged, | |
| 1168 writes_merged, busy_time) = entry | |
| 1169 if not perdisk and not is_storage_device(name): | |
| 1170 # perdisk=False means we want to calculate totals so we skip | |
| 1171 # partitions (e.g. 'sda1', 'nvme0n1p1') and only include | |
| 1172 # base disk devices (e.g. 'sda', 'nvme0n1'). Base disks | |
| 1173 # include a total of all their partitions + some extra size | |
| 1174 # of their own: | |
| 1175 # $ cat /proc/diskstats | |
| 1176 # 259 0 sda 10485760 ... | |
| 1177 # 259 1 sda1 5186039 ... | |
| 1178 # 259 1 sda2 5082039 ... | |
| 1179 # See: | |
| 1180 # https://github.com/giampaolo/psutil/pull/1313 | |
| 1181 continue | |
| 1182 | |
| 1183 rbytes *= DISK_SECTOR_SIZE | |
| 1184 wbytes *= DISK_SECTOR_SIZE | |
| 1185 retdict[name] = (reads, writes, rbytes, wbytes, rtime, wtime, | |
| 1186 reads_merged, writes_merged, busy_time) | |
| 1187 | |
| 1188 return retdict | |
| 1189 | |
| 1190 | |
| 1191 def disk_partitions(all=False): | |
| 1192 """Return mounted disk partitions as a list of namedtuples.""" | |
| 1193 fstypes = set() | |
| 1194 procfs_path = get_procfs_path() | |
| 1195 with open_text("%s/filesystems" % procfs_path) as f: | |
| 1196 for line in f: | |
| 1197 line = line.strip() | |
| 1198 if not line.startswith("nodev"): | |
| 1199 fstypes.add(line.strip()) | |
| 1200 else: | |
| 1201 # ignore all lines starting with "nodev" except "nodev zfs" | |
| 1202 fstype = line.split("\t")[1] | |
| 1203 if fstype == "zfs": | |
| 1204 fstypes.add("zfs") | |
| 1205 | |
| 1206 # See: https://github.com/giampaolo/psutil/issues/1307 | |
| 1207 if procfs_path == "/proc" and os.path.isfile('/etc/mtab'): | |
| 1208 mounts_path = os.path.realpath("/etc/mtab") | |
| 1209 else: | |
| 1210 mounts_path = os.path.realpath("%s/self/mounts" % procfs_path) | |
| 1211 | |
| 1212 retlist = [] | |
| 1213 partitions = cext.disk_partitions(mounts_path) | |
| 1214 for partition in partitions: | |
| 1215 device, mountpoint, fstype, opts = partition | |
| 1216 if device == 'none': | |
| 1217 device = '' | |
| 1218 if not all: | |
| 1219 if device == '' or fstype not in fstypes: | |
| 1220 continue | |
| 1221 maxfile = maxpath = None # set later | |
| 1222 ntuple = _common.sdiskpart(device, mountpoint, fstype, opts, | |
| 1223 maxfile, maxpath) | |
| 1224 retlist.append(ntuple) | |
| 1225 | |
| 1226 return retlist | |
| 1227 | |
| 1228 | |
| 1229 # ===================================================================== | |
| 1230 # --- sensors | |
| 1231 # ===================================================================== | |
| 1232 | |
| 1233 | |
| 1234 def sensors_temperatures(): | |
| 1235 """Return hardware (CPU and others) temperatures as a dict | |
| 1236 including hardware name, label, current, max and critical | |
| 1237 temperatures. | |
| 1238 | |
| 1239 Implementation notes: | |
| 1240 - /sys/class/hwmon looks like the most recent interface to | |
| 1241 retrieve this info, and this implementation relies on it | |
| 1242 only (old distros will probably use something else) | |
| 1243 - lm-sensors on Ubuntu 16.04 relies on /sys/class/hwmon | |
| 1244 - /sys/class/thermal/thermal_zone* is another one but it's more | |
| 1245 difficult to parse | |
| 1246 """ | |
| 1247 ret = collections.defaultdict(list) | |
| 1248 basenames = glob.glob('/sys/class/hwmon/hwmon*/temp*_*') | |
| 1249 # CentOS has an intermediate /device directory: | |
| 1250 # https://github.com/giampaolo/psutil/issues/971 | |
| 1251 # https://github.com/nicolargo/glances/issues/1060 | |
| 1252 basenames.extend(glob.glob('/sys/class/hwmon/hwmon*/device/temp*_*')) | |
| 1253 basenames = sorted(set([x.split('_')[0] for x in basenames])) | |
| 1254 | |
| 1255 # Only add the coretemp hwmon entries if they're not already in | |
| 1256 # /sys/class/hwmon/ | |
| 1257 # https://github.com/giampaolo/psutil/issues/1708 | |
| 1258 # https://github.com/giampaolo/psutil/pull/1648 | |
| 1259 basenames2 = glob.glob( | |
| 1260 '/sys/devices/platform/coretemp.*/hwmon/hwmon*/temp*_*') | |
| 1261 repl = re.compile('/sys/devices/platform/coretemp.*/hwmon/') | |
| 1262 for name in basenames2: | |
| 1263 altname = repl.sub('/sys/class/hwmon/', name) | |
| 1264 if altname not in basenames: | |
| 1265 basenames.append(name) | |
| 1266 | |
| 1267 for base in basenames: | |
| 1268 try: | |
| 1269 path = base + '_input' | |
| 1270 current = float(cat(path)) / 1000.0 | |
| 1271 path = os.path.join(os.path.dirname(base), 'name') | |
| 1272 unit_name = cat(path, binary=False) | |
| 1273 except (IOError, OSError, ValueError): | |
| 1274 # A lot of things can go wrong here, so let's just skip the | |
| 1275 # whole entry. Sure thing is Linux's /sys/class/hwmon really | |
| 1276 # is a stinky broken mess. | |
| 1277 # https://github.com/giampaolo/psutil/issues/1009 | |
| 1278 # https://github.com/giampaolo/psutil/issues/1101 | |
| 1279 # https://github.com/giampaolo/psutil/issues/1129 | |
| 1280 # https://github.com/giampaolo/psutil/issues/1245 | |
| 1281 # https://github.com/giampaolo/psutil/issues/1323 | |
| 1282 continue | |
| 1283 | |
| 1284 high = cat(base + '_max', fallback=None) | |
| 1285 critical = cat(base + '_crit', fallback=None) | |
| 1286 label = cat(base + '_label', fallback='', binary=False) | |
| 1287 | |
| 1288 if high is not None: | |
| 1289 try: | |
| 1290 high = float(high) / 1000.0 | |
| 1291 except ValueError: | |
| 1292 high = None | |
| 1293 if critical is not None: | |
| 1294 try: | |
| 1295 critical = float(critical) / 1000.0 | |
| 1296 except ValueError: | |
| 1297 critical = None | |
| 1298 | |
| 1299 ret[unit_name].append((label, current, high, critical)) | |
| 1300 | |
| 1301 # Indication that no sensors were detected in /sys/class/hwmon/ | |
| 1302 if not basenames: | |
| 1303 basenames = glob.glob('/sys/class/thermal/thermal_zone*') | |
| 1304 basenames = sorted(set(basenames)) | |
| 1305 | |
| 1306 for base in basenames: | |
| 1307 try: | |
| 1308 path = os.path.join(base, 'temp') | |
| 1309 current = float(cat(path)) / 1000.0 | |
| 1310 path = os.path.join(base, 'type') | |
| 1311 unit_name = cat(path, binary=False) | |
| 1312 except (IOError, OSError, ValueError) as err: | |
| 1313 debug("ignoring %r for file %r" % (err, path)) | |
| 1314 continue | |
| 1315 | |
| 1316 trip_paths = glob.glob(base + '/trip_point*') | |
| 1317 trip_points = set(['_'.join( | |
| 1318 os.path.basename(p).split('_')[0:3]) for p in trip_paths]) | |
| 1319 critical = None | |
| 1320 high = None | |
| 1321 for trip_point in trip_points: | |
| 1322 path = os.path.join(base, trip_point + "_type") | |
| 1323 trip_type = cat(path, fallback='', binary=False) | |
| 1324 if trip_type == 'critical': | |
| 1325 critical = cat(os.path.join(base, trip_point + "_temp"), | |
| 1326 fallback=None) | |
| 1327 elif trip_type == 'high': | |
| 1328 high = cat(os.path.join(base, trip_point + "_temp"), | |
| 1329 fallback=None) | |
| 1330 | |
| 1331 if high is not None: | |
| 1332 try: | |
| 1333 high = float(high) / 1000.0 | |
| 1334 except ValueError: | |
| 1335 high = None | |
| 1336 if critical is not None: | |
| 1337 try: | |
| 1338 critical = float(critical) / 1000.0 | |
| 1339 except ValueError: | |
| 1340 critical = None | |
| 1341 | |
| 1342 ret[unit_name].append(('', current, high, critical)) | |
| 1343 | |
| 1344 return dict(ret) | |
| 1345 | |
| 1346 | |
| 1347 def sensors_fans(): | |
| 1348 """Return hardware fans info (for CPU and other peripherals) as a | |
| 1349 dict including hardware label and current speed. | |
| 1350 | |
| 1351 Implementation notes: | |
| 1352 - /sys/class/hwmon looks like the most recent interface to | |
| 1353 retrieve this info, and this implementation relies on it | |
| 1354 only (old distros will probably use something else) | |
| 1355 - lm-sensors on Ubuntu 16.04 relies on /sys/class/hwmon | |
| 1356 """ | |
| 1357 ret = collections.defaultdict(list) | |
| 1358 basenames = glob.glob('/sys/class/hwmon/hwmon*/fan*_*') | |
| 1359 if not basenames: | |
| 1360 # CentOS has an intermediate /device directory: | |
| 1361 # https://github.com/giampaolo/psutil/issues/971 | |
| 1362 basenames = glob.glob('/sys/class/hwmon/hwmon*/device/fan*_*') | |
| 1363 | |
| 1364 basenames = sorted(set([x.split('_')[0] for x in basenames])) | |
| 1365 for base in basenames: | |
| 1366 try: | |
| 1367 current = int(cat(base + '_input')) | |
| 1368 except (IOError, OSError) as err: | |
| 1369 warnings.warn("ignoring %r" % err, RuntimeWarning) | |
| 1370 continue | |
| 1371 unit_name = cat(os.path.join(os.path.dirname(base), 'name'), | |
| 1372 binary=False) | |
| 1373 label = cat(base + '_label', fallback='', binary=False) | |
| 1374 ret[unit_name].append(_common.sfan(label, current)) | |
| 1375 | |
| 1376 return dict(ret) | |
| 1377 | |
| 1378 | |
| 1379 def sensors_battery(): | |
| 1380 """Return battery information. | |
| 1381 Implementation note: it appears /sys/class/power_supply/BAT0/ | |
| 1382 directory structure may vary and provide files with the same | |
| 1383 meaning but under different names, see: | |
| 1384 https://github.com/giampaolo/psutil/issues/966 | |
| 1385 """ | |
| 1386 null = object() | |
| 1387 | |
| 1388 def multi_cat(*paths): | |
| 1389 """Attempt to read the content of multiple files which may | |
| 1390 not exist. If none of them exist return None. | |
| 1391 """ | |
| 1392 for path in paths: | |
| 1393 ret = cat(path, fallback=null) | |
| 1394 if ret != null: | |
| 1395 return int(ret) if ret.isdigit() else ret | |
| 1396 return None | |
| 1397 | |
| 1398 bats = [x for x in os.listdir(POWER_SUPPLY_PATH) if x.startswith('BAT') or | |
| 1399 'battery' in x.lower()] | |
| 1400 if not bats: | |
| 1401 return None | |
| 1402 # Get the first available battery. Usually this is "BAT0", except | |
| 1403 # some rare exceptions: | |
| 1404 # https://github.com/giampaolo/psutil/issues/1238 | |
| 1405 root = os.path.join(POWER_SUPPLY_PATH, sorted(bats)[0]) | |
| 1406 | |
| 1407 # Base metrics. | |
| 1408 energy_now = multi_cat( | |
| 1409 root + "/energy_now", | |
| 1410 root + "/charge_now") | |
| 1411 power_now = multi_cat( | |
| 1412 root + "/power_now", | |
| 1413 root + "/current_now") | |
| 1414 energy_full = multi_cat( | |
| 1415 root + "/energy_full", | |
| 1416 root + "/charge_full") | |
| 1417 time_to_empty = multi_cat(root + "/time_to_empty_now") | |
| 1418 | |
| 1419 # Percent. If we have energy_full the percentage will be more | |
| 1420 # accurate compared to reading /capacity file (float vs. int). | |
| 1421 if energy_full is not None and energy_now is not None: | |
| 1422 try: | |
| 1423 percent = 100.0 * energy_now / energy_full | |
| 1424 except ZeroDivisionError: | |
| 1425 percent = 0.0 | |
| 1426 else: | |
| 1427 percent = int(cat(root + "/capacity", fallback=-1)) | |
| 1428 if percent == -1: | |
| 1429 return None | |
| 1430 | |
| 1431 # Is AC power cable plugged in? | |
| 1432 # Note: AC0 is not always available and sometimes (e.g. CentOS7) | |
| 1433 # it's called "AC". | |
| 1434 power_plugged = None | |
| 1435 online = multi_cat( | |
| 1436 os.path.join(POWER_SUPPLY_PATH, "AC0/online"), | |
| 1437 os.path.join(POWER_SUPPLY_PATH, "AC/online")) | |
| 1438 if online is not None: | |
| 1439 power_plugged = online == 1 | |
| 1440 else: | |
| 1441 status = cat(root + "/status", fallback="", binary=False).lower() | |
| 1442 if status == "discharging": | |
| 1443 power_plugged = False | |
| 1444 elif status in ("charging", "full"): | |
| 1445 power_plugged = True | |
| 1446 | |
| 1447 # Seconds left. | |
| 1448 # Note to self: we may also calculate the charging ETA as per: | |
| 1449 # https://github.com/thialfihar/dotfiles/blob/ | |
| 1450 # 013937745fd9050c30146290e8f963d65c0179e6/bin/battery.py#L55 | |
| 1451 if power_plugged: | |
| 1452 secsleft = _common.POWER_TIME_UNLIMITED | |
| 1453 elif energy_now is not None and power_now is not None: | |
| 1454 try: | |
| 1455 secsleft = int(energy_now / power_now * 3600) | |
| 1456 except ZeroDivisionError: | |
| 1457 secsleft = _common.POWER_TIME_UNKNOWN | |
| 1458 elif time_to_empty is not None: | |
| 1459 secsleft = int(time_to_empty * 60) | |
| 1460 if secsleft < 0: | |
| 1461 secsleft = _common.POWER_TIME_UNKNOWN | |
| 1462 else: | |
| 1463 secsleft = _common.POWER_TIME_UNKNOWN | |
| 1464 | |
| 1465 return _common.sbattery(percent, secsleft, power_plugged) | |
| 1466 | |
| 1467 | |
| 1468 # ===================================================================== | |
| 1469 # --- other system functions | |
| 1470 # ===================================================================== | |
| 1471 | |
| 1472 | |
| 1473 def users(): | |
| 1474 """Return currently connected users as a list of namedtuples.""" | |
| 1475 retlist = [] | |
| 1476 rawlist = cext.users() | |
| 1477 for item in rawlist: | |
| 1478 user, tty, hostname, tstamp, user_process, pid = item | |
| 1479 # note: the underlying C function includes entries about | |
| 1480 # system boot, run level and others. We might want | |
| 1481 # to use them in the future. | |
| 1482 if not user_process: | |
| 1483 continue | |
| 1484 if hostname in (':0.0', ':0'): | |
| 1485 hostname = 'localhost' | |
| 1486 nt = _common.suser(user, tty or None, hostname, tstamp, pid) | |
| 1487 retlist.append(nt) | |
| 1488 return retlist | |
| 1489 | |
| 1490 | |
| 1491 def boot_time(): | |
| 1492 """Return the system boot time expressed in seconds since the epoch.""" | |
| 1493 global BOOT_TIME | |
| 1494 path = '%s/stat' % get_procfs_path() | |
| 1495 with open_binary(path) as f: | |
| 1496 for line in f: | |
| 1497 if line.startswith(b'btime'): | |
| 1498 ret = float(line.strip().split()[1]) | |
| 1499 BOOT_TIME = ret | |
| 1500 return ret | |
| 1501 raise RuntimeError( | |
| 1502 "line 'btime' not found in %s" % path) | |
| 1503 | |
| 1504 | |
| 1505 # ===================================================================== | |
| 1506 # --- processes | |
| 1507 # ===================================================================== | |
| 1508 | |
| 1509 | |
| 1510 def pids(): | |
| 1511 """Returns a list of PIDs currently running on the system.""" | |
| 1512 return [int(x) for x in os.listdir(b(get_procfs_path())) if x.isdigit()] | |
| 1513 | |
| 1514 | |
| 1515 def pid_exists(pid): | |
| 1516 """Check for the existence of a unix PID. Linux TIDs are not | |
| 1517 supported (always return False). | |
| 1518 """ | |
| 1519 if not _psposix.pid_exists(pid): | |
| 1520 return False | |
| 1521 else: | |
| 1522 # Linux's apparently does not distinguish between PIDs and TIDs | |
| 1523 # (thread IDs). | |
| 1524 # listdir("/proc") won't show any TID (only PIDs) but | |
| 1525 # os.stat("/proc/{tid}") will succeed if {tid} exists. | |
| 1526 # os.kill() can also be passed a TID. This is quite confusing. | |
| 1527 # In here we want to enforce this distinction and support PIDs | |
| 1528 # only, see: | |
| 1529 # https://github.com/giampaolo/psutil/issues/687 | |
| 1530 try: | |
| 1531 # Note: already checked that this is faster than using a | |
| 1532 # regular expr. Also (a lot) faster than doing | |
| 1533 # 'return pid in pids()' | |
| 1534 path = "%s/%s/status" % (get_procfs_path(), pid) | |
| 1535 with open_binary(path) as f: | |
| 1536 for line in f: | |
| 1537 if line.startswith(b"Tgid:"): | |
| 1538 tgid = int(line.split()[1]) | |
| 1539 # If tgid and pid are the same then we're | |
| 1540 # dealing with a process PID. | |
| 1541 return tgid == pid | |
| 1542 raise ValueError("'Tgid' line not found in %s" % path) | |
| 1543 except (EnvironmentError, ValueError): | |
| 1544 return pid in pids() | |
| 1545 | |
| 1546 | |
| 1547 def ppid_map(): | |
| 1548 """Obtain a {pid: ppid, ...} dict for all running processes in | |
| 1549 one shot. Used to speed up Process.children(). | |
| 1550 """ | |
| 1551 ret = {} | |
| 1552 procfs_path = get_procfs_path() | |
| 1553 for pid in pids(): | |
| 1554 try: | |
| 1555 with open_binary("%s/%s/stat" % (procfs_path, pid)) as f: | |
| 1556 data = f.read() | |
| 1557 except (FileNotFoundError, ProcessLookupError): | |
| 1558 # Note: we should be able to access /stat for all processes | |
| 1559 # aka it's unlikely we'll bump into EPERM, which is good. | |
| 1560 pass | |
| 1561 else: | |
| 1562 rpar = data.rfind(b')') | |
| 1563 dset = data[rpar + 2:].split() | |
| 1564 ppid = int(dset[1]) | |
| 1565 ret[pid] = ppid | |
| 1566 return ret | |
| 1567 | |
| 1568 | |
| 1569 def wrap_exceptions(fun): | |
| 1570 """Decorator which translates bare OSError and IOError exceptions | |
| 1571 into NoSuchProcess and AccessDenied. | |
| 1572 """ | |
| 1573 @functools.wraps(fun) | |
| 1574 def wrapper(self, *args, **kwargs): | |
| 1575 try: | |
| 1576 return fun(self, *args, **kwargs) | |
| 1577 except PermissionError: | |
| 1578 raise AccessDenied(self.pid, self._name) | |
| 1579 except ProcessLookupError: | |
| 1580 raise NoSuchProcess(self.pid, self._name) | |
| 1581 except FileNotFoundError: | |
| 1582 if not os.path.exists("%s/%s" % (self._procfs_path, self.pid)): | |
| 1583 raise NoSuchProcess(self.pid, self._name) | |
| 1584 # Note: zombies will keep existing under /proc until they're | |
| 1585 # gone so there's no way to distinguish them in here. | |
| 1586 raise | |
| 1587 return wrapper | |
| 1588 | |
| 1589 | |
| 1590 class Process(object): | |
| 1591 """Linux process implementation.""" | |
| 1592 | |
| 1593 __slots__ = ["pid", "_name", "_ppid", "_procfs_path", "_cache"] | |
| 1594 | |
| 1595 def __init__(self, pid): | |
| 1596 self.pid = pid | |
| 1597 self._name = None | |
| 1598 self._ppid = None | |
| 1599 self._procfs_path = get_procfs_path() | |
| 1600 | |
| 1601 def _assert_alive(self): | |
| 1602 """Raise NSP if the process disappeared on us.""" | |
| 1603 # For those C function who do not raise NSP, possibly returning | |
| 1604 # incorrect or incomplete result. | |
| 1605 os.stat('%s/%s' % (self._procfs_path, self.pid)) | |
| 1606 | |
| 1607 @wrap_exceptions | |
| 1608 @memoize_when_activated | |
| 1609 def _parse_stat_file(self): | |
| 1610 """Parse /proc/{pid}/stat file and return a dict with various | |
| 1611 process info. | |
| 1612 Using "man proc" as a reference: where "man proc" refers to | |
| 1613 position N always substract 3 (e.g ppid position 4 in | |
| 1614 'man proc' == position 1 in here). | |
| 1615 The return value is cached in case oneshot() ctx manager is | |
| 1616 in use. | |
| 1617 """ | |
| 1618 with open_binary("%s/%s/stat" % (self._procfs_path, self.pid)) as f: | |
| 1619 data = f.read() | |
| 1620 # Process name is between parentheses. It can contain spaces and | |
| 1621 # other parentheses. This is taken into account by looking for | |
| 1622 # the first occurrence of "(" and the last occurence of ")". | |
| 1623 rpar = data.rfind(b')') | |
| 1624 name = data[data.find(b'(') + 1:rpar] | |
| 1625 fields = data[rpar + 2:].split() | |
| 1626 | |
| 1627 ret = {} | |
| 1628 ret['name'] = name | |
| 1629 ret['status'] = fields[0] | |
| 1630 ret['ppid'] = fields[1] | |
| 1631 ret['ttynr'] = fields[4] | |
| 1632 ret['utime'] = fields[11] | |
| 1633 ret['stime'] = fields[12] | |
| 1634 ret['children_utime'] = fields[13] | |
| 1635 ret['children_stime'] = fields[14] | |
| 1636 ret['create_time'] = fields[19] | |
| 1637 ret['cpu_num'] = fields[36] | |
| 1638 ret['blkio_ticks'] = fields[39] # aka 'delayacct_blkio_ticks' | |
| 1639 | |
| 1640 return ret | |
| 1641 | |
| 1642 @wrap_exceptions | |
| 1643 @memoize_when_activated | |
| 1644 def _read_status_file(self): | |
| 1645 """Read /proc/{pid}/stat file and return its content. | |
| 1646 The return value is cached in case oneshot() ctx manager is | |
| 1647 in use. | |
| 1648 """ | |
| 1649 with open_binary("%s/%s/status" % (self._procfs_path, self.pid)) as f: | |
| 1650 return f.read() | |
| 1651 | |
| 1652 @wrap_exceptions | |
| 1653 @memoize_when_activated | |
| 1654 def _read_smaps_file(self): | |
| 1655 with open_binary("%s/%s/smaps" % (self._procfs_path, self.pid), | |
| 1656 buffering=BIGFILE_BUFFERING) as f: | |
| 1657 return f.read().strip() | |
| 1658 | |
| 1659 def oneshot_enter(self): | |
| 1660 self._parse_stat_file.cache_activate(self) | |
| 1661 self._read_status_file.cache_activate(self) | |
| 1662 self._read_smaps_file.cache_activate(self) | |
| 1663 | |
| 1664 def oneshot_exit(self): | |
| 1665 self._parse_stat_file.cache_deactivate(self) | |
| 1666 self._read_status_file.cache_deactivate(self) | |
| 1667 self._read_smaps_file.cache_deactivate(self) | |
| 1668 | |
| 1669 @wrap_exceptions | |
| 1670 def name(self): | |
| 1671 name = self._parse_stat_file()['name'] | |
| 1672 if PY3: | |
| 1673 name = decode(name) | |
| 1674 # XXX - gets changed later and probably needs refactoring | |
| 1675 return name | |
| 1676 | |
| 1677 def exe(self): | |
| 1678 try: | |
| 1679 return readlink("%s/%s/exe" % (self._procfs_path, self.pid)) | |
| 1680 except (FileNotFoundError, ProcessLookupError): | |
| 1681 # no such file error; might be raised also if the | |
| 1682 # path actually exists for system processes with | |
| 1683 # low pids (about 0-20) | |
| 1684 if os.path.lexists("%s/%s" % (self._procfs_path, self.pid)): | |
| 1685 return "" | |
| 1686 else: | |
| 1687 if not pid_exists(self.pid): | |
| 1688 raise NoSuchProcess(self.pid, self._name) | |
| 1689 else: | |
| 1690 raise ZombieProcess(self.pid, self._name, self._ppid) | |
| 1691 except PermissionError: | |
| 1692 raise AccessDenied(self.pid, self._name) | |
| 1693 | |
| 1694 @wrap_exceptions | |
| 1695 def cmdline(self): | |
| 1696 with open_text("%s/%s/cmdline" % (self._procfs_path, self.pid)) as f: | |
| 1697 data = f.read() | |
| 1698 if not data: | |
| 1699 # may happen in case of zombie process | |
| 1700 return [] | |
| 1701 # 'man proc' states that args are separated by null bytes '\0' | |
| 1702 # and last char is supposed to be a null byte. Nevertheless | |
| 1703 # some processes may change their cmdline after being started | |
| 1704 # (via setproctitle() or similar), they are usually not | |
| 1705 # compliant with this rule and use spaces instead. Google | |
| 1706 # Chrome process is an example. See: | |
| 1707 # https://github.com/giampaolo/psutil/issues/1179 | |
| 1708 sep = '\x00' if data.endswith('\x00') else ' ' | |
| 1709 if data.endswith(sep): | |
| 1710 data = data[:-1] | |
| 1711 cmdline = data.split(sep) | |
| 1712 # Sometimes last char is a null byte '\0' but the args are | |
| 1713 # separated by spaces, see: https://github.com/giampaolo/psutil/ | |
| 1714 # issues/1179#issuecomment-552984549 | |
| 1715 if sep == '\x00' and len(cmdline) == 1 and ' ' in data: | |
| 1716 cmdline = data.split(' ') | |
| 1717 return cmdline | |
| 1718 | |
| 1719 @wrap_exceptions | |
| 1720 def environ(self): | |
| 1721 with open_text("%s/%s/environ" % (self._procfs_path, self.pid)) as f: | |
| 1722 data = f.read() | |
| 1723 return parse_environ_block(data) | |
| 1724 | |
| 1725 @wrap_exceptions | |
| 1726 def terminal(self): | |
| 1727 tty_nr = int(self._parse_stat_file()['ttynr']) | |
| 1728 tmap = _psposix.get_terminal_map() | |
| 1729 try: | |
| 1730 return tmap[tty_nr] | |
| 1731 except KeyError: | |
| 1732 return None | |
| 1733 | |
| 1734 # May not be available on old kernels. | |
| 1735 if os.path.exists('/proc/%s/io' % os.getpid()): | |
| 1736 @wrap_exceptions | |
| 1737 def io_counters(self): | |
| 1738 fname = "%s/%s/io" % (self._procfs_path, self.pid) | |
| 1739 fields = {} | |
| 1740 with open_binary(fname) as f: | |
| 1741 for line in f: | |
| 1742 # https://github.com/giampaolo/psutil/issues/1004 | |
| 1743 line = line.strip() | |
| 1744 if line: | |
| 1745 try: | |
| 1746 name, value = line.split(b': ') | |
| 1747 except ValueError: | |
| 1748 # https://github.com/giampaolo/psutil/issues/1004 | |
| 1749 continue | |
| 1750 else: | |
| 1751 fields[name] = int(value) | |
| 1752 if not fields: | |
| 1753 raise RuntimeError("%s file was empty" % fname) | |
| 1754 try: | |
| 1755 return pio( | |
| 1756 fields[b'syscr'], # read syscalls | |
| 1757 fields[b'syscw'], # write syscalls | |
| 1758 fields[b'read_bytes'], # read bytes | |
| 1759 fields[b'write_bytes'], # write bytes | |
| 1760 fields[b'rchar'], # read chars | |
| 1761 fields[b'wchar'], # write chars | |
| 1762 ) | |
| 1763 except KeyError as err: | |
| 1764 raise ValueError("%r field was not found in %s; found fields " | |
| 1765 "are %r" % (err[0], fname, fields)) | |
| 1766 | |
| 1767 @wrap_exceptions | |
| 1768 def cpu_times(self): | |
| 1769 values = self._parse_stat_file() | |
| 1770 utime = float(values['utime']) / CLOCK_TICKS | |
| 1771 stime = float(values['stime']) / CLOCK_TICKS | |
| 1772 children_utime = float(values['children_utime']) / CLOCK_TICKS | |
| 1773 children_stime = float(values['children_stime']) / CLOCK_TICKS | |
| 1774 iowait = float(values['blkio_ticks']) / CLOCK_TICKS | |
| 1775 return pcputimes(utime, stime, children_utime, children_stime, iowait) | |
| 1776 | |
| 1777 @wrap_exceptions | |
| 1778 def cpu_num(self): | |
| 1779 """What CPU the process is on.""" | |
| 1780 return int(self._parse_stat_file()['cpu_num']) | |
| 1781 | |
| 1782 @wrap_exceptions | |
| 1783 def wait(self, timeout=None): | |
| 1784 return _psposix.wait_pid(self.pid, timeout, self._name) | |
| 1785 | |
| 1786 @wrap_exceptions | |
| 1787 def create_time(self): | |
| 1788 ctime = float(self._parse_stat_file()['create_time']) | |
| 1789 # According to documentation, starttime is in field 21 and the | |
| 1790 # unit is jiffies (clock ticks). | |
| 1791 # We first divide it for clock ticks and then add uptime returning | |
| 1792 # seconds since the epoch. | |
| 1793 # Also use cached value if available. | |
| 1794 bt = BOOT_TIME or boot_time() | |
| 1795 return (ctime / CLOCK_TICKS) + bt | |
| 1796 | |
| 1797 @wrap_exceptions | |
| 1798 def memory_info(self): | |
| 1799 # ============================================================ | |
| 1800 # | FIELD | DESCRIPTION | AKA | TOP | | |
| 1801 # ============================================================ | |
| 1802 # | rss | resident set size | | RES | | |
| 1803 # | vms | total program size | size | VIRT | | |
| 1804 # | shared | shared pages (from shared mappings) | | SHR | | |
| 1805 # | text | text ('code') | trs | CODE | | |
| 1806 # | lib | library (unused in Linux 2.6) | lrs | | | |
| 1807 # | data | data + stack | drs | DATA | | |
| 1808 # | dirty | dirty pages (unused in Linux 2.6) | dt | | | |
| 1809 # ============================================================ | |
| 1810 with open_binary("%s/%s/statm" % (self._procfs_path, self.pid)) as f: | |
| 1811 vms, rss, shared, text, lib, data, dirty = \ | |
| 1812 [int(x) * PAGESIZE for x in f.readline().split()[:7]] | |
| 1813 return pmem(rss, vms, shared, text, lib, data, dirty) | |
| 1814 | |
| 1815 # /proc/pid/smaps does not exist on kernels < 2.6.14 or if | |
| 1816 # CONFIG_MMU kernel configuration option is not enabled. | |
| 1817 if HAS_SMAPS: | |
| 1818 | |
| 1819 @wrap_exceptions | |
| 1820 def memory_full_info( | |
| 1821 self, | |
| 1822 # Gets Private_Clean, Private_Dirty, Private_Hugetlb. | |
| 1823 _private_re=re.compile(br"\nPrivate.*:\s+(\d+)"), | |
| 1824 _pss_re=re.compile(br"\nPss\:\s+(\d+)"), | |
| 1825 _swap_re=re.compile(br"\nSwap\:\s+(\d+)")): | |
| 1826 basic_mem = self.memory_info() | |
| 1827 # Note: using 3 regexes is faster than reading the file | |
| 1828 # line by line. | |
| 1829 # XXX: on Python 3 the 2 regexes are 30% slower than on | |
| 1830 # Python 2 though. Figure out why. | |
| 1831 # | |
| 1832 # You might be tempted to calculate USS by subtracting | |
| 1833 # the "shared" value from the "resident" value in | |
| 1834 # /proc/<pid>/statm. But at least on Linux, statm's "shared" | |
| 1835 # value actually counts pages backed by files, which has | |
| 1836 # little to do with whether the pages are actually shared. | |
| 1837 # /proc/self/smaps on the other hand appears to give us the | |
| 1838 # correct information. | |
| 1839 smaps_data = self._read_smaps_file() | |
| 1840 # Note: smaps file can be empty for certain processes. | |
| 1841 # The code below will not crash though and will result to 0. | |
| 1842 uss = sum(map(int, _private_re.findall(smaps_data))) * 1024 | |
| 1843 pss = sum(map(int, _pss_re.findall(smaps_data))) * 1024 | |
| 1844 swap = sum(map(int, _swap_re.findall(smaps_data))) * 1024 | |
| 1845 return pfullmem(*basic_mem + (uss, pss, swap)) | |
| 1846 | |
| 1847 else: | |
| 1848 memory_full_info = memory_info | |
| 1849 | |
| 1850 if HAS_SMAPS: | |
| 1851 | |
| 1852 @wrap_exceptions | |
| 1853 def memory_maps(self): | |
| 1854 """Return process's mapped memory regions as a list of named | |
| 1855 tuples. Fields are explained in 'man proc'; here is an updated | |
| 1856 (Apr 2012) version: http://goo.gl/fmebo | |
| 1857 | |
| 1858 /proc/{PID}/smaps does not exist on kernels < 2.6.14 or if | |
| 1859 CONFIG_MMU kernel configuration option is not enabled. | |
| 1860 """ | |
| 1861 def get_blocks(lines, current_block): | |
| 1862 data = {} | |
| 1863 for line in lines: | |
| 1864 fields = line.split(None, 5) | |
| 1865 if not fields[0].endswith(b':'): | |
| 1866 # new block section | |
| 1867 yield (current_block.pop(), data) | |
| 1868 current_block.append(line) | |
| 1869 else: | |
| 1870 try: | |
| 1871 data[fields[0]] = int(fields[1]) * 1024 | |
| 1872 except ValueError: | |
| 1873 if fields[0].startswith(b'VmFlags:'): | |
| 1874 # see issue #369 | |
| 1875 continue | |
| 1876 else: | |
| 1877 raise ValueError("don't know how to inte" | |
| 1878 "rpret line %r" % line) | |
| 1879 yield (current_block.pop(), data) | |
| 1880 | |
| 1881 data = self._read_smaps_file() | |
| 1882 # Note: smaps file can be empty for certain processes. | |
| 1883 if not data: | |
| 1884 return [] | |
| 1885 lines = data.split(b'\n') | |
| 1886 ls = [] | |
| 1887 first_line = lines.pop(0) | |
| 1888 current_block = [first_line] | |
| 1889 for header, data in get_blocks(lines, current_block): | |
| 1890 hfields = header.split(None, 5) | |
| 1891 try: | |
| 1892 addr, perms, offset, dev, inode, path = hfields | |
| 1893 except ValueError: | |
| 1894 addr, perms, offset, dev, inode, path = \ | |
| 1895 hfields + [''] | |
| 1896 if not path: | |
| 1897 path = '[anon]' | |
| 1898 else: | |
| 1899 if PY3: | |
| 1900 path = decode(path) | |
| 1901 path = path.strip() | |
| 1902 if (path.endswith(' (deleted)') and not | |
| 1903 path_exists_strict(path)): | |
| 1904 path = path[:-10] | |
| 1905 ls.append(( | |
| 1906 decode(addr), decode(perms), path, | |
| 1907 data.get(b'Rss:', 0), | |
| 1908 data.get(b'Size:', 0), | |
| 1909 data.get(b'Pss:', 0), | |
| 1910 data.get(b'Shared_Clean:', 0), | |
| 1911 data.get(b'Shared_Dirty:', 0), | |
| 1912 data.get(b'Private_Clean:', 0), | |
| 1913 data.get(b'Private_Dirty:', 0), | |
| 1914 data.get(b'Referenced:', 0), | |
| 1915 data.get(b'Anonymous:', 0), | |
| 1916 data.get(b'Swap:', 0) | |
| 1917 )) | |
| 1918 return ls | |
| 1919 | |
| 1920 @wrap_exceptions | |
| 1921 def cwd(self): | |
| 1922 try: | |
| 1923 return readlink("%s/%s/cwd" % (self._procfs_path, self.pid)) | |
| 1924 except (FileNotFoundError, ProcessLookupError): | |
| 1925 # https://github.com/giampaolo/psutil/issues/986 | |
| 1926 if not pid_exists(self.pid): | |
| 1927 raise NoSuchProcess(self.pid, self._name) | |
| 1928 else: | |
| 1929 raise ZombieProcess(self.pid, self._name, self._ppid) | |
| 1930 | |
| 1931 @wrap_exceptions | |
| 1932 def num_ctx_switches(self, | |
| 1933 _ctxsw_re=re.compile(br'ctxt_switches:\t(\d+)')): | |
| 1934 data = self._read_status_file() | |
| 1935 ctxsw = _ctxsw_re.findall(data) | |
| 1936 if not ctxsw: | |
| 1937 raise NotImplementedError( | |
| 1938 "'voluntary_ctxt_switches' and 'nonvoluntary_ctxt_switches'" | |
| 1939 "lines were not found in %s/%s/status; the kernel is " | |
| 1940 "probably older than 2.6.23" % ( | |
| 1941 self._procfs_path, self.pid)) | |
| 1942 else: | |
| 1943 return _common.pctxsw(int(ctxsw[0]), int(ctxsw[1])) | |
| 1944 | |
| 1945 @wrap_exceptions | |
| 1946 def num_threads(self, _num_threads_re=re.compile(br'Threads:\t(\d+)')): | |
| 1947 # Note: on Python 3 using a re is faster than iterating over file | |
| 1948 # line by line. On Python 2 is the exact opposite, and iterating | |
| 1949 # over a file on Python 3 is slower than on Python 2. | |
| 1950 data = self._read_status_file() | |
| 1951 return int(_num_threads_re.findall(data)[0]) | |
| 1952 | |
| 1953 @wrap_exceptions | |
| 1954 def threads(self): | |
| 1955 thread_ids = os.listdir("%s/%s/task" % (self._procfs_path, self.pid)) | |
| 1956 thread_ids.sort() | |
| 1957 retlist = [] | |
| 1958 hit_enoent = False | |
| 1959 for thread_id in thread_ids: | |
| 1960 fname = "%s/%s/task/%s/stat" % ( | |
| 1961 self._procfs_path, self.pid, thread_id) | |
| 1962 try: | |
| 1963 with open_binary(fname) as f: | |
| 1964 st = f.read().strip() | |
| 1965 except FileNotFoundError: | |
| 1966 # no such file or directory; it means thread | |
| 1967 # disappeared on us | |
| 1968 hit_enoent = True | |
| 1969 continue | |
| 1970 # ignore the first two values ("pid (exe)") | |
| 1971 st = st[st.find(b')') + 2:] | |
| 1972 values = st.split(b' ') | |
| 1973 utime = float(values[11]) / CLOCK_TICKS | |
| 1974 stime = float(values[12]) / CLOCK_TICKS | |
| 1975 ntuple = _common.pthread(int(thread_id), utime, stime) | |
| 1976 retlist.append(ntuple) | |
| 1977 if hit_enoent: | |
| 1978 self._assert_alive() | |
| 1979 return retlist | |
| 1980 | |
| 1981 @wrap_exceptions | |
| 1982 def nice_get(self): | |
| 1983 # with open_text('%s/%s/stat' % (self._procfs_path, self.pid)) as f: | |
| 1984 # data = f.read() | |
| 1985 # return int(data.split()[18]) | |
| 1986 | |
| 1987 # Use C implementation | |
| 1988 return cext_posix.getpriority(self.pid) | |
| 1989 | |
| 1990 @wrap_exceptions | |
| 1991 def nice_set(self, value): | |
| 1992 return cext_posix.setpriority(self.pid, value) | |
| 1993 | |
| 1994 # starting from CentOS 6. | |
| 1995 if HAS_CPU_AFFINITY: | |
| 1996 | |
| 1997 @wrap_exceptions | |
| 1998 def cpu_affinity_get(self): | |
| 1999 return cext.proc_cpu_affinity_get(self.pid) | |
| 2000 | |
| 2001 def _get_eligible_cpus( | |
| 2002 self, _re=re.compile(br"Cpus_allowed_list:\t(\d+)-(\d+)")): | |
| 2003 # See: https://github.com/giampaolo/psutil/issues/956 | |
| 2004 data = self._read_status_file() | |
| 2005 match = _re.findall(data) | |
| 2006 if match: | |
| 2007 return list(range(int(match[0][0]), int(match[0][1]) + 1)) | |
| 2008 else: | |
| 2009 return list(range(len(per_cpu_times()))) | |
| 2010 | |
| 2011 @wrap_exceptions | |
| 2012 def cpu_affinity_set(self, cpus): | |
| 2013 try: | |
| 2014 cext.proc_cpu_affinity_set(self.pid, cpus) | |
| 2015 except (OSError, ValueError) as err: | |
| 2016 if isinstance(err, ValueError) or err.errno == errno.EINVAL: | |
| 2017 eligible_cpus = self._get_eligible_cpus() | |
| 2018 all_cpus = tuple(range(len(per_cpu_times()))) | |
| 2019 for cpu in cpus: | |
| 2020 if cpu not in all_cpus: | |
| 2021 raise ValueError( | |
| 2022 "invalid CPU number %r; choose between %s" % ( | |
| 2023 cpu, eligible_cpus)) | |
| 2024 if cpu not in eligible_cpus: | |
| 2025 raise ValueError( | |
| 2026 "CPU number %r is not eligible; choose " | |
| 2027 "between %s" % (cpu, eligible_cpus)) | |
| 2028 raise | |
| 2029 | |
| 2030 # only starting from kernel 2.6.13 | |
| 2031 if HAS_PROC_IO_PRIORITY: | |
| 2032 | |
| 2033 @wrap_exceptions | |
| 2034 def ionice_get(self): | |
| 2035 ioclass, value = cext.proc_ioprio_get(self.pid) | |
| 2036 if enum is not None: | |
| 2037 ioclass = IOPriority(ioclass) | |
| 2038 return _common.pionice(ioclass, value) | |
| 2039 | |
| 2040 @wrap_exceptions | |
| 2041 def ionice_set(self, ioclass, value): | |
| 2042 if value is None: | |
| 2043 value = 0 | |
| 2044 if value and ioclass in (IOPRIO_CLASS_IDLE, IOPRIO_CLASS_NONE): | |
| 2045 raise ValueError("%r ioclass accepts no value" % ioclass) | |
| 2046 if value < 0 or value > 7: | |
| 2047 raise ValueError("value not in 0-7 range") | |
| 2048 return cext.proc_ioprio_set(self.pid, ioclass, value) | |
| 2049 | |
| 2050 if prlimit is not None: | |
| 2051 | |
| 2052 @wrap_exceptions | |
| 2053 def rlimit(self, resource_, limits=None): | |
| 2054 # If pid is 0 prlimit() applies to the calling process and | |
| 2055 # we don't want that. We should never get here though as | |
| 2056 # PID 0 is not supported on Linux. | |
| 2057 if self.pid == 0: | |
| 2058 raise ValueError("can't use prlimit() against PID 0 process") | |
| 2059 try: | |
| 2060 if limits is None: | |
| 2061 # get | |
| 2062 return prlimit(self.pid, resource_) | |
| 2063 else: | |
| 2064 # set | |
| 2065 if len(limits) != 2: | |
| 2066 raise ValueError( | |
| 2067 "second argument must be a (soft, hard) tuple, " | |
| 2068 "got %s" % repr(limits)) | |
| 2069 prlimit(self.pid, resource_, limits) | |
| 2070 except OSError as err: | |
| 2071 if err.errno == errno.ENOSYS and pid_exists(self.pid): | |
| 2072 # I saw this happening on Travis: | |
| 2073 # https://travis-ci.org/giampaolo/psutil/jobs/51368273 | |
| 2074 raise ZombieProcess(self.pid, self._name, self._ppid) | |
| 2075 else: | |
| 2076 raise | |
| 2077 | |
| 2078 @wrap_exceptions | |
| 2079 def status(self): | |
| 2080 letter = self._parse_stat_file()['status'] | |
| 2081 if PY3: | |
| 2082 letter = letter.decode() | |
| 2083 # XXX is '?' legit? (we're not supposed to return it anyway) | |
| 2084 return PROC_STATUSES.get(letter, '?') | |
| 2085 | |
| 2086 @wrap_exceptions | |
| 2087 def open_files(self): | |
| 2088 retlist = [] | |
| 2089 files = os.listdir("%s/%s/fd" % (self._procfs_path, self.pid)) | |
| 2090 hit_enoent = False | |
| 2091 for fd in files: | |
| 2092 file = "%s/%s/fd/%s" % (self._procfs_path, self.pid, fd) | |
| 2093 try: | |
| 2094 path = readlink(file) | |
| 2095 except (FileNotFoundError, ProcessLookupError): | |
| 2096 # ENOENT == file which is gone in the meantime | |
| 2097 hit_enoent = True | |
| 2098 continue | |
| 2099 except OSError as err: | |
| 2100 if err.errno == errno.EINVAL: | |
| 2101 # not a link | |
| 2102 continue | |
| 2103 raise | |
| 2104 else: | |
| 2105 # If path is not an absolute there's no way to tell | |
| 2106 # whether it's a regular file or not, so we skip it. | |
| 2107 # A regular file is always supposed to be have an | |
| 2108 # absolute path though. | |
| 2109 if path.startswith('/') and isfile_strict(path): | |
| 2110 # Get file position and flags. | |
| 2111 file = "%s/%s/fdinfo/%s" % ( | |
| 2112 self._procfs_path, self.pid, fd) | |
| 2113 try: | |
| 2114 with open_binary(file) as f: | |
| 2115 pos = int(f.readline().split()[1]) | |
| 2116 flags = int(f.readline().split()[1], 8) | |
| 2117 except FileNotFoundError: | |
| 2118 # fd gone in the meantime; process may | |
| 2119 # still be alive | |
| 2120 hit_enoent = True | |
| 2121 else: | |
| 2122 mode = file_flags_to_mode(flags) | |
| 2123 ntuple = popenfile( | |
| 2124 path, int(fd), int(pos), mode, flags) | |
| 2125 retlist.append(ntuple) | |
| 2126 if hit_enoent: | |
| 2127 self._assert_alive() | |
| 2128 return retlist | |
| 2129 | |
| 2130 @wrap_exceptions | |
| 2131 def connections(self, kind='inet'): | |
| 2132 ret = _connections.retrieve(kind, self.pid) | |
| 2133 self._assert_alive() | |
| 2134 return ret | |
| 2135 | |
| 2136 @wrap_exceptions | |
| 2137 def num_fds(self): | |
| 2138 return len(os.listdir("%s/%s/fd" % (self._procfs_path, self.pid))) | |
| 2139 | |
| 2140 @wrap_exceptions | |
| 2141 def ppid(self): | |
| 2142 return int(self._parse_stat_file()['ppid']) | |
| 2143 | |
| 2144 @wrap_exceptions | |
| 2145 def uids(self, _uids_re=re.compile(br'Uid:\t(\d+)\t(\d+)\t(\d+)')): | |
| 2146 data = self._read_status_file() | |
| 2147 real, effective, saved = _uids_re.findall(data)[0] | |
| 2148 return _common.puids(int(real), int(effective), int(saved)) | |
| 2149 | |
| 2150 @wrap_exceptions | |
| 2151 def gids(self, _gids_re=re.compile(br'Gid:\t(\d+)\t(\d+)\t(\d+)')): | |
| 2152 data = self._read_status_file() | |
| 2153 real, effective, saved = _gids_re.findall(data)[0] | |
| 2154 return _common.pgids(int(real), int(effective), int(saved)) |
