Skip to content

Allow metrics with the same name different labels - #1800

Merged
zeitlinger merged 29 commits into
mainfrom
duplicate-names-registration-validation
Feb 5, 2026
Merged

Allow metrics with the same name different labels#1800
zeitlinger merged 29 commits into
mainfrom
duplicate-names-registration-validation

Conversation

@jaydeluca

@jaydelucajaydeluca commented Jan 24, 2026

Copy link
Copy Markdown
Member

Closes#696

Alternate approach to #1728

Validation occurs at registration time:

  • Metrics with the same name, type, unit, and help - but different labels, are allowed to be registered
  • Custom Collectors that do not implement the required fields (prometheus name, metric type) will skip validation and could risk creating duplicate series that will cause issues at scrape time.
    • I looked into adding a fallback, but it requires calling collect() at registration in order to introspect existing metadata and that seemed not great due to potential side effects

@jaydeluca
jaydelucaforce-pushed the duplicate-names-registration-validation branch from 7203084 to 2359244CompareJanuary 24, 2026 16:10
Signed-off-by: Jay DeLuca <jaydeluca4@gmail.com>
Signed-off-by: Jay DeLuca <jaydeluca4@gmail.com>
Signed-off-by: Jay DeLuca <jaydeluca4@gmail.com>
Signed-off-by: Jay DeLuca <jaydeluca4@gmail.com>
Signed-off-by: Jay DeLuca <jaydeluca4@gmail.com>
Signed-off-by: Jay DeLuca <jaydeluca4@gmail.com>
@jaydeluca
jaydelucaforce-pushed the duplicate-names-registration-validation branch from 2359244 to 9289e4aCompareJanuary 24, 2026 16:10
Signed-off-by: Jay DeLuca <jaydeluca4@gmail.com>
@zeitlinger

Copy link
Copy Markdown
Member

I still also have scrape time validation occurring along with registration time, but we can remove that if we think the registration time validation is sufficient protection.

I would opt for registration only.

What does that mean for clients that only work on snapshots like the OTel SDK?

https://github.com/open-telemetry/opentelemetry-java/blob/da310cc1fcd06e606426649f6c8db96958d9bb12/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/PrometheusMetricReader.java#L104

Signed-off-by: Jay DeLuca <jaydeluca4@gmail.com>
Signed-off-by: Jay DeLuca <jaydeluca4@gmail.com>
Signed-off-by: Jay DeLuca <jaydeluca4@gmail.com>
@jaydeluca
jaydelucaforce-pushed the duplicate-names-registration-validation branch from 62b9cbc to 02c0db0CompareJanuary 29, 2026 20:03
@jaydeluca

Copy link
Copy Markdown
MemberAuthor

still need to incorporate checks for the help/unit

