gh-108753: Enhance pystats (#108754) · python/cpython@a0773b8 · GitHub
Skip to content

Commit a0773b8

Browse files
vstinnermdboom
andauthored
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>
1 parent 8ff1142 commit a0773b8

19 files changed

Lines changed: 402 additions & 183 deletions

Doc/using/configure.rst

Lines changed: 58 additions & 3 deletions

Include/cpython/initconfig.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,11 @@ typedef struct PyConfig {
215215

216216
// If non-zero, we believe we're running from a source tree.
217217
int _is_python_build;
218+
219+
#ifdef Py_STATS
220+
// If non-zero, turns on statistics gathering.
221+
int _pystats;
222+
#endif
218223
} PyConfig;
219224

220225
PyAPI_FUNC(void) PyConfig_InitPythonConfig(PyConfig *config);

Include/cpython/pystats.h

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
// Statistics on Python performance.
2+
//
3+
// API:
4+
//
5+
// - _Py_INCREF_STAT_INC() and _Py_DECREF_STAT_INC() used by Py_INCREF()
6+
// and Py_DECREF().
7+
// - _Py_stats variable
8+
//
9+
// Functions of the sys module:
10+
//
11+
// - sys._stats_on()
12+
// - sys._stats_off()
13+
// - sys._stats_clear()
14+
// - sys._stats_dump()
15+
//
16+
// Python must be built with ./configure --enable-pystats to define the
17+
// Py_STATS macro.
18+
//
19+
// Define _PY_INTERPRETER macro to increment interpreter_increfs and
20+
// interpreter_decrefs. Otherwise, increment increfs and decrefs.
21+
22+
#ifndef Py_CPYTHON_PYSTATS_H
23+
# error "this header file must not be included directly"
24+
#endif
25+
26+
#define SPECIALIZATION_FAILURE_KINDS 36
27+
28+
/* Stats for determining who is calling PyEval_EvalFrame */
29+
#define EVAL_CALL_TOTAL 0
30+
#define EVAL_CALL_VECTOR 1
31+
#define EVAL_CALL_GENERATOR 2
32+
#define EVAL_CALL_LEGACY 3
33+
#define EVAL_CALL_FUNCTION_VECTORCALL 4
34+
#define EVAL_CALL_BUILD_CLASS 5
35+
#define EVAL_CALL_SLOT 6
36+
#define EVAL_CALL_FUNCTION_EX 7
37+
#define EVAL_CALL_API 8
38+
#define EVAL_CALL_METHOD 9
39+
40+
#define EVAL_CALL_KINDS 10
41+
42+
typedef struct _specialization_stats {
43+
uint64_t success;
44+
uint64_t failure;
45+
uint64_t hit;
46+
uint64_t deferred;
47+
uint64_t miss;
48+
uint64_t deopt;
49+
uint64_t failure_kinds[SPECIALIZATION_FAILURE_KINDS];
50+
} SpecializationStats;
51+
52+
typedef struct _opcode_stats {
53+
SpecializationStats specialization;
54+
uint64_t execution_count;
55+
uint64_t pair_count[256];
56+
} OpcodeStats;
57+
58+
typedef struct _call_stats {
59+
uint64_t inlined_py_calls;
60+
uint64_t pyeval_calls;
61+
uint64_t frames_pushed;
62+
uint64_t frame_objects_created;
63+
uint64_t eval_calls[EVAL_CALL_KINDS];
64+
} CallStats;
65+
66+
typedef struct _object_stats {
67+
uint64_t increfs;
68+
uint64_t decrefs;
69+
uint64_t interpreter_increfs;
70+
uint64_t interpreter_decrefs;
71+
uint64_t allocations;
72+
uint64_t allocations512;
73+
uint64_t allocations4k;
74+
uint64_t allocations_big;
75+
uint64_t frees;
76+
uint64_t to_freelist;
77+
uint64_t from_freelist;
78+
uint64_t new_values;
79+
uint64_t dict_materialized_on_request;
80+
uint64_t dict_materialized_new_key;
81+
uint64_t dict_materialized_too_big;
82+
uint64_t dict_materialized_str_subclass;
83+
uint64_t dict_dematerialized;
84+
uint64_t type_cache_hits;
85+
uint64_t type_cache_misses;
86+
uint64_t type_cache_dunder_hits;
87+
uint64_t type_cache_dunder_misses;
88+
uint64_t type_cache_collisions;
89+
uint64_t optimization_attempts;
90+
uint64_t optimization_traces_created;
91+
uint64_t optimization_traces_executed;
92+
uint64_t optimization_uops_executed;
93+
/* Temporary value used during GC */
94+
uint64_t object_visits;
95+
} ObjectStats;
96+
97+
typedef struct _gc_stats {
98+
uint64_t collections;
99+
uint64_t object_visits;
100+
uint64_t objects_collected;
101+
} GCStats;
102+
103+
typedef struct _stats {
104+
OpcodeStats opcode_stats[256];
105+
CallStats call_stats;
106+
ObjectStats object_stats;
107+
GCStats *gc_stats;
108+
} PyStats;
109+
110+
111+
// Export for shared extensions like 'math'
112+
PyAPI_DATA(PyStats*) _Py_stats;
113+
114+
#ifdef _PY_INTERPRETER
115+
# define _Py_INCREF_STAT_INC() do { if (_Py_stats) _Py_stats->object_stats.interpreter_increfs++; } while (0)
116+
# define _Py_DECREF_STAT_INC() do { if (_Py_stats) _Py_stats->object_stats.interpreter_decrefs++; } while (0)
117+
#else
118+
# define _Py_INCREF_STAT_INC() do { if (_Py_stats) _Py_stats->object_stats.increfs++; } while (0)
119+
# define _Py_DECREF_STAT_INC() do { if (_Py_stats) _Py_stats->object_stats.decrefs++; } while (0)
120+
#endif

Include/internal/pycore_code.h

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -268,17 +268,17 @@ extern int _PyStaticCode_Init(PyCodeObject *co);
268268

269269
#ifdef Py_STATS
270270

271-
#define STAT_INC(opname, name) do { if (_py_stats) _py_stats->opcode_stats[opname].specialization.name++; } while (0)
272-
#define STAT_DEC(opname, name) do { if (_py_stats) _py_stats->opcode_stats[opname].specialization.name--; } while (0)
273-
#define OPCODE_EXE_INC(opname) do { if (_py_stats) _py_stats->opcode_stats[opname].execution_count++; } while (0)
274-
#define CALL_STAT_INC(name) do { if (_py_stats) _py_stats->call_stats.name++; } while (0)
275-
#define OBJECT_STAT_INC(name) do { if (_py_stats) _py_stats->object_stats.name++; } while (0)
271+
#define STAT_INC(opname, name) do { if (_Py_stats) _Py_stats->opcode_stats[opname].specialization.name++; } while (0)
272+
#define STAT_DEC(opname, name) do { if (_Py_stats) _Py_stats->opcode_stats[opname].specialization.name--; } while (0)
273+
#define OPCODE_EXE_INC(opname) do { if (_Py_stats) _Py_stats->opcode_stats[opname].execution_count++; } while (0)
274+
#define CALL_STAT_INC(name) do { if (_Py_stats) _Py_stats->call_stats.name++; } while (0)
275+
#define OBJECT_STAT_INC(name) do { if (_Py_stats) _Py_stats->object_stats.name++; } while (0)
276276
#define OBJECT_STAT_INC_COND(name, cond) \
277-
do { if (_py_stats && cond) _py_stats->object_stats.name++; } while (0)
278-
#define EVAL_CALL_STAT_INC(name) do { if (_py_stats) _py_stats->call_stats.eval_calls[name]++; } while (0)
277+
do { if (_Py_stats && cond) _Py_stats->object_stats.name++; } while (0)
278+
#define EVAL_CALL_STAT_INC(name) do { if (_Py_stats) _Py_stats->call_stats.eval_calls[name]++; } while (0)
279279
#define EVAL_CALL_STAT_INC_IF_FUNCTION(name, callable) \
280-
do { if (_py_stats && PyFunction_Check(callable)) _py_stats->call_stats.eval_calls[name]++; } while (0)
281-
#define GC_STAT_ADD(gen, name, n) do { if (_py_stats) _py_stats->gc_stats[(gen)].name += (n); } while (0)
280+
do { if (_Py_stats && PyFunction_Check(callable)) _Py_stats->call_stats.eval_calls[name]++; } while (0)
281+
#define GC_STAT_ADD(gen, name, n) do { if (_Py_stats) _Py_stats->gc_stats[(gen)].name += (n); } while (0)
282282

283283
// Export for '_opcode' shared extension
284284
PyAPI_FUNC(PyObject*) _Py_GetSpecializationStats(void);

Include/internal/pycore_pystats.h

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
#ifndef Py_INTERNAL_PYSTATS_H
2+
#define Py_INTERNAL_PYSTATS_H
3+
#ifdef __cplusplus
4+
extern "C" {
5+
#endif
6+
7+
#ifndef Py_BUILD_CORE
8+
# error "this header requires Py_BUILD_CORE define"
9+
#endif
10+
11+
#ifdef Py_STATS
12+
extern void _Py_StatsOn(void);
13+
extern void _Py_StatsOff(void);
14+
extern void _Py_StatsClear(void);
15+
extern int _Py_PrintSpecializationStats(int to_file);
16+
#endif
17+
18+
#ifdef __cplusplus
19+
}
20+
#endif
21+
#endif // !Py_INTERNAL_PYSTATS_H

Include/pystats.h

Lines changed: 13 additions & 111 deletions

0 commit comments

Comments
 (0)