Skip to content

Commit 4bf1f5c

Browse files
committed
Provide MetricFamilies to make custom collectors easier.
1 parent c67b1e1 commit 4bf1f5c

File tree

2 files changed

+134
-17
lines changed

2 files changed

+134
-17
lines changed

prometheus_client/core.py

Lines changed: 123 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,14 @@ def get_sample_value(self, name, labels=None):
7474

7575

7676
class Metric(object):
77-
'''A single metric and it's samples.'''
77+
'''A single metric family and its samples.
78+
79+
This is intended only for internal use by the instrumentation client.
80+
81+
82+
Custom collectors should use GaugeMetricFamily, CounterMetricFamily
83+
and SummaryMetricFamily instead.
84+
'''
7885
def __init__(self, name, documentation, typ):
7986
self._name = name
8087
self._documentation = documentation
@@ -83,11 +90,124 @@ def __init__(self, name, documentation, typ):
8390
self._type = typ
8491
self._samples = []
8592

86-
'''Add a sample to the metric'''
8793
def add_sample(self, name, labels, value):
94+
'''Add a sample to the metric.
95+
96+
Internal-only, do not use.'''
8897
self._samples.append((name, labels, value))
8998

9099

100+
class GaugeMetricFamily(Metric):
101+
'''A single gauge and its samples.
102+
103+
For use by custom collectors.
104+
'''
105+
def __init__(self, name, documentation, value=None, labels=None):
106+
Metric.__init__(self, name, documentation, 'gauge')
107+
if labels is not None and value is not None:
108+
raise ValueError('Can only specify at most one of value and labels.')
109+
if labels is None:
110+
labels = []
111+
self._labelnames = labels
112+
if value is not None:
113+
self.add_metric([], value)
114+
115+
def add_metric(self, labels, value):
116+
'''Add a metric to the metric family.
117+
118+
Args:
119+
labels: A list of label values
120+
value: A float
121+
'''
122+
self._samples.append((self._name, dict(zip(self._labelnames, labels)), value))
123+
124+
125+
class CounterMetricFamily(Metric):
126+
'''A single counter and its samples.
127+
128+
For use by custom collectors.
129+
'''
130+
def __init__(self, name, documentation, value=None, labels=None):
131+
Metric.__init__(self, name, documentation, 'counter')
132+
if labels is not None and value is not None:
133+
raise ValueError('Can only specify at most one of value and labels.')
134+
if labels is None:
135+
labels = []
136+
self._labelnames = labels
137+
if value is not None:
138+
self.add_metric([], value)
139+
140+
def add_metric(self, labels, value):
141+
'''Add a metric to the metric family.
142+
143+
Args:
144+
labels: A list of label values
145+
value: The value of the metric.
146+
'''
147+
self._samples.append((self._name, dict(zip(self._labelnames, labels)), value))
148+
149+
150+
class SummaryMetricFamily(Metric):
151+
'''A single summary and its samples.
152+
153+
For use by custom collectors.
154+
'''
155+
def __init__(self, name, documentation, count_value=None, sum_value=None, labels=None):
156+
Metric.__init__(self, name, documentation, 'summary')
157+
if sum_value is not None != count_value is not None:
158+
raise ValueError('count_value and sum_value must be provided together.')
159+
if labels is not None and count_value is not None:
160+
raise ValueError('Can only specify at most one of value and labels.')
161+
if labels is None:
162+
labels = []
163+
self._labelnames = labels
164+
if value is not None:
165+
self.add_metric([], count_value, sum_value)
166+
167+
def add_metric(self, labels, count_value, sum_value):
168+
'''Add a metric to the metric family.
169+
170+
Args:
171+
labels: A list of label values
172+
count_value: The count value of the metric.
173+
sum_value: The sum value of the metric.
174+
'''
175+
self._samples.append((self._name + u'_count', dict(zip(self._labelnames, labels)), count_value))
176+
self._samples.append((self._name + u'_sum', dict(zip(self._labelnames, labels)), sum_value))
177+
178+
179+
class HistogramMetricFamily(Metric):
180+
'''A single histogram and its samples.
181+
182+
For use by custom collectors.
183+
'''
184+
def __init__(self, name, documentation, buckets=None, sum_value=None, labels=None):
185+
Metric.__init__(self, name, documentation, 'histogram')
186+
if sum_value is not None != buckets is not None:
187+
raise ValueError('buckets and sum_value must be provided together.')
188+
if labels is not None and buckets is not None:
189+
raise ValueError('Can only specify at most one of buckets and labels.')
190+
if labels is None:
191+
labels = []
192+
self._labelnames = labels
193+
if value is not None:
194+
self.add_metric([], buckets, sum_value)
195+
196+
def add_metric(self, labels, buckets, sum_value):
197+
'''Add a metric to the metric family.
198+
199+
Args:
200+
labels: A list of label values
201+
buckets: A dict of bucket names to values. The +Inf key must be present.
202+
sum_value: The sum value of the metric.
203+
'''
204+
for bucket, value in buckets.items:
205+
self._samples.append((self._name + u'_bucket', dict(zip(self._labelnames, labels) + (u'le', bucket)), value))
206+
self._samples.append((self._name + u'_count', dict(zip(self._labelnames, labels)), buckets['+Inf']))
207+
self._samples.append((self._name + u'_sum', dict(zip(self._labelnames, labels)), sum_value))
208+
209+
210+
91211
class _MutexValue(object):
92212
'''A float protected by a mutex.'''
93213

