Skip to content

Commit

Permalink
Check ordering within groups.
Browse files Browse the repository at this point in the history
Signed-off-by: Brian Brazil <brian.brazil@robustperception.io>
  • Loading branch information
brian-brazil committed Oct 2, 2018
1 parent 2b3a3c2 commit a6d7e60
Show file tree
Hide file tree
Showing 3 changed files with 42 additions and 11 deletions.
5 changes: 5 additions & 0 deletions prometheus_client/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ class Timestamp(object):
def __init__(self, sec, nsec):
if nsec < 0 or nsec >= 1e9:
raise ValueError("Invalid value for nanoseconds in Timestamp: {}".format(nsec))
if sec < 0:
nsec = -nsec
self.sec = int(sec)
self.nsec = int(nsec)

Expand All @@ -64,6 +66,9 @@ def __float__(self):
def __eq__(self, other):
return type(self) == type(other) and self.sec == other.sec and self.nsec == other.nsec

def __gt__(self, other):
return self.sec > other.sec or self.nsec > other.nsec


Exemplar = namedtuple('Exemplar', ['labels', 'value', 'timestamp'])
Exemplar.__new__.__defaults__ = (None, )
Expand Down
15 changes: 11 additions & 4 deletions prometheus_client/openmetrics/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,7 @@ def text_fd_to_metric_families(fd):
unit = None
group = None
seen_groups = set()
group_timestamp = None
samples = []
allowed_names = []
eof = False
Expand All @@ -285,10 +286,8 @@ def build_metric(name, documentation, typ, unit, samples):
raise ValueError("Units not allowed for this metric type: " + name)
metric = core.Metric(name, documentation, typ, unit)
# TODO: check labelvalues are valid utf8
# TODO: check samples are appropriately ordered
# TODO: Check histogram bucket rules being followed
# TODO: Check for dupliate samples
# TODO: Check for decresing timestamps
# TODO: Check for duplicate samples
metric.samples = samples
return metric

Expand Down Expand Up @@ -317,6 +316,7 @@ def build_metric(name, documentation, typ, unit, samples):
documentation = None
group = None
seen_groups = set()
group_timestamp = None
samples = []
allowed_names = [parts[2]]

Expand Down Expand Up @@ -359,6 +359,7 @@ def build_metric(name, documentation, typ, unit, samples):
typ = 'unknown'
samples = [sample]
group = None
group_timestamp = None
seen_groups = set()
allowed_names = [sample.name]
else:
Expand All @@ -375,8 +376,14 @@ def build_metric(name, documentation, typ, unit, samples):

g = tuple(sorted(_group_for_sample(sample, name, typ).items()))
if group is not None and g != group and g in seen_groups:
raise ValueError("Invalid metric group ordering: " + line)
raise ValueError("Invalid metric grouping: " + line)
if group is not None and g == group:
if (sample.timestamp is None) != (group_timestamp is None):
raise ValueError("Mix of timestamp presence within a group: " + line)
if group_timestamp is not None and group_timestamp > sample.timestamp and typ != 'info':
raise ValueError("Timestamps went backwards within a group: " + line)
group = g
group_timestamp = sample.timestamp
seen_groups.add(g)

if typ == 'stateset' and sample.value not in [0, 1]:
Expand Down
33 changes: 26 additions & 7 deletions tests/openmetrics/test_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -121,13 +121,13 @@ def test_histogram_exemplars(self):
families = text_string_to_metric_families("""# TYPE a histogram
# HELP a help
a_bucket{le="1"} 0 # {a="b"} 0.5
a_bucket{le="2"} 2 123 # {a="c"} 0.5
a_bucket{le="2"} 2 # {a="c"} 0.5
a_bucket{le="+Inf"} 3 # {a="1234567890123456789012345678901234567890123456789012345678"} 4 123
# EOF
""")
hfm = HistogramMetricFamily("a", "help")
hfm.add_sample("a_bucket", {"le": "1"}, 0.0, None, Exemplar({"a": "b"}, 0.5))
hfm.add_sample("a_bucket", {"le": "2"}, 2.0, Timestamp(123, 0), Exemplar({"a": "c"}, 0.5)),
hfm.add_sample("a_bucket", {"le": "2"}, 2.0, None, Exemplar({"a": "c"}, 0.5)),
hfm.add_sample("a_bucket", {"le": "+Inf"}, 3.0, None, Exemplar({"a": "1234567890123456789012345678901234567890123456789012345678"}, 4, Timestamp(123, 0)))
self.assertEqual([hfm], list(families))

