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