Uh oh!
There was an error while loading. Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork 870
Add support for native histograms in OM parser#1040
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
csmarchbanks
merged 11 commits into
prometheus:master
from
vesari:native-histogram-supportSep 20, 2024
Uh oh!
There was an error while loading. Please reload this page.
Merged
Changes from all commits
Commits
Show all changes
11 commits
Select commit
Hold shift + click to select a range
977b0b2
Start on native histogram parser
vesari fd1b563
Fix regex for nh sample
vesari e32d2a8
Get nh sample appended
vesari cb013d8
Complete parsing for simple native histogram
vesari 4b1f527
Add parsing for native histograms with labels, fix linting
vesari eb6d9de
Mitigate type and style errors
vesari 86f165a
Add test for parsing coexisting native and classic hist with simple l…
vesari c69a500
Solve error in Python 3.9 tests
vesari c06db3f
Add test for native + classic histograms with more than a label set a…
vesari d394c71
Separate native histogram from value field, improve conditional/try b…
vesari 90cd08e
Clean up debug lines, add warnings, delete unnecessary lines
vesari File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Uh oh!
There was an error while loading. Please reload this page.
Jump to
Jump to file
Failed to load files.
Loading
Uh oh!
There was an error while loading. Please reload this page.
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -6,7 +6,7 @@ | ||
| import re | ||
| from ..metrics_core import Metric, METRIC_LABEL_NAME_RE | ||
| from ..samples import Exemplar, Sample, Timestamp | ||
| from ..samples import BucketSpan, Exemplar, NativeHistogram, Sample, Timestamp | ||
| from ..utils import floatToGoString | ||
| @@ -364,6 +364,99 @@ def _parse_remaining_text(text): | ||
| return val, ts, exemplar | ||
| def _parse_nh_sample(text, suffixes): | ||
| labels_start = text.find("{") | ||
| # check if it's a native histogram with labels | ||
| re_nh_without_labels = re.compile(r'^[^{} ]+ {[^{}]+}$') | ||
| re_nh_with_labels = re.compile(r'[^{} ]+{[^{}]+} {[^{}]+}$') | ||
| if re_nh_with_labels.match(text): | ||
| nh_value_start = text.rindex("{") | ||
| labels_end = nh_value_start - 2 | ||
| labelstext = text[labels_start + 1:labels_end] | ||
| labels = _parse_labels(labelstext) | ||
| name_end = labels_start | ||
| name = text[:name_end] | ||
| if name.endswith(suffixes): | ||
| raise ValueError("the sample name of a native histogram with labels should have no suffixes", name) | ||
| nh_value = text[nh_value_start:] | ||
| nat_hist_value = _parse_nh_struct(nh_value) | ||
| return Sample(name, labels, None, None, None, nat_hist_value) | ||
| # check if it's a native histogram | ||
| if re_nh_without_labels.match(text): | ||
| nh_value_start = labels_start | ||
| nh_value = text[nh_value_start:] | ||
| name_end = nh_value_start - 1 | ||
| name = text[:name_end] | ||
| if name.endswith(suffixes): | ||
| raise ValueError("the sample name of a native histogram should have no suffixes", name) | ||
| nat_hist_value = _parse_nh_struct(nh_value) | ||
| return Sample(name, None, None, None, None, nat_hist_value) | ||
| else: | ||
| # it's not a native histogram | ||
| return | ||
| def _parse_nh_struct(text): | ||
| pattern = r'(\w+):\s*([^,}]+)' | ||
| 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)) | ||
| deltas = dict(re_deltas.findall(text)) | ||
| count_value = int(items['count']) | ||
| sum_value = int(items['sum']) | ||
| schema = int(items['schema']) | ||
| 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 | ||
| return NativeHistogram( | ||
| count_value=count_value, | ||
| sum_value=sum_value, | ||
| schema=schema, | ||
| zero_threshold=zero_threshold, | ||
| zero_count=zero_count, | ||
| pos_spans=pos_spans, | ||
| neg_spans=neg_spans, | ||
| pos_deltas=pos_deltas, | ||
| neg_deltas=neg_deltas | ||
| ) | ||
| def _group_for_sample(sample, name, typ): | ||
| if typ == 'info': | ||
| # We can't distinguish between groups for info metrics. | ||
| @@ -406,6 +499,8 @@ def do_checks(): | ||
| for s in samples: | ||
| suffix = s.name[len(name):] | ||
| g = _group_for_sample(s, name, 'histogram') | ||
| if len(suffix) == 0: | ||
| continue | ||
| if g != group or s.timestamp != timestamp: | ||
| if group is not None: | ||
| do_checks() | ||
| @@ -486,6 +581,8 @@ def build_metric(name, documentation, typ, unit, samples): | ||
| metric.samples = samples | ||
| return metric | ||
| is_nh = False | ||
| typ = None | ||
| for line in fd: | ||
| if line[-1] == '\n': | ||
| line = line[:-1] | ||
| @@ -518,7 +615,7 @@ def build_metric(name, documentation, typ, unit, samples): | ||
| group_timestamp_samples = set() | ||
| samples = [] | ||
| allowed_names = [parts[2]] | ||
| if parts[1] == 'HELP': | ||
| if documentation is not None: | ||
| raise ValueError("More than one HELP for metric: " + line) | ||
| @@ -537,8 +634,18 @@ def build_metric(name, documentation, typ, unit, samples): | ||
| else: | ||
| raise ValueError("Invalid line: " + line) | ||
| else: | ||
| sample = _parse_sample(line) | ||
| if sample.name not in allowed_names: | ||
| if typ == 'histogram': | ||
| # set to true to account for native histograms naming exceptions/sanitizing differences | ||
| is_nh = True | ||
| sample = _parse_nh_sample(line, tuple(type_suffixes['histogram'])) | ||
csmarchbanks marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| # It's not a native histogram | ||
| if sample is None: | ||
| is_nh = False | ||
| sample = _parse_sample(line) | ||
| else: | ||
| is_nh = False | ||
| sample = _parse_sample(line) | ||
| if sample.name not in allowed_names and not is_nh: | ||
| if name is not None: | ||
| yield build_metric(name, documentation, typ, unit, samples) | ||
| # Start an unknown metric. | ||
| @@ -570,26 +677,29 @@ def build_metric(name, documentation, typ, unit, samples): | ||
| or _isUncanonicalNumber(sample.labels['quantile']))): | ||
| raise ValueError("Invalid quantile label: " + line) | ||
| 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 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) | ||
| if not is_nh: | ||
| 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 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) | ||
| else: | ||
| group_timestamp_samples = set() | ||
| series_id = (sample.name, tuple(sorted(sample.labels.items()))) | ||
| if sample.timestamp != group_timestamp or series_id not in group_timestamp_samples: | ||
| # Not a duplicate due to timestamp truncation. | ||
| samples.append(sample) | ||
| group_timestamp_samples.add(series_id) | ||
| group = g | ||
| group_timestamp = sample.timestamp | ||
| seen_groups.add(g) | ||
| else: | ||
| group_timestamp_samples = set() | ||
| series_id = (sample.name, tuple(sorted(sample.labels.items()))) | ||
| if sample.timestamp != group_timestamp or series_id not in group_timestamp_samples: | ||
| # Not a duplicate due to timestamp truncation. | ||
| samples.append(sample) | ||
| group_timestamp_samples.add(series_id) | ||
| group = g | ||
| group_timestamp = sample.timestamp | ||
| seen_groups.add(g) | ||
| if typ == 'stateset' and sample.value not in [0, 1]: | ||
| raise ValueError("Stateset samples can only have values zero and one: " + line) | ||
| @@ -606,7 +716,7 @@ def build_metric(name, documentation, typ, unit, samples): | ||
| (typ in ['histogram', 'gaugehistogram'] and sample.name.endswith('_bucket')) | ||
| or (typ in ['counter'] and sample.name.endswith('_total'))): | ||
| raise ValueError("Invalid line only histogram/gaugehistogram buckets and counters can have exemplars: " + line) | ||
| if name is not None: | ||
| yield build_metric(name, documentation, typ, unit, samples) | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,4 +1,4 @@ | ||
| from typing import Dict, NamedTuple, Optional, Union | ||
| from typing import Dict, NamedTuple, Optional, Sequence, Tuple, Union | ||
| class Timestamp: | ||
| @@ -34,6 +34,25 @@ def __lt__(self, other: "Timestamp") -> bool: | ||
| return self.nsec < other.nsec if self.sec == other.sec else self.sec < other.sec | ||
| # BucketSpan is experimental and subject to change at any time. | ||
| class BucketSpan(NamedTuple): | ||
csmarchbanks marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| offset: int | ||
| length: int | ||
| # NativeHistogram is experimental and subject to change at any time. | ||
| class NativeHistogram(NamedTuple): | ||
| count_value: float | ||
| sum_value: float | ||
| schema: int | ||
| zero_threshold: float | ||
| zero_count: float | ||
| pos_spans: Optional[Tuple[BucketSpan, BucketSpan]] = None | ||
| neg_spans: Optional[Tuple[BucketSpan, BucketSpan]] = None | ||
| pos_deltas: Optional[Sequence[int]] = None | ||
| neg_deltas: Optional[Sequence[int]] = None | ||
| # Timestamp and exemplar are optional. | ||
| # Value can be an int or a float. | ||
| # Timestamp can be a float containing a unixtime in seconds, | ||
| @@ -51,3 +70,4 @@ class Sample(NamedTuple): | ||
| value: float | ||
| timestamp: Optional[Union[float, Timestamp]] = None | ||
| exemplar: Optional[Exemplar] = None | ||
| native_histogram: Optional[NativeHistogram] = None | ||
Oops, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.