| #!/usr/bin/env python |
| |
| # Copyright (c) 2009, Giampaolo Rodola'. All rights reserved. |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| """Windows platform implementation.""" |
| |
| import errno |
| import functools |
| import os |
| import sys |
| from collections import namedtuple |
| |
| from . import _common |
| from . import _psutil_windows as cext |
| from ._common import conn_tmap, usage_percent, isfile_strict |
| from ._common import sockfam_to_enum, socktype_to_enum |
| from ._compat import PY3, xrange, lru_cache, long |
| from ._psutil_windows import (ABOVE_NORMAL_PRIORITY_CLASS, |
| BELOW_NORMAL_PRIORITY_CLASS, |
| HIGH_PRIORITY_CLASS, |
| IDLE_PRIORITY_CLASS, |
| NORMAL_PRIORITY_CLASS, |
| REALTIME_PRIORITY_CLASS) |
| |
| if sys.version_info >= (3, 4): |
| import enum |
| else: |
| enum = None |
| |
| # process priority constants, import from __init__.py: |
| # http://msdn.microsoft.com/en-us/library/ms686219(v=vs.85).aspx |
| __extra__all__ = ["ABOVE_NORMAL_PRIORITY_CLASS", "BELOW_NORMAL_PRIORITY_CLASS", |
| "HIGH_PRIORITY_CLASS", "IDLE_PRIORITY_CLASS", |
| "NORMAL_PRIORITY_CLASS", "REALTIME_PRIORITY_CLASS", |
| "CONN_DELETE_TCB", |
| "AF_LINK", |
| ] |
| |
| # --- module level constants (gets pushed up to psutil module) |
| |
| CONN_DELETE_TCB = "DELETE_TCB" |
| WAIT_TIMEOUT = 0x00000102 # 258 in decimal |
| ACCESS_DENIED_SET = frozenset([errno.EPERM, errno.EACCES, |
| cext.ERROR_ACCESS_DENIED]) |
| if enum is None: |
| AF_LINK = -1 |
| else: |
| AddressFamily = enum.IntEnum('AddressFamily', {'AF_LINK': -1}) |
| AF_LINK = AddressFamily.AF_LINK |
| |
| TCP_STATUSES = { |
| cext.MIB_TCP_STATE_ESTAB: _common.CONN_ESTABLISHED, |
| cext.MIB_TCP_STATE_SYN_SENT: _common.CONN_SYN_SENT, |
| cext.MIB_TCP_STATE_SYN_RCVD: _common.CONN_SYN_RECV, |
| cext.MIB_TCP_STATE_FIN_WAIT1: _common.CONN_FIN_WAIT1, |
| cext.MIB_TCP_STATE_FIN_WAIT2: _common.CONN_FIN_WAIT2, |
| cext.MIB_TCP_STATE_TIME_WAIT: _common.CONN_TIME_WAIT, |
| cext.MIB_TCP_STATE_CLOSED: _common.CONN_CLOSE, |
| cext.MIB_TCP_STATE_CLOSE_WAIT: _common.CONN_CLOSE_WAIT, |
| cext.MIB_TCP_STATE_LAST_ACK: _common.CONN_LAST_ACK, |
| cext.MIB_TCP_STATE_LISTEN: _common.CONN_LISTEN, |
| cext.MIB_TCP_STATE_CLOSING: _common.CONN_CLOSING, |
| cext.MIB_TCP_STATE_DELETE_TCB: CONN_DELETE_TCB, |
| cext.PSUTIL_CONN_NONE: _common.CONN_NONE, |
| } |
| |
| if enum is not None: |
| class Priority(enum.IntEnum): |
| ABOVE_NORMAL_PRIORITY_CLASS = ABOVE_NORMAL_PRIORITY_CLASS |
| BELOW_NORMAL_PRIORITY_CLASS = BELOW_NORMAL_PRIORITY_CLASS |
| HIGH_PRIORITY_CLASS = HIGH_PRIORITY_CLASS |
| IDLE_PRIORITY_CLASS = IDLE_PRIORITY_CLASS |
| NORMAL_PRIORITY_CLASS = NORMAL_PRIORITY_CLASS |
| REALTIME_PRIORITY_CLASS = REALTIME_PRIORITY_CLASS |
| |
| globals().update(Priority.__members__) |
| |
| scputimes = namedtuple('scputimes', ['user', 'system', 'idle']) |
| svmem = namedtuple('svmem', ['total', 'available', 'percent', 'used', 'free']) |
| pextmem = namedtuple( |
| 'pextmem', ['num_page_faults', 'peak_wset', 'wset', 'peak_paged_pool', |
| 'paged_pool', 'peak_nonpaged_pool', 'nonpaged_pool', |
| 'pagefile', 'peak_pagefile', 'private']) |
| pmmap_grouped = namedtuple('pmmap_grouped', ['path', 'rss']) |
| pmmap_ext = namedtuple( |
| 'pmmap_ext', 'addr perms ' + ' '.join(pmmap_grouped._fields)) |
| ntpinfo = namedtuple( |
| 'ntpinfo', ['num_handles', 'ctx_switches', 'user_time', 'kernel_time', |
| 'create_time', 'num_threads', 'io_rcount', 'io_wcount', |
| 'io_rbytes', 'io_wbytes']) |
| |
| # set later from __init__.py |
| NoSuchProcess = None |
| AccessDenied = None |
| TimeoutExpired = None |
| |
| |
| @lru_cache(maxsize=512) |
| def _win32_QueryDosDevice(s): |
| return cext.win32_QueryDosDevice(s) |
| |
| |
| def _convert_raw_path(s): |
| # convert paths using native DOS format like: |
| # "\Device\HarddiskVolume1\Windows\systemew\file.txt" |
| # into: "C:\Windows\systemew\file.txt" |
| if PY3 and not isinstance(s, str): |
| s = s.decode('utf8') |
| rawdrive = '\\'.join(s.split('\\')[:3]) |
| driveletter = _win32_QueryDosDevice(rawdrive) |
| return os.path.join(driveletter, s[len(rawdrive):]) |
| |
| |
| # --- public functions |
| |
| |
| def virtual_memory(): |
| """System virtual memory as a namedtuple.""" |
| mem = cext.virtual_mem() |
| totphys, availphys, totpagef, availpagef, totvirt, freevirt = mem |
| # |
| total = totphys |
| avail = availphys |
| free = availphys |
| used = total - avail |
| percent = usage_percent((total - avail), total, _round=1) |
| return svmem(total, avail, percent, used, free) |
| |
| |
| def swap_memory(): |
| """Swap system memory as a (total, used, free, sin, sout) tuple.""" |
| mem = cext.virtual_mem() |
| total = mem[2] |
| free = mem[3] |
| used = total - free |
| percent = usage_percent(used, total, _round=1) |
| return _common.sswap(total, used, free, percent, 0, 0) |
| |
| |
| def disk_usage(path): |
| """Return disk usage associated with path.""" |
| try: |
| total, free = cext.disk_usage(path) |
| except WindowsError: |
| if not os.path.exists(path): |
| msg = "No such file or directory: '%s'" % path |
| raise OSError(errno.ENOENT, msg) |
| raise |
| used = total - free |
| percent = usage_percent(used, total, _round=1) |
| return _common.sdiskusage(total, used, free, percent) |
| |
| |
| def disk_partitions(all): |
| """Return disk partitions.""" |
| rawlist = cext.disk_partitions(all) |
| return [_common.sdiskpart(*x) for x in rawlist] |
| |
| |
| def cpu_times(): |
| """Return system CPU times as a named tuple.""" |
| user, system, idle = cext.cpu_times() |
| return scputimes(user, system, idle) |
| |
| |
| def per_cpu_times(): |
| """Return system per-CPU times as a list of named tuples.""" |
| ret = [] |
| for cpu_t in cext.per_cpu_times(): |
| user, system, idle = cpu_t |
| item = scputimes(user, system, idle) |
| ret.append(item) |
| return ret |
| |
| |
| def cpu_count_logical(): |
| """Return the number of logical CPUs in the system.""" |
| return cext.cpu_count_logical() |
| |
| |
| def cpu_count_physical(): |
| """Return the number of physical CPUs in the system.""" |
| return cext.cpu_count_phys() |
| |
| |
| def boot_time(): |
| """The system boot time expressed in seconds since the epoch.""" |
| return cext.boot_time() |
| |
| |
| def net_connections(kind, _pid=-1): |
| """Return socket connections. If pid == -1 return system-wide |
| connections (as opposed to connections opened by one process only). |
| """ |
| if kind not in conn_tmap: |
| raise ValueError("invalid %r kind argument; choose between %s" |
| % (kind, ', '.join([repr(x) for x in conn_tmap]))) |
| families, types = conn_tmap[kind] |
| rawlist = cext.net_connections(_pid, families, types) |
| ret = set() |
| for item in rawlist: |
| fd, fam, type, laddr, raddr, status, pid = item |
| status = TCP_STATUSES[status] |
| fam = sockfam_to_enum(fam) |
| type = socktype_to_enum(type) |
| if _pid == -1: |
| nt = _common.sconn(fd, fam, type, laddr, raddr, status, pid) |
| else: |
| nt = _common.pconn(fd, fam, type, laddr, raddr, status) |
| ret.add(nt) |
| return list(ret) |
| |
| |
| def net_if_stats(): |
| ret = cext.net_if_stats() |
| for name, items in ret.items(): |
| isup, duplex, speed, mtu = items |
| if hasattr(_common, 'NicDuplex'): |
| duplex = _common.NicDuplex(duplex) |
| ret[name] = _common.snicstats(isup, duplex, speed, mtu) |
| return ret |
| |
| |
| def users(): |
| """Return currently connected users as a list of namedtuples.""" |
| retlist = [] |
| rawlist = cext.users() |
| for item in rawlist: |
| user, hostname, tstamp = item |
| nt = _common.suser(user, None, hostname, tstamp) |
| retlist.append(nt) |
| return retlist |
| |
| |
| pids = cext.pids |
| pid_exists = cext.pid_exists |
| net_io_counters = cext.net_io_counters |
| disk_io_counters = cext.disk_io_counters |
| ppid_map = cext.ppid_map # not meant to be public |
| net_if_addrs = cext.net_if_addrs |
| |
| |
| def wrap_exceptions(fun): |
| """Decorator which translates bare OSError and WindowsError |
| exceptions into NoSuchProcess and AccessDenied. |
| """ |
| @functools.wraps(fun) |
| def wrapper(self, *args, **kwargs): |
| try: |
| return fun(self, *args, **kwargs) |
| except OSError as err: |
| # support for private module import |
| if NoSuchProcess is None or AccessDenied is None: |
| raise |
| if err.errno in ACCESS_DENIED_SET: |
| raise AccessDenied(self.pid, self._name) |
| if err.errno == errno.ESRCH: |
| raise NoSuchProcess(self.pid, self._name) |
| raise |
| return wrapper |
| |
| |
| class Process(object): |
| """Wrapper class around underlying C implementation.""" |
| |
| __slots__ = ["pid", "_name", "_ppid"] |
| |
| def __init__(self, pid): |
| self.pid = pid |
| self._name = None |
| self._ppid = None |
| |
| @wrap_exceptions |
| def name(self): |
| """Return process name, which on Windows is always the final |
| part of the executable. |
| """ |
| # This is how PIDs 0 and 4 are always represented in taskmgr |
| # and process-hacker. |
| if self.pid == 0: |
| return "System Idle Process" |
| elif self.pid == 4: |
| return "System" |
| else: |
| try: |
| # Note: this will fail with AD for most PIDs owned |
| # by another user but it's faster. |
| return os.path.basename(self.exe()) |
| except AccessDenied: |
| return cext.proc_name(self.pid) |
| |
| @wrap_exceptions |
| def exe(self): |
| # Note: os.path.exists(path) may return False even if the file |
| # is there, see: |
| # http://stackoverflow.com/questions/3112546/os-path-exists-lies |
| |
| # see https://github.com/giampaolo/psutil/issues/414 |
| # see https://github.com/giampaolo/psutil/issues/528 |
| if self.pid in (0, 4): |
| raise AccessDenied(self.pid, self._name) |
| return _convert_raw_path(cext.proc_exe(self.pid)) |
| |
| @wrap_exceptions |
| def cmdline(self): |
| return cext.proc_cmdline(self.pid) |
| |
| def ppid(self): |
| try: |
| return ppid_map()[self.pid] |
| except KeyError: |
| raise NoSuchProcess(self.pid, self._name) |
| |
| def _get_raw_meminfo(self): |
| try: |
| return cext.proc_memory_info(self.pid) |
| except OSError as err: |
| if err.errno in ACCESS_DENIED_SET: |
| # TODO: the C ext can probably be refactored in order |
| # to get this from cext.proc_info() |
| return cext.proc_memory_info_2(self.pid) |
| raise |
| |
| @wrap_exceptions |
| def memory_info(self): |
| # on Windows RSS == WorkingSetSize and VSM == PagefileUsage |
| # fields of PROCESS_MEMORY_COUNTERS struct: |
| # http://msdn.microsoft.com/en-us/library/windows/desktop/ |
| # ms684877(v=vs.85).aspx |
| t = self._get_raw_meminfo() |
| return _common.pmem(t[2], t[7]) |
| |
| @wrap_exceptions |
| def memory_info_ex(self): |
| return pextmem(*self._get_raw_meminfo()) |
| |
| def memory_maps(self): |
| try: |
| raw = cext.proc_memory_maps(self.pid) |
| except OSError as err: |
| # XXX - can't use wrap_exceptions decorator as we're |
| # returning a generator; probably needs refactoring. |
| if err.errno in ACCESS_DENIED_SET: |
| raise AccessDenied(self.pid, self._name) |
| if err.errno == errno.ESRCH: |
| raise NoSuchProcess(self.pid, self._name) |
| raise |
| else: |
| for addr, perm, path, rss in raw: |
| path = _convert_raw_path(path) |
| addr = hex(addr) |
| yield (addr, perm, path, rss) |
| |
| @wrap_exceptions |
| def kill(self): |
| return cext.proc_kill(self.pid) |
| |
| @wrap_exceptions |
| def wait(self, timeout=None): |
| if timeout is None: |
| timeout = cext.INFINITE |
| else: |
| # WaitForSingleObject() expects time in milliseconds |
| timeout = int(timeout * 1000) |
| ret = cext.proc_wait(self.pid, timeout) |
| if ret == WAIT_TIMEOUT: |
| # support for private module import |
| if TimeoutExpired is None: |
| raise RuntimeError("timeout expired") |
| raise TimeoutExpired(timeout, self.pid, self._name) |
| return ret |
| |
| @wrap_exceptions |
| def username(self): |
| if self.pid in (0, 4): |
| return 'NT AUTHORITY\\SYSTEM' |
| return cext.proc_username(self.pid) |
| |
| @wrap_exceptions |
| def create_time(self): |
| # special case for kernel process PIDs; return system boot time |
| if self.pid in (0, 4): |
| return boot_time() |
| try: |
| return cext.proc_create_time(self.pid) |
| except OSError as err: |
| if err.errno in ACCESS_DENIED_SET: |
| return ntpinfo(*cext.proc_info(self.pid)).create_time |
| raise |
| |
| @wrap_exceptions |
| def num_threads(self): |
| return ntpinfo(*cext.proc_info(self.pid)).num_threads |
| |
| @wrap_exceptions |
| def threads(self): |
| rawlist = cext.proc_threads(self.pid) |
| retlist = [] |
| for thread_id, utime, stime in rawlist: |
| ntuple = _common.pthread(thread_id, utime, stime) |
| retlist.append(ntuple) |
| return retlist |
| |
| @wrap_exceptions |
| def cpu_times(self): |
| try: |
| ret = cext.proc_cpu_times(self.pid) |
| except OSError as err: |
| if err.errno in ACCESS_DENIED_SET: |
| nt = ntpinfo(*cext.proc_info(self.pid)) |
| ret = (nt.user_time, nt.kernel_time) |
| else: |
| raise |
| return _common.pcputimes(*ret) |
| |
| @wrap_exceptions |
| def suspend(self): |
| return cext.proc_suspend(self.pid) |
| |
| @wrap_exceptions |
| def resume(self): |
| return cext.proc_resume(self.pid) |
| |
| @wrap_exceptions |
| def cwd(self): |
| if self.pid in (0, 4): |
| raise AccessDenied(self.pid, self._name) |
| # return a normalized pathname since the native C function appends |
| # "\\" at the and of the path |
| path = cext.proc_cwd(self.pid) |
| return os.path.normpath(path) |
| |
| @wrap_exceptions |
| def open_files(self): |
| if self.pid in (0, 4): |
| return [] |
| retlist = [] |
| # Filenames come in in native format like: |
| # "\Device\HarddiskVolume1\Windows\systemew\file.txt" |
| # Convert the first part in the corresponding drive letter |
| # (e.g. "C:\") by using Windows's QueryDosDevice() |
| raw_file_names = cext.proc_open_files(self.pid) |
| for _file in raw_file_names: |
| _file = _convert_raw_path(_file) |
| if isfile_strict(_file) and _file not in retlist: |
| ntuple = _common.popenfile(_file, -1) |
| retlist.append(ntuple) |
| return retlist |
| |
| @wrap_exceptions |
| def connections(self, kind='inet'): |
| return net_connections(kind, _pid=self.pid) |
| |
| @wrap_exceptions |
| def nice_get(self): |
| value = cext.proc_priority_get(self.pid) |
| if enum is not None: |
| value = Priority(value) |
| return value |
| |
| @wrap_exceptions |
| def nice_set(self, value): |
| return cext.proc_priority_set(self.pid, value) |
| |
| # available on Windows >= Vista |
| if hasattr(cext, "proc_io_priority_get"): |
| @wrap_exceptions |
| def ionice_get(self): |
| return cext.proc_io_priority_get(self.pid) |
| |
| @wrap_exceptions |
| def ionice_set(self, value, _): |
| if _: |
| raise TypeError("set_proc_ionice() on Windows takes only " |
| "1 argument (2 given)") |
| if value not in (2, 1, 0): |
| raise ValueError("value must be 2 (normal), 1 (low) or 0 " |
| "(very low); got %r" % value) |
| return cext.proc_io_priority_set(self.pid, value) |
| |
| @wrap_exceptions |
| def io_counters(self): |
| try: |
| ret = cext.proc_io_counters(self.pid) |
| except OSError as err: |
| if err.errno in ACCESS_DENIED_SET: |
| nt = ntpinfo(*cext.proc_info(self.pid)) |
| ret = (nt.io_rcount, nt.io_wcount, nt.io_rbytes, nt.io_wbytes) |
| else: |
| raise |
| return _common.pio(*ret) |
| |
| @wrap_exceptions |
| def status(self): |
| suspended = cext.proc_is_suspended(self.pid) |
| if suspended: |
| return _common.STATUS_STOPPED |
| else: |
| return _common.STATUS_RUNNING |
| |
| @wrap_exceptions |
| def cpu_affinity_get(self): |
| def from_bitmask(x): |
| return [i for i in xrange(64) if (1 << i) & x] |
| bitmask = cext.proc_cpu_affinity_get(self.pid) |
| return from_bitmask(bitmask) |
| |
| @wrap_exceptions |
| def cpu_affinity_set(self, value): |
| def to_bitmask(l): |
| if not l: |
| raise ValueError("invalid argument %r" % l) |
| out = 0 |
| for b in l: |
| out |= 2 ** b |
| return out |
| |
| # SetProcessAffinityMask() states that ERROR_INVALID_PARAMETER |
| # is returned for an invalid CPU but this seems not to be true, |
| # therefore we check CPUs validy beforehand. |
| allcpus = list(range(len(per_cpu_times()))) |
| for cpu in value: |
| if cpu not in allcpus: |
| if not isinstance(cpu, (int, long)): |
| raise TypeError( |
| "invalid CPU %r; an integer is required" % cpu) |
| else: |
| raise ValueError("invalid CPU %r" % cpu) |
| |
| bitmask = to_bitmask(value) |
| cext.proc_cpu_affinity_set(self.pid, bitmask) |
| |
| @wrap_exceptions |
| def num_handles(self): |
| try: |
| return cext.proc_num_handles(self.pid) |
| except OSError as err: |
| if err.errno in ACCESS_DENIED_SET: |
| return ntpinfo(*cext.proc_info(self.pid)).num_handles |
| raise |
| |
| @wrap_exceptions |
| def num_ctx_switches(self): |
| ctx_switches = ntpinfo(*cext.proc_info(self.pid)).ctx_switches |
| # only voluntary ctx switches are supported |
| return _common.pctxsw(ctx_switches, 0) |