mirror of
https://github.com/prometheus/prometheus.git
synced 2025-08-06 22:27:17 +02:00
feat: Support 'NoTranslation' mode in OTLP endpoint (#16441)
* feat: Support 'NoTranslation' mode in OTLP endpoint Signed-off-by: Arthur Silva Sens <arthursens2005@gmail.com> --------- Signed-off-by: Arthur Silva Sens <arthursens2005@gmail.com>
This commit is contained in:
parent
8487ed8145
commit
d9c0ad1e61
@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
* [CHANGE] Make setting out-of-order native histograms feature (`--enable-feature=ooo-native-histograms`) a no-op. Out-of-order native histograms are now always enabled when `out_of_order_time_window` is greater than zero and `--enable-feature=native-histograms` is set. #16207
|
* [CHANGE] Make setting out-of-order native histograms feature (`--enable-feature=ooo-native-histograms`) a no-op. Out-of-order native histograms are now always enabled when `out_of_order_time_window` is greater than zero and `--enable-feature=native-histograms` is set. #16207
|
||||||
* [FEATURE] OTLP translate: Add feature flag for optionally translating OTel explicit bucket histograms into native histograms with custom buckets. #15850
|
* [FEATURE] OTLP translate: Add feature flag for optionally translating OTel explicit bucket histograms into native histograms with custom buckets. #15850
|
||||||
|
* [FEATURE] OTLP translate: Add option to receive OTLP metrics without translating names or attributes. #16441
|
||||||
* [ENHANCEMENT] TSDB: add `prometheus_tsdb_wal_replay_unknown_refs_total` and `prometheus_tsdb_wbl_replay_unknown_refs_total` metrics to track unknown series references during WAL/WBL replay. #16166
|
* [ENHANCEMENT] TSDB: add `prometheus_tsdb_wal_replay_unknown_refs_total` and `prometheus_tsdb_wbl_replay_unknown_refs_total` metrics to track unknown series references during WAL/WBL replay. #16166
|
||||||
* [BUGFIX] TSDB: fix unknown series errors and possible lost data during WAL replay when series are removed from the head due to inactivity and reappear before the next WAL checkpoint. #16060
|
* [BUGFIX] TSDB: fix unknown series errors and possible lost data during WAL replay when series are removed from the head due to inactivity and reappear before the next WAL checkpoint. #16060
|
||||||
|
|
||||||
|
@ -110,9 +110,9 @@ func Load(s string, logger *slog.Logger) (*Config, error) {
|
|||||||
switch cfg.OTLPConfig.TranslationStrategy {
|
switch cfg.OTLPConfig.TranslationStrategy {
|
||||||
case UnderscoreEscapingWithSuffixes:
|
case UnderscoreEscapingWithSuffixes:
|
||||||
case "":
|
case "":
|
||||||
case NoUTF8EscapingWithSuffixes:
|
case NoTranslation, NoUTF8EscapingWithSuffixes:
|
||||||
if cfg.GlobalConfig.MetricNameValidationScheme == LegacyValidationConfig {
|
if cfg.GlobalConfig.MetricNameValidationScheme == LegacyValidationConfig {
|
||||||
return nil, errors.New("OTLP translation strategy NoUTF8EscapingWithSuffixes is not allowed when UTF8 is disabled")
|
return nil, fmt.Errorf("OTLP translation strategy %q is not allowed when UTF8 is disabled", cfg.OTLPConfig.TranslationStrategy)
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
return nil, fmt.Errorf("unsupported OTLP translation strategy %q", cfg.OTLPConfig.TranslationStrategy)
|
return nil, fmt.Errorf("unsupported OTLP translation strategy %q", cfg.OTLPConfig.TranslationStrategy)
|
||||||
@ -1509,6 +1509,21 @@ var (
|
|||||||
// and label name characters that are not alphanumerics/underscores to underscores.
|
// and label name characters that are not alphanumerics/underscores to underscores.
|
||||||
// Unit and type suffixes may be appended to metric names, according to certain rules.
|
// Unit and type suffixes may be appended to metric names, according to certain rules.
|
||||||
UnderscoreEscapingWithSuffixes translationStrategyOption = "UnderscoreEscapingWithSuffixes"
|
UnderscoreEscapingWithSuffixes translationStrategyOption = "UnderscoreEscapingWithSuffixes"
|
||||||
|
// NoTranslation (EXPERIMENTAL): disables all translation of incoming metric
|
||||||
|
// and label names. This offers a way for the OTLP users to use native metric names, reducing confusion.
|
||||||
|
//
|
||||||
|
// WARNING: This setting has significant known risks and limitations (see
|
||||||
|
// https://prometheus.io/docs/practices/naming/ for details):
|
||||||
|
// * Impaired UX when using PromQL in plain YAML (e.g. alerts, rules, dashboard, autoscaling configuration).
|
||||||
|
// * Series collisions which in the best case may result in OOO errors, in the worst case a silently malformed
|
||||||
|
// time series. For instance, you may end up in situation of ingesting `foo.bar` series with unit
|
||||||
|
// `seconds` and a separate series `foo.bar` with unit `milliseconds`.
|
||||||
|
//
|
||||||
|
// As a result, this setting is experimental and currently, should not be used in
|
||||||
|
// production systems.
|
||||||
|
//
|
||||||
|
// TODO(ArthurSens): Mention `type-and-unit-labels` feature (https://github.com/prometheus/proposals/pull/39) once released, as potential mitigation of the above risks.
|
||||||
|
NoTranslation translationStrategyOption = "NoTranslation"
|
||||||
)
|
)
|
||||||
|
|
||||||
// OTLPConfig is the configuration for writing to the OTLP endpoint.
|
// OTLPConfig is the configuration for writing to the OTLP endpoint.
|
||||||
|
@ -1677,7 +1677,7 @@ func TestOTLPConvertHistogramsToNHCB(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestOTLPAllowUTF8(t *testing.T) {
|
func TestOTLPAllowUTF8(t *testing.T) {
|
||||||
t.Run("good config", func(t *testing.T) {
|
t.Run("good config - NoUTF8EscapingWithSuffixes", func(t *testing.T) {
|
||||||
fpath := filepath.Join("testdata", "otlp_allow_utf8.good.yml")
|
fpath := filepath.Join("testdata", "otlp_allow_utf8.good.yml")
|
||||||
verify := func(t *testing.T, conf *Config, err error) {
|
verify := func(t *testing.T, conf *Config, err error) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
@ -1697,11 +1697,51 @@ func TestOTLPAllowUTF8(t *testing.T) {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("incompatible config", func(t *testing.T) {
|
t.Run("incompatible config - NoUTF8EscapingWithSuffixes", func(t *testing.T) {
|
||||||
fpath := filepath.Join("testdata", "otlp_allow_utf8.incompatible.yml")
|
fpath := filepath.Join("testdata", "otlp_allow_utf8.incompatible.yml")
|
||||||
verify := func(t *testing.T, err error) {
|
verify := func(t *testing.T, err error) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
require.ErrorContains(t, err, `OTLP translation strategy NoUTF8EscapingWithSuffixes is not allowed when UTF8 is disabled`)
|
require.ErrorContains(t, err, `OTLP translation strategy "NoUTF8EscapingWithSuffixes" is not allowed when UTF8 is disabled`)
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Run("LoadFile", func(t *testing.T) {
|
||||||
|
_, err := LoadFile(fpath, false, promslog.NewNopLogger())
|
||||||
|
verify(t, err)
|
||||||
|
})
|
||||||
|
t.Run("Load", func(t *testing.T) {
|
||||||
|
content, err := os.ReadFile(fpath)
|
||||||
|
require.NoError(t, err)
|
||||||
|
_, err = Load(string(content), promslog.NewNopLogger())
|
||||||
|
t.Log("err", err)
|
||||||
|
verify(t, err)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("good config - NoTranslation", func(t *testing.T) {
|
||||||
|
fpath := filepath.Join("testdata", "otlp_no_translation.good.yml")
|
||||||
|
verify := func(t *testing.T, conf *Config, err error) {
|
||||||
|
t.Helper()
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, NoTranslation, conf.OTLPConfig.TranslationStrategy)
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Run("LoadFile", func(t *testing.T) {
|
||||||
|
conf, err := LoadFile(fpath, false, promslog.NewNopLogger())
|
||||||
|
verify(t, conf, err)
|
||||||
|
})
|
||||||
|
t.Run("Load", func(t *testing.T) {
|
||||||
|
content, err := os.ReadFile(fpath)
|
||||||
|
require.NoError(t, err)
|
||||||
|
conf, err := Load(string(content), promslog.NewNopLogger())
|
||||||
|
verify(t, conf, err)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("incompatible config - NoTranslation", func(t *testing.T) {
|
||||||
|
fpath := filepath.Join("testdata", "otlp_no_translation.incompatible.yml")
|
||||||
|
verify := func(t *testing.T, err error) {
|
||||||
|
t.Helper()
|
||||||
|
require.ErrorContains(t, err, `OTLP translation strategy "NoTranslation" is not allowed when UTF8 is disabled`)
|
||||||
}
|
}
|
||||||
|
|
||||||
t.Run("LoadFile", func(t *testing.T) {
|
t.Run("LoadFile", func(t *testing.T) {
|
||||||
|
2
config/testdata/otlp_no_translation.good.yml
vendored
Normal file
2
config/testdata/otlp_no_translation.good.yml
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
otlp:
|
||||||
|
translation_strategy: NoTranslation
|
4
config/testdata/otlp_no_translation.incompatible.yml
vendored
Normal file
4
config/testdata/otlp_no_translation.incompatible.yml
vendored
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
global:
|
||||||
|
metric_name_validation_scheme: legacy
|
||||||
|
otlp:
|
||||||
|
translation_strategy: NoTranslation
|
@ -579,8 +579,8 @@ func (rw *rwExporter) ConsumeMetrics(ctx context.Context, md pmetric.Metrics) er
|
|||||||
|
|
||||||
converter := otlptranslator.NewPrometheusConverter()
|
converter := otlptranslator.NewPrometheusConverter()
|
||||||
annots, err := converter.FromMetrics(ctx, md, otlptranslator.Settings{
|
annots, err := converter.FromMetrics(ctx, md, otlptranslator.Settings{
|
||||||
AddMetricSuffixes: true,
|
AddMetricSuffixes: otlpCfg.TranslationStrategy != config.NoTranslation,
|
||||||
AllowUTF8: otlpCfg.TranslationStrategy == config.NoUTF8EscapingWithSuffixes,
|
AllowUTF8: otlpCfg.TranslationStrategy != config.UnderscoreEscapingWithSuffixes,
|
||||||
PromoteResourceAttributes: otlpCfg.PromoteResourceAttributes,
|
PromoteResourceAttributes: otlpCfg.PromoteResourceAttributes,
|
||||||
KeepIdentifyingResourceAttributes: otlpCfg.KeepIdentifyingResourceAttributes,
|
KeepIdentifyingResourceAttributes: otlpCfg.KeepIdentifyingResourceAttributes,
|
||||||
ConvertHistogramsToNHCB: otlpCfg.ConvertHistogramsToNHCB,
|
ConvertHistogramsToNHCB: otlpCfg.ConvertHistogramsToNHCB,
|
||||||
|
@ -382,7 +382,118 @@ func TestWriteStorageApplyConfig_PartialUpdate(t *testing.T) {
|
|||||||
|
|
||||||
func TestOTLPWriteHandler(t *testing.T) {
|
func TestOTLPWriteHandler(t *testing.T) {
|
||||||
exportRequest := generateOTLPWriteRequest()
|
exportRequest := generateOTLPWriteRequest()
|
||||||
|
timestamp := time.Now()
|
||||||
|
for _, testCase := range []struct {
|
||||||
|
name string
|
||||||
|
otlpCfg config.OTLPConfig
|
||||||
|
expectedSamples []mockSample
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "NoTranslation",
|
||||||
|
otlpCfg: config.OTLPConfig{
|
||||||
|
TranslationStrategy: config.NoTranslation,
|
||||||
|
},
|
||||||
|
expectedSamples: []mockSample{
|
||||||
|
{
|
||||||
|
l: labels.New(labels.Label{Name: "__name__", Value: "test.counter"},
|
||||||
|
labels.Label{Name: "foo.bar", Value: "baz"},
|
||||||
|
labels.Label{Name: "instance", Value: "test-instance"},
|
||||||
|
labels.Label{Name: "job", Value: "test-service"}),
|
||||||
|
t: timestamp.UnixMilli(),
|
||||||
|
v: 10.0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
l: labels.New(
|
||||||
|
labels.Label{Name: "__name__", Value: "target_info"},
|
||||||
|
labels.Label{Name: "host.name", Value: "test-host"},
|
||||||
|
labels.Label{Name: "instance", Value: "test-instance"},
|
||||||
|
labels.Label{Name: "job", Value: "test-service"},
|
||||||
|
),
|
||||||
|
t: timestamp.UnixMilli(),
|
||||||
|
v: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "UnderscoreEscapingWithSuffixes",
|
||||||
|
otlpCfg: config.OTLPConfig{
|
||||||
|
TranslationStrategy: config.UnderscoreEscapingWithSuffixes,
|
||||||
|
},
|
||||||
|
expectedSamples: []mockSample{
|
||||||
|
{
|
||||||
|
l: labels.New(labels.Label{Name: "__name__", Value: "test_counter_total"},
|
||||||
|
labels.Label{Name: "foo_bar", Value: "baz"},
|
||||||
|
labels.Label{Name: "instance", Value: "test-instance"},
|
||||||
|
labels.Label{Name: "job", Value: "test-service"}),
|
||||||
|
t: timestamp.UnixMilli(),
|
||||||
|
v: 10.0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
l: labels.New(
|
||||||
|
labels.Label{Name: "__name__", Value: "target_info"},
|
||||||
|
labels.Label{Name: "host_name", Value: "test-host"},
|
||||||
|
labels.Label{Name: "instance", Value: "test-instance"},
|
||||||
|
labels.Label{Name: "job", Value: "test-service"},
|
||||||
|
),
|
||||||
|
t: timestamp.UnixMilli(),
|
||||||
|
v: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
name: "NoUTF8EscapingWithSuffixes",
|
||||||
|
otlpCfg: config.OTLPConfig{
|
||||||
|
TranslationStrategy: config.NoUTF8EscapingWithSuffixes,
|
||||||
|
},
|
||||||
|
expectedSamples: []mockSample{
|
||||||
|
{
|
||||||
|
l: labels.New(labels.Label{Name: "__name__", Value: "test.counter_total"},
|
||||||
|
labels.Label{Name: "foo.bar", Value: "baz"},
|
||||||
|
labels.Label{Name: "instance", Value: "test-instance"},
|
||||||
|
labels.Label{Name: "job", Value: "test-service"}),
|
||||||
|
t: timestamp.UnixMilli(),
|
||||||
|
v: 10.0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
l: labels.New(
|
||||||
|
labels.Label{Name: "__name__", Value: "target_info"},
|
||||||
|
labels.Label{Name: "host.name", Value: "test-host"},
|
||||||
|
labels.Label{Name: "instance", Value: "test-instance"},
|
||||||
|
labels.Label{Name: "job", Value: "test-service"},
|
||||||
|
),
|
||||||
|
t: timestamp.UnixMilli(),
|
||||||
|
v: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
t.Run(testCase.name, func(t *testing.T) {
|
||||||
|
appendable := handleOTLP(t, exportRequest, testCase.otlpCfg)
|
||||||
|
for _, sample := range testCase.expectedSamples {
|
||||||
|
requireContainsSample(t, appendable.samples, sample)
|
||||||
|
}
|
||||||
|
require.Len(t, appendable.samples, 12) // 1 (counter) + 1 (gauge) + 1 (target_info) + 7 (hist_bucket) + 2 (hist_sum, hist_count)
|
||||||
|
require.Len(t, appendable.histograms, 1) // 1 (exponential histogram)
|
||||||
|
require.Len(t, appendable.exemplars, 1) // 1 (exemplar)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func requireContainsSample(t *testing.T, actual []mockSample, expected mockSample) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
for _, got := range actual {
|
||||||
|
if labels.Equal(expected.l, got.l) && expected.t == got.t && expected.v == got.v {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
require.Fail(t, fmt.Sprintf("Sample not found: \n"+
|
||||||
|
"expected: %v\n"+
|
||||||
|
"actual : %v", expected, actual))
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleOTLP(t *testing.T, exportRequest pmetricotlp.ExportRequest, otlpCfg config.OTLPConfig) *mockAppendable {
|
||||||
buf, err := exportRequest.MarshalProto()
|
buf, err := exportRequest.MarshalProto()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
@ -393,19 +504,16 @@ func TestOTLPWriteHandler(t *testing.T) {
|
|||||||
appendable := &mockAppendable{}
|
appendable := &mockAppendable{}
|
||||||
handler := NewOTLPWriteHandler(nil, nil, appendable, func() config.Config {
|
handler := NewOTLPWriteHandler(nil, nil, appendable, func() config.Config {
|
||||||
return config.Config{
|
return config.Config{
|
||||||
OTLPConfig: config.DefaultOTLPConfig,
|
OTLPConfig: otlpCfg,
|
||||||
}
|
}
|
||||||
}, OTLPOptions{})
|
}, OTLPOptions{})
|
||||||
|
|
||||||
recorder := httptest.NewRecorder()
|
recorder := httptest.NewRecorder()
|
||||||
handler.ServeHTTP(recorder, req)
|
handler.ServeHTTP(recorder, req)
|
||||||
|
|
||||||
resp := recorder.Result()
|
resp := recorder.Result()
|
||||||
require.Equal(t, http.StatusOK, resp.StatusCode)
|
require.Equal(t, http.StatusOK, resp.StatusCode)
|
||||||
|
|
||||||
require.Len(t, appendable.samples, 12) // 1 (counter) + 1 (gauge) + 1 (target_info) + 7 (hist_bucket) + 2 (hist_sum, hist_count)
|
return appendable
|
||||||
require.Len(t, appendable.histograms, 1) // 1 (exponential histogram)
|
|
||||||
require.Len(t, appendable.exemplars, 1) // 1 (exemplar)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func generateOTLPWriteRequest() pmetricotlp.ExportRequest {
|
func generateOTLPWriteRequest() pmetricotlp.ExportRequest {
|
||||||
@ -426,7 +534,7 @@ func generateOTLPWriteRequest() pmetricotlp.ExportRequest {
|
|||||||
|
|
||||||
// Generate One Counter
|
// Generate One Counter
|
||||||
counterMetric := scopeMetric.Metrics().AppendEmpty()
|
counterMetric := scopeMetric.Metrics().AppendEmpty()
|
||||||
counterMetric.SetName("test-counter")
|
counterMetric.SetName("test.counter")
|
||||||
counterMetric.SetDescription("test-counter-description")
|
counterMetric.SetDescription("test-counter-description")
|
||||||
counterMetric.SetEmptySum()
|
counterMetric.SetEmptySum()
|
||||||
counterMetric.Sum().SetAggregationTemporality(pmetric.AggregationTemporalityCumulative)
|
counterMetric.Sum().SetAggregationTemporality(pmetric.AggregationTemporalityCumulative)
|
||||||
@ -446,7 +554,7 @@ func generateOTLPWriteRequest() pmetricotlp.ExportRequest {
|
|||||||
|
|
||||||
// Generate One Gauge
|
// Generate One Gauge
|
||||||
gaugeMetric := scopeMetric.Metrics().AppendEmpty()
|
gaugeMetric := scopeMetric.Metrics().AppendEmpty()
|
||||||
gaugeMetric.SetName("test-gauge")
|
gaugeMetric.SetName("test.gauge")
|
||||||
gaugeMetric.SetDescription("test-gauge-description")
|
gaugeMetric.SetDescription("test-gauge-description")
|
||||||
gaugeMetric.SetEmptyGauge()
|
gaugeMetric.SetEmptyGauge()
|
||||||
|
|
||||||
@ -457,7 +565,7 @@ func generateOTLPWriteRequest() pmetricotlp.ExportRequest {
|
|||||||
|
|
||||||
// Generate One Histogram
|
// Generate One Histogram
|
||||||
histogramMetric := scopeMetric.Metrics().AppendEmpty()
|
histogramMetric := scopeMetric.Metrics().AppendEmpty()
|
||||||
histogramMetric.SetName("test-histogram")
|
histogramMetric.SetName("test.histogram")
|
||||||
histogramMetric.SetDescription("test-histogram-description")
|
histogramMetric.SetDescription("test-histogram-description")
|
||||||
histogramMetric.SetEmptyHistogram()
|
histogramMetric.SetEmptyHistogram()
|
||||||
histogramMetric.Histogram().SetAggregationTemporality(pmetric.AggregationTemporalityCumulative)
|
histogramMetric.Histogram().SetAggregationTemporality(pmetric.AggregationTemporalityCumulative)
|
||||||
@ -472,7 +580,7 @@ func generateOTLPWriteRequest() pmetricotlp.ExportRequest {
|
|||||||
|
|
||||||
// Generate One Exponential-Histogram
|
// Generate One Exponential-Histogram
|
||||||
exponentialHistogramMetric := scopeMetric.Metrics().AppendEmpty()
|
exponentialHistogramMetric := scopeMetric.Metrics().AppendEmpty()
|
||||||
exponentialHistogramMetric.SetName("test-exponential-histogram")
|
exponentialHistogramMetric.SetName("test.exponential.histogram")
|
||||||
exponentialHistogramMetric.SetDescription("test-exponential-histogram-description")
|
exponentialHistogramMetric.SetDescription("test-exponential-histogram-description")
|
||||||
exponentialHistogramMetric.SetEmptyExponentialHistogram()
|
exponentialHistogramMetric.SetEmptyExponentialHistogram()
|
||||||
exponentialHistogramMetric.ExponentialHistogram().SetAggregationTemporality(pmetric.AggregationTemporalityCumulative)
|
exponentialHistogramMetric.ExponentialHistogram().SetAggregationTemporality(pmetric.AggregationTemporalityCumulative)
|
||||||
|
Loading…
Reference in New Issue
Block a user