Skip to content

Commit 6f8875e

Browse files
authoredJun 21, 2022
GH-93841: Allow stats to be turned on and off, cleared and dumped at runtime. (GH-93843)
1 parent c7a79bb commit 6f8875e

File tree

7 files changed

+212
-22
lines changed

7 files changed

+212
-22
lines changed
 

‎Include/internal/pycore_code.h

+8-8
Original file line numberDiff line numberDiff line change
@@ -259,16 +259,16 @@ extern int _PyStaticCode_InternStrings(PyCodeObject *co);
259259
#ifdef Py_STATS
260260

261261

262-
#define STAT_INC(opname, name) _py_stats.opcode_stats[(opname)].specialization.name++
263-
#define STAT_DEC(opname, name) _py_stats.opcode_stats[(opname)].specialization.name--
264-
#define OPCODE_EXE_INC(opname) _py_stats.opcode_stats[(opname)].execution_count++
265-
#define CALL_STAT_INC(name) _py_stats.call_stats.name++
266-
#define OBJECT_STAT_INC(name) _py_stats.object_stats.name++
262+
#define STAT_INC(opname, name) do { if (_py_stats) _py_stats->opcode_stats[opname].specialization.name++; } while (0)
263+
#define STAT_DEC(opname, name) do { if (_py_stats) _py_stats->opcode_stats[opname].specialization.name--; } while (0)
264+
#define OPCODE_EXE_INC(opname) do { if (_py_stats) _py_stats->opcode_stats[opname].execution_count++; } while (0)
265+
#define CALL_STAT_INC(name) do { if (_py_stats) _py_stats->call_stats.name++; } while (0)
266+
#define OBJECT_STAT_INC(name) do { if (_py_stats) _py_stats->object_stats.name++; } while (0)
267267
#define OBJECT_STAT_INC_COND(name, cond) \
268-
do { if (cond) _py_stats.object_stats.name++; } while (0)
269-
#define EVAL_CALL_STAT_INC(name) _py_stats.call_stats.eval_calls[(name)]++
268+
do { if (_py_stats && cond) _py_stats->object_stats.name++; } while (0)
269+
#define EVAL_CALL_STAT_INC(name) do { if (_py_stats) _py_stats->call_stats.eval_calls[name]++; } while (0)
270270
#define EVAL_CALL_STAT_INC_IF_FUNCTION(name, callable) \
271-
do { if (PyFunction_Check(callable)) _py_stats.call_stats.eval_calls[(name)]++; } while (0)
271+
do { if (_py_stats && PyFunction_Check(callable)) _py_stats->call_stats.eval_calls[name]++; } while (0)
272272

273273
// Used by the _opcode extension which is built as a shared library
274274
PyAPI_FUNC(PyObject*) _Py_GetSpecializationStats(void);

‎Include/pystats.h

+8-5
Original file line numberDiff line numberDiff line change
@@ -73,19 +73,22 @@ typedef struct _stats {
7373
ObjectStats object_stats;
7474
} PyStats;
7575

76-
PyAPI_DATA(PyStats) _py_stats;
7776

77+
PyAPI_DATA(PyStats) _py_stats_struct;
78+
PyAPI_DATA(PyStats *) _py_stats;
79+
80+
extern void _Py_StatsClear(void);
7881
extern void _Py_PrintSpecializationStats(int to_file);
7982

8083
#ifdef _PY_INTERPRETER
8184

82-
#define _Py_INCREF_STAT_INC() _py_stats.object_stats.interpreter_increfs++
83-
#define _Py_DECREF_STAT_INC() _py_stats.object_stats.interpreter_decrefs++
85+
#define _Py_INCREF_STAT_INC() do { if (_py_stats) _py_stats->object_stats.interpreter_increfs++; } while (0)
86+
#define _Py_DECREF_STAT_INC() do { if (_py_stats) _py_stats->object_stats.interpreter_decrefs++; } while (0)
8487

