win_utils.py 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124
  1. import _overlapped
  2. import _thread
  3. import _winapi
  4. import math
  5. import struct
  6. import winreg
  7. # Seconds per measurement
  8. SAMPLING_INTERVAL = 1
  9. # Exponential damping factor to compute exponentially weighted moving average
  10. # on 1 minute (60 seconds)
  11. LOAD_FACTOR_1 = 1 / math.exp(SAMPLING_INTERVAL / 60)
  12. # Initialize the load using the arithmetic mean of the first NVALUE values
  13. # of the Processor Queue Length
  14. NVALUE = 5
  15. class WindowsLoadTracker():
  16. """
  17. This class asynchronously reads the performance counters to calculate
  18. the system load on Windows. A "raw" thread is used here to prevent
  19. interference with the test suite's cases for the threading module.
  20. """
  21. def __init__(self):
  22. # Pre-flight test for access to the performance data;
  23. # `PermissionError` will be raised if not allowed
  24. winreg.QueryInfoKey(winreg.HKEY_PERFORMANCE_DATA)
  25. self._values = []
  26. self._load = None
  27. self._running = _overlapped.CreateEvent(None, True, False, None)
  28. self._stopped = _overlapped.CreateEvent(None, True, False, None)
  29. _thread.start_new_thread(self._update_load, (), {})
  30. def _update_load(self,
  31. # localize module access to prevent shutdown errors
  32. _wait=_winapi.WaitForSingleObject,
  33. _signal=_overlapped.SetEvent):
  34. # run until signaled to stop
  35. while _wait(self._running, 1000):
  36. self._calculate_load()
  37. # notify stopped
  38. _signal(self._stopped)
  39. def _calculate_load(self,
  40. # localize module access to prevent shutdown errors
  41. _query=winreg.QueryValueEx,
  42. _hkey=winreg.HKEY_PERFORMANCE_DATA,
  43. _unpack=struct.unpack_from):
  44. # get the 'System' object
  45. data, _ = _query(_hkey, '2')
  46. # PERF_DATA_BLOCK {
  47. # WCHAR Signature[4] 8 +
  48. # DWOWD LittleEndian 4 +
  49. # DWORD Version 4 +
  50. # DWORD Revision 4 +
  51. # DWORD TotalByteLength 4 +
  52. # DWORD HeaderLength = 24 byte offset
  53. # ...
  54. # }
  55. obj_start, = _unpack('L', data, 24)
  56. # PERF_OBJECT_TYPE {
  57. # DWORD TotalByteLength
  58. # DWORD DefinitionLength
  59. # DWORD HeaderLength
  60. # ...
  61. # }
  62. data_start, defn_start = _unpack('4xLL', data, obj_start)
  63. data_base = obj_start + data_start
  64. defn_base = obj_start + defn_start
  65. # find the 'Processor Queue Length' counter (index=44)
  66. while defn_base < data_base:
  67. # PERF_COUNTER_DEFINITION {
  68. # DWORD ByteLength
  69. # DWORD CounterNameTitleIndex
  70. # ... [7 DWORDs/28 bytes]
  71. # DWORD CounterOffset
  72. # }
  73. size, idx, offset = _unpack('LL28xL', data, defn_base)
  74. defn_base += size
  75. if idx == 44:
  76. counter_offset = data_base + offset
  77. # the counter is known to be PERF_COUNTER_RAWCOUNT (DWORD)
  78. processor_queue_length, = _unpack('L', data, counter_offset)
  79. break
  80. else:
  81. return
  82. # We use an exponentially weighted moving average, imitating the
  83. # load calculation on Unix systems.
  84. # https://en.wikipedia.org/wiki/Load_(computing)#Unix-style_load_calculation
  85. # https://en.wikipedia.org/wiki/Moving_average#Exponential_moving_average
  86. if self._load is not None:
  87. self._load = (self._load * LOAD_FACTOR_1
  88. + processor_queue_length * (1.0 - LOAD_FACTOR_1))
  89. elif len(self._values) < NVALUE:
  90. self._values.append(processor_queue_length)
  91. else:
  92. self._load = sum(self._values) / len(self._values)
  93. def close(self, kill=True):
  94. self.__del__()
  95. return
  96. def __del__(self,
  97. # localize module access to prevent shutdown errors
  98. _wait=_winapi.WaitForSingleObject,
  99. _close=_winapi.CloseHandle,
  100. _signal=_overlapped.SetEvent):
  101. if self._running is not None:
  102. # tell the update thread to quit
  103. _signal(self._running)
  104. # wait for the update thread to signal done
  105. _wait(self._stopped, -1)
  106. # cleanup events
  107. _close(self._running)
  108. _close(self._stopped)
  109. self._running = self._stopped = None
  110. def getloadavg(self):
  111. return self._load