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

markshannon webhook-mailer at python.org
Tue Jun 21 10:41:04 EDT 2022


https://github.com/python/cpython/commit/6f8875eba38b08c802905635759b5f905e3a415c
commit: 6f8875eba38b08c802905635759b5f905e3a415c
branch: main
author: Mark Shannon <mark at hotpy.org>
committer: markshannon <mark at hotpy.org>
date: 2022-06-21T15:40:54+01:00
summary:

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

files:
A Misc/NEWS.d/next/Core and Builtins/2022-06-15-11-16-13.gh-issue-93841.06zqX3.rst
M Include/internal/pycore_code.h
M Include/pystats.h
M Python/ceval.c
M Python/clinic/sysmodule.c.h
M Python/specialize.c
M Python/sysmodule.c

diff --git a/Include/internal/pycore_code.h b/Include/internal/pycore_code.h
index 71dd3b3b42065..c975f1cb74f3d 100644
--- a/Include/internal/pycore_code.h
+++ b/Include/internal/pycore_code.h
@@ -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);
diff --git a/Include/pystats.h b/Include/pystats.h
index bea471bfc1de9..87e92aa4f05fa 100644
--- a/Include/pystats.h
+++ b/Include/pystats.h
@@ -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
 
diff --git a/Misc/NEWS.d/next/Core and Builtins/2022-06-15-11-16-13.gh-issue-93841.06zqX3.rst b/Misc/NEWS.d/next/Core and Builtins/2022-06-15-11-16-13.gh-issue-93841.06zqX3.rst
new file mode 100644
index 0000000000000..179d3808e3b4d
--- /dev/null
+++ b/Misc/NEWS.d/next/Core and Builtins/2022-06-15-11-16-13.gh-issue-93841.06zqX3.rst	
@@ -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.
diff --git a/Python/ceval.c b/Python/ceval.c
index bdae333ea41c3..47dd1008c35ce 100644
--- a/Python/ceval.c
+++ b/Python/ceval.c
@@ -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);
diff --git a/Python/clinic/sysmodule.c.h b/Python/clinic/sysmodule.c.h
index 6ee3bb2a849aa..76b4cc5578263 100644
--- a/Python/clinic/sysmodule.c.h
+++ b/Python/clinic/sysmodule.c.h
@@ -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]*/
diff --git a/Python/specialize.c b/Python/specialize.c
index c9cf35f6f2991..948fb328695bb 100644
--- a/Python/specialize.c
+++ b/Python/specialize.c
@@ -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
diff --git a/Python/sysmodule.c b/Python/sysmodule.c
index 8a9a58479aecc..444042f82d947 100644
--- a/Python/sysmodule.c
+++ b/Python/sysmodule.c
@@ -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
 };
 



More information about the Python-checkins mailing list