GH-93841: Allow stats to be turned on and off, cleared and dumped at runtime. (GH-93843)

This commit is contained in:
Mark Shannon 2022-06-21 15:40:54 +01:00 committed by GitHub
parent c7a79bb036
commit 6f8875eba3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 212 additions and 22 deletions

View file

@ -259,16 +259,16 @@ extern int _PyStaticCode_InternStrings(PyCodeObject *co);
#ifdef Py_STATS
#define STAT_INC(opname, name) _py_stats.opcode_stats[(opname)].specialization.name++
#define STAT_DEC(opname, name) _py_stats.opcode_stats[(opname)].specialization.name--
#define OPCODE_EXE_INC(opname) _py_stats.opcode_stats[(opname)].execution_count++
#define CALL_STAT_INC(name) _py_stats.call_stats.name++
#define OBJECT_STAT_INC(name) _py_stats.object_stats.name++
#define STAT_INC(opname, name) do { if (_py_stats) _py_stats->opcode_stats[opname].specialization.name++; } while (0)
#define STAT_DEC(opname, name) do { if (_py_stats) _py_stats->opcode_stats[opname].specialization.name--; } while (0)
#define OPCODE_EXE_INC(opname) do { if (_py_stats) _py_stats->opcode_stats[opname].execution_count++; } while (0)
#define CALL_STAT_INC(name) do { if (_py_stats) _py_stats->call_stats.name++; } while (0)
#define OBJECT_STAT_INC(name) do { if (_py_stats) _py_stats->object_stats.name++; } while (0)
#define OBJECT_STAT_INC_COND(name, cond) \
do { if (cond) _py_stats.object_stats.name++; } while (0)
#define EVAL_CALL_STAT_INC(name) _py_stats.call_stats.eval_calls[(name)]++
do { if (_py_stats && cond) _py_stats->object_stats.name++; } while (0)
#define EVAL_CALL_STAT_INC(name) do { if (_py_stats) _py_stats->call_stats.eval_calls[name]++; } while (0)
#define EVAL_CALL_STAT_INC_IF_FUNCTION(name, callable) \
do { if (PyFunction_Check(callable)) _py_stats.call_stats.eval_calls[(name)]++; } while (0)
do { if (_py_stats && PyFunction_Check(callable)) _py_stats->call_stats.eval_calls[name]++; } while (0)
// Used by the _opcode extension which is built as a shared library
PyAPI_FUNC(PyObject*) _Py_GetSpecializationStats(void);

View file

@ -73,19 +73,22 @@ typedef struct _stats {
ObjectStats object_stats;
} PyStats;
PyAPI_DATA(PyStats) _py_stats;
PyAPI_DATA(PyStats) _py_stats_struct;
PyAPI_DATA(PyStats *) _py_stats;
extern void _Py_StatsClear(void);
extern void _Py_PrintSpecializationStats(int to_file);
#ifdef _PY_INTERPRETER
#define _Py_INCREF_STAT_INC() _py_stats.object_stats.interpreter_increfs++
#define _Py_DECREF_STAT_INC() _py_stats.object_stats.interpreter_decrefs++
#define _Py_INCREF_STAT_INC() do { if (_py_stats) _py_stats->object_stats.interpreter_increfs++; } while (0)
#define _Py_DECREF_STAT_INC() do { if (_py_stats) _py_stats->object_stats.interpreter_decrefs++; } while (0)
#else
#define _Py_INCREF_STAT_INC() _py_stats.object_stats.increfs++
#define _Py_DECREF_STAT_INC() _py_stats.object_stats.decrefs++
#define _Py_INCREF_STAT_INC() do { if (_py_stats) _py_stats->object_stats.increfs++; } while (0)
#define _Py_DECREF_STAT_INC() do { if (_py_stats) _py_stats->object_stats.decrefs++; } while (0)
#endif

View file

@ -0,0 +1,3 @@
When built with ``-enable-pystats``, ``sys._stats_on()``,
``sys._stats_off()``, ``sys._stats_clear()`` and ``sys._stats_dump()``
functions have been added to enable gathering stats for parts of programs.

View file

