gh-102567: Add -X importtime=2 for logging an importtime message for already-loaded modules (#118655)

Co-authored-by: Adam Turner <9087854+aa-turner@users.noreply.github.com>
This commit is contained in:
Noah Kim 2025-05-05 20:03:55 -04:00 committed by GitHub
parent e6f8e0a035
commit c4bcc6a778
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
15 changed files with 166 additions and 44 deletions

View file

@ -103,6 +103,15 @@ static struct _inittab *inittab_copy = NULL;
#define FIND_AND_LOAD(interp) \
(interp)->imports.find_and_load
#define _IMPORT_TIME_HEADER(interp) \
do { \
if (FIND_AND_LOAD((interp)).header) { \
fputs("import time: self [us] | cumulative | imported package\n", \
stderr); \
FIND_AND_LOAD((interp)).header = 0; \
} \
} while (0)
/*******************/
/* the import lock */
@ -246,9 +255,13 @@ import_ensure_initialized(PyInterpreterState *interp, PyObject *mod, PyObject *n
rc = _PyModuleSpec_IsInitializing(spec);
Py_DECREF(spec);
}
if (rc <= 0) {
if (rc == 0) {
goto done;
}
else if (rc < 0) {
return rc;
}
/* Wait until module is done importing. */
PyObject *value = PyObject_CallMethodOneArg(
IMPORTLIB(interp), &_Py_ID(_lock_unlock_module), name);
@ -256,6 +269,19 @@ import_ensure_initialized(PyInterpreterState *interp, PyObject *mod, PyObject *n
return -1;
}
Py_DECREF(value);
done:
/* When -X importtime=2, print an import time entry even if an
imported module has already been loaded.
*/
if (_PyInterpreterState_GetConfig(interp)->import_time == 2) {
_IMPORT_TIME_HEADER(interp);
#define import_level FIND_AND_LOAD(interp).import_level
fprintf(stderr, "import time: cached | cached | %*s\n",
import_level*2, PyUnicode_AsUTF8(name));
#undef import_level
}
return 0;
}
@ -3686,13 +3712,7 @@ import_find_and_load(PyThreadState *tstate, PyObject *abs_name)
* _PyDict_GetItemIdWithError().
*/
if (import_time) {
#define header FIND_AND_LOAD(interp).header
if (header) {
fputs("import time: self [us] | cumulative | imported package\n",
stderr);
header = 0;
}
#undef header
_IMPORT_TIME_HEADER(interp);
import_level++;
// ignore error: don't block import if reading the clock fails

View file

@ -153,7 +153,7 @@ static const PyConfigSpec PYCONFIG_SPEC[] = {
SPEC(home, WSTR_OPT, READ_ONLY, NO_SYS),
SPEC(thread_inherit_context, INT, READ_ONLY, NO_SYS),
SPEC(context_aware_warnings, INT, READ_ONLY, NO_SYS),
SPEC(import_time, BOOL, READ_ONLY, NO_SYS),
SPEC(import_time, UINT, READ_ONLY, NO_SYS),
SPEC(install_signal_handlers, BOOL, READ_ONLY, NO_SYS),
SPEC(isolated, BOOL, READ_ONLY, NO_SYS), // sys.flags.isolated
#ifdef MS_WINDOWS
@ -312,7 +312,8 @@ The following implementation-specific options are available:\n\
"-X gil=[0|1]: enable (1) or disable (0) the GIL; also PYTHON_GIL\n"
#endif
"\
-X importtime: show how long each import takes; also PYTHONPROFILEIMPORTTIME\n\
-X importtime[=2]: show how long each import takes; use -X importtime=2 to\
log imports of already-loaded modules; also PYTHONPROFILEIMPORTTIME\n\
-X int_max_str_digits=N: limit the size of int<->str conversions;\n\
0 disables the limit; also PYTHONINTMAXSTRDIGITS\n\
-X no_debug_ranges: don't include extra location information in code objects;\n\
@ -1004,6 +1005,7 @@ _PyConfig_InitCompatConfig(PyConfig *config)
memset(config, 0, sizeof(*config));
config->_config_init = (int)_PyConfig_INIT_COMPAT;
config->import_time = -1;
config->isolated = -1;
config->use_environment = -1;
config->dev_mode = -1;
@ -2246,6 +2248,38 @@ config_init_run_presite(PyConfig *config)
}
#endif
static PyStatus
config_init_import_time(PyConfig *config)
{
int importtime = 0;
const char *env = config_get_env(config, "PYTHONPROFILEIMPORTTIME");
if (env) {
if (_Py_str_to_int(env, &importtime) != 0) {
importtime = 1;
}
if (importtime < 0 || importtime > 2) {
return _PyStatus_ERR(
"PYTHONPROFILEIMPORTTIME: numeric values other than 1 and 2 "
"are reserved for future use.");
}
}
const wchar_t *x_value = config_get_xoption_value(config, L"importtime");
if (x_value) {
if (*x_value == 0 || config_wstr_to_int(x_value, &importtime) != 0) {
importtime = 1;
}
if (importtime < 0 || importtime > 2) {
return _PyStatus_ERR(
"-X importtime: values other than 1 and 2 "
"are reserved for future use.");
}
}
config->import_time = importtime;
return _PyStatus_OK();
}
static PyStatus
config_read_complex_options(PyConfig *config)
@ -2257,17 +2291,19 @@ config_read_complex_options(PyConfig *config)
config->faulthandler = 1;
}
}
if (config_get_env(config, "PYTHONPROFILEIMPORTTIME")
|| config_get_xoption(config, L"importtime")) {
config->import_time = 1;
}
if (config_get_env(config, "PYTHONNODEBUGRANGES")
|| config_get_xoption(config, L"no_debug_ranges")) {
config->code_debug_ranges = 0;
}
PyStatus status;
if (config->import_time < 0) {
status = config_init_import_time(config);
if (_PyStatus_EXCEPTION(status)) {
return status;
}
}
if (config->tracemalloc < 0) {
status = config_init_tracemalloc(config);
if (_PyStatus_EXCEPTION(status)) {