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