| import _overlapped |
| import _thread |
| import _winapi |
| import math |
| import struct |
| import winreg |
| |
| |
| # Seconds per measurement |
| SAMPLING_INTERVAL = 1 |
| # Exponential damping factor to compute exponentially weighted moving average |
| # on 1 minute (60 seconds) |
| LOAD_FACTOR_1 = 1 / math.exp(SAMPLING_INTERVAL / 60) |
| # Initialize the load using the arithmetic mean of the first NVALUE values |
| # of the Processor Queue Length |
| NVALUE = 5 |
| |
| |
| class WindowsLoadTracker(): |
| """ |
| This class asynchronously reads the performance counters to calculate |
| the system load on Windows. A "raw" thread is used here to prevent |
| interference with the test suite's cases for the threading module. |
| """ |
| |
| def __init__(self): |
| # Pre-flight test for access to the performance data; |
| # `PermissionError` will be raised if not allowed |
| winreg.QueryInfoKey(winreg.HKEY_PERFORMANCE_DATA) |
| |
| self._values = [] |
| self._load = None |
| self._running = _overlapped.CreateEvent(None, True, False, None) |
| self._stopped = _overlapped.CreateEvent(None, True, False, None) |
| |
| _thread.start_new_thread(self._update_load, (), {}) |
| |
| def _update_load(self, |
| # localize module access to prevent shutdown errors |
| _wait=_winapi.WaitForSingleObject, |
| _signal=_overlapped.SetEvent): |
| # run until signaled to stop |
| while _wait(self._running, 1000): |
| self._calculate_load() |
| # notify stopped |
| _signal(self._stopped) |
| |
| def _calculate_load(self, |
| # localize module access to prevent shutdown errors |
| _query=winreg.QueryValueEx, |
| _hkey=winreg.HKEY_PERFORMANCE_DATA, |
| _unpack=struct.unpack_from): |
| # get the 'System' object |
| data, _ = _query(_hkey, '2') |
| # PERF_DATA_BLOCK { |
| # WCHAR Signature[4] 8 + |
| # DWOWD LittleEndian 4 + |
| # DWORD Version 4 + |
| # DWORD Revision 4 + |
| # DWORD TotalByteLength 4 + |
| # DWORD HeaderLength = 24 byte offset |
| # ... |
| # } |
| obj_start, = _unpack('L', data, 24) |
| # PERF_OBJECT_TYPE { |
| # DWORD TotalByteLength |
| # DWORD DefinitionLength |
| # DWORD HeaderLength |
| # ... |
| # } |
| data_start, defn_start = _unpack('4xLL', data, obj_start) |
| data_base = obj_start + data_start |
| defn_base = obj_start + defn_start |
| # find the 'Processor Queue Length' counter (index=44) |
| while defn_base < data_base: |
| # PERF_COUNTER_DEFINITION { |
| # DWORD ByteLength |
| # DWORD CounterNameTitleIndex |
| # ... [7 DWORDs/28 bytes] |
| # DWORD CounterOffset |
| # } |
| size, idx, offset = _unpack('LL28xL', data, defn_base) |
| defn_base += size |
| if idx == 44: |
| counter_offset = data_base + offset |
| # the counter is known to be PERF_COUNTER_RAWCOUNT (DWORD) |
| processor_queue_length, = _unpack('L', data, counter_offset) |
| break |
| else: |
| return |
| |
| # We use an exponentially weighted moving average, imitating the |
| # load calculation on Unix systems. |
| # https://en.wikipedia.org/wiki/Load_(computing)#Unix-style_load_calculation |
| # https://en.wikipedia.org/wiki/Moving_average#Exponential_moving_average |
| if self._load is not None: |
| self._load = (self._load * LOAD_FACTOR_1 |
| + processor_queue_length * (1.0 - LOAD_FACTOR_1)) |
| elif len(self._values) < NVALUE: |
| self._values.append(processor_queue_length) |
| else: |
| self._load = sum(self._values) / len(self._values) |
| |
| def close(self, kill=True): |
| self.__del__() |
| return |
| |
| def __del__(self, |
| # localize module access to prevent shutdown errors |
| _wait=_winapi.WaitForSingleObject, |
| _close=_winapi.CloseHandle, |
| _signal=_overlapped.SetEvent): |
| if self._running is not None: |
| # tell the update thread to quit |
| _signal(self._running) |
| # wait for the update thread to signal done |
| _wait(self._stopped, -1) |
| # cleanup events |
| _close(self._running) |
| _close(self._stopped) |
| self._running = self._stopped = None |
| |
| def getloadavg(self): |
| return self._load |