_pswindows.py 36 KB

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