_pslinux.py 85 KB

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