Signed-off-by: Jay DeLuca <jaydeluca4@gmail.com>
Signed-off-by: Jay DeLuca <jaydeluca4@gmail.com>
Signed-off-by: Jay DeLuca <jaydeluca4@gmail.com>
Signed-off-by: Jay DeLuca <jaydeluca4@gmail.com>
Signed-off-by: Jay DeLuca <jaydeluca4@gmail.com>
Signed-off-by: Jay DeLuca <jaydeluca4@gmail.com>
@jaydeluca
jaydelucaforce-pushed the duplicate-names-registration-validation branch from b5396a5 to 1e897dcCompareJanuary 30, 2026 17:53
@jaydelucajaydeluca changed the title Allow metrics with the same name (Option #2)Allow metrics with the same name different labelsJan 30, 2026
@jaydeluca

Copy link
Copy Markdown
MemberAuthor

What does that mean for clients that only work on snapshots like the OTel SDK?

Should be no change in behavior, I've added some tests that emulate the same type of interactions

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Enables registering multiple metrics with the same name when they differ by label schema, while ensuring exposition formats emit a single metric family per name.

Changes:

  • Adds registration-time validation in PrometheusRegistry for metric type consistency, help/unit consistency, and duplicate label schemas (when collectors provide the new metadata methods).
  • Allows MetricSnapshots to contain duplicate names (same snapshot type), and merges duplicate-name snapshots during exposition writing.
  • Introduces integration/unit tests and sample apps covering duplicate-name behavior across text/OpenMetrics/protobuf exporters.

Reviewed changes

Copilot reviewed 35 out of 36 changed files in this pull request and generated 11 comments.

Show a summary per file
FileDescription
prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/registry/PrometheusRegistryTest.javaExpands registry tests for duplicate names, label schemas, help/unit consistency, and unregister behavior.
prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/registry/OpenTelemetryExporterRegistryCompatibilityTest.javaAdds regression tests for OTel-style MultiCollector usage (no names/types/labels metadata).
prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/MetricSnapshots.javaRelaxes duplicate-name constraint; only rejects conflicting snapshot types for same name.
prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/registry/PrometheusRegistry.javaImplements registration-time validation and tracking for per-name label schemas/type/help/unit.
prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/registry/MultiCollector.javaAdds optional per-metric-name metadata methods (getMetricType, getLabelNames, getMetadata).
prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/registry/MetricType.javaIntroduces a metric-type enum used for registration-time validation.
prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/registry/Collector.javaAdds optional metadata methods (getMetricType, getLabelNames, getMetadata) for registration-time validation.
prometheus-metrics-exposition-textformats/src/test/java/io/prometheus/metrics/expositionformats/TextFormatUtilTest.javaAdds unit tests for merging duplicate-name snapshots.
prometheus-metrics-exposition-textformats/src/test/java/io/prometheus/metrics/expositionformats/DuplicateNamesExpositionTest.javaAdds text/OpenMetrics exposition tests demonstrating valid output with duplicate names.
prometheus-metrics-exposition-textformats/src/main/java/io/prometheus/metrics/expositionformats/TextFormatUtil.javaAdds duplicate-name merging utility used by writers.
prometheus-metrics-exposition-textformats/src/main/java/io/prometheus/metrics/expositionformats/PrometheusTextFormatWriter.javaMerges duplicate-name snapshots before writing Prometheus text format.
prometheus-metrics-exposition-textformats/src/main/java/io/prometheus/metrics/expositionformats/OpenMetricsTextFormatWriter.javaMerges duplicate-name snapshots before writing OpenMetrics text format.
prometheus-metrics-exposition-formats/src/test/java/io/prometheus/metrics/expositionformats/DuplicateNamesProtobufTest.javaAdds protobuf exposition tests for duplicate-name snapshots being merged into one family.
prometheus-metrics-exposition-formats/src/main/java/io/prometheus/metrics/expositionformats/internal/PrometheusProtobufWriterImpl.javaMerges duplicate-name snapshots before protobuf serialization.
prometheus-metrics-exposition-formats/generate-protobuf.shUpdates generation script to try to support macOS tooling and mise-provided protoc.
prometheus-metrics-core/src/test/java/io/prometheus/metrics/core/metrics/CounterTest.javaAdds test for label normalization affecting registration-time label schema validation.
prometheus-metrics-core/src/main/java/io/prometheus/metrics/core/metrics/SummaryWithCallback.javaImplements getMetricType() for registration-time validation.
prometheus-metrics-core/src/main/java/io/prometheus/metrics/core/metrics/Summary.javaImplements getMetricType() for registration-time validation.
prometheus-metrics-core/src/main/java/io/prometheus/metrics/core/metrics/StateSet.javaImplements getMetricType() for registration-time validation.
prometheus-metrics-core/src/main/java/io/prometheus/metrics/core/metrics/MetricWithFixedMetadata.javaExposes metadata via Collector.getMetadata() and implements normalized getLabelNames().
prometheus-metrics-core/src/main/java/io/prometheus/metrics/core/metrics/Info.javaImplements getMetricType() for registration-time validation.
prometheus-metrics-core/src/main/java/io/prometheus/metrics/core/metrics/Histogram.javaImplements getMetricType() for registration-time validation.
prometheus-metrics-core/src/main/java/io/prometheus/metrics/core/metrics/GaugeWithCallback.javaImplements getMetricType() for registration-time validation.
prometheus-metrics-core/src/main/java/io/prometheus/metrics/core/metrics/Gauge.javaImplements getMetricType() for registration-time validation.
prometheus-metrics-core/src/main/java/io/prometheus/metrics/core/metrics/CounterWithCallback.javaImplements getMetricType() for registration-time validation.
prometheus-metrics-core/src/main/java/io/prometheus/metrics/core/metrics/Counter.javaImplements getMetricType() for registration-time validation.
mise.tomlAdjusts Java toolchain version.
integration-tests/it-exporter/pom.xmlAdds a new integration-test sample module for duplicate metrics.
integration-tests/it-exporter/it-exporter-test/src/test/java/io/prometheus/metrics/it/exporter/test/DuplicateMetricsIT.javaAdds end-to-end exporter tests validating merged duplicate metrics output.
integration-tests/it-exporter/it-exporter-duplicate-metrics-sample/src/main/java/io/prometheus/metrics/it/exporter/duplicatemetrics/DuplicateMetricsSample.javaAdds sample app emitting same-name metrics with different label sets.
integration-tests/it-exporter/it-exporter-duplicate-metrics-sample/pom.xmlMaven module for the duplicate-metrics integration sample.
integration-tests/it-common/src/test/java/io/prometheus/client/it/common/ExporterTest.javaMakes the container field protected for reuse by new ITs.
docs/content/internals/model.mdDocuments behavior for collectors that don’t provide type/label metadata.
docs/content/getting-started/registry.mdDocuments registration-time-only validation and optional metadata methods.
docs/content/getting-started/metric-types.mdAdds guidance about avoiding duplicate time series for unvalidated custom collectors.
.gitignoreIgnores macOS .DS_Store files.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment threadprometheus-metrics-exposition-formats/generate-protobuf.sh Outdated
Comment threadprometheus-metrics-exposition-formats/generate-protobuf.sh Outdated
…ix build order to avoid local issues
Signed-off-by: Jay DeLuca <jaydeluca4@gmail.com>

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 37 out of 38 changed files in this pull request and generated 7 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Signed-off-by: Jay DeLuca <jaydeluca4@gmail.com>
Signed-off-by: Jay DeLuca <jaydeluca4@gmail.com>
Signed-off-by: Jay DeLuca <jaydeluca4@gmail.com>

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 37 out of 38 changed files in this pull request and generated 5 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@jaydeluca
jaydeluca marked this pull request as ready for review February 4, 2026 18:12

@zeitlingerzeitlinger 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.

great - we're almost there 😄

}

