comparison planemo/lib/python3.7/site-packages/psutil/_pslinux.py @ 1:56ad4e20f292 draft

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