@@ -271,7 +391,7 @@ def _samples(self):
271391
@_MetricWrapper
272392
class Gauge(object):
273393
'''Gauge metric, to report instantaneous values.
274-
394+
275395
Examples of Gauges include:
276396
Inprogress requests
277397
Number of items in a queue

prometheus_client/process_collector.py

Lines changed: 11 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -61,32 +61,29 @@ def collect(self):
6161
try:
6262
with open(os.path.join(pid, 'stat')) as stat:
6363
parts = (stat.read().split(')')[-1].split())
64-
vmem = core.Metric(self._prefix + 'virtual_memory_bytes', 'Virtual memory size in bytes', 'gauge')
65-
vmem.add_sample(self._prefix + 'virtual_memory_bytes', {}, float(parts[20]))
66-
rss = core.Metric(self._prefix + 'resident_memory_bytes', 'Resident memory size in bytes', 'gauge')
67-
rss.add_sample(self._prefix + 'resident_memory_bytes', {}, float(parts[21]) * _PAGESIZE)
68-
start_time = core.Metric(self._prefix + 'start_time_seconds',
69-
'Start time of the process since unix epoch in seconds.', 'gauge')
64+
vmem = core.GaugeMetricFamily(self._prefix + 'virtual_memory_bytes',
65+
'Virtual memory size in bytes', value=float(parts[20]))
66+
rss = core.GaugeMetricFamily(self._prefix + 'resident_memory_bytes', 'Resident memory size in bytes', value=float(parts[21]) * _PAGESIZE)
7067
start_time_secs = float(parts[19]) / self._ticks
71-
start_time.add_sample(self._prefix + 'start_time_seconds',{} , start_time_secs + self._btime)
68+
start_time = core.GaugeMetricFamily(self._prefix + 'start_time_seconds',
69+
'Start time of the process since unix epoch in seconds.', value=start_time_secs + self._btime)
7270
utime = float(parts[11]) / self._ticks
7371
stime = float(parts[12]) / self._ticks
74-
cpu = core.Metric(self._prefix + 'cpu_seconds_total',
75-
'Total user and system CPU time spent in seconds.', 'counter')
76-
cpu.add_sample(self._prefix + 'cpu_seconds_total', {}, utime + stime)
72+
cpu = core.CounterMetricFamily(self._prefix + 'cpu_seconds_total',
73+
'Total user and system CPU time spent in seconds.', value=utime + stime)
7774
result.extend([vmem, rss, start_time, cpu])
7875
except IOError:
7976
pass
8077

8178
try:
82-
max_fds = core.Metric(self._prefix + 'max_fds', 'Maximum number of open file descriptors.', 'gauge')
8379
with open(os.path.join(pid, 'limits')) as limits:
8480
for line in limits:
8581
if line.startswith('Max open file'):
86-
max_fds.add_sample(self._prefix + 'max_fds', {}, float(line.split()[3]))
82+
max_fds = core.GaugeMetricFamily(self._prefix + 'max_fds',
83+
'Maximum number of open file descriptors.', value=float(line.split()[3]))
8784
break
88-
open_fds = core.Metric(self._prefix + 'open_fds', 'Number of open file descriptors.', 'gauge')
89-
open_fds.add_sample(self._prefix + 'open_fds', {}, len(os.listdir(os.path.join(pid, 'fd'))))
85+
open_fds = core.GaugeMetricFamily(self._prefix + 'open_fds',
86+
'Number of open file descriptors.', len(os.listdir(os.path.join(pid, 'fd'))))
9087
result.extend([open_fds, max_fds])
9188
except IOError:
9289
pass

0 commit comments

Comments
 (0)