summaryrefslogtreecommitdiffstats
path: root/Python
diff options
context:
space:
mode:
authorVictor Stinner <vstinner@python.org>2023-09-06 15:54:59 (GMT)
committerGitHub <noreply@github.com>2023-09-06 15:54:59 (GMT)
commita0773b89dfe5cd2190d539905dd89e7f6455668e (patch)
treee11c7306e6ed7750265eda6ba8fa49f329bc0f85 /Python
parent8ff11425783806f8cb78e99f667546b1f7f3428e (diff)
downloadcpython-a0773b89dfe5cd2190d539905dd89e7f6455668e.zip
cpython-a0773b89dfe5cd2190d539905dd89e7f6455668e.tar.gz
cpython-a0773b89dfe5cd2190d539905dd89e7f6455668e.tar.bz2
gh-108753: Enhance pystats (#108754)
Statistics gathering is now off by default. Use the "-X pystats" command line option or set the new PYTHONSTATS environment variable to 1 to turn statistics gathering on at Python startup. Statistics are no longer dumped at exit if statistics gathering was off or statistics have been cleared. Changes: * Add PYTHONSTATS environment variable. * sys._stats_dump() now returns False if statistics are not dumped because they are all equal to zero. * Add PyConfig._pystats member. * Add tests on sys functions and on setting PyConfig._pystats to 1. * Add Include/cpython/pystats.h and Include/internal/pycore_pystats.h header files. * Rename '_py_stats' variable to '_Py_stats'. * Exclude Include/cpython/pystats.h from the Py_LIMITED_API. * Move pystats.h include from object.h to Python.h. * Add _Py_StatsOn() and _Py_StatsOff() functions. Remove '_py_stats_struct' variable from the API: make it static in specialize.c. * Document API in Include/pystats.h and Include/cpython/pystats.h. * Complete pystats documentation in Doc/using/configure.rst. * Don't write "all zeros" stats: if _stats_off() and _stats_clear() or _stats_dump() were called. * _PyEval_Fini() now always call _Py_PrintSpecializationStats() which does nothing if stats are all zeros. Co-authored-by: Michael Droettboom <mdboom@gmail.com>
Diffstat (limited to 'Python')
-rw-r--r--Python/ceval_gil.c5
-rw-r--r--Python/ceval_macros.h2
-rw-r--r--Python/clinic/sysmodule.c.h24
-rw-r--r--Python/initconfig.c34
-rw-r--r--Python/specialize.c98
-rw-r--r--Python/sysmodule.c28
6 files changed, 136 insertions, 55 deletions
diff --git a/Python/ceval_gil.c b/Python/ceval_gil.c
index 7c9ad07..e53ffa7 100644
--- a/Python/ceval_gil.c
+++ b/Python/ceval_gil.c
@@ -2,11 +2,12 @@
#include "Python.h"
#include "pycore_atomic.h" // _Py_atomic_int
#include "pycore_ceval.h" // _PyEval_SignalReceived()
-#include "pycore_pyerrors.h" // _PyErr_GetRaisedException()
-#include "pycore_pylifecycle.h" // _PyErr_Print()
#include "pycore_initconfig.h" // _PyStatus_OK()
#include "pycore_interp.h" // _Py_RunGC()
+#include "pycore_pyerrors.h" // _PyErr_GetRaisedException()
+#include "pycore_pylifecycle.h" // _PyErr_Print()
#include "pycore_pymem.h" // _PyMem_IsPtrFreed()
+#include "pycore_pystats.h" // _Py_PrintSpecializationStats()
/*
Notes about the implementation:
diff --git a/Python/ceval_macros.h b/Python/ceval_macros.h
index 4b7c444..81fbb79 100644
--- a/Python/ceval_macros.h
+++ b/Python/ceval_macros.h
@@ -64,7 +64,7 @@
do { \
frame->prev_instr = next_instr++; \
OPCODE_EXE_INC(op); \
- if (_py_stats) _py_stats->opcode_stats[lastopcode].pair_count[op]++; \
+ if (_Py_stats) _Py_stats->opcode_stats[lastopcode].pair_count[op]++; \
lastopcode = op; \
} while (0)
#else
diff --git a/Python/clinic/sysmodule.c.h b/Python/clinic/sysmodule.c.h
index a4b3987..30691c3 100644
--- a/Python/clinic/sysmodule.c.h
+++ b/Python/clinic/sysmodule.c.h
@@ -1120,7 +1120,7 @@ PyDoc_STRVAR(sys__stats_on__doc__,
"_stats_on($module, /)\n"
"--\n"
"\n"
-"Turns on stats gathering (stats gathering is on by default).");
+"Turns on stats gathering (stats gathering is off by default).");
#define SYS__STATS_ON_METHODDEF \
{"_stats_on", (PyCFunction)sys__stats_on, METH_NOARGS, sys__stats_on__doc__},
@@ -1142,7 +1142,7 @@ PyDoc_STRVAR(sys__stats_off__doc__,
"_stats_off($module, /)\n"
"--\n"
"\n"
-"Turns off stats gathering (stats gathering is on by default).");
+"Turns off stats gathering (stats gathering is off by default).");
#define SYS__STATS_OFF_METHODDEF \
{"_stats_off", (PyCFunction)sys__stats_off, METH_NOARGS, sys__stats_off__doc__},
@@ -1186,18 +1186,30 @@ PyDoc_STRVAR(sys__stats_dump__doc__,
"_stats_dump($module, /)\n"
"--\n"
"\n"
-"Dump stats to file, and clears the stats.");
+"Dump stats to file, and clears the stats.\n"
+"\n"
+"Return False if no statistics were not dumped because stats gathering was off.");
#define SYS__STATS_DUMP_METHODDEF \
{"_stats_dump", (PyCFunction)sys__stats_dump, METH_NOARGS, sys__stats_dump__doc__},
-static PyObject *
+static int
sys__stats_dump_impl(PyObject *module);
static PyObject *
sys__stats_dump(PyObject *module, PyObject *Py_UNUSED(ignored))
{
- return sys__stats_dump_impl(module);
+ PyObject *return_value = NULL;
+ int _return_value;
+
+ _return_value = sys__stats_dump_impl(module);
+ if ((_return_value == -1) && PyErr_Occurred()) {
+ goto exit;
+ }
+ return_value = PyBool_FromLong((long)_return_value);
+
+exit:
+ return return_value;
}
#endif /* defined(Py_STATS) */
@@ -1411,4 +1423,4 @@ exit:
#ifndef SYS_GETANDROIDAPILEVEL_METHODDEF
#define SYS_GETANDROIDAPILEVEL_METHODDEF
#endif /* !defined(SYS_GETANDROIDAPILEVEL_METHODDEF) */
-/*[clinic end generated code: output=6619682ea70e7375 input=a9049054013a1b77]*/
+/*[clinic end generated code: output=549bb1f92a15f916 input=a9049054013a1b77]*/
diff --git a/Python/initconfig.c b/Python/initconfig.c
index f9c5c64..a0467f5 100644
--- a/Python/initconfig.c
+++ b/Python/initconfig.c
@@ -9,6 +9,7 @@
#include "pycore_pylifecycle.h" // _Py_PreInitializeFromConfig()
#include "pycore_pymem.h" // _PyMem_SetDefaultAllocator()
#include "pycore_pystate.h" // _PyThreadState_GET()
+#include "pycore_pystats.h" // _Py_StatsOn()
#include "osdefs.h" // DELIM
@@ -186,7 +187,11 @@ static const char usage_envvars[] =
"PYTHONSAFEPATH : don't prepend a potentially unsafe path to sys.path (-P)\n"
"PYTHONUNBUFFERED : disable stdout/stderr buffering (-u)\n"
"PYTHONVERBOSE : trace import statements (-v)\n"
-"PYTHONWARNINGS=arg : warning control (-W arg)\n";
+"PYTHONWARNINGS=arg : warning control (-W arg)\n"
+#ifdef Py_STATS
+"PYTHONSTATS : turns on statistics gathering\n"
+#endif
+;
#if defined(MS_WINDOWS)
# define PYTHONHOMEHELP "<prefix>\\python{major}{minor}"
@@ -630,6 +635,9 @@ config_check_consistency(const PyConfig *config)
assert(config->int_max_str_digits >= 0);
// config->use_frozen_modules is initialized later
// by _PyConfig_InitImportConfig().
+#ifdef Py_STATS
+ assert(config->_pystats >= 0);
+#endif
return 1;
}
#endif
@@ -951,6 +959,9 @@ _PyConfig_Copy(PyConfig *config, const PyConfig *config2)
COPY_WSTRLIST(orig_argv);
COPY_ATTR(_is_python_build);
COPY_ATTR(int_max_str_digits);
+#ifdef Py_STATS
+ COPY_ATTR(_pystats);
+#endif
#undef COPY_ATTR
#undef COPY_WSTR_ATTR
@@ -1058,6 +1069,9 @@ _PyConfig_AsDict(const PyConfig *config)
SET_ITEM_INT(safe_path);
SET_ITEM_INT(_is_python_build);
SET_ITEM_INT(int_max_str_digits);
+#ifdef Py_STATS
+ SET_ITEM_INT(_pystats);
+#endif
return dict;
@@ -1365,6 +1379,9 @@ _PyConfig_FromDict(PyConfig *config, PyObject *dict)
GET_UINT(safe_path);
GET_UINT(_is_python_build);
GET_INT(int_max_str_digits);
+#ifdef Py_STATS
+ GET_UINT(_pystats);
+#endif
#undef CHECK_VALUE
#undef GET_UINT
@@ -2116,7 +2133,13 @@ config_read(PyConfig *config, int compute_path_config)
#ifdef Py_STATS
if (config_get_xoption(config, L"pystats")) {
- _py_stats = &_py_stats_struct;
+ config->_pystats = 1;
+ }
+ else if (config_get_env(config, "PYTHONSTATS")) {
+ config->_pystats = 1;
+ }
+ if (config->_pystats < 0) {
+ config->_pystats = 0;
}
#endif
@@ -2254,6 +2277,13 @@ _PyConfig_Write(const PyConfig *config, _PyRuntimeState *runtime)
{
return _PyStatus_NO_MEMORY();
}
+
+#ifdef Py_STATS
+ if (config->_pystats) {
+ _Py_StatsOn();
+ }
+#endif
+
return _PyStatus_OK();
}
diff --git a/Python/specialize.c b/Python/specialize.c
index aa40b33..e072167 100644
--- a/Python/specialize.c
+++ b/Python/specialize.c
@@ -13,17 +13,17 @@
#include "pycore_pylifecycle.h" // _PyOS_URandomNonblock()
#include "pycore_runtime.h" // _Py_ID()
-
#include <stdlib.h> // rand()
+
/* For guidance on adding or extending families of instructions see
* ./adaptive.md
*/
#ifdef Py_STATS
GCStats _py_gc_stats[NUM_GENERATIONS] = { 0 };
-PyStats _py_stats_struct = { .gc_stats = &_py_gc_stats[0] };
-PyStats *_py_stats = NULL;
+static PyStats _Py_stats_struct = { .gc_stats = _py_gc_stats };
+PyStats *_Py_stats = NULL;
#define ADD_STAT_TO_DICT(res, field) \
do { \
@@ -83,7 +83,7 @@ add_stat_dict(
int opcode,
const char *name) {
- SpecializationStats *stats = &_py_stats_struct.opcode_stats[opcode].specialization;
+ SpecializationStats *stats = &_Py_stats_struct.opcode_stats[opcode].specialization;
PyObject *d = stats_to_dict(stats);
if (d == NULL) {
return -1;
@@ -93,7 +93,6 @@ add_stat_dict(
return err;
}
-#ifdef Py_STATS
PyObject*
_Py_GetSpecializationStats(void) {
PyObject *stats = PyDict_New();
@@ -120,7 +119,6 @@ _Py_GetSpecializationStats(void) {
}
return stats;
}
-#endif
#define PRINT_STAT(i, field) \
@@ -218,7 +216,8 @@ print_gc_stats(FILE *out, GCStats *stats)
}
static void
-print_stats(FILE *out, PyStats *stats) {
+print_stats(FILE *out, PyStats *stats)
+{
print_spec_stats(out, stats->opcode_stats);
print_call_stats(out, &stats->call_stats);
print_object_stats(out, &stats->object_stats);
@@ -226,18 +225,56 @@ print_stats(FILE *out, PyStats *stats) {
}
void
+_Py_StatsOn(void)
+{
+ _Py_stats = &_Py_stats_struct;
+}
+
+void
+_Py_StatsOff(void)
+{
+ _Py_stats = NULL;
+}
+
+void
_Py_StatsClear(void)
{
- for (int i = 0; i < NUM_GENERATIONS; i++) {
- _py_gc_stats[i] = (GCStats) { 0 };
+ memset(&_py_gc_stats, 0, sizeof(_py_gc_stats));
+ memset(&_Py_stats_struct, 0, sizeof(_Py_stats_struct));
+ _Py_stats_struct.gc_stats = _py_gc_stats;
+}
+
+static int
+mem_is_zero(unsigned char *ptr, size_t size)
+{
+ for (size_t i=0; i < size; i++) {
+ if (*ptr != 0) {
+ return 0;
+ }
+ ptr++;
}
- _py_stats_struct = (PyStats) { 0 };
- _py_stats_struct.gc_stats = _py_gc_stats;
+ return 1;
}
-void
+int
_Py_PrintSpecializationStats(int to_file)
{
+ PyStats *stats = &_Py_stats_struct;
+#define MEM_IS_ZERO(DATA) mem_is_zero((unsigned char*)DATA, sizeof(*(DATA)))
+ int is_zero = (
+ MEM_IS_ZERO(stats->gc_stats) // is a pointer
+ && MEM_IS_ZERO(&stats->opcode_stats)
+ && MEM_IS_ZERO(&stats->call_stats)
+ && MEM_IS_ZERO(&stats->object_stats)
+ );
+#undef MEM_IS_ZERO
+ if (is_zero) {
+ // gh-108753: -X pystats command line was used, but then _stats_off()
+ // and _stats_clear() have been called: in this case, avoid printing
+ // useless "all zeros" statistics.
+ return 0;
+ }
+
FILE *out = stderr;
if (to_file) {
/* Write to a file instead of stderr. */
@@ -268,26 +305,25 @@ _Py_PrintSpecializationStats(int to_file)
else {
fprintf(out, "Specialization stats:\n");
}
- print_stats(out, &_py_stats_struct);
+ print_stats(out, stats);
if (out != stderr) {
fclose(out);
}
+ return 1;
}
-#ifdef Py_STATS
-
#define SPECIALIZATION_FAIL(opcode, kind) \
do { \
- if (_py_stats) { \
- _py_stats->opcode_stats[opcode].specialization.failure_kinds[kind]++; \
+ if (_Py_stats) { \
+ _Py_stats->opcode_stats[opcode].specialization.failure_kinds[kind]++; \
} \
} while (0)
-#endif
-#endif
+#endif // Py_STATS
+
#ifndef SPECIALIZATION_FAIL
-#define SPECIALIZATION_FAIL(opcode, kind) ((void)0)
+# define SPECIALIZATION_FAIL(opcode, kind) ((void)0)
#endif
// Initialize warmup counters and insert superinstructions. This cannot fail.
@@ -1067,7 +1103,7 @@ load_attr_fail_kind(DescriptorClassification kind)
}
Py_UNREACHABLE();
}
-#endif
+#endif // Py_STATS
static int
specialize_class_load_attr(PyObject *owner, _Py_CODEUNIT *instr,
@@ -1306,7 +1342,7 @@ binary_subscr_fail_kind(PyTypeObject *container_type, PyObject *sub)
}
return SPEC_FAIL_OTHER;
}
-#endif
+#endif // Py_STATS
static int
function_kind(PyCodeObject *code) {
@@ -1545,7 +1581,7 @@ _Py_Specialize_StoreSubscr(PyObject *container, PyObject *sub, _Py_CODEUNIT *ins
}
goto fail;
}
-#endif
+#endif // Py_STATS
SPECIALIZATION_FAIL(STORE_SUBSCR, SPEC_FAIL_OTHER);
fail:
STAT_INC(STORE_SUBSCR, failure);
@@ -1690,7 +1726,7 @@ meth_descr_call_fail_kind(int ml_flags)
return SPEC_FAIL_CALL_BAD_CALL_FLAGS;
}
}
-#endif
+#endif // Py_STATS
static int
specialize_method_descriptor(PyMethodDescrObject *descr, _Py_CODEUNIT *instr,
@@ -1871,7 +1907,7 @@ call_fail_kind(PyObject *callable)
}
return SPEC_FAIL_OTHER;
}
-#endif
+#endif // Py_STATS
/* TODO:
@@ -1995,7 +2031,7 @@ binary_op_fail_kind(int oparg, PyObject *lhs, PyObject *rhs)
}
Py_UNREACHABLE();
}
-#endif
+#endif // Py_STATS
void
_Py_Specialize_BinaryOp(PyObject *lhs, PyObject *rhs, _Py_CODEUNIT *instr,
@@ -2102,7 +2138,7 @@ compare_op_fail_kind(PyObject *lhs, PyObject *rhs)
}
return SPEC_FAIL_OTHER;
}
-#endif
+#endif // Py_STATS
void
_Py_Specialize_CompareOp(PyObject *lhs, PyObject *rhs, _Py_CODEUNIT *instr,
@@ -2165,7 +2201,7 @@ unpack_sequence_fail_kind(PyObject *seq)
}
return SPEC_FAIL_OTHER;
}
-#endif
+#endif // Py_STATS
void
_Py_Specialize_UnpackSequence(PyObject *seq, _Py_CODEUNIT *instr, int oparg)
@@ -2206,7 +2242,6 @@ success:
}
#ifdef Py_STATS
-
int
_PySpecialization_ClassifyIterator(PyObject *iter)
{
@@ -2277,8 +2312,7 @@ int
}
return SPEC_FAIL_OTHER;
}
-
-#endif
+#endif // Py_STATS
void
_Py_Specialize_ForIter(PyObject *iter, _Py_CODEUNIT *instr, int oparg)
@@ -2431,7 +2465,7 @@ _Py_Specialize_ToBool(PyObject *value, _Py_CODEUNIT *instr)
goto failure;
}
SPECIALIZATION_FAIL(TO_BOOL, SPEC_FAIL_OTHER);
-#endif
+#endif // Py_STATS
failure:
STAT_INC(TO_BOOL, failure);
instr->op.code = TO_BOOL;
diff --git a/Python/sysmodule.c b/Python/sysmodule.c
index 3835f76..fed1281 100644
--- a/Python/sysmodule.c
+++ b/Python/sysmodule.c
@@ -30,6 +30,7 @@ Data members:
#include "pycore_pymath.h" // _PY_SHORT_FLOAT_REPR
#include "pycore_pymem.h" // _PyMem_SetDefaultAllocator()
#include "pycore_pystate.h" // _PyThreadState_GET()
+#include "pycore_pystats.h" // _Py_PrintSpecializationStats()
#include "pycore_structseq.h" // _PyStructSequence_InitBuiltinWithFlags()
#include "pycore_sysmodule.h" // export _PySys_GetSizeOf()
#include "pycore_tuple.h" // _PyTuple_FromArray()
@@ -2106,32 +2107,33 @@ sys_is_finalizing_impl(PyObject *module)
return PyBool_FromLong(Py_IsFinalizing());
}
+
#ifdef Py_STATS
/*[clinic input]
sys._stats_on
-Turns on stats gathering (stats gathering is on by default).
+Turns on stats gathering (stats gathering is off by default).
[clinic start generated code]*/
static PyObject *
sys__stats_on_impl(PyObject *module)
-/*[clinic end generated code: output=aca53eafcbb4d9fe input=8ddc6df94e484f3a]*/
+/*[clinic end generated code: output=aca53eafcbb4d9fe input=43b5bfe145299e55]*/
{
- _py_stats = &_py_stats_struct;
+ _Py_StatsOn();
Py_RETURN_NONE;
}
/*[clinic input]
sys._stats_off
-Turns off stats gathering (stats gathering is on by default).
+Turns off stats gathering (stats gathering is off by default).
[clinic start generated code]*/
static PyObject *
sys__stats_off_impl(PyObject *module)
-/*[clinic end generated code: output=1534c1ee63812214 input=b3e50e71ecf29f66]*/
+/*[clinic end generated code: output=1534c1ee63812214 input=d1a84c60c56cbce2]*/
{
- _py_stats = NULL;
+ _Py_StatsOff();
Py_RETURN_NONE;
}
@@ -2150,21 +2152,23 @@ sys__stats_clear_impl(PyObject *module)
}
/*[clinic input]
-sys._stats_dump
+sys._stats_dump -> bool
Dump stats to file, and clears the stats.
+
+Return False if no statistics were not dumped because stats gathering was off.
[clinic start generated code]*/
-static PyObject *
+static int
sys__stats_dump_impl(PyObject *module)
-/*[clinic end generated code: output=79f796fb2b4ddf05 input=92346f16d64f6f95]*/
+/*[clinic end generated code: output=6e346b4ba0de4489 input=31a489e39418b2a5]*/
{
- _Py_PrintSpecializationStats(1);
+ int res = _Py_PrintSpecializationStats(1);
_Py_StatsClear();
- Py_RETURN_NONE;
+ return res;
}
+#endif // Py_STATS
-#endif
#ifdef ANDROID_API_LEVEL
/*[clinic input]