8588
#else
8689

87-
#define _Py_INCREF_STAT_INC() _py_stats.object_stats.increfs++
88-
#define _Py_DECREF_STAT_INC() _py_stats.object_stats.decrefs++
90+
#define _Py_INCREF_STAT_INC() do { if (_py_stats) _py_stats->object_stats.increfs++; } while (0)
91+
#define _Py_DECREF_STAT_INC() do { if (_py_stats) _py_stats->object_stats.decrefs++; } while (0)
8992

9093
#endif
9194

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

‎Python/ceval.c

+3-3
Original file line numberDiff line numberDiff line change
@@ -1310,7 +1310,7 @@ eval_frame_handle_pending(PyThreadState *tstate)
13101310
do { \
13111311
frame->prev_instr = next_instr++; \
13121312
OPCODE_EXE_INC(op); \
1313-
_py_stats.opcode_stats[lastopcode].pair_count[op]++; \
1313+
if (_py_stats) _py_stats->opcode_stats[lastopcode].pair_count[op]++; \
13141314
lastopcode = op; \
13151315
} while (0)
13161316
#else
@@ -7790,7 +7790,7 @@ _Py_GetDXProfile(PyObject *self, PyObject *args)
77907790
PyObject *l = PyList_New(257);
77917791
if (l == NULL) return NULL;
77927792
for (i = 0; i < 256; i++) {
7793-
PyObject *x = getarray(_py_stats.opcode_stats[i].pair_count);
7793+
PyObject *x = getarray(_py_stats_struct.opcode_stats[i].pair_count);
77947794
if (x == NULL) {
77957795
Py_DECREF(l);
77967796
return NULL;
@@ -7804,7 +7804,7 @@ _Py_GetDXProfile(PyObject *self, PyObject *args)
78047804
}
78057805
for (i = 0; i < 256; i++) {
78067806
PyObject *x = PyLong_FromUnsignedLongLong(
7807-
_py_stats.opcode_stats[i].execution_count);
7807+
_py_stats_struct.opcode_stats[i].execution_count);
78087808
if (x == NULL) {
78097809
Py_DECREF(counts);
78107810
Py_DECREF(l);

‎Python/clinic/sysmodule.c.h

+105-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎Python/specialize.c

+19-5
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,8 @@ uint8_t _PyOpcode_Adaptive[256] = {
3333

3434
Py_ssize_t _Py_QuickenedCount = 0;
3535
#ifdef Py_STATS
36-
PyStats _py_stats = { 0 };
36+
PyStats _py_stats_struct = { 0 };
37+
PyStats *_py_stats = &_py_stats_struct;
3738

3839
#define ADD_STAT_TO_DICT(res, field) \
3940
do { \
@@ -93,7 +94,7 @@ add_stat_dict(
9394
int opcode,
9495
const char *name) {
9596

96-
SpecializationStats *stats = &_py_stats.opcode_stats[opcode].specialization;
97+
SpecializationStats *stats = &_py_stats_struct.opcode_stats[opcode].specialization;
9798
PyObject *d = stats_to_dict(stats);
9899
if (d == NULL) {
99100
return -1;
@@ -209,9 +210,18 @@ print_stats(FILE *out, PyStats *stats) {
209210
print_object_stats(out, &stats->object_stats);
210211
}
211212

213+
void
214+
_Py_StatsClear(void)
215+
{
216+
_py_stats_struct = (PyStats) { 0 };
217+
}
218+
212219
void
213220
_Py_PrintSpecializationStats(int to_file)
214221
{
222+
if (_py_stats == NULL) {
223+
return;
224+
}
215225
FILE *out = stderr;
216226
if (to_file) {
217227
/* Write to a file instead of stderr. */
@@ -242,16 +252,20 @@ _Py_PrintSpecializationStats(int to_file)
242252
else {
243253
fprintf(out, "Specialization stats:\n");
244254
}
245-
print_stats(out, &_py_stats);
255+
print_stats(out, _py_stats);
246256
if (out != stderr) {
247257
fclose(out);
248258
}
249259
}
250260

251261
#ifdef Py_STATS
252262

253-
#define SPECIALIZATION_FAIL(opcode, kind) _py_stats.opcode_stats[opcode].specialization.failure_kinds[kind]++
254-
263+
#define SPECIALIZATION_FAIL(opcode, kind) \
264+
do { \
265+
if (_py_stats) { \
266+
_py_stats->opcode_stats[opcode].specialization.failure_kinds[kind]++; \
267+
} \
268+
} while (0)
255269

256270
#endif
257271
#endif

‎Python/sysmodule.c

+66
Original file line numberDiff line numberDiff line change
@@ -1909,6 +1909,66 @@ sys_is_finalizing_impl(PyObject *module)
19091909
return PyBool_FromLong(_Py_IsFinalizing());
19101910
}
19111911

1912+
#ifdef Py_STATS
1913+
/*[clinic input]
1914+
sys._stats_on
1915+
1916+
Turns on stats gathering (stats gathering is on by default).
1917+
[clinic start generated code]*/
1918+
1919+
static PyObject *
1920+
sys__stats_on_impl(PyObject *module)
1921+
/*[clinic end generated code: output=aca53eafcbb4d9fe input=8ddc6df94e484f3a]*/
1922+
{
1923+
_py_stats = &_py_stats_struct;
1924+
Py_RETURN_NONE;
1925+
}
1926+
1927+
/*[clinic input]
1928+
sys._stats_off
1929+
1930+
Turns off stats gathering (stats gathering is on by default).
1931+
[clinic start generated code]*/
1932+
1933+
static PyObject *
1934+
sys__stats_off_impl(PyObject *module)
1935+
/*[clinic end generated code: output=1534c1ee63812214 input=b3e50e71ecf29f66]*/
1936+
{
1937+
_py_stats = NULL;
1938+
Py_RETURN_NONE;
1939+
}
1940+
1941+
/*[clinic input]
1942+
sys._stats_clear
1943+
1944+
Clears the stats.
1945+
[clinic start generated code]*/
1946+
1947+
static PyObject *
1948+
sys__stats_clear_impl(PyObject *module)
1949+
/*[clinic end generated code: output=fb65a2525ee50604 input=3e03f2654f44da96]*/
1950+
{
1951+
_Py_StatsClear();
1952+
Py_RETURN_NONE;
1953+
}
1954+
1955+
/*[clinic input]
1956+
sys._stats_dump
1957+
1958+
Dump stats to file, and clears the stats.
1959+
[clinic start generated code]*/
1960+
1961+
static PyObject *
1962+
sys__stats_dump_impl(PyObject *module)
1963+
/*[clinic end generated code: output=79f796fb2b4ddf05 input=92346f16d64f6f95]*/
1964+
{
1965+
_Py_PrintSpecializationStats(1);
1966+
_Py_StatsClear();
1967+
Py_RETURN_NONE;
1968+
}
1969+
1970+
#endif
1971+
19121972
#ifdef ANDROID_API_LEVEL
19131973
/*[clinic input]
19141974
sys.getandroidapilevel
@@ -1978,6 +2038,12 @@ static PyMethodDef sys_methods[] = {
19782038
SYS_GET_ASYNCGEN_HOOKS_METHODDEF
19792039
SYS_GETANDROIDAPILEVEL_METHODDEF
19802040
SYS_UNRAISABLEHOOK_METHODDEF
2041+
#ifdef Py_STATS
2042+
SYS__STATS_ON_METHODDEF
2043+
SYS__STATS_OFF_METHODDEF
2044+
SYS__STATS_CLEAR_METHODDEF
2045+
SYS__STATS_DUMP_METHODDEF
2046+
#endif
19812047
{NULL, NULL} // sentinel
19822048
};
19832049

0 commit comments

Comments
 (0)
Please sign in to comment.