| 
									
										
										
										
											2019-04-09 08:20:41 -04:00
										 |  |  | import _winapi | 
					
						
							| 
									
										
										
										
											2019-10-03 16:15:16 +02:00
										 |  |  | import math | 
					
						
							| 
									
										
										
										
											2019-04-09 08:20:41 -04:00
										 |  |  | import msvcrt | 
					
						
							| 
									
										
										
										
											2019-04-26 04:08:53 +02:00
										 |  |  | import os | 
					
						
							|  |  |  | import subprocess | 
					
						
							| 
									
										
										
										
											2019-04-09 08:20:41 -04:00
										 |  |  | import uuid | 
					
						
							| 
									
										
										
										
											2019-10-01 12:29:36 +02:00
										 |  |  | import winreg | 
					
						
							| 
									
										
										
										
											2019-04-09 08:20:41 -04:00
										 |  |  | from test import support | 
					
						
							| 
									
										
										
										
											2019-10-01 12:29:36 +02:00
										 |  |  | from test.libregrtest.utils import print_warning | 
					
						
							| 
									
										
										
										
											2019-04-09 08:20:41 -04:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | # Max size of asynchronous reads | 
					
						
							|  |  |  | BUFSIZE = 8192 | 
					
						
							|  |  |  | # Seconds per measurement | 
					
						
							| 
									
										
										
										
											2019-10-03 10:53:17 +02:00
										 |  |  | SAMPLING_INTERVAL = 1 | 
					
						
							| 
									
										
										
										
											2019-10-03 16:15:16 +02:00
										 |  |  | # 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 | 
					
						
							| 
									
										
										
										
											2019-10-01 12:29:36 +02:00
										 |  |  | # Windows registry subkey of HKEY_LOCAL_MACHINE where the counter names | 
					
						
							|  |  |  | # of typeperf are registered | 
					
						
							|  |  |  | COUNTER_REGISTRY_KEY = (r"SOFTWARE\Microsoft\Windows NT\CurrentVersion" | 
					
						
							|  |  |  |                         r"\Perflib\CurrentLanguage") | 
					
						
							| 
									
										
										
										
											2019-04-09 08:20:41 -04:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class WindowsLoadTracker(): | 
					
						
							|  |  |  |     """
 | 
					
						
							|  |  |  |     This class asynchronously interacts with the `typeperf` command to read | 
					
						
							| 
									
										
										
										
											2019-07-31 08:16:13 +10:00
										 |  |  |     the system load on Windows. Multiprocessing and threads can't be used | 
					
						
							| 
									
										
										
										
											2019-04-09 08:20:41 -04:00
										 |  |  |     here because they interfere with the test suite's cases for those | 
					
						
							|  |  |  |     modules. | 
					
						
							|  |  |  |     """
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def __init__(self): | 
					
						
							| 
									
										
										
										
											2019-10-03 16:15:16 +02:00
										 |  |  |         self._values = [] | 
					
						
							|  |  |  |         self._load = None | 
					
						
							| 
									
										
										
										
											2019-10-03 10:53:17 +02:00
										 |  |  |         self._buffer = '' | 
					
						
							| 
									
										
										
										
											2019-10-03 16:15:16 +02:00
										 |  |  |         self._popen = None | 
					
						
							| 
									
										
										
										
											2019-04-09 08:20:41 -04:00
										 |  |  |         self.start() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def start(self): | 
					
						
							|  |  |  |         # Create a named pipe which allows for asynchronous IO in Windows | 
					
						
							|  |  |  |         pipe_name =  r'\\.\pipe\typeperf_output_' + str(uuid.uuid4()) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         open_mode =  _winapi.PIPE_ACCESS_INBOUND | 
					
						
							|  |  |  |         open_mode |= _winapi.FILE_FLAG_FIRST_PIPE_INSTANCE | 
					
						
							|  |  |  |         open_mode |= _winapi.FILE_FLAG_OVERLAPPED | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # This is the read end of the pipe, where we will be grabbing output | 
					
						
							|  |  |  |         self.pipe = _winapi.CreateNamedPipe( | 
					
						
							|  |  |  |             pipe_name, open_mode, _winapi.PIPE_WAIT, | 
					
						
							|  |  |  |             1, BUFSIZE, BUFSIZE, _winapi.NMPWAIT_WAIT_FOREVER, _winapi.NULL | 
					
						
							|  |  |  |         ) | 
					
						
							|  |  |  |         # The write end of the pipe which is passed to the created process | 
					
						
							|  |  |  |         pipe_write_end = _winapi.CreateFile( | 
					
						
							|  |  |  |             pipe_name, _winapi.GENERIC_WRITE, 0, _winapi.NULL, | 
					
						
							|  |  |  |             _winapi.OPEN_EXISTING, 0, _winapi.NULL | 
					
						
							|  |  |  |         ) | 
					
						
							|  |  |  |         # Open up the handle as a python file object so we can pass it to | 
					
						
							|  |  |  |         # subprocess | 
					
						
							|  |  |  |         command_stdout = msvcrt.open_osfhandle(pipe_write_end, 0) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # Connect to the read end of the pipe in overlap/async mode | 
					
						
							|  |  |  |         overlap = _winapi.ConnectNamedPipe(self.pipe, overlapped=True) | 
					
						
							|  |  |  |         overlap.GetOverlappedResult(True) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # Spawn off the load monitor | 
					
						
							| 
									
										
										
										
											2019-10-01 12:29:36 +02:00
										 |  |  |         counter_name = self._get_counter_name() | 
					
						
							|  |  |  |         command = ['typeperf', counter_name, '-si', str(SAMPLING_INTERVAL)] | 
					
						
							| 
									
										
										
										
											2019-10-03 16:15:16 +02:00
										 |  |  |         self._popen = subprocess.Popen(' '.join(command), stdout=command_stdout, cwd=support.SAVEDCWD) | 
					
						
							| 
									
										
										
										
											2019-04-09 08:20:41 -04:00
										 |  |  | 
 | 
					
						
							|  |  |  |         # Close our copy of the write end of the pipe | 
					
						
							|  |  |  |         os.close(command_stdout) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-10-01 12:29:36 +02:00
										 |  |  |     def _get_counter_name(self): | 
					
						
							|  |  |  |         # accessing the registry to get the counter localization name | 
					
						
							|  |  |  |         with winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, COUNTER_REGISTRY_KEY) as perfkey: | 
					
						
							|  |  |  |             counters = winreg.QueryValueEx(perfkey, 'Counter')[0] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # Convert [key1, value1, key2, value2, ...] list | 
					
						
							|  |  |  |         # to {key1: value1, key2: value2, ...} dict | 
					
						
							|  |  |  |         counters = iter(counters) | 
					
						
							|  |  |  |         counters_dict = dict(zip(counters, counters)) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # System counter has key '2' and Processor Queue Length has key '44' | 
					
						
							|  |  |  |         system = counters_dict['2'] | 
					
						
							|  |  |  |         process_queue_length = counters_dict['44'] | 
					
						
							|  |  |  |         return f'"\\{system}\\{process_queue_length}"' | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-10-03 16:15:16 +02:00
										 |  |  |     def close(self, kill=True): | 
					
						
							|  |  |  |         if self._popen is None: | 
					
						
							| 
									
										
										
										
											2019-04-26 11:12:26 +02:00
										 |  |  |             return | 
					
						
							| 
									
										
										
										
											2019-10-03 16:15:16 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |         self._load = None | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if kill: | 
					
						
							|  |  |  |             self._popen.kill() | 
					
						
							|  |  |  |         self._popen.wait() | 
					
						
							|  |  |  |         self._popen = None | 
					
						
							| 
									
										
										
										
											2019-04-26 11:12:26 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |     def __del__(self): | 
					
						
							|  |  |  |         self.close() | 
					
						
							| 
									
										
										
										
											2019-04-09 08:20:41 -04:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-10-03 10:53:17 +02:00
										 |  |  |     def _parse_line(self, line): | 
					
						
							|  |  |  |         # typeperf outputs in a CSV format like this: | 
					
						
							|  |  |  |         # "07/19/2018 01:32:26.605","3.000000" | 
					
						
							|  |  |  |         # (date, process queue length) | 
					
						
							|  |  |  |         tokens = line.split(',') | 
					
						
							|  |  |  |         if len(tokens) != 2: | 
					
						
							|  |  |  |             raise ValueError | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         value = tokens[1] | 
					
						
							|  |  |  |         if not value.startswith('"') or not value.endswith('"'): | 
					
						
							|  |  |  |             raise ValueError | 
					
						
							|  |  |  |         value = value[1:-1] | 
					
						
							|  |  |  |         return float(value) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-10-03 16:15:16 +02:00
										 |  |  |     def _read_lines(self): | 
					
						
							| 
									
										
										
										
											2019-04-09 08:20:41 -04:00
										 |  |  |         overlapped, _ = _winapi.ReadFile(self.pipe, BUFSIZE, True) | 
					
						
							|  |  |  |         bytes_read, res = overlapped.GetOverlappedResult(False) | 
					
						
							|  |  |  |         if res != 0: | 
					
						
							| 
									
										
										
										
											2019-10-03 10:53:17 +02:00
										 |  |  |             return () | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         output = overlapped.getbuffer() | 
					
						
							|  |  |  |         output = output.decode('oem', 'replace') | 
					
						
							|  |  |  |         output = self._buffer + output | 
					
						
							|  |  |  |         lines = output.splitlines(True) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # bpo-36670: typeperf only writes a newline *before* writing a value, | 
					
						
							|  |  |  |         # not after. Sometimes, the written line in incomplete (ex: only | 
					
						
							|  |  |  |         # timestamp, without the process queue length). Only pass the last line | 
					
						
							|  |  |  |         # to the parser if it's a valid value, otherwise store it in | 
					
						
							|  |  |  |         # self._buffer. | 
					
						
							|  |  |  |         try: | 
					
						
							|  |  |  |             self._parse_line(lines[-1]) | 
					
						
							|  |  |  |         except ValueError: | 
					
						
							|  |  |  |             self._buffer = lines.pop(-1) | 
					
						
							|  |  |  |         else: | 
					
						
							|  |  |  |             self._buffer = '' | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         return lines | 
					
						
							| 
									
										
										
										
											2019-04-09 08:20:41 -04:00
										 |  |  | 
 | 
					
						
							|  |  |  |     def getloadavg(self): | 
					
						
							| 
									
										
										
										
											2019-10-03 16:15:16 +02:00
										 |  |  |         if self._popen is None: | 
					
						
							|  |  |  |             return None | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         returncode = self._popen.poll() | 
					
						
							|  |  |  |         if returncode is not None: | 
					
						
							|  |  |  |             self.close(kill=False) | 
					
						
							|  |  |  |             return None | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         try: | 
					
						
							|  |  |  |             lines = self._read_lines() | 
					
						
							|  |  |  |         except BrokenPipeError: | 
					
						
							|  |  |  |             self.close() | 
					
						
							|  |  |  |             return None | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         for line in lines: | 
					
						
							| 
									
										
										
										
											2019-10-03 10:53:17 +02:00
										 |  |  |             line = line.rstrip() | 
					
						
							| 
									
										
										
										
											2019-04-09 08:20:41 -04:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-10-01 12:29:36 +02:00
										 |  |  |             # Ignore the initial header: | 
					
						
							|  |  |  |             # "(PDH-CSV 4.0)","\\\\WIN\\System\\Processor Queue Length" | 
					
						
							| 
									
										
										
										
											2019-10-03 10:53:17 +02:00
										 |  |  |             if 'PDH-CSV' in line: | 
					
						
							| 
									
										
										
										
											2019-10-01 12:29:36 +02:00
										 |  |  |                 continue | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             # Ignore blank lines | 
					
						
							| 
									
										
										
										
											2019-10-03 10:53:17 +02:00
										 |  |  |             if not line: | 
					
						
							| 
									
										
										
										
											2019-10-01 12:29:36 +02:00
										 |  |  |                 continue | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             try: | 
					
						
							| 
									
										
										
										
											2019-10-03 16:15:16 +02:00
										 |  |  |                 processor_queue_length = self._parse_line(line) | 
					
						
							| 
									
										
										
										
											2019-10-01 12:29:36 +02:00
										 |  |  |             except ValueError: | 
					
						
							|  |  |  |                 print_warning("Failed to parse typeperf output: %a" % line) | 
					
						
							| 
									
										
										
										
											2019-04-09 08:20:41 -04:00
										 |  |  |                 continue | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             # 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 | 
					
						
							| 
									
										
										
										
											2019-10-03 16:15:16 +02:00
										 |  |  |             # 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) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         return self._load |