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