| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -108,10 +108,16 @@ The :mod:`gc` module provides the following functions: | |||
| 108 | 108 | ||
| 109 | 109 | * ``uncollectable`` is the total number of objects which were found | |
| 110 | 110 | to be uncollectable (and were therefore moved to the :data:`garbage` | |
| 111 | - list) inside this generation. | ||
| 111 | + list) inside this generation; | ||
| 112 | + | ||
| 113 | + * ``duration`` is the total time in seconds spent in collections for this | ||
| 114 | + generation. | ||
| 112 | 115 | ||
| 113 | 116 | .. versionadded:: 3.4 | |
| 114 | 117 | ||
| 118 | + .. versionchanged:: next | ||
| 119 | + Add ``duration``. | ||
| 120 | + | ||
| 115 | 121 | ||
| 116 | 122 | .. function:: set_threshold(threshold0, [threshold1, [threshold2]]) | |
| 117 | 123 | ||
@@ -313,6 +319,9 @@ values but should not rebind them): | |||
| 313 | 319 | "uncollectable": When *phase* is "stop", the number of objects | |
| 314 | 320 | that could not be collected and were put in :data:`garbage`. | |
| 315 | 321 | ||
| 322 | + "duration": When *phase* is "stop", the time in seconds spent in the | ||
| 323 | + collection. | ||
| 324 | + | ||
| 316 | 325 | Applications can add their own callbacks to this list. The primary | |
| 317 | 326 | use cases are: | |
| 318 | 327 | ||
@@ -325,6 +334,9 @@ values but should not rebind them): | |||
| 325 | 334 | ||
| 326 | 335 | .. versionadded:: 3.3 | |
| 327 | 336 | ||
| 337 | + .. versionchanged:: next | ||
| 338 | + Add "duration". | ||
| 339 | + | ||
| 328 | 340 | ||
| 329 | 341 | The following constants are provided for use with :func:`set_debug`: | |
| 330 | 342 | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -179,6 +179,8 @@ struct gc_collection_stats { | |||
| 179 | 179 | Py_ssize_t collected; | |
| 180 | 180 | /* total number of uncollectable objects (put into gc.garbage) */ | |
| 181 | 181 | Py_ssize_t uncollectable; | |
| 182 | + // Duration of the collection in seconds: | ||
| 183 | + double duration; | ||
| 182 | 184 | }; | |
| 183 | 185 | ||
| 184 | 186 | /* Running stats per generation */ | |
@@ -189,6 +191,8 @@ struct gc_generation_stats { | |||
| 189 | 191 | Py_ssize_t collected; | |
| 190 | 192 | /* total number of uncollectable objects (put into gc.garbage) */ | |
| 191 | 193 | Py_ssize_t uncollectable; | |
| 194 | + // Duration of the collection in seconds: | ||
| 195 | + double duration; | ||
| 192 | 196 | }; | |
| 193 | 197 | ||
| 194 | 198 | enum _GCPhase { | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -847,10 +847,11 @@ def test_get_stats(self): | |||
| 847 | 847 | for st in stats: | |
| 848 | 848 | self.assertIsInstance(st, dict) | |
| 849 | 849 | self.assertEqual(set(st), | |
| 850 | - {"collected", "collections", "uncollectable"}) | ||
| 850 | + {"collected", "collections", "uncollectable", "duration"}) | ||
| 851 | 851 | self.assertGreaterEqual(st["collected"], 0) | |
| 852 | 852 | self.assertGreaterEqual(st["collections"], 0) | |
| 853 | 853 | self.assertGreaterEqual(st["uncollectable"], 0) | |
| 854 | + self.assertGreaterEqual(st["duration"], 0) | ||
| 854 | 855 | # Check that collection counts are incremented correctly | |
| 855 | 856 | if gc.isenabled(): | |
| 856 | 857 | self.addCleanup(gc.enable) | |
@@ -861,11 +862,25 @@ def test_get_stats(self): | |||
| 861 | 862 | self.assertEqual(new[0]["collections"], old[0]["collections"] + 1) | |
| 862 | 863 | self.assertEqual(new[1]["collections"], old[1]["collections"]) | |
| 863 | 864 | self.assertEqual(new[2]["collections"], old[2]["collections"]) | |
| 865 | + self.assertGreater(new[0]["duration"], old[0]["duration"]) | ||
| 866 | + self.assertEqual(new[1]["duration"], old[1]["duration"]) | ||
| 867 | + self.assertEqual(new[2]["duration"], old[2]["duration"]) | ||
| 868 | + for stat in ["collected", "uncollectable"]: | ||
| 869 | + self.assertGreaterEqual(new[0][stat], old[0][stat]) | ||
| 870 | + self.assertEqual(new[1][stat], old[1][stat]) | ||
| 871 | + self.assertEqual(new[2][stat], old[2][stat]) | ||
| 864 | 872 | gc.collect(2) | |
| 865 | - new = gc.get_stats() | ||
| 866 | - self.assertEqual(new[0]["collections"], old[0]["collections"] + 1) | ||
| 873 | + old, new = new, gc.get_stats() | ||
| 874 | + self.assertEqual(new[0]["collections"], old[0]["collections"]) | ||
| 867 | 875 | self.assertEqual(new[1]["collections"], old[1]["collections"]) | |
| 868 | 876 | self.assertEqual(new[2]["collections"], old[2]["collections"] + 1) | |
| 877 | + self.assertEqual(new[0]["duration"], old[0]["duration"]) | ||
| 878 | + self.assertEqual(new[1]["duration"], old[1]["duration"]) | ||
| 879 | + self.assertGreater(new[2]["duration"], old[2]["duration"]) | ||
| 880 | + for stat in ["collected", "uncollectable"]: | ||
| 881 | + self.assertEqual(new[0][stat], old[0][stat]) | ||
| 882 | + self.assertEqual(new[1][stat], old[1][stat]) | ||
| 883 | + self.assertGreaterEqual(new[2][stat], old[2][stat]) | ||
| 869 | 884 | ||
| 870 | 885 | def test_freeze(self): | |
| 871 | 886 | gc.freeze() | |
@@ -1298,9 +1313,10 @@ def test_collect(self): | |||
| 1298 | 1313 | # Check that we got the right info dict for all callbacks | |
| 1299 | 1314 | for v in self.visit: | |
| 1300 | 1315 | info = v[2] | |
| 1301 | - self.assertTrue("generation" in info) | ||
| 1302 | - self.assertTrue("collected" in info) | ||
| 1303 | - self.assertTrue("uncollectable" in info) | ||
| 1316 | + self.assertIn("generation", info) | ||
| 1317 | + self.assertIn("collected", info) | ||
| 1318 | + self.assertIn("uncollectable", info) | ||
| 1319 | + self.assertIn("duration", info) | ||
| 1304 | 1320 | ||
| 1305 | 1321 | def test_collect_generation(self): | |
| 1306 | 1322 | self.preclean() | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,2 @@ | |||
| 1 | + Expose a ``"duration"`` stat in :func:`gc.get_stats` and | ||
| 2 | + :data:`gc.callbacks`. | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -358,10 +358,11 @@ gc_get_stats_impl(PyObject *module) | |||
| 358 | 358 | for (i = 0; i < NUM_GENERATIONS; i++) { | |
| 359 | 359 | PyObject *dict; | |
| 360 | 360 | st = &stats[i]; | |
| 361 | - dict = Py_BuildValue("{snsnsn}", | ||
| 361 | + dict = Py_BuildValue("{snsnsnsd}", | ||
| 362 | 362 | "collections", st->collections, | |
| 363 | 363 | "collected", st->collected, | |
| 364 | - "uncollectable", st->uncollectable | ||
| 364 | + "uncollectable", st->uncollectable, | ||
| 365 | + "duration", st->duration | ||
| 365 | 366 | ); | |
| 366 | 367 | if (dict == NULL) | |
| 367 | 368 | goto error; | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -1363,6 +1363,7 @@ gc_list_set_space(PyGC_Head *list, int space) | |||
| 1363 | 1363 | static void | |
| 1364 | 1364 | add_stats(GCState *gcstate, int gen, struct gc_collection_stats *stats) | |
| 1365 | 1365 | { | |
| 1366 | + gcstate->generation_stats[gen].duration += stats->duration; | ||
| 1366 | 1367 | gcstate->generation_stats[gen].collected += stats->collected; | |
| 1367 | 1368 | gcstate->generation_stats[gen].uncollectable += stats->uncollectable; | |
| 1368 | 1369 | gcstate->generation_stats[gen].collections += 1; | |
@@ -1387,7 +1388,6 @@ gc_collect_young(PyThreadState *tstate, | |||
| 1387 | 1388 | validate_spaces(gcstate); | |
| 1388 | 1389 | gcstate->young.count = 0; | |
| 1389 | 1390 | gcstate->old[gcstate->visited_space].count++; | |
| 1390 | - add_stats(gcstate, 0, stats); | ||
| 1391 | 1391 | validate_spaces(gcstate); | |
| 1392 | 1392 | } | |
| 1393 | 1393 | ||
@@ -1701,7 +1701,6 @@ gc_collect_increment(PyThreadState *tstate, struct gc_collection_stats *stats) | |||
| 1701 | 1701 | assert(gc_list_is_empty(&increment)); | |
| 1702 | 1702 | gcstate->work_to_do -= increment_size; | |
| 1703 | 1703 | ||
| 1704 | - add_stats(gcstate, 1, stats); | ||
| 1705 | 1704 | if (gc_list_is_empty(not_visited)) { | |
| 1706 | 1705 | completed_scavenge(gcstate); | |
| 1707 | 1706 | } | |
@@ -1736,7 +1735,6 @@ gc_collect_full(PyThreadState *tstate, | |||
| 1736 | 1735 | completed_scavenge(gcstate); | |
| 1737 | 1736 | _PyGC_ClearAllFreeLists(tstate->interp); | |
| 1738 | 1737 | validate_spaces(gcstate); | |
| 1739 | - add_stats(gcstate, 2, stats); | ||
| 1740 | 1738 | } | |
| 1741 | 1739 | ||
| 1742 | 1740 | /* This is the main function. Read this to understand how the | |
@@ -1846,10 +1844,11 @@ do_gc_callback(GCState *gcstate, const char *phase, | |||
| 1846 | 1844 | assert(PyList_CheckExact(gcstate->callbacks)); | |
| 1847 | 1845 | PyObject *info = NULL; | |
| 1848 | 1846 | if (PyList_GET_SIZE(gcstate->callbacks) != 0) { | |
| 1849 | - info = Py_BuildValue("{sisnsn}", | ||
| 1847 | + info = Py_BuildValue("{sisnsnsd}", | ||
| 1850 | 1848 | "generation", generation, | |
| 1851 | 1849 | "collected", stats->collected, | |
| 1852 | - "uncollectable", stats->uncollectable); | ||
| 1850 | + "uncollectable", stats->uncollectable, | ||
| 1851 | + "duration", stats->duration); | ||
| 1853 | 1852 | if (info == NULL) { | |
| 1854 | 1853 | PyErr_FormatUnraisable("Exception ignored while invoking gc callbacks"); | |
| 1855 | 1854 | return; | |
@@ -2080,15 +2079,15 @@ _PyGC_Collect(PyThreadState *tstate, int generation, _PyGC_Reason reason) | |||
| 2080 | 2079 | if (reason != _Py_GC_REASON_SHUTDOWN) { | |
| 2081 | 2080 | invoke_gc_callback(gcstate, "start", generation, &stats); | |
| 2082 | 2081 | } | |
| 2083 | - PyTime_t t1; | ||
| 2084 | 2082 | if (gcstate->debug & _PyGC_DEBUG_STATS) { | |
| 2085 | 2083 | PySys_WriteStderr("gc: collecting generation %d...\n", generation); | |
| 2086 | - (void)PyTime_PerfCounterRaw(&t1); | ||
| 2087 | 2084 | show_stats_each_generations(gcstate); | |
| 2088 | 2085 | } | |
| 2089 | 2086 | if (PyDTrace_GC_START_ENABLED()) { | |
| 2090 | 2087 | PyDTrace_GC_START(generation); | |
| 2091 | 2088 | } | |
| 2089 | + PyTime_t start, stop; | ||
| 2090 | + (void)PyTime_PerfCounterRaw(&start); | ||
| 2092 | 2091 | PyObject *exc = _PyErr_GetRaisedException(tstate); | |
| 2093 | 2092 | switch(generation) { | |
| 2094 | 2093 | case 0: | |
@@ -2103,6 +2102,9 @@ _PyGC_Collect(PyThreadState *tstate, int generation, _PyGC_Reason reason) | |||
| 2103 | 2102 | default: | |
| 2104 | 2103 | Py_UNREACHABLE(); | |
| 2105 | 2104 | } | |
| 2105 | + (void)PyTime_PerfCounterRaw(&stop); | ||
| 2106 | + stats.duration = PyTime_AsSecondsDouble(stop - start); | ||
| 2107 | + add_stats(gcstate, generation, &stats); | ||
| 2106 | 2108 | if (PyDTrace_GC_DONE_ENABLED()) { | |
| 2107 | 2109 | PyDTrace_GC_DONE(stats.uncollectable + stats.collected); | |
| 2108 | 2110 | } | |
@@ -2124,12 +2126,9 @@ _PyGC_Collect(PyThreadState *tstate, int generation, _PyGC_Reason reason) | |||
| 2124 | 2126 | _Py_atomic_store_int(&gcstate->collecting, 0); | |
| 2125 | 2127 | ||
| 2126 | 2128 | if (gcstate->debug & _PyGC_DEBUG_STATS) { | |
| 2127 | - PyTime_t t2; | ||
| 2128 | - (void)PyTime_PerfCounterRaw(&t2); | ||
| 2129 | - double d = PyTime_AsSecondsDouble(t2 - t1); | ||
| 2130 | 2129 | PySys_WriteStderr( | |
| 2131 | 2130 | "gc: done, %zd unreachable, %zd uncollectable, %.4fs elapsed\n", | |
| 2132 | - stats.collected + stats.uncollectable, stats.uncollectable, d | ||
| 2131 | + stats.collected + stats.uncollectable, stats.uncollectable, stats.duration | ||
| 2133 | 2132 | ); | |
| 2134 | 2133 | } | |
| 2135 | 2134 | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -1911,7 +1911,7 @@ handle_resurrected_objects(struct collection_state *state) | |||
| 1911 | 1911 | static void | |
| 1912 | 1912 | invoke_gc_callback(PyThreadState *tstate, const char *phase, | |
| 1913 | 1913 | int generation, Py_ssize_t collected, | |
| 1914 | - Py_ssize_t uncollectable) | ||
| 1914 | + Py_ssize_t uncollectable, double duration) | ||
| 1915 | 1915 | { | |
| 1916 | 1916 | assert(!_PyErr_Occurred(tstate)); | |
| 1917 | 1917 | ||
@@ -1925,10 +1925,11 @@ invoke_gc_callback(PyThreadState *tstate, const char *phase, | |||
| 1925 | 1925 | assert(PyList_CheckExact(gcstate->callbacks)); | |
| 1926 | 1926 | PyObject *info = NULL; | |
| 1927 | 1927 | if (PyList_GET_SIZE(gcstate->callbacks) != 0) { | |
| 1928 | - info = Py_BuildValue("{sisnsn}", | ||
| 1928 | + info = Py_BuildValue("{sisnsnsd}", | ||
| 1929 | 1929 | "generation", generation, | |
| 1930 | 1930 | "collected", collected, | |
| 1931 | - "uncollectable", uncollectable); | ||
| 1931 | + "uncollectable", uncollectable, | ||
| 1932 | + "duration", duration); | ||
| 1932 | 1933 | if (info == NULL) { | |
| 1933 | 1934 | PyErr_FormatUnraisable("Exception ignored while " | |
| 1934 | 1935 | "invoking gc callbacks"); | |
@@ -2340,7 +2341,6 @@ gc_collect_main(PyThreadState *tstate, int generation, _PyGC_Reason reason) | |||
| 2340 | 2341 | { | |
| 2341 | 2342 | Py_ssize_t m = 0; /* # objects collected */ | |
| 2342 | 2343 | Py_ssize_t n = 0; /* # unreachable objects that couldn't be collected */ | |
| 2343 | - PyTime_t t1 = 0; /* initialize to prevent a compiler warning */ | ||
| 2344 | 2344 | GCState *gcstate = &tstate->interp->gc; | |
| 2345 | 2345 | ||
| 2346 | 2346 | // gc_collect_main() must not be called before _PyGC_Init | |
@@ -2372,19 +2372,19 @@ gc_collect_main(PyThreadState *tstate, int generation, _PyGC_Reason reason) | |||
| 2372 | 2372 | GC_STAT_ADD(generation, collections, 1); | |
| 2373 | 2373 | ||
| 2374 | 2374 | if (reason != _Py_GC_REASON_SHUTDOWN) { | |
| 2375 | - invoke_gc_callback(tstate, "start", generation, 0, 0); | ||
| 2375 | + invoke_gc_callback(tstate, "start", generation, 0, 0, 0); | ||
| 2376 | 2376 | } | |
| 2377 | 2377 | ||
| 2378 | 2378 | if (gcstate->debug & _PyGC_DEBUG_STATS) { | |
| 2379 | 2379 | PySys_WriteStderr("gc: collecting generation %d...\n", generation); | |
| 2380 | 2380 | show_stats_each_generations(gcstate); | |
| 2381 | - // ignore error: don't interrupt the GC if reading the clock fails | ||
| 2382 | - (void)PyTime_PerfCounterRaw(&t1); | ||
| 2383 | 2381 | } | |
| 2384 | 2382 | ||
| 2385 | 2383 | if (PyDTrace_GC_START_ENABLED()) { | |
| 2386 | 2384 | PyDTrace_GC_START(generation); | |
| 2387 | 2385 | } | |
| 2386 | + PyTime_t start, stop; | ||
| 2387 | + (void)PyTime_PerfCounterRaw(&start); | ||
| 2388 | 2388 | ||
| 2389 | 2389 | PyInterpreterState *interp = tstate->interp; | |
| 2390 | 2390 | ||
@@ -2399,13 +2399,13 @@ gc_collect_main(PyThreadState *tstate, int generation, _PyGC_Reason reason) | |||
| 2399 | 2399 | m = state.collected; | |
| 2400 | 2400 | n = state.uncollectable; | |
| 2401 | 2401 | ||
| 2402 | + (void)PyTime_PerfCounterRaw(&stop); | ||
| 2403 | + double duration = PyTime_AsSecondsDouble(stop - start); | ||
| 2404 | + | ||
| 2402 | 2405 | if (gcstate->debug & _PyGC_DEBUG_STATS) { | |
| 2403 | - PyTime_t t2; | ||
| 2404 | - (void)PyTime_PerfCounterRaw(&t2); | ||
| 2405 | - double d = PyTime_AsSecondsDouble(t2 - t1); | ||
| 2406 | 2406 | PySys_WriteStderr( | |
| 2407 | 2407 | "gc: done, %zd unreachable, %zd uncollectable, %.4fs elapsed\n", | |
| 2408 | - n+m, n, d); | ||
| 2408 | + n+m, n, duration); | ||
| 2409 | 2409 | } | |
| 2410 | 2410 | ||
| 2411 | 2411 | // Clear the current thread's free-list again. | |
@@ -2426,6 +2426,7 @@ gc_collect_main(PyThreadState *tstate, int generation, _PyGC_Reason reason) | |||
| 2426 | 2426 | stats->collections++; | |
| 2427 | 2427 | stats->collected += m; | |
| 2428 | 2428 | stats->uncollectable += n; | |
| 2429 | + stats->duration += duration; | ||
| 2429 | 2430 | ||
| 2430 | 2431 | GC_STAT_ADD(generation, objects_collected, m); | |
| 2431 | 2432 | #ifdef Py_STATS | |
@@ -2444,7 +2445,7 @@ gc_collect_main(PyThreadState *tstate, int generation, _PyGC_Reason reason) | |||
| 2444 | 2445 | } | |
| 2445 | 2446 | ||
| 2446 | 2447 | if (reason != _Py_GC_REASON_SHUTDOWN) { | |
| 2447 | - invoke_gc_callback(tstate, "stop", generation, m, n); | ||
| 2448 | + invoke_gc_callback(tstate, "stop", generation, m, n, duration); | ||
| 2448 | 2449 | } | |
| 2449 | 2450 | ||
| 2450 | 2451 | assert(!_PyErr_Occurred(tstate)); | |
| Back | FazBrowse Home | New Git URL |
0 commit comments