FazBrowse GitHub Viewer | Trending |
URL:
| Home
Tools: [Download Repo ZIP]   [Original HTTPS Page]

gh-146527, gh-155811: Backport out-of-process GC statistics to 3.313 · Kronuz/cpython@c8d47d4 · GitHub

Repository navigation

Commit c8d47d4

Browse files
committed
pythongh-146527, pythongh-155811: Backport out-of-process GC statistics to 3.313
1 parent cbc944f commit c8d47d4

11 files changed

Lines changed: 3104 additions & 53 deletions

File tree

‎Include/internal/pycore_gc.h‎

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -269,14 +269,64 @@ struct gc_generation {
269269
generations */
270270
};
271271

272+
/* Stock 3.13's three counters. The generation_counters[] array below keeps
273+
this type and therefore its original 24 bytes at its original offset, so
274+
that _gc_runtime_state's layout is byte-identical to an unpatched build.
275+
It is still maintained, so an extension compiled against stock headers
276+
that reads it keeps reading correct values. */
277+
struct gc_generation_counters {
278+
/* total number of collections */
279+
Py_ssize_t collections;
280+
/* total number of collected objects */
281+
Py_ssize_t collected;
282+
/* total number of uncollectable objects (put into gc.garbage) */
283+
Py_ssize_t uncollectable;
284+
};
285+
272286
/* Running stats per generation */
273287
struct gc_generation_stats {
288+
PyTime_t ts_start;
289+
PyTime_t ts_stop;
274290
/* total number of collections */
275291
Py_ssize_t collections;
276292
/* total number of collected objects */
277293
Py_ssize_t collected;
278294
/* total number of uncollectable objects (put into gc.garbage) */
279295
Py_ssize_t uncollectable;
296+
// Total number of objects considered for collection and traversed:
297+
Py_ssize_t candidates;
298+
// Total duration of the collection in seconds:
299+
double duration;
300+
/* heap_size on the start of the collection */
301+
Py_ssize_t heap_size;
302+
};
303+
304+
#ifdef Py_GIL_DISABLED
305+
#define GC_YOUNG_STATS_SIZE 1
306+
#define GC_OLD_STATS_SIZE 1
307+
#else
308+
#define GC_YOUNG_STATS_SIZE 11
309+
#define GC_OLD_STATS_SIZE 3
310+
#endif
311+
struct gc_young_stats_buffer {
312+
struct gc_generation_stats items[GC_YOUNG_STATS_SIZE];
313+
int8_t index;
314+
};
315+
316+
struct gc_old_stats_buffer {
317+
struct gc_generation_stats items[GC_OLD_STATS_SIZE];
318+
int8_t index;
319+
};
320+
321+
/* Ring buffers of per-collection statistics, published to external readers.
322+
Upstream 3.15 keeps a struct gc_stats* here in _gc_runtime_state; this tree
323+
heap-allocates one too, but hangs the pointer off the tail of
324+
PyInterpreterState instead -- see the Deviations note in
325+
patches/gc-stats.patch for why _gc_runtime_state cannot carry it. */
326+
struct gc_stats {
327+
struct gc_young_stats_buffer young;
328+
struct gc_old_stats_buffer old[2];
329+
uint32_t update_seq;
280330
};
281331

282332
struct _gc_runtime_state {
@@ -294,7 +344,7 @@ struct _gc_runtime_state {
294344
PyGC_Head *generation0;
295345
/* a permanent generation which won't be collected */
296346
struct gc_generation permanent_generation;
297-
struct gc_generation_stats generation_stats[NUM_GENERATIONS];
347+
struct gc_generation_counters generation_counters[NUM_GENERATIONS];
298348
/* true if we are currently running the collector */
299349
int collecting;
300350
/* list of uncollectable objects */

‎Include/internal/pycore_interp.h‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -277,6 +277,15 @@ struct _is {
277277
Py_ssize_t _interactive_src_count;
278278
// In 3.14+ this is interp->threads.preallocated.
279279
_PyThreadStateImpl *threads_preallocated;
280+
281+
/* Heap-allocated ring of per-collection GC statistics, published to
282+
external readers. Upstream 3.15 keeps this pointer in _gc_runtime_state
283+
(interp->gc); this tree cannot, because _gc_runtime_state sits mid-struct
284+
with sixty-one members after it, so growing it would move every one of
285+
their offsets. Appended at the very tail of PyInterpreterState instead,
286+
which grows sizeof(PyInterpreterState) by one pointer and moves nothing.
287+
See the Deviations note in patches/gc-stats.patch. */
288+
struct gc_stats *generation_stats;
280289
};
281290

282291

‎Lib/test/test_gc.py‎

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -806,11 +806,15 @@ def test_get_stats(self):
806806
self.assertEqual(len(stats), 3)
807807
for st in stats:
808808
self.assertIsInstance(st, dict)
809-
self.assertEqual(set(st),
810-
{"collected", "collections", "uncollectable"})
809+
self.assertEqual(
810+
set(st),
811+
{"collected", "collections", "uncollectable", "candidates", "duration"}
812+
)
811813
self.assertGreaterEqual(st["collected"], 0)
812814
self.assertGreaterEqual(st["collections"], 0)
813815
self.assertGreaterEqual(st["uncollectable"], 0)
816+
self.assertGreaterEqual(st["candidates"], 0)
817+
self.assertGreaterEqual(st["duration"], 0)
814818
# Check that collection counts are incremented correctly
815819
if gc.isenabled():
816820
self.addCleanup(gc.enable)
@@ -821,11 +825,25 @@ def test_get_stats(self):
821825
self.assertEqual(new[0]["collections"], old[0]["collections"] + 1)
822826
self.assertEqual(new[1]["collections"], old[1]["collections"])
823827
self.assertEqual(new[2]["collections"], old[2]["collections"])
828+
self.assertGreater(new[0]["duration"], old[0]["duration"])
829+
self.assertEqual(new[1]["duration"], old[1]["duration"])
830+
self.assertEqual(new[2]["duration"], old[2]["duration"])
831+
for stat in ["collected", "uncollectable", "candidates"]:
832+
self.assertGreaterEqual(new[0][stat], old[0][stat])
833+
self.assertEqual(new[1][stat], old[1][stat])
834+
self.assertEqual(new[2][stat], old[2][stat])
824835
gc.collect(2)
825-
new = gc.get_stats()
826-
self.assertEqual(new[0]["collections"], old[0]["collections"] + 1)
836+
old, new = new, gc.get_stats()
837+
self.assertEqual(new[0]["collections"], old[0]["collections"])
827838
self.assertEqual(new[1]["collections"], old[1]["collections"])
828839
self.assertEqual(new[2]["collections"], old[2]["collections"] + 1)
840+
self.assertEqual(new[0]["duration"], old[0]["duration"])
841+
self.assertEqual(new[1]["duration"], old[1]["duration"])
842+
self.assertGreater(new[2]["duration"], old[2]["duration"])
843+
for stat in ["collected", "uncollectable", "candidates"]:
844+
self.assertEqual(new[0][stat], old[0][stat])
845+
self.assertEqual(new[1][stat], old[1][stat])
846+
self.assertGreaterEqual(new[2][stat], old[2][stat])
829847

830848
def test_freeze(self):
831849
gc.freeze()

0 commit comments

Comments
 (0)

Back | FazBrowse Home | New Git URL