prometheus/schema/labels.go
Bartlomiej Plotka 8e6b008608
feature: type-and-unit-labels (PROM-39 implementation) (#16228)
* feature: type-and-unit-labels (extended MetricIdentity)

Experimental implementation of https://github.com/prometheus/proposals/pull/39

Previous (unmerged) experiments:
* https://github.com/prometheus/prometheus/compare/main...dashpole:prometheus:type_and_unit_labels
* https://github.com/prometheus/prometheus/pull/16025

Signed-off-by: bwplotka <bwplotka@gmail.com>

feature: type-and-unit-labels (extended MetricIdentity)

Experimental implementation of https://github.com/prometheus/proposals/pull/39

Previous (unmerged) experiments:
* https://github.com/prometheus/prometheus/compare/main...dashpole:prometheus:type_and_unit_labels
* https://github.com/prometheus/prometheus/pull/16025

Signed-off-by: bwplotka <bwplotka@gmail.com>

* Fix compilation errors

Signed-off-by: Arthur Silva Sens <arthursens2005@gmail.com>

Lint

Signed-off-by: Arthur Silva Sens <arthursens2005@gmail.com>

Revert change made to protobuf 'Accept' header

Signed-off-by: Arthur Silva Sens <arthursens2005@gmail.com>

Fix compilation errors for 'dedupelabels' tag

Signed-off-by: Arthur Silva Sens <arthursens2005@gmail.com>

* Rectored into schema.Metadata

Signed-off-by: bwplotka <bwplotka@gmail.com>

* texparse: Added tests for PromParse

Signed-off-by: bwplotka <bwplotka@gmail.com>

* add OM tests.

Signed-off-by: bwplotka <bwplotka@gmail.com>

* add proto tests

Signed-off-by: bwplotka <bwplotka@gmail.com>

* Addressed comments.

Signed-off-by: bwplotka <bwplotka@gmail.com>

* add schema label tests.

Signed-off-by: bwplotka <bwplotka@gmail.com>

* addressed comments.

Signed-off-by: bwplotka <bwplotka@gmail.com>

* fix tests.

Signed-off-by: bwplotka <bwplotka@gmail.com>

* add promql tests.

Signed-off-by: bwplotka <bwplotka@gmail.com>

* lint

Signed-off-by: bwplotka <bwplotka@gmail.com>

* Addressed comments.

Signed-off-by: bwplotka <bwplotka@gmail.com>

---------

Signed-off-by: bwplotka <bwplotka@gmail.com>
Signed-off-by: Arthur Silva Sens <arthursens2005@gmail.com>
Co-authored-by: Arthur Silva Sens <arthursens2005@gmail.com>
2025-05-17 09:37:25 +00:00

158 lines
6.2 KiB
Go

// Copyright 2025 The Prometheus Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package schema
import (
"github.com/prometheus/common/model"
"github.com/prometheus/prometheus/model/labels"
)
const (
// Special label names and selectors for schema.Metadata fields.
// They are currently private to ensure __name__, __type__ and __unit__ are used
// together and remain extensible in Prometheus. See NewMetadataFromLabels and Metadata
// methods for the interactions with the labels package structs.
metricName = "__name__"
metricType = "__type__"
metricUnit = "__unit__"
)
// IsMetadataLabel returns true if the given label name is a special
// schema Metadata label.
func IsMetadataLabel(name string) bool {
return name == metricName || name == metricType || name == metricUnit
}
// Metadata represents the core metric schema/metadata elements that:
// * are describing and identifying the metric schema/shape (e.g. name, type and unit).
// * are contributing to the general metric/series identity.
// * with the type-and-unit feature, are stored as Prometheus labels.
//
// Historically, similar information was encoded in the labels.MetricName (suffixes)
// and in the separate metadata.Metadata structures. However, with the
// type-and-unit-label feature (PROM-39), this information can be now stored directly
// in the special schema metadata labels, which offers better reliability (e.g. atomicity),
// compatibility and, in many cases, efficiency.
//
// NOTE: Metadata in the current form is generally similar (yet different) to:
// - The MetricFamily definition in OpenMetrics (https://prometheus.io/docs/specs/om/open_metrics_spec/#metricfamily).
// However, there is a small and important distinction around the metric name semantics
// for the "classic" representation of complex metrics like histograms. The
// Metadata.Name follows the __name__ semantics. See Name for details.
// - Original metadata.Metadata entries. However, not all fields in that metadata
// are "identifiable", notably the help field, plus metadata does not contain Name.
type Metadata struct {
// Name represents the final metric name for a Prometheus series.
// NOTE(bwplotka): Prometheus scrape formats (e.g. OpenMetrics) define
// the "metric family name". The Metadata.Name (so __name__ label) is not
// always the same as the MetricFamily.Name e.g.:
// * OpenMetrics metric family name on scrape: "acme_http_router_request_seconds"
// * Resulting Prometheus metric name: "acme_http_router_request_seconds_sum"
//
// Empty string means nameless metric (e.g. result of the PromQL function).
Name string
// Type represents the metric type. Empty value ("") is equivalent to
// model.UnknownMetricType.
Type model.MetricType
// Unit represents the metric unit. Empty string means an unitless metric (e.g.
// result of the PromQL function).
//
// NOTE: Currently unit value is not strictly defined other than OpenMetrics
// recommendations: https://prometheus.io/docs/specs/om/open_metrics_spec/#units-and-base-units
// TODO(bwplotka): Consider a stricter validation and rules e.g. lowercase only or UCUM standard.
// Read more in https://github.com/prometheus/proposals/blob/main/proposals/2024-09-25_metadata-labels.md#more-strict-unit-and-type-value-definition
Unit string
}
// NewMetadataFromLabels returns the schema metadata from the labels.
func NewMetadataFromLabels(ls labels.Labels) Metadata {
typ := model.MetricTypeUnknown
if got := ls.Get(metricType); got != "" {
typ = model.MetricType(got)
}
return Metadata{
Name: ls.Get(metricName),
Type: typ,
Unit: ls.Get(metricUnit),
}
}
// IsTypeEmpty returns true if the metric type is empty (not set).
func (m Metadata) IsTypeEmpty() bool {
return m.Type == "" || m.Type == model.MetricTypeUnknown
}
// IsEmptyFor returns true if the Metadata field, represented by the given labelName
// is empty (not set). If the labelName in not representing any Metadata field,
// IsEmptyFor returns true.
func (m Metadata) IsEmptyFor(labelName string) bool {
switch labelName {
case metricName:
return m.Name == ""
case metricType:
return m.IsTypeEmpty()
case metricUnit:
return m.Unit == ""
default:
return true
}
}
// AddToLabels adds metric schema metadata as labels into the labels.ScratchBuilder.
// Empty Metadata fields will be ignored (not added).
func (m Metadata) AddToLabels(b *labels.ScratchBuilder) {
if m.Name != "" {
b.Add(metricName, m.Name)
}
if !m.IsTypeEmpty() {
b.Add(metricType, string(m.Type))
}
if m.Unit != "" {
b.Add(metricUnit, m.Unit)
}
}
// SetToLabels injects metric schema metadata as labels into the labels.Builder.
// It follows the labels.Builder.Set semantics, so empty Metadata fields will
// remove the corresponding existing labels if they were previously set.
func (m Metadata) SetToLabels(b *labels.Builder) {
b.Set(metricName, m.Name)
if m.Type == model.MetricTypeUnknown {
// Unknown equals empty semantically, so remove the label on unknown too as per
// method signature comment.
b.Set(metricType, "")
} else {
b.Set(metricType, string(m.Type))
}
b.Set(metricUnit, m.Unit)
}
// IgnoreOverriddenMetadataLabelsScratchBuilder is a wrapper over labels scratch builder
// that ignores label additions that would collide with non-empty Overwrite Metadata fields.
type IgnoreOverriddenMetadataLabelsScratchBuilder struct {
*labels.ScratchBuilder
Overwrite Metadata
}
// Add a name/value pair, unless it would collide with the non-empty Overwrite Metadata
// field. Note if you Add the same name twice you will get a duplicate label, which is invalid.
func (b IgnoreOverriddenMetadataLabelsScratchBuilder) Add(name, value string) {
if !b.Overwrite.IsEmptyFor(name) {
return
}
b.ScratchBuilder.Add(name, value)
}