Skip to content

bpo-34060: Report system load when running test suite for Windows #8287

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 14 additions & 2 deletions Lib/test/libregrtest/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,18 @@ def __init__(self):
self.next_single_test = None
self.next_single_filename = None

# use the platform specific APIs available
# for system load averages
self.getloadavg = None
try:
import _winapi
_winapi.InitializeLoadCounter()
self.getloadavg = _winapi.GetLoadAvg
except ImportError:
pass
if hasattr(os, 'getloadavg'):
self.getloadavg = lambda: os.getloadavg()[0]

def accumulate_result(self, test, result):
ok, test_time = result
if ok not in (CHILD_ERROR, INTERRUPTED):
Expand Down Expand Up @@ -130,8 +142,8 @@ def display_progress(self, test_index, test):
line = f"[{line}] {test}"

# add the system load prefix: "load avg: 1.80 "
if hasattr(os, 'getloadavg'):
load_avg_1min = os.getloadavg()[0]
if self.getloadavg:
load_avg_1min = self.getloadavg()
line = f"load avg: {load_avg_1min:.2f} {line}"

# add the timestamp prefix: "0:01:05 "
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Report system load when running test suite on Windows. Patch by Ammar Askar.
Based on prior work by Jeremy Kloth.
89 changes: 89 additions & 0 deletions Modules/_winapi.c
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@

#define WINDOWS_LEAN_AND_MEAN
#include "windows.h"
#include <pdh.h>
#include <crtdbg.h>
#include "winreparse.h"

Expand Down Expand Up @@ -1699,6 +1700,92 @@ _winapi_GetFileType_impl(PyObject *module, HANDLE handle)
return result;
}

// We use an exponentially weighted moving average, just like Unix systems do
// https://en.wikipedia.org/wiki/Load_(computing)#Unix-style_load_calculation
// This constant serves as the damping factor.
#define LOADAVG_FACTOR_1F 0.9200444146293232478931553241
// The time interval in seconds between taking load counts
#define SAMPLING_INTERVAL 5

double load_1m = 0;

VOID CALLBACK LoadCallback(PVOID hCounter)
{
PDH_FMT_COUNTERVALUE displayValue;
PdhGetFormattedCounterValue((PDH_HCOUNTER) hCounter, PDH_FMT_DOUBLE, 0, &displayValue);

double currentLoad = displayValue.doubleValue;
double newLoad = load_1m * LOADAVG_FACTOR_1F + currentLoad * (1.0 - LOADAVG_FACTOR_1F);
load_1m = newLoad;
}

/*[clinic input]
_winapi.GetLoadAvg

Gets the 1 minute load average (processor queue length) for the system.

InitializeLoadCounter must be called before this function to engage the
mechanism that records load values.

[clinic start generated code]*/

static PyObject *
_winapi_GetLoadAvg_impl(PyObject *module)
/*[clinic end generated code: output=e79e25f9f0783a3e input=e874ce4fc553db23]*/
{
PyObject* load = PyFloat_FromDouble(load_1m);
return load;
}

/*[clinic input]
_winapi.InitializeLoadCounter

Initializes instrumentation code to keep track of system load.

[clinic start generated code]*/

static PyObject *
_winapi_InitializeLoadCounter_impl(PyObject *module)
/*[clinic end generated code: output=943e4187b2a20b39 input=3caa0958b4abcea0]*/
{
PDH_STATUS s;
HQUERY hQuery;
HCOUNTER hCounter;

if ((s = PdhOpenQueryW(NULL, 0, &hQuery)) != ERROR_SUCCESS)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please move the assignment on a separated line. Same comments for other assignments in if().

{
goto WMIerror;
}

WCHAR *szCounterPath = L"\\System\\Processor Queue Length";
if ((s = PdhAddEnglishCounterW(hQuery, szCounterPath, 0, &hCounter)) != ERROR_SUCCESS)
{
goto WMIerror;
}

HANDLE event = CreateEventW(NULL, FALSE, FALSE, L"LoadUpdateEvent");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hum. It seems like you allocate a resource, but never release it.

I would suggest to create an object which keeps track of these resources and release them later. getloadavg() would be a method of that object.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it be acceptable to do it in the module's m_free function? I was initially thinking of doing something similar but thought that would create a lot of extra code and complexity for an internal API with one use case.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I dislike using m_free() for that.

if (event == NULL) {
PyErr_SetFromWindowsErr(GetLastError());
return NULL;
}

if ((s = PdhCollectQueryDataEx(hQuery, SAMPLING_INTERVAL, event)) != ERROR_SUCCESS)
{
goto WMIerror;
}

HANDLE waitHandle;
if (RegisterWaitForSingleObject(&waitHandle, event, (WAITORTIMERCALLBACK) LoadCallback,
(PVOID) hCounter, INFINITE, WT_EXECUTEDEFAULT) == 0) {
PyErr_SetFromWindowsErr(GetLastError());
return NULL;
}

Py_RETURN_NONE;
WMIerror:
PyErr_SetExcFromWindowsErr(PyExc_OSError, 0);
return NULL;
}

static PyMethodDef winapi_functions[] = {
_WINAPI_CLOSEHANDLE_METHODDEF
Expand Down Expand Up @@ -1727,6 +1814,8 @@ static PyMethodDef winapi_functions[] = {
_WINAPI_WRITEFILE_METHODDEF
_WINAPI_GETACP_METHODDEF
_WINAPI_GETFILETYPE_METHODDEF
_WINAPI_GETLOADAVG_METHODDEF
_WINAPI_INITIALIZELOADCOUNTER_METHODDEF
{NULL, NULL}
};

Expand Down
41 changes: 40 additions & 1 deletion Modules/clinic/_winapi.c.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions PCbuild/pythoncore.vcxproj
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@
<PreprocessorDefinitions Condition="$(IncludeExternals)">_Py_HAVE_ZLIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
</ClCompile>
<Link>
<AdditionalDependencies>version.lib;shlwapi.lib;ws2_32.lib;%(AdditionalDependencies)</AdditionalDependencies>
<AdditionalDependencies>version.lib;shlwapi.lib;ws2_32.lib;Pdh.lib;%(AdditionalDependencies)</AdditionalDependencies>
</Link>
</ItemDefinitionGroup>
<ItemGroup>
Expand Down Expand Up @@ -461,4 +461,4 @@
<Target Name="_WarnAboutZlib" BeforeTargets="PrepareForBuild" Condition="!$(IncludeExternals)">
<Warning Text="Not including zlib is not a supported configuration." />
</Target>
</Project>
</Project>