Expand All @@ -145,15 +145,15 @@ def test_simple_gaugehistogram(self):
def test_gaugehistogram_exemplars(self):
families = text_string_to_metric_families("""# TYPE a gaugehistogram
# HELP a help
a_bucket{le="1"} 0 # {a="b"} 0.5
a_bucket{le="1"} 0 123 # {a="b"} 0.5
a_bucket{le="2"} 2 123 # {a="c"} 0.5
a_bucket{le="+Inf"} 3 # {a="d"} 4 123
a_bucket{le="+Inf"} 3 123 # {a="d"} 4 123
# EOF
""")
hfm = GaugeHistogramMetricFamily("a", "help")
hfm.add_sample("a_bucket", {"le": "1"}, 0.0, None, Exemplar({"a": "b"}, 0.5))
hfm.add_sample("a_bucket", {"le": "1"}, 0.0, Timestamp(123, 0), Exemplar({"a": "b"}, 0.5))
hfm.add_sample("a_bucket", {"le": "2"}, 2.0, Timestamp(123, 0), Exemplar({"a": "c"}, 0.5)),
hfm.add_sample("a_bucket", {"le": "+Inf"}, 3.0, None, Exemplar({"a": "d"}, 4, Timestamp(123, 0)))
hfm.add_sample("a_bucket", {"le": "+Inf"}, 3.0, Timestamp(123, 0), Exemplar({"a": "d"}, 4, Timestamp(123, 0)))
self.assertEqual([hfm], list(families))

def test_simple_info(self):
Expand All @@ -164,6 +164,18 @@ def test_simple_info(self):
""")
self.assertEqual([InfoMetricFamily("a", "help", {'foo': 'bar'})], list(families))

def test_info_timestamps(self):
families = text_string_to_metric_families("""# TYPE a info
# HELP a help
a_info{a="1",foo="bar"} 1 1
a_info{a="2",foo="bar"} 1 0
# EOF
""")
imf = InfoMetricFamily("a", "help")
imf.add_sample("a_info", {"a": "1", "foo": "bar"}, 1, Timestamp(1, 0))
imf.add_sample("a_info", {"a": "2", "foo": "bar"}, 1, Timestamp(0, 0))
self.assertEqual([imf], list(families))

def test_simple_stateset(self):
families = text_string_to_metric_families("""# TYPE a stateset
# HELP a help
Expand Down Expand Up @@ -516,11 +528,18 @@ def test_invalid_input(self):
('# TYPE a gaugehistogram\na_bucket{le="+Inf"} NaN\n# EOF\n'),
('# TYPE a summary\na_sum NaN\n# EOF\n'),
('# TYPE a summary\na_count NaN\n# EOF\n'),
# Bad grouping.
# Bad grouping or ordering.
('# TYPE a histogram\na_sum{a="1"} 0\na_sum{a="2"} 0\na_count{a="1"} 0\n# EOF\n'),
('# TYPE a histogram\na_bucket{a="1",le="1"} 0\na_bucket{a="2",le="+Inf""} 0\na_bucket{a="1",le="+Inf"} 0\n# EOF\n'),
('# TYPE a gaugehistogram\na_gsum{a="1"} 0\na_gsum{a="2"} 0\na_gcount{a="1"} 0\n# EOF\n'),
('# TYPE a summary\nquantile{quantile="0"} 0\na_sum{a="1"} 0\nquantile{quantile="1"} 0\n# EOF\n'),
('# TYPE a gauge\na 0 -1\na 0 -2\n# EOF\n'),
('# TYPE a gauge\na 0 -1\na 0 -1.1\n# EOF\n'),
('# TYPE a gauge\na 0 1\na 0 -1\n# EOF\n'),
('# TYPE a gauge\na 0 1.1\na 0 1\n# EOF\n'),
('# TYPE a gauge\na 0 1\na 0 0\n# EOF\n'),
('# TYPE a gauge\na 0\na 0 0\n# EOF\n'),
('# TYPE a gauge\na 0 0\na 0\n# EOF\n'),
]:
with self.assertRaises(ValueError):
list(text_string_to_metric_families(case))
Expand Down

0 comments on commit a6d7e60

Please sign in to comment.