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