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