Add __temporality__ label to schema.Metadata
Signed-off-by: Carrie Edwards <edwrdscarrie@gmail.com>
This commit is contained in:
parent
aee0c472c9
commit
f0550dfd66
|
|
@ -21,28 +21,29 @@ import (
|
|||
|
||||
const (
|
||||
// Special label names and selectors for schema.Metadata fields.
|
||||
// They are currently private to ensure __name__, __type__ and __unit__ are used
|
||||
// They are currently private to ensure __name__, __type__, __unit__ and __temporality__ 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__"
|
||||
metricTemporality = "__temporality__"
|
||||
)
|
||||
|
||||
// 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
|
||||
return name == metricName || name == metricType || name == metricUnit || name == metricTemporality
|
||||
}
|
||||
|
||||
// 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.
|
||||
// * with the type-and-unit and delta temporality features, 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
|
||||
// type-and-unit-label feature (PROM-39) and the delta temporality feature, 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.
|
||||
//
|
||||
|
|
@ -74,6 +75,9 @@ type Metadata struct {
|
|||
// 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
|
||||
// Temporality represents the metric temporality. Empty string means temporality is not set.
|
||||
// Valid values are "cumulative" and "delta".
|
||||
Temporality string
|
||||
}
|
||||
|
||||
// NewMetadataFromLabels returns the schema metadata from the labels.
|
||||
|
|
@ -86,6 +90,7 @@ func NewMetadataFromLabels(ls labels.Labels) Metadata {
|
|||
Name: ls.Get(metricName),
|
||||
Type: typ,
|
||||
Unit: ls.Get(metricUnit),
|
||||
Temporality: ls.Get(metricTemporality),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -105,6 +110,8 @@ func (m Metadata) IsEmptyFor(labelName string) bool {
|
|||
return m.IsTypeEmpty()
|
||||
case metricUnit:
|
||||
return m.Unit == ""
|
||||
case metricTemporality:
|
||||
return m.Temporality == ""
|
||||
default:
|
||||
return true
|
||||
}
|
||||
|
|
@ -122,6 +129,9 @@ func (m Metadata) AddToLabels(b *labels.ScratchBuilder) {
|
|||
if m.Unit != "" {
|
||||
b.Add(metricUnit, m.Unit)
|
||||
}
|
||||
if m.Temporality != "" {
|
||||
b.Add(metricTemporality, m.Temporality)
|
||||
}
|
||||
}
|
||||
|
||||
// SetToLabels injects metric schema metadata as labels into the labels.Builder.
|
||||
|
|
@ -137,6 +147,7 @@ func (m Metadata) SetToLabels(b *labels.Builder) {
|
|||
b.Set(metricType, string(m.Type))
|
||||
}
|
||||
b.Set(metricUnit, m.Unit)
|
||||
b.Set(metricTemporality, m.Temporality)
|
||||
}
|
||||
|
||||
// IgnoreOverriddenMetadataLabelsScratchBuilder is a wrapper over labels scratch builder
|
||||
|
|
|
|||
|
|
@ -29,16 +29,18 @@ func TestMetadata(t *testing.T) {
|
|||
Name: "metric_total",
|
||||
Type: model.MetricTypeCounter,
|
||||
Unit: "seconds",
|
||||
Temporality: "delta",
|
||||
}
|
||||
|
||||
for _, tcase := range []struct {
|
||||
emptyName, emptyType, emptyUnit bool
|
||||
emptyName, emptyType, emptyUnit, emptyTemporality bool
|
||||
}{
|
||||
{},
|
||||
{emptyName: true},
|
||||
{emptyType: true},
|
||||
{emptyUnit: true},
|
||||
{emptyName: true, emptyType: true, emptyUnit: true},
|
||||
{emptyTemporality: true},
|
||||
{emptyName: true, emptyType: true, emptyUnit: true, emptyTemporality: true},
|
||||
} {
|
||||
var (
|
||||
expectedMeta Metadata
|
||||
|
|
@ -63,6 +65,10 @@ func TestMetadata(t *testing.T) {
|
|||
lb.Add(metricUnit, testMeta.Unit)
|
||||
expectedMeta.Unit = testMeta.Unit
|
||||
}
|
||||
if !tcase.emptyTemporality {
|
||||
lb.Add(metricTemporality, testMeta.Temporality)
|
||||
expectedMeta.Temporality = testMeta.Temporality
|
||||
}
|
||||
lb.Sort()
|
||||
expectedLabels = lb.Labels()
|
||||
}
|
||||
|
|
@ -79,6 +85,7 @@ func TestMetadata(t *testing.T) {
|
|||
require.Equal(t, tcase.emptyType, expectedMeta.IsEmptyFor(metricType))
|
||||
require.Equal(t, tcase.emptyType, expectedMeta.IsTypeEmpty())
|
||||
require.Equal(t, tcase.emptyUnit, expectedMeta.IsEmptyFor(metricUnit))
|
||||
require.Equal(t, tcase.emptyTemporality, expectedMeta.IsEmptyFor(metricTemporality))
|
||||
}
|
||||
{
|
||||
// From Metadata to labels for various builders.
|
||||
|
|
@ -100,7 +107,7 @@ func TestIgnoreOverriddenMetadataLabelsScratchBuilder(t *testing.T) {
|
|||
// PROM-39 specifies that metadata labels should be sourced primarily from the metadata structures.
|
||||
// However, the original labels should be preserved IF the metadata structure does not set or support certain information.
|
||||
// Test those cases with common label interactions.
|
||||
incomingLabels := labels.FromStrings(metricName, "different_name", metricType, string(model.MetricTypeSummary), metricUnit, "MB", "foo", "bar")
|
||||
incomingLabels := labels.FromStrings(metricName, "different_name", metricType, string(model.MetricTypeSummary), metricUnit, "MB", metricTemporality, "cumulative", "foo", "bar")
|
||||
for _, tcase := range []struct {
|
||||
highPrioMeta Metadata
|
||||
expectedLabels labels.Labels
|
||||
|
|
@ -113,22 +120,23 @@ func TestIgnoreOverriddenMetadataLabelsScratchBuilder(t *testing.T) {
|
|||
Name: "metric_total",
|
||||
Type: model.MetricTypeCounter,
|
||||
Unit: "seconds",
|
||||
Temporality: "delta",
|
||||
},
|
||||
expectedLabels: labels.FromStrings(metricName, "metric_total", metricType, string(model.MetricTypeCounter), metricUnit, "seconds", "foo", "bar"),
|
||||
expectedLabels: labels.FromStrings(metricName, "metric_total", metricType, string(model.MetricTypeCounter), metricUnit, "seconds", metricTemporality, "delta", "foo", "bar"),
|
||||
},
|
||||
{
|
||||
highPrioMeta: Metadata{
|
||||
Name: "metric_total",
|
||||
Type: model.MetricTypeCounter,
|
||||
},
|
||||
expectedLabels: labels.FromStrings(metricName, "metric_total", metricType, string(model.MetricTypeCounter), metricUnit, "MB", "foo", "bar"),
|
||||
expectedLabels: labels.FromStrings(metricName, "metric_total", metricType, string(model.MetricTypeCounter), metricUnit, "MB", metricTemporality, "cumulative", "foo", "bar"),
|
||||
},
|
||||
{
|
||||
highPrioMeta: Metadata{
|
||||
Type: model.MetricTypeCounter,
|
||||
Unit: "seconds",
|
||||
},
|
||||
expectedLabels: labels.FromStrings(metricName, "different_name", metricType, string(model.MetricTypeCounter), metricUnit, "seconds", "foo", "bar"),
|
||||
expectedLabels: labels.FromStrings(metricName, "different_name", metricType, string(model.MetricTypeCounter), metricUnit, "seconds", metricTemporality, "cumulative", "foo", "bar"),
|
||||
},
|
||||
{
|
||||
highPrioMeta: Metadata{
|
||||
|
|
@ -136,7 +144,7 @@ func TestIgnoreOverriddenMetadataLabelsScratchBuilder(t *testing.T) {
|
|||
Type: model.MetricTypeUnknown,
|
||||
Unit: "seconds",
|
||||
},
|
||||
expectedLabels: labels.FromStrings(metricName, "metric_total", metricType, string(model.MetricTypeSummary), metricUnit, "seconds", "foo", "bar"),
|
||||
expectedLabels: labels.FromStrings(metricName, "metric_total", metricType, string(model.MetricTypeSummary), metricUnit, "seconds", metricTemporality, "cumulative", "foo", "bar"),
|
||||
},
|
||||
} {
|
||||
t.Run(fmt.Sprintf("meta=%#v", tcase.highPrioMeta), func(t *testing.T) {
|
||||
|
|
@ -151,3 +159,25 @@ func TestIgnoreOverriddenMetadataLabelsScratchBuilder(t *testing.T) {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsMetadataLabel(t *testing.T) {
|
||||
testCases := []struct {
|
||||
label string
|
||||
expected bool
|
||||
}{
|
||||
{"__name__", true},
|
||||
{"__type__", true},
|
||||
{"__unit__", true},
|
||||
{"__temporality__", true},
|
||||
{"foo", false},
|
||||
{"bar", false},
|
||||
{"__other__", false},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.label, func(t *testing.T) {
|
||||
result := IsMetadataLabel(tc.label)
|
||||
require.Equal(t, tc.expected, result)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue