comparison env/lib/python3.9/site-packages/psutil/_pslinux.py @ 0:4f3585e2f14b draft default tip

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