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 10 commits into
base: master
Choose a base branch
from
115 changes: 92 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,90 @@ 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 = ''
positive_spans = ''
positive_deltas = ''
negative_spans = ''
negative_deltas = ''
pos = False
neg = False

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 pos spans, append them 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 += ',positive_spans:[{}]'
args.append(positive_spans)

# If there are neg spans exist, append them 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 += ',negative_spans:[{}]'
args.append(negative_spans)

# Append pos deltas if pos spans were added
if s.native_histogram.pos_spans:
nh_sample_template += ',positive_deltas:[{}]'
args.append(positive_deltas)

# Append neg deltas if neg spans were added
if s.native_histogram.neg_spans:
nh_sample_template += ',negative_deltas:[{}]'
args.append(negative_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