Skip to content

Normalize Prometheus metric names for OTLP exporter and update tests#2262

Closed
rafaelwestphal wants to merge 13 commits into
masterfrom
westphalrafael/normalize-prom-metrics
Closed

Normalize Prometheus metric names for OTLP exporter and update tests#2262
rafaelwestphal wants to merge 13 commits into
masterfrom
westphalrafael/normalize-prom-metrics

Conversation

@rafaelwestphal

@rafaelwestphal rafaelwestphal commented Apr 7, 2026

Copy link
Copy Markdown
Contributor

Description

The original behavior of prometheus metric name is to replace dots and slashes with _. Currently otlp_exporter implementation breaks that behavior. Adding a processor to fix the breaking change.

Related issue

b/476109839

How has this been tested?

Checklist:

  • Unit tests
    • Unit tests do not apply.
    • Unit tests have been added/modified and passed for this PR.
  • Integration tests
    • Integration tests do not apply.
    • Integration tests have been added/modified and passed for this PR.
  • Documentation
    • This PR introduces no user visible changes.
    • This PR introduces user visible changes and the corresponding documentation change has been made.
  • Minor version bump
    • This PR introduces no new features.
    • This PR introduces new features, and there is a separate PR to bump the minor version since the last release already.
    • This PR bumps the version.

@quentinmit quentinmit left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is our migration plan for cleaning up this tech debt in the future? What will we do when customers ask us to support utf-8 metrics or when GMP starts supporting them by default?

Comment thread confgenerator/otel/processors.go Outdated
Comment on lines 114 to 118
pipeline.Processors["metrics"] = append(pipeline.Processors["metrics"], otel.MetricUnknownCounter())
pipeline.Processors["metrics"] = append(pipeline.Processors["metrics"], otel.PrometheusMetricNormalize())
// If a metric already has a domain, it will not be considered a prometheus metric by the UTR endpoint unless we add the prefix.
// This behavior is the same as the GCM/GMP exporters.
pipeline.Processors["metrics"] = append(pipeline.Processors["metrics"], otel.MetricsTransform(otel.AddPrefix("prometheus.googleapis.com")))

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: These three can all be a single call to append with four arguments.

@rafaelwestphal

Copy link
Copy Markdown
Contributor Author

What is our migration plan for cleaning up this tech debt in the future? What will we do when customers ask us to support utf-8 metrics or when GMP starts supporting them by default?

Added a toggle to allow Cx to enable the utf-8 metrics. Default is off, but we can switch when we want to change the behavior.

@rafaelwestphal rafaelwestphal marked this pull request as ready for review April 9, 2026 18:30
// 1. Replace all non-compliant characters (except :) with underscore
`replace_pattern(metric.name, "[^a-zA-Z0-9:]", "_")`,
// 2. Collapse multiple consecutive underscores
`replace_pattern(metric.name, "_+", "_")`,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AFAICT from reading the upstream code, multiple consecutive underscores are only collapsed when WithMetricSuffixes is true. That's set to the AddMetricSuffixes config from the exporter, which we set to false.

So instead, I think we're in this case:

	// Simple case (no full normalization, no units, etc.).
	metricName := strings.Join(strings.FieldsFunc(name, func(r rune) bool {
		return !isValidCompliantMetricChar(r) && r != '_'
	}), "_")

	// Namespace?
	if mn.Namespace != "" {
		namespace := strings.Join(strings.FieldsFunc(mn.Namespace, func(r rune) bool {
			return !isValidCompliantMetricChar(r) && r != '_'
		}), "_")
		normalizedName = namespace + "_" + metricName
		return
	}

	// Metric name starts with a digit? Prefix it with an underscore.
	if metricName != "" && unicode.IsDigit(rune(metricName[0])) {
		metricName = "_" + metricName
	}

	normalizedName = metricName
	return

This does not collapse multiple underscores, nor does it remove a leading or trailing underscore.

I think you want, instead of 1-4,

replace_pattern(metric.name, "[^a-zA-Z0-9:_]+", "_").

which replaces consecutive invalid characters with a single underscore, without touching any existing underscores.

Comment thread apps/otlp.go
converter = confgenerator.ConvertPrometheusExporterToOtlpExporter
}
return []otel.ReceiverPipeline{converter(otel.ReceiverPipeline{
AllowUTF8: (r.AllowUTF8 != nil && *r.AllowUTF8),

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this a separate field in the ReceiverPipeline struct instead of the logic just being added to metricsProcessors?

Comment thread apps/otlp.go

GRPCEndpoint string `yaml:"grpc_endpoint" validate:"omitempty,hostname_port" tracking:"endpoint"`
MetricsMode string `yaml:"metrics_mode" validate:"omitempty,oneof=googlecloudmonitoring googlemanagedprometheus" tracking:""`
AllowUTF8 *bool `yaml:"allow_utf8" validate:"omitempty"`

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe this option also applies to the Prometheus receiver. But either way, we should think hard about whether and how we want to add a config knob for this. (If we want to eventually have UTF-8 on for everyone, any config knob should probably be experimental.)

@github-actions

github-actions Bot commented Jun 9, 2026

Copy link
Copy Markdown

This PR was marked stale due to lack of activity. It will be closed in 14 days.

@github-actions github-actions Bot added the Stale label Jun 9, 2026
@github-actions

Copy link
Copy Markdown

Closed as inactive. Feel free to reopen if this PR is still being worked on.

@github-actions github-actions Bot closed this Jun 24, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants