Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Float histograms #346

Merged
merged 1 commit into from
Dec 5, 2018
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
9 changes: 6 additions & 3 deletions prometheus_client/openmetrics/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

from ..metrics_core import Metric, METRIC_LABEL_NAME_RE
from ..samples import Exemplar, Sample, Timestamp
from ..utils import floatToGoString

try:
import StringIO
Expand Down Expand Up @@ -371,7 +372,7 @@ def build_metric(name, documentation, typ, unit, samples):
allowed_names = {
'counter': ['_total', '_created'],
'summary': ['_count', '_sum', '', '_created'],
'histogram': ['_count', '_sum', '_bucket', 'created'],
'histogram': ['_count', '_sum', '_bucket', '_created'],
'gaugehistogram': ['_gcount', '_gsum', '_bucket'],
'info': ['_info'],
}.get(typ, [''])
Expand Down Expand Up @@ -402,10 +403,12 @@ def build_metric(name, documentation, typ, unit, samples):
if typ == 'stateset' and name not in sample.labels:
raise ValueError("Stateset missing label: " + line)
if (typ in ['histogram', 'gaugehistogram'] and name + '_bucket' == sample.name
and float(sample.labels.get('le', -1)) < 0):
and (float(sample.labels.get('le', -1)) < 0
or sample.labels['le'] != floatToGoString(sample.labels['le']))):
raise ValueError("Invalid le label: " + line)
if (typ == 'summary' and name == sample.name
and not (0 <= float(sample.labels.get('quantile', -1)) <= 1)):
and (not (0 <= float(sample.labels.get('quantile', -1)) <= 1)
or sample.labels['quantile'] != floatToGoString(sample.labels['quantile']))):
raise ValueError("Invalid quantile label: " + line)

g = tuple(sorted(_group_for_sample(sample, name, typ).items()))
Expand Down
10 changes: 9 additions & 1 deletion prometheus_client/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,19 @@


def floatToGoString(d):
d = float(d)
if d == INF:
return '+Inf'
elif d == MINUS_INF:
return '-Inf'
elif math.isnan(d):
return 'NaN'
else:
return repr(float(d))
s = repr(d)
dot = s.find('.')
# Go switches to exponents sooner than Python.
# We only need to care about positive values for le/quantile.
if d > 0 and dot > 6:
mantissa = '{}.{}{}'.format(s[0], s[1:dot], s[dot+1:]).rstrip('0.')
return '{}e+0{}'.format(mantissa, dot-1)
return s
55 changes: 39 additions & 16 deletions tests/openmetrics/test_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,50 +98,50 @@ def test_summary_quantiles(self):
def test_simple_histogram(self):
families = text_string_to_metric_families("""# TYPE a histogram
# HELP a help
a_bucket{le="1"} 0
a_bucket{le="1.0"} 0
a_bucket{le="+Inf"} 3
a_count 3
a_sum 2
# EOF
""")
self.assertEqual([HistogramMetricFamily("a", "help", sum_value=2, buckets=[("1", 0.0), ("+Inf", 3.0)])], list(families))
self.assertEqual([HistogramMetricFamily("a", "help", sum_value=2, buckets=[("1.0", 0.0), ("+Inf", 3.0)])], list(families))

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 # {a="c"} 0.5
a_bucket{le="1.0"} 0 # {a="b"} 0.5
a_bucket{le="2.0"} 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, None, Exemplar({"a": "c"}, 0.5)),
hfm.add_sample("a_bucket", {"le": "1.0"}, 0.0, None, Exemplar({"a": "b"}, 0.5))
hfm.add_sample("a_bucket", {"le": "2.0"}, 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))

def test_simple_gaugehistogram(self):
families = text_string_to_metric_families("""# TYPE a gaugehistogram
# HELP a help
a_bucket{le="1"} 0
a_bucket{le="1.0"} 0
a_bucket{le="+Inf"} 3
a_gcount 3
a_gsum 2
# EOF
""")
self.assertEqual([GaugeHistogramMetricFamily("a", "help", gsum_value=2, buckets=[("1", 0.0), ("+Inf", 3.0)])], list(families))
self.assertEqual([GaugeHistogramMetricFamily("a", "help", gsum_value=2, buckets=[("1.0", 0.0), ("+Inf", 3.0)])], list(families))

