_pssunos.py 23 KB

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