11import _winapi
2+ import math
23import msvcrt
34import os
45import subprocess
1011
1112# Max size of asynchronous reads
1213BUFSIZE = 8192
13- # Exponential damping factor (see below)
14- LOAD_FACTOR_1 = 0.9200444146293232478931553241
15-
1614# Seconds per measurement
1715SAMPLING_INTERVAL = 1
16+ # Exponential damping factor to compute exponentially weighted moving average
17+ # on 1 minute (60 seconds)
18+ LOAD_FACTOR_1 = 1 / math .exp (SAMPLING_INTERVAL / 60 )
19+ # Initialize the load using the arithmetic mean of the first NVALUE values
20+ # of the Processor Queue Length
21+ NVALUE = 5
1822# Windows registry subkey of HKEY_LOCAL_MACHINE where the counter names
1923# of typeperf are registered
2024COUNTER_REGISTRY_KEY = (r"SOFTWARE\Microsoft\Windows NT\CurrentVersion"
@@ -30,10 +34,10 @@ class WindowsLoadTracker():
3034 """
3135
3236 def __init__ (self ):
33- self .load = 0.0
34- self .counter_name = ''
37+ self ._values = []
38+ self ._load = None
3539 self ._buffer = ''
36- self .popen = None
40+ self ._popen = None
3741 self .start ()
3842
3943 def start (self ):
@@ -65,7 +69,7 @@ def start(self):
6569 # Spawn off the load monitor
6670 counter_name = self ._get_counter_name ()
6771 command = ['typeperf' , counter_name , '-si' , str (SAMPLING_INTERVAL )]
68- self .popen = subprocess .Popen (' ' .join (command ), stdout = command_stdout , cwd = support .SAVEDCWD )
72+ self ._popen = subprocess .Popen (' ' .join (command ), stdout = command_stdout , cwd = support .SAVEDCWD )
6973
7074 # Close our copy of the write end of the pipe
7175 os .close (command_stdout )
@@ -85,12 +89,16 @@ def _get_counter_name(self):
8589 process_queue_length = counters_dict ['44' ]
8690 return f'"\\ { system } \\ { process_queue_length } "'
8791
88- def close (self ):
89- if self .popen is None :
92+ def close (self , kill = True ):
93+ if self ._popen is None :
9094 return
91- self .popen .kill ()
92- self .popen .wait ()
93- self .popen = None
95+
96+ self ._load = None
97+
98+ if kill :
99+ self ._popen .kill ()
100+ self ._popen .wait ()
101+ self ._popen = None
94102
95103 def __del__ (self ):
96104 self .close ()
@@ -109,7 +117,7 @@ def _parse_line(self, line):
109117 value = value [1 :- 1 ]
110118 return float (value )
111119
112- def read_lines (self ):
120+ def _read_lines (self ):
113121 overlapped , _ = _winapi .ReadFile (self .pipe , BUFSIZE , True )
114122 bytes_read , res = overlapped .GetOverlappedResult (False )
115123 if res != 0 :
@@ -135,7 +143,21 @@ def read_lines(self):
135143 return lines
136144
137145 def getloadavg (self ):
138- for line in self .read_lines ():
146+ if self ._popen is None :
147+ return None
148+
149+ returncode = self ._popen .poll ()
150+ if returncode is not None :
151+ self .close (kill = False )
152+ return None
153+
154+ try :
155+ lines = self ._read_lines ()
156+ except BrokenPipeError :
157+ self .close ()
158+ return None
159+
160+ for line in lines :
139161 line = line .rstrip ()
140162
141163 # Ignore the initial header:
@@ -148,15 +170,21 @@ def getloadavg(self):
148170 continue
149171
150172 try :
151- load = self ._parse_line (line )
173+ processor_queue_length = self ._parse_line (line )
152174 except ValueError :
153175 print_warning ("Failed to parse typeperf output: %a" % line )
154176 continue
155177
156178 # We use an exponentially weighted moving average, imitating the
157179 # load calculation on Unix systems.
158180 # https://en.wikipedia.org/wiki/Load_(computing)#Unix-style_load_calculation
159- new_load = self .load * LOAD_FACTOR_1 + load * (1.0 - LOAD_FACTOR_1 )
160- self .load = new_load
161-
162- return self .load
181+ # https://en.wikipedia.org/wiki/Moving_average#Exponential_moving_average
182+ if self ._load is not None :
183+ self ._load = (self ._load * LOAD_FACTOR_1
184+ + processor_queue_length * (1.0 - LOAD_FACTOR_1 ))
185+ elif len (self ._values ) < NVALUE :
186+ self ._values .append (processor_queue_length )
187+ else :
188+ self ._load = sum (self ._values ) / len (self ._values )
189+
190+ return self ._load
0 commit comments