multiCollectorMetadata.put(collector, registrations);
} catch (Exception e) {

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.

can this be a specific ex?

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.

so now you have RuntimeException - can you list the specific exception that is thrown - or is this unknown?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

we invoke of a handful of methods in these blocks that could potentially have buggy implementations from custom collector implementations, which could throw NPEs or other runtime exceptions. And we need to catch them so we don't end up with partial registrations.

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.

OK, can you add that as a comment?

* @return the metric type, or {@code null} to skip validation
*/
@Nullable
default MetricType getMetricType() {

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 like default methods over instanceof checks!

Comment threadprometheus-metrics-exposition-formats-shaded/pom.xml Outdated
Comment threadprometheus-metrics-exposition-formats/generate-protobuf.sh Outdated
…rate script
Signed-off-by: Jay DeLuca <jaydeluca4@gmail.com>
@jaydeluca
jaydelucaforce-pushed the duplicate-names-registration-validation branch from bf4c701 to d1d4c09CompareFebruary 5, 2026 21:01
Signed-off-by: Jay DeLuca <jaydeluca4@gmail.com>
@jaydeluca
jaydelucaforce-pushed the duplicate-names-registration-validation branch from d1d4c09 to d8490cdCompareFebruary 5, 2026 21:02
@zeitlinger
zeitlinger merged commit 0c9d5bf into mainFeb 5, 2026
14 checks passed
@zeitlinger
zeitlinger deleted the duplicate-names-registration-validation branch February 5, 2026 21:47
@zeitlingerzeitlinger mentioned this pull request Feb 20, 2026
4 tasks
zeitlinger added a commit that referenced this pull request May 22, 2026
Draft validation PR for the unmodified Micrometer compatibility story.
This intentionally does **not** depend on #2114. Vanilla Micrometer does
not use
the typed descriptor API and does not implement the #1800 registration
metadata
hooks, so this PR validates the patch-compatible path independently of
typed
descriptors.
This validates upstream `micrometer-metrics/micrometer@main` against:
- current `main`, which already includes #2100 and #2124 (reserved
suffix
stripping in `PrometheusNaming.sanitizeMetricName()`).
- Micrometer compatibility test tooling/workflow from
zeitlinger#1.
Local validation:
- `mise run lint`
- `MICROMETER_DIR=/tmp/micrometer-compat-vanilla-2124 mise run
micrometer:test`
---------
Signed-off-by: Gregor Zeitlinger <gregor.zeitlinger@grafana.com>
Co-authored-by: Jay DeLuca <jaydeluca4@gmail.com>
zeitlinger added a commit that referenced this pull request Jun 1, 2026
Adds typed metric family descriptors and typed metadata support for the
model snapshots.
This is the typed-descriptor branch for downstreams that want to provide
registration-time metadata explicitly. The #1800
Collector/MultiCollector registration metadata hooks are already
optional via default methods, so unmodified downstreams should not need
this PR just to keep working.
This PR now also deprecates the fragmented registration metadata API
(`getPrometheusName()`, `getMetricType()`, `getLabelNames()`, and
`getMetadata()` plus the `MultiCollector` variants) in favor of
`getMetricFamilyDescriptor()` / `getMetricFamilyDescriptors()`. The
deprecated methods remain bridged by default implementations for
compatibility.
Related validation:
- #2121 validates unmodified Micrometer independently of #2114, against
`main` + #2124.
- #2123 validates a Micrometer branch that explicitly uses
`MetricFamilyDescriptor` to implement the existing registration metadata
hooks without invoking scrape/sample callbacks during registration.
---------
Signed-off-by: Gregor Zeitlinger <gregor.zeitlinger@grafana.com>
jaydeluca added a commit that referenced this pull request Jun 4, 2026
Draft validation PR for the unmodified Micrometer compatibility story.
This intentionally does **not** depend on #2114. Vanilla Micrometer does
not use
the typed descriptor API and does not implement the #1800 registration
metadata
hooks, so this PR validates the patch-compatible path independently of
typed
descriptors.
This validates upstream `micrometer-metrics/micrometer@main` against:
- current `main`, which already includes #2100 and #2124 (reserved
suffix
stripping in `PrometheusNaming.sanitizeMetricName()`).
- Micrometer compatibility test tooling/workflow from
zeitlinger#1.
Local validation:
- `mise run lint`
- `MICROMETER_DIR=/tmp/micrometer-compat-vanilla-2124 mise run
micrometer:test`
---------
Signed-off-by: Gregor Zeitlinger <gregor.zeitlinger@grafana.com>
Co-authored-by: Jay DeLuca <jaydeluca4@gmail.com>
Signed-off-by: Jay DeLuca <jaydeluca4@gmail.com>
jaydeluca pushed a commit that referenced this pull request Jun 4, 2026
Adds typed metric family descriptors and typed metadata support for the
model snapshots.
This is the typed-descriptor branch for downstreams that want to provide
registration-time metadata explicitly. The #1800
Collector/MultiCollector registration metadata hooks are already
optional via default methods, so unmodified downstreams should not need
this PR just to keep working.
This PR now also deprecates the fragmented registration metadata API
(`getPrometheusName()`, `getMetricType()`, `getLabelNames()`, and
`getMetadata()` plus the `MultiCollector` variants) in favor of
`getMetricFamilyDescriptor()` / `getMetricFamilyDescriptors()`. The
deprecated methods remain bridged by default implementations for
compatibility.
Related validation:
- #2121 validates unmodified Micrometer independently of #2114, against
`main` + #2124.
- #2123 validates a Micrometer branch that explicitly uses
`MetricFamilyDescriptor` to implement the existing registration metadata
hooks without invoking scrape/sample callbacks during registration.
---------
Signed-off-by: Gregor Zeitlinger <gregor.zeitlinger@grafana.com>
Signed-off-by: Jay DeLuca <jaydeluca4@gmail.com>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Same metric name, different set of label names

3 participants

@jaydeluca@zeitlinger