Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
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
6 changes: 5 additions & 1 deletion Include/internal/pycore_gc.h
Original file line number Diff line number Diff line change
Expand Up @@ -223,12 +223,14 @@ static inline void _PyObject_GC_TRACK(
"object is in generation which is garbage collected",
filename, lineno, __func__);

PyGC_Head *generation0 = _PyInterpreterState_GET()->gc.generation0;
struct _gc_runtime_state *gcstate = &_PyInterpreterState_GET()->gc;
PyGC_Head *generation0 = gcstate->generation0;
PyGC_Head *last = (PyGC_Head*)(generation0->_gc_prev);
_PyGCHead_SET_NEXT(last, gc);
_PyGCHead_SET_PREV(gc, last);
_PyGCHead_SET_NEXT(gc, generation0);
generation0->_gc_prev = (uintptr_t)gc;
gcstate->heap_size++;
#endif
}

Expand Down Expand Up @@ -263,6 +265,8 @@ static inline void _PyObject_GC_UNTRACK(
_PyGCHead_SET_PREV(next, prev);
gc->_gc_next = 0;
gc->_gc_prev &= _PyGC_PREV_MASK_FINALIZED;
struct _gc_runtime_state *gcstate = &_PyInterpreterState_GET()->gc;
gcstate->heap_size--;
#endif
}

Expand Down
17 changes: 15 additions & 2 deletions Include/internal/pycore_interp_structs.h
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,8 @@ struct gc_generation_stats {
Py_ssize_t candidates;
// Total duration of the collection in seconds:
double duration;
/* heap_size on the start of the collection */
Py_ssize_t heap_size;
};

