summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Doc/library/gc.rst18
-rw-r--r--Lib/test/test_gc.py26
-rw-r--r--Misc/NEWS3
-rw-r--r--Modules/gcmodule.c64
4 files changed, 111 insertions, 0 deletions
diff --git a/Doc/library/gc.rst b/Doc/library/gc.rst
index 41bda1e351..95df2f83e4 100644
--- a/Doc/library/gc.rst
+++ b/Doc/library/gc.rst
@@ -67,6 +67,24 @@ The :mod:`gc` module provides the following functions:
returned.
+.. function:: get_stats()
+
+ Return a list of 3 per-generation dictionaries containing collection
+ statistics since interpreter start. At this moment, each dictionary will
+ contain the following items:
+
+ * ``collections`` is the number of times this generation was collected;
+
+ * ``collected`` is the total number of objects collected inside this
+ generation;
+
+ * ``uncollectable`` is the total number of objects which were found
+ to be uncollectable (and were therefore moved to the :data:`garbage`
+ list) inside this generation.
+
+ .. versionadded:: 3.4
+
+
.. function:: set_threshold(threshold0[, threshold1[, threshold2]])
Set the garbage collection thresholds (the collection frequency). Setting
diff --git a/Lib/test/test_gc.py b/Lib/test/test_gc.py
index c59b72eacf..85dbc97bb2 100644
--- a/Lib/test/test_gc.py
+++ b/Lib/test/test_gc.py
@@ -610,6 +610,32 @@ class GCTests(unittest.TestCase):
stderr = run_command(code % "gc.DEBUG_SAVEALL")
self.assertNotIn(b"uncollectable objects at shutdown", stderr)
+ def test_get_stats(self):
+ stats = gc.get_stats()
+ self.assertEqual(len(stats), 3)
+ for st in stats:
+ self.assertIsInstance(st, dict)
+ self.assertEqual(set(st),
+ {"collected", "collections", "uncollectable"})
+ self.assertGreaterEqual(st["collected"], 0)
+ self.assertGreaterEqual(st["collections"], 0)
+ self.assertGreaterEqual(st["uncollectable"], 0)
+ # Check that collection counts are incremented correctly
+ if gc.isenabled():
+ self.addCleanup(gc.enable)
+ gc.disable()
+ old = gc.get_stats()
+ gc.collect(0)
+ new = gc.get_stats()
+ self.assertEqual(new[0]["collections"], old[0]["collections"] + 1)
+ self.assertEqual(new[1]["collections"], old[1]["collections"])
+ self.assertEqual(new[2]["collections"], old[2]["collections"])
+ gc.collect(2)
+ new = gc.get_stats()
+ self.assertEqual(new[0]["collections"], old[0]["collections"] + 1)
+ self.assertEqual(new[1]["collections"], old[1]["collections"])
+ self.assertEqual(new[2]["collections"], old[2]["collections"] + 1)
+
class GCCallbackTests(unittest.TestCase):
def setUp(self):
diff --git a/Misc/NEWS b/Misc/NEWS
index 739b52b695..c101aa5e9b 100644
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -65,6 +65,9 @@ Core and Builtins
Library
-------
+- Issue #16351: New function gc.get_stats() returns per-generation collection
+ statistics.
+
- Issue #14897: Enhance error messages of struct.pack and
struct.pack_into. Patch by Matti Mäki.
diff --git a/Modules/gcmodule.c b/Modules/gcmodule.c
index f782dd0923..9ac594fc7c 100644
--- a/Modules/gcmodule.c
+++ b/Modules/gcmodule.c
@@ -168,6 +168,18 @@ static Py_ssize_t long_lived_pending = 0;
static int debug;
static PyObject *tmod = NULL;
+/* Running stats per generation */
+struct gc_generation_stats {
+ /* total number of collections */
+ Py_ssize_t collections;
+ /* total number of collected objects */
+ Py_ssize_t collected;
+ /* total number of uncollectable objects (put into gc.garbage) */
+ Py_ssize_t uncollectable;
+};
+
+static struct gc_generation_stats generation_stats[NUM_GENERATIONS];
+
/*--------------------------------------------------------------------------
gc_refs values.
@@ -852,6 +864,7 @@ collect(int generation, Py_ssize_t *n_collected, Py_ssize_t *n_uncollectable)
PyGC_Head finalizers; /* objects with, & reachable from, __del__ */
PyGC_Head *gc;
double t1 = 0.0;
+ struct gc_generation_stats *stats = &generation_stats[generation];
if (debug & DEBUG_STATS) {
PySys_WriteStderr("gc: collecting generation %d...\n",
@@ -993,10 +1006,14 @@ collect(int generation, Py_ssize_t *n_collected, Py_ssize_t *n_uncollectable)
Py_FatalError("unexpected exception during garbage collection");
}
+ /* Update stats */
if (n_collected)
*n_collected = m;
if (n_uncollectable)
*n_uncollectable = n;
+ stats->collections++;
+ stats->collected += m;
+ stats->uncollectable += n;
return n+m;
}
@@ -1343,6 +1360,52 @@ gc_get_objects(PyObject *self, PyObject *noargs)
return result;
}
+PyDoc_STRVAR(gc_get_stats__doc__,
+"get_stats() -> [...]\n"
+"\n"
+"Return a list of dictionaries containing per-generation statistics.\n");
+
+static PyObject *
+gc_get_stats(PyObject *self, PyObject *noargs)
+{
+ int i;
+ PyObject *result;
+ struct gc_generation_stats stats[NUM_GENERATIONS], *st;
+
+ /* To get consistent values despite allocations while constructing
+ the result list, we use a snapshot of the running stats. */
+ for (i = 0; i < NUM_GENERATIONS; i++) {
+ stats[i] = generation_stats[i];
+ }
+
+ result = PyList_New(0);
+ if (result == NULL)
+ return NULL;
+
+ for (i = 0; i < NUM_GENERATIONS; i++) {
+ PyObject *dict;
+ st = &stats[i];
+ dict = Py_BuildValue("{snsnsn}",
+ "collections", st->collections,
+ "collected", st->collected,
+ "uncollectable", st->uncollectable
+ );
+ if (dict == NULL)
+ goto error;
+ if (PyList_Append(result, dict)) {
+ Py_DECREF(dict);
+ goto error;
+ }
+ Py_DECREF(dict);
+ }
+ return result;
+
+error:
+ Py_XDECREF(result);
+ return NULL;
+}
+
+
PyDoc_STRVAR(gc_is_tracked__doc__,
"is_tracked(obj) -> bool\n"
"\n"
@@ -1393,6 +1456,7 @@ static PyMethodDef GcMethods[] = {
{"collect", (PyCFunction)gc_collect,
METH_VARARGS | METH_KEYWORDS, gc_collect__doc__},
{"get_objects", gc_get_objects,METH_NOARGS, gc_get_objects__doc__},
+ {"get_stats", gc_get_stats, METH_NOARGS, gc_get_stats__doc__},
{"is_tracked", gc_is_tracked, METH_O, gc_is_tracked__doc__},
{"get_referrers", gc_get_referrers, METH_VARARGS,
gc_get_referrers__doc__},