_pswindows.py 37 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128
  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. """Windows platform implementation."""
  5. import contextlib
  6. import errno
  7. import functools
  8. import os
  9. import signal
  10. import sys
  11. import time
  12. from collections import namedtuple
  13. from . import _common
  14. from ._common import ENCODING
  15. from ._common import ENCODING_ERRS
  16. from ._common import AccessDenied
  17. from ._common import NoSuchProcess
  18. from ._common import TimeoutExpired
  19. from ._common import conn_tmap
  20. from ._common import conn_to_ntuple
  21. from ._common import debug
  22. from ._common import isfile_strict
  23. from ._common import memoize
  24. from ._common import memoize_when_activated
  25. from ._common import parse_environ_block
  26. from ._common import usage_percent
  27. from ._compat import PY3
  28. from ._compat import long
  29. from ._compat import lru_cache
  30. from ._compat import range
  31. from ._compat import unicode
  32. from ._psutil_windows import ABOVE_NORMAL_PRIORITY_CLASS
  33. from ._psutil_windows import BELOW_NORMAL_PRIORITY_CLASS
  34. from ._psutil_windows import HIGH_PRIORITY_CLASS
  35. from ._psutil_windows import IDLE_PRIORITY_CLASS
  36. from ._psutil_windows import NORMAL_PRIORITY_CLASS
  37. from ._psutil_windows import REALTIME_PRIORITY_CLASS
  38. try:
  39. from . import _psutil_windows as cext
  40. except ImportError as err:
  41. if str(err).lower().startswith("dll load failed") and \
  42. sys.getwindowsversion()[0] < 6:
  43. # We may get here if:
  44. # 1) we are on an old Windows version
  45. # 2) psutil was installed via pip + wheel
  46. # See: https://github.com/giampaolo/psutil/issues/811
  47. msg = "this Windows version is too old (< Windows Vista); "
  48. msg += "psutil 3.4.2 is the latest version which supports Windows "
  49. msg += "2000, XP and 2003 server"
  50. raise RuntimeError(msg)
  51. else:
  52. raise
  53. if PY3:
  54. import enum
  55. else:
  56. enum = None
  57. # process priority constants, import from __init__.py:
  58. # http://msdn.microsoft.com/en-us/library/ms686219(v=vs.85).aspx
  59. __extra__all__ = [
  60. "win_service_iter", "win_service_get",
  61. # Process priority
  62. "ABOVE_NORMAL_PRIORITY_CLASS", "BELOW_NORMAL_PRIORITY_CLASS",
  63. "HIGH_PRIORITY_CLASS", "IDLE_PRIORITY_CLASS", "NORMAL_PRIORITY_CLASS",
  64. "REALTIME_PRIORITY_CLASS",
  65. # IO priority
  66. "IOPRIO_VERYLOW", "IOPRIO_LOW", "IOPRIO_NORMAL", "IOPRIO_HIGH",
  67. # others
  68. "CONN_DELETE_TCB", "AF_LINK",
  69. ]
  70. # =====================================================================
  71. # --- globals
  72. # =====================================================================
  73. CONN_DELETE_TCB = "DELETE_TCB"
  74. ERROR_PARTIAL_COPY = 299
  75. PYPY = '__pypy__' in sys.builtin_module_names
  76. if enum is None:
  77. AF_LINK = -1
  78. else:
  79. AddressFamily = enum.IntEnum('AddressFamily', {'AF_LINK': -1})
  80. AF_LINK = AddressFamily.AF_LINK
  81. TCP_STATUSES = {
  82. cext.MIB_TCP_STATE_ESTAB: _common.CONN_ESTABLISHED,
  83. cext.MIB_TCP_STATE_SYN_SENT: _common.CONN_SYN_SENT,
  84. cext.MIB_TCP_STATE_SYN_RCVD: _common.CONN_SYN_RECV,
  85. cext.MIB_TCP_STATE_FIN_WAIT1: _common.CONN_FIN_WAIT1,
  86. cext.MIB_TCP_STATE_FIN_WAIT2: _common.CONN_FIN_WAIT2,
  87. cext.MIB_TCP_STATE_TIME_WAIT: _common.CONN_TIME_WAIT,
  88. cext.MIB_TCP_STATE_CLOSED: _common.CONN_CLOSE,
  89. cext.MIB_TCP_STATE_CLOSE_WAIT: _common.CONN_CLOSE_WAIT,
  90. cext.MIB_TCP_STATE_LAST_ACK: _common.CONN_LAST_ACK,
  91. cext.MIB_TCP_STATE_LISTEN: _common.CONN_LISTEN,
  92. cext.MIB_TCP_STATE_CLOSING: _common.CONN_CLOSING,
  93. cext.MIB_TCP_STATE_DELETE_TCB: CONN_DELETE_TCB,
  94. cext.PSUTIL_CONN_NONE: _common.CONN_NONE,
  95. }
  96. if enum is not None:
  97. class Priority(enum.IntEnum):
  98. ABOVE_NORMAL_PRIORITY_CLASS = ABOVE_NORMAL_PRIORITY_CLASS
  99. BELOW_NORMAL_PRIORITY_CLASS = BELOW_NORMAL_PRIORITY_CLASS
  100. HIGH_PRIORITY_CLASS = HIGH_PRIORITY_CLASS
  101. IDLE_PRIORITY_CLASS = IDLE_PRIORITY_CLASS
  102. NORMAL_PRIORITY_CLASS = NORMAL_PRIORITY_CLASS
  103. REALTIME_PRIORITY_CLASS = REALTIME_PRIORITY_CLASS
  104. globals().update(Priority.__members__)
  105. if enum is None:
  106. IOPRIO_VERYLOW = 0
  107. IOPRIO_LOW = 1
  108. IOPRIO_NORMAL = 2
  109. IOPRIO_HIGH = 3
  110. else:
  111. class IOPriority(enum.IntEnum):
  112. IOPRIO_VERYLOW = 0
  113. IOPRIO_LOW = 1
  114. IOPRIO_NORMAL = 2
  115. IOPRIO_HIGH = 3
  116. globals().update(IOPriority.__members__)
  117. pinfo_map = dict(
  118. num_handles=0,
  119. ctx_switches=1,
  120. user_time=2,
  121. kernel_time=3,
  122. create_time=4,
  123. num_threads=5,
  124. io_rcount=6,
  125. io_wcount=7,
  126. io_rbytes=8,
  127. io_wbytes=9,
  128. io_count_others=10,
  129. io_bytes_others=11,
  130. num_page_faults=12,
  131. peak_wset=13,
  132. wset=14,
  133. peak_paged_pool=15,
  134. paged_pool=16,
  135. peak_non_paged_pool=17,
  136. non_paged_pool=18,
  137. pagefile=19,
  138. peak_pagefile=20,
  139. mem_private=21,
  140. )
  141. # =====================================================================
  142. # --- named tuples
  143. # =====================================================================
  144. # psutil.cpu_times()
  145. scputimes = namedtuple('scputimes',
  146. ['user', 'system', 'idle', 'interrupt', 'dpc'])
  147. # psutil.virtual_memory()
  148. svmem = namedtuple('svmem', ['total', 'available', 'percent', 'used', 'free'])
  149. # psutil.Process.memory_info()
  150. pmem = namedtuple(
  151. 'pmem', ['rss', 'vms',
  152. 'num_page_faults', 'peak_wset', 'wset', 'peak_paged_pool',
  153. 'paged_pool', 'peak_nonpaged_pool', 'nonpaged_pool',
  154. 'pagefile', 'peak_pagefile', 'private'])
  155. # psutil.Process.memory_full_info()
  156. pfullmem = namedtuple('pfullmem', pmem._fields + ('uss', ))
  157. # psutil.Process.memory_maps(grouped=True)
  158. pmmap_grouped = namedtuple('pmmap_grouped', ['path', 'rss'])
  159. # psutil.Process.memory_maps(grouped=False)
  160. pmmap_ext = namedtuple(
  161. 'pmmap_ext', 'addr perms ' + ' '.join(pmmap_grouped._fields))
  162. # psutil.Process.io_counters()
  163. pio = namedtuple('pio', ['read_count', 'write_count',
  164. 'read_bytes', 'write_bytes',
  165. 'other_count', 'other_bytes'])
  166. # =====================================================================
  167. # --- utils
  168. # =====================================================================
  169. @lru_cache(maxsize=512)
  170. def convert_dos_path(s):
  171. r"""Convert paths using native DOS format like:
  172. "\Device\HarddiskVolume1\Windows\systemew\file.txt"
  173. into:
  174. "C:\Windows\systemew\file.txt".
  175. """
  176. rawdrive = '\\'.join(s.split('\\')[:3])
  177. driveletter = cext.QueryDosDevice(rawdrive)
  178. remainder = s[len(rawdrive):]
  179. return os.path.join(driveletter, remainder)
  180. def py2_strencode(s):
  181. """Encode a unicode string to a byte string by using the default fs
  182. encoding + "replace" error handler.
  183. """
  184. if PY3:
  185. return s
  186. else:
  187. if isinstance(s, str):
  188. return s
  189. else:
  190. return s.encode(ENCODING, ENCODING_ERRS)
  191. @memoize
  192. def getpagesize():
  193. return cext.getpagesize()
  194. # =====================================================================
  195. # --- memory
  196. # =====================================================================
  197. def virtual_memory():
  198. """System virtual memory as a namedtuple."""
  199. mem = cext.virtual_mem()
  200. totphys, availphys, totsys, availsys = mem
  201. #
  202. total = totphys
  203. avail = availphys
  204. free = availphys
  205. used = total - avail
  206. percent = usage_percent((total - avail), total, round_=1)
  207. return svmem(total, avail, percent, used, free)
  208. def swap_memory():
  209. """Swap system memory as a (total, used, free, sin, sout) tuple."""
  210. mem = cext.virtual_mem()
  211. total_phys = mem[0]
  212. total_system = mem[2]
  213. # system memory (commit total/limit) is the sum of physical and swap
  214. # thus physical memory values need to be subtracted to get swap values
  215. total = total_system - total_phys
  216. # commit total is incremented immediately (decrementing free_system)
  217. # while the corresponding free physical value is not decremented until
  218. # pages are accessed, so we can't use free system memory for swap.
  219. # instead, we calculate page file usage based on performance counter
  220. if (total > 0):
  221. percentswap = cext.swap_percent()
  222. used = int(0.01 * percentswap * total)
  223. else:
  224. percentswap = 0.0
  225. used = 0
  226. free = total - used
  227. percent = round(percentswap, 1)
  228. return _common.sswap(total, used, free, percent, 0, 0)
  229. # =====================================================================
  230. # --- disk
  231. # =====================================================================
  232. disk_io_counters = cext.disk_io_counters
  233. def disk_usage(path):
  234. """Return disk usage associated with path."""
  235. if PY3 and isinstance(path, bytes):
  236. # XXX: do we want to use "strict"? Probably yes, in order
  237. # to fail immediately. After all we are accepting input here...
  238. path = path.decode(ENCODING, errors="strict")
  239. total, free = cext.disk_usage(path)
  240. used = total - free
  241. percent = usage_percent(used, total, round_=1)
  242. return _common.sdiskusage(total, used, free, percent)
  243. def disk_partitions(all):
  244. """Return disk partitions."""
  245. rawlist = cext.disk_partitions(all)
  246. return [_common.sdiskpart(*x) for x in rawlist]
  247. # =====================================================================
  248. # --- CPU
  249. # =====================================================================
  250. def cpu_times():
  251. """Return system CPU times as a named tuple."""
  252. user, system, idle = cext.cpu_times()
  253. # Internally, GetSystemTimes() is used, and it doesn't return
  254. # interrupt and dpc times. cext.per_cpu_times() does, so we
  255. # rely on it to get those only.
  256. percpu_summed = scputimes(*[sum(n) for n in zip(*cext.per_cpu_times())])
  257. return scputimes(user, system, idle,
  258. percpu_summed.interrupt, percpu_summed.dpc)
  259. def per_cpu_times():
  260. """Return system per-CPU times as a list of named tuples."""
  261. ret = []
  262. for user, system, idle, interrupt, dpc in cext.per_cpu_times():
  263. item = scputimes(user, system, idle, interrupt, dpc)
  264. ret.append(item)
  265. return ret
  266. def cpu_count_logical():
  267. """Return the number of logical CPUs in the system."""
  268. return cext.cpu_count_logical()
  269. def cpu_count_cores():
  270. """Return the number of CPU cores in the system."""
  271. return cext.cpu_count_cores()
  272. def cpu_stats():
  273. """Return CPU statistics."""
  274. ctx_switches, interrupts, dpcs, syscalls = cext.cpu_stats()
  275. soft_interrupts = 0
  276. return _common.scpustats(ctx_switches, interrupts, soft_interrupts,
  277. syscalls)
  278. def cpu_freq():
  279. """Return CPU frequency.
  280. On Windows per-cpu frequency is not supported.
  281. """
  282. curr, max_ = cext.cpu_freq()
  283. min_ = 0.0
  284. return [_common.scpufreq(float(curr), min_, float(max_))]
  285. _loadavg_inititialized = False
  286. def getloadavg():
  287. """Return the number of processes in the system run queue averaged
  288. over the last 1, 5, and 15 minutes respectively as a tuple.
  289. """
  290. global _loadavg_inititialized
  291. if not _loadavg_inititialized:
  292. cext.init_loadavg_counter()
  293. _loadavg_inititialized = True
  294. # Drop to 2 decimal points which is what Linux does
  295. raw_loads = cext.getloadavg()
  296. return tuple([round(load, 2) for load in raw_loads])
  297. # =====================================================================
  298. # --- network
  299. # =====================================================================
  300. def net_connections(kind, _pid=-1):
  301. """Return socket connections. If pid == -1 return system-wide
  302. connections (as opposed to connections opened by one process only).
  303. """
  304. if kind not in conn_tmap:
  305. raise ValueError("invalid %r kind argument; choose between %s"
  306. % (kind, ', '.join([repr(x) for x in conn_tmap])))
  307. families, types = conn_tmap[kind]
  308. rawlist = cext.net_connections(_pid, families, types)
  309. ret = set()
  310. for item in rawlist:
  311. fd, fam, type, laddr, raddr, status, pid = item
  312. nt = conn_to_ntuple(fd, fam, type, laddr, raddr, status, TCP_STATUSES,
  313. pid=pid if _pid == -1 else None)
  314. ret.add(nt)
  315. return list(ret)
  316. def net_if_stats():
  317. """Get NIC stats (isup, duplex, speed, mtu)."""
  318. ret = {}
  319. rawdict = cext.net_if_stats()
  320. for name, items in rawdict.items():
  321. if not PY3:
  322. assert isinstance(name, unicode), type(name)
  323. name = py2_strencode(name)
  324. isup, duplex, speed, mtu = items
  325. if hasattr(_common, 'NicDuplex'):
  326. duplex = _common.NicDuplex(duplex)
  327. ret[name] = _common.snicstats(isup, duplex, speed, mtu, '')
  328. return ret
  329. def net_io_counters():
  330. """Return network I/O statistics for every network interface
  331. installed on the system as a dict of raw tuples.
  332. """
  333. ret = cext.net_io_counters()
  334. return dict([(py2_strencode(k), v) for k, v in ret.items()])
  335. def net_if_addrs():
  336. """Return the addresses associated to each NIC."""
  337. ret = []
  338. for items in cext.net_if_addrs():
  339. items = list(items)
  340. items[0] = py2_strencode(items[0])
  341. ret.append(items)
  342. return ret
  343. # =====================================================================
  344. # --- sensors
  345. # =====================================================================
  346. def sensors_battery():
  347. """Return battery information."""
  348. # For constants meaning see:
  349. # https://msdn.microsoft.com/en-us/library/windows/desktop/
  350. # aa373232(v=vs.85).aspx
  351. acline_status, flags, percent, secsleft = cext.sensors_battery()
  352. power_plugged = acline_status == 1
  353. no_battery = bool(flags & 128)
  354. charging = bool(flags & 8)
  355. if no_battery:
  356. return None
  357. if power_plugged or charging:
  358. secsleft = _common.POWER_TIME_UNLIMITED
  359. elif secsleft == -1:
  360. secsleft = _common.POWER_TIME_UNKNOWN
  361. return _common.sbattery(percent, secsleft, power_plugged)
  362. # =====================================================================
  363. # --- other system functions
  364. # =====================================================================
  365. _last_btime = 0
  366. def boot_time():
  367. """The system boot time expressed in seconds since the epoch."""
  368. # This dirty hack is to adjust the precision of the returned
  369. # value which may have a 1 second fluctuation, see:
  370. # https://github.com/giampaolo/psutil/issues/1007
  371. global _last_btime
  372. ret = float(cext.boot_time())
  373. if abs(ret - _last_btime) <= 1:
  374. return _last_btime
  375. else:
  376. _last_btime = ret
  377. return ret
  378. def users():
  379. """Return currently connected users as a list of namedtuples."""
  380. retlist = []
  381. rawlist = cext.users()
  382. for item in rawlist:
  383. user, hostname, tstamp = item
  384. user = py2_strencode(user)
  385. nt = _common.suser(user, None, hostname, tstamp, None)
  386. retlist.append(nt)
  387. return retlist
  388. # =====================================================================
  389. # --- Windows services
  390. # =====================================================================
  391. def win_service_iter():
  392. """Yields a list of WindowsService instances."""
  393. for name, display_name in cext.winservice_enumerate():
  394. yield WindowsService(py2_strencode(name), py2_strencode(display_name))
  395. def win_service_get(name):
  396. """Open a Windows service and return it as a WindowsService instance."""
  397. service = WindowsService(name, None)
  398. service._display_name = service._query_config()['display_name']
  399. return service
  400. class WindowsService:
  401. """Represents an installed Windows service."""
  402. def __init__(self, name, display_name):
  403. self._name = name
  404. self._display_name = display_name
  405. def __str__(self):
  406. details = "(name=%r, display_name=%r)" % (
  407. self._name, self._display_name)
  408. return "%s%s" % (self.__class__.__name__, details)
  409. def __repr__(self):
  410. return "<%s at %s>" % (self.__str__(), id(self))
  411. def __eq__(self, other):
  412. # Test for equality with another WindosService object based
  413. # on name.
  414. if not isinstance(other, WindowsService):
  415. return NotImplemented
  416. return self._name == other._name
  417. def __ne__(self, other):
  418. return not self == other
  419. def _query_config(self):
  420. with self._wrap_exceptions():
  421. display_name, binpath, username, start_type = \
  422. cext.winservice_query_config(self._name)
  423. # XXX - update _self.display_name?
  424. return dict(
  425. display_name=py2_strencode(display_name),
  426. binpath=py2_strencode(binpath),
  427. username=py2_strencode(username),
  428. start_type=py2_strencode(start_type))
  429. def _query_status(self):
  430. with self._wrap_exceptions():
  431. status, pid = cext.winservice_query_status(self._name)
  432. if pid == 0:
  433. pid = None
  434. return dict(status=status, pid=pid)
  435. @contextlib.contextmanager
  436. def _wrap_exceptions(self):
  437. """Ctx manager which translates bare OSError and WindowsError
  438. exceptions into NoSuchProcess and AccessDenied.
  439. """
  440. try:
  441. yield
  442. except OSError as err:
  443. if is_permission_err(err):
  444. raise AccessDenied(
  445. pid=None, name=self._name,
  446. msg="service %r is not querable (not enough privileges)" %
  447. self._name)
  448. elif err.winerror in (cext.ERROR_INVALID_NAME,
  449. cext.ERROR_SERVICE_DOES_NOT_EXIST):
  450. raise NoSuchProcess(
  451. pid=None, name=self._name,
  452. msg="service %r does not exist)" % self._name)
  453. else:
  454. raise
  455. # config query
  456. def name(self):
  457. """The service name. This string is how a service is referenced
  458. and can be passed to win_service_get() to get a new
  459. WindowsService instance.
  460. """
  461. return self._name
  462. def display_name(self):
  463. """The service display name. The value is cached when this class
  464. is instantiated.
  465. """
  466. return self._display_name
  467. def binpath(self):
  468. """The fully qualified path to the service binary/exe file as
  469. a string, including command line arguments.
  470. """
  471. return self._query_config()['binpath']
  472. def username(self):
  473. """The name of the user that owns this service."""
  474. return self._query_config()['username']
  475. def start_type(self):
  476. """A string which can either be "automatic", "manual" or
  477. "disabled".
  478. """
  479. return self._query_config()['start_type']
  480. # status query
  481. def pid(self):
  482. """The process PID, if any, else None. This can be passed
  483. to Process class to control the service's process.
  484. """
  485. return self._query_status()['pid']
  486. def status(self):
  487. """Service status as a string."""
  488. return self._query_status()['status']
  489. def description(self):
  490. """Service long description."""
  491. return py2_strencode(cext.winservice_query_descr(self.name()))
  492. # utils
  493. def as_dict(self):
  494. """Utility method retrieving all the information above as a
  495. dictionary.
  496. """
  497. d = self._query_config()
  498. d.update(self._query_status())
  499. d['name'] = self.name()
  500. d['display_name'] = self.display_name()
  501. d['description'] = self.description()
  502. return d
  503. # actions
  504. # XXX: the necessary C bindings for start() and stop() are
  505. # implemented but for now I prefer not to expose them.
  506. # I may change my mind in the future. Reasons:
  507. # - they require Administrator privileges
  508. # - can't implement a timeout for stop() (unless by using a thread,
  509. # which sucks)
  510. # - would require adding ServiceAlreadyStarted and
  511. # ServiceAlreadyStopped exceptions, adding two new APIs.
  512. # - we might also want to have modify(), which would basically mean
  513. # rewriting win32serviceutil.ChangeServiceConfig, which involves a
  514. # lot of stuff (and API constants which would pollute the API), see:
  515. # http://pyxr.sourceforge.net/PyXR/c/python24/lib/site-packages/
  516. # win32/lib/win32serviceutil.py.html#0175
  517. # - psutil is typically about "read only" monitoring stuff;
  518. # win_service_* APIs should only be used to retrieve a service and
  519. # check whether it's running
  520. # def start(self, timeout=None):
  521. # with self._wrap_exceptions():
  522. # cext.winservice_start(self.name())
  523. # if timeout:
  524. # giveup_at = time.time() + timeout
  525. # while True:
  526. # if self.status() == "running":
  527. # return
  528. # else:
  529. # if time.time() > giveup_at:
  530. # raise TimeoutExpired(timeout)
  531. # else:
  532. # time.sleep(.1)
  533. # def stop(self):
  534. # # Note: timeout is not implemented because it's just not
  535. # # possible, see:
  536. # # http://stackoverflow.com/questions/11973228/
  537. # with self._wrap_exceptions():
  538. # return cext.winservice_stop(self.name())
  539. # =====================================================================
  540. # --- processes
  541. # =====================================================================
  542. pids = cext.pids
  543. pid_exists = cext.pid_exists
  544. ppid_map = cext.ppid_map # used internally by Process.children()
  545. def is_permission_err(exc):
  546. """Return True if this is a permission error."""
  547. assert isinstance(exc, OSError), exc
  548. # On Python 2 OSError doesn't always have 'winerror'. Sometimes
  549. # it does, in which case the original exception was WindowsError
  550. # (which is a subclass of OSError).
  551. return exc.errno in (errno.EPERM, errno.EACCES) or \
  552. getattr(exc, "winerror", -1) in (cext.ERROR_ACCESS_DENIED,
  553. cext.ERROR_PRIVILEGE_NOT_HELD)
  554. def convert_oserror(exc, pid=None, name=None):
  555. """Convert OSError into NoSuchProcess or AccessDenied."""
  556. assert isinstance(exc, OSError), exc
  557. if is_permission_err(exc):
  558. return AccessDenied(pid=pid, name=name)
  559. if exc.errno == errno.ESRCH:
  560. return NoSuchProcess(pid=pid, name=name)
  561. raise exc
  562. def wrap_exceptions(fun):
  563. """Decorator which converts OSError into NoSuchProcess or AccessDenied."""
  564. @functools.wraps(fun)
  565. def wrapper(self, *args, **kwargs):
  566. try:
  567. return fun(self, *args, **kwargs)
  568. except OSError as err:
  569. raise convert_oserror(err, pid=self.pid, name=self._name)
  570. return wrapper
  571. def retry_error_partial_copy(fun):
  572. """Workaround for https://github.com/giampaolo/psutil/issues/875.
  573. See: https://stackoverflow.com/questions/4457745#4457745.
  574. """
  575. @functools.wraps(fun)
  576. def wrapper(self, *args, **kwargs):
  577. delay = 0.0001
  578. times = 33
  579. for _ in range(times): # retries for roughly 1 second
  580. try:
  581. return fun(self, *args, **kwargs)
  582. except WindowsError as _:
  583. err = _
  584. if err.winerror == ERROR_PARTIAL_COPY:
  585. time.sleep(delay)
  586. delay = min(delay * 2, 0.04)
  587. continue
  588. else:
  589. raise
  590. else:
  591. msg = (
  592. "{} retried {} times, converted to AccessDenied as it's "
  593. "still returning {}".format(fun, times, err)
  594. )
  595. raise AccessDenied(pid=self.pid, name=self._name, msg=msg)
  596. return wrapper
  597. class Process:
  598. """Wrapper class around underlying C implementation."""
  599. __slots__ = ["pid", "_name", "_ppid", "_cache"]
  600. def __init__(self, pid):
  601. self.pid = pid
  602. self._name = None
  603. self._ppid = None
  604. # --- oneshot() stuff
  605. def oneshot_enter(self):
  606. self._proc_info.cache_activate(self)
  607. self.exe.cache_activate(self)
  608. def oneshot_exit(self):
  609. self._proc_info.cache_deactivate(self)
  610. self.exe.cache_deactivate(self)
  611. @memoize_when_activated
  612. def _proc_info(self):
  613. """Return multiple information about this process as a
  614. raw tuple.
  615. """
  616. ret = cext.proc_info(self.pid)
  617. assert len(ret) == len(pinfo_map)
  618. return ret
  619. def name(self):
  620. """Return process name, which on Windows is always the final
  621. part of the executable.
  622. """
  623. # This is how PIDs 0 and 4 are always represented in taskmgr
  624. # and process-hacker.
  625. if self.pid == 0:
  626. return "System Idle Process"
  627. if self.pid == 4:
  628. return "System"
  629. return os.path.basename(self.exe())
  630. @wrap_exceptions
  631. @memoize_when_activated
  632. def exe(self):
  633. if PYPY:
  634. try:
  635. exe = cext.proc_exe(self.pid)
  636. except WindowsError as err:
  637. # 24 = ERROR_TOO_MANY_OPEN_FILES. Not sure why this happens
  638. # (perhaps PyPy's JIT delaying garbage collection of files?).
  639. if err.errno == 24:
  640. debug("%r translated into AccessDenied" % err)
  641. raise AccessDenied(self.pid, self._name)
  642. raise
  643. else:
  644. exe = cext.proc_exe(self.pid)
  645. if not PY3:
  646. exe = py2_strencode(exe)
  647. if exe.startswith('\\'):
  648. return convert_dos_path(exe)
  649. return exe # May be "Registry", "MemCompression", ...
  650. @wrap_exceptions
  651. @retry_error_partial_copy
  652. def cmdline(self):
  653. if cext.WINVER >= cext.WINDOWS_8_1:
  654. # PEB method detects cmdline changes but requires more
  655. # privileges: https://github.com/giampaolo/psutil/pull/1398
  656. try:
  657. ret = cext.proc_cmdline(self.pid, use_peb=True)
  658. except OSError as err:
  659. if is_permission_err(err):
  660. ret = cext.proc_cmdline(self.pid, use_peb=False)
  661. else:
  662. raise
  663. else:
  664. ret = cext.proc_cmdline(self.pid, use_peb=True)
  665. if PY3:
  666. return ret
  667. else:
  668. return [py2_strencode(s) for s in ret]
  669. @wrap_exceptions
  670. @retry_error_partial_copy
  671. def environ(self):
  672. ustr = cext.proc_environ(self.pid)
  673. if ustr and not PY3:
  674. assert isinstance(ustr, unicode), type(ustr)
  675. return parse_environ_block(py2_strencode(ustr))
  676. def ppid(self):
  677. try:
  678. return ppid_map()[self.pid]
  679. except KeyError:
  680. raise NoSuchProcess(self.pid, self._name)
  681. def _get_raw_meminfo(self):
  682. try:
  683. return cext.proc_memory_info(self.pid)
  684. except OSError as err:
  685. if is_permission_err(err):
  686. # TODO: the C ext can probably be refactored in order
  687. # to get this from cext.proc_info()
  688. info = self._proc_info()
  689. return (
  690. info[pinfo_map['num_page_faults']],
  691. info[pinfo_map['peak_wset']],
  692. info[pinfo_map['wset']],
  693. info[pinfo_map['peak_paged_pool']],
  694. info[pinfo_map['paged_pool']],
  695. info[pinfo_map['peak_non_paged_pool']],
  696. info[pinfo_map['non_paged_pool']],
  697. info[pinfo_map['pagefile']],
  698. info[pinfo_map['peak_pagefile']],
  699. info[pinfo_map['mem_private']],
  700. )
  701. raise
  702. @wrap_exceptions
  703. def memory_info(self):
  704. # on Windows RSS == WorkingSetSize and VSM == PagefileUsage.
  705. # Underlying C function returns fields of PROCESS_MEMORY_COUNTERS
  706. # struct.
  707. t = self._get_raw_meminfo()
  708. rss = t[2] # wset
  709. vms = t[7] # pagefile
  710. return pmem(*(rss, vms) + t)
  711. @wrap_exceptions
  712. def memory_full_info(self):
  713. basic_mem = self.memory_info()
  714. uss = cext.proc_memory_uss(self.pid)
  715. uss *= getpagesize()
  716. return pfullmem(*basic_mem + (uss, ))
  717. def memory_maps(self):
  718. try:
  719. raw = cext.proc_memory_maps(self.pid)
  720. except OSError as err:
  721. # XXX - can't use wrap_exceptions decorator as we're
  722. # returning a generator; probably needs refactoring.
  723. raise convert_oserror(err, self.pid, self._name)
  724. else:
  725. for addr, perm, path, rss in raw:
  726. path = convert_dos_path(path)
  727. if not PY3:
  728. path = py2_strencode(path)
  729. addr = hex(addr)
  730. yield (addr, perm, path, rss)
  731. @wrap_exceptions
  732. def kill(self):
  733. return cext.proc_kill(self.pid)
  734. @wrap_exceptions
  735. def send_signal(self, sig):
  736. if sig == signal.SIGTERM:
  737. cext.proc_kill(self.pid)
  738. # py >= 2.7
  739. elif sig in (getattr(signal, "CTRL_C_EVENT", object()),
  740. getattr(signal, "CTRL_BREAK_EVENT", object())):
  741. os.kill(self.pid, sig)
  742. else:
  743. msg = (
  744. "only SIGTERM, CTRL_C_EVENT and CTRL_BREAK_EVENT signals "
  745. "are supported on Windows"
  746. )
  747. raise ValueError(msg)
  748. @wrap_exceptions
  749. def wait(self, timeout=None):
  750. if timeout is None:
  751. cext_timeout = cext.INFINITE
  752. else:
  753. # WaitForSingleObject() expects time in milliseconds.
  754. cext_timeout = int(timeout * 1000)
  755. timer = getattr(time, 'monotonic', time.time)
  756. stop_at = timer() + timeout if timeout is not None else None
  757. try:
  758. # Exit code is supposed to come from GetExitCodeProcess().
  759. # May also be None if OpenProcess() failed with
  760. # ERROR_INVALID_PARAMETER, meaning PID is already gone.
  761. exit_code = cext.proc_wait(self.pid, cext_timeout)
  762. except cext.TimeoutExpired:
  763. # WaitForSingleObject() returned WAIT_TIMEOUT. Just raise.
  764. raise TimeoutExpired(timeout, self.pid, self._name)
  765. except cext.TimeoutAbandoned:
  766. # WaitForSingleObject() returned WAIT_ABANDONED, see:
  767. # https://github.com/giampaolo/psutil/issues/1224
  768. # We'll just rely on the internal polling and return None
  769. # when the PID disappears. Subprocess module does the same
  770. # (return None):
  771. # https://github.com/python/cpython/blob/
  772. # be50a7b627d0aa37e08fa8e2d5568891f19903ce/
  773. # Lib/subprocess.py#L1193-L1194
  774. exit_code = None
  775. # At this point WaitForSingleObject() returned WAIT_OBJECT_0,
  776. # meaning the process is gone. Stupidly there are cases where
  777. # its PID may still stick around so we do a further internal
  778. # polling.
  779. delay = 0.0001
  780. while True:
  781. if not pid_exists(self.pid):
  782. return exit_code
  783. if stop_at and timer() >= stop_at:
  784. raise TimeoutExpired(timeout, pid=self.pid, name=self._name)
  785. time.sleep(delay)
  786. delay = min(delay * 2, 0.04) # incremental delay
  787. @wrap_exceptions
  788. def username(self):
  789. if self.pid in (0, 4):
  790. return 'NT AUTHORITY\\SYSTEM'
  791. domain, user = cext.proc_username(self.pid)
  792. return py2_strencode(domain) + '\\' + py2_strencode(user)
  793. @wrap_exceptions
  794. def create_time(self):
  795. # Note: proc_times() not put under oneshot() 'cause create_time()
  796. # is already cached by the main Process class.
  797. try:
  798. user, system, created = cext.proc_times(self.pid)
  799. return created
  800. except OSError as err:
  801. if is_permission_err(err):
  802. return self._proc_info()[pinfo_map['create_time']]
  803. raise
  804. @wrap_exceptions
  805. def num_threads(self):
  806. return self._proc_info()[pinfo_map['num_threads']]
  807. @wrap_exceptions
  808. def threads(self):
  809. rawlist = cext.proc_threads(self.pid)
  810. retlist = []
  811. for thread_id, utime, stime in rawlist:
  812. ntuple = _common.pthread(thread_id, utime, stime)
  813. retlist.append(ntuple)
  814. return retlist
  815. @wrap_exceptions
  816. def cpu_times(self):
  817. try:
  818. user, system, created = cext.proc_times(self.pid)
  819. except OSError as err:
  820. if not is_permission_err(err):
  821. raise
  822. info = self._proc_info()
  823. user = info[pinfo_map['user_time']]
  824. system = info[pinfo_map['kernel_time']]
  825. # Children user/system times are not retrievable (set to 0).
  826. return _common.pcputimes(user, system, 0.0, 0.0)
  827. @wrap_exceptions
  828. def suspend(self):
  829. cext.proc_suspend_or_resume(self.pid, True)
  830. @wrap_exceptions
  831. def resume(self):
  832. cext.proc_suspend_or_resume(self.pid, False)
  833. @wrap_exceptions
  834. @retry_error_partial_copy
  835. def cwd(self):
  836. if self.pid in (0, 4):
  837. raise AccessDenied(self.pid, self._name)
  838. # return a normalized pathname since the native C function appends
  839. # "\\" at the and of the path
  840. path = cext.proc_cwd(self.pid)
  841. return py2_strencode(os.path.normpath(path))
  842. @wrap_exceptions
  843. def open_files(self):
  844. if self.pid in (0, 4):
  845. return []
  846. ret = set()
  847. # Filenames come in in native format like:
  848. # "\Device\HarddiskVolume1\Windows\systemew\file.txt"
  849. # Convert the first part in the corresponding drive letter
  850. # (e.g. "C:\") by using Windows's QueryDosDevice()
  851. raw_file_names = cext.proc_open_files(self.pid)
  852. for _file in raw_file_names:
  853. _file = convert_dos_path(_file)
  854. if isfile_strict(_file):
  855. if not PY3:
  856. _file = py2_strencode(_file)
  857. ntuple = _common.popenfile(_file, -1)
  858. ret.add(ntuple)
  859. return list(ret)
  860. @wrap_exceptions
  861. def connections(self, kind='inet'):
  862. return net_connections(kind, _pid=self.pid)
  863. @wrap_exceptions
  864. def nice_get(self):
  865. value = cext.proc_priority_get(self.pid)
  866. if enum is not None:
  867. value = Priority(value)
  868. return value
  869. @wrap_exceptions
  870. def nice_set(self, value):
  871. return cext.proc_priority_set(self.pid, value)
  872. @wrap_exceptions
  873. def ionice_get(self):
  874. ret = cext.proc_io_priority_get(self.pid)
  875. if enum is not None:
  876. ret = IOPriority(ret)
  877. return ret
  878. @wrap_exceptions
  879. def ionice_set(self, ioclass, value):
  880. if value:
  881. msg = "value argument not accepted on Windows"
  882. raise TypeError(msg)
  883. if ioclass not in (IOPRIO_VERYLOW, IOPRIO_LOW, IOPRIO_NORMAL,
  884. IOPRIO_HIGH):
  885. raise ValueError("%s is not a valid priority" % ioclass)
  886. cext.proc_io_priority_set(self.pid, ioclass)
  887. @wrap_exceptions
  888. def io_counters(self):
  889. try:
  890. ret = cext.proc_io_counters(self.pid)
  891. except OSError as err:
  892. if not is_permission_err(err):
  893. raise
  894. info = self._proc_info()
  895. ret = (
  896. info[pinfo_map['io_rcount']],
  897. info[pinfo_map['io_wcount']],
  898. info[pinfo_map['io_rbytes']],
  899. info[pinfo_map['io_wbytes']],
  900. info[pinfo_map['io_count_others']],
  901. info[pinfo_map['io_bytes_others']],
  902. )
  903. return pio(*ret)
  904. @wrap_exceptions
  905. def status(self):
  906. suspended = cext.proc_is_suspended(self.pid)
  907. if suspended:
  908. return _common.STATUS_STOPPED
  909. else:
  910. return _common.STATUS_RUNNING
  911. @wrap_exceptions
  912. def cpu_affinity_get(self):
  913. def from_bitmask(x):
  914. return [i for i in range(64) if (1 << i) & x]
  915. bitmask = cext.proc_cpu_affinity_get(self.pid)
  916. return from_bitmask(bitmask)
  917. @wrap_exceptions
  918. def cpu_affinity_set(self, value):
  919. def to_bitmask(ls):
  920. if not ls:
  921. raise ValueError("invalid argument %r" % ls)
  922. out = 0
  923. for b in ls:
  924. out |= 2 ** b
  925. return out
  926. # SetProcessAffinityMask() states that ERROR_INVALID_PARAMETER
  927. # is returned for an invalid CPU but this seems not to be true,
  928. # therefore we check CPUs validy beforehand.
  929. allcpus = list(range(len(per_cpu_times())))
  930. for cpu in value:
  931. if cpu not in allcpus:
  932. if not isinstance(cpu, (int, long)):
  933. raise TypeError(
  934. "invalid CPU %r; an integer is required" % cpu)
  935. else:
  936. raise ValueError("invalid CPU %r" % cpu)
  937. bitmask = to_bitmask(value)
  938. cext.proc_cpu_affinity_set(self.pid, bitmask)
  939. @wrap_exceptions
  940. def num_handles(self):
  941. try:
  942. return cext.proc_num_handles(self.pid)
  943. except OSError as err:
  944. if is_permission_err(err):
  945. return self._proc_info()[pinfo_map['num_handles']]
  946. raise
  947. @wrap_exceptions
  948. def num_ctx_switches(self):
  949. ctx_switches = self._proc_info()[pinfo_map['ctx_switches']]
  950. # only voluntary ctx switches are supported
  951. return _common.pctxsw(ctx_switches, 0)