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__" | ||||
| 	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.
 | ||||
|  | @ -83,9 +87,10 @@ func NewMetadataFromLabels(ls labels.Labels) Metadata { | |||
| 		typ = model.MetricType(got) | ||||
| 	} | ||||
| 	return Metadata{ | ||||
| 		Name: ls.Get(metricName), | ||||
| 		Type: typ, | ||||
| 		Unit: ls.Get(metricUnit), | ||||
| 		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
 | ||||
|  |  | |||
|  | @ -26,19 +26,21 @@ import ( | |||
| 
 | ||||
| func TestMetadata(t *testing.T) { | ||||
| 	testMeta := Metadata{ | ||||
| 		Name: "metric_total", | ||||
| 		Type: model.MetricTypeCounter, | ||||
| 		Unit: "seconds", | ||||
| 		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 | ||||
|  | @ -110,25 +117,26 @@ func TestIgnoreOverriddenMetadataLabelsScratchBuilder(t *testing.T) { | |||
| 		}, | ||||
| 		{ | ||||
| 			highPrioMeta: Metadata{ | ||||
| 				Name: "metric_total", | ||||
| 				Type: model.MetricTypeCounter, | ||||
| 				Unit: "seconds", | ||||
| 				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