Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Make pystats global state be per-interpreter.
  • Loading branch information
nascheme committed Jul 30, 2025
commit 7dc919150cb2f92fa5ba189d3ed0cd48b777f77a
19 changes: 1 addition & 18 deletions Include/cpython/pystats.h
Original file line number Diff line number Diff line change
Expand Up @@ -186,12 +186,10 @@ typedef struct _stats {
FTStats ft_stats;
#endif
RareEventStats rare_event_stats;
GCStats *gc_stats;
GCStats gc_stats[3]; // must match NUM_GENERATIONS
} PyStats;


#ifdef Py_GIL_DISABLED

#if defined(HAVE_THREAD_LOCAL) && !defined(Py_BUILD_CORE_MODULE)
extern _Py_thread_local PyStats *_Py_tss_stats;
#endif
Expand All @@ -200,30 +198,15 @@ extern _Py_thread_local PyStats *_Py_tss_stats;
// inline function.
PyAPI_FUNC(PyStats *) _PyStats_GetLocal(void);

#else // !Py_GIL_DISABLED

// Export for shared extensions like 'math'
PyAPI_DATA(PyStats*) _Py_stats;

#endif

// Return pointer to the PyStats structure, NULL if recording is off.
static inline PyStats*
_PyStats_GET(void)
{
#ifdef Py_GIL_DISABLED

#if defined(HAVE_THREAD_LOCAL) && !defined(Py_BUILD_CORE_MODULE)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same

return _Py_tss_stats;
#else
return _PyStats_GetLocal();
#endif

#else // !Py_GIL_DISABLED

return _Py_stats;

#endif
}

#define _Py_STATS_EXPR(expr) \
Expand Down
14 changes: 13 additions & 1 deletion Include/internal/pycore_interp_structs.h
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,7 @@ enum _GCPhase {
};

/* If we change this, we need to change the default value in the
signature of gc.collect. */
signature of gc.collect and change the size of PyStats.gc_stats */
#define NUM_GENERATIONS 3

struct _gc_runtime_state {
Expand Down Expand Up @@ -968,6 +968,18 @@ struct _is {
# ifdef Py_STACKREF_CLOSE_DEBUG
_Py_hashtable_t *closed_stackrefs_table;
# endif
#endif

#ifdef Py_STATS
// true if recording of pystats is on, this is used when new threads
// are created to decide if recording should be on for them
bool pystats_enabled;
// allocated when (and if) stats are first enabled
PyStats *pystats_struct;
#ifdef Py_GIL_DISABLED
// held when pystats related interpreter state is being updated
PyMutex pystats_mutex;
#endif
#endif

/* the initial PyInterpreterState.threads.head */
Expand Down
2 changes: 1 addition & 1 deletion Include/internal/pycore_pystats.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ extern "C" {
#endif

#ifdef Py_STATS
extern void _Py_StatsOn(void);
extern int _Py_StatsOn(void);
extern void _Py_StatsOff(void);
extern void _Py_StatsClear(void);
extern int _Py_PrintSpecializationStats(int to_file);
Expand Down
2 changes: 1 addition & 1 deletion Include/internal/pycore_stats.h
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ PyAPI_FUNC(PyObject*) _Py_GetSpecializationStats(void);
RARE_EVENT_INTERP_INC(interp, name); \
} while (0); \

bool _PyStats_ThreadInit(_PyThreadStateImpl *);
bool _PyStats_ThreadInit(PyInterpreterState *, _PyThreadStateImpl *);
void _PyStats_ThreadFini(_PyThreadStateImpl *);
void _PyStats_Attach(_PyThreadStateImpl *);
void _PyStats_Detach(_PyThreadStateImpl *);
Expand Down
8 changes: 5 additions & 3 deletions Include/internal/pycore_tstate.h
Original file line number Diff line number Diff line change
Expand Up @@ -71,14 +71,16 @@ typedef struct _PyThreadStateImpl {
// When >1, code objects do not immortalize their non-string constants.
int suppress_co_const_immortalization;

#endif // Py_GIL_DISABLED

#ifdef Py_STATS
// per-thread stats, will be merged into the _Py_stats_struct global
#ifdef Py_GIL_DISABLED
// per-thread stats, will be merged into interp->pystats_struct
PyStats *pystats_struct; // allocated by _PyStats_ThreadInit()
#endif
PyStats **pystats_tss; // pointer to tss variable
#endif

#endif // Py_GIL_DISABLED

#if defined(Py_REF_DEBUG) && defined(Py_GIL_DISABLED)
Py_ssize_t reftotal; // this thread's total refcount operations
#endif
Expand Down
7 changes: 7 additions & 0 deletions Objects/object.c
Original file line number Diff line number Diff line change
Expand Up @@ -2418,6 +2418,13 @@ _PyObject_InitState(PyInterpreterState *interp)
if (refchain_init(interp) < 0) {
return _PyStatus_NO_MEMORY();
}
#endif
#ifdef Py_STATS
Comment thread
markshannon marked this conversation as resolved.
Outdated
if (interp->config._pystats) {
// start with pystats enabled, can be disabled via sys._stats_off()
// this needs to be set before the first tstate is created
interp->pystats_enabled = true;
}
#endif
return _PyStatus_OK();
}
Expand Down
6 changes: 0 additions & 6 deletions Python/initconfig.c
Original file line number Diff line number Diff line change
Expand Up @@ -2807,12 +2807,6 @@ _PyConfig_Write(const PyConfig *config, _PyRuntimeState *runtime)
return _PyStatus_NO_MEMORY();
}

#ifdef Py_STATS
if (config->_pystats) {
_Py_StatsOn();
}
#endif

return _PyStatus_OK();
}

