100 lines
3.4 KiB
Python
100 lines
3.4 KiB
Python
import _winapi
|
|
import msvcrt
|
|
import os
|
|
import subprocess
|
|
import uuid
|
|
from test import support
|
|
|
|
|
|
# Max size of asynchronous reads
|
|
BUFSIZE = 8192
|
|
# Exponential damping factor (see below)
|
|
LOAD_FACTOR_1 = 0.9200444146293232478931553241
|
|
# Seconds per measurement
|
|
SAMPLING_INTERVAL = 5
|
|
COUNTER_NAME = r'\System\Processor Queue Length'
|
|
|
|
|
|
class WindowsLoadTracker():
|
|
"""
|
|
This class asynchronously interacts with the `typeperf` command to read
|
|
the system load on Windows. Mulitprocessing and threads can't be used
|
|
here because they interfere with the test suite's cases for those
|
|
modules.
|
|
"""
|
|
|
|
def __init__(self):
|
|
self.load = 0.0
|
|
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
|
|
command = ['typeperf', COUNTER_NAME, '-si', str(SAMPLING_INTERVAL)]
|
|
self.p = subprocess.Popen(command, stdout=command_stdout, cwd=support.SAVEDCWD)
|
|
|
|
# Close our copy of the write end of the pipe
|
|
os.close(command_stdout)
|
|
|
|
def __del__(self):
|
|
self.p.kill()
|
|
self.p.wait()
|
|
|
|
def read_output(self):
|
|
import _winapi
|
|
|
|
overlapped, _ = _winapi.ReadFile(self.pipe, BUFSIZE, True)
|
|
bytes_read, res = overlapped.GetOverlappedResult(False)
|
|
if res != 0:
|
|
return
|
|
|
|
return overlapped.getbuffer().decode()
|
|
|
|
def getloadavg(self):
|
|
typeperf_output = self.read_output()
|
|
# Nothing to update, just return the current load
|
|
if not typeperf_output:
|
|
return self.load
|
|
|
|
# Process the backlog of load values
|
|
for line in typeperf_output.splitlines():
|
|
# typeperf outputs in a CSV format like this:
|
|
# "07/19/2018 01:32:26.605","3.000000"
|
|
toks = line.split(',')
|
|
# Ignore blank lines and the initial header
|
|
if line.strip() == '' or (COUNTER_NAME in line) or len(toks) != 2:
|
|
continue
|
|
|
|
load = float(toks[1].replace('"', ''))
|
|
# 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
|
|
new_load = self.load * LOAD_FACTOR_1 + load * (1.0 - LOAD_FACTOR_1)
|
|
self.load = new_load
|
|
|
|
return self.load
|