diff --git a/.codespellrc b/.codespellrc new file mode 100644 index 000000000..e4dbb3a7f --- /dev/null +++ b/.codespellrc @@ -0,0 +1,5 @@ +[codespell] +# Ignore words that are valid technical terms: +# - vertx: Vert.x reactive framework +# - errorprone: Error Prone static analysis tool +ignore-words-list = vertx,errorprone diff --git a/.editorconfig b/.editorconfig index 69d2a4d5f..80219f95e 100644 --- a/.editorconfig +++ b/.editorconfig @@ -4,15 +4,19 @@ root = true max_line_length = 100 indent_size = 2 -[{version-rules.xml,maven-wrapper.properties,checkstyle.xml,docker-compose.yaml,docker-compose.yml,Dockerfile,example_target_info.json,mise.toml,mvnm,mvnw.cmd,generate-protobuf.sh,super-linter.env,.gitleaksignore}] +[{version-rules.xml,maven-wrapper.properties,checkstyle.xml,docker-compose.yaml,docker-compose.yml,Dockerfile,example_target_info.json,mise.toml,mvnm,mvnw.cmd,generate-protobuf.sh,.gitleaksignore,prometheus.properties}] max_line_length = 200 -[{grafana-dashboard-*.json,.editorconfig}] +[{grafana-dashboard-*.json,.editorconfig,super-linter.env,lychee.toml,renovate.json5}] max_line_length = 300 [pom.xml] -max_line_length = 110 +max_line_length = 120 [*.py] # checked by black indent_size = 4 +max_line_length = 120 + +[{.mise/tasks/build-release.sh,.github/workflows/multi-version-test.yml}] +max_line_length = 200 diff --git a/.github/config/.release-please-manifest.json b/.github/config/.release-please-manifest.json new file mode 100644 index 000000000..dd8fde779 --- /dev/null +++ b/.github/config/.release-please-manifest.json @@ -0,0 +1,3 @@ +{ + ".": "1.5.0" +} diff --git a/.github/config/lychee.toml b/.github/config/lychee.toml new file mode 100644 index 000000000..864350fb5 --- /dev/null +++ b/.github/config/lychee.toml @@ -0,0 +1,30 @@ +# Lychee configuration file +# See https://lychee.cli.rs/config/ + +timeout = 30 +retry_wait_time = 5 +max_retries = 6 +max_concurrency = 4 + +# Check link anchors +include_fragments = true + +base_url = "https://prometheus.github.io" +exclude_path = ["docs/themes"] + +exclude = [ + # excluding links to pull requests and issues is done for performance + "^https://github.com/prometheus/client_java/(issues|pull)/\\d+$", + + # exclude localhost URLs as they require running services + "^http://localhost", + "^https://localhost", + + '#', + 'CONTRIBUTING.md', + 'LICENSE', + 'MAINTAINERS.md', + + # exclude private GitHub settings pages + "^https://github.com/prometheus/client_java/settings/", +] diff --git a/.github/config/release-please-config.json b/.github/config/release-please-config.json new file mode 100644 index 000000000..0182b5204 --- /dev/null +++ b/.github/config/release-please-config.json @@ -0,0 +1,14 @@ +{ + "$schema": "https://raw.githubusercontent.com/googleapis/release-please/main/schemas/config.json", + "pull-request-footer": "> [!IMPORTANT]\n> Close and reopen this PR to trigger CI checks.", + "packages": { + ".": { + "release-type": "java", + "versioning": "always-bump-patch", + "extra-files": [ + "prometheus-metrics-parent/pom.xml", + "integration-tests/it-spring-boot-smoke-test/pom.xml" + ] + } + } +} diff --git a/.github/super-linter.env b/.github/config/super-linter.env similarity index 71% rename from .github/super-linter.env rename to .github/config/super-linter.env index 826989701..566f1bfb3 100644 --- a/.github/super-linter.env +++ b/.github/config/super-linter.env @@ -1,6 +1,11 @@ -FILTER_REGEX_EXCLUDE=mvnw|src/main/generated/.*|docs/themes/.*|keystore.pkcs12|.*.java|prometheus-metrics-exporter-opentelemetry-shaded/pom.xml|CODE_OF_CONDUCT.md +FILTER_REGEX_EXCLUDE=mvnw|src/main/generated/.*|docs/themes/.*|keystore.pkcs12|.*.java|prometheus-metrics-exporter-opentelemetry-shaded/pom.xml|CODE_OF_CONDUCT.md|CLAUDE.md IGNORE_GITIGNORED_FILES=true JAVA_FILE_NAME=google_checks.xml +LOG_LEVEL=ERROR +# conflicts with prettier +VALIDATE_BIOME_FORMAT=false +# conflicts with prettier +VALIDATE_BIOME_LINT=false # disable kubernetes linter - complains about resource limits, etc VALIDATE_CHECKOV=false VALIDATE_CSS=false @@ -14,13 +19,16 @@ VALIDATE_GO_MODULES=false VALIDATE_HTML=false # done by checkstyle VALIDATE_JAVA=false -# contradicting with prettier -VALIDATE_JAVASCRIPT_STANDARD=false # we have many duplicate code in our codebase for demo purposes VALIDATE_JSCPD=false VALIDATE_PYTHON_PYLINT=false +# conflicts with black +VALIDATE_PYTHON_RUFF_FORMAT=false +# excluding simpleclient-archive doesn't seem to work +VALIDATE_TRIVY=false FIX_ENV=true +FIX_GITHUB_ACTIONS_ZIZMOR=true FIX_GO=true FIX_JAVASCRIPT_PRETTIER=true FIX_JSON=true @@ -31,4 +39,5 @@ FIX_MARKDOWN=true FIX_MARKDOWN_PRETTIER=true FIX_PYTHON_BLACK=true FIX_SHELL_SHFMT=true +FIX_SPELL_CODESPELL=true FIX_YAML_PRETTIER=true diff --git a/.github/renovate-tracked-deps.json b/.github/renovate-tracked-deps.json new file mode 100644 index 000000000..d3089ecb7 --- /dev/null +++ b/.github/renovate-tracked-deps.json @@ -0,0 +1,560 @@ +{ + ".github/renovate.json5": { + "renovate-config-presets": ["grafana/flint"] + }, + ".mise/envs/native/mise.toml": { + "mise": ["java"] + }, + ".mvn/wrapper/maven-wrapper.properties": { + "maven-wrapper": ["maven"] + }, + "benchmarks/pom.xml": { + "maven": [ + "com.codahale.metrics:metrics-core", + "io.prometheus:client_java", + "io.prometheus:prometheus-metrics-core", + "io.prometheus:prometheus-metrics-exposition-textformats", + "io.prometheus:simpleclient", + "org.openjdk.jmh:jmh-core", + "org.openjdk.jmh:jmh-generator-annprocess" + ] + }, + "integration-tests/it-common/pom.xml": { + "maven": [ + "io.prometheus:integration-tests", + "io.prometheus:prometheus-metrics-exposition-formats" + ] + }, + "integration-tests/it-exporter/it-exporter-duplicate-metrics-sample/pom.xml": { + "maven": [ + "io.prometheus:it-exporter", + "io.prometheus:prometheus-metrics-core", + "io.prometheus:prometheus-metrics-exporter-httpserver" + ] + }, + "integration-tests/it-exporter/it-exporter-httpserver-sample/pom.xml": { + "maven": [ + "io.prometheus:it-exporter", + "io.prometheus:prometheus-metrics-core", + "io.prometheus:prometheus-metrics-exporter-httpserver" + ] + }, + "integration-tests/it-exporter/it-exporter-no-protobuf/pom.xml": { + "maven": [ + "io.prometheus:it-exporter", + "io.prometheus:prometheus-metrics-core", + "io.prometheus:prometheus-metrics-exporter-httpserver" + ] + }, + "integration-tests/it-exporter/it-exporter-servlet-jetty-sample/pom.xml": { + "maven": [ + "io.prometheus:it-exporter", + "io.prometheus:prometheus-metrics-core", + "io.prometheus:prometheus-metrics-exporter-servlet-jakarta", + "org.eclipse.jetty.ee10:jetty-ee10-servlet", + "org.eclipse.jetty:jetty-server" + ] + }, + "integration-tests/it-exporter/it-exporter-servlet-tomcat-sample/pom.xml": { + "maven": [ + "io.prometheus:it-exporter", + "io.prometheus:prometheus-metrics-core", + "io.prometheus:prometheus-metrics-exporter-servlet-jakarta", + "org.apache.tomcat.embed:tomcat-embed-core" + ] + }, + "integration-tests/it-exporter/it-exporter-test/pom.xml": { + "maven": ["io.prometheus:it-common", "io.prometheus:it-exporter"] + }, + "integration-tests/it-exporter/it-no-protobuf-test/pom.xml": { + "maven": ["io.prometheus:it-common", "io.prometheus:it-exporter"] + }, + "integration-tests/it-exporter/pom.xml": { + "maven": ["io.prometheus:integration-tests"] + }, + "integration-tests/it-pushgateway/pom.xml": { + "maven": [ + "com.jayway.jsonpath:json-path", + "com.squareup.okhttp:okhttp", + "io.prometheus:integration-tests", + "io.prometheus:it-common", + "io.prometheus:prometheus-metrics-core", + "io.prometheus:prometheus-metrics-exporter-pushgateway" + ] + }, + "integration-tests/it-spring-boot-smoke-test/pom.xml": { + "maven": [ + "com.diffplug.spotless:spotless-maven-plugin", + "io.prometheus:it-common", + "io.prometheus:prometheus-metrics-bom", + "org.junit:junit-bom", + "org.springframework.boot:spring-boot-starter-parent" + ] + }, + "integration-tests/pom.xml": { + "maven": [ + "commons-io:commons-io", + "io.prometheus:client_java", + "org.testcontainers:junit-jupiter" + ] + }, + "mise.toml": { + "mise": [ + "go:github.com/gohugoio/hugo", + "go:github.com/grafana/oats", + "java", + "lychee", + "node", + "npm:renovate", + "protoc" + ], + "regex": ["ghcr.io/super-linter/super-linter", "grafana/flint"] + }, + "mvnw": { + "maven-wrapper": ["maven-wrapper"] + }, + "mvnw.cmd": { + "maven-wrapper": ["maven-wrapper"] + }, + "pom.xml": { + "maven": [ + "com.google.code.findbugs:jsr305", + "com.google.errorprone:error_prone_core", + "com.google.guava:guava", + "com.google.protobuf:protobuf-java", + "com.uber.nullaway:nullaway", + "io.opentelemetry.instrumentation:opentelemetry-instrumentation-bom-alpha", + "io.opentelemetry:opentelemetry-proto", + "io.prometheus:client_java_parent", + "org.apache.felix:maven-bundle-plugin", + "org.apache.maven.plugins:maven-checkstyle-plugin", + "org.apache.maven.plugins:maven-clean-plugin", + "org.apache.maven.plugins:maven-compiler-plugin", + "org.apache.maven.plugins:maven-dependency-plugin", + "org.apache.maven.plugins:maven-deploy-plugin", + "org.apache.maven.plugins:maven-enforcer-plugin", + "org.apache.maven.plugins:maven-failsafe-plugin", + "org.apache.maven.plugins:maven-install-plugin", + "org.apache.maven.plugins:maven-jar-plugin", + "org.apache.maven.plugins:maven-javadoc-plugin", + "org.apache.maven.plugins:maven-resources-plugin", + "org.apache.maven.plugins:maven-shade-plugin", + "org.apache.maven.plugins:maven-site-plugin", + "org.apache.maven.plugins:maven-surefire-plugin", + "org.assertj:assertj-core", + "org.awaitility:awaitility", + "org.codehaus.mojo:build-helper-maven-plugin", + "org.codehaus.mojo:exec-maven-plugin", + "org.codehaus.mojo:versions-maven-plugin", + "org.jacoco:jacoco-maven-plugin", + "org.junit-pioneer:junit-pioneer", + "org.junit.jupiter:junit-jupiter", + "org.junit.jupiter:junit-jupiter-params", + "org.junit:junit-bom", + "org.mockito:mockito-core", + "org.slf4j:slf4j-simple", + "org.wiremock:wiremock" + ] + }, + "prometheus-metrics-bom/pom.xml": { + "maven": [ + "io.prometheus:client_java_parent", + "io.prometheus:prometheus-metrics-config", + "io.prometheus:prometheus-metrics-core", + "io.prometheus:prometheus-metrics-exporter-common", + "io.prometheus:prometheus-metrics-exporter-httpserver", + "io.prometheus:prometheus-metrics-exporter-opentelemetry", + "io.prometheus:prometheus-metrics-exporter-opentelemetry-no-otel", + "io.prometheus:prometheus-metrics-exporter-opentelemetry-otel-agent-resources", + "io.prometheus:prometheus-metrics-exporter-pushgateway", + "io.prometheus:prometheus-metrics-exporter-servlet-jakarta", + "io.prometheus:prometheus-metrics-exporter-servlet-javax", + "io.prometheus:prometheus-metrics-exposition-formats", + "io.prometheus:prometheus-metrics-exposition-formats-no-protobuf", + "io.prometheus:prometheus-metrics-exposition-textformats", + "io.prometheus:prometheus-metrics-instrumentation-caffeine", + "io.prometheus:prometheus-metrics-instrumentation-dropwizard", + "io.prometheus:prometheus-metrics-instrumentation-dropwizard5", + "io.prometheus:prometheus-metrics-instrumentation-guava", + "io.prometheus:prometheus-metrics-instrumentation-jvm", + "io.prometheus:prometheus-metrics-model", + "io.prometheus:prometheus-metrics-otel-support", + "io.prometheus:prometheus-metrics-simpleclient-bridge", + "io.prometheus:prometheus-metrics-tracer", + "io.prometheus:prometheus-metrics-tracer-common", + "io.prometheus:prometheus-metrics-tracer-initializer", + "io.prometheus:prometheus-metrics-tracer-otel", + "io.prometheus:prometheus-metrics-tracer-otel-agent" + ] + }, + "prometheus-metrics-config/pom.xml": { + "maven": ["io.prometheus:client_java"] + }, + "prometheus-metrics-core/pom.xml": { + "maven": [ + "io.prometheus:client_java", + "io.prometheus:prometheus-metrics-config", + "io.prometheus:prometheus-metrics-exposition-formats-no-protobuf", + "io.prometheus:prometheus-metrics-model", + "io.prometheus:prometheus-metrics-tracer-initializer", + "org.apache.commons:commons-math3" + ] + }, + "prometheus-metrics-exporter-common/pom.xml": { + "maven": [ + "io.prometheus:client_java", + "io.prometheus:prometheus-metrics-core", + "io.prometheus:prometheus-metrics-exposition-formats", + "io.prometheus:prometheus-metrics-exposition-textformats", + "io.prometheus:prometheus-metrics-model" + ] + }, + "prometheus-metrics-exporter-httpserver/pom.xml": { + "maven": ["io.prometheus:client_java", "io.prometheus:prometheus-metrics-exporter-common"] + }, + "prometheus-metrics-exporter-opentelemetry-otel-agent-resources/pom.xml": { + "maven": [ + "io.opentelemetry:opentelemetry-api", + "io.opentelemetry:opentelemetry-context", + "io.prometheus:client_java" + ] + }, + "prometheus-metrics-exporter-opentelemetry-shaded/pom.xml": { + "maven": [ + "io.prometheus:client_java", + "io.prometheus:prometheus-metrics-core", + "io.prometheus:prometheus-metrics-exporter-opentelemetry-otel-agent-resources" + ] + }, + "prometheus-metrics-exporter-opentelemetry/pom.xml": { + "maven": [ + "io.prometheus:client_java", + "io.prometheus:prometheus-metrics-core", + "io.prometheus:prometheus-metrics-exporter-opentelemetry-otel-agent-resources" + ] + }, + "prometheus-metrics-exporter-pushgateway/pom.xml": { + "maven": [ + "io.prometheus:client_java", + "io.prometheus:prometheus-metrics-core", + "io.prometheus:prometheus-metrics-exporter-common", + "org.mock-server:mockserver-netty-no-dependencies" + ] + }, + "prometheus-metrics-exporter-servlet-jakarta/pom.xml": { + "maven": [ + "io.prometheus:client_java", + "io.prometheus:prometheus-metrics-core", + "io.prometheus:prometheus-metrics-exporter-common", + "jakarta.servlet:jakarta.servlet-api" + ] + }, + "prometheus-metrics-exporter-servlet-javax/pom.xml": { + "maven": [ + "io.prometheus:client_java", + "io.prometheus:prometheus-metrics-core", + "io.prometheus:prometheus-metrics-exporter-common", + "javax.servlet:javax.servlet-api" + ] + }, + "prometheus-metrics-exposition-formats-shaded/pom.xml": { + "maven": [ + "io.prometheus:client_java", + "io.prometheus:prometheus-metrics-exposition-textformats" + ] + }, + "prometheus-metrics-exposition-formats/pom.xml": { + "maven": [ + "io.prometheus:client_java", + "io.prometheus:prometheus-metrics-exposition-textformats" + ] + }, + "prometheus-metrics-exposition-textformats/pom.xml": { + "maven": [ + "io.prometheus:client_java", + "io.prometheus:prometheus-metrics-config", + "io.prometheus:prometheus-metrics-model" + ] + }, + "prometheus-metrics-instrumentation-caffeine/pom.xml": { + "maven": [ + "com.github.ben-manes.caffeine:caffeine", + "io.prometheus:client_java", + "io.prometheus:prometheus-metrics-core", + "io.prometheus:prometheus-metrics-exposition-textformats" + ] + }, + "prometheus-metrics-instrumentation-dropwizard/pom.xml": { + "maven": [ + "io.dropwizard.metrics:metrics-core", + "io.prometheus:client_java", + "io.prometheus:prometheus-metrics-core", + "io.prometheus:prometheus-metrics-exporter-httpserver", + "io.prometheus:prometheus-metrics-exposition-textformats", + "io.prometheus:prometheus-metrics-instrumentation-dropwizard5" + ] + }, + "prometheus-metrics-instrumentation-dropwizard5/pom.xml": { + "maven": [ + "io.dropwizard.metrics5:metrics-core", + "io.prometheus:client_java", + "io.prometheus:prometheus-metrics-core", + "io.prometheus:prometheus-metrics-exporter-httpserver", + "io.prometheus:prometheus-metrics-exposition-textformats" + ] + }, + "prometheus-metrics-instrumentation-guava/pom.xml": { + "maven": [ + "io.prometheus:client_java", + "io.prometheus:prometheus-metrics-core", + "io.prometheus:prometheus-metrics-exposition-textformats" + ] + }, + "prometheus-metrics-instrumentation-jvm/pom.xml": { + "maven": [ + "io.prometheus:client_java", + "io.prometheus:prometheus-metrics-core", + "io.prometheus:prometheus-metrics-exporter-httpserver", + "io.prometheus:prometheus-metrics-exposition-textformats" + ] + }, + "prometheus-metrics-model/pom.xml": { + "maven": ["io.prometheus:client_java", "io.prometheus:prometheus-metrics-config"] + }, + "prometheus-metrics-otel-support/pom.xml": { + "maven": ["io.prometheus:client_java"] + }, + "prometheus-metrics-parent/pom.xml": { + "maven": [ + "com.diffplug.spotless:spotless-maven-plugin", + "org.apache.maven.plugins:maven-gpg-plugin", + "org.apache.maven.plugins:maven-source-plugin", + "org.sonatype.central:central-publishing-maven-plugin" + ] + }, + "prometheus-metrics-simpleclient-bridge/pom.xml": { + "maven": [ + "io.prometheus:client_java", + "io.prometheus:prometheus-metrics-config", + "io.prometheus:prometheus-metrics-exposition-textformats", + "io.prometheus:prometheus-metrics-model", + "io.prometheus:simpleclient", + "io.prometheus:simpleclient_common" + ] + }, + "prometheus-metrics-tracer/pom.xml": { + "maven": ["io.prometheus:client_java"] + }, + "prometheus-metrics-tracer/prometheus-metrics-tracer-common/pom.xml": { + "maven": ["io.prometheus:prometheus-metrics-tracer"] + }, + "prometheus-metrics-tracer/prometheus-metrics-tracer-initializer/pom.xml": { + "maven": [ + "io.prometheus:prometheus-metrics-tracer", + "io.prometheus:prometheus-metrics-tracer-common", + "io.prometheus:prometheus-metrics-tracer-otel", + "io.prometheus:prometheus-metrics-tracer-otel-agent" + ] + }, + "prometheus-metrics-tracer/prometheus-metrics-tracer-otel-agent/pom.xml": { + "maven": [ + "io.prometheus:prometheus-metrics-tracer", + "io.prometheus:prometheus-metrics-tracer-common" + ] + }, + "prometheus-metrics-tracer/prometheus-metrics-tracer-otel/pom.xml": { + "maven": [ + "io.prometheus:prometheus-metrics-tracer", + "io.prometheus:prometheus-metrics-tracer-common" + ] + }, + "simpleclient-archive/integration_tests/it_common/pom.xml": { + "maven": ["io.prometheus:integration_tests"] + }, + "simpleclient-archive/integration_tests/it_exemplars_otel_agent/pom.xml": { + "maven": [ + "ch.qos.logback:logback-classic", + "io.prometheus:integration_tests", + "io.prometheus:it_common", + "io.prometheus:simpleclient_bom", + "io.prometheus:simpleclient_hotspot", + "io.prometheus:simpleclient_servlet", + "org.springframework.boot:spring-boot-dependencies", + "org.springframework.boot:spring-boot-maven-plugin" + ] + }, + "simpleclient-archive/integration_tests/it_exemplars_otel_sdk/pom.xml": { + "maven": [ + "io.opentelemetry:opentelemetry-api", + "io.opentelemetry:opentelemetry-sdk", + "io.prometheus:integration_tests", + "io.prometheus:it_common", + "io.prometheus:simpleclient", + "io.prometheus:simpleclient_httpserver" + ] + }, + "simpleclient-archive/integration_tests/it_java_versions/pom.xml": { + "maven": [ + "io.prometheus:integration_tests", + "io.prometheus:it_common", + "io.prometheus:simpleclient", + "io.prometheus:simpleclient_hotspot", + "io.prometheus:simpleclient_httpserver" + ] + }, + "simpleclient-archive/integration_tests/it_log4j2/pom.xml": { + "maven": [ + "io.prometheus:integration_tests", + "io.prometheus:it_common", + "io.prometheus:simpleclient", + "io.prometheus:simpleclient_httpserver", + "io.prometheus:simpleclient_log4j2", + "org.apache.logging.log4j:log4j-api", + "org.apache.logging.log4j:log4j-core" + ] + }, + "simpleclient-archive/integration_tests/it_pushgateway/pom.xml": { + "maven": [ + "ch.qos.logback:logback-classic", + "com.squareup.okhttp3:okhttp", + "io.prometheus:integration_tests", + "io.prometheus:it_common", + "io.prometheus:simpleclient", + "io.prometheus:simpleclient_pushgateway" + ] + }, + "simpleclient-archive/integration_tests/it_servlet_jakarta_exporter_webxml/pom.xml": { + "maven": [ + "ch.qos.logback:logback-classic", + "com.squareup.okhttp3:okhttp", + "io.prometheus:integration_tests", + "io.prometheus:simpleclient", + "io.prometheus:simpleclient_hotspot", + "io.prometheus:simpleclient_servlet_jakarta", + "jakarta.servlet:jakarta.servlet-api", + "org.apache.maven.plugins:maven-war-plugin" + ] + }, + "simpleclient-archive/integration_tests/pom.xml": { + "maven": [ + "ch.qos.logback:logback-classic", + "com.squareup.okhttp3:okhttp", + "io.prometheus:client_java", + "org.testcontainers:testcontainers" + ] + }, + "simpleclient-archive/simpleclient_graphite_bridge/pom.xml": { + "maven": ["io.prometheus:client_java", "io.prometheus:simpleclient"] + }, + "simpleclient-archive/simpleclient_hibernate/pom.xml": { + "maven": [ + "io.prometheus:client_java", + "io.prometheus:simpleclient", + "org.hibernate:hibernate-core" + ] + }, + "simpleclient-archive/simpleclient_httpserver/pom.xml": { + "maven": [ + "io.prometheus:client_java", + "io.prometheus:simpleclient", + "io.prometheus:simpleclient_common", + "javax.xml.bind:jaxb-api" + ] + }, + "simpleclient-archive/simpleclient_jetty/pom.xml": { + "maven": [ + "io.prometheus:client_java", + "io.prometheus:simpleclient", + "org.eclipse.jetty:jetty-server", + "org.eclipse.jetty:jetty-servlet", + "org.hamcrest:hamcrest-all" + ] + }, + "simpleclient-archive/simpleclient_jetty_jdk8/pom.xml": { + "maven": [ + "io.prometheus:client_java", + "io.prometheus:simpleclient", + "org.eclipse.jetty:jetty-server", + "org.eclipse.jetty:jetty-servlet", + "org.hamcrest:hamcrest-all" + ] + }, + "simpleclient-archive/simpleclient_log4j/pom.xml": { + "maven": [ + "io.prometheus:client_java", + "io.prometheus:simpleclient", + "org.apache.logging.log4j:log4j-1.2-api", + "org.apache.logging.log4j:log4j-core" + ] + }, + "simpleclient-archive/simpleclient_log4j2/pom.xml": { + "maven": [ + "io.prometheus:client_java", + "io.prometheus:simpleclient", + "org.apache.logging.log4j:log4j-core" + ] + }, + "simpleclient-archive/simpleclient_logback/pom.xml": { + "maven": [ + "ch.qos.logback:logback-classic", + "io.prometheus:client_java", + "io.prometheus:simpleclient" + ] + }, + "simpleclient-archive/simpleclient_servlet/pom.xml": { + "maven": [ + "io.prometheus:client_java", + "io.prometheus:simpleclient", + "io.prometheus:simpleclient_common", + "io.prometheus:simpleclient_servlet_common", + "javax.servlet:javax.servlet-api", + "org.eclipse.jetty:jetty-servlet" + ] + }, + "simpleclient-archive/simpleclient_servlet_common/pom.xml": { + "maven": [ + "io.prometheus:client_java", + "io.prometheus:simpleclient", + "io.prometheus:simpleclient_common" + ] + }, + "simpleclient-archive/simpleclient_servlet_jakarta/pom.xml": { + "maven": [ + "io.prometheus:client_java", + "io.prometheus:simpleclient", + "io.prometheus:simpleclient_common", + "io.prometheus:simpleclient_servlet_common", + "jakarta.servlet:jakarta.servlet-api", + "org.eclipse.jetty:jetty-servlet" + ] + }, + "simpleclient-archive/simpleclient_spring_web/pom.xml": { + "maven": [ + "io.prometheus:client_java", + "io.prometheus:simpleclient", + "io.prometheus:simpleclient_common", + "org.apache.commons:commons-lang3", + "org.aspectj:aspectjweaver", + "org.springframework:spring-aop", + "org.springframework:spring-context", + "org.springframework:spring-test", + "org.springframework:spring-web" + ] + }, + "simpleclient-archive/simpleclient_vertx/pom.xml": { + "maven": [ + "io.prometheus:client_java", + "io.prometheus:simpleclient", + "io.prometheus:simpleclient_common", + "io.vertx:vertx-web" + ] + }, + "simpleclient-archive/simpleclient_vertx4/pom.xml": { + "maven": [ + "io.prometheus:client_java", + "io.prometheus:simpleclient", + "io.prometheus:simpleclient_common", + "io.vertx:vertx-web" + ] + } +} diff --git a/.github/renovate.json5 b/.github/renovate.json5 index 3b6beecd9..ff4363ba5 100644 --- a/.github/renovate.json5 +++ b/.github/renovate.json5 @@ -1,12 +1,37 @@ { $schema: "https://docs.renovatebot.com/renovate-schema.json", - extends: ["config:best-practices", "config:recommended"], + extends: ["config:best-practices", "config:recommended", "github>grafana/flint"], platformCommit: "enabled", automerge: true, ignorePaths: [ - "**/simpleclient-archive/**", // old projects + "**/simpleclient-archive/**", + // old projects // agent resources packages an OTel API that is the minimum required API version "**/prometheus-metrics-exporter-opentelemetry-otel-agent-resources/pom.xml", ], labels: ["dependencies"], + packageRules: [ + { + matchFileNames: ["mise.toml"], + matchDepNames: ["java"], + groupName: "java temurin", + additionalBranchPrefix: "temurin-", + }, + { + matchFileNames: [".mise/envs/native/mise.toml"], + matchDepNames: ["java"], + groupName: "java graalvm", + additionalBranchPrefix: "graalvm-", + }, + { + matchPackageNames: ["io.opentelemetry.instrumentation:opentelemetry-instrumentation-bom-alpha"], + ignoreUnstable: false, + }, + { + enabled: false, + description: "Ignore internal project modules", + matchPackageNames: ["/^io\\.prometheus:(examples|example-.+|integration-tests|it-.+)$/"], + }, + ], + customManagers: [], } diff --git a/.github/workflows/acceptance-tests.yml b/.github/workflows/acceptance-tests.yml index 2a9b9bd22..977973f3b 100644 --- a/.github/workflows/acceptance-tests.yml +++ b/.github/workflows/acceptance-tests.yml @@ -12,7 +12,7 @@ jobs: - name: Check out with: persist-credentials: false - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 - - uses: jdx/mise-action@bfb9fa0b029db830a8c570757cee683df207a6c5 # v2.4.0 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + - uses: jdx/mise-action@6d1e696aa24c1aa1bcc1adea0212707c71ab78a8 # v3.6.1 - name: Run acceptance tests run: mise run acceptance-test diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 906e98fe8..e5b1980e8 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -9,12 +9,12 @@ jobs: build: runs-on: ubuntu-24.04 steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: persist-credentials: false - - uses: jdx/mise-action@bfb9fa0b029db830a8c570757cee683df207a6c5 # v2.4.0 + - uses: jdx/mise-action@6d1e696aa24c1aa1bcc1adea0212707c71ab78a8 # v3.6.1 - name: Cache local Maven repository - uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4 + uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3 with: path: ~/.m2/repository key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} diff --git a/.github/workflows/github-pages.yaml b/.github/workflows/github-pages.yaml index ce5469d31..37cc39f37 100644 --- a/.github/workflows/github-pages.yaml +++ b/.github/workflows/github-pages.yaml @@ -32,12 +32,12 @@ jobs: if: github.repository == 'prometheus/client_java' runs-on: ubuntu-24.04 steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: persist-credentials: false fetch-tags: "true" fetch-depth: 0 - - uses: jdx/mise-action@bfb9fa0b029db830a8c570757cee683df207a6c5 # v2.4.0 + - uses: jdx/mise-action@6d1e696aa24c1aa1bcc1adea0212707c71ab78a8 # v3.6.1 with: cache: "false" - name: Setup Pages @@ -48,7 +48,7 @@ jobs: env: BASE_URL: "${{ steps.pages.outputs.base_url }}/" - name: Upload artifact - uses: actions/upload-pages-artifact@56afc609e74202658d3ffba0e8f6dda462b719fa # v3 + uses: actions/upload-pages-artifact@7b1f4a764d45c48632c6b24a0339c27f5614fb0b # v4 with: path: ./docs/public # Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages diff --git a/.github/workflows/java-version-matrix-tests.yml b/.github/workflows/java-version-matrix-tests.yml new file mode 100644 index 000000000..d476e7bb7 --- /dev/null +++ b/.github/workflows/java-version-matrix-tests.yml @@ -0,0 +1,81 @@ +--- +name: Integration Tests - Java Version Compatibility Matrix + +on: + pull_request: + paths: + - 'integration-tests/**' + - 'prometheus-metrics-core/**' + - 'prometheus-metrics-exporter-*/**' + - 'prometheus-metrics-exposition-*/**' + - '.github/workflows/java-version-matrix-tests.yml' + push: + branches: + - main + workflow_dispatch: + +permissions: {} + +jobs: + integration-tests: + name: Java ${{ matrix.java-version }} + runs-on: ubuntu-24.04 + strategy: + fail-fast: false + matrix: + # Note: Java 8 runtime testing is skipped due to Spotless incompatibility + java-version: [11, 17, 21, 25] + steps: + - name: Check out + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + + - name: Set up mise + uses: jdx/mise-action@6d1e696aa24c1aa1bcc1adea0212707c71ab78a8 # v3.6.1 + + - name: Cache local Maven repository + uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3 + with: + path: ~/.m2/repository + key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} + restore-keys: | + ${{ runner.os }}-maven- + + - name: Build core library artifacts + run: mise exec -- ./mvnw install -DskipTests -Dspotless.check.skip=true -Dcoverage.skip=true -Dcheckstyle.skip=true -Dwarnings=-nowarn -pl '!integration-tests' + + - name: Install parent POMs + run: | + cd integration-tests + mise exec -- ../mvnw clean install -N -Dspotless.skip=true + cd it-exporter + mise exec -- ../../mvnw install -N -Dspotless.skip=true + + - name: Rebuild sample apps targeting Java ${{ matrix.java-version }} + run: | + cd integration-tests + # Note: Jetty 12 and Tomcat 11 require Java 17+, so servlet samples are skipped for Java 11 + if [ "${{ matrix.java-version }}" = "11" ]; then + MODULES="it-common,it-exporter/it-exporter-httpserver-sample,it-exporter/it-exporter-no-protobuf,it-pushgateway" + else + MODULES="it-common,it-exporter/it-exporter-httpserver-sample,it-exporter/it-exporter-servlet-tomcat-sample,it-exporter/it-exporter-servlet-jetty-sample,it-exporter/it-exporter-no-protobuf,it-pushgateway" + fi + mise exec -- ../mvnw clean install -DskipTests -Dspotless.skip=true -Dcoverage.skip=true -Dcheckstyle.skip=true -Dwarnings=-nowarn \ + -Djava.version=${{ matrix.java-version }} \ + -Dmaven.compiler.release=${{ matrix.java-version }} \ + -pl $MODULES + + - name: Run integration tests + env: + TEST_JAVA_VERSION: ${{ matrix.java-version }} + run: | + cd integration-tests + # Note: Servlet tests require Java 17+ (due to Jetty 12 and Tomcat 11) + if [ "${{ matrix.java-version }}" = "11" ]; then + TEST_MODULES="it-exporter/it-no-protobuf-test,it-pushgateway" + else + TEST_MODULES="it-exporter/it-exporter-test,it-exporter/it-no-protobuf-test,it-pushgateway" + fi + mise exec -- ../mvnw verify -T 2C -Dspotless.skip=true -Dcoverage.skip=true -Dcheckstyle.skip=true -Dwarnings=-nowarn \ + -pl $TEST_MODULES diff --git a/.github/workflows/lint-rest.yml b/.github/workflows/lint-rest.yml deleted file mode 100644 index d7e6172d9..000000000 --- a/.github/workflows/lint-rest.yml +++ /dev/null @@ -1,18 +0,0 @@ ---- -name: Lint What Super Linter Can't - -on: [pull_request] - -permissions: {} - -jobs: - lint: - runs-on: ubuntu-24.04 - steps: - - name: Check out - with: - persist-credentials: false - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - - uses: jdx/mise-action@bfb9fa0b029db830a8c570757cee683df207a6c5 # v2.4.0 - - name: Lint - run: mise run lint-rest diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml new file mode 100644 index 000000000..c6a2575c1 --- /dev/null +++ b/.github/workflows/lint.yml @@ -0,0 +1,31 @@ +--- +name: Lint + +on: + pull_request: + +permissions: {} + +jobs: + lint: + runs-on: ubuntu-24.04 + + permissions: + contents: read + + steps: + - name: Checkout code + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + fetch-depth: 0 # needed for git diff --merge-base in lint:links + + - name: Setup mise + uses: jdx/mise-action@6d1e696aa24c1aa1bcc1adea0212707c71ab78a8 # v3.6.1 + + - name: Lint + env: + GITHUB_TOKEN: ${{ github.token }} + GITHUB_HEAD_SHA: ${{ github.event.pull_request.head.sha }} + PR_HEAD_REPO: ${{ github.event.pull_request.head.repo.full_name }} + run: mise run lint diff --git a/.github/workflows/multi-version-test.yml b/.github/workflows/multi-version-test.yml new file mode 100644 index 000000000..92318c1aa --- /dev/null +++ b/.github/workflows/multi-version-test.yml @@ -0,0 +1,39 @@ +--- +name: Java-Version Compatibility Tests + +on: [pull_request] + +permissions: {} + +jobs: + compatibility-test: + name: Test on Java ${{ matrix.java }} + runs-on: ubuntu-24.04 + strategy: + fail-fast: false + matrix: + java: [17, 21, 25] + steps: + - name: Check out + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + with: + persist-credentials: false + + - name: Set up Java ${{ matrix.java }} + id: setup-java + uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5 + with: + distribution: "temurin" + java-version: ${{ matrix.java }} + + - name: Cache local Maven repository + uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3 + with: + path: ~/.m2/repository + key: ${{ runner.os }}-maven-java${{ matrix.java }}-${{ hashFiles('**/pom.xml') }} + restore-keys: | + ${{ runner.os }}-maven-java${{ matrix.java }}- + ${{ runner.os }}-maven- + + - name: Build and test on Java ${{ matrix.java }} + run: ./mvnw clean install -Dtest.java.version=${{ matrix.java }} -Dspotless.skip=true -Dcheckstyle.skip=true -Dwarnings=-nowarn -Dcoverage.skip=true diff --git a/.github/workflows/native-tests.yml b/.github/workflows/native-tests.yml index 51a5a7fb4..b93edab06 100644 --- a/.github/workflows/native-tests.yml +++ b/.github/workflows/native-tests.yml @@ -12,9 +12,10 @@ jobs: - name: Check out with: persist-credentials: false - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 - - uses: jdx/mise-action@bfb9fa0b029db830a8c570757cee683df207a6c5 # v2.4.0 - env: - MISE_ENV: native + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + - uses: jdx/mise-action@6d1e696aa24c1aa1bcc1adea0212707c71ab78a8 # v3.6.1 + with: + working_directory: .mise/envs/native - name: Run native tests - run: mise run test + working-directory: .mise/envs/native + run: mise native-test diff --git a/.github/workflows/nightly-benchmarks.yml b/.github/workflows/nightly-benchmarks.yml new file mode 100644 index 000000000..4ee4adbc5 --- /dev/null +++ b/.github/workflows/nightly-benchmarks.yml @@ -0,0 +1,99 @@ +--- +name: Nightly Benchmarks + +on: + schedule: + # Run at 2 AM UTC every day + - cron: "0 2 * * *" + workflow_dispatch: + inputs: + jmh_args: + description: "Additional JMH arguments (e.g., '-f 1 -wi 1 -i 3' for quick run)" + required: false + default: "" + +permissions: {} + +concurrency: + group: "benchmarks" + +defaults: + run: + shell: bash + +jobs: + benchmark: + runs-on: ubuntu-24.04 + permissions: + contents: write + steps: + - name: Checkout main branch + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: true + fetch-depth: 0 + + - name: Setup mise + uses: jdx/mise-action@6d1e696aa24c1aa1bcc1adea0212707c71ab78a8 # v3.6.1 + + - name: Cache local Maven repository + uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3 + with: + path: ~/.m2/repository + key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} + restore-keys: | + ${{ runner.os }}-maven- + + - name: Run JMH benchmarks + run: mise run benchmark:ci-json + env: + JMH_ARGS: ${{ github.event.inputs.jmh_args }} + + - name: Generate benchmark summary + run: | + mise run benchmark:generate-summary \ + --input benchmark-results.json \ + --output-dir benchmark-results \ + --commit-sha "${{ github.sha }}" + env: + GITHUB_REPOSITORY: ${{ github.repository }} + + - name: Commit and push results to benchmarks branch + run: | + # Save results to a temp location + mkdir -p /tmp/benchmark-output + cp -r benchmark-results/* /tmp/benchmark-output/ + + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + + # Checkout or create benchmarks branch (use -- to disambiguate from benchmarks/ directory) + if git ls-remote --heads origin benchmarks | grep -q benchmarks; then + git fetch origin benchmarks + git switch benchmarks + # Preserve existing history + if [ -d history ]; then + cp -r history /tmp/benchmark-output/ + fi + else + git switch --orphan benchmarks + fi + + # Clean working directory + git rm -rf . 2>/dev/null || true + find . -mindepth 1 -maxdepth 1 ! -name '.git' -exec rm -rf {} + + + # Copy only the benchmark results + cp -r /tmp/benchmark-output/* . + + git add README.md results.json history/ + + DATE=$(date -u +"%Y-%m-%d") + COMMIT_SHORT=$(echo "${{ github.sha }}" | cut -c1-7) + + git commit \ + -m "Benchmark results for ${DATE} (${COMMIT_SHORT})" \ + -m "From commit ${{ github.sha }}" \ + || echo "No changes to commit" + + git push origin benchmarks --force-with-lease || git push origin benchmarks diff --git a/.github/workflows/pr-title.yml b/.github/workflows/pr-title.yml new file mode 100644 index 000000000..76a13e3df --- /dev/null +++ b/.github/workflows/pr-title.yml @@ -0,0 +1,18 @@ +--- +name: PR Title + +on: + pull_request: + types: + - opened + - edited +permissions: + pull-requests: read + +jobs: + lint: + runs-on: ubuntu-latest + steps: + - uses: amannn/action-semantic-pull-request@48f256284bd46cdaab1048c3721360e808335d50 # v6.1.1 + env: + GITHUB_TOKEN: ${{ github.token }} diff --git a/.github/workflows/release-please.yml b/.github/workflows/release-please.yml new file mode 100644 index 000000000..75cf34f16 --- /dev/null +++ b/.github/workflows/release-please.yml @@ -0,0 +1,23 @@ +--- +name: Release Please + +on: + push: + branches: + - main + +permissions: + contents: write + pull-requests: write + +jobs: + release-please: + if: ${{ github.repository == 'prometheus/client_java' }} + runs-on: ubuntu-24.04 + steps: + - uses: googleapis/release-please-action@16a9c90856f42705d54a6fda1823352bdc62cf38 # v4.4.0 + id: release-please + with: + token: ${{ secrets.GITHUB_TOKEN }} + config-file: .github/config/release-please-config.json + manifest-file: .github/config/.release-please-manifest.json diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index f358c1571..93d32c188 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -20,26 +20,22 @@ jobs: echo "${#GPG_SIGNING_KEY}" echo "${GPG_SIGNING_KEY}" | gpg --batch --import-options import-show --import - name: Checkout Plugin Repository - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: persist-credentials: false - - name: Set Up JDK - uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4 + - uses: jdx/mise-action@6d1e696aa24c1aa1bcc1adea0212707c71ab78a8 # v3.6.1 with: - java-version: 17 - distribution: temurin + cache: false - - name: Build with Maven - run: ./scripts/build-release.sh - env: - TAG: ${{ github.ref_name }} + - name: Build release version + run: mise run build-release - name: Set up Apache Maven Central - uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4 + uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5 with: distribution: "temurin" - java-version: "17" + java-version: "21" server-id: ossrh server-username: MAVEN_USERNAME server-password: MAVEN_CENTRAL_TOKEN diff --git a/.github/workflows/super-linter.yml b/.github/workflows/super-linter.yml deleted file mode 100644 index 0a8a093db..000000000 --- a/.github/workflows/super-linter.yml +++ /dev/null @@ -1,30 +0,0 @@ ---- -name: Lint - -on: [pull_request] - -jobs: - lint: - runs-on: ubuntu-24.04 - - permissions: - contents: read - packages: read - # To report GitHub Actions status checks - statuses: write - - steps: - - name: Checkout code - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - with: - persist-credentials: false - fetch-depth: 0 - - - name: Load super-linter configuration - run: grep -v '^#' .github/super-linter.env | grep -v 'FIX_' >> "$GITHUB_ENV" - - - name: Super-linter - uses: super-linter/super-linter@5119dcd8011e92182ce8219d9e9efc82f16fddb6 # v8.0.0 - env: - # To report GitHub Actions status checks - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/test-release-build.yml b/.github/workflows/test-release-build.yml index dfe0d0359..f8de00d8c 100644 --- a/.github/workflows/test-release-build.yml +++ b/.github/workflows/test-release-build.yml @@ -13,19 +13,22 @@ jobs: build: runs-on: ubuntu-24.04 steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: persist-credentials: false - - uses: jdx/mise-action@bfb9fa0b029db830a8c570757cee683df207a6c5 # v2.4.0 + fetch-tags: "true" + fetch-depth: 0 + - uses: jdx/mise-action@6d1e696aa24c1aa1bcc1adea0212707c71ab78a8 # v3.6.1 - name: Cache local Maven repository - uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4 + uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3 with: path: ~/.m2/repository key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} restore-keys: | ${{ runner.os }}-maven- - - name: Run the Maven verify phase - run: ./scripts/build-release.sh + - name: Build GitHub Pages + run: mise run build-gh-pages env: - # don't ues the current snapshot version, to test a more realistic release - TAG: ${{ github.run_number }} + BASE_URL: "/client_java" + - name: Build release version + run: mise run build-release diff --git a/.gitignore b/.gitignore index b727017a9..b98fa5703 100644 --- a/.gitignore +++ b/.gitignore @@ -20,3 +20,9 @@ dependency-reduced-pom.xml **/.settings/ docs/public .lycheecache + +benchmark-results/ +benchmark-results.json +benchmark-output.log + +*.DS_Store \ No newline at end of file diff --git a/.idea/icon.svg b/.idea/icon.svg new file mode 100644 index 000000000..5c51f66d9 --- /dev/null +++ b/.idea/icon.svg @@ -0,0 +1,50 @@ + + + +image/svg+xml \ No newline at end of file diff --git a/.mise/envs/native/mise.toml b/.mise/envs/native/mise.toml new file mode 100644 index 000000000..de9c06a68 --- /dev/null +++ b/.mise/envs/native/mise.toml @@ -0,0 +1,8 @@ +[tools] +java = "oracle-graalvm-25.0.1" + +[tasks.native-test] +depends = "build" +run = "../../mvnw test -PnativeTest" +dir = "../../../integration-tests/it-spring-boot-smoke-test" + diff --git a/.mise/tasks/build-release.sh b/.mise/tasks/build-release.sh new file mode 100755 index 000000000..620dca77a --- /dev/null +++ b/.mise/tasks/build-release.sh @@ -0,0 +1,8 @@ +#!/usr/bin/env bash + +#MISE description="Build release package" + +set -euo pipefail + +mvn -B package -P 'release,!default,!examples-and-integration-tests' \ + -Dmaven.test.skip=true -Dgpg.skip=true diff --git a/.mise/tasks/generate_benchmark_summary.py b/.mise/tasks/generate_benchmark_summary.py new file mode 100644 index 000000000..0b0c4fb01 --- /dev/null +++ b/.mise/tasks/generate_benchmark_summary.py @@ -0,0 +1,378 @@ +#!/usr/bin/env python3 + +# [MISE] description="Generate markdown summary from JMH benchmark JSON results" +# [MISE] alias="generate-benchmark-summary" + +""" +Generate a markdown summary from JMH benchmark JSON results. + +Usage: + python3 .mise/tasks/generate_benchmark_summary.py [--input results.json] [--output-dir ./benchmark-results] + +This script: +1. Reads JMH JSON output +2. Generates a README.md with formatted tables +3. Copies results to the output directory with historical naming +""" + +import argparse +import json +import os +import shutil +import sys +from datetime import datetime, timezone +from pathlib import Path +from typing import Dict, List, Optional + + +def parse_args(): + parser = argparse.ArgumentParser( + description="Generate benchmark summary from JMH JSON" + ) + parser.add_argument( + "--input", + default="benchmark-results.json", + help="Path to JMH JSON results file (default: benchmark-results.json)", + ) + parser.add_argument( + "--output-dir", + default="benchmark-results", + help="Output directory for results (default: benchmark-results)", + ) + parser.add_argument( + "--commit-sha", + default=None, + help="Git commit SHA (default: read from git or 'local')", + ) + return parser.parse_args() + + +def get_system_info() -> Dict[str, str]: + """Capture system hardware information.""" + import multiprocessing + import platform + + info = {} + + try: + info["cpu_cores"] = str(multiprocessing.cpu_count()) + except Exception: + pass + + try: + with open("/proc/cpuinfo", "r") as f: + for line in f: + if line.startswith("model name"): + info["cpu_model"] = line.split(":")[1].strip() + break + except FileNotFoundError: + # macOS + try: + import subprocess + + result = subprocess.run( + ["sysctl", "-n", "machdep.cpu.brand_string"], + capture_output=True, + text=True, + timeout=5, + ) + if result.returncode == 0: + info["cpu_model"] = result.stdout.strip() + except Exception: + pass + + try: + with open("/proc/meminfo", "r") as f: + for line in f: + if line.startswith("MemTotal"): + kb = int(line.split()[1]) + info["memory_gb"] = str(round(kb / 1024 / 1024)) + break + except FileNotFoundError: + # macOS + try: + import subprocess + + result = subprocess.run( + ["sysctl", "-n", "hw.memsize"], + capture_output=True, + text=True, + timeout=5, + ) + if result.returncode == 0: + bytes_mem = int(result.stdout.strip()) + info["memory_gb"] = str(round(bytes_mem / 1024 / 1024 / 1024)) + except Exception: + pass + + info["os"] = f"{platform.system()} {platform.release()}" + + return info + + +def get_commit_sha(provided_sha: Optional[str]) -> str: + """Get commit SHA from argument, git, or return 'local'.""" + if provided_sha: + return provided_sha + + try: + import subprocess + + result = subprocess.run( + ["git", "rev-parse", "HEAD"], + capture_output=True, + text=True, + timeout=5, + ) + if result.returncode == 0: + return result.stdout.strip() + except Exception: + pass + + return "local" + + +def format_score(score) -> str: + """Format score with appropriate precision.""" + try: + val = float(score) + if val >= 1_000_000: + return f"{val / 1_000_000:.2f}M" + elif val >= 1_000: + return f"{val / 1_000:.2f}K" + else: + return f"{val:.2f}" + except (ValueError, TypeError): + return str(score) + + +def format_error(error) -> str: + """Format error value, handling NaN.""" + try: + error_val = float(error) + if error_val != error_val: # NaN check + return "" + elif error_val >= 1_000: + return f"± {error_val / 1_000:.2f}K" + else: + return f"± {error_val:.2f}" + except (ValueError, TypeError): + return "" + + +def generate_markdown(results: List, commit_sha: str, repo: str) -> str: + """Generate markdown summary from JMH results.""" + commit_short = commit_sha[:7] + datetime_str = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ") + + # Extract metadata from first result + first = results[0] if results else {} + jdk_version = first.get("jdkVersion", "unknown") + vm_name = first.get("vmName", "unknown") + threads = first.get("threads", "?") + forks = first.get("forks", "?") + warmup_iters = first.get("warmupIterations", "?") + measure_iters = first.get("measurementIterations", "?") + + sysinfo = get_system_info() + + md = [] + md.append("# Prometheus Java Client Benchmarks") + md.append("") + + md.append("## Run Information") + md.append("") + md.append(f"- **Date:** {datetime_str}") + if commit_sha != "local": + md.append( + f"- **Commit:** [`{commit_short}`](https://github.com/{repo}/commit/{commit_sha})" + ) + else: + md.append(f"- **Commit:** `{commit_short}` (local run)") + md.append(f"- **JDK:** {jdk_version} ({vm_name})") + bench_cfg = f"{forks} fork(s), {warmup_iters} warmup, {measure_iters} measurement, {threads} threads" + md.append(f"- **Benchmark config:** {bench_cfg}") + + hw_parts = [] + if sysinfo.get("cpu_model"): + hw_parts.append(sysinfo["cpu_model"]) + if sysinfo.get("cpu_cores"): + hw_parts.append(f"{sysinfo['cpu_cores']} cores") + if sysinfo.get("memory_gb"): + hw_parts.append(f"{sysinfo['memory_gb']} GB RAM") + if hw_parts: + md.append(f"- **Hardware:** {', '.join(hw_parts)}") + if sysinfo.get("os"): + md.append(f"- **OS:** {sysinfo['os']}") + + md.append("") + + # Group by benchmark class + benchmarks_by_class: Dict[str, List] = {} + for b in results: + name = b.get("benchmark", "") + parts = name.rsplit(".", 1) + if len(parts) == 2: + class_name, method = parts + class_short = class_name.split(".")[-1] + else: + class_short = "Other" + benchmarks_by_class.setdefault(class_short, []).append(b) + + md.append("## Results") + md.append("") + + # Generate table for each class + for class_name in sorted(benchmarks_by_class.keys()): + benchmarks = benchmarks_by_class[class_name] + md.append(f"### {class_name}") + md.append("") + + # Sort by score descending + sorted_benchmarks = sorted( + benchmarks, + key=lambda x: x.get("primaryMetric", {}).get("score", 0), + reverse=True, + ) + + md.append("| Benchmark | Score | Error | Units | |") + md.append("|:----------|------:|------:|:------|:---|") + + best_score = ( + sorted_benchmarks[0].get("primaryMetric", {}).get("score", 1) + if sorted_benchmarks + else 1 + ) + + for i, b in enumerate(sorted_benchmarks): + name = b.get("benchmark", "").split(".")[-1] + score = b.get("primaryMetric", {}).get("score", 0) + error = b.get("primaryMetric", {}).get("scoreError", 0) + unit = b.get("primaryMetric", {}).get("scoreUnit", "ops/s") + + score_fmt = format_score(score) + error_fmt = format_error(error) + + # Calculate relative performance as multiplier + try: + if i == 0: + relative_fmt = "**fastest**" + else: + multiplier = float(best_score) / float(score) + if multiplier >= 10: + relative_fmt = f"{multiplier:.0f}x slower" + else: + relative_fmt = f"{multiplier:.1f}x slower" + except (ValueError, TypeError, ZeroDivisionError): + relative_fmt = "" + + md.append( + f"| {name} | {score_fmt} | {error_fmt} | {unit} | {relative_fmt} |" + ) + + md.append("") + + md.append("### Raw Results") + md.append("") + md.append("```") + md.append( + f"{'Benchmark':<50} {'Mode':>6} {'Cnt':>4} {'Score':>14} {'Error':>12} Units" + ) + + for b in sorted(results, key=lambda x: x.get("benchmark", "")): + name = b.get("benchmark", "").replace("io.prometheus.metrics.benchmarks.", "") + mode = b.get("mode", "thrpt") + cnt = b.get("measurementIterations", 0) * b.get("forks", 1) + score = b.get("primaryMetric", {}).get("score", 0) + error = b.get("primaryMetric", {}).get("scoreError", 0) + unit = b.get("primaryMetric", {}).get("scoreUnit", "ops/s") + + try: + score_str = f"{float(score):.3f}" + except (ValueError, TypeError): + score_str = str(score) + + try: + error_val = float(error) + if error_val != error_val: # NaN + error_str = "" + else: + error_str = f"± {error_val:.3f}" + except (ValueError, TypeError): + error_str = "" + + md.append( + f"{name:<50} {mode:>6} {cnt:>4} {score_str:>14} {error_str:>12} {unit}" + ) + + md.append("```") + md.append("") + + md.append("## Notes") + md.append("") + md.append("- **Score** = Throughput in operations per second (higher is better)") + md.append("- **Error** = 99.9% confidence interval") + md.append("") + + md.append("## Benchmark Descriptions") + md.append("") + md.append("| Benchmark | Description |") + md.append("|:----------|:------------|") + md.append( + "| **CounterBenchmark** | Counter increment performance: " + "Prometheus, OpenTelemetry, simpleclient, Codahale |" + ) + md.append( + "| **HistogramBenchmark** | Histogram observation performance " + "(classic vs native/exponential) |" + ) + md.append( + "| **TextFormatUtilBenchmark** | Metric exposition format writing speed |" + ) + md.append("") + return "\n".join(md) + + +def main(): + args = parse_args() + + input_path = Path(args.input) + if not input_path.exists(): + print(f"Error: Input file not found: {input_path}") + sys.exit(1) + + print(f"Reading results from: {input_path}") + with open(input_path, "r") as f: + results = json.load(f) + + print(f"Found {len(results)} benchmark results") + + commit_sha = get_commit_sha(args.commit_sha) + commit_short = commit_sha[:7] + repo = os.environ.get("GITHUB_REPOSITORY", "prometheus/client_java") + + output_dir = Path(args.output_dir) + output_dir.mkdir(parents=True, exist_ok=True) + history_dir = output_dir / "history" + history_dir.mkdir(parents=True, exist_ok=True) + + results_json_path = output_dir / "results.json" + shutil.copy(input_path, results_json_path) + print(f"Copied results to: {results_json_path}") + + date_str = datetime.now(timezone.utc).strftime("%Y-%m-%d") + history_path = history_dir / f"{date_str}-{commit_short}.json" + shutil.copy(input_path, history_path) + print(f"Saved historical entry: {history_path}") + + markdown = generate_markdown(results, commit_sha, repo) + readme_path = output_dir / "README.md" + with open(readme_path, "w") as f: + f.write(markdown) + print(f"Generated summary: {readme_path}") + + print(f"\nDone! Results are in: {output_dir}/") + + +if __name__ == "__main__": + main() diff --git a/.mise/tasks/lint/bom.py b/.mise/tasks/lint/bom.py new file mode 100755 index 000000000..d77b88e23 --- /dev/null +++ b/.mise/tasks/lint/bom.py @@ -0,0 +1,117 @@ +#!/usr/bin/env python3 + +# [MISE] description="Make sure the BOM has all necessary modules" + +import difflib +import re +import sys +from fnmatch import fnmatch +from pathlib import Path +from typing import List + +ROOT = Path(__file__).resolve().parents[3] # repo root (.. from .mise/tasks/lint) +IGNORE_DIRS = {"prometheus-metrics-parent"} +MODULE_PREFIX = "prometheus-metrics" +BOM_POM = ROOT / "prometheus-metrics-bom" / "pom.xml" + + +def first_artifact_id(pom_file: Path) -> str: + """Return the second value from the given pom.xml (matches original script). + + The original shell function greps all lines and returns the second one + (head -n 2 | tail -n 1). We replicate that behavior exactly. + """ + if not pom_file.is_file(): + raise FileNotFoundError(f"File {pom_file} does not exist.") + + text = pom_file.read_text(encoding="utf-8") + matches = re.findall(r"\s*(.*?)\s*", text) + if len(matches) < 2: + return "" + return matches[1].strip() + + +def add_dir(dir_path: Path, want: List[str]): + if not dir_path.is_dir(): + raise FileNotFoundError(f"Directory {dir_path} does not exist.") + + if any(dir_path.name == ig for ig in IGNORE_DIRS): + return + + pom = dir_path / "pom.xml" + if not pom.is_file(): + raise FileNotFoundError(f"File {pom} does not exist.") + + artifact_id = first_artifact_id(pom) + if not artifact_id: + raise RuntimeError(f"No artifactId found in {pom}") + + want.append(artifact_id) + + +def collect_want(root: Path) -> List[str]: + want: List[str] = [] + # top-level prometheus-metrics* + for entry in sorted(root.iterdir()): + if entry.is_dir() and fnmatch(entry.name, f"{MODULE_PREFIX}*"): + add_dir(entry, want) + + # prometheus-metrics-tracer/prometheus-metrics* + tracer_dir = root / "prometheus-metrics-tracer" + if tracer_dir.is_dir(): + for entry in sorted(tracer_dir.iterdir()): + if entry.is_dir() and fnmatch(entry.name, f"{MODULE_PREFIX}*"): + add_dir(entry, want) + + # deduplicate and sort + want_unique = sorted(set(want)) + return want_unique + + +def collect_have(bom_pom: Path) -> List[str]: + if not bom_pom.is_file(): + raise FileNotFoundError(f"BOM file {bom_pom} does not exist.") + + text = bom_pom.read_text(encoding="utf-8") + # find artifactId values that start with MODULE_PREFIX + matches = re.findall( + r"\s*(%s[^<\s]*)\s*" % re.escape(MODULE_PREFIX), text + ) + return sorted(matches) + + +def main() -> int: + try: + want = collect_want(ROOT) + have = collect_have(BOM_POM) + + want_text = "\n".join(want) + have_text = "\n".join(have) + + if want_text != have_text: + print( + "The BOM file prometheus-metrics-bom/bom.xml does not match the current directory contents." + ) + print("Expected:") + print(want_text) + print("Found:") + print(have_text) + print() + diff = difflib.unified_diff( + have_text.splitlines(keepends=True), + want_text.splitlines(keepends=True), + fromfile="found", + tofile="expected", + ) + sys.stdout.writelines(diff) + return 1 + else: + return 0 + + except Exception as e: + print(e, file=sys.stderr) + return 1 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/.mise/tasks/set-release-version-github-pages.sh b/.mise/tasks/set-release-version-github-pages.sh new file mode 100755 index 000000000..2016373c8 --- /dev/null +++ b/.mise/tasks/set-release-version-github-pages.sh @@ -0,0 +1,12 @@ +#!/usr/bin/env bash + +#MISE description="Set release version in all GitHub Pages docs" + +set -euox pipefail + +version=$(git tag -l | grep 'v' | sort | tail -1 | sed 's/v//') +otelVersion=$(grep -oP '\K[^<]+' pom.xml | sed 's/-alpha$//') + +find ./docs/content -name '*.md' \ + -exec sed -i "s/\$version/$version/g" {} + \ + -exec sed -i "s/\$otelVersion/$otelVersion/g" {} + diff --git a/.mise/tasks/test_update-benchmarks.py b/.mise/tasks/test_update-benchmarks.py new file mode 100644 index 000000000..3d80b4983 --- /dev/null +++ b/.mise/tasks/test_update-benchmarks.py @@ -0,0 +1,98 @@ +import os +import re +import sys +import tempfile +import unittest + +from update_benchmarks import update_pre_blocks_under_module + +# Ensure the tasks directory is importable when running the test directly +here = os.path.dirname(__file__) +if here not in sys.path: + sys.path.insert(0, here) + + +class TestRunBenchmarksFiltering(unittest.TestCase): + def setUp(self): + # sample JMH table with mixed-class lines + self.table = ( + "Benchmark Mode Cnt Score Error Units\n" + "CounterBenchmark.codahaleIncNoLabels thrpt 57881.585 ops/s\n" + "HistogramBenchmark.prometheusNative thrpt 2385.134 ops/s\n" + "TextFormatUtilBenchmark.prometheusWriteToNull thrpt 885331.328 ops/s\n" + "CounterBenchmark.prometheusInc thrpt 54090.469 ops/s\n" + ) + + # create temp dir to act as module path + self.tmpdir = tempfile.TemporaryDirectory() + self.module_path = self.tmpdir.name + + # Create three files with a javadoc
 block that contains mixed results
+        self.files = {}
+        javadoc_pre = (
+            "/**\n"
+            " * Example javadoc\n"
+            " * 
\n"
+            " * Benchmark                                             Mode  Cnt       Score   Error  Units\n"
+            " * CounterBenchmark.codahaleIncNoLabels                 thrpt        57881.585          ops/s\n"
+            " * HistogramBenchmark.prometheusNative                  thrpt         2385.134          ops/s\n"
+            " * TextFormatUtilBenchmark.prometheusWriteToNull        thrpt       885331.328          ops/s\n"
+            " * CounterBenchmark.prometheusInc                       thrpt        54090.469          ops/s\n"
+            " * 
\n" + " */\n" + ) + + for cls in ( + "CounterBenchmark", + "HistogramBenchmark", + "TextFormatUtilBenchmark", + ): + fname = os.path.join(self.module_path, f"{cls}.java") + with open(fname, "w", encoding="utf-8") as f: + f.write(javadoc_pre) + f.write(f"public class {cls} {{}}\n") + self.files[cls] = fname + + def tearDown(self): + self.tmpdir.cleanup() + + def _read_pre_contents(self, path): + with open(path, "r", encoding="utf-8") as f: + content = f.read() + m = re.search(r"
\n([\s\S]*?)
", content) + return m.group(1) if m else "" + + def test_update_only_inserts_matching_class_lines(self): + updated = update_pre_blocks_under_module(self.module_path, self.table) + # All three files should be updated + self.assertEqual( + set(os.path.basename(p) for p in updated), + { + os.path.basename(self.files["CounterBenchmark"]), + os.path.basename(self.files["HistogramBenchmark"]), + os.path.basename(self.files["TextFormatUtilBenchmark"]), + }, + ) + + # Verify CounterBenchmark file contains only CounterBenchmark lines + cb_pre = self._read_pre_contents(self.files["CounterBenchmark"]) + self.assertIn("CounterBenchmark.codahaleIncNoLabels", cb_pre) + self.assertIn("CounterBenchmark.prometheusInc", cb_pre) + self.assertNotIn("HistogramBenchmark.prometheusNative", cb_pre) + self.assertNotIn("TextFormatUtilBenchmark.prometheusWriteToNull", cb_pre) + + # Verify HistogramBenchmark contains only its line + hb_pre = self._read_pre_contents(self.files["HistogramBenchmark"]) + self.assertIn("HistogramBenchmark.prometheusNative", hb_pre) + self.assertNotIn("CounterBenchmark.codahaleIncNoLabels", hb_pre) + self.assertNotIn("TextFormatUtilBenchmark.prometheusWriteToNull", hb_pre) + + # Verify TextFormatUtilBenchmark contains only its line + tf_pre = self._read_pre_contents(self.files["TextFormatUtilBenchmark"]) + self.assertIn("TextFormatUtilBenchmark.prometheusWriteToNull", tf_pre) + self.assertNotIn("CounterBenchmark.prometheusInc", tf_pre) + self.assertNotIn("HistogramBenchmark.prometheusNative", tf_pre) + + +if __name__ == "__main__": + unittest.main() diff --git a/.mise/tasks/update_benchmarks.py b/.mise/tasks/update_benchmarks.py new file mode 100755 index 000000000..3cb550877 --- /dev/null +++ b/.mise/tasks/update_benchmarks.py @@ -0,0 +1,249 @@ +#!/usr/bin/env python3 + +# [MISE] description="Run and update JMH benchmark outputs in the benchmarks module" +# [MISE] alias="update-benchmarks" + +""" +Run benchmarks for the `benchmarks` module, capture JMH text output, and update +any
...
blocks containing "thrpt" under the `benchmarks/` module +(files such as Java sources with embedded example output in javadocs). + +Usage: ./.mise/tasks/update_benchmarks.py [--mvnw ./mvnw] [--module benchmarks] [--java java] + [--jmh-args "-f 1 -wi 0 -i 1"] + +By default this will: + - run the maven wrapper to package the benchmarks: `./mvnw -pl benchmarks -am -DskipTests package` + - locate the shaded jar under `benchmarks/target/` (named containing "benchmarks") + - run `java -jar -rf text` (add extra JMH args with --jmh-args) + - parse the first JMH table (the block starting with the "Benchmark Mode" header) + - update all files under the `benchmarks/` directory which contain a `
` block with the substring "thrpt"
+
+This script is careful to preserve Javadoc comment prefixes like " * " when replacing the
+contents of the 
 block.
+"""
+
+import argparse
+import glob
+import os
+import re
+import shlex
+import subprocess
+import sys
+from typing import List, Optional
+
+
+def run_cmd(cmd: List[str], cwd: Optional[str] = None) -> str:
+    """Run a command, stream stdout/stderr to the console for progress, and return the full output.
+
+    This replaces the previous blocking subprocess.run approach so users can see build / JMH
+    progress in real time while the command runs.
+    """
+    try:
+        proc = subprocess.Popen(
+            cmd, cwd=cwd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True
+        )
+    except FileNotFoundError:
+        # Helpful message if the executable is not found
+        print(f"Command not found: {cmd[0]}")
+        raise
+
+    output_lines: List[str] = []
+    try:
+        assert proc.stdout is not None
+        # Stream lines as they appear and capture them for returning
+        for line in proc.stdout:
+            # Print immediately so callers (and CI) can observe progress
+            print(line, end="")
+            output_lines.append(line)
+        proc.wait()
+    except KeyboardInterrupt:
+        # If the user interrupts, ensure the child process is terminated
+        proc.kill()
+        proc.wait()
+        print("\nCommand interrupted by user.")
+        raise
+
+    output = "".join(output_lines)
+    if proc.returncode != 0:
+        print(
+            f"Command failed: {' '.join(cmd)}\nExit: {proc.returncode}\nOutput:\n{output}"
+        )
+        raise SystemExit(proc.returncode)
+    return output
+
+
+def build_benchmarks(mvnw: str, module: str) -> None:
+    print(f"Building Maven module '{module}' using {mvnw} (this may take a while)...")
+    cmd = [mvnw, "-pl", module, "-am", "-DskipTests", "clean", "package"]
+    run_cmd(cmd)
+    print("Build completed.")
+
+
+def find_benchmarks_jar(module: str) -> str:
+    pattern = os.path.join(module, "target", "*.jar")
+    jars = [p for p in glob.glob(pattern) if "original" not in p and p.endswith(".jar")]
+    # prefer jar whose basename contains module name
+    jars_pref = [j for j in jars if module in os.path.basename(j)]
+    chosen = (jars_pref or jars)[:1]
+    if not chosen:
+        raise FileNotFoundError(
+            f"No jar found in {os.path.join(module, 'target')} (tried: {pattern})"
+        )
+    jar = chosen[0]
+    print(f"Using jar: {jar}")
+    return jar
+
+
+def run_jmh(jar: str, java_cmd: str, extra_args: Optional[str]) -> str:
+    args = [java_cmd, "-jar", jar, "-rf", "text"]
+    if extra_args:
+        args += shlex.split(extra_args)
+    print(f"Running JMH: {' '.join(args)}")
+    output = run_cmd(args)
+    print("JMH run completed.")
+    return output
+
+
+def extract_first_table(jmh_output: str) -> str:
+    # Try to extract the first table that starts with "Benchmark" header and continues until a blank line
+    m = re.search(r"(\nBenchmark\s+Mode[\s\S]*?)(?:\n\s*\n|\Z)", jmh_output)
+    if not m:
+        # fallback: collect all lines that contain 'thrpt' plus a header if present
+        lines = [line for line in jmh_output.splitlines() if "thrpt" in line]
+        if not lines:
+            raise ValueError('Could not find any "thrpt" lines in JMH output')
+        # try to find header
+        header = next(
+            (
+                line
+                for line in jmh_output.splitlines()
+                if line.startswith("Benchmark") and "Mode" in line
+            ),
+            "Benchmark                                     Mode  Cnt      Score     Error  Units",
+        )
+        return header + "\n" + "\n".join(lines)
+    table = m.group(1).strip("\n")
+    # Ensure we return only the table lines (remove any leading iteration info lines that JMH sometimes prints)
+    # Normalize spaces: keep as-is
+    return table
+
+
+def filter_table_for_class(table: str, class_name: str) -> Optional[str]:
+    """
+    Return a table string that contains only the header and the lines belonging to `class_name`.
+    If no matching lines are found, return None.
+    """
+    lines = table.splitlines()
+    # find header line index (starts with 'Benchmark' and contains 'Mode')
+    header_idx = None
+    for i, ln in enumerate(lines):
+        if ln.strip().startswith("Benchmark") and "Mode" in ln:
+            header_idx = i
+            break
+    header = (
+        lines[header_idx]
+        if header_idx is not None
+        else "Benchmark                                     Mode  Cnt      Score     Error  Units"
+    )
+
+    matched = []
+    pattern = re.compile(r"^\s*" + re.escape(class_name) + r"\.")
+    for ln in lines[header_idx + 1 if header_idx is not None else 0 :]:
+        if "thrpt" in ln and pattern.search(ln):
+            matched.append(ln)
+
+    if not matched:
+        return None
+    return header + "\n" + "\n".join(matched)
+
+
+def update_pre_blocks_under_module(module: str, table: str) -> List[str]:
+    # Find files under module and update any 
...
block that contains 'thrpt' + updated_files = [] + for path in glob.glob(os.path.join(module, "**"), recursive=True): + if os.path.isdir(path): + continue + try: + with open(path, "r", encoding="utf-8") as f: + content = f.read() + except Exception: + continue + # quick filter + if "
" not in content or "thrpt" not in content:
+            continue
+
+        original = content
+
+        # Determine the class name from the filename (e.g. TextFormatUtilBenchmark.java -> TextFormatUtilBenchmark)
+        base = os.path.basename(path)
+        class_name = os.path.splitext(base)[0]
+
+        # Build a filtered table for this class; if no matching lines, skip updating this file
+        filtered_table = filter_table_for_class(table, class_name)
+        if filtered_table is None:
+            # nothing to update for this class
+            continue
+
+        # Regex to find any line-starting Javadoc prefix like " * " before 
+        # This will match patterns like: " * 
... 
" and capture the prefix (e.g. " * ") + pattern = re.compile(r"(?m)^(?P[ \t]*\*[ \t]*)
[\s\S]*?
") + + def repl(m: re.Match) -> str: + prefix = m.group("prefix") + # Build the new block with the same prefix on each line + lines = filtered_table.splitlines() + replaced = prefix + "
\n"
+            for ln in lines:
+                replaced += prefix + ln.rstrip() + "\n"
+            replaced += prefix + "
" + return replaced + + new_content, nsubs = pattern.subn(repl, content) + if nsubs > 0 and new_content != original: + with open(path, "w", encoding="utf-8") as f: + f.write(new_content) + updated_files.append(path) + print(f"Updated {path}: replaced {nsubs}
 block(s)")
+    return updated_files
+
+
+def main(argv: List[str]):
+    parser = argparse.ArgumentParser()
+    parser.add_argument("--mvnw", default="./mvnw", help="Path to maven wrapper")
+    parser.add_argument(
+        "--module", default="benchmarks", help="Module directory to build/run"
+    )
+    parser.add_argument("--java", default="java", help="Java command")
+    parser.add_argument(
+        "--jmh-args",
+        default="",
+        help='Extra arguments to pass to the JMH main (e.g. "-f 1 -wi 0 -i 1")',
+    )
+    args = parser.parse_args(argv)
+
+    build_benchmarks(args.mvnw, args.module)
+    jar = find_benchmarks_jar(args.module)
+    output = run_jmh(jar, args.java, args.jmh_args)
+
+    # Print a short preview of the JMH output
+    preview = "\n".join(output.splitlines()[:120])
+    print("\n--- JMH output preview ---")
+    print(preview)
+    print("--- end preview ---\n")
+
+    table = extract_first_table(output)
+
+    updated = update_pre_blocks_under_module(args.module, table)
+
+    if not updated:
+        print(
+            'No files were updated (no 
 blocks with "thrpt" found under the module).'
+        )
+    else:
+        print("\nUpdated files:")
+        for p in updated:
+            print(" -", p)
+
+
+if __name__ == "__main__":
+    main(sys.argv[1:])
diff --git a/.mvn/wrapper/maven-wrapper.properties b/.mvn/wrapper/maven-wrapper.properties
index 12fbe1e90..5f1f57004 100644
--- a/.mvn/wrapper/maven-wrapper.properties
+++ b/.mvn/wrapper/maven-wrapper.properties
@@ -1,19 +1,2 @@
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements.  See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership.  The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License.  You may obtain a copy of the License at
-#
-#   http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing,
-# software distributed under the License is distributed on an
-# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-# KIND, either express or implied.  See the License for the
-# specific language governing permissions and limitations
-# under the License.
-wrapperVersion=3.3.2
 distributionType=only-script
-distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.11/apache-maven-3.9.11-bin.zip
+distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.12/apache-maven-3.9.12-bin.zip
diff --git a/CLAUDE.md b/CLAUDE.md
new file mode 100644
index 000000000..3e5ed1592
--- /dev/null
+++ b/CLAUDE.md
@@ -0,0 +1,133 @@
+# CLAUDE.md
+
+This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
+
+## Build Commands
+
+This project uses Maven with mise for task automation. The Maven wrapper (`./mvnw`) is used for all builds.
+
+```bash
+# Full CI build (clean + install + all checks)
+mise run ci
+
+# Quick compile without tests or checks (fastest for development)
+mise run compile
+
+# Run unit tests only (skips formatting/coverage/checkstyle checks)
+mise run test
+
+# Run all tests including integration tests
+mise run test-all
+
+# Format code with Google Java Format
+mise run format
+# or directly: ./mvnw spotless:apply
+
+# Run a single test class
+./mvnw test -Dtest=CounterTest -Dspotless.check.skip=true -Dcoverage.skip=true -Dcheckstyle.skip=true
+
+# Run a single test method
+./mvnw test -Dtest=CounterTest#testIncrement -Dspotless.check.skip=true -Dcoverage.skip=true -Dcheckstyle.skip=true
+
+# Run tests in a specific module
+./mvnw test -pl prometheus-metrics-core -Dspotless.check.skip=true -Dcoverage.skip=true -Dcheckstyle.skip=true
+
+# Regenerate protobuf classes (after protobuf dependency update)
+mise run generate
+```
+
+## Architecture
+
+The library follows a layered architecture where metrics flow from core types through a registry to exporters:
+
+```
+prometheus-metrics-core (user-facing API)
+         │
+         ▼ collect()
+prometheus-metrics-model (immutable snapshots)
+         │
+         ▼
+prometheus-metrics-exposition-formats (text/protobuf/OpenMetrics)
+         │
+         ▼
+Exporters (httpserver, servlet, pushgateway, opentelemetry)
+```
+
+### Key Modules
+
+- **prometheus-metrics-core**: User-facing metric types (Counter, Gauge, Histogram, Summary, Info, StateSet). All metrics implement the `Collector` interface with a `collect()` method.
+- **prometheus-metrics-model**: Internal read-only immutable snapshot types returned by `collect()`. Contains `PrometheusRegistry` for metric registration.
+- **prometheus-metrics-config**: Runtime configuration via properties files or system properties.
+- **prometheus-metrics-exposition-formats**: Converts snapshots to Prometheus exposition formats.
+- **prometheus-metrics-tracer**: Exemplar support with OpenTelemetry tracing integration.
+- **prometheus-metrics-simpleclient-bridge**: Allows legacy simpleclient 0.16.0 metrics to work with the new registry.
+
+### Instrumentation Modules
+
+Pre-built instrumentations: `prometheus-metrics-instrumentation-jvm`, `-caffeine`, `-guava`, `-dropwizard`, `-dropwizard5`.
+
+## Code Style
+
+- **Formatter**: Google Java Format (enforced via Spotless)
+- **Line length**: 100 characters (enforced for ALL files including Markdown, Java, YAML, etc.)
+- **Indentation**: 2 spaces
+- **Static analysis**: Error Prone with NullAway (`io.prometheus.metrics` package)
+- **Logger naming**: Logger fields must be named `logger` (not `log`, `LOG`, or `LOGGER`)
+- **Assertions in tests**: Use static imports from AssertJ (`import static org.assertj.core.api.Assertions.assertThat`)
+- **Empty catch blocks**: Use `ignored` as the exception variable name
+- **Markdown code blocks**: Always specify language (e.g., ` ```java`, ` ```bash`, ` ```text`)
+
+## Linting and Validation
+
+**CRITICAL**: These checks MUST be run before creating any commits. CI will fail if these checks fail.
+
+### Java Files
+
+- **ALWAYS** run `mise run build` after modifying Java files to ensure:
+  - Code formatting (Spotless with Google Java Format)
+  - Static analysis (Error Prone with NullAway)
+  - Checkstyle validation
+  - Build succeeds (tests are skipped; run `mise run test` or `mise run test-all` to execute tests)
+
+### Non-Java Files (Markdown, YAML, JSON, shell scripts, etc.)
+
+- **ALWAYS** run `mise run lint` after modifying non-Java files
+  (runs super-linter + link checking + BOM check)
+- `mise run fix` auto-fixes lint issues
+- Super-linter will **auto-fix** many issues (formatting, trailing whitespace, etc.)
+- It only reports ERROR-level issues (configured via `LOG_LEVEL=ERROR` in `.github/super-linter.env`)
+- Common issues caught:
+  - Lines exceeding 100 characters in Markdown files
+  - Missing language tags in fenced code blocks
+  - Table formatting issues
+  - YAML/JSON syntax errors
+
+### Running Linters
+
+```bash
+# After modifying Java files (run BEFORE committing)
+mise run build
+
+# After modifying non-Java files (run BEFORE committing)
+mise run lint
+# or to auto-fix: mise run fix
+```
+
+## Testing
+
+- JUnit 5 (Jupiter) with `@Test` annotations
+- AssertJ for fluent assertions
+- Mockito for mocking
+- **Test visibility**: Test classes and test methods must be package-protected (no `public` modifier)
+- Integration tests are in `integration-tests/` and run during `verify` phase
+- Acceptance tests use OATs framework: `mise run acceptance-test`
+
+## Documentation
+
+- Docs live under `docs/content/` and use `$version` as a placeholder for the library version
+- When publishing GitHub Pages, `mise run set-release-version-github-pages` replaces `$version` with the latest git tag across all `docs/content/**/*.md` files (the published site is not versioned)
+- Use `$version` for the Prometheus client version and `$otelVersion-alpha` for the OTel instrumentation version — never hardcode them
+
+## Java Version
+
+Source compatibility: Java 8. Tests run on Java 25 (configured in `mise.toml`).
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 50d60bfcc..2abbd91e2 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -19,6 +19,14 @@ the code.
 
 Run `./mvnw spotless:apply` to format the code (only changed files) before committing.
 
+Or run all the linters:
+
+`mise run lint`
+
+To autofix linting issues:
+
+`mise run fix`
+
 ## Running Tests
 
 If you're getting errors when running tests:
@@ -29,7 +37,7 @@ If you're getting errors when running tests:
 ### Running native tests
 
 ```shell
-mise --env native test
+mise --cd .mise/envs/native run native-test
 ```
 
 ### Avoid failures while running tests
@@ -52,12 +60,34 @@ or simply
 mise run compile
 ```
 
+## Version Numbers in Examples
+
+Example `pom.xml` files (under `examples/`) should reference the latest
+**released** version, not a SNAPSHOT. After each release, Renovate
+updates these versions automatically.
+
+Only use a SNAPSHOT version in an example when it demonstrates a new
+feature that has not been released yet.
+
 ## Updating the Protobuf Java Classes
 
+The generated protobuf `Metrics.java` lives in a versioned package
+(e.g., `...generated.com_google_protobuf_4_33_5`) that changes with each
+protobuf release. A stable extending class at
+`...generated/Metrics.java` reexports all types so that consumer code
+only imports from the version-free package. On protobuf upgrades only
+the `extends` clause in the stable class changes.
+
 In the failing PR from renovate, run:
 
 ```shell
 mise run generate
 ```
 
-Add the new `Metrics.java` to Git and commit it.
+The script will:
+
+1. Re-generate the protobuf sources with the new version.
+2. Update the versioned package name in all Java files
+   (including the stable `Metrics.java` extends clause).
+
+Add the updated files to Git and commit them.
diff --git a/RELEASING.md b/RELEASING.md
index d98ae94b1..25f2d59a5 100644
--- a/RELEASING.md
+++ b/RELEASING.md
@@ -1,20 +1,58 @@
-# Create a Release
+# Releasing Instructions for Prometheus Java Client
 
-1. Go to 
-2. Click on "Choose a tag", enter the tag name (e.g. `v0.1.0`), and click "Create a new tag".
-3. Click on "Generate release notes" to auto-generate the release notes based on the commits since
-   the last release.
-4. Click on "Publish release".
+Releases are automated via
+[release-please](https://github.com/googleapis/release-please).
 
-## If the GPG key expired
+## How It Works
+
+1. Commits to `main` using
+   [Conventional Commits](https://www.conventionalcommits.org/) are
+   tracked by release-please.
+2. Release-please maintains a release PR that accumulates changes and
+   updates the changelog.
+3. When the release PR is merged, release-please creates a GitHub
+   release and a `vX.Y.Z` tag.
+4. The tag triggers the existing `release.yml` workflow, which deploys
+   to Maven Central.
+5. After tagging, release-please opens a follow-up PR to bump the
+   SNAPSHOT version in all `pom.xml` files.
+
+## Patch Release (default)
+
+Simply merge the release PR — release-please bumps the patch version
+by default (e.g. `1.5.0` -> `1.5.1`).
+
+## Minor or Major Release
+
+Add a `release-as: X.Y.0` footer to any commit on `main`:
+
+```text
+feat: add new feature
+
+release-as: 1.6.0
+```
+
+Alternatively, edit the release PR title to
+`chore(main): release 1.6.0`.
+
+## Before the Release
+
+If there have been significant changes since the last release, update
+the benchmarks before merging the release PR:
+
+```shell
+mise run update-benchmarks
+```
+
+## If the GPG Key Expired
 
 1. Generate a new key:
    
-2. Distribute the
-   key: 
-3. use `gpg --armor --export-secret-keys YOUR_ID` to
-   export ([docs](https://github.com/actions/setup-java/blob/main/docs/advanced-usage.md#gpg))
-4. Update the
-   passphrase:  
-5. Update the GPG
-   key: 
+2. Distribute the key:
+   
+3. Use `gpg --armor --export-secret-keys YOUR_ID` to export
+   ([docs](https://github.com/actions/setup-java/blob/main/docs/advanced-usage.md#gpg))
+4. Update the passphrase:
+   
+5. Update the GPG key:
+   
diff --git a/benchmarks/README.md b/benchmarks/README.md
index cb445c7bf..b4c824d85 100644
--- a/benchmarks/README.md
+++ b/benchmarks/README.md
@@ -2,6 +2,27 @@
 
 ## How to Run
 
+### Running benchmarks
+
+Run benchmarks and update the results in the Javadoc of the benchmark classes:
+
+```shell
+mise run update-benchmarks
+```
+
+### Different benchmark configurations
+
+The full benchmark suite takes approximately 2 hours with JMH defaults.
+For faster iterations, use these preset configurations:
+
+| Command                       | Duration | Use Case                                 |
+| ----------------------------- | -------- | ---------------------------------------- |
+| `mise run benchmark:quick`    | ~10 min  | Quick smoke test during development      |
+| `mise run benchmark:standard` | ~60 min  | CI/nightly runs with good accuracy       |
+| `mise run benchmark:full`     | ~2 hours | Full JMH defaults for release validation |
+
+### Running benchmarks manually
+
 ```shell
 java -jar ./benchmarks/target/benchmarks.jar
 ```
@@ -12,12 +33,33 @@ Run only one specific benchmark:
 java -jar ./benchmarks/target/benchmarks.jar CounterBenchmark
 ```
 
+### Custom JMH arguments
+
+You can pass custom JMH arguments:
+
+```shell
+# Quick run: 1 fork, 1 warmup iteration, 3 measurement iterations
+mise run update-benchmarks -- --jmh-args "-f 1 -wi 1 -i 3"
+
+# Standard CI: 3 forks, 3 warmup iterations, 5 measurement iterations
+mise run update-benchmarks -- --jmh-args "-f 3 -wi 3 -i 5"
+```
+
+JMH parameter reference:
+
+- `-f N`: Number of forks (JVM restarts)
+- `-wi N`: Number of warmup iterations
+- `-i N`: Number of measurement iterations
+- `-w Ns`: Warmup iteration time (default: 10s)
+- `-r Ns`: Measurement iteration time (default: 10s)
+
 ## Results
 
 See Javadoc of the benchmark classes:
 
-- [CounterBenchmark](https://github.com/prometheus/client_java/blob/1.0.x/benchmarks/src/main/java/io/prometheus/metrics/benchmarks/CounterBenchmark.java) 
-- [HistogramBenchmark](https://github.com/prometheus/client_java/blob/1.0.x/benchmarks/src/main/java/io/prometheus/metrics/benchmarks/HistogramBenchmark.java) 
+- [CounterBenchmark](https://github.com/prometheus/client_java/blob/main/benchmarks/src/main/java/io/prometheus/metrics/benchmarks/CounterBenchmark.java) 
+- [HistogramBenchmark](https://github.com/prometheus/client_java/blob/main/benchmarks/src/main/java/io/prometheus/metrics/benchmarks/HistogramBenchmark.java) 
+- [TextFormatUtilBenchmark](https://github.com/prometheus/client_java/blob/main/benchmarks/src/main/java/io/prometheus/metrics/benchmarks/TextFormatUtilBenchmark.java) 
 
 ## What Prometheus Java client optimizes for
 
@@ -31,6 +73,14 @@ Prometheus client Java metrics support concurrent updates and scrapes. This show
 multiple threads recording data in shared
 metrics.
 
+## Test the benchmark creation script
+
+To test the benchmark creation script, run:
+
+```shell
+python ./.mise/tasks/test_update-benchmarks.py
+```
+
 ## Archive
 
 The `src/main/archive/` directory contains the old benchmarks from 0.16.0 and earlier. It will be
diff --git a/benchmarks/pom.xml b/benchmarks/pom.xml
index 3cdbc09c0..7c211006b 100644
--- a/benchmarks/pom.xml
+++ b/benchmarks/pom.xml
@@ -8,7 +8,7 @@
   
     io.prometheus
     client_java
-    1.4.0-SNAPSHOT
+    1.6.0-SNAPSHOT
   
 
   benchmarks
@@ -23,6 +23,7 @@
     0.16.0
     3.0.2
     true
+    true
   
 
   
@@ -85,8 +86,8 @@
         
           1.8
           1.8
-          
-            
+          
+            
             -parameters
           
           
diff --git a/benchmarks/src/main/java/io/prometheus/metrics/benchmarks/CounterBenchmark.java b/benchmarks/src/main/java/io/prometheus/metrics/benchmarks/CounterBenchmark.java
index f3c1e9309..f5d0a1a0f 100644
--- a/benchmarks/src/main/java/io/prometheus/metrics/benchmarks/CounterBenchmark.java
+++ b/benchmarks/src/main/java/io/prometheus/metrics/benchmarks/CounterBenchmark.java
@@ -18,21 +18,20 @@
 import org.openjdk.jmh.annotations.Threads;
 
 /**
- * Results on a machine with dedicated Core i7 1265U:
+ * Results on a machine with dedicated Ubuntu 24.04 LTS, AMD Ryzen™ 9 7900 × 24, 96.0 GiB RAM:
  *
  * 
- *
- * Benchmark                                     Mode  Cnt      Score     Error  Units
- * CounterBenchmark.codahaleIncNoLabels         thrpt   25  32969.795 ± 1547.775  ops/s
- * CounterBenchmark.openTelemetryAdd            thrpt   25    747.068 ±   93.128  ops/s
- * CounterBenchmark.openTelemetryInc            thrpt   25    760.784 ±   47.595  ops/s
- * CounterBenchmark.openTelemetryIncNoLabels    thrpt   25    824.346 ±   45.131  ops/s
- * CounterBenchmark.prometheusAdd               thrpt   25  28403.000 ±  250.774  ops/s
- * CounterBenchmark.prometheusInc               thrpt   25  38368.142 ±  361.914  ops/s
- * CounterBenchmark.prometheusNoLabelsInc       thrpt   25  35558.069 ± 4020.926  ops/s
- * CounterBenchmark.simpleclientAdd             thrpt   25   4081.152 ±  620.094  ops/s
- * CounterBenchmark.simpleclientInc             thrpt   25   5735.644 ± 1205.329  ops/s
- * CounterBenchmark.simpleclientNoLabelsInc     thrpt   25   6852.563 ±  544.481  ops/s
+ * Benchmark                                             Mode  Cnt       Score       Error  Units
+ * CounterBenchmark.codahaleIncNoLabels                 thrpt   25  144632.191 ±  2778.333  ops/s
+ * CounterBenchmark.openTelemetryAdd                    thrpt   25    2165.775 ±   168.554  ops/s
+ * CounterBenchmark.openTelemetryInc                    thrpt   25    1940.143 ±    86.223  ops/s
+ * CounterBenchmark.openTelemetryIncNoLabels            thrpt   25    1880.089 ±   192.395  ops/s
+ * CounterBenchmark.prometheusAdd                       thrpt   25  122427.789 ±  1377.485  ops/s
+ * CounterBenchmark.prometheusInc                       thrpt   25  183603.131 ±  2812.874  ops/s
+ * CounterBenchmark.prometheusNoLabelsInc               thrpt   25  169733.499 ±   670.495  ops/s
+ * CounterBenchmark.simpleclientAdd                     thrpt   25   13771.151 ±    77.473  ops/s
+ * CounterBenchmark.simpleclientInc                     thrpt   25   14255.342 ±   117.339  ops/s
+ * CounterBenchmark.simpleclientNoLabelsInc             thrpt   25   14175.465 ±    56.575  ops/s
  * 
* * Prometheus counters are faster than counters of other libraries. For example, incrementing a diff --git a/benchmarks/src/main/java/io/prometheus/metrics/benchmarks/HistogramBenchmark.java b/benchmarks/src/main/java/io/prometheus/metrics/benchmarks/HistogramBenchmark.java index a8e6bddb1..9ada40f35 100644 --- a/benchmarks/src/main/java/io/prometheus/metrics/benchmarks/HistogramBenchmark.java +++ b/benchmarks/src/main/java/io/prometheus/metrics/benchmarks/HistogramBenchmark.java @@ -17,15 +17,15 @@ import org.openjdk.jmh.annotations.Threads; /** - * Results on a machine with dedicated Core i7 1265U: + * Results on a machine with dedicated Ubuntu 24.04 LTS, AMD Ryzen™ 9 7900 × 24, 96.0 GiB RAM: * *
- * Benchmark                                     Mode  Cnt      Score     Error  Units
- * HistogramBenchmark.openTelemetryClassic      thrpt   25    390.982 ±   16.058  ops/s
- * HistogramBenchmark.openTelemetryExponential  thrpt   25    320.160 ±   18.056  ops/s
- * HistogramBenchmark.prometheusClassic         thrpt   25   2385.862 ±   34.766  ops/s
- * HistogramBenchmark.prometheusNative          thrpt   25   1947.371 ±   48.193  ops/s
- * HistogramBenchmark.simpleclient              thrpt   25   4324.961 ±   50.938  ops/s
+ * Benchmark                                             Mode  Cnt       Score       Error  Units
+ * HistogramBenchmark.openTelemetryClassic              thrpt   25     968.178 ±    28.582  ops/s
+ * HistogramBenchmark.openTelemetryExponential          thrpt   25     836.000 ±    17.709  ops/s
+ * HistogramBenchmark.prometheusClassic                 thrpt   25    7010.393 ±   683.782  ops/s
+ * HistogramBenchmark.prometheusNative                  thrpt   25    5040.572 ±   284.433  ops/s
+ * HistogramBenchmark.simpleclient                      thrpt   25   10485.462 ±    41.265  ops/s
  * 
* * The simpleclient (i.e. client_java version 0.16.0 and older) histograms perform about the same as diff --git a/benchmarks/src/main/java/io/prometheus/metrics/benchmarks/TextFormatUtilBenchmark.java b/benchmarks/src/main/java/io/prometheus/metrics/benchmarks/TextFormatUtilBenchmark.java index dcacf9a76..ed3ef4246 100644 --- a/benchmarks/src/main/java/io/prometheus/metrics/benchmarks/TextFormatUtilBenchmark.java +++ b/benchmarks/src/main/java/io/prometheus/metrics/benchmarks/TextFormatUtilBenchmark.java @@ -1,5 +1,6 @@ package io.prometheus.metrics.benchmarks; +import io.prometheus.metrics.config.EscapingScheme; import io.prometheus.metrics.expositionformats.ExpositionFormatWriter; import io.prometheus.metrics.expositionformats.OpenMetricsTextFormatWriter; import io.prometheus.metrics.expositionformats.PrometheusTextFormatWriter; @@ -18,6 +19,17 @@ import org.openjdk.jmh.annotations.Scope; import org.openjdk.jmh.annotations.State; +/** + * Results on a machine with dedicated Ubuntu 24.04 LTS, AMD Ryzen™ 9 7900 × 24, 96.0 GiB RAM: + * + *
+ * Benchmark                                             Mode  Cnt       Score       Error  Units
+ * TextFormatUtilBenchmark.openMetricsWriteToByteArray  thrpt   25  826847.708 ± 10941.611  ops/s
+ * TextFormatUtilBenchmark.openMetricsWriteToNull       thrpt   25  847756.101 ±  5299.128  ops/s
+ * TextFormatUtilBenchmark.prometheusWriteToByteArray   thrpt   25  874804.601 ±  9730.060  ops/s
+ * TextFormatUtilBenchmark.prometheusWriteToNull        thrpt   25  910782.719 ± 17617.167  ops/s
+ * 
+ */ public class TextFormatUtilBenchmark { private static final MetricSnapshots SNAPSHOTS; @@ -69,14 +81,15 @@ public OutputStream openMetricsWriteToByteArray(WriterState writerState) throws // avoid growing the array ByteArrayOutputStream byteArrayOutputStream = writerState.byteArrayOutputStream; byteArrayOutputStream.reset(); - OPEN_METRICS_TEXT_FORMAT_WRITER.write(byteArrayOutputStream, SNAPSHOTS); + OPEN_METRICS_TEXT_FORMAT_WRITER.write( + byteArrayOutputStream, SNAPSHOTS, EscapingScheme.ALLOW_UTF8); return byteArrayOutputStream; } @Benchmark public OutputStream openMetricsWriteToNull() throws IOException { OutputStream nullOutputStream = NullOutputStream.INSTANCE; - OPEN_METRICS_TEXT_FORMAT_WRITER.write(nullOutputStream, SNAPSHOTS); + OPEN_METRICS_TEXT_FORMAT_WRITER.write(nullOutputStream, SNAPSHOTS, EscapingScheme.ALLOW_UTF8); return nullOutputStream; } @@ -85,14 +98,15 @@ public OutputStream prometheusWriteToByteArray(WriterState writerState) throws I // avoid growing the array ByteArrayOutputStream byteArrayOutputStream = writerState.byteArrayOutputStream; byteArrayOutputStream.reset(); - PROMETHEUS_TEXT_FORMAT_WRITER.write(byteArrayOutputStream, SNAPSHOTS); + PROMETHEUS_TEXT_FORMAT_WRITER.write( + byteArrayOutputStream, SNAPSHOTS, EscapingScheme.ALLOW_UTF8); return byteArrayOutputStream; } @Benchmark public OutputStream prometheusWriteToNull() throws IOException { OutputStream nullOutputStream = NullOutputStream.INSTANCE; - PROMETHEUS_TEXT_FORMAT_WRITER.write(nullOutputStream, SNAPSHOTS); + PROMETHEUS_TEXT_FORMAT_WRITER.write(nullOutputStream, SNAPSHOTS, EscapingScheme.ALLOW_UTF8); return nullOutputStream; } diff --git a/checkstyle-suppressions.xml b/checkstyle-suppressions.xml index 5f632c578..82e964658 100644 --- a/checkstyle-suppressions.xml +++ b/checkstyle-suppressions.xml @@ -5,5 +5,20 @@ "https://checkstyle.org/dtds/suppressions_1_2.dtd"> - + + + + + + + + + + + + + + diff --git a/docs/content/_index.md b/docs/content/_index.md index 3b8966cf3..28e5165cd 100644 --- a/docs/content/_index.md +++ b/docs/content/_index.md @@ -30,7 +30,7 @@ synchronization. See Javadoc comments in **More Info** The Grafana Labs Blog has a post -[Introducing the Prometheus Java Client 1.0.0](https://grafana.com/blog/2023/09/27/introducing-the-prometheus-java-client-1.0.0/) +[Introducing the Prometheus Java Client 1.0.0](https://grafana.com/blog/2023/09/27/introducing-the-prometheus-java-client-1-0-0/) with a good overview of the release. There will also be a presentation at the [PromCon](https://promcon.io) conference on 29 Sep 2023. diff --git a/docs/content/config/config.md b/docs/content/config/config.md index 833a72653..cd7f7af7b 100644 --- a/docs/content/config/config.md +++ b/docs/content/config/config.md @@ -9,21 +9,21 @@ The Prometheus metrics library provides multiple options how to override configu - Properties file - System properties - -Future releases will add more options, like configuration via environment variables. +- Environment variables Example: ```properties -io.prometheus.exporter.httpServer.port = 9401 +io.prometheus.exporter.http_server.port=9401 ``` The property above changes the port for the [HTTPServer exporter]({{< relref "/exporters/httpserver.md" >}}) to _9401_. -- Properties file: Add the line above to the properties file. -- System properties: Use the command line parameter `-Dio.prometheus.exporter.httpServer.port=9401` -- when starting your application. +- **Properties file**: Add the line above to the properties file. +- **System properties**: Use the command line parameter + `-Dio.prometheus.exporter.http_server.port=9401` when starting your application. +- **Environment variables**: Set `IO_PROMETHEUS_EXPORTER_HTTP_SERVER_PORT=9401` ## Location of the Properties File @@ -34,25 +34,61 @@ The properties file is searched in the following locations: - System property `-Dprometheus.config=/path/to/prometheus.properties`. - Environment variable `PROMETHEUS_CONFIG=/path/to/prometheus.properties`. +## Property Naming Conventions + +Properties use **snake_case** format with underscores separating words +(e.g., `http_server`, `exemplars_enabled`). + +For backward compatibility, camelCase property names are also supported in +properties files and system properties, but snake_case is the preferred format. + +### Environment Variables + +Environment variables follow standard conventions: + +- All uppercase letters: `IO_PROMETHEUS_EXPORTER_HTTP_SERVER_PORT` +- Underscores for all separators (both package and word boundaries) +- Prefix must be `IO_PROMETHEUS` + +The library automatically converts environment variables to the correct property format. + +**Examples:** + +| Environment Variable | Property Equivalent | +| --------------------------------------------- | --------------------------------------------- | +| `IO_PROMETHEUS_METRICS_EXEMPLARS_ENABLED` | `io.prometheus.metrics.exemplars_enabled` | +| `IO_PROMETHEUS_EXPORTER_HTTP_SERVER_PORT` | `io.prometheus.exporter.http_server.port` | +| `IO_PROMETHEUS_METRICS_HISTOGRAM_NATIVE_ONLY` | `io.prometheus.metrics.histogram_native_only` | + +### Property Precedence + +When the same property is defined in multiple sources, the following precedence order applies +(highest to lowest): + +1. **External properties** (passed explicitly via API) +2. **Environment variables** +3. **System properties** (command line `-D` flags) +4. **Properties file** (from file or classpath) + ## Metrics Properties -| Name | Javadoc | Note | -| --------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- | -| io.prometheus.metrics.exemplarsEnabled | [Counter.Builder.withExemplars()]() | (1) (2) | -| io.prometheus.metrics.histogramNativeOnly | [Histogram.Builder.nativeOnly()]() | (2) | -| io.prometheus.metrics.histogramClassicOnly | [Histogram.Builder.classicOnly()]() | (2) | -| io.prometheus.metrics.histogramClassicUpperBounds | [Histogram.Builder.classicUpperBounds()]() | (3) | -| io.prometheus.metrics.histogramNativeInitialSchema | [Histogram.Builder.nativeInitialSchema()]() | | -| io.prometheus.metrics.histogramNativeMinZeroThreshold | [Histogram.Builder.nativeMinZeroThreshold()]() | | -| io.prometheus.metrics.histogramNativeMaxZeroThreshold | [Histogram.Builder.nativeMaxZeroThreshold()]() | | -| io.prometheus.metrics.histogramNativeMaxNumberOfBuckets | [Histogram.Builder.nativeMaxNumberOfBuckets()]() | | -| io.prometheus.metrics.histogramNativeResetDurationSeconds | [Histogram.Builder.nativeResetDuration()]() | | -| io.prometheus.metrics.summaryQuantiles | [Summary.Builder.quantile(double)]() | (4) | -| io.prometheus.metrics.summaryQuantileErrors | [Summary.Builder.quantile(double, double)]() | (5) | -| io.prometheus.metrics.summaryMaxAgeSeconds | [Summary.Builder.maxAgeSeconds()]() | | -| io.prometheus.metrics.summaryNumberOfAgeBuckets | [Summary.Builder.numberOfAgeBuckets()]() | | +| Name | Javadoc | Note | +| ------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- | +| io.prometheus.metrics.exemplars_enabled | [Counter.Builder.withExemplars()]() | (1) (2) | +| io.prometheus.metrics.histogram_native_only | [Histogram.Builder.nativeOnly()]() | (2) | +| io.prometheus.metrics.histogram_classic_only | [Histogram.Builder.classicOnly()]() | (2) | +| io.prometheus.metrics.histogram_classic_upper_bounds | [Histogram.Builder.classicUpperBounds()]() | (3) | +| io.prometheus.metrics.histogram_native_initial_schema | [Histogram.Builder.nativeInitialSchema()]() | | +| io.prometheus.metrics.histogram_native_min_zero_threshold | [Histogram.Builder.nativeMinZeroThreshold()]() | | +| io.prometheus.metrics.histogram_native_max_zero_threshold | [Histogram.Builder.nativeMaxZeroThreshold()]() | | +| io.prometheus.metrics.histogram_native_max_number_of_buckets | [Histogram.Builder.nativeMaxNumberOfBuckets()]() | | +| io.prometheus.metrics.histogram_native_reset_duration_seconds | [Histogram.Builder.nativeResetDuration()]() | | +| io.prometheus.metrics.summary_quantiles | [Summary.Builder.quantile(double)]() | (4) | +| io.prometheus.metrics.summary_quantile_errors | [Summary.Builder.quantile(double, double)]() | (5) | +| io.prometheus.metrics.summary_max_age_seconds | [Summary.Builder.maxAgeSeconds()]() | | +| io.prometheus.metrics.summary_number_of_age_buckets | [Summary.Builder.numberOfAgeBuckets()]() | | @@ -64,20 +100,20 @@ not just for counters
(3) Comma-separated list. Example: `.005, .01, .025, .05, .1, .25, .5, 1, 2.5, 5, 10`.
(4) Comma-separated list. Example: `0.5, 0.95, 0.99`.
(5) Comma-separated list. If specified, the list must have the same length as -`io.prometheus.metrics.summaryQuantiles`. Example: `0.01, 0.005, 0.005`. +`io.prometheus.metrics.summary_quantiles`. Example: `0.01, 0.005, 0.005`. There's one special feature about metric properties: You can set a property for one specific metric only by specifying the metric name. Example: Let's say you have a histogram named `latency_seconds`. ```properties -io.prometheus.metrics.histogramClassicUpperBounds = 0.2, 0.4, 0.8, 1.0 +io.prometheus.metrics.histogram_classic_upper_bounds=0.2, 0.4, 0.8, 1.0 ``` The line above sets histogram buckets for all histograms. However: ```properties -io.prometheus.metrics.latency_seconds.histogramClassicUpperBounds = 0.2, 0.4, 0.8, 1.0 +io.prometheus.metrics.latency_seconds.histogram_classic_upper_bounds=0.2, 0.4, 0.8, 1.0 ``` The line above sets histogram buckets only for the histogram named `latency_seconds`. @@ -88,11 +124,11 @@ This works for all Metrics properties. -| Name | Javadoc | Note | -| -------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---- | -| io.prometheus.exemplars.minRetentionPeriodSeconds | [ExemplarsProperties.getMinRetentionPeriodSeconds()]() | | -| io.prometheus.exemplars.maxRetentionPeriodSeconds | [ExemplarsProperties.getMaxRetentionPeriodSeconds()]() | | -| io.prometheus.exemplars.sampleIntervalMilliseconds | [ExemplarsProperties.getSampleIntervalMilliseconds()]() | | +| Name | Javadoc | Note | +| ---------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---- | +| io.prometheus.exemplars.min_retention_period_seconds | [ExemplarsProperties.getMinRetentionPeriodSeconds()]() | | +| io.prometheus.exemplars.max_retention_period_seconds | [ExemplarsProperties.getMaxRetentionPeriodSeconds()]() | | +| io.prometheus.exemplars.sample_interval_milliseconds | [ExemplarsProperties.getSampleIntervalMilliseconds()]() | | @@ -100,10 +136,10 @@ This works for all Metrics properties. -| Name | Javadoc | Note | -| ------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------- | ---- | -| io.prometheus.exporter.includeCreatedTimestamps | [ExporterProperties.getIncludeCreatedTimestamps()]() | (1) | -| io.prometheus.exporter.exemplarsOnAllMetricTypes | [ExporterProperties.getExemplarsOnAllMetricTypes()]() | (1) | +| Name | Javadoc | Note | +| ---------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- | ---- | +| io.prometheus.exporter.include_created_timestamps | [ExporterProperties.getIncludeCreatedTimestamps()]() | (1) | +| io.prometheus.exporter.exemplars_on_all_metric_types | [ExporterProperties.getExemplarsOnAllMetricTypes()]() | (1) | @@ -113,12 +149,12 @@ This works for all Metrics properties. -| Name | Javadoc | Note | -| -------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---- | -| io.prometheus.exporter.filter.metricNameMustBeEqualTo | [ExporterFilterProperties.getAllowedMetricNames()]() | (1) | -| io.prometheus.exporter.filter.metricNameMustNotBeEqualTo | [ExporterFilterProperties.getExcludedMetricNames()]() | (2) | -| io.prometheus.exporter.filter.metricNameMustStartWith | [ExporterFilterProperties.getAllowedMetricNamePrefixes()]() | (3) | -| io.prometheus.exporter.filter.metricNameMustNotStartWith | [ExporterFilterProperties.getExcludedMetricNamePrefixes()]() | (4) | +| Name | Javadoc | Note | +| -------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---- | +| io.prometheus.exporter.filter.metric_name_must_be_equal_to | [ExporterFilterProperties.getAllowedMetricNames()]() | (1) | +| io.prometheus.exporter.filter.metric_name_must_not_be_equal_to | [ExporterFilterProperties.getExcludedMetricNames()]() | (2) | +| io.prometheus.exporter.filter.metric_name_must_start_with | [ExporterFilterProperties.getAllowedMetricNamePrefixes()]() | (3) | +| io.prometheus.exporter.filter.metric_name_must_not_start_with | [ExporterFilterProperties.getExcludedMetricNamePrefixes()]() | (4) | @@ -132,9 +168,9 @@ Only metrics starting with these prefixes will be exposed.
-| Name | Javadoc | Note | -| -------------------------------------- | --------------------------------------------------------------------------------------------------------------------------- | ---- | -| io.prometheus.exporter.httpServer.port | [HTTPServer.Builder.port()]() | | +| Name | Javadoc | Note | +| --------------------------------------- | --------------------------------------------------------------------------------------------------------------------------- | ---- | +| io.prometheus.exporter.http_server.port | [HTTPServer.Builder.port()]() | | @@ -142,18 +178,18 @@ Only metrics starting with these prefixes will be exposed.
-| Name | Javadoc | Note | -| ------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---- | -| io.prometheus.exporter.opentelemetry.protocol | [OpenTelemetryExporter.Builder.protocol()]() | (1) | -| io.prometheus.exporter.opentelemetry.endpoint | [OpenTelemetryExporter.Builder.endpoint()]() | | -| io.prometheus.exporter.opentelemetry.headers | [OpenTelemetryExporter.Builder.headers()]() | (2) | -| io.prometheus.exporter.opentelemetry.intervalSeconds | [OpenTelemetryExporter.Builder.intervalSeconds()]() | | -| io.prometheus.exporter.opentelemetry.timeoutSeconds | [OpenTelemetryExporter.Builder.timeoutSeconds()]() | | -| io.prometheus.exporter.opentelemetry.serviceName | [OpenTelemetryExporter.Builder.serviceName()]() | | -| io.prometheus.exporter.opentelemetry.serviceNamespace | [OpenTelemetryExporter.Builder.serviceNamespace()]() | | -| io.prometheus.exporter.opentelemetry.serviceInstanceId | [OpenTelemetryExporter.Builder.serviceInstanceId()]() | | -| io.prometheus.exporter.opentelemetry.serviceVersion | [OpenTelemetryExporter.Builder.serviceVersion()]() | | -| io.prometheus.exporter.opentelemetry.resourceAttributes | [OpenTelemetryExporter.Builder.resourceAttributes()]() | (3) | +| Name | Javadoc | Note | +| -------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---- | +| io.prometheus.exporter.opentelemetry.protocol | [OpenTelemetryExporter.Builder.protocol()]() | (1) | +| io.prometheus.exporter.opentelemetry.endpoint | [OpenTelemetryExporter.Builder.endpoint()]() | | +| io.prometheus.exporter.opentelemetry.headers | [OpenTelemetryExporter.Builder.headers()]() | (2) | +| io.prometheus.exporter.opentelemetry.interval_seconds | [OpenTelemetryExporter.Builder.intervalSeconds()]() | | +| io.prometheus.exporter.opentelemetry.timeout_seconds | [OpenTelemetryExporter.Builder.timeoutSeconds()]() | | +| io.prometheus.exporter.opentelemetry.service_name | [OpenTelemetryExporter.Builder.serviceName()]() | | +| io.prometheus.exporter.opentelemetry.service_namespace | [OpenTelemetryExporter.Builder.serviceNamespace()]() | | +| io.prometheus.exporter.opentelemetry.service_instance_id | [OpenTelemetryExporter.Builder.serviceInstanceId()]() | | +| io.prometheus.exporter.opentelemetry.service_version | [OpenTelemetryExporter.Builder.serviceVersion()]() | | +| io.prometheus.exporter.opentelemetry.resource_attributes | [OpenTelemetryExporter.Builder.resourceAttributes()]() | (3) | @@ -170,10 +206,15 @@ See Javadoc for details. -| Name | Javadoc | Note | -| ------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------- | ---- | -| io.prometheus.exporter.pushgateway.address | [PushGateway.Builder.address()]() | | -| io.prometheus.exporter.pushgateway.scheme | [PushGateway.Builder.scheme()]() | | -| io.prometheus.exporter.pushgateway.job | [PushGateway.Builder.job()]() | | +| Name | Javadoc | Note | +| -------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ---- | +| io.prometheus.exporter.pushgateway.address | [PushGateway.Builder.address()]() | | +| io.prometheus.exporter.pushgateway.scheme | [PushGateway.Builder.scheme()]() | | +| io.prometheus.exporter.pushgateway.job | [PushGateway.Builder.job()]() | | +| io.prometheus.exporter.pushgateway.escaping_scheme | [PushGateway.Builder.escapingScheme()]() | (1) | + +(1) Escaping scheme can be `allow-utf-8`, `underscores`, `dots`, or `values` as described in +[escaping schemes](https://github.com/prometheus/docs/blob/main/docs/instrumenting/escaping_schemes.md#escaping-schemes) +and in the [Unicode documentation]({{< relref "../exporters/unicode.md" >}}). diff --git a/docs/content/exporters/filter.md b/docs/content/exporters/filter.md index 01ac48e69..ae7ad9564 100644 --- a/docs/content/exporters/filter.md +++ b/docs/content/exporters/filter.md @@ -1,6 +1,6 @@ --- title: Filter -weight: 2 +weight: 3 --- All exporters support a `name[]` URL parameter for querying only specific metric names. Examples: diff --git a/docs/content/exporters/httpserver.md b/docs/content/exporters/httpserver.md index 4d0aab5f2..a9017b0de 100644 --- a/docs/content/exporters/httpserver.md +++ b/docs/content/exporters/httpserver.md @@ -1,6 +1,6 @@ --- title: HTTPServer -weight: 3 +weight: 4 --- The `HTTPServer` is a standalone server for exposing a metric endpoint. A minimal example @@ -40,4 +40,4 @@ You can find an example of authentication and SSL in the See _config_ section (_todo_) on runtime configuration options. -- `io.prometheus.exporter.httpServer.port`: The port to bind to. +- `io.prometheus.exporter.http_server.port`: The port to bind to. diff --git a/docs/content/exporters/pushgateway.md b/docs/content/exporters/pushgateway.md index b4c531022..497aa9b57 100644 --- a/docs/content/exporters/pushgateway.md +++ b/docs/content/exporters/pushgateway.md @@ -1,6 +1,6 @@ --- title: Pushgateway -weight: 5 +weight: 6 --- The [Prometheus Pushgateway](https://github.com/prometheus/pushgateway) exists to allow ephemeral diff --git a/docs/content/exporters/servlet.md b/docs/content/exporters/servlet.md index 93b870b1d..2b0873b70 100644 --- a/docs/content/exporters/servlet.md +++ b/docs/content/exporters/servlet.md @@ -1,6 +1,6 @@ --- title: Servlet -weight: 4 +weight: 5 --- The diff --git a/docs/content/exporters/spring.md b/docs/content/exporters/spring.md index fc0d946dd..45df21431 100644 --- a/docs/content/exporters/spring.md +++ b/docs/content/exporters/spring.md @@ -1,6 +1,6 @@ --- title: Spring -weight: 5 +weight: 7 --- ## Alternative: Use Spring's Built-in Metrics Library diff --git a/docs/content/exporters/unicode.md b/docs/content/exporters/unicode.md new file mode 100644 index 000000000..2a34e0400 --- /dev/null +++ b/docs/content/exporters/unicode.md @@ -0,0 +1,34 @@ +--- +title: Unicode +weight: 2 +--- + +{{< hint type=warning >}} +Unicode support is experimental, because [OpenMetrics specification](https://openmetrics.io/) is not +updated yet to support Unicode characters in metric and label names. +{{< /hint >}} + +The Prometheus Java client library allows all Unicode characters, that can be encoded as UTF-8. + +At scrape time, some characters are replaced based on the `encoding` header according +to +the [Escaping scheme](https://github.com/prometheus/docs/blob/main/docs/instrumenting/escaping_schemes.md). + +For example, if you use the `underscores` escaping scheme, dots in metric and label names are +replaced with underscores, so that the metric name `http.server.duration` becomes +`http_server_duration`. + +Prometheus servers that do not support Unicode at all will not pass the `encoding` header, and the +Prometheus Java client library will replace dots, as well as any character that is not in the legacy +character set (`a-zA-Z0-9_:`), with underscores by default. + +When `escaping=allow-utf-8` is passed, add valid UTF-8 characters to the metric and label names +without replacing them. This allows you to use dots in metric and label names, as well as +other UTF-8 characters, without any replacements. + +## PushGateway + +When using the [Pushgateway]({{< relref "pushgateway.md" >}}), Unicode support has to be enabled +explicitly by setting `io.prometheus.exporter.pushgateway.escapingScheme` to `allow-utf-8` in the +Pushgateway configuration file - see +[Pushgateway configuration]({{< relref "/config/config.md#exporter-pushgateway-properties" >}}) diff --git a/docs/content/getting-started/metric-types.md b/docs/content/getting-started/metric-types.md index 3bcda84fe..97424ef5c 100644 --- a/docs/content/getting-started/metric-types.md +++ b/docs/content/getting-started/metric-types.md @@ -109,9 +109,9 @@ most cases you don't need them, defaults are good. The following is an incomplet most important options: - `nativeOnly()` / `classicOnly()`: Create a histogram with one representation only. -- `classicBuckets(...)`: Set the classic bucket boundaries. Default buckets are `.005`, `.01`, - `.025`, `.05`, `.1`, `.25`, `.5`, `1`, `2.5`, `5`, `and 10`. The default bucket boundaries are - designed for measuring request durations in seconds. +- `classicUpperBounds(...)`: Set the classic bucket upper boundaries. Default bucket upper + boundaries are `.005`, `.01`, `.025`, `.05`, `.1`, `.25`, `.5`, `1`, `2.5`, `5`, `and 10`. The + default bucket boundaries are designed for measuring request durations in seconds. - `nativeMaxNumberOfBuckets()`: Upper limit for the number of native histogram buckets. Default is 160. When the maximum is reached, the native histogram automatically reduces resolution to stay below the limit. @@ -121,6 +121,94 @@ for [Histogram.Builder](/client_java/api/io/prometheus/metrics/core/metrics/Hist for a complete list of options. Some options can be configured at runtime, see [config]({{< relref "../config/config.md" >}}). +### Custom Bucket Boundaries + +The default bucket boundaries are designed for measuring request durations in seconds. For other +use cases, you may want to define custom bucket boundaries. The histogram builder provides three +methods for this: + +**1. Arbitrary Custom Boundaries** + +Use `classicUpperBounds(...)` to specify arbitrary bucket boundaries: + +```java +Histogram responseSize = Histogram.builder() + .name("http_response_size_bytes") + .help("HTTP response size in bytes") + .classicUpperBounds(100, 1000, 10000, 100000, 1000000) // bytes + .register(); +``` + +**2. Linear Boundaries** + +Use `classicLinearUpperBounds(start, width, count)` for equal-width buckets: + +```java +Histogram queueSize = Histogram.builder() + .name("queue_size") + .help("Number of items in queue") + .classicLinearUpperBounds(10, 10, 10) // 10, 20, 30, ..., 100 + .register(); +``` + +**3. Exponential Boundaries** + +Use `classicExponentialUpperBounds(start, factor, count)` for exponential growth: + +```java +Histogram dataSize = Histogram.builder() + .name("data_size_bytes") + .help("Data size in bytes") + .classicExponentialUpperBounds(100, 10, 5) // 100, 1k, 10k, 100k, 1M + .register(); +``` + +### Native Histograms with Custom Buckets (NHCB) + +Prometheus supports a special mode called Native Histograms with Custom Buckets (NHCB) that uses +schema -53. In this mode, custom bucket boundaries from classic histograms are preserved when +converting to native histograms. + +The Java client library automatically supports NHCB: + +1. By default, histograms maintain both classic (with custom buckets) and native representations +2. The classic representation with custom buckets is exposed to Prometheus +3. Prometheus servers can convert these to NHCB upon ingestion when configured with the + `convert_classic_histograms_to_nhcb` scrape option + +Example: + +```java +// This histogram will work seamlessly with NHCB +Histogram apiLatency = Histogram.builder() + .name("api_request_duration_seconds") + .help("API request duration") + .classicUpperBounds(0.01, 0.05, 0.1, 0.5, 1.0, 5.0, 10.0) // custom boundaries + .register(); +``` + +On the Prometheus side, configure the scrape job: + +```yaml +scrape_configs: + - job_name: "my-app" + scrape_protocols: ["PrometheusProto"] + convert_classic_histograms_to_nhcb: true + static_configs: + - targets: ["localhost:9400"] +``` + +{{< hint type=note >}} +NHCB is useful when: + +- You need precise bucket boundaries for your specific use case +- You're migrating from classic histograms and want to preserve bucket boundaries +- Exponential bucketing from standard native histograms isn't a good fit for your distribution + {{< /hint >}} + +See [examples/example-custom-buckets](https://github.com/prometheus/client_java/tree/main/examples/example-custom-buckets) +for a complete example with Prometheus and Grafana. + Histograms and summaries are both used for observing distributions. Therefore, the both implement the `DistributionDataPoint` interface. Using the `DistributionDataPoint` interface directly gives you the option to switch between histograms and summaries later with minimal code changes. @@ -276,3 +364,6 @@ in the `prometheus-metrics-core` API. However, `prometheus-metrics-model` implements the underlying data model for these types. To use these types, you need to implement your own `Collector` where the `collect()` method returns an `UnknownSnapshot` or a `HistogramSnapshot` with `.gaugeHistogram(true)`. +If your custom collector does not implement `getMetricType()` and `getLabelNames()`, ensure it does +not produce the same metric name and label set as another collector, or the exposition may contain +duplicate time series. diff --git a/docs/content/getting-started/performance.md b/docs/content/getting-started/performance.md index 31b8de162..435f0d18a 100644 --- a/docs/content/getting-started/performance.md +++ b/docs/content/getting-started/performance.md @@ -60,13 +60,13 @@ or you use the corresponding [config options]({{< relref "../config/config.md" > One way to do this is with system properties in the command line when you start your application ```sh -java -Dio.prometheus.metrics.histogramClassicOnly=true my-app.jar +java -Dio.prometheus.metrics.histogram_classic_only=true my-app.jar ``` or ```sh -java -Dio.prometheus.metrics.histogramNativeOnly=true my-app.jar +java -Dio.prometheus.metrics.histogram_native_only=true my-app.jar ``` If you don't want to add a command line parameter every time you start your application, you can add @@ -75,13 +75,13 @@ that it gets packed into your JAR file). The `prometheus.properties` file should line: ```properties -io.prometheus.metrics.histogramClassicOnly=true +io.prometheus.metrics.histogram_classic_only=true ``` or ```properties -io.prometheus.metrics.histogramNativeOnly=true +io.prometheus.metrics.histogram_native_only=true ``` Future releases will add more configuration options, like support for configuration via environment diff --git a/docs/content/getting-started/registry.md b/docs/content/getting-started/registry.md index afebbb304..7f561ecef 100644 --- a/docs/content/getting-started/registry.md +++ b/docs/content/getting-started/registry.md @@ -6,7 +6,7 @@ weight: 2 In order to expose metrics, you need to register them with a `PrometheusRegistry`. We are using a counter as an example here, but the `register()` method is the same for all metric types. -## Registering a Metrics with the Default Registry +## Registering a Metric with the Default Registry ```java Counter eventsTotal = Counter.builder() @@ -18,7 +18,7 @@ Counter eventsTotal = Counter.builder() The `register()` call above builds the counter and registers it with the global static `PrometheusRegistry.defaultRegistry`. Using the default registry is recommended. -## Registering a Metrics with a Custom Registry +## Registering a Metric with a Custom Registry You can also register your metric with a custom registry: @@ -78,12 +78,30 @@ Counter eventsTotal2 = Counter.builder() .register(); // IllegalArgumentException, because a metric with that name is already registered ``` +## Validation at registration only + +Validation of duplicate metric names and label schemas happens at registration time only. +Built-in metrics (Counter, Gauge, Histogram, etc.) participate in this validation. + +Custom collectors that implement the `Collector` or `MultiCollector` interface can optionally +implement `getPrometheusName()` and `getMetricType()` (and the MultiCollector per-name variants) so +the registry can enforce consistency. **Validation is skipped when metric name or type is +unavailable:** if `getPrometheusName()` or `getMetricType()` returns `null`, the registry does not +validate that collector. If two such collectors produce the same metric name and same label set at +scrape time, the exposition output may contain duplicate time series and be invalid for Prometheus. + +When validation _is_ performed (name and type are non-null), **null label names are treated as an +empty label schema:** `getLabelNames()` returning `null` is normalized to `Collections.emptySet()` +and full label-schema validation and duplicate detection still apply. A collector that returns a +non-null type but leaves `getLabelNames()` as `null` is still validated, with its labels treated as +empty. + ## Unregistering a Metric There is no automatic expiry of unused metrics (yet), once a metric is registered it will remain registered forever. -However, you can programmatically unregistered an obsolete metric like this: +However, you can programmatically unregister an obsolete metric like this: ```java PrometheusRegistry.defaultRegistry.unregister(eventsTotal); diff --git a/docs/content/instrumentation/jvm.md b/docs/content/instrumentation/jvm.md index 804c1b09b..a9a15341f 100644 --- a/docs/content/instrumentation/jvm.md +++ b/docs/content/instrumentation/jvm.md @@ -3,6 +3,16 @@ title: JVM weight: 1 --- +{{< hint type=note >}} + +Looking for JVM metrics that follow OTel semantic +conventions? See +[OTel JVM Runtime Metrics]({{< relref "../otel/jvm-runtime-metrics.md" >}}) +for an alternative based on OpenTelemetry's +runtime-telemetry module. + +{{< /hint >}} + The JVM instrumentation module provides a variety of out-of-the-box JVM and process metrics. To use it, add the following dependency: diff --git a/docs/content/internals/model.md b/docs/content/internals/model.md index c54e79ee3..e1b2af644 100644 --- a/docs/content/internals/model.md +++ b/docs/content/internals/model.md @@ -19,7 +19,10 @@ All metric types implement the [Collector](/client_java/api/io/prometheus/metrics/model/registry/Collector.html) interface, i.e. they provide a [collect()]() -method to produce snapshots. +method to produce snapshots. Implementers that do not provide metric type or label names (returning +null from `getMetricType()` and `getLabelNames()`) are not validated at registration; they must +avoid producing the same metric name and label schema as another collector, or exposition may be +invalid. ## prometheus-metrics-model diff --git a/docs/content/otel/jvm-runtime-metrics.md b/docs/content/otel/jvm-runtime-metrics.md new file mode 100644 index 000000000..d61da1861 --- /dev/null +++ b/docs/content/otel/jvm-runtime-metrics.md @@ -0,0 +1,241 @@ +--- +title: JVM Runtime Metrics +weight: 4 +--- + +OpenTelemetry's +[runtime-telemetry](https://github.com/open-telemetry/opentelemetry-java-instrumentation/tree/main/instrumentation/runtime-telemetry) +module is an alternative to +[prometheus-metrics-instrumentation-jvm]({{< relref "../instrumentation/jvm.md" >}}) +for users who want JVM metrics following OTel semantic conventions. + +Key advantages: + +- Metric names follow + [OTel semantic conventions](https://opentelemetry.io/docs/specs/semconv/runtime/jvm-metrics/) +- Java 17+ JFR support (context switches, network I/O, + lock contention, memory allocation) +- Alignment with the broader OTel ecosystem + +Since OpenTelemetry's `opentelemetry-exporter-prometheus` +already depends on this library's `PrometheusRegistry`, +no additional code is needed in this library — only the +OTel SDK wiring shown below. + +## Dependencies + +Use the [OTel Support]({{< relref "support.md" >}}) module +to pull in the OTel SDK and Prometheus exporter, then add +the runtime-telemetry instrumentation: + +{{< tabs "jvm-runtime-deps" >}} +{{< tab "Gradle" >}} + +```groovy +implementation 'io.prometheus:prometheus-metrics-otel-support:$version' + +// Use opentelemetry-runtime-telemetry-java8 (Java 8+) +// or opentelemetry-runtime-telemetry-java17 (Java 17+, JFR-based) +implementation( + 'io.opentelemetry.instrumentation:opentelemetry-runtime-telemetry-java8:$otelVersion-alpha' +) +``` + +{{< /tab >}} +{{< tab "Maven" >}} + +```xml + + io.prometheus + prometheus-metrics-otel-support + $version + pom + + + + + + io.opentelemetry.instrumentation + opentelemetry-runtime-telemetry-java8 + $otelVersion-alpha + + + +``` + +{{< /tab >}} +{{< /tabs >}} + +## Standalone Setup + +If you **only** want OTel runtime metrics exposed as +Prometheus, without any Prometheus Java client metrics: + +```java +import io.opentelemetry.exporter.prometheus.PrometheusHttpServer; +import io.opentelemetry.instrumentation.runtimemetrics.java8.RuntimeMetrics; +import io.opentelemetry.sdk.OpenTelemetrySdk; +import io.opentelemetry.sdk.metrics.SdkMeterProvider; + +PrometheusHttpServer prometheusServer = + PrometheusHttpServer.builder() + .setPort(9464) + .build(); + +OpenTelemetrySdk openTelemetry = + OpenTelemetrySdk.builder() + .setMeterProvider( + SdkMeterProvider.builder() + .registerMetricReader(prometheusServer) + .build()) + .build(); + +RuntimeMetrics runtimeMetrics = + RuntimeMetrics.builder(openTelemetry).build(); + +// Close on shutdown to stop metric collection and server +Runtime.getRuntime().addShutdownHook(new Thread(() -> { + runtimeMetrics.close(); + prometheusServer.close(); +})); + +// Scrape at http://localhost:9464/metrics +``` + +## Combined with Prometheus Java Client Metrics + +If you already have Prometheus Java client metrics and want to +add OTel runtime metrics to the **same** `/metrics` +endpoint, use `PrometheusMetricReader` to bridge OTel +metrics into a `PrometheusRegistry`: + +```java +import io.prometheus.metrics.core.metrics.Counter; +import io.prometheus.metrics.exporter.httpserver.HTTPServer; +import io.prometheus.metrics.model.registry.PrometheusRegistry; +import io.opentelemetry.exporter.prometheus.PrometheusMetricReader; +import io.opentelemetry.instrumentation.runtimemetrics.java8.RuntimeMetrics; +import io.opentelemetry.sdk.OpenTelemetrySdk; +import io.opentelemetry.sdk.metrics.SdkMeterProvider; + +PrometheusRegistry registry = + new PrometheusRegistry(); + +// Register Prometheus metrics as usual +Counter myCounter = Counter.builder() + .name("my_requests_total") + .register(registry); + +// Bridge OTel metrics into the same registry +PrometheusMetricReader reader = + PrometheusMetricReader.create(); +registry.register(reader); + +OpenTelemetrySdk openTelemetry = + OpenTelemetrySdk.builder() + .setMeterProvider( + SdkMeterProvider.builder() + .registerMetricReader(reader) + .build()) + .build(); + +RuntimeMetrics runtimeMetrics = + RuntimeMetrics.builder(openTelemetry).build(); +Runtime.getRuntime() + .addShutdownHook(new Thread(runtimeMetrics::close)); + +// Expose everything on one endpoint +HTTPServer.builder() + .port(9400) + .registry(registry) + .buildAndStart(); +``` + +The [examples/example-otel-jvm-runtime-metrics](https://github.com/prometheus/client_java/tree/main/examples/example-otel-jvm-runtime-metrics) +directory has a complete runnable example. + +## Configuration + +The `RuntimeMetricsBuilder` supports two configuration +options: + +### `captureGcCause()` + +Adds a `jvm.gc.cause` attribute to the `jvm.gc.duration` +metric, indicating why the garbage collection occurred +(e.g. `G1 Evacuation Pause`, `System.gc()`): + +```java +RuntimeMetrics.builder(openTelemetry) + .captureGcCause() + .build(); +``` + +### `emitExperimentalTelemetry()` + +Enables additional experimental metrics beyond the stable +set. These are not yet part of the OTel semantic conventions +and may change in future releases: + +- Buffer pool metrics (direct and mapped byte buffers) +- Extended CPU metrics +- Extended memory pool metrics +- File descriptor metrics + +```java +RuntimeMetrics.builder(openTelemetry) + .emitExperimentalTelemetry() + .build(); +``` + +Both options can be combined: + +```java +RuntimeMetrics.builder(openTelemetry) + .captureGcCause() + .emitExperimentalTelemetry() + .build(); +``` + +Selective per-metric registration is not supported by the +runtime-telemetry API — it is all-or-nothing with these +two toggles. + +## Java 17 JFR Support + +The `opentelemetry-runtime-telemetry-java17` variant adds +JFR-based metrics. You can selectively enable features: + +```java +import io.opentelemetry.instrumentation.runtimemetrics.java17.JfrFeature; +import io.opentelemetry.instrumentation.runtimemetrics.java17.RuntimeMetrics; + +RuntimeMetrics.builder(openTelemetry) + .enableFeature(JfrFeature.BUFFER_METRICS) + .enableFeature(JfrFeature.NETWORK_IO_METRICS) + .enableFeature(JfrFeature.LOCK_METRICS) + .enableFeature(JfrFeature.CONTEXT_SWITCH_METRICS) + .build(); +``` + +## Metric Names + +OTel metric names are converted to Prometheus format by +the exporter. Examples: + +| OTel name | Prometheus name | +| ---------------------------- | ---------------------------------- | +| `jvm.memory.used` | `jvm_memory_used_bytes` | +| `jvm.gc.duration` | `jvm_gc_duration_seconds` | +| `jvm.thread.count` | `jvm_thread_count` | +| `jvm.class.loaded` | `jvm_class_loaded` | +| `jvm.cpu.recent_utilization` | `jvm_cpu_recent_utilization_ratio` | + +See [Names]({{< relref "names.md" >}}) for full details on +how OTel names map to Prometheus names. diff --git a/docs/content/otel/names.md b/docs/content/otel/names.md index 2945d70e9..a5425e07f 100644 --- a/docs/content/otel/names.md +++ b/docs/content/otel/names.md @@ -17,7 +17,7 @@ the Prometheus server as if you had exposed Prometheus metrics directly. The main steps when converting OpenTelemetry metric names to Prometheus metric names are: -- Replace dots with underscores. +- Escape illegal characters as described in [Unicode support] - If the metric has a unit, append the unit to the metric name, like `_seconds`. - If the metric type has a suffix, append it, like `_total` for counters. @@ -29,14 +29,8 @@ OpenTelemetry's [Semantic Conventions for HTTP Metrics](https://opentelemetry.io say that if you instrument an HTTP server with OpenTelemetry, you must have a histogram named `http.server.duration`. -Most names defined in semantic conventions use dots. In the Prometheus server, the dot is an illegal -character (this might change in future versions of the Prometheus server). +Most names defined in semantic conventions use dots. +Dots in metric and label names are now supported in the Prometheus Java client library as +described in [Unicode support]. -The Prometheus Java client library allows dots, so that you can use metric names and label names as -defined in OpenTelemetry's semantic conventions. -The dots will automatically be replaced with underscores if you expose metrics in Prometheus format, -but you will see the original names with dots if you push your metrics in OpenTelemetry format. - -That way, you can use OTel-compliant metric and label names today when instrumenting your -application with the Prometheus Java client, and you are prepared in case your monitoring backend -adds features in the future that require OTel-compliant instrumentation. +[Unicode support]: {{< relref "../exporters/unicode.md" >}} diff --git a/docs/content/otel/support.md b/docs/content/otel/support.md new file mode 100644 index 000000000..e3b8cbe3a --- /dev/null +++ b/docs/content/otel/support.md @@ -0,0 +1,47 @@ +--- +title: OTel Support +weight: 2 +--- + +The `prometheus-metrics-otel-support` module bundles the +OpenTelemetry SDK and the Prometheus exporter into a single +POM dependency. + +Use this module when you want to combine OpenTelemetry +instrumentations (e.g. JVM runtime metrics) with the +Prometheus Java client on one `/metrics` endpoint. + +## Dependencies + +{{< tabs "otel-support-deps" >}} +{{< tab "Gradle" >}} + +```groovy +implementation 'io.prometheus:prometheus-metrics-otel-support:$version' +``` + +{{< /tab >}} +{{< tab "Maven" >}} + +```xml + + io.prometheus + prometheus-metrics-otel-support + $version + pom + +``` + +{{< /tab >}} +{{< /tabs >}} + +This single dependency replaces: + +- `io.opentelemetry:opentelemetry-sdk` +- `io.opentelemetry:opentelemetry-exporter-prometheus` + +## Use Cases + +See [JVM Runtime Metrics]({{< relref "jvm-runtime-metrics.md" >}}) +for a concrete example of combining OTel JVM metrics with +the Prometheus Java client. diff --git a/docs/themes/hugo-geekdoc/layouts/partials/microformats/schema.html b/docs/themes/hugo-geekdoc/layouts/partials/microformats/schema.html index e4a71eb4e..7e49ef8c7 100644 --- a/docs/themes/hugo-geekdoc/layouts/partials/microformats/schema.html +++ b/docs/themes/hugo-geekdoc/layouts/partials/microformats/schema.html @@ -1,70 +1,58 @@ {{ $isPage := or (and (ne .Type "posts") (in "section page" .Kind )) (and (eq .Type "posts") (eq .Kind "page")) }} {{- if eq .Kind "home" }} + {{- $schema := dict "@context" "http://schema.org" "@type" "WebSite" "name" .Site.Title "url" .Site.BaseURL "inLanguage" .Lang }} + {{- with partial "utils/description" . }} + {{- $schema = merge $schema (dict "description" (. | plainify | htmlUnescape | chomp)) }} + {{- end }} + {{- with partial "utils/featured" . }} + {{- $schema = merge $schema (dict "thumbnailUrl" .) }} + {{- end }} + {{- with .Site.Params.geekdocContentLicense }} + {{- $schema = merge $schema (dict "license" .name) }} + {{- end }} {{- else if $isPage }} + {{- $title := partial "utils/title" . }} + {{- $schema := dict + "@context" "http://schema.org" + "@type" "TechArticle" + "articleSection" (.Section | humanize | title) + "name" $title + "url" .Permalink + "headline" $title + "wordCount" (string .WordCount) + "inLanguage" .Lang + "isFamilyFriendly" "true" + "copyrightHolder" .Site.Title + "copyrightYear" (.Date.Format "2006") + "dateCreated" (.Date.Format "2006-01-02T15:04:05.00Z") + "datePublished" (.PublishDate.Format "2006-01-02T15:04:05.00Z") + "dateModified" (.Lastmod.Format "2006-01-02T15:04:05.00Z") + }} + {{- with .Params.lead }} + {{- $schema = merge $schema (dict "alternativeHeadline" .) }} + {{- end }} + {{- with partial "utils/description" . }} + {{- $schema = merge $schema (dict "description" (. | plainify | htmlUnescape | chomp)) }} + {{- end }} + {{- with partial "utils/featured" . }} + {{- $schema = merge $schema (dict "thumbnailUrl" .) }} + {{- end }} + {{- with .Site.Params.geekdocContentLicense }} + {{- $schema = merge $schema (dict "license" .name) }} + {{- end }} + {{- $mainEntity := dict "@type" "WebPage" "@id" .Permalink }} + {{- $schema = merge $schema (dict "mainEntityOfPage" $mainEntity) }} + {{- with $tags := .Params.tags }} + {{- $schema = merge $schema (dict "keywords" $tags) }} + {{- end }} + {{- $logoUrl := default "brand.svg" .Site.Params.logo | absURL }} + {{- $logo := dict "@type" "ImageObject" "url" $logoUrl "width" "32" "height" "32" }} + {{- $publisher := dict "@type" "Organization" "name" .Site.Title "url" .Site.BaseURL "logo" $logo }} + {{- $schema = merge $schema (dict "publisher" $publisher) }} {{- end }} diff --git a/examples/example-custom-buckets/README.md b/examples/example-custom-buckets/README.md new file mode 100644 index 000000000..a7a6a8564 --- /dev/null +++ b/examples/example-custom-buckets/README.md @@ -0,0 +1,170 @@ +# Native Histograms with Custom Buckets (NHCB) Example + +This example demonstrates how to use native histograms with custom bucket boundaries (NHCB) in +Prometheus Java client. It shows three different types of custom bucket configurations and how +Prometheus converts them to native histograms with schema -53. + +## What are Native Histograms with Custom Buckets? + +Native Histograms with Custom Buckets (NHCB) is a Prometheus feature that combines the benefits of: + +- **Custom bucket boundaries**: Precisely defined buckets optimized for your specific use case +- **Native histograms**: Efficient storage and querying capabilities of native histograms + +When you configure Prometheus with `convert_classic_histograms_to_nhcb: true`, it converts classic +histograms with custom buckets into native histograms using schema -53, preserving the custom +bucket boundaries. + +## Example Metrics + +This example application generates three different histogram metrics demonstrating different +bucket configuration strategies: + +### 1. API Latency - Arbitrary Custom Boundaries + +```java +Histogram apiLatency = Histogram.builder() + .name("api_request_duration_seconds") + .classicUpperBounds(0.01, 0.05, 0.1, 0.5, 1.0, 5.0, 10.0) + .register(); +``` + +**Use case**: Optimized for typical API response times in seconds. + +### 2. Queue Size - Linear Boundaries + +```java +Histogram queueSize = Histogram.builder() + .name("message_queue_size") + .classicLinearUpperBounds(10, 10, 10) // 10, 20, 30, ..., 100 + .register(); +``` + +**Use case**: Equal-width buckets for monitoring queue depth or other discrete values. + +### 3. Response Size - Exponential Boundaries + +```java +Histogram responseSize = Histogram.builder() + .name("http_response_size_bytes") + .classicExponentialUpperBounds(100, 10, 6) // 100, 1k, 10k, 100k, 1M, 10M + .register(); +``` + +**Use case**: Data spanning multiple orders of magnitude (bytes, milliseconds, etc). + +## Build + +This example is built as part of the `client_java` project: + +```shell +./mvnw package +``` + +This creates `./examples/example-custom-buckets/target/example-custom-buckets.jar`. + +## Run + +With the JAR file present, run: + +```shell +cd ./examples/example-custom-buckets/ +docker-compose up +``` + +This starts three Docker containers: + +- **[http://localhost:9400/metrics](http://localhost:9400/metrics)** - Example application +- **[http://localhost:9090](http://localhost:9090)** - Prometheus server (with NHCB enabled) +- **[http://localhost:3000](http://localhost:3000)** - Grafana (user: _admin_, password: _admin_) + +You might need to replace `localhost` with `host.docker.internal` on macOS or Windows. + +## Verify NHCB Conversion + +### 1. Check Prometheus Configuration + +The Prometheus configuration enables NHCB conversion: + +```yaml +scrape_configs: + - job_name: "custom-buckets-demo" + scrape_protocols: ["PrometheusProto"] + convert_classic_histograms_to_nhcb: true + scrape_classic_histograms: true +``` + +### 2. Verify in Prometheus + +Visit [http://localhost:9090](http://localhost:9090) and run queries: + +```promql +# View histogram metadata (should show schema -53 for NHCB) +prometheus_tsdb_head_series + +# Calculate quantiles from custom buckets +histogram_quantile(0.95, rate(api_request_duration_seconds[1m])) + +# View raw histogram structure +api_request_duration_seconds +``` + +### 3. View in Grafana + +The Grafana dashboard at [http://localhost:3000](http://localhost:3000) shows: + +- p95 and p50 latencies for API endpoints (arbitrary custom buckets) +- Queue size distribution (linear buckets) +- Response size distribution (exponential buckets) + +## Key Observations + +1. **Custom Buckets Preserved**: The custom bucket boundaries you define are preserved when + converted to NHCB (schema -53). + +2. **Dual Representation**: By default, histograms maintain both classic and native + representations, allowing gradual migration. + +3. **Efficient Storage**: Native histograms provide more efficient storage than classic histograms + while preserving your custom bucket boundaries. + +4. **Flexible Bucket Strategies**: You can choose arbitrary, linear, or exponential buckets based + on your specific monitoring needs. + +## When to Use Custom Buckets + +Consider using custom buckets (and NHCB) when: + +- **Precise boundaries needed**: You know the expected distribution and want specific bucket edges +- **Migrating from classic histograms**: You want to preserve existing bucket boundaries +- **Specific use cases**: Default exponential bucketing doesn't fit your distribution well + - Temperature ranges (might include negative values) + - Queue depths (discrete values with linear growth) + - File sizes (exponential growth but with specific thresholds) + - API latencies (specific SLA boundaries) + +## Differences from Standard Native Histograms + +| Feature | Standard Native Histograms | NHCB (Schema -53) | +| ----------------- | ------------------------------- | --------------------------------- | +| Bucket boundaries | Exponential (base 2^(2^-scale)) | Custom boundaries | +| Use case | General-purpose | Specific distributions | +| Mergeability | Can merge with same schema | Cannot merge different boundaries | +| Configuration | Schema level (0-8) | Explicit boundary list | + +## Cleanup + +Stop the containers: + +```shell +docker-compose down +``` + +## Further Reading + + + + +- [Prometheus Native Histograms Specification](https://prometheus.io/docs/specs/native_histograms/) +- [Prometheus Java Client Documentation](https://prometheus.github.io/client_java/) +- [OpenTelemetry Exponential Histograms](https://opentelemetry.io/docs/specs/otel/metrics/data-model/#exponentialhistogram) diff --git a/examples/example-custom-buckets/docker-compose.yaml b/examples/example-custom-buckets/docker-compose.yaml new file mode 100644 index 000000000..b024481af --- /dev/null +++ b/examples/example-custom-buckets/docker-compose.yaml @@ -0,0 +1,26 @@ +version: "3" +services: + example-application: + image: eclipse-temurin:25.0.2_10-jre@sha256:0a9c973778b03b88f39ccae4f8cc26022d84a3237a818cb98770369eb6c5daf9 + network_mode: host + volumes: + - ./target/example-custom-buckets.jar:/example-custom-buckets.jar + command: + - /opt/java/openjdk/bin/java + - -jar + - /example-custom-buckets.jar + prometheus: + image: prom/prometheus:v3.9.1@sha256:1f0f50f06acaceb0f5670d2c8a658a599affe7b0d8e78b898c1035653849a702 + network_mode: host + volumes: + - ./docker-compose/prometheus.yml:/prometheus.yml + command: + - --enable-feature=native-histograms + - --config.file=/prometheus.yml + grafana: + image: grafana/grafana:12.3.3@sha256:9e1e77ade304069aee3196e9a4f210830e96e80ce9a2640891eccc324b152faf + network_mode: host + volumes: + - ./docker-compose/grafana-datasources.yaml:/etc/grafana/provisioning/datasources/grafana-datasources.yaml + - ./docker-compose/grafana-dashboards.yaml:/etc/grafana/provisioning/dashboards/grafana-dashboards.yaml + - ./docker-compose/grafana-dashboard-custom-buckets.json:/etc/grafana/grafana-dashboard-custom-buckets.json diff --git a/examples/example-custom-buckets/docker-compose/grafana-dashboard-custom-buckets.json b/examples/example-custom-buckets/docker-compose/grafana-dashboard-custom-buckets.json new file mode 100644 index 000000000..11ae25775 --- /dev/null +++ b/examples/example-custom-buckets/docker-compose/grafana-dashboard-custom-buckets.json @@ -0,0 +1,349 @@ +{ + "annotations": { + "list": [] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "id": null, + "links": [], + "panels": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "description": "API request duration with custom bucket boundaries (0.01, 0.05, 0.1, 0.5, 1.0, 5.0, 10.0 seconds). Shows how custom buckets are preserved in NHCB (schema -53).", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "tooltip": false, + "viz": false, + "legend": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "s" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 24, + "x": 0, + "y": 0 + }, + "id": 1, + "options": { + "legend": { + "calcs": ["mean", "max"], + "displayMode": "table", + "placement": "right", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "editorMode": "code", + "expr": "histogram_quantile(0.95, rate(api_request_duration_seconds[1m]))", + "instant": false, + "legendFormat": "{{endpoint}} {{status}} (p95)", + "range": true, + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "editorMode": "code", + "expr": "histogram_quantile(0.5, rate(api_request_duration_seconds[1m]))", + "hide": false, + "instant": false, + "legendFormat": "{{endpoint}} {{status}} (p50)", + "range": true, + "refId": "B" + } + ], + "title": "API Latency - Custom Buckets (Arbitrary Boundaries)", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "description": "Queue size with linear bucket boundaries (10, 20, 30, ..., 100). Demonstrates equal-width buckets for discrete values.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "tooltip": false, + "viz": false, + "legend": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 8 + }, + "id": 2, + "options": { + "legend": { + "calcs": ["mean", "max"], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "editorMode": "code", + "expr": "histogram_quantile(0.95, rate(message_queue_size[1m]))", + "instant": false, + "legendFormat": "{{queue_name}} (p95)", + "range": true, + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "editorMode": "code", + "expr": "histogram_quantile(0.5, rate(message_queue_size[1m]))", + "hide": false, + "instant": false, + "legendFormat": "{{queue_name}} (p50)", + "range": true, + "refId": "B" + } + ], + "title": "Queue Size - Linear Buckets", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "description": "HTTP response size with exponential bucket boundaries (100, 1k, 10k, 100k, 1M, 10M bytes). Shows exponential growth for data spanning multiple orders of magnitude.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "tooltip": false, + "viz": false, + "legend": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "bytes" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 8 + }, + "id": 3, + "options": { + "legend": { + "calcs": ["mean", "max"], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "editorMode": "code", + "expr": "histogram_quantile(0.95, rate(http_response_size_bytes[1m]))", + "instant": false, + "legendFormat": "{{endpoint}} (p95)", + "range": true, + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "editorMode": "code", + "expr": "histogram_quantile(0.5, rate(http_response_size_bytes[1m]))", + "hide": false, + "instant": false, + "legendFormat": "{{endpoint}} (p50)", + "range": true, + "refId": "B" + } + ], + "title": "Response Size - Exponential Buckets", + "type": "timeseries" + } + ], + "refresh": "5s", + "schemaVersion": 39, + "tags": ["custom-buckets", "nhcb", "native-histogram"], + "templating": { + "list": [] + }, + "time": { + "from": "now-5m", + "to": "now" + }, + "timepicker": {}, + "timezone": "browser", + "title": "Native Histograms with Custom Buckets (NHCB)", + "uid": "custom-buckets-nhcb", + "version": 1, + "weekStart": "" +} diff --git a/examples/example-custom-buckets/docker-compose/grafana-dashboards.yaml b/examples/example-custom-buckets/docker-compose/grafana-dashboards.yaml new file mode 100644 index 000000000..3225b88ae --- /dev/null +++ b/examples/example-custom-buckets/docker-compose/grafana-dashboards.yaml @@ -0,0 +1,8 @@ +apiVersion: 1 + +providers: + - name: "Custom Buckets (NHCB) Example" + type: file + options: + path: /etc/grafana/grafana-dashboard-custom-buckets.json + foldersFromFilesStructure: false diff --git a/examples/example-custom-buckets/docker-compose/grafana-datasources.yaml b/examples/example-custom-buckets/docker-compose/grafana-datasources.yaml new file mode 100644 index 000000000..d442d28d2 --- /dev/null +++ b/examples/example-custom-buckets/docker-compose/grafana-datasources.yaml @@ -0,0 +1,7 @@ +apiVersion: 1 + +datasources: + - name: Prometheus + type: prometheus + uid: prometheus + url: http://localhost:9090 diff --git a/examples/example-custom-buckets/docker-compose/prometheus.yml b/examples/example-custom-buckets/docker-compose/prometheus.yml new file mode 100644 index 000000000..5c5782023 --- /dev/null +++ b/examples/example-custom-buckets/docker-compose/prometheus.yml @@ -0,0 +1,14 @@ +--- +global: + scrape_interval: 5s # very short interval for demo purposes + +scrape_configs: + - job_name: "custom-buckets-demo" + # Use protobuf format to receive native histogram data + scrape_protocols: ["PrometheusProto"] + # Convert classic histograms with custom buckets to NHCB (schema -53) + convert_classic_histograms_to_nhcb: true + # Also scrape classic histograms for comparison + scrape_classic_histograms: true + static_configs: + - targets: ["localhost:9400"] diff --git a/examples/example-custom-buckets/pom.xml b/examples/example-custom-buckets/pom.xml new file mode 100644 index 000000000..ca4c52843 --- /dev/null +++ b/examples/example-custom-buckets/pom.xml @@ -0,0 +1,72 @@ + + + 4.0.0 + + io.prometheus + example-custom-buckets + 1.0-SNAPSHOT + + + 8 + UTF-8 + + + Example - Custom Buckets + + End-to-End example of Native Histograms with Custom Buckets (NHCB): Java app -> Prometheus -> Grafana + + + + + + io.prometheus + prometheus-metrics-bom + 1.5.0 + pom + import + + + + + + + io.prometheus + prometheus-metrics-core + + + io.prometheus + prometheus-metrics-instrumentation-jvm + + + io.prometheus + prometheus-metrics-exporter-httpserver + + + + + ${project.artifactId} + + + org.apache.maven.plugins + maven-shade-plugin + + + package + + shade + + + + + io.prometheus.metrics.examples.custombuckets.Main + + + + + + + + + diff --git a/examples/example-custom-buckets/src/main/java/io/prometheus/metrics/examples/custombuckets/Main.java b/examples/example-custom-buckets/src/main/java/io/prometheus/metrics/examples/custombuckets/Main.java new file mode 100644 index 000000000..3d286fdf0 --- /dev/null +++ b/examples/example-custom-buckets/src/main/java/io/prometheus/metrics/examples/custombuckets/Main.java @@ -0,0 +1,108 @@ +package io.prometheus.metrics.examples.custombuckets; + +import io.prometheus.metrics.core.metrics.Histogram; +import io.prometheus.metrics.exporter.httpserver.HTTPServer; +import io.prometheus.metrics.instrumentation.jvm.JvmMetrics; +import io.prometheus.metrics.model.snapshots.Unit; +import java.io.IOException; +import java.util.Random; + +/** + * Example demonstrating native histograms with custom buckets (NHCB). + * + *

This example shows three different types of custom bucket configurations: + * + *

    + *
  • API latency with arbitrary custom boundaries optimized for typical response times + *
  • Queue size with linear boundaries for equal-width buckets + *
  • Response size with exponential boundaries for data spanning multiple orders of magnitude + *
+ * + *

These histograms maintain both classic (with custom buckets) and native representations. When + * Prometheus is configured with {@code convert_classic_histograms_to_nhcb: true}, the custom bucket + * boundaries are preserved in the native histogram format (schema -53). + */ +public class Main { + + public static void main(String[] args) throws IOException, InterruptedException { + + JvmMetrics.builder().register(); + + // Example 1: API latency with arbitrary custom boundaries + // Optimized for typical API response times in seconds + Histogram apiLatency = + Histogram.builder() + .name("api_request_duration_seconds") + .help("API request duration with custom buckets") + .unit(Unit.SECONDS) + .classicUpperBounds(0.01, 0.05, 0.1, 0.5, 1.0, 5.0, 10.0) + .labelNames("endpoint", "status") + .register(); + + // Example 2: Queue size with linear boundaries + // Equal-width buckets for monitoring queue depth + Histogram queueSize = + Histogram.builder() + .name("message_queue_size") + .help("Number of messages in queue with linear buckets") + .classicLinearUpperBounds(10, 10, 10) // 10, 20, 30, ..., 100 + .labelNames("queue_name") + .register(); + + // Example 3: Response size with exponential boundaries + // Exponential growth for data spanning multiple orders of magnitude + Histogram responseSize = + Histogram.builder() + .name("http_response_size_bytes") + .help("HTTP response size in bytes with exponential buckets") + .classicExponentialUpperBounds(100, 10, 6) // 100, 1k, 10k, 100k, 1M, 10M + .labelNames("endpoint") + .register(); + + HTTPServer server = HTTPServer.builder().port(9400).buildAndStart(); + + System.out.println( + "HTTPServer listening on port http://localhost:" + server.getPort() + "/metrics"); + System.out.println("\nGenerating metrics with custom bucket configurations:"); + System.out.println("1. API latency: custom boundaries optimized for response times"); + System.out.println("2. Queue size: linear boundaries (10, 20, 30, ..., 100)"); + System.out.println("3. Response size: exponential boundaries (100, 1k, 10k, ..., 10M)"); + System.out.println("\nPrometheus will convert these to NHCB (schema -53) when configured.\n"); + + Random random = new Random(0); + + while (true) { + // Simulate API latency observations + // Fast endpoint: mostly < 100ms, occasionally slow + double fastLatency = Math.abs(random.nextGaussian() * 0.03 + 0.05); + String status = random.nextInt(100) < 95 ? "200" : "500"; + apiLatency.labelValues("/api/fast", status).observe(fastLatency); + + // Slow endpoint: typically 1-3 seconds + double slowLatency = Math.abs(random.nextGaussian() * 0.5 + 2.0); + apiLatency.labelValues("/api/slow", status).observe(slowLatency); + + // Simulate queue size observations + // Queue oscillates between 20-80 items + int queueDepth = 50 + (int) (random.nextGaussian() * 15); + queueDepth = Math.max(0, Math.min(100, queueDepth)); + queueSize.labelValues("default").observe(queueDepth); + + // Priority queue: usually smaller + int priorityQueueDepth = 10 + (int) (random.nextGaussian() * 5); + priorityQueueDepth = Math.max(0, Math.min(50, priorityQueueDepth)); + queueSize.labelValues("priority").observe(priorityQueueDepth); + + // Simulate response size observations + // Small responses: mostly < 10KB + double smallResponse = Math.abs(random.nextGaussian() * 2000 + 5000); + responseSize.labelValues("/api/summary").observe(smallResponse); + + // Large responses: can be up to several MB + double largeResponse = Math.abs(random.nextGaussian() * 200000 + 500000); + responseSize.labelValues("/api/download").observe(largeResponse); + + Thread.sleep(1000); + } + } +} diff --git a/examples/example-exemplars-tail-sampling/docker-compose.yaml b/examples/example-exemplars-tail-sampling/docker-compose.yaml index 305bf9516..dee885885 100644 --- a/examples/example-exemplars-tail-sampling/docker-compose.yaml +++ b/examples/example-exemplars-tail-sampling/docker-compose.yaml @@ -36,14 +36,14 @@ services: - -jar - /example-greeting-service.jar collector: - image: otel/opentelemetry-collector-contrib:0.130.0@sha256:867d1074c2f750936fb9358ec9eefa009308053cf156b2c7ca1761ba5ef78452 + image: otel/opentelemetry-collector-contrib:0.146.1@sha256:f6e429c1052ab50f85a7afa5f7e32f25931697751622b0e1f453d10f79a1df3c network_mode: host volumes: - ./config/otelcol-config.yaml:/config.yaml command: - --config=file:/config.yaml prometheus: - image: prom/prometheus:v3.5.0@sha256:63805ebb8d2b3920190daf1cb14a60871b16fd38bed42b857a3182bc621f4996 + image: prom/prometheus:v3.9.1@sha256:1f0f50f06acaceb0f5670d2c8a658a599affe7b0d8e78b898c1035653849a702 network_mode: host volumes: - ./config/prometheus.yaml:/prometheus.yaml @@ -52,14 +52,14 @@ services: - --enable-feature=native-histograms - --config.file=/prometheus.yaml tempo: - image: grafana/tempo:2.8.1@sha256:bc9245fe3da4e63dc4c6862d9c2dad9bcd8be13d0ba4f7705fa6acda4c904d0e + image: grafana/tempo:2.10.1@sha256:9371af1b75b4e057eb77f22dc4dd4d9176cd6985e29f181527be6723b7f29c41 network_mode: host volumes: - ./config/tempo-config.yaml:/config.yaml command: - --config.file=/config.yaml grafana: - image: grafana/grafana:12.0.2@sha256:b5b59bfc7561634c2d7b136c4543d702ebcc94a3da477f21ff26f89ffd4214fa + image: grafana/grafana:12.3.3@sha256:9e1e77ade304069aee3196e9a4f210830e96e80ce9a2640891eccc324b152faf network_mode: host ports: - "3000:3000" @@ -68,7 +68,7 @@ services: - ./config/grafana-dashboards.yaml:/etc/grafana/provisioning/dashboards/grafana-dashboards.yaml - ./config/grafana-example-dashboard.json:/etc/grafana/example-dashboard.json k6: - image: grafana/k6@sha256:b1625f686ef1c733340b00de57bce840e0b4b1f7e545c58305a5db53e7ad3797 + image: grafana/k6@sha256:5e937f439684142ba7803722b42e3c9ac9233cfa01d561de0596c1c2794fd680 network_mode: host volumes: - ./config/k6-script.js:/k6-script.js diff --git a/examples/example-exemplars-tail-sampling/example-greeting-service/pom.xml b/examples/example-exemplars-tail-sampling/example-greeting-service/pom.xml index d1938f242..8621a7e19 100644 --- a/examples/example-exemplars-tail-sampling/example-greeting-service/pom.xml +++ b/examples/example-exemplars-tail-sampling/example-greeting-service/pom.xml @@ -4,13 +4,14 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 4.0.0 - - io.prometheus - example-exemplars-tail-sampling - 1.4.0-SNAPSHOT - - + io.prometheus example-greeting-service + 1.0-SNAPSHOT + + + 17 + UTF-8 + Example - OpenTelemetry Exemplars - Greeting Service @@ -18,30 +19,35 @@ tracing - - 17 - + + + + io.prometheus + prometheus-metrics-bom + 1.5.0 + pom + import + + + io.prometheus prometheus-metrics-core - ${project.version} io.prometheus prometheus-metrics-instrumentation-jvm - ${project.version} io.prometheus prometheus-metrics-exporter-servlet-jakarta - ${project.version} org.apache.tomcat.embed tomcat-embed-core - 11.0.9 + 11.0.18 diff --git a/examples/example-exemplars-tail-sampling/example-hello-world-app/pom.xml b/examples/example-exemplars-tail-sampling/example-hello-world-app/pom.xml index f20493cff..df861667f 100644 --- a/examples/example-exemplars-tail-sampling/example-hello-world-app/pom.xml +++ b/examples/example-exemplars-tail-sampling/example-hello-world-app/pom.xml @@ -4,13 +4,14 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 4.0.0 - - io.prometheus - example-exemplars-tail-sampling - 1.4.0-SNAPSHOT - - + io.prometheus example-hello-world-app + 1.0-SNAPSHOT + + + 17 + UTF-8 + Example - OpenTelemetry Exemplars - Hello World App @@ -18,30 +19,35 @@ tracing - - 17 - + + + + io.prometheus + prometheus-metrics-bom + 1.5.0 + pom + import + + + io.prometheus prometheus-metrics-core - ${project.version} io.prometheus prometheus-metrics-instrumentation-jvm - ${project.version} io.prometheus prometheus-metrics-exporter-servlet-jakarta - ${project.version} org.apache.tomcat.embed tomcat-embed-core - 11.0.9 + 11.0.18 diff --git a/examples/example-exemplars-tail-sampling/pom.xml b/examples/example-exemplars-tail-sampling/pom.xml index 8c0c7441c..ac4c6ccf5 100644 --- a/examples/example-exemplars-tail-sampling/pom.xml +++ b/examples/example-exemplars-tail-sampling/pom.xml @@ -4,24 +4,16 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 4.0.0 - - io.prometheus - examples - 1.4.0-SNAPSHOT - - + io.prometheus example-exemplars-tail-sampling + 1.0-SNAPSHOT pom Example - Exemplars with OpenTelemetry's Tail Sampling - Example project showing Examplars with OpenTelemetry's Tail Sampling. + Example project showing Exemplars with OpenTelemetry's Tail Sampling. - - 11 - - example-greeting-service example-hello-world-app diff --git a/examples/example-exporter-httpserver/pom.xml b/examples/example-exporter-httpserver/pom.xml index 6d0b75560..7160339ad 100644 --- a/examples/example-exporter-httpserver/pom.xml +++ b/examples/example-exporter-httpserver/pom.xml @@ -4,34 +4,44 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 4.0.0 - - io.prometheus - examples - 1.4.0-SNAPSHOT - - + io.prometheus example-exporter-httpserver + 1.0-SNAPSHOT + + + 8 + UTF-8 + Example - HTTPServer Exporter Prometheus Metrics Example using the HTTPServer for exposing the metrics endpoint + + + + io.prometheus + prometheus-metrics-bom + 1.5.0 + pom + import + + + + io.prometheus prometheus-metrics-core - ${project.version} io.prometheus prometheus-metrics-instrumentation-jvm - ${project.version} io.prometheus prometheus-metrics-exporter-httpserver - ${project.version} diff --git a/examples/example-exporter-multi-target/pom.xml b/examples/example-exporter-multi-target/pom.xml index 75855a9c1..e5ee1bf83 100644 --- a/examples/example-exporter-multi-target/pom.xml +++ b/examples/example-exporter-multi-target/pom.xml @@ -4,34 +4,44 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 4.0.0 - - io.prometheus - examples - 1.4.0-SNAPSHOT - - + io.prometheus example-exporter-multi-target + 1.0-SNAPSHOT + + + 8 + UTF-8 + Example - HTTPServer Exporter Multi Target Prometheus Metrics Example for multi-target pattern implementation + + + + io.prometheus + prometheus-metrics-bom + 1.5.0 + pom + import + + + + io.prometheus prometheus-metrics-core - ${project.version} io.prometheus prometheus-metrics-instrumentation-jvm - ${project.version} io.prometheus prometheus-metrics-exporter-httpserver - ${project.version} diff --git a/examples/example-exporter-opentelemetry/docker-compose.yaml b/examples/example-exporter-opentelemetry/docker-compose.yaml index 2d3367e1c..97bc9ab9b 100644 --- a/examples/example-exporter-opentelemetry/docker-compose.yaml +++ b/examples/example-exporter-opentelemetry/docker-compose.yaml @@ -13,14 +13,14 @@ services: #- -agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=*:5005 - /example-exporter-opentelemetry.jar collector: - image: otel/opentelemetry-collector-contrib:0.130.0@sha256:867d1074c2f750936fb9358ec9eefa009308053cf156b2c7ca1761ba5ef78452 + image: otel/opentelemetry-collector-contrib:0.146.1@sha256:f6e429c1052ab50f85a7afa5f7e32f25931697751622b0e1f453d10f79a1df3c network_mode: host volumes: - ./config/otelcol-config.yaml:/config.yaml command: - --config=file:/config.yaml prometheus: - image: prom/prometheus:v3.5.0@sha256:63805ebb8d2b3920190daf1cb14a60871b16fd38bed42b857a3182bc621f4996 + image: prom/prometheus:v3.9.1@sha256:1f0f50f06acaceb0f5670d2c8a658a599affe7b0d8e78b898c1035653849a702 network_mode: host volumes: - ./config/prometheus.yaml:/prometheus.yaml diff --git a/examples/example-exporter-opentelemetry/oats-tests/agent/Dockerfile b/examples/example-exporter-opentelemetry/oats-tests/agent/Dockerfile index 6ea7b6ca9..bc1adac60 100644 --- a/examples/example-exporter-opentelemetry/oats-tests/agent/Dockerfile +++ b/examples/example-exporter-opentelemetry/oats-tests/agent/Dockerfile @@ -1,8 +1,8 @@ -FROM eclipse-temurin:21.0.7_6-jre@sha256:bca347dc76e38a60a1a01b29a7d1312e514603a97ba594268e5a2e4a1a0c9a8f +FROM eclipse-temurin:25.0.2_10-jre@sha256:0a9c973778b03b88f39ccae4f8cc26022d84a3237a818cb98770369eb6c5daf9 COPY target/example-exporter-opentelemetry.jar ./app.jar # check that the resource attributes from the agent are used, epsecially the service.instance.id should be the same -ADD --chmod=644 https://github.com/open-telemetry/opentelemetry-java-instrumentation/releases/download/v2.8.0/opentelemetry-javaagent.jar /usr/src/app/opentelemetry-javaagent.jar +ADD --chmod=644 https://github.com/open-telemetry/opentelemetry-java-instrumentation/releases/download/v2.21.0/opentelemetry-javaagent.jar /usr/src/app/opentelemetry-javaagent.jar ENV JAVA_TOOL_OPTIONS=-javaagent:/usr/src/app/opentelemetry-javaagent.jar #ENTRYPOINT [ "java", "-Dotel.javaagent.debug=true","-jar", "./app.jar" ] # for debugging diff --git a/examples/example-exporter-opentelemetry/oats-tests/agent/oats.yaml b/examples/example-exporter-opentelemetry/oats-tests/agent/oats.yaml index 9c380dea9..899d1cd5d 100644 --- a/examples/example-exporter-opentelemetry/oats-tests/agent/oats.yaml +++ b/examples/example-exporter-opentelemetry/oats-tests/agent/oats.yaml @@ -1,5 +1,6 @@ # OATS is an acceptance testing framework for OpenTelemetry - # https://github.com/grafana/oats/tree/main/yaml +oats-schema-version: 2 docker-compose: files: - ./docker-compose.yml diff --git a/examples/example-exporter-opentelemetry/oats-tests/agent/service_instance_id_check.py b/examples/example-exporter-opentelemetry/oats-tests/agent/service_instance_id_check.py index 1cfe2513f..35ff88b8d 100755 --- a/examples/example-exporter-opentelemetry/oats-tests/agent/service_instance_id_check.py +++ b/examples/example-exporter-opentelemetry/oats-tests/agent/service_instance_id_check.py @@ -1,48 +1,52 @@ #!/usr/bin/env python3 - -"""This script is used to check if the service instance id is present in the exported data -The script will return 0 if the service instance id is present in the exported data""" +""" +Check if the service instance id is present in the exported data. +Returns 0 if the service instance id is present in the exported data. +""" import json import urllib.parse from urllib.request import urlopen -def get(url): - global response, res +def get_json(url): with urlopen(url) as response: - # read the response - res = response.read() - # decode the response - res = json.loads(res.decode("utf-8")) - return res + return json.loads(response.read().decode("utf-8")) -res = get(" http://localhost:9090/api/v1/query?query=target_info") +def main(): + # Query Prometheus for target_info + res = get_json("http://localhost:9090/api/v1/query?query=target_info") -# uncomment the following line to use the local file instead of the url - for debugging -# with open('example_target_info.json') as f: -# res = json.load(f) + # Uncomment for local debugging + # with open('example_target_info.json') as f: + # res = json.load(f) -values = list( - { + instance_ids = { r["metric"]["instance"] for r in res["data"]["result"] - if not r["metric"]["service_name"] == "otelcol-contrib" + if r["metric"].get("service_name") != "otelcol-contrib" } -) -print(values) + instance_ids = list(instance_ids) + + print(f"Instance ids found:{instance_ids}") + if len(instance_ids) > 1: + print("More than one instance id found") + print(res) + + # Both the agent and the exporter should report the same instance id + assert len(instance_ids) == 1, "Expected exactly one instance id" + + query = f'target_info{{instance="{instance_ids[0]}"}}' + encoded_query = urllib.parse.quote_plus(query) + res = get_json(f"http://localhost:9090/api/v1/query?query={encoded_query}") -# both the agent and the exporter should report the same instance id -assert len(values) == 1 + infos = res["data"]["result"] + print(infos) -path = f'target_info{{instance="{values[0]}"}}' -path = urllib.parse.quote_plus(path) -res = get(f"http://localhost:9090/api/v1/query?query={path}") + # They should not have the same target info (e.g. only the agent has telemetry_distro_name) + assert len(infos) == 2, "Expected two target info results" -infos = res["data"]["result"] -print(infos) -# they should not have the same target info -# e.g. only the agent has telemetry_distro_name -assert len(infos) == 2 +if __name__ == "__main__": + main() diff --git a/examples/example-exporter-opentelemetry/oats-tests/http/Dockerfile b/examples/example-exporter-opentelemetry/oats-tests/http/Dockerfile index 88947a9d9..763ba191f 100644 --- a/examples/example-exporter-opentelemetry/oats-tests/http/Dockerfile +++ b/examples/example-exporter-opentelemetry/oats-tests/http/Dockerfile @@ -1,4 +1,4 @@ -FROM eclipse-temurin:21.0.7_6-jre@sha256:bca347dc76e38a60a1a01b29a7d1312e514603a97ba594268e5a2e4a1a0c9a8f +FROM eclipse-temurin:25.0.2_10-jre@sha256:0a9c973778b03b88f39ccae4f8cc26022d84a3237a818cb98770369eb6c5daf9 COPY target/example-exporter-opentelemetry.jar ./app.jar diff --git a/examples/example-exporter-opentelemetry/oats-tests/http/oats.yaml b/examples/example-exporter-opentelemetry/oats-tests/http/oats.yaml index 66430ca3b..dbcfcf84f 100644 --- a/examples/example-exporter-opentelemetry/oats-tests/http/oats.yaml +++ b/examples/example-exporter-opentelemetry/oats-tests/http/oats.yaml @@ -1,5 +1,6 @@ # OATS is an acceptance testing framework for OpenTelemetry - # https://github.com/grafana/oats/tree/main/yaml +oats-schema-version: 2 docker-compose: files: - ./docker-compose.yml diff --git a/examples/example-exporter-opentelemetry/pom.xml b/examples/example-exporter-opentelemetry/pom.xml index 4c9d1d143..7c7501a57 100644 --- a/examples/example-exporter-opentelemetry/pom.xml +++ b/examples/example-exporter-opentelemetry/pom.xml @@ -4,34 +4,44 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 4.0.0 - - io.prometheus - examples - 1.4.0-SNAPSHOT - - + io.prometheus example-exporter-opentelemetry + 1.0-SNAPSHOT + + + 8 + UTF-8 + Example - OpenTelemetry Metrics Exporter Example of exposing metrics in OpenTelemetry format and pushing them to an OpenTelemetry collector + + + + io.prometheus + prometheus-metrics-bom + 1.5.0 + pom + import + + + + io.prometheus prometheus-metrics-core - ${project.version} io.prometheus prometheus-metrics-instrumentation-jvm - ${project.version} io.prometheus prometheus-metrics-exporter-opentelemetry - ${project.version} diff --git a/examples/example-exporter-servlet-tomcat/README.md b/examples/example-exporter-servlet-tomcat/README.md index 05ac894e3..01e76832d 100644 --- a/examples/example-exporter-servlet-tomcat/README.md +++ b/examples/example-exporter-servlet-tomcat/README.md @@ -71,7 +71,7 @@ browser: static_configs: - targets: ["localhost:8080"] ``` -4. Run with native histograms and examplars enabled: +4. Run with native histograms and exemplars enabled: ```shell ./prometheus --enable-feature=native-histograms --enable-feature=exemplar-storage ``` diff --git a/examples/example-exporter-servlet-tomcat/pom.xml b/examples/example-exporter-servlet-tomcat/pom.xml index efb8cdd63..ab5ba0198 100644 --- a/examples/example-exporter-servlet-tomcat/pom.xml +++ b/examples/example-exporter-servlet-tomcat/pom.xml @@ -3,43 +3,49 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 4.0.0 - - io.prometheus - examples - 1.4.0-SNAPSHOT - - + io.prometheus example-exporter-servlet-tomcat + 1.0-SNAPSHOT + + + 17 + UTF-8 + Example - Servlet Exporter with Tomcat Prometheus Metrics Example using Embedded Tomcat and the Exporter Servlet - - 17 - + + + + io.prometheus + prometheus-metrics-bom + 1.5.0 + pom + import + + + io.prometheus prometheus-metrics-core - ${project.version} io.prometheus prometheus-metrics-instrumentation-jvm - ${project.version} io.prometheus prometheus-metrics-exporter-servlet-jakarta - ${project.version} org.apache.tomcat.embed tomcat-embed-core - 11.0.9 + 11.0.18 diff --git a/examples/example-native-histogram/docker-compose.yaml b/examples/example-native-histogram/docker-compose.yaml index 8490934bf..492ce015f 100644 --- a/examples/example-native-histogram/docker-compose.yaml +++ b/examples/example-native-histogram/docker-compose.yaml @@ -1,7 +1,7 @@ version: "3" services: example-application: - image: eclipse-temurin:21.0.7_6-jre@sha256:bca347dc76e38a60a1a01b29a7d1312e514603a97ba594268e5a2e4a1a0c9a8f + image: eclipse-temurin:25.0.2_10-jre@sha256:0a9c973778b03b88f39ccae4f8cc26022d84a3237a818cb98770369eb6c5daf9 network_mode: host volumes: - ./target/example-native-histogram.jar:/example-native-histogram.jar @@ -10,7 +10,7 @@ services: - -jar - /example-native-histogram.jar prometheus: - image: prom/prometheus:v3.5.0@sha256:63805ebb8d2b3920190daf1cb14a60871b16fd38bed42b857a3182bc621f4996 + image: prom/prometheus:v3.9.1@sha256:1f0f50f06acaceb0f5670d2c8a658a599affe7b0d8e78b898c1035653849a702 network_mode: host volumes: - ./docker-compose/prometheus.yml:/prometheus.yml @@ -18,7 +18,7 @@ services: - --enable-feature=native-histograms - --config.file=/prometheus.yml grafana: - image: grafana/grafana:12.0.2@sha256:b5b59bfc7561634c2d7b136c4543d702ebcc94a3da477f21ff26f89ffd4214fa + image: grafana/grafana:12.3.3@sha256:9e1e77ade304069aee3196e9a4f210830e96e80ce9a2640891eccc324b152faf network_mode: host volumes: - ./docker-compose/grafana-datasources.yaml:/etc/grafana/provisioning/datasources/grafana-datasources.yaml diff --git a/examples/example-native-histogram/pom.xml b/examples/example-native-histogram/pom.xml index 9ad73b092..7d799cc52 100644 --- a/examples/example-native-histogram/pom.xml +++ b/examples/example-native-histogram/pom.xml @@ -3,34 +3,44 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 4.0.0 - - io.prometheus - examples - 1.4.0-SNAPSHOT - - + io.prometheus example-native-histogram + 1.0-SNAPSHOT + + + 8 + UTF-8 + Example - Native Histogram End-to-End example of a Native histogram: Java app -> Prometheus -> Grafana + + + + io.prometheus + prometheus-metrics-bom + 1.5.0 + pom + import + + + + io.prometheus prometheus-metrics-core - ${project.version} io.prometheus prometheus-metrics-instrumentation-jvm - ${project.version} io.prometheus prometheus-metrics-exporter-httpserver - ${project.version} diff --git a/examples/example-otel-jvm-runtime-metrics/README.md b/examples/example-otel-jvm-runtime-metrics/README.md new file mode 100644 index 000000000..a58584694 --- /dev/null +++ b/examples/example-otel-jvm-runtime-metrics/README.md @@ -0,0 +1,41 @@ +# OTel JVM Runtime Metrics with Prometheus HTTPServer + +## Build + +This example is built as part of the `client_java` project. + +```shell +./mvnw package +``` + +## Run + +The build creates a JAR file with the example application in +`./examples/example-otel-jvm-runtime-metrics/target/`. + +```shell +java -jar ./examples/example-otel-jvm-runtime-metrics/target/example-otel-jvm-runtime-metrics.jar +``` + +## Manually Testing the Metrics Endpoint + +Accessing +[http://localhost:9400/metrics](http://localhost:9400/metrics) +with a Web browser should yield both a Prometheus counter metric +and OTel JVM runtime metrics on the same endpoint. + +Prometheus counter: + +```text +# HELP uptime_seconds_total total number of seconds since this application was started +# TYPE uptime_seconds_total counter +uptime_seconds_total 42.0 +``` + +OTel JVM runtime metrics (excerpt): + +```text +# HELP jvm_memory_used_bytes Measure of memory used. +# TYPE jvm_memory_used_bytes gauge +jvm_memory_used_bytes{jvm_memory_pool_name="G1 Eden Space",jvm_memory_type="heap"} 4194304.0 +``` diff --git a/examples/example-otel-jvm-runtime-metrics/pom.xml b/examples/example-otel-jvm-runtime-metrics/pom.xml new file mode 100644 index 000000000..58869bfd7 --- /dev/null +++ b/examples/example-otel-jvm-runtime-metrics/pom.xml @@ -0,0 +1,85 @@ + + + 4.0.0 + + io.prometheus + example-otel-jvm-runtime-metrics + 1.0-SNAPSHOT + + + 8 + UTF-8 + + + Example - OTel JVM Runtime Metrics + + Example of combining Prometheus metrics with OpenTelemetry JVM runtime metrics on one endpoint + + + + + + io.prometheus + prometheus-metrics-bom + 1.5.0 + pom + import + + + io.opentelemetry.instrumentation + opentelemetry-instrumentation-bom-alpha + 2.25.0-alpha + pom + import + + + + + + + io.prometheus + prometheus-metrics-core + + + io.prometheus + prometheus-metrics-exporter-httpserver + + + io.prometheus + prometheus-metrics-otel-support + pom + + + io.opentelemetry.instrumentation + opentelemetry-runtime-telemetry-java8 + + + + + ${project.artifactId} + + + org.apache.maven.plugins + maven-shade-plugin + + + package + + shade + + + + + io.prometheus.metrics.examples.otelruntimemetrics.Main + + + + + + + + + diff --git a/examples/example-otel-jvm-runtime-metrics/src/main/java/io/prometheus/metrics/examples/otelruntimemetrics/Main.java b/examples/example-otel-jvm-runtime-metrics/src/main/java/io/prometheus/metrics/examples/otelruntimemetrics/Main.java new file mode 100644 index 000000000..49a608651 --- /dev/null +++ b/examples/example-otel-jvm-runtime-metrics/src/main/java/io/prometheus/metrics/examples/otelruntimemetrics/Main.java @@ -0,0 +1,76 @@ +package io.prometheus.metrics.examples.otelruntimemetrics; + +import io.opentelemetry.exporter.prometheus.PrometheusMetricReader; +import io.opentelemetry.instrumentation.runtimemetrics.java8.RuntimeMetrics; +import io.opentelemetry.sdk.OpenTelemetrySdk; +import io.opentelemetry.sdk.metrics.SdkMeterProvider; +import io.prometheus.metrics.core.metrics.Counter; +import io.prometheus.metrics.exporter.httpserver.HTTPServer; +import io.prometheus.metrics.model.registry.PrometheusRegistry; +import io.prometheus.metrics.model.snapshots.Unit; +import java.io.IOException; + +/** + * Example combining Prometheus metrics with OpenTelemetry JVM runtime metrics on a single endpoint. + * + *

This demonstrates: + * + *

    + *
  • Registering a Prometheus counter metric + *
  • Bridging OTel runtime metrics into the same PrometheusRegistry + *
  • Exposing everything via the built-in HTTPServer on /metrics + *
+ */ +public class Main { + + public static void main(String[] args) throws IOException, InterruptedException { + + PrometheusRegistry registry = new PrometheusRegistry(); + + // 1. Register a Prometheus counter metric + Counter counter = + Counter.builder() + .name("uptime_seconds_total") + .help("total number of seconds since this application was started") + .unit(Unit.SECONDS) + .register(registry); + + // 2. Create a PrometheusMetricReader and register it with the same registry. + // This bridges OTel metrics into the Prometheus registry. + PrometheusMetricReader reader = PrometheusMetricReader.create(); + registry.register(reader); + + // 3. Build the OTel SDK with the reader. + OpenTelemetrySdk openTelemetry = + OpenTelemetrySdk.builder() + .setMeterProvider(SdkMeterProvider.builder().registerMetricReader(reader).build()) + .build(); + + // 4. Start OTel JVM runtime metrics collection. + // - captureGcCause() adds a jvm.gc.cause attribute to jvm.gc.duration + // - emitExperimentalTelemetry() enables buffer pools, extended CPU, + // extended memory pools, and file descriptor metrics + RuntimeMetrics runtimeMetrics = + RuntimeMetrics.builder(openTelemetry).captureGcCause().emitExperimentalTelemetry().build(); + + // 5. Expose both Prometheus and OTel metrics on a single endpoint. + HTTPServer server = HTTPServer.builder().port(9400).registry(registry).buildAndStart(); + + // 6. Close RuntimeMetrics and server on shutdown to stop JMX metric collection. + Runtime.getRuntime() + .addShutdownHook( + new Thread( + () -> { + runtimeMetrics.close(); + server.close(); + })); + + System.out.println( + "HTTPServer listening on port http://localhost:" + server.getPort() + "/metrics"); + + while (true) { + Thread.sleep(1000); + counter.inc(); + } + } +} diff --git a/examples/example-prometheus-properties/pom.xml b/examples/example-prometheus-properties/pom.xml index 4556e7f73..bedd517f7 100644 --- a/examples/example-prometheus-properties/pom.xml +++ b/examples/example-prometheus-properties/pom.xml @@ -3,34 +3,44 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 4.0.0 - - io.prometheus - examples - 1.4.0-SNAPSHOT - - + io.prometheus example-prometheus-properties + 1.0-SNAPSHOT + + + 8 + UTF-8 + Example - prometheus.properties Example of runtime configuration with prometheus.properties + + + + io.prometheus + prometheus-metrics-bom + 1.5.0 + pom + import + + + + io.prometheus prometheus-metrics-core - ${project.version} io.prometheus prometheus-metrics-instrumentation-jvm - ${project.version} io.prometheus prometheus-metrics-exporter-httpserver - ${project.version} diff --git a/examples/example-prometheus-properties/src/main/resources/prometheus.properties b/examples/example-prometheus-properties/src/main/resources/prometheus.properties index a786fd370..be895f2fe 100644 --- a/examples/example-prometheus-properties/src/main/resources/prometheus.properties +++ b/examples/example-prometheus-properties/src/main/resources/prometheus.properties @@ -1,8 +1,8 @@ -io.prometheus.exporter.httpServer.port = 9401 -io.prometheus.exporter.includeCreatedTimestamps = true +io.prometheus.exporter.http_server.port = 9401 +io.prometheus.exporter.include_created_timestamps = true # Set a new default for all histograms -io.prometheus.metrics.histogramClassicUpperBounds = .2, .4, .8, .1 +io.prometheus.metrics.histogram_classic_upper_bounds = .2, .4, .8, .1 # Override the default for one specific histogram -io.prometheus.metrics.request_size_bytes.histogramClassicUpperBounds = 256, 512, 768, 1024 +io.prometheus.metrics.request_size_bytes.histogram_classic_upper_bounds = 256, 512, 768, 1024 diff --git a/examples/example-simpleclient-bridge/pom.xml b/examples/example-simpleclient-bridge/pom.xml index f5e465d06..d0edb60cf 100644 --- a/examples/example-simpleclient-bridge/pom.xml +++ b/examples/example-simpleclient-bridge/pom.xml @@ -3,19 +3,32 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 4.0.0 - - io.prometheus - examples - 1.4.0-SNAPSHOT - - + io.prometheus example-simpleclient-bridge + 1.0-SNAPSHOT + + + 8 + UTF-8 + Example - Simpleclient Bridge Prometheus Metrics Example of the Simpleclient Backwards Compatibility module + + + + io.prometheus + prometheus-metrics-bom + 1.5.0 + pom + import + + + + io.prometheus @@ -25,12 +38,10 @@ io.prometheus prometheus-metrics-simpleclient-bridge - ${project.version} io.prometheus prometheus-metrics-exporter-httpserver - ${project.version} diff --git a/examples/pom.xml b/examples/pom.xml index b5f8886ce..e033a3d1e 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -6,7 +6,7 @@ io.prometheus client_java - 1.4.0-SNAPSHOT + 1.6.0-SNAPSHOT examples @@ -19,6 +19,7 @@ true + true @@ -29,7 +30,9 @@ example-exporter-opentelemetry example-simpleclient-bridge example-native-histogram + example-custom-buckets example-prometheus-properties + example-otel-jvm-runtime-metrics diff --git a/integration-tests/it-common/pom.xml b/integration-tests/it-common/pom.xml index 5586a7f9a..9ae86303c 100644 --- a/integration-tests/it-common/pom.xml +++ b/integration-tests/it-common/pom.xml @@ -6,7 +6,7 @@ io.prometheus integration-tests - 1.4.0-SNAPSHOT + 1.6.0-SNAPSHOT it-common diff --git a/integration-tests/it-common/src/test/java/io/prometheus/client/it/common/ExporterTest.java b/integration-tests/it-common/src/test/java/io/prometheus/client/it/common/ExporterTest.java index 00a3d544f..91a7ed712 100644 --- a/integration-tests/it-common/src/test/java/io/prometheus/client/it/common/ExporterTest.java +++ b/integration-tests/it-common/src/test/java/io/prometheus/client/it/common/ExporterTest.java @@ -4,13 +4,13 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.fail; -import io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics; +import io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.net.HttpURLConnection; +import java.net.URI; import java.net.URISyntaxException; -import java.net.URL; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -24,7 +24,7 @@ import org.testcontainers.containers.GenericContainer; public abstract class ExporterTest { - private final GenericContainer sampleAppContainer; + protected final GenericContainer sampleAppContainer; private final Volume sampleAppVolume; protected final String sampleApp; @@ -33,8 +33,12 @@ public ExporterTest(String sampleApp) throws IOException, URISyntaxException { this.sampleAppVolume = Volume.create("it-exporter") .copy("../../it-" + sampleApp + "/target/" + sampleApp + ".jar"); + String javaVersion = System.getenv("TEST_JAVA_VERSION"); + if (javaVersion == null || javaVersion.isEmpty()) { + javaVersion = "25"; + } this.sampleAppContainer = - new GenericContainer<>("openjdk:17") + new GenericContainer<>("eclipse-temurin:" + javaVersion) .withFileSystemBind(sampleAppVolume.getHostPath(), "/app", BindMode.READ_ONLY) .withWorkingDirectory("/app") .withLogConsumer(LogConsumer.withPrefix(sampleApp)) @@ -53,7 +57,7 @@ protected void start(String outcome) { } @AfterEach - public void tearDown() throws IOException { + void tearDown() throws IOException { sampleAppContainer.stop(); sampleAppVolume.remove(); } @@ -68,7 +72,7 @@ protected Response scrape(String method, String queryString, String... requestHe throws IOException { return scrape( method, - new URL( + URI.create( "http://localhost:" + sampleAppContainer.getMappedPort(9400) + "/metrics?" @@ -76,10 +80,10 @@ protected Response scrape(String method, String queryString, String... requestHe requestHeaders); } - public static Response scrape(String method, URL url, String... requestHeaders) + public static Response scrape(String method, URI uri, String... requestHeaders) throws IOException { long timeoutMillis = TimeUnit.SECONDS.toMillis(5); - HttpURLConnection con = (HttpURLConnection) url.openConnection(); + HttpURLConnection con = (HttpURLConnection) uri.toURL().openConnection(); con.setRequestMethod(method); for (int i = 0; i < requestHeaders.length; i += 2) { con.setRequestProperty(requestHeaders[i], requestHeaders[i + 1]); @@ -111,7 +115,7 @@ public static Response scrape(String method, URL url, String... requestHeaders) if (exception != null) { exception.printStackTrace(); } - fail("timeout while getting metrics from " + url); + fail("timeout while getting metrics from " + uri); return null; // will not happen } diff --git a/integration-tests/it-exporter/it-exporter-duplicate-metrics-sample/pom.xml b/integration-tests/it-exporter/it-exporter-duplicate-metrics-sample/pom.xml new file mode 100644 index 000000000..13364ec5d --- /dev/null +++ b/integration-tests/it-exporter/it-exporter-duplicate-metrics-sample/pom.xml @@ -0,0 +1,59 @@ + + + 4.0.0 + + + io.prometheus + it-exporter + 1.6.0-SNAPSHOT + + + it-exporter-duplicate-metrics-sample + + Integration Tests - Duplicate Metrics Sample + + HTTPServer Sample demonstrating duplicate metric names with different label sets + + + + + io.prometheus + prometheus-metrics-exporter-httpserver + ${project.version} + + + io.prometheus + prometheus-metrics-core + ${project.version} + + + + + exporter-duplicate-metrics-sample + + + org.apache.maven.plugins + maven-shade-plugin + + + package + + shade + + + + + + io.prometheus.metrics.it.exporter.duplicatemetrics.DuplicateMetricsSample + + + + + + + + + + \ No newline at end of file diff --git a/integration-tests/it-exporter/it-exporter-duplicate-metrics-sample/src/main/java/io/prometheus/metrics/it/exporter/duplicatemetrics/DuplicateMetricsSample.java b/integration-tests/it-exporter/it-exporter-duplicate-metrics-sample/src/main/java/io/prometheus/metrics/it/exporter/duplicatemetrics/DuplicateMetricsSample.java new file mode 100644 index 000000000..c6005674a --- /dev/null +++ b/integration-tests/it-exporter/it-exporter-duplicate-metrics-sample/src/main/java/io/prometheus/metrics/it/exporter/duplicatemetrics/DuplicateMetricsSample.java @@ -0,0 +1,91 @@ +package io.prometheus.metrics.it.exporter.duplicatemetrics; + +import io.prometheus.metrics.core.metrics.Counter; +import io.prometheus.metrics.core.metrics.Gauge; +import io.prometheus.metrics.exporter.httpserver.HTTPServer; +import io.prometheus.metrics.model.snapshots.Unit; +import java.io.IOException; + +/** Integration test sample demonstrating metrics with duplicate names but different label sets. */ +public class DuplicateMetricsSample { + + public static void main(String[] args) throws IOException, InterruptedException { + if (args.length != 2) { + System.err.println("Usage: java -jar duplicate-metrics-sample.jar "); + System.err.println("Where outcome is \"success\" or \"error\"."); + System.exit(1); + } + + int port = parsePortOrExit(args[0]); + String outcome = args[1]; + run(port, outcome); + } + + private static void run(int port, String outcome) throws IOException, InterruptedException { + // Register multiple counters with the same Prometheus name "http_requests_total" + // but different label sets + Counter requestsSuccess = + Counter.builder() + .name("http_requests_total") + .help("Total HTTP requests by status") + .labelNames("status", "method") + .register(); + requestsSuccess.labelValues("success", "GET").inc(150); + requestsSuccess.labelValues("success", "POST").inc(45); + + Counter requestsError = + Counter.builder() + .name("http_requests_total") + .help("Total HTTP requests by status") + .labelNames("status", "endpoint") + .register(); + requestsError.labelValues("error", "/api").inc(5); + requestsError.labelValues("error", "/health").inc(2); + + // Register multiple gauges with the same Prometheus name "active_connections" + // but different label sets + Gauge connectionsByRegion = + Gauge.builder() + .name("active_connections") + .help("Active connections") + .labelNames("region", "protocol") + .register(); + connectionsByRegion.labelValues("us-east", "http").set(42); + connectionsByRegion.labelValues("us-west", "http").set(38); + connectionsByRegion.labelValues("eu-west", "https").set(55); + + Gauge connectionsByPool = + Gauge.builder() + .name("active_connections") + .help("Active connections") + .labelNames("pool", "type") + .register(); + connectionsByPool.labelValues("primary", "read").set(30); + connectionsByPool.labelValues("replica", "write").set(10); + + // Also add a regular metric without duplicates for reference + Counter uniqueMetric = + Counter.builder() + .name("unique_metric_total") + .help("A unique metric for reference") + .unit(Unit.BYTES) + .register(); + uniqueMetric.inc(1024); + + HTTPServer server = HTTPServer.builder().port(port).buildAndStart(); + + System.out.println( + "DuplicateMetricsSample listening on http://localhost:" + server.getPort() + "/metrics"); + Thread.currentThread().join(); // wait forever + } + + private static int parsePortOrExit(String port) { + try { + return Integer.parseInt(port); + } catch (NumberFormatException e) { + System.err.println("\"" + port + "\": Invalid port number."); + System.exit(1); + } + return 0; // this won't happen + } +} diff --git a/integration-tests/it-exporter/it-exporter-httpserver-sample/pom.xml b/integration-tests/it-exporter/it-exporter-httpserver-sample/pom.xml index 08f3e392f..3dfa05f07 100644 --- a/integration-tests/it-exporter/it-exporter-httpserver-sample/pom.xml +++ b/integration-tests/it-exporter/it-exporter-httpserver-sample/pom.xml @@ -6,7 +6,7 @@ io.prometheus it-exporter - 1.4.0-SNAPSHOT + 1.6.0-SNAPSHOT it-exporter-httpserver-sample diff --git a/integration-tests/it-exporter/it-exporter-no-protobuf/pom.xml b/integration-tests/it-exporter/it-exporter-no-protobuf/pom.xml index 5e2c64ce3..6bfd7c5cb 100644 --- a/integration-tests/it-exporter/it-exporter-no-protobuf/pom.xml +++ b/integration-tests/it-exporter/it-exporter-no-protobuf/pom.xml @@ -6,7 +6,7 @@ io.prometheus it-exporter - 1.4.0-SNAPSHOT + 1.6.0-SNAPSHOT it-exporter-no-protobuf diff --git a/integration-tests/it-exporter/it-exporter-servlet-jetty-sample/pom.xml b/integration-tests/it-exporter/it-exporter-servlet-jetty-sample/pom.xml index fef7042df..ed5285769 100644 --- a/integration-tests/it-exporter/it-exporter-servlet-jetty-sample/pom.xml +++ b/integration-tests/it-exporter/it-exporter-servlet-jetty-sample/pom.xml @@ -6,7 +6,7 @@ io.prometheus it-exporter - 1.4.0-SNAPSHOT + 1.6.0-SNAPSHOT it-exporter-servlet-jetty-sample @@ -16,8 +16,8 @@ Jetty Sample for the Exporter Integration Test - 12.0.23 - 17 + 12.1.6 + 25 diff --git a/integration-tests/it-exporter/it-exporter-servlet-tomcat-sample/pom.xml b/integration-tests/it-exporter/it-exporter-servlet-tomcat-sample/pom.xml index 16124705e..be9c7704d 100644 --- a/integration-tests/it-exporter/it-exporter-servlet-tomcat-sample/pom.xml +++ b/integration-tests/it-exporter/it-exporter-servlet-tomcat-sample/pom.xml @@ -6,7 +6,7 @@ io.prometheus it-exporter - 1.4.0-SNAPSHOT + 1.6.0-SNAPSHOT it-exporter-servlet-tomcat-sample @@ -17,7 +17,7 @@ - 17 + 25 @@ -34,7 +34,7 @@ org.apache.tomcat.embed tomcat-embed-core - 11.0.9 + 11.0.18 diff --git a/integration-tests/it-exporter/it-exporter-test/pom.xml b/integration-tests/it-exporter/it-exporter-test/pom.xml index b50529468..027631fbd 100644 --- a/integration-tests/it-exporter/it-exporter-test/pom.xml +++ b/integration-tests/it-exporter/it-exporter-test/pom.xml @@ -6,7 +6,7 @@ io.prometheus it-exporter - 1.4.0-SNAPSHOT + 1.6.0-SNAPSHOT it-exporter-test diff --git a/integration-tests/it-exporter/it-exporter-test/src/test/java/io/prometheus/metrics/it/exporter/test/DuplicateMetricsIT.java b/integration-tests/it-exporter/it-exporter-test/src/test/java/io/prometheus/metrics/it/exporter/test/DuplicateMetricsIT.java new file mode 100644 index 000000000..7530070ac --- /dev/null +++ b/integration-tests/it-exporter/it-exporter-test/src/test/java/io/prometheus/metrics/it/exporter/test/DuplicateMetricsIT.java @@ -0,0 +1,181 @@ +package io.prometheus.metrics.it.exporter.test; + +import static org.assertj.core.api.Assertions.assertThat; + +import io.prometheus.client.it.common.ExporterTest; +import io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics; +import java.io.IOException; +import java.net.URISyntaxException; +import java.util.List; +import org.junit.jupiter.api.Test; + +class DuplicateMetricsIT extends ExporterTest { + + public DuplicateMetricsIT() throws IOException, URISyntaxException { + super("exporter-duplicate-metrics-sample"); + } + + @Test + void testDuplicateMetricsInPrometheusTextFormat() throws IOException { + start(); + Response response = scrape("GET", ""); + assertThat(response.status).isEqualTo(200); + assertContentType( + "text/plain; version=0.0.4; charset=utf-8", response.getHeader("Content-Type")); + + String expected = + """ + # HELP active_connections Active connections + # TYPE active_connections gauge + active_connections{pool="primary",type="read"} 30.0 + active_connections{pool="replica",type="write"} 10.0 + active_connections{protocol="http",region="us-east"} 42.0 + active_connections{protocol="http",region="us-west"} 38.0 + active_connections{protocol="https",region="eu-west"} 55.0 + # HELP http_requests_total Total HTTP requests by status + # TYPE http_requests_total counter + http_requests_total{endpoint="/api",status="error"} 5.0 + http_requests_total{endpoint="/health",status="error"} 2.0 + http_requests_total{method="GET",status="success"} 150.0 + http_requests_total{method="POST",status="success"} 45.0 + # HELP unique_metric_bytes_total A unique metric for reference + # TYPE unique_metric_bytes_total counter + unique_metric_bytes_total 1024.0 + """; + + assertThat(response.stringBody()).isEqualTo(expected); + } + + @Test + void testDuplicateMetricsInOpenMetricsTextFormat() throws IOException { + start(); + Response response = + scrape("GET", "", "Accept", "application/openmetrics-text; version=1.0.0; charset=utf-8"); + assertThat(response.status).isEqualTo(200); + assertContentType( + "application/openmetrics-text; version=1.0.0; charset=utf-8", + response.getHeader("Content-Type")); + + // OpenMetrics format should have UNIT for unique_metric_bytes (base name without _total) + String expected = + """ + # TYPE active_connections gauge + # HELP active_connections Active connections + active_connections{pool="primary",type="read"} 30.0 + active_connections{pool="replica",type="write"} 10.0 + active_connections{protocol="http",region="us-east"} 42.0 + active_connections{protocol="http",region="us-west"} 38.0 + active_connections{protocol="https",region="eu-west"} 55.0 + # TYPE http_requests counter + # HELP http_requests Total HTTP requests by status + http_requests_total{endpoint="/api",status="error"} 5.0 + http_requests_total{endpoint="/health",status="error"} 2.0 + http_requests_total{method="GET",status="success"} 150.0 + http_requests_total{method="POST",status="success"} 45.0 + # TYPE unique_metric_bytes counter + # UNIT unique_metric_bytes bytes + # HELP unique_metric_bytes A unique metric for reference + unique_metric_bytes_total 1024.0 + # EOF + """; + + assertThat(response.stringBody()).isEqualTo(expected); + } + + @Test + void testDuplicateMetricsInPrometheusProtobufFormat() throws IOException { + start(); + Response response = + scrape( + "GET", + "", + "Accept", + "application/vnd.google.protobuf; proto=io.prometheus.client.MetricFamily;" + + " encoding=delimited"); + assertThat(response.status).isEqualTo(200); + assertContentType( + "application/vnd.google.protobuf; proto=io.prometheus.client.MetricFamily;" + + " encoding=delimited", + response.getHeader("Content-Type")); + + List metrics = response.protoBody(); + + assertThat(metrics).hasSize(3); + + // Metrics are sorted by name + assertThat(metrics.get(0).getName()).isEqualTo("active_connections"); + assertThat(metrics.get(1).getName()).isEqualTo("http_requests_total"); + assertThat(metrics.get(2).getName()).isEqualTo("unique_metric_bytes_total"); + + // Verify active_connections has all 5 data points merged + Metrics.MetricFamily activeConnections = metrics.get(0); + assertThat(activeConnections.getType()).isEqualTo(Metrics.MetricType.GAUGE); + assertThat(activeConnections.getHelp()).isEqualTo("Active connections"); + assertThat(activeConnections.getMetricList()).hasSize(5); + + // Verify http_requests_total has all 4 data points merged + Metrics.MetricFamily httpRequests = metrics.get(1); + assertThat(httpRequests.getType()).isEqualTo(Metrics.MetricType.COUNTER); + assertThat(httpRequests.getHelp()).isEqualTo("Total HTTP requests by status"); + assertThat(httpRequests.getMetricList()).hasSize(4); + + // Verify each data point has the expected labels + boolean foundSuccessGet = false; + boolean foundSuccessPost = false; + boolean foundErrorApi = false; + boolean foundErrorHealth = false; + + for (Metrics.Metric metric : httpRequests.getMetricList()) { + List labels = metric.getLabelList(); + if (hasLabel(labels, "status", "success") && hasLabel(labels, "method", "GET")) { + assertThat(metric.getCounter().getValue()).isEqualTo(150.0); + foundSuccessGet = true; + } else if (hasLabel(labels, "status", "success") && hasLabel(labels, "method", "POST")) { + assertThat(metric.getCounter().getValue()).isEqualTo(45.0); + foundSuccessPost = true; + } else if (hasLabel(labels, "status", "error") && hasLabel(labels, "endpoint", "/api")) { + assertThat(metric.getCounter().getValue()).isEqualTo(5.0); + foundErrorApi = true; + } else if (hasLabel(labels, "status", "error") && hasLabel(labels, "endpoint", "/health")) { + assertThat(metric.getCounter().getValue()).isEqualTo(2.0); + foundErrorHealth = true; + } + } + + assertThat(foundSuccessGet).isTrue(); + assertThat(foundSuccessPost).isTrue(); + assertThat(foundErrorApi).isTrue(); + assertThat(foundErrorHealth).isTrue(); + + Metrics.MetricFamily uniqueMetric = metrics.get(2); + assertThat(uniqueMetric.getType()).isEqualTo(Metrics.MetricType.COUNTER); + assertThat(uniqueMetric.getMetricList()).hasSize(1); + assertThat(uniqueMetric.getMetric(0).getCounter().getValue()).isEqualTo(1024.0); + } + + @Test + void testDuplicateMetricsWithNameFilter() throws IOException { + start(); + // Only scrape http_requests_total + Response response = scrape("GET", nameParam()); + assertThat(response.status).isEqualTo(200); + + String body = response.stringBody(); + + assertThat(body) + .contains("http_requests_total{method=\"GET\",status=\"success\"} 150.0") + .contains("http_requests_total{endpoint=\"/api\",status=\"error\"} 5.0"); + + // Should NOT contain active_connections or unique_metric_total + assertThat(body).doesNotContain("active_connections").doesNotContain("unique_metric_total"); + } + + private boolean hasLabel(List labels, String name, String value) { + return labels.stream() + .anyMatch(label -> label.getName().equals(name) && label.getValue().equals(value)); + } + + private String nameParam() { + return "name[]=" + "http_requests_total"; + } +} diff --git a/integration-tests/it-exporter/it-exporter-test/src/test/java/io/prometheus/metrics/it/exporter/test/ExporterIT.java b/integration-tests/it-exporter/it-exporter-test/src/test/java/io/prometheus/metrics/it/exporter/test/ExporterIT.java index 1ab3f3237..e9bcc2ee7 100644 --- a/integration-tests/it-exporter/it-exporter-test/src/test/java/io/prometheus/metrics/it/exporter/test/ExporterIT.java +++ b/integration-tests/it-exporter/it-exporter-test/src/test/java/io/prometheus/metrics/it/exporter/test/ExporterIT.java @@ -5,11 +5,12 @@ import com.google.common.io.Resources; import io.prometheus.client.it.common.ExporterTest; -import io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics; +import io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics; import java.io.IOException; import java.net.URISyntaxException; import java.net.URLEncoder; import java.util.List; +import java.util.regex.Pattern; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.CsvSource; @@ -21,7 +22,7 @@ public ExporterIT(String sampleApp) throws IOException, URISyntaxException { } @Test - public void testOpenMetricsTextFormat() throws IOException { + void testOpenMetricsTextFormat() throws IOException { start(); Response response = scrape("GET", "", "Accept", "application/openmetrics-text; version=1.0.0; charset=utf-8"); @@ -43,7 +44,7 @@ public void testOpenMetricsTextFormat() throws IOException { } @Test - public void testPrometheusTextFormat() throws IOException { + void testPrometheusTextFormat() throws IOException { start(); Response response = scrape("GET", ""); assertThat(response.status).isEqualTo(200); @@ -63,7 +64,7 @@ public void testPrometheusTextFormat() throws IOException { } @Test - public void testPrometheusProtobufFormat() throws IOException { + void testPrometheusProtobufFormat() throws IOException { start(); Response response = scrape( @@ -101,15 +102,26 @@ public void testPrometheusProtobufDebugFormat(String format, String expected) th assertThat(response.status).isEqualTo(200); assertContentType( "text/plain;charset=utf-8", response.getHeader("Content-Type").replace(" ", "")); - assertThat(response.stringBody().trim()) - .isEqualTo( - Resources.toString(Resources.getResource(expected), UTF_8) - .trim() - .replace("", sampleApp)); + + String actualResponse = response.stringBody().trim(); + String expectedResponse = + Resources.toString(Resources.getResource(expected), UTF_8) + .trim() + .replace("", sampleApp); + + if ("prometheus-protobuf".equals(format)) { + assertThat(actualResponse) + .matches( + Pattern.quote(expectedResponse) + .replace("", "\\E\\d+\\Q") + .replace("", "\\E\\d+\\Q")); + } else { + assertThat(actualResponse).isEqualTo(expectedResponse); + } } @Test - public void testCompression() throws IOException { + void testCompression() throws IOException { start(); Response response = scrape( @@ -137,7 +149,7 @@ public void testCompression() throws IOException { } @Test - public void testErrorHandling() throws IOException { + void testErrorHandling() throws IOException { start("error"); Response response = scrape("GET", ""); assertThat(response.status).isEqualTo(500); @@ -145,7 +157,7 @@ public void testErrorHandling() throws IOException { } @Test - public void testHeadRequest() throws IOException { + void testHeadRequest() throws IOException { start(); Response fullResponse = scrape("GET", ""); int size = fullResponse.body.length; @@ -157,7 +169,7 @@ public void testHeadRequest() throws IOException { } @Test - public void testDebug() throws IOException { + void testDebug() throws IOException { start(); Response response = scrape("GET", "debug=openmetrics"); assertThat(response.status).isEqualTo(200); @@ -168,7 +180,7 @@ public void testDebug() throws IOException { } @Test - public void testNameFilter() throws IOException { + void testNameFilter() throws IOException { start(); Response response = scrape( @@ -187,7 +199,7 @@ public void testNameFilter() throws IOException { } @Test - public void testEmptyResponseOpenMetrics() throws IOException { + void testEmptyResponseOpenMetrics() throws IOException { start(); Response response = scrape( @@ -205,7 +217,7 @@ public void testEmptyResponseOpenMetrics() throws IOException { } @Test - public void testEmptyResponseText() throws IOException { + void testEmptyResponseText() throws IOException { start(); Response response = scrape("GET", nameParam("none_existing")); assertThat(response.status).isEqualTo(200); @@ -219,7 +231,7 @@ public void testEmptyResponseText() throws IOException { } @Test - public void testEmptyResponseProtobuf() throws IOException { + void testEmptyResponseProtobuf() throws IOException { start(); Response response = scrape( @@ -237,7 +249,7 @@ public void testEmptyResponseProtobuf() throws IOException { } @Test - public void testEmptyResponseGzipOpenMetrics() throws IOException { + void testEmptyResponseGzipOpenMetrics() throws IOException { start(); Response response = scrape( @@ -253,7 +265,7 @@ public void testEmptyResponseGzipOpenMetrics() throws IOException { } @Test - public void testEmptyResponseGzipText() throws IOException { + void testEmptyResponseGzipText() throws IOException { start(); Response response = scrape("GET", nameParam("none_existing"), "Accept-Encoding", "gzip"); assertThat(response.status).isEqualTo(200); @@ -266,7 +278,7 @@ private String nameParam(String name) { } @Test - public void testDebugUnknown() throws IOException { + void testDebugUnknown() throws IOException { start(); Response response = scrape("GET", "debug=unknown"); assertThat(response.status).isEqualTo(500); diff --git a/integration-tests/it-exporter/it-exporter-test/src/test/resources/debug-protobuf.txt b/integration-tests/it-exporter/it-exporter-test/src/test/resources/debug-protobuf.txt index 1d7603c1b..06f19b85c 100644 --- a/integration-tests/it-exporter/it-exporter-test/src/test/resources/debug-protobuf.txt +++ b/integration-tests/it-exporter/it-exporter-test/src/test/resources/debug-protobuf.txt @@ -37,6 +37,10 @@ type: COUNTER metric { counter { value: 17.0 + created_timestamp { + seconds: + nanos: + } } } diff --git a/integration-tests/it-exporter/it-no-protobuf-test/pom.xml b/integration-tests/it-exporter/it-no-protobuf-test/pom.xml index 98a58f84e..1a17b83dc 100644 --- a/integration-tests/it-exporter/it-no-protobuf-test/pom.xml +++ b/integration-tests/it-exporter/it-no-protobuf-test/pom.xml @@ -6,7 +6,7 @@ io.prometheus it-exporter - 1.4.0-SNAPSHOT + 1.6.0-SNAPSHOT it-no-protobuf-test diff --git a/integration-tests/it-exporter/it-no-protobuf-test/src/test/java/io/prometheus/metrics/it/noprotobuf/NoProtobufIT.java b/integration-tests/it-exporter/it-no-protobuf-test/src/test/java/io/prometheus/metrics/it/noprotobuf/NoProtobufIT.java index cd534dcb9..9b041795e 100644 --- a/integration-tests/it-exporter/it-no-protobuf-test/src/test/java/io/prometheus/metrics/it/noprotobuf/NoProtobufIT.java +++ b/integration-tests/it-exporter/it-no-protobuf-test/src/test/java/io/prometheus/metrics/it/noprotobuf/NoProtobufIT.java @@ -14,7 +14,7 @@ public NoProtobufIT() throws IOException, URISyntaxException { } @Test - public void testPrometheusProtobufDebugFormat() throws IOException { + void testPrometheusProtobufDebugFormat() throws IOException { start(); assertThat(scrape("GET", "debug=text").status).isEqualTo(200); // protobuf is not supported diff --git a/integration-tests/it-exporter/pom.xml b/integration-tests/it-exporter/pom.xml index 3dcb27f28..08386d8b0 100644 --- a/integration-tests/it-exporter/pom.xml +++ b/integration-tests/it-exporter/pom.xml @@ -6,7 +6,7 @@ io.prometheus integration-tests - 1.4.0-SNAPSHOT + 1.6.0-SNAPSHOT it-exporter @@ -21,6 +21,7 @@ it-exporter-servlet-tomcat-sample it-exporter-servlet-jetty-sample it-exporter-httpserver-sample + it-exporter-duplicate-metrics-sample it-exporter-no-protobuf it-exporter-test it-no-protobuf-test diff --git a/integration-tests/it-pushgateway/pom.xml b/integration-tests/it-pushgateway/pom.xml index b0b0bdc33..a70ea5458 100644 --- a/integration-tests/it-pushgateway/pom.xml +++ b/integration-tests/it-pushgateway/pom.xml @@ -6,7 +6,7 @@ io.prometheus integration-tests - 1.4.0-SNAPSHOT + 1.6.0-SNAPSHOT it-pushgateway @@ -48,7 +48,7 @@ com.jayway.jsonpath json-path - 2.9.0 + 2.10.0 test diff --git a/integration-tests/it-pushgateway/src/test/java/io/prometheus/metrics/it/pushgateway/PushGatewayIT.java b/integration-tests/it-pushgateway/src/test/java/io/prometheus/metrics/it/pushgateway/PushGatewayIT.java index beb83d7d9..3d31129f1 100644 --- a/integration-tests/it-pushgateway/src/test/java/io/prometheus/metrics/it/pushgateway/PushGatewayIT.java +++ b/integration-tests/it-pushgateway/src/test/java/io/prometheus/metrics/it/pushgateway/PushGatewayIT.java @@ -22,7 +22,7 @@ import org.testcontainers.containers.wait.strategy.Wait; import org.testcontainers.utility.MountableFile; -public class PushGatewayIT { +class PushGatewayIT { private GenericContainer sampleAppContainer; private GenericContainer pushGatewayContainer; @@ -30,9 +30,13 @@ public class PushGatewayIT { private Volume sampleAppVolume; @BeforeEach - public void setUp() throws IOException, URISyntaxException { + void setUp() throws IOException, URISyntaxException { Network network = Network.newNetwork(); sampleAppVolume = Volume.create("it-pushgateway").copy("pushgateway-test-app.jar"); + String javaVersion = System.getenv("TEST_JAVA_VERSION"); + if (javaVersion == null || javaVersion.isEmpty()) { + javaVersion = "25"; + } pushGatewayContainer = new GenericContainer<>("prom/pushgateway:v1.8.0") .withExposedPorts(9091) @@ -41,7 +45,7 @@ public void setUp() throws IOException, URISyntaxException { .withLogConsumer(LogConsumer.withPrefix("pushgateway")) .waitingFor(Wait.forListeningPort()); sampleAppContainer = - new GenericContainer<>("openjdk:17") + new GenericContainer<>("eclipse-temurin:" + javaVersion) .withFileSystemBind(sampleAppVolume.getHostPath(), "/app", BindMode.READ_ONLY) .withNetwork(network) .withWorkingDirectory("/app") @@ -56,7 +60,7 @@ public void setUp() throws IOException, URISyntaxException { } @AfterEach - public void tearDown() throws IOException { + void tearDown() throws IOException { prometheusContainer.stop(); pushGatewayContainer.stop(); sampleAppContainer.stop(); @@ -66,7 +70,7 @@ public void tearDown() throws IOException { final OkHttpClient client = new OkHttpClient(); @Test - public void testSimple() throws IOException, InterruptedException { + void testSimple() throws IOException, InterruptedException { pushGatewayContainer.start(); sampleAppContainer .withCommand( @@ -86,7 +90,7 @@ public void testSimple() throws IOException, InterruptedException { } @Test - public void testTextFormat() throws IOException, InterruptedException { + void testTextFormat() throws IOException, InterruptedException { pushGatewayContainer.start(); sampleAppContainer .withCommand( @@ -106,7 +110,7 @@ public void testTextFormat() throws IOException, InterruptedException { } @Test - public void testBasicAuth() throws IOException, InterruptedException { + void testBasicAuth() throws IOException, InterruptedException { pushGatewayContainer .withCopyFileToContainer( MountableFile.forClasspathResource("/pushgateway-basicauth.yaml"), @@ -131,7 +135,7 @@ public void testBasicAuth() throws IOException, InterruptedException { } @Test - public void testSsl() throws InterruptedException, IOException { + void testSsl() throws InterruptedException, IOException { pushGatewayContainer .withCopyFileToContainer( MountableFile.forClasspathResource("/pushgateway-ssl.yaml"), @@ -156,7 +160,7 @@ public void testSsl() throws InterruptedException, IOException { } @Test - public void testProtobuf() throws IOException, InterruptedException { + void testProtobuf() throws IOException, InterruptedException { pushGatewayContainer.start(); sampleAppContainer .withCommand( diff --git a/integration-tests/it-spring-boot-smoke-test/pom.xml b/integration-tests/it-spring-boot-smoke-test/pom.xml index 8a180a60f..ee653fbb4 100644 --- a/integration-tests/it-spring-boot-smoke-test/pom.xml +++ b/integration-tests/it-spring-boot-smoke-test/pom.xml @@ -8,21 +8,21 @@ org.springframework.boot spring-boot-starter-parent - 3.5.3 + 4.0.3 io.prometheus it-spring-boot-smoke-test - 1.4.0-SNAPSHOT + 1.6.0-SNAPSHOT Integration Test - Spring Smoke Tests Spring Smoke Tests - 17 - 5.13.4 + 25 + 6.0.3 @@ -89,80 +89,108 @@ - - org.graalvm.buildtools - native-maven-plugin - - - - - --initialize-at-build-time=org.junit.jupiter.api.DisplayNameGenerator$IndicativeSentences - - - --initialize-at-build-time=org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor$ClassInfo - - - --initialize-at-build-time=org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor$LifecycleMethods - - - --initialize-at-build-time=org.junit.jupiter.engine.descriptor.ClassTemplateInvocationTestDescriptor - - - --initialize-at-build-time=org.junit.jupiter.engine.descriptor.ClassTemplateTestDescriptor - - - --initialize-at-build-time=org.junit.jupiter.engine.descriptor.DynamicDescendantFilter$Mode - - - --initialize-at-build-time=org.junit.jupiter.engine.descriptor.ExclusiveResourceCollector$1 - - - --initialize-at-build-time=org.junit.jupiter.engine.descriptor.MethodBasedTestDescriptor$MethodInfo - - - --initialize-at-build-time=org.junit.jupiter.engine.discovery.ClassSelectorResolver$DummyClassTemplateInvocationContext - - - --initialize-at-build-time=org.junit.platform.engine.support.store.NamespacedHierarchicalStore$EvaluatedValue - - --initialize-at-build-time=org.junit.platform.launcher.core.DiscoveryIssueNotifier - - - --initialize-at-build-time=org.junit.platform.launcher.core.HierarchicalOutputDirectoryProvider - - - --initialize-at-build-time=org.junit.platform.launcher.core.LauncherDiscoveryResult$EngineResultInfo - - - --initialize-at-build-time=org.junit.platform.suite.engine.SuiteTestDescriptor$LifecycleMethods - - - - - org.springframework.boot spring-boot-maven-plugin - - com.diffplug.spotless - spotless-maven-plugin - 2.46.1 - - - - - - - - verify - - check - - - - + + + java17-plus + + [17,) + + + + + org.graalvm.buildtools + native-maven-plugin + + + + + --initialize-at-build-time=org.junit.jupiter.api.DisplayNameGenerator$IndicativeSentences + + + --initialize-at-build-time=org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor$ClassInfo + + + --initialize-at-build-time=org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor$LifecycleMethods + + + --initialize-at-build-time=org.junit.jupiter.engine.descriptor.ClassTemplateInvocationTestDescriptor + + + --initialize-at-build-time=org.junit.jupiter.engine.descriptor.ClassTemplateTestDescriptor + + + --initialize-at-build-time=org.junit.jupiter.engine.descriptor.DynamicDescendantFilter$Mode + + + --initialize-at-build-time=org.junit.jupiter.engine.descriptor.ExclusiveResourceCollector$1 + + + --initialize-at-build-time=org.junit.jupiter.engine.descriptor.MethodBasedTestDescriptor$MethodInfo + + + --initialize-at-build-time=org.junit.jupiter.engine.discovery.ClassSelectorResolver$DummyClassTemplateInvocationContext + + + --initialize-at-build-time=org.junit.platform.engine.support.store.NamespacedHierarchicalStore$EvaluatedValue + + --initialize-at-build-time=org.junit.platform.launcher.core.DiscoveryIssueNotifier + + + --initialize-at-build-time=org.junit.platform.launcher.core.HierarchicalOutputDirectoryProvider + + + --initialize-at-build-time=org.junit.platform.launcher.core.LauncherDiscoveryResult$EngineResultInfo + + + --initialize-at-build-time=org.junit.platform.suite.engine.SuiteTestDescriptor$LifecycleMethods + + + --initialize-at-build-time=org.junit.platform.commons.logging.LoggerFactory$DelegatingLogger + + + --initialize-at-build-time=org.junit.jupiter.engine.execution.ConditionEvaluator + + + --initialize-at-build-time=org.junit.jupiter.engine.execution.InterceptingExecutableInvoker + + + --initialize-at-build-time=org.junit.jupiter.api.extension.ConditionEvaluationResult + + + --initialize-at-build-time=org.junit.jupiter.engine.execution.InvocationInterceptorChain + + + + + + + com.diffplug.spotless + spotless-maven-plugin + 3.2.1 + + + + + + + + verify + + check + + + + + + + + + diff --git a/integration-tests/it-spring-boot-smoke-test/src/test/java/io/prometheus/metrics/it/springboot/ApplicationTest.java b/integration-tests/it-spring-boot-smoke-test/src/test/java/io/prometheus/metrics/it/springboot/ApplicationTest.java index 26f555847..fed9fba6d 100644 --- a/integration-tests/it-spring-boot-smoke-test/src/test/java/io/prometheus/metrics/it/springboot/ApplicationTest.java +++ b/integration-tests/it-spring-boot-smoke-test/src/test/java/io/prometheus/metrics/it/springboot/ApplicationTest.java @@ -3,24 +3,22 @@ import static org.assertj.core.api.Assertions.assertThat; import io.prometheus.client.it.common.ExporterTest; -import io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics; +import io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics; import java.io.IOException; -import java.net.URL; +import java.net.URI; import java.util.List; import java.util.Optional; import org.junit.jupiter.api.Test; -import org.springframework.boot.test.autoconfigure.actuate.observability.AutoConfigureObservability; import org.springframework.boot.test.context.SpringBootTest; @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT) -@AutoConfigureObservability class ApplicationTest { @Test - public void testPrometheusProtobufFormat() throws IOException { + void testPrometheusProtobufFormat() throws IOException { ExporterTest.Response response = ExporterTest.scrape( "GET", - new URL("http://localhost:8080/actuator/prometheus"), + URI.create("http://localhost:8080/actuator/prometheus"), "Accept", "application/vnd.google.protobuf; proto=io.prometheus.client.MetricFamily;" + " encoding=delimited"); diff --git a/integration-tests/pom.xml b/integration-tests/pom.xml index bca697c1e..f0ab29299 100644 --- a/integration-tests/pom.xml +++ b/integration-tests/pom.xml @@ -6,7 +6,7 @@ io.prometheus client_java - 1.4.0-SNAPSHOT + 1.6.0-SNAPSHOT integration-tests @@ -19,6 +19,7 @@ true + true @@ -51,12 +52,12 @@ commons-io commons-io - 2.20.0 + 2.21.0 org.testcontainers junit-jupiter - 1.21.3 + 1.21.4 test diff --git a/lychee.toml b/lychee.toml deleted file mode 100644 index 599f2de7a..000000000 --- a/lychee.toml +++ /dev/null @@ -1,15 +0,0 @@ -max_retries = 6 -exclude_loopback = true -cache = true - -base = "https://prometheus.github.io" -exclude_path = ["docs/themes"] -exclude = [ - '^https://github\.com/prometheus/client_java/settings', - '#', - 'CONTRIBUTING.md', - 'LICENSE', - 'MAINTAINERS.md' -] - - diff --git a/mise.native.toml b/mise.native.toml deleted file mode 100644 index 67ad3940f..000000000 --- a/mise.native.toml +++ /dev/null @@ -1,8 +0,0 @@ -[tools] -java = "graalvm-community-24.0.1" - -[tasks.test] -depends = "build" -run = "../../mvnw test -PnativeTest" -dir = "integration-tests/it-spring-boot-smoke-test" - diff --git a/mise.toml b/mise.toml index 79eba2876..ebf962bc4 100644 --- a/mise.toml +++ b/mise.toml @@ -1,10 +1,16 @@ [tools] -"cargo:zizmor" = "1.9.0" -"go:github.com/gohugoio/hugo" = "v0.148.1" -"go:github.com/grafana/oats" = "0.4.0" -java = "temurin-21.0.8+9.0.LTS" -lychee = "0.19.1" -protoc = "31.1" +"go:github.com/gohugoio/hugo" = "v0.156.0" +"go:github.com/grafana/oats" = "0.6.1" +java = "temurin-25.0.2+10.0.LTS" +lychee = "0.23.0" +node = "24.13.1" +"npm:renovate" = "43.8.5" +protoc = "33.5" + +[env] +RENOVATE_TRACKED_DEPS_EXCLUDE="github-actions,github-runners" +# renovate: datasource=docker depName=ghcr.io/super-linter/super-linter +SUPER_LINTER_VERSION="slim-v8.5.0@sha256:857dcc3f0bf5dd065fdeed1ace63394bb2004238a5ef02910ea23d9bcd8fd2b8" [tasks.ci] description = "CI Build" @@ -16,13 +22,20 @@ env.PROTO_GENERATION = "true" description = "format source code" run = "./mvnw spotless:apply" +[tasks.clean] +description = "clean all modules" +run = "./mvnw clean" + [tasks.compile] description = "bare compile, ignoring formatting and linters" run = "./mvnw install -DskipTests -Dspotless.check.skip=true -Dcoverage.skip=true -Dcheckstyle.skip=true -Dwarnings=-nowarn" [tasks.generate] description = "bare compile, ignoring formatting and linters" -run = "./mvnw install -DskipTests -Dspotless.check.skip=true -Dcoverage.skip=true -Dcheckstyle.skip=true -Dwarnings=-nowarn" +run = [ + "mise use --pin protoc@latest", + "./mvnw clean install -DskipTests -Dspotless.check.skip=true -Dcoverage.skip=true -Dcheckstyle.skip=true -Dwarnings=-nowarn" +] env.PROTO_GENERATION = "true" [tasks.test] @@ -34,43 +47,43 @@ description = "run all tests" run = "./mvnw verify" [tasks.build] -description = "build all modules wihthout tests" -run = "./mvnw install -DskipTests" +description = "build all modules without tests" +run = "./mvnw install -DskipTests -Dcoverage.skip=true" -[tasks.lint] -run = "scripts/super-linter.sh" +# Shared lint tasks from flint (https://github.com/grafana/flint) +[tasks."lint:super-linter"] +description = "Run Super-Linter on the repository" +file = "https://raw.githubusercontent.com/grafana/flint/5bb3726cfe3305072457c0c4fa85dce5ca154680/tasks/lint/super-linter.sh" # v0.6.0 -[tasks.lint-links] -run = "lychee --include-fragments ." +[tasks."lint:links"] +description = "Lint links" +file = "https://raw.githubusercontent.com/grafana/flint/5bb3726cfe3305072457c0c4fa85dce5ca154680/tasks/lint/links.sh" # v0.6.0 -[tasks.lint-gh-actions] -run = "zizmor .github/" +[tasks."lint:renovate-deps"] +description = "Verify renovate-tracked-deps.json is up to date" +file = "https://raw.githubusercontent.com/grafana/flint/5bb3726cfe3305072457c0c4fa85dce5ca154680/tasks/lint/renovate-deps.py" # v0.6.0 -[tasks.lint-bom] -run = "scripts/lint-bom.sh" +[tasks."lint"] +description = "Run all lints" +depends = ["lint:super-linter", "lint:links", "lint:bom", "lint:renovate-deps"] -[tasks.lint-rest] -description = "All lints not covered by super linter" -depends = ["lint-links", "lint-gh-actions", "lint-bom"] +[tasks.fix] +description = "Auto-fix lint issues" +run = "AUTOFIX=true mise run lint" [tasks.acceptance-test] description = "Run OATs acceptance tests" depends = "build" run = "oats -timeout 5m examples/" -[tasks.set-version] -run = './scripts/set-version.sh {{arg(name="version")}}' - [tasks.javadoc] +description = "Generate Javadoc" run = [ "./mvnw -B clean compile javadoc:javadoc javadoc:aggregate -P 'javadoc,!default'", "rm -rf ./docs/static/api", "mv ./target/reports/apidocs ./docs/static/api && echo && echo 'ls ./docs/static/api' && ls ./docs/static/api" ] -[tasks.set-gh-pages-version] -run = "./scripts/set-release-version-github-pages.sh" - [tasks.gh-pages-dev] description = "Build GitHub pages for dev" run = "hugo server -D" @@ -78,7 +91,7 @@ dir = "docs" [tasks.build-gh-pages] description = "Build GitHub pages" -depends = ["javadoc", "set-gh-pages-version"] +depends = ["javadoc", "set-release-version-github-pages"] # For maximum backward compatibility with Hugo modules env = { HUGO_ENVIRONMENT = "production", HUGO_ENV = "production" } dir = "docs" @@ -87,12 +100,23 @@ run = [ "echo 'ls ./public/api' && ls ./public/api" ] -[tasks.build-release] -description = "Build release" -run = "./scripts/build-release.sh" -env.TAG = "1.4.0-SNAPSHOT" - -[settings] -# to get lock file support and for go backend -experimental = true - +[tasks."benchmark:quick"] +description = "Run benchmarks with reduced iterations (quick smoke test, ~10 min)" +run = "python3 ./.mise/tasks/update_benchmarks.py --jmh-args '-f 1 -wi 1 -i 3'" + +[tasks."benchmark:ci"] +description = "Run benchmarks with CI configuration (3 forks, 3 warmup, 5 measurement iterations (~60 min total)" +run = "python3 ./.mise/tasks/update_benchmarks.py --jmh-args '-f 3 -wi 3 -i 5'" + +[tasks."benchmark:ci-json"] +description = "Run benchmarks with CI configuration and JSON output (for workflow/testing)" +run = """ +./mvnw -pl benchmarks -am -DskipTests clean package +JMH_ARGS="${JMH_ARGS:--f 3 -wi 3 -i 5}" +echo "Running benchmarks with args: $JMH_ARGS" +java -jar ./benchmarks/target/benchmarks.jar -rf json -rff benchmark-results.json $JMH_ARGS +""" + +[tasks."benchmark:generate-summary"] +description = "Generate summary from existing benchmark-results.json" +run = "python3 ./.mise/tasks/generate_benchmark_summary.py" diff --git a/mvnw b/mvnw index 6fdd4d2b2..bd8896bf2 100755 --- a/mvnw +++ b/mvnw @@ -19,7 +19,7 @@ # ---------------------------------------------------------------------------- # ---------------------------------------------------------------------------- -# Apache Maven Wrapper startup batch script, version 3.3.2 +# Apache Maven Wrapper startup batch script, version 3.3.4 # # Optional ENV vars # ----------------- @@ -36,101 +36,104 @@ set -euf native_path() { printf %s\\n "$1"; } case "$(uname)" in CYGWIN* | MINGW*) - [ -z "${JAVA_HOME-}" ] || JAVA_HOME="$(cygpath --unix "$JAVA_HOME")" - native_path() { cygpath --path --windows "$1"; } - ;; + [ -z "${JAVA_HOME-}" ] || JAVA_HOME="$(cygpath --unix "$JAVA_HOME")" + native_path() { cygpath --path --windows "$1"; } + ;; esac # set JAVACMD and JAVACCMD set_java_home() { - # For Cygwin and MinGW, ensure paths are in Unix format before anything is touched - if [ -n "${JAVA_HOME-}" ]; then - if [ -x "$JAVA_HOME/jre/sh/java" ]; then - # IBM's JDK on AIX uses strange locations for the executables - JAVACMD="$JAVA_HOME/jre/sh/java" - JAVACCMD="$JAVA_HOME/jre/sh/javac" - else - JAVACMD="$JAVA_HOME/bin/java" - JAVACCMD="$JAVA_HOME/bin/javac" - - if [ ! -x "$JAVACMD" ] || [ ! -x "$JAVACCMD" ]; then - echo "The JAVA_HOME environment variable is not defined correctly, so mvnw cannot run." >&2 - echo "JAVA_HOME is set to \"$JAVA_HOME\", but \"\$JAVA_HOME/bin/java\" or \"\$JAVA_HOME/bin/javac\" does not exist." >&2 - return 1 - fi - fi - else - JAVACMD="$( - 'set' +e - 'unset' -f command 2>/dev/null - 'command' -v java - )" || : - JAVACCMD="$( - 'set' +e - 'unset' -f command 2>/dev/null - 'command' -v javac - )" || : - - if [ ! -x "${JAVACMD-}" ] || [ ! -x "${JAVACCMD-}" ]; then - echo "The java/javac command does not exist in PATH nor is JAVA_HOME set, so mvnw cannot run." >&2 - return 1 - fi - fi + # For Cygwin and MinGW, ensure paths are in Unix format before anything is touched + if [ -n "${JAVA_HOME-}" ]; then + if [ -x "$JAVA_HOME/jre/sh/java" ]; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + JAVACCMD="$JAVA_HOME/jre/sh/javac" + else + JAVACMD="$JAVA_HOME/bin/java" + JAVACCMD="$JAVA_HOME/bin/javac" + + if [ ! -x "$JAVACMD" ] || [ ! -x "$JAVACCMD" ]; then + echo "The JAVA_HOME environment variable is not defined correctly, so mvnw cannot run." >&2 + echo "JAVA_HOME is set to \"$JAVA_HOME\", but \"\$JAVA_HOME/bin/java\" or \"\$JAVA_HOME/bin/javac\" does not exist." >&2 + return 1 + fi + fi + else + JAVACMD="$( + 'set' +e + 'unset' -f command 2>/dev/null + 'command' -v java + )" || : + JAVACCMD="$( + 'set' +e + 'unset' -f command 2>/dev/null + 'command' -v javac + )" || : + + if [ ! -x "${JAVACMD-}" ] || [ ! -x "${JAVACCMD-}" ]; then + echo "The java/javac command does not exist in PATH nor is JAVA_HOME set, so mvnw cannot run." >&2 + return 1 + fi + fi } # hash string like Java String::hashCode hash_string() { - str="${1:-}" h=0 - while [ -n "$str" ]; do - char="${str%"${str#?}"}" - h=$(((h * 31 + $(LC_CTYPE=C printf %d "'$char")) % 4294967296)) - str="${str#?}" - done - printf %x\\n $h + str="${1:-}" h=0 + while [ -n "$str" ]; do + char="${str%"${str#?}"}" + h=$(((h * 31 + $(LC_CTYPE=C printf %d "'$char")) % 4294967296)) + str="${str#?}" + done + printf %x\\n $h } verbose() { :; } [ "${MVNW_VERBOSE-}" != true ] || verbose() { printf %s\\n "${1-}"; } die() { - printf %s\\n "$1" >&2 - exit 1 + printf %s\\n "$1" >&2 + exit 1 } trim() { - # MWRAPPER-139: - # Trims trailing and leading whitespace, carriage returns, tabs, and linefeeds. - # Needed for removing poorly interpreted newline sequences when running in more - # exotic environments such as mingw bash on Windows. - printf "%s" "${1}" | tr -d '[:space:]' + # MWRAPPER-139: + # Trims trailing and leading whitespace, carriage returns, tabs, and linefeeds. + # Needed for removing poorly interpreted newline sequences when running in more + # exotic environments such as mingw bash on Windows. + printf "%s" "${1}" | tr -d '[:space:]' } +scriptDir="$(dirname "$0")" +scriptName="$(basename "$0")" + # parse distributionUrl and optional distributionSha256Sum, requires .mvn/wrapper/maven-wrapper.properties while IFS="=" read -r key value; do - case "${key-}" in - distributionUrl) distributionUrl=$(trim "${value-}") ;; - distributionSha256Sum) distributionSha256Sum=$(trim "${value-}") ;; - esac -done <"${0%/*}/.mvn/wrapper/maven-wrapper.properties" -[ -n "${distributionUrl-}" ] || die "cannot read distributionUrl property in ${0%/*}/.mvn/wrapper/maven-wrapper.properties" + case "${key-}" in + distributionUrl) distributionUrl=$(trim "${value-}") ;; + distributionSha256Sum) distributionSha256Sum=$(trim "${value-}") ;; + esac +done <"$scriptDir/.mvn/wrapper/maven-wrapper.properties" +[ -n "${distributionUrl-}" ] || die "cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties" case "${distributionUrl##*/}" in maven-mvnd-*bin.*) - MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ - case "${PROCESSOR_ARCHITECTURE-}${PROCESSOR_ARCHITEW6432-}:$(uname -a)" in - *AMD64:CYGWIN* | *AMD64:MINGW*) distributionPlatform=windows-amd64 ;; - :Darwin*x86_64) distributionPlatform=darwin-amd64 ;; - :Darwin*arm64) distributionPlatform=darwin-aarch64 ;; - :Linux*x86_64*) distributionPlatform=linux-amd64 ;; - *) - echo "Cannot detect native platform for mvnd on $(uname)-$(uname -m), use pure java version" >&2 - distributionPlatform=linux-amd64 - ;; - esac - distributionUrl="${distributionUrl%-bin.*}-$distributionPlatform.zip" - ;; + MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ + case "${PROCESSOR_ARCHITECTURE-}${PROCESSOR_ARCHITEW6432-}:$(uname -a)" in + *AMD64:CYGWIN* | *AMD64:MINGW*) distributionPlatform=windows-amd64 ;; + :Darwin*x86_64) distributionPlatform=darwin-amd64 ;; + :Darwin*arm64) distributionPlatform=darwin-aarch64 ;; + :Linux*x86_64*) distributionPlatform=linux-amd64 ;; + *) + echo "Cannot detect native platform for mvnd on $(uname)-$(uname -m), use pure java version" >&2 + distributionPlatform=linux-amd64 + ;; + esac + distributionUrl="${distributionUrl%-bin.*}-$distributionPlatform.zip" + ;; maven-mvnd-*) MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ ;; -*) MVN_CMD="mvn${0##*/mvnw}" _MVNW_REPO_PATTERN=/org/apache/maven/ ;; +*) MVN_CMD="mvn${scriptName#mvnw}" _MVNW_REPO_PATTERN=/org/apache/maven/ ;; esac # apply MVNW_REPOURL and calculate MAVEN_HOME @@ -143,13 +146,13 @@ MAVEN_USER_HOME="${MAVEN_USER_HOME:-${HOME}/.m2}" MAVEN_HOME="${MAVEN_USER_HOME}/wrapper/dists/${distributionUrlNameMain-}/$(hash_string "$distributionUrl")" exec_maven() { - unset MVNW_VERBOSE MVNW_USERNAME MVNW_PASSWORD MVNW_REPOURL || : - exec "$MAVEN_HOME/bin/$MVN_CMD" "$@" || die "cannot exec $MAVEN_HOME/bin/$MVN_CMD" + unset MVNW_VERBOSE MVNW_USERNAME MVNW_PASSWORD MVNW_REPOURL || : + exec "$MAVEN_HOME/bin/$MVN_CMD" "$@" || die "cannot exec $MAVEN_HOME/bin/$MVN_CMD" } if [ -d "$MAVEN_HOME" ]; then - verbose "found existing MAVEN_HOME at $MAVEN_HOME" - exec_maven "$@" + verbose "found existing MAVEN_HOME at $MAVEN_HOME" + exec_maven "$@" fi case "${distributionUrl-}" in @@ -159,10 +162,10 @@ esac # prepare tmp dir if TMP_DOWNLOAD_DIR="$(mktemp -d)" && [ -d "$TMP_DOWNLOAD_DIR" ]; then - clean() { rm -rf -- "$TMP_DOWNLOAD_DIR"; } - trap clean HUP INT TERM EXIT + clean() { rm -rf -- "$TMP_DOWNLOAD_DIR"; } + trap clean HUP INT TERM EXIT else - die "cannot create temp dir" + die "cannot create temp dir" fi mkdir -p -- "${MAVEN_HOME%/*}" @@ -174,8 +177,8 @@ verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName" # select .zip or .tar.gz if ! command -v unzip >/dev/null; then - distributionUrl="${distributionUrl%.zip}.tar.gz" - distributionUrlName="${distributionUrl##*/}" + distributionUrl="${distributionUrl%.zip}.tar.gz" + distributionUrlName="${distributionUrl##*/}" fi # verbose opt @@ -189,71 +192,104 @@ has-password) [ -n "${MVNW_USERNAME-}" ] || MVNW_USERNAME='' MVNW_PASSWORD='' ;; esac if [ -z "${MVNW_USERNAME-}" ] && command -v wget >/dev/null; then - verbose "Found wget ... using wget" - wget ${__MVNW_QUIET_WGET:+"$__MVNW_QUIET_WGET"} "$distributionUrl" -O "$TMP_DOWNLOAD_DIR/$distributionUrlName" || die "wget: Failed to fetch $distributionUrl" + verbose "Found wget ... using wget" + wget ${__MVNW_QUIET_WGET:+"$__MVNW_QUIET_WGET"} "$distributionUrl" -O "$TMP_DOWNLOAD_DIR/$distributionUrlName" || die "wget: Failed to fetch $distributionUrl" elif [ -z "${MVNW_USERNAME-}" ] && command -v curl >/dev/null; then - verbose "Found curl ... using curl" - curl ${__MVNW_QUIET_CURL:+"$__MVNW_QUIET_CURL"} -f -L -o "$TMP_DOWNLOAD_DIR/$distributionUrlName" "$distributionUrl" || die "curl: Failed to fetch $distributionUrl" + verbose "Found curl ... using curl" + curl ${__MVNW_QUIET_CURL:+"$__MVNW_QUIET_CURL"} -f -L -o "$TMP_DOWNLOAD_DIR/$distributionUrlName" "$distributionUrl" || die "curl: Failed to fetch $distributionUrl" elif set_java_home; then - verbose "Falling back to use Java to download" - javaSource="$TMP_DOWNLOAD_DIR/Downloader.java" - targetZip="$TMP_DOWNLOAD_DIR/$distributionUrlName" - cat >"$javaSource" <<-END - public class Downloader extends java.net.Authenticator - { - protected java.net.PasswordAuthentication getPasswordAuthentication() - { - return new java.net.PasswordAuthentication( System.getenv( "MVNW_USERNAME" ), System.getenv( "MVNW_PASSWORD" ).toCharArray() ); - } - public static void main( String[] args ) throws Exception - { - setDefault( new Downloader() ); - java.nio.file.Files.copy( java.net.URI.create( args[0] ).toURL().openStream(), java.nio.file.Paths.get( args[1] ).toAbsolutePath().normalize() ); - } - } + verbose "Falling back to use Java to download" + javaSource="$TMP_DOWNLOAD_DIR/Downloader.java" + targetZip="$TMP_DOWNLOAD_DIR/$distributionUrlName" + cat >"$javaSource" <<-END + public class Downloader extends java.net.Authenticator + { + protected java.net.PasswordAuthentication getPasswordAuthentication() + { + return new java.net.PasswordAuthentication( System.getenv( "MVNW_USERNAME" ), System.getenv( "MVNW_PASSWORD" ).toCharArray() ); + } + public static void main( String[] args ) throws Exception + { + setDefault( new Downloader() ); + java.nio.file.Files.copy( java.net.URI.create( args[0] ).toURL().openStream(), java.nio.file.Paths.get( args[1] ).toAbsolutePath().normalize() ); + } + } END - # For Cygwin/MinGW, switch paths to Windows format before running javac and java - verbose " - Compiling Downloader.java ..." - "$(native_path "$JAVACCMD")" "$(native_path "$javaSource")" || die "Failed to compile Downloader.java" - verbose " - Running Downloader.java ..." - "$(native_path "$JAVACMD")" -cp "$(native_path "$TMP_DOWNLOAD_DIR")" Downloader "$distributionUrl" "$(native_path "$targetZip")" + # For Cygwin/MinGW, switch paths to Windows format before running javac and java + verbose " - Compiling Downloader.java ..." + "$(native_path "$JAVACCMD")" "$(native_path "$javaSource")" || die "Failed to compile Downloader.java" + verbose " - Running Downloader.java ..." + "$(native_path "$JAVACMD")" -cp "$(native_path "$TMP_DOWNLOAD_DIR")" Downloader "$distributionUrl" "$(native_path "$targetZip")" fi # If specified, validate the SHA-256 sum of the Maven distribution zip file if [ -n "${distributionSha256Sum-}" ]; then - distributionSha256Result=false - if [ "$MVN_CMD" = mvnd.sh ]; then - echo "Checksum validation is not supported for maven-mvnd." >&2 - echo "Please disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2 - exit 1 - elif command -v sha256sum >/dev/null; then - if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | sha256sum -c >/dev/null 2>&1; then - distributionSha256Result=true - fi - elif command -v shasum >/dev/null; then - if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | shasum -a 256 -c >/dev/null 2>&1; then - distributionSha256Result=true - fi - else - echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." >&2 - echo "Please install either command, or disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2 - exit 1 - fi - if [ $distributionSha256Result = false ]; then - echo "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised." >&2 - echo "If you updated your Maven version, you need to update the specified distributionSha256Sum property." >&2 - exit 1 - fi + distributionSha256Result=false + if [ "$MVN_CMD" = mvnd.sh ]; then + echo "Checksum validation is not supported for maven-mvnd." >&2 + echo "Please disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2 + exit 1 + elif command -v sha256sum >/dev/null; then + if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | sha256sum -c - >/dev/null 2>&1; then + distributionSha256Result=true + fi + elif command -v shasum >/dev/null; then + if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | shasum -a 256 -c >/dev/null 2>&1; then + distributionSha256Result=true + fi + else + echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." >&2 + echo "Please install either command, or disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2 + exit 1 + fi + if [ $distributionSha256Result = false ]; then + echo "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised." >&2 + echo "If you updated your Maven version, you need to update the specified distributionSha256Sum property." >&2 + exit 1 + fi fi # unzip and move if command -v unzip >/dev/null; then - unzip ${__MVNW_QUIET_UNZIP:+"$__MVNW_QUIET_UNZIP"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -d "$TMP_DOWNLOAD_DIR" || die "failed to unzip" + unzip ${__MVNW_QUIET_UNZIP:+"$__MVNW_QUIET_UNZIP"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -d "$TMP_DOWNLOAD_DIR" || die "failed to unzip" else - tar xzf${__MVNW_QUIET_TAR:+"$__MVNW_QUIET_TAR"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -C "$TMP_DOWNLOAD_DIR" || die "failed to untar" + tar xzf${__MVNW_QUIET_TAR:+"$__MVNW_QUIET_TAR"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -C "$TMP_DOWNLOAD_DIR" || die "failed to untar" +fi + +# Find the actual extracted directory name (handles snapshots where filename != directory name) +actualDistributionDir="" + +# First try the expected directory name (for regular distributions) +if [ -d "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" ]; then + if [ -f "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain/bin/$MVN_CMD" ]; then + actualDistributionDir="$distributionUrlNameMain" + fi fi -printf %s\\n "$distributionUrl" >"$TMP_DOWNLOAD_DIR/$distributionUrlNameMain/mvnw.url" -mv -- "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" "$MAVEN_HOME" || [ -d "$MAVEN_HOME" ] || die "fail to move MAVEN_HOME" + +# If not found, search for any directory with the Maven executable (for snapshots) +if [ -z "$actualDistributionDir" ]; then + # enable globbing to iterate over items + set +f + for dir in "$TMP_DOWNLOAD_DIR"/*; do + if [ -d "$dir" ]; then + if [ -f "$dir/bin/$MVN_CMD" ]; then + actualDistributionDir="$(basename "$dir")" + break + fi + fi + done + set -f +fi + +if [ -z "$actualDistributionDir" ]; then + verbose "Contents of $TMP_DOWNLOAD_DIR:" + verbose "$(ls -la "$TMP_DOWNLOAD_DIR")" + die "Could not find Maven distribution directory in extracted archive" +fi + +verbose "Found extracted Maven distribution directory: $actualDistributionDir" +printf %s\\n "$distributionUrl" >"$TMP_DOWNLOAD_DIR/$actualDistributionDir/mvnw.url" +mv -- "$TMP_DOWNLOAD_DIR/$actualDistributionDir" "$MAVEN_HOME" || [ -d "$MAVEN_HOME" ] || die "fail to move MAVEN_HOME" clean || : exec_maven "$@" diff --git a/mvnw.cmd b/mvnw.cmd index 249bdf382..5761d9489 100644 --- a/mvnw.cmd +++ b/mvnw.cmd @@ -1,149 +1,189 @@ -<# : batch portion -@REM ---------------------------------------------------------------------------- -@REM Licensed to the Apache Software Foundation (ASF) under one -@REM or more contributor license agreements. See the NOTICE file -@REM distributed with this work for additional information -@REM regarding copyright ownership. The ASF licenses this file -@REM to you under the Apache License, Version 2.0 (the -@REM "License"); you may not use this file except in compliance -@REM with the License. You may obtain a copy of the License at -@REM -@REM http://www.apache.org/licenses/LICENSE-2.0 -@REM -@REM Unless required by applicable law or agreed to in writing, -@REM software distributed under the License is distributed on an -@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -@REM KIND, either express or implied. See the License for the -@REM specific language governing permissions and limitations -@REM under the License. -@REM ---------------------------------------------------------------------------- - -@REM ---------------------------------------------------------------------------- -@REM Apache Maven Wrapper startup batch script, version 3.3.2 -@REM -@REM Optional ENV vars -@REM MVNW_REPOURL - repo url base for downloading maven distribution -@REM MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven -@REM MVNW_VERBOSE - true: enable verbose log; others: silence the output -@REM ---------------------------------------------------------------------------- - -@IF "%__MVNW_ARG0_NAME__%"=="" (SET __MVNW_ARG0_NAME__=%~nx0) -@SET __MVNW_CMD__= -@SET __MVNW_ERROR__= -@SET __MVNW_PSMODULEP_SAVE=%PSModulePath% -@SET PSModulePath= -@FOR /F "usebackq tokens=1* delims==" %%A IN (`powershell -noprofile "& {$scriptDir='%~dp0'; $script='%__MVNW_ARG0_NAME__%'; icm -ScriptBlock ([Scriptblock]::Create((Get-Content -Raw '%~f0'))) -NoNewScope}"`) DO @( - IF "%%A"=="MVN_CMD" (set __MVNW_CMD__=%%B) ELSE IF "%%B"=="" (echo %%A) ELSE (echo %%A=%%B) -) -@SET PSModulePath=%__MVNW_PSMODULEP_SAVE% -@SET __MVNW_PSMODULEP_SAVE= -@SET __MVNW_ARG0_NAME__= -@SET MVNW_USERNAME= -@SET MVNW_PASSWORD= -@IF NOT "%__MVNW_CMD__%"=="" (%__MVNW_CMD__% %*) -@echo Cannot start maven from wrapper >&2 && exit /b 1 -@GOTO :EOF -: end batch / begin powershell #> - -$ErrorActionPreference = "Stop" -if ($env:MVNW_VERBOSE -eq "true") { - $VerbosePreference = "Continue" -} - -# calculate distributionUrl, requires .mvn/wrapper/maven-wrapper.properties -$distributionUrl = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionUrl -if (!$distributionUrl) { - Write-Error "cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties" -} - -switch -wildcard -casesensitive ( $($distributionUrl -replace '^.*/','') ) { - "maven-mvnd-*" { - $USE_MVND = $true - $distributionUrl = $distributionUrl -replace '-bin\.[^.]*$',"-windows-amd64.zip" - $MVN_CMD = "mvnd.cmd" - break - } - default { - $USE_MVND = $false - $MVN_CMD = $script -replace '^mvnw','mvn' - break - } -} - -# apply MVNW_REPOURL and calculate MAVEN_HOME -# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/ -if ($env:MVNW_REPOURL) { - $MVNW_REPO_PATTERN = if ($USE_MVND) { "/org/apache/maven/" } else { "/maven/mvnd/" } - $distributionUrl = "$env:MVNW_REPOURL$MVNW_REPO_PATTERN$($distributionUrl -replace '^.*'+$MVNW_REPO_PATTERN,'')" -} -$distributionUrlName = $distributionUrl -replace '^.*/','' -$distributionUrlNameMain = $distributionUrlName -replace '\.[^.]*$','' -replace '-bin$','' -$MAVEN_HOME_PARENT = "$HOME/.m2/wrapper/dists/$distributionUrlNameMain" -if ($env:MAVEN_USER_HOME) { - $MAVEN_HOME_PARENT = "$env:MAVEN_USER_HOME/wrapper/dists/$distributionUrlNameMain" -} -$MAVEN_HOME_NAME = ([System.Security.Cryptography.MD5]::Create().ComputeHash([byte[]][char[]]$distributionUrl) | ForEach-Object {$_.ToString("x2")}) -join '' -$MAVEN_HOME = "$MAVEN_HOME_PARENT/$MAVEN_HOME_NAME" - -if (Test-Path -Path "$MAVEN_HOME" -PathType Container) { - Write-Verbose "found existing MAVEN_HOME at $MAVEN_HOME" - Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD" - exit $? -} - -if (! $distributionUrlNameMain -or ($distributionUrlName -eq $distributionUrlNameMain)) { - Write-Error "distributionUrl is not valid, must end with *-bin.zip, but found $distributionUrl" -} - -# prepare tmp dir -$TMP_DOWNLOAD_DIR_HOLDER = New-TemporaryFile -$TMP_DOWNLOAD_DIR = New-Item -Itemtype Directory -Path "$TMP_DOWNLOAD_DIR_HOLDER.dir" -$TMP_DOWNLOAD_DIR_HOLDER.Delete() | Out-Null -trap { - if ($TMP_DOWNLOAD_DIR.Exists) { - try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null } - catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" } - } -} - -New-Item -Itemtype Directory -Path "$MAVEN_HOME_PARENT" -Force | Out-Null - -# Download and Install Apache Maven -Write-Verbose "Couldn't find MAVEN_HOME, downloading and installing it ..." -Write-Verbose "Downloading from: $distributionUrl" -Write-Verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName" - -$webclient = New-Object System.Net.WebClient -if ($env:MVNW_USERNAME -and $env:MVNW_PASSWORD) { - $webclient.Credentials = New-Object System.Net.NetworkCredential($env:MVNW_USERNAME, $env:MVNW_PASSWORD) -} -[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 -$webclient.DownloadFile($distributionUrl, "$TMP_DOWNLOAD_DIR/$distributionUrlName") | Out-Null - -# If specified, validate the SHA-256 sum of the Maven distribution zip file -$distributionSha256Sum = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionSha256Sum -if ($distributionSha256Sum) { - if ($USE_MVND) { - Write-Error "Checksum validation is not supported for maven-mvnd. `nPlease disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." - } - Import-Module $PSHOME\Modules\Microsoft.PowerShell.Utility -Function Get-FileHash - if ((Get-FileHash "$TMP_DOWNLOAD_DIR/$distributionUrlName" -Algorithm SHA256).Hash.ToLower() -ne $distributionSha256Sum) { - Write-Error "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised. If you updated your Maven version, you need to update the specified distributionSha256Sum property." - } -} - -# unzip and move -Expand-Archive "$TMP_DOWNLOAD_DIR/$distributionUrlName" -DestinationPath "$TMP_DOWNLOAD_DIR" | Out-Null -Rename-Item -Path "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" -NewName $MAVEN_HOME_NAME | Out-Null -try { - Move-Item -Path "$TMP_DOWNLOAD_DIR/$MAVEN_HOME_NAME" -Destination $MAVEN_HOME_PARENT | Out-Null -} catch { - if (! (Test-Path -Path "$MAVEN_HOME" -PathType Container)) { - Write-Error "fail to move MAVEN_HOME" - } -} finally { - try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null } - catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" } -} - -Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD" +<# : batch portion +@REM ---------------------------------------------------------------------------- +@REM Licensed to the Apache Software Foundation (ASF) under one +@REM or more contributor license agreements. See the NOTICE file +@REM distributed with this work for additional information +@REM regarding copyright ownership. The ASF licenses this file +@REM to you under the Apache License, Version 2.0 (the +@REM "License"); you may not use this file except in compliance +@REM with the License. You may obtain a copy of the License at +@REM +@REM http://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, +@REM software distributed under the License is distributed on an +@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +@REM KIND, either express or implied. See the License for the +@REM specific language governing permissions and limitations +@REM under the License. +@REM ---------------------------------------------------------------------------- + +@REM ---------------------------------------------------------------------------- +@REM Apache Maven Wrapper startup batch script, version 3.3.4 +@REM +@REM Optional ENV vars +@REM MVNW_REPOURL - repo url base for downloading maven distribution +@REM MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven +@REM MVNW_VERBOSE - true: enable verbose log; others: silence the output +@REM ---------------------------------------------------------------------------- + +@IF "%__MVNW_ARG0_NAME__%"=="" (SET __MVNW_ARG0_NAME__=%~nx0) +@SET __MVNW_CMD__= +@SET __MVNW_ERROR__= +@SET __MVNW_PSMODULEP_SAVE=%PSModulePath% +@SET PSModulePath= +@FOR /F "usebackq tokens=1* delims==" %%A IN (`powershell -noprofile "& {$scriptDir='%~dp0'; $script='%__MVNW_ARG0_NAME__%'; icm -ScriptBlock ([Scriptblock]::Create((Get-Content -Raw '%~f0'))) -NoNewScope}"`) DO @( + IF "%%A"=="MVN_CMD" (set __MVNW_CMD__=%%B) ELSE IF "%%B"=="" (echo %%A) ELSE (echo %%A=%%B) +) +@SET PSModulePath=%__MVNW_PSMODULEP_SAVE% +@SET __MVNW_PSMODULEP_SAVE= +@SET __MVNW_ARG0_NAME__= +@SET MVNW_USERNAME= +@SET MVNW_PASSWORD= +@IF NOT "%__MVNW_CMD__%"=="" ("%__MVNW_CMD__%" %*) +@echo Cannot start maven from wrapper >&2 && exit /b 1 +@GOTO :EOF +: end batch / begin powershell #> + +$ErrorActionPreference = "Stop" +if ($env:MVNW_VERBOSE -eq "true") { + $VerbosePreference = "Continue" +} + +# calculate distributionUrl, requires .mvn/wrapper/maven-wrapper.properties +$distributionUrl = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionUrl +if (!$distributionUrl) { + Write-Error "cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties" +} + +switch -wildcard -casesensitive ( $($distributionUrl -replace '^.*/','') ) { + "maven-mvnd-*" { + $USE_MVND = $true + $distributionUrl = $distributionUrl -replace '-bin\.[^.]*$',"-windows-amd64.zip" + $MVN_CMD = "mvnd.cmd" + break + } + default { + $USE_MVND = $false + $MVN_CMD = $script -replace '^mvnw','mvn' + break + } +} + +# apply MVNW_REPOURL and calculate MAVEN_HOME +# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/ +if ($env:MVNW_REPOURL) { + $MVNW_REPO_PATTERN = if ($USE_MVND -eq $False) { "/org/apache/maven/" } else { "/maven/mvnd/" } + $distributionUrl = "$env:MVNW_REPOURL$MVNW_REPO_PATTERN$($distributionUrl -replace "^.*$MVNW_REPO_PATTERN",'')" +} +$distributionUrlName = $distributionUrl -replace '^.*/','' +$distributionUrlNameMain = $distributionUrlName -replace '\.[^.]*$','' -replace '-bin$','' + +$MAVEN_M2_PATH = "$HOME/.m2" +if ($env:MAVEN_USER_HOME) { + $MAVEN_M2_PATH = "$env:MAVEN_USER_HOME" +} + +if (-not (Test-Path -Path $MAVEN_M2_PATH)) { + New-Item -Path $MAVEN_M2_PATH -ItemType Directory | Out-Null +} + +$MAVEN_WRAPPER_DISTS = $null +if ((Get-Item $MAVEN_M2_PATH).Target[0] -eq $null) { + $MAVEN_WRAPPER_DISTS = "$MAVEN_M2_PATH/wrapper/dists" +} else { + $MAVEN_WRAPPER_DISTS = (Get-Item $MAVEN_M2_PATH).Target[0] + "/wrapper/dists" +} + +$MAVEN_HOME_PARENT = "$MAVEN_WRAPPER_DISTS/$distributionUrlNameMain" +$MAVEN_HOME_NAME = ([System.Security.Cryptography.SHA256]::Create().ComputeHash([byte[]][char[]]$distributionUrl) | ForEach-Object {$_.ToString("x2")}) -join '' +$MAVEN_HOME = "$MAVEN_HOME_PARENT/$MAVEN_HOME_NAME" + +if (Test-Path -Path "$MAVEN_HOME" -PathType Container) { + Write-Verbose "found existing MAVEN_HOME at $MAVEN_HOME" + Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD" + exit $? +} + +if (! $distributionUrlNameMain -or ($distributionUrlName -eq $distributionUrlNameMain)) { + Write-Error "distributionUrl is not valid, must end with *-bin.zip, but found $distributionUrl" +} + +# prepare tmp dir +$TMP_DOWNLOAD_DIR_HOLDER = New-TemporaryFile +$TMP_DOWNLOAD_DIR = New-Item -Itemtype Directory -Path "$TMP_DOWNLOAD_DIR_HOLDER.dir" +$TMP_DOWNLOAD_DIR_HOLDER.Delete() | Out-Null +trap { + if ($TMP_DOWNLOAD_DIR.Exists) { + try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null } + catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" } + } +} + +New-Item -Itemtype Directory -Path "$MAVEN_HOME_PARENT" -Force | Out-Null + +# Download and Install Apache Maven +Write-Verbose "Couldn't find MAVEN_HOME, downloading and installing it ..." +Write-Verbose "Downloading from: $distributionUrl" +Write-Verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName" + +$webclient = New-Object System.Net.WebClient +if ($env:MVNW_USERNAME -and $env:MVNW_PASSWORD) { + $webclient.Credentials = New-Object System.Net.NetworkCredential($env:MVNW_USERNAME, $env:MVNW_PASSWORD) +} +[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 +$webclient.DownloadFile($distributionUrl, "$TMP_DOWNLOAD_DIR/$distributionUrlName") | Out-Null + +# If specified, validate the SHA-256 sum of the Maven distribution zip file +$distributionSha256Sum = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionSha256Sum +if ($distributionSha256Sum) { + if ($USE_MVND) { + Write-Error "Checksum validation is not supported for maven-mvnd. `nPlease disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." + } + Import-Module $PSHOME\Modules\Microsoft.PowerShell.Utility -Function Get-FileHash + if ((Get-FileHash "$TMP_DOWNLOAD_DIR/$distributionUrlName" -Algorithm SHA256).Hash.ToLower() -ne $distributionSha256Sum) { + Write-Error "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised. If you updated your Maven version, you need to update the specified distributionSha256Sum property." + } +} + +# unzip and move +Expand-Archive "$TMP_DOWNLOAD_DIR/$distributionUrlName" -DestinationPath "$TMP_DOWNLOAD_DIR" | Out-Null + +# Find the actual extracted directory name (handles snapshots where filename != directory name) +$actualDistributionDir = "" + +# First try the expected directory name (for regular distributions) +$expectedPath = Join-Path "$TMP_DOWNLOAD_DIR" "$distributionUrlNameMain" +$expectedMvnPath = Join-Path "$expectedPath" "bin/$MVN_CMD" +if ((Test-Path -Path $expectedPath -PathType Container) -and (Test-Path -Path $expectedMvnPath -PathType Leaf)) { + $actualDistributionDir = $distributionUrlNameMain +} + +# If not found, search for any directory with the Maven executable (for snapshots) +if (!$actualDistributionDir) { + Get-ChildItem -Path "$TMP_DOWNLOAD_DIR" -Directory | ForEach-Object { + $testPath = Join-Path $_.FullName "bin/$MVN_CMD" + if (Test-Path -Path $testPath -PathType Leaf) { + $actualDistributionDir = $_.Name + } + } +} + +if (!$actualDistributionDir) { + Write-Error "Could not find Maven distribution directory in extracted archive" +} + +Write-Verbose "Found extracted Maven distribution directory: $actualDistributionDir" +Rename-Item -Path "$TMP_DOWNLOAD_DIR/$actualDistributionDir" -NewName $MAVEN_HOME_NAME | Out-Null +try { + Move-Item -Path "$TMP_DOWNLOAD_DIR/$MAVEN_HOME_NAME" -Destination $MAVEN_HOME_PARENT | Out-Null +} catch { + if (! (Test-Path -Path "$MAVEN_HOME" -PathType Container)) { + Write-Error "fail to move MAVEN_HOME" + } +} finally { + try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null } + catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" } +} + +Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD" diff --git a/pom.xml b/pom.xml index 941f6df2b..c166f0648 100644 --- a/pom.xml +++ b/pom.xml @@ -8,11 +8,10 @@ io.prometheus client_java_parent - 1.4.0-SNAPSHOT + 1.6.0-SNAPSHOT prometheus-metrics-parent/pom.xml - 1.4.0-SNAPSHOT client_java Prometheus Metrics Library @@ -23,11 +22,12 @@ UTF-8 --module-name-need-to-be-overridden-- - 4.31.1 - 33.4.8-jre - 5.13.4 - 2.16.0-alpha + 4.33.5 + 33.5.0-jre + 6.0.3 + 2.25.0-alpha 8 + 25 0.70 false false @@ -60,6 +60,7 @@ prometheus-metrics-instrumentation-dropwizard prometheus-metrics-instrumentation-guava prometheus-metrics-simpleclient-bridge + prometheus-metrics-otel-support @@ -69,6 +70,67 @@ 3.0.2 provided + + + org.junit.jupiter + junit-jupiter + ${junit-jupiter.version} + test + + + org.junit.jupiter + junit-jupiter-params + ${junit-jupiter.version} + test + + + org.mockito + mockito-core + 5.21.0 + test + + + org.assertj + assertj-core + 3.27.7 + test + + + com.google.guava + guava + ${guava.version} + test + + + org.slf4j + slf4j-simple + 2.0.17 + test + + + org.junit-pioneer + junit-pioneer + 2.3.0 + test + + + org.awaitility + awaitility + 4.3.0 + test + + + org.wiremock + wiremock + 3.13.2 + test + + + org.hamcrest + hamcrest-core + + + @@ -82,19 +144,19 @@ maven-resources-plugin - 3.3.1 + 3.4.0 maven-compiler-plugin - 3.14.0 + 3.15.0 maven-surefire-plugin - 3.5.3 + 3.5.4 maven-jar-plugin - 3.4.2 + 3.5.0 maven-deploy-plugin @@ -111,23 +173,23 @@ maven-shade-plugin - 3.6.0 + 3.6.1 maven-failsafe-plugin - 3.5.3 + 3.5.4 maven-dependency-plugin - 3.8.1 + 3.10.0 maven-javadoc-plugin - 3.11.2 + 3.12.0 maven-enforcer-plugin - 3.6.1 + 3.6.2 org.codehaus.mojo @@ -137,7 +199,7 @@ org.codehaus.mojo exec-maven-plugin - 3.5.1 + 3.6.3 @@ -167,12 +229,14 @@ org.jacoco jacoco-maven-plugin - 0.8.13 + 0.8.14 ${coverage.skip} **/generated/** **/*BlockingRejectedExecutionHandler* + **/*AllocationCountingNotificationListener* + **/*MapperConfig* @@ -203,6 +267,11 @@ COVEREDRATIO ${jacoco.line-coverage} + + BRANCH + COVEREDRATIO + 0.50 + @@ -252,56 +321,62 @@ ${java.version} ${java.version} ${java.version} - 17 - 17 - 17 + ${test.java.version} + ${test.java.version} + ${test.java.version} true -Xlint:all,-serial,-processing,-options ${warnings} --should-stop=ifError=FLOW -XDcompilePolicy=simple - - -Xplugin:ErrorProne - -Xep:AlmostJavadoc:OFF - -Xep:MissingSummary:OFF - -Xep:LongDoubleConversion:OFF - -Xep:StringSplitter:OFF - -XepExcludedPaths:.*/generated/.* - - - - com.google.errorprone - error_prone_core - 2.40.0 - - -
org.codehaus.mojo versions-maven-plugin - 2.18.0 + 2.21.0 file://${project.basedir}/version-rules.xml + + maven-javadoc-plugin + + ${javadoc.skip} + + + + + + org.junit + junit-bom + ${junit-jupiter.version} + pom + import + + + io.opentelemetry.instrumentation + opentelemetry-instrumentation-bom-alpha + ${otel.instrumentation.version} + pom + import + + + io.opentelemetry + opentelemetry-proto + 1.7.1-alpha + test + + + + - - - maven-project-info-reports-plugin - 3.9.0 - maven-javadoc-plugin @@ -325,102 +400,15 @@ - default + examples-and-integration-tests - true + [25,) examples benchmarks integration-tests - - - - org.junit - junit-bom - ${junit-jupiter.version} - pom - import - - - io.opentelemetry.instrumentation - opentelemetry-instrumentation-bom-alpha - ${otel.instrumentation.version} - pom - import - - - io.opentelemetry - opentelemetry-proto - 1.7.1-alpha - test - - - - - - - org.junit.jupiter - junit-jupiter - ${junit-jupiter.version} - test - - - org.junit.jupiter - junit-jupiter-params - ${junit-jupiter.version} - test - - - org.mockito - mockito-core - 5.18.0 - test - - - org.assertj - assertj-core - 3.27.3 - test - - - com.google.guava - guava - ${guava.version} - test - - - org.slf4j - slf4j-simple - 2.0.17 - test - - - org.junit-pioneer - junit-pioneer - 2.3.0 - test - - - org.awaitility - awaitility - 4.3.0 - test - - - org.wiremock - wiremock - 3.13.1 - test - - - org.hamcrest - hamcrest-core - - - - javadoc @@ -429,6 +417,7 @@ maven-javadoc-plugin + ${javadoc.skip} UTF-8 UTF-8 true @@ -442,6 +431,45 @@ + + errorprone + + [21,) + + + + + maven-compiler-plugin + + + -XDaddTypeAnnotationsToSymbol=true + + -Xplugin:ErrorProne + -Xep:AlmostJavadoc:OFF + -Xep:MissingSummary:OFF + -Xep:LongDoubleConversion:OFF + -Xep:StringSplitter:OFF + -XepExcludedPaths:(.*/generated/.*|.*/src/test/java/.*|.*/examples/.*|.*/integration-tests/.*) + -XepOpt:NullAway:AnnotatedPackages=io.prometheus.metrics + + + + + com.google.errorprone + error_prone_core + 2.47.0 + + + com.uber.nullaway + nullaway + 0.13.1 + + + + + + + release diff --git a/prometheus-metrics-bom/pom.xml b/prometheus-metrics-bom/pom.xml index 242710819..167ea522e 100644 --- a/prometheus-metrics-bom/pom.xml +++ b/prometheus-metrics-bom/pom.xml @@ -6,7 +6,7 @@ io.prometheus client_java_parent - 1.4.0-SNAPSHOT + 1.6.0-SNAPSHOT ../prometheus-metrics-parent/pom.xml @@ -119,6 +119,12 @@ prometheus-metrics-model ${project.version} + + io.prometheus + prometheus-metrics-otel-support + ${project.version} + pom + io.prometheus prometheus-metrics-simpleclient-bridge diff --git a/prometheus-metrics-config/pom.xml b/prometheus-metrics-config/pom.xml index 4546c3dc9..494282cc7 100644 --- a/prometheus-metrics-config/pom.xml +++ b/prometheus-metrics-config/pom.xml @@ -6,7 +6,7 @@ io.prometheus client_java - 1.4.0-SNAPSHOT + 1.6.0-SNAPSHOT prometheus-metrics-config diff --git a/prometheus-metrics-config/src/main/java/io/prometheus/metrics/config/EscapingScheme.java b/prometheus-metrics-config/src/main/java/io/prometheus/metrics/config/EscapingScheme.java new file mode 100644 index 000000000..1cd037bf3 --- /dev/null +++ b/prometheus-metrics-config/src/main/java/io/prometheus/metrics/config/EscapingScheme.java @@ -0,0 +1,85 @@ +package io.prometheus.metrics.config; + +import javax.annotation.Nullable; + +public enum EscapingScheme { + /** NO_ESCAPING indicates that a name will not be escaped. */ + ALLOW_UTF8("allow-utf-8"), + + /** UNDERSCORE_ESCAPING replaces all legacy-invalid characters with underscores. */ + UNDERSCORE_ESCAPING("underscores"), + + /** + * DOTS_ESCAPING is similar to UNDERSCORE_ESCAPING, except that dots are converted to `_dot_` and + * pre-existing underscores are converted to `__`. + */ + DOTS_ESCAPING("dots"), + + /** + * VALUE_ENCODING_ESCAPING prepends the name with `U__` and replaces all invalid characters with + * the Unicode value, surrounded by underscores. Single underscores are replaced with double + * underscores. + */ + VALUE_ENCODING_ESCAPING("values"); + + private static final String ESCAPING_KEY = "escaping"; + + /** Default escaping scheme for names when not specified. */ + public static final EscapingScheme DEFAULT = UNDERSCORE_ESCAPING; + + public final String getValue() { + return value; + } + + private final String value; + + EscapingScheme(String value) { + this.value = value; + } + + /** + * fromAcceptHeader returns an EscapingScheme depending on the Accept header. Iff the header + * contains an escaping=allow-utf-8 term, it will select NO_ESCAPING. If a valid "escaping" term + * exists, that will be used. Otherwise, the global default will be returned. + */ + public static EscapingScheme fromAcceptHeader(@Nullable String acceptHeader) { + if (acceptHeader != null) { + for (String p : acceptHeader.split(";")) { + String[] toks = p.split("="); + if (toks.length != 2) { + continue; + } + String key = toks[0].trim(); + String value = toks[1].trim(); + if (key.equals(ESCAPING_KEY)) { + try { + return EscapingScheme.forString(value); + } catch (IllegalArgumentException e) { + // If the escaping parameter is unknown, ignore it. + return DEFAULT; + } + } + } + } + return DEFAULT; + } + + static EscapingScheme forString(String value) { + switch (value) { + case "allow-utf-8": + return ALLOW_UTF8; + case "underscores": + return UNDERSCORE_ESCAPING; + case "dots": + return DOTS_ESCAPING; + case "values": + return VALUE_ENCODING_ESCAPING; + default: + throw new IllegalArgumentException("Unknown escaping scheme: " + value); + } + } + + public String toHeaderFormat() { + return "; " + ESCAPING_KEY + "=" + value; + } +} diff --git a/prometheus-metrics-config/src/main/java/io/prometheus/metrics/config/ExemplarsProperties.java b/prometheus-metrics-config/src/main/java/io/prometheus/metrics/config/ExemplarsProperties.java index 017d67909..765d33ac5 100644 --- a/prometheus-metrics-config/src/main/java/io/prometheus/metrics/config/ExemplarsProperties.java +++ b/prometheus-metrics-config/src/main/java/io/prometheus/metrics/config/ExemplarsProperties.java @@ -1,23 +1,23 @@ package io.prometheus.metrics.config; -import java.util.Map; +import javax.annotation.Nullable; /** Properties starting with io.prometheus.exemplars */ public class ExemplarsProperties { private static final String PREFIX = "io.prometheus.exemplars"; - private static final String MIN_RETENTION_PERIOD_SECONDS = "minRetentionPeriodSeconds"; - private static final String MAX_RETENTION_PERIOD_SECONDS = "maxRetentionPeriodSeconds"; - private static final String SAMPLE_INTERVAL_MILLISECONDS = "sampleIntervalMilliseconds"; + private static final String MIN_RETENTION_PERIOD_SECONDS = "min_retention_period_seconds"; + private static final String MAX_RETENTION_PERIOD_SECONDS = "max_retention_period_seconds"; + private static final String SAMPLE_INTERVAL_MILLISECONDS = "sample_interval_milliseconds"; - private final Integer minRetentionPeriodSeconds; - private final Integer maxRetentionPeriodSeconds; - private final Integer sampleIntervalMilliseconds; + @Nullable private final Integer minRetentionPeriodSeconds; + @Nullable private final Integer maxRetentionPeriodSeconds; + @Nullable private final Integer sampleIntervalMilliseconds; private ExemplarsProperties( - Integer minRetentionPeriodSeconds, - Integer maxRetentionPeriodSeconds, - Integer sampleIntervalMilliseconds) { + @Nullable Integer minRetentionPeriodSeconds, + @Nullable Integer maxRetentionPeriodSeconds, + @Nullable Integer sampleIntervalMilliseconds) { this.minRetentionPeriodSeconds = minRetentionPeriodSeconds; this.maxRetentionPeriodSeconds = maxRetentionPeriodSeconds; this.sampleIntervalMilliseconds = sampleIntervalMilliseconds; @@ -28,6 +28,7 @@ private ExemplarsProperties( * *

Default see {@code ExemplarSamplerConfig.DEFAULT_MIN_RETENTION_PERIOD_SECONDS} */ + @Nullable public Integer getMinRetentionPeriodSeconds() { return minRetentionPeriodSeconds; } @@ -37,6 +38,7 @@ public Integer getMinRetentionPeriodSeconds() { * *

Default see {@code ExemplarSamplerConfig.DEFAULT_MAX_RETENTION_PERIOD_SECONDS} */ + @Nullable public Integer getMaxRetentionPeriodSeconds() { return maxRetentionPeriodSeconds; } @@ -48,22 +50,23 @@ public Integer getMaxRetentionPeriodSeconds() { * *

Default see {@code ExemplarSamplerConfig.DEFAULT_SAMPLE_INTERVAL_MILLISECONDS} */ + @Nullable public Integer getSampleIntervalMilliseconds() { return sampleIntervalMilliseconds; } /** - * Note that this will remove entries from {@code properties}. This is because we want to know if - * there are unused properties remaining after all properties have been loaded. + * Note that this will remove entries from {@code propertySource}. This is because we want to know + * if there are unused properties remaining after all properties have been loaded. */ - static ExemplarsProperties load(Map properties) + static ExemplarsProperties load(PropertySource propertySource) throws PrometheusPropertiesException { Integer minRetentionPeriodSeconds = - Util.loadInteger(PREFIX + "." + MIN_RETENTION_PERIOD_SECONDS, properties); + Util.loadInteger(PREFIX, MIN_RETENTION_PERIOD_SECONDS, propertySource); Integer maxRetentionPeriodSeconds = - Util.loadInteger(PREFIX + "." + MAX_RETENTION_PERIOD_SECONDS, properties); + Util.loadInteger(PREFIX, MAX_RETENTION_PERIOD_SECONDS, propertySource); Integer sampleIntervalMilliseconds = - Util.loadInteger(PREFIX + "." + SAMPLE_INTERVAL_MILLISECONDS, properties); + Util.loadInteger(PREFIX, SAMPLE_INTERVAL_MILLISECONDS, propertySource); Util.assertValue( minRetentionPeriodSeconds, @@ -108,9 +111,9 @@ public static Builder builder() { public static class Builder { - private Integer minRetentionPeriodSeconds; - private Integer maxRetentionPeriodSeconds; - private Integer sampleIntervalMilliseconds; + @Nullable private Integer minRetentionPeriodSeconds; + @Nullable private Integer maxRetentionPeriodSeconds; + @Nullable private Integer sampleIntervalMilliseconds; private Builder() {} diff --git a/prometheus-metrics-config/src/main/java/io/prometheus/metrics/config/ExporterFilterProperties.java b/prometheus-metrics-config/src/main/java/io/prometheus/metrics/config/ExporterFilterProperties.java index c2c3d48d3..d59330cb9 100644 --- a/prometheus-metrics-config/src/main/java/io/prometheus/metrics/config/ExporterFilterProperties.java +++ b/prometheus-metrics-config/src/main/java/io/prometheus/metrics/config/ExporterFilterProperties.java @@ -4,27 +4,27 @@ import java.util.Arrays; import java.util.Collections; import java.util.List; -import java.util.Map; +import javax.annotation.Nullable; /** Properties starting with io.prometheus.exporter.filter */ public class ExporterFilterProperties { - public static final String METRIC_NAME_MUST_BE_EQUAL_TO = "metricNameMustBeEqualTo"; - public static final String METRIC_NAME_MUST_NOT_BE_EQUAL_TO = "metricNameMustNotBeEqualTo"; - public static final String METRIC_NAME_MUST_START_WITH = "metricNameMustStartWith"; - public static final String METRIC_NAME_MUST_NOT_START_WITH = "metricNameMustNotStartWith"; + public static final String METRIC_NAME_MUST_BE_EQUAL_TO = "metric_name_must_be_equal_to"; + public static final String METRIC_NAME_MUST_NOT_BE_EQUAL_TO = "metric_name_must_not_be_equal_to"; + public static final String METRIC_NAME_MUST_START_WITH = "metric_name_must_start_with"; + public static final String METRIC_NAME_MUST_NOT_START_WITH = "metric_name_must_not_start_with"; private static final String PREFIX = "io.prometheus.exporter.filter"; - private final List allowedNames; - private final List excludedNames; - private final List allowedPrefixes; - private final List excludedPrefixes; + @Nullable private final List allowedNames; + @Nullable private final List excludedNames; + @Nullable private final List allowedPrefixes; + @Nullable private final List excludedPrefixes; private ExporterFilterProperties( - List allowedNames, - List excludedNames, - List allowedPrefixes, - List excludedPrefixes) { + @Nullable List allowedNames, + @Nullable List excludedNames, + @Nullable List allowedPrefixes, + @Nullable List excludedPrefixes) { this.allowedNames = allowedNames == null ? null : Collections.unmodifiableList(new ArrayList<>(allowedNames)); this.excludedNames = @@ -39,36 +39,40 @@ private ExporterFilterProperties( : Collections.unmodifiableList(new ArrayList<>(excludedPrefixes)); } + @Nullable public List getAllowedMetricNames() { return allowedNames; } + @Nullable public List getExcludedMetricNames() { return excludedNames; } + @Nullable public List getAllowedMetricNamePrefixes() { return allowedPrefixes; } + @Nullable public List getExcludedMetricNamePrefixes() { return excludedPrefixes; } /** - * Note that this will remove entries from {@code properties}. This is because we want to know if - * there are unused properties remaining after all properties have been loaded. + * Note that this will remove entries from {@code propertySource}. This is because we want to know + * if there are unused properties remaining after all properties have been loaded. */ - static ExporterFilterProperties load(Map properties) + static ExporterFilterProperties load(PropertySource propertySource) throws PrometheusPropertiesException { List allowedNames = - Util.loadStringList(PREFIX + "." + METRIC_NAME_MUST_BE_EQUAL_TO, properties); + Util.loadStringList(PREFIX, METRIC_NAME_MUST_BE_EQUAL_TO, propertySource); List excludedNames = - Util.loadStringList(PREFIX + "." + METRIC_NAME_MUST_NOT_BE_EQUAL_TO, properties); + Util.loadStringList(PREFIX, METRIC_NAME_MUST_NOT_BE_EQUAL_TO, propertySource); List allowedPrefixes = - Util.loadStringList(PREFIX + "." + METRIC_NAME_MUST_START_WITH, properties); + Util.loadStringList(PREFIX, METRIC_NAME_MUST_START_WITH, propertySource); List excludedPrefixes = - Util.loadStringList(PREFIX + "." + METRIC_NAME_MUST_NOT_START_WITH, properties); + Util.loadStringList(PREFIX, METRIC_NAME_MUST_NOT_START_WITH, propertySource); return new ExporterFilterProperties( allowedNames, excludedNames, allowedPrefixes, excludedPrefixes); } @@ -79,10 +83,10 @@ public static Builder builder() { public static class Builder { - private List allowedNames; - private List excludedNames; - private List allowedPrefixes; - private List excludedPrefixes; + @Nullable private List allowedNames; + @Nullable private List excludedNames; + @Nullable private List allowedPrefixes; + @Nullable private List excludedPrefixes; private Builder() {} diff --git a/prometheus-metrics-config/src/main/java/io/prometheus/metrics/config/ExporterHttpServerProperties.java b/prometheus-metrics-config/src/main/java/io/prometheus/metrics/config/ExporterHttpServerProperties.java index 6618ab88e..0623f78e1 100644 --- a/prometheus-metrics-config/src/main/java/io/prometheus/metrics/config/ExporterHttpServerProperties.java +++ b/prometheus-metrics-config/src/main/java/io/prometheus/metrics/config/ExporterHttpServerProperties.java @@ -1,31 +1,44 @@ package io.prometheus.metrics.config; -import java.util.Map; +import javax.annotation.Nullable; -/** Properties starting with io.prometheus.exporter.httpServer */ +/** Properties starting with io.prometheus.exporter.http_server */ public class ExporterHttpServerProperties { private static final String PORT = "port"; - private static final String PREFIX = "io.prometheus.exporter.httpServer"; - private final Integer port; + private static final String PREFER_UNCOMPRESSED_RESPONSE = "prefer_uncompressed_response"; + private static final String PREFIX = "io.prometheus.exporter.http_server"; + @Nullable private final Integer port; + private final boolean preferUncompressedResponse; - private ExporterHttpServerProperties(Integer port) { + private ExporterHttpServerProperties(@Nullable Integer port, boolean preferUncompressedResponse) { this.port = port; + this.preferUncompressedResponse = preferUncompressedResponse; } + @Nullable public Integer getPort() { return port; } + public boolean isPreferUncompressedResponse() { + return preferUncompressedResponse; + } + /** - * Note that this will remove entries from {@code properties}. This is because we want to know if - * there are unused properties remaining after all properties have been loaded. + * Note that this will remove entries from {@code propertySource}. This is because we want to know + * if there are unused properties remaining after all properties have been loaded. */ - static ExporterHttpServerProperties load(Map properties) + static ExporterHttpServerProperties load(PropertySource propertySource) throws PrometheusPropertiesException { - Integer port = Util.loadInteger(PREFIX + "." + PORT, properties); + Integer port = Util.loadInteger(PREFIX, PORT, propertySource); Util.assertValue(port, t -> t > 0, "Expecting value > 0.", PREFIX, PORT); - return new ExporterHttpServerProperties(port); + + Boolean preferUncompressedResponse = + Util.loadBoolean(PREFIX, PREFER_UNCOMPRESSED_RESPONSE, propertySource); + + return new ExporterHttpServerProperties( + port, preferUncompressedResponse != null && preferUncompressedResponse); } public static Builder builder() { @@ -34,7 +47,8 @@ public static Builder builder() { public static class Builder { - private Integer port; + @Nullable private Integer port; + private boolean preferUncompressedResponse = false; private Builder() {} @@ -43,8 +57,13 @@ public Builder port(int port) { return this; } + public Builder preferUncompressedResponse(boolean preferUncompressedResponse) { + this.preferUncompressedResponse = preferUncompressedResponse; + return this; + } + public ExporterHttpServerProperties build() { - return new ExporterHttpServerProperties(port); + return new ExporterHttpServerProperties(port, preferUncompressedResponse); } } } diff --git a/prometheus-metrics-config/src/main/java/io/prometheus/metrics/config/ExporterOpenTelemetryProperties.java b/prometheus-metrics-config/src/main/java/io/prometheus/metrics/config/ExporterOpenTelemetryProperties.java index 53acb6b14..bd1dcdaf2 100644 --- a/prometheus-metrics-config/src/main/java/io/prometheus/metrics/config/ExporterOpenTelemetryProperties.java +++ b/prometheus-metrics-config/src/main/java/io/prometheus/metrics/config/ExporterOpenTelemetryProperties.java @@ -2,8 +2,35 @@ import java.util.HashMap; import java.util.Map; +import javax.annotation.Nullable; -// TODO: JavaDoc is currently only in OpenTelemetryExporter.Builder. Look there for reference. +/** + * Properties for configuring the OpenTelemetry exporter. + * + *

These properties can be configured via {@code prometheus.properties}, system properties, or + * programmatically. + * + *

All properties are prefixed with {@code io.prometheus.exporter.opentelemetry}. + * + *

Available properties: + * + *

    + *
  • {@code protocol} - OTLP protocol: {@code "grpc"} or {@code "http/protobuf"} + *
  • {@code endpoint} - OTLP endpoint URL + *
  • {@code headers} - HTTP headers for outgoing requests + *
  • {@code intervalSeconds} - Export interval in seconds + *
  • {@code timeoutSeconds} - Request timeout in seconds + *
  • {@code serviceName} - Service name resource attribute + *
  • {@code serviceNamespace} - Service namespace resource attribute + *
  • {@code serviceInstanceId} - Service instance ID resource attribute + *
  • {@code serviceVersion} - Service version resource attribute + *
  • {@code resourceAttributes} - Additional resource attributes + *
+ * + * @see OpenTelemetry + * SDK Environment Variables + */ public class ExporterOpenTelemetryProperties { // See @@ -11,37 +38,37 @@ public class ExporterOpenTelemetryProperties { private static final String PROTOCOL = "protocol"; // otel.exporter.otlp.protocol private static final String ENDPOINT = "endpoint"; // otel.exporter.otlp.endpoint private static final String HEADERS = "headers"; // otel.exporter.otlp.headers - private static final String INTERVAL_SECONDS = "intervalSeconds"; // otel.metric.export.interval - private static final String TIMEOUT_SECONDS = "timeoutSeconds"; // otel.exporter.otlp.timeout - private static final String SERVICE_NAME = "serviceName"; // otel.service.name - private static final String SERVICE_NAMESPACE = "serviceNamespace"; - private static final String SERVICE_INSTANCE_ID = "serviceInstanceId"; - private static final String SERVICE_VERSION = "serviceVersion"; + private static final String INTERVAL_SECONDS = "interval_seconds"; // otel.metric.export.interval + private static final String TIMEOUT_SECONDS = "timeout_seconds"; // otel.exporter.otlp.timeout + private static final String SERVICE_NAME = "service_name"; // otel.service.name + private static final String SERVICE_NAMESPACE = "service_namespace"; + private static final String SERVICE_INSTANCE_ID = "service_instance_id"; + private static final String SERVICE_VERSION = "service_version"; private static final String RESOURCE_ATTRIBUTES = - "resourceAttributes"; // otel.resource.attributes + "resource_attributes"; // otel.resource.attributes private static final String PREFIX = "io.prometheus.exporter.opentelemetry"; - private final String protocol; - private final String endpoint; + @Nullable private final String endpoint; + @Nullable private final String protocol; private final Map headers; - private final String interval; - private final String timeout; - private final String serviceName; - private final String serviceNamespace; - private final String serviceInstanceId; - private final String serviceVersion; + @Nullable private final String interval; + @Nullable private final String timeout; + @Nullable private final String serviceName; + @Nullable private final String serviceNamespace; + @Nullable private final String serviceInstanceId; + @Nullable private final String serviceVersion; private final Map resourceAttributes; private ExporterOpenTelemetryProperties( - String protocol, - String endpoint, + @Nullable String protocol, + @Nullable String endpoint, Map headers, - String interval, - String timeout, - String serviceName, - String serviceNamespace, - String serviceInstanceId, - String serviceVersion, + @Nullable String interval, + @Nullable String timeout, + @Nullable String serviceName, + @Nullable String serviceNamespace, + @Nullable String serviceInstanceId, + @Nullable String serviceVersion, Map resourceAttributes) { this.protocol = protocol; this.endpoint = endpoint; @@ -55,10 +82,12 @@ private ExporterOpenTelemetryProperties( this.resourceAttributes = resourceAttributes; } + @Nullable public String getProtocol() { return protocol; } + @Nullable public String getEndpoint() { return endpoint; } @@ -67,26 +96,32 @@ public Map getHeaders() { return headers; } + @Nullable public String getInterval() { return interval; } + @Nullable public String getTimeout() { return timeout; } + @Nullable public String getServiceName() { return serviceName; } + @Nullable public String getServiceNamespace() { return serviceNamespace; } + @Nullable public String getServiceInstanceId() { return serviceInstanceId; } + @Nullable public String getServiceVersion() { return serviceVersion; } @@ -96,22 +131,22 @@ public Map getResourceAttributes() { } /** - * Note that this will remove entries from {@code properties}. This is because we want to know if - * there are unused properties remaining after all properties have been loaded. + * Note that this will remove entries from {@code propertySource}. This is because we want to know + * if there are unused properties remaining after all properties have been loaded. */ - static ExporterOpenTelemetryProperties load(Map properties) + static ExporterOpenTelemetryProperties load(PropertySource propertySource) throws PrometheusPropertiesException { - String protocol = Util.loadString(PREFIX + "." + PROTOCOL, properties); - String endpoint = Util.loadString(PREFIX + "." + ENDPOINT, properties); - Map headers = Util.loadMap(PREFIX + "." + HEADERS, properties); - String interval = Util.loadStringAddSuffix(PREFIX + "." + INTERVAL_SECONDS, properties, "s"); - String timeout = Util.loadStringAddSuffix(PREFIX + "." + TIMEOUT_SECONDS, properties, "s"); - String serviceName = Util.loadString(PREFIX + "." + SERVICE_NAME, properties); - String serviceNamespace = Util.loadString(PREFIX + "." + SERVICE_NAMESPACE, properties); - String serviceInstanceId = Util.loadString(PREFIX + "." + SERVICE_INSTANCE_ID, properties); - String serviceVersion = Util.loadString(PREFIX + "." + SERVICE_VERSION, properties); + String protocol = Util.loadString(PREFIX, PROTOCOL, propertySource); + String endpoint = Util.loadString(PREFIX, ENDPOINT, propertySource); + Map headers = Util.loadMap(PREFIX, HEADERS, propertySource); + String interval = Util.loadStringAddSuffix(PREFIX, INTERVAL_SECONDS, propertySource, "s"); + String timeout = Util.loadStringAddSuffix(PREFIX, TIMEOUT_SECONDS, propertySource, "s"); + String serviceName = Util.loadString(PREFIX, SERVICE_NAME, propertySource); + String serviceNamespace = Util.loadString(PREFIX, SERVICE_NAMESPACE, propertySource); + String serviceInstanceId = Util.loadString(PREFIX, SERVICE_INSTANCE_ID, propertySource); + String serviceVersion = Util.loadString(PREFIX, SERVICE_VERSION, propertySource); Map resourceAttributes = - Util.loadMap(PREFIX + "." + RESOURCE_ATTRIBUTES, properties); + Util.loadMap(PREFIX, RESOURCE_ATTRIBUTES, propertySource); return new ExporterOpenTelemetryProperties( protocol, endpoint, @@ -131,19 +166,27 @@ public static Builder builder() { public static class Builder { - private String protocol; - private String endpoint; + @Nullable private String protocol; + @Nullable private String endpoint; private final Map headers = new HashMap<>(); - private String interval; - private String timeout; - private String serviceName; - private String serviceNamespace; - private String serviceInstanceId; - private String serviceVersion; + @Nullable private String interval; + @Nullable private String timeout; + @Nullable private String serviceName; + @Nullable private String serviceNamespace; + @Nullable private String serviceInstanceId; + @Nullable private String serviceVersion; private final Map resourceAttributes = new HashMap<>(); private Builder() {} + /** + * The OTLP protocol to use. + * + *

Supported values: {@code "grpc"} or {@code "http/protobuf"}. + * + *

See OpenTelemetry's OTEL_EXPORTER_OTLP_PROTOCOL. + */ public Builder protocol(String protocol) { if (!protocol.equals("grpc") && !protocol.equals("http/protobuf")) { throw new IllegalArgumentException( @@ -153,17 +196,43 @@ public Builder protocol(String protocol) { return this; } + /** + * The OTLP endpoint to send metric data to. + * + *

The default depends on the protocol: + * + *

    + *
  • {@code "grpc"}: {@code "http://localhost:4317"} + *
  • {@code "http/protobuf"}: {@code "http://localhost:4318/v1/metrics"} + *
+ * + *

See OpenTelemetry's OTEL_EXPORTER_OTLP_METRICS_ENDPOINT. + */ public Builder endpoint(String endpoint) { this.endpoint = endpoint; return this; } - /** Add a request header. Call multiple times to add multiple headers. */ + /** + * Add an HTTP header to be applied to outgoing requests. Call multiple times to add multiple + * headers. + * + *

See OpenTelemetry's OTEL_EXPORTER_OTLP_HEADERS. + */ public Builder header(String name, String value) { this.headers.put(name, value); return this; } + /** + * The interval between the start of two export attempts. Default is 60 seconds. + * + *

Like OpenTelemetry's OTEL_METRIC_EXPORT_INTERVAL + * (which defaults to 60000 milliseconds), but specified in seconds rather than milliseconds. + */ public Builder intervalSeconds(int intervalSeconds) { if (intervalSeconds <= 0) { throw new IllegalArgumentException(intervalSeconds + ": Expecting intervalSeconds > 0"); @@ -172,6 +241,13 @@ public Builder intervalSeconds(int intervalSeconds) { return this; } + /** + * The timeout for outgoing requests. Default is 10. + * + *

Like OpenTelemetry's OTEL_EXPORTER_OTLP_METRICS_TIMEOUT, + * but in seconds rather than milliseconds. + */ public Builder timeoutSeconds(int timeoutSeconds) { if (timeoutSeconds <= 0) { throw new IllegalArgumentException(timeoutSeconds + ": Expecting timeoutSeconds > 0"); @@ -180,26 +256,63 @@ public Builder timeoutSeconds(int timeoutSeconds) { return this; } + /** + * The {@code service.name} resource attribute. + * + *

If not explicitly specified, {@code client_java} will try to initialize it with a + * reasonable default, like the JAR file name. + * + *

See {@code service.name} in OpenTelemetry's Resource + * Semantic Conventions. + */ public Builder serviceName(String serviceName) { this.serviceName = serviceName; return this; } + /** + * The {@code service.namespace} resource attribute. + * + *

See {@code service.namespace} in OpenTelemetry's Resource + * Semantic Conventions. + */ public Builder serviceNamespace(String serviceNamespace) { this.serviceNamespace = serviceNamespace; return this; } + /** + * The {@code service.instance.id} resource attribute. + * + *

See {@code service.instance.id} in OpenTelemetry's Resource + * Semantic Conventions. + */ public Builder serviceInstanceId(String serviceInstanceId) { this.serviceInstanceId = serviceInstanceId; return this; } + /** + * The {@code service.version} resource attribute. + * + *

See {@code service.version} in OpenTelemetry's Resource + * Semantic Conventions. + */ public Builder serviceVersion(String serviceVersion) { this.serviceVersion = serviceVersion; return this; } + /** + * Add a resource attribute. Call multiple times to add multiple resource attributes. + * + *

See OpenTelemetry's OTEL_RESOURCE_ATTRIBUTES. + */ public Builder resourceAttribute(String name, String value) { this.resourceAttributes.put(name, value); return this; diff --git a/prometheus-metrics-config/src/main/java/io/prometheus/metrics/config/ExporterProperties.java b/prometheus-metrics-config/src/main/java/io/prometheus/metrics/config/ExporterProperties.java index d35f970ff..460a5cae2 100644 --- a/prometheus-metrics-config/src/main/java/io/prometheus/metrics/config/ExporterProperties.java +++ b/prometheus-metrics-config/src/main/java/io/prometheus/metrics/config/ExporterProperties.java @@ -1,24 +1,24 @@ package io.prometheus.metrics.config; -import java.util.Map; +import javax.annotation.Nullable; /** Properties starting with io.prometheus.exporter */ public class ExporterProperties { - private static final String INCLUDE_CREATED_TIMESTAMPS = "includeCreatedTimestamps"; + private static final String INCLUDE_CREATED_TIMESTAMPS = "include_created_timestamps"; // milliseconds is the default - we only provide a boolean flag to avoid a breaking change - private static final String PROMETHEUS_TIMESTAMPS_IN_MS = "prometheusTimestampsInMs"; - private static final String EXEMPLARS_ON_ALL_METRIC_TYPES = "exemplarsOnAllMetricTypes"; + private static final String PROMETHEUS_TIMESTAMPS_IN_MS = "prometheus_timestamps_in_ms"; + private static final String EXEMPLARS_ON_ALL_METRIC_TYPES = "exemplars_on_all_metric_types"; private static final String PREFIX = "io.prometheus.exporter"; - private final Boolean includeCreatedTimestamps; - private final Boolean prometheusTimestampsInMs; - private final Boolean exemplarsOnAllMetricTypes; + @Nullable private final Boolean includeCreatedTimestamps; + @Nullable private final Boolean prometheusTimestampsInMs; + @Nullable private final Boolean exemplarsOnAllMetricTypes; private ExporterProperties( - Boolean includeCreatedTimestamps, - Boolean prometheusTimestampsInMs, - Boolean exemplarsOnAllMetricTypes) { + @Nullable Boolean includeCreatedTimestamps, + @Nullable Boolean prometheusTimestampsInMs, + @Nullable Boolean exemplarsOnAllMetricTypes) { this.includeCreatedTimestamps = includeCreatedTimestamps; this.prometheusTimestampsInMs = prometheusTimestampsInMs; this.exemplarsOnAllMetricTypes = exemplarsOnAllMetricTypes; @@ -43,17 +43,16 @@ public boolean getExemplarsOnAllMetricTypes() { } /** - * Note that this will remove entries from {@code properties}. This is because we want to know if - * there are unused properties remaining after all properties have been loaded. + * Note that this will remove entries from {@code propertySource}. This is because we want to know + * if there are unused properties remaining after all properties have been loaded. */ - static ExporterProperties load(Map properties) + static ExporterProperties load(PropertySource propertySource) throws PrometheusPropertiesException { Boolean includeCreatedTimestamps = - Util.loadBoolean(PREFIX + "." + INCLUDE_CREATED_TIMESTAMPS, properties); - Boolean timestampsInMs = - Util.loadBoolean(PREFIX + "." + PROMETHEUS_TIMESTAMPS_IN_MS, properties); + Util.loadBoolean(PREFIX, INCLUDE_CREATED_TIMESTAMPS, propertySource); + Boolean timestampsInMs = Util.loadBoolean(PREFIX, PROMETHEUS_TIMESTAMPS_IN_MS, propertySource); Boolean exemplarsOnAllMetricTypes = - Util.loadBoolean(PREFIX + "." + EXEMPLARS_ON_ALL_METRIC_TYPES, properties); + Util.loadBoolean(PREFIX, EXEMPLARS_ON_ALL_METRIC_TYPES, propertySource); return new ExporterProperties( includeCreatedTimestamps, timestampsInMs, exemplarsOnAllMetricTypes); } @@ -64,8 +63,8 @@ public static Builder builder() { public static class Builder { - private Boolean includeCreatedTimestamps; - private Boolean exemplarsOnAllMetricTypes; + @Nullable private Boolean includeCreatedTimestamps; + @Nullable private Boolean exemplarsOnAllMetricTypes; boolean prometheusTimestampsInMs; private Builder() {} diff --git a/prometheus-metrics-config/src/main/java/io/prometheus/metrics/config/ExporterPushgatewayProperties.java b/prometheus-metrics-config/src/main/java/io/prometheus/metrics/config/ExporterPushgatewayProperties.java index 8aafba3a4..aea4b2d8f 100644 --- a/prometheus-metrics-config/src/main/java/io/prometheus/metrics/config/ExporterPushgatewayProperties.java +++ b/prometheus-metrics-config/src/main/java/io/prometheus/metrics/config/ExporterPushgatewayProperties.java @@ -1,24 +1,41 @@ package io.prometheus.metrics.config; -import java.util.Map; +import java.time.Duration; +import javax.annotation.Nullable; public class ExporterPushgatewayProperties { private static final String ADDRESS = "address"; private static final String JOB = "job"; private static final String SCHEME = "scheme"; + private static final String ESCAPING_SCHEME = "escaping_scheme"; + private static final String READ_TIMEOUT = "read_timeout_seconds"; + private static final String CONNECT_TIMEOUT = "connect_timeout_seconds"; private static final String PREFIX = "io.prometheus.exporter.pushgateway"; - private final String scheme; - private final String address; - private final String job; + @Nullable private final String scheme; + @Nullable private final String address; + @Nullable private final String job; + @Nullable private final EscapingScheme escapingScheme; + @Nullable private final Duration connectTimeout; + @Nullable private final Duration readTimeout; - private ExporterPushgatewayProperties(String address, String job, String scheme) { + private ExporterPushgatewayProperties( + @Nullable String address, + @Nullable String job, + @Nullable String scheme, + @Nullable EscapingScheme escapingScheme, + @Nullable Duration connectTimeout, + @Nullable Duration readTimeout) { this.address = address; this.job = job; this.scheme = scheme; + this.escapingScheme = escapingScheme; + this.connectTimeout = connectTimeout; + this.readTimeout = readTimeout; } /** Address of the Pushgateway in the form {@code host:port}. Default is {@code localhost:9091} */ + @Nullable public String getAddress() { return address; } @@ -27,6 +44,7 @@ public String getAddress() { * {@code job} label for metrics being pushed. Default is the name of the JAR file that is * running. */ + @Nullable public String getJob() { return job; } @@ -35,19 +53,42 @@ public String getJob() { * Scheme to be used when pushing metrics to the pushgateway. Must be "http" or "https". Default * is "http". */ + @Nullable public String getScheme() { return scheme; } + /** Escaping scheme to be used when pushing metric data to the pushgateway. */ + @Nullable + public EscapingScheme getEscapingScheme() { + return escapingScheme; + } + + /** Connection timeout for connections to the Pushgateway. */ + @Nullable + public Duration getConnectTimeout() { + return connectTimeout; + } + + /** Read timeout for connections to the Pushgateway. */ + @Nullable + public Duration getReadTimeout() { + return readTimeout; + } + /** - * Note that this will remove entries from {@code properties}. This is because we want to know if - * there are unused properties remaining after all properties have been loaded. + * Note that this will remove entries from {@code propertySource}. This is because we want to know + * if there are unused properties remaining after all properties have been loaded. */ - static ExporterPushgatewayProperties load(Map properties) + static ExporterPushgatewayProperties load(PropertySource propertySource) throws PrometheusPropertiesException { - String address = Util.loadString(PREFIX + "." + ADDRESS, properties); - String job = Util.loadString(PREFIX + "." + JOB, properties); - String scheme = Util.loadString(PREFIX + "." + SCHEME, properties); + String address = Util.loadString(PREFIX, ADDRESS, propertySource); + String job = Util.loadString(PREFIX, JOB, propertySource); + String scheme = Util.loadString(PREFIX, SCHEME, propertySource); + String escapingScheme = Util.loadString(PREFIX, ESCAPING_SCHEME, propertySource); + Duration connectTimeout = Util.loadOptionalDuration(PREFIX, CONNECT_TIMEOUT, propertySource); + Duration readTimeout = Util.loadOptionalDuration(PREFIX, READ_TIMEOUT, propertySource); + if (scheme != null) { if (!scheme.equals("http") && !scheme.equals("https")) { throw new PrometheusPropertiesException( @@ -56,6 +97,80 @@ static ExporterPushgatewayProperties load(Map properties) PREFIX, SCHEME, scheme)); } } - return new ExporterPushgatewayProperties(address, job, scheme); + + return new ExporterPushgatewayProperties( + address, job, scheme, parseEscapingScheme(escapingScheme), connectTimeout, readTimeout); + } + + private static @Nullable EscapingScheme parseEscapingScheme(@Nullable String scheme) { + if (scheme == null) { + return null; + } + switch (scheme) { + case "allow-utf-8": + return EscapingScheme.ALLOW_UTF8; + case "values": + return EscapingScheme.VALUE_ENCODING_ESCAPING; + case "underscores": + return EscapingScheme.UNDERSCORE_ESCAPING; + case "dots": + return EscapingScheme.DOTS_ESCAPING; + default: + throw new PrometheusPropertiesException( + String.format( + "%s.%s: Illegal value. Expecting 'allow-utf-8', 'values', 'underscores', " + + "or 'dots'. Found: %s", + PREFIX, ESCAPING_SCHEME, scheme)); + } + } + + public static Builder builder() { + return new Builder(); + } + + public static class Builder { + @Nullable private String address; + @Nullable private String job; + @Nullable private String scheme; + @Nullable private EscapingScheme escapingScheme; + @Nullable private Duration connectTimeout; + @Nullable private Duration readTimeout; + + private Builder() {} + + public Builder address(String address) { + this.address = address; + return this; + } + + public Builder job(String job) { + this.job = job; + return this; + } + + public Builder scheme(String scheme) { + this.scheme = scheme; + return this; + } + + public Builder escapingScheme(EscapingScheme escapingScheme) { + this.escapingScheme = escapingScheme; + return this; + } + + public Builder connectTimeout(Duration connectTimeout) { + this.connectTimeout = connectTimeout; + return this; + } + + public Builder readTimeout(Duration readTimeout) { + this.readTimeout = readTimeout; + return this; + } + + public ExporterPushgatewayProperties build() { + return new ExporterPushgatewayProperties( + address, job, scheme, escapingScheme, connectTimeout, readTimeout); + } } } diff --git a/prometheus-metrics-config/src/main/java/io/prometheus/metrics/config/MetricsProperties.java b/prometheus-metrics-config/src/main/java/io/prometheus/metrics/config/MetricsProperties.java index 7667fadce..a530f35e1 100644 --- a/prometheus-metrics-config/src/main/java/io/prometheus/metrics/config/MetricsProperties.java +++ b/prometheus-metrics-config/src/main/java/io/prometheus/metrics/config/MetricsProperties.java @@ -5,57 +5,78 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; -import java.util.Map; +import javax.annotation.Nullable; /** Properties starting with io.prometheus.metrics */ public class MetricsProperties { - private static final String EXEMPLARS_ENABLED = "exemplarsEnabled"; - private static final String HISTOGRAM_NATIVE_ONLY = "histogramNativeOnly"; - private static final String HISTOGRAM_CLASSIC_ONLY = "histogramClassicOnly"; - private static final String HISTOGRAM_CLASSIC_UPPER_BOUNDS = "histogramClassicUpperBounds"; - private static final String HISTOGRAM_NATIVE_INITIAL_SCHEMA = "histogramNativeInitialSchema"; + private static final String EXEMPLARS_ENABLED = "exemplars_enabled"; + private static final String HISTOGRAM_NATIVE_ONLY = "histogram_native_only"; + private static final String HISTOGRAM_CLASSIC_ONLY = "histogram_classic_only"; + private static final String HISTOGRAM_CLASSIC_UPPER_BOUNDS = "histogram_classic_upper_bounds"; + private static final String HISTOGRAM_NATIVE_INITIAL_SCHEMA = "histogram_native_initial_schema"; private static final String HISTOGRAM_NATIVE_MIN_ZERO_THRESHOLD = - "histogramNativeMinZeroThreshold"; + "histogram_native_min_zero_threshold"; private static final String HISTOGRAM_NATIVE_MAX_ZERO_THRESHOLD = - "histogramNativeMaxZeroThreshold"; + "histogram_native_max_zero_threshold"; private static final String HISTOGRAM_NATIVE_MAX_NUMBER_OF_BUCKETS = - "histogramNativeMaxNumberOfBuckets"; // 0 means unlimited number of buckets + "histogram_native_max_number_of_buckets"; // 0 means unlimited number of buckets private static final String HISTOGRAM_NATIVE_RESET_DURATION_SECONDS = - "histogramNativeResetDurationSeconds"; // 0 means no reset - private static final String SUMMARY_QUANTILES = "summaryQuantiles"; - private static final String SUMMARY_QUANTILE_ERRORS = "summaryQuantileErrors"; - private static final String SUMMARY_MAX_AGE_SECONDS = "summaryMaxAgeSeconds"; - private static final String SUMMARY_NUMBER_OF_AGE_BUCKETS = "summaryNumberOfAgeBuckets"; - - private final Boolean exemplarsEnabled; - private final Boolean histogramNativeOnly; - private final Boolean histogramClassicOnly; - private final List histogramClassicUpperBounds; - private final Integer histogramNativeInitialSchema; - private final Double histogramNativeMinZeroThreshold; - private final Double histogramNativeMaxZeroThreshold; - private final Integer histogramNativeMaxNumberOfBuckets; - private final Long histogramNativeResetDurationSeconds; - private final List summaryQuantiles; - private final List summaryQuantileErrors; - private final Long summaryMaxAgeSeconds; - private final Integer summaryNumberOfAgeBuckets; + "histogram_native_reset_duration_seconds"; // 0 means no reset + private static final String SUMMARY_QUANTILES = "summary_quantiles"; + private static final String SUMMARY_QUANTILE_ERRORS = "summary_quantile_errors"; + private static final String SUMMARY_MAX_AGE_SECONDS = "summary_max_age_seconds"; + private static final String SUMMARY_NUMBER_OF_AGE_BUCKETS = "summary_number_of_age_buckets"; + + /** + * All known property suffixes that can be configured for metrics. + * + *

This list is used to parse metric-specific configuration keys from environment variables. + */ + static final String[] PROPERTY_SUFFIXES = { + EXEMPLARS_ENABLED, + HISTOGRAM_NATIVE_ONLY, + HISTOGRAM_CLASSIC_ONLY, + HISTOGRAM_CLASSIC_UPPER_BOUNDS, + HISTOGRAM_NATIVE_INITIAL_SCHEMA, + HISTOGRAM_NATIVE_MIN_ZERO_THRESHOLD, + HISTOGRAM_NATIVE_MAX_ZERO_THRESHOLD, + HISTOGRAM_NATIVE_MAX_NUMBER_OF_BUCKETS, + HISTOGRAM_NATIVE_RESET_DURATION_SECONDS, + SUMMARY_QUANTILES, + SUMMARY_QUANTILE_ERRORS, + SUMMARY_MAX_AGE_SECONDS, + SUMMARY_NUMBER_OF_AGE_BUCKETS + }; + + @Nullable private final Boolean exemplarsEnabled; + @Nullable private final Boolean histogramNativeOnly; + @Nullable private final Boolean histogramClassicOnly; + @Nullable private final List histogramClassicUpperBounds; + @Nullable private final Integer histogramNativeInitialSchema; + @Nullable private final Double histogramNativeMinZeroThreshold; + @Nullable private final Double histogramNativeMaxZeroThreshold; + @Nullable private final Integer histogramNativeMaxNumberOfBuckets; + @Nullable private final Long histogramNativeResetDurationSeconds; + @Nullable private final List summaryQuantiles; + @Nullable private final List summaryQuantileErrors; + @Nullable private final Long summaryMaxAgeSeconds; + @Nullable private final Integer summaryNumberOfAgeBuckets; public MetricsProperties( - Boolean exemplarsEnabled, - Boolean histogramNativeOnly, - Boolean histogramClassicOnly, - List histogramClassicUpperBounds, - Integer histogramNativeInitialSchema, - Double histogramNativeMinZeroThreshold, - Double histogramNativeMaxZeroThreshold, - Integer histogramNativeMaxNumberOfBuckets, - Long histogramNativeResetDurationSeconds, - List summaryQuantiles, - List summaryQuantileErrors, - Long summaryMaxAgeSeconds, - Integer summaryNumberOfAgeBuckets) { + @Nullable Boolean exemplarsEnabled, + @Nullable Boolean histogramNativeOnly, + @Nullable Boolean histogramClassicOnly, + @Nullable List histogramClassicUpperBounds, + @Nullable Integer histogramNativeInitialSchema, + @Nullable Double histogramNativeMinZeroThreshold, + @Nullable Double histogramNativeMaxZeroThreshold, + @Nullable Integer histogramNativeMaxNumberOfBuckets, + @Nullable Long histogramNativeResetDurationSeconds, + @Nullable List summaryQuantiles, + @Nullable List summaryQuantileErrors, + @Nullable Long summaryMaxAgeSeconds, + @Nullable Integer summaryNumberOfAgeBuckets) { this( exemplarsEnabled, histogramNativeOnly, @@ -74,19 +95,19 @@ public MetricsProperties( } private MetricsProperties( - Boolean exemplarsEnabled, - Boolean histogramNativeOnly, - Boolean histogramClassicOnly, - List histogramClassicUpperBounds, - Integer histogramNativeInitialSchema, - Double histogramNativeMinZeroThreshold, - Double histogramNativeMaxZeroThreshold, - Integer histogramNativeMaxNumberOfBuckets, - Long histogramNativeResetDurationSeconds, - List summaryQuantiles, - List summaryQuantileErrors, - Long summaryMaxAgeSeconds, - Integer summaryNumberOfAgeBuckets, + @Nullable Boolean exemplarsEnabled, + @Nullable Boolean histogramNativeOnly, + @Nullable Boolean histogramClassicOnly, + @Nullable List histogramClassicUpperBounds, + @Nullable Integer histogramNativeInitialSchema, + @Nullable Double histogramNativeMinZeroThreshold, + @Nullable Double histogramNativeMaxZeroThreshold, + @Nullable Integer histogramNativeMaxNumberOfBuckets, + @Nullable Long histogramNativeResetDurationSeconds, + @Nullable List summaryQuantiles, + @Nullable List summaryQuantileErrors, + @Nullable Long summaryMaxAgeSeconds, + @Nullable Integer summaryNumberOfAgeBuckets, String configPropertyPrefix) { this.exemplarsEnabled = exemplarsEnabled; this.histogramNativeOnly = isHistogramNativeOnly(histogramClassicOnly, histogramNativeOnly); @@ -111,25 +132,28 @@ private MetricsProperties( validate(configPropertyPrefix); } + @Nullable private Boolean isHistogramClassicOnly( - Boolean histogramClassicOnly, Boolean histogramNativeOnly) { - if (histogramClassicOnly == null && histogramNativeOnly == null) { - return null; - } + @Nullable Boolean histogramClassicOnly, @Nullable Boolean histogramNativeOnly) { if (histogramClassicOnly != null) { return histogramClassicOnly; } - return !histogramNativeOnly; + if (histogramNativeOnly != null) { + return !histogramNativeOnly; + } + return null; } - private Boolean isHistogramNativeOnly(Boolean histogramClassicOnly, Boolean histogramNativeOnly) { - if (histogramClassicOnly == null && histogramNativeOnly == null) { - return null; - } + @Nullable + private Boolean isHistogramNativeOnly( + @Nullable Boolean histogramClassicOnly, @Nullable Boolean histogramNativeOnly) { if (histogramNativeOnly != null) { return histogramNativeOnly; } - return !histogramClassicOnly; + if (histogramClassicOnly != null) { + return !histogramClassicOnly; + } + return null; } private void validate(String prefix) throws PrometheusPropertiesException { @@ -222,17 +246,16 @@ private void validate(String prefix) throws PrometheusPropertiesException { + SUMMARY_QUANTILES); } if (summaryQuantileErrors.size() != summaryQuantiles.size()) { + String fullKey = + prefix.isEmpty() ? SUMMARY_QUANTILE_ERRORS : prefix + "." + SUMMARY_QUANTILE_ERRORS; throw new PrometheusPropertiesException( - prefix - + "." - + SUMMARY_QUANTILE_ERRORS - + ": must have the same length as " - + SUMMARY_QUANTILES); + fullKey + ": must have the same length as " + SUMMARY_QUANTILES); } for (double error : summaryQuantileErrors) { if (error < 0 || error > 1) { - throw new PrometheusPropertiesException( - prefix + "." + SUMMARY_QUANTILE_ERRORS + ": Expecting 0.0 <= error <= 1.0"); + String fullKey = + prefix.isEmpty() ? SUMMARY_QUANTILE_ERRORS : prefix + "." + SUMMARY_QUANTILE_ERRORS; + throw new PrometheusPropertiesException(fullKey + ": Expecting 0.0 <= error <= 1.0"); } } } @@ -242,51 +265,61 @@ private void validate(String prefix) throws PrometheusPropertiesException { * This is the only configuration property that can be applied to all metric types. You can use it * to turn Exemplar support off. Default is {@code true}. */ + @Nullable public Boolean getExemplarsEnabled() { return exemplarsEnabled; } /** See {@code Histogram.Builder.nativeOnly()} */ + @Nullable public Boolean getHistogramNativeOnly() { return histogramNativeOnly; } /** See {@code Histogram.Builder.classicOnly()} */ + @Nullable public Boolean getHistogramClassicOnly() { return histogramClassicOnly; } - /** See {@code Histogram.Builder.classicBuckets()} */ + /** See {@code Histogram.Builder.classicUpperBounds()} */ + @Nullable public List getHistogramClassicUpperBounds() { return histogramClassicUpperBounds; } /** See {@code Histogram.Builder.nativeInitialSchema()} */ + @Nullable public Integer getHistogramNativeInitialSchema() { return histogramNativeInitialSchema; } /** See {@code Histogram.Builder.nativeMinZeroThreshold()} */ + @Nullable public Double getHistogramNativeMinZeroThreshold() { return histogramNativeMinZeroThreshold; } /** See {@code Histogram.Builder.nativeMaxZeroThreshold()} */ + @Nullable public Double getHistogramNativeMaxZeroThreshold() { return histogramNativeMaxZeroThreshold; } /** See {@code Histogram.Builder.nativeMaxNumberOfBuckets()} */ + @Nullable public Integer getHistogramNativeMaxNumberOfBuckets() { return histogramNativeMaxNumberOfBuckets; } /** See {@code Histogram.Builder.nativeResetDuration()} */ + @Nullable public Long getHistogramNativeResetDurationSeconds() { return histogramNativeResetDurationSeconds; } /** See {@code Summary.Builder.quantile()} */ + @Nullable public List getSummaryQuantiles() { return summaryQuantiles; } @@ -298,6 +331,7 @@ public List getSummaryQuantiles() { * empty list if {@link #getSummaryQuantiles()} are specified without specifying errors. If the * list is not empty, it has the same size as {@link #getSummaryQuantiles()}. */ + @Nullable public List getSummaryQuantileErrors() { if (summaryQuantiles != null) { if (summaryQuantileErrors == null) { @@ -308,35 +342,37 @@ public List getSummaryQuantileErrors() { } /** See {@code Summary.Builder.maxAgeSeconds()} */ + @Nullable public Long getSummaryMaxAgeSeconds() { return summaryMaxAgeSeconds; } /** See {@code Summary.Builder.numberOfAgeBuckets()} */ + @Nullable public Integer getSummaryNumberOfAgeBuckets() { return summaryNumberOfAgeBuckets; } /** - * Note that this will remove entries from {@code properties}. This is because we want to know if - * there are unused properties remaining after all properties have been loaded. + * Note that this will remove entries from {@code propertySource}. This is because we want to know + * if there are unused properties remaining after all properties have been loaded. */ - static MetricsProperties load(String prefix, Map properties) + static MetricsProperties load(String prefix, PropertySource propertySource) throws PrometheusPropertiesException { return new MetricsProperties( - Util.loadBoolean(prefix + "." + EXEMPLARS_ENABLED, properties), - Util.loadBoolean(prefix + "." + HISTOGRAM_NATIVE_ONLY, properties), - Util.loadBoolean(prefix + "." + HISTOGRAM_CLASSIC_ONLY, properties), - Util.loadDoubleList(prefix + "." + HISTOGRAM_CLASSIC_UPPER_BOUNDS, properties), - Util.loadInteger(prefix + "." + HISTOGRAM_NATIVE_INITIAL_SCHEMA, properties), - Util.loadDouble(prefix + "." + HISTOGRAM_NATIVE_MIN_ZERO_THRESHOLD, properties), - Util.loadDouble(prefix + "." + HISTOGRAM_NATIVE_MAX_ZERO_THRESHOLD, properties), - Util.loadInteger(prefix + "." + HISTOGRAM_NATIVE_MAX_NUMBER_OF_BUCKETS, properties), - Util.loadLong(prefix + "." + HISTOGRAM_NATIVE_RESET_DURATION_SECONDS, properties), - Util.loadDoubleList(prefix + "." + SUMMARY_QUANTILES, properties), - Util.loadDoubleList(prefix + "." + SUMMARY_QUANTILE_ERRORS, properties), - Util.loadLong(prefix + "." + SUMMARY_MAX_AGE_SECONDS, properties), - Util.loadInteger(prefix + "." + SUMMARY_NUMBER_OF_AGE_BUCKETS, properties), + Util.loadBoolean(prefix, EXEMPLARS_ENABLED, propertySource), + Util.loadBoolean(prefix, HISTOGRAM_NATIVE_ONLY, propertySource), + Util.loadBoolean(prefix, HISTOGRAM_CLASSIC_ONLY, propertySource), + Util.loadDoubleList(prefix, HISTOGRAM_CLASSIC_UPPER_BOUNDS, propertySource), + Util.loadInteger(prefix, HISTOGRAM_NATIVE_INITIAL_SCHEMA, propertySource), + Util.loadDouble(prefix, HISTOGRAM_NATIVE_MIN_ZERO_THRESHOLD, propertySource), + Util.loadDouble(prefix, HISTOGRAM_NATIVE_MAX_ZERO_THRESHOLD, propertySource), + Util.loadInteger(prefix, HISTOGRAM_NATIVE_MAX_NUMBER_OF_BUCKETS, propertySource), + Util.loadLong(prefix, HISTOGRAM_NATIVE_RESET_DURATION_SECONDS, propertySource), + Util.loadDoubleList(prefix, SUMMARY_QUANTILES, propertySource), + Util.loadDoubleList(prefix, SUMMARY_QUANTILE_ERRORS, propertySource), + Util.loadLong(prefix, SUMMARY_MAX_AGE_SECONDS, propertySource), + Util.loadInteger(prefix, SUMMARY_NUMBER_OF_AGE_BUCKETS, propertySource), prefix); } @@ -345,19 +381,19 @@ public static Builder builder() { } public static class Builder { - private Boolean exemplarsEnabled; - private Boolean histogramNativeOnly; - private Boolean histogramClassicOnly; - private List histogramClassicUpperBounds; - private Integer histogramNativeInitialSchema; - private Double histogramNativeMinZeroThreshold; - private Double histogramNativeMaxZeroThreshold; - private Integer histogramNativeMaxNumberOfBuckets; - private Long histogramNativeResetDurationSeconds; - private List summaryQuantiles; - private List summaryQuantileErrors; - private Long summaryMaxAgeSeconds; - private Integer summaryNumberOfAgeBuckets; + @Nullable private Boolean exemplarsEnabled; + @Nullable private Boolean histogramNativeOnly; + @Nullable private Boolean histogramClassicOnly; + @Nullable private List histogramClassicUpperBounds; + @Nullable private Integer histogramNativeInitialSchema; + @Nullable private Double histogramNativeMinZeroThreshold; + @Nullable private Double histogramNativeMaxZeroThreshold; + @Nullable private Integer histogramNativeMaxNumberOfBuckets; + @Nullable private Long histogramNativeResetDurationSeconds; + @Nullable private List summaryQuantiles; + @Nullable private List summaryQuantileErrors; + @Nullable private Long summaryMaxAgeSeconds; + @Nullable private Integer summaryNumberOfAgeBuckets; private Builder() {} @@ -379,19 +415,19 @@ public MetricsProperties build() { } /** See {@link MetricsProperties#getExemplarsEnabled()} */ - public Builder exemplarsEnabled(Boolean exemplarsEnabled) { + public Builder exemplarsEnabled(@Nullable Boolean exemplarsEnabled) { this.exemplarsEnabled = exemplarsEnabled; return this; } /** See {@link MetricsProperties#getHistogramNativeOnly()} */ - public Builder histogramNativeOnly(Boolean histogramNativeOnly) { + public Builder histogramNativeOnly(@Nullable Boolean histogramNativeOnly) { this.histogramNativeOnly = histogramNativeOnly; return this; } /** See {@link MetricsProperties#getHistogramClassicOnly()} */ - public Builder histogramClassicOnly(Boolean histogramClassicOnly) { + public Builder histogramClassicOnly(@Nullable Boolean histogramClassicOnly) { this.histogramClassicOnly = histogramClassicOnly; return this; } @@ -403,31 +439,35 @@ public Builder histogramClassicUpperBounds(double... histogramClassicUpperBounds } /** See {@link MetricsProperties#getHistogramNativeInitialSchema()} */ - public Builder histogramNativeInitialSchema(Integer histogramNativeInitialSchema) { + public Builder histogramNativeInitialSchema(@Nullable Integer histogramNativeInitialSchema) { this.histogramNativeInitialSchema = histogramNativeInitialSchema; return this; } /** See {@link MetricsProperties#getHistogramNativeMinZeroThreshold()} */ - public Builder histogramNativeMinZeroThreshold(Double histogramNativeMinZeroThreshold) { + public Builder histogramNativeMinZeroThreshold( + @Nullable Double histogramNativeMinZeroThreshold) { this.histogramNativeMinZeroThreshold = histogramNativeMinZeroThreshold; return this; } /** See {@link MetricsProperties#getHistogramNativeMaxZeroThreshold()} */ - public Builder histogramNativeMaxZeroThreshold(Double histogramNativeMaxZeroThreshold) { + public Builder histogramNativeMaxZeroThreshold( + @Nullable Double histogramNativeMaxZeroThreshold) { this.histogramNativeMaxZeroThreshold = histogramNativeMaxZeroThreshold; return this; } /** See {@link MetricsProperties#getHistogramNativeMaxNumberOfBuckets()} */ - public Builder histogramNativeMaxNumberOfBuckets(Integer histogramNativeMaxNumberOfBuckets) { + public Builder histogramNativeMaxNumberOfBuckets( + @Nullable Integer histogramNativeMaxNumberOfBuckets) { this.histogramNativeMaxNumberOfBuckets = histogramNativeMaxNumberOfBuckets; return this; } /** See {@link MetricsProperties#getHistogramNativeResetDurationSeconds()} */ - public Builder histogramNativeResetDurationSeconds(Long histogramNativeResetDurationSeconds) { + public Builder histogramNativeResetDurationSeconds( + @Nullable Long histogramNativeResetDurationSeconds) { this.histogramNativeResetDurationSeconds = histogramNativeResetDurationSeconds; return this; } @@ -445,13 +485,13 @@ public Builder summaryQuantileErrors(double... summaryQuantileErrors) { } /** See {@link MetricsProperties#getSummaryMaxAgeSeconds()} */ - public Builder summaryMaxAgeSeconds(Long summaryMaxAgeSeconds) { + public Builder summaryMaxAgeSeconds(@Nullable Long summaryMaxAgeSeconds) { this.summaryMaxAgeSeconds = summaryMaxAgeSeconds; return this; } /** See {@link MetricsProperties#getSummaryNumberOfAgeBuckets()} */ - public Builder summaryNumberOfAgeBuckets(Integer summaryNumberOfAgeBuckets) { + public Builder summaryNumberOfAgeBuckets(@Nullable Integer summaryNumberOfAgeBuckets) { this.summaryNumberOfAgeBuckets = summaryNumberOfAgeBuckets; return this; } diff --git a/prometheus-metrics-config/src/main/java/io/prometheus/metrics/config/PrometheusProperties.java b/prometheus-metrics-config/src/main/java/io/prometheus/metrics/config/PrometheusProperties.java index da31fe8cc..055fe4aa3 100644 --- a/prometheus-metrics-config/src/main/java/io/prometheus/metrics/config/PrometheusProperties.java +++ b/prometheus-metrics-config/src/main/java/io/prometheus/metrics/config/PrometheusProperties.java @@ -2,6 +2,7 @@ import java.util.HashMap; import java.util.Map; +import javax.annotation.Nullable; /** * The Prometheus Java client library can be configured at runtime (e.g. using a properties file). @@ -13,7 +14,7 @@ public class PrometheusProperties { private static final PrometheusProperties instance = PrometheusPropertiesLoader.load(); private final MetricsProperties defaultMetricsProperties; - private final Map metricProperties = new HashMap<>(); + private final MetricPropertiesMap metricProperties; private final ExemplarsProperties exemplarProperties; private final ExporterProperties exporterProperties; private final ExporterFilterProperties exporterFilterProperties; @@ -21,6 +22,67 @@ public class PrometheusProperties { private final ExporterOpenTelemetryProperties exporterOpenTelemetryProperties; private final ExporterPushgatewayProperties exporterPushgatewayProperties; + /** + * Map that stores metric-specific properties keyed by metric name in exposition format + * (underscores instead of dots). + * + *

This wrapper makes it explicit that metric names are normalized to underscore format for + * storage, so that environment variables and properties with dots in metric names can be + * correctly looked up using normalized names. + */ + static class MetricPropertiesMap { + private final Map map = new HashMap<>(); + + void set(Map properties) { + map.clear(); + properties.forEach(this::put); + } + + void put(String metricName, MetricsProperties properties) { + map.put(normalize(metricName), properties); + } + + /** + * Get metric properties by metric name. + * + *

Accepts metric names in any format (with dots or underscores) and automatically converts + * them to the normalized underscore format used for storage. + * + * @param metricName the metric name (dots will be converted to underscores) + * @return the metric properties, or null if not configured + */ + @Nullable + MetricsProperties get(String metricName) { + return map.get(normalize(metricName)); + } + + // copied from PrometheusNaming - but we can't reuse that class here because it's in a module + // that + // depends on PrometheusProperties, which would create a circular dependency. + private static String normalize(String name) { + StringBuilder escaped = new StringBuilder(); + + for (int i = 0; i < name.length(); ) { + int c = name.codePointAt(i); + if (isValidLegacyChar(c, i)) { + escaped.appendCodePoint(c); + } else { + escaped.append('_'); + } + i += Character.charCount(c); + } + return escaped.toString(); + } + } + + private static boolean isValidLegacyChar(int c, int i) { + return (c >= 'a' && c <= 'z') + || (c >= 'A' && c <= 'Z') + || c == '_' + || c == ':' + || (c >= '0' && c <= '9' && i > 0); + } + /** * Get the properties instance. When called for the first time, {@code get()} loads the properties * from the following locations: @@ -36,9 +98,14 @@ public static PrometheusProperties get() throws PrometheusPropertiesException { return instance; } - public PrometheusProperties( + public static Builder builder() { + return new Builder(); + } + + // Package-private constructor for PrometheusPropertiesLoader and Builder + PrometheusProperties( MetricsProperties defaultMetricsProperties, - Map metricProperties, + MetricPropertiesMap metricProperties, ExemplarsProperties exemplarProperties, ExporterProperties exporterProperties, ExporterFilterProperties exporterFilterProperties, @@ -46,7 +113,7 @@ public PrometheusProperties( ExporterPushgatewayProperties pushgatewayProperties, ExporterOpenTelemetryProperties otelConfig) { this.defaultMetricsProperties = defaultMetricsProperties; - this.metricProperties.putAll(metricProperties); + this.metricProperties = metricProperties; this.exemplarProperties = exemplarProperties; this.exporterProperties = exporterProperties; this.exporterFilterProperties = exporterFilterProperties; @@ -67,9 +134,13 @@ public MetricsProperties getDefaultMetricProperties() { * Properties specific for one metric. Should be merged with {@link * #getDefaultMetricProperties()}. May return {@code null} if no metric-specific properties are * configured for a metric name. + * + * @param metricName the metric name (dots will be automatically converted to underscores to match + * exposition format) */ + @Nullable public MetricsProperties getMetricProperties(String metricName) { - return metricProperties.get(metricName.replace(".", "_")); + return metricProperties.get(metricName); } public ExemplarsProperties getExemplarProperties() { @@ -95,4 +166,81 @@ public ExporterPushgatewayProperties getExporterPushgatewayProperties() { public ExporterOpenTelemetryProperties getExporterOpenTelemetryProperties() { return exporterOpenTelemetryProperties; } + + public static class Builder { + private MetricsProperties defaultMetricsProperties = MetricsProperties.builder().build(); + private final MetricPropertiesMap metricProperties = new MetricPropertiesMap(); + private ExemplarsProperties exemplarProperties = ExemplarsProperties.builder().build(); + private ExporterProperties exporterProperties = ExporterProperties.builder().build(); + private ExporterFilterProperties exporterFilterProperties = + ExporterFilterProperties.builder().build(); + private ExporterHttpServerProperties exporterHttpServerProperties = + ExporterHttpServerProperties.builder().build(); + private ExporterPushgatewayProperties pushgatewayProperties = + ExporterPushgatewayProperties.builder().build(); + private ExporterOpenTelemetryProperties otelConfig = + ExporterOpenTelemetryProperties.builder().build(); + + private Builder() {} + + public Builder defaultMetricsProperties(MetricsProperties defaultMetricsProperties) { + this.defaultMetricsProperties = defaultMetricsProperties; + return this; + } + + public Builder metricProperties(Map metricProperties) { + this.metricProperties.set(metricProperties); + return this; + } + + /** Convenience for adding a single named MetricsProperties */ + public Builder putMetricProperty(String name, MetricsProperties props) { + this.metricProperties.put(name, props); + return this; + } + + public Builder exemplarProperties(ExemplarsProperties exemplarProperties) { + this.exemplarProperties = exemplarProperties; + return this; + } + + public Builder exporterProperties(ExporterProperties exporterProperties) { + this.exporterProperties = exporterProperties; + return this; + } + + public Builder exporterFilterProperties(ExporterFilterProperties exporterFilterProperties) { + this.exporterFilterProperties = exporterFilterProperties; + return this; + } + + public Builder exporterHttpServerProperties( + ExporterHttpServerProperties exporterHttpServerProperties) { + this.exporterHttpServerProperties = exporterHttpServerProperties; + return this; + } + + public Builder pushgatewayProperties(ExporterPushgatewayProperties pushgatewayProperties) { + this.pushgatewayProperties = pushgatewayProperties; + return this; + } + + public Builder exporterOpenTelemetryProperties( + ExporterOpenTelemetryProperties exporterOpenTelemetryProperties) { + this.otelConfig = exporterOpenTelemetryProperties; + return this; + } + + public PrometheusProperties build() { + return new PrometheusProperties( + defaultMetricsProperties, + metricProperties, + exemplarProperties, + exporterProperties, + exporterFilterProperties, + exporterHttpServerProperties, + pushgatewayProperties, + otelConfig); + } + } } diff --git a/prometheus-metrics-config/src/main/java/io/prometheus/metrics/config/PrometheusPropertiesLoader.java b/prometheus-metrics-config/src/main/java/io/prometheus/metrics/config/PrometheusPropertiesLoader.java index a847a8dba..9119c2f65 100644 --- a/prometheus-metrics-config/src/main/java/io/prometheus/metrics/config/PrometheusPropertiesLoader.java +++ b/prometheus-metrics-config/src/main/java/io/prometheus/metrics/config/PrometheusPropertiesLoader.java @@ -5,12 +5,9 @@ import java.nio.file.Files; import java.nio.file.Paths; import java.util.HashMap; -import java.util.HashSet; import java.util.Map; import java.util.Properties; import java.util.Set; -import java.util.regex.Matcher; -import java.util.regex.Pattern; /** * The Properties Loader is early stages. @@ -29,20 +26,21 @@ public static PrometheusProperties load() throws PrometheusPropertiesException { public static PrometheusProperties load(Map externalProperties) throws PrometheusPropertiesException { - Map properties = loadProperties(externalProperties); - Map metricsConfigs = loadMetricsConfigs(properties); + PropertySource propertySource = loadProperties(externalProperties); + PrometheusProperties.MetricPropertiesMap metricsConfigs = loadMetricsConfigs(propertySource); MetricsProperties defaultMetricsProperties = - MetricsProperties.load("io.prometheus.metrics", properties); - ExemplarsProperties exemplarConfig = ExemplarsProperties.load(properties); - ExporterProperties exporterProperties = ExporterProperties.load(properties); - ExporterFilterProperties exporterFilterProperties = ExporterFilterProperties.load(properties); + MetricsProperties.load("io.prometheus.metrics", propertySource); + ExemplarsProperties exemplarConfig = ExemplarsProperties.load(propertySource); + ExporterProperties exporterProperties = ExporterProperties.load(propertySource); + ExporterFilterProperties exporterFilterProperties = + ExporterFilterProperties.load(propertySource); ExporterHttpServerProperties exporterHttpServerProperties = - ExporterHttpServerProperties.load(properties); + ExporterHttpServerProperties.load(propertySource); ExporterPushgatewayProperties exporterPushgatewayProperties = - ExporterPushgatewayProperties.load(properties); + ExporterPushgatewayProperties.load(propertySource); ExporterOpenTelemetryProperties exporterOpenTelemetryProperties = - ExporterOpenTelemetryProperties.load(properties); - validateAllPropertiesProcessed(properties); + ExporterOpenTelemetryProperties.load(propertySource); + validateAllPropertiesProcessed(propertySource); return new PrometheusProperties( defaultMetricsProperties, metricsConfigs, @@ -54,28 +52,49 @@ public static PrometheusProperties load(Map externalProperties) exporterOpenTelemetryProperties); } - // This will remove entries from properties when they are processed. - private static Map loadMetricsConfigs(Map properties) { - Map result = new HashMap<>(); + // This will remove entries from propertySource when they are processed. + static PrometheusProperties.MetricPropertiesMap loadMetricsConfigs( + PropertySource propertySource) { + PrometheusProperties.MetricPropertiesMap result = + new PrometheusProperties.MetricPropertiesMap(); // Note that the metric name in the properties file must be as exposed in the Prometheus // exposition formats, // i.e. all dots replaced with underscores. - Pattern pattern = Pattern.compile("io\\.prometheus\\.metrics\\.([^.]+)\\."); - // Create a copy of the keySet() for iterating. We cannot iterate directly over keySet() - // because entries are removed when MetricsConfig.load(...) is called. - Set propertyNames = new HashSet<>(); - for (Object key : properties.keySet()) { - propertyNames.add(key.toString()); - } + + // Get a snapshot of all keys for pattern matching. Entries will be removed + // when MetricsProperties.load(...) is called. + Set propertyNames = propertySource.getAllKeys(); for (String propertyName : propertyNames) { - Matcher matcher = pattern.matcher(propertyName); - if (matcher.find()) { - String metricName = matcher.group(1).replace(".", "_"); - if (!result.containsKey(metricName)) { - result.put( - metricName, - MetricsProperties.load("io.prometheus.metrics." + metricName, properties)); + String metricName = null; + + if (propertyName.startsWith("io.prometheus.metrics.")) { + // Dot-separated format (from regular properties, system properties, or files) + String remainder = propertyName.substring("io.prometheus.metrics.".length()); + // Try to match against known property suffixes + for (String suffix : MetricsProperties.PROPERTY_SUFFIXES) { + if (remainder.endsWith("." + suffix)) { + // Metric name in dot format, convert dots to underscores for exposition format + metricName = + remainder.substring(0, remainder.length() - suffix.length() - 1).replace(".", "_"); + break; + } } + } else if (propertyName.startsWith("io_prometheus_metrics_")) { + // Underscore-separated format (from environment variables) + String remainder = propertyName.substring("io_prometheus_metrics_".length()); + // Try to match against known property suffixes + for (String suffix : MetricsProperties.PROPERTY_SUFFIXES) { + if (remainder.endsWith("_" + suffix)) { + metricName = remainder.substring(0, remainder.length() - suffix.length() - 1); + break; + } + } + } + + if (metricName != null && result.get(metricName) == null) { + result.put( + metricName, + MetricsProperties.load("io.prometheus.metrics." + metricName, propertySource)); } } return result; @@ -84,26 +103,43 @@ private static Map loadMetricsConfigs(Map properties) { - for (Object key : properties.keySet()) { - if (key.toString().startsWith("io.prometheus")) { + private static void validateAllPropertiesProcessed(PropertySource propertySource) { + for (String key : propertySource.getRemainingKeys()) { + if (key.startsWith("io.prometheus") || key.startsWith("io_prometheus")) { throw new PrometheusPropertiesException(key + ": Unknown property"); } } } - private static Map loadProperties(Map externalProperties) { - Map properties = new HashMap<>(); - properties.putAll(loadPropertiesFromClasspath()); - properties.putAll(loadPropertiesFromFile()); // overriding the entries from the classpath file + private static PropertySource loadProperties(Map externalProperties) { + // Regular properties (lowest priority): classpath, file, system properties + Map regularProperties = new HashMap<>(); + // Normalize all properties at load time to handle camelCase in files for backward compatibility + normalizeAndPutAll(regularProperties, loadPropertiesFromClasspath()); + normalizeAndPutAll( + regularProperties, + loadPropertiesFromFile()); // overriding the entries from the classpath file // overriding the entries from the properties file // copy System properties to avoid ConcurrentModificationException + // normalize camelCase system properties to snake_case for backward compatibility System.getProperties().stringPropertyNames().stream() .filter(key -> key.startsWith("io.prometheus")) - .forEach(key -> properties.put(key, System.getProperty(key))); - properties.putAll(externalProperties); // overriding all the entries above - // TODO: Add environment variables like EXEMPLARS_ENABLED. - return properties; + .forEach(key -> regularProperties.put(normalizePropertyKey(key), System.getProperty(key))); + + // Environment variables (second priority): just lowercase, keep underscores + Map envVarProperties = loadPropertiesFromEnvironment(); + + // External properties (highest priority): normalize camelCase for backward compatibility + Map normalizedExternalProperties = new HashMap<>(); + externalProperties.forEach( + (key, value) -> + normalizedExternalProperties.put(normalizePropertyKey(key.toString()), value)); + + return new PropertySource(normalizedExternalProperties, envVarProperties, regularProperties); + } + + private static void normalizeAndPutAll(Map target, Map source) { + source.forEach((key, value) -> target.put(normalizePropertyKey(key.toString()), value)); } private static Properties loadPropertiesFromClasspath() { @@ -135,4 +171,48 @@ private static Properties loadPropertiesFromFile() throws PrometheusPropertiesEx } return properties; } + + /** + * Load properties from environment variables. + * + *

Environment variables are converted to lowercase but keep underscores as-is. For example, + * the environment variable IO_PROMETHEUS_METRICS_EXEMPLARS_ENABLED becomes + * io_prometheus_metrics_exemplars_enabled. + * + *

The transformation to dot notation happens at access time in PropertySource. + * + *

Only environment variables starting with IO_PROMETHEUS are considered. + * + * @return properties loaded from environment variables (with lowercase keys and underscores) + */ + private static Map loadPropertiesFromEnvironment() { + Map properties = new HashMap<>(); + System.getenv() + .forEach( + (key, value) -> { + if (key.startsWith("IO_PROMETHEUS")) { + String normalizedKey = key.toLowerCase(java.util.Locale.ROOT); + properties.put(normalizedKey, value); + } + }); + return properties; + } + + /** + * Normalize a property key for consistent lookup. + * + *

Converts camelCase property keys to snake_case. This allows both snake_case (preferred) and + * camelCase (deprecated) property names to be used. + * + *

For example: exemplarsEnabled → exemplars_enabled exemplars_enabled → exemplars_enabled + * (unchanged) + * + * @param key the property key + * @return the normalized property key + */ + static String normalizePropertyKey(String key) { + // Insert underscores before uppercase letters to convert camelCase to snake_case + String withUnderscores = key.replaceAll("([a-z])([A-Z])", "$1_$2"); + return withUnderscores.toLowerCase(java.util.Locale.ROOT); + } } diff --git a/prometheus-metrics-config/src/main/java/io/prometheus/metrics/config/PropertySource.java b/prometheus-metrics-config/src/main/java/io/prometheus/metrics/config/PropertySource.java new file mode 100644 index 000000000..a85212469 --- /dev/null +++ b/prometheus-metrics-config/src/main/java/io/prometheus/metrics/config/PropertySource.java @@ -0,0 +1,165 @@ +package io.prometheus.metrics.config; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import javax.annotation.Nullable; + +/** + * PropertySource encapsulates three separate property maps with different priorities and + * transformation rules. + * + *

Properties are checked in the following order: + * + *

    + *
  1. External properties (highest priority) - exact key match + *
  2. Environment variables (second priority) - with dot-to-underscore transformation + *
  3. Regular properties (lowest priority) - from system properties, files, and classpath + *
+ * + *

Property lookups remove entries from the maps. This allows detecting unused properties at the + * end of configuration loading. + */ +class PropertySource { + + private final Map externalProperties; // Highest priority + private final Map envVarProperties; // Second priority + private final Map regularProperties; // Lowest priority + + /** + * Creates a PropertySource with three separate property maps. + * + * @param externalProperties properties passed explicitly (e.g., from application code) + * @param envVarProperties properties from environment variables (keys in env var format with + * underscores, lowercase) + * @param regularProperties properties from system properties, files, and classpath (keys + * normalized to snake_case) + */ + PropertySource( + Map externalProperties, + Map envVarProperties, + Map regularProperties) { + this.externalProperties = externalProperties; + this.envVarProperties = envVarProperties; + this.regularProperties = regularProperties; + } + + /** + * Creates a PropertySource with only regular properties (for testing). + * + *

Creates empty maps for external and environment variable properties. + * + * @param regularProperties properties from system properties, files, and classpath (keys + * normalized to snake_case) + */ + PropertySource(Map regularProperties) { + this(new HashMap<>(), new HashMap<>(), regularProperties); + } + + /** + * Gets a property value by key, checking all three sources in priority order. + * + *

For external and regular properties, performs exact key match. For environment variables, + * transforms dots to underscores before lookup (e.g., "io.prometheus.metrics.exemplars_enabled" + * becomes "io_prometheus_metrics_exemplars_enabled"). + * + *

Removes the property from ALL source maps to prevent duplicate detection during validation. + * + * @param key the property key to look up + * @return the property value, or null if not found + */ + @Nullable + String getProperty(String key) { + String result = null; + + // Check external properties first (highest priority) + Object value = externalProperties.remove(key); + if (value != null) { + result = value.toString(); + } + + // Check env vars with transformation (second priority) + // Transform dots to underscores: io.prometheus.metrics.exemplars_enabled + // -> io_prometheus_metrics_exemplars_enabled + String envKey = key.replace(".", "_"); + Object envValue = envVarProperties.remove(envKey); + if (result == null && envValue != null) { + result = envValue.toString(); + } + + // Check regular properties last (lowest priority) + Object regularValue = regularProperties.remove(key); + if (result == null && regularValue != null) { + result = regularValue.toString(); + } + + return result; + } + + /** + * Gets a property value by prefix and property name. + * + *

This is a convenience method that concatenates the prefix and property name with a dot and + * calls {@link #getProperty(String)}. + * + * @param prefix the property prefix (e.g., "io.prometheus.metrics"), or empty string for no + * prefix + * @param propertyName the property name (e.g., "exemplars_enabled") + * @return the property value, or null if not found + */ + @Nullable + String getProperty(String prefix, String propertyName) { + String fullKey = prefix.isEmpty() ? propertyName : prefix + "." + propertyName; + return getProperty(fullKey); + } + + /** + * Returns all keys from all three property sources. + * + *

Keys are returned in the format they are stored in each source: external and regular + * properties typically use dot-separated keys, while environment variables are exposed in their + * underscore form (e.g., "io_prometheus_metrics_exemplars_enabled"). + * + *

This is used for pattern matching to find metric-specific configurations. + * + * @return a set of all property keys + */ + Set getAllKeys() { + Set allKeys = new HashSet<>(); + for (Object key : externalProperties.keySet()) { + allKeys.add(key.toString()); + } + // Include env var keys as stored (underscore-separated, lowercase) + for (Object key : envVarProperties.keySet()) { + String envKey = key.toString(); + allKeys.add(envKey); + } + for (Object key : regularProperties.keySet()) { + allKeys.add(key.toString()); + } + return allKeys; + } + + /** + * Returns all remaining keys from all three property sources. + * + *

This is used for validation to detect unused properties that might indicate configuration + * errors. + * + * @return a set of all remaining property keys + */ + Set getRemainingKeys() { + Set remainingKeys = new HashSet<>(); + for (Object key : externalProperties.keySet()) { + remainingKeys.add(key.toString()); + } + for (Object key : envVarProperties.keySet()) { + remainingKeys.add(key.toString()); + } + for (Object key : regularProperties.keySet()) { + remainingKeys.add(key.toString()); + } + return remainingKeys; + } +} diff --git a/prometheus-metrics-config/src/main/java/io/prometheus/metrics/config/Util.java b/prometheus-metrics-config/src/main/java/io/prometheus/metrics/config/Util.java index 7092df068..20bd75699 100644 --- a/prometheus-metrics-config/src/main/java/io/prometheus/metrics/config/Util.java +++ b/prometheus-metrics-config/src/main/java/io/prometheus/metrics/config/Util.java @@ -1,36 +1,38 @@ package io.prometheus.metrics.config; +import java.time.Duration; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.function.Predicate; +import javax.annotation.Nullable; class Util { - static String getProperty(String name, Map properties) { - Object object = properties.remove(name); - if (object != null) { - return object.toString(); - } - return null; + @Nullable + static String getProperty(String prefix, String propertyName, PropertySource propertySource) { + return propertySource.getProperty(prefix, propertyName); } - static Boolean loadBoolean(String name, Map properties) + @Nullable + static Boolean loadBoolean(String prefix, String propertyName, PropertySource propertySource) throws PrometheusPropertiesException { - String property = getProperty(name, properties); + String property = getProperty(prefix, propertyName, propertySource); if (property != null) { + String fullKey = prefix.isEmpty() ? propertyName : prefix + "." + propertyName; if (!"true".equalsIgnoreCase(property) && !"false".equalsIgnoreCase(property)) { throw new PrometheusPropertiesException( - String.format("%s: Expecting 'true' or 'false'. Found: %s", name, property)); + String.format("%s: Expecting 'true' or 'false'. Found: %s", fullKey, property)); } return Boolean.parseBoolean(property); } return null; } - static List toList(double... values) { + @Nullable + static List toList(@Nullable double... values) { if (values == null) { return null; } @@ -41,32 +43,40 @@ static List toList(double... values) { return result; } - static String loadString(String name, Map properties) + @Nullable + static String loadString(String prefix, String propertyName, PropertySource propertySource) throws PrometheusPropertiesException { - return getProperty(name, properties); + return getProperty(prefix, propertyName, propertySource); } - static String loadStringAddSuffix(String name, Map properties, String suffix) { - Object object = properties.remove(name); - if (object != null) { - return object + suffix; + @Nullable + static String loadStringAddSuffix( + String prefix, String propertyName, PropertySource propertySource, String suffix) { + String value = propertySource.getProperty(prefix, propertyName); + if (value != null) { + return value + suffix; } return null; } - static List loadStringList(String name, Map properties) + @Nullable + static List loadStringList( + String prefix, String propertyName, PropertySource propertySource) throws PrometheusPropertiesException { - String property = getProperty(name, properties); + String property = getProperty(prefix, propertyName, propertySource); if (property != null) { return Arrays.asList(property.split("\\s*,\\s*")); } return null; } - static List loadDoubleList(String name, Map properties) + @Nullable + static List loadDoubleList( + String prefix, String propertyName, PropertySource propertySource) throws PrometheusPropertiesException { - String property = getProperty(name, properties); + String property = getProperty(prefix, propertyName, propertySource); if (property != null) { + String fullKey = prefix.isEmpty() ? propertyName : prefix + "." + propertyName; String[] numbers = property.split("\\s*,\\s*"); Double[] result = new Double[numbers.length]; for (int i = 0; i < numbers.length; i++) { @@ -78,7 +88,7 @@ static List loadDoubleList(String name, Map properties) } } catch (NumberFormatException e) { throw new PrometheusPropertiesException( - name + "=" + property + ": Expecting comma separated list of double values"); + fullKey + "=" + property + ": Expecting comma separated list of double values"); } } return Arrays.asList(result); @@ -87,10 +97,11 @@ static List loadDoubleList(String name, Map properties) } // Map is represented as "key1=value1,key2=value2" - static Map loadMap(String name, Map properties) + static Map loadMap( + String prefix, String propertyName, PropertySource propertySource) throws PrometheusPropertiesException { Map result = new HashMap<>(); - String property = getProperty(name, properties); + String property = getProperty(prefix, propertyName, propertySource); if (property != null) { String[] pairs = property.split(","); for (String pair : pairs) { @@ -109,54 +120,80 @@ static Map loadMap(String name, Map properties) return result; } - static Integer loadInteger(String name, Map properties) + @Nullable + static Integer loadInteger(String prefix, String propertyName, PropertySource propertySource) throws PrometheusPropertiesException { - String property = getProperty(name, properties); + String property = getProperty(prefix, propertyName, propertySource); if (property != null) { + String fullKey = prefix.isEmpty() ? propertyName : prefix + "." + propertyName; try { return Integer.parseInt(property); } catch (NumberFormatException e) { throw new PrometheusPropertiesException( - name + "=" + property + ": Expecting integer value"); + fullKey + "=" + property + ": Expecting integer value"); } } return null; } - static Double loadDouble(String name, Map properties) + @Nullable + static Double loadDouble(String prefix, String propertyName, PropertySource propertySource) throws PrometheusPropertiesException { - String property = getProperty(name, properties); + String property = getProperty(prefix, propertyName, propertySource); if (property != null) { + String fullKey = prefix.isEmpty() ? propertyName : prefix + "." + propertyName; try { return Double.parseDouble(property); } catch (NumberFormatException e) { - throw new PrometheusPropertiesException(name + "=" + property + ": Expecting double value"); + throw new PrometheusPropertiesException( + fullKey + "=" + property + ": Expecting double value"); } } return null; } - static Long loadLong(String name, Map properties) + @Nullable + static Long loadLong(String prefix, String propertyName, PropertySource propertySource) throws PrometheusPropertiesException { - String property = getProperty(name, properties); + String property = getProperty(prefix, propertyName, propertySource); if (property != null) { + String fullKey = prefix.isEmpty() ? propertyName : prefix + "." + propertyName; try { return Long.parseLong(property); } catch (NumberFormatException e) { - throw new PrometheusPropertiesException(name + "=" + property + ": Expecting long value"); + throw new PrometheusPropertiesException( + fullKey + "=" + property + ": Expecting long value"); } } return null; } + @Nullable + static Duration loadOptionalDuration( + String prefix, String propertyName, PropertySource propertySource) + throws PrometheusPropertiesException { + + Long value = loadLong(prefix, propertyName, propertySource); + + assertValue(value, t -> t >= 0, "Expecting value >= 0.", prefix, propertyName); + + if (value == null || value == 0) { + return null; + } + return Duration.ofSeconds(value); + } + static void assertValue( - T number, Predicate predicate, String message, String prefix, String name) + @Nullable T number, + Predicate predicate, + String message, + @Nullable String prefix, + String propertyName) throws PrometheusPropertiesException { if (number != null && !predicate.test(number)) { - String fullMessage = - prefix == null - ? name + ": " + message - : String.format("%s.%s: %s Found: %s", prefix, name, message, number); + String fullKey = + (prefix == null || prefix.isEmpty()) ? propertyName : prefix + "." + propertyName; + String fullMessage = String.format("%s: %s Found: %s", fullKey, message, number); throw new PrometheusPropertiesException(fullMessage); } } diff --git a/prometheus-metrics-config/src/test/java/io/prometheus/metrics/config/EscapingSchemeTest.java b/prometheus-metrics-config/src/test/java/io/prometheus/metrics/config/EscapingSchemeTest.java new file mode 100644 index 000000000..329644bf5 --- /dev/null +++ b/prometheus-metrics-config/src/test/java/io/prometheus/metrics/config/EscapingSchemeTest.java @@ -0,0 +1,35 @@ +package io.prometheus.metrics.config; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatCode; + +import org.junit.jupiter.api.Test; + +class EscapingSchemeTest { + + @Test + void forString() { + assertThat(EscapingScheme.forString("allow-utf-8")).isEqualTo(EscapingScheme.ALLOW_UTF8); + assertThat(EscapingScheme.forString("underscores")) + .isEqualTo(EscapingScheme.UNDERSCORE_ESCAPING); + assertThat(EscapingScheme.forString("dots")).isEqualTo(EscapingScheme.DOTS_ESCAPING); + assertThat(EscapingScheme.forString("values")) + .isEqualTo(EscapingScheme.VALUE_ENCODING_ESCAPING); + assertThatCode(() -> EscapingScheme.forString("unknown")) + .isInstanceOf(IllegalArgumentException.class); + } + + @Test + void fromAcceptHeader() { + assertThat(EscapingScheme.fromAcceptHeader("application/json; escaping=allow-utf-8")) + .isEqualTo(EscapingScheme.ALLOW_UTF8); + assertThat(EscapingScheme.fromAcceptHeader("application/json; escaping=underscores")) + .isEqualTo(EscapingScheme.UNDERSCORE_ESCAPING); + assertThat(EscapingScheme.fromAcceptHeader("application/json; escaping=dots")) + .isEqualTo(EscapingScheme.DOTS_ESCAPING); + assertThat(EscapingScheme.fromAcceptHeader("application/json; escaping=values")) + .isEqualTo(EscapingScheme.VALUE_ENCODING_ESCAPING); + assertThat(EscapingScheme.fromAcceptHeader("application/json")) + .isEqualTo(EscapingScheme.DEFAULT); + } +} diff --git a/prometheus-metrics-config/src/test/java/io/prometheus/metrics/config/ExemplarsPropertiesTest.java b/prometheus-metrics-config/src/test/java/io/prometheus/metrics/config/ExemplarsPropertiesTest.java index 53f7ae072..aa87ed105 100644 --- a/prometheus-metrics-config/src/test/java/io/prometheus/metrics/config/ExemplarsPropertiesTest.java +++ b/prometheus-metrics-config/src/test/java/io/prometheus/metrics/config/ExemplarsPropertiesTest.java @@ -14,31 +14,35 @@ void load() { ExemplarsProperties properties = load( Map.of( - "io.prometheus.exemplars.minRetentionPeriodSeconds", "1", - "io.prometheus.exemplars.maxRetentionPeriodSeconds", "2", - "io.prometheus.exemplars.sampleIntervalMilliseconds", "3")); + "io.prometheus.exemplars.min_retention_period_seconds", "1", + "io.prometheus.exemplars.max_retention_period_seconds", "2", + "io.prometheus.exemplars.sample_interval_milliseconds", "3")); assertThat(properties.getMinRetentionPeriodSeconds()).isOne(); assertThat(properties.getMaxRetentionPeriodSeconds()).isEqualTo(2); assertThat(properties.getSampleIntervalMilliseconds()).isEqualTo(3); assertThatExceptionOfType(PrometheusPropertiesException.class) - .isThrownBy(() -> load(Map.of("io.prometheus.exemplars.minRetentionPeriodSeconds", "-1"))) + .isThrownBy( + () -> load(Map.of("io.prometheus.exemplars.min_retention_period_seconds", "-1"))) .withMessage( - "io.prometheus.exemplars.minRetentionPeriodSeconds: Expecting value > 0. Found: -1"); + "io.prometheus.exemplars.min_retention_period_seconds: Expecting value > 0. Found: -1"); assertThatExceptionOfType(PrometheusPropertiesException.class) - .isThrownBy(() -> load(Map.of("io.prometheus.exemplars.maxRetentionPeriodSeconds", "0"))) + .isThrownBy(() -> load(Map.of("io.prometheus.exemplars.max_retention_period_seconds", "0"))) .withMessage( - "io.prometheus.exemplars.maxRetentionPeriodSeconds: Expecting value > 0. Found: 0"); + "io.prometheus.exemplars.max_retention_period_seconds: Expecting value > 0. Found: 0"); assertThatExceptionOfType(PrometheusPropertiesException.class) - .isThrownBy(() -> load(Map.of("io.prometheus.exemplars.sampleIntervalMilliseconds", "-1"))) + .isThrownBy( + () -> load(Map.of("io.prometheus.exemplars.sample_interval_milliseconds", "-1"))) .withMessage( - "io.prometheus.exemplars.sampleIntervalMilliseconds: Expecting value > 0. Found: -1"); + "io.prometheus.exemplars.sample_interval_milliseconds: Expecting value > 0. Found: -1"); } private static ExemplarsProperties load(Map map) { - return ExemplarsProperties.load(new HashMap<>(map)); + Map regularProperties = new HashMap<>(map); + PropertySource propertySource = new PropertySource(regularProperties); + return ExemplarsProperties.load(propertySource); } @Test diff --git a/prometheus-metrics-config/src/test/java/io/prometheus/metrics/config/ExporterFilterPropertiesTest.java b/prometheus-metrics-config/src/test/java/io/prometheus/metrics/config/ExporterFilterPropertiesTest.java index 0b30fbd43..c7b9124af 100644 --- a/prometheus-metrics-config/src/test/java/io/prometheus/metrics/config/ExporterFilterPropertiesTest.java +++ b/prometheus-metrics-config/src/test/java/io/prometheus/metrics/config/ExporterFilterPropertiesTest.java @@ -13,10 +13,10 @@ void load() { ExporterFilterProperties properties = load( Map.of( - "io.prometheus.exporter.filter.metricNameMustBeEqualTo", "a,b,c", - "io.prometheus.exporter.filter.metricNameMustNotBeEqualTo", "d,e,f", - "io.prometheus.exporter.filter.metricNameMustStartWith", "g,h,i", - "io.prometheus.exporter.filter.metricNameMustNotStartWith", "j,k,l")); + "io.prometheus.exporter.filter.metric_name_must_be_equal_to", "a,b,c", + "io.prometheus.exporter.filter.metric_name_must_not_be_equal_to", "d,e,f", + "io.prometheus.exporter.filter.metric_name_must_start_with", "g,h,i", + "io.prometheus.exporter.filter.metric_name_must_not_start_with", "j,k,l")); assertThat(properties.getAllowedMetricNames()).containsExactly("a", "b", "c"); assertThat(properties.getExcludedMetricNames()).containsExactly("d", "e", "f"); assertThat(properties.getAllowedMetricNamePrefixes()).containsExactly("g", "h", "i"); @@ -24,7 +24,9 @@ void load() { } private static ExporterFilterProperties load(Map map) { - return ExporterFilterProperties.load(new HashMap<>(map)); + Map regularProperties = new HashMap<>(map); + PropertySource propertySource = new PropertySource(regularProperties); + return ExporterFilterProperties.load(propertySource); } @Test diff --git a/prometheus-metrics-config/src/test/java/io/prometheus/metrics/config/ExporterHttpServerPropertiesTest.java b/prometheus-metrics-config/src/test/java/io/prometheus/metrics/config/ExporterHttpServerPropertiesTest.java index 07b00a2a4..fc4faf141 100644 --- a/prometheus-metrics-config/src/test/java/io/prometheus/metrics/config/ExporterHttpServerPropertiesTest.java +++ b/prometheus-metrics-config/src/test/java/io/prometheus/metrics/config/ExporterHttpServerPropertiesTest.java @@ -2,6 +2,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; +import static org.assertj.core.api.SoftAssertions.assertSoftly; import java.util.HashMap; import java.util.Map; @@ -11,20 +12,30 @@ class ExporterHttpServerPropertiesTest { @Test void load() { ExporterHttpServerProperties properties = - load(Map.of("io.prometheus.exporter.httpServer.port", "1")); + load(Map.of("io.prometheus.exporter.http_server.port", "1")); assertThat(properties.getPort()).isOne(); + assertThat(properties.isPreferUncompressedResponse()).isFalse(); assertThatExceptionOfType(PrometheusPropertiesException.class) - .isThrownBy(() -> load(Map.of("io.prometheus.exporter.httpServer.port", "0"))) - .withMessage("io.prometheus.exporter.httpServer.port: Expecting value > 0. Found: 0"); + .isThrownBy(() -> load(Map.of("io.prometheus.exporter.http_server.port", "0"))) + .withMessage("io.prometheus.exporter.http_server.port: Expecting value > 0. Found: 0"); } @Test void builder() { - assertThat(ExporterHttpServerProperties.builder().port(1).build().getPort()).isOne(); + ExporterHttpServerProperties properties = + ExporterHttpServerProperties.builder().port(1).build(); + + assertSoftly( + softly -> { + softly.assertThat(properties.getPort()).isOne(); + softly.assertThat(properties.isPreferUncompressedResponse()).isFalse(); + }); } private static ExporterHttpServerProperties load(Map map) { - return ExporterHttpServerProperties.load(new HashMap<>(map)); + Map regularProperties = new HashMap<>(map); + PropertySource propertySource = new PropertySource(regularProperties); + return ExporterHttpServerProperties.load(propertySource); } } diff --git a/prometheus-metrics-config/src/test/java/io/prometheus/metrics/config/ExporterOpenTelemetryPropertiesTest.java b/prometheus-metrics-config/src/test/java/io/prometheus/metrics/config/ExporterOpenTelemetryPropertiesTest.java index 7ba275570..003fba0e6 100644 --- a/prometheus-metrics-config/src/test/java/io/prometheus/metrics/config/ExporterOpenTelemetryPropertiesTest.java +++ b/prometheus-metrics-config/src/test/java/io/prometheus/metrics/config/ExporterOpenTelemetryPropertiesTest.java @@ -1,6 +1,7 @@ package io.prometheus.metrics.config; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import java.util.HashMap; import java.util.Map; @@ -16,13 +17,13 @@ void load() { "io.prometheus.exporter.opentelemetry.protocol", "grpc", "io.prometheus.exporter.opentelemetry.endpoint", "http://localhost:8080", "io.prometheus.exporter.opentelemetry.headers", "key1=value1,key2=value2", - "io.prometheus.exporter.opentelemetry.intervalSeconds", "10", - "io.prometheus.exporter.opentelemetry.timeoutSeconds", "5", - "io.prometheus.exporter.opentelemetry.serviceName", "serviceName", - "io.prometheus.exporter.opentelemetry.serviceNamespace", "serviceNamespace", - "io.prometheus.exporter.opentelemetry.serviceInstanceId", "serviceInstanceId", - "io.prometheus.exporter.opentelemetry.serviceVersion", "serviceVersion", - "io.prometheus.exporter.opentelemetry.resourceAttributes", + "io.prometheus.exporter.opentelemetry.interval_seconds", "10", + "io.prometheus.exporter.opentelemetry.timeout_seconds", "5", + "io.prometheus.exporter.opentelemetry.service_name", "serviceName", + "io.prometheus.exporter.opentelemetry.service_namespace", "serviceNamespace", + "io.prometheus.exporter.opentelemetry.service_instance_id", "serviceInstanceId", + "io.prometheus.exporter.opentelemetry.service_version", "serviceVersion", + "io.prometheus.exporter.opentelemetry.resource_attributes", "key1=value1,key2=value2")); assertValues(properties); @@ -44,7 +45,9 @@ private static void assertValues(ExporterOpenTelemetryProperties properties) { } private static ExporterOpenTelemetryProperties load(Map map) { - return ExporterOpenTelemetryProperties.load(new HashMap<>(map)); + Map regularProperties = new HashMap<>(map); + PropertySource propertySource = new PropertySource(regularProperties); + return ExporterOpenTelemetryProperties.load(propertySource); } @Test @@ -66,4 +69,40 @@ void builder() { .build(); assertValues(properties); } + + @Test + void builderWithHttpProtobuf() { + ExporterOpenTelemetryProperties properties = + ExporterOpenTelemetryProperties.builder().protocol("http/protobuf").build(); + assertThat(properties.getProtocol()).isEqualTo("http/protobuf"); + } + + @Test + void builderWithInvalidProtocol() { + assertThatExceptionOfType(IllegalArgumentException.class) + .isThrownBy(() -> ExporterOpenTelemetryProperties.builder().protocol("invalid")) + .withMessage("invalid: Unsupported protocol. Expecting grpc or http/protobuf"); + } + + @Test + void builderWithInvalidIntervalSeconds() { + assertThatExceptionOfType(IllegalArgumentException.class) + .isThrownBy(() -> ExporterOpenTelemetryProperties.builder().intervalSeconds(0)) + .withMessage("0: Expecting intervalSeconds > 0"); + + assertThatExceptionOfType(IllegalArgumentException.class) + .isThrownBy(() -> ExporterOpenTelemetryProperties.builder().intervalSeconds(-1)) + .withMessage("-1: Expecting intervalSeconds > 0"); + } + + @Test + void builderWithInvalidTimeoutSeconds() { + assertThatExceptionOfType(IllegalArgumentException.class) + .isThrownBy(() -> ExporterOpenTelemetryProperties.builder().timeoutSeconds(0)) + .withMessage("0: Expecting timeoutSeconds > 0"); + + assertThatExceptionOfType(IllegalArgumentException.class) + .isThrownBy(() -> ExporterOpenTelemetryProperties.builder().timeoutSeconds(-1)) + .withMessage("-1: Expecting timeoutSeconds > 0"); + } } diff --git a/prometheus-metrics-config/src/test/java/io/prometheus/metrics/config/ExporterPropertiesTest.java b/prometheus-metrics-config/src/test/java/io/prometheus/metrics/config/ExporterPropertiesTest.java index dec9e03a5..6cde17648 100644 --- a/prometheus-metrics-config/src/test/java/io/prometheus/metrics/config/ExporterPropertiesTest.java +++ b/prometheus-metrics-config/src/test/java/io/prometheus/metrics/config/ExporterPropertiesTest.java @@ -15,8 +15,8 @@ void load() { load( new HashMap<>( Map.of( - "io.prometheus.exporter.includeCreatedTimestamps", "true", - "io.prometheus.exporter.exemplarsOnAllMetricTypes", "true"))); + "io.prometheus.exporter.include_created_timestamps", "true", + "io.prometheus.exporter.exemplars_on_all_metric_types", "true"))); assertThat(properties.getIncludeCreatedTimestamps()).isTrue(); assertThat(properties.getExemplarsOnAllMetricTypes()).isTrue(); @@ -25,23 +25,25 @@ void load() { () -> load( new HashMap<>( - Map.of("io.prometheus.exporter.includeCreatedTimestamps", "invalid")))) + Map.of("io.prometheus.exporter.include_created_timestamps", "invalid")))) .withMessage( - "io.prometheus.exporter.includeCreatedTimestamps: Expecting 'true' or 'false'. Found:" + "io.prometheus.exporter.include_created_timestamps: Expecting 'true' or 'false'. Found:" + " invalid"); assertThatExceptionOfType(PrometheusPropertiesException.class) .isThrownBy( () -> load( new HashMap<>( - Map.of("io.prometheus.exporter.exemplarsOnAllMetricTypes", "invalid")))) + Map.of("io.prometheus.exporter.exemplars_on_all_metric_types", "invalid")))) .withMessage( - "io.prometheus.exporter.exemplarsOnAllMetricTypes: Expecting 'true' or 'false'. Found:" + "io.prometheus.exporter.exemplars_on_all_metric_types: Expecting 'true' or 'false'. Found:" + " invalid"); } private static ExporterProperties load(Map map) { - return ExporterProperties.load(new HashMap<>(map)); + Map regularProperties = new HashMap<>(map); + PropertySource propertySource = new PropertySource(regularProperties); + return ExporterProperties.load(propertySource); } @Test @@ -54,5 +56,35 @@ void builder() { .build(); assertThat(properties.getIncludeCreatedTimestamps()).isTrue(); assertThat(properties.getExemplarsOnAllMetricTypes()).isTrue(); + assertThat(properties.getPrometheusTimestampsInMs()).isFalse(); + } + + @Test + void defaultValues() { + ExporterProperties properties = ExporterProperties.builder().build(); + assertThat(properties.getIncludeCreatedTimestamps()).isFalse(); + assertThat(properties.getExemplarsOnAllMetricTypes()).isFalse(); + assertThat(properties.getPrometheusTimestampsInMs()).isFalse(); + } + + @Test + void prometheusTimestampsInMs() { + ExporterProperties properties = + ExporterProperties.builder().prometheusTimestampsInMs(true).build(); + assertThat(properties.getPrometheusTimestampsInMs()).isTrue(); + + properties = + load(new HashMap<>(Map.of("io.prometheus.exporter.prometheus_timestamps_in_ms", "true"))); + assertThat(properties.getPrometheusTimestampsInMs()).isTrue(); + + assertThatExceptionOfType(PrometheusPropertiesException.class) + .isThrownBy( + () -> + load( + new HashMap<>( + Map.of("io.prometheus.exporter.prometheus_timestamps_in_ms", "invalid")))) + .withMessage( + "io.prometheus.exporter.prometheus_timestamps_in_ms: Expecting 'true' or 'false'. Found:" + + " invalid"); } } diff --git a/prometheus-metrics-config/src/test/java/io/prometheus/metrics/config/ExporterPushgatewayPropertiesTest.java b/prometheus-metrics-config/src/test/java/io/prometheus/metrics/config/ExporterPushgatewayPropertiesTest.java index 01f9a8a84..c92e6f2f9 100644 --- a/prometheus-metrics-config/src/test/java/io/prometheus/metrics/config/ExporterPushgatewayPropertiesTest.java +++ b/prometheus-metrics-config/src/test/java/io/prometheus/metrics/config/ExporterPushgatewayPropertiesTest.java @@ -3,6 +3,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; +import java.time.Duration; import java.util.HashMap; import java.util.Map; import org.junit.jupiter.api.Test; @@ -29,7 +30,73 @@ void load() { + " Found: foo"); } + @Test + void loadWithHttps() { + ExporterPushgatewayProperties properties = + load(Map.of("io.prometheus.exporter.pushgateway.scheme", "https")); + assertThat(properties.getScheme()).isEqualTo("https"); + } + + @Test + void loadWithEscapingSchemes() { + ExporterPushgatewayProperties properties = + load(Map.of("io.prometheus.exporter.pushgateway.escaping_scheme", "allow-utf-8")); + assertThat(properties.getEscapingScheme()).isEqualTo(EscapingScheme.ALLOW_UTF8); + + properties = load(Map.of("io.prometheus.exporter.pushgateway.escaping_scheme", "values")); + assertThat(properties.getEscapingScheme()).isEqualTo(EscapingScheme.VALUE_ENCODING_ESCAPING); + + properties = load(Map.of("io.prometheus.exporter.pushgateway.escaping_scheme", "underscores")); + assertThat(properties.getEscapingScheme()).isEqualTo(EscapingScheme.UNDERSCORE_ESCAPING); + + properties = load(Map.of("io.prometheus.exporter.pushgateway.escaping_scheme", "dots")); + assertThat(properties.getEscapingScheme()).isEqualTo(EscapingScheme.DOTS_ESCAPING); + } + + @Test + void loadWithInvalidEscapingScheme() { + assertThatExceptionOfType(PrometheusPropertiesException.class) + .isThrownBy( + () -> load(Map.of("io.prometheus.exporter.pushgateway.escaping_scheme", "invalid"))) + .withMessage( + "io.prometheus.exporter.pushgateway.escaping_scheme: Illegal value. Expecting" + + " 'allow-utf-8', 'values', 'underscores', or 'dots'. Found: invalid"); + } + + @Test + void loadWithTimeouts() { + ExporterPushgatewayProperties properties = + load( + Map.of( + "io.prometheus.exporter.pushgateway.connect_timeout_seconds", "5", + "io.prometheus.exporter.pushgateway.read_timeout_seconds", "10")); + assertThat(properties.getConnectTimeout()).isEqualTo(Duration.ofSeconds(5)); + assertThat(properties.getReadTimeout()).isEqualTo(Duration.ofSeconds(10)); + } + private static ExporterPushgatewayProperties load(Map map) { - return ExporterPushgatewayProperties.load(new HashMap<>(map)); + Map regularProperties = new HashMap<>(map); + PropertySource propertySource = new PropertySource(regularProperties); + return ExporterPushgatewayProperties.load(propertySource); + } + + @Test + void builder() { + ExporterPushgatewayProperties properties = + ExporterPushgatewayProperties.builder() + .address("http://localhost") + .job("job") + .scheme("http") + .escapingScheme(EscapingScheme.DOTS_ESCAPING) + .connectTimeout(Duration.ofSeconds(1)) + .readTimeout(Duration.ofSeconds(2)) + .build(); + + assertThat(properties.getAddress()).isEqualTo("http://localhost"); + assertThat(properties.getJob()).isEqualTo("job"); + assertThat(properties.getScheme()).isEqualTo("http"); + assertThat(properties.getEscapingScheme()).isEqualTo(EscapingScheme.DOTS_ESCAPING); + assertThat(properties.getConnectTimeout()).isEqualTo(Duration.ofSeconds(1)); + assertThat(properties.getReadTimeout()).isEqualTo(Duration.ofSeconds(2)); } } diff --git a/prometheus-metrics-config/src/test/java/io/prometheus/metrics/config/MetricsPropertiesTest.java b/prometheus-metrics-config/src/test/java/io/prometheus/metrics/config/MetricsPropertiesTest.java index 4f4c56755..9f7684a8d 100644 --- a/prometheus-metrics-config/src/test/java/io/prometheus/metrics/config/MetricsPropertiesTest.java +++ b/prometheus-metrics-config/src/test/java/io/prometheus/metrics/config/MetricsPropertiesTest.java @@ -47,17 +47,17 @@ void builder() { assertThatExceptionOfType(PrometheusPropertiesException.class) .isThrownBy(() -> MetricsProperties.builder().summaryNumberOfAgeBuckets(0).build()) - .withMessage(".summaryNumberOfAgeBuckets: Expecting value > 0. Found: 0"); + .withMessage("summary_number_of_age_buckets: Expecting value > 0. Found: 0"); assertThatExceptionOfType(PrometheusPropertiesException.class) .isThrownBy(() -> MetricsProperties.builder().summaryQuantiles(2L).build()) - .withMessage(".summaryQuantiles: Expecting 0.0 <= quantile <= 1.0. Found: 2.0"); + .withMessage(".summary_quantiles: Expecting 0.0 <= quantile <= 1.0. Found: 2.0"); assertThatExceptionOfType(PrometheusPropertiesException.class) .isThrownBy(() -> MetricsProperties.builder().summaryQuantileErrors(0.9).build()) .withMessage( - ".summaryQuantileErrors: Can't configure summaryQuantileErrors without configuring" - + " summaryQuantiles"); + ".summary_quantile_errors: Can't configure summary_quantile_errors without configuring" + + " summary_quantiles"); assertThatExceptionOfType(PrometheusPropertiesException.class) .isThrownBy( @@ -66,7 +66,7 @@ void builder() { .summaryQuantiles(0.1) .summaryQuantileErrors(0.1, 0.9) .build()) - .withMessage(".summaryQuantileErrors: must have the same length as summaryQuantiles"); + .withMessage("summary_quantile_errors: must have the same length as summary_quantiles"); assertThatExceptionOfType(PrometheusPropertiesException.class) .isThrownBy( @@ -75,7 +75,7 @@ void builder() { .summaryQuantiles(0.1) .summaryQuantileErrors(-0.9) .build()) - .withMessage(".summaryQuantileErrors: Expecting 0.0 <= error <= 1.0"); + .withMessage("summary_quantile_errors: Expecting 0.0 <= error <= 1.0"); } @Test @@ -114,24 +114,24 @@ void nativeBuilder() { assertThatExceptionOfType(PrometheusPropertiesException.class) .isThrownBy(() -> MetricsProperties.builder().histogramNativeInitialSchema(10).build()) .withMessage( - ".histogramNativeInitialSchema: Expecting number between -4 and +8. Found: 10"); + "histogram_native_initial_schema: Expecting number between -4 and +8. Found: 10"); assertThatExceptionOfType(PrometheusPropertiesException.class) .isThrownBy(() -> MetricsProperties.builder().histogramNativeMinZeroThreshold(-1.0).build()) - .withMessage(".histogramNativeMinZeroThreshold: Expecting value >= 0. Found: -1.0"); + .withMessage("histogram_native_min_zero_threshold: Expecting value >= 0. Found: -1.0"); assertThatExceptionOfType(PrometheusPropertiesException.class) .isThrownBy(() -> MetricsProperties.builder().histogramNativeMaxZeroThreshold(-1.0).build()) - .withMessage(".histogramNativeMaxZeroThreshold: Expecting value >= 0. Found: -1.0"); + .withMessage("histogram_native_max_zero_threshold: Expecting value >= 0. Found: -1.0"); assertThatExceptionOfType(PrometheusPropertiesException.class) .isThrownBy(() -> MetricsProperties.builder().histogramNativeMaxNumberOfBuckets(-1).build()) - .withMessage(".histogramNativeMaxNumberOfBuckets: Expecting value >= 0. Found: -1"); + .withMessage("histogram_native_max_number_of_buckets: Expecting value >= 0. Found: -1"); assertThatExceptionOfType(PrometheusPropertiesException.class) .isThrownBy( () -> MetricsProperties.builder().histogramNativeResetDurationSeconds(-1L).build()) - .withMessage(".histogramNativeResetDurationSeconds: Expecting value >= 0. Found: -1"); + .withMessage("histogram_native_reset_duration_seconds: Expecting value >= 0. Found: -1"); assertThatExceptionOfType(PrometheusPropertiesException.class) .isThrownBy( @@ -140,7 +140,7 @@ void nativeBuilder() { .histogramNativeOnly(true) .histogramClassicOnly(true) .build()) - .withMessage(".histogramNativeOnly and .histogramClassicOnly cannot both be true"); + .withMessage(".histogram_native_only and .histogram_classic_only cannot both be true"); assertThatExceptionOfType(PrometheusPropertiesException.class) .isThrownBy( @@ -150,7 +150,7 @@ void nativeBuilder() { .histogramNativeMaxZeroThreshold(0.01) .build()) .withMessage( - ".histogramNativeMinZeroThreshold cannot be greater than" - + " .histogramNativeMaxZeroThreshold"); + ".histogram_native_min_zero_threshold cannot be greater than" + + " .histogram_native_max_zero_threshold"); } } diff --git a/prometheus-metrics-config/src/test/java/io/prometheus/metrics/config/PrometheusPropertiesLoaderTest.java b/prometheus-metrics-config/src/test/java/io/prometheus/metrics/config/PrometheusPropertiesLoaderTest.java index cf4933acf..532b00295 100644 --- a/prometheus-metrics-config/src/test/java/io/prometheus/metrics/config/PrometheusPropertiesLoaderTest.java +++ b/prometheus-metrics-config/src/test/java/io/prometheus/metrics/config/PrometheusPropertiesLoaderTest.java @@ -3,6 +3,8 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; +import java.util.HashMap; +import java.util.Map; import java.util.Properties; import org.junit.jupiter.api.Test; import org.junitpioneer.jupiter.SetSystemProperty; @@ -11,7 +13,7 @@ class PrometheusPropertiesLoaderTest { @Test - public void propertiesShouldBeLoadedFromPropertiesFile() { + void propertiesShouldBeLoadedFromPropertiesFile() { PrometheusProperties prometheusProperties = PrometheusPropertiesLoader.load(); assertThat(prometheusProperties.getDefaultMetricProperties().getHistogramClassicUpperBounds()) .hasSize(11); @@ -28,23 +30,20 @@ public void propertiesShouldBeLoadedFromPropertiesFile() { @SetSystemProperty(key = "prometheus.config", value = "nonexistent.properties") void cantLoadPropertiesFile() { assertThatExceptionOfType(PrometheusPropertiesException.class) - .isThrownBy( - () -> { - PrometheusPropertiesLoader.load(new Properties()); - }) + .isThrownBy(() -> PrometheusPropertiesLoader.load(new Properties())) .withMessage( "Failed to read Prometheus properties from nonexistent.properties:" + " nonexistent.properties"); } @Test - public void externalPropertiesShouldOverridePropertiesFile() { + void externalPropertiesShouldOverridePropertiesFile() { Properties properties = new Properties(); - properties.setProperty("io.prometheus.metrics.histogramClassicUpperBounds", ".005, .01"); + properties.setProperty("io.prometheus.metrics.histogram_classic_upper_bounds", ".005, .01"); properties.setProperty( - "io.prometheus.metrics.http_duration_seconds.histogramClassicUpperBounds", + "io.prometheus.metrics.http_duration_seconds.histogram_classic_upper_bounds", ".005, .01, .015"); - properties.setProperty("io.prometheus.exporter.exemplarsOnAllMetricTypes", "false"); + properties.setProperty("io.prometheus.exporter.exemplars_on_all_metric_types", "false"); PrometheusProperties prometheusProperties = PrometheusPropertiesLoader.load(properties); assertThat(prometheusProperties.getDefaultMetricProperties().getHistogramClassicUpperBounds()) @@ -57,4 +56,148 @@ public void externalPropertiesShouldOverridePropertiesFile() { assertThat(prometheusProperties.getExporterProperties().getExemplarsOnAllMetricTypes()) .isFalse(); } + + @Test + void environmentVariablesShouldConfigureMetrics() { + // Simulate environment variables as they would be loaded + Map envVarProperties = new HashMap<>(); + envVarProperties.put( + "io_prometheus_metrics_http_duration_seconds_histogram_classic_upper_bounds", + ".001, .005, .01"); + envVarProperties.put("io_prometheus_metrics_exemplars_enabled", "false"); + + PropertySource propertySource = + new PropertySource(new HashMap<>(), envVarProperties, new HashMap<>()); + PrometheusProperties.MetricPropertiesMap metricsConfigs = + PrometheusPropertiesLoader.loadMetricsConfigs(propertySource); + + assertThat(metricsConfigs.get("http_duration_seconds").getHistogramClassicUpperBounds()) + .hasSize(3) + .containsExactly(0.001, 0.005, 0.01); + + MetricsProperties defaultMetrics = + MetricsProperties.load("io.prometheus.metrics", propertySource); + assertThat(defaultMetrics.getExemplarsEnabled()).isFalse(); + } + + @Test + void environmentVariablesShouldHandleSnakeCaseMetricNames() { + // Simulate environment variable for metric with snake_case name + Map envVarProperties = new HashMap<>(); + envVarProperties.put("io_prometheus_metrics_http_server_histogram_native_only", "true"); + + PropertySource propertySource = + new PropertySource(new HashMap<>(), envVarProperties, new HashMap<>()); + PrometheusProperties.MetricPropertiesMap metricsConfigs = + PrometheusPropertiesLoader.loadMetricsConfigs(propertySource); + + assertThat(metricsConfigs.get("http_server").getHistogramNativeOnly()).isTrue(); + } + + @Test + void environmentVariablesShouldHandleMultipleSnakeCaseSegments() { + // Simulate environment variable for metric with multiple snake_case segments + Map envVarProperties = new HashMap<>(); + envVarProperties.put("io_prometheus_metrics_my_custom_metric_histogram_native_only", "true"); + + PropertySource propertySource = + new PropertySource(new HashMap<>(), envVarProperties, new HashMap<>()); + PrometheusProperties.MetricPropertiesMap metricsConfigs = + PrometheusPropertiesLoader.loadMetricsConfigs(propertySource); + + assertThat(metricsConfigs.get("my_custom_metric").getHistogramNativeOnly()).isTrue(); + } + + @Test + void environmentVariablesShouldHandleMetricNamesContainingPropertyKeywords() { + // Metric names can contain words like "summary" or "histogram" + // This should not confuse the parser + Map envVarProperties = new HashMap<>(); + envVarProperties.put("io_prometheus_metrics_my_summary_metric_histogram_native_only", "true"); + envVarProperties.put( + "io_prometheus_metrics_histogram_processor_summary_quantiles", "0.5, 0.95"); + + PropertySource propertySource = + new PropertySource(new HashMap<>(), envVarProperties, new HashMap<>()); + PrometheusProperties.MetricPropertiesMap metricsConfigs = + PrometheusPropertiesLoader.loadMetricsConfigs(propertySource); + + assertThat(metricsConfigs.get("my_summary_metric").getHistogramNativeOnly()).isTrue(); + assertThat(metricsConfigs.get("histogram_processor").getSummaryQuantiles()) + .containsExactly(0.5, 0.95); + } + + @Test + void regularPropertiesShouldHandleComplexMetricNames() { + // Test that suffix-based matching works correctly for regular properties + // Metric names already use underscores (exposition format) + Properties properties = new Properties(); + properties.setProperty( + "io.prometheus.metrics.http_server_requests_total.histogram_native_only", "true"); + properties.setProperty( + "io.prometheus.metrics.my_app_custom_metric.summary_quantiles", "0.5, 0.99"); + + PropertySource propertySource = new PropertySource(properties); + PrometheusProperties.MetricPropertiesMap metricsConfigs = + PrometheusPropertiesLoader.loadMetricsConfigs(propertySource); + + assertThat(metricsConfigs.get("http_server_requests_total").getHistogramNativeOnly()).isTrue(); + assertThat(metricsConfigs.get("my_app_custom_metric").getSummaryQuantiles()) + .containsExactly(0.5, 0.99); + } + + @Test + void normalizePropertyKeyShouldConvertCamelCaseToSnakeCase() { + // Test that camelCase keys are correctly converted to snake_case for backward compatibility + assertThat( + PrometheusPropertiesLoader.normalizePropertyKey( + "io.prometheus.exporter.httpServer.port")) + .isEqualTo("io.prometheus.exporter.http_server.port"); + assertThat( + PrometheusPropertiesLoader.normalizePropertyKey( + "io.prometheus.metrics.exemplarsEnabled")) + .isEqualTo("io.prometheus.metrics.exemplars_enabled"); + assertThat( + PrometheusPropertiesLoader.normalizePropertyKey( + "io.prometheus.exporter.openTelemetry.serviceName")) + .isEqualTo("io.prometheus.exporter.open_telemetry.service_name"); + + // Test that snake_case keys remain unchanged + assertThat( + PrometheusPropertiesLoader.normalizePropertyKey( + "io.prometheus.exporter.http_server.port")) + .isEqualTo("io.prometheus.exporter.http_server.port"); + assertThat( + PrometheusPropertiesLoader.normalizePropertyKey( + "io.prometheus.metrics.exemplars_enabled")) + .isEqualTo("io.prometheus.metrics.exemplars_enabled"); + } + + @Test + void environmentVariablesShouldHaveCorrectPrecedence() { + // Test that environment variables override system properties and files, + // but are overridden by external properties + Properties fileProperties = new Properties(); + fileProperties.setProperty("io.prometheus.metrics.histogram_native_only", "false"); + + Map envVarProperties = new HashMap<>(); + envVarProperties.put("io_prometheus_metrics_histogram_native_only", "true"); + + Map externalProperties = new HashMap<>(); + externalProperties.put("io.prometheus.metrics.histogram_native_only", "false"); + + // Test that env vars override file properties + PropertySource propertySourceWithEnv = + new PropertySource(new HashMap<>(), envVarProperties, fileProperties); + MetricsProperties metricsWithEnv = + MetricsProperties.load("io.prometheus.metrics", propertySourceWithEnv); + assertThat(metricsWithEnv.getHistogramNativeOnly()).isTrue(); + + // Test that external properties override env vars + PropertySource propertySourceWithExternal = + new PropertySource(externalProperties, envVarProperties, fileProperties); + MetricsProperties metricsWithExternal = + MetricsProperties.load("io.prometheus.metrics", propertySourceWithExternal); + assertThat(metricsWithExternal.getHistogramNativeOnly()).isFalse(); + } } diff --git a/prometheus-metrics-config/src/test/java/io/prometheus/metrics/config/PrometheusPropertiesTest.java b/prometheus-metrics-config/src/test/java/io/prometheus/metrics/config/PrometheusPropertiesTest.java index cce7213a6..3e891202a 100644 --- a/prometheus-metrics-config/src/test/java/io/prometheus/metrics/config/PrometheusPropertiesTest.java +++ b/prometheus-metrics-config/src/test/java/io/prometheus/metrics/config/PrometheusPropertiesTest.java @@ -4,13 +4,16 @@ import java.io.IOException; import java.io.InputStream; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; import java.util.Properties; import org.junit.jupiter.api.Test; class PrometheusPropertiesTest { @Test - public void testPrometheusConfig() { + void testPrometheusConfig() { PrometheusProperties result = PrometheusProperties.get(); assertThat(result.getDefaultMetricProperties().getHistogramClassicUpperBounds()).hasSize(11); assertThat(result.getMetricProperties("http_duration_seconds").getHistogramClassicUpperBounds()) @@ -18,7 +21,7 @@ public void testPrometheusConfig() { } @Test - public void testEmptyUpperBounds() throws IOException { + void testEmptyUpperBounds() throws IOException { Properties properties = new Properties(); try (InputStream stream = Thread.currentThread() @@ -27,7 +30,102 @@ public void testEmptyUpperBounds() throws IOException { properties.load(stream); } assertThat(properties).hasSize(1); - MetricsProperties.load("io.prometheus.metrics", properties); - assertThat(properties).isEmpty(); + Map regularProperties = new HashMap<>(properties); + PropertySource propertySource = new PropertySource(regularProperties); + MetricsProperties.load("io.prometheus.metrics", propertySource); + assertThat(regularProperties).isEmpty(); + } + + @Test + void testBuilder() { + PrometheusProperties defaults = PrometheusPropertiesLoader.load(new HashMap<>()); + PrometheusProperties.Builder builder = PrometheusProperties.builder(); + builder.defaultMetricsProperties(defaults.getDefaultMetricProperties()); + builder.metricProperties( + Collections.singletonMap( + "http_duration_seconds", + MetricsProperties.builder().histogramClassicUpperBounds(0.1, 0.2, 0.5, 1.0).build())); + builder.exemplarProperties(defaults.getExemplarProperties()); + builder.defaultMetricsProperties(defaults.getDefaultMetricProperties()); + builder.exporterFilterProperties(defaults.getExporterFilterProperties()); + builder.exporterHttpServerProperties(defaults.getExporterHttpServerProperties()); + builder.exporterOpenTelemetryProperties(defaults.getExporterOpenTelemetryProperties()); + builder.pushgatewayProperties(defaults.getExporterPushgatewayProperties()); + builder.exporterProperties(defaults.getExporterProperties()); + PrometheusProperties result = builder.build(); + assertThat(result.getDefaultMetricProperties()).isSameAs(defaults.getDefaultMetricProperties()); + assertThat(result.getExemplarProperties()).isSameAs(defaults.getExemplarProperties()); + assertThat(result.getExporterFilterProperties()) + .isSameAs(defaults.getExporterFilterProperties()); + assertThat(result.getExporterHttpServerProperties()) + .isSameAs(defaults.getExporterHttpServerProperties()); + assertThat(result.getExporterOpenTelemetryProperties()) + .isSameAs(defaults.getExporterOpenTelemetryProperties()); + assertThat(result.getExporterPushgatewayProperties()) + .isSameAs(defaults.getExporterPushgatewayProperties()); + assertThat(result.getMetricProperties("http_duration_seconds")) + .usingRecursiveComparison() + .isEqualTo( + MetricsProperties.builder().histogramClassicUpperBounds(0.1, 0.2, 0.5, 1.0).build()); + assertThat(result.getMetricProperties("unknown_metric")).isNull(); + assertThat(result.getExporterProperties()).isSameAs(defaults.getExporterProperties()); + } + + @Test + void testMetricNameNormalization() { + PrometheusProperties.Builder builder = PrometheusProperties.builder(); + MetricsProperties customProps = + MetricsProperties.builder().histogramClassicUpperBounds(0.1, 0.5).build(); + + // Test that metric names with dots are normalized to underscores + builder.putMetricProperty("my.metric.name", customProps); + PrometheusProperties result = builder.build(); + + // Should be able to retrieve with dots + assertThat(result.getMetricProperties("my.metric.name")).isSameAs(customProps); + // Should also be able to retrieve with underscores + assertThat(result.getMetricProperties("my_metric_name")).isSameAs(customProps); + } + + @Test + void testMetricNameWithInvalidCharacters() { + PrometheusProperties.Builder builder = PrometheusProperties.builder(); + MetricsProperties customProps = + MetricsProperties.builder().histogramClassicUpperBounds(0.1, 0.5).build(); + + // Test that invalid characters are converted to underscores + builder.putMetricProperty("metric-name@with#invalid$chars", customProps); + PrometheusProperties result = builder.build(); + + // Should normalize invalid characters to underscores + assertThat(result.getMetricProperties("metric-name@with#invalid$chars")).isSameAs(customProps); + assertThat(result.getMetricProperties("metric_name_with_invalid_chars")).isSameAs(customProps); + } + + @Test + void testMetricNameWithValidCharacters() { + PrometheusProperties.Builder builder = PrometheusProperties.builder(); + MetricsProperties customProps = + MetricsProperties.builder().histogramClassicUpperBounds(0.1, 0.5).build(); + + // Test valid characters: letters, numbers (not at start), underscore, colon + builder.putMetricProperty("my_metric:name123", customProps); + PrometheusProperties result = builder.build(); + + assertThat(result.getMetricProperties("my_metric:name123")).isSameAs(customProps); + } + + @Test + void testMetricNameStartingWithNumber() { + PrometheusProperties.Builder builder = PrometheusProperties.builder(); + MetricsProperties customProps = + MetricsProperties.builder().histogramClassicUpperBounds(0.1, 0.5).build(); + + // First digit is invalid (i=0), but subsequent digits are valid (i>0) + builder.putMetricProperty("123metric", customProps); + PrometheusProperties result = builder.build(); + + assertThat(result.getMetricProperties("123metric")).isSameAs(customProps); + assertThat(result.getMetricProperties("_23metric")).isSameAs(customProps); } } diff --git a/prometheus-metrics-config/src/test/java/io/prometheus/metrics/config/UtilTest.java b/prometheus-metrics-config/src/test/java/io/prometheus/metrics/config/UtilTest.java new file mode 100644 index 000000000..e4d7fa829 --- /dev/null +++ b/prometheus-metrics-config/src/test/java/io/prometheus/metrics/config/UtilTest.java @@ -0,0 +1,56 @@ +package io.prometheus.metrics.config; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; + +import java.time.Duration; +import java.util.HashMap; +import java.util.Map; +import org.junit.jupiter.api.Test; + +class UtilTest { + @Test + void loadOptionalDuration_positive() { + Map regularProperties = new HashMap<>(Map.of("foo", "5")); + PropertySource propertySource = new PropertySource(regularProperties); + + assertThat(Util.loadOptionalDuration("", "foo", propertySource)) + .isEqualTo(Duration.ofSeconds(5)); + } + + @Test + void loadOptionalDuration_zero() { + Map regularProperties = new HashMap<>(Map.of("foo", "0")); + PropertySource propertySource = new PropertySource(regularProperties); + + assertThat(Util.loadOptionalDuration("", "foo", propertySource)).isNull(); + } + + @Test + void loadOptionalDuration_missing() { + Map regularProperties = new HashMap<>(); + PropertySource propertySource = new PropertySource(regularProperties); + + assertThat(Util.loadOptionalDuration("", "foo", propertySource)).isNull(); + } + + @Test + void loadOptionalDuration_negative_throws() { + Map regularProperties = new HashMap<>(Map.of("foo", "-1")); + PropertySource propertySource = new PropertySource(regularProperties); + + assertThatExceptionOfType(PrometheusPropertiesException.class) + .isThrownBy(() -> Util.loadOptionalDuration("", "foo", propertySource)) + .withMessage("foo: Expecting value >= 0. Found: -1"); + } + + @Test + void loadOptionalDuration_invalidNumber_throws() { + Map regularProperties = new HashMap<>(Map.of("foo", "abc")); + PropertySource propertySource = new PropertySource(regularProperties); + + assertThatExceptionOfType(PrometheusPropertiesException.class) + .isThrownBy(() -> Util.loadOptionalDuration("", "foo", propertySource)) + .withMessage("foo=abc: Expecting long value"); + } +} diff --git a/prometheus-metrics-config/src/test/resources/emptyUpperBounds.properties b/prometheus-metrics-config/src/test/resources/emptyUpperBounds.properties index c019761ca..d8c1a3515 100644 --- a/prometheus-metrics-config/src/test/resources/emptyUpperBounds.properties +++ b/prometheus-metrics-config/src/test/resources/emptyUpperBounds.properties @@ -1 +1 @@ -io.prometheus.metrics.histogramClassicUpperBounds =, +io.prometheus.metrics.histogram_classic_upper_bounds =, diff --git a/prometheus-metrics-config/src/test/resources/prometheus.properties b/prometheus-metrics-config/src/test/resources/prometheus.properties index 13a5d9e39..e5469803f 100644 --- a/prometheus-metrics-config/src/test/resources/prometheus.properties +++ b/prometheus-metrics-config/src/test/resources/prometheus.properties @@ -1,26 +1,26 @@ -io.prometheus.metrics.histogramClassicUpperBounds = .005, .01, .025, .05, .1, .25, .5, 1, 2.5, 5, 10 -io.prometheus.metrics.http_duration_seconds.histogramClassicUpperBounds = .005, .01, .015, .02 -io.prometheus.metrics.summaryQuantiles = 0.5, 0.95, 0.99 -io.prometheus.metrics.summaryQuantileErrors = 0.01, 0.01, 0.001 +io.prometheus.metrics.histogram_classic_upper_bounds = .005, .01, .025, .05, .1, .25, .5, 1, 2.5, 5, 10 +io.prometheus.metrics.http_duration_seconds.histogram_classic_upper_bounds = .005, .01, .015, .02 +io.prometheus.metrics.summary_quantiles = 0.5, 0.95, 0.99 +io.prometheus.metrics.summary_quantile_errors = 0.01, 0.01, 0.001 -io.prometheus.metrics.exemplarsEnabled = true -io.prometheus.metrics.http_duration_seconds.exemplarsEnabled = true +io.prometheus.metrics.exemplars_enabled = true +io.prometheus.metrics.http_duration_seconds.exemplars_enabled = true -io.prometheus.exporter.includeCreatedTimestamps = false -io.prometheus.exporter.exemplarsOnAllMetricTypes = true -io.prometheus.exporter.filter.metricNameMustBeEqualTo = a, b, c +io.prometheus.exporter.include_created_timestamps = false +io.prometheus.exporter.exemplars_on_all_metric_types = true +io.prometheus.exporter.filter.metric_name_must_be_equal_to = a, b, c -io.prometheus.exemplars.minRetentionPeriodSeconds = 30 -io.prometheus.exemplars.maxRetentionPeriodSeconds = 30 -io.prometheus.exemplars.sampleIntervalMilliseconds = 30 +io.prometheus.exemplars.min_retention_period_seconds = 30 +io.prometheus.exemplars.max_retention_period_seconds = 30 +io.prometheus.exemplars.sample_interval_milliseconds = 30 -io.prometheus.exporter.httpServer.port = 9000 +io.prometheus.exporter.http_server.port = 9000 io.prometheus.exporter.opentelemetry.endpoint=http://localhost:4318 -io.prometheus.exporter.opentelemetry.intervalSeconds=30 -io.prometheus.exporter.opentelemetry.serviceName=hello-world-app -io.prometheus.exporter.opentelemetry.serviceNamespace=prod -io.prometheus.exporter.opentelemetry.serviceInstanceId=localhost:8081 +io.prometheus.exporter.opentelemetry.interval_seconds=30 +io.prometheus.exporter.opentelemetry.service_name=hello-world-app +io.prometheus.exporter.opentelemetry.service_namespace=prod +io.prometheus.exporter.opentelemetry.service_instance_id=localhost:8081 -# io.prometheus.metrics.unusedLabelsRetentionTimeSeconds = 0 +# io.prometheus.metrics.unused_labels_retention_time_seconds = 0 diff --git a/prometheus-metrics-core/pom.xml b/prometheus-metrics-core/pom.xml index d8bccb458..b64c5db54 100644 --- a/prometheus-metrics-core/pom.xml +++ b/prometheus-metrics-core/pom.xml @@ -6,7 +6,7 @@ io.prometheus client_java - 1.4.0-SNAPSHOT + 1.6.0-SNAPSHOT prometheus-metrics-core @@ -38,10 +38,10 @@ ${project.version} - + io.prometheus - prometheus-metrics-exposition-formats + prometheus-metrics-exposition-formats-no-protobuf ${project.version} test diff --git a/prometheus-metrics-core/src/main/java/io/prometheus/metrics/core/datapoints/DistributionDataPoint.java b/prometheus-metrics-core/src/main/java/io/prometheus/metrics/core/datapoints/DistributionDataPoint.java index 82e42a892..0f2a072de 100644 --- a/prometheus-metrics-core/src/main/java/io/prometheus/metrics/core/datapoints/DistributionDataPoint.java +++ b/prometheus-metrics-core/src/main/java/io/prometheus/metrics/core/datapoints/DistributionDataPoint.java @@ -19,6 +19,12 @@ */ public interface DistributionDataPoint extends DataPoint, TimerApi { + /** Get the count of observations. */ + long getCount(); + + /** Get the sum of all observed values. */ + double getSum(); + /** Observe {@code value}. */ void observe(double value); diff --git a/prometheus-metrics-core/src/main/java/io/prometheus/metrics/core/exemplars/ExemplarSampler.java b/prometheus-metrics-core/src/main/java/io/prometheus/metrics/core/exemplars/ExemplarSampler.java index 15b0355cd..1219b2a09 100644 --- a/prometheus-metrics-core/src/main/java/io/prometheus/metrics/core/exemplars/ExemplarSampler.java +++ b/prometheus-metrics-core/src/main/java/io/prometheus/metrics/core/exemplars/ExemplarSampler.java @@ -1,5 +1,7 @@ package io.prometheus.metrics.core.exemplars; +import static java.util.Objects.requireNonNull; + import io.prometheus.metrics.core.util.Scheduler; import io.prometheus.metrics.model.snapshots.Exemplar; import io.prometheus.metrics.model.snapshots.Exemplars; @@ -10,6 +12,7 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.LongSupplier; +import javax.annotation.Nullable; /** * The ExemplarSampler selects Spans as exemplars. @@ -37,6 +40,8 @@ public class ExemplarSampler { // to be overwritten by automatic exemplar sampling. exemplars.length == customExemplars.length private final AtomicBoolean acceptingNewExemplars = new AtomicBoolean(true); private final AtomicBoolean acceptingNewCustomExemplars = new AtomicBoolean(true); + + @Nullable private final SpanContext spanContext; // may be null, in that case SpanContextSupplier.getSpanContext() is used. @@ -52,7 +57,7 @@ public ExemplarSampler(ExemplarSamplerConfig config) { * io.prometheus.metrics.tracer.initializer.SpanContextSupplier#getSpanContext() * SpanContextSupplier.getSpanContext()} is called to find a span context. */ - public ExemplarSampler(ExemplarSamplerConfig config, SpanContext spanContext) { + public ExemplarSampler(ExemplarSamplerConfig config, @Nullable SpanContext spanContext) { this.config = config; this.exemplars = new Exemplar[config.getNumberOfExemplars()]; this.customExemplars = new Exemplar[exemplars.length]; @@ -113,10 +118,13 @@ public void observeWithExemplar(double value, Labels labels) { private long doObserve(double value) { if (exemplars.length == 1) { return doObserveSingleExemplar(value); - } else if (config.getHistogramClassicUpperBounds() != null) { - return doObserveWithUpperBounds(value); } else { - return doObserveWithoutUpperBounds(value); + double[] classicUpperBounds = config.getHistogramClassicUpperBounds(); + if (classicUpperBounds != null) { + return doObserveWithUpperBounds(value, classicUpperBounds); + } else { + return doObserveWithoutUpperBounds(value); + } } } @@ -140,11 +148,10 @@ private long doObserveSingleExemplar(double amount, Labels labels) { return 0; } - private long doObserveWithUpperBounds(double value) { + private long doObserveWithUpperBounds(double value, double[] classicUpperBounds) { long now = System.currentTimeMillis(); - double[] upperBounds = config.getHistogramClassicUpperBounds(); - for (int i = 0; i < upperBounds.length; i++) { - if (value <= upperBounds[i]) { + for (int i = 0; i < classicUpperBounds.length; i++) { + if (value <= classicUpperBounds[i]) { Exemplar previous = exemplars[i]; if (previous == null || now - previous.getTimestampMillis() > config.getMinRetentionPeriodMillis()) { @@ -185,11 +192,11 @@ private long doObserveWithoutUpperBounds(double value) { if (nullIndex >= 0) { return updateExemplar(nullIndex, value, now); } - if (now - smallest.getTimestampMillis() > config.getMinRetentionPeriodMillis() + if (now - requireNonNull(smallest).getTimestampMillis() > config.getMinRetentionPeriodMillis() && value < smallest.getValue()) { return updateExemplar(smallestIndex, value, now); } - if (now - largest.getTimestampMillis() > config.getMinRetentionPeriodMillis() + if (now - requireNonNull(largest).getTimestampMillis() > config.getMinRetentionPeriodMillis() && value > largest.getValue()) { return updateExemplar(largestIndex, value, now); } @@ -215,18 +222,21 @@ private long doObserveWithoutUpperBounds(double value) { private long doObserveWithExemplar(double amount, Labels labels) { if (customExemplars.length == 1) { return doObserveSingleExemplar(amount, labels); - } else if (config.getHistogramClassicUpperBounds() != null) { - return doObserveWithExemplarWithUpperBounds(amount, labels); } else { - return doObserveWithExemplarWithoutUpperBounds(amount, labels); + double[] classicUpperBounds = config.getHistogramClassicUpperBounds(); + if (classicUpperBounds != null) { + return doObserveWithExemplarWithUpperBounds(amount, labels, classicUpperBounds); + } else { + return doObserveWithExemplarWithoutUpperBounds(amount, labels); + } } } - private long doObserveWithExemplarWithUpperBounds(double value, Labels labels) { + private long doObserveWithExemplarWithUpperBounds( + double value, Labels labels, double[] classicUpperBounds) { long now = System.currentTimeMillis(); - double[] upperBounds = config.getHistogramClassicUpperBounds(); - for (int i = 0; i < upperBounds.length; i++) { - if (value <= upperBounds[i]) { + for (int i = 0; i < classicUpperBounds.length; i++) { + if (value <= classicUpperBounds[i]) { Exemplar previous = customExemplars[i]; if (previous == null || now - previous.getTimestampMillis() > config.getMinRetentionPeriodMillis()) { @@ -260,7 +270,8 @@ private long doObserveWithExemplarWithoutUpperBounds(double amount, Labels label } if (nullPos != -1) { return updateCustomExemplar(nullPos, amount, labels, now); - } else if (now - oldest.getTimestampMillis() > config.getMinRetentionPeriodMillis()) { + } else if (now - requireNonNull(oldest).getTimestampMillis() + > config.getMinRetentionPeriodMillis()) { return updateCustomExemplar(oldestPos, amount, labels, now); } else { return 0; diff --git a/prometheus-metrics-core/src/main/java/io/prometheus/metrics/core/exemplars/ExemplarSamplerConfig.java b/prometheus-metrics-core/src/main/java/io/prometheus/metrics/core/exemplars/ExemplarSamplerConfig.java index 7acfbec22..5bf642e7e 100644 --- a/prometheus-metrics-core/src/main/java/io/prometheus/metrics/core/exemplars/ExemplarSamplerConfig.java +++ b/prometheus-metrics-core/src/main/java/io/prometheus/metrics/core/exemplars/ExemplarSamplerConfig.java @@ -3,6 +3,7 @@ import io.prometheus.metrics.config.ExemplarsProperties; import io.prometheus.metrics.config.PrometheusProperties; import java.util.concurrent.TimeUnit; +import javax.annotation.Nullable; public class ExemplarSamplerConfig { @@ -18,11 +19,13 @@ public class ExemplarSamplerConfig { private final long minRetentionPeriodMillis; private final long maxRetentionPeriodMillis; private final long sampleIntervalMillis; + + @Nullable private final double[] histogramClassicUpperBounds; // null unless it's a classic histogram - private final int - numberOfExemplars; // if histogramClassicUpperBounds != null, then numberOfExemplars == - // histogramClassicUpperBounds.length + // if histogramClassicUpperBounds != null, + // then numberOfExemplars == histogramClassicUpperBounds.length + private final int numberOfExemplars; /** * Constructor for all metric types except classic histograms. @@ -49,7 +52,9 @@ public ExemplarSamplerConfig( } private ExemplarSamplerConfig( - ExemplarsProperties properties, int numberOfExemplars, double[] histogramClassicUpperBounds) { + ExemplarsProperties properties, + int numberOfExemplars, + @Nullable double[] histogramClassicUpperBounds) { this( TimeUnit.SECONDS.toMillis( getOrDefault( @@ -68,7 +73,7 @@ private ExemplarSamplerConfig( long maxRetentionPeriodMillis, long sampleIntervalMillis, int numberOfExemplars, - double[] histogramClassicUpperBounds) { + @Nullable double[] histogramClassicUpperBounds) { this.minRetentionPeriodMillis = minRetentionPeriodMillis; this.maxRetentionPeriodMillis = maxRetentionPeriodMillis; this.sampleIntervalMillis = sampleIntervalMillis; @@ -110,11 +115,11 @@ private void validate() { } } - private static T getOrDefault(T result, T defaultValue) { + private static T getOrDefault(@Nullable T result, T defaultValue) { return result != null ? result : defaultValue; } - /** May be {@code null}. */ + @Nullable public double[] getHistogramClassicUpperBounds() { return histogramClassicUpperBounds; } diff --git a/prometheus-metrics-core/src/main/java/io/prometheus/metrics/core/metrics/Buffer.java b/prometheus-metrics-core/src/main/java/io/prometheus/metrics/core/metrics/Buffer.java index d4ff33a37..1c47f867c 100644 --- a/prometheus-metrics-core/src/main/java/io/prometheus/metrics/core/metrics/Buffer.java +++ b/prometheus-metrics-core/src/main/java/io/prometheus/metrics/core/metrics/Buffer.java @@ -18,7 +18,15 @@ class Buffer { private static final long bufferActiveBit = 1L << 63; - private final AtomicLong observationCount = new AtomicLong(0); + // Tracking observation counts requires an AtomicLong for coordination between recording and + // collecting. AtomicLong does much worse under contention than the LongAdder instances used + // elsewhere to hold aggregated state. To improve, we stripe the AtomicLong into N instances, + // where N is the number of available processors. Each record operation chooses the appropriate + // instance to use based on the modulo of its thread id and N. This is a more naive / simple + // implementation compared to the striping used under the hood in java.util.concurrent classes + // like LongAdder - contention and hot spots can still occur if recording thread ids happen to + // resolve to the same index. Further improvement is possible. + private final AtomicLong[] stripedObservationCounts; private double[] observationBuffer = new double[0]; private int bufferPos = 0; private boolean reset = false; @@ -27,8 +35,17 @@ class Buffer { ReentrantLock runLock = new ReentrantLock(); Condition bufferFilled = appendLock.newCondition(); + Buffer() { + stripedObservationCounts = new AtomicLong[Runtime.getRuntime().availableProcessors()]; + for (int i = 0; i < stripedObservationCounts.length; i++) { + stripedObservationCounts[i] = new AtomicLong(0); + } + } + boolean append(double value) { - long count = observationCount.incrementAndGet(); + int index = Math.abs((int) Thread.currentThread().getId()) % stripedObservationCounts.length; + AtomicLong observationCountForThread = stripedObservationCounts[index]; + long count = observationCountForThread.incrementAndGet(); if ((count & bufferActiveBit) == 0) { return false; // sign bit not set -> buffer not active. } else { @@ -69,7 +86,10 @@ T run( runLock.lock(); try { // Signal that the buffer is active. - Long expectedCount = observationCount.getAndAdd(bufferActiveBit); + long expectedCount = 0L; + for (AtomicLong observationCount : stripedObservationCounts) { + expectedCount += observationCount.getAndAdd(bufferActiveBit); + } while (!complete.apply(expectedCount)) { // Wait until all in-flight threads have added their observations to the histogram / @@ -81,14 +101,18 @@ T run( result = createResult.get(); // Signal that the buffer is inactive. - int expectedBufferSize; + long expectedBufferSize = 0; if (reset) { - expectedBufferSize = - (int) ((observationCount.getAndSet(0) & ~bufferActiveBit) - expectedCount); + for (AtomicLong observationCount : stripedObservationCounts) { + expectedBufferSize += observationCount.getAndSet(0) & ~bufferActiveBit; + } reset = false; } else { - expectedBufferSize = (int) (observationCount.addAndGet(bufferActiveBit) - expectedCount); + for (AtomicLong observationCount : stripedObservationCounts) { + expectedBufferSize += observationCount.addAndGet(bufferActiveBit); + } } + expectedBufferSize -= expectedCount; appendLock.lock(); try { diff --git a/prometheus-metrics-core/src/main/java/io/prometheus/metrics/core/metrics/Counter.java b/prometheus-metrics-core/src/main/java/io/prometheus/metrics/core/metrics/Counter.java index 8ed9b0001..c5f2f1cff 100644 --- a/prometheus-metrics-core/src/main/java/io/prometheus/metrics/core/metrics/Counter.java +++ b/prometheus-metrics-core/src/main/java/io/prometheus/metrics/core/metrics/Counter.java @@ -5,6 +5,7 @@ import io.prometheus.metrics.core.datapoints.CounterDataPoint; import io.prometheus.metrics.core.exemplars.ExemplarSampler; import io.prometheus.metrics.core.exemplars.ExemplarSamplerConfig; +import io.prometheus.metrics.model.registry.MetricType; import io.prometheus.metrics.model.snapshots.CounterSnapshot; import io.prometheus.metrics.model.snapshots.Exemplar; import io.prometheus.metrics.model.snapshots.Labels; @@ -13,6 +14,7 @@ import java.util.List; import java.util.concurrent.atomic.DoubleAdder; import java.util.concurrent.atomic.LongAdder; +import javax.annotation.Nullable; /** * Counter metric. @@ -32,13 +34,13 @@ public class Counter extends StatefulMetric implements CounterDataPoint { - private final boolean exemplarsEnabled; - private final ExemplarSamplerConfig exemplarSamplerConfig; + @Nullable private final ExemplarSamplerConfig exemplarSamplerConfig; private Counter(Builder builder, PrometheusProperties prometheusProperties) { super(builder); MetricsProperties[] properties = getMetricProperties(builder, prometheusProperties); - exemplarsEnabled = getConfigProperty(properties, MetricsProperties::getExemplarsEnabled); + boolean exemplarsEnabled = + getConfigProperty(properties, MetricsProperties::getExemplarsEnabled); if (exemplarsEnabled) { exemplarSamplerConfig = new ExemplarSamplerConfig(prometheusProperties.getExemplarProperties(), 1); @@ -92,13 +94,13 @@ protected CounterSnapshot collect(List labels, List metricDat } @Override - protected boolean isExemplarsEnabled() { - return exemplarsEnabled; + public MetricType getMetricType() { + return MetricType.COUNTER; } @Override protected DataPoint newDataPoint() { - if (isExemplarsEnabled()) { + if (exemplarSamplerConfig != null) { return new DataPoint(new ExemplarSampler(exemplarSamplerConfig)); } else { return new DataPoint(null); @@ -112,7 +114,7 @@ static String stripTotalSuffix(String name) { return name; } - class DataPoint implements CounterDataPoint { + static class DataPoint implements CounterDataPoint { private final DoubleAdder doubleValue = new DoubleAdder(); // LongAdder is 20% faster than DoubleAdder. So let's use the LongAdder for long observations, @@ -120,9 +122,11 @@ class DataPoint implements CounterDataPoint { // we will be using the LongAdder and get the best performance. private final LongAdder longValue = new LongAdder(); private final long createdTimeMillis = System.currentTimeMillis(); - private final ExemplarSampler exemplarSampler; // null if isExemplarsEnabled() is false - private DataPoint(ExemplarSampler exemplarSampler) { + @Nullable + private final ExemplarSampler exemplarSampler; // null if exemplarSamplerConfig is null + + private DataPoint(@Nullable ExemplarSampler exemplarSampler) { this.exemplarSampler = exemplarSampler; } @@ -139,7 +143,7 @@ public long getLongValue() { @Override public void inc(long amount) { validateAndAdd(amount); - if (isExemplarsEnabled()) { + if (exemplarSampler != null) { exemplarSampler.observe((double) amount); } } @@ -147,7 +151,7 @@ public void inc(long amount) { @Override public void inc(double amount) { validateAndAdd(amount); - if (isExemplarsEnabled()) { + if (exemplarSampler != null) { exemplarSampler.observe(amount); } } @@ -155,7 +159,7 @@ public void inc(double amount) { @Override public void incWithExemplar(long amount, Labels labels) { validateAndAdd(amount); - if (isExemplarsEnabled()) { + if (exemplarSampler != null) { exemplarSampler.observeWithExemplar((double) amount, labels); } } @@ -163,7 +167,7 @@ public void incWithExemplar(long amount, Labels labels) { @Override public void incWithExemplar(double amount, Labels labels) { validateAndAdd(amount); - if (isExemplarsEnabled()) { + if (exemplarSampler != null) { exemplarSampler.observeWithExemplar(amount, labels); } } diff --git a/prometheus-metrics-core/src/main/java/io/prometheus/metrics/core/metrics/CounterWithCallback.java b/prometheus-metrics-core/src/main/java/io/prometheus/metrics/core/metrics/CounterWithCallback.java index f8c1b162c..3a818c004 100644 --- a/prometheus-metrics-core/src/main/java/io/prometheus/metrics/core/metrics/CounterWithCallback.java +++ b/prometheus-metrics-core/src/main/java/io/prometheus/metrics/core/metrics/CounterWithCallback.java @@ -1,11 +1,13 @@ package io.prometheus.metrics.core.metrics; import io.prometheus.metrics.config.PrometheusProperties; +import io.prometheus.metrics.model.registry.MetricType; import io.prometheus.metrics.model.snapshots.CounterSnapshot; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.function.Consumer; +import javax.annotation.Nullable; /** * Example: @@ -31,10 +33,10 @@ public interface Callback { private CounterWithCallback(Builder builder) { super(builder); - this.callback = builder.callback; - if (callback == null) { + if (builder.callback == null) { throw new IllegalArgumentException("callback cannot be null"); } + this.callback = builder.callback; } @Override @@ -49,6 +51,11 @@ public CounterSnapshot collect() { return new CounterSnapshot(getMetadata(), dataPoints); } + @Override + public MetricType getMetricType() { + return MetricType.COUNTER; + } + public static Builder builder() { return new Builder(PrometheusProperties.get()); } @@ -60,7 +67,7 @@ public static Builder builder(PrometheusProperties properties) { public static class Builder extends CallbackMetric.Builder { - private Consumer callback; + @Nullable private Consumer callback; public Builder callback(Consumer callback) { this.callback = callback; diff --git a/prometheus-metrics-core/src/main/java/io/prometheus/metrics/core/metrics/Gauge.java b/prometheus-metrics-core/src/main/java/io/prometheus/metrics/core/metrics/Gauge.java index 4ba5d5ed3..8b1f31409 100644 --- a/prometheus-metrics-core/src/main/java/io/prometheus/metrics/core/metrics/Gauge.java +++ b/prometheus-metrics-core/src/main/java/io/prometheus/metrics/core/metrics/Gauge.java @@ -5,6 +5,7 @@ import io.prometheus.metrics.core.datapoints.GaugeDataPoint; import io.prometheus.metrics.core.exemplars.ExemplarSampler; import io.prometheus.metrics.core.exemplars.ExemplarSamplerConfig; +import io.prometheus.metrics.model.registry.MetricType; import io.prometheus.metrics.model.snapshots.Exemplar; import io.prometheus.metrics.model.snapshots.GaugeSnapshot; import io.prometheus.metrics.model.snapshots.Labels; @@ -12,6 +13,7 @@ import java.util.Collections; import java.util.List; import java.util.concurrent.atomic.AtomicLong; +import javax.annotation.Nullable; /** * Gauge metric. @@ -39,13 +41,13 @@ public class Gauge extends StatefulMetric implements GaugeDataPoint { - private final boolean exemplarsEnabled; - private final ExemplarSamplerConfig exemplarSamplerConfig; + @Nullable private final ExemplarSamplerConfig exemplarSamplerConfig; private Gauge(Builder builder, PrometheusProperties prometheusProperties) { super(builder); MetricsProperties[] properties = getMetricProperties(builder, prometheusProperties); - exemplarsEnabled = getConfigProperty(properties, MetricsProperties::getExemplarsEnabled); + boolean exemplarsEnabled = + getConfigProperty(properties, MetricsProperties::getExemplarsEnabled); if (exemplarsEnabled) { exemplarSamplerConfig = new ExemplarSamplerConfig(prometheusProperties.getExemplarProperties(), 1); @@ -93,25 +95,26 @@ protected GaugeSnapshot collect(List labels, List metricData) return new GaugeSnapshot(getMetadata(), dataPointSnapshots); } + @Override + public MetricType getMetricType() { + return MetricType.GAUGE; + } + @Override protected DataPoint newDataPoint() { - if (isExemplarsEnabled()) { + if (exemplarSamplerConfig != null) { return new DataPoint(new ExemplarSampler(exemplarSamplerConfig)); } else { return new DataPoint(null); } } - @Override - protected boolean isExemplarsEnabled() { - return exemplarsEnabled; - } - - class DataPoint implements GaugeDataPoint { + static class DataPoint implements GaugeDataPoint { - private final ExemplarSampler exemplarSampler; // null if isExemplarsEnabled() is false + @Nullable + private final ExemplarSampler exemplarSampler; // null if exemplarSamplerConfig is null - private DataPoint(ExemplarSampler exemplarSampler) { + private DataPoint(@Nullable ExemplarSampler exemplarSampler) { this.exemplarSampler = exemplarSampler; } @@ -121,7 +124,7 @@ private DataPoint(ExemplarSampler exemplarSampler) { public void inc(double amount) { long next = value.updateAndGet(l -> Double.doubleToRawLongBits(Double.longBitsToDouble(l) + amount)); - if (isExemplarsEnabled()) { + if (exemplarSampler != null) { exemplarSampler.observe(Double.longBitsToDouble(next)); } } @@ -130,7 +133,7 @@ public void inc(double amount) { public void incWithExemplar(double amount, Labels labels) { long next = value.updateAndGet(l -> Double.doubleToRawLongBits(Double.longBitsToDouble(l) + amount)); - if (isExemplarsEnabled()) { + if (exemplarSampler != null) { exemplarSampler.observeWithExemplar(Double.longBitsToDouble(next), labels); } } @@ -138,7 +141,7 @@ public void incWithExemplar(double amount, Labels labels) { @Override public void set(double value) { this.value.set(Double.doubleToRawLongBits(value)); - if (isExemplarsEnabled()) { + if (exemplarSampler != null) { exemplarSampler.observe(value); } } @@ -151,7 +154,7 @@ public double get() { @Override public void setWithExemplar(double value, Labels labels) { this.value.set(Double.doubleToRawLongBits(value)); - if (isExemplarsEnabled()) { + if (exemplarSampler != null) { exemplarSampler.observeWithExemplar(value, labels); } } @@ -162,7 +165,7 @@ private GaugeSnapshot.GaugeDataPointSnapshot collect(Labels labels) { // If there are multiple Exemplars (by default it's just one), use the oldest // so that we don't violate min age. Exemplar oldest = null; - if (isExemplarsEnabled()) { + if (exemplarSampler != null) { for (Exemplar exemplar : exemplarSampler.collect()) { if (oldest == null || exemplar.getTimestampMillis() < oldest.getTimestampMillis()) { oldest = exemplar; diff --git a/prometheus-metrics-core/src/main/java/io/prometheus/metrics/core/metrics/GaugeWithCallback.java b/prometheus-metrics-core/src/main/java/io/prometheus/metrics/core/metrics/GaugeWithCallback.java index 8b2d7a0ba..88aee225f 100644 --- a/prometheus-metrics-core/src/main/java/io/prometheus/metrics/core/metrics/GaugeWithCallback.java +++ b/prometheus-metrics-core/src/main/java/io/prometheus/metrics/core/metrics/GaugeWithCallback.java @@ -1,11 +1,13 @@ package io.prometheus.metrics.core.metrics; import io.prometheus.metrics.config.PrometheusProperties; +import io.prometheus.metrics.model.registry.MetricType; import io.prometheus.metrics.model.snapshots.GaugeSnapshot; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.function.Consumer; +import javax.annotation.Nullable; /** * Example: @@ -36,10 +38,10 @@ public interface Callback { private GaugeWithCallback(Builder builder) { super(builder); - this.callback = builder.callback; - if (callback == null) { + if (builder.callback == null) { throw new IllegalArgumentException("callback cannot be null"); } + this.callback = builder.callback; } @Override @@ -53,6 +55,11 @@ public GaugeSnapshot collect() { return new GaugeSnapshot(getMetadata(), dataPoints); } + @Override + public MetricType getMetricType() { + return MetricType.GAUGE; + } + public static Builder builder() { return new Builder(PrometheusProperties.get()); } @@ -64,7 +71,7 @@ public static Builder builder(PrometheusProperties properties) { public static class Builder extends CallbackMetric.Builder { - private Consumer callback; + @Nullable private Consumer callback; public Builder callback(Consumer callback) { this.callback = callback; diff --git a/prometheus-metrics-core/src/main/java/io/prometheus/metrics/core/metrics/Histogram.java b/prometheus-metrics-core/src/main/java/io/prometheus/metrics/core/metrics/Histogram.java index 327867fd0..930d9e67e 100644 --- a/prometheus-metrics-core/src/main/java/io/prometheus/metrics/core/metrics/Histogram.java +++ b/prometheus-metrics-core/src/main/java/io/prometheus/metrics/core/metrics/Histogram.java @@ -7,6 +7,7 @@ import io.prometheus.metrics.core.exemplars.ExemplarSampler; import io.prometheus.metrics.core.exemplars.ExemplarSamplerConfig; import io.prometheus.metrics.core.util.Scheduler; +import io.prometheus.metrics.model.registry.MetricType; import io.prometheus.metrics.model.snapshots.ClassicHistogramBuckets; import io.prometheus.metrics.model.snapshots.Exemplars; import io.prometheus.metrics.model.snapshots.HistogramSnapshot; @@ -24,6 +25,7 @@ import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.DoubleAdder; import java.util.concurrent.atomic.LongAdder; +import javax.annotation.Nullable; /** * Histogram metric. Example usage: @@ -68,8 +70,7 @@ public class Histogram extends StatefulMetric nativeBucketsForPositiveValues = @@ -195,10 +206,10 @@ public class DataPoint implements DistributionDataPoint { private volatile long createdTimeMillis = System.currentTimeMillis(); private final Buffer buffer = new Buffer(); private volatile boolean resetDurationExpired = false; - private final ExemplarSampler exemplarSampler; + @Nullable private final ExemplarSampler exemplarSampler; private DataPoint() { - if (exemplarsEnabled) { + if (exemplarSamplerConfig != null) { exemplarSampler = new ExemplarSampler(exemplarSamplerConfig); } else { exemplarSampler = null; @@ -210,6 +221,16 @@ private DataPoint() { maybeScheduleNextReset(); } + @Override + public double getSum() { + return sum.sum(); + } + + @Override + public long getCount() { + return count.sum(); + } + @Override public void observe(double value) { if (Double.isNaN(value)) { @@ -219,7 +240,7 @@ public void observe(double value) { if (!buffer.append(value)) { doObserve(value, false); } - if (isExemplarsEnabled()) { + if (exemplarSampler != null) { exemplarSampler.observe(value); } } @@ -233,7 +254,7 @@ public void observeWithExemplar(double value, Labels labels) { if (!buffer.append(value)) { doObserve(value, false); } - if (isExemplarsEnabled()) { + if (exemplarSampler != null) { exemplarSampler.observeWithExemplar(value, labels); } } @@ -629,6 +650,11 @@ protected HistogramSnapshot collect(List labels, List metricD return new HistogramSnapshot(getMetadata(), data); } + @Override + public MetricType getMetricType() { + return MetricType.HISTOGRAM; + } + @Override protected DataPoint newDataPoint() { return new DataPoint(); @@ -674,14 +700,14 @@ public static class Builder extends StatefulMetric.BuilderDefault is no reset. */ public Builder nativeResetDuration(long duration, TimeUnit unit) { - // TODO: reset interval isn't tested yet if (duration <= 0) { throw new IllegalArgumentException(duration + ": value > 0 expected"); } - nativeResetDurationSeconds = unit.toSeconds(duration); + long seconds = unit.toSeconds(duration); + if (seconds == 0) { + throw new IllegalArgumentException( + duration + + " " + + unit + + ": duration must be at least 1 second. Sub-second durations are not supported."); + } + nativeResetDurationSeconds = seconds; return this; } diff --git a/prometheus-metrics-core/src/main/java/io/prometheus/metrics/core/metrics/Info.java b/prometheus-metrics-core/src/main/java/io/prometheus/metrics/core/metrics/Info.java index aac918943..011f0bb73 100644 --- a/prometheus-metrics-core/src/main/java/io/prometheus/metrics/core/metrics/Info.java +++ b/prometheus-metrics-core/src/main/java/io/prometheus/metrics/core/metrics/Info.java @@ -1,6 +1,7 @@ package io.prometheus.metrics.core.metrics; import io.prometheus.metrics.config.PrometheusProperties; +import io.prometheus.metrics.model.registry.MetricType; import io.prometheus.metrics.model.snapshots.InfoSnapshot; import io.prometheus.metrics.model.snapshots.Labels; import io.prometheus.metrics.model.snapshots.Unit; @@ -9,6 +10,7 @@ import java.util.List; import java.util.Set; import java.util.concurrent.CopyOnWriteArraySet; +import javax.annotation.Nullable; /** * Info metric. Example: @@ -104,6 +106,11 @@ public InfoSnapshot collect() { return new InfoSnapshot(getMetadata(), data); } + @Override + public MetricType getMetricType() { + return MetricType.INFO; + } + public static Builder builder() { return new Builder(PrometheusProperties.get()); } @@ -144,7 +151,7 @@ public Builder name(String name) { /** Throws an {@link UnsupportedOperationException} because Info metrics cannot have a unit. */ @Override - public Builder unit(Unit unit) { + public Builder unit(@Nullable Unit unit) { if (unit != null) { throw new UnsupportedOperationException("Info metrics cannot have a unit."); } diff --git a/prometheus-metrics-core/src/main/java/io/prometheus/metrics/core/metrics/MetricWithFixedMetadata.java b/prometheus-metrics-core/src/main/java/io/prometheus/metrics/core/metrics/MetricWithFixedMetadata.java index 9b213a85d..12c48c51d 100644 --- a/prometheus-metrics-core/src/main/java/io/prometheus/metrics/core/metrics/MetricWithFixedMetadata.java +++ b/prometheus-metrics-core/src/main/java/io/prometheus/metrics/core/metrics/MetricWithFixedMetadata.java @@ -1,12 +1,16 @@ package io.prometheus.metrics.core.metrics; import io.prometheus.metrics.config.PrometheusProperties; +import io.prometheus.metrics.model.snapshots.Label; import io.prometheus.metrics.model.snapshots.Labels; import io.prometheus.metrics.model.snapshots.MetricMetadata; import io.prometheus.metrics.model.snapshots.PrometheusNaming; import io.prometheus.metrics.model.snapshots.Unit; import java.util.Arrays; +import java.util.HashSet; import java.util.List; +import java.util.Set; +import javax.annotation.Nullable; /** * Almost all metrics have fixed metadata, i.e. the metric name is known when the metric is created. @@ -26,11 +30,15 @@ protected MetricWithFixedMetadata(Builder builder) { this.labelNames = Arrays.copyOf(builder.labelNames, builder.labelNames.length); } - protected MetricMetadata getMetadata() { + @Override + public MetricMetadata getMetadata() { return metadata; } - private String makeName(String name, Unit unit) { + private String makeName(@Nullable String name, @Nullable Unit unit) { + if (name == null) { + throw new IllegalArgumentException("Missing required field: name is null"); + } if (unit != null) { if (!name.endsWith("_" + unit) && !name.endsWith("." + unit)) { name += "_" + unit; @@ -44,12 +52,24 @@ public String getPrometheusName() { return metadata.getPrometheusName(); } + @Override + public Set getLabelNames() { + Set names = new HashSet<>(); + for (String labelName : labelNames) { + names.add(PrometheusNaming.prometheusName(labelName)); + } + for (Label label : constLabels) { + names.add(PrometheusNaming.prometheusName(label.getName())); + } + return names; + } + public abstract static class Builder, M extends MetricWithFixedMetadata> extends Metric.Builder { - protected String name; - private Unit unit; - private String help; + @Nullable private String name; + @Nullable private Unit unit; + @Nullable private String help; private String[] labelNames = new String[0]; protected Builder(List illegalLabelNames, PrometheusProperties properties) { @@ -65,7 +85,7 @@ public B name(String name) { return self(); } - public B unit(Unit unit) { + public B unit(@Nullable Unit unit) { this.unit = unit; return self(); } diff --git a/prometheus-metrics-core/src/main/java/io/prometheus/metrics/core/metrics/SlidingWindow.java b/prometheus-metrics-core/src/main/java/io/prometheus/metrics/core/metrics/SlidingWindow.java index 5360e3349..e56134d5d 100644 --- a/prometheus-metrics-core/src/main/java/io/prometheus/metrics/core/metrics/SlidingWindow.java +++ b/prometheus-metrics-core/src/main/java/io/prometheus/metrics/core/metrics/SlidingWindow.java @@ -15,8 +15,16 @@ *

It is implemented in a generic way so that 3rd party libraries can use it for implementing * sliding windows. * - *

TODO: The current implementation is {@code synchronized}. There is likely room for - * optimization. + *

Thread Safety: This class uses coarse-grained {@code synchronized} methods for + * simplicity and correctness. All public methods ({@link #current()} and {@link #observe(double)}) + * are synchronized, which ensures thread-safe access to the ring buffer and rotation logic. + * + *

Performance Note: The synchronized approach may cause contention under high-frequency + * observations. + * + *

However, given that Summary metrics are less commonly used (Histogram is generally preferred), + * and the observation frequency is typically lower than Counter increments, the current + * implementation provides an acceptable trade-off between simplicity and performance. */ public class SlidingWindow { diff --git a/prometheus-metrics-core/src/main/java/io/prometheus/metrics/core/metrics/StateSet.java b/prometheus-metrics-core/src/main/java/io/prometheus/metrics/core/metrics/StateSet.java index a04ddb274..740183f31 100644 --- a/prometheus-metrics-core/src/main/java/io/prometheus/metrics/core/metrics/StateSet.java +++ b/prometheus-metrics-core/src/main/java/io/prometheus/metrics/core/metrics/StateSet.java @@ -2,15 +2,16 @@ import static io.prometheus.metrics.model.snapshots.PrometheusNaming.prometheusName; -import io.prometheus.metrics.config.MetricsProperties; import io.prometheus.metrics.config.PrometheusProperties; import io.prometheus.metrics.core.datapoints.StateSetDataPoint; +import io.prometheus.metrics.model.registry.MetricType; import io.prometheus.metrics.model.snapshots.Labels; import io.prometheus.metrics.model.snapshots.StateSetSnapshot; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.stream.Stream; +import javax.annotation.Nullable; /** * StateSet metric. Example: @@ -53,14 +54,11 @@ public class StateSet extends StatefulMetric implements StateSetDataPoint { - private final boolean exemplarsEnabled; private final String[] names; - private StateSet(Builder builder, PrometheusProperties prometheusProperties) { + private StateSet(Builder builder, String[] names) { super(builder); - MetricsProperties[] properties = getMetricProperties(builder, prometheusProperties); - exemplarsEnabled = getConfigProperty(properties, MetricsProperties::getExemplarsEnabled); - this.names = builder.names; // builder.names is already a validated copy + this.names = names; for (String name : names) { if (this.getMetadata().getPrometheusName().equals(prometheusName(name))) { throw new IllegalArgumentException( @@ -87,6 +85,11 @@ protected StateSetSnapshot collect(List labels, List metricDa return new StateSetSnapshot(getMetadata(), data); } + @Override + public MetricType getMetricType() { + return MetricType.STATESET; + } + @Override public void setTrue(String state) { getNoLabels().setTrue(state); @@ -102,11 +105,6 @@ protected DataPoint newDataPoint() { return new DataPoint(); } - @Override - protected boolean isExemplarsEnabled() { - return exemplarsEnabled; - } - class DataPoint implements StateSetDataPoint { private final boolean[] values = new boolean[names.length]; @@ -144,7 +142,7 @@ public static Builder builder(PrometheusProperties config) { public static class Builder extends StatefulMetric.Builder { - private String[] names; + @Nullable private String[] names; private Builder(PrometheusProperties config) { super(Collections.emptyList(), config); @@ -170,7 +168,7 @@ public StateSet build() { if (names == null) { throw new IllegalStateException("State names are required when building a StateSet."); } - return new StateSet(this, properties); + return new StateSet(this, names); } @Override diff --git a/prometheus-metrics-core/src/main/java/io/prometheus/metrics/core/metrics/StatefulMetric.java b/prometheus-metrics-core/src/main/java/io/prometheus/metrics/core/metrics/StatefulMetric.java index d4be7ccc6..386e92292 100644 --- a/prometheus-metrics-core/src/main/java/io/prometheus/metrics/core/metrics/StatefulMetric.java +++ b/prometheus-metrics-core/src/main/java/io/prometheus/metrics/core/metrics/StatefulMetric.java @@ -10,8 +10,10 @@ import java.util.Collections; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.concurrent.ConcurrentHashMap; import java.util.function.Function; +import javax.annotation.Nullable; /** * There are two kinds of metrics: @@ -28,13 +30,14 @@ * because in Java synchronous and asynchronous usually refers to multi-threading, but * this has nothing to do with multi-threading. */ -abstract class StatefulMetric extends MetricWithFixedMetadata { +public abstract class StatefulMetric + extends MetricWithFixedMetadata { /** Map label values to data points. */ private final ConcurrentHashMap, T> data = new ConcurrentHashMap<>(); /** Shortcut for data.get(Collections.emptyList()) */ - private volatile T noLabels; + @Nullable private volatile T noLabels; protected StatefulMetric(Builder builder) { super(builder); @@ -156,23 +159,26 @@ protected T getNoLabels() { return noLabels; } + /** + * Metric properties in effect by order of precedence with the highest precedence first. If a + * {@code MetricProperties} is configured for the metric name it has higher precedence than the + * builder configuration. A special case is the setting {@link Builder#withoutExemplars()} via the + * builder, which cannot be overridden by any configuration. + */ protected MetricsProperties[] getMetricProperties( Builder builder, PrometheusProperties prometheusProperties) { + List properties = new ArrayList<>(); + if (Objects.equals(builder.exemplarsEnabled, false)) { + properties.add(MetricsProperties.builder().exemplarsEnabled(false).build()); + } String metricName = getMetadata().getName(); if (prometheusProperties.getMetricProperties(metricName) != null) { - return new MetricsProperties[] { - prometheusProperties.getMetricProperties(metricName), // highest precedence - builder.toProperties(), // second-highest precedence - prometheusProperties.getDefaultMetricProperties(), // third-highest precedence - builder.getDefaultProperties() // fallback - }; - } else { - return new MetricsProperties[] { - builder.toProperties(), // highest precedence - prometheusProperties.getDefaultMetricProperties(), // second-highest precedence - builder.getDefaultProperties() // fallback - }; + properties.add(prometheusProperties.getMetricProperties(metricName)); } + properties.add(builder.toProperties()); + properties.add(prometheusProperties.getDefaultMetricProperties()); + properties.add(builder.getDefaultProperties()); // fallback + return properties.toArray(new MetricsProperties[0]); } protected

P getConfigProperty( @@ -188,12 +194,10 @@ protected

P getConfigProperty( "Missing default config. This is a bug in the Prometheus metrics core library."); } - protected abstract boolean isExemplarsEnabled(); - abstract static class Builder, M extends StatefulMetric> extends MetricWithFixedMetadata.Builder { - protected Boolean exemplarsEnabled; + @Nullable protected Boolean exemplarsEnabled; protected Builder(List illegalLabelNames, PrometheusProperties config) { super(illegalLabelNames, config); diff --git a/prometheus-metrics-core/src/main/java/io/prometheus/metrics/core/metrics/Summary.java b/prometheus-metrics-core/src/main/java/io/prometheus/metrics/core/metrics/Summary.java index ddb3eb7f2..47b6e2a9c 100644 --- a/prometheus-metrics-core/src/main/java/io/prometheus/metrics/core/metrics/Summary.java +++ b/prometheus-metrics-core/src/main/java/io/prometheus/metrics/core/metrics/Summary.java @@ -1,10 +1,13 @@ package io.prometheus.metrics.core.metrics; +import static java.util.Objects.requireNonNull; + import io.prometheus.metrics.config.MetricsProperties; import io.prometheus.metrics.config.PrometheusProperties; import io.prometheus.metrics.core.datapoints.DistributionDataPoint; import io.prometheus.metrics.core.exemplars.ExemplarSampler; import io.prometheus.metrics.core.exemplars.ExemplarSamplerConfig; +import io.prometheus.metrics.model.registry.MetricType; import io.prometheus.metrics.model.snapshots.Exemplars; import io.prometheus.metrics.model.snapshots.Labels; import io.prometheus.metrics.model.snapshots.Quantile; @@ -16,6 +19,7 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.DoubleAdder; import java.util.concurrent.atomic.LongAdder; +import javax.annotation.Nullable; /** * Summary metric. Example: @@ -42,21 +46,25 @@ public class Summary extends StatefulMetric quantiles; // May be empty, but cannot be null. + private final long maxAgeSeconds; private final int ageBuckets; - private final boolean exemplarsEnabled; - private final ExemplarSamplerConfig exemplarSamplerConfig; + @Nullable private final ExemplarSamplerConfig exemplarSamplerConfig; private Summary(Builder builder, PrometheusProperties prometheusProperties) { super(builder); MetricsProperties[] properties = getMetricProperties(builder, prometheusProperties); - this.exemplarsEnabled = getConfigProperty(properties, MetricsProperties::getExemplarsEnabled); - this.quantiles = Collections.unmodifiableList(makeQuantiles(properties)); - this.maxAgeSeconds = getConfigProperty(properties, MetricsProperties::getSummaryMaxAgeSeconds); - this.ageBuckets = - getConfigProperty(properties, MetricsProperties::getSummaryNumberOfAgeBuckets); - this.exemplarSamplerConfig = - new ExemplarSamplerConfig(prometheusProperties.getExemplarProperties(), 4); + quantiles = Collections.unmodifiableList(makeQuantiles(properties)); + maxAgeSeconds = getConfigProperty(properties, MetricsProperties::getSummaryMaxAgeSeconds); + ageBuckets = getConfigProperty(properties, MetricsProperties::getSummaryNumberOfAgeBuckets); + boolean exemplarsEnabled = + getConfigProperty(properties, MetricsProperties::getExemplarsEnabled); + if (exemplarsEnabled) { + exemplarSamplerConfig = + new ExemplarSamplerConfig(prometheusProperties.getExemplarProperties(), 4); + } else { + exemplarSamplerConfig = null; + } } private List makeQuantiles(MetricsProperties[] properties) { @@ -78,8 +86,13 @@ private List makeQuantiles(MetricsProperties[] propertie } @Override - protected boolean isExemplarsEnabled() { - return exemplarsEnabled; + public double getSum() { + return getNoLabels().getSum(); + } + + @Override + public long getCount() { + return getNoLabels().getCount(); } @Override @@ -106,6 +119,11 @@ protected SummarySnapshot collect(List labels, List metricDat return new SummarySnapshot(getMetadata(), data); } + @Override + public MetricType getMetricType() { + return MetricType.SUMMARY; + } + @Override protected DataPoint newDataPoint() { return new DataPoint(); @@ -115,14 +133,16 @@ public class DataPoint implements DistributionDataPoint { private final LongAdder count = new LongAdder(); private final DoubleAdder sum = new DoubleAdder(); - private final SlidingWindow quantileValues; + @Nullable private final SlidingWindow quantileValues; private final Buffer buffer = new Buffer(); - private final ExemplarSampler exemplarSampler; + @Nullable private final ExemplarSampler exemplarSampler; private final long createdTimeMillis = System.currentTimeMillis(); private DataPoint() { - if (quantiles.size() > 0) { + if (quantiles.isEmpty()) { + quantileValues = null; + } else { CKMSQuantiles.Quantile[] quantilesArray = quantiles.toArray(new CKMSQuantiles.Quantile[0]); quantileValues = new SlidingWindow<>( @@ -131,16 +151,24 @@ private DataPoint() { CKMSQuantiles::insert, maxAgeSeconds, ageBuckets); - } else { - quantileValues = null; } - if (exemplarsEnabled) { + if (exemplarSamplerConfig != null) { exemplarSampler = new ExemplarSampler(exemplarSamplerConfig); } else { exemplarSampler = null; } } + @Override + public double getSum() { + return sum.sum(); + } + + @Override + public long getCount() { + return count.sum(); + } + @Override public void observe(double value) { if (Double.isNaN(value)) { @@ -149,7 +177,7 @@ public void observe(double value) { if (!buffer.append(value)) { doObserve(value); } - if (isExemplarsEnabled()) { + if (exemplarSampler != null) { exemplarSampler.observe(value); } } @@ -162,7 +190,7 @@ public void observeWithExemplar(double value, Labels labels) { if (!buffer.append(value)) { doObserve(value); } - if (isExemplarsEnabled()) { + if (exemplarSampler != null) { exemplarSampler.observeWithExemplar(value, labels); } } @@ -180,7 +208,13 @@ private void doObserve(double amount) { private SummarySnapshot.SummaryDataPointSnapshot collect(Labels labels) { return buffer.run( expectedCount -> count.sum() == expectedCount, - // TODO Exemplars (are hard-coded as empty in the line below) + // Note: Exemplars are currently hard-coded as empty for Summary metrics. + // While exemplars are sampled during observe() and observeWithExemplar() calls + // via the exemplarSampler field, they are not included in the snapshot to maintain + // consistency with the buffering mechanism. The buffer.run() ensures atomic + // collection of count, sum, and quantiles. Adding exemplars would require + // coordination between the buffer and exemplarSampler, which could impact + // performance. Consider using Histogram instead if exemplars are needed. () -> new SummarySnapshot.SummaryDataPointSnapshot( count.sum(), @@ -201,7 +235,8 @@ private Quantiles makeQuantiles() { for (int i = 0; i < getQuantiles().size(); i++) { CKMSQuantiles.Quantile quantile = getQuantiles().get(i); quantiles[i] = - new Quantile(quantile.quantile, quantileValues.current().get(quantile.quantile)); + new Quantile( + quantile.quantile, requireNonNull(quantileValues).current().get(quantile.quantile)); } return Quantiles.of(quantiles); } @@ -224,8 +259,8 @@ public static class Builder extends StatefulMetric.Builder quantiles = new ArrayList<>(); - private Long maxAgeSeconds; - private Integer ageBuckets; + @Nullable private Long maxAgeSeconds; + @Nullable private Integer ageBuckets; private Builder(PrometheusProperties properties) { super(Collections.singletonList("quantile"), properties); @@ -326,10 +361,15 @@ protected MetricsProperties toProperties() { quantileErrors[i] = this.quantiles.get(i).epsilon; } } - return MetricsProperties.builder() + MetricsProperties.Builder builder = MetricsProperties.builder(); + if (quantiles != null) { + builder.summaryQuantiles(quantiles); + } + if (quantileErrors != null) { + builder.summaryQuantileErrors(quantileErrors); + } + return builder .exemplarsEnabled(exemplarsEnabled) - .summaryQuantiles(quantiles) - .summaryQuantileErrors(quantileErrors) .summaryNumberOfAgeBuckets(ageBuckets) .summaryMaxAgeSeconds(maxAgeSeconds) .build(); diff --git a/prometheus-metrics-core/src/main/java/io/prometheus/metrics/core/metrics/SummaryWithCallback.java b/prometheus-metrics-core/src/main/java/io/prometheus/metrics/core/metrics/SummaryWithCallback.java index dbe61b2ce..fa823e68e 100644 --- a/prometheus-metrics-core/src/main/java/io/prometheus/metrics/core/metrics/SummaryWithCallback.java +++ b/prometheus-metrics-core/src/main/java/io/prometheus/metrics/core/metrics/SummaryWithCallback.java @@ -1,6 +1,7 @@ package io.prometheus.metrics.core.metrics; import io.prometheus.metrics.config.PrometheusProperties; +import io.prometheus.metrics.model.registry.MetricType; import io.prometheus.metrics.model.snapshots.Exemplars; import io.prometheus.metrics.model.snapshots.Quantiles; import io.prometheus.metrics.model.snapshots.SummarySnapshot; @@ -8,6 +9,7 @@ import java.util.Collections; import java.util.List; import java.util.function.Consumer; +import javax.annotation.Nullable; /** * Example: @@ -44,10 +46,10 @@ public interface Callback { private SummaryWithCallback(Builder builder) { super(builder); - this.callback = builder.callback; - if (callback == null) { + if (builder.callback == null) { throw new IllegalArgumentException("callback cannot be null"); } + this.callback = builder.callback; } @Override @@ -62,6 +64,11 @@ public SummarySnapshot collect() { return new SummarySnapshot(getMetadata(), dataPoints); } + @Override + public MetricType getMetricType() { + return MetricType.SUMMARY; + } + public static Builder builder() { return new Builder(PrometheusProperties.get()); } @@ -73,7 +80,7 @@ public static Builder builder(PrometheusProperties properties) { public static class Builder extends CallbackMetric.Builder { - private Consumer callback; + @Nullable private Consumer callback; public Builder callback(Consumer callback) { this.callback = callback; diff --git a/prometheus-metrics-core/src/test/java/io/prometheus/metrics/core/exemplars/ExemplarSamplerTest.java b/prometheus-metrics-core/src/test/java/io/prometheus/metrics/core/exemplars/ExemplarSamplerTest.java index 059929c22..8ba1370da 100644 --- a/prometheus-metrics-core/src/test/java/io/prometheus/metrics/core/exemplars/ExemplarSamplerTest.java +++ b/prometheus-metrics-core/src/test/java/io/prometheus/metrics/core/exemplars/ExemplarSamplerTest.java @@ -52,24 +52,24 @@ public void markCurrentSpanAsExemplar() {} } @Test - public void testCustomExemplarsBuckets() throws Exception { + void testCustomExemplarsBuckets() throws Exception { // TODO } private io.prometheus.metrics.tracer.common.SpanContext origContext; @BeforeEach - public void setUp() { + void setUp() { origContext = SpanContextSupplier.getSpanContext(); } @AfterEach - public void tearDown() { + void tearDown() { SpanContextSupplier.setSpanContext(origContext); } @Test - public void testIsSampled() throws Exception { + void testIsSampled() throws Exception { SpanContext context = new SpanContext(); context.isSampled = false; ExemplarSampler sampler = new ExemplarSampler(makeConfig(), context); @@ -79,7 +79,7 @@ public void testIsSampled() throws Exception { } @Test - public void testDefaultConfigHasFourExemplars() throws Exception { + void testDefaultConfigHasFourExemplars() throws Exception { ExemplarSampler sampler = new ExemplarSampler(makeConfig(), new SpanContext()); Thread.sleep(tick); // t = 1 tick sampler.observe(0.3); @@ -96,7 +96,7 @@ public void testDefaultConfigHasFourExemplars() throws Exception { } @Test - public void testEmptyBuckets() throws Exception { + void testEmptyBuckets() throws Exception { ExemplarSampler sampler = new ExemplarSampler(makeConfig(Double.POSITIVE_INFINITY), new SpanContext()); Thread.sleep(tick); // t = 1 tick @@ -108,7 +108,7 @@ public void testEmptyBuckets() throws Exception { } @Test - public void testDefaultExemplarsBuckets() throws Exception { + void testDefaultExemplarsBuckets() throws Exception { ExemplarSampler sampler = new ExemplarSampler( makeConfig(0.2, 0.4, 0.6, 0.8, 1.0, Double.POSITIVE_INFINITY), new SpanContext()); @@ -136,12 +136,12 @@ public void testDefaultExemplarsBuckets() throws Exception { } @Test - public void testCustomExemplarsNoBuckets() throws Exception { + void testCustomExemplarsNoBuckets() throws Exception { // TODO } @Test - public void testDefaultExemplarsNoBuckets() throws Exception { + void testDefaultExemplarsNoBuckets() throws Exception { ExemplarSampler sampler = new ExemplarSampler(makeConfig(), new SpanContext()); Scheduler.awaitInitialization(); Thread.sleep(tick); // t = 1 tick diff --git a/prometheus-metrics-core/src/test/java/io/prometheus/metrics/core/exemplars/SpanContextSupplierTest.java b/prometheus-metrics-core/src/test/java/io/prometheus/metrics/core/exemplars/SpanContextSupplierTest.java index 6c89b48a5..5ddfb2ad5 100644 --- a/prometheus-metrics-core/src/test/java/io/prometheus/metrics/core/exemplars/SpanContextSupplierTest.java +++ b/prometheus-metrics-core/src/test/java/io/prometheus/metrics/core/exemplars/SpanContextSupplierTest.java @@ -51,12 +51,12 @@ public void markCurrentSpanAsExemplar() {} ); @BeforeEach - public void setUp() { + void setUp() { origSpanContext = SpanContextSupplier.getSpanContext(); } @AfterEach - public void tearDown() { + void tearDown() { SpanContextSupplier.setSpanContext(origSpanContext); } @@ -66,7 +66,7 @@ public void tearDown() { * SpanContextSupplier}. */ @Test - public void testConstructorInjection() { + void testConstructorInjection() { ExemplarsProperties properties = ExemplarsProperties.builder().build(); ExemplarSamplerConfig config = new ExemplarSamplerConfig(properties, 1); ExemplarSampler exemplarSampler = new ExemplarSampler(config, spanContextA); @@ -86,7 +86,7 @@ public void testConstructorInjection() { * ExemplarSampler}). */ @Test - public void testUpdateSpanContext() throws InterruptedException { + void testUpdateSpanContext() throws InterruptedException { ExemplarSampler exemplarSampler = new ExemplarSampler(config); SpanContextSupplier.setSpanContext(spanContextB); diff --git a/prometheus-metrics-core/src/test/java/io/prometheus/metrics/core/metrics/CKMSQuantilesTest.java b/prometheus-metrics-core/src/test/java/io/prometheus/metrics/core/metrics/CKMSQuantilesTest.java index bd6b177a4..5a7fd7f48 100644 --- a/prometheus-metrics-core/src/test/java/io/prometheus/metrics/core/metrics/CKMSQuantilesTest.java +++ b/prometheus-metrics-core/src/test/java/io/prometheus/metrics/core/metrics/CKMSQuantilesTest.java @@ -20,13 +20,13 @@ class CKMSQuantilesTest { private final Quantile qMax = new Quantile(1.0, 0.00); @Test - public void testGetOnEmptyValues() { + void testGetOnEmptyValues() { CKMSQuantiles ckms = new CKMSQuantiles(q50, q95, q99); assertThat(Double.isNaN(ckms.get(q95.quantile))).isTrue(); } @Test - public void testGet() { + void testGet() { Random random = new Random(0); CKMSQuantiles ckms = new CKMSQuantiles(q50, q95, q99); List input = shuffledValues(100, random); @@ -37,7 +37,7 @@ public void testGet() { } @Test - public void testBatchInsert() { + void testBatchInsert() { Random random = new Random(1); testInsertBatch(1, 1, 100, random); testInsertBatch(1, 10, 100, random); @@ -87,7 +87,7 @@ private void testInsertBatch( } @Test - public void testGetWithAMillionElements() { + void testGetWithAMillionElements() { Random random = new Random(2); List input = shuffledValues(1000 * 1000, random); CKMSQuantiles ckms = new CKMSQuantiles(q50, q95, q99); @@ -99,7 +99,7 @@ public void testGetWithAMillionElements() { } @Test - public void testMin() { + void testMin() { Random random = new Random(3); List input = shuffledValues(1000, random); CKMSQuantiles ckms = new CKMSQuantiles(qMin); @@ -112,7 +112,7 @@ public void testMin() { } @Test - public void testMax() { + void testMax() { Random random = new Random(4); List input = shuffledValues(1000, random); CKMSQuantiles ckms = new CKMSQuantiles(qMax); @@ -125,7 +125,7 @@ public void testMax() { } @Test - public void testMinMax() { + void testMinMax() { Random random = new Random(5); List input = shuffledValues(1000, random); CKMSQuantiles ckms = new CKMSQuantiles(qMin, qMax); @@ -138,7 +138,7 @@ public void testMinMax() { } @Test - public void testMinAndOthers() { + void testMinAndOthers() { Random random = new Random(6); List input = shuffledValues(1000, random); CKMSQuantiles ckms = new CKMSQuantiles(q95, qMin); @@ -150,7 +150,7 @@ public void testMinAndOthers() { } @Test - public void testMaxAndOthers() { + void testMaxAndOthers() { Random random = new Random(7); List input = shuffledValues(10000, random); CKMSQuantiles ckms = new CKMSQuantiles(q50, q95, qMax); @@ -162,7 +162,7 @@ public void testMaxAndOthers() { } @Test - public void testMinMaxAndOthers() { + void testMinMaxAndOthers() { Random random = new Random(8); List input = shuffledValues(10000, random); CKMSQuantiles ckms = new CKMSQuantiles(qMin, q50, q95, q99, qMax); @@ -174,7 +174,7 @@ public void testMinMaxAndOthers() { } @Test - public void testExactQuantile() { + void testExactQuantile() { Random random = new Random(9); List input = shuffledValues(10000, random); CKMSQuantiles ckms = new CKMSQuantiles(new Quantile(0.95, 0)); @@ -187,7 +187,7 @@ public void testExactQuantile() { } @Test - public void testExactAndOthers() { + void testExactAndOthers() { Random random = new Random(10); List input = shuffledValues(10000, random); CKMSQuantiles ckms = new CKMSQuantiles(q50, new Quantile(0.95, 0), q99); @@ -200,7 +200,7 @@ public void testExactAndOthers() { } @Test - public void testExactAndMin() { + void testExactAndMin() { Random random = new Random(11); List input = shuffledValues(10000, random); CKMSQuantiles ckms = new CKMSQuantiles(qMin, q50, new Quantile(0.95, 0)); @@ -213,7 +213,7 @@ public void testExactAndMin() { } @Test - public void testMaxEpsilon() { + void testMaxEpsilon() { Random random = new Random(12); List input = shuffledValues(10000, random); // epsilon == 1 basically gives you random results, but it should still not throw an exception. @@ -225,7 +225,7 @@ public void testMaxEpsilon() { } @Test - public void testGetGaussian() { + void testGetGaussian() { RandomGenerator rand = new JDKRandomGenerator(); rand.setSeed(0); @@ -284,7 +284,7 @@ public void testGetGaussian() { } @Test - public void testIllegalArgumentException() { + void testIllegalArgumentException() { try { new Quantile(-1, 0); } catch (IllegalArgumentException e) { diff --git a/prometheus-metrics-core/src/test/java/io/prometheus/metrics/core/metrics/CounterTest.java b/prometheus-metrics-core/src/test/java/io/prometheus/metrics/core/metrics/CounterTest.java index b5ca3be15..b6d6779d7 100644 --- a/prometheus-metrics-core/src/test/java/io/prometheus/metrics/core/metrics/CounterTest.java +++ b/prometheus-metrics-core/src/test/java/io/prometheus/metrics/core/metrics/CounterTest.java @@ -5,10 +5,14 @@ import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.assertj.core.data.Offset.offset; +import io.prometheus.metrics.config.EscapingScheme; +import io.prometheus.metrics.config.MetricsProperties; +import io.prometheus.metrics.config.PrometheusProperties; import io.prometheus.metrics.core.exemplars.ExemplarSamplerConfigTestUtil; -import io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics; +import io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics; import io.prometheus.metrics.expositionformats.internal.PrometheusProtobufWriterImpl; import io.prometheus.metrics.expositionformats.internal.ProtobufUtil; +import io.prometheus.metrics.model.registry.PrometheusRegistry; import io.prometheus.metrics.model.snapshots.CounterSnapshot; import io.prometheus.metrics.model.snapshots.Exemplar; import io.prometheus.metrics.model.snapshots.Label; @@ -21,6 +25,8 @@ import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; class CounterTest { @@ -31,7 +37,7 @@ class CounterTest { private SpanContext origSpanContext; @BeforeEach - public void setUp() throws NoSuchFieldException, IllegalAccessException { + void setUp() throws NoSuchFieldException, IllegalAccessException { noLabels = Counter.builder().name("nolabels").build(); labels = Counter.builder().name("labels").help("help").unit(Unit.SECONDS).labelNames("l").build(); @@ -43,7 +49,7 @@ public void setUp() throws NoSuchFieldException, IllegalAccessException { } @AfterEach - public void tearDown() { + void tearDown() { SpanContextSupplier.setSpanContext(origSpanContext); } @@ -66,7 +72,7 @@ private int getNumberOfLabels(Counter counter) { } @Test - public void testIncrement() { + void testIncrement() { noLabels.inc(); assertThat(getValue(noLabels)).isCloseTo(1.0, offset(.001)); noLabels.inc(2); @@ -78,20 +84,20 @@ public void testIncrement() { } @Test - public void testNegativeIncrementFails() { + void testNegativeIncrementFails() { assertThatExceptionOfType(IllegalArgumentException.class) .isThrownBy(() -> noLabels.inc(-1)) .withMessage("Negative increment -1 is illegal for Counter metrics."); } @Test - public void testEmptyCountersHaveNoLabels() { + void testEmptyCountersHaveNoLabels() { assertThat(getNumberOfLabels(noLabels)).isOne(); assertThat(getNumberOfLabels(labels)).isZero(); } @Test - public void testLabels() { + void testLabels() { assertThat(getNumberOfLabels(labels)).isZero(); labels.labelValues("a").inc(); assertThat(getNumberOfLabels(labels)).isOne(); @@ -102,26 +108,25 @@ public void testLabels() { assertThat(getValue(labels, "l", "b")).isCloseTo(3.0, offset(.001)); } - @Test - public void testTotalStrippedFromName() { - for (String name : - new String[] { - "my_counter_total", "my.counter.total", - "my_counter_seconds_total", "my.counter.seconds.total", - "my_counter", "my.counter", - "my_counter_seconds", "my.counter.seconds" - }) { - Counter counter = Counter.builder().name(name).unit(Unit.SECONDS).build(); - Metrics.MetricFamily protobufData = - new PrometheusProtobufWriterImpl().convert(counter.collect()); - assertThat(ProtobufUtil.shortDebugString(protobufData)) - .isEqualTo( - "name: \"my_counter_seconds_total\" type: COUNTER metric { counter { value: 0.0 } }"); - } + @ParameterizedTest + @ValueSource( + strings = { + "my_counter_total", + "my_counter_seconds_total", + "my_counter", + "my_counter_seconds", + }) + void testTotalStrippedFromName(String name) { + Counter counter = Counter.builder().name(name).unit(Unit.SECONDS).build(); + Metrics.MetricFamily protobufData = + new PrometheusProtobufWriterImpl().convert(counter.collect(), EscapingScheme.ALLOW_UTF8); + assertThat(ProtobufUtil.shortDebugString(protobufData)) + .matches( + "^name: \"my_counter_seconds_total\" type: COUNTER metric \\{ counter \\{ value: 0.0 created_timestamp \\{ seconds: \\d+ nanos: \\d+ } } }$"); } @Test - public void testSnapshotComplete() { + void testSnapshotComplete() { long before = System.currentTimeMillis(); Counter counter = Counter.builder() @@ -176,7 +181,7 @@ public void testSnapshotComplete() { } @Test - public void testIncWithExemplar() throws Exception { + void testIncWithExemplar() throws Exception { noLabels.incWithExemplar(Labels.of("key", "value")); assertExemplar(noLabels, 1.0, "key", "value"); @@ -198,7 +203,7 @@ private void assertExemplar(Counter counter, double value, String... labels) { } @Test - public void testExemplarSampler() throws Exception { + void testExemplarSampler() throws Exception { Exemplar exemplar1 = Exemplar.builder().value(2.0).traceId("abc").spanId("123").build(); Exemplar exemplar2 = Exemplar.builder().value(1.0).traceId("def").spanId("456").build(); Exemplar exemplar3 = Exemplar.builder().value(1.0).traceId("123").spanId("abc").build(); @@ -316,14 +321,21 @@ void incWithExemplar2() { } @Test - public void testExemplarSamplerDisabled() { - Counter counter = - Counter.builder() - // .withExemplarSampler((inc, prev) -> {throw new RuntimeException("unexpected call to - // exemplar sampler");}) - .name("count_total") - .withoutExemplars() + void testExemplarSamplerDisabled() { + Counter counter = Counter.builder().name("count_total").withoutExemplars().build(); + counter.incWithExemplar(3.0, Labels.of("a", "b")); + assertThat(getData(counter).getExemplar()).isNull(); + counter.inc(2.0); + assertThat(getData(counter).getExemplar()).isNull(); + } + + @Test + void testExemplarSamplerDisabled_enabledByDefault() { + PrometheusProperties properties = + PrometheusProperties.builder() + .defaultMetricsProperties(MetricsProperties.builder().exemplarsEnabled(true).build()) .build(); + Counter counter = Counter.builder(properties).name("count_total").withoutExemplars().build(); counter.incWithExemplar(3.0, Labels.of("a", "b")); assertThat(getData(counter).getExemplar()).isNull(); counter.inc(2.0); @@ -331,7 +343,20 @@ public void testExemplarSamplerDisabled() { } @Test - public void testConstLabelsFirst() { + void testExemplarSamplerDisabledInBuilder_enabledByPropertiesOnMetric() { + PrometheusProperties properties = + PrometheusProperties.builder() + .putMetricProperty("count", MetricsProperties.builder().exemplarsEnabled(true).build()) + .build(); + Counter counter = Counter.builder(properties).name("count_total").withoutExemplars().build(); + counter.incWithExemplar(3.0, Labels.of("a", "b")); + assertThat(getData(counter).getExemplar()).isNull(); + counter.inc(2.0); + assertThat(getData(counter).getExemplar()).isNull(); + } + + @Test + void testConstLabelsFirst() { assertThatExceptionOfType(IllegalArgumentException.class) .isThrownBy( () -> @@ -343,7 +368,7 @@ public void testConstLabelsFirst() { } @Test - public void testConstLabelsSecond() { + void testConstLabelsSecond() { assertThatExceptionOfType(IllegalArgumentException.class) .isThrownBy( () -> @@ -353,4 +378,17 @@ public void testConstLabelsSecond() { .constLabels(Labels.of("const_a", "const_b")) .build()); } + + @Test + void testLabelNormalizationInRegistration() { + PrometheusRegistry registry = new PrometheusRegistry(); + + Counter.builder().name("requests").labelNames("request.count").register(registry); + + // request.count and request_count normalize to the same name + assertThatExceptionOfType(IllegalArgumentException.class) + .isThrownBy( + () -> Counter.builder().name("requests").labelNames("request_count").register(registry)) + .withMessageContaining("duplicate metric name with identical label schema"); + } } diff --git a/prometheus-metrics-core/src/test/java/io/prometheus/metrics/core/metrics/CounterWithCallbackTest.java b/prometheus-metrics-core/src/test/java/io/prometheus/metrics/core/metrics/CounterWithCallbackTest.java index 47c5f5c57..2907a8a02 100644 --- a/prometheus-metrics-core/src/test/java/io/prometheus/metrics/core/metrics/CounterWithCallbackTest.java +++ b/prometheus-metrics-core/src/test/java/io/prometheus/metrics/core/metrics/CounterWithCallbackTest.java @@ -13,7 +13,7 @@ class CounterWithCallbackTest { @Test - public void testCounter() { + void testCounter() { final AtomicInteger value = new AtomicInteger(1); List labelValues = Arrays.asList("v1", "v2"); CounterWithCallback counter = @@ -38,7 +38,7 @@ public void testCounter() { } @Test - public void testCounterNoCallback() { + void testCounterNoCallback() { assertThatExceptionOfType(IllegalArgumentException.class) .isThrownBy( () -> CounterWithCallback.builder().name("counter").labelNames("l1", "l2").build()); diff --git a/prometheus-metrics-core/src/test/java/io/prometheus/metrics/core/metrics/CustomBucketsHistogramTest.java b/prometheus-metrics-core/src/test/java/io/prometheus/metrics/core/metrics/CustomBucketsHistogramTest.java new file mode 100644 index 000000000..347f775cf --- /dev/null +++ b/prometheus-metrics-core/src/test/java/io/prometheus/metrics/core/metrics/CustomBucketsHistogramTest.java @@ -0,0 +1,470 @@ +package io.prometheus.metrics.core.metrics; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.data.Offset.offset; + +import io.prometheus.metrics.config.EscapingScheme; +import io.prometheus.metrics.expositionformats.OpenMetricsTextFormatWriter; +import io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics; +import io.prometheus.metrics.expositionformats.internal.PrometheusProtobufWriterImpl; +import io.prometheus.metrics.model.snapshots.ClassicHistogramBucket; +import io.prometheus.metrics.model.snapshots.ClassicHistogramBuckets; +import io.prometheus.metrics.model.snapshots.HistogramSnapshot; +import io.prometheus.metrics.model.snapshots.Labels; +import io.prometheus.metrics.model.snapshots.MetricSnapshots; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.List; +import java.util.stream.Collectors; +import org.junit.jupiter.api.Test; + +/** + * Comprehensive tests to verify that client_java supports native histograms with custom buckets + * (NHCB). + * + *

According to the Prometheus specification + * (https://prometheus.io/docs/specs/native_histograms/), native histograms with custom buckets + * (schema -53) are exposed as classic histograms with custom bucket boundaries. Prometheus servers + * can then convert these to NHCB upon ingestion when configured with + * convert_classic_histograms_to_nhcb. + * + *

These tests verify that: + * + *

    + *
  • Histograms with custom bucket boundaries can be created + *
  • Custom buckets are properly exposed in both text and protobuf formats + *
  • Both classic-only and dual (classic+native) histograms work with custom buckets + *
  • Various custom bucket configurations (linear, exponential, arbitrary) work correctly + *
+ * + *

See issue #1838 for more context. + */ +class CustomBucketsHistogramTest { + + @Test + void testCustomBucketsWithArbitraryBoundaries() { + // Create a histogram with arbitrary custom bucket boundaries + Histogram histogram = + Histogram.builder() + .name("http_request_duration_seconds") + .help("HTTP request duration with custom buckets") + .classicUpperBounds(0.01, 0.05, 0.1, 0.5, 1.0, 5.0, 10.0) + .build(); + + // Observe some values + histogram.observe(0.008); + histogram.observe(0.045); + histogram.observe(0.3); + histogram.observe(2.5); + histogram.observe(7.8); + + HistogramSnapshot snapshot = histogram.collect(); + HistogramSnapshot.HistogramDataPointSnapshot data = snapshot.getDataPoints().get(0); + + // Verify custom bucket boundaries are set correctly + List upperBounds = + data.getClassicBuckets().stream() + .map(ClassicHistogramBucket::getUpperBound) + .collect(Collectors.toList()); + + assertThat(upperBounds) + .containsExactly(0.01, 0.05, 0.1, 0.5, 1.0, 5.0, 10.0, Double.POSITIVE_INFINITY); + + // Verify observations are distributed correctly across buckets + // Note: counts are non-cumulative (count for that specific bucket only) + ClassicHistogramBuckets buckets = data.getClassicBuckets(); + assertThat(buckets.getCount(0)).isEqualTo(1); // <= 0.01: (0.008) + assertThat(buckets.getCount(1)).isEqualTo(1); // (0.01, 0.05]: (0.045) + assertThat(buckets.getCount(2)).isEqualTo(0); // (0.05, 0.1]: none + assertThat(buckets.getCount(3)).isEqualTo(1); // (0.1, 0.5]: (0.3) + assertThat(buckets.getCount(4)).isEqualTo(0); // (0.5, 1.0]: none + assertThat(buckets.getCount(5)).isEqualTo(1); // (1.0, 5.0]: (2.5) + assertThat(buckets.getCount(6)).isEqualTo(1); // (5.0, 10.0]: (7.8) + assertThat(buckets.getCount(7)).isEqualTo(0); // (10.0, +Inf]: none + + // Verify count and sum + assertThat(data.getCount()).isEqualTo(5); + assertThat(data.getSum()).isCloseTo(10.653, offset(0.01)); + } + + @Test + void testCustomBucketsWithLinearBoundaries() { + // Create a histogram with linear custom bucket boundaries + // This represents a use case where equal-width buckets are needed + Histogram histogram = + Histogram.builder() + .name("queue_size") + .help("Queue size with linear buckets") + .classicLinearUpperBounds(10.0, 10.0, 10) // start=10, width=10, count=10 + .build(); + + // Observe some values + for (int i = 5; i <= 95; i += 10) { + histogram.observe(i); + } + + HistogramSnapshot snapshot = histogram.collect(); + HistogramSnapshot.HistogramDataPointSnapshot data = snapshot.getDataPoints().get(0); + + // Verify linear bucket boundaries + List upperBounds = + data.getClassicBuckets().stream() + .map(ClassicHistogramBucket::getUpperBound) + .collect(Collectors.toList()); + + assertThat(upperBounds) + .containsExactly( + 10.0, 20.0, 30.0, 40.0, 50.0, 60.0, 70.0, 80.0, 90.0, 100.0, Double.POSITIVE_INFINITY); + + // Verify observations + assertThat(data.getCount()).isEqualTo(10); + } + + @Test + void testCustomBucketsWithExponentialBoundaries() { + // Create a histogram with exponential custom bucket boundaries + // This is useful for metrics that span multiple orders of magnitude + Histogram histogram = + Histogram.builder() + .name("response_size_bytes") + .help("Response size with exponential buckets") + .classicExponentialUpperBounds(100.0, 10.0, 5) // start=100, factor=10, count=5 + .build(); + + // Observe some values across different magnitudes + histogram.observe(50); + histogram.observe(500); + histogram.observe(5000); + histogram.observe(50000); + histogram.observe(500000); + + HistogramSnapshot snapshot = histogram.collect(); + HistogramSnapshot.HistogramDataPointSnapshot data = snapshot.getDataPoints().get(0); + + // Verify exponential bucket boundaries + List upperBounds = + data.getClassicBuckets().stream() + .map(ClassicHistogramBucket::getUpperBound) + .collect(Collectors.toList()); + + assertThat(upperBounds) + .containsExactly(100.0, 1000.0, 10000.0, 100000.0, 1000000.0, Double.POSITIVE_INFINITY); + + // Verify observations + assertThat(data.getCount()).isEqualTo(5); + } + + @Test + void testCustomBucketsClassicOnlyHistogram() { + // Verify that custom buckets work with classic-only histograms + Histogram histogram = + Histogram.builder() + .name("test_classic_only") + .help("Classic-only histogram with custom buckets") + .classicOnly() + .classicUpperBounds(1.0, 5.0, 10.0) + .build(); + + histogram.observe(2.0); + histogram.observe(7.0); + + HistogramSnapshot snapshot = histogram.collect(); + HistogramSnapshot.HistogramDataPointSnapshot data = snapshot.getDataPoints().get(0); + + // Verify it's a classic-only histogram + assertThat(data.getNativeSchema()).isEqualTo(HistogramSnapshot.CLASSIC_HISTOGRAM); + + // Verify custom buckets + List upperBounds = + data.getClassicBuckets().stream() + .map(ClassicHistogramBucket::getUpperBound) + .collect(Collectors.toList()); + + assertThat(upperBounds).containsExactly(1.0, 5.0, 10.0, Double.POSITIVE_INFINITY); + } + + @Test + void testCustomBucketsDualModeHistogram() { + // Verify that custom buckets work with dual-mode (classic+native) histograms + // This is the default mode and most relevant for NHCB support + Histogram histogram = + Histogram.builder() + .name("test_dual_mode") + .help("Dual-mode histogram with custom buckets") + .classicUpperBounds(0.1, 1.0, 10.0) + .build(); + + histogram.observe(0.5); + histogram.observe(5.0); + + HistogramSnapshot snapshot = histogram.collect(); + HistogramSnapshot.HistogramDataPointSnapshot data = snapshot.getDataPoints().get(0); + + // Verify it has both classic and native representations + assertThat(data.getClassicBuckets().size()).isGreaterThan(0); + assertThat(data.getNativeSchema()).isNotEqualTo(HistogramSnapshot.CLASSIC_HISTOGRAM); + + // Verify custom classic buckets + List upperBounds = + data.getClassicBuckets().stream() + .map(ClassicHistogramBucket::getUpperBound) + .collect(Collectors.toList()); + + assertThat(upperBounds).containsExactly(0.1, 1.0, 10.0, Double.POSITIVE_INFINITY); + + // Verify native histogram is also populated + long nativeTotalCount = + data.getNativeBucketsForPositiveValues().stream() + .mapToLong(bucket -> bucket.getCount()) + .sum(); + assertThat(nativeTotalCount).isEqualTo(2); + } + + @Test + void testCustomBucketsTextFormatOutput() throws IOException { + // Verify that custom buckets are correctly serialized in text format + Histogram histogram = + Histogram.builder() + .name("test_custom_buckets") + .help("Test histogram with custom buckets") + .classicUpperBounds(0.5, 1.0, 2.0) + .build(); + + histogram.observe(0.3); + histogram.observe(0.7); + histogram.observe(1.5); + + ByteArrayOutputStream out = new ByteArrayOutputStream(); + OpenMetricsTextFormatWriter writer = new OpenMetricsTextFormatWriter(false, true); + writer.write(out, MetricSnapshots.of(histogram.collect()), EscapingScheme.ALLOW_UTF8); + + String output = out.toString(StandardCharsets.UTF_8); + + // Verify the output contains the custom bucket boundaries + assertThat(output).contains("le=\"0.5\""); + assertThat(output).contains("le=\"1.0\""); + assertThat(output).contains("le=\"2.0\""); + assertThat(output).contains("le=\"+Inf\""); + + // Verify bucket counts + assertThat(output).containsPattern("le=\"0.5\".*1"); // 1 observation <= 0.5 + assertThat(output).containsPattern("le=\"1.0\".*2"); // 2 observations <= 1.0 + assertThat(output).containsPattern("le=\"2.0\".*3"); // 3 observations <= 2.0 + assertThat(output).containsPattern("le=\"\\+Inf\".*3"); // 3 observations total + } + + @Test + void testCustomBucketsProtobufFormatOutput() { + // Verify that custom buckets are correctly serialized in Prometheus protobuf format + Histogram histogram = + Histogram.builder() + .name("test_custom_buckets_protobuf") + .help("Test histogram with custom buckets for protobuf") + .classicUpperBounds(1.0, 5.0, 10.0) + .build(); + + histogram.observe(0.5); + histogram.observe(3.0); + histogram.observe(7.0); + + HistogramSnapshot snapshot = histogram.collect(); + Metrics.MetricFamily metricFamily = + new PrometheusProtobufWriterImpl().convert(snapshot, EscapingScheme.ALLOW_UTF8); + + assertThat(metricFamily).isNotNull(); + assertThat(metricFamily.getName()).isEqualTo("test_custom_buckets_protobuf"); + assertThat(metricFamily.getType()).isEqualTo(Metrics.MetricType.HISTOGRAM); + + Metrics.Histogram protoHistogram = metricFamily.getMetric(0).getHistogram(); + + // Verify classic buckets in protobuf + assertThat(protoHistogram.getBucketCount()).isEqualTo(4); // 3 custom + +Inf + + // Verify bucket upper bounds + assertThat(protoHistogram.getBucket(0).getUpperBound()).isEqualTo(1.0); + assertThat(protoHistogram.getBucket(1).getUpperBound()).isEqualTo(5.0); + assertThat(protoHistogram.getBucket(2).getUpperBound()).isEqualTo(10.0); + assertThat(protoHistogram.getBucket(3).getUpperBound()).isEqualTo(Double.POSITIVE_INFINITY); + + // Verify bucket counts (cumulative) + assertThat(protoHistogram.getBucket(0).getCumulativeCount()).isEqualTo(1); // <= 1.0 + assertThat(protoHistogram.getBucket(1).getCumulativeCount()).isEqualTo(2); // <= 5.0 + assertThat(protoHistogram.getBucket(2).getCumulativeCount()).isEqualTo(3); // <= 10.0 + assertThat(protoHistogram.getBucket(3).getCumulativeCount()).isEqualTo(3); // +Inf + + // Verify native histogram fields are also present (for dual-mode) + assertThat(protoHistogram.hasSchema()).isTrue(); + assertThat(protoHistogram.getSchema()).isNotEqualTo(HistogramSnapshot.CLASSIC_HISTOGRAM); + } + + @Test + void testCustomBucketsWithNegativeValues() { + // Verify that custom buckets work correctly with negative values + Histogram histogram = + Histogram.builder() + .name("temperature_celsius") + .help("Temperature readings with custom buckets") + .classicUpperBounds(-20.0, -10.0, 0.0, 10.0, 20.0, 30.0) + .build(); + + histogram.observe(-15.0); + histogram.observe(-5.0); + histogram.observe(5.0); + histogram.observe(15.0); + histogram.observe(25.0); + + HistogramSnapshot snapshot = histogram.collect(); + HistogramSnapshot.HistogramDataPointSnapshot data = snapshot.getDataPoints().get(0); + + // Verify bucket boundaries + List upperBounds = + data.getClassicBuckets().stream() + .map(ClassicHistogramBucket::getUpperBound) + .collect(Collectors.toList()); + + assertThat(upperBounds) + .containsExactly(-20.0, -10.0, 0.0, 10.0, 20.0, 30.0, Double.POSITIVE_INFINITY); + + // Verify observations are distributed correctly + // Note: counts are non-cumulative + ClassicHistogramBuckets buckets = data.getClassicBuckets(); + assertThat(buckets.getCount(0)).isEqualTo(0); // <= -20: none + assertThat(buckets.getCount(1)).isEqualTo(1); // (-20, -10]: (-15.0) + assertThat(buckets.getCount(2)).isEqualTo(1); // (-10, 0]: (-5.0) + assertThat(buckets.getCount(3)).isEqualTo(1); // (0, 10]: (5.0) + assertThat(buckets.getCount(4)).isEqualTo(1); // (10, 20]: (15.0) + assertThat(buckets.getCount(5)).isEqualTo(1); // (20, 30]: (25.0) + + assertThat(data.getCount()).isEqualTo(5); + } + + @Test + void testCustomBucketsWithLabels() { + // Verify that custom buckets work correctly with labeled histograms + Histogram histogram = + Histogram.builder() + .name("api_request_duration_seconds") + .help("API request duration with custom buckets") + .classicUpperBounds(0.01, 0.1, 1.0, 10.0) + .labelNames("method", "endpoint") + .build(); + + histogram.labelValues("GET", "/users").observe(0.05); + histogram.labelValues("GET", "/users").observe(0.5); + histogram.labelValues("POST", "/users").observe(2.0); + + HistogramSnapshot snapshot = histogram.collect(); + + // Verify we have 2 data points (one for each unique label combination) + assertThat(snapshot.getDataPoints()).hasSize(2); + + // Verify both data points have the correct custom buckets + for (HistogramSnapshot.HistogramDataPointSnapshot data : snapshot.getDataPoints()) { + List upperBounds = + data.getClassicBuckets().stream() + .map(ClassicHistogramBucket::getUpperBound) + .collect(Collectors.toList()); + + assertThat(upperBounds).containsExactly(0.01, 0.1, 1.0, 10.0, Double.POSITIVE_INFINITY); + } + + // Verify GET /users data point + HistogramSnapshot.HistogramDataPointSnapshot getData = + getData(histogram, "method", "GET", "endpoint", "/users"); + + assertThat(getData.getCount()).isEqualTo(2); + + // Verify POST /users data point + HistogramSnapshot.HistogramDataPointSnapshot postData = + getData(histogram, "method", "POST", "endpoint", "/users"); + + assertThat(postData.getCount()).isEqualTo(1); + } + + private HistogramSnapshot.HistogramDataPointSnapshot getData( + Histogram histogram, String... labels) { + return histogram.collect().getDataPoints().stream() + .filter(d -> d.getLabels().equals(Labels.of(labels))) + .findAny() + .orElseThrow(() -> new RuntimeException("histogram with labels not found")); + } + + @Test + void testCustomBucketsBoundaryEdgeCases() { + // Test edge cases: observations exactly on bucket boundaries + Histogram histogram = + Histogram.builder() + .name("test_boundaries") + .help("Test bucket boundary edge cases") + .classicUpperBounds(1.0, 5.0, 10.0) + .build(); + + // Observe values exactly on the boundaries + histogram.observe(1.0); + histogram.observe(5.0); + histogram.observe(10.0); + + HistogramSnapshot snapshot = histogram.collect(); + HistogramSnapshot.HistogramDataPointSnapshot data = snapshot.getDataPoints().get(0); + + // Values on boundaries should be included in their respective buckets + // Buckets are inclusive of upper bound + // Note: counts are non-cumulative + ClassicHistogramBuckets buckets = data.getClassicBuckets(); + assertThat(buckets.getCount(0)).isEqualTo(1); // <= 1.0: (1.0) + assertThat(buckets.getCount(1)).isEqualTo(1); // (1.0, 5.0]: (5.0) + assertThat(buckets.getCount(2)).isEqualTo(1); // (5.0, 10.0]: (10.0) + + assertThat(data.getCount()).isEqualTo(3); + } + + @Test + void testCustomBucketsFineBoundaries() { + // Test with very fine-grained custom bucket boundaries + // This simulates a use case where precise bucket boundaries are needed + Histogram histogram = + Histogram.builder() + .name("precise_measurement") + .help("Histogram with fine-grained custom buckets") + .classicUpperBounds(0.001, 0.002, 0.003, 0.004, 0.005, 0.006, 0.007, 0.008, 0.009, 0.01) + .build(); + + histogram.observe(0.0015); + histogram.observe(0.0045); + histogram.observe(0.0075); + + HistogramSnapshot snapshot = histogram.collect(); + HistogramSnapshot.HistogramDataPointSnapshot data = snapshot.getDataPoints().get(0); + + // Verify fine-grained buckets are set correctly + List upperBounds = + data.getClassicBuckets().stream() + .map(ClassicHistogramBucket::getUpperBound) + .collect(Collectors.toList()); + + assertThat(upperBounds) + .containsExactly( + 0.001, + 0.002, + 0.003, + 0.004, + 0.005, + 0.006, + 0.007, + 0.008, + 0.009, + 0.01, + Double.POSITIVE_INFINITY); + + // Verify observations are in correct buckets + // Note: counts are non-cumulative + ClassicHistogramBuckets buckets = data.getClassicBuckets(); + assertThat(buckets.getCount(0)).isEqualTo(0); // <= 0.001: none + assertThat(buckets.getCount(1)).isEqualTo(1); // (0.001, 0.002]: (0.0015) + assertThat(buckets.getCount(4)).isEqualTo(1); // (0.004, 0.005]: (0.0045) + assertThat(buckets.getCount(7)).isEqualTo(1); // (0.007, 0.008]: (0.0075) + + assertThat(data.getCount()).isEqualTo(3); + } +} diff --git a/prometheus-metrics-core/src/test/java/io/prometheus/metrics/core/metrics/GaugeTest.java b/prometheus-metrics-core/src/test/java/io/prometheus/metrics/core/metrics/GaugeTest.java index 41d692a80..be25711c7 100644 --- a/prometheus-metrics-core/src/test/java/io/prometheus/metrics/core/metrics/GaugeTest.java +++ b/prometheus-metrics-core/src/test/java/io/prometheus/metrics/core/metrics/GaugeTest.java @@ -4,6 +4,8 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.data.Offset.offset; +import io.prometheus.metrics.config.MetricsProperties; +import io.prometheus.metrics.config.PrometheusProperties; import io.prometheus.metrics.core.datapoints.Timer; import io.prometheus.metrics.core.exemplars.ExemplarSamplerConfigTestUtil; import io.prometheus.metrics.model.snapshots.Exemplar; @@ -25,14 +27,14 @@ class GaugeTest { private SpanContext origSpanContext; @BeforeEach - public void setUp() { + void setUp() { noLabels = Gauge.builder().name("nolabels").build(); labels = Gauge.builder().name("labels").labelNames("l").build(); origSpanContext = SpanContextSupplier.getSpanContext(); } @AfterEach - public void tearDown() { + void tearDown() { SpanContextSupplier.setSpanContext(origSpanContext); } @@ -48,7 +50,7 @@ private double getValue(Gauge gauge, String... labels) { } @Test - public void testIncrement() { + void testIncrement() { noLabels.inc(); assertThat(getValue(noLabels)).isCloseTo(1.0, offset(.001)); noLabels.inc(2); @@ -60,7 +62,7 @@ public void testIncrement() { } @Test - public void testDecrement() { + void testDecrement() { noLabels.dec(); assertThat(getValue(noLabels)).isCloseTo(-1.0, offset(.001)); noLabels.dec(2); @@ -72,7 +74,7 @@ public void testDecrement() { } @Test - public void testSet() { + void testSet() { noLabels.set(42); assertThat(getValue(noLabels)).isCloseTo(42, offset(.001)); noLabels.set(7); @@ -90,12 +92,12 @@ public void testTimer() throws InterruptedException { } @Test - public void noLabelsDefaultZeroValue() { + void noLabelsDefaultZeroValue() { assertThat(getValue(noLabels)).isCloseTo(0.0, offset(.001)); } @Test - public void testLabels() { + void testLabels() { labels.labelValues("a").inc(); labels.labelValues("b").inc(3); assertThat(getValue(labels, "l", "a")).isCloseTo(1.0, offset(.001)); @@ -103,7 +105,7 @@ public void testLabels() { } @Test - public void testExemplarSampler() throws Exception { + void testExemplarSampler() throws Exception { Exemplar exemplar1 = Exemplar.builder().value(2.0).traceId("abc").spanId("123").build(); Exemplar exemplar2 = Exemplar.builder().value(6.5).traceId("def").spanId("456").build(); Exemplar exemplar3 = Exemplar.builder().value(7.0).traceId("123").spanId("abc").build(); @@ -220,11 +222,24 @@ void decWithExemplar() { } @Test - public void testExemplarSamplerDisabled() { + void testExemplarSamplerDisabled() { Gauge gauge = Gauge.builder().name("test").withoutExemplars().build(); gauge.setWithExemplar(3.0, Labels.of("a", "b")); assertThat(getData(gauge).getExemplar()).isNull(); gauge.inc(2.0); assertThat(getData(gauge).getExemplar()).isNull(); } + + @Test + void testExemplarSamplerDisabledByBuilder_enabledByPropertiesOnMetric() { + PrometheusProperties properties = + PrometheusProperties.builder() + .putMetricProperty("test", MetricsProperties.builder().exemplarsEnabled(true).build()) + .build(); + Gauge gauge = Gauge.builder(properties).name("test").withoutExemplars().build(); + gauge.setWithExemplar(3.0, Labels.of("a", "b")); + assertThat(getData(gauge).getExemplar()).isNull(); + gauge.inc(2.0); + assertThat(getData(gauge).getExemplar()).isNull(); + } } diff --git a/prometheus-metrics-core/src/test/java/io/prometheus/metrics/core/metrics/GaugeWithCallbackTest.java b/prometheus-metrics-core/src/test/java/io/prometheus/metrics/core/metrics/GaugeWithCallbackTest.java index 92a2d605e..f89fd70d2 100644 --- a/prometheus-metrics-core/src/test/java/io/prometheus/metrics/core/metrics/GaugeWithCallbackTest.java +++ b/prometheus-metrics-core/src/test/java/io/prometheus/metrics/core/metrics/GaugeWithCallbackTest.java @@ -13,7 +13,7 @@ class GaugeWithCallbackTest { @Test - public void testGauge() { + void testGauge() { final AtomicInteger value = new AtomicInteger(1); List labelValues = Arrays.asList("v1", "v2"); GaugeWithCallback gauge = @@ -38,7 +38,7 @@ public void testGauge() { } @Test - public void testGaugeNoCallback() { + void testGaugeNoCallback() { assertThatExceptionOfType(IllegalArgumentException.class) .isThrownBy(() -> GaugeWithCallback.builder().name("gauge").labelNames("l1", "l2").build()); } diff --git a/prometheus-metrics-core/src/test/java/io/prometheus/metrics/core/metrics/HistogramTest.java b/prometheus-metrics-core/src/test/java/io/prometheus/metrics/core/metrics/HistogramTest.java index 7cfa1b83e..c67c9dd42 100644 --- a/prometheus-metrics-core/src/test/java/io/prometheus/metrics/core/metrics/HistogramTest.java +++ b/prometheus-metrics-core/src/test/java/io/prometheus/metrics/core/metrics/HistogramTest.java @@ -1,22 +1,22 @@ package io.prometheus.metrics.core.metrics; import static io.prometheus.metrics.core.metrics.TestUtil.assertExemplarEquals; +import static java.util.concurrent.TimeUnit.SECONDS; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.assertj.core.data.Offset.offset; +import static org.awaitility.Awaitility.await; +import io.prometheus.metrics.config.EscapingScheme; +import io.prometheus.metrics.config.MetricsProperties; +import io.prometheus.metrics.config.PrometheusProperties; import io.prometheus.metrics.core.datapoints.DistributionDataPoint; import io.prometheus.metrics.core.exemplars.ExemplarSamplerConfigTestUtil; import io.prometheus.metrics.expositionformats.OpenMetricsTextFormatWriter; -import io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics; +import io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics; import io.prometheus.metrics.expositionformats.internal.PrometheusProtobufWriterImpl; import io.prometheus.metrics.expositionformats.internal.ProtobufUtil; -import io.prometheus.metrics.model.snapshots.ClassicHistogramBucket; -import io.prometheus.metrics.model.snapshots.Exemplar; -import io.prometheus.metrics.model.snapshots.Exemplars; -import io.prometheus.metrics.model.snapshots.HistogramSnapshot; -import io.prometheus.metrics.model.snapshots.Labels; -import io.prometheus.metrics.model.snapshots.MetricSnapshots; +import io.prometheus.metrics.model.snapshots.*; import io.prometheus.metrics.tracer.common.SpanContext; import io.prometheus.metrics.tracer.initializer.SpanContextSupplier; import java.io.ByteArrayOutputStream; @@ -53,12 +53,12 @@ class HistogramTest { private SpanContext origSpanContext; @BeforeEach - public void setUp() { + void setUp() { origSpanContext = SpanContextSupplier.getSpanContext(); } @AfterEach - public void tearDown() { + void tearDown() { SpanContextSupplier.setSpanContext(origSpanContext); } @@ -89,7 +89,8 @@ private void run() throws NoSuchFieldException, IllegalAccessException { } } Metrics.MetricFamily protobufData = - new PrometheusProtobufWriterImpl().convert(histogram.collect()); + new PrometheusProtobufWriterImpl() + .convert(histogram.collect(), EscapingScheme.ALLOW_UTF8); String expectedWithMetadata = "name: \"test\" type: HISTOGRAM metric { histogram { " + expected + " } }"; assertThat(ProtobufUtil.shortDebugString(protobufData)) @@ -100,7 +101,7 @@ private void run() throws NoSuchFieldException, IllegalAccessException { /** Test cases copied from histogram_test.go in client_golang. */ @Test - public void testGolangTests() throws NoSuchFieldException, IllegalAccessException { + void testGolangTests() throws NoSuchFieldException, IllegalAccessException { GolangTestCase[] testCases = new GolangTestCase[] { new GolangTestCase( @@ -764,7 +765,7 @@ public void testGolangTests() throws NoSuchFieldException, IllegalAccessExceptio /** Additional tests that are not part of client_golang's test suite. */ @Test - public void testAdditional() throws NoSuchFieldException, IllegalAccessException { + void testAdditional() throws NoSuchFieldException, IllegalAccessException { GolangTestCase[] testCases = new GolangTestCase[] { new GolangTestCase( @@ -798,7 +799,7 @@ public void testAdditional() throws NoSuchFieldException, IllegalAccessException *

This test is ported from client_golang's TestGetLe(). */ @Test - public void testNativeBucketIndexToUpperBound() + void testNativeBucketIndexToUpperBound() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException { int[] indexes = new int[] {-1, 0, 1, 512, 513, -1, 0, 1, 1024, 1025, -1, 0, 1, 4096, 4097}; int[] schemas = new int[] {-1, -1, -1, -1, -1, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2}; @@ -840,7 +841,7 @@ public void testNativeBucketIndexToUpperBound() * findBucketIndex() */ @Test - public void testFindBucketIndex() + void testFindBucketIndex() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException { Random rand = new Random(); Method findBucketIndex = @@ -885,7 +886,7 @@ public void testFindBucketIndex() } @Test - public void testDefaults() throws IOException { + void testDefaults() throws IOException { Histogram histogram = Histogram.builder().name("test").build(); histogram.observe(0.5); HistogramSnapshot snapshot = histogram.collect(); @@ -944,18 +945,19 @@ public void testDefaults() throws IOException { """; // protobuf - Metrics.MetricFamily protobufData = new PrometheusProtobufWriterImpl().convert(snapshot); + Metrics.MetricFamily protobufData = + new PrometheusProtobufWriterImpl().convert(snapshot, EscapingScheme.ALLOW_UTF8); assertThat(ProtobufUtil.shortDebugString(protobufData)).isEqualTo(expectedProtobuf); // text ByteArrayOutputStream out = new ByteArrayOutputStream(); OpenMetricsTextFormatWriter writer = new OpenMetricsTextFormatWriter(false, true); - writer.write(out, MetricSnapshots.of(snapshot)); + writer.write(out, MetricSnapshots.of(snapshot), EscapingScheme.ALLOW_UTF8); assertThat(out).hasToString(expectedTextFormat); } @Test - public void testExemplarsClassicHistogram() throws Exception { + void testExemplarsClassicHistogram() throws Exception { SpanContext spanContext = new SpanContext() { int callCount = 0; @@ -1020,10 +1022,18 @@ public void markCurrentSpanAsExemplar() {} assertThat(getExemplar(snapshot, Double.POSITIVE_INFINITY, "path", "/hello")).isNull(); assertThat(getExemplar(snapshot, Double.POSITIVE_INFINITY, "path", "/world")).isNull(); - Thread.sleep(sampleIntervalMillis + 1); + waitForSampleInterval(sampleIntervalMillis); histogram.labelValues("/hello").observe(4.5); histogram.labelValues("/world").observe(4.5); + await() + .atMost(2, SECONDS) + .until( + () -> { + HistogramSnapshot s = histogram.collect(); + return getExemplar(s, Double.POSITIVE_INFINITY, "path", "/hello") != null + && getExemplar(s, Double.POSITIVE_INFINITY, "path", "/world") != null; + }); snapshot = histogram.collect(); assertExemplarEquals(ex1a, getExemplar(snapshot, 1.0, "path", "/hello")); assertExemplarEquals(ex1b, getExemplar(snapshot, 1.0, "path", "/world")); @@ -1036,16 +1046,28 @@ public void markCurrentSpanAsExemplar() {} assertExemplarEquals(ex2a, getExemplar(snapshot, Double.POSITIVE_INFINITY, "path", "/hello")); assertExemplarEquals(ex2b, getExemplar(snapshot, Double.POSITIVE_INFINITY, "path", "/world")); - Thread.sleep(sampleIntervalMillis + 1); + waitForSampleInterval(sampleIntervalMillis); histogram.labelValues("/hello").observe(1.5); histogram.labelValues("/world").observe(1.5); - Thread.sleep(sampleIntervalMillis + 1); + waitForSampleInterval(sampleIntervalMillis); histogram.labelValues("/hello").observe(2.5); histogram.labelValues("/world").observe(2.5); - Thread.sleep(sampleIntervalMillis + 1); + waitForSampleInterval(sampleIntervalMillis); histogram.labelValues("/hello").observe(3.5); histogram.labelValues("/world").observe(3.5); + await() + .atMost(2, SECONDS) + .until( + () -> { + HistogramSnapshot s = histogram.collect(); + return getExemplar(s, 2.0, "path", "/hello") != null + && getExemplar(s, 2.0, "path", "/world") != null + && getExemplar(s, 3.0, "path", "/hello") != null + && getExemplar(s, 3.0, "path", "/world") != null + && getExemplar(s, 4.0, "path", "/hello") != null + && getExemplar(s, 4.0, "path", "/world") != null; + }); snapshot = histogram.collect(); assertExemplarEquals(ex1a, getExemplar(snapshot, 1.0, "path", "/hello")); assertExemplarEquals(ex1b, getExemplar(snapshot, 1.0, "path", "/world")); @@ -1072,15 +1094,32 @@ public void markCurrentSpanAsExemplar() {} "span_id", "spanId-11")) .build(); - Thread.sleep(sampleIntervalMillis + 1); + waitForSampleInterval(sampleIntervalMillis); histogram .labelValues("/hello") .observeWithExemplar(3.4, Labels.of("key1", "value1", "key2", "value2")); + await() + .atMost(2, SECONDS) + .until( + () -> { + Exemplar actual = getExemplar(histogram.collect(), 4.0, "path", "/hello"); + return actual != null && Math.abs(actual.getValue() - 3.4) < 0.00001; + }); snapshot = histogram.collect(); // custom exemplars have preference, so the automatic exemplar is replaced assertExemplarEquals(custom, getExemplar(snapshot, 4.0, "path", "/hello")); } + /** Waits for the exemplar sampler's rate limit window so the next observation is accepted. */ + private static void waitForSampleInterval(long sampleIntervalMillis) { + try { + Thread.sleep(2 * sampleIntervalMillis); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw new AssertionError("Interrupted while waiting for sample interval", e); + } + } + private Exemplar getExemplar(HistogramSnapshot snapshot, double le, String... labels) { HistogramSnapshot.HistogramDataPointSnapshot data = snapshot.getDataPoints().stream() @@ -1099,7 +1138,7 @@ private Exemplar getExemplar(HistogramSnapshot snapshot, double le, String... la } @Test - public void testCustomExemplarsClassicHistogram() + void testCustomExemplarsClassicHistogram() throws InterruptedException, NoSuchFieldException, IllegalAccessException { // TODO: This was copied from the old simpleclient, can probably be refactored. @@ -1167,7 +1206,7 @@ private void assertExemplar(Histogram histogram, double value, String... labels) } @Test - public void testExemplarsNativeHistogram() throws NoSuchFieldException, IllegalAccessException { + void testExemplarsNativeHistogram() throws NoSuchFieldException, IllegalAccessException { SpanContext spanContext = new SpanContext() { @@ -1236,13 +1275,13 @@ public void markCurrentSpanAsExemplar() {} } @Test - public void testIllegalLabelName() { + void testIllegalLabelName() { assertThatExceptionOfType(IllegalArgumentException.class) .isThrownBy(() -> Histogram.builder().name("test").labelNames("label", "le")); } @Test - public void testIllegalLabelNameConstLabels() { + void testIllegalLabelNameConstLabels() { assertThatExceptionOfType(IllegalArgumentException.class) .isThrownBy( () -> @@ -1252,31 +1291,25 @@ public void testIllegalLabelNameConstLabels() { } @Test - public void testIllegalLabelNamePrefix() { + void testIllegalLabelNamePrefix() { assertThatExceptionOfType(IllegalArgumentException.class) .isThrownBy(() -> Histogram.builder().name("test").labelNames("__hello")); } @Test - public void testIllegalName() { - assertThatExceptionOfType(IllegalArgumentException.class) - .isThrownBy(() -> Histogram.builder().name("my_namespace/server.durations")); - } - - @Test - public void testNoName() { + void testNoName() { assertThatExceptionOfType(IllegalArgumentException.class) .isThrownBy(() -> Histogram.builder().build()); } @Test - public void testNullName() { + void testNullName() { assertThatExceptionOfType(NullPointerException.class) .isThrownBy(() -> Histogram.builder().name(null)); } @Test - public void testDuplicateClassicBuckets() { + void testDuplicateClassicBuckets() { Histogram histogram = Histogram.builder().name("test").classicUpperBounds(0, 3, 17, 3, 21).build(); List upperBounds = @@ -1288,7 +1321,7 @@ public void testDuplicateClassicBuckets() { } @Test - public void testUnsortedBuckets() { + void testUnsortedBuckets() { Histogram histogram = Histogram.builder().name("test").classicUpperBounds(0.2, 0.1).build(); List upperBounds = getData(histogram).getClassicBuckets().stream() @@ -1298,7 +1331,7 @@ public void testUnsortedBuckets() { } @Test - public void testEmptyBuckets() { + void testEmptyBuckets() { Histogram histogram = Histogram.builder().name("test").classicUpperBounds().build(); List upperBounds = getData(histogram).getClassicBuckets().stream() @@ -1308,7 +1341,7 @@ public void testEmptyBuckets() { } @Test - public void testBucketsIncludePositiveInfinity() { + void testBucketsIncludePositiveInfinity() { Histogram histogram = Histogram.builder() .name("test") @@ -1322,7 +1355,7 @@ public void testBucketsIncludePositiveInfinity() { } @Test - public void testLinearBuckets() { + void testLinearBuckets() { Histogram histogram = Histogram.builder().name("test").classicLinearUpperBounds(0.1, 0.1, 10).build(); List upperBounds = @@ -1336,7 +1369,7 @@ public void testLinearBuckets() { } @Test - public void testExponentialBuckets() { + void testExponentialBuckets() { Histogram histogram = Histogram.builder().classicExponentialUpperBounds(2, 2.5, 3).name("test").build(); List upperBounds = @@ -1347,14 +1380,14 @@ public void testExponentialBuckets() { } @Test - public void testBucketsIncludeNaN() { + void testBucketsIncludeNaN() { assertThatExceptionOfType(RuntimeException.class) .isThrownBy( () -> Histogram.builder().name("test").classicUpperBounds(0.01, 0.1, 1.0, Double.NaN)); } @Test - public void testNoLabelsDefaultZeroValue() { + void testNoLabelsDefaultZeroValue() { Histogram noLabels = Histogram.builder().name("test").build(); assertThat(getBucket(noLabels, 0.005).getCount()).isZero(); assertThat(getData(noLabels).getCount()).isZero(); @@ -1369,9 +1402,11 @@ private ClassicHistogramBucket getBucket(Histogram histogram, double le, String. } @Test - public void testObserve() { + void testObserve() { Histogram noLabels = Histogram.builder().name("test").build(); noLabels.observe(2); + assertThat(noLabels.getCount()).isOne(); + assertThat(noLabels.getSum()).isCloseTo(2.0, offset(.0)); assertThat(getData(noLabels).getCount()).isOne(); assertThat(getData(noLabels).getSum()).isCloseTo(2.0, offset(.0)); assertThat(getBucket(noLabels, 1).getCount()).isZero(); @@ -1388,7 +1423,7 @@ public void testObserve() { @Test // See https://github.com/prometheus/client_java/issues/646 - public void testNegativeAmount() { + void testNegativeAmount() { Histogram histogram = Histogram.builder() .name("histogram") @@ -1414,7 +1449,7 @@ public void testNegativeAmount() { } @Test - public void testBoundaryConditions() { + void testBoundaryConditions() { Histogram histogram = Histogram.builder().name("test").build(); histogram.observe(2.5); assertThat(getBucket(histogram, 1).getCount()).isZero(); @@ -1429,7 +1464,26 @@ public void testBoundaryConditions() { } @Test - public void testObserveWithLabels() { + void testExemplarsDisabledInBuilder() { + Histogram histogram = Histogram.builder().withoutExemplars().name("test").build(); + histogram.observeWithExemplar(2.5, Labels.EMPTY); + assertThat(getData(histogram).getExemplars().size()).isZero(); + } + + @Test + void testExemplarsDisabledInBuilder_enabledByPropertiesOnMetric() { + PrometheusProperties properties = + PrometheusProperties.builder() + .putMetricProperty("test", MetricsProperties.builder().exemplarsEnabled(true).build()) + .defaultMetricsProperties(MetricsProperties.builder().build()) + .build(); + Histogram histogram = Histogram.builder(properties).withoutExemplars().name("test").build(); + histogram.observeWithExemplar(2.5, Labels.EMPTY); + assertThat(getData(histogram).getExemplars().size()).isZero(); + } + + @Test + void testObserveWithLabels() { Histogram histogram = Histogram.builder() .name("test") @@ -1437,6 +1491,9 @@ public void testObserveWithLabels() { .labelNames("path", "status") .build(); histogram.labelValues("/hello", "200").observe(0.11); + DistributionDataPoint distributionDataPoint = histogram.labelValues("/hello", "200"); + assertThat(distributionDataPoint.getCount()).isOne(); + assertThat(distributionDataPoint.getSum()).isCloseTo(0.11, offset(.0)); histogram.labelValues("/hello", "200").observe(0.2); histogram.labelValues("/hello", "500").observe(0.19); HistogramSnapshot.HistogramDataPointSnapshot data200 = @@ -1457,7 +1514,7 @@ public void testObserveWithLabels() { } @Test - public void testObserveMultithreaded() + void testObserveMultithreaded() throws InterruptedException, ExecutionException, TimeoutException { // Hard to test concurrency, but let's run a couple of observations in parallel and assert none // gets lost. @@ -1508,6 +1565,70 @@ public void testObserveMultithreaded() assertThat(executor.awaitTermination(5, TimeUnit.SECONDS)).isTrue(); } + @Test + void testNativeResetDuration() { + // Test that nativeResetDuration can be configured without error and the histogram + // functions correctly. The reset duration schedules internal reset behavior but + // is not directly observable in the snapshot. + Histogram histogram = + Histogram.builder() + .name("test_histogram_with_reset") + .nativeOnly() + .nativeResetDuration(24, TimeUnit.HOURS) + .build(); + + histogram.observe(1.0); + histogram.observe(2.0); + histogram.observe(3.0); + + HistogramSnapshot snapshot = histogram.collect(); + assertThat(snapshot.getDataPoints()).hasSize(1); + HistogramSnapshot.HistogramDataPointSnapshot dataPoint = snapshot.getDataPoints().get(0); + assertThat(dataPoint.hasNativeHistogramData()).isTrue(); + assertThat(dataPoint.getCount()).isEqualTo(3); + assertThat(dataPoint.getSum()).isEqualTo(6.0); + } + + @Test + void testNativeResetDurationNegativeValue() { + assertThatExceptionOfType(IllegalArgumentException.class) + .isThrownBy( + () -> + Histogram.builder() + .name("test_histogram") + .nativeOnly() + .nativeResetDuration(-1, TimeUnit.HOURS) + .build()) + .withMessageContaining("value > 0 expected"); + } + + @Test + void testNativeResetDurationZeroValue() { + assertThatExceptionOfType(IllegalArgumentException.class) + .isThrownBy( + () -> + Histogram.builder() + .name("test_histogram") + .nativeOnly() + .nativeResetDuration(0, TimeUnit.HOURS) + .build()) + .withMessageContaining("value > 0 expected"); + } + + @Test + void testNativeResetDurationSubSecond() { + // Sub-second durations should be rejected as they truncate to 0 seconds + assertThatExceptionOfType(IllegalArgumentException.class) + .isThrownBy( + () -> + Histogram.builder() + .name("test_histogram") + .nativeOnly() + .nativeResetDuration(500, TimeUnit.MILLISECONDS) + .build()) + .withMessageContaining("duration must be at least 1 second"); + } + private HistogramSnapshot.HistogramDataPointSnapshot getData( Histogram histogram, String... labels) { return histogram.collect().getDataPoints().stream() diff --git a/prometheus-metrics-core/src/test/java/io/prometheus/metrics/core/metrics/InfoTest.java b/prometheus-metrics-core/src/test/java/io/prometheus/metrics/core/metrics/InfoTest.java index f09988091..0681f73e1 100644 --- a/prometheus-metrics-core/src/test/java/io/prometheus/metrics/core/metrics/InfoTest.java +++ b/prometheus-metrics-core/src/test/java/io/prometheus/metrics/core/metrics/InfoTest.java @@ -3,8 +3,9 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; +import io.prometheus.metrics.config.EscapingScheme; import io.prometheus.metrics.expositionformats.OpenMetricsTextFormatWriter; -import io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics; +import io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics; import io.prometheus.metrics.expositionformats.internal.PrometheusProtobufWriterImpl; import io.prometheus.metrics.expositionformats.internal.ProtobufUtil; import io.prometheus.metrics.model.snapshots.Labels; @@ -14,31 +15,26 @@ import java.io.IOException; import java.nio.charset.StandardCharsets; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; class InfoTest { - @Test - public void testInfoStrippedFromName() { - for (String name : - new String[] { - "jvm.runtime", "jvm_runtime", - "jvm.runtime.info", "jvm_runtime_info" - }) { - for (String labelName : new String[] {"my.key", "my_key"}) { - Info info = Info.builder().name(name).labelNames(labelName).build(); - info.addLabelValues("value"); - Metrics.MetricFamily protobufData = - new PrometheusProtobufWriterImpl().convert(info.collect()); - assertThat(ProtobufUtil.shortDebugString(protobufData)) - .isEqualTo( - "name: \"jvm_runtime_info\" type: GAUGE metric { label { name: \"my_key\" value:" - + " \"value\" } gauge { value: 1.0 } }"); - } - } + @ParameterizedTest + @ValueSource(strings = {"jvm.runtime", "jvm.runtime.info"}) + public void testInfoStrippedFromName(String name) { + Info info = Info.builder().name(name).labelNames("my.key").build(); + info.addLabelValues("value"); + Metrics.MetricFamily protobufData = + new PrometheusProtobufWriterImpl().convert(info.collect(), EscapingScheme.ALLOW_UTF8); + assertThat(ProtobufUtil.shortDebugString(protobufData)) + .isEqualTo( + "name: \"jvm.runtime_info\" type: GAUGE metric { label { name: \"my.key\" value:" + + " \"value\" } gauge { value: 1.0 } }"); } @Test - public void testAddAndRemove() { + void testAddAndRemove() { Info info = Info.builder().name("test_info").labelNames("a", "b").build(); assertThat(info.collect().getDataPoints()).isEmpty(); info.addLabelValues("val1", "val2"); @@ -62,7 +58,7 @@ public void testAddAndRemove() { } @Test - public void testSet() throws IOException { + void testSet() throws IOException { Info info = Info.builder() .name("target_info") @@ -92,7 +88,7 @@ public void testSet() throws IOException { } @Test - public void testConstLabelsOnly() throws IOException { + void testConstLabelsOnly() throws IOException { Info info = Info.builder() .name("target_info") @@ -110,14 +106,14 @@ void unit() { } @Test - public void testConstLabelsDuplicate1() { + void testConstLabelsDuplicate1() { assertThatExceptionOfType(IllegalArgumentException.class) .isThrownBy( () -> Info.builder().constLabels(Labels.of("a_1", "val1")).labelNames("a.1").build()); } @Test - public void testConstLabelsDuplicate2() { + void testConstLabelsDuplicate2() { assertThatExceptionOfType(IllegalArgumentException.class) .isThrownBy( () -> Info.builder().labelNames("a_1").constLabels(Labels.of("a.1", "val1")).build()); @@ -126,7 +122,8 @@ public void testConstLabelsDuplicate2() { private void assertTextFormat(String expected, Info info) throws IOException { OpenMetricsTextFormatWriter writer = new OpenMetricsTextFormatWriter(true, true); ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); - writer.write(outputStream, MetricSnapshots.of(info.collect())); + writer.write( + outputStream, MetricSnapshots.of(info.collect()), EscapingScheme.UNDERSCORE_ESCAPING); String result = outputStream.toString(StandardCharsets.UTF_8.name()); if (!result.contains(expected)) { throw new AssertionError(expected + " is not contained in the following output:\n" + result); diff --git a/prometheus-metrics-core/src/test/java/io/prometheus/metrics/core/metrics/SlidingWindowTest.java b/prometheus-metrics-core/src/test/java/io/prometheus/metrics/core/metrics/SlidingWindowTest.java index d85e5637c..dea8fdfbd 100644 --- a/prometheus-metrics-core/src/test/java/io/prometheus/metrics/core/metrics/SlidingWindowTest.java +++ b/prometheus-metrics-core/src/test/java/io/prometheus/metrics/core/metrics/SlidingWindowTest.java @@ -43,7 +43,7 @@ void assertValues(double... expectedValues) { private final long timeBetweenRotateMillis = maxAgeSeconds * 1000 / ageBuckets + 2; @BeforeEach - public void setUp() { + void setUp() { startTime = System.currentTimeMillis(); currentTimeMillis.set(startTime); ringBuffer = @@ -57,7 +57,7 @@ public void setUp() { } @Test - public void testRotate() { + void testRotate() { for (int i = 0; i < ageBuckets; i++) { currentTimeMillis.addAndGet(timeBetweenRotateMillis); ringBuffer.observe(1.0); @@ -76,7 +76,7 @@ public void testRotate() { } @Test - public void testMultiRotate() { + void testMultiRotate() { ringBuffer.observe(1.0); currentTimeMillis.addAndGet(2 * timeBetweenRotateMillis); // 2/5 of max aqe ringBuffer.observe(2.0); diff --git a/prometheus-metrics-core/src/test/java/io/prometheus/metrics/core/metrics/StateSetTest.java b/prometheus-metrics-core/src/test/java/io/prometheus/metrics/core/metrics/StateSetTest.java index 7d466335b..318013edf 100644 --- a/prometheus-metrics-core/src/test/java/io/prometheus/metrics/core/metrics/StateSetTest.java +++ b/prometheus-metrics-core/src/test/java/io/prometheus/metrics/core/metrics/StateSetTest.java @@ -27,7 +27,7 @@ public String toString() { } @Test - public void testEnumStateSet() { + void testEnumStateSet() { StateSet stateSet = StateSet.builder() .name("feature_flags") @@ -51,7 +51,7 @@ public void testEnumStateSet() { } @Test - public void testDefaultFalse() { + void testDefaultFalse() { StateSet stateSet = StateSet.builder().name("test").states("state1", "state2", "state3").build(); assertThat(getData(stateSet).size()).isEqualTo(3); @@ -83,7 +83,7 @@ private StateSetSnapshot.StateSetDataPointSnapshot getData(StateSet stateSet, St } @Test - public void testStatesCannotBeEmpty() { + void testStatesCannotBeEmpty() { assertThatExceptionOfType(IllegalStateException.class) .isThrownBy(() -> StateSet.builder().name("invalid").build()); } diff --git a/prometheus-metrics-core/src/test/java/io/prometheus/metrics/core/metrics/StatefulMetricTest.java b/prometheus-metrics-core/src/test/java/io/prometheus/metrics/core/metrics/StatefulMetricTest.java index 00aa53bbd..f940d505d 100644 --- a/prometheus-metrics-core/src/test/java/io/prometheus/metrics/core/metrics/StatefulMetricTest.java +++ b/prometheus-metrics-core/src/test/java/io/prometheus/metrics/core/metrics/StatefulMetricTest.java @@ -12,7 +12,7 @@ class StatefulMetricTest { @Test - public void testLabelRemoveWhileCollecting() throws Exception { + void testLabelRemoveWhileCollecting() throws Exception { Counter counter = Counter.builder().name("test").labelNames("label1", "label2").build(); Field data = counter.getClass().getSuperclass().getDeclaredField("data"); data.setAccessible(true); @@ -61,7 +61,7 @@ public void testLabelRemoveIf() throws Exception { } @Test - public void testClear() { + void testClear() { Counter counter = Counter.builder().name("test").labelNames("label1", "label2").build(); counter.labelValues("a", "b").inc(3.0); counter.labelValues("c", "d").inc(3.0); @@ -76,7 +76,7 @@ public void testClear() { } @Test - public void testClearNoLabels() { + void testClearNoLabels() { Counter counter = Counter.builder().name("test").build(); counter.inc(); assertThat(counter.collect().getDataPoints()).hasSize(1); @@ -95,7 +95,7 @@ public void testClearNoLabels() { } @Test - public void testNullLabel() { + void testNullLabel() { Counter counter = Counter.builder().name("test").labelNames("l1", "l2").build(); assertThatExceptionOfType(IllegalArgumentException.class) .isThrownBy(() -> counter.labelValues("l1", null)) diff --git a/prometheus-metrics-core/src/test/java/io/prometheus/metrics/core/metrics/SummaryTest.java b/prometheus-metrics-core/src/test/java/io/prometheus/metrics/core/metrics/SummaryTest.java index 642340e13..b6c045229 100644 --- a/prometheus-metrics-core/src/test/java/io/prometheus/metrics/core/metrics/SummaryTest.java +++ b/prometheus-metrics-core/src/test/java/io/prometheus/metrics/core/metrics/SummaryTest.java @@ -5,6 +5,7 @@ import static org.assertj.core.api.Assertions.fail; import static org.assertj.core.data.Offset.offset; +import io.prometheus.metrics.core.datapoints.DistributionDataPoint; import io.prometheus.metrics.core.datapoints.Timer; import io.prometheus.metrics.model.registry.PrometheusRegistry; import io.prometheus.metrics.model.snapshots.Label; @@ -29,7 +30,7 @@ class SummaryTest { private Summary noLabelsAndQuantiles; @BeforeEach - public void setUp() { + void setUp() { registry = new PrometheusRegistry(); noLabels = Summary.builder().name("nolabels").unit(Unit.SECONDS).help("help").register(registry); @@ -62,8 +63,10 @@ public void setUp() { } @Test - public void testObserve() { + void testObserve() { noLabels.observe(2); + assertThat(noLabels.getCount()).isOne(); + assertThat(noLabels.getSum()).isCloseTo(2.0, offset(.0)); assertThat(getCount(noLabels, Labels.EMPTY)).isOne(); assertThat(getSum(noLabels, Labels.EMPTY)).isCloseTo(2.0, offset(.001)); noLabels.observe(3); @@ -71,6 +74,9 @@ public void testObserve() { assertThat(getSum(noLabels, Labels.EMPTY)).isCloseTo(5.0, offset(.001)); withLabels.labelValues(label.getValue()).observe(4); + DistributionDataPoint distributionDataPoint = withLabels.labelValues(label.getValue()); + assertThat(distributionDataPoint.getCount()).isOne(); + assertThat(distributionDataPoint.getSum()).isCloseTo(4.0, offset(.0)); assertThat(getCount(withLabels, labels)).isOne(); assertThat(getSum(withLabels, labels)).isCloseTo(4.0, offset(.001)); @@ -80,7 +86,7 @@ public void testObserve() { } @Test - public void testNegativeAmount() { + void testNegativeAmount() { noLabels.observe(-1); noLabels.observe(-3); assertThat(getCount(noLabels, Labels.EMPTY)).isEqualTo(2); @@ -88,7 +94,7 @@ public void testNegativeAmount() { } @Test - public void testQuantiles() { + void testQuantiles() { int nSamples = 1000000; // simulate one million samples for (int i = 1; i <= nSamples; i++) { @@ -113,7 +119,7 @@ public void testQuantiles() { } @Test - public void testMaxAge() throws InterruptedException { + void testMaxAge() throws InterruptedException { Summary summary = Summary.builder() .quantile(0.99, 0.001) @@ -132,7 +138,7 @@ public void testMaxAge() throws InterruptedException { } @Test - public void testTimer() { + void testTimer() { int result = noLabels.time(() -> 123); assertThat(result).isEqualTo(123); assertThat(getCount(noLabels, Labels.EMPTY)).isOne(); @@ -144,31 +150,31 @@ public void testTimer() { } @Test - public void noLabelsDefaultZeroValue() { + void noLabelsDefaultZeroValue() { assertThat(getCount(noLabels, Labels.EMPTY)).isZero(); assertThat(getSum(noLabels, Labels.EMPTY)).isCloseTo(0.0, offset(.001)); } @Test - public void testBuilderInvalidNumberOfAgeBuckets() { + void testBuilderInvalidNumberOfAgeBuckets() { assertThatExceptionOfType(IllegalArgumentException.class) .isThrownBy(() -> Summary.builder().name("name").numberOfAgeBuckets(-1).build()); } @Test - public void testBuilderInvalidMaxAge() { + void testBuilderInvalidMaxAge() { assertThatExceptionOfType(IllegalArgumentException.class) .isThrownBy(() -> Summary.builder().name("name").maxAgeSeconds(-1).build()); } @Test - public void testBuilderInvalidQuantile() { + void testBuilderInvalidQuantile() { assertThatExceptionOfType(IllegalArgumentException.class) .isThrownBy(() -> Summary.builder().name("name").quantile(42).build()); } @Test - public void testBuilderInvalidQuantileError() { + void testBuilderInvalidQuantileError() { assertThatExceptionOfType(IllegalArgumentException.class) .isThrownBy(() -> Summary.builder().name("name").quantile(0.5, 20).build()); } diff --git a/prometheus-metrics-core/src/test/java/io/prometheus/metrics/core/metrics/SummaryWithCallbackTest.java b/prometheus-metrics-core/src/test/java/io/prometheus/metrics/core/metrics/SummaryWithCallbackTest.java index 929efc597..aacb83193 100644 --- a/prometheus-metrics-core/src/test/java/io/prometheus/metrics/core/metrics/SummaryWithCallbackTest.java +++ b/prometheus-metrics-core/src/test/java/io/prometheus/metrics/core/metrics/SummaryWithCallbackTest.java @@ -15,7 +15,7 @@ class SummaryWithCallbackTest { @Test - public void testGauge() { + void testGauge() { final AtomicInteger count = new AtomicInteger(1); final AtomicInteger sum = new AtomicInteger(1); final Quantiles quantiles = Quantiles.of(new Quantile(0.5, 10)); @@ -49,7 +49,7 @@ public void testGauge() { } @Test - public void testSummaryNoCallback() { + void testSummaryNoCallback() { assertThatExceptionOfType(IllegalArgumentException.class) .isThrownBy( () -> SummaryWithCallback.builder().name("summary").labelNames("l1", "l2").build()); diff --git a/prometheus-metrics-core/src/test/java/io/prometheus/metrics/core/metrics/TestUtil.java b/prometheus-metrics-core/src/test/java/io/prometheus/metrics/core/metrics/TestUtil.java index 8d9a5bd0f..10ce1db94 100644 --- a/prometheus-metrics-core/src/test/java/io/prometheus/metrics/core/metrics/TestUtil.java +++ b/prometheus-metrics-core/src/test/java/io/prometheus/metrics/core/metrics/TestUtil.java @@ -6,7 +6,7 @@ import io.prometheus.metrics.model.snapshots.Exemplar; import io.prometheus.metrics.model.snapshots.Label; -class TestUtil { +public class TestUtil { public static void assertExemplarEquals(Exemplar expected, Exemplar actual) { // ignore timestamp diff --git a/prometheus-metrics-exporter-common/pom.xml b/prometheus-metrics-exporter-common/pom.xml index e5eb7fb1f..a471c0b9b 100644 --- a/prometheus-metrics-exporter-common/pom.xml +++ b/prometheus-metrics-exporter-common/pom.xml @@ -6,7 +6,7 @@ io.prometheus client_java - 1.4.0-SNAPSHOT + 1.6.0-SNAPSHOT prometheus-metrics-exporter-common @@ -38,6 +38,14 @@ ${project.version} runtime + + + + io.prometheus + prometheus-metrics-core + ${project.version} + test + diff --git a/prometheus-metrics-exporter-common/src/main/java/io/prometheus/metrics/exporter/common/PrometheusHttpRequest.java b/prometheus-metrics-exporter-common/src/main/java/io/prometheus/metrics/exporter/common/PrometheusHttpRequest.java index 6dc632cd9..4483fc83f 100644 --- a/prometheus-metrics-exporter-common/src/main/java/io/prometheus/metrics/exporter/common/PrometheusHttpRequest.java +++ b/prometheus-metrics-exporter-common/src/main/java/io/prometheus/metrics/exporter/common/PrometheusHttpRequest.java @@ -5,6 +5,7 @@ import java.net.URLDecoder; import java.util.ArrayList; import java.util.Enumeration; +import javax.annotation.Nullable; public interface PrometheusHttpRequest extends PrometheusScrapeRequest { @@ -18,6 +19,7 @@ public interface PrometheusHttpRequest extends PrometheusScrapeRequest { String getMethod(); /** See {@code jakarta.servlet.http.HttpServletRequest.getHeader(String)} */ + @Nullable default String getHeader(String name) { Enumeration headers = getHeaders(name); if (headers == null || !headers.hasMoreElements()) { @@ -28,6 +30,7 @@ default String getHeader(String name) { } /** See {@code jakarta.servlet.ServletRequest.getParameter(String)} */ + @Nullable default String getParameter(String name) { String[] values = getParameterValues(name); if (values == null || values.length == 0) { @@ -39,6 +42,9 @@ default String getParameter(String name) { /** See {@code jakarta.servlet.ServletRequest.getParameterValues(String)} */ @Override + @Nullable + // decode with Charset is only available in Java 10+, but we want to support Java 8 + @SuppressWarnings("JdkObsolete") default String[] getParameterValues(String name) { try { ArrayList result = new ArrayList<>(); diff --git a/prometheus-metrics-exporter-common/src/main/java/io/prometheus/metrics/exporter/common/PrometheusScrapeHandler.java b/prometheus-metrics-exporter-common/src/main/java/io/prometheus/metrics/exporter/common/PrometheusScrapeHandler.java index f978a9b36..440110b33 100644 --- a/prometheus-metrics-exporter-common/src/main/java/io/prometheus/metrics/exporter/common/PrometheusScrapeHandler.java +++ b/prometheus-metrics-exporter-common/src/main/java/io/prometheus/metrics/exporter/common/PrometheusScrapeHandler.java @@ -1,5 +1,6 @@ package io.prometheus.metrics.exporter.common; +import io.prometheus.metrics.config.EscapingScheme; import io.prometheus.metrics.config.ExporterFilterProperties; import io.prometheus.metrics.config.PrometheusProperties; import io.prometheus.metrics.expositionformats.ExpositionFormatWriter; @@ -18,15 +19,17 @@ import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Predicate; import java.util.zip.GZIPOutputStream; +import javax.annotation.Nullable; /** Prometheus scrape endpoint. */ public class PrometheusScrapeHandler { private final PrometheusRegistry registry; private final ExpositionFormats expositionFormats; - private final Predicate nameFilter; + @Nullable private final Predicate nameFilter; private final AtomicInteger lastResponseSize = new AtomicInteger(2 << 9); // 0.5 MB private final List supportedFormats; + private final boolean preferUncompressedResponse; public PrometheusScrapeHandler() { this(PrometheusProperties.get(), PrometheusRegistry.defaultRegistry); @@ -42,6 +45,8 @@ public PrometheusScrapeHandler(PrometheusProperties config) { public PrometheusScrapeHandler(PrometheusProperties config, PrometheusRegistry registry) { this.expositionFormats = ExpositionFormats.init(config.getExporterProperties()); + this.preferUncompressedResponse = + config.getExporterHttpServerProperties().isPreferUncompressedResponse(); this.registry = registry; this.nameFilter = makeNameFilter(config.getExporterFilterProperties()); supportedFormats = new ArrayList<>(Arrays.asList("openmetrics", "text")); @@ -54,15 +59,12 @@ public void handleRequest(PrometheusHttpExchange exchange) throws IOException { try { PrometheusHttpRequest request = exchange.getRequest(); MetricSnapshots snapshots = scrape(request); - if (writeDebugResponse(snapshots, exchange)) { + String acceptHeader = request.getHeader("Accept"); + EscapingScheme escapingScheme = EscapingScheme.fromAcceptHeader(acceptHeader); + if (writeDebugResponse(snapshots, escapingScheme, exchange)) { return; } - ByteArrayOutputStream responseBuffer = - new ByteArrayOutputStream(lastResponseSize.get() + 1024); - String acceptHeader = request.getHeader("Accept"); ExpositionFormatWriter writer = expositionFormats.findWriter(acceptHeader); - writer.write(responseBuffer, snapshots); - lastResponseSize.set(responseBuffer.size()); PrometheusHttpResponse response = exchange.getResponse(); response.setHeader("Content-Type", writer.getContentType()); @@ -70,9 +72,13 @@ public void handleRequest(PrometheusHttpExchange exchange) throws IOException { response.setHeader("Content-Encoding", "gzip"); try (GZIPOutputStream gzipOutputStream = new GZIPOutputStream(response.sendHeadersAndGetBody(200, 0))) { - responseBuffer.writeTo(gzipOutputStream); + writer.write(gzipOutputStream, snapshots, escapingScheme); } } else { + ByteArrayOutputStream responseBuffer = + new ByteArrayOutputStream(lastResponseSize.get() + 1024); + writer.write(responseBuffer, snapshots, escapingScheme); + lastResponseSize.set(responseBuffer.size()); int contentLength = responseBuffer.size(); if (contentLength > 0) { response.setHeader("Content-Length", String.valueOf(contentLength)); @@ -97,6 +103,7 @@ public void handleRequest(PrometheusHttpExchange exchange) throws IOException { } } + @Nullable private Predicate makeNameFilter(ExporterFilterProperties props) { if (props.getAllowedMetricNames() == null && props.getExcludedMetricNames() == null @@ -113,7 +120,8 @@ private Predicate makeNameFilter(ExporterFilterProperties props) { } } - private Predicate makeNameFilter(String[] includedNames) { + @Nullable + private Predicate makeNameFilter(@Nullable String[] includedNames) { Predicate result = null; if (includedNames != null && includedNames.length > 0) { result = MetricNameFilter.builder().nameMustBeEqualTo(includedNames).build(); @@ -136,7 +144,8 @@ private MetricSnapshots scrape(PrometheusHttpRequest request) { } } - private boolean writeDebugResponse(MetricSnapshots snapshots, PrometheusHttpExchange exchange) + private boolean writeDebugResponse( + MetricSnapshots snapshots, EscapingScheme escapingScheme, PrometheusHttpExchange exchange) throws IOException { String debugParam = exchange.getRequest().getParameter("debug"); PrometheusHttpResponse response = exchange.getResponse(); @@ -148,14 +157,16 @@ private boolean writeDebugResponse(MetricSnapshots snapshots, PrometheusHttpExch OutputStream body = response.sendHeadersAndGetBody(responseStatus, 0); switch (debugParam) { case "openmetrics": - expositionFormats.getOpenMetricsTextFormatWriter().write(body, snapshots); + expositionFormats.getOpenMetricsTextFormatWriter().write(body, snapshots, escapingScheme); break; case "text": - expositionFormats.getPrometheusTextFormatWriter().write(body, snapshots); + expositionFormats.getPrometheusTextFormatWriter().write(body, snapshots, escapingScheme); break; case "prometheus-protobuf": String debugString = - expositionFormats.getPrometheusProtobufWriter().toDebugString(snapshots); + expositionFormats + .getPrometheusProtobufWriter() + .toDebugString(snapshots, escapingScheme); body.write(debugString.getBytes(StandardCharsets.UTF_8)); break; default: @@ -172,6 +183,10 @@ private boolean writeDebugResponse(MetricSnapshots snapshots, PrometheusHttpExch } private boolean shouldUseCompression(PrometheusHttpRequest request) { + if (preferUncompressedResponse) { + return false; + } + Enumeration encodingHeaders = request.getHeaders("Accept-Encoding"); if (encodingHeaders == null) { return false; diff --git a/prometheus-metrics-exporter-common/src/test/java/io/prometheus/metrics/exporter/common/PrometheusHttpRequestTest.java b/prometheus-metrics-exporter-common/src/test/java/io/prometheus/metrics/exporter/common/PrometheusHttpRequestTest.java new file mode 100644 index 000000000..a2b630ecd --- /dev/null +++ b/prometheus-metrics-exporter-common/src/test/java/io/prometheus/metrics/exporter/common/PrometheusHttpRequestTest.java @@ -0,0 +1,120 @@ +package io.prometheus.metrics.exporter.common; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.Collections; +import java.util.Enumeration; +import org.junit.jupiter.api.Test; + +class PrometheusHttpRequestTest { + + @Test + void testGetHeaderReturnsFirstValue() { + PrometheusHttpRequest request = + new TestPrometheusHttpRequest("name[]=metric1&name[]=metric2", "gzip"); + assertThat(request.getHeader("Accept-Encoding")).isEqualTo("gzip"); + } + + @Test + void testGetHeaderReturnsNullWhenNoHeaders() { + PrometheusHttpRequest request = new TestPrometheusHttpRequest("", null); + assertThat(request.getHeader("Accept-Encoding")).isNull(); + } + + @Test + void testGetParameterReturnsFirstValue() { + PrometheusHttpRequest request = new TestPrometheusHttpRequest("name[]=metric1&name[]=metric2"); + assertThat(request.getParameter("name[]")).isEqualTo("metric1"); + } + + @Test + void testGetParameterReturnsNullWhenNotPresent() { + PrometheusHttpRequest request = new TestPrometheusHttpRequest("other=value"); + assertThat(request.getParameter("name[]")).isNull(); + } + + @Test + void testGetParameterValuesReturnsMultipleValues() { + PrometheusHttpRequest request = new TestPrometheusHttpRequest("name[]=metric1&name[]=metric2"); + String[] values = request.getParameterValues("name[]"); + assertThat(values).containsExactly("metric1", "metric2"); + } + + @Test + void testGetParameterValuesReturnsNullWhenNotPresent() { + PrometheusHttpRequest request = new TestPrometheusHttpRequest("other=value"); + assertThat(request.getParameterValues("name[]")).isNull(); + } + + @Test + void testGetParameterValuesWithEmptyQueryString() { + PrometheusHttpRequest request = new TestPrometheusHttpRequest(""); + assertThat(request.getParameterValues("name[]")).isNull(); + } + + @Test + void testGetParameterValuesWithNullQueryString() { + PrometheusHttpRequest request = new TestPrometheusHttpRequest(null); + assertThat(request.getParameterValues("name[]")).isNull(); + } + + @Test + void testGetParameterValuesWithUrlEncodedValues() { + PrometheusHttpRequest request = new TestPrometheusHttpRequest("name=hello%20world"); + String[] values = request.getParameterValues("name"); + assertThat(values).containsExactly("hello world"); + } + + @Test + void testGetParameterValuesWithSpecialCharacters() { + PrometheusHttpRequest request = new TestPrometheusHttpRequest("name=%2Ffoo%2Fbar"); + String[] values = request.getParameterValues("name"); + assertThat(values).containsExactly("/foo/bar"); + } + + @Test + void testGetParameterValuesIgnoresParametersWithoutEquals() { + PrometheusHttpRequest request = + new TestPrometheusHttpRequest("name[]=value1&invalid&name[]=value2"); + String[] values = request.getParameterValues("name[]"); + assertThat(values).containsExactly("value1", "value2"); + } + + /** Test implementation of PrometheusHttpRequest for testing default methods. */ + private static class TestPrometheusHttpRequest implements PrometheusHttpRequest { + private final String queryString; + private final String acceptEncoding; + + TestPrometheusHttpRequest(String queryString) { + this(queryString, null); + } + + TestPrometheusHttpRequest(String queryString, String acceptEncoding) { + this.queryString = queryString; + this.acceptEncoding = acceptEncoding; + } + + @Override + public String getQueryString() { + return queryString; + } + + @Override + public Enumeration getHeaders(String name) { + if (acceptEncoding != null && name.equals("Accept-Encoding")) { + return Collections.enumeration(Collections.singletonList(acceptEncoding)); + } + return null; + } + + @Override + public String getMethod() { + return "GET"; + } + + @Override + public String getRequestPath() { + return "/metrics"; + } + } +} diff --git a/prometheus-metrics-exporter-common/src/test/java/io/prometheus/metrics/exporter/common/PrometheusScrapeHandlerTest.java b/prometheus-metrics-exporter-common/src/test/java/io/prometheus/metrics/exporter/common/PrometheusScrapeHandlerTest.java new file mode 100644 index 000000000..07ea8ac52 --- /dev/null +++ b/prometheus-metrics-exporter-common/src/test/java/io/prometheus/metrics/exporter/common/PrometheusScrapeHandlerTest.java @@ -0,0 +1,303 @@ +package io.prometheus.metrics.exporter.common; + +import static org.assertj.core.api.Assertions.assertThat; + +import io.prometheus.metrics.core.metrics.Counter; +import io.prometheus.metrics.model.registry.PrometheusRegistry; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.nio.charset.StandardCharsets; +import java.util.Collections; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.Map; +import java.util.zip.GZIPInputStream; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +class PrometheusScrapeHandlerTest { + + private PrometheusRegistry registry; + private PrometheusScrapeHandler handler; + private Counter testCounter; + + @BeforeEach + void setUp() { + registry = new PrometheusRegistry(); + handler = new PrometheusScrapeHandler(registry); + testCounter = Counter.builder().name("test_counter").help("Test counter").register(registry); + testCounter.inc(5); + } + + @Test + void testBasicScrape() throws IOException { + TestHttpExchange exchange = new TestHttpExchange("GET", null); + handler.handleRequest(exchange); + + assertThat(exchange.getResponseCode()).isEqualTo(200); + assertThat(exchange.getResponseBody()).contains("test_counter"); + assertThat(exchange.getResponseBody()).contains("5.0"); + } + + @Test + void testOpenMetricsFormat() throws IOException { + TestHttpExchange exchange = + new TestHttpExchange("GET", null).withHeader("Accept", "application/openmetrics-text"); + handler.handleRequest(exchange); + + assertThat(exchange.getResponseCode()).isEqualTo(200); + assertThat(exchange.getResponseHeaders().get("Content-Type")) + .contains("application/openmetrics-text"); + assertThat(exchange.getResponseBody()).contains("test_counter"); + } + + @Test + void testPrometheusTextFormat() throws IOException { + TestHttpExchange exchange = + new TestHttpExchange("GET", null).withHeader("Accept", "text/plain"); + handler.handleRequest(exchange); + + assertThat(exchange.getResponseCode()).isEqualTo(200); + assertThat(exchange.getResponseHeaders().get("Content-Type")).contains("text/plain"); + assertThat(exchange.getResponseBody()).contains("test_counter"); + } + + @Test + void testGzipCompression() throws IOException { + TestHttpExchange exchange = + new TestHttpExchange("GET", null).withHeader("Accept-Encoding", "gzip"); + handler.handleRequest(exchange); + + assertThat(exchange.getResponseCode()).isEqualTo(200); + assertThat(exchange.getResponseHeaders().get("Content-Encoding")).isEqualTo("gzip"); + assertThat(exchange.isGzipCompressed()).isTrue(); + + // Decompress and verify content + String decompressed = exchange.getDecompressedBody(); + assertThat(decompressed).contains("test_counter"); + } + + @Test + void testMultipleAcceptEncodingHeaders() throws IOException { + TestHttpExchange exchange = + new TestHttpExchange("GET", null) + .withHeader("Accept-Encoding", "deflate") + .withHeader("Accept-Encoding", "gzip, br"); + handler.handleRequest(exchange); + + assertThat(exchange.getResponseCode()).isEqualTo(200); + assertThat(exchange.getResponseHeaders().get("Content-Encoding")).isEqualTo("gzip"); + } + + @Test + void testHeadRequest() throws IOException { + TestHttpExchange exchange = new TestHttpExchange("HEAD", null); + handler.handleRequest(exchange); + + assertThat(exchange.getResponseCode()).isEqualTo(200); + assertThat(exchange.getResponseHeaders().get("Content-Length")).isNotNull(); + // For HEAD requests, body should be empty even though Content-Length is set + assertThat(exchange.rawResponseBody.size()).isEqualTo(0); + } + + @Test + void testDebugOpenMetrics() throws IOException { + TestHttpExchange exchange = new TestHttpExchange("GET", "debug=openmetrics"); + handler.handleRequest(exchange); + + assertThat(exchange.getResponseCode()).isEqualTo(200); + assertThat(exchange.getResponseHeaders().get("Content-Type")) + .isEqualTo("text/plain; charset=utf-8"); + assertThat(exchange.getResponseBody()).contains("test_counter"); + } + + @Test + void testDebugText() throws IOException { + TestHttpExchange exchange = new TestHttpExchange("GET", "debug=text"); + handler.handleRequest(exchange); + + assertThat(exchange.getResponseCode()).isEqualTo(200); + assertThat(exchange.getResponseBody()).contains("test_counter"); + } + + @Test + void testDebugInvalidParameter() throws IOException { + TestHttpExchange exchange = new TestHttpExchange("GET", "debug=invalid"); + handler.handleRequest(exchange); + + assertThat(exchange.getResponseCode()).isEqualTo(500); + assertThat(exchange.getResponseBody()).contains("debug=invalid: Unsupported query parameter"); + } + + @Test + void testMetricNameFilter() throws IOException { + Counter anotherCounter = + Counter.builder().name("another_counter").help("Another counter").register(registry); + anotherCounter.inc(10); + + TestHttpExchange exchange = new TestHttpExchange("GET", "name[]=test_counter"); + handler.handleRequest(exchange); + + assertThat(exchange.getResponseCode()).isEqualTo(200); + assertThat(exchange.getResponseBody()).contains("test_counter"); + assertThat(exchange.getResponseBody()).doesNotContain("another_counter"); + } + + @Test + void testMultipleMetricNameFilters() throws IOException { + Counter counter1 = Counter.builder().name("metric_one").help("Metric one").register(registry); + Counter counter2 = Counter.builder().name("metric_two").help("Metric two").register(registry); + Counter counter3 = + Counter.builder().name("metric_three").help("Metric three").register(registry); + counter1.inc(); + counter2.inc(); + counter3.inc(); + + TestHttpExchange exchange = new TestHttpExchange("GET", "name[]=metric_one&name[]=metric_two"); + handler.handleRequest(exchange); + + assertThat(exchange.getResponseCode()).isEqualTo(200); + String body = exchange.getResponseBody(); + assertThat(body).contains("metric_one"); + assertThat(body).contains("metric_two"); + assertThat(body).doesNotContain("metric_three"); + } + + /** Test implementation of PrometheusHttpExchange for testing. */ + private static class TestHttpExchange implements PrometheusHttpExchange { + private final TestHttpRequest request; + private final TestHttpResponse response; + private boolean closed = false; + + ByteArrayOutputStream rawResponseBody = new ByteArrayOutputStream(); + + TestHttpExchange(String method, String queryString) { + this.request = new TestHttpRequest(method, queryString); + this.response = new TestHttpResponse(rawResponseBody); + } + + TestHttpExchange withHeader(String name, String value) { + request.addHeader(name, value); + return this; + } + + @Override + public PrometheusHttpRequest getRequest() { + return request; + } + + @Override + public PrometheusHttpResponse getResponse() { + return response; + } + + @Override + public void handleException(IOException e) throws IOException { + throw e; + } + + @Override + public void handleException(RuntimeException e) { + throw e; + } + + @Override + public void close() { + closed = true; + } + + public int getResponseCode() { + return response.statusCode; + } + + public Map getResponseHeaders() { + return response.headers; + } + + public String getResponseBody() { + return rawResponseBody.toString(StandardCharsets.UTF_8); + } + + public boolean isGzipCompressed() { + return "gzip".equals(response.headers.get("Content-Encoding")); + } + + public String getDecompressedBody() throws IOException { + if (!isGzipCompressed()) { + return getResponseBody(); + } + byte[] compressed = rawResponseBody.toByteArray(); + try (GZIPInputStream gzipInputStream = + new GZIPInputStream(new java.io.ByteArrayInputStream(compressed))) { + ByteArrayOutputStream decompressed = new ByteArrayOutputStream(); + byte[] buffer = new byte[1024]; + int len; + while ((len = gzipInputStream.read(buffer)) > 0) { + decompressed.write(buffer, 0, len); + } + return decompressed.toString(StandardCharsets.UTF_8.name()); + } + } + } + + /** Test implementation of PrometheusHttpRequest. */ + private static class TestHttpRequest implements PrometheusHttpRequest { + private final String method; + private final String queryString; + private final Map> headers = new HashMap<>(); + + TestHttpRequest(String method, String queryString) { + this.method = method; + this.queryString = queryString; + } + + void addHeader(String name, String value) { + headers.computeIfAbsent(name, k -> new java.util.ArrayList<>()).add(value); + } + + @Override + public String getQueryString() { + return queryString; + } + + @Override + public Enumeration getHeaders(String name) { + java.util.List values = headers.get(name); + return values == null ? null : Collections.enumeration(values); + } + + @Override + public String getMethod() { + return method; + } + + @Override + public String getRequestPath() { + return "/metrics"; + } + } + + /** Test implementation of PrometheusHttpResponse. */ + private static class TestHttpResponse implements PrometheusHttpResponse { + private final Map headers = new HashMap<>(); + private final ByteArrayOutputStream outputStream; + private int statusCode; + + TestHttpResponse(ByteArrayOutputStream outputStream) { + this.outputStream = outputStream; + } + + @Override + public void setHeader(String name, String value) { + headers.put(name, value); + } + + @Override + public OutputStream sendHeadersAndGetBody(int statusCode, int contentLength) + throws IOException { + this.statusCode = statusCode; + return outputStream; + } + } +} diff --git a/prometheus-metrics-exporter-httpserver/pom.xml b/prometheus-metrics-exporter-httpserver/pom.xml index 671994950..d2843b51a 100644 --- a/prometheus-metrics-exporter-httpserver/pom.xml +++ b/prometheus-metrics-exporter-httpserver/pom.xml @@ -6,7 +6,7 @@ io.prometheus client_java - 1.4.0-SNAPSHOT + 1.6.0-SNAPSHOT prometheus-metrics-exporter-httpserver @@ -19,7 +19,7 @@ io.prometheus.metrics.exporter.httpserver - 0.45 + 0.60 diff --git a/prometheus-metrics-exporter-httpserver/src/main/java/io/prometheus/metrics/exporter/httpserver/DefaultHandler.java b/prometheus-metrics-exporter-httpserver/src/main/java/io/prometheus/metrics/exporter/httpserver/DefaultHandler.java index 103383b47..a4a343d54 100644 --- a/prometheus-metrics-exporter-httpserver/src/main/java/io/prometheus/metrics/exporter/httpserver/DefaultHandler.java +++ b/prometheus-metrics-exporter-httpserver/src/main/java/io/prometheus/metrics/exporter/httpserver/DefaultHandler.java @@ -11,24 +11,27 @@ public class DefaultHandler implements HttpHandler { private final byte[] responseBytes; private final String contentType; - public DefaultHandler() { + public DefaultHandler(String metricsPath) { + String metrics = metricsPath.startsWith("/") ? metricsPath.substring(1) : metricsPath; String responseString = "\n" + "Prometheus Java Client\n" + "\n" + "

Prometheus Java Client

\n" + "

Metrics Path

\n" - + "The metrics path is /metrics.\n" + + String.format("The metrics path is %s.\n", metrics, metricsPath) + "

Name Filter

\n" + "If you want to scrape only specific metrics, " + "use the name[] parameter like this:\n" + "\n" + "You can also use multiple name[] parameters to query multiple metrics:\n" + "\n" + "The name[] parameter can be used by the Prometheus server for scraping. " + "Add the following snippet to your scrape job configuration in " @@ -50,13 +53,17 @@ public DefaultHandler() { + "The Prometheus Java metrics library supports a debug query parameter " + "for viewing the different formats in a Web browser:\n" + "\n" + "Note that the debug parameter is only for viewing different formats in a " diff --git a/prometheus-metrics-exporter-httpserver/src/main/java/io/prometheus/metrics/exporter/httpserver/HTTPServer.java b/prometheus-metrics-exporter-httpserver/src/main/java/io/prometheus/metrics/exporter/httpserver/HTTPServer.java index 90aaf9d42..55ed6c67e 100644 --- a/prometheus-metrics-exporter-httpserver/src/main/java/io/prometheus/metrics/exporter/httpserver/HTTPServer.java +++ b/prometheus-metrics-exporter-httpserver/src/main/java/io/prometheus/metrics/exporter/httpserver/HTTPServer.java @@ -21,6 +21,7 @@ import java.util.concurrent.SynchronousQueue; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; +import javax.annotation.Nullable; import javax.security.auth.Subject; /** @@ -54,26 +55,41 @@ private HTTPServer( ExecutorService executorService, HttpServer httpServer, PrometheusRegistry registry, - Authenticator authenticator, - String authenticatedSubjectAttributeName, - HttpHandler defaultHandler) { + @Nullable Authenticator authenticator, + @Nullable String authenticatedSubjectAttributeName, + @Nullable HttpHandler defaultHandler, + @Nullable String metricsHandlerPath, + @Nullable Boolean registerHealthHandler) { if (httpServer.getAddress() == null) { throw new IllegalArgumentException("HttpServer hasn't been bound to an address"); } this.server = httpServer; this.executorService = executorService; + String metricsPath = getMetricsPath(metricsHandlerPath); + try { + server.removeContext("/"); + } catch (IllegalArgumentException e) { + // context "/" not registered yet, ignore + } registerHandler( "/", - defaultHandler == null ? new DefaultHandler() : defaultHandler, + defaultHandler == null ? new DefaultHandler(metricsPath) : defaultHandler, authenticator, authenticatedSubjectAttributeName); + try { + server.removeContext(metricsPath); + } catch (IllegalArgumentException e) { + // context metricsPath not registered yet, ignore + } registerHandler( - "/metrics", + metricsPath, new MetricsHandler(config, registry), authenticator, authenticatedSubjectAttributeName); - registerHandler( - "/-/healthy", new HealthyHandler(), authenticator, authenticatedSubjectAttributeName); + if (registerHealthHandler == null || registerHealthHandler) { + registerHandler( + "/-/healthy", new HealthyHandler(), authenticator, authenticatedSubjectAttributeName); + } try { // HttpServer.start() starts the HttpServer in a new background thread. // If we call HttpServer.start() from a thread of the executorService, @@ -87,15 +103,28 @@ private HTTPServer( } } + private String getMetricsPath(@Nullable String metricsHandlerPath) { + if (metricsHandlerPath == null) { + return "/metrics"; + } + if (!metricsHandlerPath.startsWith("/")) { + return "/" + metricsHandlerPath; + } + return metricsHandlerPath; + } + private void registerHandler( - String path, HttpHandler handler, Authenticator authenticator, String subjectAttributeName) { + String path, + HttpHandler handler, + @Nullable Authenticator authenticator, + @Nullable String subjectAttributeName) { HttpContext context = server.createContext(path, wrapWithDoAs(handler, subjectAttributeName)); if (authenticator != null) { context.setAuthenticator(authenticator); } } - private HttpHandler wrapWithDoAs(HttpHandler handler, String subjectAttributeName) { + private HttpHandler wrapWithDoAs(HttpHandler handler, @Nullable String subjectAttributeName) { if (subjectAttributeName == null) { return handler; } @@ -169,15 +198,17 @@ public static Builder builder(PrometheusProperties config) { public static class Builder { private final PrometheusProperties config; - private Integer port = null; - private String hostname = null; - private InetAddress inetAddress = null; - private ExecutorService executorService = null; - private PrometheusRegistry registry = null; - private Authenticator authenticator = null; - private HttpsConfigurator httpsConfigurator = null; - private HttpHandler defaultHandler = null; - private String authenticatedSubjectAttributeName = null; + @Nullable private Integer port = null; + @Nullable private String hostname = null; + @Nullable private InetAddress inetAddress = null; + @Nullable private ExecutorService executorService = null; + @Nullable private PrometheusRegistry registry = null; + @Nullable private Authenticator authenticator = null; + @Nullable private String authenticatedSubjectAttributeName = null; + @Nullable private HttpsConfigurator httpsConfigurator = null; + @Nullable private HttpHandler defaultHandler = null; + @Nullable private String metricsHandlerPath = null; + @Nullable private Boolean registerHealthHandler = null; private Builder(PrometheusProperties config) { this.config = config; @@ -250,6 +281,18 @@ public Builder defaultHandler(HttpHandler defaultHandler) { return this; } + /** Optional: Override default path for the metrics endpoint. Default is {@code /metrics}. */ + public Builder metricsHandlerPath(String metricsHandlerPath) { + this.metricsHandlerPath = metricsHandlerPath; + return this; + } + + /** Optional: Override if the health handler should be registered. Default is {@code true}. */ + public Builder registerHealthHandler(boolean registerHealthHandler) { + this.registerHealthHandler = registerHealthHandler; + return this; + } + /** Build and start the HTTPServer. */ public HTTPServer buildAndStart() throws IOException { if (registry == null) { @@ -271,7 +314,9 @@ public HTTPServer buildAndStart() throws IOException { registry, authenticator, authenticatedSubjectAttributeName, - defaultHandler); + defaultHandler, + metricsHandlerPath, + registerHealthHandler); } private InetSocketAddress makeInetSocketAddress() { @@ -313,7 +358,7 @@ private int findPort() { return 0; // random port will be selected } - private void assertNull(Object o, String msg) { + private void assertNull(@Nullable Object o, String msg) { if (o != null) { throw new IllegalStateException(msg); } diff --git a/prometheus-metrics-exporter-httpserver/src/main/java/io/prometheus/metrics/exporter/httpserver/HttpExchangeAdapter.java b/prometheus-metrics-exporter-httpserver/src/main/java/io/prometheus/metrics/exporter/httpserver/HttpExchangeAdapter.java index 3e0322309..df99837cb 100644 --- a/prometheus-metrics-exporter-httpserver/src/main/java/io/prometheus/metrics/exporter/httpserver/HttpExchangeAdapter.java +++ b/prometheus-metrics-exporter-httpserver/src/main/java/io/prometheus/metrics/exporter/httpserver/HttpExchangeAdapter.java @@ -112,7 +112,7 @@ private void sendErrorResponseWithStackTrace(Exception requestHandlerException) httpExchange.getResponseHeaders().set("Content-Type", "text/plain; charset=utf-8"); httpExchange.sendResponseHeaders(500, stackTrace.length); httpExchange.getResponseBody().write(stackTrace); - } catch (Exception errorWriterException) { + } catch (IOException errorWriterException) { // We want to avoid logging so that we don't mess with application logs when the HTTPServer // is used in a Java agent. // However, if we can't even send an error response to the client there's nothing we can do diff --git a/prometheus-metrics-exporter-httpserver/src/test/java/io/prometheus/metrics/exporter/httpserver/HTTPServerTest.java b/prometheus-metrics-exporter-httpserver/src/test/java/io/prometheus/metrics/exporter/httpserver/HTTPServerTest.java index 06ab75b24..ff2d55048 100644 --- a/prometheus-metrics-exporter-httpserver/src/test/java/io/prometheus/metrics/exporter/httpserver/HTTPServerTest.java +++ b/prometheus-metrics-exporter-httpserver/src/test/java/io/prometheus/metrics/exporter/httpserver/HTTPServerTest.java @@ -10,26 +10,42 @@ import com.sun.net.httpserver.HttpsConfigurator; import io.prometheus.metrics.model.registry.PrometheusRegistry; import io.prometheus.metrics.model.registry.PrometheusScrapeRequest; +import io.prometheus.metrics.model.snapshots.CounterSnapshot; +import io.prometheus.metrics.model.snapshots.CounterSnapshot.CounterDataPointSnapshot; +import io.prometheus.metrics.model.snapshots.Labels; +import io.prometheus.metrics.model.snapshots.MetricMetadata; import io.prometheus.metrics.model.snapshots.MetricSnapshots; import java.io.IOException; +import java.lang.reflect.Method; import java.net.InetAddress; -import java.net.InetSocketAddress; -import java.net.Socket; -import java.nio.charset.StandardCharsets; -import java.security.AccessController; -import java.security.NoSuchAlgorithmException; +import java.net.URI; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; import java.security.Principal; +import java.util.List; import java.util.concurrent.Executors; import javax.net.ssl.SSLContext; import javax.security.auth.Subject; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -public class HTTPServerTest { +class HTTPServerTest { + + private PrometheusRegistry registry; + + @BeforeEach + void setUp() { + final MetricMetadata metadata = new MetricMetadata("my-counter"); + final CounterDataPointSnapshot dataPointSnapshot = + new CounterDataPointSnapshot(1.0, Labels.EMPTY, null, System.currentTimeMillis()); + + registry = new PrometheusRegistry(); + registry.register(() -> new CounterSnapshot(metadata, List.of(dataPointSnapshot))); + } @Test - @SuppressWarnings({"removal"}) public void testSubjectDoAs() throws Exception { - final String user = "joe"; final Subject subject = new Subject(); subject.getPrincipals().add(() -> user); @@ -46,7 +62,7 @@ public Result authenticate(HttpExchange exchange) { HttpHandler handler = exchange -> { boolean found = false; - Subject current = Subject.getSubject(AccessController.getContext()); + Subject current = getCurrentSubject(); for (Principal p : current.getPrincipals()) { if (user.equals(p.getName())) { found = true; @@ -66,48 +82,84 @@ public Result authenticate(HttpExchange exchange) { .authenticatedSubjectAttributeName("aa") .buildAndStart(); - run(server, "204", "/"); + run(server, "/", 204, ""); } - private static void run(HTTPServer server, String expected, String path) throws IOException { - try (Socket socket = new Socket()) { - socket.connect(new InetSocketAddress("localhost", server.getPort())); - - socket - .getOutputStream() - .write(("GET " + path + " HTTP/1.1 \r\n").getBytes(StandardCharsets.UTF_8)); - socket.getOutputStream().write("HOST: localhost \r\n\r\n".getBytes(StandardCharsets.UTF_8)); - socket.getOutputStream().flush(); - - String actualResponse = ""; - byte[] resp = new byte[500]; - int read = socket.getInputStream().read(resp, 0, resp.length); - if (read > 0) { - actualResponse = new String(resp, 0, read, StandardCharsets.UTF_8); - } - assertThat(actualResponse).contains(expected); - } + @Test + void testSubjectDoAsWithInvalidSubject() throws Exception { + Authenticator authenticator = + new Authenticator() { + @Override + public Result authenticate(HttpExchange exchange) { + exchange.setAttribute("aa", "not-a-subject"); + return new Success(new HttpPrincipal("user", "/")); + } + }; + + HttpHandler handler = exchange -> exchange.sendResponseHeaders(204, -1); + HTTPServer server = + HTTPServer.builder() + .port(0) + .authenticator(authenticator) + .defaultHandler(handler) + .authenticatedSubjectAttributeName("aa") + .buildAndStart(); + + run(server, "/", 403, ""); + } + + @Test + void defaultHandler() throws Exception { + run( + HTTPServer.builder().port(0).buildAndStart(), + "/", + 200, + "Prometheus Java Client"); + } + + @Test + void metrics() throws Exception { + run( + HTTPServer.builder() + .port(0) + .registry(registry) + .executorService(Executors.newFixedThreadPool(1)) + .buildAndStart(), + "/metrics", + 200, + "my_counter_total 1.0"); } @Test - void defaultHandler() throws IOException { - run(HTTPServer.builder().port(0).buildAndStart(), "200", "/"); + void metricsCustomPath() throws Exception { + run( + HTTPServer.builder() + .port(0) + .registry(registry) + .metricsHandlerPath("/my-metrics") + .executorService(Executors.newFixedThreadPool(1)) + .buildAndStart(), + "/my-metrics", + 200, + "my_counter_total 1.0"); } @Test - void metrics() throws IOException { + void metricsCustomRootPath() throws Exception { run( HTTPServer.builder() .port(0) - .registry(new PrometheusRegistry()) + .registry(registry) + .metricsHandlerPath("/") .executorService(Executors.newFixedThreadPool(1)) .buildAndStart(), - "200", - "/metrics"); + "/", + 200, + "my_counter_total 1.0"); } @Test - void registryThrows() throws IOException { + void registryThrows() throws Exception { HTTPServer server = HTTPServer.builder() .port(0) @@ -119,11 +171,12 @@ public MetricSnapshots scrape(PrometheusScrapeRequest scrapeRequest) { } }) .buildAndStart(); - run(server, "500", "/metrics"); + run(server, "/metrics", 500, "An Exception occurred while scraping metrics"); } @Test - void config() throws NoSuchAlgorithmException, IOException { + @SuppressWarnings("resource") + void config() { assertThatExceptionOfType(IllegalStateException.class) .isThrownBy( () -> @@ -134,18 +187,92 @@ void config() throws NoSuchAlgorithmException, IOException { .buildAndStart()) .withMessage("cannot configure 'inetAddress' and 'hostname' at the same time"); - // ssl doesn't work without in tests + // SSL doesn't work in this simple test configuration + assertThatExceptionOfType(IOException.class) + .isThrownBy( + () -> + run( + HTTPServer.builder() + .port(0) + .httpsConfigurator(new HttpsConfigurator(SSLContext.getDefault())) + .buildAndStart(), + "/", + 0, + "ignored")); + } + + @Test + void health() throws Exception { + run(HTTPServer.builder().port(0).buildAndStart(), "/-/healthy", 200, "Exporter is healthy."); + } + + @Test + void healthEnabled() throws Exception { + HttpHandler handler = exchange -> exchange.sendResponseHeaders(204, -1); run( HTTPServer.builder() .port(0) - .httpsConfigurator(new HttpsConfigurator(SSLContext.getDefault())) + .defaultHandler(handler) + .registerHealthHandler(true) .buildAndStart(), - "", - "/"); + "/-/healthy", + 200, + "Exporter is healthy."); } @Test - void health() throws IOException { - run(HTTPServer.builder().port(0).buildAndStart(), "200", "/-/healthy"); + void healthDisabled() throws Exception { + HttpHandler handler = exchange -> exchange.sendResponseHeaders(204, -1); + run( + HTTPServer.builder() + .port(0) + .defaultHandler(handler) + .registerHealthHandler(false) + .buildAndStart(), + "/-/healthy", + 204, + ""); + } + + private static void run( + HTTPServer server, String path, int expectedStatusCode, String expectedBody) + throws Exception { + // we cannot use try-with-resources or even client.close(), or the test will fail with Java 17 + @SuppressWarnings("resource") + final HttpClient client = HttpClient.newBuilder().build(); + try { + final URI uri = URI.create("http://localhost:%s%s".formatted(server.getPort(), path)); + final HttpRequest request = HttpRequest.newBuilder().uri(uri).GET().build(); + + final HttpResponse response = + client.send(request, HttpResponse.BodyHandlers.ofString()); + assertThat(response.statusCode()).isEqualTo(expectedStatusCode); + assertThat(response.body()).contains(expectedBody); + } finally { + server.stop(); + } + } + + /** + * Get current Subject using reflection to support both Java 17 and Java 18+. + * + *

Java 18+ has Subject.current(), but Java 17 and earlier require + * Subject.getSubject(AccessController.getContext()). + */ + @SuppressWarnings({"removal"}) + private static Subject getCurrentSubject() { + try { + Method currentMethod = Subject.class.getMethod("current"); + return (Subject) currentMethod.invoke(null); + } catch (NoSuchMethodException e) { + // Fall back to pre-Java 18 API + try { + return Subject.getSubject(java.security.AccessController.getContext()); + } catch (Exception ex) { + throw new RuntimeException("Failed to get current Subject", ex); + } + } catch (Exception e) { + throw new RuntimeException("Failed to invoke Subject.current()", e); + } } } diff --git a/prometheus-metrics-exporter-httpserver/src/test/java/io/prometheus/metrics/exporter/httpserver/HttpExchangeAdapterTest.java b/prometheus-metrics-exporter-httpserver/src/test/java/io/prometheus/metrics/exporter/httpserver/HttpExchangeAdapterTest.java new file mode 100644 index 000000000..19bc0d66e --- /dev/null +++ b/prometheus-metrics-exporter-httpserver/src/test/java/io/prometheus/metrics/exporter/httpserver/HttpExchangeAdapterTest.java @@ -0,0 +1,49 @@ +package io.prometheus.metrics.exporter.httpserver; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import com.sun.net.httpserver.Headers; +import com.sun.net.httpserver.HttpExchange; +import java.net.URI; +import java.util.List; +import org.junit.jupiter.api.Test; + +class HttpExchangeAdapterTest { + + @Test + void getRequestPath() { + HttpExchange httpExchange = mock(HttpExchange.class); + when(httpExchange.getRequestURI()).thenReturn(URI.create("/metrics?name=test")); + HttpExchangeAdapter adapter = new HttpExchangeAdapter(httpExchange); + assertThat(adapter.getRequest().getRequestPath()).isEqualTo("/metrics"); + } + + @Test + void getRequestPathWithoutQueryString() { + HttpExchange httpExchange = mock(HttpExchange.class); + when(httpExchange.getRequestURI()).thenReturn(URI.create("/metrics")); + HttpExchangeAdapter adapter = new HttpExchangeAdapter(httpExchange); + assertThat(adapter.getRequest().getRequestPath()).isEqualTo("/metrics"); + } + + @Test + void getHeadersWhenPresent() { + HttpExchange httpExchange = mock(HttpExchange.class); + Headers headers = new Headers(); + headers.put("Accept", List.of("text/plain")); + when(httpExchange.getRequestHeaders()).thenReturn(headers); + HttpExchangeAdapter adapter = new HttpExchangeAdapter(httpExchange); + assertThat(adapter.getRequest().getHeaders("Accept").nextElement()).isEqualTo("text/plain"); + } + + @Test + void getHeadersWhenNotPresent() { + HttpExchange httpExchange = mock(HttpExchange.class); + Headers headers = new Headers(); + when(httpExchange.getRequestHeaders()).thenReturn(headers); + HttpExchangeAdapter adapter = new HttpExchangeAdapter(httpExchange); + assertThat(adapter.getRequest().getHeaders("Accept").hasMoreElements()).isFalse(); + } +} diff --git a/prometheus-metrics-exporter-opentelemetry-otel-agent-resources/pom.xml b/prometheus-metrics-exporter-opentelemetry-otel-agent-resources/pom.xml index 7e2a26092..40ce26a57 100644 --- a/prometheus-metrics-exporter-opentelemetry-otel-agent-resources/pom.xml +++ b/prometheus-metrics-exporter-opentelemetry-otel-agent-resources/pom.xml @@ -6,7 +6,7 @@ io.prometheus client_java - 1.4.0-SNAPSHOT + 1.6.0-SNAPSHOT prometheus-metrics-exporter-opentelemetry-otel-agent-resources @@ -21,6 +21,8 @@ io.prometheus.otel.resource.attributes 1.29.0 + + 0.50 @@ -59,6 +61,40 @@ + + org.jacoco + jacoco-maven-plugin + + + **/io/opentelemetry/** + + + + + check + + check + + + + + CLASS + + + LINE + COVEREDRATIO + ${jacoco.line-coverage} + + + + io.opentelemetry.** + + + + + + + diff --git a/prometheus-metrics-exporter-opentelemetry-otel-agent-resources/src/test/java/io/prometheus/otelagent/ResourceAttributesFromOtelAgentTest.java b/prometheus-metrics-exporter-opentelemetry-otel-agent-resources/src/test/java/io/prometheus/otelagent/ResourceAttributesFromOtelAgentTest.java new file mode 100644 index 000000000..9f0165b45 --- /dev/null +++ b/prometheus-metrics-exporter-opentelemetry-otel-agent-resources/src/test/java/io/prometheus/otelagent/ResourceAttributesFromOtelAgentTest.java @@ -0,0 +1,67 @@ +package io.prometheus.otelagent; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.Map; +import org.junit.jupiter.api.Test; + +class ResourceAttributesFromOtelAgentTest { + + @Test + void testGetResourceAttributesWithoutOtelAgent() { + // When OTel agent is not attached, should return empty map + Map attributes = + ResourceAttributesFromOtelAgent.getResourceAttributes("test-scope"); + assertThat(attributes).isEmpty(); + } + + @Test + void testGetResourceAttributesWithDifferentInstrumentationScopes() { + // Test with different scope names to ensure temp directory creation works + Map attributes1 = + ResourceAttributesFromOtelAgent.getResourceAttributes("scope-one"); + Map attributes2 = + ResourceAttributesFromOtelAgent.getResourceAttributes("scope-two"); + + assertThat(attributes1).isEmpty(); + assertThat(attributes2).isEmpty(); + } + + @Test + void testGetResourceAttributesHandlesExceptions() { + // Test with special characters that might cause issues in temp directory names + Map attributes = + ResourceAttributesFromOtelAgent.getResourceAttributes("test/scope"); + // Should not throw, should return empty map + assertThat(attributes).isEmpty(); + } + + @Test + void testGetResourceAttributesReturnsImmutableMap() { + Map attributes = + ResourceAttributesFromOtelAgent.getResourceAttributes("test-scope"); + + // Verify the returned map is not null + assertThat(attributes).isNotNull(); + + // The returned map should be unmodifiable (per implementation) + try { + attributes.put("test.key", "test.value"); + // If we get here without exception, it's not truly immutable, + // but we still verify it returned empty + assertThat(attributes).isEmpty(); + } catch (UnsupportedOperationException e) { + // This is the expected behavior for an immutable map + assertThat(attributes).isEmpty(); + } + } + + @Test + void testGetResourceAttributesWithNullKey() { + // Test the null handling in the attribute map processing + // Without OTel agent, this returns empty map, but tests the null check logic + Map attributes = + ResourceAttributesFromOtelAgent.getResourceAttributes("test-scope"); + assertThat(attributes).isEmpty(); + } +} diff --git a/prometheus-metrics-exporter-opentelemetry-shaded/pom.xml b/prometheus-metrics-exporter-opentelemetry-shaded/pom.xml index 1abee8d6c..a30a16847 100644 --- a/prometheus-metrics-exporter-opentelemetry-shaded/pom.xml +++ b/prometheus-metrics-exporter-opentelemetry-shaded/pom.xml @@ -6,7 +6,7 @@ io.prometheus client_java - 1.4.0-SNAPSHOT + 1.6.0-SNAPSHOT prometheus-metrics-exporter-opentelemetry diff --git a/prometheus-metrics-exporter-opentelemetry/pom.xml b/prometheus-metrics-exporter-opentelemetry/pom.xml index 30eece5ae..7d427d96b 100644 --- a/prometheus-metrics-exporter-opentelemetry/pom.xml +++ b/prometheus-metrics-exporter-opentelemetry/pom.xml @@ -6,7 +6,7 @@ io.prometheus client_java - 1.4.0-SNAPSHOT + 1.6.0-SNAPSHOT prometheus-metrics-exporter-opentelemetry-no-otel diff --git a/prometheus-metrics-exporter-opentelemetry/src/main/java/io/prometheus/metrics/exporter/opentelemetry/OpenTelemetryExporter.java b/prometheus-metrics-exporter-opentelemetry/src/main/java/io/prometheus/metrics/exporter/opentelemetry/OpenTelemetryExporter.java index a00b2ef85..8f122d3ee 100644 --- a/prometheus-metrics-exporter-opentelemetry/src/main/java/io/prometheus/metrics/exporter/opentelemetry/OpenTelemetryExporter.java +++ b/prometheus-metrics-exporter-opentelemetry/src/main/java/io/prometheus/metrics/exporter/opentelemetry/OpenTelemetryExporter.java @@ -5,6 +5,7 @@ import io.prometheus.metrics.model.registry.PrometheusRegistry; import java.util.HashMap; import java.util.Map; +import javax.annotation.Nullable; public class OpenTelemetryExporter implements AutoCloseable { private final MetricReader reader; @@ -29,16 +30,16 @@ public static Builder builder(PrometheusProperties config) { public static class Builder { private final PrometheusProperties config; - private PrometheusRegistry registry = null; - String protocol; - String endpoint; + @Nullable private PrometheusRegistry registry = null; + @Nullable String protocol; + @Nullable String endpoint; final Map headers = new HashMap<>(); - String interval; - String timeout; - String serviceName; - String serviceNamespace; - String serviceInstanceId; - String serviceVersion; + @Nullable String interval; + @Nullable String timeout; + @Nullable String serviceName; + @Nullable String serviceNamespace; + @Nullable String serviceInstanceId; + @Nullable String serviceVersion; final Map resourceAttributes = new HashMap<>(); private Builder(PrometheusProperties config) { diff --git a/prometheus-metrics-exporter-opentelemetry/src/main/java/io/prometheus/metrics/exporter/opentelemetry/OtelAutoConfig.java b/prometheus-metrics-exporter-opentelemetry/src/main/java/io/prometheus/metrics/exporter/opentelemetry/OtelAutoConfig.java index fb6ebff7e..e0c6a0fa9 100644 --- a/prometheus-metrics-exporter-opentelemetry/src/main/java/io/prometheus/metrics/exporter/opentelemetry/OtelAutoConfig.java +++ b/prometheus-metrics-exporter-opentelemetry/src/main/java/io/prometheus/metrics/exporter/opentelemetry/OtelAutoConfig.java @@ -1,5 +1,7 @@ package io.prometheus.metrics.exporter.opentelemetry; +import static java.util.Objects.requireNonNull; + import io.opentelemetry.api.common.Attributes; import io.opentelemetry.api.common.AttributesBuilder; import io.opentelemetry.sdk.autoconfigure.AutoConfiguredOpenTelemetrySdk; @@ -35,7 +37,7 @@ static MetricReader createReader( config.getExporterOpenTelemetryProperties(), instrumentationScopeInfo); - MetricReader reader = readerRef.get(); + MetricReader reader = requireNonNull(readerRef.get()); reader.register( new PrometheusMetricProducer(registry, instrumentationScopeInfo, getResourceField(sdk))); return reader; @@ -110,7 +112,7 @@ static Resource getResourceField(AutoConfiguredOpenTelemetrySdk sdk) { Method method = AutoConfiguredOpenTelemetrySdk.class.getDeclaredMethod("getResource"); method.setAccessible(true); return (Resource) method.invoke(sdk); - } catch (Exception e) { + } catch (ReflectiveOperationException e) { throw new RuntimeException(e); } } diff --git a/prometheus-metrics-exporter-opentelemetry/src/main/java/io/prometheus/metrics/exporter/opentelemetry/PrometheusInstrumentationScope.java b/prometheus-metrics-exporter-opentelemetry/src/main/java/io/prometheus/metrics/exporter/opentelemetry/PrometheusInstrumentationScope.java index 5f02d38cc..91241295a 100644 --- a/prometheus-metrics-exporter-opentelemetry/src/main/java/io/prometheus/metrics/exporter/opentelemetry/PrometheusInstrumentationScope.java +++ b/prometheus-metrics-exporter-opentelemetry/src/main/java/io/prometheus/metrics/exporter/opentelemetry/PrometheusInstrumentationScope.java @@ -1,6 +1,8 @@ package io.prometheus.metrics.exporter.opentelemetry; import io.opentelemetry.sdk.common.InstrumentationScopeInfo; +import java.io.IOException; +import java.io.InputStream; import java.util.Properties; class PrometheusInstrumentationScope { @@ -21,8 +23,15 @@ static InstrumentationScopeInfo loadInstrumentationScopeInfo( String path, String nameKey, String versionKey) { try { Properties properties = new Properties(); - properties.load( - PrometheusInstrumentationScope.class.getClassLoader().getResourceAsStream(path)); + InputStream stream = + PrometheusInstrumentationScope.class.getClassLoader().getResourceAsStream(path); + if (stream == null) { + throw new IllegalStateException( + "Prometheus metrics library initialization error: Failed to read " + + path + + " from classpath."); + } + properties.load(stream); String instrumentationScopeName = properties.getProperty(nameKey); if (instrumentationScopeName == null) { throw new IllegalStateException( @@ -44,7 +53,7 @@ static InstrumentationScopeInfo loadInstrumentationScopeInfo( return InstrumentationScopeInfo.builder(instrumentationScopeName) .setVersion(instrumentationScopeVersion) .build(); - } catch (Exception e) { + } catch (IOException e) { throw new IllegalStateException( "Prometheus metrics library initialization error: Failed to read " + path diff --git a/prometheus-metrics-exporter-opentelemetry/src/main/java/io/prometheus/metrics/exporter/opentelemetry/PrometheusMetricProducer.java b/prometheus-metrics-exporter-opentelemetry/src/main/java/io/prometheus/metrics/exporter/opentelemetry/PrometheusMetricProducer.java index ff9be1cf8..9344fc4db 100644 --- a/prometheus-metrics-exporter-opentelemetry/src/main/java/io/prometheus/metrics/exporter/opentelemetry/PrometheusMetricProducer.java +++ b/prometheus-metrics-exporter-opentelemetry/src/main/java/io/prometheus/metrics/exporter/opentelemetry/PrometheusMetricProducer.java @@ -22,6 +22,7 @@ import java.util.ArrayList; import java.util.Collection; import java.util.List; +import javax.annotation.Nullable; class PrometheusMetricProducer implements CollectionRegistration { @@ -40,8 +41,14 @@ public PrometheusMetricProducer( @Override public Collection collectAllMetrics() { - // TODO: We could add a filter configuration for the OpenTelemetry exporter and call - // registry.scrape(filter) if a filter is configured, like in the Servlet exporter. + // Note: Currently all metrics from the registry are exported. To add metric filtering + // similar to the Servlet exporter, one could: + // 1. Add filter properties to ExporterOpenTelemetryProperties (allowedNames, excludedNames, + // etc.) + // 2. Convert these properties to a Predicate using MetricNameFilter.builder() + // 3. Call registry.scrape(filter) instead of registry.scrape() + // OpenTelemetry also provides its own Views API for filtering and aggregation, which may be + // preferred for OpenTelemetry-specific deployments. MetricSnapshots snapshots = registry.scrape(); Resource resourceWithTargetInfo = resource.merge(resourceFromTargetInfo(snapshots)); InstrumentationScopeInfo scopeFromInfo = instrumentationScopeFromOtelScopeInfo(snapshots); @@ -93,6 +100,7 @@ private Resource resourceFromTargetInfo(MetricSnapshots snapshots) { return result.build(); } + @Nullable private InstrumentationScopeInfo instrumentationScopeFromOtelScopeInfo( MetricSnapshots snapshots) { for (MetricSnapshot snapshot : snapshots) { @@ -125,7 +133,7 @@ private InstrumentationScopeInfo instrumentationScopeFromOtelScopeInfo( return null; } - private void addUnlessNull(List result, MetricData data) { + private void addUnlessNull(List result, @Nullable MetricData data) { if (data != null) { result.add(data); } diff --git a/prometheus-metrics-exporter-opentelemetry/src/main/java/io/prometheus/metrics/exporter/opentelemetry/PropertiesResourceProvider.java b/prometheus-metrics-exporter-opentelemetry/src/main/java/io/prometheus/metrics/exporter/opentelemetry/PropertiesResourceProvider.java index 1bb6b19bf..cd906167c 100644 --- a/prometheus-metrics-exporter-opentelemetry/src/main/java/io/prometheus/metrics/exporter/opentelemetry/PropertiesResourceProvider.java +++ b/prometheus-metrics-exporter-opentelemetry/src/main/java/io/prometheus/metrics/exporter/opentelemetry/PropertiesResourceProvider.java @@ -5,15 +5,16 @@ import io.opentelemetry.sdk.resources.Resource; import java.util.HashMap; import java.util.Map; +import javax.annotation.Nullable; final class PropertiesResourceProvider { static Resource mergeResource( Map resourceAttributes, - String serviceName, - String serviceNamespace, - String serviceInstanceId, - String serviceVersion) { + @Nullable String serviceName, + @Nullable String serviceNamespace, + @Nullable String serviceInstanceId, + @Nullable String serviceVersion) { Map resource = new HashMap<>(resourceAttributes); if (serviceName != null) { resource.put("service.name", serviceName); diff --git a/prometheus-metrics-exporter-opentelemetry/src/main/java/io/prometheus/metrics/exporter/opentelemetry/PropertyMapper.java b/prometheus-metrics-exporter-opentelemetry/src/main/java/io/prometheus/metrics/exporter/opentelemetry/PropertyMapper.java index be30197fa..2c00beb2f 100644 --- a/prometheus-metrics-exporter-opentelemetry/src/main/java/io/prometheus/metrics/exporter/opentelemetry/PropertyMapper.java +++ b/prometheus-metrics-exporter-opentelemetry/src/main/java/io/prometheus/metrics/exporter/opentelemetry/PropertyMapper.java @@ -6,6 +6,7 @@ import java.util.HashMap; import java.util.Map; import java.util.function.Function; +import javax.annotation.Nullable; class PropertyMapper { @@ -29,7 +30,8 @@ static PropertyMapper create( .addString(builder.serviceName, properties.getServiceName(), "otel.service.name"); } - PropertyMapper addString(String builderValue, String propertyValue, String otelKey) { + PropertyMapper addString( + @Nullable String builderValue, @Nullable String propertyValue, String otelKey) { if (builderValue != null) { // the low priority config should not be used for the metrics settings, so that both general // and metrics settings @@ -42,6 +44,7 @@ PropertyMapper addString(String builderValue, String propertyValue, String otelK return this; } + @Nullable private static String mapToOtelString(Map map) { if (map.isEmpty()) { return null; diff --git a/prometheus-metrics-exporter-opentelemetry/src/main/java/io/prometheus/metrics/exporter/opentelemetry/otelmodel/MetricDataFactory.java b/prometheus-metrics-exporter-opentelemetry/src/main/java/io/prometheus/metrics/exporter/opentelemetry/otelmodel/MetricDataFactory.java index a09490cc6..4c75d2622 100644 --- a/prometheus-metrics-exporter-opentelemetry/src/main/java/io/prometheus/metrics/exporter/opentelemetry/otelmodel/MetricDataFactory.java +++ b/prometheus-metrics-exporter-opentelemetry/src/main/java/io/prometheus/metrics/exporter/opentelemetry/otelmodel/MetricDataFactory.java @@ -10,6 +10,7 @@ import io.prometheus.metrics.model.snapshots.StateSetSnapshot; import io.prometheus.metrics.model.snapshots.SummarySnapshot; import io.prometheus.metrics.model.snapshots.UnknownSnapshot; +import javax.annotation.Nullable; public class MetricDataFactory { @@ -42,6 +43,7 @@ public MetricData create(GaugeSnapshot snapshot) { resource); } + @Nullable public MetricData create(HistogramSnapshot snapshot) { if (!snapshot.getDataPoints().isEmpty()) { HistogramSnapshot.HistogramDataPointSnapshot firstDataPoint = snapshot.getDataPoints().get(0); diff --git a/prometheus-metrics-exporter-opentelemetry/src/main/java/io/prometheus/metrics/exporter/opentelemetry/otelmodel/PrometheusClassicHistogram.java b/prometheus-metrics-exporter-opentelemetry/src/main/java/io/prometheus/metrics/exporter/opentelemetry/otelmodel/PrometheusClassicHistogram.java index 18e0151b2..1a2152fe0 100644 --- a/prometheus-metrics-exporter-opentelemetry/src/main/java/io/prometheus/metrics/exporter/opentelemetry/otelmodel/PrometheusClassicHistogram.java +++ b/prometheus-metrics-exporter-opentelemetry/src/main/java/io/prometheus/metrics/exporter/opentelemetry/otelmodel/PrometheusClassicHistogram.java @@ -11,6 +11,7 @@ import java.util.List; import java.util.Objects; import java.util.stream.Collectors; +import javax.annotation.Nullable; class PrometheusClassicHistogram extends PrometheusData implements HistogramData { @@ -36,6 +37,7 @@ public Collection getPoints() { return points; } + @Nullable private HistogramPointData toOtelDataPoint( HistogramSnapshot.HistogramDataPointSnapshot dataPoint, long currentTimeMillis) { if (!dataPoint.hasClassicHistogramData()) { diff --git a/prometheus-metrics-exporter-opentelemetry/src/main/java/io/prometheus/metrics/exporter/opentelemetry/otelmodel/PrometheusData.java b/prometheus-metrics-exporter-opentelemetry/src/main/java/io/prometheus/metrics/exporter/opentelemetry/otelmodel/PrometheusData.java index 9d2cff6a8..e13b4d121 100644 --- a/prometheus-metrics-exporter-opentelemetry/src/main/java/io/prometheus/metrics/exporter/opentelemetry/otelmodel/PrometheusData.java +++ b/prometheus-metrics-exporter-opentelemetry/src/main/java/io/prometheus/metrics/exporter/opentelemetry/otelmodel/PrometheusData.java @@ -20,6 +20,7 @@ import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; import java.util.stream.StreamSupport; +import javax.annotation.Nullable; abstract class PrometheusData implements Data { @@ -45,7 +46,7 @@ protected Attributes labelsToAttributes(Labels labels) { } } - protected List convertExemplar(Exemplar exemplar) { + protected List convertExemplar(@Nullable Exemplar exemplar) { if (exemplar == null) { return Collections.emptyList(); } @@ -58,7 +59,8 @@ protected List convertExemplars(Exemplars exemplars) { .collect(Collectors.toList()); } - protected DoubleExemplarData toDoubleExemplarData(Exemplar exemplar) { + @Nullable + protected DoubleExemplarData toDoubleExemplarData(@Nullable Exemplar exemplar) { if (exemplar == null) { return null; } diff --git a/prometheus-metrics-exporter-opentelemetry/src/main/java/io/prometheus/metrics/exporter/opentelemetry/otelmodel/PrometheusMetricData.java b/prometheus-metrics-exporter-opentelemetry/src/main/java/io/prometheus/metrics/exporter/opentelemetry/otelmodel/PrometheusMetricData.java index 7bf4f5e27..20603123c 100644 --- a/prometheus-metrics-exporter-opentelemetry/src/main/java/io/prometheus/metrics/exporter/opentelemetry/otelmodel/PrometheusMetricData.java +++ b/prometheus-metrics-exporter-opentelemetry/src/main/java/io/prometheus/metrics/exporter/opentelemetry/otelmodel/PrometheusMetricData.java @@ -8,14 +8,15 @@ import io.opentelemetry.sdk.resources.Resource; import io.prometheus.metrics.model.snapshots.MetricMetadata; import io.prometheus.metrics.model.snapshots.Unit; +import javax.annotation.Nullable; class PrometheusMetricData> implements MetricData { private final Resource resource; private final InstrumentationScopeInfo instrumentationScopeInfo; private final String name; - private final String description; - private final String unit; + @Nullable private final String description; + @Nullable private final String unit; T data; PrometheusMetricData( @@ -48,7 +49,8 @@ private String getNameWithoutUnit(MetricMetadata metricMetadata) { // See // https://github.com/open-telemetry/opentelemetry-collector-contrib/blob/6cf4dec6cb42d87d8840e9f67d4acf66d4eb8fda/pkg/translator/prometheus/normalize_name.go#L19 - static String convertUnit(Unit unit) { + @Nullable + static String convertUnit(@Nullable Unit unit) { if (unit == null) { return null; } @@ -129,11 +131,13 @@ public String getName() { } @Override + @Nullable public String getDescription() { return description; } @Override + @Nullable public String getUnit() { return unit; } diff --git a/prometheus-metrics-exporter-opentelemetry/src/main/java/io/prometheus/metrics/exporter/opentelemetry/otelmodel/PrometheusNativeHistogram.java b/prometheus-metrics-exporter-opentelemetry/src/main/java/io/prometheus/metrics/exporter/opentelemetry/otelmodel/PrometheusNativeHistogram.java index c452ec1ad..61a4506e2 100644 --- a/prometheus-metrics-exporter-opentelemetry/src/main/java/io/prometheus/metrics/exporter/opentelemetry/otelmodel/PrometheusNativeHistogram.java +++ b/prometheus-metrics-exporter-opentelemetry/src/main/java/io/prometheus/metrics/exporter/opentelemetry/otelmodel/PrometheusNativeHistogram.java @@ -11,6 +11,7 @@ import java.util.List; import java.util.Objects; import java.util.stream.Collectors; +import javax.annotation.Nullable; class PrometheusNativeHistogram extends PrometheusData implements ExponentialHistogramData { @@ -36,6 +37,7 @@ public Collection getPoints() { return points; } + @Nullable private ExponentialHistogramPointData toOtelDataPoint( HistogramSnapshot.HistogramDataPointSnapshot dataPoint, long currentTimeMillis) { if (!dataPoint.hasNativeHistogramData()) { diff --git a/prometheus-metrics-exporter-opentelemetry/src/test/java/io/prometheus/metrics/exporter/opentelemetry/ExemplarTest.java b/prometheus-metrics-exporter-opentelemetry/src/test/java/io/prometheus/metrics/exporter/opentelemetry/ExemplarTest.java index 48054b192..c21f1ead4 100644 --- a/prometheus-metrics-exporter-opentelemetry/src/test/java/io/prometheus/metrics/exporter/opentelemetry/ExemplarTest.java +++ b/prometheus-metrics-exporter-opentelemetry/src/test/java/io/prometheus/metrics/exporter/opentelemetry/ExemplarTest.java @@ -45,7 +45,7 @@ class ExemplarTest { private OpenTelemetryExporter openTelemetryExporter; @BeforeEach - public void setUp() { + void setUp() { openTelemetryExporter = OpenTelemetryExporter.builder() .endpoint("http://localhost:4317") @@ -64,7 +64,7 @@ public void setUp() { } @AfterEach - public void tearDown() { + void tearDown() { PrometheusRegistry.defaultRegistry.unregister(testCounter); openTelemetryExporter.close(); } diff --git a/prometheus-metrics-exporter-opentelemetry/src/test/java/io/prometheus/metrics/exporter/opentelemetry/ExportTest.java b/prometheus-metrics-exporter-opentelemetry/src/test/java/io/prometheus/metrics/exporter/opentelemetry/ExportTest.java index 55589ce84..5ec213563 100644 --- a/prometheus-metrics-exporter-opentelemetry/src/test/java/io/prometheus/metrics/exporter/opentelemetry/ExportTest.java +++ b/prometheus-metrics-exporter-opentelemetry/src/test/java/io/prometheus/metrics/exporter/opentelemetry/ExportTest.java @@ -29,7 +29,7 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; -public class ExportTest { +class ExportTest { private static final Attributes ATTRIBUTES = Attributes.of(AttributeKey.stringKey("label"), "val", AttributeKey.stringKey("key"), "value"); diff --git a/prometheus-metrics-exporter-opentelemetry/src/test/java/io/prometheus/metrics/exporter/opentelemetry/PrometheusInstrumentationScopeTest.java b/prometheus-metrics-exporter-opentelemetry/src/test/java/io/prometheus/metrics/exporter/opentelemetry/PrometheusInstrumentationScopeTest.java index c50354b24..948dfc498 100644 --- a/prometheus-metrics-exporter-opentelemetry/src/test/java/io/prometheus/metrics/exporter/opentelemetry/PrometheusInstrumentationScopeTest.java +++ b/prometheus-metrics-exporter-opentelemetry/src/test/java/io/prometheus/metrics/exporter/opentelemetry/PrometheusInstrumentationScopeTest.java @@ -22,7 +22,6 @@ void loadInstrumentationScopeInfo() { () -> PrometheusInstrumentationScope.loadInstrumentationScopeInfo( "instrumentationScope.properties", "name", "version")) - .havingRootCause() .withMessage( "Prometheus metrics library initialization error: name not found in" + " instrumentationScope.properties in classpath."); @@ -32,7 +31,6 @@ void loadInstrumentationScopeInfo() { () -> PrometheusInstrumentationScope.loadInstrumentationScopeInfo( "instrumentationScope.properties", "instrumentationScope.name", "version")) - .havingRootCause() .withMessage( "Prometheus metrics library initialization error: version not found in" + " instrumentationScope.properties in classpath."); diff --git a/prometheus-metrics-exporter-opentelemetry/src/test/java/io/prometheus/metrics/exporter/opentelemetry/otelmodel/ExponentialHistogramPointDataImplTest.java b/prometheus-metrics-exporter-opentelemetry/src/test/java/io/prometheus/metrics/exporter/opentelemetry/otelmodel/ExponentialHistogramPointDataImplTest.java new file mode 100644 index 000000000..066913420 --- /dev/null +++ b/prometheus-metrics-exporter-opentelemetry/src/test/java/io/prometheus/metrics/exporter/opentelemetry/otelmodel/ExponentialHistogramPointDataImplTest.java @@ -0,0 +1,92 @@ +package io.prometheus.metrics.exporter.opentelemetry.otelmodel; + +import static org.assertj.core.api.Assertions.assertThat; + +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.sdk.metrics.data.ExponentialHistogramBuckets; +import java.util.List; +import org.junit.jupiter.api.Test; + +class ExponentialHistogramPointDataImplTest { + + private static final ExponentialHistogramBuckets EMPTY_BUCKETS = + ExponentialHistogramBuckets.create(0, 0, List.of()); + + @Test + void hasMinReturnsTrueWhenMinIsNotNaN() { + ExponentialHistogramPointDataImpl histogramPoint = + new ExponentialHistogramPointDataImpl( + 0, + 10.0, + 5, + 0, + 1.0, + 5.0, + EMPTY_BUCKETS, + EMPTY_BUCKETS, + 0L, + 1L, + Attributes.empty(), + List.of()); + assertThat(histogramPoint.hasMin()).isTrue(); + assertThat(histogramPoint.getMin()).isEqualTo(1.0); + } + + @Test + void hasMinReturnsFalseWhenMinIsNaN() { + ExponentialHistogramPointDataImpl histogramPoint = + new ExponentialHistogramPointDataImpl( + 0, + 10.0, + 5, + 0, + Double.NaN, + 5.0, + EMPTY_BUCKETS, + EMPTY_BUCKETS, + 0L, + 1L, + Attributes.empty(), + List.of()); + assertThat(histogramPoint.hasMin()).isFalse(); + } + + @Test + void hasMaxReturnsTrueWhenMaxIsNotNaN() { + ExponentialHistogramPointDataImpl histogramPoint = + new ExponentialHistogramPointDataImpl( + 0, + 10.0, + 5, + 0, + 1.0, + 5.0, + EMPTY_BUCKETS, + EMPTY_BUCKETS, + 0L, + 1L, + Attributes.empty(), + List.of()); + assertThat(histogramPoint.hasMax()).isTrue(); + assertThat(histogramPoint.getMax()).isEqualTo(5.0); + } + + @Test + void hasMaxReturnsFalseWhenMaxIsNaN() { + ExponentialHistogramPointDataImpl histogramPoint = + new ExponentialHistogramPointDataImpl( + 0, + 10.0, + 5, + 0, + 1.0, + Double.NaN, + EMPTY_BUCKETS, + EMPTY_BUCKETS, + 0L, + 1L, + Attributes.empty(), + List.of()); + assertThat(histogramPoint.hasMax()).isFalse(); + } +} diff --git a/prometheus-metrics-exporter-opentelemetry/src/test/java/io/prometheus/metrics/exporter/opentelemetry/otelmodel/HistogramPointDataImplTest.java b/prometheus-metrics-exporter-opentelemetry/src/test/java/io/prometheus/metrics/exporter/opentelemetry/otelmodel/HistogramPointDataImplTest.java new file mode 100644 index 000000000..b5c4e9373 --- /dev/null +++ b/prometheus-metrics-exporter-opentelemetry/src/test/java/io/prometheus/metrics/exporter/opentelemetry/otelmodel/HistogramPointDataImplTest.java @@ -0,0 +1,80 @@ +package io.prometheus.metrics.exporter.opentelemetry.otelmodel; + +import static org.assertj.core.api.Assertions.assertThat; + +import io.opentelemetry.api.common.Attributes; +import java.util.List; +import org.junit.jupiter.api.Test; + +class HistogramPointDataImplTest { + + @Test + void hasMinReturnsTrueWhenMinIsNotNaN() { + HistogramPointDataImpl histogramPoint = + new HistogramPointDataImpl( + 10.0, + 5, + 1.0, + 5.0, + List.of(1.0, 2.0), + List.of(2L, 3L), + 0L, + 1L, + Attributes.empty(), + List.of()); + assertThat(histogramPoint.hasMin()).isTrue(); + assertThat(histogramPoint.getMin()).isEqualTo(1.0); + } + + @Test + void hasMinReturnsFalseWhenMinIsNaN() { + HistogramPointDataImpl histogramPoint = + new HistogramPointDataImpl( + 10.0, + 5, + Double.NaN, + 5.0, + List.of(1.0, 2.0), + List.of(2L, 3L), + 0L, + 1L, + Attributes.empty(), + List.of()); + assertThat(histogramPoint.hasMin()).isFalse(); + } + + @Test + void hasMaxReturnsTrueWhenMaxIsNotNaN() { + HistogramPointDataImpl histogramPoint = + new HistogramPointDataImpl( + 10.0, + 5, + 1.0, + 5.0, + List.of(1.0, 2.0), + List.of(2L, 3L), + 0L, + 1L, + Attributes.empty(), + List.of()); + assertThat(histogramPoint.hasMax()).isTrue(); + assertThat(histogramPoint.getMax()).isEqualTo(5.0); + } + + @Test + void hasMaxReturnsFalseWhenMaxIsNaN() { + HistogramPointDataImpl histogramPoint = + new HistogramPointDataImpl( + 10.0, + 5, + 1.0, + Double.NaN, + List.of(1.0, 2.0), + List.of(2L, 3L), + 0L, + 1L, + Attributes.empty(), + List.of()); + assertThat(histogramPoint.hasMax()).isFalse(); + } +} diff --git a/prometheus-metrics-exporter-pushgateway/pom.xml b/prometheus-metrics-exporter-pushgateway/pom.xml index 5356a13d9..c466c8552 100644 --- a/prometheus-metrics-exporter-pushgateway/pom.xml +++ b/prometheus-metrics-exporter-pushgateway/pom.xml @@ -6,7 +6,7 @@ io.prometheus client_java - 1.4.0-SNAPSHOT + 1.6.0-SNAPSHOT prometheus-metrics-exporter-pushgateway diff --git a/prometheus-metrics-exporter-pushgateway/src/main/java/io/prometheus/metrics/exporter/pushgateway/DefaultJobLabelDetector.java b/prometheus-metrics-exporter-pushgateway/src/main/java/io/prometheus/metrics/exporter/pushgateway/DefaultJobLabelDetector.java index 59a11a473..6caa03492 100644 --- a/prometheus-metrics-exporter-pushgateway/src/main/java/io/prometheus/metrics/exporter/pushgateway/DefaultJobLabelDetector.java +++ b/prometheus-metrics-exporter-pushgateway/src/main/java/io/prometheus/metrics/exporter/pushgateway/DefaultJobLabelDetector.java @@ -4,6 +4,7 @@ import java.nio.file.InvalidPathException; import java.nio.file.Path; import java.nio.file.Paths; +import javax.annotation.Nullable; /** * The default {@code job} label is the name of the JAR file being executed. @@ -27,6 +28,7 @@ private static String getServiceName(Path jarPath) { return dotIndex == -1 ? jarName : jarName.substring(0, dotIndex); } + @Nullable private static Path getJarPathFromSunCommandLine() { String programArguments = System.getProperty("sun.java.command"); if (programArguments == null) { @@ -48,6 +50,7 @@ private static Path getJarPathFromSunCommandLine() { } } + @Nullable private static Path pathIfExists(String programArguments) { Path candidate; try { diff --git a/prometheus-metrics-exporter-pushgateway/src/main/java/io/prometheus/metrics/exporter/pushgateway/PushGateway.java b/prometheus-metrics-exporter-pushgateway/src/main/java/io/prometheus/metrics/exporter/pushgateway/PushGateway.java index 1f7262368..594132cf2 100644 --- a/prometheus-metrics-exporter-pushgateway/src/main/java/io/prometheus/metrics/exporter/pushgateway/PushGateway.java +++ b/prometheus-metrics-exporter-pushgateway/src/main/java/io/prometheus/metrics/exporter/pushgateway/PushGateway.java @@ -1,7 +1,10 @@ package io.prometheus.metrics.exporter.pushgateway; import static io.prometheus.metrics.exporter.pushgateway.Scheme.HTTP; +import static io.prometheus.metrics.model.snapshots.PrometheusNaming.escapeName; +import static java.util.Objects.requireNonNull; +import io.prometheus.metrics.config.EscapingScheme; import io.prometheus.metrics.config.ExporterPushgatewayProperties; import io.prometheus.metrics.config.PrometheusProperties; import io.prometheus.metrics.config.PrometheusPropertiesException; @@ -24,11 +27,13 @@ import java.net.URLEncoder; import java.net.UnknownHostException; import java.nio.charset.StandardCharsets; +import java.time.Duration; import java.util.Base64; import java.util.Collections; import java.util.HashMap; import java.util.Map; import java.util.TreeMap; +import javax.annotation.Nullable; /** * Export metrics via the Prometheus @@ -75,15 +80,15 @@ * href="https://github.com/prometheus/pushgateway">https://github.com/prometheus/pushgateway. */ public class PushGateway { - - private static final int MILLISECONDS_PER_SECOND = 1000; - private final URL url; private final ExpositionFormatWriter writer; private final boolean prometheusTimestampsInMs; private final Map requestHeaders; private final PrometheusRegistry registry; private final HttpConnectionFactory connectionFactory; + private final EscapingScheme escapingScheme; + private final Duration connectionTimeout; + private final Duration readTimeout; private PushGateway( PrometheusRegistry registry, @@ -91,12 +96,18 @@ private PushGateway( URL url, HttpConnectionFactory connectionFactory, Map requestHeaders, - boolean prometheusTimestampsInMs) { + boolean prometheusTimestampsInMs, + EscapingScheme escapingScheme, + Duration connectionTimeout, + Duration readTimeout) { this.registry = registry; this.url = url; this.requestHeaders = Collections.unmodifiableMap(new HashMap<>(requestHeaders)); this.connectionFactory = connectionFactory; this.prometheusTimestampsInMs = prometheusTimestampsInMs; + this.escapingScheme = escapingScheme; + this.connectionTimeout = connectionTimeout; + this.readTimeout = readTimeout; writer = getWriter(format); if (!writer.isAvailable()) { throw new RuntimeException(writer.getClass() + " is not available"); @@ -189,7 +200,7 @@ public void delete() throws IOException { doRequest(null, "DELETE"); } - private void doRequest(PrometheusRegistry registry, String method) throws IOException { + private void doRequest(@Nullable PrometheusRegistry registry, String method) throws IOException { try { HttpURLConnection connection = connectionFactory.create(url); requestHeaders.forEach(connection::setRequestProperty); @@ -199,14 +210,14 @@ private void doRequest(PrometheusRegistry registry, String method) throws IOExce } connection.setRequestMethod(method); - connection.setConnectTimeout(10 * MILLISECONDS_PER_SECOND); - connection.setReadTimeout(10 * MILLISECONDS_PER_SECOND); + connection.setConnectTimeout((int) this.connectionTimeout.toMillis()); + connection.setReadTimeout((int) this.readTimeout.toMillis()); connection.connect(); try { if (!method.equals("DELETE")) { OutputStream outputStream = connection.getOutputStream(); - writer.write(outputStream, registry.scrape()); + writer.write(outputStream, requireNonNull(registry).scrape(), this.escapingScheme); outputStream.flush(); outputStream.close(); } @@ -242,6 +253,8 @@ private void doRequest(PrometheusRegistry registry, String method) throws IOExce } } + // toString with Charset is only available in Java 10+, but we want to support Java 8 + @SuppressWarnings("JdkObsolete") private static String readFromStream(InputStream is) throws IOException { ByteArrayOutputStream result = new ByteArrayOutputStream(); byte[] buffer = new byte[1024]; @@ -266,15 +279,18 @@ public static Builder builder(PrometheusProperties config) { public static class Builder { private final PrometheusProperties config; - private Format format; - private String address; - private Scheme scheme; - private String job; + @Nullable private Format format; + @Nullable private String address; + @Nullable private Scheme scheme; + @Nullable private String job; + @Nullable private Duration connectionTimeout; + @Nullable private Duration readTimeout; private boolean prometheusTimestampsInMs; private final Map requestHeaders = new HashMap<>(); private PrometheusRegistry registry = PrometheusRegistry.defaultRegistry; private HttpConnectionFactory connectionFactory = new DefaultHttpConnectionFactory(); - private Map groupingKey = new TreeMap<>(); + private final Map groupingKey = new TreeMap<>(); + @Nullable private EscapingScheme escapingScheme; private Builder(PrometheusProperties config) { this.config = config; @@ -282,10 +298,7 @@ private Builder(PrometheusProperties config) { /** Default is {@link Format#PROMETHEUS_PROTOBUF}. */ public Builder format(Format format) { - if (format == null) { - throw new NullPointerException(); - } - this.format = format; + this.format = requireNonNull(format, "format must not be null"); return this; } @@ -295,19 +308,17 @@ public Builder format(Format format) { * property. */ public Builder address(String address) { - if (address == null) { - throw new NullPointerException(); - } - this.address = address; + this.address = requireNonNull(address, "address must not be null"); return this; } /** Username and password for HTTP basic auth when pushing to the Pushgateway. */ public Builder basicAuth(String user, String password) { - if (user == null || password == null) { - throw new NullPointerException(); - } - byte[] credentialsBytes = (user + ":" + password).getBytes(StandardCharsets.UTF_8); + byte[] credentialsBytes = + (requireNonNull(user, "user must not be null") + + ":" + + requireNonNull(password, "password must not be null")) + .getBytes(StandardCharsets.UTF_8); String encoded = Base64.getEncoder().encodeToString(credentialsBytes); requestHeaders.put("Authorization", String.format("Basic %s", encoded)); return this; @@ -315,10 +326,9 @@ public Builder basicAuth(String user, String password) { /** Bearer token authorization when pushing to the Pushgateway. */ public Builder bearerToken(String token) { - if (token == null) { - throw new NullPointerException(); - } - requestHeaders.put("Authorization", String.format("Bearer %s", token)); + requestHeaders.put( + "Authorization", + String.format("Bearer %s", requireNonNull(token, "token must not be null"))); return this; } @@ -327,10 +337,7 @@ public Builder bearerToken(String token) { * at runtime with the {@code io.prometheus.exporter.pushgateway.scheme} property. */ public Builder scheme(Scheme scheme) { - if (scheme == null) { - throw new NullPointerException(); - } - this.scheme = scheme; + this.scheme = requireNonNull(scheme, "scheme must not be null"); return this; } @@ -341,10 +348,8 @@ public Builder scheme(Scheme scheme) { * of a custom connection factory that skips SSL certificate validation for HTTPS connections. */ public Builder connectionFactory(HttpConnectionFactory connectionFactory) { - if (connectionFactory == null) { - throw new NullPointerException(); - } - this.connectionFactory = connectionFactory; + this.connectionFactory = + requireNonNull(connectionFactory, "connectionFactory must not be null"); return this; } @@ -354,10 +359,7 @@ public Builder connectionFactory(HttpConnectionFactory connectionFactory) { * io.prometheus.exporter.pushgateway.job} property. */ public Builder job(String job) { - if (job == null) { - throw new NullPointerException(); - } - this.job = job; + this.job = requireNonNull(job, "job must not be null"); return this; } @@ -366,10 +368,9 @@ public Builder job(String job) { * adding multiple grouping keys. */ public Builder groupingKey(String name, String value) { - if (name == null || value == null) { - throw new NullPointerException(); - } - groupingKey.put(name, value); + groupingKey.put( + requireNonNull(name, "name must not be null"), + requireNonNull(value, "value must not be null")); return this; } @@ -380,29 +381,78 @@ public Builder instanceIpGroupingKey() throws UnknownHostException { /** Push metrics from this registry instead of {@link PrometheusRegistry#defaultRegistry}. */ public Builder registry(PrometheusRegistry registry) { - if (registry == null) { - throw new NullPointerException(); - } - this.registry = registry; + this.registry = requireNonNull(registry, "registry must not be null"); + return this; + } + + /** + * Specify the escaping scheme to be used when pushing metrics. Default is {@link + * EscapingScheme#UNDERSCORE_ESCAPING}. + */ + public Builder escapingScheme(EscapingScheme escapingScheme) { + this.escapingScheme = requireNonNull(escapingScheme, "escapingScheme must not be null"); return this; } /** * Use milliseconds for timestamps in text format? Default is {@code false}. Can be overwritten - * at runtime with the {@code io.prometheus.exporter.timestampsInMs} property. + * at runtime with the {@code io.prometheus.exporter.timestamps_in_ms} property. */ public Builder prometheusTimestampsInMs(boolean prometheusTimestampsInMs) { this.prometheusTimestampsInMs = prometheusTimestampsInMs; return this; } + /** + * Specify the connection timeout for HTTP connections to the PushGateway. Default is 10 + * seconds. + * + * @param connectionTimeout timeout value + * @return this {@link Builder} instance + */ + public Builder connectionTimeout(Duration connectionTimeout) { + this.connectionTimeout = connectionTimeout; + return this; + } + + private Duration getConnectionTimeout(@Nullable ExporterPushgatewayProperties properties) { + if (properties != null && properties.getConnectTimeout() != null) { + return properties.getConnectTimeout(); + } else if (this.connectionTimeout != null) { + return this.connectionTimeout; + } else { + return Duration.ofSeconds(10); + } + } + + /** + * Specify the read timeout for HTTP connections to the PushGateway. Default is 10 seconds. + * + * @param readTimeout timeout value + * @return this {@link Builder} instance + */ + public Builder readTimeout(Duration readTimeout) { + this.readTimeout = readTimeout; + return this; + } + + private Duration getReadTimeout(@Nullable ExporterPushgatewayProperties properties) { + if (properties != null && properties.getReadTimeout() != null) { + return properties.getReadTimeout(); + } else if (this.readTimeout != null) { + return this.readTimeout; + } else { + return Duration.ofSeconds(10); + } + } + private boolean getPrometheusTimestampsInMs() { // accept either to opt in to timestamps in milliseconds return config.getExporterProperties().getPrometheusTimestampsInMs() || this.prometheusTimestampsInMs; } - private Scheme getScheme(ExporterPushgatewayProperties properties) { + private Scheme getScheme(@Nullable ExporterPushgatewayProperties properties) { if (properties != null && properties.getScheme() != null) { return Scheme.valueOf(properties.getScheme()); } else if (this.scheme != null) { @@ -412,7 +462,7 @@ private Scheme getScheme(ExporterPushgatewayProperties properties) { } } - private String getAddress(ExporterPushgatewayProperties properties) { + private String getAddress(@Nullable ExporterPushgatewayProperties properties) { if (properties != null && properties.getAddress() != null) { return properties.getAddress(); } else if (this.address != null) { @@ -422,7 +472,7 @@ private String getAddress(ExporterPushgatewayProperties properties) { } } - private String getJob(ExporterPushgatewayProperties properties) { + private String getJob(@Nullable ExporterPushgatewayProperties properties) { if (properties != null && properties.getJob() != null) { return properties.getJob(); } else if (this.job != null) { @@ -432,6 +482,15 @@ private String getJob(ExporterPushgatewayProperties properties) { } } + private EscapingScheme getEscapingScheme(@Nullable ExporterPushgatewayProperties properties) { + if (properties != null && properties.getEscapingScheme() != null) { + return properties.getEscapingScheme(); + } else if (this.escapingScheme != null) { + return this.escapingScheme; + } + return EscapingScheme.UNDERSCORE_ESCAPING; + } + private Format getFormat() { // currently not configurable via properties if (this.format != null) { @@ -440,27 +499,36 @@ private Format getFormat() { return Format.PROMETHEUS_PROTOBUF; } - private URL makeUrl(ExporterPushgatewayProperties properties) + // encode with Charset is only available in Java 10+, but we want to support Java 8 + @SuppressWarnings("JdkObsolete") + private URL makeUrl(@Nullable ExporterPushgatewayProperties properties) throws UnsupportedEncodingException, MalformedURLException { - String url = getScheme(properties) + "://" + getAddress(properties) + "/metrics/"; + StringBuilder url = + new StringBuilder(getScheme(properties) + "://" + getAddress(properties) + "/metrics/"); String job = getJob(properties); if (job.contains("/")) { - url += "job@base64/" + base64url(job); + url.append("job@base64/").append(base64url(job)); } else { - url += "job/" + URLEncoder.encode(job, "UTF-8"); + url.append("job/").append(URLEncoder.encode(job, "UTF-8")); } - if (groupingKey != null) { - for (Map.Entry entry : groupingKey.entrySet()) { - if (entry.getValue().isEmpty()) { - url += "/" + entry.getKey() + "@base64/="; - } else if (entry.getValue().contains("/")) { - url += "/" + entry.getKey() + "@base64/" + base64url(entry.getValue()); - } else { - url += "/" + entry.getKey() + "/" + URLEncoder.encode(entry.getValue(), "UTF-8"); - } + for (Map.Entry entry : groupingKey.entrySet()) { + if (entry.getValue().isEmpty()) { + url.append("/") + .append(escapeName(entry.getKey(), EscapingScheme.VALUE_ENCODING_ESCAPING)) + .append("@base64/="); + } else if (entry.getValue().contains("/")) { + url.append("/") + .append(escapeName(entry.getKey(), EscapingScheme.VALUE_ENCODING_ESCAPING)) + .append("@base64/") + .append(base64url(entry.getValue())); + } else { + url.append("/") + .append(escapeName(entry.getKey(), EscapingScheme.VALUE_ENCODING_ESCAPING)) + .append("/") + .append(URLEncoder.encode(entry.getValue(), "UTF-8")); } } - return URI.create(url).normalize().toURL(); + return URI.create(url.toString()).normalize().toURL(); } private String base64url(String v) { @@ -480,7 +548,10 @@ public PushGateway build() { makeUrl(properties), connectionFactory, requestHeaders, - getPrometheusTimestampsInMs()); + getPrometheusTimestampsInMs(), + getEscapingScheme(properties), + getConnectionTimeout(properties), + getReadTimeout(properties)); } catch (MalformedURLException e) { throw new PrometheusPropertiesException( address + ": Invalid address. Expecting :"); diff --git a/prometheus-metrics-exporter-pushgateway/src/test/java/io/prometheus/metrics/exporter/pushgateway/BasicAuthPushGatewayTest.java b/prometheus-metrics-exporter-pushgateway/src/test/java/io/prometheus/metrics/exporter/pushgateway/BasicAuthPushGatewayTest.java index ce0fec354..137686d32 100644 --- a/prometheus-metrics-exporter-pushgateway/src/test/java/io/prometheus/metrics/exporter/pushgateway/BasicAuthPushGatewayTest.java +++ b/prometheus-metrics-exporter-pushgateway/src/test/java/io/prometheus/metrics/exporter/pushgateway/BasicAuthPushGatewayTest.java @@ -3,6 +3,7 @@ import static org.mockserver.model.HttpRequest.request; import static org.mockserver.model.HttpResponse.response; +import io.prometheus.metrics.config.EscapingScheme; import io.prometheus.metrics.core.metrics.Gauge; import io.prometheus.metrics.model.registry.PrometheusRegistry; import java.io.IOException; @@ -20,7 +21,7 @@ class BasicAuthPushGatewayTest { PushGateway pushGateway; @BeforeEach - public void setUp() { + void setUp() { mockServerClient = ClientAndServer.startClientAndServer(0); registry = new PrometheusRegistry(); gauge = Gauge.builder().name("g").help("help").build(); @@ -30,6 +31,7 @@ public void setUp() { .basicAuth("testUser", "testPwd") .registry(registry) .prometheusTimestampsInMs(true) + .escapingScheme(EscapingScheme.ALLOW_UTF8) .job("j") .build(); } @@ -40,7 +42,7 @@ void tearDown() { } @Test - public void testAuthorizedPush() throws IOException { + void testAuthorizedPush() throws IOException { mockServerClient .when( request() diff --git a/prometheus-metrics-exporter-pushgateway/src/test/java/io/prometheus/metrics/exporter/pushgateway/BearerTokenPushGatewayTest.java b/prometheus-metrics-exporter-pushgateway/src/test/java/io/prometheus/metrics/exporter/pushgateway/BearerTokenPushGatewayTest.java index 09e6ff5e9..996442e5a 100644 --- a/prometheus-metrics-exporter-pushgateway/src/test/java/io/prometheus/metrics/exporter/pushgateway/BearerTokenPushGatewayTest.java +++ b/prometheus-metrics-exporter-pushgateway/src/test/java/io/prometheus/metrics/exporter/pushgateway/BearerTokenPushGatewayTest.java @@ -21,7 +21,7 @@ class BearerTokenPushGatewayTest { PushGateway pushGateway; @BeforeEach - public void setUp() { + void setUp() { mockServerClient = ClientAndServer.startClientAndServer(0); registry = new PrometheusRegistry(); gauge = Gauge.builder().name("g").help("help").build(); @@ -40,7 +40,7 @@ void tearDown() { } @Test - public void testAuthorizedPush() throws IOException { + void testAuthorizedPush() throws IOException { mockServerClient .when( request() diff --git a/prometheus-metrics-exporter-pushgateway/src/test/java/io/prometheus/metrics/exporter/pushgateway/PushGatewayTest.java b/prometheus-metrics-exporter-pushgateway/src/test/java/io/prometheus/metrics/exporter/pushgateway/PushGatewayTest.java index 27617913b..bae8fdd91 100644 --- a/prometheus-metrics-exporter-pushgateway/src/test/java/io/prometheus/metrics/exporter/pushgateway/PushGatewayTest.java +++ b/prometheus-metrics-exporter-pushgateway/src/test/java/io/prometheus/metrics/exporter/pushgateway/PushGatewayTest.java @@ -1,10 +1,11 @@ package io.prometheus.metrics.exporter.pushgateway; -import static org.assertj.core.api.Assertions.*; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.mockserver.model.HttpRequest.request; import static org.mockserver.model.HttpResponse.response; +import io.prometheus.metrics.config.EscapingScheme; import io.prometheus.metrics.core.metrics.Gauge; import io.prometheus.metrics.model.registry.PrometheusRegistry; import java.io.IOException; @@ -25,7 +26,7 @@ class PushGatewayTest { Gauge gauge; @BeforeEach - public void setUp() { + void setUp() { mockServerClient = ClientAndServer.startClientAndServer(0); registry = new PrometheusRegistry(); gauge = Gauge.builder().name("g").help("help").build(); @@ -37,7 +38,7 @@ void tearDown() { } @Test - public void testInvalidURLThrowsRuntimeException() { + void testInvalidURLThrowsRuntimeException() { assertThatExceptionOfType(RuntimeException.class) .isThrownBy( () -> { @@ -48,8 +49,7 @@ public void testInvalidURLThrowsRuntimeException() { } @Test - public void testMultipleSlashesAreStrippedFromURL() - throws NoSuchFieldException, IllegalAccessException { + void testMultipleSlashesAreStrippedFromURL() throws NoSuchFieldException, IllegalAccessException { final PushGateway pushGateway = PushGateway.builder().address("example.com:1234/context///path//").job("test").build(); assertThat(getUrl(pushGateway)) @@ -63,7 +63,7 @@ private URL getUrl(PushGateway pushGateway) throws IllegalAccessException, NoSuc } @Test - public void testPush() throws IOException { + void testPush() throws IOException { mockServerClient .when(request().withMethod("PUT").withPath("/metrics/job/j")) .respond(response().withStatusCode(202)); @@ -77,7 +77,7 @@ public void testPush() throws IOException { } @Test - public void testPush200Response() throws IOException { + void testPush200Response() throws IOException { mockServerClient .when(request().withMethod("PUT").withPath("/metrics/job/j")) .respond(response().withStatusCode(200)); @@ -91,7 +91,7 @@ public void testPush200Response() throws IOException { } @Test - public void testNon202ResponseThrows() { + void testNon202ResponseThrows() { mockServerClient .when(request().withMethod("PUT").withPath("/metrics/job/j")) .respond(response().withStatusCode(500)); @@ -113,7 +113,7 @@ public void testNon202ResponseThrows() { } @Test - public void testPushCollector() throws IOException { + void testPushCollector() throws IOException { mockServerClient .when(request().withMethod("PUT").withPath("/metrics/job/j")) .respond(response().withStatusCode(202)); @@ -127,7 +127,7 @@ public void testPushCollector() throws IOException { } @Test - public void testPushWithGroupingKey() throws IOException { + void testPushWithGroupingKey() throws IOException { mockServerClient .when(request().withMethod("PUT").withPath("/metrics/job/j/l/v")) .respond(response().withStatusCode(202)); @@ -142,7 +142,22 @@ public void testPushWithGroupingKey() throws IOException { } @Test - public void testPushWithMultiGroupingKey() throws IOException { + void testPushWithEscapedGroupingKey() throws IOException { + mockServerClient + .when(request().withMethod("PUT").withPath("/metrics/job/j/U__l_2e_1/v1")) + .respond(response().withStatusCode(202)); + PushGateway pg = + PushGateway.builder() + .address("localhost:" + mockServerClient.getPort()) + .registry(registry) + .job("j") + .groupingKey("l.1", "v1") + .build(); + pg.push(); + } + + @Test + void testPushWithMultiGroupingKey() throws IOException { mockServerClient .when(request().withMethod("PUT").withPath("/metrics/job/j/l/v/l2/v2")) .respond(response().withStatusCode(202)); @@ -158,7 +173,24 @@ public void testPushWithMultiGroupingKey() throws IOException { } @Test - public void testPushWithEmptyLabelGroupingKey() throws IOException { + void testPushWithMultiEscapedGroupingKey() throws IOException { + + mockServerClient + .when(request().withMethod("PUT").withPath("/metrics/job/j/U__l_2e_1/v1/U__l_2e_2/v2")) + .respond(response().withStatusCode(202)); + PushGateway pg = + PushGateway.builder() + .address("localhost:" + mockServerClient.getPort()) + .registry(registry) + .job("j") + .groupingKey("l.1", "v1") + .groupingKey("l.2", "v2") + .build(); + pg.push(); + } + + @Test + void testPushWithEmptyLabelGroupingKey() throws IOException { mockServerClient .when(request().withMethod("PUT").withPath("/metrics/job/j/l/v/l2@base64/=")) .respond(response().withStatusCode(202)); @@ -174,7 +206,7 @@ public void testPushWithEmptyLabelGroupingKey() throws IOException { } @Test - public void testPushWithGroupingKeyWithSlashes() throws IOException { + void testPushWithGroupingKeyWithSlashes() throws IOException { mockServerClient .when( request().withMethod("PUT").withPath("/metrics/job@base64/YS9i/l/v/l2@base64/75-_Lw==")) @@ -191,7 +223,7 @@ public void testPushWithGroupingKeyWithSlashes() throws IOException { } @Test - public void testPushCollectorWithGroupingKey() throws IOException { + void testPushCollectorWithGroupingKey() throws IOException { mockServerClient .when(request().withMethod("PUT").withPath("/metrics/job/j/l/v")) .respond(response().withStatusCode(202)); @@ -206,7 +238,22 @@ public void testPushCollectorWithGroupingKey() throws IOException { } @Test - public void testPushAdd() throws IOException { + void testPushCollectorWithEscapedGroupingKey() throws IOException { + mockServerClient + .when(request().withMethod("PUT").withPath("/metrics/job/j/U__l_2e_1/v1")) + .respond(response().withStatusCode(202)); + PushGateway pg = + PushGateway.builder() + .address("localhost:" + mockServerClient.getPort()) + .registry(registry) + .job("j") + .groupingKey("l.1", "v1") + .build(); + pg.push(gauge); + } + + @Test + void testPushAdd() throws IOException { mockServerClient .when(request().withMethod("POST").withPath("/metrics/job/j")) .respond(response().withStatusCode(202)); @@ -220,7 +267,7 @@ public void testPushAdd() throws IOException { } @Test - public void testPushAddCollector() throws IOException { + void testPushAddCollector() throws IOException { mockServerClient .when(request().withMethod("POST").withPath("/metrics/job/j")) .respond(response().withStatusCode(202)); @@ -230,7 +277,7 @@ public void testPushAddCollector() throws IOException { } @Test - public void testPushAddWithGroupingKey() throws IOException { + void testPushAddWithGroupingKey() throws IOException { mockServerClient .when(request().withMethod("POST").withPath("/metrics/job/j/l/v")) .respond(response().withStatusCode(202)); @@ -245,7 +292,22 @@ public void testPushAddWithGroupingKey() throws IOException { } @Test - public void testPushAddCollectorWithGroupingKey() throws IOException { + void testPushAddWithEscapedGroupingKey() throws IOException { + mockServerClient + .when(request().withMethod("POST").withPath("/metrics/job/j/U__l_2e_1/v1")) + .respond(response().withStatusCode(202)); + PushGateway pg = + PushGateway.builder() + .address("localhost:" + mockServerClient.getPort()) + .registry(registry) + .groupingKey("l.1", "v1") + .job("j") + .build(); + pg.pushAdd(); + } + + @Test + void testPushAddCollectorWithGroupingKey() throws IOException { mockServerClient .when(request().withMethod("POST").withPath("/metrics/job/j/l/v")) .respond(response().withStatusCode(202)); @@ -260,7 +322,23 @@ public void testPushAddCollectorWithGroupingKey() throws IOException { } @Test - public void testDelete() throws IOException { + void testPushAddCollectorWithEscapedGroupingKey() throws IOException { + + mockServerClient + .when(request().withMethod("POST").withPath("/metrics/job/j/U__l_2e_1/v1")) + .respond(response().withStatusCode(202)); + PushGateway pg = + PushGateway.builder() + .address("localhost:" + mockServerClient.getPort()) + .registry(registry) + .groupingKey("l.1", "v1") + .job("j") + .build(); + pg.pushAdd(gauge); + } + + @Test + void testDelete() throws IOException { mockServerClient .when(request().withMethod("DELETE").withPath("/metrics/job/j")) .respond(response().withStatusCode(202)); @@ -270,7 +348,7 @@ public void testDelete() throws IOException { } @Test - public void testDeleteWithGroupingKey() throws IOException { + void testDeleteWithGroupingKey() throws IOException { mockServerClient .when(request().withMethod("DELETE").withPath("/metrics/job/j/l/v")) .respond(response().withStatusCode(202)); @@ -284,7 +362,22 @@ public void testDeleteWithGroupingKey() throws IOException { } @Test - public void testInstanceIpGroupingKey() throws IOException { + void testDeleteWithEscapedGroupingKey() throws IOException { + + mockServerClient + .when(request().withMethod("DELETE").withPath("/metrics/job/j/U__l_2e_1/v1")) + .respond(response().withStatusCode(202)); + PushGateway pg = + PushGateway.builder() + .address("localhost:" + mockServerClient.getPort()) + .job("j") + .groupingKey("l.1", "v1") + .build(); + pg.delete(); + } + + @Test + void testInstanceIpGroupingKey() throws IOException { String ip = InetAddress.getLocalHost().getHostAddress(); assertThat(ip).isNotEmpty(); mockServerClient @@ -299,4 +392,40 @@ public void testInstanceIpGroupingKey() throws IOException { .build(); pg.delete(); } + + @Test + void testInstanceIpEscapedGroupingKey() throws IOException { + + String ip = InetAddress.getLocalHost().getHostAddress(); + assertThat(ip).isNotEmpty(); + mockServerClient + .when( + request() + .withMethod("DELETE") + .withPath("/metrics/job/j/instance/" + ip + "/U__l_2e_1/v1")) + .respond(response().withStatusCode(202)); + PushGateway pg = + PushGateway.builder() + .address("localhost:" + mockServerClient.getPort()) + .job("j") + .groupingKey("l.1", "v1") + .instanceIpGroupingKey() + .build(); + pg.delete(); + } + + @Test + void testEscapingSchemeDefaultValue() throws IllegalAccessException, NoSuchFieldException { + PushGateway pg = + PushGateway.builder() + .address("localhost:" + mockServerClient.getPort()) + .job("test") + .build(); + + Field escapingSchemeField = pg.getClass().getDeclaredField("escapingScheme"); + escapingSchemeField.setAccessible(true); + EscapingScheme scheme = (EscapingScheme) escapingSchemeField.get(pg); + + assertThat(scheme).isEqualTo(EscapingScheme.UNDERSCORE_ESCAPING); + } } diff --git a/prometheus-metrics-exporter-pushgateway/src/test/java/io/prometheus/metrics/exporter/pushgateway/SchemeTest.java b/prometheus-metrics-exporter-pushgateway/src/test/java/io/prometheus/metrics/exporter/pushgateway/SchemeTest.java index 6695a2911..0f6bf61d9 100644 --- a/prometheus-metrics-exporter-pushgateway/src/test/java/io/prometheus/metrics/exporter/pushgateway/SchemeTest.java +++ b/prometheus-metrics-exporter-pushgateway/src/test/java/io/prometheus/metrics/exporter/pushgateway/SchemeTest.java @@ -9,6 +9,8 @@ class SchemeTest { @Test void fromString() { + assertThat(Scheme.fromString("http")).isEqualTo(Scheme.HTTP); + assertThat(Scheme.fromString("https")).isEqualTo(Scheme.HTTPS); assertThat(Scheme.HTTP).hasToString("http"); assertThat(Scheme.HTTPS).hasToString("https"); assertThatExceptionOfType(IllegalArgumentException.class) diff --git a/prometheus-metrics-exporter-servlet-jakarta/pom.xml b/prometheus-metrics-exporter-servlet-jakarta/pom.xml index 546fa5c59..6dd93f190 100644 --- a/prometheus-metrics-exporter-servlet-jakarta/pom.xml +++ b/prometheus-metrics-exporter-servlet-jakarta/pom.xml @@ -6,7 +6,7 @@ io.prometheus client_java - 1.4.0-SNAPSHOT + 1.6.0-SNAPSHOT prometheus-metrics-exporter-servlet-jakarta @@ -34,5 +34,13 @@ 6.1.0 provided + + + + io.prometheus + prometheus-metrics-core + ${project.version} + test + diff --git a/prometheus-metrics-exporter-servlet-jakarta/src/test/java/io/prometheus/metrics/exporter/servlet/jakarta/HttpExchangeAdapterTest.java b/prometheus-metrics-exporter-servlet-jakarta/src/test/java/io/prometheus/metrics/exporter/servlet/jakarta/HttpExchangeAdapterTest.java new file mode 100644 index 000000000..7a795cc04 --- /dev/null +++ b/prometheus-metrics-exporter-servlet-jakarta/src/test/java/io/prometheus/metrics/exporter/servlet/jakarta/HttpExchangeAdapterTest.java @@ -0,0 +1,165 @@ +package io.prometheus.metrics.exporter.servlet.jakarta; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import io.prometheus.metrics.exporter.common.PrometheusHttpRequest; +import io.prometheus.metrics.exporter.common.PrometheusHttpResponse; +import jakarta.servlet.ServletOutputStream; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.util.Collections; +import org.junit.jupiter.api.Test; + +class HttpExchangeAdapterTest { + + @Test + void testRequestGetQueryString() { + HttpServletRequest servletRequest = mock(HttpServletRequest.class); + HttpServletResponse servletResponse = mock(HttpServletResponse.class); + when(servletRequest.getQueryString()).thenReturn("name[]=test"); + + HttpExchangeAdapter adapter = new HttpExchangeAdapter(servletRequest, servletResponse); + PrometheusHttpRequest request = adapter.getRequest(); + + assertThat(request.getQueryString()).isEqualTo("name[]=test"); + } + + @Test + void testRequestGetHeaders() { + HttpServletRequest servletRequest = mock(HttpServletRequest.class); + HttpServletResponse servletResponse = mock(HttpServletResponse.class); + when(servletRequest.getHeaders("Accept")) + .thenReturn(Collections.enumeration(Collections.singletonList("text/plain"))); + + HttpExchangeAdapter adapter = new HttpExchangeAdapter(servletRequest, servletResponse); + PrometheusHttpRequest request = adapter.getRequest(); + + assertThat(request.getHeaders("Accept").nextElement()).isEqualTo("text/plain"); + } + + @Test + void testRequestGetMethod() { + HttpServletRequest servletRequest = mock(HttpServletRequest.class); + HttpServletResponse servletResponse = mock(HttpServletResponse.class); + when(servletRequest.getMethod()).thenReturn("GET"); + + HttpExchangeAdapter adapter = new HttpExchangeAdapter(servletRequest, servletResponse); + PrometheusHttpRequest request = adapter.getRequest(); + + assertThat(request.getMethod()).isEqualTo("GET"); + } + + @Test + void testRequestGetRequestPath() { + HttpServletRequest servletRequest = mock(HttpServletRequest.class); + HttpServletResponse servletResponse = mock(HttpServletResponse.class); + when(servletRequest.getContextPath()).thenReturn("/app"); + when(servletRequest.getServletPath()).thenReturn("/metrics"); + when(servletRequest.getPathInfo()).thenReturn(null); + + HttpExchangeAdapter adapter = new HttpExchangeAdapter(servletRequest, servletResponse); + PrometheusHttpRequest request = adapter.getRequest(); + + assertThat(request.getRequestPath()).isEqualTo("/app/metrics"); + } + + @Test + void testRequestGetRequestPathWithPathInfo() { + HttpServletRequest servletRequest = mock(HttpServletRequest.class); + HttpServletResponse servletResponse = mock(HttpServletResponse.class); + when(servletRequest.getContextPath()).thenReturn("/app"); + when(servletRequest.getServletPath()).thenReturn("/metrics"); + when(servletRequest.getPathInfo()).thenReturn("/extra"); + + HttpExchangeAdapter adapter = new HttpExchangeAdapter(servletRequest, servletResponse); + PrometheusHttpRequest request = adapter.getRequest(); + + assertThat(request.getRequestPath()).isEqualTo("/app/metrics/extra"); + } + + @Test + void testResponseSetHeader() { + HttpServletRequest servletRequest = mock(HttpServletRequest.class); + HttpServletResponse servletResponse = mock(HttpServletResponse.class); + + HttpExchangeAdapter adapter = new HttpExchangeAdapter(servletRequest, servletResponse); + PrometheusHttpResponse response = adapter.getResponse(); + + response.setHeader("Content-Type", "text/plain"); + verify(servletResponse).setHeader("Content-Type", "text/plain"); + } + + @Test + void testResponseSendHeadersAndGetBody() throws IOException { + HttpServletRequest servletRequest = mock(HttpServletRequest.class); + HttpServletResponse servletResponse = mock(HttpServletResponse.class); + ServletOutputStream outputStream = mock(ServletOutputStream.class); + when(servletResponse.getOutputStream()).thenReturn(outputStream); + + HttpExchangeAdapter adapter = new HttpExchangeAdapter(servletRequest, servletResponse); + PrometheusHttpResponse response = adapter.getResponse(); + + response.sendHeadersAndGetBody(200, 100); + + verify(servletResponse).setContentLength(100); + verify(servletResponse).setStatus(200); + verify(servletResponse).getOutputStream(); + } + + @Test + void testResponseSendHeadersWithContentLengthAlreadySet() throws IOException { + HttpServletRequest servletRequest = mock(HttpServletRequest.class); + HttpServletResponse servletResponse = mock(HttpServletResponse.class); + ServletOutputStream outputStream = mock(ServletOutputStream.class); + when(servletResponse.getHeader("Content-Length")).thenReturn("50"); + when(servletResponse.getOutputStream()).thenReturn(outputStream); + + HttpExchangeAdapter adapter = new HttpExchangeAdapter(servletRequest, servletResponse); + PrometheusHttpResponse response = adapter.getResponse(); + + response.sendHeadersAndGetBody(200, 100); + + verify(servletResponse).setStatus(200); + verify(servletResponse).getOutputStream(); + } + + @Test + void testHandleIOException() { + HttpServletRequest servletRequest = mock(HttpServletRequest.class); + HttpServletResponse servletResponse = mock(HttpServletResponse.class); + + HttpExchangeAdapter adapter = new HttpExchangeAdapter(servletRequest, servletResponse); + IOException exception = new IOException("Test exception"); + + assertThatExceptionOfType(IOException.class) + .isThrownBy(() -> adapter.handleException(exception)) + .withMessage("Test exception"); + } + + @Test + void testHandleRuntimeException() { + HttpServletRequest servletRequest = mock(HttpServletRequest.class); + HttpServletResponse servletResponse = mock(HttpServletResponse.class); + + HttpExchangeAdapter adapter = new HttpExchangeAdapter(servletRequest, servletResponse); + RuntimeException exception = new RuntimeException("Test exception"); + + assertThatExceptionOfType(RuntimeException.class) + .isThrownBy(() -> adapter.handleException(exception)) + .withMessage("Test exception"); + } + + @Test + void testClose() { + HttpServletRequest servletRequest = mock(HttpServletRequest.class); + HttpServletResponse servletResponse = mock(HttpServletResponse.class); + + HttpExchangeAdapter adapter = new HttpExchangeAdapter(servletRequest, servletResponse); + adapter.close(); // Should not throw + } +} diff --git a/prometheus-metrics-exporter-servlet-jakarta/src/test/java/io/prometheus/metrics/exporter/servlet/jakarta/PrometheusMetricsServletTest.java b/prometheus-metrics-exporter-servlet-jakarta/src/test/java/io/prometheus/metrics/exporter/servlet/jakarta/PrometheusMetricsServletTest.java new file mode 100644 index 000000000..5d0d66bfd --- /dev/null +++ b/prometheus-metrics-exporter-servlet-jakarta/src/test/java/io/prometheus/metrics/exporter/servlet/jakarta/PrometheusMetricsServletTest.java @@ -0,0 +1,82 @@ +package io.prometheus.metrics.exporter.servlet.jakarta; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import io.prometheus.metrics.core.metrics.Counter; +import io.prometheus.metrics.model.registry.PrometheusRegistry; +import jakarta.servlet.ServletOutputStream; +import jakarta.servlet.WriteListener; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.Collections; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +class PrometheusMetricsServletTest { + + private PrometheusRegistry registry; + private Counter testCounter; + + @BeforeEach + void setUp() { + registry = new PrometheusRegistry(); + testCounter = Counter.builder().name("test_counter").help("Test counter").register(registry); + testCounter.inc(42); + } + + @Test + void testDoGetWritesMetrics() throws IOException { + HttpServletRequest request = mock(HttpServletRequest.class); + HttpServletResponse response = mock(HttpServletResponse.class); + + when(request.getQueryString()).thenReturn(null); + when(request.getMethod()).thenReturn("GET"); + when(request.getHeaders("Accept-Encoding")).thenReturn(Collections.emptyEnumeration()); + when(request.getHeaders("Accept")).thenReturn(Collections.emptyEnumeration()); + when(request.getContextPath()).thenReturn(""); + when(request.getServletPath()).thenReturn("/metrics"); + when(request.getPathInfo()).thenReturn(null); + + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + when(response.getOutputStream()) + .thenReturn( + new ServletOutputStream() { + @Override + public void write(int b) throws IOException { + outputStream.write(b); + } + + @Override + public boolean isReady() { + return true; + } + + @Override + public void setWriteListener(WriteListener writeListener) {} + }); + + PrometheusMetricsServlet servlet = new PrometheusMetricsServlet(registry); + servlet.doGet(request, response); + + String output = outputStream.toString(StandardCharsets.UTF_8.name()); + assertThat(output).contains("test_counter"); + assertThat(output).contains("42.0"); + } + + @Test + void testServletUsesDefaultRegistry() { + PrometheusMetricsServlet servlet = new PrometheusMetricsServlet(); + assertThat(servlet).isNotNull(); + } + + @Test + void testServletWithCustomRegistry() { + PrometheusMetricsServlet servlet = new PrometheusMetricsServlet(registry); + assertThat(servlet).isNotNull(); + } +} diff --git a/prometheus-metrics-exporter-servlet-javax/pom.xml b/prometheus-metrics-exporter-servlet-javax/pom.xml index 711e75314..9d78816f0 100644 --- a/prometheus-metrics-exporter-servlet-javax/pom.xml +++ b/prometheus-metrics-exporter-servlet-javax/pom.xml @@ -6,7 +6,7 @@ io.prometheus client_java - 1.4.0-SNAPSHOT + 1.6.0-SNAPSHOT prometheus-metrics-exporter-servlet-javax @@ -41,6 +41,14 @@ 4.0.1 provided + + + + io.prometheus + prometheus-metrics-core + ${project.version} + test + diff --git a/prometheus-metrics-exporter-servlet-javax/src/test/java/io/prometheus/metrics/exporter/servlet/javax/HttpExchangeAdapterTest.java b/prometheus-metrics-exporter-servlet-javax/src/test/java/io/prometheus/metrics/exporter/servlet/javax/HttpExchangeAdapterTest.java new file mode 100644 index 000000000..f4f1f0f3f --- /dev/null +++ b/prometheus-metrics-exporter-servlet-javax/src/test/java/io/prometheus/metrics/exporter/servlet/javax/HttpExchangeAdapterTest.java @@ -0,0 +1,190 @@ +package io.prometheus.metrics.exporter.servlet.javax; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import io.prometheus.metrics.exporter.common.PrometheusHttpRequest; +import io.prometheus.metrics.exporter.common.PrometheusHttpResponse; +import java.io.IOException; +import java.util.Collections; +import javax.servlet.ServletOutputStream; +import javax.servlet.WriteListener; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import org.junit.jupiter.api.Test; + +class HttpExchangeAdapterTest { + + @Test + void testRequestGetQueryString() { + HttpServletRequest servletRequest = mock(HttpServletRequest.class); + HttpServletResponse servletResponse = mock(HttpServletResponse.class); + when(servletRequest.getQueryString()).thenReturn("name[]=test"); + + HttpExchangeAdapter adapter = new HttpExchangeAdapter(servletRequest, servletResponse); + PrometheusHttpRequest request = adapter.getRequest(); + + assertThat(request.getQueryString()).isEqualTo("name[]=test"); + } + + @Test + void testRequestGetHeaders() { + HttpServletRequest servletRequest = mock(HttpServletRequest.class); + HttpServletResponse servletResponse = mock(HttpServletResponse.class); + when(servletRequest.getHeaders("Accept")) + .thenReturn(Collections.enumeration(Collections.singletonList("text/plain"))); + + HttpExchangeAdapter adapter = new HttpExchangeAdapter(servletRequest, servletResponse); + PrometheusHttpRequest request = adapter.getRequest(); + + assertThat(request.getHeaders("Accept").nextElement()).isEqualTo("text/plain"); + } + + @Test + void testRequestGetMethod() { + HttpServletRequest servletRequest = mock(HttpServletRequest.class); + HttpServletResponse servletResponse = mock(HttpServletResponse.class); + when(servletRequest.getMethod()).thenReturn("GET"); + + HttpExchangeAdapter adapter = new HttpExchangeAdapter(servletRequest, servletResponse); + PrometheusHttpRequest request = adapter.getRequest(); + + assertThat(request.getMethod()).isEqualTo("GET"); + } + + @Test + void testRequestGetRequestPath() { + HttpServletRequest servletRequest = mock(HttpServletRequest.class); + HttpServletResponse servletResponse = mock(HttpServletResponse.class); + when(servletRequest.getContextPath()).thenReturn("/app"); + when(servletRequest.getServletPath()).thenReturn("/metrics"); + when(servletRequest.getPathInfo()).thenReturn(null); + + HttpExchangeAdapter adapter = new HttpExchangeAdapter(servletRequest, servletResponse); + PrometheusHttpRequest request = adapter.getRequest(); + + assertThat(request.getRequestPath()).isEqualTo("/app/metrics"); + } + + @Test + void testRequestGetRequestPathWithPathInfo() { + HttpServletRequest servletRequest = mock(HttpServletRequest.class); + HttpServletResponse servletResponse = mock(HttpServletResponse.class); + when(servletRequest.getContextPath()).thenReturn("/app"); + when(servletRequest.getServletPath()).thenReturn("/metrics"); + when(servletRequest.getPathInfo()).thenReturn("/extra"); + + HttpExchangeAdapter adapter = new HttpExchangeAdapter(servletRequest, servletResponse); + PrometheusHttpRequest request = adapter.getRequest(); + + assertThat(request.getRequestPath()).isEqualTo("/app/metrics/extra"); + } + + @Test + void testResponseSetHeader() { + HttpServletRequest servletRequest = mock(HttpServletRequest.class); + HttpServletResponse servletResponse = mock(HttpServletResponse.class); + + HttpExchangeAdapter adapter = new HttpExchangeAdapter(servletRequest, servletResponse); + PrometheusHttpResponse response = adapter.getResponse(); + + response.setHeader("Content-Type", "text/plain"); + verify(servletResponse).setHeader("Content-Type", "text/plain"); + } + + @Test + void testResponseSendHeadersAndGetBody() throws IOException { + HttpServletRequest servletRequest = mock(HttpServletRequest.class); + HttpServletResponse servletResponse = mock(HttpServletResponse.class); + ServletOutputStream outputStream = + new ServletOutputStream() { + @Override + public void write(int b) throws IOException {} + + @Override + public boolean isReady() { + return true; + } + + @Override + public void setWriteListener(WriteListener writeListener) {} + }; + when(servletResponse.getOutputStream()).thenReturn(outputStream); + + HttpExchangeAdapter adapter = new HttpExchangeAdapter(servletRequest, servletResponse); + PrometheusHttpResponse response = adapter.getResponse(); + + response.sendHeadersAndGetBody(200, 100); + + verify(servletResponse).setContentLength(100); + verify(servletResponse).setStatus(200); + verify(servletResponse).getOutputStream(); + } + + @Test + void testResponseSendHeadersWithContentLengthAlreadySet() throws IOException { + HttpServletRequest servletRequest = mock(HttpServletRequest.class); + HttpServletResponse servletResponse = mock(HttpServletResponse.class); + ServletOutputStream outputStream = + new ServletOutputStream() { + @Override + public void write(int b) throws IOException {} + + @Override + public boolean isReady() { + return true; + } + + @Override + public void setWriteListener(WriteListener writeListener) {} + }; + when(servletResponse.getHeader("Content-Length")).thenReturn("50"); + when(servletResponse.getOutputStream()).thenReturn(outputStream); + + HttpExchangeAdapter adapter = new HttpExchangeAdapter(servletRequest, servletResponse); + PrometheusHttpResponse response = adapter.getResponse(); + + response.sendHeadersAndGetBody(200, 100); + + verify(servletResponse).setStatus(200); + verify(servletResponse).getOutputStream(); + } + + @Test + void testHandleIOException() { + HttpServletRequest servletRequest = mock(HttpServletRequest.class); + HttpServletResponse servletResponse = mock(HttpServletResponse.class); + + HttpExchangeAdapter adapter = new HttpExchangeAdapter(servletRequest, servletResponse); + IOException exception = new IOException("Test exception"); + + assertThatExceptionOfType(IOException.class) + .isThrownBy(() -> adapter.handleException(exception)) + .withMessage("Test exception"); + } + + @Test + void testHandleRuntimeException() { + HttpServletRequest servletRequest = mock(HttpServletRequest.class); + HttpServletResponse servletResponse = mock(HttpServletResponse.class); + + HttpExchangeAdapter adapter = new HttpExchangeAdapter(servletRequest, servletResponse); + RuntimeException exception = new RuntimeException("Test exception"); + + assertThatExceptionOfType(RuntimeException.class) + .isThrownBy(() -> adapter.handleException(exception)) + .withMessage("Test exception"); + } + + @Test + void testClose() { + HttpServletRequest servletRequest = mock(HttpServletRequest.class); + HttpServletResponse servletResponse = mock(HttpServletResponse.class); + + HttpExchangeAdapter adapter = new HttpExchangeAdapter(servletRequest, servletResponse); + adapter.close(); // Should not throw + } +} diff --git a/prometheus-metrics-exporter-servlet-javax/src/test/java/io/prometheus/metrics/exporter/servlet/javax/PrometheusMetricsServletTest.java b/prometheus-metrics-exporter-servlet-javax/src/test/java/io/prometheus/metrics/exporter/servlet/javax/PrometheusMetricsServletTest.java new file mode 100644 index 000000000..0b3c5e5f6 --- /dev/null +++ b/prometheus-metrics-exporter-servlet-javax/src/test/java/io/prometheus/metrics/exporter/servlet/javax/PrometheusMetricsServletTest.java @@ -0,0 +1,82 @@ +package io.prometheus.metrics.exporter.servlet.javax; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import io.prometheus.metrics.core.metrics.Counter; +import io.prometheus.metrics.model.registry.PrometheusRegistry; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.Collections; +import javax.servlet.ServletOutputStream; +import javax.servlet.WriteListener; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +class PrometheusMetricsServletTest { + + private PrometheusRegistry registry; + private Counter testCounter; + + @BeforeEach + void setUp() { + registry = new PrometheusRegistry(); + testCounter = Counter.builder().name("test_counter").help("Test counter").register(registry); + testCounter.inc(42); + } + + @Test + void testDoGetWritesMetrics() throws IOException { + HttpServletRequest request = mock(HttpServletRequest.class); + HttpServletResponse response = mock(HttpServletResponse.class); + + when(request.getQueryString()).thenReturn(null); + when(request.getMethod()).thenReturn("GET"); + when(request.getHeaders("Accept-Encoding")).thenReturn(Collections.emptyEnumeration()); + when(request.getHeaders("Accept")).thenReturn(Collections.emptyEnumeration()); + when(request.getContextPath()).thenReturn(""); + when(request.getServletPath()).thenReturn("/metrics"); + when(request.getPathInfo()).thenReturn(null); + + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + when(response.getOutputStream()) + .thenReturn( + new ServletOutputStream() { + @Override + public void write(int b) throws IOException { + outputStream.write(b); + } + + @Override + public boolean isReady() { + return true; + } + + @Override + public void setWriteListener(WriteListener writeListener) {} + }); + + PrometheusMetricsServlet servlet = new PrometheusMetricsServlet(registry); + servlet.doGet(request, response); + + String output = outputStream.toString(StandardCharsets.UTF_8.name()); + assertThat(output).contains("test_counter"); + assertThat(output).contains("42.0"); + } + + @Test + void testServletUsesDefaultRegistry() { + PrometheusMetricsServlet servlet = new PrometheusMetricsServlet(); + assertThat(servlet).isNotNull(); + } + + @Test + void testServletWithCustomRegistry() { + PrometheusMetricsServlet servlet = new PrometheusMetricsServlet(registry); + assertThat(servlet).isNotNull(); + } +} diff --git a/prometheus-metrics-exposition-formats-shaded/pom.xml b/prometheus-metrics-exposition-formats-shaded/pom.xml index 178dbebe4..d3e02eb2b 100644 --- a/prometheus-metrics-exposition-formats-shaded/pom.xml +++ b/prometheus-metrics-exposition-formats-shaded/pom.xml @@ -7,7 +7,7 @@ io.prometheus client_java - 1.4.0-SNAPSHOT + 1.6.0-SNAPSHOT prometheus-metrics-exposition-formats diff --git a/prometheus-metrics-exposition-formats/generate-protobuf.sh b/prometheus-metrics-exposition-formats/generate-protobuf.sh index 5a7a6d9d2..323cf033c 100755 --- a/prometheus-metrics-exposition-formats/generate-protobuf.sh +++ b/prometheus-metrics-exposition-formats/generate-protobuf.sh @@ -18,7 +18,7 @@ mkdir -p "$TARGET_DIR" rm -rf $PROTO_DIR || true mkdir -p $PROTO_DIR -OLD_PACKAGE=$(sed -nE 's/import (io.prometheus.metrics.expositionformats.generated.*).Metrics;/\1/p' src/main/java/io/prometheus/metrics/expositionformats/internal/PrometheusProtobufWriterImpl.java) +OLD_PACKAGE=$(sed -nE 's/.*extends (io\.prometheus\.metrics\.expositionformats\.generated\.[^ ]*?)\.Metrics.*/\1/p' src/main/java/io/prometheus/metrics/expositionformats/generated/Metrics.java) PACKAGE="io.prometheus.metrics.expositionformats.generated.com_google_protobuf_${PROTOBUF_VERSION_STRING}" if [[ $OLD_PACKAGE != "$PACKAGE" ]]; then @@ -33,19 +33,30 @@ protoc --java_out "$TARGET_DIR" $PROTO_DIR/metrics.proto sed -i '1 i\//CHECKSTYLE:OFF: checkstyle' "$(find src/main/generated/io -type f)" sed -i -e $'$a\\\n//CHECKSTYLE:ON: checkstyle' "$(find src/main/generated/io -type f)" +GENERATED_FILE="$TARGET_DIR/${PACKAGE//\.//}/Metrics.java" +sed -i 's/public final class Metrics/public class Metrics/' "$GENERATED_FILE" +sed -i 's/private Metrics() {}/protected Metrics() {}/' "$GENERATED_FILE" + GENERATED_WITH=$(grep -oP '\/\/ Protobuf Java Version: \K.*' "$TARGET_DIR/${PACKAGE//\.//}"/Metrics.java) +function help() { + echo "Please use https://mise.jdx.dev/ - this will use the version specified in mise.toml" + echo "Generated protobuf sources are not up-to-date. Please run 'mise run generate' and commit the changes." + echo "NOTE:" + echo "1. You should only run 'mise run generate' in a PR from renovate" + echo "2. The PR should update both '' in pom.xml and protoc in mise.toml" + echo " - but at least . If not, wait until renovate updates the PR." +} + if [[ $GENERATED_WITH != "$PROTOBUF_VERSION" ]]; then echo "Generated protobuf sources version $GENERATED_WITH does not match provided version $PROTOBUF_VERSION" - echo "Please use https://mise.jdx.dev/ - this will use the version specified in mise.toml" - echo "Generated protobuf sources are not up-to-date. Please run 'mise up && mise run generater' and commit the changes." + help exit 1 fi STATUS=$(git status --porcelain) if [[ ${REQUIRE_PROTO_UP_TO_DATE:-false} == "true" && -n "$STATUS" ]]; then - echo "Please use https://mise.jdx.dev/ - this will use the version specified in mise.toml" - echo "Generated protobuf sources are not up-to-date. Please run 'mise run generate' and commit the changes." + help echo "Local changes:" echo "$STATUS" exit 1 diff --git a/prometheus-metrics-exposition-formats/pom.xml b/prometheus-metrics-exposition-formats/pom.xml index 5b35f33f7..5cb35db2e 100644 --- a/prometheus-metrics-exposition-formats/pom.xml +++ b/prometheus-metrics-exposition-formats/pom.xml @@ -7,7 +7,7 @@ io.prometheus client_java - 1.4.0-SNAPSHOT + 1.6.0-SNAPSHOT prometheus-metrics-exposition-formats-no-protobuf diff --git a/prometheus-metrics-exposition-formats/src/main/generated/io/prometheus/metrics/expositionformats/generated/com_google_protobuf_4_31_1/Metrics.java b/prometheus-metrics-exposition-formats/src/main/generated/io/prometheus/metrics/expositionformats/generated/com_google_protobuf_4_33_5/Metrics.java similarity index 88% rename from prometheus-metrics-exposition-formats/src/main/generated/io/prometheus/metrics/expositionformats/generated/com_google_protobuf_4_31_1/Metrics.java rename to prometheus-metrics-exposition-formats/src/main/generated/io/prometheus/metrics/expositionformats/generated/com_google_protobuf_4_33_5/Metrics.java index 7e9e71ca5..5f4788c14 100644 --- a/prometheus-metrics-exposition-formats/src/main/generated/io/prometheus/metrics/expositionformats/generated/com_google_protobuf_4_31_1/Metrics.java +++ b/prometheus-metrics-exposition-formats/src/main/generated/io/prometheus/metrics/expositionformats/generated/com_google_protobuf_4_33_5/Metrics.java @@ -2,21 +2,21 @@ // Generated by the protocol buffer compiler. DO NOT EDIT! // NO CHECKED-IN PROTOBUF GENCODE // source: src/main/protobuf/metrics.proto -// Protobuf Java Version: 4.31.1 +// Protobuf Java Version: 4.33.5 -package io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1; +package io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5; @com.google.protobuf.Generated -public final class Metrics { - private Metrics() {} +public class Metrics extends com.google.protobuf.GeneratedFile { + protected Metrics() {} static { com.google.protobuf.RuntimeVersion.validateProtobufGencodeVersion( com.google.protobuf.RuntimeVersion.RuntimeDomain.PUBLIC, /* major= */ 4, - /* minor= */ 31, - /* patch= */ 1, + /* minor= */ 33, + /* patch= */ 5, /* suffix= */ "", - Metrics.class.getName()); + "Metrics"); } public static void registerAllExtensions( com.google.protobuf.ExtensionRegistryLite registry) { @@ -86,10 +86,10 @@ public enum MetricType com.google.protobuf.RuntimeVersion.validateProtobufGencodeVersion( com.google.protobuf.RuntimeVersion.RuntimeDomain.PUBLIC, /* major= */ 4, - /* minor= */ 31, - /* patch= */ 1, + /* minor= */ 33, + /* patch= */ 5, /* suffix= */ "", - MetricType.class.getName()); + "MetricType"); } /** *

@@ -193,7 +193,7 @@ public MetricType findValueByNumber(int number) {
     }
     public static com.google.protobuf.Descriptors.EnumDescriptor
         getDescriptor() {
-      return io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.getDescriptor().getEnumTypes().get(0);
+      return io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.getDescriptor().getEnumTypes().get(0);
     }
 
     private static final MetricType[] VALUES = values();
@@ -266,10 +266,10 @@ public static final class LabelPair extends
       com.google.protobuf.RuntimeVersion.validateProtobufGencodeVersion(
         com.google.protobuf.RuntimeVersion.RuntimeDomain.PUBLIC,
         /* major= */ 4,
-        /* minor= */ 31,
-        /* patch= */ 1,
+        /* minor= */ 33,
+        /* patch= */ 5,
         /* suffix= */ "",
-        LabelPair.class.getName());
+        "LabelPair");
     }
     // Use LabelPair.newBuilder() to construct.
     private LabelPair(com.google.protobuf.GeneratedMessage.Builder builder) {
@@ -282,15 +282,15 @@ private LabelPair() {
 
     public static final com.google.protobuf.Descriptors.Descriptor
         getDescriptor() {
-      return io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.internal_static_io_prometheus_client_LabelPair_descriptor;
+      return io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.internal_static_io_prometheus_client_LabelPair_descriptor;
     }
 
     @java.lang.Override
     protected com.google.protobuf.GeneratedMessage.FieldAccessorTable
         internalGetFieldAccessorTable() {
-      return io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.internal_static_io_prometheus_client_LabelPair_fieldAccessorTable
+      return io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.internal_static_io_prometheus_client_LabelPair_fieldAccessorTable
           .ensureFieldAccessorsInitialized(
-              io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.LabelPair.class, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.LabelPair.Builder.class);
+              io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.LabelPair.class, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.LabelPair.Builder.class);
     }
 
     private int bitField0_;
@@ -437,10 +437,10 @@ public boolean equals(final java.lang.Object obj) {
       if (obj == this) {
        return true;
       }
-      if (!(obj instanceof io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.LabelPair)) {
+      if (!(obj instanceof io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.LabelPair)) {
         return super.equals(obj);
       }
-      io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.LabelPair other = (io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.LabelPair) obj;
+      io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.LabelPair other = (io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.LabelPair) obj;
 
       if (hasName() != other.hasName()) return false;
       if (hasName()) {
@@ -476,44 +476,44 @@ public int hashCode() {
       return hash;
     }
 
-    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.LabelPair parseFrom(
+    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.LabelPair parseFrom(
         java.nio.ByteBuffer data)
         throws com.google.protobuf.InvalidProtocolBufferException {
       return PARSER.parseFrom(data);
     }
-    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.LabelPair parseFrom(
+    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.LabelPair parseFrom(
         java.nio.ByteBuffer data,
         com.google.protobuf.ExtensionRegistryLite extensionRegistry)
         throws com.google.protobuf.InvalidProtocolBufferException {
       return PARSER.parseFrom(data, extensionRegistry);
     }
-    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.LabelPair parseFrom(
+    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.LabelPair parseFrom(
         com.google.protobuf.ByteString data)
         throws com.google.protobuf.InvalidProtocolBufferException {
       return PARSER.parseFrom(data);
     }
-    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.LabelPair parseFrom(
+    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.LabelPair parseFrom(
         com.google.protobuf.ByteString data,
         com.google.protobuf.ExtensionRegistryLite extensionRegistry)
         throws com.google.protobuf.InvalidProtocolBufferException {
       return PARSER.parseFrom(data, extensionRegistry);
     }
-    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.LabelPair parseFrom(byte[] data)
+    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.LabelPair parseFrom(byte[] data)
         throws com.google.protobuf.InvalidProtocolBufferException {
       return PARSER.parseFrom(data);
     }
-    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.LabelPair parseFrom(
+    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.LabelPair parseFrom(
         byte[] data,
         com.google.protobuf.ExtensionRegistryLite extensionRegistry)
         throws com.google.protobuf.InvalidProtocolBufferException {
       return PARSER.parseFrom(data, extensionRegistry);
     }
-    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.LabelPair parseFrom(java.io.InputStream input)
+    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.LabelPair parseFrom(java.io.InputStream input)
         throws java.io.IOException {
       return com.google.protobuf.GeneratedMessage
           .parseWithIOException(PARSER, input);
     }
-    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.LabelPair parseFrom(
+    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.LabelPair parseFrom(
         java.io.InputStream input,
         com.google.protobuf.ExtensionRegistryLite extensionRegistry)
         throws java.io.IOException {
@@ -521,26 +521,26 @@ public static io.prometheus.metrics.expositionformats.generated.com_google_proto
           .parseWithIOException(PARSER, input, extensionRegistry);
     }
 
-    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.LabelPair parseDelimitedFrom(java.io.InputStream input)
+    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.LabelPair parseDelimitedFrom(java.io.InputStream input)
         throws java.io.IOException {
       return com.google.protobuf.GeneratedMessage
           .parseDelimitedWithIOException(PARSER, input);
     }
 
-    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.LabelPair parseDelimitedFrom(
+    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.LabelPair parseDelimitedFrom(
         java.io.InputStream input,
         com.google.protobuf.ExtensionRegistryLite extensionRegistry)
         throws java.io.IOException {
       return com.google.protobuf.GeneratedMessage
           .parseDelimitedWithIOException(PARSER, input, extensionRegistry);
     }
-    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.LabelPair parseFrom(
+    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.LabelPair parseFrom(
         com.google.protobuf.CodedInputStream input)
         throws java.io.IOException {
       return com.google.protobuf.GeneratedMessage
           .parseWithIOException(PARSER, input);
     }
-    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.LabelPair parseFrom(
+    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.LabelPair parseFrom(
         com.google.protobuf.CodedInputStream input,
         com.google.protobuf.ExtensionRegistryLite extensionRegistry)
         throws java.io.IOException {
@@ -553,7 +553,7 @@ public static io.prometheus.metrics.expositionformats.generated.com_google_proto
     public static Builder newBuilder() {
       return DEFAULT_INSTANCE.toBuilder();
     }
-    public static Builder newBuilder(io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.LabelPair prototype) {
+    public static Builder newBuilder(io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.LabelPair prototype) {
       return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype);
     }
     @java.lang.Override
@@ -574,21 +574,21 @@ protected Builder newBuilderForType(
     public static final class Builder extends
         com.google.protobuf.GeneratedMessage.Builder implements
         // @@protoc_insertion_point(builder_implements:io.prometheus.client.LabelPair)
-        io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.LabelPairOrBuilder {
+        io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.LabelPairOrBuilder {
       public static final com.google.protobuf.Descriptors.Descriptor
           getDescriptor() {
-        return io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.internal_static_io_prometheus_client_LabelPair_descriptor;
+        return io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.internal_static_io_prometheus_client_LabelPair_descriptor;
       }
 
       @java.lang.Override
       protected com.google.protobuf.GeneratedMessage.FieldAccessorTable
           internalGetFieldAccessorTable() {
-        return io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.internal_static_io_prometheus_client_LabelPair_fieldAccessorTable
+        return io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.internal_static_io_prometheus_client_LabelPair_fieldAccessorTable
             .ensureFieldAccessorsInitialized(
-                io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.LabelPair.class, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.LabelPair.Builder.class);
+                io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.LabelPair.class, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.LabelPair.Builder.class);
       }
 
-      // Construct using io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.LabelPair.newBuilder()
+      // Construct using io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.LabelPair.newBuilder()
       private Builder() {
 
       }
@@ -610,17 +610,17 @@ public Builder clear() {
       @java.lang.Override
       public com.google.protobuf.Descriptors.Descriptor
           getDescriptorForType() {
-        return io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.internal_static_io_prometheus_client_LabelPair_descriptor;
+        return io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.internal_static_io_prometheus_client_LabelPair_descriptor;
       }
 
       @java.lang.Override
-      public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.LabelPair getDefaultInstanceForType() {
-        return io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.LabelPair.getDefaultInstance();
+      public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.LabelPair getDefaultInstanceForType() {
+        return io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.LabelPair.getDefaultInstance();
       }
 
       @java.lang.Override
-      public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.LabelPair build() {
-        io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.LabelPair result = buildPartial();
+      public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.LabelPair build() {
+        io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.LabelPair result = buildPartial();
         if (!result.isInitialized()) {
           throw newUninitializedMessageException(result);
         }
@@ -628,14 +628,14 @@ public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_3
       }
 
       @java.lang.Override
-      public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.LabelPair buildPartial() {
-        io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.LabelPair result = new io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.LabelPair(this);
+      public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.LabelPair buildPartial() {
+        io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.LabelPair result = new io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.LabelPair(this);
         if (bitField0_ != 0) { buildPartial0(result); }
         onBuilt();
         return result;
       }
 
-      private void buildPartial0(io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.LabelPair result) {
+      private void buildPartial0(io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.LabelPair result) {
         int from_bitField0_ = bitField0_;
         int to_bitField0_ = 0;
         if (((from_bitField0_ & 0x00000001) != 0)) {
@@ -651,16 +651,16 @@ private void buildPartial0(io.prometheus.metrics.expositionformats.generated.com
 
       @java.lang.Override
       public Builder mergeFrom(com.google.protobuf.Message other) {
-        if (other instanceof io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.LabelPair) {
-          return mergeFrom((io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.LabelPair)other);
+        if (other instanceof io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.LabelPair) {
+          return mergeFrom((io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.LabelPair)other);
         } else {
           super.mergeFrom(other);
           return this;
         }
       }
 
-      public Builder mergeFrom(io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.LabelPair other) {
-        if (other == io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.LabelPair.getDefaultInstance()) return this;
+      public Builder mergeFrom(io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.LabelPair other) {
+        if (other == io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.LabelPair.getDefaultInstance()) return this;
         if (other.hasName()) {
           name_ = other.name_;
           bitField0_ |= 0x00000001;
@@ -888,12 +888,12 @@ public Builder setValueBytes(
     }
 
     // @@protoc_insertion_point(class_scope:io.prometheus.client.LabelPair)
-    private static final io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.LabelPair DEFAULT_INSTANCE;
+    private static final io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.LabelPair DEFAULT_INSTANCE;
     static {
-      DEFAULT_INSTANCE = new io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.LabelPair();
+      DEFAULT_INSTANCE = new io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.LabelPair();
     }
 
-    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.LabelPair getDefaultInstance() {
+    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.LabelPair getDefaultInstance() {
       return DEFAULT_INSTANCE;
     }
 
@@ -929,7 +929,7 @@ public com.google.protobuf.Parser getParserForType() {
     }
 
     @java.lang.Override
-    public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.LabelPair getDefaultInstanceForType() {
+    public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.LabelPair getDefaultInstanceForType() {
       return DEFAULT_INSTANCE;
     }
 
@@ -962,10 +962,10 @@ public static final class Gauge extends
       com.google.protobuf.RuntimeVersion.validateProtobufGencodeVersion(
         com.google.protobuf.RuntimeVersion.RuntimeDomain.PUBLIC,
         /* major= */ 4,
-        /* minor= */ 31,
-        /* patch= */ 1,
+        /* minor= */ 33,
+        /* patch= */ 5,
         /* suffix= */ "",
-        Gauge.class.getName());
+        "Gauge");
     }
     // Use Gauge.newBuilder() to construct.
     private Gauge(com.google.protobuf.GeneratedMessage.Builder builder) {
@@ -976,15 +976,15 @@ private Gauge() {
 
     public static final com.google.protobuf.Descriptors.Descriptor
         getDescriptor() {
-      return io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.internal_static_io_prometheus_client_Gauge_descriptor;
+      return io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.internal_static_io_prometheus_client_Gauge_descriptor;
     }
 
     @java.lang.Override
     protected com.google.protobuf.GeneratedMessage.FieldAccessorTable
         internalGetFieldAccessorTable() {
-      return io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.internal_static_io_prometheus_client_Gauge_fieldAccessorTable
+      return io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.internal_static_io_prometheus_client_Gauge_fieldAccessorTable
           .ensureFieldAccessorsInitialized(
-              io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Gauge.class, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Gauge.Builder.class);
+              io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Gauge.class, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Gauge.Builder.class);
     }
 
     private int bitField0_;
@@ -1047,10 +1047,10 @@ public boolean equals(final java.lang.Object obj) {
       if (obj == this) {
        return true;
       }
-      if (!(obj instanceof io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Gauge)) {
+      if (!(obj instanceof io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Gauge)) {
         return super.equals(obj);
       }
-      io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Gauge other = (io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Gauge) obj;
+      io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Gauge other = (io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Gauge) obj;
 
       if (hasValue() != other.hasValue()) return false;
       if (hasValue()) {
@@ -1079,44 +1079,44 @@ public int hashCode() {
       return hash;
     }
 
-    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Gauge parseFrom(
+    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Gauge parseFrom(
         java.nio.ByteBuffer data)
         throws com.google.protobuf.InvalidProtocolBufferException {
       return PARSER.parseFrom(data);
     }
-    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Gauge parseFrom(
+    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Gauge parseFrom(
         java.nio.ByteBuffer data,
         com.google.protobuf.ExtensionRegistryLite extensionRegistry)
         throws com.google.protobuf.InvalidProtocolBufferException {
       return PARSER.parseFrom(data, extensionRegistry);
     }
-    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Gauge parseFrom(
+    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Gauge parseFrom(
         com.google.protobuf.ByteString data)
         throws com.google.protobuf.InvalidProtocolBufferException {
       return PARSER.parseFrom(data);
     }
-    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Gauge parseFrom(
+    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Gauge parseFrom(
         com.google.protobuf.ByteString data,
         com.google.protobuf.ExtensionRegistryLite extensionRegistry)
         throws com.google.protobuf.InvalidProtocolBufferException {
       return PARSER.parseFrom(data, extensionRegistry);
     }
-    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Gauge parseFrom(byte[] data)
+    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Gauge parseFrom(byte[] data)
         throws com.google.protobuf.InvalidProtocolBufferException {
       return PARSER.parseFrom(data);
     }
-    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Gauge parseFrom(
+    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Gauge parseFrom(
         byte[] data,
         com.google.protobuf.ExtensionRegistryLite extensionRegistry)
         throws com.google.protobuf.InvalidProtocolBufferException {
       return PARSER.parseFrom(data, extensionRegistry);
     }
-    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Gauge parseFrom(java.io.InputStream input)
+    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Gauge parseFrom(java.io.InputStream input)
         throws java.io.IOException {
       return com.google.protobuf.GeneratedMessage
           .parseWithIOException(PARSER, input);
     }
-    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Gauge parseFrom(
+    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Gauge parseFrom(
         java.io.InputStream input,
         com.google.protobuf.ExtensionRegistryLite extensionRegistry)
         throws java.io.IOException {
@@ -1124,26 +1124,26 @@ public static io.prometheus.metrics.expositionformats.generated.com_google_proto
           .parseWithIOException(PARSER, input, extensionRegistry);
     }
 
-    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Gauge parseDelimitedFrom(java.io.InputStream input)
+    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Gauge parseDelimitedFrom(java.io.InputStream input)
         throws java.io.IOException {
       return com.google.protobuf.GeneratedMessage
           .parseDelimitedWithIOException(PARSER, input);
     }
 
-    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Gauge parseDelimitedFrom(
+    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Gauge parseDelimitedFrom(
         java.io.InputStream input,
         com.google.protobuf.ExtensionRegistryLite extensionRegistry)
         throws java.io.IOException {
       return com.google.protobuf.GeneratedMessage
           .parseDelimitedWithIOException(PARSER, input, extensionRegistry);
     }
-    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Gauge parseFrom(
+    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Gauge parseFrom(
         com.google.protobuf.CodedInputStream input)
         throws java.io.IOException {
       return com.google.protobuf.GeneratedMessage
           .parseWithIOException(PARSER, input);
     }
-    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Gauge parseFrom(
+    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Gauge parseFrom(
         com.google.protobuf.CodedInputStream input,
         com.google.protobuf.ExtensionRegistryLite extensionRegistry)
         throws java.io.IOException {
@@ -1156,7 +1156,7 @@ public static io.prometheus.metrics.expositionformats.generated.com_google_proto
     public static Builder newBuilder() {
       return DEFAULT_INSTANCE.toBuilder();
     }
-    public static Builder newBuilder(io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Gauge prototype) {
+    public static Builder newBuilder(io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Gauge prototype) {
       return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype);
     }
     @java.lang.Override
@@ -1177,21 +1177,21 @@ protected Builder newBuilderForType(
     public static final class Builder extends
         com.google.protobuf.GeneratedMessage.Builder implements
         // @@protoc_insertion_point(builder_implements:io.prometheus.client.Gauge)
-        io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.GaugeOrBuilder {
+        io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.GaugeOrBuilder {
       public static final com.google.protobuf.Descriptors.Descriptor
           getDescriptor() {
-        return io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.internal_static_io_prometheus_client_Gauge_descriptor;
+        return io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.internal_static_io_prometheus_client_Gauge_descriptor;
       }
 
       @java.lang.Override
       protected com.google.protobuf.GeneratedMessage.FieldAccessorTable
           internalGetFieldAccessorTable() {
-        return io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.internal_static_io_prometheus_client_Gauge_fieldAccessorTable
+        return io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.internal_static_io_prometheus_client_Gauge_fieldAccessorTable
             .ensureFieldAccessorsInitialized(
-                io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Gauge.class, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Gauge.Builder.class);
+                io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Gauge.class, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Gauge.Builder.class);
       }
 
-      // Construct using io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Gauge.newBuilder()
+      // Construct using io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Gauge.newBuilder()
       private Builder() {
 
       }
@@ -1212,17 +1212,17 @@ public Builder clear() {
       @java.lang.Override
       public com.google.protobuf.Descriptors.Descriptor
           getDescriptorForType() {
-        return io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.internal_static_io_prometheus_client_Gauge_descriptor;
+        return io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.internal_static_io_prometheus_client_Gauge_descriptor;
       }
 
       @java.lang.Override
-      public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Gauge getDefaultInstanceForType() {
-        return io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Gauge.getDefaultInstance();
+      public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Gauge getDefaultInstanceForType() {
+        return io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Gauge.getDefaultInstance();
       }
 
       @java.lang.Override
-      public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Gauge build() {
-        io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Gauge result = buildPartial();
+      public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Gauge build() {
+        io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Gauge result = buildPartial();
         if (!result.isInitialized()) {
           throw newUninitializedMessageException(result);
         }
@@ -1230,14 +1230,14 @@ public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_3
       }
 
       @java.lang.Override
-      public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Gauge buildPartial() {
-        io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Gauge result = new io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Gauge(this);
+      public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Gauge buildPartial() {
+        io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Gauge result = new io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Gauge(this);
         if (bitField0_ != 0) { buildPartial0(result); }
         onBuilt();
         return result;
       }
 
-      private void buildPartial0(io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Gauge result) {
+      private void buildPartial0(io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Gauge result) {
         int from_bitField0_ = bitField0_;
         int to_bitField0_ = 0;
         if (((from_bitField0_ & 0x00000001) != 0)) {
@@ -1249,16 +1249,16 @@ private void buildPartial0(io.prometheus.metrics.expositionformats.generated.com
 
       @java.lang.Override
       public Builder mergeFrom(com.google.protobuf.Message other) {
-        if (other instanceof io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Gauge) {
-          return mergeFrom((io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Gauge)other);
+        if (other instanceof io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Gauge) {
+          return mergeFrom((io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Gauge)other);
         } else {
           super.mergeFrom(other);
           return this;
         }
       }
 
-      public Builder mergeFrom(io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Gauge other) {
-        if (other == io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Gauge.getDefaultInstance()) return this;
+      public Builder mergeFrom(io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Gauge other) {
+        if (other == io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Gauge.getDefaultInstance()) return this;
         if (other.hasValue()) {
           setValue(other.getValue());
         }
@@ -1354,12 +1354,12 @@ public Builder clearValue() {
     }
 
     // @@protoc_insertion_point(class_scope:io.prometheus.client.Gauge)
-    private static final io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Gauge DEFAULT_INSTANCE;
+    private static final io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Gauge DEFAULT_INSTANCE;
     static {
-      DEFAULT_INSTANCE = new io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Gauge();
+      DEFAULT_INSTANCE = new io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Gauge();
     }
 
-    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Gauge getDefaultInstance() {
+    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Gauge getDefaultInstance() {
       return DEFAULT_INSTANCE;
     }
 
@@ -1395,7 +1395,7 @@ public com.google.protobuf.Parser getParserForType() {
     }
 
     @java.lang.Override
-    public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Gauge getDefaultInstanceForType() {
+    public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Gauge getDefaultInstanceForType() {
       return DEFAULT_INSTANCE;
     }
 
@@ -1425,11 +1425,11 @@ public interface CounterOrBuilder extends
      * optional .io.prometheus.client.Exemplar exemplar = 2;
      * @return The exemplar.
      */
-    io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Exemplar getExemplar();
+    io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Exemplar getExemplar();
     /**
      * optional .io.prometheus.client.Exemplar exemplar = 2;
      */
-    io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.ExemplarOrBuilder getExemplarOrBuilder();
+    io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.ExemplarOrBuilder getExemplarOrBuilder();
 
     /**
      * optional .google.protobuf.Timestamp created_timestamp = 3;
@@ -1458,10 +1458,10 @@ public static final class Counter extends
       com.google.protobuf.RuntimeVersion.validateProtobufGencodeVersion(
         com.google.protobuf.RuntimeVersion.RuntimeDomain.PUBLIC,
         /* major= */ 4,
-        /* minor= */ 31,
-        /* patch= */ 1,
+        /* minor= */ 33,
+        /* patch= */ 5,
         /* suffix= */ "",
-        Counter.class.getName());
+        "Counter");
     }
     // Use Counter.newBuilder() to construct.
     private Counter(com.google.protobuf.GeneratedMessage.Builder builder) {
@@ -1472,15 +1472,15 @@ private Counter() {
 
     public static final com.google.protobuf.Descriptors.Descriptor
         getDescriptor() {
-      return io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.internal_static_io_prometheus_client_Counter_descriptor;
+      return io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.internal_static_io_prometheus_client_Counter_descriptor;
     }
 
     @java.lang.Override
     protected com.google.protobuf.GeneratedMessage.FieldAccessorTable
         internalGetFieldAccessorTable() {
-      return io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.internal_static_io_prometheus_client_Counter_fieldAccessorTable
+      return io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.internal_static_io_prometheus_client_Counter_fieldAccessorTable
           .ensureFieldAccessorsInitialized(
-              io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Counter.class, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Counter.Builder.class);
+              io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Counter.class, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Counter.Builder.class);
     }
 
     private int bitField0_;
@@ -1504,7 +1504,7 @@ public double getValue() {
     }
 
     public static final int EXEMPLAR_FIELD_NUMBER = 2;
-    private io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Exemplar exemplar_;
+    private io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Exemplar exemplar_;
     /**
      * optional .io.prometheus.client.Exemplar exemplar = 2;
      * @return Whether the exemplar field is set.
@@ -1518,15 +1518,15 @@ public boolean hasExemplar() {
      * @return The exemplar.
      */
     @java.lang.Override
-    public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Exemplar getExemplar() {
-      return exemplar_ == null ? io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Exemplar.getDefaultInstance() : exemplar_;
+    public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Exemplar getExemplar() {
+      return exemplar_ == null ? io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Exemplar.getDefaultInstance() : exemplar_;
     }
     /**
      * optional .io.prometheus.client.Exemplar exemplar = 2;
      */
     @java.lang.Override
-    public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.ExemplarOrBuilder getExemplarOrBuilder() {
-      return exemplar_ == null ? io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Exemplar.getDefaultInstance() : exemplar_;
+    public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.ExemplarOrBuilder getExemplarOrBuilder() {
+      return exemplar_ == null ? io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Exemplar.getDefaultInstance() : exemplar_;
     }
 
     public static final int CREATED_TIMESTAMP_FIELD_NUMBER = 3;
@@ -1609,10 +1609,10 @@ public boolean equals(final java.lang.Object obj) {
       if (obj == this) {
        return true;
       }
-      if (!(obj instanceof io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Counter)) {
+      if (!(obj instanceof io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Counter)) {
         return super.equals(obj);
       }
-      io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Counter other = (io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Counter) obj;
+      io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Counter other = (io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Counter) obj;
 
       if (hasValue() != other.hasValue()) return false;
       if (hasValue()) {
@@ -1659,44 +1659,44 @@ public int hashCode() {
       return hash;
     }
 
-    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Counter parseFrom(
+    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Counter parseFrom(
         java.nio.ByteBuffer data)
         throws com.google.protobuf.InvalidProtocolBufferException {
       return PARSER.parseFrom(data);
     }
-    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Counter parseFrom(
+    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Counter parseFrom(
         java.nio.ByteBuffer data,
         com.google.protobuf.ExtensionRegistryLite extensionRegistry)
         throws com.google.protobuf.InvalidProtocolBufferException {
       return PARSER.parseFrom(data, extensionRegistry);
     }
-    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Counter parseFrom(
+    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Counter parseFrom(
         com.google.protobuf.ByteString data)
         throws com.google.protobuf.InvalidProtocolBufferException {
       return PARSER.parseFrom(data);
     }
-    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Counter parseFrom(
+    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Counter parseFrom(
         com.google.protobuf.ByteString data,
         com.google.protobuf.ExtensionRegistryLite extensionRegistry)
         throws com.google.protobuf.InvalidProtocolBufferException {
       return PARSER.parseFrom(data, extensionRegistry);
     }
-    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Counter parseFrom(byte[] data)
+    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Counter parseFrom(byte[] data)
         throws com.google.protobuf.InvalidProtocolBufferException {
       return PARSER.parseFrom(data);
     }
-    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Counter parseFrom(
+    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Counter parseFrom(
         byte[] data,
         com.google.protobuf.ExtensionRegistryLite extensionRegistry)
         throws com.google.protobuf.InvalidProtocolBufferException {
       return PARSER.parseFrom(data, extensionRegistry);
     }
-    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Counter parseFrom(java.io.InputStream input)
+    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Counter parseFrom(java.io.InputStream input)
         throws java.io.IOException {
       return com.google.protobuf.GeneratedMessage
           .parseWithIOException(PARSER, input);
     }
-    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Counter parseFrom(
+    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Counter parseFrom(
         java.io.InputStream input,
         com.google.protobuf.ExtensionRegistryLite extensionRegistry)
         throws java.io.IOException {
@@ -1704,26 +1704,26 @@ public static io.prometheus.metrics.expositionformats.generated.com_google_proto
           .parseWithIOException(PARSER, input, extensionRegistry);
     }
 
-    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Counter parseDelimitedFrom(java.io.InputStream input)
+    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Counter parseDelimitedFrom(java.io.InputStream input)
         throws java.io.IOException {
       return com.google.protobuf.GeneratedMessage
           .parseDelimitedWithIOException(PARSER, input);
     }
 
-    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Counter parseDelimitedFrom(
+    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Counter parseDelimitedFrom(
         java.io.InputStream input,
         com.google.protobuf.ExtensionRegistryLite extensionRegistry)
         throws java.io.IOException {
       return com.google.protobuf.GeneratedMessage
           .parseDelimitedWithIOException(PARSER, input, extensionRegistry);
     }
-    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Counter parseFrom(
+    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Counter parseFrom(
         com.google.protobuf.CodedInputStream input)
         throws java.io.IOException {
       return com.google.protobuf.GeneratedMessage
           .parseWithIOException(PARSER, input);
     }
-    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Counter parseFrom(
+    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Counter parseFrom(
         com.google.protobuf.CodedInputStream input,
         com.google.protobuf.ExtensionRegistryLite extensionRegistry)
         throws java.io.IOException {
@@ -1736,7 +1736,7 @@ public static io.prometheus.metrics.expositionformats.generated.com_google_proto
     public static Builder newBuilder() {
       return DEFAULT_INSTANCE.toBuilder();
     }
-    public static Builder newBuilder(io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Counter prototype) {
+    public static Builder newBuilder(io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Counter prototype) {
       return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype);
     }
     @java.lang.Override
@@ -1757,21 +1757,21 @@ protected Builder newBuilderForType(
     public static final class Builder extends
         com.google.protobuf.GeneratedMessage.Builder implements
         // @@protoc_insertion_point(builder_implements:io.prometheus.client.Counter)
-        io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.CounterOrBuilder {
+        io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.CounterOrBuilder {
       public static final com.google.protobuf.Descriptors.Descriptor
           getDescriptor() {
-        return io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.internal_static_io_prometheus_client_Counter_descriptor;
+        return io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.internal_static_io_prometheus_client_Counter_descriptor;
       }
 
       @java.lang.Override
       protected com.google.protobuf.GeneratedMessage.FieldAccessorTable
           internalGetFieldAccessorTable() {
-        return io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.internal_static_io_prometheus_client_Counter_fieldAccessorTable
+        return io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.internal_static_io_prometheus_client_Counter_fieldAccessorTable
             .ensureFieldAccessorsInitialized(
-                io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Counter.class, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Counter.Builder.class);
+                io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Counter.class, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Counter.Builder.class);
       }
 
-      // Construct using io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Counter.newBuilder()
+      // Construct using io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Counter.newBuilder()
       private Builder() {
         maybeForceBuilderInitialization();
       }
@@ -1809,17 +1809,17 @@ public Builder clear() {
       @java.lang.Override
       public com.google.protobuf.Descriptors.Descriptor
           getDescriptorForType() {
-        return io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.internal_static_io_prometheus_client_Counter_descriptor;
+        return io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.internal_static_io_prometheus_client_Counter_descriptor;
       }
 
       @java.lang.Override
-      public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Counter getDefaultInstanceForType() {
-        return io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Counter.getDefaultInstance();
+      public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Counter getDefaultInstanceForType() {
+        return io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Counter.getDefaultInstance();
       }
 
       @java.lang.Override
-      public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Counter build() {
-        io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Counter result = buildPartial();
+      public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Counter build() {
+        io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Counter result = buildPartial();
         if (!result.isInitialized()) {
           throw newUninitializedMessageException(result);
         }
@@ -1827,14 +1827,14 @@ public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_3
       }
 
       @java.lang.Override
-      public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Counter buildPartial() {
-        io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Counter result = new io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Counter(this);
+      public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Counter buildPartial() {
+        io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Counter result = new io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Counter(this);
         if (bitField0_ != 0) { buildPartial0(result); }
         onBuilt();
         return result;
       }
 
-      private void buildPartial0(io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Counter result) {
+      private void buildPartial0(io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Counter result) {
         int from_bitField0_ = bitField0_;
         int to_bitField0_ = 0;
         if (((from_bitField0_ & 0x00000001) != 0)) {
@@ -1858,16 +1858,16 @@ private void buildPartial0(io.prometheus.metrics.expositionformats.generated.com
 
       @java.lang.Override
       public Builder mergeFrom(com.google.protobuf.Message other) {
-        if (other instanceof io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Counter) {
-          return mergeFrom((io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Counter)other);
+        if (other instanceof io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Counter) {
+          return mergeFrom((io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Counter)other);
         } else {
           super.mergeFrom(other);
           return this;
         }
       }
 
-      public Builder mergeFrom(io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Counter other) {
-        if (other == io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Counter.getDefaultInstance()) return this;
+      public Builder mergeFrom(io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Counter other) {
+        if (other == io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Counter.getDefaultInstance()) return this;
         if (other.hasValue()) {
           setValue(other.getValue());
         }
@@ -1979,9 +1979,9 @@ public Builder clearValue() {
         return this;
       }
 
-      private io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Exemplar exemplar_;
+      private io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Exemplar exemplar_;
       private com.google.protobuf.SingleFieldBuilder<
-          io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Exemplar, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Exemplar.Builder, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.ExemplarOrBuilder> exemplarBuilder_;
+          io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Exemplar, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Exemplar.Builder, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.ExemplarOrBuilder> exemplarBuilder_;
       /**
        * optional .io.prometheus.client.Exemplar exemplar = 2;
        * @return Whether the exemplar field is set.
@@ -1993,9 +1993,9 @@ public boolean hasExemplar() {
        * optional .io.prometheus.client.Exemplar exemplar = 2;
        * @return The exemplar.
        */
-      public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Exemplar getExemplar() {
+      public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Exemplar getExemplar() {
         if (exemplarBuilder_ == null) {
-          return exemplar_ == null ? io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Exemplar.getDefaultInstance() : exemplar_;
+          return exemplar_ == null ? io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Exemplar.getDefaultInstance() : exemplar_;
         } else {
           return exemplarBuilder_.getMessage();
         }
@@ -2003,7 +2003,7 @@ public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_3
       /**
        * optional .io.prometheus.client.Exemplar exemplar = 2;
        */
-      public Builder setExemplar(io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Exemplar value) {
+      public Builder setExemplar(io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Exemplar value) {
         if (exemplarBuilder_ == null) {
           if (value == null) {
             throw new NullPointerException();
@@ -2020,7 +2020,7 @@ public Builder setExemplar(io.prometheus.metrics.expositionformats.generated.com
        * optional .io.prometheus.client.Exemplar exemplar = 2;
        */
       public Builder setExemplar(
-          io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Exemplar.Builder builderForValue) {
+          io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Exemplar.Builder builderForValue) {
         if (exemplarBuilder_ == null) {
           exemplar_ = builderForValue.build();
         } else {
@@ -2033,11 +2033,11 @@ public Builder setExemplar(
       /**
        * optional .io.prometheus.client.Exemplar exemplar = 2;
        */
-      public Builder mergeExemplar(io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Exemplar value) {
+      public Builder mergeExemplar(io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Exemplar value) {
         if (exemplarBuilder_ == null) {
           if (((bitField0_ & 0x00000002) != 0) &&
             exemplar_ != null &&
-            exemplar_ != io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Exemplar.getDefaultInstance()) {
+            exemplar_ != io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Exemplar.getDefaultInstance()) {
             getExemplarBuilder().mergeFrom(value);
           } else {
             exemplar_ = value;
@@ -2067,7 +2067,7 @@ public Builder clearExemplar() {
       /**
        * optional .io.prometheus.client.Exemplar exemplar = 2;
        */
-      public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Exemplar.Builder getExemplarBuilder() {
+      public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Exemplar.Builder getExemplarBuilder() {
         bitField0_ |= 0x00000002;
         onChanged();
         return internalGetExemplarFieldBuilder().getBuilder();
@@ -2075,23 +2075,23 @@ public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_3
       /**
        * optional .io.prometheus.client.Exemplar exemplar = 2;
        */
-      public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.ExemplarOrBuilder getExemplarOrBuilder() {
+      public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.ExemplarOrBuilder getExemplarOrBuilder() {
         if (exemplarBuilder_ != null) {
           return exemplarBuilder_.getMessageOrBuilder();
         } else {
           return exemplar_ == null ?
-              io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Exemplar.getDefaultInstance() : exemplar_;
+              io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Exemplar.getDefaultInstance() : exemplar_;
         }
       }
       /**
        * optional .io.prometheus.client.Exemplar exemplar = 2;
        */
       private com.google.protobuf.SingleFieldBuilder<
-          io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Exemplar, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Exemplar.Builder, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.ExemplarOrBuilder> 
+          io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Exemplar, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Exemplar.Builder, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.ExemplarOrBuilder> 
           internalGetExemplarFieldBuilder() {
         if (exemplarBuilder_ == null) {
           exemplarBuilder_ = new com.google.protobuf.SingleFieldBuilder<
-              io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Exemplar, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Exemplar.Builder, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.ExemplarOrBuilder>(
+              io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Exemplar, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Exemplar.Builder, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.ExemplarOrBuilder>(
                   getExemplar(),
                   getParentForChildren(),
                   isClean());
@@ -2225,12 +2225,12 @@ public com.google.protobuf.TimestampOrBuilder getCreatedTimestampOrBuilder() {
     }
 
     // @@protoc_insertion_point(class_scope:io.prometheus.client.Counter)
-    private static final io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Counter DEFAULT_INSTANCE;
+    private static final io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Counter DEFAULT_INSTANCE;
     static {
-      DEFAULT_INSTANCE = new io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Counter();
+      DEFAULT_INSTANCE = new io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Counter();
     }
 
-    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Counter getDefaultInstance() {
+    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Counter getDefaultInstance() {
       return DEFAULT_INSTANCE;
     }
 
@@ -2266,7 +2266,7 @@ public com.google.protobuf.Parser getParserForType() {
     }
 
     @java.lang.Override
-    public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Counter getDefaultInstanceForType() {
+    public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Counter getDefaultInstanceForType() {
       return DEFAULT_INSTANCE;
     }
 
@@ -2310,10 +2310,10 @@ public static final class Quantile extends
       com.google.protobuf.RuntimeVersion.validateProtobufGencodeVersion(
         com.google.protobuf.RuntimeVersion.RuntimeDomain.PUBLIC,
         /* major= */ 4,
-        /* minor= */ 31,
-        /* patch= */ 1,
+        /* minor= */ 33,
+        /* patch= */ 5,
         /* suffix= */ "",
-        Quantile.class.getName());
+        "Quantile");
     }
     // Use Quantile.newBuilder() to construct.
     private Quantile(com.google.protobuf.GeneratedMessage.Builder builder) {
@@ -2324,15 +2324,15 @@ private Quantile() {
 
     public static final com.google.protobuf.Descriptors.Descriptor
         getDescriptor() {
-      return io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.internal_static_io_prometheus_client_Quantile_descriptor;
+      return io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.internal_static_io_prometheus_client_Quantile_descriptor;
     }
 
     @java.lang.Override
     protected com.google.protobuf.GeneratedMessage.FieldAccessorTable
         internalGetFieldAccessorTable() {
-      return io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.internal_static_io_prometheus_client_Quantile_fieldAccessorTable
+      return io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.internal_static_io_prometheus_client_Quantile_fieldAccessorTable
           .ensureFieldAccessorsInitialized(
-              io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Quantile.class, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Quantile.Builder.class);
+              io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Quantile.class, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Quantile.Builder.class);
     }
 
     private int bitField0_;
@@ -2421,10 +2421,10 @@ public boolean equals(final java.lang.Object obj) {
       if (obj == this) {
        return true;
       }
-      if (!(obj instanceof io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Quantile)) {
+      if (!(obj instanceof io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Quantile)) {
         return super.equals(obj);
       }
-      io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Quantile other = (io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Quantile) obj;
+      io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Quantile other = (io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Quantile) obj;
 
       if (hasQuantile() != other.hasQuantile()) return false;
       if (hasQuantile()) {
@@ -2464,44 +2464,44 @@ public int hashCode() {
       return hash;
     }
 
-    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Quantile parseFrom(
+    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Quantile parseFrom(
         java.nio.ByteBuffer data)
         throws com.google.protobuf.InvalidProtocolBufferException {
       return PARSER.parseFrom(data);
     }
-    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Quantile parseFrom(
+    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Quantile parseFrom(
         java.nio.ByteBuffer data,
         com.google.protobuf.ExtensionRegistryLite extensionRegistry)
         throws com.google.protobuf.InvalidProtocolBufferException {
       return PARSER.parseFrom(data, extensionRegistry);
     }
-    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Quantile parseFrom(
+    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Quantile parseFrom(
         com.google.protobuf.ByteString data)
         throws com.google.protobuf.InvalidProtocolBufferException {
       return PARSER.parseFrom(data);
     }
-    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Quantile parseFrom(
+    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Quantile parseFrom(
         com.google.protobuf.ByteString data,
         com.google.protobuf.ExtensionRegistryLite extensionRegistry)
         throws com.google.protobuf.InvalidProtocolBufferException {
       return PARSER.parseFrom(data, extensionRegistry);
     }
-    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Quantile parseFrom(byte[] data)
+    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Quantile parseFrom(byte[] data)
         throws com.google.protobuf.InvalidProtocolBufferException {
       return PARSER.parseFrom(data);
     }
-    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Quantile parseFrom(
+    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Quantile parseFrom(
         byte[] data,
         com.google.protobuf.ExtensionRegistryLite extensionRegistry)
         throws com.google.protobuf.InvalidProtocolBufferException {
       return PARSER.parseFrom(data, extensionRegistry);
     }
-    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Quantile parseFrom(java.io.InputStream input)
+    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Quantile parseFrom(java.io.InputStream input)
         throws java.io.IOException {
       return com.google.protobuf.GeneratedMessage
           .parseWithIOException(PARSER, input);
     }
-    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Quantile parseFrom(
+    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Quantile parseFrom(
         java.io.InputStream input,
         com.google.protobuf.ExtensionRegistryLite extensionRegistry)
         throws java.io.IOException {
@@ -2509,26 +2509,26 @@ public static io.prometheus.metrics.expositionformats.generated.com_google_proto
           .parseWithIOException(PARSER, input, extensionRegistry);
     }
 
-    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Quantile parseDelimitedFrom(java.io.InputStream input)
+    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Quantile parseDelimitedFrom(java.io.InputStream input)
         throws java.io.IOException {
       return com.google.protobuf.GeneratedMessage
           .parseDelimitedWithIOException(PARSER, input);
     }
 
-    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Quantile parseDelimitedFrom(
+    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Quantile parseDelimitedFrom(
         java.io.InputStream input,
         com.google.protobuf.ExtensionRegistryLite extensionRegistry)
         throws java.io.IOException {
       return com.google.protobuf.GeneratedMessage
           .parseDelimitedWithIOException(PARSER, input, extensionRegistry);
     }
-    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Quantile parseFrom(
+    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Quantile parseFrom(
         com.google.protobuf.CodedInputStream input)
         throws java.io.IOException {
       return com.google.protobuf.GeneratedMessage
           .parseWithIOException(PARSER, input);
     }
-    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Quantile parseFrom(
+    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Quantile parseFrom(
         com.google.protobuf.CodedInputStream input,
         com.google.protobuf.ExtensionRegistryLite extensionRegistry)
         throws java.io.IOException {
@@ -2541,7 +2541,7 @@ public static io.prometheus.metrics.expositionformats.generated.com_google_proto
     public static Builder newBuilder() {
       return DEFAULT_INSTANCE.toBuilder();
     }
-    public static Builder newBuilder(io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Quantile prototype) {
+    public static Builder newBuilder(io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Quantile prototype) {
       return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype);
     }
     @java.lang.Override
@@ -2562,21 +2562,21 @@ protected Builder newBuilderForType(
     public static final class Builder extends
         com.google.protobuf.GeneratedMessage.Builder implements
         // @@protoc_insertion_point(builder_implements:io.prometheus.client.Quantile)
-        io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.QuantileOrBuilder {
+        io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.QuantileOrBuilder {
       public static final com.google.protobuf.Descriptors.Descriptor
           getDescriptor() {
-        return io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.internal_static_io_prometheus_client_Quantile_descriptor;
+        return io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.internal_static_io_prometheus_client_Quantile_descriptor;
       }
 
       @java.lang.Override
       protected com.google.protobuf.GeneratedMessage.FieldAccessorTable
           internalGetFieldAccessorTable() {
-        return io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.internal_static_io_prometheus_client_Quantile_fieldAccessorTable
+        return io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.internal_static_io_prometheus_client_Quantile_fieldAccessorTable
             .ensureFieldAccessorsInitialized(
-                io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Quantile.class, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Quantile.Builder.class);
+                io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Quantile.class, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Quantile.Builder.class);
       }
 
-      // Construct using io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Quantile.newBuilder()
+      // Construct using io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Quantile.newBuilder()
       private Builder() {
 
       }
@@ -2598,17 +2598,17 @@ public Builder clear() {
       @java.lang.Override
       public com.google.protobuf.Descriptors.Descriptor
           getDescriptorForType() {
-        return io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.internal_static_io_prometheus_client_Quantile_descriptor;
+        return io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.internal_static_io_prometheus_client_Quantile_descriptor;
       }
 
       @java.lang.Override
-      public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Quantile getDefaultInstanceForType() {
-        return io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Quantile.getDefaultInstance();
+      public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Quantile getDefaultInstanceForType() {
+        return io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Quantile.getDefaultInstance();
       }
 
       @java.lang.Override
-      public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Quantile build() {
-        io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Quantile result = buildPartial();
+      public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Quantile build() {
+        io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Quantile result = buildPartial();
         if (!result.isInitialized()) {
           throw newUninitializedMessageException(result);
         }
@@ -2616,14 +2616,14 @@ public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_3
       }
 
       @java.lang.Override
-      public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Quantile buildPartial() {
-        io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Quantile result = new io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Quantile(this);
+      public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Quantile buildPartial() {
+        io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Quantile result = new io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Quantile(this);
         if (bitField0_ != 0) { buildPartial0(result); }
         onBuilt();
         return result;
       }
 
-      private void buildPartial0(io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Quantile result) {
+      private void buildPartial0(io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Quantile result) {
         int from_bitField0_ = bitField0_;
         int to_bitField0_ = 0;
         if (((from_bitField0_ & 0x00000001) != 0)) {
@@ -2639,16 +2639,16 @@ private void buildPartial0(io.prometheus.metrics.expositionformats.generated.com
 
       @java.lang.Override
       public Builder mergeFrom(com.google.protobuf.Message other) {
-        if (other instanceof io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Quantile) {
-          return mergeFrom((io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Quantile)other);
+        if (other instanceof io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Quantile) {
+          return mergeFrom((io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Quantile)other);
         } else {
           super.mergeFrom(other);
           return this;
         }
       }
 
-      public Builder mergeFrom(io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Quantile other) {
-        if (other == io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Quantile.getDefaultInstance()) return this;
+      public Builder mergeFrom(io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Quantile other) {
+        if (other == io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Quantile.getDefaultInstance()) return this;
         if (other.hasQuantile()) {
           setQuantile(other.getQuantile());
         }
@@ -2792,12 +2792,12 @@ public Builder clearValue() {
     }
 
     // @@protoc_insertion_point(class_scope:io.prometheus.client.Quantile)
-    private static final io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Quantile DEFAULT_INSTANCE;
+    private static final io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Quantile DEFAULT_INSTANCE;
     static {
-      DEFAULT_INSTANCE = new io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Quantile();
+      DEFAULT_INSTANCE = new io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Quantile();
     }
 
-    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Quantile getDefaultInstance() {
+    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Quantile getDefaultInstance() {
       return DEFAULT_INSTANCE;
     }
 
@@ -2833,7 +2833,7 @@ public com.google.protobuf.Parser getParserForType() {
     }
 
     @java.lang.Override
-    public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Quantile getDefaultInstanceForType() {
+    public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Quantile getDefaultInstanceForType() {
       return DEFAULT_INSTANCE;
     }
 
@@ -2868,12 +2868,12 @@ public interface SummaryOrBuilder extends
     /**
      * repeated .io.prometheus.client.Quantile quantile = 3;
      */
-    java.util.List 
+    java.util.List 
         getQuantileList();
     /**
      * repeated .io.prometheus.client.Quantile quantile = 3;
      */
-    io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Quantile getQuantile(int index);
+    io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Quantile getQuantile(int index);
     /**
      * repeated .io.prometheus.client.Quantile quantile = 3;
      */
@@ -2881,12 +2881,12 @@ public interface SummaryOrBuilder extends
     /**
      * repeated .io.prometheus.client.Quantile quantile = 3;
      */
-    java.util.List 
+    java.util.List 
         getQuantileOrBuilderList();
     /**
      * repeated .io.prometheus.client.Quantile quantile = 3;
      */
-    io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.QuantileOrBuilder getQuantileOrBuilder(
+    io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.QuantileOrBuilder getQuantileOrBuilder(
         int index);
 
     /**
@@ -2916,10 +2916,10 @@ public static final class Summary extends
       com.google.protobuf.RuntimeVersion.validateProtobufGencodeVersion(
         com.google.protobuf.RuntimeVersion.RuntimeDomain.PUBLIC,
         /* major= */ 4,
-        /* minor= */ 31,
-        /* patch= */ 1,
+        /* minor= */ 33,
+        /* patch= */ 5,
         /* suffix= */ "",
-        Summary.class.getName());
+        "Summary");
     }
     // Use Summary.newBuilder() to construct.
     private Summary(com.google.protobuf.GeneratedMessage.Builder builder) {
@@ -2931,15 +2931,15 @@ private Summary() {
 
     public static final com.google.protobuf.Descriptors.Descriptor
         getDescriptor() {
-      return io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.internal_static_io_prometheus_client_Summary_descriptor;
+      return io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.internal_static_io_prometheus_client_Summary_descriptor;
     }
 
     @java.lang.Override
     protected com.google.protobuf.GeneratedMessage.FieldAccessorTable
         internalGetFieldAccessorTable() {
-      return io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.internal_static_io_prometheus_client_Summary_fieldAccessorTable
+      return io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.internal_static_io_prometheus_client_Summary_fieldAccessorTable
           .ensureFieldAccessorsInitialized(
-              io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Summary.class, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Summary.Builder.class);
+              io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Summary.class, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Summary.Builder.class);
     }
 
     private int bitField0_;
@@ -2983,19 +2983,19 @@ public double getSampleSum() {
 
     public static final int QUANTILE_FIELD_NUMBER = 3;
     @SuppressWarnings("serial")
-    private java.util.List quantile_;
+    private java.util.List quantile_;
     /**
      * repeated .io.prometheus.client.Quantile quantile = 3;
      */
     @java.lang.Override
-    public java.util.List getQuantileList() {
+    public java.util.List getQuantileList() {
       return quantile_;
     }
     /**
      * repeated .io.prometheus.client.Quantile quantile = 3;
      */
     @java.lang.Override
-    public java.util.List 
+    public java.util.List 
         getQuantileOrBuilderList() {
       return quantile_;
     }
@@ -3010,14 +3010,14 @@ public int getQuantileCount() {
      * repeated .io.prometheus.client.Quantile quantile = 3;
      */
     @java.lang.Override
-    public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Quantile getQuantile(int index) {
+    public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Quantile getQuantile(int index) {
       return quantile_.get(index);
     }
     /**
      * repeated .io.prometheus.client.Quantile quantile = 3;
      */
     @java.lang.Override
-    public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.QuantileOrBuilder getQuantileOrBuilder(
+    public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.QuantileOrBuilder getQuantileOrBuilder(
         int index) {
       return quantile_.get(index);
     }
@@ -3109,10 +3109,10 @@ public boolean equals(final java.lang.Object obj) {
       if (obj == this) {
        return true;
       }
-      if (!(obj instanceof io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Summary)) {
+      if (!(obj instanceof io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Summary)) {
         return super.equals(obj);
       }
-      io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Summary other = (io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Summary) obj;
+      io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Summary other = (io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Summary) obj;
 
       if (hasSampleCount() != other.hasSampleCount()) return false;
       if (hasSampleCount()) {
@@ -3166,44 +3166,44 @@ public int hashCode() {
       return hash;
     }
 
-    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Summary parseFrom(
+    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Summary parseFrom(
         java.nio.ByteBuffer data)
         throws com.google.protobuf.InvalidProtocolBufferException {
       return PARSER.parseFrom(data);
     }
-    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Summary parseFrom(
+    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Summary parseFrom(
         java.nio.ByteBuffer data,
         com.google.protobuf.ExtensionRegistryLite extensionRegistry)
         throws com.google.protobuf.InvalidProtocolBufferException {
       return PARSER.parseFrom(data, extensionRegistry);
     }
-    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Summary parseFrom(
+    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Summary parseFrom(
         com.google.protobuf.ByteString data)
         throws com.google.protobuf.InvalidProtocolBufferException {
       return PARSER.parseFrom(data);
     }
-    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Summary parseFrom(
+    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Summary parseFrom(
         com.google.protobuf.ByteString data,
         com.google.protobuf.ExtensionRegistryLite extensionRegistry)
         throws com.google.protobuf.InvalidProtocolBufferException {
       return PARSER.parseFrom(data, extensionRegistry);
     }
-    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Summary parseFrom(byte[] data)
+    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Summary parseFrom(byte[] data)
         throws com.google.protobuf.InvalidProtocolBufferException {
       return PARSER.parseFrom(data);
     }
-    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Summary parseFrom(
+    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Summary parseFrom(
         byte[] data,
         com.google.protobuf.ExtensionRegistryLite extensionRegistry)
         throws com.google.protobuf.InvalidProtocolBufferException {
       return PARSER.parseFrom(data, extensionRegistry);
     }
-    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Summary parseFrom(java.io.InputStream input)
+    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Summary parseFrom(java.io.InputStream input)
         throws java.io.IOException {
       return com.google.protobuf.GeneratedMessage
           .parseWithIOException(PARSER, input);
     }
-    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Summary parseFrom(
+    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Summary parseFrom(
         java.io.InputStream input,
         com.google.protobuf.ExtensionRegistryLite extensionRegistry)
         throws java.io.IOException {
@@ -3211,26 +3211,26 @@ public static io.prometheus.metrics.expositionformats.generated.com_google_proto
           .parseWithIOException(PARSER, input, extensionRegistry);
     }
 
-    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Summary parseDelimitedFrom(java.io.InputStream input)
+    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Summary parseDelimitedFrom(java.io.InputStream input)
         throws java.io.IOException {
       return com.google.protobuf.GeneratedMessage
           .parseDelimitedWithIOException(PARSER, input);
     }
 
-    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Summary parseDelimitedFrom(
+    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Summary parseDelimitedFrom(
         java.io.InputStream input,
         com.google.protobuf.ExtensionRegistryLite extensionRegistry)
         throws java.io.IOException {
       return com.google.protobuf.GeneratedMessage
           .parseDelimitedWithIOException(PARSER, input, extensionRegistry);
     }
-    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Summary parseFrom(
+    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Summary parseFrom(
         com.google.protobuf.CodedInputStream input)
         throws java.io.IOException {
       return com.google.protobuf.GeneratedMessage
           .parseWithIOException(PARSER, input);
     }
-    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Summary parseFrom(
+    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Summary parseFrom(
         com.google.protobuf.CodedInputStream input,
         com.google.protobuf.ExtensionRegistryLite extensionRegistry)
         throws java.io.IOException {
@@ -3243,7 +3243,7 @@ public static io.prometheus.metrics.expositionformats.generated.com_google_proto
     public static Builder newBuilder() {
       return DEFAULT_INSTANCE.toBuilder();
     }
-    public static Builder newBuilder(io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Summary prototype) {
+    public static Builder newBuilder(io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Summary prototype) {
       return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype);
     }
     @java.lang.Override
@@ -3264,21 +3264,21 @@ protected Builder newBuilderForType(
     public static final class Builder extends
         com.google.protobuf.GeneratedMessage.Builder implements
         // @@protoc_insertion_point(builder_implements:io.prometheus.client.Summary)
-        io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.SummaryOrBuilder {
+        io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.SummaryOrBuilder {
       public static final com.google.protobuf.Descriptors.Descriptor
           getDescriptor() {
-        return io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.internal_static_io_prometheus_client_Summary_descriptor;
+        return io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.internal_static_io_prometheus_client_Summary_descriptor;
       }
 
       @java.lang.Override
       protected com.google.protobuf.GeneratedMessage.FieldAccessorTable
           internalGetFieldAccessorTable() {
-        return io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.internal_static_io_prometheus_client_Summary_fieldAccessorTable
+        return io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.internal_static_io_prometheus_client_Summary_fieldAccessorTable
             .ensureFieldAccessorsInitialized(
-                io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Summary.class, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Summary.Builder.class);
+                io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Summary.class, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Summary.Builder.class);
       }
 
-      // Construct using io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Summary.newBuilder()
+      // Construct using io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Summary.newBuilder()
       private Builder() {
         maybeForceBuilderInitialization();
       }
@@ -3319,17 +3319,17 @@ public Builder clear() {
       @java.lang.Override
       public com.google.protobuf.Descriptors.Descriptor
           getDescriptorForType() {
-        return io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.internal_static_io_prometheus_client_Summary_descriptor;
+        return io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.internal_static_io_prometheus_client_Summary_descriptor;
       }
 
       @java.lang.Override
-      public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Summary getDefaultInstanceForType() {
-        return io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Summary.getDefaultInstance();
+      public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Summary getDefaultInstanceForType() {
+        return io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Summary.getDefaultInstance();
       }
 
       @java.lang.Override
-      public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Summary build() {
-        io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Summary result = buildPartial();
+      public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Summary build() {
+        io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Summary result = buildPartial();
         if (!result.isInitialized()) {
           throw newUninitializedMessageException(result);
         }
@@ -3337,15 +3337,15 @@ public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_3
       }
 
       @java.lang.Override
-      public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Summary buildPartial() {
-        io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Summary result = new io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Summary(this);
+      public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Summary buildPartial() {
+        io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Summary result = new io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Summary(this);
         buildPartialRepeatedFields(result);
         if (bitField0_ != 0) { buildPartial0(result); }
         onBuilt();
         return result;
       }
 
-      private void buildPartialRepeatedFields(io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Summary result) {
+      private void buildPartialRepeatedFields(io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Summary result) {
         if (quantileBuilder_ == null) {
           if (((bitField0_ & 0x00000004) != 0)) {
             quantile_ = java.util.Collections.unmodifiableList(quantile_);
@@ -3357,7 +3357,7 @@ private void buildPartialRepeatedFields(io.prometheus.metrics.expositionformats.
         }
       }
 
-      private void buildPartial0(io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Summary result) {
+      private void buildPartial0(io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Summary result) {
         int from_bitField0_ = bitField0_;
         int to_bitField0_ = 0;
         if (((from_bitField0_ & 0x00000001) != 0)) {
@@ -3379,16 +3379,16 @@ private void buildPartial0(io.prometheus.metrics.expositionformats.generated.com
 
       @java.lang.Override
       public Builder mergeFrom(com.google.protobuf.Message other) {
-        if (other instanceof io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Summary) {
-          return mergeFrom((io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Summary)other);
+        if (other instanceof io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Summary) {
+          return mergeFrom((io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Summary)other);
         } else {
           super.mergeFrom(other);
           return this;
         }
       }
 
-      public Builder mergeFrom(io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Summary other) {
-        if (other == io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Summary.getDefaultInstance()) return this;
+      public Builder mergeFrom(io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Summary other) {
+        if (other == io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Summary.getDefaultInstance()) return this;
         if (other.hasSampleCount()) {
           setSampleCount(other.getSampleCount());
         }
@@ -3461,9 +3461,9 @@ public Builder mergeFrom(
                 break;
               } // case 17
               case 26: {
-                io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Quantile m =
+                io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Quantile m =
                     input.readMessage(
-                        io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Quantile.parser(),
+                        io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Quantile.parser(),
                         extensionRegistry);
                 if (quantileBuilder_ == null) {
                   ensureQuantileIsMutable();
@@ -3577,22 +3577,22 @@ public Builder clearSampleSum() {
         return this;
       }
 
-      private java.util.List quantile_ =
+      private java.util.List quantile_ =
         java.util.Collections.emptyList();
       private void ensureQuantileIsMutable() {
         if (!((bitField0_ & 0x00000004) != 0)) {
-          quantile_ = new java.util.ArrayList(quantile_);
+          quantile_ = new java.util.ArrayList(quantile_);
           bitField0_ |= 0x00000004;
          }
       }
 
       private com.google.protobuf.RepeatedFieldBuilder<
-          io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Quantile, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Quantile.Builder, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.QuantileOrBuilder> quantileBuilder_;
+          io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Quantile, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Quantile.Builder, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.QuantileOrBuilder> quantileBuilder_;
 
       /**
        * repeated .io.prometheus.client.Quantile quantile = 3;
        */
-      public java.util.List getQuantileList() {
+      public java.util.List getQuantileList() {
         if (quantileBuilder_ == null) {
           return java.util.Collections.unmodifiableList(quantile_);
         } else {
@@ -3612,7 +3612,7 @@ public int getQuantileCount() {
       /**
        * repeated .io.prometheus.client.Quantile quantile = 3;
        */
-      public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Quantile getQuantile(int index) {
+      public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Quantile getQuantile(int index) {
         if (quantileBuilder_ == null) {
           return quantile_.get(index);
         } else {
@@ -3623,7 +3623,7 @@ public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_3
        * repeated .io.prometheus.client.Quantile quantile = 3;
        */
       public Builder setQuantile(
-          int index, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Quantile value) {
+          int index, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Quantile value) {
         if (quantileBuilder_ == null) {
           if (value == null) {
             throw new NullPointerException();
@@ -3640,7 +3640,7 @@ public Builder setQuantile(
        * repeated .io.prometheus.client.Quantile quantile = 3;
        */
       public Builder setQuantile(
-          int index, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Quantile.Builder builderForValue) {
+          int index, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Quantile.Builder builderForValue) {
         if (quantileBuilder_ == null) {
           ensureQuantileIsMutable();
           quantile_.set(index, builderForValue.build());
@@ -3653,7 +3653,7 @@ public Builder setQuantile(
       /**
        * repeated .io.prometheus.client.Quantile quantile = 3;
        */
-      public Builder addQuantile(io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Quantile value) {
+      public Builder addQuantile(io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Quantile value) {
         if (quantileBuilder_ == null) {
           if (value == null) {
             throw new NullPointerException();
@@ -3670,7 +3670,7 @@ public Builder addQuantile(io.prometheus.metrics.expositionformats.generated.com
        * repeated .io.prometheus.client.Quantile quantile = 3;
        */
       public Builder addQuantile(
-          int index, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Quantile value) {
+          int index, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Quantile value) {
         if (quantileBuilder_ == null) {
           if (value == null) {
             throw new NullPointerException();
@@ -3687,7 +3687,7 @@ public Builder addQuantile(
        * repeated .io.prometheus.client.Quantile quantile = 3;
        */
       public Builder addQuantile(
-          io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Quantile.Builder builderForValue) {
+          io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Quantile.Builder builderForValue) {
         if (quantileBuilder_ == null) {
           ensureQuantileIsMutable();
           quantile_.add(builderForValue.build());
@@ -3701,7 +3701,7 @@ public Builder addQuantile(
        * repeated .io.prometheus.client.Quantile quantile = 3;
        */
       public Builder addQuantile(
-          int index, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Quantile.Builder builderForValue) {
+          int index, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Quantile.Builder builderForValue) {
         if (quantileBuilder_ == null) {
           ensureQuantileIsMutable();
           quantile_.add(index, builderForValue.build());
@@ -3715,7 +3715,7 @@ public Builder addQuantile(
        * repeated .io.prometheus.client.Quantile quantile = 3;
        */
       public Builder addAllQuantile(
-          java.lang.Iterable values) {
+          java.lang.Iterable values) {
         if (quantileBuilder_ == null) {
           ensureQuantileIsMutable();
           com.google.protobuf.AbstractMessageLite.Builder.addAll(
@@ -3755,14 +3755,14 @@ public Builder removeQuantile(int index) {
       /**
        * repeated .io.prometheus.client.Quantile quantile = 3;
        */
-      public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Quantile.Builder getQuantileBuilder(
+      public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Quantile.Builder getQuantileBuilder(
           int index) {
         return internalGetQuantileFieldBuilder().getBuilder(index);
       }
       /**
        * repeated .io.prometheus.client.Quantile quantile = 3;
        */
-      public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.QuantileOrBuilder getQuantileOrBuilder(
+      public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.QuantileOrBuilder getQuantileOrBuilder(
           int index) {
         if (quantileBuilder_ == null) {
           return quantile_.get(index);  } else {
@@ -3772,7 +3772,7 @@ public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_3
       /**
        * repeated .io.prometheus.client.Quantile quantile = 3;
        */
-      public java.util.List 
+      public java.util.List 
            getQuantileOrBuilderList() {
         if (quantileBuilder_ != null) {
           return quantileBuilder_.getMessageOrBuilderList();
@@ -3783,31 +3783,31 @@ public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_3
       /**
        * repeated .io.prometheus.client.Quantile quantile = 3;
        */
-      public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Quantile.Builder addQuantileBuilder() {
+      public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Quantile.Builder addQuantileBuilder() {
         return internalGetQuantileFieldBuilder().addBuilder(
-            io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Quantile.getDefaultInstance());
+            io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Quantile.getDefaultInstance());
       }
       /**
        * repeated .io.prometheus.client.Quantile quantile = 3;
        */
-      public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Quantile.Builder addQuantileBuilder(
+      public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Quantile.Builder addQuantileBuilder(
           int index) {
         return internalGetQuantileFieldBuilder().addBuilder(
-            index, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Quantile.getDefaultInstance());
+            index, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Quantile.getDefaultInstance());
       }
       /**
        * repeated .io.prometheus.client.Quantile quantile = 3;
        */
-      public java.util.List 
+      public java.util.List 
            getQuantileBuilderList() {
         return internalGetQuantileFieldBuilder().getBuilderList();
       }
       private com.google.protobuf.RepeatedFieldBuilder<
-          io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Quantile, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Quantile.Builder, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.QuantileOrBuilder> 
+          io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Quantile, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Quantile.Builder, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.QuantileOrBuilder> 
           internalGetQuantileFieldBuilder() {
         if (quantileBuilder_ == null) {
           quantileBuilder_ = new com.google.protobuf.RepeatedFieldBuilder<
-              io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Quantile, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Quantile.Builder, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.QuantileOrBuilder>(
+              io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Quantile, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Quantile.Builder, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.QuantileOrBuilder>(
                   quantile_,
                   ((bitField0_ & 0x00000004) != 0),
                   getParentForChildren(),
@@ -3942,12 +3942,12 @@ public com.google.protobuf.TimestampOrBuilder getCreatedTimestampOrBuilder() {
     }
 
     // @@protoc_insertion_point(class_scope:io.prometheus.client.Summary)
-    private static final io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Summary DEFAULT_INSTANCE;
+    private static final io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Summary DEFAULT_INSTANCE;
     static {
-      DEFAULT_INSTANCE = new io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Summary();
+      DEFAULT_INSTANCE = new io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Summary();
     }
 
-    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Summary getDefaultInstance() {
+    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Summary getDefaultInstance() {
       return DEFAULT_INSTANCE;
     }
 
@@ -3983,7 +3983,7 @@ public com.google.protobuf.Parser getParserForType() {
     }
 
     @java.lang.Override
-    public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Summary getDefaultInstanceForType() {
+    public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Summary getDefaultInstanceForType() {
       return DEFAULT_INSTANCE;
     }
 
@@ -4016,10 +4016,10 @@ public static final class Untyped extends
       com.google.protobuf.RuntimeVersion.validateProtobufGencodeVersion(
         com.google.protobuf.RuntimeVersion.RuntimeDomain.PUBLIC,
         /* major= */ 4,
-        /* minor= */ 31,
-        /* patch= */ 1,
+        /* minor= */ 33,
+        /* patch= */ 5,
         /* suffix= */ "",
-        Untyped.class.getName());
+        "Untyped");
     }
     // Use Untyped.newBuilder() to construct.
     private Untyped(com.google.protobuf.GeneratedMessage.Builder builder) {
@@ -4030,15 +4030,15 @@ private Untyped() {
 
     public static final com.google.protobuf.Descriptors.Descriptor
         getDescriptor() {
-      return io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.internal_static_io_prometheus_client_Untyped_descriptor;
+      return io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.internal_static_io_prometheus_client_Untyped_descriptor;
     }
 
     @java.lang.Override
     protected com.google.protobuf.GeneratedMessage.FieldAccessorTable
         internalGetFieldAccessorTable() {
-      return io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.internal_static_io_prometheus_client_Untyped_fieldAccessorTable
+      return io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.internal_static_io_prometheus_client_Untyped_fieldAccessorTable
           .ensureFieldAccessorsInitialized(
-              io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Untyped.class, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Untyped.Builder.class);
+              io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Untyped.class, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Untyped.Builder.class);
     }
 
     private int bitField0_;
@@ -4101,10 +4101,10 @@ public boolean equals(final java.lang.Object obj) {
       if (obj == this) {
        return true;
       }
-      if (!(obj instanceof io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Untyped)) {
+      if (!(obj instanceof io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Untyped)) {
         return super.equals(obj);
       }
-      io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Untyped other = (io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Untyped) obj;
+      io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Untyped other = (io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Untyped) obj;
 
       if (hasValue() != other.hasValue()) return false;
       if (hasValue()) {
@@ -4133,44 +4133,44 @@ public int hashCode() {
       return hash;
     }
 
-    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Untyped parseFrom(
+    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Untyped parseFrom(
         java.nio.ByteBuffer data)
         throws com.google.protobuf.InvalidProtocolBufferException {
       return PARSER.parseFrom(data);
     }
-    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Untyped parseFrom(
+    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Untyped parseFrom(
         java.nio.ByteBuffer data,
         com.google.protobuf.ExtensionRegistryLite extensionRegistry)
         throws com.google.protobuf.InvalidProtocolBufferException {
       return PARSER.parseFrom(data, extensionRegistry);
     }
-    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Untyped parseFrom(
+    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Untyped parseFrom(
         com.google.protobuf.ByteString data)
         throws com.google.protobuf.InvalidProtocolBufferException {
       return PARSER.parseFrom(data);
     }
-    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Untyped parseFrom(
+    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Untyped parseFrom(
         com.google.protobuf.ByteString data,
         com.google.protobuf.ExtensionRegistryLite extensionRegistry)
         throws com.google.protobuf.InvalidProtocolBufferException {
       return PARSER.parseFrom(data, extensionRegistry);
     }
-    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Untyped parseFrom(byte[] data)
+    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Untyped parseFrom(byte[] data)
         throws com.google.protobuf.InvalidProtocolBufferException {
       return PARSER.parseFrom(data);
     }
-    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Untyped parseFrom(
+    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Untyped parseFrom(
         byte[] data,
         com.google.protobuf.ExtensionRegistryLite extensionRegistry)
         throws com.google.protobuf.InvalidProtocolBufferException {
       return PARSER.parseFrom(data, extensionRegistry);
     }
-    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Untyped parseFrom(java.io.InputStream input)
+    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Untyped parseFrom(java.io.InputStream input)
         throws java.io.IOException {
       return com.google.protobuf.GeneratedMessage
           .parseWithIOException(PARSER, input);
     }
-    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Untyped parseFrom(
+    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Untyped parseFrom(
         java.io.InputStream input,
         com.google.protobuf.ExtensionRegistryLite extensionRegistry)
         throws java.io.IOException {
@@ -4178,26 +4178,26 @@ public static io.prometheus.metrics.expositionformats.generated.com_google_proto
           .parseWithIOException(PARSER, input, extensionRegistry);
     }
 
-    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Untyped parseDelimitedFrom(java.io.InputStream input)
+    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Untyped parseDelimitedFrom(java.io.InputStream input)
         throws java.io.IOException {
       return com.google.protobuf.GeneratedMessage
           .parseDelimitedWithIOException(PARSER, input);
     }
 
-    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Untyped parseDelimitedFrom(
+    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Untyped parseDelimitedFrom(
         java.io.InputStream input,
         com.google.protobuf.ExtensionRegistryLite extensionRegistry)
         throws java.io.IOException {
       return com.google.protobuf.GeneratedMessage
           .parseDelimitedWithIOException(PARSER, input, extensionRegistry);
     }
-    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Untyped parseFrom(
+    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Untyped parseFrom(
         com.google.protobuf.CodedInputStream input)
         throws java.io.IOException {
       return com.google.protobuf.GeneratedMessage
           .parseWithIOException(PARSER, input);
     }
-    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Untyped parseFrom(
+    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Untyped parseFrom(
         com.google.protobuf.CodedInputStream input,
         com.google.protobuf.ExtensionRegistryLite extensionRegistry)
         throws java.io.IOException {
@@ -4210,7 +4210,7 @@ public static io.prometheus.metrics.expositionformats.generated.com_google_proto
     public static Builder newBuilder() {
       return DEFAULT_INSTANCE.toBuilder();
     }
-    public static Builder newBuilder(io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Untyped prototype) {
+    public static Builder newBuilder(io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Untyped prototype) {
       return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype);
     }
     @java.lang.Override
@@ -4231,21 +4231,21 @@ protected Builder newBuilderForType(
     public static final class Builder extends
         com.google.protobuf.GeneratedMessage.Builder implements
         // @@protoc_insertion_point(builder_implements:io.prometheus.client.Untyped)
-        io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.UntypedOrBuilder {
+        io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.UntypedOrBuilder {
       public static final com.google.protobuf.Descriptors.Descriptor
           getDescriptor() {
-        return io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.internal_static_io_prometheus_client_Untyped_descriptor;
+        return io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.internal_static_io_prometheus_client_Untyped_descriptor;
       }
 
       @java.lang.Override
       protected com.google.protobuf.GeneratedMessage.FieldAccessorTable
           internalGetFieldAccessorTable() {
-        return io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.internal_static_io_prometheus_client_Untyped_fieldAccessorTable
+        return io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.internal_static_io_prometheus_client_Untyped_fieldAccessorTable
             .ensureFieldAccessorsInitialized(
-                io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Untyped.class, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Untyped.Builder.class);
+                io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Untyped.class, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Untyped.Builder.class);
       }
 
-      // Construct using io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Untyped.newBuilder()
+      // Construct using io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Untyped.newBuilder()
       private Builder() {
 
       }
@@ -4266,17 +4266,17 @@ public Builder clear() {
       @java.lang.Override
       public com.google.protobuf.Descriptors.Descriptor
           getDescriptorForType() {
-        return io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.internal_static_io_prometheus_client_Untyped_descriptor;
+        return io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.internal_static_io_prometheus_client_Untyped_descriptor;
       }
 
       @java.lang.Override
-      public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Untyped getDefaultInstanceForType() {
-        return io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Untyped.getDefaultInstance();
+      public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Untyped getDefaultInstanceForType() {
+        return io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Untyped.getDefaultInstance();
       }
 
       @java.lang.Override
-      public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Untyped build() {
-        io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Untyped result = buildPartial();
+      public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Untyped build() {
+        io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Untyped result = buildPartial();
         if (!result.isInitialized()) {
           throw newUninitializedMessageException(result);
         }
@@ -4284,14 +4284,14 @@ public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_3
       }
 
       @java.lang.Override
-      public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Untyped buildPartial() {
-        io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Untyped result = new io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Untyped(this);
+      public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Untyped buildPartial() {
+        io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Untyped result = new io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Untyped(this);
         if (bitField0_ != 0) { buildPartial0(result); }
         onBuilt();
         return result;
       }
 
-      private void buildPartial0(io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Untyped result) {
+      private void buildPartial0(io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Untyped result) {
         int from_bitField0_ = bitField0_;
         int to_bitField0_ = 0;
         if (((from_bitField0_ & 0x00000001) != 0)) {
@@ -4303,16 +4303,16 @@ private void buildPartial0(io.prometheus.metrics.expositionformats.generated.com
 
       @java.lang.Override
       public Builder mergeFrom(com.google.protobuf.Message other) {
-        if (other instanceof io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Untyped) {
-          return mergeFrom((io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Untyped)other);
+        if (other instanceof io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Untyped) {
+          return mergeFrom((io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Untyped)other);
         } else {
           super.mergeFrom(other);
           return this;
         }
       }
 
-      public Builder mergeFrom(io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Untyped other) {
-        if (other == io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Untyped.getDefaultInstance()) return this;
+      public Builder mergeFrom(io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Untyped other) {
+        if (other == io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Untyped.getDefaultInstance()) return this;
         if (other.hasValue()) {
           setValue(other.getValue());
         }
@@ -4408,12 +4408,12 @@ public Builder clearValue() {
     }
 
     // @@protoc_insertion_point(class_scope:io.prometheus.client.Untyped)
-    private static final io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Untyped DEFAULT_INSTANCE;
+    private static final io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Untyped DEFAULT_INSTANCE;
     static {
-      DEFAULT_INSTANCE = new io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Untyped();
+      DEFAULT_INSTANCE = new io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Untyped();
     }
 
-    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Untyped getDefaultInstance() {
+    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Untyped getDefaultInstance() {
       return DEFAULT_INSTANCE;
     }
 
@@ -4449,7 +4449,7 @@ public com.google.protobuf.Parser getParserForType() {
     }
 
     @java.lang.Override
-    public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Untyped getDefaultInstanceForType() {
+    public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Untyped getDefaultInstanceForType() {
       return DEFAULT_INSTANCE;
     }
 
@@ -4507,7 +4507,7 @@ public interface HistogramOrBuilder extends
      *
      * repeated .io.prometheus.client.Bucket bucket = 3;
      */
-    java.util.List 
+    java.util.List 
         getBucketList();
     /**
      * 
@@ -4516,7 +4516,7 @@ public interface HistogramOrBuilder extends
      *
      * repeated .io.prometheus.client.Bucket bucket = 3;
      */
-    io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Bucket getBucket(int index);
+    io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Bucket getBucket(int index);
     /**
      * 
      * Buckets for the conventional histogram.
@@ -4532,7 +4532,7 @@ public interface HistogramOrBuilder extends
      *
      * repeated .io.prometheus.client.Bucket bucket = 3;
      */
-    java.util.List 
+    java.util.List 
         getBucketOrBuilderList();
     /**
      * 
@@ -4541,7 +4541,7 @@ public interface HistogramOrBuilder extends
      *
      * repeated .io.prometheus.client.Bucket bucket = 3;
      */
-    io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.BucketOrBuilder getBucketOrBuilder(
+    io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.BucketOrBuilder getBucketOrBuilder(
         int index);
 
     /**
@@ -4650,7 +4650,7 @@ io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Met
      *
      * repeated .io.prometheus.client.BucketSpan negative_span = 9;
      */
-    java.util.List 
+    java.util.List 
         getNegativeSpanList();
     /**
      * 
@@ -4659,7 +4659,7 @@ io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Met
      *
      * repeated .io.prometheus.client.BucketSpan negative_span = 9;
      */
-    io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.BucketSpan getNegativeSpan(int index);
+    io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.BucketSpan getNegativeSpan(int index);
     /**
      * 
      * Negative buckets for the native histogram.
@@ -4675,7 +4675,7 @@ io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Met
      *
      * repeated .io.prometheus.client.BucketSpan negative_span = 9;
      */
-    java.util.List 
+    java.util.List 
         getNegativeSpanOrBuilderList();
     /**
      * 
@@ -4684,7 +4684,7 @@ io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Met
      *
      * repeated .io.prometheus.client.BucketSpan negative_span = 9;
      */
-    io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.BucketSpanOrBuilder getNegativeSpanOrBuilder(
+    io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.BucketSpanOrBuilder getNegativeSpanOrBuilder(
         int index);
 
     /**
@@ -4761,7 +4761,7 @@ io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Met
      *
      * repeated .io.prometheus.client.BucketSpan positive_span = 12;
      */
-    java.util.List 
+    java.util.List 
         getPositiveSpanList();
     /**
      * 
@@ -4773,7 +4773,7 @@ io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Met
      *
      * repeated .io.prometheus.client.BucketSpan positive_span = 12;
      */
-    io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.BucketSpan getPositiveSpan(int index);
+    io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.BucketSpan getPositiveSpan(int index);
     /**
      * 
      * Positive buckets for the native histogram.
@@ -4795,7 +4795,7 @@ io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Met
      *
      * repeated .io.prometheus.client.BucketSpan positive_span = 12;
      */
-    java.util.List 
+    java.util.List 
         getPositiveSpanOrBuilderList();
     /**
      * 
@@ -4807,7 +4807,7 @@ io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Met
      *
      * repeated .io.prometheus.client.BucketSpan positive_span = 12;
      */
-    io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.BucketSpanOrBuilder getPositiveSpanOrBuilder(
+    io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.BucketSpanOrBuilder getPositiveSpanOrBuilder(
         int index);
 
     /**
@@ -4881,7 +4881,7 @@ io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Met
      *
      * repeated .io.prometheus.client.Exemplar exemplars = 16;
      */
-    java.util.List 
+    java.util.List 
         getExemplarsList();
     /**
      * 
@@ -4890,7 +4890,7 @@ io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Met
      *
      * repeated .io.prometheus.client.Exemplar exemplars = 16;
      */
-    io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Exemplar getExemplars(int index);
+    io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Exemplar getExemplars(int index);
     /**
      * 
      * Only used for native histograms. These exemplars MUST have a timestamp.
@@ -4906,7 +4906,7 @@ io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Met
      *
      * repeated .io.prometheus.client.Exemplar exemplars = 16;
      */
-    java.util.List 
+    java.util.List 
         getExemplarsOrBuilderList();
     /**
      * 
@@ -4915,7 +4915,7 @@ io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Met
      *
      * repeated .io.prometheus.client.Exemplar exemplars = 16;
      */
-    io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.ExemplarOrBuilder getExemplarsOrBuilder(
+    io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.ExemplarOrBuilder getExemplarsOrBuilder(
         int index);
   }
   /**
@@ -4930,10 +4930,10 @@ public static final class Histogram extends
       com.google.protobuf.RuntimeVersion.validateProtobufGencodeVersion(
         com.google.protobuf.RuntimeVersion.RuntimeDomain.PUBLIC,
         /* major= */ 4,
-        /* minor= */ 31,
-        /* patch= */ 1,
+        /* minor= */ 33,
+        /* patch= */ 5,
         /* suffix= */ "",
-        Histogram.class.getName());
+        "Histogram");
     }
     // Use Histogram.newBuilder() to construct.
     private Histogram(com.google.protobuf.GeneratedMessage.Builder builder) {
@@ -4952,15 +4952,15 @@ private Histogram() {
 
     public static final com.google.protobuf.Descriptors.Descriptor
         getDescriptor() {
-      return io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.internal_static_io_prometheus_client_Histogram_descriptor;
+      return io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.internal_static_io_prometheus_client_Histogram_descriptor;
     }
 
     @java.lang.Override
     protected com.google.protobuf.GeneratedMessage.FieldAccessorTable
         internalGetFieldAccessorTable() {
-      return io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.internal_static_io_prometheus_client_Histogram_fieldAccessorTable
+      return io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.internal_static_io_prometheus_client_Histogram_fieldAccessorTable
           .ensureFieldAccessorsInitialized(
-              io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Histogram.class, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Histogram.Builder.class);
+              io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Histogram.class, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Histogram.Builder.class);
     }
 
     private int bitField0_;
@@ -5031,7 +5031,7 @@ public double getSampleSum() {
 
     public static final int BUCKET_FIELD_NUMBER = 3;
     @SuppressWarnings("serial")
-    private java.util.List bucket_;
+    private java.util.List bucket_;
     /**
      * 
      * Buckets for the conventional histogram.
@@ -5040,7 +5040,7 @@ public double getSampleSum() {
      * repeated .io.prometheus.client.Bucket bucket = 3;
      */
     @java.lang.Override
-    public java.util.List getBucketList() {
+    public java.util.List getBucketList() {
       return bucket_;
     }
     /**
@@ -5051,7 +5051,7 @@ public java.util.Listrepeated .io.prometheus.client.Bucket bucket = 3;
      */
     @java.lang.Override
-    public java.util.List 
+    public java.util.List 
         getBucketOrBuilderList() {
       return bucket_;
     }
@@ -5074,7 +5074,7 @@ public int getBucketCount() {
      * repeated .io.prometheus.client.Bucket bucket = 3;
      */
     @java.lang.Override
-    public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Bucket getBucket(int index) {
+    public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Bucket getBucket(int index) {
       return bucket_.get(index);
     }
     /**
@@ -5085,7 +5085,7 @@ public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_3
      * repeated .io.prometheus.client.Bucket bucket = 3;
      */
     @java.lang.Override
-    public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.BucketOrBuilder getBucketOrBuilder(
+    public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.BucketOrBuilder getBucketOrBuilder(
         int index) {
       return bucket_.get(index);
     }
@@ -5234,7 +5234,7 @@ public double getZeroCountFloat() {
 
     public static final int NEGATIVE_SPAN_FIELD_NUMBER = 9;
     @SuppressWarnings("serial")
-    private java.util.List negativeSpan_;
+    private java.util.List negativeSpan_;
     /**
      * 
      * Negative buckets for the native histogram.
@@ -5243,7 +5243,7 @@ public double getZeroCountFloat() {
      * repeated .io.prometheus.client.BucketSpan negative_span = 9;
      */
     @java.lang.Override
-    public java.util.List getNegativeSpanList() {
+    public java.util.List getNegativeSpanList() {
       return negativeSpan_;
     }
     /**
@@ -5254,7 +5254,7 @@ public java.util.Listrepeated .io.prometheus.client.BucketSpan negative_span = 9;
      */
     @java.lang.Override
-    public java.util.List 
+    public java.util.List 
         getNegativeSpanOrBuilderList() {
       return negativeSpan_;
     }
@@ -5277,7 +5277,7 @@ public int getNegativeSpanCount() {
      * repeated .io.prometheus.client.BucketSpan negative_span = 9;
      */
     @java.lang.Override
-    public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.BucketSpan getNegativeSpan(int index) {
+    public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.BucketSpan getNegativeSpan(int index) {
       return negativeSpan_.get(index);
     }
     /**
@@ -5288,7 +5288,7 @@ public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_3
      * repeated .io.prometheus.client.BucketSpan negative_span = 9;
      */
     @java.lang.Override
-    public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.BucketSpanOrBuilder getNegativeSpanOrBuilder(
+    public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.BucketSpanOrBuilder getNegativeSpanOrBuilder(
         int index) {
       return negativeSpan_.get(index);
     }
@@ -5383,7 +5383,7 @@ public double getNegativeCount(int index) {
 
     public static final int POSITIVE_SPAN_FIELD_NUMBER = 12;
     @SuppressWarnings("serial")
-    private java.util.List positiveSpan_;
+    private java.util.List positiveSpan_;
     /**
      * 
      * Positive buckets for the native histogram.
@@ -5395,7 +5395,7 @@ public double getNegativeCount(int index) {
      * repeated .io.prometheus.client.BucketSpan positive_span = 12;
      */
     @java.lang.Override
-    public java.util.List getPositiveSpanList() {
+    public java.util.List getPositiveSpanList() {
       return positiveSpan_;
     }
     /**
@@ -5409,7 +5409,7 @@ public java.util.Listrepeated .io.prometheus.client.BucketSpan positive_span = 12;
      */
     @java.lang.Override
-    public java.util.List 
+    public java.util.List 
         getPositiveSpanOrBuilderList() {
       return positiveSpan_;
     }
@@ -5438,7 +5438,7 @@ public int getPositiveSpanCount() {
      * repeated .io.prometheus.client.BucketSpan positive_span = 12;
      */
     @java.lang.Override
-    public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.BucketSpan getPositiveSpan(int index) {
+    public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.BucketSpan getPositiveSpan(int index) {
       return positiveSpan_.get(index);
     }
     /**
@@ -5452,7 +5452,7 @@ public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_3
      * repeated .io.prometheus.client.BucketSpan positive_span = 12;
      */
     @java.lang.Override
-    public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.BucketSpanOrBuilder getPositiveSpanOrBuilder(
+    public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.BucketSpanOrBuilder getPositiveSpanOrBuilder(
         int index) {
       return positiveSpan_.get(index);
     }
@@ -5547,7 +5547,7 @@ public double getPositiveCount(int index) {
 
     public static final int EXEMPLARS_FIELD_NUMBER = 16;
     @SuppressWarnings("serial")
-    private java.util.List exemplars_;
+    private java.util.List exemplars_;
     /**
      * 
      * Only used for native histograms. These exemplars MUST have a timestamp.
@@ -5556,7 +5556,7 @@ public double getPositiveCount(int index) {
      * repeated .io.prometheus.client.Exemplar exemplars = 16;
      */
     @java.lang.Override
-    public java.util.List getExemplarsList() {
+    public java.util.List getExemplarsList() {
       return exemplars_;
     }
     /**
@@ -5567,7 +5567,7 @@ public java.util.Listrepeated .io.prometheus.client.Exemplar exemplars = 16;
      */
     @java.lang.Override
-    public java.util.List 
+    public java.util.List 
         getExemplarsOrBuilderList() {
       return exemplars_;
     }
@@ -5590,7 +5590,7 @@ public int getExemplarsCount() {
      * repeated .io.prometheus.client.Exemplar exemplars = 16;
      */
     @java.lang.Override
-    public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Exemplar getExemplars(int index) {
+    public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Exemplar getExemplars(int index) {
       return exemplars_.get(index);
     }
     /**
@@ -5601,7 +5601,7 @@ public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_3
      * repeated .io.prometheus.client.Exemplar exemplars = 16;
      */
     @java.lang.Override
-    public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.ExemplarOrBuilder getExemplarsOrBuilder(
+    public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.ExemplarOrBuilder getExemplarsOrBuilder(
         int index) {
       return exemplars_.get(index);
     }
@@ -5765,10 +5765,10 @@ public boolean equals(final java.lang.Object obj) {
       if (obj == this) {
        return true;
       }
-      if (!(obj instanceof io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Histogram)) {
+      if (!(obj instanceof io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Histogram)) {
         return super.equals(obj);
       }
-      io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Histogram other = (io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Histogram) obj;
+      io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Histogram other = (io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Histogram) obj;
 
       if (hasSampleCount() != other.hasSampleCount()) return false;
       if (hasSampleCount()) {
@@ -5916,44 +5916,44 @@ public int hashCode() {
       return hash;
     }
 
-    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Histogram parseFrom(
+    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Histogram parseFrom(
         java.nio.ByteBuffer data)
         throws com.google.protobuf.InvalidProtocolBufferException {
       return PARSER.parseFrom(data);
     }
-    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Histogram parseFrom(
+    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Histogram parseFrom(
         java.nio.ByteBuffer data,
         com.google.protobuf.ExtensionRegistryLite extensionRegistry)
         throws com.google.protobuf.InvalidProtocolBufferException {
       return PARSER.parseFrom(data, extensionRegistry);
     }
-    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Histogram parseFrom(
+    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Histogram parseFrom(
         com.google.protobuf.ByteString data)
         throws com.google.protobuf.InvalidProtocolBufferException {
       return PARSER.parseFrom(data);
     }
-    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Histogram parseFrom(
+    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Histogram parseFrom(
         com.google.protobuf.ByteString data,
         com.google.protobuf.ExtensionRegistryLite extensionRegistry)
         throws com.google.protobuf.InvalidProtocolBufferException {
       return PARSER.parseFrom(data, extensionRegistry);
     }
-    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Histogram parseFrom(byte[] data)
+    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Histogram parseFrom(byte[] data)
         throws com.google.protobuf.InvalidProtocolBufferException {
       return PARSER.parseFrom(data);
     }
-    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Histogram parseFrom(
+    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Histogram parseFrom(
         byte[] data,
         com.google.protobuf.ExtensionRegistryLite extensionRegistry)
         throws com.google.protobuf.InvalidProtocolBufferException {
       return PARSER.parseFrom(data, extensionRegistry);
     }
-    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Histogram parseFrom(java.io.InputStream input)
+    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Histogram parseFrom(java.io.InputStream input)
         throws java.io.IOException {
       return com.google.protobuf.GeneratedMessage
           .parseWithIOException(PARSER, input);
     }
-    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Histogram parseFrom(
+    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Histogram parseFrom(
         java.io.InputStream input,
         com.google.protobuf.ExtensionRegistryLite extensionRegistry)
         throws java.io.IOException {
@@ -5961,26 +5961,26 @@ public static io.prometheus.metrics.expositionformats.generated.com_google_proto
           .parseWithIOException(PARSER, input, extensionRegistry);
     }
 
-    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Histogram parseDelimitedFrom(java.io.InputStream input)
+    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Histogram parseDelimitedFrom(java.io.InputStream input)
         throws java.io.IOException {
       return com.google.protobuf.GeneratedMessage
           .parseDelimitedWithIOException(PARSER, input);
     }
 
-    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Histogram parseDelimitedFrom(
+    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Histogram parseDelimitedFrom(
         java.io.InputStream input,
         com.google.protobuf.ExtensionRegistryLite extensionRegistry)
         throws java.io.IOException {
       return com.google.protobuf.GeneratedMessage
           .parseDelimitedWithIOException(PARSER, input, extensionRegistry);
     }
-    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Histogram parseFrom(
+    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Histogram parseFrom(
         com.google.protobuf.CodedInputStream input)
         throws java.io.IOException {
       return com.google.protobuf.GeneratedMessage
           .parseWithIOException(PARSER, input);
     }
-    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Histogram parseFrom(
+    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Histogram parseFrom(
         com.google.protobuf.CodedInputStream input,
         com.google.protobuf.ExtensionRegistryLite extensionRegistry)
         throws java.io.IOException {
@@ -5993,7 +5993,7 @@ public static io.prometheus.metrics.expositionformats.generated.com_google_proto
     public static Builder newBuilder() {
       return DEFAULT_INSTANCE.toBuilder();
     }
-    public static Builder newBuilder(io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Histogram prototype) {
+    public static Builder newBuilder(io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Histogram prototype) {
       return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype);
     }
     @java.lang.Override
@@ -6014,21 +6014,21 @@ protected Builder newBuilderForType(
     public static final class Builder extends
         com.google.protobuf.GeneratedMessage.Builder implements
         // @@protoc_insertion_point(builder_implements:io.prometheus.client.Histogram)
-        io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.HistogramOrBuilder {
+        io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.HistogramOrBuilder {
       public static final com.google.protobuf.Descriptors.Descriptor
           getDescriptor() {
-        return io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.internal_static_io_prometheus_client_Histogram_descriptor;
+        return io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.internal_static_io_prometheus_client_Histogram_descriptor;
       }
 
       @java.lang.Override
       protected com.google.protobuf.GeneratedMessage.FieldAccessorTable
           internalGetFieldAccessorTable() {
-        return io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.internal_static_io_prometheus_client_Histogram_fieldAccessorTable
+        return io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.internal_static_io_prometheus_client_Histogram_fieldAccessorTable
             .ensureFieldAccessorsInitialized(
-                io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Histogram.class, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Histogram.Builder.class);
+                io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Histogram.class, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Histogram.Builder.class);
       }
 
-      // Construct using io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Histogram.newBuilder()
+      // Construct using io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Histogram.newBuilder()
       private Builder() {
         maybeForceBuilderInitialization();
       }
@@ -6102,17 +6102,17 @@ public Builder clear() {
       @java.lang.Override
       public com.google.protobuf.Descriptors.Descriptor
           getDescriptorForType() {
-        return io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.internal_static_io_prometheus_client_Histogram_descriptor;
+        return io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.internal_static_io_prometheus_client_Histogram_descriptor;
       }
 
       @java.lang.Override
-      public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Histogram getDefaultInstanceForType() {
-        return io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Histogram.getDefaultInstance();
+      public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Histogram getDefaultInstanceForType() {
+        return io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Histogram.getDefaultInstance();
       }
 
       @java.lang.Override
-      public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Histogram build() {
-        io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Histogram result = buildPartial();
+      public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Histogram build() {
+        io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Histogram result = buildPartial();
         if (!result.isInitialized()) {
           throw newUninitializedMessageException(result);
         }
@@ -6120,15 +6120,15 @@ public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_3
       }
 
       @java.lang.Override
-      public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Histogram buildPartial() {
-        io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Histogram result = new io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Histogram(this);
+      public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Histogram buildPartial() {
+        io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Histogram result = new io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Histogram(this);
         buildPartialRepeatedFields(result);
         if (bitField0_ != 0) { buildPartial0(result); }
         onBuilt();
         return result;
       }
 
-      private void buildPartialRepeatedFields(io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Histogram result) {
+      private void buildPartialRepeatedFields(io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Histogram result) {
         if (bucketBuilder_ == null) {
           if (((bitField0_ & 0x00000008) != 0)) {
             bucket_ = java.util.Collections.unmodifiableList(bucket_);
@@ -6167,7 +6167,7 @@ private void buildPartialRepeatedFields(io.prometheus.metrics.expositionformats.
         }
       }
 
-      private void buildPartial0(io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Histogram result) {
+      private void buildPartial0(io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Histogram result) {
         int from_bitField0_ = bitField0_;
         int to_bitField0_ = 0;
         if (((from_bitField0_ & 0x00000001) != 0)) {
@@ -6225,16 +6225,16 @@ private void buildPartial0(io.prometheus.metrics.expositionformats.generated.com
 
       @java.lang.Override
       public Builder mergeFrom(com.google.protobuf.Message other) {
-        if (other instanceof io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Histogram) {
-          return mergeFrom((io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Histogram)other);
+        if (other instanceof io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Histogram) {
+          return mergeFrom((io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Histogram)other);
         } else {
           super.mergeFrom(other);
           return this;
         }
       }
 
-      public Builder mergeFrom(io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Histogram other) {
-        if (other == io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Histogram.getDefaultInstance()) return this;
+      public Builder mergeFrom(io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Histogram other) {
+        if (other == io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Histogram.getDefaultInstance()) return this;
         if (other.hasSampleCount()) {
           setSampleCount(other.getSampleCount());
         }
@@ -6444,9 +6444,9 @@ public Builder mergeFrom(
                 break;
               } // case 17
               case 26: {
-                io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Bucket m =
+                io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Bucket m =
                     input.readMessage(
-                        io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Bucket.parser(),
+                        io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Bucket.parser(),
                         extensionRegistry);
                 if (bucketBuilder_ == null) {
                   ensureBucketIsMutable();
@@ -6482,9 +6482,9 @@ public Builder mergeFrom(
                 break;
               } // case 65
               case 74: {
-                io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.BucketSpan m =
+                io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.BucketSpan m =
                     input.readMessage(
-                        io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.BucketSpan.parser(),
+                        io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.BucketSpan.parser(),
                         extensionRegistry);
                 if (negativeSpanBuilder_ == null) {
                   ensureNegativeSpanIsMutable();
@@ -6528,9 +6528,9 @@ public Builder mergeFrom(
                 break;
               } // case 90
               case 98: {
-                io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.BucketSpan m =
+                io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.BucketSpan m =
                     input.readMessage(
-                        io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.BucketSpan.parser(),
+                        io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.BucketSpan.parser(),
                         extensionRegistry);
                 if (positiveSpanBuilder_ == null) {
                   ensurePositiveSpanIsMutable();
@@ -6581,9 +6581,9 @@ public Builder mergeFrom(
                 break;
               } // case 122
               case 130: {
-                io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Exemplar m =
+                io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Exemplar m =
                     input.readMessage(
-                        io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Exemplar.parser(),
+                        io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Exemplar.parser(),
                         extensionRegistry);
                 if (exemplarsBuilder_ == null) {
                   ensureExemplarsIsMutable();
@@ -6746,17 +6746,17 @@ public Builder clearSampleSum() {
         return this;
       }
 
-      private java.util.List bucket_ =
+      private java.util.List bucket_ =
         java.util.Collections.emptyList();
       private void ensureBucketIsMutable() {
         if (!((bitField0_ & 0x00000008) != 0)) {
-          bucket_ = new java.util.ArrayList(bucket_);
+          bucket_ = new java.util.ArrayList(bucket_);
           bitField0_ |= 0x00000008;
          }
       }
 
       private com.google.protobuf.RepeatedFieldBuilder<
-          io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Bucket, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Bucket.Builder, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.BucketOrBuilder> bucketBuilder_;
+          io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Bucket, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Bucket.Builder, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.BucketOrBuilder> bucketBuilder_;
 
       /**
        * 
@@ -6765,7 +6765,7 @@ private void ensureBucketIsMutable() {
        *
        * repeated .io.prometheus.client.Bucket bucket = 3;
        */
-      public java.util.List getBucketList() {
+      public java.util.List getBucketList() {
         if (bucketBuilder_ == null) {
           return java.util.Collections.unmodifiableList(bucket_);
         } else {
@@ -6793,7 +6793,7 @@ public int getBucketCount() {
        *
        * repeated .io.prometheus.client.Bucket bucket = 3;
        */
-      public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Bucket getBucket(int index) {
+      public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Bucket getBucket(int index) {
         if (bucketBuilder_ == null) {
           return bucket_.get(index);
         } else {
@@ -6808,7 +6808,7 @@ public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_3
        * repeated .io.prometheus.client.Bucket bucket = 3;
        */
       public Builder setBucket(
-          int index, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Bucket value) {
+          int index, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Bucket value) {
         if (bucketBuilder_ == null) {
           if (value == null) {
             throw new NullPointerException();
@@ -6829,7 +6829,7 @@ public Builder setBucket(
        * repeated .io.prometheus.client.Bucket bucket = 3;
        */
       public Builder setBucket(
-          int index, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Bucket.Builder builderForValue) {
+          int index, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Bucket.Builder builderForValue) {
         if (bucketBuilder_ == null) {
           ensureBucketIsMutable();
           bucket_.set(index, builderForValue.build());
@@ -6846,7 +6846,7 @@ public Builder setBucket(
        *
        * repeated .io.prometheus.client.Bucket bucket = 3;
        */
-      public Builder addBucket(io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Bucket value) {
+      public Builder addBucket(io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Bucket value) {
         if (bucketBuilder_ == null) {
           if (value == null) {
             throw new NullPointerException();
@@ -6867,7 +6867,7 @@ public Builder addBucket(io.prometheus.metrics.expositionformats.generated.com_g
        * repeated .io.prometheus.client.Bucket bucket = 3;
        */
       public Builder addBucket(
-          int index, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Bucket value) {
+          int index, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Bucket value) {
         if (bucketBuilder_ == null) {
           if (value == null) {
             throw new NullPointerException();
@@ -6888,7 +6888,7 @@ public Builder addBucket(
        * repeated .io.prometheus.client.Bucket bucket = 3;
        */
       public Builder addBucket(
-          io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Bucket.Builder builderForValue) {
+          io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Bucket.Builder builderForValue) {
         if (bucketBuilder_ == null) {
           ensureBucketIsMutable();
           bucket_.add(builderForValue.build());
@@ -6906,7 +6906,7 @@ public Builder addBucket(
        * repeated .io.prometheus.client.Bucket bucket = 3;
        */
       public Builder addBucket(
-          int index, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Bucket.Builder builderForValue) {
+          int index, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Bucket.Builder builderForValue) {
         if (bucketBuilder_ == null) {
           ensureBucketIsMutable();
           bucket_.add(index, builderForValue.build());
@@ -6924,7 +6924,7 @@ public Builder addBucket(
        * repeated .io.prometheus.client.Bucket bucket = 3;
        */
       public Builder addAllBucket(
-          java.lang.Iterable values) {
+          java.lang.Iterable values) {
         if (bucketBuilder_ == null) {
           ensureBucketIsMutable();
           com.google.protobuf.AbstractMessageLite.Builder.addAll(
@@ -6976,7 +6976,7 @@ public Builder removeBucket(int index) {
        *
        * repeated .io.prometheus.client.Bucket bucket = 3;
        */
-      public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Bucket.Builder getBucketBuilder(
+      public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Bucket.Builder getBucketBuilder(
           int index) {
         return internalGetBucketFieldBuilder().getBuilder(index);
       }
@@ -6987,7 +6987,7 @@ public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_3
        *
        * repeated .io.prometheus.client.Bucket bucket = 3;
        */
-      public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.BucketOrBuilder getBucketOrBuilder(
+      public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.BucketOrBuilder getBucketOrBuilder(
           int index) {
         if (bucketBuilder_ == null) {
           return bucket_.get(index);  } else {
@@ -7001,7 +7001,7 @@ public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_3
        *
        * repeated .io.prometheus.client.Bucket bucket = 3;
        */
-      public java.util.List 
+      public java.util.List 
            getBucketOrBuilderList() {
         if (bucketBuilder_ != null) {
           return bucketBuilder_.getMessageOrBuilderList();
@@ -7016,9 +7016,9 @@ public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_3
        *
        * repeated .io.prometheus.client.Bucket bucket = 3;
        */
-      public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Bucket.Builder addBucketBuilder() {
+      public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Bucket.Builder addBucketBuilder() {
         return internalGetBucketFieldBuilder().addBuilder(
-            io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Bucket.getDefaultInstance());
+            io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Bucket.getDefaultInstance());
       }
       /**
        * 
@@ -7027,10 +7027,10 @@ public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_3
        *
        * repeated .io.prometheus.client.Bucket bucket = 3;
        */
-      public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Bucket.Builder addBucketBuilder(
+      public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Bucket.Builder addBucketBuilder(
           int index) {
         return internalGetBucketFieldBuilder().addBuilder(
-            index, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Bucket.getDefaultInstance());
+            index, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Bucket.getDefaultInstance());
       }
       /**
        * 
@@ -7039,16 +7039,16 @@ public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_3
        *
        * repeated .io.prometheus.client.Bucket bucket = 3;
        */
-      public java.util.List 
+      public java.util.List 
            getBucketBuilderList() {
         return internalGetBucketFieldBuilder().getBuilderList();
       }
       private com.google.protobuf.RepeatedFieldBuilder<
-          io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Bucket, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Bucket.Builder, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.BucketOrBuilder> 
+          io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Bucket, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Bucket.Builder, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.BucketOrBuilder> 
           internalGetBucketFieldBuilder() {
         if (bucketBuilder_ == null) {
           bucketBuilder_ = new com.google.protobuf.RepeatedFieldBuilder<
-              io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Bucket, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Bucket.Builder, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.BucketOrBuilder>(
+              io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Bucket, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Bucket.Builder, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.BucketOrBuilder>(
                   bucket_,
                   ((bitField0_ & 0x00000008) != 0),
                   getParentForChildren(),
@@ -7419,17 +7419,17 @@ public Builder clearZeroCountFloat() {
         return this;
       }
 
-      private java.util.List negativeSpan_ =
+      private java.util.List negativeSpan_ =
         java.util.Collections.emptyList();
       private void ensureNegativeSpanIsMutable() {
         if (!((bitField0_ & 0x00000200) != 0)) {
-          negativeSpan_ = new java.util.ArrayList(negativeSpan_);
+          negativeSpan_ = new java.util.ArrayList(negativeSpan_);
           bitField0_ |= 0x00000200;
          }
       }
 
       private com.google.protobuf.RepeatedFieldBuilder<
-          io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.BucketSpan, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.BucketSpan.Builder, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.BucketSpanOrBuilder> negativeSpanBuilder_;
+          io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.BucketSpan, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.BucketSpan.Builder, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.BucketSpanOrBuilder> negativeSpanBuilder_;
 
       /**
        * 
@@ -7438,7 +7438,7 @@ private void ensureNegativeSpanIsMutable() {
        *
        * repeated .io.prometheus.client.BucketSpan negative_span = 9;
        */
-      public java.util.List getNegativeSpanList() {
+      public java.util.List getNegativeSpanList() {
         if (negativeSpanBuilder_ == null) {
           return java.util.Collections.unmodifiableList(negativeSpan_);
         } else {
@@ -7466,7 +7466,7 @@ public int getNegativeSpanCount() {
        *
        * repeated .io.prometheus.client.BucketSpan negative_span = 9;
        */
-      public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.BucketSpan getNegativeSpan(int index) {
+      public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.BucketSpan getNegativeSpan(int index) {
         if (negativeSpanBuilder_ == null) {
           return negativeSpan_.get(index);
         } else {
@@ -7481,7 +7481,7 @@ public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_3
        * repeated .io.prometheus.client.BucketSpan negative_span = 9;
        */
       public Builder setNegativeSpan(
-          int index, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.BucketSpan value) {
+          int index, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.BucketSpan value) {
         if (negativeSpanBuilder_ == null) {
           if (value == null) {
             throw new NullPointerException();
@@ -7502,7 +7502,7 @@ public Builder setNegativeSpan(
        * repeated .io.prometheus.client.BucketSpan negative_span = 9;
        */
       public Builder setNegativeSpan(
-          int index, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.BucketSpan.Builder builderForValue) {
+          int index, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.BucketSpan.Builder builderForValue) {
         if (negativeSpanBuilder_ == null) {
           ensureNegativeSpanIsMutable();
           negativeSpan_.set(index, builderForValue.build());
@@ -7519,7 +7519,7 @@ public Builder setNegativeSpan(
        *
        * repeated .io.prometheus.client.BucketSpan negative_span = 9;
        */
-      public Builder addNegativeSpan(io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.BucketSpan value) {
+      public Builder addNegativeSpan(io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.BucketSpan value) {
         if (negativeSpanBuilder_ == null) {
           if (value == null) {
             throw new NullPointerException();
@@ -7540,7 +7540,7 @@ public Builder addNegativeSpan(io.prometheus.metrics.expositionformats.generated
        * repeated .io.prometheus.client.BucketSpan negative_span = 9;
        */
       public Builder addNegativeSpan(
-          int index, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.BucketSpan value) {
+          int index, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.BucketSpan value) {
         if (negativeSpanBuilder_ == null) {
           if (value == null) {
             throw new NullPointerException();
@@ -7561,7 +7561,7 @@ public Builder addNegativeSpan(
        * repeated .io.prometheus.client.BucketSpan negative_span = 9;
        */
       public Builder addNegativeSpan(
-          io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.BucketSpan.Builder builderForValue) {
+          io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.BucketSpan.Builder builderForValue) {
         if (negativeSpanBuilder_ == null) {
           ensureNegativeSpanIsMutable();
           negativeSpan_.add(builderForValue.build());
@@ -7579,7 +7579,7 @@ public Builder addNegativeSpan(
        * repeated .io.prometheus.client.BucketSpan negative_span = 9;
        */
       public Builder addNegativeSpan(
-          int index, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.BucketSpan.Builder builderForValue) {
+          int index, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.BucketSpan.Builder builderForValue) {
         if (negativeSpanBuilder_ == null) {
           ensureNegativeSpanIsMutable();
           negativeSpan_.add(index, builderForValue.build());
@@ -7597,7 +7597,7 @@ public Builder addNegativeSpan(
        * repeated .io.prometheus.client.BucketSpan negative_span = 9;
        */
       public Builder addAllNegativeSpan(
-          java.lang.Iterable values) {
+          java.lang.Iterable values) {
         if (negativeSpanBuilder_ == null) {
           ensureNegativeSpanIsMutable();
           com.google.protobuf.AbstractMessageLite.Builder.addAll(
@@ -7649,7 +7649,7 @@ public Builder removeNegativeSpan(int index) {
        *
        * repeated .io.prometheus.client.BucketSpan negative_span = 9;
        */
-      public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.BucketSpan.Builder getNegativeSpanBuilder(
+      public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.BucketSpan.Builder getNegativeSpanBuilder(
           int index) {
         return internalGetNegativeSpanFieldBuilder().getBuilder(index);
       }
@@ -7660,7 +7660,7 @@ public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_3
        *
        * repeated .io.prometheus.client.BucketSpan negative_span = 9;
        */
-      public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.BucketSpanOrBuilder getNegativeSpanOrBuilder(
+      public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.BucketSpanOrBuilder getNegativeSpanOrBuilder(
           int index) {
         if (negativeSpanBuilder_ == null) {
           return negativeSpan_.get(index);  } else {
@@ -7674,7 +7674,7 @@ public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_3
        *
        * repeated .io.prometheus.client.BucketSpan negative_span = 9;
        */
-      public java.util.List 
+      public java.util.List 
            getNegativeSpanOrBuilderList() {
         if (negativeSpanBuilder_ != null) {
           return negativeSpanBuilder_.getMessageOrBuilderList();
@@ -7689,9 +7689,9 @@ public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_3
        *
        * repeated .io.prometheus.client.BucketSpan negative_span = 9;
        */
-      public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.BucketSpan.Builder addNegativeSpanBuilder() {
+      public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.BucketSpan.Builder addNegativeSpanBuilder() {
         return internalGetNegativeSpanFieldBuilder().addBuilder(
-            io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.BucketSpan.getDefaultInstance());
+            io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.BucketSpan.getDefaultInstance());
       }
       /**
        * 
@@ -7700,10 +7700,10 @@ public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_3
        *
        * repeated .io.prometheus.client.BucketSpan negative_span = 9;
        */
-      public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.BucketSpan.Builder addNegativeSpanBuilder(
+      public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.BucketSpan.Builder addNegativeSpanBuilder(
           int index) {
         return internalGetNegativeSpanFieldBuilder().addBuilder(
-            index, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.BucketSpan.getDefaultInstance());
+            index, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.BucketSpan.getDefaultInstance());
       }
       /**
        * 
@@ -7712,16 +7712,16 @@ public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_3
        *
        * repeated .io.prometheus.client.BucketSpan negative_span = 9;
        */
-      public java.util.List 
+      public java.util.List 
            getNegativeSpanBuilderList() {
         return internalGetNegativeSpanFieldBuilder().getBuilderList();
       }
       private com.google.protobuf.RepeatedFieldBuilder<
-          io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.BucketSpan, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.BucketSpan.Builder, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.BucketSpanOrBuilder> 
+          io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.BucketSpan, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.BucketSpan.Builder, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.BucketSpanOrBuilder> 
           internalGetNegativeSpanFieldBuilder() {
         if (negativeSpanBuilder_ == null) {
           negativeSpanBuilder_ = new com.google.protobuf.RepeatedFieldBuilder<
-              io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.BucketSpan, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.BucketSpan.Builder, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.BucketSpanOrBuilder>(
+              io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.BucketSpan, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.BucketSpan.Builder, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.BucketSpanOrBuilder>(
                   negativeSpan_,
                   ((bitField0_ & 0x00000200) != 0),
                   getParentForChildren(),
@@ -7975,17 +7975,17 @@ public Builder clearNegativeCount() {
         return this;
       }
 
-      private java.util.List positiveSpan_ =
+      private java.util.List positiveSpan_ =
         java.util.Collections.emptyList();
       private void ensurePositiveSpanIsMutable() {
         if (!((bitField0_ & 0x00001000) != 0)) {
-          positiveSpan_ = new java.util.ArrayList(positiveSpan_);
+          positiveSpan_ = new java.util.ArrayList(positiveSpan_);
           bitField0_ |= 0x00001000;
          }
       }
 
       private com.google.protobuf.RepeatedFieldBuilder<
-          io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.BucketSpan, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.BucketSpan.Builder, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.BucketSpanOrBuilder> positiveSpanBuilder_;
+          io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.BucketSpan, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.BucketSpan.Builder, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.BucketSpanOrBuilder> positiveSpanBuilder_;
 
       /**
        * 
@@ -7997,7 +7997,7 @@ private void ensurePositiveSpanIsMutable() {
        *
        * repeated .io.prometheus.client.BucketSpan positive_span = 12;
        */
-      public java.util.List getPositiveSpanList() {
+      public java.util.List getPositiveSpanList() {
         if (positiveSpanBuilder_ == null) {
           return java.util.Collections.unmodifiableList(positiveSpan_);
         } else {
@@ -8031,7 +8031,7 @@ public int getPositiveSpanCount() {
        *
        * repeated .io.prometheus.client.BucketSpan positive_span = 12;
        */
-      public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.BucketSpan getPositiveSpan(int index) {
+      public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.BucketSpan getPositiveSpan(int index) {
         if (positiveSpanBuilder_ == null) {
           return positiveSpan_.get(index);
         } else {
@@ -8049,7 +8049,7 @@ public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_3
        * repeated .io.prometheus.client.BucketSpan positive_span = 12;
        */
       public Builder setPositiveSpan(
-          int index, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.BucketSpan value) {
+          int index, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.BucketSpan value) {
         if (positiveSpanBuilder_ == null) {
           if (value == null) {
             throw new NullPointerException();
@@ -8073,7 +8073,7 @@ public Builder setPositiveSpan(
        * repeated .io.prometheus.client.BucketSpan positive_span = 12;
        */
       public Builder setPositiveSpan(
-          int index, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.BucketSpan.Builder builderForValue) {
+          int index, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.BucketSpan.Builder builderForValue) {
         if (positiveSpanBuilder_ == null) {
           ensurePositiveSpanIsMutable();
           positiveSpan_.set(index, builderForValue.build());
@@ -8093,7 +8093,7 @@ public Builder setPositiveSpan(
        *
        * repeated .io.prometheus.client.BucketSpan positive_span = 12;
        */
-      public Builder addPositiveSpan(io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.BucketSpan value) {
+      public Builder addPositiveSpan(io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.BucketSpan value) {
         if (positiveSpanBuilder_ == null) {
           if (value == null) {
             throw new NullPointerException();
@@ -8117,7 +8117,7 @@ public Builder addPositiveSpan(io.prometheus.metrics.expositionformats.generated
        * repeated .io.prometheus.client.BucketSpan positive_span = 12;
        */
       public Builder addPositiveSpan(
-          int index, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.BucketSpan value) {
+          int index, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.BucketSpan value) {
         if (positiveSpanBuilder_ == null) {
           if (value == null) {
             throw new NullPointerException();
@@ -8141,7 +8141,7 @@ public Builder addPositiveSpan(
        * repeated .io.prometheus.client.BucketSpan positive_span = 12;
        */
       public Builder addPositiveSpan(
-          io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.BucketSpan.Builder builderForValue) {
+          io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.BucketSpan.Builder builderForValue) {
         if (positiveSpanBuilder_ == null) {
           ensurePositiveSpanIsMutable();
           positiveSpan_.add(builderForValue.build());
@@ -8162,7 +8162,7 @@ public Builder addPositiveSpan(
        * repeated .io.prometheus.client.BucketSpan positive_span = 12;
        */
       public Builder addPositiveSpan(
-          int index, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.BucketSpan.Builder builderForValue) {
+          int index, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.BucketSpan.Builder builderForValue) {
         if (positiveSpanBuilder_ == null) {
           ensurePositiveSpanIsMutable();
           positiveSpan_.add(index, builderForValue.build());
@@ -8183,7 +8183,7 @@ public Builder addPositiveSpan(
        * repeated .io.prometheus.client.BucketSpan positive_span = 12;
        */
       public Builder addAllPositiveSpan(
-          java.lang.Iterable values) {
+          java.lang.Iterable values) {
         if (positiveSpanBuilder_ == null) {
           ensurePositiveSpanIsMutable();
           com.google.protobuf.AbstractMessageLite.Builder.addAll(
@@ -8244,7 +8244,7 @@ public Builder removePositiveSpan(int index) {
        *
        * repeated .io.prometheus.client.BucketSpan positive_span = 12;
        */
-      public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.BucketSpan.Builder getPositiveSpanBuilder(
+      public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.BucketSpan.Builder getPositiveSpanBuilder(
           int index) {
         return internalGetPositiveSpanFieldBuilder().getBuilder(index);
       }
@@ -8258,7 +8258,7 @@ public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_3
        *
        * repeated .io.prometheus.client.BucketSpan positive_span = 12;
        */
-      public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.BucketSpanOrBuilder getPositiveSpanOrBuilder(
+      public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.BucketSpanOrBuilder getPositiveSpanOrBuilder(
           int index) {
         if (positiveSpanBuilder_ == null) {
           return positiveSpan_.get(index);  } else {
@@ -8275,7 +8275,7 @@ public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_3
        *
        * repeated .io.prometheus.client.BucketSpan positive_span = 12;
        */
-      public java.util.List 
+      public java.util.List 
            getPositiveSpanOrBuilderList() {
         if (positiveSpanBuilder_ != null) {
           return positiveSpanBuilder_.getMessageOrBuilderList();
@@ -8293,9 +8293,9 @@ public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_3
        *
        * repeated .io.prometheus.client.BucketSpan positive_span = 12;
        */
-      public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.BucketSpan.Builder addPositiveSpanBuilder() {
+      public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.BucketSpan.Builder addPositiveSpanBuilder() {
         return internalGetPositiveSpanFieldBuilder().addBuilder(
-            io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.BucketSpan.getDefaultInstance());
+            io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.BucketSpan.getDefaultInstance());
       }
       /**
        * 
@@ -8307,10 +8307,10 @@ public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_3
        *
        * repeated .io.prometheus.client.BucketSpan positive_span = 12;
        */
-      public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.BucketSpan.Builder addPositiveSpanBuilder(
+      public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.BucketSpan.Builder addPositiveSpanBuilder(
           int index) {
         return internalGetPositiveSpanFieldBuilder().addBuilder(
-            index, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.BucketSpan.getDefaultInstance());
+            index, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.BucketSpan.getDefaultInstance());
       }
       /**
        * 
@@ -8322,16 +8322,16 @@ public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_3
        *
        * repeated .io.prometheus.client.BucketSpan positive_span = 12;
        */
-      public java.util.List 
+      public java.util.List 
            getPositiveSpanBuilderList() {
         return internalGetPositiveSpanFieldBuilder().getBuilderList();
       }
       private com.google.protobuf.RepeatedFieldBuilder<
-          io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.BucketSpan, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.BucketSpan.Builder, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.BucketSpanOrBuilder> 
+          io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.BucketSpan, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.BucketSpan.Builder, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.BucketSpanOrBuilder> 
           internalGetPositiveSpanFieldBuilder() {
         if (positiveSpanBuilder_ == null) {
           positiveSpanBuilder_ = new com.google.protobuf.RepeatedFieldBuilder<
-              io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.BucketSpan, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.BucketSpan.Builder, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.BucketSpanOrBuilder>(
+              io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.BucketSpan, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.BucketSpan.Builder, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.BucketSpanOrBuilder>(
                   positiveSpan_,
                   ((bitField0_ & 0x00001000) != 0),
                   getParentForChildren(),
@@ -8585,17 +8585,17 @@ public Builder clearPositiveCount() {
         return this;
       }
 
-      private java.util.List exemplars_ =
+      private java.util.List exemplars_ =
         java.util.Collections.emptyList();
       private void ensureExemplarsIsMutable() {
         if (!((bitField0_ & 0x00008000) != 0)) {
-          exemplars_ = new java.util.ArrayList(exemplars_);
+          exemplars_ = new java.util.ArrayList(exemplars_);
           bitField0_ |= 0x00008000;
          }
       }
 
       private com.google.protobuf.RepeatedFieldBuilder<
-          io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Exemplar, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Exemplar.Builder, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.ExemplarOrBuilder> exemplarsBuilder_;
+          io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Exemplar, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Exemplar.Builder, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.ExemplarOrBuilder> exemplarsBuilder_;
 
       /**
        * 
@@ -8604,7 +8604,7 @@ private void ensureExemplarsIsMutable() {
        *
        * repeated .io.prometheus.client.Exemplar exemplars = 16;
        */
-      public java.util.List getExemplarsList() {
+      public java.util.List getExemplarsList() {
         if (exemplarsBuilder_ == null) {
           return java.util.Collections.unmodifiableList(exemplars_);
         } else {
@@ -8632,7 +8632,7 @@ public int getExemplarsCount() {
        *
        * repeated .io.prometheus.client.Exemplar exemplars = 16;
        */
-      public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Exemplar getExemplars(int index) {
+      public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Exemplar getExemplars(int index) {
         if (exemplarsBuilder_ == null) {
           return exemplars_.get(index);
         } else {
@@ -8647,7 +8647,7 @@ public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_3
        * repeated .io.prometheus.client.Exemplar exemplars = 16;
        */
       public Builder setExemplars(
-          int index, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Exemplar value) {
+          int index, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Exemplar value) {
         if (exemplarsBuilder_ == null) {
           if (value == null) {
             throw new NullPointerException();
@@ -8668,7 +8668,7 @@ public Builder setExemplars(
        * repeated .io.prometheus.client.Exemplar exemplars = 16;
        */
       public Builder setExemplars(
-          int index, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Exemplar.Builder builderForValue) {
+          int index, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Exemplar.Builder builderForValue) {
         if (exemplarsBuilder_ == null) {
           ensureExemplarsIsMutable();
           exemplars_.set(index, builderForValue.build());
@@ -8685,7 +8685,7 @@ public Builder setExemplars(
        *
        * repeated .io.prometheus.client.Exemplar exemplars = 16;
        */
-      public Builder addExemplars(io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Exemplar value) {
+      public Builder addExemplars(io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Exemplar value) {
         if (exemplarsBuilder_ == null) {
           if (value == null) {
             throw new NullPointerException();
@@ -8706,7 +8706,7 @@ public Builder addExemplars(io.prometheus.metrics.expositionformats.generated.co
        * repeated .io.prometheus.client.Exemplar exemplars = 16;
        */
       public Builder addExemplars(
-          int index, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Exemplar value) {
+          int index, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Exemplar value) {
         if (exemplarsBuilder_ == null) {
           if (value == null) {
             throw new NullPointerException();
@@ -8727,7 +8727,7 @@ public Builder addExemplars(
        * repeated .io.prometheus.client.Exemplar exemplars = 16;
        */
       public Builder addExemplars(
-          io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Exemplar.Builder builderForValue) {
+          io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Exemplar.Builder builderForValue) {
         if (exemplarsBuilder_ == null) {
           ensureExemplarsIsMutable();
           exemplars_.add(builderForValue.build());
@@ -8745,7 +8745,7 @@ public Builder addExemplars(
        * repeated .io.prometheus.client.Exemplar exemplars = 16;
        */
       public Builder addExemplars(
-          int index, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Exemplar.Builder builderForValue) {
+          int index, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Exemplar.Builder builderForValue) {
         if (exemplarsBuilder_ == null) {
           ensureExemplarsIsMutable();
           exemplars_.add(index, builderForValue.build());
@@ -8763,7 +8763,7 @@ public Builder addExemplars(
        * repeated .io.prometheus.client.Exemplar exemplars = 16;
        */
       public Builder addAllExemplars(
-          java.lang.Iterable values) {
+          java.lang.Iterable values) {
         if (exemplarsBuilder_ == null) {
           ensureExemplarsIsMutable();
           com.google.protobuf.AbstractMessageLite.Builder.addAll(
@@ -8815,7 +8815,7 @@ public Builder removeExemplars(int index) {
        *
        * repeated .io.prometheus.client.Exemplar exemplars = 16;
        */
-      public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Exemplar.Builder getExemplarsBuilder(
+      public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Exemplar.Builder getExemplarsBuilder(
           int index) {
         return internalGetExemplarsFieldBuilder().getBuilder(index);
       }
@@ -8826,7 +8826,7 @@ public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_3
        *
        * repeated .io.prometheus.client.Exemplar exemplars = 16;
        */
-      public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.ExemplarOrBuilder getExemplarsOrBuilder(
+      public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.ExemplarOrBuilder getExemplarsOrBuilder(
           int index) {
         if (exemplarsBuilder_ == null) {
           return exemplars_.get(index);  } else {
@@ -8840,7 +8840,7 @@ public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_3
        *
        * repeated .io.prometheus.client.Exemplar exemplars = 16;
        */
-      public java.util.List 
+      public java.util.List 
            getExemplarsOrBuilderList() {
         if (exemplarsBuilder_ != null) {
           return exemplarsBuilder_.getMessageOrBuilderList();
@@ -8855,9 +8855,9 @@ public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_3
        *
        * repeated .io.prometheus.client.Exemplar exemplars = 16;
        */
-      public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Exemplar.Builder addExemplarsBuilder() {
+      public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Exemplar.Builder addExemplarsBuilder() {
         return internalGetExemplarsFieldBuilder().addBuilder(
-            io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Exemplar.getDefaultInstance());
+            io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Exemplar.getDefaultInstance());
       }
       /**
        * 
@@ -8866,10 +8866,10 @@ public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_3
        *
        * repeated .io.prometheus.client.Exemplar exemplars = 16;
        */
-      public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Exemplar.Builder addExemplarsBuilder(
+      public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Exemplar.Builder addExemplarsBuilder(
           int index) {
         return internalGetExemplarsFieldBuilder().addBuilder(
-            index, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Exemplar.getDefaultInstance());
+            index, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Exemplar.getDefaultInstance());
       }
       /**
        * 
@@ -8878,16 +8878,16 @@ public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_3
        *
        * repeated .io.prometheus.client.Exemplar exemplars = 16;
        */
-      public java.util.List 
+      public java.util.List 
            getExemplarsBuilderList() {
         return internalGetExemplarsFieldBuilder().getBuilderList();
       }
       private com.google.protobuf.RepeatedFieldBuilder<
-          io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Exemplar, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Exemplar.Builder, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.ExemplarOrBuilder> 
+          io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Exemplar, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Exemplar.Builder, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.ExemplarOrBuilder> 
           internalGetExemplarsFieldBuilder() {
         if (exemplarsBuilder_ == null) {
           exemplarsBuilder_ = new com.google.protobuf.RepeatedFieldBuilder<
-              io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Exemplar, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Exemplar.Builder, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.ExemplarOrBuilder>(
+              io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Exemplar, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Exemplar.Builder, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.ExemplarOrBuilder>(
                   exemplars_,
                   ((bitField0_ & 0x00008000) != 0),
                   getParentForChildren(),
@@ -8901,12 +8901,12 @@ public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_3
     }
 
     // @@protoc_insertion_point(class_scope:io.prometheus.client.Histogram)
-    private static final io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Histogram DEFAULT_INSTANCE;
+    private static final io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Histogram DEFAULT_INSTANCE;
     static {
-      DEFAULT_INSTANCE = new io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Histogram();
+      DEFAULT_INSTANCE = new io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Histogram();
     }
 
-    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Histogram getDefaultInstance() {
+    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Histogram getDefaultInstance() {
       return DEFAULT_INSTANCE;
     }
 
@@ -8942,7 +8942,7 @@ public com.google.protobuf.Parser getParserForType() {
     }
 
     @java.lang.Override
-    public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Histogram getDefaultInstanceForType() {
+    public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Histogram getDefaultInstanceForType() {
       return DEFAULT_INSTANCE;
     }
 
@@ -9018,11 +9018,11 @@ public interface BucketOrBuilder extends
      * optional .io.prometheus.client.Exemplar exemplar = 3;
      * @return The exemplar.
      */
-    io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Exemplar getExemplar();
+    io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Exemplar getExemplar();
     /**
      * optional .io.prometheus.client.Exemplar exemplar = 3;
      */
-    io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.ExemplarOrBuilder getExemplarOrBuilder();
+    io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.ExemplarOrBuilder getExemplarOrBuilder();
   }
   /**
    * 
@@ -9041,10 +9041,10 @@ public static final class Bucket extends
       com.google.protobuf.RuntimeVersion.validateProtobufGencodeVersion(
         com.google.protobuf.RuntimeVersion.RuntimeDomain.PUBLIC,
         /* major= */ 4,
-        /* minor= */ 31,
-        /* patch= */ 1,
+        /* minor= */ 33,
+        /* patch= */ 5,
         /* suffix= */ "",
-        Bucket.class.getName());
+        "Bucket");
     }
     // Use Bucket.newBuilder() to construct.
     private Bucket(com.google.protobuf.GeneratedMessage.Builder builder) {
@@ -9055,15 +9055,15 @@ private Bucket() {
 
     public static final com.google.protobuf.Descriptors.Descriptor
         getDescriptor() {
-      return io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.internal_static_io_prometheus_client_Bucket_descriptor;
+      return io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.internal_static_io_prometheus_client_Bucket_descriptor;
     }
 
     @java.lang.Override
     protected com.google.protobuf.GeneratedMessage.FieldAccessorTable
         internalGetFieldAccessorTable() {
-      return io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.internal_static_io_prometheus_client_Bucket_fieldAccessorTable
+      return io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.internal_static_io_prometheus_client_Bucket_fieldAccessorTable
           .ensureFieldAccessorsInitialized(
-              io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Bucket.class, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Bucket.Builder.class);
+              io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Bucket.class, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Bucket.Builder.class);
     }
 
     private int bitField0_;
@@ -9149,7 +9149,7 @@ public double getUpperBound() {
     }
 
     public static final int EXEMPLAR_FIELD_NUMBER = 3;
-    private io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Exemplar exemplar_;
+    private io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Exemplar exemplar_;
     /**
      * optional .io.prometheus.client.Exemplar exemplar = 3;
      * @return Whether the exemplar field is set.
@@ -9163,15 +9163,15 @@ public boolean hasExemplar() {
      * @return The exemplar.
      */
     @java.lang.Override
-    public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Exemplar getExemplar() {
-      return exemplar_ == null ? io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Exemplar.getDefaultInstance() : exemplar_;
+    public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Exemplar getExemplar() {
+      return exemplar_ == null ? io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Exemplar.getDefaultInstance() : exemplar_;
     }
     /**
      * optional .io.prometheus.client.Exemplar exemplar = 3;
      */
     @java.lang.Override
-    public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.ExemplarOrBuilder getExemplarOrBuilder() {
-      return exemplar_ == null ? io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Exemplar.getDefaultInstance() : exemplar_;
+    public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.ExemplarOrBuilder getExemplarOrBuilder() {
+      return exemplar_ == null ? io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Exemplar.getDefaultInstance() : exemplar_;
     }
 
     private byte memoizedIsInitialized = -1;
@@ -9235,10 +9235,10 @@ public boolean equals(final java.lang.Object obj) {
       if (obj == this) {
        return true;
       }
-      if (!(obj instanceof io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Bucket)) {
+      if (!(obj instanceof io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Bucket)) {
         return super.equals(obj);
       }
-      io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Bucket other = (io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Bucket) obj;
+      io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Bucket other = (io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Bucket) obj;
 
       if (hasCumulativeCount() != other.hasCumulativeCount()) return false;
       if (hasCumulativeCount()) {
@@ -9297,44 +9297,44 @@ public int hashCode() {
       return hash;
     }
 
-    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Bucket parseFrom(
+    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Bucket parseFrom(
         java.nio.ByteBuffer data)
         throws com.google.protobuf.InvalidProtocolBufferException {
       return PARSER.parseFrom(data);
     }
-    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Bucket parseFrom(
+    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Bucket parseFrom(
         java.nio.ByteBuffer data,
         com.google.protobuf.ExtensionRegistryLite extensionRegistry)
         throws com.google.protobuf.InvalidProtocolBufferException {
       return PARSER.parseFrom(data, extensionRegistry);
     }
-    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Bucket parseFrom(
+    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Bucket parseFrom(
         com.google.protobuf.ByteString data)
         throws com.google.protobuf.InvalidProtocolBufferException {
       return PARSER.parseFrom(data);
     }
-    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Bucket parseFrom(
+    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Bucket parseFrom(
         com.google.protobuf.ByteString data,
         com.google.protobuf.ExtensionRegistryLite extensionRegistry)
         throws com.google.protobuf.InvalidProtocolBufferException {
       return PARSER.parseFrom(data, extensionRegistry);
     }
-    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Bucket parseFrom(byte[] data)
+    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Bucket parseFrom(byte[] data)
         throws com.google.protobuf.InvalidProtocolBufferException {
       return PARSER.parseFrom(data);
     }
-    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Bucket parseFrom(
+    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Bucket parseFrom(
         byte[] data,
         com.google.protobuf.ExtensionRegistryLite extensionRegistry)
         throws com.google.protobuf.InvalidProtocolBufferException {
       return PARSER.parseFrom(data, extensionRegistry);
     }
-    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Bucket parseFrom(java.io.InputStream input)
+    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Bucket parseFrom(java.io.InputStream input)
         throws java.io.IOException {
       return com.google.protobuf.GeneratedMessage
           .parseWithIOException(PARSER, input);
     }
-    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Bucket parseFrom(
+    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Bucket parseFrom(
         java.io.InputStream input,
         com.google.protobuf.ExtensionRegistryLite extensionRegistry)
         throws java.io.IOException {
@@ -9342,26 +9342,26 @@ public static io.prometheus.metrics.expositionformats.generated.com_google_proto
           .parseWithIOException(PARSER, input, extensionRegistry);
     }
 
-    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Bucket parseDelimitedFrom(java.io.InputStream input)
+    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Bucket parseDelimitedFrom(java.io.InputStream input)
         throws java.io.IOException {
       return com.google.protobuf.GeneratedMessage
           .parseDelimitedWithIOException(PARSER, input);
     }
 
-    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Bucket parseDelimitedFrom(
+    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Bucket parseDelimitedFrom(
         java.io.InputStream input,
         com.google.protobuf.ExtensionRegistryLite extensionRegistry)
         throws java.io.IOException {
       return com.google.protobuf.GeneratedMessage
           .parseDelimitedWithIOException(PARSER, input, extensionRegistry);
     }
-    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Bucket parseFrom(
+    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Bucket parseFrom(
         com.google.protobuf.CodedInputStream input)
         throws java.io.IOException {
       return com.google.protobuf.GeneratedMessage
           .parseWithIOException(PARSER, input);
     }
-    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Bucket parseFrom(
+    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Bucket parseFrom(
         com.google.protobuf.CodedInputStream input,
         com.google.protobuf.ExtensionRegistryLite extensionRegistry)
         throws java.io.IOException {
@@ -9374,7 +9374,7 @@ public static io.prometheus.metrics.expositionformats.generated.com_google_proto
     public static Builder newBuilder() {
       return DEFAULT_INSTANCE.toBuilder();
     }
-    public static Builder newBuilder(io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Bucket prototype) {
+    public static Builder newBuilder(io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Bucket prototype) {
       return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype);
     }
     @java.lang.Override
@@ -9400,21 +9400,21 @@ protected Builder newBuilderForType(
     public static final class Builder extends
         com.google.protobuf.GeneratedMessage.Builder implements
         // @@protoc_insertion_point(builder_implements:io.prometheus.client.Bucket)
-        io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.BucketOrBuilder {
+        io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.BucketOrBuilder {
       public static final com.google.protobuf.Descriptors.Descriptor
           getDescriptor() {
-        return io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.internal_static_io_prometheus_client_Bucket_descriptor;
+        return io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.internal_static_io_prometheus_client_Bucket_descriptor;
       }
 
       @java.lang.Override
       protected com.google.protobuf.GeneratedMessage.FieldAccessorTable
           internalGetFieldAccessorTable() {
-        return io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.internal_static_io_prometheus_client_Bucket_fieldAccessorTable
+        return io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.internal_static_io_prometheus_client_Bucket_fieldAccessorTable
             .ensureFieldAccessorsInitialized(
-                io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Bucket.class, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Bucket.Builder.class);
+                io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Bucket.class, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Bucket.Builder.class);
       }
 
-      // Construct using io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Bucket.newBuilder()
+      // Construct using io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Bucket.newBuilder()
       private Builder() {
         maybeForceBuilderInitialization();
       }
@@ -9448,17 +9448,17 @@ public Builder clear() {
       @java.lang.Override
       public com.google.protobuf.Descriptors.Descriptor
           getDescriptorForType() {
-        return io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.internal_static_io_prometheus_client_Bucket_descriptor;
+        return io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.internal_static_io_prometheus_client_Bucket_descriptor;
       }
 
       @java.lang.Override
-      public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Bucket getDefaultInstanceForType() {
-        return io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Bucket.getDefaultInstance();
+      public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Bucket getDefaultInstanceForType() {
+        return io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Bucket.getDefaultInstance();
       }
 
       @java.lang.Override
-      public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Bucket build() {
-        io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Bucket result = buildPartial();
+      public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Bucket build() {
+        io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Bucket result = buildPartial();
         if (!result.isInitialized()) {
           throw newUninitializedMessageException(result);
         }
@@ -9466,14 +9466,14 @@ public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_3
       }
 
       @java.lang.Override
-      public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Bucket buildPartial() {
-        io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Bucket result = new io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Bucket(this);
+      public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Bucket buildPartial() {
+        io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Bucket result = new io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Bucket(this);
         if (bitField0_ != 0) { buildPartial0(result); }
         onBuilt();
         return result;
       }
 
-      private void buildPartial0(io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Bucket result) {
+      private void buildPartial0(io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Bucket result) {
         int from_bitField0_ = bitField0_;
         int to_bitField0_ = 0;
         if (((from_bitField0_ & 0x00000001) != 0)) {
@@ -9499,16 +9499,16 @@ private void buildPartial0(io.prometheus.metrics.expositionformats.generated.com
 
       @java.lang.Override
       public Builder mergeFrom(com.google.protobuf.Message other) {
-        if (other instanceof io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Bucket) {
-          return mergeFrom((io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Bucket)other);
+        if (other instanceof io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Bucket) {
+          return mergeFrom((io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Bucket)other);
         } else {
           super.mergeFrom(other);
           return this;
         }
       }
 
-      public Builder mergeFrom(io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Bucket other) {
-        if (other == io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Bucket.getDefaultInstance()) return this;
+      public Builder mergeFrom(io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Bucket other) {
+        if (other == io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Bucket.getDefaultInstance()) return this;
         if (other.hasCumulativeCount()) {
           setCumulativeCount(other.getCumulativeCount());
         }
@@ -9754,9 +9754,9 @@ public Builder clearUpperBound() {
         return this;
       }
 
-      private io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Exemplar exemplar_;
+      private io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Exemplar exemplar_;
       private com.google.protobuf.SingleFieldBuilder<
-          io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Exemplar, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Exemplar.Builder, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.ExemplarOrBuilder> exemplarBuilder_;
+          io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Exemplar, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Exemplar.Builder, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.ExemplarOrBuilder> exemplarBuilder_;
       /**
        * optional .io.prometheus.client.Exemplar exemplar = 3;
        * @return Whether the exemplar field is set.
@@ -9768,9 +9768,9 @@ public boolean hasExemplar() {
        * optional .io.prometheus.client.Exemplar exemplar = 3;
        * @return The exemplar.
        */
-      public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Exemplar getExemplar() {
+      public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Exemplar getExemplar() {
         if (exemplarBuilder_ == null) {
-          return exemplar_ == null ? io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Exemplar.getDefaultInstance() : exemplar_;
+          return exemplar_ == null ? io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Exemplar.getDefaultInstance() : exemplar_;
         } else {
           return exemplarBuilder_.getMessage();
         }
@@ -9778,7 +9778,7 @@ public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_3
       /**
        * optional .io.prometheus.client.Exemplar exemplar = 3;
        */
-      public Builder setExemplar(io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Exemplar value) {
+      public Builder setExemplar(io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Exemplar value) {
         if (exemplarBuilder_ == null) {
           if (value == null) {
             throw new NullPointerException();
@@ -9795,7 +9795,7 @@ public Builder setExemplar(io.prometheus.metrics.expositionformats.generated.com
        * optional .io.prometheus.client.Exemplar exemplar = 3;
        */
       public Builder setExemplar(
-          io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Exemplar.Builder builderForValue) {
+          io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Exemplar.Builder builderForValue) {
         if (exemplarBuilder_ == null) {
           exemplar_ = builderForValue.build();
         } else {
@@ -9808,11 +9808,11 @@ public Builder setExemplar(
       /**
        * optional .io.prometheus.client.Exemplar exemplar = 3;
        */
-      public Builder mergeExemplar(io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Exemplar value) {
+      public Builder mergeExemplar(io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Exemplar value) {
         if (exemplarBuilder_ == null) {
           if (((bitField0_ & 0x00000008) != 0) &&
             exemplar_ != null &&
-            exemplar_ != io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Exemplar.getDefaultInstance()) {
+            exemplar_ != io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Exemplar.getDefaultInstance()) {
             getExemplarBuilder().mergeFrom(value);
           } else {
             exemplar_ = value;
@@ -9842,7 +9842,7 @@ public Builder clearExemplar() {
       /**
        * optional .io.prometheus.client.Exemplar exemplar = 3;
        */
-      public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Exemplar.Builder getExemplarBuilder() {
+      public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Exemplar.Builder getExemplarBuilder() {
         bitField0_ |= 0x00000008;
         onChanged();
         return internalGetExemplarFieldBuilder().getBuilder();
@@ -9850,23 +9850,23 @@ public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_3
       /**
        * optional .io.prometheus.client.Exemplar exemplar = 3;
        */
-      public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.ExemplarOrBuilder getExemplarOrBuilder() {
+      public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.ExemplarOrBuilder getExemplarOrBuilder() {
         if (exemplarBuilder_ != null) {
           return exemplarBuilder_.getMessageOrBuilder();
         } else {
           return exemplar_ == null ?
-              io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Exemplar.getDefaultInstance() : exemplar_;
+              io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Exemplar.getDefaultInstance() : exemplar_;
         }
       }
       /**
        * optional .io.prometheus.client.Exemplar exemplar = 3;
        */
       private com.google.protobuf.SingleFieldBuilder<
-          io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Exemplar, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Exemplar.Builder, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.ExemplarOrBuilder> 
+          io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Exemplar, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Exemplar.Builder, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.ExemplarOrBuilder> 
           internalGetExemplarFieldBuilder() {
         if (exemplarBuilder_ == null) {
           exemplarBuilder_ = new com.google.protobuf.SingleFieldBuilder<
-              io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Exemplar, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Exemplar.Builder, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.ExemplarOrBuilder>(
+              io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Exemplar, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Exemplar.Builder, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.ExemplarOrBuilder>(
                   getExemplar(),
                   getParentForChildren(),
                   isClean());
@@ -9879,12 +9879,12 @@ public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_3
     }
 
     // @@protoc_insertion_point(class_scope:io.prometheus.client.Bucket)
-    private static final io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Bucket DEFAULT_INSTANCE;
+    private static final io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Bucket DEFAULT_INSTANCE;
     static {
-      DEFAULT_INSTANCE = new io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Bucket();
+      DEFAULT_INSTANCE = new io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Bucket();
     }
 
-    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Bucket getDefaultInstance() {
+    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Bucket getDefaultInstance() {
       return DEFAULT_INSTANCE;
     }
 
@@ -9920,7 +9920,7 @@ public com.google.protobuf.Parser getParserForType() {
     }
 
     @java.lang.Override
-    public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Bucket getDefaultInstanceForType() {
+    public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Bucket getDefaultInstanceForType() {
       return DEFAULT_INSTANCE;
     }
 
@@ -9989,10 +9989,10 @@ public static final class BucketSpan extends
       com.google.protobuf.RuntimeVersion.validateProtobufGencodeVersion(
         com.google.protobuf.RuntimeVersion.RuntimeDomain.PUBLIC,
         /* major= */ 4,
-        /* minor= */ 31,
-        /* patch= */ 1,
+        /* minor= */ 33,
+        /* patch= */ 5,
         /* suffix= */ "",
-        BucketSpan.class.getName());
+        "BucketSpan");
     }
     // Use BucketSpan.newBuilder() to construct.
     private BucketSpan(com.google.protobuf.GeneratedMessage.Builder builder) {
@@ -10003,15 +10003,15 @@ private BucketSpan() {
 
     public static final com.google.protobuf.Descriptors.Descriptor
         getDescriptor() {
-      return io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.internal_static_io_prometheus_client_BucketSpan_descriptor;
+      return io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.internal_static_io_prometheus_client_BucketSpan_descriptor;
     }
 
     @java.lang.Override
     protected com.google.protobuf.GeneratedMessage.FieldAccessorTable
         internalGetFieldAccessorTable() {
-      return io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.internal_static_io_prometheus_client_BucketSpan_fieldAccessorTable
+      return io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.internal_static_io_prometheus_client_BucketSpan_fieldAccessorTable
           .ensureFieldAccessorsInitialized(
-              io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.BucketSpan.class, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.BucketSpan.Builder.class);
+              io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.BucketSpan.class, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.BucketSpan.Builder.class);
     }
 
     private int bitField0_;
@@ -10116,10 +10116,10 @@ public boolean equals(final java.lang.Object obj) {
       if (obj == this) {
        return true;
       }
-      if (!(obj instanceof io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.BucketSpan)) {
+      if (!(obj instanceof io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.BucketSpan)) {
         return super.equals(obj);
       }
-      io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.BucketSpan other = (io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.BucketSpan) obj;
+      io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.BucketSpan other = (io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.BucketSpan) obj;
 
       if (hasOffset() != other.hasOffset()) return false;
       if (hasOffset()) {
@@ -10155,44 +10155,44 @@ public int hashCode() {
       return hash;
     }
 
-    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.BucketSpan parseFrom(
+    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.BucketSpan parseFrom(
         java.nio.ByteBuffer data)
         throws com.google.protobuf.InvalidProtocolBufferException {
       return PARSER.parseFrom(data);
     }
-    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.BucketSpan parseFrom(
+    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.BucketSpan parseFrom(
         java.nio.ByteBuffer data,
         com.google.protobuf.ExtensionRegistryLite extensionRegistry)
         throws com.google.protobuf.InvalidProtocolBufferException {
       return PARSER.parseFrom(data, extensionRegistry);
     }
-    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.BucketSpan parseFrom(
+    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.BucketSpan parseFrom(
         com.google.protobuf.ByteString data)
         throws com.google.protobuf.InvalidProtocolBufferException {
       return PARSER.parseFrom(data);
     }
-    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.BucketSpan parseFrom(
+    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.BucketSpan parseFrom(
         com.google.protobuf.ByteString data,
         com.google.protobuf.ExtensionRegistryLite extensionRegistry)
         throws com.google.protobuf.InvalidProtocolBufferException {
       return PARSER.parseFrom(data, extensionRegistry);
     }
-    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.BucketSpan parseFrom(byte[] data)
+    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.BucketSpan parseFrom(byte[] data)
         throws com.google.protobuf.InvalidProtocolBufferException {
       return PARSER.parseFrom(data);
     }
-    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.BucketSpan parseFrom(
+    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.BucketSpan parseFrom(
         byte[] data,
         com.google.protobuf.ExtensionRegistryLite extensionRegistry)
         throws com.google.protobuf.InvalidProtocolBufferException {
       return PARSER.parseFrom(data, extensionRegistry);
     }
-    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.BucketSpan parseFrom(java.io.InputStream input)
+    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.BucketSpan parseFrom(java.io.InputStream input)
         throws java.io.IOException {
       return com.google.protobuf.GeneratedMessage
           .parseWithIOException(PARSER, input);
     }
-    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.BucketSpan parseFrom(
+    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.BucketSpan parseFrom(
         java.io.InputStream input,
         com.google.protobuf.ExtensionRegistryLite extensionRegistry)
         throws java.io.IOException {
@@ -10200,26 +10200,26 @@ public static io.prometheus.metrics.expositionformats.generated.com_google_proto
           .parseWithIOException(PARSER, input, extensionRegistry);
     }
 
-    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.BucketSpan parseDelimitedFrom(java.io.InputStream input)
+    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.BucketSpan parseDelimitedFrom(java.io.InputStream input)
         throws java.io.IOException {
       return com.google.protobuf.GeneratedMessage
           .parseDelimitedWithIOException(PARSER, input);
     }
 
-    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.BucketSpan parseDelimitedFrom(
+    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.BucketSpan parseDelimitedFrom(
         java.io.InputStream input,
         com.google.protobuf.ExtensionRegistryLite extensionRegistry)
         throws java.io.IOException {
       return com.google.protobuf.GeneratedMessage
           .parseDelimitedWithIOException(PARSER, input, extensionRegistry);
     }
-    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.BucketSpan parseFrom(
+    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.BucketSpan parseFrom(
         com.google.protobuf.CodedInputStream input)
         throws java.io.IOException {
       return com.google.protobuf.GeneratedMessage
           .parseWithIOException(PARSER, input);
     }
-    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.BucketSpan parseFrom(
+    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.BucketSpan parseFrom(
         com.google.protobuf.CodedInputStream input,
         com.google.protobuf.ExtensionRegistryLite extensionRegistry)
         throws java.io.IOException {
@@ -10232,7 +10232,7 @@ public static io.prometheus.metrics.expositionformats.generated.com_google_proto
     public static Builder newBuilder() {
       return DEFAULT_INSTANCE.toBuilder();
     }
-    public static Builder newBuilder(io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.BucketSpan prototype) {
+    public static Builder newBuilder(io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.BucketSpan prototype) {
       return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype);
     }
     @java.lang.Override
@@ -10262,21 +10262,21 @@ protected Builder newBuilderForType(
     public static final class Builder extends
         com.google.protobuf.GeneratedMessage.Builder implements
         // @@protoc_insertion_point(builder_implements:io.prometheus.client.BucketSpan)
-        io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.BucketSpanOrBuilder {
+        io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.BucketSpanOrBuilder {
       public static final com.google.protobuf.Descriptors.Descriptor
           getDescriptor() {
-        return io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.internal_static_io_prometheus_client_BucketSpan_descriptor;
+        return io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.internal_static_io_prometheus_client_BucketSpan_descriptor;
       }
 
       @java.lang.Override
       protected com.google.protobuf.GeneratedMessage.FieldAccessorTable
           internalGetFieldAccessorTable() {
-        return io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.internal_static_io_prometheus_client_BucketSpan_fieldAccessorTable
+        return io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.internal_static_io_prometheus_client_BucketSpan_fieldAccessorTable
             .ensureFieldAccessorsInitialized(
-                io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.BucketSpan.class, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.BucketSpan.Builder.class);
+                io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.BucketSpan.class, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.BucketSpan.Builder.class);
       }
 
-      // Construct using io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.BucketSpan.newBuilder()
+      // Construct using io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.BucketSpan.newBuilder()
       private Builder() {
 
       }
@@ -10298,17 +10298,17 @@ public Builder clear() {
       @java.lang.Override
       public com.google.protobuf.Descriptors.Descriptor
           getDescriptorForType() {
-        return io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.internal_static_io_prometheus_client_BucketSpan_descriptor;
+        return io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.internal_static_io_prometheus_client_BucketSpan_descriptor;
       }
 
       @java.lang.Override
-      public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.BucketSpan getDefaultInstanceForType() {
-        return io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.BucketSpan.getDefaultInstance();
+      public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.BucketSpan getDefaultInstanceForType() {
+        return io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.BucketSpan.getDefaultInstance();
       }
 
       @java.lang.Override
-      public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.BucketSpan build() {
-        io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.BucketSpan result = buildPartial();
+      public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.BucketSpan build() {
+        io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.BucketSpan result = buildPartial();
         if (!result.isInitialized()) {
           throw newUninitializedMessageException(result);
         }
@@ -10316,14 +10316,14 @@ public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_3
       }
 
       @java.lang.Override
-      public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.BucketSpan buildPartial() {
-        io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.BucketSpan result = new io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.BucketSpan(this);
+      public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.BucketSpan buildPartial() {
+        io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.BucketSpan result = new io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.BucketSpan(this);
         if (bitField0_ != 0) { buildPartial0(result); }
         onBuilt();
         return result;
       }
 
-      private void buildPartial0(io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.BucketSpan result) {
+      private void buildPartial0(io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.BucketSpan result) {
         int from_bitField0_ = bitField0_;
         int to_bitField0_ = 0;
         if (((from_bitField0_ & 0x00000001) != 0)) {
@@ -10339,16 +10339,16 @@ private void buildPartial0(io.prometheus.metrics.expositionformats.generated.com
 
       @java.lang.Override
       public Builder mergeFrom(com.google.protobuf.Message other) {
-        if (other instanceof io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.BucketSpan) {
-          return mergeFrom((io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.BucketSpan)other);
+        if (other instanceof io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.BucketSpan) {
+          return mergeFrom((io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.BucketSpan)other);
         } else {
           super.mergeFrom(other);
           return this;
         }
       }
 
-      public Builder mergeFrom(io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.BucketSpan other) {
-        if (other == io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.BucketSpan.getDefaultInstance()) return this;
+      public Builder mergeFrom(io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.BucketSpan other) {
+        if (other == io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.BucketSpan.getDefaultInstance()) return this;
         if (other.hasOffset()) {
           setOffset(other.getOffset());
         }
@@ -10524,12 +10524,12 @@ public Builder clearLength() {
     }
 
     // @@protoc_insertion_point(class_scope:io.prometheus.client.BucketSpan)
-    private static final io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.BucketSpan DEFAULT_INSTANCE;
+    private static final io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.BucketSpan DEFAULT_INSTANCE;
     static {
-      DEFAULT_INSTANCE = new io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.BucketSpan();
+      DEFAULT_INSTANCE = new io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.BucketSpan();
     }
 
-    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.BucketSpan getDefaultInstance() {
+    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.BucketSpan getDefaultInstance() {
       return DEFAULT_INSTANCE;
     }
 
@@ -10565,7 +10565,7 @@ public com.google.protobuf.Parser getParserForType() {
     }
 
     @java.lang.Override
-    public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.BucketSpan getDefaultInstanceForType() {
+    public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.BucketSpan getDefaultInstanceForType() {
       return DEFAULT_INSTANCE;
     }
 
@@ -10578,12 +10578,12 @@ public interface ExemplarOrBuilder extends
     /**
      * repeated .io.prometheus.client.LabelPair label = 1;
      */
-    java.util.List 
+    java.util.List 
         getLabelList();
     /**
      * repeated .io.prometheus.client.LabelPair label = 1;
      */
-    io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.LabelPair getLabel(int index);
+    io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.LabelPair getLabel(int index);
     /**
      * repeated .io.prometheus.client.LabelPair label = 1;
      */
@@ -10591,12 +10591,12 @@ public interface ExemplarOrBuilder extends
     /**
      * repeated .io.prometheus.client.LabelPair label = 1;
      */
-    java.util.List 
+    java.util.List 
         getLabelOrBuilderList();
     /**
      * repeated .io.prometheus.client.LabelPair label = 1;
      */
-    io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.LabelPairOrBuilder getLabelOrBuilder(
+    io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.LabelPairOrBuilder getLabelOrBuilder(
         int index);
 
     /**
@@ -10649,10 +10649,10 @@ public static final class Exemplar extends
       com.google.protobuf.RuntimeVersion.validateProtobufGencodeVersion(
         com.google.protobuf.RuntimeVersion.RuntimeDomain.PUBLIC,
         /* major= */ 4,
-        /* minor= */ 31,
-        /* patch= */ 1,
+        /* minor= */ 33,
+        /* patch= */ 5,
         /* suffix= */ "",
-        Exemplar.class.getName());
+        "Exemplar");
     }
     // Use Exemplar.newBuilder() to construct.
     private Exemplar(com.google.protobuf.GeneratedMessage.Builder builder) {
@@ -10664,33 +10664,33 @@ private Exemplar() {
 
     public static final com.google.protobuf.Descriptors.Descriptor
         getDescriptor() {
-      return io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.internal_static_io_prometheus_client_Exemplar_descriptor;
+      return io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.internal_static_io_prometheus_client_Exemplar_descriptor;
     }
 
     @java.lang.Override
     protected com.google.protobuf.GeneratedMessage.FieldAccessorTable
         internalGetFieldAccessorTable() {
-      return io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.internal_static_io_prometheus_client_Exemplar_fieldAccessorTable
+      return io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.internal_static_io_prometheus_client_Exemplar_fieldAccessorTable
           .ensureFieldAccessorsInitialized(
-              io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Exemplar.class, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Exemplar.Builder.class);
+              io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Exemplar.class, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Exemplar.Builder.class);
     }
 
     private int bitField0_;
     public static final int LABEL_FIELD_NUMBER = 1;
     @SuppressWarnings("serial")
-    private java.util.List label_;
+    private java.util.List label_;
     /**
      * repeated .io.prometheus.client.LabelPair label = 1;
      */
     @java.lang.Override
-    public java.util.List getLabelList() {
+    public java.util.List getLabelList() {
       return label_;
     }
     /**
      * repeated .io.prometheus.client.LabelPair label = 1;
      */
     @java.lang.Override
-    public java.util.List 
+    public java.util.List 
         getLabelOrBuilderList() {
       return label_;
     }
@@ -10705,14 +10705,14 @@ public int getLabelCount() {
      * repeated .io.prometheus.client.LabelPair label = 1;
      */
     @java.lang.Override
-    public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.LabelPair getLabel(int index) {
+    public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.LabelPair getLabel(int index) {
       return label_.get(index);
     }
     /**
      * repeated .io.prometheus.client.LabelPair label = 1;
      */
     @java.lang.Override
-    public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.LabelPairOrBuilder getLabelOrBuilder(
+    public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.LabelPairOrBuilder getLabelOrBuilder(
         int index) {
       return label_.get(index);
     }
@@ -10828,10 +10828,10 @@ public boolean equals(final java.lang.Object obj) {
       if (obj == this) {
        return true;
       }
-      if (!(obj instanceof io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Exemplar)) {
+      if (!(obj instanceof io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Exemplar)) {
         return super.equals(obj);
       }
-      io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Exemplar other = (io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Exemplar) obj;
+      io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Exemplar other = (io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Exemplar) obj;
 
       if (!getLabelList()
           .equals(other.getLabelList())) return false;
@@ -10875,44 +10875,44 @@ public int hashCode() {
       return hash;
     }
 
-    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Exemplar parseFrom(
+    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Exemplar parseFrom(
         java.nio.ByteBuffer data)
         throws com.google.protobuf.InvalidProtocolBufferException {
       return PARSER.parseFrom(data);
     }
-    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Exemplar parseFrom(
+    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Exemplar parseFrom(
         java.nio.ByteBuffer data,
         com.google.protobuf.ExtensionRegistryLite extensionRegistry)
         throws com.google.protobuf.InvalidProtocolBufferException {
       return PARSER.parseFrom(data, extensionRegistry);
     }
-    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Exemplar parseFrom(
+    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Exemplar parseFrom(
         com.google.protobuf.ByteString data)
         throws com.google.protobuf.InvalidProtocolBufferException {
       return PARSER.parseFrom(data);
     }
-    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Exemplar parseFrom(
+    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Exemplar parseFrom(
         com.google.protobuf.ByteString data,
         com.google.protobuf.ExtensionRegistryLite extensionRegistry)
         throws com.google.protobuf.InvalidProtocolBufferException {
       return PARSER.parseFrom(data, extensionRegistry);
     }
-    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Exemplar parseFrom(byte[] data)
+    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Exemplar parseFrom(byte[] data)
         throws com.google.protobuf.InvalidProtocolBufferException {
       return PARSER.parseFrom(data);
     }
-    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Exemplar parseFrom(
+    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Exemplar parseFrom(
         byte[] data,
         com.google.protobuf.ExtensionRegistryLite extensionRegistry)
         throws com.google.protobuf.InvalidProtocolBufferException {
       return PARSER.parseFrom(data, extensionRegistry);
     }
-    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Exemplar parseFrom(java.io.InputStream input)
+    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Exemplar parseFrom(java.io.InputStream input)
         throws java.io.IOException {
       return com.google.protobuf.GeneratedMessage
           .parseWithIOException(PARSER, input);
     }
-    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Exemplar parseFrom(
+    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Exemplar parseFrom(
         java.io.InputStream input,
         com.google.protobuf.ExtensionRegistryLite extensionRegistry)
         throws java.io.IOException {
@@ -10920,26 +10920,26 @@ public static io.prometheus.metrics.expositionformats.generated.com_google_proto
           .parseWithIOException(PARSER, input, extensionRegistry);
     }
 
-    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Exemplar parseDelimitedFrom(java.io.InputStream input)
+    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Exemplar parseDelimitedFrom(java.io.InputStream input)
         throws java.io.IOException {
       return com.google.protobuf.GeneratedMessage
           .parseDelimitedWithIOException(PARSER, input);
     }
 
-    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Exemplar parseDelimitedFrom(
+    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Exemplar parseDelimitedFrom(
         java.io.InputStream input,
         com.google.protobuf.ExtensionRegistryLite extensionRegistry)
         throws java.io.IOException {
       return com.google.protobuf.GeneratedMessage
           .parseDelimitedWithIOException(PARSER, input, extensionRegistry);
     }
-    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Exemplar parseFrom(
+    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Exemplar parseFrom(
         com.google.protobuf.CodedInputStream input)
         throws java.io.IOException {
       return com.google.protobuf.GeneratedMessage
           .parseWithIOException(PARSER, input);
     }
-    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Exemplar parseFrom(
+    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Exemplar parseFrom(
         com.google.protobuf.CodedInputStream input,
         com.google.protobuf.ExtensionRegistryLite extensionRegistry)
         throws java.io.IOException {
@@ -10952,7 +10952,7 @@ public static io.prometheus.metrics.expositionformats.generated.com_google_proto
     public static Builder newBuilder() {
       return DEFAULT_INSTANCE.toBuilder();
     }
-    public static Builder newBuilder(io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Exemplar prototype) {
+    public static Builder newBuilder(io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Exemplar prototype) {
       return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype);
     }
     @java.lang.Override
@@ -10973,21 +10973,21 @@ protected Builder newBuilderForType(
     public static final class Builder extends
         com.google.protobuf.GeneratedMessage.Builder implements
         // @@protoc_insertion_point(builder_implements:io.prometheus.client.Exemplar)
-        io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.ExemplarOrBuilder {
+        io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.ExemplarOrBuilder {
       public static final com.google.protobuf.Descriptors.Descriptor
           getDescriptor() {
-        return io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.internal_static_io_prometheus_client_Exemplar_descriptor;
+        return io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.internal_static_io_prometheus_client_Exemplar_descriptor;
       }
 
       @java.lang.Override
       protected com.google.protobuf.GeneratedMessage.FieldAccessorTable
           internalGetFieldAccessorTable() {
-        return io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.internal_static_io_prometheus_client_Exemplar_fieldAccessorTable
+        return io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.internal_static_io_prometheus_client_Exemplar_fieldAccessorTable
             .ensureFieldAccessorsInitialized(
-                io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Exemplar.class, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Exemplar.Builder.class);
+                io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Exemplar.class, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Exemplar.Builder.class);
       }
 
-      // Construct using io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Exemplar.newBuilder()
+      // Construct using io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Exemplar.newBuilder()
       private Builder() {
         maybeForceBuilderInitialization();
       }
@@ -11027,17 +11027,17 @@ public Builder clear() {
       @java.lang.Override
       public com.google.protobuf.Descriptors.Descriptor
           getDescriptorForType() {
-        return io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.internal_static_io_prometheus_client_Exemplar_descriptor;
+        return io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.internal_static_io_prometheus_client_Exemplar_descriptor;
       }
 
       @java.lang.Override
-      public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Exemplar getDefaultInstanceForType() {
-        return io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Exemplar.getDefaultInstance();
+      public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Exemplar getDefaultInstanceForType() {
+        return io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Exemplar.getDefaultInstance();
       }
 
       @java.lang.Override
-      public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Exemplar build() {
-        io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Exemplar result = buildPartial();
+      public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Exemplar build() {
+        io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Exemplar result = buildPartial();
         if (!result.isInitialized()) {
           throw newUninitializedMessageException(result);
         }
@@ -11045,15 +11045,15 @@ public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_3
       }
 
       @java.lang.Override
-      public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Exemplar buildPartial() {
-        io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Exemplar result = new io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Exemplar(this);
+      public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Exemplar buildPartial() {
+        io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Exemplar result = new io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Exemplar(this);
         buildPartialRepeatedFields(result);
         if (bitField0_ != 0) { buildPartial0(result); }
         onBuilt();
         return result;
       }
 
-      private void buildPartialRepeatedFields(io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Exemplar result) {
+      private void buildPartialRepeatedFields(io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Exemplar result) {
         if (labelBuilder_ == null) {
           if (((bitField0_ & 0x00000001) != 0)) {
             label_ = java.util.Collections.unmodifiableList(label_);
@@ -11065,7 +11065,7 @@ private void buildPartialRepeatedFields(io.prometheus.metrics.expositionformats.
         }
       }
 
-      private void buildPartial0(io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Exemplar result) {
+      private void buildPartial0(io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Exemplar result) {
         int from_bitField0_ = bitField0_;
         int to_bitField0_ = 0;
         if (((from_bitField0_ & 0x00000002) != 0)) {
@@ -11083,16 +11083,16 @@ private void buildPartial0(io.prometheus.metrics.expositionformats.generated.com
 
       @java.lang.Override
       public Builder mergeFrom(com.google.protobuf.Message other) {
-        if (other instanceof io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Exemplar) {
-          return mergeFrom((io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Exemplar)other);
+        if (other instanceof io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Exemplar) {
+          return mergeFrom((io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Exemplar)other);
         } else {
           super.mergeFrom(other);
           return this;
         }
       }
 
-      public Builder mergeFrom(io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Exemplar other) {
-        if (other == io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Exemplar.getDefaultInstance()) return this;
+      public Builder mergeFrom(io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Exemplar other) {
+        if (other == io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Exemplar.getDefaultInstance()) return this;
         if (labelBuilder_ == null) {
           if (!other.label_.isEmpty()) {
             if (label_.isEmpty()) {
@@ -11152,9 +11152,9 @@ public Builder mergeFrom(
                 done = true;
                 break;
               case 10: {
-                io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.LabelPair m =
+                io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.LabelPair m =
                     input.readMessage(
-                        io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.LabelPair.parser(),
+                        io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.LabelPair.parser(),
                         extensionRegistry);
                 if (labelBuilder_ == null) {
                   ensureLabelIsMutable();
@@ -11193,22 +11193,22 @@ public Builder mergeFrom(
       }
       private int bitField0_;
 
-      private java.util.List label_ =
+      private java.util.List label_ =
         java.util.Collections.emptyList();
       private void ensureLabelIsMutable() {
         if (!((bitField0_ & 0x00000001) != 0)) {
-          label_ = new java.util.ArrayList(label_);
+          label_ = new java.util.ArrayList(label_);
           bitField0_ |= 0x00000001;
          }
       }
 
       private com.google.protobuf.RepeatedFieldBuilder<
-          io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.LabelPair, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.LabelPair.Builder, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.LabelPairOrBuilder> labelBuilder_;
+          io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.LabelPair, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.LabelPair.Builder, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.LabelPairOrBuilder> labelBuilder_;
 
       /**
        * repeated .io.prometheus.client.LabelPair label = 1;
        */
-      public java.util.List getLabelList() {
+      public java.util.List getLabelList() {
         if (labelBuilder_ == null) {
           return java.util.Collections.unmodifiableList(label_);
         } else {
@@ -11228,7 +11228,7 @@ public int getLabelCount() {
       /**
        * repeated .io.prometheus.client.LabelPair label = 1;
        */
-      public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.LabelPair getLabel(int index) {
+      public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.LabelPair getLabel(int index) {
         if (labelBuilder_ == null) {
           return label_.get(index);
         } else {
@@ -11239,7 +11239,7 @@ public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_3
        * repeated .io.prometheus.client.LabelPair label = 1;
        */
       public Builder setLabel(
-          int index, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.LabelPair value) {
+          int index, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.LabelPair value) {
         if (labelBuilder_ == null) {
           if (value == null) {
             throw new NullPointerException();
@@ -11256,7 +11256,7 @@ public Builder setLabel(
        * repeated .io.prometheus.client.LabelPair label = 1;
        */
       public Builder setLabel(
-          int index, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.LabelPair.Builder builderForValue) {
+          int index, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.LabelPair.Builder builderForValue) {
         if (labelBuilder_ == null) {
           ensureLabelIsMutable();
           label_.set(index, builderForValue.build());
@@ -11269,7 +11269,7 @@ public Builder setLabel(
       /**
        * repeated .io.prometheus.client.LabelPair label = 1;
        */
-      public Builder addLabel(io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.LabelPair value) {
+      public Builder addLabel(io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.LabelPair value) {
         if (labelBuilder_ == null) {
           if (value == null) {
             throw new NullPointerException();
@@ -11286,7 +11286,7 @@ public Builder addLabel(io.prometheus.metrics.expositionformats.generated.com_go
        * repeated .io.prometheus.client.LabelPair label = 1;
        */
       public Builder addLabel(
-          int index, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.LabelPair value) {
+          int index, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.LabelPair value) {
         if (labelBuilder_ == null) {
           if (value == null) {
             throw new NullPointerException();
@@ -11303,7 +11303,7 @@ public Builder addLabel(
        * repeated .io.prometheus.client.LabelPair label = 1;
        */
       public Builder addLabel(
-          io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.LabelPair.Builder builderForValue) {
+          io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.LabelPair.Builder builderForValue) {
         if (labelBuilder_ == null) {
           ensureLabelIsMutable();
           label_.add(builderForValue.build());
@@ -11317,7 +11317,7 @@ public Builder addLabel(
        * repeated .io.prometheus.client.LabelPair label = 1;
        */
       public Builder addLabel(
-          int index, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.LabelPair.Builder builderForValue) {
+          int index, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.LabelPair.Builder builderForValue) {
         if (labelBuilder_ == null) {
           ensureLabelIsMutable();
           label_.add(index, builderForValue.build());
@@ -11331,7 +11331,7 @@ public Builder addLabel(
        * repeated .io.prometheus.client.LabelPair label = 1;
        */
       public Builder addAllLabel(
-          java.lang.Iterable values) {
+          java.lang.Iterable values) {
         if (labelBuilder_ == null) {
           ensureLabelIsMutable();
           com.google.protobuf.AbstractMessageLite.Builder.addAll(
@@ -11371,14 +11371,14 @@ public Builder removeLabel(int index) {
       /**
        * repeated .io.prometheus.client.LabelPair label = 1;
        */
-      public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.LabelPair.Builder getLabelBuilder(
+      public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.LabelPair.Builder getLabelBuilder(
           int index) {
         return internalGetLabelFieldBuilder().getBuilder(index);
       }
       /**
        * repeated .io.prometheus.client.LabelPair label = 1;
        */
-      public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.LabelPairOrBuilder getLabelOrBuilder(
+      public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.LabelPairOrBuilder getLabelOrBuilder(
           int index) {
         if (labelBuilder_ == null) {
           return label_.get(index);  } else {
@@ -11388,7 +11388,7 @@ public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_3
       /**
        * repeated .io.prometheus.client.LabelPair label = 1;
        */
-      public java.util.List 
+      public java.util.List 
            getLabelOrBuilderList() {
         if (labelBuilder_ != null) {
           return labelBuilder_.getMessageOrBuilderList();
@@ -11399,31 +11399,31 @@ public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_3
       /**
        * repeated .io.prometheus.client.LabelPair label = 1;
        */
-      public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.LabelPair.Builder addLabelBuilder() {
+      public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.LabelPair.Builder addLabelBuilder() {
         return internalGetLabelFieldBuilder().addBuilder(
-            io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.LabelPair.getDefaultInstance());
+            io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.LabelPair.getDefaultInstance());
       }
       /**
        * repeated .io.prometheus.client.LabelPair label = 1;
        */
-      public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.LabelPair.Builder addLabelBuilder(
+      public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.LabelPair.Builder addLabelBuilder(
           int index) {
         return internalGetLabelFieldBuilder().addBuilder(
-            index, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.LabelPair.getDefaultInstance());
+            index, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.LabelPair.getDefaultInstance());
       }
       /**
        * repeated .io.prometheus.client.LabelPair label = 1;
        */
-      public java.util.List 
+      public java.util.List 
            getLabelBuilderList() {
         return internalGetLabelFieldBuilder().getBuilderList();
       }
       private com.google.protobuf.RepeatedFieldBuilder<
-          io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.LabelPair, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.LabelPair.Builder, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.LabelPairOrBuilder> 
+          io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.LabelPair, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.LabelPair.Builder, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.LabelPairOrBuilder> 
           internalGetLabelFieldBuilder() {
         if (labelBuilder_ == null) {
           labelBuilder_ = new com.google.protobuf.RepeatedFieldBuilder<
-              io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.LabelPair, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.LabelPair.Builder, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.LabelPairOrBuilder>(
+              io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.LabelPair, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.LabelPair.Builder, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.LabelPairOrBuilder>(
                   label_,
                   ((bitField0_ & 0x00000001) != 0),
                   getParentForChildren(),
@@ -11634,12 +11634,12 @@ public com.google.protobuf.TimestampOrBuilder getTimestampOrBuilder() {
     }
 
     // @@protoc_insertion_point(class_scope:io.prometheus.client.Exemplar)
-    private static final io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Exemplar DEFAULT_INSTANCE;
+    private static final io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Exemplar DEFAULT_INSTANCE;
     static {
-      DEFAULT_INSTANCE = new io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Exemplar();
+      DEFAULT_INSTANCE = new io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Exemplar();
     }
 
-    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Exemplar getDefaultInstance() {
+    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Exemplar getDefaultInstance() {
       return DEFAULT_INSTANCE;
     }
 
@@ -11675,7 +11675,7 @@ public com.google.protobuf.Parser getParserForType() {
     }
 
     @java.lang.Override
-    public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Exemplar getDefaultInstanceForType() {
+    public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Exemplar getDefaultInstanceForType() {
       return DEFAULT_INSTANCE;
     }
 
@@ -11688,12 +11688,12 @@ public interface MetricOrBuilder extends
     /**
      * repeated .io.prometheus.client.LabelPair label = 1;
      */
-    java.util.List 
+    java.util.List 
         getLabelList();
     /**
      * repeated .io.prometheus.client.LabelPair label = 1;
      */
-    io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.LabelPair getLabel(int index);
+    io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.LabelPair getLabel(int index);
     /**
      * repeated .io.prometheus.client.LabelPair label = 1;
      */
@@ -11701,12 +11701,12 @@ public interface MetricOrBuilder extends
     /**
      * repeated .io.prometheus.client.LabelPair label = 1;
      */
-    java.util.List 
+    java.util.List 
         getLabelOrBuilderList();
     /**
      * repeated .io.prometheus.client.LabelPair label = 1;
      */
-    io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.LabelPairOrBuilder getLabelOrBuilder(
+    io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.LabelPairOrBuilder getLabelOrBuilder(
         int index);
 
     /**
@@ -11718,11 +11718,11 @@ io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Met
      * optional .io.prometheus.client.Gauge gauge = 2;
      * @return The gauge.
      */
-    io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Gauge getGauge();
+    io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Gauge getGauge();
     /**
      * optional .io.prometheus.client.Gauge gauge = 2;
      */
-    io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.GaugeOrBuilder getGaugeOrBuilder();
+    io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.GaugeOrBuilder getGaugeOrBuilder();
 
     /**
      * optional .io.prometheus.client.Counter counter = 3;
@@ -11733,11 +11733,11 @@ io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Met
      * optional .io.prometheus.client.Counter counter = 3;
      * @return The counter.
      */
-    io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Counter getCounter();
+    io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Counter getCounter();
     /**
      * optional .io.prometheus.client.Counter counter = 3;
      */
-    io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.CounterOrBuilder getCounterOrBuilder();
+    io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.CounterOrBuilder getCounterOrBuilder();
 
     /**
      * optional .io.prometheus.client.Summary summary = 4;
@@ -11748,11 +11748,11 @@ io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Met
      * optional .io.prometheus.client.Summary summary = 4;
      * @return The summary.
      */
-    io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Summary getSummary();
+    io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Summary getSummary();
     /**
      * optional .io.prometheus.client.Summary summary = 4;
      */
-    io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.SummaryOrBuilder getSummaryOrBuilder();
+    io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.SummaryOrBuilder getSummaryOrBuilder();
 
     /**
      * optional .io.prometheus.client.Untyped untyped = 5;
@@ -11763,11 +11763,11 @@ io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Met
      * optional .io.prometheus.client.Untyped untyped = 5;
      * @return The untyped.
      */
-    io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Untyped getUntyped();
+    io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Untyped getUntyped();
     /**
      * optional .io.prometheus.client.Untyped untyped = 5;
      */
-    io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.UntypedOrBuilder getUntypedOrBuilder();
+    io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.UntypedOrBuilder getUntypedOrBuilder();
 
     /**
      * optional .io.prometheus.client.Histogram histogram = 7;
@@ -11778,11 +11778,11 @@ io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Met
      * optional .io.prometheus.client.Histogram histogram = 7;
      * @return The histogram.
      */
-    io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Histogram getHistogram();
+    io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Histogram getHistogram();
     /**
      * optional .io.prometheus.client.Histogram histogram = 7;
      */
-    io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.HistogramOrBuilder getHistogramOrBuilder();
+    io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.HistogramOrBuilder getHistogramOrBuilder();
 
     /**
      * optional int64 timestamp_ms = 6;
@@ -11807,10 +11807,10 @@ public static final class Metric extends
       com.google.protobuf.RuntimeVersion.validateProtobufGencodeVersion(
         com.google.protobuf.RuntimeVersion.RuntimeDomain.PUBLIC,
         /* major= */ 4,
-        /* minor= */ 31,
-        /* patch= */ 1,
+        /* minor= */ 33,
+        /* patch= */ 5,
         /* suffix= */ "",
-        Metric.class.getName());
+        "Metric");
     }
     // Use Metric.newBuilder() to construct.
     private Metric(com.google.protobuf.GeneratedMessage.Builder builder) {
@@ -11822,33 +11822,33 @@ private Metric() {
 
     public static final com.google.protobuf.Descriptors.Descriptor
         getDescriptor() {
-      return io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.internal_static_io_prometheus_client_Metric_descriptor;
+      return io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.internal_static_io_prometheus_client_Metric_descriptor;
     }
 
     @java.lang.Override
     protected com.google.protobuf.GeneratedMessage.FieldAccessorTable
         internalGetFieldAccessorTable() {
-      return io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.internal_static_io_prometheus_client_Metric_fieldAccessorTable
+      return io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.internal_static_io_prometheus_client_Metric_fieldAccessorTable
           .ensureFieldAccessorsInitialized(
-              io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Metric.class, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Metric.Builder.class);
+              io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Metric.class, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Metric.Builder.class);
     }
 
     private int bitField0_;
     public static final int LABEL_FIELD_NUMBER = 1;
     @SuppressWarnings("serial")
-    private java.util.List label_;
+    private java.util.List label_;
     /**
      * repeated .io.prometheus.client.LabelPair label = 1;
      */
     @java.lang.Override
-    public java.util.List getLabelList() {
+    public java.util.List getLabelList() {
       return label_;
     }
     /**
      * repeated .io.prometheus.client.LabelPair label = 1;
      */
     @java.lang.Override
-    public java.util.List 
+    public java.util.List 
         getLabelOrBuilderList() {
       return label_;
     }
@@ -11863,20 +11863,20 @@ public int getLabelCount() {
      * repeated .io.prometheus.client.LabelPair label = 1;
      */
     @java.lang.Override
-    public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.LabelPair getLabel(int index) {
+    public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.LabelPair getLabel(int index) {
       return label_.get(index);
     }
     /**
      * repeated .io.prometheus.client.LabelPair label = 1;
      */
     @java.lang.Override
-    public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.LabelPairOrBuilder getLabelOrBuilder(
+    public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.LabelPairOrBuilder getLabelOrBuilder(
         int index) {
       return label_.get(index);
     }
 
     public static final int GAUGE_FIELD_NUMBER = 2;
-    private io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Gauge gauge_;
+    private io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Gauge gauge_;
     /**
      * optional .io.prometheus.client.Gauge gauge = 2;
      * @return Whether the gauge field is set.
@@ -11890,19 +11890,19 @@ public boolean hasGauge() {
      * @return The gauge.
      */
     @java.lang.Override
-    public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Gauge getGauge() {
-      return gauge_ == null ? io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Gauge.getDefaultInstance() : gauge_;
+    public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Gauge getGauge() {
+      return gauge_ == null ? io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Gauge.getDefaultInstance() : gauge_;
     }
     /**
      * optional .io.prometheus.client.Gauge gauge = 2;
      */
     @java.lang.Override
-    public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.GaugeOrBuilder getGaugeOrBuilder() {
-      return gauge_ == null ? io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Gauge.getDefaultInstance() : gauge_;
+    public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.GaugeOrBuilder getGaugeOrBuilder() {
+      return gauge_ == null ? io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Gauge.getDefaultInstance() : gauge_;
     }
 
     public static final int COUNTER_FIELD_NUMBER = 3;
-    private io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Counter counter_;
+    private io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Counter counter_;
     /**
      * optional .io.prometheus.client.Counter counter = 3;
      * @return Whether the counter field is set.
@@ -11916,19 +11916,19 @@ public boolean hasCounter() {
      * @return The counter.
      */
     @java.lang.Override
-    public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Counter getCounter() {
-      return counter_ == null ? io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Counter.getDefaultInstance() : counter_;
+    public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Counter getCounter() {
+      return counter_ == null ? io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Counter.getDefaultInstance() : counter_;
     }
     /**
      * optional .io.prometheus.client.Counter counter = 3;
      */
     @java.lang.Override
-    public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.CounterOrBuilder getCounterOrBuilder() {
-      return counter_ == null ? io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Counter.getDefaultInstance() : counter_;
+    public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.CounterOrBuilder getCounterOrBuilder() {
+      return counter_ == null ? io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Counter.getDefaultInstance() : counter_;
     }
 
     public static final int SUMMARY_FIELD_NUMBER = 4;
-    private io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Summary summary_;
+    private io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Summary summary_;
     /**
      * optional .io.prometheus.client.Summary summary = 4;
      * @return Whether the summary field is set.
@@ -11942,19 +11942,19 @@ public boolean hasSummary() {
      * @return The summary.
      */
     @java.lang.Override
-    public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Summary getSummary() {
-      return summary_ == null ? io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Summary.getDefaultInstance() : summary_;
+    public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Summary getSummary() {
+      return summary_ == null ? io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Summary.getDefaultInstance() : summary_;
     }
     /**
      * optional .io.prometheus.client.Summary summary = 4;
      */
     @java.lang.Override
-    public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.SummaryOrBuilder getSummaryOrBuilder() {
-      return summary_ == null ? io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Summary.getDefaultInstance() : summary_;
+    public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.SummaryOrBuilder getSummaryOrBuilder() {
+      return summary_ == null ? io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Summary.getDefaultInstance() : summary_;
     }
 
     public static final int UNTYPED_FIELD_NUMBER = 5;
-    private io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Untyped untyped_;
+    private io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Untyped untyped_;
     /**
      * optional .io.prometheus.client.Untyped untyped = 5;
      * @return Whether the untyped field is set.
@@ -11968,19 +11968,19 @@ public boolean hasUntyped() {
      * @return The untyped.
      */
     @java.lang.Override
-    public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Untyped getUntyped() {
-      return untyped_ == null ? io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Untyped.getDefaultInstance() : untyped_;
+    public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Untyped getUntyped() {
+      return untyped_ == null ? io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Untyped.getDefaultInstance() : untyped_;
     }
     /**
      * optional .io.prometheus.client.Untyped untyped = 5;
      */
     @java.lang.Override
-    public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.UntypedOrBuilder getUntypedOrBuilder() {
-      return untyped_ == null ? io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Untyped.getDefaultInstance() : untyped_;
+    public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.UntypedOrBuilder getUntypedOrBuilder() {
+      return untyped_ == null ? io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Untyped.getDefaultInstance() : untyped_;
     }
 
     public static final int HISTOGRAM_FIELD_NUMBER = 7;
-    private io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Histogram histogram_;
+    private io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Histogram histogram_;
     /**
      * optional .io.prometheus.client.Histogram histogram = 7;
      * @return Whether the histogram field is set.
@@ -11994,15 +11994,15 @@ public boolean hasHistogram() {
      * @return The histogram.
      */
     @java.lang.Override
-    public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Histogram getHistogram() {
-      return histogram_ == null ? io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Histogram.getDefaultInstance() : histogram_;
+    public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Histogram getHistogram() {
+      return histogram_ == null ? io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Histogram.getDefaultInstance() : histogram_;
     }
     /**
      * optional .io.prometheus.client.Histogram histogram = 7;
      */
     @java.lang.Override
-    public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.HistogramOrBuilder getHistogramOrBuilder() {
-      return histogram_ == null ? io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Histogram.getDefaultInstance() : histogram_;
+    public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.HistogramOrBuilder getHistogramOrBuilder() {
+      return histogram_ == null ? io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Histogram.getDefaultInstance() : histogram_;
     }
 
     public static final int TIMESTAMP_MS_FIELD_NUMBER = 6;
@@ -12106,10 +12106,10 @@ public boolean equals(final java.lang.Object obj) {
       if (obj == this) {
        return true;
       }
-      if (!(obj instanceof io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Metric)) {
+      if (!(obj instanceof io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Metric)) {
         return super.equals(obj);
       }
-      io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Metric other = (io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Metric) obj;
+      io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Metric other = (io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Metric) obj;
 
       if (!getLabelList()
           .equals(other.getLabelList())) return false;
@@ -12188,44 +12188,44 @@ public int hashCode() {
       return hash;
     }
 
-    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Metric parseFrom(
+    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Metric parseFrom(
         java.nio.ByteBuffer data)
         throws com.google.protobuf.InvalidProtocolBufferException {
       return PARSER.parseFrom(data);
     }
-    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Metric parseFrom(
+    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Metric parseFrom(
         java.nio.ByteBuffer data,
         com.google.protobuf.ExtensionRegistryLite extensionRegistry)
         throws com.google.protobuf.InvalidProtocolBufferException {
       return PARSER.parseFrom(data, extensionRegistry);
     }
-    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Metric parseFrom(
+    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Metric parseFrom(
         com.google.protobuf.ByteString data)
         throws com.google.protobuf.InvalidProtocolBufferException {
       return PARSER.parseFrom(data);
     }
-    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Metric parseFrom(
+    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Metric parseFrom(
         com.google.protobuf.ByteString data,
         com.google.protobuf.ExtensionRegistryLite extensionRegistry)
         throws com.google.protobuf.InvalidProtocolBufferException {
       return PARSER.parseFrom(data, extensionRegistry);
     }
-    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Metric parseFrom(byte[] data)
+    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Metric parseFrom(byte[] data)
         throws com.google.protobuf.InvalidProtocolBufferException {
       return PARSER.parseFrom(data);
     }
-    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Metric parseFrom(
+    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Metric parseFrom(
         byte[] data,
         com.google.protobuf.ExtensionRegistryLite extensionRegistry)
         throws com.google.protobuf.InvalidProtocolBufferException {
       return PARSER.parseFrom(data, extensionRegistry);
     }
-    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Metric parseFrom(java.io.InputStream input)
+    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Metric parseFrom(java.io.InputStream input)
         throws java.io.IOException {
       return com.google.protobuf.GeneratedMessage
           .parseWithIOException(PARSER, input);
     }
-    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Metric parseFrom(
+    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Metric parseFrom(
         java.io.InputStream input,
         com.google.protobuf.ExtensionRegistryLite extensionRegistry)
         throws java.io.IOException {
@@ -12233,26 +12233,26 @@ public static io.prometheus.metrics.expositionformats.generated.com_google_proto
           .parseWithIOException(PARSER, input, extensionRegistry);
     }
 
-    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Metric parseDelimitedFrom(java.io.InputStream input)
+    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Metric parseDelimitedFrom(java.io.InputStream input)
         throws java.io.IOException {
       return com.google.protobuf.GeneratedMessage
           .parseDelimitedWithIOException(PARSER, input);
     }
 
-    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Metric parseDelimitedFrom(
+    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Metric parseDelimitedFrom(
         java.io.InputStream input,
         com.google.protobuf.ExtensionRegistryLite extensionRegistry)
         throws java.io.IOException {
       return com.google.protobuf.GeneratedMessage
           .parseDelimitedWithIOException(PARSER, input, extensionRegistry);
     }
-    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Metric parseFrom(
+    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Metric parseFrom(
         com.google.protobuf.CodedInputStream input)
         throws java.io.IOException {
       return com.google.protobuf.GeneratedMessage
           .parseWithIOException(PARSER, input);
     }
-    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Metric parseFrom(
+    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Metric parseFrom(
         com.google.protobuf.CodedInputStream input,
         com.google.protobuf.ExtensionRegistryLite extensionRegistry)
         throws java.io.IOException {
@@ -12265,7 +12265,7 @@ public static io.prometheus.metrics.expositionformats.generated.com_google_proto
     public static Builder newBuilder() {
       return DEFAULT_INSTANCE.toBuilder();
     }
-    public static Builder newBuilder(io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Metric prototype) {
+    public static Builder newBuilder(io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Metric prototype) {
       return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype);
     }
     @java.lang.Override
@@ -12286,21 +12286,21 @@ protected Builder newBuilderForType(
     public static final class Builder extends
         com.google.protobuf.GeneratedMessage.Builder implements
         // @@protoc_insertion_point(builder_implements:io.prometheus.client.Metric)
-        io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.MetricOrBuilder {
+        io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.MetricOrBuilder {
       public static final com.google.protobuf.Descriptors.Descriptor
           getDescriptor() {
-        return io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.internal_static_io_prometheus_client_Metric_descriptor;
+        return io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.internal_static_io_prometheus_client_Metric_descriptor;
       }
 
       @java.lang.Override
       protected com.google.protobuf.GeneratedMessage.FieldAccessorTable
           internalGetFieldAccessorTable() {
-        return io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.internal_static_io_prometheus_client_Metric_fieldAccessorTable
+        return io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.internal_static_io_prometheus_client_Metric_fieldAccessorTable
             .ensureFieldAccessorsInitialized(
-                io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Metric.class, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Metric.Builder.class);
+                io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Metric.class, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Metric.Builder.class);
       }
 
-      // Construct using io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Metric.newBuilder()
+      // Construct using io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Metric.newBuilder()
       private Builder() {
         maybeForceBuilderInitialization();
       }
@@ -12364,17 +12364,17 @@ public Builder clear() {
       @java.lang.Override
       public com.google.protobuf.Descriptors.Descriptor
           getDescriptorForType() {
-        return io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.internal_static_io_prometheus_client_Metric_descriptor;
+        return io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.internal_static_io_prometheus_client_Metric_descriptor;
       }
 
       @java.lang.Override
-      public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Metric getDefaultInstanceForType() {
-        return io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Metric.getDefaultInstance();
+      public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Metric getDefaultInstanceForType() {
+        return io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Metric.getDefaultInstance();
       }
 
       @java.lang.Override
-      public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Metric build() {
-        io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Metric result = buildPartial();
+      public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Metric build() {
+        io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Metric result = buildPartial();
         if (!result.isInitialized()) {
           throw newUninitializedMessageException(result);
         }
@@ -12382,15 +12382,15 @@ public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_3
       }
 
       @java.lang.Override
-      public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Metric buildPartial() {
-        io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Metric result = new io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Metric(this);
+      public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Metric buildPartial() {
+        io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Metric result = new io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Metric(this);
         buildPartialRepeatedFields(result);
         if (bitField0_ != 0) { buildPartial0(result); }
         onBuilt();
         return result;
       }
 
-      private void buildPartialRepeatedFields(io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Metric result) {
+      private void buildPartialRepeatedFields(io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Metric result) {
         if (labelBuilder_ == null) {
           if (((bitField0_ & 0x00000001) != 0)) {
             label_ = java.util.Collections.unmodifiableList(label_);
@@ -12402,7 +12402,7 @@ private void buildPartialRepeatedFields(io.prometheus.metrics.expositionformats.
         }
       }
 
-      private void buildPartial0(io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Metric result) {
+      private void buildPartial0(io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Metric result) {
         int from_bitField0_ = bitField0_;
         int to_bitField0_ = 0;
         if (((from_bitField0_ & 0x00000002) != 0)) {
@@ -12444,16 +12444,16 @@ private void buildPartial0(io.prometheus.metrics.expositionformats.generated.com
 
       @java.lang.Override
       public Builder mergeFrom(com.google.protobuf.Message other) {
-        if (other instanceof io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Metric) {
-          return mergeFrom((io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Metric)other);
+        if (other instanceof io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Metric) {
+          return mergeFrom((io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Metric)other);
         } else {
           super.mergeFrom(other);
           return this;
         }
       }
 
-      public Builder mergeFrom(io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Metric other) {
-        if (other == io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Metric.getDefaultInstance()) return this;
+      public Builder mergeFrom(io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Metric other) {
+        if (other == io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Metric.getDefaultInstance()) return this;
         if (labelBuilder_ == null) {
           if (!other.label_.isEmpty()) {
             if (label_.isEmpty()) {
@@ -12525,9 +12525,9 @@ public Builder mergeFrom(
                 done = true;
                 break;
               case 10: {
-                io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.LabelPair m =
+                io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.LabelPair m =
                     input.readMessage(
-                        io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.LabelPair.parser(),
+                        io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.LabelPair.parser(),
                         extensionRegistry);
                 if (labelBuilder_ == null) {
                   ensureLabelIsMutable();
@@ -12594,22 +12594,22 @@ public Builder mergeFrom(
       }
       private int bitField0_;
 
-      private java.util.List label_ =
+      private java.util.List label_ =
         java.util.Collections.emptyList();
       private void ensureLabelIsMutable() {
         if (!((bitField0_ & 0x00000001) != 0)) {
-          label_ = new java.util.ArrayList(label_);
+          label_ = new java.util.ArrayList(label_);
           bitField0_ |= 0x00000001;
          }
       }
 
       private com.google.protobuf.RepeatedFieldBuilder<
-          io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.LabelPair, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.LabelPair.Builder, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.LabelPairOrBuilder> labelBuilder_;
+          io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.LabelPair, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.LabelPair.Builder, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.LabelPairOrBuilder> labelBuilder_;
 
       /**
        * repeated .io.prometheus.client.LabelPair label = 1;
        */
-      public java.util.List getLabelList() {
+      public java.util.List getLabelList() {
         if (labelBuilder_ == null) {
           return java.util.Collections.unmodifiableList(label_);
         } else {
@@ -12629,7 +12629,7 @@ public int getLabelCount() {
       /**
        * repeated .io.prometheus.client.LabelPair label = 1;
        */
-      public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.LabelPair getLabel(int index) {
+      public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.LabelPair getLabel(int index) {
         if (labelBuilder_ == null) {
           return label_.get(index);
         } else {
@@ -12640,7 +12640,7 @@ public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_3
        * repeated .io.prometheus.client.LabelPair label = 1;
        */
       public Builder setLabel(
-          int index, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.LabelPair value) {
+          int index, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.LabelPair value) {
         if (labelBuilder_ == null) {
           if (value == null) {
             throw new NullPointerException();
@@ -12657,7 +12657,7 @@ public Builder setLabel(
        * repeated .io.prometheus.client.LabelPair label = 1;
        */
       public Builder setLabel(
-          int index, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.LabelPair.Builder builderForValue) {
+          int index, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.LabelPair.Builder builderForValue) {
         if (labelBuilder_ == null) {
           ensureLabelIsMutable();
           label_.set(index, builderForValue.build());
@@ -12670,7 +12670,7 @@ public Builder setLabel(
       /**
        * repeated .io.prometheus.client.LabelPair label = 1;
        */
-      public Builder addLabel(io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.LabelPair value) {
+      public Builder addLabel(io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.LabelPair value) {
         if (labelBuilder_ == null) {
           if (value == null) {
             throw new NullPointerException();
@@ -12687,7 +12687,7 @@ public Builder addLabel(io.prometheus.metrics.expositionformats.generated.com_go
        * repeated .io.prometheus.client.LabelPair label = 1;
        */
       public Builder addLabel(
-          int index, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.LabelPair value) {
+          int index, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.LabelPair value) {
         if (labelBuilder_ == null) {
           if (value == null) {
             throw new NullPointerException();
@@ -12704,7 +12704,7 @@ public Builder addLabel(
        * repeated .io.prometheus.client.LabelPair label = 1;
        */
       public Builder addLabel(
-          io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.LabelPair.Builder builderForValue) {
+          io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.LabelPair.Builder builderForValue) {
         if (labelBuilder_ == null) {
           ensureLabelIsMutable();
           label_.add(builderForValue.build());
@@ -12718,7 +12718,7 @@ public Builder addLabel(
        * repeated .io.prometheus.client.LabelPair label = 1;
        */
       public Builder addLabel(
-          int index, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.LabelPair.Builder builderForValue) {
+          int index, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.LabelPair.Builder builderForValue) {
         if (labelBuilder_ == null) {
           ensureLabelIsMutable();
           label_.add(index, builderForValue.build());
@@ -12732,7 +12732,7 @@ public Builder addLabel(
        * repeated .io.prometheus.client.LabelPair label = 1;
        */
       public Builder addAllLabel(
-          java.lang.Iterable values) {
+          java.lang.Iterable values) {
         if (labelBuilder_ == null) {
           ensureLabelIsMutable();
           com.google.protobuf.AbstractMessageLite.Builder.addAll(
@@ -12772,14 +12772,14 @@ public Builder removeLabel(int index) {
       /**
        * repeated .io.prometheus.client.LabelPair label = 1;
        */
-      public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.LabelPair.Builder getLabelBuilder(
+      public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.LabelPair.Builder getLabelBuilder(
           int index) {
         return internalGetLabelFieldBuilder().getBuilder(index);
       }
       /**
        * repeated .io.prometheus.client.LabelPair label = 1;
        */
-      public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.LabelPairOrBuilder getLabelOrBuilder(
+      public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.LabelPairOrBuilder getLabelOrBuilder(
           int index) {
         if (labelBuilder_ == null) {
           return label_.get(index);  } else {
@@ -12789,7 +12789,7 @@ public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_3
       /**
        * repeated .io.prometheus.client.LabelPair label = 1;
        */
-      public java.util.List 
+      public java.util.List 
            getLabelOrBuilderList() {
         if (labelBuilder_ != null) {
           return labelBuilder_.getMessageOrBuilderList();
@@ -12800,31 +12800,31 @@ public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_3
       /**
        * repeated .io.prometheus.client.LabelPair label = 1;
        */
-      public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.LabelPair.Builder addLabelBuilder() {
+      public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.LabelPair.Builder addLabelBuilder() {
         return internalGetLabelFieldBuilder().addBuilder(
-            io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.LabelPair.getDefaultInstance());
+            io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.LabelPair.getDefaultInstance());
       }
       /**
        * repeated .io.prometheus.client.LabelPair label = 1;
        */
-      public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.LabelPair.Builder addLabelBuilder(
+      public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.LabelPair.Builder addLabelBuilder(
           int index) {
         return internalGetLabelFieldBuilder().addBuilder(
-            index, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.LabelPair.getDefaultInstance());
+            index, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.LabelPair.getDefaultInstance());
       }
       /**
        * repeated .io.prometheus.client.LabelPair label = 1;
        */
-      public java.util.List 
+      public java.util.List 
            getLabelBuilderList() {
         return internalGetLabelFieldBuilder().getBuilderList();
       }
       private com.google.protobuf.RepeatedFieldBuilder<
-          io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.LabelPair, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.LabelPair.Builder, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.LabelPairOrBuilder> 
+          io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.LabelPair, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.LabelPair.Builder, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.LabelPairOrBuilder> 
           internalGetLabelFieldBuilder() {
         if (labelBuilder_ == null) {
           labelBuilder_ = new com.google.protobuf.RepeatedFieldBuilder<
-              io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.LabelPair, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.LabelPair.Builder, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.LabelPairOrBuilder>(
+              io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.LabelPair, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.LabelPair.Builder, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.LabelPairOrBuilder>(
                   label_,
                   ((bitField0_ & 0x00000001) != 0),
                   getParentForChildren(),
@@ -12834,9 +12834,9 @@ public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_3
         return labelBuilder_;
       }
 
-      private io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Gauge gauge_;
+      private io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Gauge gauge_;
       private com.google.protobuf.SingleFieldBuilder<
-          io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Gauge, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Gauge.Builder, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.GaugeOrBuilder> gaugeBuilder_;
+          io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Gauge, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Gauge.Builder, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.GaugeOrBuilder> gaugeBuilder_;
       /**
        * optional .io.prometheus.client.Gauge gauge = 2;
        * @return Whether the gauge field is set.
@@ -12848,9 +12848,9 @@ public boolean hasGauge() {
        * optional .io.prometheus.client.Gauge gauge = 2;
        * @return The gauge.
        */
-      public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Gauge getGauge() {
+      public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Gauge getGauge() {
         if (gaugeBuilder_ == null) {
-          return gauge_ == null ? io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Gauge.getDefaultInstance() : gauge_;
+          return gauge_ == null ? io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Gauge.getDefaultInstance() : gauge_;
         } else {
           return gaugeBuilder_.getMessage();
         }
@@ -12858,7 +12858,7 @@ public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_3
       /**
        * optional .io.prometheus.client.Gauge gauge = 2;
        */
-      public Builder setGauge(io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Gauge value) {
+      public Builder setGauge(io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Gauge value) {
         if (gaugeBuilder_ == null) {
           if (value == null) {
             throw new NullPointerException();
@@ -12875,7 +12875,7 @@ public Builder setGauge(io.prometheus.metrics.expositionformats.generated.com_go
        * optional .io.prometheus.client.Gauge gauge = 2;
        */
       public Builder setGauge(
-          io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Gauge.Builder builderForValue) {
+          io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Gauge.Builder builderForValue) {
         if (gaugeBuilder_ == null) {
           gauge_ = builderForValue.build();
         } else {
@@ -12888,11 +12888,11 @@ public Builder setGauge(
       /**
        * optional .io.prometheus.client.Gauge gauge = 2;
        */
-      public Builder mergeGauge(io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Gauge value) {
+      public Builder mergeGauge(io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Gauge value) {
         if (gaugeBuilder_ == null) {
           if (((bitField0_ & 0x00000002) != 0) &&
             gauge_ != null &&
-            gauge_ != io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Gauge.getDefaultInstance()) {
+            gauge_ != io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Gauge.getDefaultInstance()) {
             getGaugeBuilder().mergeFrom(value);
           } else {
             gauge_ = value;
@@ -12922,7 +12922,7 @@ public Builder clearGauge() {
       /**
        * optional .io.prometheus.client.Gauge gauge = 2;
        */
-      public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Gauge.Builder getGaugeBuilder() {
+      public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Gauge.Builder getGaugeBuilder() {
         bitField0_ |= 0x00000002;
         onChanged();
         return internalGetGaugeFieldBuilder().getBuilder();
@@ -12930,23 +12930,23 @@ public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_3
       /**
        * optional .io.prometheus.client.Gauge gauge = 2;
        */
-      public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.GaugeOrBuilder getGaugeOrBuilder() {
+      public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.GaugeOrBuilder getGaugeOrBuilder() {
         if (gaugeBuilder_ != null) {
           return gaugeBuilder_.getMessageOrBuilder();
         } else {
           return gauge_ == null ?
-              io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Gauge.getDefaultInstance() : gauge_;
+              io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Gauge.getDefaultInstance() : gauge_;
         }
       }
       /**
        * optional .io.prometheus.client.Gauge gauge = 2;
        */
       private com.google.protobuf.SingleFieldBuilder<
-          io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Gauge, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Gauge.Builder, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.GaugeOrBuilder> 
+          io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Gauge, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Gauge.Builder, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.GaugeOrBuilder> 
           internalGetGaugeFieldBuilder() {
         if (gaugeBuilder_ == null) {
           gaugeBuilder_ = new com.google.protobuf.SingleFieldBuilder<
-              io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Gauge, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Gauge.Builder, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.GaugeOrBuilder>(
+              io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Gauge, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Gauge.Builder, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.GaugeOrBuilder>(
                   getGauge(),
                   getParentForChildren(),
                   isClean());
@@ -12955,9 +12955,9 @@ public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_3
         return gaugeBuilder_;
       }
 
-      private io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Counter counter_;
+      private io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Counter counter_;
       private com.google.protobuf.SingleFieldBuilder<
-          io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Counter, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Counter.Builder, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.CounterOrBuilder> counterBuilder_;
+          io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Counter, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Counter.Builder, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.CounterOrBuilder> counterBuilder_;
       /**
        * optional .io.prometheus.client.Counter counter = 3;
        * @return Whether the counter field is set.
@@ -12969,9 +12969,9 @@ public boolean hasCounter() {
        * optional .io.prometheus.client.Counter counter = 3;
        * @return The counter.
        */
-      public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Counter getCounter() {
+      public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Counter getCounter() {
         if (counterBuilder_ == null) {
-          return counter_ == null ? io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Counter.getDefaultInstance() : counter_;
+          return counter_ == null ? io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Counter.getDefaultInstance() : counter_;
         } else {
           return counterBuilder_.getMessage();
         }
@@ -12979,7 +12979,7 @@ public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_3
       /**
        * optional .io.prometheus.client.Counter counter = 3;
        */
-      public Builder setCounter(io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Counter value) {
+      public Builder setCounter(io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Counter value) {
         if (counterBuilder_ == null) {
           if (value == null) {
             throw new NullPointerException();
@@ -12996,7 +12996,7 @@ public Builder setCounter(io.prometheus.metrics.expositionformats.generated.com_
        * optional .io.prometheus.client.Counter counter = 3;
        */
       public Builder setCounter(
-          io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Counter.Builder builderForValue) {
+          io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Counter.Builder builderForValue) {
         if (counterBuilder_ == null) {
           counter_ = builderForValue.build();
         } else {
@@ -13009,11 +13009,11 @@ public Builder setCounter(
       /**
        * optional .io.prometheus.client.Counter counter = 3;
        */
-      public Builder mergeCounter(io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Counter value) {
+      public Builder mergeCounter(io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Counter value) {
         if (counterBuilder_ == null) {
           if (((bitField0_ & 0x00000004) != 0) &&
             counter_ != null &&
-            counter_ != io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Counter.getDefaultInstance()) {
+            counter_ != io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Counter.getDefaultInstance()) {
             getCounterBuilder().mergeFrom(value);
           } else {
             counter_ = value;
@@ -13043,7 +13043,7 @@ public Builder clearCounter() {
       /**
        * optional .io.prometheus.client.Counter counter = 3;
        */
-      public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Counter.Builder getCounterBuilder() {
+      public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Counter.Builder getCounterBuilder() {
         bitField0_ |= 0x00000004;
         onChanged();
         return internalGetCounterFieldBuilder().getBuilder();
@@ -13051,23 +13051,23 @@ public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_3
       /**
        * optional .io.prometheus.client.Counter counter = 3;
        */
-      public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.CounterOrBuilder getCounterOrBuilder() {
+      public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.CounterOrBuilder getCounterOrBuilder() {
         if (counterBuilder_ != null) {
           return counterBuilder_.getMessageOrBuilder();
         } else {
           return counter_ == null ?
-              io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Counter.getDefaultInstance() : counter_;
+              io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Counter.getDefaultInstance() : counter_;
         }
       }
       /**
        * optional .io.prometheus.client.Counter counter = 3;
        */
       private com.google.protobuf.SingleFieldBuilder<
-          io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Counter, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Counter.Builder, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.CounterOrBuilder> 
+          io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Counter, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Counter.Builder, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.CounterOrBuilder> 
           internalGetCounterFieldBuilder() {
         if (counterBuilder_ == null) {
           counterBuilder_ = new com.google.protobuf.SingleFieldBuilder<
-              io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Counter, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Counter.Builder, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.CounterOrBuilder>(
+              io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Counter, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Counter.Builder, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.CounterOrBuilder>(
                   getCounter(),
                   getParentForChildren(),
                   isClean());
@@ -13076,9 +13076,9 @@ public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_3
         return counterBuilder_;
       }
 
-      private io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Summary summary_;
+      private io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Summary summary_;
       private com.google.protobuf.SingleFieldBuilder<
-          io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Summary, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Summary.Builder, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.SummaryOrBuilder> summaryBuilder_;
+          io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Summary, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Summary.Builder, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.SummaryOrBuilder> summaryBuilder_;
       /**
        * optional .io.prometheus.client.Summary summary = 4;
        * @return Whether the summary field is set.
@@ -13090,9 +13090,9 @@ public boolean hasSummary() {
        * optional .io.prometheus.client.Summary summary = 4;
        * @return The summary.
        */
-      public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Summary getSummary() {
+      public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Summary getSummary() {
         if (summaryBuilder_ == null) {
-          return summary_ == null ? io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Summary.getDefaultInstance() : summary_;
+          return summary_ == null ? io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Summary.getDefaultInstance() : summary_;
         } else {
           return summaryBuilder_.getMessage();
         }
@@ -13100,7 +13100,7 @@ public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_3
       /**
        * optional .io.prometheus.client.Summary summary = 4;
        */
-      public Builder setSummary(io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Summary value) {
+      public Builder setSummary(io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Summary value) {
         if (summaryBuilder_ == null) {
           if (value == null) {
             throw new NullPointerException();
@@ -13117,7 +13117,7 @@ public Builder setSummary(io.prometheus.metrics.expositionformats.generated.com_
        * optional .io.prometheus.client.Summary summary = 4;
        */
       public Builder setSummary(
-          io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Summary.Builder builderForValue) {
+          io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Summary.Builder builderForValue) {
         if (summaryBuilder_ == null) {
           summary_ = builderForValue.build();
         } else {
@@ -13130,11 +13130,11 @@ public Builder setSummary(
       /**
        * optional .io.prometheus.client.Summary summary = 4;
        */
-      public Builder mergeSummary(io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Summary value) {
+      public Builder mergeSummary(io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Summary value) {
         if (summaryBuilder_ == null) {
           if (((bitField0_ & 0x00000008) != 0) &&
             summary_ != null &&
-            summary_ != io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Summary.getDefaultInstance()) {
+            summary_ != io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Summary.getDefaultInstance()) {
             getSummaryBuilder().mergeFrom(value);
           } else {
             summary_ = value;
@@ -13164,7 +13164,7 @@ public Builder clearSummary() {
       /**
        * optional .io.prometheus.client.Summary summary = 4;
        */
-      public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Summary.Builder getSummaryBuilder() {
+      public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Summary.Builder getSummaryBuilder() {
         bitField0_ |= 0x00000008;
         onChanged();
         return internalGetSummaryFieldBuilder().getBuilder();
@@ -13172,23 +13172,23 @@ public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_3
       /**
        * optional .io.prometheus.client.Summary summary = 4;
        */
-      public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.SummaryOrBuilder getSummaryOrBuilder() {
+      public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.SummaryOrBuilder getSummaryOrBuilder() {
         if (summaryBuilder_ != null) {
           return summaryBuilder_.getMessageOrBuilder();
         } else {
           return summary_ == null ?
-              io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Summary.getDefaultInstance() : summary_;
+              io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Summary.getDefaultInstance() : summary_;
         }
       }
       /**
        * optional .io.prometheus.client.Summary summary = 4;
        */
       private com.google.protobuf.SingleFieldBuilder<
-          io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Summary, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Summary.Builder, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.SummaryOrBuilder> 
+          io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Summary, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Summary.Builder, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.SummaryOrBuilder> 
           internalGetSummaryFieldBuilder() {
         if (summaryBuilder_ == null) {
           summaryBuilder_ = new com.google.protobuf.SingleFieldBuilder<
-              io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Summary, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Summary.Builder, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.SummaryOrBuilder>(
+              io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Summary, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Summary.Builder, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.SummaryOrBuilder>(
                   getSummary(),
                   getParentForChildren(),
                   isClean());
@@ -13197,9 +13197,9 @@ public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_3
         return summaryBuilder_;
       }
 
-      private io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Untyped untyped_;
+      private io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Untyped untyped_;
       private com.google.protobuf.SingleFieldBuilder<
-          io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Untyped, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Untyped.Builder, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.UntypedOrBuilder> untypedBuilder_;
+          io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Untyped, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Untyped.Builder, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.UntypedOrBuilder> untypedBuilder_;
       /**
        * optional .io.prometheus.client.Untyped untyped = 5;
        * @return Whether the untyped field is set.
@@ -13211,9 +13211,9 @@ public boolean hasUntyped() {
        * optional .io.prometheus.client.Untyped untyped = 5;
        * @return The untyped.
        */
-      public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Untyped getUntyped() {
+      public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Untyped getUntyped() {
         if (untypedBuilder_ == null) {
-          return untyped_ == null ? io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Untyped.getDefaultInstance() : untyped_;
+          return untyped_ == null ? io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Untyped.getDefaultInstance() : untyped_;
         } else {
           return untypedBuilder_.getMessage();
         }
@@ -13221,7 +13221,7 @@ public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_3
       /**
        * optional .io.prometheus.client.Untyped untyped = 5;
        */
-      public Builder setUntyped(io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Untyped value) {
+      public Builder setUntyped(io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Untyped value) {
         if (untypedBuilder_ == null) {
           if (value == null) {
             throw new NullPointerException();
@@ -13238,7 +13238,7 @@ public Builder setUntyped(io.prometheus.metrics.expositionformats.generated.com_
        * optional .io.prometheus.client.Untyped untyped = 5;
        */
       public Builder setUntyped(
-          io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Untyped.Builder builderForValue) {
+          io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Untyped.Builder builderForValue) {
         if (untypedBuilder_ == null) {
           untyped_ = builderForValue.build();
         } else {
@@ -13251,11 +13251,11 @@ public Builder setUntyped(
       /**
        * optional .io.prometheus.client.Untyped untyped = 5;
        */
-      public Builder mergeUntyped(io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Untyped value) {
+      public Builder mergeUntyped(io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Untyped value) {
         if (untypedBuilder_ == null) {
           if (((bitField0_ & 0x00000010) != 0) &&
             untyped_ != null &&
-            untyped_ != io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Untyped.getDefaultInstance()) {
+            untyped_ != io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Untyped.getDefaultInstance()) {
             getUntypedBuilder().mergeFrom(value);
           } else {
             untyped_ = value;
@@ -13285,7 +13285,7 @@ public Builder clearUntyped() {
       /**
        * optional .io.prometheus.client.Untyped untyped = 5;
        */
-      public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Untyped.Builder getUntypedBuilder() {
+      public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Untyped.Builder getUntypedBuilder() {
         bitField0_ |= 0x00000010;
         onChanged();
         return internalGetUntypedFieldBuilder().getBuilder();
@@ -13293,23 +13293,23 @@ public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_3
       /**
        * optional .io.prometheus.client.Untyped untyped = 5;
        */
-      public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.UntypedOrBuilder getUntypedOrBuilder() {
+      public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.UntypedOrBuilder getUntypedOrBuilder() {
         if (untypedBuilder_ != null) {
           return untypedBuilder_.getMessageOrBuilder();
         } else {
           return untyped_ == null ?
-              io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Untyped.getDefaultInstance() : untyped_;
+              io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Untyped.getDefaultInstance() : untyped_;
         }
       }
       /**
        * optional .io.prometheus.client.Untyped untyped = 5;
        */
       private com.google.protobuf.SingleFieldBuilder<
-          io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Untyped, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Untyped.Builder, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.UntypedOrBuilder> 
+          io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Untyped, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Untyped.Builder, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.UntypedOrBuilder> 
           internalGetUntypedFieldBuilder() {
         if (untypedBuilder_ == null) {
           untypedBuilder_ = new com.google.protobuf.SingleFieldBuilder<
-              io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Untyped, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Untyped.Builder, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.UntypedOrBuilder>(
+              io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Untyped, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Untyped.Builder, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.UntypedOrBuilder>(
                   getUntyped(),
                   getParentForChildren(),
                   isClean());
@@ -13318,9 +13318,9 @@ public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_3
         return untypedBuilder_;
       }
 
-      private io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Histogram histogram_;
+      private io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Histogram histogram_;
       private com.google.protobuf.SingleFieldBuilder<
-          io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Histogram, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Histogram.Builder, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.HistogramOrBuilder> histogramBuilder_;
+          io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Histogram, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Histogram.Builder, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.HistogramOrBuilder> histogramBuilder_;
       /**
        * optional .io.prometheus.client.Histogram histogram = 7;
        * @return Whether the histogram field is set.
@@ -13332,9 +13332,9 @@ public boolean hasHistogram() {
        * optional .io.prometheus.client.Histogram histogram = 7;
        * @return The histogram.
        */
-      public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Histogram getHistogram() {
+      public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Histogram getHistogram() {
         if (histogramBuilder_ == null) {
-          return histogram_ == null ? io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Histogram.getDefaultInstance() : histogram_;
+          return histogram_ == null ? io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Histogram.getDefaultInstance() : histogram_;
         } else {
           return histogramBuilder_.getMessage();
         }
@@ -13342,7 +13342,7 @@ public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_3
       /**
        * optional .io.prometheus.client.Histogram histogram = 7;
        */
-      public Builder setHistogram(io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Histogram value) {
+      public Builder setHistogram(io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Histogram value) {
         if (histogramBuilder_ == null) {
           if (value == null) {
             throw new NullPointerException();
@@ -13359,7 +13359,7 @@ public Builder setHistogram(io.prometheus.metrics.expositionformats.generated.co
        * optional .io.prometheus.client.Histogram histogram = 7;
        */
       public Builder setHistogram(
-          io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Histogram.Builder builderForValue) {
+          io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Histogram.Builder builderForValue) {
         if (histogramBuilder_ == null) {
           histogram_ = builderForValue.build();
         } else {
@@ -13372,11 +13372,11 @@ public Builder setHistogram(
       /**
        * optional .io.prometheus.client.Histogram histogram = 7;
        */
-      public Builder mergeHistogram(io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Histogram value) {
+      public Builder mergeHistogram(io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Histogram value) {
         if (histogramBuilder_ == null) {
           if (((bitField0_ & 0x00000020) != 0) &&
             histogram_ != null &&
-            histogram_ != io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Histogram.getDefaultInstance()) {
+            histogram_ != io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Histogram.getDefaultInstance()) {
             getHistogramBuilder().mergeFrom(value);
           } else {
             histogram_ = value;
@@ -13406,7 +13406,7 @@ public Builder clearHistogram() {
       /**
        * optional .io.prometheus.client.Histogram histogram = 7;
        */
-      public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Histogram.Builder getHistogramBuilder() {
+      public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Histogram.Builder getHistogramBuilder() {
         bitField0_ |= 0x00000020;
         onChanged();
         return internalGetHistogramFieldBuilder().getBuilder();
@@ -13414,23 +13414,23 @@ public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_3
       /**
        * optional .io.prometheus.client.Histogram histogram = 7;
        */
-      public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.HistogramOrBuilder getHistogramOrBuilder() {
+      public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.HistogramOrBuilder getHistogramOrBuilder() {
         if (histogramBuilder_ != null) {
           return histogramBuilder_.getMessageOrBuilder();
         } else {
           return histogram_ == null ?
-              io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Histogram.getDefaultInstance() : histogram_;
+              io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Histogram.getDefaultInstance() : histogram_;
         }
       }
       /**
        * optional .io.prometheus.client.Histogram histogram = 7;
        */
       private com.google.protobuf.SingleFieldBuilder<
-          io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Histogram, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Histogram.Builder, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.HistogramOrBuilder> 
+          io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Histogram, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Histogram.Builder, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.HistogramOrBuilder> 
           internalGetHistogramFieldBuilder() {
         if (histogramBuilder_ == null) {
           histogramBuilder_ = new com.google.protobuf.SingleFieldBuilder<
-              io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Histogram, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Histogram.Builder, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.HistogramOrBuilder>(
+              io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Histogram, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Histogram.Builder, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.HistogramOrBuilder>(
                   getHistogram(),
                   getParentForChildren(),
                   isClean());
@@ -13483,12 +13483,12 @@ public Builder clearTimestampMs() {
     }
 
     // @@protoc_insertion_point(class_scope:io.prometheus.client.Metric)
-    private static final io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Metric DEFAULT_INSTANCE;
+    private static final io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Metric DEFAULT_INSTANCE;
     static {
-      DEFAULT_INSTANCE = new io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Metric();
+      DEFAULT_INSTANCE = new io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Metric();
     }
 
-    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Metric getDefaultInstance() {
+    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Metric getDefaultInstance() {
       return DEFAULT_INSTANCE;
     }
 
@@ -13524,7 +13524,7 @@ public com.google.protobuf.Parser getParserForType() {
     }
 
     @java.lang.Override
-    public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Metric getDefaultInstanceForType() {
+    public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Metric getDefaultInstanceForType() {
       return DEFAULT_INSTANCE;
     }
 
@@ -13577,17 +13577,17 @@ public interface MetricFamilyOrBuilder extends
      * optional .io.prometheus.client.MetricType type = 3;
      * @return The type.
      */
-    io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.MetricType getType();
+    io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.MetricType getType();
 
     /**
      * repeated .io.prometheus.client.Metric metric = 4;
      */
-    java.util.List 
+    java.util.List 
         getMetricList();
     /**
      * repeated .io.prometheus.client.Metric metric = 4;
      */
-    io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Metric getMetric(int index);
+    io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Metric getMetric(int index);
     /**
      * repeated .io.prometheus.client.Metric metric = 4;
      */
@@ -13595,12 +13595,12 @@ public interface MetricFamilyOrBuilder extends
     /**
      * repeated .io.prometheus.client.Metric metric = 4;
      */
-    java.util.List 
+    java.util.List 
         getMetricOrBuilderList();
     /**
      * repeated .io.prometheus.client.Metric metric = 4;
      */
-    io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.MetricOrBuilder getMetricOrBuilder(
+    io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.MetricOrBuilder getMetricOrBuilder(
         int index);
 
     /**
@@ -13632,10 +13632,10 @@ public static final class MetricFamily extends
       com.google.protobuf.RuntimeVersion.validateProtobufGencodeVersion(
         com.google.protobuf.RuntimeVersion.RuntimeDomain.PUBLIC,
         /* major= */ 4,
-        /* minor= */ 31,
-        /* patch= */ 1,
+        /* minor= */ 33,
+        /* patch= */ 5,
         /* suffix= */ "",
-        MetricFamily.class.getName());
+        "MetricFamily");
     }
     // Use MetricFamily.newBuilder() to construct.
     private MetricFamily(com.google.protobuf.GeneratedMessage.Builder builder) {
@@ -13651,15 +13651,15 @@ private MetricFamily() {
 
     public static final com.google.protobuf.Descriptors.Descriptor
         getDescriptor() {
-      return io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.internal_static_io_prometheus_client_MetricFamily_descriptor;
+      return io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.internal_static_io_prometheus_client_MetricFamily_descriptor;
     }
 
     @java.lang.Override
     protected com.google.protobuf.GeneratedMessage.FieldAccessorTable
         internalGetFieldAccessorTable() {
-      return io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.internal_static_io_prometheus_client_MetricFamily_fieldAccessorTable
+      return io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.internal_static_io_prometheus_client_MetricFamily_fieldAccessorTable
           .ensureFieldAccessorsInitialized(
-              io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.MetricFamily.class, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.MetricFamily.Builder.class);
+              io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.MetricFamily.class, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.MetricFamily.Builder.class);
     }
 
     private int bitField0_;
@@ -13774,26 +13774,26 @@ public java.lang.String getHelp() {
      * optional .io.prometheus.client.MetricType type = 3;
      * @return The type.
      */
-    @java.lang.Override public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.MetricType getType() {
-      io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.MetricType result = io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.MetricType.forNumber(type_);
-      return result == null ? io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.MetricType.COUNTER : result;
+    @java.lang.Override public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.MetricType getType() {
+      io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.MetricType result = io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.MetricType.forNumber(type_);
+      return result == null ? io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.MetricType.COUNTER : result;
     }
 
     public static final int METRIC_FIELD_NUMBER = 4;
     @SuppressWarnings("serial")
-    private java.util.List metric_;
+    private java.util.List metric_;
     /**
      * repeated .io.prometheus.client.Metric metric = 4;
      */
     @java.lang.Override
-    public java.util.List getMetricList() {
+    public java.util.List getMetricList() {
       return metric_;
     }
     /**
      * repeated .io.prometheus.client.Metric metric = 4;
      */
     @java.lang.Override
-    public java.util.List 
+    public java.util.List 
         getMetricOrBuilderList() {
       return metric_;
     }
@@ -13808,14 +13808,14 @@ public int getMetricCount() {
      * repeated .io.prometheus.client.Metric metric = 4;
      */
     @java.lang.Override
-    public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Metric getMetric(int index) {
+    public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Metric getMetric(int index) {
       return metric_.get(index);
     }
     /**
      * repeated .io.prometheus.client.Metric metric = 4;
      */
     @java.lang.Override
-    public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.MetricOrBuilder getMetricOrBuilder(
+    public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.MetricOrBuilder getMetricOrBuilder(
         int index) {
       return metric_.get(index);
     }
@@ -13934,10 +13934,10 @@ public boolean equals(final java.lang.Object obj) {
       if (obj == this) {
        return true;
       }
-      if (!(obj instanceof io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.MetricFamily)) {
+      if (!(obj instanceof io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.MetricFamily)) {
         return super.equals(obj);
       }
-      io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.MetricFamily other = (io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.MetricFamily) obj;
+      io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.MetricFamily other = (io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.MetricFamily) obj;
 
       if (hasName() != other.hasName()) return false;
       if (hasName()) {
@@ -13996,44 +13996,44 @@ public int hashCode() {
       return hash;
     }
 
-    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.MetricFamily parseFrom(
+    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.MetricFamily parseFrom(
         java.nio.ByteBuffer data)
         throws com.google.protobuf.InvalidProtocolBufferException {
       return PARSER.parseFrom(data);
     }
-    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.MetricFamily parseFrom(
+    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.MetricFamily parseFrom(
         java.nio.ByteBuffer data,
         com.google.protobuf.ExtensionRegistryLite extensionRegistry)
         throws com.google.protobuf.InvalidProtocolBufferException {
       return PARSER.parseFrom(data, extensionRegistry);
     }
-    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.MetricFamily parseFrom(
+    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.MetricFamily parseFrom(
         com.google.protobuf.ByteString data)
         throws com.google.protobuf.InvalidProtocolBufferException {
       return PARSER.parseFrom(data);
     }
-    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.MetricFamily parseFrom(
+    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.MetricFamily parseFrom(
         com.google.protobuf.ByteString data,
         com.google.protobuf.ExtensionRegistryLite extensionRegistry)
         throws com.google.protobuf.InvalidProtocolBufferException {
       return PARSER.parseFrom(data, extensionRegistry);
     }
-    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.MetricFamily parseFrom(byte[] data)
+    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.MetricFamily parseFrom(byte[] data)
         throws com.google.protobuf.InvalidProtocolBufferException {
       return PARSER.parseFrom(data);
     }
-    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.MetricFamily parseFrom(
+    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.MetricFamily parseFrom(
         byte[] data,
         com.google.protobuf.ExtensionRegistryLite extensionRegistry)
         throws com.google.protobuf.InvalidProtocolBufferException {
       return PARSER.parseFrom(data, extensionRegistry);
     }
-    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.MetricFamily parseFrom(java.io.InputStream input)
+    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.MetricFamily parseFrom(java.io.InputStream input)
         throws java.io.IOException {
       return com.google.protobuf.GeneratedMessage
           .parseWithIOException(PARSER, input);
     }
-    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.MetricFamily parseFrom(
+    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.MetricFamily parseFrom(
         java.io.InputStream input,
         com.google.protobuf.ExtensionRegistryLite extensionRegistry)
         throws java.io.IOException {
@@ -14041,26 +14041,26 @@ public static io.prometheus.metrics.expositionformats.generated.com_google_proto
           .parseWithIOException(PARSER, input, extensionRegistry);
     }
 
-    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.MetricFamily parseDelimitedFrom(java.io.InputStream input)
+    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.MetricFamily parseDelimitedFrom(java.io.InputStream input)
         throws java.io.IOException {
       return com.google.protobuf.GeneratedMessage
           .parseDelimitedWithIOException(PARSER, input);
     }
 
-    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.MetricFamily parseDelimitedFrom(
+    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.MetricFamily parseDelimitedFrom(
         java.io.InputStream input,
         com.google.protobuf.ExtensionRegistryLite extensionRegistry)
         throws java.io.IOException {
       return com.google.protobuf.GeneratedMessage
           .parseDelimitedWithIOException(PARSER, input, extensionRegistry);
     }
-    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.MetricFamily parseFrom(
+    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.MetricFamily parseFrom(
         com.google.protobuf.CodedInputStream input)
         throws java.io.IOException {
       return com.google.protobuf.GeneratedMessage
           .parseWithIOException(PARSER, input);
     }
-    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.MetricFamily parseFrom(
+    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.MetricFamily parseFrom(
         com.google.protobuf.CodedInputStream input,
         com.google.protobuf.ExtensionRegistryLite extensionRegistry)
         throws java.io.IOException {
@@ -14073,7 +14073,7 @@ public static io.prometheus.metrics.expositionformats.generated.com_google_proto
     public static Builder newBuilder() {
       return DEFAULT_INSTANCE.toBuilder();
     }
-    public static Builder newBuilder(io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.MetricFamily prototype) {
+    public static Builder newBuilder(io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.MetricFamily prototype) {
       return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype);
     }
     @java.lang.Override
@@ -14094,21 +14094,21 @@ protected Builder newBuilderForType(
     public static final class Builder extends
         com.google.protobuf.GeneratedMessage.Builder implements
         // @@protoc_insertion_point(builder_implements:io.prometheus.client.MetricFamily)
-        io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.MetricFamilyOrBuilder {
+        io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.MetricFamilyOrBuilder {
       public static final com.google.protobuf.Descriptors.Descriptor
           getDescriptor() {
-        return io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.internal_static_io_prometheus_client_MetricFamily_descriptor;
+        return io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.internal_static_io_prometheus_client_MetricFamily_descriptor;
       }
 
       @java.lang.Override
       protected com.google.protobuf.GeneratedMessage.FieldAccessorTable
           internalGetFieldAccessorTable() {
-        return io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.internal_static_io_prometheus_client_MetricFamily_fieldAccessorTable
+        return io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.internal_static_io_prometheus_client_MetricFamily_fieldAccessorTable
             .ensureFieldAccessorsInitialized(
-                io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.MetricFamily.class, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.MetricFamily.Builder.class);
+                io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.MetricFamily.class, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.MetricFamily.Builder.class);
       }
 
-      // Construct using io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.MetricFamily.newBuilder()
+      // Construct using io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.MetricFamily.newBuilder()
       private Builder() {
 
       }
@@ -14139,17 +14139,17 @@ public Builder clear() {
       @java.lang.Override
       public com.google.protobuf.Descriptors.Descriptor
           getDescriptorForType() {
-        return io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.internal_static_io_prometheus_client_MetricFamily_descriptor;
+        return io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.internal_static_io_prometheus_client_MetricFamily_descriptor;
       }
 
       @java.lang.Override
-      public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.MetricFamily getDefaultInstanceForType() {
-        return io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.MetricFamily.getDefaultInstance();
+      public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.MetricFamily getDefaultInstanceForType() {
+        return io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.MetricFamily.getDefaultInstance();
       }
 
       @java.lang.Override
-      public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.MetricFamily build() {
-        io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.MetricFamily result = buildPartial();
+      public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.MetricFamily build() {
+        io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.MetricFamily result = buildPartial();
         if (!result.isInitialized()) {
           throw newUninitializedMessageException(result);
         }
@@ -14157,15 +14157,15 @@ public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_3
       }
 
       @java.lang.Override
-      public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.MetricFamily buildPartial() {
-        io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.MetricFamily result = new io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.MetricFamily(this);
+      public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.MetricFamily buildPartial() {
+        io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.MetricFamily result = new io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.MetricFamily(this);
         buildPartialRepeatedFields(result);
         if (bitField0_ != 0) { buildPartial0(result); }
         onBuilt();
         return result;
       }
 
-      private void buildPartialRepeatedFields(io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.MetricFamily result) {
+      private void buildPartialRepeatedFields(io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.MetricFamily result) {
         if (metricBuilder_ == null) {
           if (((bitField0_ & 0x00000008) != 0)) {
             metric_ = java.util.Collections.unmodifiableList(metric_);
@@ -14177,7 +14177,7 @@ private void buildPartialRepeatedFields(io.prometheus.metrics.expositionformats.
         }
       }
 
-      private void buildPartial0(io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.MetricFamily result) {
+      private void buildPartial0(io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.MetricFamily result) {
         int from_bitField0_ = bitField0_;
         int to_bitField0_ = 0;
         if (((from_bitField0_ & 0x00000001) != 0)) {
@@ -14201,16 +14201,16 @@ private void buildPartial0(io.prometheus.metrics.expositionformats.generated.com
 
       @java.lang.Override
       public Builder mergeFrom(com.google.protobuf.Message other) {
-        if (other instanceof io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.MetricFamily) {
-          return mergeFrom((io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.MetricFamily)other);
+        if (other instanceof io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.MetricFamily) {
+          return mergeFrom((io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.MetricFamily)other);
         } else {
           super.mergeFrom(other);
           return this;
         }
       }
 
-      public Builder mergeFrom(io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.MetricFamily other) {
-        if (other == io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.MetricFamily.getDefaultInstance()) return this;
+      public Builder mergeFrom(io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.MetricFamily other) {
+        if (other == io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.MetricFamily.getDefaultInstance()) return this;
         if (other.hasName()) {
           name_ = other.name_;
           bitField0_ |= 0x00000001;
@@ -14293,8 +14293,8 @@ public Builder mergeFrom(
               } // case 18
               case 24: {
                 int tmpRaw = input.readEnum();
-                io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.MetricType tmpValue =
-                    io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.MetricType.forNumber(tmpRaw);
+                io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.MetricType tmpValue =
+                    io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.MetricType.forNumber(tmpRaw);
                 if (tmpValue == null) {
                   mergeUnknownVarintField(3, tmpRaw);
                 } else {
@@ -14304,9 +14304,9 @@ public Builder mergeFrom(
                 break;
               } // case 24
               case 34: {
-                io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Metric m =
+                io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Metric m =
                     input.readMessage(
-                        io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Metric.parser(),
+                        io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Metric.parser(),
                         extensionRegistry);
                 if (metricBuilder_ == null) {
                   ensureMetricIsMutable();
@@ -14511,16 +14511,16 @@ public Builder setHelpBytes(
        * @return The type.
        */
       @java.lang.Override
-      public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.MetricType getType() {
-        io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.MetricType result = io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.MetricType.forNumber(type_);
-        return result == null ? io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.MetricType.COUNTER : result;
+      public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.MetricType getType() {
+        io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.MetricType result = io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.MetricType.forNumber(type_);
+        return result == null ? io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.MetricType.COUNTER : result;
       }
       /**
        * optional .io.prometheus.client.MetricType type = 3;
        * @param value The type to set.
        * @return This builder for chaining.
        */
-      public Builder setType(io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.MetricType value) {
+      public Builder setType(io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.MetricType value) {
         if (value == null) { throw new NullPointerException(); }
         bitField0_ |= 0x00000004;
         type_ = value.getNumber();
@@ -14538,22 +14538,22 @@ public Builder clearType() {
         return this;
       }
 
-      private java.util.List metric_ =
+      private java.util.List metric_ =
         java.util.Collections.emptyList();
       private void ensureMetricIsMutable() {
         if (!((bitField0_ & 0x00000008) != 0)) {
-          metric_ = new java.util.ArrayList(metric_);
+          metric_ = new java.util.ArrayList(metric_);
           bitField0_ |= 0x00000008;
          }
       }
 
       private com.google.protobuf.RepeatedFieldBuilder<
-          io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Metric, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Metric.Builder, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.MetricOrBuilder> metricBuilder_;
+          io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Metric, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Metric.Builder, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.MetricOrBuilder> metricBuilder_;
 
       /**
        * repeated .io.prometheus.client.Metric metric = 4;
        */
-      public java.util.List getMetricList() {
+      public java.util.List getMetricList() {
         if (metricBuilder_ == null) {
           return java.util.Collections.unmodifiableList(metric_);
         } else {
@@ -14573,7 +14573,7 @@ public int getMetricCount() {
       /**
        * repeated .io.prometheus.client.Metric metric = 4;
        */
-      public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Metric getMetric(int index) {
+      public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Metric getMetric(int index) {
         if (metricBuilder_ == null) {
           return metric_.get(index);
         } else {
@@ -14584,7 +14584,7 @@ public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_3
        * repeated .io.prometheus.client.Metric metric = 4;
        */
       public Builder setMetric(
-          int index, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Metric value) {
+          int index, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Metric value) {
         if (metricBuilder_ == null) {
           if (value == null) {
             throw new NullPointerException();
@@ -14601,7 +14601,7 @@ public Builder setMetric(
        * repeated .io.prometheus.client.Metric metric = 4;
        */
       public Builder setMetric(
-          int index, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Metric.Builder builderForValue) {
+          int index, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Metric.Builder builderForValue) {
         if (metricBuilder_ == null) {
           ensureMetricIsMutable();
           metric_.set(index, builderForValue.build());
@@ -14614,7 +14614,7 @@ public Builder setMetric(
       /**
        * repeated .io.prometheus.client.Metric metric = 4;
        */
-      public Builder addMetric(io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Metric value) {
+      public Builder addMetric(io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Metric value) {
         if (metricBuilder_ == null) {
           if (value == null) {
             throw new NullPointerException();
@@ -14631,7 +14631,7 @@ public Builder addMetric(io.prometheus.metrics.expositionformats.generated.com_g
        * repeated .io.prometheus.client.Metric metric = 4;
        */
       public Builder addMetric(
-          int index, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Metric value) {
+          int index, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Metric value) {
         if (metricBuilder_ == null) {
           if (value == null) {
             throw new NullPointerException();
@@ -14648,7 +14648,7 @@ public Builder addMetric(
        * repeated .io.prometheus.client.Metric metric = 4;
        */
       public Builder addMetric(
-          io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Metric.Builder builderForValue) {
+          io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Metric.Builder builderForValue) {
         if (metricBuilder_ == null) {
           ensureMetricIsMutable();
           metric_.add(builderForValue.build());
@@ -14662,7 +14662,7 @@ public Builder addMetric(
        * repeated .io.prometheus.client.Metric metric = 4;
        */
       public Builder addMetric(
-          int index, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Metric.Builder builderForValue) {
+          int index, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Metric.Builder builderForValue) {
         if (metricBuilder_ == null) {
           ensureMetricIsMutable();
           metric_.add(index, builderForValue.build());
@@ -14676,7 +14676,7 @@ public Builder addMetric(
        * repeated .io.prometheus.client.Metric metric = 4;
        */
       public Builder addAllMetric(
-          java.lang.Iterable values) {
+          java.lang.Iterable values) {
         if (metricBuilder_ == null) {
           ensureMetricIsMutable();
           com.google.protobuf.AbstractMessageLite.Builder.addAll(
@@ -14716,14 +14716,14 @@ public Builder removeMetric(int index) {
       /**
        * repeated .io.prometheus.client.Metric metric = 4;
        */
-      public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Metric.Builder getMetricBuilder(
+      public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Metric.Builder getMetricBuilder(
           int index) {
         return internalGetMetricFieldBuilder().getBuilder(index);
       }
       /**
        * repeated .io.prometheus.client.Metric metric = 4;
        */
-      public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.MetricOrBuilder getMetricOrBuilder(
+      public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.MetricOrBuilder getMetricOrBuilder(
           int index) {
         if (metricBuilder_ == null) {
           return metric_.get(index);  } else {
@@ -14733,7 +14733,7 @@ public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_3
       /**
        * repeated .io.prometheus.client.Metric metric = 4;
        */
-      public java.util.List 
+      public java.util.List 
            getMetricOrBuilderList() {
         if (metricBuilder_ != null) {
           return metricBuilder_.getMessageOrBuilderList();
@@ -14744,31 +14744,31 @@ public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_3
       /**
        * repeated .io.prometheus.client.Metric metric = 4;
        */
-      public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Metric.Builder addMetricBuilder() {
+      public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Metric.Builder addMetricBuilder() {
         return internalGetMetricFieldBuilder().addBuilder(
-            io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Metric.getDefaultInstance());
+            io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Metric.getDefaultInstance());
       }
       /**
        * repeated .io.prometheus.client.Metric metric = 4;
        */
-      public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Metric.Builder addMetricBuilder(
+      public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Metric.Builder addMetricBuilder(
           int index) {
         return internalGetMetricFieldBuilder().addBuilder(
-            index, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Metric.getDefaultInstance());
+            index, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Metric.getDefaultInstance());
       }
       /**
        * repeated .io.prometheus.client.Metric metric = 4;
        */
-      public java.util.List 
+      public java.util.List 
            getMetricBuilderList() {
         return internalGetMetricFieldBuilder().getBuilderList();
       }
       private com.google.protobuf.RepeatedFieldBuilder<
-          io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Metric, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Metric.Builder, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.MetricOrBuilder> 
+          io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Metric, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Metric.Builder, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.MetricOrBuilder> 
           internalGetMetricFieldBuilder() {
         if (metricBuilder_ == null) {
           metricBuilder_ = new com.google.protobuf.RepeatedFieldBuilder<
-              io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Metric, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.Metric.Builder, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.MetricOrBuilder>(
+              io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Metric, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.Metric.Builder, io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.MetricOrBuilder>(
                   metric_,
                   ((bitField0_ & 0x00000008) != 0),
                   getParentForChildren(),
@@ -14862,12 +14862,12 @@ public Builder setUnitBytes(
     }
 
     // @@protoc_insertion_point(class_scope:io.prometheus.client.MetricFamily)
-    private static final io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.MetricFamily DEFAULT_INSTANCE;
+    private static final io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.MetricFamily DEFAULT_INSTANCE;
     static {
-      DEFAULT_INSTANCE = new io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.MetricFamily();
+      DEFAULT_INSTANCE = new io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.MetricFamily();
     }
 
-    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.MetricFamily getDefaultInstance() {
+    public static io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.MetricFamily getDefaultInstance() {
       return DEFAULT_INSTANCE;
     }
 
@@ -14903,7 +14903,7 @@ public com.google.protobuf.Parser getParserForType() {
     }
 
     @java.lang.Override
-    public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics.MetricFamily getDefaultInstanceForType() {
+    public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics.MetricFamily getDefaultInstanceForType() {
       return DEFAULT_INSTANCE;
     }
 
@@ -15028,7 +15028,7 @@ public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_3
       "\002\022\013\n\007UNTYPED\020\003\022\r\n\tHISTOGRAM\020\004\022\023\n\017GAUGE_H" +
       "ISTOGRAM\020\005B\212\001\nLio.prometheus.metrics.exp" +
       "ositionformats.generated.com_google_prot" +
-      "obuf_4_31_1Z:github.com/prometheus/clien" +
+      "obuf_4_33_5Z:github.com/prometheus/clien" +
       "t_model/go;io_prometheus_client"
     };
     descriptor = com.google.protobuf.Descriptors.FileDescriptor
@@ -15037,73 +15037,73 @@ public io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_3
           com.google.protobuf.TimestampProto.getDescriptor(),
         });
     internal_static_io_prometheus_client_LabelPair_descriptor =
-      getDescriptor().getMessageTypes().get(0);
+      getDescriptor().getMessageType(0);
     internal_static_io_prometheus_client_LabelPair_fieldAccessorTable = new
       com.google.protobuf.GeneratedMessage.FieldAccessorTable(
         internal_static_io_prometheus_client_LabelPair_descriptor,
         new java.lang.String[] { "Name", "Value", });
     internal_static_io_prometheus_client_Gauge_descriptor =
-      getDescriptor().getMessageTypes().get(1);
+      getDescriptor().getMessageType(1);
     internal_static_io_prometheus_client_Gauge_fieldAccessorTable = new
       com.google.protobuf.GeneratedMessage.FieldAccessorTable(
         internal_static_io_prometheus_client_Gauge_descriptor,
         new java.lang.String[] { "Value", });
     internal_static_io_prometheus_client_Counter_descriptor =
-      getDescriptor().getMessageTypes().get(2);
+      getDescriptor().getMessageType(2);
     internal_static_io_prometheus_client_Counter_fieldAccessorTable = new
       com.google.protobuf.GeneratedMessage.FieldAccessorTable(
         internal_static_io_prometheus_client_Counter_descriptor,
         new java.lang.String[] { "Value", "Exemplar", "CreatedTimestamp", });
     internal_static_io_prometheus_client_Quantile_descriptor =
-      getDescriptor().getMessageTypes().get(3);
+      getDescriptor().getMessageType(3);
     internal_static_io_prometheus_client_Quantile_fieldAccessorTable = new
       com.google.protobuf.GeneratedMessage.FieldAccessorTable(
         internal_static_io_prometheus_client_Quantile_descriptor,
         new java.lang.String[] { "Quantile", "Value", });
     internal_static_io_prometheus_client_Summary_descriptor =
-      getDescriptor().getMessageTypes().get(4);
+      getDescriptor().getMessageType(4);
     internal_static_io_prometheus_client_Summary_fieldAccessorTable = new
       com.google.protobuf.GeneratedMessage.FieldAccessorTable(
         internal_static_io_prometheus_client_Summary_descriptor,
         new java.lang.String[] { "SampleCount", "SampleSum", "Quantile", "CreatedTimestamp", });
     internal_static_io_prometheus_client_Untyped_descriptor =
-      getDescriptor().getMessageTypes().get(5);
+      getDescriptor().getMessageType(5);
     internal_static_io_prometheus_client_Untyped_fieldAccessorTable = new
       com.google.protobuf.GeneratedMessage.FieldAccessorTable(
         internal_static_io_prometheus_client_Untyped_descriptor,
         new java.lang.String[] { "Value", });
     internal_static_io_prometheus_client_Histogram_descriptor =
-      getDescriptor().getMessageTypes().get(6);
+      getDescriptor().getMessageType(6);
     internal_static_io_prometheus_client_Histogram_fieldAccessorTable = new
       com.google.protobuf.GeneratedMessage.FieldAccessorTable(
         internal_static_io_prometheus_client_Histogram_descriptor,
         new java.lang.String[] { "SampleCount", "SampleCountFloat", "SampleSum", "Bucket", "CreatedTimestamp", "Schema", "ZeroThreshold", "ZeroCount", "ZeroCountFloat", "NegativeSpan", "NegativeDelta", "NegativeCount", "PositiveSpan", "PositiveDelta", "PositiveCount", "Exemplars", });
     internal_static_io_prometheus_client_Bucket_descriptor =
-      getDescriptor().getMessageTypes().get(7);
+      getDescriptor().getMessageType(7);
     internal_static_io_prometheus_client_Bucket_fieldAccessorTable = new
       com.google.protobuf.GeneratedMessage.FieldAccessorTable(
         internal_static_io_prometheus_client_Bucket_descriptor,
         new java.lang.String[] { "CumulativeCount", "CumulativeCountFloat", "UpperBound", "Exemplar", });
     internal_static_io_prometheus_client_BucketSpan_descriptor =
-      getDescriptor().getMessageTypes().get(8);
+      getDescriptor().getMessageType(8);
     internal_static_io_prometheus_client_BucketSpan_fieldAccessorTable = new
       com.google.protobuf.GeneratedMessage.FieldAccessorTable(
         internal_static_io_prometheus_client_BucketSpan_descriptor,
         new java.lang.String[] { "Offset", "Length", });
     internal_static_io_prometheus_client_Exemplar_descriptor =
-      getDescriptor().getMessageTypes().get(9);
+      getDescriptor().getMessageType(9);
     internal_static_io_prometheus_client_Exemplar_fieldAccessorTable = new
       com.google.protobuf.GeneratedMessage.FieldAccessorTable(
         internal_static_io_prometheus_client_Exemplar_descriptor,
         new java.lang.String[] { "Label", "Value", "Timestamp", });
     internal_static_io_prometheus_client_Metric_descriptor =
-      getDescriptor().getMessageTypes().get(10);
+      getDescriptor().getMessageType(10);
     internal_static_io_prometheus_client_Metric_fieldAccessorTable = new
       com.google.protobuf.GeneratedMessage.FieldAccessorTable(
         internal_static_io_prometheus_client_Metric_descriptor,
         new java.lang.String[] { "Label", "Gauge", "Counter", "Summary", "Untyped", "Histogram", "TimestampMs", });
     internal_static_io_prometheus_client_MetricFamily_descriptor =
-      getDescriptor().getMessageTypes().get(11);
+      getDescriptor().getMessageType(11);
     internal_static_io_prometheus_client_MetricFamily_fieldAccessorTable = new
       com.google.protobuf.GeneratedMessage.FieldAccessorTable(
         internal_static_io_prometheus_client_MetricFamily_descriptor,
diff --git a/prometheus-metrics-exposition-formats/src/main/java/io/prometheus/metrics/expositionformats/generated/Metrics.java b/prometheus-metrics-exposition-formats/src/main/java/io/prometheus/metrics/expositionformats/generated/Metrics.java
new file mode 100644
index 000000000..605aa4cd7
--- /dev/null
+++ b/prometheus-metrics-exposition-formats/src/main/java/io/prometheus/metrics/expositionformats/generated/Metrics.java
@@ -0,0 +1,6 @@
+package io.prometheus.metrics.expositionformats.generated;
+
+public final class Metrics
+    extends io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_33_5.Metrics {
+  private Metrics() {}
+}
diff --git a/prometheus-metrics-exposition-formats/src/main/java/io/prometheus/metrics/expositionformats/internal/PrometheusProtobufWriterImpl.java b/prometheus-metrics-exposition-formats/src/main/java/io/prometheus/metrics/expositionformats/internal/PrometheusProtobufWriterImpl.java
index e3e450a28..d714fb5cd 100644
--- a/prometheus-metrics-exposition-formats/src/main/java/io/prometheus/metrics/expositionformats/internal/PrometheusProtobufWriterImpl.java
+++ b/prometheus-metrics-exposition-formats/src/main/java/io/prometheus/metrics/expositionformats/internal/PrometheusProtobufWriterImpl.java
@@ -1,10 +1,13 @@
 package io.prometheus.metrics.expositionformats.internal;
 
 import static io.prometheus.metrics.expositionformats.internal.ProtobufUtil.timestampFromMillis;
+import static io.prometheus.metrics.model.snapshots.SnapshotEscaper.getSnapshotLabelName;
 
 import com.google.protobuf.TextFormat;
+import io.prometheus.metrics.config.EscapingScheme;
 import io.prometheus.metrics.expositionformats.ExpositionFormatWriter;
-import io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics;
+import io.prometheus.metrics.expositionformats.TextFormatUtil;
+import io.prometheus.metrics.expositionformats.generated.Metrics;
 import io.prometheus.metrics.model.snapshots.ClassicHistogramBuckets;
 import io.prometheus.metrics.model.snapshots.CounterSnapshot;
 import io.prometheus.metrics.model.snapshots.CounterSnapshot.CounterDataPointSnapshot;
@@ -19,16 +22,19 @@
 import io.prometheus.metrics.model.snapshots.MetricSnapshots;
 import io.prometheus.metrics.model.snapshots.NativeHistogramBuckets;
 import io.prometheus.metrics.model.snapshots.Quantiles;
+import io.prometheus.metrics.model.snapshots.SnapshotEscaper;
 import io.prometheus.metrics.model.snapshots.StateSetSnapshot;
 import io.prometheus.metrics.model.snapshots.SummarySnapshot;
 import io.prometheus.metrics.model.snapshots.UnknownSnapshot;
 import java.io.IOException;
 import java.io.OutputStream;
+import javax.annotation.Nullable;
 
+@SuppressWarnings("NonCanonicalType")
 public class PrometheusProtobufWriterImpl implements ExpositionFormatWriter {
 
   @Override
-  public boolean accepts(String acceptHeader) {
+  public boolean accepts(@Nullable String acceptHeader) {
     throw new IllegalStateException("use PrometheusProtobufWriter instead");
   }
 
@@ -38,102 +44,120 @@ public String getContentType() {
   }
 
   @Override
-  public String toDebugString(MetricSnapshots metricSnapshots) {
+  public String toDebugString(MetricSnapshots metricSnapshots, EscapingScheme escapingScheme) {
+    MetricSnapshots merged = TextFormatUtil.mergeDuplicates(metricSnapshots);
     StringBuilder stringBuilder = new StringBuilder();
-    for (MetricSnapshot snapshot : metricSnapshots) {
+    for (MetricSnapshot s : merged) {
+      MetricSnapshot snapshot = SnapshotEscaper.escapeMetricSnapshot(s, escapingScheme);
       if (!snapshot.getDataPoints().isEmpty()) {
-        stringBuilder.append(TextFormat.printer().printToString(convert(snapshot)));
+        stringBuilder.append(TextFormat.printer().printToString(convert(snapshot, escapingScheme)));
       }
     }
     return stringBuilder.toString();
   }
 
   @Override
-  public void write(OutputStream out, MetricSnapshots metricSnapshots) throws IOException {
-    for (MetricSnapshot snapshot : metricSnapshots) {
+  public void write(
+      OutputStream out, MetricSnapshots metricSnapshots, EscapingScheme escapingScheme)
+      throws IOException {
+    MetricSnapshots merged = TextFormatUtil.mergeDuplicates(metricSnapshots);
+    for (MetricSnapshot s : merged) {
+      MetricSnapshot snapshot = SnapshotEscaper.escapeMetricSnapshot(s, escapingScheme);
       if (!snapshot.getDataPoints().isEmpty()) {
-        convert(snapshot).writeDelimitedTo(out);
+        convert(snapshot, escapingScheme).writeDelimitedTo(out);
       }
     }
   }
 
-  public Metrics.MetricFamily convert(MetricSnapshot snapshot) {
+  public Metrics.MetricFamily convert(MetricSnapshot snapshot, EscapingScheme scheme) {
     Metrics.MetricFamily.Builder builder = Metrics.MetricFamily.newBuilder();
     if (snapshot instanceof CounterSnapshot) {
       for (CounterDataPointSnapshot data : ((CounterSnapshot) snapshot).getDataPoints()) {
-        builder.addMetric(convert(data));
+        builder.addMetric(convert(data, scheme));
       }
-      setMetadataUnlessEmpty(builder, snapshot.getMetadata(), "_total", Metrics.MetricType.COUNTER);
+      setMetadataUnlessEmpty(
+          builder, snapshot.getMetadata(), "_total", Metrics.MetricType.COUNTER, scheme);
     } else if (snapshot instanceof GaugeSnapshot) {
       for (GaugeSnapshot.GaugeDataPointSnapshot data : ((GaugeSnapshot) snapshot).getDataPoints()) {
-        builder.addMetric(convert(data));
+        builder.addMetric(convert(data, scheme));
       }
-      setMetadataUnlessEmpty(builder, snapshot.getMetadata(), null, Metrics.MetricType.GAUGE);
+      setMetadataUnlessEmpty(
+          builder, snapshot.getMetadata(), null, Metrics.MetricType.GAUGE, scheme);
     } else if (snapshot instanceof HistogramSnapshot) {
       HistogramSnapshot histogram = (HistogramSnapshot) snapshot;
       for (HistogramSnapshot.HistogramDataPointSnapshot data : histogram.getDataPoints()) {
-        builder.addMetric(convert(data));
+        builder.addMetric(convert(data, scheme));
       }
       Metrics.MetricType type =
           histogram.isGaugeHistogram()
               ? Metrics.MetricType.GAUGE_HISTOGRAM
               : Metrics.MetricType.HISTOGRAM;
-      setMetadataUnlessEmpty(builder, snapshot.getMetadata(), null, type);
+      setMetadataUnlessEmpty(builder, snapshot.getMetadata(), null, type, scheme);
     } else if (snapshot instanceof SummarySnapshot) {
       for (SummarySnapshot.SummaryDataPointSnapshot data :
           ((SummarySnapshot) snapshot).getDataPoints()) {
         if (data.hasCount() || data.hasSum() || data.getQuantiles().size() > 0) {
-          builder.addMetric(convert(data));
+          builder.addMetric(convert(data, scheme));
         }
       }
-      setMetadataUnlessEmpty(builder, snapshot.getMetadata(), null, Metrics.MetricType.SUMMARY);
+      setMetadataUnlessEmpty(
+          builder, snapshot.getMetadata(), null, Metrics.MetricType.SUMMARY, scheme);
     } else if (snapshot instanceof InfoSnapshot) {
       for (InfoSnapshot.InfoDataPointSnapshot data : ((InfoSnapshot) snapshot).getDataPoints()) {
-        builder.addMetric(convert(data));
+        builder.addMetric(convert(data, scheme));
       }
-      setMetadataUnlessEmpty(builder, snapshot.getMetadata(), "_info", Metrics.MetricType.GAUGE);
+      setMetadataUnlessEmpty(
+          builder, snapshot.getMetadata(), "_info", Metrics.MetricType.GAUGE, scheme);
     } else if (snapshot instanceof StateSetSnapshot) {
       for (StateSetSnapshot.StateSetDataPointSnapshot data :
           ((StateSetSnapshot) snapshot).getDataPoints()) {
         for (int i = 0; i < data.size(); i++) {
-          builder.addMetric(convert(data, snapshot.getMetadata().getPrometheusName(), i));
+          builder.addMetric(convert(data, snapshot.getMetadata().getPrometheusName(), i, scheme));
         }
       }
-      setMetadataUnlessEmpty(builder, snapshot.getMetadata(), null, Metrics.MetricType.GAUGE);
+      setMetadataUnlessEmpty(
+          builder, snapshot.getMetadata(), null, Metrics.MetricType.GAUGE, scheme);
     } else if (snapshot instanceof UnknownSnapshot) {
       for (UnknownSnapshot.UnknownDataPointSnapshot data :
           ((UnknownSnapshot) snapshot).getDataPoints()) {
-        builder.addMetric(convert(data));
+        builder.addMetric(convert(data, scheme));
       }
-      setMetadataUnlessEmpty(builder, snapshot.getMetadata(), null, Metrics.MetricType.UNTYPED);
+      setMetadataUnlessEmpty(
+          builder, snapshot.getMetadata(), null, Metrics.MetricType.UNTYPED, scheme);
     }
     return builder.build();
   }
 
-  private Metrics.Metric.Builder convert(CounterDataPointSnapshot data) {
+  private Metrics.Metric.Builder convert(CounterDataPointSnapshot data, EscapingScheme scheme) {
     Metrics.Counter.Builder counterBuilder = Metrics.Counter.newBuilder();
     counterBuilder.setValue(data.getValue());
     if (data.getExemplar() != null) {
-      counterBuilder.setExemplar(convert(data.getExemplar()));
+      counterBuilder.setExemplar(convert(data.getExemplar(), scheme));
+    }
+    if (data.hasCreatedTimestamp()) {
+      counterBuilder.setCreatedTimestamp(
+          ProtobufUtil.timestampFromMillis(data.getCreatedTimestampMillis()));
     }
     Metrics.Metric.Builder metricBuilder = Metrics.Metric.newBuilder();
-    addLabels(metricBuilder, data.getLabels());
+    addLabels(metricBuilder, data.getLabels(), scheme);
     metricBuilder.setCounter(counterBuilder.build());
     setScrapeTimestamp(metricBuilder, data);
     return metricBuilder;
   }
 
-  private Metrics.Metric.Builder convert(GaugeSnapshot.GaugeDataPointSnapshot data) {
+  private Metrics.Metric.Builder convert(
+      GaugeSnapshot.GaugeDataPointSnapshot data, EscapingScheme scheme) {
     Metrics.Gauge.Builder gaugeBuilder = Metrics.Gauge.newBuilder();
     gaugeBuilder.setValue(data.getValue());
     Metrics.Metric.Builder metricBuilder = Metrics.Metric.newBuilder();
-    addLabels(metricBuilder, data.getLabels());
+    addLabels(metricBuilder, data.getLabels(), scheme);
     metricBuilder.setGauge(gaugeBuilder);
     setScrapeTimestamp(metricBuilder, data);
     return metricBuilder;
   }
 
-  private Metrics.Metric.Builder convert(HistogramSnapshot.HistogramDataPointSnapshot data) {
+  private Metrics.Metric.Builder convert(
+      HistogramSnapshot.HistogramDataPointSnapshot data, EscapingScheme scheme) {
     Metrics.Metric.Builder metricBuilder = Metrics.Metric.newBuilder();
     Metrics.Histogram.Builder histogramBuilder = Metrics.Histogram.newBuilder();
     if (data.hasNativeHistogramData()) {
@@ -151,7 +175,7 @@ private Metrics.Metric.Builder convert(HistogramSnapshot.HistogramDataPointSnaps
               Metrics.Bucket.newBuilder()
                   .setCumulativeCount(getNativeCount(data))
                   .setUpperBound(Double.POSITIVE_INFINITY);
-          bucketBuilder.setExemplar(convert(exemplar));
+          bucketBuilder.setExemplar(convert(exemplar, scheme));
           histogramBuilder.addBucket(bucketBuilder);
         }
       }
@@ -170,13 +194,13 @@ private Metrics.Metric.Builder convert(HistogramSnapshot.HistogramDataPointSnaps
                 .setUpperBound(upperBound);
         Exemplar exemplar = data.getExemplars().get(lowerBound, upperBound);
         if (exemplar != null) {
-          bucketBuilder.setExemplar(convert(exemplar));
+          bucketBuilder.setExemplar(convert(exemplar, scheme));
         }
         histogramBuilder.addBucket(bucketBuilder);
         lowerBound = upperBound;
       }
     }
-    addLabels(metricBuilder, data.getLabels());
+    addLabels(metricBuilder, data.getLabels(), scheme);
     setScrapeTimestamp(metricBuilder, data);
     if (data.hasCount()) {
       histogramBuilder.setSampleCount(data.getCount());
@@ -188,7 +212,8 @@ private Metrics.Metric.Builder convert(HistogramSnapshot.HistogramDataPointSnaps
     return metricBuilder;
   }
 
-  private Metrics.Metric.Builder convert(SummarySnapshot.SummaryDataPointSnapshot data) {
+  private Metrics.Metric.Builder convert(
+      SummarySnapshot.SummaryDataPointSnapshot data, EscapingScheme scheme) {
     Metrics.Summary.Builder summaryBuilder = Metrics.Summary.newBuilder();
     if (data.hasCount()) {
       summaryBuilder.setSampleCount(data.getCount());
@@ -205,27 +230,28 @@ private Metrics.Metric.Builder convert(SummarySnapshot.SummaryDataPointSnapshot
               .build());
     }
     Metrics.Metric.Builder metricBuilder = Metrics.Metric.newBuilder();
-    addLabels(metricBuilder, data.getLabels());
+    addLabels(metricBuilder, data.getLabels(), scheme);
     metricBuilder.setSummary(summaryBuilder.build());
     setScrapeTimestamp(metricBuilder, data);
     return metricBuilder;
   }
 
-  private Metrics.Metric.Builder convert(InfoSnapshot.InfoDataPointSnapshot data) {
+  private Metrics.Metric.Builder convert(
+      InfoSnapshot.InfoDataPointSnapshot data, EscapingScheme scheme) {
     Metrics.Metric.Builder metricBuilder = Metrics.Metric.newBuilder();
     Metrics.Gauge.Builder gaugeBuilder = Metrics.Gauge.newBuilder();
     gaugeBuilder.setValue(1);
-    addLabels(metricBuilder, data.getLabels());
+    addLabels(metricBuilder, data.getLabels(), scheme);
     metricBuilder.setGauge(gaugeBuilder);
     setScrapeTimestamp(metricBuilder, data);
     return metricBuilder;
   }
 
   private Metrics.Metric.Builder convert(
-      StateSetSnapshot.StateSetDataPointSnapshot data, String name, int i) {
+      StateSetSnapshot.StateSetDataPointSnapshot data, String name, int i, EscapingScheme scheme) {
     Metrics.Metric.Builder metricBuilder = Metrics.Metric.newBuilder();
     Metrics.Gauge.Builder gaugeBuilder = Metrics.Gauge.newBuilder();
-    addLabels(metricBuilder, data.getLabels());
+    addLabels(metricBuilder, data.getLabels(), scheme);
     metricBuilder.addLabel(
         Metrics.LabelPair.newBuilder().setName(name).setValue(data.getName(i)).build());
     if (data.isTrue(i)) {
@@ -238,19 +264,20 @@ private Metrics.Metric.Builder convert(
     return metricBuilder;
   }
 
-  private Metrics.Metric.Builder convert(UnknownSnapshot.UnknownDataPointSnapshot data) {
+  private Metrics.Metric.Builder convert(
+      UnknownSnapshot.UnknownDataPointSnapshot data, EscapingScheme scheme) {
     Metrics.Metric.Builder metricBuilder = Metrics.Metric.newBuilder();
     Metrics.Untyped.Builder untypedBuilder = Metrics.Untyped.newBuilder();
     untypedBuilder.setValue(data.getValue());
-    addLabels(metricBuilder, data.getLabels());
+    addLabels(metricBuilder, data.getLabels(), scheme);
     metricBuilder.setUntyped(untypedBuilder);
     return metricBuilder;
   }
 
-  private Metrics.Exemplar.Builder convert(Exemplar exemplar) {
+  private Metrics.Exemplar.Builder convert(Exemplar exemplar, EscapingScheme scheme) {
     Metrics.Exemplar.Builder builder = Metrics.Exemplar.newBuilder();
     builder.setValue(exemplar.getValue());
-    addLabels(builder, exemplar.getLabels());
+    addLabels(builder, exemplar.getLabels(), scheme);
     if (exemplar.hasTimestamp()) {
       builder.setTimestamp(timestampFromMillis(exemplar.getTimestampMillis()));
     }
@@ -260,15 +287,16 @@ private Metrics.Exemplar.Builder convert(Exemplar exemplar) {
   private void setMetadataUnlessEmpty(
       Metrics.MetricFamily.Builder builder,
       MetricMetadata metadata,
-      String nameSuffix,
-      Metrics.MetricType type) {
+      @Nullable String nameSuffix,
+      Metrics.MetricType type,
+      EscapingScheme scheme) {
     if (builder.getMetricCount() == 0) {
       return;
     }
     if (nameSuffix == null) {
-      builder.setName(metadata.getPrometheusName());
+      builder.setName(SnapshotEscaper.getMetadataName(metadata, scheme));
     } else {
-      builder.setName(metadata.getPrometheusName() + nameSuffix);
+      builder.setName(SnapshotEscaper.getMetadataName(metadata, scheme) + nameSuffix);
     }
     if (metadata.getHelp() != null) {
       builder.setHelp(metadata.getHelp());
@@ -341,21 +369,23 @@ private void addBuckets(
     }
   }
 
-  private void addLabels(Metrics.Metric.Builder metricBuilder, Labels labels) {
+  private void addLabels(
+      Metrics.Metric.Builder metricBuilder, Labels labels, EscapingScheme scheme) {
     for (int i = 0; i < labels.size(); i++) {
       metricBuilder.addLabel(
           Metrics.LabelPair.newBuilder()
-              .setName(labels.getPrometheusName(i))
+              .setName(getSnapshotLabelName(labels, i, scheme))
               .setValue(labels.getValue(i))
               .build());
     }
   }
 
-  private void addLabels(Metrics.Exemplar.Builder metricBuilder, Labels labels) {
+  private void addLabels(
+      Metrics.Exemplar.Builder metricBuilder, Labels labels, EscapingScheme scheme) {
     for (int i = 0; i < labels.size(); i++) {
       metricBuilder.addLabel(
           Metrics.LabelPair.newBuilder()
-              .setName(labels.getPrometheusName(i))
+              .setName(getSnapshotLabelName(labels, i, scheme))
               .setValue(labels.getValue(i))
               .build());
     }
diff --git a/prometheus-metrics-exposition-formats/src/test/java/io/prometheus/metrics/expositionformats/DuplicateNamesProtobufTest.java b/prometheus-metrics-exposition-formats/src/test/java/io/prometheus/metrics/expositionformats/DuplicateNamesProtobufTest.java
new file mode 100644
index 000000000..c5fc7bf34
--- /dev/null
+++ b/prometheus-metrics-exposition-formats/src/test/java/io/prometheus/metrics/expositionformats/DuplicateNamesProtobufTest.java
@@ -0,0 +1,301 @@
+package io.prometheus.metrics.expositionformats;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import io.prometheus.metrics.config.EscapingScheme;
+import io.prometheus.metrics.expositionformats.generated.Metrics;
+import io.prometheus.metrics.expositionformats.internal.PrometheusProtobufWriterImpl;
+import io.prometheus.metrics.model.registry.Collector;
+import io.prometheus.metrics.model.registry.PrometheusRegistry;
+import io.prometheus.metrics.model.snapshots.CounterSnapshot;
+import io.prometheus.metrics.model.snapshots.GaugeSnapshot;
+import io.prometheus.metrics.model.snapshots.Labels;
+import io.prometheus.metrics.model.snapshots.MetricSnapshot;
+import io.prometheus.metrics.model.snapshots.MetricSnapshots;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import org.junit.jupiter.api.Test;
+
+@SuppressWarnings("NonCanonicalType")
+class DuplicateNamesProtobufTest {
+
+  private static PrometheusRegistry getPrometheusRegistry() {
+    PrometheusRegistry registry = new PrometheusRegistry();
+
+    registry.register(
+        new Collector() {
+          @Override
+          public MetricSnapshot collect() {
+            return CounterSnapshot.builder()
+                .name("api_responses")
+                .help("API responses")
+                .dataPoint(
+                    CounterSnapshot.CounterDataPointSnapshot.builder()
+                        .labels(Labels.of("uri", "/hello", "outcome", "SUCCESS"))
+                        .value(100)
+                        .build())
+                .build();
+          }
+
+          @Override
+          public String getPrometheusName() {
+            return "api_responses";
+          }
+        });
+
+    registry.register(
+        new Collector() {
+          @Override
+          public MetricSnapshot collect() {
+            return CounterSnapshot.builder()
+                .name("api_responses")
+                .help("API responses")
+                .dataPoint(
+                    CounterSnapshot.CounterDataPointSnapshot.builder()
+                        .labels(
+                            Labels.of("uri", "/hello", "outcome", "FAILURE", "error", "TIMEOUT"))
+                        .value(10)
+                        .build())
+                .build();
+          }
+
+          @Override
+          public String getPrometheusName() {
+            return "api_responses";
+          }
+        });
+    return registry;
+  }
+
+  @Test
+  void testDuplicateNames_differentLabels_producesSingleMetricFamily() throws IOException {
+    PrometheusRegistry registry = getPrometheusRegistry();
+
+    MetricSnapshots snapshots = registry.scrape();
+    ByteArrayOutputStream out = new ByteArrayOutputStream();
+    PrometheusProtobufWriterImpl writer = new PrometheusProtobufWriterImpl();
+    writer.write(out, snapshots, EscapingScheme.UNDERSCORE_ESCAPING);
+
+    List metricFamilies = parseProtobufOutput(out);
+
+    assertThat(metricFamilies).hasSize(1);
+    Metrics.MetricFamily family = metricFamilies.get(0);
+    assertThat(family.getName()).isEqualTo("api_responses_total");
+    assertThat(family.getHelp()).isEqualTo("API responses");
+    assertThat(family.getType()).isEqualTo(Metrics.MetricType.COUNTER);
+    assertThat(family.getMetricCount()).isEqualTo(2);
+
+    Metrics.Metric successMetric =
+        family.getMetricList().stream()
+            .filter(
+                m ->
+                    m.getLabelList().stream()
+                        .anyMatch(
+                            l -> l.getName().equals("outcome") && l.getValue().equals("SUCCESS")))
+            .findFirst()
+            .orElseThrow(() -> new AssertionError("SUCCESS metric not found"));
+    assertThat(successMetric.getCounter().getValue()).isEqualTo(100.0);
+
+    Metrics.Metric failureMetric =
+        family.getMetricList().stream()
+            .filter(
+                m ->
+                    m.getLabelList().stream()
+                            .anyMatch(
+                                l ->
+                                    l.getName().equals("outcome") && l.getValue().equals("FAILURE"))
+                        && m.getLabelList().stream()
+                            .anyMatch(
+                                l -> l.getName().equals("error") && l.getValue().equals("TIMEOUT")))
+            .findFirst()
+            .orElseThrow(() -> new AssertionError("FAILURE metric not found"));
+    assertThat(failureMetric.getCounter().getValue()).isEqualTo(10.0);
+  }
+
+  @Test
+  void testDuplicateNames_multipleDataPoints_producesSingleMetricFamily() throws IOException {
+    PrometheusRegistry registry = new PrometheusRegistry();
+
+    registry.register(
+        new Collector() {
+          @Override
+          public MetricSnapshot collect() {
+            return CounterSnapshot.builder()
+                .name("api_responses")
+                .help("API responses")
+                .dataPoint(
+                    CounterSnapshot.CounterDataPointSnapshot.builder()
+                        .labels(Labels.of("uri", "/hello", "outcome", "SUCCESS"))
+                        .value(100)
+                        .build())
+                .dataPoint(
+                    CounterSnapshot.CounterDataPointSnapshot.builder()
+                        .labels(Labels.of("uri", "/world", "outcome", "SUCCESS"))
+                        .value(200)
+                        .build())
+                .build();
+          }
+
+          @Override
+          public String getPrometheusName() {
+            return "api_responses";
+          }
+        });
+
+    registry.register(
+        new Collector() {
+          @Override
+          public MetricSnapshot collect() {
+            return CounterSnapshot.builder()
+                .name("api_responses")
+                .help("API responses")
+                .dataPoint(
+                    CounterSnapshot.CounterDataPointSnapshot.builder()
+                        .labels(
+                            Labels.of("uri", "/hello", "outcome", "FAILURE", "error", "TIMEOUT"))
+                        .value(10)
+                        .build())
+                .dataPoint(
+                    CounterSnapshot.CounterDataPointSnapshot.builder()
+                        .labels(
+                            Labels.of("uri", "/world", "outcome", "FAILURE", "error", "NOT_FOUND"))
+                        .value(5)
+                        .build())
+                .build();
+          }
+
+          @Override
+          public String getPrometheusName() {
+            return "api_responses";
+          }
+        });
+
+    MetricSnapshots snapshots = registry.scrape();
+    ByteArrayOutputStream out = new ByteArrayOutputStream();
+    PrometheusProtobufWriterImpl writer = new PrometheusProtobufWriterImpl();
+    writer.write(out, snapshots, EscapingScheme.UNDERSCORE_ESCAPING);
+
+    List metricFamilies = parseProtobufOutput(out);
+
+    assertThat(metricFamilies).hasSize(1);
+    Metrics.MetricFamily family = metricFamilies.get(0);
+    assertThat(family.getName()).isEqualTo("api_responses_total");
+    assertThat(family.getMetricCount()).isEqualTo(4);
+
+    long successCount =
+        family.getMetricList().stream()
+            .filter(
+                m ->
+                    m.getLabelList().stream()
+                        .anyMatch(
+                            l -> l.getName().equals("outcome") && l.getValue().equals("SUCCESS")))
+            .count();
+
+    long failureCount =
+        family.getMetricList().stream()
+            .filter(
+                m ->
+                    m.getLabelList().stream()
+                        .anyMatch(
+                            l -> l.getName().equals("outcome") && l.getValue().equals("FAILURE")))
+            .count();
+
+    assertThat(successCount).isEqualTo(2);
+    assertThat(failureCount).isEqualTo(2);
+  }
+
+  @Test
+  void testDifferentMetrics_producesSeparateMetricFamilies() throws IOException {
+    MetricSnapshots snapshots = getMetricSnapshots();
+    ByteArrayOutputStream out = new ByteArrayOutputStream();
+    PrometheusProtobufWriterImpl writer = new PrometheusProtobufWriterImpl();
+    writer.write(out, snapshots, EscapingScheme.UNDERSCORE_ESCAPING);
+
+    List metricFamilies = parseProtobufOutput(out);
+
+    assertThat(metricFamilies).hasSize(2);
+
+    Metrics.MetricFamily counterFamily = null;
+    Metrics.MetricFamily gaugeFamily = null;
+    for (Metrics.MetricFamily family : metricFamilies) {
+      if (family.getName().equals("http_requests_total")) {
+        counterFamily = family;
+      } else if (family.getName().equals("active_sessions")) {
+        gaugeFamily = family;
+      }
+    }
+
+    assertThat(counterFamily).isNotNull();
+    assertThat(counterFamily.getType()).isEqualTo(Metrics.MetricType.COUNTER);
+    assertThat(counterFamily.getMetricCount()).isEqualTo(1);
+    assertThat(counterFamily.getMetric(0).getCounter().getValue()).isEqualTo(100.0);
+
+    assertThat(gaugeFamily).isNotNull();
+    assertThat(gaugeFamily.getType()).isEqualTo(Metrics.MetricType.GAUGE);
+    assertThat(gaugeFamily.getMetricCount()).isEqualTo(1);
+    assertThat(gaugeFamily.getMetric(0).getGauge().getValue()).isEqualTo(50.0);
+  }
+
+  private static MetricSnapshots getMetricSnapshots() {
+    PrometheusRegistry registry = new PrometheusRegistry();
+
+    registry.register(
+        new Collector() {
+          @Override
+          public MetricSnapshot collect() {
+            return CounterSnapshot.builder()
+                .name("http_requests")
+                .help("HTTP Request counter")
+                .dataPoint(
+                    CounterSnapshot.CounterDataPointSnapshot.builder()
+                        .labels(Labels.of("method", "GET"))
+                        .value(100)
+                        .build())
+                .build();
+          }
+
+          @Override
+          public String getPrometheusName() {
+            return "http_requests";
+          }
+        });
+
+    registry.register(
+        new Collector() {
+          @Override
+          public MetricSnapshot collect() {
+            return GaugeSnapshot.builder()
+                .name("active_sessions")
+                .help("Active sessions gauge")
+                .dataPoint(
+                    GaugeSnapshot.GaugeDataPointSnapshot.builder()
+                        .labels(Labels.of("region", "us-east-1"))
+                        .value(50)
+                        .build())
+                .build();
+          }
+
+          @Override
+          public String getPrometheusName() {
+            return "active_sessions";
+          }
+        });
+
+    return registry.scrape();
+  }
+
+  private static List parseProtobufOutput(ByteArrayOutputStream out)
+      throws IOException {
+    List metricFamilies = new ArrayList<>();
+    try (ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray())) {
+      Metrics.MetricFamily family;
+      while ((family = Metrics.MetricFamily.parseDelimitedFrom(in)) != null) {
+        metricFamilies.add(family);
+      }
+    }
+    return metricFamilies;
+  }
+}
diff --git a/prometheus-metrics-exposition-formats/src/test/java/io/prometheus/metrics/expositionformats/ProtobufExpositionFormatsTest.java b/prometheus-metrics-exposition-formats/src/test/java/io/prometheus/metrics/expositionformats/ProtobufExpositionFormatsTest.java
index 6902c0264..5f871c585 100644
--- a/prometheus-metrics-exposition-formats/src/test/java/io/prometheus/metrics/expositionformats/ProtobufExpositionFormatsTest.java
+++ b/prometheus-metrics-exposition-formats/src/test/java/io/prometheus/metrics/expositionformats/ProtobufExpositionFormatsTest.java
@@ -2,17 +2,20 @@
 
 import static org.assertj.core.api.Assertions.assertThat;
 
-import io.prometheus.metrics.expositionformats.generated.com_google_protobuf_4_31_1.Metrics;
+import io.prometheus.metrics.config.EscapingScheme;
+import io.prometheus.metrics.expositionformats.generated.Metrics;
 import io.prometheus.metrics.expositionformats.internal.PrometheusProtobufWriterImpl;
 import io.prometheus.metrics.expositionformats.internal.ProtobufUtil;
 import io.prometheus.metrics.model.snapshots.MetricSnapshot;
 
+@SuppressWarnings("NonCanonicalType")
 class ProtobufExpositionFormatsTest extends ExpositionFormatsTest {
 
   @Override
   protected void assertPrometheusProtobuf(String expected, MetricSnapshot snapshot) {
     PrometheusProtobufWriterImpl writer = new PrometheusProtobufWriterImpl();
-    Metrics.MetricFamily protobufData = writer.convert(snapshot);
+    Metrics.MetricFamily protobufData =
+        writer.convert(snapshot, EscapingScheme.UNDERSCORE_ESCAPING);
     String actual = ProtobufUtil.shortDebugString(protobufData);
     assertThat(actual).isEqualTo(expected);
   }
diff --git a/prometheus-metrics-exposition-textformats/pom.xml b/prometheus-metrics-exposition-textformats/pom.xml
index 38bdf8b47..16e7b73b6 100644
--- a/prometheus-metrics-exposition-textformats/pom.xml
+++ b/prometheus-metrics-exposition-textformats/pom.xml
@@ -6,7 +6,7 @@
   
     io.prometheus
     client_java
-    1.4.0-SNAPSHOT
+    1.6.0-SNAPSHOT
   
 
   prometheus-metrics-exposition-textformats
@@ -19,7 +19,7 @@
 
   
     io.prometheus.writer.text
-    0.50
+    0.60
   
 
   
diff --git a/prometheus-metrics-exposition-textformats/src/main/java/io/prometheus/metrics/expositionformats/ExpositionFormatWriter.java b/prometheus-metrics-exposition-textformats/src/main/java/io/prometheus/metrics/expositionformats/ExpositionFormatWriter.java
index b472af0e1..03ac229ca 100644
--- a/prometheus-metrics-exposition-textformats/src/main/java/io/prometheus/metrics/expositionformats/ExpositionFormatWriter.java
+++ b/prometheus-metrics-exposition-textformats/src/main/java/io/prometheus/metrics/expositionformats/ExpositionFormatWriter.java
@@ -1,26 +1,42 @@
 package io.prometheus.metrics.expositionformats;
 
+import io.prometheus.metrics.config.EscapingScheme;
 import io.prometheus.metrics.model.snapshots.MetricSnapshots;
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 import java.io.OutputStream;
+import javax.annotation.Nullable;
 
 public interface ExpositionFormatWriter {
-  boolean accepts(String acceptHeader);
+  boolean accepts(@Nullable String acceptHeader);
 
-  /** Text formats use UTF-8 encoding. */
-  void write(OutputStream out, MetricSnapshots metricSnapshots) throws IOException;
+  /** Writes the given metric snapshots to the output stream using the specified escaping scheme. */
+  void write(OutputStream out, MetricSnapshots metricSnapshots, EscapingScheme escapingScheme)
+      throws IOException;
 
-  default String toDebugString(MetricSnapshots metricSnapshots) {
+  /** Writes the given metric snapshots to the output stream using the default escaping scheme. */
+  default void write(OutputStream out, MetricSnapshots metricSnapshots) throws IOException {
+    write(out, metricSnapshots, EscapingScheme.DEFAULT);
+  }
+
+  /** Converts the metric snapshots to a debug string using the specified escaping scheme. */
+  // toString with Charset is only available in Java 10+, but we want to support Java 8
+  @SuppressWarnings("JdkObsolete")
+  default String toDebugString(MetricSnapshots metricSnapshots, EscapingScheme escapingScheme) {
     ByteArrayOutputStream out = new ByteArrayOutputStream();
     try {
-      write(out, metricSnapshots);
+      write(out, metricSnapshots, escapingScheme);
       return out.toString("UTF-8");
     } catch (IOException e) {
       throw new RuntimeException(e);
     }
   }
 
+  /** Converts the metric snapshots to a debug string using the default escaping scheme. */
+  default String toDebugString(MetricSnapshots metricSnapshots) {
+    return toDebugString(metricSnapshots, EscapingScheme.DEFAULT);
+  }
+
   String getContentType();
 
   /**
diff --git a/prometheus-metrics-exposition-textformats/src/main/java/io/prometheus/metrics/expositionformats/ExpositionFormats.java b/prometheus-metrics-exposition-textformats/src/main/java/io/prometheus/metrics/expositionformats/ExpositionFormats.java
index daec7677b..0ffa23a58 100644
--- a/prometheus-metrics-exposition-textformats/src/main/java/io/prometheus/metrics/expositionformats/ExpositionFormats.java
+++ b/prometheus-metrics-exposition-textformats/src/main/java/io/prometheus/metrics/expositionformats/ExpositionFormats.java
@@ -2,6 +2,7 @@
 
 import io.prometheus.metrics.config.ExporterProperties;
 import io.prometheus.metrics.config.PrometheusProperties;
+import javax.annotation.Nullable;
 
 public class ExpositionFormats {
 
@@ -36,7 +37,7 @@ public static ExpositionFormats init(ExporterProperties properties) {
             .build());
   }
 
-  public ExpositionFormatWriter findWriter(String acceptHeader) {
+  public ExpositionFormatWriter findWriter(@Nullable String acceptHeader) {
     if (prometheusProtobufWriter.accepts(acceptHeader)) {
       return prometheusProtobufWriter;
     }
diff --git a/prometheus-metrics-exposition-textformats/src/main/java/io/prometheus/metrics/expositionformats/NameType.java b/prometheus-metrics-exposition-textformats/src/main/java/io/prometheus/metrics/expositionformats/NameType.java
new file mode 100644
index 000000000..1a9a813aa
--- /dev/null
+++ b/prometheus-metrics-exposition-textformats/src/main/java/io/prometheus/metrics/expositionformats/NameType.java
@@ -0,0 +1,6 @@
+package io.prometheus.metrics.expositionformats;
+
+enum NameType {
+  Metric,
+  Label
+}
diff --git a/prometheus-metrics-exposition-textformats/src/main/java/io/prometheus/metrics/expositionformats/OpenMetricsTextFormatWriter.java b/prometheus-metrics-exposition-textformats/src/main/java/io/prometheus/metrics/expositionformats/OpenMetricsTextFormatWriter.java
index 914b28515..293fbfb8c 100644
--- a/prometheus-metrics-exposition-textformats/src/main/java/io/prometheus/metrics/expositionformats/OpenMetricsTextFormatWriter.java
+++ b/prometheus-metrics-exposition-textformats/src/main/java/io/prometheus/metrics/expositionformats/OpenMetricsTextFormatWriter.java
@@ -1,11 +1,15 @@
 package io.prometheus.metrics.expositionformats;
 
 import static io.prometheus.metrics.expositionformats.TextFormatUtil.writeDouble;
-import static io.prometheus.metrics.expositionformats.TextFormatUtil.writeEscapedLabelValue;
+import static io.prometheus.metrics.expositionformats.TextFormatUtil.writeEscapedString;
 import static io.prometheus.metrics.expositionformats.TextFormatUtil.writeLabels;
 import static io.prometheus.metrics.expositionformats.TextFormatUtil.writeLong;
+import static io.prometheus.metrics.expositionformats.TextFormatUtil.writeName;
 import static io.prometheus.metrics.expositionformats.TextFormatUtil.writeOpenMetricsTimestamp;
+import static io.prometheus.metrics.model.snapshots.SnapshotEscaper.getMetadataName;
+import static io.prometheus.metrics.model.snapshots.SnapshotEscaper.getSnapshotLabelName;
 
+import io.prometheus.metrics.config.EscapingScheme;
 import io.prometheus.metrics.model.snapshots.ClassicHistogramBuckets;
 import io.prometheus.metrics.model.snapshots.CounterSnapshot;
 import io.prometheus.metrics.model.snapshots.DataPointSnapshot;
@@ -19,7 +23,9 @@
 import io.prometheus.metrics.model.snapshots.MetricMetadata;
 import io.prometheus.metrics.model.snapshots.MetricSnapshot;
 import io.prometheus.metrics.model.snapshots.MetricSnapshots;
+import io.prometheus.metrics.model.snapshots.PrometheusNaming;
 import io.prometheus.metrics.model.snapshots.Quantile;
+import io.prometheus.metrics.model.snapshots.SnapshotEscaper;
 import io.prometheus.metrics.model.snapshots.StateSetSnapshot;
 import io.prometheus.metrics.model.snapshots.SummarySnapshot;
 import io.prometheus.metrics.model.snapshots.UnknownSnapshot;
@@ -30,6 +36,7 @@
 import java.io.Writer;
 import java.nio.charset.StandardCharsets;
 import java.util.List;
+import javax.annotation.Nullable;
 
 /**
  * Write the OpenMetrics text format as defined on  dataList)
+      List dataList,
+      EscapingScheme scheme)
       throws IOException {
     for (HistogramSnapshot.HistogramDataPointSnapshot data : dataList) {
       ClassicHistogramBuckets buckets = getClassicBuckets(data);
@@ -179,9 +196,10 @@ private void writeClassicHistogramBuckets(
         cumulativeCount += buckets.getCount(i);
         writeNameAndLabels(
             writer,
-            metadata.getPrometheusName(),
+            getMetadataName(metadata, scheme),
             "_bucket",
             data.getLabels(),
+            scheme,
             "le",
             buckets.getUpperBound(i));
         writeLong(writer, cumulativeCount);
@@ -191,13 +209,13 @@ private void writeClassicHistogramBuckets(
         } else {
           exemplar = exemplars.get(buckets.getUpperBound(i - 1), buckets.getUpperBound(i));
         }
-        writeScrapeTimestampAndExemplar(writer, data, exemplar);
+        writeScrapeTimestampAndExemplar(writer, data, exemplar, scheme);
       }
       // In OpenMetrics format, histogram _count and _sum are either both present or both absent.
       if (data.hasCount() && data.hasSum()) {
-        writeCountAndSum(writer, metadata, data, countSuffix, sumSuffix, exemplars);
+        writeCountAndSum(writer, metadata, data, countSuffix, sumSuffix, exemplars, scheme);
       }
-      writeCreated(writer, metadata, data);
+      writeCreated(writer, metadata, data, scheme);
     }
   }
 
@@ -211,7 +229,8 @@ private ClassicHistogramBuckets getClassicBuckets(
     }
   }
 
-  private void writeSummary(Writer writer, SummarySnapshot snapshot) throws IOException {
+  private void writeSummary(Writer writer, SummarySnapshot snapshot, EscapingScheme scheme)
+      throws IOException {
     boolean metadataWritten = false;
     MetricMetadata metadata = snapshot.getMetadata();
     for (SummarySnapshot.SummaryDataPointSnapshot data : snapshot.getDataPoints()) {
@@ -219,7 +238,7 @@ private void writeSummary(Writer writer, SummarySnapshot snapshot) throws IOExce
         continue;
       }
       if (!metadataWritten) {
-        writeMetadata(writer, "summary", metadata);
+        writeMetadata(writer, "summary", metadata, scheme);
         metadataWritten = true;
       }
       Exemplars exemplars = data.getExemplars();
@@ -232,78 +251,84 @@ private void writeSummary(Writer writer, SummarySnapshot snapshot) throws IOExce
       for (Quantile quantile : data.getQuantiles()) {
         writeNameAndLabels(
             writer,
-            metadata.getPrometheusName(),
+            getMetadataName(metadata, scheme),
             null,
             data.getLabels(),
+            scheme,
             "quantile",
             quantile.getQuantile());
         writeDouble(writer, quantile.getValue());
         if (exemplars.size() > 0 && exemplarsOnAllMetricTypesEnabled) {
           exemplarIndex = (exemplarIndex + 1) % exemplars.size();
-          writeScrapeTimestampAndExemplar(writer, data, exemplars.get(exemplarIndex));
+          writeScrapeTimestampAndExemplar(writer, data, exemplars.get(exemplarIndex), scheme);
         } else {
-          writeScrapeTimestampAndExemplar(writer, data, null);
+          writeScrapeTimestampAndExemplar(writer, data, null, scheme);
         }
       }
       // Unlike histograms, summaries can have only a count or only a sum according to OpenMetrics.
-      writeCountAndSum(writer, metadata, data, "_count", "_sum", exemplars);
-      writeCreated(writer, metadata, data);
+      writeCountAndSum(writer, metadata, data, "_count", "_sum", exemplars, scheme);
+      writeCreated(writer, metadata, data, scheme);
     }
   }
 
-  private void writeInfo(Writer writer, InfoSnapshot snapshot) throws IOException {
+  private void writeInfo(Writer writer, InfoSnapshot snapshot, EscapingScheme scheme)
+      throws IOException {
     MetricMetadata metadata = snapshot.getMetadata();
-    writeMetadata(writer, "info", metadata);
+    writeMetadata(writer, "info", metadata, scheme);
     for (InfoSnapshot.InfoDataPointSnapshot data : snapshot.getDataPoints()) {
-      writeNameAndLabels(writer, metadata.getPrometheusName(), "_info", data.getLabels());
+      writeNameAndLabels(
+          writer, getMetadataName(metadata, scheme), "_info", data.getLabels(), scheme);
       writer.write("1");
-      writeScrapeTimestampAndExemplar(writer, data, null);
+      writeScrapeTimestampAndExemplar(writer, data, null, scheme);
     }
   }
 
-  private void writeStateSet(Writer writer, StateSetSnapshot snapshot) throws IOException {
+  private void writeStateSet(Writer writer, StateSetSnapshot snapshot, EscapingScheme scheme)
+      throws IOException {
     MetricMetadata metadata = snapshot.getMetadata();
-    writeMetadata(writer, "stateset", metadata);
+    writeMetadata(writer, "stateset", metadata, scheme);
     for (StateSetSnapshot.StateSetDataPointSnapshot data : snapshot.getDataPoints()) {
       for (int i = 0; i < data.size(); i++) {
-        writer.write(metadata.getPrometheusName());
+        writer.write(getMetadataName(metadata, scheme));
         writer.write('{');
-        for (int j = 0; j < data.getLabels().size(); j++) {
+        Labels labels = data.getLabels();
+        for (int j = 0; j < labels.size(); j++) {
           if (j > 0) {
             writer.write(",");
           }
-          writer.write(data.getLabels().getPrometheusName(j));
+          writer.write(getSnapshotLabelName(labels, j, scheme));
           writer.write("=\"");
-          writeEscapedLabelValue(writer, data.getLabels().getValue(j));
+          writeEscapedString(writer, labels.getValue(j));
           writer.write("\"");
         }
-        if (!data.getLabels().isEmpty()) {
+        if (!labels.isEmpty()) {
           writer.write(",");
         }
-        writer.write(metadata.getPrometheusName());
+        writer.write(getMetadataName(metadata, scheme));
         writer.write("=\"");
-        writeEscapedLabelValue(writer, data.getName(i));
+        writeEscapedString(writer, data.getName(i));
         writer.write("\"} ");
         if (data.isTrue(i)) {
           writer.write("1");
         } else {
           writer.write("0");
         }
-        writeScrapeTimestampAndExemplar(writer, data, null);
+        writeScrapeTimestampAndExemplar(writer, data, null, scheme);
       }
     }
   }
 
-  private void writeUnknown(Writer writer, UnknownSnapshot snapshot) throws IOException {
+  private void writeUnknown(Writer writer, UnknownSnapshot snapshot, EscapingScheme scheme)
+      throws IOException {
     MetricMetadata metadata = snapshot.getMetadata();
-    writeMetadata(writer, "unknown", metadata);
+    writeMetadata(writer, "unknown", metadata, scheme);
     for (UnknownSnapshot.UnknownDataPointSnapshot data : snapshot.getDataPoints()) {
-      writeNameAndLabels(writer, metadata.getPrometheusName(), null, data.getLabels());
+      writeNameAndLabels(writer, getMetadataName(metadata, scheme), null, data.getLabels(), scheme);
       writeDouble(writer, data.getValue());
       if (exemplarsOnAllMetricTypesEnabled) {
-        writeScrapeTimestampAndExemplar(writer, data, data.getExemplar());
+        writeScrapeTimestampAndExemplar(writer, data, data.getExemplar(), scheme);
       } else {
-        writeScrapeTimestampAndExemplar(writer, data, null);
+        writeScrapeTimestampAndExemplar(writer, data, null, scheme);
       }
     }
   }
@@ -314,28 +339,33 @@ private void writeCountAndSum(
       DistributionDataPointSnapshot data,
       String countSuffix,
       String sumSuffix,
-      Exemplars exemplars)
+      Exemplars exemplars,
+      EscapingScheme scheme)
       throws IOException {
     if (data.hasCount()) {
-      writeNameAndLabels(writer, metadata.getPrometheusName(), countSuffix, data.getLabels());
+      writeNameAndLabels(
+          writer, getMetadataName(metadata, scheme), countSuffix, data.getLabels(), scheme);
       writeLong(writer, data.getCount());
       if (exemplarsOnAllMetricTypesEnabled) {
-        writeScrapeTimestampAndExemplar(writer, data, exemplars.getLatest());
+        writeScrapeTimestampAndExemplar(writer, data, exemplars.getLatest(), scheme);
       } else {
-        writeScrapeTimestampAndExemplar(writer, data, null);
+        writeScrapeTimestampAndExemplar(writer, data, null, scheme);
       }
     }
     if (data.hasSum()) {
-      writeNameAndLabels(writer, metadata.getPrometheusName(), sumSuffix, data.getLabels());
+      writeNameAndLabels(
+          writer, getMetadataName(metadata, scheme), sumSuffix, data.getLabels(), scheme);
       writeDouble(writer, data.getSum());
-      writeScrapeTimestampAndExemplar(writer, data, null);
+      writeScrapeTimestampAndExemplar(writer, data, null, scheme);
     }
   }
 
-  private void writeCreated(Writer writer, MetricMetadata metadata, DataPointSnapshot data)
+  private void writeCreated(
+      Writer writer, MetricMetadata metadata, DataPointSnapshot data, EscapingScheme scheme)
       throws IOException {
     if (createdTimestampsEnabled && data.hasCreatedTimestamp()) {
-      writeNameAndLabels(writer, metadata.getPrometheusName(), "_created", data.getLabels());
+      writeNameAndLabels(
+          writer, getMetadataName(metadata, scheme), "_created", data.getLabels(), scheme);
       writeOpenMetricsTimestamp(writer, data.getCreatedTimestampMillis());
       if (data.hasScrapeTimestamp()) {
         writer.write(' ');
@@ -345,38 +375,57 @@ private void writeCreated(Writer writer, MetricMetadata metadata, DataPointSnaps
     }
   }
 
-  private void writeNameAndLabels(Writer writer, String name, String suffix, Labels labels)
+  private void writeNameAndLabels(
+      Writer writer,
+      String name,
+      @Nullable String suffix,
+      Labels labels,
+      EscapingScheme escapingScheme)
       throws IOException {
-    writeNameAndLabels(writer, name, suffix, labels, null, 0.0);
+    writeNameAndLabels(writer, name, suffix, labels, escapingScheme, null, 0.0);
   }
 
   private void writeNameAndLabels(
       Writer writer,
       String name,
-      String suffix,
+      @Nullable String suffix,
       Labels labels,
-      String additionalLabelName,
+      EscapingScheme escapingScheme,
+      @Nullable String additionalLabelName,
       double additionalLabelValue)
       throws IOException {
-    writer.write(name);
-    if (suffix != null) {
-      writer.write(suffix);
+    boolean metricInsideBraces = false;
+    // If the name does not pass the legacy validity check, we must put the
+    // metric name inside the braces.
+    if (!PrometheusNaming.isValidLegacyMetricName(name)) {
+      metricInsideBraces = true;
+      writer.write('{');
     }
+    writeName(writer, name + (suffix != null ? suffix : ""), NameType.Metric);
     if (!labels.isEmpty() || additionalLabelName != null) {
-      writeLabels(writer, labels, additionalLabelName, additionalLabelValue);
+      writeLabels(
+          writer,
+          labels,
+          additionalLabelName,
+          additionalLabelValue,
+          metricInsideBraces,
+          escapingScheme);
+    } else if (metricInsideBraces) {
+      writer.write('}');
     }
     writer.write(' ');
   }
 
   private void writeScrapeTimestampAndExemplar(
-      Writer writer, DataPointSnapshot data, Exemplar exemplar) throws IOException {
+      Writer writer, DataPointSnapshot data, @Nullable Exemplar exemplar, EscapingScheme scheme)
+      throws IOException {
     if (data.hasScrapeTimestamp()) {
       writer.write(' ');
       writeOpenMetricsTimestamp(writer, data.getScrapeTimestampMillis());
     }
     if (exemplar != null) {
       writer.write(" # ");
-      writeLabels(writer, exemplar.getLabels(), null, 0);
+      writeLabels(writer, exemplar.getLabels(), null, 0, false, scheme);
       writer.write(' ');
       writeDouble(writer, exemplar.getValue());
       if (exemplar.hasTimestamp()) {
@@ -387,25 +436,26 @@ private void writeScrapeTimestampAndExemplar(
     writer.write('\n');
   }
 
-  private void writeMetadata(Writer writer, String typeName, MetricMetadata metadata)
+  private void writeMetadata(
+      Writer writer, String typeName, MetricMetadata metadata, EscapingScheme scheme)
       throws IOException {
     writer.write("# TYPE ");
-    writer.write(metadata.getPrometheusName());
+    writeName(writer, getMetadataName(metadata, scheme), NameType.Metric);
     writer.write(' ');
     writer.write(typeName);
     writer.write('\n');
     if (metadata.getUnit() != null) {
       writer.write("# UNIT ");
-      writer.write(metadata.getPrometheusName());
+      writeName(writer, getMetadataName(metadata, scheme), NameType.Metric);
       writer.write(' ');
-      writeEscapedLabelValue(writer, metadata.getUnit().toString());
+      writeEscapedString(writer, metadata.getUnit().toString());
       writer.write('\n');
     }
     if (metadata.getHelp() != null && !metadata.getHelp().isEmpty()) {
       writer.write("# HELP ");
-      writer.write(metadata.getPrometheusName());
+      writeName(writer, getMetadataName(metadata, scheme), NameType.Metric);
       writer.write(' ');
-      writeEscapedLabelValue(writer, metadata.getHelp());
+      writeEscapedString(writer, metadata.getHelp());
       writer.write('\n');
     }
   }
diff --git a/prometheus-metrics-exposition-textformats/src/main/java/io/prometheus/metrics/expositionformats/PrometheusProtobufWriter.java b/prometheus-metrics-exposition-textformats/src/main/java/io/prometheus/metrics/expositionformats/PrometheusProtobufWriter.java
index 0572a99a7..342beb255 100644
--- a/prometheus-metrics-exposition-textformats/src/main/java/io/prometheus/metrics/expositionformats/PrometheusProtobufWriter.java
+++ b/prometheus-metrics-exposition-textformats/src/main/java/io/prometheus/metrics/expositionformats/PrometheusProtobufWriter.java
@@ -1,5 +1,6 @@
 package io.prometheus.metrics.expositionformats;
 
+import io.prometheus.metrics.config.EscapingScheme;
 import io.prometheus.metrics.model.snapshots.MetricSnapshots;
 import java.io.IOException;
 import java.io.OutputStream;
@@ -34,7 +35,7 @@ private static ExpositionFormatWriter createProtobufWriter() {
   }
 
   @Override
-  public boolean accepts(String acceptHeader) {
+  public boolean accepts(@Nullable String acceptHeader) {
     if (acceptHeader == null) {
       return false;
     } else {
@@ -54,20 +55,21 @@ public boolean isAvailable() {
   }
 
   @Override
-  public String toDebugString(MetricSnapshots metricSnapshots) {
-    checkAvailable();
-    return DELEGATE.toDebugString(metricSnapshots);
+  public String toDebugString(MetricSnapshots metricSnapshots, EscapingScheme escapingScheme) {
+    return getDelegate().toDebugString(metricSnapshots, escapingScheme);
   }
 
   @Override
-  public void write(OutputStream out, MetricSnapshots metricSnapshots) throws IOException {
-    checkAvailable();
-    DELEGATE.write(out, metricSnapshots);
+  public void write(
+      OutputStream out, MetricSnapshots metricSnapshots, EscapingScheme escapingScheme)
+      throws IOException {
+    getDelegate().write(out, metricSnapshots, escapingScheme);
   }
 
-  private void checkAvailable() {
+  private ExpositionFormatWriter getDelegate() {
     if (DELEGATE == null) {
       throw new UnsupportedOperationException("Prometheus protobuf writer not available");
     }
+    return DELEGATE;
   }
 }
diff --git a/prometheus-metrics-exposition-textformats/src/main/java/io/prometheus/metrics/expositionformats/PrometheusTextFormatWriter.java b/prometheus-metrics-exposition-textformats/src/main/java/io/prometheus/metrics/expositionformats/PrometheusTextFormatWriter.java
index 76a5e4228..cc9f067ba 100644
--- a/prometheus-metrics-exposition-textformats/src/main/java/io/prometheus/metrics/expositionformats/PrometheusTextFormatWriter.java
+++ b/prometheus-metrics-exposition-textformats/src/main/java/io/prometheus/metrics/expositionformats/PrometheusTextFormatWriter.java
@@ -1,11 +1,16 @@
 package io.prometheus.metrics.expositionformats;
 
 import static io.prometheus.metrics.expositionformats.TextFormatUtil.writeDouble;
-import static io.prometheus.metrics.expositionformats.TextFormatUtil.writeEscapedLabelValue;
+import static io.prometheus.metrics.expositionformats.TextFormatUtil.writeEscapedString;
 import static io.prometheus.metrics.expositionformats.TextFormatUtil.writeLabels;
 import static io.prometheus.metrics.expositionformats.TextFormatUtil.writeLong;
+import static io.prometheus.metrics.expositionformats.TextFormatUtil.writeName;
 import static io.prometheus.metrics.expositionformats.TextFormatUtil.writePrometheusTimestamp;
+import static io.prometheus.metrics.model.snapshots.SnapshotEscaper.escapeMetricSnapshot;
+import static io.prometheus.metrics.model.snapshots.SnapshotEscaper.getMetadataName;
+import static io.prometheus.metrics.model.snapshots.SnapshotEscaper.getSnapshotLabelName;
 
+import io.prometheus.metrics.config.EscapingScheme;
 import io.prometheus.metrics.model.snapshots.ClassicHistogramBuckets;
 import io.prometheus.metrics.model.snapshots.CounterSnapshot;
 import io.prometheus.metrics.model.snapshots.DataPointSnapshot;
@@ -16,6 +21,7 @@
 import io.prometheus.metrics.model.snapshots.MetricMetadata;
 import io.prometheus.metrics.model.snapshots.MetricSnapshot;
 import io.prometheus.metrics.model.snapshots.MetricSnapshots;
+import io.prometheus.metrics.model.snapshots.PrometheusNaming;
 import io.prometheus.metrics.model.snapshots.Quantile;
 import io.prometheus.metrics.model.snapshots.StateSetSnapshot;
 import io.prometheus.metrics.model.snapshots.SummarySnapshot;
@@ -26,6 +32,7 @@
 import java.io.OutputStreamWriter;
 import java.io.Writer;
 import java.nio.charset.StandardCharsets;
+import javax.annotation.Nullable;
 
 /**
  * Write the Prometheus text format. This is the default if you view a Prometheus endpoint with your
@@ -88,7 +95,7 @@ public static PrometheusTextFormatWriter create() {
   }
 
   @Override
-  public boolean accepts(String acceptHeader) {
+  public boolean accepts(@Nullable String acceptHeader) {
     if (acceptHeader == null) {
       return false;
     } else {
@@ -102,39 +109,43 @@ public String getContentType() {
   }
 
   @Override
-  public void write(OutputStream out, MetricSnapshots metricSnapshots) throws IOException {
+  public void write(OutputStream out, MetricSnapshots metricSnapshots, EscapingScheme scheme)
+      throws IOException {
     // See https://prometheus.io/docs/instrumenting/exposition_formats/
     // "unknown", "gauge", "counter", "stateset", "info", "histogram", "gaugehistogram", and
     // "summary".
     Writer writer = new BufferedWriter(new OutputStreamWriter(out, StandardCharsets.UTF_8));
-    for (MetricSnapshot snapshot : metricSnapshots) {
+    MetricSnapshots merged = TextFormatUtil.mergeDuplicates(metricSnapshots);
+    for (MetricSnapshot s : merged) {
+      MetricSnapshot snapshot = escapeMetricSnapshot(s, scheme);
       if (!snapshot.getDataPoints().isEmpty()) {
         if (snapshot instanceof CounterSnapshot) {
-          writeCounter(writer, (CounterSnapshot) snapshot);
+          writeCounter(writer, (CounterSnapshot) snapshot, scheme);
         } else if (snapshot instanceof GaugeSnapshot) {
-          writeGauge(writer, (GaugeSnapshot) snapshot);
+          writeGauge(writer, (GaugeSnapshot) snapshot, scheme);
         } else if (snapshot instanceof HistogramSnapshot) {
-          writeHistogram(writer, (HistogramSnapshot) snapshot);
+          writeHistogram(writer, (HistogramSnapshot) snapshot, scheme);
         } else if (snapshot instanceof SummarySnapshot) {
-          writeSummary(writer, (SummarySnapshot) snapshot);
+          writeSummary(writer, (SummarySnapshot) snapshot, scheme);
         } else if (snapshot instanceof InfoSnapshot) {
-          writeInfo(writer, (InfoSnapshot) snapshot);
+          writeInfo(writer, (InfoSnapshot) snapshot, scheme);
         } else if (snapshot instanceof StateSetSnapshot) {
-          writeStateSet(writer, (StateSetSnapshot) snapshot);
+          writeStateSet(writer, (StateSetSnapshot) snapshot, scheme);
         } else if (snapshot instanceof UnknownSnapshot) {
-          writeUnknown(writer, (UnknownSnapshot) snapshot);
+          writeUnknown(writer, (UnknownSnapshot) snapshot, scheme);
         }
       }
     }
     if (writeCreatedTimestamps) {
-      for (MetricSnapshot snapshot : metricSnapshots) {
+      for (MetricSnapshot s : merged) {
+        MetricSnapshot snapshot = escapeMetricSnapshot(s, scheme);
         if (!snapshot.getDataPoints().isEmpty()) {
           if (snapshot instanceof CounterSnapshot) {
-            writeCreated(writer, snapshot);
+            writeCreated(writer, snapshot, scheme);
           } else if (snapshot instanceof HistogramSnapshot) {
-            writeCreated(writer, snapshot);
+            writeCreated(writer, snapshot, scheme);
           } else if (snapshot instanceof SummarySnapshot) {
-            writeCreated(writer, snapshot);
+            writeCreated(writer, snapshot, scheme);
           }
         }
       }
@@ -142,47 +153,53 @@ public void write(OutputStream out, MetricSnapshots metricSnapshots) throws IOEx
     writer.flush();
   }
 
-  public void writeCreated(Writer writer, MetricSnapshot snapshot) throws IOException {
+  public void writeCreated(Writer writer, MetricSnapshot snapshot, EscapingScheme scheme)
+      throws IOException {
     boolean metadataWritten = false;
     MetricMetadata metadata = snapshot.getMetadata();
     for (DataPointSnapshot data : snapshot.getDataPoints()) {
       if (data.hasCreatedTimestamp()) {
         if (!metadataWritten) {
-          writeMetadata(writer, "_created", "gauge", metadata);
+          writeMetadata(writer, "_created", "gauge", metadata, scheme);
           metadataWritten = true;
         }
-        writeNameAndLabels(writer, metadata.getPrometheusName(), "_created", data.getLabels());
+        writeNameAndLabels(
+            writer, getMetadataName(metadata, scheme), "_created", data.getLabels(), scheme);
         writePrometheusTimestamp(writer, data.getCreatedTimestampMillis(), timestampsInMs);
         writeScrapeTimestampAndNewline(writer, data);
       }
     }
   }
 
-  private void writeCounter(Writer writer, CounterSnapshot snapshot) throws IOException {
+  private void writeCounter(Writer writer, CounterSnapshot snapshot, EscapingScheme scheme)
+      throws IOException {
     if (!snapshot.getDataPoints().isEmpty()) {
       MetricMetadata metadata = snapshot.getMetadata();
-      writeMetadata(writer, "_total", "counter", metadata);
+      writeMetadata(writer, "_total", "counter", metadata, scheme);
       for (CounterSnapshot.CounterDataPointSnapshot data : snapshot.getDataPoints()) {
-        writeNameAndLabels(writer, metadata.getPrometheusName(), "_total", data.getLabels());
+        writeNameAndLabels(
+            writer, getMetadataName(metadata, scheme), "_total", data.getLabels(), scheme);
         writeDouble(writer, data.getValue());
         writeScrapeTimestampAndNewline(writer, data);
       }
     }
   }
 
-  private void writeGauge(Writer writer, GaugeSnapshot snapshot) throws IOException {
+  private void writeGauge(Writer writer, GaugeSnapshot snapshot, EscapingScheme scheme)
+      throws IOException {
     MetricMetadata metadata = snapshot.getMetadata();
-    writeMetadata(writer, "", "gauge", metadata);
+    writeMetadata(writer, "", "gauge", metadata, scheme);
     for (GaugeSnapshot.GaugeDataPointSnapshot data : snapshot.getDataPoints()) {
-      writeNameAndLabels(writer, metadata.getPrometheusName(), null, data.getLabels());
+      writeNameAndLabels(writer, getMetadataName(metadata, scheme), null, data.getLabels(), scheme);
       writeDouble(writer, data.getValue());
       writeScrapeTimestampAndNewline(writer, data);
     }
   }
 
-  private void writeHistogram(Writer writer, HistogramSnapshot snapshot) throws IOException {
+  private void writeHistogram(Writer writer, HistogramSnapshot snapshot, EscapingScheme scheme)
+      throws IOException {
     MetricMetadata metadata = snapshot.getMetadata();
-    writeMetadata(writer, "", "histogram", metadata);
+    writeMetadata(writer, "", "histogram", metadata, scheme);
     for (HistogramSnapshot.HistogramDataPointSnapshot data : snapshot.getDataPoints()) {
       ClassicHistogramBuckets buckets = getClassicBuckets(data);
       long cumulativeCount = 0;
@@ -190,9 +207,10 @@ private void writeHistogram(Writer writer, HistogramSnapshot snapshot) throws IO
         cumulativeCount += buckets.getCount(i);
         writeNameAndLabels(
             writer,
-            metadata.getPrometheusName(),
+            getMetadataName(metadata, scheme),
             "_bucket",
             data.getLabels(),
+            scheme,
             "le",
             buckets.getUpperBound(i));
         writeLong(writer, cumulativeCount);
@@ -200,19 +218,21 @@ private void writeHistogram(Writer writer, HistogramSnapshot snapshot) throws IO
       }
       if (!snapshot.isGaugeHistogram()) {
         if (data.hasCount()) {
-          writeNameAndLabels(writer, metadata.getPrometheusName(), "_count", data.getLabels());
+          writeNameAndLabels(
+              writer, getMetadataName(metadata, scheme), "_count", data.getLabels(), scheme);
           writeLong(writer, data.getCount());
           writeScrapeTimestampAndNewline(writer, data);
         }
         if (data.hasSum()) {
-          writeNameAndLabels(writer, metadata.getPrometheusName(), "_sum", data.getLabels());
+          writeNameAndLabels(
+              writer, getMetadataName(metadata, scheme), "_sum", data.getLabels(), scheme);
           writeDouble(writer, data.getSum());
           writeScrapeTimestampAndNewline(writer, data);
         }
       }
     }
     if (snapshot.isGaugeHistogram()) {
-      writeGaugeCountSum(writer, snapshot, metadata);
+      writeGaugeCountSum(writer, snapshot, metadata, scheme);
     }
   }
 
@@ -227,17 +247,19 @@ private ClassicHistogramBuckets getClassicBuckets(
   }
 
   private void writeGaugeCountSum(
-      Writer writer, HistogramSnapshot snapshot, MetricMetadata metadata) throws IOException {
+      Writer writer, HistogramSnapshot snapshot, MetricMetadata metadata, EscapingScheme scheme)
+      throws IOException {
     // Prometheus text format does not support gaugehistogram's _gcount and _gsum.
     // So we append _gcount and _gsum as gauge metrics.
     boolean metadataWritten = false;
     for (HistogramSnapshot.HistogramDataPointSnapshot data : snapshot.getDataPoints()) {
       if (data.hasCount()) {
         if (!metadataWritten) {
-          writeMetadata(writer, "_gcount", "gauge", metadata);
+          writeMetadata(writer, "_gcount", "gauge", metadata, scheme);
           metadataWritten = true;
         }
-        writeNameAndLabels(writer, metadata.getPrometheusName(), "_gcount", data.getLabels());
+        writeNameAndLabels(
+            writer, getMetadataName(metadata, scheme), "_gcount", data.getLabels(), scheme);
         writeLong(writer, data.getCount());
         writeScrapeTimestampAndNewline(writer, data);
       }
@@ -246,17 +268,19 @@ private void writeGaugeCountSum(
     for (HistogramSnapshot.HistogramDataPointSnapshot data : snapshot.getDataPoints()) {
       if (data.hasSum()) {
         if (!metadataWritten) {
-          writeMetadata(writer, "_gsum", "gauge", metadata);
+          writeMetadata(writer, "_gsum", "gauge", metadata, scheme);
           metadataWritten = true;
         }
-        writeNameAndLabels(writer, metadata.getPrometheusName(), "_gsum", data.getLabels());
+        writeNameAndLabels(
+            writer, getMetadataName(metadata, scheme), "_gsum", data.getLabels(), scheme);
         writeDouble(writer, data.getSum());
         writeScrapeTimestampAndNewline(writer, data);
       }
     }
   }
 
-  private void writeSummary(Writer writer, SummarySnapshot snapshot) throws IOException {
+  private void writeSummary(Writer writer, SummarySnapshot snapshot, EscapingScheme scheme)
+      throws IOException {
     boolean metadataWritten = false;
     MetricMetadata metadata = snapshot.getMetadata();
     for (SummarySnapshot.SummaryDataPointSnapshot data : snapshot.getDataPoints()) {
@@ -264,65 +288,71 @@ private void writeSummary(Writer writer, SummarySnapshot snapshot) throws IOExce
         continue;
       }
       if (!metadataWritten) {
-        writeMetadata(writer, "", "summary", metadata);
+        writeMetadata(writer, "", "summary", metadata, scheme);
         metadataWritten = true;
       }
       for (Quantile quantile : data.getQuantiles()) {
         writeNameAndLabels(
             writer,
-            metadata.getPrometheusName(),
+            getMetadataName(metadata, scheme),
             null,
             data.getLabels(),
+            scheme,
             "quantile",
             quantile.getQuantile());
         writeDouble(writer, quantile.getValue());
         writeScrapeTimestampAndNewline(writer, data);
       }
       if (data.hasCount()) {
-        writeNameAndLabels(writer, metadata.getPrometheusName(), "_count", data.getLabels());
+        writeNameAndLabels(
+            writer, getMetadataName(metadata, scheme), "_count", data.getLabels(), scheme);
         writeLong(writer, data.getCount());
         writeScrapeTimestampAndNewline(writer, data);
       }
       if (data.hasSum()) {
-        writeNameAndLabels(writer, metadata.getPrometheusName(), "_sum", data.getLabels());
+        writeNameAndLabels(
+            writer, getMetadataName(metadata, scheme), "_sum", data.getLabels(), scheme);
         writeDouble(writer, data.getSum());
         writeScrapeTimestampAndNewline(writer, data);
       }
     }
   }
 
-  private void writeInfo(Writer writer, InfoSnapshot snapshot) throws IOException {
+  private void writeInfo(Writer writer, InfoSnapshot snapshot, EscapingScheme scheme)
+      throws IOException {
     MetricMetadata metadata = snapshot.getMetadata();
-    writeMetadata(writer, "_info", "gauge", metadata);
+    writeMetadata(writer, "_info", "gauge", metadata, scheme);
     for (InfoSnapshot.InfoDataPointSnapshot data : snapshot.getDataPoints()) {
-      writeNameAndLabels(writer, metadata.getPrometheusName(), "_info", data.getLabels());
+      writeNameAndLabels(
+          writer, getMetadataName(metadata, scheme), "_info", data.getLabels(), scheme);
       writer.write("1");
       writeScrapeTimestampAndNewline(writer, data);
     }
   }
 
-  private void writeStateSet(Writer writer, StateSetSnapshot snapshot) throws IOException {
+  private void writeStateSet(Writer writer, StateSetSnapshot snapshot, EscapingScheme scheme)
+      throws IOException {
     MetricMetadata metadata = snapshot.getMetadata();
-    writeMetadata(writer, "", "gauge", metadata);
+    writeMetadata(writer, "", "gauge", metadata, scheme);
     for (StateSetSnapshot.StateSetDataPointSnapshot data : snapshot.getDataPoints()) {
       for (int i = 0; i < data.size(); i++) {
-        writer.write(metadata.getPrometheusName());
+        writer.write(getMetadataName(metadata, scheme));
         writer.write('{');
         for (int j = 0; j < data.getLabels().size(); j++) {
           if (j > 0) {
             writer.write(",");
           }
-          writer.write(data.getLabels().getPrometheusName(j));
+          writer.write(getSnapshotLabelName(data.getLabels(), j, scheme));
           writer.write("=\"");
-          writeEscapedLabelValue(writer, data.getLabels().getValue(j));
+          writeEscapedString(writer, data.getLabels().getValue(j));
           writer.write("\"");
         }
         if (!data.getLabels().isEmpty()) {
           writer.write(",");
         }
-        writer.write(metadata.getPrometheusName());
+        writer.write(getMetadataName(metadata, scheme));
         writer.write("=\"");
-        writeEscapedLabelValue(writer, data.getName(i));
+        writeEscapedString(writer, data.getName(i));
         writer.write("\"} ");
         if (data.isTrue(i)) {
           writer.write("1");
@@ -334,56 +364,70 @@ private void writeStateSet(Writer writer, StateSetSnapshot snapshot) throws IOEx
     }
   }
 
-  private void writeUnknown(Writer writer, UnknownSnapshot snapshot) throws IOException {
+  private void writeUnknown(Writer writer, UnknownSnapshot snapshot, EscapingScheme scheme)
+      throws IOException {
     MetricMetadata metadata = snapshot.getMetadata();
-    writeMetadata(writer, "", "untyped", metadata);
+    writeMetadata(writer, "", "untyped", metadata, scheme);
     for (UnknownSnapshot.UnknownDataPointSnapshot data : snapshot.getDataPoints()) {
-      writeNameAndLabels(writer, metadata.getPrometheusName(), null, data.getLabels());
+      writeNameAndLabels(writer, getMetadataName(metadata, scheme), null, data.getLabels(), scheme);
       writeDouble(writer, data.getValue());
       writeScrapeTimestampAndNewline(writer, data);
     }
   }
 
-  private void writeNameAndLabels(Writer writer, String name, String suffix, Labels labels)
+  private void writeNameAndLabels(
+      Writer writer,
+      String name,
+      @Nullable String suffix,
+      Labels labels,
+      EscapingScheme escapingScheme)
       throws IOException {
-    writeNameAndLabels(writer, name, suffix, labels, null, 0.0);
+    writeNameAndLabels(writer, name, suffix, labels, escapingScheme, null, 0.0);
   }
 
   private void writeNameAndLabels(
       Writer writer,
       String name,
-      String suffix,
+      @Nullable String suffix,
       Labels labels,
-      String additionalLabelName,
+      EscapingScheme scheme,
+      @Nullable String additionalLabelName,
       double additionalLabelValue)
       throws IOException {
-    writer.write(name);
-    if (suffix != null) {
-      writer.write(suffix);
+    boolean metricInsideBraces = false;
+    // If the name does not pass the legacy validity check, we must put the
+    // metric name inside the braces.
+    if (!PrometheusNaming.isValidLegacyMetricName(name)) {
+      metricInsideBraces = true;
+      writer.write('{');
     }
+    writeName(writer, name + (suffix != null ? suffix : ""), NameType.Metric);
     if (!labels.isEmpty() || additionalLabelName != null) {
-      writeLabels(writer, labels, additionalLabelName, additionalLabelValue);
+      writeLabels(
+          writer, labels, additionalLabelName, additionalLabelValue, metricInsideBraces, scheme);
+    } else if (metricInsideBraces) {
+      writer.write('}');
     }
     writer.write(' ');
   }
 
   private void writeMetadata(
-      Writer writer, String suffix, String typeString, MetricMetadata metadata) throws IOException {
+      Writer writer,
+      @Nullable String suffix,
+      String typeString,
+      MetricMetadata metadata,
+      EscapingScheme scheme)
+      throws IOException {
+    String name = getMetadataName(metadata, scheme) + (suffix != null ? suffix : "");
     if (metadata.getHelp() != null && !metadata.getHelp().isEmpty()) {
       writer.write("# HELP ");
-      writer.write(metadata.getPrometheusName());
-      if (suffix != null) {
-        writer.write(suffix);
-      }
+      writeName(writer, name, NameType.Metric);
       writer.write(' ');
       writeEscapedHelp(writer, metadata.getHelp());
       writer.write('\n');
     }
     writer.write("# TYPE ");
-    writer.write(metadata.getPrometheusName());
-    if (suffix != null) {
-      writer.write(suffix);
-    }
+    writeName(writer, name, NameType.Metric);
     writer.write(' ');
     writer.write(typeString);
     writer.write('\n');
diff --git a/prometheus-metrics-exposition-textformats/src/main/java/io/prometheus/metrics/expositionformats/TextFormatUtil.java b/prometheus-metrics-exposition-textformats/src/main/java/io/prometheus/metrics/expositionformats/TextFormatUtil.java
index 787a11c01..5f5f05e8b 100644
--- a/prometheus-metrics-exposition-textformats/src/main/java/io/prometheus/metrics/expositionformats/TextFormatUtil.java
+++ b/prometheus-metrics-exposition-textformats/src/main/java/io/prometheus/metrics/expositionformats/TextFormatUtil.java
@@ -1,10 +1,70 @@
 package io.prometheus.metrics.expositionformats;
 
+import io.prometheus.metrics.config.EscapingScheme;
+import io.prometheus.metrics.model.snapshots.CounterSnapshot;
+import io.prometheus.metrics.model.snapshots.DataPointSnapshot;
+import io.prometheus.metrics.model.snapshots.GaugeSnapshot;
+import io.prometheus.metrics.model.snapshots.HistogramSnapshot;
+import io.prometheus.metrics.model.snapshots.InfoSnapshot;
 import io.prometheus.metrics.model.snapshots.Labels;
+import io.prometheus.metrics.model.snapshots.MetricSnapshot;
+import io.prometheus.metrics.model.snapshots.MetricSnapshots;
+import io.prometheus.metrics.model.snapshots.PrometheusNaming;
+import io.prometheus.metrics.model.snapshots.SnapshotEscaper;
+import io.prometheus.metrics.model.snapshots.StateSetSnapshot;
+import io.prometheus.metrics.model.snapshots.SummarySnapshot;
+import io.prometheus.metrics.model.snapshots.UnknownSnapshot;
 import java.io.IOException;
 import java.io.Writer;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import javax.annotation.Nullable;
 
+/**
+ * Utility methods for writing Prometheus text exposition formats.
+ *
+ * 

This class provides low-level formatting utilities used by both Prometheus text format and + * OpenMetrics format writers. It handles escaping, label formatting, timestamp conversion, and + * merging of duplicate metric names. + */ public class TextFormatUtil { + /** + * Merges snapshots with duplicate Prometheus names by combining their data points. This ensures + * only one HELP/TYPE declaration per metric family. + */ + public static MetricSnapshots mergeDuplicates(MetricSnapshots metricSnapshots) { + if (metricSnapshots.size() <= 1) { + return metricSnapshots; + } + + Map> grouped = new LinkedHashMap<>(); + + for (MetricSnapshot snapshot : metricSnapshots) { + String prometheusName = snapshot.getMetadata().getPrometheusName(); + List list = grouped.get(prometheusName); + if (list == null) { + list = new ArrayList<>(); + grouped.put(prometheusName, list); + } + list.add(snapshot); + } + + MetricSnapshots.Builder builder = MetricSnapshots.builder(); + for (List group : grouped.values()) { + if (group.size() == 1) { + builder.metricSnapshot(group.get(0)); + } else { + MetricSnapshot merged = mergeSnapshots(group); + builder.metricSnapshot(merged); + } + } + + return builder.build(); + } static void writeLong(Writer writer, long value) throws IOException { writer.append(Long.toString(value)); @@ -47,7 +107,7 @@ static void writeOpenMetricsTimestamp(Writer writer, long timestampMs) throws IO writer.write(Long.toString(ms)); } - static void writeEscapedLabelValue(Writer writer, String s) throws IOException { + static void writeEscapedString(Writer writer, String s) throws IOException { // optimize for the common case where no escaping is needed int start = 0; // #indexOf is a vectorized intrinsic @@ -99,20 +159,27 @@ static void writeEscapedLabelValue(Writer writer, String s) throws IOException { } static void writeLabels( - Writer writer, Labels labels, String additionalLabelName, double additionalLabelValue) + Writer writer, + Labels labels, + @Nullable String additionalLabelName, + double additionalLabelValue, + boolean metricInsideBraces, + EscapingScheme scheme) throws IOException { - writer.write('{'); + if (!metricInsideBraces) { + writer.write('{'); + } for (int i = 0; i < labels.size(); i++) { - if (i > 0) { + if (i > 0 || metricInsideBraces) { writer.write(","); } - writer.write(labels.getPrometheusName(i)); + writeName(writer, SnapshotEscaper.getSnapshotLabelName(labels, i, scheme), NameType.Label); writer.write("=\""); - writeEscapedLabelValue(writer, labels.getValue(i)); + writeEscapedString(writer, labels.getValue(i)); writer.write("\""); } if (additionalLabelName != null) { - if (!labels.isEmpty()) { + if (!labels.isEmpty() || metricInsideBraces) { writer.write(","); } writer.write(additionalLabelName); @@ -122,4 +189,103 @@ static void writeLabels( } writer.write('}'); } + + static void writeName(Writer writer, String name, NameType nameType) throws IOException { + switch (nameType) { + case Metric: + if (PrometheusNaming.isValidLegacyMetricName(name)) { + writer.write(name); + return; + } + break; + case Label: + if (PrometheusNaming.isValidLegacyLabelName(name)) { + writer.write(name); + return; + } + break; + default: + throw new RuntimeException("Invalid name type requested: " + nameType); + } + writer.write('"'); + writeEscapedString(writer, name); + writer.write('"'); + } + + /** + * Merges multiple snapshots of the same type into a single snapshot with combined data points. + */ + @SuppressWarnings("unchecked") + private static MetricSnapshot mergeSnapshots(List snapshots) { + MetricSnapshot first = snapshots.get(0); + + int totalDataPoints = 0; + for (MetricSnapshot snapshot : snapshots) { + if (snapshot.getClass() != first.getClass()) { + throw new IllegalArgumentException( + "Cannot merge snapshots of different types: " + + first.getClass().getName() + + " and " + + snapshot.getClass().getName()); + } + if (first instanceof HistogramSnapshot) { + HistogramSnapshot histogramFirst = (HistogramSnapshot) first; + HistogramSnapshot histogramSnapshot = (HistogramSnapshot) snapshot; + if (histogramFirst.isGaugeHistogram() != histogramSnapshot.isGaugeHistogram()) { + throw new IllegalArgumentException( + "Cannot merge histograms: gauge histogram and classic histogram"); + } + } + // Validate metadata consistency so we don't silently pick one help/unit when they differ. + if (!Objects.equals( + first.getMetadata().getPrometheusName(), snapshot.getMetadata().getPrometheusName())) { + throw new IllegalArgumentException("Cannot merge snapshots: inconsistent metric name"); + } + if (!Objects.equals(first.getMetadata().getUnit(), snapshot.getMetadata().getUnit())) { + throw new IllegalArgumentException( + "Cannot merge snapshots: conflicting unit for metric " + + first.getMetadata().getPrometheusName()); + } + totalDataPoints += snapshot.getDataPoints().size(); + } + + List allDataPoints = new ArrayList<>(totalDataPoints); + for (MetricSnapshot snapshot : snapshots) { + allDataPoints.addAll(snapshot.getDataPoints()); + } + + if (first instanceof CounterSnapshot) { + return new CounterSnapshot( + first.getMetadata(), + (Collection) (Object) allDataPoints); + } else if (first instanceof GaugeSnapshot) { + return new GaugeSnapshot( + first.getMetadata(), + (Collection) (Object) allDataPoints); + } else if (first instanceof HistogramSnapshot) { + HistogramSnapshot histFirst = (HistogramSnapshot) first; + return new HistogramSnapshot( + histFirst.isGaugeHistogram(), + first.getMetadata(), + (Collection) (Object) allDataPoints); + } else if (first instanceof SummarySnapshot) { + return new SummarySnapshot( + first.getMetadata(), + (Collection) (Object) allDataPoints); + } else if (first instanceof InfoSnapshot) { + return new InfoSnapshot( + first.getMetadata(), + (Collection) (Object) allDataPoints); + } else if (first instanceof StateSetSnapshot) { + return new StateSetSnapshot( + first.getMetadata(), + (Collection) (Object) allDataPoints); + } else if (first instanceof UnknownSnapshot) { + return new UnknownSnapshot( + first.getMetadata(), + (Collection) (Object) allDataPoints); + } else { + throw new IllegalArgumentException("Unknown snapshot type: " + first.getClass().getName()); + } + } } diff --git a/prometheus-metrics-exposition-textformats/src/test/java/io/prometheus/metrics/expositionformats/DuplicateNamesExpositionTest.java b/prometheus-metrics-exposition-textformats/src/test/java/io/prometheus/metrics/expositionformats/DuplicateNamesExpositionTest.java new file mode 100644 index 000000000..bdd21440f --- /dev/null +++ b/prometheus-metrics-exposition-textformats/src/test/java/io/prometheus/metrics/expositionformats/DuplicateNamesExpositionTest.java @@ -0,0 +1,258 @@ +package io.prometheus.metrics.expositionformats; + +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.assertj.core.api.Assertions.assertThat; + +import io.prometheus.metrics.model.registry.Collector; +import io.prometheus.metrics.model.registry.PrometheusRegistry; +import io.prometheus.metrics.model.snapshots.CounterSnapshot; +import io.prometheus.metrics.model.snapshots.Labels; +import io.prometheus.metrics.model.snapshots.MetricSnapshot; +import io.prometheus.metrics.model.snapshots.MetricSnapshots; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import org.junit.jupiter.api.Test; + +class DuplicateNamesExpositionTest { + + private static PrometheusRegistry getPrometheusRegistry() { + PrometheusRegistry registry = new PrometheusRegistry(); + + registry.register( + new Collector() { + @Override + public MetricSnapshot collect() { + return CounterSnapshot.builder() + .name("api_responses") + .help("API responses") + .dataPoint( + CounterSnapshot.CounterDataPointSnapshot.builder() + .labels(Labels.of("uri", "/hello", "outcome", "SUCCESS")) + .value(100) + .build()) + .build(); + } + + @Override + public String getPrometheusName() { + return "api_responses"; + } + }); + + registry.register( + new Collector() { + @Override + public MetricSnapshot collect() { + return CounterSnapshot.builder() + .name("api_responses") + .help("API responses") + .dataPoint( + CounterSnapshot.CounterDataPointSnapshot.builder() + .labels( + Labels.of("uri", "/hello", "outcome", "FAILURE", "error", "TIMEOUT")) + .value(10) + .build()) + .build(); + } + + @Override + public String getPrometheusName() { + return "api_responses"; + } + }); + return registry; + } + + @Test + void testDuplicateNames_differentLabels_producesValidOutput() throws IOException { + PrometheusRegistry registry = getPrometheusRegistry(); + + MetricSnapshots snapshots = registry.scrape(); + ByteArrayOutputStream out = new ByteArrayOutputStream(); + PrometheusTextFormatWriter writer = PrometheusTextFormatWriter.create(); + writer.write(out, snapshots); + String output = out.toString(UTF_8); + + String expected = + """ + # HELP api_responses_total API responses + # TYPE api_responses_total counter + api_responses_total{error="TIMEOUT",outcome="FAILURE",uri="/hello"} 10.0 + api_responses_total{outcome="SUCCESS",uri="/hello"} 100.0 + """; + + assertThat(output).isEqualTo(expected); + } + + @Test + void testDuplicateNames_multipleDataPoints_producesValidOutput() throws IOException { + PrometheusRegistry registry = new PrometheusRegistry(); + + registry.register( + new Collector() { + @Override + public MetricSnapshot collect() { + return CounterSnapshot.builder() + .name("api_responses") + .help("API responses") + .dataPoint( + CounterSnapshot.CounterDataPointSnapshot.builder() + .labels(Labels.of("uri", "/hello", "outcome", "SUCCESS")) + .value(100) + .build()) + .dataPoint( + CounterSnapshot.CounterDataPointSnapshot.builder() + .labels(Labels.of("uri", "/world", "outcome", "SUCCESS")) + .value(200) + .build()) + .build(); + } + + @Override + public String getPrometheusName() { + return "api_responses"; + } + }); + + registry.register( + new Collector() { + @Override + public MetricSnapshot collect() { + return CounterSnapshot.builder() + .name("api_responses") + .help("API responses") + .dataPoint( + CounterSnapshot.CounterDataPointSnapshot.builder() + .labels( + Labels.of("uri", "/hello", "outcome", "FAILURE", "error", "TIMEOUT")) + .value(10) + .build()) + .dataPoint( + CounterSnapshot.CounterDataPointSnapshot.builder() + .labels( + Labels.of("uri", "/world", "outcome", "FAILURE", "error", "NOT_FOUND")) + .value(5) + .build()) + .build(); + } + + @Override + public String getPrometheusName() { + return "api_responses"; + } + }); + + MetricSnapshots snapshots = registry.scrape(); + ByteArrayOutputStream out = new ByteArrayOutputStream(); + PrometheusTextFormatWriter writer = PrometheusTextFormatWriter.create(); + writer.write(out, snapshots); + String output = out.toString(UTF_8); + + String expected = + """ + # HELP api_responses_total API responses + # TYPE api_responses_total counter + api_responses_total{error="NOT_FOUND",outcome="FAILURE",uri="/world"} 5.0 + api_responses_total{error="TIMEOUT",outcome="FAILURE",uri="/hello"} 10.0 + api_responses_total{outcome="SUCCESS",uri="/hello"} 100.0 + api_responses_total{outcome="SUCCESS",uri="/world"} 200.0 + """; + assertThat(output).isEqualTo(expected); + } + + @Test + void testOpenMetricsFormat_withDuplicateNames() throws IOException { + PrometheusRegistry registry = getPrometheusRegistry(); + + MetricSnapshots snapshots = registry.scrape(); + ByteArrayOutputStream out = new ByteArrayOutputStream(); + OpenMetricsTextFormatWriter writer = new OpenMetricsTextFormatWriter(false, false); + writer.write(out, snapshots); + String output = out.toString(UTF_8); + + String expected = + """ + # TYPE api_responses counter + # HELP api_responses API responses + api_responses_total{error="TIMEOUT",outcome="FAILURE",uri="/hello"} 10.0 + api_responses_total{outcome="SUCCESS",uri="/hello"} 100.0 + # EOF + """; + assertThat(output).isEqualTo(expected); + } + + @Test + void testDuplicateNames_withCreatedTimestamps_emitsSingleHelpTypeAndNoDuplicateCreatedSeries() + throws IOException { + long createdTs = 1672850385800L; + PrometheusRegistry registry = new PrometheusRegistry(); + + registry.register( + new Collector() { + @Override + public MetricSnapshot collect() { + return CounterSnapshot.builder() + .name("api_responses") + .help("API responses") + .dataPoint( + CounterSnapshot.CounterDataPointSnapshot.builder() + .labels(Labels.of("uri", "/hello", "outcome", "SUCCESS")) + .value(100) + .createdTimestampMillis(createdTs) + .build()) + .build(); + } + + @Override + public String getPrometheusName() { + return "api_responses"; + } + }); + + registry.register( + new Collector() { + @Override + public MetricSnapshot collect() { + return CounterSnapshot.builder() + .name("api_responses") + .help("API responses") + .dataPoint( + CounterSnapshot.CounterDataPointSnapshot.builder() + .labels( + Labels.of("uri", "/hello", "outcome", "FAILURE", "error", "TIMEOUT")) + .value(10) + .createdTimestampMillis(createdTs + 1000) + .build()) + .build(); + } + + @Override + public String getPrometheusName() { + return "api_responses"; + } + }); + + MetricSnapshots snapshots = registry.scrape(); + ByteArrayOutputStream out = new ByteArrayOutputStream(); + PrometheusTextFormatWriter writer = + PrometheusTextFormatWriter.builder().setIncludeCreatedTimestamps(true).build(); + writer.write(out, snapshots); + String output = out.toString(UTF_8); + + // Merged snapshots: one metric family with two data points. Created-timestamp section uses + // merged snapshots too, so single HELP/TYPE for _created and one _created line per label set. + String expected = + """ + # HELP api_responses_total API responses + # TYPE api_responses_total counter + api_responses_total{error="TIMEOUT",outcome="FAILURE",uri="/hello"} 10.0 + api_responses_total{outcome="SUCCESS",uri="/hello"} 100.0 + # HELP api_responses_created API responses + # TYPE api_responses_created gauge + api_responses_created{error="TIMEOUT",outcome="FAILURE",uri="/hello"} 1672850386800 + api_responses_created{outcome="SUCCESS",uri="/hello"} 1672850385800 + """; + + assertThat(output).isEqualTo(expected); + } +} diff --git a/prometheus-metrics-exposition-textformats/src/test/java/io/prometheus/metrics/expositionformats/ExpositionFormatWriterTest.java b/prometheus-metrics-exposition-textformats/src/test/java/io/prometheus/metrics/expositionformats/ExpositionFormatWriterTest.java index aa50d9876..51b4de8bb 100644 --- a/prometheus-metrics-exposition-textformats/src/test/java/io/prometheus/metrics/expositionformats/ExpositionFormatWriterTest.java +++ b/prometheus-metrics-exposition-textformats/src/test/java/io/prometheus/metrics/expositionformats/ExpositionFormatWriterTest.java @@ -2,15 +2,32 @@ import static org.assertj.core.api.Assertions.assertThat; +import io.prometheus.metrics.config.EscapingScheme; import io.prometheus.metrics.model.snapshots.MetricSnapshots; +import java.io.ByteArrayOutputStream; +import java.io.IOException; import org.junit.jupiter.api.Test; class ExpositionFormatWriterTest { private final ExpositionFormatWriter writer = OpenMetricsTextFormatWriter.create(); + @Test + void write() throws IOException { + MetricSnapshots snapshots = new MetricSnapshots(); + ByteArrayOutputStream out = new ByteArrayOutputStream(); + writer.write(out, snapshots, EscapingScheme.ALLOW_UTF8); + assertThat(out).hasToString("# EOF\n"); + + out.reset(); + writer.write(out, snapshots); + assertThat(out).hasToString("# EOF\n"); + } + @Test void toDebugString() { + assertThat(writer.toDebugString(new MetricSnapshots(), EscapingScheme.ALLOW_UTF8)) + .isEqualTo("# EOF\n"); assertThat(writer.toDebugString(new MetricSnapshots())).isEqualTo("# EOF\n"); } diff --git a/prometheus-metrics-exposition-textformats/src/test/java/io/prometheus/metrics/expositionformats/ExpositionFormatsTest.java b/prometheus-metrics-exposition-textformats/src/test/java/io/prometheus/metrics/expositionformats/ExpositionFormatsTest.java index d6da09537..b43688bbb 100644 --- a/prometheus-metrics-exposition-textformats/src/test/java/io/prometheus/metrics/expositionformats/ExpositionFormatsTest.java +++ b/prometheus-metrics-exposition-textformats/src/test/java/io/prometheus/metrics/expositionformats/ExpositionFormatsTest.java @@ -1,7 +1,9 @@ package io.prometheus.metrics.expositionformats; +import static java.nio.charset.StandardCharsets.UTF_8; import static org.assertj.core.api.Assertions.assertThat; +import io.prometheus.metrics.config.EscapingScheme; import io.prometheus.metrics.model.snapshots.ClassicHistogramBuckets; import io.prometheus.metrics.model.snapshots.CounterSnapshot; import io.prometheus.metrics.model.snapshots.CounterSnapshot.CounterDataPointSnapshot; @@ -25,7 +27,10 @@ import io.prometheus.metrics.model.snapshots.UnknownSnapshot.UnknownDataPointSnapshot; import java.io.ByteArrayOutputStream; import java.io.IOException; +import java.util.concurrent.atomic.AtomicInteger; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; class ExpositionFormatsTest { @@ -104,80 +109,80 @@ void init() { } @Test - public void testCounterComplete() throws IOException { + void testCounterComplete() throws IOException { String openMetricsText = - "# TYPE service_time_seconds counter\n" - + "# UNIT service_time_seconds seconds\n" - + "# HELP service_time_seconds total time spent serving\n" - + "service_time_seconds_total{path=\"/hello\",status=\"200\"} 0.8 " + "# TYPE service:time_seconds counter\n" + + "# UNIT service:time_seconds seconds\n" + + "# HELP service:time_seconds total time spent serving\n" + + "service:time_seconds_total{path=\"/hello\",status=\"200\"} 0.8 " + scrapeTimestamp1s + " # " + exemplar1String + "\n" - + "service_time_seconds_created{path=\"/hello\",status=\"200\"} " + + "service:time_seconds_created{path=\"/hello\",status=\"200\"} " + createdTimestamp1s + " " + scrapeTimestamp1s + "\n" - + "service_time_seconds_total{path=\"/hello\",status=\"500\"} 0.9 " + + "service:time_seconds_total{path=\"/hello\",status=\"500\"} 0.9 " + scrapeTimestamp2s + " # " + exemplar2String + "\n" - + "service_time_seconds_created{path=\"/hello\",status=\"500\"} " + + "service:time_seconds_created{path=\"/hello\",status=\"500\"} " + createdTimestamp2s + " " + scrapeTimestamp2s + "\n" + "# EOF\n"; String prometheusText = - "# HELP service_time_seconds_total total time spent serving\n" - + "# TYPE service_time_seconds_total counter\n" - + "service_time_seconds_total{path=\"/hello\",status=\"200\"} 0.8 " + "# HELP service:time_seconds_total total time spent serving\n" + + "# TYPE service:time_seconds_total counter\n" + + "service:time_seconds_total{path=\"/hello\",status=\"200\"} 0.8 " + scrapeTimestamp1s + "\n" - + "service_time_seconds_total{path=\"/hello\",status=\"500\"} 0.9 " + + "service:time_seconds_total{path=\"/hello\",status=\"500\"} 0.9 " + scrapeTimestamp2s + "\n" - + "# HELP service_time_seconds_created total time spent serving\n" - + "# TYPE service_time_seconds_created gauge\n" - + "service_time_seconds_created{path=\"/hello\",status=\"200\"} " + + "# HELP service:time_seconds_created total time spent serving\n" + + "# TYPE service:time_seconds_created gauge\n" + + "service:time_seconds_created{path=\"/hello\",status=\"200\"} " + createdTimestamp1s + " " + scrapeTimestamp1s + "\n" - + "service_time_seconds_created{path=\"/hello\",status=\"500\"} " + + "service:time_seconds_created{path=\"/hello\",status=\"500\"} " + createdTimestamp2s + " " + scrapeTimestamp2s + "\n"; String openMetricsTextWithoutCreated = - "# TYPE service_time_seconds counter\n" - + "# UNIT service_time_seconds seconds\n" - + "# HELP service_time_seconds total time spent serving\n" - + "service_time_seconds_total{path=\"/hello\",status=\"200\"} 0.8 " + "# TYPE service:time_seconds counter\n" + + "# UNIT service:time_seconds seconds\n" + + "# HELP service:time_seconds total time spent serving\n" + + "service:time_seconds_total{path=\"/hello\",status=\"200\"} 0.8 " + scrapeTimestamp1s + " # " + exemplar1String + "\n" - + "service_time_seconds_total{path=\"/hello\",status=\"500\"} 0.9 " + + "service:time_seconds_total{path=\"/hello\",status=\"500\"} 0.9 " + scrapeTimestamp2s + " # " + exemplar2String + "\n" + "# EOF\n"; String prometheusTextWithoutCreated = - "# HELP service_time_seconds_total total time spent serving\n" - + "# TYPE service_time_seconds_total counter\n" - + "service_time_seconds_total{path=\"/hello\",status=\"200\"} 0.8 " + "# HELP service:time_seconds_total total time spent serving\n" + + "# TYPE service:time_seconds_total counter\n" + + "service:time_seconds_total{path=\"/hello\",status=\"200\"} 0.8 " + scrapeTimestamp1s + "\n" - + "service_time_seconds_total{path=\"/hello\",status=\"500\"} 0.9 " + + "service:time_seconds_total{path=\"/hello\",status=\"500\"} 0.9 " + scrapeTimestamp2s + "\n"; String prometheusProtobuf = // @formatter:off - "name: \"service_time_seconds_total\" " + "name: \"service:time_seconds_total\" " + "help: \"total time spent serving\" " + "type: COUNTER " + "metric { " @@ -187,6 +192,7 @@ public void testCounterComplete() throws IOException { + "value: 0.8 " + exemplar1protoString + " " + + "created_timestamp { seconds: 1672850385 nanos: 800000000 } " + "} " + "timestamp_ms: 1672850685829 " + "} " @@ -197,6 +203,7 @@ public void testCounterComplete() throws IOException { + "value: 0.9 " + exemplar2protoString + " " + + "created_timestamp { seconds: 1672850285 } " + "} " + "timestamp_ms: 1672850585820 " + "}"; @@ -204,7 +211,7 @@ public void testCounterComplete() throws IOException { CounterSnapshot counter = CounterSnapshot.builder() - .name("service_time_seconds") + .name("service:time_seconds") .help("total time spent serving") .unit(Unit.SECONDS) .dataPoint( @@ -232,7 +239,7 @@ public void testCounterComplete() throws IOException { } @Test - public void testCounterMinimal() throws IOException { + void testCounterMinimal() throws IOException { String openMetricsText = """ # TYPE my_counter counter @@ -259,13 +266,13 @@ public void testCounterMinimal() throws IOException { } @Test - public void testCounterWithDots() throws IOException { + void testCounterWithDots() throws IOException { String openMetricsText = - "# TYPE my_request_count counter\n" - + "my_request_count_total{http_path=\"/hello\"} 3.0 # " - + exemplarWithDotsString - + "\n" - + "# EOF\n"; + """ + # TYPE U__my_2e_request_2e_count counter + U__my_2e_request_2e_count_total{U__http_2e_path="/hello"} 3.0 # {U__some_2e_exemplar_2e_key="some value"} 3.0 1690298864.383 + # EOF + """; String prometheusText = """ # TYPE my_request_count_total counter @@ -301,7 +308,7 @@ public void testCounterWithDots() throws IOException { } @Test - public void testGaugeComplete() throws IOException { + void testGaugeComplete() throws IOException { String openMetricsText = "# TYPE disk_usage_ratio gauge\n" + "# UNIT disk_usage_ratio ratio\n" @@ -382,7 +389,7 @@ public void testGaugeComplete() throws IOException { } @Test - public void testGaugeMinimal() throws IOException { + void testGaugeMinimal() throws IOException { String openMetricsText = """ # TYPE temperature_centigrade gauge @@ -409,13 +416,13 @@ public void testGaugeMinimal() throws IOException { } @Test - public void testGaugeWithDots() throws IOException { + void testGaugeWithDots() throws IOException { String openMetricsText = """ - # TYPE my_temperature_celsius gauge - # UNIT my_temperature_celsius celsius - # HELP my_temperature_celsius Temperature - my_temperature_celsius{location_id="data-center-1"} 23.0 + # TYPE U__my_2e_temperature_2e_celsius gauge + # UNIT U__my_2e_temperature_2e_celsius celsius + # HELP U__my_2e_temperature_2e_celsius Temperature + U__my_2e_temperature_2e_celsius{U__location_2e_id="data-center-1"} 23.0 # EOF """; String openMetricsTextWithExemplarsOnAllTimeSeries = @@ -465,7 +472,41 @@ public void testGaugeWithDots() throws IOException { } @Test - public void testSummaryComplete() throws IOException { + void testGaugeUTF8() throws IOException { + String prometheusText = + """ + # HELP "gauge.name" gauge\\ndoc\\nstr"ing + # TYPE "gauge.name" gauge + {"gauge.name","name.1"="Björn","name*2"="佖佥"} 3.14E42 + {"gauge.name","name.1"="val with\\nnew line","name*2"="val with \\\\backslash and \\"quotes\\""} +Inf + """; + GaugeSnapshot gauge = + GaugeSnapshot.builder() + .name("gauge.name") + .help("gauge\ndoc\nstr\"ing") + .dataPoint( + GaugeDataPointSnapshot.builder() + .value(Double.POSITIVE_INFINITY) + .labels( + Labels.builder() + .label("name.1", "val with\nnew line") + .label("name*2", "val with \\backslash and \"quotes\"") + .build()) + .build()) + .dataPoint( + GaugeDataPointSnapshot.builder() + .value(3.14e42) + .labels(Labels.builder().label("name.1", "Björn").label("name*2", "佖佥").build()) + .build()) + .build(); + ByteArrayOutputStream out = new ByteArrayOutputStream(); + getPrometheusWriter(PrometheusTextFormatWriter.builder().setIncludeCreatedTimestamps(true)) + .write(out, MetricSnapshots.of((MetricSnapshot) gauge), EscapingScheme.ALLOW_UTF8); + assertThat(out).hasToString(prometheusText); + } + + @Test + void testSummaryComplete() throws IOException { String openMetricsText = "# TYPE http_request_duration_seconds summary\n" + "# UNIT http_request_duration_seconds seconds\n" @@ -758,7 +799,7 @@ public void testSummaryComplete() throws IOException { } @Test - public void testSummaryWithoutQuantiles() throws IOException { + void testSummaryWithoutQuantiles() throws IOException { String openMetricsText = """ # TYPE latency_seconds summary @@ -802,7 +843,7 @@ public void testSummaryWithoutQuantiles() throws IOException { } @Test - public void testSummaryNoCountAndSum() throws IOException { + void testSummaryNoCountAndSum() throws IOException { String openMetricsText = """ # TYPE latency_seconds summary @@ -840,7 +881,7 @@ public void testSummaryNoCountAndSum() throws IOException { } @Test - public void testSummaryJustCount() throws IOException { + void testSummaryJustCount() throws IOException { String openMetricsText = """ # TYPE latency_seconds summary @@ -875,7 +916,7 @@ public void testSummaryJustCount() throws IOException { } @Test - public void testSummaryJustSum() throws IOException { + void testSummaryJustSum() throws IOException { String openMetricsText = """ # TYPE latency_seconds summary @@ -910,7 +951,7 @@ public void testSummaryJustSum() throws IOException { } @Test - public void testSummaryEmptyData() throws IOException { + void testSummaryEmptyData() throws IOException { // SummaryData can be present but empty (no count, no sum, no quantiles). // This should be treated like no data is present. SummarySnapshot summary = @@ -928,7 +969,7 @@ public void testSummaryEmptyData() throws IOException { } @Test - public void testSummaryEmptyAndNonEmpty() throws IOException { + void testSummaryEmptyAndNonEmpty() throws IOException { String openMetricsText = """ # TYPE latency_seconds summary @@ -974,14 +1015,14 @@ public void testSummaryEmptyAndNonEmpty() throws IOException { } @Test - public void testSummaryWithDots() throws IOException { + void testSummaryWithDots() throws IOException { String openMetricsText = """ - # TYPE my_request_duration_seconds summary - # UNIT my_request_duration_seconds seconds - # HELP my_request_duration_seconds Request duration in seconds - my_request_duration_seconds_count{http_path="/hello"} 1 - my_request_duration_seconds_sum{http_path="/hello"} 0.03 + # TYPE U__my_2e_request_2e_duration_2e_seconds summary + # UNIT U__my_2e_request_2e_duration_2e_seconds seconds + # HELP U__my_2e_request_2e_duration_2e_seconds Request duration in seconds + U__my_2e_request_2e_duration_2e_seconds_count{U__http_2e_path="/hello"} 1 + U__my_2e_request_2e_duration_2e_seconds_sum{U__http_2e_path="/hello"} 0.03 # EOF """; String openMetricsTextWithExemplarsOnAllTimeSeries = @@ -1032,7 +1073,7 @@ public void testSummaryWithDots() throws IOException { } @Test - public void testClassicHistogramComplete() throws Exception { + void testClassicHistogramComplete() throws Exception { String openMetricsText = "# TYPE response_size_bytes histogram\n" + "# UNIT response_size_bytes bytes\n" @@ -1338,7 +1379,7 @@ public void testClassicHistogramComplete() throws Exception { } @Test - public void testClassicHistogramMinimal() throws Exception { + void testClassicHistogramMinimal() throws Exception { // In OpenMetrics a histogram can have a _count if and only if it has a _sum. // In Prometheus format, a histogram can have a _count without a _sum. String openMetricsText = @@ -1386,7 +1427,38 @@ public void testClassicHistogramMinimal() throws Exception { } @Test - public void testClassicHistogramCountAndSum() throws Exception { + void testClassicHistogramMinimalWithDots() throws Exception { + String openMetricsText = + """ + # TYPE "request.latency_seconds" histogram + {"request.latency_seconds_bucket",le="+Inf"} 2 + # EOF + """; + String prometheusText = + """ + # TYPE request_latency_seconds histogram + request_latency_seconds_bucket{le="+Inf"} 2 + request_latency_seconds_count 2 + """; + HistogramSnapshot histogram = + HistogramSnapshot.builder() + .name("request.latency_seconds") + .dataPoint( + HistogramSnapshot.HistogramDataPointSnapshot.builder() + .classicHistogramBuckets( + ClassicHistogramBuckets.builder() + .bucket(Double.POSITIVE_INFINITY, 2) + .build()) + .build()) + .build(); + // test that the name and label are separated with commas + assertOpenMetricsText(openMetricsText, histogram, EscapingScheme.ALLOW_UTF8); + assertPrometheusText(prometheusText, histogram); + assertPrometheusTextWithoutCreated(prometheusText, histogram); + } + + @Test + void testClassicHistogramCountAndSum() throws Exception { String openMetricsText = """ # TYPE request_latency_seconds histogram @@ -1437,7 +1509,7 @@ public void testClassicHistogramCountAndSum() throws Exception { } @Test - public void testClassicGaugeHistogramComplete() throws IOException { + void testClassicGaugeHistogramComplete() throws IOException { String openMetricsText = "# TYPE cache_size_bytes gaugehistogram\n" + "# UNIT cache_size_bytes bytes\n" @@ -1733,7 +1805,7 @@ public void testClassicGaugeHistogramComplete() throws IOException { } @Test - public void testClassicGaugeHistogramMinimal() throws IOException { + void testClassicGaugeHistogramMinimal() throws IOException { // In OpenMetrics a histogram can have a _count if and only if it has a _sum. // In Prometheus format, a histogram can have a _count without a _sum. String openMetricsText = @@ -1783,7 +1855,7 @@ public void testClassicGaugeHistogramMinimal() throws IOException { } @Test - public void testClassicGaugeHistogramCountAndSum() throws IOException { + void testClassicGaugeHistogramCountAndSum() throws IOException { String openMetricsText = """ # TYPE queue_size_bytes gaugehistogram @@ -1837,17 +1909,17 @@ public void testClassicGaugeHistogramCountAndSum() throws IOException { } @Test - public void testClassicHistogramWithDots() throws IOException { + void testClassicHistogramWithDots() throws IOException { String openMetricsText = - "# TYPE my_request_duration_seconds histogram\n" - + "# UNIT my_request_duration_seconds seconds\n" - + "# HELP my_request_duration_seconds Request duration in seconds\n" - + "my_request_duration_seconds_bucket{http_path=\"/hello\",le=\"+Inf\"} 130 # " - + exemplarWithDotsString - + "\n" - + "my_request_duration_seconds_count{http_path=\"/hello\"} 130\n" - + "my_request_duration_seconds_sum{http_path=\"/hello\"} 0.01\n" - + "# EOF\n"; + """ + # TYPE U__my_2e_request_2e_duration_2e_seconds histogram + # UNIT U__my_2e_request_2e_duration_2e_seconds seconds + # HELP U__my_2e_request_2e_duration_2e_seconds Request duration in seconds + U__my_2e_request_2e_duration_2e_seconds_bucket{U__http_2e_path="/hello",le="+Inf"} 130 # {U__some_2e_exemplar_2e_key="some value"} 3.0 1690298864.383 + U__my_2e_request_2e_duration_2e_seconds_count{U__http_2e_path="/hello"} 130 + U__my_2e_request_2e_duration_2e_seconds_sum{U__http_2e_path="/hello"} 0.01 + # EOF + """; String openMetricsTextWithExemplarsOnAllTimeSeries = "# TYPE my_request_duration_seconds histogram\n" + "# UNIT my_request_duration_seconds seconds\n" @@ -1909,7 +1981,7 @@ public void testClassicHistogramWithDots() throws IOException { } @Test - public void testNativeHistogramComplete() throws IOException { + void testNativeHistogramComplete() throws IOException { String openMetricsText = "# TYPE response_size_bytes histogram\n" + "# UNIT response_size_bytes bytes\n" @@ -2220,7 +2292,7 @@ public void testNativeHistogramComplete() throws IOException { } @Test - public void testNativeHistogramMinimal() throws IOException { + void testNativeHistogramMinimal() throws IOException { String openMetricsText = """ # TYPE latency_seconds histogram @@ -2258,17 +2330,17 @@ public void testNativeHistogramMinimal() throws IOException { } @Test - public void testNativeHistogramWithDots() throws IOException { + void testNativeHistogramWithDots() throws IOException { String openMetricsText = - "# TYPE my_request_duration_seconds histogram\n" - + "# UNIT my_request_duration_seconds seconds\n" - + "# HELP my_request_duration_seconds Request duration in seconds\n" - + "my_request_duration_seconds_bucket{http_path=\"/hello\",le=\"+Inf\"} 4 # " - + exemplarWithDotsString - + "\n" - + "my_request_duration_seconds_count{http_path=\"/hello\"} 4\n" - + "my_request_duration_seconds_sum{http_path=\"/hello\"} 3.2\n" - + "# EOF\n"; + """ + # TYPE U__my_2e_request_2e_duration_2e_seconds histogram + # UNIT U__my_2e_request_2e_duration_2e_seconds seconds + # HELP U__my_2e_request_2e_duration_2e_seconds Request duration in seconds + U__my_2e_request_2e_duration_2e_seconds_bucket{U__http_2e_path="/hello",le="+Inf"} 4 # {U__some_2e_exemplar_2e_key="some value"} 3.0 1690298864.383 + U__my_2e_request_2e_duration_2e_seconds_count{U__http_2e_path="/hello"} 4 + U__my_2e_request_2e_duration_2e_seconds_sum{U__http_2e_path="/hello"} 3.2 + # EOF + """; String openMetricsTextWithExemplarsOnAllTimeSeries = "# TYPE my_request_duration_seconds histogram\n" + "# UNIT my_request_duration_seconds seconds\n" @@ -2337,7 +2409,7 @@ public void testNativeHistogramWithDots() throws IOException { // TODO: Gauge Native Histogram @Test - public void testInfo() throws IOException { + void testInfo() throws IOException { String openMetrics = """ # TYPE version info @@ -2367,12 +2439,12 @@ public void testInfo() throws IOException { } @Test - public void testInfoWithDots() throws IOException { + void testInfoWithDots() throws IOException { String openMetricsText = """ - # TYPE jvm_status info - # HELP jvm_status JVM status info - jvm_status_info{jvm_version="1.2.3"} 1 + # TYPE U__jvm_2e_status info + # HELP U__jvm_2e_status JVM status info + U__jvm_2e_status_info{U__jvm_2e_version="1.2.3"} 1 # EOF """; String prometheusText = @@ -2406,7 +2478,7 @@ public void testInfoWithDots() throws IOException { } @Test - public void testStateSetComplete() throws IOException { + void testStateSetComplete() throws IOException { String openMetrics = "# TYPE state stateset\n" + "# HELP state complete state set example\n" @@ -2464,7 +2536,7 @@ public void testStateSetComplete() throws IOException { } @Test - public void testStateSetMinimal() throws IOException { + void testStateSetMinimal() throws IOException { String openMetrics = """ # TYPE state stateset @@ -2494,13 +2566,13 @@ public void testStateSetMinimal() throws IOException { } @Test - public void testStateSetWithDots() throws IOException { + void testStateSetWithDots() throws IOException { String openMetricsText = """ - # TYPE my_application_state stateset - # HELP my_application_state My application state - my_application_state{data_center="us east",my_application_state="feature.enabled"} 1 - my_application_state{data_center="us east",my_application_state="is.alpha.version"} 0 + # TYPE U__my_2e_application_2e_state stateset + # HELP U__my_2e_application_2e_state My application state + U__my_2e_application_2e_state{U__data_2e_center="us east",U__my_2e_application_2e_state="feature.enabled"} 1 + U__my_2e_application_2e_state{U__data_2e_center="us east",U__my_2e_application_2e_state="is.alpha.version"} 0 # EOF """; String prometheusText = @@ -2542,7 +2614,7 @@ public void testStateSetWithDots() throws IOException { } @Test - public void testUnknownComplete() throws IOException { + void testUnknownComplete() throws IOException { String openMetrics = "# TYPE my_special_thing_bytes unknown\n" + "# UNIT my_special_thing_bytes bytes\n" @@ -2607,7 +2679,7 @@ public void testUnknownComplete() throws IOException { } @Test - public void testUnknownMinimal() throws IOException { + void testUnknownMinimal() throws IOException { String openMetrics = """ # TYPE other unknown @@ -2631,13 +2703,13 @@ public void testUnknownMinimal() throws IOException { } @Test - public void testUnknownWithDots() throws IOException { + void testUnknownWithDots() throws IOException { String openMetrics = """ - # TYPE some_unknown_metric_bytes unknown - # UNIT some_unknown_metric_bytes bytes - # HELP some_unknown_metric_bytes help message - some_unknown_metric_bytes{test_env="7"} 0.7 + # TYPE U__some_2e_unknown_2e_metric__bytes unknown + # UNIT U__some_2e_unknown_2e_metric__bytes bytes + # HELP U__some_2e_unknown_2e_metric__bytes help message + U__some_2e_unknown_2e_metric__bytes{U__test_2e_env="7"} 0.7 # EOF """; String openMetricsWithExemplarsOnAllTimeSeries = @@ -2684,7 +2756,7 @@ public void testUnknownWithDots() throws IOException { } @Test - public void testHelpEscape() throws IOException { + void testHelpEscape() throws IOException { String openMetrics = """ # TYPE test counter @@ -2711,7 +2783,7 @@ public void testHelpEscape() throws IOException { } @Test - public void testLabelValueEscape() throws IOException { + void testLabelValueEscape() throws IOException { String openMetrics = """ # TYPE test counter @@ -2737,11 +2809,59 @@ public void testLabelValueEscape() throws IOException { assertPrometheusText(prometheus, counter); } + @ParameterizedTest + @CsvSource({ + "'application/vnd.google.protobuf;proto=io.prometheus.client.MetricFamily;encoding=delimited', 'application/vnd.google.protobuf; proto=io.prometheus.client.MetricFamily; encoding=delimited; escaping=underscores'", + "'text/plain;version=0.0.4', 'text/plain; version=0.0.4; charset=utf-8; escaping=underscores'", + "'application/vnd.google.protobuf;proto=io.prometheus.client.MetricFamily;encoding=delimited; escaping=allow-utf-8', 'application/vnd.google.protobuf; proto=io.prometheus.client.MetricFamily; encoding=delimited; escaping=allow-utf-8'", + "'application/openmetrics-text', 'application/openmetrics-text; version=1.0.0; charset=utf-8; escaping=underscores'", + "'application/openmetrics-text;version=0.0.1; escaping=underscores', 'application/openmetrics-text; version=1.0.0; charset=utf-8; escaping=underscores'", + "'text/plain;version=0.0.4; escaping=allow-utf-8', 'text/plain; version=0.0.4; charset=utf-8; escaping=allow-utf-8'" + }) + public void testFindWriter(String acceptHeaderValue, String expectedFmt) { + ExpositionFormats expositionFormats = ExpositionFormats.init(); + EscapingScheme escapingScheme = EscapingScheme.fromAcceptHeader(acceptHeaderValue); + ExpositionFormatWriter writer = expositionFormats.findWriter(acceptHeaderValue); + assertThat(writer.getContentType() + escapingScheme.toHeaderFormat()).hasToString(expectedFmt); + } + + @Test + void testWrite() throws IOException { + ByteArrayOutputStream buff = new ByteArrayOutputStream(new AtomicInteger(2 << 9).get() + 1024); + ExpositionFormats expositionFormats = ExpositionFormats.init(); + UnknownSnapshot unknown = + UnknownSnapshot.builder() + .name("foo_metric") + .dataPoint(UnknownDataPointSnapshot.builder().value(1.234).build()) + .build(); + + String acceptHeaderValue = "text/plain; version=0.0.4; charset=utf-8"; + EscapingScheme escapingScheme = EscapingScheme.fromAcceptHeader(acceptHeaderValue); + ExpositionFormatWriter textWriter = expositionFormats.findWriter(acceptHeaderValue); + + textWriter.write(buff, MetricSnapshots.of(unknown), escapingScheme); + byte[] out = buff.toByteArray(); + assertThat(out.length).isNotEqualTo(0); + + String expected = + """ + # TYPE foo_metric untyped + foo_metric 1.234 + """; + + assertThat(new String(out, UTF_8)).hasToString(expected); + } + private void assertOpenMetricsText(String expected, MetricSnapshot snapshot) throws IOException { + assertOpenMetricsText(expected, snapshot, EscapingScheme.VALUE_ENCODING_ESCAPING); + } + + private void assertOpenMetricsText( + String expected, MetricSnapshot snapshot, EscapingScheme escapingScheme) throws IOException { ByteArrayOutputStream out = new ByteArrayOutputStream(); OpenMetricsTextFormatWriter writer = OpenMetricsTextFormatWriter.builder().setCreatedTimestampsEnabled(true).build(); - writer.write(out, MetricSnapshots.of(snapshot)); + writer.write(out, MetricSnapshots.of(snapshot), escapingScheme); assertThat(out).hasToString(expected); } @@ -2753,7 +2873,7 @@ private void assertOpenMetricsTextWithExemplarsOnAllTimeSeries( .setCreatedTimestampsEnabled(true) .setExemplarsOnAllMetricTypesEnabled(true) .build(); - writer.write(out, MetricSnapshots.of(snapshot)); + writer.write(out, MetricSnapshots.of(snapshot), EscapingScheme.UNDERSCORE_ESCAPING); assertThat(out).hasToString(expected); } @@ -2761,15 +2881,14 @@ private void assertOpenMetricsTextWithoutCreated(String expected, MetricSnapshot throws IOException { ByteArrayOutputStream out = new ByteArrayOutputStream(); OpenMetricsTextFormatWriter writer = OpenMetricsTextFormatWriter.create(); - writer.write(out, MetricSnapshots.of(snapshot)); + writer.write(out, MetricSnapshots.of(snapshot), EscapingScheme.UNDERSCORE_ESCAPING); assertThat(out).hasToString(expected); } private void assertPrometheusText(String expected, MetricSnapshot snapshot) throws IOException { ByteArrayOutputStream out = new ByteArrayOutputStream(); - getPrometheusWriter(PrometheusTextFormatWriter.builder().setIncludeCreatedTimestamps(true)) - .write(out, MetricSnapshots.of(snapshot)); + .write(out, MetricSnapshots.of(snapshot), EscapingScheme.UNDERSCORE_ESCAPING); assertThat(out).hasToString(expected); } @@ -2783,7 +2902,7 @@ private void assertPrometheusTextWithoutCreated(String expected, MetricSnapshot throws IOException { ByteArrayOutputStream out = new ByteArrayOutputStream(); getPrometheusWriter(PrometheusTextFormatWriter.builder()) - .write(out, MetricSnapshots.of(snapshot)); + .write(out, MetricSnapshots.of(snapshot), EscapingScheme.UNDERSCORE_ESCAPING); assertThat(out).hasToString(expected); } diff --git a/prometheus-metrics-exposition-textformats/src/test/java/io/prometheus/metrics/expositionformats/PrometheusProtobufWriterTest.java b/prometheus-metrics-exposition-textformats/src/test/java/io/prometheus/metrics/expositionformats/PrometheusProtobufWriterTest.java index 814154325..59b413d81 100644 --- a/prometheus-metrics-exposition-textformats/src/test/java/io/prometheus/metrics/expositionformats/PrometheusProtobufWriterTest.java +++ b/prometheus-metrics-exposition-textformats/src/test/java/io/prometheus/metrics/expositionformats/PrometheusProtobufWriterTest.java @@ -3,6 +3,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatCode; +import io.prometheus.metrics.config.EscapingScheme; import org.junit.jupiter.api.Test; class PrometheusProtobufWriterTest { @@ -12,6 +13,13 @@ class PrometheusProtobufWriterTest { @Test void accepts() { assertThat(writer.accepts(null)).isFalse(); + assertThat(writer.accepts("text/plain")).isFalse(); + assertThat(writer.accepts("application/vnd.google.protobuf")).isFalse(); + assertThat(writer.accepts("proto=io.prometheus.client.MetricFamily")).isFalse(); + assertThat( + writer.accepts( + "application/vnd.google.protobuf; proto=io.prometheus.client.MetricFamily")) + .isTrue(); } @Test @@ -24,13 +32,18 @@ void getContentType() { @Test void write() { - assertThatCode(() -> writer.write(null, null)) + assertThatCode(() -> writer.write(null, null, EscapingScheme.ALLOW_UTF8)) .isInstanceOf(UnsupportedOperationException.class); } @Test void toDebugString() { - assertThatCode(() -> writer.toDebugString(null)) + assertThatCode(() -> writer.toDebugString(null, EscapingScheme.ALLOW_UTF8)) .isInstanceOf(UnsupportedOperationException.class); } + + @Test + void isAvailable() { + assertThat(writer.isAvailable()).isFalse(); + } } diff --git a/prometheus-metrics-exposition-textformats/src/test/java/io/prometheus/metrics/expositionformats/TextFormatUtilTest.java b/prometheus-metrics-exposition-textformats/src/test/java/io/prometheus/metrics/expositionformats/TextFormatUtilTest.java index 3f3558160..3a6fea740 100644 --- a/prometheus-metrics-exposition-textformats/src/test/java/io/prometheus/metrics/expositionformats/TextFormatUtilTest.java +++ b/prometheus-metrics-exposition-textformats/src/test/java/io/prometheus/metrics/expositionformats/TextFormatUtilTest.java @@ -3,6 +3,11 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; +import io.prometheus.metrics.model.snapshots.ClassicHistogramBuckets; +import io.prometheus.metrics.model.snapshots.CounterSnapshot; +import io.prometheus.metrics.model.snapshots.HistogramSnapshot; +import io.prometheus.metrics.model.snapshots.Labels; +import io.prometheus.metrics.model.snapshots.MetricSnapshots; import java.io.IOException; import java.io.StringWriter; import org.junit.jupiter.api.Test; @@ -19,7 +24,7 @@ void writeEscapedLabelValue() throws IOException { private static String escape(String s) throws IOException { StringWriter writer = new StringWriter(); - TextFormatUtil.writeEscapedLabelValue(writer, s); + TextFormatUtil.writeEscapedString(writer, s); return writer.toString(); } @@ -34,4 +39,128 @@ private static String writePrometheusTimestamp(boolean timestampsInMs) throws IO TextFormatUtil.writePrometheusTimestamp(writer, 1000, timestampsInMs); return writer.toString(); } + + @Test + void testMergeDuplicates_sameName_mergesDataPoints() { + CounterSnapshot counter1 = + CounterSnapshot.builder() + .name("api_responses") + .dataPoint( + CounterSnapshot.CounterDataPointSnapshot.builder() + .labels(Labels.of("uri", "/hello", "outcome", "SUCCESS")) + .value(100) + .build()) + .build(); + + CounterSnapshot counter2 = + CounterSnapshot.builder() + .name("api_responses") + .dataPoint( + CounterSnapshot.CounterDataPointSnapshot.builder() + .labels(Labels.of("uri", "/hello", "outcome", "FAILURE")) + .value(10) + .build()) + .build(); + + MetricSnapshots snapshots = new MetricSnapshots(counter1, counter2); + MetricSnapshots result = TextFormatUtil.mergeDuplicates(snapshots); + + assertThat(result).hasSize(1); + assertThat(result.get(0).getMetadata().getName()).isEqualTo("api_responses"); + assertThat(result.get(0).getDataPoints()).hasSize(2); + + CounterSnapshot merged = (CounterSnapshot) result.get(0); + assertThat(merged.getDataPoints()) + .anyMatch( + dp -> + dp.getLabels().equals(Labels.of("uri", "/hello", "outcome", "SUCCESS")) + && dp.getValue() == 100); + assertThat(merged.getDataPoints()) + .anyMatch( + dp -> + dp.getLabels().equals(Labels.of("uri", "/hello", "outcome", "FAILURE")) + && dp.getValue() == 10); + } + + @Test + void testMergeDuplicates_multipleDataPoints_allMerged() { + CounterSnapshot counter1 = + CounterSnapshot.builder() + .name("api_responses") + .dataPoint( + CounterSnapshot.CounterDataPointSnapshot.builder() + .labels(Labels.of("uri", "/hello", "outcome", "SUCCESS")) + .value(100) + .build()) + .dataPoint( + CounterSnapshot.CounterDataPointSnapshot.builder() + .labels(Labels.of("uri", "/world", "outcome", "SUCCESS")) + .value(200) + .build()) + .build(); + + CounterSnapshot counter2 = + CounterSnapshot.builder() + .name("api_responses") + .dataPoint( + CounterSnapshot.CounterDataPointSnapshot.builder() + .labels(Labels.of("uri", "/hello", "outcome", "FAILURE")) + .value(10) + .build()) + .dataPoint( + CounterSnapshot.CounterDataPointSnapshot.builder() + .labels(Labels.of("uri", "/world", "outcome", "FAILURE")) + .value(5) + .build()) + .build(); + + MetricSnapshots snapshots = new MetricSnapshots(counter1, counter2); + MetricSnapshots result = TextFormatUtil.mergeDuplicates(snapshots); + + assertThat(result).hasSize(1); + assertThat(result.get(0).getDataPoints()).hasSize(4); + } + + @Test + void testMergeDuplicates_emptySnapshots_returnsEmpty() { + MetricSnapshots snapshots = MetricSnapshots.builder().build(); + MetricSnapshots result = TextFormatUtil.mergeDuplicates(snapshots); + + assertThat(result).isEmpty(); + } + + @Test + void testMergeDuplicates_histogramSameGaugeFlag_preservesGaugeHistogram() { + HistogramSnapshot gauge1 = + HistogramSnapshot.builder() + .name("my_histogram") + .gaugeHistogram(true) + .dataPoint( + HistogramSnapshot.HistogramDataPointSnapshot.builder() + .labels(Labels.of("a", "1")) + .classicHistogramBuckets( + ClassicHistogramBuckets.of( + new double[] {Double.POSITIVE_INFINITY}, new long[] {0})) + .build()) + .build(); + HistogramSnapshot gauge2 = + HistogramSnapshot.builder() + .name("my_histogram") + .gaugeHistogram(true) + .dataPoint( + HistogramSnapshot.HistogramDataPointSnapshot.builder() + .labels(Labels.of("a", "2")) + .classicHistogramBuckets( + ClassicHistogramBuckets.of( + new double[] {Double.POSITIVE_INFINITY}, new long[] {0})) + .build()) + .build(); + MetricSnapshots snapshots = new MetricSnapshots(gauge1, gauge2); + MetricSnapshots result = TextFormatUtil.mergeDuplicates(snapshots); + + assertThat(result).hasSize(1); + HistogramSnapshot merged = (HistogramSnapshot) result.get(0); + assertThat(merged.isGaugeHistogram()).isTrue(); + assertThat(merged.getDataPoints()).hasSize(2); + } } diff --git a/prometheus-metrics-instrumentation-caffeine/pom.xml b/prometheus-metrics-instrumentation-caffeine/pom.xml index c38d51ccf..355bea276 100644 --- a/prometheus-metrics-instrumentation-caffeine/pom.xml +++ b/prometheus-metrics-instrumentation-caffeine/pom.xml @@ -6,7 +6,7 @@ io.prometheus client_java - 1.4.0-SNAPSHOT + 1.6.0-SNAPSHOT prometheus-metrics-instrumentation-caffeine @@ -38,7 +38,7 @@ com.github.ben-manes.caffeine caffeine - 3.2.2 + 3.2.3 provided diff --git a/prometheus-metrics-instrumentation-caffeine/src/main/java/io/prometheus/metrics/instrumentation/caffeine/CacheMetricsCollector.java b/prometheus-metrics-instrumentation-caffeine/src/main/java/io/prometheus/metrics/instrumentation/caffeine/CacheMetricsCollector.java index a1b8782a3..c847e13eb 100644 --- a/prometheus-metrics-instrumentation-caffeine/src/main/java/io/prometheus/metrics/instrumentation/caffeine/CacheMetricsCollector.java +++ b/prometheus-metrics-instrumentation-caffeine/src/main/java/io/prometheus/metrics/instrumentation/caffeine/CacheMetricsCollector.java @@ -226,7 +226,7 @@ public MetricSnapshots collect() { .labels(labels) .value(stats.evictionWeight()) .build()); - } catch (Exception e) { + } catch (UnsupportedOperationException e) { // EvictionWeight metric is unavailable, newer version of Caffeine is needed. } diff --git a/prometheus-metrics-instrumentation-caffeine/src/test/java/io/prometheus/metrics/instrumentation/caffeine/CacheMetricsCollectorTest.java b/prometheus-metrics-instrumentation-caffeine/src/test/java/io/prometheus/metrics/instrumentation/caffeine/CacheMetricsCollectorTest.java index 347990f70..d2d12aa11 100644 --- a/prometheus-metrics-instrumentation-caffeine/src/test/java/io/prometheus/metrics/instrumentation/caffeine/CacheMetricsCollectorTest.java +++ b/prometheus-metrics-instrumentation-caffeine/src/test/java/io/prometheus/metrics/instrumentation/caffeine/CacheMetricsCollectorTest.java @@ -9,14 +9,10 @@ import com.github.benmanes.caffeine.cache.CacheLoader; import com.github.benmanes.caffeine.cache.Caffeine; import com.github.benmanes.caffeine.cache.LoadingCache; +import io.prometheus.metrics.config.EscapingScheme; import io.prometheus.metrics.expositionformats.OpenMetricsTextFormatWriter; import io.prometheus.metrics.model.registry.PrometheusRegistry; -import io.prometheus.metrics.model.snapshots.CounterSnapshot; -import io.prometheus.metrics.model.snapshots.DataPointSnapshot; -import io.prometheus.metrics.model.snapshots.GaugeSnapshot; -import io.prometheus.metrics.model.snapshots.Labels; -import io.prometheus.metrics.model.snapshots.MetricSnapshots; -import io.prometheus.metrics.model.snapshots.SummarySnapshot; +import io.prometheus.metrics.model.snapshots.*; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.UncheckedIOException; @@ -29,7 +25,7 @@ @SuppressWarnings("CheckReturnValue") class CacheMetricsCollectorTest { // This enum was added to simplify test parametrization on argument options. - public enum Options { + enum Options { LEGACY(false, false), COLLECT_EVICTION_WEIGHT_AS_COUNTER(true, false), COLLECT_WEIGHTED_SIZE(false, true), @@ -79,7 +75,7 @@ public void cacheExposesMetricsForHitMissAndEviction(Options options) { if (options.collectEvictionWeightAsCounter) { assertCounterMetric(registry, "caffeine_cache_eviction_weight", "users", 2.0); openMetricEvictionWeightExpectedText = - """ +""" # TYPE caffeine_cache_eviction_weight counter # HELP caffeine_cache_eviction_weight Weight of evicted cache entries, doesn't include manually removed entries caffeine_cache_eviction_weight_total{cache="users"} 2.0 @@ -87,7 +83,7 @@ public void cacheExposesMetricsForHitMissAndEviction(Options options) { } else { assertGaugeMetric(registry, "caffeine_cache_eviction_weight", "users", 2.0); openMetricEvictionWeightExpectedText = - """ +""" # TYPE caffeine_cache_eviction_weight gauge # HELP caffeine_cache_eviction_weight Weight of evicted cache entries, doesn't include manually removed entries caffeine_cache_eviction_weight{cache="users"} 2.0 @@ -157,7 +153,7 @@ public void weightedCacheExposesMetricsForHitMissAndEvictionWeightedSize(Options if (options.collectEvictionWeightAsCounter) { assertCounterMetric(registry, "caffeine_cache_eviction_weight", "users", 31.0); openMetricEvictionWeightExpectedText = - """ +""" # TYPE caffeine_cache_eviction_weight counter # HELP caffeine_cache_eviction_weight Weight of evicted cache entries, doesn't include manually removed entries caffeine_cache_eviction_weight_total{cache="users"} 31.0 @@ -165,7 +161,7 @@ public void weightedCacheExposesMetricsForHitMissAndEvictionWeightedSize(Options } else { assertGaugeMetric(registry, "caffeine_cache_eviction_weight", "users", 31.0); openMetricEvictionWeightExpectedText = - """ +""" # TYPE caffeine_cache_eviction_weight gauge # HELP caffeine_cache_eviction_weight Weight of evicted cache entries, doesn't include manually removed entries caffeine_cache_eviction_weight{cache="users"} 31.0 @@ -209,7 +205,7 @@ public void weightedCacheExposesMetricsForHitMissAndEvictionWeightedSize(Options @SuppressWarnings("unchecked") @Test - public void loadingCacheExposesMetricsForLoadsAndExceptions() throws Exception { + void loadingCacheExposesMetricsForLoadsAndExceptions() throws Exception { final CacheLoader loader = mock(CacheLoader.class); when(loader.load(anyString())) .thenReturn("First User") @@ -316,7 +312,7 @@ private String convertToOpenMetricsFormat(PrometheusRegistry registry) { final ByteArrayOutputStream out = new ByteArrayOutputStream(); final OpenMetricsTextFormatWriter writer = new OpenMetricsTextFormatWriter(true, true); try { - writer.write(out, registry.scrape()); + writer.write(out, registry.scrape(), EscapingScheme.ALLOW_UTF8); return out.toString(StandardCharsets.UTF_8.name()); } catch (IOException e) { throw new UncheckedIOException(e); diff --git a/prometheus-metrics-instrumentation-dropwizard/pom.xml b/prometheus-metrics-instrumentation-dropwizard/pom.xml index cb67807ea..e25368513 100644 --- a/prometheus-metrics-instrumentation-dropwizard/pom.xml +++ b/prometheus-metrics-instrumentation-dropwizard/pom.xml @@ -6,7 +6,7 @@ io.prometheus client_java - 1.4.0-SNAPSHOT + 1.6.0-SNAPSHOT prometheus-metrics-instrumentation-dropwizard @@ -38,7 +38,7 @@ io.dropwizard.metrics metrics-core - 4.2.33 + 4.2.38 provided diff --git a/prometheus-metrics-instrumentation-dropwizard/src/main/java/io/prometheus/metrics/instrumentation/dropwizard/DropwizardExports.java b/prometheus-metrics-instrumentation-dropwizard/src/main/java/io/prometheus/metrics/instrumentation/dropwizard/DropwizardExports.java index 66957de42..0c1c8455f 100644 --- a/prometheus-metrics-instrumentation-dropwizard/src/main/java/io/prometheus/metrics/instrumentation/dropwizard/DropwizardExports.java +++ b/prometheus-metrics-instrumentation-dropwizard/src/main/java/io/prometheus/metrics/instrumentation/dropwizard/DropwizardExports.java @@ -10,32 +10,29 @@ import com.codahale.metrics.Snapshot; import com.codahale.metrics.Timer; import io.prometheus.metrics.instrumentation.dropwizard5.InvalidMetricHandler; +import io.prometheus.metrics.instrumentation.dropwizard5.internal.AbstractDropwizardExports; import io.prometheus.metrics.instrumentation.dropwizard5.labels.CustomLabelMapper; -import io.prometheus.metrics.model.registry.MultiCollector; import io.prometheus.metrics.model.registry.PrometheusRegistry; -import io.prometheus.metrics.model.snapshots.CounterSnapshot; -import io.prometheus.metrics.model.snapshots.GaugeSnapshot; -import io.prometheus.metrics.model.snapshots.MetricMetadata; -import io.prometheus.metrics.model.snapshots.MetricSnapshot; import io.prometheus.metrics.model.snapshots.MetricSnapshots; -import io.prometheus.metrics.model.snapshots.PrometheusNaming; -import io.prometheus.metrics.model.snapshots.Quantiles; -import io.prometheus.metrics.model.snapshots.SummarySnapshot; -import java.util.Collections; -import java.util.Map; -import java.util.Optional; -import java.util.concurrent.TimeUnit; -import java.util.function.BiFunction; -import java.util.logging.Level; -import java.util.logging.Logger; - -/** Collect Dropwizard metrics from a MetricRegistry. */ -public class DropwizardExports implements MultiCollector { - private static final Logger logger = Logger.getLogger(DropwizardExports.class.getName()); - private final MetricRegistry registry; - private final MetricFilter metricFilter; - private final Optional labelMapper; - private final InvalidMetricHandler invalidMetricHandler; +import javax.annotation.Nullable; + +/** + * Collect Dropwizard 4.x metrics from a MetricRegistry. + * + *

This is a thin wrapper around {@link AbstractDropwizardExports} that handles the Dropwizard + * 4.x specific API where metric names are Strings. + */ +public class DropwizardExports + extends AbstractDropwizardExports< + MetricRegistry, + MetricFilter, + Counter, + Gauge, + Histogram, + Timer, + Meter, + Metric, + Snapshot> { /** * Creates a new DropwizardExports and {@link MetricFilter#ALL}. @@ -43,11 +40,7 @@ public class DropwizardExports implements MultiCollector { * @param registry a metric registry to export in prometheus. */ public DropwizardExports(MetricRegistry registry) { - super(); - this.registry = registry; - this.metricFilter = MetricFilter.ALL; - this.labelMapper = Optional.empty(); - this.invalidMetricHandler = InvalidMetricHandler.ALWAYS_THROW; + this(registry, MetricFilter.ALL, null, InvalidMetricHandler.ALWAYS_THROW); } /** @@ -57,10 +50,7 @@ public DropwizardExports(MetricRegistry registry) { * @param metricFilter a custom metric filter. */ public DropwizardExports(MetricRegistry registry, MetricFilter metricFilter) { - this.registry = registry; - this.metricFilter = metricFilter; - this.labelMapper = Optional.empty(); - this.invalidMetricHandler = InvalidMetricHandler.ALWAYS_THROW; + this(registry, metricFilter, null, InvalidMetricHandler.ALWAYS_THROW); } /** @@ -69,181 +59,104 @@ public DropwizardExports(MetricRegistry registry, MetricFilter metricFilter) { * @param labelMapper a labelMapper to use to map labels. */ public DropwizardExports( - MetricRegistry registry, MetricFilter metricFilter, CustomLabelMapper labelMapper) { - this.registry = registry; - this.metricFilter = metricFilter; - this.labelMapper = Optional.ofNullable(labelMapper); - this.invalidMetricHandler = InvalidMetricHandler.ALWAYS_THROW; + MetricRegistry registry, MetricFilter metricFilter, @Nullable CustomLabelMapper labelMapper) { + this(registry, metricFilter, labelMapper, InvalidMetricHandler.ALWAYS_THROW); } /** * @param registry a metric registry to export in prometheus. * @param metricFilter a custom metric filter. * @param labelMapper a labelMapper to use to map labels. + * @param invalidMetricHandler handler for invalid metrics. */ private DropwizardExports( MetricRegistry registry, MetricFilter metricFilter, - CustomLabelMapper labelMapper, + @Nullable CustomLabelMapper labelMapper, InvalidMetricHandler invalidMetricHandler) { - this.registry = registry; - this.metricFilter = metricFilter; - this.labelMapper = Optional.ofNullable(labelMapper); - this.invalidMetricHandler = invalidMetricHandler; + super(registry, metricFilter, labelMapper, invalidMetricHandler); } - private static String getHelpMessage(String metricName, Metric metric) { - return String.format( - "Generated from Dropwizard metric import (metric=%s, type=%s)", - metricName, metric.getClass().getName()); + @Override + protected MetricSnapshots collectMetricSnapshots() { + MetricSnapshots.Builder metricSnapshots = MetricSnapshots.builder(); + // For Dropwizard 4.x, map keys are Strings, so we just use identity function + collectMetricKind( + metricSnapshots, registry.getGauges(metricFilter), this::fromGauge, key -> key); + collectMetricKind( + metricSnapshots, registry.getCounters(metricFilter), this::fromCounter, key -> key); + collectMetricKind( + metricSnapshots, registry.getHistograms(metricFilter), this::fromHistogram, key -> key); + collectMetricKind( + metricSnapshots, registry.getTimers(metricFilter), this::fromTimer, key -> key); + collectMetricKind( + metricSnapshots, registry.getMeters(metricFilter), this::fromMeter, key -> key); + return metricSnapshots.build(); } - private MetricMetadata getMetricMetaData(String metricName, Metric metric) { - String name = labelMapper.isPresent() ? labelMapper.get().getName(metricName) : metricName; - return new MetricMetadata( - PrometheusNaming.sanitizeMetricName(name), getHelpMessage(metricName, metric)); + @Override + protected long getCounterCount(Counter counter) { + return counter.getCount(); } - /** - * Export counter as Prometheus Gauge. - */ - MetricSnapshot fromCounter(String dropwizardName, Counter counter) { - MetricMetadata metadata = getMetricMetaData(dropwizardName, counter); - CounterSnapshot.CounterDataPointSnapshot.Builder dataPointBuilder = - CounterSnapshot.CounterDataPointSnapshot.builder() - .value(Long.valueOf(counter.getCount()).doubleValue()); - labelMapper.ifPresent( - mapper -> - dataPointBuilder.labels( - mapper.getLabels( - dropwizardName, Collections.emptyList(), Collections.emptyList()))); - return new CounterSnapshot(metadata, Collections.singletonList(dataPointBuilder.build())); + @Override + protected Object getGaugeValue(Gauge gauge) { + return gauge.getValue(); } - /** Export gauge as a prometheus gauge. */ - MetricSnapshot fromGauge(String dropwizardName, Gauge gauge) { - Object obj = gauge.getValue(); - double value; - if (obj instanceof Number) { - value = ((Number) obj).doubleValue(); - } else if (obj instanceof Boolean) { - value = ((Boolean) obj) ? 1 : 0; - } else { - logger.log( - Level.FINE, - String.format( - "Invalid type for Gauge %s: %s", - PrometheusNaming.sanitizeMetricName(dropwizardName), - obj == null ? "null" : obj.getClass().getName())); - return null; - } - MetricMetadata metadata = getMetricMetaData(dropwizardName, gauge); - GaugeSnapshot.GaugeDataPointSnapshot.Builder dataPointBuilder = - GaugeSnapshot.GaugeDataPointSnapshot.builder().value(value); - labelMapper.ifPresent( - mapper -> - dataPointBuilder.labels( - mapper.getLabels( - dropwizardName, Collections.emptyList(), Collections.emptyList()))); - return new GaugeSnapshot(metadata, Collections.singletonList(dataPointBuilder.build())); + @Override + protected Snapshot getHistogramSnapshot(Histogram histogram) { + return histogram.getSnapshot(); } - /** - * Export a histogram snapshot as a prometheus SUMMARY. - * - * @param dropwizardName metric name. - * @param snapshot the histogram snapshot. - * @param count the total sample count for this snapshot. - * @param factor a factor to apply to histogram values. - */ - MetricSnapshot fromSnapshotAndCount( - String dropwizardName, Snapshot snapshot, long count, double factor, String helpMessage) { - Quantiles quantiles = - Quantiles.builder() - .quantile(0.5, snapshot.getMedian() * factor) - .quantile(0.75, snapshot.get75thPercentile() * factor) - .quantile(0.95, snapshot.get95thPercentile() * factor) - .quantile(0.98, snapshot.get98thPercentile() * factor) - .quantile(0.99, snapshot.get99thPercentile() * factor) - .quantile(0.999, snapshot.get999thPercentile() * factor) - .build(); + @Override + protected long getHistogramCount(Histogram histogram) { + return histogram.getCount(); + } - String name = - labelMapper.isPresent() ? labelMapper.get().getName(dropwizardName) : dropwizardName; - MetricMetadata metadata = - new MetricMetadata(PrometheusNaming.sanitizeMetricName(name), helpMessage); - SummarySnapshot.SummaryDataPointSnapshot.Builder dataPointBuilder = - SummarySnapshot.SummaryDataPointSnapshot.builder().quantiles(quantiles).count(count); - labelMapper.ifPresent( - mapper -> - dataPointBuilder.labels( - mapper.getLabels( - dropwizardName, Collections.emptyList(), Collections.emptyList()))); - return new SummarySnapshot(metadata, Collections.singletonList(dataPointBuilder.build())); + @Override + protected Snapshot getTimerSnapshot(Timer timer) { + return timer.getSnapshot(); } - /** Convert histogram snapshot. */ - MetricSnapshot fromHistogram(String dropwizardName, Histogram histogram) { - return fromSnapshotAndCount( - dropwizardName, - histogram.getSnapshot(), - histogram.getCount(), - 1.0, - getHelpMessage(dropwizardName, histogram)); + @Override + protected long getTimerCount(Timer timer) { + return timer.getCount(); } - /** Export Dropwizard Timer as a histogram. Use TIME_UNIT as time unit. */ - MetricSnapshot fromTimer(String dropwizardName, Timer timer) { - return fromSnapshotAndCount( - dropwizardName, - timer.getSnapshot(), - timer.getCount(), - 1.0D / TimeUnit.SECONDS.toNanos(1L), - getHelpMessage(dropwizardName, timer)); + @Override + protected long getMeterCount(Meter meter) { + return meter.getCount(); } - /** Export a Meter as a prometheus COUNTER. */ - MetricSnapshot fromMeter(String dropwizardName, Meter meter) { - MetricMetadata metadata = getMetricMetaData(dropwizardName + "_total", meter); - CounterSnapshot.CounterDataPointSnapshot.Builder dataPointBuilder = - CounterSnapshot.CounterDataPointSnapshot.builder().value(meter.getCount()); - labelMapper.ifPresent( - mapper -> - dataPointBuilder.labels( - mapper.getLabels( - dropwizardName, Collections.emptyList(), Collections.emptyList()))); - return new CounterSnapshot(metadata, Collections.singletonList(dataPointBuilder.build())); + @Override + protected double getMedian(Snapshot snapshot) { + return snapshot.getMedian(); } @Override - public MetricSnapshots collect() { - MetricSnapshots.Builder metricSnapshots = MetricSnapshots.builder(); - collectMetricKind(metricSnapshots, registry.getGauges(metricFilter), this::fromGauge); - collectMetricKind(metricSnapshots, registry.getCounters(metricFilter), this::fromCounter); - collectMetricKind(metricSnapshots, registry.getHistograms(metricFilter), this::fromHistogram); - collectMetricKind(metricSnapshots, registry.getTimers(metricFilter), this::fromTimer); - collectMetricKind(metricSnapshots, registry.getMeters(metricFilter), this::fromMeter); - return metricSnapshots.build(); + protected double get75thPercentile(Snapshot snapshot) { + return snapshot.get75thPercentile(); } - private void collectMetricKind( - MetricSnapshots.Builder builder, - Map metric, - BiFunction toSnapshot) { - for (Map.Entry entry : metric.entrySet()) { - String metricName = entry.getKey(); - try { - MetricSnapshot snapshot = toSnapshot.apply(metricName, entry.getValue()); - if (snapshot != null) { - builder.metricSnapshot(snapshot); - } - } catch (Exception e) { - if (!invalidMetricHandler.suppressException(metricName, e)) { - throw e; - } - } - } + @Override + protected double get95thPercentile(Snapshot snapshot) { + return snapshot.get95thPercentile(); + } + + @Override + protected double get98thPercentile(Snapshot snapshot) { + return snapshot.get98thPercentile(); + } + + @Override + protected double get99thPercentile(Snapshot snapshot) { + return snapshot.get99thPercentile(); + } + + @Override + protected double get999thPercentile(Snapshot snapshot) { + return snapshot.get999thPercentile(); } public static Builder builder() { @@ -252,9 +165,9 @@ public static Builder builder() { // Builder class for DropwizardExports public static class Builder { - private MetricRegistry registry; + @Nullable private MetricRegistry registry; private MetricFilter metricFilter; - private CustomLabelMapper labelMapper; + @Nullable private CustomLabelMapper labelMapper; private InvalidMetricHandler invalidMetricHandler; private Builder() { diff --git a/prometheus-metrics-instrumentation-dropwizard/src/test/java/io/prometheus/metrics/instrumentation/dropwizard/DropwizardExportsTest.java b/prometheus-metrics-instrumentation-dropwizard/src/test/java/io/prometheus/metrics/instrumentation/dropwizard/DropwizardExportsTest.java index 7625771b0..c4c0b6241 100644 --- a/prometheus-metrics-instrumentation-dropwizard/src/test/java/io/prometheus/metrics/instrumentation/dropwizard/DropwizardExportsTest.java +++ b/prometheus-metrics-instrumentation-dropwizard/src/test/java/io/prometheus/metrics/instrumentation/dropwizard/DropwizardExportsTest.java @@ -5,6 +5,7 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy; import com.codahale.metrics.*; +import io.prometheus.metrics.config.EscapingScheme; import io.prometheus.metrics.expositionformats.OpenMetricsTextFormatWriter; import io.prometheus.metrics.instrumentation.dropwizard5.InvalidMetricHandler; import io.prometheus.metrics.model.registry.PrometheusRegistry; @@ -22,7 +23,7 @@ class DropwizardExportsTest { private MetricRegistry metricRegistry; @BeforeEach - public void setUp() { + void setUp() { metricRegistry = new MetricRegistry(); DropwizardExports.builder() .dropwizardRegistry(metricRegistry) @@ -31,24 +32,24 @@ public void setUp() { } @Test - public void testBuilderThrowsErrorOnNullRegistry() { + void testBuilderThrowsErrorOnNullRegistry() { assertThatThrownBy( () -> DropwizardExports.builder().dropwizardRegistry(null).register(registry)) .isInstanceOf(IllegalArgumentException.class); } @Test - public void testBuilderCreatesOkay() { + void testBuilderCreatesOkay() { assertThatCode( () -> DropwizardExports.builder().dropwizardRegistry(metricRegistry).register(registry)) .doesNotThrowAnyException(); } @Test - public void testCounter() { + void testCounter() { metricRegistry.counter("foo.bar").inc(1); String expected = - """ +""" # TYPE foo_bar counter # HELP foo_bar Generated from Dropwizard metric import (metric=foo.bar, type=com.codahale.metrics.Counter) foo_bar_total 1.0 @@ -59,7 +60,7 @@ public void testCounter() { } @Test - public void testGauge() { + void testGauge() { // don't convert to lambda, as we need to test the type Gauge integerGauge = new Gauge() { @@ -104,7 +105,7 @@ public Boolean getValue() { metricRegistry.register("boolean.gauge", booleanGauge); String expected = - """ +""" # TYPE boolean_gauge gauge # HELP boolean_gauge Generated from Dropwizard metric import (metric=boolean.gauge, type=io.prometheus.metrics.instrumentation.dropwizard.DropwizardExportsTest$5) boolean_gauge 1.0 @@ -127,7 +128,7 @@ public Boolean getValue() { } @Test - public void testInvalidGaugeType() { + void testInvalidGaugeType() { Gauge invalidGauge = () -> "foobar"; metricRegistry.register("invalid_gauge", invalidGauge); @@ -137,7 +138,7 @@ public void testInvalidGaugeType() { } @Test - public void testGaugeReturningNullValue() { + void testGaugeReturningNullValue() { Gauge invalidGauge = () -> null; metricRegistry.register("invalid_gauge", invalidGauge); String expected = "# EOF\n"; @@ -145,7 +146,7 @@ public void testGaugeReturningNullValue() { } @Test - public void testHistogram() { + void testHistogram() { // just test the standard mapper final MetricRegistry metricRegistry = new MetricRegistry(); PrometheusRegistry pmRegistry = new PrometheusRegistry(); @@ -160,7 +161,7 @@ public void testHistogram() { // The result should look like this String expected1 = - """ +""" # TYPE hist summary # HELP hist Generated from Dropwizard metric import (metric=hist, type=com.codahale.metrics.Histogram) hist{quantile="0.5"} 49.0 @@ -176,7 +177,7 @@ public void testHistogram() { // However, Dropwizard uses a random reservoir sampling algorithm, so the values could as well // be off-by-one String expected2 = - """ +""" # TYPE hist summary # HELP hist Generated from Dropwizard metric import (metric=hist, type=com.codahale.metrics.Histogram) hist{quantile="0.5"} 50.0 @@ -198,13 +199,13 @@ public void testHistogram() { } @Test - public void testMeter() { + void testMeter() { Meter meter = metricRegistry.meter("meter"); meter.mark(); meter.mark(); String expected = - """ +""" # TYPE meter counter # HELP meter Generated from Dropwizard metric import (metric=meter_total, type=com.codahale.metrics.Meter) meter_total 2.0 @@ -214,7 +215,7 @@ public void testMeter() { } @Test - public void testTimer() throws InterruptedException { + void testTimer() throws InterruptedException { final MetricRegistry metricRegistry = new MetricRegistry(); DropwizardExports exports = new DropwizardExports(metricRegistry); Timer t = metricRegistry.timer("timer"); @@ -238,7 +239,7 @@ public void testTimer() throws InterruptedException { } @Test - public void testThatMetricHelpUsesOriginalDropwizardName() { + void testThatMetricHelpUsesOriginalDropwizardName() { metricRegistry.timer("my.application.namedTimer1"); metricRegistry.counter("my.application.namedCounter1"); metricRegistry.meter("my.application.namedMeter1"); @@ -246,7 +247,7 @@ public void testThatMetricHelpUsesOriginalDropwizardName() { metricRegistry.register("my.application.namedGauge1", new ExampleDoubleGauge()); String expected = - """ +""" # TYPE my_application_namedCounter1 counter # HELP my_application_namedCounter1 Generated from Dropwizard metric import (metric=my.application.namedCounter1, type=com.codahale.metrics.Counter) my_application_namedCounter1_total 0.0 @@ -285,7 +286,7 @@ void responseWhenRegistryIsEmpty() { registry.register(DropwizardExports.builder().dropwizardRegistry(metricRegistry).build()); assertThat(convertToOpenMetricsFormat(registry)) .isEqualTo( - """ +""" # EOF """); } @@ -319,7 +320,7 @@ void collectInvalidMetricPassesWhenExceptionIsIgnored() { .register(registry); assertThat(convertToOpenMetricsFormat(registry)) .isEqualTo( - """ +""" # TYPE my_application_namedCounter2 counter # HELP my_application_namedCounter2 Generated from Dropwizard metric import (metric=my.application.namedCounter2, type=com.codahale.metrics.Counter) my_application_namedCounter2_total 10.0 @@ -340,7 +341,7 @@ private String convertToOpenMetricsFormat(PrometheusRegistry _registry) { ByteArrayOutputStream out = new ByteArrayOutputStream(); OpenMetricsTextFormatWriter writer = new OpenMetricsTextFormatWriter(true, true); try { - writer.write(out, _registry.scrape()); + writer.write(out, _registry.scrape(), EscapingScheme.UNDERSCORE_ESCAPING); return out.toString(StandardCharsets.UTF_8); } catch (IOException e) { throw new RuntimeException(e); diff --git a/prometheus-metrics-instrumentation-dropwizard5/pom.xml b/prometheus-metrics-instrumentation-dropwizard5/pom.xml index 9a471f187..bb14196b4 100644 --- a/prometheus-metrics-instrumentation-dropwizard5/pom.xml +++ b/prometheus-metrics-instrumentation-dropwizard5/pom.xml @@ -6,7 +6,7 @@ io.prometheus client_java - 1.4.0-SNAPSHOT + 1.6.0-SNAPSHOT prometheus-metrics-instrumentation-dropwizard5 @@ -19,7 +19,7 @@ io.prometheus.metrics.instrumentation.dropwizard5 - 0.50 + 0.60 @@ -31,7 +31,7 @@ io.dropwizard.metrics5 metrics-core - 5.0.1 + 5.0.6 provided diff --git a/prometheus-metrics-instrumentation-dropwizard5/src/main/java/io/prometheus/metrics/instrumentation/dropwizard5/DropwizardExports.java b/prometheus-metrics-instrumentation-dropwizard5/src/main/java/io/prometheus/metrics/instrumentation/dropwizard5/DropwizardExports.java index a8ee9916f..86c64b54e 100644 --- a/prometheus-metrics-instrumentation-dropwizard5/src/main/java/io/prometheus/metrics/instrumentation/dropwizard5/DropwizardExports.java +++ b/prometheus-metrics-instrumentation-dropwizard5/src/main/java/io/prometheus/metrics/instrumentation/dropwizard5/DropwizardExports.java @@ -10,44 +10,37 @@ import io.dropwizard.metrics5.MetricRegistry; import io.dropwizard.metrics5.Snapshot; import io.dropwizard.metrics5.Timer; +import io.prometheus.metrics.instrumentation.dropwizard5.internal.AbstractDropwizardExports; import io.prometheus.metrics.instrumentation.dropwizard5.labels.CustomLabelMapper; -import io.prometheus.metrics.model.registry.MultiCollector; import io.prometheus.metrics.model.registry.PrometheusRegistry; -import io.prometheus.metrics.model.snapshots.CounterSnapshot; -import io.prometheus.metrics.model.snapshots.GaugeSnapshot; -import io.prometheus.metrics.model.snapshots.MetricMetadata; -import io.prometheus.metrics.model.snapshots.MetricSnapshot; import io.prometheus.metrics.model.snapshots.MetricSnapshots; -import io.prometheus.metrics.model.snapshots.PrometheusNaming; -import io.prometheus.metrics.model.snapshots.Quantiles; -import io.prometheus.metrics.model.snapshots.SummarySnapshot; -import java.util.Collections; -import java.util.Map; -import java.util.Optional; -import java.util.concurrent.TimeUnit; -import java.util.function.BiFunction; -import java.util.logging.Level; -import java.util.logging.Logger; - -/** Collect Dropwizard metrics from a MetricRegistry. */ -public class DropwizardExports implements MultiCollector { - private static final Logger logger = Logger.getLogger(DropwizardExports.class.getName()); - private final MetricRegistry registry; - private final MetricFilter metricFilter; - private final Optional labelMapper; - private final InvalidMetricHandler invalidMetricHandler; +import javax.annotation.Nullable; + +/** + * Collect Dropwizard 5.x metrics from a MetricRegistry. + * + *

This is a thin wrapper around {@link AbstractDropwizardExports} that handles the Dropwizard + * 5.x specific API where metric names are {@link MetricName} objects. + */ +public class DropwizardExports + extends AbstractDropwizardExports< + MetricRegistry, + MetricFilter, + Counter, + Gauge, + Histogram, + Timer, + Meter, + Metric, + Snapshot> { /** - * Creates a new DropwizardExports and {@link MetricFilter#ALL}. + * Creates a new DropwizardExports with {@link MetricFilter#ALL}. * * @param registry a metric registry to export in prometheus. */ public DropwizardExports(MetricRegistry registry) { - super(); - this.registry = registry; - this.metricFilter = MetricFilter.ALL; - this.labelMapper = Optional.empty(); - this.invalidMetricHandler = InvalidMetricHandler.ALWAYS_THROW; + this(registry, MetricFilter.ALL, null, InvalidMetricHandler.ALWAYS_THROW); } /** @@ -57,10 +50,7 @@ public DropwizardExports(MetricRegistry registry) { * @param metricFilter a custom metric filter. */ public DropwizardExports(MetricRegistry registry, MetricFilter metricFilter) { - this.registry = registry; - this.metricFilter = metricFilter; - this.labelMapper = Optional.empty(); - this.invalidMetricHandler = InvalidMetricHandler.ALWAYS_THROW; + this(registry, metricFilter, null, InvalidMetricHandler.ALWAYS_THROW); } /** @@ -69,181 +59,122 @@ public DropwizardExports(MetricRegistry registry, MetricFilter metricFilter) { * @param labelMapper a labelMapper to use to map labels. */ public DropwizardExports( - MetricRegistry registry, MetricFilter metricFilter, CustomLabelMapper labelMapper) { - this.registry = registry; - this.metricFilter = metricFilter; - this.labelMapper = Optional.ofNullable(labelMapper); - this.invalidMetricHandler = InvalidMetricHandler.ALWAYS_THROW; + MetricRegistry registry, MetricFilter metricFilter, @Nullable CustomLabelMapper labelMapper) { + this(registry, metricFilter, labelMapper, InvalidMetricHandler.ALWAYS_THROW); } /** * @param registry a metric registry to export in prometheus. * @param metricFilter a custom metric filter. * @param labelMapper a labelMapper to use to map labels. + * @param invalidMetricHandler handler for invalid metrics. */ private DropwizardExports( MetricRegistry registry, MetricFilter metricFilter, - CustomLabelMapper labelMapper, + @Nullable CustomLabelMapper labelMapper, InvalidMetricHandler invalidMetricHandler) { - this.registry = registry; - this.metricFilter = metricFilter; - this.labelMapper = Optional.ofNullable(labelMapper); - this.invalidMetricHandler = invalidMetricHandler; + super(registry, metricFilter, labelMapper, invalidMetricHandler); } - private static String getHelpMessage(String metricName, Metric metric) { - return String.format( - "Generated from Dropwizard metric import (metric=%s, type=%s)", - metricName, metric.getClass().getName()); + @Override + protected MetricSnapshots collectMetricSnapshots() { + MetricSnapshots.Builder metricSnapshots = MetricSnapshots.builder(); + collectMetricKind( + metricSnapshots, + registry.getGauges(metricFilter), + this::fromGauge, + this::extractMetricName); + collectMetricKind( + metricSnapshots, + registry.getCounters(metricFilter), + this::fromCounter, + this::extractMetricName); + collectMetricKind( + metricSnapshots, + registry.getHistograms(metricFilter), + this::fromHistogram, + this::extractMetricName); + collectMetricKind( + metricSnapshots, + registry.getTimers(metricFilter), + this::fromTimer, + this::extractMetricName); + collectMetricKind( + metricSnapshots, + registry.getMeters(metricFilter), + this::fromMeter, + this::extractMetricName); + return metricSnapshots.build(); } - private MetricMetadata getMetricMetaData(String metricName, Metric metric) { - String name = labelMapper.isPresent() ? labelMapper.get().getName(metricName) : metricName; - return new MetricMetadata( - PrometheusNaming.sanitizeMetricName(name), getHelpMessage(metricName, metric)); + private String extractMetricName(MetricName metricName) { + return metricName.getKey(); } - /** - * Export counter as Prometheus Gauge. - */ - MetricSnapshot fromCounter(String dropwizardName, Counter counter) { - MetricMetadata metadata = getMetricMetaData(dropwizardName, counter); - CounterSnapshot.CounterDataPointSnapshot.Builder dataPointBuilder = - CounterSnapshot.CounterDataPointSnapshot.builder() - .value(Long.valueOf(counter.getCount()).doubleValue()); - labelMapper.ifPresent( - mapper -> - dataPointBuilder.labels( - mapper.getLabels( - dropwizardName, Collections.emptyList(), Collections.emptyList()))); - return new CounterSnapshot(metadata, Collections.singletonList(dataPointBuilder.build())); + @Override + protected long getCounterCount(Counter counter) { + return counter.getCount(); } - /** Export gauge as a prometheus gauge. */ - MetricSnapshot fromGauge(String dropwizardName, Gauge gauge) { - Object obj = gauge.getValue(); - double value; - if (obj instanceof Number) { - value = ((Number) obj).doubleValue(); - } else if (obj instanceof Boolean) { - value = ((Boolean) obj) ? 1 : 0; - } else { - logger.log( - Level.FINE, - String.format( - "Invalid type for Gauge %s: %s", - PrometheusNaming.sanitizeMetricName(dropwizardName), - obj == null ? "null" : obj.getClass().getName())); - return null; - } - MetricMetadata metadata = getMetricMetaData(dropwizardName, gauge); - GaugeSnapshot.GaugeDataPointSnapshot.Builder dataPointBuilder = - GaugeSnapshot.GaugeDataPointSnapshot.builder().value(value); - labelMapper.ifPresent( - mapper -> - dataPointBuilder.labels( - mapper.getLabels( - dropwizardName, Collections.emptyList(), Collections.emptyList()))); - return new GaugeSnapshot(metadata, Collections.singletonList(dataPointBuilder.build())); + @Override + protected Object getGaugeValue(Gauge gauge) { + return gauge.getValue(); } - /** - * Export a histogram snapshot as a prometheus SUMMARY. - * - * @param dropwizardName metric name. - * @param snapshot the histogram snapshot. - * @param count the total sample count for this snapshot. - * @param factor a factor to apply to histogram values. - */ - MetricSnapshot fromSnapshotAndCount( - String dropwizardName, Snapshot snapshot, long count, double factor, String helpMessage) { - Quantiles quantiles = - Quantiles.builder() - .quantile(0.5, snapshot.getMedian() * factor) - .quantile(0.75, snapshot.get75thPercentile() * factor) - .quantile(0.95, snapshot.get95thPercentile() * factor) - .quantile(0.98, snapshot.get98thPercentile() * factor) - .quantile(0.99, snapshot.get99thPercentile() * factor) - .quantile(0.999, snapshot.get999thPercentile() * factor) - .build(); + @Override + protected Snapshot getHistogramSnapshot(Histogram histogram) { + return histogram.getSnapshot(); + } - String name = - labelMapper.isPresent() ? labelMapper.get().getName(dropwizardName) : dropwizardName; - MetricMetadata metadata = - new MetricMetadata(PrometheusNaming.sanitizeMetricName(name), helpMessage); - SummarySnapshot.SummaryDataPointSnapshot.Builder dataPointBuilder = - SummarySnapshot.SummaryDataPointSnapshot.builder().quantiles(quantiles).count(count); - labelMapper.ifPresent( - mapper -> - dataPointBuilder.labels( - mapper.getLabels( - dropwizardName, Collections.emptyList(), Collections.emptyList()))); - return new SummarySnapshot(metadata, Collections.singletonList(dataPointBuilder.build())); + @Override + protected long getHistogramCount(Histogram histogram) { + return histogram.getCount(); } - /** Convert histogram snapshot. */ - MetricSnapshot fromHistogram(String dropwizardName, Histogram histogram) { - return fromSnapshotAndCount( - dropwizardName, - histogram.getSnapshot(), - histogram.getCount(), - 1.0, - getHelpMessage(dropwizardName, histogram)); + @Override + protected Snapshot getTimerSnapshot(Timer timer) { + return timer.getSnapshot(); } - /** Export Dropwizard Timer as a histogram. Use TIME_UNIT as time unit. */ - MetricSnapshot fromTimer(String dropwizardName, Timer timer) { - return fromSnapshotAndCount( - dropwizardName, - timer.getSnapshot(), - timer.getCount(), - 1.0D / TimeUnit.SECONDS.toNanos(1L), - getHelpMessage(dropwizardName, timer)); + @Override + protected long getTimerCount(Timer timer) { + return timer.getCount(); } - /** Export a Meter as a prometheus COUNTER. */ - MetricSnapshot fromMeter(String dropwizardName, Meter meter) { - MetricMetadata metadata = getMetricMetaData(dropwizardName + "_total", meter); - CounterSnapshot.CounterDataPointSnapshot.Builder dataPointBuilder = - CounterSnapshot.CounterDataPointSnapshot.builder().value(meter.getCount()); - labelMapper.ifPresent( - mapper -> - dataPointBuilder.labels( - mapper.getLabels( - dropwizardName, Collections.emptyList(), Collections.emptyList()))); - return new CounterSnapshot(metadata, Collections.singletonList(dataPointBuilder.build())); + @Override + protected long getMeterCount(Meter meter) { + return meter.getCount(); } @Override - public MetricSnapshots collect() { - MetricSnapshots.Builder metricSnapshots = MetricSnapshots.builder(); - collectMetricKind(metricSnapshots, registry.getGauges(metricFilter), this::fromGauge); - collectMetricKind(metricSnapshots, registry.getCounters(metricFilter), this::fromCounter); - collectMetricKind(metricSnapshots, registry.getHistograms(metricFilter), this::fromHistogram); - collectMetricKind(metricSnapshots, registry.getTimers(metricFilter), this::fromTimer); - collectMetricKind(metricSnapshots, registry.getMeters(metricFilter), this::fromMeter); - return metricSnapshots.build(); + protected double getMedian(Snapshot snapshot) { + return snapshot.getMedian(); } - private void collectMetricKind( - MetricSnapshots.Builder builder, - Map metric, - BiFunction toSnapshot) { - for (Map.Entry entry : metric.entrySet()) { - String metricName = entry.getKey().getKey(); - try { - MetricSnapshot snapshot = toSnapshot.apply(metricName, entry.getValue()); - if (snapshot != null) { - builder.metricSnapshot(snapshot); - } - } catch (Exception e) { - if (!invalidMetricHandler.suppressException(metricName, e)) { - throw e; - } - } - } + @Override + protected double get75thPercentile(Snapshot snapshot) { + return snapshot.get75thPercentile(); + } + + @Override + protected double get95thPercentile(Snapshot snapshot) { + return snapshot.get95thPercentile(); + } + + @Override + protected double get98thPercentile(Snapshot snapshot) { + return snapshot.get98thPercentile(); + } + + @Override + protected double get99thPercentile(Snapshot snapshot) { + return snapshot.get99thPercentile(); + } + + @Override + protected double get999thPercentile(Snapshot snapshot) { + return snapshot.get999thPercentile(); } public static Builder builder() { @@ -252,9 +183,9 @@ public static Builder builder() { // Builder class for DropwizardExports public static class Builder { - private MetricRegistry registry; + @Nullable private MetricRegistry registry; private MetricFilter metricFilter; - private CustomLabelMapper labelMapper; + @Nullable private CustomLabelMapper labelMapper; private InvalidMetricHandler invalidMetricHandler; private Builder() { diff --git a/prometheus-metrics-instrumentation-dropwizard5/src/main/java/io/prometheus/metrics/instrumentation/dropwizard5/internal/AbstractDropwizardExports.java b/prometheus-metrics-instrumentation-dropwizard5/src/main/java/io/prometheus/metrics/instrumentation/dropwizard5/internal/AbstractDropwizardExports.java new file mode 100644 index 000000000..df9ea8ffa --- /dev/null +++ b/prometheus-metrics-instrumentation-dropwizard5/src/main/java/io/prometheus/metrics/instrumentation/dropwizard5/internal/AbstractDropwizardExports.java @@ -0,0 +1,236 @@ +package io.prometheus.metrics.instrumentation.dropwizard5.internal; + +import io.prometheus.metrics.instrumentation.dropwizard5.InvalidMetricHandler; +import io.prometheus.metrics.instrumentation.dropwizard5.labels.CustomLabelMapper; +import io.prometheus.metrics.model.registry.MultiCollector; +import io.prometheus.metrics.model.snapshots.CounterSnapshot; +import io.prometheus.metrics.model.snapshots.GaugeSnapshot; +import io.prometheus.metrics.model.snapshots.MetricMetadata; +import io.prometheus.metrics.model.snapshots.MetricSnapshot; +import io.prometheus.metrics.model.snapshots.MetricSnapshots; +import io.prometheus.metrics.model.snapshots.PrometheusNaming; +import io.prometheus.metrics.model.snapshots.Quantiles; +import io.prometheus.metrics.model.snapshots.SummarySnapshot; +import java.util.Collections; +import java.util.Map; +import java.util.concurrent.TimeUnit; +import java.util.function.BiFunction; +import java.util.logging.Level; +import java.util.logging.Logger; +import javax.annotation.Nullable; + +/** + * Abstract base class for Dropwizard metrics exporters. Contains all the common logic for + * converting Dropwizard metrics to Prometheus metrics. Subclasses only need to implement {@link + * #collectMetricSnapshots()} to handle version-specific registry APIs. + * + * @param The Dropwizard MetricRegistry type + * @param The Dropwizard MetricFilter type + * @param The Dropwizard Counter type + * @param The Dropwizard Gauge type + * @param The Dropwizard Histogram type + * @param The Dropwizard Timer type + * @param The Dropwizard Meter type + * @param The Dropwizard Metric base type + * @param The Dropwizard Snapshot type + */ +public abstract class AbstractDropwizardExports + implements MultiCollector { + + private static final Logger logger = Logger.getLogger(AbstractDropwizardExports.class.getName()); + + protected final R registry; + protected final F metricFilter; + @Nullable protected final CustomLabelMapper labelMapper; + protected final InvalidMetricHandler invalidMetricHandler; + + protected AbstractDropwizardExports( + R registry, + F metricFilter, + @Nullable CustomLabelMapper labelMapper, + InvalidMetricHandler invalidMetricHandler) { + this.registry = registry; + this.metricFilter = metricFilter; + this.labelMapper = labelMapper; + this.invalidMetricHandler = invalidMetricHandler; + } + + protected static String getHelpMessage(String metricName, Object metric) { + return String.format( + "Generated from Dropwizard metric import (metric=%s, type=%s)", + metricName, metric.getClass().getName()); + } + + protected MetricMetadata getMetricMetaData(String metricName, B metric) { + String name = labelMapper != null ? labelMapper.getName(metricName) : metricName; + return new MetricMetadata( + PrometheusNaming.sanitizeMetricName(name), getHelpMessage(metricName, metric)); + } + + /** + * Export counter as Prometheus Gauge. + */ + @SuppressWarnings("unchecked") + protected MetricSnapshot fromCounter(String dropwizardName, C counter) { + long count = getCounterCount(counter); + MetricMetadata metadata = getMetricMetaData(dropwizardName, (B) counter); + CounterSnapshot.CounterDataPointSnapshot.Builder dataPointBuilder = + CounterSnapshot.CounterDataPointSnapshot.builder().value(Long.valueOf(count).doubleValue()); + if (labelMapper != null) { + dataPointBuilder.labels( + labelMapper.getLabels(dropwizardName, Collections.emptyList(), Collections.emptyList())); + } + return new CounterSnapshot(metadata, Collections.singletonList(dataPointBuilder.build())); + } + + /** Export gauge as a prometheus gauge. */ + @SuppressWarnings("unchecked") + @Nullable + protected MetricSnapshot fromGauge(String dropwizardName, G gauge) { + Object obj = getGaugeValue(gauge); + double value; + if (obj instanceof Number) { + value = ((Number) obj).doubleValue(); + } else if (obj instanceof Boolean) { + value = ((Boolean) obj) ? 1 : 0; + } else { + logger.log( + Level.FINE, + String.format( + "Invalid type for Gauge %s: %s", + PrometheusNaming.sanitizeMetricName(dropwizardName), + obj == null ? "null" : obj.getClass().getName())); + return null; + } + MetricMetadata metadata = getMetricMetaData(dropwizardName, (B) gauge); + GaugeSnapshot.GaugeDataPointSnapshot.Builder dataPointBuilder = + GaugeSnapshot.GaugeDataPointSnapshot.builder().value(value); + if (labelMapper != null) { + dataPointBuilder.labels( + labelMapper.getLabels(dropwizardName, Collections.emptyList(), Collections.emptyList())); + } + return new GaugeSnapshot(metadata, Collections.singletonList(dataPointBuilder.build())); + } + + /** + * Export a histogram snapshot as a prometheus SUMMARY. + * + * @param dropwizardName metric name. + * @param snapshot the histogram snapshot. + * @param count the total sample count for this snapshot. + * @param factor a factor to apply to histogram values. + */ + protected MetricSnapshot fromSnapshotAndCount( + String dropwizardName, S snapshot, long count, double factor, String helpMessage) { + Quantiles quantiles = + Quantiles.builder() + .quantile(0.5, getMedian(snapshot) * factor) + .quantile(0.75, get75thPercentile(snapshot) * factor) + .quantile(0.95, get95thPercentile(snapshot) * factor) + .quantile(0.98, get98thPercentile(snapshot) * factor) + .quantile(0.99, get99thPercentile(snapshot) * factor) + .quantile(0.999, get999thPercentile(snapshot) * factor) + .build(); + + String name = labelMapper != null ? labelMapper.getName(dropwizardName) : dropwizardName; + MetricMetadata metadata = + new MetricMetadata(PrometheusNaming.sanitizeMetricName(name), helpMessage); + SummarySnapshot.SummaryDataPointSnapshot.Builder dataPointBuilder = + SummarySnapshot.SummaryDataPointSnapshot.builder().quantiles(quantiles).count(count); + if (labelMapper != null) { + dataPointBuilder.labels( + labelMapper.getLabels(dropwizardName, Collections.emptyList(), Collections.emptyList())); + } + return new SummarySnapshot(metadata, Collections.singletonList(dataPointBuilder.build())); + } + + /** Convert histogram snapshot. */ + protected MetricSnapshot fromHistogram(String dropwizardName, H histogram) { + S snapshot = getHistogramSnapshot(histogram); + long count = getHistogramCount(histogram); + return fromSnapshotAndCount( + dropwizardName, snapshot, count, 1.0, getHelpMessage(dropwizardName, histogram)); + } + + /** Export Dropwizard Timer as a histogram. Use TIME_UNIT as time unit. */ + protected MetricSnapshot fromTimer(String dropwizardName, T timer) { + S snapshot = getTimerSnapshot(timer); + long count = getTimerCount(timer); + return fromSnapshotAndCount( + dropwizardName, + snapshot, + count, + 1.0D / TimeUnit.SECONDS.toNanos(1L), + getHelpMessage(dropwizardName, timer)); + } + + /** Export a Meter as a prometheus COUNTER. */ + @SuppressWarnings("unchecked") + protected MetricSnapshot fromMeter(String dropwizardName, M meter) { + MetricMetadata metadata = getMetricMetaData(dropwizardName + "_total", (B) meter); + CounterSnapshot.CounterDataPointSnapshot.Builder dataPointBuilder = + CounterSnapshot.CounterDataPointSnapshot.builder().value(getMeterCount(meter)); + if (labelMapper != null) { + dataPointBuilder.labels( + labelMapper.getLabels(dropwizardName, Collections.emptyList(), Collections.emptyList())); + } + return new CounterSnapshot(metadata, Collections.singletonList(dataPointBuilder.build())); + } + + @Override + public MetricSnapshots collect() { + return collectMetricSnapshots(); + } + + protected void collectMetricKind( + MetricSnapshots.Builder builder, + Map metrics, + BiFunction toSnapshot, + java.util.function.Function keyExtractor) { + for (Map.Entry entry : metrics.entrySet()) { + String metricName = keyExtractor.apply(entry.getKey()); + try { + MetricSnapshot snapshot = toSnapshot.apply(metricName, entry.getValue()); + if (snapshot != null) { + builder.metricSnapshot(snapshot); + } + } catch (RuntimeException e) { + if (!invalidMetricHandler.suppressException(metricName, e)) { + throw e; + } + } + } + } + + // Abstract methods to be implemented by version-specific subclasses + + /** Collect all metric snapshots from the registry. */ + protected abstract MetricSnapshots collectMetricSnapshots(); + + protected abstract long getCounterCount(C counter); + + protected abstract Object getGaugeValue(G gauge); + + protected abstract S getHistogramSnapshot(H histogram); + + protected abstract long getHistogramCount(H histogram); + + protected abstract S getTimerSnapshot(T timer); + + protected abstract long getTimerCount(T timer); + + protected abstract long getMeterCount(M meter); + + protected abstract double getMedian(S snapshot); + + protected abstract double get75thPercentile(S snapshot); + + protected abstract double get95thPercentile(S snapshot); + + protected abstract double get98thPercentile(S snapshot); + + protected abstract double get99thPercentile(S snapshot); + + protected abstract double get999thPercentile(S snapshot); +} diff --git a/prometheus-metrics-instrumentation-dropwizard5/src/main/java/io/prometheus/metrics/instrumentation/dropwizard5/labels/CustomLabelMapper.java b/prometheus-metrics-instrumentation-dropwizard5/src/main/java/io/prometheus/metrics/instrumentation/dropwizard5/labels/CustomLabelMapper.java index af45769cc..f2174970c 100644 --- a/prometheus-metrics-instrumentation-dropwizard5/src/main/java/io/prometheus/metrics/instrumentation/dropwizard5/labels/CustomLabelMapper.java +++ b/prometheus-metrics-instrumentation-dropwizard5/src/main/java/io/prometheus/metrics/instrumentation/dropwizard5/labels/CustomLabelMapper.java @@ -13,18 +13,18 @@ public class CustomLabelMapper { private final List compiledMapperConfigs; - public CustomLabelMapper(final List mapperConfigs) { + public CustomLabelMapper(List mapperConfigs) { if (mapperConfigs == null || mapperConfigs.isEmpty()) { throw new IllegalArgumentException("CustomLabelMapper needs some mapper configs!"); } - this.compiledMapperConfigs = new ArrayList(mapperConfigs.size()); + this.compiledMapperConfigs = new ArrayList<>(mapperConfigs.size()); for (MapperConfig config : mapperConfigs) { this.compiledMapperConfigs.add(new CompiledMapperConfig(config)); } } - public String getName(final String dropwizardName) { + public String getName(String dropwizardName) { if (dropwizardName == null) { throw new IllegalArgumentException("Dropwizard metric name cannot be null"); } @@ -73,8 +73,8 @@ public Labels getLabels( return Labels.of(additionalLabelNames, additionalLabelValues); } - protected NameAndLabels getNameAndLabels( - final MapperConfig config, final Map parameters) { + @SuppressWarnings("NullAway") // not sure if it can be null here + protected NameAndLabels getNameAndLabels(MapperConfig config, Map parameters) { final String metricName = formatTemplate(config.getName(), parameters); final List labels = new ArrayList(config.getLabels().size()); final List labelValues = new ArrayList(config.getLabels().size()); @@ -86,7 +86,7 @@ protected NameAndLabels getNameAndLabels( return new NameAndLabels(metricName, labels, labelValues); } - private String formatTemplate(final String template, final Map params) { + private String formatTemplate(String template, Map params) { String result = template; for (Map.Entry entry : params.entrySet()) { result = result.replace(entry.getKey(), entry.getValue()); @@ -99,7 +99,8 @@ static class CompiledMapperConfig { final MapperConfig mapperConfig; final GraphiteNamePattern pattern; - CompiledMapperConfig(final MapperConfig mapperConfig) { + @SuppressWarnings("NullAway") // not sure if it can be null here + CompiledMapperConfig(MapperConfig mapperConfig) { this.mapperConfig = mapperConfig; this.pattern = new GraphiteNamePattern(mapperConfig.getMatch()); } diff --git a/prometheus-metrics-instrumentation-dropwizard5/src/main/java/io/prometheus/metrics/instrumentation/dropwizard5/labels/GraphiteNamePattern.java b/prometheus-metrics-instrumentation-dropwizard5/src/main/java/io/prometheus/metrics/instrumentation/dropwizard5/labels/GraphiteNamePattern.java index bd37fe04c..c13ffd91c 100644 --- a/prometheus-metrics-instrumentation-dropwizard5/src/main/java/io/prometheus/metrics/instrumentation/dropwizard5/labels/GraphiteNamePattern.java +++ b/prometheus-metrics-instrumentation-dropwizard5/src/main/java/io/prometheus/metrics/instrumentation/dropwizard5/labels/GraphiteNamePattern.java @@ -31,7 +31,7 @@ class GraphiteNamePattern { * * @param pattern The glob style pattern to be used. */ - GraphiteNamePattern(final String pattern) throws IllegalArgumentException { + GraphiteNamePattern(String pattern) throws IllegalArgumentException { if (!VALIDATION_PATTERN.matcher(pattern).matches()) { throw new IllegalArgumentException( String.format("Provided pattern [%s] does not matches [%s]", pattern, METRIC_GLOB_REGEX)); diff --git a/prometheus-metrics-instrumentation-dropwizard5/src/main/java/io/prometheus/metrics/instrumentation/dropwizard5/labels/MapperConfig.java b/prometheus-metrics-instrumentation-dropwizard5/src/main/java/io/prometheus/metrics/instrumentation/dropwizard5/labels/MapperConfig.java index 22f1c3339..13d890861 100644 --- a/prometheus-metrics-instrumentation-dropwizard5/src/main/java/io/prometheus/metrics/instrumentation/dropwizard5/labels/MapperConfig.java +++ b/prometheus-metrics-instrumentation-dropwizard5/src/main/java/io/prometheus/metrics/instrumentation/dropwizard5/labels/MapperConfig.java @@ -3,6 +3,7 @@ import java.util.HashMap; import java.util.Map; import java.util.regex.Pattern; +import javax.annotation.Nullable; /** * POJO containing info on how to map a graphite metric to a prometheus one. Example mapping in yaml @@ -30,7 +31,7 @@ public final class MapperConfig { * allowed. E.g: org.company.controller.*.status.* Will be used to match * org.company.controller.controller1.status.200 and org.company.controller.controller2.status.400 */ - private String match; + @Nullable private String match; /** * New metric name. Can contain placeholders to be replaced with actual values from the incoming @@ -41,7 +42,7 @@ public final class MapperConfig { *

A metric "test.dispatcher.old.test.yay" will be converted in a new metric with name * "dispatcher_events_total_test" */ - private String name; + @Nullable private String name; /** * Labels to be extracted from the metric name. They should contain placeholders to be replaced @@ -54,19 +55,19 @@ public final class MapperConfig { * *

Label names have to match the regex ^[a-zA-Z_][a-zA-Z0-9_]+$ */ - private Map labels = new HashMap(); + private Map labels = new HashMap<>(); public MapperConfig() { // empty constructor } // for tests - MapperConfig(final String match) { + MapperConfig(String match) { validateMatch(match); this.match = match; } - public MapperConfig(final String match, final String name, final Map labels) { + public MapperConfig(String match, String name, Map labels) { this.name = name; validateMatch(match); this.match = match; @@ -79,20 +80,22 @@ public String toString() { return String.format("MapperConfig{match=%s, name=%s, labels=%s}", match, name, labels); } + @Nullable public String getMatch() { return match; } - public void setMatch(final String match) { + public void setMatch(String match) { validateMatch(match); this.match = match; } + @Nullable public String getName() { return name; } - public void setName(final String name) { + public void setName(String name) { this.name = name; } @@ -100,12 +103,12 @@ public Map getLabels() { return labels; } - public void setLabels(final Map labels) { + public void setLabels(Map labels) { validateLabels(labels); this.labels = labels; } - private void validateMatch(final String match) { + private void validateMatch(String match) { if (!MATCH_EXPRESSION_PATTERN.matcher(match).matches()) { throw new IllegalArgumentException( String.format( @@ -114,9 +117,9 @@ private void validateMatch(final String match) { } } - private void validateLabels(final Map labels) { + private void validateLabels(Map labels) { if (labels != null) { - for (final String key : labels.keySet()) { + for (String key : labels.keySet()) { if (!LABEL_PATTERN.matcher(key).matches()) { throw new IllegalArgumentException( String.format("Label [%s] does not match required pattern %s", match, LABEL_PATTERN)); @@ -126,7 +129,7 @@ private void validateLabels(final Map labels) { } @Override - public boolean equals(final Object o) { + public boolean equals(Object o) { if (this == o) { return true; } diff --git a/prometheus-metrics-instrumentation-dropwizard5/src/test/java/io/prometheus/metrics/instrumentation/dropwizard5/DropwizardExportsTest.java b/prometheus-metrics-instrumentation-dropwizard5/src/test/java/io/prometheus/metrics/instrumentation/dropwizard5/DropwizardExportsTest.java index 3feb5e69c..7a7dcdf31 100644 --- a/prometheus-metrics-instrumentation-dropwizard5/src/test/java/io/prometheus/metrics/instrumentation/dropwizard5/DropwizardExportsTest.java +++ b/prometheus-metrics-instrumentation-dropwizard5/src/test/java/io/prometheus/metrics/instrumentation/dropwizard5/DropwizardExportsTest.java @@ -5,6 +5,7 @@ import static org.assertj.core.data.Offset.offset; import io.dropwizard.metrics5.*; +import io.prometheus.metrics.config.EscapingScheme; import io.prometheus.metrics.expositionformats.OpenMetricsTextFormatWriter; import io.prometheus.metrics.model.registry.PrometheusRegistry; import io.prometheus.metrics.model.snapshots.MetricSnapshots; @@ -23,16 +24,16 @@ class DropwizardExportsTest { private MetricRegistry metricRegistry; @BeforeEach - public void setUp() { + void setUp() { metricRegistry = new MetricRegistry(); DropwizardExports.builder().dropwizardRegistry(metricRegistry).register(registry); } @Test - public void testCounter() { + void testCounter() { metricRegistry.counter("foo.bar").inc(1); String expected = - """ +""" # TYPE foo_bar counter # HELP foo_bar Generated from Dropwizard metric import (metric=foo.bar, type=io.dropwizard.metrics5.Counter) foo_bar_total 1.0 @@ -43,7 +44,7 @@ public void testCounter() { } @Test - public void testGauge() { + void testGauge() { // don't convert to lambda, as we need to test the type Gauge integerGauge = new Gauge() { @@ -88,7 +89,7 @@ public Boolean getValue() { metricRegistry.register(MetricName.parse("boolean.gauge"), booleanGauge); String expected = - """ +""" # TYPE boolean_gauge gauge # HELP boolean_gauge Generated from Dropwizard metric import (metric=boolean.gauge, type=io.prometheus.metrics.instrumentation.dropwizard5.DropwizardExportsTest$5) boolean_gauge 1.0 @@ -111,7 +112,7 @@ public Boolean getValue() { } @Test - public void testInvalidGaugeType() { + void testInvalidGaugeType() { Gauge invalidGauge = () -> "foobar"; metricRegistry.register(MetricName.parse("invalid_gauge"), invalidGauge); @@ -121,7 +122,7 @@ public void testInvalidGaugeType() { } @Test - public void testGaugeReturningNullValue() { + void testGaugeReturningNullValue() { Gauge invalidGauge = () -> null; metricRegistry.register(MetricName.parse("invalid_gauge"), invalidGauge); String expected = "# EOF\n"; @@ -129,7 +130,7 @@ public void testGaugeReturningNullValue() { } @Test - public void testHistogram() { + void testHistogram() { // just test the standard mapper final MetricRegistry metricRegistry = new MetricRegistry(); PrometheusRegistry pmRegistry = new PrometheusRegistry(); @@ -203,13 +204,13 @@ public void testHistogram() { } @Test - public void testMeter() { + void testMeter() { Meter meter = metricRegistry.meter("meter"); meter.mark(); meter.mark(); String expected = - """ +""" # TYPE meter counter # HELP meter Generated from Dropwizard metric import (metric=meter_total, type=io.dropwizard.metrics5.Meter) meter_total 2.0 @@ -219,7 +220,7 @@ public void testMeter() { } @Test - public void testTimer() throws InterruptedException { + void testTimer() throws InterruptedException { final MetricRegistry metricRegistry = new MetricRegistry(); DropwizardExports exports = new DropwizardExports(metricRegistry); Timer t = metricRegistry.timer("timer"); @@ -245,7 +246,7 @@ public void testTimer() throws InterruptedException { } @Test - public void testThatMetricHelpUsesOriginalDropwizardName() { + void testThatMetricHelpUsesOriginalDropwizardName() { metricRegistry.timer("my.application.namedTimer1"); metricRegistry.counter("my.application.namedCounter1"); @@ -255,7 +256,7 @@ public void testThatMetricHelpUsesOriginalDropwizardName() { MetricName.parse("my.application.namedGauge1"), new ExampleDoubleGauge()); String expected = - """ +""" # TYPE my_application_namedCounter1 counter # HELP my_application_namedCounter1 Generated from Dropwizard metric import (metric=my.application.namedCounter1, type=io.dropwizard.metrics5.Counter) my_application_namedCounter1_total 0.0 @@ -294,7 +295,7 @@ void responseWhenRegistryIsEmpty() { registry.register(DropwizardExports.builder().dropwizardRegistry(metricRegistry).build()); assertThat(convertToOpenMetricsFormat(registry)) .isEqualTo( - """ +""" # EOF """); } @@ -328,7 +329,7 @@ void collectInvalidMetricPassesWhenExceptionIsIgnored() { .register(registry); assertThat(convertToOpenMetricsFormat(registry)) .isEqualTo( - """ +""" # TYPE my_application_namedCounter2 counter # HELP my_application_namedCounter2 Generated from Dropwizard metric import (metric=my.application.namedCounter2, type=io.dropwizard.metrics5.Counter) my_application_namedCounter2_total 10.0 @@ -349,8 +350,8 @@ private String convertToOpenMetricsFormat(PrometheusRegistry _registry) { ByteArrayOutputStream out = new ByteArrayOutputStream(); OpenMetricsTextFormatWriter writer = new OpenMetricsTextFormatWriter(true, true); try { - writer.write(out, _registry.scrape()); - return out.toString(StandardCharsets.UTF_8.name()); + writer.write(out, _registry.scrape(), EscapingScheme.UNDERSCORE_ESCAPING); + return out.toString(StandardCharsets.UTF_8); } catch (IOException e) { throw new RuntimeException(e); } diff --git a/prometheus-metrics-instrumentation-dropwizard5/src/test/java/io/prometheus/metrics/instrumentation/dropwizard5/labels/CustomLabelMapperTest.java b/prometheus-metrics-instrumentation-dropwizard5/src/test/java/io/prometheus/metrics/instrumentation/dropwizard5/labels/CustomLabelMapperTest.java index ea9636e0c..8c61e1a01 100644 --- a/prometheus-metrics-instrumentation-dropwizard5/src/test/java/io/prometheus/metrics/instrumentation/dropwizard5/labels/CustomLabelMapperTest.java +++ b/prometheus-metrics-instrumentation-dropwizard5/src/test/java/io/prometheus/metrics/instrumentation/dropwizard5/labels/CustomLabelMapperTest.java @@ -5,6 +5,7 @@ import io.dropwizard.metrics5.MetricFilter; import io.dropwizard.metrics5.MetricRegistry; +import io.prometheus.metrics.config.EscapingScheme; import io.prometheus.metrics.expositionformats.OpenMetricsTextFormatWriter; import io.prometheus.metrics.instrumentation.dropwizard5.DropwizardExports; import io.prometheus.metrics.model.snapshots.MetricSnapshots; @@ -19,18 +20,18 @@ class CustomLabelMapperTest { private MetricRegistry metricRegistry; @BeforeEach - public void setUp() { + void setUp() { metricRegistry = new MetricRegistry(); } @Test - public void test_WHEN_EmptyConfig_THEN_Fail() { + void test_WHEN_EmptyConfig_THEN_Fail() { assertThatExceptionOfType(IllegalArgumentException.class) .isThrownBy(() -> new CustomLabelMapper(Collections.emptyList())); } @Test - public void test_WHEN_NoMatches_THEN_ShouldReturnDefaultSample() { + void test_WHEN_NoMatches_THEN_ShouldReturnDefaultSample() { final List mapperConfigs = Arrays.asList( new MapperConfig("client-nope.*.*.*"), @@ -44,7 +45,7 @@ public void test_WHEN_NoMatches_THEN_ShouldReturnDefaultSample() { System.out.println(convertToOpenMetricsFormat(dropwizardExports.collect())); String expected = - """ +""" # TYPE app_okhttpclient_client_HttpClient_service counter # HELP app_okhttpclient_client_HttpClient_service Generated from Dropwizard metric import (metric=app.okhttpclient.client.HttpClient.service.total, type=io.dropwizard.metrics5.Counter) app_okhttpclient_client_HttpClient_service_total 1.0 @@ -55,7 +56,7 @@ public void test_WHEN_NoMatches_THEN_ShouldReturnDefaultSample() { } @Test - public void test_WHEN_OneMatch_THEN_ShouldReturnConverted() { + void test_WHEN_OneMatch_THEN_ShouldReturnConverted() { final Map labels = new HashMap(); labels.put("service", "${0}"); final MapperConfig mapperConfig = @@ -75,7 +76,7 @@ public void test_WHEN_OneMatch_THEN_ShouldReturnConverted() { metricRegistry.counter("app.okhttpclient.client.HttpClient.greatService.total").inc(1); String expected = - """ +""" # TYPE app_okhttpclient_client_HttpClient counter # HELP app_okhttpclient_client_HttpClient Generated from Dropwizard metric import (metric=app.okhttpclient.client.HttpClient.greatService.total, type=io.dropwizard.metrics5.Counter) app_okhttpclient_client_HttpClient_total{service="greatService"} 1.0 @@ -85,7 +86,7 @@ public void test_WHEN_OneMatch_THEN_ShouldReturnConverted() { } @Test - public void test_WHEN_MoreMatches_THEN_ShouldReturnFirstOne() { + void test_WHEN_MoreMatches_THEN_ShouldReturnFirstOne() { final Map labels = new HashMap<>(); labels.put("service", "${0}"); final MapperConfig mapperConfig = @@ -106,7 +107,7 @@ public void test_WHEN_MoreMatches_THEN_ShouldReturnFirstOne() { metricRegistry.counter("app.okhttpclient.client.HttpClient.greatService.total").inc(1); String expected = - """ +""" # TYPE app_okhttpclient_client_HttpClient counter # HELP app_okhttpclient_client_HttpClient Generated from Dropwizard metric import (metric=app.okhttpclient.client.HttpClient.greatService.total, type=io.dropwizard.metrics5.Counter) app_okhttpclient_client_HttpClient_total{service="greatService"} 1.0 @@ -116,7 +117,7 @@ public void test_WHEN_MoreMatches_THEN_ShouldReturnFirstOne() { } @Test - public void test_WHEN_MoreMatchesReverseOrder_THEN_ShouldReturnFirstOne() { + void test_WHEN_MoreMatchesReverseOrder_THEN_ShouldReturnFirstOne() { final Map labels = new LinkedHashMap<>(); labels.put("service", "${0}"); labels.put("status", "${1}"); @@ -143,7 +144,7 @@ public void test_WHEN_MoreMatchesReverseOrder_THEN_ShouldReturnFirstOne() { metricRegistry.counter("app.okhttpclient.client.HttpClient.greatService.400").inc(1); String expected = - """ +""" # TYPE app_okhttpclient_client_HttpClient counter # HELP app_okhttpclient_client_HttpClient Generated from Dropwizard metric import (metric=app.okhttpclient.client.HttpClient.greatService.400, type=io.dropwizard.metrics5.Counter) app_okhttpclient_client_HttpClient_total{service="greatService",status="400"} 1.0 @@ -153,7 +154,7 @@ public void test_WHEN_MoreMatchesReverseOrder_THEN_ShouldReturnFirstOne() { } @Test - public void test_WHEN_MoreToFormatInLabelsAndName_THEN_ShouldReturnCorrectSample() { + void test_WHEN_MoreToFormatInLabelsAndName_THEN_ShouldReturnCorrectSample() { final Map labels = new LinkedHashMap<>(); labels.put("service", "${0}_${1}"); labels.put("status", "s_${1}"); @@ -176,7 +177,7 @@ public void test_WHEN_MoreToFormatInLabelsAndName_THEN_ShouldReturnCorrectSample System.out.println(convertToOpenMetricsFormat(dropwizardExports.collect())); String expected = - """ +""" # TYPE app_okhttpclient_client_HttpClient_greatService counter # HELP app_okhttpclient_client_HttpClient_greatService Generated from Dropwizard metric import (metric=app.okhttpclient.client.HttpClient.greatService.400, type=io.dropwizard.metrics5.Counter) app_okhttpclient_client_HttpClient_greatService_total{service="greatService_400",status="s_400"} 1.0 @@ -186,7 +187,7 @@ public void test_WHEN_MoreToFormatInLabelsAndName_THEN_ShouldReturnCorrectSample } @Test - public void test_WHEN_AdditionalLabels_THEN_ShouldReturnCorrectSample() { + void test_WHEN_AdditionalLabels_THEN_ShouldReturnCorrectSample() { final Map labels = new LinkedHashMap<>(); labels.put("service", "${0}"); labels.put("status", "s_${1}"); @@ -205,7 +206,7 @@ public void test_WHEN_AdditionalLabels_THEN_ShouldReturnCorrectSample() { metricRegistry.counter("app.okhttpclient.client.HttpClient.greatService.400").inc(1); String expected = - """ +""" # TYPE app_okhttpclient_client_HttpClient_greatService counter # HELP app_okhttpclient_client_HttpClient_greatService Generated from Dropwizard metric import (metric=app.okhttpclient.client.HttpClient.greatService.400, type=io.dropwizard.metrics5.Counter) app_okhttpclient_client_HttpClient_greatService_total{client="sampleClient",service="greatService",status="s_400"} 1.0 @@ -218,8 +219,8 @@ private String convertToOpenMetricsFormat(MetricSnapshots snapshots) { ByteArrayOutputStream out = new ByteArrayOutputStream(); OpenMetricsTextFormatWriter writer = new OpenMetricsTextFormatWriter(true, true); try { - writer.write(out, snapshots); - return out.toString(StandardCharsets.UTF_8.name()); + writer.write(out, snapshots, EscapingScheme.UNDERSCORE_ESCAPING); + return out.toString(StandardCharsets.UTF_8); } catch (IOException e) { throw new RuntimeException(e); } diff --git a/prometheus-metrics-instrumentation-dropwizard5/src/test/java/io/prometheus/metrics/instrumentation/dropwizard5/labels/GraphiteNamePatternTest.java b/prometheus-metrics-instrumentation-dropwizard5/src/test/java/io/prometheus/metrics/instrumentation/dropwizard5/labels/GraphiteNamePatternTest.java index ed27d7f22..100703d94 100644 --- a/prometheus-metrics-instrumentation-dropwizard5/src/test/java/io/prometheus/metrics/instrumentation/dropwizard5/labels/GraphiteNamePatternTest.java +++ b/prometheus-metrics-instrumentation-dropwizard5/src/test/java/io/prometheus/metrics/instrumentation/dropwizard5/labels/GraphiteNamePatternTest.java @@ -13,7 +13,7 @@ class GraphiteNamePatternTest { @Test - public void createNew_WHEN_InvalidPattern_THEN_ShouldThrowException() { + void createNew_WHEN_InvalidPattern_THEN_ShouldThrowException() { List invalidPatterns = Arrays.asList( "", @@ -39,7 +39,7 @@ public void createNew_WHEN_InvalidPattern_THEN_ShouldThrowException() { } @Test - public void createNew_WHEN_ValidPattern_THEN_ShouldCreateThePatternSuccessfully() { + void createNew_WHEN_ValidPattern_THEN_ShouldCreateThePatternSuccessfully() { final List validPatterns = Arrays.asList( "org.test.controller.gather.status.400", @@ -54,7 +54,7 @@ public void createNew_WHEN_ValidPattern_THEN_ShouldCreateThePatternSuccessfully( } @Test - public void createNew_WHEN_ValidPattern_THEN_ShouldInitInternalPatternSuccessfully() { + void createNew_WHEN_ValidPattern_THEN_ShouldInitInternalPatternSuccessfully() { final Map validPatterns = new HashMap(); validPatterns.put( "org.test.controller.gather.status.400", "^\\Qorg.test.controller.gather.status.400\\E$"); @@ -74,7 +74,7 @@ public void createNew_WHEN_ValidPattern_THEN_ShouldInitInternalPatternSuccessful } @Test - public void match_WHEN_NotMatchingMetricNameProvided_THEN_ShouldNotMatch() { + void match_WHEN_NotMatchingMetricNameProvided_THEN_ShouldNotMatch() { final GraphiteNamePattern pattern = new GraphiteNamePattern("org.test.controller.*.status.*"); final List notMatchingMetricNamed = Arrays.asList("org.test.controller.status.400", "", null); @@ -87,7 +87,7 @@ public void match_WHEN_NotMatchingMetricNameProvided_THEN_ShouldNotMatch() { } @Test - public void match_WHEN_MatchingMetricNameProvided_THEN_ShouldMatch() { + void match_WHEN_MatchingMetricNameProvided_THEN_ShouldMatch() { final GraphiteNamePattern pattern = new GraphiteNamePattern("org.test.controller.*.status.*"); final List matchingMetricNamed = Arrays.asList( @@ -105,7 +105,7 @@ public void match_WHEN_MatchingMetricNameProvided_THEN_ShouldMatch() { } @Test - public void extractParameters() { + void extractParameters() { GraphiteNamePattern pattern; Map expected = new HashMap(); expected.put("${0}", "gather"); @@ -124,7 +124,7 @@ public void extractParameters() { } @Test - public void extractParameters_WHEN_emptyStringInDottedMetricsName_THEN_ShouldReturnEmptyString() { + void extractParameters_WHEN_emptyStringInDottedMetricsName_THEN_ShouldReturnEmptyString() { GraphiteNamePattern pattern; Map expected = new HashMap(); expected.put("${0}", ""); @@ -135,7 +135,7 @@ public void extractParameters_WHEN_emptyStringInDottedMetricsName_THEN_ShouldRet } @Test - public void extractParameters_WHEN_moreDots_THEN_ShouldReturnNoMatches() { + void extractParameters_WHEN_moreDots_THEN_ShouldReturnNoMatches() { GraphiteNamePattern pattern; pattern = new GraphiteNamePattern("org.test.controller.*.status.*"); Assertions.assertThat(pattern.extractParameters("org.test.controller...status.400")) diff --git a/prometheus-metrics-instrumentation-dropwizard5/src/test/java/io/prometheus/metrics/instrumentation/dropwizard5/labels/MapperConfigTest.java b/prometheus-metrics-instrumentation-dropwizard5/src/test/java/io/prometheus/metrics/instrumentation/dropwizard5/labels/MapperConfigTest.java index a17963e0d..982e8f24e 100644 --- a/prometheus-metrics-instrumentation-dropwizard5/src/test/java/io/prometheus/metrics/instrumentation/dropwizard5/labels/MapperConfigTest.java +++ b/prometheus-metrics-instrumentation-dropwizard5/src/test/java/io/prometheus/metrics/instrumentation/dropwizard5/labels/MapperConfigTest.java @@ -10,20 +10,20 @@ class MapperConfigTest { @Test - public void setMatch_WHEN_ExpressionMatchesPattern_AllGood() { + void setMatch_WHEN_ExpressionMatchesPattern_AllGood() { final MapperConfig mapperConfig = new MapperConfig(); mapperConfig.setMatch("com.company.meter.*"); assertThat(mapperConfig.getMatch()).isEqualTo("com.company.meter.*"); } @Test - public void setMatch_WHEN_ExpressionDoesnNotMatchPattern_ThrowException() { + void setMatch_WHEN_ExpressionDoesnNotMatchPattern_ThrowException() { assertThatExceptionOfType(IllegalArgumentException.class) .isThrownBy(() -> new MapperConfig().setMatch("com.company.meter.**.yay")); } @Test - public void setLabels_WHEN_ExpressionMatchesPattern_AllGood() { + void setLabels_WHEN_ExpressionMatchesPattern_AllGood() { final MapperConfig mapperConfig = new MapperConfig(); final Map labels = new HashMap<>(); labels.put("valid", "${0}"); @@ -32,7 +32,7 @@ public void setLabels_WHEN_ExpressionMatchesPattern_AllGood() { } @Test - public void setLabels_WHEN_ExpressionDoesnNotMatchPattern_ThrowException() { + void setLabels_WHEN_ExpressionDoesnNotMatchPattern_ThrowException() { final MapperConfig mapperConfig = new MapperConfig(); final Map labels = new HashMap<>(); labels.put("valid", "${0}"); @@ -42,13 +42,13 @@ public void setLabels_WHEN_ExpressionDoesnNotMatchPattern_ThrowException() { } @Test - public void toString_WHEN_EmptyConfig_AllGood() { + void toString_WHEN_EmptyConfig_AllGood() { final MapperConfig mapperConfig = new MapperConfig(); assertThat(mapperConfig).hasToString("MapperConfig{match=null, name=null, labels={}}"); } @Test - public void toString_WHEN_FullyConfigured_AllGood() { + void toString_WHEN_FullyConfigured_AllGood() { final MapperConfig mapperConfig = new MapperConfig(); mapperConfig.setMatch("com.company.meter.*.foo"); mapperConfig.setName("foo"); diff --git a/prometheus-metrics-instrumentation-guava/pom.xml b/prometheus-metrics-instrumentation-guava/pom.xml index 729a7ee93..cb333eacd 100644 --- a/prometheus-metrics-instrumentation-guava/pom.xml +++ b/prometheus-metrics-instrumentation-guava/pom.xml @@ -6,7 +6,7 @@ io.prometheus client_java - 1.4.0-SNAPSHOT + 1.6.0-SNAPSHOT prometheus-metrics-instrumentation-guava diff --git a/prometheus-metrics-instrumentation-guava/src/test/java/io/prometheus/metrics/instrumentation/guava/CacheMetricsCollectorTest.java b/prometheus-metrics-instrumentation-guava/src/test/java/io/prometheus/metrics/instrumentation/guava/CacheMetricsCollectorTest.java index c751e9ec4..3373afaed 100644 --- a/prometheus-metrics-instrumentation-guava/src/test/java/io/prometheus/metrics/instrumentation/guava/CacheMetricsCollectorTest.java +++ b/prometheus-metrics-instrumentation-guava/src/test/java/io/prometheus/metrics/instrumentation/guava/CacheMetricsCollectorTest.java @@ -9,13 +9,10 @@ import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheLoader; import com.google.common.cache.LoadingCache; +import io.prometheus.metrics.config.EscapingScheme; import io.prometheus.metrics.expositionformats.OpenMetricsTextFormatWriter; import io.prometheus.metrics.model.registry.PrometheusRegistry; -import io.prometheus.metrics.model.snapshots.CounterSnapshot; -import io.prometheus.metrics.model.snapshots.DataPointSnapshot; -import io.prometheus.metrics.model.snapshots.Labels; -import io.prometheus.metrics.model.snapshots.MetricSnapshots; -import io.prometheus.metrics.model.snapshots.SummarySnapshot; +import io.prometheus.metrics.model.snapshots.*; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.UncheckedIOException; @@ -26,7 +23,7 @@ class CacheMetricsCollectorTest { @Test - public void cacheExposesMetricsForHitMissAndEviction() { + void cacheExposesMetricsForHitMissAndEviction() { final Cache cache = CacheBuilder.newBuilder().maximumSize(2).recordStats().build(); @@ -76,7 +73,7 @@ public void cacheExposesMetricsForHitMissAndEviction() { @SuppressWarnings("unchecked") @Test - public void loadingCacheExposesMetricsForLoadsAndExceptions() throws Exception { + void loadingCacheExposesMetricsForLoadsAndExceptions() throws Exception { final CacheLoader loader = mock(CacheLoader.class); when(loader.load(anyString())) .thenReturn("First User") @@ -115,7 +112,7 @@ public void loadingCacheExposesMetricsForLoadsAndExceptions() throws Exception { } @Test - public void getPrometheusNamesHasSameSizeAsMetricSizeWhenScraping() { + void getPrometheusNamesHasSameSizeAsMetricSizeWhenScraping() { final CacheMetricsCollector collector = new CacheMetricsCollector(); final PrometheusRegistry registry = new PrometheusRegistry(); @@ -128,7 +125,7 @@ public void getPrometheusNamesHasSameSizeAsMetricSizeWhenScraping() { } @Test - public void collectedMetricNamesAreKnownPrometheusNames() { + void collectedMetricNamesAreKnownPrometheusNames() { final CacheMetricsCollector collector = new CacheMetricsCollector(); final PrometheusRegistry registry = new PrometheusRegistry(); @@ -165,8 +162,8 @@ private String convertToOpenMetricsFormat(PrometheusRegistry registry) { final ByteArrayOutputStream out = new ByteArrayOutputStream(); final OpenMetricsTextFormatWriter writer = new OpenMetricsTextFormatWriter(true, true); try { - writer.write(out, registry.scrape()); - return out.toString(StandardCharsets.UTF_8.name()); + writer.write(out, registry.scrape(), EscapingScheme.ALLOW_UTF8); + return out.toString(StandardCharsets.UTF_8); } catch (IOException e) { throw new UncheckedIOException(e); } diff --git a/prometheus-metrics-instrumentation-jvm/pom.xml b/prometheus-metrics-instrumentation-jvm/pom.xml index bc920ee39..f60b55a49 100644 --- a/prometheus-metrics-instrumentation-jvm/pom.xml +++ b/prometheus-metrics-instrumentation-jvm/pom.xml @@ -6,7 +6,7 @@ io.prometheus client_java - 1.4.0-SNAPSHOT + 1.6.0-SNAPSHOT prometheus-metrics-instrumentation-jvm @@ -19,7 +19,7 @@ io.prometheus.metrics.instrumentation.jvm - 0.55 + 0.60 diff --git a/prometheus-metrics-instrumentation-jvm/src/main/java/io/prometheus/metrics/instrumentation/jvm/JvmBufferPoolMetrics.java b/prometheus-metrics-instrumentation-jvm/src/main/java/io/prometheus/metrics/instrumentation/jvm/JvmBufferPoolMetrics.java index ad047f1ba..8fc4b87e9 100644 --- a/prometheus-metrics-instrumentation-jvm/src/main/java/io/prometheus/metrics/instrumentation/jvm/JvmBufferPoolMetrics.java +++ b/prometheus-metrics-instrumentation-jvm/src/main/java/io/prometheus/metrics/instrumentation/jvm/JvmBufferPoolMetrics.java @@ -3,10 +3,12 @@ import io.prometheus.metrics.config.PrometheusProperties; import io.prometheus.metrics.core.metrics.GaugeWithCallback; import io.prometheus.metrics.model.registry.PrometheusRegistry; +import io.prometheus.metrics.model.snapshots.Labels; import io.prometheus.metrics.model.snapshots.Unit; import java.lang.management.BufferPoolMXBean; import java.lang.management.ManagementFactory; import java.util.List; +import javax.annotation.Nullable; /** * JVM Buffer Pool metrics. The {@link JvmBufferPoolMetrics} are registered as part of the {@link @@ -47,11 +49,13 @@ public class JvmBufferPoolMetrics { private final PrometheusProperties config; private final List bufferPoolBeans; + private final Labels constLabels; private JvmBufferPoolMetrics( - List bufferPoolBeans, PrometheusProperties config) { + List bufferPoolBeans, PrometheusProperties config, Labels constLabels) { this.config = config; this.bufferPoolBeans = bufferPoolBeans; + this.constLabels = constLabels; } private void register(PrometheusRegistry registry) { @@ -67,6 +71,7 @@ private void register(PrometheusRegistry registry) { callback.call(pool.getMemoryUsed(), pool.getName()); } }) + .constLabels(constLabels) .register(registry); GaugeWithCallback.builder(config) @@ -80,6 +85,7 @@ private void register(PrometheusRegistry registry) { callback.call(pool.getTotalCapacity(), pool.getName()); } }) + .constLabels(constLabels) .register(registry); GaugeWithCallback.builder(config) @@ -92,6 +98,7 @@ private void register(PrometheusRegistry registry) { callback.call(pool.getCount(), pool.getName()); } }) + .constLabels(constLabels) .register(registry); } @@ -106,12 +113,18 @@ public static Builder builder(PrometheusProperties config) { public static class Builder { private final PrometheusProperties config; - private List bufferPoolBeans; + @Nullable private List bufferPoolBeans; + private Labels constLabels = Labels.EMPTY; private Builder(PrometheusProperties config) { this.config = config; } + public Builder constLabels(Labels constLabels) { + this.constLabels = constLabels; + return this; + } + /** Package private. For testing only. */ Builder bufferPoolBeans(List bufferPoolBeans) { this.bufferPoolBeans = bufferPoolBeans; @@ -127,7 +140,7 @@ public void register(PrometheusRegistry registry) { if (bufferPoolBeans == null) { bufferPoolBeans = ManagementFactory.getPlatformMXBeans(BufferPoolMXBean.class); } - new JvmBufferPoolMetrics(bufferPoolBeans, config).register(registry); + new JvmBufferPoolMetrics(bufferPoolBeans, config, constLabels).register(registry); } } } diff --git a/prometheus-metrics-instrumentation-jvm/src/main/java/io/prometheus/metrics/instrumentation/jvm/JvmClassLoadingMetrics.java b/prometheus-metrics-instrumentation-jvm/src/main/java/io/prometheus/metrics/instrumentation/jvm/JvmClassLoadingMetrics.java index ebd0f296c..34e9dcb8a 100644 --- a/prometheus-metrics-instrumentation-jvm/src/main/java/io/prometheus/metrics/instrumentation/jvm/JvmClassLoadingMetrics.java +++ b/prometheus-metrics-instrumentation-jvm/src/main/java/io/prometheus/metrics/instrumentation/jvm/JvmClassLoadingMetrics.java @@ -4,8 +4,10 @@ import io.prometheus.metrics.core.metrics.CounterWithCallback; import io.prometheus.metrics.core.metrics.GaugeWithCallback; import io.prometheus.metrics.model.registry.PrometheusRegistry; +import io.prometheus.metrics.model.snapshots.Labels; import java.lang.management.ClassLoadingMXBean; import java.lang.management.ManagementFactory; +import javax.annotation.Nullable; /** * JVM Class Loading metrics. The {@link JvmClassLoadingMetrics} are registered as part of the @@ -43,10 +45,13 @@ public class JvmClassLoadingMetrics { private final PrometheusProperties config; private final ClassLoadingMXBean classLoadingBean; + private final Labels constLabels; - private JvmClassLoadingMetrics(ClassLoadingMXBean classLoadingBean, PrometheusProperties config) { + private JvmClassLoadingMetrics( + ClassLoadingMXBean classLoadingBean, PrometheusProperties config, Labels constLabels) { this.classLoadingBean = classLoadingBean; this.config = config; + this.constLabels = constLabels; } private void register(PrometheusRegistry registry) { @@ -55,6 +60,7 @@ private void register(PrometheusRegistry registry) { .name(JVM_CLASSES_CURRENTLY_LOADED) .help("The number of classes that are currently loaded in the JVM") .callback(callback -> callback.call(classLoadingBean.getLoadedClassCount())) + .constLabels(constLabels) .register(registry); CounterWithCallback.builder(config) @@ -62,6 +68,7 @@ private void register(PrometheusRegistry registry) { .help( "The total number of classes that have been loaded since the JVM has started execution") .callback(callback -> callback.call(classLoadingBean.getTotalLoadedClassCount())) + .constLabels(constLabels) .register(registry); CounterWithCallback.builder(config) @@ -70,6 +77,7 @@ private void register(PrometheusRegistry registry) { "The total number of classes that have been unloaded since the JVM has " + "started execution") .callback(callback -> callback.call(classLoadingBean.getUnloadedClassCount())) + .constLabels(constLabels) .register(registry); } @@ -84,12 +92,18 @@ public static Builder builder(PrometheusProperties config) { public static class Builder { private final PrometheusProperties config; - private ClassLoadingMXBean classLoadingBean; + @Nullable private ClassLoadingMXBean classLoadingBean; + private Labels constLabels = Labels.EMPTY; private Builder(PrometheusProperties config) { this.config = config; } + public Builder constLabels(Labels constLabels) { + this.constLabels = constLabels; + return this; + } + /** Package private. For testing only. */ Builder classLoadingBean(ClassLoadingMXBean classLoadingBean) { this.classLoadingBean = classLoadingBean; @@ -105,7 +119,7 @@ public void register(PrometheusRegistry registry) { this.classLoadingBean != null ? this.classLoadingBean : ManagementFactory.getClassLoadingMXBean(); - new JvmClassLoadingMetrics(classLoadingBean, config).register(registry); + new JvmClassLoadingMetrics(classLoadingBean, config, constLabels).register(registry); } } } diff --git a/prometheus-metrics-instrumentation-jvm/src/main/java/io/prometheus/metrics/instrumentation/jvm/JvmCompilationMetrics.java b/prometheus-metrics-instrumentation-jvm/src/main/java/io/prometheus/metrics/instrumentation/jvm/JvmCompilationMetrics.java index 308b00877..975b6c6a3 100644 --- a/prometheus-metrics-instrumentation-jvm/src/main/java/io/prometheus/metrics/instrumentation/jvm/JvmCompilationMetrics.java +++ b/prometheus-metrics-instrumentation-jvm/src/main/java/io/prometheus/metrics/instrumentation/jvm/JvmCompilationMetrics.java @@ -5,9 +5,11 @@ import io.prometheus.metrics.config.PrometheusProperties; import io.prometheus.metrics.core.metrics.CounterWithCallback; import io.prometheus.metrics.model.registry.PrometheusRegistry; +import io.prometheus.metrics.model.snapshots.Labels; import io.prometheus.metrics.model.snapshots.Unit; import java.lang.management.CompilationMXBean; import java.lang.management.ManagementFactory; +import javax.annotation.Nullable; /** * JVM Compilation metrics. The {@link JvmCompilationMetrics} are registered as part of the {@link @@ -38,10 +40,13 @@ public class JvmCompilationMetrics { private final PrometheusProperties config; private final CompilationMXBean compilationBean; + private final Labels constLabels; - private JvmCompilationMetrics(CompilationMXBean compilationBean, PrometheusProperties config) { + private JvmCompilationMetrics( + CompilationMXBean compilationBean, PrometheusProperties config, Labels constLabels) { this.compilationBean = compilationBean; this.config = config; + this.constLabels = constLabels; } private void register(PrometheusRegistry registry) { @@ -56,6 +61,7 @@ private void register(PrometheusRegistry registry) { .unit(Unit.SECONDS) .callback( callback -> callback.call(millisToSeconds(compilationBean.getTotalCompilationTime()))) + .constLabels(constLabels) .register(registry); } @@ -70,12 +76,18 @@ public static Builder builder(PrometheusProperties config) { public static class Builder { private final PrometheusProperties config; - private CompilationMXBean compilationBean; + @Nullable private CompilationMXBean compilationBean; + private Labels constLabels = Labels.EMPTY; private Builder(PrometheusProperties config) { this.config = config; } + public Builder constLabels(Labels constLabels) { + this.constLabels = constLabels; + return this; + } + /** Package private. For testing only. */ Builder compilationBean(CompilationMXBean compilationBean) { this.compilationBean = compilationBean; @@ -91,7 +103,7 @@ public void register(PrometheusRegistry registry) { this.compilationBean != null ? this.compilationBean : ManagementFactory.getCompilationMXBean(); - new JvmCompilationMetrics(compilationBean, config).register(registry); + new JvmCompilationMetrics(compilationBean, config, constLabels).register(registry); } } } diff --git a/prometheus-metrics-instrumentation-jvm/src/main/java/io/prometheus/metrics/instrumentation/jvm/JvmGarbageCollectorMetrics.java b/prometheus-metrics-instrumentation-jvm/src/main/java/io/prometheus/metrics/instrumentation/jvm/JvmGarbageCollectorMetrics.java index e8b9aaa70..a87b52a4f 100644 --- a/prometheus-metrics-instrumentation-jvm/src/main/java/io/prometheus/metrics/instrumentation/jvm/JvmGarbageCollectorMetrics.java +++ b/prometheus-metrics-instrumentation-jvm/src/main/java/io/prometheus/metrics/instrumentation/jvm/JvmGarbageCollectorMetrics.java @@ -3,11 +3,13 @@ import io.prometheus.metrics.config.PrometheusProperties; import io.prometheus.metrics.core.metrics.SummaryWithCallback; import io.prometheus.metrics.model.registry.PrometheusRegistry; +import io.prometheus.metrics.model.snapshots.Labels; import io.prometheus.metrics.model.snapshots.Quantiles; import io.prometheus.metrics.model.snapshots.Unit; import java.lang.management.GarbageCollectorMXBean; import java.lang.management.ManagementFactory; import java.util.List; +import javax.annotation.Nullable; /** * JVM Garbage Collector metrics. The {@link JvmGarbageCollectorMetrics} are registered as part of @@ -41,11 +43,15 @@ public class JvmGarbageCollectorMetrics { private final PrometheusProperties config; private final List garbageCollectorBeans; + private final Labels constLabels; private JvmGarbageCollectorMetrics( - List garbageCollectorBeans, PrometheusProperties config) { + List garbageCollectorBeans, + PrometheusProperties config, + Labels constLabels) { this.config = config; this.garbageCollectorBeans = garbageCollectorBeans; + this.constLabels = constLabels; } private void register(PrometheusRegistry registry) { @@ -65,6 +71,7 @@ private void register(PrometheusRegistry registry) { gc.getName()); } }) + .constLabels(constLabels) .register(registry); } @@ -79,12 +86,18 @@ public static Builder builder(PrometheusProperties config) { public static class Builder { private final PrometheusProperties config; - private List garbageCollectorBeans; + @Nullable private List garbageCollectorBeans; + private Labels constLabels = Labels.EMPTY; private Builder(PrometheusProperties config) { this.config = config; } + public Builder constLabels(Labels constLabels) { + this.constLabels = constLabels; + return this; + } + /** Package private. For testing only. */ Builder garbageCollectorBeans(List garbageCollectorBeans) { this.garbageCollectorBeans = garbageCollectorBeans; @@ -100,7 +113,7 @@ public void register(PrometheusRegistry registry) { if (garbageCollectorBeans == null) { garbageCollectorBeans = ManagementFactory.getGarbageCollectorMXBeans(); } - new JvmGarbageCollectorMetrics(garbageCollectorBeans, config).register(registry); + new JvmGarbageCollectorMetrics(garbageCollectorBeans, config, constLabels).register(registry); } } } diff --git a/prometheus-metrics-instrumentation-jvm/src/main/java/io/prometheus/metrics/instrumentation/jvm/JvmMemoryMetrics.java b/prometheus-metrics-instrumentation-jvm/src/main/java/io/prometheus/metrics/instrumentation/jvm/JvmMemoryMetrics.java index 0b6a4d993..1b34dba6c 100644 --- a/prometheus-metrics-instrumentation-jvm/src/main/java/io/prometheus/metrics/instrumentation/jvm/JvmMemoryMetrics.java +++ b/prometheus-metrics-instrumentation-jvm/src/main/java/io/prometheus/metrics/instrumentation/jvm/JvmMemoryMetrics.java @@ -3,6 +3,7 @@ import io.prometheus.metrics.config.PrometheusProperties; import io.prometheus.metrics.core.metrics.GaugeWithCallback; import io.prometheus.metrics.model.registry.PrometheusRegistry; +import io.prometheus.metrics.model.snapshots.Labels; import io.prometheus.metrics.model.snapshots.Unit; import java.lang.management.ManagementFactory; import java.lang.management.MemoryMXBean; @@ -11,6 +12,7 @@ import java.util.List; import java.util.function.Consumer; import java.util.function.Function; +import javax.annotation.Nullable; /** * JVM memory metrics. The {@link JvmMemoryMetrics} are registered as part of the {@link JvmMetrics} @@ -126,12 +128,17 @@ public class JvmMemoryMetrics { private final PrometheusProperties config; private final MemoryMXBean memoryBean; private final List poolBeans; + private final Labels constLabels; private JvmMemoryMetrics( - List poolBeans, MemoryMXBean memoryBean, PrometheusProperties config) { + List poolBeans, + MemoryMXBean memoryBean, + PrometheusProperties config, + Labels constLabels) { this.config = config; this.poolBeans = poolBeans; this.memoryBean = memoryBean; + this.constLabels = constLabels; } private void register(PrometheusRegistry registry) { @@ -140,6 +147,7 @@ private void register(PrometheusRegistry registry) { .name(JVM_MEMORY_OBJECTS_PENDING_FINALIZATION) .help("The number of objects waiting in the finalizer queue.") .callback(callback -> callback.call(memoryBean.getObjectPendingFinalizationCount())) + .constLabels(constLabels) .register(registry); GaugeWithCallback.builder(config) @@ -152,6 +160,7 @@ private void register(PrometheusRegistry registry) { callback.call(memoryBean.getHeapMemoryUsage().getUsed(), "heap"); callback.call(memoryBean.getNonHeapMemoryUsage().getUsed(), "nonheap"); }) + .constLabels(constLabels) .register(registry); GaugeWithCallback.builder(config) @@ -164,6 +173,7 @@ private void register(PrometheusRegistry registry) { callback.call(memoryBean.getHeapMemoryUsage().getCommitted(), "heap"); callback.call(memoryBean.getNonHeapMemoryUsage().getCommitted(), "nonheap"); }) + .constLabels(constLabels) .register(registry); GaugeWithCallback.builder(config) @@ -176,6 +186,7 @@ private void register(PrometheusRegistry registry) { callback.call(memoryBean.getHeapMemoryUsage().getMax(), "heap"); callback.call(memoryBean.getNonHeapMemoryUsage().getMax(), "nonheap"); }) + .constLabels(constLabels) .register(registry); GaugeWithCallback.builder(config) @@ -188,6 +199,7 @@ private void register(PrometheusRegistry registry) { callback.call(memoryBean.getHeapMemoryUsage().getInit(), "heap"); callback.call(memoryBean.getNonHeapMemoryUsage().getInit(), "nonheap"); }) + .constLabels(constLabels) .register(registry); GaugeWithCallback.builder(config) @@ -196,6 +208,7 @@ private void register(PrometheusRegistry registry) { .unit(Unit.BYTES) .labelNames("pool") .callback(makeCallback(poolBeans, MemoryPoolMXBean::getUsage, MemoryUsage::getUsed)) + .constLabels(constLabels) .register(registry); GaugeWithCallback.builder(config) @@ -204,6 +217,7 @@ private void register(PrometheusRegistry registry) { .unit(Unit.BYTES) .labelNames("pool") .callback(makeCallback(poolBeans, MemoryPoolMXBean::getUsage, MemoryUsage::getCommitted)) + .constLabels(constLabels) .register(registry); GaugeWithCallback.builder(config) @@ -212,6 +226,7 @@ private void register(PrometheusRegistry registry) { .unit(Unit.BYTES) .labelNames("pool") .callback(makeCallback(poolBeans, MemoryPoolMXBean::getUsage, MemoryUsage::getMax)) + .constLabels(constLabels) .register(registry); GaugeWithCallback.builder(config) @@ -220,6 +235,7 @@ private void register(PrometheusRegistry registry) { .unit(Unit.BYTES) .labelNames("pool") .callback(makeCallback(poolBeans, MemoryPoolMXBean::getUsage, MemoryUsage::getInit)) + .constLabels(constLabels) .register(registry); GaugeWithCallback.builder(config) @@ -229,6 +245,7 @@ private void register(PrometheusRegistry registry) { .labelNames("pool") .callback( makeCallback(poolBeans, MemoryPoolMXBean::getCollectionUsage, MemoryUsage::getUsed)) + .constLabels(constLabels) .register(registry); GaugeWithCallback.builder(config) @@ -239,6 +256,7 @@ private void register(PrometheusRegistry registry) { .callback( makeCallback( poolBeans, MemoryPoolMXBean::getCollectionUsage, MemoryUsage::getCommitted)) + .constLabels(constLabels) .register(registry); GaugeWithCallback.builder(config) @@ -248,6 +266,7 @@ private void register(PrometheusRegistry registry) { .labelNames("pool") .callback( makeCallback(poolBeans, MemoryPoolMXBean::getCollectionUsage, MemoryUsage::getMax)) + .constLabels(constLabels) .register(registry); GaugeWithCallback.builder(config) @@ -257,6 +276,7 @@ private void register(PrometheusRegistry registry) { .labelNames("pool") .callback( makeCallback(poolBeans, MemoryPoolMXBean::getCollectionUsage, MemoryUsage::getInit)) + .constLabels(constLabels) .register(registry); } @@ -285,13 +305,19 @@ public static Builder builder(PrometheusProperties config) { public static class Builder { private final PrometheusProperties config; - private MemoryMXBean memoryBean; - private List poolBeans; + @Nullable private MemoryMXBean memoryBean; + @Nullable private List poolBeans; + private Labels constLabels = Labels.EMPTY; private Builder(PrometheusProperties config) { this.config = config; } + public Builder constLabels(Labels constLabels) { + this.constLabels = constLabels; + return this; + } + /** Package private. For testing only. */ Builder withMemoryBean(MemoryMXBean memoryBean) { this.memoryBean = memoryBean; @@ -313,7 +339,7 @@ public void register(PrometheusRegistry registry) { this.memoryBean != null ? this.memoryBean : ManagementFactory.getMemoryMXBean(); List poolBeans = this.poolBeans != null ? this.poolBeans : ManagementFactory.getMemoryPoolMXBeans(); - new JvmMemoryMetrics(poolBeans, bean, config).register(registry); + new JvmMemoryMetrics(poolBeans, bean, config, constLabels).register(registry); } } } diff --git a/prometheus-metrics-instrumentation-jvm/src/main/java/io/prometheus/metrics/instrumentation/jvm/JvmMemoryPoolAllocationMetrics.java b/prometheus-metrics-instrumentation-jvm/src/main/java/io/prometheus/metrics/instrumentation/jvm/JvmMemoryPoolAllocationMetrics.java index a5e7768c8..5dfb4199b 100644 --- a/prometheus-metrics-instrumentation-jvm/src/main/java/io/prometheus/metrics/instrumentation/jvm/JvmMemoryPoolAllocationMetrics.java +++ b/prometheus-metrics-instrumentation-jvm/src/main/java/io/prometheus/metrics/instrumentation/jvm/JvmMemoryPoolAllocationMetrics.java @@ -5,12 +5,15 @@ import io.prometheus.metrics.config.PrometheusProperties; import io.prometheus.metrics.core.metrics.Counter; import io.prometheus.metrics.model.registry.PrometheusRegistry; +import io.prometheus.metrics.model.snapshots.Labels; import java.lang.management.GarbageCollectorMXBean; import java.lang.management.ManagementFactory; import java.lang.management.MemoryUsage; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Objects; +import javax.annotation.Nullable; import javax.management.Notification; import javax.management.NotificationEmitter; import javax.management.NotificationListener; @@ -50,9 +53,12 @@ public class JvmMemoryPoolAllocationMetrics { "jvm_memory_pool_allocated_bytes_total"; private final List garbageCollectorBeans; + private final Labels constLabels; - private JvmMemoryPoolAllocationMetrics(List garbageCollectorBeans) { + private JvmMemoryPoolAllocationMetrics( + List garbageCollectorBeans, Labels constLabels) { this.garbageCollectorBeans = garbageCollectorBeans; + this.constLabels = constLabels; } private void register(PrometheusRegistry registry) { @@ -63,6 +69,7 @@ private void register(PrometheusRegistry registry) { "Total bytes allocated in a given JVM memory pool. Only updated after GC, " + "not continuously.") .labelNames("pool") + .constLabels(constLabels) .register(registry); AllocationCountingNotificationListener listener = @@ -93,7 +100,7 @@ public synchronized void handleNotification(Notification notification, Object ha for (Map.Entry entry : memoryUsageBeforeGc.entrySet()) { String memoryPool = entry.getKey(); long before = entry.getValue().getUsed(); - long after = memoryUsageAfterGc.get(memoryPool).getUsed(); + long after = Objects.requireNonNull(memoryUsageAfterGc.get(memoryPool)).getUsed(); handleMemoryPool(memoryPool, before, after); } } @@ -149,10 +156,16 @@ public static Builder builder(PrometheusProperties config) { } public static class Builder { - private List garbageCollectorBeans; + @Nullable private List garbageCollectorBeans; + private Labels constLabels = Labels.EMPTY; private Builder() {} + public Builder constLabels(Labels constLabels) { + this.constLabels = constLabels; + return this; + } + /** Package private. For testing only. */ Builder withGarbageCollectorBeans(List garbageCollectorBeans) { this.garbageCollectorBeans = garbageCollectorBeans; @@ -168,7 +181,7 @@ public void register(PrometheusRegistry registry) { if (garbageCollectorBeans == null) { garbageCollectorBeans = ManagementFactory.getGarbageCollectorMXBeans(); } - new JvmMemoryPoolAllocationMetrics(garbageCollectorBeans).register(registry); + new JvmMemoryPoolAllocationMetrics(garbageCollectorBeans, constLabels).register(registry); } } } diff --git a/prometheus-metrics-instrumentation-jvm/src/main/java/io/prometheus/metrics/instrumentation/jvm/JvmMetrics.java b/prometheus-metrics-instrumentation-jvm/src/main/java/io/prometheus/metrics/instrumentation/jvm/JvmMetrics.java index 0f5a56eee..b0abd86b1 100644 --- a/prometheus-metrics-instrumentation-jvm/src/main/java/io/prometheus/metrics/instrumentation/jvm/JvmMetrics.java +++ b/prometheus-metrics-instrumentation-jvm/src/main/java/io/prometheus/metrics/instrumentation/jvm/JvmMetrics.java @@ -2,6 +2,7 @@ import io.prometheus.metrics.config.PrometheusProperties; import io.prometheus.metrics.model.registry.PrometheusRegistry; +import io.prometheus.metrics.model.snapshots.Labels; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; @@ -32,11 +33,18 @@ public static Builder builder(PrometheusProperties config) { public static class Builder { private final PrometheusProperties config; + private Labels constLabels = Labels.EMPTY; private Builder(PrometheusProperties config) { this.config = config; } + /** Set constant labels that will be applied to all JVM metrics registered by this builder. */ + public Builder constLabels(Labels constLabels) { + this.constLabels = constLabels; + return this; + } + /** * Register all JVM metrics with the default registry. * @@ -55,16 +63,16 @@ public void register() { */ public void register(PrometheusRegistry registry) { if (REGISTERED.add(registry)) { - JvmThreadsMetrics.builder(config).register(registry); - JvmBufferPoolMetrics.builder(config).register(registry); - JvmClassLoadingMetrics.builder(config).register(registry); - JvmCompilationMetrics.builder(config).register(registry); - JvmGarbageCollectorMetrics.builder(config).register(registry); - JvmMemoryPoolAllocationMetrics.builder(config).register(registry); - JvmMemoryMetrics.builder(config).register(registry); - JvmNativeMemoryMetrics.builder(config).register(registry); - JvmRuntimeInfoMetric.builder(config).register(registry); - ProcessMetrics.builder(config).register(registry); + JvmThreadsMetrics.builder(config).constLabels(constLabels).register(registry); + JvmBufferPoolMetrics.builder(config).constLabels(constLabels).register(registry); + JvmClassLoadingMetrics.builder(config).constLabels(constLabels).register(registry); + JvmCompilationMetrics.builder(config).constLabels(constLabels).register(registry); + JvmGarbageCollectorMetrics.builder(config).constLabels(constLabels).register(registry); + JvmMemoryPoolAllocationMetrics.builder(config).constLabels(constLabels).register(registry); + JvmMemoryMetrics.builder(config).constLabels(constLabels).register(registry); + JvmNativeMemoryMetrics.builder(config).constLabels(constLabels).register(registry); + JvmRuntimeInfoMetric.builder(config).constLabels(constLabels).register(registry); + ProcessMetrics.builder(config).constLabels(constLabels).register(registry); } } } diff --git a/prometheus-metrics-instrumentation-jvm/src/main/java/io/prometheus/metrics/instrumentation/jvm/JvmNativeMemoryMetrics.java b/prometheus-metrics-instrumentation-jvm/src/main/java/io/prometheus/metrics/instrumentation/jvm/JvmNativeMemoryMetrics.java index 53225edca..6b11df38a 100644 --- a/prometheus-metrics-instrumentation-jvm/src/main/java/io/prometheus/metrics/instrumentation/jvm/JvmNativeMemoryMetrics.java +++ b/prometheus-metrics-instrumentation-jvm/src/main/java/io/prometheus/metrics/instrumentation/jvm/JvmNativeMemoryMetrics.java @@ -3,6 +3,7 @@ import io.prometheus.metrics.config.PrometheusProperties; import io.prometheus.metrics.core.metrics.GaugeWithCallback; import io.prometheus.metrics.model.registry.PrometheusRegistry; +import io.prometheus.metrics.model.snapshots.Labels; import io.prometheus.metrics.model.snapshots.Unit; import java.lang.management.ManagementFactory; import java.util.concurrent.atomic.AtomicBoolean; @@ -97,10 +98,13 @@ public class JvmNativeMemoryMetrics { private final PrometheusProperties config; private final PlatformMBeanServerAdapter adapter; + private final Labels constLabels; - private JvmNativeMemoryMetrics(PrometheusProperties config, PlatformMBeanServerAdapter adapter) { + private JvmNativeMemoryMetrics( + PrometheusProperties config, PlatformMBeanServerAdapter adapter, Labels constLabels) { this.config = config; this.adapter = adapter; + this.constLabels = constLabels; } private void register(PrometheusRegistry registry) { @@ -115,6 +119,7 @@ private void register(PrometheusRegistry registry) { .unit(Unit.BYTES) .labelNames("pool") .callback(makeCallback(true)) + .constLabels(constLabels) .register(registry); GaugeWithCallback.builder(config) @@ -125,6 +130,7 @@ private void register(PrometheusRegistry registry) { .unit(Unit.BYTES) .labelNames("pool") .callback(makeCallback(false)) + .constLabels(constLabels) .register(registry); } } @@ -157,8 +163,8 @@ private String vmNativeMemorySummaryInBytesOrEmpty() { } else { return summary; } - } catch (Exception ex) { - // ignore errors + } catch (RuntimeException ex) { + // ignore errors (native memory tracking not enabled or other runtime failures) isEnabled.set(false); return ""; } @@ -200,6 +206,7 @@ public static class Builder { private final PrometheusProperties config; private final PlatformMBeanServerAdapter adapter; + private Labels constLabels = Labels.EMPTY; private Builder(PrometheusProperties config) { this(config, new DefaultPlatformMBeanServerAdapter()); @@ -211,12 +218,17 @@ private Builder(PrometheusProperties config) { this.adapter = adapter; } + public Builder constLabels(Labels constLabels) { + this.constLabels = constLabels; + return this; + } + public void register() { register(PrometheusRegistry.defaultRegistry); } public void register(PrometheusRegistry registry) { - new JvmNativeMemoryMetrics(config, adapter).register(registry); + new JvmNativeMemoryMetrics(config, adapter, constLabels).register(registry); } } } diff --git a/prometheus-metrics-instrumentation-jvm/src/main/java/io/prometheus/metrics/instrumentation/jvm/JvmRuntimeInfoMetric.java b/prometheus-metrics-instrumentation-jvm/src/main/java/io/prometheus/metrics/instrumentation/jvm/JvmRuntimeInfoMetric.java index 333395425..58363780f 100644 --- a/prometheus-metrics-instrumentation-jvm/src/main/java/io/prometheus/metrics/instrumentation/jvm/JvmRuntimeInfoMetric.java +++ b/prometheus-metrics-instrumentation-jvm/src/main/java/io/prometheus/metrics/instrumentation/jvm/JvmRuntimeInfoMetric.java @@ -3,6 +3,8 @@ import io.prometheus.metrics.config.PrometheusProperties; import io.prometheus.metrics.core.metrics.Info; import io.prometheus.metrics.model.registry.PrometheusRegistry; +import io.prometheus.metrics.model.snapshots.Labels; +import javax.annotation.Nullable; /** * JVM Runtime Info metric. The {@link JvmRuntimeInfoMetric} is registered as part of the {@link @@ -32,13 +34,19 @@ public class JvmRuntimeInfoMetric { private final String version; private final String vendor; private final String runtime; + private final Labels constLabels; private JvmRuntimeInfoMetric( - String version, String vendor, String runtime, PrometheusProperties config) { + String version, + String vendor, + String runtime, + PrometheusProperties config, + Labels constLabels) { this.config = config; this.version = version; this.vendor = vendor; this.runtime = runtime; + this.constLabels = constLabels; } private void register(PrometheusRegistry registry) { @@ -48,6 +56,7 @@ private void register(PrometheusRegistry registry) { .name(JVM_RUNTIME_INFO) .help("JVM runtime info") .labelNames("version", "vendor", "runtime") + .constLabels(constLabels) .register(registry); jvmInfo.setLabelValues(version, vendor, runtime); @@ -64,14 +73,20 @@ public static Builder builder(PrometheusProperties config) { public static class Builder { private final PrometheusProperties config; - private String version; - private String vendor; - private String runtime; + @Nullable private String version; + @Nullable private String vendor; + @Nullable private String runtime; + private Labels constLabels = Labels.EMPTY; private Builder(PrometheusProperties config) { this.config = config; } + public Builder constLabels(Labels constLabels) { + this.constLabels = constLabels; + return this; + } + /** Package private. For testing only. */ Builder version(String version) { this.version = version; @@ -103,7 +118,7 @@ public void register(PrometheusRegistry registry) { this.vendor != null ? this.vendor : System.getProperty("java.vm.vendor", "unknown"); String runtime = this.runtime != null ? this.runtime : System.getProperty("java.runtime.name", "unknown"); - new JvmRuntimeInfoMetric(version, vendor, runtime, config).register(registry); + new JvmRuntimeInfoMetric(version, vendor, runtime, config, constLabels).register(registry); } } } diff --git a/prometheus-metrics-instrumentation-jvm/src/main/java/io/prometheus/metrics/instrumentation/jvm/JvmThreadsMetrics.java b/prometheus-metrics-instrumentation-jvm/src/main/java/io/prometheus/metrics/instrumentation/jvm/JvmThreadsMetrics.java index d3d140250..0e98e29d3 100644 --- a/prometheus-metrics-instrumentation-jvm/src/main/java/io/prometheus/metrics/instrumentation/jvm/JvmThreadsMetrics.java +++ b/prometheus-metrics-instrumentation-jvm/src/main/java/io/prometheus/metrics/instrumentation/jvm/JvmThreadsMetrics.java @@ -1,15 +1,19 @@ package io.prometheus.metrics.instrumentation.jvm; +import static java.util.Objects.requireNonNull; + import io.prometheus.metrics.config.PrometheusProperties; import io.prometheus.metrics.core.metrics.CounterWithCallback; import io.prometheus.metrics.core.metrics.GaugeWithCallback; import io.prometheus.metrics.model.registry.PrometheusRegistry; +import io.prometheus.metrics.model.snapshots.Labels; import java.lang.management.ManagementFactory; import java.lang.management.ThreadInfo; import java.lang.management.ThreadMXBean; import java.util.Arrays; import java.util.HashMap; import java.util.Map; +import javax.annotation.Nullable; /** * JVM Thread metrics. The {@link JvmThreadsMetrics} are registered as part of the {@link @@ -71,12 +75,17 @@ public class JvmThreadsMetrics { private final PrometheusProperties config; private final ThreadMXBean threadBean; private final boolean isNativeImage; + private final Labels constLabels; private JvmThreadsMetrics( - boolean isNativeImage, ThreadMXBean threadBean, PrometheusProperties config) { + boolean isNativeImage, + ThreadMXBean threadBean, + PrometheusProperties config, + Labels constLabels) { this.config = config; this.threadBean = threadBean; this.isNativeImage = isNativeImage; + this.constLabels = constLabels; } private void register(PrometheusRegistry registry) { @@ -85,24 +94,28 @@ private void register(PrometheusRegistry registry) { .name(JVM_THREADS_CURRENT) .help("Current thread count of a JVM") .callback(callback -> callback.call(threadBean.getThreadCount())) + .constLabels(constLabels) .register(registry); GaugeWithCallback.builder(config) .name(JVM_THREADS_DAEMON) .help("Daemon thread count of a JVM") .callback(callback -> callback.call(threadBean.getDaemonThreadCount())) + .constLabels(constLabels) .register(registry); GaugeWithCallback.builder(config) .name(JVM_THREADS_PEAK) .help("Peak thread count of a JVM") .callback(callback -> callback.call(threadBean.getPeakThreadCount())) + .constLabels(constLabels) .register(registry); CounterWithCallback.builder(config) .name(JVM_THREADS_STARTED_TOTAL) .help("Started thread count of a JVM") .callback(callback -> callback.call(threadBean.getTotalStartedThreadCount())) + .constLabels(constLabels) .register(registry); if (!isNativeImage) { @@ -113,6 +126,7 @@ private void register(PrometheusRegistry registry) { + "ownable synchronizers") .callback( callback -> callback.call(nullSafeArrayLength(threadBean.findDeadlockedThreads()))) + .constLabels(constLabels) .register(registry); GaugeWithCallback.builder(config) @@ -121,6 +135,7 @@ private void register(PrometheusRegistry registry) { .callback( callback -> callback.call(nullSafeArrayLength(threadBean.findMonitorDeadlockedThreads()))) + .constLabels(constLabels) .register(registry); GaugeWithCallback.builder(config) @@ -134,6 +149,7 @@ private void register(PrometheusRegistry registry) { callback.call(entry.getValue(), entry.getKey()); } }) + .constLabels(constLabels) .register(registry); } } @@ -165,7 +181,8 @@ private Map getThreadStateCountMap(ThreadMXBean threadBean) { for (ThreadInfo curThread : allThreads) { if (curThread != null) { Thread.State threadState = curThread.getThreadState(); - threadCounts.put(threadState.name(), threadCounts.get(threadState.name()) + 1); + threadCounts.put( + threadState.name(), requireNonNull(threadCounts.get(threadState.name())) + 1); } } @@ -190,13 +207,19 @@ public static Builder builder(PrometheusProperties config) { public static class Builder { private final PrometheusProperties config; - private Boolean isNativeImage; - private ThreadMXBean threadBean; + @Nullable private Boolean isNativeImage; + @Nullable private ThreadMXBean threadBean; + private Labels constLabels = Labels.EMPTY; private Builder(PrometheusProperties config) { this.config = config; } + public Builder constLabels(Labels constLabels) { + this.constLabels = constLabels; + return this; + } + /** Package private. For testing only. */ Builder threadBean(ThreadMXBean threadBean) { this.threadBean = threadBean; @@ -218,7 +241,7 @@ public void register(PrometheusRegistry registry) { this.threadBean != null ? this.threadBean : ManagementFactory.getThreadMXBean(); boolean isNativeImage = this.isNativeImage != null ? this.isNativeImage : NativeImageChecker.isGraalVmNativeImage; - new JvmThreadsMetrics(isNativeImage, threadBean, config).register(registry); + new JvmThreadsMetrics(isNativeImage, threadBean, config, constLabels).register(registry); } } } diff --git a/prometheus-metrics-instrumentation-jvm/src/main/java/io/prometheus/metrics/instrumentation/jvm/ProcessMetrics.java b/prometheus-metrics-instrumentation-jvm/src/main/java/io/prometheus/metrics/instrumentation/jvm/ProcessMetrics.java index e4dabd275..b341f848a 100644 --- a/prometheus-metrics-instrumentation-jvm/src/main/java/io/prometheus/metrics/instrumentation/jvm/ProcessMetrics.java +++ b/prometheus-metrics-instrumentation-jvm/src/main/java/io/prometheus/metrics/instrumentation/jvm/ProcessMetrics.java @@ -4,6 +4,7 @@ import io.prometheus.metrics.core.metrics.CounterWithCallback; import io.prometheus.metrics.core.metrics.GaugeWithCallback; import io.prometheus.metrics.model.registry.PrometheusRegistry; +import io.prometheus.metrics.model.snapshots.Labels; import io.prometheus.metrics.model.snapshots.Unit; import java.io.BufferedReader; import java.io.File; @@ -16,6 +17,7 @@ import java.lang.reflect.Method; import java.nio.charset.StandardCharsets; import java.nio.file.Files; +import javax.annotation.Nullable; /** * Process metrics. @@ -81,17 +83,20 @@ public class ProcessMetrics { private final RuntimeMXBean runtimeBean; private final Grepper grepper; private final boolean linux; + private final Labels constLabels; private ProcessMetrics( OperatingSystemMXBean osBean, RuntimeMXBean runtimeBean, Grepper grepper, - PrometheusProperties config) { + PrometheusProperties config, + Labels constLabels) { this.osBean = osBean; this.runtimeBean = runtimeBean; this.grepper = grepper; this.config = config; this.linux = PROC_SELF_STATUS.canRead(); + this.constLabels = constLabels; } private void register(PrometheusRegistry registry) { @@ -117,6 +122,7 @@ private void register(PrometheusRegistry registry) { // Ignored } }) + .constLabels(constLabels) .register(registry); GaugeWithCallback.builder(config) @@ -124,6 +130,7 @@ private void register(PrometheusRegistry registry) { .help("Start time of the process since unix epoch in seconds.") .unit(Unit.SECONDS) .callback(callback -> callback.call(Unit.millisToSeconds(runtimeBean.getStartTime()))) + .constLabels(constLabels) .register(registry); GaugeWithCallback.builder(config) @@ -140,6 +147,7 @@ private void register(PrometheusRegistry registry) { // Ignored } }) + .constLabels(constLabels) .register(registry); GaugeWithCallback.builder(config) @@ -156,6 +164,7 @@ private void register(PrometheusRegistry registry) { // Ignored } }) + .constLabels(constLabels) .register(registry); if (linux) { @@ -173,6 +182,7 @@ private void register(PrometheusRegistry registry) { // Ignored } }) + .constLabels(constLabels) .register(registry); GaugeWithCallback.builder(config) @@ -188,10 +198,12 @@ private void register(PrometheusRegistry registry) { // Ignored } }) + .constLabels(constLabels) .register(registry); } } + @Nullable private Long callLongGetter(String getterName, Object obj) throws NoSuchMethodException, InvocationTargetException { return callLongGetter(obj.getClass().getMethod(getterName), obj); @@ -211,6 +223,7 @@ private Long callLongGetter(String getterName, Object obj) * assumption doesn't hold, the method might be called repeatedly and the returned value will be * the one produced by the last call. */ + @Nullable private Long callLongGetter(Method method, Object obj) throws InvocationTargetException { try { return (Long) method.invoke(obj); @@ -253,7 +266,8 @@ public String lineStartingWith(File file, String prefix) throws IOException { line = reader.readLine(); } } - return null; + throw new IllegalStateException( + String.format("File %s does not contain a line starting with '%s'.", file, prefix)); } } @@ -268,9 +282,10 @@ public static Builder builder(PrometheusProperties config) { public static class Builder { private final PrometheusProperties config; - private OperatingSystemMXBean osBean; - private RuntimeMXBean runtimeBean; - private Grepper grepper; + @Nullable private OperatingSystemMXBean osBean; + @Nullable private RuntimeMXBean runtimeBean; + @Nullable private Grepper grepper; + private Labels constLabels = Labels.EMPTY; private Builder(PrometheusProperties config) { this.config = config; @@ -294,6 +309,11 @@ Builder grepper(Grepper grepper) { return this; } + public Builder constLabels(Labels constLabels) { + this.constLabels = constLabels; + return this; + } + public void register() { register(PrometheusRegistry.defaultRegistry); } @@ -304,7 +324,7 @@ public void register(PrometheusRegistry registry) { RuntimeMXBean bean = this.runtimeBean != null ? this.runtimeBean : ManagementFactory.getRuntimeMXBean(); Grepper grepper = this.grepper != null ? this.grepper : new FileGrepper(); - new ProcessMetrics(osBean, bean, grepper, config).register(registry); + new ProcessMetrics(osBean, bean, grepper, config, constLabels).register(registry); } } } diff --git a/prometheus-metrics-instrumentation-jvm/src/test/java/io/prometheus/metrics/instrumentation/jvm/JvmBufferPoolMetricsTest.java b/prometheus-metrics-instrumentation-jvm/src/test/java/io/prometheus/metrics/instrumentation/jvm/JvmBufferPoolMetricsTest.java index e3bf55fb4..5b320c678 100644 --- a/prometheus-metrics-instrumentation-jvm/src/test/java/io/prometheus/metrics/instrumentation/jvm/JvmBufferPoolMetricsTest.java +++ b/prometheus-metrics-instrumentation-jvm/src/test/java/io/prometheus/metrics/instrumentation/jvm/JvmBufferPoolMetricsTest.java @@ -22,7 +22,7 @@ class JvmBufferPoolMetricsTest { private final BufferPoolMXBean mappedBuffer = Mockito.mock(BufferPoolMXBean.class); @BeforeEach - public void setUp() { + void setUp() { when(directBuffer.getName()).thenReturn("direct"); when(directBuffer.getCount()).thenReturn(2L); when(directBuffer.getMemoryUsed()).thenReturn(1234L); @@ -34,7 +34,7 @@ public void setUp() { } @Test - public void testGoodCase() throws IOException { + void testGoodCase() throws IOException { PrometheusRegistry registry = new PrometheusRegistry(); JvmBufferPoolMetrics.builder() .bufferPoolBeans(Arrays.asList(mappedBuffer, directBuffer)) @@ -64,7 +64,7 @@ public void testGoodCase() throws IOException { } @Test - public void testIgnoredMetricNotScraped() { + void testIgnoredMetricNotScraped() { MetricNameFilter filter = MetricNameFilter.builder().nameMustNotBeEqualTo("jvm_buffer_pool_used_bytes").build(); diff --git a/prometheus-metrics-instrumentation-jvm/src/test/java/io/prometheus/metrics/instrumentation/jvm/JvmClassLoadingMetricsTest.java b/prometheus-metrics-instrumentation-jvm/src/test/java/io/prometheus/metrics/instrumentation/jvm/JvmClassLoadingMetricsTest.java index 6c32e0ee8..7fe86c753 100644 --- a/prometheus-metrics-instrumentation-jvm/src/test/java/io/prometheus/metrics/instrumentation/jvm/JvmClassLoadingMetricsTest.java +++ b/prometheus-metrics-instrumentation-jvm/src/test/java/io/prometheus/metrics/instrumentation/jvm/JvmClassLoadingMetricsTest.java @@ -20,20 +20,20 @@ class JvmClassLoadingMetricsTest { private final ClassLoadingMXBean mockClassLoadingBean = Mockito.mock(ClassLoadingMXBean.class); @BeforeEach - public void setUp() { + void setUp() { when(mockClassLoadingBean.getLoadedClassCount()).thenReturn(1000); when(mockClassLoadingBean.getTotalLoadedClassCount()).thenReturn(2000L); when(mockClassLoadingBean.getUnloadedClassCount()).thenReturn(500L); } @Test - public void testGoodCase() throws IOException { + void testGoodCase() throws IOException { PrometheusRegistry registry = new PrometheusRegistry(); JvmClassLoadingMetrics.builder().classLoadingBean(mockClassLoadingBean).register(registry); MetricSnapshots snapshots = registry.scrape(); String expected = - """ +""" # TYPE jvm_classes_currently_loaded gauge # HELP jvm_classes_currently_loaded The number of classes that are currently loaded in the JVM jvm_classes_currently_loaded 1000.0 @@ -50,7 +50,7 @@ public void testGoodCase() throws IOException { } @Test - public void testIgnoredMetricNotScraped() { + void testIgnoredMetricNotScraped() { MetricNameFilter filter = MetricNameFilter.builder().nameMustNotBeEqualTo("jvm_classes_currently_loaded").build(); diff --git a/prometheus-metrics-instrumentation-jvm/src/test/java/io/prometheus/metrics/instrumentation/jvm/JvmCompilationMetricsTest.java b/prometheus-metrics-instrumentation-jvm/src/test/java/io/prometheus/metrics/instrumentation/jvm/JvmCompilationMetricsTest.java index 5b8abab1b..e961d9394 100644 --- a/prometheus-metrics-instrumentation-jvm/src/test/java/io/prometheus/metrics/instrumentation/jvm/JvmCompilationMetricsTest.java +++ b/prometheus-metrics-instrumentation-jvm/src/test/java/io/prometheus/metrics/instrumentation/jvm/JvmCompilationMetricsTest.java @@ -20,19 +20,19 @@ class JvmCompilationMetricsTest { private final CompilationMXBean mockCompilationBean = Mockito.mock(CompilationMXBean.class); @BeforeEach - public void setUp() { + void setUp() { when(mockCompilationBean.getTotalCompilationTime()).thenReturn(10000L); when(mockCompilationBean.isCompilationTimeMonitoringSupported()).thenReturn(true); } @Test - public void testGoodCase() throws IOException { + void testGoodCase() throws IOException { PrometheusRegistry registry = new PrometheusRegistry(); JvmCompilationMetrics.builder().compilationBean(mockCompilationBean).register(registry); MetricSnapshots snapshots = registry.scrape(); String expected = - """ +""" # TYPE jvm_compilation_time_seconds counter # UNIT jvm_compilation_time_seconds seconds # HELP jvm_compilation_time_seconds The total time in seconds taken for HotSpot class compilation @@ -44,7 +44,7 @@ public void testGoodCase() throws IOException { } @Test - public void testIgnoredMetricNotScraped() { + void testIgnoredMetricNotScraped() { MetricNameFilter filter = MetricNameFilter.builder() .nameMustNotBeEqualTo("jvm_compilation_time_seconds_total") diff --git a/prometheus-metrics-instrumentation-jvm/src/test/java/io/prometheus/metrics/instrumentation/jvm/JvmGarbageCollectorMetricsTest.java b/prometheus-metrics-instrumentation-jvm/src/test/java/io/prometheus/metrics/instrumentation/jvm/JvmGarbageCollectorMetricsTest.java index 177f29d2e..0f928ef34 100644 --- a/prometheus-metrics-instrumentation-jvm/src/test/java/io/prometheus/metrics/instrumentation/jvm/JvmGarbageCollectorMetricsTest.java +++ b/prometheus-metrics-instrumentation-jvm/src/test/java/io/prometheus/metrics/instrumentation/jvm/JvmGarbageCollectorMetricsTest.java @@ -23,7 +23,7 @@ class JvmGarbageCollectorMetricsTest { private final GarbageCollectorMXBean mockGcBean2 = Mockito.mock(GarbageCollectorMXBean.class); @BeforeEach - public void setUp() { + void setUp() { when(mockGcBean1.getName()).thenReturn("MyGC1"); when(mockGcBean1.getCollectionCount()).thenReturn(100L); when(mockGcBean1.getCollectionTime()).thenReturn(TimeUnit.SECONDS.toMillis(10)); @@ -33,7 +33,7 @@ public void setUp() { } @Test - public void testGoodCase() throws IOException { + void testGoodCase() throws IOException { PrometheusRegistry registry = new PrometheusRegistry(); JvmGarbageCollectorMetrics.builder() .garbageCollectorBeans(Arrays.asList(mockGcBean1, mockGcBean2)) @@ -56,7 +56,7 @@ public void testGoodCase() throws IOException { } @Test - public void testIgnoredMetricNotScraped() { + void testIgnoredMetricNotScraped() { MetricNameFilter filter = MetricNameFilter.builder().nameMustNotBeEqualTo("jvm_gc_collection_seconds").build(); diff --git a/prometheus-metrics-instrumentation-jvm/src/test/java/io/prometheus/metrics/instrumentation/jvm/JvmMemoryMetricsTest.java b/prometheus-metrics-instrumentation-jvm/src/test/java/io/prometheus/metrics/instrumentation/jvm/JvmMemoryMetricsTest.java index 844389384..b1916c064 100644 --- a/prometheus-metrics-instrumentation-jvm/src/test/java/io/prometheus/metrics/instrumentation/jvm/JvmMemoryMetricsTest.java +++ b/prometheus-metrics-instrumentation-jvm/src/test/java/io/prometheus/metrics/instrumentation/jvm/JvmMemoryMetricsTest.java @@ -30,8 +30,9 @@ class JvmMemoryMetricsTest { private final MemoryUsage memoryUsagePoolCollectionEdenSpace = Mockito.mock(MemoryUsage.class); private final MemoryUsage memoryUsagePoolCollectionOldGen = Mockito.mock(MemoryUsage.class); + @SuppressWarnings("deprecation") @BeforeEach - public void setUp() { + void setUp() { when(mockMemoryBean.getHeapMemoryUsage()).thenReturn(memoryUsageHeap); when(mockMemoryBean.getNonHeapMemoryUsage()).thenReturn(memoryUsageNonHeap); @@ -79,7 +80,7 @@ public void setUp() { } @Test - public void testGoodCase() throws IOException { + void testGoodCase() throws IOException { PrometheusRegistry registry = new PrometheusRegistry(); JvmMemoryMetrics.builder() .withMemoryBean(mockMemoryBean) @@ -88,7 +89,7 @@ public void testGoodCase() throws IOException { MetricSnapshots snapshots = registry.scrape(); String expected = - """ +""" # TYPE jvm_memory_committed_bytes gauge # UNIT jvm_memory_committed_bytes bytes # HELP jvm_memory_committed_bytes Committed (bytes) of a given JVM memory area. @@ -159,7 +160,7 @@ public void testGoodCase() throws IOException { } @Test - public void testIgnoredMetricNotScraped() { + void testIgnoredMetricNotScraped() { MetricNameFilter filter = MetricNameFilter.builder().nameMustNotBeEqualTo("jvm_memory_pool_used_bytes").build(); diff --git a/prometheus-metrics-instrumentation-jvm/src/test/java/io/prometheus/metrics/instrumentation/jvm/JvmMemoryPoolAllocationMetricsTest.java b/prometheus-metrics-instrumentation-jvm/src/test/java/io/prometheus/metrics/instrumentation/jvm/JvmMemoryPoolAllocationMetricsTest.java index 67dd6f2e6..2f681ce89 100644 --- a/prometheus-metrics-instrumentation-jvm/src/test/java/io/prometheus/metrics/instrumentation/jvm/JvmMemoryPoolAllocationMetricsTest.java +++ b/prometheus-metrics-instrumentation-jvm/src/test/java/io/prometheus/metrics/instrumentation/jvm/JvmMemoryPoolAllocationMetricsTest.java @@ -14,7 +14,7 @@ class JvmMemoryPoolAllocationMetricsTest { @Test - public void testListenerLogic() { + void testListenerLogic() { PrometheusRegistry registry = new PrometheusRegistry(); Counter counter = Counter.builder().name("test").labelNames("pool").register(registry); AllocationCountingNotificationListener listener = @@ -47,6 +47,10 @@ public void testListenerLogic() { // Decrease to 17, then increase by 3 listener.handleMemoryPool("TestPool", 17, 20); assertThat(getCountByPool("test", "TestPool", registry.scrape())).isEqualTo(153); + + // Edge case: before < last (tests diff1 < 0 branch) + listener.handleMemoryPool("TestPool", 10, 15); + assertThat(getCountByPool("test", "TestPool", registry.scrape())).isEqualTo(158); } private double getCountByPool(String metricName, String poolName, MetricSnapshots snapshots) { diff --git a/prometheus-metrics-instrumentation-jvm/src/test/java/io/prometheus/metrics/instrumentation/jvm/JvmMetricsTest.java b/prometheus-metrics-instrumentation-jvm/src/test/java/io/prometheus/metrics/instrumentation/jvm/JvmMetricsTest.java index 2bb908a37..c0553703d 100644 --- a/prometheus-metrics-instrumentation-jvm/src/test/java/io/prometheus/metrics/instrumentation/jvm/JvmMetricsTest.java +++ b/prometheus-metrics-instrumentation-jvm/src/test/java/io/prometheus/metrics/instrumentation/jvm/JvmMetricsTest.java @@ -4,6 +4,10 @@ import io.prometheus.metrics.config.PrometheusProperties; import io.prometheus.metrics.model.registry.PrometheusRegistry; +import io.prometheus.metrics.model.snapshots.DataPointSnapshot; +import io.prometheus.metrics.model.snapshots.Labels; +import io.prometheus.metrics.model.snapshots.MetricSnapshot; +import io.prometheus.metrics.model.snapshots.MetricSnapshots; import java.lang.management.ManagementFactory; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -16,7 +20,7 @@ void setUp() { } @Test - public void testRegisterIdempotent() { + void testRegisterIdempotent() { PrometheusRegistry registry = new PrometheusRegistry(); assertThat(registry.scrape().size()).isZero(); JvmMetrics.builder().register(registry); @@ -32,6 +36,25 @@ void pool() { .register(); } + @Test + void testConstLabelsApplied() { + PrometheusRegistry registry = new PrometheusRegistry(); + Labels labels = Labels.of("env", "dev"); + JvmMetrics.builder().constLabels(labels).register(registry); + MetricSnapshots snapshots = registry.scrape(); + boolean found = false; + for (MetricSnapshot snapshot : snapshots) { + for (DataPointSnapshot dp : snapshot.getDataPoints()) { + if ("dev".equals(dp.getLabels().get("env"))) { + found = true; + break; + } + } + if (found) break; + } + assertThat(found).isTrue(); + } + @Test void testJvmMetrics() { JvmMetrics.builder(PrometheusProperties.get()).register(); diff --git a/prometheus-metrics-instrumentation-jvm/src/test/java/io/prometheus/metrics/instrumentation/jvm/JvmNativeMemoryMetricsTest.java b/prometheus-metrics-instrumentation-jvm/src/test/java/io/prometheus/metrics/instrumentation/jvm/JvmNativeMemoryMetricsTest.java index 5350f6f29..e2f2dcdca 100644 --- a/prometheus-metrics-instrumentation-jvm/src/test/java/io/prometheus/metrics/instrumentation/jvm/JvmNativeMemoryMetricsTest.java +++ b/prometheus-metrics-instrumentation-jvm/src/test/java/io/prometheus/metrics/instrumentation/jvm/JvmNativeMemoryMetricsTest.java @@ -14,7 +14,7 @@ class JvmNativeMemoryMetricsTest { @Test - public void testNativeMemoryTrackingFail() throws IOException { + void testNativeMemoryTrackingFail() throws IOException { JvmNativeMemoryMetrics.isEnabled.set(true); JvmNativeMemoryMetrics.PlatformMBeanServerAdapter adapter = @@ -39,7 +39,7 @@ void nativeMemoryTrackingNotEnabled() { } @Test - public void testNativeMemoryTrackingEmpty() throws IOException { + void testNativeMemoryTrackingEmpty() throws IOException { JvmNativeMemoryMetrics.isEnabled.set(true); JvmNativeMemoryMetrics.PlatformMBeanServerAdapter adapter = @@ -56,7 +56,7 @@ public void testNativeMemoryTrackingEmpty() throws IOException { } @Test - public void testNativeMemoryTrackingDisabled() throws IOException { + void testNativeMemoryTrackingDisabled() throws IOException { JvmNativeMemoryMetrics.isEnabled.set(true); JvmNativeMemoryMetrics.PlatformMBeanServerAdapter adapter = @@ -74,7 +74,7 @@ public void testNativeMemoryTrackingDisabled() throws IOException { } @Test - public void testNativeMemoryTrackingEnabled() throws IOException { + void testNativeMemoryTrackingEnabled() throws IOException { JvmNativeMemoryMetrics.isEnabled.set(true); JvmNativeMemoryMetrics.PlatformMBeanServerAdapter adapter = @@ -182,7 +182,7 @@ public void testNativeMemoryTrackingEnabled() throws IOException { MetricSnapshots snapshots = registry.scrape(); String expected = - """ +""" # TYPE jvm_native_memory_committed_bytes gauge # UNIT jvm_native_memory_committed_bytes bytes # HELP jvm_native_memory_committed_bytes Committed bytes of a given JVM. Committed memory represents the amount of memory the JVM is using right now. diff --git a/prometheus-metrics-instrumentation-jvm/src/test/java/io/prometheus/metrics/instrumentation/jvm/JvmRuntimeInfoMetricTest.java b/prometheus-metrics-instrumentation-jvm/src/test/java/io/prometheus/metrics/instrumentation/jvm/JvmRuntimeInfoMetricTest.java index c030387a3..2c6a2773f 100644 --- a/prometheus-metrics-instrumentation-jvm/src/test/java/io/prometheus/metrics/instrumentation/jvm/JvmRuntimeInfoMetricTest.java +++ b/prometheus-metrics-instrumentation-jvm/src/test/java/io/prometheus/metrics/instrumentation/jvm/JvmRuntimeInfoMetricTest.java @@ -11,7 +11,7 @@ class JvmRuntimeInfoMetricTest { @Test - public void testGoodCase() throws IOException { + void testGoodCase() throws IOException { PrometheusRegistry registry = new PrometheusRegistry(); JvmRuntimeInfoMetric.builder() .version("1.8.0_382-b05") @@ -21,7 +21,7 @@ public void testGoodCase() throws IOException { MetricSnapshots snapshots = registry.scrape(); String expected = - """ +""" # TYPE jvm_runtime info # HELP jvm_runtime JVM runtime info jvm_runtime_info{runtime="OpenJDK Runtime Environment",vendor="Oracle Corporation",version="1.8.0_382-b05"} 1 diff --git a/prometheus-metrics-instrumentation-jvm/src/test/java/io/prometheus/metrics/instrumentation/jvm/JvmThreadsMetricsTest.java b/prometheus-metrics-instrumentation-jvm/src/test/java/io/prometheus/metrics/instrumentation/jvm/JvmThreadsMetricsTest.java index cbd0c1345..f001fad37 100644 --- a/prometheus-metrics-instrumentation-jvm/src/test/java/io/prometheus/metrics/instrumentation/jvm/JvmThreadsMetricsTest.java +++ b/prometheus-metrics-instrumentation-jvm/src/test/java/io/prometheus/metrics/instrumentation/jvm/JvmThreadsMetricsTest.java @@ -2,6 +2,7 @@ import static io.prometheus.metrics.instrumentation.jvm.TestUtil.convertToOpenMetricsFormat; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assumptions.assumeThat; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -29,7 +30,7 @@ class JvmThreadsMetricsTest { private final ThreadInfo mockThreadInfoRunnable2 = Mockito.mock(ThreadInfo.class); @BeforeEach - public void setUp() { + void setUp() { when(mockThreadsBean.getThreadCount()).thenReturn(300); when(mockThreadsBean.getDaemonThreadCount()).thenReturn(200); when(mockThreadsBean.getPeakThreadCount()).thenReturn(301); @@ -48,13 +49,13 @@ public void setUp() { } @Test - public void testGoodCase() throws IOException { + void testGoodCase() throws IOException { PrometheusRegistry registry = new PrometheusRegistry(); JvmThreadsMetrics.builder().threadBean(mockThreadsBean).isNativeImage(false).register(registry); MetricSnapshots snapshots = registry.scrape(); String expected = - """ +""" # TYPE jvm_threads_current gauge # HELP jvm_threads_current Current thread count of a JVM jvm_threads_current 300.0 @@ -89,7 +90,7 @@ public void testGoodCase() throws IOException { } @Test - public void testIgnoredMetricNotScraped() { + void testIgnoredMetricNotScraped() { MetricNameFilter filter = MetricNameFilter.builder().nameMustNotBeEqualTo("jvm_threads_deadlocked").build(); @@ -102,14 +103,12 @@ public void testIgnoredMetricNotScraped() { } @Test - public void testInvalidThreadIds() { + void testInvalidThreadIds() { try { String javaVersion = System.getProperty("java.version"); // Example: "21.0.2" String majorJavaVersion = javaVersion.replaceAll("\\..*", ""); // Example: "21" - if (Integer.parseInt(majorJavaVersion) >= 21) { - // With Java 21 and newer you can no longer have invalid thread ids. - return; - } + // With Java 21 and newer you can no longer have invalid thread ids. + assumeThat(Integer.parseInt(majorJavaVersion)).isLessThan(21); } catch (NumberFormatException ignored) { // ignore } @@ -165,7 +164,7 @@ private static class ThreadWithInvalidId extends Thread { private final long id; - public ThreadWithInvalidId(long id, Runnable runnable) { + private ThreadWithInvalidId(long id, Runnable runnable) { super(runnable); setDaemon(true); this.id = id; @@ -175,20 +174,14 @@ public ThreadWithInvalidId(long id, Runnable runnable) { * Note that only Java versions < 21 call this to get the thread id. With Java 21 and newer it's * no longer possible to make an invalid thread id. */ + @SuppressWarnings("deprecation") @Override public long getId() { return this.id; } } - private static class TestRunnable implements Runnable { - - private final CountDownLatch countDownLatch; - - public TestRunnable(CountDownLatch countDownLatch) { - this.countDownLatch = countDownLatch; - } - + private record TestRunnable(CountDownLatch countDownLatch) implements Runnable { @Override public void run() { try { diff --git a/prometheus-metrics-instrumentation-jvm/src/test/java/io/prometheus/metrics/instrumentation/jvm/ProcessMetricsTest.java b/prometheus-metrics-instrumentation-jvm/src/test/java/io/prometheus/metrics/instrumentation/jvm/ProcessMetricsTest.java index f5804ad7f..9c2efd301 100644 --- a/prometheus-metrics-instrumentation-jvm/src/test/java/io/prometheus/metrics/instrumentation/jvm/ProcessMetricsTest.java +++ b/prometheus-metrics-instrumentation-jvm/src/test/java/io/prometheus/metrics/instrumentation/jvm/ProcessMetricsTest.java @@ -30,7 +30,7 @@ class ProcessMetricsTest { private final RuntimeMXBean runtimeBean = Mockito.mock(RuntimeMXBean.class); @BeforeEach - public void setUp() throws IOException { + void setUp() throws IOException { when(sunOsBean.getProcessCpuTime()).thenReturn(TimeUnit.MILLISECONDS.toNanos(72)); when(sunOsBean.getOpenFileDescriptorCount()).thenReturn(127L); when(sunOsBean.getMaxFileDescriptorCount()).thenReturn(244L); @@ -42,7 +42,7 @@ public void setUp() throws IOException { } @Test - public void testGoodCase() throws IOException { + void testGoodCase() throws IOException { PrometheusRegistry registry = new PrometheusRegistry(); ProcessMetrics.builder() .osBean(sunOsBean) @@ -97,7 +97,7 @@ public void testGoodCase() throws IOException { } @Test - public void testMinimal() throws IOException { + void testMinimal() throws IOException { PrometheusRegistry registry = new PrometheusRegistry(); ProcessMetrics.builder() .osBean(javaOsBean) @@ -119,7 +119,7 @@ public void testMinimal() throws IOException { } @Test - public void testIgnoredMetricNotScraped() { + void testIgnoredMetricNotScraped() { MetricNameFilter filter = MetricNameFilter.builder().nameMustNotBeEqualTo("process_max_fds").build(); diff --git a/prometheus-metrics-instrumentation-jvm/src/test/java/io/prometheus/metrics/instrumentation/jvm/TestUtil.java b/prometheus-metrics-instrumentation-jvm/src/test/java/io/prometheus/metrics/instrumentation/jvm/TestUtil.java index 70a093f4b..8cdd6b1ae 100644 --- a/prometheus-metrics-instrumentation-jvm/src/test/java/io/prometheus/metrics/instrumentation/jvm/TestUtil.java +++ b/prometheus-metrics-instrumentation-jvm/src/test/java/io/prometheus/metrics/instrumentation/jvm/TestUtil.java @@ -1,17 +1,18 @@ package io.prometheus.metrics.instrumentation.jvm; +import io.prometheus.metrics.config.EscapingScheme; import io.prometheus.metrics.expositionformats.OpenMetricsTextFormatWriter; import io.prometheus.metrics.model.snapshots.MetricSnapshots; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.nio.charset.StandardCharsets; -class TestUtil { +public class TestUtil { static String convertToOpenMetricsFormat(MetricSnapshots snapshots) throws IOException { ByteArrayOutputStream out = new ByteArrayOutputStream(); OpenMetricsTextFormatWriter writer = new OpenMetricsTextFormatWriter(true, true); - writer.write(out, snapshots); - return out.toString(StandardCharsets.UTF_8.name()); + writer.write(out, snapshots, EscapingScheme.ALLOW_UTF8); + return out.toString(StandardCharsets.UTF_8); } } diff --git a/prometheus-metrics-model/pom.xml b/prometheus-metrics-model/pom.xml index c582a279b..ff07241ce 100644 --- a/prometheus-metrics-model/pom.xml +++ b/prometheus-metrics-model/pom.xml @@ -7,7 +7,7 @@ io.prometheus client_java - 1.4.0-SNAPSHOT + 1.6.0-SNAPSHOT prometheus-metrics-model @@ -21,4 +21,12 @@ io.prometheus.metrics.model + + + + io.prometheus + prometheus-metrics-config + ${project.version} + + diff --git a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/registry/Collector.java b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/registry/Collector.java index 57b2b640f..beca6001e 100644 --- a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/registry/Collector.java +++ b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/registry/Collector.java @@ -1,7 +1,10 @@ package io.prometheus.metrics.model.registry; +import io.prometheus.metrics.model.snapshots.MetricMetadata; import io.prometheus.metrics.model.snapshots.MetricSnapshot; +import java.util.Set; import java.util.function.Predicate; +import javax.annotation.Nullable; /** * To be registered with the Prometheus collector registry. See Overall Structure on Override this if there is a more efficient way than first collecting the snapshot and then * discarding it. */ + @Nullable default MetricSnapshot collect(Predicate includedNames) { MetricSnapshot result = collect(); if (includedNames.test(result.getMetadata().getPrometheusName())) { @@ -43,6 +47,7 @@ default MetricSnapshot collect(Predicate includedNames) { *

Override this if there is a more efficient way than first collecting the snapshot and then * discarding it. */ + @Nullable default MetricSnapshot collect( Predicate includedNames, PrometheusScrapeRequest scrapeRequest) { MetricSnapshot result = collect(scrapeRequest); @@ -62,8 +67,8 @@ default MetricSnapshot collect( * and the metric name is excluded. * * - * Returning {@code null} means checks are omitted (registration the metric always succeeds), and - * the collector is always scraped (the result is dropped after scraping if a name filter is + *

Returning {@code null} means checks are omitted (registration the metric always succeeds), + * and the collector is always scraped (the result is dropped after scraping if a name filter is * present and the metric name is excluded). * *

If your metric has a name that does not change at runtime it is a good idea to overwrite @@ -71,7 +76,63 @@ default MetricSnapshot collect( * *

All metrics in {@code prometheus-metrics-core} override this. */ + @Nullable default String getPrometheusName() { return null; } + + /** + * Returns the metric type for registration-time validation. + * + *

This is used to prevent different metric types (e.g., Counter and Gauge) from sharing the + * same name. Returning {@code null} means type validation is skipped for this collector. + * + *

Validation is performed only at registration time. If this method returns {@code null}, no + * type validation is performed for this collector, and duplicate or conflicting metrics may + * result in invalid exposition output. + * + * @return the metric type, or {@code null} to skip validation + */ + @Nullable + default MetricType getMetricType() { + return null; + } + + /** + * Returns the complete set of label names for this metric. + * + *

This includes both dynamic label names (specified in {@code labelNames()}) and constant + * label names (specified in {@code constLabels()}). Label names are normalized using Prometheus + * naming conventions. + * + *

This is used for registration-time validation to prevent duplicate label schemas for the + * same metric name. Two collectors with the same name and type can coexist if they have different + * label name sets. + * + *

Returning {@code null} is treated as an empty label set: the registry normalizes it to + * {@code Collections.emptySet()} and performs full label-schema validation and duplicate + * detection. Two collectors with the same name, type, and {@code null} (or empty) label names are + * considered duplicate and registration of the second will fail. + * + * @return the set of all label names, or {@code null} (treated as empty) for a metric with no + * labels + */ + @Nullable + default Set getLabelNames() { + return null; + } + + /** + * Returns the metric metadata (name, help, unit) for registration-time validation. + * + *

When non-null, the registry uses this to validate that metrics with the same name have + * consistent help and unit. Returning {@code null} means help/unit validation is skipped for this + * collector. + * + * @return the metric metadata, or {@code null} to skip help/unit validation + */ + @Nullable + default MetricMetadata getMetadata() { + return null; + } } diff --git a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/registry/MetricNameFilter.java b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/registry/MetricNameFilter.java index 59fbe6a13..609f7fd33 100644 --- a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/registry/MetricNameFilter.java +++ b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/registry/MetricNameFilter.java @@ -6,6 +6,7 @@ import java.util.Arrays; import java.util.Collection; import java.util.function.Predicate; +import javax.annotation.Nullable; /** Filter samples (i.e. time series) by name. */ public class MetricNameFilter implements Predicate { @@ -121,7 +122,7 @@ public Builder nameMustBeEqualTo(String... names) { * * @param names empty means no restriction. */ - public Builder nameMustBeEqualTo(Collection names) { + public Builder nameMustBeEqualTo(@Nullable Collection names) { if (names != null) { nameEqualTo.addAll(names); } @@ -144,7 +145,7 @@ public Builder nameMustNotBeEqualTo(String... names) { * * @param names empty means no name will be excluded. */ - public Builder nameMustNotBeEqualTo(Collection names) { + public Builder nameMustNotBeEqualTo(@Nullable Collection names) { if (names != null) { nameNotEqualTo.addAll(names); } @@ -163,7 +164,7 @@ public Builder nameMustStartWith(String... prefixes) { * * @param prefixes empty means no restriction. */ - public Builder nameMustStartWith(Collection prefixes) { + public Builder nameMustStartWith(@Nullable Collection prefixes) { if (prefixes != null) { nameStartsWith.addAll(prefixes); } @@ -182,7 +183,7 @@ public Builder nameMustNotStartWith(String... prefixes) { * * @param prefixes empty means no time series will be excluded. */ - public Builder nameMustNotStartWith(Collection prefixes) { + public Builder nameMustNotStartWith(@Nullable Collection prefixes) { if (prefixes != null) { nameDoesNotStartWith.addAll(prefixes); } diff --git a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/registry/MetricType.java b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/registry/MetricType.java new file mode 100644 index 000000000..5258da84e --- /dev/null +++ b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/registry/MetricType.java @@ -0,0 +1,18 @@ +package io.prometheus.metrics.model.registry; + +/** + * Represents the type of Prometheus metric. + * + *

This enum is used for registration-time validation to ensure that metrics with the same name + * have consistent types across all registered collectors. + */ +public enum MetricType { + COUNTER, + GAUGE, + HISTOGRAM, + SUMMARY, + INFO, + STATESET, + /** Unknown metric type, used as a fallback. */ + UNKNOWN +} diff --git a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/registry/MultiCollector.java b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/registry/MultiCollector.java index a4f52746e..27ac3e10c 100644 --- a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/registry/MultiCollector.java +++ b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/registry/MultiCollector.java @@ -1,10 +1,13 @@ package io.prometheus.metrics.model.registry; +import io.prometheus.metrics.model.snapshots.MetricMetadata; import io.prometheus.metrics.model.snapshots.MetricSnapshot; import io.prometheus.metrics.model.snapshots.MetricSnapshots; import java.util.Collections; import java.util.List; +import java.util.Set; import java.util.function.Predicate; +import javax.annotation.Nullable; /** Like {@link Collector}, but collecting multiple Snapshots at once. */ @FunctionalInterface @@ -39,7 +42,7 @@ default MetricSnapshots collect(Predicate includedNames) { * discarding it. */ default MetricSnapshots collect( - Predicate includedNames, PrometheusScrapeRequest scrapeRequest) { + Predicate includedNames, @Nullable PrometheusScrapeRequest scrapeRequest) { MetricSnapshots allSnapshots = scrapeRequest == null ? collect() : collect(scrapeRequest); MetricSnapshots.Builder result = MetricSnapshots.builder(); for (MetricSnapshot snapshot : allSnapshots) { @@ -59,9 +62,9 @@ default MetricSnapshots collect( * and all names are excluded. * * - * Returning an empty list means checks are omitted (registration metric always succeeds), and the - * collector is always scraped (if a name filter is present and all names are excluded the result - * is dropped). + *

Returning an empty list means checks are omitted (registration metric always succeeds), and + * the collector is always scraped (if a name filter is present and all names are excluded the + * result is dropped). * *

If your collector returns a constant list of metrics that have names that do not change at * runtime it is a good idea to overwrite this and return the names. @@ -69,4 +72,60 @@ default MetricSnapshots collect( default List getPrometheusNames() { return Collections.emptyList(); } + + /** + * Returns the metric type for the given Prometheus name. + * + *

This is used for per-name type validation during registration. Returning {@code null} means + * type validation is skipped for that specific metric name. + * + *

Validation is performed only at registration time. If this method returns {@code null}, no + * type validation is performed for that name, and duplicate or conflicting metrics may result in + * invalid exposition output. + * + * @param prometheusName the Prometheus metric name + * @return the metric type for the given name, or {@code null} to skip validation + */ + @Nullable + default MetricType getMetricType(String prometheusName) { + return null; + } + + /** + * Returns the complete set of label names for the given Prometheus name. + * + *

This includes both dynamic label names and constant label names. Label names are normalized + * using Prometheus naming conventions (dots converted to underscores). + * + *

This is used for per-name label schema validation during registration. Two collectors with + * the same name and type can coexist if they have different label name sets. + * + *

Returning {@code null} is treated as an empty label set: the registry normalizes it to + * {@code Collections.emptySet()} and performs full label-schema validation and duplicate + * detection. Two collectors with the same name, type, and {@code null} (or empty) label names are + * considered duplicate and registration of the second will fail. + * + * @param prometheusName the Prometheus metric name + * @return the set of all label names for the given name, or {@code null} (treated as empty) for a + * metric with no labels + */ + @Nullable + default Set getLabelNames(String prometheusName) { + return null; + } + + /** + * Returns the metric metadata (name, help, unit) for the given Prometheus name. + * + *

When non-null, the registry uses this to validate that metrics with the same name have + * consistent help and unit. Returning {@code null} means help/unit validation is skipped for that + * name. + * + * @param prometheusName the Prometheus metric name + * @return the metric metadata for that name, or {@code null} to skip help/unit validation + */ + @Nullable + default MetricMetadata getMetadata(String prometheusName) { + return null; + } } diff --git a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/registry/PrometheusRegistry.java b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/registry/PrometheusRegistry.java index 0a2434845..f66824972 100644 --- a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/registry/PrometheusRegistry.java +++ b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/registry/PrometheusRegistry.java @@ -1,95 +1,332 @@ package io.prometheus.metrics.model.registry; -import static io.prometheus.metrics.model.snapshots.PrometheusNaming.prometheusName; +import static java.util.Collections.emptySet; +import io.prometheus.metrics.model.snapshots.MetricMetadata; import io.prometheus.metrics.model.snapshots.MetricSnapshot; import io.prometheus.metrics.model.snapshots.MetricSnapshots; +import io.prometheus.metrics.model.snapshots.Unit; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; import java.util.List; +import java.util.Objects; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.CopyOnWriteArrayList; import java.util.function.Predicate; +import javax.annotation.Nullable; public class PrometheusRegistry { public static final PrometheusRegistry defaultRegistry = new PrometheusRegistry(); - private final Set prometheusNames = ConcurrentHashMap.newKeySet(); - private final List collectors = new CopyOnWriteArrayList<>(); - private final List multiCollectors = new CopyOnWriteArrayList<>(); + private final Set collectors = ConcurrentHashMap.newKeySet(); + private final Set multiCollectors = ConcurrentHashMap.newKeySet(); + private final ConcurrentHashMap registered = new ConcurrentHashMap<>(); + private final ConcurrentHashMap collectorMetadata = + new ConcurrentHashMap<>(); + private final ConcurrentHashMap> + multiCollectorMetadata = new ConcurrentHashMap<>(); + + /** Stores the registration details for a Collector at registration time. */ + private static class CollectorRegistration { + final String prometheusName; + final Set labelNames; + + CollectorRegistration(String prometheusName, @Nullable Set labelNames) { + this.prometheusName = prometheusName; + this.labelNames = immutableLabelNames(labelNames); + } + } + + /** + * Stores the registration details for a single metric within a MultiCollector. A MultiCollector + * can produce multiple metrics, so we need one of these per metric name. + */ + private static class MultiCollectorRegistration { + final String prometheusName; + final Set labelNames; + + MultiCollectorRegistration(String prometheusName, @Nullable Set labelNames) { + this.prometheusName = prometheusName; + this.labelNames = immutableLabelNames(labelNames); + } + } + + /** + * Tracks registration information for each metric name to enable validation of type consistency, + * label schema uniqueness, and help/unit consistency. + */ + private static class RegistrationInfo { + private final MetricType type; + private final Set> labelSchemas; + @Nullable private String help; + @Nullable private Unit unit; + + private RegistrationInfo( + MetricType type, + Set> labelSchemas, + @Nullable String help, + @Nullable Unit unit) { + this.type = type; + this.labelSchemas = labelSchemas; + this.help = help; + this.unit = unit; + } + + static RegistrationInfo of( + MetricType type, + @Nullable Set labelNames, + @Nullable String help, + @Nullable Unit unit) { + Set> labelSchemas = ConcurrentHashMap.newKeySet(); + Set normalized = + (labelNames == null || labelNames.isEmpty()) ? emptySet() : labelNames; + labelSchemas.add(normalized); + return new RegistrationInfo(type, labelSchemas, help, unit); + } + + /** + * Validates that the given help and unit are exactly equal to this registration. Throws if + * values differ, including when one is null and the other is non-null. This ensures consistent + * metadata across all collectors sharing the same metric name. + */ + void validateMetadata(@Nullable String newHelp, @Nullable Unit newUnit) { + if (!Objects.equals(help, newHelp)) { + throw new IllegalArgumentException( + "Conflicting help strings. Existing: \"" + help + "\", new: \"" + newHelp + "\""); + } + if (!Objects.equals(unit, newUnit)) { + throw new IllegalArgumentException( + "Conflicting unit. Existing: " + unit + ", new: " + newUnit); + } + } + + /** + * Adds a label schema to this registration. + * + * @param labelNames the label names to add (null or empty sets are normalized to empty set) + * @return true if the schema was added (new), false if it already existed + */ + boolean addLabelSet(@Nullable Set labelNames) { + Set normalized = + (labelNames == null || labelNames.isEmpty()) ? emptySet() : labelNames; + return labelSchemas.add(normalized); + } + + /** + * Removes a label schema from this registration. + * + * @param labelNames the label names to remove (null or empty sets are normalized to empty set) + */ + void removeLabelSet(@Nullable Set labelNames) { + Set normalized = + (labelNames == null || labelNames.isEmpty()) ? emptySet() : labelNames; + labelSchemas.remove(normalized); + } + + /** Returns true if all label schemas have been unregistered. */ + boolean isEmpty() { + return labelSchemas.isEmpty(); + } + + MetricType getType() { + return type; + } + } + + /** + * Returns an immutable set of label names for storage. Defends against mutation of the set + * returned by {@code Collector.getLabelNames()} after registration, which would break duplicate + * detection and unregistration. + */ + private static Set immutableLabelNames(@Nullable Set labelNames) { + if (labelNames == null || labelNames.isEmpty()) { + return emptySet(); + } + return Collections.unmodifiableSet(new HashSet<>(labelNames)); + } + + /** + * Validates the registration of a metric with the given parameters. Ensures type consistency, + * label schema uniqueness, and help/unit consistency. + */ + private void validateRegistration( + String prometheusName, + MetricType metricType, + Set normalizedLabels, + @Nullable String help, + @Nullable Unit unit) { + final MetricType type = metricType; + final Set names = normalizedLabels; + final String helpForValidation = help; + final Unit unitForValidation = unit; + registered.compute( + prometheusName, + (n, existingInfo) -> { + if (existingInfo == null) { + return RegistrationInfo.of(type, names, helpForValidation, unitForValidation); + } else { + if (existingInfo.getType() != type) { + throw new IllegalArgumentException( + prometheusName + + ": Conflicting metric types. Existing: " + + existingInfo.getType() + + ", new: " + + type); + } + // Check label set first; only mutate help/unit after validation passes. + if (!existingInfo.addLabelSet(names)) { + throw new IllegalArgumentException( + prometheusName + ": duplicate metric name with identical label schema " + names); + } + // Roll back label schema if metadata validation fails + try { + existingInfo.validateMetadata(helpForValidation, unitForValidation); + } catch (IllegalArgumentException e) { + existingInfo.removeLabelSet(names); + throw e; + } + return existingInfo; + } + }); + } public void register(Collector collector) { - String prometheusName = collector.getPrometheusName(); - if (prometheusName != null) { - if (!prometheusNames.add(prometheusName)) { - throw new IllegalStateException( - "Can't register " - + prometheusName - + " because a metric with that name is already registered."); + if (!collectors.add(collector)) { + throw new IllegalArgumentException("Collector instance is already registered"); + } + try { + String prometheusName = collector.getPrometheusName(); + MetricType metricType = collector.getMetricType(); + Set normalizedLabels = immutableLabelNames(collector.getLabelNames()); + MetricMetadata metadata = collector.getMetadata(); + String help = metadata != null ? metadata.getHelp() : null; + Unit unit = metadata != null ? metadata.getUnit() : null; + + // Only perform validation if collector provides sufficient metadata. + // Collectors that don't implement getPrometheusName()/getMetricType() will skip validation. + if (prometheusName != null && metricType != null) { + validateRegistration(prometheusName, metricType, normalizedLabels, help, unit); + collectorMetadata.put( + collector, new CollectorRegistration(prometheusName, normalizedLabels)); } + // Catch RuntimeException broadly because collector methods (getPrometheusName, getMetricType, + // etc.) are user-implemented and could throw any RuntimeException. Ensures cleanup on + // failure. + } catch (RuntimeException e) { + collectors.remove(collector); + CollectorRegistration reg = collectorMetadata.remove(collector); + if (reg != null && reg.prometheusName != null) { + unregisterLabelSchema(reg.prometheusName, reg.labelNames); + } + throw e; } - collectors.add(collector); } public void register(MultiCollector collector) { - for (String prometheusName : collector.getPrometheusNames()) { - if (!prometheusNames.add(prometheusName)) { - throw new IllegalStateException( - "Can't register " + prometheusName + " because that name is already registered."); + if (!multiCollectors.add(collector)) { + throw new IllegalArgumentException("MultiCollector instance is already registered"); + } + List prometheusNamesList = collector.getPrometheusNames(); + List registrations = new ArrayList<>(); + + try { + for (String prometheusName : prometheusNamesList) { + MetricType metricType = collector.getMetricType(prometheusName); + Set normalizedLabels = immutableLabelNames(collector.getLabelNames(prometheusName)); + MetricMetadata metadata = collector.getMetadata(prometheusName); + String help = metadata != null ? metadata.getHelp() : null; + Unit unit = metadata != null ? metadata.getUnit() : null; + + if (metricType != null) { + validateRegistration(prometheusName, metricType, normalizedLabels, help, unit); + registrations.add(new MultiCollectorRegistration(prometheusName, normalizedLabels)); + } } + + multiCollectorMetadata.put(collector, registrations); + // Catch RuntimeException broadly because collector methods (getPrometheusNames, + // getMetricType, etc.) are user-implemented and could throw any RuntimeException. + // Ensures cleanup on failure. + } catch (RuntimeException e) { + multiCollectors.remove(collector); + for (MultiCollectorRegistration registration : registrations) { + unregisterLabelSchema(registration.prometheusName, registration.labelNames); + } + throw e; } - multiCollectors.add(collector); } public void unregister(Collector collector) { collectors.remove(collector); - String prometheusName = collector.getPrometheusName(); - if (prometheusName != null) { - prometheusNames.remove(collector.getPrometheusName()); + + CollectorRegistration registration = collectorMetadata.remove(collector); + if (registration != null && registration.prometheusName != null) { + unregisterLabelSchema(registration.prometheusName, registration.labelNames); } } public void unregister(MultiCollector collector) { multiCollectors.remove(collector); - for (String prometheusName : collector.getPrometheusNames()) { - prometheusNames.remove(prometheusName(prometheusName)); + + List registrations = multiCollectorMetadata.remove(collector); + if (registrations != null) { + for (MultiCollectorRegistration registration : registrations) { + unregisterLabelSchema(registration.prometheusName, registration.labelNames); + } } } + /** + * Removes the label schema for the given metric name. If no label schemas remain for that name, + * removes the metric name entirely from the registry. + */ + private void unregisterLabelSchema(String prometheusName, Set labelNames) { + registered.computeIfPresent( + prometheusName, + (name, info) -> { + info.removeLabelSet(labelNames); + if (info.isEmpty()) { + return null; + } + return info; + }); + } + public void clear() { collectors.clear(); multiCollectors.clear(); - prometheusNames.clear(); + registered.clear(); + collectorMetadata.clear(); + multiCollectorMetadata.clear(); } public MetricSnapshots scrape() { return scrape((PrometheusScrapeRequest) null); } - public MetricSnapshots scrape(PrometheusScrapeRequest scrapeRequest) { - MetricSnapshots.Builder result = MetricSnapshots.builder(); + public MetricSnapshots scrape(@Nullable PrometheusScrapeRequest scrapeRequest) { + List allSnapshots = new ArrayList<>(); for (Collector collector : collectors) { MetricSnapshot snapshot = scrapeRequest == null ? collector.collect() : collector.collect(scrapeRequest); if (snapshot != null) { - if (result.containsMetricName(snapshot.getMetadata().getName())) { - throw new IllegalStateException( - snapshot.getMetadata().getPrometheusName() + ": duplicate metric name."); - } - result.metricSnapshot(snapshot); + allSnapshots.add(snapshot); } } for (MultiCollector collector : multiCollectors) { MetricSnapshots snapshots = scrapeRequest == null ? collector.collect() : collector.collect(scrapeRequest); for (MetricSnapshot snapshot : snapshots) { - if (result.containsMetricName(snapshot.getMetadata().getName())) { - throw new IllegalStateException( - snapshot.getMetadata().getPrometheusName() + ": duplicate metric name."); - } - result.metricSnapshot(snapshot); + allSnapshots.add(snapshot); } } + + MetricSnapshots.Builder result = MetricSnapshots.builder(); + for (MetricSnapshot snapshot : allSnapshots) { + result.metricSnapshot(snapshot); + } return result.build(); } @@ -101,11 +338,11 @@ public MetricSnapshots scrape(Predicate includedNames) { } public MetricSnapshots scrape( - Predicate includedNames, PrometheusScrapeRequest scrapeRequest) { + Predicate includedNames, @Nullable PrometheusScrapeRequest scrapeRequest) { if (includedNames == null) { return scrape(scrapeRequest); } - MetricSnapshots.Builder result = MetricSnapshots.builder(); + List allSnapshots = new ArrayList<>(); for (Collector collector : collectors) { String prometheusName = collector.getPrometheusName(); // prometheusName == null means the name is unknown, and we have to scrape to learn the name. @@ -116,7 +353,7 @@ public MetricSnapshots scrape( ? collector.collect(includedNames) : collector.collect(includedNames, scrapeRequest); if (snapshot != null) { - result.metricSnapshot(snapshot); + allSnapshots.add(snapshot); } } } @@ -140,11 +377,16 @@ public MetricSnapshots scrape( : collector.collect(includedNames, scrapeRequest); for (MetricSnapshot snapshot : snapshots) { if (snapshot != null) { - result.metricSnapshot(snapshot); + allSnapshots.add(snapshot); } } } } + + MetricSnapshots.Builder result = MetricSnapshots.builder(); + for (MetricSnapshot snapshot : allSnapshots) { + result.metricSnapshot(snapshot); + } return result.build(); } } diff --git a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/registry/PrometheusScrapeRequest.java b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/registry/PrometheusScrapeRequest.java index b1789c3bd..91a1313ab 100644 --- a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/registry/PrometheusScrapeRequest.java +++ b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/registry/PrometheusScrapeRequest.java @@ -1,5 +1,7 @@ package io.prometheus.metrics.model.registry; +import javax.annotation.Nullable; + /** Infos extracted from the request received by the endpoint */ public interface PrometheusScrapeRequest { @@ -7,5 +9,6 @@ public interface PrometheusScrapeRequest { String getRequestPath(); /** See {@code jakarta.servlet.ServletRequest.getParameterValues(String name)} */ + @Nullable String[] getParameterValues(String name); } diff --git a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/CounterSnapshot.java b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/CounterSnapshot.java index 373c7550e..72a83a879 100644 --- a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/CounterSnapshot.java +++ b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/CounterSnapshot.java @@ -1,8 +1,10 @@ package io.prometheus.metrics.model.snapshots; +import io.prometheus.metrics.config.EscapingScheme; import java.util.ArrayList; import java.util.Collection; import java.util.List; +import javax.annotation.Nullable; /** Immutable snapshot of a Counter. */ public class CounterSnapshot extends MetricSnapshot { @@ -16,7 +18,12 @@ public class CounterSnapshot extends MetricSnapshot { * @param dataPoints the constructor will create a sorted copy of the collection. */ public CounterSnapshot(MetricMetadata metadata, Collection dataPoints) { - super(metadata, dataPoints); + this(metadata, dataPoints, false); + } + + private CounterSnapshot( + MetricMetadata metadata, Collection dataPoints, boolean internal) { + super(metadata, dataPoints, internal); } @SuppressWarnings("unchecked") @@ -25,10 +32,20 @@ public List getDataPoints() { return (List) dataPoints; } + @SuppressWarnings("unchecked") + @Override + MetricSnapshot escape( + EscapingScheme escapingScheme, List dataPointSnapshots) { + return new CounterSnapshot( + getMetadata().escape(escapingScheme), + (List) dataPointSnapshots, + true); + } + public static class CounterDataPointSnapshot extends DataPointSnapshot { private final double value; - private final Exemplar exemplar; // may be null + @Nullable private final Exemplar exemplar; /** * To create a new {@link CounterDataPointSnapshot}, you can either call the constructor @@ -42,7 +59,7 @@ public static class CounterDataPointSnapshot extends DataPointSnapshot { * Use {@code 0L} if there is no created timestamp. */ public CounterDataPointSnapshot( - double value, Labels labels, Exemplar exemplar, long createdTimestampMillis) { + double value, Labels labels, @Nullable Exemplar exemplar, long createdTimestampMillis) { this(value, labels, exemplar, createdTimestampMillis, 0); } @@ -55,20 +72,33 @@ public CounterDataPointSnapshot( public CounterDataPointSnapshot( double value, Labels labels, - Exemplar exemplar, + @Nullable Exemplar exemplar, long createdTimestampMillis, long scrapeTimestampMillis) { - super(labels, createdTimestampMillis, scrapeTimestampMillis); + this(value, labels, exemplar, createdTimestampMillis, scrapeTimestampMillis, false); + } + + @SuppressWarnings("this-escape") + public CounterDataPointSnapshot( + double value, + Labels labels, + @Nullable Exemplar exemplar, + long createdTimestampMillis, + long scrapeTimestampMillis, + boolean internal) { + super(labels, createdTimestampMillis, scrapeTimestampMillis, internal); this.value = value; this.exemplar = exemplar; - validate(); + if (!internal) { + validate(); + } } public double getValue() { return value; } - /** May be {@code null}. */ + @Nullable public Exemplar getExemplar() { return exemplar; } @@ -79,14 +109,25 @@ protected void validate() { } } + @Override + DataPointSnapshot escape(EscapingScheme escapingScheme) { + return new CounterSnapshot.CounterDataPointSnapshot( + value, + SnapshotEscaper.escapeLabels(getLabels(), escapingScheme), + SnapshotEscaper.escapeExemplar(exemplar, escapingScheme), + getCreatedTimestampMillis(), + getScrapeTimestampMillis(), + true); + } + public static Builder builder() { return new Builder(); } public static class Builder extends DataPointSnapshot.Builder { - private Exemplar exemplar = null; - private Double value = null; + @Nullable private Exemplar exemplar = null; + @Nullable private Double value = null; private long createdTimestampMillis = 0L; private Builder() {} @@ -97,7 +138,7 @@ public Builder value(double value) { return this; } - public Builder exemplar(Exemplar exemplar) { + public Builder exemplar(@Nullable Exemplar exemplar) { this.exemplar = exemplar; return this; } diff --git a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/DataPointSnapshot.java b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/DataPointSnapshot.java index 6b455bff6..d960912a0 100644 --- a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/DataPointSnapshot.java +++ b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/DataPointSnapshot.java @@ -1,5 +1,7 @@ package io.prometheus.metrics.model.snapshots; +import io.prometheus.metrics.config.EscapingScheme; + @SuppressWarnings("this-escape") public abstract class DataPointSnapshot { private final Labels labels; @@ -7,11 +9,13 @@ public abstract class DataPointSnapshot { private final long scrapeTimestampMillis; protected DataPointSnapshot( - Labels labels, long createdTimestampMillis, long scrapeTimestampMillis) { + Labels labels, long createdTimestampMillis, long scrapeTimestampMillis, boolean internal) { this.labels = labels; this.createdTimestampMillis = createdTimestampMillis; this.scrapeTimestampMillis = scrapeTimestampMillis; - validate(); + if (!internal) { + validate(); + } } private void validate() { @@ -85,4 +89,6 @@ public T scrapeTimestampMillis(long scrapeTimestampMillis) { protected abstract T self(); } + + abstract DataPointSnapshot escape(EscapingScheme escapingScheme); } diff --git a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/DistributionDataPointSnapshot.java b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/DistributionDataPointSnapshot.java index c8092237c..1ae0559e1 100644 --- a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/DistributionDataPointSnapshot.java +++ b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/DistributionDataPointSnapshot.java @@ -16,12 +16,15 @@ protected DistributionDataPointSnapshot( Exemplars exemplars, Labels labels, long createdTimestampMillis, - long scrapeTimestampMillis) { - super(labels, createdTimestampMillis, scrapeTimestampMillis); + long scrapeTimestampMillis, + boolean internal) { + super(labels, createdTimestampMillis, scrapeTimestampMillis, internal); this.count = count; this.sum = sum; this.exemplars = exemplars == null ? Exemplars.EMPTY : exemplars; - validate(); + if (!internal) { + validate(); + } } private void validate() { diff --git a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/Exemplar.java b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/Exemplar.java index e56d9e1c7..418603580 100644 --- a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/Exemplar.java +++ b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/Exemplar.java @@ -1,5 +1,9 @@ package io.prometheus.metrics.model.snapshots; +import static java.util.Objects.requireNonNull; + +import javax.annotation.Nullable; + /** Immutable representation of an Exemplar. */ public class Exemplar { @@ -59,10 +63,10 @@ public static Builder builder() { public static class Builder { - private Double value = null; + @Nullable private Double value = null; private Labels labels = Labels.EMPTY; - private String traceId = null; - private String spanId = null; + @Nullable private String traceId = null; + @Nullable private String spanId = null; private long timestampMillis = 0L; private Builder() {} @@ -83,10 +87,7 @@ public Builder spanId(String spanId) { } public Builder labels(Labels labels) { - if (labels == null) { - throw new NullPointerException(); - } - this.labels = labels; + this.labels = requireNonNull(labels, "Labels must not be null."); return this; } diff --git a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/Exemplars.java b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/Exemplars.java index ca9109b96..887a80fff 100644 --- a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/Exemplars.java +++ b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/Exemplars.java @@ -6,6 +6,7 @@ import java.util.Collections; import java.util.Iterator; import java.util.List; +import javax.annotation.Nullable; /** * Immutable container for Exemplars. @@ -60,6 +61,7 @@ public int size() { return exemplars.size(); } + @Nullable public Exemplar get(int index) { return exemplars.get(index); } @@ -69,6 +71,7 @@ public Exemplar get(int index) { * upperBound. If there is more than one exemplar within the bounds the one with the newest time * stamp is returned. */ + @Nullable public Exemplar get(double lowerBound, double upperBound) { Exemplar result = null; for (Exemplar exemplar : exemplars) { @@ -87,6 +90,7 @@ public Exemplar get(double lowerBound, double upperBound) { } /** Find the Exemplar with the newest timestamp. May return {@code null}. */ + @Nullable public Exemplar getLatest() { Exemplar latest = null; for (Exemplar candidate : exemplars) { diff --git a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/GaugeSnapshot.java b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/GaugeSnapshot.java index 641aa166a..03474852f 100644 --- a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/GaugeSnapshot.java +++ b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/GaugeSnapshot.java @@ -1,8 +1,10 @@ package io.prometheus.metrics.model.snapshots; +import io.prometheus.metrics.config.EscapingScheme; import java.util.ArrayList; import java.util.Collection; import java.util.List; +import javax.annotation.Nullable; /** Immutable snapshot of a Gauge. */ public final class GaugeSnapshot extends MetricSnapshot { @@ -15,7 +17,12 @@ public final class GaugeSnapshot extends MetricSnapshot { * @param data the constructor will create a sorted copy of the collection. */ public GaugeSnapshot(MetricMetadata metadata, Collection data) { - super(metadata, data); + this(metadata, data, false); + } + + private GaugeSnapshot( + MetricMetadata metadata, Collection data, boolean internal) { + super(metadata, data, internal); } @SuppressWarnings("unchecked") @@ -24,10 +31,20 @@ public List getDataPoints() { return (List) dataPoints; } + @SuppressWarnings("unchecked") + @Override + MetricSnapshot escape( + EscapingScheme escapingScheme, List dataPointSnapshots) { + return new GaugeSnapshot( + getMetadata().escape(escapingScheme), + (List) dataPointSnapshots, + true); + } + public static final class GaugeDataPointSnapshot extends DataPointSnapshot { private final double value; - private final Exemplar exemplar; // may be null + @Nullable private final Exemplar exemplar; /** * To create a new {@link GaugeDataPointSnapshot}, you can either call the constructor directly @@ -37,7 +54,7 @@ public static final class GaugeDataPointSnapshot extends DataPointSnapshot { * @param labels must not be null. Use {@link Labels#EMPTY} if there are no labels. * @param exemplar may be null. */ - public GaugeDataPointSnapshot(double value, Labels labels, Exemplar exemplar) { + public GaugeDataPointSnapshot(double value, Labels labels, @Nullable Exemplar exemplar) { this(value, labels, exemplar, 0); } @@ -47,8 +64,17 @@ public GaugeDataPointSnapshot(double value, Labels labels, Exemplar exemplar) { * mirroring metrics with given timestamps from other metric sources. */ public GaugeDataPointSnapshot( - double value, Labels labels, Exemplar exemplar, long scrapeTimestampMillis) { - super(labels, 0L, scrapeTimestampMillis); + double value, Labels labels, @Nullable Exemplar exemplar, long scrapeTimestampMillis) { + this(value, labels, exemplar, scrapeTimestampMillis, false); + } + + private GaugeDataPointSnapshot( + double value, + Labels labels, + @Nullable Exemplar exemplar, + long scrapeTimestampMillis, + boolean internal) { + super(labels, 0L, scrapeTimestampMillis, internal); this.value = value; this.exemplar = exemplar; } @@ -57,7 +83,7 @@ public double getValue() { return value; } - /** May be {@code null}. */ + @Nullable public Exemplar getExemplar() { return exemplar; } @@ -66,10 +92,20 @@ public static Builder builder() { return new Builder(); } + @Override + DataPointSnapshot escape(EscapingScheme escapingScheme) { + return new GaugeSnapshot.GaugeDataPointSnapshot( + value, + SnapshotEscaper.escapeLabels(getLabels(), escapingScheme), + SnapshotEscaper.escapeExemplar(exemplar, escapingScheme), + getCreatedTimestampMillis(), + true); + } + public static class Builder extends DataPointSnapshot.Builder { - private Exemplar exemplar = null; - private Double value = null; + @Nullable private Exemplar exemplar = null; + @Nullable private Double value = null; private Builder() {} diff --git a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/HistogramSnapshot.java b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/HistogramSnapshot.java index 51b529616..acc78d3d3 100644 --- a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/HistogramSnapshot.java +++ b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/HistogramSnapshot.java @@ -1,5 +1,6 @@ package io.prometheus.metrics.model.snapshots; +import io.prometheus.metrics.config.EscapingScheme; import java.util.ArrayList; import java.util.Collection; import java.util.List; @@ -31,7 +32,15 @@ public HistogramSnapshot( boolean isGaugeHistogram, MetricMetadata metadata, Collection data) { - super(metadata, data); + this(isGaugeHistogram, metadata, data, false); + } + + private HistogramSnapshot( + boolean isGaugeHistogram, + MetricMetadata metadata, + Collection data, + boolean internal) { + super(metadata, data, internal); this.gaugeHistogram = isGaugeHistogram; } @@ -45,6 +54,17 @@ public List getDataPoints() { return (List) dataPoints; } + @SuppressWarnings("unchecked") + @Override + MetricSnapshot escape( + EscapingScheme escapingScheme, List dataPointSnapshots) { + return new HistogramSnapshot( + gaugeHistogram, + getMetadata().escape(escapingScheme), + (List) dataPointSnapshots, + true); + } + public static final class HistogramDataPointSnapshot extends DistributionDataPointSnapshot { // There are two types of histograms: Classic histograms and native histograms. @@ -222,33 +242,55 @@ public HistogramDataPointSnapshot( Exemplars exemplars, long createdTimestampMillis, long scrapeTimestampMillis) { - super( + this( + classicBuckets, + nativeSchema, + sum, + labels, + exemplars, + createdTimestampMillis, + scrapeTimestampMillis, calculateCount( classicBuckets, nativeSchema, nativeZeroCount, nativeBucketsForPositiveValues, nativeBucketsForNegativeValues), - sum, - exemplars, - labels, - createdTimestampMillis, - scrapeTimestampMillis); - this.classicBuckets = classicBuckets; - this.nativeSchema = nativeSchema; - this.nativeZeroCount = nativeSchema == CLASSIC_HISTOGRAM ? 0 : nativeZeroCount; - this.nativeZeroThreshold = nativeSchema == CLASSIC_HISTOGRAM ? 0 : nativeZeroThreshold; - this.nativeBucketsForPositiveValues = nativeSchema == CLASSIC_HISTOGRAM ? NativeHistogramBuckets.EMPTY - : nativeBucketsForPositiveValues; - this.nativeBucketsForNegativeValues = + : nativeBucketsForPositiveValues, nativeSchema == CLASSIC_HISTOGRAM ? NativeHistogramBuckets.EMPTY - : nativeBucketsForNegativeValues; + : nativeBucketsForNegativeValues, + nativeSchema == CLASSIC_HISTOGRAM ? 0 : nativeZeroCount, + nativeSchema == CLASSIC_HISTOGRAM ? 0 : nativeZeroThreshold, + false); validate(); } + private HistogramDataPointSnapshot( + ClassicHistogramBuckets classicBuckets, + int nativeSchema, + double sum, + Labels labels, + Exemplars exemplars, + long createdTimestampMillis, + long scrapeTimestampMillis, + long count, + NativeHistogramBuckets nativeBucketsForPositiveValues, + NativeHistogramBuckets nativeBucketsForNegativeValues, + long nativeZeroCount, + double nativeZeroThreshold, + boolean internal) { + super(count, sum, exemplars, labels, createdTimestampMillis, scrapeTimestampMillis, internal); + this.classicBuckets = classicBuckets; + this.nativeSchema = nativeSchema; + this.nativeZeroCount = nativeZeroCount; + this.nativeZeroThreshold = nativeZeroThreshold; + this.nativeBucketsForPositiveValues = nativeBucketsForPositiveValues; + this.nativeBucketsForNegativeValues = nativeBucketsForNegativeValues; + } + private static long calculateCount( ClassicHistogramBuckets classicBuckets, int nativeSchema, @@ -378,6 +420,24 @@ private void validate() { } } + @Override + DataPointSnapshot escape(EscapingScheme escapingScheme) { + return new HistogramSnapshot.HistogramDataPointSnapshot( + classicBuckets, + nativeSchema, + getSum(), + SnapshotEscaper.escapeLabels(getLabels(), escapingScheme), + SnapshotEscaper.escapeExemplars(getExemplars(), escapingScheme), + getCreatedTimestampMillis(), + getScrapeTimestampMillis(), + getCount(), + nativeBucketsForPositiveValues, + nativeBucketsForNegativeValues, + nativeZeroCount, + nativeZeroThreshold, + true); + } + public static Builder builder() { return new Builder(); } diff --git a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/InfoSnapshot.java b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/InfoSnapshot.java index f464c12ae..ca6cf70a0 100644 --- a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/InfoSnapshot.java +++ b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/InfoSnapshot.java @@ -1,8 +1,10 @@ package io.prometheus.metrics.model.snapshots; +import io.prometheus.metrics.config.EscapingScheme; import java.util.ArrayList; import java.util.Collection; import java.util.List; +import javax.annotation.Nullable; /** Immutable snapshot of an Info metric. */ public final class InfoSnapshot extends MetricSnapshot { @@ -16,18 +18,33 @@ public final class InfoSnapshot extends MetricSnapshot { * @param data the constructor will create a sorted copy of the collection. */ public InfoSnapshot(MetricMetadata metadata, Collection data) { - super(metadata, data); + this(metadata, data, false); if (metadata.hasUnit()) { throw new IllegalArgumentException("An Info metric cannot have a unit."); } } + private InfoSnapshot( + MetricMetadata metadata, Collection data, boolean internal) { + super(metadata, data, internal); + } + @SuppressWarnings("unchecked") @Override public List getDataPoints() { return (List) dataPoints; } + @SuppressWarnings("unchecked") + @Override + MetricSnapshot escape( + EscapingScheme escapingScheme, List dataPointSnapshots) { + return new InfoSnapshot( + getMetadata().escape(escapingScheme), + (List) dataPointSnapshots, + true); + } + public static class InfoDataPointSnapshot extends DataPointSnapshot { /** @@ -46,19 +63,31 @@ public InfoDataPointSnapshot(Labels labels) { * mirroring metrics with given timestamps from other metric sources. */ public InfoDataPointSnapshot(Labels labels, long scrapeTimestampMillis) { - super(labels, 0L, scrapeTimestampMillis); + this(labels, scrapeTimestampMillis, false); + } + + private InfoDataPointSnapshot(Labels labels, long scrapeTimestampMillis, boolean internal) { + super(labels, 0L, scrapeTimestampMillis, internal); } public static Builder builder() { return new Builder(); } + @Override + DataPointSnapshot escape(EscapingScheme escapingScheme) { + return new InfoSnapshot.InfoDataPointSnapshot( + SnapshotEscaper.escapeLabels(getLabels(), escapingScheme), + getScrapeTimestampMillis(), + true); + } + public static class Builder extends DataPointSnapshot.Builder { private Builder() {} public InfoDataPointSnapshot build() { - return new InfoDataPointSnapshot(labels, scrapeTimestampMillis); + return new InfoDataPointSnapshot(labels, scrapeTimestampMillis, true); } @Override @@ -85,7 +114,7 @@ public Builder dataPoint(InfoDataPointSnapshot dataPoint) { } @Override - public Builder unit(Unit unit) { + public Builder unit(@Nullable Unit unit) { throw new IllegalArgumentException("Info metric cannot have a unit."); } diff --git a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/Labels.java b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/Labels.java index 97cbfd43a..31c6ab485 100644 --- a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/Labels.java +++ b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/Labels.java @@ -9,6 +9,7 @@ import java.util.Iterator; import java.util.List; import java.util.stream.Stream; +import javax.annotation.Nullable; /** Immutable set of name/value pairs, sorted by name. */ public final class Labels implements Comparable, Iterable

Dots are treated as underscores, so {@code get("my.label")} and {@code get("my_label")} are * the same. */ + @Nullable public String get(String labelName) { labelName = prometheusName(labelName); for (int i = 0; i < prometheusNames.length; i++) { diff --git a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/MetricMetadata.java b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/MetricMetadata.java index 581cb9143..9c54f96d5 100644 --- a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/MetricMetadata.java +++ b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/MetricMetadata.java @@ -1,5 +1,8 @@ package io.prometheus.metrics.model.snapshots; +import io.prometheus.metrics.config.EscapingScheme; +import javax.annotation.Nullable; + /** Immutable container for metric metadata: name, help, unit. */ public final class MetricMetadata { @@ -18,16 +21,16 @@ public final class MetricMetadata { private final String name; /** - * Same as name, except if name contains dots, then the prometheusName is {@code name.replace(".", - * "_")}. + * Same as name that all invalid char (without Unicode support) are replaced by _ + * + *

Multiple metrics with the same prometheusName are not allowed, because they would end up in + * the same time series in Prometheus if {@link EscapingScheme#UNDERSCORE_ESCAPING} or {@link + * EscapingScheme#DOTS_ESCAPING} is used. */ private final String prometheusName; - /** optional, may be {@code null}. */ - private final String help; - - /** optional, may be {@code null}. */ - private final Unit unit; + @Nullable private final String help; + @Nullable private final Unit unit; /** See {@link #MetricMetadata(String, String, Unit)} */ public MetricMetadata(String name) { @@ -48,27 +51,27 @@ public MetricMetadata(String name, String help) { * @param help optional. May be {@code null}. * @param unit optional. May be {@code null}. */ - public MetricMetadata(String name, String help, Unit unit) { + public MetricMetadata(String name, @Nullable String help, @Nullable Unit unit) { this.name = name; this.help = help; this.unit = unit; validate(); - this.prometheusName = name.contains(".") ? PrometheusNaming.prometheusName(name) : name; + this.prometheusName = PrometheusNaming.prometheusName(name); } /** * The name does not include the {@code _total} suffix for counter metrics or the {@code _info} * suffix for Info metrics. * - *

The name may contain dots. Use {@link #getPrometheusName()} to get the name in Prometheus - * format, i.e. with dots replaced by underscores. + *

The name may contain any Unicode chars. Use {@link #getPrometheusName()} to get the name in + * legacy Prometheus format, i.e. with all dots and all invalid chars replaced by underscores. */ public String getName() { return name; } /** - * Same as {@link #getName()} but with dots replaced by underscores. + * Same as {@link #getName()} but with all invalid characters and dots replaced by underscores. * *

This is used by Prometheus exposition formats. */ @@ -76,6 +79,7 @@ public String getPrometheusName() { return prometheusName; } + @Nullable public String getHelp() { return help; } @@ -84,6 +88,7 @@ public boolean hasUnit() { return unit != null; } + @Nullable public Unit getUnit() { return unit; } @@ -118,4 +123,8 @@ private void validate() { } } } + + MetricMetadata escape(EscapingScheme escapingScheme) { + return new MetricMetadata(PrometheusNaming.escapeName(name, escapingScheme), help, unit); + } } diff --git a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/MetricSnapshot.java b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/MetricSnapshot.java index 3e1e49a2f..4dac2e30e 100644 --- a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/MetricSnapshot.java +++ b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/MetricSnapshot.java @@ -1,30 +1,37 @@ package io.prometheus.metrics.model.snapshots; +import io.prometheus.metrics.config.EscapingScheme; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.List; +import javax.annotation.Nullable; /** Base class for metric snapshots. */ public abstract class MetricSnapshot { - private final MetricMetadata metadata; protected final List dataPoints; protected MetricSnapshot( - MetricMetadata metadata, Collection dataPoints) { - if (metadata == null) { - throw new NullPointerException("metadata"); - } - if (dataPoints == null) { - throw new NullPointerException("dataPoints"); - } + MetricMetadata metadata, + Collection dataPoints, + boolean internal) { this.metadata = metadata; - List dataCopy = new ArrayList<>(dataPoints); - dataCopy.sort(Comparator.comparing(DataPointSnapshot::getLabels)); - this.dataPoints = Collections.unmodifiableList(dataCopy); - validateLabels(this.dataPoints, metadata); + if (internal) { + this.dataPoints = (List) dataPoints; + } else { + if (metadata == null) { + throw new NullPointerException("metadata"); + } + if (dataPoints == null) { + throw new NullPointerException("dataPoints"); + } + List dataCopy = new ArrayList<>(dataPoints); + dataCopy.sort(Comparator.comparing(DataPointSnapshot::getLabels)); + this.dataPoints = Collections.unmodifiableList(dataCopy); + validateLabels(this.dataPoints, metadata); + } } public MetricMetadata getMetadata() { @@ -48,9 +55,9 @@ private static void validateLabels( public abstract static class Builder> { - private String name; - private String help; - private Unit unit; + @Nullable private String name; + @Nullable private String help; + @Nullable private Unit unit; /** * The name is required. If the name is missing or invalid, {@code build()} will throw an {@link @@ -62,12 +69,12 @@ public T name(String name) { return self(); } - public T help(String help) { + public T help(@Nullable String help) { this.help = help; return self(); } - public T unit(Unit unit) { + public T unit(@Nullable Unit unit) { this.unit = unit; return self(); } @@ -75,9 +82,15 @@ public T unit(Unit unit) { public abstract MetricSnapshot build(); protected MetricMetadata buildMetadata() { + if (name == null) { + throw new IllegalArgumentException("Missing required field: name is null"); + } return new MetricMetadata(name, help, unit); } protected abstract T self(); } + + abstract MetricSnapshot escape( + EscapingScheme escapingScheme, List dataPointSnapshots); } diff --git a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/MetricSnapshots.java b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/MetricSnapshots.java index ecee897e4..cdec0ddaf 100644 --- a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/MetricSnapshots.java +++ b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/MetricSnapshots.java @@ -28,23 +28,46 @@ public MetricSnapshots(MetricSnapshot... snapshots) { * #builder()}. * * @param snapshots the constructor creates a sorted copy of snapshots. - * @throws IllegalArgumentException if snapshots contains duplicate metric names. To avoid - * duplicate metric names use {@link #builder()} and check {@link - * Builder#containsMetricName(String)} before calling {@link - * Builder#metricSnapshot(MetricSnapshot)}. + * @throws IllegalArgumentException if snapshots contain conflicting metric types (same name but + * different metric types like Counter vs Gauge), or if two HistogramSnapshots share a name + * but differ in gauge histogram vs classic histogram. */ public MetricSnapshots(Collection snapshots) { List list = new ArrayList<>(snapshots); list.sort(comparing(s -> s.getMetadata().getPrometheusName())); - for (int i = 0; i < snapshots.size() - 1; i++) { - if (list.get(i) - .getMetadata() - .getPrometheusName() - .equals(list.get(i + 1).getMetadata().getPrometheusName())) { - throw new IllegalArgumentException( - list.get(i).getMetadata().getPrometheusName() + ": duplicate metric name"); + + // Validate no conflicting metric types + for (int i = 0; i < list.size() - 1; i++) { + String name1 = list.get(i).getMetadata().getPrometheusName(); + String name2 = list.get(i + 1).getMetadata().getPrometheusName(); + + if (name1.equals(name2)) { + MetricSnapshot s1 = list.get(i); + MetricSnapshot s2 = list.get(i + 1); + Class type1 = s1.getClass(); + Class type2 = s2.getClass(); + + if (!type1.equals(type2)) { + throw new IllegalArgumentException( + name1 + + ": conflicting metric types: " + + type1.getSimpleName() + + " and " + + type2.getSimpleName()); + } + + // HistogramSnapshot: gauge histogram vs classic histogram are semantically different + if (s1 instanceof HistogramSnapshot) { + HistogramSnapshot h1 = (HistogramSnapshot) s1; + HistogramSnapshot h2 = (HistogramSnapshot) s2; + if (h1.isGaugeHistogram() != h2.isGaugeHistogram()) { + throw new IllegalArgumentException( + name1 + ": conflicting histogram types: gauge histogram and classic histogram"); + } + } } } + this.snapshots = unmodifiableList(list); } diff --git a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/PrometheusNaming.java b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/PrometheusNaming.java index 5cb1604d1..4f766fdad 100644 --- a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/PrometheusNaming.java +++ b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/PrometheusNaming.java @@ -1,6 +1,12 @@ package io.prometheus.metrics.model.snapshots; -import java.util.regex.Pattern; +import static java.lang.Character.MAX_CODE_POINT; +import static java.lang.Character.MAX_LOW_SURROGATE; +import static java.lang.Character.MIN_HIGH_SURROGATE; + +import io.prometheus.metrics.config.EscapingScheme; +import java.nio.charset.StandardCharsets; +import javax.annotation.Nullable; /** * Utility for Prometheus Metric and Label naming. @@ -11,16 +17,6 @@ */ public class PrometheusNaming { - /** Legal characters for metric names, including dot. */ - private static final Pattern METRIC_NAME_PATTERN = - Pattern.compile("^[a-zA-Z_.:][a-zA-Z0-9_.:]*$"); - - /** Legal characters for label names, including dot. */ - private static final Pattern LABEL_NAME_PATTERN = Pattern.compile("^[a-zA-Z_.][a-zA-Z0-9_.]*$"); - - /** Legal characters for unit names, including dot. */ - private static final Pattern UNIT_NAME_PATTERN = Pattern.compile("^[a-zA-Z0-9_.:]+$"); - /** * According to OpenMetrics {@code _count} and {@code _sum} (and {@code _gcount}, {@code _gsum}) * should also be reserved metric name suffixes. However, popular instrumentation libraries have @@ -31,7 +27,7 @@ public class PrometheusNaming { *

  • OpenTelemetry: {@code process_runtime_jvm_buffer_count} * * - * We do not treat {@code _count} and {@code _sum} as reserved suffixes here for compatibility + *

    We do not treat {@code _count} and {@code _sum} as reserved suffixes here for compatibility * with these libraries. However, there is a risk of name conflict if someone creates a gauge * named {@code my_data_count} and a histogram or summary named {@code my_data}, because the * histogram or summary will implicitly have a sample named {@code my_data_count}. @@ -45,13 +41,15 @@ public class PrometheusNaming { * Test if a metric name is valid. Rules: * *

    * - * If a metric has a {@link Unit}, the metric name SHOULD end with the unit as a suffix. Note that - * OpenMetrics requires metric names to have their unit as - * suffix, and we implement this in {@code prometheus-metrics-core}. However, {@code + *

    If a metric has a {@link Unit}, the metric name SHOULD end with the unit as a suffix. Note + * that OpenMetrics requires metric names to have their unit + * as suffix, and we implement this in {@code prometheus-metrics-core}. However, {@code * prometheus-metrics-model} does not enforce Unit suffixes. * *

    Example: If you create a Counter for a processing time with Unit {@link Unit#SECONDS @@ -70,26 +68,79 @@ public static boolean isValidMetricName(String name) { * *

    The name is valid if the error message is {@code null}. */ + @Nullable public static String validateMetricName(String name) { for (String reservedSuffix : RESERVED_METRIC_NAME_SUFFIXES) { if (name.endsWith(reservedSuffix)) { return "The metric name must not include the '" + reservedSuffix + "' suffix."; } } - if (!METRIC_NAME_PATTERN.matcher(name).matches()) { - return "The metric name contains unsupported characters"; + if (isValidUtf8(name)) { + return null; } - return null; + return "The metric name contains unsupported characters"; + } + + public static boolean isValidLegacyMetricName(String name) { + if (name.isEmpty()) { + return false; + } + // First character must be [a-zA-Z_:] + char first = name.charAt(0); + if (!((first >= 'a' && first <= 'z') + || (first >= 'A' && first <= 'Z') + || first == '_' + || first == ':')) { + return false; + } + // Remaining characters must be [a-zA-Z0-9_:] + for (int i = 1; i < name.length(); i++) { + char c = name.charAt(i); + if (!((c >= 'a' && c <= 'z') + || (c >= 'A' && c <= 'Z') + || (c >= '0' && c <= '9') + || c == '_' + || c == ':')) { + return false; + } + } + return true; } public static boolean isValidLabelName(String name) { - return LABEL_NAME_PATTERN.matcher(name).matches() + return isValidUtf8(name) && !(name.startsWith("__") || name.startsWith("._") || name.startsWith("..") || name.startsWith("_.")); } + private static boolean isValidUtf8(String name) { + return !name.isEmpty() && StandardCharsets.UTF_8.newEncoder().canEncode(name); + } + + public static boolean isValidLegacyLabelName(String name) { + if (name.isEmpty()) { + return false; + } + // First character must be [a-zA-Z_] + char first = name.charAt(0); + if (!((first >= 'a' && first <= 'z') || (first >= 'A' && first <= 'Z') || first == '_')) { + return false; + } + // Remaining characters must be [a-zA-Z0-9_] + for (int i = 1; i < name.length(); i++) { + char c = name.charAt(i); + if (!((c >= 'a' && c <= 'z') + || (c >= 'A' && c <= 'Z') + || (c >= '0' && c <= '9') + || c == '_')) { + return false; + } + } + return true; + } + /** * Units may not have illegal characters, and they may not end with a reserved suffix like * 'total'. @@ -99,6 +150,7 @@ public static boolean isValidUnitName(String name) { } /** Same as {@link #isValidUnitName(String)} but returns an error message. */ + @Nullable public static String validateUnitName(String name) { if (name.isEmpty()) { return "The unit name must not be empty."; @@ -109,8 +161,17 @@ public static String validateUnitName(String name) { return suffixName + " is a reserved suffix in Prometheus"; } } - if (!UNIT_NAME_PATTERN.matcher(name).matches()) { - return "The unit name contains unsupported characters"; + // Check if all characters are [a-zA-Z0-9_.:]+ + for (int i = 0; i < name.length(); i++) { + char c = name.charAt(i); + if (!((c >= 'a' && c <= 'z') + || (c >= 'A' && c <= 'Z') + || (c >= '0' && c <= '9') + || c == '_' + || c == '.' + || c == ':')) { + return "The unit name contains unsupported characters"; + } } return null; } @@ -124,7 +185,7 @@ public static String validateUnitName(String name) { * @return the name with dots replaced by underscores. */ public static String prometheusName(String name) { - return name.replace(".", "_"); + return escapeName(name, EscapingScheme.UNDERSCORE_ESCAPING); } /** @@ -135,7 +196,7 @@ public static String sanitizeMetricName(String metricName) { if (metricName.isEmpty()) { throw new IllegalArgumentException("Cannot convert an empty string to a valid metric name."); } - String sanitizedName = replaceIllegalCharsInMetricName(metricName); + String sanitizedName = metricName; boolean modified = true; while (modified) { modified = false; @@ -177,7 +238,7 @@ public static String sanitizeLabelName(String labelName) { if (labelName.isEmpty()) { throw new IllegalArgumentException("Cannot convert an empty string to a valid label name."); } - String sanitizedName = replaceIllegalCharsInLabelName(labelName); + String sanitizedName = labelName; while (sanitizedName.startsWith("__") || sanitizedName.startsWith("_.") || sanitizedName.startsWith("._") @@ -188,8 +249,8 @@ public static String sanitizeLabelName(String labelName) { } /** - * Convert an arbitrary string to a name where {@link #isValidUnitName(String) - * isValidUnitName(name)} is true. + * Convert an arbitrary string to a name where {@link #validateUnitName(String)} is {@code null} + * (i.e. the name is valid). * * @throws IllegalArgumentException if the {@code unitName} cannot be converted, for example if * you call {@code sanitizeUnitName("total")} or {@code sanitizeUnitName("")}. @@ -226,16 +287,17 @@ public static String sanitizeUnitName(String unitName) { return sanitizedName; } - /** Returns a string that matches {@link #METRIC_NAME_PATTERN}. */ - private static String replaceIllegalCharsInMetricName(String name) { + /** Returns a string with only valid unit name characters [a-zA-Z0-9_.:]. */ + private static String replaceIllegalCharsInUnitName(String name) { int length = name.length(); char[] sanitized = new char[length]; for (int i = 0; i < length; i++) { char ch = name.charAt(i); - if (ch == '.' + if (ch == ':' + || ch == '.' || (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') - || (i > 0 && ch >= '0' && ch <= '9')) { + || (ch >= '0' && ch <= '9')) { sanitized[i] = ch; } else { sanitized[i] = '_'; @@ -244,40 +306,84 @@ private static String replaceIllegalCharsInMetricName(String name) { return new String(sanitized); } - /** Returns a string that matches {@link #LABEL_NAME_PATTERN}. */ - private static String replaceIllegalCharsInLabelName(String name) { - int length = name.length(); - char[] sanitized = new char[length]; - for (int i = 0; i < length; i++) { - char ch = name.charAt(i); - if (ch == '.' - || (ch >= 'a' && ch <= 'z') - || (ch >= 'A' && ch <= 'Z') - || (i > 0 && ch >= '0' && ch <= '9')) { - sanitized[i] = ch; - } else { - sanitized[i] = '_'; - } + /** + * Escapes the incoming name according to the provided escaping scheme. Depending on the rules of + * escaping, this may cause no change in the string that is returned (especially NO_ESCAPING, + * which by definition is a noop). This method does not do any validation of the name. + */ + public static String escapeName(String name, EscapingScheme scheme) { + if (name.isEmpty() || !needsEscaping(name, scheme)) { + return name; } - return new String(sanitized); - } - /** Returns a string that matches {@link #UNIT_NAME_PATTERN}. */ - private static String replaceIllegalCharsInUnitName(String name) { - int length = name.length(); - char[] sanitized = new char[length]; - for (int i = 0; i < length; i++) { - char ch = name.charAt(i); - if (ch == ':' - || ch == '.' - || (ch >= 'a' && ch <= 'z') - || (ch >= 'A' && ch <= 'Z') - || (ch >= '0' && ch <= '9')) { - sanitized[i] = ch; - } else { - sanitized[i] = '_'; - } + StringBuilder escaped = new StringBuilder(); + switch (scheme) { + case ALLOW_UTF8: + return name; + case UNDERSCORE_ESCAPING: + for (int i = 0; i < name.length(); ) { + int c = name.codePointAt(i); + if (isValidLegacyChar(c, i)) { + escaped.appendCodePoint(c); + } else { + escaped.append('_'); + } + i += Character.charCount(c); + } + return escaped.toString(); + case DOTS_ESCAPING: + // Do not early return for legacy valid names, we still escape underscores. + for (int i = 0; i < name.length(); ) { + int c = name.codePointAt(i); + if (c == '_') { + escaped.append("__"); + } else if (c == '.') { + escaped.append("_dot_"); + } else if (isValidLegacyChar(c, i)) { + escaped.appendCodePoint(c); + } else { + escaped.append("__"); + } + i += Character.charCount(c); + } + return escaped.toString(); + case VALUE_ENCODING_ESCAPING: + escaped.append("U__"); + for (int i = 0; i < name.length(); ) { + int c = name.codePointAt(i); + if (c == '_') { + escaped.append("__"); + } else if (isValidLegacyChar(c, i)) { + escaped.appendCodePoint(c); + } else if (!isValidUtf8Char(c)) { + escaped.append("_FFFD_"); + } else { + escaped.append('_'); + escaped.append(Integer.toHexString(c)); + escaped.append('_'); + } + i += Character.charCount(c); + } + return escaped.toString(); + default: + throw new IllegalArgumentException("Invalid escaping scheme " + scheme); } - return new String(sanitized); + } + + public static boolean needsEscaping(String name, EscapingScheme scheme) { + return !isValidLegacyMetricName(name) + || (scheme == EscapingScheme.DOTS_ESCAPING && (name.contains(".") || name.contains("_"))); + } + + static boolean isValidLegacyChar(int c, int i) { + return (c >= 'a' && c <= 'z') + || (c >= 'A' && c <= 'Z') + || c == '_' + || c == ':' + || (c >= '0' && c <= '9' && i > 0); + } + + private static boolean isValidUtf8Char(int c) { + return (0 <= c && c < MIN_HIGH_SURROGATE) || (MAX_LOW_SURROGATE < c && c <= MAX_CODE_POINT); } } diff --git a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/SnapshotEscaper.java b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/SnapshotEscaper.java new file mode 100644 index 000000000..422b36ee0 --- /dev/null +++ b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/SnapshotEscaper.java @@ -0,0 +1,128 @@ +package io.prometheus.metrics.model.snapshots; + +import io.prometheus.metrics.config.EscapingScheme; +import java.util.ArrayList; +import java.util.List; +import javax.annotation.Nullable; + +public class SnapshotEscaper { + + private SnapshotEscaper() {} + + /** Escapes the given metric names and labels with the given escaping scheme. */ + public static MetricSnapshot escapeMetricSnapshot(MetricSnapshot v, EscapingScheme scheme) { + if (scheme == EscapingScheme.ALLOW_UTF8 || scheme == EscapingScheme.UNDERSCORE_ESCAPING) { + // we re-use the prometheus name for underscore escaping as an optimization + return v; + } + + List outDataPoints = new ArrayList<>(); + + for (DataPointSnapshot d : v.getDataPoints()) { + if (snapshotNeedsEscaping(d, scheme)) { + outDataPoints.add(d.escape(scheme)); + } else { + outDataPoints.add(d); + } + } + + return v.escape(scheme, outDataPoints); + } + + static boolean snapshotNeedsEscaping(DataPointSnapshot d, EscapingScheme scheme) { + Labels labels = d.getLabels(); + if (labelsNeedsEscaping(labels, scheme)) { + return true; + } + if (d instanceof SummarySnapshot.SummaryDataPointSnapshot) { + return exemplarsNeedsEscaping( + ((SummarySnapshot.SummaryDataPointSnapshot) d).getExemplars(), scheme); + } + if (d instanceof HistogramSnapshot.HistogramDataPointSnapshot) { + return exemplarsNeedsEscaping( + ((HistogramSnapshot.HistogramDataPointSnapshot) d).getExemplars(), scheme); + } + if (d instanceof CounterSnapshot.CounterDataPointSnapshot) { + return exemplarNeedsEscaping( + ((CounterSnapshot.CounterDataPointSnapshot) d).getExemplar(), scheme); + } + if (d instanceof UnknownSnapshot.UnknownDataPointSnapshot) { + return exemplarNeedsEscaping( + ((UnknownSnapshot.UnknownDataPointSnapshot) d).getExemplar(), scheme); + } + if (d instanceof GaugeSnapshot.GaugeDataPointSnapshot) { + return exemplarNeedsEscaping( + ((GaugeSnapshot.GaugeDataPointSnapshot) d).getExemplar(), scheme); + } + + return false; + } + + private static boolean labelsNeedsEscaping(Labels labels, EscapingScheme scheme) { + for (Label l : labels) { + if (PrometheusNaming.needsEscaping(l.getName(), scheme)) { + return true; + } + } + return false; + } + + private static boolean exemplarNeedsEscaping(@Nullable Exemplar exemplar, EscapingScheme scheme) { + return exemplar != null && labelsNeedsEscaping(exemplar.getLabels(), scheme); + } + + private static boolean exemplarsNeedsEscaping(Exemplars exemplars, EscapingScheme scheme) { + for (Exemplar exemplar : exemplars) { + if (labelsNeedsEscaping(exemplar.getLabels(), scheme)) { + return true; + } + } + return false; + } + + public static String getSnapshotLabelName(Labels labels, int index, EscapingScheme scheme) { + if (scheme == EscapingScheme.UNDERSCORE_ESCAPING) { + return labels.getPrometheusName(index); + } else { + return labels.getName(index); + } + } + + public static String getMetadataName(MetricMetadata metadata, EscapingScheme scheme) { + if (scheme == EscapingScheme.UNDERSCORE_ESCAPING) { + return metadata.getPrometheusName(); + } else { + return metadata.getName(); + } + } + + public static Labels escapeLabels(Labels labels, EscapingScheme scheme) { + Labels.Builder outLabelsBuilder = Labels.builder(); + + for (Label l : labels) { + outLabelsBuilder.label(PrometheusNaming.escapeName(l.getName(), scheme), l.getValue()); + } + + return outLabelsBuilder.build(); + } + + public static Exemplars escapeExemplars(Exemplars exemplars, EscapingScheme scheme) { + List escapedExemplars = new ArrayList<>(exemplars.size()); + for (Exemplar exemplar : exemplars) { + escapedExemplars.add(escapeExemplar(exemplar, scheme)); + } + return Exemplars.of(escapedExemplars); + } + + @Nullable + public static Exemplar escapeExemplar(@Nullable Exemplar exemplar, EscapingScheme scheme) { + if (exemplar == null) { + return null; + } + return Exemplar.builder() + .labels(escapeLabels(exemplar.getLabels(), scheme)) + .timestampMillis(exemplar.getTimestampMillis()) + .value(exemplar.getValue()) + .build(); + } +} diff --git a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/StateSetSnapshot.java b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/StateSetSnapshot.java index e49a9b27d..7ca7f36d1 100644 --- a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/StateSetSnapshot.java +++ b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/StateSetSnapshot.java @@ -1,5 +1,6 @@ package io.prometheus.metrics.model.snapshots; +import io.prometheus.metrics.config.EscapingScheme; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -7,6 +8,7 @@ import java.util.Iterator; import java.util.List; import java.util.stream.Stream; +import javax.annotation.Nullable; /** Immutable snapshot of a StateSet metric. */ public final class StateSetSnapshot extends MetricSnapshot { @@ -19,10 +21,15 @@ public final class StateSetSnapshot extends MetricSnapshot { * @param data the constructor will create a sorted copy of the collection. */ public StateSetSnapshot(MetricMetadata metadata, Collection data) { - super(metadata, data); + this(metadata, data, false); validate(); } + private StateSetSnapshot( + MetricMetadata metadata, Collection data, boolean internal) { + super(metadata, data, internal); + } + private void validate() { if (getMetadata().hasUnit()) { throw new IllegalArgumentException("An state set metric cannot have a unit."); @@ -41,10 +48,20 @@ public List getDataPoints() { return (List) dataPoints; } + @SuppressWarnings("unchecked") + @Override + MetricSnapshot escape( + EscapingScheme escapingScheme, List dataPointSnapshots) { + return new StateSetSnapshot( + getMetadata().escape(escapingScheme), + (List) dataPointSnapshots, + true); + } + public static class StateSetDataPointSnapshot extends DataPointSnapshot implements Iterable { - private final String[] names; - private final boolean[] values; + final String[] names; + final boolean[] values; /** * To create a new {@link StateSetDataPointSnapshot}, you can either call the constructor @@ -67,19 +84,34 @@ public StateSetDataPointSnapshot(String[] names, boolean[] values, Labels labels */ public StateSetDataPointSnapshot( String[] names, boolean[] values, Labels labels, long scrapeTimestampMillis) { - super(labels, 0L, scrapeTimestampMillis); - if (names.length == 0) { - throw new IllegalArgumentException("StateSet must have at least one state."); - } - if (names.length != values.length) { - throw new IllegalArgumentException("names[] and values[] must have the same length"); + this(names, values, labels, scrapeTimestampMillis, false); + } + + private StateSetDataPointSnapshot( + String[] names, + boolean[] values, + Labels labels, + long scrapeTimestampMillis, + boolean internal) { + super(labels, 0L, scrapeTimestampMillis, false); + if (internal) { + this.names = names; + this.values = values; + } else { + if (names.length == 0) { + throw new IllegalArgumentException("StateSet must have at least one state."); + } + if (names.length != values.length) { + throw new IllegalArgumentException("names[] and values[] must have the same length"); + } + + String[] namesCopy = Arrays.copyOf(names, names.length); + boolean[] valuesCopy = Arrays.copyOf(values, names.length); + sort(namesCopy, valuesCopy); + this.names = namesCopy; + this.values = valuesCopy; + validate(); } - String[] namesCopy = Arrays.copyOf(names, names.length); - boolean[] valuesCopy = Arrays.copyOf(values, names.length); - sort(namesCopy, valuesCopy); - this.names = namesCopy; - this.values = valuesCopy; - validate(); } public int size() { @@ -105,6 +137,16 @@ private void validate() { } } + @Override + DataPointSnapshot escape(EscapingScheme escapingScheme) { + return new StateSetSnapshot.StateSetDataPointSnapshot( + names, + values, + SnapshotEscaper.escapeLabels(getLabels(), escapingScheme), + getScrapeTimestampMillis(), + true); + } + private List asList() { List result = new ArrayList<>(size()); for (int i = 0; i < names.length; i++) { @@ -212,7 +254,7 @@ public Builder dataPoint(StateSetDataPointSnapshot dataPoint) { } @Override - public Builder unit(Unit unit) { + public Builder unit(@Nullable Unit unit) { throw new IllegalArgumentException("StateSet metric cannot have a unit."); } diff --git a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/SummarySnapshot.java b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/SummarySnapshot.java index ac83ef7ae..1b8dbc2e9 100644 --- a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/SummarySnapshot.java +++ b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/SummarySnapshot.java @@ -1,5 +1,6 @@ package io.prometheus.metrics.model.snapshots; +import io.prometheus.metrics.config.EscapingScheme; import java.util.ArrayList; import java.util.Collection; import java.util.List; @@ -15,7 +16,12 @@ public final class SummarySnapshot extends MetricSnapshot { * @param data the constructor will create a sorted copy of the collection. */ public SummarySnapshot(MetricMetadata metadata, Collection data) { - super(metadata, data); + this(metadata, data, false); + } + + private SummarySnapshot( + MetricMetadata metadata, Collection data, boolean internal) { + super(metadata, data, internal); } @SuppressWarnings("unchecked") @@ -24,6 +30,16 @@ public List getDataPoints() { return (List) dataPoints; } + @SuppressWarnings("unchecked") + @Override + MetricSnapshot escape( + EscapingScheme escapingScheme, List dataPointSnapshots) { + return new SummarySnapshot( + getMetadata().escape(escapingScheme), + (List) dataPointSnapshots, + true); + } + public static final class SummaryDataPointSnapshot extends DistributionDataPointSnapshot { private final Quantiles quantiles; @@ -67,11 +83,31 @@ public SummaryDataPointSnapshot( Exemplars exemplars, long createdTimestampMillis, long scrapeTimestampMillis) { - super(count, sum, exemplars, labels, createdTimestampMillis, scrapeTimestampMillis); - this.quantiles = quantiles; + this( + count, + sum, + quantiles, + labels, + exemplars, + createdTimestampMillis, + scrapeTimestampMillis, + false); validate(); } + private SummaryDataPointSnapshot( + long count, + double sum, + Quantiles quantiles, + Labels labels, + Exemplars exemplars, + long createdTimestampMillis, + long scrapeTimestampMillis, + boolean internal) { + super(count, sum, exemplars, labels, createdTimestampMillis, scrapeTimestampMillis, internal); + this.quantiles = quantiles; + } + public Quantiles getQuantiles() { return quantiles; } @@ -87,6 +123,19 @@ private void validate() { } } + @Override + DataPointSnapshot escape(EscapingScheme escapingScheme) { + return new SummarySnapshot.SummaryDataPointSnapshot( + getCount(), + getSum(), + getQuantiles(), + SnapshotEscaper.escapeLabels(getLabels(), escapingScheme), + SnapshotEscaper.escapeExemplars(getExemplars(), escapingScheme), + getCreatedTimestampMillis(), + getScrapeTimestampMillis(), + true); + } + public static Builder builder() { return new Builder(); } diff --git a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/Unit.java b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/Unit.java index 31a9524e7..6e652af13 100644 --- a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/Unit.java +++ b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/Unit.java @@ -9,8 +9,8 @@ * new Unit("myUnit"); *

  • * - * Note that in Prometheus, units are largely based on SI base units (seconds, bytes, joules, grams, - * meters, ratio, volts, amperes, and Celsius). + *

    Note that in Prometheus, units are largely based on SI base units (seconds, bytes, joules, + * grams, meters, ratio, volts, amperes, and Celsius). */ public final class Unit { diff --git a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/UnknownSnapshot.java b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/UnknownSnapshot.java index 120841bb5..09574d6cd 100644 --- a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/UnknownSnapshot.java +++ b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/UnknownSnapshot.java @@ -1,8 +1,10 @@ package io.prometheus.metrics.model.snapshots; +import io.prometheus.metrics.config.EscapingScheme; import java.util.ArrayList; import java.util.Collection; import java.util.List; +import javax.annotation.Nullable; /** Immutable snapshot of an Unknown (Untyped) metric. */ public final class UnknownSnapshot extends MetricSnapshot { @@ -16,7 +18,12 @@ public final class UnknownSnapshot extends MetricSnapshot { * @param data the constructor will create a sorted copy of the collection. */ public UnknownSnapshot(MetricMetadata metadata, Collection data) { - super(metadata, data); + this(metadata, data, false); + } + + private UnknownSnapshot( + MetricMetadata metadata, Collection data, boolean internal) { + super(metadata, data, internal); } @SuppressWarnings("unchecked") @@ -25,10 +32,20 @@ public List getDataPoints() { return (List) dataPoints; } + @SuppressWarnings("unchecked") + @Override + MetricSnapshot escape( + EscapingScheme escapingScheme, List dataPointSnapshots) { + return new UnknownSnapshot( + getMetadata().escape(escapingScheme), + (List) dataPointSnapshots, + true); + } + public static final class UnknownDataPointSnapshot extends DataPointSnapshot { private final double value; - private final Exemplar exemplar; // may be null + @Nullable private final Exemplar exemplar; /** * To create a new {@link UnknownDataPointSnapshot}, you can either call the constructor @@ -38,7 +55,7 @@ public static final class UnknownDataPointSnapshot extends DataPointSnapshot { * @param labels must not be null. Use {@link Labels#EMPTY} if there are no labels. * @param exemplar may be null. */ - public UnknownDataPointSnapshot(double value, Labels labels, Exemplar exemplar) { + public UnknownDataPointSnapshot(double value, Labels labels, @Nullable Exemplar exemplar) { this(value, labels, exemplar, 0); } @@ -48,8 +65,17 @@ public UnknownDataPointSnapshot(double value, Labels labels, Exemplar exemplar) * mirroring metrics with given timestamps from other metric sources. */ public UnknownDataPointSnapshot( - double value, Labels labels, Exemplar exemplar, long scrapeTimestampMillis) { - super(labels, 0L, scrapeTimestampMillis); + double value, Labels labels, @Nullable Exemplar exemplar, long scrapeTimestampMillis) { + this(value, labels, exemplar, scrapeTimestampMillis, false); + } + + private UnknownDataPointSnapshot( + double value, + Labels labels, + @Nullable Exemplar exemplar, + long scrapeTimestampMillis, + boolean internal) { + super(labels, 0L, scrapeTimestampMillis, internal); this.value = value; this.exemplar = exemplar; } @@ -58,7 +84,7 @@ public double getValue() { return value; } - /** May return {@code null}. */ + @Nullable public Exemplar getExemplar() { return exemplar; } @@ -67,10 +93,20 @@ public static Builder builder() { return new Builder(); } + @Override + DataPointSnapshot escape(EscapingScheme escapingScheme) { + return new UnknownDataPointSnapshot( + value, + SnapshotEscaper.escapeLabels(getLabels(), escapingScheme), + SnapshotEscaper.escapeExemplar(exemplar, escapingScheme), + getScrapeTimestampMillis(), + true); + } + public static class Builder extends DataPointSnapshot.Builder { - private Exemplar exemplar = null; - private Double value = null; + @Nullable private Exemplar exemplar = null; + @Nullable private Double value = null; private Builder() {} @@ -81,7 +117,7 @@ public Builder value(double value) { } /** Optional */ - public Builder exemplar(Exemplar exemplar) { + public Builder exemplar(@Nullable Exemplar exemplar) { this.exemplar = exemplar; return this; } diff --git a/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/registry/MetricNameFilterTest.java b/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/registry/MetricNameFilterTest.java index 0dc9cdee1..8f554bdc5 100644 --- a/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/registry/MetricNameFilterTest.java +++ b/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/registry/MetricNameFilterTest.java @@ -13,7 +13,7 @@ class MetricNameFilterTest { private final PrometheusRegistry registry = new PrometheusRegistry(); @Test - public void testCounter() { + void testCounter() { registry.register( () -> CounterSnapshot.builder() diff --git a/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/registry/MultiCollectorNameFilterTest.java b/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/registry/MultiCollectorNameFilterTest.java index b8810316f..48be456a6 100644 --- a/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/registry/MultiCollectorNameFilterTest.java +++ b/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/registry/MultiCollectorNameFilterTest.java @@ -18,7 +18,7 @@ class MultiCollectorNameFilterTest { private static class Registry extends PrometheusRegistry { private final AtomicBoolean collectCalled = new AtomicBoolean(); - public Registry(List prometheusNames) { + private Registry(List prometheusNames) { register( new MultiCollector() { @Override @@ -45,13 +45,13 @@ public List getPrometheusNames() { }); } - public boolean collectCalled() { + private boolean collectCalled() { return collectCalled.get(); } } @Test - public void testPartialFilter() { + void testPartialFilter() { Registry registry = new Registry(Collections.emptyList()); MetricSnapshots snapshots = registry.scrape(name -> name.equals("counter_1")); assertThat(registry.collectCalled()).isTrue(); @@ -60,7 +60,7 @@ public void testPartialFilter() { } @Test - public void testPartialFilterWithPrometheusNames() { + void testPartialFilterWithPrometheusNames() { Registry registry = new Registry(Arrays.asList("counter_1", "gauge_2")); MetricSnapshots snapshots = registry.scrape(name -> name.equals("counter_1")); @@ -70,7 +70,7 @@ public void testPartialFilterWithPrometheusNames() { } @Test - public void testCompleteFilter_CollectCalled() { + void testCompleteFilter_CollectCalled() { Registry registry = new Registry(Collections.emptyList()); MetricSnapshots snapshots = registry.scrape(name -> !name.equals("counter_1") && !name.equals("gauge_2")); @@ -79,7 +79,7 @@ public void testCompleteFilter_CollectCalled() { } @Test - public void testCompleteFilter_CollectNotCalled() { + void testCompleteFilter_CollectNotCalled() { Registry registry = new Registry(Arrays.asList("counter_1", "gauge_2")); MetricSnapshots snapshots = registry.scrape(name -> !name.equals("counter_1") && !name.equals("gauge_2")); diff --git a/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/registry/OpenTelemetryExporterRegistryCompatibilityTest.java b/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/registry/OpenTelemetryExporterRegistryCompatibilityTest.java new file mode 100644 index 000000000..166b374b8 --- /dev/null +++ b/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/registry/OpenTelemetryExporterRegistryCompatibilityTest.java @@ -0,0 +1,116 @@ +package io.prometheus.metrics.model.registry; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatCode; + +import io.prometheus.metrics.model.snapshots.CounterSnapshot; +import io.prometheus.metrics.model.snapshots.GaugeSnapshot; +import io.prometheus.metrics.model.snapshots.MetricSnapshot; +import io.prometheus.metrics.model.snapshots.MetricSnapshots; +import java.util.Collections; +import org.junit.jupiter.api.Test; + +/** + * Tests that use the Prometheus registry in the same way as the OpenTelemetry Java SDK Prometheus + * exporter ({@code io.opentelemetry.exporter.prometheus}). The SDK's {@code PrometheusMetricReader} + * implements {@link MultiCollector} with default implementations for all optional methods: {@link + * MultiCollector#getPrometheusNames()} returns an empty list, and {@link + * MultiCollector#getMetricType(String)}, {@link MultiCollector#getLabelNames(String)}, and {@link + * MultiCollector#getMetadata(String)} return null. This test suite ensures that registration, + * scrape, and unregister continue to work for that usage pattern and that a shared registry with + * both SDK-style and validated collectors behaves correctly. + */ +class OpenTelemetryExporterRegistryCompatibilityTest { + + /** + * A MultiCollector that mimics the OpenTelemetry Java SDK's PrometheusMetricReader: it does not + * override getPrometheusNames() (empty list), getMetricType(String), getLabelNames(String), or + * getMetadata(String) (all null). Only collect() is implemented and returns MetricSnapshots. + */ + private static final MultiCollector OTEL_STYLE_MULTI_COLLECTOR = + new MultiCollector() { + @Override + public MetricSnapshots collect() { + return new MetricSnapshots( + CounterSnapshot.builder() + .name("otel_metric") + .help("A metric produced by an OTel-style converter") + .dataPoint(CounterSnapshot.CounterDataPointSnapshot.builder().value(42.0).build()) + .build()); + } + }; + + @Test + void registerOtelStyleMultiCollector_succeeds() { + PrometheusRegistry registry = new PrometheusRegistry(); + + assertThatCode(() -> registry.register(OTEL_STYLE_MULTI_COLLECTOR)).doesNotThrowAnyException(); + } + + @Test + void scrape_afterRegisteringOtelStyleMultiCollector_returnsSnapshotsFromCollector() { + PrometheusRegistry registry = new PrometheusRegistry(); + registry.register(OTEL_STYLE_MULTI_COLLECTOR); + + MetricSnapshots snapshots = registry.scrape(); + + assertThat(snapshots).hasSize(1); + MetricSnapshot snapshot = snapshots.get(0); + assertThat(snapshot.getMetadata().getPrometheusName()).isEqualTo("otel_metric"); + } + + @Test + void unregisterOtelStyleMultiCollector_succeedsAndScrapeNoLongerIncludesIt() { + PrometheusRegistry registry = new PrometheusRegistry(); + registry.register(OTEL_STYLE_MULTI_COLLECTOR); + + assertThat(registry.scrape()).hasSize(1); + + assertThatCode(() -> registry.unregister(OTEL_STYLE_MULTI_COLLECTOR)) + .doesNotThrowAnyException(); + + assertThat(registry.scrape()).isEmpty(); + } + + @Test + void sharedRegistry_otelStyleMultiCollectorAndValidatedCollector_bothParticipateInScrape() { + PrometheusRegistry registry = new PrometheusRegistry(); + + Collector validatedCollector = + new Collector() { + @Override + public MetricSnapshot collect() { + return GaugeSnapshot.builder().name("app_gauge").help("App gauge").build(); + } + + @Override + public String getPrometheusName() { + return "app_gauge"; + } + + @Override + public MetricType getMetricType() { + return MetricType.GAUGE; + } + + @Override + public java.util.Set getLabelNames() { + return Collections.emptySet(); + } + }; + + registry.register(validatedCollector); + registry.register(OTEL_STYLE_MULTI_COLLECTOR); + + MetricSnapshots snapshots = registry.scrape(); + + assertThat(snapshots).hasSize(2); + assertThat(snapshots) + .extracting(s -> s.getMetadata().getPrometheusName()) + .containsExactlyInAnyOrder("app_gauge", "otel_metric"); + + registry.unregister(OTEL_STYLE_MULTI_COLLECTOR); + assertThat(registry.scrape()).hasSize(1); + assertThat(registry.scrape().get(0).getMetadata().getPrometheusName()).isEqualTo("app_gauge"); + } +} diff --git a/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/registry/PrometheusRegistryTest.java b/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/registry/PrometheusRegistryTest.java index 405321d29..90a04934e 100644 --- a/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/registry/PrometheusRegistryTest.java +++ b/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/registry/PrometheusRegistryTest.java @@ -1,15 +1,19 @@ package io.prometheus.metrics.model.registry; +import static java.util.Arrays.asList; import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatExceptionOfType; -import static org.assertj.core.api.Assertions.fail; +import static org.assertj.core.api.Assertions.assertThatCode; +import static org.assertj.core.api.Assertions.assertThatThrownBy; import io.prometheus.metrics.model.snapshots.CounterSnapshot; import io.prometheus.metrics.model.snapshots.GaugeSnapshot; +import io.prometheus.metrics.model.snapshots.MetricMetadata; import io.prometheus.metrics.model.snapshots.MetricSnapshot; import io.prometheus.metrics.model.snapshots.MetricSnapshots; import java.util.Arrays; +import java.util.HashSet; import java.util.List; +import java.util.Set; import org.junit.jupiter.api.Test; class PrometheusRegistryTest { @@ -82,34 +86,429 @@ public List getPrometheusNames() { }; @Test - public void registerNoName() { + void register_sameInstanceTwice_notAllowed() { PrometheusRegistry registry = new PrometheusRegistry(); - // If the collector does not have a name at registration time, there is no conflict during - // registration. - registry.register(noName); + registry.register(noName); - // However, at scrape time the collector has to provide a metric name, and then we'll get a - // duplicate name error. - try { - registry.scrape(); - } catch (IllegalStateException e) { - assertThat(e.getMessage().contains("duplicate") && e.getMessage().contains("no_name_gauge")) - .isTrue(); - return; - } - fail("Expected duplicate name exception"); + + assertThatThrownBy(() -> registry.register(noName)) + .hasMessageContaining("Collector instance is already registered"); } @Test - public void registerDuplicateName() { + void register_duplicateName_differentTypes_notAllowed() { PrometheusRegistry registry = new PrometheusRegistry(); + + Collector counterA1 = + new Collector() { + @Override + public MetricSnapshot collect() { + return CounterSnapshot.builder().name("counter_a").build(); + } + + @Override + public String getPrometheusName() { + return "counter_a"; + } + + @Override + public MetricType getMetricType() { + return MetricType.COUNTER; + } + }; + + Collector gaugeA1 = + new Collector() { + @Override + public MetricSnapshot collect() { + return GaugeSnapshot.builder().name("counter_a").build(); + } + + @Override + public String getPrometheusName() { + return "counter_a"; + } + + @Override + public MetricType getMetricType() { + return MetricType.GAUGE; + } + }; + registry.register(counterA1); - assertThatExceptionOfType(IllegalStateException.class) - .isThrownBy(() -> registry.register(counterA2)); + + assertThatThrownBy(() -> registry.register(gaugeA1)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("Conflicting metric types"); + } + + @Test + void register_sameName_sameType_differentLabelSchemas_allowed() { + PrometheusRegistry registry = new PrometheusRegistry(); + + Collector counterWithPathLabel = + new Collector() { + @Override + public MetricSnapshot collect() { + return CounterSnapshot.builder().name("requests_total").build(); + } + + @Override + public String getPrometheusName() { + return "requests_total"; + } + + @Override + public MetricType getMetricType() { + return MetricType.COUNTER; + } + + @Override + public Set getLabelNames() { + return new HashSet<>(asList("path", "status")); + } + }; + + Collector counterWithRegionLabel = + new Collector() { + @Override + public MetricSnapshot collect() { + return CounterSnapshot.builder().name("requests_total").build(); + } + + @Override + public String getPrometheusName() { + return "requests_total"; + } + + @Override + public MetricType getMetricType() { + return MetricType.COUNTER; + } + + @Override + public Set getLabelNames() { + return new HashSet<>(asList("region")); + } + }; + + // Both collectors have same name and type, but different label schemas + // This should succeed + registry.register(counterWithPathLabel); + assertThatCode(() -> registry.register(counterWithRegionLabel)).doesNotThrowAnyException(); + } + + @Test + void register_sameName_sameType_sameLabelSchema_notAllowed() { + PrometheusRegistry registry = new PrometheusRegistry(); + + Collector counter1 = + new Collector() { + @Override + public MetricSnapshot collect() { + return CounterSnapshot.builder().name("requests_total").build(); + } + + @Override + public String getPrometheusName() { + return "requests_total"; + } + + @Override + public MetricType getMetricType() { + return MetricType.COUNTER; + } + + @Override + public Set getLabelNames() { + return new HashSet<>(asList("path", "status")); + } + }; + + Collector counter2 = + new Collector() { + @Override + public MetricSnapshot collect() { + return CounterSnapshot.builder().name("requests_total").build(); + } + + @Override + public String getPrometheusName() { + return "requests_total"; + } + + @Override + public MetricType getMetricType() { + return MetricType.COUNTER; + } + + @Override + public Set getLabelNames() { + return new HashSet<>(asList("path", "status")); + } + }; + + registry.register(counter1); + + // Second collector has same name, type, and label schema - should fail + assertThatThrownBy(() -> registry.register(counter2)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("duplicate metric name with identical label schema"); + } + + @Test + void register_duplicateLabelSchema_rollsBackCollectorOnFailure() { + PrometheusRegistry registry = new PrometheusRegistry(); + + Collector counter1 = + new Collector() { + @Override + public MetricSnapshot collect() { + return CounterSnapshot.builder().name("http_requests").build(); + } + + @Override + public String getPrometheusName() { + return "http_requests"; + } + + @Override + public MetricType getMetricType() { + return MetricType.COUNTER; + } + + @Override + public Set getLabelNames() { + return new HashSet<>(asList("path", "status")); + } + }; + + Collector counter2 = + new Collector() { + @Override + public MetricSnapshot collect() { + return CounterSnapshot.builder().name("http_requests").build(); + } + + @Override + public String getPrometheusName() { + return "http_requests"; + } + + @Override + public MetricType getMetricType() { + return MetricType.COUNTER; + } + + @Override + public Set getLabelNames() { + return new HashSet<>(asList("path", "status")); + } + }; + + registry.register(counter1); + + // Second collector has same name and label schema - registration fails during metadata + // validation. The failed collector must be rolled back (not present in the registry). + assertThatThrownBy(() -> registry.register(counter2)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("duplicate metric name with identical label schema"); + + // Only the first collector should be in the registry; counter2 was removed on rollback. + assertThat(registry.scrape().size()).isEqualTo(1); + } + + @Test + void register_metadataValidationFailure_rollsBackLabelSchema() { + PrometheusRegistry registry = new PrometheusRegistry(); + + Collector counterWithHelpOne = + new Collector() { + @Override + public MetricSnapshot collect() { + return CounterSnapshot.builder().name("requests").build(); + } + + @Override + public String getPrometheusName() { + return "requests"; + } + + @Override + public MetricType getMetricType() { + return MetricType.COUNTER; + } + + @Override + public Set getLabelNames() { + return new HashSet<>(asList("path")); + } + + @Override + public MetricMetadata getMetadata() { + return new MetricMetadata("requests", "First help", null); + } + }; + + Collector counterWithHelpTwo = + new Collector() { + @Override + public MetricSnapshot collect() { + return CounterSnapshot.builder().name("requests").build(); + } + + @Override + public String getPrometheusName() { + return "requests"; + } + + @Override + public MetricType getMetricType() { + return MetricType.COUNTER; + } + + @Override + public Set getLabelNames() { + return new HashSet<>(asList("status")); + } + + @Override + public MetricMetadata getMetadata() { + return new MetricMetadata("requests", "Second help", null); + } + }; + + Collector counterWithCorrectHelp = + new Collector() { + @Override + public MetricSnapshot collect() { + return CounterSnapshot.builder().name("requests").build(); + } + + @Override + public String getPrometheusName() { + return "requests"; + } + + @Override + public MetricType getMetricType() { + return MetricType.COUNTER; + } + + @Override + public Set getLabelNames() { + return new HashSet<>(asList("status")); + } + + @Override + public MetricMetadata getMetadata() { + return new MetricMetadata("requests", "First help", null); + } + }; + + registry.register(counterWithHelpOne); + + // Second collector has conflicting help - should fail and roll back its label schema + assertThatThrownBy(() -> registry.register(counterWithHelpTwo)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("Conflicting help strings"); + + // Third collector has same label schema as second (which failed), but correct help. + // If label schema wasn't rolled back, this would fail with "duplicate label schema". + // If rollback worked, this should succeed. + assertThatCode(() -> registry.register(counterWithCorrectHelp)).doesNotThrowAnyException(); + + // Verify both collectors are in the registry + assertThat(registry.scrape().size()).isEqualTo(2); + } + + @Test + void register_nullType_skipsValidation() { + PrometheusRegistry registry = new PrometheusRegistry(); + + // Collectors without getMetricType() skip registration-time validation. + // This allows legacy collectors to work without implementing all getters. + Collector legacyCollector1 = + new Collector() { + @Override + public MetricSnapshot collect() { + return CounterSnapshot.builder().name("legacy_metric").build(); + } + + @Override + public String getPrometheusName() { + return "legacy_metric"; + } + }; + + Collector legacyCollector2 = + new Collector() { + @Override + public MetricSnapshot collect() { + return GaugeSnapshot.builder().name("legacy_metric").build(); + } + + @Override + public String getPrometheusName() { + return "legacy_metric"; + } + }; + + // Both collectors can register successfully since validation is skipped + assertThatCode(() -> registry.register(legacyCollector1)).doesNotThrowAnyException(); + assertThatCode(() -> registry.register(legacyCollector2)).doesNotThrowAnyException(); } @Test - public void registerOk() { + void register_multiCollector_withTypeValidation() { + PrometheusRegistry registry = new PrometheusRegistry(); + + Collector counter = + new Collector() { + @Override + public MetricSnapshot collect() { + return CounterSnapshot.builder().name("shared_metric").build(); + } + + @Override + public String getPrometheusName() { + return "shared_metric"; + } + + @Override + public MetricType getMetricType() { + return MetricType.COUNTER; + } + }; + + MultiCollector multiWithGauge = + new MultiCollector() { + @Override + public MetricSnapshots collect() { + return new MetricSnapshots(GaugeSnapshot.builder().name("shared_metric").build()); + } + + @Override + public List getPrometheusNames() { + return asList("shared_metric"); + } + + @Override + public MetricType getMetricType(String prometheusName) { + return MetricType.GAUGE; + } + }; + + registry.register(counter); + + // MultiCollector tries to register a Gauge with the same name as existing Counter + assertThatThrownBy(() -> registry.register(multiWithGauge)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("Conflicting metric types"); + } + + @Test + void registerOk() { PrometheusRegistry registry = new PrometheusRegistry(); registry.register(counterA1); registry.register(counterB); @@ -127,15 +526,17 @@ public void registerOk() { } @Test - public void registerDuplicateMultiCollector() { + void registerDuplicateMultiCollector() { PrometheusRegistry registry = new PrometheusRegistry(); registry.register(multiCollector); - assertThatExceptionOfType(IllegalStateException.class) - .isThrownBy(() -> registry.register(multiCollector)); + // Registering the same instance twice should fail + assertThatThrownBy(() -> registry.register(multiCollector)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("already registered"); } @Test - public void registerOkMultiCollector() { + void registerOkMultiCollector() { PrometheusRegistry registry = new PrometheusRegistry(); registry.register(multiCollector); MetricSnapshots snapshots = registry.scrape(); @@ -147,7 +548,7 @@ public void registerOkMultiCollector() { } @Test - public void clearOk() { + void clearOk() { PrometheusRegistry registry = new PrometheusRegistry(); registry.register(counterA1); registry.register(counterB); @@ -157,4 +558,533 @@ public void clearOk() { registry.clear(); assertThat(registry.scrape().size()).isZero(); } + + @Test + void unregister_shouldRemoveLabelSchemaFromRegistrationInfo() { + PrometheusRegistry registry = new PrometheusRegistry(); + + Collector counterWithPathLabel = + new Collector() { + @Override + public MetricSnapshot collect() { + return CounterSnapshot.builder().name("requests_total").build(); + } + + @Override + public String getPrometheusName() { + return "requests_total"; + } + + @Override + public MetricType getMetricType() { + return MetricType.COUNTER; + } + + @Override + public Set getLabelNames() { + return new HashSet<>(asList("path", "status")); + } + }; + + Collector counterWithRegionLabel = + new Collector() { + @Override + public MetricSnapshot collect() { + return CounterSnapshot.builder().name("requests_total").build(); + } + + @Override + public String getPrometheusName() { + return "requests_total"; + } + + @Override + public MetricType getMetricType() { + return MetricType.COUNTER; + } + + @Override + public Set getLabelNames() { + return new HashSet<>(asList("region")); + } + }; + + Collector counterWithPathLabelAgain = + new Collector() { + @Override + public MetricSnapshot collect() { + return CounterSnapshot.builder().name("requests_total").build(); + } + + @Override + public String getPrometheusName() { + return "requests_total"; + } + + @Override + public MetricType getMetricType() { + return MetricType.COUNTER; + } + + @Override + public Set getLabelNames() { + return new HashSet<>(asList("path", "status")); + } + }; + + registry.register(counterWithPathLabel); + registry.register(counterWithRegionLabel); + + registry.unregister(counterWithPathLabel); + + assertThatCode(() -> registry.register(counterWithPathLabelAgain)).doesNotThrowAnyException(); + } + + @Test + void register_withEmptyLabelSets_shouldDetectDuplicates() { + PrometheusRegistry registry = new PrometheusRegistry(); + + Collector collector1 = + new Collector() { + @Override + public MetricSnapshot collect() { + return GaugeSnapshot.builder().name("requests").build(); + } + + @Override + public String getPrometheusName() { + return "requests"; + } + + @Override + public MetricType getMetricType() { + return MetricType.GAUGE; + } + + // getLabelNames() returns null by default + }; + + // Register another collector with same name and type, also no labels + Collector collector2 = + new Collector() { + @Override + public MetricSnapshot collect() { + return GaugeSnapshot.builder().name("requests").build(); + } + + @Override + public String getPrometheusName() { + return "requests"; + } + + @Override + public MetricType getMetricType() { + return MetricType.GAUGE; + } + + // getLabelNames() returns null by default + }; + + registry.register(collector1); + + assertThatThrownBy(() -> registry.register(collector2)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("duplicate metric name with identical label schema"); + } + + @Test + void register_withMixedNullAndEmptyLabelSets_shouldDetectDuplicates() { + PrometheusRegistry registry = new PrometheusRegistry(); + + Collector collector1 = + new Collector() { + @Override + public MetricSnapshot collect() { + return GaugeSnapshot.builder().name("requests").build(); + } + + @Override + public String getPrometheusName() { + return "requests"; + } + + @Override + public MetricType getMetricType() { + return MetricType.GAUGE; + } + + @Override + public Set getLabelNames() { + return new HashSet<>(); + } + }; + + Collector collector2 = + new Collector() { + @Override + public MetricSnapshot collect() { + return GaugeSnapshot.builder().name("requests").build(); + } + + @Override + public String getPrometheusName() { + return "requests"; + } + + @Override + public MetricType getMetricType() { + return MetricType.GAUGE; + } + + // getLabelNames() returns null by default + }; + + registry.register(collector1); + + // null and empty should be treated the same + assertThatThrownBy(() -> registry.register(collector2)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("duplicate metric name with identical label schema"); + } + + @Test + void register_sameName_differentHelp_notAllowed() { + PrometheusRegistry registry = new PrometheusRegistry(); + + Collector withHelpOne = + new Collector() { + @Override + public MetricSnapshot collect() { + return CounterSnapshot.builder().name("requests").help("First help").build(); + } + + @Override + public String getPrometheusName() { + return "requests"; + } + + @Override + public MetricType getMetricType() { + return MetricType.COUNTER; + } + + @Override + public Set getLabelNames() { + return new HashSet<>(asList("path")); + } + + @Override + public MetricMetadata getMetadata() { + return new MetricMetadata("requests", "First help", null); + } + }; + + Collector withHelpTwo = + new Collector() { + @Override + public MetricSnapshot collect() { + return CounterSnapshot.builder().name("requests").help("Second help").build(); + } + + @Override + public String getPrometheusName() { + return "requests"; + } + + @Override + public MetricType getMetricType() { + return MetricType.COUNTER; + } + + @Override + public Set getLabelNames() { + return new HashSet<>(asList("status")); + } + + @Override + public MetricMetadata getMetadata() { + return new MetricMetadata("requests", "Second help", null); + } + }; + + registry.register(withHelpOne); + assertThatThrownBy(() -> registry.register(withHelpTwo)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("Conflicting help strings"); + } + + @Test + void register_sameName_nullVsNonNullHelp_notAllowed() { + PrometheusRegistry registry = new PrometheusRegistry(); + + Collector withHelp = + new Collector() { + @Override + public MetricSnapshot collect() { + return CounterSnapshot.builder().name("requests").help("Total requests").build(); + } + + @Override + public String getPrometheusName() { + return "requests"; + } + + @Override + public MetricType getMetricType() { + return MetricType.COUNTER; + } + + @Override + public Set getLabelNames() { + return new HashSet<>(asList("path")); + } + + @Override + public MetricMetadata getMetadata() { + return new MetricMetadata("requests", "Total requests", null); + } + }; + + Collector withoutHelp = + new Collector() { + @Override + public MetricSnapshot collect() { + return CounterSnapshot.builder().name("requests").build(); + } + + @Override + public String getPrometheusName() { + return "requests"; + } + + @Override + public MetricType getMetricType() { + return MetricType.COUNTER; + } + + @Override + public Set getLabelNames() { + return new HashSet<>(asList("status")); + } + + @Override + public MetricMetadata getMetadata() { + return new MetricMetadata("requests", null, null); + } + }; + + registry.register(withHelp); + assertThatThrownBy(() -> registry.register(withoutHelp)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("Conflicting help strings"); + } + + @Test + void register_sameName_sameHelpAndUnit_allowed() { + PrometheusRegistry registry = new PrometheusRegistry(); + + Collector withPath = + new Collector() { + @Override + public MetricSnapshot collect() { + return CounterSnapshot.builder().name("requests").help("Total requests").build(); + } + + @Override + public String getPrometheusName() { + return "requests"; + } + + @Override + public MetricType getMetricType() { + return MetricType.COUNTER; + } + + @Override + public Set getLabelNames() { + return new HashSet<>(asList("path")); + } + + @Override + public MetricMetadata getMetadata() { + return new MetricMetadata("requests", "Total requests", null); + } + }; + + Collector withStatus = + new Collector() { + @Override + public MetricSnapshot collect() { + return CounterSnapshot.builder().name("requests").help("Total requests").build(); + } + + @Override + public String getPrometheusName() { + return "requests"; + } + + @Override + public MetricType getMetricType() { + return MetricType.COUNTER; + } + + @Override + public Set getLabelNames() { + return new HashSet<>(asList("status")); + } + + @Override + public MetricMetadata getMetadata() { + return new MetricMetadata("requests", "Total requests", null); + } + }; + + registry.register(withPath); + assertThatCode(() -> registry.register(withStatus)).doesNotThrowAnyException(); + } + + @Test + void unregister_lastCollector_removesPrometheusName() { + PrometheusRegistry registry = new PrometheusRegistry(); + + Collector counter1 = + new Collector() { + @Override + public MetricSnapshot collect() { + return CounterSnapshot.builder().name("requests").build(); + } + + @Override + public String getPrometheusName() { + return "requests"; + } + + @Override + public MetricType getMetricType() { + return MetricType.COUNTER; + } + + @Override + public Set getLabelNames() { + return new HashSet<>(asList("path")); + } + }; + + Collector counter2 = + new Collector() { + @Override + public MetricSnapshot collect() { + return CounterSnapshot.builder().name("requests").build(); + } + + @Override + public String getPrometheusName() { + return "requests"; + } + + @Override + public MetricType getMetricType() { + return MetricType.COUNTER; + } + + @Override + public Set getLabelNames() { + return new HashSet<>(asList("status")); + } + }; + + registry.register(counter1); + registry.register(counter2); + + // Unregister first collector - name should still be registered + registry.unregister(counter1); + MetricSnapshots snapshots = registry.scrape(); + assertThat(snapshots.size()).isEqualTo(1); + + // Unregister second collector - name should be removed + registry.unregister(counter2); + snapshots = registry.scrape(); + assertThat(snapshots.size()).isEqualTo(0); + + // Should be able to register again with same name + assertThatCode(() -> registry.register(counter1)).doesNotThrowAnyException(); + } + + @Test + void unregister_multiCollector_removesAllLabelSchemas() { + PrometheusRegistry registry = new PrometheusRegistry(); + + MultiCollector multi = + new MultiCollector() { + @Override + public MetricSnapshots collect() { + return new MetricSnapshots( + CounterSnapshot.builder().name("requests").build(), + GaugeSnapshot.builder().name("connections").build()); + } + + @Override + public List getPrometheusNames() { + return asList("requests", "connections"); + } + + @Override + public MetricType getMetricType(String prometheusName) { + return prometheusName.equals("requests") ? MetricType.COUNTER : MetricType.GAUGE; + } + }; + + registry.register(multi); + assertThat(registry.scrape().size()).isEqualTo(2); + + registry.unregister(multi); + assertThat(registry.scrape().size()).isEqualTo(0); + + // Should be able to register collectors with same names again + Collector counter = + new Collector() { + @Override + public MetricSnapshot collect() { + return CounterSnapshot.builder().name("requests").build(); + } + + @Override + public String getPrometheusName() { + return "requests"; + } + + @Override + public MetricType getMetricType() { + return MetricType.COUNTER; + } + }; + + assertThatCode(() -> registry.register(counter)).doesNotThrowAnyException(); + } + + @Test + void unregister_legacyCollector_noErrors() { + PrometheusRegistry registry = new PrometheusRegistry(); + + Collector legacy = + new Collector() { + @Override + public MetricSnapshot collect() { + return GaugeSnapshot.builder().name("legacy_metric").build(); + } + + @Override + public String getPrometheusName() { + return "legacy_metric"; + } + // No getMetricType() - returns null + }; + + registry.register(legacy); + assertThat(registry.scrape().size()).isEqualTo(1); + + // Unregister should work without errors even for legacy collectors + assertThatCode(() -> registry.unregister(legacy)).doesNotThrowAnyException(); + assertThat(registry.scrape().size()).isEqualTo(0); + } } diff --git a/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/ClassicHistogramBucketsTest.java b/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/ClassicHistogramBucketsTest.java index d39b32436..d06c2ac70 100644 --- a/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/ClassicHistogramBucketsTest.java +++ b/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/ClassicHistogramBucketsTest.java @@ -11,7 +11,7 @@ class ClassicHistogramBucketsTest { @Test - public void testGoodCase() { + void testGoodCase() { ClassicHistogramBuckets buckets = ClassicHistogramBuckets.builder() .bucket(Double.NEGATIVE_INFINITY, 0) @@ -23,7 +23,7 @@ public void testGoodCase() { } @Test - public void testSort() { + void testSort() { ClassicHistogramBuckets buckets = ClassicHistogramBuckets.builder() .bucket(7, 2) @@ -40,21 +40,21 @@ public void testSort() { } @Test - public void testMinimalBuckets() { + void testMinimalBuckets() { ClassicHistogramBuckets buckets = ClassicHistogramBuckets.builder().bucket(Double.POSITIVE_INFINITY, 0).build(); assertThat(buckets.size()).isOne(); } @Test - public void testInfBucketMissing() { + void testInfBucketMissing() { assertThatExceptionOfType(IllegalArgumentException.class) .isThrownBy( () -> ClassicHistogramBuckets.builder().bucket(Double.NEGATIVE_INFINITY, 0).build()); } @Test - public void testNegativeCount() { + void testNegativeCount() { assertThatExceptionOfType(IllegalArgumentException.class) .isThrownBy( () -> @@ -65,7 +65,7 @@ public void testNegativeCount() { } @Test - public void testNaNBoundary() { + void testNaNBoundary() { assertThatExceptionOfType(IllegalArgumentException.class) .isThrownBy( () -> @@ -77,7 +77,7 @@ public void testNaNBoundary() { } @Test - public void testDuplicateBoundary() { + void testDuplicateBoundary() { assertThatExceptionOfType(IllegalArgumentException.class) .isThrownBy( () -> @@ -90,13 +90,13 @@ public void testDuplicateBoundary() { } @Test - public void testEmptyBuckets() { + void testEmptyBuckets() { assertThatExceptionOfType(IllegalArgumentException.class) .isThrownBy(() -> ClassicHistogramBuckets.builder().build()); } @Test - public void testDifferentLength() { + void testDifferentLength() { double[] upperBounds = new double[] {0.7, 1.3, Double.POSITIVE_INFINITY}; long[] counts = new long[] {13, 178, 1024, 3000}; assertThatExceptionOfType(IllegalArgumentException.class) @@ -104,7 +104,7 @@ public void testDifferentLength() { } @Test - public void testImmutable() { + void testImmutable() { ClassicHistogramBuckets buckets = ClassicHistogramBuckets.builder() .bucket(1.0, 7) @@ -117,7 +117,7 @@ public void testImmutable() { } @Test - public void compare() { + void compare() { ClassicHistogramBuckets buckets = ClassicHistogramBuckets.builder() .bucket(1.0, 7) diff --git a/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/CounterSnapshotTest.java b/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/CounterSnapshotTest.java index a69ccfbab..ca4346cdb 100644 --- a/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/CounterSnapshotTest.java +++ b/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/CounterSnapshotTest.java @@ -11,7 +11,7 @@ class CounterSnapshotTest { @Test - public void testCompleteGoodCase() { + void testCompleteGoodCase() { long createdTimestamp1 = System.currentTimeMillis() - TimeUnit.DAYS.toMillis(1); long createdTimestamp2 = System.currentTimeMillis() - TimeUnit.MINUTES.toMillis(2); long exemplarTimestamp = System.currentTimeMillis(); @@ -68,7 +68,7 @@ public void testCompleteGoodCase() { } @Test - public void testMinimalGoodCase() { + void testMinimalGoodCase() { CounterSnapshot snapshot = CounterSnapshot.builder() .name("events") @@ -85,25 +85,25 @@ public void testMinimalGoodCase() { } @Test - public void testEmptyCounter() { + void testEmptyCounter() { CounterSnapshot snapshot = CounterSnapshot.builder().name("events").build(); assertThat(snapshot.getDataPoints()).isEmpty(); } @Test - public void testTotalSuffixPresent() { + void testTotalSuffixPresent() { assertThatExceptionOfType(IllegalArgumentException.class) .isThrownBy(() -> CounterSnapshot.builder().name("test_total").build()); } @Test - public void testValueMissing() { + void testValueMissing() { assertThatExceptionOfType(IllegalArgumentException.class) .isThrownBy(() -> CounterDataPointSnapshot.builder().build()); } @Test - public void testDataImmutable() { + void testDataImmutable() { CounterSnapshot snapshot = CounterSnapshot.builder() .name("events") diff --git a/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/ExemplarTest.java b/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/ExemplarTest.java index 4a40b47ef..db7ad2426 100644 --- a/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/ExemplarTest.java +++ b/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/ExemplarTest.java @@ -9,7 +9,7 @@ class ExemplarTest { @Test - public void testGoodCaseComplete() { + void testGoodCaseComplete() { long timestamp = System.currentTimeMillis(); Exemplar exemplar = Exemplar.builder() @@ -36,13 +36,13 @@ public void testGoodCaseComplete() { } @Test - public void testValueMissing() { + void testValueMissing() { assertThatExceptionOfType(IllegalStateException.class) .isThrownBy(() -> Exemplar.builder().build()); } @Test - public void testMinimal() { + void testMinimal() { Exemplar exemplar = Exemplar.builder().value(0.0).build(); assertThat(exemplar.getValue()).isEqualTo(0.0); assertLabels(exemplar.getLabels()).isEqualTo(Labels.EMPTY); @@ -50,7 +50,7 @@ public void testMinimal() { } @Test - public void testLabelsMergeTraceId() { + void testLabelsMergeTraceId() { Exemplar exemplar = Exemplar.builder().value(0.0).labels(Labels.of("a", "b")).traceId("abc").build(); assertLabels(exemplar.getLabels()).isEqualTo(Labels.of("a", "b", "trace_id", "abc")); @@ -61,14 +61,14 @@ private static IterableAssert assertLabels(Labels labels) { } @Test - public void testLabelsMergeSpanId() { + void testLabelsMergeSpanId() { Exemplar exemplar = Exemplar.builder().value(0.0).labels(Labels.of("a", "b")).spanId("abc").build(); assertLabels(exemplar.getLabels()).isEqualTo(Labels.of("a", "b", "span_id", "abc")); } @Test - public void testLabelsMergeTraceIdAndSpanId() { + void testLabelsMergeTraceIdAndSpanId() { Exemplar exemplar = Exemplar.builder() .value(0.0) @@ -81,7 +81,7 @@ public void testLabelsMergeTraceIdAndSpanId() { } @Test - public void testLabelsMergeNone() { + void testLabelsMergeNone() { Exemplar exemplar = Exemplar.builder().value(0.0).labels(Labels.of("a", "b")).build(); assertLabels(exemplar.getLabels()).isEqualTo(Labels.of("a", "b")); } diff --git a/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/ExemplarsTest.java b/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/ExemplarsTest.java index b26eaf84a..be69e7f16 100644 --- a/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/ExemplarsTest.java +++ b/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/ExemplarsTest.java @@ -9,16 +9,14 @@ class ExemplarsTest { @Test - public void testUpperBound() { + void testUpperBound() { Exemplars exemplars = Exemplars.of( Exemplar.builder().value(1.0).build(), Exemplar.builder().value(3.0).build(), Exemplar.builder().value(2.0).build()); assertThat(exemplars.size()).isEqualTo(3); - assertThat(exemplars.get(0).getValue()).isEqualTo(1.0); - assertThat(exemplars.get(1).getValue()).isEqualTo(3.0); - assertThat(exemplars.get(2).getValue()).isEqualTo(2.0); + assertThat(exemplars).extracting(Exemplar::getValue).containsExactly(1.0, 3.0, 2.0); assertThat(exemplars.get(0.0, Double.POSITIVE_INFINITY).getValue()).isEqualTo(1.0); assertThat(exemplars.get(0.0, 1.0).getValue()).isEqualTo(1.0); assertThat(exemplars.get(1.0, 4.0).getValue()).isEqualTo(3.0); @@ -28,7 +26,7 @@ public void testUpperBound() { } @Test - public void testImmutable() { + void testImmutable() { Exemplars exemplars = Exemplars.of( Exemplar.builder().value(1.0).build(), @@ -40,7 +38,7 @@ public void testImmutable() { } @Test - public void testGet() { + void testGet() { Exemplar oldest = Exemplar.builder().timestampMillis(System.currentTimeMillis() - 100).value(1.8).build(); Exemplar middle = diff --git a/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/GaugeSnapshotTest.java b/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/GaugeSnapshotTest.java index c469647e0..5154e1eb1 100644 --- a/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/GaugeSnapshotTest.java +++ b/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/GaugeSnapshotTest.java @@ -11,7 +11,7 @@ class GaugeSnapshotTest { @Test - public void testCompleteGoodCase() { + void testCompleteGoodCase() { long exemplarTimestamp = System.currentTimeMillis(); GaugeSnapshot snapshot = GaugeSnapshot.builder() @@ -63,7 +63,7 @@ public void testCompleteGoodCase() { } @Test - public void testMinimalGoodCase() { + void testMinimalGoodCase() { GaugeSnapshot snapshot = GaugeSnapshot.builder() .name("temperature") @@ -80,31 +80,31 @@ public void testMinimalGoodCase() { } @Test - public void testEmptyGauge() { + void testEmptyGauge() { GaugeSnapshot snapshot = GaugeSnapshot.builder().name("temperature").build(); assertThat(snapshot.getDataPoints().size()).isZero(); } @Test - public void testTotalSuffixPresent() { + void testTotalSuffixPresent() { assertThatExceptionOfType(IllegalArgumentException.class) .isThrownBy(() -> CounterSnapshot.builder().name("test_total").build()); } @Test - public void testTotalSuffixPresentDot() { + void testTotalSuffixPresentDot() { assertThatExceptionOfType(IllegalArgumentException.class) .isThrownBy(() -> CounterSnapshot.builder().name("test.total").build()); } @Test - public void testValueMissing() { + void testValueMissing() { assertThatExceptionOfType(IllegalArgumentException.class) .isThrownBy(() -> CounterDataPointSnapshot.builder().build()); } @Test - public void testDataImmutable() { + void testDataImmutable() { GaugeSnapshot snapshot = GaugeSnapshot.builder() .name("gauge") diff --git a/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/HistogramSnapshotTest.java b/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/HistogramSnapshotTest.java index 54eefbe66..6233481df 100644 --- a/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/HistogramSnapshotTest.java +++ b/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/HistogramSnapshotTest.java @@ -13,7 +13,7 @@ class HistogramSnapshotTest { @Test - public void testGoodCaseComplete() { + void testGoodCaseComplete() { long createdTimestamp = System.currentTimeMillis() - TimeUnit.DAYS.toMillis(1); long scrapeTimestamp = System.currentTimeMillis() - TimeUnit.MINUTES.toMillis(2); long exemplarTimestamp = System.currentTimeMillis(); @@ -170,7 +170,7 @@ public void testGoodCaseComplete() { } @Test - public void testEmptyHistogram() { + void testEmptyHistogram() { assertThat(HistogramSnapshot.builder().name("empty_histogram").build().getDataPoints()) .isEmpty(); HistogramSnapshot snapshot = @@ -181,7 +181,7 @@ public void testEmptyHistogram() { } @Test - public void testMinimalClassicHistogram() { + void testMinimalClassicHistogram() { HistogramSnapshot snapshot = HistogramSnapshot.builder() .name("minimal_histogram") @@ -198,7 +198,7 @@ public void testMinimalClassicHistogram() { } @Test - public void testMinimalNativeHistogram() { + void testMinimalNativeHistogram() { HistogramSnapshot snapshot = HistogramSnapshot.builder() .name("hist") @@ -218,7 +218,7 @@ public void testMinimalNativeHistogram() { } @Test - public void testClassicCount() { + void testClassicCount() { HistogramSnapshot snapshot = HistogramSnapshot.builder() .name("test_histogram") @@ -239,21 +239,21 @@ public void testClassicCount() { } @Test - public void testEmptyData() { + void testEmptyData() { // This will fail because one of nativeSchema and classicHistogramBuckets is required assertThatExceptionOfType(IllegalArgumentException.class) .isThrownBy(() -> HistogramDataPointSnapshot.builder().build()); } @Test - public void testEmptyNativeData() { + void testEmptyNativeData() { HistogramDataPointSnapshot data = HistogramDataPointSnapshot.builder().nativeSchema(5).build(); assertThat(data.getNativeBucketsForNegativeValues().size()).isZero(); assertThat(data.getNativeBucketsForPositiveValues().size()).isZero(); } @Test - public void testDataImmutable() { + void testDataImmutable() { HistogramSnapshot snapshot = HistogramSnapshot.builder() .name("test_histogram") @@ -278,7 +278,7 @@ public void testDataImmutable() { } @Test - public void testEmptyClassicBuckets() { + void testEmptyClassicBuckets() { assertThatExceptionOfType(IllegalArgumentException.class) .isThrownBy( () -> @@ -287,7 +287,7 @@ public void testEmptyClassicBuckets() { } @Test - public void testMinimalNativeData() { + void testMinimalNativeData() { new HistogramDataPointSnapshot( ClassicHistogramBuckets.EMPTY, 0, @@ -302,7 +302,7 @@ public void testMinimalNativeData() { } @Test - public void testMinimalClassicData() { + void testMinimalClassicData() { ClassicHistogramBuckets buckets = ClassicHistogramBuckets.builder().bucket(Double.POSITIVE_INFINITY, 0).build(); new HistogramDataPointSnapshot( diff --git a/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/InfoSnapshotTest.java b/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/InfoSnapshotTest.java index 983450181..3cf7d69af 100644 --- a/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/InfoSnapshotTest.java +++ b/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/InfoSnapshotTest.java @@ -9,7 +9,7 @@ class InfoSnapshotTest { @Test - public void testCompleteGoodCase() { + void testCompleteGoodCase() { InfoSnapshot snapshot = InfoSnapshot.builder() .name("target") @@ -33,13 +33,13 @@ void create() { } @Test - public void testEmptyInfo() { + void testEmptyInfo() { InfoSnapshot snapshot = InfoSnapshot.builder().name("target").build(); assertThat(snapshot.getDataPoints()).isEmpty(); } @Test - public void testDataImmutable() { + void testDataImmutable() { InfoSnapshot snapshot = InfoSnapshot.builder() .name("target") @@ -60,13 +60,13 @@ public void testDataImmutable() { } @Test - public void testNameMustNotIncludeSuffix() { + void testNameMustNotIncludeSuffix() { assertThatExceptionOfType(IllegalArgumentException.class) .isThrownBy(() -> InfoSnapshot.builder().name("jvm_info").build()); } @Test - public void testNameMustNotIncludeSuffixDot() { + void testNameMustNotIncludeSuffixDot() { assertThatExceptionOfType(IllegalArgumentException.class) .isThrownBy(() -> InfoSnapshot.builder().name("jvm.info").build()); } diff --git a/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/LabelsTest.java b/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/LabelsTest.java index 4d68b82a8..3dc8f639f 100644 --- a/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/LabelsTest.java +++ b/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/LabelsTest.java @@ -17,7 +17,7 @@ private > void assertGreaterThan(T a, T b) { } @Test - public void testCompareDifferentLabelNames() { + void testCompareDifferentLabelNames() { Labels labels1 = Labels.of("env", "prod", "status2", "200"); Labels labels2 = Labels.of("env", "prod", "status1", "200"); assertGreaterThan(labels1, labels2); @@ -31,7 +31,7 @@ private static IterableAssert assertLabels(Labels labels) { } @Test - public void testCompareSameLabelNames() { + void testCompareSameLabelNames() { // If all label names are the same, labels should be sorted by label value. Labels labels1 = Labels.of("env", "prod", "status", "200"); Labels labels2 = Labels.of("env", "prod", "status", "500"); @@ -42,7 +42,7 @@ public void testCompareSameLabelNames() { } @Test - public void testCompareDifferentNumberOfLabels() { + void testCompareDifferentNumberOfLabels() { Labels labels1 = Labels.of("env", "prod", "status", "200"); Labels labels2 = Labels.of("env", "prod", "status", "200", "x_code", "none"); assertLessThan(labels1, labels2); @@ -52,14 +52,14 @@ public void testCompareDifferentNumberOfLabels() { } @Test - public void testComparePrometheusNames() { + void testComparePrometheusNames() { Labels labels1 = Labels.of("my_a", "val"); Labels labels2 = Labels.of("my.b", "val"); assertLessThan(labels1, labels2); // this is true because it compares "my_a" to "my_b". } @Test - public void testEqualsHashcodeDots() { + void testEqualsHashcodeDots() { Labels labels1 = Labels.of("my_a", "val"); Labels labels2 = Labels.of("my.a", "val"); assertLabels(labels2).isEqualTo(labels1).hasSameHashCodeAs(labels1); @@ -67,7 +67,7 @@ public void testEqualsHashcodeDots() { @SuppressWarnings({"unchecked", "rawtypes"}) @Test - public void testCompareEquals() { + void testCompareEquals() { Labels labels1 = Labels.of("env", "prod", "status", "200"); Labels labels2 = Labels.of("env", "prod", "status", "200"); assertThat((Comparable) labels1).isEqualByComparingTo(labels2); @@ -77,25 +77,19 @@ public void testCompareEquals() { } @Test - public void testIllegalLabelName() { - assertThatExceptionOfType(IllegalArgumentException.class) - .isThrownBy(() -> Labels.of("my_service/status", "200")); - } - - @Test - public void testReservedLabelName() { + void testReservedLabelName() { assertThatExceptionOfType(IllegalArgumentException.class) .isThrownBy(() -> Labels.of("__name__", "requests_total")); } @Test - public void testDuplicateLabelName() { + void testDuplicateLabelName() { assertThatExceptionOfType(IllegalArgumentException.class) .isThrownBy(() -> Labels.of("name1", "value1", "name2", "value2", "name1", "value3")); } @Test - public void testMakePrometheusNames() { + void testMakePrometheusNames() { String[] names = new String[] {}; String[] prometheusNames = Labels.makePrometheusNames(names); assertThat(prometheusNames).isSameAs(names); @@ -113,7 +107,7 @@ public void testMakePrometheusNames() { } @Test - public void testMerge() { + void testMerge() { Labels labels1 = Labels.of("key.1", "value 1", "key.3", "value 3"); Labels labels2 = Labels.of("key_2", "value 2"); Labels merged = labels2.merge(labels1); @@ -123,7 +117,7 @@ public void testMerge() { } @Test - public void testMergeDuplicateName() { + void testMergeDuplicateName() { Labels labels1 = Labels.of("key_one", "v1"); Labels labels2 = Labels.of("key.one", "v2"); assertThatExceptionOfType(IllegalArgumentException.class) @@ -131,7 +125,7 @@ public void testMergeDuplicateName() { } @Test - public void testDuplicateName() { + void testDuplicateName() { assertThatExceptionOfType(IllegalArgumentException.class) .isThrownBy(() -> Labels.of("key_one", "v1", "key.one", "v2")); } diff --git a/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/MetricMetadataTest.java b/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/MetricMetadataTest.java index fab885488..41efe043b 100644 --- a/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/MetricMetadataTest.java +++ b/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/MetricMetadataTest.java @@ -9,77 +9,67 @@ class MetricMetadataTest { @Test - public void testEmptyName() { + void testEmptyName() { assertThatExceptionOfType(IllegalArgumentException.class) .isThrownBy(() -> new MetricMetadata("")); } @Test - public void testNullName() { + void testNullName() { assertThatExceptionOfType(IllegalArgumentException.class) .isThrownBy(() -> new MetricMetadata(null)); } @Test - public void testIllegalName() { - assertThatExceptionOfType(IllegalArgumentException.class) - .isThrownBy( - () -> - new MetricMetadata( - "my_namespace/http_server_duration")); // let's see when we decide to allow - // slashes :) - } - - @Test - public void testSanitizationIllegalCharacters() { + void testSanitizationIllegalCharacters() { MetricMetadata metadata = new MetricMetadata( sanitizeMetricName("my_namespace/http.server.duration", Unit.SECONDS), "help string", Unit.SECONDS); - assertThat(metadata.getName()).isEqualTo("my_namespace_http.server.duration_seconds"); + assertThat(metadata.getName()).isEqualTo("my_namespace/http.server.duration_seconds"); assertThat(metadata.getPrometheusName()).isEqualTo("my_namespace_http_server_duration_seconds"); assertThat(metadata.getHelp()).isEqualTo("help string"); - assertThat(metadata.getUnit().toString()).isEqualTo("seconds"); + assertThat(metadata.getUnit()).hasToString("seconds"); } @Test - public void testSanitizationCounter() { + void testSanitizationCounter() { MetricMetadata metadata = new MetricMetadata(sanitizeMetricName("my_events_total")); assertThat(metadata.getName()).isEqualTo("my_events"); } @Test - public void testSanitizationInfo() { + void testSanitizationInfo() { MetricMetadata metadata = new MetricMetadata(sanitizeMetricName("target_info")); assertThat(metadata.getName()).isEqualTo("target"); } @Test - public void testSanitizationWeirdCornerCase() { + void testSanitizationWeirdCornerCase() { MetricMetadata metadata = new MetricMetadata(sanitizeMetricName("_total_created")); assertThat(metadata.getName()).isEqualTo("total"); } @Test - public void testSanitizeEmptyString() { + void testSanitizeEmptyString() { assertThatExceptionOfType(IllegalArgumentException.class) .isThrownBy(() -> sanitizeMetricName("")); } @Test - public void testUnitSuffixRequired() { + void testUnitSuffixRequired() { assertThatExceptionOfType(IllegalArgumentException.class) .isThrownBy(() -> new MetricMetadata("my_counter", "help", Unit.SECONDS)); } @Test - public void testUnitSuffixAdded() { + void testUnitSuffixAdded() { new MetricMetadata(sanitizeMetricName("my_counter", Unit.SECONDS), "help", Unit.SECONDS); } @Test - public void testUnitNotDuplicated() { + void testUnitNotDuplicated() { assertThat(sanitizeMetricName("my_counter_bytes", Unit.BYTES)).isEqualTo("my_counter_bytes"); } } diff --git a/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/MetricSnapshotTest.java b/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/MetricSnapshotTest.java index 2c1c04bb6..199cb8f4c 100644 --- a/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/MetricSnapshotTest.java +++ b/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/MetricSnapshotTest.java @@ -8,7 +8,7 @@ class MetricSnapshotTest { @Test - public void testDuplicateLabels() { + void testDuplicateLabels() { assertThatExceptionOfType(DuplicateLabelsException.class) .isThrownBy( () -> @@ -39,13 +39,13 @@ public void testDuplicateLabels() { } @Test - public void testNoData() { + void testNoData() { MetricSnapshot snapshot = CounterSnapshot.builder().name("test").build(); assertThat(snapshot.getDataPoints().size()).isEqualTo(0); } @Test - public void testNullData() { + void testNullData() { assertThatExceptionOfType(NullPointerException.class) .isThrownBy(() -> new CounterSnapshot(new MetricMetadata("test"), null)); } diff --git a/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/MetricSnapshotsTest.java b/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/MetricSnapshotsTest.java index ed2f66fec..224ca691a 100644 --- a/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/MetricSnapshotsTest.java +++ b/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/MetricSnapshotsTest.java @@ -9,13 +9,13 @@ class MetricSnapshotsTest { @Test - public void testEmpty() { + void testEmpty() { MetricSnapshots snapshots = MetricSnapshots.builder().build(); - assertThat(snapshots.stream().findAny().isPresent()).isFalse(); + assertThat(snapshots.stream().findAny()).isNotPresent(); } @Test - public void testSort() { + void testSort() { CounterSnapshot c1 = CounterSnapshot.builder() .name("counter1") @@ -39,7 +39,7 @@ public void testSort() { } @Test - public void testDuplicateName() { + void testDuplicateName() { // Q: What if you have a counter named "foo" and a gauge named "foo"? // A: Great question. You might think this is a valid scenario, because the counter will produce // the values "foo_total" and "foo_created" while the gauge will produce the value "foo". @@ -62,7 +62,35 @@ public void testDuplicateName() { } @Test - public void testBuilder() { + void testDuplicateName_histogramGaugeVsClassic_throws() { + HistogramSnapshot classic = + HistogramSnapshot.builder() + .name("my_histogram") + .dataPoint( + HistogramSnapshot.HistogramDataPointSnapshot.builder() + .classicHistogramBuckets( + ClassicHistogramBuckets.of( + new double[] {Double.POSITIVE_INFINITY}, new long[] {0})) + .build()) + .build(); + HistogramSnapshot gauge = + HistogramSnapshot.builder() + .name("my_histogram") + .gaugeHistogram(true) + .dataPoint( + HistogramSnapshot.HistogramDataPointSnapshot.builder() + .classicHistogramBuckets( + ClassicHistogramBuckets.of( + new double[] {Double.POSITIVE_INFINITY}, new long[] {0})) + .build()) + .build(); + assertThatExceptionOfType(IllegalArgumentException.class) + .isThrownBy(() -> new MetricSnapshots(classic, gauge)) + .withMessageContaining("conflicting histogram types"); + } + + @Test + void testBuilder() { CounterSnapshot counter = CounterSnapshot.builder() .name("my_metric") @@ -75,7 +103,7 @@ public void testBuilder() { } @Test - public void testImmutable() { + void testImmutable() { CounterSnapshot c1 = CounterSnapshot.builder() .name("counter1") diff --git a/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/NativeHistogramBucketsTest.java b/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/NativeHistogramBucketsTest.java index 32c7f3eb3..ed52a7d1a 100644 --- a/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/NativeHistogramBucketsTest.java +++ b/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/NativeHistogramBucketsTest.java @@ -9,7 +9,7 @@ class NativeHistogramBucketsTest { @Test - public void testGoodCase() { + void testGoodCase() { NativeHistogramBuckets buckets = NativeHistogramBuckets.builder().bucket(-10, 12).bucket(120, 17).build(); assertThat(buckets.size()).isEqualTo(2); @@ -20,13 +20,13 @@ public void testGoodCase() { } @Test - public void testEmpty() { + void testEmpty() { NativeHistogramBuckets buckets = NativeHistogramBuckets.builder().build(); assertThat(buckets.size()).isZero(); } @Test - public void testSort() { + void testSort() { NativeHistogramBuckets buckets = NativeHistogramBuckets.builder().bucket(7, 4).bucket(2, 0).bucket(5, 3).build(); assertThat(buckets.size()).isEqualTo(3); @@ -39,7 +39,7 @@ public void testSort() { } @Test - public void testDifferentLength() { + void testDifferentLength() { int[] bucketIndexes = new int[] {0, 1, 2}; long[] cumulativeCounts = new long[] {13, 178, 1024, 3000}; assertThatExceptionOfType(IllegalArgumentException.class) @@ -47,7 +47,7 @@ public void testDifferentLength() { } @Test - public void testImmutable() { + void testImmutable() { NativeHistogramBuckets buckets = NativeHistogramBuckets.builder().bucket(1, 1).bucket(2, 1).build(); Iterator iterator = buckets.iterator(); diff --git a/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/PrometheusNamingTest.java b/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/PrometheusNamingTest.java index fad55e0ac..847bb0f38 100644 --- a/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/PrometheusNamingTest.java +++ b/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/PrometheusNamingTest.java @@ -1,19 +1,28 @@ package io.prometheus.metrics.model.snapshots; -import static io.prometheus.metrics.model.snapshots.PrometheusNaming.*; +import static io.prometheus.metrics.model.snapshots.PrometheusNaming.escapeName; +import static io.prometheus.metrics.model.snapshots.PrometheusNaming.isValidLabelName; +import static io.prometheus.metrics.model.snapshots.PrometheusNaming.prometheusName; +import static io.prometheus.metrics.model.snapshots.PrometheusNaming.sanitizeLabelName; +import static io.prometheus.metrics.model.snapshots.PrometheusNaming.sanitizeMetricName; +import static io.prometheus.metrics.model.snapshots.PrometheusNaming.sanitizeUnitName; +import static io.prometheus.metrics.model.snapshots.PrometheusNaming.validateMetricName; +import static io.prometheus.metrics.model.snapshots.PrometheusNaming.validateUnitName; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; +import io.prometheus.metrics.config.EscapingScheme; +import java.util.stream.Stream; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; class PrometheusNamingTest { @Test - public void testSanitizeMetricName() { - assertThat(prometheusName(sanitizeMetricName("0abc.def"))).isEqualTo("_abc_def"); - assertThat(prometheusName(sanitizeMetricName("___ab.:c0"))).isEqualTo("___ab__c0"); - assertThat(sanitizeMetricName("my_prefix/my_metric")).isEqualTo("my_prefix_my_metric"); - assertThat(prometheusName(sanitizeMetricName("my_counter_total"))).isEqualTo("my_counter"); + void testSanitizeMetricName() { + assertThat(sanitizeMetricName("my_counter_total")).isEqualTo("my_counter"); assertThat(sanitizeMetricName("jvm.info")).isEqualTo("jvm"); assertThat(sanitizeMetricName("jvm_info")).isEqualTo("jvm"); assertThat(sanitizeMetricName("jvm.info")).isEqualTo("jvm"); @@ -23,25 +32,18 @@ public void testSanitizeMetricName() { } @Test - public void testSanitizeMetricNameWithUnit() { - assertThat(prometheusName(sanitizeMetricName("0abc.def", Unit.RATIO))) - .isEqualTo("_abc_def_" + Unit.RATIO); - assertThat(prometheusName(sanitizeMetricName("___ab.:c0", Unit.RATIO))) - .isEqualTo("___ab__c0_" + Unit.RATIO); - assertThat(sanitizeMetricName("my_prefix/my_metric", Unit.RATIO)) - .isEqualTo("my_prefix_my_metric_" + Unit.RATIO); + void testSanitizeMetricNameWithUnit() { + assertThat(prometheusName(sanitizeMetricName("def", Unit.RATIO))) + .isEqualTo("def_" + Unit.RATIO); assertThat(prometheusName(sanitizeMetricName("my_counter_total", Unit.RATIO))) .isEqualTo("my_counter_" + Unit.RATIO); assertThat(sanitizeMetricName("jvm.info", Unit.RATIO)).isEqualTo("jvm_" + Unit.RATIO); - assertThat(sanitizeMetricName("jvm_info", Unit.RATIO)).isEqualTo("jvm_" + Unit.RATIO); - assertThat(sanitizeMetricName("jvm.info", Unit.RATIO)).isEqualTo("jvm_" + Unit.RATIO); - assertThat(sanitizeMetricName("a.b", Unit.RATIO)).isEqualTo("a.b_" + Unit.RATIO); assertThat(sanitizeMetricName("_total", Unit.RATIO)).isEqualTo("total_" + Unit.RATIO); assertThat(sanitizeMetricName("total", Unit.RATIO)).isEqualTo("total_" + Unit.RATIO); } @Test - public void testSanitizeLabelName() { + void testSanitizeLabelName() { assertThat(prometheusName(sanitizeLabelName("0abc.def"))).isEqualTo("_abc_def"); assertThat(prometheusName(sanitizeLabelName("_abc"))).isEqualTo("_abc"); assertThat(prometheusName(sanitizeLabelName("__abc"))).isEqualTo("_abc"); @@ -52,7 +54,7 @@ public void testSanitizeLabelName() { } @Test - public void testValidateUnitName() { + void testValidateUnitName() { assertThat(validateUnitName("secondstotal")).isNotNull(); assertThat(validateUnitName("total")).isNotNull(); assertThat(validateUnitName("seconds_total")).isNotNull(); @@ -64,7 +66,7 @@ public void testValidateUnitName() { } @Test - public void testSanitizeUnitName() { + void testSanitizeUnitName() { assertThat(sanitizeUnitName("seconds")).isEqualTo("seconds"); assertThat(sanitizeUnitName("seconds_total")).isEqualTo("seconds"); assertThat(sanitizeUnitName("seconds_total_total")).isEqualTo("seconds"); @@ -74,26 +76,125 @@ public void testSanitizeUnitName() { } @Test - public void testInvalidUnitName1() { + void testInvalidUnitName1() { assertThatExceptionOfType(IllegalArgumentException.class) .isThrownBy(() -> sanitizeUnitName("total")); } @Test - public void testInvalidUnitName2() { + void testInvalidUnitName2() { assertThatExceptionOfType(IllegalArgumentException.class) .isThrownBy(() -> sanitizeUnitName("_total")); } @Test - public void testInvalidUnitName3() { + void testInvalidUnitName3() { assertThatExceptionOfType(IllegalArgumentException.class) .isThrownBy(() -> sanitizeUnitName("%")); } @Test - public void testEmptyUnitName() { + void testEmptyUnitName() { assertThatExceptionOfType(IllegalArgumentException.class) .isThrownBy(() -> sanitizeUnitName("")); } + + @ParameterizedTest + @MethodSource("nameIsValid") + public void testLabelNameIsValidUtf8(String labelName, boolean utf8Valid) { + assertMetricName(labelName, utf8Valid); + assertLabelName(labelName, utf8Valid); + } + + private static void assertLabelName(String labelName, boolean legacyValid) { + assertThat(isValidLabelName(labelName)) + .describedAs("isValidLabelName(%s)", labelName) + .isEqualTo(legacyValid); + } + + private static void assertMetricName(String labelName, boolean valid) { + assertThat(validateMetricName(labelName)) + .describedAs("validateMetricName(%s)", labelName) + .isEqualTo(valid ? null : "The metric name contains unsupported characters"); + } + + static Stream nameIsValid() { + return Stream.of( + Arguments.of("", false), + Arguments.of("Avalid_23name", true), + Arguments.of("_Avalid_23name", true), + Arguments.of("1valid_23name", true), + Arguments.of("avalid_23name", true), + Arguments.of("Ava:lid_23name", true), + Arguments.of("a lid_23name", true), + Arguments.of(":leading_colon", true), + Arguments.of("colon:in:the:middle", true), + Arguments.of("aΩz", true), + Arguments.of("a\ud800z", false)); + } + + @ParameterizedTest + @MethodSource("escapeNameLegacyTestCases") + public void testEscapeName(String input, EscapingScheme escapingScheme, String expected) { + assertThat(escapeName(input, escapingScheme)).isEqualTo(expected); + } + + static Stream escapeNameLegacyTestCases() { + return Stream.of( + Arguments.of("", EscapingScheme.UNDERSCORE_ESCAPING, ""), + Arguments.of("", EscapingScheme.DOTS_ESCAPING, ""), + Arguments.of("", EscapingScheme.VALUE_ENCODING_ESCAPING, ""), + Arguments.of( + "no:escaping_required", EscapingScheme.UNDERSCORE_ESCAPING, "no:escaping_required"), + // Dots escaping will escape underscores even though it's not strictly + // necessary for compatibility. + Arguments.of("no:escaping_required", EscapingScheme.DOTS_ESCAPING, "no:escaping__required"), + Arguments.of( + "no:escaping_required", EscapingScheme.VALUE_ENCODING_ESCAPING, "no:escaping_required"), + Arguments.of( + "no:escaping_required", EscapingScheme.UNDERSCORE_ESCAPING, "no:escaping_required"), + Arguments.of( + "mysystem.prod.west.cpu.load", + EscapingScheme.DOTS_ESCAPING, + "mysystem_dot_prod_dot_west_dot_cpu_dot_load"), + Arguments.of( + "mysystem.prod.west.cpu.load_total", + EscapingScheme.DOTS_ESCAPING, + "mysystem_dot_prod_dot_west_dot_cpu_dot_load__total"), + Arguments.of("http.status:sum", EscapingScheme.DOTS_ESCAPING, "http_dot_status:sum"), + Arguments.of("label with 😱", EscapingScheme.UNDERSCORE_ESCAPING, "label_with__"), + Arguments.of("label with 😱", EscapingScheme.DOTS_ESCAPING, "label__with____"), + Arguments.of( + "label with 😱", EscapingScheme.VALUE_ENCODING_ESCAPING, "U__label_20_with_20__1f631_"), + // name with unicode characters > 0x100 + Arguments.of("花火", EscapingScheme.UNDERSCORE_ESCAPING, "__"), + // Dots-replacement does not know the difference between two replaced + Arguments.of("花火", EscapingScheme.DOTS_ESCAPING, "____"), + Arguments.of("花火", EscapingScheme.VALUE_ENCODING_ESCAPING, "U___82b1__706b_"), + // name with spaces and edge-case value + Arguments.of("label with Ā", EscapingScheme.UNDERSCORE_ESCAPING, "label_with__"), + Arguments.of("label with Ā", EscapingScheme.DOTS_ESCAPING, "label__with____"), + Arguments.of( + "label with Ā", EscapingScheme.VALUE_ENCODING_ESCAPING, "U__label_20_with_20__100_"), + // name with dots - needs UTF-8 validation for escaping to occur + Arguments.of( + "mysystem.prod.west.cpu.load", + EscapingScheme.UNDERSCORE_ESCAPING, + "mysystem_prod_west_cpu_load"), + Arguments.of( + "mysystem.prod.west.cpu.load", + EscapingScheme.VALUE_ENCODING_ESCAPING, + "U__mysystem_2e_prod_2e_west_2e_cpu_2e_load"), + Arguments.of( + "mysystem.prod.west.cpu.load_total", + EscapingScheme.UNDERSCORE_ESCAPING, + "mysystem_prod_west_cpu_load_total"), + Arguments.of( + "mysystem.prod.west.cpu.load_total", + EscapingScheme.VALUE_ENCODING_ESCAPING, + "U__mysystem_2e_prod_2e_west_2e_cpu_2e_load__total"), + Arguments.of("http.status:sum", EscapingScheme.UNDERSCORE_ESCAPING, "http_status:sum"), + Arguments.of( + "http.status:sum", EscapingScheme.VALUE_ENCODING_ESCAPING, "U__http_2e_status:sum")); + } } diff --git a/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/QuantilesTest.java b/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/QuantilesTest.java index 8703657ca..4ccadddc8 100644 --- a/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/QuantilesTest.java +++ b/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/QuantilesTest.java @@ -9,7 +9,7 @@ class QuantilesTest { @Test - public void testSort() { + void testSort() { Quantiles quantiles = Quantiles.builder().quantile(0.99, 0.23).quantile(0.5, 0.2).quantile(0.95, 0.22).build(); assertThat(quantiles.size()).isEqualTo(3); @@ -22,7 +22,7 @@ public void testSort() { } @Test - public void testImmutable() { + void testImmutable() { Quantiles quantiles = Quantiles.builder().quantile(0.99, 0.23).quantile(0.5, 0.2).quantile(0.95, 0.22).build(); Iterator iterator = quantiles.iterator(); @@ -31,12 +31,12 @@ public void testImmutable() { } @Test - public void testEmpty() { + void testEmpty() { assertThat(Quantiles.EMPTY.size()).isZero(); } @Test - public void testDuplicate() { + void testDuplicate() { assertThatExceptionOfType(IllegalArgumentException.class) .isThrownBy( () -> diff --git a/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/SnapshotEscaperTest.java b/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/SnapshotEscaperTest.java new file mode 100644 index 000000000..5c04719c0 --- /dev/null +++ b/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/SnapshotEscaperTest.java @@ -0,0 +1,220 @@ +package io.prometheus.metrics.model.snapshots; + +import static io.prometheus.metrics.model.snapshots.SnapshotEscaper.escapeMetricSnapshot; +import static io.prometheus.metrics.model.snapshots.SnapshotEscaper.getSnapshotLabelName; +import static org.assertj.core.api.Assertions.assertThat; + +import io.prometheus.metrics.config.EscapingScheme; +import java.util.Objects; +import java.util.stream.Stream; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +class SnapshotEscaperTest { + + @Test + void testEscapeMetricSnapshotEmpty() { + MetricSnapshot original = CounterSnapshot.builder().name("empty").build(); + MetricSnapshot got = escapeMetricSnapshot(original, EscapingScheme.VALUE_ENCODING_ESCAPING); + assertThat(Objects.requireNonNull(got).getMetadata().getName()).isEqualTo("empty"); + assertThat(original.getMetadata().getName()).isEqualTo("empty"); + } + + @Test + void testEscapeMetricSnapshotSimpleNoEscapingNeeded() { + testEscapeMetricSnapshot( + "my_metric", + "some_label", + "labelvalue", + "my_metric", + "some_label", + "labelvalue", + EscapingScheme.VALUE_ENCODING_ESCAPING, + CounterSnapshot.class); + } + + @Test + void testEscapeMetricSnapshotLabelNameEscapingNeeded() { + testEscapeMetricSnapshot( + "my_metric", + "some.label", + "labelvalue", + "my_metric", + "U__some_2e_label", + "labelvalue", + EscapingScheme.VALUE_ENCODING_ESCAPING, + CounterSnapshot.class); + } + + @Test + void testEscapeMetricSnapshotCounterEscapingNeeded() { + testEscapeMetricSnapshot( + "my.metric", + "some?label", + "label??value", + "U__my_2e_metric", + "U__some_3f_label", + "label??value", + EscapingScheme.VALUE_ENCODING_ESCAPING, + CounterSnapshot.class); + } + + @Test + void testEscapeMetricSnapshotGaugeEscapingNeeded() { + testEscapeMetricSnapshot( + "unicode.and.dots.花火", + "some_label", + "label??value", + "unicode_dot_and_dot_dots_dot_____", + "some__label", + "label??value", + EscapingScheme.DOTS_ESCAPING, + GaugeSnapshot.class); + } + + private void testEscapeMetricSnapshot( + String name, + String labelName, + String labelValue, + String expectedName, + String expectedLabelName, + String expectedLabelValue, + EscapingScheme escapingScheme, + Class snapshotType) { + + MetricSnapshot original = createTestSnapshot(name, labelName, labelValue, snapshotType); + MetricSnapshot got = escapeMetricSnapshot(original, escapingScheme); + + assertThat(got.getMetadata().getName()).isEqualTo(expectedName); + assertThat(got.getMetadata().getHelp()).isEqualTo("some help text"); + assertThat(got.getDataPoints()).hasSize(1); + + DataPointSnapshot escapedData = got.getDataPoints().get(0); + assertThat((Iterable) escapedData.getLabels()) + .isEqualTo(Labels.builder().label(expectedLabelName, expectedLabelValue).build()); + + assertThat(original.getMetadata().getName()).isEqualTo(name); + assertThat(original.getMetadata().getHelp()).isEqualTo("some help text"); + assertThat(original.getDataPoints()).hasSize(1); + + DataPointSnapshot originalData = original.getDataPoints().get(0); + assertThat((Iterable) originalData.getLabels()) + .isEqualTo(Labels.builder().label(labelName, labelValue).build()); + } + + private MetricSnapshot createTestSnapshot( + String name, + String labelName, + String labelValue, + Class snapshotType) { + Labels labels = Labels.builder().label(labelName, labelValue).build(); + + if (snapshotType.equals(CounterSnapshot.class)) { + return CounterSnapshot.builder() + .name(name) + .help("some help text") + .dataPoint( + CounterSnapshot.CounterDataPointSnapshot.builder().value(34.2).labels(labels).build()) + .build(); + } else if (snapshotType.equals(GaugeSnapshot.class)) { + return GaugeSnapshot.builder() + .name(name) + .help("some help text") + .dataPoint( + GaugeSnapshot.GaugeDataPointSnapshot.builder().value(34.2).labels(labels).build()) + .build(); + } + + throw new IllegalArgumentException("Unsupported snapshot type: " + snapshotType); + } + + @ParameterizedTest + @MethodSource("emptySnapshots") + void escape(MetricSnapshot original) { + assertThat(original) + .isSameAs(escapeMetricSnapshot(original, EscapingScheme.ALLOW_UTF8)) + .isSameAs(escapeMetricSnapshot(original, EscapingScheme.UNDERSCORE_ESCAPING)); + assertThat(escapeMetricSnapshot(original, EscapingScheme.VALUE_ENCODING_ESCAPING)) + .usingRecursiveComparison() + .isEqualTo(original); + } + + @Test + void escapeNull() { + assertThat(escapeMetricSnapshot(null, EscapingScheme.ALLOW_UTF8)).isNull(); + } + + public static Stream emptySnapshots() { + return Stream.of( + Arguments.of( + CounterSnapshot.builder() + .name("empty") + .dataPoint(CounterSnapshot.CounterDataPointSnapshot.builder().value(0).build()) + .build()), + Arguments.of( + GaugeSnapshot.builder() + .name("empty") + .dataPoint(GaugeSnapshot.GaugeDataPointSnapshot.builder().value(0).build()) + .build()), + Arguments.of( + SummarySnapshot.builder() + .name("empty") + .dataPoint( + SummarySnapshot.SummaryDataPointSnapshot.builder().count(0).sum(0.0).build()) + .build()), + Arguments.of( + HistogramSnapshot.builder() + .name("empty") + .dataPoint( + HistogramSnapshot.HistogramDataPointSnapshot.builder() + .count(0) + .sum(0.0) + .classicHistogramBuckets( + ClassicHistogramBuckets.builder() + .bucket(0.0, 0) + .bucket(1.0, 0) + .bucket(2.0, 0) + .bucket(Double.POSITIVE_INFINITY, 0) + .build()) + .exemplars( + Exemplars.builder() + .exemplar( + Exemplar.builder() + .labels(Labels.of("exemplar_label", "exemplar_value")) + .value(0.0) + .build()) + .build()) + .build()) + .build()), + Arguments.of( + StateSetSnapshot.builder() + .name("empty") + .dataPoint( + StateSetSnapshot.StateSetDataPointSnapshot.builder().state("foo", true).build()) + .build()), + Arguments.of( + UnknownSnapshot.builder() + .name("empty") + .dataPoint(UnknownSnapshot.UnknownDataPointSnapshot.builder().value(1.0).build()) + .build())); + } + + @Test + void metadataName() { + MetricMetadata metadata = new MetricMetadata("test."); + assertThat(SnapshotEscaper.getMetadataName(metadata, EscapingScheme.ALLOW_UTF8)) + .isEqualTo("test."); + assertThat(SnapshotEscaper.getMetadataName(metadata, EscapingScheme.UNDERSCORE_ESCAPING)) + .isEqualTo("test_"); + } + + @Test + void snapshotLabelName() { + Labels labels = Labels.builder().label("test.", "value").build(); + assertThat(getSnapshotLabelName(labels, 0, EscapingScheme.ALLOW_UTF8)).isEqualTo("test."); + assertThat(getSnapshotLabelName(labels, 0, EscapingScheme.UNDERSCORE_ESCAPING)) + .isEqualTo("test_"); + } +} diff --git a/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/SnapshotTestUtil.java b/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/SnapshotTestUtil.java index d8e8d33e4..8a8a7f93b 100644 --- a/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/SnapshotTestUtil.java +++ b/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/SnapshotTestUtil.java @@ -2,7 +2,7 @@ import static org.assertj.core.api.Assertions.assertThat; -class SnapshotTestUtil { +public class SnapshotTestUtil { public static void assertMetadata( MetricSnapshot snapshot, String name, String help, String unit) { diff --git a/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/StateSetSnapshotTest.java b/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/StateSetSnapshotTest.java index 05e102b9d..02d16e64f 100644 --- a/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/StateSetSnapshotTest.java +++ b/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/StateSetSnapshotTest.java @@ -10,7 +10,7 @@ class StateSetSnapshotTest { @Test - public void testCompleteGoodCase() { + void testCompleteGoodCase() { long scrapeTimestamp = System.currentTimeMillis() - TimeUnit.DAYS.toMillis(1); StateSetSnapshot snapshot = StateSetSnapshot.builder() @@ -49,7 +49,7 @@ public void testCompleteGoodCase() { } @Test - public void testStateSetDataSorted() { + void testStateSetDataSorted() { StateSetSnapshot.StateSetDataPointSnapshot data = StateSetSnapshot.StateSetDataPointSnapshot.builder() .state("b", true) @@ -69,14 +69,14 @@ public void testStateSetDataSorted() { } @Test - public void testMustHaveState() { + void testMustHaveState() { // Must have at least one state. assertThatExceptionOfType(IllegalArgumentException.class) .isThrownBy(() -> StateSetSnapshot.StateSetDataPointSnapshot.builder().build()); } @Test - public void testMinimal() { + void testMinimal() { StateSetSnapshot snapshot = StateSetSnapshot.builder() .name("my_flag") @@ -87,13 +87,13 @@ public void testMinimal() { } @Test - public void testEmpty() { + void testEmpty() { StateSetSnapshot snapshot = StateSetSnapshot.builder().name("my_flag").build(); assertThat(snapshot.dataPoints).isEmpty(); } @Test - public void testDataImmutable() { + void testDataImmutable() { StateSetSnapshot.StateSetDataPointSnapshot data = StateSetSnapshot.StateSetDataPointSnapshot.builder() .state("a", true) @@ -111,7 +111,7 @@ public void testDataImmutable() { } @Test - public void testDuplicateState() { + void testDuplicateState() { assertThatExceptionOfType(IllegalArgumentException.class) .isThrownBy( () -> @@ -123,13 +123,13 @@ public void testDuplicateState() { } @Test - public void noUnit() { + void noUnit() { assertThatExceptionOfType(IllegalArgumentException.class) .isThrownBy(() -> StateSetSnapshot.builder().name("flags").unit(Unit.BYTES).build()); } @Test - public void testStateSetImmutable() { + void testStateSetImmutable() { StateSetSnapshot snapshot = StateSetSnapshot.builder() .name("flags") @@ -151,7 +151,7 @@ public void testStateSetImmutable() { } @Test - public void testLabelsUnique() { + void testLabelsUnique() { assertThatExceptionOfType(IllegalArgumentException.class) .isThrownBy( () -> diff --git a/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/SummarySnapshotTest.java b/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/SummarySnapshotTest.java index c9a12f393..8cd9571f5 100644 --- a/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/SummarySnapshotTest.java +++ b/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/SummarySnapshotTest.java @@ -2,13 +2,14 @@ import static org.assertj.core.api.Assertions.assertThat; +import io.prometheus.metrics.config.EscapingScheme; import java.util.concurrent.TimeUnit; import org.junit.jupiter.api.Test; class SummarySnapshotTest { @Test - public void testCompleteGoodCase() { + void testCompleteGoodCase() { long createdTimestamp = System.currentTimeMillis() - TimeUnit.DAYS.toMillis(1); long scrapeTimestamp = System.currentTimeMillis() - TimeUnit.MINUTES.toMillis(2); long exemplarTimestamp = System.currentTimeMillis(); @@ -81,7 +82,7 @@ public void testCompleteGoodCase() { } @Test - public void testMinimal() { + void testMinimal() { SummarySnapshot snapshot = SummarySnapshot.builder() .name("size_bytes") @@ -94,13 +95,13 @@ public void testMinimal() { } @Test - public void testEmptySnapshot() { + void testEmptySnapshot() { SummarySnapshot snapshot = SummarySnapshot.builder().name("empty_summary").build(); assertThat(snapshot.getDataPoints()).isEmpty(); } @Test - public void testEmptyData() { + void testEmptyData() { SummarySnapshot.SummaryDataPointSnapshot data = SummarySnapshot.SummaryDataPointSnapshot.builder().build(); assertThat(data.getQuantiles().size()).isZero(); @@ -110,4 +111,13 @@ public void testEmptyData() { assertThat(data.hasScrapeTimestamp()).isFalse(); assertThat(data.getExemplars().size()).isZero(); } + + @Test + void escape() { + SummarySnapshot.SummaryDataPointSnapshot data = + SummarySnapshot.SummaryDataPointSnapshot.builder().sum(12.0).build(); + assertThat(data.escape(EscapingScheme.UNDERSCORE_ESCAPING)) + .usingRecursiveComparison() + .isEqualTo(data); + } } diff --git a/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/UnitTest.java b/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/UnitTest.java index 4585e7281..e288a2375 100644 --- a/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/UnitTest.java +++ b/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/UnitTest.java @@ -8,12 +8,12 @@ class UnitTest { @Test - public void testEmpty() { + void testEmpty() { assertThatExceptionOfType(IllegalArgumentException.class).isThrownBy(() -> new Unit(" ")); } @Test - public void testEquals1() { + void testEquals1() { Unit unit1 = Unit.BYTES; Unit unit2 = new Unit("bytes"); @@ -21,7 +21,7 @@ public void testEquals1() { } @Test - public void testEquals2() { + void testEquals2() { Unit unit1 = new Unit("bytes "); Unit unit2 = new Unit("bytes"); @@ -29,7 +29,7 @@ public void testEquals2() { } @Test - public void testEquals3() { + void testEquals3() { Unit unit1 = new Unit(" bytes"); Unit unit2 = new Unit("bytes"); @@ -37,7 +37,7 @@ public void testEquals3() { } @Test - public void testEquals4() { + void testEquals4() { Unit unit1 = new Unit(" bytes "); Unit unit2 = new Unit("bytes"); diff --git a/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/UnknownSnapshotTest.java b/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/UnknownSnapshotTest.java index 53df0a95d..33b4ac2b4 100644 --- a/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/UnknownSnapshotTest.java +++ b/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/UnknownSnapshotTest.java @@ -9,7 +9,7 @@ class UnknownSnapshotTest { @Test - public void testCompleteGoodCase() { + void testCompleteGoodCase() { long exemplarTimestamp = System.currentTimeMillis(); UnknownSnapshot snapshot = UnknownSnapshot.builder() @@ -46,7 +46,7 @@ public void testCompleteGoodCase() { } @Test - public void testMinimal() { + void testMinimal() { UnknownSnapshot snapshot = UnknownSnapshot.builder() .name("test") @@ -56,25 +56,25 @@ public void testMinimal() { } @Test - public void testEmpty() { + void testEmpty() { UnknownSnapshot snapshot = UnknownSnapshot.builder().name("test").build(); assertThat(snapshot.getDataPoints()).isEmpty(); } @Test - public void testNameMissing() { + void testNameMissing() { assertThatExceptionOfType(IllegalArgumentException.class) .isThrownBy(() -> UnknownSnapshot.builder().build()); } @Test - public void testValueMissing() { + void testValueMissing() { assertThatExceptionOfType(IllegalArgumentException.class) .isThrownBy(() -> UnknownSnapshot.UnknownDataPointSnapshot.builder().build()); } @Test - public void testUnknownDataPointSnapshot() { + void testUnknownDataPointSnapshot() { Labels labels = Labels.of("k1", "v1"); Exemplar exemplar = Exemplar.builder().value(2.0).build(); diff --git a/prometheus-metrics-otel-support/pom.xml b/prometheus-metrics-otel-support/pom.xml new file mode 100644 index 000000000..18328637e --- /dev/null +++ b/prometheus-metrics-otel-support/pom.xml @@ -0,0 +1,52 @@ + + + 4.0.0 + + + io.prometheus + client_java + 1.6.0-SNAPSHOT + + + prometheus-metrics-otel-support + pom + + Prometheus Metrics OpenTelemetry Support + + Bundles the OpenTelemetry SDK and Prometheus exporter so that users + who want to combine OTel instrumentations with the Prometheus Java + client only need a single dependency. + + + + true + true + true + true + + + + + + io.opentelemetry.instrumentation + opentelemetry-instrumentation-bom-alpha + ${otel.instrumentation.version} + pom + import + + + + + + + io.opentelemetry + opentelemetry-sdk + + + io.opentelemetry + opentelemetry-exporter-prometheus + + + diff --git a/prometheus-metrics-parent/pom.xml b/prometheus-metrics-parent/pom.xml index 4f9b3ad9d..f70317154 100644 --- a/prometheus-metrics-parent/pom.xml +++ b/prometheus-metrics-parent/pom.xml @@ -7,7 +7,7 @@ io.prometheus client_java_parent - 1.4.0-SNAPSHOT + 1.6.0-SNAPSHOT Prometheus Metrics Library Parent http://github.com/prometheus/client_java @@ -68,29 +68,39 @@ - - com.diffplug.spotless - spotless-maven-plugin - 2.46.1 - - - - - ${spotless.skip} - - - - verify - - check - - - - + + java17-plus + + [17,) + + + + + com.diffplug.spotless + spotless-maven-plugin + 3.2.1 + + + + + ${spotless.skip} + + + + verify + + check + + + + + + + release @@ -118,7 +128,7 @@ org.apache.maven.plugins maven-source-plugin - 3.3.1 + 3.4.0 attach-sources @@ -131,7 +141,7 @@ org.sonatype.central central-publishing-maven-plugin - 0.8.0 + 0.10.0 true ossrh diff --git a/prometheus-metrics-simpleclient-bridge/pom.xml b/prometheus-metrics-simpleclient-bridge/pom.xml index 4aa5ce156..80ae0b6e4 100644 --- a/prometheus-metrics-simpleclient-bridge/pom.xml +++ b/prometheus-metrics-simpleclient-bridge/pom.xml @@ -6,7 +6,7 @@ io.prometheus client_java - 1.4.0-SNAPSHOT + 1.6.0-SNAPSHOT prometheus-metrics-simpleclient-bridge diff --git a/prometheus-metrics-simpleclient-bridge/src/main/java/io/prometheus/metrics/simpleclient/bridge/SimpleclientCollector.java b/prometheus-metrics-simpleclient-bridge/src/main/java/io/prometheus/metrics/simpleclient/bridge/SimpleclientCollector.java index 5873a4977..3a96453e7 100644 --- a/prometheus-metrics-simpleclient-bridge/src/main/java/io/prometheus/metrics/simpleclient/bridge/SimpleclientCollector.java +++ b/prometheus-metrics-simpleclient-bridge/src/main/java/io/prometheus/metrics/simpleclient/bridge/SimpleclientCollector.java @@ -1,6 +1,7 @@ package io.prometheus.metrics.simpleclient.bridge; import static io.prometheus.metrics.model.snapshots.PrometheusNaming.sanitizeMetricName; +import static java.util.Objects.requireNonNull; import io.prometheus.client.Collector; import io.prometheus.client.CollectorRegistry; @@ -29,6 +30,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import javax.annotation.Nullable; /** * Bridge from {@code simpleclient} (version 0.16.0 and older) to the new {@code prometheus-metrics} @@ -116,7 +118,10 @@ private MetricSnapshot convertCounter(Collector.MetricFamilySamples samples) { if (sample.name.endsWith("_created")) { dataPoint.createdTimestampMillis((long) Unit.secondsToMillis(sample.value)); } else { - dataPoint.value(sample.value).exemplar(convertExemplar(sample.exemplar)); + dataPoint.value(sample.value); + if (sample.exemplar != null) { + dataPoint.exemplar(convertExemplar(sample.exemplar)); + } if (sample.timestampMs != null) { dataPoint.scrapeTimestampMillis(sample.timestampMs); } @@ -138,8 +143,10 @@ private MetricSnapshot convertGauge(Collector.MetricFamilySamples samples) { GaugeSnapshot.GaugeDataPointSnapshot.Builder dataPoint = GaugeSnapshot.GaugeDataPointSnapshot.builder() .value(sample.value) - .labels(Labels.of(sample.labelNames, sample.labelValues)) - .exemplar(convertExemplar(sample.exemplar)); + .labels(Labels.of(sample.labelNames, sample.labelValues)); + if (sample.exemplar != null) { + dataPoint.exemplar(convertExemplar(sample.exemplar)); + } if (sample.timestampMs != null) { dataPoint.scrapeTimestampMillis(sample.timestampMs); } @@ -183,10 +190,9 @@ private MetricSnapshot convertHistogram( } for (Labels labels : dataPoints.keySet()) { histogram.dataPoint( - dataPoints - .get(labels) - .classicHistogramBuckets(makeBuckets(cumulativeBuckets.get(labels))) - .exemplars(exemplars.get(labels).build()) + requireNonNull(dataPoints.get(labels)) + .classicHistogramBuckets(makeBuckets(requireNonNull(cumulativeBuckets.get(labels)))) + .exemplars(requireNonNull(exemplars.get(labels)).build()) .build()); } return histogram.build(); @@ -233,10 +239,9 @@ private MetricSnapshot convertSummary(Collector.MetricFamilySamples samples) { } for (Labels labels : dataPoints.keySet()) { summary.dataPoint( - dataPoints - .get(labels) - .quantiles(quantiles.get(labels).build()) - .exemplars(exemplars.get(labels).build()) + requireNonNull(dataPoints.get(labels)) + .quantiles(requireNonNull(quantiles.get(labels)).build()) + .exemplars(requireNonNull(exemplars.get(labels)).build()) .build()); } return summary.build(); @@ -281,8 +286,10 @@ private MetricSnapshot convertUnknown(Collector.MetricFamilySamples samples) { UnknownSnapshot.UnknownDataPointSnapshot.Builder dataPoint = UnknownSnapshot.UnknownDataPointSnapshot.builder() .value(sample.value) - .labels(Labels.of(sample.labelNames, sample.labelValues)) - .exemplar(convertExemplar(sample.exemplar)); + .labels(Labels.of(sample.labelNames, sample.labelValues)); + if (sample.exemplar != null) { + dataPoint.exemplar(convertExemplar(sample.exemplar)); + } if (sample.timestampMs != null) { dataPoint.scrapeTimestampMillis(sample.timestampMs); } @@ -291,6 +298,7 @@ private MetricSnapshot convertUnknown(Collector.MetricFamilySamples samples) { return unknown.build(); } + @Nullable private Unit convertUnit(Collector.MetricFamilySamples samples) { if (samples.unit != null && !samples.unit.isEmpty()) { return new Unit(samples.unit); @@ -306,7 +314,7 @@ private ClassicHistogramBuckets makeBuckets(Map cumulativeBuckets) ClassicHistogramBuckets.Builder result = ClassicHistogramBuckets.builder(); long previousCount = 0L; for (Double upperBound : upperBounds) { - long cumulativeCount = cumulativeBuckets.get(upperBound); + long cumulativeCount = requireNonNull(cumulativeBuckets.get(upperBound)); result.bucket(upperBound, cumulativeCount - previousCount); previousCount = cumulativeCount; } @@ -358,9 +366,6 @@ private MetricSnapshot convertInfo(Collector.MetricFamilySamples samples) { } private Exemplar convertExemplar(io.prometheus.client.exemplars.Exemplar exemplar) { - if (exemplar == null) { - return null; - } Exemplar.Builder result = Exemplar.builder().value(exemplar.getValue()); if (exemplar.getTimestampMs() != null) { result.timestampMillis(exemplar.getTimestampMs()); @@ -388,7 +393,7 @@ public static Builder builder() { public static class Builder { - private CollectorRegistry collectorRegistry; + @Nullable private CollectorRegistry collectorRegistry; private Builder() {} diff --git a/prometheus-metrics-simpleclient-bridge/src/test/java/io/prometheus/metrics/simpleclient/bridge/SimpleclientCollectorTest.java b/prometheus-metrics-simpleclient-bridge/src/test/java/io/prometheus/metrics/simpleclient/bridge/SimpleclientCollectorTest.java index 89cae65ba..3905f8f42 100644 --- a/prometheus-metrics-simpleclient-bridge/src/test/java/io/prometheus/metrics/simpleclient/bridge/SimpleclientCollectorTest.java +++ b/prometheus-metrics-simpleclient-bridge/src/test/java/io/prometheus/metrics/simpleclient/bridge/SimpleclientCollectorTest.java @@ -10,6 +10,7 @@ import io.prometheus.client.Info; import io.prometheus.client.Summary; import io.prometheus.client.exporter.common.TextFormat; +import io.prometheus.metrics.config.EscapingScheme; import io.prometheus.metrics.expositionformats.OpenMetricsTextFormatWriter; import io.prometheus.metrics.model.registry.PrometheusRegistry; import java.io.ByteArrayOutputStream; @@ -29,14 +30,14 @@ class SimpleclientCollectorTest { private PrometheusRegistry newRegistry; @BeforeEach - public void setUp() { + void setUp() { origRegistry = new CollectorRegistry(); newRegistry = new PrometheusRegistry(); SimpleclientCollector.builder().collectorRegistry(origRegistry).register(newRegistry); } @Test - public void testCounterComplete() throws IOException, InterruptedException { + void testCounterComplete() throws IOException, InterruptedException { Counter counter = Counter.build() .name("service_time_seconds_total") @@ -51,14 +52,14 @@ public void testCounterComplete() throws IOException, InterruptedException { } @Test - public void testCounterMinimal() throws IOException { + void testCounterMinimal() throws IOException { Counter.build().name("events").help("total number of events").register(origRegistry); assertThat(sort(newOpenMetrics())).isEqualTo(fixTimestamps(sort(origOpenMetrics()))); } @Test - public void testGaugeComplete() throws IOException, InterruptedException { + void testGaugeComplete() throws IOException, InterruptedException { Gauge gauge = Gauge.build() .name("disk_usage_ratio") @@ -74,7 +75,7 @@ public void testGaugeComplete() throws IOException, InterruptedException { } @Test - public void testGaugeMinimal() throws IOException { + void testGaugeMinimal() throws IOException { Gauge gauge = Gauge.build() .name("temperature_centigrade") @@ -87,7 +88,7 @@ public void testGaugeMinimal() throws IOException { } @Test - public void testHistogramComplete() throws IOException, InterruptedException { + void testHistogramComplete() throws IOException, InterruptedException { Histogram histogram = Histogram.build() .name("response_size_bytes") @@ -107,14 +108,14 @@ public void testHistogramComplete() throws IOException, InterruptedException { } @Test - public void testHistogramMinimal() throws IOException { + void testHistogramMinimal() throws IOException { Histogram.build().name("request_latency").help("request latency").register(origRegistry); assertThat(sort(newOpenMetrics())).isEqualTo(fixCounts(fixTimestamps(sort(origOpenMetrics())))); } @Test - public void testSummaryComplete() throws IOException, InterruptedException { + void testSummaryComplete() throws IOException, InterruptedException { Summary summary = Summary.build() .name("http_request_duration_seconds") @@ -138,14 +139,14 @@ public void testSummaryComplete() throws IOException, InterruptedException { } @Test - public void testSummaryMinimal() throws IOException { + void testSummaryMinimal() throws IOException { Summary.build().name("request_size").help("request size").register(origRegistry); assertThat(sort(newOpenMetrics())).isEqualTo(fixCounts(fixTimestamps(sort(origOpenMetrics())))); } @Test - public void testInfoComplete() throws IOException, InterruptedException { + void testInfoComplete() throws IOException, InterruptedException { Info info = Info.build() .name("version") @@ -160,7 +161,7 @@ public void testInfoComplete() throws IOException, InterruptedException { } @Test - public void testInfoMinimal() throws IOException { + void testInfoMinimal() throws IOException { Info info = Info.build().name("jvm").help("JVM info").register(origRegistry); info.info("version", "17"); @@ -168,7 +169,7 @@ public void testInfoMinimal() throws IOException { } @Test - public void testStateSetComplete() throws IOException { + void testStateSetComplete() throws IOException { Collector stateSet = new Collector() { @Override @@ -191,7 +192,7 @@ public List collect() { } @Test - public void testUnknownComplete() throws IOException { + void testUnknownComplete() throws IOException { Collector unknown = new Collector() { @Override @@ -266,7 +267,7 @@ private String origOpenMetrics() throws IOException { private String newOpenMetrics() throws IOException { ByteArrayOutputStream out = new ByteArrayOutputStream(); OpenMetricsTextFormatWriter writer = new OpenMetricsTextFormatWriter(true, false); - writer.write(out, newRegistry.scrape()); - return out.toString(StandardCharsets.UTF_8.name()); + writer.write(out, newRegistry.scrape(), EscapingScheme.ALLOW_UTF8); + return out.toString(StandardCharsets.UTF_8); } } diff --git a/prometheus-metrics-tracer/pom.xml b/prometheus-metrics-tracer/pom.xml index fa864ec2c..a97ae0a54 100644 --- a/prometheus-metrics-tracer/pom.xml +++ b/prometheus-metrics-tracer/pom.xml @@ -6,7 +6,7 @@ io.prometheus client_java - 1.4.0-SNAPSHOT + 1.6.0-SNAPSHOT prometheus-metrics-tracer diff --git a/prometheus-metrics-tracer/prometheus-metrics-tracer-common/pom.xml b/prometheus-metrics-tracer/prometheus-metrics-tracer-common/pom.xml index 4678e72dc..11d4fcc80 100644 --- a/prometheus-metrics-tracer/prometheus-metrics-tracer-common/pom.xml +++ b/prometheus-metrics-tracer/prometheus-metrics-tracer-common/pom.xml @@ -6,7 +6,7 @@ io.prometheus prometheus-metrics-tracer - 1.4.0-SNAPSHOT + 1.6.0-SNAPSHOT prometheus-metrics-tracer-common diff --git a/prometheus-metrics-tracer/prometheus-metrics-tracer-common/src/main/java/io/prometheus/metrics/tracer/common/SpanContext.java b/prometheus-metrics-tracer/prometheus-metrics-tracer-common/src/main/java/io/prometheus/metrics/tracer/common/SpanContext.java index 7ff30a09f..7d087f8b5 100644 --- a/prometheus-metrics-tracer/prometheus-metrics-tracer-common/src/main/java/io/prometheus/metrics/tracer/common/SpanContext.java +++ b/prometheus-metrics-tracer/prometheus-metrics-tracer-common/src/main/java/io/prometheus/metrics/tracer/common/SpanContext.java @@ -1,5 +1,7 @@ package io.prometheus.metrics.tracer.common; +import javax.annotation.Nullable; + public interface SpanContext { String EXEMPLAR_ATTRIBUTE_NAME = "exemplar"; @@ -9,12 +11,14 @@ public interface SpanContext { * @return the current trace id, or {@code null} if this call is not happening within a span * context. */ + @Nullable String getCurrentTraceId(); /** * @return the current span id, or {@code null} if this call is not happening within a span * context. */ + @Nullable String getCurrentSpanId(); /** diff --git a/prometheus-metrics-tracer/prometheus-metrics-tracer-initializer/pom.xml b/prometheus-metrics-tracer/prometheus-metrics-tracer-initializer/pom.xml index cc12196b8..07dc768f5 100644 --- a/prometheus-metrics-tracer/prometheus-metrics-tracer-initializer/pom.xml +++ b/prometheus-metrics-tracer/prometheus-metrics-tracer-initializer/pom.xml @@ -6,7 +6,7 @@ io.prometheus prometheus-metrics-tracer - 1.4.0-SNAPSHOT + 1.6.0-SNAPSHOT prometheus-metrics-tracer-initializer diff --git a/prometheus-metrics-tracer/prometheus-metrics-tracer-initializer/src/main/java/io/prometheus/metrics/tracer/initializer/SpanContextSupplier.java b/prometheus-metrics-tracer/prometheus-metrics-tracer-initializer/src/main/java/io/prometheus/metrics/tracer/initializer/SpanContextSupplier.java index de8bf24f5..0e39e19ac 100644 --- a/prometheus-metrics-tracer/prometheus-metrics-tracer-initializer/src/main/java/io/prometheus/metrics/tracer/initializer/SpanContextSupplier.java +++ b/prometheus-metrics-tracer/prometheus-metrics-tracer-initializer/src/main/java/io/prometheus/metrics/tracer/initializer/SpanContextSupplier.java @@ -4,6 +4,7 @@ import io.prometheus.metrics.tracer.common.SpanContext; import io.prometheus.metrics.tracer.otel.OpenTelemetrySpanContext; import java.util.concurrent.atomic.AtomicReference; +import javax.annotation.Nullable; public class SpanContextSupplier { @@ -18,6 +19,7 @@ public static boolean hasSpanContext() { return getSpanContext() != null; } + @Nullable public static SpanContext getSpanContext() { return spanContextRef.get(); } diff --git a/prometheus-metrics-tracer/prometheus-metrics-tracer-otel-agent/pom.xml b/prometheus-metrics-tracer/prometheus-metrics-tracer-otel-agent/pom.xml index 13d147de0..4db6ea530 100644 --- a/prometheus-metrics-tracer/prometheus-metrics-tracer-otel-agent/pom.xml +++ b/prometheus-metrics-tracer/prometheus-metrics-tracer-otel-agent/pom.xml @@ -7,7 +7,7 @@ io.prometheus prometheus-metrics-tracer - 1.4.0-SNAPSHOT + 1.6.0-SNAPSHOT prometheus-metrics-tracer-otel-agent diff --git a/prometheus-metrics-tracer/prometheus-metrics-tracer-otel-agent/src/main/java/io/prometheus/metrics/tracer/agent/OpenTelemetryAgentSpanContext.java b/prometheus-metrics-tracer/prometheus-metrics-tracer-otel-agent/src/main/java/io/prometheus/metrics/tracer/agent/OpenTelemetryAgentSpanContext.java index f5f93e6f3..265995cb8 100644 --- a/prometheus-metrics-tracer/prometheus-metrics-tracer-otel-agent/src/main/java/io/prometheus/metrics/tracer/agent/OpenTelemetryAgentSpanContext.java +++ b/prometheus-metrics-tracer/prometheus-metrics-tracer-otel-agent/src/main/java/io/prometheus/metrics/tracer/agent/OpenTelemetryAgentSpanContext.java @@ -4,6 +4,7 @@ import io.opentelemetry.api.trace.SpanId; import io.opentelemetry.api.trace.TraceId; import io.prometheus.metrics.tracer.common.SpanContext; +import javax.annotation.Nullable; /** * This is exactly the same as the {@code OpenTelemetrySpanContextSupplier}. However, the {@code @@ -31,12 +32,14 @@ public static boolean isAvailable() { } @Override + @Nullable public String getCurrentTraceId() { String traceId = Span.current().getSpanContext().getTraceId(); return TraceId.isValid(traceId) ? traceId : null; } @Override + @Nullable public String getCurrentSpanId() { String spanId = Span.current().getSpanContext().getSpanId(); return SpanId.isValid(spanId) ? spanId : null; diff --git a/prometheus-metrics-tracer/prometheus-metrics-tracer-otel/pom.xml b/prometheus-metrics-tracer/prometheus-metrics-tracer-otel/pom.xml index 6d435d38d..955d1cf34 100644 --- a/prometheus-metrics-tracer/prometheus-metrics-tracer-otel/pom.xml +++ b/prometheus-metrics-tracer/prometheus-metrics-tracer-otel/pom.xml @@ -6,7 +6,7 @@ io.prometheus prometheus-metrics-tracer - 1.4.0-SNAPSHOT + 1.6.0-SNAPSHOT prometheus-metrics-tracer-otel diff --git a/prometheus-metrics-tracer/prometheus-metrics-tracer-otel/src/main/java/io/prometheus/metrics/tracer/otel/OpenTelemetrySpanContext.java b/prometheus-metrics-tracer/prometheus-metrics-tracer-otel/src/main/java/io/prometheus/metrics/tracer/otel/OpenTelemetrySpanContext.java index 73f8d1316..d760cdff2 100644 --- a/prometheus-metrics-tracer/prometheus-metrics-tracer-otel/src/main/java/io/prometheus/metrics/tracer/otel/OpenTelemetrySpanContext.java +++ b/prometheus-metrics-tracer/prometheus-metrics-tracer-otel/src/main/java/io/prometheus/metrics/tracer/otel/OpenTelemetrySpanContext.java @@ -4,6 +4,7 @@ import io.opentelemetry.api.trace.SpanId; import io.opentelemetry.api.trace.TraceId; import io.prometheus.metrics.tracer.common.SpanContext; +import javax.annotation.Nullable; public class OpenTelemetrySpanContext implements SpanContext { @@ -26,12 +27,14 @@ public static boolean isAvailable() { } @Override + @Nullable public String getCurrentTraceId() { String traceId = Span.current().getSpanContext().getTraceId(); return TraceId.isValid(traceId) ? traceId : null; } @Override + @Nullable public String getCurrentSpanId() { String spanId = Span.current().getSpanContext().getSpanId(); return SpanId.isValid(spanId) ? spanId : null; diff --git a/scripts/build-release.sh b/scripts/build-release.sh deleted file mode 100755 index 94348d8ee..000000000 --- a/scripts/build-release.sh +++ /dev/null @@ -1,8 +0,0 @@ -#!/usr/bin/env bash - -set -euo pipefail - -VERSION=${TAG#v} - -./scripts/set-version.sh "$VERSION" -mvn -B package -P 'release,!default' -Dmaven.test.skip=true diff --git a/scripts/lint-bom.sh b/scripts/lint-bom.sh deleted file mode 100755 index e55a6d102..000000000 --- a/scripts/lint-bom.sh +++ /dev/null @@ -1,70 +0,0 @@ -#!/usr/bin/env bash - -set -euo pipefail - -function first_artifact_id() { - local bom_file="$1" - grep '' "$bom_file" | head -n 2 | tail -n 1 | - sed 's/.*\(.*\)<\/artifactId>.*/\1/' -} - -function add_dir() { - local dir="$1" - if [[ ! -d "$dir" ]]; then - echo "Directory $dir does not exist." - exit 1 - fi - - if [[ $ignore_dirs =~ $dir ]]; then - echo "Skipping $dir" - return - fi - - if [[ ! -f "$dir/pom.xml" ]]; then - echo "File $dir/pom.xml does not exist." - exit 1 - fi - - artifact_id=$(first_artifact_id "$dir/pom.xml") - if [[ -z "$artifact_id" ]]; then - echo "No artifactId found in $dir/pom.xml" - exit 1 - fi - - echo "Found artifactId '$artifact_id' in $dir/pom.xml" - # add to want - if [[ -z "${want+x}" ]]; then - want="$artifact_id" - else - want="$want -$artifact_id" - fi -} - -declare want -ignore_dirs="prometheus-metrics-parent" - -for dir in prometheus-metrics*; do - add_dir "$dir" -done -for dir in prometheus-metrics-tracer/prometheus-metrics*; do - if [[ -d "$dir" ]]; then - add_dir "$dir" - fi -done - -want=$(echo "$want" | sort | uniq) -have="$(grep 'prometheus-metrics' prometheus-metrics-bom/pom.xml | - sed 's/.*\(.*\)<\/artifactId>.*/\1/' | sort)" - -if [[ "$want" != "$have" ]]; then - echo "The BOM file prometheus-metrics-bom/bom.xml does not match the current directory contents." - echo "Expected: $want" - echo "Found: $have" - - diff -u <(echo "$have") <(echo "$want") - - exit 1 -else - echo "BOM file is up to date." -fi diff --git a/scripts/set-release-version-github-pages.sh b/scripts/set-release-version-github-pages.sh deleted file mode 100755 index 310c6f1b8..000000000 --- a/scripts/set-release-version-github-pages.sh +++ /dev/null @@ -1,7 +0,0 @@ -#!/usr/bin/env bash - -set -euox pipefail - -version=$(git tag -l | grep 'v' | sort | tail -1 | sed 's/v//') -marker="\$version" -sed -i "s/$marker/$version/g" docs/content/getting-started/quickstart.md diff --git a/scripts/set-version.sh b/scripts/set-version.sh deleted file mode 100755 index 728e11627..000000000 --- a/scripts/set-version.sh +++ /dev/null @@ -1,16 +0,0 @@ -#!/usr/bin/env bash - -set -euo pipefail - -VERSION=$1 - -if [ -z "$VERSION" ]; then - echo "Usage: $0 " - exit 1 -fi - -# replace all occurrences '1.4.0-SNAPSHOT' with '$VERSION' -# in all pom.xml files in the current directory and subdirectories - -find . -name 'pom.xml' -exec \ - sed -i "s/1.4.0-SNAPSHOT<\/version>/$VERSION<\/version>/g" {} + diff --git a/scripts/super-linter.sh b/scripts/super-linter.sh deleted file mode 100755 index 57c48274c..000000000 --- a/scripts/super-linter.sh +++ /dev/null @@ -1,16 +0,0 @@ -#!/usr/bin/env bash - -set -euo pipefail - -pushd "$(dirname "$0")/.." - -docker pull ghcr.io/super-linter/super-linter:latest - -docker run --rm \ - -e RUN_LOCAL=true \ - -e DEFAULT_BRANCH=main \ - --env-file ".github/super-linter.env" \ - -v "$(pwd)":/tmp/lint \ - ghcr.io/super-linter/super-linter:latest - -popd