8000 Correct nh sample span structure and parsing (#1082) · prometheus/client_python@ecf344b · GitHub
[go: up one dir, main page]

Skip to content

Commit ecf344b

Browse files
authored
Correct nh sample span structure and parsing (#1082)
* Add test for nh with more spans * Allow for span arrays to be of whatever length and for delta lists to be None * Allow for spans to be None, condense spans and deltas composition Signed-off-by: Arianna Vespri <arianna.vespri@yahoo.it>
1 parent 5926a7c commit ecf344b

File tree

3 files changed

+63
-38
lines changed

3 files changed

+63
-38
lines changed

prometheus_client/openmetrics/parser.py

Lines changed: 48 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -307,12 +307,11 @@ def _parse_nh_sample(text, suffixes):
307307

308308
def _parse_nh_struct(text):
309309
pattern = r'(\w+):\s*([^,}]+)'
310-
311-
re_spans = re.compile(r'(positive_spans|negative_spans):\[(\d+:\d+,\d+:\d+)\]')
310+
re_spans = re.compile(r'(positive_spans|negative_spans):\[(\d+:\d+(,\d+:\d+)*)\]')
312311
re_deltas = re.compile(r'(positive_deltas|negative_deltas):\[(-?\d+(?:,-?\d+)*)\]')
313312

314313
items = dict(re.findall(pattern, text))
315-
spans = dict(re_spans.findall(text))
314+
span_matches = re_spans.findall(text)
316315
deltas = dict(re_deltas.findall(text))
317316

318317
count_value = int(items['count'])
@@ -321,38 +320,11 @@ def _parse_nh_struct(text):
321320
zero_threshold = float(items['zero_threshold'])
322321
zero_count = int(items['zero_count'])
323322

324-
try:
325-
pos_spans_text = spans['positive_spans']
326-
elems = pos_spans_text.split(',')
327-
arg1 = [int(x) for x in elems[0].split(':')]
328-
arg2 = [int(x) for x in elems[1].split(':')]
329-
pos_spans = (BucketSpan(arg1[0], arg1[1]), BucketSpan(arg2[0], arg2[1]))
330-
except KeyError:
331-
pos_spans = None
332-
333-
try:
334-
neg_spans_text = spans['negative_spans']
335-
elems = neg_spans_text.split(',')
336-
arg1 = [int(x) for x in elems[0].split(':')]
337-
arg2 = [int(x) for x in elems[1].split(':')]
338-
neg_spans = (BucketSpan(arg1[0], arg1[1]), BucketSpan(arg2[0], arg2[1]))
339-
except KeyError:
340-
neg_spans = None
341-
342-
try:
343-
pos_deltas_text = deltas['positive_deltas']
344-
elems = pos_deltas_text.split(',')
345-
pos_deltas = tuple([int(x) for x in elems])
346-
except KeyError:
347-
pos_deltas = None
348-
349-
try:
350-
neg_deltas_text = deltas['negative_deltas']
351-
elems = neg_deltas_text.split(',')
352-
neg_deltas = tuple([int(x) for x in elems])
353-
except KeyError:
354-
neg_deltas = None
355-
323+
pos_spans = _compose_spans(span_matches, 'positive_spans')
324+
neg_spans = _compose_spans(span_matches, 'negative_spans')
325+
pos_deltas = _compose_deltas(deltas, 'positive_deltas')
326+
neg_deltas = _compose_deltas(deltas, 'negative_deltas')
327+
356328
return NativeHistogram(
357329
count_value=count_value,
358330
sum_value=sum_value,
@@ -364,6 +336,47 @@ def _parse_nh_struct(text):
364336
pos_deltas=pos_deltas,
365337
neg_deltas=neg_deltas
366338
)
339+
340+
341+
def _compose_spans(span_matches, spans_name):
342+
"""Takes a list of span matches (expected to be a list of tuples) and a string
343+
(the expected span list name) and processes the list so that the values extracted
344+
from the span matches can be used to compose a tuple of BucketSpan objects"""
345+
spans = {}
346+
for match in span_matches:
347+
# Extract the key from the match (first element of the tuple).
348+
key = match[0]
349+
# Extract the value from the match (second element of the tuple).
350+
# Split the value string by commas to get individual pairs,
351+
# split each pair by ':' to get start and end, and convert them to integers.
352+
value = [tuple(map(int, pair.split(':'))) for pair in match[1].split(',')]
353+
# Store the processed value in the spans dictionary with the key.
354+
spans[key] = value
355+
if spans_name not in spans:
356+
return None
357+
out_spans = []
358+
# Iterate over each start and end tuple in the list of tuples for the specified spans_name.
359+
for start, end in spans[spans_name]:
360+
# Compose a BucketSpan object with the start and end values
361+
# and append it to the out_spans list.
362+
out_spans.append(BucketSpan(start, end))
363+
# Convert to tuple
364+
out_spans_tuple = tuple(out_spans)
365+
return out_spans_tuple
366+
367+
368+
def _compose_deltas(deltas, deltas_name):
369+
"""Takes a list of deltas matches (a dictionary) and a string (the expected delta list name),
370+
and processes its elements to compose a tuple of integers representing the deltas"""
371+
if deltas_name not in deltas:
372+
return None
373+
out_deltas = deltas.get(deltas_name)
374+
if out_deltas is not None and out_deltas.strip():
375+
elems = out_deltas.split(',')
376+
# Convert each element in the list elems to an integer
377+
# after stripping whitespace and create a tuple from these integers.
378+
out_deltas_tuple = tuple(int(x.strip()) for x in elems)
379+
return out_deltas_tuple
367380

368381

369382
def _group_for_sample(sample, name, typ):

prometheus_client/samples.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from typing import Dict, NamedTuple, Optional, Sequence, Tuple, Union
1+
from typing import Dict, NamedTuple, Optional, Sequence, Union
22

33

44
class Timestamp:
@@ -47,8 +47,8 @@ class NativeHistogram(NamedTuple):
4747
schema: int
4848
zero_threshold: float
4949
zero_count: float
50-
pos_spans: Optional[Tuple[BucketSpan, BucketSpan]] = None
51-
neg_spans: Optional[Tuple[BucketSpan, BucketSpan]] = None
50+
pos_spans: Optional[Sequence[BucketSpan]] = None
51+
neg_spans: Optional[Sequence[BucketSpan]] = None
5252
pos_deltas: Optional[Sequence[int]] = None
5353
neg_deltas: Optional[Sequence[int]] = None
5454

tests/openmetrics/test_parser.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -242,6 +242,18 @@ def test_native_histogram_utf8_stress(self):
242242
hfm.add_sample("native{histogram", {'xx{} # {}': ' EOF # {}}}'}, None, None, None, NativeHistogram(24, 100, 0, 0.001, 4, (BucketSpan(0, 2), BucketSpan(1, 2)), (BucketSpan(0, 2), BucketSpan(1, 2)), (2, 1, -3, 3), (2, 1, -2, 3)))
243243
self.assertEqual([hfm], families)
244244

245+
def test_native_histogram_three_pos_spans_no_neg_spans_or_deltas(self):
246+
families = text_string_to_metric_families("""# TYPE nhsp histogram
247+
# HELP nhsp Is a basic example of a native histogram with three spans
248+
nhsp {count:4,sum:6,schema:3,zero_threshold:2.938735877055719e-39,zero_count:1,positive_spans:[0:1,7:1,4:1],positive_deltas:[1,0,0]}
249+
# EOF
250+
""")
251+
families = list(families)
252+
253+
hfm = HistogramMetricFamily("nhsp", "Is a basic example of a native histogram with three spans")
254+
hfm.add_sample("nhsp", None, None, None, None, NativeHistogram(4, 6, 3, 2.938735877055719e-39, 1, (BucketSpan(0, 1), BucketSpan(7, 1), BucketSpan(4, 1)), None, (1, 0, 0), None))
255+
self.assertEqual([hfm], families)
256+
245257
def test_native_histogram_with_labels(self):
246258
families = text_string_to_metric_families("""# TYPE hist_w_labels histogram
247259
# HELP hist_w_labels Is a basic example of a native histogram with labels

0 commit comments

Comments
 (0)
0