Skip to content

OM text exposition for NH #1087

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

Open
wants to merge 13 commits into
base: master
Choose a base branch
from
Open
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
107 changes: 84 additions & 23 deletions prometheus_client/openmetrics/exposition.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,29 @@ def _is_valid_exemplar_metric(metric, sample):
return False


def _compose_exemplar_string(metric, sample, exemplar):
"""Constructs an exemplar string."""
if not _is_valid_exemplar_metric(metric, sample):
raise ValueError(f"Metric {metric.name} has exemplars, but is not a histogram bucket or counter")
labels = '{{{0}}}'.format(','.join(
['{}="{}"'.format(
k, v.replace('\\', r'\\').replace('\n', r'\n').replace('"', r'\"'))
for k, v in sorted(exemplar.labels.items())]))
if exemplar.timestamp is not None:
exemplarstr = ' # {} {} {}'.format(
labels,
floatToGoString(exemplar.value),
exemplar.timestamp,
)
else:
exemplarstr = ' # {} {}'.format(
labels,
floatToGoString(exemplar.value),
)

return exemplarstr


def generate_latest(registry):
'''Returns the metrics from the registry in latest text format as a string.'''
output = []
Expand All @@ -38,7 +61,7 @@ def generate_latest(registry):
labelstr += ', '
else:
labelstr = ''

if s.labels:
items = sorted(s.labels.items())
labelstr += ','.join(
Expand All @@ -47,44 +70,82 @@ def generate_latest(registry):
for k, v in items])
if labelstr:
labelstr = "{" + labelstr + "}"

if s.exemplar:
if not _is_valid_exemplar_metric(metric, s):
raise ValueError(f"Metric {metric.name} has exemplars, but is not a histogram bucket or counter")
labels = '{{{0}}}'.format(','.join(
['{}="{}"'.format(
k, v.replace('\\', r'\\').replace('\n', r'\n').replace('"', r'\"'))
for k, v in sorted(s.exemplar.labels.items())]))
if s.exemplar.timestamp is not None:
exemplarstr = ' # {} {} {}'.format(
labels,
floatToGoString(s.exemplar.value),
s.exemplar.timestamp,
)
else:
exemplarstr = ' # {} {}'.format(
labels,
floatToGoString(s.exemplar.value),
)
exemplarstr = _compose_exemplar_string(metric, s, s.exemplar)
else:
exemplarstr = ''

timestamp = ''
if s.timestamp is not None:
timestamp = f' {s.timestamp}'

native_histogram = ''
negative_spans = ''
negative_deltas = ''
positive_spans = ''
positive_deltas = ''

if s.native_histogram:
# Initialize basic nh template
nh_sample_template = '{{count:{},sum:{},schema:{},zero_threshold:{},zero_count:{}'

args = [
s.native_histogram.count_value,
s.native_histogram.sum_value,
s.native_histogram.schema,
s.native_histogram.zero_threshold,
s.native_histogram.zero_count,
]

# If there are neg spans, append them and the neg deltas to the template and args
if s.native_histogram.neg_spans:
negative_spans = ','.join([f'{ns[0]}:{ns[1]}' for ns in s.native_histogram.neg_spans])
negative_deltas = ','.join(str(nd) for nd in s.native_histogram.neg_deltas)
nh_sample_template += ',n_spans:[{}]'
args.append(negative_spans)
nh_sample_template += ',n_deltas:[{}]'
args.append(negative_deltas)

# If there are pos spans, append them and the pos spans to the template and args
if s.native_histogram.pos_spans:
positive_spans = ','.join([f'{ps[0]}:{ps[1]}' for ps in s.native_histogram.pos_spans])
positive_deltas = ','.join(f'{pd}' for pd in s.native_histogram.pos_deltas)
nh_sample_template += ',p_spans:[{}]'
args.append(positive_spans)
nh_sample_template += ',p_deltas:[{}]'
args.append(positive_deltas)

# Add closing brace
nh_sample_template += '}}'

# Format the template with the args
native_histogram = nh_sample_template.format(*args)

if s.native_histogram.nh_exemplars:
for nh_ex in s.native_histogram.nh_exemplars:
nh_exemplarstr = _compose_exemplar_string(metric, s, nh_ex)
exemplarstr += nh_exemplarstr

value = ''
if s.native_histogram:
value = native_histogram
elif s.value is not None:
value = floatToGoString(s.value)
if _is_valid_legacy_metric_name(s.name):
output.append('{}{} {}{}{}\n'.format(
s.name,
labelstr,
floatToGoString(s.value),
value,
timestamp,
exemplarstr,
exemplarstr
))
else:
output.append('{} {}{}{}\n'.format(
labelstr,
floatToGoString(s.value),
value,
timestamp,
exemplarstr,
exemplarstr
))
except Exception as exception:
exception.args = (exception.args or ('',)) + (metric,)
Expand Down
23 changes: 12 additions & 11 deletions prometheus_client/samples.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,17 @@ class BucketSpan(NamedTuple):
length: int


# Timestamp and exemplar are optional.
# Value can be an int or a float.
# Timestamp can be a float containing a unixtime in seconds,
# a Timestamp object, or None.
# Exemplar can be an Exemplar object, or None.
class Exemplar(NamedTuple):
labels: Dict[str, str]
value: float
timestamp: Optional[Union[float, Timestamp]] = None


# NativeHistogram is experimental and subject to change at any time.
class NativeHistogram(NamedTuple):
count_value: float
Expand All @@ -51,17 +62,7 @@ class NativeHistogram(NamedTuple):
neg_spans: Optional[Sequence[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,
# a Timestamp object, or None.
# Exemplar can be an Exemplar object, or None.
class Exemplar(NamedTuple):
labels: Dict[str, str]
value: float
timestamp: Optional[Union[float, Timestamp]] = None
nh_exemplars: Optional[Sequence[Exemplar]] = None


class Sample(NamedTuple):
Expand Down
Loading