// 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) }