From afb9db9033603427eceee4cbc81a4102423a8b09 Mon Sep 17 00:00:00 2001 From: Tristan Colgate Date: Fri, 31 Aug 2018 08:33:25 +0100 Subject: [PATCH] Add detailed Garbage Collection statistics. This patch adds details stats for the python garbage collector. - `python_gc_collected_objects`: Histogram of the number of objects collected, by GC generation. - `python_gc_uncollectable_objects`: Histogram of the number of objects count to be uncollectable, by GC generation. - `python_gc_duration_seconds`: Histogram of the duration of each GC generation. Signed-off-by: Tristan Colgate --- prometheus_client/__init__.py | 3 ++ prometheus_client/gc_collector.py | 59 +++++++++++++++++++++++++++++++ tests/test_gc_collector.py | 54 ++++++++++++++++++++++++++++ 3 files changed, 116 insertions(+) create mode 100644 prometheus_client/gc_collector.py create mode 100644 tests/test_gc_collector.py diff --git a/prometheus_client/__init__.py b/prometheus_client/__init__.py index 8208d458..3a239353 100644 --- a/prometheus_client/__init__.py +++ b/prometheus_client/__init__.py @@ -4,6 +4,7 @@ from . import exposition from . import process_collector from . import platform_collector +from . import gc_collector __all__ = ['Counter', 'Gauge', 'Summary', 'Histogram', 'Info', 'Enum'] @@ -35,6 +36,8 @@ PlatformCollector = platform_collector.PlatformCollector PLATFORM_COLLECTOR = platform_collector.PLATFORM_COLLECTOR +GCCollector = gc_collector.GCCollector +GC_COLLECTOR = gc_collector.GC_COLLECTOR if __name__ == '__main__': c = Counter('cc', 'A counter') diff --git a/prometheus_client/gc_collector.py b/prometheus_client/gc_collector.py new file mode 100644 index 00000000..34efadec --- /dev/null +++ b/prometheus_client/gc_collector.py @@ -0,0 +1,59 @@ +#!/usr/bin/python + +from __future__ import unicode_literals + +import gc +import time + +from . import core + +class GCCollector(object): + """Collector for Garbage collection statistics.""" + def __init__(self, registry=core.REGISTRY, gc=gc): + if not hasattr(gc, 'callbacks'): + return + + collected = core.Histogram( + 'python_gc_collected_objects', + 'Objects collected during gc', + ['generation'], + buckets=[500, 1000, 5000, 10000, 50000], + registry=registry + ) + + uncollectable = core.Histogram( + 'python_gc_uncollectable_objects', + 'Uncollectable object found during GC', + ['generation'], + buckets=[500, 1000, 5000, 10000, 50000], + registry=registry + ) + + latency = core.Histogram( + 'python_gc_duration_seconds', + 'Time spent in garbage collection', + ['generation'], + registry=registry + ) + + times = {} + + def _cb(phase, info): + gen = info['generation'] + + if phase == 'start': + times[gen] = time.time() + + if phase == 'stop': + delta = time.time() - times[gen] + latency.labels(gen).observe(delta) + if 'collected' in info: + collected.labels(gen).observe(info['collected']) + if 'uncollectable' in info: + uncollectable.labels(gen).observe(info['uncollectable']) + + gc.callbacks.append(_cb) + + +GC_COLLECTOR = GCCollector() +"""Default GCCollector in default Registry REGISTRY.""" diff --git a/tests/test_gc_collector.py b/tests/test_gc_collector.py new file mode 100644 index 00000000..573a7dac --- /dev/null +++ b/tests/test_gc_collector.py @@ -0,0 +1,54 @@ +from __future__ import unicode_literals + +import unittest + +from prometheus_client import CollectorRegistry, GCCollector + + +class TestGCCollector(unittest.TestCase): + def setUp(self): + self.registry = CollectorRegistry() + self.gc = _MockGC() + + def test_working(self): + collector = GCCollector(registry=self.registry, gc=self.gc) + self.gc.start_gc({'generation': 0}) + self.gc.stop_gc({'generation': 0, 'collected': 10, 'uncollectable': 2}) + + self.assertEqual(1, + self.registry.get_sample_value( + 'python_gc_duration_seconds_count', + labels={"generation": "0"})) + + self.assertEqual(1, + self.registry.get_sample_value( + 'python_gc_collected_objects_count', + labels={"generation": "0"})) + + self.assertEqual(1, + self.registry.get_sample_value( + 'python_gc_uncollectable_objects_count', + labels={"generation": "0"})) + + self.assertEqual(10, + self.registry.get_sample_value( + 'python_gc_collected_objects_sum', + labels={"generation": "0"})) + + self.assertEqual(2, + self.registry.get_sample_value( + 'python_gc_uncollectable_objects_sum', + labels={"generation": "0"})) + + +class _MockGC(object): + def __init__(self): + self.callbacks = [] + + def start_gc(self, info): + for cb in self.callbacks: + cb('start', info) + + def stop_gc(self, info): + for cb in self.callbacks: + cb('stop', info)