8000 Correct nh sample span structure and parsing by vesari · Pull Request #1082 · prometheus/client_python · GitHub
[go: up one dir, main page]

Skip to content

Correct nh sample span structure and parsing #1082

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

Merged
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
83 changes: 48 additions & 35 deletions prometheus_client/openmetrics/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -307,12 +307,11 @@ def _parse_nh_sample(text, suffixes):

def _parse_nh_struct(text):
pattern = r'(\w+):\s*([^,}]+)'

re_spans = re.compile(r'(positive_spans|negative_spans):\[(\d+:\d+,\d+:\d+)\]')
re_spans = re.compile(r'(positive_spans|negative_spans):\[(\d+:\d+(,\d+:\d+)*)\]')
re_deltas = re.compile(r'(positive_deltas|negative_deltas):\[(-?\d+(?:,-?\d+)*)\]')

items = dict(re.findall(pattern, text))
spans = dict(re_spans.findall(text))
span_matches = re_spans.findall(text)
deltas = dict(re_deltas.findall(text))

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

try:
pos_spans_text = spans['positive_spans']
elems = pos_spans_text.split(',')
arg1 = [int(x) for x in elems[0].split(':')]
arg2 = [int(x) for x in elems[1].split(':')]
pos_spans = (BucketSpan(arg1[0], arg1[1]), BucketSpan(arg2[0], arg2[1]))
except KeyError:
pos_spans = None

try:
neg_spans_text = spans['negative_spans']
elems = neg_spans_text.split(',')
arg1 = [int(x) for x in elems[0].split(':')]
arg2 = [int(x) for x in elems[1].split(':')]
neg_spans = (BucketSpan(arg1[0], arg1[1]), BucketSpan(arg2[0], arg2[1]))
except KeyError:
neg_spans = None

try:
pos_deltas_text = deltas['positive_deltas']
elems = pos_deltas_text.split(',')
pos_deltas = tuple([int(x) for x in elems])
except KeyError:
pos_deltas = None

try:
neg_deltas_text = deltas['negative_deltas']
elems = neg_deltas_text.split(',')
neg_deltas = tuple([int(x) for x in elems])
except KeyError:
neg_deltas = None

pos_spans = _compose_spans(span_matches, 'positive_spans')
neg_spans = _compose_spans(span_matches, 'negative_spans')
pos_deltas = _compose_deltas(deltas, 'positive_deltas')
neg_deltas = _compose_deltas(deltas, 'negative_deltas')

return NativeHistogram(
count_value=count_value,
sum_value=sum_value,
Expand All @@ -364,6 +336,47 @@ def _parse_nh_struct(text):
pos_deltas=pos_deltas,
neg_deltas=neg_deltas
)


def _compose_spans(span_matches, spans_name):
Copy link
Member

Choose a reason for hiding this comment

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

nit: might want to add a comment what this does, because the list comprehension takes a minute to understand :)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, that makes sense! I've also added comments to the other less complicated function, while I was at it :D

"""Takes a list of span matches (expected to be a list of tuples) and a string
(the expected span list name) and processes the list so that the values extracted
from the span matches can be used to compose a tuple of BucketSpan objects"""
spans = {}
for match in span_matches:
# Extract the key from the match (first element of the tuple).
key = match[0]
# Extract the value from the match (second element of the tuple).
# Split the value string by commas to get individual pairs,
# split each pair by ':' to get start and end, and convert them to integers.
value = [tuple(map(int, pair.split(':'))) for pair in match[1].split(',')]
# Store the processed value in the spans dictionary with the key.
spans[key] = value
if spans_name not in spans:
return None
out_spans = []
# Iterate over each start and end tuple in the list of tuples for the specified spans_name.
for start, end in spans[spans_name]:
# Compose a BucketSpan object with the start and end values
# and append it to the out_spans list.
out_spans.append(BucketSpan(start, end))
# Convert to tuple
out_spans_tuple = tuple(out_spans)
return out_spans_tuple


def _compose_deltas(deltas, deltas_name):
"""Takes a list of deltas matches (a dictionary) and a string (the expected delta list name),
and processes its elements to compose a tuple of integers representing the deltas"""
if deltas_name not in deltas:
return None
out_deltas = deltas.get(deltas_name)
if out_deltas is not None and out_deltas.strip():
elems = out_deltas.split(',')
# Convert each element in the list elems to an integer
# after stripping whitespace and create a tuple from these integers.
out_deltas_tuple = tuple(int(x.strip()) for x in elems)
return out_deltas_tuple


def _group_for_sample(sample, name, typ):
Expand Down
6 changes: 3 additions & 3 deletions prometheus_client/samples.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from typing import Dict, NamedTuple, Optional, Sequence, Tuple, Union
from typing import Dict, NamedTuple, Optional, Sequence, Union


class Timestamp:
Expand Down Expand Up @@ -47,8 +47,8 @@ class NativeHistogram(NamedTuple):
schema: int
zero_threshold: float
zero_count: float
pos_spans: Optional[Tuple[BucketSpan, BucketSpan]] = None
neg_spans: Optional[Tuple[BucketSpan, BucketSpan]] = None
pos_spans: Optional[Sequence[BucketSpan]] = None
neg_spans: Optional[Sequence[BucketSpan]] = None
pos_deltas: Optional[Sequence[int]] = None
neg_deltas: Optional[Sequence[int]] = None

Expand Down
12 changes: 12 additions & 0 deletions tests/openmetrics/test_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,18 @@ def test_native_histogram_utf8_stress(self):
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)))
self.assertEqual([hfm], families)

def test_native_histogram_three_pos_spans_no_neg_spans_or_deltas(self):
families = text_string_to_metric_families("""# TYPE nhsp histogram
# HELP nhsp Is a basic example of a native histogram with three spans
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]}
# EOF
""")
families = list(families)

hfm = HistogramMetricFamily("nhsp", "Is a basic example of a native histogram with three spans")
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))
self.assertEqual([hfm], families)

def test_native_histogram_with_labels(self):
families = text_string_to_metric_families("""# TYPE hist_w_labels histogram
# HELP hist_w_labels Is a basic example of a native histogram with labels
Expand Down
0