Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -1604,6 +1604,9 @@ type OTLPConfig struct {
TranslationStrategy translationStrategyOption `yaml:"translation_strategy,omitempty"`
KeepIdentifyingResourceAttributes bool `yaml:"keep_identifying_resource_attributes,omitempty"`
ConvertHistogramsToNHCB bool `yaml:"convert_histograms_to_nhcb,omitempty"`
// PromoteScopeMetadata controls whether to promote OTel scope metadata (i.e. name, version, schema URL, and attributes) to metric labels.
// As per OTel spec, the aforementioned scope metadata should be identifying, i.e. made into metric labels.
PromoteScopeMetadata bool `yaml:"promote_scope_metadata,omitempty"`
}

// UnmarshalYAML implements the yaml.Unmarshaler interface.
Expand Down
14 changes: 14 additions & 0 deletions config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1808,6 +1808,20 @@ func TestOTLPConvertHistogramsToNHCB(t *testing.T) {
})
}

func TestOTLPPromoteScopeMetadata(t *testing.T) {
t.Run("good config", func(t *testing.T) {
want, err := LoadFile(filepath.Join("testdata", "otlp_promote_scope_metadata.good.yml"), false, promslog.NewNopLogger())
require.NoError(t, err)

out, err := yaml.Marshal(want)
require.NoError(t, err)
var got Config
require.NoError(t, yaml.UnmarshalStrict(out, &got))

require.True(t, got.OTLPConfig.PromoteScopeMetadata)
})
}

func TestOTLPAllowUTF8(t *testing.T) {
t.Run("good config - NoUTF8EscapingWithSuffixes", func(t *testing.T) {
fpath := filepath.Join("testdata", "otlp_allow_utf8.good.yml")
Expand Down
2 changes: 2 additions & 0 deletions config/testdata/otlp_promote_scope_metadata.good.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
otlp:
promote_scope_metadata: true
3 changes: 3 additions & 0 deletions docs/configuration/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,9 @@ otlp:
[ keep_identifying_resource_attributes: <boolean> | default = false ]
# Configures optional translation of OTLP explicit bucket histograms into native histograms with custom buckets.
[ convert_histograms_to_nhcb: <boolean> | default = false ]
# Enables promotion of OTel scope metadata (i.e. name, version, schema URL, and attributes) to metric labels.
# This is disabled by default for backwards compatibility, but according to OTel spec, scope metadata _should_ be identifying, i.e. translated to metric labels.
[ promote_scope_metadata: <boolean> | default = false ]

# Settings related to the remote read feature.
remote_read:
Expand Down
32 changes: 25 additions & 7 deletions storage/remote/otlptranslator/prometheusremotewrite/helper.go
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ var seps = []byte{'\xff'}
// Unpaired string values are ignored. String pairs overwrite OTLP labels if collisions happen and
// if logOnOverwrite is true, the overwrite is logged. Resulting label names are sanitized.
// If settings.PromoteResourceAttributes is not empty, it's a set of resource attributes that should be promoted to labels.
func createAttributes(resource pcommon.Resource, attributes pcommon.Map, settings Settings,
func createAttributes(resource pcommon.Resource, attributes pcommon.Map, scope scope, settings Settings,
ignoreAttrs []string, logOnOverwrite bool, extras ...string,
) []prompb.Label {
resourceAttrs := resource.Attributes()
Expand All @@ -126,8 +126,15 @@ func createAttributes(resource pcommon.Resource, attributes pcommon.Map, setting

promotedAttrs := settings.PromoteResourceAttributes.promotedAttributes(resourceAttrs)

promoteScope := settings.PromoteScopeMetadata && scope.name != ""
scopeLabelCount := 0
if promoteScope {
// Include name, version and schema URL.
scopeLabelCount = scope.attributes.Len() + 3
}

// Calculate the maximum possible number of labels we could return so we can preallocate l.
maxLabelCount := attributes.Len() + len(settings.ExternalLabels) + len(promotedAttrs) + len(extras)/2
maxLabelCount := attributes.Len() + len(settings.ExternalLabels) + len(promotedAttrs) + scopeLabelCount + len(extras)/2

if haveServiceName {
maxLabelCount++
Expand Down Expand Up @@ -167,6 +174,17 @@ func createAttributes(resource pcommon.Resource, attributes pcommon.Map, setting
l[normalized] = lbl.Value
}
}
if promoteScope {
scope.attributes.Range(func(k string, v pcommon.Value) bool {
name := labelNamer.Build("otel_scope_" + k)
l[name] = v.AsString()
return true
})
// Scope Name, Version and Schema URL are added after attributes to ensure they are not overwritten by attributes.
l["otel_scope_name"] = scope.name
l["otel_scope_version"] = scope.version
l["otel_scope_schema_url"] = scope.schemaURL
}

// Map service.name + service.namespace to job.
if haveServiceName {
Expand Down Expand Up @@ -237,7 +255,7 @@ func aggregationTemporality(metric pmetric.Metric) (pmetric.AggregationTemporali
// However, work is under way to resolve this shortcoming through a feature called native histograms custom buckets:
// https://github.com/prometheus/prometheus/issues/13485.
func (c *PrometheusConverter) addHistogramDataPoints(ctx context.Context, dataPoints pmetric.HistogramDataPointSlice,
resource pcommon.Resource, settings Settings, baseName string,
resource pcommon.Resource, settings Settings, baseName string, scope scope,
) error {
for x := 0; x < dataPoints.Len(); x++ {
if err := c.everyN.checkContext(ctx); err != nil {
Expand All @@ -246,7 +264,7 @@ func (c *PrometheusConverter) addHistogramDataPoints(ctx context.Context, dataPo

pt := dataPoints.At(x)
timestamp := convertTimeStamp(pt.Timestamp())
baseLabels := createAttributes(resource, pt.Attributes(), settings, nil, false)
baseLabels := createAttributes(resource, pt.Attributes(), scope, settings, nil, false)

// If the sum is unset, it indicates the _sum metric point should be
// omitted
Expand Down Expand Up @@ -447,7 +465,7 @@ func findMinAndMaxTimestamps(metric pmetric.Metric, minTimestamp, maxTimestamp p
}

func (c *PrometheusConverter) addSummaryDataPoints(ctx context.Context, dataPoints pmetric.SummaryDataPointSlice, resource pcommon.Resource,
settings Settings, baseName string,
settings Settings, baseName string, scope scope,
) error {
for x := 0; x < dataPoints.Len(); x++ {
if err := c.everyN.checkContext(ctx); err != nil {
Expand All @@ -456,7 +474,7 @@ func (c *PrometheusConverter) addSummaryDataPoints(ctx context.Context, dataPoin

pt := dataPoints.At(x)
timestamp := convertTimeStamp(pt.Timestamp())
baseLabels := createAttributes(resource, pt.Attributes(), settings, nil, false)
baseLabels := createAttributes(resource, pt.Attributes(), scope, settings, nil, false)

// treat sum as a sample in an individual TimeSeries
sum := &prompb.Sample{
Expand Down Expand Up @@ -609,7 +627,7 @@ func addResourceTargetInfo(resource pcommon.Resource, settings Settings, earlies
// Do not pass identifying attributes as ignoreAttrs below.
identifyingAttrs = nil
}
labels := createAttributes(resource, attributes, settings, identifyingAttrs, false, model.MetricNameLabel, name)
labels := createAttributes(resource, attributes, scope{}, settings, identifyingAttrs, false, model.MetricNameLabel, name)
haveIdentifier := false
for _, l := range labels {
if l.Name == model.JobLabel || l.Name == model.InstanceLabel {
Expand Down
Loading
Loading