Skip to content

Commit 6d35de3

Browse files
committed
bpo-34060: Report system load when running test suite for Windows
While Windows exposes the system processor queue length: the raw value used for load calculations on Unix systems, it does not provide an API to access the averaged value. Hence to calculate the load we must track and average it ourselves. We can't use multiprocessing or a thread to read it in the background while the tests run since using those would conflict with test_multiprocessing and test_xxsubprocess. Thus, we use Window's asynchronous IO API to run the tracker in the background with it sampling at the correct rate. When we wish to access the load we check to see if there's new data on the stream, if there is, we update our load values.
1 parent cafaf04 commit 6d35de3

File tree

3 files changed

+108
-4
lines changed

3 files changed

+108
-4
lines changed

Lib/test/libregrtest/main.py

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@
1717
INTERRUPTED, CHILD_ERROR,
1818
PROGRESS_MIN_TIME, format_test_result)
1919
from test.libregrtest.setup import setup_tests
20-
from test.libregrtest.utils import removepy, count, format_duration, printlist
20+
from test.libregrtest.utils import (
21+
removepy, count, format_duration, printlist, WindowsLoadTracker)
2122
from test import support
2223
try:
2324
import gc
@@ -130,8 +131,8 @@ def display_progress(self, test_index, test):
130131
line = f"[{line}] {test}"
131132

132133
# add the system load prefix: "load avg: 1.80 "
133-
if hasattr(os, 'getloadavg'):
134-
load_avg_1min = os.getloadavg()[0]
134+
if self.getloadavg:
135+
load_avg_1min = self.getloadavg()
135136
line = f"load avg: {load_avg_1min:.2f} {line}"
136137

137138
# add the timestamp prefix: "0:01:05 "
@@ -533,6 +534,13 @@ def main(self, tests=None, **kwargs):
533534
def _main(self, tests, kwargs):
534535
self.ns = self.parse_args(kwargs)
535536

537+
self.getloadavg = None
538+
if hasattr(os, 'getloadavg'):
539+
self.getloadavg = lambda: os.getloadavg()[0]
540+
elif sys.platform == 'win32' and (self.ns.slaveargs is None):
541+
load_tracker = WindowsLoadTracker()
542+
self.getloadavg = load_tracker.getloadavg
543+
536544
if self.ns.huntrleaks:
537545
warmup, repetitions, _ = self.ns.huntrleaks
538546
if warmup < 1 or repetitions < 1:

Lib/test/libregrtest/utils.py

Lines changed: 95 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
1-
import os.path
1+
import os
22
import math
33
import textwrap
4+
import subprocess
5+
import sys
6+
from test import support
47

58

69
def format_duration(seconds):
@@ -54,3 +57,94 @@ def printlist(x, width=70, indent=4, file=None):
5457
print(textwrap.fill(' '.join(str(elt) for elt in sorted(x)), width,
5558
initial_indent=blanks, subsequent_indent=blanks),
5659
file=file)
60+
61+
BUFSIZE = 8192
62+
LOAD_FACTOR_1 = 0.9200444146293232478931553241
63+
SAMPLING_INTERVAL = 5
64+
COUNTER_NAME = r'\System\Processor Queue Length'
65+
66+
"""
67+
This class asynchronously interacts with the `typeperf` command to read
68+
the system load on Windows. Mulitprocessing and threads can't be used
69+
here because they interfere with the test suite's cases for those
70+
modules.
71+
"""
72+
class WindowsLoadTracker():
73+
def __init__(self):
74+
self.load = 0.0
75+
self.start()
76+
77+
def start(self):
78+
import _winapi
79+
import msvcrt
80+
import uuid
81+
82+
# Create a named pipe which allows for asynchronous IO in Windows
83+
pipe_name = r'\\.\pipe\typeperf_output_' + str(uuid.uuid4())
84+
85+
open_mode = _winapi.PIPE_ACCESS_INBOUND
86+
open_mode |= _winapi.FILE_FLAG_FIRST_PIPE_INSTANCE
87+
open_mode |= _winapi.FILE_FLAG_OVERLAPPED
88+
89+
# This is the read end of the pipe, where we will be grabbing output
90+
self.pipe = _winapi.CreateNamedPipe(
91+
pipe_name, open_mode, _winapi.PIPE_WAIT,
92+
1, BUFSIZE, BUFSIZE, _winapi.NMPWAIT_WAIT_FOREVER, _winapi.NULL
93+
)
94+
# The write end of the pipe which is passed to the created process
95+
pipe_write_end = _winapi.CreateFile(
96+
pipe_name, _winapi.GENERIC_WRITE, 0, _winapi.NULL,
97+
_winapi.OPEN_EXISTING, 0x80000000, _winapi.NULL
98+
)
99+
# Open up the handle as a python file object so we can pass it to
100+
# subprocess
101+
command_stdout = msvcrt.open_osfhandle(pipe_write_end, 0)
102+
103+
# Connect to the read end of the pipe in overlap/async mode
104+
overlap = _winapi.ConnectNamedPipe(self.pipe, overlapped=True)
105+
overlap.GetOverlappedResult(True)
106+
107+
# Spawn off the load monitor
108+
command = ['typeperf', COUNTER_NAME, '-si', str(SAMPLING_INTERVAL)]
109+
self.p = subprocess.Popen(command, stdout=command_stdout, cwd=support.SAVEDCWD)
110+
111+
# Close our copy of the write end of the pipe
112+
os.close(command_stdout)
113+
114+
def read_output(self):
115+
import _winapi
116+
117+
overlapped, _ = _winapi.ReadFile(self.pipe, BUFSIZE, True)
118+
bytes_read, res = overlapped.GetOverlappedResult(False)
119+
if res != 0:
120+
return
121+
122+
return overlapped.getbuffer().decode()
123+
124+
def getloadavg(self):
125+
typeperf_output = self.read_output()
126+
# Nothing to update, just return the current load
127+
if not typeperf_output:
128+
return self.load
129+
130+
# Process the backlog of load values
131+
for line in typeperf_output.splitlines():
132+
# typeperf outputs in a CSV format like this:
133+
# "07/19/2018 01:32:26.605","3.000000"
134+
toks = line.split(',')
135+
# Ignore blank lines and the initial header
136+
if line.strip() == '' or (COUNTER_NAME in line) or len(toks) != 2:
137+
continue
138+
139+
load = float(toks[1].replace('"', ''))
140+
# We use an exponentially weighted moving average, imitating the
141+
# load calculation on Unix systems.
142+
# https://en.wikipedia.org/wiki/Load_(computing)#Unix-style_load_calculation
143+
new_load = self.load * LOAD_FACTOR_1 + load * (1.0 - LOAD_FACTOR_1)
144+
self.load = new_load
145+
146+
return self.load
147+
148+
def __del__(self):
149+
self.p.kill()
150+
self.p.wait()
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Report system load when running test suite on Windows. Patch by Ammar Askar.
2+
Based on prior work by Jeremy Kloth.

0 commit comments

Comments
 (0)