GH-140638: Add a GC "duration" stat (GH-141720)

This commit is contained in:
Brandt Bucher 2025-11-19 08:51:39 -08:00 committed by GitHub
parent b3b63e8d6d
commit 598d4c64de
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 67 additions and 32 deletions

View file

@ -1363,6 +1363,7 @@ gc_list_set_space(PyGC_Head *list, int space)
static void
add_stats(GCState *gcstate, int gen, struct gc_collection_stats *stats)
{
gcstate->generation_stats[gen].duration += stats->duration;
gcstate->generation_stats[gen].collected += stats->collected;
gcstate->generation_stats[gen].uncollectable += stats->uncollectable;
gcstate->generation_stats[gen].collections += 1;
@ -1387,7 +1388,6 @@ gc_collect_young(PyThreadState *tstate,
validate_spaces(gcstate);
gcstate->young.count = 0;
gcstate->old[gcstate->visited_space].count++;
add_stats(gcstate, 0, stats);
validate_spaces(gcstate);
}
@ -1701,7 +1701,6 @@ gc_collect_increment(PyThreadState *tstate, struct gc_collection_stats *stats)
assert(gc_list_is_empty(&increment));
gcstate->work_to_do -= increment_size;
add_stats(gcstate, 1, stats);
if (gc_list_is_empty(not_visited)) {
completed_scavenge(gcstate);
}
@ -1736,7 +1735,6 @@ gc_collect_full(PyThreadState *tstate,
completed_scavenge(gcstate);
_PyGC_ClearAllFreeLists(tstate->interp);
validate_spaces(gcstate);
add_stats(gcstate, 2, stats);
}
/* This is the main function. Read this to understand how the
@ -1846,10 +1844,11 @@ do_gc_callback(GCState *gcstate, const char *phase,
assert(PyList_CheckExact(gcstate->callbacks));
PyObject *info = NULL;
if (PyList_GET_SIZE(gcstate->callbacks) != 0) {
info = Py_BuildValue("{sisnsn}",
info = Py_BuildValue("{sisnsnsd}",
"generation", generation,
"collected", stats->collected,
"uncollectable", stats->uncollectable);
"uncollectable", stats->uncollectable,
"duration", stats->duration);
if (info == NULL) {
PyErr_FormatUnraisable("Exception ignored while invoking gc callbacks");
return;
@ -2080,15 +2079,15 @@ _PyGC_Collect(PyThreadState *tstate, int generation, _PyGC_Reason reason)
if (reason != _Py_GC_REASON_SHUTDOWN) {
invoke_gc_callback(gcstate, "start", generation, &stats);
}
PyTime_t t1;
if (gcstate->debug & _PyGC_DEBUG_STATS) {
PySys_WriteStderr("gc: collecting generation %d...\n", generation);
(void)PyTime_PerfCounterRaw(&t1);
show_stats_each_generations(gcstate);
}
if (PyDTrace_GC_START_ENABLED()) {
PyDTrace_GC_START(generation);
}
PyTime_t start, stop;
(void)PyTime_PerfCounterRaw(&start);
PyObject *exc = _PyErr_GetRaisedException(tstate);
switch(generation) {
case 0:
@ -2103,6 +2102,9 @@ _PyGC_Collect(PyThreadState *tstate, int generation, _PyGC_Reason reason)
default:
Py_UNREACHABLE();
}
(void)PyTime_PerfCounterRaw(&stop);
stats.duration = PyTime_AsSecondsDouble(stop - start);
add_stats(gcstate, generation, &stats);
if (PyDTrace_GC_DONE_ENABLED()) {
PyDTrace_GC_DONE(stats.uncollectable + stats.collected);
}
@ -2124,12 +2126,9 @@ _PyGC_Collect(PyThreadState *tstate, int generation, _PyGC_Reason reason)
_Py_atomic_store_int(&gcstate->collecting, 0);
if (gcstate->debug & _PyGC_DEBUG_STATS) {
PyTime_t t2;
(void)PyTime_PerfCounterRaw(&t2);
double d = PyTime_AsSecondsDouble(t2 - t1);
PySys_WriteStderr(
"gc: done, %zd unreachable, %zd uncollectable, %.4fs elapsed\n",
stats.collected + stats.uncollectable, stats.uncollectable, d
stats.collected + stats.uncollectable, stats.uncollectable, stats.duration
);
}

View file

@ -1911,7 +1911,7 @@ handle_resurrected_objects(struct collection_state *state)
static void
invoke_gc_callback(PyThreadState *tstate, const char *phase,
int generation, Py_ssize_t collected,
Py_ssize_t uncollectable)
Py_ssize_t uncollectable, double duration)
{
assert(!_PyErr_Occurred(tstate));
@ -1925,10 +1925,11 @@ invoke_gc_callback(PyThreadState *tstate, const char *phase,
assert(PyList_CheckExact(gcstate->callbacks));
PyObject *info = NULL;
if (PyList_GET_SIZE(gcstate->callbacks) != 0) {
info = Py_BuildValue("{sisnsn}",
info = Py_BuildValue("{sisnsnsd}",
"generation", generation,
"collected", collected,
"uncollectable", uncollectable);
"uncollectable", uncollectable,
"duration", duration);
if (info == NULL) {
PyErr_FormatUnraisable("Exception ignored while "
"invoking gc callbacks");
@ -2340,7 +2341,6 @@ gc_collect_main(PyThreadState *tstate, int generation, _PyGC_Reason reason)
{
Py_ssize_t m = 0; /* # objects collected */
Py_ssize_t n = 0; /* # unreachable objects that couldn't be collected */
PyTime_t t1 = 0; /* initialize to prevent a compiler warning */
GCState *gcstate = &tstate->interp->gc;
// gc_collect_main() must not be called before _PyGC_Init
@ -2372,19 +2372,19 @@ gc_collect_main(PyThreadState *tstate, int generation, _PyGC_Reason reason)
GC_STAT_ADD(generation, collections, 1);
if (reason != _Py_GC_REASON_SHUTDOWN) {
invoke_gc_callback(tstate, "start", generation, 0, 0);
invoke_gc_callback(tstate, "start", generation, 0, 0, 0);
}
if (gcstate->debug & _PyGC_DEBUG_STATS) {
PySys_WriteStderr("gc: collecting generation %d...\n", generation);
show_stats_each_generations(gcstate);
// ignore error: don't interrupt the GC if reading the clock fails
(void)PyTime_PerfCounterRaw(&t1);
}
if (PyDTrace_GC_START_ENABLED()) {
PyDTrace_GC_START(generation);
}
PyTime_t start, stop;
(void)PyTime_PerfCounterRaw(&start);
PyInterpreterState *interp = tstate->interp;
@ -2399,13 +2399,13 @@ gc_collect_main(PyThreadState *tstate, int generation, _PyGC_Reason reason)
m = state.collected;
n = state.uncollectable;
(void)PyTime_PerfCounterRaw(&stop);
double duration = PyTime_AsSecondsDouble(stop - start);
if (gcstate->debug & _PyGC_DEBUG_STATS) {
PyTime_t t2;
(void)PyTime_PerfCounterRaw(&t2);
double d = PyTime_AsSecondsDouble(t2 - t1);
PySys_WriteStderr(
"gc: done, %zd unreachable, %zd uncollectable, %.4fs elapsed\n",
n+m, n, d);
n+m, n, duration);
}
// Clear the current thread's free-list again.
@ -2426,6 +2426,7 @@ gc_collect_main(PyThreadState *tstate, int generation, _PyGC_Reason reason)
stats->collections++;
stats->collected += m;
stats->uncollectable += n;
stats->duration += duration;
GC_STAT_ADD(generation, objects_collected, m);
#ifdef Py_STATS
@ -2444,7 +2445,7 @@ gc_collect_main(PyThreadState *tstate, int generation, _PyGC_Reason reason)
}
if (reason != _Py_GC_REASON_SHUTDOWN) {
invoke_gc_callback(tstate, "stop", generation, m, n);
invoke_gc_callback(tstate, "stop", generation, m, n, duration);
}
assert(!_PyErr_Occurred(tstate));