Skip to content

Commit 2181e9e

Browse files
Krukovbrian-brazil
authored andcommitted
Fix deadlock on gcCollector (#371)
* Use gc.stats for gcCollector metrics Signed-off-by: dmitry.kryukov <[email protected]>
1 parent 4aa1936 commit 2181e9e

File tree

2 files changed

+65
-92
lines changed

2 files changed

+65
-92
lines changed

prometheus_client/gc_collector.py

+21-56
Original file line numberDiff line numberDiff line change
@@ -3,79 +3,44 @@
33
from __future__ import unicode_literals
44

55
import gc
6-
import os
7-
import time
86

9-
from .metrics import Histogram
7+
from .metrics_core import CounterMetricFamily
108
from .registry import REGISTRY
119

1210

1311
class GCCollector(object):
1412
"""Collector for Garbage collection statistics."""
1513

16-
def __init__(self, registry=REGISTRY, gc=gc):
17-
# To work around the deadlock issue described in
18-
# https://github.com/prometheus/client_python/issues/322,
19-
# the GC collector is always disabled in multiprocess mode.
20-
if 'prometheus_multiproc_dir' in os.environ:
14+
def __init__(self, registry=REGISTRY):
15+
if not hasattr(gc, 'get_stats'):
2116
return
17+
registry.register(self)
2218

23-
if not hasattr(gc, 'callbacks'):
24-
return
25-
26-
collected = Histogram(
27-
'python_gc_collected_objects',
19+
def collect(self):
20+
collected = CounterMetricFamily(
21+
'python_gc_objects_collected',
2822
'Objects collected during gc',
29-
['generation'],
30-
buckets=[500, 1000, 5000, 10000, 50000],
31-
registry=registry
23+
labels=['generation'],
3224
)
33-
34-
uncollectable = Histogram(
35-
'python_gc_uncollectable_objects',
25+
uncollectable = CounterMetricFamily(
26+
'python_gc_objects_uncollectable',
3627
'Uncollectable object found during GC',
37-
['generation'],
38-
buckets=[500, 1000, 5000, 10000, 50000],
39-
registry=registry
28+
labels=['generation'],
4029
)
4130

42-
latency = Histogram(
43-
'python_gc_duration_seconds',
44-
'Time spent in garbage collection',
45-
['generation'],
46-
registry=registry
31+
collections = CounterMetricFamily(
32+
'python_gc_collections',
33+
'Number of times this generation was collected',
34+
labels=['generation'],
4735
)
4836

49-
times = {}
50-
51-
# Avoid _cb() being called re-entrantly
52-
# by setting this flag and clearing it once
53-
# the callback operation is complete.
54-
# See https://github.com/prometheus/client_python/issues/322#issuecomment-438021132
55-
self.gc_cb_active = False
56-
57-
def _cb(phase, info):
58-
try:
59-
if self.gc_cb_active:
60-
return
61-
self.gc_cb_active = True
62-
63-
gen = info['generation']
64-
65-
if phase == 'start':
66-
times[gen] = time.time()
67-
68-
if phase == 'stop':
69-
delta = time.time() - times[gen]
70-
latency.labels(gen).observe(delta)
71-
if 'collected' in info:
72-
collected.labels(gen).observe(info['collected'])
73-
if 'uncollectable' in info:
74-
uncollectable.labels(gen).observe(info['uncollectable'])
75-
finally:
76-
self.gc_cb_active = False
37+
for generation, stat in enumerate(gc.get_stats()):
38+
generation = str(generation)
39+
collected.add_metric([generation], value=stat['collected'])
40+
uncollectable.add_metric([generation], value=stat['uncollectable'])
41+
collections.add_metric([generation], value=stat['collections'])
7742

78-
gc.callbacks.append(_cb)
43+
return [collected, uncollectable, collections]
7944

8045

8146
GC_COLLECTOR = GCCollector()

tests/test_gc_collector.py

+44-36
Original file line numberDiff line numberDiff line change
@@ -1,54 +1,62 @@
11
from __future__ import unicode_literals
22

3-
import unittest
3+
import gc
4+
import sys
5+
6+
if sys.version_info < (2, 7):
7+
# We need the skip decorators from unittest2 on Python 2.6.
8+
import unittest2 as unittest
9+
else:
10+
import unittest
411

512
from prometheus_client import CollectorRegistry, GCCollector
613

714

15+
@unittest.skipIf(sys.version_info < (3, 4), "Test requires Python 3.4 +")
816
class TestGCCollector(unittest.TestCase):
917
def setUp(self):
18+
gc.disable()
19+
gc.collect()
1020
self.registry = CollectorRegistry()
11-
self.gc = _MockGC()
1221

1322
def test_working(self):
14-
collector = GCCollector(registry=self.registry, gc=self.gc)
15-
self.gc.start_gc({'generation': 0})
16-
self.gc.stop_gc({'generation': 0, 'collected': 10, 'uncollectable': 2})
17-
18-
self.assertEqual(1,
19-
self.registry.get_sample_value(
20-
'python_gc_duration_seconds_count',
21-
labels={"generation": "0"}))
22-
23-
self.assertEqual(1,
24-
self.registry.get_sample_value(
25-
'python_gc_collected_objects_count',
26-
labels={"generation": "0"}))
27-
28-
self.assertEqual(1,
29-
self.registry.get_sample_value(
30-
'python_gc_uncollectable_objects_count',
31-
labels={"generation": "0"}))
32-
33-
self.assertEqual(10,
34-
self.registry.get_sample_value(
35-
'python_gc_collected_objects_sum',
36-
labels={"generation": "0"}))
3723

38-
self.assertEqual(2,
24+
GCCollector(registry=self.registry)
25+
self.registry.collect()
26+
before = self.registry.get_sample_value('python_gc_objects_collected_total',
27+
labels={"generation": "0"})
28+
29+
# add targets for gc
30+
a = []
31+
a.append(a)
32+
del a
33+
b = []
34+
b.append(b)
35+
del b
36+
37+
gc.collect(0)
38+
self.registry.collect()
39+
40+
after = self.registry.get_sample_value('python_gc_objects_collected_total',
41+
labels={"generation": "0"})
42+
self.assertEqual(2, after - before)
43+
self.assertEqual(0,
3944
self.registry.get_sample_value(
40-
'python_gc_uncollectable_objects_sum',
45+
'python_gc_objects_uncollectable_total',
4146
labels={"generation": "0"}))
4247

48+
def test_empty(self):
4349

44-
class _MockGC(object):
45-
def __init__(self):
46-
self.callbacks = []
50+
GCCollector(registry=self.registry)
51+
self.registry.collect()
52+
before = self.registry.get_sample_value('python_gc_objects_collected_total',
53+
labels={"generation": "0"})
54+
gc.collect(0)
55+
self.registry.collect()
4756

48-
def start_gc(self, info):
49-
for cb in self.callbacks:
50-
cb('start', info)
57+
after = self.registry.get_sample_value('python_gc_objects_collected_total',
58+
labels={"generation": "0"})
59+
self.assertEqual(0, after - before)
5160

52-
def stop_gc(self, info):
53-
for cb in self.callbacks:
54-
cb('stop', info)
61+
def tearDown(self):
62+
gc.enable()

0 commit comments

Comments
 (0)