]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
Issue #16351: New function gc.get_stats() returns per-generation collection statistics.
authorAntoine Pitrou <solipsis@pitrou.net>
Tue, 30 Oct 2012 21:43:19 +0000 (22:43 +0100)
committerAntoine Pitrou <solipsis@pitrou.net>
Tue, 30 Oct 2012 21:43:19 +0000 (22:43 +0100)
Doc/library/gc.rst
Lib/test/test_gc.py
Misc/NEWS
Modules/gcmodule.c

index 41bda1e3517b72db41146ca410cdea7a54569328..95df2f83e493664f1595e99e182857de28668f83 100644 (file)
@@ -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
index c59b72eacf87fa81cdc328d2672b0311231e9293..85dbc97bb2a9940f932195b043e54ab82deda31f 100644 (file)
@@ -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):
index 739b52b69539197c29f73b053178a607ed56d8bb..c101aa5e9b7785f1588c4968228180dd4c3b477b 100644 (file)
--- 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.
 
index f782dd0923b6cdd0f51f8e784d1b4d766ec8e895..9ac594fc7c8fa83a26d9c6f2f3ffbaee2d27bdb7 100644 (file)
@@ -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__},