#ifdef Py_GIL_DISABLED
Expand Down Expand Up @@ -226,11 +228,11 @@ struct _gc_runtime_state {
/* linked lists of container objects */
#ifndef Py_GIL_DISABLED
struct gc_generation generations[NUM_GENERATIONS];
PyGC_Head *generation0;
#else
struct gc_generation young;
struct gc_generation old[2];
#endif

/* a permanent generation which won't be collected */
struct gc_generation permanent_generation;
struct gc_stats *generation_stats;
Expand All @@ -244,6 +246,14 @@ struct _gc_runtime_state {
/* a list of callbacks to be invoked when collection is performed */
PyObject *callbacks;

/* The number of live objects. */
Py_ssize_t heap_size;

/* dummy members to preserve other offsets */
Py_ssize_t dummy1; /* was work_to_do */
int dummy2; /* was visited_space */
int dummy3; /* was phase */

/* This is the number of objects that survived the last full
collection. It approximates the number of long lived objects
tracked by the GC.
Expand All @@ -269,6 +279,8 @@ struct _gc_runtime_state {

/* Mutex held for gc_should_collect_mem_usage(). */
PyMutex mutex;
#else
PyGC_Head *generation0;
#endif
};

Expand All @@ -278,7 +290,8 @@ struct _gc_runtime_state {
{ .threshold = 2000, }, \
{ .threshold = 10, }, \
{ .threshold = 10, }, \
},
}, \
.heap_size = 0,
#else
#define GC_GENERATION_INIT \
.young = { .threshold = 2000, }, \
Expand Down
9 changes: 9 additions & 0 deletions Lib/test/test_gc.py
Original file line number Diff line number Diff line change
Expand Up @@ -1288,6 +1288,15 @@ def test_tuple_untrack_counts(self):
# Use n // 2 just in case some other objects were collected.
self.assertTrue(new_count - count > (n // 2))

@requires_gil_enabled('need generational GC')
@unittest.skipIf(_testinternalcapi is None, "requires _testinternalcapi")
def test_heap_size(self):
count = _testinternalcapi.get_tracked_heap_size()
l = []
self.assertEqual(count + 1, _testinternalcapi.get_tracked_heap_size())
del l
self.assertEqual(count, _testinternalcapi.get_tracked_heap_size())


class GCCallbackTests(unittest.TestCase):
def setUp(self):
Expand Down
2 changes: 1 addition & 1 deletion Lib/test/test_gc_stats.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@

GC_STATS_FIELDS = (
"gen", "iid", "ts_start", "ts_stop", "collections", "collected",
"uncollectable", "candidates", "duration")
"uncollectable", "candidates", "heap_size", "duration")


def get_interpreter_identifiers(gc_stats) -> tuple[int,...]:
Expand Down
3 changes: 2 additions & 1 deletion Modules/_remote_debugging/clinic/module.c.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Modules/_remote_debugging/gc_stats.c
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ read_gc_stats(struct gc_stats *stats, int64_t iid, PyObject *result,
SET_FIELD(PyLong_FromSsize_t, items->collected);
SET_FIELD(PyLong_FromSsize_t, items->uncollectable);
SET_FIELD(PyLong_FromSsize_t, items->candidates);
SET_FIELD(PyLong_FromSsize_t, items->heap_size);

SET_FIELD(PyFloat_FromDouble, items->duration);

Expand Down
6 changes: 4 additions & 2 deletions Modules/_remote_debugging/module.c
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,7 @@ static PyStructSequence_Field GCStatsInfo_fields[] = {
{"collected", "Total number of collected objects"},
{"uncollectable", "Total number of uncollectable objects"},
{"candidates", "Total objects considered and traversed"},
{"heap_size", "Number of live objects"},
{"duration", "Total collection time, in seconds"},
{NULL}
};
Expand All @@ -151,7 +152,7 @@ PyStructSequence_Desc GCStatsInfo_desc = {
"_remote_debugging.GCStatsInfo",
"Information about a garbage collector stats sample",
GCStatsInfo_fields,
9
10
};

/* ============================================================================
Expand Down Expand Up @@ -1225,6 +1226,7 @@ Returns a list of GCStatsInfo objects with GC statistics data.
- collected: Total number of collected objects.
- uncollectable: Total number of uncollectable objects.
- candidates: Total objects considered and traversed.
- heap_size: number of live objects.
- duration: Total collection time, in seconds.

Raises:
Expand All @@ -1235,7 +1237,7 @@ Returns a list of GCStatsInfo objects with GC statistics data.
static PyObject *
_remote_debugging_GCMonitor_get_gc_stats_impl(GCMonitorObject *self,
int all_interpreters)
/*[clinic end generated code: output=f73f365725224f7a input=09e647719c65f9e4]*/
/*[clinic end generated code: output=f73f365725224f7a input=12f7c1a288cf2741]*/
{
RemoteDebuggingState *st = RemoteDebugging_GetStateFromType(Py_TYPE(self));
return get_gc_stats(&self->offsets, all_interpreters, st->GCStatsInfo_Type);
Expand Down
3 changes: 1 addition & 2 deletions Modules/_testinternalcapi.c
Original file line number Diff line number Diff line change
Expand Up @@ -2731,8 +2731,7 @@ has_deferred_refcount(PyObject *self, PyObject *op)
static PyObject *
get_tracked_heap_size(PyObject *self, PyObject *Py_UNUSED(ignored))
{
// Generational GC doesn't track heap_size, return -1.
return PyLong_FromInt64(-1);
return PyLong_FromInt64(PyInterpreterState_Get()->gc.heap_size);
}

static PyObject *
Expand Down
5 changes: 4 additions & 1 deletion Python/gc.c
Original file line number Diff line number Diff line change
Expand Up @@ -1405,13 +1405,13 @@ add_stats(GCState *gcstate, int gen, struct gc_generation_stats *stats)
memcpy(cur_stats, prev_stats, sizeof(struct gc_generation_stats));

cur_stats->ts_start = stats->ts_start;

cur_stats->collections += 1;
cur_stats->collected += stats->collected;
cur_stats->uncollectable += stats->uncollectable;
cur_stats->candidates += stats->candidates;

cur_stats->duration += stats->duration;
cur_stats->heap_size = stats->heap_size;
/* Publish ts_stop last so remote readers do not select a partially
updated stats record as the latest collection. */
cur_stats->ts_stop = stats->ts_stop;
Expand Down Expand Up @@ -1471,6 +1471,7 @@ gc_collect_main(PyThreadState *tstate, int generation, _PyGC_Reason reason)
invoke_gc_callback(tstate, "start", generation, &stats);
}

stats.heap_size = gcstate->heap_size;
// ignore error: don't interrupt the GC if reading the clock fails
(void)PyTime_PerfCounterRaw(&stats.ts_start);
if (gcstate->debug & _PyGC_DEBUG_STATS) {
Expand Down Expand Up @@ -2097,6 +2098,8 @@ PyObject_GC_Del(void *op)
PyGC_Head *g = AS_GC(op);
if (_PyObject_GC_IS_TRACKED(op)) {
gc_list_remove(g);
GCState *gcstate = get_gc_state();
gcstate->heap_size--;
#ifdef Py_DEBUG
PyObject *exc = PyErr_GetRaisedException();
if (PyErr_WarnExplicitFormat(PyExc_ResourceWarning, "gc", 0,
Expand Down
Loading