125 lines
4.4 KiB
Python
125 lines
4.4 KiB
Python
|
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
|