histogram: Identify native histograms even without observations

Native histograms without observations and with a zero threshold of
zero look the same as classic histograms in the protobuf exposition
format. According to
https://github.com/prometheus/client_golang/issues/1127 , the idea is
to add a no-op span to those histograms to mark them as native
histograms. This commit enables Prometheus to detect that no-op span
and adds a doc comment to the proto spec describing the behavior.

Signed-off-by: beorn7 <beorn@grafana.com>
This commit is contained in:
beorn7 2023-07-20 17:29:31 +02:00
parent 0e12f11d61
commit c58e20ad0e
4 changed files with 69 additions and 15 deletions

View File

@ -554,20 +554,17 @@ func formatOpenMetricsFloat(f float64) string {
return s + ".0" return s + ".0"
} }
// isNativeHistogram returns false iff the provided histograms has no sparse // isNativeHistogram returns false iff the provided histograms has no spans at
// buckets and a zero threshold of 0 and a zero count of 0. In principle, this // all (neither positive nor negative) and a zero threshold of 0 and a zero
// could still be meant to be a native histogram (with a zero threshold of 0 and // count of 0. In principle, this could still be meant to be a native histogram
// no observations yet), but for now, we'll treat this case as a conventional // with a zero threshold of 0 and no observations yet. In that case,
// histogram. // instrumentation libraries should add a "no-op" span (e.g. length zero, offset
// // zero) to signal that the histogram is meant to be parsed as a native
// TODO(beorn7): In the final format, there should be an unambiguous way of // histogram. Failing to do so will cause Prometheus to parse it as a classic
// deciding if a histogram should be ingested as a conventional one or a native // histogram as long as no observations have happened.
// one.
func isNativeHistogram(h *dto.Histogram) bool { func isNativeHistogram(h *dto.Histogram) bool {
return h.GetZeroThreshold() > 0 || return len(h.GetPositiveSpan()) > 0 ||
h.GetZeroCount() > 0 || len(h.GetNegativeSpan()) > 0 ||
len(h.GetNegativeDelta()) > 0 || h.GetZeroThreshold() > 0 ||
len(h.GetPositiveDelta()) > 0 || h.GetZeroCount() > 0
len(h.GetNegativeCount()) > 0 ||
len(h.GetPositiveCount()) > 0
} }

View File

@ -517,6 +517,19 @@ metric: <
sample_sum: 1.234 sample_sum: 1.234
> >
> >
`,
`name: "empty_histogram"
help: "A histogram without observations and with a zero threshold of zero but with a no-op span to identify it as a native histogram."
type: HISTOGRAM
metric: <
histogram: <
positive_span: <
offset: 0
length: 0
>
>
>
`, `,
} }
@ -965,6 +978,25 @@ func TestProtobufParse(t *testing.T) {
"__name__", "without_quantiles_sum", "__name__", "without_quantiles_sum",
), ),
}, },
{
m: "empty_histogram",
help: "A histogram without observations and with a zero threshold of zero but with a no-op span to identify it as a native histogram.",
},
{
m: "empty_histogram",
typ: MetricTypeHistogram,
},
{
m: "empty_histogram",
shs: &histogram.Histogram{
CounterResetHint: histogram.UnknownCounterReset,
PositiveSpans: []histogram.Span{},
NegativeSpans: []histogram.Span{},
},
lset: labels.FromStrings(
"__name__", "empty_histogram",
),
},
}, },
}, },
{ {
@ -1688,6 +1720,25 @@ func TestProtobufParse(t *testing.T) {
"__name__", "without_quantiles_sum", "__name__", "without_quantiles_sum",
), ),
}, },
{ // 78
m: "empty_histogram",
help: "A histogram without observations and with a zero threshold of zero but with a no-op span to identify it as a native histogram.",
},
{ // 79
m: "empty_histogram",
typ: MetricTypeHistogram,
},
{ // 80
m: "empty_histogram",
shs: &histogram.Histogram{
CounterResetHint: histogram.UnknownCounterReset,
PositiveSpans: []histogram.Span{},
NegativeSpans: []histogram.Span{},
},
lset: labels.FromStrings(
"__name__", "empty_histogram",
),
},
}, },
}, },
} }

View File

@ -414,6 +414,9 @@ type Histogram struct {
NegativeDelta []int64 `protobuf:"zigzag64,10,rep,packed,name=negative_delta,json=negativeDelta,proto3" json:"negative_delta,omitempty"` NegativeDelta []int64 `protobuf:"zigzag64,10,rep,packed,name=negative_delta,json=negativeDelta,proto3" json:"negative_delta,omitempty"`
NegativeCount []float64 `protobuf:"fixed64,11,rep,packed,name=negative_count,json=negativeCount,proto3" json:"negative_count,omitempty"` NegativeCount []float64 `protobuf:"fixed64,11,rep,packed,name=negative_count,json=negativeCount,proto3" json:"negative_count,omitempty"`
// Positive buckets for the native histogram. // Positive buckets for the native histogram.
// Use a no-op span (offset 0, length 0) for a native histogram without any
// observations yet and with a zero_threshold of 0. Otherwise, it would be
// indistinguishable from a classic histogram.
PositiveSpan []BucketSpan `protobuf:"bytes,12,rep,name=positive_span,json=positiveSpan,proto3" json:"positive_span"` PositiveSpan []BucketSpan `protobuf:"bytes,12,rep,name=positive_span,json=positiveSpan,proto3" json:"positive_span"`
// Use either "positive_delta" or "positive_count", the former for // Use either "positive_delta" or "positive_count", the former for
// regular histograms with integer counts, the latter for float // regular histograms with integer counts, the latter for float

View File

@ -97,6 +97,9 @@ message Histogram {
repeated double negative_count = 11; // Absolute count of each bucket. repeated double negative_count = 11; // Absolute count of each bucket.
// Positive buckets for the native histogram. // Positive buckets for the native histogram.
// Use a no-op span (offset 0, length 0) for a native histogram without any
// observations yet and with a zero_threshold of 0. Otherwise, it would be
// indistinguishable from a classic histogram.
repeated BucketSpan positive_span = 12 [(gogoproto.nullable) = false]; repeated BucketSpan positive_span = 12 [(gogoproto.nullable) = false];
// Use either "positive_delta" or "positive_count", the former for // Use either "positive_delta" or "positive_count", the former for
// regular histograms with integer counts, the latter for float // regular histograms with integer counts, the latter for float