codekingpro/portable-devtools
114k
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
7from __future__ import division
8
9import base64
10import collections
11import errno
12import functools
13import glob
14import os
15import re
16import socket
17import struct
18import sys
19import warnings
20from collections import defaultdict
21from collections import namedtuple
22
23from . import _common
24from . import _psposix
25from . import _psutil_linux as cext
26from . import _psutil_posix as cext_posix
27from ._common import NIC_DUPLEX_FULL
28from ._common import NIC_DUPLEX_HALF
29from ._common import NIC_DUPLEX_UNKNOWN
30from ._common import AccessDenied
31from ._common import NoSuchProcess
32from ._common import ZombieProcess
33from ._common import bcat
34from ._common import cat
35from ._common import debug
36from ._common import decode
37from ._common import get_procfs_path
38from ._common import isfile_strict
39from ._common import memoize
40from ._common import memoize_when_activated
41from ._common import open_binary
42from ._common import open_text
43from ._common import parse_environ_block
44from ._common import path_exists_strict
45from ._common import supports_ipv6
46from ._common import usage_percent
47from ._compat import PY3
48from ._compat import FileNotFoundError
49from ._compat import PermissionError
50from ._compat import ProcessLookupError
51from ._compat import b
52from ._compat import basestring
53
54
55if PY3:
56 import enum
57else:
58 enum = None
59
60
61# fmt: off
62__extra__all__ = [
63 #
64 'PROCFS_PATH',
65 # io prio constants
66 "IOPRIO_CLASS_NONE", "IOPRIO_CLASS_RT", "IOPRIO_CLASS_BE",
67 "IOPRIO_CLASS_IDLE",
68 # connection status constants
69 "CONN_ESTABLISHED", "CONN_SYN_SENT", "CONN_SYN_RECV", "CONN_FIN_WAIT1",
70 "CONN_FIN_WAIT2", "CONN_TIME_WAIT", "CONN_CLOSE", "CONN_CLOSE_WAIT",
71 "CONN_LAST_ACK", "CONN_LISTEN", "CONN_CLOSING",
72]
73# fmt: on
74
75
76# =====================================================================
77# --- globals
78# =====================================================================
79
80
81POWER_SUPPLY_PATH = "/sys/class/power_supply"
82HAS_PROC_SMAPS = os.path.exists('/proc/%s/smaps' % os.getpid())
83HAS_PROC_SMAPS_ROLLUP = os.path.exists('/proc/%s/smaps_rollup' % os.getpid())
84HAS_PROC_IO_PRIORITY = hasattr(cext, "proc_ioprio_get")
85HAS_CPU_AFFINITY = hasattr(cext, "proc_cpu_affinity_get")
86
87# Number of clock ticks per second
88CLOCK_TICKS = os.sysconf("SC_CLK_TCK")
89PAGESIZE = cext_posix.getpagesize()
90BOOT_TIME = None # set later
91LITTLE_ENDIAN = sys.byteorder == 'little'
92
93# "man iostat" states that sectors are equivalent with blocks and have
94# a size of 512 bytes. Despite this value can be queried at runtime
95# via /sys/block/{DISK}/queue/hw_sector_size and results may vary
96# between 1k, 2k, or 4k... 512 appears to be a magic constant used
97# throughout Linux source code:
98# * https://stackoverflow.com/a/38136179/376587
99# * https://lists.gt.net/linux/kernel/2241060
100# * https://github.com/giampaolo/psutil/issues/1305
101# * https://github.com/torvalds/linux/blob/
102# 4f671fe2f9523a1ea206f63fe60a7c7b3a56d5c7/include/linux/bio.h#L99
103# * https://lkml.org/lkml/2015/8/17/234
104DISK_SECTOR_SIZE = 512
105
106if enum is None:
107 AF_LINK = socket.AF_PACKET
108else:
109 AddressFamily = enum.IntEnum(
110 'AddressFamily', {'AF_LINK': int(socket.AF_PACKET)}
111 )
112 AF_LINK = AddressFamily.AF_LINK
113
114# ioprio_* constants http://linux.die.net/man/2/ioprio_get
115if enum is None:
116 IOPRIO_CLASS_NONE = 0
117 IOPRIO_CLASS_RT = 1
118 IOPRIO_CLASS_BE = 2
119 IOPRIO_CLASS_IDLE = 3
120else:
121
122 class IOPriority(enum.IntEnum):
123 IOPRIO_CLASS_NONE = 0
124 IOPRIO_CLASS_RT = 1
125 IOPRIO_CLASS_BE = 2
126 IOPRIO_CLASS_IDLE = 3
127
128 globals().update(IOPriority.__members__)
129
130# See:
131# https://github.com/torvalds/linux/blame/master/fs/proc/array.c
132# ...and (TASK_* constants):
133# https://github.com/torvalds/linux/blob/master/include/linux/sched.h
134PROC_STATUSES = {
135 "R": _common.STATUS_RUNNING,
136 "S": _common.STATUS_SLEEPING,
137 "D": _common.STATUS_DISK_SLEEP,
138 "T": _common.STATUS_STOPPED,
139 "t": _common.STATUS_TRACING_STOP,
140 "Z": _common.STATUS_ZOMBIE,
141 "X": _common.STATUS_DEAD,
142 "x": _common.STATUS_DEAD,
143 "K": _common.STATUS_WAKE_KILL,
144 "W": _common.STATUS_WAKING,
145 "I": _common.STATUS_IDLE,
146 "P": _common.STATUS_PARKED,
147}
148
149# https://github.com/torvalds/linux/blob/master/include/net/tcp_states.h
150TCP_STATUSES = {
151 "01": _common.CONN_ESTABLISHED,
152 "02": _common.CONN_SYN_SENT,
153 "03": _common.CONN_SYN_RECV,
154 "04": _common.CONN_FIN_WAIT1,
155 "05": _common.CONN_FIN_WAIT2,
156 "06": _common.CONN_TIME_WAIT,
157 "07": _common.CONN_CLOSE,
158 "08": _common.CONN_CLOSE_WAIT,
159 "09": _common.CONN_LAST_ACK,
160 "0A": _common.CONN_LISTEN,
161 "0B": _common.CONN_CLOSING,
162}
163
164
165# =====================================================================
166# --- named tuples
167# =====================================================================
168
169
170# fmt: off
171# psutil.virtual_memory()
172svmem = namedtuple(
173 'svmem', ['total', 'available', 'percent', 'used', 'free',
174 'active', 'inactive', 'buffers', 'cached', 'shared', 'slab'])
175# psutil.disk_io_counters()
176sdiskio = namedtuple(
177 'sdiskio', ['read_count', 'write_count',
178 'read_bytes', 'write_bytes',
179 'read_time', 'write_time',
180 'read_merged_count', 'write_merged_count',
181 'busy_time'])
182# psutil.Process().open_files()
183popenfile = namedtuple(
184 'popenfile', ['path', 'fd', 'position', 'mode', 'flags'])
185# psutil.Process().memory_info()
186pmem = namedtuple('pmem', 'rss vms shared text lib data dirty')
187# psutil.Process().memory_full_info()
188pfullmem = namedtuple('pfullmem', pmem._fields + ('uss', 'pss', 'swap'))
189# psutil.Process().memory_maps(grouped=True)
190pmmap_grouped = namedtuple(
191 'pmmap_grouped',
192 ['path', 'rss', 'size', 'pss', 'shared_clean', 'shared_dirty',
193 'private_clean', 'private_dirty', 'referenced', 'anonymous', 'swap'])
194# psutil.Process().memory_maps(grouped=False)
195pmmap_ext = namedtuple(
196 'pmmap_ext', 'addr perms ' + ' '.join(pmmap_grouped._fields))
197# psutil.Process.io_counters()
198pio = namedtuple('pio', ['read_count', 'write_count',
199 'read_bytes', 'write_bytes',
200 'read_chars', 'write_chars'])
201# psutil.Process.cpu_times()
202pcputimes = namedtuple('pcputimes',
203 ['user', 'system', 'children_user', 'children_system',
204 'iowait'])
205# fmt: on
206
207
208# =====================================================================
209# --- utils
210# =====================================================================
211
212
213def readlink(path):
214 """Wrapper around os.readlink()."""
215 assert isinstance(path, basestring), path
216 path = os.readlink(path)
217 # readlink() might return paths containing null bytes ('\x00')
218 # resulting in "TypeError: must be encoded string without NULL
219 # bytes, not str" errors when the string is passed to other
220 # fs-related functions (os.*, open(), ...).
221 # Apparently everything after '\x00' is garbage (we can have
222 # ' (deleted)', 'new' and possibly others), see:
223 # https://github.com/giampaolo/psutil/issues/717
224 path = path.split('\x00')[0]
225 # Certain paths have ' (deleted)' appended. Usually this is
226 # bogus as the file actually exists. Even if it doesn't we
227 # don't care.
228 if path.endswith(' (deleted)') and not path_exists_strict(path):
229 path = path[:-10]
230 return path
231
232
233def file_flags_to_mode(flags):
234 """Convert file's open() flags into a readable string.
235 Used by Process.open_files().
236 """
237 modes_map = {os.O_RDONLY: 'r', os.O_WRONLY: 'w', os.O_RDWR: 'w+'}
238 mode = modes_map[flags & (os.O_RDONLY | os.O_WRONLY | os.O_RDWR)]
239 if flags & os.O_APPEND:
240 mode = mode.replace('w', 'a', 1)
241 mode = mode.replace('w+', 'r+')
242 # possible values: r, w, a, r+, a+
243 return mode
244
245
246def is_storage_device(name):
247 """Return True if the given name refers to a root device (e.g.
248 "sda", "nvme0n1") as opposed to a logical partition (e.g. "sda1",
249 "nvme0n1p1"). If name is a virtual device (e.g. "loop1", "ram")
250 return True.
251 """
252 # Re-adapted from iostat source code, see:
253 # https://github.com/sysstat/sysstat/blob/
254 # 97912938cd476645b267280069e83b1c8dc0e1c7/common.c#L208
255 # Some devices may have a slash in their name (e.g. cciss/c0d0...).
256 name = name.replace('/', '!')
257 including_virtual = True
258 if including_virtual:
259 path = "/sys/block/%s" % name
260 else:
261 path = "/sys/block/%s/device" % name
262 return os.access(path, os.F_OK)
263
264
265@memoize
266def set_scputimes_ntuple(procfs_path):
267 """Set a namedtuple of variable fields depending on the CPU times
268 available on this Linux kernel version which may be:
269 (user, nice, system, idle, iowait, irq, softirq, [steal, [guest,
270 [guest_nice]]])
271 Used by cpu_times() function.
272 """
273 global scputimes
274 with open_binary('%s/stat' % procfs_path) as f:
275 values = f.readline().split()[1:]
276 fields = ['user', 'nice', 'system', 'idle', 'iowait', 'irq', 'softirq']
277 vlen = len(values)
278 if vlen >= 8:
279 # Linux >= 2.6.11
280 fields.append('steal')
281 if vlen >= 9:
282 # Linux >= 2.6.24
283 fields.append('guest')
284 if vlen >= 10:
285 # Linux >= 3.2.0
286 fields.append('guest_nice')
287 scputimes = namedtuple('scputimes', fields)
288
289
290try:
291 set_scputimes_ntuple("/proc")
292except Exception as err: # noqa: BLE001
293 # Don't want to crash at import time.
294 debug("ignoring exception on import: %r" % err)
295 scputimes = namedtuple('scputimes', 'user system idle')(0.0, 0.0, 0.0)
296
297
298# =====================================================================
299# --- prlimit
300# =====================================================================
301
302# Backport of resource.prlimit() for Python 2. Originally this was done
303# in C, but CentOS-6 which we use to create manylinux wheels is too old
304# and does not support prlimit() syscall. As such the resulting wheel
305# would not include prlimit(), even when installed on newer systems.
306# This is the only part of psutil using ctypes.
307
308prlimit = None
309try:
310 from resource import prlimit # python >= 3.4
311except ImportError:
312 import ctypes
313
314 libc = ctypes.CDLL(None, use_errno=True)
315
316 if hasattr(libc, "prlimit"):
317
318 def prlimit(pid, resource_, limits=None):
319 class StructRlimit(ctypes.Structure):
320 _fields_ = [
321 ('rlim_cur', ctypes.c_longlong),
322 ('rlim_max', ctypes.c_longlong),
323 ]
324
325 current = StructRlimit()
326 if limits is None:
327 # get
328 ret = libc.prlimit(pid, resource_, None, ctypes.byref(current))
329 else:
330 # set
331 new = StructRlimit()
332 new.rlim_cur = limits[0]
333 new.rlim_max = limits[1]
334 ret = libc.prlimit(
335 pid, resource_, ctypes.byref(new), ctypes.byref(current)
336 )
337
338 if ret != 0:
339 errno_ = ctypes.get_errno()
340 raise OSError(errno_, os.strerror(errno_))
341 return (current.rlim_cur, current.rlim_max)
342
343
344if prlimit is not None:
345 __extra__all__.extend(
346 [x for x in dir(cext) if x.startswith('RLIM') and x.isupper()]
347 )
348
349
350# =====================================================================
351# --- system memory
352# =====================================================================
353
354
355def calculate_avail_vmem(mems):
356 """Fallback for kernels < 3.14 where /proc/meminfo does not provide
357 "MemAvailable", see:
358 https://blog.famzah.net/2014/09/24/.
359
360 This code reimplements the algorithm outlined here:
361 https://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/
362 commit/?id=34e431b0ae398fc54ea69ff85ec700722c9da773
363
364 We use this function also when "MemAvailable" returns 0 (possibly a
365 kernel bug, see: https://github.com/giampaolo/psutil/issues/1915).
366 In that case this routine matches "free" CLI tool result ("available"
367 column).
368
369 XXX: on recent kernels this calculation may differ by ~1.5% compared
370 to "MemAvailable:", as it's calculated slightly differently.
371 It is still way more realistic than doing (free + cached) though.
372 See:
373 * https://gitlab.com/procps-ng/procps/issues/42
374 * https://github.com/famzah/linux-memavailable-procfs/issues/2
375 """
376 # Note about "fallback" value. According to:
377 # https://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/
378 # commit/?id=34e431b0ae398fc54ea69ff85ec700722c9da773
379 # ...long ago "available" memory was calculated as (free + cached),
380 # We use fallback when one of these is missing from /proc/meminfo:
381 # "Active(file)": introduced in 2.6.28 / Dec 2008
382 # "Inactive(file)": introduced in 2.6.28 / Dec 2008
383 # "SReclaimable": introduced in 2.6.19 / Nov 2006
384 # /proc/zoneinfo: introduced in 2.6.13 / Aug 2005
385 free = mems[b'MemFree:']
386 fallback = free + mems.get(b"Cached:", 0)
387 try:
388 lru_active_file = mems[b'Active(file):']
389 lru_inactive_file = mems[b'Inactive(file):']
390 slab_reclaimable = mems[b'SReclaimable:']
391 except KeyError as err:
392 debug(
393 "%s is missing from /proc/meminfo; using an approximation for "
394 "calculating available memory"
395 % err.args[0]
396 )
397 return fallback
398 try:
399 f = open_binary('%s/zoneinfo' % get_procfs_path())
400 except IOError:
401 return fallback # kernel 2.6.13
402
403 watermark_low = 0
404 with f:
405 for line in f:
406 line = line.strip()
407 if line.startswith(b'low'):
408 watermark_low += int(line.split()[1])
409 watermark_low *= PAGESIZE
410
411 avail = free - watermark_low
412 pagecache = lru_active_file + lru_inactive_file
413 pagecache -= min(pagecache / 2, watermark_low)
414 avail += pagecache
415 avail += slab_reclaimable - min(slab_reclaimable / 2.0, watermark_low)
416 return int(avail)
417
418
419def virtual_memory():
420 """Report virtual memory stats.
421 This implementation mimicks procps-ng-3.3.12, aka "free" CLI tool:
422 https://gitlab.com/procps-ng/procps/blob/
423 24fd2605c51fccc375ab0287cec33aa767f06718/proc/sysinfo.c#L778-791
424 The returned values are supposed to match both "free" and "vmstat -s"
425 CLI tools.
426 """
427 missing_fields = []
428 mems = {}
429 with open_binary('%s/meminfo' % get_procfs_path()) as f:
430 for line in f:
431 fields = line.split()
432 mems[fields[0]] = int(fields[1]) * 1024
433
434 # /proc doc states that the available fields in /proc/meminfo vary
435 # by architecture and compile options, but these 3 values are also
436 # returned by sysinfo(2); as such we assume they are always there.
437 total = mems[b'MemTotal:']
438 free = mems[b'MemFree:']
439 try:
440 buffers = mems[b'Buffers:']
441 except KeyError:
442 # https://github.com/giampaolo/psutil/issues/1010
443 buffers = 0
444 missing_fields.append('buffers')
445 try:
446 cached = mems[b"Cached:"]
447 except KeyError:
448 cached = 0
449 missing_fields.append('cached')
450 else:
451 # "free" cmdline utility sums reclaimable to cached.
452 # Older versions of procps used to add slab memory instead.
453 # This got changed in:
454 # https://gitlab.com/procps-ng/procps/commit/
455 # 05d751c4f076a2f0118b914c5e51cfbb4762ad8e
456 cached += mems.get(b"SReclaimable:", 0) # since kernel 2.6.19
457
458 try:
459 shared = mems[b'Shmem:'] # since kernel 2.6.32
460 except KeyError:
461 try:
462 shared = mems[b'MemShared:'] # kernels 2.4
463 except KeyError:
464 shared = 0
465 missing_fields.append('shared')
466
467 try:
468 active = mems[b"Active:"]
469 except KeyError:
470 active = 0
471 missing_fields.append('active')
472
473 try:
474 inactive = mems[b"Inactive:"]
475 except KeyError:
476 try:
477 inactive = (
478 mems[b"Inact_dirty:"]
479 + mems[b"Inact_clean:"]
480 + mems[b"Inact_laundry:"]
481 )
482 except KeyError:
483 inactive = 0
484 missing_fields.append('inactive')
485
486 try:
487 slab = mems[b"Slab:"]
488 except KeyError:
489 slab = 0
490
491 used = total - free - cached - buffers
492 if used < 0:
493 # May be symptomatic of running within a LCX container where such
494 # values will be dramatically distorted over those of the host.
495 used = total - free
496
497 # - starting from 4.4.0 we match free's "available" column.
498 # Before 4.4.0 we calculated it as (free + buffers + cached)
499 # which matched htop.
500 # - free and htop available memory differs as per:
501 # http://askubuntu.com/a/369589
502 # http://unix.stackexchange.com/a/65852/168884
503 # - MemAvailable has been introduced in kernel 3.14
504 try:
505 avail = mems[b'MemAvailable:']
506 except KeyError:
507 avail = calculate_avail_vmem(mems)
508 else:
509 if avail == 0:
510 # Yes, it can happen (probably a kernel bug):
511 # https://github.com/giampaolo/psutil/issues/1915
512 # In this case "free" CLI tool makes an estimate. We do the same,
513 # and it matches "free" CLI tool.
514 avail = calculate_avail_vmem(mems)
515
516 if avail < 0:
517 avail = 0
518 missing_fields.append('available')
519 elif avail > total:
520 # If avail is greater than total or our calculation overflows,
521 # that's symptomatic of running within a LCX container where such
522 # values will be dramatically distorted over those of the host.
523 # https://gitlab.com/procps-ng/procps/blob/
524 # 24fd2605c51fccc375ab0287cec33aa767f06718/proc/sysinfo.c#L764
525 avail = free
526
527 percent = usage_percent((total - avail), total, round_=1)
528
529 # Warn about missing metrics which are set to 0.
530 if missing_fields:
531 msg = "%s memory stats couldn't be determined and %s set to 0" % (
532 ", ".join(missing_fields),
533 "was" if len(missing_fields) == 1 else "were",
534 )
535 warnings.warn(msg, RuntimeWarning, stacklevel=2)
536
537 return svmem(
538 total,
539 avail,
540 percent,
541 used,
542 free,
543 active,
544 inactive,
545 buffers,
546 cached,
547 shared,
548 slab,
549 )
550
551
552def swap_memory():
553 """Return swap memory metrics."""
554 mems = {}
555 with open_binary('%s/meminfo' % get_procfs_path()) as f:
556 for line in f:
557 fields = line.split()
558 mems[fields[0]] = int(fields[1]) * 1024
559 # We prefer /proc/meminfo over sysinfo() syscall so that
560 # psutil.PROCFS_PATH can be used in order to allow retrieval
561 # for linux containers, see:
562 # https://github.com/giampaolo/psutil/issues/1015
563 try:
564 total = mems[b'SwapTotal:']
565 free = mems[b'SwapFree:']
566 except KeyError:
567 _, _, _, _, total, free, unit_multiplier = cext.linux_sysinfo()
568 total *= unit_multiplier
569 free *= unit_multiplier
570
571 used = total - free
572 percent = usage_percent(used, total, round_=1)
573 # get pgin/pgouts
574 try:
575 f = open_binary("%s/vmstat" % get_procfs_path())
576 except IOError as err:
577 # see https://github.com/giampaolo/psutil/issues/722
578 msg = (
579 "'sin' and 'sout' swap memory stats couldn't "
580 + "be determined and were set to 0 (%s)" % str(err)
581 )
582 warnings.warn(msg, RuntimeWarning, stacklevel=2)
583 sin = sout = 0
584 else:
585 with f:
586 sin = sout = None
587 for line in f:
588 # values are expressed in 4 kilo bytes, we want
589 # bytes instead
590 if line.startswith(b'pswpin'):
591 sin = int(line.split(b' ')[1]) * 4 * 1024
592 elif line.startswith(b'pswpout'):
593 sout = int(line.split(b' ')[1]) * 4 * 1024
594 if sin is not None and sout is not None:
595 break
596 else:
597 # we might get here when dealing with exotic Linux
598 # flavors, see:
599 # https://github.com/giampaolo/psutil/issues/313
600 msg = "'sin' and 'sout' swap memory stats couldn't "
601 msg += "be determined and were set to 0"
602 warnings.warn(msg, RuntimeWarning, stacklevel=2)
603 sin = sout = 0
604 return _common.sswap(total, used, free, percent, sin, sout)
605
606
607# =====================================================================
608# --- CPU
609# =====================================================================
610
611
612def cpu_times():
613 """Return a named tuple representing the following system-wide
614 CPU times:
615 (user, nice, system, idle, iowait, irq, softirq [steal, [guest,
616 [guest_nice]]])
617 Last 3 fields may not be available on all Linux kernel versions.
618 """
619 procfs_path = get_procfs_path()
620 set_scputimes_ntuple(procfs_path)
621 with open_binary('%s/stat' % procfs_path) as f:
622 values = f.readline().split()
623 fields = values[1 : len(scputimes._fields) + 1]
624 fields = [float(x) / CLOCK_TICKS for x in fields]
625 return scputimes(*fields)
626
627
628def per_cpu_times():
629 """Return a list of namedtuple representing the CPU times
630 for every CPU available on the system.
631 """
632 procfs_path = get_procfs_path()
633 set_scputimes_ntuple(procfs_path)
634 cpus = []
635 with open_binary('%s/stat' % procfs_path) as f:
636 # get rid of the first line which refers to system wide CPU stats
637 f.readline()
638 for line in f:
639 if line.startswith(b'cpu'):
640 values = line.split()
641 fields = values[1 : len(scputimes._fields) + 1]
642 fields = [float(x) / CLOCK_TICKS for x in fields]
643 entry = scputimes(*fields)
644 cpus.append(entry)
645 return cpus
646
647
648def cpu_count_logical():
649 """Return the number of logical CPUs in the system."""
650 try:
651 return os.sysconf("SC_NPROCESSORS_ONLN")
652 except ValueError:
653 # as a second fallback we try to parse /proc/cpuinfo
654 num = 0
655 with open_binary('%s/cpuinfo' % get_procfs_path()) as f:
656 for line in f:
657 if line.lower().startswith(b'processor'):
658 num += 1
659
660 # unknown format (e.g. amrel/sparc architectures), see:
661 # https://github.com/giampaolo/psutil/issues/200
662 # try to parse /proc/stat as a last resort
663 if num == 0:
664 search = re.compile(r'cpu\d')
665 with open_text('%s/stat' % get_procfs_path()) as f:
666 for line in f:
667 line = line.split(' ')[0]
668 if search.match(line):
669 num += 1
670
671 if num == 0:
672 # mimic os.cpu_count()
673 return None
674 return num
675
676
677def cpu_count_cores():
678 """Return the number of CPU cores in the system."""
679 # Method #1
680 ls = set()
681 # These 2 files are the same but */core_cpus_list is newer while
682 # */thread_siblings_list is deprecated and may disappear in the future.
683 # https://www.kernel.org/doc/Documentation/admin-guide/cputopology.rst
684 # https://github.com/giampaolo/psutil/pull/1727#issuecomment-707624964
685 # https://lkml.org/lkml/2019/2/26/41
686 p1 = "/sys/devices/system/cpu/cpu[0-9]*/topology/core_cpus_list"
687 p2 = "/sys/devices/system/cpu/cpu[0-9]*/topology/thread_siblings_list"
688 for path in glob.glob(p1) or glob.glob(p2):
689 with open_binary(path) as f:
690 ls.add(f.read().strip())
691 result = len(ls)
692 if result != 0:
693 return result
694
695 # Method #2
696 mapping = {}
697 current_info = {}
698 with open_binary('%s/cpuinfo' % get_procfs_path()) as f:
699 for line in f:
700 line = line.strip().lower()
701 if not line:
702 # new section
703 try:
704 mapping[current_info[b'physical id']] = current_info[
705 b'cpu cores'
706 ]
707 except KeyError:
708 pass
709 current_info = {}
710 else:
711 # ongoing section
712 if line.startswith((b'physical id', b'cpu cores')):
713 key, value = line.split(b'\t:', 1)
714 current_info[key] = int(value)
715
716 result = sum(mapping.values())
717 return result or None # mimic os.cpu_count()
718
719
720def cpu_stats():
721 """Return various CPU stats as a named tuple."""
722 with open_binary('%s/stat' % get_procfs_path()) as f:
723 ctx_switches = None
724 interrupts = None
725 soft_interrupts = None
726 for line in f:
727 if line.startswith(b'ctxt'):
728 ctx_switches = int(line.split()[1])
729 elif line.startswith(b'intr'):
730 interrupts = int(line.split()[1])
731 elif line.startswith(b'softirq'):
732 soft_interrupts = int(line.split()[1])
733 if (
734 ctx_switches is not None
735 and soft_interrupts is not None
736 and interrupts is not None
737 ):
738 break
739 syscalls = 0
740 return _common.scpustats(
741 ctx_switches, interrupts, soft_interrupts, syscalls
742 )
743
744
745def _cpu_get_cpuinfo_freq():
746 """Return current CPU frequency from cpuinfo if available."""
747 ret = []
748 with open_binary('%s/cpuinfo' % get_procfs_path()) as f:
749 for line in f:
750 if line.lower().startswith(b'cpu mhz'):
751 ret.append(float(line.split(b':', 1)[1]))
752 return ret
753
754
755if os.path.exists("/sys/devices/system/cpu/cpufreq/policy0") or os.path.exists(
756 "/sys/devices/system/cpu/cpu0/cpufreq"
757):
758
759 def cpu_freq():
760 """Return frequency metrics for all CPUs.
761 Contrarily to other OSes, Linux updates these values in
762 real-time.
763 """
764 cpuinfo_freqs = _cpu_get_cpuinfo_freq()
765 paths = glob.glob(
766 "/sys/devices/system/cpu/cpufreq/policy[0-9]*"
767 ) or glob.glob("/sys/devices/system/cpu/cpu[0-9]*/cpufreq")
768 paths.sort(key=lambda x: int(re.search(r"[0-9]+", x).group()))
769 ret = []
770 pjoin = os.path.join
771 for i, path in enumerate(paths):
772 if len(paths) == len(cpuinfo_freqs):
773 # take cached value from cpuinfo if available, see:
774 # https://github.com/giampaolo/psutil/issues/1851
775 curr = cpuinfo_freqs[i] * 1000
776 else:
777 curr = bcat(pjoin(path, "scaling_cur_freq"), fallback=None)
778 if curr is None:
779 # Likely an old RedHat, see:
780 # https://github.com/giampaolo/psutil/issues/1071
781 curr = bcat(pjoin(path, "cpuinfo_cur_freq"), fallback=None)
782 if curr is None:
783 msg = "can't find current frequency file"
784 raise NotImplementedError(msg)
785 curr = int(curr) / 1000
786 max_ = int(bcat(pjoin(path, "scaling_max_freq"))) / 1000
787 min_ = int(bcat(pjoin(path, "scaling_min_freq"))) / 1000
788 ret.append(_common.scpufreq(curr, min_, max_))
789 return ret
790
791else:
792
793 def cpu_freq():
794 """Alternate implementation using /proc/cpuinfo.
795 min and max frequencies are not available and are set to None.
796 """
797 return [_common.scpufreq(x, 0.0, 0.0) for x in _cpu_get_cpuinfo_freq()]
798
799
800# =====================================================================
801# --- network
802# =====================================================================
803
804
805net_if_addrs = cext_posix.net_if_addrs
806
807
808class _Ipv6UnsupportedError(Exception):
809 pass
810
811
812class Connections:
813 """A wrapper on top of /proc/net/* files, retrieving per-process
814 and system-wide open connections (TCP, UDP, UNIX) similarly to
815 "netstat -an".
816
817 Note: in case of UNIX sockets we're only able to determine the
818 local endpoint/path, not the one it's connected to.
819 According to [1] it would be possible but not easily.
820
821 [1] http://serverfault.com/a/417946
822 """
823
824 def __init__(self):
825 # The string represents the basename of the corresponding
826 # /proc/net/{proto_name} file.
827 tcp4 = ("tcp", socket.AF_INET, socket.SOCK_STREAM)
828 tcp6 = ("tcp6", socket.AF_INET6, socket.SOCK_STREAM)
829 udp4 = ("udp", socket.AF_INET, socket.SOCK_DGRAM)
830 udp6 = ("udp6", socket.AF_INET6, socket.SOCK_DGRAM)
831 unix = ("unix", socket.AF_UNIX, None)
832 self.tmap = {
833 "all": (tcp4, tcp6, udp4, udp6, unix),
834 "tcp": (tcp4, tcp6),
835 "tcp4": (tcp4,),
836 "tcp6": (tcp6,),
837 "udp": (udp4, udp6),
838 "udp4": (udp4,),
839 "udp6": (udp6,),
840 "unix": (unix,),
841 "inet": (tcp4, tcp6, udp4, udp6),
842 "inet4": (tcp4, udp4),
843 "inet6": (tcp6, udp6),
844 }
845 self._procfs_path = None
846
847 def get_proc_inodes(self, pid):
848 inodes = defaultdict(list)
849 for fd in os.listdir("%s/%s/fd" % (self._procfs_path, pid)):
850 try:
851 inode = readlink("%s/%s/fd/%s" % (self._procfs_path, pid, fd))
852 except (FileNotFoundError, ProcessLookupError):
853 # ENOENT == file which is gone in the meantime;
854 # os.stat('/proc/%s' % self.pid) will be done later
855 # to force NSP (if it's the case)
856 continue
857 except OSError as err:
858 if err.errno == errno.EINVAL:
859 # not a link
860 continue
861 if err.errno == errno.ENAMETOOLONG:
862 # file name too long
863 debug(err)
864 continue
865 raise
866 else:
867 if inode.startswith('socket:['):
868 # the process is using a socket
869 inode = inode[8:][:-1]
870 inodes[inode].append((pid, int(fd)))
871 return inodes
872
873 def get_all_inodes(self):
874 inodes = {}
875 for pid in pids():
876 try:
877 inodes.update(self.get_proc_inodes(pid))
878 except (FileNotFoundError, ProcessLookupError, PermissionError):
879 # os.listdir() is gonna raise a lot of access denied
880 # exceptions in case of unprivileged user; that's fine
881 # as we'll just end up returning a connection with PID
882 # and fd set to None anyway.
883 # Both netstat -an and lsof does the same so it's
884 # unlikely we can do any better.
885 # ENOENT just means a PID disappeared on us.
886 continue
887 return inodes
888
889 @staticmethod
890 def decode_address(addr, family):
891 """Accept an "ip:port" address as displayed in /proc/net/*
892 and convert it into a human readable form, like:
893
894 "0500000A:0016" -> ("10.0.0.5", 22)
895 "0000000000000000FFFF00000100007F:9E49" -> ("::ffff:127.0.0.1", 40521)
896
897 The IP address portion is a little or big endian four-byte
898 hexadecimal number; that is, the least significant byte is listed
899 first, so we need to reverse the order of the bytes to convert it
900 to an IP address.
901 The port is represented as a two-byte hexadecimal number.
902
903 Reference:
904 http://linuxdevcenter.com/pub/a/linux/2000/11/16/LinuxAdmin.html
905 """
906 ip, port = addr.split(':')
907 port = int(port, 16)
908 # this usually refers to a local socket in listen mode with
909 # no end-points connected
910 if not port:
911 return ()
912 if PY3:
913 ip = ip.encode('ascii')
914 if family == socket.AF_INET:
915 # see: https://github.com/giampaolo/psutil/issues/201
916 if LITTLE_ENDIAN:
917 ip = socket.inet_ntop(family, base64.b16decode(ip)[::-1])
918 else:
919 ip = socket.inet_ntop(family, base64.b16decode(ip))
920 else: # IPv6
921 ip = base64.b16decode(ip)
922 try:
923 # see: https://github.com/giampaolo/psutil/issues/201
924 if LITTLE_ENDIAN:
925 ip = socket.inet_ntop(
926 socket.AF_INET6,
927 struct.pack('>4I', *struct.unpack('<4I', ip)),
928 )
929 else:
930 ip = socket.inet_ntop(
931 socket.AF_INET6,
932 struct.pack('<4I', *struct.unpack('<4I', ip)),
933 )
934 except ValueError:
935 # see: https://github.com/giampaolo/psutil/issues/623
936 if not supports_ipv6():
937 raise _Ipv6UnsupportedError
938 else:
939 raise
940 return _common.addr(ip, port)
941
942 @staticmethod
943 def process_inet(file, family, type_, inodes, filter_pid=None):
944 """Parse /proc/net/tcp* and /proc/net/udp* files."""
945 if file.endswith('6') and not os.path.exists(file):
946 # IPv6 not supported
947 return
948 with open_text(file) as f:
949 f.readline() # skip the first line
950 for lineno, line in enumerate(f, 1):
951 try:
952 _, laddr, raddr, status, _, _, _, _, _, inode = (
953 line.split()[:10]
954 )
955 except ValueError:
956 raise RuntimeError(
957 "error while parsing %s; malformed line %s %r"
958 % (file, lineno, line)
959 )
960 if inode in inodes:
961 # # We assume inet sockets are unique, so we error
962 # # out if there are multiple references to the
963 # # same inode. We won't do this for UNIX sockets.
964 # if len(inodes[inode]) > 1 and family != socket.AF_UNIX:
965 # raise ValueError("ambiguous inode with multiple "
966 # "PIDs references")
967 pid, fd = inodes[inode][0]
968 else:
969 pid, fd = None, -1
970 if filter_pid is not None and filter_pid != pid:
971 continue
972 else:
973 if type_ == socket.SOCK_STREAM:
974 status = TCP_STATUSES[status]
975 else:
976 status = _common.CONN_NONE
977 try:
978 laddr = Connections.decode_address(laddr, family)
979 raddr = Connections.decode_address(raddr, family)
980 except _Ipv6UnsupportedError:
981 continue
982 yield (fd, family, type_, laddr, raddr, status, pid)
983
984 @staticmethod
985 def process_unix(file, family, inodes, filter_pid=None):
986 """Parse /proc/net/unix files."""
987 with open_text(file) as f:
988 f.readline() # skip the first line
989 for line in f:
990 tokens = line.split()
991 try:
992 _, _, _, _, type_, _, inode = tokens[0:7]
993 except ValueError:
994 if ' ' not in line:
995 # see: https://github.com/giampaolo/psutil/issues/766
996 continue
997 raise RuntimeError(
998 "error while parsing %s; malformed line %r"
999 % (file, line)
1000 )
1001 if inode in inodes: # noqa
1002 # With UNIX sockets we can have a single inode
1003 # referencing many file descriptors.
1004 pairs = inodes[inode]
1005 else:
1006 pairs = [(None, -1)]
1007 for pid, fd in pairs:
1008 if filter_pid is not None and filter_pid != pid:
1009 continue
1010 else:
1011 path = tokens[-1] if len(tokens) == 8 else ''
1012 type_ = _common.socktype_to_enum(int(type_))
1013 # XXX: determining the remote endpoint of a
1014 # UNIX socket on Linux is not possible, see:
1015 # https://serverfault.com/questions/252723/
1016 raddr = ""
1017 status = _common.CONN_NONE
1018 yield (fd, family, type_, path, raddr, status, pid)
1019
1020 def retrieve(self, kind, pid=None):
1021 if kind not in self.tmap:
1022 raise ValueError(
1023 "invalid %r kind argument; choose between %s"
1024 % (kind, ', '.join([repr(x) for x in self.tmap]))
1025 )
1026 self._procfs_path = get_procfs_path()
1027 if pid is not None:
1028 inodes = self.get_proc_inodes(pid)
1029 if not inodes:
1030 # no connections for this process
1031 return []
1032 else:
1033 inodes = self.get_all_inodes()
1034 ret = set()
1035 for proto_name, family, type_ in self.tmap[kind]:
1036 path = "%s/net/%s" % (self._procfs_path, proto_name)
1037 if family in (socket.AF_INET, socket.AF_INET6):
1038 ls = self.process_inet(
1039 path, family, type_, inodes, filter_pid=pid
1040 )
1041 else:
1042 ls = self.process_unix(path, family, inodes, filter_pid=pid)
1043 for fd, family, type_, laddr, raddr, status, bound_pid in ls:
1044 if pid:
1045 conn = _common.pconn(
1046 fd, family, type_, laddr, raddr, status
1047 )
1048 else:
1049 conn = _common.sconn(
1050 fd, family, type_, laddr, raddr, status, bound_pid
1051 )
1052 ret.add(conn)
1053 return list(ret)
1054
1055
1056_connections = Connections()
1057
1058
1059def net_connections(kind='inet'):
1060 """Return system-wide open connections."""
1061 return _connections.retrieve(kind)
1062
1063
1064def net_io_counters():
1065 """Return network I/O statistics for every network interface
1066 installed on the system as a dict of raw tuples.
1067 """
1068 with open_text("%s/net/dev" % get_procfs_path()) as f:
1069 lines = f.readlines()
1070 retdict = {}
1071 for line in lines[2:]:
1072 colon = line.rfind(':')
1073 assert colon > 0, repr(line)
1074 name = line[:colon].strip()
1075 fields = line[colon + 1 :].strip().split()
1076
1077 # in
1078 (
1079 bytes_recv,
1080 packets_recv,
1081 errin,
1082 dropin,
1083 fifoin, # unused
1084 framein, # unused
1085 compressedin, # unused
1086 multicastin, # unused
1087 # out
1088 bytes_sent,
1089 packets_sent,
1090 errout,
1091 dropout,
1092 fifoout, # unused
1093 collisionsout, # unused
1094 carrierout, # unused
1095 compressedout,
1096 ) = map(int, fields)
1097
1098 retdict[name] = (
1099 bytes_sent,
1100 bytes_recv,
1101 packets_sent,
1102 packets_recv,
1103 errin,
1104 errout,
1105 dropin,
1106 dropout,
1107 )
1108 return retdict
1109
1110
1111def net_if_stats():
1112 """Get NIC stats (isup, duplex, speed, mtu)."""
1113 duplex_map = {
1114 cext.DUPLEX_FULL: NIC_DUPLEX_FULL,
1115 cext.DUPLEX_HALF: NIC_DUPLEX_HALF,
1116 cext.DUPLEX_UNKNOWN: NIC_DUPLEX_UNKNOWN,
1117 }
1118 names = net_io_counters().keys()
1119 ret = {}
1120 for name in names:
1121 try:
1122 mtu = cext_posix.net_if_mtu(name)
1123 flags = cext_posix.net_if_flags(name)
1124 duplex, speed = cext.net_if_duplex_speed(name)
1125 except OSError as err:
1126 # https://github.com/giampaolo/psutil/issues/1279
1127 if err.errno != errno.ENODEV:
1128 raise
1129 else:
1130 debug(err)
1131 else:
1132 output_flags = ','.join(flags)
1133 isup = 'running' in flags
1134 ret[name] = _common.snicstats(
1135 isup, duplex_map[duplex], speed, mtu, output_flags
1136 )
1137 return ret
1138
1139
1140# =====================================================================
1141# --- disks
1142# =====================================================================
1143
1144
1145disk_usage = _psposix.disk_usage
1146
1147
1148def disk_io_counters(perdisk=False):
1149 """Return disk I/O statistics for every disk installed on the
1150 system as a dict of raw tuples.
1151 """
1152
1153 def read_procfs():
1154 # OK, this is a bit confusing. The format of /proc/diskstats can
1155 # have 3 variations.
1156 # On Linux 2.4 each line has always 15 fields, e.g.:
1157 # "3 0 8 hda 8 8 8 8 8 8 8 8 8 8 8"
1158 # On Linux 2.6+ each line *usually* has 14 fields, and the disk
1159 # name is in another position, like this:
1160 # "3 0 hda 8 8 8 8 8 8 8 8 8 8 8"
1161 # ...unless (Linux 2.6) the line refers to a partition instead
1162 # of a disk, in which case the line has less fields (7):
1163 # "3 1 hda1 8 8 8 8"
1164 # 4.18+ has 4 fields added:
1165 # "3 0 hda 8 8 8 8 8 8 8 8 8 8 8 0 0 0 0"
1166 # 5.5 has 2 more fields.
1167 # See:
1168 # https://www.kernel.org/doc/Documentation/iostats.txt
1169 # https://www.kernel.org/doc/Documentation/ABI/testing/procfs-diskstats
1170 with open_text("%s/diskstats" % get_procfs_path()) as f:
1171 lines = f.readlines()
1172 for line in lines:
1173 fields = line.split()
1174 flen = len(fields)
1175 # fmt: off
1176 if flen == 15:
1177 # Linux 2.4
1178 name = fields[3]
1179 reads = int(fields[2])
1180 (reads_merged, rbytes, rtime, writes, writes_merged,
1181 wbytes, wtime, _, busy_time, _) = map(int, fields[4:14])
1182 elif flen == 14 or flen >= 18:
1183 # Linux 2.6+, line referring to a disk
1184 name = fields[2]
1185 (reads, reads_merged, rbytes, rtime, writes, writes_merged,
1186 wbytes, wtime, _, busy_time, _) = map(int, fields[3:14])
1187 elif flen == 7:
1188 # Linux 2.6+, line referring to a partition
1189 name = fields[2]
1190 reads, rbytes, writes, wbytes = map(int, fields[3:])
1191 rtime = wtime = reads_merged = writes_merged = busy_time = 0
1192 else:
1193 raise ValueError("not sure how to interpret line %r" % line)
1194 yield (name, reads, writes, rbytes, wbytes, rtime, wtime,
1195 reads_merged, writes_merged, busy_time)
1196 # fmt: on
1197
1198 def read_sysfs():
1199 for block in os.listdir('/sys/block'):
1200 for root, _, files in os.walk(os.path.join('/sys/block', block)):
