_pssunos.py 24 KB

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