def test_gaugehistogram_exemplars(self):
families = text_string_to_metric_families("""# TYPE a gaugehistogram
# HELP a help
a_bucket{le="1"} 0 123 # {a="b"} 0.5
a_bucket{le="2"} 2 123 # {a="c"} 0.5
a_bucket{le="1.0"} 0 123 # {a="b"} 0.5
a_bucket{le="2.0"} 2 123 # {a="c"} 0.5
a_bucket{le="+Inf"} 3 123 # {a="d"} 4 123
# EOF
""")
hfm = GaugeHistogramMetricFamily("a", "help")
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": "1.0"}, 0.0, Timestamp(123, 0), Exemplar({"a": "b"}, 0.5))
hfm.add_sample("a_bucket", {"le": "2.0"}, 2.0, Timestamp(123, 0), Exemplar({"a": "c"}, 0.5)),
hfm.add_sample("a_bucket", {"le": "+Inf"}, 3.0, Timestamp(123, 0), Exemplar({"a": "d"}, 4, Timestamp(123, 0)))
self.assertEqual([hfm], list(families))

Expand Down Expand Up @@ -383,11 +383,11 @@ def test_timestamps(self):
def test_roundtrip(self):
text = """# HELP go_gc_duration_seconds A summary of the GC invocation durations.
# TYPE go_gc_duration_seconds summary
go_gc_duration_seconds{quantile="0"} 0.013300656000000001
go_gc_duration_seconds{quantile="0.0"} 0.013300656000000001
go_gc_duration_seconds{quantile="0.25"} 0.013638736
go_gc_duration_seconds{quantile="0.5"} 0.013759906
go_gc_duration_seconds{quantile="0.75"} 0.013962066
go_gc_duration_seconds{quantile="1"} 0.021383540000000003
go_gc_duration_seconds{quantile="1.0"} 0.021383540000000003
go_gc_duration_seconds_sum 56.12904785
go_gc_duration_seconds_count 7476.0
# HELP go_goroutines Number of goroutines that currently exist.
Expand All @@ -405,20 +405,36 @@ def test_roundtrip(self):
process_cpu_seconds_total 29323.4
# HELP process_virtual_memory_bytes Virtual memory size in bytes.
# TYPE process_virtual_memory_bytes gauge
process_virtual_memory_bytes 2478268416.0
process_virtual_memory_bytes 2.478268416e+09
# HELP prometheus_build_info A metric with a constant '1' value labeled by version, revision, and branch from which Prometheus was built.
# TYPE prometheus_build_info gauge
prometheus_build_info{branch="HEAD",revision="ef176e5",version="0.16.0rc1"} 1.0
# HELP prometheus_local_storage_chunk_ops The total number of chunk operations by their type.
# TYPE prometheus_local_storage_chunk_ops counter
prometheus_local_storage_chunk_ops_total{type="clone"} 28.0
prometheus_local_storage_chunk_ops_total{type="create"} 997844.0
prometheus_local_storage_chunk_ops_total{type="drop"} 1345758.0
prometheus_local_storage_chunk_ops_total{type="drop"} 1.345758e+06
prometheus_local_storage_chunk_ops_total{type="load"} 1641.0
prometheus_local_storage_chunk_ops_total{type="persist"} 981408.0
prometheus_local_storage_chunk_ops_total{type="pin"} 32662.0
prometheus_local_storage_chunk_ops_total{type="transcode"} 980180.0
prometheus_local_storage_chunk_ops_total{type="unpin"} 32662.0
# HELP foo histogram Testing histogram buckets
# TYPE foo histogram
foo_bucket{le="0.0"} 0.0
foo_bucket{le="1e-05"} 0.0
foo_bucket{le="0.0001"} 0.0
foo_bucket{le="0.1"} 8.0
foo_bucket{le="1.0"} 10.0
foo_bucket{le="10.0"} 17.0
foo_bucket{le="100000.0"} 17.0
foo_bucket{le="1e+06"} 17.0
foo_bucket{le="1.55555555555552e+06"} 17.0
foo_bucket{le="1e+23"} 17.0
foo_bucket{le="+Inf"} 17.0
foo_count 17.0
foo_sum 324789.3
foo_created 1.520430000123e+09
# EOF
"""
families = list(text_string_to_metric_families(text))
Expand Down Expand Up @@ -522,6 +538,7 @@ def test_invalid_input(self):
('# TYPE a summary\na{quantile="foo"} 0\n# EOF\n'),
('# TYPE a summary\na{quantile="1.01"} 0\n# EOF\n'),
('# TYPE a summary\na{quantile="NaN"} 0\n# EOF\n'),
('# TYPE a summary\na{quantile="1"} 0\n# EOF\n'),
('# TYPE a histogram\na_bucket 0\n# EOF\n'),
('# TYPE a gaugehistogram\na_bucket 0\n# EOF\n'),
('# TYPE a stateset\na 0\n# EOF\n'),
Expand All @@ -538,6 +555,12 @@ def test_invalid_input(self):
('# TYPE a gaugehistogram\na_gsum 1\n# EOF\n'),
('# TYPE a histogram\na_count 1\na_bucket{le="+Inf"} 0\n# EOF\n'),
('# TYPE a histogram\na_bucket{le="+Inf"} 0\na_count 1\n# EOF\n'),
('# TYPE a histogram\na_bucket{le="1"} 0\na_bucket{le="+Inf"} 0\n# EOF\n'),
('# TYPE a histogram\na_bucket{le="9.999999999999999e+22"} 0\na_bucket{le="+Inf"} 0\n# EOF\n'),
('# TYPE a histogram\na_bucket{le="1.5555555555555201e+06"} 0\na_bucket{le="+Inf"} 0\n# EOF\n'),
('# TYPE a histogram\na_bucket{le="1e-04"} 0\na_bucket{le="+Inf"} 0\n# EOF\n'),
('# TYPE a histogram\na_bucket{le="1e+05"} 0\na_bucket{le="+Inf"} 0\n# EOF\n'),
('# TYPE a histogram\na_bucket{le="+INF"} 0\n# EOF\n'),
('# TYPE a histogram\na_bucket{le="2"} 0\na_bucket{le="1"} 0\na_bucket{le="+Inf"} 0\n# EOF\n'),
('# TYPE a histogram\na_bucket{le="1"} 1\na_bucket{le="2"} 1\na_bucket{le="+Inf"} 0\n# EOF\n'),
# Bad grouping or ordering.
Expand Down
4 changes: 2 additions & 2 deletions tests/test_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -305,15 +305,15 @@ def test_roundtrip(self):
process_cpu_seconds_total 29323.4
# HELP process_virtual_memory_bytes Virtual memory size in bytes.
# TYPE process_virtual_memory_bytes gauge
process_virtual_memory_bytes 2478268416.0
process_virtual_memory_bytes 2.478268416e+09
# HELP prometheus_build_info A metric with a constant '1' value labeled by version, revision, and branch from which Prometheus was built.
# TYPE prometheus_build_info gauge
prometheus_build_info{branch="HEAD",revision="ef176e5",version="0.16.0rc1"} 1.0
# HELP prometheus_local_storage_chunk_ops_total The total number of chunk operations by their type.
# TYPE prometheus_local_storage_chunk_ops_total counter
prometheus_local_storage_chunk_ops_total{type="clone"} 28.0
prometheus_local_storage_chunk_ops_total{type="create"} 997844.0
prometheus_local_storage_chunk_ops_total{type="drop"} 1345758.0
prometheus_local_storage_chunk_ops_total{type="drop"} 1.345758e+06
prometheus_local_storage_chunk_ops_total{type="load"} 1641.0
prometheus_local_storage_chunk_ops_total{type="persist"} 981408.0
prometheus_local_storage_chunk_ops_total{type="pin"} 32662.0
Expand Down