\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(
+ {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..6f7f788dd
--- /dev/null
+++ b/.mise/tasks/update_benchmarks.py
@@ -0,0 +1,251 @@
+#!/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
+
+
+def run_cmd(cmd: list[str], cwd: str | None = 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="", flush=True)
+ 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: str | None) -> 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) -> str | None:
+ """
+ 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
+ content = None
+ try:
+ with open(path, "r", encoding="utf-8") as f:
+ content = f.read()
+ except (OSError, UnicodeError):
+ pass
+ if content is None:
+ 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, replacement_table: str = filtered_table) -> str:
+ prefix = m.group("prefix")
+ # Build the new block with the same prefix on each line
+ lines = replacement_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/jvm.config b/.mvn/jvm.config
new file mode 100644
index 000000000..32599cefe
--- /dev/null
+++ b/.mvn/jvm.config
@@ -0,0 +1,10 @@
+--add-exports jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED
+--add-exports jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED
+--add-exports jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED
+--add-exports jdk.compiler/com.sun.tools.javac.model=ALL-UNNAMED
+--add-exports jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED
+--add-exports jdk.compiler/com.sun.tools.javac.processing=ALL-UNNAMED
+--add-exports jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED
+--add-exports jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED
+--add-opens jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED
+--add-opens jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED
diff --git a/.mvn/wrapper/maven-wrapper.properties b/.mvn/wrapper/maven-wrapper.properties
new file mode 100644
index 000000000..e788e5e69
--- /dev/null
+++ b/.mvn/wrapper/maven-wrapper.properties
@@ -0,0 +1,2 @@
+distributionType=only-script
+distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.16/apache-maven-3.9.16-bin.zip
diff --git a/.travis.yml b/.travis.yml
deleted file mode 100644
index 294da3583..000000000
--- a/.travis.yml
+++ /dev/null
@@ -1,5 +0,0 @@
-language: java
-
-script:
-- mvn test
-- mvn javadoc:aggregate
diff --git a/.yaml-lint.yml b/.yaml-lint.yml
new file mode 100644
index 000000000..a06ffeed5
--- /dev/null
+++ b/.yaml-lint.yml
@@ -0,0 +1,5 @@
+extends: relaxed
+
+rules:
+ line-length:
+ max: 120
diff --git a/AGENTS.md b/AGENTS.md
new file mode 100644
index 000000000..2949d8089
--- /dev/null
+++ b/AGENTS.md
@@ -0,0 +1,163 @@
+# AGENTS.md
+
+This file provides guidance to AI coding agents 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)
+mise run compile
+
+# Run unit tests only (skips formatting and coverage)
+mise run test
+
+# Run all tests including integration tests
+mise run test-all
+
+# Run a single test class
+./mvnw test -Dtest=CounterTest \
+ -Dcoverage.skip=true
+
+# Run a single test method
+./mvnw test -Dtest=CounterTest#testIncrement \
+ -Dcoverage.skip=true
+
+# Run tests in a specific module
+./mvnw test -pl prometheus-metrics-core \
+ -Dcoverage.skip=true
+
+# Regenerate protobuf classes (after protobuf dep update)
+mise run generate
+```
+
+## Architecture
+
+The library follows a layered architecture where metrics
+flow from core types through a registry to exporters:
+
+```text
+prometheus-metrics-core (user-facing API)
+ │
+ ▼ collect()
+prometheus-metrics-model (immutable snapshots)
+ │
+ ▼
+prometheus-metrics-exposition-formats
+ │
+ ▼
+Exporters (httpserver, servlet, pushgateway, otel)
+```
+
+### Key Modules
+
+- **prometheus-metrics-core**: User-facing metric types
+ (Counter, Gauge, Histogram, Summary, Info, StateSet).
+ All metrics implement `Collector` with `collect()`.
+- **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 flint)
+- **Line length**: 100 characters
+ (enforced for ALL files including Markdown, Java, YAML)
+- **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 ...Assertions.assertThat`)
+- **Empty catch blocks**: Use `ignored` as the variable name
+- **Markdown code blocks**: Always specify language
+ (e.g., ` ```java`, ` ```bash`, ` ```text`)
+
+## Pull Requests
+
+- PR titles must use semantic/conventional prefixes, for example
+ `feat: ...`, `fix: ...`, `docs: ...`, `chore: ...`, or
+ `test: ...`.
+- Do not prefix PR titles with `[codex]`.
+- Match the PR title type to the primary user-facing change.
+
+## Linting
+
+Run `mise run lint:fix` before committing changes.
+If output includes `fixed`, keep those changes.
+If output includes `partial` or `review`, address the remaining issues and
+run `mise run lint:fix` again.
+
+Example output:
+flint: fixed: gofmt — commit before pushing | partial: cargo-clippy
+
+**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 via flint
+ - Static analysis (`Error Prone` with NullAway)
+ - Checkstyle validation
+ - Build succeeds (tests are skipped;
+ run `mise run test` or `mise run test-all` for tests)
+
+## API Design
+
+- For internal or SDK-facing classes, prefer static factories and builders
+ over adding new public constructors.
+- Keep constructors non-public unless they are intentionally part of the
+ stable API.
+
+## 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/AUTHORS.md b/AUTHORS.md
deleted file mode 100644
index 4b5814ef4..000000000
--- a/AUTHORS.md
+++ /dev/null
@@ -1,18 +0,0 @@
-The Prometheus project was started by Matt T. Proud (emeritus) and
-Julius Volz in 2012.
-
-Maintainers of this repository:
-
-* Björn Rabenstein
-* Brian Brazil
-
-The following individuals have contributed code to this repository
-(listed in alphabetical order):
-
-* Björn Rabenstein
-* Brian Brazil
-* Flavio W. Brasil
-* Julius Volz
-* Matt T. Proud
-* Michal Witkowski
-* Ursula Kallio
diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 000000000..81ba1f52a
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,144 @@
+# Changelog
+
+## [1.8.0](https://github.com/prometheus/client_java/compare/v1.7.0...v1.8.0) (2026-06-11)
+
+
+### Features
+
+* Add custom labels to exemplars ([#2191](https://github.com/prometheus/client_java/issues/2191)) ([fd1f3e8](https://github.com/prometheus/client_java/commit/fd1f3e85177ec4d4e4922f22f3aa79dc2dd7e17e))
+* add MetricMetadata.Builder, deprecate wide constructors ([#2202](https://github.com/prometheus/client_java/issues/2202)) ([adeef32](https://github.com/prometheus/client_java/commit/adeef32f303a9dfadee9b7702b255db193c9c533))
+
+
+### Bug Fixes
+
+* Avoid unnuecessary exemplar allocations ([#2209](https://github.com/prometheus/client_java/issues/2209)) ([0b6a91f](https://github.com/prometheus/client_java/commit/0b6a91f2bafe0fa15f6fe828f315103d8c20f9f9))
+* **deps:** update spring boot to v4.1.0 ([#2213](https://github.com/prometheus/client_java/issues/2213)) ([df25c08](https://github.com/prometheus/client_java/commit/df25c0821605b7edf7b87b9874a65d3d529592a5))
+
+
+### Documentation
+
+* cover typed family descriptors and @StableApi since v1.6.1 ([#2181](https://github.com/prometheus/client_java/issues/2181)) ([7ca9f99](https://github.com/prometheus/client_java/commit/7ca9f99b8f1731315d2cf8f68247fc94174a8b3b))
+
+## [1.7.0](https://github.com/prometheus/client_java/compare/v1.6.1...v1.7.0) (2026-06-03)
+
+
+### Features
+
+* Add StableApi marker and API diff check ([#2168](https://github.com/prometheus/client_java/issues/2168)) ([768fd3a](https://github.com/prometheus/client_java/commit/768fd3a7aab5f11f3558a35c0d6257b5a217a078))
+* add typed metric family descriptors ([#2114](https://github.com/prometheus/client_java/issues/2114)) ([9c3b097](https://github.com/prometheus/client_java/commit/9c3b097f6842ffc08fb3a2ed00217c73a6c2b191))
+* track api-diff baseline via Renovate and store diffs in docs/apidiffs ([#2174](https://github.com/prometheus/client_java/issues/2174)) ([3adb890](https://github.com/prometheus/client_java/commit/3adb89078df4bf3d7739886612d4cf051176a6f3))
+
+
+### Bug Fixes
+
+* **deps:** update dependency com.github.ben-manes.caffeine:caffeine to v3.2.4 ([#2088](https://github.com/prometheus/client_java/issues/2088)) ([144eb61](https://github.com/prometheus/client_java/commit/144eb61030d412afe83631b8f341d2cb1595ab1c))
+* **deps:** update dependency io.dropwizard.metrics:metrics-core to v4.2.39 ([#2139](https://github.com/prometheus/client_java/issues/2139)) ([5817d13](https://github.com/prometheus/client_java/commit/5817d1395dc348b6634ea169264fd13f4ad56e82))
+* **deps:** update dependency io.dropwizard.metrics5:metrics-core to v5.0.7 ([#2140](https://github.com/prometheus/client_java/issues/2140)) ([261c451](https://github.com/prometheus/client_java/commit/261c4510eefe156ad688e019b9239cfcfd39bd2b))
+* **deps:** update dependency io.opentelemetry.instrumentation:opentelemetry-instrumentation-bom-alpha to v2.28.0-alpha ([#2126](https://github.com/prometheus/client_java/issues/2126)) ([b62b5d0](https://github.com/prometheus/client_java/commit/b62b5d0ab4b8d3a1335286bd3d36e8c9ac5aa269))
+* **deps:** update dependency io.opentelemetry.instrumentation:opentelemetry-instrumentation-bom-alpha to v2.28.0-alpha ([#2127](https://github.com/prometheus/client_java/issues/2127)) ([e11ce3d](https://github.com/prometheus/client_java/commit/e11ce3de19daf5acd2f73ffb90c96689c172f3c3))
+* **deps:** update dependency io.opentelemetry.instrumentation:opentelemetry-instrumentation-bom-alpha to v2.28.1-alpha ([#2132](https://github.com/prometheus/client_java/issues/2132)) ([b09be38](https://github.com/prometheus/client_java/commit/b09be3882f0ad95ff299db41d706a2e52faa7525))
+* **deps:** update dependency io.opentelemetry.instrumentation:opentelemetry-instrumentation-bom-alpha to v2.28.1-alpha ([#2133](https://github.com/prometheus/client_java/issues/2133)) ([a241c16](https://github.com/prometheus/client_java/commit/a241c165927d3cbb91b97eedd52de9c9eff595d0))
+* **deps:** update dependency org.apache.tomcat.embed:tomcat-embed-core to v11.0.22 ([#2099](https://github.com/prometheus/client_java/issues/2099)) ([22125c5](https://github.com/prometheus/client_java/commit/22125c5f531467030793fc48cb2308ff14bbcaa7))
+* **deps:** update jetty monorepo to v12.1.10 ([#2169](https://github.com/prometheus/client_java/issues/2169)) ([ddd3991](https://github.com/prometheus/client_java/commit/ddd3991096d409a3e58ae2003ce13457a51b8876))
+* **deps:** update jetty monorepo to v12.1.9 ([#2102](https://github.com/prometheus/client_java/issues/2102)) ([04bee70](https://github.com/prometheus/client_java/commit/04bee70efff866f8c4966643926905c28a4eae3a))
+* **deps:** update protobuf ([#2129](https://github.com/prometheus/client_java/issues/2129)) ([320538a](https://github.com/prometheus/client_java/commit/320538a09efad128c6d80bcc3d6eecca394603db))
+* Reduce allocations for classic histogram buckets ([#2081](https://github.com/prometheus/client_java/issues/2081)) ([edd160a](https://github.com/prometheus/client_java/commit/edd160ab93254c80250d7cf58a1dcb399fef67a1))
+* restore legacy suffix compatibility ([#2100](https://github.com/prometheus/client_java/issues/2100)) ([b2ae70f](https://github.com/prometheus/client_java/commit/b2ae70ffd4ac0830fb567319beae9d1c3ad8bc2f))
+* restore reserved suffix stripping in `PrometheusNaming.sanitizeMetricName()` ([#2124](https://github.com/prometheus/client_java/issues/2124)) ([2d0f508](https://github.com/prometheus/client_java/commit/2d0f508efd2f5e009b6f09f6a9ccb451cf9f3b6f))
+
+
+### Performance Improvements
+
+* Refactored sorting to use optimized sort algorithms ([#2161](https://github.com/prometheus/client_java/issues/2161)) ([25b94fc](https://github.com/prometheus/client_java/commit/25b94fc16273659892af0132cedb71f57597adf7))
+
+
+### Documentation
+
+* clarify downstream adapter validation requirements ([#2101](https://github.com/prometheus/client_java/issues/2101)) ([ef8c75c](https://github.com/prometheus/client_java/commit/ef8c75cf352bddd0d3a2052c3f1b0c8b6103a6f4))
+* Document OM2 ([#2059](https://github.com/prometheus/client_java/issues/2059)) ([45d753c](https://github.com/prometheus/client_java/commit/45d753c418f005fbb17bf7caca3dc94655717687))
+* document PushGateway shading workaround ([#2106](https://github.com/prometheus/client_java/issues/2106)) ([8ca0eb8](https://github.com/prometheus/client_java/commit/8ca0eb8d79b800ad8d7a08f10762ed631f4f2a70))
+
+## [1.6.1](https://github.com/prometheus/client_java/compare/v1.6.0...v1.6.1) (2026-04-27)
+
+> Note: With the OM2 metric-name preservation fix in this release, OpenMetrics 2.0 can now be
+> tested. It is still in progress and not ready for general use yet.
+
+### Bug Fixes
+
+* Preserve original metric names in OM2 output ([#2058](https://github.com/prometheus/client_java/issues/2058)) ([59a7a6d](https://github.com/prometheus/client_java/commit/59a7a6d4d5a9eb31c33167764b11ba96d6625b74))
+
+
+### Documentation
+
+* clarify 1.6.0 release notes ([#2062](https://github.com/prometheus/client_java/issues/2062)) ([9e5d591](https://github.com/prometheus/client_java/commit/9e5d591f4c2e8e0d39ce5141ac14fff057b09c67))
+* Document semantic PR title guidance ([#2060](https://github.com/prometheus/client_java/issues/2060)) ([7277889](https://github.com/prometheus/client_java/commit/727788942cccbecfa57d75eee9fb3e942083a95e))
+
+## [1.6.0](https://github.com/prometheus/client_java/compare/v1.5.1...v1.6.0) (2026-04-25)
+
+> Note: OpenMetrics 2.0 support is still in progress and not ready for general use yet.
+>
+> As part of the OM2 work, metric-name suffix handling moved from metric creation time to scrape
+> time. A positive side effect is that metric names are now more flexible across the board, for
+> example names ending in suffixes like `_total` are accepted where they were previously rejected.
+> To keep the Prometheus and OM1 output unambiguous, the registry tracks claimed exposition names
+> and still rejects registrations that would collide at scrape time.
+>
+> Downstream adapter libraries that implement `MultiCollector` need their registration-time
+> metadata to match the metric families they emit at scrape time. When upgrading to 1.6.0+, adapter
+> registration metadata needs to stay aligned with emitted names, types, label names, and suffix
+> behavior under the new collision model.
+> See also: [Validation at registration only](docs/content/getting-started/registry.md#validation-at-registration-only)
+>
+> | Example | Before 1.6.0 | Since 1.6.0 | Reason |
+> | --- | --- | --- | --- |
+> | `Gauge("foo_total")` | Rejected | Allowed | Not breaking because this previously failed at registration, so no working setup changes behavior, and safe because `_total` suffix expansion applies to counters, not gauges. |
+> | `Counter("events_total")` | Rejected | Allowed | Not breaking because the OM1 output is still `events_total`; only the builder now accepts the name. |
+> | `Gauge("foo_total")` + `Histogram("foo")` | Rejected | Allowed | Not breaking because this combination used to be blocked even though the exposed names do not overlap. |
+> | `Gauge("events_total")` + `Counter("events")` | Rejected | Rejected | Not breaking because the ambiguous OM1 output would still expose two `events_total` series. |
+> | `Gauge("foo_count")` + `Histogram("foo")` | Allowed | Rejected | Intentionally breaking because the old behavior could expose a conflicting `foo_count` name at scrape time. |
+
+### Features
+
+* Relax metric name validation in Dropwizard5 ([#1985](https://github.com/prometheus/client_java/issues/1985)) ([deb782f](https://github.com/prometheus/client_java/commit/deb782f9fce60ffb1308a98b661c0a1ccb79a82b))
+
+
+### Bug Fixes
+
+* **deps:** update dependency com.google.guava:guava to v33.6.0-jre ([#2021](https://github.com/prometheus/client_java/issues/2021)) ([1382693](https://github.com/prometheus/client_java/commit/13826930b9c2f566040a6929090ef23c94e81796))
+* **deps:** update dependency commons-io:commons-io to v2.22.0 ([#2044](https://github.com/prometheus/client_java/issues/2044)) ([9e05c1d](https://github.com/prometheus/client_java/commit/9e05c1d56b7b0de17ba5aaaa300eb6433cc70824))
+* **deps:** update dependency io.opentelemetry.instrumentation:opentelemetry-instrumentation-bom-alpha to v2.27.0-alpha ([#2022](https://github.com/prometheus/client_java/issues/2022)) ([30ac534](https://github.com/prometheus/client_java/commit/30ac534d860fb7c60a1e7835723a6cf0035ea7f7))
+* **deps:** update dependency io.opentelemetry.instrumentation:opentelemetry-instrumentation-bom-alpha to v2.27.0-alpha ([#2023](https://github.com/prometheus/client_java/issues/2023)) ([2d51a32](https://github.com/prometheus/client_java/commit/2d51a3251f6943cf1b03ba9ea8778ff052f83ef9))
+* **deps:** update dependency io.prometheus:prometheus-metrics-bom to v1.5.1 ([#2004](https://github.com/prometheus/client_java/issues/2004)) ([650ce4b](https://github.com/prometheus/client_java/commit/650ce4b677f2ca65f5877e77260e403fa85533db))
+* **deps:** update dependency org.apache.tomcat.embed:tomcat-embed-core to v11.0.21 ([#2005](https://github.com/prometheus/client_java/issues/2005)) ([7a36df7](https://github.com/prometheus/client_java/commit/7a36df7151e55adafd5bb5a72af81fd7bf8f1133))
+* **deps:** update dependency org.springframework.boot:spring-boot-starter-parent to v4.0.5 ([#2006](https://github.com/prometheus/client_java/issues/2006)) ([0106c18](https://github.com/prometheus/client_java/commit/0106c18adffd6d3e829f73979917bdf7cc5f53dd))
+* **deps:** update dependency org.springframework.boot:spring-boot-starter-parent to v4.0.6 ([#2046](https://github.com/prometheus/client_java/issues/2046)) ([40a9db8](https://github.com/prometheus/client_java/commit/40a9db868805e36fbaa0f9ac3d02becb17104cd0))
+* **deps:** update jetty monorepo to v12.1.8 ([#2007](https://github.com/prometheus/client_java/issues/2007)) ([acab5b2](https://github.com/prometheus/client_java/commit/acab5b213e7661818470716158b3cfe67caae9da))
+* **deps:** update protobuf ([#2024](https://github.com/prometheus/client_java/issues/2024)) ([8e2214e](https://github.com/prometheus/client_java/commit/8e2214e0a3ac2fe8a9194d9519dcee10f6c9a694))
+* pass release tag as input to deploy workflow ([#1982](https://github.com/prometheus/client_java/issues/1982)) ([165c921](https://github.com/prometheus/client_java/commit/165c921c2508e073baa8f403b30e536ba9b43df9))
+* pin grafana/otel-lgtm to 0.7.2 in OATs acceptance test ([#1992](https://github.com/prometheus/client_java/issues/1992)) ([f17ad9a](https://github.com/prometheus/client_java/commit/f17ad9ad9be2ed0a8519db094f9d8fe9a8a83c48))
+* stabilize flaky timer and thread count tests ([#1973](https://github.com/prometheus/client_java/issues/1973)) ([ce5867b](https://github.com/prometheus/client_java/commit/ce5867b3e25e10c68a6face275732b994a80ec98))
+* trigger Maven deploy from release-please via workflow_dispatch ([#1981](https://github.com/prometheus/client_java/issues/1981)) ([698f956](https://github.com/prometheus/client_java/commit/698f9565e825cdb0f58d2782131cb152cc13894a))
+
+## [1.5.1](https://github.com/prometheus/client_java/compare/v1.5.0...v1.5.1) (2026-03-20)
+
+
+### Bug Fixes
+
+* **deps:** update dependency io.prometheus:prometheus-metrics-bom to v1.5.0 ([#1877](https://github.com/prometheus/client_java/issues/1877)) ([043fc57](https://github.com/prometheus/client_java/commit/043fc5742752fdc2f67f0219418030a190c53bde))
+* **deps:** update dependency org.springframework.boot:spring-boot-starter-parent to v4.0.3 ([#1900](https://github.com/prometheus/client_java/issues/1900)) ([0d800d0](https://github.com/prometheus/client_java/commit/0d800d0a91578e48f34909472c183174fdf1d83e))
+* **deps:** update jetty monorepo to v12.1.7 ([#1932](https://github.com/prometheus/client_java/issues/1932)) ([5bd3b79](https://github.com/prometheus/client_java/commit/5bd3b7932f454f3ed2cf55f26d6e1e1908d9ad16))
+* **deps:** update junit-framework monorepo to v6.0.3 ([#1880](https://github.com/prometheus/client_java/issues/1880)) ([05ad751](https://github.com/prometheus/client_java/commit/05ad751a40053f11eae90b9e6cbd741814ca71a7))
+* exclude standalone examples from `mise run format` ([#1931](https://github.com/prometheus/client_java/issues/1931)) ([537fb88](https://github.com/prometheus/client_java/commit/537fb88aae4048ab36041268f902afbbdce54a96))
+* fix release-please PR title pattern and permissions ([#1978](https://github.com/prometheus/client_java/issues/1978)) ([d737978](https://github.com/prometheus/client_java/commit/d7379780f1351a1521c8d93d0544bffce49d02a6))
+* Handle empty datapoints in otel exporter ([#1898](https://github.com/prometheus/client_java/issues/1898)) ([59c8552](https://github.com/prometheus/client_java/commit/59c8552f3d67c06d82344383b45e07fea8ed88b9))
+* inline set-version logic in build-release.sh ([#1884](https://github.com/prometheus/client_java/issues/1884)) ([c050435](https://github.com/prometheus/client_java/commit/c050435a4153046c72d158991c4c8e064dfb24ec))
+* reduce lychee retries to avoid compounding GitHub 429s ([#1940](https://github.com/prometheus/client_java/issues/1940)) ([cc17d6e](https://github.com/prometheus/client_java/commit/cc17d6e4346c9d51e054010fddc75cf8935cbc7d))
+* remove version manipulation from build-release.sh ([#1886](https://github.com/prometheus/client_java/issues/1886)) ([93e2b6d](https://github.com/prometheus/client_java/commit/93e2b6da48abc03ba7d96ffff5020ab73c1ee8c1))
+* trigger Maven deploy on release-please published events ([#1966](https://github.com/prometheus/client_java/issues/1966)) ([643d0e7](https://github.com/prometheus/client_java/commit/643d0e70c274e2a55024611c73a53545a65e94a0))
+* use /tree/ instead of /blob/ for directory URL ([#1944](https://github.com/prometheus/client_java/issues/1944)) ([b81332e](https://github.com/prometheus/client_java/commit/b81332e3a09e465f956f118a2403e64b83771ae5))
+* use maven release type for release-please ([#1967](https://github.com/prometheus/client_java/issues/1967)) ([ff3bd2d](https://github.com/prometheus/client_java/commit/ff3bd2d329acdb6761044846559c353a526c0384))
+
+
+### Documentation
+
+* document DCO sign-off requirement for contributions ([#1937](https://github.com/prometheus/client_java/issues/1937)) ([0860e77](https://github.com/prometheus/client_java/commit/0860e7742b08ab019d129ea24c348191c3f9e0da))
diff --git a/CLAUDE.md b/CLAUDE.md
new file mode 100644
index 000000000..6b5e23414
--- /dev/null
+++ b/CLAUDE.md
@@ -0,0 +1,3 @@
+
+
+@AGENTS.md
diff --git a/CODEOWNERS b/CODEOWNERS
new file mode 100644
index 000000000..1f70593ce
--- /dev/null
+++ b/CODEOWNERS
@@ -0,0 +1,4 @@
+# https://help.github.com/articles/about-codeowners/
+# https://git-scm.com/docs/gitignore#_pattern_format
+
+* @fstab @dhoard @zeitlinger @jaydeluca
diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md
new file mode 100644
index 000000000..d325872bd
--- /dev/null
+++ b/CODE_OF_CONDUCT.md
@@ -0,0 +1,3 @@
+# Prometheus Community Code of Conduct
+
+Prometheus follows the [CNCF Code of Conduct](https://github.com/cncf/foundation/blob/main/code-of-conduct.md).
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 5c8ea596a..1b42bfaf4 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -2,11 +2,142 @@
Prometheus uses GitHub to manage reviews of pull requests.
-* If you have a trivial fix or improvement, go ahead and create a pull
- request, addressing (with `@...`) one or more of the maintainers
- (see [AUTHORS.md](AUTHORS.md)) in the description of the pull request.
+- If you have a trivial fix or improvement, go ahead and create a pull request,
+ addressing (with `@...`) the maintainer of this repository (see
+ [MAINTAINERS.md](MAINTAINERS.md)) in the
+ description of the pull request.
-* If you plan to do something more involved, first discuss your ideas
+- If you plan to do something more involved, first discuss your ideas
on our [mailing list](https://groups.google.com/forum/?fromgroups#!forum/prometheus-developers).
This will avoid unnecessary work and surely give you and us a good deal
of inspiration.
+
+## Pull Request Titles
+
+Use a [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/#summary)-style
+title for pull requests:
+
+```text
+[optional scope]:
+```
+
+For example: `fix(metrics): handle empty scrapes`. Common types include `feat`, `fix`,
+`docs`, `test`, `refactor`, `perf`, `build`, `ci`, `chore`, and `revert`.
+
+## Signing Off Commits
+
+Every commit must include a `Signed-off-by` line, as required by the
+[Developer Certificate of Origin (DCO)](https://developercertificate.org/).
+
+Sign off each commit by passing `--signoff` (or `-s`) to `git commit`:
+
+```bash
+git commit --signoff -m "Your commit message"
+```
+
+To sign off only the most recent commit, use `--amend`:
+
+```bash
+git commit --amend --signoff --no-edit
+```
+
+To sign off multiple commits, rebase (replace `N` with the number of commits):
+
+```bash
+git rebase --signoff HEAD~N
+```
+
+Then force-push the branch:
+
+```bash
+git push --force-with-lease
+```
+
+## Formatting
+
+This repository uses flint to run formatting and lint checks.
+
+Run all the linters:
+
+`mise run lint`
+
+To autofix linting issues:
+
+`mise run lint:fix`
+
+### Pre-commit Hook (Optional)
+
+Run `mise run setup:pre-commit-hook` to install a git pre-commit hook
+that auto-lints changed files before each commit.
+This requires native lint tools,
+which you can install with `mise run setup:native-lint-tools`.
+These are optional but catch formatting and lint issues before CI.
+
+## API Design
+
+For internal or SDK-facing classes, prefer static factories and builders over
+adding new public constructors. Constructors are difficult to evolve
+compatibly, so keep them non-public unless they are intentionally part of the
+stable API.
+
+## Running Tests
+
+If you're getting errors when running tests:
+
+- Make sure that the IDE uses only the "Maven Shade" dependency of "
+ prometheus-metrics-exposition-formats" and the "prometheus-metrics-tracer\*" dependencies.
+
+### Running native tests
+
+```shell
+mise --cd .mise/envs/native run native-test
+```
+
+### Avoid failures while running tests
+
+- Use `-Dcoverage.skip=true` to skip the coverage check during development.
+- Use `-Dwarnings=-nowarn` to skip the warnings during development.
+
+Combine all with
+
+```shell
+./mvnw install -DskipTests -Dcoverage.skip=true -Dwarnings=-nowarn
+```
+
+or simply
+
+```shell
+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
+```
+
+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/MAINTAINERS.md b/MAINTAINERS.md
new file mode 100644
index 000000000..a247eb14a
--- /dev/null
+++ b/MAINTAINERS.md
@@ -0,0 +1,10 @@
+# Maintainers
+
+- Fabian Stäber @fstab
+- Doug Hoard @dhoard
+- Gregor Zeitlinger @zeitlinger
+- Jay DeLuca @jaydeluca
+
+## Emeritus
+
+- Tom Wilkie @tomwilkie
diff --git a/NOTICE b/NOTICE
index b5846b48e..c920ec3fe 100644
--- a/NOTICE
+++ b/NOTICE
@@ -6,3 +6,6 @@ Boxever Ltd. (http://www.boxever.com/).
This product includes software developed at
SoundCloud Ltd. (http://soundcloud.com/).
+
+This product includes software developed as part of the
+Ocelli project by Netflix Inc. (https://github.com/Netflix/ocelli/).
diff --git a/README.md b/README.md
index c08e6827b..daef2ac3d 100644
--- a/README.md
+++ b/README.md
@@ -1,172 +1,35 @@
-# Prometheus JVM Client
-It supports Java, Clojure, Scala, JRuby, and anything else that runs on the JVM.
+# Prometheus Java Metrics Library
-## Using
-### Assets
-If you use Maven, you can simply reference the assets below. The latest
-version can be found on in the maven repository for
-[io.prometheus](http://mvnrepository.com/artifact/io.prometheus) and
-[io.prometheus.client.utility](http://mvnrepository.com/artifact/io.prometheus.client.utility).
+
-#### Simpleclient
+[![Build][build-badge]][build-workflow]
+
+
-```
-
-
- io.prometheus
- simpleclient
- 0.0.10
-
-
-
- io.prometheus
- simpleclient_hotspot
- 0.0.10
-
-
-
- io.prometheus
- simpleclient_servlet
- 0.0.10
-
-
-
- io.prometheus
- simpleclient_pushgateway
- 0.0.10
-
-```
+
-#### Original client
-```
-
-
- io.prometheus
- client
- 0.0.10
-
-
-
- io.prometheus.client.utility
- jvmstat
- 0.0.10
-
-
-
- io.prometheus.client.utility
- jvmstat
- 0.0.10
-
-
-
- io.prometheus.client.utility
- metrics
- 0.0.10
-
-
-
- io.prometheus.client.utility
- servlet
- 0.0.10
-
-```
-
-### Getting Started
-There are canonical examples defined in the class definition Javadoc of the client packages.
+[build-badge]: https://github.com/prometheus/client_java/actions/workflows/build.yml/badge.svg
+[build-workflow]: https://github.com/prometheus/client_java/actions/workflows/build.yml
## Documentation
-The client is canonically documented with Javadoc. Running the following will produce local documentation
-in _apidocs_ directories for you to read.
-
- $ mvn package
-
-If you use the Mavenized version of the Prometheus client, you can also instruct Maven to download the Javadoc and
-source artifacts.
-
-Alternatively, you can also look at the generated [Java Client
-Github Project Page](http://prometheus.github.io/client_java), but the raw
-Javadoc in Java source in version control should be treated as the canonical
-form of documentation.
-
-## Maintenance of this Library
-This suite is built and managed by [Maven](http://maven.apache.org), and the
-artifacts are hosted on the [Sonatype OSS Asset Repository](https://docs.sonatype.org/display/Repository/Sonatype+OSS+Maven+Repository+Usage+Guide).
-
-All contributions to this library must follow, as far as practical, the
-prevalent patterns in the library for consistency and the following style
-guide: [Google Java Style](http://goo.gl/FfwVsc). Depending upon your
-development environment, you may be able to find an automatic formatter
-and adherence checker that follows these rules.
-
-### Building
-
- $ mvn compile
-
-### Testing
-
- $ mvn test
-
-Please note that tests on Travis may be unreliable due to the absence of
-installed Maven artifacts. Ensure that the current snapshot version is
-deployed to Sonatype OSS Repository.
-
-### Deployment
-These steps below are only useful if you are in a release engineering capacity
-and want to publicize these changes for external users. You will also need to
-have your local Maven setup correctly along with valid and public GPG key and
-adequate authorization on the Sonatype OSS Repository to submit new artifacts,
-be they _staging_ or _release_ ones.
-
-You should read the [Sonatype OSS Apache Maven
-Guide](http://central.sonatype.org/pages/apache-maven.html) before performing any of the following:
-
-### Snapshot Deployment
- $ mvn clean deploy
-
-#### Staging
- $ mvn release:clean release:prepare -Prelease
- $ mvn release:perform -Prelease
-
-#### Release
-
-Go to https://oss.sonatype.org/#stagingRepositories and Close the `ioprometheus-XXX` release.
-Once it's closed, Release it. Wait for the new version to appear in
-[The Central Repository](http://search.maven.org/#search%7Cga%7C1%7Cg%3A%22io.prometheus%22).
-
-Send an email to the developer's mailing list announcing the release.
-
-
-#### Documentation
-Documentation can also be released to the public via the Github Pages subproduct
-through the magic _gh-pages_ branch for a Github project. Documentation is
-generated via the following command:
-
- $ mvn javadoc:aggregate
-It will need to be automatically merged into the _gh-pages_ branch, but that is
-as simple as this:
+[https://prometheus.github.io/client_java](https://prometheus.github.io/client_java)
- $ git checkout master
- $ mvn javadoc:aggregate
- $ git checkout gh-pages
- $ mv target/site/apidocs/ ./
- $ git status
- $ # Amend the branch as necessary.
- $ git commit
- $ git push
+## Contributing and community
-There is a Maven plugin to perform this work, but it is broken. The
-javadoc:aggregate step will emit documentation into
-_target/site/apidocs_. The reason that we use this aggregate step instead
-of bare javadoc is that we want one comprehensive Javadoc emission that includes
-all Maven submodules versus trying to manually concatenate this together.
+See [CONTRIBUTING.md](CONTRIBUTING.md) and
+the [community section](http://prometheus.io/community/)
+of the Prometheus homepage.
-Output documentation lives in the [Java Client Github Project
-Page](http://prometheus.github.io/client_java).
+The Prometheus Java community is present on the [CNCF Slack](https://cloud-native.slack.com) on
+`#prometheus-java`, and we have a fortnightly community call in
+the [Prometheus public calendar](https://prometheus.io/community/).
+## Previous Releases
-## Contact
- * All of the core developers are accessible via the [Prometheus Developers Mailinglist](https://groups.google.com/forum/?fromgroups#!forum/prometheus-developers).
+The source code for 0.16.0 and older is on
+the [simpleclient](https://github.com/prometheus/client_java/tree/simpleclient) branch.
+## License
-[](https://travis-ci.org/prometheus/client_java)
+Apache License 2.0, see [LICENSE](LICENSE).
diff --git a/RELEASING.md b/RELEASING.md
new file mode 100644
index 000000000..c5ae0ed4b
--- /dev/null
+++ b/RELEASING.md
@@ -0,0 +1,79 @@
+# Releasing Instructions for Prometheus Java Client
+
+Releases are automated via
+[release-please](https://github.com/googleapis/release-please).
+
+## 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 Sonatype Central Token is Invalid
+
+The release workflow verifies the token before deploy. If it fails:
+
+1. Sign in at and open
+ View Account -> Generate User Token.
+2. Copy the `username` and `password` values from the snippet.
+3. Update the secrets:
+ -
+ -
+4. Verify locally:
+
+ ```shell
+ curl -i -u "$USER:$PASS" \
+ "https://central.sonatype.com/api/v1/publisher/status?id=test"
+ ```
+
+ `{"error":{"message":"Invalid token"}}` means the token is still
+ wrong. Any other response (including 404 for the test id) means the
+ token works.
+
+## 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:
+
diff --git a/SECURITY.md b/SECURITY.md
new file mode 100644
index 000000000..5e6f976db
--- /dev/null
+++ b/SECURITY.md
@@ -0,0 +1,6 @@
+# Reporting a security issue
+
+The Prometheus security policy, including how to report vulnerabilities, can be
+found here:
+
+[https://prometheus.io/docs/operating/security/](https://prometheus.io/docs/operating/security/)
diff --git a/benchmark/README.md b/benchmark/README.md
deleted file mode 100644
index 628a4d444..000000000
--- a/benchmark/README.md
+++ /dev/null
@@ -1,161 +0,0 @@
-# Client Benchmarks
-
-This module contains microbenchmarks for client instrumentation operations.
-
-## Result Overview
-
-The main outcomes of the benchmarks:
-* Simpleclient Counters/Gauges have similar performance to Codahale Counters.
-* The original client is much slower than the Simpleclient or Codahale, especially when used concurrently.
-* Codahale Meters are slower than Codahale/Simpleclient Counters
-* Codahale and original client Summaries are 10x slower than other metrics.
-* Simpleclient Histograms are 10-100X faster than Codahale and original client Summaries.
-* Simpleclient `Gauge.Child.set` is relatively slow, especially when done concurrently.
-* Label lookups in both Prometheus clients are relatively slow.
-
-Accordingly, in terms of client instrumentation performance I suggest the following:
-* It's cheap to extensively instrument your code with Simpleclient Counters/Gauges/Summaries without labels, or Codahale Counters.
-* Avoid Codahale Meters, in favour of Codahale/Simpleclient Counters and calculating the rate in your monitoring system (e.g. the `rate()` function in Prometheus).
-* Use Simpleclient Histograms rather than original client Summaries and Codahale Histograms/Timers.
-* Avoid the original client.
-* For high update rate (>1000 per second) prometheus metrics using labels, you should cache the Child. Java 8 may make this better due to an improved ConcurrentHashMap implementation.
-* If a use case appears for high update rate use of SimpleClient's `Gauge.Child.set`, we should alter `DoubleAdder` to more efficiently handle this use case.
-
-## Benchmark Results
-
-These benchmarks were run using JMH on a 2-core MacBook Pro with a 2.5GHz i5 processor,
-with Oracle Java 64 1.7.0\_51.
-
-### Counters
- java -jar target/benchmarks.jar CounterBenchmark -wi 5 -i 5 -f 1 -t 1
- i.p.b.CounterBenchmark.codahaleCounterIncBenchmark avgt 5 11.554 ± 0.251 ns/op
- i.p.b.CounterBenchmark.codahaleMeterMarkBenchmark avgt 5 75.305 ± 7.147 ns/op
- i.p.b.CounterBenchmark.prometheusCounterChildIncBenchmark avgt 5 13.249 ± 0.029 ns/op
- i.p.b.CounterBenchmark.prometheusCounterIncBenchmark avgt 5 127.397 ± 4.072 ns/op
- i.p.b.CounterBenchmark.prometheusSimpleCounterChildIncBenchmark avgt 5 12.989 ± 0.285 ns/op
- i.p.b.CounterBenchmark.prometheusSimpleCounterIncBenchmark avgt 5 54.822 ± 7.994 ns/op
- i.p.b.CounterBenchmark.prometheusSimpleCounterNoLabelsIncBenchmark avgt 5 13.131 ± 1.661 ns/op
-
- java -jar target/benchmarks.jar CounterBenchmark -wi 5 -i 5 -f 1 -t 2
- i.p.b.CounterBenchmark.codahaleCounterIncBenchmark avgt 5 16.707 ± 2.116 ns/op
- i.p.b.CounterBenchmark.codahaleMeterMarkBenchmark avgt 5 107.346 ± 23.127 ns/op
- i.p.b.CounterBenchmark.prometheusCounterChildIncBenchmark avgt 5 41.912 ± 18.167 ns/op
- i.p.b.CounterBenchmark.prometheusCounterIncBenchmark avgt 5 170.860 ± 5.110 ns/op
- i.p.b.CounterBenchmark.prometheusSimpleCounterChildIncBenchmark avgt 5 17.782 ± 2.764 ns/op
- i.p.b.CounterBenchmark.prometheusSimpleCounterIncBenchmark avgt 5 89.656 ± 4.577 ns/op
- i.p.b.CounterBenchmark.prometheusSimpleCounterNoLabelsIncBenchmark avgt 5 16.109 ± 1.723 ns/op
-
- java -jar target/benchmarks.jar CounterBenchmark -wi 5 -i 5 -f 1 -t 4
- i.p.b.CounterBenchmark.codahaleCounterIncBenchmark avgt 5 17.628 ± 0.501 ns/op
- i.p.b.CounterBenchmark.codahaleMeterMarkBenchmark avgt 5 121.836 ± 15.888 ns/op
- i.p.b.CounterBenchmark.prometheusCounterChildIncBenchmark avgt 5 377.916 ± 7.965 ns/op
- i.p.b.CounterBenchmark.prometheusCounterIncBenchmark avgt 5 250.919 ± 2.728 ns/op
- i.p.b.CounterBenchmark.prometheusSimpleCounterChildIncBenchmark avgt 5 18.055 ± 1.391 ns/op
- i.p.b.CounterBenchmark.prometheusSimpleCounterIncBenchmark avgt 5 120.543 ± 1.770 ns/op
- i.p.b.CounterBenchmark.prometheusSimpleCounterNoLabelsIncBenchmark avgt 5 19.334 ± 1.471 ns/op
-
-### Gauges
-
-Codahale lacks a metric with a `set` method, so we'll compare to `Counter` which has `inc` and `dec`.
-
- java -jar target/benchmarks.jar GaugeBenchmark -wi 5 -i 5 -f 1 -t 1
- i.p.b.GaugeBenchmark.codahaleCounterDecBenchmark avgt 5 11.620 ± 0.288 ns/op
- i.p.b.GaugeBenchmark.codahaleCounterIncBenchmark avgt 5 11.718 ± 0.333 ns/op
- i.p.b.GaugeBenchmark.prometheusGaugeChildDecBenchmark avgt 5 13.358 ± 0.554 ns/op
- i.p.b.GaugeBenchmark.prometheusGaugeChildIncBenchmark avgt 5 13.268 ± 0.276 ns/op
- i.p.b.GaugeBenchmark.prometheusGaugeChildSetBenchmark avgt 5 11.624 ± 0.210 ns/op
- i.p.b.GaugeBenchmark.prometheusGaugeDecBenchmark avgt 5 125.058 ± 2.764 ns/op
- i.p.b.GaugeBenchmark.prometheusGaugeIncBenchmark avgt 5 127.814 ± 7.741 ns/op
- i.p.b.GaugeBenchmark.prometheusGaugeSetBenchmark avgt 5 127.899 ± 6.690 ns/op
- i.p.b.GaugeBenchmark.prometheusSimpleGaugeChildDecBenchmark avgt 5 12.961 ± 0.393 ns/op
- i.p.b.GaugeBenchmark.prometheusSimpleGaugeChildIncBenchmark avgt 5 12.932 ± 0.212 ns/op
- i.p.b.GaugeBenchmark.prometheusSimpleGaugeChildSetBenchmark avgt 5 36.672 ± 1.112 ns/op
- i.p.b.GaugeBenchmark.prometheusSimpleGaugeDecBenchmark avgt 5 54.677 ± 3.704 ns/op
- i.p.b.GaugeBenchmark.prometheusSimpleGaugeIncBenchmark avgt 5 53.278 ± 1.104 ns/op
- i.p.b.GaugeBenchmark.prometheusSimpleGaugeSetBenchmark avgt 5 79.724 ± 2.723 ns/op
- i.p.b.GaugeBenchmark.prometheusSimpleGaugeNoLabelsDecBenchmark avgt 5 12.957 ± 0.437 ns/op
- i.p.b.GaugeBenchmark.prometheusSimpleGaugeNoLabelsIncBenchmark avgt 5 12.932 ± 0.284 ns/op
- i.p.b.GaugeBenchmark.prometheusSimpleGaugeNoLabelsSetBenchmark avgt 5 40.235 ± 1.735 ns/op
-
- java -jar target/benchmarks.jar GaugeBenchmark -wi 5 -i 5 -f 1 -t 2
- i.p.b.GaugeBenchmark.codahaleCounterDecBenchmark avgt 5 17.443 ± 4.819 ns/op
- i.p.b.GaugeBenchmark.codahaleCounterIncBenchmark avgt 5 14.882 ± 2.875 ns/op
- i.p.b.GaugeBenchmark.prometheusGaugeChildDecBenchmark avgt 5 45.206 ± 29.575 ns/op
- i.p.b.GaugeBenchmark.prometheusGaugeChildIncBenchmark avgt 5 46.657 ± 33.518 ns/op
- i.p.b.GaugeBenchmark.prometheusGaugeChildSetBenchmark avgt 5 21.810 ± 9.370 ns/op
- i.p.b.GaugeBenchmark.prometheusGaugeDecBenchmark avgt 5 177.370 ± 2.477 ns/op
- i.p.b.GaugeBenchmark.prometheusGaugeIncBenchmark avgt 5 172.136 ± 3.056 ns/op
- i.p.b.GaugeBenchmark.prometheusGaugeSetBenchmark avgt 5 186.791 ± 7.996 ns/op
- i.p.b.GaugeBenchmark.prometheusSimpleGaugeChildDecBenchmark avgt 5 15.978 ± 2.762 ns/op
- i.p.b.GaugeBenchmark.prometheusSimpleGaugeChildIncBenchmark avgt 5 15.457 ± 1.052 ns/op
- i.p.b.GaugeBenchmark.prometheusSimpleGaugeChildSetBenchmark avgt 5 156.604 ± 10.953 ns/op
- i.p.b.GaugeBenchmark.prometheusSimpleGaugeDecBenchmark avgt 5 107.134 ± 33.620 ns/op
- i.p.b.GaugeBenchmark.prometheusSimpleGaugeIncBenchmark avgt 5 89.362 ± 16.608 ns/op
- i.p.b.GaugeBenchmark.prometheusSimpleGaugeSetBenchmark avgt 5 163.823 ± 25.270 ns/op
- i.p.b.GaugeBenchmark.prometheusSimpleGaugeNoLabelsDecBenchmark avgt 5 16.380 ± 1.915 ns/op
- i.p.b.GaugeBenchmark.prometheusSimpleGaugeNoLabelsIncBenchmark avgt 5 17.042 ± 1.113 ns/op
- i.p.b.GaugeBenchmark.prometheusSimpleGaugeNoLabelsSetBenchmark avgt 5 164.930 ± 2.565 ns/op
-
- java -jar target/benchmarks.jar GaugeBenchmark -wi 5 -i 5 -f 1 -t 4
- i.p.b.GaugeBenchmark.codahaleCounterDecBenchmark avgt 5 17.291 ± 1.769 ns/op
- i.p.b.GaugeBenchmark.codahaleCounterIncBenchmark avgt 5 17.445 ± 0.709 ns/op
- i.p.b.GaugeBenchmark.prometheusGaugeChildDecBenchmark avgt 5 389.411 ± 13.078 ns/op
- i.p.b.GaugeBenchmark.prometheusGaugeChildIncBenchmark avgt 5 399.549 ± 29.274 ns/op
- i.p.b.GaugeBenchmark.prometheusGaugeChildSetBenchmark avgt 5 123.700 ± 3.894 ns/op
- i.p.b.GaugeBenchmark.prometheusGaugeDecBenchmark avgt 5 244.741 ± 22.477 ns/op
- i.p.b.GaugeBenchmark.prometheusGaugeIncBenchmark avgt 5 243.525 ± 6.332 ns/op
- i.p.b.GaugeBenchmark.prometheusGaugeSetBenchmark avgt 5 252.363 ± 2.664 ns/op
- i.p.b.GaugeBenchmark.prometheusSimpleGaugeChildDecBenchmark avgt 5 18.330 ± 2.673 ns/op
- i.p.b.GaugeBenchmark.prometheusSimpleGaugeChildIncBenchmark avgt 5 20.633 ± 1.219 ns/op
- i.p.b.GaugeBenchmark.prometheusSimpleGaugeChildSetBenchmark avgt 5 335.455 ± 4.562 ns/op
- i.p.b.GaugeBenchmark.prometheusSimpleGaugeDecBenchmark avgt 5 116.432 ± 4.793 ns/op
- i.p.b.GaugeBenchmark.prometheusSimpleGaugeIncBenchmark avgt 5 129.390 ± 2.360 ns/op
- i.p.b.GaugeBenchmark.prometheusSimpleGaugeSetBenchmark avgt 5 613.186 ± 20.548 ns/op
- i.p.b.GaugeBenchmark.prometheusSimpleGaugeNoLabelsDecBenchmark avgt 5 19.765 ± 3.189 ns/op
- i.p.b.GaugeBenchmark.prometheusSimpleGaugeNoLabelsIncBenchmark avgt 5 19.589 ± 1.634 ns/op
- i.p.b.GaugeBenchmark.prometheusSimpleGaugeNoLabelsSetBenchmark avgt 5 307.238 ± 1.918 ns/op
-
-### Summaries
-
-The simpleclient `Summary` doesn't have percentiles, simpleclient's `Histogram`
-offers a way to calculate percentiles on the server side that works with aggregation.
-The closest to the original client's `Summary` is Codahale's
-`Timer`, but that includes timing calls so we compare with `Histogram` instead.
-
- java -jar target/benchmarks.jar SummaryBenchmark -wi 5 -i 5 -f 1 -t 1
- i.p.b.SummaryBenchmark.codahaleHistogramBenchmark avgt 5 186.306 ± 4.958 ns/op
- i.p.b.SummaryBenchmark.prometheusSimpleHistogramBenchmark avgt 5 81.595 ± 4.491 ns/op
- i.p.b.SummaryBenchmark.prometheusSimpleHistogramChildBenchmark avgt 5 22.143 ± 1.713 ns/op
- i.p.b.SummaryBenchmark.prometheusSimpleHistogramNoLabelsBenchmark avgt 5 22.066 ± 0.812 ns/op
- i.p.b.SummaryBenchmark.prometheusSimpleSummaryBenchmark avgt 5 59.588 ± 2.087 ns/op
- i.p.b.SummaryBenchmark.prometheusSimpleSummaryChildBenchmark avgt 5 15.300 ± 0.659 ns/op
- i.p.b.SummaryBenchmark.prometheusSimpleSummaryNoLabelsBenchmark avgt 5 15.608 ± 0.271 ns/op
- i.p.b.SummaryBenchmark.prometheusSummaryBenchmark avgt 5 981.640 ± 315.146 ns/op
- i.p.b.SummaryBenchmark.prometheusSummaryChildBenchmark avgt 5 1155.179 ± 850.237 ns/op
-
- java -jar target/benchmarks.jar SummaryBenchmark -wi 5 -i 5 -f 1 -t 2
- i.p.b.SummaryBenchmark.codahaleHistogramBenchmark avgt 5 289.245 ± 39.721 ns/op
- i.p.b.SummaryBenchmark.prometheusSimpleHistogramBenchmark avgt 5 127.014 ± 19.285 ns/op
- i.p.b.SummaryBenchmark.prometheusSimpleHistogramChildBenchmark avgt 5 52.597 ± 10.781 ns/op
- i.p.b.SummaryBenchmark.prometheusSimpleHistogramNoLabelsBenchmark avgt 5 53.295 ± 9.891 ns/op
- i.p.b.SummaryBenchmark.prometheusSimpleSummaryBenchmark avgt 5 117.810 ± 11.694 ns/op
- i.p.b.SummaryBenchmark.prometheusSimpleSummaryChildBenchmark avgt 5 31.933 ± 3.439 ns/op
- i.p.b.SummaryBenchmark.prometheusSimpleSummaryNoLabelsBenchmark avgt 5 33.918 ± 5.571 ns/op
- i.p.b.SummaryBenchmark.prometheusSummaryBenchmark avgt 5 2059.498 ± 616.954 ns/op
- i.p.b.SummaryBenchmark.prometheusSummaryChildBenchmark avgt 5 2346.163 ± 1503.034 ns/op
-
- java -jar target/benchmarks.jar SummaryBenchmark -wi 5 -i 5 -f 1 -t 4
- i.p.b.SummaryBenchmark.codahaleHistogramBenchmark avgt 5 587.956 ± 2.788 ns/op
- i.p.b.SummaryBenchmark.prometheusSimpleHistogramBenchmark avgt 5 163.313 ± 5.163 ns/op
- i.p.b.SummaryBenchmark.prometheusSimpleHistogramChildBenchmark avgt 5 66.957 ± 1.746 ns/op
- i.p.b.SummaryBenchmark.prometheusSimpleHistogramNoLabelsBenchmark avgt 5 67.064 ± 1.681 ns/op
- i.p.b.SummaryBenchmark.prometheusSimpleSummaryBenchmark avgt 5 140.166 ± 4.263 ns/op
- i.p.b.SummaryBenchmark.prometheusSimpleSummaryChildBenchmark avgt 5 40.065 ± 0.138 ns/op
- i.p.b.SummaryBenchmark.prometheusSimpleSummaryNoLabelsBenchmark avgt 5 41.331 ± 1.899 ns/op
- i.p.b.SummaryBenchmark.prometheusSummaryBenchmark avgt 5 3950.152 ± 1214.866 ns/op
- i.p.b.SummaryBenchmark.prometheusSummaryChildBenchmark avgt 5 4676.946 ± 3625.977 ns/op
-
-Note the high error bars for the original client, it got slower with each iteration
-so I suspect a flaw in the test setup.
-
-
diff --git a/benchmark/pom.xml b/benchmark/pom.xml
deleted file mode 100644
index 78f2db669..000000000
--- a/benchmark/pom.xml
+++ /dev/null
@@ -1,94 +0,0 @@
-
-
- 4.0.0
-
-
- io.prometheus
- parent
- 0.0.11-SNAPSHOT
-
-
- io.prometheus
- benchmarks
-
- Prometheus Java Client Benchmarks
-
- Benchmarks of client performance, and comparison to other systems.
-
-
-
-
- The Apache Software License, Version 2.0
- http://www.apache.org/licenses/LICENSE-2.0.txt
- repo
-
-
-
-
-
- org.openjdk.jmh
- jmh-core
- 1.3.2
-
-
- org.openjdk.jmh
- jmh-generator-annprocess
- 1.3.2
-
-
-
- io.prometheus
- client
- 0.0.11-SNAPSHOT
-
-
- io.prometheus
- simpleclient
- 0.0.11-SNAPSHOT
-
-
- com.codahale.metrics
- metrics-core
- 3.0.2
-
-
-
-
-
- org.apache.maven.plugins
- maven-shade-plugin
- 2.2
-
-
- package
-
- shade
-
-
- benchmarks
-
-
- org.openjdk.jmh.Main
-
-
-
-
-
- *:*
-
- META-INF/*.SF
- META-INF/*.DSA
- META-INF/*.RSA
-
-
-
-
-
-
-
-
-
-
diff --git a/benchmarks/README.md b/benchmarks/README.md
new file mode 100644
index 000000000..3859eddea
--- /dev/null
+++ b/benchmarks/README.md
@@ -0,0 +1,87 @@
+# Benchmarks
+
+## 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
+```
+
+Run only one specific benchmark:
+
+```shell
+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/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
+
+concurrent updates of metrics in multi-threaded applications.
+If your application is single-threaded and uses only one processor core, your application isn't
+performance critical anyway.
+If your application is designed to use all available processor cores for maximum performance, then
+you want a metric library that doesn't slow your
+application down.
+Prometheus client Java metrics support concurrent updates and scrapes. This shows in benchmarks with
+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
+removed as soon as all benchmarks are ported to the 1.0.0 release.
diff --git a/benchmarks/pom.xml b/benchmarks/pom.xml
new file mode 100644
index 000000000..6d8c9c5c4
--- /dev/null
+++ b/benchmarks/pom.xml
@@ -0,0 +1,122 @@
+
+
+ 4.0.0
+
+
+ io.prometheus
+ client_java
+ 1.8.1-SNAPSHOT
+
+
+ benchmarks
+
+ Prometheus Java Client Benchmarks
+
+ Benchmarks of client performance, and comparison to other systems.
+
+
+
+ 1.37
+ 0.16.0
+ 3.0.2
+ true
+ true
+
+
+
+
+
+ io.opentelemetry.instrumentation
+ opentelemetry-instrumentation-bom-alpha
+ ${otel.instrumentation.version}
+ pom
+ import
+
+
+
+
+
+
+ org.openjdk.jmh
+ jmh-core
+ ${jmh.version}
+
+
+ io.prometheus
+ prometheus-metrics-core
+ ${project.version}
+
+
+ io.prometheus
+ prometheus-metrics-exposition-textformats
+ ${project.version}
+
+
+ io.prometheus
+ simpleclient
+ ${simpleclient.version}
+
+
+ com.codahale.metrics
+ metrics-core
+ ${codahale.version}
+
+
+ io.opentelemetry
+ opentelemetry-api
+
+
+ io.opentelemetry
+ opentelemetry-sdk
+
+
+ io.opentelemetry
+ opentelemetry-sdk-testing
+
+
+
+ ${project.artifactId}
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+
+ 1.8
+ 1.8
+
+
+ -parameters
+
+
+
+ org.openjdk.jmh
+ jmh-generator-annprocess
+ ${jmh.version}
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-shade-plugin
+
+
+ package
+
+ shade
+
+
+ benchmarks
+
+
+ io.prometheus.metrics.benchmarks.BenchmarkRunner
+
+
+
+
+
+
+
+
+
+
diff --git a/benchmarks/src/archive/java/io/prometheus/client/CKMSQuantileBenchmark.java b/benchmarks/src/archive/java/io/prometheus/client/CKMSQuantileBenchmark.java
new file mode 100644
index 000000000..ab383d327
--- /dev/null
+++ b/benchmarks/src/archive/java/io/prometheus/client/CKMSQuantileBenchmark.java
@@ -0,0 +1,132 @@
+package io.prometheus.client;
+
+import io.prometheus.client.CKMSQuantiles.Quantile;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Random;
+import java.util.concurrent.TimeUnit;
+import org.openjdk.jmh.annotations.*;
+import org.openjdk.jmh.infra.Blackhole;
+import org.openjdk.jmh.runner.Runner;
+import org.openjdk.jmh.runner.RunnerException;
+import org.openjdk.jmh.runner.options.Options;
+import org.openjdk.jmh.runner.options.OptionsBuilder;
+
+public class CKMSQuantileBenchmark {
+
+ @State(Scope.Benchmark)
+ public static class EmptyBenchmarkState {
+ @Param({"10000", "100000", "1000000"})
+ public int value;
+
+ List quantiles;
+ Random rand = new Random(0);
+
+ List shuffle;
+
+ Quantile mean = new Quantile(0.50, 0.050);
+ Quantile q90 = new Quantile(0.90, 0.010);
+ Quantile q95 = new Quantile(0.95, 0.005);
+ Quantile q99 = new Quantile(0.99, 0.001);
+
+ @Setup(Level.Trial)
+ public void setup() {
+ quantiles = new ArrayList();
+ quantiles.add(mean);
+ quantiles.add(q90);
+ quantiles.add(q95);
+ quantiles.add(q99);
+
+ shuffle = new ArrayList(value);
+ for (int i = 0; i < value; i++) {
+ shuffle.add((double) i);
+ }
+ Collections.shuffle(shuffle, rand);
+ }
+ }
+
+ @Benchmark
+ @BenchmarkMode({Mode.AverageTime})
+ @OutputTimeUnit(TimeUnit.MILLISECONDS)
+ public void ckmsQuantileInsertBenchmark(EmptyBenchmarkState state) {
+ CKMSQuantiles q = new CKMSQuantiles(state.quantiles.toArray(new Quantile[] {}));
+ for (Double l : state.shuffle) {
+ q.insert(l);
+ }
+ }
+
+ /** prefilled benchmark, means that we already have a filled and compressed samples available */
+ @State(Scope.Benchmark)
+ public static class PrefilledBenchmarkState {
+ @Param({"10000", "100000", "1000000"})
+ public int value;
+
+ CKMSQuantiles ckmsQuantiles;
+
+ List quantiles;
+ Random rand = new Random(0);
+
+ Quantile mean = new Quantile(0.50, 0.050);
+ Quantile q90 = new Quantile(0.90, 0.010);
+ Quantile q95 = new Quantile(0.95, 0.005);
+ Quantile q99 = new Quantile(0.99, 0.001);
+ List shuffle;
+
+ int rank = (int) (value * q95.quantile);
+
+ @Setup(Level.Trial)
+ public void setup() {
+ quantiles = new ArrayList();
+ quantiles.add(mean);
+ quantiles.add(q90);
+ quantiles.add(q95);
+ quantiles.add(q99);
+
+ shuffle = new ArrayList(value);
+ for (int i = 0; i < value; i++) {
+ shuffle.add((double) i);
+ }
+ Collections.shuffle(shuffle, rand);
+
+ ckmsQuantiles = new CKMSQuantiles(quantiles.toArray(new Quantile[] {}));
+ for (Double l : shuffle) {
+ ckmsQuantiles.insert(l);
+ }
+ // make sure we inserted all 'hanging' samples (count % 128)
+ ckmsQuantiles.get(0);
+ // compress everything so we have a similar samples size regardless of n.
+ ckmsQuantiles.compress();
+ System.out.println("Sample size is: " + ckmsQuantiles.samples.size());
+ }
+ }
+
+ @Benchmark
+ @BenchmarkMode({Mode.AverageTime})
+ @OutputTimeUnit(TimeUnit.NANOSECONDS)
+ public void ckmsQuantileGetBenchmark(Blackhole blackhole, PrefilledBenchmarkState state) {
+ blackhole.consume(state.ckmsQuantiles.get(state.q90.quantile));
+ }
+
+ /** benchmark for the f method. */
+ @Benchmark
+ @BenchmarkMode({Mode.AverageTime})
+ @OutputTimeUnit(TimeUnit.NANOSECONDS)
+ public void ckmsQuantileF(Blackhole blackhole, PrefilledBenchmarkState state) {
+ blackhole.consume(state.ckmsQuantiles.f(state.rank));
+ }
+
+ public static void main(String[] args) throws RunnerException {
+
+ Options opt =
+ new OptionsBuilder()
+ .include(CKMSQuantileBenchmark.class.getSimpleName())
+ .warmupIterations(5)
+ .measurementIterations(4)
+ .threads(1)
+ .forks(1)
+ .build();
+
+ new Runner(opt).run();
+ }
+}
diff --git a/benchmark/src/main/java/io/prometheus/benchmark/CounterBenchmark.java b/benchmarks/src/archive/java/io/prometheus/client/benchmark/CounterBenchmark.java
similarity index 59%
rename from benchmark/src/main/java/io/prometheus/benchmark/CounterBenchmark.java
rename to benchmarks/src/archive/java/io/prometheus/client/benchmark/CounterBenchmark.java
index ca644070c..c37076156 100644
--- a/benchmark/src/main/java/io/prometheus/benchmark/CounterBenchmark.java
+++ b/benchmarks/src/archive/java/io/prometheus/client/benchmark/CounterBenchmark.java
@@ -1,11 +1,10 @@
-package io.prometheus.benchmark;
+package io.prometheus.client.benchmark;
import com.codahale.metrics.MetricRegistry;
-
import java.util.concurrent.TimeUnit;
+import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.BenchmarkMode;
import org.openjdk.jmh.annotations.Mode;
-import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.OutputTimeUnit;
import org.openjdk.jmh.annotations.Scope;
import org.openjdk.jmh.annotations.Setup;
@@ -22,69 +21,47 @@ public class CounterBenchmark {
com.codahale.metrics.Counter codahaleCounter;
com.codahale.metrics.Meter codahaleMeter;
- io.prometheus.client.metrics.Counter prometheusCounter;
- io.prometheus.client.metrics.Counter.Child prometheusCounterChild;
io.prometheus.client.Counter prometheusSimpleCounter;
io.prometheus.client.Counter.Child prometheusSimpleCounterChild;
io.prometheus.client.Counter prometheusSimpleCounterNoLabels;
@Setup
public void setup() {
- prometheusCounter = io.prometheus.client.metrics.Counter.newBuilder()
- .name("name")
- .documentation("some description..")
- .build();
- prometheusCounterChild = prometheusCounter.newPartial().apply();
-
- prometheusSimpleCounter = io.prometheus.client.Counter.build()
- .name("name")
- .help("some description..")
- .labelNames("some", "group").create();
+ prometheusSimpleCounter =
+ io.prometheus.client.Counter.build()
+ .name("name")
+ .help("some description..")
+ .labelNames("some", "group")
+ .create();
prometheusSimpleCounterChild = prometheusSimpleCounter.labels("test", "group");
- prometheusSimpleCounterNoLabels = io.prometheus.client.Counter.build()
- .name("name")
- .help("some description..")
- .create();
+ prometheusSimpleCounterNoLabels =
+ io.prometheus.client.Counter.build().name("name").help("some description..").create();
registry = new MetricRegistry();
codahaleCounter = registry.counter("counter");
codahaleMeter = registry.meter("meter");
}
- @Benchmark
- @BenchmarkMode({Mode.AverageTime})
- @OutputTimeUnit(TimeUnit.NANOSECONDS)
- public void prometheusCounterIncBenchmark() {
- prometheusCounter.newPartial().apply().increment();
- }
-
- @Benchmark
- @BenchmarkMode({Mode.AverageTime})
- @OutputTimeUnit(TimeUnit.NANOSECONDS)
- public void prometheusCounterChildIncBenchmark() {
- prometheusCounterChild.increment();
- }
-
@Benchmark
@BenchmarkMode({Mode.AverageTime})
@OutputTimeUnit(TimeUnit.NANOSECONDS)
public void prometheusSimpleCounterIncBenchmark() {
- prometheusSimpleCounter.labels("test", "group").inc();
+ prometheusSimpleCounter.labels("test", "group").inc();
}
-
+
@Benchmark
@BenchmarkMode({Mode.AverageTime})
@OutputTimeUnit(TimeUnit.NANOSECONDS)
public void prometheusSimpleCounterChildIncBenchmark() {
- prometheusSimpleCounterChild.inc();
+ prometheusSimpleCounterChild.inc();
}
@Benchmark
@BenchmarkMode({Mode.AverageTime})
@OutputTimeUnit(TimeUnit.NANOSECONDS)
public void prometheusSimpleCounterNoLabelsIncBenchmark() {
- prometheusSimpleCounterNoLabels.inc();
+ prometheusSimpleCounterNoLabels.inc();
}
@Benchmark
@@ -103,13 +80,14 @@ public void codahaleMeterMarkBenchmark() {
public static void main(String[] args) throws RunnerException {
- Options opt = new OptionsBuilder()
- .include(CounterBenchmark.class.getSimpleName())
- .warmupIterations(5)
- .measurementIterations(4)
- .threads(4)
- .forks(1)
- .build();
+ Options opt =
+ new OptionsBuilder()
+ .include(CounterBenchmark.class.getSimpleName())
+ .warmupIterations(5)
+ .measurementIterations(4)
+ .threads(4)
+ .forks(1)
+ .build();
new Runner(opt).run();
}
diff --git a/benchmarks/src/archive/java/io/prometheus/client/benchmark/ExemplarsBenchmark.java b/benchmarks/src/archive/java/io/prometheus/client/benchmark/ExemplarsBenchmark.java
new file mode 100644
index 000000000..a3fa45ae1
--- /dev/null
+++ b/benchmarks/src/archive/java/io/prometheus/client/benchmark/ExemplarsBenchmark.java
@@ -0,0 +1,87 @@
+package io.prometheus.client.benchmark;
+
+import io.prometheus.client.Counter;
+import io.prometheus.client.exemplars.DefaultExemplarSampler;
+import io.prometheus.client.exemplars.tracer.common.SpanContextSupplier;
+import java.util.concurrent.TimeUnit;
+import org.openjdk.jmh.annotations.Benchmark;
+import org.openjdk.jmh.annotations.BenchmarkMode;
+import org.openjdk.jmh.annotations.Mode;
+import org.openjdk.jmh.annotations.OutputTimeUnit;
+import org.openjdk.jmh.annotations.Scope;
+import org.openjdk.jmh.annotations.Setup;
+import org.openjdk.jmh.annotations.State;
+
+@State(Scope.Benchmark)
+public class ExemplarsBenchmark {
+
+ private Counter counter;
+ private Counter counterWithExemplars;
+ private Counter counterWithoutExemplars;
+
+ @Setup
+ public void setup() {
+
+ counter =
+ Counter.build()
+ .name("counter_total")
+ .help("Total number of requests.")
+ .labelNames("path")
+ .create();
+
+ counterWithExemplars =
+ Counter.build()
+ .name("counter_with_exemplars_total")
+ .help("Total number of requests.")
+ .labelNames("path")
+ .withExemplarSampler(new DefaultExemplarSampler(new MockSpanContextSupplier()))
+ .create();
+
+ counterWithoutExemplars =
+ Counter.build()
+ .name("counter_without_exemplars_total")
+ .help("Total number of requests.")
+ .labelNames("path")
+ .withoutExemplars()
+ .create();
+ }
+
+ @Benchmark
+ @BenchmarkMode({Mode.AverageTime})
+ @OutputTimeUnit(TimeUnit.NANOSECONDS)
+ public void testCounter() {
+ counter.labels("test").inc();
+ }
+
+ @Benchmark
+ @BenchmarkMode({Mode.AverageTime})
+ @OutputTimeUnit(TimeUnit.NANOSECONDS)
+ public void testCounterWithExemplars() {
+ counterWithExemplars.labels("test").inc();
+ }
+
+ @Benchmark
+ @BenchmarkMode({Mode.AverageTime})
+ @OutputTimeUnit(TimeUnit.NANOSECONDS)
+ public void testCounterWithoutExemplars() {
+ counterWithoutExemplars.labels("test").inc();
+ }
+
+ private static class MockSpanContextSupplier implements SpanContextSupplier {
+
+ @Override
+ public String getTraceId() {
+ return "trace-id";
+ }
+
+ @Override
+ public String getSpanId() {
+ return "span-id";
+ }
+
+ @Override
+ public boolean isSampled() {
+ return true;
+ }
+ }
+}
diff --git a/benchmark/src/main/java/io/prometheus/benchmark/GaugeBenchmark.java b/benchmarks/src/archive/java/io/prometheus/client/benchmark/GaugeBenchmark.java
similarity index 55%
rename from benchmark/src/main/java/io/prometheus/benchmark/GaugeBenchmark.java
rename to benchmarks/src/archive/java/io/prometheus/client/benchmark/GaugeBenchmark.java
index d8037723f..a8eb03c83 100644
--- a/benchmark/src/main/java/io/prometheus/benchmark/GaugeBenchmark.java
+++ b/benchmarks/src/archive/java/io/prometheus/client/benchmark/GaugeBenchmark.java
@@ -1,11 +1,10 @@
-package io.prometheus.benchmark;
+package io.prometheus.client.benchmark;
import com.codahale.metrics.MetricRegistry;
-
import java.util.concurrent.TimeUnit;
+import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.BenchmarkMode;
import org.openjdk.jmh.annotations.Mode;
-import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.OutputTimeUnit;
import org.openjdk.jmh.annotations.Scope;
import org.openjdk.jmh.annotations.Setup;
@@ -21,69 +20,47 @@ public class GaugeBenchmark {
MetricRegistry registry;
com.codahale.metrics.Counter codahaleCounter;
- io.prometheus.client.metrics.Gauge prometheusGauge;
- io.prometheus.client.metrics.Gauge.Child prometheusGaugeChild;
io.prometheus.client.Gauge prometheusSimpleGauge;
io.prometheus.client.Gauge.Child prometheusSimpleGaugeChild;
io.prometheus.client.Gauge prometheusSimpleGaugeNoLabels;
@Setup
public void setup() {
- prometheusGauge = io.prometheus.client.metrics.Gauge.newBuilder()
- .name("name")
- .documentation("some description..")
- .build();
- prometheusGaugeChild = prometheusGauge.newPartial().apply();
-
- prometheusSimpleGauge = io.prometheus.client.Gauge.build()
- .name("name")
- .help("some description..")
- .labelNames("some", "group").create();
+ prometheusSimpleGauge =
+ io.prometheus.client.Gauge.build()
+ .name("name")
+ .help("some description..")
+ .labelNames("some", "group")
+ .create();
prometheusSimpleGaugeChild = prometheusSimpleGauge.labels("test", "group");
- prometheusSimpleGaugeNoLabels = io.prometheus.client.Gauge.build()
- .name("name")
- .help("some description..")
- .create();
+ prometheusSimpleGaugeNoLabels =
+ io.prometheus.client.Gauge.build().name("name").help("some description..").create();
registry = new MetricRegistry();
codahaleCounter = registry.counter("name");
}
// Increment.
- @Benchmark
- @BenchmarkMode({Mode.AverageTime})
- @OutputTimeUnit(TimeUnit.NANOSECONDS)
- public void prometheusGaugeIncBenchmark() {
- prometheusGauge.newPartial().apply().increment();
- }
-
- @Benchmark
- @BenchmarkMode({Mode.AverageTime})
- @OutputTimeUnit(TimeUnit.NANOSECONDS)
- public void prometheusGaugeChildIncBenchmark() {
- prometheusGaugeChild.increment();
- }
-
@Benchmark
@BenchmarkMode({Mode.AverageTime})
@OutputTimeUnit(TimeUnit.NANOSECONDS)
public void prometheusSimpleGaugeIncBenchmark() {
- prometheusSimpleGauge.labels("test", "group").inc();
+ prometheusSimpleGauge.labels("test", "group").inc();
}
-
+
@Benchmark
@BenchmarkMode({Mode.AverageTime})
@OutputTimeUnit(TimeUnit.NANOSECONDS)
public void prometheusSimpleGaugeChildIncBenchmark() {
- prometheusSimpleGaugeChild.inc();
+ prometheusSimpleGaugeChild.inc();
}
@Benchmark
@BenchmarkMode({Mode.AverageTime})
@OutputTimeUnit(TimeUnit.NANOSECONDS)
public void prometheusSimpleGaugeNoLabelsIncBenchmark() {
- prometheusSimpleGaugeNoLabels.inc();
+ prometheusSimpleGaugeNoLabels.inc();
}
@Benchmark
@@ -93,41 +70,26 @@ public void codahaleCounterIncBenchmark() {
codahaleCounter.inc();
}
-
// Decrement.
- @Benchmark
- @BenchmarkMode({Mode.AverageTime})
- @OutputTimeUnit(TimeUnit.NANOSECONDS)
- public void prometheusGaugeDecBenchmark() {
- prometheusGauge.newPartial().apply().decrement();
- }
-
- @Benchmark
- @BenchmarkMode({Mode.AverageTime})
- @OutputTimeUnit(TimeUnit.NANOSECONDS)
- public void prometheusGaugeChildDecBenchmark() {
- prometheusGaugeChild.decrement();
- }
-
@Benchmark
@BenchmarkMode({Mode.AverageTime})
@OutputTimeUnit(TimeUnit.NANOSECONDS)
public void prometheusSimpleGaugeDecBenchmark() {
- prometheusSimpleGauge.labels("test", "group").dec();
+ prometheusSimpleGauge.labels("test", "group").dec();
}
-
+
@Benchmark
@BenchmarkMode({Mode.AverageTime})
@OutputTimeUnit(TimeUnit.NANOSECONDS)
public void prometheusSimpleGaugeChildDecBenchmark() {
- prometheusSimpleGaugeChild.dec();
+ prometheusSimpleGaugeChild.dec();
}
@Benchmark
@BenchmarkMode({Mode.AverageTime})
@OutputTimeUnit(TimeUnit.NANOSECONDS)
public void prometheusSimpleGaugeNoLabelsDecBenchmark() {
- prometheusSimpleGaugeNoLabels.dec();
+ prometheusSimpleGaugeNoLabels.dec();
}
@Benchmark
@@ -138,27 +100,13 @@ public void codahaleCounterDecBenchmark() {
}
// Set.
- @Benchmark
- @BenchmarkMode({Mode.AverageTime})
- @OutputTimeUnit(TimeUnit.NANOSECONDS)
- public void prometheusGaugeSetBenchmark() {
- prometheusGauge.newPartial().apply().set(42);
- }
-
- @Benchmark
- @BenchmarkMode({Mode.AverageTime})
- @OutputTimeUnit(TimeUnit.NANOSECONDS)
- public void prometheusGaugeChildSetBenchmark() {
- prometheusGaugeChild.set(42);
- }
-
@Benchmark
@BenchmarkMode({Mode.AverageTime})
@OutputTimeUnit(TimeUnit.NANOSECONDS)
public void prometheusSimpleGaugeSetBenchmark() {
- prometheusSimpleGauge.labels("test", "group").set(42);
+ prometheusSimpleGauge.labels("test", "group").set(42);
}
-
+
@Benchmark
@BenchmarkMode({Mode.AverageTime})
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@@ -170,18 +118,19 @@ public void prometheusSimpleGaugeChildSetBenchmark() {
@BenchmarkMode({Mode.AverageTime})
@OutputTimeUnit(TimeUnit.NANOSECONDS)
public void prometheusSimpleGaugeNoLabelsSetBenchmark() {
- prometheusSimpleGaugeNoLabels.set(42);
+ prometheusSimpleGaugeNoLabels.set(42);
}
public static void main(String[] args) throws RunnerException {
- Options opt = new OptionsBuilder()
- .include(GaugeBenchmark.class.getSimpleName())
- .warmupIterations(5)
- .measurementIterations(4)
- .threads(4)
- .forks(1)
- .build();
+ Options opt =
+ new OptionsBuilder()
+ .include(GaugeBenchmark.class.getSimpleName())
+ .warmupIterations(5)
+ .measurementIterations(4)
+ .threads(4)
+ .forks(1)
+ .build();
new Runner(opt).run();
}
diff --git a/benchmarks/src/archive/java/io/prometheus/client/benchmark/SanitizeMetricNameBenchmark.java b/benchmarks/src/archive/java/io/prometheus/client/benchmark/SanitizeMetricNameBenchmark.java
new file mode 100644
index 000000000..cb5f300ce
--- /dev/null
+++ b/benchmarks/src/archive/java/io/prometheus/client/benchmark/SanitizeMetricNameBenchmark.java
@@ -0,0 +1,49 @@
+package io.prometheus.client.benchmark;
+
+import io.prometheus.client.Collector;
+import java.util.concurrent.TimeUnit;
+import org.openjdk.jmh.annotations.Benchmark;
+import org.openjdk.jmh.annotations.BenchmarkMode;
+import org.openjdk.jmh.annotations.Mode;
+import org.openjdk.jmh.annotations.OutputTimeUnit;
+import org.openjdk.jmh.annotations.Scope;
+import org.openjdk.jmh.annotations.State;
+import org.openjdk.jmh.runner.Runner;
+import org.openjdk.jmh.runner.RunnerException;
+import org.openjdk.jmh.runner.options.Options;
+import org.openjdk.jmh.runner.options.OptionsBuilder;
+import org.openjdk.jmh.runner.options.TimeValue;
+
+@State(Scope.Benchmark)
+public class SanitizeMetricNameBenchmark {
+
+ @Benchmark
+ @BenchmarkMode({Mode.AverageTime})
+ @OutputTimeUnit(TimeUnit.NANOSECONDS)
+ public void sanitizeSanitizedName() {
+ Collector.sanitizeMetricName("good_name");
+ }
+
+ @Benchmark
+ @BenchmarkMode({Mode.AverageTime})
+ @OutputTimeUnit(TimeUnit.NANOSECONDS)
+ public void sanitizeNonSanitizedName() {
+ Collector.sanitizeMetricName("9not_good_name!");
+ }
+
+ public static void main(String[] args) throws RunnerException {
+
+ Options opt =
+ new OptionsBuilder()
+ .include(SanitizeMetricNameBenchmark.class.getSimpleName())
+ .warmupIterations(5)
+ .measurementIterations(4)
+ .measurementTime(TimeValue.seconds(1))
+ .warmupTime(TimeValue.seconds(1))
+ .threads(4)
+ .forks(1)
+ .build();
+
+ new Runner(opt).run();
+ }
+}
diff --git a/benchmark/src/main/java/io/prometheus/benchmark/SummaryBenchmark.java b/benchmarks/src/archive/java/io/prometheus/client/benchmark/SummaryBenchmark.java
similarity index 60%
rename from benchmark/src/main/java/io/prometheus/benchmark/SummaryBenchmark.java
rename to benchmarks/src/archive/java/io/prometheus/client/benchmark/SummaryBenchmark.java
index 3a77dc5ec..2964b8ea8 100644
--- a/benchmark/src/main/java/io/prometheus/benchmark/SummaryBenchmark.java
+++ b/benchmarks/src/archive/java/io/prometheus/client/benchmark/SummaryBenchmark.java
@@ -1,11 +1,10 @@
-package io.prometheus.benchmark;
+package io.prometheus.client.benchmark;
import com.codahale.metrics.MetricRegistry;
-
import java.util.concurrent.TimeUnit;
+import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.BenchmarkMode;
import org.openjdk.jmh.annotations.Mode;
-import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.OutputTimeUnit;
import org.openjdk.jmh.annotations.Scope;
import org.openjdk.jmh.annotations.Setup;
@@ -21,8 +20,6 @@ public class SummaryBenchmark {
MetricRegistry registry;
com.codahale.metrics.Histogram codahaleHistogram;
- io.prometheus.client.metrics.Summary prometheusSummary;
- io.prometheus.client.metrics.Summary.Child prometheusSummaryChild;
io.prometheus.client.Summary prometheusSimpleSummary;
io.prometheus.client.Summary.Child prometheusSimpleSummaryChild;
io.prometheus.client.Summary prometheusSimpleSummaryNoLabels;
@@ -32,78 +29,58 @@ public class SummaryBenchmark {
@Setup
public void setup() {
- prometheusSummary = io.prometheus.client.metrics.Summary.newBuilder()
- .name("name")
- .documentation("some description..")
- .build();
- prometheusSummaryChild = prometheusSummary.newPartial().apply();
-
- prometheusSimpleSummary = io.prometheus.client.Summary.build()
- .name("name")
- .help("some description..")
- .labelNames("some", "group").create();
+ prometheusSimpleSummary =
+ io.prometheus.client.Summary.build()
+ .name("name")
+ .help("some description..")
+ .labelNames("some", "group")
+ .create();
prometheusSimpleSummaryChild = prometheusSimpleSummary.labels("test", "group");
- prometheusSimpleSummaryNoLabels = io.prometheus.client.Summary.build()
- .name("name")
- .help("some description..")
- .create();
+ prometheusSimpleSummaryNoLabels =
+ io.prometheus.client.Summary.build().name("name").help("some description..").create();
- prometheusSimpleHistogram = io.prometheus.client.Histogram.build()
- .name("name")
- .help("some description..")
- .labelNames("some", "group").create();
+ prometheusSimpleHistogram =
+ io.prometheus.client.Histogram.build()
+ .name("name")
+ .help("some description..")
+ .labelNames("some", "group")
+ .create();
prometheusSimpleHistogramChild = prometheusSimpleHistogram.labels("test", "group");
- prometheusSimpleHistogramNoLabels = io.prometheus.client.Histogram.build()
- .name("name")
- .help("some description..")
- .create();
+ prometheusSimpleHistogramNoLabels =
+ io.prometheus.client.Histogram.build().name("name").help("some description..").create();
registry = new MetricRegistry();
codahaleHistogram = registry.histogram("name");
}
- @Benchmark
- @BenchmarkMode({Mode.AverageTime})
- @OutputTimeUnit(TimeUnit.NANOSECONDS)
- public void prometheusSummaryBenchmark() {
- prometheusSummary.newPartial().apply().observe(1.0);
- }
-
- @Benchmark
- @BenchmarkMode({Mode.AverageTime})
- @OutputTimeUnit(TimeUnit.NANOSECONDS)
- public void prometheusSummaryChildBenchmark() {
- prometheusSummaryChild.observe(1.0);
- }
-
@Benchmark
@BenchmarkMode({Mode.AverageTime})
@OutputTimeUnit(TimeUnit.NANOSECONDS)
public void prometheusSimpleSummaryBenchmark() {
- prometheusSimpleSummary.labels("test", "group").observe(1) ;
+ prometheusSimpleSummary.labels("test", "group").observe(1);
}
@Benchmark
@BenchmarkMode({Mode.AverageTime})
@OutputTimeUnit(TimeUnit.NANOSECONDS)
public void prometheusSimpleSummaryChildBenchmark() {
- prometheusSimpleSummaryChild.observe(1);
+ prometheusSimpleSummaryChild.observe(1);
}
@Benchmark
@BenchmarkMode({Mode.AverageTime})
@OutputTimeUnit(TimeUnit.NANOSECONDS)
public void prometheusSimpleSummaryNoLabelsBenchmark() {
- prometheusSimpleSummaryNoLabels.observe(1);
+ prometheusSimpleSummaryNoLabels.observe(1);
}
@Benchmark
@BenchmarkMode({Mode.AverageTime})
@OutputTimeUnit(TimeUnit.NANOSECONDS)
public void prometheusSimpleHistogramBenchmark() {
- prometheusSimpleHistogram.labels("test", "group").observe(1) ;
+ prometheusSimpleHistogram.labels("test", "group").observe(1);
}
@Benchmark
@@ -129,13 +106,14 @@ public void codahaleHistogramBenchmark() {
public static void main(String[] args) throws RunnerException {
- Options opt = new OptionsBuilder()
- .include(SummaryBenchmark.class.getSimpleName())
- .warmupIterations(5)
- .measurementIterations(4)
- .threads(4)
- .forks(1)
- .build();
+ Options opt =
+ new OptionsBuilder()
+ .include(SummaryBenchmark.class.getSimpleName())
+ .warmupIterations(5)
+ .measurementIterations(4)
+ .threads(4)
+ .forks(1)
+ .build();
new Runner(opt).run();
}
diff --git a/benchmarks/src/main/java/io/prometheus/metrics/benchmarks/BenchmarkRunner.java b/benchmarks/src/main/java/io/prometheus/metrics/benchmarks/BenchmarkRunner.java
new file mode 100644
index 000000000..9d5d242ae
--- /dev/null
+++ b/benchmarks/src/main/java/io/prometheus/metrics/benchmarks/BenchmarkRunner.java
@@ -0,0 +1,7 @@
+package io.prometheus.metrics.benchmarks;
+
+public class BenchmarkRunner {
+ public static void main(String[] args) throws Exception {
+ org.openjdk.jmh.Main.main(args);
+ }
+}
diff --git a/benchmarks/src/main/java/io/prometheus/metrics/benchmarks/CounterBenchmark.java b/benchmarks/src/main/java/io/prometheus/metrics/benchmarks/CounterBenchmark.java
new file mode 100644
index 000000000..d8b75e437
--- /dev/null
+++ b/benchmarks/src/main/java/io/prometheus/metrics/benchmarks/CounterBenchmark.java
@@ -0,0 +1,206 @@
+package io.prometheus.metrics.benchmarks;
+
+import io.opentelemetry.api.OpenTelemetry;
+import io.opentelemetry.api.common.AttributeKey;
+import io.opentelemetry.api.common.Attributes;
+import io.opentelemetry.api.metrics.DoubleCounter;
+import io.opentelemetry.api.metrics.LongCounter;
+import io.opentelemetry.api.metrics.Meter;
+import io.opentelemetry.sdk.OpenTelemetrySdk;
+import io.opentelemetry.sdk.metrics.SdkMeterProvider;
+import io.opentelemetry.sdk.resources.Resource;
+import io.opentelemetry.sdk.testing.exporter.InMemoryMetricReader;
+import io.prometheus.metrics.core.datapoints.CounterDataPoint;
+import io.prometheus.metrics.core.metrics.Counter;
+import org.openjdk.jmh.annotations.Benchmark;
+import org.openjdk.jmh.annotations.Scope;
+import org.openjdk.jmh.annotations.State;
+import org.openjdk.jmh.annotations.Threads;
+
+/**
+ * Results on a machine with dedicated Ubuntu 24.04 LTS, AMD Ryzen™ 9 7900 × 24, 96.0 GiB RAM:
+ *
+ *
+ *
+ * The simpleclient (i.e. client_java version 0.16.0 and older) histograms perform about the same as
+ * the classic histogram of the current 1.0.0 version.
+ *
+ *
Compared to OpenTelemetry histograms the Prometheus Java client histograms perform more than 3
+ * times better (OpenTelemetry has 1908 ops / sec for classic histograms, while Prometheus has 6451
+ * ops / sec).
+ */
+public class HistogramBenchmark {
+
+ @State(Scope.Benchmark)
+ public static class PrometheusClassicHistogram {
+
+ final Histogram noLabels;
+
+ public PrometheusClassicHistogram() {
+ noLabels = Histogram.builder().name("test").help("help").classicOnly().build();
+ }
+ }
+
+ @State(Scope.Thread)
+ public static class PrometheusClassicHistogramPerThread {
+
+ final Histogram noLabels;
+
+ public PrometheusClassicHistogramPerThread() {
+ noLabels = Histogram.builder().name("test").help("help").classicOnly().build();
+ }
+ }
+
+ @State(Scope.Benchmark)
+ public static class PrometheusNativeHistogram {
+
+ final Histogram noLabels;
+
+ public PrometheusNativeHistogram() {
+ noLabels =
+ Histogram.builder()
+ .name("test")
+ .help("help")
+ .nativeOnly()
+ .nativeInitialSchema(5)
+ .nativeMaxNumberOfBuckets(0)
+ .build();
+ }
+ }
+
+ @State(Scope.Benchmark)
+ public static class SimpleclientHistogram {
+
+ final io.prometheus.client.Histogram noLabels;
+
+ public SimpleclientHistogram() {
+ noLabels = io.prometheus.client.Histogram.build().name("name").help("help").create();
+ }
+ }
+
+ @State(Scope.Benchmark)
+ public static class OpenTelemetryClassicHistogram {
+
+ final io.opentelemetry.api.metrics.DoubleHistogram histogram;
+
+ public OpenTelemetryClassicHistogram() {
+
+ SdkMeterProvider sdkMeterProvider =
+ SdkMeterProvider.builder()
+ .registerMetricReader(InMemoryMetricReader.create())
+ .setResource(Resource.getDefault())
+ .registerView(
+ InstrumentSelector.builder().setName("test").build(),
+ View.builder()
+ .setAggregation(
+ Aggregation.explicitBucketHistogram(
+ Arrays.asList(
+ .005, .01, .025, .05, .1, .25, .5, 1.0, 2.5, 5.0, 10.0)))
+ .build())
+ .build();
+ OpenTelemetry openTelemetry =
+ OpenTelemetrySdk.builder().setMeterProvider(sdkMeterProvider).build();
+ Meter meter =
+ openTelemetry
+ .meterBuilder("instrumentation-library-name")
+ .setInstrumentationVersion("1.0.0")
+ .build();
+ this.histogram = meter.histogramBuilder("test").setDescription("test").build();
+ }
+ }
+
+ @State(Scope.Benchmark)
+ public static class OpenTelemetryExponentialHistogram {
+
+ final io.opentelemetry.api.metrics.DoubleHistogram histogram;
+
+ public OpenTelemetryExponentialHistogram() {
+
+ SdkMeterProvider sdkMeterProvider =
+ SdkMeterProvider.builder()
+ .registerMetricReader(InMemoryMetricReader.create())
+ .setResource(Resource.getDefault())
+ .registerView(
+ InstrumentSelector.builder().setName("test").build(),
+ View.builder()
+ .setAggregation(Aggregation.base2ExponentialBucketHistogram(10_000, 5))
+ .build())
+ .build();
+ OpenTelemetry openTelemetry =
+ OpenTelemetrySdk.builder().setMeterProvider(sdkMeterProvider).build();
+ Meter meter =
+ openTelemetry
+ .meterBuilder("instrumentation-library-name")
+ .setInstrumentationVersion("1.0.0")
+ .build();
+ this.histogram = meter.histogramBuilder("test").setDescription("test").build();
+ }
+ }
+
+ @Benchmark
+ @Threads(4)
+ public Histogram prometheusClassic(
+ RandomNumbers randomNumbers, PrometheusClassicHistogram histogram) {
+ for (int i = 0; i < randomNumbers.randomNumbers.length; i++) {
+ histogram.noLabels.observe(randomNumbers.randomNumbers[i]);
+ }
+ return histogram.noLabels;
+ }
+
+ @Benchmark
+ @Threads(1)
+ public Histogram prometheusClassicSingleThread(
+ RandomNumbers randomNumbers, PrometheusClassicHistogram histogram) {
+ for (int i = 0; i < randomNumbers.randomNumbers.length; i++) {
+ histogram.noLabels.observe(randomNumbers.randomNumbers[i]);
+ }
+ return histogram.noLabels;
+ }
+
+ @Benchmark
+ @Threads(4)
+ public Histogram prometheusClassicPerThread(
+ RandomNumbers randomNumbers, PrometheusClassicHistogramPerThread histogram) {
+ for (int i = 0; i < randomNumbers.randomNumbers.length; i++) {
+ histogram.noLabels.observe(randomNumbers.randomNumbers[i]);
+ }
+ return histogram.noLabels;
+ }
+
+ @Benchmark
+ @Threads(4)
+ public Histogram prometheusNative(
+ RandomNumbers randomNumbers, PrometheusNativeHistogram histogram) {
+ for (int i = 0; i < randomNumbers.randomNumbers.length; i++) {
+ histogram.noLabels.observe(randomNumbers.randomNumbers[i]);
+ }
+ return histogram.noLabels;
+ }
+
+ @Benchmark
+ @Threads(4)
+ public io.prometheus.client.Histogram simpleclient(
+ RandomNumbers randomNumbers, SimpleclientHistogram histogram) {
+ for (int i = 0; i < randomNumbers.randomNumbers.length; i++) {
+ histogram.noLabels.observe(randomNumbers.randomNumbers[i]);
+ }
+ return histogram.noLabels;
+ }
+
+ @Benchmark
+ @Threads(4)
+ public io.opentelemetry.api.metrics.DoubleHistogram openTelemetryClassic(
+ RandomNumbers randomNumbers, OpenTelemetryClassicHistogram histogram) {
+ for (int i = 0; i < randomNumbers.randomNumbers.length; i++) {
+ histogram.histogram.record(randomNumbers.randomNumbers[i]);
+ }
+ return histogram.histogram;
+ }
+
+ @Benchmark
+ @Threads(4)
+ public io.opentelemetry.api.metrics.DoubleHistogram openTelemetryExponential(
+ RandomNumbers randomNumbers, OpenTelemetryExponentialHistogram histogram) {
+ for (int i = 0; i < randomNumbers.randomNumbers.length; i++) {
+ histogram.histogram.record(randomNumbers.randomNumbers[i]);
+ }
+ return histogram.histogram;
+ }
+}
diff --git a/benchmarks/src/main/java/io/prometheus/metrics/benchmarks/HistogramTextFormatBenchmark.java b/benchmarks/src/main/java/io/prometheus/metrics/benchmarks/HistogramTextFormatBenchmark.java
new file mode 100644
index 000000000..47a34ac18
--- /dev/null
+++ b/benchmarks/src/main/java/io/prometheus/metrics/benchmarks/HistogramTextFormatBenchmark.java
@@ -0,0 +1,71 @@
+package io.prometheus.metrics.benchmarks;
+
+import io.prometheus.metrics.config.EscapingScheme;
+import io.prometheus.metrics.expositionformats.OpenMetricsTextFormatWriter;
+import io.prometheus.metrics.expositionformats.PrometheusTextFormatWriter;
+import io.prometheus.metrics.model.snapshots.ClassicHistogramBuckets;
+import io.prometheus.metrics.model.snapshots.HistogramSnapshot;
+import io.prometheus.metrics.model.snapshots.HistogramSnapshot.HistogramDataPointSnapshot;
+import io.prometheus.metrics.model.snapshots.Labels;
+import io.prometheus.metrics.model.snapshots.MetricSnapshots;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.concurrent.TimeUnit;
+import org.openjdk.jmh.annotations.Benchmark;
+import org.openjdk.jmh.annotations.Fork;
+import org.openjdk.jmh.annotations.Measurement;
+import org.openjdk.jmh.annotations.Warmup;
+
+/**
+ * Benchmarks for writing a classic histogram (10 label combinations × 12 buckets) to text formats.
+ * Output goes to /dev/null to isolate pure formatting CPU cost with zero IO overhead.
+ */
+@Fork(3)
+@Warmup(iterations = 5, time = 2, timeUnit = TimeUnit.SECONDS)
+@Measurement(iterations = 10, time = 2, timeUnit = TimeUnit.SECONDS)
+public class HistogramTextFormatBenchmark {
+
+ private static final MetricSnapshots SNAPSHOTS;
+
+ static {
+ double[] upperBounds = {
+ .005, .01, .025, .05, .1, .25, .5, 1.0, 2.5, 5.0, 10.0, Double.POSITIVE_INFINITY
+ };
+ Number[] counts = {1L, 2L, 3L, 4L, 5L, 6L, 7L, 8L, 9L, 10L, 11L, 12L};
+ ClassicHistogramBuckets buckets = ClassicHistogramBuckets.of(upperBounds, counts);
+
+ HistogramSnapshot.Builder builder =
+ HistogramSnapshot.builder().name("http_request_duration_seconds");
+
+ for (int i = 0; i < 10; i++) {
+ builder.dataPoint(
+ HistogramDataPointSnapshot.builder()
+ .classicHistogramBuckets(buckets)
+ .labels(Labels.of("status", "value_" + i))
+ .sum(123.456)
+ .createdTimestampMillis(1000L)
+ .build());
+ }
+
+ SNAPSHOTS = MetricSnapshots.of(builder.build());
+ }
+
+ private static final OpenMetricsTextFormatWriter OPEN_METRICS_TEXT_FORMAT_WRITER =
+ OpenMetricsTextFormatWriter.create();
+ private static final PrometheusTextFormatWriter PROMETHEUS_TEXT_FORMAT_WRITER =
+ PrometheusTextFormatWriter.create();
+
+ @Benchmark
+ public OutputStream openMetricsWriteToNull() throws IOException {
+ OutputStream nullOutputStream = TextFormatUtilBenchmark.NullOutputStream.INSTANCE;
+ OPEN_METRICS_TEXT_FORMAT_WRITER.write(nullOutputStream, SNAPSHOTS, EscapingScheme.ALLOW_UTF8);
+ return nullOutputStream;
+ }
+
+ @Benchmark
+ public OutputStream prometheusWriteToNull() throws IOException {
+ OutputStream nullOutputStream = TextFormatUtilBenchmark.NullOutputStream.INSTANCE;
+ PROMETHEUS_TEXT_FORMAT_WRITER.write(nullOutputStream, SNAPSHOTS, EscapingScheme.ALLOW_UTF8);
+ return nullOutputStream;
+ }
+}
diff --git a/benchmarks/src/main/java/io/prometheus/metrics/benchmarks/RandomNumbers.java b/benchmarks/src/main/java/io/prometheus/metrics/benchmarks/RandomNumbers.java
new file mode 100644
index 000000000..6778c4ea1
--- /dev/null
+++ b/benchmarks/src/main/java/io/prometheus/metrics/benchmarks/RandomNumbers.java
@@ -0,0 +1,18 @@
+package io.prometheus.metrics.benchmarks;
+
+import java.util.Random;
+import org.openjdk.jmh.annotations.Scope;
+import org.openjdk.jmh.annotations.State;
+
+@State(Scope.Thread)
+public class RandomNumbers {
+
+ final double[] randomNumbers = new double[10 * 1024];
+
+ public RandomNumbers() {
+ Random rand = new Random(0);
+ for (int i = 0; i < randomNumbers.length; i++) {
+ randomNumbers[i] = Math.abs(rand.nextGaussian());
+ }
+ }
+}
diff --git a/benchmarks/src/main/java/io/prometheus/metrics/benchmarks/TextFormatUtilBenchmark.java b/benchmarks/src/main/java/io/prometheus/metrics/benchmarks/TextFormatUtilBenchmark.java
new file mode 100644
index 000000000..19fb60bcb
--- /dev/null
+++ b/benchmarks/src/main/java/io/prometheus/metrics/benchmarks/TextFormatUtilBenchmark.java
@@ -0,0 +1,136 @@
+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;
+import io.prometheus.metrics.model.snapshots.GaugeSnapshot;
+import io.prometheus.metrics.model.snapshots.GaugeSnapshot.GaugeDataPointSnapshot;
+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.SummarySnapshot;
+import io.prometheus.metrics.model.snapshots.SummarySnapshot.SummaryDataPointSnapshot;
+import io.prometheus.metrics.model.snapshots.Unit;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import org.openjdk.jmh.annotations.Benchmark;
+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:
+ *
+ *
- * A {@link JsonSerializer} for converting {@link AtomicDouble} into an
- * acceptable value for {@link com.google.gson.Gson}.
- *
- *
- * @author matt.proud@gmail.com (Matt T. Proud)
- */
-@ThreadSafe
-class AtomicDoubleSerializer implements JsonSerializer {
- @Override
- public JsonElement serialize(final AtomicDouble src, final Type typeOfSrc,
- final JsonSerializationContext context) {
- return new JsonPrimitive(src.doubleValue());
- }
-}
diff --git a/client/src/main/java/io/prometheus/client/Prometheus.java b/client/src/main/java/io/prometheus/client/Prometheus.java
deleted file mode 100644
index 5c0bb1591..000000000
--- a/client/src/main/java/io/prometheus/client/Prometheus.java
+++ /dev/null
@@ -1,288 +0,0 @@
-/*
- * Copyright 2013 Prometheus Team Licensed 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.
- */
-
-package io.prometheus.client;
-
-import com.google.common.util.concurrent.AtomicDouble;
-import com.google.gson.Gson;
-import com.google.gson.GsonBuilder;
-import com.google.gson.JsonArray;
-import io.prometheus.client.metrics.Counter;
-import io.prometheus.client.metrics.Gauge;
-import io.prometheus.client.metrics.Metric;
-import io.prometheus.client.metrics.Summary;
-import io.prometheus.client.utility.Clock;
-import io.prometheus.client.utility.SystemClock;
-import org.reflections.Reflections;
-import org.reflections.scanners.FieldAnnotationsScanner;
-import org.reflections.util.ClasspathHelper;
-import org.reflections.util.ConfigurationBuilder;
-
-import javax.annotation.concurrent.ThreadSafe;
-import java.io.IOException;
-import java.io.OutputStream;
-import java.io.Writer;
-import java.lang.reflect.Field;
-import java.util.Collection;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.logging.Level;
-import java.util.logging.Logger;
-
-/**
- *
- * {@link Prometheus} manages the registration and exposition of
- * {@link io.prometheus.client.metrics.Metric} instances.
- *
- *
- *
- * You can apply the patterns from examples in the following classes' Javadocs:
- *
- *
- *
- * {@link Counter}
- *
- * {@link Gauge}
- *
- * {@link Summary}
- *
- *
- * Important: To initialize the whole stack, call
- * {@link Prometheus#defaultInitialize()} once somewhere in your main
- * function.
- *
- * @see io.prometheus.client.metrics.Metric
- * @author matt.proud@gmail.com (Matt T. Proud)
- */
-@ThreadSafe
-public class Prometheus {
- private static final Logger log = Logger.getLogger(Prometheus.class.getName());
-
- private static final Gson serializer = new GsonBuilder()
- .registerTypeAdapter(AtomicDouble.class, new AtomicDoubleSerializer())
- .registerTypeAdapter(Counter.class, new Counter.Serializer())
- .registerTypeAdapter(Gauge.class, new Gauge.Serializer())
- .registerTypeAdapter(Summary.class, new Summary.Serializer()).create();
-
- private static final Prometheus defaultPrometheus = new Prometheus();
-
- private final Clock clock = new SystemClock();
- private final ConcurrentHashMap metrics = new ConcurrentHashMap();
- private final ConcurrentHashMap preexpositionHooks =
- new ConcurrentHashMap();
-
- private void register(final Metric m) {
- final Metric existing = metrics.putIfAbsent(m, m);
-
- if (existing == null) {
- log.log(Level.FINE, String.format("Registered %s", m));
- } else {
- if (existing != m) {
- log.log(Level.WARNING, String.format(
- "Cannot register %s, because %s is registered in its place.", m, existing));
- }
- }
- }
-
- private void dumpProto(final OutputStream o) throws IOException {
- final long start = clock.nowMs();
-
- runPreexpositionHooks();
-
- final Counter.Partial requests = Telemetry.telemetryRequests.newPartial();
- final Summary.Partial latencies = Telemetry.telemetryGenerationLatencies.newPartial();
- try {
- for (final Metric m : metrics.keySet()) {
- m.dump().writeDelimitedTo(o);
- }
- requests.labelPair("result", "success");
- latencies.labelPair("result", "success");
- } catch (final IOException e) {
- requests.labelPair("result", "failure");
- latencies.labelPair("result", "failure");
- throw e;
- } catch (final RuntimeException e) {
- requests.labelPair("result", "failure");
- latencies.labelPair("result", "failure");
- throw e;
- } finally {
- final double duration = clock.nowMs() - start;
-
- requests.apply().increment();
- latencies.apply().observe(duration);
- }
- }
-
- @Deprecated
- private void dumpJson(final Writer writer) throws IOException {
- final long start = clock.nowMs();
-
- runPreexpositionHooks();
-
- final Counter.Partial requests = Telemetry.telemetryRequests.newPartial();
- final Summary.Partial latencies = Telemetry.telemetryGenerationLatencies.newPartial();
- try {
- final JsonArray array = new JsonArray();
- for (final Metric m : metrics.keySet()) {
- array.add(serializer.toJsonTree(m));
- }
- writer.write(array.toString());
- requests.labelPair("result", "success");
- latencies.labelPair("result", "success");
- } catch (final IOException e) {
- requests.labelPair("result", "failure");
- latencies.labelPair("result", "failure");
- throw e;
- } catch (final RuntimeException e) {
- requests.labelPair("result", "failure");
- latencies.labelPair("result", "failure");
- throw e;
- } finally {
- final double duration = clock.nowMs() - start;
-
- requests.apply().increment();
- latencies.apply().observe(duration);
- }
- }
-
- /**
- *
- * Register a {@link Metric} with Prometheus
- *
- */
- public static void defaultRegister(final Metric m) {
- defaultPrometheus.register(m);
- }
-
- /**
- *
- * Dump all metrics registered via {@link Register} to the provided
- * {@link Writer} in JSON.
- *
- * Dump all metrics registered via {@link Register} to the provided
- * {@link OutputStream} in varint-encoded record-length delimited Protocol
- * Buffer messages of {@link io.prometheus.client.Metrics.MetricFamily}.
- *
- */
- public static void defaultDumpProto(final OutputStream o) throws IOException {
- defaultPrometheus.dumpProto(o);
- }
-
- private Collection collectAnnotatedFields() {
- final Reflections reflections =
- new Reflections(new ConfigurationBuilder().setUrls(ClasspathHelper.forJavaClassPath())
- .setScanners(new FieldAnnotationsScanner()));
-
- return reflections.getFieldsAnnotatedWith(Register.class);
- }
-
- private void initialize() {
- final long start = clock.nowMs();
- final Gauge.Partial duration = Telemetry.initializeTime.newPartial();
-
- try {
- final Collection fields = collectAnnotatedFields();
- for (final Field field : fields) {
- final boolean wasAccessible = field.isAccessible();
- final String candidateName = field.getDeclaringClass().getCanonicalName();
- try {
- // Explicitly load the class to invoke any static blocks and
- // initializers.
- final Class> klass = Class.forName(candidateName);
- if (klass == null) {
- continue;
- }
-
- final Register annotation = field.getAnnotation(Register.class);
- if (annotation == null) {
- continue;
- }
-
- if (!wasAccessible) {
- field.setAccessible(true);
- }
-
- final Metric metric = (Metric) field.get(klass);
- register(metric);
- } catch (final ClassNotFoundException e) {
- System.err.printf("Could not find %s\n", candidateName);
- } catch (final IllegalAccessException e) {
- System.err.printf("Not allowed to access %s\n", field);
- } finally {
- if (!wasAccessible) {
- field.setAccessible(false);
- }
- }
- }
- duration.labelPair("result", "success");
- } catch (final RuntimeException e) {
- duration.labelPair("result", "failure");
- throw e;
- } finally {
- final float elapsed = clock.nowMs() - start;
- duration.apply().set(elapsed);
- }
- }
-
- private void addPreexpositionHook(final ExpositionHook h) {
- preexpositionHooks.putIfAbsent(h, this);
- }
-
- private void runPreexpositionHooks() {
- for (final ExpositionHook hook : preexpositionHooks.keySet()) {
- hook.run();
- }
- }
-
- /**
- *
- * Register all {@link Metric} instances and their derivatives according to
- * the classpath findability discussion in {@link Register}.
- *
- *
- *
- * Important Usage Notes:
- *
- *
- *
- *
Calling this is a prerequisite for successful Prometheus
- * usage, meaning if it is never called, no metrics will be exposed.
- *
It is recommended that it is invoked early in the cycle of the main
- * class' main block.
- * A management hook to be run prior to each metric exposition request.
- *
- */
- public static interface ExpositionHook extends Runnable {
- }
-}
diff --git a/client/src/main/java/io/prometheus/client/Register.java b/client/src/main/java/io/prometheus/client/Register.java
deleted file mode 100644
index 0ec206934..000000000
--- a/client/src/main/java/io/prometheus/client/Register.java
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * Copyright 2013 Prometheus Team Licensed 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.
- */
-
-package io.prometheus.client;
-
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-
-/**
- *
- * Register {@link io.prometheus.client.metrics.Metric} and their derivatives with the
- * {@link Prometheus} registrar across all runtime libraries and their dependencies and the
- * class path.
- *
- *
- *
- * The purpose of this runtime annotation is to provide a common fragment to search for
- * across an entire server's classpath for metrics to export. Due to nuances in the underlying
- * Java Virtual Machine implementations and behaviors around both class loading and initialization
- * (i.e., behaviors outside of Prometheus' and your direct control), not every class referenced in
- * an application's transitive closure and its classpath will be loaded and initialized, unless it
- * is referenced by a dependent type, which is itself referenced by a root type in the
- * application.
- *
- *
Not using this optional annotation may prevent expected telemetry from being
- * found and registered! This is to say, metric consumers may not find the metrics they
- * want without it. Each {@link io.prometheus.client.metrics.Metric} decorated with
- * {@link Register} will be found and registered for exposition across all libraries your server
- * depends on. This registration property can be beneficial for authors of shared
- * infrastructure libraries that are used by multiple teams!
- *
- *
- * @author matt.proud@gmail.com (Matt T. Proud)
- */
-@Target(value = {ElementType.FIELD})
-@Retention(RetentionPolicy.RUNTIME)
-public @interface Register {
-}
diff --git a/client/src/main/java/io/prometheus/client/Telemetry.java b/client/src/main/java/io/prometheus/client/Telemetry.java
deleted file mode 100644
index 49e4f2720..000000000
--- a/client/src/main/java/io/prometheus/client/Telemetry.java
+++ /dev/null
@@ -1,65 +0,0 @@
-/*
- * Copyright 2013 Prometheus Team Licensed 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.
- */
-
-package io.prometheus.client;
-
-import io.prometheus.client.metrics.Counter;
-import io.prometheus.client.metrics.Gauge;
-import io.prometheus.client.metrics.Summary;
-
-/**
- *
- * Standard telemetry for all Prometheus clients.
- *
- *
- * @author matt.proud@gmail.com (Matt T. Proud)
- */
-class Telemetry {
- @Register
- static final Gauge initializeTime = Gauge.newBuilder()
- .namespace("telemetry")
- .name("initialization_time_ms")
- .documentation("The time it took for the telemetry system to initialize.")
- .build();
-
- @Register
- static final Gauge serverStartTime = Gauge.newBuilder()
- .namespace("telemetry")
- .name("server_start_time_ms")
- .documentation("The time at which the server started.")
- .build();
-
- @Register
- static final Counter telemetryRequests = Counter.newBuilder()
- .namespace("telemetry")
- .name("requests_metrics_total")
- .documentation("A counter of the total requests made against the telemetry system.")
- .build();
-
- @Register
- static final Summary telemetryGenerationLatencies = Summary.newBuilder()
- .namespace("telemetry")
- .name("generation_latency_ms")
- .documentation("A histogram of telemetry generation latencies.")
- .targetQuantile(0.01, 0.05)
- .targetQuantile(0.05, 0.05)
- .targetQuantile(0.5, 0.05)
- .targetQuantile(0.9, 0.01)
- .targetQuantile(0.99, 0.001)
- .build();
-
- static {
- serverStartTime.newPartial().apply().set(System.currentTimeMillis() / 1000);
- }
-}
diff --git a/client/src/main/java/io/prometheus/client/metrics/Counter.java b/client/src/main/java/io/prometheus/client/metrics/Counter.java
deleted file mode 100644
index e85cab821..000000000
--- a/client/src/main/java/io/prometheus/client/metrics/Counter.java
+++ /dev/null
@@ -1,470 +0,0 @@
-/*
- * Copyright 2013 Prometheus Team Licensed 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.
- */
-
-package io.prometheus.client.metrics;
-
-import com.google.common.base.Optional;
-import com.google.common.util.concurrent.AtomicDouble;
-import com.google.gson.JsonArray;
-import com.google.gson.JsonElement;
-import com.google.gson.JsonObject;
-import com.google.gson.JsonSerializationContext;
-import com.google.gson.JsonSerializer;
-import io.prometheus.client.Metrics;
-import io.prometheus.client.utility.labels.Reserved;
-
-import javax.annotation.concurrent.NotThreadSafe;
-import javax.annotation.concurrent.ThreadSafe;
-import java.lang.reflect.Type;
-import java.util.List;
-import java.util.Map;
-
-/**
- *
- * {@link Counter} is a {@link Metric} that tracks the addition or subtraction
- * of a value from itself.
- *
- *
- *
- * Tallies: Number of people who walked through that door.
- *
- * Running Sums: Amount of money that has been brought through the door.
- *
- *
- *
- *
- * An example follows:
- *
- *
- *
- * {@code
- * package example;
- *
- * import io.prometheus.client.Prometheus;
- * import io.prometheus.client.Register;
- * import io.prometheus.client.metrics.Counter;
- *
- * public class CashRegister {
- * // Annotate this with "Register" if this class is not explicitly loaded
- * // by your project.
- * private static final Counter operations = Counter.newBuilder()
- * .namespace("cash_register")
- * .name("operations")
- * .labelNames("operation", "result")
- * .documentation("Cash register operations partitioned by type and outcome.")
- * .build()
- *
- * public float divide(float dividend, float divisor) {
- * Counter.Partial result = operations.newPartial()
- * .labelPair("operation", "division");
- * try {
- * float f = dividend / divisor;
- * result.labelPair("result", "success");
- * return f;
- * } catch (ArithmeticException e) {
- * result.labelPair("result", "failure");
- * throw e;
- * } finally {
- * result.apply().increment();
- * }
- * }
- * }}
- *
- *
- *
- * Assuming that each code path is executed once, {@code operations} yields the following
- * child metrics:
- *
- * Create a {@link Builder} to configure the {@link Counter}.
- *
- */
- public static Builder newBuilder() {
- return new Builder();
- }
-
- /**
- *
- * Define the characteristics for this {@link Counter}.
- *
- *
- * Implementation-Specific Behaviors:
- *
- *
- *
- * If the metric and its children are reset, a default value of {@code 0} is
- * used.
- *
- *
- * For all other behaviors, see {@link Metric.BaseBuilder}.
- *
- */
- @ThreadSafe
- public static class Builder implements Metric.Builder {
- private static final Double DEFAULT_VALUE = Double.valueOf(0);
-
- private final Metric.BaseBuilder base;
- private final Optional defaultValue;
-
- Builder() {
- base = new Metric.BaseBuilder();
- defaultValue = Optional.absent();
- }
-
- private Builder(final BaseBuilder base, final Optional defaultValue) {
- this.base = base;
- this.defaultValue = defaultValue;
- }
-
- @Override
- public Builder labelNames(String... ds) {
- return new Builder(base.labelNames(ds), defaultValue);
- }
-
- @Override
- public Builder documentation(String d) {
- return new Builder(base.documentation(d), defaultValue);
- }
-
- @Override
- public Builder name(String n) {
- return new Builder(base.name(n), defaultValue);
- }
-
- @Override
- public Builder subsystem(String ss) {
- return new Builder(base.subsystem(ss), defaultValue);
- }
-
- @Override
- public Builder namespace(String ns) {
- return new Builder(base.namespace(ns), defaultValue);
- }
-
- @Override
- public Builder registerStatic(final boolean rs) {
- return new Builder(base.registerStatic(rs), defaultValue);
- }
-
- /**
- *
- * Provide a custom default value for this {@link Counter} when it undergoes
- * a {@link io.prometheus.client.metrics.Counter#resetAll()} or a specific
- * {@link Child} undergoes a {@link Counter.Child#reset()}.
- *
- *
- * @return A copy of the original {@link Builder} with the new
- * target value.
- */
- public Builder defaultValue(final Double v) {
- return new Builder(base, Optional.of(v));
- }
-
- private double getDefaultValue() {
- return defaultValue.or(DEFAULT_VALUE);
- }
-
- /**
- *
- * Generate a concrete {@link Counter} from this {@link Builder}.
- *
- */
- public Counter build() {
- final String name = base.buildName();
- final String docstring = base.buildDocstring();
-
- final Metrics.MetricFamily.Builder builder =
- Metrics.MetricFamily.newBuilder().setName(name).setHelp(docstring)
- .setType(Metrics.MetricType.COUNTER);
-
- return new Counter(name, docstring, base.buildLabelNames(), getDefaultValue(),
- builder.build(), base.getRegisterStatic());
- }
- }
-
- /**
- *
- * A derivative of {@link Counter} that lets you accumulate labels to build a
- * concrete metric via {@link #apply()} for mutation with the methods of
- * {@link Counter.Child}.
- *
- *
- *
- * Warning: All mutations to {@link Partial} are retained. You should not
- * share {@link Partial} between distinct label sets unless you have a parent
- * {@link Partial} that you {@link io.prometheus.client.metrics.Counter.Partial#clone()}.
- *
- *
- *
- * In this example below, we have both a race condition with a nasty outcome that
- * unformedMetric is mutated in both threads and that it is an undefined behavior, which
- * {@code data-type} label pair setting wins.
- *
- * A concrete instance of {@link Counter} for a unique set of label
- * dimensions.
- *
- *
- *
- * Warning: Do not hold onto a reference of a {@link Child} if you
- * ever use the {@link #resetAll()}. If you want to hold onto a concrete
- * instance, please hold onto a {@link io.prometheus.client.metrics.Counter.Partial} and use
- * {@link io.prometheus.client.metrics.Counter.Partial#apply()}.
- *
- *
- * @see Metric.Child
- */
- @ThreadSafe
- public class Child implements Metric.Child {
- final AtomicDouble value = new AtomicDouble();
-
- /**
- *
- * Increment this {@link Counter.Child} by one.
- *
- * Used to serialize {@link Counter} instances for Gson.
- *
- */
- @Deprecated
- public static class Serializer implements JsonSerializer {
- @Override
- public JsonElement serialize(final Counter src, final Type typeOfSrc,
- final JsonSerializationContext context) {
- final JsonObject container = new JsonObject();
- final JsonObject baseLabels = new JsonObject();
- baseLabels.addProperty(Reserved.NAME.label(), src.name);
-
- container.add(SERIALIZE_BASE_LABELS, baseLabels);
- container.addProperty(SERIALIZE_DOCSTRING, src.docstring);
-
- final JsonObject metric = new JsonObject();
- metric.addProperty("type", "counter");
- final JsonArray values = new JsonArray();
- for (final Map labelSet : src.children.keySet()) {
- final JsonObject element = new JsonObject();
- element.add("labels", context.serialize(labelSet));
- final Child vector = src.children.get(labelSet);
- element.add("value", context.serialize(vector.value.get()));
- values.add(element);
- }
- metric.add("value", values);
-
- container.add(SERIALIZE_METRIC, context.serialize(metric));
-
- return container;
- }
- }
-
- @Override
- public boolean equals(final Object o) {
- if (this == o) return true;
- if (!(o instanceof Counter)) return false;
- if (!super.equals(o)) return false;
-
- final Counter counter = (Counter) o;
-
- if (Double.compare(counter.defaultValue, defaultValue) != 0) return false;
-
- return true;
- }
-
- @Override
- public int hashCode() {
- int result = super.hashCode();
- long temp;
- temp = Double.doubleToLongBits(defaultValue);
- result = 31 * result + (int) (temp ^ (temp >>> 32));
- return result;
- }
-}
diff --git a/client/src/main/java/io/prometheus/client/metrics/Gauge.java b/client/src/main/java/io/prometheus/client/metrics/Gauge.java
deleted file mode 100644
index db01a186c..000000000
--- a/client/src/main/java/io/prometheus/client/metrics/Gauge.java
+++ /dev/null
@@ -1,493 +0,0 @@
-/*
- * Copyright 2013 Prometheus Team Licensed 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.
- */
-
-package io.prometheus.client.metrics;
-
-import com.google.common.base.Optional;
-import com.google.common.util.concurrent.AtomicDouble;
-import com.google.gson.JsonArray;
-import com.google.gson.JsonElement;
-import com.google.gson.JsonObject;
-import com.google.gson.JsonSerializationContext;
-import com.google.gson.JsonSerializer;
-import io.prometheus.client.Metrics;
-import io.prometheus.client.utility.labels.Reserved;
-
-import javax.annotation.concurrent.NotThreadSafe;
-import javax.annotation.concurrent.ThreadSafe;
-import java.lang.reflect.Type;
-import java.util.List;
-import java.util.Map;
-
-/**
- *
- * {@link Gauge} is a {@link Metric} that reports instantaneous values based on
- * external state.
- *
- *
- *
- *
- * Instantaneous value: The amount of money currently in the room. The value
- * comes from a blackbox system that can only return the state result; it does
- * not return how the state was derived.
- *
- *
- *
- * An example follows:
- *
- *
- *
- * {@code
- * package example;
- *
- * import io.prometheus.client.Prometheus;
- * import io.prometheus.client.Register;
- * import io.prometheus.client.metrics.Gauge;
- *
- * public class Aquarium {
- * // Annotate this with "Register" if this class is not explicitly loaded
- * // by your project.
- * private static final Gauge waterTemp = Gauge.newBuilder()
- * .namespace("seaworld")
- * .inSubsystem("aquatic_tanks")
- * .name("water_temperature_c")
- * .labelNames("tank_name")
- * .documentation("The current aquarium tank temperature partitioned by tank name.")
- * .build()
- *
- * public void run() {
- * while (true) {
- * // Busy loop. :sad-trombone:
- * waterTemp.newPartial()
- * .labelPair("tank_name", "shamu")
- * .apply()
- * .set(getShamuTemperature());
- *
- * waterTemp.newPartial()
- * .labelPair("tank_name", "urchin")
- * .apply()
- * .set(getUrchinTemperature());
- * }
- * }
- *
- * private double getShamuTemperature() {
- * // That poor orca's boiling alive!
- * return 42;
- * }
- *
- * private double getUrchinTemperature() {
- * return 9;
- * }
- * }}
- *
- *
- *
- * Assuming that each code path is executed once, {@code waterTemp} yields the following
- * child metrics:
- *
- * Create a {@link Builder} to configure the {@link Gauge}.
- *
- */
- public static Builder newBuilder() {
- return new Builder();
- }
-
- /**
- *
- * Define the characteristics for this {@link Gauge}.
- *
- *
- * Implementation-Specific Behaviors:
- *
- *
- *
- * If the metric and its children are reset, a default value of {@code 0} is
- * used.
- *
- *
- * For all other behaviors, see {@link Metric.BaseBuilder}.
- *
- */
- @ThreadSafe
- public static class Builder implements Metric.Builder {
- private static final Double DEFAULT_VALUE = Double.valueOf(0);
-
- private final BaseBuilder base;
- private final Optional defaultValue;
-
- Builder() {
- base = new BaseBuilder();
- defaultValue = Optional.absent();
- }
-
- private Builder(final BaseBuilder base, final Optional defaultValue) {
- this.base = base;
- this.defaultValue = defaultValue;
- }
-
- @Override
- public Builder labelNames(String... ds) {
- return new Builder(base.labelNames(ds), defaultValue);
- }
-
- @Override
- public Builder documentation(String d) {
- return new Builder(base.documentation(d), defaultValue);
- }
-
- @Override
- public Builder name(String n) {
- return new Builder(base.name(n), defaultValue);
- }
-
- @Override
- public Builder subsystem(String ss) {
- return new Builder(base.subsystem(ss), defaultValue);
- }
-
- @Override
- public Builder namespace(String ns) {
- return new Builder(base.namespace(ns), defaultValue);
- }
-
- @Override
- public Builder registerStatic(final boolean rs) {
- return new Builder(base.registerStatic(rs), defaultValue);
- }
-
- /**
- *
- * Provide a custom default value for this {@link Gauge} when it undergoes a
- * {@link io.prometheus.client.metrics.Gauge#resetAll()} or a specific
- * {@link Child} undergoes a {@link Gauge.Child#reset()}.
- *
- * @return A copy of the original {@link Builder} with the new
- * target value.
- */
- public Builder defaultValue(final Double v) {
- return new Builder(base, Optional.of(v));
- }
-
- private double getDefaultValue() {
- return defaultValue.or(DEFAULT_VALUE);
- }
-
- @Override
- public Gauge build() {
- final String name = base.buildName();
- final String docstring = base.buildDocstring();
-
- final Metrics.MetricFamily.Builder builder =
- Metrics.MetricFamily.newBuilder().setName(name).setHelp(docstring)
- .setType(Metrics.MetricType.GAUGE);
-
- return new Gauge(base.buildName(), base.buildDocstring(), base.buildLabelNames(),
- getDefaultValue(), builder.build(), base.getRegisterStatic());
- }
- }
-
- /**
- *
- * A derivative of {@link Gauge} that lets you accumulate labels to build a
- * concrete metric via {@link #apply()} for mutation with the methods of
- * {@link Gauge.Child}.
- *
- *
- *
- * Warning: All mutations to {@link Partial} are retained. You should not
- * share {@link Partial} between distinct label sets unless you have a parent
- * {@link Partial} that you {@link io.prometheus.client.metrics.Gauge.Partial#clone()}.
- *
- *
- *
- * In this example below, we have both a race condition with a nasty outcome that
- * unformedMetric is mutated in both threads and that it is an undefined behavior, which
- * {@code data-type} label pair setting wins.
- *
- * A concrete instance of {@link Gauge} for a unique set of label dimensions.
- *
- *
- *
- * Warning: Do not hold onto a reference of a {@link Child} if you
- * ever use the {@link #resetAll()}. If you want to hold onto a concrete
- * instance, please hold onto a {@link io.prometheus.client.metrics.Gauge.Partial} and use
- * {@link io.prometheus.client.metrics.Gauge.Partial#apply()}.
- *
- *
- * @see Metric.Child
- */
- @ThreadSafe
- public class Child implements Metric.Child {
- final AtomicDouble value = new AtomicDouble();
-
- /**
- *
- * Set this {@link io.prometheus.client.metrics.Gauge.Child} to an arbitrary
- * value.
- *
- * Used to serialize {@link Gauge} instances for {@link com.google.gson.Gson}.
- *
- */
- @Deprecated
- public static class Serializer implements JsonSerializer {
- @Override
- public JsonElement serialize(final Gauge src, final Type typeOfSrc,
- final JsonSerializationContext context) {
- final JsonObject container = new JsonObject();
- final JsonObject baseLabels = new JsonObject();
- baseLabels.addProperty(Reserved.NAME.label(), src.name);
-
- container.add(SERIALIZE_BASE_LABELS, baseLabels);
- container.addProperty(SERIALIZE_DOCSTRING, src.docstring);
-
- final JsonObject metric = new JsonObject();
- metric.addProperty("type", "gauge");
- final JsonArray values = new JsonArray();
- for (final Map labelSet : src.children.keySet()) {
- final JsonObject element = new JsonObject();
- element.add("labels", context.serialize(labelSet));
- final Child vector = src.children.get(labelSet);
- element.add("value", context.serialize(vector.value.get()));
- values.add(element);
- }
- metric.add("value", values);
-
- container.add(SERIALIZE_METRIC, context.serialize(metric));
-
- return container;
- }
- }
-
- @Override
- public boolean equals(final Object o) {
- if (this == o) return true;
- if (!(o instanceof Gauge)) return false;
- if (!super.equals(o)) return false;
-
- final Gauge gauge = (Gauge) o;
-
- if (Double.compare(gauge.defaultValue, defaultValue) != 0) return false;
-
- return true;
- }
-
- @Override
- public int hashCode() {
- int result = super.hashCode();
- long temp;
- temp = Double.doubleToLongBits(defaultValue);
- result = 31 * result + (int) (temp ^ (temp >>> 32));
- return result;
- }
-}
diff --git a/client/src/main/java/io/prometheus/client/metrics/Metric.java b/client/src/main/java/io/prometheus/client/metrics/Metric.java
deleted file mode 100644
index e069b4068..000000000
--- a/client/src/main/java/io/prometheus/client/metrics/Metric.java
+++ /dev/null
@@ -1,729 +0,0 @@
-/*
- * Copyright 2013 Prometheus Team Licensed 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.
- */
-
-package io.prometheus.client.metrics;
-
-import com.google.common.base.Optional;
-import com.google.common.base.Preconditions;
-import io.prometheus.client.Metrics;
-import io.prometheus.client.Prometheus;
-import net.jcip.annotations.Immutable;
-import net.jcip.annotations.NotThreadSafe;
-import net.jcip.annotations.ThreadSafe;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.TreeMap;
-import java.util.concurrent.ConcurrentHashMap;
-
-/**
- *
- * A {@link Metric} is the base type of all Prometheus metrics.
- *
- *
- *
- * Note: If you are using Prometheus, you do not need to be familiar with the internals of
- * this class.
- *
- *
- * @param The concrete implementation of {@link Metric}
- * @param The concrete implementation of {@link Metric.Child}
- * @param
- * Per decision
- * on the developer mailinglist, we have declared that no label names shall be allowed that
- * are prefixed with "__" are to be allowed, since they are used internally for private indices.
- *
- */
- private static final String PRIVATE_LABEL_NAMESPACE = "__";
-
- static final String SERIALIZE_BASE_LABELS = "baseLabels";
- static final String SERIALIZE_DOCSTRING = "docstring";
- static final String SERIALIZE_METRIC = "metric";
-
- private final List labelNames;
- private final Metrics.MetricFamily partial;
-
- final String name;
- final String docstring;
- final ConcurrentHashMap