| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent cbc944f commit c8d47d4
11 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -269,14 +269,64 @@ struct gc_generation { | |||
| 269 | 269 | generations */ | |
| 270 | 270 | }; | |
| 271 | 271 | ||
| 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 | + | ||
| 272 | 286 | /* Running stats per generation */ | |
| 273 | 287 | struct gc_generation_stats { | |
| 288 | + PyTime_t ts_start; | ||
| 289 | + PyTime_t ts_stop; | ||
| 274 | 290 | /* total number of collections */ | |
| 275 | 291 | Py_ssize_t collections; | |
| 276 | 292 | /* total number of collected objects */ | |
| 277 | 293 | Py_ssize_t collected; | |
| 278 | 294 | /* total number of uncollectable objects (put into gc.garbage) */ | |
| 279 | 295 | 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; | ||
| 280 | 330 | }; | |
| 281 | 331 | ||
| 282 | 332 | struct _gc_runtime_state { | |
@@ -294,7 +344,7 @@ struct _gc_runtime_state { | |||
| 294 | 344 | PyGC_Head *generation0; | |
| 295 | 345 | /* a permanent generation which won't be collected */ | |
| 296 | 346 | struct gc_generation permanent_generation; | |
| 297 | - struct gc_generation_stats generation_stats[NUM_GENERATIONS]; | ||
| 347 | + struct gc_generation_counters generation_counters[NUM_GENERATIONS]; | ||
| 298 | 348 | /* true if we are currently running the collector */ | |
| 299 | 349 | int collecting; | |
| 300 | 350 | /* list of uncollectable objects */ | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -277,6 +277,15 @@ struct _is { | |||
| 277 | 277 | Py_ssize_t _interactive_src_count; | |
| 278 | 278 | // In 3.14+ this is interp->threads.preallocated. | |
| 279 | 279 | _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; | ||
| 280 | 289 | }; | |
| 281 | 290 | ||
| 282 | 291 | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -806,11 +806,15 @@ def test_get_stats(self): | |||
| 806 | 806 | self.assertEqual(len(stats), 3) | |
| 807 | 807 | for st in stats: | |
| 808 | 808 | 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 | + ) | ||
| 811 | 813 | self.assertGreaterEqual(st["collected"], 0) | |
| 812 | 814 | self.assertGreaterEqual(st["collections"], 0) | |
| 813 | 815 | self.assertGreaterEqual(st["uncollectable"], 0) | |
| 816 | + self.assertGreaterEqual(st["candidates"], 0) | ||
| 817 | + self.assertGreaterEqual(st["duration"], 0) | ||
| 814 | 818 | # Check that collection counts are incremented correctly | |
| 815 | 819 | if gc.isenabled(): | |
| 816 | 820 | self.addCleanup(gc.enable) | |
@@ -821,11 +825,25 @@ def test_get_stats(self): | |||
| 821 | 825 | self.assertEqual(new[0]["collections"], old[0]["collections"] + 1) | |
| 822 | 826 | self.assertEqual(new[1]["collections"], old[1]["collections"]) | |
| 823 | 827 | 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]) | ||
| 824 | 835 | 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"]) | ||
| 827 | 838 | self.assertEqual(new[1]["collections"], old[1]["collections"]) | |
| 828 | 839 | 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]) | ||
| 829 | 847 | ||
| 830 | 848 | def test_freeze(self): | |
| 831 | 849 | gc.freeze() | |
| Back | FazBrowse Home | New Git URL |
0 commit comments