_pssunos.py 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734
  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. """Sun OS Solaris platform implementation."""
  5. import errno
  6. import functools
  7. import os
  8. import socket
  9. import subprocess
  10. import sys
  11. from collections import namedtuple
  12. from socket import AF_INET
  13. from . import _common
  14. from . import _psposix
  15. from . import _psutil_posix as cext_posix
  16. from . import _psutil_sunos as cext
  17. from ._common import AF_INET6
  18. from ._common import ENCODING
  19. from ._common import AccessDenied
  20. from ._common import NoSuchProcess
  21. from ._common import ZombieProcess
  22. from ._common import debug
  23. from ._common import get_procfs_path
  24. from ._common import isfile_strict
  25. from ._common import memoize_when_activated
  26. from ._common import sockfam_to_enum
  27. from ._common import socktype_to_enum
  28. from ._common import usage_percent
  29. __extra__all__ = ["CONN_IDLE", "CONN_BOUND", "PROCFS_PATH"]
  30. # =====================================================================
  31. # --- globals
  32. # =====================================================================
  33. PAGE_SIZE = cext_posix.getpagesize()
  34. AF_LINK = cext_posix.AF_LINK
  35. IS_64_BIT = sys.maxsize > 2**32
  36. CONN_IDLE = "IDLE"
  37. CONN_BOUND = "BOUND"
  38. PROC_STATUSES = {
  39. cext.SSLEEP: _common.STATUS_SLEEPING,
  40. cext.SRUN: _common.STATUS_RUNNING,
  41. cext.SZOMB: _common.STATUS_ZOMBIE,
  42. cext.SSTOP: _common.STATUS_STOPPED,
  43. cext.SIDL: _common.STATUS_IDLE,
  44. cext.SONPROC: _common.STATUS_RUNNING, # same as run
  45. cext.SWAIT: _common.STATUS_WAITING,
  46. }
  47. TCP_STATUSES = {
  48. cext.TCPS_ESTABLISHED: _common.CONN_ESTABLISHED,
  49. cext.TCPS_SYN_SENT: _common.CONN_SYN_SENT,
  50. cext.TCPS_SYN_RCVD: _common.CONN_SYN_RECV,
  51. cext.TCPS_FIN_WAIT_1: _common.CONN_FIN_WAIT1,
  52. cext.TCPS_FIN_WAIT_2: _common.CONN_FIN_WAIT2,
  53. cext.TCPS_TIME_WAIT: _common.CONN_TIME_WAIT,
  54. cext.TCPS_CLOSED: _common.CONN_CLOSE,
  55. cext.TCPS_CLOSE_WAIT: _common.CONN_CLOSE_WAIT,
  56. cext.TCPS_LAST_ACK: _common.CONN_LAST_ACK,
  57. cext.TCPS_LISTEN: _common.CONN_LISTEN,
  58. cext.TCPS_CLOSING: _common.CONN_CLOSING,
  59. cext.PSUTIL_CONN_NONE: _common.CONN_NONE,
  60. cext.TCPS_IDLE: CONN_IDLE, # sunos specific
  61. cext.TCPS_BOUND: CONN_BOUND, # sunos specific
  62. }
  63. proc_info_map = dict(
  64. ppid=0,
  65. rss=1,
  66. vms=2,
  67. create_time=3,
  68. nice=4,
  69. num_threads=5,
  70. status=6,
  71. ttynr=7,
  72. uid=8,
  73. euid=9,
  74. gid=10,
  75. egid=11,
  76. )
  77. # =====================================================================
  78. # --- named tuples
  79. # =====================================================================
  80. # psutil.cpu_times()
  81. scputimes = namedtuple('scputimes', ['user', 'system', 'idle', 'iowait'])
  82. # psutil.cpu_times(percpu=True)
  83. pcputimes = namedtuple(
  84. 'pcputimes', ['user', 'system', 'children_user', 'children_system']
  85. )
  86. # psutil.virtual_memory()
  87. svmem = namedtuple('svmem', ['total', 'available', 'percent', 'used', 'free'])
  88. # psutil.Process.memory_info()
  89. pmem = namedtuple('pmem', ['rss', 'vms'])
  90. pfullmem = pmem
  91. # psutil.Process.memory_maps(grouped=True)
  92. pmmap_grouped = namedtuple(
  93. 'pmmap_grouped', ['path', 'rss', 'anonymous', 'locked']
  94. )
  95. # psutil.Process.memory_maps(grouped=False)
  96. pmmap_ext = namedtuple(
  97. 'pmmap_ext', 'addr perms ' + ' '.join(pmmap_grouped._fields)
  98. )
  99. # =====================================================================
  100. # --- memory
  101. # =====================================================================
  102. def virtual_memory():
  103. """Report virtual memory metrics."""
  104. # we could have done this with kstat, but IMHO this is good enough
  105. total = os.sysconf('SC_PHYS_PAGES') * PAGE_SIZE
  106. # note: there's no difference on Solaris
  107. free = avail = os.sysconf('SC_AVPHYS_PAGES') * PAGE_SIZE
  108. used = total - free
  109. percent = usage_percent(used, total, round_=1)
  110. return svmem(total, avail, percent, used, free)
  111. def swap_memory():
  112. """Report swap memory metrics."""
  113. sin, sout = cext.swap_mem()
  114. # XXX
  115. # we are supposed to get total/free by doing so:
  116. # http://cvs.opensolaris.org/source/xref/onnv/onnv-gate/
  117. # usr/src/cmd/swap/swap.c
  118. # ...nevertheless I can't manage to obtain the same numbers as 'swap'
  119. # cmdline utility, so let's parse its output (sigh!)
  120. p = subprocess.Popen(
  121. [
  122. '/usr/bin/env',
  123. f"PATH=/usr/sbin:/sbin:{os.environ['PATH']}",
  124. 'swap',
  125. '-l',
  126. ],
  127. stdout=subprocess.PIPE,
  128. )
  129. stdout, _ = p.communicate()
  130. stdout = stdout.decode(sys.stdout.encoding)
  131. if p.returncode != 0:
  132. msg = f"'swap -l' failed (retcode={p.returncode})"
  133. raise RuntimeError(msg)
  134. lines = stdout.strip().split('\n')[1:]
  135. if not lines:
  136. msg = 'no swap device(s) configured'
  137. raise RuntimeError(msg)
  138. total = free = 0
  139. for line in lines:
  140. line = line.split()
  141. t, f = line[3:5]
  142. total += int(int(t) * 512)
  143. free += int(int(f) * 512)
  144. used = total - free
  145. percent = usage_percent(used, total, round_=1)
  146. return _common.sswap(
  147. total, used, free, percent, sin * PAGE_SIZE, sout * PAGE_SIZE
  148. )
  149. # =====================================================================
  150. # --- CPU
  151. # =====================================================================
  152. def cpu_times():
  153. """Return system-wide CPU times as a named tuple."""
  154. ret = cext.per_cpu_times()
  155. return scputimes(*[sum(x) for x in zip(*ret)])
  156. def per_cpu_times():
  157. """Return system per-CPU times as a list of named tuples."""
  158. ret = cext.per_cpu_times()
  159. return [scputimes(*x) for x in ret]
  160. def cpu_count_logical():
  161. """Return the number of logical CPUs in the system."""
  162. try:
  163. return os.sysconf("SC_NPROCESSORS_ONLN")
  164. except ValueError:
  165. # mimic os.cpu_count() behavior
  166. return None
  167. def cpu_count_cores():
  168. """Return the number of CPU cores in the system."""
  169. return cext.cpu_count_cores()
  170. def cpu_stats():
  171. """Return various CPU stats as a named tuple."""
  172. ctx_switches, interrupts, syscalls, _traps = cext.cpu_stats()
  173. soft_interrupts = 0
  174. return _common.scpustats(
  175. ctx_switches, interrupts, soft_interrupts, syscalls
  176. )
  177. # =====================================================================
  178. # --- disks
  179. # =====================================================================
  180. disk_io_counters = cext.disk_io_counters
  181. disk_usage = _psposix.disk_usage
  182. def disk_partitions(all=False):
  183. """Return system disk partitions."""
  184. # TODO - the filtering logic should be better checked so that
  185. # it tries to reflect 'df' as much as possible
  186. retlist = []
  187. partitions = cext.disk_partitions()
  188. for partition in partitions:
  189. device, mountpoint, fstype, opts = partition
  190. if device == 'none':
  191. device = ''
  192. if not all:
  193. # Differently from, say, Linux, we don't have a list of
  194. # common fs types so the best we can do, AFAIK, is to
  195. # filter by filesystem having a total size > 0.
  196. try:
  197. if not disk_usage(mountpoint).total:
  198. continue
  199. except OSError as err:
  200. # https://github.com/giampaolo/psutil/issues/1674
  201. debug(f"skipping {mountpoint!r}: {err}")
  202. continue
  203. ntuple = _common.sdiskpart(device, mountpoint, fstype, opts)
  204. retlist.append(ntuple)
  205. return retlist
  206. # =====================================================================
  207. # --- network
  208. # =====================================================================
  209. net_io_counters = cext.net_io_counters
  210. net_if_addrs = cext_posix.net_if_addrs
  211. def net_connections(kind, _pid=-1):
  212. """Return socket connections. If pid == -1 return system-wide
  213. connections (as opposed to connections opened by one process only).
  214. Only INET sockets are returned (UNIX are not).
  215. """
  216. families, types = _common.conn_tmap[kind]
  217. rawlist = cext.net_connections(_pid)
  218. ret = set()
  219. for item in rawlist:
  220. fd, fam, type_, laddr, raddr, status, pid = item
  221. if fam not in families:
  222. continue
  223. if type_ not in types:
  224. continue
  225. # TODO: refactor and use _common.conn_to_ntuple.
  226. if fam in {AF_INET, AF_INET6}:
  227. if laddr:
  228. laddr = _common.addr(*laddr)
  229. if raddr:
  230. raddr = _common.addr(*raddr)
  231. status = TCP_STATUSES[status]
  232. fam = sockfam_to_enum(fam)
  233. type_ = socktype_to_enum(type_)
  234. if _pid == -1:
  235. nt = _common.sconn(fd, fam, type_, laddr, raddr, status, pid)
  236. else:
  237. nt = _common.pconn(fd, fam, type_, laddr, raddr, status)
  238. ret.add(nt)
  239. return list(ret)
  240. def net_if_stats():
  241. """Get NIC stats (isup, duplex, speed, mtu)."""
  242. ret = cext.net_if_stats()
  243. for name, items in ret.items():
  244. isup, duplex, speed, mtu = items
  245. if hasattr(_common, 'NicDuplex'):
  246. duplex = _common.NicDuplex(duplex)
  247. ret[name] = _common.snicstats(isup, duplex, speed, mtu, '')
  248. return ret
  249. # =====================================================================
  250. # --- other system functions
  251. # =====================================================================
  252. def boot_time():
  253. """The system boot time expressed in seconds since the epoch."""
  254. return cext.boot_time()
  255. def users():
  256. """Return currently connected users as a list of namedtuples."""
  257. retlist = []
  258. rawlist = cext.users()
  259. localhost = (':0.0', ':0')
  260. for item in rawlist:
  261. user, tty, hostname, tstamp, user_process, pid = item
  262. # note: the underlying C function includes entries about
  263. # system boot, run level and others. We might want
  264. # to use them in the future.
  265. if not user_process:
  266. continue
  267. if hostname in localhost:
  268. hostname = 'localhost'
  269. nt = _common.suser(user, tty, hostname, tstamp, pid)
  270. retlist.append(nt)
  271. return retlist
  272. # =====================================================================
  273. # --- processes
  274. # =====================================================================
  275. def pids():
  276. """Returns a list of PIDs currently running on the system."""
  277. path = get_procfs_path().encode(ENCODING)
  278. return [int(x) for x in os.listdir(path) if x.isdigit()]
  279. def pid_exists(pid):
  280. """Check for the existence of a unix pid."""
  281. return _psposix.pid_exists(pid)
  282. def wrap_exceptions(fun):
  283. """Call callable into a try/except clause and translate ENOENT,
  284. EACCES and EPERM in NoSuchProcess or AccessDenied exceptions.
  285. """
  286. @functools.wraps(fun)
  287. def wrapper(self, *args, **kwargs):
  288. pid, ppid, name = self.pid, self._ppid, self._name
  289. try:
  290. return fun(self, *args, **kwargs)
  291. except (FileNotFoundError, ProcessLookupError) as err:
  292. # ENOENT (no such file or directory) gets raised on open().
  293. # ESRCH (no such process) can get raised on read() if
  294. # process is gone in meantime.
  295. if not pid_exists(pid):
  296. raise NoSuchProcess(pid, name) from err
  297. raise ZombieProcess(pid, name, ppid) from err
  298. except PermissionError as err:
  299. raise AccessDenied(pid, name) from err
  300. except OSError as err:
  301. if pid == 0:
  302. if 0 in pids():
  303. raise AccessDenied(pid, name) from err
  304. raise
  305. raise
  306. return wrapper
  307. class Process:
  308. """Wrapper class around underlying C implementation."""
  309. __slots__ = ["_cache", "_name", "_ppid", "_procfs_path", "pid"]
  310. def __init__(self, pid):
  311. self.pid = pid
  312. self._name = None
  313. self._ppid = None
  314. self._procfs_path = get_procfs_path()
  315. def _assert_alive(self):
  316. """Raise NSP if the process disappeared on us."""
  317. # For those C function who do not raise NSP, possibly returning
  318. # incorrect or incomplete result.
  319. os.stat(f"{self._procfs_path}/{self.pid}")
  320. def oneshot_enter(self):
  321. self._proc_name_and_args.cache_activate(self)
  322. self._proc_basic_info.cache_activate(self)
  323. self._proc_cred.cache_activate(self)
  324. def oneshot_exit(self):
  325. self._proc_name_and_args.cache_deactivate(self)
  326. self._proc_basic_info.cache_deactivate(self)
  327. self._proc_cred.cache_deactivate(self)
  328. @wrap_exceptions
  329. @memoize_when_activated
  330. def _proc_name_and_args(self):
  331. return cext.proc_name_and_args(self.pid, self._procfs_path)
  332. @wrap_exceptions
  333. @memoize_when_activated
  334. def _proc_basic_info(self):
  335. if self.pid == 0 and not os.path.exists(
  336. f"{self._procfs_path}/{self.pid}/psinfo"
  337. ):
  338. raise AccessDenied(self.pid)
  339. ret = cext.proc_basic_info(self.pid, self._procfs_path)
  340. assert len(ret) == len(proc_info_map)
  341. return ret
  342. @wrap_exceptions
  343. @memoize_when_activated
  344. def _proc_cred(self):
  345. return cext.proc_cred(self.pid, self._procfs_path)
  346. @wrap_exceptions
  347. def name(self):
  348. # note: max len == 15
  349. return self._proc_name_and_args()[0]
  350. @wrap_exceptions
  351. def exe(self):
  352. try:
  353. return os.readlink(f"{self._procfs_path}/{self.pid}/path/a.out")
  354. except OSError:
  355. pass # continue and guess the exe name from the cmdline
  356. # Will be guessed later from cmdline but we want to explicitly
  357. # invoke cmdline here in order to get an AccessDenied
  358. # exception if the user has not enough privileges.
  359. self.cmdline()
  360. return ""
  361. @wrap_exceptions
  362. def cmdline(self):
  363. return self._proc_name_and_args()[1].split(' ')
  364. @wrap_exceptions
  365. def environ(self):
  366. return cext.proc_environ(self.pid, self._procfs_path)
  367. @wrap_exceptions
  368. def create_time(self):
  369. return self._proc_basic_info()[proc_info_map['create_time']]
  370. @wrap_exceptions
  371. def num_threads(self):
  372. return self._proc_basic_info()[proc_info_map['num_threads']]
  373. @wrap_exceptions
  374. def nice_get(self):
  375. # Note #1: getpriority(3) doesn't work for realtime processes.
  376. # Psinfo is what ps uses, see:
  377. # https://github.com/giampaolo/psutil/issues/1194
  378. return self._proc_basic_info()[proc_info_map['nice']]
  379. @wrap_exceptions
  380. def nice_set(self, value):
  381. if self.pid in {2, 3}:
  382. # Special case PIDs: internally setpriority(3) return ESRCH
  383. # (no such process), no matter what.
  384. # The process actually exists though, as it has a name,
  385. # creation time, etc.
  386. raise AccessDenied(self.pid, self._name)
  387. return cext_posix.setpriority(self.pid, value)
  388. @wrap_exceptions
  389. def ppid(self):
  390. self._ppid = self._proc_basic_info()[proc_info_map['ppid']]
  391. return self._ppid
  392. @wrap_exceptions
  393. def uids(self):
  394. try:
  395. real, effective, saved, _, _, _ = self._proc_cred()
  396. except AccessDenied:
  397. real = self._proc_basic_info()[proc_info_map['uid']]
  398. effective = self._proc_basic_info()[proc_info_map['euid']]
  399. saved = None
  400. return _common.puids(real, effective, saved)
  401. @wrap_exceptions
  402. def gids(self):
  403. try:
  404. _, _, _, real, effective, saved = self._proc_cred()
  405. except AccessDenied:
  406. real = self._proc_basic_info()[proc_info_map['gid']]
  407. effective = self._proc_basic_info()[proc_info_map['egid']]
  408. saved = None
  409. return _common.puids(real, effective, saved)
  410. @wrap_exceptions
  411. def cpu_times(self):
  412. try:
  413. times = cext.proc_cpu_times(self.pid, self._procfs_path)
  414. except OSError as err:
  415. if err.errno == errno.EOVERFLOW and not IS_64_BIT:
  416. # We may get here if we attempt to query a 64bit process
  417. # with a 32bit python.
  418. # Error originates from read() and also tools like "cat"
  419. # fail in the same way (!).
  420. # Since there simply is no way to determine CPU times we
  421. # return 0.0 as a fallback. See:
  422. # https://github.com/giampaolo/psutil/issues/857
  423. times = (0.0, 0.0, 0.0, 0.0)
  424. else:
  425. raise
  426. return _common.pcputimes(*times)
  427. @wrap_exceptions
  428. def cpu_num(self):
  429. return cext.proc_cpu_num(self.pid, self._procfs_path)
  430. @wrap_exceptions
  431. def terminal(self):
  432. procfs_path = self._procfs_path
  433. hit_enoent = False
  434. tty = wrap_exceptions(self._proc_basic_info()[proc_info_map['ttynr']])
  435. if tty != cext.PRNODEV:
  436. for x in (0, 1, 2, 255):
  437. try:
  438. return os.readlink(f"{procfs_path}/{self.pid}/path/{x}")
  439. except FileNotFoundError:
  440. hit_enoent = True
  441. continue
  442. if hit_enoent:
  443. self._assert_alive()
  444. @wrap_exceptions
  445. def cwd(self):
  446. # /proc/PID/path/cwd may not be resolved by readlink() even if
  447. # it exists (ls shows it). If that's the case and the process
  448. # is still alive return None (we can return None also on BSD).
  449. # Reference: https://groups.google.com/g/comp.unix.solaris/c/tcqvhTNFCAs
  450. procfs_path = self._procfs_path
  451. try:
  452. return os.readlink(f"{procfs_path}/{self.pid}/path/cwd")
  453. except FileNotFoundError:
  454. os.stat(f"{procfs_path}/{self.pid}") # raise NSP or AD
  455. return ""
  456. @wrap_exceptions
  457. def memory_info(self):
  458. ret = self._proc_basic_info()
  459. rss = ret[proc_info_map['rss']] * 1024
  460. vms = ret[proc_info_map['vms']] * 1024
  461. return pmem(rss, vms)
  462. memory_full_info = memory_info
  463. @wrap_exceptions
  464. def status(self):
  465. code = self._proc_basic_info()[proc_info_map['status']]
  466. # XXX is '?' legit? (we're not supposed to return it anyway)
  467. return PROC_STATUSES.get(code, '?')
  468. @wrap_exceptions
  469. def threads(self):
  470. procfs_path = self._procfs_path
  471. ret = []
  472. tids = os.listdir(f"{procfs_path}/{self.pid}/lwp")
  473. hit_enoent = False
  474. for tid in tids:
  475. tid = int(tid)
  476. try:
  477. utime, stime = cext.query_process_thread(
  478. self.pid, tid, procfs_path
  479. )
  480. except OSError as err:
  481. if err.errno == errno.EOVERFLOW and not IS_64_BIT:
  482. # We may get here if we attempt to query a 64bit process
  483. # with a 32bit python.
  484. # Error originates from read() and also tools like "cat"
  485. # fail in the same way (!).
  486. # Since there simply is no way to determine CPU times we
  487. # return 0.0 as a fallback. See:
  488. # https://github.com/giampaolo/psutil/issues/857
  489. continue
  490. # ENOENT == thread gone in meantime
  491. if err.errno == errno.ENOENT:
  492. hit_enoent = True
  493. continue
  494. raise
  495. else:
  496. nt = _common.pthread(tid, utime, stime)
  497. ret.append(nt)
  498. if hit_enoent:
  499. self._assert_alive()
  500. return ret
  501. @wrap_exceptions
  502. def open_files(self):
  503. retlist = []
  504. hit_enoent = False
  505. procfs_path = self._procfs_path
  506. pathdir = f"{procfs_path}/{self.pid}/path"
  507. for fd in os.listdir(f"{procfs_path}/{self.pid}/fd"):
  508. path = os.path.join(pathdir, fd)
  509. if os.path.islink(path):
  510. try:
  511. file = os.readlink(path)
  512. except FileNotFoundError:
  513. hit_enoent = True
  514. continue
  515. else:
  516. if isfile_strict(file):
  517. retlist.append(_common.popenfile(file, int(fd)))
  518. if hit_enoent:
  519. self._assert_alive()
  520. return retlist
  521. def _get_unix_sockets(self, pid):
  522. """Get UNIX sockets used by process by parsing 'pfiles' output."""
  523. # TODO: rewrite this in C (...but the damn netstat source code
  524. # does not include this part! Argh!!)
  525. cmd = ["pfiles", str(pid)]
  526. p = subprocess.Popen(
  527. cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE
  528. )
  529. stdout, stderr = p.communicate()
  530. stdout, stderr = (
  531. x.decode(sys.stdout.encoding) for x in (stdout, stderr)
  532. )
  533. if p.returncode != 0:
  534. if 'permission denied' in stderr.lower():
  535. raise AccessDenied(self.pid, self._name)
  536. if 'no such process' in stderr.lower():
  537. raise NoSuchProcess(self.pid, self._name)
  538. msg = f"{cmd!r} command error\n{stderr}"
  539. raise RuntimeError(msg)
  540. lines = stdout.split('\n')[2:]
  541. for i, line in enumerate(lines):
  542. line = line.lstrip()
  543. if line.startswith('sockname: AF_UNIX'):
  544. path = line.split(' ', 2)[2]
  545. type = lines[i - 2].strip()
  546. if type == 'SOCK_STREAM':
  547. type = socket.SOCK_STREAM
  548. elif type == 'SOCK_DGRAM':
  549. type = socket.SOCK_DGRAM
  550. else:
  551. type = -1
  552. yield (-1, socket.AF_UNIX, type, path, "", _common.CONN_NONE)
  553. @wrap_exceptions
  554. def net_connections(self, kind='inet'):
  555. ret = net_connections(kind, _pid=self.pid)
  556. # The underlying C implementation retrieves all OS connections
  557. # and filters them by PID. At this point we can't tell whether
  558. # an empty list means there were no connections for process or
  559. # process is no longer active so we force NSP in case the PID
  560. # is no longer there.
  561. if not ret:
  562. # will raise NSP if process is gone
  563. os.stat(f"{self._procfs_path}/{self.pid}")
  564. # UNIX sockets
  565. if kind in {'all', 'unix'}:
  566. ret.extend([
  567. _common.pconn(*conn)
  568. for conn in self._get_unix_sockets(self.pid)
  569. ])
  570. return ret
  571. nt_mmap_grouped = namedtuple('mmap', 'path rss anon locked')
  572. nt_mmap_ext = namedtuple('mmap', 'addr perms path rss anon locked')
  573. @wrap_exceptions
  574. def memory_maps(self):
  575. def toaddr(start, end):
  576. return "{}-{}".format(
  577. hex(start)[2:].strip('L'), hex(end)[2:].strip('L')
  578. )
  579. procfs_path = self._procfs_path
  580. retlist = []
  581. try:
  582. rawlist = cext.proc_memory_maps(self.pid, procfs_path)
  583. except OSError as err:
  584. if err.errno == errno.EOVERFLOW and not IS_64_BIT:
  585. # We may get here if we attempt to query a 64bit process
  586. # with a 32bit python.
  587. # Error originates from read() and also tools like "cat"
  588. # fail in the same way (!).
  589. # Since there simply is no way to determine CPU times we
  590. # return 0.0 as a fallback. See:
  591. # https://github.com/giampaolo/psutil/issues/857
  592. return []
  593. else:
  594. raise
  595. hit_enoent = False
  596. for item in rawlist:
  597. addr, addrsize, perm, name, rss, anon, locked = item
  598. addr = toaddr(addr, addrsize)
  599. if not name.startswith('['):
  600. try:
  601. name = os.readlink(f"{procfs_path}/{self.pid}/path/{name}")
  602. except OSError as err:
  603. if err.errno == errno.ENOENT:
  604. # sometimes the link may not be resolved by
  605. # readlink() even if it exists (ls shows it).
  606. # If that's the case we just return the
  607. # unresolved link path.
  608. # This seems an inconsistency with /proc similar
  609. # to: http://goo.gl/55XgO
  610. name = f"{procfs_path}/{self.pid}/path/{name}"
  611. hit_enoent = True
  612. else:
  613. raise
  614. retlist.append((addr, perm, name, rss, anon, locked))
  615. if hit_enoent:
  616. self._assert_alive()
  617. return retlist
  618. @wrap_exceptions
  619. def num_fds(self):
  620. return len(os.listdir(f"{self._procfs_path}/{self.pid}/fd"))
  621. @wrap_exceptions
  622. def num_ctx_switches(self):
  623. return _common.pctxsw(
  624. *cext.proc_num_ctx_switches(self.pid, self._procfs_path)
  625. )
  626. @wrap_exceptions
  627. def wait(self, timeout=None):
  628. return _psposix.wait_pid(self.pid, timeout, self._name)