Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions prometheus_client/bridge/graphite.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,12 +53,12 @@ def push(self):
now = int(self._time.time())
output = []
for metric in self._registry.collect():
for name, labels, value in metric._samples:
for name, labels, value in metric.samples:
if labels:
labelstr = '.' + '.'.join(
['{0}.{1}'.format(
_sanitize(k), _sanitize(v))
for k, v in sorted(labels.items())])
_sanitize(k), _sanitize(v))
for k, v in sorted(labels.items())])
else:
labelstr = ''
output.append('{0}{1} {2} {3}\n'.format(
Expand Down
148 changes: 139 additions & 9 deletions prometheus_client/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from __future__ import unicode_literals

import copy
import math
import re
import time
import types
Expand Down Expand Up @@ -61,7 +62,7 @@ def get_sample_value(self, name, labels=None):
if labels is None:
labels = {}
for metric in self.collect():
for n, l, value in metric._samples:
for n, l, value in metric.samples:
if n == name and l == labels:
return value
return None
Expand All @@ -74,18 +75,145 @@ def get_sample_value(self, name, labels=None):


class Metric(object):
'''A single metric and it's samples.'''
'''A single metric family and its samples.

This is intended only for internal use by the instrumentation client.

Custom collectors should use GaugeMetricFamily, CounterMetricFamily
and SummaryMetricFamily instead.
'''
def __init__(self, name, documentation, typ):

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Has nothing to do with the change, but: Why typ and not type?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NM, keywords are evil... :-(

self._name = name
self._documentation = documentation
self.name = name
self.documentation = documentation
if typ not in _METRIC_TYPES:
raise ValueError('Invalid metric type: ' + typ)
self._type = typ
self._samples = []
self.type = typ
self.samples = []

'''Add a sample to the metric'''
def add_sample(self, name, labels, value):
self._samples.append((name, labels, value))
'''Add a sample to the metric.

Internal-only, do not use.'''
self.samples.append((name, labels, value))

def __eq__(self, other):
return (isinstance(other, Metric)
and self.name == other.name
and self.documentation == other.documentation
and self.type == other.type
and self.samples == other.samples)


class CounterMetricFamily(Metric):
'''A single counter and its samples.

For use by custom collectors.
'''
def __init__(self, name, documentation, value=None, labels=None):
Metric.__init__(self, name, documentation, 'counter')
if labels is not None and value is not None:
raise ValueError('Can only specify at most one of value and labels.')
if labels is None:
labels = []
self._labelnames = labels
if value is not None:
self.add_metric([], value)

def add_metric(self, labels, value):
'''Add a metric to the metric family.

Args:
labels: A list of label values
value: The value of the metric.
'''
self.samples.append((self.name, dict(zip(self._labelnames, labels)), value))


class GaugeMetricFamily(Metric):
'''A single gauge and its samples.

For use by custom collectors.
'''
def __init__(self, name, documentation, value=None, labels=None):
Metric.__init__(self, name, documentation, 'gauge')
if labels is not None and value is not None:
raise ValueError('Can only specify at most one of value and labels.')
if labels is None:
labels = []
self._labelnames = labels
if value is not None:
self.add_metric([], value)

def add_metric(self, labels, value):
'''Add a metric to the metric family.

Args:
labels: A list of label values
value: A float
'''
self.samples.append((self.name, dict(zip(self._labelnames, labels)), value))


class SummaryMetricFamily(Metric):
'''A single summary and its samples.

For use by custom collectors.
'''
def __init__(self, name, documentation, count_value=None, sum_value=None, labels=None):
Metric.__init__(self, name, documentation, 'summary')
if (sum_value is None) != (count_value is None):
raise ValueError('count_value and sum_value must be provided together.')
if labels is not None and count_value is not None:
raise ValueError('Can only specify at most one of value and labels.')
if labels is None:
labels = []
self._labelnames = labels
if count_value is not None:
self.add_metric([], count_value, sum_value)

def add_metric(self, labels, count_value, sum_value):
'''Add a metric to the metric family.

Args:
labels: A list of label values
count_value: The count value of the metric.
sum_value: The sum value of the metric.
'''
self.samples.append((self.name + '_count', dict(zip(self._labelnames, labels)), count_value))
self.samples.append((self.name + '_sum', dict(zip(self._labelnames, labels)), sum_value))


class HistogramMetricFamily(Metric):
'''A single histogram and its samples.

For use by custom collectors.
'''
def __init__(self, name, documentation, buckets=None, sum_value=None, labels=None):
Metric.__init__(self, name, documentation, 'histogram')
if (sum_value is None) != (buckets is None):
raise ValueError('buckets and sum_value must be provided together.')
if labels is not None and buckets is not None:
raise ValueError('Can only specify at most one of buckets and labels.')
if labels is None:
labels = []
self._labelnames = labels
if buckets is not None:
self.add_metric([], buckets, sum_value)

def add_metric(self, labels, buckets, sum_value):
'''Add a metric to the metric family.

Args:
labels: A list of label values
buckets: A list of pairs of bucket names and values.
The buckets must be sorted, and +Inf present.
sum_value: The sum value of the metric.
'''
for bucket, value in buckets:
self.samples.append((self.name + '_bucket', dict(list(zip(self._labelnames, labels)) + [('le', bucket)]), value))
# +Inf is last and provides the count value.
self.samples.append((self.name + '_count', dict(zip(self._labelnames, labels)), buckets[-1][1]))
self.samples.append((self.name + '_sum', dict(zip(self._labelnames, labels)), sum_value))


class _MutexValue(object):
Expand Down Expand Up @@ -271,7 +399,7 @@ def _samples(self):
@_MetricWrapper
class Gauge(object):
'''Gauge metric, to report instantaneous values.

Examples of Gauges include:
Inprogress requests
Number of items in a queue
Expand Down Expand Up @@ -450,6 +578,8 @@ def _floatToGoString(d):
return '+Inf'
elif d == _MINUS_INF:
return '-Inf'
elif math.isnan(d):
return 'NaN'
else:
return repr(float(d))

Expand Down
6 changes: 3 additions & 3 deletions prometheus_client/exposition.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,9 @@ def generate_latest(registry=core.REGISTRY):
output = []
for metric in registry.collect():
output.append('# HELP {0} {1}'.format(
metric._name, metric._documentation.replace('\\', r'\\').replace('\n', r'\n')))
output.append('\n# TYPE {0} {1}\n'.format(metric._name, metric._type))
for name, labels, value in metric._samples:
metric.name, metric.documentation.replace('\\', r'\\').replace('\n', r'\n')))
output.append('\n# TYPE {0} {1}\n'.format(metric.name, metric.type))
for name, labels, value in metric.samples:
if labels:
labelstr = '{{{0}}}'.format(','.join(
['{0}="{1}"'.format(
Expand Down
Loading