@ -1310,7 +1310,7 @@ eval_frame_handle_pending(PyThreadState *tstate)
do { \
frame->prev_instr = next_instr++; \
OPCODE_EXE_INC(op); \
_py_stats.opcode_stats[lastopcode].pair_count[op]++; \
if (_py_stats) _py_stats->opcode_stats[lastopcode].pair_count[op]++; \
lastopcode = op; \
} while (0)
#else
@ -7790,7 +7790,7 @@ _Py_GetDXProfile(PyObject *self, PyObject *args)
PyObject *l = PyList_New(257);
if (l == NULL) return NULL;
for (i = 0; i < 256; i++) {
PyObject *x = getarray(_py_stats.opcode_stats[i].pair_count);
PyObject *x = getarray(_py_stats_struct.opcode_stats[i].pair_count);
if (x == NULL) {
Py_DECREF(l);
return NULL;
@ -7804,7 +7804,7 @@ _Py_GetDXProfile(PyObject *self, PyObject *args)
}
for (i = 0; i < 256; i++) {
PyObject *x = PyLong_FromUnsignedLongLong(
_py_stats.opcode_stats[i].execution_count);
_py_stats_struct.opcode_stats[i].execution_count);
if (x == NULL) {
Py_DECREF(counts);
Py_DECREF(l);

View file

@ -965,6 +965,94 @@ sys_is_finalizing(PyObject *module, PyObject *Py_UNUSED(ignored))
return sys_is_finalizing_impl(module);
}
#if defined(Py_STATS)
PyDoc_STRVAR(sys__stats_on__doc__,
"_stats_on($module, /)\n"
"--\n"
"\n"
"Turns on stats gathering (stats gathering is on by default).");
#define SYS__STATS_ON_METHODDEF \
{"_stats_on", (PyCFunction)sys__stats_on, METH_NOARGS, sys__stats_on__doc__},
static PyObject *
sys__stats_on_impl(PyObject *module);
static PyObject *
sys__stats_on(PyObject *module, PyObject *Py_UNUSED(ignored))
{
return sys__stats_on_impl(module);
}
#endif /* defined(Py_STATS) */
#if defined(Py_STATS)
PyDoc_STRVAR(sys__stats_off__doc__,
"_stats_off($module, /)\n"
"--\n"
"\n"
"Turns off stats gathering (stats gathering is on by default).");
#define SYS__STATS_OFF_METHODDEF \
{"_stats_off", (PyCFunction)sys__stats_off, METH_NOARGS, sys__stats_off__doc__},
static PyObject *
sys__stats_off_impl(PyObject *module);
static PyObject *
sys__stats_off(PyObject *module, PyObject *Py_UNUSED(ignored))
{
return sys__stats_off_impl(module);
}
#endif /* defined(Py_STATS) */
#if defined(Py_STATS)
PyDoc_STRVAR(sys__stats_clear__doc__,
"_stats_clear($module, /)\n"
"--\n"
"\n"
"Clears the stats.");
#define SYS__STATS_CLEAR_METHODDEF \
{"_stats_clear", (PyCFunction)sys__stats_clear, METH_NOARGS, sys__stats_clear__doc__},
static PyObject *
sys__stats_clear_impl(PyObject *module);
static PyObject *
sys__stats_clear(PyObject *module, PyObject *Py_UNUSED(ignored))
{
return sys__stats_clear_impl(module);
}
#endif /* defined(Py_STATS) */
#if defined(Py_STATS)
PyDoc_STRVAR(sys__stats_dump__doc__,
"_stats_dump($module, /)\n"
"--\n"
"\n"
"Dump stats to file, and clears the stats.");
#define SYS__STATS_DUMP_METHODDEF \
{"_stats_dump", (PyCFunction)sys__stats_dump, METH_NOARGS, sys__stats_dump__doc__},
static PyObject *
sys__stats_dump_impl(PyObject *module);
static PyObject *
sys__stats_dump(PyObject *module, PyObject *Py_UNUSED(ignored))
{
return sys__stats_dump_impl(module);
}
#endif /* defined(Py_STATS) */
#if defined(ANDROID_API_LEVEL)
PyDoc_STRVAR(sys_getandroidapilevel__doc__,
@ -1011,7 +1099,23 @@ sys_getandroidapilevel(PyObject *module, PyObject *Py_UNUSED(ignored))
#define SYS_GETTOTALREFCOUNT_METHODDEF
#endif /* !defined(SYS_GETTOTALREFCOUNT_METHODDEF) */
#ifndef SYS__STATS_ON_METHODDEF
#define SYS__STATS_ON_METHODDEF
#endif /* !defined(SYS__STATS_ON_METHODDEF) */
#ifndef SYS__STATS_OFF_METHODDEF
#define SYS__STATS_OFF_METHODDEF
#endif /* !defined(SYS__STATS_OFF_METHODDEF) */
#ifndef SYS__STATS_CLEAR_METHODDEF
#define SYS__STATS_CLEAR_METHODDEF
#endif /* !defined(SYS__STATS_CLEAR_METHODDEF) */
#ifndef SYS__STATS_DUMP_METHODDEF
#define SYS__STATS_DUMP_METHODDEF
#endif /* !defined(SYS__STATS_DUMP_METHODDEF) */
#ifndef SYS_GETANDROIDAPILEVEL_METHODDEF
#define SYS_GETANDROIDAPILEVEL_METHODDEF
#endif /* !defined(SYS_GETANDROIDAPILEVEL_METHODDEF) */
/*[clinic end generated code: output=98efd34fd9b9b6ab input=a9049054013a1b77]*/
/*[clinic end generated code: output=41122dae1bb7158c input=a9049054013a1b77]*/

View file

@ -33,7 +33,8 @@ uint8_t _PyOpcode_Adaptive[256] = {
Py_ssize_t _Py_QuickenedCount = 0;
#ifdef Py_STATS
PyStats _py_stats = { 0 };
PyStats _py_stats_struct = { 0 };
PyStats *_py_stats = &_py_stats_struct;
#define ADD_STAT_TO_DICT(res, field) \
do { \
@ -93,7 +94,7 @@ add_stat_dict(
int opcode,
const char *name) {
SpecializationStats *stats = &_py_stats.opcode_stats[opcode].specialization;
SpecializationStats *stats = &_py_stats_struct.opcode_stats[opcode].specialization;
PyObject *d = stats_to_dict(stats);
if (d == NULL) {
return -1;
@ -209,9 +210,18 @@ print_stats(FILE *out, PyStats *stats) {
print_object_stats(out, &stats->object_stats);
}
void
_Py_StatsClear(void)
{
_py_stats_struct = (PyStats) { 0 };
}
void
_Py_PrintSpecializationStats(int to_file)
{
if (_py_stats == NULL) {
return;
}
FILE *out = stderr;
if (to_file) {
/* Write to a file instead of stderr. */
@ -242,7 +252,7 @@ _Py_PrintSpecializationStats(int to_file)
else {
fprintf(out, "Specialization stats:\n");
}
print_stats(out, &_py_stats);
print_stats(out, _py_stats);
if (out != stderr) {
fclose(out);
}
@ -250,8 +260,12 @@ _Py_PrintSpecializationStats(int to_file)
#ifdef Py_STATS
#define SPECIALIZATION_FAIL(opcode, kind) _py_stats.opcode_stats[opcode].specialization.failure_kinds[kind]++
#define SPECIALIZATION_FAIL(opcode, kind) \
do { \
if (_py_stats) { \
_py_stats->opcode_stats[opcode].specialization.failure_kinds[kind]++; \
} \
} while (0)
#endif
#endif

View file

@ -1909,6 +1909,66 @@ 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).
[clinic start generated code]*/
static PyObject *
sys__stats_on_impl(PyObject *module)
/*[clinic end generated code: output=aca53eafcbb4d9fe input=8ddc6df94e484f3a]*/
{
_py_stats = &_py_stats_struct;
Py_RETURN_NONE;
}
/*[clinic input]
sys._stats_off
Turns off stats gathering (stats gathering is on by default).
[clinic start generated code]*/
static PyObject *
sys__stats_off_impl(PyObject *module)
/*[clinic end generated code: output=1534c1ee63812214 input=b3e50e71ecf29f66]*/
{
_py_stats = NULL;
Py_RETURN_NONE;
}
/*[clinic input]
sys._stats_clear
Clears the stats.
[clinic start generated code]*/
static PyObject *
sys__stats_clear_impl(PyObject *module)
/*[clinic end generated code: output=fb65a2525ee50604 input=3e03f2654f44da96]*/
{
_Py_StatsClear();
Py_RETURN_NONE;
}
/*[clinic input]
sys._stats_dump
Dump stats to file, and clears the stats.
[clinic start generated code]*/
static PyObject *
sys__stats_dump_impl(PyObject *module)
/*[clinic end generated code: output=79f796fb2b4ddf05 input=92346f16d64f6f95]*/
{
_Py_PrintSpecializationStats(1);
_Py_StatsClear();
Py_RETURN_NONE;
}
#endif
#ifdef ANDROID_API_LEVEL
/*[clinic input]
sys.getandroidapilevel
@ -1978,6 +2038,12 @@ static PyMethodDef sys_methods[] = {
SYS_GET_ASYNCGEN_HOOKS_METHODDEF
SYS_GETANDROIDAPILEVEL_METHODDEF
SYS_UNRAISABLEHOOK_METHODDEF
#ifdef Py_STATS
SYS__STATS_ON_METHODDEF
SYS__STATS_OFF_METHODDEF
SYS__STATS_CLEAR_METHODDEF
SYS__STATS_DUMP_METHODDEF
#endif
{NULL, NULL} // sentinel
};