Expand Down
18 changes: 12 additions & 6 deletions Python/pystate.c
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,9 @@
#include "pycore_runtime.h" // _PyRuntime
#include "pycore_runtime_init.h" // _PyRuntimeState_INIT
#include "pycore_stackref.h" // Py_STACKREF_DEBUG
#include "pycore_stats.h" // FT_STAT_WORLD_STOP_INC()
#include "pycore_time.h" // _PyTime_Init()
#include "pycore_uniqueid.h" // _PyObject_FinalizePerThreadRefcounts()
#include "pycore_stats.h" // FT_STAT_WORLD_STOP_INC()



/* --------------------------------------------------------------------------
Expand Down Expand Up @@ -480,6 +479,12 @@ alloc_interpreter(void)
static void
free_interpreter(PyInterpreterState *interp)
{
#ifdef Py_STATS
if (interp->pystats_struct) {
PyMem_RawFree(interp->pystats_struct);
interp->pystats_struct = NULL;
}
#endif
// The main interpreter is statically allocated so
// should not be freed.
if (interp != &_PyRuntime._main_interpreter) {
Expand Down Expand Up @@ -1532,7 +1537,7 @@ new_threadstate(PyInterpreterState *interp, int whence)
#endif
#ifdef Py_STATS
// The PyStats structure is quite large and is allocated separated from tstate.
if (!_PyStats_ThreadInit(tstate)) {
if (!_PyStats_ThreadInit(interp, tstate)) {
free_threadstate(tstate);
return NULL;
}
Expand Down Expand Up @@ -2017,9 +2022,6 @@ tstate_activate(PyThreadState *tstate)
if (!tstate->_status.bound_gilstate) {
bind_gilstate_tstate(tstate);
}
#ifdef Py_STATS
_PyStats_Attach((_PyThreadStateImpl *)tstate);
#endif

tstate->_status.active = 1;
}
Expand Down Expand Up @@ -2139,6 +2141,10 @@ _PyThreadState_Attach(PyThreadState *tstate)
_PyCriticalSection_Resume(tstate);
}

#ifdef Py_STATS
_PyStats_Attach((_PyThreadStateImpl *)tstate);
#endif

#if defined(Py_DEBUG)
errno = err;
#endif
Expand Down
Loading