10000 Add timestamp type for nano resolution · hidesoon/client_python@53726b3 · GitHub
[go: up one dir, main page]

Skip to content

Commit 53726b3

Browse files
committed
Add timestamp type for nano resolution
Add support for this in Prometheus text format exposition Signed-off-by: Brian Brazil <brian.brazil@robustperception.io>
1 parent 7e3385b commit 53726b3

File tree

3 files changed

+58
-7
lines changed

3 files changed

+58
-7
lines changed

prometheus_client/core.py

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -35,11 +35,29 @@
3535
_unpack_integer = struct.Struct(b'i').unpack_from
3636
_unpack_double = struct.Struct(b'd').unpack_from
3737

38-
Sample = namedtuple('Sample', ['name', 'labels', 'value', 'timestamp', 'exemplar'])
39-
# Value can be an int or a float.
4038
# Timestamp and exemplar are optional.
39+
# Value can be an int or a float.
40+
# Timestamp can be a float containing a unixtime in seconds,
41+
# or a Timestamp object.
42+
Sample = namedtuple('Sample', ['name', 'labels', 'value', 'timestamp', 'exemplar'])
4143
Sample.__new__.__defaults__ = (None, None)
4244

45+
46+
class Timestamp(object):
47+
'''A nanosecond-resolution timestamp.'''
48+
def __init__(self, sec, nsec):
49+
if nsec < 0 or nsec >= 1e9:
50+
raise ValueError("Invalid value for nanoseconds in Timestamp: {}".format(nsec))
51+
self.sec = int(sec)
52+
self.nsec = int(nsec)
53+
54+
def __str__(self):
55+
return "{0}.{1:09d}".format(self.sec, self.nsec)
56+
57+
def __float__(self):
58+
return float(self.sec) + float(self.nsec) / 1e9
59+
60+
4361
class CollectorRegistry(object):
4462
'''Metric collector registry.
4563
@@ -60,7 +78,7 @@ def register(self, collector):
6078
duplicates = set(self._names_to_collectors).intersection(names)
6179
if duplicates:
6280
raise ValueError(
63-
'Duplicated timeseries in CollectorRegistry: {}'.format(
81+
'Duplicated timeseries in CollectorRegistry: {0}'.format(
6482
duplicates))
6583
for name in names:
6684
self._names_to_collectors[name] = collector
@@ -179,19 +197,24 @@ def __init__(self, name, documentation, typ, unit=''):
179197
if typ not in _METRIC_TYPES:
180198
raise ValueError('Invalid metric type: ' + typ)
181199
self.type = typ
200+
if unit:
201+
if not name.endswith('_' + unit):
202+
raise ValueError('Metric name does not end with unit: ' + name)
203+
self.unit = unit
182204
self.samples = []
183205

184-
def add_sample(self, name, labels, value):
206+
def add_sample(self, name, labels, value, timestamp=None):
185207
'''Add a sample to the metric.
186208
187209
Internal-only, do not use.'''
188-
self.samples.append(Sample(name, labels, value))
210+
self.samples.append(Sample(name, labels, value, timestamp))
189211

190212
def __eq__(self, other):
191213
return (isinstance(other, Metric) and
192214
self.name == other.name and
193215
self.documentation == other.documentation and
194216
self.type == other.type and
217+
self.unit == other.unit and
195218
self.samples == other.samples)
196219

197220
def __repr__(self):

prometheus_client/exposition.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,12 @@ def generate_latest(registry=core.REGISTRY):
9595
for k, v in sorted(s.labels.items())]))
9696
else:
9797
labelstr = ''
98-
output.append('{0}{1} {2}\n'.format(s.name, labelstr, core._floatToGoString(s.value)))
98+
timestamp = ''
99+
if s.timestamp is not None:
100+
# Convert to milliseconds.
101+
timestamp = ' {0:d}'.format(int(float(s.timestamp) * 1000))
102+
output.append('{0}{1} {2}{3}\n'.format(
103+
s.name, labelstr, core._floatToGoString(s.value), timestamp))
99104
return ''.join(output).encode('utf-8')
100105

101106

tests/test_exposition.py

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
from prometheus_client import CollectorRegistry, generate_latest
1414
from prometheus_client import push_to_gateway, pushadd_to_gateway, delete_from_gateway
1515
from prometheus_client import CONTENT_TYPE_LATEST, instance_ip_grouping_key
16-
from prometheus_client.core import GaugeHistogramMetricFamily
16+
from prometheus_client.core import GaugeHistogramMetricFamily, Timestamp
1717
from prometheus_client.exposition import default_handler, basic_auth_handler, MetricsHandler
1818

1919
try:
@@ -121,6 +121,29 @@ def collect(self):
121121
self.registry.register(MyCollector())
122122
self.assertEqual(b'# HELP nonnumber Non number\n# TYPE nonnumber untyped\nnonnumber 123.0\n', generate_latest(self.registry))
123123

124+
def test_timestamp(self):
125+
class MyCollector(object):
126+
def collect(self):
127+
metric = Metric("ts", "help", 'untyped')
128+
metric.add_sample("ts", {"foo": "a"}, 0, 123.456)
129+
metric.add_sample("ts", {"foo": "b"}, 0, -123.456)
130+
metric.add_sample("ts", {"foo": "c"}, 0, 123)
131+
metric.add_sample("ts", {"foo": "d"}, 0, Timestamp(123, 456000000))
132+
metric.add_sample("ts", {"foo": "e"}, 0, Timestamp(123, 456000))
133+
metric.add_sample("ts", {"foo": "f"}, 0, Timestamp(123, 456))
134+
yield metric
135+
136+
self.registry.register(MyCollector())
137+
self.assertEqual(b'''# HELP ts help
138+
# TYPE ts untyped
139+
ts{foo="a"} 0.0 123456
140+
ts{foo="b"} 0.0 -123456
141+
ts{foo="c"} 0.0 123000
142+
ts{foo="d"} 0.0 123456
143+
ts{foo="e"} 0.0 123000
144+
ts{foo="f"} 0.0 123000
145+
''', generate_latest(self.registry))
146+
124147

125148
class TestPushGateway(unittest.TestCase):
126149
def setUp(self):

0 commit comments

Comments
 (0)
0