_pssunos.py 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728
  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 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. from ._compat import PY3
  29. from ._compat import FileNotFoundError
  30. from ._compat import PermissionError
  31. from ._compat import ProcessLookupError
  32. from ._compat import b
  33. __extra__all__ = ["CONN_IDLE", "CONN_BOUND", "PROCFS_PATH"]
  34. # =====================================================================
  35. # --- globals
  36. # =====================================================================
  37. PAGE_SIZE = cext_posix.getpagesize()
  38. AF_LINK = cext_posix.AF_LINK
  39. IS_64_BIT = sys.maxsize > 2**32
  40. CONN_IDLE = "IDLE"
  41. CONN_BOUND = "BOUND"
  42. PROC_STATUSES = {
  43. cext.SSLEEP: _common.STATUS_SLEEPING,
  44. cext.SRUN: _common.STATUS_RUNNING,
  45. cext.SZOMB: _common.STATUS_ZOMBIE,
  46. cext.SSTOP: _common.STATUS_STOPPED,
  47. cext.SIDL: _common.STATUS_IDLE,
  48. cext.SONPROC: _common.STATUS_RUNNING, # same as run
  49. cext.SWAIT: _common.STATUS_WAITING,
  50. }
  51. TCP_STATUSES = {
  52. cext.TCPS_ESTABLISHED: _common.CONN_ESTABLISHED,
  53. cext.TCPS_SYN_SENT: _common.CONN_SYN_SENT,
  54. cext.TCPS_SYN_RCVD: _common.CONN_SYN_RECV,
  55. cext.TCPS_FIN_WAIT_1: _common.CONN_FIN_WAIT1,
  56. cext.TCPS_FIN_WAIT_2: _common.CONN_FIN_WAIT2,
  57. cext.TCPS_TIME_WAIT: _common.CONN_TIME_WAIT,
  58. cext.TCPS_CLOSED: _common.CONN_CLOSE,
  59. cext.TCPS_CLOSE_WAIT: _common.CONN_CLOSE_WAIT,
  60. cext.TCPS_LAST_ACK: _common.CONN_LAST_ACK,
  61. cext.TCPS_LISTEN: _common.CONN_LISTEN,
  62. cext.TCPS_CLOSING: _common.CONN_CLOSING,
  63. cext.PSUTIL_CONN_NONE: _common.CONN_NONE,
  64. cext.TCPS_IDLE: CONN_IDLE, # sunos specific
  65. cext.TCPS_BOUND: CONN_BOUND, # sunos specific
  66. }
  67. proc_info_map = dict(
  68. ppid=0,
  69. rss=1,
  70. vms=2,
  71. create_time=3,
  72. nice=4,
  73. num_threads=5,
  74. status=6,
  75. ttynr=7,
  76. uid=8,
  77. euid=9,
  78. gid=10,
  79. egid=11)
  80. # =====================================================================
  81. # --- named tuples
  82. # =====================================================================
  83. # psutil.cpu_times()
  84. scputimes = namedtuple('scputimes', ['user', 'system', 'idle', 'iowait'])
  85. # psutil.cpu_times(percpu=True)
  86. pcputimes = namedtuple('pcputimes',
  87. ['user', 'system', 'children_user', 'children_system'])
  88. # psutil.virtual_memory()
  89. svmem = namedtuple('svmem', ['total', 'available', 'percent', 'used', 'free'])
  90. # psutil.Process.memory_info()
  91. pmem = namedtuple('pmem', ['rss', 'vms'])
  92. pfullmem = pmem
  93. # psutil.Process.memory_maps(grouped=True)
  94. pmmap_grouped = namedtuple('pmmap_grouped',
  95. ['path', 'rss', 'anonymous', 'locked'])
  96. # psutil.Process.memory_maps(grouped=False)
  97. pmmap_ext = namedtuple(
  98. 'pmmap_ext', 'addr perms ' + ' '.join(pmmap_grouped._fields))
  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(['/usr/bin/env', 'PATH=/usr/sbin:/sbin:%s' %
  121. os.environ['PATH'], 'swap', '-l'],
  122. stdout=subprocess.PIPE)
  123. stdout, _ = p.communicate()
  124. if PY3:
  125. stdout = stdout.decode(sys.stdout.encoding)
  126. if p.returncode != 0:
  127. raise RuntimeError("'swap -l' failed (retcode=%s)" % p.returncode)
  128. lines = stdout.strip().split('\n')[1:]
  129. if not lines:
  130. msg = 'no swap device(s) configured'
  131. raise RuntimeError(msg)
  132. total = free = 0
  133. for line in lines:
  134. line = line.split()
  135. t, f = line[3:5]
  136. total += int(int(t) * 512)
  137. free += int(int(f) * 512)
  138. used = total - free
  139. percent = usage_percent(used, total, round_=1)
  140. return _common.sswap(total, used, free, percent,
  141. sin * PAGE_SIZE, sout * PAGE_SIZE)
  142. # =====================================================================
  143. # --- CPU
  144. # =====================================================================
  145. def cpu_times():
  146. """Return system-wide CPU times as a named tuple."""
  147. ret = cext.per_cpu_times()
  148. return scputimes(*[sum(x) for x in zip(*ret)])
  149. def per_cpu_times():
  150. """Return system per-CPU times as a list of named tuples."""
  151. ret = cext.per_cpu_times()
  152. return [scputimes(*x) for x in ret]
  153. def cpu_count_logical():
  154. """Return the number of logical CPUs in the system."""
  155. try:
  156. return os.sysconf("SC_NPROCESSORS_ONLN")
  157. except ValueError:
  158. # mimic os.cpu_count() behavior
  159. return None
  160. def cpu_count_cores():
  161. """Return the number of CPU cores in the system."""
  162. return cext.cpu_count_cores()
  163. def cpu_stats():
  164. """Return various CPU stats as a named tuple."""
  165. ctx_switches, interrupts, syscalls, traps = cext.cpu_stats()
  166. soft_interrupts = 0
  167. return _common.scpustats(ctx_switches, interrupts, soft_interrupts,
  168. syscalls)
  169. # =====================================================================
  170. # --- disks
  171. # =====================================================================
  172. disk_io_counters = cext.disk_io_counters
  173. disk_usage = _psposix.disk_usage
  174. def disk_partitions(all=False):
  175. """Return system disk partitions."""
  176. # TODO - the filtering logic should be better checked so that
  177. # it tries to reflect 'df' as much as possible
  178. retlist = []
  179. partitions = cext.disk_partitions()
  180. for partition in partitions:
  181. device, mountpoint, fstype, opts = partition
  182. if device == 'none':
  183. device = ''
  184. if not all:
  185. # Differently from, say, Linux, we don't have a list of
  186. # common fs types so the best we can do, AFAIK, is to
  187. # filter by filesystem having a total size > 0.
  188. try:
  189. if not disk_usage(mountpoint).total:
  190. continue
  191. except OSError as err:
  192. # https://github.com/giampaolo/psutil/issues/1674
  193. debug("skipping %r: %s" % (mountpoint, err))
  194. continue
  195. maxfile = maxpath = None # set later
  196. ntuple = _common.sdiskpart(device, mountpoint, fstype, opts,
  197. maxfile, maxpath)
  198. retlist.append(ntuple)
  199. return retlist
  200. # =====================================================================
  201. # --- network
  202. # =====================================================================
  203. net_io_counters = cext.net_io_counters
  204. net_if_addrs = cext_posix.net_if_addrs
  205. def net_connections(kind, _pid=-1):
  206. """Return socket connections. If pid == -1 return system-wide
  207. connections (as opposed to connections opened by one process only).
  208. Only INET sockets are returned (UNIX are not).
  209. """
  210. cmap = _common.conn_tmap.copy()
  211. if _pid == -1:
  212. cmap.pop('unix', 0)
  213. if kind not in cmap:
  214. raise ValueError("invalid %r kind argument; choose between %s"
  215. % (kind, ', '.join([repr(x) for x in cmap])))
  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. return [int(x) for x in os.listdir(b(get_procfs_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. try:
  288. return fun(self, *args, **kwargs)
  289. except (FileNotFoundError, ProcessLookupError):
  290. # ENOENT (no such file or directory) gets raised on open().
  291. # ESRCH (no such process) can get raised on read() if
  292. # process is gone in meantime.
  293. if not pid_exists(self.pid):
  294. raise NoSuchProcess(self.pid, self._name)
  295. else:
  296. raise ZombieProcess(self.pid, self._name, self._ppid)
  297. except PermissionError:
  298. raise AccessDenied(self.pid, self._name)
  299. except OSError:
  300. if self.pid == 0:
  301. if 0 in pids():
  302. raise AccessDenied(self.pid, self._name)
  303. else:
  304. raise
  305. raise
  306. return wrapper
  307. class Process:
  308. """Wrapper class around underlying C implementation."""
  309. __slots__ = ["pid", "_name", "_ppid", "_procfs_path", "_cache"]
  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('%s/%s' % (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 \
  336. os.path.exists('%s/%s/psinfo' % (self._procfs_path, self.pid)):
  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(
  353. "%s/%s/path/a.out" % (self._procfs_path, self.pid))
  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(
  435. self._proc_basic_info()[proc_info_map['ttynr']])
  436. if tty != cext.PRNODEV:
  437. for x in (0, 1, 2, 255):
  438. try:
  439. return os.readlink(
  440. '%s/%d/path/%d' % (procfs_path, self.pid, x))
  441. except FileNotFoundError:
  442. hit_enoent = True
  443. continue
  444. if hit_enoent:
  445. self._assert_alive()
  446. @wrap_exceptions
  447. def cwd(self):
  448. # /proc/PID/path/cwd may not be resolved by readlink() even if
  449. # it exists (ls shows it). If that's the case and the process
  450. # is still alive return None (we can return None also on BSD).
  451. # Reference: http://goo.gl/55XgO
  452. procfs_path = self._procfs_path
  453. try:
  454. return os.readlink("%s/%s/path/cwd" % (procfs_path, self.pid))
  455. except FileNotFoundError:
  456. os.stat("%s/%s" % (procfs_path, self.pid)) # raise NSP or AD
  457. return ""
  458. @wrap_exceptions
  459. def memory_info(self):
  460. ret = self._proc_basic_info()
  461. rss = ret[proc_info_map['rss']] * 1024
  462. vms = ret[proc_info_map['vms']] * 1024
  463. return pmem(rss, vms)
  464. memory_full_info = memory_info
  465. @wrap_exceptions
  466. def status(self):
  467. code = self._proc_basic_info()[proc_info_map['status']]
  468. # XXX is '?' legit? (we're not supposed to return it anyway)
  469. return PROC_STATUSES.get(code, '?')
  470. @wrap_exceptions
  471. def threads(self):
  472. procfs_path = self._procfs_path
  473. ret = []
  474. tids = os.listdir('%s/%d/lwp' % (procfs_path, self.pid))
  475. hit_enoent = False
  476. for tid in tids:
  477. tid = int(tid)
  478. try:
  479. utime, stime = cext.query_process_thread(
  480. self.pid, tid, procfs_path)
  481. except EnvironmentError as err:
  482. if err.errno == errno.EOVERFLOW and not IS_64_BIT:
  483. # We may get here if we attempt to query a 64bit process
  484. # with a 32bit python.
  485. # Error originates from read() and also tools like "cat"
  486. # fail in the same way (!).
  487. # Since there simply is no way to determine CPU times we
  488. # return 0.0 as a fallback. See:
  489. # https://github.com/giampaolo/psutil/issues/857
  490. continue
  491. # ENOENT == thread gone in meantime
  492. if err.errno == errno.ENOENT:
  493. hit_enoent = True
  494. continue
  495. raise
  496. else:
  497. nt = _common.pthread(tid, utime, stime)
  498. ret.append(nt)
  499. if hit_enoent:
  500. self._assert_alive()
  501. return ret
  502. @wrap_exceptions
  503. def open_files(self):
  504. retlist = []
  505. hit_enoent = False
  506. procfs_path = self._procfs_path
  507. pathdir = '%s/%d/path' % (procfs_path, self.pid)
  508. for fd in os.listdir('%s/%d/fd' % (procfs_path, self.pid)):
  509. path = os.path.join(pathdir, fd)
  510. if os.path.islink(path):
  511. try:
  512. file = os.readlink(path)
  513. except FileNotFoundError:
  514. hit_enoent = True
  515. continue
  516. else:
  517. if isfile_strict(file):
  518. retlist.append(_common.popenfile(file, int(fd)))
  519. if hit_enoent:
  520. self._assert_alive()
  521. return retlist
  522. def _get_unix_sockets(self, pid):
  523. """Get UNIX sockets used by process by parsing 'pfiles' output."""
  524. # TODO: rewrite this in C (...but the damn netstat source code
  525. # does not include this part! Argh!!)
  526. cmd = ["pfiles", str(pid)]
  527. p = subprocess.Popen(cmd, stdout=subprocess.PIPE,
  528. stderr=subprocess.PIPE)
  529. stdout, stderr = p.communicate()
  530. if PY3:
  531. stdout, stderr = (x.decode(sys.stdout.encoding)
  532. for x in (stdout, stderr))
  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. raise RuntimeError("%r command error\n%s" % (cmd, stderr))
  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 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('%s/%s' % (self._procfs_path, self.pid))
  563. # UNIX sockets
  564. if kind in ('all', 'unix'):
  565. ret.extend([_common.pconn(*conn) for conn in
  566. self._get_unix_sockets(self.pid)])
  567. return ret
  568. nt_mmap_grouped = namedtuple('mmap', 'path rss anon locked')
  569. nt_mmap_ext = namedtuple('mmap', 'addr perms path rss anon locked')
  570. @wrap_exceptions
  571. def memory_maps(self):
  572. def toaddr(start, end):
  573. return '%s-%s' % (hex(start)[2:].strip('L'),
  574. hex(end)[2:].strip('L'))
  575. procfs_path = self._procfs_path
  576. retlist = []
  577. try:
  578. rawlist = cext.proc_memory_maps(self.pid, procfs_path)
  579. except OSError as err:
  580. if err.errno == errno.EOVERFLOW and not IS_64_BIT:
  581. # We may get here if we attempt to query a 64bit process
  582. # with a 32bit python.
  583. # Error originates from read() and also tools like "cat"
  584. # fail in the same way (!).
  585. # Since there simply is no way to determine CPU times we
  586. # return 0.0 as a fallback. See:
  587. # https://github.com/giampaolo/psutil/issues/857
  588. return []
  589. else:
  590. raise
  591. hit_enoent = False
  592. for item in rawlist:
  593. addr, addrsize, perm, name, rss, anon, locked = item
  594. addr = toaddr(addr, addrsize)
  595. if not name.startswith('['):
  596. try:
  597. name = os.readlink(
  598. '%s/%s/path/%s' % (procfs_path, self.pid, name))
  599. except OSError as err:
  600. if err.errno == errno.ENOENT:
  601. # sometimes the link may not be resolved by
  602. # readlink() even if it exists (ls shows it).
  603. # If that's the case we just return the
  604. # unresolved link path.
  605. # This seems an incosistency with /proc similar
  606. # to: http://goo.gl/55XgO
  607. name = '%s/%s/path/%s' % (procfs_path, self.pid, name)
  608. hit_enoent = True
  609. else:
  610. raise
  611. retlist.append((addr, perm, name, rss, anon, locked))
  612. if hit_enoent:
  613. self._assert_alive()
  614. return retlist
  615. @wrap_exceptions
  616. def num_fds(self):
  617. return len(os.listdir("%s/%s/fd" % (self._procfs_path, self.pid)))
  618. @wrap_exceptions
  619. def num_ctx_switches(self):
  620. return _common.pctxsw(
  621. *cext.proc_num_ctx_switches(self.pid, self._procfs_path))
  622. @wrap_exceptions
  623. def wait(self, timeout=None):
  624. return _psposix.wait_pid(self.pid, timeout, self._name)